oxidize_pdf/text/fonts/
embedding.rs

1//! Font embedding for PDF generation according to ISO 32000-1 Section 9.8
2//!
3//! This module provides complete font embedding capabilities including:
4//! - TrueType font embedding with subsetting
5//! - Font descriptor generation  
6//! - Character encoding mappings
7//! - CID font support for complex scripts
8
9use crate::error::{PdfError, Result};
10use crate::objects::{Dictionary, Object, ObjectId};
11use crate::text::fonts::truetype::TrueTypeFont;
12use std::collections::{HashMap, HashSet};
13
14/// Font type enumeration for embedding
15#[derive(Debug, Clone, Copy, PartialEq)]
16pub enum FontType {
17    /// TrueType font
18    TrueType,
19    /// Type 0 font (composite/CID)
20    Type0,
21}
22
23/// Font encoding types for embedding
24#[derive(Debug, Clone, PartialEq)]
25pub enum FontEncoding {
26    /// Standard encoding
27    StandardEncoding,
28    /// MacRoman encoding
29    MacRomanEncoding,
30    /// WinAnsi encoding
31    WinAnsiEncoding,
32    /// Custom encoding with differences
33    Custom(Vec<EncodingDifference>),
34    /// Identity encoding for CID fonts
35    Identity,
36}
37
38/// Encoding difference entry
39#[derive(Debug, Clone, PartialEq)]
40pub struct EncodingDifference {
41    /// Starting character code
42    pub code: u8,
43    /// Glyph names for consecutive character codes
44    pub names: Vec<String>,
45}
46
47/// Font flags for font descriptor
48#[derive(Debug, Clone, Copy, Default)]
49pub struct FontFlags {
50    /// All glyphs have the same width
51    pub fixed_pitch: bool,
52    /// Glyphs have serifs
53    pub serif: bool,
54    /// Font uses symbolic character set
55    pub symbolic: bool,
56    /// Font is a script font
57    pub script: bool,
58    /// Font uses Adobe standard Latin character set
59    pub non_symbolic: bool,
60    /// Glyphs resemble cursive handwriting
61    pub italic: bool,
62    /// All glyphs have dominant vertical strokes
63    pub all_cap: bool,
64    /// Font is a small-cap font
65    pub small_cap: bool,
66    /// Font weight is bold or black
67    pub force_bold: bool,
68}
69
70impl FontFlags {
71    /// Convert to PDF font flags integer
72    pub fn to_flags(&self) -> u32 {
73        let mut flags = 0u32;
74        if self.fixed_pitch {
75            flags |= 1 << 0;
76        }
77        if self.serif {
78            flags |= 1 << 1;
79        }
80        if self.symbolic {
81            flags |= 1 << 2;
82        }
83        if self.script {
84            flags |= 1 << 3;
85        }
86        if self.non_symbolic {
87            flags |= 1 << 5;
88        }
89        if self.italic {
90            flags |= 1 << 6;
91        }
92        if self.all_cap {
93            flags |= 1 << 16;
94        }
95        if self.small_cap {
96            flags |= 1 << 17;
97        }
98        if self.force_bold {
99            flags |= 1 << 18;
100        }
101        flags
102    }
103}
104
105/// Font descriptor for PDF embedding
106#[derive(Debug, Clone)]
107pub struct FontDescriptor {
108    /// Font name
109    pub font_name: String,
110    /// Font flags
111    pub flags: FontFlags,
112    /// Font bounding box [llx, lly, urx, ury]
113    pub bbox: [i32; 4],
114    /// Italic angle in degrees
115    pub italic_angle: f64,
116    /// Maximum height above baseline
117    pub ascent: i32,
118    /// Maximum depth below baseline (negative)
119    pub descent: i32,
120    /// Height of capital letters
121    pub cap_height: i32,
122    /// Thickness of dominant vertical stems
123    pub stem_v: i32,
124    /// Thickness of dominant horizontal stems
125    pub stem_h: i32,
126    /// Average character width
127    pub avg_width: i32,
128    /// Maximum character width
129    pub max_width: i32,
130    /// Width for missing characters
131    pub missing_width: i32,
132    /// Font file reference (if embedded)
133    pub font_file: Option<ObjectId>,
134}
135
136/// Font metrics for embedded fonts
137#[derive(Debug, Clone)]
138pub struct FontMetrics {
139    /// Maximum height above baseline
140    pub ascent: i32,
141    /// Maximum depth below baseline (negative)
142    pub descent: i32,
143    /// Height of capital letters
144    pub cap_height: i32,
145    /// Height of lowercase letters
146    pub x_height: i32,
147    /// Thickness of dominant vertical stems
148    pub stem_v: i32,
149    /// Thickness of dominant horizontal stems
150    pub stem_h: i32,
151    /// Average character width
152    pub avg_width: i32,
153    /// Maximum character width
154    pub max_width: i32,
155    /// Width for missing characters
156    pub missing_width: i32,
157}
158
159/// PDF font embedding manager
160#[derive(Debug)]
161pub struct FontEmbedder {
162    /// Font data cache
163    embedded_fonts: HashMap<String, EmbeddedFontData>,
164    /// Next font ID
165    next_font_id: u32,
166}
167
168/// Embedded font data for PDF generation
169#[derive(Debug, Clone)]
170pub struct EmbeddedFontData {
171    /// Font name in PDF
172    pub pdf_name: String,
173    /// Font type
174    pub font_type: FontType,
175    /// Font descriptor object
176    pub descriptor: FontDescriptor,
177    /// Font program data (subset or full)
178    pub font_program: Vec<u8>,
179    /// Character mappings
180    pub encoding: FontEncoding,
181    /// Font metrics
182    pub metrics: FontMetrics,
183    /// Subset glyph set (if subsetted)
184    pub subset_glyphs: Option<HashSet<u16>>,
185    /// Unicode mappings for ToUnicode CMap
186    pub unicode_mappings: HashMap<u16, String>,
187}
188
189/// Font embedding options
190#[derive(Debug, Clone)]
191pub struct EmbeddingOptions {
192    /// Whether to subset the font
193    pub subset: bool,
194    /// Maximum number of glyphs in subset
195    pub max_subset_size: Option<usize>,
196    /// Whether to compress font streams
197    pub compress_font_streams: bool,
198    /// Whether to embed font license info
199    pub embed_license_info: bool,
200}
201
202impl Default for EmbeddingOptions {
203    fn default() -> Self {
204        Self {
205            subset: true,
206            max_subset_size: Some(256),
207            compress_font_streams: true,
208            embed_license_info: false,
209        }
210    }
211}
212
213impl FontEmbedder {
214    /// Create a new font embedder
215    pub fn new() -> Self {
216        Self {
217            embedded_fonts: HashMap::new(),
218            next_font_id: 1,
219        }
220    }
221
222    /// Embed a TrueType font with optional subsetting
223    pub fn embed_truetype_font(
224        &mut self,
225        font_data: &[u8],
226        used_glyphs: &HashSet<u16>,
227        options: &EmbeddingOptions,
228    ) -> Result<String> {
229        // Parse the TrueType font
230        let font = TrueTypeFont::from_data(font_data)
231            .map_err(|e| PdfError::FontError(format!("Failed to parse font: {e}")))?;
232
233        // Generate unique font name
234        let font_name = format!("ABCDEF+Font{next_id}", next_id = self.next_font_id);
235        self.next_font_id += 1;
236
237        // Determine if we should subset
238        let should_subset =
239            options.subset && used_glyphs.len() < options.max_subset_size.unwrap_or(256);
240
241        // Create font program (subset or full)
242        let font_program = if should_subset {
243            font.create_subset(used_glyphs)
244                .map_err(|e| PdfError::FontError(format!("Failed to create subset: {e}")))?
245        } else {
246            font_data.to_vec()
247        };
248
249        // Extract font metrics
250        let metrics = self.extract_font_metrics(&font)?;
251
252        // Create font descriptor
253        let descriptor = self.create_font_descriptor(&font, &font_name)?;
254
255        // Create character encoding
256        let encoding = self.create_encoding_for_font(&font, used_glyphs)?;
257
258        // Create Unicode mappings for ToUnicode CMap
259        let unicode_mappings = self.create_unicode_mappings(&font, used_glyphs)?;
260
261        // Store embedded font data
262        let embedded_font = EmbeddedFontData {
263            pdf_name: font_name.clone(),
264            font_type: FontType::TrueType,
265            descriptor,
266            font_program,
267            encoding,
268            metrics,
269            subset_glyphs: if should_subset {
270                Some(used_glyphs.clone())
271            } else {
272                None
273            },
274            unicode_mappings,
275        };
276
277        self.embedded_fonts.insert(font_name.clone(), embedded_font);
278        Ok(font_name)
279    }
280
281    /// Create a Type0 (CID) font for complex scripts
282    pub fn embed_cid_font(
283        &mut self,
284        font_data: &[u8],
285        used_chars: &HashSet<u32>,
286        _cmap_name: &str,
287        options: &EmbeddingOptions,
288    ) -> Result<String> {
289        // Parse the font
290        let font = TrueTypeFont::from_data(font_data)
291            .map_err(|e| PdfError::FontError(format!("Failed to parse font: {e}")))?;
292
293        // Generate unique font name
294        let font_name = format!("ABCDEF+CIDFont{next_id}", next_id = self.next_font_id);
295        self.next_font_id += 1;
296
297        // Convert character codes to glyph indices
298        let used_glyphs = self.chars_to_glyphs(&font, used_chars)?;
299
300        // Create subset if requested
301        let font_program = if options.subset {
302            font.create_subset(&used_glyphs)
303                .map_err(|e| PdfError::FontError(format!("Failed to create subset: {e}")))?
304        } else {
305            font_data.to_vec()
306        };
307
308        // Extract metrics
309        let metrics = self.extract_font_metrics(&font)?;
310
311        // Create CID font descriptor
312        let descriptor = self.create_cid_font_descriptor(&font, &font_name)?;
313
314        // Create Identity encoding for CID fonts
315        let encoding = FontEncoding::Identity;
316
317        // Create Unicode mappings
318        let unicode_mappings = self.create_cid_unicode_mappings(&font, used_chars)?;
319
320        let embedded_font = EmbeddedFontData {
321            pdf_name: font_name.clone(),
322            font_type: FontType::Type0,
323            descriptor,
324            font_program,
325            encoding,
326            metrics,
327            subset_glyphs: Some(used_glyphs),
328            unicode_mappings,
329        };
330
331        self.embedded_fonts.insert(font_name.clone(), embedded_font);
332        Ok(font_name)
333    }
334
335    /// Generate PDF font dictionary for embedded font
336    pub fn generate_font_dictionary(&self, font_name: &str) -> Result<Dictionary> {
337        let font_data = self
338            .embedded_fonts
339            .get(font_name)
340            .ok_or_else(|| PdfError::FontError(format!("Font {font_name} not found")))?;
341
342        match font_data.font_type {
343            FontType::TrueType => self.generate_truetype_dictionary(font_data),
344            FontType::Type0 => self.generate_type0_dictionary(font_data),
345            // _ => Err(PdfError::FontError("Unsupported font type for embedding".to_string())),
346        }
347    }
348
349    /// Generate TrueType font dictionary
350    fn generate_truetype_dictionary(&self, font_data: &EmbeddedFontData) -> Result<Dictionary> {
351        let mut font_dict = Dictionary::new();
352
353        // Basic font properties
354        font_dict.set("Type", Object::Name("Font".to_string()));
355        font_dict.set("Subtype", Object::Name("TrueType".to_string()));
356        font_dict.set("BaseFont", Object::Name(font_data.pdf_name.clone()));
357
358        // Font descriptor reference (would be resolved during PDF generation)
359        font_dict.set("FontDescriptor", Object::Reference(ObjectId::new(0, 0))); // Placeholder
360
361        // Encoding
362        match &font_data.encoding {
363            FontEncoding::WinAnsiEncoding => {
364                font_dict.set("Encoding", Object::Name("WinAnsiEncoding".to_string()));
365            }
366            FontEncoding::MacRomanEncoding => {
367                font_dict.set("Encoding", Object::Name("MacRomanEncoding".to_string()));
368            }
369            FontEncoding::StandardEncoding => {
370                font_dict.set("Encoding", Object::Name("StandardEncoding".to_string()));
371            }
372            FontEncoding::Custom(differences) => {
373                let mut encoding_dict = Dictionary::new();
374                encoding_dict.set("Type", Object::Name("Encoding".to_string()));
375                encoding_dict.set("BaseEncoding", Object::Name("WinAnsiEncoding".to_string()));
376
377                // Add differences array
378                let mut diff_array = Vec::new();
379                for diff in differences {
380                    diff_array.push(Object::Integer(diff.code as i64));
381                    for name in &diff.names {
382                        diff_array.push(Object::Name(name.clone()));
383                    }
384                }
385                encoding_dict.set("Differences", Object::Array(diff_array));
386                font_dict.set("Encoding", Object::Dictionary(encoding_dict));
387            }
388            _ => {}
389        }
390
391        // First and last character codes
392        font_dict.set("FirstChar", Object::Integer(32));
393        font_dict.set("LastChar", Object::Integer(255));
394
395        // Character widths (simplified - would need actual glyph widths)
396        let widths: Vec<Object> = (32..=255)
397            .map(|_| Object::Integer(500)) // Default width
398            .collect();
399        font_dict.set("Widths", Object::Array(widths));
400
401        Ok(font_dict)
402    }
403
404    /// Generate Type0 (CID) font dictionary
405    fn generate_type0_dictionary(&self, font_data: &EmbeddedFontData) -> Result<Dictionary> {
406        let mut font_dict = Dictionary::new();
407
408        // Type0 font properties
409        font_dict.set("Type", Object::Name("Font".to_string()));
410        font_dict.set("Subtype", Object::Name("Type0".to_string()));
411        font_dict.set("BaseFont", Object::Name(font_data.pdf_name.clone()));
412
413        // Encoding (CMap)
414        font_dict.set("Encoding", Object::Name("Identity-H".to_string()));
415
416        // DescendantFonts array (would contain CIDFont reference)
417        font_dict.set(
418            "DescendantFonts",
419            Object::Array(vec![
420                Object::Reference(ObjectId::new(0, 0)), // Placeholder for CIDFont reference
421            ]),
422        );
423
424        // ToUnicode CMap reference (if needed)
425        if !font_data.unicode_mappings.is_empty() {
426            font_dict.set("ToUnicode", Object::Reference(ObjectId::new(0, 0))); // Placeholder
427        }
428
429        Ok(font_dict)
430    }
431
432    /// Generate font descriptor dictionary
433    pub fn generate_font_descriptor(&self, font_name: &str) -> Result<Dictionary> {
434        let font_data = self
435            .embedded_fonts
436            .get(font_name)
437            .ok_or_else(|| PdfError::FontError(format!("Font {font_name} not found")))?;
438
439        let mut desc_dict = Dictionary::new();
440
441        desc_dict.set("Type", Object::Name("FontDescriptor".to_string()));
442        desc_dict.set("FontName", Object::Name(font_data.pdf_name.clone()));
443
444        // Font flags
445        desc_dict.set(
446            "Flags",
447            Object::Integer(font_data.descriptor.flags.to_flags() as i64),
448        );
449
450        // Font metrics
451        desc_dict.set("Ascent", Object::Integer(font_data.metrics.ascent as i64));
452        desc_dict.set("Descent", Object::Integer(font_data.metrics.descent as i64));
453        desc_dict.set(
454            "CapHeight",
455            Object::Integer(font_data.metrics.cap_height as i64),
456        );
457        desc_dict.set(
458            "ItalicAngle",
459            Object::Real(font_data.descriptor.italic_angle),
460        );
461        desc_dict.set("StemV", Object::Integer(font_data.descriptor.stem_v as i64));
462
463        // Font bounding box
464        let bbox = vec![
465            Object::Integer(font_data.descriptor.bbox[0] as i64),
466            Object::Integer(font_data.descriptor.bbox[1] as i64),
467            Object::Integer(font_data.descriptor.bbox[2] as i64),
468            Object::Integer(font_data.descriptor.bbox[3] as i64),
469        ];
470        desc_dict.set("FontBBox", Object::Array(bbox));
471
472        // Font file reference (would be set during PDF generation)
473        match font_data.font_type {
474            FontType::TrueType => {
475                desc_dict.set("FontFile2", Object::Reference(ObjectId::new(0, 0)));
476                // Placeholder
477            }
478            FontType::Type0 => {
479                desc_dict.set("FontFile2", Object::Reference(ObjectId::new(0, 0)));
480                // Placeholder
481            }
482        }
483
484        Ok(desc_dict)
485    }
486
487    /// Generate ToUnicode CMap stream
488    pub fn generate_tounicode_cmap(&self, font_name: &str) -> Result<String> {
489        let font_data = self
490            .embedded_fonts
491            .get(font_name)
492            .ok_or_else(|| PdfError::FontError(format!("Font {font_name} not found")))?;
493
494        if font_data.unicode_mappings.is_empty() {
495            return Err(PdfError::FontError(
496                "No Unicode mappings available".to_string(),
497            ));
498        }
499
500        let mut cmap_content = String::new();
501
502        // CMap header
503        cmap_content.push_str("/CIDInit /ProcSet findresource begin\n");
504        cmap_content.push_str("12 dict begin\n");
505        cmap_content.push_str("begincmap\n");
506        cmap_content.push_str("/CIDSystemInfo\n");
507        cmap_content.push_str("<<\n");
508        cmap_content.push_str("/Registry (Adobe)\n");
509        cmap_content.push_str("/Ordering (UCS)\n");
510        cmap_content.push_str("/Supplement 0\n");
511        cmap_content.push_str(">> def\n");
512        cmap_content.push_str("/CMapName /Adobe-Identity-UCS def\n");
513        cmap_content.push_str("/CMapType 2 def\n");
514        cmap_content.push_str("1 begincodespacerange\n");
515        cmap_content.push_str("<0000> <FFFF>\n");
516        cmap_content.push_str("endcodespacerange\n");
517
518        // Unicode mappings
519        cmap_content.push_str(&format!(
520            "{} beginbfchar\n",
521            font_data.unicode_mappings.len()
522        ));
523        for (glyph_id, unicode_string) in &font_data.unicode_mappings {
524            cmap_content.push_str(&format!(
525                "<{:04X}> <{}>\n",
526                glyph_id,
527                unicode_string
528                    .chars()
529                    .map(|c| format!("{c:04X}", c = c as u32))
530                    .collect::<String>()
531            ));
532        }
533        cmap_content.push_str("endbfchar\n");
534
535        // CMap footer
536        cmap_content.push_str("endcmap\n");
537        cmap_content.push_str("CMapName currentdict /CMap defineresource pop\n");
538        cmap_content.push_str("end\n");
539        cmap_content.push_str("end\n");
540
541        Ok(cmap_content)
542    }
543
544    /// Get all embedded fonts
545    pub fn embedded_fonts(&self) -> &HashMap<String, EmbeddedFontData> {
546        &self.embedded_fonts
547    }
548
549    /// Extract font metrics from TrueType font
550    fn extract_font_metrics(&self, _font: &TrueTypeFont) -> Result<FontMetrics> {
551        // This would extract actual metrics from font tables
552        // For now, return default metrics
553        Ok(FontMetrics {
554            ascent: 750,
555            descent: -250,
556            cap_height: 700,
557            x_height: 500,
558            stem_v: 100,
559            stem_h: 50,
560            avg_width: 500,
561            max_width: 1000,
562            missing_width: 500,
563        })
564    }
565
566    /// Create font descriptor from TrueType font
567    fn create_font_descriptor(
568        &self,
569        _font: &TrueTypeFont,
570        font_name: &str,
571    ) -> Result<FontDescriptor> {
572        Ok(FontDescriptor {
573            font_name: font_name.to_string(),
574            flags: FontFlags {
575                non_symbolic: true,
576                ..Default::default()
577            },
578            bbox: [-100, -250, 1000, 750], // Default bounding box
579            italic_angle: 0.0,
580            ascent: 750,
581            descent: -250,
582            cap_height: 700,
583            stem_v: 100,
584            stem_h: 50,
585            avg_width: 500,
586            max_width: 1000,
587            missing_width: 500,
588            font_file: None,
589        })
590    }
591
592    /// Create CID font descriptor
593    fn create_cid_font_descriptor(
594        &self,
595        font: &TrueTypeFont,
596        font_name: &str,
597    ) -> Result<FontDescriptor> {
598        // Similar to create_font_descriptor but for CID fonts
599        self.create_font_descriptor(font, font_name)
600    }
601
602    /// Create encoding for font
603    fn create_encoding_for_font(
604        &self,
605        _font: &TrueTypeFont,
606        _used_glyphs: &HashSet<u16>,
607    ) -> Result<FontEncoding> {
608        // For now, return WinAnsi encoding
609        // In a full implementation, this would analyze the font and create appropriate encoding
610        Ok(FontEncoding::WinAnsiEncoding)
611    }
612
613    /// Create Unicode mappings for simple fonts
614    fn create_unicode_mappings(
615        &self,
616        _font: &TrueTypeFont,
617        used_glyphs: &HashSet<u16>,
618    ) -> Result<HashMap<u16, String>> {
619        let mut mappings = HashMap::new();
620
621        // Create basic ASCII mappings
622        for glyph_id in used_glyphs {
623            if *glyph_id < 256 {
624                let unicode_char = char::from(*glyph_id as u8);
625                if unicode_char.is_ascii_graphic() || unicode_char == ' ' {
626                    mappings.insert(*glyph_id, unicode_char.to_string());
627                }
628            }
629        }
630
631        Ok(mappings)
632    }
633
634    /// Create Unicode mappings for CID fonts
635    fn create_cid_unicode_mappings(
636        &self,
637        _font: &TrueTypeFont,
638        used_chars: &HashSet<u32>,
639    ) -> Result<HashMap<u16, String>> {
640        let mut mappings = HashMap::new();
641
642        // Convert character codes to Unicode strings
643        for &char_code in used_chars {
644            if let Some(unicode_char) = char::from_u32(char_code) {
645                // Find glyph ID for this character (simplified)
646                let glyph_id = char_code as u16; // Simplified mapping
647                mappings.insert(glyph_id, unicode_char.to_string());
648            }
649        }
650
651        Ok(mappings)
652    }
653
654    /// Convert character codes to glyph indices
655    fn chars_to_glyphs(&self, _font: &TrueTypeFont, chars: &HashSet<u32>) -> Result<HashSet<u16>> {
656        let mut glyphs = HashSet::new();
657
658        // Always include glyph 0 (missing glyph)
659        glyphs.insert(0);
660
661        // Convert characters to glyph indices using font's character map
662        for &char_code in chars {
663            // This is simplified - a real implementation would use the font's cmap table
664            let glyph_id = if char_code < 65536 {
665                char_code as u16
666            } else {
667                0 // Missing glyph for characters outside BMP
668            };
669            glyphs.insert(glyph_id);
670        }
671
672        Ok(glyphs)
673    }
674}
675
676impl Default for FontEmbedder {
677    fn default() -> Self {
678        Self::new()
679    }
680}
681
682#[cfg(test)]
683mod tests {
684    use super::*;
685
686    #[test]
687    fn test_font_embedder_creation() {
688        let embedder = FontEmbedder::new();
689        assert_eq!(embedder.embedded_fonts.len(), 0);
690        assert_eq!(embedder.next_font_id, 1);
691    }
692
693    #[test]
694    fn test_embedding_options_default() {
695        let options = EmbeddingOptions::default();
696        assert!(options.subset);
697        assert_eq!(options.max_subset_size, Some(256));
698        assert!(options.compress_font_streams);
699        assert!(!options.embed_license_info);
700    }
701
702    #[test]
703    fn test_generate_tounicode_cmap_empty() {
704        let mut embedder = FontEmbedder::new();
705
706        // Create a font with no Unicode mappings
707        let font_data = EmbeddedFontData {
708            pdf_name: "TestFont".to_string(),
709            font_type: FontType::TrueType,
710            descriptor: FontDescriptor {
711                font_name: "TestFont".to_string(),
712                flags: FontFlags::default(),
713                bbox: [0, 0, 1000, 1000],
714                italic_angle: 0.0,
715                ascent: 750,
716                descent: -250,
717                cap_height: 700,
718                stem_v: 100,
719                stem_h: 50,
720                avg_width: 500,
721                max_width: 1000,
722                missing_width: 500,
723                font_file: None,
724            },
725            font_program: vec![],
726            encoding: FontEncoding::WinAnsiEncoding,
727            metrics: FontMetrics {
728                ascent: 750,
729                descent: -250,
730                cap_height: 700,
731                x_height: 500,
732                stem_v: 100,
733                stem_h: 50,
734                avg_width: 500,
735                max_width: 1000,
736                missing_width: 500,
737            },
738            subset_glyphs: None,
739            unicode_mappings: HashMap::new(),
740        };
741
742        embedder
743            .embedded_fonts
744            .insert("TestFont".to_string(), font_data);
745
746        let result = embedder.generate_tounicode_cmap("TestFont");
747        assert!(result.is_err());
748    }
749
750    #[test]
751    fn test_generate_truetype_dictionary() {
752        let embedder = FontEmbedder::new();
753
754        let font_data = EmbeddedFontData {
755            pdf_name: "TestFont".to_string(),
756            font_type: FontType::TrueType,
757            descriptor: FontDescriptor {
758                font_name: "TestFont".to_string(),
759                flags: FontFlags::default(),
760                bbox: [0, 0, 1000, 1000],
761                italic_angle: 0.0,
762                ascent: 750,
763                descent: -250,
764                cap_height: 700,
765                stem_v: 100,
766                stem_h: 50,
767                avg_width: 500,
768                max_width: 1000,
769                missing_width: 500,
770                font_file: None,
771            },
772            font_program: vec![],
773            encoding: FontEncoding::WinAnsiEncoding,
774            metrics: FontMetrics {
775                ascent: 750,
776                descent: -250,
777                cap_height: 700,
778                x_height: 500,
779                stem_v: 100,
780                stem_h: 50,
781                avg_width: 500,
782                max_width: 1000,
783                missing_width: 500,
784            },
785            subset_glyphs: None,
786            unicode_mappings: HashMap::new(),
787        };
788
789        let dict = embedder.generate_truetype_dictionary(&font_data).unwrap();
790
791        // Verify basic font properties
792        if let Some(Object::Name(font_type)) = dict.get("Type") {
793            assert_eq!(font_type, "Font");
794        }
795        if let Some(Object::Name(subtype)) = dict.get("Subtype") {
796            assert_eq!(subtype, "TrueType");
797        }
798        if let Some(Object::Name(base_font)) = dict.get("BaseFont") {
799            assert_eq!(base_font, "TestFont");
800        }
801    }
802
803    #[test]
804    fn test_generate_type0_dictionary() {
805        let embedder = FontEmbedder::new();
806
807        let font_data = EmbeddedFontData {
808            pdf_name: "TestCIDFont".to_string(),
809            font_type: FontType::Type0,
810            descriptor: FontDescriptor {
811                font_name: "TestCIDFont".to_string(),
812                flags: FontFlags::default(),
813                bbox: [0, 0, 1000, 1000],
814                italic_angle: 0.0,
815                ascent: 750,
816                descent: -250,
817                cap_height: 700,
818                stem_v: 100,
819                stem_h: 50,
820                avg_width: 500,
821                max_width: 1000,
822                missing_width: 500,
823                font_file: None,
824            },
825            font_program: vec![],
826            encoding: FontEncoding::Identity,
827            metrics: FontMetrics {
828                ascent: 750,
829                descent: -250,
830                cap_height: 700,
831                x_height: 500,
832                stem_v: 100,
833                stem_h: 50,
834                avg_width: 500,
835                max_width: 1000,
836                missing_width: 500,
837            },
838            subset_glyphs: None,
839            unicode_mappings: HashMap::new(),
840        };
841
842        let dict = embedder.generate_type0_dictionary(&font_data).unwrap();
843
844        // Verify Type0 font properties
845        if let Some(Object::Name(subtype)) = dict.get("Subtype") {
846            assert_eq!(subtype, "Type0");
847        }
848        if let Some(Object::Name(encoding)) = dict.get("Encoding") {
849            assert_eq!(encoding, "Identity-H");
850        }
851        if let Some(Object::Array(descendant_fonts)) = dict.get("DescendantFonts") {
852            assert_eq!(descendant_fonts.len(), 1);
853        }
854    }
855
856    #[test]
857    fn test_chars_to_glyphs_conversion() {
858        let _embedder = FontEmbedder::new();
859        let _font_data = vec![0; 100]; // Dummy font data
860
861        // This would fail in real implementation due to invalid font data
862        // but tests the function structure
863        let chars: HashSet<u32> = [65, 66, 67].iter().cloned().collect(); // A, B, C
864
865        // Test would require valid font data to complete
866        // For now, test that the function exists and compiles
867        assert!(chars.len() == 3);
868    }
869
870    #[test]
871    fn test_unicode_mappings_creation() {
872        let _embedder = FontEmbedder::new();
873        let glyphs: HashSet<u16> = [65, 66, 67].iter().cloned().collect();
874
875        // Create dummy font for testing
876        let _font_data = vec![0; 100];
877
878        // Test would require valid TrueType font parsing
879        // For now, verify function signature
880        assert!(glyphs.len() == 3);
881    }
882
883    #[test]
884    fn test_font_descriptor_generation() {
885        let _embedder = FontEmbedder::new();
886
887        let font_data = EmbeddedFontData {
888            pdf_name: "TestFont".to_string(),
889            font_type: FontType::TrueType,
890            descriptor: FontDescriptor {
891                font_name: "TestFont".to_string(),
892                flags: FontFlags {
893                    non_symbolic: true,
894                    serif: true,
895                    ..Default::default()
896                },
897                bbox: [-100, -250, 1000, 750],
898                italic_angle: 0.0,
899                ascent: 750,
900                descent: -250,
901                cap_height: 700,
902                stem_v: 100,
903                stem_h: 50,
904                avg_width: 500,
905                max_width: 1000,
906                missing_width: 500,
907                font_file: None,
908            },
909            font_program: vec![],
910            encoding: FontEncoding::WinAnsiEncoding,
911            metrics: FontMetrics {
912                ascent: 750,
913                descent: -250,
914                cap_height: 700,
915                x_height: 500,
916                stem_v: 100,
917                stem_h: 50,
918                avg_width: 500,
919                max_width: 1000,
920                missing_width: 500,
921            },
922            subset_glyphs: None,
923            unicode_mappings: HashMap::new(),
924        };
925
926        let mut embedder_with_font = FontEmbedder::new();
927        embedder_with_font
928            .embedded_fonts
929            .insert("TestFont".to_string(), font_data);
930
931        let desc_dict = embedder_with_font
932            .generate_font_descriptor("TestFont")
933            .unwrap();
934
935        // Verify font descriptor properties
936        if let Some(Object::Name(font_name)) = desc_dict.get("FontName") {
937            assert_eq!(font_name, "TestFont");
938        }
939        if let Some(Object::Integer(flags)) = desc_dict.get("Flags") {
940            assert!(*flags > 0); // Should have some flags set
941        }
942        if let Some(Object::Array(bbox)) = desc_dict.get("FontBBox") {
943            assert_eq!(bbox.len(), 4);
944        }
945    }
946}