use crate::common::error::{Error, Result};
use zerocopy::FromBytes;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum EmfRecordType {
Header = 0x00000001,
PolyBezier = 0x00000002,
Polygon = 0x00000003,
Polyline = 0x00000004,
PolyBezierTo = 0x00000005,
PolyLineTo = 0x00000006,
PolyPolyline = 0x00000007,
PolyPolygon = 0x00000008,
SetWindowExtEx = 0x00000009,
SetWindowOrgEx = 0x0000000A,
SetViewportExtEx = 0x0000000B,
SetViewportOrgEx = 0x0000000C,
SetBrushOrgEx = 0x0000000D,
Eof = 0x0000000E,
}
impl EmfRecordType {
pub fn from_u32(value: u32) -> Option<Self> {
match value {
0x00000001 => Some(Self::Header),
0x00000002 => Some(Self::PolyBezier),
0x00000003 => Some(Self::Polygon),
0x00000004 => Some(Self::Polyline),
0x00000005 => Some(Self::PolyBezierTo),
0x00000006 => Some(Self::PolyLineTo),
0x00000007 => Some(Self::PolyPolyline),
0x00000008 => Some(Self::PolyPolygon),
0x00000009 => Some(Self::SetWindowExtEx),
0x0000000A => Some(Self::SetWindowOrgEx),
0x0000000B => Some(Self::SetViewportExtEx),
0x0000000C => Some(Self::SetViewportOrgEx),
0x0000000D => Some(Self::SetBrushOrgEx),
0x0000000E => Some(Self::Eof),
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub struct EmfHeader {
pub bounds: (i32, i32, i32, i32),
pub frame: (i32, i32, i32, i32),
pub signature: u32,
pub version: u32,
pub size: u32,
pub num_records: u32,
pub num_handles: u16,
pub description_size: u16,
pub description_offset: u32,
pub num_palette: u32,
pub device_width: i32,
pub device_height: i32,
pub device_width_mm: i32,
pub device_height_mm: i32,
}
#[derive(Debug, Clone, FromBytes)]
#[repr(C)]
struct RawEmfHeader {
record_type: u32,
record_size: u32,
bounds_left: i32,
bounds_top: i32,
bounds_right: i32,
bounds_bottom: i32,
frame_left: i32,
frame_top: i32,
frame_right: i32,
frame_bottom: i32,
signature: u32,
version: u32,
size: u32,
num_records: u32,
num_handles: u16,
reserved: u16,
description_size: u32,
description_offset: u32,
num_palette: u32,
device_width: i32,
device_height: i32,
device_width_mm: i32,
device_height_mm: i32,
}
impl EmfHeader {
pub fn parse(data: &[u8]) -> Result<Self> {
if data.len() < 88 {
return Err(Error::ParseError("EMF header too short".into()));
}
let raw_header = RawEmfHeader::read_from_bytes(data)
.map_err(|_| Error::ParseError("Invalid EMF header format".into()))?;
if raw_header.record_type != 0x00000001 {
return Err(Error::ParseError(format!(
"Invalid EMF header record type: 0x{:08X}",
raw_header.record_type
)));
}
if raw_header.signature != 0x464D4520 {
return Err(Error::ParseError(format!(
"Invalid EMF signature: 0x{:08X}",
raw_header.signature
)));
}
Ok(Self {
bounds: (
raw_header.bounds_left,
raw_header.bounds_top,
raw_header.bounds_right,
raw_header.bounds_bottom,
),
frame: (
raw_header.frame_left,
raw_header.frame_top,
raw_header.frame_right,
raw_header.frame_bottom,
),
signature: raw_header.signature,
version: raw_header.version,
size: raw_header.size,
num_records: raw_header.num_records,
num_handles: raw_header.num_handles,
description_size: raw_header.description_size as u16,
description_offset: raw_header.description_offset,
num_palette: raw_header.num_palette,
device_width: raw_header.device_width,
device_height: raw_header.device_height,
device_width_mm: raw_header.device_width_mm,
device_height_mm: raw_header.device_height_mm,
})
}
pub fn width(&self) -> i32 {
self.bounds.2 - self.bounds.0
}
pub fn height(&self) -> i32 {
self.bounds.3 - self.bounds.1
}
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
}
}
}
#[derive(Debug, Clone)]
pub struct EmfRecord {
pub record_type: u32,
pub size: u32,
pub data: Vec<u8>,
}
#[derive(Debug, Clone, zerocopy::FromBytes)]
#[repr(C)]
struct RawEmfRecordHeader {
record_type: u32,
size: u32,
}
impl EmfRecord {
pub fn parse(data: &[u8], offset: usize) -> Result<(Self, usize)> {
if offset + 8 > data.len() {
return Err(Error::ParseError("Insufficient data for EMF record".into()));
}
let header = RawEmfRecordHeader::read_from_bytes(&data[offset..offset + 8])
.map_err(|_| Error::ParseError("Invalid EMF record header".into()))?;
let record_type = header.record_type;
let size = header.size;
if size < 8 || offset + size as usize > data.len() {
return Err(Error::ParseError(format!(
"Invalid EMF record size: {} at offset {}",
size, offset
)));
}
let record_data = data[offset + 8..offset + size as usize].to_vec();
Ok((
Self {
record_type,
size,
data: record_data,
},
size as usize,
))
}
}
#[derive(Debug)]
pub struct EmfParser {
pub header: EmfHeader,
pub records: Vec<EmfRecord>,
data: Vec<u8>,
}
impl EmfParser {
pub fn new(data: &[u8]) -> Result<Self> {
if data.len() < 88 {
return Err(Error::ParseError("EMF data too short".into()));
}
let header = EmfHeader::parse(data)?;
let mut records = Vec::new();
let mut offset = header.size as usize;
while offset < data.len() {
match EmfRecord::parse(data, offset) {
Ok((record, consumed)) => {
if record.record_type == 0x0000000E {
records.push(record);
break;
}
records.push(record);
offset += consumed;
}
Err(_) => break,
}
}
Ok(Self {
header,
records,
data: data.to_vec(),
})
}
pub fn data(&self) -> &[u8] {
&self.data
}
pub fn width(&self) -> i32 {
self.header.width()
}
pub fn height(&self) -> i32 {
self.header.height()
}
pub fn aspect_ratio(&self) -> f64 {
self.header.aspect_ratio()
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_emf_signature() {
assert_eq!(0x464D4520u32.to_le_bytes(), [0x20, 0x45, 0x4D, 0x46]);
}
}