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}