code wiki / _hdl_build / nx_font_space_tool.nx
nx_font_space_tool.nx source
↩ module page · 125 lines · 6222 B
1// nx_font_space_tool.nx -- AUTO-SPACING generator (the fix for "ransom-note rhythm"): per-glyph OPTICAL
2// sidebearings, computed not hand-set. Method (Tracy/FontForge-autowidth class): render each glyph through
3// the REAL engine, measure its ink extents in the OPTICAL BAND (x-height middle, 350..650em -- where the
4// eye reads letter spacing), allow diagonals/serif-tips to overhang the band extent by DIAG_ALLOW, then set
5// shift(ch) = SB_T - eff_left (moves the glyph so its optical left edge sits at SB_T)
6// adv(ch) = SB_T + eff_width + SB_T
7// so EVERY pair gap reads ~2*SB_T -> even rhythm (kerning of specific pairs = a later refinement axis).
8// EMITS the tables as NishiLang code on stdout (build generators, not artifacts) -> pasted into nx_font.nx
9// marked GENERATED-BY; rerun this tool after any glyph-geometry change. license_tier: ORIGINAL expect_exit: 0
10import "nx_font.nx"
11import "nx_aa_raster.nx"
12const SB_MAGIC_60000: i64 = 60000
13const SB_MAGIC_6000: i64 = 6000
14
15func st_puts(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
16func st_num(v: i64) -> i64 { let b: *u8=sys_mmap(28); var m: i64=v; if m<0{m=0-m;sys_write(1,"-" as *u8,1)} let t: *u8=sys_mmap(28); var k: i64=0; if m==0{t[0]=48 as u8;k=1} while m>0{t[k]=(48+(m%10)) as u8;m=m/10;k=k+1} var i: i64=0; while i<k{b[i]=t[k-1-i];i=i+1} sys_write(1,b,k); return 0 }
17
18const CW: i64 = 260 // canvas px (em/5 + margins)
19const CH2: i64 = 280
20const MX: i64 = 20 // margin px
21const SPX: i64 = 200 // em -> px scale (S): 1000em = 200px, 1px = 5em
22const SB_T: i64 = 50 // target optical sidebearing (em)
23const DIAG_ALLOW: i64 = 35 // how far diagonals may overhang the optical edge (em)
24
25func main() -> i64 {
26 st_puts("=== nx_font_space_tool -- optical sidebearings, GENERATED (paste into nx_font.nx) ===\n" as *u8)
27 let xs: *i64 = sys_mmap(8*SB_MAGIC_60000) as *i64
28 let ys: *i64 = sys_mmap(8*SB_MAGIC_60000) as *i64
29 let cstart: *i64 = sys_mmap(8*SB_MAGIC_6000) as *i64
30 let clen: *i64 = sys_mmap(8*SB_MAGIC_6000) as *i64
31 let np: *i64 = sys_mmap(16) as *i64
32 let nc: *i64 = sys_mmap(16) as *i64
33 let cs: *i64 = sys_mmap(8*20) as *i64
34 let sn: *i64 = sys_mmap(8*20) as *i64
35 font_trig_init(cs, sn)
36 let cov: *u8 = sys_mmap(CW*CH2 + 16)
37 let shifts: *i64 = sys_mmap(8*140) as *i64
38 let advs: *i64 = sys_mmap(8*140) as *i64
39 font_spec_reset()
40 // ★MODE: default organic (geo regen: uncomment) font_spec_geo(1); font_spec_mod(100); font_spec_swell(100)
41
42 // the glyph set: a-z A-Z 0-9 . , - : ! ? ( ) ' /
43 let set: *i64 = sys_mmap(8*90) as *i64
44 var nset: i64 = 0
45 var c: i64 = 97
46 while c <= 122 { set[nset]=c; nset=nset+1; c=c+1 }
47 c = 65
48 while c <= 90 { set[nset]=c; nset=nset+1; c=c+1 }
49 c = 48
50 while c <= 57 { set[nset]=c; nset=nset+1; c=c+1 }
51 set[nset]=46; nset=nset+1 ; set[nset]=44; nset=nset+1 ; set[nset]=45; nset=nset+1
52 set[nset]=58; nset=nset+1 ; set[nset]=33; nset=nset+1 ; set[nset]=63; nset=nset+1
53 set[nset]=40; nset=nset+1 ; set[nset]=41; nset=nset+1 ; set[nset]=39; nset=nset+1
54 set[nset]=47; nset=nset+1
55
56 var si: i64 = 0
57 while si < nset {
58 let ch: i64 = set[si]
59 np[0]=0; nc[0]=0
60 font_emit(ch, xs, ys, cstart, clen, np, nc, cs, sn, MX, MX, SPX)
61 var z: i64 = 0
62 while z < CW*CH2 { cov[z]=0 as u8; z=z+1 }
63 aa_render(xs, ys, cstart, clen, nc[0], cov, CW, CH2, 1)
64 // full ink extents (px)
65 var fl: i64 = CW
66 var fr: i64 = 0 - 1
67 var y: i64 = 0
68 while y < CH2 {
69 var x: i64 = 0
70 while x < CW { if (cov[y*CW+x] as i64) > 127 { if x < fl { fl = x } ; if x > fr { fr = x } } x = x + 1 }
71 y = y + 1
72 }
73 // optical-band extents (x-height middle 350..650em -> px rows)
74 let by0: i64 = MX + (350*SPX)/1000
75 let by1: i64 = MX + (650*SPX)/1000
76 var bl: i64 = CW
77 var br2: i64 = 0 - 1
78 y = by0
79 while y <= by1 {
80 var x: i64 = 0
81 while x < CW { if (cov[y*CW+x] as i64) > 127 { if x < bl { bl = x } ; if x > br2 { br2 = x } } x = x + 1 }
82 y = y + 1
83 }
84 var shift: i64 = 0
85 var adv: i64 = 520
86 if fr < 0 { shift = 0; adv = 300 } // no ink (shouldn't happen in the set)
87 else {
88 if br2 < 0 { bl = fl; br2 = fr } // nothing in the band (apostrophe) -> full extents
89 // effective optical edges (em): band edge, but let full ink overhang by at most DIAG_ALLOW
90 var el: i64 = (bl - MX) * 5
91 var er: i64 = (br2 - MX) * 5
92 let fle: i64 = (fl - MX) * 5
93 let fre: i64 = (fr - MX) * 5
94 if fle + DIAG_ALLOW < el { el = fle + DIAG_ALLOW }
95 if fre - DIAG_ALLOW > er { er = fre - DIAG_ALLOW }
96 shift = SB_T - el
97 adv = SB_T + (er - el) + SB_T
98 }
99 shifts[si] = shift
100 advs[si] = adv
101 st_puts(" ch " as *u8); st_num(ch); st_puts(" shift=" as *u8); st_num(shift); st_puts(" adv=" as *u8); st_num(adv); st_puts("\n" as *u8)
102 si = si + 1
103 }
104
105 // ---- emit the generated tables (paste into nx_font.nx) ----
106 st_puts("\n// ---- GENERATED-BY nx_font_space_tool (optical sidebearings; rerun after glyph edits) ----\n" as *u8)
107 st_puts("func font_sp_shift(ch: i64) -> i64 {\n" as *u8)
108 si = 0
109 while si < nset {
110 if shifts[si] != 0 {
111 st_puts(" if ch == " as *u8); st_num(set[si]); st_puts(" { return " as *u8); st_num(shifts[si]); st_puts(" }\n" as *u8)
112 }
113 si = si + 1
114 }
115 st_puts(" return 0\n}\n" as *u8)
116 st_puts("func font_adv_em_base(ch: i64) -> i64 {\n" as *u8)
117 st_puts(" if ch == 32 { return 300 }\n" as *u8)
118 si = 0
119 while si < nset {
120 st_puts(" if ch == " as *u8); st_num(set[si]); st_puts(" { return " as *u8); st_num(advs[si]); st_puts(" }\n" as *u8)
121 si = si + 1
122 }
123 st_puts(" return 520\n}\n" as *u8)
124 return 0
125}