mirror of
https://github.com/storytold/wordcraft.git
synced 2026-11-10 20:29:51 -06:00
Windows metafiles (WMF, EMF), common for logos, crests and diagrams in older documents, couldn't be decoded and showed as grey boxes, on screen and in PDF. The new crate wordcraft-metafile (layer 1) reads them from the public [MS-WMF] and [MS-EMF] specifications into paths and bitmaps, and places them in a picture's box; render and pdf paint the result as vectors. It covers the drawing records Office pictures use (lines, polygons, curves, rectangles, ellipses, arcs, path brackets), pens and brushes, the mapping modes and world transforms, and bitmaps in DIB form with the usual raster operations. Input is treated as hostile: every read is bounded, and a byte budget caps what one picture may allocate. Not yet: text records (ExtTextOut) and EMF+ records are skipped, so a metafile whose content is mostly text shows only its lines and fills. docxide-pdf bench (365 documents, vs main + #135): the twelve documents with WMF/EMF pictures show them; the pictures are a small part of their pages, so the scores move little (Jaccard passes 208 -> 209, five documents up to 3 SSIM points better, none worse). Co-authored-by: Oliver Zieschang <4963183+naxvog@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
60 lines
2.0 KiB
Rust
60 lines
2.0 KiB
Rust
//! Bounds-checked little-endian access to hostile input. Every read returns `None` past the end, so
|
|
//! a truncated or lying size field stops the parse instead of panicking.
|
|
|
|
#[derive(Clone, Copy)]
|
|
pub(crate) struct Bytes<'a>(pub(crate) &'a [u8]);
|
|
|
|
impl<'a> Bytes<'a> {
|
|
pub fn slice(&self, off: usize, len: usize) -> Option<&'a [u8]> {
|
|
self.0.get(off..off.checked_add(len)?)
|
|
}
|
|
|
|
pub fn tail(&self, off: usize) -> Option<Bytes<'a>> {
|
|
self.0.get(off..).map(Bytes)
|
|
}
|
|
|
|
fn arr<const N: usize>(&self, off: usize) -> Option<[u8; N]> {
|
|
self.slice(off, N)?.try_into().ok()
|
|
}
|
|
|
|
pub fn u16(&self, off: usize) -> Option<u16> {
|
|
Some(u16::from_le_bytes(self.arr(off)?))
|
|
}
|
|
|
|
pub fn i16(&self, off: usize) -> Option<i16> {
|
|
Some(i16::from_le_bytes(self.arr(off)?))
|
|
}
|
|
|
|
pub fn u32(&self, off: usize) -> Option<u32> {
|
|
Some(u32::from_le_bytes(self.arr(off)?))
|
|
}
|
|
|
|
pub fn i32(&self, off: usize) -> Option<i32> {
|
|
Some(i32::from_le_bytes(self.arr(off)?))
|
|
}
|
|
|
|
pub fn f32(&self, off: usize) -> Option<f32> {
|
|
Some(f32::from_le_bytes(self.arr(off)?))
|
|
}
|
|
|
|
/// `n` consecutive (x, y) pairs of 16- or 32-bit signed integers starting at `off`.
|
|
pub fn points(&self, off: usize, n: usize, wide: bool) -> Option<Vec<(f64, f64)>> {
|
|
let sz = if wide { 8 } else { 4 };
|
|
let raw = self.slice(off, n.checked_mul(sz)?)?;
|
|
let mut out = Vec::with_capacity(n);
|
|
for c in raw.chunks_exact(sz) {
|
|
let p = if wide {
|
|
let x = i32::from_le_bytes(c.get(0..4)?.try_into().ok()?);
|
|
let y = i32::from_le_bytes(c.get(4..8)?.try_into().ok()?);
|
|
(f64::from(x), f64::from(y))
|
|
} else {
|
|
let x = i16::from_le_bytes(c.get(0..2)?.try_into().ok()?);
|
|
let y = i16::from_le_bytes(c.get(2..4)?.try_into().ok()?);
|
|
(f64::from(x), f64::from(y))
|
|
};
|
|
out.push(p);
|
|
}
|
|
Some(out)
|
|
}
|
|
}
|