use crate::error::PdfError;
use crate::Result;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FontFormat {
TrueType,
OpenType,
}
impl FontFormat {
pub fn detect(data: &[u8]) -> Result<Self> {
if data.len() < 4 {
return Err(PdfError::FontError("Font data too small".into()));
}
match &data[0..4] {
[0x00, 0x01, 0x00, 0x00] => Ok(FontFormat::TrueType),
[0x4F, 0x54, 0x54, 0x4F] => Ok(FontFormat::OpenType),
[0x74, 0x72, 0x75, 0x65] => Ok(FontFormat::TrueType),
_ => Err(PdfError::FontError("Unknown font format".into())),
}
}
}
#[derive(Debug, Clone)]
pub struct FontData {
pub bytes: Vec<u8>,
pub format: FontFormat,
}
pub struct FontLoader;
impl FontLoader {
pub fn load_from_file(path: impl AsRef<std::path::Path>) -> Result<FontData> {
let bytes = std::fs::read(path)?;
Self::load_from_bytes(bytes)
}
pub fn load_from_bytes(bytes: Vec<u8>) -> Result<FontData> {
let format = FontFormat::detect(&bytes)?;
Ok(FontData { bytes, format })
}
pub fn validate(data: &FontData) -> Result<()> {
if data.bytes.len() < 12 {
return Err(PdfError::FontError("Font file too small".into()));
}
match data.format {
FontFormat::TrueType => Self::validate_ttf(&data.bytes),
FontFormat::OpenType => Self::validate_otf(&data.bytes),
}
}
fn validate_ttf(data: &[u8]) -> Result<()> {
if data.len() < 12 {
return Err(PdfError::FontError("Invalid TTF structure".into()));
}
Ok(())
}
fn validate_otf(data: &[u8]) -> Result<()> {
if data.len() < 12 {
return Err(PdfError::FontError("Invalid OTF structure".into()));
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_font_format_detection() {
let ttf_header = vec![0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
let ttf_data = FontLoader::load_from_bytes(ttf_header).unwrap();
assert_eq!(ttf_data.format, FontFormat::TrueType);
let otf_header = vec![0x4F, 0x54, 0x54, 0x4F, 0x00, 0x00, 0x00, 0x00];
let otf_data = FontLoader::load_from_bytes(otf_header).unwrap();
assert_eq!(otf_data.format, FontFormat::OpenType);
}
#[test]
fn test_invalid_font_data() {
let small_data = vec![0x00, 0x01];
assert!(FontLoader::load_from_bytes(small_data).is_err());
let invalid_data = vec![0xFF, 0xFF, 0xFF, 0xFF];
assert!(FontLoader::load_from_bytes(invalid_data).is_err());
}
#[test]
fn test_font_validation() {
let mut ttf_data = vec![0x00, 0x01, 0x00, 0x00];
ttf_data.extend_from_slice(&[0x00; 20]); let font_data = FontLoader::load_from_bytes(ttf_data).unwrap();
assert!(FontLoader::validate(&font_data).is_ok());
let small_font = FontData {
bytes: vec![0x00; 10],
format: FontFormat::TrueType,
};
assert!(FontLoader::validate(&small_font).is_err());
}
#[test]
fn test_ttf_true_tag_detection() {
let true_header = vec![0x74, 0x72, 0x75, 0x65, 0x00, 0x00, 0x00, 0x00];
let true_data = FontLoader::load_from_bytes(true_header).unwrap();
assert_eq!(true_data.format, FontFormat::TrueType);
}
#[test]
fn test_font_data_clone() {
let font_data = FontData {
bytes: vec![1, 2, 3, 4],
format: FontFormat::TrueType,
};
let cloned = font_data.clone();
assert_eq!(cloned.bytes, font_data.bytes);
assert_eq!(cloned.format, font_data.format);
}
#[test]
fn test_font_format_equality() {
assert_eq!(FontFormat::TrueType, FontFormat::TrueType);
assert_eq!(FontFormat::OpenType, FontFormat::OpenType);
assert_ne!(FontFormat::TrueType, FontFormat::OpenType);
}
#[test]
fn test_otf_validation() {
let mut otf_data = vec![0x4F, 0x54, 0x54, 0x4F];
otf_data.extend_from_slice(&[0x00; 20]); let font_data = FontLoader::load_from_bytes(otf_data).unwrap();
assert!(FontLoader::validate(&font_data).is_ok());
let small_otf = FontData {
bytes: vec![0x4F, 0x54, 0x54, 0x4F],
format: FontFormat::OpenType,
};
assert!(FontLoader::validate(&small_otf).is_err());
}
#[test]
fn test_empty_font_data() {
let empty_data = vec![];
let result = FontLoader::load_from_bytes(empty_data);
assert!(result.is_err());
if let Err(PdfError::FontError(msg)) = result {
assert!(msg.contains("too small"));
}
}
#[test]
fn test_font_format_detect_edge_cases() {
let exact_ttf = vec![0x00, 0x01, 0x00, 0x00];
assert_eq!(
FontFormat::detect(&exact_ttf).unwrap(),
FontFormat::TrueType
);
let too_small = vec![0x00, 0x01, 0x00];
assert!(FontFormat::detect(&too_small).is_err());
}
}