Files
cadcraft/crates/io/tests/text_left_justify.rs
Valentin KleinandClaude Opus 5.5 71706f8e6a TL/ML/BL text is placed by its alignment point (fixes #342) (#346)
For every justification but baseline-left (and aligned/fit) the DXF
Reference makes group 11 the point the text is placed by; group 10 is a
computed point on the baseline. Left-justified text with a vertical
alignment was drawn, measured and picked from group 10 and then shifted
by the alignment again: TL and ML too low, BL too high. The renderer
(fonts::place) and text extents (doc text_box, used for picking) now
place it from the alignment point; a mirrored (extrusion 0,0,-1) TL/ML/BL
text keeps its alignment point when it turns right-justified; and the
Properties position moves justified text by its alignment point.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 17:31:56 -04:00

47 lines
2.5 KiB
Rust

//! Left-justified TEXT with a vertical alignment (TL, ML, BL) is placed by its alignment point
//! (DXF group 11), like every justification but baseline-left; group 10 is then a computed point
//! on the baseline (#342).
use cadcraft_doc::{EntityKind, Space, entity_bounds};
/// One TEXT, height 1, "AB", group 72 = 0 and group 73 = `v`, with group 10 at `p10`, group 11 at
/// `p11`, and `extra` group codes.
fn dxf(v: i32, p10: (f64, f64), p11: (f64, f64), extra: &str) -> String {
format!(
"0\nSECTION\n2\nENTITIES\n0\nTEXT\n8\n0\n10\n{}\n20\n{}\n40\n1\n1\nAB\n72\n0\n11\n{}\n21\n{}\n{extra}73\n{v}\n0\nENDSEC\n0\nEOF\n",
p10.0, p10.1, p11.0, p11.1
)
}
/// (rendered box, extents box) of the drawing's only object, as (min x, min y, max x, max y).
fn boxes(text: &str) -> ([f64; 4], [f64; 4]) {
let d = cadcraft_io::read(text.as_bytes(), "t.dxf").unwrap();
let r = cadcraft_render::build(&d, &Space::Model, &cadcraft_render::Options::default()).bounds;
let e = d.model.iter().find(|e| matches!(e.kind, EntityKind::Text(_))).map(|e| entity_bounds(&d, e, 0)).unwrap();
([r.min.x, r.min.y, r.max.x, r.max.y], [e.min.x, e.min.y, e.max.x, e.max.y])
}
fn near(a: f64, b: f64) -> bool {
(a - b).abs() < 1e-6
}
#[test]
fn left_justified_text_is_placed_by_its_alignment_point() {
// TL: the top-left of the text at the alignment point 0,0; the baseline one height lower.
let (r, e) = boxes(&dxf(3, (0.0, -1.0), (0.0, 0.0), ""));
assert!(near(r[0], 0.0) && near(r[3], 0.0), "TL drawn at {r:?}");
assert!(near(e[0], 0.0) && near(e[3], 0.0) && near(e[1], -1.0), "TL extents {e:?}");
// ML: the middle of the text height at the alignment point.
let (r, e) = boxes(&dxf(2, (0.0, -0.5), (0.0, 0.0), ""));
assert!(near(r[3], 0.5) && near(e[3], 0.5) && near(e[1], -0.5), "ML drawn at {r:?}, extents {e:?}");
// BL: the descender line at the alignment point, so the baseline ("AB" has no descenders) a
// third of the height above it.
let (r, e) = boxes(&dxf(1, (0.0, 1.0 / 3.0), (0.0, 0.0), ""));
assert!(near(r[1], 1.0 / 3.0) && near(e[1], 1.0 / 3.0) && near(e[3], 4.0 / 3.0), "BL drawn at {r:?}, extents {e:?}");
// Mirrored (extrusion 0,0,-1): the OCS points (-10,4) and (-10,5) are WCS (10,4) and (10,5);
// drawn readable, the text runs left from its alignment point with its top there.
let (r, _) = boxes(&dxf(3, (-10.0, 4.0), (-10.0, 5.0), "210\n0\n220\n0\n230\n-1\n"));
assert!(near(r[2], 10.0) && near(r[3], 5.0), "mirrored TL drawn at {r:?}");
}