1 # Font book: glyph outlines, advances, kerning and metrics. 2 import Base 3 import gralha-text-scan@0.1.0.0/scan.bend as U 4 import gralha-bytes@0.1.0.0/bytes.bend as Bin 5 6 type TTFont is Data: 7 TTFont{glyf: U32, loca: U32, long: Bool, hmtx: U32, metrics: U32, glyphs: U32, units: F32} 8 9 type 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). 14 type TTFace is Data: 15 TTFace{bytes: Bin.Bytes, font: TTFont, pairs: TTPairs, cmap: U32} 16 17 type 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. 22 type FontBook is Data: 23 FontBook{glyphs: Map<&2, FontGlyph>, kerning: Map<&2, F32>, metrics: Map<&2, F32>, faces: Map<&2, TTFace>} 24 25 def font.empty() -> FontBook: 26 FontBook{MTip{}, MTip{}, MTip{}, MTip{}} 27 28 def font.missing() -> FontGlyph: 29 FontGlyph{600.0, "M94 0V714H505V0ZM151 57H448V657H151Z", 0, 0, 0} 30 31 def font.glyph.result(got: Map<&2, FontGlyph> & FontGlyph) -> FontGlyph: 32 (map, glyph) = got 33 glyph 34 35 def font.number.result(got: Map<&2, F32> & F32) -> F32: 36 (map, val) = got 37 val 38 39 def 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 43 def 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 46 def font.kern.value(+specific: F32, fallback: Unit -> F32) -> F32: 47 U.branch(F32, F32.is_eq(specific, 1000000.0), _ => fallback(Unit{}), _ => specific) 48 49 def 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 53 def 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 60 def 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 67 def 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 71 def 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 76 def 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 88 def ttf.u8(+b: Bin.Bytes, +at: U32) -> U32: 89 Bin.bytes.get(b, at) 90 91 def ttf.u16(+b: Bin.Bytes, +at: U32) -> U32: 92 Bin.bytes.be(b, at, 2n, 0) 93 94 def ttf.u32(+b: Bin.Bytes, +at: U32) -> U32: 95 Bin.bytes.be(b, at, 4n, 0) 96 97 def 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 101 def 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 105 def ttf.f2dot14(+b: Bin.Bytes, +at: U32) -> F32: 106 (ttf.i16(b, at) / 16384.0 : F32) 107 108 def ttf.bit(+flags: U32, +mask: U32) -> Bool: 109 U32.is_ne(U32.and(flags, mask), 0) 110 111 def 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. 117 def ttf.table(+b: Bin.Bytes, +tag: U32) -> U32: 118 ttf.table.find(b, U32.to_nat(ttf.u16(b, 4)), 12, tag) 119 120 def 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 127 def 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 131 def ttf.units(+f: TTFont) -> F32: 132 TTFont{glyf, loca, long, hmtx, metrics, glyphs, units} = f 133 units 134 135 def ttf.glyf(+f: TTFont) -> U32: 136 TTFont{glyf, loca, long, hmtx, metrics, glyphs, units} = f 137 glyf 138 139 def 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). 146 def 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 154 type TTPoint is Data: 155 TTPoint{x: F32, y: F32, on: Bool} 156 157 # Flat point list with the last point index of every contour. 158 type TTGlyph is Data: 159 TTGlyph{points: List<&2, TTPoint>, ends: List<&2, U32>, count: U32} 160 161 def ttf.glyph.empty() -> TTGlyph: 162 TTGlyph{Nil{}, Nil{}, 0} 163 164 def ttf.glyph.points(+g: TTGlyph) -> List<&2, TTPoint>: 165 TTGlyph{points, ends, count} = g 166 points 167 168 def 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 173 def 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 178 def 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. 183 def 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 195 def 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. 200 def 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 211 def 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 216 def 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 220 def 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 224 def 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. 230 def 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 236 def 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 242 def 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 250 def 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 255 def 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 260 def 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 265 def 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 271 def ttf.xy(p: TTPoint) -> F32 & F32: 272 TTPoint{x, y, on} = p 273 (x, y) 274 275 def 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). 282 type TTPlace is Data: 283 TTPlace{xy: Bool, a1: F32, a2: F32, scaledOffset: Bool, a: F32, b: F32, c: F32, d: F32} 284 285 def ttf.place.identity() -> TTPlace: 286 TTPlace{True{}, 0.0, 0.0, False{}, 1.0, 0.0, 0.0, 1.0} 287 288 def 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 296 def 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 301 def 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. 307 def 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. 313 type TTFrame is Data: 314 TTFrame{at: U32, acc: TTGlyph, place: TTPlace} 315 316 def 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. 332 def 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 337 def ttf.frame.at(+frame: TTFrame) -> U32: 338 TTFrame{at, acc, place} = frame 339 at 340 341 def ttf.frame.acc(+frame: TTFrame) -> TTGlyph: 342 TTFrame{at, acc, place} = frame 343 acc 344 345 # Pop a finished frame into its parent frame. 346 def 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 352 def 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. 367 def 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 373 def 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 381 def 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 386 def 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. 393 def 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 398 def 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 407 def 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 412 def ttf.pair(p: TTPoint) -> String: 413 TTPoint{x, y, on} = p 414 F32.show(x) ++ " " ++ F32.show(y) 415 416 def 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 421 def 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 426 def 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. 433 def 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 438 def 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 443 def 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 454 def 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 460 def 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 465 def 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. 470 def 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 478 def ttf.contour.off(+first: TTPoint, +rest: List<&2, TTPoint>) -> String: 479 ttf.contour.last(first, rest, ttf.last.point(rest, first)) 480 481 def 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 492 def 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 498 def 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 505 def 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 513 def 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. 520 def ttf.pairs.empty() -> TTPairs: 521 TTPairs{Nil{}, 0, 0, 0, 0} 522 523 def 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 527 def 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 531 def ttf.pairs.unclassed(+acc: TTPairs) -> Bool: 532 TTPairs{specific, classes, coverage, class1, class2} = acc 533 U32.is_eq(classes, 0) 534 535 def 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 540 def 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 545 def ttf.pairs.any(+p: TTPairs) -> Bool: 546 TTPairs{specific, classes, coverage, class1, class2} = p 547 ttf.pairs.any.list(specific, classes) 548 549 def 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. 560 def 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 568 def 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. 574 def 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 585 def 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 599 def 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 604 def 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). 614 def 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 618 def 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 631 def 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 639 def 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 643 def 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 651 def 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 658 def 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 668 def 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 671 def 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. 677 def 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 686 def 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 695 def 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 704 def 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 711 def 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. 718 def 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 724 def 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. 728 def ttf.value.size(+vf: U32) -> U32: 729 (ttf.bits(U32.and(vf, 255)) * 2 : U32) 730 731 def 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. 735 def 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 742 def 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. 750 def 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). 758 def 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 767 def 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 774 def 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 778 def 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. 790 def 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 797 def font.ttf.base64(+face: U32, +text: String, book: FontBook) -> FontBook: 798 font.ttf(face, Bin.base64(text), book) 799 800 # ---- lookups ---- 801 802 def 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 805 def font.face.result(got: Map<&2, TTFace> & TTFace) -> TTFace: 806 (map, fc) = got 807 fc 808 809 def font.face(+faces: Map<&2, TTFace>, +face: U32) -> TTFace: 810 font.face.result(Map.get(TTFace, font.face.absent(), faces, U32.show(face))) 811 812 def 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. 818 def 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 823 def 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 828 def 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))