1pub mod cid_mapper;
7pub mod embedder;
8pub mod font_cache;
9pub mod font_descriptor;
10pub mod font_metrics;
11pub mod loader;
12pub mod standard_14;
13pub mod ttf_parser;
14pub mod type0;
15pub mod type0_parsing;
16
17pub use cid_mapper::{analyze_unicode_ranges, CidMapping, UnicodeRanges};
18pub use embedder::{EmbeddingOptions, FontEmbedder, FontEncoding};
19pub use font_cache::FontCache;
20pub use font_descriptor::{FontDescriptor, FontFlags};
21pub use font_metrics::{FontMetrics, TextMeasurement};
22pub use loader::{FontData, FontFormat, FontLoader};
23pub use standard_14::Standard14Font;
24pub use ttf_parser::{GlyphMapping, TtfParser};
25pub use type0::{create_type0_from_font, needs_type0_font, Type0Font};
26pub use type0_parsing::{
27 detect_cidfont_subtype, detect_type0_font, extract_default_width, extract_descendant_fonts_ref,
28 extract_font_descriptor_ref, extract_font_file_ref, extract_tounicode_ref, extract_widths_ref,
29 resolve_type0_hierarchy, CIDFontSubtype, FontFileType, Type0FontInfo, MAX_FONT_STREAM_SIZE,
30};
31
32use crate::Result;
33
34#[derive(Debug, Clone)]
36pub struct Font {
37 pub name: String,
39 pub data: Vec<u8>,
41 pub format: FontFormat,
43 pub metrics: FontMetrics,
45 pub descriptor: FontDescriptor,
47 pub glyph_mapping: GlyphMapping,
49}
50
51impl Font {
52 pub fn new(name: impl Into<String>) -> Self {
54 Font {
55 name: name.into(),
56 data: Vec::new(),
57 format: FontFormat::TrueType,
58 metrics: FontMetrics::default(),
59 descriptor: FontDescriptor::default(),
60 glyph_mapping: GlyphMapping::default(),
61 }
62 }
63
64 pub fn from_file(name: impl Into<String>, path: impl AsRef<std::path::Path>) -> Result<Self> {
66 let data = std::fs::read(path)?;
67 Self::from_bytes(name, data)
68 }
69
70 pub fn from_bytes(name: impl Into<String>, data: Vec<u8>) -> Result<Self> {
72 let name = name.into();
73 let format = FontFormat::detect(&data)?;
74
75 let parser = TtfParser::new(&data)?;
76 let metrics = parser.extract_metrics()?;
77 let descriptor = parser.create_descriptor()?;
78 let glyph_mapping = parser.extract_glyph_mapping()?;
79
80 Ok(Font {
81 name,
82 data,
83 format,
84 metrics,
85 descriptor,
86 glyph_mapping,
87 })
88 }
89
90 pub fn postscript_name(&self) -> &str {
92 &self.descriptor.font_name
93 }
94
95 pub fn has_glyph(&self, ch: char) -> bool {
97 self.glyph_mapping.char_to_glyph(ch).is_some()
98 }
99
100 pub fn measure_text(&self, text: &str, font_size: f32) -> TextMeasurement {
102 self.metrics
103 .measure_text(text, font_size, &self.glyph_mapping)
104 }
105
106 pub fn line_height(&self, font_size: f32) -> f32 {
108 self.metrics.line_height(font_size)
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115
116 #[test]
117 fn test_font_format_detection() {
118 let ttf_data = vec![0x00, 0x01, 0x00, 0x00];
120 assert!(matches!(
121 FontFormat::detect(&ttf_data),
122 Ok(FontFormat::TrueType)
123 ));
124
125 let otf_data = vec![0x4F, 0x54, 0x54, 0x4F];
127 assert!(matches!(
128 FontFormat::detect(&otf_data),
129 Ok(FontFormat::OpenType)
130 ));
131
132 let invalid_data = vec![0xFF, 0xFF, 0xFF, 0xFF];
134 assert!(FontFormat::detect(&invalid_data).is_err());
135 }
136
137 #[test]
142 fn test_font_new() {
143 let font = Font::new("TestFont");
144
145 assert_eq!(font.name, "TestFont");
146 assert!(font.data.is_empty(), "Data should be empty for new font");
147 assert!(
148 matches!(font.format, FontFormat::TrueType),
149 "Default format should be TrueType"
150 );
151 }
152
153 #[test]
154 fn test_font_new_with_string() {
155 let font = Font::new("Arial".to_string());
156
157 assert_eq!(font.name, "Arial");
158 assert!(font.data.is_empty());
159 }
160
161 #[test]
162 fn test_font_postscript_name() {
163 let mut font = Font::new("TestFont");
164 font.descriptor.font_name = "Helvetica-Bold".to_string();
165
166 assert_eq!(font.postscript_name(), "Helvetica-Bold");
167 }
168
169 #[test]
170 fn test_font_has_glyph_with_empty_mapping() {
171 let font = Font::new("TestFont");
172
173 assert!(!font.has_glyph('A'), "Empty mapping should not have glyph");
175 assert!(!font.has_glyph('€'), "Empty mapping should not have glyph");
176 }
177
178 #[test]
179 fn test_font_measure_text_with_defaults() {
180 let font = Font::new("TestFont");
181
182 let measurement = font.measure_text("Hello", 12.0);
184
185 assert_eq!(
188 measurement.width, 36.0,
189 "5 chars with default 600 units at 12pt should be 36.0"
190 );
191 }
192
193 #[test]
194 fn test_font_line_height_with_defaults() {
195 let font = Font::new("TestFont");
196
197 let line_height = font.line_height(12.0);
198
199 assert_eq!(
202 line_height, 14.4,
203 "Default metrics should produce 14.4 line height at 12pt"
204 );
205 }
206
207 #[test]
208 fn test_font_from_file_nonexistent() {
209 let result = Font::from_file("TestFont", "/nonexistent/path/font.ttf");
210
211 assert!(
212 result.is_err(),
213 "Loading nonexistent file should return error"
214 );
215 }
216
217 #[test]
218 fn test_font_from_bytes_invalid_format() {
219 let invalid_data = vec![0xFF, 0xFE, 0xFD, 0xFC, 0x00, 0x01, 0x02, 0x03];
221
222 let result = Font::from_bytes("InvalidFont", invalid_data);
223
224 assert!(
225 result.is_err(),
226 "Invalid font data should return error from FontFormat::detect"
227 );
228 }
229
230 #[test]
231 fn test_font_from_bytes_too_small() {
232 let tiny_data = vec![0x00, 0x01];
234
235 let result = Font::from_bytes("TinyFont", tiny_data);
236
237 assert!(
238 result.is_err(),
239 "Too small data should return error during detection"
240 );
241 }
242
243 #[test]
244 fn test_font_name_conversion() {
245 let font1 = Font::new("StrName");
247 let font2 = Font::new("StringName".to_string());
248
249 assert_eq!(font1.name, "StrName");
250 assert_eq!(font2.name, "StringName");
251 }
252
253 #[test]
254 fn test_font_fields_are_accessible() {
255 let mut font = Font::new("TestFont");
256
257 font.name = "ModifiedName".to_string();
259 font.data = vec![1, 2, 3, 4];
260 font.format = FontFormat::OpenType;
261
262 assert_eq!(font.name, "ModifiedName");
263 assert_eq!(font.data, vec![1, 2, 3, 4]);
264 assert!(matches!(font.format, FontFormat::OpenType));
265 }
266}