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