use crate::error::IffError;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Rotation {
None,
Ccw90,
Rot180,
Cw90,
}
#[derive(Debug, Clone, PartialEq)]
pub struct PageInfo {
pub width: u16,
pub height: u16,
pub dpi: u16,
pub gamma: f32,
pub rotation: Rotation,
}
impl PageInfo {
pub fn parse(data: &[u8]) -> Result<Self, IffError> {
if data.len() < 10 {
return Err(IffError::Truncated);
}
let width = u16::from_be_bytes(data[0..2].try_into().map_err(|_| IffError::Truncated)?);
let height = u16::from_be_bytes(data[2..4].try_into().map_err(|_| IffError::Truncated)?);
let dpi = u16::from_le_bytes(data[6..8].try_into().map_err(|_| IffError::Truncated)?);
let gamma_byte = data[8];
let gamma = if gamma_byte == 0 {
2.2_f32 } else {
gamma_byte as f32 / 10.0
};
let flags = data[9];
let rotation = match flags & 0x07 {
5 => Rotation::Cw90,
2 => Rotation::Rot180,
6 => Rotation::Ccw90,
_ => Rotation::None,
};
Ok(PageInfo {
width,
height,
dpi,
gamma,
rotation,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
fn chicken_info_bytes() -> [u8; 10] {
[
0x00, 0xB5, 0x00, 0xF0, 0x18, 0x00, 0x64, 0x00, 0x16, 0x00, ]
}
#[test]
fn parse_chicken_info() {
let info = PageInfo::parse(&chicken_info_bytes()).expect("should parse");
assert_eq!(info.width, 181);
assert_eq!(info.height, 240);
assert_eq!(info.dpi, 100);
assert!((info.gamma - 2.2).abs() < 0.01, "gamma should be 2.2");
assert_eq!(info.rotation, Rotation::None);
}
#[test]
fn too_short_is_error() {
let data = [0u8; 9]; assert_eq!(PageInfo::parse(&data).unwrap_err(), IffError::Truncated);
}
#[test]
fn empty_is_error() {
assert_eq!(PageInfo::parse(&[]).unwrap_err(), IffError::Truncated);
}
#[test]
fn rotation_none() {
let mut bytes = chicken_info_bytes();
bytes[9] = 0x00; let info = PageInfo::parse(&bytes).unwrap();
assert_eq!(info.rotation, Rotation::None);
}
#[test]
fn rotation_flag1_is_none() {
let mut bytes = chicken_info_bytes();
bytes[9] = 0x01;
let info = PageInfo::parse(&bytes).unwrap();
assert_eq!(info.rotation, Rotation::None);
}
#[test]
fn rotation_flag2_is_180() {
let mut bytes = chicken_info_bytes();
bytes[9] = 0x02;
let info = PageInfo::parse(&bytes).unwrap();
assert_eq!(info.rotation, Rotation::Rot180);
}
#[test]
fn rotation_flag5_is_cw90() {
let mut bytes = chicken_info_bytes();
bytes[9] = 0x05;
let info = PageInfo::parse(&bytes).unwrap();
assert_eq!(info.rotation, Rotation::Cw90);
}
#[test]
fn rotation_flag6_is_ccw90() {
let mut bytes = chicken_info_bytes();
bytes[9] = 0x06;
let info = PageInfo::parse(&bytes).unwrap();
assert_eq!(info.rotation, Rotation::Ccw90);
}
#[test]
fn gamma_zero_defaults_to_2_2() {
let mut bytes = chicken_info_bytes();
bytes[8] = 0x00; let info = PageInfo::parse(&bytes).unwrap();
assert!(
(info.gamma - 2.2).abs() < 0.01,
"default gamma should be 2.2"
);
}
#[test]
fn parse_real_chicken_info_from_iff() {
let path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("references/djvujs/library/assets/chicken.djvu");
let data = std::fs::read(&path).expect("chicken.djvu must exist");
let form = crate::iff::parse_form(&data).expect("IFF parse failed");
let info_chunk = form
.chunks
.iter()
.find(|c| &c.id == b"INFO")
.expect("INFO chunk must be present");
let info = PageInfo::parse(info_chunk.data).expect("INFO parse failed");
assert_eq!(info.width, 181);
assert_eq!(info.height, 240);
assert_eq!(info.dpi, 100);
}
}