rwml 0.1.0

Native Rust toolkit for Microsoft Word — read, write, edit, and render legacy .doc (Word 97-2003, [MS-DOC]) and modern .docx (OOXML): one document model, package-preserving edits, field evaluation, Markdown/HTML export, PDF preview
Documentation
use std::io::Read;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum MetafileKind {
    Emf,
    Wmf,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct MetafileRaster {
    pub(crate) rgba: Vec<u8>,
    pub(crate) width_px: u32,
    pub(crate) height_px: u32,
}

pub(crate) fn format_for_part(name: &str) -> Option<(MetafileKind, bool)> {
    let ext = name.rsplit('.').next()?.to_ascii_lowercase();
    match ext.as_str() {
        "emf" => Some((MetafileKind::Emf, false)),
        "wmf" => Some((MetafileKind::Wmf, false)),
        "emz" => Some((MetafileKind::Emf, true)),
        "wmz" => Some((MetafileKind::Wmf, true)),
        _ => None,
    }
}

pub(crate) fn is_gzip_payload(bytes: &[u8]) -> bool {
    bytes.len() >= 2 && bytes[0] == 0x1f && bytes[1] == 0x8b
}

pub(crate) fn dimensions(
    kind: MetafileKind,
    bytes: &[u8],
    compressed: bool,
) -> (Option<u32>, Option<u32>) {
    let inflated = if compressed && is_gzip_payload(bytes) {
        inflate_gzip_metafile(bytes)
    } else {
        None
    };
    let payload = match (compressed, inflated.as_deref()) {
        (true, Some(payload)) => payload,
        (true, None) => return (None, None),
        (false, _) => bytes,
    };
    dimensions_uncompressed(kind, payload).unzip()
}

pub(crate) fn extract_raster(
    kind: MetafileKind,
    bytes: &[u8],
    compressed: bool,
) -> Option<MetafileRaster> {
    let inflated = if compressed && is_gzip_payload(bytes) {
        inflate_gzip_metafile(bytes)
    } else {
        None
    };
    let payload = match (compressed, inflated.as_deref()) {
        (true, Some(payload)) => payload,
        (true, None) => return None,
        (false, _) => bytes,
    };
    let (frame_w, frame_h) = dimensions_uncompressed(kind, payload)?;
    let raster = match kind {
        MetafileKind::Emf => extract_emf_stretchdibits(payload)?,
        MetafileKind::Wmf => extract_wmf_stretchdib(payload)?,
    };
    (raster.width_px == frame_w && raster.height_px == frame_h).then_some(raster)
}

fn inflate_gzip_metafile(bytes: &[u8]) -> Option<Vec<u8>> {
    const MAX_METAFILE_INFLATE: u64 = 1 << 20;
    let decoder = flate2::read::GzDecoder::new(bytes);
    let mut limited = decoder.take(MAX_METAFILE_INFLATE);
    let mut out = Vec::new();
    limited.read_to_end(&mut out).ok()?;
    (!out.is_empty()).then_some(out)
}

fn dimensions_uncompressed(kind: MetafileKind, bytes: &[u8]) -> Option<(u32, u32)> {
    match kind {
        MetafileKind::Emf => emf_dimensions(bytes),
        MetafileKind::Wmf => wmf_dimensions(bytes),
    }
}

fn emf_dimensions(bytes: &[u8]) -> Option<(u32, u32)> {
    if bytes.len() < 44 || read_u32le(bytes, 0)? != 1 {
        return None;
    }
    let header_size = read_u32le(bytes, 4)? as usize;
    if header_size < 44 || header_size > bytes.len() || bytes.get(40..44)? != b" EMF" {
        return None;
    }
    rect_dimensions(
        read_i32le(bytes, 8)?,
        read_i32le(bytes, 12)?,
        read_i32le(bytes, 16)?,
        read_i32le(bytes, 20)?,
    )
}

fn wmf_dimensions(bytes: &[u8]) -> Option<(u32, u32)> {
    if bytes.len() < 22 || read_u32le(bytes, 0)? != 0x9AC6CDD7 {
        return None;
    }
    let units_per_inch = read_u16le(bytes, 14)? as u32;
    if units_per_inch == 0 {
        return None;
    }
    let width_units = (read_i16le(bytes, 10)? as i32) - (read_i16le(bytes, 6)? as i32);
    let height_units = (read_i16le(bytes, 12)? as i32) - (read_i16le(bytes, 8)? as i32);
    let width_px = scale_wmf_units(width_units, units_per_inch)?;
    let height_px = scale_wmf_units(height_units, units_per_inch)?;
    Some((width_px, height_px))
}

fn extract_emf_stretchdibits(bytes: &[u8]) -> Option<MetafileRaster> {
    if bytes.len() < 44 || read_u32le(bytes, 0)? != 1 {
        return None;
    }
    let mut offset = 0usize;
    let mut stretchdibits = 0usize;
    let mut raster = None;
    while offset.checked_add(8)? <= bytes.len() {
        let record_type = read_u32le(bytes, offset)?;
        let record_size = read_u32le(bytes, offset + 4)? as usize;
        if record_size < 8 || record_size % 4 != 0 {
            return None;
        }
        let end = offset.checked_add(record_size)?;
        if end > bytes.len() {
            return None;
        }
        if record_type == 81 {
            stretchdibits += 1;
            if stretchdibits > 1 {
                return None;
            }
            raster = extract_emf_stretchdibits_record(&bytes[offset..end]);
        }
        offset = end;
    }
    (stretchdibits == 1).then_some(raster?)
}

fn extract_emf_stretchdibits_record(record: &[u8]) -> Option<MetafileRaster> {
    if record.len() < 64 {
        return None;
    }
    let off_bmi = read_u32le(record, 48)? as usize;
    let cb_bmi = read_u32le(record, 52)? as usize;
    let off_bits = read_u32le(record, 56)? as usize;
    let cb_bits = read_u32le(record, 60)? as usize;
    let bmi_end = off_bmi.checked_add(cb_bmi)?;
    let bits_end = off_bits.checked_add(cb_bits)?;
    let bmi = record.get(off_bmi..bmi_end)?;
    let bits = record.get(off_bits..bits_end)?;
    decode_dib(bmi, bits)
}

fn extract_wmf_stretchdib(bytes: &[u8]) -> Option<MetafileRaster> {
    let mut offset = if bytes.len() >= 22 && read_u32le(bytes, 0)? == 0x9AC6CDD7 {
        22usize
    } else {
        0usize
    };
    if bytes.len() < offset.checked_add(18)? {
        return None;
    }
    offset += 18;
    let mut stretchdib = 0usize;
    let mut raster = None;
    while offset.checked_add(6)? <= bytes.len() {
        let record_words = read_u32le(bytes, offset)? as usize;
        if record_words < 3 {
            return None;
        }
        let record_size = record_words.checked_mul(2)?;
        let end = offset.checked_add(record_size)?;
        if end > bytes.len() {
            return None;
        }
        let function = read_u16le(bytes, offset + 4)?;
        if function == 0 {
            break;
        }
        if function == 0x0F43 {
            stretchdib += 1;
            if stretchdib > 1 {
                return None;
            }
            let dib_start = offset.checked_add(6 + 22)?;
            let dib = bytes.get(dib_start..end)?;
            raster = decode_packed_dib(dib);
        }
        offset = end;
    }
    (stretchdib == 1).then_some(raster?)
}

fn decode_packed_dib(dib: &[u8]) -> Option<MetafileRaster> {
    let header_len = bitmap_info_header_len(dib)?;
    decode_dib(dib.get(..header_len)?, dib.get(header_len..)?)
}

fn bitmap_info_header_len(dib: &[u8]) -> Option<usize> {
    if dib.len() < 40 || read_u32le(dib, 0)? != 40 {
        return None;
    }
    let bit_count = read_u16le(dib, 14)?;
    let compression = read_u32le(dib, 16)?;
    let colors_used = read_u32le(dib, 32)?;
    if compression != 0 || !matches!(bit_count, 24 | 32) || colors_used != 0 {
        return None;
    }
    Some(40)
}

fn decode_dib(bmi: &[u8], bits: &[u8]) -> Option<MetafileRaster> {
    if bmi.len() < 40 || read_u32le(bmi, 0)? != 40 {
        return None;
    }
    let width = read_i32le(bmi, 4)?;
    let height = read_i32le(bmi, 8)?;
    let planes = read_u16le(bmi, 12)?;
    let bit_count = read_u16le(bmi, 14)?;
    let compression = read_u32le(bmi, 16)?;
    let colors_used = read_u32le(bmi, 32)?;
    if width <= 0
        || height == 0
        || planes != 1
        || compression != 0
        || colors_used != 0
        || !matches!(bit_count, 24 | 32)
    {
        return None;
    }
    let width = width as u32;
    let height_abs = height.unsigned_abs();
    let bytes_per_pixel = (bit_count / 8) as usize;
    let row_stride = dib_row_stride(width, bit_count)?;
    let needed = row_stride.checked_mul(height_abs as usize)?;
    if bits.len() < needed {
        return None;
    }
    let pixel_count = (width as usize).checked_mul(height_abs as usize)?;
    let mut rgba = vec![0u8; pixel_count.checked_mul(4)?];
    for y in 0..height_abs as usize {
        let src_y = if height < 0 {
            y
        } else {
            height_abs as usize - 1 - y
        };
        let row = bits.get(src_y * row_stride..src_y * row_stride + row_stride)?;
        for x in 0..width as usize {
            let src = x.checked_mul(bytes_per_pixel)?;
            let dst = (y.checked_mul(width as usize)?.checked_add(x)?).checked_mul(4)?;
            rgba[dst] = *row.get(src + 2)?;
            rgba[dst + 1] = *row.get(src + 1)?;
            rgba[dst + 2] = *row.get(src)?;
            rgba[dst + 3] = if bit_count == 32 {
                *row.get(src + 3)?
            } else {
                255
            };
        }
    }
    Some(MetafileRaster {
        rgba,
        width_px: width,
        height_px: height_abs,
    })
}

fn dib_row_stride(width: u32, bit_count: u16) -> Option<usize> {
    let bits = (width as usize).checked_mul(bit_count as usize)?;
    bits.checked_add(31)?.checked_div(32)?.checked_mul(4)
}

fn rect_dimensions(left: i32, top: i32, right: i32, bottom: i32) -> Option<(u32, u32)> {
    let width = right.checked_sub(left)?;
    let height = bottom.checked_sub(top)?;
    (width > 0 && height > 0).then_some((width as u32, height as u32))
}

fn scale_wmf_units(value: i32, units_per_inch: u32) -> Option<u32> {
    if value <= 0 {
        return None;
    }
    let value = value as u64;
    let units = units_per_inch as u64;
    let px = (value * 96 + units / 2) / units;
    (px > 0 && px <= u32::MAX as u64).then_some(px as u32)
}

fn read_u16le(bytes: &[u8], offset: usize) -> Option<u16> {
    let end = offset.checked_add(2)?;
    Some(u16::from_le_bytes(bytes.get(offset..end)?.try_into().ok()?))
}

fn read_i16le(bytes: &[u8], offset: usize) -> Option<i16> {
    let end = offset.checked_add(2)?;
    Some(i16::from_le_bytes(bytes.get(offset..end)?.try_into().ok()?))
}

fn read_u32le(bytes: &[u8], offset: usize) -> Option<u32> {
    let end = offset.checked_add(4)?;
    Some(u32::from_le_bytes(bytes.get(offset..end)?.try_into().ok()?))
}

fn read_i32le(bytes: &[u8], offset: usize) -> Option<i32> {
    let end = offset.checked_add(4)?;
    Some(i32::from_le_bytes(bytes.get(offset..end)?.try_into().ok()?))
}