1use super::Font;
4use crate::objects::{Dictionary, Object, ObjectId};
5use crate::text::fonts::embedding::CjkFontType;
6use crate::Result;
7
8#[derive(Debug, Clone)]
10pub struct EmbeddingOptions {
11 pub subset: bool,
13 pub compress: bool,
15 pub encoding: FontEncoding,
17}
18
19impl Default for EmbeddingOptions {
20 fn default() -> Self {
21 EmbeddingOptions {
22 subset: true,
23 compress: true,
24 encoding: FontEncoding::WinAnsiEncoding,
25 }
26 }
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum FontEncoding {
32 WinAnsiEncoding,
34 MacRomanEncoding,
36 StandardEncoding,
38 IdentityH,
40}
41
42impl FontEncoding {
43 pub fn name(&self) -> &'static str {
45 match self {
46 FontEncoding::WinAnsiEncoding => "WinAnsiEncoding",
47 FontEncoding::MacRomanEncoding => "MacRomanEncoding",
48 FontEncoding::StandardEncoding => "StandardEncoding",
49 FontEncoding::IdentityH => "Identity-H",
50 }
51 }
52}
53
54pub struct FontEmbedder<'a> {
56 font: &'a Font,
57 options: EmbeddingOptions,
58 used_chars: Vec<char>,
59}
60
61impl<'a> FontEmbedder<'a> {
62 pub fn new(font: &'a Font, options: EmbeddingOptions) -> Self {
64 FontEmbedder {
65 font,
66 options,
67 used_chars: Vec::new(),
68 }
69 }
70
71 pub fn add_used_chars(&mut self, text: &str) {
73 for ch in text.chars() {
74 if !self.used_chars.contains(&ch) {
75 self.used_chars.push(ch);
76 }
77 }
78 }
79
80 pub fn create_font_dict(
82 &self,
83 descriptor_id: ObjectId,
84 to_unicode_id: Option<ObjectId>,
85 ) -> Dictionary {
86 let mut dict = Dictionary::new();
87
88 dict.set("Type", Object::Name("Font".into()));
90
91 if self.options.encoding == FontEncoding::IdentityH {
93 self.create_type0_font_dict(&mut dict, descriptor_id, to_unicode_id);
95 } else {
96 self.create_simple_font_dict(&mut dict, descriptor_id, to_unicode_id);
98 }
99
100 dict
101 }
102
103 fn create_type0_font_dict(
105 &self,
106 dict: &mut Dictionary,
107 descriptor_id: ObjectId,
108 to_unicode_id: Option<ObjectId>,
109 ) {
110 dict.set("Subtype", Object::Name("Type0".into()));
111 dict.set("BaseFont", Object::Name(self.font.postscript_name().into()));
112 dict.set(
113 "Encoding",
114 Object::Name(self.options.encoding.name().into()),
115 );
116
117 let cid_font_dict = self.create_cid_font_dict(descriptor_id);
119 dict.set(
120 "DescendantFonts",
121 Object::Array(vec![Object::Dictionary(cid_font_dict)]),
122 );
123
124 if let Some(to_unicode) = to_unicode_id {
125 dict.set("ToUnicode", Object::Reference(to_unicode));
126 }
127 }
128
129 fn create_cid_font_dict(&self, descriptor_id: ObjectId) -> Dictionary {
131 let mut dict = Dictionary::new();
132
133 dict.set("Type", Object::Name("Font".into()));
134
135 let font_name = self.font.postscript_name();
136 let cid_font_subtype = match self.font.format {
138 super::FontFormat::OpenType => "CIDFontType0",
139 super::FontFormat::TrueType => "CIDFontType2",
140 };
141
142 dict.set("Subtype", Object::Name(cid_font_subtype.into()));
143 dict.set("BaseFont", Object::Name(font_name.into()));
144
145 let mut cid_system_info = Dictionary::new();
147 let font_name = self.font.postscript_name();
148 let (registry, ordering, supplement) =
149 if let Some(cjk_type) = CjkFontType::detect_from_name(font_name) {
150 cjk_type.cid_system_info()
151 } else {
152 ("Adobe", "Identity", 0)
153 };
154
155 cid_system_info.set("Registry", Object::String(registry.into()));
156 cid_system_info.set("Ordering", Object::String(ordering.into()));
157 cid_system_info.set("Supplement", Object::Integer(supplement as i64));
158 dict.set("CIDSystemInfo", Object::Dictionary(cid_system_info));
159
160 dict.set("FontDescriptor", Object::Reference(descriptor_id));
161
162 dict.set("DW", Object::Integer(1000));
164
165 let widths_array = self.create_cid_widths_array();
167 dict.set("W", Object::Array(widths_array));
168
169 dict
170 }
171
172 fn create_simple_font_dict(
174 &self,
175 dict: &mut Dictionary,
176 descriptor_id: ObjectId,
177 to_unicode_id: Option<ObjectId>,
178 ) {
179 dict.set("Subtype", Object::Name("TrueType".into()));
180 dict.set("BaseFont", Object::Name(self.font.postscript_name().into()));
181 dict.set(
182 "Encoding",
183 Object::Name(self.options.encoding.name().into()),
184 );
185
186 dict.set("FontDescriptor", Object::Reference(descriptor_id));
187
188 let (first_char, last_char) = self.get_char_range();
190 dict.set("FirstChar", Object::Integer(first_char as i64));
191 dict.set("LastChar", Object::Integer(last_char as i64));
192
193 let widths = self.create_widths_array(first_char, last_char);
195 dict.set("Widths", Object::Array(widths));
196
197 if let Some(to_unicode) = to_unicode_id {
198 dict.set("ToUnicode", Object::Reference(to_unicode));
199 }
200 }
201
202 fn get_char_range(&self) -> (u8, u8) {
204 if self.used_chars.is_empty() {
205 return (32, 126); }
207
208 let mut min = 255;
209 let mut max = 0;
210
211 for &ch in &self.used_chars {
212 if ch as u32 <= 255 {
213 let byte = ch as u8;
214 if byte < min {
215 min = byte;
216 }
217 if byte > max {
218 max = byte;
219 }
220 }
221 }
222
223 (min, max)
224 }
225
226 fn create_widths_array(&self, first_char: u8, last_char: u8) -> Vec<Object> {
228 let mut widths = Vec::new();
229
230 for ch in first_char..=last_char {
231 if let Some(width) = self.font.glyph_mapping.get_char_width(char::from(ch)) {
232 let pdf_width = (width as f64 * 1000.0) / self.font.metrics.units_per_em as f64;
234 widths.push(Object::Integer(pdf_width as i64));
235 } else {
236 widths.push(Object::Integer(600));
238 }
239 }
240
241 widths
242 }
243
244 fn create_cid_widths_array(&self) -> Vec<Object> {
246 let mut width_array = Vec::new();
247
248 let mut char_widths = std::collections::HashMap::new();
250
251 for &ch in &self.used_chars {
253 if let Some(width) = self.font.glyph_mapping.get_char_width(ch) {
254 let pdf_width = (width as f64 * 1000.0) / self.font.metrics.units_per_em as f64;
256 char_widths.insert(ch as u32, pdf_width as i64);
257 }
258 }
259
260 let mut sorted_chars: Vec<_> = char_widths.iter().collect();
262 sorted_chars.sort_by_key(|(code, _)| *code);
263
264 let mut current_range_start = None;
265 let mut current_range_end = None; let mut current_width = None;
267
268 for (&code, &width) in sorted_chars {
269 match (current_range_start, current_range_end) {
270 (None, _) => {
271 current_range_start = Some(code);
273 current_range_end = Some(code);
274 current_width = Some(width);
275 }
276 (Some(_start), Some(end)) => {
277 if current_width == Some(width) && code == end + 1 {
280 current_range_end = Some(code);
282 } else {
283 if let (Some(start_code), Some(w)) = (current_range_start, current_width) {
285 width_array.push(Object::Integer(start_code as i64));
286 width_array.push(Object::Array(vec![Object::Integer(w)]));
287 }
288
289 current_range_start = Some(code);
291 current_range_end = Some(code);
292 current_width = Some(width);
293 }
294 }
295 (Some(_), None) => {
296 unreachable!("Range start without range end (this is a bug)")
298 }
299 }
300 }
301
302 if let (Some(start_code), Some(w)) = (current_range_start, current_width) {
304 width_array.push(Object::Integer(start_code as i64));
305 width_array.push(Object::Array(vec![Object::Integer(w)]));
306 }
307
308 width_array
309 }
310
311 pub fn create_to_unicode_cmap(&self) -> Vec<u8> {
313 let mut cmap = String::new();
314
315 cmap.push_str("/CIDInit /ProcSet findresource begin\n");
317 cmap.push_str("12 dict begin\n");
318 cmap.push_str("begincmap\n");
319 cmap.push_str("/CIDSystemInfo\n");
320 cmap.push_str("<< /Registry (Adobe)\n");
321 cmap.push_str(" /Ordering (UCS)\n");
322 cmap.push_str(" /Supplement 0\n");
323 cmap.push_str(">> def\n");
324 cmap.push_str("/CMapName /Adobe-Identity-UCS def\n");
325 cmap.push_str("/CMapType 2 def\n");
326 cmap.push_str("1 begincodespacerange\n");
327 cmap.push_str("<0000> <FFFF>\n");
328 cmap.push_str("endcodespacerange\n");
329
330 let mut mappings = Vec::new();
332 for &ch in &self.used_chars {
333 if let Some(glyph) = self.font.glyph_mapping.char_to_glyph(ch) {
334 mappings.push((glyph, ch));
335 }
336 }
337
338 if !mappings.is_empty() {
339 cmap.push_str(&format!("{} beginbfchar\n", mappings.len()));
340 for (glyph, ch) in mappings {
341 cmap.push_str(&format!("<{:04X}> <{:04X}>\n", glyph, ch as u32));
342 }
343 cmap.push_str("endbfchar\n");
344 }
345
346 cmap.push_str("endcmap\n");
348 cmap.push_str("CMapName currentdict /CMap defineresource pop\n");
349 cmap.push_str("end\n");
350 cmap.push_str("end\n");
351
352 cmap.into_bytes()
353 }
354
355 pub fn get_font_data(&self) -> Result<Vec<u8>> {
357 if self.options.subset {
358 if !self.used_chars.is_empty() {
368 Ok(self.font.data.clone())
370 } else {
371 Ok(self.font.data.clone())
373 }
374 } else {
375 Ok(self.font.data.clone())
376 }
377 }
378}
379
380#[cfg(test)]
381mod tests {
382 use super::*;
383 use crate::fonts::{Font, FontDescriptor, FontFormat, FontMetrics, GlyphMapping};
384
385 fn create_test_font() -> Font {
386 let mut glyph_mapping = GlyphMapping::default();
387 for ch in 32..127 {
388 glyph_mapping.add_mapping(char::from(ch), ch as u16);
389 glyph_mapping.set_glyph_width(ch as u16, 600);
390 }
391
392 Font {
393 name: "TestFont".to_string(),
394 data: vec![0; 1000],
395 format: FontFormat::TrueType,
396 metrics: FontMetrics {
397 units_per_em: 1000,
398 ascent: 800,
399 descent: -200,
400 line_gap: 200,
401 cap_height: 700,
402 x_height: 500,
403 },
404 descriptor: FontDescriptor::new("TestFont"),
405 glyph_mapping,
406 }
407 }
408
409 #[test]
410 fn test_font_embedder_creation() {
411 let font = create_test_font();
412 let options = EmbeddingOptions::default();
413 let embedder = FontEmbedder::new(&font, options);
414
415 assert_eq!(embedder.used_chars.len(), 0);
416 }
417
418 #[test]
419 fn test_add_used_chars() {
420 let font = create_test_font();
421 let options = EmbeddingOptions::default();
422 let mut embedder = FontEmbedder::new(&font, options);
423
424 embedder.add_used_chars("Hello");
425 assert_eq!(embedder.used_chars.len(), 4); embedder.add_used_chars("World");
428 assert_eq!(embedder.used_chars.len(), 7); }
430
431 #[test]
432 fn test_char_range() {
433 let font = create_test_font();
434 let options = EmbeddingOptions::default();
435 let mut embedder = FontEmbedder::new(&font, options);
436
437 embedder.add_used_chars("AZ");
438 let (first, last) = embedder.get_char_range();
439 assert_eq!(first, b'A');
440 assert_eq!(last, b'Z');
441 }
442
443 #[test]
444 fn test_font_encoding_names() {
445 assert_eq!(FontEncoding::WinAnsiEncoding.name(), "WinAnsiEncoding");
446 assert_eq!(FontEncoding::MacRomanEncoding.name(), "MacRomanEncoding");
447 assert_eq!(FontEncoding::StandardEncoding.name(), "StandardEncoding");
448 assert_eq!(FontEncoding::IdentityH.name(), "Identity-H");
449 }
450
451 #[test]
452 fn test_create_simple_font_dict() {
453 let font = create_test_font();
454 let options = EmbeddingOptions {
455 subset: false,
456 compress: false,
457 encoding: FontEncoding::WinAnsiEncoding,
458 };
459 let mut embedder = FontEmbedder::new(&font, options);
460 embedder.add_used_chars("ABC");
461
462 let font_dict = embedder.create_font_dict(ObjectId::new(10, 0), Some(ObjectId::new(11, 0)));
463
464 assert_eq!(font_dict.get("Type").unwrap(), &Object::Name("Font".into()));
465 assert_eq!(
466 font_dict.get("Subtype").unwrap(),
467 &Object::Name("TrueType".into())
468 );
469 assert!(font_dict.get("FirstChar").is_some());
470 assert!(font_dict.get("LastChar").is_some());
471 assert!(font_dict.get("Widths").is_some());
472 }
473
474 #[test]
475 fn test_create_type0_font_dict() {
476 let font = create_test_font();
477 let options = EmbeddingOptions {
478 subset: false,
479 compress: false,
480 encoding: FontEncoding::IdentityH,
481 };
482 let embedder = FontEmbedder::new(&font, options);
483
484 let font_dict = embedder.create_font_dict(ObjectId::new(10, 0), Some(ObjectId::new(11, 0)));
485
486 assert_eq!(font_dict.get("Type").unwrap(), &Object::Name("Font".into()));
487 assert_eq!(
488 font_dict.get("Subtype").unwrap(),
489 &Object::Name("Type0".into())
490 );
491 assert_eq!(
492 font_dict.get("Encoding").unwrap(),
493 &Object::Name("Identity-H".into())
494 );
495 assert!(font_dict.get("DescendantFonts").is_some());
496 }
497
498 #[test]
499 fn test_create_widths_array() {
500 let font = create_test_font();
501 let options = EmbeddingOptions::default();
502 let embedder = FontEmbedder::new(&font, options);
503
504 let widths = embedder.create_widths_array(65, 67); assert_eq!(widths.len(), 3);
506 for width in &widths {
507 if let Object::Integer(w) = width {
508 assert_eq!(*w, 600); } else {
510 panic!("Expected Integer object");
511 }
512 }
513 }
514
515 #[test]
516 fn test_create_to_unicode_cmap() {
517 let font = create_test_font();
518 let options = EmbeddingOptions::default();
519 let mut embedder = FontEmbedder::new(&font, options);
520 embedder.add_used_chars("Hello");
521
522 let cmap = embedder.create_to_unicode_cmap();
523 let cmap_str = String::from_utf8(cmap).unwrap();
524
525 assert!(cmap_str.contains("begincmap"));
526 assert!(cmap_str.contains("endcmap"));
527 assert!(cmap_str.contains("beginbfchar"));
528 assert!(cmap_str.contains("endbfchar"));
529 }
530
531 #[test]
532 fn test_get_font_data() {
533 let font = create_test_font();
534 let options = EmbeddingOptions {
535 subset: false,
536 compress: false,
537 encoding: FontEncoding::WinAnsiEncoding,
538 };
539 let embedder = FontEmbedder::new(&font, options);
540
541 let font_data = embedder.get_font_data().unwrap();
542 assert_eq!(font_data.len(), 1000);
543 }
544
545 #[test]
546 fn test_embedding_options_default() {
547 let options = EmbeddingOptions::default();
548 assert!(options.subset);
549 assert!(options.compress);
550 assert_eq!(options.encoding, FontEncoding::WinAnsiEncoding);
551 }
552
553 #[test]
554 fn test_char_range_empty() {
555 let font = create_test_font();
556 let options = EmbeddingOptions::default();
557 let embedder = FontEmbedder::new(&font, options);
558
559 let (first, last) = embedder.get_char_range();
560 assert_eq!(first, 32); assert_eq!(last, 126);
562 }
563
564 #[test]
565 fn test_char_range_with_unicode() {
566 let font = create_test_font();
567 let options = EmbeddingOptions::default();
568 let mut embedder = FontEmbedder::new(&font, options);
569
570 embedder.add_used_chars("A€B"); let (first, last) = embedder.get_char_range();
573
574 assert_eq!(first, b'A');
576 assert_eq!(last, b'B');
577 }
578
579 #[test]
580 fn test_cid_font_dict_creation() {
581 let font = create_test_font();
582 let options = EmbeddingOptions {
583 subset: false,
584 compress: false,
585 encoding: FontEncoding::IdentityH,
586 };
587 let embedder = FontEmbedder::new(&font, options);
588
589 let cid_dict = embedder.create_cid_font_dict(ObjectId::new(10, 0));
590
591 assert_eq!(cid_dict.get("Type").unwrap(), &Object::Name("Font".into()));
592 assert_eq!(
593 cid_dict.get("Subtype").unwrap(),
594 &Object::Name("CIDFontType2".into())
595 );
596 assert!(cid_dict.get("CIDSystemInfo").is_some());
597 assert_eq!(cid_dict.get("DW").unwrap(), &Object::Integer(1000));
598
599 if let Object::Dictionary(sys_info) = cid_dict.get("CIDSystemInfo").unwrap() {
601 assert_eq!(
602 sys_info.get("Registry").unwrap(),
603 &Object::String("Adobe".into())
604 );
605 assert_eq!(
606 sys_info.get("Ordering").unwrap(),
607 &Object::String("Identity".into())
608 );
609 assert_eq!(sys_info.get("Supplement").unwrap(), &Object::Integer(0));
610 } else {
611 panic!("Expected Dictionary for CIDSystemInfo");
612 }
613 }
614
615 #[test]
616 fn test_font_encoding_equality() {
617 assert_eq!(FontEncoding::WinAnsiEncoding, FontEncoding::WinAnsiEncoding);
618 assert_ne!(
619 FontEncoding::WinAnsiEncoding,
620 FontEncoding::MacRomanEncoding
621 );
622 assert_ne!(FontEncoding::StandardEncoding, FontEncoding::IdentityH);
623 }
624
625 #[test]
626 fn test_add_duplicate_chars() {
627 let font = create_test_font();
628 let options = EmbeddingOptions::default();
629 let mut embedder = FontEmbedder::new(&font, options);
630
631 embedder.add_used_chars("AAA");
632 assert_eq!(embedder.used_chars.len(), 1); embedder.add_used_chars("ABBA");
635 assert_eq!(embedder.used_chars.len(), 2); }
637
638 #[test]
639 fn test_widths_array_missing_glyphs() {
640 let mut font = create_test_font();
641 font.glyph_mapping = GlyphMapping::default();
643
644 let options = EmbeddingOptions::default();
645 let embedder = FontEmbedder::new(&font, options);
646
647 let widths = embedder.create_widths_array(65, 67); assert_eq!(widths.len(), 3);
649
650 for width in &widths {
652 if let Object::Integer(w) = width {
653 assert_eq!(*w, 600);
654 }
655 }
656 }
657}