nx_cdmap_raster_lib.nx
buildroot/runtime/nx_cdmap_raster_lib.nx
about
nx_cdmap_raster_lib.nx -- THE SOVEREIGN RASTERISER FOR THE POSITION MAP (datavis DV6 export, 2026-09-15).
A compare page's position map is an inline SVG the emitter writes from nx_swcompare_lib (cdmap_pass). This library
turns that SVG subset into ink: it reads the page's :root colour tokens, walks the figure's elements in document order
(rect, circle, line, polyline with the momentum arrow marker, polygon, text incl. the -90 rotated axis title and the
note groups), paints them into an i64 framebuffer (r + g*256 + b*65536, the packing nx_png writes) at an integer scale,
draws every glyph as coverage through the estate's own TrueType engine (nx_ttf_fontlib, the same engine the pixel
referee nx_cdmap_pixjudge already trusts), and writes the PNG through nx_png. It also writes the STANDALONE SVG twin:
the figure's bytes with the svg namespace declared and every var(--nx-color-*) resolved to the rgb the page defined,
so the file renders identically outside the page's stylesheet.
WHY A SUBSET AND NOT A GENERAL SVG ENGINE: the emitter is the ONLY producer of these bytes, so the set of constructs is
closed and known (cdp_* in nx_swcompare_lib), and every construct outside it is COUNTED and NAMED in the stats, never
silently dropped: a render that cannot say what it skipped is a picture of the renderer's blind spots.
SCOPE STATED: the face is Liberation Sans (SIL-OFL data, licence beside the bytes) where the page asks for system-ui,
letter-spacing and rounded rect corners are not drawn, and a text rotated by anything but -90 degrees is skipped by
name. The pixel referee (DV2) and this rasteriser share the font engine, so a label the referee measures as ink is the
same ink this file paints. license_tier: ORIGINAL
dependencies 3 imports · 3 importers
imports: nx_syscalls.nxnx_cdmap_referee_lib.nxnx_png.nx
imported by: nx_cdmap_export.nxnx_cdmap_export_gate.nxnx_swcompare_lib.nx
structs
| none |
consts
| 21 | const RS_I64_BYTES: i64 = 8 |
| 22 | const RS_ABSENT: i64 = 0 - 1 |
| 23 | const RS_SCALE_DEFAULT: i64 = 2 |
| 24 | const RS_SCALE_MAX: i64 = 8 // 760x520 at 8 is a 24 MiB framebuffer; past this the export is refused by name |
| 25 | const RS_CANVAS_MAX: i64 = 4096 // a viewBox axis past this is refused (the pixel referee's bound) |
| 26 | const RS_VB_W_DEFAULT: i64 = 760 // the emitter's CDP_SVG_W when the tag carries no viewBox |
| 27 | const RS_VB_H_DEFAULT: i64 = 520 |
| 28 | const RS_TOK_MAX: i64 = 48 // :root colour tokens read (the page defines under twenty) |
| 29 | const RS_TOK_NAME_W: i64 = 32 |
| 30 | const RS_TOK_REC: i64 = 4 // r, g, b, name-slot |
| 31 | const RS_TOK_R: i64 = 0 |
| 32 | const RS_TOK_G: i64 = 1 |
| 33 | const RS_TOK_B: i64 = 2 |
| 34 | const RS_PERMIL: i64 = 1000 |
| 35 | const RS_COV_MAX: i64 = 255 |
| 36 | const RS_INK_MIN: i64 = 8 // coverage below this paints nothing (fringe) |
| 37 | const RS_STROKE_DEFAULT: i64 = 1 // canvas px when an element carries no stroke-width |
| 38 | const RS_HALO_PX: i64 = 1 // the label halo (paint-order:stroke, 3px) reads as one canvas px each side of the ink |
| 39 | const RS_ARROW_LEN: i64 = 7 // the emitter's marker: 7 px, an 8x8 viewBox with its tip at (8,4) |
| 40 | const RS_ARROW_HALF: i64 = 3 |
| 41 | const RS_WALK_DIV: i64 = 2 // line walk step = scaled px / this |
| 42 | const RS_DEG_ROT: i64 = 0 - 90 // the one rotation the emitter uses (the y-axis title) |
| 43 | const RS_VAL_W: i64 = 64 // an attribute value copy (fill='..', transform='..') |
| 44 | const RS_TEXT_W: i64 = 256 // a text element's content copy |
| 45 | const RS_PTS_MAX: i64 = 64 // points on one polyline or polygon (a momentum path has two) |
| 46 | const RS_XS_MAX: i64 = 64 // scanline crossings per row |
| 47 | const RS_MAX_DASH: i64 = 4096 // scaled px of one dash pattern, bounds the walker |
| 48 | const RS_STAMP: i64 = 1 // announce on the png publish |
| 49 | const RS_SVG_HEAD: *u8 = "<?xml version='1.0' encoding='UTF-8'?>\n" |
| 50 | const RS_SVG_NS: *u8 = " xmlns='http://www.w3.org/2000/svg'" |
| 51 | const RS_VAR_OPEN: *u8 = "var(--nx-color-" |
| 52 | const RS_SVG_OPEN: *u8 = "<svg class='cdmap'" |
| 53 | const RS_SVG_CLOSE: *u8 = "</svg>" |
| 54 | const RS_ROOT_OPEN: *u8 = ":root{" |
| 55 | const RS_TOK_PREFIX: *u8 = "--nx-color-" |
| 56 | const RS_RGB_OPEN: *u8 = "rgb(" |
| 57 | const RS_TAG_TEXT_CLOSE: *u8 = "</text>" |
| 58 | const RS_TAG_TITLE_CLOSE: *u8 = "</title>" |
| 59 | const RS_TAG_DESC_CLOSE: *u8 = "</desc>" |
| 60 | const RS_TAG_DEFS_CLOSE: *u8 = "</defs>" |
| 61 | const RS_TAG_STYLE_CLOSE: *u8 = "</style>" |
| 62 | const RS_NOTE_CLASS: *u8 = "class='cdnote'" |
| 63 | const RS_CH_LT: i64 = 60 |
| 64 | const RS_CH_GT: i64 = 62 |
| 65 | const RS_CH_SLASH: i64 = 47 |
| 66 | const RS_CH_QUOTE: i64 = 39 |
| 67 | const RS_CH_SPACE: i64 = 32 |
| 68 | const RS_CH_COMMA: i64 = 44 |
| 69 | const RS_CH_DOT: i64 = 46 |
| 70 | const RS_CH_MINUS: i64 = 45 |
| 71 | const RS_CH_RPAREN: i64 = 41 |
| 72 | const RS_CH_SEMI: i64 = 59 |
| 73 | const RS_CH_COLON: i64 = 58 |
| 74 | const RS_CH_RBRACE: i64 = 125 |
| 75 | const RS_CH_0: i64 = 48 |
| 76 | const RS_CH_9: i64 = 57 |
| 77 | const RS_CH_A: i64 = 65 |
| 78 | const RS_CH_Z: i64 = 90 |
| 79 | const RS_CH_a: i64 = 97 |
| 80 | const RS_CH_z: i64 = 122 |
| 81 | const RS_CH_AMP: i64 = 38 |
| 82 | const RS_CH_NL: i64 = 10 |
| 83 | const RS_CASE_GAP: i64 = 32 |
| 84 | const RS_BASE10: i64 = 10 |
| 85 | const RS_BYTE: i64 = 256 |
| 86 | const RS_BYTE2: i64 = 65536 |
| 87 | const RS_TWO: i64 = 2 |
| 88 | const RS_MODE_0644: i64 = 420 |
| 91 | const RS_S_RECT: i64 = 0 |
| 92 | const RS_S_CIRCLE: i64 = 1 |
| 93 | const RS_S_LINE: i64 = 2 |
| 94 | const RS_S_POLYLINE: i64 = 3 |
| 95 | const RS_S_POLYGON: i64 = 4 |
| 96 | const RS_S_TEXT: i64 = 5 |
| 97 | const RS_S_SKIPPED: i64 = 6 // elements outside the subset (named on stderr-free stdout by the caller through rs_skip_name) |
| 98 | const RS_S_UNRESOLVED: i64 = 7 // colour values no token or literal resolved (painted fg) |
| 99 | const RS_S_GLYPHS: i64 = 8 // glyphs drawn |
| 100 | const RS_S_ROTATED: i64 = 9 // rotated texts drawn |
| 101 | const RS_S_ROTSKIP: i64 = 10 // rotated texts skipped (an angle the subset does not draw) |
| 102 | const RS_S_NOTES: i64 = 11 // cdnote groups seen |
| 103 | const RS_S_ARROWS: i64 = 12 // marker-end arrows drawn |
| 104 | const RS_S_INK: i64 = 13 // pixels painted by text ink (a scale field: 0 means no glyph landed) |
| 105 | const RS_S_W: i64 = 14 |
| 106 | const RS_S_TOKENS: i64 = 15 // colour tokens read from :root |
| 107 | const RS_S_SLOTS: i64 = 16 |
| 110 | const RS_OK: i64 = 0 |
| 111 | const RS_E_NOPAGE: i64 = 0 - 1 |
| 112 | const RS_E_NOSVG: i64 = 0 - 2 |
| 113 | const RS_E_NOFONT: i64 = 0 - 3 |
| 114 | const RS_E_CANVAS: i64 = 0 - 4 |
| 115 | const RS_E_PNG: i64 = 0 - 5 |
| 116 | const RS_E_SVG: i64 = 0 - 6 |
| 117 | const RS_E_SCALE: i64 = 0 - 7 |
| 118 | const RS_E_NOTOKENS: i64 = 0 - 8 |
| 478 | const RS_ENT_N: i64 = 10 |
| 479 | const RS_ENT_MAX: i64 = 8 // bytes of the longest entity name incl. & and ; |
functions
| 120 | func rs_i64(n: i64) -> *i64 { return sys_mmap(n * RS_I64_BYTES) as *i64 } |
| 121 | func rs_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } |
| 122 | func rs_abs(v: i64) -> i64 { if v < 0 { return 0 - v } return v } |
| 123 | func rs_max(a: i64, b: i64) -> i64 { if a > b { return a } return b } |
| 124 | func rs_min(a: i64, b: i64) -> i64 { if a < b { return a } return b } |
| 125 | func rs_isqrt(v: i64) -> i64 { if v <= 0 { return 0 } var r: i64 = 1; while r * r <= v { r = r + 1 } return r - 1 } |
| 126 | func rs_rc_name(rc: i64) -> *u8 |
| 140 | func rs_tok_rec(recs: *i64, i: i64) -> *i64 { return ((recs as i64) + i * RS_TOK_REC * RS_I64_BYTES) as *i64 } |
| 141 | func rs_tok_name(names: *u8, i: i64) -> *u8 { return ((names as i64) + i * RS_TOK_NAME_W) as *u8 } |
| 142 | func rs_is_ident(c: i64) -> i64 |
| 150 | func rs_rgb_at(h: *u8, p: i64, to: i64, rgb: *i64) -> i64 |
| 170 | func rs_theme_load(h: *u8, n: i64, names: *u8, recs: *i64) -> i64 called by 1: rs_render |
| 199 | func rs_tok_find(names: *u8, ntok: i64, want: *u8, wl: i64) -> i64 |
| 212 | func rs_tok_rgb(names: *u8, recs: *i64, ntok: i64, name: *u8, rgb: *i64) -> i64 |
| 222 | func rs_attr_str(h: *u8, t0: i64, t1: i64, needle: *u8, out: *u8, cap: i64) -> i64 |
| 232 | func rs_attr_permil(h: *u8, t0: i64, t1: i64, needle: *u8) -> i64 |
| 251 | func rs_color_value(v: *u8, names: *u8, recs: *i64, ntok: i64, fg: *i64, rgb: *i64, stats: *i64) -> i64 |
| 270 | func 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 |
| 281 | func rs_attr_dash(h: *u8, t0: i64, t1: i64, scale: i64, dash: *i64) -> i64 |
| 299 | func rs_attr_points(h: *u8, t0: i64, t1: i64, scale: i64, xs: *i64, ys: *i64) -> i64 |
| 330 | func 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 } |
| 331 | func rs_pack(r: i64, g: i64, b: i64) -> i64 { return r + g * RS_BYTE + b * RS_BYTE2 } |
| 333 | func rs_px(fb: *i64, W: i64, H: i64, x: i64, y: i64, rgb: *i64, a: i64) -> i64 |
| 350 | func 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 } |
| 351 | func rs_fill_rect(fb: *i64, W: i64, H: i64, x0: i64, y0: i64, w: i64, h: i64, rgb: *i64, a: i64) -> i64 |
| 357 | func rs_fill_disk(fb: *i64, W: i64, H: i64, cx: i64, cy: i64, r: i64, rgb: *i64, a: i64) -> i64 |
| 367 | func rs_ring(fb: *i64, W: i64, H: i64, cx: i64, cy: i64, r: i64, wd: i64, rgb: *i64, a: i64) -> i64 |
| 385 | func 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 |
| 406 | func rs_fill_poly(fb: *i64, W: i64, H: i64, xs: *i64, ys: *i64, np: i64, rgb: *i64, a: i64) -> i64 |
| 449 | func rs_arrow(fb: *i64, W: i64, H: i64, x1: i64, y1: i64, x2: i64, y2: i64, scale: i64, rgb: *i64, a: i64) -> i64 |
| 480 | func rs_ent_name(i: i64) -> *u8 |
| 493 | func rs_ent_ascii(i: i64) -> *u8 |
| 506 | func rs_ent_xml(i: i64) -> *u8 |
| 519 | func rs_ent_at(h: *u8, q: i64, to: i64) -> i64 |
| 530 | func rs_unescape(h: *u8, from: i64, to: i64, out: *u8, cap: i64) -> i64 |
| 555 | func 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 } |
| 557 | func rs_rot_x(px: i64, py: i64, cx: i64, cy: i64, rot: i64) -> i64 { if rot == 0 { return px } return cx + (py - cy) } |
| 558 | func rs_rot_y(px: i64, py: i64, cx: i64, cy: i64, rot: i64) -> i64 { if rot == 0 { return py } return cy - (px - cx) } |
| 561 | func 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 |
| 613 | func 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 } |
| 617 | func rs_tag_name(h: *u8, p: i64, to: i64, name: *u8, cap: i64) -> i64 |
| 625 | func 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) } |
| 627 | func rs_viewbox(h: *u8, s0: i64, s1: i64, wh: *i64) -> i64 |
| 656 | func 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 |
| 823 | func rs_svg_standalone(h: *u8, s0: i64, s1: i64, names: *u8, recs: *i64, ntok: i64, path: *u8) -> i64 |
| 885 | func rs_dec_into(dst: *u8, off: i64, v: i64) -> i64 |
| 901 | func rs_export(page: *u8, svg_path: *u8, png_path: *u8, freg: *u8, fbold: *u8, scale: i64, stats: *i64, out: *i64) -> i64 |