1use core::ops::Range;
2
3use read_fonts::{
4 tables::{
5 ankr::Ankr,
6 cmap::{Cmap, CmapSubtable, PlatformId},
7 feat::Feat,
8 gdef::Gdef,
9 glyf::Glyf,
10 gpos::Gpos,
11 gsub::Gsub,
12 gvar::Gvar,
13 hmtx::Hmtx,
14 hvar::Hvar,
15 kern::Kern,
16 kerx::Kerx,
17 loca::Loca,
18 morx::Morx,
19 mvar::Mvar,
20 trak::Trak,
21 vmtx::Vmtx,
22 vorg::Vorg,
23 vvar::Vvar,
24 },
25 types::Tag,
26 FontData, FontRead, FontRef, TableProvider, TopLevelTable,
27};
28
29const WINDOWS_SYMBOL_ENCODING: u16 = 0;
31const WINDOWS_UNICODE_BMP_ENCODING: u16 = 1;
32const WINDOWS_UNICODE_FULL_ENCODING: u16 = 10;
33
34const UNICODE_1_0_ENCODING: u16 = 0;
36const UNICODE_1_1_ENCODING: u16 = 1;
37const UNICODE_ISO_ENCODING: u16 = 2;
38const UNICODE_2_0_BMP_ENCODING: u16 = 3;
39const UNICODE_2_0_FULL_ENCODING: u16 = 4;
40
41const UNICODE_FULL_ENCODING: u16 = 6;
43
44#[derive(Clone)]
45pub struct TableRanges {
46 pub num_glyphs: u32,
47 pub units_per_em: u16,
48 pub loca_long: bool,
49 pub num_v_metrics: u16,
50 pub num_h_metrics: u16,
51 pub ascent: i16,
52 pub descent: i16,
53 pub loca: TableRange,
54 pub glyf: TableRange,
55 pub gvar: TableRange,
56 pub hmtx: TableRange,
57 pub hvar: TableRange,
58 pub vmtx: TableRange,
59 pub vvar: TableRange,
60 pub vorg: TableRange,
61 pub mvar: TableRange,
62 pub cmap: TableRange,
63 pub cmap_subtable: Option<SelectedCmapSubtable>,
64 pub cmap_vs_subtable: Option<u16>,
65 pub gdef: TableRange,
66 pub gsub: TableRange,
67 pub gpos: TableRange,
68 pub morx: TableRange,
69 pub kerx: TableRange,
70 pub ankr: TableRange,
71 pub kern: TableRange,
72 pub feat: TableRange,
73 pub trak: TableRange,
74}
75
76#[derive(Copy, Clone)]
77pub struct SelectedCmapSubtable {
78 pub index: u16,
79 pub is_mac_roman: bool,
80 pub is_symbol: bool,
81}
82
83impl TableRanges {
84 pub fn new(font: &FontRef) -> Self {
85 let num_glyphs = font
86 .maxp()
87 .map(|maxp| maxp.num_glyphs() as u32)
88 .unwrap_or_default();
89 let (units_per_em, loca_long) = font.head().map_or((1000, false), |head| {
90 (head.units_per_em(), head.index_to_loc_format() == 1)
91 });
92 let os2 = font.os2().ok();
93 let hhea = font.hhea().ok();
94 let (ascent, descent) = if let Some(os2) = &os2 {
95 (os2.s_typo_ascender(), os2.s_typo_descender())
96 } else if let Some(hhea) = &hhea {
97 (hhea.ascender().to_i16(), hhea.descender().to_i16())
98 } else {
99 (0, 0) };
101 let num_h_metrics = hhea
102 .map(|hhea| hhea.number_of_h_metrics())
103 .unwrap_or_default();
104 let num_v_metrics = font
105 .vhea()
106 .map(|vhea| vhea.number_of_long_ver_metrics())
107 .unwrap_or_default();
108 let offset = |tag| TableRange::new(font, tag).unwrap_or_default();
109 let loca = offset(Loca::TAG);
110 let glyf = offset(Glyf::TAG);
111 let gvar = offset(Gvar::TAG);
112 let hmtx = offset(Hmtx::TAG);
113 let hvar = offset(Hvar::TAG);
114 let vmtx = offset(Vmtx::TAG);
115 let vvar = offset(Vvar::TAG);
116 let vorg = offset(Vorg::TAG);
117 let mvar = offset(Mvar::TAG);
118 let cmap = offset(Cmap::TAG);
119 let cmap_table: Option<Cmap> = cmap.resolve_table(font);
120 let cmap_subtable = cmap_table
121 .as_ref()
122 .and_then(|cmap| find_best_cmap_subtable(cmap))
123 .map(|(index, platform, encoding, _)| SelectedCmapSubtable {
124 index,
125 is_mac_roman: platform == PlatformId::Macintosh,
126 is_symbol: platform == PlatformId::Windows && encoding == WINDOWS_SYMBOL_ENCODING,
127 });
128 let cmap_vs_subtable = cmap_table.and_then(|cmap| {
129 let data = cmap.offset_data();
130 cmap.encoding_records()
131 .iter()
132 .enumerate()
133 .filter_map(|(index, record)| Some((index, record.subtable(data).ok()?)))
134 .find_map(|(index, subtable)| match subtable {
135 CmapSubtable::Format14(_) => Some(index as u16),
136 _ => None,
137 })
138 });
139 let gdef = offset(Gdef::TAG);
140 let gsub = offset(Gsub::TAG);
141 let gpos = offset(Gpos::TAG);
142 let morx = offset(Morx::TAG);
143 let kerx = offset(Kerx::TAG);
144 let ankr = offset(Ankr::TAG);
145 let kern = offset(Kern::TAG);
146 let feat = offset(Feat::TAG);
147 let trak = offset(Trak::TAG);
148 Self {
149 num_glyphs,
150 units_per_em,
151 loca_long,
152 num_v_metrics,
153 num_h_metrics,
154 ascent,
155 descent,
156 loca,
157 glyf,
158 gvar,
159 hmtx,
160 hvar,
161 vmtx,
162 vvar,
163 vorg,
164 mvar,
165 cmap,
166 cmap_subtable,
167 cmap_vs_subtable,
168 gdef,
169 gsub,
170 gpos,
171 morx,
172 kerx,
173 ankr,
174 kern,
175 feat,
176 trak,
177 }
178 }
179
180 pub(crate) fn from_tables<'a>(font: &impl TableProvider<'a>) -> Self {
181 let num_glyphs = font
182 .maxp()
183 .map(|maxp| maxp.num_glyphs() as u32)
184 .unwrap_or_default();
185 let (units_per_em, loca_long) = font.head().map_or((1000, false), |head| {
186 (head.units_per_em(), head.index_to_loc_format() == 1)
187 });
188 let os2 = font.os2().ok();
189 let hhea = font.hhea().ok();
190 let (ascent, descent) = if let Some(os2) = &os2 {
191 (os2.s_typo_ascender(), os2.s_typo_descender())
192 } else if let Some(hhea) = &hhea {
193 (hhea.ascender().to_i16(), hhea.descender().to_i16())
194 } else {
195 (0, 0) };
197 let num_h_metrics = hhea
198 .map(|hhea| hhea.number_of_h_metrics())
199 .unwrap_or_default();
200 let num_v_metrics = font
201 .vhea()
202 .map(|vhea| vhea.number_of_long_ver_metrics())
203 .unwrap_or_default();
204 Self {
205 num_glyphs,
206 units_per_em,
207 loca_long,
208 num_v_metrics,
209 num_h_metrics,
210 ascent,
211 descent,
212 loca: TableRange::default(),
213 glyf: TableRange::default(),
214 gvar: TableRange::default(),
215 hmtx: TableRange::default(),
216 hvar: TableRange::default(),
217 vmtx: TableRange::default(),
218 vvar: TableRange::default(),
219 vorg: TableRange::default(),
220 mvar: TableRange::default(),
221 cmap: TableRange::default(),
222 cmap_subtable: None,
223 cmap_vs_subtable: None,
224 gdef: TableRange::default(),
225 gsub: TableRange::default(),
226 gpos: TableRange::default(),
227 morx: TableRange::default(),
228 kerx: TableRange::default(),
229 ankr: TableRange::default(),
230 kern: TableRange::default(),
231 feat: TableRange::default(),
232 trak: TableRange::default(),
233 }
234 }
235}
236
237#[derive(Copy, Clone, Default, Debug)]
238pub struct TableRange(u32, u32);
239
240impl TableRange {
241 fn new(font: &FontRef, tag: Tag) -> Option<Self> {
242 let records = font.table_directory().table_records();
243 records
244 .binary_search_by_key(&tag, |rec| rec.tag())
245 .ok()
246 .and_then(|ix| records.get(ix))
247 .map(|rec| Self(rec.offset(), rec.length()))
248 }
249
250 pub fn resolve(self) -> Option<Range<usize>> {
251 let start = self.0 as usize;
252 (start != 0).then_some(start..start.wrapping_add(self.1 as usize))
253 }
254
255 pub fn resolve_data<'a>(self, font: &FontRef<'a>) -> Option<FontData<'a>> {
256 font.data().slice(self.resolve()?)
257 }
258
259 pub fn resolve_table<'a, T: FontRead<'a>>(self, font: &FontRef<'a>) -> Option<T> {
260 T::read(self.resolve_data(font)?).ok()
261 }
262
263 pub fn len(self) -> u32 {
264 self.1
265 }
266}
267
268fn find_best_cmap_subtable<'a>(
269 cmap: &Cmap<'a>,
270) -> Option<(u16, PlatformId, u16, CmapSubtable<'a>)> {
271 find_cmap_subtable(cmap, PlatformId::Windows, WINDOWS_SYMBOL_ENCODING)
275 .or_else(|| find_cmap_subtable(cmap, PlatformId::Windows, WINDOWS_UNICODE_FULL_ENCODING))
277 .or_else(|| find_cmap_subtable(cmap, PlatformId::Unicode, UNICODE_FULL_ENCODING))
278 .or_else(|| find_cmap_subtable(cmap, PlatformId::Unicode, UNICODE_2_0_FULL_ENCODING))
279 .or_else(|| find_cmap_subtable(cmap, PlatformId::Windows, WINDOWS_UNICODE_BMP_ENCODING))
281 .or_else(|| find_cmap_subtable(cmap, PlatformId::Unicode, UNICODE_2_0_BMP_ENCODING))
282 .or_else(|| find_cmap_subtable(cmap, PlatformId::Unicode, UNICODE_ISO_ENCODING))
283 .or_else(|| find_cmap_subtable(cmap, PlatformId::Unicode, UNICODE_1_1_ENCODING))
284 .or_else(|| find_cmap_subtable(cmap, PlatformId::Unicode, UNICODE_1_0_ENCODING))
285 .or_else(|| find_cmap_subtable(cmap, PlatformId::Macintosh, 0))
287}
288
289fn find_cmap_subtable<'a>(
290 cmap: &Cmap<'a>,
291 platform_id: PlatformId,
292 encoding_id: u16,
293) -> Option<(u16, PlatformId, u16, CmapSubtable<'a>)> {
294 let offset_data = cmap.offset_data();
295 for (index, record) in cmap.encoding_records().iter().enumerate() {
296 if record.platform_id() != platform_id || record.encoding_id() != encoding_id {
297 continue;
298 }
299 if let Ok(subtable) = record.subtable(offset_data) {
300 match subtable {
301 CmapSubtable::Format0(_)
302 | CmapSubtable::Format4(_)
303 | CmapSubtable::Format6(_)
304 | CmapSubtable::Format10(_)
305 | CmapSubtable::Format12(_)
306 | CmapSubtable::Format13(_) => {
307 return Some((index as u16, platform_id, encoding_id, subtable))
308 }
309 _ => {}
310 }
311 }
312 }
313 None
314}