Skip to main content

text/
registry.rs

1// registry -- the font registry: embedded SGF1 fonts, runtime fonts (ttf.rs) and the derived
2// fonts of font_style (synthetic bold / italic / sharp)       [src/text/font.s, outline.s]
3//
4//   register(data) -> id / -1            add an SGF1 font
5//   load_mem(data, face) / load_file(path, face) -> id / ERR_*   runtime fonts (ttf.rs)
6//   style(font, flags) -> id / ERR_*     derived font with FS_SYN_BOLD (outline dilated by
7//        upem/30, advance + upem/30), FS_SYN_ITALIC (x += y * tan 12 deg) and/or FS_SHARP
8//        (vertical grid fitting); flags 0 = font itself; the same (font, flags) pair always
9//        gives the same id
10//   unload(id) -> 0 / -1                 a runtime font and every font derived from it; the
11//        glyph cache is flushed; the id may be reused later
12//   count(), valid(id), hdr(font) (font_data), glyph(font, cp), has, adv, kern, kern_table,
13//   upem, metrics, metric, is_color, name, layers, glyph_box, glyph_path
14// Variable fonts (var.rs):
15//   var_axes(font) / var_instances(font)   fvar's axes and named instances (empty: not variable)
16//   var_values(font) -> user values per axis (an instance's, else the defaults)
17//   vary(font, &[(tag, value)]) -> id / ERR_*   an instance (VarFont: the base font at
18//        normalized coordinates; own id, serial and metrics, so glyph cache entries, outline
19//        buffer and shaping engines never mix instances); axes not named keep font's values;
20//        all-default coordinates give the variable font itself; the same coordinates the same
21//        id; synthetic styles of font carry over; unload(base) unloads its instances
22//   vary_named(font, i) -> id / ERR_*    named instance i
23//
24// Font ids 0..3 are the embedded fonts (never reused).  An unknown or unloaded id behaves
25// like font 0.
26
27use alloc::boxed::Box;
28use alloc::vec::Vec;
29
30use rt::fx::cvt;
31
32use crate::colr;
33use crate::font::*;
34use crate::outline::{Zones, F_INV30, F_TAN12};
35use crate::ttf::{self, TtFont, ERR_FAIL, ERR_IO, ERR_LIMIT, FILE_MAX};
36use crate::var::{VarAxis, VarInstance};
37use crate::Text;
38
39pub const FONT_EMBEDDED: u32 = 4; // ids below this are the embedded fonts (never unloaded)
40pub const FONT_MAX_IDS: u32 = 4096; // registry capacity
41
42/// a font derived by font_style (TFK_SYN context)
43pub struct SynFont {
44    pub hdr: Hdr,
45    pub fm: Metrics,
46    pub base: u32,
47    pub syn: u32,
48    pub strength: f32, // embolden strength in font units
49    pub serial: u64,
50}
51
52/// an instance of a variable font (vary): the base font's tables at normalized coordinates
53pub(crate) struct VarFont {
54    pub hdr: Hdr,
55    pub fm: Metrics,
56    pub base: u32,
57    /// user values per axis and the normalized F2Dot14 coordinates (avar applied)
58    pub user: Vec<f32>,
59    pub coords: Vec<i16>,
60    pub serial: u64,
61    /// every glyph's advance when gvar's phantom points give it (no HVAR); else empty
62    pub adv: Vec<i32>,
63}
64
65pub(crate) enum Face {
66    Free,
67    Sgf(Font),
68    Tt(Box<TtFont>),
69    Syn(Box<SynFont>),
70    Var(Box<VarFont>),
71}
72
73pub(crate) struct Slot {
74    pub face: Face,
75    pub raster: crate::FontRaster,
76    pub fmap: Vec<u16>, // glyph ids for cp < FAST_CP (runtime fonts; empty: slow path)
77}
78
79impl Slot {
80    fn free() -> Slot {
81        Slot { face: Face::Free, raster: crate::FontRaster::Legacy, fmap: Vec::new() }
82    }
83
84    fn is_free(&self) -> bool {
85        matches!(self.face, Face::Free)
86    }
87}
88
89static S_SGF1: &[u8] = b"SGF1";
90static S_REGULAR: &[u8] = b"Regular";
91
92impl Text {
93    // ------------------------------------------------------------------ slots
94
95    /// font_slot: the registry index used for font (unknown / free ids: font 0); None when no
96    /// font is registered
97    pub(crate) fn slot_idx(&self, font: u32) -> Option<usize> {
98        if self.slots.is_empty() {
99            return None;
100        }
101        let i = font as usize;
102        if i < self.slots.len() && !self.slots[i].is_free() { Some(i) } else { Some(0) }
103    }
104
105    pub(crate) fn face(&self, font: u32) -> Option<&Face> {
106        self.slot_idx(font).map(|i| &self.slots[i].face)
107    }
108
109    // a free id (ids below FONT_EMBEDDED are never reused) or None  [font_slot_alloc]
110    fn slot_alloc(&mut self) -> Option<usize> {
111        for i in FONT_EMBEDDED as usize..self.slots.len() {
112            if self.slots[i].is_free() {
113                return Some(i);
114            }
115        }
116        if self.slots.len() >= FONT_MAX_IDS as usize {
117            return None;
118        }
119        self.slots.push(Slot::free());
120        Some(self.slots.len() - 1)
121    }
122
123    /// font_register(data) -> id / -1
124    pub fn register(&mut self, data: &'static [u8]) -> i32 {
125        let Some(f) = Font::parse(data) else { return -1 };
126        let Some(i) = self.slot_alloc() else { return -1 };
127        self.slots[i] = Slot { face: Face::Sgf(f), raster: crate::FontRaster::Legacy, fmap: Vec::new() };
128        i as i32
129    }
130
131    // the fast glyph table of a runtime face (code points below FAST_CP)
132    fn fast_map(&self, face: &Face) -> Vec<u16> {
133        let mut fmap = Vec::with_capacity(FAST_CP as usize);
134        for cp in 0..FAST_CP {
135            let g = match face {
136                Face::Tt(t) => t.glyph(cp),
137                Face::Syn(s) => self.glyph(s.base, cp),
138                Face::Var(v) => self.glyph(v.base, cp),
139                _ => 0,
140            };
141            fmap.push(g as u16);
142        }
143        fmap
144    }
145
146    // register a runtime face; the fast table through its glyph op  [font_add_rt]
147    fn add_rt(&mut self, face: Face) -> i32 {
148        let fmap = self.fast_map(&face);
149        let Some(i) = self.slot_alloc() else { return ERR_LIMIT };
150        let raster = match &face {
151            Face::Syn(s) => self.font_raster(s.base),
152            Face::Var(v) => self.font_raster(v.base),
153            _ => crate::FontRaster::Legacy,
154        };
155        self.slots[i] = Slot { face, raster, fmap };
156        i as i32
157    }
158
159    /// replace the embedded fonts 0..3 (SGF1 or TrueType / OpenType; a style switched at run
160    /// time): the ids keep their meaning, fonts derived from them (style) take the new
161    /// metrics, cached glyphs are dropped; each face starts with the legacy raster policy
162    pub fn set_embedded(&mut self, fonts: [&'static [u8]; 4], names: [[&'static str; 5]; 4]) {
163        for (i, f) in fonts.iter().enumerate() {
164            let face = if f.starts_with(b"SGF1") {
165                Font::parse(f).map_or(Face::Free, Face::Sgf)
166            } else {
167                let serial = self.next_serial();
168                TtFont::open(Vec::from(*f), 0, &mut self.ol, serial).map_or(Face::Free, Face::Tt)
169            };
170            let fmap = if matches!(face, Face::Tt(_)) { self.fast_map(&face) } else { Vec::new() };
171            while self.slots.len() <= i {
172                self.slots.push(Slot::free());
173            }
174            self.slots[i] = Slot { face, raster: crate::FontRaster::Legacy, fmap };
175        }
176        self.names = names;
177        // instances of the embedded ones: the same axis values on the new base (none: its
178        // default)
179        for i in FONT_EMBEDDED as usize..self.slots.len() {
180            let (base, user) = match &self.slots[i].face {
181                Face::Var(v) if v.base < FONT_EMBEDDED => (v.base, v.user.clone()),
182                _ => continue,
183            };
184            let serial = self.next_serial();
185            let Some((hdr, fm, coords, adv)) = self.var_build(base, &user) else { continue };
186            if let Face::Var(v) = &mut self.slots[i].face {
187                (v.hdr, v.fm, v.coords, v.adv, v.serial) = (hdr, fm, coords, adv, serial);
188            }
189            let fmap = self.fast_map(&self.slots[i].face);
190            self.slots[i].fmap = fmap;
191            self.slots[i].raster = self.font_raster(base);
192        }
193        // the derived fonts of the embedded ones: header, metrics and glyph table of the new base
194        for i in FONT_EMBEDDED as usize..self.slots.len() {
195            let (base, syn) = match &self.slots[i].face {
196                Face::Syn(s) if s.base < FONT_EMBEDDED => (s.base, s.syn),
197                _ => continue,
198            };
199            let Some((hdr, fm, strength)) = self.syn_metrics(base, syn) else { continue };
200            let serial = self.next_serial();
201            if let Face::Syn(s) = &mut self.slots[i].face {
202                (s.hdr, s.fm, s.strength, s.serial) = (hdr, fm, strength, serial);
203            }
204            let fmap = self.fast_map(&self.slots[i].face);
205            self.slots[i].fmap = fmap;
206            self.slots[i].raster = self.font_raster(base);
207        }
208        self.gc_flush();
209    }
210
211    fn next_serial(&mut self) -> u64 {
212        self.serial += 1;
213        self.serial
214    }
215
216    /// font_face_count(data) -> faces (1 for a plain sfnt, n for a TTC / OTC) / ERR_*
217    pub fn face_count(data: &[u8]) -> i32 {
218        ttf::face_count(data)
219    }
220
221    /// font_load_mem(data, face) -> id / ERR_* (the data is copied)
222    pub fn load_mem(&mut self, data: &[u8], face: u32) -> i32 {
223        self.load_vec(Vec::from(data), face)
224    }
225
226    fn load_vec(&mut self, data: Vec<u8>, face: u32) -> i32 {
227        let serial = self.next_serial();
228        match TtFont::open(data, face, &mut self.ol, serial) {
229            Ok(t) => self.add_rt(Face::Tt(t)),
230            Err(e) => e,
231        }
232    }
233
234    /// font_load_file(path, face) -> id / ERR_* (ERR_IO when it cannot be read)
235    pub fn load_file(&mut self, path: &str, face: u32) -> i32 {
236        let Some(f) = plat::File::open(path, plat::FO_READ) else { return ERR_IO };
237        let size = f.size().unwrap_or(0);
238        if size == 0 {
239            return ERR_IO;
240        }
241        if size > FILE_MAX {
242            return ERR_LIMIT;
243        }
244        let mut buf = alloc::vec![0u8; size as usize];
245        if f.read_at(0, &mut buf) != Some(size as usize) {
246            return ERR_IO;
247        }
248        drop(f);
249        self.load_vec(buf, face)
250    }
251
252    /// font_unload(id) -> 0 / -1
253    pub fn unload(&mut self, id: u32) -> i32 {
254        let i = id as usize;
255        if id < FONT_EMBEDDED || i >= self.slots.len() || self.slots[i].is_free() {
256            return -1;
257        }
258        // dependents no longer find it
259        let old = core::mem::replace(&mut self.slots[i].face, Face::Free);
260        // unload fonts derived from this one first
261        let mut j = 0;
262        while j < self.slots.len() {
263            let dep = match &self.slots[j].face {
264                Face::Tt(t) => t.hdr.baseid == id + 1,
265                Face::Syn(s) => s.hdr.baseid == id + 1,
266                Face::Var(v) => v.hdr.baseid == id + 1,
267                _ => false,
268            };
269            if dep {
270                self.unload(j as u32);
271            }
272            j += 1;
273        }
274        drop(old);
275        if i < self.slots.len() {
276            self.slots[i] = Slot::free();
277        }
278        // drop trailing free slots
279        while self.slots.len() > FONT_EMBEDDED as usize && self.slots[self.slots.len() - 1].is_free() {
280            self.slots.pop();
281        }
282        self.gc_flush(); // cached masks are keyed by the id
283        0
284    }
285
286    /// font_count() -> ids in use are below this value
287    pub fn count(&self) -> u32 {
288        self.slots.len() as u32
289    }
290
291    /// font_valid(id): the id names a registered font
292    pub fn valid(&self, id: u32) -> bool {
293        (id as usize) < self.slots.len() && !self.slots[id as usize].is_free()
294    }
295
296    /// font_data: the SGF1-compatible header (an unknown id gives font 0)
297    pub fn hdr(&self, font: u32) -> Hdr {
298        match self.face(font) {
299            Some(Face::Sgf(f)) => f.hdr(),
300            Some(Face::Tt(t)) => t.hdr,
301            Some(Face::Syn(s)) => s.hdr,
302            Some(Face::Var(v)) => v.hdr,
303            _ => Hdr::default(),
304        }
305    }
306
307    pub fn upem(&self, font: u32) -> i32 {
308        if self.slots.is_empty() { 2048 } else { self.hdr(font).upem }
309    }
310
311    /// font_glyph(font, cp) -> glyph (SGF1: fallback glyph, runtime: 0 = .notdef)
312    pub fn glyph(&self, font: u32, cp: u32) -> u32 {
313        if cp < FAST_CP && (font as usize) < self.slots.len() {
314            let s = &self.slots[font as usize];
315            if !s.fmap.is_empty() {
316                return s.fmap[cp as usize] as u32;
317            }
318            if let Face::Sgf(f) = &s.face {
319                return f.glyph(cp);
320            }
321        }
322        match self.face(font) {
323            Some(Face::Sgf(f)) => f.glyph(cp),
324            Some(Face::Tt(t)) => t.glyph(cp),
325            Some(Face::Syn(s)) => self.glyph(s.base, cp),
326            Some(Face::Var(v)) => self.glyph(v.base, cp),
327            _ => 0,
328        }
329    }
330
331    /// font_has(font, cp)
332    pub fn has(&self, font: u32, cp: u32) -> bool {
333        match self.face(font) {
334            Some(Face::Sgf(f)) => f.lookup(cp).is_some(),
335            Some(Face::Tt(t)) => t.has(cp),
336            Some(Face::Syn(s)) => self.has(s.base, cp),
337            Some(Face::Var(v)) => self.has(v.base, cp),
338            _ => false,
339        }
340    }
341
342    /// font_adv(font, glyph): advance in font units (synthetic bold included)
343    pub fn adv(&self, font: u32, g: u32) -> i32 {
344        match self.face(font) {
345            Some(Face::Sgf(f)) => f.adv(g),
346            Some(Face::Tt(t)) => t.adv(g),
347            Some(Face::Syn(s)) => {
348                let a = self.adv(s.base, g);
349                if s.syn & FS_SYN_BOLD != 0 { a.wrapping_add(cvt(s.strength)) } else { a }
350            }
351            Some(Face::Var(v)) => match (v.adv.get(g as usize), self.face(v.base)) {
352                (Some(&a), _) => a,
353                (None, Some(Face::Tt(t))) => t.adv_var(g, &v.coords),
354                _ => 0,
355            },
356            _ => 0,
357        }
358    }
359
360    /// font_kern(font, g1, g2): kerning in font units (signed)
361    pub fn kern(&self, font: u32, g1: u32, g2: u32) -> i32 {
362        match self.face(font) {
363            Some(Face::Sgf(f)) => f.kern(g1, g2),
364            Some(Face::Tt(t)) => t.kern(g1, g2),
365            Some(Face::Syn(s)) => self.kern(s.base, g1, g2),
366            Some(Face::Var(v)) => match self.face(v.base) {
367                Some(Face::Tt(t)) => t.kern_var(g1, g2, &v.coords),
368                _ => 0,
369            },
370            _ => 0,
371        }
372    }
373
374    /// font_kern_table: the 'kern' table only (runtime fonts; others: font_kern)
375    pub fn kern_table(&self, font: u32, g1: u32, g2: u32) -> i32 {
376        match self.face(font) {
377            Some(Face::Tt(t)) => t.kern_table(g1, g2),
378            Some(Face::Var(v)) => self.kern_table(v.base, g1, g2),
379            Some(_) => self.kern(font, g1, g2),
380            None => 0,
381        }
382    }
383
384    /// font_is_color: COLR layers or CBDT bitmaps
385    pub fn is_color(&self, font: u32) -> bool {
386        self.slot_idx(font).is_some() && self.hdr(font).flags & (SGH_COLR | SGH_CBDT) != 0
387    }
388
389    /// font_layers(font, glyph, cap) into out: {glyph | LAYER_FG, argb}; empty = draw it
390    /// normally
391    pub fn layers(&self, font: u32, g: u32, cap: usize, out: &mut Vec<(u32, u32)>) {
392        out.clear();
393        match self.face(font) {
394            Some(Face::Tt(t)) if t.hdr.flags & SGH_COLR != 0 => colr::layers(t, g, cap, out),
395            Some(Face::Syn(s)) if s.hdr.flags & SGH_COLR != 0 => self.layers(s.base, g, cap, out),
396            Some(Face::Var(v)) if v.hdr.flags & SGH_COLR != 0 => self.layers(v.base, g, cap, out),
397            _ => {}
398        }
399    }
400
401    /// font_name(font, FN_*) -> UTF-8 ("" when unknown)
402    pub fn name(&self, font: u32, which: u32) -> &[u8] {
403        let Some(i) = self.slot_idx(font) else { return b"" };
404        match &self.slots[i].face {
405            Face::Sgf(_) => {
406                if i < FONT_EMBEDDED as usize {
407                    // embedded fonts: the style's names
408                    let k = match which {
409                        FN_FAMILY => 0,
410                        FN_STYLE => 1,
411                        FN_FULL => 2,
412                        FN_TYPO_FAMILY => 3,
413                        FN_TYPO_STYLE => 4,
414                        _ => return b"",
415                    };
416                    return self.names[i][k].as_bytes();
417                }
418                match which {
419                    FN_FAMILY | FN_TYPO_FAMILY | FN_FULL => S_SGF1,
420                    FN_STYLE | FN_TYPO_STYLE => S_REGULAR,
421                    _ => b"",
422                }
423            }
424            Face::Tt(t) => t.name(which),
425            Face::Syn(s) => self.name(s.base, which),
426            Face::Var(v) => self.name(v.base, which),
427            Face::Free => b"",
428        }
429    }
430
431    /// font_metrics(font) -> FONTMETRICS (None when no font exists)
432    pub fn metrics(&self, font: u32) -> Option<Metrics> {
433        let i = self.slot_idx(font)?;
434        match &self.slots[i].face {
435            Face::Tt(t) => Some(t.fm),
436            Face::Syn(s) => Some(s.fm),
437            Face::Var(v) => Some(v.fm),
438            Face::Sgf(f) => {
439                let mut m = Metrics {
440                    upem: f.upem,
441                    asc: f.asc,
442                    typo_asc: f.asc,
443                    win_asc: f.asc,
444                    desc: f.desc,
445                    typo_desc: f.desc,
446                    win_desc: f.desc.wrapping_neg(),
447                    gap: f.gap,
448                    typo_gap: f.gap,
449                    xh: f.xh,
450                    caph: f.caph,
451                    ul_pos: f.ulpos,
452                    ul_thick: f.ulthick,
453                    st_thick: f.ulthick,
454                    // strikeout: centred at half the x-height (top of the stroke is stored)
455                    st_pos: (f.xh + f.ulthick) >> 1,
456                    width: 5,
457                    nglyphs: f.nglyphs as i32,
458                    ..Default::default()
459                };
460                m.weight = match i as u32 {
461                    1 => 600,
462                    2 => 700,
463                    _ => 400,
464                };
465                let mut fl = FMF_EMBED;
466                if f.flags & SGH_KERN != 0 {
467                    fl |= FMF_KERN;
468                }
469                if i == 2 {
470                    fl |= FMF_BOLD;
471                }
472                m.flags = fl;
473                Some(m)
474            }
475            Face::Free => None,
476        }
477    }
478
479    /// font_metric(font, FM_*) -> one FONTMETRICS field
480    pub fn metric(&self, font: u32, field: u32) -> i32 {
481        self.metrics(font).unwrap_or_default().field(field)
482    }
483
484    /// glyph_box(font, glyph) -> {xmin, ymin, xmax, ymax} (font units, control box); None =
485    /// empty.  Runtime fonts leave the glyph's outline in the outline buffer.
486    pub fn glyph_box(&mut self, font: u32, g: u32) -> Option<[i32; 4]> {
487        let i = self.slot_idx(font)?;
488        match &self.slots[i].face {
489            Face::Sgf(f) => f.glyph_box(g),
490            Face::Tt(t) => t.bbox(g, &mut self.ol),
491            Face::Syn(_) => self.syn_box(i, g),
492            Face::Var(_) => {
493                if self.var_prep(i, g) { self.ol.bbox() } else { None }
494            }
495            Face::Free => None,
496        }
497    }
498
499    /// glyph_path(font, glyph): append the outline to the path, transformed by gp (origin
500    /// x / y in float pixels, sx / sy float pixels per unit)
501    pub fn glyph_path(&mut self, font: u32, g: u32) {
502        let Some(i) = self.slot_idx(font) else { return };
503        match &self.slots[i].face {
504            Face::Sgf(f) => {
505                let mut gp = self.gp;
506                f.path(g, &mut gp, &mut self.path, 0);
507            }
508            Face::Tt(t) => t.path(g, &mut self.ol, &mut self.path, &self.gp),
509            Face::Syn(_) => self.syn_path(i, g),
510            Face::Var(v) => {
511                let serial = v.serial;
512                if !self.ol.owner_is(serial, g) {
513                    self.var_prep(i, g);
514                }
515                if self.ol.npts() != 0 {
516                    self.ol.emit(&mut self.path, &self.gp, None);
517                }
518            }
519            Face::Free => {}
520        }
521    }
522
523    /// OP_BITMAP: the CBDT bitmap of glyph (runtime fonts; derived fonts use their base's)
524    pub(crate) fn op_bitmap(&mut self, font: u32, g: u32, size_fx: i32) -> Option<colr::Bitmap> {
525        let i = self.slot_idx(font)?;
526        match &self.slots[i].face {
527            Face::Tt(t) => colr::bitmap(t, g, size_fx, self.png_decode, self.img_scale, &mut self.cb_metrics),
528            Face::Syn(s) => {
529                let base = s.base;
530                match self.face(base) {
531                    Some(Face::Tt(_)) | Some(Face::Syn(_)) | Some(Face::Var(_)) => self.op_bitmap(base, g, size_fx),
532                    _ => None,
533                }
534            }
535            Face::Var(v) => {
536                let base = v.base;
537                self.op_bitmap(base, g, size_fx)
538            }
539            _ => None,
540        }
541    }
542
543    // ------------------------------------------------------------------ variable fonts
544
545    // the variable font under font: (its id, the instance's user values or None for the
546    // defaults, the synthetic style flags on top)
547    fn var_root(&self, font: u32) -> Option<(u32, Option<&[f32]>, u32)> {
548        let (mut f, mut syn) = (font, 0);
549        if let Some(Face::Syn(s)) = self.face(f) {
550            syn = s.syn;
551            f = s.base;
552        }
553        let i = self.slot_idx(f)?;
554        match &self.slots[i].face {
555            Face::Var(v) => Some((v.base, Some(&v.user), syn)),
556            Face::Tt(t) if t.var.is_some() => Some((i as u32, None, syn)),
557            _ => None,
558        }
559    }
560
561    fn var_tt(&self, base: u32) -> Option<&TtFont> {
562        match self.face(base) {
563            Some(Face::Tt(t)) => Some(t),
564            _ => None,
565        }
566    }
567
568    /// the variation axes of a font (fvar; empty: not a variable font); instances and the
569    /// fonts derived from them report their variable font's
570    pub fn var_axes(&self, font: u32) -> Vec<VarAxis> {
571        self.var_root(font).and_then(|(b, _, _)| self.var_tt(b)).and_then(|t| t.var.as_ref()).map_or(Vec::new(), |v| v.axes.clone())
572    }
573
574    /// the named instances of a variable font (fvar; empty: none or not variable)
575    pub fn var_instances(&self, font: u32) -> Vec<VarInstance> {
576        self.var_root(font).and_then(|(b, _, _)| self.var_tt(b)).and_then(|t| t.var.as_ref()).map_or(Vec::new(), |v| v.instances.clone())
577    }
578
579    /// the axis values of a font in user units, one per axis of var_axes (an instance's own,
580    /// else the defaults)
581    pub fn var_values(&self, font: u32) -> Vec<f32> {
582        match self.var_root(font) {
583            Some((_, Some(u), _)) => u.to_vec(),
584            Some(_) => self.var_axes(font).iter().map(|a| a.def).collect(),
585            None => Vec::new(),
586        }
587    }
588
589    /// the normalized coordinates of an instance: F2Dot14 bits per axis with avar applied
590    /// (what HarfBuzz's hb_font_get_var_coords_normalized reports); empty for a variable font
591    /// itself (its default instance) and for fonts that are not variable
592    pub fn var_coords(&self, font: u32) -> Vec<i16> {
593        let mut f = font;
594        if let Some(Face::Syn(s)) = self.face(f) {
595            f = s.base;
596        }
597        match self.face(f) {
598            Some(Face::Var(v)) => v.coords.clone(),
599            _ => Vec::new(),
600        }
601    }
602
603    /// an instance of a variable font with axis values in user units (tag, value): axes not
604    /// named keep font's values (an instance's, else the defaults); values are clamped to their
605    /// axis, unknown tags ignored -> font id / ERR_*.  All-default coordinates give the variable
606    /// font itself, the same coordinates the same id; synthetic styles of font (style) carry
607    /// over; a font that is not variable gives itself.  Each instance has its own glyph cache
608    /// entries and metrics (MVAR); unloading the variable font unloads its instances.
609    pub fn vary(&mut self, font: u32, values: &[([u8; 4], f32)]) -> i32 {
610        if !self.valid(font) {
611            return ERR_FAIL;
612        }
613        let Some((base, cur, syn)) = self.var_root(font) else { return font as i32 };
614        let axes = self.var_axes(base);
615        let mut user: Vec<f32> = match cur {
616            Some(u) => u.to_vec(),
617            None => axes.iter().map(|a| a.def).collect(),
618        };
619        for &(tag, v) in values {
620            if let Some(k) = axes.iter().position(|a| a.tag == tag) {
621                user[k] = if v.is_nan() { axes[k].def } else { v.clamp(axes[k].min, axes[k].max) };
622            }
623        }
624        let id = self.var_instance(base, &user);
625        if id < 0 || syn == 0 { id } else { self.style(id as u32, syn) }
626    }
627
628    /// named instance i of a variable font (var_instances) -> id / ERR_* (ERR_FAIL: no such
629    /// instance)
630    pub fn vary_named(&mut self, font: u32, i: usize) -> i32 {
631        let ins = self.var_instances(font);
632        let Some(inst) = ins.get(i) else { return ERR_FAIL };
633        let axes = self.var_axes(font);
634        let vals: Vec<([u8; 4], f32)> = axes.iter().zip(&inst.coords).map(|(a, &c)| (a.tag, c)).collect();
635        self.vary(font, &vals)
636    }
637
638    // the instance of variable font base at user values (all-default: base itself)
639    fn var_instance(&mut self, base: u32, user: &[f32]) -> i32 {
640        let Some(coords) = self.var_tt(base).and_then(|t| t.var.as_ref()).map(|v| v.normalize(user)) else { return base as i32 };
641        if coords.iter().all(|&c| c == 0) {
642            return base as i32;
643        }
644        for i in FONT_EMBEDDED as usize..self.slots.len() {
645            if let Face::Var(v) = &self.slots[i].face
646                && v.base == base
647                && v.coords == coords
648            {
649                return i as i32;
650            }
651        }
652        let Some((hdr, fm, coords, adv)) = self.var_build(base, user) else { return ERR_FAIL };
653        let serial = self.next_serial();
654        self.add_rt(Face::Var(Box::new(VarFont { hdr, fm, base, user: user.to_vec(), coords, serial, adv })))
655    }
656
657    // header, metrics, coordinates and phantom-point advances of base's instance at user
658    // values (weight, italic and oblique flags from wght / ital / slnt when the font has them)
659    fn var_build(&mut self, base: u32, user: &[f32]) -> Option<(Hdr, Metrics, Vec<i16>, Vec<i32>)> {
660        let bi = self.slot_idx(base)?;
661        let Face::Tt(t) = &self.slots[bi].face else { return None };
662        let v = t.var.as_ref();
663        let coords = v.map_or(Vec::new(), |v| v.normalize(user));
664        let axes: &[VarAxis] = v.map_or(&[], |v| &v.axes);
665        let val = |tag: &[u8; 4]| axes.iter().position(|a| &a.tag == tag).and_then(|k| user.get(k).copied());
666        let (wght, ital, slnt) = (val(b"wght"), val(b"ital"), val(b"slnt"));
667        let (mut hdr, mut fm) = {
668            let (m, h) = t.metrics_var(&coords, wght, &mut self.ol);
669            (h, m)
670        };
671        let adv = t.adv_table(&coords, &mut self.ol);
672        hdr.baseid = base + 1;
673        if let Some(w) = wght {
674            fm.flags = (fm.flags & !FMF_BOLD) | if w >= 600.0 { FMF_BOLD } else { 0 };
675        }
676        if let Some(i) = ital {
677            fm.flags = (fm.flags & !FMF_ITALIC) | if i >= 0.5 { FMF_ITALIC } else { 0 };
678        }
679        if let Some(s) = slnt {
680            fm.flags = (fm.flags & !FMF_OBLIQUE) | if s != 0.0 { FMF_OBLIQUE } else { 0 };
681        }
682        Some((hdr, fm, coords, adv))
683    }
684
685    // the outline of the instance in slot i into the buffer (owner: the instance)
686    fn var_prep(&mut self, i: usize, g: u32) -> bool {
687        let Face::Var(v) = &self.slots[i].face else { return false };
688        let Some(bi) = self.slot_idx(v.base) else { return false };
689        let (Face::Var(v), Face::Tt(t)) = (&self.slots[i].face, &self.slots[bi].face) else { return false };
690        t.prep_var(g, &v.coords, v.serial, &mut self.ol)
691    }
692
693    // (shaping.rs) a HarfRust engine for font: the font file at the instance's coordinates
694    // (synthetic styles shape like their base)
695    pub(crate) fn shape_engine(&self, mut font: u32) -> Option<text_shape::Engine> {
696        let mut coords: &[i16] = &[];
697        for _ in 0..16 {
698            match self.face(font)? {
699                Face::Tt(t) => {
700                    let e = text_shape::Engine::new(&t.data, t.face_index)?;
701                    return Some(if coords.is_empty() { e } else { e.with_coords(coords) });
702                }
703                Face::Syn(s) => font = s.base,
704                Face::Var(v) => {
705                    if coords.is_empty() {
706                        coords = &v.coords;
707                    }
708                    font = v.base;
709                }
710                _ => return None,
711            }
712        }
713        None
714    }
715
716    // (shaping.rs) the serial that names font's shaping engine (an instance's own)
717    pub(crate) fn shape_serial(&self, mut font: u32) -> u64 {
718        for _ in 0..16 {
719            match self.face(font) {
720                Some(Face::Tt(t)) => return t.serial,
721                Some(Face::Var(v)) => return v.serial,
722                Some(Face::Syn(s)) => font = s.base,
723                _ => return 0,
724            }
725        }
726        0
727    }
728
729    // ------------------------------------------------------------------ synthetic styles
730
731    pub(crate) fn classic_metrics(&self, font: u32) -> bool {
732        matches!(self.face(font), Some(Face::Syn(s)) if s.syn & FS_CLASSIC != 0)
733    }
734
735    /// font_style(font, FS_*) -> id / ERR_*
736    pub fn style(&mut self, font: u32, flags: u32) -> i32 {
737        let mut flags = flags & FS_MASK;
738        if !self.valid(font) {
739            return ERR_FAIL;
740        }
741        if flags == 0 {
742            return font as i32;
743        }
744        // a derived font: combine with its flags, derive from its base
745        let mut base = font;
746        if let Some(Face::Syn(s)) = self.face(font) {
747            flags |= s.syn;
748            base = s.base;
749        }
750        // existing (base, flags)?
751        for i in 0..self.slots.len() {
752            if let Face::Syn(s) = &self.slots[i].face
753                && s.base == base
754                && s.syn == flags
755            {
756                return i as i32;
757            }
758        }
759        let Some((hdr, fm, strength)) = self.syn_metrics(base, flags) else { return ERR_FAIL };
760        let serial = self.next_serial();
761        self.add_rt(Face::Syn(Box::new(SynFont { hdr, fm, base, syn: flags, strength, serial })))
762    }
763
764    // header, metrics and embolden strength of the font derived from base with flags
765    fn syn_metrics(&self, base: u32, flags: u32) -> Option<(Hdr, Metrics, f32)> {
766        let mut hdr = self.hdr(base);
767        let mut fm = self.metrics(base)?;
768        hdr.flags = (hdr.flags & (SGH_COLR | SGH_CBDT)) | SGH_RUNTIME;
769        hdr.baseid = base + 1;
770        // strength = upem / 30
771        let strength = fm.upem as f32 * F_INV30;
772        fm.flags |= FMF_SYN;
773        if flags & FS_SYN_BOLD != 0 {
774            fm.flags |= FMF_BOLD;
775            fm.weight = fm.weight.wrapping_add(300).clamp(700, 1000);
776            fm.advmax = fm.advmax.wrapping_add(cvt(strength));
777        }
778        if flags & FS_SYN_ITALIC != 0 {
779            fm.flags |= FMF_ITALIC;
780        }
781        Some((hdr, fm, strength))
782    }
783
784    // the transformed outline of glyph into the buffer: true = not empty  [syn_prep]
785    fn syn_prep(&mut self, i: usize, g: u32) -> bool {
786        let Face::Syn(s) = &self.slots[i].face else { return false };
787        let (base, syn, strength, serial) = (s.base, s.syn, s.strength, s.serial);
788        let have = match self.slot_idx(base) {
789            None => false,
790            Some(bi) => match &self.slots[bi].face {
791                Face::Sgf(f) => {
792                    self.ol.reset();
793                    f.outline(g, 0, 0, 0, &mut self.ol);
794                    true
795                }
796                Face::Tt(t) => t.bbox(g, &mut self.ol).is_some(),
797                Face::Var(_) => self.var_prep(bi, g),
798                _ => false,
799            },
800        };
801        if !have || self.ol.npts() == 0 {
802            self.ol.reset();
803            self.ol.owner_set(serial, g);
804            return false;
805        }
806        if syn & FS_SYN_BOLD != 0 {
807            self.ol.embolden(strength);
808        }
809        if syn & FS_SYN_ITALIC != 0 {
810            self.ol.shear(F_TAN12);
811        }
812        self.ol.owner_set(serial, g);
813        true
814    }
815
816    // [syn_box]
817    fn syn_box(&mut self, i: usize, g: u32) -> Option<[i32; 4]> {
818        if !self.syn_prep(i, g) {
819            return None;
820        }
821        let mut b = self.ol.bbox()?;
822        let Face::Syn(s) = &self.slots[i].face else { return Some(b) };
823        if s.syn & FS_SHARP != 0 {
824            // grid fitting moves points by up to half a pixel: grow the box vertically
825            let grow = ((s.fm.upem as u32 >> 4) + 2) as i32;
826            b[1] = b[1].wrapping_sub(grow);
827            b[3] = b[3].wrapping_add(grow);
828        }
829        Some(b)
830    }
831
832    // append the transformed outline to the path  [syn_path]
833    fn syn_path(&mut self, i: usize, g: u32) {
834        let serial = match &self.slots[i].face {
835            Face::Syn(s) => s.serial,
836            _ => return,
837        };
838        if !self.ol.owner_is(serial, g) {
839            self.syn_prep(i, g);
840        }
841        if self.ol.npts() == 0 {
842            return;
843        }
844        let Face::Syn(s) = &self.slots[i].face else { return };
845        if s.syn & FS_SHARP != 0 {
846            let zn = Zones::fit(s.fm.desc, s.fm.xh, s.fm.caph, s.fm.asc, self.gp.sy);
847            self.ol.emit(&mut self.path, &self.gp, Some(&zn));
848        } else {
849            self.ol.emit(&mut self.path, &self.gp, None);
850        }
851    }
852}