Skip to main content

read_fonts/model/
charmap.rs

1//! Character mappings for a font.
2
3use crate::{
4    model::{font::SharedFont, Font},
5    ps::type1::Type1Font,
6    ps::PredefinedEncoding,
7    tables::{
8        cmap::{
9            Cmap, CmapIterLimits, CmapSubtable, EncodingRecord, MapVariant, PlatformId, UvsSubtable,
10        },
11        name::MacRomanMapping,
12    },
13    FontData, TableProvider,
14};
15use alloc::vec::Vec;
16use types::{GlyphId, Tag};
17use yoke::Yokeable;
18
19/// Character mappings for a font.
20///
21/// Obtain this facade with [`Font::charmap`]. It offers two ways to map
22/// characters, depending on what the caller knows about its input.
23///
24/// # Unicode lookup
25///
26/// [`map_unicode`](Self::map_unicode) accepts a Unicode codepoint (or `char`).
27/// For an SFNT font, it uses one selected `cmap` subtable, with a preference
28/// for a Microsoft symbol subtable, then full Unicode, then BMP Unicode, and
29/// finally Mac Roman. A symbol subtable retries codes in U+0000..U+00FF at
30/// U+F000..U+F0FF when the direct lookup fails; [`unicodes`](Self::unicodes)
31/// lists only the codes the subtable holds. A Mac Roman subtable converts
32/// Unicode codepoints to Mac Roman character codes before lookup. For Type 1 and standalone CFF
33/// fonts, the Unicode map is derived from glyph names when the `agl` feature is
34/// enabled. Glyph ID 0 is treated as unmapped.
35/// An optional `DMAP` table takes precedence, with missing mappings falling
36/// back to `cmap` (ISO/IEC 14496-22:2026, 5.6.15).
37/// [`map_unicode_batched`](Self::map_unicode_batched) writes several results
38/// while reusing the selected subtable and, for formats 4 and 12, the preceding
39/// segment or group when possible.
40///
41/// [`map_unicode_variant`](Self::map_unicode_variant) uses the last SFNT
42/// `cmap` variation sequences subtable, preferring format 15 to format 14. A non-default variation sequence names its glyph
43/// directly; a default sequence resolves through the selected Unicode map.
44/// Type 1 and standalone CFF fonts have no variation-sequence map.
45///
46/// The corresponding iterators are [`unicodes`](Self::unicodes) and
47/// [`unicode_variants`](Self::unicode_variants).
48///
49/// # Individual encodings
50///
51/// [`encodings`](Self::encodings) lists each supported SFNT `cmap` encoding
52/// record separately, including multiple records with the same
53/// [`EncodingKind`]. It also lists native and glyph-name-derived Unicode
54/// encodings for Type 1 and standalone CFF fonts when available. Keep the complete
55/// [`Encoding`] value to select that specific mapping with
56/// [`map_code`](Self::map_code) or [`codes`](Self::codes).
57/// These methods use character codes in the selected encoding as stored in
58/// the font. They do not convert Mac Roman codes from Unicode or apply the
59/// Microsoft symbol fallback. A mapping to glyph ID 0 is treated as unmapped.
60/// DMAP overrides are applied to matching encodings, and encodings supplied
61/// only by DMAP are also available.
62///
63/// The selected Unicode subtable and the individual encoding records are
64/// parsed and cached separately, only when their respective methods need them.
65#[derive(Clone, Copy)]
66pub struct Charmap<'a> {
67    font: &'a SharedFont,
68}
69
70impl<'a> Charmap<'a> {
71    pub(crate) fn new(font: &'a Font) -> Self {
72        Self {
73            font: font.shared(),
74        }
75    }
76
77    /// Returns whether this font has a selected Unicode mapping.
78    ///
79    /// For Type 1 fonts, this requires a nonempty glyph-name-derived map.
80    pub fn has_unicode(&self) -> bool {
81        match self.font.kind() {
82            crate::model::Kind::Type1(font) => font.unicode_charmap().iter().next().is_some(),
83            crate::model::Kind::Cff(_, _) => self
84                .font
85                .standalone_cff_unicode_charmap()
86                .is_some_and(|map| map.iter().next().is_some()),
87            _ => self
88                .font
89                .unicode_charmap()
90                .is_some_and(|map| map.dmap.subtable.is_some() || map.cmap.subtable.is_some()),
91        }
92    }
93
94    /// Returns whether Unicode lookup uses a Microsoft symbol subtable.
95    pub fn unicode_is_symbol(&self) -> bool {
96        self.font
97            .unicode_charmap()
98            .is_some_and(|map| map.dmap.is_symbol || map.cmap.is_symbol)
99    }
100
101    /// Returns whether Unicode lookup uses a Mac Roman subtable.
102    pub fn unicode_is_mac_roman(&self) -> bool {
103        self.font
104            .unicode_charmap()
105            .is_some_and(|map| map.dmap.is_mac_roman || map.cmap.is_mac_roman)
106    }
107
108    /// Maps a Unicode codepoint to a glyph.
109    ///
110    /// Type 1 and standalone CFF fonts use a glyph-name-based map when the
111    /// `agl` feature is enabled.
112    /// A mapping to glyph 0 is treated as unmapped.
113    pub fn map_unicode(&self, codepoint: impl Into<u32>) -> Option<GlyphId> {
114        let codepoint = codepoint.into();
115        let glyph = match self.font.kind() {
116            crate::model::Kind::Type1(font) => font.unicode_charmap().map(codepoint),
117            crate::model::Kind::Cff(_, _) => {
118                self.font.standalone_cff_unicode_charmap()?.map(codepoint)
119            }
120            _ => self.font.unicode_charmap()?.map(codepoint),
121        };
122        glyph.filter(|glyph| *glyph != GlyphId::NOTDEF)
123    }
124
125    /// Writes the Unicode mapping of each codepoint to its slot, in order.
126    ///
127    /// Unmapped codepoints, including mappings to glyph 0, receive
128    /// [`GlyphId::NOTDEF`]. Returns the number of initial consecutive nonzero
129    /// mappings, while still writing every output. This uses
130    /// the same selected mapping, Mac Roman conversion, and Microsoft symbol
131    /// fallback as [`map_unicode`](Self::map_unicode).
132    pub fn map_unicode_batched<'o>(
133        &self,
134        codepoints: impl Iterator<Item = (u32, &'o mut GlyphId)>,
135    ) -> usize {
136        match self.font.kind() {
137            crate::model::Kind::Type1(font) => {
138                map_ps_batched(Some(font.unicode_charmap()), codepoints)
139            }
140            crate::model::Kind::Cff(_, _) => {
141                map_ps_batched(self.font.standalone_cff_unicode_charmap(), codepoints)
142            }
143            _ => {
144                if let Some(map) = self.font.unicode_charmap() {
145                    map.map_batched(codepoints)
146                } else {
147                    for (_, out) in codepoints {
148                        *out = GlyphId::NOTDEF;
149                    }
150                    0
151                }
152            }
153        }
154    }
155
156    /// Iterates over Unicode codepoints and their resolved glyph IDs.
157    ///
158    /// A Microsoft symbol subtable yields only the codes it contains.
159    /// [`map_unicode`](Self::map_unicode) also maps U+0000..U+00FF through
160    /// U+F000..U+F0FF, but those codes are not included.
161    ///
162    /// Type 1 and standalone CFF mappings are derived from glyph names when
163    /// `agl` is enabled.
164    pub fn unicodes(&self) -> impl Iterator<Item = (u32, GlyphId)> + 'a {
165        let this = *self;
166        let type1 = match this.font.kind() {
167            crate::model::Kind::Type1(font) => Some(font),
168            _ => None,
169        };
170        let type1 = type1.into_iter().flat_map(iter_type1_unicodes);
171        let standalone_cff = this
172            .font
173            .standalone_cff_unicode_charmap()
174            .into_iter()
175            .flat_map(iter_ps_unicodes);
176        let sfnt = this.font.unicode_charmap().into_iter().flat_map(|map| {
177            map.iter(CmapIterLimits {
178                max_char: char::MAX as u32,
179                glyph_count: this.font.num_glyphs(),
180            })
181        });
182        type1
183            .chain(standalone_cff)
184            .chain(sfnt)
185            .filter(|(_, glyph)| *glyph != GlyphId::NOTDEF)
186    }
187
188    /// Returns whether this font has a Unicode variation-sequence subtable.
189    pub fn has_unicode_variants(&self) -> bool {
190        self.font
191            .unicode_charmap()
192            .is_some_and(|map| map.dmap.vs_subtable.is_some() || map.cmap.vs_subtable.is_some())
193    }
194
195    /// Maps a Unicode codepoint and variation selector to a glyph.
196    ///
197    /// A default UVS entry resolves through [`map_unicode`](Self::map_unicode).
198    /// A mapping to glyph 0 is treated as unmapped.
199    pub fn map_unicode_variant(
200        &self,
201        codepoint: impl Into<u32>,
202        selector: impl Into<u32>,
203    ) -> Option<GlyphId> {
204        self.font
205            .unicode_charmap()?
206            .map_variant(codepoint.into(), selector.into())
207            .filter(|glyph| *glyph != GlyphId::NOTDEF)
208    }
209
210    /// Iterates over Unicode variation sequences and their resolved glyph IDs.
211    ///
212    /// Default variation entries use the selected Unicode mapping, just as
213    /// [`map_unicode_variant`](Self::map_unicode_variant) does.
214    ///
215    /// Yields `(codepoint, selector, glyph)` tuples, where `glyph` is the
216    /// resolved glyph ID.
217    pub fn unicode_variants(&self) -> impl Iterator<Item = (u32, u32, GlyphId)> + 'a {
218        let this = *self;
219        this.font.unicode_charmap().into_iter().flat_map(|map| {
220            map.variant_mappings(CmapIterLimits {
221                max_char: char::MAX as u32,
222                glyph_count: this.font.num_glyphs(),
223            })
224            .filter_map(move |(codepoint, selector, variant)| {
225                let glyph = match variant {
226                    MapVariant::UseDefault => map.map(codepoint)?,
227                    MapVariant::Variant(glyph) => glyph,
228                };
229                (glyph != GlyphId::NOTDEF).then_some((codepoint, selector, glyph))
230            })
231        })
232    }
233
234    /// Returns the selectable character mappings of this font.
235    ///
236    /// More than one mapping can have the same kind. Keep the full
237    /// [`Encoding`] value to select a mapping with [`map_code`](Self::map_code).
238    pub fn encodings(&self) -> impl Iterator<Item = Encoding> + 'a {
239        let this = *self;
240        let sfnt = this
241            .font
242            .encodings()
243            .into_iter()
244            .flat_map(EncodingTables::iter);
245        let type1 = match this.font.kind() {
246            crate::model::Kind::Type1(font) => Some(font),
247            _ => None,
248        };
249        let unicode = type1
250            .filter(|font| font.unicode_charmap().iter().next().is_some())
251            .map(|_| Encoding::type1_unicode());
252        let native = type1
253            .and_then(|font| font.encoding())
254            .map(|encoding| Encoding::type1_native(encoding.predefined()));
255        let standalone_cff = match this.font.kind() {
256            crate::model::Kind::Cff(font, _) => Some(font),
257            _ => None,
258        };
259        let standalone_cff_unicode = standalone_cff
260            .as_ref()
261            .filter(|_| this.has_unicode())
262            .map(|_| Encoding::standalone_cff_unicode());
263        let standalone_cff_native = standalone_cff
264            .as_ref()
265            .filter(|font| !font.is_cid())
266            .and_then(|font| font.encoding())
267            .map(|encoding| Encoding::standalone_cff_native(encoding.predefined()));
268        sfnt.chain(unicode)
269            .chain(native)
270            .chain(standalone_cff_unicode)
271            .chain(standalone_cff_native)
272    }
273
274    /// Maps a character code through a mapping returned by [`encodings`](Self::encodings).
275    ///
276    /// The code is interpreted in the selected encoding, without Unicode
277    /// conversion. The encoding must come from this font. A mapping to glyph 0
278    /// is treated as unmapped.
279    pub fn map_code(&self, encoding: Encoding, code: u32) -> Option<GlyphId> {
280        let glyph = match self.font.kind() {
281            crate::model::Kind::Type1(font) if encoding == Encoding::type1_unicode() => {
282                font.unicode_charmap().map(code)
283            }
284            crate::model::Kind::Type1(font) => {
285                let native = font.encoding()?;
286                if encoding != Encoding::type1_native(native.predefined()) {
287                    return None;
288                }
289                native.map(u8::try_from(code).ok()?)
290            }
291            crate::model::Kind::Cff(_, _) if encoding == Encoding::standalone_cff_unicode() => {
292                self.font.standalone_cff_unicode_charmap()?.map(code)
293            }
294            crate::model::Kind::Cff(font, _) => {
295                if font.is_cid() {
296                    return None;
297                }
298                let native = font.encoding()?;
299                if encoding != Encoding::standalone_cff_native(native.predefined()) {
300                    return None;
301                }
302                native.map(u8::try_from(code).ok()?)
303            }
304            _ => self.font.encodings()?.map(encoding, code),
305        };
306        glyph.filter(|glyph| *glyph != GlyphId::NOTDEF)
307    }
308
309    /// Iterates over character codes and glyph IDs in the selected encoding.
310    ///
311    /// The `encoding` must come from this font's [`encodings`](Self::encodings).
312    /// Codes are returned as stored in the encoding; Mac Roman conversion and
313    /// symbol fallback are only used by the Unicode methods. Mappings to glyph
314    /// 0 are omitted.
315    pub fn codes(&self, encoding: Encoding) -> impl Iterator<Item = (u32, GlyphId)> + 'a {
316        let this = *self;
317        let type1 = match this.font.kind() {
318            crate::model::Kind::Type1(font) => Some(font),
319            _ => None,
320        };
321        let type1_unicode = type1
322            .filter(|_| encoding == Encoding::type1_unicode())
323            .into_iter()
324            .flat_map(iter_type1_unicodes);
325        let type1_native = type1
326            .and_then(|font| font.encoding())
327            .filter(|native| encoding == Encoding::type1_native(native.predefined()))
328            .into_iter()
329            .flat_map(|native| {
330                (0_u32..=255)
331                    .filter_map(move |code| native.map(code as u8).map(|glyph| (code, glyph)))
332            });
333        let standalone_cff = match this.font.kind() {
334            crate::model::Kind::Cff(font, _) => Some(font),
335            _ => None,
336        };
337        let standalone_cff_unicode = (encoding == Encoding::standalone_cff_unicode())
338            .then(|| this.font.standalone_cff_unicode_charmap())
339            .flatten()
340            .into_iter()
341            .flat_map(iter_ps_unicodes);
342        let standalone_cff_native = standalone_cff
343            .as_ref()
344            .filter(|font| !font.is_cid())
345            .and_then(|font| font.encoding())
346            .filter(|native| encoding == Encoding::standalone_cff_native(native.predefined()))
347            .into_iter()
348            .flat_map(|native| {
349                (0_u32..=255)
350                    .filter_map(move |code| native.map(code as u8).map(|glyph| (code, glyph)))
351            });
352        let sfnt = this.font.encodings().into_iter().flat_map(move |tables| {
353            tables.codes(
354                encoding,
355                CmapIterLimits {
356                    max_char: u32::MAX,
357                    glyph_count: this.font.num_glyphs(),
358                },
359            )
360        });
361        type1_unicode
362            .chain(type1_native)
363            .chain(standalone_cff_unicode)
364            .chain(standalone_cff_native)
365            .chain(sfnt)
366            .filter(|(_, glyph)| *glyph != GlyphId::NOTDEF)
367    }
368}
369
370fn map_ps_batched<'o>(
371    map: Option<&crate::ps::UnicodeCharmap>,
372    codepoints: impl Iterator<Item = (u32, &'o mut GlyphId)>,
373) -> usize {
374    let mut mapped = 0;
375    let mut initial = true;
376    for (codepoint, out) in codepoints {
377        *out = map
378            .and_then(|map| map.map(codepoint))
379            .unwrap_or(GlyphId::NOTDEF);
380        initial &= *out != GlyphId::NOTDEF;
381        mapped += usize::from(initial);
382    }
383    mapped
384}
385
386fn iter_type1_unicodes(font: &Type1Font) -> impl Iterator<Item = (u32, GlyphId)> + '_ {
387    iter_ps_unicodes(font.unicode_charmap())
388}
389
390fn iter_ps_unicodes(map: &crate::ps::UnicodeCharmap) -> impl Iterator<Item = (u32, GlyphId)> + '_ {
391    // Each character after the last, as FreeType steps through a charmap
392    // built from glyph names. No glyph maps to zero, so the first is the
393    // one after it. A character mapped to glyph zero ends the walk, as it
394    // ends FreeType's.
395    let mut codepoint = 0;
396    core::iter::from_fn(move || {
397        let (next, glyph) = map.next_char(codepoint)?;
398        codepoint = next;
399        (glyph != GlyphId::NOTDEF).then_some((next, glyph))
400    })
401}
402
403/// The character encoding of a font mapping.
404///
405/// Values match FreeType's `FT_Encoding` tags. Multiple mappings can have
406/// the same kind, so use the full [`Encoding`] to select one.
407#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
408#[repr(u32)]
409pub enum EncodingKind {
410    /// An unclassified mapping.
411    None = 0,
412    /// Microsoft symbol encoding.
413    MsSymbol = 0x7379_6d62,
414    /// Unicode encoding.
415    Unicode = 0x756e_6963,
416    /// Shift JIS encoding.
417    Sjis = 0x736a_6973,
418    /// Simplified Chinese encoding.
419    Prc = 0x6762_2020,
420    /// Traditional Chinese encoding.
421    Big5 = 0x6269_6735,
422    /// Wansung encoding.
423    Wansung = 0x7761_6e73,
424    /// Johab encoding.
425    Johab = 0x6a6f_6861,
426    /// Adobe Standard encoding.
427    AdobeStandard = 0x4144_4f42,
428    /// Adobe Expert encoding.
429    AdobeExpert = 0x4144_4245,
430    /// Adobe Custom encoding.
431    AdobeCustom = 0x4144_4243,
432    /// Adobe Latin 1 encoding.
433    AdobeLatin1 = 0x6c61_7431,
434    /// Apple Roman encoding.
435    AppleRoman = 0x6172_6d6e,
436}
437
438/// A selectable character mapping of a font.
439///
440/// This fixed-size value contains no borrowed data. It is a key for the font
441/// that produced it; passing it to another font has no defined mapping.
442#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
443#[repr(C)]
444pub struct Encoding {
445    kind: EncodingKind,
446    index: u32,
447    platform_id: u16,
448    encoding_id: u16,
449}
450
451impl Encoding {
452    const DMAP: u32 = 1 << 31;
453    const BARE_CFF_UNICODE: u32 = u32::MAX - 3;
454    const BARE_CFF_NATIVE: u32 = u32::MAX - 2;
455    const TYPE1_UNICODE: u32 = u32::MAX - 1;
456    const TYPE1_NATIVE: u32 = u32::MAX;
457
458    /// Returns the encoding kind.
459    pub fn kind(self) -> EncodingKind {
460        self.kind
461    }
462
463    /// Returns the tag of the table that holds this encoding's record, `cmap`
464    /// or `DMAP`, and the index of the record within that table.
465    ///
466    /// Returns `None` for an encoding from a Type 1 or standalone CFF font.
467    pub fn sfnt_index(self) -> Option<(Tag, u32)> {
468        // The DMAP bit is set on the standalone CFF and Type 1 values too, so
469        // records end below them.
470        if self.index >= Self::BARE_CFF_UNICODE {
471            return None;
472        }
473        let tag = if self.index & Self::DMAP != 0 {
474            Tag::new(b"DMAP")
475        } else {
476            Tag::new(b"cmap")
477        };
478        Some((tag, self.index & !Self::DMAP))
479    }
480
481    /// Returns the SFNT platform and encoding identifiers, if applicable.
482    pub fn sfnt_ids(self) -> Option<(u16, u16)> {
483        self.sfnt_index()
484            .map(|_| (self.platform_id, self.encoding_id))
485    }
486
487    pub(crate) fn type1_unicode() -> Self {
488        Self {
489            kind: EncodingKind::Unicode,
490            index: Self::TYPE1_UNICODE,
491            platform_id: 0,
492            encoding_id: 0,
493        }
494    }
495
496    pub(crate) fn type1_native(predefined: Option<PredefinedEncoding>) -> Self {
497        let kind = match predefined {
498            Some(PredefinedEncoding::Standard) => EncodingKind::AdobeStandard,
499            Some(PredefinedEncoding::Expert) => EncodingKind::AdobeExpert,
500            Some(PredefinedEncoding::IsoLatin1) => EncodingKind::AdobeLatin1,
501            None => EncodingKind::AdobeCustom,
502        };
503        Self {
504            kind,
505            index: Self::TYPE1_NATIVE,
506            platform_id: 0,
507            encoding_id: 0,
508        }
509    }
510
511    pub(crate) fn standalone_cff_unicode() -> Self {
512        Self {
513            index: Self::BARE_CFF_UNICODE,
514            ..Self::type1_unicode()
515        }
516    }
517
518    pub(crate) fn standalone_cff_native(predefined: Option<PredefinedEncoding>) -> Self {
519        Self {
520            index: Self::BARE_CFF_NATIVE,
521            ..Self::type1_native(predefined)
522        }
523    }
524}
525
526/// Parsed, selectable SFNT character mappings, indexed by table and encoding record.
527#[derive(Clone, Default, Yokeable)]
528pub(crate) struct EncodingTables<'a> {
529    cmap: Vec<Option<EncodingTable<'a>>>,
530    dmap: Vec<Option<EncodingTable<'a>>>,
531}
532
533#[derive(Clone, Yokeable)]
534struct EncodingTable<'a> {
535    encoding: Encoding,
536    subtable: CmapSubtable<'a>,
537    delta: Option<CmapSubtable<'a>>,
538}
539
540impl<'a> EncodingTables<'a> {
541    pub(crate) fn read(font: &impl TableProvider<'a>) -> Self {
542        let read = |cmap: Option<Cmap<'a>>, source| {
543            cmap.into_iter()
544                .flat_map(|cmap| {
545                    cmap.encoding_records()
546                        .iter()
547                        .enumerate()
548                        .map(move |(index, record)| {
549                            EncodingTable::read(index as u32 | source, record, cmap.offset_data())
550                        })
551                })
552                .collect::<Vec<_>>()
553        };
554        let dmap = font.dmap().ok().map(|table| table.as_cmap());
555        let mut tables = Self {
556            cmap: read(font.cmap().ok(), 0),
557            dmap: read(dmap.clone(), Encoding::DMAP),
558        };
559        let unicode_delta = dmap
560            .and_then(|table| table.best_subtable())
561            .and_then(|(index, _, _)| tables.dmap.get(index as usize)?.as_ref())
562            .filter(|table| table.encoding.kind == EncodingKind::Unicode);
563        for base in tables.cmap.iter_mut().flatten() {
564            let matches = |delta: &&EncodingTable<'_>| {
565                delta.encoding.kind == base.encoding.kind
566                    && (base.encoding.kind != EncodingKind::None
567                        || delta.encoding.sfnt_ids() == base.encoding.sfnt_ids())
568            };
569            let delta = (base.encoding.kind == EncodingKind::Unicode)
570                .then_some(unicode_delta)
571                .flatten()
572                .or_else(|| {
573                    tables
574                        .dmap
575                        .iter()
576                        .flatten()
577                        .find(|delta| delta.encoding.sfnt_ids() == base.encoding.sfnt_ids())
578                })
579                .or_else(|| tables.dmap.iter().flatten().find(matches));
580            base.delta = delta.map(|table| table.subtable.clone());
581        }
582        tables
583    }
584
585    pub(crate) fn iter(&self) -> impl Iterator<Item = Encoding> + '_ {
586        self.cmap
587            .iter()
588            .flatten()
589            .chain(self.dmap.iter().flatten().filter(|delta| {
590                !self.cmap.iter().flatten().any(|base| {
591                    base.encoding.kind == delta.encoding.kind
592                        && (base.encoding.kind != EncodingKind::None
593                            || base.encoding.sfnt_ids() == delta.encoding.sfnt_ids())
594                })
595            }))
596            .map(|table| table.encoding)
597    }
598
599    fn table(&self, encoding: Encoding) -> Option<&EncodingTable<'a>> {
600        let (tag, index) = encoding.sfnt_index()?;
601        let tables = if tag == Tag::new(b"DMAP") {
602            &self.dmap
603        } else {
604            &self.cmap
605        };
606        tables
607            .get(index as usize)?
608            .as_ref()
609            .filter(|table| table.encoding == encoding)
610    }
611
612    pub(crate) fn map(&self, encoding: Encoding, code: u32) -> Option<GlyphId> {
613        let table = self.table(encoding)?;
614        table
615            .delta
616            .as_ref()
617            .and_then(|delta| delta.map_codepoint(code))
618            .filter(|glyph| *glyph != GlyphId::NOTDEF)
619            .or_else(|| table.subtable.map_codepoint(code))
620    }
621
622    fn codes(
623        &self,
624        encoding: Encoding,
625        limits: CmapIterLimits,
626    ) -> impl Iterator<Item = (u32, GlyphId)> + '_ {
627        self.table(encoding).into_iter().flat_map(move |table| {
628            table
629                .delta
630                .as_ref()
631                .into_iter()
632                .flat_map(move |delta| iter_subtable(delta, limits))
633                .filter(|(_, glyph)| *glyph != GlyphId::NOTDEF)
634                .chain(iter_subtable(&table.subtable, limits).filter(|(code, _)| {
635                    table
636                        .delta
637                        .as_ref()
638                        .and_then(|delta| delta.map_codepoint(*code))
639                        .is_none_or(|glyph| glyph == GlyphId::NOTDEF)
640                }))
641        })
642    }
643}
644
645fn iter_subtable<'a>(
646    subtable: &'a CmapSubtable<'a>,
647    limits: CmapIterLimits,
648) -> impl Iterator<Item = (u32, GlyphId)> + 'a {
649    subtable
650        .iter_with_limits(limits)
651        .chain((0_u32..=255).filter_map(move |code| {
652            matches!(subtable, CmapSubtable::Format0(_))
653                .then(|| subtable.map_codepoint(code))
654                .flatten()
655                .map(|glyph| (code, glyph))
656        }))
657}
658
659impl<'a> EncodingTable<'a> {
660    fn read(index: u32, record: &EncodingRecord, data: FontData<'a>) -> Option<Self> {
661        let subtable = record.subtable(data).ok()?;
662        match subtable {
663            CmapSubtable::Format0(_)
664            | CmapSubtable::Format4(_)
665            | CmapSubtable::Format6(_)
666            | CmapSubtable::Format10(_)
667            | CmapSubtable::Format12(_)
668            | CmapSubtable::Format13(_) => {}
669            _ => return None,
670        }
671        let platform = record.platform_id();
672        let id = record.encoding_id();
673        let kind = match platform {
674            PlatformId::Unicode | PlatformId::ISO => EncodingKind::Unicode,
675            PlatformId::Macintosh if id == 0 => EncodingKind::AppleRoman,
676            PlatformId::Windows => match id {
677                0 => EncodingKind::MsSymbol,
678                1 | 10 => EncodingKind::Unicode,
679                2 => EncodingKind::Sjis,
680                3 => EncodingKind::Prc,
681                4 => EncodingKind::Big5,
682                5 => EncodingKind::Wansung,
683                6 => EncodingKind::Johab,
684                _ => EncodingKind::None,
685            },
686            _ => EncodingKind::None,
687        };
688        Some(EncodingTable {
689            encoding: Encoding {
690                kind,
691                index,
692                platform_id: platform as u16,
693                encoding_id: id,
694            },
695            subtable,
696            delta: None,
697        })
698    }
699}
700
701/// The selected cmap subtables and the metadata needed to use them.
702#[derive(Clone, Default, Yokeable)]
703pub(crate) struct SfntUnicodeCharmap<'a> {
704    cmap: UnicodeSubtables<'a>,
705    dmap: UnicodeSubtables<'a>,
706}
707
708#[derive(Clone, Default, Yokeable)]
709struct UnicodeSubtables<'a> {
710    subtable: Option<CmapSubtable<'a>>,
711    vs_subtable: Option<UvsSubtable<'a>>,
712    is_symbol: bool,
713    is_mac_roman: bool,
714}
715
716impl<'a> SfntUnicodeCharmap<'a> {
717    pub(crate) fn read(font: &impl TableProvider<'a>) -> Self {
718        Self {
719            cmap: UnicodeSubtables::read(font.cmap().ok()),
720            dmap: UnicodeSubtables::read(font.dmap().ok().map(|table| table.as_cmap())),
721        }
722    }
723
724    pub(crate) fn map(&self, codepoint: u32) -> Option<GlyphId> {
725        self.dmap
726            .map(codepoint)
727            .filter(|glyph| *glyph != GlyphId::NOTDEF)
728            .or_else(|| self.cmap.map(codepoint))
729    }
730
731    /// Writes the mapping of each codepoint to its slot and returns the
732    /// number of initial consecutive nonzero mappings.
733    ///
734    /// `DMAP` overrides `cmap` codepoint by codepoint, so only a font without
735    /// one runs through the cached lookups of a single subtable.
736    fn map_batched<'o>(&self, codepoints: impl Iterator<Item = (u32, &'o mut GlyphId)>) -> usize {
737        if self.dmap.subtable.is_none() {
738            return self.cmap.map_batched(codepoints);
739        }
740        let mut mapped = 0;
741        let mut initial = true;
742        for (codepoint, out) in codepoints {
743            *out = self.map(codepoint).unwrap_or(GlyphId::NOTDEF);
744            initial &= *out != GlyphId::NOTDEF;
745            mapped += usize::from(initial);
746        }
747        mapped
748    }
749
750    pub(crate) fn map_variant(&self, codepoint: u32, selector: u32) -> Option<GlyphId> {
751        let variant = self
752            .dmap
753            .vs_subtable
754            .as_ref()
755            .and_then(|table| table.map_variant(codepoint, selector))
756            .or_else(|| {
757                self.cmap
758                    .vs_subtable
759                    .as_ref()?
760                    .map_variant(codepoint, selector)
761            })?;
762        match variant {
763            MapVariant::UseDefault => self.map(codepoint),
764            MapVariant::Variant(glyph) => Some(glyph),
765        }
766    }
767
768    fn iter(&self, limits: CmapIterLimits) -> impl Iterator<Item = (u32, GlyphId)> + '_ {
769        self.dmap
770            .iter(limits)
771            .filter(|(_, glyph)| *glyph != GlyphId::NOTDEF)
772            .chain(self.cmap.iter(limits).filter(|(codepoint, _)| {
773                self.dmap
774                    .map(*codepoint)
775                    .is_none_or(|glyph| glyph == GlyphId::NOTDEF)
776            }))
777    }
778
779    fn variant_mappings(
780        &self,
781        limits: CmapIterLimits,
782    ) -> impl Iterator<Item = (u32, u32, MapVariant)> + '_ {
783        self.dmap
784            .vs_subtable
785            .as_ref()
786            .into_iter()
787            .flat_map(move |table| table.iter_with_limits(limits))
788            .chain(
789                self.cmap
790                    .vs_subtable
791                    .as_ref()
792                    .into_iter()
793                    .flat_map(move |table| table.iter_with_limits(limits))
794                    .filter(|(codepoint, selector, _)| {
795                        self.dmap
796                            .vs_subtable
797                            .as_ref()
798                            .and_then(|table| table.map_variant(*codepoint, *selector))
799                            .is_none()
800                    }),
801            )
802    }
803}
804
805impl<'a> UnicodeSubtables<'a> {
806    fn read(cmap: Option<Cmap<'a>>) -> Self {
807        let Some(cmap) = cmap else {
808            return Self::default();
809        };
810        let (subtable, is_symbol, is_mac_roman) = cmap
811            .best_subtable()
812            .map(|(_, record, subtable)| {
813                (Some(subtable), record.is_symbol(), record.is_mac_roman())
814            })
815            .unwrap_or((None, false, false));
816        Self {
817            subtable,
818            vs_subtable: cmap.uvs_subtable().map(|(_, subtable)| subtable),
819            is_symbol,
820            is_mac_roman,
821        }
822    }
823
824    pub(crate) fn map(&self, codepoint: u32) -> Option<GlyphId> {
825        let subtable = self.subtable.as_ref()?;
826        self.map_with(codepoint, |codepoint| subtable.map_codepoint(codepoint))
827    }
828
829    fn map_with(
830        &self,
831        mut codepoint: u32,
832        mut lookup: impl FnMut(u32) -> Option<GlyphId>,
833    ) -> Option<GlyphId> {
834        // A codepoint Mac Roman cannot encode maps to nothing.
835        if self.is_mac_roman && codepoint > 0x7f {
836            codepoint = char::from_u32(codepoint)
837                .and_then(|value| MacRomanMapping.encode(value))
838                .map(u32::from)?;
839        }
840        let result = lookup(codepoint);
841        if result.is_none_or(|glyph| glyph == GlyphId::NOTDEF)
842            && self.is_symbol
843            && codepoint <= 0xff
844        {
845            return lookup(0xf000 + codepoint);
846        }
847        result
848    }
849
850    fn map_batched<'o>(&self, codepoints: impl Iterator<Item = (u32, &'o mut GlyphId)>) -> usize {
851        let mut mapped = 0;
852        let mut initial = true;
853        match self.subtable.as_ref() {
854            Some(CmapSubtable::Format4(subtable)) => {
855                let mut cached = None;
856                for (codepoint, out) in codepoints {
857                    *out = self
858                        .map_with(codepoint, |codepoint| {
859                            subtable.map_codepoint_cached(codepoint, &mut cached)
860                        })
861                        .unwrap_or(GlyphId::NOTDEF);
862                    initial &= *out != GlyphId::NOTDEF;
863                    mapped += usize::from(initial);
864                }
865            }
866            Some(CmapSubtable::Format12(subtable)) => {
867                let mut cached = None;
868                for (codepoint, out) in codepoints {
869                    *out = self
870                        .map_with(codepoint, |codepoint| {
871                            subtable.map_codepoint_cached(codepoint, &mut cached)
872                        })
873                        .unwrap_or(GlyphId::NOTDEF);
874                    initial &= *out != GlyphId::NOTDEF;
875                    mapped += usize::from(initial);
876                }
877            }
878            Some(subtable) => {
879                for (codepoint, out) in codepoints {
880                    *out = self
881                        .map_with(codepoint, |codepoint| subtable.map_codepoint(codepoint))
882                        .unwrap_or(GlyphId::NOTDEF);
883                    initial &= *out != GlyphId::NOTDEF;
884                    mapped += usize::from(initial);
885                }
886            }
887            None => {
888                for (_, out) in codepoints {
889                    *out = GlyphId::NOTDEF;
890                }
891            }
892        }
893        mapped
894    }
895
896    fn iter(&self, limits: CmapIterLimits) -> impl Iterator<Item = (u32, GlyphId)> + '_ {
897        let roman = self
898            .is_mac_roman
899            .then_some(self)
900            .into_iter()
901            .flat_map(|map| {
902                (u8::MIN..=u8::MAX)
903                    .map(|code| MacRomanMapping.decode(code) as u32)
904                    .filter_map(move |codepoint| map.map(codepoint).map(|glyph| (codepoint, glyph)))
905            });
906        let normal = (!self.is_mac_roman)
907            .then_some(self)
908            .into_iter()
909            .flat_map(move |map| {
910                map.subtable
911                    .as_ref()
912                    .into_iter()
913                    .flat_map(move |subtable| subtable.iter_with_limits(limits))
914            });
915        roman.chain(normal)
916    }
917}
918
919#[cfg(test)]
920mod tests {
921    use super::*;
922    use crate::{
923        model::{Blob, Font},
924        FontRef,
925    };
926    use alloc::{sync::Arc, vec};
927    use core::sync::atomic::{AtomicUsize, Ordering};
928    use types::Tag;
929
930    // The iterators borrow the font, not the charmap they came from, so
931    // they outlive it.
932    #[test]
933    fn iterators_outlive_the_charmap() {
934        let font = Font::new(font_test_data::CMAP14_FONT1, 0).unwrap();
935        let unicodes = font.charmap().unicodes();
936        let variants = font.charmap().unicode_variants();
937        let encodings: Vec<_> = font.charmap().encodings().collect();
938        let codes = font.charmap().codes(encodings[0]);
939        assert!(unicodes.count() > 0);
940        assert!(variants.count() > 0);
941        assert!(codes.count() > 0);
942    }
943
944    fn check_unicode_batch(data: &'static [u8], codepoints: &[u32]) {
945        let font = Font::new(data, 0).unwrap();
946        let charmap = font.charmap();
947        let expected: Vec<_> = codepoints
948            .iter()
949            .map(|&codepoint| charmap.map_unicode(codepoint).unwrap_or(GlyphId::NOTDEF))
950            .collect();
951        let mut actual = vec![GlyphId::new(123); codepoints.len()];
952        let mapped = charmap.map_unicode_batched(codepoints.iter().copied().zip(actual.iter_mut()));
953        assert_eq!(actual, expected);
954        assert_eq!(
955            mapped,
956            expected
957                .iter()
958                .take_while(|&&glyph| glyph != GlyphId::NOTDEF)
959                .count()
960        );
961    }
962
963    #[test]
964    fn batched_unicode_matches_scalar_across_cmap_formats() {
965        let codepoints = [
966            0, 0x20, 0x41, 0x42, 0xe9, 0x400, 0xf000, 0xffff, 0x10000, 0x10ffff, 0x110000, 0x41,
967            0x20, 0,
968        ];
969        for data in [
970            font_test_data::TINOS_SUBSET,
971            font_test_data::CMAP12_FONT1,
972            font_test_data::CMAP4_SYMBOL_PUA,
973            font_test_data::CMAP6,
974            font_test_data::NOTO_SERIF_DISPLAY_TRIMMED,
975            font_test_data::NOTO_COLOR_EMOJI_FLAGS,
976        ] {
977            check_unicode_batch(data, &codepoints);
978        }
979        check_unicode_batch(
980            font_test_data::type1::NOTO_SERIF_REGULAR_SUBSET_PFB,
981            &codepoints,
982        );
983
984        let font = Font::new(font_test_data::TINOS_SUBSET, 0).unwrap();
985        let mut from_chars = [GlyphId::NOTDEF; 3];
986        let mapped = font.charmap().map_unicode_batched(
987            ['A', 'B', 'C']
988                .into_iter()
989                .map(u32::from)
990                .zip(from_chars.iter_mut()),
991        );
992        assert_eq!(
993            from_chars,
994            ['A', 'B', 'C'].map(|ch| font.charmap().map_unicode(ch).unwrap_or(GlyphId::NOTDEF))
995        );
996        assert_eq!(
997            mapped,
998            from_chars
999                .iter()
1000                .take_while(|&&glyph| glyph != GlyphId::NOTDEF)
1001                .count()
1002        );
1003    }
1004
1005    #[test]
1006    fn batched_unicode_handles_large_ordered_and_reversed_inputs() {
1007        let codepoints: Vec<_> = (0..=0x11000).step_by(7).collect();
1008        for data in [font_test_data::TINOS_SUBSET, font_test_data::CMAP12_FONT1] {
1009            check_unicode_batch(data, &codepoints);
1010            check_unicode_batch(data, &codepoints.iter().copied().rev().collect::<Vec<_>>());
1011        }
1012    }
1013
1014    // DMAP overrides cmap codepoint by codepoint and supplies mappings cmap
1015    // lacks, so a batch has to agree with single lookups when it is present.
1016    #[test]
1017    fn batched_unicode_honors_dmap() {
1018        use font_test_data::cmap::{font_with_cmaps, format12, table};
1019        let cmap = format12(&[(65, 1), (66, 2), (67, 3)]);
1020        let dmap = format12(&[(66, 9), (68, 10)]);
1021        let data = font_with_cmaps(
1022            Some(&table(&[(3, 10, cmap.as_slice())])),
1023            Some(&table(&[(3, 10, dmap.as_slice())])),
1024        );
1025        let font = Font::new(data, 0).unwrap();
1026        let charmap = font.charmap();
1027        let codepoints = [65, 66, 67, 68, 69, 66];
1028        let mut actual = [GlyphId::new(123); 6];
1029        let mapped = charmap.map_unicode_batched(codepoints.iter().copied().zip(actual.iter_mut()));
1030        let expected = [1, 9, 3, 10, 0, 9].map(GlyphId::new);
1031        assert_eq!(actual, expected);
1032        assert_eq!(mapped, 4);
1033        for (codepoint, glyph) in codepoints.iter().zip(expected) {
1034            assert_eq!(
1035                charmap.map_unicode(*codepoint).unwrap_or(GlyphId::NOTDEF),
1036                glyph
1037            );
1038        }
1039    }
1040
1041    #[test]
1042    fn cmap15_selection_and_dmap_variants() {
1043        use font_test_data::cmap::{font_with_cmaps, format12, format14, format15, table};
1044        let nominal = format12(&[(65, 1)]);
1045        let old = format14(0xfe0f, &[], &[(65, 2), (67, 3)]);
1046        let new = format15(0xfe0f, &[65], &[(66, 70000), (68, 0xffffff)]);
1047        for records in [
1048            vec![
1049                (3, 10, nominal.as_slice()),
1050                (0, 5, old.as_slice()),
1051                (0, 5, new.as_slice()),
1052            ],
1053            vec![
1054                (3, 10, nominal.as_slice()),
1055                (0, 5, new.as_slice()),
1056                (0, 5, old.as_slice()),
1057            ],
1058        ] {
1059            let base = table(&records);
1060            let delta = table(&[(0, 5, old.as_slice())]);
1061            for (dmap, expected) in [
1062                (None, vec![(65, 1), (66, 70000), (68, 0xffffff)]),
1063                (
1064                    Some(delta.as_slice()),
1065                    vec![(65, 2), (66, 70000), (67, 3), (68, 0xffffff)],
1066                ),
1067            ] {
1068                let data = font_with_cmaps(Some(&base), dmap);
1069                let font = Font::new(data, 0).unwrap();
1070                let charmap = font.charmap();
1071                assert!(charmap.has_unicode_variants());
1072                let mut mappings: Vec<_> = charmap.unicode_variants().collect();
1073                mappings.sort_unstable();
1074                assert_eq!(
1075                    mappings,
1076                    expected
1077                        .iter()
1078                        .map(|&(ch, glyph)| (ch, 0xfe0f, GlyphId::new(glyph)))
1079                        .collect::<Vec<_>>()
1080                );
1081                for (ch, selector, glyph) in mappings {
1082                    assert_eq!(charmap.map_unicode_variant(ch, selector), Some(glyph));
1083                }
1084            }
1085        }
1086        let delta = table(&[(0, 5, new.as_slice())]);
1087        let data = font_with_cmaps(None, Some(&delta));
1088        let font = Font::new(data, 0).unwrap();
1089        assert_eq!(
1090            font.charmap().map_unicode_variant(66u32, 0xfe0fu32),
1091            Some(GlyphId::new(70000))
1092        );
1093    }
1094
1095    #[test]
1096    fn dmap_unicode_lookups_and_iterators() {
1097        use font_test_data::cmap::{font_with_cmaps, format12, format14, table};
1098        let base = table(&[
1099            (3, 10, &format12(&[(65, 1), (66, 2), (68, 5)])),
1100            (0, 5, &format14(0xfe0f, &[66], &[(65, 5), (67, 6)])),
1101        ]);
1102        let delta = table(&[
1103            (0, 4, &format12(&[(65, 3), (66, 0), (67, 4)])),
1104            (0, 5, &format14(0xfe0f, &[65], &[(66, 4), (68, 7)])),
1105        ]);
1106        let data = font_with_cmaps(Some(&base), Some(&delta));
1107        let font = Font::new(data, 0).unwrap();
1108        let charmap = font.charmap();
1109        assert!(charmap.has_unicode());
1110        assert!(charmap.has_unicode_variants());
1111        let mut mappings: Vec<_> = charmap.unicodes().collect();
1112        mappings.sort_unstable();
1113        assert_eq!(
1114            mappings,
1115            vec![
1116                (65, GlyphId::new(3)),
1117                (66, GlyphId::new(2)),
1118                (67, GlyphId::new(4)),
1119                (68, GlyphId::new(5))
1120            ]
1121        );
1122        for (ch, glyph) in mappings {
1123            assert_eq!(charmap.map_unicode(ch), Some(glyph));
1124        }
1125        for encoding in charmap.encodings() {
1126            assert_eq!(encoding.sfnt_index().unwrap().0, Tag::new(b"cmap"));
1127            assert_eq!(charmap.map_code(encoding, 65), Some(GlyphId::new(3)));
1128            let mut codes: Vec<_> = charmap.codes(encoding).collect();
1129            codes.sort_unstable();
1130            assert_eq!(
1131                codes,
1132                vec![
1133                    (65, GlyphId::new(3)),
1134                    (66, GlyphId::new(2)),
1135                    (67, GlyphId::new(4)),
1136                    (68, GlyphId::new(5))
1137                ]
1138            );
1139        }
1140        let mut variants: Vec<_> = charmap.unicode_variants().collect();
1141        variants.sort_unstable();
1142        assert_eq!(
1143            variants,
1144            vec![
1145                (65, 0xfe0f, GlyphId::new(3)),
1146                (66, 0xfe0f, GlyphId::new(4)),
1147                (67, 0xfe0f, GlyphId::new(6)),
1148                (68, 0xfe0f, GlyphId::new(7))
1149            ]
1150        );
1151        for (ch, selector, glyph) in variants {
1152            assert_eq!(charmap.map_unicode_variant(ch, selector), Some(glyph));
1153        }
1154        let data = font_with_cmaps(None, Some(&delta));
1155        let font = Font::new(data, 0).unwrap();
1156        assert!(font.charmap().has_unicode());
1157        assert_eq!(font.charmap().map_unicode('A'), Some(GlyphId::new(3)));
1158        assert_eq!(
1159            font.charmap().map_unicode_variant('A', 0xfe0fu32),
1160            Some(GlyphId::new(3))
1161        );
1162        let encoding = font.charmap().encodings().next().unwrap();
1163        // The index is into DMAP, not cmap.
1164        assert_eq!(encoding.sfnt_index(), Some((Tag::new(b"DMAP"), 0)));
1165        assert_eq!(font.charmap().map_code(encoding, 65), Some(GlyphId::new(3)));
1166    }
1167
1168    #[test]
1169    fn standalone_cff_batch_matches_scalar_and_writes_after_a_miss() {
1170        let sfnt = FontRef::new(font_test_data::NOTO_SERIF_DISPLAY_TRIMMED).unwrap();
1171        let cff = sfnt.cff().unwrap().offset_data().as_bytes();
1172        let codepoints = ['i' as u32, 0x10ffff, 'i' as u32];
1173        check_unicode_batch(cff, &codepoints);
1174
1175        let font = Font::new(cff, 0).unwrap();
1176        let mut glyphs = [GlyphId::new(123); 3];
1177        let mapped = font
1178            .charmap()
1179            .map_unicode_batched(codepoints.into_iter().zip(glyphs.iter_mut()));
1180        #[cfg(feature = "agl")]
1181        {
1182            assert_eq!(mapped, 1);
1183            assert_eq!(glyphs, [GlyphId::new(1), GlyphId::NOTDEF, GlyphId::new(1)]);
1184        }
1185        #[cfg(not(feature = "agl"))]
1186        {
1187            assert_eq!(mapped, 0);
1188            assert_eq!(glyphs, [GlyphId::NOTDEF; 3]);
1189        }
1190    }
1191
1192    #[test]
1193    fn cid_cff_has_no_glyph_name_unicode_mapping() {
1194        let sfnt = FontRef::new(font_test_data::NOTO_SANS_JP_CFF).unwrap();
1195        let cff = sfnt.cff().unwrap().offset_data().as_bytes();
1196        let font = Font::new(cff, 0).unwrap();
1197        let charmap = font.charmap();
1198        assert!(!charmap.has_unicode());
1199        assert_eq!(charmap.map_unicode('A'), None);
1200        assert_eq!(charmap.encodings().count(), 0);
1201        assert_eq!(
1202            charmap.map_code(Encoding::standalone_cff_native(None), 65),
1203            None
1204        );
1205        let mut glyphs = [GlyphId::new(123); 2];
1206        assert_eq!(
1207            charmap.map_unicode_batched([0x41, 0x42].into_iter().zip(glyphs.iter_mut())),
1208            0
1209        );
1210        assert_eq!(glyphs, [GlyphId::NOTDEF; 2]);
1211    }
1212
1213    #[test]
1214    fn maps_a_selected_unicode_subtable() {
1215        let font = Font::new(font_test_data::VAZIRMATN_VAR, 0).unwrap();
1216        assert!(font.charmap().has_unicode());
1217        assert!(!font.charmap().unicode_is_symbol());
1218        assert!(!font.charmap().unicode_is_mac_roman());
1219        assert_eq!(font.charmap().map_unicode('A'), Some(GlyphId::new(1)));
1220        assert_eq!(
1221            font.charmap().map_unicode('A' as u32),
1222            Some(GlyphId::new(1))
1223        );
1224        assert_eq!(font.charmap().map_unicode('B'), None);
1225        assert_eq!(font.charmap().map_unicode(0xffff_u32), None);
1226        assert!(font
1227            .charmap()
1228            .unicodes()
1229            .any(|entry| entry == ('A' as u32, GlyphId::new(1))));
1230    }
1231
1232    #[test]
1233    fn selectable_mappings_are_fixed_size_and_keep_record_identity() {
1234        assert_eq!(core::mem::size_of::<Encoding>(), 12);
1235        assert_eq!(EncodingKind::Unicode as u32, u32::from_be_bytes(*b"unic"));
1236        assert_eq!(
1237            EncodingKind::AppleRoman as u32,
1238            u32::from_be_bytes(*b"armn")
1239        );
1240
1241        let data = font_test_data::VORG;
1242        let font = Font::new(data, 0).unwrap();
1243        let direct = FontRef::new(data).unwrap();
1244        let cmap = direct.cmap().unwrap();
1245        let encodings: Vec<_> = font.charmap().encodings().collect();
1246        for encoding in &encodings {
1247            let (tag, index) = encoding.sfnt_index().unwrap();
1248            assert_eq!(tag, Tag::new(b"cmap"));
1249            let index = index as usize;
1250            let record = &cmap.encoding_records()[index];
1251            assert_eq!(
1252                encoding.sfnt_ids(),
1253                Some((record.platform_id() as u16, record.encoding_id()))
1254            );
1255            let subtable = record.subtable(cmap.offset_data()).unwrap();
1256            assert_eq!(
1257                font.charmap().map_code(*encoding, 'A' as u32),
1258                subtable.map_codepoint('A')
1259            );
1260            for (code, glyph) in font.charmap().codes(*encoding).take(3) {
1261                assert_eq!(font.charmap().map_code(*encoding, code), Some(glyph));
1262            }
1263        }
1264        assert!(
1265            encodings
1266                .iter()
1267                .filter(|encoding| encoding.kind() == EncodingKind::Unicode)
1268                .count()
1269                > 1
1270        );
1271        assert!(encodings.windows(2).all(|pair| pair[0] != pair[1]));
1272    }
1273
1274    #[test]
1275    #[cfg(feature = "agl")]
1276    fn type1_exposes_unicode_and_native_mappings() {
1277        let font = Font::new(font_test_data::type1::NOTO_SERIF_REGULAR_SUBSET_PFB, 0).unwrap();
1278        let encodings: Vec<_> = font.charmap().encodings().collect();
1279        let unicode = encodings
1280            .iter()
1281            .find(|encoding| encoding.kind() == EncodingKind::Unicode)
1282            .unwrap();
1283        assert_eq!(unicode.sfnt_index(), None);
1284        assert_eq!(
1285            font.charmap().map_code(*unicode, 'H' as u32),
1286            font.charmap().map_unicode('H')
1287        );
1288        let native = encodings
1289            .iter()
1290            .find(|encoding| encoding.kind() != EncodingKind::Unicode)
1291            .unwrap();
1292        assert_eq!(native.sfnt_index(), None);
1293        assert_eq!(
1294            font.charmap().map_code(*native, b'H' as u32),
1295            Some(GlyphId::new(1))
1296        );
1297        assert_eq!(font.charmap().map_code(*native, 256), None);
1298        assert!(font
1299            .charmap()
1300            .unicodes()
1301            .any(|entry| entry == ('H' as u32, GlyphId::new(1))));
1302        assert!(font
1303            .charmap()
1304            .codes(*native)
1305            .any(|entry| entry == (b'H' as u32, GlyphId::new(1))));
1306    }
1307
1308    #[test]
1309    fn selected_subtables_are_cached_across_font_instances() {
1310        let cmap_reads = Arc::new(AtomicUsize::new(0));
1311        let count = cmap_reads.clone();
1312        let source: Arc<dyn Fn(Tag) -> Option<Blob> + Send + Sync> = Arc::new(move |tag| {
1313            if tag == Tag::new(b"cmap") {
1314                count.fetch_add(1, Ordering::Relaxed);
1315            }
1316            let font = FontRef::new(font_test_data::VAZIRMATN_VAR).ok()?;
1317            font.table_data(tag)
1318                .map(|data| Blob::from(data.as_bytes().to_vec()))
1319        });
1320        let font = Font::new(source, 0).unwrap();
1321        let charmap = font.charmap();
1322        assert_eq!(cmap_reads.load(Ordering::Relaxed), 0);
1323        assert_eq!(charmap.map_unicode('A'), Some(GlyphId::new(1)));
1324        let first_reads = cmap_reads.load(Ordering::Relaxed);
1325        assert!(first_reads > 0);
1326        assert_eq!(font.charmap().map_unicode('A'), Some(GlyphId::new(1)));
1327        assert_eq!(
1328            font.default_instance().charmap().map_unicode('A'),
1329            Some(GlyphId::new(1))
1330        );
1331        let varied = font
1332            .instance_builder()
1333            .variations([("wght", 500.0)])
1334            .build();
1335        assert!(!varied.normalized_coords().is_empty());
1336        assert_eq!(varied.charmap().map_unicode('A'), Some(GlyphId::new(1)));
1337        assert_eq!(cmap_reads.load(Ordering::Relaxed), first_reads);
1338    }
1339
1340    #[test]
1341    fn resolves_default_and_non_default_variants() {
1342        let font = Font::new(font_test_data::CMAP14_FONT1, 0).unwrap();
1343        assert!(font.charmap().has_unicode_variants());
1344        let selector = '\u{e0100}';
1345        assert_eq!(
1346            font.charmap().map_unicode_variant('\u{4e00}', selector),
1347            font.charmap().map_unicode('\u{4e00}')
1348        );
1349        assert_eq!(
1350            font.charmap().map_unicode_variant('\u{4e08}', selector),
1351            Some(GlyphId::new(25))
1352        );
1353        assert_eq!(
1354            font.charmap()
1355                .map_unicode_variant(0x4e08_u32, selector as u32),
1356            Some(GlyphId::new(25))
1357        );
1358        assert_eq!(font.charmap().map_unicode_variant('a', selector), None);
1359        let variants: Vec<_> = font.charmap().unicode_variants().collect();
1360        assert!(variants.contains(&(
1361            0x4e00,
1362            selector as u32,
1363            font.charmap().map_unicode(0x4e00_u32).unwrap()
1364        )));
1365        assert!(variants.contains(&(0x4e08, selector as u32, GlyphId::new(25))));
1366    }
1367
1368    #[test]
1369    fn symbol_encoding_falls_back_to_the_private_use_page() {
1370        let data = font_test_data::CMAP4_SYMBOL_PUA;
1371        let direct = FontRef::new(data).unwrap();
1372        let (_, _, subtable) = direct.cmap().unwrap().best_subtable().unwrap();
1373        let (codepoint, glyph) = (0xf000..=0xf0ff)
1374            .find_map(|codepoint| {
1375                subtable
1376                    .map_codepoint(codepoint)
1377                    .map(|glyph| (codepoint, glyph))
1378            })
1379            .unwrap();
1380        let character = char::from_u32(codepoint - 0xf000).unwrap();
1381        assert_eq!(subtable.map_codepoint(character), None);
1382        let font = Font::new(data, 0).unwrap();
1383        assert!(font.charmap().has_unicode());
1384        assert!(font.charmap().unicode_is_symbol());
1385        assert!(!font.charmap().unicode_is_mac_roman());
1386        assert_eq!(font.charmap().map_unicode(character), Some(glyph));
1387        let unicodes: Vec<_> = font.charmap().unicodes().collect();
1388        assert!(unicodes.contains(&(codepoint, glyph)));
1389        assert!(!unicodes.contains(&(character as u32, glyph)));
1390        let encoding = font.charmap().encodings().next().unwrap();
1391        assert_eq!(font.charmap().map_code(encoding, codepoint), Some(glyph));
1392        assert!(font
1393            .charmap()
1394            .codes(encoding)
1395            .any(|entry| entry == (codepoint, glyph)));
1396    }
1397
1398    #[test]
1399    fn symbol_encoding_retries_when_direct_mapping_is_notdef() {
1400        let mut cmap = vec![0, 0, 0, 1, 0, 3, 0, 0, 0, 0, 0, 12];
1401        // Format 12: U+0041 maps to .notdef, while U+F041 maps to glyph 3.
1402        cmap.extend_from_slice(&[0, 12, 0, 0]);
1403        cmap.extend_from_slice(&40_u32.to_be_bytes());
1404        cmap.extend_from_slice(&0_u32.to_be_bytes());
1405        cmap.extend_from_slice(&2_u32.to_be_bytes());
1406        for (codepoint, glyph) in [(0x41_u32, 0_u32), (0xf041, 3)] {
1407            cmap.extend_from_slice(&codepoint.to_be_bytes());
1408            cmap.extend_from_slice(&codepoint.to_be_bytes());
1409            cmap.extend_from_slice(&glyph.to_be_bytes());
1410        }
1411        let source: Arc<dyn Fn(Tag) -> Option<Blob> + Send + Sync> = Arc::new(move |tag| {
1412            if tag == Tag::new(b"cmap") {
1413                return Some(Blob::from(cmap.clone()));
1414            }
1415            let font = FontRef::new(font_test_data::TINOS_SUBSET).ok()?;
1416            font.table_data(tag)
1417                .map(|data| Blob::from(data.as_bytes().to_vec()))
1418        });
1419        let font = Font::new(source, 0).unwrap();
1420        let charmap = font.charmap();
1421        assert_eq!(charmap.map_unicode('A'), Some(GlyphId::new(3)));
1422        let mut batched = [GlyphId::NOTDEF; 3];
1423        let mapped = charmap.map_unicode_batched(
1424            ['A', 'B', 'A']
1425                .into_iter()
1426                .map(u32::from)
1427                .zip(batched.iter_mut()),
1428        );
1429        assert_eq!(batched, [GlyphId::new(3), GlyphId::NOTDEF, GlyphId::new(3)]);
1430        assert_eq!(mapped, 1);
1431        let unicodes: Vec<_> = charmap.unicodes().collect();
1432        assert!(unicodes.contains(&(0xf041, GlyphId::new(3))));
1433        assert!(!unicodes.iter().any(|(code, _)| *code == 0x41));
1434        let encoding = charmap.encodings().next().unwrap();
1435        assert_eq!(charmap.map_code(encoding, 0x41), None);
1436        assert!(!charmap.codes(encoding).any(|(code, _)| code == 0x41));
1437    }
1438
1439    /// Creates a font with Tinos's tables and one format 0 `cmap` subtable.
1440    ///
1441    /// The subtable has the given `platform` and `encoding` IDs and maps
1442    /// each code to the glyph at that position in `glyphs`.
1443    fn font_with_format0_cmap(platform: u8, encoding: u8, glyphs: [u8; 256]) -> Font {
1444        let mut cmap = vec![0, 0, 0, 1, 0, platform, 0, encoding, 0, 0, 0, 12];
1445        cmap.extend_from_slice(&[0, 0, 1, 6, 0, 0]);
1446        cmap.extend_from_slice(&glyphs);
1447        let source: Arc<dyn Fn(Tag) -> Option<Blob> + Send + Sync> = Arc::new(move |tag| {
1448            if tag == Tag::new(b"cmap") {
1449                return Some(Blob::from(cmap.clone()));
1450            }
1451            let font = FontRef::new(font_test_data::TINOS_SUBSET).ok()?;
1452            font.table_data(tag)
1453                .map(|data| Blob::from(data.as_bytes().to_vec()))
1454        });
1455        Font::new(source, 0).unwrap()
1456    }
1457
1458    #[test]
1459    fn mac_roman_encoding_translates_unicode() {
1460        let mut glyphs = [0u8; 256];
1461        glyphs[0x8e] = 3; // U+00E9, encoded as Mac Roman 0x8E.
1462        glyphs[0] = 5;
1463        let font = font_with_format0_cmap(1, 0, glyphs);
1464        assert!(font.charmap().has_unicode());
1465        assert!(!font.charmap().unicode_is_symbol());
1466        assert!(font.charmap().unicode_is_mac_roman());
1467        assert_eq!(font.charmap().map_unicode('é'), Some(GlyphId::new(3)));
1468        assert_eq!(font.charmap().map_unicode('A'), None);
1469        // Mac Roman cannot encode U+0100, so it maps to nothing rather than
1470        // to what code 0 maps to.
1471        assert_eq!(font.charmap().map_unicode('\u{100}'), None);
1472        let mut batch = [GlyphId::new(9)];
1473        font.charmap()
1474            .map_unicode_batched([0x100u32].into_iter().zip(batch.iter_mut()));
1475        assert_eq!(batch, [GlyphId::NOTDEF]);
1476        assert!(font
1477            .charmap()
1478            .unicodes()
1479            .any(|entry| entry == ('é' as u32, GlyphId::new(3))));
1480        let encoding = font.charmap().encodings().next().unwrap();
1481        assert_eq!(
1482            font.charmap().map_code(encoding, 0x8e),
1483            Some(GlyphId::new(3))
1484        );
1485        assert!(font
1486            .charmap()
1487            .codes(encoding)
1488            .any(|entry| entry == (0x8e, GlyphId::new(3))));
1489    }
1490
1491    #[test]
1492    fn format0_mappings_are_listed_once() {
1493        let mut glyphs = [0u8; 256];
1494        glyphs[0x41] = 3;
1495        glyphs[0x42] = 4;
1496        let font = font_with_format0_cmap(3, 1, glyphs);
1497        assert!(!font.charmap().unicode_is_mac_roman());
1498        let mappings: Vec<_> = font.charmap().unicodes().collect();
1499        let mut unique = mappings.clone();
1500        unique.sort();
1501        unique.dedup();
1502        assert_eq!(mappings.len(), unique.len());
1503        assert!(mappings.contains(&(0x41, GlyphId::new(3))));
1504        assert!(mappings.contains(&(0x42, GlyphId::new(4))));
1505    }
1506
1507    #[test]
1508    #[cfg(feature = "agl")]
1509    fn type1_uses_its_unicode_charmap() {
1510        let font = Font::new(font_test_data::type1::NOTO_SERIF_REGULAR_SUBSET_PFB, 0).unwrap();
1511        assert!(font.charmap().has_unicode());
1512        assert!(!font.charmap().unicode_is_symbol());
1513        assert!(!font.charmap().unicode_is_mac_roman());
1514        assert!(!font.charmap().has_unicode_variants());
1515        assert_eq!(font.charmap().map_unicode('H'), Some(GlyphId::new(1)));
1516        assert_eq!(font.charmap().map_unicode('a'), None);
1517        assert_eq!(font.charmap().map_unicode_variant('H', '\u{fe0f}'), None);
1518    }
1519}