code wiki / _hdl_build / nx_ttf_fontlib.nx

nx_ttf_fontlib.nx source

↩ module page · 601 lines · 29060 B

1// nx_ttf_fontlib.nx -- the sovereign TrueType FONT ENGINE as a LIBRARY (the browser's text renderer). 2// Canonical home of the TTF read path first proven in the nx_ttf_raster demo (which has a main() and so 3// cannot be imported): sfnt dir -> head/cmap/loca/glyf/hhea/hmtx(+kern), cmap format-4 (BOTH the delta 4// path and the idRangeOffset/glyphIdArray path), glyf outline decode (flags/short/same/repeat), COMPOSITE 5// glyphs (component offsets + F2Dot14 scale, bounded recursion), quadratic Bezier flattening, NONZERO- 6// WINDING scan conversion with SSxSS supersampled ANTI-ALIASED coverage, a per-(char,size) COVERAGE CACHE 7// (each glyph rasterizes once per size), REAL per-glyph advances from hmtx, pair KERNING from the 'kern' 8// table (format 0, binary search), a mild contrast curve (stem darkening at small sizes), and an 9// RGBA-framebuffer ALPHA-BLEND draw (nx_framebuffer px = R,G,B,A). 10// 11// 2026-09-02 (BR4 first half, /compare/browser rung "Real font files + shaping"): EVERY METRIC IS NOW 12// READ FROM THE FONT, NEVER ASSUMED. The first cut hard-coded unitsPerEm=1000 and ascender=760 -- true 13// of our own emitted nishi_sans.ttf and FALSE of every professional font (Liberation Sans, Arial, Noto: 14// 2048), so a real face would have rasterized at HALF size with its advances halved, and nothing would 15// have failed loudly. unitsPerEm comes from head, ascender/descender/lineGap from hhea, the cell height 16// is DERIVED from (ascender - descender), and the browser's 1000-em advance unit is a conversion at the 17// API edge (tf_adv_em1000), not an assumption inside the rasterizer. A negative left side bearing 18// ('j', '_', 'w' in Liberation) is carried as the record's left margin so no ink is clipped, and ink 19// wider than the advance ('f', 'k') widens the cell instead of being cut at the advance. 20// Fonts proven: our own emitted web_assets/nishi_sans.ttf (96 glyphs, upem 1000) and Liberation Sans 21// (SIL-OFL, upem 2048, 908 kern pairs, 1,076 composite glyphs). No FreeType, no GDI, no DirectWrite. 22// license_tier: ORIGINAL 23import "nx_syscalls.nx" 24import "nx_paint_solid_rect.nx" 25const TF_MAGIC_32768: i64 = 32768 26const TF_MAGIC_65536: i64 = 65536 27const TF_MAGIC_16777216: i64 = 16777216 28const TF_MAGIC_2048: i64 = 2048 29const TF_F2DOT14_ONE: i64 = 16384 // 1.0 in the F2Dot14 fixed-point the glyf composite scale fields use 30 31// handle slots (one i64 each) 32const TF_H_BUF: i64 = 0 33const TF_H_GLYF: i64 = 1 34const TF_H_LOCA: i64 = 2 35const TF_H_LOCFMT: i64 = 3 36const TF_H_CMAPSUB: i64 = 4 37const TF_H_HMTX: i64 = 5 38const TF_H_NUMHM: i64 = 6 39const TF_H_CACHE: i64 = 7 40const TF_H_NSIZES: i64 = 8 41const TF_H_SIZE0: i64 = 9 // 9..9+TF_MAXSZ-1 = the pixel heights with a cache slot 42const TF_MAXSZ: i64 = 16 43const TF_H_UPEM: i64 = 25 // head.unitsPerEm -- READ, never assumed 44const TF_H_ASC: i64 = 26 // hhea.ascender (font units, positive up) 45const TF_H_DESC: i64 = 27 // hhea.descender (font units, negative) 46const TF_H_LINEGAP: i64 = 28 // hhea.lineGap 47const TF_H_KPAIRS: i64 = 29 // byte offset of the kern format-0 pair array (0 = no horizontal kern table) 48const TF_H_KN: i64 = 30 // number of kern pairs 49const TF_H_LEN: i64 = 31 // byte length of the font buffer (every table offset is checked against it) 50const TF_H_NGLYPHS: i64 = 32 // maxp.numGlyphs (bounds every gid before loca is indexed) 51const TF_HSLOTS: i64 = 40 52 53// coverage-record slots (per cached (size, char)) 54const TF_R_COV: i64 = 0 // coverage bytes, cw x chh, row-major; row 0 = the ASCENDER line 55const TF_R_CW: i64 = 1 // cell width px 56const TF_R_CHH: i64 = 2 // cell height px = ((asc - desc) * ph) / upem + 1 57const TF_R_ADV: i64 = 3 // advance px at this size (hmtx scaled) 58const TF_R_LM: i64 = 4 // left margin px: the cell's x0 sits this far LEFT of the pen (negative lsb) 59const TF_R_PH: i64 = 5 60const TF_REC: i64 = 8 // i64 slots per record 61 62const TF_ASCII_LO: i64 = 32 63const TF_ASCII_HI: i64 = 126 64const TF_ASCII_N: i64 = TF_ASCII_HI - TF_ASCII_LO + 1 65const TF_EM1000: i64 = 1000 // the browser's advance unit (font_adv_em speaks 1000-em) 66const TF_SS: i64 = 4 // supersample factor per axis (SS*SS samples per pixel) 67const TF_MAXE: i64 = 500 // flattened-edge capacity per glyph 68const TF_BEZ_STEPS: i64 = 6 // quadratic Bezier flattening steps 69const TF_COMPOSITE_DEPTH: i64 = 4 // bounded recursion for nested composites 70const TF_CONTRAST_DIV: i64 = 170 // stem-darkening curve: a += a*(255-a)/170 71const TF_KERN_SUBHDR: i64 = 14 // kern format-0 subtable header bytes before the pair array 72const TF_KERN_PAIR: i64 = 6 // left(2) right(2) value(2) 73const TF_CMAP_REC: i64 = 8 // encoding record: platform(2) encoding(2) offset(4) 74const TF_MIN_ADV_PX: i64 = 2 75 76func tf_rd16(b: *u8, o: i64) -> i64 { return (b[o] as i64)*256 + (b[o+1] as i64) } 77func tf_rd16s(b: *u8, o: i64) -> i64 { let v: i64=tf_rd16(b,o); if v>=TF_MAGIC_32768 { return v-TF_MAGIC_65536 } return v } 78func tf_rd32(b: *u8, o: i64) -> i64 { return (b[o] as i64)*TF_MAGIC_16777216 + (b[o+1] as i64)*TF_MAGIC_65536 + (b[o+2] as i64)*256 + (b[o+3] as i64) } 79func tf_bit(f: i64, m: i64) -> i64 { if (f/m)%2 == 1 { return 1 } return 0 } 80// signed 8-bit 81func tf_rd8s(b: *u8, o: i64) -> i64 { let v: i64 = b[o] as i64; if v >= 128 { return v - 256 } return v } 82 83func tf_find_table(b: *u8, t0: i64, t1: i64, t2: i64, t3: i64) -> i64 { 84 let nt: i64=tf_rd16(b,4) 85 var i: i64=0 86 while i<nt { let d: i64=12+i*16 87 if (b[d] as i64)==t0 { if (b[d+1] as i64)==t1 { if (b[d+2] as i64)==t2 { if (b[d+3] as i64)==t3 { return tf_rd32(b,d+8) } } } } 88 i=i+1 } 89 return 0 - 1 90} 91// cmap format-4 lookup: BOTH paths. idRangeOffset==0 -> gid = (c + idDelta) mod 65536; 92// idRangeOffset!=0 -> gid comes from glyphIdArray at &idRangeOffset[i] + idRangeOffset[i] + 2*(c-start), 93// and a 0 there means .notdef (idDelta is NOT applied to 0). The first cut returned 0 for every 94// glyphIdArray segment, which is where fonts put their scattered ranges. 95func tf_cmap_lookup(b: *u8, sub: i64, c: i64) -> i64 { 96 let segX2: i64=tf_rd16(b,sub+6) 97 let segCount: i64=segX2/2 98 var i: i64=0 99 while i<segCount { 100 let endc: i64=tf_rd16(b,sub+14+i*2) 101 if endc>=c { 102 let startc: i64=tf_rd16(b,sub+16+segX2+i*2) 103 if startc<=c { 104 let iro_at: i64 = sub+16+segX2*3+i*2 105 let iro: i64=tf_rd16(b,iro_at) 106 let delta: i64=tf_rd16s(b,sub+16+segX2*2+i*2) 107 if iro==0 { return (c+delta)%TF_MAGIC_65536 } 108 let g: i64 = tf_rd16(b, iro_at + iro + (c-startc)*2) 109 if g == 0 { return 0 } 110 return (g+delta)%TF_MAGIC_65536 111 } 112 return 0 113 } 114 i=i+1 115 } 116 return 0 117} 118// pick the format-4 subtable: scan the encoding records rather than trusting record 0 (a font may list a 119// format-6 or format-12 table first). Returns the absolute subtable offset, or -1 when no format-4 exists. 120func tf_cmap_pick4(b: *u8, cmap: i64, len: i64) -> i64 { 121 let n: i64 = tf_rd16(b, cmap+2) 122 var i: i64 = 0 123 while i < n { 124 let rec: i64 = cmap + 4 + i*TF_CMAP_REC 125 let off: i64 = tf_rd32(b, rec+4) 126 let sub: i64 = cmap + off 127 if sub + 8 < len { if tf_rd16(b, sub) == 4 { return sub } } 128 i = i + 1 129 } 130 return 0 - 1 131} 132 133// append one straight edge (scaled/offset by the composite transform when nested) 134func tf_edge_push(ex0: *i64, ey0: *i64, ex1: *i64, ey1: *i64, ne: i64, x0: i64, y0: i64, x1: i64, y1: i64) -> i64 { 135 if ne >= TF_MAXE { return ne } 136 ex0[ne]=x0; ey0[ne]=y0; ex1[ne]=x1; ey1[ne]=y1 137 return ne+1 138} 139// decode glyph gid -> flattened line edges. returns edge count (max TF_MAXE). Coordinates are transformed 140// by (a,b,c,d as F2Dot14; dx,dy font units): x' = (a*x + c*y)/16384 + dx, y' = (b*x + d*y)/16384 + dy -- 141// identity for a simple glyph called directly. Composite glyphs recurse per component with depth bounded. 142func tf_glyph_edges_x(b: *u8, glyf: i64, loca: i64, locfmt: i64, nglyphs: i64, gid: i64, 143 ex0: *i64, ey0: *i64, ex1: *i64, ey1: *i64, ne0: i64, 144 ta: i64, tb: i64, tc: i64, td: i64, tdx: i64, tdy: i64, depth: i64) -> i64 { 145 var ne: i64 = ne0 146 if gid < 0 { return ne } 147 if gid >= nglyphs { return ne } 148 var g0: i64=0 149 var g1: i64=0 150 if locfmt==0 { g0=tf_rd16(b,loca+gid*2)*2; g1=tf_rd16(b,loca+gid*2+2)*2 } else { g0=tf_rd32(b,loca+gid*4); g1=tf_rd32(b,loca+gid*4+4) } 151 if g1<=g0 { return ne } 152 let gp: i64=glyf+g0 153 let nc: i64=tf_rd16s(b,gp) 154 if nc < 0 { 155 // COMPOSITE: components until MORE_COMPONENTS (bit 5) clears 156 if depth >= TF_COMPOSITE_DEPTH { return ne } 157 var cp: i64 = gp + 10 158 var more: i64 = 1 159 while more == 1 { 160 let flags: i64 = tf_rd16(b, cp) 161 let cgid: i64 = tf_rd16(b, cp+2) 162 cp = cp + 4 163 var cdx: i64 = 0 164 var cdy: i64 = 0 165 if tf_bit(flags, 1) == 1 { cdx = tf_rd16s(b, cp); cdy = tf_rd16s(b, cp+2); cp = cp + 4 } else { cdx = tf_rd8s(b, cp); cdy = tf_rd8s(b, cp+1); cp = cp + 2 } // ARG_1_AND_2_ARE_WORDS 166 if tf_bit(flags, 2) == 0 { cdx = 0; cdy = 0 } // ARGS_ARE_XY_VALUES clear = point matching: unsupported, treated as no offset 167 var ca: i64 = TF_F2DOT14_ONE 168 var cb: i64 = 0 169 var cc: i64 = 0 170 var cd: i64 = TF_F2DOT14_ONE 171 if tf_bit(flags, 8) == 1 { ca = tf_rd16s(b, cp); cd = ca; cp = cp + 2 } else { if tf_bit(flags, 64) == 1 { ca = tf_rd16s(b, cp); cd = tf_rd16s(b, cp+2); cp = cp + 4 } else { if tf_bit(flags, 128) == 1 { ca = tf_rd16s(b, cp); cb = tf_rd16s(b, cp+2); cc = tf_rd16s(b, cp+4); cd = tf_rd16s(b, cp+6); cp = cp + 8 } } } // WE_HAVE_A_SCALE / X_AND_Y_SCALE / TWO_BY_TWO 172 // compose the component transform with the caller's: first component, then caller 173 let na: i64 = (ta*ca + tc*cb) / TF_F2DOT14_ONE 174 let nb: i64 = (tb*ca + td*cb) / TF_F2DOT14_ONE 175 let ncc: i64 = (ta*cc + tc*cd) / TF_F2DOT14_ONE 176 let nd: i64 = (tb*cc + td*cd) / TF_F2DOT14_ONE 177 let ndx: i64 = (ta*cdx + tc*cdy) / TF_F2DOT14_ONE + tdx 178 let ndy: i64 = (tb*cdx + td*cdy) / TF_F2DOT14_ONE + tdy 179 ne = tf_glyph_edges_x(b, glyf, loca, locfmt, nglyphs, cgid, ex0, ey0, ex1, ey1, ne, na, nb, ncc, nd, ndx, ndy, depth+1) 180 if tf_bit(flags, 32) == 0 { more = 0 } 181 } 182 return ne 183 } 184 if nc == 0 { return ne } 185 let ep: *i64=sys_mmap(8*(nc+1)) as *i64 186 var i: i64=0 187 while i<nc { ep[i]=tf_rd16(b,gp+10+i*2); i=i+1 } 188 let np: i64=ep[nc-1]+1 189 let instr: i64=tf_rd16(b,gp+10+nc*2) 190 var p: i64=gp+10+nc*2+2+instr 191 let FL: *i64=sys_mmap(8*(np+1)) as *i64 192 let PX: *i64=sys_mmap(8*(np+1)) as *i64 193 let PY: *i64=sys_mmap(8*(np+1)) as *i64 194 let ON: *i64=sys_mmap(8*(np+1)) as *i64 195 var fi: i64=0 196 while fi<np { let f: i64=b[p] as i64; p=p+1; FL[fi]=f; fi=fi+1 197 if tf_bit(f,8)==1 { var rep: i64=b[p] as i64; p=p+1; while rep>0 { if fi<np { FL[fi]=f; fi=fi+1 } rep=rep-1 } } } 198 var x: i64=0 199 i=0 200 while i<np { let f: i64=FL[i] 201 if tf_bit(f,2)==1 { var dx: i64=b[p] as i64; p=p+1; if tf_bit(f,16)==0 { dx=0-dx } x=x+dx } 202 else { if tf_bit(f,16)==0 { x=x+tf_rd16s(b,p); p=p+2 } } 203 PX[i]=x; i=i+1 } 204 var y: i64=0 205 i=0 206 while i<np { let f: i64=FL[i] 207 if tf_bit(f,4)==1 { var dy: i64=b[p] as i64; p=p+1; if tf_bit(f,32)==0 { dy=0-dy } y=y+dy } 208 else { if tf_bit(f,32)==0 { y=y+tf_rd16s(b,p); p=p+2 } } 209 PY[i]=y; ON[i]=tf_bit(f,1); i=i+1 } 210 // apply the transform in place (identity when called directly) 211 i=0 212 while i<np { 213 let ox: i64 = PX[i] 214 let oy: i64 = PY[i] 215 PX[i] = (ta*ox + tc*oy) / TF_F2DOT14_ONE + tdx 216 PY[i] = (tb*ox + td*oy) / TF_F2DOT14_ONE + tdy 217 i=i+1 218 } 219 var cs: i64=0 220 var ci: i64=0 221 while ci<nc { 222 let ce: i64=ep[ci] 223 let cnt: i64=ce-cs+1 224 if cnt >= 1 { 225 // a contour that starts OFF-curve: start from the implied on-curve midpoint (or the last on point) 226 var sidx: i64 = cs 227 var curx: i64=PX[cs] 228 var cury: i64=PY[cs] 229 if ON[cs] == 0 { 230 let lidx: i64 = ce 231 if ON[lidx] == 1 { curx = PX[lidx]; cury = PY[lidx] } 232 else { curx = (PX[cs] + PX[lidx]) / 2; cury = (PY[cs] + PY[lidx]) / 2 } 233 sidx = cs - 1 234 } 235 var j: i64=1 236 while j<=cnt { 237 let idx: i64=cs + (((sidx - cs) + j) % cnt) 238 if ON[idx]==1 { 239 ne = tf_edge_push(ex0, ey0, ex1, ey1, ne, curx, cury, PX[idx], PY[idx]) 240 curx=PX[idx]; cury=PY[idx]; j=j+1 241 } else { 242 let eidx: i64=cs + (((sidx - cs) + j + 1)%cnt) 243 let cxp: i64=PX[idx] 244 let cyp: i64=PY[idx] 245 var exp: i64=PX[eidx] 246 var eyp: i64=PY[eidx] 247 var consumed: i64 = 2 248 if ON[eidx]==0 { exp = (cxp + PX[eidx]) / 2; eyp = (cyp + PY[eidx]) / 2; consumed = 1 } // two off-curve points imply an on-curve midpoint 249 var t: i64=1 250 var pxp: i64=curx 251 var pyp: i64=cury 252 let SQ: i64 = TF_BEZ_STEPS*TF_BEZ_STEPS 253 while t<=TF_BEZ_STEPS { 254 let u: i64=TF_BEZ_STEPS-t 255 let qx: i64=(u*u*curx + 2*u*t*cxp + t*t*exp)/SQ 256 let qy: i64=(u*u*cury + 2*u*t*cyp + t*t*eyp)/SQ 257 ne = tf_edge_push(ex0, ey0, ex1, ey1, ne, pxp, pyp, qx, qy) 258 pxp=qx; pyp=qy; t=t+1 259 } 260 curx=exp; cury=eyp; j=j+consumed 261 } 262 } 263 } 264 cs=ce+1; ci=ci+1 265 } 266 return ne 267} 268// the simple entry every caller uses: identity transform, depth 0 269func tf_glyph_edges(b: *u8, glyf: i64, loca: i64, locfmt: i64, nglyphs: i64, gid: i64, ex0: *i64, ey0: *i64, ex1: *i64, ey1: *i64) -> i64 { 270 return tf_glyph_edges_x(b, glyf, loca, locfmt, nglyphs, gid, ex0, ey0, ex1, ey1, 0, TF_F2DOT14_ONE, 0, 0, TF_F2DOT14_ONE, 0, 0, 0) 271} 272func tf_inside(gx: i64, gy: i64, ex0: *i64, ey0: *i64, ex1: *i64, ey1: *i64, ne: i64) -> i64 { 273 var w: i64=0 274 var i: i64=0 275 while i<ne { 276 let y0: i64=ey0[i] 277 let y1: i64=ey1[i] 278 var dir: i64=0 279 if y0<=gy { if gy<y1 { dir=1 } } 280 if y1<=gy { if gy<y0 { dir=0-1 } } 281 if dir!=0 { 282 let xc: i64=ex0[i] + (gy-y0)*(ex1[i]-ex0[i])/(y1-y0) 283 if xc>gx { w=w+dir } 284 } 285 i=i+1 286 } 287 if w!=0 { return 1 } 288 return 0 289} 290 291// parse a .ttf already in memory (b, len). returns handle (as *i64), or 0 on failure. This is the ONE 292// parse core; tf_load reads the file then calls this. Surfaces whose keystone lacks sys_read_file (the 293// native-Windows GUI PE) read the bytes their own way (win32 CreateFileA/ReadFile) and call this directly. 294// Every table offset is checked against len so a truncated or foreign file refuses instead of reading 295// past its buffer. 296func tf_load_buf(b: *u8, len: i64) -> *i64 { 297 if (b as i64) == 0 { return 0 as *i64 } 298 if len <= 12 { return 0 as *i64 } 299 let nt: i64 = tf_rd16(b, 4) 300 if 12 + nt*16 > len { return 0 as *i64 } 301 let glyf: i64 = tf_find_table(b, 103,108,121,102) 302 let loca: i64 = tf_find_table(b, 108,111,99,97) 303 let head: i64 = tf_find_table(b, 104,101,97,100) 304 let cmap: i64 = tf_find_table(b, 99,109,97,112) 305 let hhea: i64 = tf_find_table(b, 104,104,101,97) 306 let hmtx: i64 = tf_find_table(b, 104,109,116,120) 307 let maxp: i64 = tf_find_table(b, 109,97,120,112) 308 if glyf<0 { return 0 as *i64 } 309 if loca<0 { return 0 as *i64 } 310 if head<0 { return 0 as *i64 } 311 if cmap<0 { return 0 as *i64 } 312 if hhea<0 { return 0 as *i64 } 313 if hmtx<0 { return 0 as *i64 } 314 if maxp<0 { return 0 as *i64 } 315 if glyf >= len { return 0 as *i64 } 316 if loca >= len { return 0 as *i64 } 317 if head + 54 > len { return 0 as *i64 } 318 if cmap + 4 > len { return 0 as *i64 } 319 if hhea + 36 > len { return 0 as *i64 } 320 if hmtx >= len { return 0 as *i64 } 321 if maxp + 6 > len { return 0 as *i64 } 322 let upem: i64 = tf_rd16(b, head+18) 323 if upem <= 0 { return 0 as *i64 } 324 let sub: i64 = tf_cmap_pick4(b, cmap, len) 325 if sub < 0 { return 0 as *i64 } 326 let fh: *i64 = sys_mmap(8*TF_HSLOTS) as *i64 327 fh[TF_H_BUF] = b as i64 328 fh[TF_H_LEN] = len 329 fh[TF_H_GLYF] = glyf 330 fh[TF_H_LOCA] = loca 331 fh[TF_H_LOCFMT] = tf_rd16s(b, head+50) 332 fh[TF_H_CMAPSUB] = sub 333 fh[TF_H_HMTX] = hmtx 334 fh[TF_H_NUMHM] = tf_rd16(b, hhea+34) 335 fh[TF_H_NGLYPHS] = tf_rd16(b, maxp+4) 336 fh[TF_H_CACHE] = (sys_mmap(TF_MAXSZ*TF_ASCII_N*TF_REC*8) as *i64) as i64 337 fh[TF_H_NSIZES] = 0 338 fh[TF_H_UPEM] = upem 339 fh[TF_H_ASC] = tf_rd16s(b, hhea+4) 340 fh[TF_H_DESC] = tf_rd16s(b, hhea+6) 341 fh[TF_H_LINEGAP] = tf_rd16s(b, hhea+8) 342 fh[TF_H_KPAIRS] = 0 343 fh[TF_H_KN] = 0 344 if fh[TF_H_NUMHM] <= 0 { return 0 as *i64 } 345 if fh[TF_H_ASC] <= fh[TF_H_DESC] { return 0 as *i64 } 346 // 'kern' table, subtable 0, format 0, horizontal: the pair array is sorted by (left<<16 | right) 347 let kern: i64 = tf_find_table(b, 107,101,114,110) 348 if kern >= 0 { if kern + 4 + TF_KERN_SUBHDR <= len { 349 let ntab: i64 = tf_rd16(b, kern+2) 350 if ntab >= 1 { 351 let st: i64 = kern + 4 352 let cov: i64 = tf_rd16(b, st+4) 353 if cov / 256 == 0 { if cov % 2 == 1 { 354 let npairs: i64 = tf_rd16(b, st+6) 355 if st + TF_KERN_SUBHDR + npairs*TF_KERN_PAIR <= len { fh[TF_H_KPAIRS] = st + TF_KERN_SUBHDR; fh[TF_H_KN] = npairs } 356 } } 357 } 358 } } 359 return fh 360} 361 362// load + parse a .ttf from a file path (needs sys_read_file -- WSL/Linux/NishiOS). returns 0 on failure. 363func tf_load(path: *u8) -> *i64 { 364 let lp: *i64 = sys_mmap(16) as *i64 365 lp[0] = 0 - 1 366 let b: *u8 = sys_read_file(path, lp) 367 if lp[0] <= 12 { return 0 as *i64 } 368 return tf_load_buf(b, lp[0]) 369} 370 371// ---- metrics API (the numbers a layout engine needs; all READ from the loaded face) ---- 372func tf_upem(fh: *i64) -> i64 { return fh[TF_H_UPEM] } 373func tf_asc(fh: *i64) -> i64 { return fh[TF_H_ASC] } 374func tf_desc(fh: *i64) -> i64 { return fh[TF_H_DESC] } 375func tf_linegap(fh: *i64) -> i64 { return fh[TF_H_LINEGAP] } 376func tf_kern_count(fh: *i64) -> i64 { return fh[TF_H_KN] } 377func tf_nglyphs(fh: *i64) -> i64 { return fh[TF_H_NGLYPHS] } 378// ascender / descender / content height in px at pixel-em ph (descender returned as a POSITIVE depth) 379func tf_asc_px(fh: *i64, ph: i64) -> i64 { return (fh[TF_H_ASC]*ph) / fh[TF_H_UPEM] } 380func tf_desc_px(fh: *i64, ph: i64) -> i64 { return ((0 - fh[TF_H_DESC])*ph) / fh[TF_H_UPEM] } 381func tf_content_px(fh: *i64, ph: i64) -> i64 { return ((fh[TF_H_ASC] - fh[TF_H_DESC])*ph) / fh[TF_H_UPEM] } 382// glyph id for a codepoint (0 = .notdef) 383func tf_gid(fh: *i64, ch: i64) -> i64 { return tf_cmap_lookup(fh[TF_H_BUF] as *u8, fh[TF_H_CMAPSUB], ch) } 384// advance width in FONT UNITS for a gid (hmtx; gids past numHM share the last advance) 385func tf_adv_units_gid(fh: *i64, gid: i64) -> i64 { 386 var hmi: i64 = gid 387 if hmi >= fh[TF_H_NUMHM] { hmi = fh[TF_H_NUMHM]-1 } 388 if hmi < 0 { hmi = 0 } 389 let at: i64 = fh[TF_H_HMTX] + hmi*4 390 if at + 2 > fh[TF_H_LEN] { return 0 } 391 return tf_rd16(fh[TF_H_BUF] as *u8, at) 392} 393func tf_adv_units(fh: *i64, ch: i64) -> i64 { return tf_adv_units_gid(fh, tf_gid(fh, ch)) } 394// advance in the browser's 1000-em unit (font_adv_em's unit): floor(aw * 1000 / upem). For Liberation Sans 395// this reproduces the estate's hand-baked oracle table exactly (space 277, i 222, m 833, W 943). 396func tf_adv_em1000(fh: *i64, ch: i64) -> i64 { return (tf_adv_units(fh, ch) * TF_EM1000) / fh[TF_H_UPEM] } 397// pair kerning in 1000-em units (negative pulls the pair closer). 0 when the face has no horizontal kern 398// table, either glyph is .notdef, or the pair is absent. Binary search over the sorted pair array. 399func tf_kern_em1000(fh: *i64, a: i64, c: i64) -> i64 { 400 let n: i64 = fh[TF_H_KN] 401 if n <= 0 { return 0 } 402 let ga: i64 = tf_gid(fh, a) 403 let gc: i64 = tf_gid(fh, c) 404 if ga == 0 { return 0 } 405 if gc == 0 { return 0 } 406 let key: i64 = ga*TF_MAGIC_65536 + gc 407 let b: *u8 = fh[TF_H_BUF] as *u8 408 let base: i64 = fh[TF_H_KPAIRS] 409 var lo: i64 = 0 410 var hi: i64 = n - 1 411 while lo <= hi { 412 let mid: i64 = (lo + hi) / 2 413 let p: i64 = base + mid*TF_KERN_PAIR 414 let k: i64 = tf_rd16(b, p)*TF_MAGIC_65536 + tf_rd16(b, p+2) 415 if k == key { return (tf_rd16s(b, p+4) * TF_EM1000) / fh[TF_H_UPEM] } 416 if k < key { lo = mid + 1 } else { hi = mid - 1 } 417 } 418 return 0 419} 420 421// get (rasterizing + caching on miss) the coverage record for char ch at pixel height ph (the em size). 422// rec: [TF_R_COV]=cov ptr (bytes, cw x chh; row 0 = ASCENDER line) [TF_R_CW]=cw [TF_R_CHH]=chh 423// [TF_R_ADV]=advance px [TF_R_LM]=left margin px. returns 0 as *i64 if no glyph. 424func tf_glyph(fh: *i64, ch: i64, ph: i64) -> *i64 { 425 if ph <= 2 { return 0 as *i64 } 426 if ch < TF_ASCII_LO { return 0 as *i64 } 427 if ch > TF_ASCII_HI { return 0 as *i64 } 428 // size slot 429 var slot: i64 = 0 - 1 430 var i: i64 = 0 431 while i < fh[TF_H_NSIZES] { if fh[TF_H_SIZE0+i]==ph { slot=i; i=fh[TF_H_NSIZES] } else { i=i+1 } } 432 if slot < 0 { 433 if fh[TF_H_NSIZES] < TF_MAXSZ { slot=fh[TF_H_NSIZES]; fh[TF_H_SIZE0+slot]=ph; fh[TF_H_NSIZES]=fh[TF_H_NSIZES]+1 } 434 else { 435 // evict the LAST slot for this size: clear its records so a stale size cannot be served 436 slot=TF_MAXSZ-1; fh[TF_H_SIZE0+slot]=ph 437 let cache0: *i64 = fh[TF_H_CACHE] as *i64 438 var z: i64 = 0 439 while z < TF_ASCII_N*TF_REC { cache0[slot*TF_ASCII_N*TF_REC + z] = 0; z = z + 1 } 440 } 441 } 442 let cache: *i64 = fh[TF_H_CACHE] as *i64 443 let rec: *i64 = ((cache as i64) + ((slot*TF_ASCII_N + (ch-TF_ASCII_LO))*TF_REC)*8) as *i64 444 if rec[TF_R_COV] != 0 { return rec } 445 let gid0: i64 = tf_cmap_lookup(fh[TF_H_BUF] as *u8, fh[TF_H_CMAPSUB], ch) 446 tf_raster(fh, gid0, ph, rec) 447 return rec 448} 449// BR30 (2026-09-02): rasterize ANY codepoint the cmap maps (Latin-1, Extended, Greek, Cyrillic ...), 450// UNCACHED here -- the caller's atlas caches by (face slot, codepoint, size), so the engine keeps its 451// small fixed ASCII cache and never grows with the page's alphabet. 0 = uncovered (gid 0) or ph too small. 452func tf_glyph_cp(fh: *i64, cp: i64, ph: i64) -> *i64 { 453 if ph <= 2 { return 0 as *i64 } 454 if cp < TF_ASCII_LO { return 0 as *i64 } 455 let gid: i64 = tf_cmap_lookup(fh[TF_H_BUF] as *u8, fh[TF_H_CMAPSUB], cp) 456 if gid <= 0 { return 0 as *i64 } 457 let rec: *i64 = sys_mmap(8*TF_REC) as *i64 458 tf_raster(fh, gid, ph, rec) 459 return rec 460} 461// BR39 (2026-09-02): the face NOTDEF cell (gid 0), for a codepoint the face cannot map -- the box every 462// browser shows without a fallback font. Uncached here like tf_glyph_cp; the atlas caches it once per size. 463func tf_glyph_notdef(fh: *i64, ph: i64) -> *i64 { 464 if ph <= 2 { return 0 as *i64 } 465 let rec: *i64 = sys_mmap(8*TF_REC) as *i64 466 tf_raster(fh, 0, ph, rec) 467 return rec 468} 469// the ONE raster body: ASCII cache path and any-codepoint path both fill their record through it 470func tf_raster(fh: *i64, gid: i64, ph: i64, rec: *i64) -> i64 { 471 let b: *u8 = fh[TF_H_BUF] as *u8 472 let upem: i64 = fh[TF_H_UPEM] 473 let asc: i64 = fh[TF_H_ASC] 474 let aw_units: i64 = tf_adv_units_gid(fh, gid) 475 var advpx: i64 = (aw_units*ph)/upem 476 if advpx < TF_MIN_ADV_PX { advpx = TF_MIN_ADV_PX } 477 let ex0: *i64 = sys_mmap(8*TF_MAXE) as *i64 478 let ey0: *i64 = sys_mmap(8*TF_MAXE) as *i64 479 let ex1: *i64 = sys_mmap(8*TF_MAXE) as *i64 480 let ey1: *i64 = sys_mmap(8*TF_MAXE) as *i64 481 let ne: i64 = tf_glyph_edges(b, fh[TF_H_GLYF], fh[TF_H_LOCA], fh[TF_H_LOCFMT], fh[TF_H_NGLYPHS], gid, ex0, ey0, ex1, ey1) 482 // ink extent in font units: a negative xmin needs a left margin, an xmax past the advance widens the cell 483 var xmin: i64 = 0 484 var xmax: i64 = aw_units 485 var k: i64 = 0 486 while k < ne { 487 if ex0[k] < xmin { xmin = ex0[k] } 488 if ex1[k] < xmin { xmin = ex1[k] } 489 if ex0[k] > xmax { xmax = ex0[k] } 490 if ex1[k] > xmax { xmax = ex1[k] } 491 k = k + 1 492 } 493 var lm: i64 = 0 494 if xmin < 0 { lm = ((0 - xmin)*ph + upem - 1)/upem } 495 var cw: i64 = (xmax*ph + upem - 1)/upem + lm + 2 496 if cw < advpx + 2 { cw = advpx + 2 } 497 let chh: i64 = ((asc - fh[TF_H_DESC])*ph)/upem + 1 498 let cov: *u8 = sys_mmap(cw*chh + 16) 499 let SS: i64 = TF_SS 500 if ne > 0 { 501 var py: i64 = 0 502 while py < chh { 503 var px: i64 = 0 504 while px < cw { 505 var cnt: i64 = 0 506 var sy: i64 = 0 507 while sy < SS { 508 var sx: i64 = 0 509 while sx < SS { 510 let gx: i64 = (((px - lm)*SS+sx)*upem)/(ph*SS) 511 let gy: i64 = asc - ((py*SS+sy)*upem)/(ph*SS) 512 cnt = cnt + tf_inside(gx, gy, ex0, ey0, ex1, ey1, ne) 513 sx = sx + 1 514 } 515 sy = sy + 1 516 } 517 var a: i64 = (cnt*255)/(SS*SS) 518 if a > 0 { a = a + (a*(255-a))/TF_CONTRAST_DIV; if a > 255 { a = 255 } } // contrast boost (stem darkening) 519 cov[py*cw+px] = a as u8 520 px = px + 1 521 } 522 py = py + 1 523 } 524 } 525 rec[TF_R_COV] = cov as i64 526 rec[TF_R_CW] = cw 527 rec[TF_R_CHH] = chh 528 rec[TF_R_ADV] = advpx 529 rec[TF_R_LM] = lm 530 rec[TF_R_PH] = ph 531 return 1 532} 533 534// draw a text run. fh = font handle; (x,y) = TOP-LEFT of the line's glyph box (its ASCENDER line); ph = 535// pixel height of the em; cellw>0 = fixed advance per char (a monospace cell); cellw==0 = proportional 536// advances from hmtx (spaces included -- no guessed space width). returns total width advanced. 537func tf_draw_text(fh: *i64, fb: *Framebuffer, x: i64, y: i64, s: *u8, n: i64, ph: i64, cr: i64, cg: i64, cb: i64, cellw: i64) -> i64 { 538 let fw: i64 = fb.width 539 let fhh: i64 = fb.height 540 let pxb: *u8 = fb.pixels 541 var pen: i64 = x 542 var i: i64 = 0 543 while i < n { 544 let ch: i64 = s[i] & 0xff 545 var adv: i64 = cellw 546 if ch >= TF_ASCII_LO { if ch <= TF_ASCII_HI { 547 let rec: *i64 = tf_glyph(fh, ch, ph) 548 if rec != (0 as *i64) { 549 let cov: *u8 = rec[TF_R_COV] as *u8 550 let cw: i64 = rec[TF_R_CW] 551 let chh: i64 = rec[TF_R_CHH] 552 let natural: i64 = rec[TF_R_ADV] 553 let lm: i64 = rec[TF_R_LM] 554 if cellw == 0 { adv = natural } 555 // draw the FULL coverage width cw (the glyph ink can extend past its own advance -- e.g. an 556 // 'i'/'l' stem near the right edge). Squeeze only when the ink is wider than the cell. 557 var dw: i64 = cw 558 var squeeze: i64 = 0 559 if cellw > 0 { if cw > cellw { dw = cellw; squeeze = 1 } } 560 var xoff: i64 = 0 - lm 561 if cellw > 0 { if cw < cellw { xoff = (cellw-cw)/2 } } 562 var gy: i64 = 0 563 while gy < chh { 564 let fy: i64 = y + gy 565 if fy >= 0 { if fy < fhh { 566 var gx: i64 = 0 567 while gx < dw { 568 var sxc: i64 = gx 569 if squeeze == 1 { sxc = (gx*cw)/dw } 570 if sxc >= cw { sxc = cw-1 } 571 let a: i64 = cov[gy*cw+sxc] as i64 572 if a > 0 { 573 let fx: i64 = pen + xoff + gx 574 if fx >= 0 { if fx < fw { 575 let o: i64 = (fy*fw+fx)*4 576 let ia: i64 = 255-a 577 pxb[o] = (((pxb[o] as i64)*ia + cr*a)/255) as u8 578 pxb[o+1] = (((pxb[o+1] as i64)*ia + cg*a)/255) as u8 579 pxb[o+2] = (((pxb[o+2] as i64)*ia + cb*a)/255) as u8 580 pxb[o+3] = 255 as u8 581 } } 582 } 583 gx = gx + 1 584 } 585 } } 586 gy = gy + 1 587 } 588 } 589 } } 590 pen = pen + adv 591 i = i + 1 592 } 593 return pen - x 594} 595 596// proportional advance of one char at ph (for measurement) -- hmtx, spaces included 597func tf_char_adv(fh: *i64, ch: i64, ph: i64) -> i64 { 598 let rec: *i64 = tf_glyph(fh, ch, ph) 599 if rec == (0 as *i64) { return (tf_adv_units_gid(fh, 0)*ph) / fh[TF_H_UPEM] } 600 return rec[TF_R_ADV] 601}