Skip to main content

docling_pdf/render/font/
mod.rs

1//! PDF fonts for the renderer: a font dictionary becomes a [`LoadedFont`]
2//! that splits strings into codes, gives each code its advance and its glyph
3//! outline in text space, or — for Type 3 — its glyph procedure.
4//!
5//! Glyph programs: TrueType / OpenType (`/FontFile2`, `/FontFile3 /OpenType`)
6//! and bare CFF (`/FontFile3 /Type1C`, `/CIDFontType0C`) through ttf-parser,
7//! Type 1 (`/FontFile`) through [`type1`]. Glyph selection follows ISO
8//! 32000-1 9.6.6 / 9.7.4: a simple font's code goes through `/Differences`,
9//! the base encoding or the program's built-in one to a glyph *name* (Type 1,
10//! CFF) or through the `cmap` subtables to a glyph *index* (TrueType — (3,0)
11//! symbol lookups with the `F0xx` convention, (1,0) by code, (3,1) by the
12//! name's Unicode, `post` names, and the bare code when the font has no
13//! `cmap`); a composite font's code goes through its CMap to a CID and
14//! `/CIDToGIDMap` or the CFF charset to a glyph. A font without a program is
15//! drawn with a host face chosen by [`fallback`].
16
17pub mod cmap;
18pub mod encodings;
19pub mod fallback;
20pub mod type1;
21
22use std::cell::RefCell;
23use std::collections::HashMap;
24use std::rc::Rc;
25use std::sync::Arc;
26
27use lopdf::{Dictionary, Document, Object, ObjectId};
28use ttf_parser::GlyphId;
29
30use super::geom::Mat;
31use super::objects::{
32    as_dict, as_stream, deref, get, get_dict, get_int, get_name, get_num, name, num, nums,
33};
34use cmap::CMap;
35
36/// A glyph outline in the program's own units with its advance.
37pub struct GlyphPath {
38    pub path: Option<tiny_skia::Path>,
39    pub advance: f64,
40}
41
42/// The program that draws the glyphs.
43enum Program {
44    /// TrueType / OpenType (glyf or CFF table): parsed per use, cheap.
45    Sfnt {
46        data: Vec<u8>,
47        /// glyph units → text space (1/unitsPerEm, or the CFF FontMatrix).
48        matrix: Mat,
49        /// CID → glyph index for a CID-keyed CFF inside the sfnt.
50        cid_to_gid: Option<HashMap<u16, u16>>,
51        has_cmap: bool,
52        has_glyf: bool,
53        /// Glyph names of a CFF-flavoured sfnt, for name lookups.
54        names: HashMap<String, u16>,
55    },
56    /// A bare CFF program.
57    Cff {
58        data: Vec<u8>,
59        matrix: Mat,
60        cid_to_gid: Option<HashMap<u16, u16>>,
61        /// glyph name → glyph index (ttf-parser's own lookup by name has
62        /// no answer for the predefined ISOAdobe/Expert charsets).
63        names: HashMap<String, u16>,
64    },
65    Type1 {
66        font: type1::Type1Font,
67        matrix: Mat,
68    },
69    /// A host face standing in for a non-embedded font.
70    Fallback {
71        face: Arc<fallback::FallbackFace>,
72        matrix: Mat,
73    },
74    None,
75}
76
77/// Type 3 glyph procedures.
78pub struct Type3 {
79    pub font_matrix: Mat,
80    pub char_procs: HashMap<String, ObjectId>,
81    pub resources: Option<Dictionary>,
82}
83
84pub struct LoadedFont {
85    pub composite: bool,
86    pub type3: Option<Type3>,
87    program: Program,
88    /// Simple fonts: code → glyph name from `/Encoding` (`/Differences` over
89    /// the base encoding), when the PDF gives one.
90    encoding_names: HashMap<u8, String>,
91    /// The base encoding was named explicitly (`/WinAnsiEncoding`, …).
92    has_base_encoding: bool,
93    /// The font is symbolic (descriptor flag 3) and the PDF names no encoding.
94    symbolic: bool,
95    /// Composite fonts: string → codes → CIDs.
96    cmap: Option<CMap>,
97    cid_to_gid: CidToGid,
98    /// code (simple) or CID (composite) → advance in 1/1000 text space.
99    widths: HashMap<u32, f64>,
100    default_width: Option<f64>,
101    /// Type 3: `/Widths` are in glyph space (through `FontMatrix`).
102    /// `ToUnicode`, for a fallback face's cmap lookups.
103    to_unicode: HashMap<u32, String>,
104    /// Standard-14 metrics for a non-embedded font without `/Widths`.
105    std14: Option<crate::std14::Std14Widths>,
106    /// Glyph cache: code/CID → outline in text space (font size 1).
107    cache: RefCell<HashMap<u32, Option<Rc<tiny_skia::Path>>>>,
108    advance_cache: RefCell<HashMap<u32, f64>>,
109    pub vertical: bool,
110    is_dingbats: bool,
111    is_symbol_face: bool,
112}
113
114enum CidToGid {
115    Identity,
116    Map(Vec<u8>),
117}
118
119fn descriptor<'a>(
120    doc: &'a Document,
121    fdict: &'a Dictionary,
122    composite: bool,
123) -> Option<&'a Dictionary> {
124    let owner = if composite {
125        descendant(doc, fdict)?
126    } else {
127        fdict
128    };
129    get_dict(doc, owner, b"FontDescriptor")
130}
131
132fn descendant<'a>(doc: &'a Document, fdict: &'a Dictionary) -> Option<&'a Dictionary> {
133    match get(doc, fdict, b"DescendantFonts")? {
134        Object::Array(a) => a.first().and_then(|o| as_dict(doc, o)),
135        Object::Dictionary(d) => Some(d),
136        _ => None,
137    }
138}
139
140fn base_font_name(doc: &Document, fdict: &Dictionary) -> String {
141    let raw = get_name(doc, fdict, b"BaseFont")
142        .map(|n| String::from_utf8_lossy(n).into_owned())
143        .unwrap_or_default();
144    // Strip an `ABCDEF+` subset prefix.
145    match raw.split_once('+') {
146        Some((pre, rest)) if pre.len() == 6 && pre.bytes().all(|b| b.is_ascii_uppercase()) => {
147            rest.to_string()
148        }
149        _ => raw,
150    }
151}
152
153impl LoadedFont {
154    pub fn load(doc: &Document, fdict: &Dictionary) -> LoadedFont {
155        let subtype = get_name(doc, fdict, b"Subtype").unwrap_or(b"");
156        let composite = subtype == b"Type0";
157        let base_font = base_font_name(doc, fdict);
158        let desc = descriptor(doc, fdict, composite);
159        let flags = desc.and_then(|d| get_int(doc, d, b"Flags"));
160        let symbolic_flag = flags.is_some_and(|f| f & 4 != 0 && f & 32 == 0);
161
162        // --- the embedded program -------------------------------------------
163        let mut program = Program::None;
164        if let Some(d) = desc {
165            if let Some(s) = d.get(b"FontFile2").ok().and_then(|o| as_stream(doc, o)) {
166                if let Ok(data) = s.decompressed_content() {
167                    program = sfnt_program(data).unwrap_or(Program::None);
168                }
169            }
170            if matches!(program, Program::None) {
171                if let Some(s) = d.get(b"FontFile3").ok().and_then(|o| as_stream(doc, o)) {
172                    if let Ok(data) = s.decompressed_content() {
173                        let st = get_name(doc, &s.dict, b"Subtype").unwrap_or(b"");
174                        program = if st == b"OpenType"
175                            || data.starts_with(b"OTTO")
176                            || data.starts_with(&[0, 1, 0, 0])
177                            || data.starts_with(b"true")
178                        {
179                            sfnt_program(data.clone())
180                                .or_else(|| cff_program(data))
181                                .unwrap_or(Program::None)
182                        } else {
183                            cff_program(data.clone())
184                                .or_else(|| sfnt_program(data))
185                                .unwrap_or(Program::None)
186                        };
187                    }
188                }
189            }
190            if matches!(program, Program::None) {
191                if let Some(s) = d.get(b"FontFile").ok().and_then(|o| as_stream(doc, o)) {
192                    if let Ok(data) = s.decompressed_content() {
193                        if let Some(f) = type1::Type1Font::parse(&data) {
194                            let m =
195                                Mat::from_slice(&f.font_matrix).unwrap_or(Mat::scale(0.001, 0.001));
196                            program = Program::Type1 { font: f, matrix: m };
197                        } else if let Some(p) =
198                            cff_program(data.clone()).or_else(|| sfnt_program(data))
199                        {
200                            // Mislabelled programs happen.
201                            program = p;
202                        }
203                    }
204                }
205            }
206        }
207
208        // --- Type 3 ----------------------------------------------------------
209        let type3 = if subtype == b"Type3" {
210            let fm = get(doc, fdict, b"FontMatrix")
211                .and_then(|o| nums(doc, o))
212                .and_then(|v| Mat::from_slice(&v))
213                .unwrap_or(Mat::scale(0.001, 0.001));
214            let mut char_procs = HashMap::new();
215            if let Some(cp) = get_dict(doc, fdict, b"CharProcs") {
216                for (k, v) in cp.iter() {
217                    if let Object::Reference(id) = v {
218                        char_procs.insert(String::from_utf8_lossy(k).into_owned(), *id);
219                    }
220                }
221            }
222            let resources = get_dict(doc, fdict, b"Resources").cloned();
223            Some(Type3 {
224                font_matrix: fm,
225                char_procs,
226                resources,
227            })
228        } else {
229            None
230        };
231
232        // --- encodings -----------------------------------------------------
233        let lower = base_font.to_ascii_lowercase();
234        let is_symbol_face = lower.starts_with("symbol");
235        let is_dingbats = lower.contains("dingbat");
236        let mut encoding_names: HashMap<u8, String> = HashMap::new();
237        let mut has_base_encoding = false;
238        let enc = get(doc, fdict, b"Encoding");
239        let mut cmap = None;
240        if composite {
241            cmap = Some(match enc {
242                Some(Object::Name(n)) => CMap::predefined(n),
243                Some(Object::Stream(s)) => {
244                    let mut c = s
245                        .decompressed_content()
246                        .ok()
247                        .map(|d| CMap::parse(&d))
248                        .unwrap_or_else(CMap::identity_h);
249                    if let Some(Object::Name(n)) = get(doc, &s.dict, b"UseCMap") {
250                        if n.starts_with(b"Identity") && !c.identity {
251                            // Codes not covered map to themselves.
252                        }
253                    }
254                    if get_int(doc, &s.dict, b"WMode") == Some(1) {
255                        c.vertical = true;
256                    }
257                    c
258                }
259                _ => CMap::identity_h(),
260            });
261        } else {
262            let base_table: Option<&'static [(u8, &'static str)]> = match enc {
263                Some(Object::Name(n)) => {
264                    has_base_encoding = true;
265                    encodings::by_name(n)
266                }
267                Some(Object::Dictionary(d)) => match get_name(doc, d, b"BaseEncoding") {
268                    Some(n) => {
269                        has_base_encoding = true;
270                        encodings::by_name(n)
271                    }
272                    None => None,
273                },
274                _ => None,
275            };
276            // The implicit base: Standard for non-symbolic fonts; the face's
277            // own encoding for Symbol/ZapfDingbats; nothing for other
278            // symbolic fonts (their program's built-in encoding applies).
279            let implicit: Option<&'static [(u8, &'static str)]> = if is_symbol_face
280                && matches!(program, Program::None)
281            {
282                Some(encodings::SYMBOL)
283            } else if is_dingbats && matches!(program, Program::None) {
284                Some(encodings::ZAPF_DINGBATS)
285            } else if !symbolic_flag || matches!(program, Program::None | Program::Fallback { .. })
286            {
287                Some(encodings::STANDARD)
288            } else {
289                None
290            };
291            if let Some(t) = base_table.or(implicit) {
292                for &(c, n) in t {
293                    encoding_names.insert(c, n.to_string());
294                }
295            }
296            if let Some(Object::Dictionary(d)) = enc {
297                if let Some(Object::Array(diffs)) = get(doc, d, b"Differences") {
298                    let mut code: i64 = 0;
299                    for el in diffs {
300                        match deref(doc, el) {
301                            Object::Integer(i) => code = *i,
302                            Object::Real(r) => code = *r as i64,
303                            Object::Name(n) => {
304                                if (0..=255).contains(&code) {
305                                    encoding_names.insert(
306                                        code as u8,
307                                        String::from_utf8_lossy(n).into_owned(),
308                                    );
309                                }
310                                code += 1;
311                            }
312                            _ => {}
313                        }
314                    }
315                }
316            }
317        }
318
319        // --- widths ----------------------------------------------------------
320        let mut widths = HashMap::new();
321        let mut default_width = None;
322        if composite {
323            if let Some(dd) = descendant(doc, fdict) {
324                default_width = Some(get_num(doc, dd, b"DW").unwrap_or(1000.0));
325                if let Some(Object::Array(w)) = get(doc, dd, b"W") {
326                    let mut i = 0;
327                    while i < w.len() {
328                        let c = w.get(i).map(|o| deref(doc, o)).and_then(num);
329                        match (c, w.get(i + 1).map(|o| deref(doc, o))) {
330                            (Some(c), Some(Object::Array(list))) => {
331                                for (k, wv) in list.iter().enumerate() {
332                                    if let Some(wv) = num(deref(doc, wv)) {
333                                        widths.insert(c as u32 + k as u32, wv);
334                                    }
335                                }
336                                i += 2;
337                            }
338                            (Some(c1), Some(o2)) => {
339                                if let (Some(c2), Some(wv)) =
340                                    (num(o2), w.get(i + 2).map(|o| deref(doc, o)).and_then(num))
341                                {
342                                    let (c1, c2) = (c1.max(0.0) as u32, c2.max(0.0) as u32);
343                                    if c2 >= c1 && c2 - c1 < 65536 {
344                                        for cid in c1..=c2 {
345                                            widths.insert(cid, wv);
346                                        }
347                                    }
348                                }
349                                i += 3;
350                            }
351                            _ => break,
352                        }
353                    }
354                }
355            }
356        } else {
357            let first = get_int(doc, fdict, b"FirstChar").unwrap_or(0);
358            if let Some(Object::Array(w)) = get(doc, fdict, b"Widths") {
359                for (k, wv) in w.iter().enumerate() {
360                    if let Some(wv) = num(deref(doc, wv)) {
361                        let code = first + k as i64;
362                        if (0..=255).contains(&code) {
363                            widths.insert(code as u32, wv);
364                        }
365                    }
366                }
367                if !w.is_empty() {
368                    default_width = Some(
369                        desc.and_then(|d| get_num(doc, d, b"MissingWidth"))
370                            .unwrap_or(0.0),
371                    );
372                }
373            }
374            if widths.is_empty() {
375                if let Some(mw) = desc.and_then(|d| get_num(doc, d, b"MissingWidth")) {
376                    if mw > 0.0 {
377                        default_width = Some(mw);
378                    }
379                }
380            }
381        }
382        let std14 = if widths.is_empty() && !composite && type3.is_none() {
383            crate::std14::widths_for(base_font.as_bytes())
384        } else {
385            None
386        };
387
388        // --- CID → GID -------------------------------------------------------
389        let mut cid_to_gid = CidToGid::Identity;
390        if composite {
391            if let Some(dd) = descendant(doc, fdict) {
392                if let Some(Object::Stream(s)) = get(doc, dd, b"CIDToGIDMap") {
393                    if let Ok(data) = s.decompressed_content() {
394                        cid_to_gid = CidToGid::Map(data);
395                    }
396                }
397            }
398        }
399
400        // --- fallback face ---------------------------------------------------
401        if matches!(program, Program::None) && type3.is_none() {
402            let ordering_cjk = composite
403                && descendant(doc, fdict)
404                    .and_then(|dd| get_dict(doc, dd, b"CIDSystemInfo"))
405                    .and_then(|si| get(doc, si, b"Ordering"))
406                    .and_then(|o| match o {
407                        Object::String(s, _) => Some(s.clone()),
408                        _ => None,
409                    })
410                    .is_some_and(|o| {
411                        matches!(
412                            o.as_slice(),
413                            b"Japan1" | b"GB1" | b"CNS1" | b"Korea1" | b"KR"
414                        )
415                    });
416            let mut style = fallback::style_for(&base_font, flags, None);
417            if ordering_cjk {
418                style.family = fallback::Family::Cjk;
419            }
420            if let Some(face) = fallback::face(style) {
421                let upem = ttf_parser::Face::parse(&face.data, 0)
422                    .ok()
423                    .map(|f| f64::from(f.units_per_em()))
424                    .filter(|u| *u > 0.0)
425                    .unwrap_or(1000.0);
426                program = Program::Fallback {
427                    face,
428                    matrix: Mat::scale(1.0 / upem, 1.0 / upem),
429                };
430            }
431        }
432
433        let to_unicode = get(doc, fdict, b"ToUnicode")
434            .and_then(|o| match o {
435                Object::Stream(s) => s.decompressed_content().ok(),
436                _ => None,
437            })
438            .map(|d| crate::textparse::parse_tounicode(&d))
439            .unwrap_or_default();
440
441        let vertical = cmap.as_ref().is_some_and(|c| c.vertical);
442        LoadedFont {
443            composite,
444            type3,
445            program,
446            encoding_names,
447            has_base_encoding,
448            symbolic: symbolic_flag && enc.is_none(),
449            cmap,
450            cid_to_gid,
451            widths,
452            default_width,
453            to_unicode,
454            std14,
455            cache: RefCell::new(HashMap::new()),
456            advance_cache: RefCell::new(HashMap::new()),
457            vertical,
458            is_dingbats,
459            is_symbol_face,
460        }
461    }
462
463    /// Split a string operand into `(code, cid, is_single_byte)`; `cid` is
464    /// the code itself for a simple font.
465    pub fn decode(&self, bytes: &[u8]) -> Vec<(u32, u32, bool)> {
466        match &self.cmap {
467            Some(c) => c
468                .split(bytes)
469                .into_iter()
470                .map(|(code, n)| (code, c.cid(code), n == 1))
471                .collect(),
472            None => bytes
473                .iter()
474                .map(|&b| (u32::from(b), u32::from(b), true))
475                .collect(),
476        }
477    }
478
479    /// The horizontal advance of `cid` (a code for simple fonts) in text
480    /// space (font size 1). Type 3 advances go through the font matrix.
481    pub fn advance(&self, code: u32, cid: u32) -> f64 {
482        let key = if self.composite { cid } else { code };
483        if let Some(a) = self.advance_cache.borrow().get(&key) {
484            return *a;
485        }
486        let a = self.advance_uncached(code, cid);
487        self.advance_cache.borrow_mut().insert(key, a);
488        a
489    }
490
491    fn advance_uncached(&self, code: u32, cid: u32) -> f64 {
492        let key = if self.composite { cid } else { code };
493        if let Some(t3) = &self.type3 {
494            let w = self.widths.get(&key).copied().unwrap_or(0.0);
495            let (ax, _) = t3.font_matrix.apply_vec(w, 0.0);
496            return ax;
497        }
498        if let Some(w) = self.widths.get(&key) {
499            return w / 1000.0;
500        }
501        if let Some(std) = &self.std14 {
502            if let Some(ch) = self.unicode_of(code, cid) {
503                if let Some(w) = std.width(ch) {
504                    return w / 1000.0;
505                }
506            }
507        }
508        if let Some(dw) = self.default_width {
509            if dw > 0.0 || !self.widths.is_empty() {
510                return dw / 1000.0;
511            }
512        }
513        // The program's own advance.
514        if let Some(adv) = self.program_advance(code, cid) {
515            return adv;
516        }
517        if self.default_width.is_some() {
518            return self.default_width.unwrap_or(0.0) / 1000.0;
519        }
520        0.5
521    }
522
523    fn unicode_of(&self, code: u32, cid: u32) -> Option<char> {
524        let key = if self.composite { cid } else { code };
525        if let Some(s) = self
526            .to_unicode
527            .get(&key)
528            .or_else(|| self.to_unicode.get(&code))
529        {
530            return s.chars().next();
531        }
532        if !self.composite {
533            if let Some(n) = self.encoding_names.get(&(code as u8)) {
534                return crate::textparse::glyph_name_to_char(n.as_bytes());
535            }
536            return char::from_u32(code).filter(|c| c.is_ascii_graphic() || *c == ' ');
537        }
538        if self.cmap.as_ref().is_some_and(|c| c.unicode_codes) {
539            return char::from_u32(code);
540        }
541        None
542    }
543
544    /// A Type 3 font's procedure name for `code` (its `/Encoding /Differences`).
545    pub fn type3_glyph_name(&self, code: u32) -> Option<String> {
546        self.encoding_names.get(&(code as u8)).cloned()
547    }
548
549    /// The glyph name a simple font's code selects, if the PDF names one.
550    fn glyph_name(&self, code: u32) -> Option<&str> {
551        self.encoding_names.get(&(code as u8)).map(String::as_str)
552    }
553
554    /// The glyph outline for a code, in text space (font size 1), cached.
555    pub fn glyph(&self, code: u32, cid: u32) -> Option<Rc<tiny_skia::Path>> {
556        let key = if self.composite { cid } else { code };
557        if let Some(p) = self.cache.borrow().get(&key) {
558            return p.clone();
559        }
560        let built = self.build_glyph(code, cid).and_then(|g| {
561            let m = self.program_matrix();
562            g.path.and_then(|p| p.transform(m.to_ts())).map(Rc::new)
563        });
564        self.cache.borrow_mut().insert(key, built.clone());
565        built
566    }
567
568    fn program_matrix(&self) -> Mat {
569        match &self.program {
570            Program::Sfnt { matrix, .. }
571            | Program::Cff { matrix, .. }
572            | Program::Type1 { matrix, .. }
573            | Program::Fallback { matrix, .. } => *matrix,
574            Program::None => Mat::scale(0.001, 0.001),
575        }
576    }
577
578    pub fn has_program(&self) -> bool {
579        !matches!(self.program, Program::None)
580    }
581
582    fn program_advance(&self, code: u32, cid: u32) -> Option<f64> {
583        let g = self.build_glyph(code, cid)?;
584        let (ax, _) = self.program_matrix().apply_vec(g.advance, 0.0);
585        Some(ax)
586    }
587
588    fn build_glyph(&self, code: u32, cid: u32) -> Option<GlyphPath> {
589        match &self.program {
590            Program::None => None,
591            Program::Type1 { font, .. } => {
592                let name = self.type1_glyph_name(font, code)?;
593                font.glyph(&name)
594            }
595            Program::Cff {
596                data,
597                cid_to_gid,
598                names,
599                ..
600            } => {
601                let table = ttf_parser::cff::Table::parse(data)?;
602                let gid = if self.composite {
603                    let gid = match &self.cid_to_gid {
604                        CidToGid::Map(m) => map_cid(m, cid),
605                        CidToGid::Identity => cid,
606                    };
607                    match cid_to_gid {
608                        Some(map) => GlyphId(*map.get(&(gid as u16))?),
609                        None => GlyphId(gid as u16),
610                    }
611                } else {
612                    self.cff_simple_gid(&table, names, code)?
613                };
614                outline_cff(&table, gid)
615            }
616            Program::Sfnt {
617                data,
618                cid_to_gid,
619                has_cmap,
620                has_glyf,
621                names,
622                ..
623            } => {
624                let face = ttf_parser::Face::parse(data, 0).ok()?;
625                let gid = if self.composite {
626                    let gid = match &self.cid_to_gid {
627                        CidToGid::Map(m) => map_cid(m, cid),
628                        CidToGid::Identity => cid,
629                    };
630                    match cid_to_gid {
631                        Some(map) => GlyphId(*map.get(&(gid as u16))?),
632                        None => GlyphId(gid as u16),
633                    }
634                } else {
635                    self.sfnt_simple_gid(&face, names, code, *has_cmap, *has_glyf)?
636                };
637                outline_face(&face, gid)
638            }
639            Program::Fallback { face, .. } => {
640                let f = ttf_parser::Face::parse(&face.data, 0).ok()?;
641                let gid = self.fallback_gid(&f, code, cid)?;
642                outline_face(&f, gid)
643            }
644        }
645    }
646
647    fn type1_glyph_name(&self, font: &type1::Type1Font, code: u32) -> Option<String> {
648        let c = code as u8;
649        // /Differences (or an explicit base encoding) first, then the
650        // program's built-in encoding, then Standard.
651        let pdf_name = self.glyph_name(code);
652        let builtin = font.encoding.get(&c).cloned();
653        let candidates: Vec<String> = if self.symbolic && !self.has_base_encoding {
654            // Symbolic without /Encoding: the built-in encoding rules, but a
655            // /Differences entry (already merged into encoding_names) wins.
656            [pdf_name.map(str::to_string), builtin.clone()]
657                .into_iter()
658                .flatten()
659                .collect()
660        } else {
661            [pdf_name.map(str::to_string), builtin.clone()]
662                .into_iter()
663                .flatten()
664                .collect()
665        };
666        for cand in &candidates {
667            if font.has_glyph(cand) {
668                return Some(cand.clone());
669            }
670            // `uniXXXX` / AGL name → the program's own name for that character.
671            if let Some(ch) = crate::textparse::glyph_name_to_char(cand.as_bytes()) {
672                if let Some(n) = self.type1_name_for_char(font, ch) {
673                    return Some(n);
674                }
675            }
676        }
677        if let Some(n) = encodings::lookup(encodings::STANDARD, c) {
678            if font.has_glyph(n) {
679                return Some(n.to_string());
680            }
681        }
682        if let Some(b) = builtin {
683            return Some(b);
684        }
685        None
686    }
687
688    fn type1_name_for_char(&self, font: &type1::Type1Font, ch: char) -> Option<String> {
689        // Reverse AGL over the font's names (small fonts, done rarely).
690        font.glyph_names()
691            .find(|n| crate::textparse::glyph_name_to_char(n.as_bytes()) == Some(ch))
692            .cloned()
693    }
694
695    fn cff_simple_gid(
696        &self,
697        table: &ttf_parser::cff::Table,
698        names: &HashMap<String, u16>,
699        code: u32,
700    ) -> Option<GlyphId> {
701        let c = code as u8;
702        let by_name = |n: &str| -> Option<GlyphId> {
703            names
704                .get(n)
705                .map(|g| GlyphId(*g))
706                .or_else(|| table.glyph_index_by_name(n))
707        };
708        if let Some(name) = self.glyph_name(code) {
709            if let Some(g) = by_name(name) {
710                return Some(g);
711            }
712            if let Some(ch) = crate::textparse::glyph_name_to_char(name.as_bytes()) {
713                // Another name for the same character (`uni0041` vs `A`).
714                if let Some(std_name) = agl_name(ch) {
715                    if let Some(g) = by_name(std_name) {
716                        return Some(g);
717                    }
718                }
719                let uni = format!("uni{:04X}", ch as u32);
720                if let Some(g) = by_name(&uni) {
721                    return Some(g);
722                }
723                if let Some(g) = names
724                    .iter()
725                    .find(|(n, _)| crate::textparse::glyph_name_to_char(n.as_bytes()) == Some(ch))
726                    .map(|(_, g)| GlyphId(*g))
727                {
728                    return Some(g);
729                }
730            }
731        }
732        // The program's built-in encoding.
733        if let Some(g) = table.glyph_index(c) {
734            if g.0 != 0 {
735                return Some(g);
736            }
737        }
738        if let Some(n) = encodings::lookup(encodings::STANDARD, c) {
739            if let Some(g) = by_name(n) {
740                return Some(g);
741            }
742        }
743        None
744    }
745
746    fn sfnt_simple_gid(
747        &self,
748        face: &ttf_parser::Face,
749        names: &HashMap<String, u16>,
750        code: u32,
751        has_cmap: bool,
752        has_glyf: bool,
753    ) -> Option<GlyphId> {
754        let c = code as u8;
755        let cmap = face.tables().cmap.as_ref();
756        let symbol_lookup = |code: u32| -> Option<GlyphId> {
757            let cm = cmap?;
758            for sub in cm.subtables {
759                if sub.platform_id == ttf_parser::PlatformId::Windows && sub.encoding_id == 0 {
760                    for probe in [code, 0xF000 + code, 0xF100 + code, 0xF200 + code] {
761                        if let Some(g) = sub.glyph_index(probe) {
762                            if g.0 != 0 {
763                                return Some(g);
764                            }
765                        }
766                    }
767                }
768            }
769            None
770        };
771        let mac_lookup = |code: u32| -> Option<GlyphId> {
772            let cm = cmap?;
773            for sub in cm.subtables {
774                if sub.platform_id == ttf_parser::PlatformId::Macintosh {
775                    if let Some(g) = sub.glyph_index(code) {
776                        if g.0 != 0 {
777                            return Some(g);
778                        }
779                    }
780                }
781            }
782            None
783        };
784        let unicode_lookup = |ch: char| -> Option<GlyphId> {
785            let cm = cmap?;
786            for sub in cm.subtables {
787                if sub.is_unicode() {
788                    if let Some(g) = sub.glyph_index(ch as u32) {
789                        if g.0 != 0 {
790                            return Some(g);
791                        }
792                    }
793                }
794            }
795            None
796        };
797        let name = self.glyph_name(code);
798        let symbolic_path = self.symbolic || !self.has_base_encoding && name.is_none();
799        if symbolic_path {
800            if let Some(g) = symbol_lookup(code) {
801                return Some(g);
802            }
803            if let Some(g) = mac_lookup(code) {
804                return Some(g);
805            }
806        }
807        if let Some(n) = name {
808            if let Some(ch) = crate::textparse::glyph_name_to_char(n.as_bytes()) {
809                if let Some(g) = unicode_lookup(ch) {
810                    return Some(g);
811                }
812                if let Some(g) = symbol_lookup(ch as u32) {
813                    return Some(g);
814                }
815            }
816            if let Some(g) = names
817                .get(n)
818                .map(|g| GlyphId(*g))
819                .or_else(|| face.glyph_index_by_name(n))
820            {
821                if g.0 != 0 {
822                    return Some(g);
823                }
824            }
825            // `gXX` / `glyphXX` / `index XX` names address glyphs directly.
826            if let Some(g) = gid_from_name(n) {
827                return Some(GlyphId(g));
828            }
829        }
830        if !symbolic_path {
831            if let Some(g) = symbol_lookup(code) {
832                return Some(g);
833            }
834            if let Some(g) = mac_lookup(code) {
835                return Some(g);
836            }
837        }
838        if let Some(std) = encodings::lookup(encodings::STANDARD, c) {
839            if let Some(ch) = crate::textparse::glyph_name_to_char(std.as_bytes()) {
840                if let Some(g) = unicode_lookup(ch) {
841                    return Some(g);
842                }
843            }
844        }
845        if !has_cmap || face.tables().cmap.is_none() {
846            // No usable cmap: the code is the glyph index.
847            let _ = has_glyf;
848            return Some(GlyphId(code as u16));
849        }
850        None
851    }
852
853    fn fallback_gid(&self, face: &ttf_parser::Face, code: u32, cid: u32) -> Option<GlyphId> {
854        // Symbol / Dingbats faces are addressed by glyph name.
855        if !self.composite && (self.is_symbol_face || self.is_dingbats) {
856            if let Some(n) = self.glyph_name(code) {
857                if let Some(g) = face.glyph_index_by_name(n) {
858                    return Some(g);
859                }
860            }
861        }
862        if !self.composite {
863            if let Some(n) = self.glyph_name(code) {
864                if let Some(ch) = crate::textparse::glyph_name_to_char(n.as_bytes()) {
865                    if let Some(g) = face.glyph_index(ch) {
866                        return Some(g);
867                    }
868                }
869                if let Some(g) = face.glyph_index_by_name(n) {
870                    return Some(g);
871                }
872            }
873        }
874        let ch = self.unicode_of(code, cid)?;
875        face.glyph_index(ch)
876    }
877}
878
879fn map_cid(map: &[u8], cid: u32) -> u32 {
880    let i = cid as usize * 2;
881    match (map.get(i), map.get(i + 1)) {
882        (Some(hi), Some(lo)) => (u32::from(*hi) << 8) | u32::from(*lo),
883        _ => 0,
884    }
885}
886
887fn gid_from_name(n: &str) -> Option<u16> {
888    for prefix in ["glyph", "index", "cid", "g", "G"] {
889        if let Some(rest) = n.strip_prefix(prefix) {
890            if !rest.is_empty() && rest.bytes().all(|b| b.is_ascii_digit()) {
891                return rest.parse().ok();
892            }
893        }
894    }
895    None
896}
897
898/// The Standard-encoding name of a character (the reverse of the AGL subset
899/// used here), for CFF fonts named by convention.
900fn agl_name(ch: char) -> Option<&'static str> {
901    encodings::STANDARD
902        .iter()
903        .chain(encodings::WIN_ANSI.iter())
904        .find(|(_, n)| crate::textparse::glyph_name_to_char(n.as_bytes()) == Some(ch))
905        .map(|(_, n)| *n)
906}
907
908struct Builder(tiny_skia::PathBuilder);
909
910impl ttf_parser::OutlineBuilder for Builder {
911    fn move_to(&mut self, x: f32, y: f32) {
912        self.0.move_to(x, y);
913    }
914    fn line_to(&mut self, x: f32, y: f32) {
915        self.0.line_to(x, y);
916    }
917    fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
918        self.0.quad_to(x1, y1, x, y);
919    }
920    fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
921        self.0.cubic_to(x1, y1, x2, y2, x, y);
922    }
923    fn close(&mut self) {
924        self.0.close();
925    }
926}
927
928fn outline_face(face: &ttf_parser::Face, gid: GlyphId) -> Option<GlyphPath> {
929    let mut b = Builder(tiny_skia::PathBuilder::new());
930    let bbox = face.outline_glyph(gid, &mut b);
931    let advance = face
932        .glyph_hor_advance(gid)
933        .map(f64::from)
934        .or_else(|| {
935            face.tables()
936                .cff
937                .as_ref()
938                .and_then(|c| c.glyph_width(gid))
939                .map(f64::from)
940        })
941        .unwrap_or(0.0);
942    if bbox.is_none() && gid.0 >= face.number_of_glyphs() {
943        return None;
944    }
945    Some(GlyphPath {
946        path: b.0.finish(),
947        advance,
948    })
949}
950
951fn outline_cff(table: &ttf_parser::cff::Table, gid: GlyphId) -> Option<GlyphPath> {
952    let mut b = Builder(tiny_skia::PathBuilder::new());
953    let ok = table.outline(gid, &mut b).is_ok();
954    let advance = table.glyph_width(gid).map(f64::from).unwrap_or(0.0);
955    if !ok && gid.0 >= table.number_of_glyphs() {
956        return None;
957    }
958    Some(GlyphPath {
959        path: b.0.finish(),
960        advance,
961    })
962}
963
964fn cff_matrix(m: ttf_parser::cff::Matrix) -> Mat {
965    let mat = Mat::new(
966        f64::from(m.sx),
967        f64::from(m.ky),
968        f64::from(m.kx),
969        f64::from(m.sy),
970        f64::from(m.tx),
971        f64::from(m.ty),
972    );
973    if mat.is_finite() && mat.det().abs() > 1e-12 {
974        mat
975    } else {
976        Mat::scale(0.001, 0.001)
977    }
978}
979
980fn sfnt_program(data: Vec<u8>) -> Option<Program> {
981    let face = ttf_parser::Face::parse(&data, 0).ok()?;
982    let tables = face.tables();
983    let has_glyf = tables.glyf.is_some();
984    let has_cmap = tables.cmap.is_some();
985    let (matrix, cid_to_gid, names) = match &tables.cff {
986        Some(cff) if !has_glyf => (cff_matrix(cff.matrix()), cid_map(cff), cff_names(cff)),
987        _ => {
988            let upem = f64::from(face.units_per_em()).max(1.0);
989            (Mat::scale(1.0 / upem, 1.0 / upem), None, HashMap::new())
990        }
991    };
992    Some(Program::Sfnt {
993        data,
994        matrix,
995        cid_to_gid,
996        has_cmap,
997        has_glyf,
998        names,
999    })
1000}
1001
1002fn cff_program(data: Vec<u8>) -> Option<Program> {
1003    let table = ttf_parser::cff::Table::parse(&data)?;
1004    let matrix = cff_matrix(table.matrix());
1005    let cid_to_gid = cid_map(&table);
1006    let names = cff_names(&table);
1007    Some(Program::Cff {
1008        data,
1009        matrix,
1010        cid_to_gid,
1011        names,
1012    })
1013}
1014
1015/// glyph name → glyph index over the whole charset (name-keyed fonts only).
1016fn cff_names(table: &ttf_parser::cff::Table) -> HashMap<String, u16> {
1017    let mut map = HashMap::new();
1018    if table.glyph_cid(GlyphId(0)).is_some() {
1019        return map;
1020    }
1021    for g in 0..table.number_of_glyphs() {
1022        if let Some(n) = table.glyph_name(GlyphId(g)) {
1023            map.entry(n.to_string()).or_insert(g);
1024        }
1025    }
1026    map
1027}
1028
1029/// CID → GID for a CID-keyed CFF (`None` for a name-keyed one).
1030fn cid_map(table: &ttf_parser::cff::Table) -> Option<HashMap<u16, u16>> {
1031    let n = table.number_of_glyphs();
1032    // A name-keyed font has no CIDs: probe glyph 1.
1033    table.glyph_cid(GlyphId(0))?;
1034    let mut map = HashMap::with_capacity(n as usize);
1035    for g in 0..n {
1036        if let Some(cid) = table.glyph_cid(GlyphId(g)) {
1037            map.entry(cid).or_insert(g);
1038        }
1039    }
1040    Some(map)
1041}
1042
1043/// A per-document font cache keyed by the font dictionary's object id.
1044#[derive(Default)]
1045pub struct FontCache {
1046    by_id: HashMap<ObjectId, Rc<LoadedFont>>,
1047    /// Direct (unreferenced) font dictionaries, keyed by their debug print.
1048    by_repr: HashMap<String, Rc<LoadedFont>>,
1049}
1050
1051impl FontCache {
1052    pub fn get(&mut self, doc: &Document, obj: &Object) -> Option<Rc<LoadedFont>> {
1053        match obj {
1054            Object::Reference(id) => {
1055                if let Some(f) = self.by_id.get(id) {
1056                    return Some(f.clone());
1057                }
1058                let d = as_dict(doc, obj)?;
1059                let f = Rc::new(LoadedFont::load(doc, d));
1060                self.by_id.insert(*id, f.clone());
1061                Some(f)
1062            }
1063            Object::Dictionary(d) => {
1064                let key = format!("{d:?}");
1065                if let Some(f) = self.by_repr.get(&key) {
1066                    return Some(f.clone());
1067                }
1068                let f = Rc::new(LoadedFont::load(doc, d));
1069                self.by_repr.insert(key, f.clone());
1070                Some(f)
1071            }
1072            _ => None,
1073        }
1074    }
1075}
1076
1077#[allow(dead_code)]
1078fn _unused(_: &Dictionary) -> Option<&[u8]> {
1079    None.and_then(name)
1080}