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}