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::parser::{FromData, LazyArray16, Offset, Offset32, Stream};
12use crate::{name::PlatformId, GlyphId};
13
14mod format0;
15mod format10;
16mod format12;
17mod format13;
18mod format14;
19mod format2;
20mod format4;
21mod format6;
22
23pub use format0::Subtable0;
24pub use format10::Subtable10;
25pub use format12::Subtable12;
26pub use format13::Subtable13;
27pub use format14::{GlyphVariationResult, Subtable14};
28pub use format2::Subtable2;
29pub use format4::Subtable4;
30pub use format6::Subtable6;
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/// A character encoding subtable.
48#[derive(Clone, Copy, Debug)]
49pub 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
58impl<'a> Subtable<'a> {
59 /// Checks that the current encoding is Unicode compatible.
60 #[inline]
61 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 match self.platform_id {
67 PlatformId::Unicode => true,
68 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 let is_format_12_compatible = matches!(
73 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 self.encoding_id == WINDOWS_UNICODE_FULL_REPERTOIRE_ENCODING_ID
81 && is_format_12_compatible
82 }
83 _ => false,
84 }
85 }
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 pub fn glyph_index(&self, code_point: u32) -> Option<GlyphId> {
99 match self.format {
100 Format::ByteEncodingTable(ref subtable) => subtable.glyph_index(code_point),
101 Format::HighByteMappingThroughTable(ref subtable) => subtable.glyph_index(code_point),
102 Format::SegmentMappingToDeltaValues(ref subtable) => subtable.glyph_index(code_point),
103 Format::TrimmedTableMapping(ref subtable) => subtable.glyph_index(code_point),
104 Format::MixedCoverage => None,
105 Format::TrimmedArray(ref subtable) => subtable.glyph_index(code_point),
106 Format::SegmentedCoverage(ref subtable) => subtable.glyph_index(code_point),
107 Format::ManyToOneRangeMappings(ref subtable) => subtable.glyph_index(code_point),
108 // This subtable should be accessed via glyph_variation_index().
109 Format::UnicodeVariationSequences(_) => None,
110 }
111 }
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)]
161struct EncodingRecord {
162 platform_id: PlatformId,
163 encoding_id: u16,
164 offset: Offset32,
165}
166
167impl FromData for EncodingRecord {
168 const SIZE: usize = 8;
169
170 #[inline]
171 fn parse(data: &[u8]) -> Option<Self> {
172 let mut s: Stream<'_> = Stream::new(data);
173 Some(EncodingRecord {
174 platform_id: s.read::<PlatformId>()?,
175 encoding_id: s.read::<u16>()?,
176 offset: s.read::<Offset32>()?,
177 })
178 }
179}
180
181/// A list of subtables.
182#[derive(Clone, Copy, Default)]
183pub struct Subtables<'a> {
184 data: &'a [u8],
185 records: LazyArray16<'a, EncodingRecord>,
186}
187
188impl core::fmt::Debug for Subtables<'_> {
189 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
190 write!(f, "Subtables {{ ... }}")
191 }
192}
193
194impl<'a> Subtables<'a> {
195 /// Returns a subtable at an index.
196 pub fn get(&self, index: u16) -> Option<Subtable<'a>> {
197 let record = self.records.get(index)?;
198 let data = self.data.get(record.offset.to_usize()..)?;
199 let format = match Stream::read_at::<u16>(data, 0)? {
200 0 => Format::ByteEncodingTable(Subtable0::parse(data)?),
201 2 => Format::HighByteMappingThroughTable(Subtable2::parse(data)?),
202 4 => Format::SegmentMappingToDeltaValues(Subtable4::parse(data)?),
203 6 => Format::TrimmedTableMapping(Subtable6::parse(data)?),
204 8 => Format::MixedCoverage, // unsupported
205 10 => Format::TrimmedArray(Subtable10::parse(data)?),
206 12 => Format::SegmentedCoverage(Subtable12::parse(data)?),
207 13 => Format::ManyToOneRangeMappings(Subtable13::parse(data)?),
208 14 => Format::UnicodeVariationSequences(Subtable14::parse(data)?),
209 _ => return None,
210 };
211
212 Some(Subtable {
213 platform_id: record.platform_id,
214 encoding_id: record.encoding_id,
215 format,
216 })
217 }
218
219 /// Returns the number of subtables.
220 #[inline]
221 pub fn len(&self) -> u16 {
222 self.records.len()
223 }
224
225 /// Checks if there are any subtables.
226 pub fn is_empty(&self) -> bool {
227 self.records.is_empty()
228 }
229}
230
231impl<'a> IntoIterator for Subtables<'a> {
232 type Item = Subtable<'a>;
233 type IntoIter = SubtablesIter<'a>;
234
235 #[inline]
236 fn into_iter(self) -> Self::IntoIter {
237 SubtablesIter {
238 subtables: self,
239 index: 0,
240 }
241 }
242}
243
244/// An iterator over [`Subtables`].
245#[allow(missing_debug_implementations)]
246pub struct SubtablesIter<'a> {
247 subtables: Subtables<'a>,
248 index: u16,
249}
250
251impl<'a> Iterator for SubtablesIter<'a> {
252 type Item = Subtable<'a>;
253
254 #[inline]
255 fn next(&mut self) -> Option<Self::Item> {
256 if self.index < self.subtables.len() {
257 self.index += 1;
258 self.subtables.get(self.index - 1)
259 } else {
260 None
261 }
262 }
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)]
268pub struct Table<'a> {
269 /// A list of subtables.
270 pub subtables: Subtables<'a>,
271}
272
273impl<'a> Table<'a> {
274 /// Parses a table from raw data.
275 pub fn parse(data: &'a [u8]) -> Option<Self> {
276 let mut s: Stream<'_> = Stream::new(data);
277 s.skip::<u16>(); // version
278 let count: u16 = s.read::<u16>()?;
279 let records: LazyArray16<'_, EncodingRecord> = s.read_array16::<EncodingRecord>(count)?;
280 Some(Table {
281 subtables: Subtables { data, records },
282 })
283 }
284}
285