Lift the MText parser + renderer into a reusable `layout_mtext` helper in
`text_support` and re-point the remaining text-bearing entities at it, so
every inline format code (`\f`, `\C`/`\c`, `\H`, `\W`, `\Q`, `\T`, `\A`,
`\p…`, decorations, stacked fractions, paragraph indents, tab stops, …)
reaches the stroke output uniformly.
Per-entity changes:
- TABLE cell ([entities/table.rs]) — anchor + alignment math now feeds
`layout_mtext`; `\C7;Red`-style codes no longer render as literal
glyph noise.
- MLEADER MText content (both entity path in [entities/multileader.rs]
and the LOD-aware path in [scene/tessellate.rs]) — drops the
`strip_mtext_codes` + per-line `tessellate_text_ex` chain; the LOD 0
baseline / LOD 1 greek rect / LOD 2 stroke ladder and the text-frame /
bg-fill rectangle now read line widths from the layout.
- ATTRIB / ATTDEF — when `mtext_flag` is MultiLine / ConstantMultiLine
the value goes through the MText pipeline (SingleLine stays on the
Text-style anchor maths because the DXF spec disallows codes there).
- DIMENSION text_override — `dimension_text_entity` emits a synthetic
MText (instead of Text) whenever the value contains MText escapes;
the LOD ladder via `tessellate_entity_dim_text` handles both kinds.
Infrastructure:
- `MTextVAnchor` gains `MiddleOfTopLine`, `MiddleOfBottomLine`,
`BottomOfTopLine` so MLEADER's 9 attachment variants all map cleanly.
- `MTextLayout` returns `line_widths` / `line_height` / `v_offset` so
callers that draw chrome (frames, fills, baselines, greek rects)
share the same numbers as the rendered strokes.
- `mtext_line_count` re-routed through the new parser so LOD row counts
and rendered row counts can never disagree.
- Legacy helpers (`strip_mtext_codes`, `split_mtext_lines`,
`measure_mtext_chars`, `word_wrap`, `cxf::measure_text`, the
`v_offset_for_attachment` MLEADER offset table, `MTextLine::plain_text`)
deleted — all callers migrated.
Audit (TEXT_FORMAT_CODE_INTEGRATION.md) removed; its plan is implemented
here.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`text_obb_corners_native` derived MText line count from explicit `\n`
matches in `m.value`, so any MText that relied on `\P`/`\N` breaks or
word-wrap (rectangle_width-driven) reported a single line. The greek
emitter then split the OBB into one row and the user saw a single box
on top of a 5-line wrapped paragraph.
- text_support: new `mtext_line_count` — strip codes, split on
\n/\P/\N, word-wrap each paragraph against `rectangle_width`,
sum. Same line splitting the MText/MultiLeader renderers already
perform; centralized for reuse.
- text_obb_corners_native: takes `mtext_lines_override: Option<usize>`.
When supplied, OBB height = h_world × n × line_spacing_factor; this
also overrides a possibly stale `rectangle_height` on resaved DWGs.
- Top-level greek paths (scene/mod.rs) compute the line count via
`mtext_line_count` and pass it through to baseline / rect emission.
- block_cache: tessellate_sub_local computes the count at defn build
so the stored `text_obb_local` is wrap-aware; emit_text_baseline
and emit_greeked_text recover n_lines from `ulen / h_local` as
before and now produce the correct per-row split inside Inserts.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Previously \\U+XXXX was in the "strip until ;" list, silently dropping
all Unicode character references (°, ±, ⌀, Ø, €, …).
Changes:
- \\U+XXXX / \\u+XXXX decode to the actual Unicode character (was stripped)
- \\S upper/lower; stacked fractions/tolerances render as "upper/lower"
or "upper^lower" instead of leaking as literal text
- \\t tab → four spaces (was leaking as literal 't')
- Unknown escapes silently consume the code char (was leaking it)
- \\M+...; DBCS multibyte — stripped (added to semicolon-arg list)
- \\X paragraph-align end marker — stripped cleanly
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Line spacing: DXF code 44 (line_spacing_factor) is a multiplier on
AutoCAD's default baseline-to-baseline distance of 5/3 × text_height.
The renderer was using factor × 1.0 instead of factor × 1.667, making
all MTEXT appear cramped.
Blank paragraphs: split_mtext_lines was filtering out empty strings, so
\\P\\P (double paragraph break used to separate sections) was silently
collapsed. Empty lines in the middle of the block are now preserved;
only leading/trailing blank lines are stripped.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Spline entities now expose fit_points (or control_points when no fit
points are defined) as key_vertices, enabling Endpoint and Midpoint
snap to reach spline construction points.
TextStyle is_backward flag is applied by negating the width_factor,
producing horizontally mirrored text. is_upside_down rotates the text
180°. Both tessellate_text_ex and text_local_bounds now handle negative
width_factor without clamping it to zero.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Text/MText values now correctly handle DXF special-character sequences:
- %%d → °, %%p → ±, %%c → ⌀, %%nnn → Unicode scalar (resolved in tessellation)
- %%u / %%o toggle underline and overline strokes for TEXT entities
MText decoration codes (\L/\l, \O/\o, \K/\k) were previously consumed
as argument-bearing codes, silently eating the decorated text. Fixed
strip_mtext_codes to keep them as zero-width markers; tessellate_text_ex
now emits actual line strokes (underline, overline, strikethrough).
measure_mtext_chars and word_wrap skip markers so line widths are correct.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>