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

+1
View File
@@ -292,6 +292,7 @@ fn build_attr_truck(input: AttrTextInputs<'_>, document: &acadrust::CadDocument)
origin,
color: None,
fill_tris,
run: None,
});
}
let _ = input.line_count; // round-trip only — recomputed above
+43 -2
View File
@@ -6,7 +6,7 @@ use crate::entities::text_support::{
resolve_dxf_special_chars, resolve_text_style, text_local_bounds,
};
use crate::entities::traits::{Grippable, PropertyEditable, Transformable, TruckConvertible};
use crate::scene::convert::acad_to_truck::{TextStroke, TruckEntity, TruckObject};
use crate::scene::convert::acad_to_truck::{GlyphRun, TextStroke, TruckEntity, TruckObject};
use crate::scene::text::lff;
use crate::scene::model::object::{GripApply, GripDef, PropSection, PropValue, Property};
use crate::scene::model::wire_model::SnapHint;
@@ -66,9 +66,41 @@ pub struct TextPlacement {
pub wcs_insertion: [f64; 3],
}
/// Parse a TEXT value's `%%` control codes through acadrust's `parse_plain_text`
/// (the same parser MTEXT uses), then re-encode into the stroke tessellator's
/// inline grammar: specials arrive resolved to Unicode, and `%%u`/`%%o`
/// underline/overline become `\L…\l` / `\O…\o` decoration markers. This keeps
/// TEXT parsing in acadrust rather than OCS's own tokenizer.
fn acad_text_encode(value: &str) -> String {
use acadrust::entities::mtext_format::parse_plain_text;
let doc = parse_plain_text(value);
let mut out = String::new();
for para in &doc.paragraphs {
for span in &para.spans {
let (u, o) = (span.properties.underline(), span.properties.overline());
if u {
out.push_str("\\L");
}
if o {
out.push_str("\\O");
}
out.push_str(&span.text);
if o {
out.push_str("\\o");
}
if u {
out.push_str("\\l");
}
}
}
out
}
fn to_truck(t: &Text, document: &acadrust::CadDocument) -> TruckEntity {
let p = text_run_placement(t, document);
let snap_pt = glam::DVec3::new(p.wcs_insertion[0], p.wcs_insertion[1], p.wcs_insertion[2]);
// Parse `%%` codes via acadrust, re-encoded for the stroke tessellator.
let value = acad_text_encode(&p.value);
// Strokes are in glyph-local space (origin = [0,0]).
let (strokes, fill_tris) = lff::tessellate_text_ex(
[0.0, 0.0],
@@ -77,7 +109,7 @@ fn to_truck(t: &Text, document: &acadrust::CadDocument) -> TruckEntity {
p.width_factor,
p.oblique_angle,
&p.font,
&p.value,
&value,
);
TruckEntity {
object: TruckObject::Text(vec![TextStroke {
@@ -85,6 +117,15 @@ fn to_truck(t: &Text, document: &acadrust::CadDocument) -> TruckEntity {
origin: p.origin,
color: None,
fill_tris,
run: Some(GlyphRun {
text: value,
font: p.font.clone(),
height: p.height,
rotation: p.rotation,
width_factor: p.width_factor,
oblique: p.oblique_angle,
tracking: 1.0,
}),
}]),
snap_pts: vec![(snap_pt, SnapHint::Insertion)],
tangent_geoms: vec![],
+12 -1
View File
@@ -1,7 +1,7 @@
use acadrust::types::aci_table::aci_to_rgb;
use acadrust::{CadDocument, EntityType};
use crate::scene::convert::acad_to_truck::TextStroke;
use crate::scene::convert::acad_to_truck::{GlyphRun, TextStroke};
use crate::scene::text::font_face::Face;
use crate::scene::text::lff;
@@ -1175,6 +1175,17 @@ pub fn layout_mtext(opts: &MTextRenderOpts) -> MTextLayout {
origin,
color,
fill_tris,
// `text` is the plain word (specials already resolved,
// no decoration markers); `run_h` is raw like the strokes.
run: Some(GlyphRun {
text: text.clone(),
font: font_name.to_string(),
height: run_h,
rotation: rot,
width_factor: signed_wf,
oblique,
tracking,
}),
});
if opts.want_glyph_boxes {
// Per-character boxes, advancing exactly as
+1
View File
@@ -249,6 +249,7 @@ impl TruckConvertible for Tolerance {
origin,
color: None,
fill_tris: vec![],
run: None,
}]),
snap_pts: vec![(snap_pt, SnapHint::Insertion)],
tangent_geoms: vec![],