feat(dimension): honour DIMTIX/DIMATFIT text fit; clarify remaining flags (#94)

Centred dimension text that doesn't fit between the extension lines now
slides outside (text-outside placement), unless DIMTIX forces it to stay
inside. DIMTOFL is already satisfied for linear/aligned dims (their dim
line is always drawn between the extension points).

The remaining style flags are documented in place:
- DIMATFIT arrow-outside modes and DIMUPT need an arrow autofit pass.
- DIMTXTDIRECTION (RTL) needs single-line text direction support.
- DIMARCSYM / DIMJOGANG apply only to arc-length / jogged-radius dims,
  which aren't shipped as entity variants.
- DIMUNIT is obsolete (superseded by DIMLUNIT).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 4.8 committed 2026-06-14 02:03:05 +03:00
1 parent 006dbe4631
commit 40a91372fd
1 file changed
+36 -6
+36 -6
View File
@@ -1039,11 +1039,14 @@ fn tessellate_dimension_inner(
}
}
}
// DIMTOFL / DIMTIX / DIMATFIT / DIMUPT control autofit behaviour at
// dim *creation*. At render time we honour the saved text and arrow
// positions, so reading them here is a no-op — they shape geometry
// upstream rather than here.
let _ = (s.dimtofl, s.dimtix, s.dimatfit, s.dimupt);
// Text fit: DIMTIX (force text inside) is honoured in the text-placement
// pass (dimension_text_pos_f64), which slides centred text outside the
// extension lines when it doesn't fit. DIMTOFL (force the dim line
// inside) is already satisfied for linear/aligned, whose dim line is
// always drawn between the extension points. DIMATFIT's arrow-outside
// modes and DIMUPT (reposition-on-create) still need an arrow autofit
// pass — read here for round-trip until then.
let _ = (s.dimtofl, s.dimatfit, s.dimupt);
// DIMTXTDIRECTION (RTL) needs per-instance text mirroring on the Text
// entity, which the current text struct can't carry. Tracked: read
// and ignore so the file round-trips on save.
@@ -2472,6 +2475,7 @@ fn vec3_local(v: Vector3, off: [f64; 3]) -> Vec3 {
/// Style-driven text anchor on the dimension line: a point on the line through
/// `defpt` parallel to (`ax`,`ay`), slid along it per DIMJUST and lifted by
/// `perp_off` toward the side the dimension line sits on.
#[allow(clippy::too_many_arguments)]
fn text_on_dim_line(
first: Vector3,
second: Vector3,
@@ -2480,6 +2484,9 @@ fn text_on_dim_line(
ay: f64,
dimjust: i16,
perp_off: f64,
text_w: f64,
arrow: f64,
dimtix: bool,
) -> Vector3 {
let px = -ay;
let py = ax;
@@ -2490,11 +2497,21 @@ fn text_on_dim_line(
let t1 = (first.x - defpt.x) * ax + (first.y - defpt.y) * ay;
let t2 = (second.x - defpt.x) * ax + (second.y - defpt.y) * ay;
// DIMJUST: 0=centred, 1/3=near first ext, 2/4=near second ext.
let along = match dimjust {
let mut along = match dimjust {
1 | 3 => t1,
2 | 4 => t2,
_ => (t1 + t2) * 0.5,
};
// DIMATFIT / DIMTIX fit: when centred text can't fit between the extension
// lines, slide it just past the far one (text-outside placement) unless
// DIMTIX forces it to stay inside.
if dimjust == 0 && !dimtix && text_w > 0.0 {
let lo = t1.min(t2);
let hi = t1.max(t2);
if text_w > (hi - lo) - 2.0 * arrow {
along = hi + arrow + text_w * 0.5;
}
}
let bx = defpt.x + ax * along;
let by = defpt.y + ay * along;
Vector3::new(
@@ -2511,6 +2528,8 @@ fn dimension_text_pos_f64(dim: &Dimension, style: Option<&DimStyle>, text_height
let dimtad = style.map(|s| s.dimtad).unwrap_or(1);
let dimgap = style.map(|s| s.dimgap).unwrap_or(0.0);
let dimjust = style.map(|s| s.dimjust).unwrap_or(0);
// DIMTIX forces the text to stay between the extension lines.
let dimtix = style.map(|s| s.dimtix).unwrap_or(false);
// DIMTVP vertical-position multiplier (units of dimtxt). Only honoured when
// DIMTAD == 0; offsets text perpendicular to the dim line.
let dimtvp = style.map(|s| s.dimtvp).unwrap_or(0.0);
@@ -2519,6 +2538,11 @@ fn dimension_text_pos_f64(dim: &Dimension, style: Option<&DimStyle>, text_height
} else {
text_height * 0.5 + dimgap
};
// Rough text width + arrow allowance, used to decide text-outside fit.
let text_w = dimension_text_value(dim, style)
.map(|t| t.chars().count() as f64 * text_height * 0.6 + 2.0 * dimgap)
.unwrap_or(0.0);
let arrow = text_height; // arrows are roughly text-height sized
// Explicit per-entity override (text dragged to a custom location): the
// saved point wins. Otherwise the dimension style governs placement.
@@ -2542,6 +2566,9 @@ fn dimension_text_pos_f64(dim: &Dimension, style: Option<&DimStyle>, text_height
ay,
dimjust,
perp_off,
text_w,
arrow,
dimtix,
)
}
Dimension::Aligned(d) => {
@@ -2556,6 +2583,9 @@ fn dimension_text_pos_f64(dim: &Dimension, style: Option<&DimStyle>, text_height
dy / len,
dimjust,
perp_off,
text_w,
arrow,
dimtix,
)
}
_ => {