code wiki / _hdl_build / nx_ttf_fontlib.nx
nx_ttf_fontlib.nx source
↩ module page · 341 lines · 13954 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, cmap format-4, glyf outline decode
4// (flags/short/same/repeat), quadratic Bezier flattening, NONZERO-WINDING scan conversion with SSxSS
5// supersampled ANTI-ALIASED coverage -- plus what the demo lacked: a per-(char,size) COVERAGE CACHE
6// (each glyph rasterizes once per size), REAL per-glyph advances from hmtx (proportional metrics), a
7// mild contrast curve (stem darkening at small sizes), and an RGBA-framebuffer ALPHA-BLEND draw
8// (nx_framebuffer px = R,G,B,A) -- the piece the CSS-on-native arc named as "R4b the browser wire-in".
9// Fonts: our own emitted web_assets/nishi_sans.ttf (96 glyphs, FULL printable ASCII). No FreeType, no GDI.
10// license_tier: ORIGINAL
11import "nx_syscalls.nx"
12import "nx_paint_solid_rect.nx"
13const TF_MAGIC_32768: i64 = 32768
14const TF_MAGIC_65536: i64 = 65536
15const TF_MAGIC_16777216: i64 = 16777216
16const TF_MAGIC_2048: i64 = 2048
17
18func tf_rd16(b: *u8, o: i64) -> i64 { return (b[o] as i64)*256 + (b[o+1] as i64) }
19func 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 }
20func 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) }
21func tf_bit(f: i64, m: i64) -> i64 { if (f/m)%2 == 1 { return 1 } return 0 }
22
23func tf_find_table(b: *u8, t0: i64, t1: i64, t2: i64, t3: i64) -> i64 {
24 let nt: i64=tf_rd16(b,4)
25 var i: i64=0
26 while i<nt { let d: i64=12+i*16
27 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) } } } }
28 i=i+1 }
29 return 0 - 1
30}
31func tf_cmap_lookup(b: *u8, sub: i64, c: i64) -> i64 {
32 let segX2: i64=tf_rd16(b,sub+6)
33 let segCount: i64=segX2/2
34 var i: i64=0
35 while i<segCount {
36 let endc: i64=tf_rd16(b,sub+14+i*2)
37 if endc>=c {
38 let startc: i64=tf_rd16(b,sub+16+segX2+i*2)
39 if startc<=c {
40 let iro: i64=tf_rd16(b,sub+16+segX2*3+i*2)
41 if iro==0 { let delta: i64=tf_rd16s(b,sub+16+segX2*2+i*2); return (c+delta)%TF_MAGIC_65536 }
42 return 0
43 }
44 return 0
45 }
46 i=i+1
47 }
48 return 0
49}
50// decode glyph gid -> flattened line edges. returns edge count (max MAXE).
51func tf_glyph_edges(b: *u8, glyf: i64, loca: i64, locfmt: i64, gid: i64, ex0: *i64, ey0: *i64, ex1: *i64, ey1: *i64) -> i64 {
52 var g0: i64=0
53 var g1: i64=0
54 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) }
55 if g1<=g0 { return 0 }
56 let gp: i64=glyf+g0
57 let nc: i64=tf_rd16s(b,gp)
58 if nc<=0 { return 0 }
59 let ep: *i64=sys_mmap(512) as *i64
60 var i: i64=0
61 while i<nc { ep[i]=tf_rd16(b,gp+10+i*2); i=i+1 }
62 let np: i64=ep[nc-1]+1
63 let instr: i64=tf_rd16(b,gp+10+nc*2)
64 var p: i64=gp+10+nc*2+2+instr
65 let FL: *i64=sys_mmap(TF_MAGIC_2048) as *i64
66 let PX: *i64=sys_mmap(TF_MAGIC_2048) as *i64
67 let PY: *i64=sys_mmap(TF_MAGIC_2048) as *i64
68 let ON: *i64=sys_mmap(TF_MAGIC_2048) as *i64
69 var fi: i64=0
70 while fi<np { let f: i64=b[p] as i64; p=p+1; FL[fi]=f; fi=fi+1
71 if tf_bit(f,8)==1 { var rep: i64=b[p] as i64; p=p+1; while rep>0 { FL[fi]=f; fi=fi+1; rep=rep-1 } } }
72 var x: i64=0
73 i=0
74 while i<np { let f: i64=FL[i]
75 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 }
76 else { if tf_bit(f,16)==0 { x=x+tf_rd16s(b,p); p=p+2 } }
77 PX[i]=x; i=i+1 }
78 var y: i64=0
79 i=0
80 while i<np { let f: i64=FL[i]
81 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 }
82 else { if tf_bit(f,32)==0 { y=y+tf_rd16s(b,p); p=p+2 } }
83 PY[i]=y; ON[i]=tf_bit(f,1); i=i+1 }
84 var ne: i64=0
85 var cs: i64=0
86 var ci: i64=0
87 while ci<nc {
88 let ce: i64=ep[ci]
89 let cnt: i64=ce-cs+1
90 var curx: i64=PX[cs]
91 var cury: i64=PY[cs]
92 var j: i64=1
93 while j<=cnt {
94 let idx: i64=cs + (j%cnt)
95 if ON[idx]==1 {
96 if ne<500 { ex0[ne]=curx; ey0[ne]=cury; ex1[ne]=PX[idx]; ey1[ne]=PY[idx]; ne=ne+1 }
97 curx=PX[idx]; cury=PY[idx]; j=j+1
98 } else {
99 let eidx: i64=cs + ((j+1)%cnt)
100 let cxp: i64=PX[idx]
101 let cyp: i64=PY[idx]
102 let exp: i64=PX[eidx]
103 let eyp: i64=PY[eidx]
104 var t: i64=1
105 var pxp: i64=curx
106 var pyp: i64=cury
107 while t<=6 {
108 let u: i64=6-t
109 let qx: i64=(u*u*curx + 2*u*t*cxp + t*t*exp)/36
110 let qy: i64=(u*u*cury + 2*u*t*cyp + t*t*eyp)/36
111 if ne<500 { ex0[ne]=pxp; ey0[ne]=pyp; ex1[ne]=qx; ey1[ne]=qy; ne=ne+1 }
112 pxp=qx; pyp=qy; t=t+1
113 }
114 curx=exp; cury=eyp; j=j+2
115 }
116 }
117 cs=ce+1; ci=ci+1
118 }
119 return ne
120}
121func tf_inside(gx: i64, gy: i64, ex0: *i64, ey0: *i64, ex1: *i64, ey1: *i64, ne: i64) -> i64 {
122 var w: i64=0
123 var i: i64=0
124 while i<ne {
125 let y0: i64=ey0[i]
126 let y1: i64=ey1[i]
127 var dir: i64=0
128 if y0<=gy { if gy<y1 { dir=1 } }
129 if y1<=gy { if gy<y0 { dir=0-1 } }
130 if dir!=0 {
131 let xc: i64=ex0[i] + (gy-y0)*(ex1[i]-ex0[i])/(y1-y0)
132 if xc>gx { w=w+dir }
133 }
134 i=i+1
135 }
136 if w!=0 { return 1 }
137 return 0
138}
139
140// handle slots: [0]=buf [1]=glyf [2]=loca [3]=locfmt [4]=cmap_sub [5]=hmtx [6]=numHM [7]=cache [8]=nsizes [9..24]=sizes
141const TF_MAXSZ: i64 = 16
142const TF_ASC: i64 = 760 // em top of the pixel box (hhea ascent); box spans [ASC-1000 .. ASC]
143
144// parse a .ttf already in memory (b, len). returns handle (as *i64), or 0 on failure. This is the core;
145// tf_load reads the file then calls this. Surfaces whose keystone lacks sys_read_file (the native-Windows
146// GUI PE) read the bytes their own way (win32 CreateFileA/ReadFile) and call this directly.
147func tf_load_buf(b: *u8, len: i64) -> *i64 {
148 if (b as i64) == 0 { return 0 as *i64 }
149 if len <= 12 { return 0 as *i64 }
150 let glyf: i64 = tf_find_table(b, 103,108,121,102)
151 let loca: i64 = tf_find_table(b, 108,111,99,97)
152 let head: i64 = tf_find_table(b, 104,101,97,100)
153 let cmap: i64 = tf_find_table(b, 99,109,97,112)
154 let hhea: i64 = tf_find_table(b, 104,104,101,97)
155 let hmtx: i64 = tf_find_table(b, 104,109,116,120)
156 if glyf<0 { return 0 as *i64 }
157 if loca<0 { return 0 as *i64 }
158 if head<0 { return 0 as *i64 }
159 if cmap<0 { return 0 as *i64 }
160 if hhea<0 { return 0 as *i64 }
161 if hmtx<0 { return 0 as *i64 }
162 let fh: *i64 = sys_mmap(8*32) as *i64
163 fh[0] = b as i64
164 fh[1] = glyf
165 fh[2] = loca
166 fh[3] = tf_rd16s(b, head+50)
167 fh[4] = cmap + tf_rd32(b, cmap+8)
168 fh[5] = hmtx
169 fh[6] = tf_rd16(b, hhea+34)
170 fh[7] = (sys_mmap(TF_MAXSZ*96*4*8) as *i64) as i64
171 fh[8] = 0
172 return fh
173}
174
175// load + parse a .ttf from a file path (needs sys_read_file -- WSL/Linux/NishiOS). returns 0 on failure.
176func tf_load(path: *u8) -> *i64 {
177 let lp: *i64 = sys_mmap(16) as *i64
178 lp[0] = 0 - 1
179 let b: *u8 = sys_read_file(path, lp)
180 if lp[0] <= 12 { return 0 as *i64 }
181 let glyf: i64 = tf_find_table(b, 103,108,121,102)
182 let loca: i64 = tf_find_table(b, 108,111,99,97)
183 let head: i64 = tf_find_table(b, 104,101,97,100)
184 let cmap: i64 = tf_find_table(b, 99,109,97,112)
185 let hhea: i64 = tf_find_table(b, 104,104,101,97)
186 let hmtx: i64 = tf_find_table(b, 104,109,116,120)
187 if glyf<0 { return 0 as *i64 }
188 if loca<0 { return 0 as *i64 }
189 if head<0 { return 0 as *i64 }
190 if cmap<0 { return 0 as *i64 }
191 if hhea<0 { return 0 as *i64 }
192 if hmtx<0 { return 0 as *i64 }
193 let fh: *i64 = sys_mmap(8*32) as *i64
194 fh[0] = b as i64
195 fh[1] = glyf
196 fh[2] = loca
197 fh[3] = tf_rd16s(b, head+50)
198 fh[4] = cmap + tf_rd32(b, cmap+8)
199 fh[5] = hmtx
200 fh[6] = tf_rd16(b, hhea+34)
201 fh[7] = (sys_mmap(TF_MAXSZ*96*4*8) as *i64) as i64
202 fh[8] = 0
203 return fh
204}
205
206// get (rasterizing + caching on miss) the coverage record for char ch at pixel height ph.
207// rec: [0]=cov ptr (bytes, cw x chh) [1]=cw [2]=chh [3]=advance px. returns 0 as *i64 if no glyph.
208func tf_glyph(fh: *i64, ch: i64, ph: i64) -> *i64 {
209 if ph <= 2 { return 0 as *i64 }
210 if ch < 32 { return 0 as *i64 }
211 if ch > 126 { return 0 as *i64 }
212 // size slot
213 var slot: i64 = 0 - 1
214 var i: i64 = 0
215 while i < fh[8] { if fh[9+i]==ph { slot=i; i=fh[8] } else { i=i+1 } }
216 if slot < 0 {
217 if fh[8] < TF_MAXSZ { slot=fh[8]; fh[9+slot]=ph; fh[8]=fh[8]+1 }
218 else { slot=TF_MAXSZ-1; fh[9+slot]=ph }
219 }
220 let cache: *i64 = fh[7] as *i64
221 let rec: *i64 = ((cache as i64) + ((slot*96 + (ch-32))*4)*8) as *i64
222 if rec[0] != 0 { return rec }
223 // rasterize
224 let b: *u8 = fh[0] as *u8
225 var gid: i64 = tf_cmap_lookup(b, fh[4], ch)
226 let numHM: i64 = fh[6]
227 var hmi: i64 = gid
228 if hmi >= numHM { hmi = numHM-1 }
229 let aw_em: i64 = tf_rd16(b, fh[5] + hmi*4)
230 var advpx: i64 = (aw_em*ph)/1000
231 if advpx < 2 { advpx = 2 }
232 let ex0: *i64 = sys_mmap(8*512) as *i64
233 let ey0: *i64 = sys_mmap(8*512) as *i64
234 let ex1: *i64 = sys_mmap(8*512) as *i64
235 let ey1: *i64 = sys_mmap(8*512) as *i64
236 let ne: i64 = tf_glyph_edges(b, fh[1], fh[2], fh[3], gid, ex0, ey0, ex1, ey1)
237 let cw: i64 = advpx + 2
238 let chh: i64 = ph + 1
239 let cov: *u8 = sys_mmap(cw*chh + 16)
240 let SS: i64 = 4
241 if ne > 0 {
242 var py: i64 = 0
243 while py < chh {
244 var px: i64 = 0
245 while px < cw {
246 var cnt: i64 = 0
247 var sy: i64 = 0
248 while sy < SS {
249 var sx: i64 = 0
250 while sx < SS {
251 let gx: i64 = ((px*SS+sx)*1000)/(ph*SS)
252 let gy: i64 = TF_ASC - ((py*SS+sy)*1000)/(ph*SS)
253 cnt = cnt + tf_inside(gx, gy, ex0, ey0, ex1, ey1, ne)
254 sx = sx + 1
255 }
256 sy = sy + 1
257 }
258 var a: i64 = (cnt*255)/(SS*SS)
259 if a > 0 { a = a + (a*(255-a))/170; if a > 255 { a = 255 } } // contrast boost (stem darkening)
260 cov[py*cw+px] = a as u8
261 px = px + 1
262 }
263 py = py + 1
264 }
265 }
266 rec[0] = cov as i64
267 rec[1] = cw
268 rec[2] = chh
269 rec[3] = advpx
270 return rec
271}
272
273// draw a text run. fh = font handle; (x,y) = TOP-LEFT of the line's glyph box; ph = pixel height of the em
274// box; cellw>0 = fixed advance per char (the browser layout's cell); cellw==0 = proportional advances.
275// returns total width advanced.
276func 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 {
277 let fw: i64 = fb.width
278 let fhh: i64 = fb.height
279 let pxb: *u8 = fb.pixels
280 var pen: i64 = x
281 var i: i64 = 0
282 while i < n {
283 let ch: i64 = s[i] & 0xff
284 var adv: i64 = cellw
285 if ch >= 33 { if ch <= 126 {
286 let rec: *i64 = tf_glyph(fh, ch, ph)
287 if rec != (0 as *i64) {
288 let cov: *u8 = rec[0] as *u8
289 let cw: i64 = rec[1]
290 let chh: i64 = rec[2]
291 let natural: i64 = rec[3]
292 if cellw == 0 { adv = natural }
293 // draw the FULL coverage width cw (the glyph ink can extend past its own advance -- e.g. an
294 // 'i'/'l' stem near the right edge). Squeeze only when the ink is wider than the cell.
295 var dw: i64 = cw
296 var squeeze: i64 = 0
297 if cellw > 0 { if cw > cellw { dw = cellw; squeeze = 1 } }
298 var xoff: i64 = 0
299 if cellw > 0 { if cw < cellw { xoff = (cellw-cw)/2 } }
300 var gy: i64 = 0
301 while gy < chh {
302 let fy: i64 = y + gy
303 if fy >= 0 { if fy < fhh {
304 var gx: i64 = 0
305 while gx < dw {
306 var sxc: i64 = gx
307 if squeeze == 1 { sxc = (gx*cw)/dw }
308 if sxc >= cw { sxc = cw-1 }
309 let a: i64 = cov[gy*cw+sxc] as i64
310 if a > 0 {
311 let fx: i64 = pen + xoff + gx
312 if fx >= 0 { if fx < fw {
313 let o: i64 = (fy*fw+fx)*4
314 let ia: i64 = 255-a
315 pxb[o] = (((pxb[o] as i64)*ia + cr*a)/255) as u8
316 pxb[o+1] = (((pxb[o+1] as i64)*ia + cg*a)/255) as u8
317 pxb[o+2] = (((pxb[o+2] as i64)*ia + cb*a)/255) as u8
318 pxb[o+3] = 255 as u8
319 } }
320 }
321 gx = gx + 1
322 }
323 } }
324 gy = gy + 1
325 }
326 }
327 } }
328 if cellw == 0 { if ch == 32 { adv = (380*ph)/1000 } }
329 pen = pen + adv
330 i = i + 1
331 }
332 return pen - x
333}
334
335// proportional advance of one char at ph (for measurement)
336func tf_char_adv(fh: *i64, ch: i64, ph: i64) -> i64 {
337 if ch == 32 { return (380*ph)/1000 }
338 let rec: *i64 = tf_glyph(fh, ch, ph)
339 if rec == (0 as *i64) { return (600*ph)/1000 }
340 return rec[3]
341}