code wiki / (root) / nx_cdmap_raster_lib.nx

nx_cdmap_raster_lib.nx source

↩ module page · 940 lines · 49430 B

1// nx_cdmap_raster_lib.nx -- THE SOVEREIGN RASTERISER FOR THE POSITION MAP (datavis DV6 export, 2026-09-15). 2// A compare page's position map is an inline SVG the emitter writes from nx_swcompare_lib (cdmap_pass). This library 3// turns that SVG subset into ink: it reads the page's :root colour tokens, walks the figure's elements in document order 4// (rect, circle, line, polyline with the momentum arrow marker, polygon, text incl. the -90 rotated axis title and the 5// note groups), paints them into an i64 framebuffer (r + g*256 + b*65536, the packing nx_png writes) at an integer scale, 6// draws every glyph as coverage through the estate's own TrueType engine (nx_ttf_fontlib, the same engine the pixel 7// referee nx_cdmap_pixjudge already trusts), and writes the PNG through nx_png. It also writes the STANDALONE SVG twin: 8// the figure's bytes with the svg namespace declared and every var(--nx-color-*) resolved to the rgb the page defined, 9// so the file renders identically outside the page's stylesheet. 10// WHY A SUBSET AND NOT A GENERAL SVG ENGINE: the emitter is the ONLY producer of these bytes, so the set of constructs is 11// closed and known (cdp_* in nx_swcompare_lib), and every construct outside it is COUNTED and NAMED in the stats, never 12// silently dropped: a render that cannot say what it skipped is a picture of the renderer's blind spots. 13// SCOPE STATED: the face is Liberation Sans (SIL-OFL data, licence beside the bytes) where the page asks for system-ui, 14// letter-spacing and rounded rect corners are not drawn, and a text rotated by anything but -90 degrees is skipped by 15// name. The pixel referee (DV2) and this rasteriser share the font engine, so a label the referee measures as ink is the 16// same ink this file paints. license_tier: ORIGINAL 17import "nx_syscalls.nx" 18import "nx_cdmap_referee_lib.nx" // cr_find / cr_attr / cr_int_at / cr_tag_end: the ONE tag reader the referees use; brings nx_ttf_fontlib 19import "nx_png.nx" 20 21const RS_I64_BYTES: i64 = 8 22const RS_ABSENT: i64 = 0 - 1 23const RS_SCALE_DEFAULT: i64 = 2 24const RS_SCALE_MAX: i64 = 8 // 760x520 at 8 is a 24 MiB framebuffer; past this the export is refused by name 25const RS_CANVAS_MAX: i64 = 4096 // a viewBox axis past this is refused (the pixel referee's bound) 26const RS_VB_W_DEFAULT: i64 = 760 // the emitter's CDP_SVG_W when the tag carries no viewBox 27const RS_VB_H_DEFAULT: i64 = 520 28const RS_TOK_MAX: i64 = 48 // :root colour tokens read (the page defines under twenty) 29const RS_TOK_NAME_W: i64 = 32 30const RS_TOK_REC: i64 = 4 // r, g, b, name-slot 31const RS_TOK_R: i64 = 0 32const RS_TOK_G: i64 = 1 33const RS_TOK_B: i64 = 2 34const RS_PERMIL: i64 = 1000 35const RS_COV_MAX: i64 = 255 36const RS_INK_MIN: i64 = 8 // coverage below this paints nothing (fringe) 37const RS_STROKE_DEFAULT: i64 = 1 // canvas px when an element carries no stroke-width 38const RS_HALO_PX: i64 = 1 // the label halo (paint-order:stroke, 3px) reads as one canvas px each side of the ink 39const RS_ARROW_LEN: i64 = 7 // the emitter's marker: 7 px, an 8x8 viewBox with its tip at (8,4) 40const RS_ARROW_HALF: i64 = 3 41const RS_WALK_DIV: i64 = 2 // line walk step = scaled px / this 42const RS_DEG_ROT: i64 = 0 - 90 // the one rotation the emitter uses (the y-axis title) 43const RS_VAL_W: i64 = 64 // an attribute value copy (fill='..', transform='..') 44const RS_TEXT_W: i64 = 256 // a text element's content copy 45const RS_PTS_MAX: i64 = 64 // points on one polyline or polygon (a momentum path has two) 46const RS_XS_MAX: i64 = 64 // scanline crossings per row 47const RS_MAX_DASH: i64 = 4096 // scaled px of one dash pattern, bounds the walker 48const RS_STAMP: i64 = 1 // announce on the png publish 49const RS_SVG_HEAD: *u8 = "<?xml version='1.0' encoding='UTF-8'?>\n" 50const RS_SVG_NS: *u8 = " xmlns='http://www.w3.org/2000/svg'" 51const RS_VAR_OPEN: *u8 = "var(--nx-color-" 52const RS_SVG_OPEN: *u8 = "<svg class='cdmap'" 53const RS_SVG_CLOSE: *u8 = "</svg>" 54const RS_ROOT_OPEN: *u8 = ":root{" 55const RS_TOK_PREFIX: *u8 = "--nx-color-" 56const RS_RGB_OPEN: *u8 = "rgb(" 57const RS_TAG_TEXT_CLOSE: *u8 = "</text>" 58const RS_TAG_TITLE_CLOSE: *u8 = "</title>" 59const RS_TAG_DESC_CLOSE: *u8 = "</desc>" 60const RS_TAG_DEFS_CLOSE: *u8 = "</defs>" 61const RS_TAG_STYLE_CLOSE: *u8 = "</style>" 62const RS_NOTE_CLASS: *u8 = "class='cdnote'" 63const RS_CH_LT: i64 = 60 64const RS_CH_GT: i64 = 62 65const RS_CH_SLASH: i64 = 47 66const RS_CH_QUOTE: i64 = 39 67const RS_CH_SPACE: i64 = 32 68const RS_CH_COMMA: i64 = 44 69const RS_CH_DOT: i64 = 46 70const RS_CH_MINUS: i64 = 45 71const RS_CH_RPAREN: i64 = 41 72const RS_CH_SEMI: i64 = 59 73const RS_CH_COLON: i64 = 58 74const RS_CH_RBRACE: i64 = 125 75const RS_CH_0: i64 = 48 76const RS_CH_9: i64 = 57 77const RS_CH_A: i64 = 65 78const RS_CH_Z: i64 = 90 79const RS_CH_a: i64 = 97 80const RS_CH_z: i64 = 122 81const RS_CH_AMP: i64 = 38 82const RS_CH_NL: i64 = 10 83const RS_CASE_GAP: i64 = 32 84const RS_BASE10: i64 = 10 85const RS_BYTE: i64 = 256 86const RS_BYTE2: i64 = 65536 87const RS_TWO: i64 = 2 88const RS_MODE_0644: i64 = 420 89 90// ---- the stats record: every count a caller or a gate reads ---- 91const RS_S_RECT: i64 = 0 92const RS_S_CIRCLE: i64 = 1 93const RS_S_LINE: i64 = 2 94const RS_S_POLYLINE: i64 = 3 95const RS_S_POLYGON: i64 = 4 96const RS_S_TEXT: i64 = 5 97const RS_S_SKIPPED: i64 = 6 // elements outside the subset (named on stderr-free stdout by the caller through rs_skip_name) 98const RS_S_UNRESOLVED: i64 = 7 // colour values no token or literal resolved (painted fg) 99const RS_S_GLYPHS: i64 = 8 // glyphs drawn 100const RS_S_ROTATED: i64 = 9 // rotated texts drawn 101const RS_S_ROTSKIP: i64 = 10 // rotated texts skipped (an angle the subset does not draw) 102const RS_S_NOTES: i64 = 11 // cdnote groups seen 103const RS_S_ARROWS: i64 = 12 // marker-end arrows drawn 104const RS_S_INK: i64 = 13 // pixels painted by text ink (a scale field: 0 means no glyph landed) 105const RS_S_W: i64 = 14 106const RS_S_TOKENS: i64 = 15 // colour tokens read from :root 107const RS_S_SLOTS: i64 = 16 108 109// ---- outcomes of rs_export ---- 110const RS_OK: i64 = 0 111const RS_E_NOPAGE: i64 = 0 - 1 112const RS_E_NOSVG: i64 = 0 - 2 113const RS_E_NOFONT: i64 = 0 - 3 114const RS_E_CANVAS: i64 = 0 - 4 115const RS_E_PNG: i64 = 0 - 5 116const RS_E_SVG: i64 = 0 - 6 117const RS_E_SCALE: i64 = 0 - 7 118const RS_E_NOTOKENS: i64 = 0 - 8 119 120func rs_i64(n: i64) -> *i64 { return sys_mmap(n * RS_I64_BYTES) as *i64 } 121func rs_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 122func rs_abs(v: i64) -> i64 { if v < 0 { return 0 - v } return v } 123func rs_max(a: i64, b: i64) -> i64 { if a > b { return a } return b } 124func rs_min(a: i64, b: i64) -> i64 { if a < b { return a } return b } 125func rs_isqrt(v: i64) -> i64 { if v <= 0 { return 0 } var r: i64 = 1; while r * r <= v { r = r + 1 } return r - 1 } 126func rs_rc_name(rc: i64) -> *u8 { 127 if rc == RS_OK { return "OK" as *u8 } 128 if rc == RS_E_NOPAGE { return "NO-PAGE" as *u8 } 129 if rc == RS_E_NOSVG { return "NO-CDMAP-SVG" as *u8 } 130 if rc == RS_E_NOFONT { return "NO-FONT" as *u8 } 131 if rc == RS_E_CANVAS { return "CANVAS-UNREADABLE" as *u8 } 132 if rc == RS_E_PNG { return "PNG-WRITE-FAILED" as *u8 } 133 if rc == RS_E_SVG { return "SVG-WRITE-FAILED" as *u8 } 134 if rc == RS_E_SCALE { return "SCALE-OUT-OF-RANGE" as *u8 } 135 if rc == RS_E_NOTOKENS { return "NO-ROOT-TOKENS" as *u8 } 136 return "UNKNOWN" as *u8 137} 138 139// ---- the colour token table: name slot i at names + i*RS_TOK_NAME_W, rgb at rec + i*RS_TOK_REC ---- 140func rs_tok_rec(recs: *i64, i: i64) -> *i64 { return ((recs as i64) + i * RS_TOK_REC * RS_I64_BYTES) as *i64 } 141func rs_tok_name(names: *u8, i: i64) -> *u8 { return ((names as i64) + i * RS_TOK_NAME_W) as *u8 } 142func rs_is_ident(c: i64) -> i64 { 143 if c >= RS_CH_a { if c <= RS_CH_z { return 1 } } 144 if c >= RS_CH_A { if c <= RS_CH_Z { return 1 } } 145 if c >= RS_CH_0 { if c <= RS_CH_9 { return 1 } } 146 if c == RS_CH_MINUS { return 1 } 147 return 0 148} 149// parse "rgb(r,g,b" at h[p..to): 1 and the triple, 0 when the bytes are not that shape 150func rs_rgb_at(h: *u8, p: i64, to: i64, rgb: *i64) -> i64 { 151 let key: *u8 = RS_RGB_OPEN 152 if cr_find(h, p, rs_min(to, p + rs_slen(key)), key) != p { return 0 } 153 var q: i64 = p + rs_slen(key) 154 var k: i64 = 0 155 while k < 3 { 156 while q < to { if (h[q] as i64) == RS_CH_SPACE { q = q + 1 } else { q = to + q } } 157 if q > to { q = q - to } 158 let v: i64 = cr_int_at(h, q, to) 159 if v == CR_ABSENT { return 0 } 160 rgb[k] = v 161 while q < to { let c: i64 = h[q] as i64; if c >= RS_CH_0 { if c <= RS_CH_9 { q = q + 1 } else { q = to + q } } else { q = to + q } } 162 if q > to { q = q - to } 163 if k < 2 { if q < to { if (h[q] as i64) == RS_CH_COMMA { q = q + 1 } else { return 0 } } else { return 0 } } 164 k = k + 1 165 } 166 return 1 167} 168// read the FIRST :root{...} block's --nx-color-* tokens (the light theme, the one the page paints by default). Returns the 169// token count, 0 when the page has no :root block. 170func rs_theme_load(h: *u8, n: i64, names: *u8, recs: *i64) -> i64 { 171 let r0: i64 = cr_find(h, 0, n, RS_ROOT_OPEN) 172 if r0 == CR_ABSENT { return 0 } 173 var r1: i64 = r0 174 while r1 < n { if (h[r1] as i64) == RS_CH_RBRACE { r1 = n + r1 } else { r1 = r1 + 1 } } 175 if r1 > n { r1 = r1 - n } else { r1 = n } 176 var ntok: i64 = 0 177 var p: i64 = r0 178 var go: i64 = 1 179 while go == 1 { 180 let t0: i64 = cr_find(h, p, r1, RS_TOK_PREFIX) 181 if t0 == CR_ABSENT { go = 0 } else { 182 var q: i64 = t0 + rs_slen(RS_TOK_PREFIX) 183 let nm: *u8 = rs_tok_name(names, ntok) 184 var k: i64 = 0 185 while q < r1 { if rs_is_ident(h[q] as i64) == 1 { if k < RS_TOK_NAME_W - 1 { nm[k] = h[q]; k = k + 1 } q = q + 1 } else { q = r1 + q } } 186 if q > r1 { q = q - r1 } 187 nm[k] = 0 as u8 188 // ':' then spaces then rgb( 189 if q < r1 { if (h[q] as i64) == RS_CH_COLON { q = q + 1 } } 190 while q < r1 { if (h[q] as i64) == RS_CH_SPACE { q = q + 1 } else { q = r1 + q } } 191 if q > r1 { q = q - r1 } 192 let rec: *i64 = rs_tok_rec(recs, ntok) 193 if rs_rgb_at(h, q, r1, rec) == 1 { if ntok < RS_TOK_MAX - 1 { ntok = ntok + 1 } } 194 p = q 195 } 196 } 197 return ntok 198} 199func rs_tok_find(names: *u8, ntok: i64, want: *u8, wl: i64) -> i64 { 200 var i: i64 = 0 201 while i < ntok { 202 let nm: *u8 = rs_tok_name(names, i) 203 var k: i64 = 0 204 var same: i64 = 1 205 while k < wl { if nm[k] != want[k] { same = 0; k = wl } else { k = k + 1 } } 206 if same == 1 { if nm[wl] == (0 as u8) { return i } } 207 i = i + 1 208 } 209 return RS_ABSENT 210} 211// the rgb of a token by name (NUL-terminated); 1 found, 0 absent 212func rs_tok_rgb(names: *u8, recs: *i64, ntok: i64, name: *u8, rgb: *i64) -> i64 { 213 let i: i64 = rs_tok_find(names, ntok, name, rs_slen(name)) 214 if i == RS_ABSENT { return 0 } 215 let rec: *i64 = rs_tok_rec(recs, i) 216 rgb[0] = rec[RS_TOK_R]; rgb[1] = rec[RS_TOK_G]; rgb[2] = rec[RS_TOK_B] 217 return 1 218} 219 220// ---- attribute readers over one tag [t0, t1) ---- 221// copy an attribute's quoted value (needle spelled WITH its leading space, name, = and quote) into out; the length, or RS_ABSENT 222func rs_attr_str(h: *u8, t0: i64, t1: i64, needle: *u8, out: *u8, cap: i64) -> i64 { 223 let at: i64 = cr_find(h, t0, t1, needle) 224 if at == CR_ABSENT { return RS_ABSENT } 225 var q: i64 = at + rs_slen(needle) 226 var k: i64 = 0 227 while q < t1 { if (h[q] as i64) == RS_CH_QUOTE { q = t1 + q } else { if k < cap - 1 { out[k] = h[q]; k = k + 1 } q = q + 1 } } 228 out[k] = 0 as u8 229 return k 230} 231// a fraction attribute ("0.82") as permil; RS_PERMIL when absent, 0..1000 otherwise 232func rs_attr_permil(h: *u8, t0: i64, t1: i64, needle: *u8) -> i64 { 233 let at: i64 = cr_find(h, t0, t1, needle) 234 if at == CR_ABSENT { return RS_PERMIL } 235 var q: i64 = at + rs_slen(needle) 236 let whole: i64 = cr_int_at(h, q, t1) 237 if whole == CR_ABSENT { return RS_PERMIL } 238 if whole >= 1 { return RS_PERMIL } 239 while q < t1 { let c: i64 = h[q] as i64; if c >= RS_CH_0 { if c <= RS_CH_9 { q = q + 1 } else { q = t1 + q } } else { q = t1 + q } } 240 if q > t1 { q = q - t1 } 241 if q >= t1 { return 0 } 242 if (h[q] as i64) != RS_CH_DOT { return 0 } 243 q = q + 1 244 var v: i64 = 0 245 var nd: i64 = 0 246 while q < t1 { let c: i64 = h[q] as i64; if c >= RS_CH_0 { if c <= RS_CH_9 { if nd < 3 { v = v * RS_BASE10 + (c - RS_CH_0); nd = nd + 1 } q = q + 1 } else { q = t1 + q } } else { q = t1 + q } } 247 while nd < 3 { v = v * RS_BASE10; nd = nd + 1 } 248 return v 249} 250// resolve a colour value copied from an attribute: 1 = rgb filled, 0 = none, RS_ABSENT = unresolved (rgb = fg, counted) 251func rs_color_value(v: *u8, names: *u8, recs: *i64, ntok: i64, fg: *i64, rgb: *i64, stats: *i64) -> i64 { 252 if v[0] == (0 as u8) { return 0 } 253 if cr_streq(v, "none" as *u8) == 1 { return 0 } 254 if cr_streq(v, "currentColor" as *u8) == 1 { rgb[0] = fg[0]; rgb[1] = fg[1]; rgb[2] = fg[2]; return 1 } 255 let vl: i64 = rs_slen(v) 256 if rs_rgb_at(v, 0, vl, rgb) == 1 { return 1 } 257 let vo: i64 = rs_slen(RS_VAR_OPEN) 258 if cr_find(v, 0, rs_min(vl, vo), RS_VAR_OPEN) == 0 { 259 var e: i64 = vo 260 while e < vl { if (v[e] as i64) == RS_CH_RPAREN { e = vl + e } else { e = e + 1 } } 261 if e > vl { e = e - vl } 262 let i: i64 = rs_tok_find(names, ntok, ((v as i64) + vo) as *u8, e - vo) 263 if i != RS_ABSENT { let rec: *i64 = rs_tok_rec(recs, i); rgb[0] = rec[RS_TOK_R]; rgb[1] = rec[RS_TOK_G]; rgb[2] = rec[RS_TOK_B]; return 1 } 264 } 265 stats[RS_S_UNRESOLVED] = stats[RS_S_UNRESOLVED] + 1 266 rgb[0] = fg[0]; rgb[1] = fg[1]; rgb[2] = fg[2] 267 return RS_ABSENT 268} 269// the colour of an attribute (" fill='" / " stroke='"): 1 rgb, 0 none/absent, RS_ABSENT unresolved-painted-fg 270func rs_attr_color(h: *u8, t0: i64, t1: i64, needle: *u8, names: *u8, recs: *i64, ntok: i64, fg: *i64, rgb: *i64, stats: *i64, present: *i64) -> i64 { 271 let v: *u8 = sys_mmap(RS_VAL_W) 272 let l: i64 = rs_attr_str(h, t0, t1, needle, v, RS_VAL_W) 273 present[0] = 0 274 if l == RS_ABSENT { sys_munmap(v, RS_VAL_W); return 0 } 275 present[0] = 1 276 let r: i64 = rs_color_value(v, names, recs, ntok, fg, rgb, stats) 277 sys_munmap(v, RS_VAL_W) 278 return r 279} 280// "6 4" -> on, off (scaled px); absent -> 0,0 (solid) 281func rs_attr_dash(h: *u8, t0: i64, t1: i64, scale: i64, dash: *i64) -> i64 { 282 dash[0] = 0; dash[1] = 0 283 let key: *u8 = " stroke-dasharray='" as *u8 284 let at: i64 = cr_find(h, t0, t1, key) 285 if at == CR_ABSENT { return 0 } 286 var q: i64 = at + rs_slen(key) 287 let a: i64 = cr_int_at(h, q, t1) 288 if a == CR_ABSENT { return 0 } 289 while q < t1 { let c: i64 = h[q] as i64; if c >= RS_CH_0 { if c <= RS_CH_9 { q = q + 1 } else { q = t1 + q } } else { q = t1 + q } } 290 if q > t1 { q = q - t1 } 291 while q < t1 { if (h[q] as i64) == RS_CH_SPACE { q = q + 1 } else { q = t1 + q } } 292 if q > t1 { q = q - t1 } 293 var b: i64 = cr_int_at(h, q, t1) 294 if b == CR_ABSENT { b = a } 295 dash[0] = a * scale; dash[1] = b * scale 296 return 1 297} 298// "x,y x,y ..." points into xs/ys (scaled); the count 299func rs_attr_points(h: *u8, t0: i64, t1: i64, scale: i64, xs: *i64, ys: *i64) -> i64 { 300 let key: *u8 = " points='" as *u8 301 let at: i64 = cr_find(h, t0, t1, key) 302 if at == CR_ABSENT { return 0 } 303 var q: i64 = at + rs_slen(key) 304 var np: i64 = 0 305 var go: i64 = 1 306 while go == 1 { 307 while q < t1 { if (h[q] as i64) == RS_CH_SPACE { q = q + 1 } else { q = t1 + q } } 308 if q > t1 { q = q - t1 } 309 if q >= t1 { go = 0 } else { if (h[q] as i64) == RS_CH_QUOTE { go = 0 } else { 310 let x: i64 = cr_int_at(h, q, t1) 311 if x == CR_ABSENT { go = 0 } else { 312 if (h[q] as i64) == RS_CH_MINUS { q = q + 1 } 313 while q < t1 { let c: i64 = h[q] as i64; if c >= RS_CH_0 { if c <= RS_CH_9 { q = q + 1 } else { q = t1 + q } } else { q = t1 + q } } 314 if q > t1 { q = q - t1 } 315 if q < t1 { if (h[q] as i64) == RS_CH_COMMA { q = q + 1 } } 316 let y: i64 = cr_int_at(h, q, t1) 317 if y == CR_ABSENT { go = 0 } else { 318 if (h[q] as i64) == RS_CH_MINUS { q = q + 1 } 319 while q < t1 { let c: i64 = h[q] as i64; if c >= RS_CH_0 { if c <= RS_CH_9 { q = q + 1 } else { q = t1 + q } } else { q = t1 + q } } 320 if q > t1 { q = q - t1 } 321 if np < RS_PTS_MAX { xs[np] = x * scale; ys[np] = y * scale; np = np + 1 } 322 } 323 } 324 } } 325 } 326 return np 327} 328 329// ---- the framebuffer ---- 330func rs_fb_get(fb: *i64, W: i64, x: i64, y: i64, rgb: *i64) -> i64 { let v: i64 = fb[y * W + x]; rgb[0] = v % RS_BYTE; rgb[1] = (v / RS_BYTE) % RS_BYTE; rgb[2] = (v / RS_BYTE2) % RS_BYTE; return 0 } 331func rs_pack(r: i64, g: i64, b: i64) -> i64 { return r + g * RS_BYTE + b * RS_BYTE2 } 332// paint one pixel with alpha in permil (clipped) 333func rs_px(fb: *i64, W: i64, H: i64, x: i64, y: i64, rgb: *i64, a: i64) -> i64 { 334 if x < 0 { return 0 } 335 if y < 0 { return 0 } 336 if x >= W { return 0 } 337 if y >= H { return 0 } 338 if a >= RS_PERMIL { fb[y * W + x] = rs_pack(rgb[0], rgb[1], rgb[2]); return 1 } 339 if a <= 0 { return 0 } 340 let v: i64 = fb[y * W + x] 341 let r0: i64 = v % RS_BYTE 342 let g0: i64 = (v / RS_BYTE) % RS_BYTE 343 let b0: i64 = (v / RS_BYTE2) % RS_BYTE 344 let r: i64 = (r0 * (RS_PERMIL - a) + rgb[0] * a) / RS_PERMIL 345 let g: i64 = (g0 * (RS_PERMIL - a) + rgb[1] * a) / RS_PERMIL 346 let b: i64 = (b0 * (RS_PERMIL - a) + rgb[2] * a) / RS_PERMIL 347 fb[y * W + x] = rs_pack(r, g, b) 348 return 1 349} 350func rs_fill_all(fb: *i64, W: i64, H: i64, rgb: *i64) -> i64 { let v: i64 = rs_pack(rgb[0], rgb[1], rgb[2]); var i: i64 = 0; while i < W * H { fb[i] = v; i = i + 1 } return 0 } 351func rs_fill_rect(fb: *i64, W: i64, H: i64, x0: i64, y0: i64, w: i64, h: i64, rgb: *i64, a: i64) -> i64 { 352 var y: i64 = y0 353 while y < y0 + h { var x: i64 = x0; while x < x0 + w { rs_px(fb, W, H, x, y, rgb, a); x = x + 1 } y = y + 1 } 354 return 0 355} 356// a filled disk; radius in scaled px 357func rs_fill_disk(fb: *i64, W: i64, H: i64, cx: i64, cy: i64, r: i64, rgb: *i64, a: i64) -> i64 { 358 var y: i64 = cy - r 359 while y <= cy + r { 360 var x: i64 = cx - r 361 while x <= cx + r { if (x - cx) * (x - cx) + (y - cy) * (y - cy) <= r * r { rs_px(fb, W, H, x, y, rgb, a) } x = x + 1 } 362 y = y + 1 363 } 364 return 0 365} 366// a ring of width wd centred on radius r 367func rs_ring(fb: *i64, W: i64, H: i64, cx: i64, cy: i64, r: i64, wd: i64, rgb: *i64, a: i64) -> i64 { 368 let ro: i64 = r + (wd + 1) / RS_TWO 369 var ri: i64 = r - wd / RS_TWO 370 if ri < 0 { ri = 0 } 371 var y: i64 = cy - ro 372 while y <= cy + ro { 373 var x: i64 = cx - ro 374 while x <= cx + ro { 375 let d2: i64 = (x - cx) * (x - cx) + (y - cy) * (y - cy) 376 if d2 <= ro * ro { if d2 >= ri * ri { rs_px(fb, W, H, x, y, rgb, a) } } 377 x = x + 1 378 } 379 y = y + 1 380 } 381 return 0 382} 383// a stroked segment of width wd with an optional dash (on/off in scaled px, 0 = solid): the walker steps along the 384// segment in sub-pixel increments and stamps a disk of radius wd/2 wherever the dash phase is ON 385func rs_seg(fb: *i64, W: i64, H: i64, x1: i64, y1: i64, x2: i64, y2: i64, wd: i64, rgb: *i64, a: i64, dash: *i64, phase0: i64) -> i64 { 386 let dx: i64 = x2 - x1 387 let dy: i64 = y2 - y1 388 let len: i64 = rs_isqrt(dx * dx + dy * dy) 389 if len == 0 { rs_fill_disk(fb, W, H, x1, y1, wd / RS_TWO, rgb, a); return phase0 } 390 let steps: i64 = len * RS_WALK_DIV + 1 391 let period: i64 = dash[0] + dash[1] 392 var s: i64 = 0 393 var phase: i64 = phase0 394 while s <= steps { 395 let px: i64 = x1 + (dx * s) / steps 396 let py: i64 = y1 + (dy * s) / steps 397 var on: i64 = 1 398 if period > 0 { phase = phase0 + (len * s) / steps; if phase % period >= dash[0] { on = 0 } } 399 if on == 1 { if wd <= 1 { rs_px(fb, W, H, px, py, rgb, a) } else { rs_fill_disk(fb, W, H, px, py, wd / RS_TWO, rgb, a) } } 400 s = s + 1 401 } 402 if period > 0 { return (phase0 + len) % period } 403 return 0 404} 405// even-odd scanline fill of a closed polygon (scaled coords) 406func rs_fill_poly(fb: *i64, W: i64, H: i64, xs: *i64, ys: *i64, np: i64, rgb: *i64, a: i64) -> i64 { 407 if np < 3 { return 0 } 408 var ymin: i64 = ys[0] 409 var ymax: i64 = ys[0] 410 var i: i64 = 1 411 while i < np { if ys[i] < ymin { ymin = ys[i] } if ys[i] > ymax { ymax = ys[i] } i = i + 1 } 412 if ymin < 0 { ymin = 0 } 413 if ymax >= H { ymax = H - 1 } 414 let xcross: *i64 = rs_i64(RS_XS_MAX) 415 var y: i64 = ymin 416 while y <= ymax { 417 var nx: i64 = 0 418 var k: i64 = 0 419 while k < np { 420 let j: i64 = (k + 1) % np 421 let ya: i64 = ys[k] 422 let yb: i64 = ys[j] 423 if ya != yb { 424 var lo: i64 = ya 425 var hi: i64 = yb 426 if lo > hi { lo = yb; hi = ya } 427 if y >= lo { if y < hi { 428 let x: i64 = xs[k] + ((xs[j] - xs[k]) * (y - ya)) / (yb - ya) 429 if nx < RS_XS_MAX { xcross[nx] = x; nx = nx + 1 } 430 } } 431 } 432 k = k + 1 433 } 434 // sort the crossings (tiny n) 435 var p: i64 = 1 436 while p < nx { var q: i64 = p; while q > 0 { if xcross[q - 1] > xcross[q] { let t: i64 = xcross[q]; xcross[q] = xcross[q - 1]; xcross[q - 1] = t; q = q - 1 } else { q = 0 } } p = p + 1 } 437 var c: i64 = 0 438 while c + 1 < nx { 439 var x: i64 = xcross[c] 440 while x <= xcross[c + 1] { rs_px(fb, W, H, x, y, rgb, a); x = x + 1 } 441 c = c + RS_TWO 442 } 443 y = y + 1 444 } 445 sys_munmap(xcross as *u8, RS_XS_MAX * RS_I64_BYTES) 446 return 0 447} 448// the momentum arrow: a filled triangle whose tip is (x2,y2), pointing along (x1,y1)->(x2,y2) 449func rs_arrow(fb: *i64, W: i64, H: i64, x1: i64, y1: i64, x2: i64, y2: i64, scale: i64, rgb: *i64, a: i64) -> i64 { 450 let dx: i64 = x2 - x1 451 let dy: i64 = y2 - y1 452 let len: i64 = rs_isqrt(dx * dx + dy * dy) 453 if len == 0 { return 0 } 454 let al: i64 = RS_ARROW_LEN * scale 455 let ah: i64 = RS_ARROW_HALF * scale 456 let bx: i64 = x2 - (dx * al) / len 457 let by: i64 = y2 - (dy * al) / len 458 let xs: *i64 = rs_i64(3) 459 let ys: *i64 = rs_i64(3) 460 xs[0] = x2; ys[0] = y2 461 xs[1] = bx + (0 - dy) * ah / len; ys[1] = by + dx * ah / len 462 xs[2] = bx - (0 - dy) * ah / len; ys[2] = by - dx * ah / len 463 rs_fill_poly(fb, W, H, xs, ys, 3, rgb, a) 464 sys_munmap(xs as *u8, 3 * RS_I64_BYTES) 465 sys_munmap(ys as *u8, 3 * RS_I64_BYTES) 466 return 0 467} 468 469// ---- text ---- 470// ---- entities ---- 471// The emitter writes the page's text with HTML entities, and the figure carries five of them inside its svg (measured on 472// the live search page 2026-09-15: &ndash; in both axis titles, &middot; and &rarr; in the mark titles, &mdash; once, 473// &amp; in names). Two consequences: the RASTERISER draws ASCII faces only, so each entity maps to its nearest ASCII 474// (an en or em dash to a hyphen, a middle dot to a period, an arrow to the two bytes "->", a no-break space to a space), 475// and the STANDALONE SVG is XML, where a NAMED HTML entity is a parse error -- so the twin rewrites every named entity 476// the emitter uses to its NUMERIC character reference, which every XML reader accepts. The table is ONE list read by 477// both functions, so an entity added to one cannot be missed by the other. 478const RS_ENT_N: i64 = 10 479const RS_ENT_MAX: i64 = 8 // bytes of the longest entity name incl. & and ; 480func rs_ent_name(i: i64) -> *u8 { 481 if i == 0 { return "&amp;" as *u8 } 482 if i == 1 { return "&lt;" as *u8 } 483 if i == 2 { return "&gt;" as *u8 } 484 if i == 3 { return "&quot;" as *u8 } 485 if i == 4 { return "&#39;" as *u8 } 486 if i == 5 { return "&ndash;" as *u8 } 487 if i == 6 { return "&mdash;" as *u8 } 488 if i == 7 { return "&middot;" as *u8 } 489 if i == 8 { return "&rarr;" as *u8 } 490 return "&nbsp;" as *u8 491} 492// the ASCII the rasteriser draws for entity i (two bytes for the arrow, NUL-terminated) 493func rs_ent_ascii(i: i64) -> *u8 { 494 if i == 0 { return "&" as *u8 } 495 if i == 1 { return "<" as *u8 } 496 if i == 2 { return ">" as *u8 } 497 if i == 3 { return "\"" as *u8 } 498 if i == 4 { return "'" as *u8 } 499 if i == 5 { return "-" as *u8 } 500 if i == 6 { return "-" as *u8 } 501 if i == 7 { return "." as *u8 } 502 if i == 8 { return "->" as *u8 } 503 return " " as *u8 504} 505// the XML-safe spelling the standalone twin writes for entity i (the five XML built-ins stay themselves) 506func rs_ent_xml(i: i64) -> *u8 { 507 if i == 0 { return "&amp;" as *u8 } 508 if i == 1 { return "&lt;" as *u8 } 509 if i == 2 { return "&gt;" as *u8 } 510 if i == 3 { return "&quot;" as *u8 } 511 if i == 4 { return "&#39;" as *u8 } 512 if i == 5 { return "&#8211;" as *u8 } 513 if i == 6 { return "&#8212;" as *u8 } 514 if i == 7 { return "&#183;" as *u8 } 515 if i == 8 { return "&#8594;" as *u8 } 516 return "&#160;" as *u8 517} 518// the entity at h[q] (h[q] == '&'), or RS_ABSENT 519func rs_ent_at(h: *u8, q: i64, to: i64) -> i64 { 520 var i: i64 = 0 521 while i < RS_ENT_N { 522 let nm: *u8 = rs_ent_name(i) 523 let nl: i64 = rs_slen(nm) 524 if q + nl <= to { if cr_find(h, q, q + nl, nm) == q { return i } } 525 i = i + 1 526 } 527 return RS_ABSENT 528} 529// unescape the emitter's entities into out (NUL-terminated); returns the byte count 530func rs_unescape(h: *u8, from: i64, to: i64, out: *u8, cap: i64) -> i64 { 531 var q: i64 = from 532 var k: i64 = 0 533 while q < to { 534 var c: i64 = h[q] as i64 535 var adv: i64 = 1 536 if c == RS_CH_AMP { 537 let e: i64 = rs_ent_at(h, q, to) 538 if e != RS_ABSENT { 539 let a: *u8 = rs_ent_ascii(e) 540 var j: i64 = 0 541 while a[j] != (0 as u8) { if k < cap - 1 { out[k] = a[j]; k = k + 1 } j = j + 1 } 542 q = q + rs_slen(rs_ent_name(e)) 543 c = RS_ABSENT 544 } 545 } 546 if c != RS_ABSENT { 547 if c == RS_CH_NL { c = RS_CH_SPACE } 548 if k < cap - 1 { out[k] = c as u8; k = k + 1 } 549 q = q + adv 550 } 551 } 552 out[k] = 0 as u8 553 return k 554} 555func rs_text_w(fh: *i64, s: *u8, ph: i64) -> i64 { var i: i64 = 0; var wpx: i64 = 0; while s[i] != (0 as u8) { wpx = wpx + tf_char_adv(fh, s[i] as i64, ph); i = i + 1 } return wpx } 556// map a canvas point through rotate(-90 cx cy): (px,py) -> (cx + (py-cy), cy - (px-cx)); rot=0 is the identity 557func rs_rot_x(px: i64, py: i64, cx: i64, cy: i64, rot: i64) -> i64 { if rot == 0 { return px } return cx + (py - cy) } 558func rs_rot_y(px: i64, py: i64, cx: i64, cy: i64, rot: i64) -> i64 { if rot == 0 { return py } return cy - (px - cx) } 559// draw s at pixel size ph with its baseline at (x, ybase); anchor 0 start / 1 middle / 2 end; halo paints halo_rgb one 560// scaled-px ring around the ink FIRST (the emitter's paint-order:stroke); rot=1 rotates -90 about (cx,cy). Returns ink px. 561func rs_text(fb: *i64, W: i64, H: i64, fh: *i64, s: *u8, ph: i64, x: i64, ybase: i64, anchor: i64, rgb: *i64, a: i64, halo: i64, halo_rgb: *i64, rot: i64, cx: i64, cy: i64, scale: i64, stats: *i64) -> i64 { 562 let wpx: i64 = rs_text_w(fh, s, ph) 563 var x0: i64 = x 564 if anchor == 1 { x0 = x - wpx / RS_TWO } 565 if anchor == 2 { x0 = x - wpx } 566 let y0: i64 = ybase - tf_asc_px(fh, ph) 567 var ink: i64 = 0 568 var pass: i64 = 0 569 let hr: i64 = RS_HALO_PX * scale 570 while pass < RS_TWO { 571 if pass == 0 { if halo == 0 { pass = 1 } } 572 if pass < RS_TWO { 573 var pen: i64 = x0 574 var i: i64 = 0 575 while s[i] != (0 as u8) { 576 let ch: i64 = s[i] as i64 577 let rec: *i64 = tf_glyph(fh, ch, ph) 578 if (rec as i64) == 0 { pen = pen + tf_char_adv(fh, ch, ph) } else { 579 let cov: *u8 = rec[TF_R_COV] as *u8 580 let cw: i64 = rec[TF_R_CW] 581 let chh: i64 = rec[TF_R_CHH] 582 let gx0: i64 = pen - rec[TF_R_LM] 583 var gy: i64 = 0 584 while gy < chh { 585 var gx: i64 = 0 586 while gx < cw { 587 let cv: i64 = cov[gy * cw + gx] as i64 588 if cv >= RS_INK_MIN { 589 let fx: i64 = gx0 + gx 590 let fy: i64 = y0 + gy 591 if pass == 0 { 592 var hy: i64 = fy - hr 593 while hy <= fy + hr { var hx: i64 = fx - hr; while hx <= fx + hr { rs_px(fb, W, H, rs_rot_x(hx, hy, cx, cy, rot), rs_rot_y(hx, hy, cx, cy, rot), halo_rgb, (cv * a) / RS_COV_MAX); hx = hx + 1 } hy = hy + 1 } 594 } else { 595 ink = ink + rs_px(fb, W, H, rs_rot_x(fx, fy, cx, cy, rot), rs_rot_y(fx, fy, cx, cy, rot), rgb, (cv * a) / RS_COV_MAX) 596 } 597 } 598 gx = gx + 1 599 } 600 gy = gy + 1 601 } 602 pen = pen + rec[TF_R_ADV] 603 if pass == 1 { stats[RS_S_GLYPHS] = stats[RS_S_GLYPHS] + 1 } 604 } 605 i = i + 1 606 } 607 pass = pass + 1 608 } 609 } 610 stats[RS_S_INK] = stats[RS_S_INK] + ink 611 return ink 612} 613func rs_upper(s: *u8) -> i64 { var i: i64 = 0; while s[i] != (0 as u8) { let c: i64 = s[i] as i64; if c >= RS_CH_a { if c <= RS_CH_z { s[i] = (c - RS_CASE_GAP) as u8 } } i = i + 1 } return 0 } 614 615// ---- the tag walker: the figure between s0 (at "<svg") and s1 (at "</svg>") ---- 616// the element name at h[p] where h[p] == '<': copies it into name (NUL-terminated), returns its length (0 = not a tag) 617func rs_tag_name(h: *u8, p: i64, to: i64, name: *u8, cap: i64) -> i64 { 618 var q: i64 = p + 1 619 var k: i64 = 0 620 if q < to { if (h[q] as i64) == RS_CH_SLASH { name[k] = h[q]; k = k + 1; q = q + 1 } } 621 while q < to { let c: i64 = h[q] as i64; if rs_is_ident(c) == 1 { if c != RS_CH_MINUS { if k < cap - 1 { name[k] = h[q]; k = k + 1 } q = q + 1 } else { q = to + q } } else { q = to + q } } 622 name[k] = 0 as u8 623 return k 624} 625func rs_skip_past(h: *u8, p: i64, to: i64, closer: *u8) -> i64 { let e: i64 = cr_find(h, p, to, closer); if e == CR_ABSENT { return to } return e + rs_slen(closer) } 626// the viewBox width and height (canvas px) into wh; 1 read, 0 defaulted, RS_ABSENT unreadable or out of bounds 627func rs_viewbox(h: *u8, s0: i64, s1: i64, wh: *i64) -> i64 { 628 wh[0] = RS_VB_W_DEFAULT; wh[1] = RS_VB_H_DEFAULT 629 let t1: i64 = cr_tag_end(h, s0, s1) 630 if t1 == CR_ABSENT { return RS_ABSENT } 631 let key: *u8 = " viewBox='" as *u8 632 let vb: i64 = cr_find(h, s0, t1, key) 633 if vb == CR_ABSENT { return 0 } 634 var p: i64 = vb + rs_slen(key) 635 var k: i64 = 0 636 let v: *i64 = rs_i64(4) 637 while k < 4 { 638 while p < t1 { if (h[p] as i64) == RS_CH_SPACE { p = p + 1 } else { p = t1 + p } } 639 if p > t1 { p = p - t1 } 640 v[k] = cr_int_at(h, p, t1) 641 if v[k] == CR_ABSENT { return RS_ABSENT } 642 if (h[p] as i64) == RS_CH_MINUS { p = p + 1 } 643 while p < t1 { let c: i64 = h[p] as i64; if c >= RS_CH_0 { if c <= RS_CH_9 { p = p + 1 } else { p = t1 + p } } else { p = t1 + p } } 644 if p > t1 { p = p - t1 } 645 k = k + 1 646 } 647 if v[2] <= 0 { return RS_ABSENT } 648 if v[3] <= 0 { return RS_ABSENT } 649 if v[2] > RS_CANVAS_MAX { return RS_ABSENT } 650 if v[3] > RS_CANVAS_MAX { return RS_ABSENT } 651 wh[0] = v[2]; wh[1] = v[3] 652 sys_munmap(v as *u8, 4 * RS_I64_BYTES) 653 return 1 654} 655// render the figure into fb (W x H = canvas * scale). fg/bg from the tokens; fhr/fhb the regular and bold faces. 656func rs_render(h: *u8, s0: i64, s1: i64, names: *u8, recs: *i64, ntok: i64, fhr: *i64, fhb: *i64, scale: i64, fb: *i64, W: i64, H: i64, stats: *i64) -> i64 { 657 let fg: *i64 = rs_i64(3) 658 let bg: *i64 = rs_i64(3) 659 let rgb: *i64 = rs_i64(3) 660 let rgb2: *i64 = rs_i64(3) 661 let present: *i64 = rs_i64(1) 662 let dash: *i64 = rs_i64(2) 663 let xs: *i64 = rs_i64(RS_PTS_MAX) 664 let ys: *i64 = rs_i64(RS_PTS_MAX) 665 let name: *u8 = sys_mmap(RS_VAL_W) 666 let val: *u8 = sys_mmap(RS_VAL_W) 667 let txt: *u8 = sys_mmap(RS_TEXT_W) 668 if rs_tok_rgb(names, recs, ntok, "fg" as *u8, fg) == 0 { fg[0] = 0; fg[1] = 0; fg[2] = 0 } 669 if rs_tok_rgb(names, recs, ntok, "bg" as *u8, bg) == 0 { bg[0] = RS_COV_MAX; bg[1] = RS_COV_MAX; bg[2] = RS_COV_MAX } 670 rs_fill_all(fb, W, H, bg) 671 var p: i64 = s0 672 var go: i64 = 1 673 while go == 1 { 674 if p >= s1 { go = 0 } else { 675 if (h[p] as i64) != RS_CH_LT { p = p + 1 } else { 676 let nl: i64 = rs_tag_name(h, p, s1, name, RS_VAL_W) 677 let t1: i64 = cr_tag_end(h, p, s1) 678 if t1 == CR_ABSENT { go = 0 } else { 679 let t0: i64 = p 680 p = t1 + 1 681 if nl == 0 { stats[RS_S_SKIPPED] = stats[RS_S_SKIPPED] + 1 } 682 else { if cr_streq(name, "title" as *u8) == 1 { p = rs_skip_past(h, t1, s1, RS_TAG_TITLE_CLOSE) } 683 else { if cr_streq(name, "desc" as *u8) == 1 { p = rs_skip_past(h, t1, s1, RS_TAG_DESC_CLOSE) } 684 else { if cr_streq(name, "defs" as *u8) == 1 { p = rs_skip_past(h, t1, s1, RS_TAG_DEFS_CLOSE) } 685 else { if cr_streq(name, "style" as *u8) == 1 { p = rs_skip_past(h, t1, s1, RS_TAG_STYLE_CLOSE) } 686 else { if cr_streq(name, "svg" as *u8) == 1 { p = t1 + 1 } 687 else { if cr_streq(name, "g" as *u8) == 1 { if cr_find(h, t0, t1, RS_NOTE_CLASS) != CR_ABSENT { stats[RS_S_NOTES] = stats[RS_S_NOTES] + 1 } } 688 else { if (name[0] as i64) == RS_CH_SLASH { p = t1 + 1 } 689 else { if cr_streq(name, "rect" as *u8) == 1 { 690 stats[RS_S_RECT] = stats[RS_S_RECT] + 1 691 let x: i64 = cr_attr(h, t0, t1, " x='" as *u8) * scale 692 let y: i64 = cr_attr(h, t0, t1, " y='" as *u8) * scale 693 let wd: i64 = cr_attr(h, t0, t1, " width='" as *u8) * scale 694 let ht: i64 = cr_attr(h, t0, t1, " height='" as *u8) * scale 695 let fa: i64 = rs_attr_permil(h, t0, t1, " fill-opacity='" as *u8) 696 if rs_attr_color(h, t0, t1, " fill='" as *u8, names, recs, ntok, fg, rgb, stats, present) != 0 { rs_fill_rect(fb, W, H, x, y, wd, ht, rgb, fa) } 697 if rs_attr_color(h, t0, t1, " stroke='" as *u8, names, recs, ntok, fg, rgb2, stats, present) != 0 { 698 var sw: i64 = cr_attr(h, t0, t1, " stroke-width='" as *u8) 699 if sw == CR_ABSENT { sw = RS_STROKE_DEFAULT } 700 sw = sw * scale 701 rs_attr_dash(h, t0, t1, scale, dash) 702 var ph0: i64 = rs_seg(fb, W, H, x, y, x + wd, y, sw, rgb2, RS_PERMIL, dash, 0) 703 ph0 = rs_seg(fb, W, H, x + wd, y, x + wd, y + ht, sw, rgb2, RS_PERMIL, dash, ph0) 704 ph0 = rs_seg(fb, W, H, x + wd, y + ht, x, y + ht, sw, rgb2, RS_PERMIL, dash, ph0) 705 rs_seg(fb, W, H, x, y + ht, x, y, sw, rgb2, RS_PERMIL, dash, ph0) 706 } 707 } 708 else { if cr_streq(name, "circle" as *u8) == 1 { 709 stats[RS_S_CIRCLE] = stats[RS_S_CIRCLE] + 1 710 let cx: i64 = cr_attr(h, t0, t1, " cx='" as *u8) * scale 711 let cy: i64 = cr_attr(h, t0, t1, " cy='" as *u8) * scale 712 let r: i64 = cr_attr(h, t0, t1, " r='" as *u8) * scale 713 let fa: i64 = rs_attr_permil(h, t0, t1, " fill-opacity='" as *u8) 714 if rs_attr_color(h, t0, t1, " fill='" as *u8, names, recs, ntok, fg, rgb, stats, present) != 0 { rs_fill_disk(fb, W, H, cx, cy, r, rgb, fa) } 715 if rs_attr_color(h, t0, t1, " stroke='" as *u8, names, recs, ntok, fg, rgb2, stats, present) != 0 { 716 var sw: i64 = cr_attr(h, t0, t1, " stroke-width='" as *u8) 717 if sw == CR_ABSENT { sw = RS_STROKE_DEFAULT } 718 rs_ring(fb, W, H, cx, cy, r, sw * scale, rgb2, RS_PERMIL) 719 } 720 // a <title> child and the </circle> closer follow: the walker skips both as tags of their own 721 } 722 else { if cr_streq(name, "line" as *u8) == 1 { 723 stats[RS_S_LINE] = stats[RS_S_LINE] + 1 724 let x1: i64 = cr_attr(h, t0, t1, " x1='" as *u8) * scale 725 let y1: i64 = cr_attr(h, t0, t1, " y1='" as *u8) * scale 726 let x2: i64 = cr_attr(h, t0, t1, " x2='" as *u8) * scale 727 let y2: i64 = cr_attr(h, t0, t1, " y2='" as *u8) * scale 728 if rs_attr_color(h, t0, t1, " stroke='" as *u8, names, recs, ntok, fg, rgb, stats, present) != 0 { 729 var sw: i64 = cr_attr(h, t0, t1, " stroke-width='" as *u8) 730 if sw == CR_ABSENT { sw = RS_STROKE_DEFAULT } 731 rs_attr_dash(h, t0, t1, scale, dash) 732 rs_seg(fb, W, H, x1, y1, x2, y2, sw * scale, rgb, RS_PERMIL, dash, 0) 733 } 734 } 735 else { if cr_streq(name, "polyline" as *u8) == 1 { 736 stats[RS_S_POLYLINE] = stats[RS_S_POLYLINE] + 1 737 let np: i64 = rs_attr_points(h, t0, t1, scale, xs, ys) 738 if rs_attr_color(h, t0, t1, " stroke='" as *u8, names, recs, ntok, fg, rgb, stats, present) != 0 { 739 var sw: i64 = cr_attr(h, t0, t1, " stroke-width='" as *u8) 740 if sw == CR_ABSENT { sw = RS_STROKE_DEFAULT } 741 rs_attr_dash(h, t0, t1, scale, dash) 742 var ph1: i64 = 0 743 var k: i64 = 1 744 while k < np { ph1 = rs_seg(fb, W, H, xs[k - 1], ys[k - 1], xs[k], ys[k], sw * scale, rgb, RS_PERMIL, dash, ph1); k = k + 1 } 745 if np >= RS_TWO { if cr_find(h, t0, t1, " marker-end='" as *u8) != CR_ABSENT { rs_arrow(fb, W, H, xs[np - RS_TWO], ys[np - RS_TWO], xs[np - 1], ys[np - 1], scale, rgb, RS_PERMIL); stats[RS_S_ARROWS] = stats[RS_S_ARROWS] + 1 } } 746 } 747 } 748 else { if cr_streq(name, "polygon" as *u8) == 1 { 749 stats[RS_S_POLYGON] = stats[RS_S_POLYGON] + 1 750 let np: i64 = rs_attr_points(h, t0, t1, scale, xs, ys) 751 let fa: i64 = rs_attr_permil(h, t0, t1, " fill-opacity='" as *u8) 752 if rs_attr_color(h, t0, t1, " fill='" as *u8, names, recs, ntok, fg, rgb, stats, present) != 0 { rs_fill_poly(fb, W, H, xs, ys, np, rgb, fa) } 753 if rs_attr_color(h, t0, t1, " stroke='" as *u8, names, recs, ntok, fg, rgb2, stats, present) != 0 { 754 var sw: i64 = cr_attr(h, t0, t1, " stroke-width='" as *u8) 755 if sw == CR_ABSENT { sw = RS_STROKE_DEFAULT } 756 rs_attr_dash(h, t0, t1, scale, dash) 757 var ph2: i64 = 0 758 var k: i64 = 0 759 while k < np { let j: i64 = (k + 1) % np; ph2 = rs_seg(fb, W, H, xs[k], ys[k], xs[j], ys[j], sw * scale, rgb2, RS_PERMIL, dash, ph2); k = k + 1 } 760 } 761 } 762 else { if cr_streq(name, "text" as *u8) == 1 { 763 stats[RS_S_TEXT] = stats[RS_S_TEXT] + 1 764 let te: i64 = cr_find(h, t1, s1, RS_TAG_TEXT_CLOSE) 765 var tend: i64 = s1 766 if te != CR_ABSENT { tend = te } 767 rs_unescape(h, t1 + 1, tend, txt, RS_TEXT_W) 768 let x: i64 = cr_attr(h, t0, t1, " x='" as *u8) * scale 769 let y: i64 = cr_attr(h, t0, t1, " y='" as *u8) * scale 770 var fs: i64 = cr_attr(h, t0, t1, " font-size='" as *u8) 771 if fs == CR_ABSENT { fs = 12 } 772 var fh: *i64 = fhr 773 if cr_find(h, t0, t1, " font-weight='700'" as *u8) != CR_ABSENT { if (fhb as i64) != 0 { fh = fhb } } 774 var anchor: i64 = 0 775 if cr_find(h, t0, t1, "text-anchor='middle'" as *u8) != CR_ABSENT { anchor = 1 } 776 if cr_find(h, t0, t1, "text-anchor='end'" as *u8) != CR_ABSENT { anchor = 2 } 777 if cr_find(h, t0, t1, "text-transform:uppercase" as *u8) != CR_ABSENT { rs_upper(txt) } 778 let op: i64 = rs_attr_permil(h, t0, t1, " opacity='" as *u8) 779 var halo: i64 = 0 780 if cr_find(h, t0, t1, "paint-order:stroke" as *u8) != CR_ABSENT { halo = 1 } 781 var rot: i64 = 0 782 var rcx: i64 = 0 783 var rcy: i64 = 0 784 var drawable: i64 = 1 785 let rk: *u8 = " transform='rotate(" as *u8 786 let ra: i64 = cr_find(h, t0, t1, rk) 787 if ra != CR_ABSENT { 788 var q: i64 = ra + rs_slen(rk) 789 let ang: i64 = cr_int_at(h, q, t1) 790 if (h[q] as i64) == RS_CH_MINUS { q = q + 1 } 791 while q < t1 { let c: i64 = h[q] as i64; if c >= RS_CH_0 { if c <= RS_CH_9 { q = q + 1 } else { q = t1 + q } } else { q = t1 + q } } 792 if q > t1 { q = q - t1 } 793 while q < t1 { if (h[q] as i64) == RS_CH_SPACE { q = q + 1 } else { q = t1 + q } } 794 if q > t1 { q = q - t1 } 795 rcx = cr_int_at(h, q, t1) * scale 796 if (h[q] as i64) == RS_CH_MINUS { q = q + 1 } 797 while q < t1 { let c: i64 = h[q] as i64; if c >= RS_CH_0 { if c <= RS_CH_9 { q = q + 1 } else { q = t1 + q } } else { q = t1 + q } } 798 if q > t1 { q = q - t1 } 799 while q < t1 { if (h[q] as i64) == RS_CH_SPACE { q = q + 1 } else { q = t1 + q } } 800 if q > t1 { q = q - t1 } 801 rcy = cr_int_at(h, q, t1) * scale 802 if ang == RS_DEG_ROT { rot = 1; stats[RS_S_ROTATED] = stats[RS_S_ROTATED] + 1 } else { drawable = 0; stats[RS_S_ROTSKIP] = stats[RS_S_ROTSKIP] + 1 } 803 } 804 if drawable == 1 { 805 let fr: i64 = rs_attr_color(h, t0, t1, " fill='" as *u8, names, recs, ntok, fg, rgb, stats, present) 806 if fr == 0 { if present[0] == 0 { rgb[0] = fg[0]; rgb[1] = fg[1]; rgb[2] = fg[2] } } 807 if fr != 0 { rs_text(fb, W, H, fh, txt, fs * scale, x, y, anchor, rgb, op, halo, bg, rot, rcx, rcy, scale, stats) } 808 else { if present[0] == 0 { rs_text(fb, W, H, fh, txt, fs * scale, x, y, anchor, rgb, op, halo, bg, rot, rcx, rcy, scale, stats) } } 809 } 810 p = tend + rs_slen(RS_TAG_TEXT_CLOSE) 811 } 812 else { stats[RS_S_SKIPPED] = stats[RS_S_SKIPPED] + 1 } } } } } } } } } } } } } } 813 } 814 } 815 } 816 } 817 sys_munmap(name, RS_VAL_W); sys_munmap(val, RS_VAL_W); sys_munmap(txt, RS_TEXT_W) 818 return 0 819} 820 821// ---- the standalone SVG twin ---- 822// write the figure with the namespace declared and every colour variable resolved; returns bytes written or RS_E_SVG 823func rs_svg_standalone(h: *u8, s0: i64, s1: i64, names: *u8, recs: *i64, ntok: i64, path: *u8) -> i64 { 824 let fd: i64 = sys_openat_wr(path, RS_MODE_0644) 825 if fd < 0 { return RS_E_SVG } 826 var total: i64 = 0 827 let head: *u8 = RS_SVG_HEAD 828 total = total + sys_write(fd, head, rs_slen(head)) 829 let svg_open: i64 = rs_slen("<svg" as *u8) 830 total = total + sys_write(fd, ((h as i64) + s0) as *u8, svg_open) 831 let ns: *u8 = RS_SVG_NS 832 total = total + sys_write(fd, ns, rs_slen(ns)) 833 let end: i64 = s1 + rs_slen(RS_SVG_CLOSE) 834 let fg: *i64 = rs_i64(3) 835 if rs_tok_rgb(names, recs, ntok, "fg" as *u8, fg) == 0 { fg[0] = 0; fg[1] = 0; fg[2] = 0 } 836 let num: *u8 = sys_mmap(RS_VAL_W) 837 var p: i64 = s0 + svg_open 838 var run0: i64 = p 839 let vo: i64 = rs_slen(RS_VAR_OPEN) 840 // one pass over the figure: colour variables become rgb literals, named HTML entities become numeric references 841 while p < end { 842 let c: i64 = h[p] as i64 843 var consumed: i64 = 0 844 if c == RS_CH_AMP { 845 let e: i64 = rs_ent_at(h, p, end) 846 if e != RS_ABSENT { 847 total = total + sys_write(fd, ((h as i64) + run0) as *u8, p - run0) 848 let xs: *u8 = rs_ent_xml(e) 849 total = total + sys_write(fd, xs, rs_slen(xs)) 850 p = p + rs_slen(rs_ent_name(e)) 851 run0 = p 852 consumed = 1 853 } 854 } 855 if consumed == 0 { if cr_find(h, p, rs_min(end, p + vo), RS_VAR_OPEN) == p { 856 var e2: i64 = p + vo 857 while e2 < end { if (h[e2] as i64) == RS_CH_RPAREN { e2 = end + e2 } else { e2 = e2 + 1 } } 858 if e2 > end { e2 = e2 - end } 859 let i: i64 = rs_tok_find(names, ntok, ((h as i64) + p + vo) as *u8, e2 - p - vo) 860 if i != RS_ABSENT { 861 total = total + sys_write(fd, ((h as i64) + run0) as *u8, p - run0) 862 let rec: *i64 = rs_tok_rec(recs, i) 863 var o: i64 = 0 864 let rk: *u8 = RS_RGB_OPEN 865 var k: i64 = 0 866 while rk[k] != (0 as u8) { num[o] = rk[k]; o = o + 1; k = k + 1 } 867 o = rs_dec_into(num, o, rec[RS_TOK_R]); num[o] = RS_CH_COMMA as u8; o = o + 1 868 o = rs_dec_into(num, o, rec[RS_TOK_G]); num[o] = RS_CH_COMMA as u8; o = o + 1 869 o = rs_dec_into(num, o, rec[RS_TOK_B]); num[o] = RS_CH_RPAREN as u8; o = o + 1 870 total = total + sys_write(fd, num, o) 871 p = e2 + 1 872 run0 = p 873 consumed = 1 874 } 875 } } 876 if consumed == 0 { p = p + 1 } 877 } 878 total = total + sys_write(fd, ((h as i64) + run0) as *u8, end - run0) 879 num[0] = RS_CH_NL as u8 880 total = total + sys_write(fd, num, 1) 881 sys_close(fd) 882 sys_munmap(num, RS_VAL_W) 883 return total 884} 885func rs_dec_into(dst: *u8, off: i64, v: i64) -> i64 { 886 var o: i64 = off 887 var x: i64 = v 888 if x < 0 { dst[o] = RS_CH_MINUS as u8; o = o + 1; x = 0 - x } 889 let t: *u8 = sys_mmap(24) 890 var n: i64 = 0 891 if x == 0 { t[0] = RS_CH_0 as u8; n = 1 } 892 while x > 0 { t[n] = (RS_CH_0 + x % RS_BASE10) as u8; x = x / RS_BASE10; n = n + 1 } 893 while n > 0 { n = n - 1; dst[o] = t[n]; o = o + 1 } 894 sys_munmap(t, 24) 895 return o 896} 897 898// ---- the one-call export: page -> cdmap.svg + cdmap.png ---- 899// stats receives the render counts plus RS_S_W (the png width); svg_bytes/png_bytes land in out[0], out[1]. 900// fonts: regular and bold TrueType paths (bold may be the empty string to reuse regular). Returns RS_OK or a named code. 901func rs_export(page: *u8, svg_path: *u8, png_path: *u8, freg: *u8, fbold: *u8, scale: i64, stats: *i64, out: *i64) -> i64 { 902 out[0] = 0; out[1] = 0 903 var k: i64 = 0 904 while k < RS_S_SLOTS { stats[k] = 0; k = k + 1 } 905 if scale < 1 { return RS_E_SCALE } 906 if scale > RS_SCALE_MAX { return RS_E_SCALE } 907 let nb: *i64 = rs_i64(2) 908 nb[0] = 0 909 let h: *u8 = sys_read_file(page, nb) 910 let n: i64 = nb[0] 911 if (h as i64) == 0 { return RS_E_NOPAGE } 912 if n <= 0 { return RS_E_NOPAGE } 913 let s0: i64 = cr_find(h, 0, n, RS_SVG_OPEN) 914 if s0 == CR_ABSENT { return RS_E_NOSVG } 915 let s1: i64 = cr_find(h, s0, n, RS_SVG_CLOSE) 916 if s1 == CR_ABSENT { return RS_E_NOSVG } 917 let names: *u8 = sys_mmap(RS_TOK_MAX * RS_TOK_NAME_W) 918 let recs: *i64 = rs_i64(RS_TOK_MAX * RS_TOK_REC) 919 let ntok: i64 = rs_theme_load(h, n, names, recs) 920 stats[RS_S_TOKENS] = ntok 921 if ntok == 0 { return RS_E_NOTOKENS } 922 let wh: *i64 = rs_i64(2) 923 if rs_viewbox(h, s0, s1, wh) == RS_ABSENT { return RS_E_CANVAS } 924 let fhr: *i64 = tf_load(freg) 925 if (fhr as i64) == 0 { return RS_E_NOFONT } 926 var fhb: *i64 = fhr 927 if fbold[0] != (0 as u8) { fhb = tf_load(fbold); if (fhb as i64) == 0 { fhb = fhr } } 928 let W: i64 = wh[0] * scale 929 let H: i64 = wh[1] * scale 930 let fb: *i64 = rs_i64(W * H) 931 rs_render(h, s0, s1, names, recs, ntok, fhr, fhb, scale, fb, W, H, stats) 932 stats[RS_S_W] = W 933 let sb: i64 = rs_svg_standalone(h, s0, s1, names, recs, ntok, svg_path) 934 if sb < 0 { return RS_E_SVG } 935 out[0] = sb 936 let pb: i64 = png_publish(fb, W, H, png_path, RS_STAMP) 937 if pb <= 0 { return RS_E_PNG } 938 out[1] = pb 939 return RS_OK 940}