1use std::collections::{BTreeMap, HashMap};
7use std::fmt::Write as _;
8
9use pdfrum_font::{
10 FaceEncoding, FontFlags, GlyphSource, StandardFont, canonical_font_name, em_adjust,
11};
12use pdfrum_object::{Array, ByteSpan, Dict, Name, ObjRef, Object, PdfString, Stream};
13
14use crate::doc::EditDoc;
15use crate::error::Error;
16use crate::font::is_opentype_cff;
17use crate::names;
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
23pub enum FontEncoding {
24 Simple,
26 Composite,
28}
29
30#[derive(Debug, Clone)]
36pub struct EmbeddedFont {
37 font: ObjRef,
38 kind: EncodeKind,
39}
40
41#[derive(Debug, Clone)]
43enum EncodeKind {
44 Identity { unicode_to_gid: HashMap<u32, u16> },
46 Simple { unicode_to_code: HashMap<u32, u8> },
48 WinAnsi,
50 CustomCid { unicode_to_cid: HashMap<char, u32> },
54}
55
56impl EmbeddedFont {
57 #[must_use]
59 pub fn object(&self) -> ObjRef {
60 self.font
61 }
62
63 #[must_use]
83 pub fn encode(&self, text: &str) -> Vec<u8> {
84 let mut out = Vec::with_capacity(text.len());
85 let notdef = self.kind.notdef();
86 for ch in text.chars() {
87 push_code(&mut out, self.code_of(ch).unwrap_or(notdef));
88 }
89 out
90 }
91
92 pub fn encode_checked(&self, text: &str) -> Result<Vec<u8>, MissingGlyph> {
121 let mut out = Vec::with_capacity(text.len());
122 for (offset, ch) in text.char_indices() {
123 let code = self.code_of(ch).ok_or(MissingGlyph {
124 character: ch,
125 offset,
126 })?;
127 push_code(&mut out, code);
128 }
129 Ok(out)
130 }
131
132 fn code_of(&self, ch: char) -> Option<Code> {
135 let missing = |code: u32| (code != 0).then_some(code);
136 match &self.kind {
137 EncodeKind::Identity { unicode_to_gid } => unicode_to_gid
138 .get(&u32::from(ch))
139 .copied()
140 .and_then(|gid| (gid != 0).then_some(gid))
141 .map(Code::Two),
142 EncodeKind::Simple { unicode_to_code } => unicode_to_code
143 .get(&u32::from(ch))
144 .copied()
145 .and_then(|code| (code != 0).then_some(code))
146 .map(Code::One),
147 EncodeKind::CustomCid { unicode_to_cid } => unicode_to_cid
148 .get(&ch)
149 .copied()
150 .and_then(missing)
151 .and_then(|cid| u16::try_from(cid).ok())
152 .map(Code::Two),
153 EncodeKind::WinAnsi => u16::try_from(u32::from(ch))
154 .ok()
155 .map(|u| FaceEncoding::Latin1.charcode_from_unicode(u))
156 .and_then(missing)
157 .and_then(|code| u8::try_from(code).ok())
158 .map(Code::One),
159 }
160 }
161}
162
163#[derive(Debug, Clone, Copy, PartialEq, Eq)]
169enum Code {
170 One(u8),
172 Two(u16),
174}
175
176impl EncodeKind {
177 fn notdef(&self) -> Code {
183 match self {
184 Self::Identity { .. } | Self::CustomCid { .. } => Code::Two(0),
185 Self::Simple { .. } | Self::WinAnsi => Code::One(0),
186 }
187 }
188}
189
190fn push_code(out: &mut Vec<u8>, code: Code) {
192 match code {
193 Code::One(byte) => out.push(byte),
194 Code::Two(pair) => out.extend_from_slice(&pair.to_be_bytes()),
195 }
196}
197
198#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
210#[error("the font has no glyph for {character:?} at byte {offset}")]
211pub struct MissingGlyph {
212 pub character: char,
214 pub offset: usize,
216}
217
218#[derive(Debug, Clone, Copy, PartialEq, Eq)]
220enum ProgramKind {
221 TrueType,
222 OpenTypeCff,
223 Type1,
224}
225
226impl EditDoc<'_> {
227 pub fn embed_font(
238 &mut self,
239 program: &[u8],
240 encoding: FontEncoding,
241 ) -> Result<EmbeddedFont, Error> {
242 let kind = sniff_kind(program).ok_or(Error::UnrecognisedFontProgram)?;
243 let glyphs = GlyphSource::from_bytes(program).ok_or(Error::UnrecognisedFontProgram)?;
244 if glyphs.num_glyphs() == 0 {
245 return Err(Error::EmptyFontProgram);
246 }
247 match encoding {
248 FontEncoding::Simple => embed_simple(self, program, kind, &glyphs),
249 FontEncoding::Composite => embed_composite(self, program, kind, &glyphs),
250 }
251 }
252
253 pub fn embed_cid_font(
286 &mut self,
287 program: &[u8],
288 to_unicode: &str,
289 cid_to_gid: &[u8],
290 ) -> Result<EmbeddedFont, Error> {
291 if to_unicode.is_empty() {
298 return Err(Error::EmptyToUnicodeCMap);
299 }
300 if cid_to_gid.is_empty() || !cid_to_gid.len().is_multiple_of(2) {
301 return Err(Error::BadCidToGidMap(cid_to_gid.len()));
302 }
303 let glyphs = GlyphSource::from_bytes(program).ok_or(Error::UnrecognisedFontProgram)?;
304 if glyphs.num_glyphs() == 0 {
305 return Err(Error::EmptyFontProgram);
306 }
307 Ok(embed_custom_composite(
308 self, program, to_unicode, cid_to_gid, &glyphs,
309 ))
310 }
311
312 pub fn standard_font(&mut self, which: StandardFont) -> Result<EmbeddedFont, Error> {
321 let name = canonical_font_name(which);
322 let dict = Dict::from_pairs([
323 (names::TYPE.clone(), Object::Name(names::FONT.clone())),
324 (names::SUBTYPE.clone(), Object::Name(names::TYPE1.clone())),
325 (
326 names::BASE_FONT.clone(),
327 Object::Name(Name::from(name.as_bytes())),
328 ),
329 (
330 names::ENCODING.clone(),
331 Object::Name(names::WIN_ANSI_ENCODING.clone()),
332 ),
333 ]);
334 Ok(EmbeddedFont {
335 font: self.add(Object::Dict(dict)),
336 kind: EncodeKind::WinAnsi,
337 })
338 }
339}
340
341fn sniff_kind(bytes: &[u8]) -> Option<ProgramKind> {
342 if is_opentype_cff(bytes) {
343 return Some(ProgramKind::OpenTypeCff);
344 }
345 if matches!(
346 bytes.get(..4),
347 Some(&[0x00, 0x01, 0x00, 0x00] | b"true" | b"typ1")
348 ) {
349 return Some(ProgramKind::TrueType);
350 }
351 if bytes.first() == Some(&0x80) {
352 return Some(ProgramKind::Type1);
353 }
354 let start = bytes
355 .iter()
356 .position(|b| !b.is_ascii_whitespace())
357 .unwrap_or(bytes.len());
358 let rest = bytes.get(start..).unwrap_or_default();
359 if rest.starts_with(b"%!PS-AdobeFont") || rest.starts_with(b"%!FontType1") {
360 return Some(ProgramKind::Type1);
361 }
362 let glyphs = GlyphSource::from_bytes(bytes)?;
364 if matches!(glyphs, GlyphSource::Type1(_)) {
365 Some(ProgramKind::Type1)
366 } else if glyphs.is_truetype() {
367 Some(ProgramKind::TrueType)
368 } else {
369 Some(ProgramKind::OpenTypeCff)
370 }
371}
372
373fn embed_simple(
374 doc: &mut EditDoc<'_>,
375 program: &[u8],
376 kind: ProgramKind,
377 glyphs: &GlyphSource,
378) -> Result<EmbeddedFont, Error> {
379 let pairs = char_maps(glyphs, 0xFF);
380 if pairs.is_empty() {
381 return Err(Error::EmptyFontProgram);
382 }
383 let name = font_name(glyphs);
384 let first = pairs.first().map_or(0, |p| p.0);
385 let last = pairs.last().map_or(first, |p| p.0);
386 let by_code: BTreeMap<u32, u16> = pairs.iter().copied().collect();
387 let mut widths = Vec::new();
388 let mut code = first;
389 while code <= last {
390 let w = by_code
391 .get(&code)
392 .map_or(0, |gid| glyphs.default_advance(pdfrum_font::Gid(*gid)));
393 widths.push(Object::Int(i64::from(w)));
394 code = code.saturating_add(1);
395 if code == 0 && last == 0 {
396 break;
397 }
398 }
399 let widths_ref = doc.add(Object::Array(Array::of(widths)));
400 let descriptor = load_font_desc(doc, &name, program, kind, glyphs);
401 let subtype = match kind {
402 ProgramKind::Type1 => names::TYPE1.clone(),
403 ProgramKind::TrueType | ProgramKind::OpenTypeCff => names::TRUE_TYPE.clone(),
404 };
405 let dict = Dict::from_pairs([
406 (names::TYPE.clone(), Object::Name(names::FONT.clone())),
407 (names::SUBTYPE.clone(), Object::Name(subtype)),
408 (
409 names::BASE_FONT.clone(),
410 Object::Name(Name::from(name.as_slice())),
411 ),
412 (names::FIRST_CHAR.clone(), Object::Int(i64::from(first))),
413 (names::LAST_CHAR.clone(), Object::Int(i64::from(last))),
414 (names::WIDTHS.clone(), Object::Ref(widths_ref)),
415 (names::FONT_DESCRIPTOR.clone(), Object::Ref(descriptor)),
416 ]);
417 let unicode_to_code: HashMap<u32, u8> = pairs
418 .into_iter()
419 .filter_map(|(cp, _)| u8::try_from(cp).ok().map(|b| (cp, b)))
420 .collect();
421 Ok(EmbeddedFont {
422 font: doc.add(Object::Dict(dict)),
423 kind: EncodeKind::Simple { unicode_to_code },
424 })
425}
426
427fn embed_composite(
428 doc: &mut EditDoc<'_>,
429 program: &[u8],
430 kind: ProgramKind,
431 glyphs: &GlyphSource,
432) -> Result<EmbeddedFont, Error> {
433 let pairs = char_maps(glyphs, 0x0010_FFFF);
434 if pairs.is_empty() {
435 return Err(Error::EmptyFontProgram);
436 }
437 let name = font_name(glyphs);
438 let base = match kind {
439 ProgramKind::Type1 => {
440 let mut n = name.clone();
441 n.extend_from_slice(b"-Identity-H");
442 n
443 }
444 ProgramKind::TrueType | ProgramKind::OpenTypeCff => name.clone(),
445 };
446 let descriptor = load_font_desc(doc, &name, program, kind, glyphs);
447
448 let mut widths_map: BTreeMap<u32, u32> = BTreeMap::new();
449 let mut to_unicode: BTreeMap<u32, u32> = BTreeMap::new();
450 let mut unicode_to_gid = HashMap::new();
451 for (cp, gid) in &pairs {
452 let w = glyphs.default_advance(pdfrum_font::Gid(*gid));
453 widths_map
454 .entry(u32::from(*gid))
455 .or_insert(u32::try_from(w).unwrap_or(0));
456 to_unicode.entry(u32::from(*gid)).or_insert(*cp);
457 unicode_to_gid.insert(*cp, *gid);
458 }
459 let w_ref = doc.add(Object::Array(create_widths_array(&widths_map)));
460 let tounicode_ref = doc.add(Object::Stream(Box::new(Stream::new(
461 Dict::new(),
462 ByteSpan::from(load_unicode(&to_unicode)),
463 ))));
464
465 let cid_subtype = match kind {
466 ProgramKind::TrueType => names::CID_FONT_TYPE2.clone(),
467 ProgramKind::Type1 | ProgramKind::OpenTypeCff => names::CID_FONT_TYPE0.clone(),
468 };
469 let dict = cid_font_dict(doc, &name, cid_subtype, descriptor, w_ref);
470 let cid_font = doc.add(Object::Dict(dict));
471 Ok(EmbeddedFont {
472 font: doc.add(Object::Dict(type0_font_dict(
473 &base,
474 cid_font,
475 tounicode_ref,
476 ))),
477 kind: EncodeKind::Identity { unicode_to_gid },
478 })
479}
480
481fn embed_custom_composite(
489 doc: &mut EditDoc<'_>,
490 program: &[u8],
491 to_unicode: &str,
492 cid_to_gid: &[u8],
493 glyphs: &GlyphSource,
494) -> EmbeddedFont {
495 let name = font_name(glyphs);
496 let descriptor = load_font_desc(doc, &name, program, ProgramKind::TrueType, glyphs);
500
501 let mut widths: BTreeMap<u32, u32> = BTreeMap::new();
502 for (cid, entry) in cid_to_gid.as_chunks::<2>().0.iter().enumerate() {
503 let gid = u16::from_be_bytes(*entry);
504 let advance = glyphs.default_advance(pdfrum_font::Gid(gid));
505 widths.insert(
506 u32::try_from(cid).unwrap_or(u32::MAX),
507 u32::try_from(advance).unwrap_or(0),
508 );
509 }
510 let w_ref = doc.add(Object::Array(create_widths_array(&widths)));
511
512 let map_ref = doc.add(Object::Stream(Box::new(Stream::new(
513 Dict::new(),
514 ByteSpan::from(cid_to_gid.to_vec()),
515 ))));
516 let tounicode_ref = doc.add(Object::Stream(Box::new(Stream::new(
517 Dict::new(),
518 ByteSpan::from(to_unicode.as_bytes().to_vec()),
519 ))));
520
521 let mut dict = cid_font_dict(doc, &name, names::CID_FONT_TYPE2.clone(), descriptor, w_ref);
522 dict.push(names::CID_TO_GID_MAP.clone(), Object::Ref(map_ref));
523 let cid_font = doc.add(Object::Dict(dict));
524
525 let unicode_to_cid = pdfrum_font::invert_to_unicode(
530 to_unicode.as_bytes(),
531 &pdfrum_common::Limits::default(),
532 &mut pdfrum_common::Diagnostics::default(),
533 );
534 EmbeddedFont {
535 font: doc.add(Object::Dict(type0_font_dict(
536 &name,
537 cid_font,
538 tounicode_ref,
539 ))),
540 kind: EncodeKind::CustomCid { unicode_to_cid },
541 }
542}
543
544fn cid_font_dict(
550 doc: &mut EditDoc<'_>,
551 name: &[u8],
552 subtype: Name,
553 descriptor: ObjRef,
554 widths: ObjRef,
555) -> Dict {
556 let system_info = doc.add(Object::Dict(Dict::from_pairs([
557 (
558 names::REGISTRY.clone(),
559 Object::Str(PdfString::literal(b"Adobe")),
560 ),
561 (
562 names::ORDERING.clone(),
563 Object::Str(PdfString::literal(b"Identity")),
564 ),
565 (names::SUPPLEMENT.clone(), Object::Int(0)),
566 ])));
567 Dict::from_pairs([
568 (names::TYPE.clone(), Object::Name(names::FONT.clone())),
569 (names::SUBTYPE.clone(), Object::Name(subtype)),
570 (names::BASE_FONT.clone(), Object::Name(Name::from(name))),
571 (names::CID_SYSTEM_INFO.clone(), Object::Ref(system_info)),
572 (names::FONT_DESCRIPTOR.clone(), Object::Ref(descriptor)),
573 (names::W.clone(), Object::Ref(widths)),
574 ])
575}
576
577fn type0_font_dict(base: &[u8], cid_font: ObjRef, to_unicode: ObjRef) -> Dict {
579 Dict::from_pairs([
580 (names::TYPE.clone(), Object::Name(names::FONT.clone())),
581 (names::SUBTYPE.clone(), Object::Name(names::TYPE0.clone())),
582 (
583 names::ENCODING.clone(),
584 Object::Name(names::IDENTITY_H.clone()),
585 ),
586 (names::BASE_FONT.clone(), Object::Name(Name::from(base))),
587 (
588 names::DESCENDANT_FONTS.clone(),
589 Object::Array(Array::of([Object::Ref(cid_font)])),
590 ),
591 (names::TO_UNICODE.clone(), Object::Ref(to_unicode)),
592 ])
593}
594
595fn load_font_desc(
611 doc: &mut EditDoc<'_>,
612 font_name: &[u8],
613 program: &[u8],
614 kind: ProgramKind,
615 glyphs: &GlyphSource,
616) -> ObjRef {
617 let mut flags = FontFlags::NON_SYMBOLIC;
618 if glyphs.is_fixed_pitch() {
619 flags = flags.with(FontFlags::FIXED_PITCH);
620 }
621 if font_name.windows(5).any(|w| w == b"Serif") {
622 flags = flags.with(FontFlags::SERIF);
623 }
624 if glyphs.is_italic() {
625 flags = flags.with(FontFlags::ITALIC);
626 }
627 if glyphs.is_bold() {
628 flags = flags.with(FontFlags::FORCE_BOLD);
629 }
630
631 let upem = glyphs.units_per_em();
632 let ascent = glyphs.unscaled_ascent().map_or(0, |v| em_adjust(v, upem));
633 let descent = glyphs.unscaled_descent().map_or(0, |v| em_adjust(v, upem));
634 let (x0, y0, x1, y1) = glyphs.unscaled_bbox().map_or((0, 0, 0, 0), |(l, b, r, t)| {
635 (
636 em_adjust(l, upem),
637 em_adjust(b, upem),
638 em_adjust(r, upem),
639 em_adjust(t, upem),
640 )
641 });
642 let cap_height = glyphs.cap_height_unscaled().map_or(ascent, |v| {
651 #[expect(
652 clippy::cast_possible_truncation,
653 reason = "font units rounded to the integer the PDF descriptor stores"
654 )]
655 let n = v.round() as i32;
656 em_adjust(n, upem)
657 });
658 let italic_angle = if glyphs.is_italic() { -12 } else { 0 };
659 let stem_v = if glyphs.is_bold() { 120 } else { 70 };
660
661 let file = embed_program(doc, program, kind);
662 let mut desc = Dict::from_pairs([
663 (
664 names::TYPE.clone(),
665 Object::Name(Name::from("FontDescriptor")),
666 ),
667 (
668 names::FONT_NAME.clone(),
669 Object::Name(Name::from(font_name)),
670 ),
671 (names::FLAGS.clone(), Object::Int(i64::from(flags.bits()))),
672 (
673 names::FONT_BBOX.clone(),
674 Object::Array(Array::of(
675 [x0, y0, x1, y1].map(|v| Object::Int(i64::from(v))),
676 )),
677 ),
678 (names::ITALIC_ANGLE.clone(), Object::Int(italic_angle)),
679 (names::ASCENT.clone(), Object::Int(i64::from(ascent))),
680 (names::DESCENT.clone(), Object::Int(i64::from(descent))),
681 (
682 names::CAP_HEIGHT.clone(),
683 Object::Int(i64::from(cap_height)),
684 ),
685 (names::STEM_V.clone(), Object::Int(stem_v)),
686 ]);
687 let file_key = match kind {
688 ProgramKind::Type1 => names::FONT_FILE.clone(),
689 ProgramKind::TrueType => names::FONT_FILE2.clone(),
690 ProgramKind::OpenTypeCff => names::FONT_FILE3.clone(),
701 };
702 desc.push(file_key, Object::Ref(file));
703 doc.add(Object::Dict(desc))
704}
705
706fn embed_program(doc: &mut EditDoc<'_>, program: &[u8], kind: ProgramKind) -> ObjRef {
716 let mut dict = Dict::new();
717 let bytes = match kind {
718 ProgramKind::TrueType => {
719 dict.push(
720 names::LENGTH1.clone(),
721 Object::Int(i64::try_from(program.len()).unwrap_or(i64::MAX)),
722 );
723 program.to_vec()
724 }
725 ProgramKind::OpenTypeCff => {
726 dict.push(
727 names::SUBTYPE.clone(),
728 Object::Name(names::OPEN_TYPE.clone()),
729 );
730 program.to_vec()
731 }
732 ProgramKind::Type1 => {
733 let file = pdfrum_font::type1_font_file(program);
748 dict.push(names::LENGTH1.clone(), Object::Int(i64::from(file.length1)));
749 dict.push(names::LENGTH2.clone(), Object::Int(i64::from(file.length2)));
750 dict.push(names::LENGTH3.clone(), Object::Int(i64::from(file.length3)));
751 file.program
752 }
753 };
754 doc.add(Object::Stream(Box::new(Stream::new(
755 dict,
756 ByteSpan::from(bytes),
757 ))))
758}
759
760fn font_name(glyphs: &GlyphSource) -> Vec<u8> {
761 match glyphs.postscript_name().filter(|s| !s.is_empty()) {
762 Some(name) => name.into_bytes(),
763 None => b"Untitled".to_vec(),
764 }
765}
766
767fn char_maps(glyphs: &GlyphSource, max: u32) -> Vec<(u32, u16)> {
768 let mut pairs = glyphs.unicode_mappings(max);
769 if pairs.is_empty() {
770 let n = glyphs.num_glyphs().min(max.saturating_add(1));
771 pairs = (0..n)
772 .filter_map(|i| u16::try_from(i).ok().map(|g| (u32::from(g), g)))
773 .collect();
774 }
775 pairs
776}
777
778fn create_widths_array(widths: &BTreeMap<u32, u32>) -> Array {
781 let mut out = Array::new();
782 let keys: Vec<u32> = widths.keys().copied().collect();
783 let mut i = 0;
784 while i < keys.len() {
785 let Some(&cid) = keys.get(i) else {
786 break;
787 };
788 let width = widths.get(&cid).copied().unwrap_or(0);
789 let mut j = i + 1;
790 let same_run = keys.get(j).copied() == Some(cid.saturating_add(1))
791 && widths.get(&cid.saturating_add(1)).copied() == Some(width);
792 if same_run {
793 let mut last = cid;
794 while let Some(&next) = keys.get(j) {
795 if next != last.saturating_add(1) || widths.get(&next).copied() != Some(width) {
796 break;
797 }
798 last = next;
799 j += 1;
800 }
801 out.push(Object::Int(i64::from(cid)));
802 out.push(Object::Int(i64::from(last)));
803 out.push(Object::Int(i64::from(width)));
804 i = j;
805 continue;
806 }
807 out.push(Object::Int(i64::from(cid)));
808 let mut inner = Array::new();
809 inner.push(Object::Int(i64::from(width)));
810 let mut last = cid;
811 while let Some(&next) = keys.get(j) {
812 if next != last.saturating_add(1) {
813 break;
814 }
815 inner.push(Object::Int(i64::from(
816 widths.get(&next).copied().unwrap_or(0),
817 )));
818 last = next;
819 j += 1;
820 }
821 out.push(Object::Array(inner));
822 i = j;
823 }
824 out
825}
826
827const TO_UNICODE_START: &str = "/CIDInit /ProcSet findresource begin\n\
82812 dict begin\n\
829begincmap\n\
830/CIDSystemInfo\n\
831<</Registry (Adobe)\n\
832/Ordering (Identity)\n\
833/Supplement 0\n\
834>> def\n\
835/CMapName /Adobe-Identity-H def\n\
836/CMapType 2 def\n\
8371 begincodespacerange\n\
838<0000> <FFFF>\n\
839endcodespacerange\n";
840
841const TO_UNICODE_END: &str = "endcmap\n\
842CMapName currentdict /CMap defineresource pop\n\
843end\n\
844end\n";
845
846const MAX_BF_ENTRIES: usize = 100;
847
848fn load_unicode(to_unicode: &BTreeMap<u32, u32>) -> Vec<u8> {
856 let entries: Vec<(u32, u32)> = to_unicode.iter().map(|(&c, &u)| (c, u)).collect();
861 let mut singles: BTreeMap<u32, u32> = BTreeMap::new();
862 let mut range_list: BTreeMap<(u32, u32), Vec<u32>> = BTreeMap::new();
863 let mut range_consec: BTreeMap<(u32, u32), u32> = BTreeMap::new();
864
865 let mut i = 0;
866 while i < entries.len() {
867 let Some(&(first_code, first_uni)) = entries.get(i) else {
868 break;
869 };
870 let next = entries.get(i + 1).copied();
871 if next.is_none_or(|(c, _)| c != first_code.saturating_add(1)) {
872 singles.insert(first_code, first_uni);
873 i += 1;
874 continue;
875 }
876 let Some((current_code, current_uni)) = next else {
877 break;
878 };
879 i += 1;
880 if current_code % 256 == 0 {
881 singles.insert(first_code, first_uni);
882 singles.insert(current_code, current_uni);
883 i += 1;
884 continue;
885 }
886 let max_extra = 255 - (current_code % 256);
887 if first_uni.saturating_add(1) != current_uni {
888 let mut unicodes = vec![first_uni, current_uni];
889 let mut last_code = current_code;
890 let mut extra = 0;
891 while extra < max_extra {
892 let Some(&(ncode, nuni)) = entries.get(i + 1) else {
893 break;
894 };
895 if ncode != last_code.saturating_add(1) {
896 break;
897 }
898 i += 1;
899 last_code = ncode;
900 unicodes.push(nuni);
901 extra += 1;
902 }
903 range_list.insert((first_code, last_code), unicodes);
904 i += 1;
905 continue;
906 }
907 let mut last_code = current_code;
908 let mut last_uni = current_uni;
909 let mut extra = 0;
910 while extra < max_extra {
911 let Some(&(ncode, nuni)) = entries.get(i + 1) else {
912 break;
913 };
914 if ncode != last_code.saturating_add(1) || nuni != last_uni.saturating_add(1) {
915 break;
916 }
917 i += 1;
918 last_code = ncode;
919 last_uni = nuni;
920 extra += 1;
921 }
922 range_consec.insert((first_code, last_code), first_uni);
923 i += 1;
924 }
925
926 let mut buf = String::from(TO_UNICODE_START);
927 write_bfchar(&mut buf, &singles);
928 write_bfrange_list(&mut buf, &range_list);
929 write_bfrange_consec(&mut buf, &range_consec);
930 buf.push_str(TO_UNICODE_END);
931 buf.into_bytes()
932}
933
934fn write_bfchar(buf: &mut String, map: &BTreeMap<u32, u32>) {
935 let items: Vec<(u32, u32)> = map.iter().map(|(&c, &u)| (c, u)).collect();
936 for chunk in items.chunks(MAX_BF_ENTRIES) {
937 let _ = writeln!(buf, "{} beginbfchar", chunk.len());
938 for &(code, uni) in chunk {
939 add_charcode(buf, code);
940 buf.push(' ');
941 add_unicode(buf, uni);
942 buf.push('\n');
943 }
944 buf.push_str("endbfchar\n");
945 }
946}
947
948fn write_bfrange_list(buf: &mut String, map: &BTreeMap<(u32, u32), Vec<u32>>) {
949 let items: Vec<(&(u32, u32), &Vec<u32>)> = map.iter().collect();
950 for chunk in items.chunks(MAX_BF_ENTRIES) {
951 let _ = writeln!(buf, "{} beginbfrange", chunk.len());
952 for ((start, end), unicodes) in chunk {
953 add_charcode(buf, *start);
954 buf.push(' ');
955 add_charcode(buf, *end);
956 buf.push_str(" [");
957 for (i, u) in unicodes.iter().enumerate() {
958 if i > 0 {
959 buf.push(' ');
960 }
961 add_unicode(buf, *u);
962 }
963 buf.push_str("]\n");
964 }
965 buf.push_str("endbfrange\n");
966 }
967}
968
969fn write_bfrange_consec(buf: &mut String, map: &BTreeMap<(u32, u32), u32>) {
970 let items: Vec<((u32, u32), u32)> = map.iter().map(|(&k, &v)| (k, v)).collect();
971 for chunk in items.chunks(MAX_BF_ENTRIES) {
972 let _ = writeln!(buf, "{} beginbfrange", chunk.len());
973 for &((start, end), uni) in chunk {
974 add_charcode(buf, start);
975 buf.push(' ');
976 add_charcode(buf, end);
977 buf.push(' ');
978 add_unicode(buf, uni);
979 buf.push('\n');
980 }
981 buf.push_str("endbfrange\n");
982 }
983}
984
985fn add_charcode(buf: &mut String, number: u32) {
986 let _ = std::fmt::Write::write_fmt(buf, format_args!("<{number:04X}>"));
987}
988
989fn add_unicode(buf: &mut String, unicode: u32) {
990 let u = if (0xD800..=0xDFFF).contains(&unicode) {
991 0
992 } else {
993 unicode
994 };
995 if let Some(ch) = char::from_u32(u) {
996 let mut utf16 = [0u16; 2];
997 let enc = ch.encode_utf16(&mut utf16);
998 buf.push('<');
999 for unit in enc.iter() {
1000 let _ = std::fmt::Write::write_fmt(buf, format_args!("{unit:04X}"));
1001 }
1002 buf.push('>');
1003 } else {
1004 buf.push_str("<0000>");
1005 }
1006}
1007
1008#[must_use]
1013pub fn string_width(
1014 font: ObjRef,
1015 codes: &[u8],
1016 r: &impl pdfrum_object::Resolve,
1017 limits: &pdfrum_common::Limits,
1018 diags: &mut pdfrum_common::Diagnostics,
1019) -> f64 {
1020 let loaded = r
1021 .fetch(font)
1022 .ok()
1023 .as_deref()
1024 .and_then(Object::as_dict)
1025 .and_then(|dict| pdfrum_font::load(dict, r, &pdfrum_font::FontCache::new(), limits, diags));
1026 match loaded {
1027 Some(metrics) => f64::from(metrics.string_width(codes)),
1028 None => 500.0 * f64::from(u32::try_from(codes.len()).unwrap_or(u32::MAX)),
1029 }
1030}
1031
1032#[cfg(test)]
1033mod tests {
1034 use super::{FontEncoding, ProgramKind, sniff_kind};
1035 use crate::doc::EditDoc;
1036 use crate::names;
1037 use pdfrum_object::{Name, ObjRef, Resolve};
1038 use pdfrum_parser::{Document, LoadOptions, load};
1039 use std::sync::Arc;
1040
1041 const TINY: &[u8] = include_bytes!("../../tests/files/tiny.ttf");
1042 const HELLO: &[u8] = include_bytes!("../../tests/files/hello.pdf");
1043
1044 fn loaded() -> Document {
1045 load(Arc::from(HELLO), &LoadOptions::default()).expect("opens")
1046 }
1047
1048 fn fetch_dict(edit: &EditDoc<'_>, r: ObjRef) -> pdfrum_object::Dict {
1049 edit.fetch(r)
1050 .expect("fetches")
1051 .as_dict()
1052 .expect("dict")
1053 .clone()
1054 }
1055
1056 #[test]
1057 fn sniff_recognises_truetype_and_otto() {
1058 assert_eq!(sniff_kind(TINY), Some(ProgramKind::TrueType));
1059 assert_eq!(sniff_kind(b"OTTO\x00\x01"), Some(ProgramKind::OpenTypeCff));
1060 assert_eq!(sniff_kind(b"%!PS-AdobeFont-1.0"), Some(ProgramKind::Type1));
1061 assert_eq!(sniff_kind(b"\x80\x01"), Some(ProgramKind::Type1));
1062 assert_eq!(sniff_kind(b"not a font"), None);
1063 }
1064
1065 #[test]
1066 fn simple_writes_firstchar_lastchar_widths_and_length1() {
1067 let doc = loaded();
1068 let mut edit = EditDoc::new(&doc);
1069 let font = edit.embed_font(TINY, FontEncoding::Simple).expect("embeds");
1070 let dict = fetch_dict(&edit, font.object());
1071 assert_eq!(
1072 dict.name(names::SUBTYPE).map(Name::as_bytes),
1073 Some(&b"TrueType"[..])
1074 );
1075 let first = dict.direct_int(names::FIRST_CHAR).expect("FirstChar");
1076 let last = dict.direct_int(names::LAST_CHAR).expect("LastChar");
1077 assert!(last >= first);
1078 let widths_ref = dict.reference(names::WIDTHS).expect("Widths");
1079 let widths = edit
1080 .fetch(widths_ref)
1081 .expect("widths")
1082 .as_array()
1083 .expect("array")
1084 .clone();
1085 assert_eq!(
1086 widths.len(),
1087 usize::try_from(last - first + 1).expect("fits")
1088 );
1089
1090 let desc_ref = dict.reference(names::FONT_DESCRIPTOR).expect("desc");
1091 let desc = fetch_dict(&edit, desc_ref);
1092 let flags = desc.direct_int(names::FLAGS).expect("Flags");
1093 assert_eq!(flags & (1 << 5), 1 << 5, "NonSymbolic");
1094 let file = desc.reference(names::FONT_FILE2).expect("FontFile2");
1095 let stream = edit
1096 .fetch(file)
1097 .expect("file")
1098 .as_stream()
1099 .expect("stream")
1100 .clone();
1101 let length1 = stream.dict.direct_int(names::LENGTH1).expect("Length1");
1102 assert_eq!(length1, i64::try_from(TINY.len()).expect("fits"));
1103 assert!(desc.reference(names::FONT_FILE3).is_none());
1104 }
1105
1106 #[test]
1107 fn composite_writes_w_and_tounicode() {
1108 let doc = loaded();
1109 let mut edit = EditDoc::new(&doc);
1110 let font = edit
1111 .embed_font(TINY, FontEncoding::Composite)
1112 .expect("embeds");
1113 let dict = fetch_dict(&edit, font.object());
1114 assert_eq!(
1115 dict.name(names::SUBTYPE).map(Name::as_bytes),
1116 Some(&b"Type0"[..])
1117 );
1118 assert_eq!(
1119 dict.name(names::ENCODING).map(Name::as_bytes),
1120 Some(&b"Identity-H"[..])
1121 );
1122 assert!(dict.reference(names::TO_UNICODE).is_some());
1123 let descendants = dict
1124 .array(names::DESCENDANT_FONTS, &edit)
1125 .expect("DescendantFonts");
1126 let cid_ref = descendants.reference_at(0).expect("cid");
1127 let cid = fetch_dict(&edit, cid_ref);
1128 assert_eq!(
1129 cid.name(names::SUBTYPE).map(Name::as_bytes),
1130 Some(&b"CIDFontType2"[..])
1131 );
1132 let w_ref = cid.reference(names::W).expect("W");
1133 let w = edit
1134 .fetch(w_ref)
1135 .expect("W")
1136 .as_array()
1137 .expect("array")
1138 .clone();
1139 assert!(!w.is_empty());
1140 let tu = dict.reference(names::TO_UNICODE).expect("ToUnicode");
1141 let stream = edit
1142 .fetch(tu)
1143 .expect("tu")
1144 .as_stream()
1145 .expect("stream")
1146 .clone();
1147 let bytes: &[u8] = &stream.data;
1148 assert!(bytes.windows(9).any(|w| w == b"begincmap"));
1149 assert!(bytes.windows(7).any(|w| w == b"endcmap"));
1150 }
1151
1152 #[test]
1153 fn otto_tag_selects_fontfile3() {
1154 let mut otto = TINY.to_vec();
1155 if let Some(head) = otto.get_mut(..4) {
1156 head.copy_from_slice(b"OTTO");
1157 }
1158 let doc = loaded();
1159 let mut edit = EditDoc::new(&doc);
1160 match edit.embed_font(&otto, FontEncoding::Composite) {
1161 Ok(font) => {
1162 let dict = fetch_dict(&edit, font.object());
1163 let descendants = dict
1164 .array(names::DESCENDANT_FONTS, &edit)
1165 .expect("DescendantFonts");
1166 let cid = fetch_dict(&edit, descendants.reference_at(0).expect("cid"));
1167 assert_eq!(
1168 cid.name(names::SUBTYPE).map(Name::as_bytes),
1169 Some(&b"CIDFontType0"[..])
1170 );
1171 let desc = fetch_dict(&edit, cid.reference(names::FONT_DESCRIPTOR).expect("desc"));
1172 let file = desc.reference(names::FONT_FILE3).expect("FontFile3");
1173 assert!(desc.reference(names::FONT_FILE2).is_none());
1174 let stream = edit
1175 .fetch(file)
1176 .expect("file")
1177 .as_stream()
1178 .expect("stream")
1179 .clone();
1180 assert_eq!(
1181 stream.dict.name(names::SUBTYPE).map(Name::as_bytes),
1182 Some(&b"OpenType"[..])
1183 );
1184 }
1185 Err(_) => {
1186 assert_eq!(sniff_kind(&otto), Some(ProgramKind::OpenTypeCff));
1189 }
1190 }
1191 }
1192
1193 #[test]
1194 fn encode_unmapped_is_notdef() {
1195 let doc = loaded();
1196 let mut edit = EditDoc::new(&doc);
1197 let font = edit
1198 .embed_font(TINY, FontEncoding::Composite)
1199 .expect("embeds");
1200 let codes = font.encode("Hello");
1201 assert_eq!(codes.len(), 10, "two bytes per character");
1202 let standard = edit
1203 .standard_font(pdfrum_font::StandardFont::Helvetica)
1204 .expect("standard");
1205 assert_eq!(standard.encode("Hi"), b"Hi");
1206 assert_eq!(standard.encode("\u{4e00}"), vec![0]);
1207 }
1208
1209 #[test]
1214 fn a_composite_notdef_keeps_the_two_byte_width() {
1215 let doc = loaded();
1216 let mut edit = EditDoc::new(&doc);
1217 let font = edit
1218 .embed_font(TINY, FontEncoding::Composite)
1219 .expect("embeds");
1220 let codes = font.encode("H\u{4e00}H");
1223 assert_eq!(codes.len(), 6, "two bytes per character");
1224 assert_eq!(codes.get(2..4), Some(&[0, 0][..]), "a two-byte .notdef");
1225 let missing = font.encode_checked("H\u{4e00}H").expect_err("refused");
1230 assert_eq!((missing.character, missing.offset), ('H', 0));
1231 }
1232
1233 #[test]
1234 fn standard_font_has_no_widths_and_no_program() {
1235 let doc = loaded();
1236 let mut edit = EditDoc::new(&doc);
1237 let font = edit
1238 .standard_font(pdfrum_font::StandardFont::Helvetica)
1239 .expect("standard");
1240 let dict = fetch_dict(&edit, font.object());
1241 assert_eq!(
1242 dict.name(names::SUBTYPE).map(Name::as_bytes),
1243 Some(&b"Type1"[..])
1244 );
1245 assert_eq!(
1246 dict.name(names::BASE_FONT).map(Name::as_bytes),
1247 Some(&b"Helvetica"[..])
1248 );
1249 assert_eq!(
1250 dict.name(names::ENCODING).map(Name::as_bytes),
1251 Some(&b"WinAnsiEncoding"[..])
1252 );
1253 assert!(dict.raw(names::WIDTHS).is_none());
1254 assert!(dict.raw(names::FONT_DESCRIPTOR).is_none());
1255 }
1256
1257 #[test]
1258 fn junk_is_refused() {
1259 let doc = loaded();
1260 let mut edit = EditDoc::new(&doc);
1261 assert!(
1262 edit.embed_font(b"not a font", FontEncoding::Simple)
1263 .is_err()
1264 );
1265 assert!(edit.embed_font(&[], FontEncoding::Composite).is_err());
1266 }
1267}