code wiki / (root) / nx_font.nx

nx_font.nx source

↩ module page · 1392 lines · 72383 B

1// nx_font.nx -- RUNG 4 of the sovereign font BUILDER: the FONT ENGINE. A char -> its stroke-composition (in 2// 0..1000 em) + advance width; `font_text` lays out a string (pen advances per glyph). This is the table-driven 3// layer the browser calls. Glyphs are stroke-compositions (rung 1-3); growing the set = DATA. Subset here 4// (the letters for a demo phrase); the alphabet + kanji fill in the same way. ORIGINAL. 5import "nx_glyph_stroke.nx" 6import "nx_aa_raster.nx" 7 8// ---- ★FONT SPEC (the GENERATOR knobs; Metafont-style parameters -- one glyph program, many fonts) ---- 9// 0 = pioneer default. Set via font_spec() BEFORE rendering; every glyph re-derives from these: 10// fs_w pen weight em (default 132 = Regular; ~95 Light, ~175 Bold -> OpenType wght) 11// fs_over overshoot em (default 12: how far round ink kisses past the metric lines) 12// fs_passes Chaikin passes (default 1 = crisp grotesque; 2 = softer/rounder) 13// fs_caps terminal style (default 0 = butt/flat = professional; 1 = round = soft/marker style) 14// The metric lines are FUNCTIONS of these (XT/XB/SH/BB/DB/CT in font_emit), so alignment survives any knob 15// change BY CONSTRUCTION -- the property nx_font_family_gate proves mechanically. 16static fs_w: i64 17static fs_over: i64 18static fs_passes: i64 19static fs_caps: i64 20static fs_mod: i64 // stroke MODULATION percent: horizontal pen = vertical pen * mod/100 (0 -> default 88 = grotesque contrast; 100 = uniform/monoline) 21static fs_swell: i64 // WALL SWELL percent: interior CURVE walls = pen * swell/100 (0 -> default 126; 100 = no swell). Grotesques swell round walls over stems (Arial ~130). 22static fs_slant: i64 // OBLIQUE slant percent (the OpenType slnt axis): x += (baseline - y) * slant/100, sheared 23 // ABOUT THE BASELINE so alignment survives BY CONSTRUCTION. 0 = upright; ~18 = 10deg oblique. 24static fs_geo: i64 // GEOMETRIC construction: 1 = machine-regular letterforms from exact shared primitives. 25static fs_wdth: i64 // WIDTH axis (the OpenType wdth): scales centreline x-spacing (stems keep their width -> 26 // TRUE condense/extend, not squish). 100 = normal; ~80 = condensed; ~125 = extended. 27func font_spec(w: i64, over: i64, passes: i64, caps: i64) -> i64 { fs_w=w; fs_over=over; fs_passes=passes; fs_caps=caps; return 0 } 28func font_spec_mod(m: i64) -> i64 { fs_mod=m; return 0 } 29func font_spec_swell(s: i64) -> i64 { fs_swell=s; return 0 } 30func font_spec_slant(s: i64) -> i64 { fs_slant=s; return 0 } 31func font_spec_reset() -> i64 { fs_w=0; fs_over=0; fs_passes=0; fs_caps=0; fs_mod=0; fs_swell=0; fs_slant=0; fs_geo=0; fs_wdth=0; return 0 } 32func font_mod() -> i64 { if fs_mod > 0 { return fs_mod } return 88 } 33func font_swell() -> i64 { if fs_swell > 0 { return fs_swell } return 126 } 34func font_slant() -> i64 { return fs_slant } 35func font_spec_geo(g: i64) -> i64 { fs_geo=g; return 0 } 36func font_spec_wdth(w: i64) -> i64 { fs_wdth=w; return 0 } 37func font_wdth() -> i64 { if fs_wdth <= 0 { return 100 } return fs_wdth } 38// effective pen weight (em). fs_w overrides. GEOMETRIC defaults LIGHTER (85 vs 123) -- measured vs the pros: 39// our old 123 gave a 7px stem at 64px where Century Gothic=4, Segoe/Arial=5 -> ours read chunky/"meh". 85 40// lands a ~5px stem = the refined professional geometric weight. Applies to geo letters AND the i/j/punct 41// that fall through to the organic path, so the whole geometric face is one consistent colour. 42func font_weight() -> i64 { if fs_w > 0 { return fs_w } if fs_geo == 1 { return 91 } return 123 } 43func font_geo() -> i64 { return fs_geo } 44 45// ★GEOMETRIC ARC primitive: a polyline sampling the 16-gon ring from index k0 to k1 (wraps if k1<k0), scaled 46// rx/ry about (cx,cy). EXACT circles/arcs -> bowls and arches are identical by construction (machine regular). 47// Downstream Chaikin + variable-width outline smooth it. Needs the trig table (cs/sn) filled. 48func geo_arc(xs: *i64, ys: *i64, cstart: *i64, clen: *i64, np: *i64, nc: *i64, cs: *i64, sn: *i64, 49 cx: i64, cy: i64, r: i64, krange: i64, lw: i64, ox: i64, oy: i64, S: i64) -> i64 { 50 let k0: i64 = krange / 100 51 let k1: i64 = krange % 100 52 let ax: *i64 = sys_mmap(8*40) as *i64 53 let ay: *i64 = sys_mmap(8*40) as *i64 54 var kend: i64 = k1 55 if kend < k0 { kend = k1 + 16 } 56 var m: i64 = 0 57 var kk: i64 = k0 58 while kk <= kend { 59 let idx: i64 = kk % 16 60 ax[m] = cx + (r * cs[idx]) / 1000 61 ay[m] = cy + (r * sn[idx]) / 1000 62 m = m + 1 63 kk = kk + 1 64 } 65 fe_stroke(xs, ys, cstart, clen, np, nc, cs, sn, ax, ay, m, lw, ox, oy, S) 66 return 0 67} 68 69// ★ELLIPTICAL arc: like geo_arc but independent rx/ry (for m's narrow-but-full-height humps, g's tail). 70// klw packs krange + lw*100000 (16-arg cap): lw = klw/100000, krange = klw%100000, k0=krange/100, k1=krange%100. 71func geo_earc(xs: *i64, ys: *i64, cstart: *i64, clen: *i64, np: *i64, nc: *i64, cs: *i64, sn: *i64, 72 cx: i64, cy: i64, rx: i64, ry: i64, klw: i64, ox: i64, oy: i64, S: i64) -> i64 { 73 let lw: i64 = klw / 100000 74 let krange: i64 = klw % 100000 75 let k0: i64 = krange / 100 76 let k1: i64 = krange % 100 77 let ax: *i64 = sys_mmap(8*40) as *i64 78 let ay: *i64 = sys_mmap(8*40) as *i64 79 var kend: i64 = k1 80 if kend < k0 { kend = k1 + 16 } 81 var m: i64 = 0 82 var kk: i64 = k0 83 while kk <= kend { 84 let idx: i64 = kk % 16 85 ax[m] = cx + (rx * cs[idx]) / 1000 86 ay[m] = cy + (ry * sn[idx]) / 1000 87 m = m + 1 88 kk = kk + 1 89 } 90 fe_stroke(xs, ys, cstart, clen, np, nc, cs, sn, ax, ay, m, lw, ox, oy, S) 91 return 0 92} 93// ★WIDTH COMPENSATION, percent (the wght-width interaction every real family has): heavier pens need WIDER 94// letterforms or the counters CLOG (the Bold 'e' filled in -- caught by the family-sheet eyeball). Pure 95// x-scale about 0 applied to glyph geometry AND advances together, so set-width and ink stay proportional: 96// Bold(175) -> +8%, Regular(132) -> 100 (pioneer byte-identical), Light(95) -> -7% (sets a touch tighter). 97func font_ws() -> i64 { 98 var w: i64 = 123 // anchored at the Regular pen (isolated-stem-tuned) 99 if fs_w > 0 { w = fs_w } 100 return 100 + (w - 123) / 5 101} 102 103// stroke an em-space polyline (lx/ly in 0..1000) at pen (ox,oy) grid, scale S (= px*ss), width lw(em). 104// ★CHAIKIN CORNER-CUTTING (fs_passes iterations) smooths faceted polyline curves into true smooth curves 105// before stroking -- kills the "hand-drawn kink" tell on bowls (e/o/a/R/s/g...). Open-polyline variant KEEPS 106// the endpoints (stroke starts/ends stay put); 2-point stems (no interior corner) pass through straight. 107func fe_chaikin(sx: *i64, sy: *i64, n: i64, dx: *i64, dy: *i64) -> i64 { 108 if n < 3 { var k: i64=0; while k<n { dx[k]=sx[k]; dy[k]=sy[k]; k=k+1 } return n } 109 dx[0]=sx[0]; dy[0]=sy[0] 110 var m: i64 = 1 111 var i: i64 = 0 112 while i < n-1 { 113 dx[m] = (3*sx[i] + sx[i+1])/4; dy[m] = (3*sy[i] + sy[i+1])/4; m = m + 1 114 dx[m] = (sx[i] + 3*sx[i+1])/4; dy[m] = (sy[i] + 3*sy[i+1])/4; m = m + 1 115 i = i + 1 116 } 117 dx[m]=sx[n-1]; dy[m]=sy[n-1]; m = m + 1 118 return m 119} 120// ★CORNER-PRESERVING Chaikin with per-output-segment SOURCE tags. Chaikin smooths CURVES; a sharp turn 121// (>~60deg: V/W/A apexes, z corners, k joints) is a CORNER and must stay a corner -- plain corner-cutting 122// FLATTENED the V apex at 25% of its arm length (the truncated-V bug). Interior vertices whose turn has 123// cos(theta) < 500 (x1000) are re-emitted EXACTLY between their two cut points. stag_in = per-input-segment 124// swell tags; stag_out = per-OUTPUT-segment tags (junction segments take the max of the two sources). 125func fe_chaikin_cp(sx: *i64, sy: *i64, stag: *i64, n: i64, dx: *i64, dy: *i64, dtag: *i64) -> i64 { 126 if n < 3 { 127 var k: i64=0 128 while k<n { dx[k]=sx[k]; dy[k]=sy[k]; k=k+1 } 129 if n >= 2 { dtag[0]=stag[0] } 130 return n 131 } 132 dx[0]=sx[0]; dy[0]=sy[0] 133 var m: i64 = 1 134 var i: i64 = 0 135 while i < n-1 { 136 // cut pair inside input segment i; the out-segment ENTERING each new point carries a source tag 137 dx[m] = (3*sx[i] + sx[i+1])/4; dy[m] = (3*sy[i] + sy[i+1])/4 138 var tprev: i64 = stag[i] 139 if i > 0 { if stag[i-1] > tprev { tprev = stag[i-1] } } // junction from the previous segment 140 dtag[m-1] = tprev 141 m = m + 1 142 dx[m] = (sx[i] + 3*sx[i+1])/4; dy[m] = (sy[i] + 3*sy[i+1])/4 143 dtag[m-1] = stag[i] 144 m = m + 1 145 // corner preservation: if vertex i+1 turns sharply, re-emit it exactly 146 if i < n-2 { 147 let d1x: i64 = sx[i+1]-sx[i] 148 let d1y: i64 = sy[i+1]-sy[i] 149 let d2x: i64 = sx[i+2]-sx[i+1] 150 let d2y: i64 = sy[i+2]-sy[i+1] 151 let l1: i64 = gs_isqrt(d1x*d1x+d1y*d1y) 152 let l2: i64 = gs_isqrt(d2x*d2x+d2y*d2y) 153 if l1 > 0 { if l2 > 0 { 154 let cosn: i64 = (d1x*d2x + d1y*d2y) * 1000 / (l1*l2) 155 if cosn < 500 { // turn > 60deg = CORNER 156 dx[m] = sx[i+1]; dy[m] = sy[i+1] 157 var tj: i64 = stag[i] 158 if stag[i+1] > tj { tj = stag[i+1] } 159 dtag[m-1] = tj 160 m = m + 1 161 } 162 } } 163 } 164 i = i + 1 165 } 166 dx[m]=sx[n-1]; dy[m]=sy[n-1] 167 dtag[m-1] = stag[n-2] 168 m = m + 1 169 return m 170} 171func fe_stroke(xs: *i64, ys: *i64, cstart: *i64, clen: *i64, np: *i64, nc: *i64, cs: *i64, sn: *i64, 172 lx: *i64, ly: *i64, n: i64, lw: i64, ox: i64, oy: i64, S: i64) -> i64 { 173 let ax: *i64 = sys_mmap(8*512) as *i64 174 let ay: *i64 = sys_mmap(8*512) as *i64 175 let bx: *i64 = sys_mmap(8*512) as *i64 176 let by: *i64 = sys_mmap(8*512) as *i64 177 let st: *i64 = sys_mmap(8*512) as *i64 // per-SEGMENT swell tags (1 = interior curve wall) 178 let st2: *i64 = sys_mmap(8*512) as *i64 179 var k: i64 = 0 180 while k < n { ax[k]=lx[k]; ay[k]=ly[k]; k=k+1 } 181 // tag the ORIGINAL segments: interior segments of a curve polyline swell; a 2-point stroke is a STEM 182 // (never swells); the FIRST/LAST segments of open strokes are tails/junction legs (never swell) -- 183 // this is what keeps n's right leg the same weight as its left stem. Closed loops swell everywhere. 184 var closed0: i64 = 0 185 if lx[0] == lx[n-1] { if ly[0] == ly[n-1] { closed0 = 1 } } 186 k = 0 187 while k < n - 1 { 188 var t: i64 = 0 189 if closed0 == 1 { t = 1 } 190 else { if k >= 1 { if k <= n-3 { t = 1 } } } 191 st[k] = t 192 k = k + 1 193 } 194 var passes: i64 = 1 195 if fs_passes > 0 { passes = fs_passes } // generator knob (1 = crisp, 2 = soft) 196 if passes > 3 { passes = 3 } 197 var m: i64 = n 198 var p: i64 = 0 199 while p < passes { 200 let m2: i64 = fe_chaikin_cp(ax, ay, st, m, bx, by, st2) // corner-preserving + carries the tags 201 var j: i64 = 0 202 while j < m2 - 1 { st[j] = st2[j]; j = j + 1 } 203 m = m2 204 var k2: i64 = 0 205 while k2 < m { ax[k2]=bx[k2]; ay[k2]=by[k2]; k2=k2+1 } 206 p = p + 1 207 } 208 // generator knob: ROUND terminals (soft style). The cap disc extends w_end/2 past the endpoint, so INSET 209 // each open end by w_end/2 along its direction first -- the cap's ink then lands exactly where butt ink 210 // did (keeps the metric alignment: stems still end ON the baseline). Standard round-cap geometry. 211 let wh2: i64 = (lw * font_mod()) / 100 // horizontal pen (stroke contrast) 212 var w0: i64 = lw 213 var w1: i64 = lw 214 if m >= 2 { 215 var edx: i64 = ax[1]-ax[0] 216 var edy: i64 = ay[1]-ay[0] 217 if edy < 0 { edy = 0 - edy } 218 var el: i64 = gs_isqrt(edx*edx+edy*edy) 219 if el > 0 { w0 = wh2 + ((lw - wh2) * edy) / el } 220 edx = ax[m-1]-ax[m-2] 221 edy = ay[m-1]-ay[m-2] 222 if edy < 0 { edy = 0 - edy } 223 el = gs_isqrt(edx*edx+edy*edy) 224 if el > 0 { w1 = wh2 + ((lw - wh2) * edy) / el } 225 } 226 var capped: i64 = 0 227 if fs_caps == 1 { 228 var closed: i64 = 0 229 if ax[0] == ax[m-1] { if ay[0] == ay[m-1] { closed = 1 } } 230 if closed == 0 { if m >= 2 { 231 capped = 1 232 var h: i64 = w0/2 233 var ddx: i64 = ax[1]-ax[0] 234 var ddy: i64 = ay[1]-ay[0] 235 var dl: i64 = gs_isqrt(ddx*ddx+ddy*ddy) 236 if dl > h { ax[0] = ax[0] + (ddx*h)/dl; ay[0] = ay[0] + (ddy*h)/dl } 237 h = w1/2 238 ddx = ax[m-2]-ax[m-1] 239 ddy = ay[m-2]-ay[m-1] 240 dl = gs_isqrt(ddx*ddx+ddy*ddy) 241 if dl > h { ax[m-1] = ax[m-1] + (ddx*h)/dl; ay[m-1] = ay[m-1] + (ddy*h)/dl } 242 } } 243 } 244 let gx: *i64 = sys_mmap(8*512) as *i64 245 let gy: *i64 = sys_mmap(8*512) as *i64 246 let ws: i64 = (font_ws() * font_wdth()) / 100 // family width-comp x WIDTH axis (x-only; y untouched -> metrics hold) 247 let sl: i64 = font_slant() // oblique shear about the BASELINE (y=750): x += (750-y)*sl/100 248 var i: i64 = 0 249 while i < m { 250 gx[i] = ox + (((ax[i] * ws) / 100 + ((750 - ay[i]) * sl) / 100) * S) / 1000 251 gy[i] = oy + (ay[i] * S) / 1000 252 i = i + 1 253 } 254 // per-SEGMENT pen widths (grid units): role-aware modulated nib -- vertical curve walls swell toward 255 // wsw, stems/tails stay wv, horizontals thin to wh; blended by |dy|/len. Fed to the VARIABLE-WIDTH 256 // OUTLINE stroke (one smooth polygon, width interpolates continuously -> no join-disc lumps). 257 let wvg: i64 = (lw * S) / 1000 258 let whg: i64 = (wh2 * S) / 1000 259 let wswg: i64 = ((lw * font_swell()) / 100 * S) / 1000 260 let segw: *i64 = sys_mmap(8*512) as *i64 261 var closed2: i64 = 0 262 if gx[0] == gx[m-1] { if gy[0] == gy[m-1] { closed2 = 1 } } 263 i = 0 264 while i < m - 1 { 265 let sdx: i64 = gx[i+1]-gx[i] 266 var sdy: i64 = gy[i+1]-gy[i] 267 if sdy < 0 { sdy = 0 - sdy } 268 let sl: i64 = gs_isqrt(sdx*sdx + sdy*sdy) 269 var wtop: i64 = wvg 270 if st[i] == 1 { wtop = wswg } 271 var w2: i64 = wtop 272 if sl > 0 { w2 = whg + ((wtop - whg) * sdy) / sl } 273 segw[i] = w2 274 i = i + 1 275 } 276 gs_stroke_var(xs, ys, cstart, clen, np, nc, gx, gy, m, segw, closed2) 277 if capped == 1 { 278 gs_disc(xs, ys, cstart, clen, np, nc, cs, sn, gx[0], gy[0], (w0*S)/2000, 0) 279 gs_disc(xs, ys, cstart, clen, np, nc, cs, sn, gx[m-1], gy[m-1], (w1*S)/2000, 0) 280 } 281 return 0 282} 283 284// ---- GENERATED-BY nx_font_space_tool (optical sidebearings, Tracy/autowidth method: per-glyph ink 285// measured through the REAL engine in the x-height optical band; every pair gap reads ~2x SB_T=100em -> 286// EVEN RHYTHM, the fix for "ransom-note spacing"). Rerun the tool after any glyph-geometry change. ---- 287// font_sp_shift: x-offset applied at layout so each glyph's optical left edge sits at SB_T. 288func font_sp_shift(ch: i64) -> i64 { 289 if ch == 97 { return 35 } 290 if ch == 98 { return -20 } 291 if ch == 99 { return 25 } 292 if ch == 100 { return 35 } 293 if ch == 101 { return 30 } 294 if ch == 102 { return -35 } 295 if ch == 103 { return 35 } 296 if ch == 104 { return -20 } 297 if ch == 105 { return -20 } 298 if ch == 106 { return -20 } 299 if ch == 107 { return -20 } 300 if ch == 108 { return -70 } 301 if ch == 110 { return -20 } 302 if ch == 111 { return 40 } 303 if ch == 112 { return -20 } 304 if ch == 113 { return 35 } 305 if ch == 114 { return -50 } 306 if ch == 115 { return -20 } 307 if ch == 116 { return -20 } 308 if ch == 117 { return -5 } 309 if ch == 118 { return -25 } 310 if ch == 120 { return -40 } 311 if ch == 121 { return -25 } 312 if ch == 122 { return -45 } 313 if ch == 65 { return -30 } 314 if ch == 66 { return -20 } 315 if ch == 67 { return 5 } 316 if ch == 68 { return -20 } 317 if ch == 69 { return -5 } 318 if ch == 70 { return -20 } 319 if ch == 71 { return 5 } 320 if ch == 72 { return -20 } 321 if ch == 73 { return -140 } 322 if ch == 74 { return -25 } 323 if ch == 75 { return -20 } 324 if ch == 76 { return -30 } 325 if ch == 78 { return -20 } 326 if ch == 79 { return 15 } 327 if ch == 80 { return -20 } 328 if ch == 81 { return 15 } 329 if ch == 82 { return -20 } 330 if ch == 83 { return -15 } 331 if ch == 84 { return -85 } 332 if ch == 85 { return -5 } 333 if ch == 86 { return -40 } 334 if ch == 87 { return -10 } 335 if ch == 88 { return -55 } 336 if ch == 89 { return -50 } 337 if ch == 90 { return -85 } 338 if ch == 49 { return -140 } 339 if ch == 50 { return -85 } 340 if ch == 51 { return -60 } 341 if ch == 52 { return -95 } 342 if ch == 53 { return -45 } 343 if ch == 54 { return -10 } 344 if ch == 55 { return -115 } 345 if ch == 56 { return -5 } 346 if ch == 57 { return -20 } 347 if ch == 46 { return -125 } 348 if ch == 44 { return -110 } 349 if ch == 45 { return -80 } 350 if ch == 58 { return -95 } 351 if ch == 33 { return -90 } 352 if ch == 63 { return -85 } 353 if ch == 40 { return -95 } 354 if ch == 41 { return -95 } 355 if ch == 39 { return -105 } 356 if ch == 47 { return -95 } 357 return 0 358} 359func font_adv_em_base(ch: i64) -> i64 { 360 if ch == 32 { return 300 } 361 if ch == 97 { return 490 } 362 if ch == 98 { return 485 } 363 if ch == 99 { return 490 } 364 if ch == 100 { return 490 } 365 if ch == 101 { return 530 } 366 if ch == 102 { return 380 } 367 if ch == 103 { return 510 } 368 if ch == 104 { return 490 } 369 if ch == 105 { return 215 } 370 if ch == 106 { return 350 } 371 if ch == 107 { return 410 } 372 if ch == 108 { return 215 } 373 if ch == 109 { return 710 } 374 if ch == 110 { return 490 } 375 if ch == 111 { return 590 } 376 if ch == 112 { return 485 } 377 if ch == 113 { return 490 } 378 if ch == 114 { return 365 } 379 if ch == 115 { return 470 } 380 if ch == 116 { return 345 } 381 if ch == 117 { return 490 } 382 if ch == 118 { return 450 } 383 if ch == 119 { return 600 } 384 if ch == 120 { return 415 } 385 if ch == 121 { return 450 } 386 if ch == 122 { return 400 } 387 if ch == 65 { return 540 } 388 if ch == 66 { return 515 } 389 if ch == 67 { return 555 } 390 if ch == 68 { return 555 } 391 if ch == 69 { return 500 } 392 if ch == 70 { return 440 } 393 if ch == 71 { return 600 } 394 if ch == 72 { return 555 } 395 if ch == 73 { return 295 } 396 if ch == 74 { return 525 } 397 if ch == 75 { return 505 } 398 if ch == 76 { return 465 } 399 if ch == 77 { return 615 } 400 if ch == 78 { return 555 } 401 if ch == 79 { return 625 } 402 if ch == 80 { return 505 } 403 if ch == 81 { return 625 } 404 if ch == 82 { return 505 } 405 if ch == 83 { return 560 } 406 if ch == 84 { return 425 } 407 if ch == 85 { return 585 } 408 if ch == 86 { return 515 } 409 if ch == 87 { return 585 } 410 if ch == 88 { return 485 } 411 if ch == 89 { return 495 } 412 if ch == 90 { return 440 } 413 if ch == 48 { return 595 } 414 if ch == 49 { return 335 } 415 if ch == 50 { return 475 } 416 if ch == 51 { return 500 } 417 if ch == 52 { return 425 } 418 if ch == 53 { return 525 } 419 if ch == 54 { return 570 } 420 if ch == 55 { return 400 } 421 if ch == 56 { return 585 } 422 if ch == 57 { return 555 } 423 if ch == 46 { return 175 } // hand-clamped: the period dot sits below the optical band (tool sees a sliver) 424 if ch == 44 { return 185 } // hand-clamped, same reason 425 if ch == 45 { return 365 } 426 if ch == 58 { return 205 } 427 if ch == 33 { return 215 } 428 if ch == 63 { return 460 } 429 if ch == 40 { return 290 } 430 if ch == 41 { return 300 } 431 if ch == 39 { return 180 } 432 if ch == 47 { return 365 } 433 if ch == 91 { return 330 } // [ 434 if ch == 93 { return 330 } // ] 435 if ch == 37 { return 720 } // % 436 if ch == 59 { return 205 } // ; 437 if ch == 34 { return 430 } // " 438 if ch == 61 { return 600 } // = 439 if ch == 43 { return 600 } // + 440 return 520 441} 442// ---- END GENERATED ---- 443// ---- GENERATED-BY nx_font_space_tool (GEOMETRIC mode) ---- 444func font_sp_shift_geo(ch: i64) -> i64 { 445 if ch == 97 { return -40 } 446 if ch == 98 { return -35 } 447 if ch == 99 { return -40 } 448 if ch == 100 { return -40 } 449 if ch == 101 { return -40 } 450 if ch == 102 { return -70 } 451 if ch == 103 { return -40 } 452 if ch == 104 { return -35 } 453 if ch == 105 { return -35 } 454 if ch == 106 { return -30 } 455 if ch == 107 { return -35 } 456 if ch == 108 { return -85 } 457 if ch == 109 { return -35 } 458 if ch == 110 { return -35 } 459 if ch == 111 { return -40 } 460 if ch == 112 { return -35 } 461 if ch == 113 { return -40 } 462 if ch == 114 { return -35 } 463 if ch == 115 { return -50 } 464 if ch == 116 { return -55 } 465 if ch == 117 { return -35 } 466 if ch == 118 { return -35 } 467 if ch == 119 { return -20 } 468 if ch == 120 { return -50 } 469 if ch == 121 { return -35 } 470 if ch == 122 { return -45 } 471 if ch == 65 { return -45 } 472 if ch == 66 { return -35 } 473 if ch == 67 { return -30 } 474 if ch == 68 { return -35 } 475 if ch == 69 { return -35 } 476 if ch == 70 { return -35 } 477 if ch == 71 { return -30 } 478 if ch == 72 { return -35 } 479 if ch == 73 { return -175 } 480 if ch == 74 { return -45 } 481 if ch == 75 { return -35 } 482 if ch == 76 { return -45 } 483 if ch == 77 { return -15 } 484 if ch == 78 { return -35 } 485 if ch == 79 { return -20 } 486 if ch == 80 { return -35 } 487 if ch == 81 { return -20 } 488 if ch == 82 { return -35 } 489 if ch == 83 { return -30 } 490 if ch == 84 { return -85 } 491 if ch == 85 { return -40 } 492 if ch == 86 { return -50 } 493 if ch == 87 { return -30 } 494 if ch == 88 { return -65 } 495 if ch == 89 { return -60 } 496 if ch == 90 { return -65 } 497 if ch == 48 { return -35 } 498 if ch == 49 { return -155 } 499 if ch == 50 { return -80 } 500 if ch == 51 { return -60 } 501 if ch == 52 { return -45 } 502 if ch == 53 { return -50 } 503 if ch == 54 { return -45 } 504 if ch == 55 { return -115 } 505 if ch == 56 { return -40 } 506 if ch == 57 { return -55 } 507 if ch == 46 { return -110 } 508 if ch == 44 { return -85 } 509 if ch == 45 { return -80 } 510 if ch == 58 { return -110 } 511 if ch == 33 { return -105 } 512 if ch == 63 { return -100 } 513 if ch == 40 { return -130 } 514 if ch == 41 { return -120 } 515 if ch == 39 { return -120 } 516 if ch == 47 { return -105 } 517 return 0 518} 519func font_adv_em_base_geo(ch: i64) -> i64 { 520 if ch == 32 { return 300 } 521 if ch == 97 { return 610 } 522 if ch == 98 { return 610 } 523 if ch == 99 { return 505 } 524 if ch == 100 { return 610 } 525 if ch == 101 { return 605 } 526 if ch == 102 { return 330 } 527 if ch == 103 { return 610 } 528 if ch == 104 { return 615 } 529 if ch == 105 { return 185 } 530 if ch == 106 { return 310 } 531 if ch == 107 { return 385 } 532 if ch == 108 { return 185 } 533 if ch == 109 { return 705 } 534 if ch == 110 { return 615 } 535 if ch == 111 { return 605 } 536 if ch == 112 { return 610 } 537 if ch == 113 { return 610 } 538 if ch == 114 { return 330 } 539 if ch == 115 { return 390 } 540 if ch == 116 { return 285 } 541 if ch == 117 { return 615 } 542 if ch == 118 { return 430 } 543 if ch == 119 { return 560 } 544 if ch == 120 { return 395 } 545 if ch == 121 { return 430 } 546 if ch == 122 { return 400 } 547 if ch == 65 { return 510 } 548 if ch == 66 { return 500 } 549 if ch == 67 { return 505 } 550 if ch == 68 { return 505 } 551 if ch == 69 { return 435 } 552 if ch == 70 { return 425 } 553 if ch == 71 { return 540 } 554 if ch == 72 { return 525 } 555 if ch == 73 { return 225 } 556 if ch == 74 { return 475 } 557 if ch == 75 { return 480 } 558 if ch == 76 { return 415 } 559 if ch == 77 { return 580 } 560 if ch == 78 { return 525 } 561 if ch == 79 { return 555 } 562 if ch == 80 { return 460 } 563 if ch == 81 { return 555 } 564 if ch == 82 { return 480 } 565 if ch == 83 { return 510 } 566 if ch == 84 { return 425 } 567 if ch == 85 { return 520 } 568 if ch == 86 { return 495 } 569 if ch == 87 { return 545 } 570 if ch == 88 { return 465 } 571 if ch == 89 { return 475 } 572 if ch == 90 { return 460 } 573 if ch == 48 { return 525 } 574 if ch == 49 { return 285 } 575 if ch == 50 { return 445 } 576 if ch == 51 { return 470 } 577 if ch == 52 { return 475 } 578 if ch == 53 { return 485 } 579 if ch == 54 { return 500 } 580 if ch == 55 { return 405 } 581 if ch == 56 { return 515 } 582 if ch == 57 { return 485 } 583 if ch == 46 { return 175 } 584 if ch == 44 { return 185 } 585 if ch == 45 { return 365 } 586 if ch == 58 { return 175 } 587 if ch == 33 { return 185 } 588 if ch == 63 { return 405 } 589 if ch == 40 { return 245 } 590 if ch == 41 { return 240 } 591 if ch == 39 { return 150 } 592 if ch == 47 { return 345 } 593 return 520 594} 595// ---- END GEO GENERATED ---- 596func font_adv_em(ch: i64) -> i64 { 597 var b: i64 = font_adv_em_base(ch) 598 if fs_geo == 1 { b = font_adv_em_base_geo(ch) } 599 return (((b * font_ws()) / 100) * font_wdth()) / 100 // advance tracks the WIDTH axis so spacing stays proportional 600} 601func font_sp_shift_disp(ch: i64) -> i64 { if fs_geo == 1 { return font_sp_shift_geo(ch) } return font_sp_shift(ch) } 602func font_adv(ch: i64, S: i64) -> i64 { return (font_adv_em(ch) * S) / 1000 } 603 604// ★GEOMETRIC (machine-regular) lowercase: bowls = ONE exact circle reused; arches = ONE exact semicircle 605// reused; stems = exact verticals on a fixed grid. Returns 1 if it drew the glyph, 0 = fall through to the 606// organic font_emit. Monoline is expected (set mod=100, swell=100 for the "Geometric" style). 607func font_emit_geo(ch: i64, xs: *i64, ys: *i64, cstart: *i64, clen: *i64, np: *i64, nc: *i64, cs: *i64, sn: *i64, 608 ox: i64, oy: i64, S: i64) -> i64 { 609 var W: i64 = font_weight() 610 var OV: i64 = 12 611 if fs_over > 0 { OV = fs_over } 612 let WHp: i64 = (W * font_mod()) / 100 613 let V2H: i64 = (W + WHp) / 4 614 let XT: i64 = 250 - OV + V2H // round top / bottom extrema (ink kisses x-height/baseline +- overshoot) 615 let XB: i64 = 750 + OV - V2H 616 let CY: i64 = (XT + XB) / 2 // x-height circle centre y 617 let R: i64 = (XB - XT) / 2 // x-height circle radius (the MODULE -- every bowl/arch uses it) 618 let SPR: i64 = CY // arch spring line (arch = top half of the module circle) 619 let ax: *i64 = sys_mmap(8*16) as *i64 620 let ay: *i64 = sys_mmap(8*16) as *i64 621 // stem grid: left stem x=130, the module circle sits tangent to it (centre 130+R) 622 let LX: i64 = 130 623 let BC: i64 = LX + R // bowl centre x for stem+bowl letters (b d p q) 624 if ch == 111 { // o -- the module circle 625 fe_ring(xs,ys,cstart,clen,np,nc,cs,sn, 130+R, CY, R, R, W, ox,oy,S) 626 return 1 627 } 628 if ch == 99 { // c -- module circle open on the right (idx 2..14) 629 geo_arc(xs,ys,cstart,clen,np,nc,cs,sn, 130+R, CY, R, 214, W, ox,oy,S) 630 return 1 631 } 632 if ch == 101 { // e -- c-arc (open lower-right) + horizontal bar at mid 633 geo_arc(xs,ys,cstart,clen,np,nc,cs,sn, 130+R, CY, R, 216, W, ox,oy,S) // idx 2..16: bottom->left->top->right (closes top-right, opens lower-right) 634 ax[0]=130; ay[0]=CY; ax[1]=130+2*R; ay[1]=CY 635 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 636 return 1 637 } 638 if ch == 110 { // n -- left stem + top-half arch + right stem 639 ax[0]=LX;ay[0]=250; ax[1]=LX;ay[1]=750 640 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 641 geo_arc(xs,ys,cstart,clen,np,nc,cs,sn, LX+R, SPR, R, 816, W, ox,oy,S) // left->top->right semicircle 642 ax[0]=LX+2*R;ay[0]=SPR-40; ax[1]=LX+2*R;ay[1]=750 // stem overlaps into the arch (fills the junction seam) 643 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 644 return 1 645 } 646 if ch == 104 { // h -- ascender stem + arch + right stem 647 ax[0]=LX;ay[0]=110; ax[1]=LX;ay[1]=750 648 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 649 geo_arc(xs,ys,cstart,clen,np,nc,cs,sn, LX+R, SPR, R, 816, W, ox,oy,S) 650 ax[0]=LX+2*R;ay[0]=SPR-40; ax[1]=LX+2*R;ay[1]=750 // h: overlap stem into arch 651 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 652 return 1 653 } 654 if ch == 117 { // u -- left stem + bottom-half arch + right stem 655 ax[0]=LX;ay[0]=250; ax[1]=LX;ay[1]=SPR 656 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 657 geo_arc(xs,ys,cstart,clen,np,nc,cs,sn, LX+R, SPR, R, 8, W, ox,oy,S) // right->bottom->left semicircle 658 ax[0]=LX+2*R;ay[0]=250; ax[1]=LX+2*R;ay[1]=SPR 659 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 660 return 1 661 } 662 if ch == 114 { // r -- stem + top-left quarter arch 663 ax[0]=LX;ay[0]=250; ax[1]=LX;ay[1]=750 664 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 665 geo_arc(xs,ys,cstart,clen,np,nc,cs,sn, LX+R, SPR, R, 812, W, ox,oy,S) // left->top quarter 666 return 1 667 } 668 if ch == 109 { // m -- 3 stems + 2 ELLIPTICAL arches (narrow rx, FULL ry -> humps reach x-height top like n) 669 let mr: i64 = (R*3)/5 // narrow hump half-width (m is a double-arch) 670 let mk: i64 = 816 + W*100000 // packed krange(top-half 8..16) + weight 671 ax[0]=LX;ay[0]=250; ax[1]=LX;ay[1]=750 672 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 673 geo_earc(xs,ys,cstart,clen,np,nc,cs,sn, LX+mr, CY, mr, R, mk, ox,oy,S) 674 ax[0]=LX+2*mr;ay[0]=CY-40; ax[1]=LX+2*mr;ay[1]=750 // overlap into arch (seam fill) 675 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 676 geo_earc(xs,ys,cstart,clen,np,nc,cs,sn, LX+3*mr, CY, mr, R, mk, ox,oy,S) 677 ax[0]=LX+4*mr;ay[0]=CY-40; ax[1]=LX+4*mr;ay[1]=750 // overlap into arch (seam fill) 678 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 679 return 1 680 } 681 if ch == 98 { // b -- ascender stem + module circle (tangent) 682 ax[0]=LX;ay[0]=110; ax[1]=LX;ay[1]=750 683 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 684 fe_ring(xs,ys,cstart,clen,np,nc,cs,sn, BC, CY, R, R, W, ox,oy,S) 685 return 1 686 } 687 if ch == 100 { // d -- right ascender stem + module circle 688 ax[0]=LX+2*R;ay[0]=110; ax[1]=LX+2*R;ay[1]=750 689 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 690 fe_ring(xs,ys,cstart,clen,np,nc,cs,sn, BC, CY, R, R, W, ox,oy,S) 691 return 1 692 } 693 if ch == 112 { // p -- descender stem + module circle 694 ax[0]=LX;ay[0]=250; ax[1]=LX;ay[1]=900 695 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 696 fe_ring(xs,ys,cstart,clen,np,nc,cs,sn, BC, CY, R, R, W, ox,oy,S) 697 return 1 698 } 699 if ch == 113 { // q -- module circle + right descender stem 700 ax[0]=LX+2*R;ay[0]=250; ax[1]=LX+2*R;ay[1]=900 701 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 702 fe_ring(xs,ys,cstart,clen,np,nc,cs,sn, BC, CY, R, R, W, ox,oy,S) 703 return 1 704 } 705 if ch == 97 { // a -- module circle + right stem (single-storey, geometric) 706 fe_ring(xs,ys,cstart,clen,np,nc,cs,sn, BC, CY, R, R, W, ox,oy,S) 707 ax[0]=LX+2*R;ay[0]=CY-R+40; ax[1]=LX+2*R;ay[1]=750 708 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 709 return 1 710 } 711 if ch == 115 { // s -- two stacked circles sharing the CENTRE point (top circle's bottom == bottom circle's top == (sx,CY)) 712 let rs: i64 = (XB - XT) / 4 713 let sx: i64 = 130 + rs + 12 714 geo_arc(xs,ys,cstart,clen,np,nc,cs,sn, sx, CY-rs, rs, 414, W, ox,oy,S) // top bowl: centre(4)->left(8)->top(12)->upper-right(14) 715 geo_arc(xs,ys,cstart,clen,np,nc,cs,sn, sx, CY+rs, rs, 1206, W, ox,oy,S) // bottom bowl: centre(12)->right(0)->bottom(4)->lower-left(6) 716 return 1 717 } 718 if ch == 103 { // g -- single-storey, smooth gentle-J tail (Futura model): closed circle bowl + a tail that descends with 719 fe_ring(xs,ys,cstart,clen,np,nc,cs,sn, BC, CY, R, R, W, ox,oy,S) // MINIMAL rightward bulge (so it is not a 9) and hooks decisively LEFT (so it is not a q); 6 pts, Chaikin-smoothed 720 ax[0]=BC+(R*52)/100; ay[0]=712 // on the bowl, lower-right 721 ax[1]=BC+(R*58)/100; ay[1]=802 // descend nearly straight (small bulge) 722 ax[2]=BC+(R*46)/100; ay[2]=880 // begin the curve 723 ax[3]=BC+(R*4)/100; ay[3]=928 // DEEP bottom of the descender (registers at small px) 724 ax[4]=BC-(R*50)/100; ay[4]=910 // strong LEFT hook 725 ax[5]=BC-(R*88)/100; ay[5]=846 // open terminal, up-left 726 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,6, W, ox,oy,S) 727 return 1 728 } 729 return 0 // straights (l i t k v w x y z s f j m) -> organic path (already geometric enough) 730} 731 732// emit one glyph's stroke-composition at pen (ox,oy) grid, scale S. Em: baseline 750, x-height-top 250, 733// ascender-top 100, descender 900, stem width ~70. Strokes -> curves come free. 734func font_emit(ch: i64, xs: *i64, ys: *i64, cstart: *i64, clen: *i64, np: *i64, nc: *i64, cs: *i64, sn: *i64, 735 ox: i64, oy: i64, S: i64) -> i64 { 736 if fs_geo == 1 { if font_emit_geo(ch, xs, ys, cstart, clen, np, nc, cs, sn, ox, oy, S) == 1 { return 0 } } 737 let ax: *i64 = sys_mmap(8*16) as *i64 738 let ay: *i64 = sys_mmap(8*16) as *i64 739 var W: i64 = font_weight() // pen weight (em): organic Regular=123 (tuned on isolated-stem vs Arial-600); 740 // fs_w overrides (Light ~95, Bold ~160); geo defaults 85 (lighter=pro) 741 var OV: i64 = 12 // overshoot (em): how far round ink kisses past the metric lines 742 if fs_over > 0 { OV = fs_over } // generator knob 743 // ★PARAMETRIC METRIC LINES (METAFONT-style: coordinates as functions of the pen). The stroke's INK 744 // extends V2=W/2 past the centerline, so CURVE EXTREMA paths sit V2 INSIDE the intended ink line, while 745 // stem BUTT ends sit exactly ON it. This kills the systematic stem-vs-bowl misalignment (baseline bounce, 746 // uneven x-height) BY CONSTRUCTION -- change W and the whole font re-aligns. The generator core. 747 let V2: i64 = W/2 748 let WH2: i64 = (W * font_mod()) / 100 // horizontal pen (stroke contrast/modulation) 749 let V2H: i64 = (W + WH2) / 4 // curve-extremum half-pen: the segments AROUND an extremum are 750 // part-diagonal, so their width blends W..WH2 -- use the mean 751 // half-pen so extremum ink still kisses the overshoot line (at 752 // mod=100 this reduces to W/2 exactly) 753 let XT: i64 = 250 - OV + V2H // round TOP extremum: ink kisses x-height - overshoot 754 let XB: i64 = 750 + OV - V2H // round BOTTOM extremum: ink kisses baseline + overshoot 755 let SH: i64 = 250 + V2H // arch/shoulder top + x-height BAR: ink exactly AT x-height 250 756 let BB: i64 = 750 - V2H // baseline BAR: ink exactly AT baseline 750 757 let DB: i64 = 900 + OV - V2H // descender round bottom: ink kisses descender + overshoot 758 let CT: i64 = 130 - OV + V2H // cap round TOP extremum: ink kisses cap height - overshoot 759 let CB: i64 = 130 + V2H // cap corner/bar top path: ink AT cap height 130 760 let WSC: i64 = (font_ws() * font_wdth()) / 100 // family width-comp x WIDTH axis (applies to disc x-positions here; 761 // stroke geometry gets it inside fe_stroke) 762 let SL: i64 = font_slant() // oblique shear for disc x-positions (strokes get it in fe_stroke) 763 let DR: i64 = (45 * W) / 123 // i/j dot radius: scales with the pen weight 764 let PR: i64 = (57 * W) / 123 // punctuation dot radius 765 if ch == 110 { // n -- wide counter (condensed fix): stems 130/390 766 ax[0]=130;ay[0]=750; ax[1]=130;ay[1]=250 767 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 768 ax[0]=130;ay[0]=430; ax[1]=180;ay[1]=SH+28; ax[2]=260;ay[2]=SH; ax[3]=340;ay[3]=SH+28; ax[4]=390;ay[4]=430; ax[5]=390;ay[5]=750 769 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,6, W, ox,oy,S) 770 return 0 771 } 772 if ch == 104 { // h -- ascender stem + wide arch 773 ax[0]=130;ay[0]=750; ax[1]=130;ay[1]=110 774 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 775 ax[0]=130;ay[0]=430; ax[1]=180;ay[1]=SH+28; ax[2]=260;ay[2]=SH; ax[3]=340;ay[3]=SH+28; ax[4]=390;ay[4]=430; ax[5]=390;ay[5]=750 776 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,6, W, ox,oy,S) 777 return 0 778 } 779 if ch == 105 { // i 780 ax[0]=130;ay[0]=750; ax[1]=130;ay[1]=250 781 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 782 gs_disc(xs,ys,cstart,clen,np,nc,cs,sn, ox+(((130*WSC)/100 + ((750-160)*SL)/100)*S)/1000, oy+(160*S)/1000, (DR*S)/1000, 0) 783 return 0 784 } 785 if ch == 115 { // s -- double curve (extrema on the parametric lines) 786 ax[0]=372;ay[0]=XT+44; ax[1]=292;ay[1]=XT+4; ax[2]=190;ay[2]=XT; ax[3]=140;ay[3]=350; ax[4]=178;ay[4]=436 787 ax[5]=275;ay[5]=478; ax[6]=350;ay[6]=528; ax[7]=380;ay[7]=616; ax[8]=330;ay[8]=XB+4; ax[9]=222;ay[9]=XB; ax[10]=118;ay[10]=XB-42 788 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,11, W, ox,oy,S) 789 return 0 790 } 791 if ch == 116 { // t -- stem + foot hook (ink lands ~750) + crossbar ON x-height 792 ax[0]=180;ay[0]=130; ax[1]=180;ay[1]=614; ax[2]=222;ay[2]=684; ax[3]=298;ay[3]=670 793 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,4, W, ox,oy,S) 794 ax[0]=70;ay[0]=SH; ax[1]=316;ay[1]=SH 795 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 796 return 0 797 } 798 if ch == 101 { // e -- bar + open bowl on the parametric lines 799 ax[0]=95;ay[0]=505; ax[1]=395;ay[1]=505 800 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 801 ax[0]=395;ay[0]=505; ax[1]=385;ay[1]=372; ax[2]=255;ay[2]=XT; ax[3]=128;ay[3]=XT+36; ax[4]=88;ay[4]=500 802 ax[5]=126;ay[5]=XB-44; ax[6]=250;ay[6]=XB; ax[7]=378;ay[7]=XB-36 803 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,8, W, ox,oy,S) 804 return 0 805 } 806 if ch == 111 { // o -- ring on the parametric lines 807 ax[0]=432;ay[0]=500; ax[1]=388;ay[1]=XT+62; ax[2]=255;ay[2]=XT; ax[3]=122;ay[3]=XT+62; ax[4]=78;ay[4]=500 808 ax[5]=122;ay[5]=XB-62; ax[6]=255;ay[6]=XB; ax[7]=388;ay[7]=XB-62; ax[8]=432;ay[8]=500 809 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,9, W, ox,oy,S) 810 return 0 811 } 812 if ch == 114 { // r -- stem + shoulder arm at x-height 813 ax[0]=160;ay[0]=750; ax[1]=160;ay[1]=250 814 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 815 ax[0]=160;ay[0]=430; ax[1]=205;ay[1]=SH+28; ax[2]=290;ay[2]=SH; ax[3]=356;ay[3]=SH+16 816 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,4, W, ox,oy,S) 817 return 0 818 } 819 if ch == 108 { // l -- ascender vertical 820 ax[0]=180;ay[0]=110; ax[1]=180;ay[1]=750 821 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 822 return 0 823 } 824 if ch == 99 { // c -- open arc on the parametric lines 825 ax[0]=398;ay[0]=352; ax[1]=270;ay[1]=XT+4; ax[2]=140;ay[2]=XT+28; ax[3]=90;ay[3]=500; ax[4]=140;ay[4]=XB-28; ax[5]=270;ay[5]=XB+4; ax[6]=398;ay[6]=648 826 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,7, W, ox,oy,S) 827 return 0 828 } 829 if ch == 100 { // d -- bowl + right ascender stem 830 ax[0]=350;ay[0]=352; ax[1]=270;ay[1]=XT+4; ax[2]=170;ay[2]=XT; ax[3]=110;ay[3]=XT+34; ax[4]=85;ay[4]=500; ax[5]=110;ay[5]=XB-34; ax[6]=195;ay[6]=XB; ax[7]=290;ay[7]=XB+6; ax[8]=350;ay[8]=648 831 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,9, W, ox,oy,S) 832 ax[0]=350;ay[0]=110; ax[1]=350;ay[1]=750 833 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 834 return 0 835 } 836 if ch == 97 { // a -- bowl + right x-height stem 837 ax[0]=350;ay[0]=352; ax[1]=270;ay[1]=XT+4; ax[2]=170;ay[2]=XT; ax[3]=108;ay[3]=XT+34; ax[4]=85;ay[4]=500; ax[5]=108;ay[5]=XB-34; ax[6]=185;ay[6]=XB; ax[7]=290;ay[7]=XB+6; ax[8]=350;ay[8]=648 838 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,9, W, ox,oy,S) 839 ax[0]=350;ay[0]=280; ax[1]=350;ay[1]=750 840 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 841 return 0 842 } 843 if ch == 117 { // u -- wide counter + bottom curve on the parametric line 844 ax[0]=130;ay[0]=250; ax[1]=130;ay[1]=580; ax[2]=180;ay[2]=XB-28; ax[3]=255;ay[3]=XB; ax[4]=330;ay[4]=XB-28; ax[5]=390;ay[5]=580 845 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,6, W, ox,oy,S) 846 ax[0]=390;ay[0]=250; ax[1]=390;ay[1]=750 847 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 848 return 0 849 } 850 if ch == 109 { // m -- wide double counter: stems 110/350/590 851 ax[0]=110;ay[0]=750; ax[1]=110;ay[1]=250 852 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 853 ax[0]=110;ay[0]=430; ax[1]=155;ay[1]=SH+28; ax[2]=230;ay[2]=SH; ax[3]=305;ay[3]=SH+28; ax[4]=350;ay[4]=430; ax[5]=350;ay[5]=750 854 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,6, W, ox,oy,S) 855 ax[0]=350;ay[0]=430; ax[1]=395;ay[1]=SH+28; ax[2]=470;ay[2]=SH; ax[3]=545;ay[3]=SH+28; ax[4]=590;ay[4]=430; ax[5]=590;ay[5]=750 856 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,6, W, ox,oy,S) 857 return 0 858 } 859 if ch == 119 { // w -- zigzag (apex ink kisses 762) 860 ax[0]=85;ay[0]=250; ax[1]=185;ay[1]=XB; ax[2]=300;ay[2]=400; ax[3]=415;ay[3]=XB; ax[4]=515;ay[4]=250 861 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,5, W, ox,oy,S) 862 return 0 863 } 864 if ch == 112 { // p -- descender stem + bowl (bowl bottom kisses baseline) 865 ax[0]=130;ay[0]=250; ax[1]=130;ay[1]=900 866 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 867 ax[0]=130;ay[0]=344; ax[1]=215;ay[1]=SH+2; ax[2]=320;ay[2]=SH+22; ax[3]=384;ay[3]=430; ax[4]=386;ay[4]=550; ax[5]=340;ay[5]=652; ax[6]=228;ay[6]=XB; ax[7]=130;ay[7]=644 868 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,8, W, ox,oy,S) 869 return 0 870 } 871 if ch == 98 { // b -- left ascender stem + right bowl 872 ax[0]=130;ay[0]=110; ax[1]=130;ay[1]=750 873 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 874 ax[0]=130;ay[0]=344; ax[1]=215;ay[1]=SH+2; ax[2]=320;ay[2]=SH+22; ax[3]=384;ay[3]=430; ax[4]=386;ay[4]=550; ax[5]=340;ay[5]=652; ax[6]=228;ay[6]=XB; ax[7]=130;ay[7]=644 875 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,8, W, ox,oy,S) 876 return 0 877 } 878 if ch == 102 { // f -- hooked ascender (hook ink kisses ~108) + bar ON x-height 879 ax[0]=362;ay[0]=240; ax[1]=290;ay[1]=100+V2H; ax[2]=214;ay[2]=122+V2H; ax[3]=190;ay[3]=300; ax[4]=190;ay[4]=750 880 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,5, W, ox,oy,S) 881 ax[0]=85;ay[0]=SH; ax[1]=330;ay[1]=SH 882 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 883 return 0 884 } 885 if ch == 103 { // g -- bowl (kisses baseline) + descender loop (kisses 912) 886 ax[0]=355;ay[0]=352; ax[1]=270;ay[1]=XT+4; ax[2]=170;ay[2]=XT; ax[3]=110;ay[3]=XT+34; ax[4]=88;ay[4]=495; ax[5]=112;ay[5]=XB-36; ax[6]=190;ay[6]=XB; ax[7]=282;ay[7]=XB-8; ax[8]=355;ay[8]=628 887 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,9, W, ox,oy,S) 888 ax[0]=355;ay[0]=270; ax[1]=355;ay[1]=778; ax[2]=316;ay[2]=DB; ax[3]=212;ay[3]=DB+8; ax[4]=126;ay[4]=DB-32 889 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,5, W, ox,oy,S) 890 return 0 891 } 892 if ch == 106 { // j -- descender hook (kisses 912) + dot 893 ax[0]=250;ay[0]=250; ax[1]=250;ay[1]=780; ax[2]=214;ay[2]=DB; ax[3]=134;ay[3]=DB+6; ax[4]=76;ay[4]=DB-38 894 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,5, W, ox,oy,S) 895 gs_disc(xs,ys,cstart,clen,np,nc,cs,sn, ox+(((250*WSC)/100 + ((750-160)*SL)/100)*S)/1000, oy+(160*S)/1000, (DR*S)/1000, 0) 896 return 0 897 } 898 if ch == 107 { // k -- ascender stem + two diagonals 899 ax[0]=130;ay[0]=110; ax[1]=130;ay[1]=750 900 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 901 ax[0]=360;ay[0]=300; ax[1]=130;ay[1]=530 902 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 903 ax[0]=210;ay[0]=455; ax[1]=375;ay[1]=750 904 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 905 return 0 906 } 907 if ch == 113 { // q -- bowl + right descender stem 908 ax[0]=350;ay[0]=352; ax[1]=270;ay[1]=XT+4; ax[2]=170;ay[2]=XT; ax[3]=110;ay[3]=XT+34; ax[4]=85;ay[4]=500; ax[5]=110;ay[5]=XB-34; ax[6]=185;ay[6]=XB; ax[7]=290;ay[7]=XB+6; ax[8]=350;ay[8]=648 909 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,9, W, ox,oy,S) 910 ax[0]=350;ay[0]=280; ax[1]=350;ay[1]=900 911 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 912 return 0 913 } 914 if ch == 118 { // v -- two diagonals (apex ink kisses 762) 915 ax[0]=90;ay[0]=250; ax[1]=250;ay[1]=XB; ax[2]=410;ay[2]=250 916 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,3, W, ox,oy,S) 917 return 0 918 } 919 if ch == 120 { // x -- crossing diagonals 920 ax[0]=100;ay[0]=250; ax[1]=400;ay[1]=750 921 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 922 ax[0]=400;ay[0]=250; ax[1]=100;ay[1]=750 923 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 924 return 0 925 } 926 if ch == 121 { // y -- v + descender tail 927 ax[0]=90;ay[0]=250; ax[1]=250;ay[1]=670 928 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 929 ax[0]=410;ay[0]=250; ax[1]=250;ay[1]=670; ax[2]=196;ay[2]=828; ax[3]=122;ay[3]=872 930 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,4, W, ox,oy,S) 931 return 0 932 } 933 if ch == 122 { // z -- bars ON the metric lines + diagonal 934 ax[0]=95;ay[0]=SH; ax[1]=390;ay[1]=SH; ax[2]=105;ay[2]=BB; ax[3]=400;ay[3]=BB 935 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,4, W, ox,oy,S) 936 return 0 937 } 938 // ---- UPPERCASE A-Z (cap band: top 130 .. baseline 750) ---- 939 if ch == 65 { // A -- apex ink at cap height 940 ax[0]=100;ay[0]=750; ax[1]=300;ay[1]=CB; ax[2]=500;ay[2]=750 941 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,3, W, ox,oy,S) 942 ax[0]=185;ay[0]=520; ax[1]=415;ay[1]=520 943 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 944 return 0 945 } 946 if ch == 66 { // B -- bars on the cap metric lines 947 ax[0]=130;ay[0]=130; ax[1]=130;ay[1]=750 948 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 949 ax[0]=130;ay[0]=CB; ax[1]=320;ay[1]=CB; ax[2]=405;ay[2]=280; ax[3]=320;ay[3]=430; ax[4]=130;ay[4]=434 950 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,5, W, ox,oy,S) 951 ax[0]=130;ay[0]=434; ax[1]=340;ay[1]=438; ax[2]=440;ay[2]=560; ax[3]=340;ay[3]=BB; ax[4]=130;ay[4]=BB 952 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,5, W, ox,oy,S) 953 return 0 954 } 955 if ch == 67 { // C -- open arc on the cap lines 956 ax[0]=475;ay[0]=262; ax[1]=380;ay[1]=196; ax[2]=245;ay[2]=CT; ax[3]=140;ay[3]=262; ax[4]=115;ay[4]=440 957 ax[5]=140;ay[5]=618; ax[6]=245;ay[6]=XB; ax[7]=380;ay[7]=684; ax[8]=475;ay[8]=618 958 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,9, W, ox,oy,S) 959 return 0 960 } 961 if ch == 68 { // D 962 ax[0]=130;ay[0]=130; ax[1]=130;ay[1]=750 963 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 964 ax[0]=130;ay[0]=CB; ax[1]=300;ay[1]=CB; ax[2]=430;ay[2]=300; ax[3]=465;ay[3]=440; ax[4]=430;ay[4]=580; ax[5]=300;ay[5]=BB; ax[6]=130;ay[6]=BB 965 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,7, W, ox,oy,S) 966 return 0 967 } 968 if ch == 69 { // E 969 ax[0]=450;ay[0]=CB; ax[1]=130;ay[1]=CB; ax[2]=130;ay[2]=BB; ax[3]=460;ay[3]=BB 970 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,4, W, ox,oy,S) 971 ax[0]=130;ay[0]=436; ax[1]=400;ay[1]=436 972 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 973 return 0 974 } 975 if ch == 70 { // F 976 ax[0]=450;ay[0]=CB; ax[1]=130;ay[1]=CB; ax[2]=130;ay[2]=750 977 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,3, W, ox,oy,S) 978 ax[0]=130;ay[0]=436; ax[1]=390;ay[1]=436 979 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 980 return 0 981 } 982 if ch == 71 { // G -- C + inner bar 983 ax[0]=475;ay[0]=262; ax[1]=380;ay[1]=196; ax[2]=245;ay[2]=CT; ax[3]=140;ay[3]=262; ax[4]=115;ay[4]=440 984 ax[5]=140;ay[5]=618; ax[6]=245;ay[6]=XB; ax[7]=390;ay[7]=684; ax[8]=480;ay[8]=608; ax[9]=480;ay[9]=486 985 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,10, W, ox,oy,S) 986 ax[0]=480;ay[0]=486; ax[1]=340;ay[1]=486 987 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 988 return 0 989 } 990 if ch == 72 { // H 991 ax[0]=130;ay[0]=130; ax[1]=130;ay[1]=750 992 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 993 ax[0]=470;ay[0]=130; ax[1]=470;ay[1]=750 994 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 995 ax[0]=130;ay[0]=436; ax[1]=470;ay[1]=436 996 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 997 return 0 998 } 999 if ch == 73 { // I -- stem + top/bottom serifs (vs l/1) 1000 ax[0]=190;ay[0]=CB; ax[1]=390;ay[1]=CB 1001 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 1002 ax[0]=290;ay[0]=CB; ax[1]=290;ay[1]=BB 1003 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 1004 ax[0]=190;ay[0]=BB; ax[1]=390;ay[1]=BB 1005 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 1006 return 0 1007 } 1008 if ch == 74 { // J 1009 ax[0]=430;ay[0]=130; ax[1]=430;ay[1]=590; ax[2]=390;ay[2]=XB-24; ax[3]=280;ay[3]=XB; ax[4]=172;ay[4]=XB-24; ax[5]=135;ay[5]=600 1010 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,6, W, ox,oy,S) 1011 return 0 1012 } 1013 if ch == 75 { // K 1014 ax[0]=130;ay[0]=130; ax[1]=130;ay[1]=750 1015 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 1016 ax[0]=450;ay[0]=130; ax[1]=135;ay[1]=470 1017 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 1018 ax[0]=250;ay[0]=395; ax[1]=470;ay[1]=750 1019 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 1020 return 0 1021 } 1022 if ch == 76 { // L 1023 ax[0]=140;ay[0]=130; ax[1]=140;ay[1]=BB; ax[2]=450;ay[2]=BB 1024 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,3, W, ox,oy,S) 1025 return 0 1026 } 1027 if ch == 77 { // M -- corner joins ink at cap height 1028 ax[0]=110;ay[0]=750; ax[1]=112;ay[1]=CB; ax[2]=310;ay[2]=540; ax[3]=508;ay[3]=CB; ax[4]=510;ay[4]=750 1029 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,5, W, ox,oy,S) 1030 return 0 1031 } 1032 if ch == 78 { // N -- corner joins on the cap lines 1033 ax[0]=130;ay[0]=750; ax[1]=130;ay[1]=CB; ax[2]=470;ay[2]=BB; ax[3]=470;ay[3]=130 1034 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,4, W, ox,oy,S) 1035 return 0 1036 } 1037 if ch == 79 { // O -- closed ring (ry from the parametric lines) 1038 fe_ring(xs,ys,cstart,clen,np,nc,cs,sn, 300,440, 190,(XB-CT)/2, W, ox,oy,S) 1039 return 0 1040 } 1041 if ch == 80 { // P 1042 ax[0]=130;ay[0]=130; ax[1]=130;ay[1]=750 1043 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 1044 ax[0]=130;ay[0]=CB; ax[1]=330;ay[1]=CB; ax[2]=430;ay[2]=300; ax[3]=330;ay[3]=474; ax[4]=130;ay[4]=474 1045 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,5, W, ox,oy,S) 1046 return 0 1047 } 1048 if ch == 81 { // Q -- O + tail 1049 fe_ring(xs,ys,cstart,clen,np,nc,cs,sn, 300,440, 190,(XB-CT)/2, W, ox,oy,S) 1050 ax[0]=370;ay[0]=610; ax[1]=505;ay[1]=780 1051 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 1052 return 0 1053 } 1054 if ch == 82 { // R -- P + leg 1055 ax[0]=130;ay[0]=130; ax[1]=130;ay[1]=750 1056 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 1057 ax[0]=130;ay[0]=CB; ax[1]=330;ay[1]=CB; ax[2]=430;ay[2]=300; ax[3]=330;ay[3]=474; ax[4]=130;ay[4]=474 1058 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,5, W, ox,oy,S) 1059 ax[0]=280;ay[0]=474; ax[1]=470;ay[1]=750 1060 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 1061 return 0 1062 } 1063 if ch == 83 { // S -- cap double curve on the lines 1064 ax[0]=455;ay[0]=266; ax[1]=350;ay[1]=CT+12; ax[2]=205;ay[2]=CT+14; ax[3]=125;ay[3]=286; ax[4]=165;ay[4]=400 1065 ax[5]=300;ay[5]=448; ax[6]=430;ay[6]=506; ax[7]=468;ay[7]=612; ax[8]=382;ay[8]=XB-4; ax[9]=222;ay[9]=XB; ax[10]=112;ay[10]=614 1066 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,11, W, ox,oy,S) 1067 return 0 1068 } 1069 if ch == 84 { // T 1070 ax[0]=100;ay[0]=CB; ax[1]=500;ay[1]=CB 1071 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 1072 ax[0]=300;ay[0]=CB; ax[1]=300;ay[1]=750 1073 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 1074 return 0 1075 } 1076 if ch == 85 { // U 1077 ax[0]=130;ay[0]=130; ax[1]=130;ay[1]=560; ax[2]=180;ay[2]=XB-34; ax[3]=300;ay[3]=XB; ax[4]=420;ay[4]=XB-34; ax[5]=470;ay[5]=560; ax[6]=470;ay[6]=130 1078 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,7, W, ox,oy,S) 1079 return 0 1080 } 1081 if ch == 86 { // V -- apex ink kisses 762 1082 ax[0]=105;ay[0]=130; ax[1]=300;ay[1]=XB; ax[2]=495;ay[2]=130 1083 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,3, W, ox,oy,S) 1084 return 0 1085 } 1086 if ch == 87 { // W 1087 ax[0]=85;ay[0]=130; ax[1]=185;ay[1]=XB; ax[2]=305;ay[2]=360; ax[3]=425;ay[3]=XB; ax[4]=525;ay[4]=130 1088 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,5, W, ox,oy,S) 1089 return 0 1090 } 1091 if ch == 88 { // X 1092 ax[0]=115;ay[0]=130; ax[1]=485;ay[1]=750 1093 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 1094 ax[0]=485;ay[0]=130; ax[1]=115;ay[1]=750 1095 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 1096 return 0 1097 } 1098 if ch == 89 { // Y 1099 ax[0]=110;ay[0]=130; ax[1]=300;ay[1]=440 1100 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 1101 ax[0]=490;ay[0]=130; ax[1]=300;ay[1]=440; ax[2]=300;ay[2]=750 1102 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,3, W, ox,oy,S) 1103 return 0 1104 } 1105 if ch == 90 { // Z 1106 ax[0]=120;ay[0]=CB; ax[1]=470;ay[1]=CB; ax[2]=125;ay[2]=BB; ax[3]=480;ay[3]=BB 1107 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,4, W, ox,oy,S) 1108 return 0 1109 } 1110 // ---- DIGITS 0-9 (cap band, on the parametric lines) ---- 1111 if ch == 48 { // 0 1112 fe_ring(xs,ys,cstart,clen,np,nc,cs,sn, 300,440, 175,(XB-CT)/2, W, ox,oy,S) 1113 return 0 1114 } 1115 if ch == 49 { // 1 1116 ax[0]=200;ay[0]=268; ax[1]=310;ay[1]=CB; ax[2]=310;ay[2]=BB 1117 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,3, W, ox,oy,S) 1118 ax[0]=190;ay[0]=BB; ax[1]=430;ay[1]=BB 1119 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 1120 return 0 1121 } 1122 if ch == 50 { // 2 1123 ax[0]=145;ay[0]=272; ax[1]=225;ay[1]=CB+6; ax[2]=370;ay[2]=CB; ax[3]=450;ay[3]=292; ax[4]=425;ay[4]=420; ax[5]=140;ay[5]=BB; ax[6]=470;ay[6]=BB 1124 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,7, W, ox,oy,S) 1125 return 0 1126 } 1127 if ch == 51 { // 3 1128 ax[0]=150;ay[0]=246; ax[1]=250;ay[1]=CT+12; ax[2]=380;ay[2]=CT+20; ax[3]=430;ay[3]=292; ax[4]=380;ay[4]=418; ax[5]=285;ay[5]=446 1129 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,6, W, ox,oy,S) 1130 ax[0]=285;ay[0]=446; ax[1]=400;ay[1]=482; ax[2]=455;ay[2]=588; ax[3]=392;ay[3]=XB-26; ax[4]=242;ay[4]=XB; ax[5]=140;ay[5]=624 1131 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,6, W, ox,oy,S) 1132 return 0 1133 } 1134 if ch == 52 { // 4 1135 ax[0]=400;ay[0]=130; ax[1]=140;ay[1]=515; ax[2]=475;ay[2]=515 1136 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,3, W, ox,oy,S) 1137 ax[0]=400;ay[0]=130; ax[1]=400;ay[1]=750 1138 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 1139 return 0 1140 } 1141 if ch == 53 { // 5 1142 ax[0]=440;ay[0]=CB; ax[1]=165;ay[1]=CB; ax[2]=155;ay[2]=405 1143 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,3, W, ox,oy,S) 1144 ax[0]=155;ay[0]=405; ax[1]=300;ay[1]=368; ax[2]=425;ay[2]=428; ax[3]=462;ay[3]=556; ax[4]=392;ay[4]=XB-26; ax[5]=235;ay[5]=XB; ax[6]=130;ay[6]=618 1145 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,7, W, ox,oy,S) 1146 return 0 1147 } 1148 if ch == 54 { // 6 1149 ax[0]=420;ay[0]=222; ax[1]=310;ay[1]=CT+6; ax[2]=200;ay[2]=246; ax[3]=145;ay[3]=380; ax[4]=135;ay[4]=540; ax[5]=170;ay[5]=636; ax[6]=240;ay[6]=678 1150 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,7, W, ox,oy,S) 1151 fe_ring(xs,ys,cstart,clen,np,nc,cs,sn, 305,565, 158,(XB-434)/2, W, ox,oy,S) 1152 return 0 1153 } 1154 if ch == 55 { // 7 1155 ax[0]=130;ay[0]=CB; ax[1]=470;ay[1]=CB; ax[2]=260;ay[2]=750 1156 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,3, W, ox,oy,S) 1157 return 0 1158 } 1159 if ch == 56 { // 8 -- two rings on the lines 1160 fe_ring(xs,ys,cstart,clen,np,nc,cs,sn, 300,304, 148,304-CT, W, ox,oy,S) 1161 fe_ring(xs,ys,cstart,clen,np,nc,cs,sn, 300,580, 170,XB-580, W, ox,oy,S) 1162 return 0 1163 } 1164 if ch == 57 { // 9 -- mirrored 6 1165 ax[0]=180;ay[0]=658; ax[1]=290;ay[1]=XB-4; ax[2]=400;ay[2]=636; ax[3]=452;ay[3]=505; ax[4]=462;ay[4]=345; ax[5]=430;ay[5]=264; ax[6]=360;ay[6]=222 1166 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,7, W, ox,oy,S) 1167 fe_ring(xs,ys,cstart,clen,np,nc,cs,sn, 295,308, 158,308-CT, W, ox,oy,S) 1168 return 0 1169 } 1170 // ---- HEADING PUNCTUATION ---- 1171 if ch == 46 { // . 1172 gs_disc(xs,ys,cstart,clen,np,nc,cs,sn, ox+(((200*WSC)/100 + ((750-695)*SL)/100)*S)/1000, oy+(695*S)/1000, (PR*S)/1000, 0) 1173 return 0 1174 } 1175 if ch == 44 { // , 1176 gs_disc(xs,ys,cstart,clen,np,nc,cs,sn, ox+(((200*WSC)/100 + ((750-695)*SL)/100)*S)/1000, oy+(695*S)/1000, (PR*S)/1000, 0) 1177 ax[0]=210;ay[0]=740; ax[1]=155;ay[1]=855 1178 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, (80*W)/123, ox,oy,S) 1179 return 0 1180 } 1181 if ch == 45 { // - 1182 ax[0]=130;ay[0]=440; ax[1]=400;ay[1]=440 1183 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 1184 return 0 1185 } 1186 if ch == 58 { // : 1187 gs_disc(xs,ys,cstart,clen,np,nc,cs,sn, ox+(((200*WSC)/100 + ((750-360)*SL)/100)*S)/1000, oy+(360*S)/1000, (PR*S)/1000, 0) 1188 gs_disc(xs,ys,cstart,clen,np,nc,cs,sn, ox+(((200*WSC)/100 + ((750-695)*SL)/100)*S)/1000, oy+(695*S)/1000, (PR*S)/1000, 0) 1189 return 0 1190 } 1191 if ch == 33 { // ! 1192 ax[0]=200;ay[0]=135; ax[1]=200;ay[1]=545 1193 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 1194 gs_disc(xs,ys,cstart,clen,np,nc,cs,sn, ox+(((200*WSC)/100 + ((750-700)*SL)/100)*S)/1000, oy+(700*S)/1000, (PR*S)/1000, 0) 1195 return 0 1196 } 1197 if ch == 63 { // ? 1198 ax[0]=140;ay[0]=268; ax[1]=225;ay[1]=CB+6; ax[2]=365;ay[2]=CB; ax[3]=435;ay[3]=296; ax[4]=400;ay[4]=410; ax[5]=290;ay[5]=466; ax[6]=290;ay[6]=560 1199 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,7, W, ox,oy,S) 1200 gs_disc(xs,ys,cstart,clen,np,nc,cs,sn, ox+(((290*WSC)/100 + ((750-700)*SL)/100)*S)/1000, oy+(700*S)/1000, (PR*S)/1000, 0) 1201 return 0 1202 } 1203 if ch == 40 { // ( 1204 ax[0]=330;ay[0]=110; ax[1]=245;ay[1]=270; ax[2]=215;ay[2]=440; ax[3]=245;ay[3]=610; ax[4]=330;ay[4]=780 1205 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,5, W, ox,oy,S) 1206 return 0 1207 } 1208 if ch == 41 { // ) 1209 ax[0]=170;ay[0]=110; ax[1]=255;ay[1]=270; ax[2]=285;ay[2]=440; ax[3]=255;ay[3]=610; ax[4]=170;ay[4]=780 1210 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,5, W, ox,oy,S) 1211 return 0 1212 } 1213 if ch == 39 { // ' 1214 ax[0]=200;ay[0]=130; ax[1]=190;ay[1]=270 1215 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, (85*W)/123, ox,oy,S) 1216 return 0 1217 } 1218 if ch == 47 { // / 1219 ax[0]=400;ay[0]=110; ax[1]=160;ay[1]=790 1220 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 1221 return 0 1222 } 1223 if ch == 91 { // [ -- bracket 1224 ax[0]=320;ay[0]=140; ax[1]=200;ay[1]=140; ax[2]=200;ay[2]=760; ax[3]=320;ay[3]=760 1225 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,4, W, ox,oy,S) 1226 return 0 1227 } 1228 if ch == 93 { // ] 1229 ax[0]=180;ay[0]=140; ax[1]=300;ay[1]=140; ax[2]=300;ay[2]=760; ax[3]=180;ay[3]=760 1230 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,4, W, ox,oy,S) 1231 return 0 1232 } 1233 if ch == 37 { // % -- diagonal + two small rings 1234 ax[0]=500;ay[0]=150; ax[1]=160;ay[1]=750 1235 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 1236 fe_ring(xs,ys,cstart,clen,np,nc,cs,sn, 250,270, 100,120, (80*W)/123, ox,oy,S) 1237 fe_ring(xs,ys,cstart,clen,np,nc,cs,sn, 470,630, 100,120, (80*W)/123, ox,oy,S) 1238 return 0 1239 } 1240 if ch == 59 { // ; -- top dot + comma tail 1241 gs_disc(xs,ys,cstart,clen,np,nc,cs,sn, ox+(((200*WSC)/100 + ((750-360)*SL)/100)*S)/1000, oy+(360*S)/1000, (PR*S)/1000, 0) 1242 gs_disc(xs,ys,cstart,clen,np,nc,cs,sn, ox+(((200*WSC)/100 + ((750-695)*SL)/100)*S)/1000, oy+(695*S)/1000, (PR*S)/1000, 0) 1243 ax[0]=210;ay[0]=740; ax[1]=155;ay[1]=855 1244 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, (80*W)/123, ox,oy,S) 1245 return 0 1246 } 1247 if ch == 34 { // " -- double quote 1248 ax[0]=180;ay[0]=140; ax[1]=170;ay[1]=290 1249 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, (85*W)/123, ox,oy,S) 1250 ax[0]=320;ay[0]=140; ax[1]=310;ay[1]=290 1251 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, (85*W)/123, ox,oy,S) 1252 return 0 1253 } 1254 if ch == 61 { // = -- two bars 1255 ax[0]=140;ay[0]=390; ax[1]=460;ay[1]=390 1256 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 1257 ax[0]=140;ay[0]=530; ax[1]=460;ay[1]=530 1258 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 1259 return 0 1260 } 1261 if ch == 43 { // + -- cross 1262 ax[0]=300;ay[0]=300; ax[1]=300;ay[1]=600 1263 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 1264 ax[0]=150;ay[0]=450; ax[1]=450;ay[1]=450 1265 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, ax,ay,2, W, ox,oy,S) 1266 return 0 1267 } 1268 return 0 // space / unknown -> just advance 1269} 1270 1271// closed 16-point ellipse ring stroked as a polyline (O, 0, Q, 8-bowls). cx,cy center; rx,ry radii (em). 1272// 16 points (matches the 16-gon join discs -> one consistent trig table -> smooth, no wobble). 1273func fe_ring(xs: *i64, ys: *i64, cstart: *i64, clen: *i64, np: *i64, nc: *i64, cs: *i64, sn: *i64, 1274 cx: i64, cy: i64, rx: i64, ry: i64, lw: i64, ox: i64, oy: i64, S: i64) -> i64 { 1275 let rx2: *i64 = sys_mmap(8*20) as *i64 1276 let ry2: *i64 = sys_mmap(8*20) as *i64 1277 var k: i64 = 0 1278 while k < 16 { 1279 rx2[k] = cx + (rx * cs[k]) / 1000 1280 ry2[k] = cy + (ry * sn[k]) / 1000 1281 k = k + 1 1282 } 1283 rx2[16] = rx2[0] 1284 ry2[16] = ry2[0] 1285 fe_stroke(xs,ys,cstart,clen,np,nc,cs,sn, rx2,ry2,17, lw, ox,oy,S) 1286 return 0 1287} 1288 1289// which chars have a vector glyph (headings switch to the vector font only when a whole run qualifies) 1290func font_has(ch: i64) -> i64 { 1291 if ch == 32 { return 1 } 1292 if ch >= 97 { if ch <= 122 { return 1 } } // a-z 1293 if ch >= 65 { if ch <= 90 { return 1 } } // A-Z 1294 if ch >= 48 { if ch <= 57 { return 1 } } // 0-9 1295 if ch == 46 { return 1 } 1296 if ch == 44 { return 1 } 1297 if ch == 45 { return 1 } 1298 if ch == 58 { return 1 } 1299 if ch == 33 { return 1 } 1300 if ch == 63 { return 1 } 1301 if ch == 40 { return 1 } 1302 if ch == 41 { return 1 } 1303 if ch == 39 { return 1 } 1304 if ch == 47 { return 1 } 1305 return 0 1306} 1307func font_run_has(text: *u8, len: i64) -> i64 { 1308 var i: i64 = 0 1309 while i < len { if font_has(text[i] & 0xff) == 0 { return 0 } i = i + 1 } 1310 return 1 1311} 1312// EXTENDED coverage (the glyphs added for the browser: % [ ] ; " = +). Used ONLY by the GUI/atlas fast 1313// path so the EXACT gate path (font_run_has above) is byte-unchanged -> no NishiOS/OCR/polish regression. 1314func font_has_ext(ch: i64) -> i64 { 1315 if font_has(ch) == 1 { return 1 } 1316 if ch == 37 { return 1 } 1317 if ch == 91 { return 1 } 1318 if ch == 93 { return 1 } 1319 if ch == 59 { return 1 } 1320 if ch == 34 { return 1 } 1321 if ch == 61 { return 1 } 1322 if ch == 43 { return 1 } 1323 return 0 1324} 1325func font_run_has_ext(text: *u8, len: i64) -> i64 { 1326 var i: i64 = 0 1327 while i < len { if font_has_ext(text[i] & 0xff) == 0 { return 0 } i = i + 1 } 1328 return 1 1329} 1330// fill the 16-step trig table (22.5deg steps) that BOTH gs_disc (round joins/caps on every glyph) AND 1331// fe_ring use -- one consistent table. ★BUG FIXED: the old 12-entry init left cs[12..15] uninitialized, 1332// so the 16-gon join discs read garbage -> malformed joins = the heading stroke WOBBLE. Now 16 clean. 1333func font_trig_init(cs: *i64, sn: *i64) -> i64 { 1334 cs[0]=1000; sn[0]=0 1335 cs[1]=924; sn[1]=383 1336 cs[2]=707; sn[2]=707 1337 cs[3]=383; sn[3]=924 1338 cs[4]=0; sn[4]=1000 1339 cs[5]=0-383; sn[5]=924 1340 cs[6]=0-707; sn[6]=707 1341 cs[7]=0-924; sn[7]=383 1342 cs[8]=0-1000;sn[8]=0 1343 cs[9]=0-924; sn[9]=0-383 1344 cs[10]=0-707;sn[10]=0-707 1345 cs[11]=0-383;sn[11]=0-924 1346 cs[12]=0; sn[12]=0-1000 1347 cs[13]=383; sn[13]=0-924 1348 cs[14]=707; sn[14]=0-707 1349 cs[15]=924; sn[15]=0-383 1350 return 0 1351} 1352// ---- VECTOR METRICS for the measured layout (heading runs, scale sc): em size 15*sc px ---- 1353func font_vec_adv_px(ch: i64, sc: i64) -> i64 { return (font_adv_em(ch) * 15 * sc) / 1000 } 1354func font_vec_text_w(text: *u8, start: i64, end: i64, sc: i64) -> i64 { 1355 var w: i64 = 0 1356 var i: i64 = start 1357 while i < end { w = w + font_vec_adv_px(text[i] & 0xff, sc); i = i + 1 } 1358 return w 1359} 1360func font_vec_next_break(text: *u8, len: i64, avail_px: i64, start: i64, sc: i64) -> i64 { 1361 if start >= len { return len } 1362 var w: i64 = 0 1363 var i: i64 = start 1364 var brk: i64 = 0 - 1 1365 while i < len { 1366 w = w + font_vec_adv_px(text[i] & 0xff, sc) 1367 if w > avail_px { 1368 if brk > start { return brk + 1 } 1369 if i > start { return i } 1370 return start + 1 1371 } 1372 if (text[i] & 0xff) == 32 { brk = i } 1373 i = i + 1 1374 } 1375 return len 1376} 1377 1378// lay out + emit a whole string at (ox,oy) grid, scale S. Returns the pen x after the string. 1379func font_text(text: *u8, len: i64, xs: *i64, ys: *i64, cstart: *i64, clen: *i64, np: *i64, nc: *i64, 1380 cs: *i64, sn: *i64, ox: i64, oy: i64, S: i64) -> i64 { 1381 var pen: i64 = ox 1382 var i: i64 = 0 1383 while i < len { 1384 let ch: i64 = text[i] & 0xff 1385 // optical spacing: shift each glyph so its measured optical left edge sits at the target sidebearing 1386 let sh: i64 = (((((font_sp_shift_disp(ch) * font_ws()) / 100) * font_wdth()) / 100) * S) / 1000 1387 font_emit(ch, xs, ys, cstart, clen, np, nc, cs, sn, pen + sh, oy, S) 1388 pen = pen + font_adv(ch, S) 1389 i = i + 1 1390 } 1391 return pen 1392}