fix(text): integrate ignored fields across text-bearing entities

- MultiLeader: stop double-scaling when ctx.text_height (WCS-baked) is set;
  use it as-is and only multiply by scale_factor + anno on the fallback path.
  ctx.text_width gets the same treatment.
- Text / Attribute: width_factor and oblique_angle are entity-authoritative.
  The previous entity*style multiplication / addition mis-rendered any text
  whose TXSTYLE had a non-default width_factor or oblique_angle (RR 0.6,
  STYLE2 15°, Romans/ARIEL/A32 0.65-0.8 in the sample file).
- MText: honour TXSTYLE is_backward (negative width_factor mirror) and
  is_upside_down (180° rotation). measure_mtext_chars uses positive
  magnitude so anchor math stays correct under backward text.
- Tolerance: use tol.dimension_gap when set instead of the hard-coded
  0.35 * height heuristic.
- Table: cell text height / font / alignment resolve through
  CellContent -> CellStyle -> TableStyle.{title|header|data}_row_style
  -> compiled-in defaults. Row classification respects the suppressed
  flags. Cell + content rotation applied.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Hakan 2026-05-21 12:38:03 +03:00
commit 62876b1081
7 changed files with 198 additions and 55 deletions

View file

@ -187,13 +187,14 @@ fn to_truck(ml: &MultiLeader, document: &acadrust::CadDocument) -> Option<TruckE
// here — this path is consumed by snap / truck export which work in WCS.
if ml.content_type == LeaderContentType::MText && !ml.context.text_string.is_empty() {
let ctx = &ml.context;
let raw_height = if ctx.text_height > 0.0 {
ctx.text_height
} else {
ml.text_height
} as f32;
// `ctx.text_height` (when > 0) is already in WCS — scale_factor is
// baked in. Only the fallback path needs to multiply.
let scale_factor = ml.scale_factor as f32;
let height = raw_height * scale_factor;
let height = if ctx.text_height > 0.0 {
ctx.text_height as f32
} else {
ml.text_height as f32 * scale_factor
};
let ins = &ctx.text_location;
let ins_x = ins.x;
@ -202,12 +203,11 @@ fn to_truck(ml: &MultiLeader, document: &acadrust::CadDocument) -> Option<TruckE
// Prefer text_direction (carries through rotations/mirrors); fall back
// to text_rotation when no direction has been set.
let td = ctx.text_direction;
let rot = if td.x.abs() > 1e-9 || td.y.abs() > 1e-9 {
let mut rot = if td.x.abs() > 1e-9 || td.y.abs() > 1e-9 {
(td.y as f32).atan2(td.x as f32)
} else {
ctx.text_rotation as f32
};
let (cos_r, sin_r) = (rot.cos(), rot.sin());
snap_pts.push(node([ins_x, ins_y, z]));
// Resolve text style via handle when available, falling back to STANDARD.
@ -225,16 +225,28 @@ fn to_truck(ml: &MultiLeader, document: &acadrust::CadDocument) -> Option<TruckE
let style = crate::entities::text_support::resolve_text_style(&style_name, document);
let font_name = style.font_name;
let font = crate::scene::cxf::get_font(&font_name);
let width_factor = style.width_factor.max(0.01);
// MultiLeader text inherits width factor / oblique from the style.
// is_backward flips the sign on width_factor; is_upside_down adds π
// to the text rotation — same convention as Text / MText.
let base_wf = style.width_factor.max(0.01);
let width_factor = if style.is_backward { -base_wf } else { base_wf };
let oblique = style.oblique_angle;
if style.is_upside_down {
rot += std::f32::consts::PI;
}
let (cos_r, sin_r) = (rot.cos(), rot.sin());
// Strip MText format codes (e.g. `{\fArial Black|b0|i0|c162|p34;...}`),
// then split on \P / \n / \N and optionally word-wrap to text_width.
let plain = crate::entities::text_support::strip_mtext_codes(&ctx.text_string);
let explicit_lines = crate::entities::text_support::split_mtext_lines(&plain);
let lines: Vec<String> = if ctx.text_width > 0.0 {
let scale = height / 9.0 * width_factor;
let max_w = ctx.text_width as f32 * scale_factor;
// Width-factor sign is a render-time mirror flag; measurement uses
// the magnitude only.
let scale = height / 9.0 * width_factor.abs();
// ctx.text_width is in the same WCS frame as ctx.text_height —
// do not re-multiply by scale_factor.
let max_w = ctx.text_width as f32;
explicit_lines
.iter()
.flat_map(|line| {
@ -261,7 +273,9 @@ fn to_truck(ml: &MultiLeader, document: &acadrust::CadDocument) -> Option<TruckE
let v_offset =
v_offset_for_attachment(ctx.text_left_attachment, n_lines, height, line_h);
let scale = height / 9.0 * width_factor;
// Line-width measurement uses positive magnitude; mirror sign feeds
// tessellate_text_ex below.
let scale = height / 9.0 * width_factor.abs();
for (i, line) in lines.iter().enumerate() {
let li = i as f32;
let line_y_local = -li * line_h + v_offset;