Skip to main content

read_fonts/tables/
cmap.rs

1//! The [cmap](https://docs.microsoft.com/en-us/typography/opentype/spec/cmap) table
2
3include!("../../generated/generated_cmap.rs");
4
5#[cfg(feature = "std")]
6use crate::collections::IntSet;
7use crate::{FontRef, TableProvider};
8use std::ops::Range;
9
10// See <https://docs.microsoft.com/en-us/typography/opentype/spec/cmap#windows-platform-platform-id--3>
11const WINDOWS_SYMBOL_ENCODING: u16 = 0;
12const WINDOWS_UNICODE_BMP_ENCODING: u16 = 1;
13const WINDOWS_UNICODE_FULL_ENCODING: u16 = 10;
14
15// See <https://docs.microsoft.com/en-us/typography/opentype/spec/name#platform-specific-encoding-and-language-ids-unicode-platform-platform-id--0>
16const UNICODE_1_0_ENCODING: u16 = 0;
17const UNICODE_1_1_ENCODING: u16 = 1;
18const UNICODE_ISO_ENCODING: u16 = 2;
19const UNICODE_2_0_BMP_ENCODING: u16 = 3;
20const UNICODE_2_0_FULL_ENCODING: u16 = 4;
21const UNICODE_FULL_ENCODING: u16 = 6;
22
23/// Result of mapping a codepoint with a variation selector.
24#[derive(Copy, Clone, PartialEq, Eq, Debug)]
25pub enum MapVariant {
26    /// The variation selector should be ignored and the default mapping
27    /// of the character should be used.
28    UseDefault,
29    /// The variant glyph mapped by a codepoint and associated variation
30    /// selector.
31    Variant(GlyphId),
32}
33
34impl<'a> Cmap<'a> {
35    /// Map a codepoint to a nominal glyph identifier
36    ///
37    /// This uses the first available subtable that provides a valid mapping.
38    ///
39    /// # Note:
40    ///
41    /// Mapping logic is currently only implemented for the most common subtable
42    /// formats.
43    pub fn map_codepoint(&self, codepoint: impl Into<u32>) -> Option<GlyphId> {
44        let codepoint = codepoint.into();
45        for record in self.encoding_records() {
46            if let Ok(subtable) = record.subtable(self.offset_data()) {
47                if let Some(gid) = subtable.map_codepoint(codepoint) {
48                    return Some(gid);
49                }
50            }
51        }
52        None
53    }
54
55    /// Returns the index, encoding record and subtable for the most
56    /// comprehensive mapping available.
57    ///
58    /// Comprehensive means that tables capable of mapping the Unicode full
59    /// repertoire are chosen over those that only support the basic
60    /// multilingual plane. The exception is that symbol mappings are
61    /// preferred above all others
62    /// (see <https://github.com/harfbuzz/harfbuzz/issues/1918>).
63    pub fn best_subtable(&self) -> Option<(u16, EncodingRecord, CmapSubtable<'a>)> {
64        // Follows the HarfBuzz approach
65        // See <https://github.com/harfbuzz/harfbuzz/blob/a9a78e1bff9d4a62429d22277fea4e0e76e9ac7e/src/hb-ot-cmap-table.hh#L1962>
66        let offset_data = self.offset_data();
67        let records = self.encoding_records();
68        let find = |platform_id, encoding_id| {
69            for (index, record) in records.iter().enumerate() {
70                if record.platform_id() != platform_id || record.encoding_id() != encoding_id {
71                    continue;
72                }
73                if let Ok(subtable) = record.subtable(offset_data) {
74                    match subtable {
75                        CmapSubtable::Format0(_)
76                        | CmapSubtable::Format4(_)
77                        | CmapSubtable::Format6(_)
78                        | CmapSubtable::Format10(_)
79                        | CmapSubtable::Format12(_)
80                        | CmapSubtable::Format13(_) => {
81                            return Some((index as u16, *record, subtable))
82                        }
83                        _ => {}
84                    }
85                }
86            }
87            None
88        };
89        // Symbol subtable.
90        // Prefer symbol if available.
91        // https://github.com/harfbuzz/harfbuzz/issues/1918
92        find(PlatformId::Windows, WINDOWS_SYMBOL_ENCODING)
93            // 32-bit subtables:
94            .or_else(|| find(PlatformId::Windows, WINDOWS_UNICODE_FULL_ENCODING))
95            .or_else(|| find(PlatformId::Unicode, UNICODE_FULL_ENCODING))
96            .or_else(|| find(PlatformId::Unicode, UNICODE_2_0_FULL_ENCODING))
97            // 16-bit subtables:
98            .or_else(|| find(PlatformId::Windows, WINDOWS_UNICODE_BMP_ENCODING))
99            .or_else(|| find(PlatformId::Unicode, UNICODE_2_0_BMP_ENCODING))
100            .or_else(|| find(PlatformId::Unicode, UNICODE_ISO_ENCODING))
101            .or_else(|| find(PlatformId::Unicode, UNICODE_1_1_ENCODING))
102            .or_else(|| find(PlatformId::Unicode, UNICODE_1_0_ENCODING))
103            // MacRoman subtable:
104            .or_else(|| find(PlatformId::Macintosh, 0))
105    }
106
107    /// Returns the index and subtable for the first mapping capable of
108    /// handling Unicode variation sequences.
109    ///
110    /// This is always a [format 14](https://learn.microsoft.com/en-us/typography/opentype/spec/cmap#format-14-unicode-variation-sequences)
111    /// subtable.
112    pub fn uvs_subtable(&self) -> Option<(u16, Cmap14<'a>)> {
113        let offset_data = self.offset_data();
114        for (index, record) in self.encoding_records().iter().enumerate() {
115            if let Ok(CmapSubtable::Format14(cmap14)) = record.subtable(offset_data) {
116                return Some((index as u16, cmap14));
117            };
118        }
119        None
120    }
121
122    /// Returns the subtable at the given index.
123    pub fn subtable(&self, index: u16) -> Result<CmapSubtable<'a>, ReadError> {
124        self.encoding_records()
125            .get(index as usize)
126            .ok_or(ReadError::OutOfBounds)
127            .and_then(|encoding| encoding.subtable(self.offset_data()))
128    }
129
130    #[cfg(feature = "std")]
131    pub fn closure_glyphs(&self, unicodes: &IntSet<u32>, glyph_set: &mut IntSet<GlyphId>) {
132        for record in self.encoding_records() {
133            if let Ok(subtable) = record.subtable(self.offset_data()) {
134                match subtable {
135                    CmapSubtable::Format14(format14) => {
136                        format14.closure_glyphs(unicodes, glyph_set);
137                        return;
138                    }
139                    _ => {
140                        continue;
141                    }
142                }
143            }
144        }
145    }
146}
147
148impl EncodingRecord {
149    pub fn is_symbol(&self) -> bool {
150        self.platform_id() == PlatformId::Windows && self.encoding_id() == WINDOWS_SYMBOL_ENCODING
151    }
152
153    pub fn is_mac_roman(&self) -> bool {
154        self.platform_id() == PlatformId::Macintosh && self.encoding_id() == 0
155    }
156}
157
158impl<'a> CmapSubtable<'a> {
159    pub fn language(&self) -> u32 {
160        match self {
161            Self::Format0(item) => item.language() as u32,
162            Self::Format2(item) => item.language() as u32,
163            Self::Format4(item) => item.language() as u32,
164            Self::Format6(item) => item.language() as u32,
165            Self::Format10(item) => item.language(),
166            Self::Format12(item) => item.language(),
167            Self::Format13(item) => item.language(),
168            _ => 0,
169        }
170    }
171
172    /// Attempts to map the given codepoint to a nominal glyph identifier using
173    /// the underlying subtable.
174    #[inline]
175    pub fn map_codepoint(&self, codepoint: impl Into<u32>) -> Option<GlyphId> {
176        match self {
177            Self::Format0(item) => item.map_codepoint(codepoint),
178            Self::Format4(item) => item.map_codepoint(codepoint),
179            Self::Format6(item) => item.map_codepoint(codepoint),
180            Self::Format10(item) => item.map_codepoint(codepoint),
181            Self::Format12(item) => item.map_codepoint(codepoint),
182            Self::Format13(item) => item.map_codepoint(codepoint),
183            _ => None,
184        }
185    }
186
187    /// Returns an iterator over all (codepoint, glyph identifier) pairs
188    /// in the subtable.
189    ///
190    /// Malicious and malformed fonts can produce a large number of invalid
191    /// pairs. Use [`Self::iter_with_limits`] to generate a pruned sequence
192    /// that is limited to reasonable values.
193    pub fn iter(&self) -> CmapSubtableIter<'a> {
194        let limits = CmapIterLimits {
195            max_char: u32::MAX,
196            glyph_count: u32::MAX,
197        };
198        self.iter_with_limits(limits)
199    }
200
201    /// Returns an iterator over all (codepoint, glyph identifier) pairs
202    /// in the subtable within the given limits.    
203    pub fn iter_with_limits(&self, limits: CmapIterLimits) -> CmapSubtableIter<'a> {
204        match self {
205            Self::Format4(item) => CmapSubtableIter::Format4(item.iter()),
206            Self::Format6(item) => CmapSubtableIter::Format6(item.iter()),
207            Self::Format10(item) => CmapSubtableIter::Format10(item.iter()),
208            Self::Format12(item) => CmapSubtableIter::Format12(item.iter_with_limits(limits)),
209            Self::Format13(item) => CmapSubtableIter::Format13(item.iter_with_limits(limits)),
210            _ => CmapSubtableIter::None,
211        }
212    }
213}
214
215/// Iterator over all (codepoint, glyph identifier) pairs in
216/// the subtable.
217#[derive(Clone)]
218#[non_exhaustive]
219pub enum CmapSubtableIter<'a> {
220    None,
221    Format4(Cmap4Iter<'a>),
222    Format6(Cmap6Iter<'a>),
223    Format10(Cmap10Iter<'a>),
224    Format12(Cmap12Iter<'a>),
225    Format13(Cmap13Iter<'a>),
226}
227
228impl Iterator for CmapSubtableIter<'_> {
229    type Item = (u32, GlyphId);
230
231    #[inline]
232    fn next(&mut self) -> Option<Self::Item> {
233        match self {
234            Self::None => None,
235            Self::Format4(iter) => iter.next(),
236            Self::Format6(iter) => iter.next(),
237            Self::Format10(iter) => iter.next(),
238            Self::Format12(iter) => iter.next(),
239            Self::Format13(iter) => iter.next(),
240        }
241    }
242}
243
244impl Cmap0<'_> {
245    pub fn map_codepoint(&self, codepoint: impl Into<u32>) -> Option<GlyphId> {
246        let codepoint = codepoint.into();
247
248        self.glyph_id_array()
249            .get(codepoint as usize)
250            .map(|g| GlyphId::new(*g as u32))
251    }
252}
253
254impl<'a> Cmap4<'a> {
255    /// Maps a codepoint to a nominal glyph identifier.
256    pub fn map_codepoint(&self, codepoint: impl Into<u32>) -> Option<GlyphId> {
257        let codepoint = codepoint.into();
258        if codepoint > 0xFFFF {
259            return None;
260        }
261        let codepoint = codepoint as u16;
262        let mut lo = 0;
263        let mut hi = self.seg_count_x2() as usize / 2;
264        let start_codes = self.start_code();
265        let end_codes = self.end_code();
266        while lo < hi {
267            let i = (lo + hi) / 2;
268            let start_code = start_codes.get(i)?.get();
269            if codepoint < start_code {
270                hi = i;
271            } else if codepoint > end_codes.get(i)?.get() {
272                lo = i + 1;
273            } else {
274                return self.lookup_glyph_id(codepoint, i, start_code);
275            }
276        }
277        None
278    }
279
280    /// Returns an iterator over all (codepoint, glyph identifier) pairs
281    /// in the subtable.
282    pub fn iter(&self) -> Cmap4Iter<'a> {
283        Cmap4Iter::new(self.clone())
284    }
285
286    /// Does the final phase of glyph id lookup.
287    ///
288    /// Shared between Self::map and Cmap4Iter.
289    fn lookup_glyph_id(&self, codepoint: u16, index: usize, start_code: u16) -> Option<GlyphId> {
290        let deltas = self.id_delta();
291        let range_offsets = self.id_range_offsets();
292        let delta = deltas.get(index)?.get() as i32;
293        let range_offset = range_offsets.get(index)?.get() as usize;
294        if range_offset == 0 {
295            return Some(GlyphId::from((codepoint as i32 + delta) as u16));
296        }
297        let mut offset = range_offset / 2 + (codepoint - start_code) as usize;
298        offset = offset.saturating_sub(range_offsets.len() - index);
299        let gid = self.glyph_id_array().get(offset)?.get();
300        (gid != 0).then_some(GlyphId::from((gid as i32 + delta) as u16))
301    }
302
303    /// Returns the [start_code, end_code] range at the given index.
304    fn code_range(&self, index: usize) -> Option<Range<u32>> {
305        // Extend to u32 to ensure we don't overflow on the end + 1 bound
306        // below.
307        let start = self.start_code().get(index)?.get() as u32;
308        let end = self.end_code().get(index)?.get() as u32;
309        // Use end + 1 here because the range in the table is inclusive
310        Some(start..end + 1)
311    }
312}
313
314/// Iterator over all (codepoint, glyph identifier) pairs in
315/// the subtable.
316#[derive(Clone)]
317pub struct Cmap4Iter<'a> {
318    subtable: Cmap4<'a>,
319    cur_range: Range<u32>,
320    cur_start_code: u16,
321    cur_range_ix: usize,
322}
323
324impl<'a> Cmap4Iter<'a> {
325    fn new(subtable: Cmap4<'a>) -> Self {
326        let cur_range = subtable.code_range(0).unwrap_or_default();
327        let cur_start_code = cur_range.start as u16;
328        Self {
329            subtable,
330            cur_range,
331            cur_start_code,
332            cur_range_ix: 0,
333        }
334    }
335}
336
337impl Iterator for Cmap4Iter<'_> {
338    type Item = (u32, GlyphId);
339
340    fn next(&mut self) -> Option<Self::Item> {
341        loop {
342            if let Some(codepoint) = self.cur_range.next() {
343                let Some(glyph_id) = self.subtable.lookup_glyph_id(
344                    codepoint as u16,
345                    self.cur_range_ix,
346                    self.cur_start_code,
347                ) else {
348                    continue;
349                };
350                return Some((codepoint, glyph_id));
351            } else {
352                self.cur_range_ix += 1;
353                let next_range = self.subtable.code_range(self.cur_range_ix)?;
354                // Groups should be in order and non-overlapping so make sure
355                // that the start code of next group is at least current_end + 1.
356                // Also avoid start sliding backwards if we see data where end < start by taking the max
357                // of next.end and curr.end as the new end.
358                // This prevents timeout and bizarre results in the face of numerous overlapping ranges
359                // https://github.com/googlefonts/fontations/issues/1100
360                // cmap4 ranges are u16 so no need to stress about values past char::MAX
361                // Clamp only the iteration range; the segment's real start code
362                // is still needed by lookup_glyph_id to index the glyph id array.
363                let start_code = next_range.start as u16;
364                self.cur_range = next_range.start.max(self.cur_range.end)
365                    ..next_range.end.max(self.cur_range.end);
366                self.cur_start_code = start_code;
367            }
368        }
369    }
370}
371
372impl<'a> Cmap6<'a> {
373    pub fn map_codepoint(&self, codepoint: impl Into<u32>) -> Option<GlyphId> {
374        let codepoint = codepoint.into();
375
376        let first = self.first_code() as u32;
377        let idx = codepoint.checked_sub(first)?;
378        self.glyph_id_array()
379            .get(idx as usize)
380            .map(|g| GlyphId::new(g.get() as u32))
381    }
382
383    /// Returns an iterator over all (codepoint, glyph identifier) pairs
384    /// in the subtable.    
385    pub fn iter(&self) -> Cmap6Iter<'a> {
386        Cmap6Iter {
387            first: self.first_code() as u32,
388            glyph_ids: self.glyph_id_array(),
389            pos: 0,
390        }
391    }
392}
393
394/// Iterator over all (codepoint, glyph identifier) pairs in
395/// the subtable.
396#[derive(Clone)]
397pub struct Cmap6Iter<'a> {
398    first: u32,
399    glyph_ids: &'a [BigEndian<u16>],
400    pos: u32,
401}
402
403impl Iterator for Cmap6Iter<'_> {
404    type Item = (u32, GlyphId);
405
406    fn next(&mut self) -> Option<Self::Item> {
407        let gid = self.glyph_ids.get(self.pos as usize)?.get().into();
408        let codepoint = self.first + self.pos;
409        self.pos += 1;
410        Some((codepoint, gid))
411    }
412}
413
414impl<'a> Cmap10<'a> {
415    pub fn map_codepoint(&self, codepoint: impl Into<u32>) -> Option<GlyphId> {
416        let codepoint = codepoint.into();
417        let idx = codepoint.checked_sub(self.start_char_code())?;
418        self.glyph_id_array()
419            .get(idx as usize)
420            .map(|g| GlyphId::new(g.get() as u32))
421    }
422
423    /// Returns an iterator over all (codepoint, glyph identifier) pairs
424    /// in the subtable.    
425    pub fn iter(&self) -> Cmap10Iter<'a> {
426        Cmap10Iter {
427            first: self.start_char_code(),
428            glyph_ids: self.glyph_id_array(),
429            pos: 0,
430        }
431    }
432}
433
434/// Iterator over all (codepoint, glyph identifier) pairs in
435/// the subtable.
436#[derive(Clone)]
437pub struct Cmap10Iter<'a> {
438    first: u32,
439    glyph_ids: &'a [BigEndian<u16>],
440    pos: u32,
441}
442
443impl Iterator for Cmap10Iter<'_> {
444    type Item = (u32, GlyphId);
445
446    fn next(&mut self) -> Option<Self::Item> {
447        let gid = self.glyph_ids.get(self.pos as usize)?.get().into();
448        let codepoint = self.first + self.pos;
449        self.pos += 1;
450        Some((codepoint, gid))
451    }
452}
453
454/// Trait to unify constant and sequential map groups.
455trait AnyMapGroup {
456    const IS_CONSTANT: bool;
457
458    fn start_char_code(&self) -> u32;
459    fn end_char_code(&self) -> u32;
460    /// Either start glyph id for a sequential group or just glyph id
461    /// for a constant group.
462    fn ref_glyph_id(&self) -> u32;
463
464    fn compute_glyph_id(codepoint: u32, start_char_code: u32, ref_glyph_id: u32) -> GlyphId {
465        if Self::IS_CONSTANT {
466            GlyphId::new(ref_glyph_id)
467        } else {
468            GlyphId::new(ref_glyph_id.wrapping_add(codepoint.wrapping_sub(start_char_code)))
469        }
470    }
471}
472
473impl AnyMapGroup for ConstantMapGroup {
474    const IS_CONSTANT: bool = true;
475
476    fn start_char_code(&self) -> u32 {
477        self.start_char_code()
478    }
479
480    fn end_char_code(&self) -> u32 {
481        self.end_char_code()
482    }
483
484    fn ref_glyph_id(&self) -> u32 {
485        self.glyph_id()
486    }
487}
488
489impl AnyMapGroup for SequentialMapGroup {
490    const IS_CONSTANT: bool = false;
491
492    fn start_char_code(&self) -> u32 {
493        self.start_char_code()
494    }
495
496    fn end_char_code(&self) -> u32 {
497        self.end_char_code()
498    }
499
500    fn ref_glyph_id(&self) -> u32 {
501        self.start_glyph_id()
502    }
503}
504
505/// Shared codepoint mapping code for cmap 12/13.
506fn cmap1213_map_codepoint<T: AnyMapGroup>(
507    groups: &[T],
508    codepoint: impl Into<u32>,
509) -> Option<GlyphId> {
510    let codepoint = codepoint.into();
511    let mut lo = 0;
512    let mut hi = groups.len();
513    while lo < hi {
514        let i = (lo + hi) / 2;
515        let group = groups.get(i)?;
516        if codepoint < group.start_char_code() {
517            hi = i;
518        } else if codepoint > group.end_char_code() {
519            lo = i + 1;
520        } else {
521            return Some(T::compute_glyph_id(
522                codepoint,
523                group.start_char_code(),
524                group.ref_glyph_id(),
525            ));
526        }
527    }
528    None
529}
530
531/// Character and glyph limits for iterating format 12 and 13 subtables.
532#[derive(Copy, Clone, Debug)]
533pub struct CmapIterLimits {
534    /// The maximum valid character.
535    pub max_char: u32,
536    /// The number of glyphs in the font.
537    pub glyph_count: u32,
538}
539
540impl CmapIterLimits {
541    /// Returns the default limits for the given font.
542    ///
543    /// This will limit pairs to `char::MAX` and the number of glyphs contained
544    /// in the font. If the font is missing a `maxp` table, the number of
545    /// glyphs will be limited to `u16::MAX`.
546    pub fn default_for_font(font: &FontRef) -> Self {
547        let glyph_count = font
548            .maxp()
549            .map(|maxp| maxp.num_glyphs())
550            .unwrap_or(u16::MAX) as u32;
551        Self {
552            // Limit to the valid range of Unicode characters
553            // per https://github.com/googlefonts/fontations/issues/952#issuecomment-2161510184
554            max_char: char::MAX as u32,
555            glyph_count,
556        }
557    }
558}
559
560impl Default for CmapIterLimits {
561    fn default() -> Self {
562        Self {
563            max_char: char::MAX as u32,
564            // Revisit this when we actually support big glyph ids
565            glyph_count: u16::MAX as u32,
566        }
567    }
568}
569
570/// Remapped groups for iterating cmap12/13.
571#[derive(Clone, Debug)]
572struct Cmap1213IterGroup {
573    range: Range<u64>,
574    start_code: u32,
575    ref_glyph_id: u32,
576}
577
578/// Shared group resolution code for cmap 12/13.
579fn cmap1213_iter_group<T: AnyMapGroup>(
580    groups: &[T],
581    index: usize,
582    limits: &Option<CmapIterLimits>,
583) -> Option<Cmap1213IterGroup> {
584    let group = groups.get(index)?;
585    let start_code = group.start_char_code();
586    // Change to exclusive range. This can never overflow since the source
587    // is a 32-bit value
588    let end_code = group.end_char_code() as u64 + 1;
589    let start_glyph_id = group.ref_glyph_id();
590    let end_code = if let Some(limits) = limits {
591        // Set our end code to the minimum of our character and glyph
592        // count limit
593        if T::IS_CONSTANT {
594            end_code.min(limits.max_char as u64)
595        } else {
596            (limits.glyph_count as u64)
597                .saturating_sub(start_glyph_id as u64)
598                .saturating_add(start_code as u64)
599                .min(end_code.min(limits.max_char as u64))
600        }
601    } else {
602        end_code
603    };
604    Some(Cmap1213IterGroup {
605        range: start_code as u64..end_code,
606        start_code,
607        ref_glyph_id: start_glyph_id,
608    })
609}
610
611/// Shared iterator for cmap 12/13.
612#[derive(Clone)]
613struct Cmap1213Iter<'a, T> {
614    groups: &'a [T],
615    cur_group: Option<Cmap1213IterGroup>,
616    cur_group_ix: usize,
617    limits: Option<CmapIterLimits>,
618}
619
620impl<'a, T> Cmap1213Iter<'a, T>
621where
622    T: AnyMapGroup,
623{
624    fn new(groups: &'a [T], limits: Option<CmapIterLimits>) -> Self {
625        let cur_group = cmap1213_iter_group(groups, 0, &limits);
626        Self {
627            groups,
628            cur_group,
629            cur_group_ix: 0,
630            limits,
631        }
632    }
633}
634
635impl<T> Iterator for Cmap1213Iter<'_, T>
636where
637    T: AnyMapGroup,
638{
639    type Item = (u32, GlyphId);
640
641    fn next(&mut self) -> Option<Self::Item> {
642        loop {
643            let group = self.cur_group.as_mut()?;
644            if let Some(codepoint) = group.range.next() {
645                let codepoint = codepoint as u32;
646                let glyph_id = T::compute_glyph_id(codepoint, group.start_code, group.ref_glyph_id);
647                return Some((codepoint, glyph_id));
648            } else {
649                self.cur_group_ix += 1;
650                let mut next_group =
651                    cmap1213_iter_group(self.groups, self.cur_group_ix, &self.limits)?;
652                // Groups should be in order and non-overlapping so make sure
653                // that the start code of next group is at least
654                // current_end.
655                if next_group.range.start < group.range.end {
656                    next_group.range.start = group.range.end;
657                }
658                next_group.range.end = next_group.range.end.max(group.range.end);
659                self.cur_group = Some(next_group);
660            }
661        }
662    }
663}
664
665impl<'a> Cmap12<'a> {
666    /// Maps a codepoint to a nominal glyph identifier.
667    pub fn map_codepoint(&self, codepoint: impl Into<u32>) -> Option<GlyphId> {
668        cmap1213_map_codepoint(self.groups(), codepoint)
669    }
670
671    /// Returns an iterator over all (codepoint, glyph identifier) pairs
672    /// in the subtable.
673    ///
674    /// Malicious and malformed fonts can produce a large number of invalid
675    /// pairs. Use [`Self::iter_with_limits`] to generate a pruned sequence
676    /// that is limited to reasonable values.
677    pub fn iter(&self) -> Cmap12Iter<'a> {
678        Cmap12Iter::new(self.clone(), None)
679    }
680
681    /// Returns an iterator over all (codepoint, glyph identifier) pairs
682    /// in the subtable within the given limits.
683    pub fn iter_with_limits(&self, limits: CmapIterLimits) -> Cmap12Iter<'a> {
684        Cmap12Iter::new(self.clone(), Some(limits))
685    }
686}
687
688/// Iterator over all (codepoint, glyph identifier) pairs in
689/// the subtable.
690#[derive(Clone)]
691pub struct Cmap12Iter<'a>(Cmap1213Iter<'a, SequentialMapGroup>);
692
693impl<'a> Cmap12Iter<'a> {
694    fn new(subtable: Cmap12<'a>, limits: Option<CmapIterLimits>) -> Self {
695        Self(Cmap1213Iter::new(subtable.groups(), limits))
696    }
697}
698
699impl Iterator for Cmap12Iter<'_> {
700    type Item = (u32, GlyphId);
701
702    fn next(&mut self) -> Option<Self::Item> {
703        self.0.next()
704    }
705}
706
707impl<'a> Cmap13<'a> {
708    /// Maps a codepoint to a nominal glyph identifier.
709    pub fn map_codepoint(&self, codepoint: impl Into<u32>) -> Option<GlyphId> {
710        cmap1213_map_codepoint(self.groups(), codepoint)
711    }
712
713    /// Returns an iterator over all (codepoint, glyph identifier) pairs
714    /// in the subtable.
715    ///
716    /// Malicious and malformed fonts can produce a large number of invalid
717    /// pairs. Use [`Self::iter_with_limits`] to generate a pruned sequence
718    /// that is limited to reasonable values.
719    pub fn iter(&self) -> Cmap13Iter<'a> {
720        Cmap13Iter::new(self.clone(), None)
721    }
722
723    /// Returns an iterator over all (codepoint, glyph identifier) pairs
724    /// in the subtable within the given limits.
725    ///
726    /// Malicious and malformed fonts can produce a large number of invalid
727    /// pairs. Use [`Self::iter_with_limits`] to generate a pruned sequence
728    /// that is limited to reasonable values.
729    pub fn iter_with_limits(&self, limits: CmapIterLimits) -> Cmap13Iter<'a> {
730        Cmap13Iter::new(self.clone(), Some(limits))
731    }
732}
733
734/// Iterator over all (codepoint, glyph identifier) pairs in
735/// the subtable.
736#[derive(Clone)]
737pub struct Cmap13Iter<'a>(Cmap1213Iter<'a, ConstantMapGroup>);
738
739impl<'a> Cmap13Iter<'a> {
740    fn new(subtable: Cmap13<'a>, limits: Option<CmapIterLimits>) -> Self {
741        Self(Cmap1213Iter::new(subtable.groups(), limits))
742    }
743}
744
745impl Iterator for Cmap13Iter<'_> {
746    type Item = (u32, GlyphId);
747
748    fn next(&mut self) -> Option<Self::Item> {
749        self.0.next()
750    }
751}
752
753impl<'a> Cmap14<'a> {
754    /// Maps a codepoint and variation selector to a nominal glyph identifier.
755    pub fn map_variant(
756        &self,
757        codepoint: impl Into<u32>,
758        selector: impl Into<u32>,
759    ) -> Option<MapVariant> {
760        let codepoint = codepoint.into();
761        let selector = selector.into();
762        let selector_records = self.var_selector();
763        // Variation selector records are sorted in order of var_selector. Binary search to find
764        // the appropriate record.
765        let selector_record = selector_records
766            .binary_search_by(|rec| {
767                let rec_selector: u32 = rec.var_selector().into();
768                rec_selector.cmp(&selector)
769            })
770            .ok()
771            .and_then(|idx| selector_records.get(idx))?;
772        // If a default UVS table is present in this selector record, binary search on the ranges
773        // (start_unicode_value, start_unicode_value + additional_count) to find the requested codepoint.
774        // If found, ignore the selector and return a value indicating that the default cmap mapping
775        // should be used.
776        if let Some(Ok(default_uvs)) = selector_record.default_uvs(self.offset_data()) {
777            use core::cmp::Ordering;
778            let found_default_uvs = default_uvs
779                .ranges()
780                .binary_search_by(|range| {
781                    let start = range.start_unicode_value().into();
782                    if codepoint < start {
783                        Ordering::Greater
784                    } else if codepoint > (start + range.additional_count() as u32) {
785                        Ordering::Less
786                    } else {
787                        Ordering::Equal
788                    }
789                })
790                .is_ok();
791            if found_default_uvs {
792                return Some(MapVariant::UseDefault);
793            }
794        }
795        // Binary search the non-default UVS table if present. This maps codepoint+selector to a variant glyph.
796        let non_default_uvs = selector_record.non_default_uvs(self.offset_data())?.ok()?;
797        let mapping = non_default_uvs.uvs_mapping();
798        let ix = mapping
799            .binary_search_by(|map| {
800                let map_codepoint: u32 = map.unicode_value().into();
801                map_codepoint.cmp(&codepoint)
802            })
803            .ok()?;
804        Some(MapVariant::Variant(GlyphId::from(
805            mapping.get(ix)?.glyph_id(),
806        )))
807    }
808
809    /// Returns an iterator over all (codepoint, selector, mapping variant)
810    /// triples in the subtable.
811    ///
812    /// Malicious and malformed fonts can produce a large number of invalid
813    /// triples. Use [`Self::iter_with_limits`] to generate a pruned sequence
814    /// that is limited to reasonable values.
815    pub fn iter(&self) -> Cmap14Iter<'a> {
816        Cmap14Iter::new(self.clone(), None)
817    }
818
819    /// Returns an iterator over all (codepoint, selector, mapping variant)
820    /// triples in the subtable within the given limits.
821    pub fn iter_with_limits(&self, limits: CmapIterLimits) -> Cmap14Iter<'a> {
822        Cmap14Iter::new(self.clone(), Some(limits))
823    }
824
825    #[cfg(feature = "std")]
826    pub fn closure_glyphs(&self, unicodes: &IntSet<u32>, glyph_set: &mut IntSet<GlyphId>) {
827        for selector in self.var_selector() {
828            if !unicodes.contains(selector.var_selector().to_u32()) {
829                continue;
830            }
831            if let Some(non_default_uvs) = selector
832                .non_default_uvs(self.offset_data())
833                .transpose()
834                .ok()
835                .flatten()
836            {
837                glyph_set.extend(
838                    non_default_uvs
839                        .uvs_mapping()
840                        .iter()
841                        .filter(|m| unicodes.contains(m.unicode_value().to_u32()))
842                        .map(|m| m.glyph_id().into()),
843                );
844            }
845        }
846    }
847}
848
849/// Iterator over all (codepoint, selector, mapping variant) triples
850/// in the subtable.
851#[derive(Clone)]
852pub struct Cmap14Iter<'a> {
853    offset_data: FontData<'a>,
854    records: core::slice::Iter<'a, VariationSelector>,
855    cur_selector: Option<u32>,
856    default_uvs: Option<DefaultUvsIter<'a>>,
857    non_default_uvs: Option<NonDefaultUvsIter<'a>>,
858    default_uv_left: u32,
859    non_default_uv_left: u32,
860}
861
862impl<'a> Cmap14Iter<'a> {
863    fn new(subtable: Cmap14<'a>, limits: Option<CmapIterLimits>) -> Self {
864        let (default_uv_left, non_default_uv_left) = if let Some(limits) = limits {
865            (limits.max_char.saturating_add(1), limits.glyph_count)
866        } else {
867            (u32::MAX, u32::MAX)
868        };
869        Self {
870            offset_data: subtable.offset_data(),
871            records: subtable.var_selector().iter(),
872            cur_selector: None,
873            default_uvs: None,
874            non_default_uvs: None,
875            default_uv_left,
876            non_default_uv_left,
877        }
878    }
879
880    fn advance_selector(&mut self) -> Option<u32> {
881        loop {
882            let record = self.records.next()?;
883            let selector = record.var_selector().to_u32();
884            // The spec says:
885            // "The VariationSelector records are sorted in increasing order of
886            // varSelector. No two records may have the same varSelector value."
887            //
888            // So skip any selectors that are less than or equal to the current
889            // selector.
890            if let Some(cur_selector) = self.cur_selector {
891                if selector <= cur_selector {
892                    continue;
893                }
894            }
895            self.cur_selector = Some(selector);
896            self.default_uvs = record
897                .default_uvs(self.offset_data)
898                .transpose()
899                .ok()
900                .flatten()
901                .map(DefaultUvsIter::new);
902            self.non_default_uvs = record
903                .non_default_uvs(self.offset_data)
904                .transpose()
905                .ok()
906                .flatten()
907                .map(NonDefaultUvsIter::new);
908            return Some(selector);
909        }
910    }
911}
912
913impl Iterator for Cmap14Iter<'_> {
914    type Item = (u32, u32, MapVariant);
915
916    fn next(&mut self) -> Option<Self::Item> {
917        loop {
918            let selector = if let Some(selector) = self.cur_selector {
919                selector
920            } else {
921                self.advance_selector()?
922            };
923            if let Some(default_uvs) = self.default_uvs.as_mut() {
924                if let Some(codepoint) = default_uvs.next() {
925                    self.default_uv_left = self.default_uv_left.checked_sub(1)?;
926                    return Some((codepoint, selector, MapVariant::UseDefault));
927                }
928            }
929            if let Some(non_default_uvs) = self.non_default_uvs.as_mut() {
930                if let Some((codepoint, variant)) = non_default_uvs.next() {
931                    self.non_default_uv_left = self.non_default_uv_left.checked_sub(1)?;
932                    return Some((codepoint, selector, MapVariant::Variant(variant.into())));
933                }
934            }
935            self.advance_selector()?;
936        }
937    }
938}
939
940#[derive(Clone)]
941struct DefaultUvsIter<'a> {
942    ranges: std::slice::Iter<'a, UnicodeRange>,
943    cur_range: Range<u32>,
944}
945
946impl<'a> DefaultUvsIter<'a> {
947    fn new(ranges: DefaultUvs<'a>) -> Self {
948        let mut ranges = ranges.ranges().iter();
949        let cur_range = if let Some(range) = ranges.next() {
950            let start: u32 = range.start_unicode_value().into();
951            let end = start + range.additional_count() as u32 + 1;
952            start..end
953        } else {
954            0..0
955        };
956        Self { ranges, cur_range }
957    }
958}
959
960impl Iterator for DefaultUvsIter<'_> {
961    type Item = u32;
962
963    fn next(&mut self) -> Option<Self::Item> {
964        loop {
965            if let Some(codepoint) = self.cur_range.next() {
966                return Some(codepoint);
967            }
968            let range = self.ranges.next()?;
969            let start: u32 = range.start_unicode_value().into();
970            let end = start + range.additional_count() as u32 + 1;
971            self.cur_range = start..end;
972        }
973    }
974}
975
976#[derive(Clone)]
977struct NonDefaultUvsIter<'a> {
978    iter: std::slice::Iter<'a, UvsMapping>,
979}
980
981impl<'a> NonDefaultUvsIter<'a> {
982    fn new(uvs: NonDefaultUvs<'a>) -> Self {
983        Self {
984            iter: uvs.uvs_mapping().iter(),
985        }
986    }
987}
988
989impl Iterator for NonDefaultUvsIter<'_> {
990    type Item = (u32, GlyphId16);
991
992    fn next(&mut self) -> Option<Self::Item> {
993        let mapping = self.iter.next()?;
994        let codepoint: u32 = mapping.unicode_value().into();
995        let glyph_id = GlyphId16::new(mapping.glyph_id());
996        Some((codepoint, glyph_id))
997    }
998}
999
1000#[cfg(test)]
1001mod tests {
1002    use super::*;
1003    use crate::{types::Uint24, FontRef, GlyphId, TableProvider};
1004    use font_test_data::{be_buffer, bebuffer::BeBuffer};
1005
1006    #[test]
1007    fn map_codepoints() {
1008        let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
1009        let cmap = font.cmap().unwrap();
1010        assert_eq!(cmap.map_codepoint('A'), Some(GlyphId::new(1)));
1011        assert_eq!(cmap.map_codepoint('À'), Some(GlyphId::new(2)));
1012        assert_eq!(cmap.map_codepoint('`'), Some(GlyphId::new(3)));
1013        assert_eq!(cmap.map_codepoint('B'), None);
1014
1015        let font = FontRef::new(font_test_data::SIMPLE_GLYF).unwrap();
1016        let cmap = font.cmap().unwrap();
1017        assert_eq!(cmap.map_codepoint(' '), Some(GlyphId::new(1)));
1018        assert_eq!(cmap.map_codepoint(0xE_u32), Some(GlyphId::new(2)));
1019        assert_eq!(cmap.map_codepoint('B'), None);
1020
1021        let cmap0_data = cmap0_data();
1022        let cmap = Cmap::read(FontData::new(cmap0_data.data())).unwrap();
1023
1024        assert_eq!(cmap.map_codepoint(0u8), Some(GlyphId::new(0)));
1025        assert_eq!(cmap.map_codepoint(b' '), Some(GlyphId::new(178)));
1026        assert_eq!(cmap.map_codepoint(b'r'), Some(GlyphId::new(193)));
1027        assert_eq!(cmap.map_codepoint(b'X'), Some(GlyphId::new(13)));
1028        assert_eq!(cmap.map_codepoint(255u8), Some(GlyphId::new(3)));
1029
1030        let cmap6_data = be_buffer! {
1031            // version
1032            0u16,
1033            // numTables
1034            1u16,
1035            // platformID
1036            1u16,
1037            // encodingID
1038            0u16,
1039            // subtableOffset
1040            12u32,
1041            // format
1042            6u16,
1043            // length
1044            32u16,
1045            // language
1046            0u16,
1047            // firstCode
1048            32u16,
1049            // entryCount
1050            5u16,
1051            // glyphIDArray
1052            [10u16, 15, 7, 20, 4]
1053        };
1054
1055        let cmap = Cmap::read(FontData::new(cmap6_data.data())).unwrap();
1056
1057        assert_eq!(cmap.map_codepoint(0u8), None);
1058        assert_eq!(cmap.map_codepoint(31u8), None);
1059        assert_eq!(cmap.map_codepoint(33u8), Some(GlyphId::new(15)));
1060        assert_eq!(cmap.map_codepoint(35u8), Some(GlyphId::new(20)));
1061        assert_eq!(cmap.map_codepoint(36u8), Some(GlyphId::new(4)));
1062        assert_eq!(cmap.map_codepoint(50u8), None);
1063    }
1064
1065    #[test]
1066    fn map_variants() {
1067        use super::MapVariant::*;
1068        let font = FontRef::new(font_test_data::CMAP14_FONT1).unwrap();
1069        let cmap = font.cmap().unwrap();
1070        let cmap14 = find_cmap14(&cmap).unwrap();
1071        let selector = '\u{e0100}';
1072        assert_eq!(cmap14.map_variant('a', selector), None);
1073        assert_eq!(cmap14.map_variant('\u{4e00}', selector), Some(UseDefault));
1074        assert_eq!(cmap14.map_variant('\u{4e06}', selector), Some(UseDefault));
1075        assert_eq!(
1076            cmap14.map_variant('\u{4e08}', selector),
1077            Some(Variant(GlyphId::new(25)))
1078        );
1079        assert_eq!(
1080            cmap14.map_variant('\u{4e09}', selector),
1081            Some(Variant(GlyphId::new(26)))
1082        );
1083    }
1084
1085    #[test]
1086    #[cfg(feature = "std")]
1087    fn cmap14_closure_glyphs() {
1088        let font = FontRef::new(font_test_data::CMAP14_FONT1).unwrap();
1089        let cmap = font.cmap().unwrap();
1090        let mut unicodes = IntSet::empty();
1091        unicodes.insert(0x4e08_u32);
1092        unicodes.insert(0xe0100_u32);
1093
1094        let mut glyph_set = IntSet::empty();
1095        glyph_set.insert(GlyphId::new(18));
1096        cmap.closure_glyphs(&unicodes, &mut glyph_set);
1097
1098        assert_eq!(glyph_set.len(), 2);
1099        assert!(glyph_set.contains(GlyphId::new(18)));
1100        assert!(glyph_set.contains(GlyphId::new(25)));
1101    }
1102
1103    #[test]
1104    fn cmap4_iter() {
1105        let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
1106        let cmap4 = find_cmap4(&font.cmap().unwrap()).unwrap();
1107        let mut count = 0;
1108        for (codepoint, glyph_id) in cmap4.iter() {
1109            assert_eq!(cmap4.map_codepoint(codepoint), Some(glyph_id));
1110            count += 1;
1111        }
1112        assert_eq!(count, 4);
1113        let font = FontRef::new(font_test_data::SIMPLE_GLYF).unwrap();
1114        let cmap4 = find_cmap4(&font.cmap().unwrap()).unwrap();
1115        let mut count = 0;
1116        for (codepoint, glyph_id) in cmap4.iter() {
1117            assert_eq!(cmap4.map_codepoint(codepoint), Some(glyph_id));
1118            count += 1;
1119        }
1120        assert_eq!(count, 3);
1121    }
1122
1123    #[test]
1124    fn cmap4_iter_explicit_notdef() {
1125        let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
1126        let cmap4 = find_cmap4(&font.cmap().unwrap()).unwrap();
1127        let mut notdef_count = 0;
1128        for (_, glyph_id) in cmap4.iter() {
1129            notdef_count += (glyph_id == GlyphId::NOTDEF) as i32;
1130        }
1131        assert!(notdef_count > 0);
1132        assert_eq!(cmap4.map_codepoint(0xFFFF_u32), Some(GlyphId::NOTDEF));
1133    }
1134
1135    // Make sure we don't bail early when iterating ranges with holes.
1136    // Encountered with Gentium Basic and Gentium Basic Book.
1137    // See <https://github.com/googlefonts/fontations/issues/897>
1138    #[test]
1139    fn cmap4_iter_sparse_range() {
1140        #[rustfmt::skip]
1141        let cmap4_data: &[u16] = &[
1142            // format, length, lang
1143            4, 0, 0,
1144            // segCountX2
1145            4,
1146            // bin search data
1147            0, 0, 0,
1148            // end code
1149            262, 0xFFFF, 
1150            // reserved pad
1151            0,
1152            // start code
1153            259, 0xFFFF,
1154            // id delta
1155            0, 1, 
1156            // id range offset
1157            4, 0,
1158            // glyph ids
1159            236, 0, 0, 326,
1160        ];
1161        let mut buf = BeBuffer::new();
1162        for &word in cmap4_data {
1163            buf = buf.push(word);
1164        }
1165        let cmap4 = Cmap4::read(FontData::new(&buf)).unwrap();
1166        let mappings = cmap4
1167            .iter()
1168            .map(|(ch, gid)| (ch, gid.to_u32()))
1169            .collect::<Vec<_>>();
1170        assert_eq!(mappings, &[(259, 236), (262, 326), (65535, 0)]);
1171    }
1172
1173    // When two segments overlap, the iterator clamps the *iteration* range of
1174    // the later segment to avoid emitting duplicate codepoints, but it must
1175    // still use that segment's real start code when indexing the glyph id
1176    // array. Otherwise codepoints in the clamped tail resolve to the wrong
1177    // glyph. See the overlap handling in the format 12/13 iterator for the
1178    // correct shape.
1179    #[test]
1180    fn cmap4_iter_overlapping_range_offset_segment() {
1181        #[rustfmt::skip]
1182        let cmap4_data: &[u16] = &[
1183            // format, length, lang
1184            4, 0, 0,
1185            // segCountX2
1186            6,
1187            // bin search data (searchRange, entrySelector, rangeShift)
1188            0, 0, 0,
1189            // end code
1190            20, 25, 0xFFFF,
1191            // reserved pad
1192            0,
1193            // start code (segment 1 overlaps segment 0: 15 <= 20)
1194            10, 15, 0xFFFF,
1195            // id delta
1196            0, 0, 1,
1197            // id range offset (segment 1 maps via the glyph id array)
1198            0, 8, 0,
1199            // glyph id array
1200            100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112,
1201        ];
1202        let mut buf = BeBuffer::new();
1203        for &word in cmap4_data {
1204            buf = buf.push(word);
1205        }
1206        let cmap4 = Cmap4::read(FontData::new(&buf)).unwrap();
1207        let mappings = cmap4
1208            .iter()
1209            .map(|(ch, gid)| (ch, gid.to_u32()))
1210            .collect::<Vec<_>>();
1211
1212        // Codepoints 21..=25 live only in segment 1, so they are resolved
1213        // through its glyph id array using start code 15. With start code 15
1214        // the indices land on glyph ids 108..=112; using the clamped value 21
1215        // instead would (incorrectly) yield 102..=106.
1216        assert_eq!(
1217            mappings,
1218            &[
1219                (10, 10),
1220                (11, 11),
1221                (12, 12),
1222                (13, 13),
1223                (14, 14),
1224                (15, 15),
1225                (16, 16),
1226                (17, 17),
1227                (18, 18),
1228                (19, 19),
1229                (20, 20),
1230                (21, 108),
1231                (22, 109),
1232                (23, 110),
1233                (24, 111),
1234                (25, 112),
1235                (65535, 0),
1236            ]
1237        );
1238    }
1239
1240    const CMAP6_PAIRS: &[(u32, u32)] = &[
1241        (0x1723, 1),
1242        (0x1724, 2),
1243        (0x1725, 3),
1244        (0x1726, 4),
1245        (0x1727, 5),
1246    ];
1247
1248    #[test]
1249    fn cmap6_map() {
1250        let font = FontRef::new(font_test_data::CMAP6).unwrap();
1251        let cmap = font.cmap().unwrap();
1252        let CmapSubtable::Format6(cmap6) = cmap.subtable(0).unwrap() else {
1253            panic!("should be a format 6 subtable");
1254        };
1255        for (ch, gid) in CMAP6_PAIRS {
1256            assert_eq!(cmap6.map_codepoint(*ch).unwrap().to_u32(), *gid);
1257        }
1258        // Check out of bounds codepoints
1259        assert!(cmap6.map_codepoint(CMAP6_PAIRS[0].0 - 1).is_none());
1260        assert!(cmap6
1261            .map_codepoint(CMAP6_PAIRS.last().copied().unwrap().0 + 1)
1262            .is_none());
1263    }
1264
1265    #[test]
1266    fn cmap6_iter() {
1267        let font = FontRef::new(font_test_data::CMAP6).unwrap();
1268        let cmap = font.cmap().unwrap();
1269        let CmapSubtable::Format6(cmap6) = cmap.subtable(0).unwrap() else {
1270            panic!("should be a format 6 subtable");
1271        };
1272        let pairs = cmap6
1273            .iter()
1274            .map(|(ch, gid)| (ch, gid.to_u32()))
1275            .collect::<Vec<_>>();
1276        assert_eq!(pairs, CMAP6_PAIRS);
1277    }
1278
1279    const CMAP10_PAIRS: &[(u32, u32)] = &[(0x109423, 26), (0x109424, 27), (0x109425, 32)];
1280
1281    #[test]
1282    fn cmap10_map() {
1283        let font = FontRef::new(font_test_data::CMAP10).unwrap();
1284        let cmap = font.cmap().unwrap();
1285        let CmapSubtable::Format10(cmap10) = cmap.subtable(0).unwrap() else {
1286            panic!("should be a format 10 subtable");
1287        };
1288        for (ch, gid) in CMAP10_PAIRS {
1289            assert_eq!(cmap10.map_codepoint(*ch).unwrap().to_u32(), *gid);
1290        }
1291        // Check out of bounds codepoints
1292        assert!(cmap10.map_codepoint(CMAP10_PAIRS[0].0 - 1).is_none());
1293        assert!(cmap10
1294            .map_codepoint(CMAP10_PAIRS.last().copied().unwrap().0 + 1)
1295            .is_none());
1296    }
1297
1298    #[test]
1299    fn cmap10_iter() {
1300        let font = FontRef::new(font_test_data::CMAP10).unwrap();
1301        let cmap = font.cmap().unwrap();
1302        let CmapSubtable::Format10(cmap10) = cmap.subtable(0).unwrap() else {
1303            panic!("should be a format 10 subtable");
1304        };
1305        let pairs = cmap10
1306            .iter()
1307            .map(|(ch, gid)| (ch, gid.to_u32()))
1308            .collect::<Vec<_>>();
1309        assert_eq!(pairs, CMAP10_PAIRS);
1310    }
1311
1312    #[test]
1313    fn cmap12_iter() {
1314        let font = FontRef::new(font_test_data::CMAP12_FONT1).unwrap();
1315        let cmap12 = find_cmap12(&font.cmap().unwrap()).unwrap();
1316        let mut count = 0;
1317        for (codepoint, glyph_id) in cmap12.iter() {
1318            assert_eq!(cmap12.map_codepoint(codepoint), Some(glyph_id));
1319            count += 1;
1320        }
1321        assert_eq!(count, 10);
1322    }
1323
1324    // oss-fuzz: detected integer addition overflow in Cmap12::group()
1325    // ref: https://oss-fuzz.com/testcase-detail/5141969742397440
1326    // and https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=69547
1327    #[test]
1328    fn cmap12_iter_avoid_overflow() {
1329        // reconstructed cmap from <https://oss-fuzz.com/testcase-detail/5141969742397440>
1330        let data = be_buffer! {
1331            12u16,      // format
1332            0u16,       // reserved, set to 0
1333            0u32,       // length, ignored
1334            0u32,       // language, ignored
1335            2u32,       // numGroups
1336            // groups: [startCode, endCode, startGlyphID]
1337            [0xFFFFFFFA_u32, 0xFFFFFFFC, 0], // group 0
1338            [0xFFFFFFFB_u32, 0xFFFFFFFF, 0] // group 1
1339        };
1340        let cmap12 = Cmap12::read(data.data().into()).unwrap();
1341        let _ = cmap12.iter().count();
1342    }
1343
1344    // oss-fuzz: timeout in Cmap12Iter
1345    // ref: https://oss-fuzz.com/testcase-detail/4628971063934976
1346    // and https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=69540
1347    #[test]
1348    fn cmap12_iter_avoid_timeout() {
1349        // ranges: [SequentialMapGroup { start_char_code: 170, end_char_code: 1330926671, start_glyph_id: 328960 }]
1350        let cmap12_data = be_buffer! {
1351            12u16,      // format
1352            0u16,       // reserved, set to 0
1353            0u32,       // length, ignored
1354            0u32,       // language, ignored
1355            1u32,       // numGroups
1356            // groups: [startCode, endCode, startGlyphID]
1357            [170u32, 1330926671, 328960] // group 0
1358        };
1359        let cmap12 = Cmap12::read(cmap12_data.data().into()).unwrap();
1360        assert!(
1361            cmap12.iter_with_limits(CmapIterLimits::default()).count() <= char::MAX as usize + 1
1362        );
1363    }
1364
1365    // oss-fuzz: timeout in outlines, caused by cmap 12 iter
1366    // ref: <https://issues.oss-fuzz.com/issues/394638728>
1367    #[test]
1368    fn cmap12_iter_avoid_timeout2() {
1369        let cmap12_data = be_buffer! {
1370            12u16,      // format
1371            0u16,       // reserved, set to 0
1372            0u32,       // length, ignored
1373            0u32,       // language, ignored
1374            3u32,       // numGroups
1375            // groups: [startCode, endCode, startGlyphID]
1376            [199u32, 16777271, 2],
1377            [262u32, 262, 3],
1378            [268u32, 268, 4]
1379        };
1380        let cmap12 = Cmap12::read(cmap12_data.data().into()).unwrap();
1381        // In the test case, maxp.numGlyphs = 8
1382        const MAX_GLYPHS: u32 = 8;
1383        let limits = CmapIterLimits {
1384            glyph_count: MAX_GLYPHS,
1385            ..Default::default()
1386        };
1387        assert_eq!(cmap12.iter_with_limits(limits).count(), MAX_GLYPHS as usize);
1388    }
1389
1390    #[test]
1391    fn cmap12_iter_glyph_limit() {
1392        let font = FontRef::new(font_test_data::CMAP12_FONT1).unwrap();
1393        let cmap12 = find_cmap12(&font.cmap().unwrap()).unwrap();
1394        let mut limits = CmapIterLimits::default_for_font(&font);
1395        // Ensure we obey the glyph count limit.
1396        // This font has 11 glyphs
1397        for glyph_count in 0..=11 {
1398            limits.glyph_count = glyph_count;
1399            assert_eq!(
1400                cmap12.iter_with_limits(limits).count(),
1401                // We always return one less than glyph count limit because
1402                // notdef is not mapped
1403                (glyph_count as usize).saturating_sub(1)
1404            );
1405        }
1406    }
1407
1408    #[test]
1409    fn cmap12_iter_range_clamping() {
1410        let data = be_buffer! {
1411            12u16,      // format
1412            0u16,       // reserved, set to 0
1413            0u32,       // length, ignored
1414            0u32,       // language, ignored
1415            2u32,       // numGroups
1416            // groups: [startCode, endCode, startGlyphID]
1417            [0u32, 16777215, 0], // group 0
1418            [255u32, 0xFFFFFFFF, 0] // group 1
1419        };
1420        let cmap12 = Cmap12::read(data.data().into()).unwrap();
1421        let ranges = cmap12
1422            .groups()
1423            .iter()
1424            .map(|group| (group.start_char_code(), group.end_char_code()))
1425            .collect::<Vec<_>>();
1426        // These groups overlap and extend to the whole u32 range
1427        assert_eq!(ranges, &[(0, 16777215), (255, u32::MAX)]);
1428        // But we produce at most char::MAX + 1 results
1429        let limits = CmapIterLimits {
1430            glyph_count: u32::MAX,
1431            ..Default::default()
1432        };
1433        assert!(cmap12.iter_with_limits(limits).count() <= char::MAX as usize + 1);
1434    }
1435
1436    // Ensure range bounds stay monotonic across groups even when a middle
1437    // group's end is saturated by glyph limits. Without this, a later group
1438    // can reset iteration backwards.
1439    #[test]
1440    fn cmap12_iter_saturated_group_does_not_reset_range() {
1441        let data = be_buffer! {
1442            12u16,      // format
1443            0u16,       // reserved, set to 0
1444            0u32,       // length, ignored
1445            0u32,       // language, ignored
1446            3u32,       // numGroups
1447            // groups: [startCode, endCode, startGlyphID]
1448            [10u32, 20, 0],
1449            [15u32, 40, 100],
1450            [18u32, 22, 0]
1451        };
1452        let cmap12 = Cmap12::read(data.data().into()).unwrap();
1453        let limits = CmapIterLimits {
1454            glyph_count: 50,
1455            ..Default::default()
1456        };
1457        let codepoints = cmap12
1458            .iter_with_limits(limits)
1459            .map(|(cp, _)| cp)
1460            .collect::<Vec<_>>();
1461        assert_eq!(codepoints, (10..=22).collect::<Vec<_>>());
1462    }
1463
1464    #[test]
1465    fn cmap12_iter_explicit_notdef() {
1466        let data = be_buffer! {
1467            12u16,      // format
1468            0u16,       // reserved, set to 0
1469            0u32,       // length, ignored
1470            0u32,       // language, ignored
1471            1u32,       // numGroups
1472            // groups: [startCode, endCode, startGlyphID]
1473            [0_u32, 1_u32, 0] // group 0
1474        };
1475        let cmap12 = Cmap12::read(data.data().into()).unwrap();
1476        for (i, (codepoint, glyph_id)) in cmap12.iter().enumerate() {
1477            assert_eq!(codepoint as usize, i);
1478            assert_eq!(glyph_id.to_u32() as usize, i);
1479        }
1480        assert_eq!(cmap12.iter().next().unwrap().1, GlyphId::NOTDEF);
1481    }
1482
1483    fn cmap13_data() -> Vec<u8> {
1484        let data = be_buffer! {
1485            13u16,      // format
1486            0u16,       // reserved, set to 0
1487            0u32,       // length, ignored
1488            0u32,       // language, ignored
1489            2u32,       // numGroups
1490            // groups: [startCode, endCode, startGlyphID]
1491            [0u32, 8, 20], // group 0
1492            [42u32, 46u32, 30] // group 1
1493        };
1494        data.to_vec()
1495    }
1496
1497    #[test]
1498    fn cmap13_map() {
1499        let data = cmap13_data();
1500        let cmap13 = Cmap13::read(FontData::new(&data)).unwrap();
1501        for ch in 0u32..=8 {
1502            assert_eq!(cmap13.map_codepoint(ch), Some(GlyphId::new(20)));
1503        }
1504        for ch in 9u32..42 {
1505            assert_eq!(cmap13.map_codepoint(ch), None);
1506        }
1507        for ch in 42u32..=46 {
1508            assert_eq!(cmap13.map_codepoint(ch), Some(GlyphId::new(30)));
1509        }
1510        for ch in 47u32..1024 {
1511            assert_eq!(cmap13.map_codepoint(ch), None);
1512        }
1513    }
1514
1515    #[test]
1516    fn cmap13_iter() {
1517        let data = cmap13_data();
1518        let cmap13 = Cmap13::read(FontData::new(&data)).unwrap();
1519        for (ch, gid) in cmap13.iter() {
1520            assert_eq!(cmap13.map_codepoint(ch), Some(gid));
1521        }
1522    }
1523
1524    #[test]
1525    fn cmap14_iter() {
1526        let font = FontRef::new(font_test_data::CMAP14_FONT1).unwrap();
1527        let cmap14 = find_cmap14(&font.cmap().unwrap()).unwrap();
1528        let mut count = 0;
1529        for (codepoint, selector, mapping) in cmap14.iter() {
1530            assert_eq!(cmap14.map_variant(codepoint, selector), Some(mapping));
1531            count += 1;
1532        }
1533        assert_eq!(count, 7);
1534    }
1535
1536    /// Slice of default UVS ranges, each of which is (start_unicode_value, additional_count).
1537    type DefaultUvs<'a> = &'a [(u32, u8)];
1538
1539    /// Slice of non-default UVS ranges, each of which is (unicode_value, glyph_id).
1540    type NonDefaultUvs<'a> = &'a [(u32, u16)];
1541
1542    /// Build a minimal synthetic format-14 subtable.
1543    ///
1544    /// Each record is (selector, default_ranges, non_default_mappings), where
1545    /// default_ranges is a slice of (start, additional_count) and
1546    /// non_default_mappings is a slice of (codepoint, glyph_id).
1547    fn build_cmap14(records: &[(u32, Option<DefaultUvs>, Option<NonDefaultUvs>)]) -> BeBuffer {
1548        fn default_uvs_data(ranges: &[(u32, u8)]) -> BeBuffer {
1549            let mut bytes = BeBuffer::new();
1550            bytes = bytes.push(ranges.len() as u32);
1551            for &(start, additional_count) in ranges {
1552                bytes = bytes.push(Uint24::new(start));
1553                bytes = bytes.push(additional_count);
1554            }
1555            bytes
1556        }
1557        fn non_default_uvs_data(mappings: &[(u32, u16)]) -> BeBuffer {
1558            let mut bytes = BeBuffer::new();
1559            bytes = bytes.push(mappings.len() as u32);
1560            for &(codepoint, glyph_id) in mappings {
1561                bytes = bytes.push(Uint24::new(codepoint));
1562                bytes = bytes.push(glyph_id);
1563            }
1564            bytes
1565        }
1566        let mut data = BeBuffer::new();
1567        // format
1568        data = data.push(14u16);
1569        // length (patched after writing all data)
1570        data = data.push_with_tag(0u32, "cmap14_length");
1571        // num_var_selector_records
1572        data = data.push(records.len() as u32);
1573        let mut default_offset_tags = Vec::with_capacity(records.len());
1574        let mut non_default_offset_tags = Vec::with_capacity(records.len());
1575        for (i, (selector, default_ranges, non_default_mappings)) in records.iter().enumerate() {
1576            // var_selector (24-bit packed value)
1577            data = data.push(Uint24::new(*selector));
1578            // default_uvs_offset
1579            if default_ranges.is_some() {
1580                let tag = format!("default_uvs_offset_{i}");
1581                data = data.push_with_tag(0u32, tag.as_str());
1582                default_offset_tags.push(Some(tag));
1583            } else {
1584                data = data.push(0u32);
1585                default_offset_tags.push(None);
1586            }
1587            // non_default_uvs_offset
1588            if non_default_mappings.is_some() {
1589                let tag = format!("non_default_uvs_offset_{i}");
1590                data = data.push_with_tag(0u32, tag.as_str());
1591                non_default_offset_tags.push(Some(tag));
1592            } else {
1593                data = data.push(0u32);
1594                non_default_offset_tags.push(None);
1595            }
1596        }
1597        for (i, (_, default_ranges, _)) in records.iter().enumerate() {
1598            if let Some(ranges) = default_ranges {
1599                let table_offset = data.len() as u32;
1600                data.write_at(default_offset_tags[i].as_ref().unwrap(), table_offset);
1601                // DefaultUvs: num_unicode_value_ranges + UnicodeRange[]
1602                let bytes = default_uvs_data(ranges);
1603                data = data.extend(bytes.as_slice().iter().copied());
1604            }
1605        }
1606        for (i, (_, _, non_default_mappings)) in records.iter().enumerate() {
1607            if let Some(mappings) = non_default_mappings {
1608                let table_offset = data.len() as u32;
1609                data.write_at(non_default_offset_tags[i].as_ref().unwrap(), table_offset);
1610                // NonDefaultUvs: num_uvs_mappings + UvsMapping[]
1611                let bytes = non_default_uvs_data(mappings);
1612                data = data.extend(bytes.as_slice().iter().copied());
1613            }
1614        }
1615        data.write_at("cmap14_length", data.len() as u32);
1616        data
1617    }
1618
1619    #[test]
1620    fn cmap14_iter_only_handles_monotone_selectors() {
1621        use super::MapVariant::*;
1622        let data = build_cmap14(&[
1623            (0xE100, Some(&[(0x4E00, 0)]), Some(&[(0x4E01, 7)])),
1624            // This one is ignored, same as previous
1625            (0xE100, None, Some(&[(0x4E02, 8)])),
1626            (0xE200, None, Some(&[(0x4E03, 9)])),
1627            // This one is ignored too, less than previous
1628            (0xE100, None, Some(&[(0x4E03, 10)])),
1629        ]);
1630        let cmap14 = Cmap14::read(FontData::new(data.data())).unwrap();
1631        assert_eq!(
1632            cmap14.iter().collect::<Vec<_>>(),
1633            vec![
1634                (0x4E00, 0xE100, UseDefault),
1635                (0x4E01, 0xE100, Variant(GlyphId::new(7))),
1636                (0x4E03, 0xE200, Variant(GlyphId::new(9))),
1637            ]
1638        );
1639    }
1640
1641    #[test]
1642    fn cmap14_iter_limits_default_uvs_to_max_char() {
1643        use super::MapVariant::*;
1644        let data = build_cmap14(&[(0xE100, Some(&[(0x41, 2), (0x50, 0)]), None)]);
1645        let cmap14 = Cmap14::read(FontData::new(data.data())).unwrap();
1646        assert_eq!(
1647            cmap14
1648                .iter_with_limits(CmapIterLimits {
1649                    max_char: 1,
1650                    glyph_count: u32::MAX,
1651                })
1652                .collect::<Vec<_>>(),
1653            vec![(0x41, 0xE100, UseDefault), (0x42, 0xE100, UseDefault),]
1654        );
1655    }
1656
1657    #[test]
1658    fn cmap14_iter_limits_non_default_uvs_to_glyph_count() {
1659        use super::MapVariant::*;
1660        let data = build_cmap14(&[(
1661            0xE100,
1662            None,
1663            Some(&[(0x4E00, 10), (0x4E01, 11), (0x4E02, 12)]),
1664        )]);
1665        let cmap14 = Cmap14::read(FontData::new(data.data())).unwrap();
1666        assert_eq!(
1667            cmap14
1668                .iter_with_limits(CmapIterLimits {
1669                    max_char: u32::MAX,
1670                    glyph_count: 2,
1671                })
1672                .collect::<Vec<_>>(),
1673            vec![
1674                (0x4E00, 0xE100, Variant(GlyphId::new(10))),
1675                (0x4E01, 0xE100, Variant(GlyphId::new(11))),
1676            ]
1677        );
1678    }
1679
1680    fn find_cmap4<'a>(cmap: &Cmap<'a>) -> Option<Cmap4<'a>> {
1681        cmap.encoding_records()
1682            .iter()
1683            .filter_map(|record| record.subtable(cmap.offset_data()).ok())
1684            .find_map(|subtable| match subtable {
1685                CmapSubtable::Format4(cmap4) => Some(cmap4),
1686                _ => None,
1687            })
1688    }
1689
1690    fn find_cmap12<'a>(cmap: &Cmap<'a>) -> Option<Cmap12<'a>> {
1691        cmap.encoding_records()
1692            .iter()
1693            .filter_map(|record| record.subtable(cmap.offset_data()).ok())
1694            .find_map(|subtable| match subtable {
1695                CmapSubtable::Format12(cmap12) => Some(cmap12),
1696                _ => None,
1697            })
1698    }
1699
1700    fn find_cmap14<'a>(cmap: &Cmap<'a>) -> Option<Cmap14<'a>> {
1701        cmap.encoding_records()
1702            .iter()
1703            .filter_map(|record| record.subtable(cmap.offset_data()).ok())
1704            .find_map(|subtable| match subtable {
1705                CmapSubtable::Format14(cmap14) => Some(cmap14),
1706                _ => None,
1707            })
1708    }
1709
1710    /// <https://github.com/googlefonts/fontations/issues/1100>
1711    ///
1712    /// Note that this doesn't demonstrate the timeout, merely that we've eliminated the underlying
1713    /// enthusiasm for non-ascending ranges that enabled it
1714    #[test]
1715    fn cmap4_bad_data() {
1716        let buf = font_test_data::cmap::repetitive_cmap4();
1717        let cmap4 = Cmap4::read(FontData::new(buf.as_slice())).unwrap();
1718
1719        // we should have unique, ascending codepoints, not duplicates and overlaps
1720        assert_eq!(
1721            (6..=64).collect::<Vec<_>>(),
1722            cmap4.iter().map(|(cp, _)| cp).collect::<Vec<_>>()
1723        );
1724    }
1725
1726    fn cmap0_data() -> BeBuffer {
1727        be_buffer! {
1728            // version
1729            0u16,
1730            // numTables
1731            1u16,
1732            // platformID
1733            1u16,
1734            // encodingID
1735            0u16,
1736            // subtableOffset
1737            12u32,
1738            // format
1739            0u16,
1740            // length
1741            274u16,
1742            // language
1743            0u16,
1744            // glyphIDArray
1745            [0u8, 249, 32, 2, 198, 23, 1, 4, 26, 36,
1746            171, 168, 69, 151, 208, 238, 226, 153, 161, 138,
1747            160, 130, 169, 223, 162, 207, 146, 227, 111, 248,
1748            163, 79, 178, 27, 50, 234, 213, 57, 45, 63,
1749            103, 186, 30, 105, 131, 118, 35, 140, 51, 211,
1750            75, 172, 56, 71, 137, 99, 22, 76, 61, 125,
1751            39, 8, 177, 117, 108, 97, 202, 92, 49, 134,
1752            93, 43, 80, 66, 84, 54, 180, 113, 11, 176,
1753            229, 48, 47, 17, 124, 40, 119, 21, 13, 133,
1754            181, 224, 33, 128, 44, 46, 38, 24, 65, 152,
1755            197, 225, 102, 251, 157, 126, 182, 242, 28, 184,
1756            90, 170, 201, 144, 193, 189, 250, 142, 77, 221,
1757            81, 164, 154, 60, 37, 200, 12, 53, 219, 89,
1758            31, 209, 188, 179, 253, 220, 127, 18, 19, 64,
1759            20, 141, 98, 173, 55, 194, 70, 107, 228, 104,
1760            10, 9, 15, 217, 255, 222, 196, 236, 67, 165,
1761            5, 143, 149, 100, 91, 95, 135, 235, 145, 204,
1762            72, 114, 246, 82, 245, 233, 106, 158, 185, 212,
1763            86, 243, 16, 195, 123, 190, 120, 187, 132, 139,
1764            192, 239, 110, 183, 240, 214, 166, 41, 59, 231,
1765            42, 94, 244, 83, 121, 25, 215, 96, 73, 87,
1766            174, 136, 62, 206, 156, 175, 230, 150, 116, 147,
1767            68, 122, 78, 112, 6, 167, 232, 254, 52, 34,
1768            191, 85, 241, 14, 216, 155, 29, 101, 115, 210,
1769            252, 218, 129, 247, 203, 159, 109, 74, 7, 58,
1770            237, 199, 88, 205, 148, 3]
1771        }
1772    }
1773
1774    #[test]
1775    fn best_subtable_full() {
1776        let font = FontRef::new(font_test_data::VORG).unwrap();
1777        let cmap = font.cmap().unwrap();
1778        let (index, record, _) = cmap.best_subtable().unwrap();
1779        assert_eq!(
1780            (index, record.platform_id(), record.encoding_id()),
1781            (3, PlatformId::Windows, WINDOWS_UNICODE_FULL_ENCODING)
1782        );
1783    }
1784
1785    #[test]
1786    fn best_subtable_bmp() {
1787        let font = FontRef::new(font_test_data::CMAP12_FONT1).unwrap();
1788        let cmap = font.cmap().unwrap();
1789        let (index, record, _) = cmap.best_subtable().unwrap();
1790        assert_eq!(
1791            (index, record.platform_id(), record.encoding_id()),
1792            (0, PlatformId::Windows, WINDOWS_UNICODE_BMP_ENCODING)
1793        );
1794    }
1795
1796    #[test]
1797    fn best_subtable_symbol() {
1798        let font = FontRef::new(font_test_data::CMAP4_SYMBOL_PUA).unwrap();
1799        let cmap = font.cmap().unwrap();
1800        let (index, record, _) = cmap.best_subtable().unwrap();
1801        assert!(record.is_symbol());
1802        assert_eq!(
1803            (index, record.platform_id(), record.encoding_id()),
1804            (0, PlatformId::Windows, WINDOWS_SYMBOL_ENCODING)
1805        );
1806    }
1807
1808    #[test]
1809    fn uvs_subtable() {
1810        let font = FontRef::new(font_test_data::CMAP14_FONT1).unwrap();
1811        let cmap = font.cmap().unwrap();
1812        let (index, _) = cmap.uvs_subtable().unwrap();
1813        assert_eq!(index, 0);
1814    }
1815}