diff --git a/crates/doc/src/extents.rs b/crates/doc/src/extents.rs index 4aff09d8b..651ac4b05 100644 --- a/crates/doc/src/extents.rs +++ b/crates/doc/src/extents.rs @@ -51,7 +51,7 @@ const OLE_CORNERS: usize = 12; /// Move the four corners at the start of the OLE data (the 310 groups' hex, in order) by `m`, /// keeping z and every tag's length. Data that is not valid hex or is too short is left alone. -fn ole_data_transform(tags: &mut [crate::RawTag], m: &cadcraft_geom::Mat3) { +fn ole_data_transform(tags: &mut [crate::RawTag], m: &cadcraft_geom::Mat3, frame: [f64; 4]) { let idx: Vec = outside_app(tags).into_iter().filter(|&i| tags.get(i).is_some_and(|t| t.code == 310)).collect(); let mut bytes: Vec = Vec::new(); let mut lens: Vec = Vec::new(); @@ -71,6 +71,19 @@ fn ole_data_transform(tags: &mut [crate::RawTag], m: &cadcraft_geom::Mat3) { if bytes.len() < end { return; } + // Only data whose corners are this frame's corners (UL, UR, LR, LL) has the layout above; any + // other header is not ours to rewrite. + let [x1, y1, x2, y2] = frame; + let want = [(x1, y1), (x2, y1), (x2, y2), (x1, y2)]; + let near = |a: f64, b: f64| (a - b).abs() <= 1e-9 * a.abs().max(b.abs()).max(1.0); + for (c, (wx, wy)) in want.iter().enumerate() { + let at = OLE_HEADER + c * 24; + let rd = |o: usize| bytes.get(o..o + 8).and_then(|s| <[u8; 8]>::try_from(s).ok()).map(f64::from_le_bytes); + let (Some(x), Some(y)) = (rd(at), rd(at + 8)) else { return }; + if !(near(x, *wx) && near(y, *wy)) { + return; + } + } let mut out = bytes.clone(); for c in 0..4 { let at = OLE_HEADER + c * 24; @@ -103,6 +116,7 @@ fn ole_data_transform(tags: &mut [crate::RawTag], m: &cadcraft_geom::Mat3) { /// left alone. pub fn ole_frame_transform(tags: &mut [crate::RawTag], m: &cadcraft_geom::Mat3) { let Some([(ix1, x1), (iy1, y1), (ix2, x2), (iy2, y2)]) = ole_corner_groups(tags) else { return }; + let frame = [x1, y1, x2, y2]; let (a, b) = (m.apply(Vec2::new(x1, y1)), m.apply(Vec2::new(x2, y2))); if !(a.x.is_finite() && a.y.is_finite() && b.x.is_finite() && b.y.is_finite()) { return; @@ -112,7 +126,7 @@ pub fn ole_frame_transform(tags: &mut [crate::RawTag], m: &cadcraft_geom::Mat3) t.value = v.to_string(); } } - ole_data_transform(tags, m); + ole_data_transform(tags, m, frame); } /// Whether `m` can move an OLE2FRAME: only a translation (move, copy, stretch of the whole frame). diff --git a/crates/engine/tests/ole_frame_transform.rs b/crates/engine/tests/ole_frame_transform.rs index 50ecc1f2f..22d88075a 100644 --- a/crates/engine/tests/ole_frame_transform.rs +++ b/crates/engine/tests/ole_frame_transform.rs @@ -124,3 +124,21 @@ fn move_leaves_malformed_or_short_ole_data_alone() { assert_eq!(corners(&s)[0].1, "40", "the corner groups still move"); } } + +/// OLE data whose bytes are not this frame's corners (an unknown header, so the layout does not +/// apply) is not rewritten by MOVE; the frame's own corner groups still move (review on #2587). +#[test] +fn move_leaves_ole_data_that_is_not_the_frames_corners_alone() { + let mut blob = vec![9u8, 9]; + for (x, y) in [(1.0f64, 2.0f64), (3.0, 2.0), (3.0, 4.0), (1.0, 4.0)] { + for v in [x, y, 7.0] { + blob.extend_from_slice(&v.to_le_bytes()); + } + } + let hex: String = blob.iter().map(|b| format!("{b:02X}")).collect(); + let (mut s, h) = data_session(&[40, 100], Some(&hex)); + let before = data_tags(&s); + s.execute("move", &json!({ "handles": [h], "delta": [10, 5] })).unwrap(); + assert_eq!(data_tags(&s), before); + assert_eq!(corners(&s)[0].1, "40", "the corner groups still move"); +}