1mod truetype;
9mod type1;
10
11use crate::descriptor::{self, FontDescriptor};
12use crate::encoding::{FontEncoding, adobe_char_name, load_differences};
13use crate::glyphs::{Charmap, Face, GlyphSource};
14use crate::ids::GlyphName;
15use crate::subst::{
16 self, CodePage, FontRequest, StandardFont, SubstFont, SubstitutionOptions, strip_subset_prefix,
17};
18use crate::tounicode::{self, ToUnicode};
19use crate::widths::{SimpleWidths, WIDTH_UNSET};
20use crate::{CharCode, CharItem, FontCache, FontFlags, FontId, Gid, names, widths};
21use pdfrum_common::kurbo::Rect;
22use pdfrum_common::{DiagKind, Diagnostics, Limits, Severity};
23use pdfrum_object::{Dict, Resolve};
24use smallvec::SmallVec;
25
26const SPACE: u8 = 32;
29
30#[cfg(test)]
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub enum SimpleKind {
34 Type1 {
36 base14: Option<StandardFont>,
39 },
40 TrueType,
42}
43
44#[derive(Debug)]
50pub struct SimpleFont {
51 pub(crate) id: FontId,
53 pub glyphs: GlyphSource,
55 pub(crate) encoding_kind: FontEncoding,
57 pub(crate) unicodes: [u16; 256],
61 pub(crate) glyph_index: [u16; 256],
70 pub(crate) widths: SimpleWidths,
72 pub(crate) to_unicode: Option<ToUnicode>,
74 pub(crate) descriptor: FontDescriptor,
76 pub(crate) subst: Option<SubstFont>,
78 #[cfg(test)]
80 pub(crate) kind: SimpleKind,
81 pub(crate) embedded: bool,
85 pub(crate) base_font_name: Vec<u8>,
87 char_bbox: [Rect; 256],
89}
90
91impl SimpleFont {
92 #[must_use]
97 pub(crate) fn glyph_from_charcode(&self, code: CharCode) -> Option<Gid> {
98 let index = usize::try_from(code.0).ok()?;
99 match self.glyph_index.get(index) {
100 Some(&WIDTH_UNSET) | None => None,
101 Some(&g) => Some(Gid(g)),
102 }
103 }
104
105 #[must_use]
111 pub(crate) fn char_width(&self, code: CharCode) -> f32 {
112 let code = if code.0 > 0xff { 0 } else { code.0 as u8 };
113 if let Some(w) = self.widths.get(code) {
114 return w;
115 }
116 match self.glyph_from_charcode(CharCode(u32::from(code))) {
118 Some(gid) => f32::from(self.glyphs.advance_tt(gid) as i16),
119 None if !self.embedded && code != SPACE => self.space_metric(),
128 None => 0.0,
129 }
130 }
131
132 fn space_metric(&self) -> f32 {
134 if let Some(w) = self.widths.get(SPACE) {
135 return w;
136 }
137 match self.glyph_from_charcode(CharCode(u32::from(SPACE))) {
138 Some(gid) => f32::from(self.glyphs.advance_tt(gid) as i16),
139 None => 0.0,
140 }
141 }
142
143 #[must_use]
146 pub(crate) fn unicode_from_charcode(&self, code: CharCode) -> SmallVec<[char; 2]> {
147 if let Some(tu) = &self.to_unicode {
148 let chars = tu.lookup(code);
149 if !chars.is_empty() {
150 return chars;
151 }
152 }
153 let Ok(index) = usize::try_from(code.0) else {
154 return SmallVec::new();
155 };
156 match self.unicodes.get(index) {
157 Some(&0) | None => SmallVec::new(),
158 Some(&u) => char::from_u32(u32::from(u))
159 .map(|c| SmallVec::from_slice(&[c]))
160 .unwrap_or_default(),
161 }
162 }
163
164 #[must_use]
171 pub(crate) fn char_code_from_unicode(&self, unicode: char) -> Option<CharCode> {
172 if let Some(tu) = &self.to_unicode {
173 let code = tu.reverse(unicode);
174 if code.0 != 0 {
175 return Some(code);
176 }
177 }
178 let target = u16::try_from(u32::from(unicode)).ok()?;
179 if target == 0 {
180 return None;
181 }
182 self.unicodes
185 .iter()
186 .position(|&u| u == target)
187 .and_then(|i| u32::try_from(i).ok())
188 .map(CharCode)
189 }
190
191 #[must_use]
197 pub(crate) fn char_bbox(&self, code: CharCode) -> Rect {
198 let code = if code.0 > 0xff { 0 } else { code.0 as u8 };
199 let stored = self
200 .char_bbox
201 .get(usize::from(code))
202 .copied()
203 .unwrap_or(Rect::ZERO);
204 if stored != Rect::ZERO
205 || self.embedded
206 || code == SPACE
207 || self
208 .glyph_from_charcode(CharCode(u32::from(code)))
209 .is_some()
210 {
211 return stored;
212 }
213 self.char_bbox
214 .get(usize::from(SPACE))
215 .copied()
216 .unwrap_or(Rect::ZERO)
217 }
218
219 pub(crate) fn char_item(&self, code: CharCode) -> CharItem {
221 let gid = self.glyph_from_charcode(code);
222 CharItem {
223 code,
224 cid: None,
225 gid,
226 unicode: self.unicode_from_charcode(code),
227 width: self.char_width(code),
228 vertical_glyph: false,
229 }
230 }
231
232 #[must_use]
235 pub(crate) fn has_font_widths(&self) -> bool {
236 self.widths.has_declared_widths()
237 }
238
239 #[cfg(test)]
243 #[must_use]
244 pub(crate) fn is_standard_font(&self) -> bool {
245 matches!(self.kind, SimpleKind::Type1 { base14: Some(_) }) && !self.embedded
246 }
247}
248
249#[allow(clippy::too_many_lines)]
259pub(crate) fn load(
260 dict: &Dict,
261 r: &impl Resolve,
262 cache: &FontCache,
263 opts: &SubstitutionOptions,
264 limits: &Limits,
265 diags: &mut Diagnostics,
266 is_truetype: bool,
267) -> SimpleFont {
268 let mut base_font_name = dict
269 .name(names::BASE_FONT)
270 .map(|n| n.as_bytes().to_vec())
271 .unwrap_or_default();
272
273 let base14 = if is_truetype {
276 None
277 } else {
278 subst::standard_font_index(&base_font_name)
279 };
280 let mut flags = FontFlags::DEFAULT;
281 let mut encoding_kind = FontEncoding::Builtin;
282 let mut widths_table = SimpleWidths::default();
283 if let Some(f) = base14 {
284 base_font_name = subst::canonical_font_name(f).as_bytes().to_vec();
285 flags = if f.is_symbolic() {
286 FontFlags::SYMBOLIC
287 } else {
288 FontFlags::NON_SYMBOLIC
289 };
290 if f.is_fixed() {
291 widths_table = SimpleWidths {
293 raw: [600; 256],
294 use_face_widths: false,
295 };
296 }
297 encoding_kind = match f {
298 StandardFont::Symbol => FontEncoding::AdobeSymbol,
299 StandardFont::Dingbats => FontEncoding::ZapfDingbats,
300 _ if flags.is_non_symbolic() => FontEncoding::Standard,
301 _ => encoding_kind,
302 };
303 }
304
305 let desc = dict.dict(names::FONT_DESCRIPTOR, r);
307 let mut descriptor = FontDescriptor {
308 flags,
309 ..FontDescriptor::default()
310 };
311 if let Some(d) = &desc {
312 descriptor = descriptor::load(d, r);
313 }
314
315 let (mut glyphs, mut embedded) = load_font_program(desc.as_ref(), r, limits, diags);
318
319 let declared = widths::load_simple(dict, desc.as_ref(), r);
322 if declared.has_declared_widths() || !widths_table.has_declared_widths() {
323 widths_table = declared;
324 }
325
326 let mut subst_font = None;
328 if embedded {
329 base_font_name = strip_subset_prefix(&base_font_name).to_vec();
330 } else {
331 let request = FontRequest {
332 name: base_font_name.clone(),
333 is_truetype,
334 flags: descriptor.flags,
335 weight: descriptor.subst_weight(),
336 italic_angle: descriptor.italic_angle,
337 code_page: CodePage::DefAnsi,
338 vertical: false,
339 };
340 let s = substitute(&request, opts, diags);
341 glyphs = s.glyphs;
342 subst_font = Some(s.subst);
343 }
344
345 if !descriptor.flags.is_symbolic() {
348 encoding_kind = FontEncoding::Standard;
349 }
350
351 let mut differences: [Option<GlyphName>; 256] = [const { None }; 256];
353 let _has_differences = load_pdf_encoding(
354 dict,
355 r,
356 &base_font_name,
357 descriptor.flags,
358 embedded,
359 is_truetype,
360 &mut encoding_kind,
361 &mut differences,
362 );
363
364 let to_unicode = load_to_unicode(dict, r, limits, diags);
365
366 let mut unicodes = [0u16; 256];
368 let mut glyph_index = [WIDTH_UNSET; 256];
369 if glyphs.is_some() {
370 let ctx = LadderContext {
371 glyphs: &glyphs,
372 encoding: encoding_kind,
373 differences: &differences,
374 flags: descriptor.flags,
375 embedded,
376 base14,
377 to_unicode: to_unicode.as_ref(),
378 first_char: dict.int(names::FIRST_CHAR, r).unwrap_or(0),
379 };
380 if is_truetype {
381 truetype::load_glyph_map(&ctx, &mut unicodes, &mut glyph_index);
382 } else {
383 type1::load_glyph_map(&ctx, &mut unicodes, &mut glyph_index);
384 }
385 }
386
387 if descriptor.flags.is_all_cap() {
390 apply_all_caps(&mut glyph_index, &mut widths_table, embedded);
391 }
392
393 let mut char_bbox = [Rect::ZERO; 256];
395 for (code, slot) in char_bbox.iter_mut().enumerate() {
396 let Some(&g) = glyph_index.get(code) else {
397 continue;
398 };
399 if g == WIDTH_UNSET {
400 continue;
401 }
402 if let Some(b) = glyphs.glyph_bbox(Gid(g)) {
403 *slot = b;
404 }
405 }
406 let metrics = match &glyphs {
407 GlyphSource::Fontations(f) => f.metrics(),
408 GlyphSource::Type1(f) => Some(descriptor::FaceMetrics {
409 upem: f.units_per_em(),
410 bbox_left: f.bbox().x0 as i64,
411 bbox_top: f.bbox().y1 as i64,
412 bbox_right: f.bbox().x1 as i64,
413 bbox_bottom: f.bbox().y0 as i64,
414 ascender: f.bbox().y1 as i64,
415 descender: f.bbox().y0 as i64,
416 }),
417 GlyphSource::None => None,
418 };
419 descriptor::check_font_metrics(&mut descriptor, metrics, |c| {
420 char_bbox.get(usize::from(c)).copied().unwrap_or(Rect::ZERO)
421 });
422
423 if !embedded && !glyphs.is_some() {
424 diags.record(Severity::Suspicious, DiagKind::FontSubstitutionFailed, None);
425 }
426 if !glyphs.is_some() {
427 embedded = false;
428 }
429
430 SimpleFont {
431 id: cache.next_id(),
432 glyphs,
433 encoding_kind,
434 unicodes,
435 glyph_index,
436 widths: widths_table,
437 to_unicode,
438 descriptor,
439 subst: subst_font,
440 #[cfg(test)]
441 kind: if is_truetype {
442 SimpleKind::TrueType
443 } else {
444 SimpleKind::Type1 { base14 }
445 },
446 embedded,
447 base_font_name,
448 char_bbox,
449 }
450}
451
452pub(crate) struct LadderContext<'a> {
454 pub glyphs: &'a GlyphSource,
455 pub encoding: FontEncoding,
456 pub differences: &'a [Option<GlyphName>; 256],
457 pub flags: FontFlags,
458 pub embedded: bool,
459 pub base14: Option<StandardFont>,
460 pub to_unicode: Option<&'a ToUnicode>,
461 pub first_char: i64,
462}
463
464impl LadderContext<'_> {
465 pub(crate) fn char_name(&self, code: u8) -> Option<&[u8]> {
467 adobe_char_name(self.encoding, self.differences, u32::from(code))
468 }
469
470 pub(crate) fn has_differences(&self) -> bool {
473 self.differences.iter().any(Option::is_some)
474 }
475}
476
477pub(crate) fn load_font_program(
484 desc: Option<&Dict>,
485 r: &impl Resolve,
486 limits: &Limits,
487 diags: &mut Diagnostics,
488) -> (GlyphSource, bool) {
489 let Some(desc) = desc else {
490 return (GlyphSource::None, false);
491 };
492 let stream = [names::FONT_FILE, names::FONT_FILE2, names::FONT_FILE3]
493 .into_iter()
494 .find_map(|k| desc.stream(k, r));
495 let Some(stream) = stream else {
496 return (GlyphSource::None, false);
497 };
498
499 let bytes = pdfrum_filters::decode_chain(&stream, 0, r, limits, diags).data;
503 if bytes.is_empty() {
504 diags.record(Severity::Suspicious, DiagKind::FontProgramUnreadable, None);
505 return (GlyphSource::None, false);
506 }
507 let shared: std::sync::Arc<[u8]> = std::sync::Arc::from(bytes.as_slice());
508
509 if let Some(face) = Face::new(shared.clone(), 0) {
510 return (GlyphSource::Fontations(face), true);
511 }
512 if let Ok(f) = pdfrum_type1::Type1Font::parse(&shared, limits, diags) {
515 (GlyphSource::Type1(std::sync::Arc::new(f)), true)
516 } else {
517 diags.record(Severity::Suspicious, DiagKind::FontProgramUnreadable, None);
520 (GlyphSource::None, false)
521 }
522}
523
524fn substitute(
526 request: &FontRequest,
527 opts: &SubstitutionOptions,
528 diags: &mut Diagnostics,
529) -> subst::Substitution {
530 subst::resolve_with_options(request, opts, diags)
531}
532
533#[allow(clippy::too_many_arguments)]
541pub(crate) fn load_pdf_encoding(
542 dict: &Dict,
543 r: &impl Resolve,
544 base_font_name: &[u8],
545 flags: FontFlags,
546 embedded: bool,
547 is_truetype: bool,
548 encoding: &mut FontEncoding,
549 differences: &mut [Option<GlyphName>; 256],
550) -> bool {
551 let Some(enc) = dict.get(names::ENCODING, r) else {
552 if base_font_name == b"Symbol" {
553 *encoding = if is_truetype {
554 FontEncoding::MsSymbol
555 } else {
556 FontEncoding::AdobeSymbol
557 };
558 } else if !embedded && *encoding == FontEncoding::Builtin {
559 *encoding = FontEncoding::WinAnsi;
560 }
561 return false;
562 };
563
564 if let Some(name) = enc.as_name() {
565 if matches!(
567 *encoding,
568 FontEncoding::AdobeSymbol | FontEncoding::ZapfDingbats
569 ) {
570 return false;
571 }
572 if flags.is_symbolic() && base_font_name == b"Symbol" {
573 if !is_truetype {
574 *encoding = FontEncoding::AdobeSymbol;
575 }
576 return false;
577 }
578 let mut spelling = name.as_bytes();
579 if spelling == b"MacExpertEncoding" {
580 spelling = b"WinAnsiEncoding";
581 }
582 if let Some(e) = FontEncoding::from_pdf_name(spelling) {
583 *encoding = e;
584 }
585 return false;
586 }
587
588 let Some(enc_dict) = enc.as_dict() else {
589 return false;
591 };
592 if !matches!(
593 *encoding,
594 FontEncoding::AdobeSymbol | FontEncoding::ZapfDingbats
595 ) && let Some(base) = enc_dict.name(names::BASE_ENCODING)
596 {
597 let mut spelling = base.as_bytes();
598 if is_truetype && spelling == b"MacExpertEncoding" {
599 spelling = b"WinAnsiEncoding";
600 }
601 if let Some(e) = FontEncoding::from_pdf_name(spelling) {
602 *encoding = e;
603 }
604 }
605 if (!embedded || is_truetype) && *encoding == FontEncoding::Builtin {
606 *encoding = FontEncoding::Standard;
607 }
608 match enc_dict.array(names::DIFFERENCES, r) {
609 Some(diffs) => load_differences(&diffs, r, differences),
610 None => false,
611 }
612}
613
614pub(crate) fn load_to_unicode(
616 dict: &Dict,
617 r: &impl Resolve,
618 limits: &Limits,
619 diags: &mut Diagnostics,
620) -> Option<ToUnicode> {
621 let stream = dict.stream(names::TO_UNICODE, r)?;
622 let bytes = pdfrum_filters::decode_chain(&stream, 0, r, limits, diags).data;
623 let map = tounicode::parse(&bytes, limits, diags);
624 if map.is_empty() { None } else { Some(map) }
625}
626
627fn apply_all_caps(glyph_index: &mut [u16; 256], widths: &mut SimpleWidths, embedded: bool) {
634 for (lo, hi) in [(b'a', b'z'), (0xE0u8, 0xF6u8), (0xF8, 0xFD)] {
635 for i in lo..=hi {
636 let idx = usize::from(i);
637 if glyph_index.get(idx) != Some(&WIDTH_UNSET) && embedded {
638 continue;
639 }
640 let Some(j) = idx.checked_sub(32) else {
641 continue;
642 };
643 let (Some(&src_glyph), Some(&src_width)) = (glyph_index.get(j), widths.raw.get(j))
644 else {
645 continue;
646 };
647 if let Some(slot) = glyph_index.get_mut(idx) {
648 *slot = src_glyph;
649 }
650 if src_width != 0
653 && let Some(slot) = widths.raw.get_mut(idx)
654 {
655 *slot = src_width;
656 }
657 }
658 }
659}
660
661pub(crate) fn name_index(glyphs: &GlyphSource, name: &[u8]) -> u16 {
663 glyphs.name_index(name)
664}
665
666pub(crate) fn char_index(glyphs: &GlyphSource, charmap: Charmap, code: u32) -> u16 {
668 glyphs.char_index(charmap, code)
669}
670
671impl SimpleFont {
673 #[cfg(test)]
675 #[must_use]
676 pub(crate) fn glyph_path(&self, gid: Gid) -> Option<pdfrum_common::kurbo::BezPath> {
677 self.glyphs
678 .outline(gid, crate::glyphs::GlyphParams::default())
679 }
680}
681
682#[cfg(test)]
684pub(crate) fn resolve_encoding_for_test(
685 dict: &Dict,
686 r: &impl Resolve,
687 base_font_name: &[u8],
688 flags: FontFlags,
689 embedded: bool,
690 is_truetype: bool,
691 prior: FontEncoding,
692) -> (FontEncoding, bool) {
693 let mut e = prior;
694 let mut diffs: [Option<GlyphName>; 256] = [const { None }; 256];
695 let had = load_pdf_encoding(
696 dict,
697 r,
698 base_font_name,
699 flags,
700 embedded,
701 is_truetype,
702 &mut e,
703 &mut diffs,
704 );
705 (e, had)
706}
707
708#[cfg(test)]
709use pdfrum_object::Object;
710
711#[cfg(test)]
712#[path = "simple_tests.rs"]
713mod tests;