diff --git a/crates/metafile/src/import/wmf.rs b/crates/metafile/src/import/wmf.rs index 1a811746e..ec99aadc9 100644 --- a/crates/metafile/src/import/wmf.rs +++ b/crates/metafile/src/import/wmf.rs @@ -99,6 +99,13 @@ fn window(bytes: &[u8], mut at: usize) -> Option { Some(Rect::new(o.x, o.y, o.x + e.x, o.y + e.y).abs()) } +/// Logical → document for a placeable file whose window (`org`, `ext`) is mapped onto its box `b`; +/// a negative extent flips that axis, as in GDI. +fn window_map(b: Rect, k: f64, org: Point, ext: Point) -> Affine { + let ratio = |box_len: f64, e: f64| if e.abs() > 1e-12 { box_len / e } else { 1.0 }; + Affine::scale_non_uniform(k * ratio(b.width(), ext.x), k * ratio(b.height(), ext.y)) * Affine::translate(-org.to_vec2()) +} + pub(super) fn play(bytes: &[u8]) -> Result { let (start, placeable) = header(bytes).ok_or("the WMF header is damaged")?; let (frame, inch) = match placeable { @@ -109,6 +116,9 @@ pub(super) fn play(bytes: &[u8]) -> Result { let origin = frame.map_or(Point::ZERO, |f| f.origin()); let artboard = frame.map_or(Rect::new(0.0, 0.0, 1.0, 1.0), |f| Rect::new(0.0, 0.0, f.width() * k, f.height() * k)); let mut p = Player::new(Kind::Wmf, Affine::scale(k) * Affine::translate(-origin.to_vec2()), 25.4 / inch, artboard); + // With a placeable header, the window records map the window onto the box. + let placed = placeable.map(|(b, _)| b).filter(|b| b.width() > 0.0 && b.height() > 0.0); + let mut win = (placed.map_or(Point::ZERO, |b| b.origin()), placed.map_or(Point::ZERO, |b| Point::new(b.width(), b.height()))); let mut at = start; let mut ended = false; while let Some(head) = bytes.get(at..at + 6) { @@ -122,7 +132,16 @@ pub(super) fn play(bytes: &[u8]) -> Result { if !matches!(function, 0x0213 | 0x0214) { p.flush(); } - if record(&mut p, function, rec.get(6..).unwrap_or_default()).is_none() { + if let (Some(b), 0x020B | 0x020C) = (placed, function) { + if let Some(v) = point_yx(&mut Reader::new(rec.get(6..).unwrap_or_default())) { + if function == 0x020B { + win.0 = v; + } else { + win.1 = v; + } + p.dev_to_doc = window_map(b, k, win.0, win.1); + } + } else if record(&mut p, function, rec.get(6..).unwrap_or_default()).is_none() { p.skip(); } at += rec.len(); diff --git a/crates/metafile/src/tests_import.rs b/crates/metafile/src/tests_import.rs index 657739788..f145a11d3 100644 --- a/crates/metafile/src/tests_import.rs +++ b/crates/metafile/src/tests_import.rs @@ -289,6 +289,25 @@ fn hand_built_wmf_plays_like_gdi() { assert_eq!(fill_rgb(&p[1]), Some([0, 255, 0]), "the new brush took the freed slot"); } +#[test] +fn wmf_window_is_mapped_onto_the_placeable_box() { + // The box is 1000 × 500 units at 1440 an inch: 50 × 25 pt. + let want = Rect::new(12.5, 6.25, 37.5, 18.75); + // Window 0,0 – 4000 × 2000: the rectangle 1000,500 – 3000,1500 is the middle half. + let ext = vec![(0x020C, le16(&[2000, 4000])), (0x041B, le16(&[1500, 3000, 500, 1000]))]; + // Window 10000,20000 – 1000 × 500 (the box): the rectangle 10250,20125 – 10750,20375. + let org = vec![(0x020B, le16(&[20000, 10000])), (0x020C, le16(&[500, 1000])), (0x041B, le16(&[20375, 10750, 20125, 10250]))]; + // Window 0,2000 – 4000 × -2000 (y up): the same rectangle, flipped onto the same place. + let flip = vec![(0x020B, le16(&[2000, 0])), (0x020C, le16(&[-2000, 4000])), (0x041B, le16(&[1500, 3000, 500, 1000]))]; + for records in [ext, org, flip] { + let back = import(&hand_wmf(1000, 500, &records)).unwrap(); + assert!(near(back.document.artboards[0].rect, Rect::new(0.0, 0.0, 50.0, 25.0), 1e-9)); + let p = paths(&back.document); + assert_eq!(p.len(), 1); + assert!(near(p[0].geometric_bounds().unwrap(), want, 1e-6), "{:?}", p[0].geometric_bounds()); + } +} + #[test] fn damaged_files_fail_or_import_partly() { let good = emf(&sample()).bytes;