use crate::common::error::{Error, Result};
use zerocopy::FromBytes;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WmfFileType {
Memory = 1,
Disk = 2,
}
#[derive(Debug, Clone)]
pub struct WmfPlaceableHeader {
pub key: u32,
pub left: i16,
pub top: i16,
pub right: i16,
pub bottom: i16,
pub inch: u16,
pub checksum: u16,
}
impl WmfPlaceableHeader {
const PLACEABLE_KEY: u32 = 0x9AC6CDD7;
pub fn is_placeable(data: &[u8]) -> bool {
if data.len() < 4 {
return false;
}
let key = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
key == Self::PLACEABLE_KEY
}
pub fn parse(data: &[u8]) -> Result<Self> {
if data.len() < 22 {
return Err(Error::ParseError("WMF placeable header too short".into()));
}
let raw_header = RawWmfPlaceableHeader::read_from_bytes(data)
.map_err(|_| Error::ParseError("Invalid WMF placeable header format".into()))?;
if raw_header.key != Self::PLACEABLE_KEY {
return Err(Error::ParseError(format!(
"Invalid WMF placeable key: 0x{:08X}",
raw_header.key
)));
}
Ok(Self {
key: raw_header.key,
left: raw_header.left,
top: raw_header.top,
right: raw_header.right,
bottom: raw_header.bottom,
inch: raw_header.inch,
checksum: raw_header.checksum,
})
}
pub fn width(&self) -> i16 {
self.right - self.left
}
pub fn height(&self) -> i16 {
self.bottom - self.top
}
}
#[derive(Debug, Clone)]
pub struct WmfHeader {
pub file_type: u16,
pub header_size: u16,
pub version: u16,
pub file_size: u32,
pub num_objects: u16,
pub max_record: u32,
pub num_params: u16,
}
#[derive(Debug, Clone, FromBytes)]
#[repr(C)]
struct RawWmfPlaceableHeader {
key: u32,
handle: u16,
left: i16,
top: i16,
right: i16,
bottom: i16,
inch: u16,
reserved: u32,
checksum: u16,
}
#[derive(Debug, Clone, FromBytes)]
#[repr(C)]
struct RawWmfHeader {
file_type: u16,
header_size: u16,
version: u16,
file_size: u32,
num_objects: u16,
max_record: u32,
num_params: u16,
}
#[derive(Debug, Clone, FromBytes)]
#[repr(C)]
struct RawWmfRecordHeader {
size: u32,
function: u16,
}
impl WmfHeader {
pub fn parse(data: &[u8]) -> Result<Self> {
if data.len() < 18 {
return Err(Error::ParseError("WMF header too short".into()));
}
let raw_header = RawWmfHeader::read_from_bytes(data)
.map_err(|_| Error::ParseError("Invalid WMF header format".into()))?;
Ok(Self {
file_type: raw_header.file_type,
header_size: raw_header.header_size,
version: raw_header.version,
file_size: raw_header.file_size,
num_objects: raw_header.num_objects,
max_record: raw_header.max_record,
num_params: raw_header.num_params,
})
}
}
#[derive(Debug, Clone)]
pub struct WmfRecord {
pub size: u32,
pub function: u16,
pub params: Vec<u8>,
}
impl WmfRecord {
pub fn parse(data: &[u8], offset: usize) -> Result<(Self, usize)> {
if offset + 6 > data.len() {
return Err(Error::ParseError("Insufficient data for WMF record".into()));
}
let header = RawWmfRecordHeader::read_from_bytes(&data[offset..offset + 6])
.map_err(|_| Error::ParseError("Invalid WMF record header".into()))?;
let size = header.size;
let function = header.function;
let size_bytes = (size as usize) * 2;
if size < 3 || offset + size_bytes > data.len() {
return Err(Error::ParseError(format!(
"Invalid WMF record size: {} at offset {}",
size, offset
)));
}
let param_size = size_bytes - 6;
let params = data[offset + 6..offset + 6 + param_size].to_vec();
Ok((Self { size, function, params }, size_bytes))
}
pub const fn is_eof(&self) -> bool {
self.function == 0x0000
}
}
#[derive(Debug)]
pub struct WmfParser {
pub placeable: Option<WmfPlaceableHeader>,
pub header: WmfHeader,
pub records: Vec<WmfRecord>,
data: Vec<u8>,
}
impl WmfParser {
pub fn new(data: &[u8]) -> Result<Self> {
let mut offset = 0;
let placeable = if WmfPlaceableHeader::is_placeable(data) {
let header = WmfPlaceableHeader::parse(data)?;
offset = 22; Some(header)
} else {
None
};
if offset + 18 > data.len() {
return Err(Error::ParseError("WMF data too short for header".into()));
}
let header = WmfHeader::parse(&data[offset..])?;
offset += 18;
let mut records = Vec::new();
while offset < data.len() {
match WmfRecord::parse(data, offset) {
Ok((record, consumed)) => {
let is_eof = record.is_eof();
records.push(record);
offset += consumed;
if is_eof {
break;
}
}
Err(_) => break,
}
}
Ok(Self {
placeable,
header,
records,
data: data.to_vec(),
})
}
pub fn data(&self) -> &[u8] {
&self.data
}
pub fn width(&self) -> i32 {
if let Some(ref placeable) = self.placeable {
placeable.width() as i32
} else {
1000
}
}
pub fn height(&self) -> i32 {
if let Some(ref placeable) = self.placeable {
placeable.height() as i32
} else {
1000
}
}
pub fn aspect_ratio(&self) -> f64 {
let w = self.width() as f64;
let h = self.height() as f64;
if h == 0.0 {
1.0
} else {
w / h
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_placeable_key() {
assert_eq!(WmfPlaceableHeader::PLACEABLE_KEY, 0x9AC6CDD7);
}
}