fix(scene): correct MIRROR of TEXT under MIRRTEXT (commit + preview)

Root cause: the glyph tessellator advanced the pen by advance*width_factor
while the transform already scales the pen position by the width factor, so
a backward (negative width factor) run kept its word order and grew on the
same side of the anchor instead of mirroring across it. Advance in unscaled
glyph units to match text_local_bounds; the transform applies the factor once.

On top of that:
- The renderer honours the entity's own text-generation flags (DXF 71: bit 2
  backward / bit 4 upside-down) XOR the text style's flags, and signs the
  horizontal anchor by the width factor so Center/Right backward text mirrors
  about its anchor.
- MIRRTEXT on: a MIRROR toggles both group-71 bits so the reflected rotation
  renders a true glyph mirror (backward-upright for a vertical axis, flipped
  for a horizontal one).
- MIRRTEXT off: the text flips left<->right and lands mirror-symmetric to the
  source (right-reading) instead of hugging the axis.
- The MIRROR preview re-derives text ghosts to match the commit; mirrored()
  now reflects the double-single residual too.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 4.8 committed 2026-07-03 12:42:55 +03:00
1 parent a24232842c
commit 79a5ac6fed
7 files changed
+153 -23

No files matched your search

+27 -2
View File
@@ -34,14 +34,26 @@ enum Step {
pub struct MirrorCommand {
handles: Vec<Handle>,
wire_models: Vec<WireModel>,
/// Text ghosts paired with their bounding-box centre — mirrored per MIRRTEXT
/// so the preview matches the commit (true glyph mirror on / symmetric
/// right-reading off).
text_ghosts: Vec<(WireModel, DVec3)>,
mirror_text: bool,
step: Step,
}
impl MirrorCommand {
pub fn new(handles: Vec<Handle>, wire_models: Vec<WireModel>) -> Self {
pub fn new(
handles: Vec<Handle>,
wire_models: Vec<WireModel>,
text_ghosts: Vec<(WireModel, DVec3)>,
mirror_text: bool,
) -> Self {
Self {
handles,
wire_models,
text_ghosts,
mirror_text,
step: Step::P1,
}
}
@@ -121,12 +133,25 @@ impl CadCommand for MirrorCommand {
Step::AskErase { p1, p2 } => (*p1, *p2),
_ => return vec![],
};
// Mirrored ghosts of all selected objects.
// Mirrored ghosts of all non-text objects (full geometric reflection).
let mut out: Vec<WireModel> = self
.wire_models
.iter()
.map(|w| w.mirrored(p1.as_vec3(), p2.as_vec3()))
.collect();
// Text ghosts honour MIRRTEXT: on → true glyph mirror (full reflection);
// off → keep glyphs readable, relocate to the mirror-symmetric position
// by reflecting the box centre and translating.
for (w, center) in &self.text_ghosts {
if self.mirror_text {
out.push(w.mirrored(p1.as_vec3(), p2.as_vec3()));
} else {
let (mut cx, mut cy) = (center.x, center.y);
crate::scene::view::transform::reflect_xy_point(&mut cx, &mut cy, p1, p2);
let delta = glam::Vec3::new((cx - center.x) as f32, (cy - center.y) as f32, 0.0);
out.push(w.translated(delta));
}
}
// Mirror-axis line (rubber-band).
out.push(WireModel::solid(
"rubber_band".into(),