Skip to main content

pdfrum_font/
load.rs

1//! Loading a `/Font` resource into a [`Font`].
2
3use pdfrum_common::kurbo::{BezPath, Rect};
4use pdfrum_common::{Diagnostics, Limits};
5use pdfrum_object::{Dict, ObjRef, Resolve};
6use smallvec::SmallVec;
7use std::collections::HashMap;
8use std::sync::atomic::{AtomicU64, Ordering};
9use std::sync::{Arc, RwLock};
10
11use crate::cid::{self, CidTransform, Type0Font};
12use crate::encoding;
13use crate::glyphs::{self, GlyphSource};
14use crate::ids::{CharCode, Cid, FontId, Gid};
15use crate::names;
16use crate::simple::{self, SimpleFont};
17use crate::subst::{self, StandardFont, SubstFont, SubstitutionOptions};
18use crate::type3::{self, Type3Font};
19
20/// A loaded PDF font, ready to decode strings and produce glyphs.
21///
22/// Three variants, because PDF has three genuinely different kinds of font and
23/// they share almost nothing below the surface: a simple font maps one byte to
24/// one glyph through a name, a Type0 font maps a multi-byte code through a
25/// CMap to a CID and then to a glyph, and a Type3 font has no glyphs at all —
26/// its "glyphs" are content streams the page layer executes.
27#[derive(Debug)]
28pub enum Font {
29    /// `Type1`, MMType1, TrueType, or a font whose `/Subtype` was missing or
30    /// unrecognised — PDFium's dispatch sends all of those here.
31    Simple(Box<SimpleFont>),
32    /// A composite font: `/Type0` with a CID-keyed descendant.
33    Type0(Box<Type0Font>),
34    /// A font whose glyph procedures are content streams.
35    Type3(Box<Type3Font>),
36}
37
38/// One decoded character: everything the layers above need about one character
39/// code, computed once.
40///
41/// The fields answer the three separate questions a font is asked. `gid` is
42/// glyph selection, `unicode` is what the character *means*, and `width` is
43/// how far the pen moves — and none of the three is derivable from the others.
44#[derive(Debug, Clone, PartialEq)]
45pub struct CharItem {
46    /// The character code as the font's encoding delimited it: one byte for a
47    /// simple font, whatever the CMap's codespace says for a Type0 font.
48    pub code: CharCode,
49    /// The CID this code maps to, for a Type0 font only.
50    pub cid: Option<Cid>,
51    /// The glyph to draw, or `None` when the ladder found no glyph at all.
52    ///
53    /// `None` is PDFium's `-1`, which is distinct from glyph 0 (`.notdef`):
54    /// `.notdef` draws a box, `None` draws nothing. The two were a `Gid` and
55    /// a `bool` beside it until the pair could express a state that has no
56    /// meaning -- "no glyph" carrying an index.
57    pub gid: Option<Gid>,
58    /// The characters this code stands for, usually one and occasionally none
59    /// — a ligature glyph maps to several, an unmapped code to zero.
60    pub unicode: SmallVec<[char; 2]>,
61    /// The advance width in 1000/em text space.
62    pub width: f32,
63    /// Set when the `GSUB` `vert`/`vrt2` feature substituted a vertical form.
64    ///
65    /// Not derivable downstream, and load-bearing: it suppresses the Japan1
66    /// CID transform, which would otherwise rotate an already-rotated glyph.
67    pub vertical_glyph: bool,
68}
69
70impl Font {
71    /// Decode a string into one [`CharItem`] per character code.
72    ///
73    /// The one text-decoding entry point rendering and extraction share, so
74    /// they cannot disagree about where one character ends and the next
75    /// begins — which for a Type0 font is a question only the CMap's codespace
76    /// can answer.
77    pub fn decode<'a>(&'a self, s: &'a [u8]) -> impl Iterator<Item = CharItem> + 'a {
78        Decoder {
79            font: self,
80            bytes: s,
81            offset: 0,
82        }
83    }
84
85    /// A glyph's outline in 1000/em text space.
86    ///
87    /// `None` for a missing or degenerate outline, and always for a Type3
88    /// font, whose glyphs are content streams rather than outlines.
89    ///
90    /// This is the uncached path. A renderer drawing many glyphs should go
91    /// through [`crate::GlyphCache`] instead, which keys on the substitution
92    /// parameters that change the outline for a Multiple-Master face.
93    #[must_use]
94    pub fn glyph_path(&self, gid: Gid) -> Option<BezPath> {
95        self.glyphs().outline(gid, glyphs::GlyphParams::default())
96    }
97
98    /// A glyph's outline in 1000/em text space, **grid-fitted at 64 ppem**.
99    ///
100    /// The same space [`Self::glyph_path`] returns, so a caller can substitute
101    /// one for the other without touching its matrices — which is what a
102    /// renderer rasterizing a glyph *bitmap* does, since hinting applies to
103    /// that path and not to the outline one.
104    ///
105    /// `None` for every font that is not hinted: a face with no table
106    /// directory (every bare CFF and every Type 1 program, so every base-14
107    /// substitution), a Type 3 font, and a face whose own programs the
108    /// interpreter refuses. In all of them the caller falls back to
109    /// [`Self::glyph_path`] rather than drawing nothing.
110    ///
111    /// Uncached, and *expensive*: it builds a hinting instance and runs the
112    /// face's bytecode. A renderer should call it only on a bitmap-cache miss.
113    // The refused-programs arm is `cfx_face.cpp:849-857`, which reloads the
114    // glyph unhinted rather than failing; falling back to `glyph_path` lands
115    // in the same place.
116    #[must_use]
117    pub fn hinted_glyph_path(&self, gid: Gid) -> Option<BezPath> {
118        self.glyphs().hinted_outline(gid)
119    }
120
121    /// Is this a vertical-writing font? Only a Type0 font with a `-V` CMap is.
122    #[must_use]
123    pub fn is_vertical(&self) -> bool {
124        match self {
125            Self::Type0(f) => f.cmap.is_vertical(),
126            Self::Simple(_) | Self::Type3(_) => false,
127        }
128    }
129
130    /// Does the font carry its own program, rather than being substituted?
131    ///
132    /// Consulted far more widely than it looks: PDFium's per-glyph fallback,
133    /// its glyph-spacing heuristic and its all-caps aliasing all branch on it,
134    /// and a program that *failed to parse* counts as not embedded.
135    #[must_use]
136    pub fn is_embedded(&self) -> bool {
137        match self {
138            Self::Simple(f) => f.embedded,
139            Self::Type0(f) => f.embedded,
140            Self::Type3(_) => false,
141        }
142    }
143
144    /// Whether character codes can be turned into Unicode at all, which text
145    /// extraction uses to decide a font is worth reading.
146    #[must_use]
147    pub fn is_unicode_compatible(&self) -> bool {
148        match self {
149            Self::Simple(f) => {
150                f.to_unicode.is_some() || f.encoding_kind != encoding::FontEncoding::Builtin
151            }
152            Self::Type0(f) => f.is_unicode_compatible(),
153            Self::Type3(f) => f.to_unicode.is_some(),
154        }
155    }
156
157    /// The font bounding box in 1000/em text space, after the derivation of
158    /// the former working note has filled in whatever the PDF failed to declare.
159    #[must_use]
160    pub fn font_bbox(&self) -> Rect {
161        match self {
162            Self::Simple(f) => f.descriptor.font_bbox,
163            Self::Type0(f) => f.descriptor.font_bbox,
164            Self::Type3(f) => f.font_bbox,
165        }
166    }
167
168    /// The ascent in 1000/em text space.
169    #[must_use]
170    pub fn ascent(&self) -> f32 {
171        match self {
172            Self::Simple(f) => f.descriptor.ascent,
173            Self::Type0(f) => f.descriptor.ascent,
174            Self::Type3(_) => 0.0,
175        }
176    }
177
178    /// The descent in 1000/em text space, normally negative.
179    #[must_use]
180    pub fn descent(&self) -> f32 {
181        match self {
182            Self::Simple(f) => f.descriptor.descent,
183            Self::Type0(f) => f.descriptor.descent,
184            Self::Type3(_) => 0.0,
185        }
186    }
187
188    /// The Type3 font, when this is one. Its glyph procedures are raw
189    /// `/CharProcs` streams that `pdfrum-page` executes.
190    #[must_use]
191    pub fn type3(&self) -> Option<&Type3Font> {
192        match self {
193            Self::Type3(f) => Some(f),
194            Self::Simple(_) | Self::Type0(_) => None,
195        }
196    }
197
198    /// The base font name, with any subset prefix already stripped.
199    #[must_use]
200    pub fn base_font_name(&self) -> &[u8] {
201        match self {
202            Self::Simple(f) => &f.base_font_name,
203            Self::Type0(f) => &f.base_font_name,
204            Self::Type3(_) => b"",
205        }
206    }
207
208    /// This font's identity within a [`FontCache`], for glyph-cache keys.
209    #[must_use]
210    pub fn id(&self) -> FontId {
211        match self {
212            Self::Simple(f) => f.id,
213            Self::Type0(f) => f.id,
214            Self::Type3(f) => f.id,
215        }
216    }
217
218    /// The substitution record, when the font was substituted rather than
219    /// embedded. Carries the synthetic skew and embolden levels a renderer
220    /// applies.
221    #[must_use]
222    pub fn subst(&self) -> Option<&SubstFont> {
223        match self {
224            Self::Simple(f) => f.subst.as_ref(),
225            Self::Type0(f) => f.subst.as_ref(),
226            Self::Type3(_) => None,
227        }
228    }
229
230    /// The width of one character code in 1000/em text space.
231    #[must_use]
232    pub fn char_width(&self, code: CharCode) -> f32 {
233        match self {
234            Self::Simple(f) => f.char_width(code),
235            Self::Type0(f) => f.char_width(code),
236            Self::Type3(f) => f.char_width(code),
237        }
238    }
239
240    /// Whether the PDF itself declared the advance widths this font reports.
241    ///
242    /// False only for a **simple** font with no `/Widths` array, where every
243    /// width already comes from the face and comparing the two would be
244    /// comparing a number against itself. A composite font always answers
245    /// true: its `/W` array defaults to `/DW` rather than to the face.
246    ///
247    /// Read by the glyph-spacing correction (`applies_glyph_spacing`), which
248    /// only means anything when the document's widths and the face's disagree.
249    #[must_use]
250    pub(crate) fn has_declared_widths(&self) -> bool {
251        match self {
252            Self::Simple(f) => f.has_font_widths(),
253            Self::Type0(_) | Self::Type3(_) => true,
254        }
255    }
256
257    /// Whether this font's glyphs take the glyph-spacing correction.
258    ///
259    /// Reads the font's five relevant facts and asks the glyph-spacing
260    /// rule, where the reasoning lives.
261    #[must_use]
262    pub fn applies_glyph_spacing(&self) -> bool {
263        subst::applies_glyph_spacing(&subst::GlyphSpacingGate {
264            vertical: self.is_vertical(),
265            embedded: self.is_embedded(),
266            declared_widths: self.has_declared_widths(),
267            base_font_name: self.base_font_name(),
268            subst: self.subst(),
269        })
270    }
271
272    /// One glyph's own advance width, in 1000/em units, as the *face* declares
273    /// it — not as the PDF does.
274    ///
275    /// Zero when there is no face or the glyph has no advance, which callers
276    /// treat as "unknown" rather than as a genuine zero-width glyph.
277    #[must_use]
278    pub fn glyph_advance(&self, gid: Gid) -> i32 {
279        self.glyphs().advance(gid, glyphs::GlyphParams::default())
280    }
281
282    /// The bounding box of one character code's glyph, in 1000/em text space
283    /// and **y-up**: `Rect::new(left, bottom, right, top)` with
284    /// `bottom <= top`.
285    ///
286    /// `Rect::ZERO` when there is no glyph, and always for a Type 3 font,
287    /// whose glyph boxes are a property of the content streams the page layer
288    /// executes rather than of the font.
289    ///
290    /// Text extraction reads this per code — not per decoded string — when it
291    /// builds a character's tight box and when its width ladder has run out of
292    /// better answers.
293    #[must_use]
294    pub fn char_bbox(&self, code: CharCode) -> Rect {
295        match self {
296            Self::Simple(f) => f.char_bbox(code),
297            Self::Type0(f) => f.char_bbox(code),
298            Self::Type3(_) => Rect::ZERO,
299        }
300    }
301
302    /// The Adobe-Japan1 per-CID transform a character takes, when one applies.
303    ///
304    /// Only a **non-embedded** Japan1 CID font has one, and only for the
305    /// hundred and fifty-four CIDs the table lists. It moves the glyph within
306    /// its em box without touching the advance, so a renderer applies it to
307    /// the drawing origin alone — the pen walks on as if it were not there.
308    ///
309    /// Non-Japan1, embedded and non-CID fonts all answer `None` — those three
310    /// tests are the whole gate, and there is no fourth.
311    #[must_use]
312    pub fn japan1_transform(&self, code: CharCode) -> Option<CidTransform> {
313        match self {
314            Self::Type0(f) => f.japan1_transform(code),
315            Self::Simple(_) | Self::Type3(_) => None,
316        }
317    }
318
319    /// The width of a string of character codes, decoded through this font's
320    /// own encoding and summed.
321    ///
322    /// Not the same as summing [`char_width`](Self::char_width) over the codes
323    /// a caller already has: the string is re-decoded, so a code that does not
324    /// round-trip through [`append_char`](Self::append_char) — a simple font's
325    /// code above 255, say — comes back as a *different* code and contributes
326    /// a different width. That difference is the whole point of the rung this
327    /// serves in text extraction's width ladder.
328    #[must_use]
329    pub fn string_width(&self, bytes: &[u8]) -> f32 {
330        self.decode(bytes)
331            .map(|item| self.char_width(item.code))
332            .sum()
333    }
334
335    /// The typographic ascent, truncated to an integer as the C++ stores it.
336    #[must_use]
337    pub fn type_ascent(&self) -> i32 {
338        truncate(self.ascent())
339    }
340
341    /// The typographic descent, truncated to an integer, normally negative.
342    #[must_use]
343    pub fn type_descent(&self) -> i32 {
344        truncate(self.descent())
345    }
346
347    /// The CID a character code maps to, for a composite font only.
348    #[must_use]
349    pub fn cid_from_charcode(&self, code: CharCode) -> Option<Cid> {
350        match self {
351            Self::Type0(f) => Some(f.cid_from_charcode(code)),
352            Self::Simple(_) | Self::Type3(_) => None,
353        }
354    }
355
356    /// The vertical origin of a character code, in 1000/em units, for a
357    /// composite font only.
358    #[must_use]
359    pub fn vert_origin(&self, code: CharCode) -> Option<(f32, f32)> {
360        match self {
361            Self::Type0(f) => Some(f.vert_origin(code)),
362            Self::Simple(_) | Self::Type3(_) => None,
363        }
364    }
365
366    /// The vertical advance of a character code, in 1000/em units, for a
367    /// composite font only. Normally negative.
368    #[must_use]
369    pub fn vert_width(&self, code: CharCode) -> Option<f32> {
370        match self {
371            Self::Type0(f) => Some(f.vert_width(code)),
372            Self::Simple(_) | Self::Type3(_) => None,
373        }
374    }
375
376    /// The Unicode a character code stands for, `/ToUnicode` first.
377    #[must_use]
378    pub fn unicode_from_charcode(&self, code: CharCode) -> SmallVec<[char; 2]> {
379        match self {
380            Self::Simple(f) => f.unicode_from_charcode(code),
381            Self::Type0(f) => f.unicode_from_charcode(code),
382            Self::Type3(f) => f.unicode_from_charcode(code),
383        }
384    }
385
386    /// The character code that produces `unicode`, or `None`.
387    ///
388    /// The inverse of [`unicode_from_charcode`](Self::unicode_from_charcode),
389    /// and the direction appearance generation needs: to *write* a string with
390    /// a font the document already carries, a caller has to turn characters
391    /// back into the codes that font understands.
392    ///
393    /// `None` means the font cannot express that character at all, which is
394    /// the caller's signal to pick a different font rather than to emit a
395    /// code that will draw the wrong glyph.
396    ///
397    /// ```
398    /// use pdfrum_common::{Diagnostics, Limits};
399    /// use pdfrum_font::{CharCode, Font, FontCache, StandardFont};
400    ///
401    /// let font = Font::load_standard(StandardFont::Helvetica, &FontCache::new());
402    /// assert_eq!(font.char_code_from_unicode('A'), Some(CharCode(u32::from(b'A'))));
403    /// // A character no Latin encoding carries.
404    /// assert_eq!(font.char_code_from_unicode('\u{4e00}'), None);
405    /// # let _ = (Diagnostics::default(), Limits::default());
406    /// ```
407    #[must_use]
408    pub fn char_code_from_unicode(&self, unicode: char) -> Option<CharCode> {
409        match self {
410            Self::Simple(f) => f.char_code_from_unicode(unicode),
411            Self::Type0(f) => {
412                let code = f.charcode_from_unicode(unicode);
413                (code.0 != 0).then_some(code)
414            }
415            Self::Type3(f) => f.char_code_from_unicode(unicode),
416        }
417    }
418
419    /// Append one character code to a string being built, in the font's own
420    /// byte encoding.
421    ///
422    /// A simple font writes one byte; a composite font writes as many as its
423    /// CMap's codespace says, which is the whole reason this is a method
424    /// rather than a cast at the call site. Pairs with
425    /// [`char_code_from_unicode`](Self::char_code_from_unicode) to turn text
426    /// into a string a content stream can show.
427    ///
428    /// ```
429    /// use pdfrum_font::{CharCode, Font, FontCache, StandardFont};
430    ///
431    /// let font = Font::load_standard(StandardFont::Helvetica, &FontCache::new());
432    /// let mut out = Vec::new();
433    /// for ch in "Hi".chars() {
434    ///     if let Some(code) = font.char_code_from_unicode(ch) {
435    ///         font.append_char(&mut out, code);
436    ///     }
437    /// }
438    /// assert_eq!(out, b"Hi");
439    /// ```
440    pub fn append_char(&self, out: &mut Vec<u8>, code: CharCode) {
441        match self {
442            // A composite font's codespace decides the width, so only its
443            // CMap can encode a code correctly.
444            Self::Type0(f) => f.cmap.append_char(out, code),
445            Self::Simple(_) | Self::Type3(_) => out.push((code.0 & 0xff) as u8),
446        }
447    }
448
449    /// Build one of the fourteen standard fonts, with no document behind it.
450    ///
451    /// Every reader must supply these faces, so a caller that needs to draw
452    /// text of its own — an annotation's appearance stream, say — can have one
453    /// without inventing a font dictionary. The result is exactly what
454    /// synthesizing `/Type /Font /Subtype /Type1 /BaseFont <name> /Encoding
455    /// /WinAnsiEncoding` would produce, which is how PDFium's own stock-font
456    /// path builds them.
457    ///
458    /// ```
459    /// use pdfrum_font::{CharCode, Font, FontCache, StandardFont};
460    ///
461    /// let cache = FontCache::new();
462    /// let helvetica = Font::load_standard(StandardFont::Helvetica, &cache);
463    /// assert_eq!(helvetica.base_font_name(), b"Helvetica");
464    ///
465    /// // The Couriers are fixed-pitch: every glyph is 600 units wide.
466    /// let courier = Font::load_standard(StandardFont::Courier, &cache);
467    /// assert_eq!(courier.char_width(CharCode(u32::from(b'i'))), 600.0);
468    /// assert_eq!(courier.char_width(CharCode(u32::from(b'W'))), 600.0);
469    /// ```
470    #[must_use]
471    pub fn load_standard(which: StandardFont, cache: &FontCache) -> Self {
472        let dict = Dict::from_pairs([
473            (
474                names::TYPE.clone(),
475                pdfrum_object::Object::Name(names::FONT.clone()),
476            ),
477            (
478                names::SUBTYPE.clone(),
479                pdfrum_object::Object::Name(pdfrum_object::Name::from("Type1")),
480            ),
481            (
482                names::BASE_FONT.clone(),
483                pdfrum_object::Object::Name(pdfrum_object::Name::from(subst::canonical_font_name(
484                    which,
485                ))),
486            ),
487            (
488                names::ENCODING.clone(),
489                pdfrum_object::Object::Name(names::WIN_ANSI_ENCODING.clone()),
490            ),
491        ]);
492        Self::Simple(Box::new(simple::load(
493            &dict,
494            &pdfrum_object::NoResolve,
495            cache,
496            &SubstitutionOptions::default(),
497            &Limits::default(),
498            &mut Diagnostics::with_limit(0),
499            false,
500        )))
501    }
502
503    pub(crate) fn glyphs(&self) -> &GlyphSource {
504        match self {
505            Self::Simple(f) => &f.glyphs,
506            Self::Type0(f) => &f.glyphs,
507            Self::Type3(_) => &GlyphSource::None,
508        }
509    }
510}
511
512/// The [`Font::decode`] iterator.
513struct Decoder<'a> {
514    font: &'a Font,
515    bytes: &'a [u8],
516    offset: usize,
517}
518
519impl Iterator for Decoder<'_> {
520    type Item = CharItem;
521
522    fn next(&mut self) -> Option<CharItem> {
523        if self.offset >= self.bytes.len() {
524            return None;
525        }
526        Some(match self.font {
527            Font::Simple(f) => {
528                let byte = *self.bytes.get(self.offset)?;
529                self.offset += 1;
530                f.char_item(CharCode(u32::from(byte)))
531            }
532            Font::Type3(f) => {
533                let byte = *self.bytes.get(self.offset)?;
534                self.offset += 1;
535                f.char_item(CharCode(u32::from(byte)))
536            }
537            Font::Type0(f) => {
538                // Only the CMap knows how wide this code is, and a truncated
539                // code yields code 0 with the offset left unmoved — which
540                // would loop forever, so a stalled offset ends iteration.
541                let before = self.offset;
542                let code = f.cmap.next_char(self.bytes, &mut self.offset);
543                if self.offset <= before {
544                    return None;
545                }
546                f.char_item(code)
547            }
548        })
549    }
550}
551
552/// A metric truncated toward zero, which is how the C++ stores ascent and
553/// descent: it reads them into `int` fields at load time, so every consumer
554/// sees the truncation rather than the declared float.
555fn truncate(value: f32) -> i32 {
556    #[expect(
557        clippy::cast_possible_truncation,
558        reason = "the saturating cast is the point: a metric outside i32 is nonsense"
559    )]
560    let truncated = value.trunc() as i32;
561    truncated
562}
563
564/// Per-document caches: loaded fonts, and the font-identity counter.
565///
566/// A value the document owns rather than process-wide state, so two documents
567/// loaded on two threads never share a face or a font identity. `Send + Sync`
568/// and shared by `Arc`, so every session over one document — every worker of
569/// a parallel render, and a text run and a render run alike — loads each font
570/// once between them rather than once each.
571///
572/// # What is cached, and what is not
573///
574/// The key is the [`ObjRef`] that named the `/Font` resource. A font
575/// dictionary written **inline**, with no reference of its own, is not cached
576/// and is loaded afresh at every use: two inline copies genuinely are two
577/// fonts, and there is no document-scoped identity to key them on.
578///
579/// The value is an `Arc<Font>`, so a hit shares the whole loaded font — its
580/// parsed `/ToUnicode`, its CID tables and its glyph cache — rather than
581/// rebuilding them. Text extraction's duplicate suppression compares fonts by
582/// that pointer, so sharing is load-bearing for correctness as well as speed.
583///
584/// A dictionary that would not load caches its `None` too: that is as stable
585/// an answer as a font, and re-deriving it per page is the same wasted work.
586///
587/// # Why the substitution options are not part of the key
588///
589/// Every load under one document must make the same substitution choice — a
590/// substitution that varied between two `Tf` operators naming the same
591/// resource would give one line of text different metrics from the next — so
592/// a cache is created for one set of options and used with those. The caller
593/// that owns the options owns the cache: `pdfrum_page::BuildContext` carries
594/// both, in one value, and hands this out by `Arc`.
595#[derive(Debug, Default)]
596pub struct FontCache {
597    next_id: AtomicU64,
598    /// Loaded fonts, keyed on the reference that named them.
599    loaded: RwLock<HashMap<ObjRef, Option<Arc<Font>>>>,
600}
601
602impl FontCache {
603    /// A fresh cache.
604    #[must_use]
605    pub fn new() -> Self {
606        Self::default()
607    }
608
609    /// The font `reference` names, loading it on the first ask and sharing it
610    /// on every later one.
611    ///
612    /// `load` runs at most once per reference per cache in the uncontended
613    /// case, and never under the lock — two threads asking for two different
614    /// fonts do not serialize on each other. Two threads racing on the *same*
615    /// reference may both load; whichever inserts first is the shared
616    /// instance and both callers get that one `Arc`, so the loser's copy is
617    /// dropped rather than replacing an instance another page already holds.
618    /// That costs one duplicate parse and keeps the loader off the lock.
619    pub fn get_or_load<F>(&self, reference: ObjRef, load: F) -> Option<Arc<Font>>
620    where
621        F: FnOnce() -> Option<Font>,
622    {
623        if let Ok(map) = self.loaded.read()
624            && let Some(hit) = map.get(&reference)
625        {
626            return hit.clone();
627        }
628        let font = load().map(Arc::new);
629        match self.loaded.write() {
630            Ok(mut map) => map.entry(reference).or_insert(font).clone(),
631            // A poisoned lock means another thread panicked mid-load. The
632            // font itself is fine; hand it back uncached rather than panic.
633            Err(_) => font,
634        }
635    }
636
637    /// Hand out the next font identity.
638    pub(crate) fn next_id(&self) -> FontId {
639        FontId(self.next_id.fetch_add(1, Ordering::Relaxed))
640    }
641}
642
643/// Build a [`Font`] from a `/Font` resource dictionary.
644///
645/// Never panics; damage goes to `diags`. Returns `None` only for the four
646/// unrecoverable Type0 cases — every other font kind always constructs, even
647/// with no program and no glyphs at all.
648///
649/// The dispatch has one quirk worth knowing about: a `/TrueType` font whose
650/// `/BaseFont` begins with one of five GBK-encoded Chinese family names, and
651/// which carries no `/FontFile2`, is built as a **CID font** instead. Real
652/// files depend on it.
653#[must_use]
654pub fn load(
655    dict: &Dict,
656    r: &impl Resolve,
657    cache: &FontCache,
658    limits: &Limits,
659    diags: &mut Diagnostics,
660) -> Option<Font> {
661    load_with_options(
662        dict,
663        r,
664        cache,
665        &SubstitutionOptions::default(),
666        limits,
667        diags,
668    )
669}
670
671/// [`load`], with control over how substitution finds system faces.
672#[must_use]
673pub fn load_with_options(
674    dict: &Dict,
675    r: &impl Resolve,
676    cache: &FontCache,
677    opts: &SubstitutionOptions,
678    limits: &Limits,
679    diags: &mut Diagnostics,
680) -> Option<Font> {
681    let subtype = dict.name(names::SUBTYPE).map(|n| n.as_bytes().to_vec());
682    match subtype.as_deref() {
683        Some(b"Type3") => Some(Font::Type3(Box::new(type3::load(
684            dict, r, cache, limits, diags,
685        )))),
686        Some(b"Type0") => cid::load(dict, r, cache, opts, limits, diags)
687            .ok()
688            .map(|f| Font::Type0(Box::new(f))),
689        Some(b"TrueType") if wants_chinese_cid_rescue(dict, r) => {
690            // The GBK-name rescue: build it as a CID font, which then takes
691            // its own `/Subtype == TrueType` path and loads GBK-EUC-H.
692            match cid::load_gb2312(dict, r, cache, opts, limits, diags) {
693                Ok(f) => Some(Font::Type0(Box::new(f))),
694                // A matched-but-unusable font still falls through to the
695                // ordinary TrueType path, as the C++'s `if (!font)` guard does.
696                Err(_) => Some(Font::Simple(Box::new(simple::load(
697                    dict, r, cache, opts, limits, diags, true,
698                )))),
699            }
700        }
701        Some(b"TrueType") => Some(Font::Simple(Box::new(simple::load(
702            dict, r, cache, opts, limits, diags, true,
703        )))),
704        // Everything else — `/Type1`, `/MMType1`, a missing `/Subtype`, and
705        // outright garbage — is a Type 1 font.
706        _ => Some(Font::Simple(Box::new(simple::load(
707            dict, r, cache, opts, limits, diags, false,
708        )))),
709    }
710}
711
712/// The five GBK-encoded family names that reroute a `/TrueType` font to the
713/// CID loader, compared against `/BaseFont`'s **first four bytes**.
714///
715/// 宋体 (SimSun), 楷体 (KaiTi), 黑体 (HeiTi), 仿宋 (FangSong), 新宋 (XinSong).
716const CHINESE_FONT_NAMES: [[u8; 4]; 5] = [
717    [0xcb, 0xce, 0xcc, 0xe5],
718    [0xbf, 0xac, 0xcc, 0xe5],
719    [0xba, 0xda, 0xcc, 0xe5],
720    [0xb7, 0xc2, 0xcb, 0xce],
721    [0xd0, 0xc2, 0xcb, 0xce],
722];
723
724pub(crate) fn wants_chinese_cid_rescue(dict: &Dict, r: &impl Resolve) -> bool {
725    let Some(base) = dict.name(names::BASE_FONT) else {
726        return false;
727    };
728    let Some(prefix) = base.as_bytes().get(..4) else {
729        return false;
730    };
731    if !CHINESE_FONT_NAMES.iter().any(|n| n == prefix) {
732        return false;
733    }
734    // Only when there is nothing to draw with: a descriptor carrying a real
735    // TrueType program keeps the ordinary path.
736    match dict.dict(names::FONT_DESCRIPTOR, r) {
737        None => true,
738        Some(desc) => desc.raw(names::FONT_FILE2).is_none(),
739    }
740}