diff --git a/crates/metafile/src/canvas.rs b/crates/metafile/src/canvas.rs
index 4ffa56bf..99f42071 100644
--- a/crates/metafile/src/canvas.rs
+++ b/crates/metafile/src/canvas.rs
@@ -16,7 +16,7 @@ const STOCK: u32 = 0x8000_0000;
pub(crate) const PS_NULL: u32 = 5;
/// Pen style bits; the rest of the style word holds end-cap, join and type flags.
pub(crate) const PS_STYLE_MASK: u32 = 0x0F;
-const BS_SOLID: u32 = 0;
+pub(crate) const BS_SOLID: u32 = 0;
pub(crate) const BS_NULL: u32 = 1;
pub(crate) const BS_HATCHED: u32 = 2;
/// BS_PATTERN, BS_DIBPATTERN and BS_DIBPATTERNPT: the brush paints a bitmap tile.
@@ -442,6 +442,20 @@ impl Canvas {
}
}
+ /// The current world transform (EMF+ draws through it).
+ pub fn world(&self) -> Xform {
+ self.map.world
+ }
+
+ /// Sets the pen and brush directly (EMF+ records name their own pen and brush).
+ pub fn set_pen(&mut self, pen: Pen) {
+ self.pen = pen;
+ }
+
+ pub fn set_brush(&mut self, brush: Brush) {
+ self.brush = brush;
+ }
+
/// Pushes the drawing state. Past [`MAX_STACK`] levels the parse stops, since later restores
/// could no longer match the file.
pub fn save_dc(&mut self) {
diff --git a/crates/metafile/src/emf.rs b/crates/metafile/src/emf.rs
index 709537f7..21ad9b62 100644
--- a/crates/metafile/src/emf.rs
+++ b/crates/metafile/src/emf.rs
@@ -42,6 +42,7 @@ pub(crate) fn parse(input: Bytes) -> Result<(Canvas, Option), Error> {
if let Some(px_mm) = device_px_per_mm(input) {
cv.set_device_mm(px_mm);
}
+ let mut plus = crate::emfplus::Plus::new();
let mut parsed = 0usize;
let mut pos = hsize.max(88);
while !cv.full() {
@@ -54,7 +55,13 @@ pub(crate) fn parse(input: Bytes) -> Result<(Canvas, Option), Error> {
if typ == 14 {
break;
}
- record(&mut cv, typ, Bytes(rec));
+ if typ == 70 && Bytes(rec).u32(12) == Some(crate::emfplus::EMF_PLUS) {
+ if let Some(data) = Bytes(rec).tail(16) {
+ plus.comment(&mut cv, data);
+ }
+ } else {
+ record(&mut cv, typ, Bytes(rec));
+ }
parsed += 1;
let Some(next) = pos.checked_add(size) else { break };
pos = next;
diff --git a/crates/metafile/src/emfplus.rs b/crates/metafile/src/emfplus.rs
new file mode 100644
index 00000000..3aef2842
--- /dev/null
+++ b/crates/metafile/src/emfplus.rs
@@ -0,0 +1,408 @@
+//! EMF+ (MS-EMFPLUS): GDI+ drawing records carried in EMF comment records.
+//!
+//! Most EMF+ files are *dual*: the same picture is also drawn with plain EMF records, which
+//! [`crate::emf`] already plays, so this reader ignores their EMF+ part. An *EMF+ only* file has
+//! nothing else to draw, so it used to show as an empty picture; for those this reader plays the
+//! vector records: solid brushes and pens, paths, rectangles, polygons, lines, ellipses and
+//! Béziers, under the world and page transforms, save/restore and containers. Images, text,
+//! gradient brushes, clipping and arcs are not drawn yet.
+
+use crate::bytes::Bytes;
+use crate::canvas::{BS_NULL, BS_SOLID, Brush, Canvas, PS_NULL, Pen, Xform, mul};
+use crate::geom::{Op, P};
+
+/// "EMF+" as the comment identifier of an EMR_COMMENT holding EMF+ records.
+pub(crate) const EMF_PLUS: u32 = 0x2B46_4D45;
+const IDENTITY: Xform = [1.0, 0.0, 0.0, 1.0, 0.0, 0.0];
+/// GDI+ allows 64 objects; ids are a byte.
+const MAX_OBJECTS: usize = 64;
+const MAX_STACK: usize = 64;
+/// Points per path or point list read, as the plain EMF reader caps paths.
+const MAX_POINTS: usize = 100_000;
+
+#[derive(Clone, Debug)]
+enum Obj {
+ Brush([u8; 4]),
+ Pen { color: [u8; 4], width: f64 },
+ Path(Vec),
+}
+
+pub(crate) struct Plus {
+ /// An EMF+ header said the file is EMF+ only, so these records are the picture.
+ active: bool,
+ dpi: f64,
+ world: Xform,
+ page: f64,
+ objects: Vec