Skip to main content

oxidize_pdf/fonts/
loader.rs

1//! Font loading utilities
2
3use crate::error::PdfError;
4use crate::Result;
5
6/// Font format enumeration
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum FontFormat {
9    /// TrueType font format
10    TrueType,
11    /// OpenType font format  
12    OpenType,
13}
14
15impl FontFormat {
16    /// Detect font format from raw data
17    pub fn detect(data: &[u8]) -> Result<Self> {
18        if data.len() < 4 {
19            return Err(PdfError::FontError("Font data too small".into()));
20        }
21
22        // Check magic bytes
23        match &data[0..4] {
24            // TTF magic: 0x00010000
25            [0x00, 0x01, 0x00, 0x00] => Ok(FontFormat::TrueType),
26            // OTF magic: "OTTO"
27            [0x4F, 0x54, 0x54, 0x4F] => Ok(FontFormat::OpenType),
28            // TTF with 'true' tag
29            [0x74, 0x72, 0x75, 0x65] => Ok(FontFormat::TrueType),
30            _ => Err(PdfError::FontError("Unknown font format".into())),
31        }
32    }
33}
34
35/// Raw font data container
36#[derive(Debug, Clone)]
37pub struct FontData {
38    /// Raw font bytes
39    pub bytes: Vec<u8>,
40    /// Detected font format
41    pub format: FontFormat,
42}
43
44/// Font loader for reading font files
45pub struct FontLoader;
46
47impl FontLoader {
48    /// Load font data from file
49    pub fn load_from_file(path: impl AsRef<std::path::Path>) -> Result<FontData> {
50        let bytes = std::fs::read(path)?;
51        Self::load_from_bytes(bytes)
52    }
53
54    /// Load font data from bytes
55    pub fn load_from_bytes(bytes: Vec<u8>) -> Result<FontData> {
56        let format = FontFormat::detect(&bytes)?;
57        Ok(FontData { bytes, format })
58    }
59
60    /// Validate font data
61    pub fn validate(data: &FontData) -> Result<()> {
62        if data.bytes.len() < 12 {
63            return Err(PdfError::FontError("Font file too small".into()));
64        }
65
66        // Basic validation based on format
67        match data.format {
68            FontFormat::TrueType => Self::validate_ttf(&data.bytes),
69            FontFormat::OpenType => Self::validate_otf(&data.bytes),
70        }
71    }
72
73    fn validate_ttf(data: &[u8]) -> Result<()> {
74        // TTF should have table directory after header
75        if data.len() < 12 {
76            return Err(PdfError::FontError("Invalid TTF structure".into()));
77        }
78        Ok(())
79    }
80
81    fn validate_otf(data: &[u8]) -> Result<()> {
82        // OTF should have CFF table
83        if data.len() < 12 {
84            return Err(PdfError::FontError("Invalid OTF structure".into()));
85        }
86        Ok(())
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93
94    #[test]
95    fn test_font_format_detection() {
96        // Test TTF detection
97        let ttf_header = vec![0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
98        let ttf_data = FontLoader::load_from_bytes(ttf_header).unwrap();
99        assert_eq!(ttf_data.format, FontFormat::TrueType);
100
101        // Test OTF detection
102        let otf_header = vec![0x4F, 0x54, 0x54, 0x4F, 0x00, 0x00, 0x00, 0x00];
103        let otf_data = FontLoader::load_from_bytes(otf_header).unwrap();
104        assert_eq!(otf_data.format, FontFormat::OpenType);
105    }
106
107    #[test]
108    fn test_invalid_font_data() {
109        // Too small
110        let small_data = vec![0x00, 0x01];
111        assert!(FontLoader::load_from_bytes(small_data).is_err());
112
113        // Invalid magic
114        let invalid_data = vec![0xFF, 0xFF, 0xFF, 0xFF];
115        assert!(FontLoader::load_from_bytes(invalid_data).is_err());
116    }
117
118    #[test]
119    fn test_font_validation() {
120        // Valid TTF with minimal data
121        let mut ttf_data = vec![0x00, 0x01, 0x00, 0x00];
122        ttf_data.extend_from_slice(&[0x00; 20]); // Add padding
123        let font_data = FontLoader::load_from_bytes(ttf_data).unwrap();
124        assert!(FontLoader::validate(&font_data).is_ok());
125
126        // Invalid - too small
127        let small_font = FontData {
128            bytes: vec![0x00; 10],
129            format: FontFormat::TrueType,
130        };
131        assert!(FontLoader::validate(&small_font).is_err());
132    }
133
134    #[test]
135    fn test_ttf_true_tag_detection() {
136        // Test TTF with 'true' tag
137        let true_header = vec![0x74, 0x72, 0x75, 0x65, 0x00, 0x00, 0x00, 0x00];
138        let true_data = FontLoader::load_from_bytes(true_header).unwrap();
139        assert_eq!(true_data.format, FontFormat::TrueType);
140    }
141
142    #[test]
143    fn test_font_data_clone() {
144        let font_data = FontData {
145            bytes: vec![1, 2, 3, 4],
146            format: FontFormat::TrueType,
147        };
148
149        let cloned = font_data.clone();
150        assert_eq!(cloned.bytes, font_data.bytes);
151        assert_eq!(cloned.format, font_data.format);
152    }
153
154    #[test]
155    fn test_font_format_equality() {
156        assert_eq!(FontFormat::TrueType, FontFormat::TrueType);
157        assert_eq!(FontFormat::OpenType, FontFormat::OpenType);
158        assert_ne!(FontFormat::TrueType, FontFormat::OpenType);
159    }
160
161    #[test]
162    fn test_otf_validation() {
163        // Valid OTF with minimal data
164        let mut otf_data = vec![0x4F, 0x54, 0x54, 0x4F];
165        otf_data.extend_from_slice(&[0x00; 20]); // Add padding
166        let font_data = FontLoader::load_from_bytes(otf_data).unwrap();
167        assert!(FontLoader::validate(&font_data).is_ok());
168
169        // Invalid OTF - too small
170        let small_otf = FontData {
171            bytes: vec![0x4F, 0x54, 0x54, 0x4F],
172            format: FontFormat::OpenType,
173        };
174        assert!(FontLoader::validate(&small_otf).is_err());
175    }
176
177    #[test]
178    fn test_empty_font_data() {
179        let empty_data = vec![];
180        let result = FontLoader::load_from_bytes(empty_data);
181        assert!(result.is_err());
182        if let Err(PdfError::FontError(msg)) = result {
183            assert!(msg.contains("too small"));
184        }
185    }
186
187    #[test]
188    fn test_font_format_detect_edge_cases() {
189        // Test with exactly 4 bytes
190        let exact_ttf = vec![0x00, 0x01, 0x00, 0x00];
191        assert_eq!(
192            FontFormat::detect(&exact_ttf).unwrap(),
193            FontFormat::TrueType
194        );
195
196        // Test with 3 bytes (too small)
197        let too_small = vec![0x00, 0x01, 0x00];
198        assert!(FontFormat::detect(&too_small).is_err());
199    }
200}