Skip to main content

harfrust/hb/
tables.rs

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
29// https://docs.microsoft.com/en-us/typography/opentype/spec/cmap#windows-platform-platform-id--3
30const WINDOWS_SYMBOL_ENCODING: u16 = 0;
31const WINDOWS_UNICODE_BMP_ENCODING: u16 = 1;
32const WINDOWS_UNICODE_FULL_ENCODING: u16 = 10;
33
34// https://docs.microsoft.com/en-us/typography/opentype/spec/name#platform-specific-encoding-and-language-ids-unicode-platform-platform-id--0
35const 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
41//const UNICODE_VARIATION_ENCODING: u16 = 5;
42const 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) // TODO
100        };
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) // TODO
196        };
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    // Symbol subtable.
272    // Prefer symbol if available.
273    // https://github.com/harfbuzz/harfbuzz/issues/1918
274    find_cmap_subtable(cmap, PlatformId::Windows, WINDOWS_SYMBOL_ENCODING)
275        // 32-bit subtables:
276        .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        // 16-bit subtables:
280        .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        // MacRoman subtable:
286        .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}