Skip to main content

pdfrum_font/cid/
mod.rs

1//! Composite (Type0) fonts: a multi-byte code, through a CMap, to a CID, to a
2//! glyph.
3//!
4//! This is the one font kind whose load can genuinely **fail** — four ways,
5//! all of which mean "the resource is not there" and cause the content-stream
6//! interpreter to skip text using the font.
7
8mod glyph;
9mod gsub;
10mod transform;
11
12pub use transform::{CidTransform, cid_transform_to_float, japan1_transform};
13
14use crate::descriptor::{self, FontDescriptor};
15use crate::fallback::GlyphFallback;
16use crate::glyphs::{Charmap, Face, GlyphSource};
17use crate::subst::{self, CodePage, FontRequest, SubstFont, SubstitutionOptions};
18use crate::tounicode::ToUnicode;
19use crate::widths::CidWidths;
20use crate::{CharCode, CharItem, Cid, Error, FontCache, FontId, Gid, names, widths};
21use pdfrum_cmap::{CMap, CidCoding, CidSet};
22use pdfrum_common::kurbo::Rect;
23use pdfrum_common::{DiagKind, Diagnostics, Limits, Severity};
24use pdfrum_object::{Dict, Object, Resolve, Resolved};
25use smallvec::SmallVec;
26use std::sync::OnceLock;
27
28pub use crate::widths::VerticalMetrics;
29
30/// How a CID becomes a glyph index.
31#[derive(Debug, Clone, PartialEq, Eq)]
32pub enum CidToGid {
33    /// `/CIDToGIDMap /Identity`, or the absence of the key for a font where
34    /// CID and GID coincide.
35    Identity,
36    /// A `/CIDToGIDMap` stream: a big-endian `u16` table indexed by CID.
37    Stream(Box<[u8]>),
38    /// Neither: the glyph is found through a charmap instead.
39    ViaCharmap,
40}
41
42/// Which flavour of descendant font.
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub enum CidFontKind {
45    /// `CIDFontType0` — CFF outlines, where CID *is* GID for an embedded font.
46    Type1,
47    /// `CIDFontType2` — TrueType outlines.
48    TrueType,
49}
50
51/// A composite font.
52#[derive(Debug)]
53pub struct Type0Font {
54    /// This font's identity, for glyph-cache keys.
55    pub(crate) id: FontId,
56    /// The CMap that splits bytes into codes and maps them to CIDs.
57    pub(crate) cmap: CMap,
58    /// Where glyphs come from.
59    pub(crate) glyphs: GlyphSource,
60    /// The CID collection, from the CMap or from `/CIDSystemInfo`.
61    pub(crate) charset: CidSet,
62    /// How a CID becomes a glyph.
63    pub(crate) cid_to_gid: CidToGid,
64    /// `/W` and `/DW`.
65    pub(crate) widths: CidWidths,
66    /// `/W2` and `/DW2`, only for a vertical font.
67    pub(crate) vertical: Option<VerticalMetrics>,
68    /// The `/ToUnicode` CMap.
69    pub(crate) to_unicode: Option<ToUnicode>,
70    /// The descendant's `/FontDescriptor`.
71    pub(crate) descriptor: FontDescriptor,
72    /// What substitution decided.
73    pub(crate) subst: Option<SubstFont>,
74    /// Which flavour of descendant.
75    pub(crate) kind: CidFontKind,
76    /// Whether a usable font program was embedded.
77    pub(crate) embedded: bool,
78    /// The descendant's `/BaseFont`.
79    pub(crate) base_font_name: Vec<u8>,
80    /// Adobe's CourierStd, whose CIDs are offset from the standard encoding by
81    /// 31 — a hard-coded rescue with no general rule behind it.
82    pub(crate) adobe_courier_std: bool,
83    /// The GB2312 rescue path, which fixes ASCII widths.
84    #[cfg(test)]
85    pub(crate) ansi_widths_fixed: bool,
86    /// Vertical substitution, parsed from `GSUB` on first use.
87    gsub: gsub::VerticalSubst,
88    /// The Arial stand-in `GetCharPosList` builds on the first glyph the
89    /// ladder could not place. Empty until then.
90    pub(crate) fallback: OnceLock<Option<GlyphFallback>>,
91}
92
93impl Type0Font {
94    /// The CID a character code maps to.
95    #[must_use]
96    pub(crate) fn cid_from_charcode(&self, code: CharCode) -> Cid {
97        self.cmap.cid(code)
98    }
99
100    /// The glyph a character code selects, or `None` for "draw nothing".
101    ///
102    /// Also reports whether a vertical form was substituted, which suppresses
103    /// the Japan1 transform downstream.
104    #[must_use]
105    pub(crate) fn glyph_from_charcode(&self, code: CharCode) -> (Option<Gid>, bool) {
106        glyph::resolve(self, code)
107    }
108
109    /// The advance width for a character code, in 1000/em units.
110    #[must_use]
111    pub(crate) fn char_width(&self, code: CharCode) -> f32 {
112        self.widths.width(code, self.cid_from_charcode(code))
113    }
114
115    /// The vertical advance for a character code.
116    #[must_use]
117    pub(crate) fn vert_width(&self, code: CharCode) -> f32 {
118        match &self.vertical {
119            Some(v) => v.width(self.cid_from_charcode(code)),
120            None => -1000.0,
121        }
122    }
123
124    /// The vertical origin for a character code, in 1000/em units.
125    ///
126    /// Absent a `/W2` record this is **half the horizontal width** and
127    /// `DW2[0]`, which is why the horizontal table is consulted here.
128    #[must_use]
129    pub(crate) fn vert_origin(&self, code: CharCode) -> (f32, f32) {
130        let cid = self.cid_from_charcode(code);
131        match &self.vertical {
132            Some(v) => v.origin(cid, &self.widths),
133            None => ((self.widths.width(code, cid) / 2.0).trunc(), 880.0),
134        }
135    }
136
137    /// The Unicode a code stands for, `/ToUnicode` first.
138    #[must_use]
139    pub(crate) fn unicode_from_charcode(&self, code: CharCode) -> SmallVec<[char; 2]> {
140        if let Some(tu) = &self.to_unicode {
141            let chars = tu.lookup(code);
142            if !chars.is_empty() {
143                return chars;
144            }
145        }
146        match self.scalar_unicode(code) {
147            0 => SmallVec::new(),
148            u => char::from_u32(u32::from(u))
149                .map(|c| SmallVec::from_slice(&[c]))
150                .unwrap_or_default(),
151        }
152    }
153
154    /// The *scalar* Unicode derivation, which does **not** consult
155    /// `/ToUnicode`.
156    ///
157    /// The CMap's coding scheme decides: a UCS-2 or UTF-16 CMap means the
158    /// character code simply *is* the Unicode, while a CID-coded one goes
159    /// through the collection's table.
160    #[must_use]
161    pub(crate) fn scalar_unicode(&self, code: CharCode) -> u16 {
162        match self.cmap.coding() {
163            CidCoding::Ucs2 | CidCoding::Utf16 => return (code.0 & 0xffff) as u16,
164            CidCoding::Cid => {
165                if !pdfrum_cmap::has_cid2unicode(self.charset) {
166                    return 0;
167                }
168                let cid = Cid((code.0 & 0xffff) as u16);
169                return pdfrum_cmap::unicode_from_cid(self.charset, cid).map_or(0, |c| c as u16);
170            }
171            _ => {}
172        }
173        if pdfrum_cmap::has_cid2unicode(self.charset) && self.cmap.is_loaded() {
174            let cid = self.cid_from_charcode(code);
175            return pdfrum_cmap::unicode_from_cid(self.charset, cid).map_or(0, |c| c as u16);
176        }
177        // The non-Windows tail: only the four CJK registries have a static
178        // map to walk backwards through.
179        if !self.cmap.has_static_map() {
180            return 0;
181        }
182        let cid = self.cid_from_charcode(code);
183        if cid.0 == 0 {
184            return 0;
185        }
186        pdfrum_cmap::unicode_from_cid(self.charset, cid).map_or(0, |c| c as u16)
187    }
188
189    /// The character code for a Unicode, or 0 (`CharCodeFromUnicode`).
190    #[must_use]
191    pub(crate) fn charcode_from_unicode(&self, unicode: char) -> CharCode {
192        if let Some(tu) = &self.to_unicode {
193            let c = tu.reverse(unicode);
194            if c.0 != 0 {
195                return c;
196            }
197        }
198        match self.cmap.coding() {
199            CidCoding::Unknown => return CharCode(0),
200            CidCoding::Ucs2 | CidCoding::Utf16 => return CharCode(unicode as u32),
201            CidCoding::Cid => {
202                if !pdfrum_cmap::has_cid2unicode(self.charset) {
203                    return CharCode(0);
204                }
205                // The C++ scans all 65 536 CIDs linearly; the cmap crate has
206                // the same scan behind a name.
207                for cid in 0..=u16::MAX {
208                    if pdfrum_cmap::unicode_from_cid(self.charset, Cid(cid)) == Some(unicode) {
209                        return CharCode(u32::from(cid));
210                    }
211                }
212            }
213            _ => {}
214        }
215        if (unicode as u32) < 0x80 {
216            return CharCode(unicode as u32);
217        }
218        if self.cmap.coding() == CidCoding::Cid {
219            return CharCode(0);
220        }
221        pdfrum_cmap::charcode_from_unicode(&self.cmap, unicode)
222    }
223
224    /// Whether codes can be turned into Unicode at all (`IsUnicodeCompatible`).
225    #[must_use]
226    pub(crate) fn is_unicode_compatible(&self) -> bool {
227        if pdfrum_cmap::has_cid2unicode(self.charset) && self.cmap.is_loaded() {
228            return true;
229        }
230        self.cmap.coding() != CidCoding::Unknown
231    }
232
233    /// Whether the font writes vertically.
234    #[must_use]
235    pub(crate) fn is_vertical(&self) -> bool {
236        self.cmap.is_vertical()
237    }
238
239    /// The bounding box for a code, in 1000/em units, with the top's
240    /// sixty-fourth added and the Japan1 transform applied when it applies.
241    #[must_use]
242    pub(crate) fn char_bbox(&self, code: CharCode) -> Rect {
243        let (gid, vertical) = self.glyph_from_charcode(code);
244        let Some(gid) = gid else { return Rect::ZERO };
245        let Some(bbox) = self.glyphs.glyph_bbox(gid) else {
246            return Rect::ZERO;
247        };
248        let bbox = grow_top(bbox);
249        // The transform rotates an upright glyph into a vertical one — so a
250        // glyph GSUB *already* substituted must not be rotated again.
251        if vertical {
252            return bbox;
253        }
254        match self.japan1_transform(code) {
255            Some(t) => transform::apply(t, bbox),
256            None => bbox,
257        }
258    }
259
260    /// The Japan1 vertical transform for a code, when one applies.
261    ///
262    /// Only for a **non-embedded** Adobe-Japan1 font: an embedded one is
263    /// expected to carry its own vertical forms.
264    #[must_use]
265    pub(crate) fn japan1_transform(&self, code: CharCode) -> Option<CidTransform> {
266        if self.charset != CidSet::Japan1 || self.embedded {
267            return None;
268        }
269        japan1_transform(self.cid_from_charcode(code))
270    }
271
272    /// Build one [`CharItem`].
273    pub(crate) fn char_item(&self, code: CharCode) -> CharItem {
274        let (gid, vertical_glyph) = self.glyph_from_charcode(code);
275        CharItem {
276            code,
277            cid: Some(self.cid_from_charcode(code)),
278            gid,
279            unicode: self.unicode_from_charcode(code),
280            width: if self.is_vertical() {
281                self.vert_width(code)
282            } else {
283                self.char_width(code)
284            },
285            vertical_glyph,
286        }
287    }
288
289    fn gsub(&self) -> &gsub::VerticalSubst {
290        &self.gsub
291    }
292}
293
294/// The CMap an `/Encoding` stream's `/UseCMap` key names, or `None` when it
295/// has none.
296///
297/// The key may be a name — the built-in CMap of that name — or a stream
298/// holding another CMap program, which is read the same way the `/Encoding`
299/// stream itself was. Anything else is not an inheritance and is ignored.
300//
301// [oracle-bug] `grep -rn 'usecmap|UseCMap' core/fpdfapi/` over the oracle
302// returns exactly one line — `cpdf_cmapparser.cpp:61`, an empty
303// `} else if (word == "usecmap") {` — so the `/UseCMap` *dictionary* key is
304// not read anywhere in PDFium at all, and neither of the two inheritance
305// channels ISO 32000-1 §9.7.5.3 defines exists. A CMap stream that says
306// `/UseCMap /GBK-EUC-H` and overrides a handful of codes therefore decodes
307// every inherited code to CID 0 rather than to the base map's answer: total
308// loss, not degradation. pdf.js reads the key, and gives it precedence over
309// the program's own `usecmap` operator — `cmap.js:639-643` takes the embedded
310// operand only `if (!useCMap && embeddedUseCMap)` — then merges child-wins in
311// `extendCMap` (:650-669). We do the same; `pdfrum_cmap::inherit_from` is the
312// call that overrides whatever the program named.
313/// Add a sixty-fourth of the top to the top (`CFX_Face::GetCharBBox`).
314///
315/// `cfx_face.cpp:1211-1216` does this to every box it returns from the
316/// non-tricky branch, and `cpdf_cidfont.cpp:550` is the only caller — so
317/// every CID box carries it. The simple-font path reads `GetGlyphBBox`
318/// directly (`cpdf_facebasedsimplefont.cpp:74`) and carries none.
319///
320/// The saturation is the oracle's: past `kMaxRectTop` (`cfx_face.cpp:74`),
321/// the value one below where the addition would overflow an `int`, the top
322/// becomes `INT_MAX` outright rather than wrapping.
323fn grow_top(bbox: Rect) -> Rect {
324    /// `cfx_face.cpp:74` — the largest top a sixty-fourth still fits above.
325    const MAX_RECT_TOP: i32 = 2_114_445_437;
326
327    let top = bbox.y1 as i32;
328    let grown = if top <= MAX_RECT_TOP {
329        top + top / 64
330    } else {
331        i32::MAX
332    };
333    Rect::new(bbox.x0, bbox.y0, bbox.x1, f64::from(grown))
334}
335
336fn use_cmap_parent(
337    dict: &Dict,
338    r: &impl Resolve,
339    limits: &Limits,
340    diags: &mut Diagnostics,
341) -> Option<CMap> {
342    if let Some(stream) = dict.stream(names::USE_CMAP, r) {
343        let bytes = pdfrum_filters::decode_chain(&stream, 0, r, limits, diags).data;
344        // Depth 1: this is already one link down from the `/Encoding` stream.
345        // A parent naming its own `/UseCMap` is not followed further, which
346        // is what keeps a stream that names itself from recursing.
347        return Some(pdfrum_cmap::parse_embedded(&bytes, limits, diags));
348    }
349    let resolved = dict.get(names::USE_CMAP, r)?;
350    let name = resolved.as_name()?;
351    Some(pdfrum_cmap::from_encoding_name(name, diags))
352}
353
354/// Load a Type0 font.
355///
356/// # Errors
357///
358/// The four cases PDFium treats as "this resource does not exist": a
359/// `/DescendantFonts` that is missing or does not hold exactly one element, a
360/// first element that is not a dictionary, a missing `/Encoding`, and an
361/// `/Encoding` that is neither a name nor a stream.
362pub(crate) fn load(
363    dict: &Dict,
364    r: &impl Resolve,
365    cache: &FontCache,
366    opts: &SubstitutionOptions,
367    limits: &Limits,
368    diags: &mut Diagnostics,
369) -> Result<Type0Font, Error> {
370    let descendants = dict
371        .array(names::DESCENDANT_FONTS, r)
372        .ok_or(Error::BadDescendantFonts)?;
373    if descendants.len() != 1 {
374        return Err(Error::BadDescendantFonts);
375    }
376    let cid_dict = descendants.dict_at(0, r).ok_or(Error::BadDescendantFonts)?;
377
378    let base_font_name = cid_dict
379        .name(names::BASE_FONT)
380        .map(|n| n.as_bytes().to_vec())
381        .unwrap_or_default();
382
383    let encoding = dict.raw(names::ENCODING).ok_or(Error::BadCidEncoding)?;
384    let kind = match cid_dict.name(names::SUBTYPE).map(|n| n.as_bytes().to_vec()) {
385        Some(s) if s == b"CIDFontType0" => CidFontKind::Type1,
386        _ => CidFontKind::TrueType,
387    };
388
389    // An `/Encoding` must be a name or a stream. A dictionary, an array or a
390    // number fails the load outright.
391    let cmap = match encoding {
392        Object::Name(name) => pdfrum_cmap::from_encoding_name(name, diags),
393        Object::Stream(_) | Object::Ref(_) => {
394            if let Some(stream) = dict.stream(names::ENCODING, r) {
395                let bytes = pdfrum_filters::decode_chain(&stream, 0, r, limits, diags).data;
396                let cmap = pdfrum_cmap::parse_embedded(&bytes, limits, diags);
397                match use_cmap_parent(&stream.dict, r, limits, diags) {
398                    Some(parent) => pdfrum_cmap::inherit_from(cmap, parent, 0, limits, diags),
399                    None => cmap,
400                }
401            } else {
402                // A reference is only usable if it resolves to a stream or a
403                // name; anything else is one of the four fatal cases.
404                let resolved = dict.get(names::ENCODING, r);
405                match resolved.as_ref().and_then(|o| o.as_name()) {
406                    Some(name) => pdfrum_cmap::from_encoding_name(name, diags),
407                    None => return Err(Error::BadCidEncoding),
408                }
409            }
410        }
411        _ => return Err(Error::BadCidEncoding),
412    };
413
414    Ok(build(
415        &cid_dict,
416        dict,
417        cmap,
418        kind,
419        base_font_name,
420        r,
421        cache,
422        opts,
423        limits,
424        diags,
425        false,
426    ))
427}
428
429/// The GB2312 rescue path, reached only through the Chinese-name special case
430/// of the font-type dispatch.
431///
432/// Charset GB1 with the predefined `GBK-EUC-H` CMap, and fixed ASCII widths.
433pub(crate) fn load_gb2312(
434    dict: &Dict,
435    r: &impl Resolve,
436    cache: &FontCache,
437    opts: &SubstitutionOptions,
438    limits: &Limits,
439    diags: &mut Diagnostics,
440) -> Result<Type0Font, Error> {
441    let cmap = pdfrum_cmap::predefined(&pdfrum_object::Name::from("GBK-EUC-H"))
442        .ok_or(Error::BadCidEncoding)?;
443    let base_font_name = dict
444        .name(names::BASE_FONT)
445        .map(|n| n.as_bytes().to_vec())
446        .unwrap_or_default();
447    Ok(build(
448        dict,
449        dict,
450        cmap,
451        CidFontKind::TrueType,
452        base_font_name,
453        r,
454        cache,
455        opts,
456        limits,
457        diags,
458        true,
459    ))
460}
461
462// The ladder below reads as one sequence — each step's inputs come from the
463// step above it — and splitting it into pieces would hide the order the
464// decisions have to be taken in.
465#[allow(clippy::too_many_arguments, clippy::too_many_lines)]
466fn build(
467    cid_dict: &Dict,
468    font_dict: &Dict,
469    cmap: CMap,
470    kind: CidFontKind,
471    base_font_name: Vec<u8>,
472    r: &impl Resolve,
473    cache: &FontCache,
474    opts: &SubstitutionOptions,
475    limits: &Limits,
476    diags: &mut Diagnostics,
477    gb2312: bool,
478) -> Type0Font {
479    // Adobe's CourierStd, whose CIDs sit 31 codes below the standard
480    // encoding. There is no general rule here — four literal names.
481    let adobe_courier_std = matches!(
482        base_font_name.as_slice(),
483        b"CourierStd" | b"CourierStd-Bold" | b"CourierStd-BoldOblique" | b"CourierStd-Oblique"
484    );
485
486    let desc = cid_dict.dict(names::FONT_DESCRIPTOR, r);
487    let mut descriptor = FontDescriptor::default();
488    if let Some(d) = &desc {
489        descriptor = descriptor::load(d, r);
490    }
491    let (mut glyphs, mut embedded) =
492        crate::simple::load_font_program(desc.as_ref(), r, limits, diags);
493
494    // The collection: what the CMap says, or what `/CIDSystemInfo` says.
495    let mut charset = if gb2312 { CidSet::Gb1 } else { cmap.charset() };
496    if charset == CidSet::Unknown
497        && let Some(info) = cid_dict.dict(names::CID_SYSTEM_INFO, r)
498        && let Some(ordering) = info.byte_string(names::ORDERING, r)
499    {
500        charset = pdfrum_cmap::charset_from_ordering(&ordering);
501    }
502
503    let mut widths_table = CidWidths::load(cid_dict, r, diags);
504    if gb2312 {
505        widths_table.set_ansi_widths_fixed();
506    }
507
508    let mut subst_font = None;
509    if !embedded {
510        let request = FontRequest {
511            name: base_font_name.clone(),
512            is_truetype: kind == CidFontKind::TrueType,
513            flags: descriptor.flags,
514            // The C++ multiplies the stem by five here rather than using the
515            // descriptor's own weight estimate, saturating to 400.
516            weight: descriptor
517                .stem_v
518                .checked_mul(5)
519                .filter(|w| *w > 0)
520                .unwrap_or(400),
521            italic_angle: descriptor.italic_angle,
522            code_page: CodePage::for_cid_set(charset),
523            vertical: cmap.is_vertical(),
524        };
525        let s = substitute(&request, opts, diags);
526        glyphs = s.glyphs;
527        subst_font = Some(s.subst);
528    }
529    if !glyphs.is_some() {
530        embedded = false;
531    }
532
533    // `/CIDToGIDMap` is read *through* a reference: the C++ reaches it with
534    // `GetDirectObjectFor`, whose `GetDirectInternal` follows an indirect
535    // reference before anything looks at the type
536    // (`core/fpdfapi/parser/cpdf_dictionary.cpp:89-93`), so `/CIDToGIDMap
537    // 12 0 R` pointing at `/Identity` is the Identity mapping just as the
538    // direct name is (`core/fpdfapi/font/cpdf_cidfont.cpp:507-518`).
539    let cid_to_gid = match cid_dict
540        .get(names::CID_TO_GID_MAP, r)
541        .as_ref()
542        .map(Resolved::get)
543    {
544        Some(Object::Name(n)) if n.as_bytes() == b"Identity" && embedded => CidToGid::Identity,
545        Some(Object::Stream(_)) => {
546            match cid_dict.stream(names::CID_TO_GID_MAP, r) {
547                Some(s) => {
548                    let bytes = pdfrum_filters::decode_chain(&s, 0, r, limits, diags).data;
549                    // Two bytes per CID: a table shorter than the face has
550                    // glyphs leaves the tail unmapped rather than erroring.
551                    if bytes.len() < glyphs.num_glyphs() as usize * 2 {
552                        diags.record(Severity::Suspicious, DiagKind::CidToGidStreamShort, None);
553                    }
554                    CidToGid::Stream(bytes.into_boxed_slice())
555                }
556                None => CidToGid::ViaCharmap,
557            }
558        }
559        _ => CidToGid::ViaCharmap,
560    };
561
562    let vertical = if cmap.is_vertical() {
563        Some(widths::VerticalMetrics::load(cid_dict, r, diags))
564    } else {
565        None
566    };
567
568    let to_unicode = crate::simple::load_to_unicode(font_dict, r, limits, diags);
569
570    let metrics = match &glyphs {
571        GlyphSource::Fontations(f) => f.metrics(),
572        GlyphSource::Type1(f) => Some(descriptor::FaceMetrics {
573            upem: f.units_per_em(),
574            bbox_left: f.bbox().x0 as i64,
575            bbox_top: f.bbox().y1 as i64,
576            bbox_right: f.bbox().x1 as i64,
577            bbox_bottom: f.bbox().y0 as i64,
578            ascender: f.bbox().y1 as i64,
579            descender: f.bbox().y0 as i64,
580        }),
581        GlyphSource::None => None,
582    };
583    descriptor::check_font_metrics(&mut descriptor, metrics, |_| Rect::ZERO);
584
585    let gsub = if cmap.is_vertical() {
586        gsub::VerticalSubst::parse(&glyphs, diags)
587    } else {
588        gsub::VerticalSubst::none()
589    };
590
591    Type0Font {
592        id: cache.next_id(),
593        cmap,
594        glyphs,
595        charset,
596        cid_to_gid,
597        widths: widths_table,
598        vertical,
599        to_unicode,
600        descriptor,
601        subst: subst_font,
602        kind,
603        embedded,
604        base_font_name,
605        adobe_courier_std,
606        #[cfg(test)]
607        ansi_widths_fixed: gb2312,
608        gsub,
609        fallback: OnceLock::new(),
610    }
611}
612
613fn substitute(
614    request: &FontRequest,
615    opts: &SubstitutionOptions,
616    diags: &mut Diagnostics,
617) -> subst::Substitution {
618    subst::resolve_with_options(request, opts, diags)
619}
620
621/// Choose the charmap a CID font drives (`UseCIDCharmap`).
622///
623/// Three rungs: the **legacy** charmap the coding scheme names, then Unicode,
624/// then whatever charmap comes first. A CJK font that carries its national
625/// encoding's own subtable is driven through *that*, with character codes
626/// passed straight in — which is why the embedded glyph ladder branches on
627/// whether the chosen charmap is Unicode before deciding what to look up.
628///
629/// Note **Korea asks for Johab**, encoding id 6, not Wansung's 5. A font
630/// carrying only a Wansung subtable therefore falls through to Unicode, which
631/// looks like an oversight and is what the oracle does.
632pub(crate) fn cid_charmap(glyphs: &GlyphSource, coding: CidCoding) -> Charmap {
633    let charmaps = glyphs.charmaps();
634
635    // Rung 1 — the national encoding, as a Windows-platform subtable.
636    if let Some(wanted) = legacy_encoding_id(coding)
637        && let Some(i) = charmaps
638            .iter()
639            .position(|c| c.platform == 3 && c.encoding == wanted)
640    {
641        return Charmap::Index(i);
642    }
643    // Rung 2 — Unicode.
644    if let Some(i) = charmaps.iter().position(|c| c.is_unicode()) {
645        return Charmap::Index(i);
646    }
647    // Rung 3 — anything at all.
648    if charmaps.is_empty() {
649        Charmap::None
650    } else {
651        Charmap::Index(0)
652    }
653}
654
655/// The `cmap` encoding id a CID coding scheme's legacy charmap carries, on the
656/// Windows platform.
657///
658/// The ids are the `TT_MS_ID_*` values FreeType maps its `FT_ENCODING_*`
659/// constants onto: Shift-JIS 2, GB2312 3, Big5 4, Johab 6.
660fn legacy_encoding_id(coding: CidCoding) -> Option<u16> {
661    Some(match coding {
662        CidCoding::Gb => 3,
663        CidCoding::Big5 => 4,
664        CidCoding::Jis => 2,
665        CidCoding::Korea => 6,
666        // Every other scheme asks for Unicode outright, which rung 2 covers.
667        CidCoding::Unknown | CidCoding::Ucs2 | CidCoding::Cid | CidCoding::Utf16 => return None,
668    })
669}
670
671/// Read a `Face` out of a glyph source, for the GSUB reader.
672pub(crate) fn face_of(glyphs: &GlyphSource) -> Option<&Face> {
673    match glyphs {
674        GlyphSource::Fontations(f) => Some(f),
675        GlyphSource::Type1(_) | GlyphSource::None => None,
676    }
677}
678
679#[cfg(test)]
680#[path = "cid_tests.rs"]
681mod tests;