use crate::common::error::Result;
use crate::ole::binary::{read_u16_le, read_u32_le};
use std::io::Read;
use zerocopy::FromBytes;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BlipType {
Emf = 0xF01A,
Wmf = 0xF01B,
Pict = 0xF01C,
Jpeg = 0xF01D,
Png = 0xF01E,
Dib = 0xF01F,
Tiff = 0xF029,
}
impl BlipType {
pub fn from_record_id(record_id: u16) -> Option<Self> {
match record_id {
0xF01A => Some(Self::Emf),
0xF01B => Some(Self::Wmf),
0xF01C => Some(Self::Pict),
0xF01D => Some(Self::Jpeg),
0xF01E => Some(Self::Png),
0xF01F => Some(Self::Dib),
0xF029 => Some(Self::Tiff),
_ => None,
}
}
pub const fn is_metafile(&self) -> bool {
matches!(self, Self::Emf | Self::Wmf | Self::Pict)
}
pub const fn is_bitmap(&self) -> bool {
!self.is_metafile()
}
pub const fn extension(&self) -> &'static str {
match self {
Self::Emf => "emf",
Self::Wmf => "wmf",
Self::Pict => "pict",
Self::Jpeg => "jpg",
Self::Png => "png",
Self::Dib => "dib",
Self::Tiff => "tiff",
}
}
}
#[derive(Debug, Clone)]
pub struct RecordHeader {
pub version: u8,
pub instance: u16,
pub record_type: u16,
pub length: u32,
}
impl RecordHeader {
pub fn parse(data: &[u8]) -> Result<Self> {
if data.len() < 8 {
return Err(crate::common::error::Error::ParseError(
"Insufficient data for record header".into(),
));
}
let ver_inst = read_u16_le(data, 0).unwrap_or(0);
let version = (ver_inst & 0x0F) as u8;
let instance = (ver_inst >> 4) & 0xFFF;
let record_type = read_u16_le(data, 2).unwrap_or(0);
let length = read_u32_le(data, 4).unwrap_or(0);
Ok(Self {
version,
instance,
record_type,
length,
})
}
pub const fn options(&self) -> u16 {
(self.instance << 4) | (self.version as u16)
}
}
#[derive(Debug, Clone)]
pub struct MetafileBlip {
pub header: RecordHeader,
pub uid: [u8; 16],
pub secondary_uid: Option<[u8; 16]>,
pub uncompressed_size: u32,
pub bounds: (i32, i32, i32, i32),
pub size_emu: (i32, i32),
pub compressed_size: u32,
pub compression: u8,
pub filter: u8,
pub picture_data: Vec<u8>,
}
#[derive(Debug, Clone, FromBytes)]
#[repr(C)]
struct RawMetafileMetadata {
pub uncompressed_size: u32,
pub bounds_left: i32,
pub bounds_top: i32,
pub bounds_right: i32,
pub bounds_bottom: i32,
pub size_width: i32,
pub size_height: i32,
pub compressed_size: u32,
pub compression: u8,
pub filter: u8,
}
impl MetafileBlip {
pub fn parse(data: &[u8]) -> Result<Self> {
let header = RecordHeader::parse(data)?;
let mut offset = 8;
if offset + 16 > data.len() {
return Err(crate::common::error::Error::ParseError(
"Insufficient data for UID".into(),
));
}
let mut uid = [0u8; 16];
uid.copy_from_slice(&data[offset..offset + 16]);
offset += 16;
let signature = Self::get_signature(header.record_type);
let has_secondary = (header.options() ^ signature) == 0x10;
let secondary_uid = if has_secondary {
if offset + 16 > data.len() {
return Err(crate::common::error::Error::ParseError(
"Insufficient data for secondary UID".into(),
));
}
let mut sec_uid = [0u8; 16];
sec_uid.copy_from_slice(&data[offset..offset + 16]);
offset += 16;
Some(sec_uid)
} else {
None
};
if offset + 34 > data.len() {
return Err(crate::common::error::Error::ParseError(
"Insufficient data for metafile metadata".into(),
));
}
let metadata = RawMetafileMetadata::read_from_bytes(&data[offset..offset + 34])
.map_err(|_| crate::common::error::Error::ParseError("Invalid metafile metadata format".into()))?;
offset += 34;
let pic_data_len = metadata.compressed_size as usize;
if offset + pic_data_len > data.len() {
return Err(crate::common::error::Error::ParseError(
"Insufficient data for picture data".into(),
));
}
let picture_data = data[offset..offset + pic_data_len].to_vec();
Ok(Self {
header,
uid,
secondary_uid,
uncompressed_size: metadata.uncompressed_size,
bounds: (
metadata.bounds_left,
metadata.bounds_top,
metadata.bounds_right,
metadata.bounds_bottom,
),
size_emu: (metadata.size_width, metadata.size_height),
compressed_size: metadata.compressed_size,
compression: metadata.compression,
filter: metadata.filter,
picture_data,
})
}
fn get_signature(record_type: u16) -> u16 {
match record_type {
0xF01A => 0x3D4, 0xF01B => 0x216, 0xF01C => 0x542, _ => 0,
}
}
pub const fn is_compressed(&self) -> bool {
self.compression == 0
}
pub fn decompress(&self) -> Result<Vec<u8>> {
if !self.is_compressed() {
return Ok(self.picture_data.clone());
}
let mut decoder = flate2::read::DeflateDecoder::new(&self.picture_data[..]);
let mut decompressed = Vec::with_capacity(self.uncompressed_size as usize);
decoder
.read_to_end(&mut decompressed)
.map_err(|e| crate::common::error::Error::ParseError(format!("Decompression failed: {}", e)))?;
Ok(decompressed)
}
pub fn blip_type(&self) -> Option<BlipType> {
BlipType::from_record_id(self.header.record_type)
}
}
#[derive(Debug, Clone)]
pub struct BitmapBlip {
pub header: RecordHeader,
pub uid: [u8; 16],
pub marker: u8,
pub picture_data: Vec<u8>,
}
impl BitmapBlip {
pub fn parse(data: &[u8]) -> Result<Self> {
let header = RecordHeader::parse(data)?;
let mut offset = 8;
if offset + 16 > data.len() {
return Err(crate::common::error::Error::ParseError(
"Insufficient data for UID".into(),
));
}
let mut uid = [0u8; 16];
uid.copy_from_slice(&data[offset..offset + 16]);
offset += 16;
if offset >= data.len() {
return Err(crate::common::error::Error::ParseError(
"Insufficient data for marker".into(),
));
}
let marker = data[offset];
offset += 1;
let picture_data = data[offset..].to_vec();
Ok(Self {
header,
uid,
marker,
picture_data,
})
}
pub fn blip_type(&self) -> Option<BlipType> {
BlipType::from_record_id(self.header.record_type)
}
}
#[derive(Debug, Clone)]
pub enum Blip {
Metafile(MetafileBlip),
Bitmap(BitmapBlip),
}
impl Blip {
pub fn parse(data: &[u8]) -> Result<Self> {
if data.len() < 8 {
return Err(crate::common::error::Error::ParseError(
"Insufficient data for BLIP record".into(),
));
}
let header = RecordHeader::parse(data)?;
let blip_type = BlipType::from_record_id(header.record_type)
.ok_or_else(|| {
crate::common::error::Error::ParseError(format!(
"Unknown BLIP record type: 0x{:04X}",
header.record_type
))
})?;
match blip_type {
BlipType::Emf | BlipType::Wmf | BlipType::Pict => {
Ok(Self::Metafile(MetafileBlip::parse(data)?))
}
BlipType::Jpeg | BlipType::Png | BlipType::Dib | BlipType::Tiff => {
Ok(Self::Bitmap(BitmapBlip::parse(data)?))
}
}
}
pub fn blip_type(&self) -> Option<BlipType> {
match self {
Self::Metafile(m) => m.blip_type(),
Self::Bitmap(b) => b.blip_type(),
}
}
pub fn picture_data(&self) -> &[u8] {
match self {
Self::Metafile(m) => &m.picture_data,
Self::Bitmap(b) => &b.picture_data,
}
}
pub fn get_decompressed_data(&self) -> Result<Vec<u8>> {
match self {
Self::Metafile(m) => m.decompress(),
Self::Bitmap(b) => Ok(b.picture_data.clone()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_blip_type_metafile() {
assert!(BlipType::Emf.is_metafile());
assert!(BlipType::Wmf.is_metafile());
assert!(BlipType::Pict.is_metafile());
assert!(!BlipType::Jpeg.is_metafile());
}
#[test]
fn test_blip_type_extension() {
assert_eq!(BlipType::Emf.extension(), "emf");
assert_eq!(BlipType::Png.extension(), "png");
}
}