1mod truetype;
9mod type1;
10
11use crate::descriptor::{self, FontDescriptor};
12use crate::encoding::{FontEncoding, adobe_char_name, load_differences};
13use crate::fallback::GlyphFallback;
14use crate::glyphs::{Charmap, Face, GlyphSource};
15use crate::ids::GlyphName;
16use crate::subst::{
17 self, CodePage, FontRequest, StandardFont, SubstFont, SubstitutionOptions, strip_subset_prefix,
18};
19use crate::tounicode::{self, ToUnicode};
20use crate::widths::{SimpleWidths, WIDTH_UNSET};
21use crate::{CharCode, CharItem, FontCache, FontFlags, FontId, Gid, names, widths};
22use pdfrum_common::kurbo::Rect;
23use pdfrum_common::{DiagKind, Diagnostics, Limits, Severity};
24use pdfrum_object::{Dict, Resolve};
25use smallvec::SmallVec;
26use std::sync::OnceLock;
27
28const SPACE: u8 = 32;
31
32#[cfg(test)]
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub enum SimpleKind {
36 Type1 {
38 base14: Option<StandardFont>,
41 },
42 TrueType,
44}
45
46#[derive(Debug)]
52pub struct SimpleFont {
53 pub(crate) id: FontId,
55 pub glyphs: GlyphSource,
57 pub(crate) encoding_kind: FontEncoding,
59 pub(crate) unicodes: [u16; 256],
63 pub(crate) glyph_index: [u16; 256],
72 pub(crate) widths: SimpleWidths,
74 pub(crate) to_unicode: Option<ToUnicode>,
76 pub(crate) descriptor: FontDescriptor,
78 pub(crate) subst: Option<SubstFont>,
80 #[cfg(test)]
82 pub(crate) kind: SimpleKind,
83 pub(crate) embedded: bool,
87 pub(crate) is_truetype: bool,
91 pub(crate) base_font_name: Vec<u8>,
93 char_bbox: [Rect; 256],
95 pub(crate) fallback: OnceLock<Option<GlyphFallback>>,
98}
99
100impl SimpleFont {
101 #[must_use]
106 pub(crate) fn glyph_from_charcode(&self, code: CharCode) -> Option<Gid> {
107 let index = usize::try_from(code.0).ok()?;
108 match self.glyph_index.get(index) {
109 Some(&WIDTH_UNSET) | None => None,
110 Some(&g) => Some(Gid(g)),
111 }
112 }
113
114 #[must_use]
120 pub(crate) fn char_width(&self, code: CharCode) -> f32 {
121 let code = if code.0 > 0xff { 0 } else { code.0 as u8 };
122 if let Some(w) = self.widths.get(code) {
123 return w;
124 }
125 match self.glyph_from_charcode(CharCode(u32::from(code))) {
127 Some(gid) => f32::from(self.glyphs.advance_tt(gid) as i16),
128 None if !self.embedded && code != SPACE => self.space_metric(),
137 None => 0.0,
138 }
139 }
140
141 fn space_metric(&self) -> f32 {
143 if let Some(w) = self.widths.get(SPACE) {
144 return w;
145 }
146 match self.glyph_from_charcode(CharCode(u32::from(SPACE))) {
147 Some(gid) => f32::from(self.glyphs.advance_tt(gid) as i16),
148 None => 0.0,
149 }
150 }
151
152 #[must_use]
155 pub(crate) fn unicode_from_charcode(&self, code: CharCode) -> SmallVec<[char; 2]> {
156 if let Some(tu) = &self.to_unicode {
157 let chars = tu.lookup(code);
158 if !chars.is_empty() {
159 return chars;
160 }
161 }
162 let Ok(index) = usize::try_from(code.0) else {
163 return SmallVec::new();
164 };
165 match self.unicodes.get(index) {
166 Some(&0) | None => {
167 match self
173 .encoding_kind
174 .unicodes()
175 .and_then(|table| table.get(index))
176 .copied()
177 {
178 Some(0) | None => SmallVec::new(),
179 Some(u) => char::from_u32(u32::from(u))
180 .map(|c| SmallVec::from_slice(&[c]))
181 .unwrap_or_default(),
182 }
183 }
184 Some(&u) => char::from_u32(u32::from(u))
185 .map(|c| SmallVec::from_slice(&[c]))
186 .unwrap_or_default(),
187 }
188 }
189
190 #[must_use]
197 pub(crate) fn char_code_from_unicode(&self, unicode: char) -> Option<CharCode> {
198 if let Some(tu) = &self.to_unicode {
199 let code = tu.reverse(unicode);
200 if code.0 != 0 {
201 return Some(code);
202 }
203 }
204 let target = u16::try_from(u32::from(unicode)).ok()?;
205 if target == 0 {
206 return None;
207 }
208 self.unicodes
211 .iter()
212 .position(|&u| u == target)
213 .and_then(|i| u32::try_from(i).ok())
214 .map(CharCode)
215 }
216
217 #[must_use]
223 pub(crate) fn char_bbox(&self, code: CharCode) -> Rect {
224 let code = if code.0 > 0xff { 0 } else { code.0 as u8 };
225 let stored = self
226 .char_bbox
227 .get(usize::from(code))
228 .copied()
229 .unwrap_or(Rect::ZERO);
230 if stored != Rect::ZERO
231 || self.embedded
232 || code == SPACE
233 || self
234 .glyph_from_charcode(CharCode(u32::from(code)))
235 .is_some()
236 {
237 return stored;
238 }
239 self.char_bbox
240 .get(usize::from(SPACE))
241 .copied()
242 .unwrap_or(Rect::ZERO)
243 }
244
245 pub(crate) fn char_item(&self, code: CharCode) -> CharItem {
247 let gid = self.glyph_from_charcode(code);
248 CharItem {
249 code,
250 cid: None,
251 gid,
252 unicode: self.unicode_from_charcode(code),
253 width: self.char_width(code),
254 vertical_glyph: false,
255 }
256 }
257
258 #[must_use]
261 pub(crate) fn has_font_widths(&self) -> bool {
262 self.widths.has_declared_widths()
263 }
264
265 #[cfg(test)]
269 #[must_use]
270 pub(crate) fn is_standard_font(&self) -> bool {
271 matches!(self.kind, SimpleKind::Type1 { base14: Some(_) }) && !self.embedded
272 }
273}
274
275#[allow(clippy::too_many_lines)]
285pub(crate) fn load(
286 dict: &Dict,
287 r: &impl Resolve,
288 cache: &FontCache,
289 opts: &SubstitutionOptions,
290 limits: &Limits,
291 diags: &mut Diagnostics,
292 is_truetype: bool,
293) -> SimpleFont {
294 let mut base_font_name = dict
295 .name(names::BASE_FONT)
296 .map(|n| n.as_bytes().to_vec())
297 .unwrap_or_default();
298
299 let base14 = if is_truetype {
302 None
303 } else {
304 subst::standard_font_index(&base_font_name)
305 };
306 let mut flags = FontFlags::DEFAULT;
307 let mut encoding_kind = FontEncoding::Builtin;
308 let mut widths_table = SimpleWidths::default();
309 if let Some(f) = base14 {
310 base_font_name = subst::canonical_font_name(f).as_bytes().to_vec();
311 flags = if f.is_symbolic() {
312 FontFlags::SYMBOLIC
313 } else {
314 FontFlags::NON_SYMBOLIC
315 };
316 if f.is_fixed() {
317 widths_table = SimpleWidths {
319 raw: [600; 256],
320 use_face_widths: false,
321 };
322 }
323 encoding_kind = match f {
324 StandardFont::Symbol => FontEncoding::AdobeSymbol,
325 StandardFont::Dingbats => FontEncoding::ZapfDingbats,
326 _ if flags.is_non_symbolic() => FontEncoding::Standard,
327 _ => encoding_kind,
328 };
329 }
330
331 let desc = dict.dict(names::FONT_DESCRIPTOR, r);
333 let mut descriptor = FontDescriptor {
334 flags,
335 ..FontDescriptor::default()
336 };
337 if let Some(d) = &desc {
338 descriptor = descriptor::load(d, r);
339 }
340
341 let (mut glyphs, mut embedded) = load_font_program(desc.as_ref(), r, limits, diags);
344
345 let declared = widths::load_simple(dict, desc.as_ref(), r);
348 if declared.has_declared_widths() || !widths_table.has_declared_widths() {
349 widths_table = declared;
350 }
351
352 let mut subst_font = None;
354 if embedded {
355 base_font_name = strip_subset_prefix(&base_font_name).to_vec();
356 } else {
357 let request = FontRequest {
358 name: base_font_name.clone(),
359 is_truetype,
360 flags: descriptor.flags,
361 weight: descriptor.subst_weight(),
362 italic_angle: descriptor.italic_angle,
363 code_page: CodePage::DefAnsi,
364 vertical: false,
365 };
366 let s = substitute(&request, opts, diags);
367 glyphs = s.glyphs;
368 subst_font = Some(s.subst);
369 }
370
371 if !descriptor.flags.is_symbolic() {
374 encoding_kind = FontEncoding::Standard;
375 }
376
377 let mut differences: [Option<GlyphName>; 256] = [const { None }; 256];
379 let _has_differences = load_pdf_encoding(
380 dict,
381 r,
382 &base_font_name,
383 descriptor.flags,
384 embedded,
385 is_truetype,
386 &mut encoding_kind,
387 &mut differences,
388 );
389
390 let to_unicode = load_to_unicode(dict, r, limits, diags);
391
392 let mut unicodes = [0u16; 256];
394 let mut glyph_index = [WIDTH_UNSET; 256];
395 if glyphs.is_some() {
396 let ctx = LadderContext {
397 glyphs: &glyphs,
398 encoding: encoding_kind,
399 differences: &differences,
400 flags: descriptor.flags,
401 embedded,
402 base14,
403 to_unicode: to_unicode.as_ref(),
404 first_char: dict.int(names::FIRST_CHAR, r).unwrap_or(0),
405 };
406 if is_truetype {
407 truetype::load_glyph_map(&ctx, &mut unicodes, &mut glyph_index);
408 } else {
409 type1::load_glyph_map(&ctx, &mut unicodes, &mut glyph_index);
410 }
411 }
412
413 if descriptor.flags.is_all_cap() {
416 apply_all_caps(&mut glyph_index, &mut widths_table, embedded);
417 }
418
419 let mut char_bbox = [Rect::ZERO; 256];
421 for (code, slot) in char_bbox.iter_mut().enumerate() {
422 let Some(&g) = glyph_index.get(code) else {
423 continue;
424 };
425 if g == WIDTH_UNSET {
426 continue;
427 }
428 if let Some(b) = glyphs.glyph_bbox(Gid(g)) {
429 *slot = b;
430 }
431 }
432 let metrics = match &glyphs {
433 GlyphSource::Fontations(f) => f.metrics(),
434 GlyphSource::Type1(f) => Some(descriptor::FaceMetrics {
435 upem: f.units_per_em(),
436 bbox_left: f.bbox().x0 as i64,
437 bbox_top: f.bbox().y1 as i64,
438 bbox_right: f.bbox().x1 as i64,
439 bbox_bottom: f.bbox().y0 as i64,
440 ascender: f.bbox().y1 as i64,
441 descender: f.bbox().y0 as i64,
442 }),
443 GlyphSource::None => None,
444 };
445 descriptor::check_font_metrics(&mut descriptor, metrics, |c| {
446 char_bbox.get(usize::from(c)).copied().unwrap_or(Rect::ZERO)
447 });
448
449 if !embedded && !glyphs.is_some() {
450 diags.record(Severity::Suspicious, DiagKind::FontSubstitutionFailed, None);
451 }
452 if !glyphs.is_some() {
453 embedded = false;
454 }
455
456 SimpleFont {
457 id: cache.next_id(),
458 glyphs,
459 encoding_kind,
460 unicodes,
461 glyph_index,
462 widths: widths_table,
463 to_unicode,
464 descriptor,
465 subst: subst_font,
466 #[cfg(test)]
467 kind: if is_truetype {
468 SimpleKind::TrueType
469 } else {
470 SimpleKind::Type1 { base14 }
471 },
472 embedded,
473 is_truetype,
474 base_font_name,
475 char_bbox,
476 fallback: OnceLock::new(),
477 }
478}
479
480pub(crate) struct LadderContext<'a> {
482 pub glyphs: &'a GlyphSource,
483 pub encoding: FontEncoding,
484 pub differences: &'a [Option<GlyphName>; 256],
485 pub flags: FontFlags,
486 pub embedded: bool,
487 pub base14: Option<StandardFont>,
488 pub to_unicode: Option<&'a ToUnicode>,
489 pub first_char: i64,
490}
491
492impl LadderContext<'_> {
493 pub(crate) fn char_name(&self, code: u8) -> Option<&[u8]> {
495 adobe_char_name(self.encoding, self.differences, u32::from(code))
496 }
497
498 pub(crate) fn has_differences(&self) -> bool {
501 self.differences.iter().any(Option::is_some)
502 }
503}
504
505pub(crate) fn load_font_program(
512 desc: Option<&Dict>,
513 r: &impl Resolve,
514 limits: &Limits,
515 diags: &mut Diagnostics,
516) -> (GlyphSource, bool) {
517 let Some(desc) = desc else {
518 return (GlyphSource::None, false);
519 };
520 let stream = [names::FONT_FILE, names::FONT_FILE2, names::FONT_FILE3]
521 .into_iter()
522 .find_map(|k| desc.stream(k, r));
523 let Some(stream) = stream else {
524 return (GlyphSource::None, false);
525 };
526
527 let bytes = pdfrum_filters::decode_chain(&stream, 0, r, limits, diags).data;
531 if bytes.is_empty() {
532 diags.record(Severity::Suspicious, DiagKind::FontProgramUnreadable, None);
533 return (GlyphSource::None, false);
534 }
535 let shared: std::sync::Arc<[u8]> = std::sync::Arc::from(bytes.as_slice());
536
537 if let Some(face) = Face::new(shared.clone(), 0) {
538 return (GlyphSource::Fontations(face), true);
539 }
540 if let Ok(f) = pdfrum_type1::Type1Font::parse(&shared, limits, diags) {
543 (GlyphSource::Type1(std::sync::Arc::new(f)), true)
544 } else {
545 diags.record(Severity::Suspicious, DiagKind::FontProgramUnreadable, None);
548 (GlyphSource::None, false)
549 }
550}
551
552fn substitute(
554 request: &FontRequest,
555 opts: &SubstitutionOptions,
556 diags: &mut Diagnostics,
557) -> subst::Substitution {
558 subst::resolve_with_options(request, opts, diags)
559}
560
561#[allow(clippy::too_many_arguments)]
569pub(crate) fn load_pdf_encoding(
570 dict: &Dict,
571 r: &impl Resolve,
572 base_font_name: &[u8],
573 flags: FontFlags,
574 embedded: bool,
575 is_truetype: bool,
576 encoding: &mut FontEncoding,
577 differences: &mut [Option<GlyphName>; 256],
578) -> bool {
579 let Some(enc) = dict.get(names::ENCODING, r) else {
580 if base_font_name == b"Symbol" {
581 *encoding = if is_truetype {
582 FontEncoding::MsSymbol
583 } else {
584 FontEncoding::AdobeSymbol
585 };
586 } else if !embedded && *encoding == FontEncoding::Builtin {
587 *encoding = FontEncoding::WinAnsi;
588 }
589 return false;
590 };
591
592 if let Some(name) = enc.as_name() {
593 if matches!(
595 *encoding,
596 FontEncoding::AdobeSymbol | FontEncoding::ZapfDingbats
597 ) {
598 return false;
599 }
600 if flags.is_symbolic() && base_font_name == b"Symbol" {
601 if !is_truetype {
602 *encoding = FontEncoding::AdobeSymbol;
603 }
604 return false;
605 }
606 let mut spelling = name.as_bytes();
607 if spelling == b"MacExpertEncoding" {
608 spelling = b"WinAnsiEncoding";
609 }
610 if let Some(e) = FontEncoding::from_pdf_name(spelling) {
611 *encoding = e;
612 }
613 return false;
614 }
615
616 let Some(enc_dict) = enc.as_dict() else {
617 return false;
619 };
620 if !matches!(
621 *encoding,
622 FontEncoding::AdobeSymbol | FontEncoding::ZapfDingbats
623 ) && let Some(base) = enc_dict.name(names::BASE_ENCODING)
624 {
625 let mut spelling = base.as_bytes();
626 if is_truetype && spelling == b"MacExpertEncoding" {
627 spelling = b"WinAnsiEncoding";
628 }
629 if let Some(e) = FontEncoding::from_pdf_name(spelling) {
630 *encoding = e;
631 }
632 }
633 if (!embedded || is_truetype) && *encoding == FontEncoding::Builtin {
634 *encoding = FontEncoding::Standard;
635 }
636 match enc_dict.array(names::DIFFERENCES, r) {
637 Some(diffs) => load_differences(&diffs, r, differences),
638 None => false,
639 }
640}
641
642pub(crate) fn load_to_unicode(
644 dict: &Dict,
645 r: &impl Resolve,
646 limits: &Limits,
647 diags: &mut Diagnostics,
648) -> Option<ToUnicode> {
649 let stream = dict.stream(names::TO_UNICODE, r)?;
650 let bytes = pdfrum_filters::decode_chain(&stream, 0, r, limits, diags).data;
651 let map = tounicode::parse(&bytes, limits, diags);
652 if map.is_empty() { None } else { Some(map) }
653}
654
655fn apply_all_caps(glyph_index: &mut [u16; 256], widths: &mut SimpleWidths, embedded: bool) {
662 for (lo, hi) in [(b'a', b'z'), (0xE0u8, 0xF6u8), (0xF8, 0xFD)] {
663 for i in lo..=hi {
664 let idx = usize::from(i);
665 if glyph_index.get(idx) != Some(&WIDTH_UNSET) && embedded {
666 continue;
667 }
668 let Some(j) = idx.checked_sub(32) else {
669 continue;
670 };
671 let (Some(&src_glyph), Some(&src_width)) = (glyph_index.get(j), widths.raw.get(j))
672 else {
673 continue;
674 };
675 if let Some(slot) = glyph_index.get_mut(idx) {
676 *slot = src_glyph;
677 }
678 if src_width != 0
681 && let Some(slot) = widths.raw.get_mut(idx)
682 {
683 *slot = src_width;
684 }
685 }
686 }
687}
688
689pub(crate) fn name_index(glyphs: &GlyphSource, name: &[u8]) -> u16 {
691 glyphs.name_index(name)
692}
693
694pub(crate) fn char_index(glyphs: &GlyphSource, charmap: Charmap, code: u32) -> u16 {
696 glyphs.char_index(charmap, code)
697}
698
699impl SimpleFont {
701 #[cfg(test)]
703 #[must_use]
704 pub(crate) fn glyph_path(&self, gid: Gid) -> Option<pdfrum_common::kurbo::BezPath> {
705 self.glyphs
706 .outline(gid, crate::glyphs::GlyphParams::default())
707 }
708}
709
710#[cfg(test)]
712pub(crate) fn resolve_encoding_for_test(
713 dict: &Dict,
714 r: &impl Resolve,
715 base_font_name: &[u8],
716 flags: FontFlags,
717 embedded: bool,
718 is_truetype: bool,
719 prior: FontEncoding,
720) -> (FontEncoding, bool) {
721 let mut e = prior;
722 let mut diffs: [Option<GlyphName>; 256] = [const { None }; 256];
723 let had = load_pdf_encoding(
724 dict,
725 r,
726 base_font_name,
727 flags,
728 embedded,
729 is_truetype,
730 &mut e,
731 &mut diffs,
732 );
733 (e, had)
734}
735
736#[cfg(test)]
737use pdfrum_object::Object;
738
739#[cfg(test)]
740#[path = "simple_tests.rs"]
741mod tests;