const PLACEABLE_MAGIC: [u8; 4] = [0xD7, 0xCD, 0xC6, 0x9A];
const STANDARD_MAGIC: [u8; 4] = [0x01, 0x00, 0x09, 0x00];
const META_DIBBITBLT: u16 = 0x0940; const META_DIBSTRETCHBLT: u16 = 0x0B41; const META_STRETCHDIB: u16 = 0x0F43;
pub(super) fn is_wmf(data: &[u8]) -> bool {
data.len() >= 4 && (data[..4] == PLACEABLE_MAGIC || data[..4] == STANDARD_MAGIC)
}
struct DibBlit<'a> {
dib: &'a [u8],
x: i32,
y: i32,
w: i32,
h: i32,
}
fn collect_dib_blits(data: &[u8]) -> Option<Vec<DibBlit<'_>>> {
let header_skip = if data.starts_with(&PLACEABLE_MAGIC) {
22 + 18 } else if data.starts_with(&STANDARD_MAGIC) {
18
} else {
return None;
};
let mut blits = Vec::new();
let mut i = header_skip;
while i + 6 <= data.len() {
let size_words = u32::from_le_bytes(data[i..i + 4].try_into().ok()?) as usize;
let func = u16::from_le_bytes(data[i + 4..i + 6].try_into().ok()?);
let size_bytes = size_words.checked_mul(2)?;
if size_bytes < 6 || i.checked_add(size_bytes)? > data.len() {
return None;
}
if size_words == (func >> 8) as usize + 3 {
i += size_bytes;
continue;
}
let params_before_dib = match func {
META_DIBBITBLT => 16,
META_DIBSTRETCHBLT => 20,
META_STRETCHDIB => 22,
_ => {
i += size_bytes;
continue;
}
};
let dib_start = i + 6 + params_before_dib;
let dib_end = i + size_bytes;
if dib_start >= dib_end {
return None;
}
let params = &data[i + 6..dib_start];
let i16_at =
|off: usize| -> i32 { i16::from_le_bytes([params[off], params[off + 1]]) as i32 };
let (h, w, y, x) = match func {
META_DIBBITBLT => (i16_at(8), i16_at(10), i16_at(12), i16_at(14)),
META_DIBSTRETCHBLT => (i16_at(12), i16_at(14), i16_at(16), i16_at(18)),
_ => (i16_at(14), i16_at(16), i16_at(18), i16_at(20)),
};
let dib = &data[dib_start..dib_end];
if dib_pixel_count(dib).is_some_and(|n| n <= 64) {
i += size_bytes;
continue;
}
blits.push(DibBlit { dib, x, y, w, h });
i += size_bytes;
}
Some(blits)
}
fn dib_pixel_count(dib: &[u8]) -> Option<u64> {
let header = u32::from_le_bytes(dib.get(0..4)?.try_into().ok()?);
let (w, h) = if header == 12 {
let w = u16::from_le_bytes(dib.get(4..6)?.try_into().ok()?) as i64;
let h = u16::from_le_bytes(dib.get(6..8)?.try_into().ok()?) as i64;
(w, h)
} else {
let w = i32::from_le_bytes(dib.get(4..8)?.try_into().ok()?) as i64;
let h = i32::from_le_bytes(dib.get(8..12)?.try_into().ok()?) as i64;
(w, h)
};
Some(w.unsigned_abs() * h.unsigned_abs())
}
const META_ESCAPE: u16 = 0x0626;
const MFCOMMENT: u16 = 0x000F;
const WMFC_HEADER: usize = 34;
pub(super) fn embedded_emf(data: &[u8]) -> Option<Vec<u8>> {
let mut i = if data.starts_with(&PLACEABLE_MAGIC) {
40
} else {
18
};
let mut emf = Vec::new();
let mut total = 0usize;
while i + 6 <= data.len() {
let size_bytes =
(u32::from_le_bytes(data[i..i + 4].try_into().ok()?) as usize).checked_mul(2)?;
let func = u16::from_le_bytes(data[i + 4..i + 6].try_into().ok()?);
if size_bytes < 6 || i + size_bytes > data.len() {
break;
}
let rec = &data[i + 6..i + size_bytes];
if func == META_ESCAPE
&& rec.len() >= 4 + WMFC_HEADER
&& u16::from_le_bytes([rec[0], rec[1]]) == MFCOMMENT
&& &rec[4..8] == b"WMFC"
{
let u32_at =
|o: usize| u32::from_le_bytes(rec[4 + o..8 + o].try_into().unwrap()) as usize;
let chunk = u32_at(22);
total = u32_at(30);
let start = 4 + WMFC_HEADER;
emf.extend_from_slice(rec.get(start..start + chunk)?);
}
i += size_bytes;
}
(total > 0 && emf.len() == total && super::emf::is_emf(&emf)).then_some(emf)
}
pub(super) fn wmf_to_raster(data: &[u8]) -> Option<Vec<u8>> {
let blits = collect_dib_blits(data)?;
match blits.as_slice() {
[] => None,
[only] => dib_to_bmp(only.dib),
many => composite_blits(many),
}
}
fn composite_blits(blits: &[DibBlit]) -> Option<Vec<u8>> {
let mut decoded: Vec<(image::RgbaImage, &DibBlit)> = Vec::new();
let (mut min_x, mut min_y, mut max_x, mut max_y) = (i32::MAX, i32::MAX, i32::MIN, i32::MIN);
for b in blits {
if b.w <= 0 || b.h <= 0 {
continue;
}
let bmp = dib_to_bmp(b.dib)?;
let img = image::load_from_memory_with_format(&bmp, image::ImageFormat::Bmp)
.ok()?
.to_rgba8();
min_x = min_x.min(b.x);
min_y = min_y.min(b.y);
max_x = max_x.max(b.x + b.w);
max_y = max_y.max(b.y + b.h);
decoded.push((img, b));
}
if decoded.is_empty() {
return None;
}
let span_w = (max_x - min_x).max(1) as f32;
let span_h = (max_y - min_y).max(1) as f32;
let mut scale = 1.0_f32;
for (img, b) in &decoded {
scale = scale.max(img.width() as f32 / b.w as f32);
scale = scale.max(img.height() as f32 / b.h as f32);
}
scale = scale.clamp(1.0, 4.0);
let canvas_w = ((span_w * scale).round() as u32).clamp(1, 5000);
let canvas_h = ((span_h * scale).round() as u32).clamp(1, 5000);
let mut canvas =
image::RgbaImage::from_pixel(canvas_w, canvas_h, image::Rgba([255, 255, 255, 255]));
for (img, b) in &decoded {
let dw = ((b.w as f32 * scale).round() as u32).max(1);
let dh = ((b.h as f32 * scale).round() as u32).max(1);
let resized = image::imageops::resize(img, dw, dh, image::imageops::FilterType::Triangle);
let px = ((b.x - min_x) as f32 * scale).round() as i64;
let py = ((b.y - min_y) as f32 * scale).round() as i64;
image::imageops::replace(&mut canvas, &resized, px, py);
}
let mut out = std::io::Cursor::new(Vec::new());
image::DynamicImage::ImageRgba8(canvas)
.write_to(&mut out, image::ImageFormat::Png)
.ok()?;
Some(out.into_inner())
}
fn dib_to_bmp(dib: &[u8]) -> Option<Vec<u8>> {
if dib.len() < 20 {
return None;
}
let bih_size = u32::from_le_bytes(dib[0..4].try_into().ok()?) as usize;
if bih_size < 12 || bih_size > dib.len() {
return None;
}
let bpp = u16::from_le_bytes(dib[14..16].try_into().ok()?) as usize;
let palette_bytes = if bpp <= 8 { (1usize << bpp) * 4 } else { 0 };
let info_len = bih_size + palette_bytes;
if info_len > dib.len() {
return None;
}
Some(bmp_from_parts(&dib[..info_len], &dib[info_len..]))
}
pub(super) fn bmp_from_parts(info: &[u8], bits: &[u8]) -> Vec<u8> {
let pixel_off = 14 + info.len();
let mut bmp = Vec::with_capacity(pixel_off + bits.len());
bmp.extend_from_slice(b"BM");
bmp.extend_from_slice(&((pixel_off + bits.len()) as u32).to_le_bytes());
bmp.extend_from_slice(&[0u8; 4]); bmp.extend_from_slice(&(pixel_off as u32).to_le_bytes());
bmp.extend_from_slice(info);
bmp.extend_from_slice(bits);
bmp
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rejects_non_wmf() {
assert!(!is_wmf(b"\x89PNG\r\n\x1a\n"));
assert!(!is_wmf(b""));
assert!(wmf_to_raster(b"\x89PNG\r\n\x1a\n").is_none());
}
#[test]
fn embedded_emf_joins_wmfc_chunks() {
let mut emf = vec![0u8; 92];
emf[0..4].copy_from_slice(&1u32.to_le_bytes());
emf[40..44].copy_from_slice(b" EMF");
let mut wmf = vec![0u8; 18];
for chunk in [&emf[..50], &emf[50..]] {
let mut rec = vec![0x0F, 0x00, 0, 0];
rec.extend_from_slice(b"WMFC");
rec.extend_from_slice(&[0u8; 18]);
rec.extend_from_slice(&(chunk.len() as u32).to_le_bytes());
rec.extend_from_slice(&0u32.to_le_bytes());
rec.extend_from_slice(&(emf.len() as u32).to_le_bytes());
rec.extend_from_slice(chunk);
wmf.extend_from_slice(&((6 + rec.len()) as u32 / 2).to_le_bytes());
wmf.extend_from_slice(&META_ESCAPE.to_le_bytes());
wmf.extend_from_slice(&rec);
}
wmf.extend_from_slice(&3u32.to_le_bytes());
wmf.extend_from_slice(&0u16.to_le_bytes());
assert_eq!(embedded_emf(&wmf), Some(emf.clone()));
assert_eq!(embedded_emf(&wmf[..18 + 94]), None);
}
#[test]
fn dib_to_bmp_24bpp_roundtrip() {
let mut dib = Vec::new();
dib.extend_from_slice(&40u32.to_le_bytes()); dib.extend_from_slice(&2i32.to_le_bytes()); dib.extend_from_slice(&1i32.to_le_bytes()); dib.extend_from_slice(&1u16.to_le_bytes()); dib.extend_from_slice(&24u16.to_le_bytes()); dib.extend_from_slice(&0u32.to_le_bytes()); dib.extend_from_slice(&8u32.to_le_bytes()); dib.extend_from_slice(&0i32.to_le_bytes()); dib.extend_from_slice(&0i32.to_le_bytes()); dib.extend_from_slice(&0u32.to_le_bytes()); dib.extend_from_slice(&0u32.to_le_bytes()); dib.extend_from_slice(&[0xFF, 0, 0, 0, 0xFF, 0, 0, 0]);
let bmp = dib_to_bmp(&dib).expect("converts");
assert_eq!(&bmp[..2], b"BM");
let pixel_off = u32::from_le_bytes(bmp[10..14].try_into().unwrap());
assert_eq!(pixel_off as usize, 14 + 40); let file_size = u32::from_le_bytes(bmp[2..6].try_into().unwrap());
assert_eq!(file_size as usize, bmp.len());
}
}