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    pub fn iter_with_limits(&self, limits: CmapIterLimits) -> Cmap13Iter<'a> {
726        Cmap13Iter::new(self.clone(), Some(limits))
727    }
728}
729
730/// Iterator over all (codepoint, glyph identifier) pairs in
731/// the subtable.
732#[derive(Clone)]
733pub struct Cmap13Iter<'a>(Cmap1213Iter<'a, ConstantMapGroup>);
734
735impl<'a> Cmap13Iter<'a> {
736    fn new(subtable: Cmap13<'a>, limits: Option<CmapIterLimits>) -> Self {
737        Self(Cmap1213Iter::new(subtable.groups(), limits))
738    }
739}
740
741impl Iterator for Cmap13Iter<'_> {
742    type Item = (u32, GlyphId);
743
744    fn next(&mut self) -> Option<Self::Item> {
745        self.0.next()
746    }
747}
748
749impl<'a> Cmap14<'a> {
750    /// Maps a codepoint and variation selector to a nominal glyph identifier.
751    pub fn map_variant(
752        &self,
753        codepoint: impl Into<u32>,
754        selector: impl Into<u32>,
755    ) -> Option<MapVariant> {
756        let codepoint = codepoint.into();
757        let selector = selector.into();
758        let selector_records = self.var_selector();
759        // Variation selector records are sorted in order of var_selector. Binary search to find
760        // the appropriate record.
761        let selector_record = selector_records
762            .binary_search_by(|rec| {
763                let rec_selector: u32 = rec.var_selector().into();
764                rec_selector.cmp(&selector)
765            })
766            .ok()
767            .and_then(|idx| selector_records.get(idx))?;
768        // If a default UVS table is present in this selector record, binary search on the ranges
769        // (start_unicode_value, start_unicode_value + additional_count) to find the requested codepoint.
770        // If found, ignore the selector and return a value indicating that the default cmap mapping
771        // should be used.
772        if let Some(Ok(default_uvs)) = selector_record.default_uvs(self.offset_data()) {
773            use core::cmp::Ordering;
774            let found_default_uvs = default_uvs
775                .ranges()
776                .binary_search_by(|range| {
777                    let start = range.start_unicode_value().into();
778                    if codepoint < start {
779                        Ordering::Greater
780                    } else if codepoint > (start + range.additional_count() as u32) {
781                        Ordering::Less
782                    } else {
783                        Ordering::Equal
784                    }
785                })
786                .is_ok();
787            if found_default_uvs {
788                return Some(MapVariant::UseDefault);
789            }
790        }
791        // Binary search the non-default UVS table if present. This maps codepoint+selector to a variant glyph.
792        let non_default_uvs = selector_record.non_default_uvs(self.offset_data())?.ok()?;
793        let mapping = non_default_uvs.uvs_mapping();
794        let ix = mapping
795            .binary_search_by(|map| {
796                let map_codepoint: u32 = map.unicode_value().into();
797                map_codepoint.cmp(&codepoint)
798            })
799            .ok()?;
800        Some(MapVariant::Variant(GlyphId::from(
801            mapping.get(ix)?.glyph_id(),
802        )))
803    }
804
805    /// Returns an iterator over all (codepoint, selector, mapping variant)
806    /// triples in the subtable.
807    pub fn iter(&self) -> Cmap14Iter<'a> {
808        Cmap14Iter::new(self.clone())
809    }
810
811    fn selector(
812        &self,
813        index: usize,
814    ) -> (
815        Option<VariationSelector>,
816        Option<DefaultUvs<'a>>,
817        Option<NonDefaultUvs<'a>>,
818    ) {
819        let selector = self.var_selector().get(index).cloned();
820        let default_uvs = selector.as_ref().and_then(|selector| {
821            selector
822                .default_uvs(self.offset_data())
823                .transpose()
824                .ok()
825                .flatten()
826        });
827        let non_default_uvs = selector.as_ref().and_then(|selector| {
828            selector
829                .non_default_uvs(self.offset_data())
830                .transpose()
831                .ok()
832                .flatten()
833        });
834        (selector, default_uvs, non_default_uvs)
835    }
836
837    #[cfg(feature = "std")]
838    pub fn closure_glyphs(&self, unicodes: &IntSet<u32>, glyph_set: &mut IntSet<GlyphId>) {
839        for selector in self.var_selector() {
840            if !unicodes.contains(selector.var_selector().to_u32()) {
841                continue;
842            }
843            if let Some(non_default_uvs) = selector
844                .non_default_uvs(self.offset_data())
845                .transpose()
846                .ok()
847                .flatten()
848            {
849                glyph_set.extend(
850                    non_default_uvs
851                        .uvs_mapping()
852                        .iter()
853                        .filter(|m| unicodes.contains(m.unicode_value().to_u32()))
854                        .map(|m| m.glyph_id().into()),
855                );
856            }
857        }
858    }
859}
860
861/// Iterator over all (codepoint, selector, mapping variant) triples
862/// in the subtable.
863#[derive(Clone)]
864pub struct Cmap14Iter<'a> {
865    subtable: Cmap14<'a>,
866    selector_record: Option<VariationSelector>,
867    default_uvs: Option<DefaultUvsIter<'a>>,
868    non_default_uvs: Option<NonDefaultUvsIter<'a>>,
869    cur_selector_ix: usize,
870}
871
872impl<'a> Cmap14Iter<'a> {
873    fn new(subtable: Cmap14<'a>) -> Self {
874        let (selector_record, default_uvs, non_default_uvs) = subtable.selector(0);
875        Self {
876            subtable,
877            selector_record,
878            default_uvs: default_uvs.map(DefaultUvsIter::new),
879            non_default_uvs: non_default_uvs.map(NonDefaultUvsIter::new),
880            cur_selector_ix: 0,
881        }
882    }
883}
884
885impl Iterator for Cmap14Iter<'_> {
886    type Item = (u32, u32, MapVariant);
887
888    fn next(&mut self) -> Option<Self::Item> {
889        loop {
890            let selector_record = self.selector_record.as_ref()?;
891            let selector: u32 = selector_record.var_selector().into();
892            if let Some(default_uvs) = self.default_uvs.as_mut() {
893                if let Some(codepoint) = default_uvs.next() {
894                    return Some((codepoint, selector, MapVariant::UseDefault));
895                }
896            }
897            if let Some(non_default_uvs) = self.non_default_uvs.as_mut() {
898                if let Some((codepoint, variant)) = non_default_uvs.next() {
899                    return Some((codepoint, selector, MapVariant::Variant(variant.into())));
900                }
901            }
902            self.cur_selector_ix += 1;
903            let (selector_record, default_uvs, non_default_uvs) =
904                self.subtable.selector(self.cur_selector_ix);
905            self.selector_record = selector_record;
906            self.default_uvs = default_uvs.map(DefaultUvsIter::new);
907            self.non_default_uvs = non_default_uvs.map(NonDefaultUvsIter::new);
908        }
909    }
910}
911
912#[derive(Clone)]
913struct DefaultUvsIter<'a> {
914    ranges: std::slice::Iter<'a, UnicodeRange>,
915    cur_range: Range<u32>,
916}
917
918impl<'a> DefaultUvsIter<'a> {
919    fn new(ranges: DefaultUvs<'a>) -> Self {
920        let mut ranges = ranges.ranges().iter();
921        let cur_range = if let Some(range) = ranges.next() {
922            let start: u32 = range.start_unicode_value().into();
923            let end = start + range.additional_count() as u32 + 1;
924            start..end
925        } else {
926            0..0
927        };
928        Self { ranges, cur_range }
929    }
930}
931
932impl Iterator for DefaultUvsIter<'_> {
933    type Item = u32;
934
935    fn next(&mut self) -> Option<Self::Item> {
936        loop {
937            if let Some(codepoint) = self.cur_range.next() {
938                return Some(codepoint);
939            }
940            let range = self.ranges.next()?;
941            let start: u32 = range.start_unicode_value().into();
942            let end = start + range.additional_count() as u32 + 1;
943            self.cur_range = start..end;
944        }
945    }
946}
947
948#[derive(Clone)]
949struct NonDefaultUvsIter<'a> {
950    iter: std::slice::Iter<'a, UvsMapping>,
951}
952
953impl<'a> NonDefaultUvsIter<'a> {
954    fn new(uvs: NonDefaultUvs<'a>) -> Self {
955        Self {
956            iter: uvs.uvs_mapping().iter(),
957        }
958    }
959}
960
961impl Iterator for NonDefaultUvsIter<'_> {
962    type Item = (u32, GlyphId16);
963
964    fn next(&mut self) -> Option<Self::Item> {
965        let mapping = self.iter.next()?;
966        let codepoint: u32 = mapping.unicode_value().into();
967        let glyph_id = GlyphId16::new(mapping.glyph_id());
968        Some((codepoint, glyph_id))
969    }
970}
971
972#[cfg(test)]
973mod tests {
974    use font_test_data::{be_buffer, bebuffer::BeBuffer};
975
976    use super::*;
977    use crate::{FontRef, GlyphId, TableProvider};
978
979    #[test]
980    fn map_codepoints() {
981        let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
982        let cmap = font.cmap().unwrap();
983        assert_eq!(cmap.map_codepoint('A'), Some(GlyphId::new(1)));
984        assert_eq!(cmap.map_codepoint('À'), Some(GlyphId::new(2)));
985        assert_eq!(cmap.map_codepoint('`'), Some(GlyphId::new(3)));
986        assert_eq!(cmap.map_codepoint('B'), None);
987
988        let font = FontRef::new(font_test_data::SIMPLE_GLYF).unwrap();
989        let cmap = font.cmap().unwrap();
990        assert_eq!(cmap.map_codepoint(' '), Some(GlyphId::new(1)));
991        assert_eq!(cmap.map_codepoint(0xE_u32), Some(GlyphId::new(2)));
992        assert_eq!(cmap.map_codepoint('B'), None);
993
994        let cmap0_data = cmap0_data();
995        let cmap = Cmap::read(FontData::new(cmap0_data.data())).unwrap();
996
997        assert_eq!(cmap.map_codepoint(0u8), Some(GlyphId::new(0)));
998        assert_eq!(cmap.map_codepoint(b' '), Some(GlyphId::new(178)));
999        assert_eq!(cmap.map_codepoint(b'r'), Some(GlyphId::new(193)));
1000        assert_eq!(cmap.map_codepoint(b'X'), Some(GlyphId::new(13)));
1001        assert_eq!(cmap.map_codepoint(255u8), Some(GlyphId::new(3)));
1002
1003        let cmap6_data = be_buffer! {
1004            // version
1005            0u16,
1006            // numTables
1007            1u16,
1008            // platformID
1009            1u16,
1010            // encodingID
1011            0u16,
1012            // subtableOffset
1013            12u32,
1014            // format
1015            6u16,
1016            // length
1017            32u16,
1018            // language
1019            0u16,
1020            // firstCode
1021            32u16,
1022            // entryCount
1023            5u16,
1024            // glyphIDArray
1025            [10u16, 15, 7, 20, 4]
1026        };
1027
1028        let cmap = Cmap::read(FontData::new(cmap6_data.data())).unwrap();
1029
1030        assert_eq!(cmap.map_codepoint(0u8), None);
1031        assert_eq!(cmap.map_codepoint(31u8), None);
1032        assert_eq!(cmap.map_codepoint(33u8), Some(GlyphId::new(15)));
1033        assert_eq!(cmap.map_codepoint(35u8), Some(GlyphId::new(20)));
1034        assert_eq!(cmap.map_codepoint(36u8), Some(GlyphId::new(4)));
1035        assert_eq!(cmap.map_codepoint(50u8), None);
1036    }
1037
1038    #[test]
1039    fn map_variants() {
1040        use super::MapVariant::*;
1041        let font = FontRef::new(font_test_data::CMAP14_FONT1).unwrap();
1042        let cmap = font.cmap().unwrap();
1043        let cmap14 = find_cmap14(&cmap).unwrap();
1044        let selector = '\u{e0100}';
1045        assert_eq!(cmap14.map_variant('a', selector), None);
1046        assert_eq!(cmap14.map_variant('\u{4e00}', selector), Some(UseDefault));
1047        assert_eq!(cmap14.map_variant('\u{4e06}', selector), Some(UseDefault));
1048        assert_eq!(
1049            cmap14.map_variant('\u{4e08}', selector),
1050            Some(Variant(GlyphId::new(25)))
1051        );
1052        assert_eq!(
1053            cmap14.map_variant('\u{4e09}', selector),
1054            Some(Variant(GlyphId::new(26)))
1055        );
1056    }
1057
1058    #[test]
1059    #[cfg(feature = "std")]
1060    fn cmap14_closure_glyphs() {
1061        let font = FontRef::new(font_test_data::CMAP14_FONT1).unwrap();
1062        let cmap = font.cmap().unwrap();
1063        let mut unicodes = IntSet::empty();
1064        unicodes.insert(0x4e08_u32);
1065        unicodes.insert(0xe0100_u32);
1066
1067        let mut glyph_set = IntSet::empty();
1068        glyph_set.insert(GlyphId::new(18));
1069        cmap.closure_glyphs(&unicodes, &mut glyph_set);
1070
1071        assert_eq!(glyph_set.len(), 2);
1072        assert!(glyph_set.contains(GlyphId::new(18)));
1073        assert!(glyph_set.contains(GlyphId::new(25)));
1074    }
1075
1076    #[test]
1077    fn cmap4_iter() {
1078        let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
1079        let cmap4 = find_cmap4(&font.cmap().unwrap()).unwrap();
1080        let mut count = 0;
1081        for (codepoint, glyph_id) in cmap4.iter() {
1082            assert_eq!(cmap4.map_codepoint(codepoint), Some(glyph_id));
1083            count += 1;
1084        }
1085        assert_eq!(count, 4);
1086        let font = FontRef::new(font_test_data::SIMPLE_GLYF).unwrap();
1087        let cmap4 = find_cmap4(&font.cmap().unwrap()).unwrap();
1088        let mut count = 0;
1089        for (codepoint, glyph_id) in cmap4.iter() {
1090            assert_eq!(cmap4.map_codepoint(codepoint), Some(glyph_id));
1091            count += 1;
1092        }
1093        assert_eq!(count, 3);
1094    }
1095
1096    #[test]
1097    fn cmap4_iter_explicit_notdef() {
1098        let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
1099        let cmap4 = find_cmap4(&font.cmap().unwrap()).unwrap();
1100        let mut notdef_count = 0;
1101        for (_, glyph_id) in cmap4.iter() {
1102            notdef_count += (glyph_id == GlyphId::NOTDEF) as i32;
1103        }
1104        assert!(notdef_count > 0);
1105        assert_eq!(cmap4.map_codepoint(0xFFFF_u32), Some(GlyphId::NOTDEF));
1106    }
1107
1108    // Make sure we don't bail early when iterating ranges with holes.
1109    // Encountered with Gentium Basic and Gentium Basic Book.
1110    // See <https://github.com/googlefonts/fontations/issues/897>
1111    #[test]
1112    fn cmap4_iter_sparse_range() {
1113        #[rustfmt::skip]
1114        let cmap4_data: &[u16] = &[
1115            // format, length, lang
1116            4, 0, 0,
1117            // segCountX2
1118            4,
1119            // bin search data
1120            0, 0, 0,
1121            // end code
1122            262, 0xFFFF, 
1123            // reserved pad
1124            0,
1125            // start code
1126            259, 0xFFFF,
1127            // id delta
1128            0, 1, 
1129            // id range offset
1130            4, 0,
1131            // glyph ids
1132            236, 0, 0, 326,
1133        ];
1134        let mut buf = BeBuffer::new();
1135        for &word in cmap4_data {
1136            buf = buf.push(word);
1137        }
1138        let cmap4 = Cmap4::read(FontData::new(&buf)).unwrap();
1139        let mappings = cmap4
1140            .iter()
1141            .map(|(ch, gid)| (ch, gid.to_u32()))
1142            .collect::<Vec<_>>();
1143        assert_eq!(mappings, &[(259, 236), (262, 326), (65535, 0)]);
1144    }
1145
1146    // When two segments overlap, the iterator clamps the *iteration* range of
1147    // the later segment to avoid emitting duplicate codepoints, but it must
1148    // still use that segment's real start code when indexing the glyph id
1149    // array. Otherwise codepoints in the clamped tail resolve to the wrong
1150    // glyph. See the overlap handling in the format 12/13 iterator for the
1151    // correct shape.
1152    #[test]
1153    fn cmap4_iter_overlapping_range_offset_segment() {
1154        #[rustfmt::skip]
1155        let cmap4_data: &[u16] = &[
1156            // format, length, lang
1157            4, 0, 0,
1158            // segCountX2
1159            6,
1160            // bin search data (searchRange, entrySelector, rangeShift)
1161            0, 0, 0,
1162            // end code
1163            20, 25, 0xFFFF,
1164            // reserved pad
1165            0,
1166            // start code (segment 1 overlaps segment 0: 15 <= 20)
1167            10, 15, 0xFFFF,
1168            // id delta
1169            0, 0, 1,
1170            // id range offset (segment 1 maps via the glyph id array)
1171            0, 8, 0,
1172            // glyph id array
1173            100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112,
1174        ];
1175        let mut buf = BeBuffer::new();
1176        for &word in cmap4_data {
1177            buf = buf.push(word);
1178        }
1179        let cmap4 = Cmap4::read(FontData::new(&buf)).unwrap();
1180        let mappings = cmap4
1181            .iter()
1182            .map(|(ch, gid)| (ch, gid.to_u32()))
1183            .collect::<Vec<_>>();
1184
1185        // Codepoints 21..=25 live only in segment 1, so they are resolved
1186        // through its glyph id array using start code 15. With start code 15
1187        // the indices land on glyph ids 108..=112; using the clamped value 21
1188        // instead would (incorrectly) yield 102..=106.
1189        assert_eq!(
1190            mappings,
1191            &[
1192                (10, 10),
1193                (11, 11),
1194                (12, 12),
1195                (13, 13),
1196                (14, 14),
1197                (15, 15),
1198                (16, 16),
1199                (17, 17),
1200                (18, 18),
1201                (19, 19),
1202                (20, 20),
1203                (21, 108),
1204                (22, 109),
1205                (23, 110),
1206                (24, 111),
1207                (25, 112),
1208                (65535, 0),
1209            ]
1210        );
1211    }
1212
1213    const CMAP6_PAIRS: &[(u32, u32)] = &[
1214        (0x1723, 1),
1215        (0x1724, 2),
1216        (0x1725, 3),
1217        (0x1726, 4),
1218        (0x1727, 5),
1219    ];
1220
1221    #[test]
1222    fn cmap6_map() {
1223        let font = FontRef::new(font_test_data::CMAP6).unwrap();
1224        let cmap = font.cmap().unwrap();
1225        let CmapSubtable::Format6(cmap6) = cmap.subtable(0).unwrap() else {
1226            panic!("should be a format 6 subtable");
1227        };
1228        for (ch, gid) in CMAP6_PAIRS {
1229            assert_eq!(cmap6.map_codepoint(*ch).unwrap().to_u32(), *gid);
1230        }
1231        // Check out of bounds codepoints
1232        assert!(cmap6.map_codepoint(CMAP6_PAIRS[0].0 - 1).is_none());
1233        assert!(cmap6
1234            .map_codepoint(CMAP6_PAIRS.last().copied().unwrap().0 + 1)
1235            .is_none());
1236    }
1237
1238    #[test]
1239    fn cmap6_iter() {
1240        let font = FontRef::new(font_test_data::CMAP6).unwrap();
1241        let cmap = font.cmap().unwrap();
1242        let CmapSubtable::Format6(cmap6) = cmap.subtable(0).unwrap() else {
1243            panic!("should be a format 6 subtable");
1244        };
1245        let pairs = cmap6
1246            .iter()
1247            .map(|(ch, gid)| (ch, gid.to_u32()))
1248            .collect::<Vec<_>>();
1249        assert_eq!(pairs, CMAP6_PAIRS);
1250    }
1251
1252    const CMAP10_PAIRS: &[(u32, u32)] = &[(0x109423, 26), (0x109424, 27), (0x109425, 32)];
1253
1254    #[test]
1255    fn cmap10_map() {
1256        let font = FontRef::new(font_test_data::CMAP10).unwrap();
1257        let cmap = font.cmap().unwrap();
1258        let CmapSubtable::Format10(cmap10) = cmap.subtable(0).unwrap() else {
1259            panic!("should be a format 10 subtable");
1260        };
1261        for (ch, gid) in CMAP10_PAIRS {
1262            assert_eq!(cmap10.map_codepoint(*ch).unwrap().to_u32(), *gid);
1263        }
1264        // Check out of bounds codepoints
1265        assert!(cmap10.map_codepoint(CMAP10_PAIRS[0].0 - 1).is_none());
1266        assert!(cmap10
1267            .map_codepoint(CMAP10_PAIRS.last().copied().unwrap().0 + 1)
1268            .is_none());
1269    }
1270
1271    #[test]
1272    fn cmap10_iter() {
1273        let font = FontRef::new(font_test_data::CMAP10).unwrap();
1274        let cmap = font.cmap().unwrap();
1275        let CmapSubtable::Format10(cmap10) = cmap.subtable(0).unwrap() else {
1276            panic!("should be a format 10 subtable");
1277        };
1278        let pairs = cmap10
1279            .iter()
1280            .map(|(ch, gid)| (ch, gid.to_u32()))
1281            .collect::<Vec<_>>();
1282        assert_eq!(pairs, CMAP10_PAIRS);
1283    }
1284
1285    #[test]
1286    fn cmap12_iter() {
1287        let font = FontRef::new(font_test_data::CMAP12_FONT1).unwrap();
1288        let cmap12 = find_cmap12(&font.cmap().unwrap()).unwrap();
1289        let mut count = 0;
1290        for (codepoint, glyph_id) in cmap12.iter() {
1291            assert_eq!(cmap12.map_codepoint(codepoint), Some(glyph_id));
1292            count += 1;
1293        }
1294        assert_eq!(count, 10);
1295    }
1296
1297    // oss-fuzz: detected integer addition overflow in Cmap12::group()
1298    // ref: https://oss-fuzz.com/testcase-detail/5141969742397440
1299    // and https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=69547
1300    #[test]
1301    fn cmap12_iter_avoid_overflow() {
1302        // reconstructed cmap from <https://oss-fuzz.com/testcase-detail/5141969742397440>
1303        let data = be_buffer! {
1304            12u16,      // format
1305            0u16,       // reserved, set to 0
1306            0u32,       // length, ignored
1307            0u32,       // language, ignored
1308            2u32,       // numGroups
1309            // groups: [startCode, endCode, startGlyphID]
1310            [0xFFFFFFFA_u32, 0xFFFFFFFC, 0], // group 0
1311            [0xFFFFFFFB_u32, 0xFFFFFFFF, 0] // group 1
1312        };
1313        let cmap12 = Cmap12::read(data.data().into()).unwrap();
1314        let _ = cmap12.iter().count();
1315    }
1316
1317    // oss-fuzz: timeout in Cmap12Iter
1318    // ref: https://oss-fuzz.com/testcase-detail/4628971063934976
1319    // and https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=69540
1320    #[test]
1321    fn cmap12_iter_avoid_timeout() {
1322        // ranges: [SequentialMapGroup { start_char_code: 170, end_char_code: 1330926671, start_glyph_id: 328960 }]
1323        let cmap12_data = be_buffer! {
1324            12u16,      // format
1325            0u16,       // reserved, set to 0
1326            0u32,       // length, ignored
1327            0u32,       // language, ignored
1328            1u32,       // numGroups
1329            // groups: [startCode, endCode, startGlyphID]
1330            [170u32, 1330926671, 328960] // group 0
1331        };
1332        let cmap12 = Cmap12::read(cmap12_data.data().into()).unwrap();
1333        assert!(
1334            cmap12.iter_with_limits(CmapIterLimits::default()).count() <= char::MAX as usize + 1
1335        );
1336    }
1337
1338    // oss-fuzz: timeout in outlines, caused by cmap 12 iter
1339    // ref: <https://issues.oss-fuzz.com/issues/394638728>
1340    #[test]
1341    fn cmap12_iter_avoid_timeout2() {
1342        let cmap12_data = be_buffer! {
1343            12u16,      // format
1344            0u16,       // reserved, set to 0
1345            0u32,       // length, ignored
1346            0u32,       // language, ignored
1347            3u32,       // numGroups
1348            // groups: [startCode, endCode, startGlyphID]
1349            [199u32, 16777271, 2],
1350            [262u32, 262, 3],
1351            [268u32, 268, 4]
1352        };
1353        let cmap12 = Cmap12::read(cmap12_data.data().into()).unwrap();
1354        // In the test case, maxp.numGlyphs = 8
1355        const MAX_GLYPHS: u32 = 8;
1356        let limits = CmapIterLimits {
1357            glyph_count: MAX_GLYPHS,
1358            ..Default::default()
1359        };
1360        assert_eq!(cmap12.iter_with_limits(limits).count(), MAX_GLYPHS as usize);
1361    }
1362
1363    #[test]
1364    fn cmap12_iter_glyph_limit() {
1365        let font = FontRef::new(font_test_data::CMAP12_FONT1).unwrap();
1366        let cmap12 = find_cmap12(&font.cmap().unwrap()).unwrap();
1367        let mut limits = CmapIterLimits::default_for_font(&font);
1368        // Ensure we obey the glyph count limit.
1369        // This font has 11 glyphs
1370        for glyph_count in 0..=11 {
1371            limits.glyph_count = glyph_count;
1372            assert_eq!(
1373                cmap12.iter_with_limits(limits).count(),
1374                // We always return one less than glyph count limit because
1375                // notdef is not mapped
1376                (glyph_count as usize).saturating_sub(1)
1377            );
1378        }
1379    }
1380
1381    #[test]
1382    fn cmap12_iter_range_clamping() {
1383        let data = be_buffer! {
1384            12u16,      // format
1385            0u16,       // reserved, set to 0
1386            0u32,       // length, ignored
1387            0u32,       // language, ignored
1388            2u32,       // numGroups
1389            // groups: [startCode, endCode, startGlyphID]
1390            [0u32, 16777215, 0], // group 0
1391            [255u32, 0xFFFFFFFF, 0] // group 1
1392        };
1393        let cmap12 = Cmap12::read(data.data().into()).unwrap();
1394        let ranges = cmap12
1395            .groups()
1396            .iter()
1397            .map(|group| (group.start_char_code(), group.end_char_code()))
1398            .collect::<Vec<_>>();
1399        // These groups overlap and extend to the whole u32 range
1400        assert_eq!(ranges, &[(0, 16777215), (255, u32::MAX)]);
1401        // But we produce at most char::MAX + 1 results
1402        let limits = CmapIterLimits {
1403            glyph_count: u32::MAX,
1404            ..Default::default()
1405        };
1406        assert!(cmap12.iter_with_limits(limits).count() <= char::MAX as usize + 1);
1407    }
1408
1409    // Ensure range bounds stay monotonic across groups even when a middle
1410    // group's end is saturated by glyph limits. Without this, a later group
1411    // can reset iteration backwards.
1412    #[test]
1413    fn cmap12_iter_saturated_group_does_not_reset_range() {
1414        let data = be_buffer! {
1415            12u16,      // format
1416            0u16,       // reserved, set to 0
1417            0u32,       // length, ignored
1418            0u32,       // language, ignored
1419            3u32,       // numGroups
1420            // groups: [startCode, endCode, startGlyphID]
1421            [10u32, 20, 0],
1422            [15u32, 40, 100],
1423            [18u32, 22, 0]
1424        };
1425        let cmap12 = Cmap12::read(data.data().into()).unwrap();
1426        let limits = CmapIterLimits {
1427            glyph_count: 50,
1428            ..Default::default()
1429        };
1430        let codepoints = cmap12
1431            .iter_with_limits(limits)
1432            .map(|(cp, _)| cp)
1433            .collect::<Vec<_>>();
1434        assert_eq!(codepoints, (10..=22).collect::<Vec<_>>());
1435    }
1436
1437    #[test]
1438    fn cmap12_iter_explicit_notdef() {
1439        let data = be_buffer! {
1440            12u16,      // format
1441            0u16,       // reserved, set to 0
1442            0u32,       // length, ignored
1443            0u32,       // language, ignored
1444            1u32,       // numGroups
1445            // groups: [startCode, endCode, startGlyphID]
1446            [0_u32, 1_u32, 0] // group 0
1447        };
1448        let cmap12 = Cmap12::read(data.data().into()).unwrap();
1449        for (i, (codepoint, glyph_id)) in cmap12.iter().enumerate() {
1450            assert_eq!(codepoint as usize, i);
1451            assert_eq!(glyph_id.to_u32() as usize, i);
1452        }
1453        assert_eq!(cmap12.iter().next().unwrap().1, GlyphId::NOTDEF);
1454    }
1455
1456    fn cmap13_data() -> Vec<u8> {
1457        let data = be_buffer! {
1458            13u16,      // format
1459            0u16,       // reserved, set to 0
1460            0u32,       // length, ignored
1461            0u32,       // language, ignored
1462            2u32,       // numGroups
1463            // groups: [startCode, endCode, startGlyphID]
1464            [0u32, 8, 20], // group 0
1465            [42u32, 46u32, 30] // group 1
1466        };
1467        data.to_vec()
1468    }
1469
1470    #[test]
1471    fn cmap13_map() {
1472        let data = cmap13_data();
1473        let cmap13 = Cmap13::read(FontData::new(&data)).unwrap();
1474        for ch in 0u32..=8 {
1475            assert_eq!(cmap13.map_codepoint(ch), Some(GlyphId::new(20)));
1476        }
1477        for ch in 9u32..42 {
1478            assert_eq!(cmap13.map_codepoint(ch), None);
1479        }
1480        for ch in 42u32..=46 {
1481            assert_eq!(cmap13.map_codepoint(ch), Some(GlyphId::new(30)));
1482        }
1483        for ch in 47u32..1024 {
1484            assert_eq!(cmap13.map_codepoint(ch), None);
1485        }
1486    }
1487
1488    #[test]
1489    fn cmap13_iter() {
1490        let data = cmap13_data();
1491        let cmap13 = Cmap13::read(FontData::new(&data)).unwrap();
1492        for (ch, gid) in cmap13.iter() {
1493            assert_eq!(cmap13.map_codepoint(ch), Some(gid));
1494        }
1495    }
1496
1497    #[test]
1498    fn cmap14_iter() {
1499        let font = FontRef::new(font_test_data::CMAP14_FONT1).unwrap();
1500        let cmap14 = find_cmap14(&font.cmap().unwrap()).unwrap();
1501        let mut count = 0;
1502        for (codepoint, selector, mapping) in cmap14.iter() {
1503            assert_eq!(cmap14.map_variant(codepoint, selector), Some(mapping));
1504            count += 1;
1505        }
1506        assert_eq!(count, 7);
1507    }
1508
1509    fn find_cmap4<'a>(cmap: &Cmap<'a>) -> Option<Cmap4<'a>> {
1510        cmap.encoding_records()
1511            .iter()
1512            .filter_map(|record| record.subtable(cmap.offset_data()).ok())
1513            .find_map(|subtable| match subtable {
1514                CmapSubtable::Format4(cmap4) => Some(cmap4),
1515                _ => None,
1516            })
1517    }
1518
1519    fn find_cmap12<'a>(cmap: &Cmap<'a>) -> Option<Cmap12<'a>> {
1520        cmap.encoding_records()
1521            .iter()
1522            .filter_map(|record| record.subtable(cmap.offset_data()).ok())
1523            .find_map(|subtable| match subtable {
1524                CmapSubtable::Format12(cmap12) => Some(cmap12),
1525                _ => None,
1526            })
1527    }
1528
1529    fn find_cmap14<'a>(cmap: &Cmap<'a>) -> Option<Cmap14<'a>> {
1530        cmap.encoding_records()
1531            .iter()
1532            .filter_map(|record| record.subtable(cmap.offset_data()).ok())
1533            .find_map(|subtable| match subtable {
1534                CmapSubtable::Format14(cmap14) => Some(cmap14),
1535                _ => None,
1536            })
1537    }
1538
1539    /// <https://github.com/googlefonts/fontations/issues/1100>
1540    ///
1541    /// Note that this doesn't demonstrate the timeout, merely that we've eliminated the underlying
1542    /// enthusiasm for non-ascending ranges that enabled it
1543    #[test]
1544    fn cmap4_bad_data() {
1545        let buf = font_test_data::cmap::repetitive_cmap4();
1546        let cmap4 = Cmap4::read(FontData::new(buf.as_slice())).unwrap();
1547
1548        // we should have unique, ascending codepoints, not duplicates and overlaps
1549        assert_eq!(
1550            (6..=64).collect::<Vec<_>>(),
1551            cmap4.iter().map(|(cp, _)| cp).collect::<Vec<_>>()
1552        );
1553    }
1554
1555    fn cmap0_data() -> BeBuffer {
1556        be_buffer! {
1557            // version
1558            0u16,
1559            // numTables
1560            1u16,
1561            // platformID
1562            1u16,
1563            // encodingID
1564            0u16,
1565            // subtableOffset
1566            12u32,
1567            // format
1568            0u16,
1569            // length
1570            274u16,
1571            // language
1572            0u16,
1573            // glyphIDArray
1574            [0u8, 249, 32, 2, 198, 23, 1, 4, 26, 36,
1575            171, 168, 69, 151, 208, 238, 226, 153, 161, 138,
1576            160, 130, 169, 223, 162, 207, 146, 227, 111, 248,
1577            163, 79, 178, 27, 50, 234, 213, 57, 45, 63,
1578            103, 186, 30, 105, 131, 118, 35, 140, 51, 211,
1579            75, 172, 56, 71, 137, 99, 22, 76, 61, 125,
1580            39, 8, 177, 117, 108, 97, 202, 92, 49, 134,
1581            93, 43, 80, 66, 84, 54, 180, 113, 11, 176,
1582            229, 48, 47, 17, 124, 40, 119, 21, 13, 133,
1583            181, 224, 33, 128, 44, 46, 38, 24, 65, 152,
1584            197, 225, 102, 251, 157, 126, 182, 242, 28, 184,
1585            90, 170, 201, 144, 193, 189, 250, 142, 77, 221,
1586            81, 164, 154, 60, 37, 200, 12, 53, 219, 89,
1587            31, 209, 188, 179, 253, 220, 127, 18, 19, 64,
1588            20, 141, 98, 173, 55, 194, 70, 107, 228, 104,
1589            10, 9, 15, 217, 255, 222, 196, 236, 67, 165,
1590            5, 143, 149, 100, 91, 95, 135, 235, 145, 204,
1591            72, 114, 246, 82, 245, 233, 106, 158, 185, 212,
1592            86, 243, 16, 195, 123, 190, 120, 187, 132, 139,
1593            192, 239, 110, 183, 240, 214, 166, 41, 59, 231,
1594            42, 94, 244, 83, 121, 25, 215, 96, 73, 87,
1595            174, 136, 62, 206, 156, 175, 230, 150, 116, 147,
1596            68, 122, 78, 112, 6, 167, 232, 254, 52, 34,
1597            191, 85, 241, 14, 216, 155, 29, 101, 115, 210,
1598            252, 218, 129, 247, 203, 159, 109, 74, 7, 58,
1599            237, 199, 88, 205, 148, 3]
1600        }
1601    }
1602
1603    #[test]
1604    fn best_subtable_full() {
1605        let font = FontRef::new(font_test_data::VORG).unwrap();
1606        let cmap = font.cmap().unwrap();
1607        let (index, record, _) = cmap.best_subtable().unwrap();
1608        assert_eq!(
1609            (index, record.platform_id(), record.encoding_id()),
1610            (3, PlatformId::Windows, WINDOWS_UNICODE_FULL_ENCODING)
1611        );
1612    }
1613
1614    #[test]
1615    fn best_subtable_bmp() {
1616        let font = FontRef::new(font_test_data::CMAP12_FONT1).unwrap();
1617        let cmap = font.cmap().unwrap();
1618        let (index, record, _) = cmap.best_subtable().unwrap();
1619        assert_eq!(
1620            (index, record.platform_id(), record.encoding_id()),
1621            (0, PlatformId::Windows, WINDOWS_UNICODE_BMP_ENCODING)
1622        );
1623    }
1624
1625    #[test]
1626    fn best_subtable_symbol() {
1627        let font = FontRef::new(font_test_data::CMAP4_SYMBOL_PUA).unwrap();
1628        let cmap = font.cmap().unwrap();
1629        let (index, record, _) = cmap.best_subtable().unwrap();
1630        assert!(record.is_symbol());
1631        assert_eq!(
1632            (index, record.platform_id(), record.encoding_id()),
1633            (0, PlatformId::Windows, WINDOWS_SYMBOL_ENCODING)
1634        );
1635    }
1636
1637    #[test]
1638    fn uvs_subtable() {
1639        let font = FontRef::new(font_test_data::CMAP14_FONT1).unwrap();
1640        let cmap = font.cmap().unwrap();
1641        let (index, _) = cmap.uvs_subtable().unwrap();
1642        assert_eq!(index, 0);
1643    }
1644}