feat(mtext): lay out \N columns and stack \S fractions

Two things an MTEXT can ask for that the layout ignored, both found on the
same drawing (budweiser2018.dwg handles 8782 and 2609).

Columns: `column_data` was read but only ever reached the properties panel,
so a three-column MTEXT ran as one stacked block. Each line now carries the
column it belongs to, the row counter restarts per column, and wrapping
measures against the column rather than the block — measuring against the
block would let a line run across the gutter and over its neighbour.
Paragraph alignment already worked, which is what made the bug read as
"columns are there but misplaced": `\pxqr;` pushed col3 to the right edge
either way.

Only explicit `\N` breaks move to the next column. A column that fills up
does not spill into the next on its own — `heights` / `auto_height` are not
read, so a column runs as long as its content.

Stacking: `\S1/2;` was flattened to the literal "1/2", which is why the
line then overflowed its 7.26-wide box and dropped `°C ª` onto a second
row. The halves now share one horizontal slot, drawn at a shrunk height
with the numerator lifted, so the slot is as wide as the wider half. `^`
stacks without a rule; `#` stays inline `num/den`, which is what a slanted
fraction reads as and needs no second baseline.

The fraction rule rides a run-less stroke group: a group carrying a
`GlyphRun` renders as SDF quads with its strokes suppressed, a run-less one
keeps them, and the rule is plain geometry rather than a glyph.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hakan SevenandClaude Opus 4.8 committed 2026-07-17 16:48:10 +03:00
1 parent 6d7eb94701
commit 7e1cd6842b
5 files changed
+305 -21

No files matched your search

+21 -1
View File
@@ -5,13 +5,31 @@ use crate::entities::common::{
edit_prop as edit, num_prop as num_row, ro_prop as ro, square_grip, triangle_grip,
};
use crate::entities::text_support::{
layout_mtext, resolve_text_style, GlyphBox, MTextRenderOpts, MTextVAnchor,
layout_mtext, resolve_text_style, GlyphBox, MTextColumns, MTextRenderOpts, MTextVAnchor,
};
use crate::entities::traits::{Grippable, PropertyEditable, Transformable, TruckConvertible};
use crate::scene::convert::acad_to_truck::{TruckEntity, TruckObject};
use crate::scene::model::object::{GripApply, GripDef, PropSection, PropValue, Property};
use crate::scene::model::wire_model::SnapHint;
/// The entity's `column_data` as the layout's column description.
///
/// `column_type` 0 means the MTEXT never opted into columns, so ignore whatever
/// stale width/count sit alongside it. Static (1) and dynamic (2) both flow at
/// the `\N` breaks in the value; the difference between them is how a *full*
/// column spills into the next, which the layout does not model.
fn columns_of(t: &MText) -> MTextColumns {
let c = &t.column_data;
if c.column_type == 0 {
return MTextColumns::default();
}
MTextColumns {
count: c.column_count.max(0) as usize,
width: c.width as f32,
gutter: c.gutter as f32,
}
}
/// Combined attachment point shown as a single justify dropdown value.
fn attachment_str(a: &AttachmentPoint) -> &'static str {
match a {
@@ -96,6 +114,7 @@ pub fn glyph_boxes(t: &MText, document: &acadrust::CadDocument) -> Vec<GlyphBox>
line_spacing_factor: t.line_spacing_factor as f32,
vertical_text: matches!(t.drawing_direction, DrawingDirection::TopToBottom),
want_glyph_boxes: true,
columns: columns_of(t),
});
layout.glyph_boxes
}
@@ -143,6 +162,7 @@ fn to_truck(t: &MText, document: &acadrust::CadDocument) -> TruckEntity {
line_spacing_factor: t.line_spacing_factor as f32,
vertical_text: matches!(t.drawing_direction, DrawingDirection::TopToBottom),
want_glyph_boxes: false,
columns: columns_of(t),
});
let insertion = glam::DVec3::new(
t.insertion_point.x,