forked from 1b-agent/opencadstudio-upstream
fix(mtext): correct imported-text size in the editor (#394)
The editor preview mis-sized imported MTEXT badly (tiny, huge, or collapsed). Two root causes: - The entity's base height often isn't the visible text height: relative `\H…x;` codes compound, so an entity of height 1 whose body is `\H0.2x` renders at 0.2. Normalize the preview scale by the DOMINANT rendered run height (the mode of glyph-box heights, skipping zero-width caret slots), not `height_value()`, so the body lands at a fixed readable size and headings keep their proportions. EM_PX = 15. - The bug that made it "completely wrong" was in the serializer, fixed in acadrust 389b31a (\H heights decayed on the parse→serialize→parse the editor does via folded_value); bumped here. Also render multi-column MTEXT with its columns side by side (as the drawing does) and let the preview scroll horizontally to reach them. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
57ff14d3f3
commit
4479ba8c29
2 files changed
+66
-11
No files matched your search
Generated
+2
-2
@@ -73,7 +73,7 @@ checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618"
|
||||
[[package]]
|
||||
name = "acadrust"
|
||||
version = "0.4.0"
|
||||
source = "git+https://github.com/OpenAEC-Foundation/acadifc?branch=main#9d2e67fb7bfc054281641228e3543e8ac26d7f08"
|
||||
source = "git+https://github.com/OpenAEC-Foundation/acadifc?branch=main#389b31ac5865e72f403d16d81eec958228065af0"
|
||||
dependencies = [
|
||||
"ahash 0.8.12",
|
||||
"anyhow",
|
||||
@@ -3179,7 +3179,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b577e2d69827c4740cba2b52efaad1c4cc7c73042860b199710b3575c68438d"
|
||||
dependencies = [
|
||||
"bytecount",
|
||||
"memchr 1.0.2",
|
||||
"memchr 2.8.3",
|
||||
"nom 8.0.0",
|
||||
]
|
||||
|
||||
|
||||
+64
-9
@@ -261,11 +261,13 @@ impl iced::widget::canvas::Program<Message> for MTextPreview {
|
||||
let collapsed = self.caret_on && self.sel.map(|(a, b)| a == b).unwrap_or(true);
|
||||
if collapsed && self.boxes.is_empty() {
|
||||
// Empty text: show a caret at the top-left so the user can type.
|
||||
// Its height matches the preview's constant em (see EM_PX) so the
|
||||
// empty-editor caret isn't tiny.
|
||||
let path = Path::new(|p| {
|
||||
p.move_to(iced::Point::new(MTEXT_PREVIEW_PAD, MTEXT_PREVIEW_PAD));
|
||||
p.line_to(iced::Point::new(
|
||||
MTEXT_PREVIEW_PAD,
|
||||
(MTEXT_PREVIEW_PAD + 22.0).min(self.content_h),
|
||||
(MTEXT_PREVIEW_PAD + 40.0).min(self.content_h),
|
||||
));
|
||||
});
|
||||
frame.stroke(
|
||||
@@ -570,15 +572,58 @@ pub(super) fn mtext_editor_overlay<'a>(
|
||||
maxx = maxx.max(b.xmax);
|
||||
maxy = maxy.max(b.ymax);
|
||||
}
|
||||
let h_unit = ed.height_value() as f32;
|
||||
// Real text size: fixed pixels per em so more/taller text grows the
|
||||
// canvas (and scrolls) instead of shrinking to fit.
|
||||
let scale = (22.0 / h_unit.max(1e-3)).clamp(2.0, 600.0);
|
||||
// Normalize the display by the DOMINANT rendered text height — the run
|
||||
// height the most glyphs share (the body) — NOT the entity's base height
|
||||
// field. That field can be many times the visible text: `\H…x;` runs
|
||||
// compound, so an entity of height 1 whose body is `\H0.2x` renders at
|
||||
// 0.2, and normalizing by 1 would shrink everything 5×. The mode lands
|
||||
// the body at EM_PX while headings / fine print keep their proportions
|
||||
// (one uniform factor, so no height is distorted). Zero-width boxes —
|
||||
// the empty-line caret slots, which carry the raw entity height — are
|
||||
// skipped so they can't skew it.
|
||||
let h_unit = {
|
||||
let mut heights: Vec<f32> = ed
|
||||
.glyph_boxes
|
||||
.iter()
|
||||
.filter(|b| b.xmax - b.xmin > 1e-6)
|
||||
.map(|b| b.ymax - b.ymin)
|
||||
.filter(|h| h.is_finite() && *h > 1e-6)
|
||||
.collect();
|
||||
if heights.is_empty() {
|
||||
ed.height_value() as f32
|
||||
} else {
|
||||
heights.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
|
||||
// Largest cluster of near-equal heights (within 5%) = the body.
|
||||
let (mut best_h, mut best_n, mut i) = (heights[0], 0usize, 0usize);
|
||||
while i < heights.len() {
|
||||
let mut j = i;
|
||||
while j < heights.len() && heights[j] <= heights[i] * 1.05 {
|
||||
j += 1;
|
||||
}
|
||||
if j - i > best_n {
|
||||
best_n = j - i;
|
||||
best_h = heights[(i + j - 1) / 2];
|
||||
}
|
||||
i = j;
|
||||
}
|
||||
best_h
|
||||
}
|
||||
};
|
||||
const EM_PX: f32 = 15.0;
|
||||
let scale = (EM_PX / h_unit.max(1e-6)).clamp(1e-4, 1e6);
|
||||
let content_h = if maxx >= minx {
|
||||
((maxy - miny) * scale + 2.0 * MTEXT_PREVIEW_PAD).max(40.0)
|
||||
} else {
|
||||
40.0
|
||||
};
|
||||
// Multi-column MTEXT lays its columns out side by side, so the content
|
||||
// can be wider than the editor; size the canvas to the real content
|
||||
// width and let the scroll area pan horizontally to reach later columns.
|
||||
let content_w = if maxx >= minx {
|
||||
(maxx - minx) * scale + 2.0 * MTEXT_PREVIEW_PAD
|
||||
} else {
|
||||
40.0
|
||||
};
|
||||
let prog = MTextPreview {
|
||||
segments,
|
||||
boxes: ed.glyph_boxes.clone(),
|
||||
@@ -591,11 +636,21 @@ pub(super) fn mtext_editor_overlay<'a>(
|
||||
content_h,
|
||||
};
|
||||
let cv = canvas(prog)
|
||||
.width(Fill)
|
||||
.width(iced::Length::Fixed(content_w))
|
||||
.height(iced::Length::Fixed(content_h));
|
||||
// The preview fills the space the toolbars leave, so the resizable
|
||||
// modal's extra height goes to the text area (it scrolls if taller).
|
||||
container(iced::widget::scrollable(cv).height(Fill))
|
||||
// The preview fills the space the toolbars leave; it scrolls in BOTH
|
||||
// directions so a taller-than-view text scrolls vertically and a
|
||||
// wider-than-view multi-column text scrolls horizontally.
|
||||
use iced::widget::scrollable::{Direction, Scrollbar};
|
||||
container(
|
||||
iced::widget::scrollable(cv)
|
||||
.direction(Direction::Both {
|
||||
vertical: Scrollbar::default(),
|
||||
horizontal: Scrollbar::default(),
|
||||
})
|
||||
.width(Fill)
|
||||
.height(Fill),
|
||||
)
|
||||
.style(move |_: &Theme| container::Style {
|
||||
background: Some(Background::Color(FIELD_BG)),
|
||||
border: Border {
|
||||
|
||||
Reference in new issue
Block a user