Skip to main content

text/
font.rs

1// font -- embedded SGF1 fonts (tools/ttf2bin.py, ASMLIB/docs/FONTS.md), the font header and
2// FONTMETRICS records shared by every font kind                       [src/text/font.s]
3//
4//   Font::parse(data)         the 64-byte header, the fast codepoint table (None: not SGF1)
5//   glyph(cp) -> id           fallback glyph when unmapped      lookup(cp) -> Option<id>
6//   adv(g), kern(g1, g2)      font units      glyph_box(g) -> [xmin, ymin, xmax, ymax]
7//   path(g, gp, path)         append the outline (TrueType quadratics, composites)
8//   outline(g, dx, dy, ol)    the outline into the outline buffer (sgf_outline, outline.s)
9
10use gfx::path::Path;
11
12use crate::outline::Outline;
13
14pub const FAST_CP: u32 = 384;
15pub const SGF1_MAGIC: u32 = 0x3146_4753;
16
17// SGF1 header flags (SG_FLAGS)
18pub const SGH_ICON: u16 = 1;
19pub const SGH_KERN: u16 = 2;
20pub const SGH_RUNTIME: u16 = 0x100;
21pub const SGH_COLR: u16 = 0x200; // COLR layers (text runs draw them layer by layer)
22pub const SGH_CBDT: u16 = 0x400; // CBDT bitmaps (PARGB glyph cache entries)
23
24// font_style flags
25pub const FS_SYN_BOLD: u32 = 1;
26pub const FS_SYN_ITALIC: u32 = 2;
27pub const FS_SHARP: u32 = 4;
28/// Classic screen typography: round kerning and advances to whole device pixels.
29pub const FS_CLASSIC: u32 = 8;
30pub const FS_MASK: u32 = 15;
31
32// text_decor flags
33pub const TD_UNDERLINE: u32 = 1;
34pub const TD_STRIKEOUT: u32 = 2;
35
36// font_name ids (OpenType name IDs)
37pub const FN_FAMILY: u32 = 1;
38pub const FN_STYLE: u32 = 2;
39pub const FN_FULL: u32 = 4;
40pub const FN_TYPO_FAMILY: u32 = 16;
41pub const FN_TYPO_STYLE: u32 = 17;
42
43// FONTMETRICS byte offsets (font_metric)
44pub const FM_UPEM: u32 = 0;
45pub const FM_ASC: u32 = 4;
46pub const FM_DESC: u32 = 8;
47pub const FM_GAP: u32 = 12;
48pub const FM_TYPO_ASC: u32 = 16;
49pub const FM_TYPO_DESC: u32 = 20;
50pub const FM_TYPO_GAP: u32 = 24;
51pub const FM_WIN_ASC: u32 = 28;
52pub const FM_WIN_DESC: u32 = 32;
53pub const FM_XH: u32 = 36;
54pub const FM_CAPH: u32 = 40;
55pub const FM_UL_POS: u32 = 44;
56pub const FM_UL_THICK: u32 = 48;
57pub const FM_ST_POS: u32 = 52;
58pub const FM_ST_THICK: u32 = 56;
59pub const FM_WEIGHT: u32 = 60;
60pub const FM_FLAGS: u32 = 64;
61pub const FM_NGLYPHS: u32 = 68;
62pub const FM_ADVMAX: u32 = 72;
63pub const FM_WIDTH: u32 = 76;
64pub const FM_SIZE: u32 = 80;
65
66// FONTMETRICS flags
67pub const FMF_ITALIC: i32 = 1;
68pub const FMF_BOLD: i32 = 2;
69pub const FMF_OBLIQUE: i32 = 4;
70pub const FMF_COLOR: i32 = 8;
71pub const FMF_CFF: i32 = 16;
72pub const FMF_VAR: i32 = 32;
73pub const FMF_SYMBOL: i32 = 64;
74pub const FMF_MONO: i32 = 128;
75pub const FMF_TYPO: i32 = 256;
76pub const FMF_EMBED: i32 = 512;
77pub const FMF_SYN: i32 = 1024;
78pub const FMF_BITMAP: i32 = 2048;
79pub const FMF_KERN: i32 = 4096;
80
81/// the SGF1-compatible header every font kind has (font_data): what gc_scale and the
82/// text metrics read.  i16 fields sign-extended, upem / nglyphs zero-extended.
83#[derive(Clone, Copy, Default)]
84pub struct Hdr {
85    pub flags: u16,
86    pub upem: i32,
87    pub asc: i32,
88    pub desc: i32,
89    pub gap: i32,
90    pub caph: i32,
91    pub xh: i32,
92    pub ulpos: i32,
93    pub ulthick: i32,
94    pub nglyphs: u32,
95    pub baseid: u32, // derived fonts: base font id + 1 (0 = none)
96}
97
98/// FONTMETRICS (font.inc): all values in font units (y up)
99#[derive(Clone, Copy, Default)]
100pub struct Metrics {
101    pub upem: i32,
102    pub asc: i32,
103    pub desc: i32,
104    pub gap: i32,
105    pub typo_asc: i32,
106    pub typo_desc: i32,
107    pub typo_gap: i32,
108    pub win_asc: i32,
109    pub win_desc: i32,
110    pub xh: i32,
111    pub caph: i32,
112    pub ul_pos: i32,
113    pub ul_thick: i32,
114    pub st_pos: i32,
115    pub st_thick: i32,
116    pub weight: i32,
117    pub flags: i32,
118    pub nglyphs: i32,
119    pub advmax: i32,
120    pub width: i32,
121}
122
123impl Metrics {
124    /// the field at byte offset FM_* (0 for an invalid field)  [font_metric]
125    pub fn field(&self, off: u32) -> i32 {
126        if off > FM_SIZE - 4 || off & 3 != 0 {
127            return 0;
128        }
129        self.as_array()[(off / 4) as usize]
130    }
131
132    pub fn as_array(&self) -> [i32; 20] {
133        [
134            self.upem, self.asc, self.desc, self.gap, self.typo_asc, self.typo_desc, self.typo_gap,
135            self.win_asc, self.win_desc, self.xh, self.caph, self.ul_pos, self.ul_thick, self.st_pos,
136            self.st_thick, self.weight, self.flags, self.nglyphs, self.advmax, self.width,
137        ]
138    }
139}
140
141/// the glyph transform (font.s g_gp): origin in float pixels, pixels per font unit,
142/// composite offset in font units
143#[derive(Clone, Copy, Default)]
144pub struct Gp {
145    pub x: f32,
146    pub y: f32,
147    pub sx: f32,
148    pub sy: f32,
149    pub dx: i32,
150    pub dy: i32,
151}
152
153pub struct Font {
154    data: &'static [u8],
155    pub flags: u16,
156    pub upem: i32,
157    pub asc: i32,
158    pub desc: i32,
159    pub gap: i32,
160    pub caph: i32,
161    pub xh: i32,
162    pub ulpos: i32,
163    pub ulthick: i32,
164    pub nglyphs: u32,
165    nranges: u32,
166    kern_r: u32,
167    off_ranges: usize,
168    off_glyphs: usize,
169    off_outlines: usize,
170    off_kern: usize,
171    pub fallback: u32,
172    fmap: [u16; FAST_CP as usize],
173}
174
175fn u16_at(d: &[u8], o: usize) -> u16 {
176    u16::from_le_bytes([d[o], d[o + 1]])
177}
178
179fn i16_at(d: &[u8], o: usize) -> i32 {
180    i16::from_le_bytes([d[o], d[o + 1]]) as i32
181}
182
183fn u32_at(d: &[u8], o: usize) -> u32 {
184    u32::from_le_bytes([d[o], d[o + 1], d[o + 2], d[o + 3]])
185}
186
187impl Font {
188    /// font_register's parse: None when the data is not SGF1
189    pub fn parse(data: &'static [u8]) -> Option<Font> {
190        if data.len() < 64 || u32_at(data, 0) != SGF1_MAGIC {
191            return None;
192        }
193        let mut f = Font {
194            data,
195            flags: u16_at(data, 6),
196            upem: u16_at(data, 8) as i32,
197            asc: i16_at(data, 10),
198            desc: i16_at(data, 12),
199            gap: i16_at(data, 14),
200            caph: i16_at(data, 16),
201            xh: i16_at(data, 18),
202            ulpos: i16_at(data, 20),
203            ulthick: i16_at(data, 22),
204            nglyphs: u16_at(data, 24) as u32,
205            nranges: u16_at(data, 26) as u32,
206            kern_r: u16_at(data, 30) as u32,
207            off_ranges: u32_at(data, 32) as usize,
208            off_glyphs: u32_at(data, 36) as usize,
209            off_outlines: u32_at(data, 40) as usize,
210            off_kern: u32_at(data, 44) as usize,
211            fallback: u16_at(data, 48) as u32,
212            fmap: [0; FAST_CP as usize],
213        };
214        for cp in 0..FAST_CP {
215            f.fmap[cp as usize] = f.glyph_slow(cp) as u16;
216        }
217        Some(f)
218    }
219
220    pub fn new(data: &'static [u8]) -> Font {
221        Font::parse(data).expect("not an SGF1 font")
222    }
223
224    pub fn hdr(&self) -> Hdr {
225        Hdr {
226            flags: self.flags,
227            upem: self.upem,
228            asc: self.asc,
229            desc: self.desc,
230            gap: self.gap,
231            caph: self.caph,
232            xh: self.xh,
233            ulpos: self.ulpos,
234            ulthick: self.ulthick,
235            nglyphs: self.nglyphs,
236            baseid: 0,
237        }
238    }
239
240    /// binary search over the ranges {u32 firstCp, u16 count, u16 firstGlyph}  [font_lookup]
241    pub fn lookup(&self, cp: u32) -> Option<u32> {
242        let (mut lo, mut hi) = (0u32, self.nranges);
243        while lo < hi {
244            let mid = (lo + hi) >> 1;
245            let o = self.off_ranges + mid as usize * 8;
246            let first = u32_at(self.data, o);
247            if cp < first {
248                hi = mid;
249                continue;
250            }
251            let count = u16_at(self.data, o + 4) as u32;
252            if cp >= first.wrapping_add(count) {
253                lo = mid + 1;
254                continue;
255            }
256            return Some((cp - first).wrapping_add(u16_at(self.data, o + 6) as u32));
257        }
258        None
259    }
260
261    fn glyph_slow(&self, cp: u32) -> u32 {
262        self.lookup(cp).unwrap_or(self.fallback)
263    }
264
265    pub fn glyph(&self, cp: u32) -> u32 {
266        if cp < FAST_CP { self.fmap[cp as usize] as u32 } else { self.glyph_slow(cp) }
267    }
268
269    fn rec(&self, g: u32) -> Option<usize> {
270        if g >= self.nglyphs { None } else { Some(self.off_glyphs + g as usize * 16) }
271    }
272
273    pub fn adv(&self, g: u32) -> i32 {
274        self.rec(g).map_or(0, |r| u16_at(self.data, r) as i32)
275    }
276
277    /// kern(l, r) = matrix[lclass[l] * kernR + rclass[r]] * 4 font units
278    pub fn kern(&self, g1: u32, g2: u32) -> i32 {
279        if self.flags & SGH_KERN == 0 || g1 >= self.nglyphs || g2 >= self.nglyphs {
280            return 0;
281        }
282        let n = self.nglyphs as usize;
283        let k = self.off_kern;
284        let lc = self.data[k + g1 as usize] as u32;
285        let rc = self.data[k + n + g2 as usize] as u32;
286        if lc == 0 || rc == 0 {
287            return 0;
288        }
289        let m = (lc * self.kern_r + rc) as usize;
290        (self.data[k + 2 * n + m] as i8 as i32) << 2
291    }
292
293    pub fn glyph_box(&self, g: u32) -> Option<[i32; 4]> {
294        let r = self.rec(g)?;
295        if u16_at(self.data, r + 10) == 0 {
296            return None;
297        }
298        Some([
299            i16_at(self.data, r + 2),
300            i16_at(self.data, r + 4),
301            i16_at(self.data, r + 6),
302            i16_at(self.data, r + 8),
303        ])
304    }
305
306    // gp_point: font units + composite offset -> float pixels (y up -> y down)
307    fn point(gp: &Gp, fx: i32, fy: i32) -> (f32, f32) {
308        let x = (fx.wrapping_add(gp.dx)) as f32 * gp.sx + gp.x;
309        let y = gp.y - (fy.wrapping_add(gp.dy)) as f32 * gp.sy;
310        (x, y)
311    }
312
313    /// glyph_path: append the outline transformed by gp (implied on-curve midpoints,
314    /// composites recursively)
315    pub fn path(&self, g: u32, gp: &mut Gp, path: &mut Path, depth: u32) {
316        let Some(r) = self.rec(g) else { return };
317        let n = u16_at(self.data, r + 10) as usize;
318        if n == 0 || depth > 64 {
319            return;
320        }
321        let ol = self.off_outlines + u32_at(self.data, r + 12) as usize;
322        if n & 0x8000 != 0 {
323            for c in 0..n & 0x7FFF {
324                let o = ol + c * 8;
325                let (sdx, sdy) = (gp.dx, gp.dy);
326                gp.dx = gp.dx.wrapping_add(i16_at(self.data, o + 2));
327                gp.dy = gp.dy.wrapping_add(i16_at(self.data, o + 4));
328                self.path(u16_at(self.data, o) as u32, gp, path, depth + 1);
329                gp.dx = sdx;
330                gp.dy = sdy;
331            }
332            return;
333        }
334        let pts = ol + ((n + n + 3) & !3);
335        let on = |i: usize| u16_at(self.data, pts + i * 4) & 1 != 0;
336        let load = |i: usize| {
337            let x = i16_at(self.data, pts + i * 4) >> 1;
338            let y = i16_at(self.data, pts + i * 4 + 2);
339            Self::point(gp, x, y)
340        };
341        let mut first = 0usize;
342        for c in 0..n {
343            let last = u16_at(self.data, ol + c * 2) as usize;
344            if last < first {
345                first = last + 1;
346                continue;
347            }
348            let count = last - first + 1;
349            // start: the first on-curve point of first / last, else their midpoint
350            let (start, mut next, mut left);
351            if on(first) {
352                start = load(first);
353                next = first + 1;
354                left = count - 1;
355            } else if on(last) {
356                start = load(last);
357                next = first;
358                left = count - 1;
359            } else {
360                let a = load(last);
361                let b = load(first);
362                start = ((b.0 + a.0) * 0.5, (b.1 + a.1) * 0.5);
363                next = first;
364                left = count;
365            }
366            path.move_f(start.0, start.1);
367            let mut pending: Option<(f32, f32)> = None;
368            while left > 0 {
369                left -= 1;
370                let i = next;
371                next += 1;
372                let p = load(i);
373                if on(i) {
374                    match pending.take() {
375                        Some(c) => path.quad_f(c.0, c.1, p.0, p.1),
376                        None => path.line_f(p.0, p.1),
377                    }
378                } else {
379                    if let Some(c) = pending {
380                        // two off-curve points: quad to their midpoint
381                        path.quad_f(c.0, c.1, (p.0 + c.0) * 0.5, (p.1 + c.1) * 0.5);
382                    }
383                    pending = Some(p);
384                }
385            }
386            if let Some(c) = pending {
387                path.quad_f(c.0, c.1, start.0, start.1);
388            }
389            path.close();
390            first = last + 1;
391        }
392    }
393
394    /// append glyph g (composites resolved) to the outline buffer: points in font units +
395    /// (dx, dy), one contour per SGF1 contour  [sgf_outline]
396    pub fn outline(&self, g: u32, dx: i32, dy: i32, depth: u32, ol: &mut Outline) {
397        if depth > 16 {
398            return;
399        }
400        let Some(r) = self.rec(g) else { return };
401        let n = u16_at(self.data, r + 10) as usize;
402        if n == 0 {
403            return;
404        }
405        let o = self.off_outlines + u32_at(self.data, r + 12) as usize;
406        if n & 0x8000 != 0 {
407            for c in 0..n & 0x7FFF {
408                let e = o + c * 8;
409                let cdx = i16_at(self.data, e + 2).wrapping_add(dx);
410                let cdy = i16_at(self.data, e + 4).wrapping_add(dy);
411                self.outline(u16_at(self.data, e) as u32, cdx, cdy, depth + 1, ol);
412            }
413            return;
414        }
415        let pts = o + ((n + n + 3) & !3);
416        let mut pt = 0usize;
417        for c in 0..n {
418            let last = u16_at(self.data, o + c * 2) as usize;
419            while pt <= last {
420                let v = i16_at(self.data, pts + pt * 4);
421                let tag = (v & 1) as u8;
422                let x = (v >> 1).wrapping_add(dx) as f32;
423                let y = i16_at(self.data, pts + pt * 4 + 2).wrapping_add(dy) as f32;
424                ol.point(x, y, tag);
425                pt += 1;
426            }
427            ol.close();
428        }
429    }
430}