ttf_parser/tables/cmap/
mod.rs

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