use crate::common::error::{Error, Result};
use zerocopy::{FromBytes, BE, I16, U16};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PictVersion {
V1,
V2,
}
#[derive(Debug, Clone)]
pub struct PictHeader {
pub version: PictVersion,
pub frame: (i16, i16, i16, i16),
pub has_512_header: bool,
}
impl PictHeader {
pub fn parse(data: &[u8]) -> Result<Self> {
if data.len() < 10 {
return Err(Error::ParseError("PICT data too short".into()));
}
let mut offset = 0;
let has_512_header = data.len() > 512 && Self::check_512_header(data);
if has_512_header {
offset = 512;
}
if offset + 10 > data.len() {
return Err(Error::ParseError("Insufficient data for PICT header".into()));
}
let _pic_size = U16::<BE>::read_from_bytes(&data[offset..offset + 2])
.map_err(|_| Error::ParseError("Failed to read pic size".into()))?
.get();
offset += 2;
let top = I16::<BE>::read_from_bytes(&data[offset..offset + 2])
.map_err(|_| Error::ParseError("Failed to read top".into()))?
.get();
let left = I16::<BE>::read_from_bytes(&data[offset + 2..offset + 4])
.map_err(|_| Error::ParseError("Failed to read left".into()))?
.get();
let bottom = I16::<BE>::read_from_bytes(&data[offset + 4..offset + 6])
.map_err(|_| Error::ParseError("Failed to read bottom".into()))?
.get();
let right = I16::<BE>::read_from_bytes(&data[offset + 6..offset + 8])
.map_err(|_| Error::ParseError("Failed to read right".into()))?
.get();
offset += 8;
let version = if offset + 4 <= data.len() {
let op1 = U16::<BE>::read_from_bytes(&data[offset..offset + 2])
.map_err(|_| Error::ParseError("Failed to read op1".into()))?
.get();
let op2 = U16::<BE>::read_from_bytes(&data[offset + 2..offset + 4])
.map_err(|_| Error::ParseError("Failed to read op2".into()))?
.get();
if op1 == 0x0011 && op2 == 0x02FF {
PictVersion::V2
} else {
PictVersion::V1
}
} else {
PictVersion::V1
};
Ok(Self {
version,
frame: (top, left, bottom, right),
has_512_header,
})
}
fn check_512_header(data: &[u8]) -> bool {
if data.len() < 522 {
return false;
}
let potential_size = U16::<BE>::read_from_bytes(&data[512..514])
.map(|v| v.get())
.unwrap_or(0);
let potential_opcode = U16::<BE>::read_from_bytes(&data[522..524])
.map(|v| v.get())
.unwrap_or(0);
potential_opcode == 0x0011 || potential_size < 0x4000
}
pub fn width(&self) -> i16 {
self.frame.3 - self.frame.1
}
pub fn height(&self) -> i16 {
self.frame.2 - self.frame.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PictOpcode {
Nop = 0x0000,
Clip = 0x0001,
BkPat = 0x0002,
TxFont = 0x0003,
TxFace = 0x0004,
TxMode = 0x0005,
Version = 0x0011,
HeaderOp = 0x0C00,
EndPic = 0x00FF,
DirectBitsRect = 0x009A,
PackedDirectBitsRect = 0x009B,
CompressedQuickTime = 0x8200,
}
impl PictOpcode {
pub fn from_u16(value: u16) -> Option<Self> {
match value {
0x0000 => Some(Self::Nop),
0x0001 => Some(Self::Clip),
0x0002 => Some(Self::BkPat),
0x0003 => Some(Self::TxFont),
0x0004 => Some(Self::TxFace),
0x0005 => Some(Self::TxMode),
0x0011 => Some(Self::Version),
0x0C00 => Some(Self::HeaderOp),
0x00FF => Some(Self::EndPic),
0x009A => Some(Self::DirectBitsRect),
0x009B => Some(Self::PackedDirectBitsRect),
0x8200 => Some(Self::CompressedQuickTime),
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub struct PictRecord {
pub opcode: u16,
pub data: Vec<u8>,
}
pub struct PictParser {
pub header: PictHeader,
pub records: Vec<PictRecord>,
data: Vec<u8>,
}
impl PictParser {
pub fn new(data: &[u8]) -> Result<Self> {
let header = PictHeader::parse(data)?;
let data_start = if header.has_512_header { 512 } else { 0 };
let mut offset = data_start + 10;
if header.version == PictVersion::V2 && offset + 4 <= data.len() {
offset += 4; }
let mut records = Vec::new();
while offset < data.len() {
if offset + 2 > data.len() {
break;
}
let opcode = U16::<BE>::read_from_bytes(&data[offset..offset + 2])
.map_err(|_| Error::ParseError("Failed to read opcode".into()))?
.get();
offset += 2;
if opcode == 0x00FF {
records.push(PictRecord {
opcode,
data: Vec::new(),
});
break;
}
let data_size = Self::get_opcode_data_size(opcode, data, offset)?;
if offset + data_size > data.len() {
break;
}
let record_data = data[offset..offset + data_size].to_vec();
offset += data_size;
records.push(PictRecord {
opcode,
data: record_data,
});
}
Ok(Self {
header,
records,
data: data.to_vec(),
})
}
fn get_opcode_data_size(opcode: u16, data: &[u8], offset: usize) -> Result<usize> {
match opcode {
0x0000 => Ok(0), 0x0003 => Ok(2), 0x0004 => Ok(1), 0x0005 => Ok(2), 0x0011 => Ok(2),
0x0001 | 0x00A1 | 0x009A | 0x009B => { if offset + 2 > data.len() {
return Err(Error::ParseError("Insufficient data for opcode size".into()));
}
let size = U16::<BE>::read_from_bytes(&data[offset..offset + 2])
.map_err(|_| Error::ParseError("Failed to read size".into()))?
.get() as usize;
Ok(size + 2) }
_ => {
if offset + 2 > data.len() {
return Ok(0);
}
let size = U16::<BE>::read_from_bytes(&data[offset..offset + 2])
.map_err(|_| Error::ParseError("Failed to read size".into()))?
.get() as usize;
Ok(size.min(data.len() - offset))
}
}
}
pub fn data(&self) -> &[u8] {
&self.data
}
pub fn width(&self) -> i32 {
self.header.width() as i32
}
pub fn height(&self) -> i32 {
self.header.height() as i32
}
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_pict_opcodes() {
assert_eq!(PictOpcode::from_u16(0x00FF), Some(PictOpcode::EndPic));
assert_eq!(PictOpcode::from_u16(0x0011), Some(PictOpcode::Version));
}
}