feat(text): parse TEXT via acadrust + extend SDF text to MTEXT

Two related steps for the text pipeline:

- TEXT parsing now goes through acadrust's parse_plain_text (like MTEXT
  uses parse_mtext). text::acad_text_encode parses the value's %% codes —
  including %%u/%%o underline/overline and %%nnn — and re-encodes to the
  stroke tessellator's \L/\O + resolved-Unicode grammar, so OCS no longer
  tokenizes TEXT %% codes itself. (ATTRIB/TOLERANCE/DIMENSION still use the
  local tokenizer for now.) Bumps the acadrust pin to the commit that
  completes the TEXT/MTEXT control-code set.

- SDF text (OCS_TEXT_SDF) now covers MTEXT and every text-producing
  entity, not just top-level TEXT. TextStroke carries a GlyphRun (layout
  params); the collector walks convert()'s TruckObject::Text runs and
  applies annotation scale exactly as tessellate does for strokes.
  layout_glyph_quads shares lff::tokenize_run so %% specials resolve and
  \L/\O decorations are skipped (decoration LINES in SDF are still TODO).

Verified: stroke rendering unchanged with the flag off; SDF glyphs render
correctly for TEXT and MTEXT with OCS_TEXT_SDF=1. 154 lib tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hakan SevenandClaude Opus 4.8 committed 2026-07-07 00:53:19 +03:00
1 parent b88e5e4ad5
commit d528a3e2a5
9 files changed
+149 -28

No files matched your search

+17 -7
View File
@@ -70,11 +70,22 @@ pub fn layout_glyph_quads(
let mut cursor_x = 0.0f32;
let mut quads = Vec::new();
for ch in text.chars() {
if ch == ' ' {
cursor_x += face.word_spacing();
continue;
}
// Same token walk as `tessellate_text_run` (LFF branch): DXF `%%` specials
// resolve to glyphs, spaces/missing advance the pen, decoration toggles emit
// no glyph (they render as lines on the stroke path).
for tok in crate::scene::text::lff::tokenize_run(text) {
let ch = match tok {
crate::scene::text::lff::Tok::Glyph(c) => c,
crate::scene::text::lff::Tok::Space => {
cursor_x += face.word_spacing();
continue;
}
crate::scene::text::lff::Tok::Missing => {
cursor_x += 6.0 + face.letter_spacing() * tracking;
continue;
}
crate::scene::text::lff::Tok::Deco(..) => continue,
};
match atlas.get_or_insert(font_name, ch) {
Some(e) => {
let (lo, hi) = (e.plane_min, e.plane_max);
@@ -91,8 +102,7 @@ pub fn layout_glyph_quads(
cursor_x += e.advance + face.letter_spacing() * tracking;
}
None => {
// No ink (whitespace glyph) or atlas full: advance only, using
// the glyph's own advance when the font knows it.
// No ink (whitespace glyph) or atlas full: advance only.
let adv = face.glyph(ch).map(|g| g.advance).unwrap_or(6.0);
cursor_x += adv + face.letter_spacing() * tracking;
}