Skip to main content

text/
var.rs

1// var -- OpenType font variations: fvar (axes, named instances), avar (segment maps), gvar
2// (TrueType point deltas), HVAR (advance deltas), MVAR (font-wide metrics) and the item
3// variation store HVAR, MVAR and GDEF share
4//
5//   VarAxis { tag, min, def, max, hidden, name }, VarInstance { name, coords }   what fvar
6//        says, in user units; names from the name table (an axis without one: its tag)
7//   Var::parse(tables) -> Option<Var>  a font's fvar, avar, gvar, HVAR, MVAR, GDEF store
8//   normalize(user) -> coords          F2Dot14 per axis: clamped to the axis, -1 .. 0 .. 1
9//        around the default in 16.16, avar in 16.16, then 2.14 (rounded like HarfBuzz and
10//        FreeType: floor(x + 0.5))
11//   gvar_deltas(gvar, glyph, coords, orig, ends, out, budget)   the deltas of a glyph's points
12//        and of its 4 phantom points, added to out: embedded and shared peak tuples,
13//        intermediate regions, shared and private packed point numbers, packed deltas (zero
14//        runs, bytes, words, longs), IUP for the outline points a tuple leaves out
15//   hvar_delta(hvar, glyph, coords)    the advance delta (HVAR, with or without its mapping)
16//   mvar_delta(mvar, tag, coords)      a font-wide metric's delta (MVAR)
17//   gdef_delta(gdef, outer, inner, coords)   a GPOS VariationIndex delta (GDEF's store)
18//   ivs_scalars(t, store, outer, coords, max, out), ivs_region_count(t, store, outer)   CFF2
19//        (cff.rs): the region scalars of one item variation data for blend, its region count
20//        for a Private DICT's blend
21//
22// Every offset is checked against its table (gvar data against the glyph's own range).
23// Broken gvar data of a glyph: false (the glyph comes out empty, like broken glyf data);
24// broken HVAR / MVAR / store data: no delta.  Bounded work: a glyph whose tuples x points
25// exceed VAR_BUDGET counts as broken; a store row whose regions x axes exceed IVS_WORK has
26// no delta (a CFF2 blend: the glyph is broken).  CFF2 outlines vary through cff.rs's blend at
27// the same coordinates (advances: hmtx + HVAR).  Not supported: avar version 2 beyond its
28// segment maps, VVAR (no vertical layout).
29
30use alloc::string::String;
31use alloc::vec;
32use alloc::vec::Vec;
33
34use rt::fx::floor_i;
35
36use crate::ttf::{sfnt_name, Tb, NAME_CAP};
37
38/// axes and named instances read at most
39pub const AXES_MAX: usize = 64;
40pub const INSTANCES_MAX: usize = 1024;
41/// tuple x point steps of one glyph (composites: of the whole glyph)
42pub(crate) const VAR_BUDGET: u32 = 1 << 22;
43const IVS_WORK: u32 = 1 << 16;
44
45const EMBEDDED_PEAK: u32 = 0x8000;
46const INTERMEDIATE: u32 = 0x4000;
47const PRIVATE_POINTS: u32 = 0x2000;
48const SHARED_POINTS: u32 = 0x8000;
49
50/// an axis of a variable font (fvar) in user units (min <= def <= max)
51#[derive(Clone, Debug, PartialEq)]
52pub struct VarAxis {
53    /// `wght`, `wdth`, `slnt`, `ital`, `opsz` or one of the font's own
54    pub tag: [u8; 4],
55    /// the smallest value
56    pub min: f32,
57    /// the default (the font's own outlines)
58    pub def: f32,
59    /// the largest value
60    pub max: f32,
61    /// the font asks user interfaces not to show it (HIDDEN_AXIS)
62    pub hidden: bool,
63    /// the name table's name (UTF-8; the tag when there is none)
64    pub name: String,
65}
66
67/// a named instance of a variable font (fvar)
68#[derive(Clone, Debug, PartialEq)]
69pub struct VarInstance {
70    /// its subfamily name ("SemiLight Condensed"; "" when the name table has none)
71    pub name: String,
72    /// user coordinates, one per axis
73    pub coords: Vec<f32>,
74}
75
76/// a 4-byte tag as the u32 of the font file
77pub const fn tag_u32(t: [u8; 4]) -> u32 {
78    u32::from_be_bytes(t)
79}
80
81// gvar header values (offsets into the gvar table)
82struct Gvar {
83    shared: u32,
84    nshared: u32,
85    nglyphs: u32,
86    long: bool,
87    data: u32,
88}
89
90/// the variation data of a font (TtFont::var)
91pub(crate) struct Var {
92    pub axes: Vec<VarAxis>,
93    pub instances: Vec<VarInstance>,
94    /// avar segment maps per axis {from, to} (F2Dot14); none for an axis: identity
95    avar: Vec<Vec<(i32, i32)>>,
96    gvar: Option<Gvar>,
97    /// HVAR item variation store offset (0: none) and advance mapping offset (0: implicit)
98    hvar: (u32, u32),
99    /// MVAR records {tag, outer, inner} and its store offset
100    mvar: Vec<(u32, u16, u16)>,
101    mvar_store: u32,
102    /// GDEF's item variation store offset (0: none)
103    gdef_store: u32,
104}
105
106// ------------------------------------------------------------------ readers
107
108fn r16(t: Tb, o: u32) -> Option<u32> {
109    let v = t.u16(o);
110    if v < 0 { None } else { Some(v as u32) }
111}
112
113fn s16(t: Tb, o: u32) -> Option<i32> {
114    r16(t, o).map(|v| v as u16 as i16 as i32)
115}
116
117fn r32(t: Tb, o: u32) -> Option<u32> {
118    let v = t.u32(o);
119    if v < 0 { None } else { Some(v as u32) }
120}
121
122fn fixed(v: u32) -> f32 {
123    v as i32 as f32 * (1.0 / 65536.0)
124}
125
126fn name_of(name: Tb, id: u32) -> String {
127    String::from_utf8_lossy(&sfnt_name(name, id, NAME_CAP)).into_owned()
128}
129
130// a + b * c as u32 (None on overflow)
131fn at(a: u32, b: u32, c: u32) -> Option<u32> {
132    a.checked_add(b.checked_mul(c)?)
133}
134
135impl Var {
136    /// fvar (with its names), avar, gvar, HVAR, MVAR and GDEF's store; None when fvar is
137    /// missing or unusable
138    pub(crate) fn parse(fvar: Tb, avar: Tb, gvar: Tb, hvar: Tb, mvar: Tb, gdef: Tb, name: Tb) -> Option<Var> {
139        // ---- fvar: {u16 major 1, minor, axesArrayOffset, reserved, axisCount, axisSize,
140        // instanceCount, instanceSize}
141        let t = fvar;
142        if r16(t, 0)? != 1 {
143            return None;
144        }
145        let ao = r16(t, 4)?;
146        let n = r16(t, 8)?;
147        let asz = r16(t, 10)?;
148        let ni = r16(t, 12)?;
149        let isz = r16(t, 14)?;
150        if n == 0 || n as usize > AXES_MAX || asz < 20 {
151            return None;
152        }
153        let mut axes = Vec::with_capacity(n as usize);
154        for i in 0..n {
155            let r = at(ao, i, asz)?;
156            let tag = r32(t, r)?;
157            let (mn, df, mx) = (fixed(r32(t, r + 4)?), fixed(r32(t, r + 8)?), fixed(r32(t, r + 12)?));
158            let flags = r16(t, r + 16)?;
159            let mut nm = name_of(name, r16(t, r + 18)?);
160            if nm.is_empty() {
161                nm = String::from_utf8_lossy(&tag.to_be_bytes()).into_owned();
162            }
163            // min and max never cross the default (HarfBuzz's get_coordinates)
164            axes.push(VarAxis { tag: tag.to_be_bytes(), min: mn.min(df), def: df, max: mx.max(df), hidden: flags & 1 != 0, name: nm });
165        }
166        // instances {u16 subfamilyNameID, flags, Fixed coords[axisCount], [u16 psNameID]}
167        let mut instances = Vec::new();
168        if isz >= 4 + 4 * n {
169            let io = at(ao, n, asz)?;
170            for j in 0..ni.min(INSTANCES_MAX as u32) {
171                let Some(r) = at(io, j, isz) else { break };
172                let mut coords = Vec::with_capacity(n as usize);
173                for k in 0..n {
174                    let Some(v) = r32(t, r + 4 + 4 * k) else { break };
175                    coords.push(fixed(v));
176                }
177                if coords.len() != n as usize {
178                    break;
179                }
180                let Some(id) = r16(t, r) else { break };
181                instances.push(VarInstance { name: name_of(name, id), coords });
182            }
183        }
184        let mut v = Var {
185            axes,
186            instances,
187            avar: Vec::new(),
188            gvar: None,
189            hvar: (0, 0),
190            mvar: Vec::new(),
191            mvar_store: 0,
192            gdef_store: 0,
193        };
194        v.avar = Self::parse_avar(avar, n);
195        v.gvar = Self::parse_gvar(gvar, n);
196        // ---- HVAR {u16 major 1, minor, Offset32 store, advanceMap, lsbMap, rsbMap}
197        if r16(hvar, 0) == Some(1) {
198            let store = r32(hvar, 4).unwrap_or(0);
199            if store != 0 && store < hvar.len() {
200                v.hvar = (store, r32(hvar, 8).unwrap_or(0));
201            }
202        }
203        // ---- MVAR {u16 major 1, minor, reserved, valueRecordSize, count, Offset16 store}
204        if r16(mvar, 0) == Some(1) {
205            let rsz = r16(mvar, 6).unwrap_or(0);
206            let cnt = r16(mvar, 8).unwrap_or(0);
207            let store = r16(mvar, 10).unwrap_or(0);
208            if rsz >= 8 && store != 0 {
209                for i in 0..cnt {
210                    let r = 12 + i * rsz;
211                    let (Some(tag), Some(o), Some(inn)) = (r32(mvar, r), r16(mvar, r + 4), r16(mvar, r + 6)) else { break };
212                    v.mvar.push((tag, o as u16, inn as u16));
213                }
214                v.mvar_store = store;
215            }
216        }
217        // ---- GDEF 1.3: Offset32 itemVarStore at 14
218        if r16(gdef, 0) == Some(1) && r16(gdef, 2).is_some_and(|m| m >= 3) {
219            v.gdef_store = r32(gdef, 14).unwrap_or(0);
220        }
221        Some(v)
222    }
223
224    // avar {u16 major, minor, reserved, axisCount, segment maps {u16 n, {F2Dot14 from, to}[n]}}:
225    // the maps of the first min(axisCount, n) axes (avar 2: only these)
226    fn parse_avar(t: Tb, n: u32) -> Vec<Vec<(i32, i32)>> {
227        let mut maps = Vec::new();
228        let (Some(major), Some(cnt)) = (r16(t, 0), r16(t, 6)) else { return maps };
229        if major != 1 && major != 2 {
230            return maps;
231        }
232        let mut o = 8u32;
233        for _ in 0..cnt.min(n) {
234            let Some(m) = r16(t, o) else { return Vec::new() };
235            let mut seg = Vec::with_capacity(m as usize);
236            for k in 0..m {
237                let p = o + 2 + k * 4;
238                let (Some(f), Some(to)) = (s16(t, p), s16(t, p + 2)) else { return Vec::new() };
239                seg.push((f, to));
240            }
241            maps.push(seg);
242            o += 2 + m * 4;
243        }
244        maps
245    }
246
247    // gvar {u16 major 1, minor, axisCount, sharedTupleCount, Offset32 sharedTuples, u16
248    // glyphCount, flags, Offset32 data, offsets[glyphCount + 1]}; unusable: None
249    fn parse_gvar(t: Tb, n: u32) -> Option<Gvar> {
250        if r16(t, 0)? != 1 || r16(t, 4)? != n {
251            return None;
252        }
253        let nshared = r16(t, 6)?;
254        let shared = r32(t, 8)?;
255        let nglyphs = r16(t, 12)?;
256        let long = r16(t, 14)? & 1 != 0;
257        let data = r32(t, 16)?;
258        let osz = if long { 4u64 } else { 2 };
259        if 20 + (nglyphs as u64 + 1) * osz > t.len() as u64 || shared as u64 + nshared as u64 * n as u64 * 2 > t.len() as u64 {
260            return None;
261        }
262        Some(Gvar { shared, nshared, nglyphs, long, data })
263    }
264
265    pub(crate) fn has_gvar(&self) -> bool {
266        self.gvar.is_some()
267    }
268
269    pub(crate) fn has_hvar(&self) -> bool {
270        self.hvar.0 != 0
271    }
272
273    // ------------------------------------------------------------------ coordinates
274
275    /// user values (one per axis; missing: the default) -> normalized F2Dot14 coordinates
276    /// with avar applied
277    pub(crate) fn normalize(&self, user: &[f32]) -> Vec<i16> {
278        let mut c = Vec::with_capacity(self.axes.len());
279        for (i, a) in self.axes.iter().enumerate() {
280            let mut v = user.get(i).copied().unwrap_or(a.def);
281            if v.is_nan() {
282                v = a.def;
283            }
284            v = v.clamp(a.min, a.max);
285            // 16.16 first, avar in 16.16, then 2.14 (HarfBuzz, FreeType)
286            let n = if v == a.def {
287                0
288            } else if v < a.def {
289                floor_i((v - a.def) / (a.def - a.min) * 65536.0 + 0.5)
290            } else {
291                floor_i((v - a.def) / (a.max - a.def) * 65536.0 + 0.5)
292            };
293            let m = match self.avar.get(i) {
294                Some(seg) => avar_map(seg, n),
295                None => n,
296            };
297            c.push(((m.clamp(-65536, 65536) + 2) >> 2) as i16);
298        }
299        c
300    }
301
302    // ------------------------------------------------------------------ gvar
303
304    /// the deltas of glyph's points at coords, added to out (out[..n]: the outline points or
305    /// components, out[n..n + 4]: the phantom points).  orig / ends: the outline points and
306    /// contour ends for IUP (empty for a composite: its untouched components do not move).
307    /// false: broken data or the budget is spent
308    #[allow(clippy::too_many_arguments)]
309    pub(crate) fn gvar_deltas(&self, t: Tb, glyph: u32, coords: &[i16], orig: &[(f32, f32)], ends: &[usize], out: &mut [(f32, f32)], budget: &mut u32) -> bool {
310        let Some(gv) = &self.gvar else { return true };
311        let npts = out.len();
312        if glyph >= gv.nglyphs || coords.iter().all(|&c| c == 0) {
313            return true;
314        }
315        // the glyph's data range
316        let (a, b) = if gv.long {
317            (r32(t, 20 + glyph * 4), r32(t, 24 + glyph * 4))
318        } else {
319            (r16(t, 20 + glyph * 2).map(|v| v * 2), r16(t, 22 + glyph * 2).map(|v| v * 2))
320        };
321        let (Some(a), Some(b)) = (a, b) else { return false };
322        if b <= a {
323            return true; // no variation data
324        }
325        let (Some(a), Some(b)) = (gv.data.checked_add(a), gv.data.checked_add(b)) else { return false };
326        if b > t.len() {
327            return false;
328        }
329        let g = Tb(&t.0[a as usize..b as usize]);
330        self.glyph_deltas(t, gv, g, coords, orig, ends, out, npts, budget).is_some()
331    }
332
333    #[allow(clippy::too_many_arguments)]
334    fn glyph_deltas(&self, t: Tb, gv: &Gvar, g: Tb, coords: &[i16], orig: &[(f32, f32)], ends: &[usize], out: &mut [(f32, f32)], npts: usize, budget: &mut u32) -> Option<()> {
335        let naxes = self.axes.len() as u32;
336        let tvc = r16(g, 0)?;
337        let count = tvc & 0x0FFF;
338        let mut p = r16(g, 2)?; // serialized data
339        let end = g.len();
340        let mut shared: Vec<u32> = Vec::new();
341        if tvc & SHARED_POINTS != 0 {
342            packed_points(g, &mut p, end, &mut shared)?;
343        }
344        let mut h = 4u32; // tuple variation headers
345        let mut peak = vec![0i32; naxes as usize];
346        let mut inter = vec![(0i32, 0i32); naxes as usize];
347        let mut pts: Vec<u32> = Vec::new();
348        let mut dx: Vec<i32> = Vec::new();
349        let mut dy: Vec<i32> = Vec::new();
350        let mut tmp: Vec<(f32, f32)> = Vec::new();
351        let mut touched: Vec<bool> = Vec::new();
352        for _ in 0..count {
353            let size = r16(g, h)?;
354            let idx = r16(g, h + 2)?;
355            h += 4;
356            if idx & EMBEDDED_PEAK != 0 {
357                for (k, v) in peak.iter_mut().enumerate() {
358                    *v = s16(g, h + k as u32 * 2)?;
359                }
360                h += naxes * 2;
361            } else {
362                let si = idx & 0x0FFF;
363                if si >= gv.nshared {
364                    return None;
365                }
366                for (k, v) in peak.iter_mut().enumerate() {
367                    *v = s16(t, gv.shared + (si * naxes + k as u32) * 2)?;
368                }
369            }
370            let has_inter = idx & INTERMEDIATE != 0;
371            if has_inter {
372                for (k, v) in inter.iter_mut().enumerate() {
373                    *v = (s16(g, h + k as u32 * 2)?, s16(g, h + (naxes + k as u32) * 2)?);
374                }
375                h += naxes * 4;
376            }
377            let q0 = p;
378            let qend = p.checked_add(size)?;
379            if qend > end {
380                return None;
381            }
382            p = qend;
383            let scalar = tuple_scalar(coords, &peak, if has_inter { Some(&inter) } else { None });
384            if scalar == 0.0 {
385                continue;
386            }
387            // budget: every tuple that applies costs the glyph's points
388            let cost = (npts as u32).max(1);
389            if *budget < cost {
390                return None;
391            }
392            *budget -= cost;
393            let mut q = q0;
394            let all = if idx & PRIVATE_POINTS != 0 {
395                packed_points(g, &mut q, qend, &mut pts)?;
396                pts.is_empty()
397            } else {
398                pts.clear();
399                pts.extend_from_slice(&shared);
400                pts.is_empty()
401            };
402            let num = if all { npts } else { pts.len() };
403            packed_deltas(g, &mut q, qend, num, &mut dx)?;
404            packed_deltas(g, &mut q, qend, num, &mut dy)?;
405            let s = scalar as f32;
406            if all {
407                for (i, o) in out.iter_mut().enumerate() {
408                    o.0 += dx[i] as f32 * s;
409                    o.1 += dy[i] as f32 * s;
410                }
411                continue;
412            }
413            // explicit points: the tuple's own deltas, IUP for the untouched outline points
414            tmp.clear();
415            tmp.resize(npts, (0.0, 0.0));
416            touched.clear();
417            touched.resize(npts, false);
418            for (k, &pi) in pts.iter().enumerate() {
419                let pi = pi as usize;
420                if pi >= npts {
421                    continue;
422                }
423                tmp[pi].0 += dx[k] as f32 * s;
424                tmp[pi].1 += dy[k] as f32 * s;
425                touched[pi] = true;
426            }
427            if !ends.is_empty() {
428                iup(orig, ends, &touched, &mut tmp);
429            }
430            for (o, d) in out.iter_mut().zip(&tmp) {
431                o.0 += d.0;
432                o.1 += d.1;
433            }
434        }
435        Some(())
436    }
437
438    // ------------------------------------------------------------------ item variation stores
439
440    /// the advance delta of glyph (HVAR); None without HVAR
441    pub(crate) fn hvar_delta(&self, t: Tb, glyph: u32, coords: &[i16]) -> Option<f32> {
442        let (store, map) = self.hvar;
443        if store == 0 {
444            return None;
445        }
446        let (outer, inner) = if map == 0 { (0, glyph) } else { dsim(t, map, glyph).unwrap_or((0, glyph)) };
447        Some(ivs(t, store, outer, inner, coords).unwrap_or(0.0))
448    }
449
450    /// the delta of a font-wide metric (MVAR tag); 0 without one
451    pub(crate) fn mvar_delta(&self, t: Tb, tag: u32, coords: &[i16]) -> f32 {
452        match self.mvar.iter().find(|r| r.0 == tag) {
453            Some(&(_, o, i)) => ivs(t, self.mvar_store, o as u32, i as u32, coords).unwrap_or(0.0),
454            None => 0.0,
455        }
456    }
457
458    /// a delta of GDEF's store (a GPOS VariationIndex)
459    pub(crate) fn gdef_delta(&self, t: Tb, outer: u32, inner: u32, coords: &[i16]) -> f32 {
460        if self.gdef_store == 0 {
461            return 0.0;
462        }
463        ivs(t, self.gdef_store, outer, inner, coords).unwrap_or(0.0)
464    }
465}
466
467// avar segment map of one axis on a 16.16 value (the map's F2Dot14 values x 4; HarfBuzz's
468// SegmentMaps::map, also for broken maps)
469fn avar_map(seg: &[(i32, i32)], v: i32) -> i32 {
470    let fr = |k: usize| seg[k].0 << 2;
471    let to = |k: usize| seg[k].1 << 2;
472    match seg.len() {
473        0 => return v,
474        1 => return v - fr(0) + to(0),
475        _ => {}
476    }
477    if v <= fr(0) {
478        return v - fr(0) + to(0);
479    }
480    let count = seg.len() - 1;
481    let mut i = 1;
482    while i < count && v > fr(i) {
483        i += 1;
484    }
485    if v >= fr(i) {
486        return v - fr(i) + to(i);
487    }
488    if fr(i - 1) == fr(i) {
489        return to(i - 1);
490    }
491    let denom = (fr(i) - fr(i - 1)) as f32;
492    floor_i(to(i - 1) as f32 + ((to(i) - to(i - 1)) as f32 * (v - fr(i - 1)) as f32) / denom + 0.5)
493}
494
495// the scalar of a tuple at coords (HarfBuzz's calculate_scalar)
496fn tuple_scalar(coords: &[i16], peak: &[i32], inter: Option<&Vec<(i32, i32)>>) -> f64 {
497    let mut s = 1.0f64;
498    for (i, &pk) in peak.iter().enumerate() {
499        if pk == 0 {
500            continue;
501        }
502        let v = coords.get(i).copied().unwrap_or(0) as i32;
503        if v == 0 {
504            return 0.0;
505        }
506        if v == pk {
507            continue;
508        }
509        match inter {
510            Some(r) => {
511                let (st, en) = r[i];
512                if st > pk || pk > en || (st < 0 && en > 0) {
513                    continue; // an invalid region: the axis is ignored
514                }
515                if v < st || v > en {
516                    return 0.0;
517                }
518                if v < pk {
519                    if pk != st {
520                        s *= (v - st) as f64 / (pk - st) as f64;
521                    }
522                } else if pk != en {
523                    s *= (en - v) as f64 / (en - pk) as f64;
524                }
525            }
526            None => {
527                if v < pk.min(0) || v > pk.max(0) {
528                    return 0.0;
529                }
530                s *= v as f64 / pk as f64;
531            }
532        }
533    }
534    s
535}
536
537// packed point numbers at *p (< end), moved past them: out empty = every point
538fn packed_points(t: Tb, p: &mut u32, end: u32, out: &mut Vec<u32>) -> Option<()> {
539    out.clear();
540    let rd = |p: &mut u32| -> Option<u32> {
541        if *p >= end {
542            return None;
543        }
544        let v = t.u8(*p);
545        *p += 1;
546        if v < 0 { None } else { Some(v as u32) }
547    };
548    let mut count = rd(p)?;
549    if count & 0x80 != 0 {
550        count = ((count & 0x7F) << 8) | rd(p)?;
551    }
552    let mut n = 0u32;
553    while (out.len() as u32) < count {
554        let ctrl = rd(p)?;
555        let run = (ctrl & 0x7F) + 1;
556        if out.len() as u32 + run > count {
557            return None;
558        }
559        for _ in 0..run {
560            let d = if ctrl & 0x80 != 0 { (rd(p)? << 8) | rd(p)? } else { rd(p)? };
561            n = n.wrapping_add(d);
562            out.push(n);
563        }
564    }
565    Some(())
566}
567
568// n packed deltas at *p (< end) into out
569fn packed_deltas(t: Tb, p: &mut u32, end: u32, n: usize, out: &mut Vec<i32>) -> Option<()> {
570    out.clear();
571    while out.len() < n {
572        if *p >= end {
573            return None;
574        }
575        let ctrl = t.u8(*p);
576        if ctrl < 0 {
577            return None;
578        }
579        *p += 1;
580        let ctrl = ctrl as u32;
581        let run = (ctrl & 0x3F) as usize + 1;
582        if out.len() + run > n {
583            return None;
584        }
585        let size = match ctrl & 0xC0 {
586            0x80 => 0,
587            0x40 => 2,
588            0xC0 => 4,
589            _ => 1,
590        };
591        if *p as u64 + (run * size) as u64 > end as u64 {
592            return None;
593        }
594        for _ in 0..run {
595            let v = match size {
596                0 => 0,
597                1 => t.u8(*p) as u8 as i8 as i32,
598                2 => t.u16(*p) as u16 as i16 as i32,
599                _ => t.u32(*p) as u32 as i32,
600            };
601            *p += size as u32;
602            out.push(v);
603        }
604    }
605    Some(())
606}
607
608// infer the deltas of the untouched points of each contour from the nearest touched ones
609// before and after (IUP): between their coordinates interpolated, outside them the nearer
610// one's delta; equal coordinates with different deltas give 0
611fn iup(orig: &[(f32, f32)], ends: &[usize], touched: &[bool], d: &mut [(f32, f32)]) {
612    let mut start = 0usize;
613    for &e in ends {
614        if e >= orig.len() || e < start {
615            break;
616        }
617        let range = start..e + 1;
618        start = e + 1;
619        let first = match range.clone().find(|&i| touched[i]) {
620            Some(f) => f,
621            None => continue, // nothing touched: no deltas
622        };
623        // walk the touched points cyclically; fill each gap
624        let n = range.end - range.start;
625        let mut cur = first;
626        loop {
627            // the next touched point after cur (cyclic)
628            let mut nx = cur;
629            let mut gap = 0usize;
630            loop {
631                nx = if nx + 1 == range.end { range.start } else { nx + 1 };
632                if touched[nx] {
633                    break;
634                }
635                gap += 1;
636            }
637            if gap > 0 {
638                let mut i = if cur + 1 == range.end { range.start } else { cur + 1 };
639                for _ in 0..gap {
640                    let (x, y) = orig[i];
641                    let px = iup_one(x, orig[cur].0, d[cur].0, orig[nx].0, d[nx].0);
642                    let py = iup_one(y, orig[cur].1, d[cur].1, orig[nx].1, d[nx].1);
643                    d[i] = (px, py);
644                    i = if i + 1 == range.end { range.start } else { i + 1 };
645                }
646            }
647            if nx == first || n == 1 {
648                break;
649            }
650            cur = nx;
651        }
652    }
653}
654
655// one coordinate: c between the reference coordinates (c1, d1) and (c2, d2)
656fn iup_one(c: f32, c1: f32, d1: f32, c2: f32, d2: f32) -> f32 {
657    let (c1, d1, c2, d2) = if c1 <= c2 { (c1, d1, c2, d2) } else { (c2, d2, c1, d1) };
658    if c1 == c2 {
659        return if d1 == d2 { d1 } else { 0.0 };
660    }
661    if c <= c1 {
662        d1
663    } else if c >= c2 {
664        d2
665    } else {
666        d1 + (c - c1) * (d2 - d1) / (c2 - c1)
667    }
668}
669
670// DeltaSetIndexMap entry i -> (outer, inner); the last entry for i beyond the map
671fn dsim(t: Tb, m: u32, i: u32) -> Option<(u32, u32)> {
672    let fmt = t.u8(m);
673    let ef = t.u8(m.checked_add(1)?);
674    if ef < 0 {
675        return None;
676    }
677    let ef = ef as u32;
678    let (count, data) = match fmt {
679        0 => (r16(t, m + 2)?, m + 4),
680        1 => (r32(t, m + 2)?, m + 6),
681        _ => return None,
682    };
683    if count == 0 {
684        return Some((i >> 16, i & 0xFFFF));
685    }
686    let i = i.min(count - 1);
687    let size = ((ef >> 4) & 3) + 1;
688    let bits = (ef & 0xF) + 1;
689    let o = at(data, i, size)?;
690    let mut v = 0u32;
691    for k in 0..size {
692        let b = t.u8(o.checked_add(k)?);
693        if b < 0 {
694            return None;
695        }
696        v = (v << 8) | b as u32;
697    }
698    Some((v >> bits, v & ((1u32 << bits) - 1)))
699}
700
701// one region axis' factor (HarfBuzz's VarRegionAxis::evaluate)
702fn region_factor(v: i32, start: i32, peak: i32, end: i32) -> f32 {
703    if peak == 0 || v == peak {
704        return 1.0;
705    }
706    if v == 0 {
707        return 0.0;
708    }
709    if start > peak || peak > end || (start < 0 && end > 0) {
710        return 1.0;
711    }
712    if v <= start || end <= v {
713        return 0.0;
714    }
715    if v < peak { (v - start) as f32 / (peak - start) as f32 } else { (end - v) as f32 / (end - peak) as f32 }
716}
717
718// item variation data `outer` of the store at s -> (its offset, the region list's offset, its
719// regionIndexCount)
720fn ivs_data(t: Tb, s: u32, outer: u32) -> Option<(u32, u32, u32)> {
721    if s == 0 || r16(t, s)? != 1 {
722        return None;
723    }
724    let rl = s.checked_add(r32(t, s.checked_add(2)?)?)?;
725    if outer >= r16(t, s.checked_add(6)?)? {
726        return None;
727    }
728    let d = s.checked_add(r32(t, at(s.checked_add(8)?, outer, 4)?)?)?;
729    Some((d, rl, r16(t, d.checked_add(4)?)?))
730}
731
732/// CFF2 (a DICT's blend): the number of regions item variation data `outer` of the store at s
733/// lists; None: no such data
734pub(crate) fn ivs_region_count(t: Tb, s: u32, outer: u32) -> Option<u32> {
735    Some(ivs_data(t, s, outer)?.2)
736}
737
738/// CFF2 (a charstring's blend): the scalars at coords of the regions that item variation data
739/// `outer` of the store at s lists, in its order, into out (all 0 at the default instance);
740/// None: a broken store or data, more than max regions, regions x axes beyond IVS_WORK
741pub(crate) fn ivs_scalars(t: Tb, s: u32, outer: u32, coords: &[i16], max: usize, out: &mut Vec<f32>) -> Option<()> {
742    out.clear();
743    let (d, rl, rc) = ivs_data(t, s, outer)?;
744    if rc as usize > max {
745        return None;
746    }
747    let axes = r16(t, rl)?;
748    let nreg = r16(t, rl.checked_add(2)?)?;
749    if rc.saturating_mul(axes.max(1)) > IVS_WORK {
750        return None;
751    }
752    let zero = coords.iter().all(|&c| c == 0);
753    let recs = rl.checked_add(4)?;
754    for k in 0..rc {
755        let ri = r16(t, at(d.checked_add(6)?, k, 2)?)?;
756        if ri >= nreg {
757            return None;
758        }
759        let mut sc = if zero { 0.0f32 } else { 1.0 };
760        if !zero {
761            for a in 0..axes {
762                let rec = at(recs, ri * axes + a, 6)?;
763                let (st, pk, en) = (s16(t, rec)?, s16(t, rec.checked_add(2)?)?, s16(t, rec.checked_add(4)?)?);
764                let v = coords.get(a as usize).copied().unwrap_or(0) as i32;
765                let f = region_factor(v, st, pk, en);
766                if f == 0.0 {
767                    sc = 0.0;
768                    break;
769                }
770                sc *= f;
771            }
772        }
773        out.push(sc);
774    }
775    Some(())
776}
777
778// the delta (outer, inner) of the item variation store at s
779fn ivs(t: Tb, s: u32, outer: u32, inner: u32, coords: &[i16]) -> Option<f32> {
780    if s == 0 || r16(t, s)? != 1 {
781        return None;
782    }
783    let rl = s.checked_add(r32(t, s + 2)?)?;
784    if outer >= r16(t, s + 6)? {
785        return None;
786    }
787    let d = s.checked_add(r32(t, at(s + 8, outer, 4)?)?)?;
788    let items = r16(t, d)?;
789    let wc = r16(t, d + 2)?;
790    let rc = r16(t, d + 4)?;
791    if inner >= items {
792        return None;
793    }
794    let long = wc & 0x8000 != 0;
795    let words = wc & 0x7FFF;
796    if words > rc {
797        return None;
798    }
799    let (ws, bs) = if long { (4u32, 2u32) } else { (2, 1) };
800    let row = words * ws + (rc - words) * bs;
801    let base = at(d.checked_add(6 + rc * 2)?, inner, row)?;
802    let axes = r16(t, rl)?;
803    let nreg = r16(t, rl + 2)?;
804    if rc.saturating_mul(axes.max(1)) > IVS_WORK {
805        return None;
806    }
807    let mut sum = 0.0f32;
808    for k in 0..rc {
809        let ri = r16(t, d + 6 + k * 2)?;
810        if ri >= nreg {
811            return None;
812        }
813        let mut sc = 1.0f32;
814        for a in 0..axes {
815            let rec = at(rl + 4, ri * axes + a, 6)?;
816            let (st, pk, en) = (s16(t, rec)?, s16(t, rec + 2)?, s16(t, rec + 4)?);
817            let v = coords.get(a as usize).copied().unwrap_or(0) as i32;
818            let f = region_factor(v, st, pk, en);
819            if f == 0.0 {
820                sc = 0.0;
821                break;
822            }
823            sc *= f;
824        }
825        if sc == 0.0 {
826            continue;
827        }
828        let dv = if k < words {
829            let o = at(base, k, ws)?;
830            if long { r32(t, o)? as i32 } else { s16(t, o)? }
831        } else {
832            let o = at(base.checked_add(words * ws)?, k - words, bs)?;
833            if long {
834                s16(t, o)?
835            } else {
836                let b = t.u8(o);
837                if b < 0 {
838                    return None;
839                }
840                b as u8 as i8 as i32
841            }
842        };
843        sum += sc * dv as f32;
844    }
845    Some(sum)
846}