code wiki / (root) / nx_font.nx

nx_font.nx source

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