Skip to main content

oxidize_pdf/text/
font_manager.rs

1//! Font Manager for Type 1 and TrueType font support according to ISO 32000-1 Chapter 9
2//!
3//! This module provides comprehensive support for custom fonts including Type 1 and TrueType
4//! fonts with proper embedding, encoding, and font descriptor management.
5
6use crate::error::{PdfError, Result};
7use crate::objects::{Dictionary, Object};
8use crate::text::fonts::truetype::TrueTypeFont;
9use std::collections::{HashMap, HashSet};
10use std::fs;
11use std::path::Path;
12
13/// Font type enumeration
14#[derive(Debug, Clone, Copy, PartialEq)]
15pub enum FontType {
16    /// Type 1 font
17    Type1,
18    /// TrueType font
19    TrueType,
20    /// CFF/OpenType font
21    CFF,
22    /// Type 3 font (user-defined)
23    Type3,
24    /// Type 0 font (composite)
25    Type0,
26}
27
28/// Font encoding types
29#[derive(Debug, Clone, PartialEq)]
30pub enum FontEncoding {
31    /// Standard encoding
32    StandardEncoding,
33    /// MacRoman encoding
34    MacRomanEncoding,
35    /// WinAnsi encoding
36    WinAnsiEncoding,
37    /// Custom encoding with differences
38    Custom(Vec<EncodingDifference>),
39    /// Identity encoding for CID fonts
40    Identity,
41}
42
43/// Encoding difference entry
44#[derive(Debug, Clone, PartialEq)]
45pub struct EncodingDifference {
46    /// Starting character code
47    pub code: u8,
48    /// Glyph names for consecutive character codes
49    pub names: Vec<String>,
50}
51
52/// Font flags for font descriptor (ISO 32000-1 Table 123)
53#[derive(Debug, Clone, Copy, Default)]
54pub struct FontFlags {
55    /// All glyphs have the same width
56    pub fixed_pitch: bool,
57    /// Glyphs have serifs
58    pub serif: bool,
59    /// Font uses symbolic character set
60    pub symbolic: bool,
61    /// Font is a script font
62    pub script: bool,
63    /// Font uses Adobe standard Latin character set
64    pub non_symbolic: bool,
65    /// Glyphs resemble cursive handwriting
66    pub italic: bool,
67    /// All glyphs have dominant vertical strokes
68    pub all_cap: bool,
69    /// Font is a small-cap font
70    pub small_cap: bool,
71    /// Font weight is bold or black
72    pub force_bold: bool,
73}
74
75impl FontFlags {
76    /// Convert to PDF font flags integer
77    pub fn to_flags(&self) -> u32 {
78        let mut flags = 0u32;
79
80        if self.fixed_pitch {
81            flags |= 1 << 0;
82        }
83        if self.serif {
84            flags |= 1 << 1;
85        }
86        if self.symbolic {
87            flags |= 1 << 2;
88        }
89        if self.script {
90            flags |= 1 << 3;
91        }
92        if self.non_symbolic {
93            flags |= 1 << 5;
94        }
95        if self.italic {
96            flags |= 1 << 6;
97        }
98        if self.all_cap {
99            flags |= 1 << 16;
100        }
101        if self.small_cap {
102            flags |= 1 << 17;
103        }
104        if self.force_bold {
105            flags |= 1 << 18;
106        }
107
108        flags
109    }
110}
111
112/// Font descriptor for custom fonts
113#[derive(Debug, Clone)]
114pub struct FontDescriptor {
115    /// Font name
116    pub font_name: String,
117    /// Font family
118    pub font_family: Option<String>,
119    /// Font stretch
120    pub font_stretch: Option<String>,
121    /// Font weight
122    pub font_weight: Option<i32>,
123    /// Font flags
124    pub flags: FontFlags,
125    /// Font bounding box [llx lly urx ury]
126    pub font_bbox: [f64; 4],
127    /// Italic angle in degrees
128    pub italic_angle: f64,
129    /// Ascent (maximum height above baseline)
130    pub ascent: f64,
131    /// Descent (maximum depth below baseline)
132    pub descent: f64,
133    /// Leading (spacing between lines)
134    pub leading: Option<f64>,
135    /// Capital height
136    pub cap_height: f64,
137    /// X-height (height of lowercase x)
138    pub x_height: Option<f64>,
139    /// Stem width
140    pub stem_v: f64,
141    /// Horizontal stem width
142    pub stem_h: Option<f64>,
143    /// Average width of glyphs
144    pub avg_width: Option<f64>,
145    /// Maximum width of glyphs
146    pub max_width: Option<f64>,
147    /// Width of missing character
148    pub missing_width: Option<f64>,
149}
150
151impl FontDescriptor {
152    /// Create a new font descriptor with required fields
153    #[allow(clippy::too_many_arguments)]
154    pub fn new(
155        font_name: String,
156        flags: FontFlags,
157        font_bbox: [f64; 4],
158        italic_angle: f64,
159        ascent: f64,
160        descent: f64,
161        cap_height: f64,
162        stem_v: f64,
163    ) -> Self {
164        Self {
165            font_name,
166            font_family: None,
167            font_stretch: None,
168            font_weight: None,
169            flags,
170            font_bbox,
171            italic_angle,
172            ascent,
173            descent,
174            leading: None,
175            cap_height,
176            x_height: None,
177            stem_v,
178            stem_h: None,
179            avg_width: None,
180            max_width: None,
181            missing_width: None,
182        }
183    }
184
185    /// Convert to PDF dictionary
186    pub fn to_pdf_dict(&self) -> Dictionary {
187        let mut dict = Dictionary::new();
188
189        dict.set("Type", Object::Name("FontDescriptor".to_string()));
190        dict.set("FontName", Object::Name(self.font_name.clone()));
191
192        if let Some(ref family) = self.font_family {
193            dict.set("FontFamily", Object::String(family.clone()));
194        }
195
196        if let Some(ref stretch) = self.font_stretch {
197            dict.set("FontStretch", Object::Name(stretch.clone()));
198        }
199
200        if let Some(weight) = self.font_weight {
201            dict.set("FontWeight", Object::Integer(weight as i64));
202        }
203
204        dict.set("Flags", Object::Integer(self.flags.to_flags() as i64));
205
206        let bbox = vec![
207            Object::Real(self.font_bbox[0]),
208            Object::Real(self.font_bbox[1]),
209            Object::Real(self.font_bbox[2]),
210            Object::Real(self.font_bbox[3]),
211        ];
212        dict.set("FontBBox", Object::Array(bbox));
213
214        dict.set("ItalicAngle", Object::Real(self.italic_angle));
215        dict.set("Ascent", Object::Real(self.ascent));
216        dict.set("Descent", Object::Real(self.descent));
217
218        if let Some(leading) = self.leading {
219            dict.set("Leading", Object::Real(leading));
220        }
221
222        dict.set("CapHeight", Object::Real(self.cap_height));
223
224        if let Some(x_height) = self.x_height {
225            dict.set("XHeight", Object::Real(x_height));
226        }
227
228        dict.set("StemV", Object::Real(self.stem_v));
229
230        if let Some(stem_h) = self.stem_h {
231            dict.set("StemH", Object::Real(stem_h));
232        }
233
234        if let Some(avg_width) = self.avg_width {
235            dict.set("AvgWidth", Object::Real(avg_width));
236        }
237
238        if let Some(max_width) = self.max_width {
239            dict.set("MaxWidth", Object::Real(max_width));
240        }
241
242        if let Some(missing_width) = self.missing_width {
243            dict.set("MissingWidth", Object::Real(missing_width));
244        }
245
246        dict
247    }
248}
249
250/// Font metrics for character widths
251#[derive(Debug, Clone)]
252pub struct FontMetrics {
253    /// First character code
254    pub first_char: u8,
255    /// Last character code
256    pub last_char: u8,
257    /// Character widths (in glyph space units)
258    pub widths: Vec<f64>,
259    /// Default width for missing characters
260    pub missing_width: f64,
261}
262
263impl FontMetrics {
264    /// Create new font metrics
265    pub fn new(first_char: u8, last_char: u8, widths: Vec<f64>, missing_width: f64) -> Self {
266        Self {
267            first_char,
268            last_char,
269            widths,
270            missing_width,
271        }
272    }
273
274    /// Get width for a character
275    pub fn get_width(&self, char_code: u8) -> f64 {
276        if char_code < self.first_char || char_code > self.last_char {
277            self.missing_width
278        } else {
279            let index = (char_code - self.first_char) as usize;
280            self.widths
281                .get(index)
282                .copied()
283                .unwrap_or(self.missing_width)
284        }
285    }
286}
287
288/// Represents a custom font (Type 1 or TrueType)
289#[derive(Debug, Clone)]
290pub struct CustomFont {
291    /// Font name
292    pub name: String,
293    /// Font type
294    pub font_type: FontType,
295    /// Font encoding
296    pub encoding: FontEncoding,
297    /// Font descriptor
298    pub descriptor: FontDescriptor,
299    /// Font metrics
300    pub metrics: FontMetrics,
301    /// Font data (for embedding)
302    pub font_data: Option<Vec<u8>>,
303    /// Font file type for embedding
304    pub font_file_type: Option<FontFileType>,
305    /// Parsed TrueType font (for subsetting)
306    pub truetype_font: Option<TrueTypeFont>,
307    /// Used glyphs for subsetting
308    pub used_glyphs: HashSet<u16>,
309}
310
311/// Font file type for embedding
312#[derive(Debug, Clone, Copy, PartialEq)]
313pub enum FontFileType {
314    /// Type 1 font file
315    Type1,
316    /// TrueType font file
317    TrueType,
318    /// OpenType font file with CFF outlines
319    OpenTypeCFF,
320}
321
322impl CustomFont {
323    /// Create a font from byte data
324    pub fn from_bytes(name: &str, data: Vec<u8>) -> Result<Self> {
325        // Parse TrueType font
326        let ttf = TrueTypeFont::parse(data.clone()).map_err(|e| {
327            PdfError::InvalidStructure(format!("Failed to parse TrueType font: {}", e))
328        })?;
329
330        // Get font name from the font file or use provided name
331        let font_name = ttf.get_font_name().unwrap_or_else(|_| name.to_string());
332
333        // Create font descriptor from TrueType data
334        let flags = FontFlags {
335            fixed_pitch: false,
336            symbolic: false,
337            non_symbolic: true,
338            ..Default::default()
339        };
340
341        let descriptor = FontDescriptor::new(
342            font_name,
343            flags,
344            [-500.0, -300.0, 1500.0, 1000.0], // Font bbox
345            0.0,                              // Italic angle
346            750.0,                            // Ascent
347            -250.0,                           // Descent
348            700.0,                            // Cap height
349            100.0,                            // Stem V
350        );
351
352        // Create metrics with proper Unicode support (including CJK)
353        let mut char_widths = std::collections::HashMap::new();
354        if let Ok(cmap_tables) = ttf.parse_cmap() {
355            if let Some(cmap) = cmap_tables
356                .iter()
357                .find(|t| t.platform_id == 3 && t.encoding_id == 1) // Windows Unicode BMP
358                .or_else(|| cmap_tables.iter().find(|t| t.platform_id == 0)) // Unicode
359                .or_else(|| cmap_tables.first())
360            {
361                // Get widths for common Unicode ranges including CJK
362                let ranges = [
363                    (0x0020, 0x007F), // Basic Latin
364                    (0x00A0, 0x00FF), // Latin-1 Supplement
365                    (0x3000, 0x303F), // CJK Symbols and Punctuation
366                    (0x3040, 0x309F), // Hiragana
367                    (0x30A0, 0x30FF), // Katakana
368                    (0x4E00, 0x9FFF), // CJK Unified Ideographs (Common)
369                ];
370
371                for (start, end) in ranges {
372                    for char_code in start..=end {
373                        if let Some(&glyph_id) = cmap.mappings.get(&char_code) {
374                            if let Ok((advance_width, _)) = ttf.get_glyph_metrics(glyph_id) {
375                                let width =
376                                    (advance_width as f64 * 1000.0) / ttf.units_per_em as f64;
377                                if let Some(ch) = char::from_u32(char_code) {
378                                    char_widths.insert(ch, width);
379                                }
380                            }
381                        }
382                    }
383                }
384            }
385        }
386
387        // For legacy compatibility, create a simple width array for ASCII range
388        let mut widths = Vec::new();
389        for char_code in 32u8..=255 {
390            let ch = char::from(char_code);
391            let width = char_widths.get(&ch).copied().unwrap_or(500.0); // Default width for CJK
392            widths.push(width);
393        }
394
395        let metrics = FontMetrics {
396            first_char: 32,
397            last_char: 255,
398            widths,
399            missing_width: 500.0, // Larger default for CJK characters
400        };
401
402        let font = Self {
403            name: name.to_string(),
404            font_type: FontType::Type0, // Use Type0 for Unicode support
405            encoding: FontEncoding::Identity, // Identity encoding for Unicode
406            descriptor,
407            metrics,
408            font_data: Some(data),
409            font_file_type: Some(FontFileType::TrueType),
410            truetype_font: Some(ttf),
411            used_glyphs: HashSet::new(),
412        };
413
414        Ok(font)
415    }
416
417    /// Create a new Type 1 font
418    pub fn new_type1(
419        name: String,
420        encoding: FontEncoding,
421        descriptor: FontDescriptor,
422        metrics: FontMetrics,
423    ) -> Self {
424        Self {
425            name,
426            font_type: FontType::Type1,
427            encoding,
428            descriptor,
429            metrics,
430            font_data: None,
431            font_file_type: None,
432            truetype_font: None,
433            used_glyphs: HashSet::new(),
434        }
435    }
436
437    /// Create a new TrueType font
438    pub fn new_truetype(
439        name: String,
440        encoding: FontEncoding,
441        descriptor: FontDescriptor,
442        metrics: FontMetrics,
443    ) -> Self {
444        Self {
445            name,
446            font_type: FontType::TrueType,
447            encoding,
448            descriptor,
449            metrics,
450            font_data: None,
451            font_file_type: None,
452            truetype_font: None,
453            used_glyphs: HashSet::new(),
454        }
455    }
456
457    /// Create a new CFF/OpenType font
458    pub fn new_cff(
459        name: String,
460        encoding: FontEncoding,
461        descriptor: FontDescriptor,
462        metrics: FontMetrics,
463    ) -> Self {
464        Self {
465            name,
466            font_type: FontType::CFF,
467            encoding,
468            descriptor,
469            metrics,
470            font_data: None,
471            font_file_type: None,
472            truetype_font: None,
473            used_glyphs: HashSet::new(),
474        }
475    }
476
477    /// Optimize the font for the given text content
478    pub fn optimize_for_text(&mut self, text: &str) {
479        // Check if text contains Unicode characters beyond Latin-1
480        let needs_unicode = text.chars().any(|c| c as u32 > 255);
481
482        if needs_unicode && self.font_type != FontType::Type0 && self.font_type != FontType::CFF {
483            // Convert to Type0 for Unicode support (CFF fonts already support Unicode)
484            self.convert_to_type0();
485        }
486
487        // Mark characters as used for subsetting
488        self.mark_characters_used(text);
489    }
490
491    /// Convert font to Type0 for Unicode support
492    fn convert_to_type0(&mut self) {
493        // Convert TrueType fonts to Type0, CFF fonts already use Type0 semantics
494        if self.font_type == FontType::TrueType {
495            self.font_type = FontType::Type0;
496            self.encoding = FontEncoding::Identity;
497
498            // Clear used glyphs as we'll need to rebuild with CIDs
499            self.used_glyphs.clear();
500        }
501    }
502
503    /// Get the glyph mapping (Unicode -> GlyphID) from the font's cmap table
504    pub fn get_glyph_mapping(&self) -> Option<HashMap<u32, u16>> {
505        if let Some(ref ttf) = self.truetype_font {
506            // Parse the cmap table to get Unicode to GlyphID mappings
507            if let Ok(cmap_tables) = ttf.parse_cmap() {
508                // Prefer Windows Unicode mapping (platform 3, encoding 1)
509                // or fallback to Unicode mapping (platform 0)
510                let cmap = cmap_tables
511                    .iter()
512                    .find(|t| t.platform_id == 3 && t.encoding_id == 1)
513                    .or_else(|| cmap_tables.iter().find(|t| t.platform_id == 0));
514
515                if let Some(cmap) = cmap {
516                    return Some(cmap.mappings.clone());
517                }
518            }
519        }
520        None
521    }
522
523    /// Load font data from file for embedding
524    pub fn load_font_file<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
525        let data = fs::read(path.as_ref())?;
526
527        // Detect font file type
528        let file_type = Self::detect_font_file_type(&data)?;
529        self.font_file_type = Some(file_type);
530
531        // For TrueType fonts, parse the font structure
532        if matches!(file_type, FontFileType::TrueType) {
533            match TrueTypeFont::parse(data.clone()) {
534                Ok(ttf) => {
535                    // Update font name from the font file
536                    if let Ok(font_name) = ttf.get_font_name() {
537                        self.name = font_name.clone();
538                        self.descriptor.font_name = font_name;
539                    }
540
541                    // Update metrics from the font
542                    if let Ok(cmap_tables) = ttf.parse_cmap() {
543                        // Find best cmap table
544                        if let Some(cmap) = cmap_tables
545                            .iter()
546                            .find(|t| t.platform_id == 3 && t.encoding_id == 1)
547                            .or_else(|| cmap_tables.first())
548                        {
549                            // Update character widths
550                            let mut widths = Vec::new();
551                            for char_code in self.metrics.first_char..=self.metrics.last_char {
552                                if let Some(&glyph_id) = cmap.mappings.get(&(char_code as u32)) {
553                                    if let Ok((advance_width, _)) = ttf.get_glyph_metrics(glyph_id)
554                                    {
555                                        // Convert from font units to 1000ths of a unit
556                                        let width = (advance_width as f64 * 1000.0)
557                                            / ttf.units_per_em as f64;
558                                        widths.push(width);
559                                    } else {
560                                        widths.push(self.metrics.missing_width);
561                                    }
562                                } else {
563                                    widths.push(self.metrics.missing_width);
564                                }
565                            }
566                            self.metrics.widths = widths;
567                        }
568                    }
569
570                    self.truetype_font = Some(ttf);
571                }
572                Err(_) => {
573                    // Continue without parsing - will embed full font
574                }
575            }
576        }
577
578        self.font_data = Some(data);
579        Ok(())
580    }
581
582    /// Load TrueType font from file
583    pub fn load_truetype_font<P: AsRef<Path>>(path: P) -> Result<Self> {
584        let data = fs::read(path.as_ref())?;
585
586        // Parse TrueType font
587        let ttf = TrueTypeFont::parse(data.clone()).map_err(|e| {
588            PdfError::InvalidStructure(format!("Failed to parse TrueType font: {}", e))
589        })?;
590
591        // Get font name
592        let font_name = ttf
593            .get_font_name()
594            .unwrap_or_else(|_| "Unknown".to_string());
595
596        // Create font descriptor from TrueType data with real metrics
597        let fixed_pitch = ttf.is_fixed_pitch().unwrap_or(false);
598        let flags = FontFlags {
599            fixed_pitch,
600            symbolic: false,
601            non_symbolic: true,
602            ..Default::default()
603        };
604
605        let font_bbox = ttf
606            .get_font_bbox()
607            .unwrap_or([-500.0, -300.0, 1500.0, 1000.0]);
608        let font_bbox_f64 = [
609            font_bbox[0] as f64,
610            font_bbox[1] as f64,
611            font_bbox[2] as f64,
612            font_bbox[3] as f64,
613        ];
614        let italic_angle = ttf.get_italic_angle().unwrap_or(0.0) as f64;
615        let ascent = ttf.get_ascent().unwrap_or(750) as f64;
616        let descent = ttf.get_descent().unwrap_or(-250) as f64;
617        let cap_height = ttf.get_cap_height().unwrap_or(700.0) as f64;
618        let stem_width = ttf.get_stem_width().unwrap_or(100.0) as f64;
619
620        let descriptor = FontDescriptor::new(
621            font_name.clone(),
622            flags,
623            font_bbox_f64,
624            italic_angle,
625            ascent,
626            descent,
627            cap_height,
628            stem_width,
629        );
630
631        // Create metrics
632        let mut widths = Vec::new();
633        if let Ok(cmap_tables) = ttf.parse_cmap() {
634            if let Some(cmap) = cmap_tables
635                .iter()
636                .find(|t| t.platform_id == 3 && t.encoding_id == 1)
637                .or_else(|| cmap_tables.first())
638            {
639                for char_code in 32u8..=255 {
640                    if let Some(&glyph_id) = cmap.mappings.get(&(char_code as u32)) {
641                        if let Ok((advance_width, _)) = ttf.get_glyph_metrics(glyph_id) {
642                            let width = (advance_width as f64 * 1000.0) / ttf.units_per_em as f64;
643                            widths.push(width);
644                        } else {
645                            widths.push(250.0);
646                        }
647                    } else {
648                        widths.push(250.0);
649                    }
650                }
651            }
652        }
653
654        if widths.is_empty() {
655            widths = vec![250.0; 224]; // Default widths
656        }
657
658        let metrics = FontMetrics::new(32, 255, widths, 250.0);
659
660        // Check if font is CFF/OpenType
661        let mut font = if ttf.is_cff {
662            // CFF fonts use Identity encoding and Type0 for Unicode support
663            let mut font = Self::new_cff(font_name, FontEncoding::Identity, descriptor, metrics);
664            font.font_file_type = Some(FontFileType::OpenTypeCFF);
665            font
666        } else {
667            // Standard TrueType font
668            let mut font = Self::new_truetype(
669                font_name,
670                FontEncoding::WinAnsiEncoding,
671                descriptor,
672                metrics,
673            );
674            font.font_file_type = Some(FontFileType::TrueType);
675            font
676        };
677
678        font.font_data = Some(data);
679        font.truetype_font = Some(ttf);
680
681        Ok(font)
682    }
683
684    /// Mark characters as used for subsetting
685    pub fn mark_characters_used(&mut self, text: &str) {
686        if let Some(ref ttf) = self.truetype_font {
687            if let Ok(cmap_tables) = ttf.parse_cmap() {
688                if let Some(cmap) = cmap_tables
689                    .iter()
690                    .find(|t| t.platform_id == 3 && t.encoding_id == 1)
691                    .or_else(|| cmap_tables.first())
692                {
693                    for ch in text.chars() {
694                        if let Some(&glyph_id) = cmap.mappings.get(&(ch as u32)) {
695                            self.used_glyphs.insert(glyph_id);
696                        }
697                    }
698                }
699            }
700        }
701    }
702
703    /// Get subset font data
704    pub fn get_subset_font_data(&self) -> Result<Option<Vec<u8>>> {
705        if self.font_type != FontType::TrueType {
706            return Ok(self.font_data.clone());
707        }
708
709        if let Some(ref ttf) = self.truetype_font {
710            if self.used_glyphs.is_empty() {
711                // No subsetting needed if no glyphs used
712                return Ok(self.font_data.clone());
713            }
714
715            // Create subset
716            let subset_data = ttf.create_subset(&self.used_glyphs).map_err(|e| {
717                PdfError::InvalidStructure(format!("Failed to create font subset: {}", e))
718            })?;
719
720            Ok(Some(subset_data))
721        } else {
722            Ok(self.font_data.clone())
723        }
724    }
725
726    /// Detect font file type from data
727    fn detect_font_file_type(data: &[u8]) -> Result<FontFileType> {
728        if data.len() < 4 {
729            return Err(PdfError::InvalidStructure(
730                "Font file too small".to_string(),
731            ));
732        }
733
734        // Check for TrueType signature
735        let signature = u32::from_be_bytes([data[0], data[1], data[2], data[3]]);
736        match signature {
737            0x00010000 | 0x74727565 => Ok(FontFileType::TrueType), // TrueType
738            0x4F54544F => Ok(FontFileType::OpenTypeCFF),           // OpenType with CFF
739            _ => {
740                // Check for Type 1 font (starts with %!PS or %!FontType1)
741                if data.starts_with(b"%!PS") || data.starts_with(b"%!FontType1") {
742                    Ok(FontFileType::Type1)
743                } else {
744                    Err(PdfError::InvalidStructure(
745                        "Unknown font file format".to_string(),
746                    ))
747                }
748            }
749        }
750    }
751
752    /// Convert to PDF font dictionary
753    pub fn to_pdf_dict(&self) -> Dictionary {
754        let mut dict = Dictionary::new();
755
756        // Font type
757        dict.set("Type", Object::Name("Font".to_string()));
758        dict.set(
759            "Subtype",
760            Object::Name(
761                match self.font_type {
762                    FontType::Type1 => "Type1",
763                    FontType::TrueType => "TrueType",
764                    FontType::CFF => "Type0", // CFF fonts use Type0 for Unicode support
765                    FontType::Type3 => "Type3",
766                    FontType::Type0 => "Type0",
767                }
768                .to_string(),
769            ),
770        );
771
772        // Base font name
773        dict.set("BaseFont", Object::Name(self.name.clone()));
774
775        // Encoding
776        match &self.encoding {
777            FontEncoding::StandardEncoding => {
778                dict.set("Encoding", Object::Name("StandardEncoding".to_string()));
779            }
780            FontEncoding::MacRomanEncoding => {
781                dict.set("Encoding", Object::Name("MacRomanEncoding".to_string()));
782            }
783            FontEncoding::WinAnsiEncoding => {
784                dict.set("Encoding", Object::Name("WinAnsiEncoding".to_string()));
785            }
786            FontEncoding::Custom(differences) => {
787                let mut enc_dict = Dictionary::new();
788                enc_dict.set("Type", Object::Name("Encoding".to_string()));
789
790                // Build differences array
791                let mut diff_array = Vec::new();
792                for diff in differences {
793                    diff_array.push(Object::Integer(diff.code as i64));
794                    for name in &diff.names {
795                        diff_array.push(Object::Name(name.clone()));
796                    }
797                }
798                enc_dict.set("Differences", Object::Array(diff_array));
799
800                dict.set("Encoding", Object::Dictionary(enc_dict));
801            }
802            FontEncoding::Identity => {
803                dict.set("Encoding", Object::Name("Identity-H".to_string()));
804            }
805        }
806
807        // Font metrics
808        dict.set("FirstChar", Object::Integer(self.metrics.first_char as i64));
809        dict.set("LastChar", Object::Integer(self.metrics.last_char as i64));
810
811        let widths: Vec<Object> = self
812            .metrics
813            .widths
814            .iter()
815            .map(|&w| Object::Real(w))
816            .collect();
817        dict.set("Widths", Object::Array(widths));
818
819        // Font descriptor reference will be added by FontManager
820
821        dict
822    }
823}
824
825/// Font manager for handling custom fonts
826#[derive(Debug, Clone)]
827pub struct FontManager {
828    /// Registered fonts by name
829    fonts: HashMap<String, CustomFont>,
830    /// Font ID counter
831    next_font_id: usize,
832}
833
834impl Default for FontManager {
835    fn default() -> Self {
836        Self::new()
837    }
838}
839
840impl FontManager {
841    /// Create a new font manager
842    pub fn new() -> Self {
843        Self {
844            fonts: HashMap::new(),
845            next_font_id: 1,
846        }
847    }
848
849    /// Register a custom font
850    pub fn register_font(&mut self, font: CustomFont) -> Result<String> {
851        let font_name = format!("F{}", self.next_font_id);
852        self.fonts.insert(font_name.clone(), font);
853        self.next_font_id += 1;
854        Ok(font_name)
855    }
856
857    /// Get a registered font
858    pub fn get_font(&self, name: &str) -> Option<&CustomFont> {
859        self.fonts.get(name)
860    }
861
862    /// Get the glyph mapping for a registered font
863    pub fn get_font_glyph_mapping(&self, name: &str) -> Option<HashMap<u32, u16>> {
864        if let Some(font) = self.fonts.get(name) {
865            font.get_glyph_mapping()
866        } else {
867            None
868        }
869    }
870
871    /// Get all registered fonts
872    pub fn fonts(&self) -> &HashMap<String, CustomFont> {
873        &self.fonts
874    }
875
876    /// Create font resource dictionary
877    pub fn to_resource_dictionary(&self) -> Result<Dictionary> {
878        let mut font_dict = Dictionary::new();
879
880        for (name, font) in &self.fonts {
881            font_dict.set(name, Object::Dictionary(font.to_pdf_dict()));
882        }
883
884        Ok(font_dict)
885    }
886
887    /// Create standard fonts from built-in Type 1 fonts
888    pub fn create_standard_type1(name: &str) -> Result<CustomFont> {
889        let (encoding, descriptor, metrics) = match name {
890            "Helvetica" => (
891                FontEncoding::WinAnsiEncoding,
892                FontDescriptor::new(
893                    "Helvetica".to_string(),
894                    FontFlags {
895                        non_symbolic: true,
896                        ..Default::default()
897                    },
898                    [-166.0, -225.0, 1000.0, 931.0],
899                    0.0,
900                    718.0,
901                    -207.0,
902                    718.0,
903                    88.0,
904                ),
905                FontMetrics::new(32, 255, Self::helvetica_widths(), 278.0),
906            ),
907            "Times-Roman" => (
908                FontEncoding::WinAnsiEncoding,
909                FontDescriptor::new(
910                    "Times-Roman".to_string(),
911                    FontFlags {
912                        serif: true,
913                        non_symbolic: true,
914                        ..Default::default()
915                    },
916                    [-168.0, -218.0, 1000.0, 898.0],
917                    0.0,
918                    683.0,
919                    -217.0,
920                    662.0,
921                    84.0,
922                ),
923                FontMetrics::new(32, 255, Self::times_widths(), 250.0),
924            ),
925            _ => {
926                return Err(PdfError::InvalidStructure(
927                    "Unknown standard font".to_string(),
928                ))
929            }
930        };
931
932        Ok(CustomFont::new_type1(
933            name.to_string(),
934            encoding,
935            descriptor,
936            metrics,
937        ))
938    }
939
940    /// Helvetica character widths (simplified subset)
941    fn helvetica_widths() -> Vec<f64> {
942        // This would contain the full width table for characters 32-255
943        // Simplified for example
944        vec![278.0; 224] // All characters same width for now
945    }
946
947    /// Times Roman character widths (simplified subset)
948    fn times_widths() -> Vec<f64> {
949        // This would contain the full width table for characters 32-255
950        // Simplified for example
951        vec![250.0; 224] // All characters same width for now
952    }
953}
954
955#[cfg(test)]
956mod tests {
957    use super::*;
958
959    #[test]
960    fn test_font_type() {
961        assert_eq!(FontType::Type1, FontType::Type1);
962        assert_ne!(FontType::Type1, FontType::TrueType);
963    }
964
965    #[test]
966    fn test_font_flags() {
967        let mut flags = FontFlags::default();
968        assert_eq!(flags.to_flags(), 0);
969
970        flags.fixed_pitch = true;
971        flags.serif = true;
972        flags.italic = true;
973        let value = flags.to_flags();
974        assert!(value & (1 << 0) != 0); // fixed_pitch
975        assert!(value & (1 << 1) != 0); // serif
976        assert!(value & (1 << 6) != 0); // italic
977    }
978
979    #[test]
980    fn test_font_descriptor() {
981        let flags = FontFlags {
982            serif: true,
983            non_symbolic: true,
984            ..Default::default()
985        };
986        let descriptor = FontDescriptor::new(
987            "TestFont".to_string(),
988            flags,
989            [-100.0, -200.0, 1000.0, 900.0],
990            0.0,
991            700.0,
992            -200.0,
993            700.0,
994            80.0,
995        );
996
997        let dict = descriptor.to_pdf_dict();
998        assert_eq!(
999            dict.get("Type"),
1000            Some(&Object::Name("FontDescriptor".to_string()))
1001        );
1002        assert_eq!(
1003            dict.get("FontName"),
1004            Some(&Object::Name("TestFont".to_string()))
1005        );
1006    }
1007
1008    #[test]
1009    fn test_font_metrics() {
1010        let widths = vec![100.0, 200.0, 300.0];
1011        let metrics = FontMetrics::new(65, 67, widths, 250.0);
1012
1013        assert_eq!(metrics.get_width(65), 100.0);
1014        assert_eq!(metrics.get_width(66), 200.0);
1015        assert_eq!(metrics.get_width(67), 300.0);
1016        assert_eq!(metrics.get_width(64), 250.0); // Before range
1017        assert_eq!(metrics.get_width(68), 250.0); // After range
1018    }
1019
1020    #[test]
1021    fn test_encoding_difference() {
1022        let diff = EncodingDifference {
1023            code: 128,
1024            names: vec!["Euro".to_string(), "bullet".to_string()],
1025        };
1026        assert_eq!(diff.code, 128);
1027        assert_eq!(diff.names.len(), 2);
1028    }
1029
1030    #[test]
1031    fn test_custom_font_type1() {
1032        let flags = FontFlags::default();
1033        let descriptor = FontDescriptor::new(
1034            "CustomType1".to_string(),
1035            flags,
1036            [0.0, 0.0, 1000.0, 1000.0],
1037            0.0,
1038            750.0,
1039            -250.0,
1040            750.0,
1041            100.0,
1042        );
1043        let metrics = FontMetrics::new(32, 126, vec![250.0; 95], 250.0);
1044
1045        let font = CustomFont::new_type1(
1046            "CustomType1".to_string(),
1047            FontEncoding::StandardEncoding,
1048            descriptor,
1049            metrics,
1050        );
1051
1052        assert_eq!(font.font_type, FontType::Type1);
1053        assert_eq!(font.name, "CustomType1");
1054    }
1055
1056    #[test]
1057    fn test_custom_font_truetype() {
1058        let flags = FontFlags::default();
1059        let descriptor = FontDescriptor::new(
1060            "CustomTrueType".to_string(),
1061            flags,
1062            [0.0, 0.0, 1000.0, 1000.0],
1063            0.0,
1064            750.0,
1065            -250.0,
1066            750.0,
1067            100.0,
1068        );
1069        let metrics = FontMetrics::new(32, 126, vec![250.0; 95], 250.0);
1070
1071        let font = CustomFont::new_truetype(
1072            "CustomTrueType".to_string(),
1073            FontEncoding::WinAnsiEncoding,
1074            descriptor,
1075            metrics,
1076        );
1077
1078        assert_eq!(font.font_type, FontType::TrueType);
1079        assert_eq!(font.name, "CustomTrueType");
1080    }
1081
1082    #[test]
1083    fn test_font_manager() {
1084        let mut manager = FontManager::new();
1085
1086        let font = FontManager::create_standard_type1("Helvetica").unwrap();
1087        let font_name = manager.register_font(font).unwrap();
1088
1089        assert!(font_name.starts_with('F'));
1090        assert!(manager.get_font(&font_name).is_some());
1091
1092        let registered_font = manager.get_font(&font_name).unwrap();
1093        assert_eq!(registered_font.name, "Helvetica");
1094    }
1095
1096    #[test]
1097    fn test_detect_font_file_type() {
1098        // TrueType signature
1099        let ttf_data = vec![0x00, 0x01, 0x00, 0x00];
1100        let font_type = CustomFont::detect_font_file_type(&ttf_data).unwrap();
1101        assert_eq!(font_type, FontFileType::TrueType);
1102
1103        // Type 1 signature
1104        let type1_data = b"%!PS-AdobeFont-1.0";
1105        let font_type = CustomFont::detect_font_file_type(type1_data).unwrap();
1106        assert_eq!(font_type, FontFileType::Type1);
1107
1108        // Invalid data
1109        let invalid_data = vec![0xFF, 0xFF];
1110        assert!(CustomFont::detect_font_file_type(&invalid_data).is_err());
1111    }
1112
1113    #[test]
1114    fn test_font_encoding() {
1115        let encoding = FontEncoding::StandardEncoding;
1116        assert!(matches!(encoding, FontEncoding::StandardEncoding));
1117
1118        let custom = FontEncoding::Custom(vec![EncodingDifference {
1119            code: 128,
1120            names: vec!["Euro".to_string()],
1121        }]);
1122        assert!(matches!(custom, FontEncoding::Custom(_)));
1123    }
1124
1125    #[test]
1126    fn test_font_descriptor_optional_fields() {
1127        let mut descriptor = FontDescriptor::new(
1128            "TestFont".to_string(),
1129            FontFlags::default(),
1130            [0.0, 0.0, 1000.0, 1000.0],
1131            0.0,
1132            750.0,
1133            -250.0,
1134            750.0,
1135            100.0,
1136        );
1137
1138        descriptor.font_family = Some("TestFamily".to_string());
1139        descriptor.font_weight = Some(700);
1140        descriptor.x_height = Some(500.0);
1141
1142        let dict = descriptor.to_pdf_dict();
1143        assert!(dict.get("FontFamily").is_some());
1144        assert!(dict.get("FontWeight").is_some());
1145        assert!(dict.get("XHeight").is_some());
1146    }
1147
1148    #[test]
1149    fn test_font_pdf_dict_generation() {
1150        let flags = FontFlags::default();
1151        let descriptor = FontDescriptor::new(
1152            "TestFont".to_string(),
1153            flags,
1154            [0.0, 0.0, 1000.0, 1000.0],
1155            0.0,
1156            750.0,
1157            -250.0,
1158            750.0,
1159            100.0,
1160        );
1161        let metrics = FontMetrics::new(32, 126, vec![250.0; 95], 250.0);
1162
1163        let font = CustomFont::new_type1(
1164            "TestFont".to_string(),
1165            FontEncoding::WinAnsiEncoding,
1166            descriptor,
1167            metrics,
1168        );
1169
1170        let dict = font.to_pdf_dict();
1171        assert_eq!(dict.get("Type"), Some(&Object::Name("Font".to_string())));
1172        assert_eq!(
1173            dict.get("Subtype"),
1174            Some(&Object::Name("Type1".to_string()))
1175        );
1176        assert_eq!(
1177            dict.get("BaseFont"),
1178            Some(&Object::Name("TestFont".to_string()))
1179        );
1180        assert_eq!(
1181            dict.get("Encoding"),
1182            Some(&Object::Name("WinAnsiEncoding".to_string()))
1183        );
1184    }
1185}