font.bend

gralha-truetype@0.1.0.0 · 831 lines · 0 definitions

1# Font book: glyph outlines, advances, kerning and metrics.
2import Base
3import gralha-text-scan@0.1.0.0/scan.bend as U
4import gralha-bytes@0.1.0.0/bytes.bend as Bin
5
6type TTFont is Data:
7  TTFont{glyf: U32, loca: U32, long: Bool, hmtx: U32, metrics: U32, glyphs: U32, units: F32}
8
9type TTPairs is Data:
10  TTPairs{specific: List<&2, U32>, classes: U32, coverage: U32, class1: U32, class2: U32}
11
12# A TrueType face stays as its bytes plus table offsets; glyphs and kerning
13# are read from it on demand (see font.glyph and font.kern at the end).
14type TTFace is Data:
15  TTFace{bytes: Bin.Bytes, font: TTFont, pairs: TTPairs, cmap: U32}
16
17type FontGlyph is Data:
18  FontGlyph{advance: F32, outline: String, glyph: U32, leftClass: U32, rightClass: U32}
19
20# The glyph, kerning and metric maps hold text-format fonts (font.parse) and
21# every face's metrics; faces holds the lazily read TrueType faces by number.
22type FontBook is Data:
23  FontBook{glyphs: Map<&2, FontGlyph>, kerning: Map<&2, F32>, metrics: Map<&2, F32>, faces: Map<&2, TTFace>}
24
25def font.empty() -> FontBook:
26  FontBook{MTip{}, MTip{}, MTip{}, MTip{}}
27
28def font.missing() -> FontGlyph:
29  FontGlyph{600.0, "M94 0V714H505V0ZM151 57H448V657H151Z", 0, 0, 0}
30
31def font.glyph.result(got: Map<&2, FontGlyph> & FontGlyph) -> FontGlyph:
32  (map, glyph) = got
33  glyph
34
35def font.number.result(got: Map<&2, F32> & F32) -> F32:
36  (map, val) = got
37  val
38
39def font.metric(book: FontBook, face: U32, name: String, fallback: F32) -> F32:
40  FontBook{glyphs, kerning, faceMetrics, faces} = book
41  font.number.result(Map.get(F32, fallback, faceMetrics, U32.show(face) ++ ":" ++ name))
42
43def font.glyph.map(+glyphs: Map<&2, FontGlyph>, +face: U32, ch: U32) -> FontGlyph:
44  font.glyph.result(Map.get(FontGlyph, font.glyph.result(Map.get(FontGlyph, font.missing(), glyphs, U32.show(face) ++ ":?")), glyphs, U32.show(face) ++ ":" ++ U32.show(ch)))
45
46def font.kern.value(+specific: F32, fallback: Unit -> F32) -> F32:
47  U.branch(F32, F32.is_eq(specific, 1000000.0), _ => fallback(Unit{}), _ => specific)
48
49def font.kern.map(+kerning: Map<&2, F32>, +face: U32, left: U32, leftClass: U32, right: U32, rightClass: U32) -> F32:
50  font.kern.value(font.number.result(Map.get(F32, 1000000.0, kerning, U32.show(face) ++ ":" ++ U32.show((16777216 + left * 4096 + right : U32)))),
51    _ => font.number.result(Map.get(F32, 0.0, kerning, U32.show(face) ++ ":" ++ U32.show((leftClass * 128 + rightClass : U32)))))
52
53def font.parse.glyph(key: String, scan: U.Scan, book: FontBook) -> FontBook:
54  U.Scan{metrics, outline} = scan
55  FontBook{glyphs, kerning, faceMetrics, faces} = book
56  +vs = U.numbers(metrics)
57  FontBook{Map.set(&2, FontGlyph, glyphs, key, FontGlyph{U.nth(vs, 0n, 600.0), outline,
58    F32.to_u32(U.nth(vs, 1n, 0.0)), F32.to_u32(U.nth(vs, 2n, 0.0)), F32.to_u32(U.nth(vs, 3n, 0.0))}), kerning, faceMetrics, faces}
59
60def font.parse.record(kind: String, +scan: U.Scan, +book: FontBook) -> FontBook:
61  U.Scan{key, body} = scan
62  FontBook{glyphs, kerning, faceMetrics, faces} = book
63  +tag = kind
64  U.branch(FontBook, U.string.equal(tag, "g"), _ => font.parse.glyph(key, U.until(body, 124, ""), book),
65    _ => U.branch(FontBook, U.string.equal(tag, "k"), _ => FontBook{glyphs, Map.set(&2, F32, kerning, key, U.scalar(body, 0.0)), faceMetrics, faces}, _ => U.branch(FontBook, U.string.equal(tag, "m"), _ => FontBook{glyphs, kerning, Map.set(&2, F32, faceMetrics, key, U.scalar(body, 0.0)), faces}, _ => book)))
66
67def font.parse.row(scan: U.Scan, book: FontBook) -> FontBook:
68  U.Scan{kind, body} = scan
69  font.parse.record(kind, U.until(body, 124, ""), book)
70
71def font.parse.line(scan: U.Scan, book: FontBook, next: String -> FontBook -> FontBook) -> FontBook:
72  U.Scan{line, tail} = scan
73  next(tail)(font.parse.row(U.until(line, 124, ""), book))
74
75@unsafe
76def font.parse(s: String, book: FontBook) -> FontBook:
77  match s:
78    case SNil{}: book
79    case SCon{c, t}: font.parse.line(U.until(SCon{c, t}, 10, ""), book, rest => fonts => font.parse(rest, fonts))
80# ---- TrueType reader ----
81# Reads the table directory, glyf/loca/hmtx, cmap, OS/2 and GPOS of one face:
82# outlines as SVG path data in font units (y up), advances, GPOS pair kerning
83# (every format 1 subtable and the first format 2 subtable of the first pair
84# lookup) and OS/2 metrics normalized by units per em. Glyphs and kerning are
85# resolved when asked for, from the face's bytes. CFF outlines, collections,
86# WOFF wrappers and the legacy kern table are not read.
87
88def ttf.u8(+b: Bin.Bytes, +at: U32) -> U32:
89  Bin.bytes.get(b, at)
90
91def ttf.u16(+b: Bin.Bytes, +at: U32) -> U32:
92  Bin.bytes.be(b, at, 2n, 0)
93
94def ttf.u32(+b: Bin.Bytes, +at: U32) -> U32:
95  Bin.bytes.be(b, at, 4n, 0)
96
97def ttf.i16(+b: Bin.Bytes, +at: U32) -> F32:
98  +v = ttf.u16(b, at)
99  U.choose(F32, U32.is_ge(v, 32768), (U32.to_f32(v) - 65536.0 : F32), U32.to_f32(v))
100
101def ttf.i8(+b: Bin.Bytes, +at: U32) -> F32:
102  +v = ttf.u8(b, at)
103  U.choose(F32, U32.is_ge(v, 128), (U32.to_f32(v) - 256.0 : F32), U32.to_f32(v))
104
105def ttf.f2dot14(+b: Bin.Bytes, +at: U32) -> F32:
106  (ttf.i16(b, at) / 16384.0 : F32)
107
108def ttf.bit(+flags: U32, +mask: U32) -> Bool:
109  U32.is_ne(U32.and(flags, mask), 0)
110
111def ttf.table.find(+b: Bin.Bytes, n: Nat, +at: U32, +tag: U32) -> U32:
112  match n:
113    case 0n: 0
114    case 1n+d: U.branch(U32, U32.is_eq(ttf.u32(b, at), tag), _ => ttf.u32(b, (at + 8 : U32)), _ => ttf.table.find(b, d, (at + 16 : U32), tag))
115
116# Table offset by tag, read as a big-endian word; 0 when absent.
117def ttf.table(+b: Bin.Bytes, +tag: U32) -> U32:
118  ttf.table.find(b, U32.to_nat(ttf.u16(b, 4)), 12, tag)
119
120def ttf.font(+b: Bin.Bytes) -> TTFont:
121  +head = ttf.table(b, 1751474532)
122  +maxp = ttf.table(b, 1835104368)
123  +hhea = ttf.table(b, 1751672161)
124  TTFont{ttf.table(b, 1735162214), ttf.table(b, 1819239265), U32.is_ne(ttf.u16(b, (head + 50 : U32)), 0), ttf.table(b, 1752003704),
125    ttf.u16(b, (hhea + 34 : U32)), ttf.u16(b, (maxp + 4 : U32)), U32.to_f32(ttf.u16(b, (head + 18 : U32)))}
126
127def ttf.valid(+f: TTFont) -> Bool:
128  TTFont{glyf, loca, long, hmtx, metrics, glyphs, units} = f
129  U32.is_ne(glyf, 0) && U32.is_ne(loca, 0) && U32.is_ne(hmtx, 0) && U32.is_gt(metrics, 0) && F32.is_gt(units, 0.0)
130
131def ttf.units(+f: TTFont) -> F32:
132  TTFont{glyf, loca, long, hmtx, metrics, glyphs, units} = f
133  units
134
135def ttf.glyf(+f: TTFont) -> U32:
136  TTFont{glyf, loca, long, hmtx, metrics, glyphs, units} = f
137  glyf
138
139def ttf.advance(+b: Bin.Bytes, +f: TTFont, +gid: U32) -> F32:
140  TTFont{glyf, loca, long, hmtx, metrics, glyphs, units} = f
141  +m = metrics
142  +i = U.choose(U32, U32.is_lt(gid, m), gid, (m - 1 : U32))
143  U32.to_f32(ttf.u16(b, (hmtx + i * 4 : U32)))
144
145# Absolute offset of a glyph's data and its byte length (0 for empty or invalid glyphs).
146def ttf.location(+b: Bin.Bytes, +f: TTFont, +gid: U32) -> U32 & U32:
147  TTFont{glyf, loca, long, hmtx, metrics, glyphs, units} = f
148  +lo = loca
149  +lg = long
150  +a = U.choose(U32, lg, ttf.u32(b, (lo + gid * 4 : U32)), (ttf.u16(b, (lo + gid * 2 : U32)) * 2 : U32))
151  +c = U.choose(U32, lg, ttf.u32(b, (lo + gid * 4 + 4 : U32)), (ttf.u16(b, (lo + gid * 2 + 2 : U32)) * 2 : U32))
152  ((glyf + a : U32), U.choose(U32, U32.is_lt(gid, glyphs) && U32.is_gt(c, a), (c - a : U32), 0))
153
154type TTPoint is Data:
155  TTPoint{x: F32, y: F32, on: Bool}
156
157# Flat point list with the last point index of every contour.
158type TTGlyph is Data:
159  TTGlyph{points: List<&2, TTPoint>, ends: List<&2, U32>, count: U32}
160
161def ttf.glyph.empty() -> TTGlyph:
162  TTGlyph{Nil{}, Nil{}, 0}
163
164def ttf.glyph.points(+g: TTGlyph) -> List<&2, TTPoint>:
165  TTGlyph{points, ends, count} = g
166  points
167
168def ttf.ends(+b: Bin.Bytes, n: Nat, +at: U32) -> List<&2, U32>:
169  match n:
170    case 0n: Nil{}
171    case 1n+d: Con{ttf.u16(b, at), ttf.ends(b, d, (at + 2 : U32))}
172
173def ttf.last(xs: List<&2, U32>, +fallback: U32) -> U32:
174  match xs:
175    case Nil{}: fallback
176    case Con{h, t}: ttf.last(t, h)
177
178def ttf.after.flag(+f: U32, got: List<&2, U32> & U32) -> List<&2, U32> & U32:
179  (rest, next) = got
180  (Con{f, rest}, next)
181
182# Point flags with repeat counts expanded; returns the flags and the next byte offset.
183def ttf.flags(+b: Bin.Bytes, n: Nat, +at: U32, +pending: U32, +last: U32) -> List<&2, U32> & U32:
184  match n:
185    case 0n: (Nil{}, at)
186    case 1n+d:
187      +dd = d
188      +f = ttf.u8(b, at)
189      U.branch(List<&2, U32> & U32, U32.is_gt(pending, 0),
190        _ => ttf.after.flag(last, ttf.flags(b, dd, at, (pending - 1 : U32), last)),
191        _ => U.branch(List<&2, U32> & U32, ttf.bit(f, 8),
192          _ => ttf.after.flag(f, ttf.flags(b, dd, (at + 2 : U32), ttf.u8(b, (at + 1 : U32)), f)),
193          _ => ttf.after.flag(f, ttf.flags(b, dd, (at + 1 : U32), 0, f))))
194
195def ttf.after.coord(+v: F32, got: List<&2, F32> & U32) -> List<&2, F32> & U32:
196  (rest, next) = got
197  (Con{v, rest}, next)
198
199# One coordinate axis: short bytes with a sign flag, repeated values, or signed word deltas.
200def ttf.coords(+b: Bin.Bytes, flags: List<&2, U32>, +at: U32, +prev: F32, +short: U32, +same: U32) -> List<&2, F32> & U32:
201  match flags:
202    case Nil{}: (Nil{}, at)
203    case Con{f, rest}:
204      +fl = f
205      +isShort = ttf.bit(fl, short)
206      +isSame = ttf.bit(fl, same)
207      +v = U.branch(F32, isShort, _ => (prev + U.choose(F32, isSame, 1.0, F32.neg(1.0)) * U32.to_f32(ttf.u8(b, at)) : F32),
208        _ => U.branch(F32, isSame, _ => prev, _ => (prev + ttf.i16(b, at) : F32)))
209      ttf.after.coord(v, ttf.coords(b, rest, (at + U.choose(U32, isShort, 1, U.choose(U32, isSame, 0, 2)) : U32), v, short, same))
210
211def ttf.points(flags: List<&2, U32>, xs: List<&2, F32>, ys: List<&2, F32>) -> List<&2, TTPoint>:
212  match flags xs ys:
213    case Con{f, fr} Con{x, xr} Con{y, yr}: Con{TTPoint{x, y, ttf.bit(f, 1)}, ttf.points(fr, xr, yr)}
214    case _ _ _: Nil{}
215
216def ttf.simple.y(flags: List<&2, U32>, xs: List<&2, F32>, ends: List<&2, U32>, +count: U32, got: List<&2, F32> & U32) -> TTGlyph:
217  (ys, next) = got
218  TTGlyph{ttf.points(flags, xs, ys), ends, count}
219
220def ttf.simple.x(+b: Bin.Bytes, +flags: List<&2, U32>, ends: List<&2, U32>, +count: U32, got: List<&2, F32> & U32) -> TTGlyph:
221  (xs, next) = got
222  ttf.simple.y(flags, xs, ends, count, ttf.coords(b, flags, next, 0.0, 4, 32))
223
224def ttf.simple.flags(+b: Bin.Bytes, ends: List<&2, U32>, +count: U32, got: List<&2, U32> & U32) -> TTGlyph:
225  (flags, next) = got
226  +fl = flags
227  ttf.simple.x(b, fl, ends, count, ttf.coords(b, fl, next, 0.0, 2, 16))
228
229# A simple glyph at absolute offset g with the given contour count.
230def ttf.simple(+b: Bin.Bytes, +g: U32, +contours: U32) -> TTGlyph:
231  +ends = ttf.ends(b, U32.to_nat(contours), (g + 10 : U32))
232  +count = U.choose(U32, U32.is_gt(contours, 0), (ttf.last(ends, 0) + 1 : U32), 0)
233  +instructions = ttf.u16(b, (g + 10 + contours * 2 : U32))
234  ttf.simple.flags(b, ends, count, ttf.flags(b, U32.to_nat(count), (g + 12 + contours * 2 + instructions : U32), 0, 0))
235
236def ttf.point(pts: List<&2, TTPoint>, i: Nat) -> TTPoint:
237  match pts i:
238    case Nil{} _: TTPoint{0.0, 0.0, True{}}
239    case Con{h, t} 0n: h
240    case Con{h, t} 1n+p: ttf.point(t, p)
241
242def ttf.transform(pts: List<&2, TTPoint>, +a: F32, +bb: F32, +c: F32, +d: F32, +dx: F32, +dy: F32) -> List<&2, TTPoint>:
243  match pts:
244    case Nil{}: Nil{}
245    case Con{TTPoint{x, y, on}, rest}:
246      +px = x
247      +py = y
248      Con{TTPoint{(a * px + c * py + dx : F32), (bb * px + d * py + dy : F32), on}, ttf.transform(rest, a, bb, c, d, dx, dy)}
249
250def ttf.append.points(xs: List<&2, TTPoint>, ys: List<&2, TTPoint>) -> List<&2, TTPoint>:
251  match xs:
252    case Nil{}: ys
253    case Con{h, t}: Con{h, ttf.append.points(t, ys)}
254
255def ttf.shift.ends(ys: List<&2, U32>, +offset: U32) -> List<&2, U32>:
256  match ys:
257    case Nil{}: Nil{}
258    case Con{h, t}: Con{(h + offset : U32), ttf.shift.ends(t, offset)}
259
260def ttf.append.ends(xs: List<&2, U32>, ys: List<&2, U32>, +offset: U32) -> List<&2, U32>:
261  match xs:
262    case Nil{}: ttf.shift.ends(ys, offset)
263    case Con{h, t}: Con{h, ttf.append.ends(t, ys, offset)}
264
265def ttf.merge(parent: TTGlyph, child: TTGlyph) -> TTGlyph:
266  TTGlyph{ps, pe, pc} = parent
267  TTGlyph{cs, ce, cc} = child
268  +n = pc
269  TTGlyph{ttf.append.points(ps, cs), ttf.append.ends(pe, ce, n), (n + cc : U32)}
270
271def ttf.xy(p: TTPoint) -> F32 & F32:
272  TTPoint{x, y, on} = p
273  (x, y)
274
275def ttf.matched(pp: F32 & F32, cp: F32 & F32) -> F32 & F32:
276  (px, py) = pp
277  (cx, cy) = cp
278  ((px - cx : F32), (py - cy : F32))
279
280# A component's placement: an affine 2x2 with either an explicit offset or two
281# matched point indices (parent point a1 meets child point a2).
282type TTPlace is Data:
283  TTPlace{xy: Bool, a1: F32, a2: F32, scaledOffset: Bool, a: F32, b: F32, c: F32, d: F32}
284
285def ttf.place.identity() -> TTPlace:
286  TTPlace{True{}, 0.0, 0.0, False{}, 1.0, 0.0, 0.0, 1.0}
287
288def ttf.place.offset(+parent: TTGlyph, +child: TTGlyph, +place: TTPlace) -> F32 & F32:
289  TTPlace{xy, a1, a2, scaledOffset, a, bb, c, d} = place
290  +a2Dup = a2
291  +a1Dup = a1
292  U.branch(F32 & F32, xy,
293    _ => U.choose(F32 & F32, scaledOffset, ((a * a1Dup + c * a2Dup : F32), (bb * a1Dup + d * a2Dup : F32)), (a1Dup, a2Dup)),
294    _ => ttf.matched(ttf.xy(ttf.point(ttf.glyph.points(parent), U32.to_nat(F32.to_u32(a1Dup)))), ttf.xy(ttf.point(ttf.glyph.points(child), U32.to_nat(F32.to_u32(a2Dup))))))
295
296def ttf.place.scaled(child: TTGlyph, +place: TTPlace) -> TTGlyph:
297  TTGlyph{points, ends, count} = child
298  TTPlace{xy, a1, a2, scaledOffset, a, bb, c, d} = place
299  TTGlyph{ttf.transform(points, a, bb, c, d, 0.0, 0.0), ends, count}
300
301def ttf.place.shifted(child: TTGlyph, off: F32 & F32) -> TTGlyph:
302  TTGlyph{points, ends, count} = child
303  (dx, dy) = off
304  TTGlyph{ttf.transform(points, 1.0, 0.0, 0.0, 1.0, dx, dy), ends, count}
305
306# Scale the child, then offset it (explicitly or by point matching) and append it to the parent.
307def ttf.place(+parent: TTGlyph, +child: TTGlyph, +place: TTPlace) -> TTGlyph:
308  +scaled = ttf.place.scaled(child, place)
309  ttf.merge(parent, ttf.place.shifted(scaled, ttf.place.offset(parent, scaled, place)))
310
311# A composite being assembled: the next component offset (0 once all are read),
312# the points so far and how the finished glyph is placed into its parent.
313type TTFrame is Data:
314  TTFrame{at: U32, acc: TTGlyph, place: TTPlace}
315
316def ttf.component.place(+b: Bin.Bytes, +at: U32, +flags: U32) -> TTPlace:
317  +words = ttf.bit(flags, 1)
318  +xy = ttf.bit(flags, 2)
319  +a1 = U.choose(F32, words, U.choose(F32, xy, ttf.i16(b, (at + 4 : U32)), U32.to_f32(ttf.u16(b, (at + 4 : U32)))), U.choose(F32, xy, ttf.i8(b, (at + 4 : U32)), U32.to_f32(ttf.u8(b, (at + 4 : U32)))))
320  +a2 = U.choose(F32, words, U.choose(F32, xy, ttf.i16(b, (at + 6 : U32)), U32.to_f32(ttf.u16(b, (at + 6 : U32)))), U.choose(F32, xy, ttf.i8(b, (at + 5 : U32)), U32.to_f32(ttf.u8(b, (at + 5 : U32)))))
321  +at2 = (at + U.choose(U32, words, 8, 6) : U32)
322  +one = ttf.bit(flags, 8)
323  +two = ttf.bit(flags, 64)
324  +four = ttf.bit(flags, 128)
325  +sa = U.choose(F32, one || two || four, ttf.f2dot14(b, at2), 1.0)
326  TTPlace{xy, a1, a2, ttf.bit(flags, 2048), sa,
327    U.choose(F32, four, ttf.f2dot14(b, (at2 + 2 : U32)), 0.0),
328    U.choose(F32, four, ttf.f2dot14(b, (at2 + 4 : U32)), 0.0),
329    U.choose(F32, one, sa, U.choose(F32, two, ttf.f2dot14(b, (at2 + 2 : U32)), U.choose(F32, four, ttf.f2dot14(b, (at2 + 6 : U32)), 1.0)))}
330
331# Byte offset of the component after the one at `at`, or 0 when it was the last.
332def ttf.component.next(+at: U32, +flags: U32) -> U32:
333  +at2 = (at + U.choose(U32, ttf.bit(flags, 1), 8, 6) : U32)
334  +at3 = (at2 + U.choose(U32, ttf.bit(flags, 8), 2, U.choose(U32, ttf.bit(flags, 64), 4, U.choose(U32, ttf.bit(flags, 128), 8, 0))) : U32)
335  U.choose(U32, ttf.bit(flags, 32), at3, 0)
336
337def ttf.frame.at(+frame: TTFrame) -> U32:
338  TTFrame{at, acc, place} = frame
339  at
340
341def ttf.frame.acc(+frame: TTFrame) -> TTGlyph:
342  TTFrame{at, acc, place} = frame
343  acc
344
345# Pop a finished frame into its parent frame.
346def ttf.assemble.pop(frame: TTFrame, parents: List<&2, TTFrame>) -> List<&2, TTFrame>:
347  TTFrame{at, acc, place} = frame
348  match parents:
349    case Nil{}: Con{TTFrame{0, acc, place}, Nil{}}
350    case Con{TTFrame{pat, pacc, pplace}, rest}: Con{TTFrame{pat, ttf.place(pacc, acc, place), pplace}, rest}
351
352def ttf.assemble.child(+b: Bin.Bytes, +f: TTFont, frame: TTFrame, parents: List<&2, TTFrame>, loc: U32 & U32, +place: TTPlace, +next: U32) -> List<&2, TTFrame>:
353  TTFrame{at, acc, fplace} = frame
354  (g, len) = loc
355  +gg = g
356  +ac = acc
357  +fp = fplace
358  +ps = parents
359  +contours = ttf.u16(b, gg)
360  U.branch(List<&2, TTFrame>, U32.is_eq(len, 0), _ => Con{TTFrame{next, ac, fp}, ps},
361    _ => U.branch(List<&2, TTFrame>, U32.is_ge(contours, 32768),
362      _ => Con{TTFrame{(gg + 10 : U32), ttf.glyph.empty(), place}, Con{TTFrame{next, ac, fp}, ps}},
363      _ => Con{TTFrame{next, ttf.place(ac, ttf.simple(b, gg, contours), place), fp}, ps}))
364
365# Read the component at the frame's offset: a simple child is placed at once, a
366# composite child opens a new frame above the advanced parent.
367def ttf.assemble.step(+b: Bin.Bytes, +f: TTFont, frame: TTFrame, parents: List<&2, TTFrame>) -> List<&2, TTFrame>:
368  TTFrame{at, acc, place} = frame
369  +a = at
370  +flags = ttf.u16(b, a)
371  ttf.assemble.child(b, f, TTFrame{a, acc, place}, parents, ttf.location(b, f, ttf.u16(b, (a + 2 : U32))), ttf.component.place(b, a, flags), ttf.component.next(a, flags))
372
373def ttf.assemble.next(+b: Bin.Bytes, +f: TTFont, stack: List<&2, TTFrame>) -> List<&2, TTFrame>:
374  match stack:
375    case Nil{}: Nil{}
376    case Con{frame, parents}:
377      +parentsDup = parents
378      +fr = frame
379      U.branch(List<&2, TTFrame>, U32.is_eq(ttf.frame.at(fr), 0), _ => ttf.assemble.pop(fr, parentsDup), _ => ttf.assemble.step(b, f, fr, parentsDup))
380
381def ttf.stack.top(stack: List<&2, TTFrame>) -> TTGlyph:
382  match stack:
383    case Nil{}: ttf.glyph.empty()
384    case Con{frame, parents}: ttf.frame.acc(frame)
385
386def ttf.stack.done(stack: List<&2, TTFrame>) -> Bool:
387  match stack:
388    case Nil{}: True{}
389    case Con{frame, Nil{}}: U32.is_eq(ttf.frame.at(frame), 0)
390    case Con{frame, Con{parent, rest}}: False{}
391
392# Iterative composite assembly over an explicit frame stack; fuel bounds the component count.
393def ttf.assemble(+b: Bin.Bytes, +f: TTFont, fuel: Nat, +stack: List<&2, TTFrame>) -> TTGlyph:
394  match fuel:
395    case 0n: ttf.stack.top(stack)
396    case 1n+d: U.branch(TTGlyph, ttf.stack.done(stack), _ => ttf.stack.top(stack), _ => ttf.assemble(b, f, d, ttf.assemble.next(b, f, stack)))
397
398def ttf.glyph.at(+b: Bin.Bytes, +f: TTFont, loc: U32 & U32) -> TTGlyph:
399  (g, len) = loc
400  +gg = g
401  +contours = ttf.u16(b, gg)
402  U.branch(TTGlyph, U32.is_eq(len, 0), _ => ttf.glyph.empty(),
403    _ => U.branch(TTGlyph, U32.is_ge(contours, 32768),
404      _ => ttf.assemble(b, f, 256n, Con{TTFrame{(gg + 10 : U32), ttf.glyph.empty(), ttf.place.identity()}, Nil{}}),
405      _ => ttf.simple(b, gg, contours)))
406
407def ttf.glyph(+b: Bin.Bytes, +f: TTFont, +gid: U32) -> TTGlyph:
408  ttf.glyph.at(b, f, ttf.location(b, f, gid))
409
410# ---- outline emission (M/L/Q/Z in font units, implied on-curve midpoints inserted) ----
411
412def ttf.pair(p: TTPoint) -> String:
413  TTPoint{x, y, on} = p
414  F32.show(x) ++ " " ++ F32.show(y)
415
416def ttf.mid(a: TTPoint, b: TTPoint) -> TTPoint:
417  TTPoint{ax, ay, aon} = a
418  TTPoint{bx, by, bon} = b
419  TTPoint{((ax + bx) / 2.0 : F32), ((ay + by) / 2.0 : F32), True{}}
420
421def ttf.walk.on(p: TTPoint, pending: Maybe<&2, TTPoint>) -> String:
422  match pending:
423    case None{}: "L" ++ ttf.pair(p)
424    case Some{c}: "Q" ++ ttf.pair(c) ++ " " ++ ttf.pair(p)
425
426def ttf.walk.between(c: TTPoint, p: TTPoint) -> String:
427  TTPoint{cx, cy, con} = c
428  +x = cx
429  +y = cy
430  "Q" ++ ttf.pair(TTPoint{x, y, False{}}) ++ " " ++ ttf.pair(ttf.mid(TTPoint{x, y, False{}}, p))
431
432# An off-curve point after another emits the implied on-curve midpoint between them.
433def ttf.walk.off(p: TTPoint, pending: Maybe<&2, TTPoint>) -> String:
434  match pending:
435    case None{}: ""
436    case Some{c}: ttf.walk.between(c, p)
437
438def ttf.walk.close(pending: Maybe<&2, TTPoint>, +start: TTPoint) -> String:
439  match pending:
440    case None{}: "Z"
441    case Some{c}: "Q" ++ ttf.pair(c) ++ " " ++ ttf.pair(start) ++ "Z"
442
443def ttf.walk(pts: List<&2, TTPoint>, +pending: Maybe<&2, TTPoint>, +start: TTPoint) -> String:
444  match pts:
445    case Nil{}: ttf.walk.close(pending, start)
446    case Con{TTPoint{x, y, on}, rest}:
447      +px = x
448      +py = y
449      +r = rest
450      U.branch(String, on,
451        _ => ttf.walk.on(TTPoint{px, py, True{}}, pending) ++ ttf.walk(r, None{}, start),
452        _ => ttf.walk.off(TTPoint{px, py, False{}}, pending) ++ ttf.walk(r, Some{TTPoint{px, py, False{}}}, start))
453
454def ttf.init(pts: List<&2, TTPoint>) -> List<&2, TTPoint>:
455  match pts:
456    case Nil{}: Nil{}
457    case Con{h, Nil{}}: Nil{}
458    case Con{h, Con{g, t}}: Con{h, ttf.init(Con{g, t})}
459
460def ttf.last.point(pts: List<&2, TTPoint>, fallback: TTPoint) -> TTPoint:
461  match pts:
462    case Nil{}: fallback
463    case Con{h, t}: ttf.last.point(t, h)
464
465def ttf.contour.implied(+start: TTPoint, first: TTPoint, rest: List<&2, TTPoint>) -> String:
466  "M" ++ ttf.pair(start) ++ ttf.walk(Con{first, rest}, None{}, start)
467
468# A contour starts at its first on-curve point; when the first point is off-curve,
469# the last point starts it if on-curve, else the implied midpoint between last and first.
470def ttf.contour.last(+first: TTPoint, +rest: List<&2, TTPoint>, last: TTPoint) -> String:
471  TTPoint{lx, ly, lon} = last
472  +x = lx
473  +y = ly
474  U.branch(String, lon,
475    _ => "M" ++ ttf.pair(TTPoint{x, y, True{}}) ++ ttf.walk(Con{first, ttf.init(rest)}, None{}, TTPoint{x, y, True{}}),
476    _ => ttf.contour.implied(ttf.mid(first, TTPoint{x, y, False{}}), first, rest))
477
478def ttf.contour.off(+first: TTPoint, +rest: List<&2, TTPoint>) -> String:
479  ttf.contour.last(first, rest, ttf.last.point(rest, first))
480
481def ttf.contour(pts: List<&2, TTPoint>) -> String:
482  match pts:
483    case Nil{}: ""
484    case Con{TTPoint{x, y, on}, rest}:
485      +fx = x
486      +fy = y
487      +r = rest
488      U.branch(String, on,
489        _ => "M" ++ ttf.pair(TTPoint{fx, fy, True{}}) ++ ttf.walk(r, None{}, TTPoint{fx, fy, True{}}),
490        _ => ttf.contour.off(TTPoint{fx, fy, False{}}, r))
491
492def ttf.take(n: Nat, pts: List<&2, TTPoint>) -> List<&2, TTPoint>:
493  match n pts:
494    case 0n _: Nil{}
495    case 1n+d Nil{}: Nil{}
496    case 1n+d Con{h, t}: Con{h, ttf.take(d, t)}
497
498def ttf.drop(n: Nat, pts: List<&2, TTPoint>) -> List<&2, TTPoint>:
499  match n pts:
500    case 0n _: pts
501    case 1n+d Nil{}: Nil{}
502    case 1n+d Con{h, t}: ttf.drop(d, t)
503
504@unsafe
505def ttf.contours(+pts: List<&2, TTPoint>, ends: List<&2, U32>, +index: U32) -> String:
506  match ends:
507    case Nil{}: ""
508    case Con{e, more}:
509      +eDup = e
510      +n = U32.to_nat((eDup + 1 - index : U32))
511      ttf.contour(ttf.take(n, pts)) ++ ttf.contours(ttf.drop(n, pts), more, (eDup + 1 : U32))
512
513def ttf.outline(g: TTGlyph) -> String:
514  TTGlyph{points, ends, count} = g
515  ttf.contours(points, ends, 0)
516
517# ---- GPOS pair positioning ----
518
519# Format 1 subtables list specific glyph pairs; the first format 2 subtable supplies classes.
520def ttf.pairs.empty() -> TTPairs:
521  TTPairs{Nil{}, 0, 0, 0, 0}
522
523def ttf.pairs.add1(+sub: U32, acc: TTPairs) -> TTPairs:
524  TTPairs{specific, classes, coverage, class1, class2} = acc
525  TTPairs{Con{sub, specific}, classes, coverage, class1, class2}
526
527def ttf.pairs.add2(+b: Bin.Bytes, +sub: U32, acc: TTPairs) -> TTPairs:
528  TTPairs{specific, classes, coverage, class1, class2} = acc
529  TTPairs{specific, sub, (sub + ttf.u16(b, (sub + 2 : U32)) : U32), (sub + ttf.u16(b, (sub + 8 : U32)) : U32), (sub + ttf.u16(b, (sub + 10 : U32)) : U32)}
530
531def ttf.pairs.unclassed(+acc: TTPairs) -> Bool:
532  TTPairs{specific, classes, coverage, class1, class2} = acc
533  U32.is_eq(classes, 0)
534
535def ttf.pairs.add(+b: Bin.Bytes, +sub: U32, +acc: TTPairs) -> TTPairs:
536  +format = ttf.u16(b, sub)
537  U.branch(TTPairs, U32.is_eq(format, 1), _ => ttf.pairs.add1(sub, acc),
538    _ => U.branch(TTPairs, U32.is_eq(format, 2) && ttf.pairs.unclassed(acc), _ => ttf.pairs.add2(b, sub, acc), _ => acc))
539
540def ttf.pairs.any.list(specific: List<&2, U32>, +classes: U32) -> Bool:
541  match specific:
542    case Nil{}: U32.is_ne(classes, 0)
543    case Con{h, t}: True{}
544
545def ttf.pairs.any(+p: TTPairs) -> Bool:
546  TTPairs{specific, classes, coverage, class1, class2} = p
547  ttf.pairs.any.list(specific, classes)
548
549def ttf.pairs.subtables(+b: Bin.Bytes, n: Nat, +lookup: U32, +at: U32, +kind: U32, +acc: TTPairs) -> TTPairs:
550  match n:
551    case 0n: acc
552    case 1n+d:
553      +sub = (lookup + ttf.u16(b, at) : U32)
554      +extension = U32.is_eq(kind, 9)
555      +actual = U.choose(U32, extension, (sub + ttf.u32(b, (sub + 4 : U32)) : U32), sub)
556      +subkind = U.choose(U32, extension, ttf.u16(b, (sub + 2 : U32)), kind)
557      ttf.pairs.subtables(b, d, lookup, (at + 2 : U32), kind, U.branch(TTPairs, U32.is_eq(subkind, 2), _ => ttf.pairs.add(b, actual, acc), _ => acc))
558
559# The first lookup holding pair subtables supplies both kinds of kerning.
560def ttf.pairs.lookups(+b: Bin.Bytes, n: Nat, +list: U32, +at: U32) -> TTPairs:
561  match n:
562    case 0n: ttf.pairs.empty()
563    case 1n+d:
564      +lookup = (list + ttf.u16(b, at) : U32)
565      +found = ttf.pairs.subtables(b, U32.to_nat(ttf.u16(b, (lookup + 4 : U32))), lookup, (lookup + 6 : U32), ttf.u16(b, lookup), ttf.pairs.empty())
566      U.branch(TTPairs, ttf.pairs.any(found), _ => found, _ => ttf.pairs.lookups(b, d, list, (at + 2 : U32)))
567
568def ttf.pairs(+b: Bin.Bytes) -> TTPairs:
569  +gpos = ttf.table(b, 1196445523)
570  +list = (gpos + ttf.u16(b, (gpos + 8 : U32)) : U32)
571  U.branch(TTPairs, U32.is_eq(gpos, 0), _ => ttf.pairs.empty(), _ => ttf.pairs.lookups(b, U32.to_nat(ttf.u16(b, list)), list, (list + 2 : U32)))
572
573# Coverage index plus one (0 when absent), by binary search over sorted glyphs or ranges.
574def ttf.coverage.array(+b: Bin.Bytes, fuel: Nat, +base: U32, +lo: U32, +hi: U32, +gid: U32) -> U32:
575  match fuel:
576    case 0n: 0
577    case 1n+d:
578      +dd = d
579      +mid = U32.div((lo + hi : U32), 2)
580      +g = ttf.u16(b, (base + mid * 2 : U32))
581      U.branch(U32, U32.is_ge(lo, hi), _ => 0,
582        _ => U.branch(U32, U32.is_eq(g, gid), _ => (mid + 1 : U32),
583          _ => U.branch(U32, U32.is_lt(g, gid), _ => ttf.coverage.array(b, dd, base, (mid + 1 : U32), hi, gid), _ => ttf.coverage.array(b, dd, base, lo, mid, gid))))
584
585def ttf.coverage.ranges(+b: Bin.Bytes, fuel: Nat, +base: U32, +lo: U32, +hi: U32, +gid: U32) -> U32:
586  match fuel:
587    case 0n: 0
588    case 1n+d:
589      +dd = d
590      +mid = U32.div((lo + hi : U32), 2)
591      +at = (base + mid * 6 : U32)
592      +start = ttf.u16(b, at)
593      +end = ttf.u16(b, (at + 2 : U32))
594      U.branch(U32, U32.is_ge(lo, hi), _ => 0,
595        _ => U.branch(U32, U32.is_lt(gid, start), _ => ttf.coverage.ranges(b, dd, base, lo, mid, gid),
596          _ => U.branch(U32, U32.is_gt(gid, end), _ => ttf.coverage.ranges(b, dd, base, (mid + 1 : U32), hi, gid),
597            _ => (ttf.u16(b, (at + 4 : U32)) + gid - start + 1 : U32))))
598
599def ttf.coverage(+b: Bin.Bytes, +cov: U32, +gid: U32) -> U32:
600  +count = ttf.u16(b, (cov + 2 : U32))
601  U.branch(U32, U32.is_eq(cov, 0), _ => 0,
602    _ => U.branch(U32, U32.is_eq(ttf.u16(b, cov), 1), _ => ttf.coverage.array(b, 20n, (cov + 4 : U32), 0, count, gid), _ => ttf.coverage.ranges(b, 20n, (cov + 4 : U32), 0, count, gid)))
603
604def ttf.coverage.walk(+b: Bin.Bytes, n: Nat, +at: U32, +index: U32) -> U32:
605  match n:
606    case 0n: 0
607    case 1n+d:
608      +start = ttf.u16(b, at)
609      +end = ttf.u16(b, (at + 2 : U32))
610      +first = ttf.u16(b, (at + 4 : U32))
611      U.branch(U32, U32.is_ge(index, first) && U32.is_le((index - first : U32), (end - start : U32)), _ => (start + index - first : U32), _ => ttf.coverage.walk(b, d, (at + 6 : U32), index))
612
613# The glyph at a coverage index (format 2 walks its ranges).
614def ttf.coverage.glyph(+b: Bin.Bytes, +cov: U32, +index: U32) -> U32:
615  U.branch(U32, U32.is_eq(ttf.u16(b, cov), 1), _ => ttf.u16(b, (cov + 4 + index * 2 : U32)),
616    _ => ttf.coverage.walk(b, U32.to_nat(ttf.u16(b, (cov + 2 : U32))), (cov + 4 : U32), index))
617
618def ttf.classdef.ranges(+b: Bin.Bytes, fuel: Nat, +base: U32, +lo: U32, +hi: U32, +gid: U32) -> U32:
619  match fuel:
620    case 0n: 0
621    case 1n+d:
622      +dd = d
623      +mid = U32.div((lo + hi : U32), 2)
624      +at = (base + mid * 6 : U32)
625      +start = ttf.u16(b, at)
626      +end = ttf.u16(b, (at + 2 : U32))
627      U.branch(U32, U32.is_ge(lo, hi), _ => 0,
628        _ => U.branch(U32, U32.is_lt(gid, start), _ => ttf.classdef.ranges(b, dd, base, lo, mid, gid),
629          _ => U.branch(U32, U32.is_gt(gid, end), _ => ttf.classdef.ranges(b, dd, base, (mid + 1 : U32), hi, gid), _ => ttf.u16(b, (at + 4 : U32)))))
630
631def ttf.classdef(+b: Bin.Bytes, +cd: U32, +gid: U32) -> U32:
632  +start = ttf.u16(b, (cd + 2 : U32))
633  +count = ttf.u16(b, (cd + 4 : U32))
634  U.branch(U32, U32.is_eq(cd, 0), _ => 0,
635    _ => U.branch(U32, U32.is_eq(ttf.u16(b, cd), 1),
636      _ => U.choose(U32, U32.is_ge(gid, start) && U32.is_lt((gid - start : U32), count), ttf.u16(b, (cd + 6 + (gid - start) * 2 : U32)), 0),
637      _ => ttf.classdef.ranges(b, 20n, (cd + 4 : U32), 0, ttf.u16(b, (cd + 2 : U32)), gid)))
638
639def ttf.left(+b: Bin.Bytes, +pairs: TTPairs, +gid: U32) -> U32:
640  TTPairs{specific, classes, coverage, class1, class2} = pairs
641  U.choose(U32, U32.is_gt(ttf.coverage(b, coverage, gid), 0), (ttf.classdef(b, class1, gid) + 1 : U32), 0)
642
643def ttf.right(+b: Bin.Bytes, +pairs: TTPairs, +gid: U32) -> U32:
644  TTPairs{specific, classes, coverage, class1, class2} = pairs
645  ttf.classdef(b, class2, gid)
646
647# ---- glyph records ----
648
649# ---- cmap ----
650
651def ttf.cmap.score(+platform: U32, +encoding: U32) -> U32:
652  U.choose(U32, U32.is_eq(platform, 3) && U32.is_eq(encoding, 10), 6,
653    U.choose(U32, U32.is_eq(platform, 0) && U32.is_eq(encoding, 6), 5,
654      U.choose(U32, U32.is_eq(platform, 0) && U32.is_eq(encoding, 4), 4,
655        U.choose(U32, U32.is_eq(platform, 3) && U32.is_eq(encoding, 1), 3,
656          U.choose(U32, U32.is_eq(platform, 0) && U32.is_eq(encoding, 3), 2, U.choose(U32, U32.is_eq(platform, 0), 1, 0))))))
657
658def ttf.cmap.pick(+b: Bin.Bytes, n: Nat, +cmap: U32, +at: U32, +best: U32, +score: U32) -> U32:
659  match n:
660    case 0n: best
661    case 1n+d:
662      +dd = d
663      +s = ttf.cmap.score(ttf.u16(b, at), ttf.u16(b, (at + 2 : U32)))
664      U.branch(U32, U32.is_gt(s, score),
665        _ => ttf.cmap.pick(b, dd, cmap, (at + 8 : U32), (cmap + ttf.u32(b, (at + 4 : U32)) : U32), s),
666        _ => ttf.cmap.pick(b, dd, cmap, (at + 8 : U32), best, score))
667
668def ttf.cmap4.indexed(+g: U32, +delta: U32) -> U32:
669  U.choose(U32, U32.is_eq(g, 0), 0, U32.and((g + delta : U32), 65535))
670
671def ttf.cmap4.glyph(+b: Bin.Bytes, +cp: U32, +start: U32, +delta: U32, +roAt: U32, +ro: U32) -> U32:
672  U.branch(U32, U32.is_eq(ro, 0), _ => U32.and((cp + delta : U32), 65535),
673    _ => ttf.cmap4.indexed(ttf.u16(b, (roAt + ro + (cp - start) * 2 : U32)), delta))
674
675# Glyph index of a code point through the picked subtable: a binary search over
676# format 4 segments (first end code at or above the code point) or format 12 groups.
677def ttf.cmap4.find(+b: Bin.Bytes, fuel: Nat, +sub: U32, +lo: U32, +hi: U32, +cp: U32) -> U32:
678  match fuel:
679    case 0n: lo
680    case 1n+d:
681      +dd = d
682      +mid = U32.div((lo + hi : U32), 2)
683      U.branch(U32, U32.is_ge(lo, hi), _ => lo,
684        _ => U.branch(U32, U32.is_lt(ttf.u16(b, (sub + 14 + mid * 2 : U32)), cp), _ => ttf.cmap4.find(b, dd, sub, (mid + 1 : U32), hi, cp), _ => ttf.cmap4.find(b, dd, sub, lo, mid, cp)))
685
686def ttf.cmap4.gid(+b: Bin.Bytes, +sub: U32, +cp: U32) -> U32:
687  +segX2 = ttf.u16(b, (sub + 6 : U32))
688  +count = U32.div(segX2, 2)
689  +i = ttf.cmap4.find(b, 20n, sub, 0, count, cp)
690  +start = ttf.u16(b, (sub + 16 + segX2 + i * 2 : U32))
691  +delta = ttf.u16(b, (sub + 16 + segX2 * 2 + i * 2 : U32))
692  +roAt = (sub + 16 + segX2 * 3 + i * 2 : U32)
693  U.branch(U32, U32.is_gt(cp, 65534) || U32.is_ge(i, count) || U32.is_lt(cp, start), _ => 0, _ => ttf.cmap4.glyph(b, cp, start, delta, roAt, ttf.u16(b, roAt)))
694
695def ttf.cmap12.find(+b: Bin.Bytes, fuel: Nat, +at: U32, +lo: U32, +hi: U32, +cp: U32) -> U32:
696  match fuel:
697    case 0n: lo
698    case 1n+d:
699      +dd = d
700      +mid = U32.div((lo + hi : U32), 2)
701      U.branch(U32, U32.is_ge(lo, hi), _ => lo,
702        _ => U.branch(U32, U32.is_lt(ttf.u32(b, (at + mid * 12 + 4 : U32)), cp), _ => ttf.cmap12.find(b, dd, at, (mid + 1 : U32), hi, cp), _ => ttf.cmap12.find(b, dd, at, lo, mid, cp)))
703
704def ttf.cmap12.gid(+b: Bin.Bytes, +sub: U32, +cp: U32) -> U32:
705  +at = (sub + 16 : U32)
706  +count = ttf.u32(b, (sub + 12 : U32))
707  +i = ttf.cmap12.find(b, 40n, at, 0, count, cp)
708  +start = ttf.u32(b, (at + i * 12 : U32))
709  U.branch(U32, U32.is_ge(i, count) || U32.is_lt(cp, start), _ => 0, _ => (ttf.u32(b, (at + i * 12 + 8 : U32)) + cp - start : U32))
710
711def ttf.cmap.gid(+b: Bin.Bytes, +sub: U32, +cp: U32) -> U32:
712  +format = ttf.u16(b, sub)
713  U.branch(U32, U32.is_eq(sub, 0), _ => 0,
714    _ => U.branch(U32, U32.is_eq(format, 4), _ => ttf.cmap4.gid(b, sub, cp),
715      _ => U.branch(U32, U32.is_eq(format, 12), _ => ttf.cmap12.gid(b, sub, cp), _ => 0)))
716
717# The best Unicode subtable (formats 4 and 12), 0 when the font has none.
718def ttf.cmap.subtable(+b: Bin.Bytes) -> U32:
719  +cmap = ttf.table(b, 1668112752)
720  U.branch(U32, U32.is_eq(cmap, 0), _ => 0, _ => ttf.cmap.pick(b, U32.to_nat(ttf.u16(b, (cmap + 2 : U32))), cmap, (cmap + 4 : U32), 0, 0))
721
722# ---- kerning values ----
723
724def ttf.bits(+v: U32) -> U32:
725  (U32.and(v, 1) + U32.and(U32.shrn(v, 1n), 1) + U32.and(U32.shrn(v, 2n), 1) + U32.and(U32.shrn(v, 3n), 1) + U32.and(U32.shrn(v, 4n), 1) + U32.and(U32.shrn(v, 5n), 1) + U32.and(U32.shrn(v, 6n), 1) + U32.and(U32.shrn(v, 7n), 1) : U32)
726
727# A value record holds two bytes per set format bit; XAdvance follows the two placement fields.
728def ttf.value.size(+vf: U32) -> U32:
729  (ttf.bits(U32.and(vf, 255)) * 2 : U32)
730
731def ttf.xadvance(+b: Bin.Bytes, +at: U32, +vf: U32) -> F32:
732  U.choose(F32, ttf.bit(vf, 4), ttf.i16(b, (at + ttf.bits(U32.and(vf, 3)) * 2 : U32)), 0.0)
733
734# A format 1 pair set: the record whose second glyph matches, 1000000 when none.
735def ttf.kern.records(+b: Bin.Bytes, n: Nat, +at: U32, +vf1: U32, +vf2: U32, +right: U32) -> F32:
736  match n:
737    case 0n: 1000000.0
738    case 1n+d:
739      U.branch(F32, U32.is_eq(ttf.u16(b, at), right), _ => ttf.xadvance(b, (at + 2 : U32), vf1),
740        _ => ttf.kern.records(b, d, (at + 2 + ttf.value.size(vf1) + ttf.value.size(vf2) : U32), vf1, vf2, right))
741
742def ttf.kern.subtable(+b: Bin.Bytes, +sub: U32, +left: U32, +right: U32) -> F32:
743  +index = ttf.coverage(b, (sub + ttf.u16(b, (sub + 2 : U32)) : U32), left)
744  +vf1 = ttf.u16(b, (sub + 4 : U32))
745  +vf2 = ttf.u16(b, (sub + 6 : U32))
746  +set = (sub + ttf.u16(b, (sub + 10 + (index - 1) * 2 : U32)) : U32)
747  U.branch(F32, U32.is_eq(index, 0), _ => 1000000.0, _ => ttf.kern.records(b, U32.to_nat(ttf.u16(b, set)), (set + 2 : U32), vf1, vf2, right))
748
749# Every format 1 subtable in turn; the first pair found wins.
750def ttf.kern.specific(+b: Bin.Bytes, subs: List<&2, U32>, +left: U32, +right: U32) -> F32:
751  match subs:
752    case Nil{}: 1000000.0
753    case Con{sub, rest}:
754      +v = ttf.kern.subtable(b, sub, left, right)
755      U.branch(F32, F32.is_eq(v, 1000000.0), _ => ttf.kern.specific(b, rest, left, right), _ => v)
756
757# The format 2 class grid; leftClass counts from one (0 means uncovered).
758def ttf.kern.classes(+b: Bin.Bytes, +sub: U32, +leftClass: U32, +rightClass: U32) -> F32:
759  +vf1 = ttf.u16(b, (sub + 4 : U32))
760  +vf2 = ttf.u16(b, (sub + 6 : U32))
761  +rows = ttf.u16(b, (sub + 12 : U32))
762  +columns = ttf.u16(b, (sub + 14 : U32))
763  +size = (ttf.value.size(vf1) + ttf.value.size(vf2) : U32)
764  U.branch(F32, U32.is_eq(sub, 0) || U32.is_eq(leftClass, 0) || U32.is_gt(leftClass, rows) || U32.is_ge(rightClass, columns), _ => 0.0,
765    _ => ttf.xadvance(b, (sub + 16 + ((leftClass - 1) * columns + rightClass) * size : U32), vf1))
766
767def ttf.kern(+fc: TTFace, +left: U32, +leftClass: U32, +right: U32, +rightClass: U32) -> F32:
768  TTFace{b, f, pairs, cmap} = fc
769  TTPairs{specific, classes, coverage, class1, class2} = pairs
770  font.kern.value(ttf.kern.specific(b, specific, left, right), _ => ttf.kern.classes(b, classes, leftClass, rightClass))
771
772# ---- OS/2 metrics ----
773
774def ttf.metric(+face: U32, +name: String, +value: F32, book: FontBook) -> FontBook:
775  FontBook{glyphs, kerning, metrics, faces} = book
776  FontBook{glyphs, kerning, Map.set(&2, F32, metrics, U32.show(face) ++ ":" ++ name, value), faces}
777
778def ttf.metrics(+b: Bin.Bytes, +face: U32, +os2: U32, +units: F32, book: FontBook) -> FontBook:
779  +bookDup = book
780  +version = ttf.u16(b, os2)
781  U.branch(FontBook, U32.is_eq(os2, 0), _ => bookDup,
782    _ => ttf.metric(face, "subscript", (ttf.i16(b, (os2 + 16 : U32)) / units : F32),
783      ttf.metric(face, "superscript", (ttf.i16(b, (os2 + 24 : U32)) / units : F32),
784        ttf.metric(face, "ascender", (ttf.i16(b, (os2 + 68 : U32)) / units : F32),
785          ttf.metric(face, "descender", (ttf.i16(b, (os2 + 70 : U32)) / units : F32),
786            U.branch(FontBook, U32.is_ge(version, 2), _ => ttf.metric(face, "xheight", (ttf.i16(b, (os2 + 86 : U32)) / units : F32), bookDup), _ => bookDup))))))
787
788# Adds one face from TrueType bytes: the face record for on-demand glyphs and
789# kerning, and its OS/2 metrics. Fonts without glyf/loca/hmtx leave the book unchanged.
790def font.ttf(+face: U32, +b: Bin.Bytes, +book: FontBook) -> FontBook:
791  FontBook{glyphs, kerning, metrics, faces} = book
792  +f = ttf.font(b)
793  U.branch(FontBook, ttf.valid(f),
794    _ => ttf.metrics(b, face, ttf.table(b, 1330851634), ttf.units(f), FontBook{glyphs, kerning, metrics, Map.set(&2, TTFace, faces, U32.show(face), TTFace{b, f, ttf.pairs(b), ttf.cmap.subtable(b)})}),
795    _ => FontBook{glyphs, kerning, metrics, faces})
796
797def font.ttf.base64(+face: U32, +text: String, book: FontBook) -> FontBook:
798  font.ttf(face, Bin.base64(text), book)
799
800# ---- lookups ----
801
802def font.face.absent() -> TTFace:
803  TTFace{Bin.bytes.empty(), TTFont{0, 0, False{}, 0, 0, 0, 0.0}, TTPairs{Nil{}, 0, 0, 0, 0}, 0}
804
805def font.face.result(got: Map<&2, TTFace> & TTFace) -> TTFace:
806  (map, fc) = got
807  fc
808
809def font.face(+faces: Map<&2, TTFace>, +face: U32) -> TTFace:
810  font.face.result(Map.get(TTFace, font.face.absent(), faces, U32.show(face)))
811
812def font.face.present(+fc: TTFace) -> Bool:
813  TTFace{b, f, pairs, cmap} = fc
814  ttf.valid(f)
815
816# A glyph read on demand: cmap to glyph index (0, the missing glyph, when
817# unmapped), then advance, outline and kerning classes from the tables.
818def ttf.lookup(+fc: TTFace, +ch: U32) -> FontGlyph:
819  TTFace{b, f, pairs, cmap} = fc
820  +gid = ttf.cmap.gid(b, cmap, ch)
821  FontGlyph{ttf.advance(b, f, gid), ttf.outline(ttf.glyph(b, f, gid)), gid, ttf.left(b, pairs, gid), ttf.right(b, pairs, gid)}
822
823def font.glyph(+book: FontBook, +face: U32, +ch: U32) -> FontGlyph:
824  FontBook{glyphs, kerning, faceMetrics, faces} = book
825  +fc = font.face(faces, face)
826  U.branch(FontGlyph, font.face.present(fc), _ => ttf.lookup(fc, ch), _ => font.glyph.map(glyphs, face, ch))
827
828def font.kern(+book: FontBook, +face: U32, +left: U32, +leftClass: U32, +right: U32, +rightClass: U32) -> F32:
829  FontBook{glyphs, kerning, faceMetrics, faces} = book
830  +fc = font.face(faces, face)
831  U.branch(F32, font.face.present(fc), _ => ttf.kern(fc, left, leftClass, right, rightClass), _ => font.kern.map(kerning, face, left, leftClass, right, rightClass))