From 89d538e40cd2069984427078cbae1333e39a51e7 Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Wed, 28 Oct 2026 05:02:26 +0000 Subject: [PATCH] Separate metafile record parsing into focused handlers --- crates/metafile/src/import/emf.rs | 269 +++++++++++++++++------------- crates/metafile/src/import/wmf.rs | 190 ++++++++++++--------- 2 files changed, 263 insertions(+), 196 deletions(-) diff --git a/crates/metafile/src/import/emf.rs b/crates/metafile/src/import/emf.rs index 2b97a5a37..ece16c786 100644 --- a/crates/metafile/src/import/emf.rs +++ b/crates/metafile/src/import/emf.rs @@ -166,24 +166,154 @@ pub(super) fn play(bytes: &[u8]) -> Result { 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 = (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::>>()? + } 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::>()) + } 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::>>()? - } 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::>()) - } 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 = (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, } diff --git a/crates/metafile/src/import/wmf.rs b/crates/metafile/src/import/wmf.rs index d0fbd190d..7a682e59a 100644 --- a/crates/metafile/src/import/wmf.rs +++ b/crates/metafile/src/import/wmf.rs @@ -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 = (0..n).map(|_| r.u16().map(usize::from)).collect::>()?; + 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 = 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 = (0..n).map(|_| r.u16().map(usize::from)).collect::>()?; - 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 = 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()?));