Skip to main content

text/
ttf.rs

1// ttf -- sfnt loader for runtime fonts: TrueType (glyf), OpenType (CFF and CFF2 outlines via
2// cff.rs), collections (TTC / OTC), colour tables (colr.rs)             [src/text/ttf.s]
3//
4//   face_count(data) -> faces (1 for a plain sfnt, n for a TTC / OTC) / ERR_*
5//   TtFont::open(data, face, ol, serial) -> the font context / ERR_*
6//   glyph(cp), has(cp), adv(g), kern(g1, g2), kern_table(g1, g2), name(which)
7//   bbox(g, ol), path(g, ol, path, gp)       the outline through the outline buffer
8//   sfnt_name(table, id) -> UTF-8            best record: Windows Unicode German (0x0407),
9//        German variants, English (0x0409), other Windows languages, Unicode platform,
10//        Mac Roman English
11//
12// Errors: ERR_TRUNC (too short / truncated directory), ERR_HDR (not an sfnt, bad head /
13// maxp, a broken CFF / CFF2 table), ERR_UNSUPPORTED (no usable outlines), ERR_FAIL (face
14// index out of range), ERR_LIMIT (file >= 2 GB).  A font with both: CFF2 before 'CFF ' (like
15// HarfBuzz and FreeType), 'CFF ' when the CFF2 table does not open.
16//
17// Tables: head, hhea, hmtx, maxp, loca, glyf (simple + composite: offsets, scale, x/y scale,
18// 2x2, point matching, SCALED_COMPONENT_OFFSET; nesting <= 16, <= 4096 components per
19// glyph), cmap (formats 0, 4, 6, 12, 13; (3,10) > (0,x) fmt 12 > (3,1) > (0,x) > (3,0)
20// symbol > (1,0) Mac Roman), OS/2, post, name, kern (version 0 and Apple version 1, format
21// 0), GPOS pair adjustment (lookup type 2 formats 1 and 2, extension type 9; x advance of
22// the first glyph; 'kern' feature of the DFLT and latn default language systems).  Every
23// offset is checked against its table and the file; malformed glyphs come out empty.  Like
24// GDI / FreeType: glyf outlines are shifted by hmtx lsb - xMin; symbol fonts ((3,0) cmap)
25// also map U+0000..U+00FF and the Windows-1252 characters to U+F000 + code page byte; a CFF
26// FontMatrix that is not exactly 1/unitsPerEm scales the outline.
27//
28// Variable fonts (var.rs): fvar / avar / gvar / HVAR / MVAR / GDEF's store are read at open
29// (var); the plain calls give the default instance, the *_var calls an instance at
30// normalized coordinates (registry.rs: VarFont):
31//   prep_var(g, coords, serial, ol)    the outline with gvar deltas (simple glyphs: points with
32//        IUP; composites: the component offsets), shifted by its varied phantom point pp1; a
33//        CFF2 font's charstrings with blend at coords (cff.rs)
34//   adv_var(g, coords)                 hmtx + HVAR; without HVAR: hmtx + the phantom points'
35//        deltas (adv_table: every glyph at once)
36//   kern_var(g1, g2, coords)           GPOS pair values + their VariationIndex deltas (GDEF)
37//   metrics_var(coords, wght, ol)      FONTMETRICS + header with MVAR deltas (hasc / hdsc /
38//        hlgp on hhea and OS/2, hcla, hcld, xhgt, cpht, undo, unds, stro, strs)
39// A CFF2 font's advances: hmtx + HVAR (CFF2 has no phantom points); without fvar its outlines
40// are the default's.
41
42use alloc::boxed::Box;
43use alloc::vec::Vec;
44
45use gfx::path::Path;
46use rt::fx::{floor_i, sqrt};
47
48use crate::cff::Cff;
49use crate::colr;
50use crate::font::*;
51use crate::outline::{Outline, OLT_ON};
52use crate::var::{tag_u32, Var, VAR_BUDGET};
53
54pub const ERR_FAIL: i32 = -1;
55pub const ERR_TRUNC: i32 = -1;
56pub const ERR_HDR: i32 = -2;
57pub const ERR_NOMEM: i32 = -8;
58pub const ERR_UNSUPPORTED: i32 = -9;
59pub const ERR_IO: i32 = -13;
60pub const ERR_LIMIT: i32 = -15;
61
62const TAG_TTCF: u32 = 0x7474_6366;
63const TAG_TRUE: u32 = 0x7472_7565;
64const TAG_OTTO: u32 = 0x4F54_544F;
65const TAG_V1: u32 = 0x0001_0000;
66const TAG_DFLT: u32 = 0x4446_4C54;
67const TAG_LATN: u32 = 0x6C61_746E;
68const TAG_KERNF: u32 = 0x6B65_726E;
69pub const FILE_MAX: u64 = 0x7FFF_FFF0;
70const GLYF_DEPTH: u32 = 16;
71const GLYF_BUDGET: i32 = 4096;
72const GPOS_LK_MAX: usize = 64;
73const KERN_SUB_MAX: usize = 8;
74const GPOS_SUB_MAX: usize = 96;
75pub const NAME_CAP: usize = 256;
76
77// table indices (TT_*)
78pub const TT_HEAD: usize = 0;
79pub const TT_HHEA: usize = 1;
80pub const TT_HMTX: usize = 2;
81pub const TT_MAXP: usize = 3;
82pub const TT_LOCA: usize = 4;
83pub const TT_GLYF: usize = 5;
84pub const TT_CMAP: usize = 6;
85pub const TT_OS2: usize = 7;
86pub const TT_POST: usize = 8;
87pub const TT_NAME: usize = 9;
88pub const TT_KERN: usize = 10;
89pub const TT_GPOS: usize = 11;
90pub const TT_CFF: usize = 12;
91pub const TT_COLR: usize = 13;
92pub const TT_CPAL: usize = 14;
93pub const TT_CBDT: usize = 15;
94pub const TT_CBLC: usize = 16;
95pub const TT_FVAR: usize = 17;
96pub const TT_CFF2: usize = 18;
97pub const TT_AVAR: usize = 20;
98pub const TT_GVAR: usize = 21;
99pub const TT_HVAR: usize = 22;
100pub const TT_MVAR: usize = 23;
101pub const TT_GDEF: usize = 24;
102pub const TT_COUNT: usize = 25;
103
104pub const TFK_TTF: u32 = 1;
105pub const TFK_CFF: u32 = 2;
106
107pub const TFF_LOCA_LONG: u32 = 2;
108pub const TFF_SYMBOL: u32 = 4;
109pub const TFF_COLR: u32 = 8;
110pub const TFF_CBDT: u32 = 16;
111pub const TFF_MACCMAP: u32 = 32;
112
113// table tags in TT_* order (big-endian values)
114static TT_TAGS: [u32; TT_COUNT] = [
115    0x6865_6164, 0x6868_6561, 0x686D_7478, 0x6D61_7870, 0x6C6F_6361, 0x676C_7966, 0x636D_6170,
116    0x4F53_2F32, 0x706F_7374, 0x6E61_6D65, 0x6B65_726E, 0x4750_4F53, 0x4346_4620, 0x434F_4C52,
117    0x4350_414C, 0x4342_4454, 0x4342_4C43, 0x6676_6172, 0x4346_4632, 0x7668_6561, 0x6176_6172,
118    0x6776_6172, 0x4856_4152, 0x4D56_4152, 0x4744_4546,
119];
120static NAME_IDS: [u32; 5] = [FN_FAMILY, FN_STYLE, FN_FULL, FN_TYPO_FAMILY, FN_TYPO_STYLE];
121const F_INV16384: f32 = 0.000_061_035_156_25;
122
123// Windows-1252 0x80..0x9F -> Unicode (0 = undefined)
124static CP1252_HI: [u16; 32] = [
125    0x20AC, 0, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, 0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0,
126    0x017D, 0, 0, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, 0x02DC, 0x2122, 0x0161, 0x203A,
127    0x0153, 0, 0x017E, 0x0178,
128];
129
130/// Mac OS Roman 0x80..0xFF -> Unicode
131pub static MAC_ROMAN: [u16; 128] = [
132    0x00C4, 0x00C5, 0x00C7, 0x00C9, 0x00D1, 0x00D6, 0x00DC, 0x00E1, 0x00E0, 0x00E2, 0x00E4, 0x00E3, 0x00E5,
133    0x00E7, 0x00E9, 0x00E8, 0x00EA, 0x00EB, 0x00ED, 0x00EC, 0x00EE, 0x00EF, 0x00F1, 0x00F3, 0x00F2, 0x00F4,
134    0x00F6, 0x00F5, 0x00FA, 0x00F9, 0x00FB, 0x00FC, 0x2020, 0x00B0, 0x00A2, 0x00A3, 0x00A7, 0x2022, 0x00B6,
135    0x00DF, 0x00AE, 0x00A9, 0x2122, 0x00B4, 0x00A8, 0x2260, 0x00C6, 0x00D8, 0x221E, 0x00B1, 0x2264, 0x2265,
136    0x00A5, 0x00B5, 0x2202, 0x2211, 0x220F, 0x03C0, 0x222B, 0x00AA, 0x00BA, 0x03A9, 0x00E6, 0x00F8, 0x00BF,
137    0x00A1, 0x00AC, 0x221A, 0x0192, 0x2248, 0x2206, 0x00AB, 0x00BB, 0x2026, 0x00A0, 0x00C0, 0x00C3, 0x00D5,
138    0x0152, 0x0153, 0x2013, 0x2014, 0x201C, 0x201D, 0x2018, 0x2019, 0x00F7, 0x25CA, 0x00FF, 0x0178, 0x2044,
139    0x20AC, 0x2039, 0x203A, 0xFB01, 0xFB02, 0x2021, 0x00B7, 0x201A, 0x201E, 0x2030, 0x00C2, 0x00CA, 0x00C1,
140    0x00CB, 0x00C8, 0x00CD, 0x00CE, 0x00CF, 0x00CC, 0x00D3, 0x00D4, 0xF8FF, 0x00D2, 0x00DA, 0x00DB, 0x00D9,
141    0x0131, 0x02C6, 0x02DC, 0x00AF, 0x02D8, 0x02D9, 0x02DA, 0x00B8, 0x02DD, 0x02DB, 0x02C7,
142];
143
144// ------------------------------------------------------------------ readers
145
146pub(crate) fn be16(d: &[u8], o: usize) -> u32 {
147    u16::from_be_bytes([d[o], d[o + 1]]) as u32
148}
149
150pub(crate) fn be32(d: &[u8], o: usize) -> u32 {
151    u32::from_be_bytes([d[o], d[o + 1], d[o + 2], d[o + 3]])
152}
153
154/// bounds-checked big-endian reads of one table (t16 / t32 of the assembly: -1 when the
155/// read would cross the table end; offsets are u32 and wrap like the 32-bit registers)
156#[derive(Clone, Copy)]
157pub struct Tb<'a>(pub &'a [u8]);
158
159impl Tb<'_> {
160    pub fn len(&self) -> u32 {
161        self.0.len() as u32
162    }
163
164    pub fn u8(&self, off: u32) -> i32 {
165        if off as usize >= self.0.len() { -1 } else { self.0[off as usize] as i32 }
166    }
167
168    /// t16 -> u16 or -1
169    pub fn u16(&self, off: u32) -> i32 {
170        if off as u64 + 2 > self.0.len() as u64 { -1 } else { be16(self.0, off as usize) as i32 }
171    }
172
173    /// t32 -> u32 or -1
174    pub fn u32(&self, off: u32) -> i64 {
175        if off as u64 + 4 > self.0.len() as u64 { -1 } else { be32(self.0, off as usize) as i64 }
176    }
177}
178
179fn popcnt8(v: u32) -> u32 {
180    (v & 0xFF).count_ones()
181}
182
183fn utf8_put(out: &mut Vec<u8>, cp: u32) {
184    if cp < 0x80 {
185        out.push(cp as u8);
186    } else if cp < 0x800 {
187        out.push(0xC0 | (cp >> 6) as u8);
188        out.push(0x80 | (cp & 0x3F) as u8);
189    } else if cp < 0x10000 {
190        out.push(0xE0 | (cp >> 12) as u8);
191        out.push(0x80 | ((cp >> 6) & 0x3F) as u8);
192        out.push(0x80 | (cp & 0x3F) as u8);
193    } else {
194        out.push(0xF0 | (cp >> 18) as u8);
195        out.push(0x80 | ((cp >> 12) & 0x3F) as u8);
196        out.push(0x80 | ((cp >> 6) & 0x3F) as u8);
197        out.push(0x80 | (cp & 0x3F) as u8);
198    }
199}
200
201/// font_face_count(data) -> faces / ERR_*
202pub fn face_count(d: &[u8]) -> i32 {
203    if d.len() < 12 {
204        return ERR_TRUNC;
205    }
206    match be32(d, 0) {
207        TAG_TTCF => {
208            let n = be32(d, 8);
209            if n == 0 || n > 65536 {
210                return ERR_HDR;
211            }
212            if n as u64 * 4 + 12 > d.len() as u64 {
213                return ERR_TRUNC;
214            }
215            n as i32
216        }
217        TAG_V1 | TAG_TRUE | TAG_OTTO => 1,
218        _ => ERR_HDR,
219    }
220}
221
222/// sfnt_name(table, id) -> UTF-8 (empty = not found), at most cap - 1 bytes
223pub fn sfnt_name(tb: Tb, id: u32, cap: usize) -> Vec<u8> {
224    let mut out = Vec::new();
225    if cap == 0 || tb.len() < 6 {
226        return out;
227    }
228    let count = tb.u16(2) as u32;
229    let (mut best, mut best_rec) = (0u32, 0u32);
230    let mut i = 0u32;
231    while i < count {
232        let rec = i.wrapping_mul(12).wrapping_add(6);
233        let nid = tb.u16(rec.wrapping_add(6));
234        if nid < 0 {
235            break;
236        }
237        if nid as u32 == id {
238            let pid = tb.u16(rec) as u32;
239            let eid = tb.u16(rec.wrapping_add(2)) as u32;
240            let lang = tb.u16(rec.wrapping_add(4)) as u32;
241            let score = if pid == 3 {
242                if eid == 1 || eid == 10 || eid == 0 {
243                    if lang == 0x0407 {
244                        100
245                    } else if lang & 0x3FF == 0x07 {
246                        95
247                    } else if lang == 0x0409 {
248                        90
249                    } else {
250                        50
251                    }
252                } else {
253                    0
254                }
255            } else if pid == 0 {
256                60
257            } else if pid == 1 && eid == 0 && lang == 0 {
258                40
259            } else {
260                0
261            };
262            if score > best {
263                best = score;
264                best_rec = rec;
265            }
266        }
267        i += 1;
268    }
269    if best == 0 {
270        return out;
271    }
272    let r = best_rec;
273    let mut s = (tb.u16(4) as u32).wrapping_add(tb.u16(r.wrapping_add(10)) as u32);
274    let mut len = tb.u16(r.wrapping_add(8)) as u32;
275    let tl = tb.len();
276    if tl <= s {
277        return out;
278    }
279    if len > tl - s {
280        len = tl - s;
281    }
282    let end = len + s;
283    let mac = tb.u16(r) == 1;
284    let d = tb.0;
285    while s < end {
286        let mut cp;
287        if mac {
288            cp = d[s as usize] as u32;
289            s += 1;
290            if cp >= 0x80 {
291                cp = MAC_ROMAN[(cp - 0x80) as usize] as u32;
292            }
293        } else {
294            if s + 2 > end {
295                break;
296            }
297            cp = be16(d, s as usize);
298            s += 2;
299            let hi = cp & 0xFC00;
300            if hi == 0xD800 {
301                // high surrogate: combine with a following low surrogate
302                if s + 2 > end {
303                    cp = 0xFFFD;
304                } else {
305                    let lo = be16(d, s as usize);
306                    if lo & 0xFC00 != 0xDC00 {
307                        cp = 0xFFFD;
308                    } else {
309                        s += 2;
310                        cp = (((cp & 0x3FF) << 10) | (lo & 0x3FF)) + 0x10000;
311                    }
312                }
313            } else if hi == 0xDC00 {
314                cp = 0xFFFD;
315            }
316        }
317        if cp == 0 {
318            continue;
319        }
320        if cp < 0x20 {
321            cp = 0x20; // control characters -> blank
322        }
323        if cap - out.len() < 5 {
324            break; // keep room for 4 bytes + NUL
325        }
326        utf8_put(&mut out, cp);
327    }
328    out
329}
330
331// glyf decoding state shared by the recursion (g_tt_budget, g_tt_xmin, g_tt_rg, g_tt_hasx);
332// variations: the normalized coordinates (empty: the default instance), the deltas of the
333// metrics glyph's phantom points pp1.x / pp2.x, the gvar work budget
334struct GlyfSt<'a> {
335    budget: i32,
336    xmin: i32,
337    rg: u32,
338    hasx: bool,
339    coords: &'a [i16],
340    pp: (f32, f32),
341    vbudget: u32,
342}
343
344impl GlyfSt<'_> {
345    fn new(coords: &[i16]) -> GlyfSt<'_> {
346        GlyfSt { budget: GLYF_BUDGET, xmin: 0, rg: 0, hasx: false, coords, pp: (0.0, 0.0), vbudget: VAR_BUDGET }
347    }
348}
349
350// MVAR tags
351const MV_HASC: u32 = tag_u32(*b"hasc");
352const MV_HDSC: u32 = tag_u32(*b"hdsc");
353const MV_HLGP: u32 = tag_u32(*b"hlgp");
354const MV_HCLA: u32 = tag_u32(*b"hcla");
355const MV_HCLD: u32 = tag_u32(*b"hcld");
356const MV_XHGT: u32 = tag_u32(*b"xhgt");
357const MV_CPHT: u32 = tag_u32(*b"cpht");
358const MV_UNDO: u32 = tag_u32(*b"undo");
359const MV_UNDS: u32 = tag_u32(*b"unds");
360const MV_STRO: u32 = tag_u32(*b"stro");
361const MV_STRS: u32 = tag_u32(*b"strs");
362
363/// a runtime font context (TrueType or CFF outlines)
364pub struct TtFont {
365    pub face_index: u32,
366    pub data: Vec<u8>,
367    pub hdr: Hdr,
368    pub fm: Metrics,
369    pub serial: u64, // outline buffer owner identity
370    pub kind: u32,
371    pub flags: u32,
372    pub nglyphs: u32,
373    pub upem: u32,
374    nhm: u32,
375    cmap: u32,
376    cmapend: u32,
377    cmapfmt: u32,
378    pub tab: [(u32, u32); TT_COUNT],
379    kernsub: Vec<(u32, u32)>,
380    gpossub: Vec<(u32, u32)>,
381    pub names: [Vec<u8>; 5],
382    cff: Option<Box<Cff>>,
383    pub cpal: u32,
384    pub cpaln: u32,
385    /// the variation tables (var.rs; None: not a variable font)
386    pub(crate) var: Option<Box<Var>>,
387}
388
389impl TtFont {
390    /// the bytes of table idx (empty when missing; clamped to the file at load)
391    pub(crate) fn tb(&self, idx: usize) -> Tb<'_> {
392        let (o, l) = self.tab[idx];
393        Tb(&self.data[o as usize..(o + l) as usize])
394    }
395
396    /// ttf_open(data, face): the context, or ERR_*
397    pub fn open(data: Vec<u8>, face: u32, ol: &mut Outline, serial: u64) -> Result<Box<TtFont>, i32> {
398        let len = data.len() as u64;
399        if len < 12 {
400            return Err(ERR_TRUNC);
401        }
402        if len > FILE_MAX {
403            return Err(ERR_LIMIT);
404        }
405        let mut dir = 0u32;
406        if be32(&data, 0) == TAG_TTCF {
407            let nfonts = be32(&data, 8);
408            if face >= nfonts {
409                return Err(ERR_FAIL);
410            }
411            if face as u64 * 4 + 16 > len {
412                return Err(ERR_TRUNC);
413            }
414            dir = be32(&data, face as usize * 4 + 12);
415        } else if face != 0 {
416            return Err(ERR_FAIL);
417        }
418        if dir as u64 + 12 > len {
419            return Err(ERR_TRUNC);
420        }
421        let v = be32(&data, dir as usize);
422        if v != TAG_V1 && v != TAG_TRUE && v != TAG_OTTO {
423            return Err(ERR_HDR);
424        }
425        let ntab = be16(&data, dir as usize + 4);
426        if ((ntab as u64) << 4) + dir as u64 + 12 > len {
427            return Err(ERR_TRUNC);
428        }
429        let mut f = Box::new(TtFont {
430            face_index: face,
431            data: Vec::new(),
432            hdr: Hdr::default(),
433            fm: Metrics::default(),
434            serial,
435            kind: 0,
436            flags: 0,
437            nglyphs: 0,
438            upem: 0,
439            nhm: 0,
440            cmap: 0,
441            cmapend: 0,
442            cmapfmt: 0,
443            tab: [(0, 0); TT_COUNT],
444            kernsub: Vec::new(),
445            gpossub: Vec::new(),
446            names: Default::default(),
447            cff: None,
448            cpal: 0,
449            cpaln: 0,
450            var: None,
451        });
452        // ---- table records (the first record of a tag wins)
453        for i in 0..ntab {
454            let r = (dir + 12 + i * 16) as usize;
455            let tag = be32(&data, r);
456            let off = be32(&data, r + 8);
457            let tl = be32(&data, r + 12);
458            let Some(k) = TT_TAGS.iter().position(|&t| t == tag) else { continue };
459            if f.tab[k].1 != 0 || off as u64 >= len {
460                continue;
461            }
462            let avail = len - off as u64;
463            let l = (tl as u64).min(avail) as u32;
464            if l == 0 {
465                continue;
466            }
467            f.tab[k] = (off, l);
468        }
469        // ---- head
470        if f.tab[TT_HEAD].1 < 54 {
471            return Err(ERR_HDR);
472        }
473        let h = f.tab[TT_HEAD].0 as usize;
474        let upem = be16(&data, h + 18);
475        if !(16..=16384).contains(&upem) {
476            return Err(ERR_HDR);
477        }
478        f.upem = upem;
479        if be16(&data, h + 50) as u16 != 0 {
480            f.flags |= TFF_LOCA_LONG;
481        }
482        // ---- maxp
483        if f.tab[TT_MAXP].1 < 6 {
484            return Err(ERR_HDR);
485        }
486        let ng = be16(&data, f.tab[TT_MAXP].0 as usize + 4);
487        if ng == 0 {
488            return Err(ERR_HDR);
489        }
490        f.nglyphs = ng;
491        // ---- hhea / hmtx: numberOfHMetrics clamped to hmtx and numGlyphs
492        let mut nhm = 0;
493        if f.tab[TT_HHEA].1 >= 36 {
494            nhm = be16(&data, f.tab[TT_HHEA].0 as usize + 34);
495        }
496        nhm = nhm.min(f.tab[TT_HMTX].1 >> 2).min(f.nglyphs);
497        f.nhm = nhm;
498        f.data = data;
499        // ---- outlines
500        if f.tab[TT_GLYF].1 != 0 && f.tab[TT_LOCA].1 != 0 {
501            f.kind = TFK_TTF;
502        } else if f.tab[TT_CFF2].1 != 0 || f.tab[TT_CFF].1 != 0 {
503            f.kind = TFK_CFF;
504            let mut r = Err(ERR_UNSUPPORTED);
505            if f.tab[TT_CFF2].1 != 0 {
506                r = Cff::open2(f.tb(TT_CFF2), f.upem);
507            }
508            if r.is_err() && f.tab[TT_CFF].1 != 0 {
509                r = Cff::open(f.tb(TT_CFF), f.upem);
510            }
511            f.cff = Some(Box::new(r?));
512        } else if f.tab[TT_CBDT].1 != 0 {
513            f.kind = TFK_TTF; // bitmap-only colour fonts have no outlines at all
514        } else {
515            return Err(ERR_UNSUPPORTED);
516        }
517        f.cmap_setup();
518        f.names_setup();
519        f.kern_setup();
520        f.gpos_setup();
521        colr::open(&mut f);
522        if f.tab[TT_FVAR].1 != 0 {
523            let v = Var::parse(f.tb(TT_FVAR), f.tb(TT_AVAR), f.tb(TT_GVAR), f.tb(TT_HVAR), f.tb(TT_MVAR), f.tb(TT_GDEF), f.tb(TT_NAME));
524            f.var = v.map(Box::new);
525        }
526        let (m, h) = f.metrics_calc(&[], None, ol);
527        f.fm = m;
528        f.hdr = h;
529        Ok(f)
530    }
531
532    // ------------------------------------------------------------------ cmap
533
534    // tt_cmap_score(pid, eid, fmt) -> preference (0 = unusable)
535    fn cmap_score(pid: u32, eid: u32, fmt: u32) -> u32 {
536        match fmt {
537            12 => {
538                if pid == 3 {
539                    match eid {
540                        10 => 10,
541                        1 => 8,
542                        0 => 5,
543                        _ => 0,
544                    }
545                } else if pid == 0 {
546                    9
547                } else {
548                    0
549                }
550            }
551            4 => {
552                if pid == 3 {
553                    match eid {
554                        1 => 8,
555                        0 => 5,
556                        _ => 0,
557                    }
558                } else if pid == 0 {
559                    7
560                } else {
561                    0
562                }
563            }
564            6 | 0 => {
565                let s = if fmt == 6 { 4 } else { 3 };
566                if pid == 0 || (pid == 3 && eid == 1) {
567                    s
568                } else if pid == 1 && eid == 0 {
569                    2
570                } else {
571                    0
572                }
573            }
574            13 => {
575                if pid == 0 || (pid == 3 && eid == 10) {
576                    1
577                } else {
578                    0
579                }
580            }
581            _ => 0,
582        }
583    }
584
585    // choose the cmap subtable  [tt_cmap_setup]
586    fn cmap_setup(&mut self) {
587        let tb = self.tb(TT_CMAP);
588        if tb.len() < 4 {
589            return;
590        }
591        let n = tb.u16(2);
592        if n < 0 {
593            return;
594        }
595        let (mut best, mut boff, mut bfmt, mut bpid, mut beid) = (0u32, 0u32, 0u32, 0u32, 0u32);
596        for i in 0..n as u32 {
597            let pid = tb.u16(i * 8 + 4);
598            if pid < 0 {
599                break;
600            }
601            let eid = tb.u16(i * 8 + 6) as u32;
602            let off = tb.u32(i * 8 + 8);
603            if off < 0 {
604                break;
605            }
606            let off = off as u32;
607            let fmt = tb.u16(off);
608            if fmt < 0 {
609                continue;
610            }
611            let s = Self::cmap_score(pid as u32, eid, fmt as u32);
612            if s > best {
613                best = s;
614                boff = off;
615                bfmt = fmt as u32;
616                bpid = pid as u32;
617                beid = eid;
618            }
619        }
620        if best == 0 {
621            return;
622        }
623        // subtable length (u16 at +2 for the 16-bit formats, u32 at +4 otherwise)
624        let l: i64 = if bfmt < 8 { tb.u16(boff.wrapping_add(2)) as i64 } else { tb.u32(boff.wrapping_add(4)) };
625        if l < 0 {
626            return;
627        }
628        let left = tb.len().wrapping_sub(boff); // bytes left in the table
629        let mut l = (l as u64).min(left as u64) as u32;
630        if bfmt == 4 {
631            l = left; // the 16-bit length of a big format 4 table can be wrong
632        }
633        let start = self.tab[TT_CMAP].0.wrapping_add(boff);
634        self.cmap = start;
635        self.cmapend = l.wrapping_add(start);
636        self.cmapfmt = bfmt;
637        if bpid == 1 {
638            self.flags |= TFF_MACCMAP;
639        }
640        if bpid == 3 && beid == 0 {
641            self.flags |= TFF_SYMBOL;
642        }
643    }
644
645    // glyph through the chosen subtable (0 = not mapped)  [tt_cmap_raw]
646    fn cmap_raw(&self, cp: u32) -> u32 {
647        if self.cmap == 0 {
648            return 0;
649        }
650        let tb = Tb(&self.data[self.cmap as usize..self.cmapend as usize]);
651        let mut cp = cp;
652        // Mac Roman subtables: Unicode -> Mac Roman byte
653        if self.flags & TFF_MACCMAP != 0 && cp >= 0x80 {
654            match MAC_ROMAN.iter().position(|&u| u as u32 == cp) {
655                Some(i) => cp = i as u32 + 0x80,
656                None => return 0,
657            }
658        }
659        let g: u32 = match self.cmapfmt {
660            0 => {
661                if cp >= 256 || cp + 6 >= tb.len() {
662                    return 0;
663                }
664                tb.0[(cp + 6) as usize] as u32
665            }
666            6 => {
667                let first = tb.u16(6);
668                if first < 0 || cp < first as u32 {
669                    return 0;
670                }
671                let i = cp - first as u32;
672                if i >= tb.u16(8) as u32 {
673                    return 0;
674                }
675                let g = tb.u16(i.wrapping_mul(2).wrapping_add(10));
676                if g < 0 {
677                    return 0;
678                }
679                g as u32
680            }
681            4 => {
682                if cp > 0xFFFF {
683                    return 0;
684                }
685                let sx2 = tb.u16(6);
686                if sx2 < 0 {
687                    return 0;
688                }
689                let sx2 = sx2 as u32 & !1;
690                if sx2 == 0 {
691                    return 0;
692                }
693                let (mut lo, mut hi) = (0u32, sx2 >> 1);
694                while lo < hi {
695                    let mid = (lo + hi) >> 1;
696                    let end = tb.u16(mid * 2 + 14);
697                    if end < 0 {
698                        return 0;
699                    }
700                    if end as u32 >= cp {
701                        hi = mid;
702                    } else {
703                        lo = mid + 1;
704                    }
705                }
706                if lo >= sx2 >> 1 {
707                    return 0;
708                }
709                let start = tb.u16(sx2 + lo * 2 + 16);
710                if start < 0 || start as u32 > cp {
711                    return 0;
712                }
713                let delta = tb.u16(sx2 * 2 + 16 + lo * 2);
714                if delta < 0 {
715                    return 0;
716                }
717                let ro_pos = sx2 * 3 + 16 + lo * 2;
718                let ro = tb.u16(ro_pos);
719                if ro < 0 {
720                    return 0;
721                }
722                if ro == 0 {
723                    cp.wrapping_add(delta as u32) & 0xFFFF
724                } else {
725                    let p = (ro as u32).wrapping_add(ro_pos).wrapping_add((cp - start as u32).wrapping_mul(2));
726                    let g = tb.u16(p);
727                    if g <= 0 {
728                        return 0;
729                    }
730                    (g as u32).wrapping_add(delta as u32) & 0xFFFF
731                }
732            }
733            12 | 13 => {
734                let n = tb.u32(12);
735                if n < 0 || tb.len() < 16 {
736                    return 0;
737                }
738                let n = (n as u32).min((tb.len() - 16) / 12);
739                let (mut lo, mut hi) = (0u32, n);
740                let mut found = None;
741                while lo < hi {
742                    let mid = (lo + hi) >> 1;
743                    let rec = mid * 12 + 16;
744                    let start = tb.u32(rec) as u32;
745                    if cp < start {
746                        hi = mid;
747                        continue;
748                    }
749                    let end = tb.u32(rec + 4) as u32;
750                    if cp > end {
751                        lo = mid + 1;
752                        continue;
753                    }
754                    let mut g = tb.u32(rec + 8) as u32;
755                    if self.cmapfmt != 13 {
756                        g = g.wrapping_add(cp - start);
757                    }
758                    found = Some(g);
759                    break;
760                }
761                match found {
762                    Some(g) if g <= 0xFFFF => g,
763                    _ => return 0,
764                }
765            }
766            _ => return 0,
767        };
768        if g < self.nglyphs { g } else { 0 }
769    }
770
771    /// glyph for cp (0 = .notdef).  Symbol fonts ((3,0) cmap) map like GDI: U+0000..U+00FF
772    /// and the Windows-1252 characters to U+F000 + their code page byte  [ttf_glyph]
773    pub fn glyph(&self, cp: u32) -> u32 {
774        let g = self.cmap_raw(cp);
775        if g != 0 || self.flags & TFF_SYMBOL == 0 {
776            return g;
777        }
778        let b = if cp <= 0xFF {
779            cp
780        } else {
781            match CP1252_HI.iter().position(|&u| u as u32 == cp) {
782                Some(i) => i as u32 + 0x80,
783                None => return 0,
784            }
785        };
786        self.cmap_raw(b + 0xF000)
787    }
788
789    pub fn has(&self, cp: u32) -> bool {
790        self.glyph(cp) != 0
791    }
792
793    /// advance (hmtx; the last long metric repeats)  [ttf_adv]
794    pub fn adv(&self, g: u32) -> i32 {
795        if self.nhm == 0 {
796            return 0;
797        }
798        let i = g.min(self.nhm - 1);
799        be16(&self.data, (self.tab[TT_HMTX].0 + i * 4) as usize) as i32
800    }
801
802    // ------------------------------------------------------------------ kerning
803
804    // collect horizontal format 0 subtables of the 'kern' table  [tt_kern_setup]
805    fn kern_setup(&mut self) {
806        let tb = self.tb(TT_KERN);
807        if tb.len() < 4 {
808            return;
809        }
810        let kbase = self.tab[TT_KERN].0;
811        let mut subs = Vec::new();
812        // ks_add(pairs_off, npairs): npairs clamped to the table
813        let add = |subs: &mut Vec<(u32, u32)>, off: u32, np: u32| {
814            if subs.len() >= KERN_SUB_MAX || tb.len() <= off {
815                return;
816            }
817            let fit = (tb.len() - off) / 6;
818            let np = np.min(fit);
819            if np == 0 {
820                return;
821            }
822            subs.push((off.wrapping_add(kbase), np));
823        };
824        if tb.u32(0) == 0x0001_0000 {
825            // Apple version 1: u32 nTables, subtables {u32 length, u16 coverage, u16 tuple}
826            let n = tb.u32(4);
827            if n < 0 {
828                self.kernsub = subs;
829                return;
830            }
831            let mut n = (n as u32).min(64);
832            let mut off = 8u32;
833            while n > 0 {
834                n -= 1;
835                let l = tb.u32(off);
836                if l < 0 {
837                    break;
838                }
839                let cov = tb.u16(off.wrapping_add(4));
840                if cov < 0 {
841                    break;
842                }
843                if cov & 0xE000 == 0 && cov & 0xFF == 0 {
844                    let np = tb.u16(off.wrapping_add(8));
845                    if np < 0 {
846                        break;
847                    }
848                    add(&mut subs, off.wrapping_add(16), np as u32);
849                }
850                let l = l as u32;
851                if l < 8 {
852                    break;
853                }
854                let (o2, carry) = off.overflowing_add(l);
855                if carry {
856                    break;
857                }
858                off = o2;
859            }
860        } else if tb.u16(0) == 0 {
861            // Microsoft version 0: u16 nTables, subtables {u16 version, length, coverage}
862            let mut n = tb.u16(2) as u32;
863            let mut off = 4u32;
864            while n > 0 {
865                n -= 1;
866                let l = tb.u16(off.wrapping_add(2));
867                if l < 0 {
868                    break;
869                }
870                let cov = tb.u16(off.wrapping_add(4));
871                if cov < 0 {
872                    break;
873                }
874                if cov >> 8 == 0 && cov & 7 == 1 {
875                    let np = tb.u16(off.wrapping_add(6));
876                    if np < 0 {
877                        break;
878                    }
879                    add(&mut subs, off.wrapping_add(14), np as u32);
880                    // length is only 16 bits: recompute it from the pair count
881                    let np = tb.u16(off.wrapping_add(6)) as u32;
882                    off = off.wrapping_add(np.wrapping_mul(6)).wrapping_add(14);
883                    continue;
884                }
885                if (l as u32) < 6 {
886                    break;
887                }
888                off = off.wrapping_add(l as u32);
889            }
890        }
891        self.kernsub = subs;
892    }
893
894    /// sum of the 'kern' format 0 values  [tt_kern_table]
895    pub fn kern_table(&self, g1: u32, g2: u32) -> i32 {
896        let key = (g1 << 16) | g2;
897        let mut sum = 0i32;
898        let d = &self.data;
899        for &(off, np) in &self.kernsub {
900            let (mut lo, mut hi) = (0u32, np);
901            while lo < hi {
902                let mid = (lo + hi) >> 1;
903                let o = off as usize + mid as usize * 6;
904                let k = be32(d, o);
905                if key < k {
906                    hi = mid;
907                } else if key > k {
908                    lo = mid + 1;
909                } else {
910                    sum = sum.wrapping_add(be16(d, o + 4) as u16 as i16 as i32);
911                    break;
912                }
913            }
914        }
915        sum
916    }
917
918    /// GPOS 'kern' when present, else 'kern'  [ttf_kern]
919    pub fn kern(&self, g1: u32, g2: u32) -> i32 {
920        if self.gpossub.is_empty() { self.kern_table(g1, g2) } else { self.kern_gpos(g1, g2) }
921    }
922
923    // ------------------------------------------------------------------ GPOS
924
925    // collect the pair adjustment subtables of the 'kern' feature  [tt_gpos_setup]
926    fn gpos_setup(&mut self) {
927        let tb = self.tb(TT_GPOS);
928        if tb.len() < 10 || tb.u16(0) != 1 {
929            return;
930        }
931        let sl = tb.u16(4) as u32;
932        let fl = tb.u16(6) as u32;
933        let ll = tb.u16(8) as u32;
934        let mut lks: Vec<u16> = Vec::new();
935        // scripts
936        let ns = tb.u16(sl);
937        if ns > 0 {
938            let mut found = false;
939            for i in 0..ns as u32 {
940                let rec = i.wrapping_mul(6).wrapping_add(sl).wrapping_add(2);
941                let tag = tb.u32(rec) as u32;
942                if tag == TAG_DFLT || tag == TAG_LATN {
943                    let o = tb.u16(rec.wrapping_add(4));
944                    if o < 0 {
945                        continue;
946                    }
947                    gs_langsys(tb, (o as u32).wrapping_add(sl), fl, &mut lks);
948                    found = true;
949                }
950            }
951            if !found {
952                // neither DFLT nor latn: the first script
953                let o = tb.u16(sl.wrapping_add(6));
954                if o >= 0 {
955                    gs_langsys(tb, (o as u32).wrapping_add(sl), fl, &mut lks);
956                }
957            }
958        }
959        lks.sort_unstable();
960        // subtables of every lookup (type 2, or type 9 wrapping type 2)
961        let lcount = tb.u16(ll);
962        if lcount <= 0 {
963            return;
964        }
965        let mut subs = Vec::new();
966        'lk: for (ord, &li) in lks.iter().enumerate() {
967            if li as i32 >= lcount {
968                continue;
969            }
970            let lo = tb.u16(ll.wrapping_add(li as u32 * 2).wrapping_add(2));
971            if lo < 0 {
972                continue;
973            }
974            let lt = (lo as u32).wrapping_add(ll);
975            let ty = tb.u16(lt);
976            if ty != 2 && ty != 9 {
977                continue;
978            }
979            let nsub = tb.u16(lt.wrapping_add(4));
980            if nsub <= 0 {
981                continue;
982            }
983            for s in 0..nsub as u32 {
984                let so = tb.u16(lt.wrapping_add(s * 2).wrapping_add(6));
985                if so < 0 {
986                    continue 'lk;
987                }
988                let mut sub = (so as u32).wrapping_add(lt);
989                if ty == 9 {
990                    // extension: u16 format 1, u16 type, u32 offset
991                    if tb.u16(sub) != 1 || tb.u16(sub.wrapping_add(2)) != 2 {
992                        continue;
993                    }
994                    let off = tb.u32(sub.wrapping_add(4));
995                    if off < 0 {
996                        continue;
997                    }
998                    let s64 = sub as u64 + off as u64;
999                    if s64 >= tb.len() as u64 {
1000                        continue;
1001                    }
1002                    sub = s64 as u32;
1003                }
1004                if subs.len() >= GPOS_SUB_MAX {
1005                    break 'lk;
1006                }
1007                subs.push((sub, ord as u32));
1008            }
1009        }
1010        self.gpossub = subs;
1011    }
1012
1013    // sum over the 'kern' lookups (first applicable subtable of each lookup)  [tt_kern_gpos]
1014    fn kern_gpos(&self, g1: u32, g2: u32) -> i32 {
1015        let tb = self.tb(TT_GPOS);
1016        let mut sum = 0i32;
1017        let mut done = u32::MAX;
1018        for &(sub, ord) in &self.gpossub {
1019            if ord == done {
1020                continue; // this lookup already applied
1021            }
1022            if let Some(v) = gp_pair(tb, sub, g1, g2) {
1023                sum = sum.wrapping_add(v);
1024                done = ord;
1025            }
1026        }
1027        sum
1028    }
1029
1030    // ------------------------------------------------------------------ names
1031
1032    // family, style, full, typographic family / style  [tt_names_setup]
1033    fn names_setup(&mut self) {
1034        let tb = self.tb(TT_NAME);
1035        let mut names: [Vec<u8>; 5] = Default::default();
1036        for (i, &id) in NAME_IDS.iter().enumerate() {
1037            names[i] = sfnt_name(tb, id, NAME_CAP);
1038        }
1039        // typographic names default to the legacy ones
1040        if names[3].is_empty() {
1041            names[3] = names[0].clone();
1042        }
1043        if names[4].is_empty() {
1044            names[4] = names[1].clone();
1045        }
1046        self.names = names;
1047    }
1048
1049    /// font_name of this font ("" for an unknown id)  [ttf_name]
1050    pub fn name(&self, which: u32) -> &[u8] {
1051        match which {
1052            FN_FAMILY => &self.names[0],
1053            FN_STYLE => &self.names[1],
1054            FN_FULL => &self.names[2],
1055            FN_TYPO_FAMILY => &self.names[3],
1056            FN_TYPO_STYLE => &self.names[4],
1057            _ => b"",
1058        }
1059    }
1060
1061    // ------------------------------------------------------------------ metrics
1062
1063    // FONTMETRICS and the SGF1-style header at normalized coordinates (empty: the default
1064    // instance; MVAR deltas on the values read from the tables, glyph-derived heights from the
1065    // varied outlines); wght: the instance's weight axis value  [tt_metrics_setup]
1066    fn metrics_calc(&self, coords: &[i16], wght: Option<f32>, ol: &mut Outline) -> (Metrics, Hdr) {
1067        let mac_style = self.tb(TT_HEAD).u16(44) as u32;
1068        // a rounded MVAR delta (0 at the default instance)
1069        let mv = |tag: u32| -> i32 {
1070            match (&self.var, coords.is_empty()) {
1071                (Some(v), false) => floor_i(v.mvar_delta(self.tb(TT_MVAR), tag, coords) + 0.5),
1072                _ => 0,
1073            }
1074        };
1075        let mut m = Metrics { upem: self.upem as i32, nglyphs: self.nglyphs as i32, ..Default::default() };
1076        // hhea (defaults 0.8 / -0.2 em)
1077        m.asc = (self.upem * 4 / 5) as i32;
1078        m.desc = m.asc - self.upem as i32;
1079        let t = self.tb(TT_HHEA);
1080        if t.len() >= 36 {
1081            m.asc = (t.u16(4) as i16 as i32).wrapping_add(mv(MV_HASC));
1082            m.desc = (t.u16(6) as i16 as i32).wrapping_add(mv(MV_HDSC));
1083            m.gap = (t.u16(8) as i16 as i32).wrapping_add(mv(MV_HLGP));
1084            m.advmax = t.u16(10);
1085        }
1086        // OS/2 defaults from hhea / head
1087        m.typo_asc = m.asc;
1088        m.win_asc = m.asc;
1089        m.typo_desc = m.desc;
1090        m.win_desc = m.desc.wrapping_neg();
1091        m.typo_gap = m.gap;
1092        m.weight = 400;
1093        m.width = 5;
1094        let mut fl = 0i32;
1095        if mac_style & 1 != 0 {
1096            fl |= FMF_BOLD;
1097            m.weight = 700;
1098        }
1099        if mac_style & 2 != 0 {
1100            fl |= FMF_ITALIC;
1101        }
1102        let t = self.tb(TT_OS2);
1103        if t.len() >= 68 {
1104            fl = 0; // OS/2 decides bold / italic
1105            let mut w = t.u16(4) as u32;
1106            if w < 10 {
1107                w *= 100; // old fonts: 1..9
1108            }
1109            if w > 1000 {
1110                w = 1000;
1111            }
1112            if w == 0 {
1113                w = 400;
1114            }
1115            m.weight = w as i32;
1116            m.width = t.u16(6);
1117            m.st_thick = (t.u16(26) as i16 as i32).wrapping_add(mv(MV_STRS));
1118            m.st_pos = (t.u16(28) as i16 as i32).wrapping_add(mv(MV_STRO));
1119            let s = t.u16(62);
1120            if s & 1 != 0 {
1121                fl |= FMF_ITALIC;
1122            }
1123            if s & 0x20 != 0 {
1124                fl |= FMF_BOLD;
1125            }
1126            if s & 0x200 != 0 {
1127                fl |= FMF_OBLIQUE;
1128            }
1129            if s & 0x80 != 0 {
1130                fl |= FMF_TYPO;
1131            }
1132            if t.len() >= 78 {
1133                m.typo_asc = (t.u16(68) as i16 as i32).wrapping_add(mv(MV_HASC));
1134                m.typo_desc = (t.u16(70) as i16 as i32).wrapping_add(mv(MV_HDSC));
1135                m.typo_gap = (t.u16(72) as i16 as i32).wrapping_add(mv(MV_HLGP));
1136                m.win_asc = t.u16(74).wrapping_add(mv(MV_HCLA));
1137                m.win_desc = t.u16(76).wrapping_add(mv(MV_HCLD));
1138            }
1139            if t.len() >= 90 && t.u16(0) >= 2 {
1140                m.xh = (t.u16(86) as i16 as i32).wrapping_add(mv(MV_XHGT));
1141                m.caph = (t.u16(88) as i16 as i32).wrapping_add(mv(MV_CPHT));
1142            }
1143        }
1144        // a variable font's instance: the weight axis says how heavy it is
1145        if let Some(w) = wght {
1146            m.weight = floor_i(w + 0.5).clamp(1, 1000);
1147        }
1148        let t = self.tb(TT_POST);
1149        if t.len() >= 16 {
1150            m.ul_pos = (t.u16(8) as i16 as i32).wrapping_add(mv(MV_UNDO));
1151            m.ul_thick = (t.u16(10) as i16 as i32).wrapping_add(mv(MV_UNDS));
1152            if t.u32(12) as u32 != 0 {
1153                fl |= FMF_MONO;
1154            }
1155        }
1156        if self.kind == TFK_CFF {
1157            fl |= FMF_CFF;
1158        }
1159        if self.tab[TT_FVAR].1 != 0 {
1160            fl |= FMF_VAR;
1161        }
1162        if self.flags & TFF_SYMBOL != 0 {
1163            fl |= FMF_SYMBOL;
1164        }
1165        if self.flags & TFF_COLR != 0 {
1166            fl |= FMF_COLOR;
1167        }
1168        if self.flags & TFF_CBDT != 0 {
1169            fl |= FMF_COLOR | FMF_BITMAP;
1170        }
1171        if !self.kernsub.is_empty() || !self.gpossub.is_empty() {
1172            fl |= FMF_KERN;
1173        }
1174        m.flags = fl;
1175        // x-height / cap height from 'x' / 'H' when OS/2 has none (not for symbol fonts)
1176        if fl & FMF_SYMBOL == 0 {
1177            if m.xh <= 0 {
1178                m.xh = self.glyph_ymax('x' as u32, coords, ol);
1179            }
1180            if m.caph <= 0 {
1181                m.caph = self.glyph_ymax('H' as u32, coords, ol);
1182            }
1183        }
1184        // underline / strikeout defaults
1185        let mut d = (self.upem / 14) as i32;
1186        if d == 0 {
1187            d = 1;
1188        }
1189        if m.ul_thick <= 0 {
1190            m.ul_thick = d;
1191        }
1192        if m.ul_pos >= 0 {
1193            m.ul_pos = (m.ul_thick + m.ul_thick).wrapping_neg();
1194        }
1195        if m.st_thick <= 0 {
1196            m.st_thick = m.ul_thick;
1197        }
1198        if m.st_pos <= 0 {
1199            let mut c = m.xh;
1200            if c <= 0 {
1201                c = (self.upem >> 1) as i32;
1202            }
1203            m.st_pos = c.wrapping_add(m.st_thick) >> 1;
1204        }
1205        // SGF1-compatible header (i16 fields clamped)
1206        let mut flags = SGH_RUNTIME;
1207        if self.flags & TFF_COLR != 0 {
1208            flags |= SGH_COLR;
1209        }
1210        if self.flags & TFF_CBDT != 0 {
1211            flags |= SGH_CBDT;
1212        }
1213        let c16 = |v: i32| v.clamp(-32768, 32767);
1214        let hdr = Hdr {
1215            flags,
1216            upem: self.upem as i32,
1217            asc: c16(m.asc),
1218            desc: c16(m.desc),
1219            gap: c16(m.gap),
1220            caph: c16(m.caph),
1221            xh: if fl & FMF_SYMBOL != 0 { 0 } else { c16(m.xh) }, // no x-height snapping for symbol fonts
1222            ulpos: c16(m.ul_pos),
1223            ulthick: c16(m.ul_thick),
1224            nglyphs: self.nglyphs & 0xFFFF,
1225            baseid: 0,
1226        };
1227        (m, hdr)
1228    }
1229
1230    /// FONTMETRICS and the header of the instance at coords (MVAR; weight from the wght
1231    /// value)
1232    pub(crate) fn metrics_var(&self, coords: &[i16], wght: Option<f32>, ol: &mut Outline) -> (Metrics, Hdr) {
1233        self.metrics_calc(coords, wght, ol)
1234    }
1235
1236    // yMax of the glyph for cp (0 when missing / empty)  [tt_glyph_ymax]
1237    fn glyph_ymax(&self, cp: u32, coords: &[i16], ol: &mut Outline) -> i32 {
1238        let g = self.glyph(cp);
1239        if g == 0 {
1240            return 0;
1241        }
1242        // (an instance's heights: owner 0, so no font takes the varied outline for its own)
1243        let ok = if coords.is_empty() { self.prep(g, ol) } else { self.prep_var(g, coords, 0, ol) };
1244        if ok { ol.bbox().map_or(0, |b| b[3]) } else { 0 }
1245    }
1246
1247    // ------------------------------------------------------------------ outlines
1248
1249    /// the outline of glyph into the buffer (owner set either way): true = not empty
1250    /// [ttf_prep]
1251    pub fn prep(&self, glyph: u32, ol: &mut Outline) -> bool {
1252        self.prep_at(glyph, &[], self.serial, ol)
1253    }
1254
1255    /// prep at normalized coordinates (gvar, CFF2 blend; CFF outlines stay the default's), the
1256    /// buffer's owner serial (an instance's own)
1257    pub(crate) fn prep_var(&self, glyph: u32, coords: &[i16], serial: u64, ol: &mut Outline) -> bool {
1258        let varies = self.var.as_ref().is_some_and(|v| v.has_gvar()) || self.cff.as_ref().is_some_and(|c| c.varies());
1259        let coords = if varies { coords } else { &[] };
1260        self.prep_at(glyph, coords, serial, ol)
1261    }
1262
1263    fn prep_at(&self, glyph: u32, coords: &[i16], serial: u64, ol: &mut Outline) -> bool {
1264        ol.reset();
1265        let ok = if self.kind == TFK_CFF {
1266            match &self.cff {
1267                Some(c) => c.outline_var(self.tb(if c.is_v2() { TT_CFF2 } else { TT_CFF }), glyph, coords, ol),
1268                None => false,
1269            }
1270        } else {
1271            let mut st = GlyfSt::new(coords);
1272            let ok = self.glyf_decode(glyph, 0, ol, &mut st);
1273            if ok && st.hasx {
1274                // the origin is at xMin - lsb (hmtx): shift like the TrueType rasterizers do
1275                // (an instance: at its varied phantom point pp1, like HarfBuzz)
1276                if let Some(lsb) = self.lsb(st.rg) {
1277                    let d = lsb.wrapping_sub(st.xmin);
1278                    if coords.is_empty() {
1279                        if d != 0 {
1280                            ol.translate(0, d as f32, 0.0);
1281                        }
1282                    } else {
1283                        let df = d as f32 - st.pp.0;
1284                        if df != 0.0 {
1285                            ol.translate(0, df, 0.0);
1286                        }
1287                    }
1288                }
1289            }
1290            ok
1291        };
1292        if !ok || ol.err {
1293            ol.reset();
1294            ol.owner_set(serial, glyph);
1295            return false;
1296        }
1297        ol.owner_set(serial, glyph);
1298        ol.npts() != 0
1299    }
1300
1301    /// advance at normalized coordinates: hmtx + HVAR; without HVAR hmtx + the deltas of the
1302    /// phantom points (see adv_table), else the default's
1303    pub(crate) fn adv_var(&self, g: u32, coords: &[i16]) -> i32 {
1304        let a = self.adv(g);
1305        match &self.var {
1306            Some(v) if !coords.is_empty() => match v.hvar_delta(self.tb(TT_HVAR), g, coords) {
1307                Some(d) => a.wrapping_add(floor_i(d + 0.5)).max(0),
1308                None => a,
1309            },
1310            _ => a,
1311        }
1312    }
1313
1314    /// the advances of every glyph at coords when they come from gvar's phantom points (no
1315    /// HVAR): hmtx + round(pp2.x - pp1.x deltas) of the glyph (USE_MY_METRICS: of that
1316    /// component); empty when HVAR has them or nothing varies
1317    pub(crate) fn adv_table(&self, coords: &[i16], ol: &mut Outline) -> Vec<i32> {
1318        let Some(v) = &self.var else { return Vec::new() };
1319        if v.has_hvar() || !v.has_gvar() || self.kind != TFK_TTF || coords.iter().all(|&c| c == 0) {
1320            return Vec::new();
1321        }
1322        let mut out = Vec::with_capacity(self.nglyphs as usize);
1323        for g in 0..self.nglyphs {
1324            ol.reset();
1325            let mut st = GlyfSt::new(coords);
1326            let a = if self.glyf_decode(g, 0, ol, &mut st) {
1327                self.adv(st.rg).wrapping_add(floor_i(st.pp.1 - st.pp.0 + 0.5)).max(0)
1328            } else {
1329                self.adv(g)
1330            };
1331            out.push(a);
1332        }
1333        ol.reset();
1334        out
1335    }
1336
1337    /// kern at normalized coordinates: GPOS pair values with their VariationIndex deltas
1338    /// (GDEF's store); 'kern' tables do not vary
1339    pub(crate) fn kern_var(&self, g1: u32, g2: u32, coords: &[i16]) -> i32 {
1340        let Some(v) = &self.var else { return self.kern(g1, g2) };
1341        if self.gpossub.is_empty() || coords.is_empty() {
1342            return self.kern(g1, g2);
1343        }
1344        let tb = self.tb(TT_GPOS);
1345        let gdef = self.tb(TT_GDEF);
1346        let mut sum = 0i32;
1347        let mut done = u32::MAX;
1348        for &(sub, ord) in &self.gpossub {
1349            if ord == done {
1350                continue;
1351            }
1352            if let Some((rec, vf1, base)) = gp_value(tb, sub, g1, g2) {
1353                let mut val = 0i32;
1354                if vf1 & 4 != 0 {
1355                    let x = tb.u16(rec.wrapping_add(popcnt8(vf1 & 3) * 2));
1356                    if x >= 0 {
1357                        val = x as u16 as i16 as i32;
1358                    }
1359                }
1360                // xAdvDevice: a VariationIndex {outer, inner, 0x8000}
1361                if vf1 & 0x40 != 0 {
1362                    let dev = tb.u16(rec.wrapping_add(popcnt8(vf1 & 0x3F) * 2));
1363                    if dev > 0 {
1364                        let d = base.wrapping_add(dev as u32);
1365                        if tb.u16(d.wrapping_add(4)) == 0x8000 {
1366                            let (o, i) = (tb.u16(d), tb.u16(d.wrapping_add(2)));
1367                            if o >= 0 && i >= 0 {
1368                                val = val.wrapping_add(floor_i(v.gdef_delta(gdef, o as u32, i as u32, coords) + 0.5));
1369                            }
1370                        }
1371                    }
1372                }
1373                sum = sum.wrapping_add(val);
1374                done = ord;
1375            }
1376        }
1377        sum
1378    }
1379
1380    // left side bearing (hmtx)  [tt_lsb]
1381    fn lsb(&self, g: u32) -> Option<i32> {
1382        let nhm = self.nhm;
1383        let off = if g < nhm { g * 4 + 2 } else { (nhm * 4).wrapping_add((g - nhm).wrapping_mul(2)) };
1384        if off.wrapping_add(2) > self.tab[TT_HMTX].1 {
1385            return None;
1386        }
1387        Some(be16(&self.data, (self.tab[TT_HMTX].0 + off) as usize) as u16 as i16 as i32)
1388    }
1389
1390    /// control box (font units) and the prepared outline  [ttf_box]
1391    pub fn bbox(&self, g: u32, ol: &mut Outline) -> Option<[i32; 4]> {
1392        if self.prep(g, ol) { ol.bbox() } else { None }
1393    }
1394
1395    /// append the outline to the path with the gp transform  [ttf_path]
1396    pub fn path(&self, g: u32, ol: &mut Outline, path: &mut Path, gp: &Gp) {
1397        if !ol.owner_is(self.serial, g) {
1398            self.prep(g, ol);
1399        }
1400        if ol.npts() != 0 {
1401            ol.emit(path, gp, None);
1402        }
1403    }
1404
1405    // the gvar deltas of glyph at st.coords added to out (outline points / components, then
1406    // the 4 phantom points)
1407    fn gvar_pts(&self, glyph: u32, st: &mut GlyfSt, orig: &[(f32, f32)], ends: &[usize], out: &mut [(f32, f32)]) -> bool {
1408        match &self.var {
1409            Some(v) => v.gvar_deltas(self.tb(TT_GVAR), glyph, st.coords, orig, ends, out, &mut st.vbudget),
1410            None => true,
1411        }
1412    }
1413
1414    // the phantom point deltas (pp1.x, pp2.x) of a glyph without outline; None: broken data
1415    fn gvar_phantom(&self, glyph: u32, st: &mut GlyfSt) -> Option<(f32, f32)> {
1416        let mut out = [(0.0f32, 0.0f32); 4];
1417        if !self.gvar_pts(glyph, st, &[], &[], &mut out) {
1418            return None;
1419        }
1420        Some((out[0].0, out[1].0))
1421    }
1422
1423    // glyf_decode(glyph, depth): false = malformed (points appended).  At an instance
1424    // (st.coords) the gvar deltas move the points, the component offsets and the phantom
1425    // points (st.pp of the metrics glyph)
1426    fn glyf_decode(&self, glyph: u32, depth: u32, ol: &mut Outline, st: &mut GlyfSt) -> bool {
1427        let mut my_rg = glyph; // metrics glyph (USE_MY_METRICS may change it)
1428        let mut my_xmin = 0i32;
1429        let mut my_pp = (0.0f32, 0.0f32);
1430        if depth > GLYF_DEPTH {
1431            return false;
1432        }
1433        st.budget -= 1;
1434        if st.budget < 0 {
1435            return false;
1436        }
1437        let ok = |st: &mut GlyfSt, xmin: i32, rg: u32, pp: (f32, f32)| {
1438            st.xmin = xmin;
1439            st.rg = rg;
1440            st.pp = pp;
1441            st.hasx = true;
1442            true
1443        };
1444        let var = !st.coords.is_empty();
1445        if glyph >= self.nglyphs {
1446            return ok(st, my_xmin, my_rg, my_pp);
1447        }
1448        let d = &self.data;
1449        // ---- loca
1450        let (loca, lcl) = self.tab[TT_LOCA];
1451        let (start, end);
1452        if self.flags & TFF_LOCA_LONG != 0 {
1453            if glyph as u64 * 4 + 8 > lcl as u64 {
1454                return ok(st, my_xmin, my_rg, my_pp); // beyond loca: empty
1455            }
1456            let p = loca as usize + glyph as usize * 4;
1457            start = be32(d, p);
1458            end = be32(d, p + 4);
1459        } else {
1460            if glyph as u64 * 2 + 4 > lcl as u64 {
1461                return ok(st, my_xmin, my_rg, my_pp);
1462            }
1463            let p = loca as usize + glyph as usize * 2;
1464            start = be16(d, p) * 2;
1465            end = be16(d, p + 2) * 2;
1466        }
1467        if end <= start || (end <= self.tab[TT_GLYF].1 && end - start < 10) {
1468            // empty glyph (a space varies its advance through the phantom points)
1469            if var {
1470                match self.gvar_phantom(glyph, st) {
1471                    Some(pp) => my_pp = pp,
1472                    None => return false,
1473                }
1474            }
1475            return ok(st, my_xmin, my_rg, my_pp);
1476        }
1477        if end > self.tab[TT_GLYF].1 {
1478            return false;
1479        }
1480        let gl = self.tab[TT_GLYF].0 as usize;
1481        let p = gl + start as usize;
1482        let pend = gl + end as usize;
1483        my_xmin = be16(d, p + 2) as u16 as i16 as i32; // phantom point pp1 = xMin - lsb
1484        let nc = be16(d, p) as u16 as i16 as i32;
1485        if nc == 0 {
1486            if var {
1487                match self.gvar_phantom(glyph, st) {
1488                    Some(pp) => my_pp = pp,
1489                    None => return false,
1490                }
1491            }
1492            return ok(st, my_xmin, my_rg, my_pp);
1493        }
1494        if nc > 0 {
1495            // ---- simple glyph
1496            let nc = nc as usize;
1497            if p + nc * 2 + 12 > pend {
1498                return false;
1499            }
1500            let npts = be16(d, p + nc * 2 + 8) as usize + 1;
1501            let ins = be16(d, p + nc * 2 + 10) as usize;
1502            let mut q = p + nc * 2 + 12 + ins;
1503            if q > pend {
1504                return false;
1505            }
1506            let Some(base) = ol.reserve(npts) else { return false };
1507            let mut i = 0;
1508            while i < npts {
1509                if q >= pend {
1510                    return false;
1511                }
1512                let f = d[q];
1513                q += 1;
1514                ol.tags[base + i] = f;
1515                i += 1;
1516                if f & 8 == 0 {
1517                    continue;
1518                }
1519                if q >= pend {
1520                    return false;
1521                }
1522                let mut r = d[q];
1523                q += 1;
1524                while r != 0 && i < npts {
1525                    ol.tags[base + i] = f;
1526                    i += 1;
1527                    r -= 1;
1528                }
1529            }
1530            let mut x = 0i32;
1531            for i in 0..npts {
1532                let f = ol.tags[base + i];
1533                if f & 2 != 0 {
1534                    if q >= pend {
1535                        return false;
1536                    }
1537                    let mut v = d[q] as i32;
1538                    q += 1;
1539                    if f & 16 == 0 {
1540                        v = -v;
1541                    }
1542                    x = x.wrapping_add(v);
1543                } else if f & 16 == 0 {
1544                    if q + 2 > pend {
1545                        return false;
1546                    }
1547                    x = x.wrapping_add(be16(d, q) as u16 as i16 as i32);
1548                    q += 2;
1549                }
1550                ol.pts[base + i].0 = x as f32;
1551            }
1552            let mut y = 0i32;
1553            for i in 0..npts {
1554                let f = ol.tags[base + i];
1555                if f & 4 != 0 {
1556                    if q >= pend {
1557                        return false;
1558                    }
1559                    let mut v = d[q] as i32;
1560                    q += 1;
1561                    if f & 32 == 0 {
1562                        v = -v;
1563                    }
1564                    y = y.wrapping_add(v);
1565                } else if f & 32 == 0 {
1566                    if q + 2 > pend {
1567                        return false;
1568                    }
1569                    y = y.wrapping_add(be16(d, q) as u16 as i16 as i32);
1570                    q += 2;
1571                }
1572                ol.pts[base + i].1 = y as f32;
1573            }
1574            for i in 0..npts {
1575                ol.tags[base + i] &= OLT_ON;
1576            }
1577            let mut prev = -1i32;
1578            let mut ends: Vec<usize> = Vec::new();
1579            for c in 0..nc {
1580                let e = be16(d, p + 10 + c * 2) as i32;
1581                if e as usize >= npts || e < prev {
1582                    return false;
1583                }
1584                if e == prev {
1585                    continue;
1586                }
1587                prev = e;
1588                if !ol.contour(base as u32 + e as u32) {
1589                    return false;
1590                }
1591                if var {
1592                    ends.push(e as usize);
1593                }
1594            }
1595            if var {
1596                // the points' deltas (IUP from the default coordinates), the phantom points'
1597                let orig: Vec<(f32, f32)> = ol.pts[base..base + npts].to_vec();
1598                let mut dl = alloc::vec![(0.0f32, 0.0f32); npts + 4];
1599                if !self.gvar_pts(glyph, st, &orig, &ends, &mut dl) {
1600                    return false;
1601                }
1602                for (pt, dv) in ol.pts[base..base + npts].iter_mut().zip(&dl) {
1603                    pt.0 += dv.0;
1604                    pt.1 += dv.1;
1605                }
1606                my_pp = (dl[npts].0, dl[npts + 1].0);
1607            }
1608            return ok(st, my_xmin, my_rg, my_pp);
1609        }
1610        // ---- composite glyph
1611        let mut q = p + 10;
1612        let cbase = ol.npts() as u32; // first point of this composite
1613        // an instance: one delta per component (its offset), then the phantom points
1614        let mut cd: Vec<(f32, f32)> = Vec::new();
1615        if var {
1616            let mut r = q;
1617            let mut n = 0usize;
1618            loop {
1619                if r + 4 > pend {
1620                    return false;
1621                }
1622                let fl = be16(d, r);
1623                r += 4 + if fl & 1 != 0 { 4 } else { 2 };
1624                r += if fl & 8 != 0 {
1625                    2
1626                } else if fl & 0x40 != 0 {
1627                    4
1628                } else if fl & 0x80 != 0 {
1629                    8
1630                } else {
1631                    0
1632                };
1633                n += 1;
1634                if fl & 0x20 == 0 {
1635                    break;
1636                }
1637            }
1638            cd = alloc::vec![(0.0f32, 0.0f32); n + 4];
1639            if !self.gvar_pts(glyph, st, &[], &[], &mut cd) {
1640                return false;
1641            }
1642            my_pp = (cd[n].0, cd[n + 1].0);
1643        }
1644        let mut k = 0usize;
1645        loop {
1646            if q + 4 > pend {
1647                return false;
1648            }
1649            let flags = be16(d, q);
1650            let comp = be16(d, q + 2);
1651            q += 4;
1652            let (a1, a2): (i32, i32);
1653            if flags & 1 != 0 {
1654                if q + 4 > pend {
1655                    return false;
1656                }
1657                let (u, v) = (be16(d, q), be16(d, q + 2));
1658                if flags & 2 != 0 {
1659                    a1 = u as u16 as i16 as i32;
1660                    a2 = v as u16 as i16 as i32;
1661                } else {
1662                    a1 = u as i32;
1663                    a2 = v as i32;
1664                }
1665                q += 4;
1666            } else {
1667                if q + 2 > pend {
1668                    return false;
1669                }
1670                if flags & 2 != 0 {
1671                    a1 = d[q] as i8 as i32;
1672                    a2 = d[q + 1] as i8 as i32;
1673                } else {
1674                    a1 = d[q] as i32;
1675                    a2 = d[q + 1] as i32;
1676                }
1677                q += 2;
1678            }
1679            // transform
1680            let f2 = |o: usize| (be16(d, o) as u16 as i16 as i32) as f32 * F_INV16384;
1681            let (mut ma, mut mb, mut mc, mut md) = (0.0f32, 0.0f32, 0.0f32, 0.0f32);
1682            let mut hasmat = true;
1683            if flags & 0x08 != 0 {
1684                if q + 2 > pend {
1685                    return false;
1686                }
1687                ma = f2(q);
1688                md = ma;
1689                q += 2;
1690            } else if flags & 0x40 != 0 {
1691                if q + 4 > pend {
1692                    return false;
1693                }
1694                ma = f2(q);
1695                md = f2(q + 2);
1696                q += 4;
1697            } else if flags & 0x80 != 0 {
1698                if q + 8 > pend {
1699                    return false;
1700                }
1701                ma = f2(q);
1702                mb = f2(q + 2);
1703                mc = f2(q + 4);
1704                md = f2(q + 6);
1705                q += 8;
1706            } else {
1707                hasmat = false;
1708            }
1709            let from = ol.npts();
1710            if !self.glyf_decode(comp, depth + 1, ol, st) {
1711                return false;
1712            }
1713            if flags & 0x200 != 0 {
1714                // USE_MY_METRICS: the component's phantom points
1715                my_xmin = st.xmin;
1716                my_rg = st.rg;
1717                my_pp = st.pp;
1718            }
1719            if hasmat {
1720                ol.transform(from, ma, mb, mc, md);
1721            }
1722            if flags & 2 != 0 {
1723                let mut dx = a1 as f32;
1724                let mut dy = a2 as f32;
1725                if let Some(v) = cd.get(k) {
1726                    // the instance's offset (gvar), scaled below like the stored one
1727                    dx += v.0;
1728                    dy += v.1;
1729                }
1730                if hasmat && flags & 0x800 != 0 && flags & 0x1000 == 0 {
1731                    // SCALED_COMPONENT_OFFSET: scale the offset by the column lengths
1732                    dx = dx * sqrt(ma * ma + mb * mb);
1733                    dy = dy * sqrt(mc * mc + md * md);
1734                }
1735                ol.translate(from, dx, dy);
1736            } else {
1737                // point matching: parent point a1 (from this composite's first point) onto
1738                // child point a2
1739                let pa = (a1 as u32).wrapping_add(cbase);
1740                let pb = (a2 as u32).wrapping_add(from as u32);
1741                if (pa as usize) < from && (pb as usize) < ol.npts() {
1742                    let (xa, ya) = ol.pts[pa as usize];
1743                    let (xb, yb) = ol.pts[pb as usize];
1744                    ol.translate(from, xa - xb, ya - yb);
1745                }
1746            }
1747            k += 1;
1748            if flags & 0x20 == 0 {
1749                break;
1750            }
1751        }
1752        ok(st, my_xmin, my_rg, my_pp)
1753    }
1754}
1755
1756// ------------------------------------------------------------------ GPOS helpers
1757
1758// add the 'kern' lookups of the script's default language system  [gs_langsys]
1759fn gs_langsys(tb: Tb, script: u32, fl: u32, lks: &mut Vec<u16>) {
1760    let o = tb.u16(script);
1761    if o <= 0 {
1762        return;
1763    }
1764    let ls = script.wrapping_add(o as u32);
1765    let req = tb.u16(ls.wrapping_add(2));
1766    if req != 0xFFFF {
1767        if req < 0 {
1768            return;
1769        }
1770        gs_feature(tb, req as u32, fl, lks);
1771    }
1772    let n = tb.u16(ls.wrapping_add(4));
1773    if n <= 0 {
1774        return;
1775    }
1776    for i in 0..n as u32 {
1777        let fi = tb.u16(ls.wrapping_add(i * 2).wrapping_add(6));
1778        if fi < 0 {
1779            return;
1780        }
1781        gs_feature(tb, fi as u32, fl, lks);
1782    }
1783}
1784
1785// add the lookups of feature `index` when its tag is 'kern'  [gs_feature]
1786fn gs_feature(tb: Tb, index: u32, fl: u32, lks: &mut Vec<u16>) {
1787    if index >= tb.u16(fl) as u32 {
1788        return;
1789    }
1790    let rec = index.wrapping_mul(6).wrapping_add(fl).wrapping_add(2);
1791    if tb.u32(rec) as u32 != TAG_KERNF {
1792        return;
1793    }
1794    let o = tb.u16(rec.wrapping_add(4));
1795    if o < 0 {
1796        return;
1797    }
1798    let ft = (o as u32).wrapping_add(fl);
1799    let n = tb.u16(ft.wrapping_add(2));
1800    if n <= 0 {
1801        return;
1802    }
1803    for i in 0..n as u32 {
1804        let li = tb.u16(ft.wrapping_add(i * 2).wrapping_add(4));
1805        if li < 0 {
1806            return;
1807        }
1808        let li = li as u16;
1809        if lks.contains(&li) {
1810            continue;
1811        }
1812        if lks.len() >= GPOS_LK_MAX {
1813            return;
1814        }
1815        lks.push(li);
1816    }
1817}
1818
1819// coverage index of glyph or -1  [gp_coverage]
1820fn gp_coverage(tb: Tb, off: u32, g: u32) -> i32 {
1821    match tb.u16(off) {
1822        1 => {
1823            let n = tb.u16(off.wrapping_add(2));
1824            if n <= 0 {
1825                return -1;
1826            }
1827            let (mut lo, mut hi) = (0u32, n as u32);
1828            while lo < hi {
1829                let mid = (lo + hi) >> 1;
1830                let v = tb.u16(off.wrapping_add(mid * 2).wrapping_add(4));
1831                if v < 0 {
1832                    return -1;
1833                }
1834                let v = v as u32;
1835                if g < v {
1836                    hi = mid;
1837                } else if g > v {
1838                    lo = mid + 1;
1839                } else {
1840                    return mid as i32;
1841                }
1842            }
1843            -1
1844        }
1845        2 => {
1846            let n = tb.u16(off.wrapping_add(2));
1847            if n <= 0 {
1848                return -1;
1849            }
1850            let (mut lo, mut hi) = (0u32, n as u32);
1851            while lo < hi {
1852                let mid = (lo + hi) >> 1;
1853                let r = mid.wrapping_mul(6).wrapping_add(off).wrapping_add(4);
1854                let s = tb.u16(r);
1855                if s < 0 {
1856                    return -1;
1857                }
1858                if g < s as u32 {
1859                    hi = mid;
1860                    continue;
1861                }
1862                let e = tb.u16(r.wrapping_add(2));
1863                if e < 0 {
1864                    return -1;
1865                }
1866                if g > e as u32 {
1867                    lo = mid + 1;
1868                    continue;
1869                }
1870                let si = tb.u16(r.wrapping_add(4));
1871                if si < 0 {
1872                    return -1;
1873                }
1874                return (si as u32).wrapping_add(g).wrapping_sub(s as u32) as i32;
1875            }
1876            -1
1877        }
1878        _ => -1,
1879    }
1880}
1881
1882// class of glyph (0 when not listed)  [gp_class]
1883fn gp_class(tb: Tb, off: u32, g: u32) -> u32 {
1884    match tb.u16(off) {
1885        1 => {
1886            let s = tb.u16(off.wrapping_add(2));
1887            if s < 0 || g < s as u32 {
1888                return 0;
1889            }
1890            let i = g - s as u32;
1891            if i >= tb.u16(off.wrapping_add(4)) as u32 {
1892                return 0;
1893            }
1894            let c = tb.u16(off.wrapping_add(i.wrapping_mul(2)).wrapping_add(6));
1895            if c < 0 { 0 } else { c as u32 }
1896        }
1897        2 => {
1898            let n = tb.u16(off.wrapping_add(2));
1899            if n <= 0 {
1900                return 0;
1901            }
1902            let (mut lo, mut hi) = (0u32, n as u32);
1903            while lo < hi {
1904                let mid = (lo + hi) >> 1;
1905                let r = mid.wrapping_mul(6).wrapping_add(off).wrapping_add(4);
1906                let s = tb.u16(r);
1907                if s < 0 {
1908                    return 0;
1909                }
1910                if g < s as u32 {
1911                    hi = mid;
1912                    continue;
1913                }
1914                let e = tb.u16(r.wrapping_add(2));
1915                if e < 0 {
1916                    return 0;
1917                }
1918                if g > e as u32 {
1919                    lo = mid + 1;
1920                    continue;
1921                }
1922                let c = tb.u16(r.wrapping_add(4));
1923                return if c < 0 { 0 } else { c as u32 };
1924            }
1925            0
1926        }
1927        _ => 0,
1928    }
1929}
1930
1931// x advance adjustment of the first glyph: Some(v) when the subtable applies  [gp_pair]
1932fn gp_pair(tb: Tb, sub: u32, g1: u32, g2: u32) -> Option<i32> {
1933    let (value, vf1, _) = gp_value(tb, sub, g1, g2)?;
1934    if vf1 & 4 == 0 {
1935        return Some(0);
1936    }
1937    let v = tb.u16((popcnt8(vf1 & 3) * 2).wrapping_add(value));
1938    if v < 0 { Some(0) } else { Some(v as u16 as i16 as i32) }
1939}
1940
1941// the first glyph's value record of a pair adjustment subtable that applies: (its offset,
1942// valueFormat1, the table its device offsets start from: the PairSet of format 1, else the
1943// subtable)
1944fn gp_value(tb: Tb, sub: u32, g1: u32, g2: u32) -> Option<(u32, u32, u32)> {
1945    let vf1 = tb.u16(sub.wrapping_add(4));
1946    if vf1 < 0 {
1947        return None;
1948    }
1949    let vf1 = vf1 as u32;
1950    let vf1_size = popcnt8(vf1) * 2;
1951    let vf2 = tb.u16(sub.wrapping_add(6));
1952    if vf2 < 0 {
1953        return None;
1954    }
1955    let rec = popcnt8(vf2 as u32) * 2 + vf1_size; // size of both value records
1956    let co = tb.u16(sub.wrapping_add(2));
1957    if co < 0 {
1958        return None;
1959    }
1960    let cov = gp_coverage(tb, sub.wrapping_add(co as u32), g1);
1961    if cov < 0 {
1962        return None;
1963    }
1964    let mut base = sub;
1965    let value = match tb.u16(sub) {
1966        2 => {
1967            // class pair matrix
1968            let c1o = tb.u16(sub.wrapping_add(8));
1969            if c1o < 0 {
1970                return None;
1971            }
1972            let c1 = gp_class(tb, sub.wrapping_add(c1o as u32), g1);
1973            let c2o = tb.u16(sub.wrapping_add(10));
1974            if c2o < 0 {
1975                return None;
1976            }
1977            let c2 = gp_class(tb, sub.wrapping_add(c2o as u32), g2);
1978            if c1 >= tb.u16(sub.wrapping_add(12)) as u32 {
1979                return None;
1980            }
1981            let n2 = tb.u16(sub.wrapping_add(14));
1982            if n2 <= 0 || c2 >= n2 as u32 {
1983                return None;
1984            }
1985            (n2 as u32).wrapping_mul(c1).wrapping_add(c2).wrapping_mul(rec).wrapping_add(16).wrapping_add(sub)
1986        }
1987        1 => {
1988            // pair sets
1989            let cov = cov as u32;
1990            if cov >= tb.u16(sub.wrapping_add(8)) as u32 {
1991                return None;
1992            }
1993            let pso = tb.u16(sub.wrapping_add(cov.wrapping_mul(2)).wrapping_add(10));
1994            if pso < 0 {
1995                return None;
1996            }
1997            let ps = (pso as u32).wrapping_add(sub);
1998            base = ps;
1999            let n = tb.u16(ps);
2000            if n <= 0 {
2001                return None;
2002            }
2003            let size = rec + 2; // PairValueRecord
2004            let (mut lo, mut hi) = (0u32, n as u32);
2005            let mut found = None;
2006            while lo < hi {
2007                let mid = (lo + hi) >> 1;
2008                let r = ps.wrapping_add(mid.wrapping_mul(size)).wrapping_add(2);
2009                let s = tb.u16(r);
2010                if s < 0 {
2011                    return None;
2012                }
2013                if g2 < s as u32 {
2014                    hi = mid;
2015                } else if g2 > s as u32 {
2016                    lo = mid + 1;
2017                } else {
2018                    found = Some(r.wrapping_add(2));
2019                    break;
2020                }
2021            }
2022            found?
2023        }
2024        _ => return None,
2025    };
2026    Some((value, vf1, base))
2027}