1use crate::error::{PdfError, Result};
10use crate::objects::{Dictionary, Object, ObjectId};
11use crate::text::fonts::truetype::TrueTypeFont;
12use std::collections::{HashMap, HashSet};
13
14#[derive(Debug, Clone, Copy, PartialEq)]
16pub enum FontType {
17 TrueType,
19 Type0,
21}
22
23#[derive(Debug, Clone, PartialEq)]
25pub enum FontEncoding {
26 StandardEncoding,
28 MacRomanEncoding,
30 WinAnsiEncoding,
32 Custom(Vec<EncodingDifference>),
34 Identity,
36}
37
38#[derive(Debug, Clone, Copy, PartialEq)]
40pub enum CjkFontType {
41 ChineseSimplified,
43 ChineseTraditional,
45 Japanese,
47 Korean,
49 Generic,
51}
52
53impl CjkFontType {
54 pub fn cid_system_info(&self) -> (&'static str, &'static str, i32) {
56 match self {
57 CjkFontType::ChineseSimplified => ("Adobe", "Identity", 0), CjkFontType::ChineseTraditional => ("Adobe", "Identity", 0), CjkFontType::Japanese => ("Adobe", "Identity", 0), CjkFontType::Korean => ("Adobe", "Identity", 0), CjkFontType::Generic => ("Adobe", "Identity", 0),
62 }
63 }
64
65 pub fn detect_from_name(font_name: &str) -> Option<Self> {
67 let name_lower = font_name.to_lowercase();
68
69 if name_lower.contains("sourcehansans")
71 || name_lower.contains("source han sans")
72 || name_lower.contains("hansans")
73 || name_lower.contains("han sans")
74 || name_lower.contains("sourcehan")
75 || name_lower.contains("source han")
76 {
77 if name_lower.contains("sc") || name_lower.contains("simplifiedchinese") {
78 return Some(CjkFontType::ChineseSimplified);
79 }
80 if name_lower.contains("tc") || name_lower.contains("traditionalchinese") {
81 return Some(CjkFontType::ChineseTraditional);
82 }
83 if name_lower.contains("jp") || name_lower.contains("japanese") {
84 return Some(CjkFontType::Japanese);
85 }
86 if name_lower.contains("kr") || name_lower.contains("korean") {
87 return Some(CjkFontType::Korean);
88 }
89 }
90
91 if name_lower.contains("notosanscjk") || name_lower.contains("noto sans cjk") {
93 if name_lower.contains("sc") {
94 return Some(CjkFontType::ChineseSimplified);
95 }
96 if name_lower.contains("tc") {
97 return Some(CjkFontType::ChineseTraditional);
98 }
99 if name_lower.contains("jp") {
100 return Some(CjkFontType::Japanese);
101 }
102 if name_lower.contains("kr") {
103 return Some(CjkFontType::Korean);
104 }
105 }
106
107 if name_lower.contains("chinese") || name_lower.contains("zh") || name_lower.contains("gb")
109 {
110 if name_lower.contains("traditional")
111 || name_lower.contains("tw")
112 || name_lower.contains("hk")
113 {
114 return Some(CjkFontType::ChineseTraditional);
115 }
116 return Some(CjkFontType::ChineseSimplified);
117 }
118
119 if name_lower.contains("japanese")
120 || name_lower.contains("jp")
121 || name_lower.contains("japan")
122 {
123 return Some(CjkFontType::Japanese);
124 }
125
126 if name_lower.contains("korean")
127 || name_lower.contains("kr")
128 || name_lower.contains("korea")
129 {
130 return Some(CjkFontType::Korean);
131 }
132
133 None
134 }
135
136 pub fn should_use_cidfonttype2(is_cff: bool) -> bool {
152 !is_cff
153 }
154}
155
156#[derive(Debug, Clone, PartialEq)]
158pub struct EncodingDifference {
159 pub code: u8,
161 pub names: Vec<String>,
163}
164
165#[derive(Debug, Clone, Copy, Default)]
167pub struct FontFlags {
168 pub fixed_pitch: bool,
170 pub serif: bool,
172 pub symbolic: bool,
174 pub script: bool,
176 pub non_symbolic: bool,
178 pub italic: bool,
180 pub all_cap: bool,
182 pub small_cap: bool,
184 pub force_bold: bool,
186}
187
188impl FontFlags {
189 pub fn to_flags(&self) -> u32 {
191 let mut flags = 0u32;
192 if self.fixed_pitch {
193 flags |= 1 << 0;
194 }
195 if self.serif {
196 flags |= 1 << 1;
197 }
198 if self.symbolic {
199 flags |= 1 << 2;
200 }
201 if self.script {
202 flags |= 1 << 3;
203 }
204 if self.non_symbolic {
205 flags |= 1 << 5;
206 }
207 if self.italic {
208 flags |= 1 << 6;
209 }
210 if self.all_cap {
211 flags |= 1 << 16;
212 }
213 if self.small_cap {
214 flags |= 1 << 17;
215 }
216 if self.force_bold {
217 flags |= 1 << 18;
218 }
219 flags
220 }
221}
222
223#[derive(Debug, Clone)]
225pub struct FontDescriptor {
226 pub font_name: String,
228 pub flags: FontFlags,
230 pub bbox: [i32; 4],
232 pub italic_angle: f64,
234 pub ascent: i32,
236 pub descent: i32,
238 pub cap_height: i32,
240 pub stem_v: i32,
242 pub stem_h: i32,
244 pub avg_width: i32,
246 pub max_width: i32,
248 pub missing_width: i32,
250 pub font_file: Option<ObjectId>,
252}
253
254#[derive(Debug, Clone)]
256pub struct FontMetrics {
257 pub ascent: i32,
259 pub descent: i32,
261 pub cap_height: i32,
263 pub x_height: i32,
265 pub stem_v: i32,
267 pub stem_h: i32,
269 pub avg_width: i32,
271 pub max_width: i32,
273 pub missing_width: i32,
275}
276
277#[derive(Debug)]
279pub struct FontEmbedder {
280 embedded_fonts: HashMap<String, EmbeddedFontData>,
282 next_font_id: u32,
284}
285
286#[derive(Debug, Clone)]
288pub struct EmbeddedFontData {
289 pub pdf_name: String,
291 pub font_type: FontType,
293 pub descriptor: FontDescriptor,
295 pub font_program: Vec<u8>,
297 pub encoding: FontEncoding,
299 pub metrics: FontMetrics,
301 pub subset_glyphs: Option<HashSet<u16>>,
303 pub unicode_mappings: HashMap<u16, String>,
305}
306
307#[derive(Debug, Clone)]
309pub struct EmbeddingOptions {
310 pub subset: bool,
312 pub max_subset_size: Option<usize>,
314 pub compress_font_streams: bool,
316 pub embed_license_info: bool,
318}
319
320impl Default for EmbeddingOptions {
321 fn default() -> Self {
322 Self {
323 subset: true,
324 max_subset_size: Some(256),
325 compress_font_streams: true,
326 embed_license_info: false,
327 }
328 }
329}
330
331impl FontEmbedder {
332 pub fn new() -> Self {
334 Self {
335 embedded_fonts: HashMap::new(),
336 next_font_id: 1,
337 }
338 }
339
340 pub fn embed_truetype_font(
342 &mut self,
343 font_data: &[u8],
344 used_glyphs: &HashSet<u16>,
345 options: &EmbeddingOptions,
346 ) -> Result<String> {
347 let font = TrueTypeFont::from_data(font_data)
349 .map_err(|e| PdfError::FontError(format!("Failed to parse font: {e}")))?;
350
351 let font_name = format!("ABCDEF+Font{next_id}", next_id = self.next_font_id);
353 self.next_font_id += 1;
354
355 let should_subset =
357 options.subset && used_glyphs.len() < options.max_subset_size.unwrap_or(256);
358
359 let font_program = if should_subset {
361 font.create_subset(used_glyphs)
362 .map_err(|e| PdfError::FontError(format!("Failed to create subset: {e}")))?
363 } else {
364 font_data.to_vec()
365 };
366
367 let metrics = self.extract_font_metrics(&font)?;
369
370 let descriptor = self.create_font_descriptor(&font, &font_name)?;
372
373 let encoding = self.create_encoding_for_font(&font, used_glyphs)?;
375
376 let unicode_mappings = self.create_unicode_mappings(&font, used_glyphs)?;
378
379 let embedded_font = EmbeddedFontData {
381 pdf_name: font_name.clone(),
382 font_type: FontType::TrueType,
383 descriptor,
384 font_program,
385 encoding,
386 metrics,
387 subset_glyphs: if should_subset {
388 Some(used_glyphs.clone())
389 } else {
390 None
391 },
392 unicode_mappings,
393 };
394
395 self.embedded_fonts.insert(font_name.clone(), embedded_font);
396 Ok(font_name)
397 }
398
399 pub fn embed_cid_font(
401 &mut self,
402 font_data: &[u8],
403 used_chars: &HashSet<u32>,
404 _cmap_name: &str,
405 options: &EmbeddingOptions,
406 ) -> Result<String> {
407 let font = TrueTypeFont::from_data(font_data)
409 .map_err(|e| PdfError::FontError(format!("Failed to parse font: {e}")))?;
410
411 let font_name = format!("ABCDEF+CIDFont{next_id}", next_id = self.next_font_id);
413 self.next_font_id += 1;
414
415 let used_glyphs = self.chars_to_glyphs(&font, used_chars)?;
417
418 let font_program = if options.subset {
420 font.create_subset(&used_glyphs)
421 .map_err(|e| PdfError::FontError(format!("Failed to create subset: {e}")))?
422 } else {
423 font_data.to_vec()
424 };
425
426 let metrics = self.extract_font_metrics(&font)?;
428
429 let descriptor = self.create_cid_font_descriptor(&font, &font_name)?;
431
432 let encoding = FontEncoding::Identity;
434
435 let unicode_mappings = self.create_cid_unicode_mappings(&font, used_chars)?;
437
438 let embedded_font = EmbeddedFontData {
439 pdf_name: font_name.clone(),
440 font_type: FontType::Type0,
441 descriptor,
442 font_program,
443 encoding,
444 metrics,
445 subset_glyphs: Some(used_glyphs),
446 unicode_mappings,
447 };
448
449 self.embedded_fonts.insert(font_name.clone(), embedded_font);
450 Ok(font_name)
451 }
452
453 pub fn generate_font_dictionary(&self, font_name: &str) -> Result<Dictionary> {
455 let font_data = self
456 .embedded_fonts
457 .get(font_name)
458 .ok_or_else(|| PdfError::FontError(format!("Font {font_name} not found")))?;
459
460 match font_data.font_type {
461 FontType::TrueType => self.generate_truetype_dictionary(font_data),
462 FontType::Type0 => self.generate_type0_dictionary(font_data),
463 }
465 }
466
467 fn generate_truetype_dictionary(&self, font_data: &EmbeddedFontData) -> Result<Dictionary> {
469 let mut font_dict = Dictionary::new();
470
471 font_dict.set("Type", Object::Name("Font".to_string()));
473 font_dict.set("Subtype", Object::Name("TrueType".to_string()));
474 font_dict.set("BaseFont", Object::Name(font_data.pdf_name.clone()));
475
476 font_dict.set("FontDescriptor", Object::Reference(ObjectId::new(0, 0))); match &font_data.encoding {
481 FontEncoding::WinAnsiEncoding => {
482 font_dict.set("Encoding", Object::Name("WinAnsiEncoding".to_string()));
483 }
484 FontEncoding::MacRomanEncoding => {
485 font_dict.set("Encoding", Object::Name("MacRomanEncoding".to_string()));
486 }
487 FontEncoding::StandardEncoding => {
488 font_dict.set("Encoding", Object::Name("StandardEncoding".to_string()));
489 }
490 FontEncoding::Custom(differences) => {
491 let mut encoding_dict = Dictionary::new();
492 encoding_dict.set("Type", Object::Name("Encoding".to_string()));
493 encoding_dict.set("BaseEncoding", Object::Name("WinAnsiEncoding".to_string()));
494
495 let mut diff_array = Vec::new();
497 for diff in differences {
498 diff_array.push(Object::Integer(diff.code as i64));
499 for name in &diff.names {
500 diff_array.push(Object::Name(name.clone()));
501 }
502 }
503 encoding_dict.set("Differences", Object::Array(diff_array));
504 font_dict.set("Encoding", Object::Dictionary(encoding_dict));
505 }
506 _ => {}
507 }
508
509 font_dict.set("FirstChar", Object::Integer(32));
511 font_dict.set("LastChar", Object::Integer(255));
512
513 let widths: Vec<Object> = (32..=255)
515 .map(|_| Object::Integer(500)) .collect();
517 font_dict.set("Widths", Object::Array(widths));
518
519 Ok(font_dict)
520 }
521
522 fn generate_type0_dictionary(&self, font_data: &EmbeddedFontData) -> Result<Dictionary> {
524 let mut font_dict = Dictionary::new();
525
526 font_dict.set("Type", Object::Name("Font".to_string()));
528 font_dict.set("Subtype", Object::Name("Type0".to_string()));
529 font_dict.set("BaseFont", Object::Name(font_data.pdf_name.clone()));
530
531 font_dict.set("Encoding", Object::Name("Identity-H".to_string()));
533
534 font_dict.set(
536 "DescendantFonts",
537 Object::Array(vec![
538 Object::Reference(ObjectId::new(0, 0)), ]),
540 );
541
542 if !font_data.unicode_mappings.is_empty() {
544 font_dict.set("ToUnicode", Object::Reference(ObjectId::new(0, 0))); }
546
547 Ok(font_dict)
548 }
549
550 pub fn generate_font_descriptor(&self, font_name: &str) -> Result<Dictionary> {
552 let font_data = self
553 .embedded_fonts
554 .get(font_name)
555 .ok_or_else(|| PdfError::FontError(format!("Font {font_name} not found")))?;
556
557 let mut desc_dict = Dictionary::new();
558
559 desc_dict.set("Type", Object::Name("FontDescriptor".to_string()));
560 desc_dict.set("FontName", Object::Name(font_data.pdf_name.clone()));
561
562 desc_dict.set(
564 "Flags",
565 Object::Integer(font_data.descriptor.flags.to_flags() as i64),
566 );
567
568 desc_dict.set("Ascent", Object::Integer(font_data.metrics.ascent as i64));
570 desc_dict.set("Descent", Object::Integer(font_data.metrics.descent as i64));
571 desc_dict.set(
572 "CapHeight",
573 Object::Integer(font_data.metrics.cap_height as i64),
574 );
575 desc_dict.set(
576 "ItalicAngle",
577 Object::Real(font_data.descriptor.italic_angle),
578 );
579 desc_dict.set("StemV", Object::Integer(font_data.descriptor.stem_v as i64));
580
581 let bbox = vec![
583 Object::Integer(font_data.descriptor.bbox[0] as i64),
584 Object::Integer(font_data.descriptor.bbox[1] as i64),
585 Object::Integer(font_data.descriptor.bbox[2] as i64),
586 Object::Integer(font_data.descriptor.bbox[3] as i64),
587 ];
588 desc_dict.set("FontBBox", Object::Array(bbox));
589
590 match font_data.font_type {
592 FontType::TrueType => {
593 desc_dict.set("FontFile2", Object::Reference(ObjectId::new(0, 0)));
594 }
596 FontType::Type0 => {
597 desc_dict.set("FontFile2", Object::Reference(ObjectId::new(0, 0)));
598 }
600 }
601
602 Ok(desc_dict)
603 }
604
605 pub fn generate_tounicode_cmap(&self, font_name: &str) -> Result<String> {
607 let font_data = self
608 .embedded_fonts
609 .get(font_name)
610 .ok_or_else(|| PdfError::FontError(format!("Font {font_name} not found")))?;
611
612 if font_data.unicode_mappings.is_empty() {
613 return Err(PdfError::FontError(
614 "No Unicode mappings available".to_string(),
615 ));
616 }
617
618 let mut cmap_content = String::new();
619
620 cmap_content.push_str("/CIDInit /ProcSet findresource begin\n");
622 cmap_content.push_str("12 dict begin\n");
623 cmap_content.push_str("begincmap\n");
624 cmap_content.push_str("/CIDSystemInfo\n");
625 cmap_content.push_str("<<\n");
626 cmap_content.push_str("/Registry (Adobe)\n");
627 cmap_content.push_str("/Ordering (UCS)\n");
628 cmap_content.push_str("/Supplement 0\n");
629 cmap_content.push_str(">> def\n");
630 cmap_content.push_str("/CMapName /Adobe-Identity-UCS def\n");
631 cmap_content.push_str("/CMapType 2 def\n");
632 cmap_content.push_str("1 begincodespacerange\n");
633 cmap_content.push_str("<0000> <FFFF>\n");
634 cmap_content.push_str("endcodespacerange\n");
635
636 cmap_content.push_str(&format!(
638 "{} beginbfchar\n",
639 font_data.unicode_mappings.len()
640 ));
641 for (glyph_id, unicode_string) in &font_data.unicode_mappings {
642 cmap_content.push_str(&format!(
643 "<{:04X}> <{}>\n",
644 glyph_id,
645 unicode_string
646 .chars()
647 .map(|c| format!("{c:04X}", c = c as u32))
648 .collect::<String>()
649 ));
650 }
651 cmap_content.push_str("endbfchar\n");
652
653 cmap_content.push_str("endcmap\n");
655 cmap_content.push_str("CMapName currentdict /CMap defineresource pop\n");
656 cmap_content.push_str("end\n");
657 cmap_content.push_str("end\n");
658
659 Ok(cmap_content)
660 }
661
662 pub fn embedded_fonts(&self) -> &HashMap<String, EmbeddedFontData> {
664 &self.embedded_fonts
665 }
666
667 fn extract_font_metrics(&self, _font: &TrueTypeFont) -> Result<FontMetrics> {
669 Ok(FontMetrics {
672 ascent: 750,
673 descent: -250,
674 cap_height: 700,
675 x_height: 500,
676 stem_v: 100,
677 stem_h: 50,
678 avg_width: 500,
679 max_width: 1000,
680 missing_width: 500,
681 })
682 }
683
684 fn create_font_descriptor(
686 &self,
687 _font: &TrueTypeFont,
688 font_name: &str,
689 ) -> Result<FontDescriptor> {
690 Ok(FontDescriptor {
691 font_name: font_name.to_string(),
692 flags: FontFlags {
693 non_symbolic: true,
694 ..Default::default()
695 },
696 bbox: [-100, -250, 1000, 750], italic_angle: 0.0,
698 ascent: 750,
699 descent: -250,
700 cap_height: 700,
701 stem_v: 100,
702 stem_h: 50,
703 avg_width: 500,
704 max_width: 1000,
705 missing_width: 500,
706 font_file: None,
707 })
708 }
709
710 fn create_cid_font_descriptor(
712 &self,
713 font: &TrueTypeFont,
714 font_name: &str,
715 ) -> Result<FontDescriptor> {
716 self.create_font_descriptor(font, font_name)
718 }
719
720 fn create_encoding_for_font(
722 &self,
723 _font: &TrueTypeFont,
724 _used_glyphs: &HashSet<u16>,
725 ) -> Result<FontEncoding> {
726 Ok(FontEncoding::WinAnsiEncoding)
729 }
730
731 fn create_unicode_mappings(
733 &self,
734 _font: &TrueTypeFont,
735 used_glyphs: &HashSet<u16>,
736 ) -> Result<HashMap<u16, String>> {
737 let mut mappings = HashMap::new();
738
739 for glyph_id in used_glyphs {
741 if *glyph_id < 256 {
742 let unicode_char = char::from(*glyph_id as u8);
743 if unicode_char.is_ascii_graphic() || unicode_char == ' ' {
744 mappings.insert(*glyph_id, unicode_char.to_string());
745 }
746 }
747 }
748
749 Ok(mappings)
750 }
751
752 fn create_cid_unicode_mappings(
754 &self,
755 _font: &TrueTypeFont,
756 used_chars: &HashSet<u32>,
757 ) -> Result<HashMap<u16, String>> {
758 let mut mappings = HashMap::new();
759
760 for &char_code in used_chars {
762 if let Some(unicode_char) = char::from_u32(char_code) {
763 let glyph_id = char_code as u16; mappings.insert(glyph_id, unicode_char.to_string());
766 }
767 }
768
769 Ok(mappings)
770 }
771
772 fn chars_to_glyphs(&self, _font: &TrueTypeFont, chars: &HashSet<u32>) -> Result<HashSet<u16>> {
774 let mut glyphs = HashSet::new();
775
776 glyphs.insert(0);
778
779 for &char_code in chars {
781 let glyph_id = if char_code < 65536 {
783 char_code as u16
784 } else {
785 0 };
787 glyphs.insert(glyph_id);
788 }
789
790 Ok(glyphs)
791 }
792}
793
794impl Default for FontEmbedder {
795 fn default() -> Self {
796 Self::new()
797 }
798}
799
800#[cfg(test)]
801mod tests {
802 use super::*;
803
804 #[test]
805 fn test_font_embedder_creation() {
806 let embedder = FontEmbedder::new();
807 assert_eq!(embedder.embedded_fonts.len(), 0);
808 assert_eq!(embedder.next_font_id, 1);
809 }
810
811 #[test]
812 fn test_embedding_options_default() {
813 let options = EmbeddingOptions::default();
814 assert!(options.subset);
815 assert_eq!(options.max_subset_size, Some(256));
816 assert!(options.compress_font_streams);
817 assert!(!options.embed_license_info);
818 }
819
820 #[test]
821 fn test_generate_tounicode_cmap_empty() {
822 let mut embedder = FontEmbedder::new();
823
824 let font_data = EmbeddedFontData {
826 pdf_name: "TestFont".to_string(),
827 font_type: FontType::TrueType,
828 descriptor: FontDescriptor {
829 font_name: "TestFont".to_string(),
830 flags: FontFlags::default(),
831 bbox: [0, 0, 1000, 1000],
832 italic_angle: 0.0,
833 ascent: 750,
834 descent: -250,
835 cap_height: 700,
836 stem_v: 100,
837 stem_h: 50,
838 avg_width: 500,
839 max_width: 1000,
840 missing_width: 500,
841 font_file: None,
842 },
843 font_program: vec![],
844 encoding: FontEncoding::WinAnsiEncoding,
845 metrics: FontMetrics {
846 ascent: 750,
847 descent: -250,
848 cap_height: 700,
849 x_height: 500,
850 stem_v: 100,
851 stem_h: 50,
852 avg_width: 500,
853 max_width: 1000,
854 missing_width: 500,
855 },
856 subset_glyphs: None,
857 unicode_mappings: HashMap::new(),
858 };
859
860 embedder
861 .embedded_fonts
862 .insert("TestFont".to_string(), font_data);
863
864 let result = embedder.generate_tounicode_cmap("TestFont");
865 assert!(result.is_err());
866 }
867
868 #[test]
869 fn test_generate_truetype_dictionary() {
870 let embedder = FontEmbedder::new();
871
872 let font_data = EmbeddedFontData {
873 pdf_name: "TestFont".to_string(),
874 font_type: FontType::TrueType,
875 descriptor: FontDescriptor {
876 font_name: "TestFont".to_string(),
877 flags: FontFlags::default(),
878 bbox: [0, 0, 1000, 1000],
879 italic_angle: 0.0,
880 ascent: 750,
881 descent: -250,
882 cap_height: 700,
883 stem_v: 100,
884 stem_h: 50,
885 avg_width: 500,
886 max_width: 1000,
887 missing_width: 500,
888 font_file: None,
889 },
890 font_program: vec![],
891 encoding: FontEncoding::WinAnsiEncoding,
892 metrics: FontMetrics {
893 ascent: 750,
894 descent: -250,
895 cap_height: 700,
896 x_height: 500,
897 stem_v: 100,
898 stem_h: 50,
899 avg_width: 500,
900 max_width: 1000,
901 missing_width: 500,
902 },
903 subset_glyphs: None,
904 unicode_mappings: HashMap::new(),
905 };
906
907 let dict = embedder.generate_truetype_dictionary(&font_data).unwrap();
908
909 if let Some(Object::Name(font_type)) = dict.get("Type") {
911 assert_eq!(font_type, "Font");
912 }
913 if let Some(Object::Name(subtype)) = dict.get("Subtype") {
914 assert_eq!(subtype, "TrueType");
915 }
916 if let Some(Object::Name(base_font)) = dict.get("BaseFont") {
917 assert_eq!(base_font, "TestFont");
918 }
919 }
920
921 #[test]
922 fn test_generate_type0_dictionary() {
923 let embedder = FontEmbedder::new();
924
925 let font_data = EmbeddedFontData {
926 pdf_name: "TestCIDFont".to_string(),
927 font_type: FontType::Type0,
928 descriptor: FontDescriptor {
929 font_name: "TestCIDFont".to_string(),
930 flags: FontFlags::default(),
931 bbox: [0, 0, 1000, 1000],
932 italic_angle: 0.0,
933 ascent: 750,
934 descent: -250,
935 cap_height: 700,
936 stem_v: 100,
937 stem_h: 50,
938 avg_width: 500,
939 max_width: 1000,
940 missing_width: 500,
941 font_file: None,
942 },
943 font_program: vec![],
944 encoding: FontEncoding::Identity,
945 metrics: FontMetrics {
946 ascent: 750,
947 descent: -250,
948 cap_height: 700,
949 x_height: 500,
950 stem_v: 100,
951 stem_h: 50,
952 avg_width: 500,
953 max_width: 1000,
954 missing_width: 500,
955 },
956 subset_glyphs: None,
957 unicode_mappings: HashMap::new(),
958 };
959
960 let dict = embedder.generate_type0_dictionary(&font_data).unwrap();
961
962 if let Some(Object::Name(subtype)) = dict.get("Subtype") {
964 assert_eq!(subtype, "Type0");
965 }
966 if let Some(Object::Name(encoding)) = dict.get("Encoding") {
967 assert_eq!(encoding, "Identity-H");
968 }
969 if let Some(Object::Array(descendant_fonts)) = dict.get("DescendantFonts") {
970 assert_eq!(descendant_fonts.len(), 1);
971 }
972 }
973
974 #[test]
975 fn test_chars_to_glyphs_conversion() {
976 let _embedder = FontEmbedder::new();
977 let _font_data = vec![0; 100]; let chars: HashSet<u32> = [65, 66, 67].iter().cloned().collect(); assert!(chars.len() == 3);
986 }
987
988 #[test]
989 fn test_unicode_mappings_creation() {
990 let _embedder = FontEmbedder::new();
991 let glyphs: HashSet<u16> = [65, 66, 67].iter().cloned().collect();
992
993 let _font_data = vec![0; 100];
995
996 assert!(glyphs.len() == 3);
999 }
1000
1001 #[test]
1002 fn test_font_descriptor_generation() {
1003 let _embedder = FontEmbedder::new();
1004
1005 let font_data = EmbeddedFontData {
1006 pdf_name: "TestFont".to_string(),
1007 font_type: FontType::TrueType,
1008 descriptor: FontDescriptor {
1009 font_name: "TestFont".to_string(),
1010 flags: FontFlags {
1011 non_symbolic: true,
1012 serif: true,
1013 ..Default::default()
1014 },
1015 bbox: [-100, -250, 1000, 750],
1016 italic_angle: 0.0,
1017 ascent: 750,
1018 descent: -250,
1019 cap_height: 700,
1020 stem_v: 100,
1021 stem_h: 50,
1022 avg_width: 500,
1023 max_width: 1000,
1024 missing_width: 500,
1025 font_file: None,
1026 },
1027 font_program: vec![],
1028 encoding: FontEncoding::WinAnsiEncoding,
1029 metrics: FontMetrics {
1030 ascent: 750,
1031 descent: -250,
1032 cap_height: 700,
1033 x_height: 500,
1034 stem_v: 100,
1035 stem_h: 50,
1036 avg_width: 500,
1037 max_width: 1000,
1038 missing_width: 500,
1039 },
1040 subset_glyphs: None,
1041 unicode_mappings: HashMap::new(),
1042 };
1043
1044 let mut embedder_with_font = FontEmbedder::new();
1045 embedder_with_font
1046 .embedded_fonts
1047 .insert("TestFont".to_string(), font_data);
1048
1049 let desc_dict = embedder_with_font
1050 .generate_font_descriptor("TestFont")
1051 .unwrap();
1052
1053 if let Some(Object::Name(font_name)) = desc_dict.get("FontName") {
1055 assert_eq!(font_name, "TestFont");
1056 }
1057 if let Some(Object::Integer(flags)) = desc_dict.get("Flags") {
1058 assert!(*flags > 0); }
1060 if let Some(Object::Array(bbox)) = desc_dict.get("FontBBox") {
1061 assert_eq!(bbox.len(), 4);
1062 }
1063 }
1064
1065 #[test]
1070 fn test_cff_font_uses_cidfonttype0() {
1071 assert!(
1073 !CjkFontType::should_use_cidfonttype2(true),
1074 "CFF → CIDFontType0"
1075 );
1076 }
1077
1078 #[test]
1079 fn test_truetype_font_uses_cidfonttype2() {
1080 assert!(
1082 CjkFontType::should_use_cidfonttype2(false),
1083 "TrueType → CIDFontType2"
1084 );
1085 }
1086}