1pub mod cid_mapper;
7pub mod cmap_utils;
8pub mod embedder;
9pub mod font_cache;
10pub mod font_descriptor;
11pub mod font_metrics;
12pub mod loader;
13pub mod standard_14;
14pub mod ttf_parser;
15pub mod type0;
16pub mod type0_parsing;
17
18pub use cid_mapper::{analyze_unicode_ranges, CidMapping, UnicodeRanges};
19pub use embedder::{EmbeddingOptions, FontEmbedder, FontEncoding};
20pub use font_cache::FontCache;
21pub use font_descriptor::{FontDescriptor, FontFlags};
22pub use font_metrics::{FontMetrics, TextMeasurement};
23pub use loader::{FontData, FontFormat, FontLoader};
24pub use standard_14::Standard14Font;
25pub use ttf_parser::{GlyphMapping, TtfParser};
26pub use type0::{create_type0_from_font, needs_type0_font, Type0Font};
27pub use type0_parsing::{
28 detect_cidfont_subtype, detect_type0_font, extract_default_width, extract_descendant_fonts_ref,
29 extract_font_descriptor_ref, extract_font_file_ref, extract_tounicode_ref, extract_widths_ref,
30 resolve_type0_hierarchy, CIDFontSubtype, FontFileType, Type0FontInfo, MAX_FONT_STREAM_SIZE,
31};
32
33use crate::Result;
34
35#[derive(Debug, Clone)]
37pub struct Font {
38 pub name: String,
40 pub data: Vec<u8>,
42 pub format: FontFormat,
44 pub metrics: FontMetrics,
46 pub descriptor: FontDescriptor,
48 pub glyph_mapping: GlyphMapping,
50}
51
52impl Font {
53 pub fn new(name: impl Into<String>) -> Self {
55 Font {
56 name: name.into(),
57 data: Vec::new(),
58 format: FontFormat::TrueType,
59 metrics: FontMetrics::default(),
60 descriptor: FontDescriptor::default(),
61 glyph_mapping: GlyphMapping::default(),
62 }
63 }
64
65 pub fn from_file(name: impl Into<String>, path: impl AsRef<std::path::Path>) -> Result<Self> {
67 let data = std::fs::read(path)?;
68 Self::from_bytes(name, data)
69 }
70
71 pub fn from_bytes(name: impl Into<String>, data: Vec<u8>) -> Result<Self> {
73 let name = name.into();
74 let format = FontFormat::detect(&data)?;
75
76 let parser = TtfParser::new(&data)?;
77 let metrics = parser.extract_metrics()?;
78 let descriptor = parser.create_descriptor()?;
79 let glyph_mapping = parser.extract_glyph_mapping()?;
80
81 Ok(Font {
82 name,
83 data,
84 format,
85 metrics,
86 descriptor,
87 glyph_mapping,
88 })
89 }
90
91 pub fn postscript_name(&self) -> &str {
93 &self.descriptor.font_name
94 }
95
96 pub fn has_glyph(&self, ch: char) -> bool {
98 self.glyph_mapping.char_to_glyph(ch).is_some()
99 }
100
101 pub fn missing_glyphs(&self, text: &str) -> Vec<char> {
110 if !self.glyph_mapping.coverage_known() {
114 return Vec::new();
115 }
116 let mut seen = std::collections::HashSet::new();
117 let mut missing = Vec::new();
118 for ch in text.chars() {
119 if ch.is_control() {
120 continue;
121 }
122 if seen.insert(ch) && !self.has_glyph(ch) {
125 missing.push(ch);
126 }
127 }
128 missing
129 }
130
131 pub fn measure_text(&self, text: &str, font_size: f32) -> TextMeasurement {
133 self.metrics
134 .measure_text(text, font_size, &self.glyph_mapping)
135 }
136
137 pub fn line_height(&self, font_size: f32) -> f32 {
139 self.metrics.line_height(font_size)
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146
147 #[test]
148 fn test_font_format_detection() {
149 let ttf_data = vec![0x00, 0x01, 0x00, 0x00];
151 assert!(matches!(
152 FontFormat::detect(&ttf_data),
153 Ok(FontFormat::TrueType)
154 ));
155
156 let otf_data = vec![0x4F, 0x54, 0x54, 0x4F];
158 assert!(matches!(
159 FontFormat::detect(&otf_data),
160 Ok(FontFormat::OpenType)
161 ));
162
163 let invalid_data = vec![0xFF, 0xFF, 0xFF, 0xFF];
165 assert!(FontFormat::detect(&invalid_data).is_err());
166 }
167
168 #[test]
173 fn test_font_new() {
174 let font = Font::new("TestFont");
175
176 assert_eq!(font.name, "TestFont");
177 assert!(font.data.is_empty(), "Data should be empty for new font");
178 assert!(
179 matches!(font.format, FontFormat::TrueType),
180 "Default format should be TrueType"
181 );
182 }
183
184 #[test]
185 fn test_font_new_with_string() {
186 let font = Font::new("Arial".to_string());
187
188 assert_eq!(font.name, "Arial");
189 assert!(font.data.is_empty());
190 }
191
192 #[test]
193 fn test_font_postscript_name() {
194 let mut font = Font::new("TestFont");
195 font.descriptor.font_name = "Helvetica-Bold".to_string();
196
197 assert_eq!(font.postscript_name(), "Helvetica-Bold");
198 }
199
200 #[test]
201 fn test_font_has_glyph_with_empty_mapping() {
202 let font = Font::new("TestFont");
203
204 assert!(!font.has_glyph('A'), "Empty mapping should not have glyph");
206 assert!(!font.has_glyph('€'), "Empty mapping should not have glyph");
207 }
208
209 #[test]
210 fn test_font_measure_text_with_defaults() {
211 let font = Font::new("TestFont");
212
213 let measurement = font.measure_text("Hello", 12.0);
215
216 assert_eq!(
219 measurement.width, 36.0,
220 "5 chars with default 600 units at 12pt should be 36.0"
221 );
222 }
223
224 #[test]
225 fn test_font_line_height_with_defaults() {
226 let font = Font::new("TestFont");
227
228 let line_height = font.line_height(12.0);
229
230 assert_eq!(
233 line_height, 14.4,
234 "Default metrics should produce 14.4 line height at 12pt"
235 );
236 }
237
238 #[test]
239 fn test_font_from_file_nonexistent() {
240 let result = Font::from_file("TestFont", "/nonexistent/path/font.ttf");
241
242 assert!(
243 result.is_err(),
244 "Loading nonexistent file should return error"
245 );
246 }
247
248 #[test]
249 fn test_font_from_bytes_invalid_format() {
250 let invalid_data = vec![0xFF, 0xFE, 0xFD, 0xFC, 0x00, 0x01, 0x02, 0x03];
252
253 let result = Font::from_bytes("InvalidFont", invalid_data);
254
255 assert!(
256 result.is_err(),
257 "Invalid font data should return error from FontFormat::detect"
258 );
259 }
260
261 #[test]
262 fn test_font_from_bytes_too_small() {
263 let tiny_data = vec![0x00, 0x01];
265
266 let result = Font::from_bytes("TinyFont", tiny_data);
267
268 assert!(
269 result.is_err(),
270 "Too small data should return error during detection"
271 );
272 }
273
274 #[test]
275 fn test_font_name_conversion() {
276 let font1 = Font::new("StrName");
278 let font2 = Font::new("StringName".to_string());
279
280 assert_eq!(font1.name, "StrName");
281 assert_eq!(font2.name, "StringName");
282 }
283
284 #[test]
285 fn test_font_fields_are_accessible() {
286 let mut font = Font::new("TestFont");
287
288 font.name = "ModifiedName".to_string();
290 font.data = vec![1, 2, 3, 4];
291 font.format = FontFormat::OpenType;
292
293 assert_eq!(font.name, "ModifiedName");
294 assert_eq!(font.data, vec![1, 2, 3, 4]);
295 assert!(matches!(font.format, FontFormat::OpenType));
296 }
297}