From 3d761bdcd349bd3e75c3c67ce9fe305f6120a7b2 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Fri, 3 Jul 2026 09:51:34 +0300 Subject: [PATCH] fix(scene): honor MIRRTEXT on the MIRROR copy path transform_entities kept text/mtext/shape right-reading when MIRRTEXT is off, but the default MIRROR-with-copy path did not, so copied text mirrored backwards. Extract the capture/restore into shared helpers and apply them in copy_entities too. (#243) Co-Authored-By: Claude Opus 4.8 --- src/scene/modify.rs | 100 ++++++++++++++++++++++++++++++-------------- 1 file changed, 69 insertions(+), 31 deletions(-) diff --git a/src/scene/modify.rs b/src/scene/modify.rs index 66a177ee2..bc7314414 100644 --- a/src/scene/modify.rs +++ b/src/scene/modify.rs @@ -1,6 +1,57 @@ // Auto-split from scene/mod.rs. Pure text-move; behaviour unchanged. use super::*; +/// Orientation fields of a text-like entity, captured so a mirror can keep the +/// glyphs right-reading when MIRRTEXT (`header.mirror_text`) is off. +struct TextOrient { + rotation: f64, + oblique: f64, + x_scale: f64, +} + +/// Snapshot the orientation of a text / mtext / shape entity, or `None` for any +/// other kind. +fn capture_text_orient(e: &EntityType) -> Option { + match e { + EntityType::Text(t) => Some(TextOrient { + rotation: t.rotation, + oblique: t.oblique_angle, + x_scale: 0.0, + }), + EntityType::MText(m) => Some(TextOrient { + rotation: m.rotation, + oblique: 0.0, + x_scale: 0.0, + }), + EntityType::Shape(s) => Some(TextOrient { + rotation: s.rotation, + oblique: s.oblique_angle, + x_scale: s.relative_x_scale, + }), + _ => None, + } +} + +/// Re-apply a captured orientation after a transform (the position stays +/// mirrored, only the rotation / oblique / x-scale are restored). +fn restore_text_orient(e: &mut EntityType, o: &TextOrient) { + match e { + EntityType::Text(t) => { + t.rotation = o.rotation; + t.oblique_angle = o.oblique; + } + EntityType::MText(m) => { + m.rotation = o.rotation; + } + EntityType::Shape(s) => { + s.rotation = o.rotation; + s.oblique_angle = o.oblique; + s.relative_x_scale = o.x_scale; + } + _ => {} + } +} + impl Scene { // ── Modify (transform / copy) ───────────────────────────────────────── @@ -16,24 +67,12 @@ impl Scene { // transform and re-apply afterwards. let preserve_text_orientation = matches!(t, EntityTransform::Mirror { .. }) && !self.document.header.mirror_text; - let mut text_orient_backup: Vec<(Handle, f64, f64, f64)> = Vec::new(); + let mut text_orient_backup: Vec<(Handle, TextOrient)> = Vec::new(); if preserve_text_orientation { for &h in handles { if let Some(entity) = self.document.get_entity(h) { - match entity { - EntityType::Text(t) => { - text_orient_backup.push((h, t.rotation, t.oblique_angle, 0.0)) - } - EntityType::MText(m) => { - text_orient_backup.push((h, m.rotation, 0.0, 0.0)) - } - EntityType::Shape(s) => text_orient_backup.push(( - h, - s.rotation, - s.oblique_angle, - s.relative_x_scale, - )), - _ => {} + if let Some(o) = capture_text_orient(entity) { + text_orient_backup.push((h, o)); } } } @@ -87,23 +126,9 @@ impl Scene { } } if preserve_text_orientation { - for (h, rot, oblique, x_scale) in text_orient_backup { + for (h, o) in text_orient_backup { if let Some(entity) = self.document.get_entity_mut(h) { - match entity { - EntityType::Text(t) => { - t.rotation = rot; - t.oblique_angle = oblique; - } - EntityType::MText(m) => { - m.rotation = rot; - } - EntityType::Shape(s) => { - s.rotation = rot; - s.oblique_angle = oblique; - s.relative_x_scale = x_scale; - } - _ => {} - } + restore_text_orient(entity, &o); } } } @@ -215,9 +240,22 @@ impl Scene { .filter(|&&h| !self.is_layer_locked(h)) .filter_map(|&h| self.document.get_entity(h).cloned()) .collect(); + // MIRRTEXT also governs the copy path (default MIRROR keeps the source + // and adds a mirrored copy): keep the copied text right-reading when the + // header flag is off. + let preserve_text_orientation = + matches!(t, EntityTransform::Mirror { .. }) && !self.document.header.mirror_text; let mut new_handles = Vec::with_capacity(clones.len()); for mut entity in clones { + let text_orient = if preserve_text_orientation { + capture_text_orient(&entity) + } else { + None + }; view::dispatch::apply_transform(&mut entity, t); + if let Some(o) = &text_orient { + restore_text_orient(&mut entity, o); + } // A dimension draws from its baked `*D` block; give the copy its own // transformed block so it lands at the copy position rather than // rendering on top of the source. (#161)