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, SynthGlyph};
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    /// The synthetic italic and embolden the glyph-*bitmap* side applies,
122    /// resolved against the device matrix's two horizontal components.
123    ///
124    /// The bitmap side is a second call site with its *own* two levels
125    /// (`CFX_Face::RenderGlyph`, `cfx_face.cpp:769-778` and `:806-816`), not
126    /// the path side's, which is why it cannot simply reuse the outline the
127    /// glyph cache already adjusted: the render-path embolden level depends on
128    /// the *device* matrix, which only the renderer knows, and the render-path
129    /// skew is the effective one rather than the plain one.
130    ///
131    /// `xx` and `xy` are the oracle's own 16.16 quantities —
132    /// `matrix.a / 64 * 65536` and `matrix.c / 64 * 65536`
133    /// (`cfx_face.cpp:766-767`) — because the embolden table's `/ 36655` is
134    /// calibrated to that scale and nothing else. The [`SynthGlyph`] that
135    /// comes back is therefore in **device pixels**, and belongs on an outline
136    /// already mapped into that space.
137    ///
138    /// `None` where the C++ returns a negative level and `RenderGlyph` bails
139    /// out with a null bitmap (`cfx_face.cpp:809-811`): a substitution weight
140    /// of 1400 or more, which is past the table. A caller draws nothing.
141    #[must_use]
142    pub fn render_synth(&self, xx: i32, xy: i32) -> Option<SynthGlyph> {
143        let Some(subst) = self.subst() else {
144            return Some(SynthGlyph::NONE);
145        };
146        let is_cid = matches!(self, Self::Type0(_));
147        let level = subst.embolden_level_for_render(is_cid, xx, xy)?;
148        Some(SynthGlyph {
149            skew: subst.effective_skew(is_cid),
150            vertical: self.is_vertical(),
151            // The level is a strength in the 26.6 units the transformed
152            // outline is loaded in, so it becomes device pixels by /64 —
153            // which is the space the caller has already mapped the outline
154            // into by the time it asks.
155            embolden: f64::from(level) / 64.0,
156        })
157    }
158
159    /// Is this a vertical-writing font? Only a Type0 font with a `-V` CMap is.
160    #[must_use]
161    pub fn is_vertical(&self) -> bool {
162        match self {
163            Self::Type0(f) => f.cmap.is_vertical(),
164            Self::Simple(_) | Self::Type3(_) => false,
165        }
166    }
167
168    /// Does the font carry its own program, rather than being substituted?
169    ///
170    /// Consulted far more widely than it looks: PDFium's per-glyph fallback,
171    /// its glyph-spacing heuristic and its all-caps aliasing all branch on it,
172    /// and a program that *failed to parse* counts as not embedded.
173    #[must_use]
174    pub fn is_embedded(&self) -> bool {
175        match self {
176            Self::Simple(f) => f.embedded,
177            Self::Type0(f) => f.embedded,
178            Self::Type3(_) => false,
179        }
180    }
181
182    /// Whether character codes can be turned into Unicode at all, which text
183    /// extraction uses to decide a font is worth reading.
184    #[must_use]
185    pub fn is_unicode_compatible(&self) -> bool {
186        match self {
187            Self::Simple(f) => {
188                f.to_unicode.is_some() || f.encoding_kind != encoding::FontEncoding::Builtin
189            }
190            Self::Type0(f) => f.is_unicode_compatible(),
191            Self::Type3(f) => f.to_unicode.is_some(),
192        }
193    }
194
195    /// The font bounding box in 1000/em text space, after the derivation of
196    /// the former working note has filled in whatever the PDF failed to declare.
197    #[must_use]
198    pub fn font_bbox(&self) -> Rect {
199        match self {
200            Self::Simple(f) => f.descriptor.font_bbox,
201            Self::Type0(f) => f.descriptor.font_bbox,
202            Self::Type3(f) => f.font_bbox,
203        }
204    }
205
206    /// The ascent in 1000/em text space.
207    #[must_use]
208    pub fn ascent(&self) -> f32 {
209        match self {
210            Self::Simple(f) => f.descriptor.ascent,
211            Self::Type0(f) => f.descriptor.ascent,
212            Self::Type3(_) => 0.0,
213        }
214    }
215
216    /// The descent in 1000/em text space, normally negative.
217    #[must_use]
218    pub fn descent(&self) -> f32 {
219        match self {
220            Self::Simple(f) => f.descriptor.descent,
221            Self::Type0(f) => f.descriptor.descent,
222            Self::Type3(_) => 0.0,
223        }
224    }
225
226    /// The Type3 font, when this is one. Its glyph procedures are raw
227    /// `/CharProcs` streams that `pdfrum-page` executes.
228    #[must_use]
229    pub fn type3(&self) -> Option<&Type3Font> {
230        match self {
231            Self::Type3(f) => Some(f),
232            Self::Simple(_) | Self::Type0(_) => None,
233        }
234    }
235
236    /// The base font name, with any subset prefix already stripped.
237    #[must_use]
238    pub fn base_font_name(&self) -> &[u8] {
239        match self {
240            Self::Simple(f) => &f.base_font_name,
241            Self::Type0(f) => &f.base_font_name,
242            Self::Type3(_) => b"",
243        }
244    }
245
246    /// This font's identity within a [`FontCache`], for glyph-cache keys.
247    #[must_use]
248    pub fn id(&self) -> FontId {
249        match self {
250            Self::Simple(f) => f.id,
251            Self::Type0(f) => f.id,
252            Self::Type3(f) => f.id,
253        }
254    }
255
256    /// The substitution record, when the font was substituted rather than
257    /// embedded. Carries the synthetic skew and embolden levels a renderer
258    /// applies.
259    #[must_use]
260    pub fn subst(&self) -> Option<&SubstFont> {
261        match self {
262            Self::Simple(f) => f.subst.as_ref(),
263            Self::Type0(f) => f.subst.as_ref(),
264            Self::Type3(_) => None,
265        }
266    }
267
268    /// The width of one character code in 1000/em text space.
269    #[must_use]
270    pub fn char_width(&self, code: CharCode) -> f32 {
271        match self {
272            Self::Simple(f) => f.char_width(code),
273            Self::Type0(f) => f.char_width(code),
274            Self::Type3(f) => f.char_width(code),
275        }
276    }
277
278    /// Whether the PDF itself declared the advance widths this font reports.
279    ///
280    /// False only for a **simple** font with no `/Widths` array, where every
281    /// width already comes from the face and comparing the two would be
282    /// comparing a number against itself. A composite font always answers
283    /// true: its `/W` array defaults to `/DW` rather than to the face.
284    ///
285    /// Read by the glyph-spacing correction (`applies_glyph_spacing`), which
286    /// only means anything when the document's widths and the face's disagree.
287    #[must_use]
288    pub(crate) fn has_declared_widths(&self) -> bool {
289        match self {
290            Self::Simple(f) => f.has_font_widths(),
291            Self::Type0(_) | Self::Type3(_) => true,
292        }
293    }
294
295    /// Whether this font's glyphs take the glyph-spacing correction.
296    ///
297    /// Reads the font's five relevant facts and asks the glyph-spacing
298    /// rule, where the reasoning lives.
299    #[must_use]
300    pub fn applies_glyph_spacing(&self) -> bool {
301        subst::applies_glyph_spacing(&subst::GlyphSpacingGate {
302            vertical: self.is_vertical(),
303            embedded: self.is_embedded(),
304            declared_widths: self.has_declared_widths(),
305            base_font_name: self.base_font_name(),
306            subst: self.subst(),
307        })
308    }
309
310    /// One glyph's own advance width, in 1000/em units, as the *face* declares
311    /// it — not as the PDF does.
312    ///
313    /// Zero when there is no face or the glyph has no advance, which callers
314    /// treat as "unknown" rather than as a genuine zero-width glyph.
315    #[must_use]
316    pub fn glyph_advance(&self, gid: Gid) -> i32 {
317        self.glyphs().advance(gid, glyphs::GlyphParams::default())
318    }
319
320    /// The bounding box of one character code's glyph, in 1000/em text space
321    /// and **y-up**: `Rect::new(left, bottom, right, top)` with
322    /// `bottom <= top`.
323    ///
324    /// `Rect::ZERO` when there is no glyph, and always for a Type 3 font,
325    /// whose glyph boxes are a property of the content streams the page layer
326    /// executes rather than of the font.
327    ///
328    /// Text extraction reads this per code — not per decoded string — when it
329    /// builds a character's tight box and when its width ladder has run out of
330    /// better answers.
331    #[must_use]
332    pub fn char_bbox(&self, code: CharCode) -> Rect {
333        match self {
334            Self::Simple(f) => f.char_bbox(code),
335            Self::Type0(f) => f.char_bbox(code),
336            Self::Type3(_) => Rect::ZERO,
337        }
338    }
339
340    /// The Adobe-Japan1 per-CID transform a character takes, when one applies.
341    ///
342    /// Only a **non-embedded** Japan1 CID font has one, and only for the
343    /// hundred and fifty-four CIDs the table lists. It moves the glyph within
344    /// its em box without touching the advance, so a renderer applies it to
345    /// the drawing origin alone — the pen walks on as if it were not there.
346    ///
347    /// Non-Japan1, embedded and non-CID fonts all answer `None` — those three
348    /// tests are the whole gate, and there is no fourth.
349    #[must_use]
350    pub fn japan1_transform(&self, code: CharCode) -> Option<CidTransform> {
351        match self {
352            Self::Type0(f) => f.japan1_transform(code),
353            Self::Simple(_) | Self::Type3(_) => None,
354        }
355    }
356
357    /// The width of a string of character codes, decoded through this font's
358    /// own encoding and summed.
359    ///
360    /// Not the same as summing [`char_width`](Self::char_width) over the codes
361    /// a caller already has: the string is re-decoded, so a code that does not
362    /// round-trip through [`append_char`](Self::append_char) — a simple font's
363    /// code above 255, say — comes back as a *different* code and contributes
364    /// a different width. That difference is the whole point of the rung this
365    /// serves in text extraction's width ladder.
366    #[must_use]
367    pub fn string_width(&self, bytes: &[u8]) -> f32 {
368        self.decode(bytes)
369            .map(|item| self.char_width(item.code))
370            .sum()
371    }
372
373    /// The typographic ascent, truncated to an integer as the C++ stores it.
374    #[must_use]
375    pub fn type_ascent(&self) -> i32 {
376        truncate(self.ascent())
377    }
378
379    /// The typographic descent, truncated to an integer, normally negative.
380    #[must_use]
381    pub fn type_descent(&self) -> i32 {
382        truncate(self.descent())
383    }
384
385    /// The CID a character code maps to, for a composite font only.
386    #[must_use]
387    pub fn cid_from_charcode(&self, code: CharCode) -> Option<Cid> {
388        match self {
389            Self::Type0(f) => Some(f.cid_from_charcode(code)),
390            Self::Simple(_) | Self::Type3(_) => None,
391        }
392    }
393
394    /// The vertical origin of a character code, in 1000/em units, for a
395    /// composite font only.
396    #[must_use]
397    pub fn vert_origin(&self, code: CharCode) -> Option<(f32, f32)> {
398        match self {
399            Self::Type0(f) => Some(f.vert_origin(code)),
400            Self::Simple(_) | Self::Type3(_) => None,
401        }
402    }
403
404    /// The vertical advance of a character code, in 1000/em units, for a
405    /// composite font only. Normally negative.
406    #[must_use]
407    pub fn vert_width(&self, code: CharCode) -> Option<f32> {
408        match self {
409            Self::Type0(f) => Some(f.vert_width(code)),
410            Self::Simple(_) | Self::Type3(_) => None,
411        }
412    }
413
414    /// The Unicode a character code stands for, `/ToUnicode` first.
415    #[must_use]
416    pub fn unicode_from_charcode(&self, code: CharCode) -> SmallVec<[char; 2]> {
417        match self {
418            Self::Simple(f) => f.unicode_from_charcode(code),
419            Self::Type0(f) => f.unicode_from_charcode(code),
420            Self::Type3(f) => f.unicode_from_charcode(code),
421        }
422    }
423
424    /// The character code that produces `unicode`, or `None`.
425    ///
426    /// The inverse of [`unicode_from_charcode`](Self::unicode_from_charcode),
427    /// and the direction appearance generation needs: to *write* a string with
428    /// a font the document already carries, a caller has to turn characters
429    /// back into the codes that font understands.
430    ///
431    /// `None` means the font cannot express that character at all, which is
432    /// the caller's signal to pick a different font rather than to emit a
433    /// code that will draw the wrong glyph.
434    ///
435    /// ```
436    /// use pdfrum_common::{Diagnostics, Limits};
437    /// use pdfrum_font::{CharCode, Font, FontCache, StandardFont};
438    ///
439    /// let font = Font::load_standard(StandardFont::Helvetica, &FontCache::new());
440    /// assert_eq!(font.char_code_from_unicode('A'), Some(CharCode(u32::from(b'A'))));
441    /// // A character no Latin encoding carries.
442    /// assert_eq!(font.char_code_from_unicode('\u{4e00}'), None);
443    /// # let _ = (Diagnostics::default(), Limits::default());
444    /// ```
445    #[must_use]
446    pub fn char_code_from_unicode(&self, unicode: char) -> Option<CharCode> {
447        match self {
448            Self::Simple(f) => f.char_code_from_unicode(unicode),
449            Self::Type0(f) => {
450                let code = f.charcode_from_unicode(unicode);
451                (code.0 != 0).then_some(code)
452            }
453            Self::Type3(f) => f.char_code_from_unicode(unicode),
454        }
455    }
456
457    /// Append one character code to a string being built, in the font's own
458    /// byte encoding.
459    ///
460    /// A simple font writes one byte; a composite font writes as many as its
461    /// CMap's codespace says, which is the whole reason this is a method
462    /// rather than a cast at the call site. Pairs with
463    /// [`char_code_from_unicode`](Self::char_code_from_unicode) to turn text
464    /// into a string a content stream can show.
465    ///
466    /// ```
467    /// use pdfrum_font::{CharCode, Font, FontCache, StandardFont};
468    ///
469    /// let font = Font::load_standard(StandardFont::Helvetica, &FontCache::new());
470    /// let mut out = Vec::new();
471    /// for ch in "Hi".chars() {
472    ///     if let Some(code) = font.char_code_from_unicode(ch) {
473    ///         font.append_char(&mut out, code);
474    ///     }
475    /// }
476    /// assert_eq!(out, b"Hi");
477    /// ```
478    pub fn append_char(&self, out: &mut Vec<u8>, code: CharCode) {
479        match self {
480            // A composite font's codespace decides the width, so only its
481            // CMap can encode a code correctly.
482            Self::Type0(f) => f.cmap.append_char(out, code),
483            Self::Simple(_) | Self::Type3(_) => out.push((code.0 & 0xff) as u8),
484        }
485    }
486
487    /// Build one of the fourteen standard fonts, with no document behind it.
488    ///
489    /// Every reader must supply these faces, so a caller that needs to draw
490    /// text of its own — an annotation's appearance stream, say — can have one
491    /// without inventing a font dictionary. The result is exactly what
492    /// synthesizing `/Type /Font /Subtype /Type1 /BaseFont <name> /Encoding
493    /// /WinAnsiEncoding` would produce, which is how PDFium's own stock-font
494    /// path builds them.
495    ///
496    /// ```
497    /// use pdfrum_font::{CharCode, Font, FontCache, StandardFont};
498    ///
499    /// let cache = FontCache::new();
500    /// let helvetica = Font::load_standard(StandardFont::Helvetica, &cache);
501    /// assert_eq!(helvetica.base_font_name(), b"Helvetica");
502    ///
503    /// // The Couriers are fixed-pitch: every glyph is 600 units wide.
504    /// let courier = Font::load_standard(StandardFont::Courier, &cache);
505    /// assert_eq!(courier.char_width(CharCode(u32::from(b'i'))), 600.0);
506    /// assert_eq!(courier.char_width(CharCode(u32::from(b'W'))), 600.0);
507    /// ```
508    #[must_use]
509    pub fn load_standard(which: StandardFont, cache: &FontCache) -> Self {
510        let dict = Dict::from_pairs([
511            (
512                names::TYPE.clone(),
513                pdfrum_object::Object::Name(names::FONT.clone()),
514            ),
515            (
516                names::SUBTYPE.clone(),
517                pdfrum_object::Object::Name(pdfrum_object::Name::from("Type1")),
518            ),
519            (
520                names::BASE_FONT.clone(),
521                pdfrum_object::Object::Name(pdfrum_object::Name::from(subst::canonical_font_name(
522                    which,
523                ))),
524            ),
525            (
526                names::ENCODING.clone(),
527                pdfrum_object::Object::Name(names::WIN_ANSI_ENCODING.clone()),
528            ),
529        ]);
530        Self::Simple(Box::new(simple::load(
531            &dict,
532            &pdfrum_object::NoResolve,
533            cache,
534            &SubstitutionOptions::default(),
535            &Limits::default(),
536            &mut Diagnostics::with_limit(0),
537            false,
538        )))
539    }
540
541    pub(crate) fn glyphs(&self) -> &GlyphSource {
542        match self {
543            Self::Simple(f) => &f.glyphs,
544            Self::Type0(f) => &f.glyphs,
545            Self::Type3(_) => &GlyphSource::None,
546        }
547    }
548
549    /// Whether this character's glyph is drawn from this font (`ShouldUseFont`).
550    ///
551    /// A Type 3 font has no glyph indices and never takes `GetCharPosList`, so
552    /// it always answers true: there is no Arial stand-in for a content stream.
553    #[must_use]
554    pub fn should_use_own_glyph(&self, gid: Option<Gid>) -> bool {
555        match self {
556            Self::Type3(_) => true,
557            Self::Simple(f) => crate::fallback::should_use_own_glyph(
558                f.embedded,
559                f.is_truetype,
560                f.to_unicode.is_some(),
561                gid,
562            ),
563            Self::Type0(f) => crate::fallback::should_use_own_glyph(
564                f.embedded,
565                false,
566                f.to_unicode.is_some(),
567                gid,
568            ),
569        }
570    }
571
572    /// The Arial stand-in `GetCharPosList` draws when
573    /// [`Self::should_use_own_glyph`] fails. Created on first miss; `None` if
574    /// even Arial failed to load.
575    #[must_use]
576    pub fn glyph_fallback(&self) -> Option<&crate::GlyphFallback> {
577        match self {
578            Self::Type3(_) => None,
579            Self::Simple(f) => crate::fallback::ensure(
580                &f.fallback,
581                f.id,
582                f.is_truetype,
583                f.descriptor.flags,
584                f.descriptor.stem_v,
585                f.descriptor.italic_angle,
586                false,
587            ),
588            Self::Type0(f) => crate::fallback::ensure(
589                &f.fallback,
590                f.id,
591                false,
592                f.descriptor.flags,
593                f.descriptor.stem_v,
594                f.descriptor.italic_angle,
595                f.cmap.is_vertical(),
596            ),
597        }
598    }
599}
600
601/// The [`Font::decode`] iterator.
602struct Decoder<'a> {
603    font: &'a Font,
604    bytes: &'a [u8],
605    offset: usize,
606}
607
608impl Iterator for Decoder<'_> {
609    type Item = CharItem;
610
611    fn next(&mut self) -> Option<CharItem> {
612        if self.offset >= self.bytes.len() {
613            return None;
614        }
615        Some(match self.font {
616            Font::Simple(f) => {
617                let byte = *self.bytes.get(self.offset)?;
618                self.offset += 1;
619                f.char_item(CharCode(u32::from(byte)))
620            }
621            Font::Type3(f) => {
622                let byte = *self.bytes.get(self.offset)?;
623                self.offset += 1;
624                f.char_item(CharCode(u32::from(byte)))
625            }
626            Font::Type0(f) => {
627                // Only the CMap knows how wide this code is, and a truncated
628                // code yields code 0 with the offset left unmoved — which
629                // would loop forever, so a stalled offset ends iteration.
630                let before = self.offset;
631                let code = f.cmap.next_char(self.bytes, &mut self.offset);
632                if self.offset <= before {
633                    return None;
634                }
635                f.char_item(code)
636            }
637        })
638    }
639}
640
641/// A metric truncated toward zero, which is how the C++ stores ascent and
642/// descent: it reads them into `int` fields at load time, so every consumer
643/// sees the truncation rather than the declared float.
644fn truncate(value: f32) -> i32 {
645    #[expect(
646        clippy::cast_possible_truncation,
647        reason = "the saturating cast is the point: a metric outside i32 is nonsense"
648    )]
649    let truncated = value.trunc() as i32;
650    truncated
651}
652
653/// Per-document caches: loaded fonts, and the font-identity counter.
654///
655/// A value the document owns rather than process-wide state, so two documents
656/// loaded on two threads never share a face or a font identity. `Send + Sync`
657/// and shared by `Arc`, so every session over one document — every worker of
658/// a parallel render, and a text run and a render run alike — loads each font
659/// once between them rather than once each.
660///
661/// # What is cached, and what is not
662///
663/// The key is the [`ObjRef`] that named the `/Font` resource. A font
664/// dictionary written **inline**, with no reference of its own, is not cached
665/// and is loaded afresh at every use: two inline copies genuinely are two
666/// fonts, and there is no document-scoped identity to key them on.
667///
668/// The value is an `Arc<Font>`, so a hit shares the whole loaded font — its
669/// parsed `/ToUnicode`, its CID tables and its glyph cache — rather than
670/// rebuilding them. Text extraction's duplicate suppression compares fonts by
671/// that pointer, so sharing is load-bearing for correctness as well as speed.
672///
673/// A dictionary that would not load caches its `None` too: that is as stable
674/// an answer as a font, and re-deriving it per page is the same wasted work.
675///
676/// # Why the substitution options are not part of the key
677///
678/// Every load under one document must make the same substitution choice — a
679/// substitution that varied between two `Tf` operators naming the same
680/// resource would give one line of text different metrics from the next — so
681/// a cache is created for one set of options and used with those. The caller
682/// that owns the options owns the cache: `pdfrum_page::BuildContext` carries
683/// both, in one value, and hands this out by `Arc`.
684#[derive(Debug, Default)]
685pub struct FontCache {
686    next_id: AtomicU64,
687    /// Loaded fonts, keyed on the reference that named them.
688    loaded: RwLock<HashMap<ObjRef, Option<Arc<Font>>>>,
689}
690
691impl FontCache {
692    /// A fresh cache.
693    #[must_use]
694    pub fn new() -> Self {
695        Self::default()
696    }
697
698    /// The font `reference` names, loading it on the first ask and sharing it
699    /// on every later one.
700    ///
701    /// `load` runs at most once per reference per cache in the uncontended
702    /// case, and never under the lock — two threads asking for two different
703    /// fonts do not serialize on each other. Two threads racing on the *same*
704    /// reference may both load; whichever inserts first is the shared
705    /// instance and both callers get that one `Arc`, so the loser's copy is
706    /// dropped rather than replacing an instance another page already holds.
707    /// That costs one duplicate parse and keeps the loader off the lock.
708    pub fn get_or_load<F>(&self, reference: ObjRef, load: F) -> Option<Arc<Font>>
709    where
710        F: FnOnce() -> Option<Font>,
711    {
712        if let Ok(map) = self.loaded.read()
713            && let Some(hit) = map.get(&reference)
714        {
715            return hit.clone();
716        }
717        let font = load().map(Arc::new);
718        match self.loaded.write() {
719            Ok(mut map) => map.entry(reference).or_insert(font).clone(),
720            // A poisoned lock means another thread panicked mid-load. The
721            // font itself is fine; hand it back uncached rather than panic.
722            Err(_) => font,
723        }
724    }
725
726    /// Hand out the next font identity.
727    pub(crate) fn next_id(&self) -> FontId {
728        FontId(self.next_id.fetch_add(1, Ordering::Relaxed))
729    }
730}
731
732/// Build a [`Font`] from a `/Font` resource dictionary.
733///
734/// Never panics; damage goes to `diags`. Returns `None` only for the four
735/// unrecoverable Type0 cases — every other font kind always constructs, even
736/// with no program and no glyphs at all.
737///
738/// The dispatch has one quirk worth knowing about: a `/TrueType` font whose
739/// `/BaseFont` begins with one of five GBK-encoded Chinese family names, and
740/// which carries no `/FontFile2`, is built as a **CID font** instead. Real
741/// files depend on it.
742#[must_use]
743pub fn load(
744    dict: &Dict,
745    r: &impl Resolve,
746    cache: &FontCache,
747    limits: &Limits,
748    diags: &mut Diagnostics,
749) -> Option<Font> {
750    load_with_options(
751        dict,
752        r,
753        cache,
754        &SubstitutionOptions::default(),
755        limits,
756        diags,
757    )
758}
759
760/// [`load`], with control over how substitution finds system faces.
761#[must_use]
762pub fn load_with_options(
763    dict: &Dict,
764    r: &impl Resolve,
765    cache: &FontCache,
766    opts: &SubstitutionOptions,
767    limits: &Limits,
768    diags: &mut Diagnostics,
769) -> Option<Font> {
770    let subtype = dict.name(names::SUBTYPE).map(|n| n.as_bytes().to_vec());
771    match subtype.as_deref() {
772        Some(b"Type3") => Some(Font::Type3(Box::new(type3::load(
773            dict, r, cache, limits, diags,
774        )))),
775        Some(b"Type0") => cid::load(dict, r, cache, opts, limits, diags)
776            .ok()
777            .map(|f| Font::Type0(Box::new(f))),
778        Some(b"TrueType") if wants_chinese_cid_rescue(dict, r) => {
779            // The GBK-name rescue: build it as a CID font, which then takes
780            // its own `/Subtype == TrueType` path and loads GBK-EUC-H.
781            match cid::load_gb2312(dict, r, cache, opts, limits, diags) {
782                Ok(f) => Some(Font::Type0(Box::new(f))),
783                // A matched-but-unusable font still falls through to the
784                // ordinary TrueType path, as the C++'s `if (!font)` guard does.
785                Err(_) => Some(Font::Simple(Box::new(simple::load(
786                    dict, r, cache, opts, limits, diags, true,
787                )))),
788            }
789        }
790        Some(b"TrueType") => Some(Font::Simple(Box::new(simple::load(
791            dict, r, cache, opts, limits, diags, true,
792        )))),
793        // Everything else — `/Type1`, `/MMType1`, a missing `/Subtype`, and
794        // outright garbage — is a Type 1 font.
795        _ => Some(Font::Simple(Box::new(simple::load(
796            dict, r, cache, opts, limits, diags, false,
797        )))),
798    }
799}
800
801/// The five GBK-encoded family names that reroute a `/TrueType` font to the
802/// CID loader, compared against `/BaseFont`'s **first four bytes**.
803///
804/// 宋体 (SimSun), 楷体 (KaiTi), 黑体 (HeiTi), 仿宋 (FangSong), 新宋 (XinSong).
805const CHINESE_FONT_NAMES: [[u8; 4]; 5] = [
806    [0xcb, 0xce, 0xcc, 0xe5],
807    [0xbf, 0xac, 0xcc, 0xe5],
808    [0xba, 0xda, 0xcc, 0xe5],
809    [0xb7, 0xc2, 0xcb, 0xce],
810    [0xd0, 0xc2, 0xcb, 0xce],
811];
812
813pub(crate) fn wants_chinese_cid_rescue(dict: &Dict, r: &impl Resolve) -> bool {
814    let Some(base) = dict.name(names::BASE_FONT) else {
815        return false;
816    };
817    let Some(prefix) = base.as_bytes().get(..4) else {
818        return false;
819    };
820    if !CHINESE_FONT_NAMES.iter().any(|n| n == prefix) {
821        return false;
822    }
823    // Only when there is nothing to draw with: a descriptor carrying a real
824    // TrueType program keeps the ordinary path.
825    match dict.dict(names::FONT_DESCRIPTOR, r) {
826        None => true,
827        Some(desc) => desc.raw(names::FONT_FILE2).is_none(),
828    }
829}