1use fop_types::{FopError, Result};
6use std::collections::{BTreeSet, HashMap};
7
8#[derive(Debug, Clone)]
10pub struct PdfFont {
11 pub font_name: String,
13
14 pub font_data: Vec<u8>,
16
17 pub flags: u32,
19
20 pub bbox: [i16; 4],
22
23 pub italic_angle: i16,
25
26 pub ascent: i16,
28
29 pub descent: i16,
31
32 pub cap_height: i16,
34
35 pub stem_v: i16,
37
38 pub widths: Vec<u16>,
40
41 pub first_char: u32,
43
44 pub last_char: u32,
46
47 pub units_per_em: u16,
49
50 pub char_to_glyph: std::collections::HashMap<char, u16>,
52}
53
54impl PdfFont {
55 pub fn from_ttf_data(font_data: Vec<u8>) -> Result<Self> {
60 let face = ttf_parser::Face::parse(&font_data, 0)
61 .map_err(|e| FopError::Generic(format!("Failed to parse TTF: {:?}", e)))?;
62
63 let font_name = face
65 .names()
66 .into_iter()
67 .find(|name| name.name_id == ttf_parser::name_id::POST_SCRIPT_NAME)
68 .and_then(|name| name.to_string())
69 .unwrap_or_else(|| "CustomFont".to_string());
70
71 let units_per_em = face.units_per_em();
73 let ascent = face.ascender();
74 let descent = face.descender();
75
76 let bbox = {
78 let bb = face.global_bounding_box();
79 [bb.x_min, bb.y_min, bb.x_max, bb.y_max]
80 };
81
82 let cap_height = face
84 .capital_height()
85 .unwrap_or((ascent as f32 * 0.7) as i16);
86
87 let stem_v = face
89 .weight()
90 .to_number()
91 .clamp(400, 900)
92 .saturating_sub(300)
93 / 5;
94
95 let italic_angle = face.italic_angle() as i16;
97
98 let mut flags = 32; if face.is_monospaced() {
108 flags |= 1;
109 }
110
111 if italic_angle != 0 {
112 flags |= 64; }
114
115 if face.is_bold() {
116 flags |= 0x40000; }
118
119 let char_to_glyph = std::collections::HashMap::new();
122
123 let first_char = 32u32;
125 let last_char = 126u32;
126 let mut widths = Vec::new();
127
128 for char_code in first_char..=last_char {
129 let c = char::from_u32(char_code).unwrap_or('\0');
130 let glyph_id = face.glyph_index(c).unwrap_or(ttf_parser::GlyphId(0));
131
132 let width = face.glyph_hor_advance(glyph_id).unwrap_or(units_per_em / 2);
133
134 widths.push(width);
135 }
136
137 Ok(Self {
138 font_name,
139 font_data,
140 flags,
141 bbox,
142 italic_angle,
143 ascent,
144 descent,
145 cap_height,
146 stem_v: stem_v as i16,
147 widths,
148 first_char,
149 last_char,
150 units_per_em,
151 char_to_glyph,
152 })
153 }
154
155 pub fn char_width(&self, c: char) -> u16 {
157 let char_code = c as u32;
158 if char_code >= self.first_char && char_code <= self.last_char {
159 let index = (char_code - self.first_char) as usize;
160 self.widths
161 .get(index)
162 .copied()
163 .unwrap_or(self.units_per_em / 2)
164 } else {
165 self.units_per_em / 2
167 }
168 }
169
170 pub fn measure_text(&self, text: &str, font_size_pt: f64) -> f64 {
172 let mut total_width = 0u32;
173 for c in text.chars() {
174 total_width += self.char_width(c) as u32;
175 }
176
177 (total_width as f64 / self.units_per_em as f64) * font_size_pt
179 }
180}
181
182pub type FontObjectTuple = (usize, usize, usize, usize, usize, usize, PdfFont);
184
185#[derive(Debug, Clone, Default)]
187pub struct FontSubsetter {
188 used_chars: BTreeSet<char>,
190}
191
192impl FontSubsetter {
193 pub fn new() -> Self {
195 Self {
196 used_chars: BTreeSet::new(),
197 }
198 }
199
200 pub fn record_text(&mut self, text: &str) {
202 for c in text.chars() {
203 self.used_chars.insert(c);
204 }
205 }
206
207 pub fn used_chars(&self) -> &BTreeSet<char> {
209 &self.used_chars
210 }
211
212 pub fn is_empty(&self) -> bool {
214 self.used_chars.is_empty()
215 }
216}
217
218#[derive(Debug, Default)]
220pub struct FontManager {
221 fonts: Vec<PdfFont>,
223
224 subsetters: Vec<FontSubsetter>,
226}
227
228impl FontManager {
229 pub fn new() -> Self {
231 Self {
232 fonts: Vec::new(),
233 subsetters: Vec::new(),
234 }
235 }
236
237 pub fn embed_font(&mut self, font_data: Vec<u8>) -> Result<usize> {
239 let font = PdfFont::from_ttf_data(font_data)?;
240 self.fonts.push(font);
241 self.subsetters.push(FontSubsetter::new());
242 Ok(self.fonts.len() - 1)
243 }
244
245 pub fn record_text(&mut self, font_index: usize, text: &str) {
247 if let Some(subsetter) = self.subsetters.get_mut(font_index) {
248 subsetter.record_text(text);
249 }
250 }
251
252 pub fn get_font(&self, index: usize) -> Option<&PdfFont> {
254 self.fonts.get(index)
255 }
256
257 pub fn fonts(&self) -> &[PdfFont] {
259 &self.fonts
260 }
261
262 pub fn font_count(&self) -> usize {
264 self.fonts.len()
265 }
266
267 pub fn find_by_name(&self, family: &str) -> Option<usize> {
273 let needle = family.to_lowercase();
274 self.fonts.iter().position(|f| {
275 f.font_name.to_lowercase() == needle
276 || f.font_name
279 .to_lowercase()
280 .replace('-', " ")
281 .starts_with(&needle)
282 })
283 }
284
285 pub fn get_subsetter(&self, index: usize) -> Option<&FontSubsetter> {
287 self.subsetters.get(index)
288 }
289
290 pub fn generate_font_objects(&self, start_obj_id: usize) -> Result<Vec<FontObjectTuple>> {
296 let mut result = Vec::new();
297 let mut obj_id = start_obj_id;
298
299 for (font_idx, font) in self.fonts.iter().enumerate() {
300 let descriptor_id = obj_id;
301 let stream_id = obj_id + 1;
302 let cidfont_id = obj_id + 2;
303 let type0_dict_id = obj_id + 3;
304 let to_unicode_id = obj_id + 4;
305 let cidtogidmap_id = obj_id + 5;
306 obj_id += 6; let subset_font = if let Some(subsetter) = self.subsetters.get(font_idx) {
310 if !subsetter.is_empty() {
311 create_subset_font(font, subsetter)?
312 } else {
313 font.clone()
315 }
316 } else {
317 font.clone()
319 };
320
321 result.push((
322 descriptor_id,
323 stream_id,
324 cidfont_id,
325 type0_dict_id,
326 to_unicode_id,
327 cidtogidmap_id,
328 subset_font,
329 ));
330 }
331
332 Ok(result)
333 }
334}
335
336pub fn generate_font_descriptor(font: &PdfFont, font_stream_obj_id: usize) -> String {
338 format!(
339 "<<\n\
340 /Type /FontDescriptor\n\
341 /FontName /{}\n\
342 /Flags {}\n\
343 /FontBBox [{} {} {} {}]\n\
344 /ItalicAngle {}\n\
345 /Ascent {}\n\
346 /Descent {}\n\
347 /CapHeight {}\n\
348 /StemV {}\n\
349 /FontFile2 {} 0 R\n\
350 >>",
351 font.font_name,
352 font.flags,
353 font.bbox[0],
354 font.bbox[1],
355 font.bbox[2],
356 font.bbox[3],
357 font.italic_angle,
358 font.ascent,
359 font.descent,
360 font.cap_height,
361 font.stem_v,
362 font_stream_obj_id
363 )
364}
365
366pub fn generate_font_stream_header(font: &PdfFont) -> String {
368 format!(
369 "<<\n\
370 /Length {}\n\
371 /Length1 {}\n\
372 >>",
373 font.font_data.len(),
374 font.font_data.len()
375 )
376}
377
378pub fn generate_font_dictionary(
380 font: &PdfFont,
381 descriptor_obj_id: usize,
382 to_unicode_obj_id: Option<usize>,
383) -> String {
384 generate_type0_font_dict(font, descriptor_obj_id, to_unicode_obj_id)
386}
387
388fn generate_type0_font_dict(
391 font: &PdfFont,
392 cidfont_obj_id: usize,
393 to_unicode_obj_id: Option<usize>,
394) -> String {
395 let to_unicode_entry = if let Some(obj_id) = to_unicode_obj_id {
396 format!("/ToUnicode {} 0 R\n ", obj_id)
397 } else {
398 String::new()
399 };
400
401 format!(
402 "<<\n\
403 /Type /Font\n\
404 /Subtype /Type0\n\
405 /BaseFont /{}\n\
406 /Encoding /Identity-H\n\
407 /DescendantFonts [{} 0 R]\n\
408 {}\
409 >>",
410 font.font_name, cidfont_obj_id, to_unicode_entry
411 )
412}
413
414pub fn generate_cidfont_dict(
417 font: &PdfFont,
418 descriptor_obj_id: usize,
419 cidtogidmap_obj_id: usize,
420) -> String {
421 let default_width = font.units_per_em / 2;
424
425 let mut w_array = String::new();
427 if !font.widths.is_empty() {
428 w_array.push_str(&format!("{} [", font.first_char));
429 for (i, width) in font.widths.iter().enumerate() {
430 if i > 0 {
431 w_array.push(' ');
432 }
433 w_array.push_str(&width.to_string());
434 }
435 w_array.push(']');
436 }
437
438 format!(
439 "<<\n\
440 /Type /Font\n\
441 /Subtype /CIDFontType2\n\
442 /BaseFont /{}\n\
443 /CIDSystemInfo <<\n\
444 /Registry (Adobe)\n\
445 /Ordering (Identity)\n\
446 /Supplement 0\n\
447 >>\n\
448 /FontDescriptor {} 0 R\n\
449 /DW {}\n\
450 {}\
451 /CIDToGIDMap {} 0 R\n\
452 >>",
453 font.font_name,
454 descriptor_obj_id,
455 default_width,
456 if w_array.is_empty() {
457 String::new()
458 } else {
459 format!("/W [{}]\n ", w_array)
460 },
461 cidtogidmap_obj_id
462 )
463}
464
465pub fn generate_to_unicode_cmap(font: &PdfFont) -> String {
468 let mut cmap = String::from(
469 "/CIDInit /ProcSet findresource begin\n\
470 12 dict begin\n\
471 begincmap\n\
472 /CIDSystemInfo <<\n\
473 /Registry (Adobe)\n\
474 /Ordering (Identity)\n\
475 /Supplement 0\n\
476 >> def\n\
477 /CMapName /Adobe-Identity-UCS def\n\
478 /CMapType 2 def\n\
479 1 begincodespacerange\n\
480 <0000> <FFFF>\n\
481 endcodespacerange\n",
482 );
483
484 if !font.char_to_glyph.is_empty() {
487 let mapping_count = font.char_to_glyph.len();
488 cmap.push_str(&format!("{} beginbfchar\n", mapping_count));
489
490 for (&c, _glyph_id) in font.char_to_glyph.iter() {
491 let char_code = c as u32;
493 cmap.push_str(&format!("<{:04X}> <{:04X}>\n", char_code, char_code));
494 }
495
496 cmap.push_str("endbfchar\n");
497 } else {
498 let range_size = (font.last_char - font.first_char + 1) as usize;
500 if range_size > 0 && range_size <= 256 {
501 cmap.push_str(&format!("{} beginbfchar\n", range_size));
502 for char_code in font.first_char..=font.last_char {
503 cmap.push_str(&format!("<{:04X}> <{:04X}>\n", char_code, char_code));
504 }
505 cmap.push_str("endbfchar\n");
506 }
507 }
508
509 cmap.push_str(
510 "endcmap\n\
511 CMapName currentdict /CMap defineresource pop\n\
512 end\n\
513 end\n",
514 );
515
516 cmap
517}
518
519fn create_subset_font(original_font: &PdfFont, subsetter: &FontSubsetter) -> Result<PdfFont> {
521 let face = ttf_parser::Face::parse(&original_font.font_data, 0)
522 .map_err(|e| FopError::Generic(format!("Failed to parse TTF for subsetting: {:?}", e)))?;
523
524 let used_chars = subsetter.used_chars();
525
526 if used_chars.is_empty() {
528 return Ok(original_font.clone());
529 }
530
531 let mut char_to_glyph = HashMap::new();
533 let mut used_glyphs = BTreeSet::new();
534
535 used_glyphs.insert(ttf_parser::GlyphId(0));
537
538 for &c in used_chars.iter() {
539 if let Some(glyph_id) = face.glyph_index(c) {
540 char_to_glyph.insert(c, glyph_id);
541 used_glyphs.insert(glyph_id);
542 }
543 }
544
545 let first_char = used_chars.iter().next().map(|&c| c as u32).unwrap_or(0);
552 let last_char = used_chars
553 .iter()
554 .next_back()
555 .map(|&c| c as u32)
556 .unwrap_or(0xFFFF);
557
558 let mut char_to_glyph_map = std::collections::HashMap::new();
560 for &c in used_chars.iter() {
561 if let Some(glyph_id) = face.glyph_index(c) {
562 char_to_glyph_map.insert(c, glyph_id.0);
563 }
564 }
565
566 let mut widths = Vec::new();
569
570 let range_size = (last_char - first_char + 1) as usize;
573 if range_size > 0 && range_size <= 65536 {
574 for char_code in first_char..=last_char {
576 if let Some(c) = char::from_u32(char_code) {
577 if used_chars.contains(&c) {
578 let glyph_id = face.glyph_index(c).unwrap_or(ttf_parser::GlyphId(0));
579 let width = face
580 .glyph_hor_advance(glyph_id)
581 .unwrap_or(original_font.units_per_em / 2);
582 widths.push(width);
583 } else {
584 widths.push(original_font.units_per_em / 2);
586 }
587 } else {
588 widths.push(original_font.units_per_em / 2);
589 }
590 }
591 }
592
593 let subset_font_data = create_simple_subset(&original_font.font_data, &used_glyphs)?;
596
597 Ok(PdfFont {
598 font_name: original_font.font_name.clone(),
599 font_data: subset_font_data,
600 flags: original_font.flags,
601 bbox: original_font.bbox,
602 italic_angle: original_font.italic_angle,
603 ascent: original_font.ascent,
604 descent: original_font.descent,
605 cap_height: original_font.cap_height,
606 stem_v: original_font.stem_v,
607 widths,
608 first_char,
609 last_char,
610 units_per_em: original_font.units_per_em,
611 char_to_glyph: char_to_glyph_map,
612 })
613}
614
615fn create_simple_subset(
617 font_data: &[u8],
618 used_glyphs: &BTreeSet<ttf_parser::GlyphId>,
619) -> Result<Vec<u8>> {
620 let face = ttf_parser::Face::parse(font_data, 0)
621 .map_err(|e| FopError::Generic(format!("Failed to parse TTF for subsetting: {:?}", e)))?;
622
623 let total_glyphs = face.number_of_glyphs();
628 let used_glyph_count = used_glyphs.len();
629
630 if used_glyph_count as f32 / total_glyphs as f32 > 0.5 {
632 return Ok(font_data.to_vec());
633 }
634
635 Ok(font_data.to_vec())
644}
645
646#[cfg(test)]
647mod tests {
648 use super::*;
649
650 #[test]
651 fn test_font_manager_creation() {
652 let manager = FontManager::new();
653 assert_eq!(manager.font_count(), 0);
654 }
655
656 #[test]
657 fn test_font_manager_default() {
658 let manager = FontManager::default();
659 assert_eq!(manager.font_count(), 0);
660 }
661
662 #[test]
663 fn test_font_subsetter_creation() {
664 let subsetter = FontSubsetter::new();
665 assert!(subsetter.is_empty());
666 assert_eq!(subsetter.used_chars().len(), 0);
667 }
668
669 #[test]
670 fn test_font_subsetter_record_text() {
671 let mut subsetter = FontSubsetter::new();
672 subsetter.record_text("Hello");
673
674 assert!(!subsetter.is_empty());
675 assert_eq!(subsetter.used_chars().len(), 4); assert!(subsetter.used_chars().contains(&'H'));
678 assert!(subsetter.used_chars().contains(&'e'));
679 assert!(subsetter.used_chars().contains(&'l'));
680 assert!(subsetter.used_chars().contains(&'o'));
681 }
682
683 #[test]
684 fn test_font_subsetter_multiple_texts() {
685 let mut subsetter = FontSubsetter::new();
686 subsetter.record_text("ABC");
687 subsetter.record_text("BCD");
688
689 assert_eq!(subsetter.used_chars().len(), 4); assert!(subsetter.used_chars().contains(&'A'));
691 assert!(subsetter.used_chars().contains(&'B'));
692 assert!(subsetter.used_chars().contains(&'C'));
693 assert!(subsetter.used_chars().contains(&'D'));
694 }
695
696 #[test]
697 fn test_font_manager_record_text() {
698 let mut manager = FontManager::new();
699
700 manager.record_text(0, "test");
706 }
708
709 #[test]
710 fn test_subsetter_unicode_support() {
711 let mut subsetter = FontSubsetter::new();
712 subsetter.record_text("Hello 世界");
713
714 assert!(subsetter.used_chars().contains(&'H'));
715 assert!(subsetter.used_chars().contains(&'世'));
716 assert!(subsetter.used_chars().contains(&'界'));
717 }
718
719 #[test]
720 fn test_subsetter_special_characters() {
721 let mut subsetter = FontSubsetter::new();
722 subsetter.record_text("!@#$%^&*()");
723
724 assert!(subsetter.used_chars().contains(&'!'));
725 assert!(subsetter.used_chars().contains(&'@'));
726 assert!(subsetter.used_chars().contains(&'#'));
727 assert!(subsetter.used_chars().contains(&'('));
728 assert!(subsetter.used_chars().contains(&')'));
729 }
730
731 }
734
735#[cfg(test)]
736mod tests_extended {
737 use super::*;
738
739 fn minimal_pdf_font() -> PdfFont {
740 PdfFont {
741 font_name: "TestFont".to_string(),
742 font_data: vec![0u8; 100],
743 flags: 32, bbox: [-100, -200, 900, 800],
745 italic_angle: 0,
746 ascent: 800,
747 descent: -200,
748 cap_height: 700,
749 stem_v: 80,
750 widths: vec![500; 95], first_char: 32,
752 last_char: 126,
753 units_per_em: 1000,
754 char_to_glyph: HashMap::new(),
755 }
756 }
757
758 #[test]
759 fn test_font_subsetter_empty_initially() {
760 let s = FontSubsetter::new();
761 assert!(s.is_empty());
762 }
763
764 #[test]
765 fn test_font_subsetter_deduplicates() {
766 let mut s = FontSubsetter::new();
767 s.record_text("aaa");
768 assert_eq!(s.used_chars().len(), 1);
770 assert!(s.used_chars().contains(&'a'));
771 }
772
773 #[test]
774 fn test_font_subsetter_is_not_empty_after_text() {
775 let mut s = FontSubsetter::new();
776 s.record_text("X");
777 assert!(!s.is_empty());
778 }
779
780 #[test]
781 fn test_font_manager_default_empty() {
782 let m = FontManager::default();
783 assert_eq!(m.font_count(), 0);
784 assert!(m.get_font(0).is_none());
785 assert!(m.get_subsetter(0).is_none());
786 }
787
788 #[test]
789 fn test_font_manager_find_by_name_empty() {
790 let m = FontManager::new();
791 assert!(m.find_by_name("Arial").is_none());
792 }
793
794 #[test]
795 fn test_generate_font_descriptor_contains_font_name() {
796 let font = minimal_pdf_font();
797 let descriptor = generate_font_descriptor(&font, 42);
798 assert!(descriptor.contains("TestFont"));
799 assert!(descriptor.contains("/FontDescriptor"));
800 }
801
802 #[test]
803 fn test_generate_font_descriptor_references_stream_obj() {
804 let font = minimal_pdf_font();
805 let descriptor = generate_font_descriptor(&font, 99);
806 assert!(descriptor.contains("99"));
807 }
808
809 #[test]
810 fn test_generate_font_stream_header_contains_length() {
811 let font = minimal_pdf_font();
812 let header = generate_font_stream_header(&font);
813 assert!(header.contains("/Length"));
814 assert!(header.contains("100"));
816 }
817
818 #[test]
819 fn test_generate_font_dictionary_type0() {
820 let font = minimal_pdf_font();
821 let dict = generate_font_dictionary(&font, 10, Some(15));
822 assert!(dict.contains("/Type /Font"));
823 assert!(dict.contains("/Subtype /Type0"));
824 assert!(dict.contains("TestFont"));
825 }
826
827 #[test]
828 fn test_generate_font_dictionary_no_to_unicode() {
829 let font = minimal_pdf_font();
830 let dict = generate_font_dictionary(&font, 10, None);
831 assert!(!dict.contains("/ToUnicode"));
833 }
834
835 #[test]
836 fn test_generate_to_unicode_cmap_identity_range() {
837 let mut font = minimal_pdf_font();
838 font.first_char = 65; font.last_char = 67; font.widths = vec![500; 3];
842 let cmap = generate_to_unicode_cmap(&font);
843 assert!(cmap.contains("begincmap"));
844 assert!(cmap.contains("endcmap"));
845 assert!(cmap.contains("<0041> <0041>")); assert!(cmap.contains("<0042> <0042>")); }
848
849 #[test]
850 fn test_generate_to_unicode_cmap_with_char_map() {
851 let mut font = minimal_pdf_font();
852 font.char_to_glyph.insert('A', 100);
853 font.char_to_glyph.insert('Z', 200);
854 let cmap = generate_to_unicode_cmap(&font);
855 assert!(cmap.contains("begincmap"));
856 assert!(cmap.contains("beginbfchar"));
857 assert!(cmap.contains("<0041> <0041>"));
859 }
860
861 #[test]
862 fn test_generate_font_objects_empty_manager() {
863 let manager = FontManager::new();
864 let objects = manager
865 .generate_font_objects(10)
866 .expect("test: should succeed");
867 assert!(objects.is_empty());
868 }
869}