Separate metafile record parsing into focused handlers

This commit is contained in:
Dylan M. Taylor
2026-10-28 05:02:26 +00:00
parent a9e82efcf0
commit 89d538e40c
2 changed files with 263 additions and 196 deletions
+152 -117
View File
@@ -166,24 +166,154 @@ pub(super) fn play(bytes: &[u8]) -> Result<Imported, String> {
Ok(p.finish())
}
/// PolyBezier, Polygon, Polyline, PolyBezierTo, PolylineTo and their 16-bit forms.
fn rec_poly(p: &mut Player, kind: u32, r: &mut Reader) -> Option<()> {
let wide = kind < 85;
r.skip(16)?;
let n = r.u32()?;
let pts = points(r, n, wide)?;
match if wide { kind } else { kind - 83 } {
2 => p.poly(&pts, false, true),
3 => p.poly(&pts, true, false),
4 => p.poly(&pts, false, false),
5 => p.poly_to(&pts, true),
_ => p.poly_to(&pts, false),
}
Some(())
}
/// CreatePen (38).
fn rec_create_pen(p: &mut Player, r: &mut Reader) -> Option<()> {
let ih = r.u32()?;
let style = r.u32()?;
let width = f64::from(r.i32()?);
r.skip(4)?;
p.create_at(ih, Obj::Pen(PenObj { style, width, color: color(r)?, entries: vec![] }));
Some(())
}
/// ExtCreatePen (95).
fn rec_ext_create_pen(p: &mut Player, r: &mut Reader) -> Option<()> {
let ih = r.u32()?;
r.skip(16)?;
let style = r.u32()?;
let width = f64::from(r.u32()?);
let brush = r.u32()?;
let c = color(r)?;
r.skip(4)?;
let n = (r.u32()? as usize).min(64);
let entries: Vec<f64> = (0..n).map_while(|_| r.u32()).map(f64::from).collect();
// A pen painted with the null brush draws nothing.
let style = if brush == 1 { super::PS_NULL } else { style };
p.create_at(ih, Obj::Pen(PenObj { style, width, color: c, entries }));
Some(())
}
/// GdiComment (70): EMF+ records ride in comments starting "EMF+".
fn rec_comment(p: &mut Player, r: &mut Reader) -> Option<()> {
r.skip(4)?;
p.emf_plus |= r.u32()? == 0x2B46_4D45;
Some(())
}
/// ExtSelectClipRgn (75).
fn rec_clip_region(p: &mut Player, r: &mut Reader) -> Option<()> {
let cb = r.u32()?;
let mode = r.u32()?;
let data = r.bytes(cb as usize)?;
let mut d = Reader::new(data);
let rects = if cb >= 32 {
d.skip(8)?;
let n = d.u32()? as usize;
d.skip(20)?;
if n > MAX_POINTS || d.rest().len() < n * 16 {
return None;
}
(0..n).map(|_| rect(&mut d)).collect::<Option<Vec<_>>>()?
} else {
vec![]
};
p.clip_region(&rects, mode);
Some(())
}
/// The rectangle at `(x, y)` of size `(w, h)`.
fn at_size(x: i32, y: i32, w: i32, h: i32) -> Rect {
Rect::new(f64::from(x), f64::from(y), f64::from(x) + f64::from(w), f64::from(y) + f64::from(h))
}
/// BitBlt (76) and StretchBlt (77).
fn rec_blt(p: &mut Player, kind: u32, rec: &[u8], r: &mut Reader) -> Option<()> {
r.skip(16)?;
let dest = (r.i32()?, r.i32()?, r.i32()?, r.i32()?);
let rop = r.u32()?;
let (xs, ys) = (r.i32()?, r.i32()?);
r.skip(24 + 4 + 4)?;
let (off_bmi, cb_bmi, off_bits, cb_bits) = (r.u32()?, r.u32()?, r.u32()?, r.u32()?);
let (ws, hs) = if kind == 77 { (r.i32()?, r.i32()?) } else { (dest.2, dest.3) };
let d = at_size(dest.0, dest.1, dest.2, dest.3);
if cb_bmi == 0 {
p.pattern_blit(d, rop);
} else {
let img = bitmap(rec, off_bmi, cb_bmi, off_bits, cb_bits, Alpha::Ignore, &mut p.pixels_left);
let src = source(xs, ys, ws, hs, &img, false);
p.image(img, src, d, rop, 1.0);
}
Some(())
}
/// StretchDIBits (81).
fn rec_stretch_dibits(p: &mut Player, rec: &[u8], r: &mut Reader) -> Option<()> {
r.skip(16)?;
let (xd, yd, xs, ys, ws, hs) = (r.i32()?, r.i32()?, r.i32()?, r.i32()?, r.i32()?, r.i32()?);
let (off_bmi, cb_bmi, off_bits, cb_bits) = (r.u32()?, r.u32()?, r.u32()?, r.u32()?);
r.skip(4)?;
let rop = r.u32()?;
let (wd, hd) = (r.i32()?, r.i32()?);
let img = bitmap(rec, off_bmi, cb_bmi, off_bits, cb_bits, Alpha::Ignore, &mut p.pixels_left);
let src = source(xs, ys, ws, hs, &img, bottom_up(rec, off_bmi));
p.image(img, src, at_size(xd, yd, wd, hd), rop, 1.0);
Some(())
}
/// AlphaBlend (114).
fn rec_alpha_blend(p: &mut Player, rec: &[u8], r: &mut Reader) -> Option<()> {
r.skip(16)?;
let (xd, yd, wd, hd) = (r.i32()?, r.i32()?, r.i32()?, r.i32()?);
let blend = r.bytes(4)?;
let (constant, per_pixel) = (blend.get(2).copied().unwrap_or(255), blend.get(3).is_some_and(|f| *f & 1 != 0));
let (xs, ys) = (r.i32()?, r.i32()?);
r.skip(24 + 4 + 4)?;
let (off_bmi, cb_bmi, off_bits, cb_bits) = (r.u32()?, r.u32()?, r.u32()?, r.u32()?);
let (ws, hs) = (r.i32()?, r.i32()?);
let alpha = if per_pixel { Alpha::Premultiplied } else { Alpha::Ignore };
let img = bitmap(rec, off_bmi, cb_bmi, off_bits, cb_bits, alpha, &mut p.pixels_left);
let src = source(xs, ys, ws, hs, &img, false);
p.image(img, src, at_size(xd, yd, wd, hd), crate::import::SRCCOPY, f64::from(constant) / 255.0);
Some(())
}
/// ExtTextOutA (83) and ExtTextOutW (84).
fn rec_text(p: &mut Player, kind: u32, rec: &[u8], r: &mut Reader) -> Option<()> {
r.skip(16 + 4 + 8)?;
let at = point32(r)?;
let n = r.u32()? as usize;
let off = r.u32()?;
let s = if kind == 84 {
let raw = slice(rec, off, u32::try_from(n.checked_mul(2)?).ok()?)?;
String::from_utf16_lossy(&raw.as_chunks::<2>().0.iter().map(|c| u16::from_le_bytes(*c)).collect::<Vec<_>>())
} else {
slice(rec, off, u32::try_from(n).ok()?)?.iter().map(|b| char::from(*b)).collect()
};
p.text(at, &s);
Some(())
}
/// Play one record (`rec`: all of it, with its type and size). `None`: unknown or damaged.
fn record(p: &mut Player, kind: u32, rec: &[u8]) -> Option<()> {
let mut r = Reader::new(rec.get(8..)?);
match kind {
// PolyBezier, Polygon, Polyline, PolyBezierTo, PolylineTo and their 16-bit forms.
2..=6 | 85..=89 => {
let wide = kind < 85;
r.skip(16)?;
let n = r.u32()?;
let pts = points(&mut r, n, wide)?;
match if wide { kind } else { kind - 83 } {
2 => p.poly(&pts, false, true),
3 => p.poly(&pts, true, false),
4 => p.poly(&pts, false, false),
5 => p.poly_to(&pts, true),
_ => p.poly_to(&pts, false),
}
}
2..=6 | 85..=89 => rec_poly(p, kind, &mut r)?,
7 | 90 => p.poly_poly(&polys(&mut r, kind == 7)?, false),
8 | 91 => p.poly_poly(&polys(&mut r, kind == 8)?, true),
9 => p.set_window_ext(f64::from(r.i32()?), f64::from(r.i32()?)),
@@ -209,13 +339,7 @@ fn record(p: &mut Player, kind: u32, rec: &[u8]) -> Option<()> {
p.world(x, r.u32()?);
}
37 => p.select(r.u32()?),
38 => {
let ih = r.u32()?;
let style = r.u32()?;
let width = f64::from(r.i32()?);
r.skip(4)?;
p.create_at(ih, Obj::Pen(PenObj { style, width, color: color(&mut r)?, entries: vec![] }));
}
38 => rec_create_pen(p, &mut r)?,
39 => {
let ih = r.u32()?;
let style = r.u32()?;
@@ -249,107 +373,18 @@ fn record(p: &mut Player, kind: u32, rec: &[u8]) -> Option<()> {
64 => p.paint_path(false, true),
67 => p.clip_path(r.u32()?),
68 => p.abort_path(),
70 => {
// EMF+ records ride in comments starting "EMF+".
r.skip(4)?;
if r.u32()? == 0x2B46_4D45 {
p.emf_plus = true;
}
}
75 => {
let cb = r.u32()?;
let mode = r.u32()?;
let data = r.bytes(cb as usize)?;
let mut d = Reader::new(data);
let rects = if cb >= 32 {
d.skip(8)?;
let n = d.u32()? as usize;
d.skip(20)?;
if n > MAX_POINTS || d.rest().len() < n * 16 {
return None;
}
(0..n).map(|_| rect(&mut d)).collect::<Option<Vec<_>>>()?
} else {
vec![]
};
p.clip_region(&rects, mode);
}
76 | 77 => {
r.skip(16)?;
let dest = (r.i32()?, r.i32()?, r.i32()?, r.i32()?);
let rop = r.u32()?;
let (xs, ys) = (r.i32()?, r.i32()?);
r.skip(24 + 4 + 4)?;
let (off_bmi, cb_bmi, off_bits, cb_bits) = (r.u32()?, r.u32()?, r.u32()?, r.u32()?);
let (ws, hs) = if kind == 77 { (r.i32()?, r.i32()?) } else { (dest.2, dest.3) };
let d = Rect::new(f64::from(dest.0), f64::from(dest.1), f64::from(dest.0) + f64::from(dest.2), f64::from(dest.1) + f64::from(dest.3));
if cb_bmi == 0 {
p.pattern_blit(d, rop);
} else {
let img = bitmap(rec, off_bmi, cb_bmi, off_bits, cb_bits, Alpha::Ignore, &mut p.pixels_left);
let src = source(xs, ys, ws, hs, &img, false);
p.image(img, src, d, rop, 1.0);
}
}
81 => {
r.skip(16)?;
let (xd, yd, xs, ys, ws, hs) = (r.i32()?, r.i32()?, r.i32()?, r.i32()?, r.i32()?, r.i32()?);
let (off_bmi, cb_bmi, off_bits, cb_bits) = (r.u32()?, r.u32()?, r.u32()?, r.u32()?);
r.skip(4)?;
let rop = r.u32()?;
let (wd, hd) = (r.i32()?, r.i32()?);
let img = bitmap(rec, off_bmi, cb_bmi, off_bits, cb_bits, Alpha::Ignore, &mut p.pixels_left);
let src = source(xs, ys, ws, hs, &img, bottom_up(rec, off_bmi));
let d = Rect::new(f64::from(xd), f64::from(yd), f64::from(xd) + f64::from(wd), f64::from(yd) + f64::from(hd));
p.image(img, src, d, rop, 1.0);
}
70 => rec_comment(p, &mut r)?,
75 => rec_clip_region(p, &mut r)?,
76 | 77 => rec_blt(p, kind, rec, &mut r)?,
81 => rec_stretch_dibits(p, rec, &mut r)?,
82 => {
let ih = r.u32()?;
p.create_at(ih, Obj::Font(font(&mut r)?));
}
83 | 84 => {
r.skip(16 + 4 + 8)?;
let at = point32(&mut r)?;
let n = r.u32()? as usize;
let off = r.u32()?;
let s = if kind == 84 {
let raw = slice(rec, off, u32::try_from(n.checked_mul(2)?).ok()?)?;
String::from_utf16_lossy(&raw.as_chunks::<2>().0.iter().map(|c| u16::from_le_bytes(*c)).collect::<Vec<_>>())
} else {
slice(rec, off, u32::try_from(n).ok()?)?.iter().map(|b| char::from(*b)).collect()
};
p.text(at, &s);
}
83 | 84 => rec_text(p, kind, rec, &mut r)?,
93 | 94 => p.create_at(r.u32()?, Obj::Brush(BrushObj::Pattern)),
95 => {
let ih = r.u32()?;
r.skip(16)?;
let style = r.u32()?;
let width = f64::from(r.u32()?);
let brush = r.u32()?;
let c = color(&mut r)?;
r.skip(4)?;
let n = (r.u32()? as usize).min(64);
let entries: Vec<f64> = (0..n).map_while(|_| r.u32()).map(f64::from).collect();
// A pen painted with the null brush draws nothing.
let style = if brush == 1 { super::PS_NULL } else { style };
p.create_at(ih, Obj::Pen(PenObj { style, width, color: c, entries }));
}
114 => {
r.skip(16)?;
let (xd, yd, wd, hd) = (r.i32()?, r.i32()?, r.i32()?, r.i32()?);
let blend = r.bytes(4)?;
let (constant, per_pixel) = (blend.get(2).copied().unwrap_or(255), blend.get(3).is_some_and(|f| *f & 1 != 0));
let (xs, ys) = (r.i32()?, r.i32()?);
r.skip(24 + 4 + 4)?;
let (off_bmi, cb_bmi, off_bits, cb_bits) = (r.u32()?, r.u32()?, r.u32()?, r.u32()?);
let (ws, hs) = (r.i32()?, r.i32()?);
let alpha = if per_pixel { Alpha::Premultiplied } else { Alpha::Ignore };
let img = bitmap(rec, off_bmi, cb_bmi, off_bits, cb_bits, alpha, &mut p.pixels_left);
let src = source(xs, ys, ws, hs, &img, false);
let d = Rect::new(f64::from(xd), f64::from(yd), f64::from(xd) + f64::from(wd), f64::from(yd) + f64::from(hd));
p.image(img, src, d, crate::import::SRCCOPY, f64::from(constant) / 255.0);
}
95 => rec_ext_create_pen(p, &mut r)?,
114 => rec_alpha_blend(p, rec, &mut r)?,
k if IGNORED.contains(&k) => {}
_ => return None,
}
+111 -79
View File
@@ -174,6 +174,109 @@ fn blit(p: &mut Player, rop: u32, src: (i16, i16, i16, i16), dest: (i16, i16, i1
p.image(img, Rect::new(xs, top, xs + ws, top + hs), d, rop, 1.0);
}
/// PolyPolygon (0x0538).
fn rec_poly_polygon(p: &mut Player, r: &mut Reader) -> Option<()> {
let n = usize::from(r.u16()?);
if r.rest().len() < n * 2 {
return None;
}
let counts: Vec<usize> = (0..n).map(|_| r.u16().map(usize::from)).collect::<Option<_>>()?;
let mut polys = Vec::with_capacity(n);
for c in counts {
polys.push(points(r, c)?);
}
p.poly_poly(&polys, true);
Some(())
}
/// Arc (0x0817), Pie (0x081A) and Chord (0x0830).
fn rec_arc(p: &mut Player, function: u16, r: &mut Reader) -> Option<()> {
let (end, start) = (point_yx(r)?, point_yx(r)?);
let b = rect_brtl(r)?;
let kind = match function {
0x0817 => ArcKind::Open,
0x081A => ArcKind::Pie,
_ => ArcKind::Chord,
};
p.arc(b, start, end, kind);
Some(())
}
/// CreateFontIndirect (0x02FB).
fn rec_create_font(p: &mut Player, r: &mut Reader) -> Option<()> {
let height = f64::from(r.i16()?);
r.skip(2)?;
let escapement = f64::from(r.i16()?);
r.skip(2)?;
let weight = i32::from(r.i16()?);
let italic = r.bytes(1)?.first().is_some_and(|b| *b != 0);
r.skip(7)?;
let face: Vec<u8> = r.rest().iter().take(32).take_while(|b| **b != 0).copied().collect();
p.create(Obj::Font(FontObj { height, escapement, weight, italic, face: ansi(&face) }));
Some(())
}
/// TextOut (0x0521).
fn rec_text_out(p: &mut Player, r: &mut Reader) -> Option<()> {
let n = usize::try_from(r.i16()?).ok()?;
let s = ansi(r.bytes(n)?);
r.skip(n % 2)?;
let at = point_yx(r)?;
p.text(at, &s);
Some(())
}
/// ExtTextOut (0x0A32).
fn rec_ext_text_out(p: &mut Player, r: &mut Reader) -> Option<()> {
let at = point_yx(r)?;
let n = usize::try_from(r.i16()?).ok()?;
let opts = r.u16()?;
// ETO_OPAQUE or ETO_CLIPPED: a rectangle comes first.
if opts & 0x6 != 0 {
r.skip(8)?;
}
let s = ansi(r.bytes(n)?);
p.text(at, &s);
Some(())
}
/// StretchDIB (0x0F43).
fn rec_stretch_dib(p: &mut Player, r: &mut Reader) -> Option<()> {
let rop = r.u32()?;
r.skip(2)?;
let (hs, ws, ys, xs) = (r.i16()?, r.i16()?, r.i16()?, r.i16()?);
let (hd, wd, yd, xd) = (r.i16()?, r.i16()?, r.i16()?, r.i16()?);
blit(p, rop, (xs, ys, ws, hs), (xd, yd, wd, hd), Some(r.rest()));
Some(())
}
/// StretchBlt (0x0B41).
fn rec_stretch_blt(p: &mut Player, body: &[u8], r: &mut Reader) -> Option<()> {
let rop = r.u32()?;
let (hs, ws, ys, xs) = (r.i16()?, r.i16()?, r.i16()?, r.i16()?);
// Without a bitmap, a reserved word comes before the destination.
let with_bitmap = body.len() > 22;
if !with_bitmap {
r.skip(2)?;
}
let (hd, wd, yd, xd) = (r.i16()?, r.i16()?, r.i16()?, r.i16()?);
blit(p, rop, (xs, ys, ws, hs), (xd, yd, wd, hd), with_bitmap.then(|| r.rest()));
Some(())
}
/// BitBlt (0x0940).
fn rec_bit_blt(p: &mut Player, body: &[u8], r: &mut Reader) -> Option<()> {
let rop = r.u32()?;
let (ys, xs) = (r.i16()?, r.i16()?);
let with_bitmap = body.len() > 20;
if !with_bitmap {
r.skip(2)?;
}
let (h, w, yd, xd) = (r.i16()?, r.i16()?, r.i16()?, r.i16()?);
blit(p, rop, (xs, ys, w, h), (xd, yd, w, h), with_bitmap.then(|| r.rest()));
Some(())
}
/// Play one record (`r`: its parameters). `None`: unknown or damaged.
fn record(p: &mut Player, function: u16, body: &[u8]) -> Option<()> {
let mut r = Reader::new(body);
@@ -193,34 +296,14 @@ fn record(p: &mut Player, function: u16, body: &[u8]) -> Option<()> {
let pts = points(&mut r, n)?;
p.poly(&pts, function == 0x0324, false);
}
0x0538 => {
let n = usize::from(r.u16()?);
if r.rest().len() < n * 2 {
return None;
}
let counts: Vec<usize> = (0..n).map(|_| r.u16().map(usize::from)).collect::<Option<_>>()?;
let mut polys = Vec::with_capacity(n);
for c in counts {
polys.push(points(&mut r, c)?);
}
p.poly_poly(&polys, true);
}
0x0538 => rec_poly_polygon(p, &mut r)?,
0x041B => p.figure(shapes::rectangle(rect_brtl(&mut r)?.abs()).to_bezpath(), true),
0x0418 => p.figure(shapes::ellipse(rect_brtl(&mut r)?.abs()).to_bezpath(), true),
0x061C => {
let (h, w) = (f64::from(r.i16()?), f64::from(r.i16()?));
p.figure(super::round_rect(rect_brtl(&mut r)?, w, h), true);
}
0x0817 | 0x081A | 0x0830 => {
let (end, start) = (point_yx(&mut r)?, point_yx(&mut r)?);
let b = rect_brtl(&mut r)?;
let kind = match function {
0x0817 => ArcKind::Open,
0x081A => ArcKind::Pie,
_ => ArcKind::Chord,
};
p.arc(b, start, end, kind);
}
0x0817 | 0x081A | 0x0830 => rec_arc(p, function, &mut r)?,
0x0416 => p.clip_rect(rect_brtl(&mut r)?),
0x0415 => p.exclude_clip(),
0x012D => p.select(u32::from(r.u16()?)),
@@ -235,65 +318,14 @@ fn record(p: &mut Player, function: u16, body: &[u8]) -> Option<()> {
let style = u32::from(r.u16()?);
p.create(Obj::Brush(BrushObj::from_log(style, color(&mut r)?)));
}
0x02FB => {
let height = f64::from(r.i16()?);
r.skip(2)?;
let escapement = f64::from(r.i16()?);
r.skip(2)?;
let weight = i32::from(r.i16()?);
let italic = r.bytes(1)?.first().is_some_and(|b| *b != 0);
r.skip(7)?;
let face: Vec<u8> = r.rest().iter().take(32).take_while(|b| **b != 0).copied().collect();
p.create(Obj::Font(FontObj { height, escapement, weight, italic, face: ansi(&face) }));
}
0x02FB => rec_create_font(p, &mut r)?,
0x00F7 | 0x06FF => p.create(Obj::Other),
0x01F9 | 0x0142 => p.create(Obj::Brush(BrushObj::Pattern)),
0x0521 => {
let n = usize::try_from(r.i16()?).ok()?;
let s = ansi(r.bytes(n)?);
r.skip(n % 2)?;
let at = point_yx(&mut r)?;
p.text(at, &s);
}
0x0A32 => {
let at = point_yx(&mut r)?;
let n = usize::try_from(r.i16()?).ok()?;
let opts = r.u16()?;
// ETO_OPAQUE or ETO_CLIPPED: a rectangle comes first.
if opts & 0x6 != 0 {
r.skip(8)?;
}
let s = ansi(r.bytes(n)?);
p.text(at, &s);
}
0x0F43 => {
let rop = r.u32()?;
r.skip(2)?;
let (hs, ws, ys, xs) = (r.i16()?, r.i16()?, r.i16()?, r.i16()?);
let (hd, wd, yd, xd) = (r.i16()?, r.i16()?, r.i16()?, r.i16()?);
blit(p, rop, (xs, ys, ws, hs), (xd, yd, wd, hd), Some(r.rest()));
}
0x0B41 => {
let rop = r.u32()?;
let (hs, ws, ys, xs) = (r.i16()?, r.i16()?, r.i16()?, r.i16()?);
// Without a bitmap, a reserved word comes before the destination.
let with_bitmap = body.len() > 22;
if !with_bitmap {
r.skip(2)?;
}
let (hd, wd, yd, xd) = (r.i16()?, r.i16()?, r.i16()?, r.i16()?);
blit(p, rop, (xs, ys, ws, hs), (xd, yd, wd, hd), with_bitmap.then(|| r.rest()));
}
0x0940 => {
let rop = r.u32()?;
let (ys, xs) = (r.i16()?, r.i16()?);
let with_bitmap = body.len() > 20;
if !with_bitmap {
r.skip(2)?;
}
let (h, w, yd, xd) = (r.i16()?, r.i16()?, r.i16()?, r.i16()?);
blit(p, rop, (xs, ys, w, h), (xd, yd, w, h), with_bitmap.then(|| r.rest()));
}
0x0521 => rec_text_out(p, &mut r)?,
0x0A32 => rec_ext_text_out(p, &mut r)?,
0x0F43 => rec_stretch_dib(p, &mut r)?,
0x0B41 => rec_stretch_blt(p, body, &mut r)?,
0x0940 => rec_bit_blt(p, body, &mut r)?,
0x061D => {
let rop = r.u32()?;
let (h, w, y, x) = (f64::from(r.i16()?), f64::from(r.i16()?), f64::from(r.i16()?), f64::from(r.i16()?));