Coverage Report

Created: 2026-08-31 06:46

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/ttf-parser/src/tables/cmap/mod.rs
Line
Count
Source
1
/*!
2
A [Character to Glyph Index Mapping Table](
3
https://docs.microsoft.com/en-us/typography/opentype/spec/cmap) implementation.
4
5
This module provides a low-level alternative to
6
[`Face::glyph_index`](../struct.Face.html#method.glyph_index) and
7
[`Face::glyph_variation_index`](../struct.Face.html#method.glyph_variation_index)
8
methods.
9
*/
10
11
use crate::parser::{FromData, LazyArray16, Offset, Offset32, Stream};
12
use crate::{GlyphId, name::PlatformId};
13
14
mod format0;
15
mod format10;
16
mod format12;
17
mod format13;
18
mod format14;
19
mod format2;
20
mod format4;
21
mod format6;
22
23
pub use format0::Subtable0;
24
pub use format2::Subtable2;
25
pub use format4::Subtable4;
26
pub use format6::Subtable6;
27
pub use format10::Subtable10;
28
pub use format12::Subtable12;
29
pub use format13::Subtable13;
30
pub use format14::{GlyphVariationResult, Subtable14};
31
32
/// A character encoding subtable variant.
33
#[allow(missing_docs)]
34
#[derive(Clone, Copy, Debug)]
35
pub enum Format<'a> {
36
    ByteEncodingTable(Subtable0<'a>),
37
    HighByteMappingThroughTable(Subtable2<'a>),
38
    SegmentMappingToDeltaValues(Subtable4<'a>),
39
    TrimmedTableMapping(Subtable6<'a>),
40
    MixedCoverage, // unsupported
41
    TrimmedArray(Subtable10<'a>),
42
    SegmentedCoverage(Subtable12<'a>),
43
    ManyToOneRangeMappings(Subtable13<'a>),
44
    UnicodeVariationSequences(Subtable14<'a>),
45
}
46
47
/// A character encoding subtable.
48
#[derive(Clone, Copy, Debug)]
49
pub struct Subtable<'a> {
50
    /// Subtable platform.
51
    pub platform_id: PlatformId,
52
    /// Subtable encoding.
53
    pub encoding_id: u16,
54
    /// A subtable format.
55
    pub format: Format<'a>,
56
}
57
58
impl<'a> Subtable<'a> {
59
    /// Checks that the current encoding is Unicode compatible.
60
    #[inline]
61
49.7k
    pub fn is_unicode(&self) -> bool {
62
        // https://docs.microsoft.com/en-us/typography/opentype/spec/name#windows-encoding-ids
63
        const WINDOWS_UNICODE_BMP_ENCODING_ID: u16 = 1;
64
        const WINDOWS_UNICODE_FULL_REPERTOIRE_ENCODING_ID: u16 = 10;
65
66
1.72k
        match self.platform_id {
67
45.4k
            PlatformId::Unicode => true,
68
1.72k
            PlatformId::Windows if self.encoding_id == WINDOWS_UNICODE_BMP_ENCODING_ID => true,
69
            PlatformId::Windows => {
70
                // "Note: Subtable format 13 has the same structure as format 12; it differs only
71
                // in the interpretation of the startGlyphID/glyphID fields".
72
1.32k
                let is_format_12_compatible = matches!(
73
1.32k
                    self.format,
74
                    Format::SegmentedCoverage(..) | Format::ManyToOneRangeMappings(..)
75
                );
76
77
                // "Fonts that support Unicode supplementary-plane characters (U+10000 to U+10FFFF)
78
                // on the Windows platform must have a format 12 subtable for platform ID 3,
79
                // encoding ID 10."
80
1.32k
                self.encoding_id == WINDOWS_UNICODE_FULL_REPERTOIRE_ENCODING_ID
81
338
                    && is_format_12_compatible
82
            }
83
2.59k
            _ => false,
84
        }
85
49.7k
    }
<ttf_parser::tables::cmap::Subtable>::is_unicode
Line
Count
Source
61
41.3k
    pub fn is_unicode(&self) -> bool {
62
        // https://docs.microsoft.com/en-us/typography/opentype/spec/name#windows-encoding-ids
63
        const WINDOWS_UNICODE_BMP_ENCODING_ID: u16 = 1;
64
        const WINDOWS_UNICODE_FULL_REPERTOIRE_ENCODING_ID: u16 = 10;
65
66
1.53k
        match self.platform_id {
67
37.5k
            PlatformId::Unicode => true,
68
1.53k
            PlatformId::Windows if self.encoding_id == WINDOWS_UNICODE_BMP_ENCODING_ID => true,
69
            PlatformId::Windows => {
70
                // "Note: Subtable format 13 has the same structure as format 12; it differs only
71
                // in the interpretation of the startGlyphID/glyphID fields".
72
1.15k
                let is_format_12_compatible = matches!(
73
1.15k
                    self.format,
74
                    Format::SegmentedCoverage(..) | Format::ManyToOneRangeMappings(..)
75
                );
76
77
                // "Fonts that support Unicode supplementary-plane characters (U+10000 to U+10FFFF)
78
                // on the Windows platform must have a format 12 subtable for platform ID 3,
79
                // encoding ID 10."
80
1.15k
                self.encoding_id == WINDOWS_UNICODE_FULL_REPERTOIRE_ENCODING_ID
81
303
                    && is_format_12_compatible
82
            }
83
2.20k
            _ => false,
84
        }
85
41.3k
    }
<ttf_parser::tables::cmap::Subtable>::is_unicode
Line
Count
Source
61
8.43k
    pub fn is_unicode(&self) -> bool {
62
        // https://docs.microsoft.com/en-us/typography/opentype/spec/name#windows-encoding-ids
63
        const WINDOWS_UNICODE_BMP_ENCODING_ID: u16 = 1;
64
        const WINDOWS_UNICODE_FULL_REPERTOIRE_ENCODING_ID: u16 = 10;
65
66
197
        match self.platform_id {
67
7.85k
            PlatformId::Unicode => true,
68
197
            PlatformId::Windows if self.encoding_id == WINDOWS_UNICODE_BMP_ENCODING_ID => true,
69
            PlatformId::Windows => {
70
                // "Note: Subtable format 13 has the same structure as format 12; it differs only
71
                // in the interpretation of the startGlyphID/glyphID fields".
72
165
                let is_format_12_compatible = matches!(
73
165
                    self.format,
74
                    Format::SegmentedCoverage(..) | Format::ManyToOneRangeMappings(..)
75
                );
76
77
                // "Fonts that support Unicode supplementary-plane characters (U+10000 to U+10FFFF)
78
                // on the Windows platform must have a format 12 subtable for platform ID 3,
79
                // encoding ID 10."
80
165
                self.encoding_id == WINDOWS_UNICODE_FULL_REPERTOIRE_ENCODING_ID
81
35
                    && is_format_12_compatible
82
            }
83
384
            _ => false,
84
        }
85
8.43k
    }
86
87
    /// Maps a character to a glyph ID.
88
    ///
89
    /// This is a low-level method and unlike `Face::glyph_index` it doesn't
90
    /// check that the current encoding is Unicode.
91
    /// It simply maps a `u32` codepoint number to a glyph ID.
92
    ///
93
    /// Returns `None`:
94
    /// - when glyph ID is `0`.
95
    /// - when format is `MixedCoverage`, since it's not supported.
96
    /// - when format is `UnicodeVariationSequences`. Use `glyph_variation_index` instead.
97
    #[inline]
98
46.1k
    pub fn glyph_index(&self, code_point: u32) -> Option<GlyphId> {
99
46.1k
        match self.format {
100
3.19k
            Format::ByteEncodingTable(ref subtable) => subtable.glyph_index(code_point),
101
19.1k
            Format::HighByteMappingThroughTable(ref subtable) => subtable.glyph_index(code_point),
102
8.90k
            Format::SegmentMappingToDeltaValues(ref subtable) => subtable.glyph_index(code_point),
103
2.77k
            Format::TrimmedTableMapping(ref subtable) => subtable.glyph_index(code_point),
104
718
            Format::MixedCoverage => None,
105
1.88k
            Format::TrimmedArray(ref subtable) => subtable.glyph_index(code_point),
106
5.49k
            Format::SegmentedCoverage(ref subtable) => subtable.glyph_index(code_point),
107
3.54k
            Format::ManyToOneRangeMappings(ref subtable) => subtable.glyph_index(code_point),
108
            // This subtable should be accessed via glyph_variation_index().
109
512
            Format::UnicodeVariationSequences(_) => None,
110
        }
111
46.1k
    }
<ttf_parser::tables::cmap::Subtable>::glyph_index
Line
Count
Source
98
38.2k
    pub fn glyph_index(&self, code_point: u32) -> Option<GlyphId> {
99
38.2k
        match self.format {
100
2.67k
            Format::ByteEncodingTable(ref subtable) => subtable.glyph_index(code_point),
101
16.9k
            Format::HighByteMappingThroughTable(ref subtable) => subtable.glyph_index(code_point),
102
7.41k
            Format::SegmentMappingToDeltaValues(ref subtable) => subtable.glyph_index(code_point),
103
2.13k
            Format::TrimmedTableMapping(ref subtable) => subtable.glyph_index(code_point),
104
502
            Format::MixedCoverage => None,
105
1.40k
            Format::TrimmedArray(ref subtable) => subtable.glyph_index(code_point),
106
4.17k
            Format::SegmentedCoverage(ref subtable) => subtable.glyph_index(code_point),
107
2.65k
            Format::ManyToOneRangeMappings(ref subtable) => subtable.glyph_index(code_point),
108
            // This subtable should be accessed via glyph_variation_index().
109
302
            Format::UnicodeVariationSequences(_) => None,
110
        }
111
38.2k
    }
<ttf_parser::tables::cmap::Subtable>::glyph_index
Line
Count
Source
98
7.92k
    pub fn glyph_index(&self, code_point: u32) -> Option<GlyphId> {
99
7.92k
        match self.format {
100
523
            Format::ByteEncodingTable(ref subtable) => subtable.glyph_index(code_point),
101
2.13k
            Format::HighByteMappingThroughTable(ref subtable) => subtable.glyph_index(code_point),
102
1.49k
            Format::SegmentMappingToDeltaValues(ref subtable) => subtable.glyph_index(code_point),
103
639
            Format::TrimmedTableMapping(ref subtable) => subtable.glyph_index(code_point),
104
216
            Format::MixedCoverage => None,
105
485
            Format::TrimmedArray(ref subtable) => subtable.glyph_index(code_point),
106
1.32k
            Format::SegmentedCoverage(ref subtable) => subtable.glyph_index(code_point),
107
892
            Format::ManyToOneRangeMappings(ref subtable) => subtable.glyph_index(code_point),
108
            // This subtable should be accessed via glyph_variation_index().
109
210
            Format::UnicodeVariationSequences(_) => None,
110
        }
111
7.92k
    }
112
113
    /// Resolves a variation of a glyph ID from two code points.
114
    ///
115
    /// Returns `None`:
116
    /// - when glyph ID is `0`.
117
    /// - when format is not `UnicodeVariationSequences`.
118
    #[inline]
119
    pub fn glyph_variation_index(
120
        &self,
121
        code_point: u32,
122
        variation: u32,
123
    ) -> Option<GlyphVariationResult> {
124
        match self.format {
125
            Format::UnicodeVariationSequences(ref subtable) => {
126
                subtable.glyph_index(code_point, variation)
127
            }
128
            _ => None,
129
        }
130
    }
131
132
    /// Calls `f` for all codepoints contained in this subtable.
133
    ///
134
    /// This is a low-level method and it doesn't check that the current
135
    /// encoding is Unicode. It simply calls the function `f` for all `u32`
136
    /// codepoints that are present in this subtable.
137
    ///
138
    /// Note that this may list codepoints for which `glyph_index` still returns
139
    /// `None` because this method finds all codepoints which were _defined_ in
140
    /// this subtable. The subtable may still map them to glyph ID `0`.
141
    ///
142
    /// Returns without doing anything:
143
    /// - when format is `MixedCoverage`, since it's not supported.
144
    /// - when format is `UnicodeVariationSequences`, since it's not supported.
145
    pub fn codepoints<F: FnMut(u32)>(&self, f: F) {
146
        match self.format {
147
            Format::ByteEncodingTable(ref subtable) => subtable.codepoints(f),
148
            Format::HighByteMappingThroughTable(ref subtable) => subtable.codepoints(f),
149
            Format::SegmentMappingToDeltaValues(ref subtable) => subtable.codepoints(f),
150
            Format::TrimmedTableMapping(ref subtable) => subtable.codepoints(f),
151
            Format::MixedCoverage => {} // unsupported
152
            Format::TrimmedArray(ref subtable) => subtable.codepoints(f),
153
            Format::SegmentedCoverage(ref subtable) => subtable.codepoints(f),
154
            Format::ManyToOneRangeMappings(ref subtable) => subtable.codepoints(f),
155
            Format::UnicodeVariationSequences(_) => {} // unsupported
156
        };
157
    }
158
}
159
160
#[derive(Clone, Copy)]
161
struct EncodingRecord {
162
    platform_id: PlatformId,
163
    encoding_id: u16,
164
    offset: Offset32,
165
}
166
167
impl FromData for EncodingRecord {
168
    const SIZE: usize = 8;
169
170
    #[inline]
171
56.1k
    fn parse(data: &[u8]) -> Option<Self> {
172
56.1k
        let mut s = Stream::new(data);
173
        Some(EncodingRecord {
174
56.1k
            platform_id: s.read::<PlatformId>()?,
175
54.6k
            encoding_id: s.read::<u16>()?,
176
54.6k
            offset: s.read::<Offset32>()?,
177
        })
178
56.1k
    }
179
}
180
181
/// A list of subtables.
182
#[derive(Clone, Copy, Default)]
183
pub struct Subtables<'a> {
184
    data: &'a [u8],
185
    records: LazyArray16<'a, EncodingRecord>,
186
}
187
188
impl core::fmt::Debug for Subtables<'_> {
189
0
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
190
0
        write!(f, "Subtables {{ ... }}")
191
0
    }
192
}
193
194
impl<'a> Subtables<'a> {
195
    /// Returns a subtable at an index.
196
56.1k
    pub fn get(&self, index: u16) -> Option<Subtable<'a>> {
197
56.1k
        let record = self.records.get(index)?;
198
54.6k
        let data = self.data.get(record.offset.to_usize()..)?;
199
52.7k
        let format = match Stream::read_at::<u16>(data, 0)? {
200
3.69k
            0 => Format::ByteEncodingTable(Subtable0::parse(data)?),
201
20.9k
            2 => Format::HighByteMappingThroughTable(Subtable2::parse(data)?),
202
9.74k
            4 => Format::SegmentMappingToDeltaValues(Subtable4::parse(data)?),
203
3.14k
            6 => Format::TrimmedTableMapping(Subtable6::parse(data)?),
204
807
            8 => Format::MixedCoverage, // unsupported
205
2.55k
            10 => Format::TrimmedArray(Subtable10::parse(data)?),
206
6.26k
            12 => Format::SegmentedCoverage(Subtable12::parse(data)?),
207
4.18k
            13 => Format::ManyToOneRangeMappings(Subtable13::parse(data)?),
208
961
            14 => Format::UnicodeVariationSequences(Subtable14::parse(data)?),
209
413
            _ => return None,
210
        };
211
212
49.7k
        Some(Subtable {
213
49.7k
            platform_id: record.platform_id,
214
49.7k
            encoding_id: record.encoding_id,
215
49.7k
            format,
216
49.7k
        })
217
56.1k
    }
218
219
    /// Returns the number of subtables.
220
    #[inline]
221
57.1k
    pub fn len(&self) -> u16 {
222
57.1k
        self.records.len()
223
57.1k
    }
<ttf_parser::tables::cmap::Subtables>::len
Line
Count
Source
221
47.5k
    pub fn len(&self) -> u16 {
222
47.5k
        self.records.len()
223
47.5k
    }
<ttf_parser::tables::cmap::Subtables>::len
Line
Count
Source
221
9.62k
    pub fn len(&self) -> u16 {
222
9.62k
        self.records.len()
223
9.62k
    }
224
225
    /// Checks if there are any subtables.
226
0
    pub fn is_empty(&self) -> bool {
227
0
        self.records.is_empty()
228
0
    }
229
}
230
231
impl<'a> IntoIterator for Subtables<'a> {
232
    type Item = Subtable<'a>;
233
    type IntoIter = SubtablesIter<'a>;
234
235
    #[inline]
236
8.91k
    fn into_iter(self) -> Self::IntoIter {
237
8.91k
        SubtablesIter {
238
8.91k
            subtables: self,
239
8.91k
            index: 0,
240
8.91k
        }
241
8.91k
    }
<ttf_parser::tables::cmap::Subtables as core::iter::traits::collect::IntoIterator>::into_iter
Line
Count
Source
236
7.63k
    fn into_iter(self) -> Self::IntoIter {
237
7.63k
        SubtablesIter {
238
7.63k
            subtables: self,
239
7.63k
            index: 0,
240
7.63k
        }
241
7.63k
    }
<ttf_parser::tables::cmap::Subtables as core::iter::traits::collect::IntoIterator>::into_iter
Line
Count
Source
236
1.27k
    fn into_iter(self) -> Self::IntoIter {
237
1.27k
        SubtablesIter {
238
1.27k
            subtables: self,
239
1.27k
            index: 0,
240
1.27k
        }
241
1.27k
    }
242
}
243
244
/// An iterator over [`Subtables`].
245
#[allow(missing_debug_implementations)]
246
pub struct SubtablesIter<'a> {
247
    subtables: Subtables<'a>,
248
    index: u16,
249
}
250
251
impl<'a> Iterator for SubtablesIter<'a> {
252
    type Item = Subtable<'a>;
253
254
    #[inline]
255
57.1k
    fn next(&mut self) -> Option<Self::Item> {
256
57.1k
        if self.index < self.subtables.len() {
257
56.1k
            self.index += 1;
258
56.1k
            self.subtables.get(self.index - 1)
259
        } else {
260
996
            None
261
        }
262
57.1k
    }
<ttf_parser::tables::cmap::SubtablesIter as core::iter::traits::iterator::Iterator>::next
Line
Count
Source
255
47.5k
    fn next(&mut self) -> Option<Self::Item> {
256
47.5k
        if self.index < self.subtables.len() {
257
46.7k
            self.index += 1;
258
46.7k
            self.subtables.get(self.index - 1)
259
        } else {
260
831
            None
261
        }
262
47.5k
    }
<ttf_parser::tables::cmap::SubtablesIter as core::iter::traits::iterator::Iterator>::next
Line
Count
Source
255
9.62k
    fn next(&mut self) -> Option<Self::Item> {
256
9.62k
        if self.index < self.subtables.len() {
257
9.45k
            self.index += 1;
258
9.45k
            self.subtables.get(self.index - 1)
259
        } else {
260
165
            None
261
        }
262
9.62k
    }
263
}
264
265
/// A [Character to Glyph Index Mapping Table](
266
/// https://docs.microsoft.com/en-us/typography/opentype/spec/cmap).
267
#[derive(Clone, Copy, Debug)]
268
pub struct Table<'a> {
269
    /// A list of subtables.
270
    pub subtables: Subtables<'a>,
271
}
272
273
impl<'a> Table<'a> {
274
    /// Parses a table from raw data.
275
4.64k
    pub fn parse(data: &'a [u8]) -> Option<Self> {
276
4.64k
        let mut s = Stream::new(data);
277
4.64k
        s.skip::<u16>(); // version
278
4.64k
        let count = s.read::<u16>()?;
279
4.60k
        let records = s.read_array16::<EncodingRecord>(count)?;
280
3.10k
        Some(Table {
281
3.10k
            subtables: Subtables { data, records },
282
3.10k
        })
283
4.64k
    }
284
}