nx_cdmap_referee_lib.nx source
↩ module page · 298 lines · 13959 B
1// nx_cdmap_referee_lib.nx -- THE SECOND RULER for the position map (datavis DV2a, 2026-09-15): re-derives the readability
2// grade of an EMITTED cdmap figure from the published SVG alone, then re-measures every label with REAL TrueType advances
3// (nx_ttf_fontlib over a real sans face -- Liberation Sans, SIL-OFL data under knowledge/fonts/) in place of the emitter's
4// 560-permil-em estimate, and runs the same critic (nx_chartlay_lib cl_critic) over both. The emitter's self-grade
5// (api.json readability_cut) is a CLAIM; this lib is the independent method class that can refute it: the heuristic critic
6// must REPRODUCE the published grade from the SVG (else the page and its api.json disagree), and the metric critic says
7// whether real glyph widths would have produced overlaps the estimate hid.
8// It is NOT the browser-pixel referee (DV2, crg_judge): the viewer's font is unknown to us and Liberation Sans is a stand-in
9// of the same class; a browser render judged on pixels stays the contracted rung above this one and is not claimed here.
10// Scope stated: label boxes keep the emitter's 1.25-em height so the ONE variable under test is width; kerning is not applied
11// (a sum of hmtx advances is the width the emitter would need to reserve); entity bytes in a label (a rare &) are
12// measured as their literal characters. license_tier: ORIGINAL
13import "nx_syscalls.nx"
14import "nx_chartlay_lib.nx"
15import "nx_ttf_fontlib.nx"
16
17const CR_MAXN: i64 = 64 // marks or labels one cdmap figure can carry
18const CR_NAME_W: i64 = 64 // bytes per label name slot (zero-terminated)
19const CR_I64_BYTES: i64 = 8
20const CR_ABSENT: i64 = 0 - 1
21const CR_CH_GT: i64 = 62
22const CR_CH_MINUS: i64 = 45
23const CR_CH_0: i64 = 48
24const CR_CH_9: i64 = 57
25const CR_CH_SPACE: i64 = 32
26const CR_CH_COLON: i64 = 58
27const CR_LABEL_BASE: i64 = 12 // the emitter's CDP_LABEL_BASE: a label's text baseline sits this far below its box top
28const CR_FRAME_W: i64 = 4
29const CR_F_X0: i64 = 0
30const CR_F_Y0: i64 = 1
31const CR_F_X1: i64 = 2
32const CR_F_Y1: i64 = 3
33const CR_BASE10: i64 = 10
34const CR_PERMIL: i64 = 1000
35// THE ANNOTATION LAYER (datavis DV5): the emitter wraps every note in <g class='cdnote'> ... </g>; its text lines carry the
36// foreground fill like a label does, so the scanner tells them apart by the GROUP, never by the fill
37const CR_NOTE_OPEN: *u8 = "<g class='cdnote'>"
38const CR_G_CLOSE: *u8 = "</g>"
39
40func cr_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
41func cr_streq(a: *u8, b: *u8) -> i64 {
42 var i: i64 = 0
43 while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 }
44 if b[i] != (0 as u8) { return 0 }
45 return 1
46}
47// bounded substring search: the index of needle inside h[from..to), or CR_ABSENT
48func cr_find(h: *u8, from: i64, to: i64, needle: *u8) -> i64 {
49 let nl: i64 = cr_slen(needle)
50 if nl == 0 { return CR_ABSENT }
51 var i: i64 = from
52 while i + nl <= to {
53 var k: i64 = 0
54 var ok: i64 = 1
55 while k < nl {
56 if h[i + k] != needle[k] { ok = 0; k = nl } else { k = k + 1 }
57 }
58 if ok == 1 { return i }
59 i = i + 1
60 }
61 return CR_ABSENT
62}
63// the integer at h[i] (an optional minus, then digits) read no further than `to`; CR_ABSENT when no digit is there
64func cr_int_at(h: *u8, i: i64, to: i64) -> i64 {
65 var p: i64 = i
66 var neg: i64 = 0
67 if p < to { if (h[p] as i64) == CR_CH_MINUS { neg = 1; p = p + 1 } }
68 var v: i64 = 0
69 var nd: i64 = 0
70 var go: i64 = 1
71 while go == 1 {
72 if p >= to { go = 0 } else {
73 let c: i64 = h[p] as i64
74 if c < CR_CH_0 { go = 0 } else {
75 if c > CR_CH_9 { go = 0 } else { v = v * CR_BASE10 + (c - CR_CH_0); nd = nd + 1; p = p + 1 }
76 }
77 }
78 }
79 if nd == 0 { return CR_ABSENT }
80 if neg == 1 { return 0 - v }
81 return v
82}
83// an integer attribute inside ONE tag [tag0, tag1): the needle is the attribute spelled WITH its leading space and quote
84// (" cx='"), so " x='" can never match inside " cx='"
85func cr_attr(h: *u8, tag0: i64, tag1: i64, needle: *u8) -> i64 {
86 let at: i64 = cr_find(h, tag0, tag1, needle)
87 if at == CR_ABSENT { return CR_ABSENT }
88 return cr_int_at(h, at + cr_slen(needle), tag1)
89}
90func cr_tag_end(h: *u8, from: i64, to: i64) -> i64 {
91 var i: i64 = from
92 while i < to { if (h[i] as i64) == CR_CH_GT { return i } i = i + 1 }
93 return CR_ABSENT
94}
95func cr_name_at(names: *u8, i: i64) -> *u8 { return (names as i64 + i * CR_NAME_W) as *u8 }
96// the LAST index of needle inside h[from..to), or CR_ABSENT
97func cr_rfind(h: *u8, from: i64, to: i64, needle: *u8) -> i64 {
98 var last: i64 = CR_ABSENT
99 var p: i64 = from
100 var go: i64 = 1
101 while go == 1 {
102 let at: i64 = cr_find(h, p, to, needle)
103 if at == CR_ABSENT { go = 0 } else { last = at; p = at + 1 }
104 }
105 return last
106}
107// 1 when offset t sits inside a note group: the nearest group opener before t is not yet closed
108func cr_in_note(h: *u8, s0: i64, t: i64) -> i64 {
109 let g0: i64 = cr_rfind(h, s0, t, CR_NOTE_OPEN)
110 if g0 == CR_ABSENT { return 0 }
111 let g1: i64 = cr_rfind(h, g0, t, CR_G_CLOSE)
112 if g1 == CR_ABSENT { return 1 }
113 return 0
114}
115// copy a text element's content [t1+1, </text>) into names slot i (zero-terminated, CR_NAME_W bound)
116func cr_text_into(h: *u8, t1: i64, s1: i64, names: *u8, i: i64) -> i64 {
117 let e: i64 = cr_find(h, t1, s1, "</text>" as *u8)
118 var q: i64 = t1 + 1
119 var k: i64 = 0
120 let slot: i64 = i * CR_NAME_W
121 while q < e { if k < CR_NAME_W - 1 { names[slot + k] = h[q]; k = k + 1 } q = q + 1 }
122 names[slot + k] = 0 as u8
123 return k
124}
125// parse the NOTE LINES of the cdmap figure: every <text> inside a <g class='cdnote'> group, in document order, with the
126// group's index in nlnote (0-based), x, the baseline y as emitted, and the font size; names in CR_NAME_W slots.
127// out[0] = the number of note groups. Returns the lines found, or CR_ABSENT when the page carries no cdmap svg.
128func cr_scan_notes(h: *u8, n: i64, nlx: *i64, nly: *i64, nlfont: *i64, nlnote: *i64, names: *u8, out: *i64) -> i64 {
129 let s0: i64 = cr_find(h, 0, n, "<svg class='cdmap'" as *u8)
130 if s0 == CR_ABSENT { return CR_ABSENT }
131 var s1: i64 = cr_find(h, s0, n, "</svg>" as *u8)
132 if s1 == CR_ABSENT { s1 = n }
133 var ng: i64 = 0
134 var nl: i64 = 0
135 var p: i64 = s0
136 var go: i64 = 1
137 while go == 1 {
138 let g0: i64 = cr_find(h, p, s1, CR_NOTE_OPEN)
139 if g0 == CR_ABSENT { go = 0 } else {
140 var g1: i64 = cr_find(h, g0, s1, CR_G_CLOSE)
141 if g1 == CR_ABSENT { g1 = s1 }
142 var q: i64 = g0
143 var inner: i64 = 1
144 while inner == 1 {
145 let t0: i64 = cr_find(h, q, g1, "<text" as *u8)
146 if t0 == CR_ABSENT { inner = 0 } else {
147 let t1: i64 = cr_tag_end(h, t0, g1)
148 if t1 == CR_ABSENT { inner = 0 } else {
149 if nl < CR_MAXN {
150 nlx[nl] = cr_attr(h, t0, t1, " x='" as *u8)
151 nly[nl] = cr_attr(h, t0, t1, " y='" as *u8)
152 nlfont[nl] = cr_attr(h, t0, t1, " font-size='" as *u8)
153 nlnote[nl] = ng
154 cr_text_into(h, t1, g1, names, nl)
155 nl = nl + 1
156 }
157 q = t1
158 }
159 }
160 }
161 ng = ng + 1
162 p = g1
163 }
164 }
165 out[0] = ng
166 return nl
167}
168// parse the cdmap figure out of an emitted page: marks (mx, my, mr), labels (lx = box left, ly = box top, lfont, lbold,
169// names in CR_NAME_W slots) and the plot frame from the panel rect (x0, y0, x1, y1). out[0] = marks found.
170// Returns the labels found, or CR_ABSENT when the page carries no cdmap svg at all.
171func cr_scan(h: *u8, n: i64, mx: *i64, my: *i64, mr: *i64, lx: *i64, ly: *i64, lfont: *i64, lbold: *i64, names: *u8, frame: *i64, out: *i64) -> i64 {
172 let s0: i64 = cr_find(h, 0, n, "<svg class='cdmap'" as *u8)
173 if s0 == CR_ABSENT { return CR_ABSENT }
174 var s1: i64 = cr_find(h, s0, n, "</svg>" as *u8)
175 if s1 == CR_ABSENT { s1 = n }
176 frame[CR_F_X0] = CR_ABSENT; frame[CR_F_Y0] = CR_ABSENT; frame[CR_F_X1] = CR_ABSENT; frame[CR_F_Y1] = CR_ABSENT
177 let r0: i64 = cr_find(h, s0, s1, "<rect" as *u8)
178 if r0 != CR_ABSENT {
179 let r1: i64 = cr_tag_end(h, r0, s1)
180 if r1 != CR_ABSENT {
181 frame[CR_F_X0] = cr_attr(h, r0, r1, " x='" as *u8)
182 frame[CR_F_Y0] = cr_attr(h, r0, r1, " y='" as *u8)
183 frame[CR_F_X1] = frame[CR_F_X0] + cr_attr(h, r0, r1, " width='" as *u8)
184 frame[CR_F_Y1] = frame[CR_F_Y0] + cr_attr(h, r0, r1, " height='" as *u8)
185 }
186 }
187 var nm: i64 = 0
188 var p: i64 = s0
189 var go: i64 = 1
190 while go == 1 {
191 let c0: i64 = cr_find(h, p, s1, "<circle" as *u8)
192 if c0 == CR_ABSENT { go = 0 } else {
193 let c1: i64 = cr_tag_end(h, c0, s1)
194 if c1 == CR_ABSENT { go = 0 } else {
195 if nm < CR_MAXN {
196 mx[nm] = cr_attr(h, c0, c1, " cx='" as *u8)
197 my[nm] = cr_attr(h, c0, c1, " cy='" as *u8)
198 mr[nm] = cr_attr(h, c0, c1, " r='" as *u8)
199 nm = nm + 1
200 }
201 p = c1
202 }
203 }
204 }
205 out[0] = nm
206 var nl: i64 = 0
207 p = s0
208 go = 1
209 while go == 1 {
210 let t0: i64 = cr_find(h, p, s1, "<text" as *u8)
211 if t0 == CR_ABSENT { go = 0 } else {
212 let t1: i64 = cr_tag_end(h, t0, s1)
213 if t1 == CR_ABSENT { go = 0 } else {
214 // a player label is the only text the emitter fills with the foreground token; ticks, means and quadrants are muted,
215 // and a note's lines (also foreground) sit inside a cdnote group, which cr_scan_notes reads instead
216 var is_label: i64 = 0
217 if cr_find(h, t0, t1, "fill='var(--nx-color-fg)'" as *u8) != CR_ABSENT { if cr_in_note(h, s0, t0) == 0 { is_label = 1 } }
218 if is_label == 1 {
219 if nl < CR_MAXN {
220 lx[nl] = cr_attr(h, t0, t1, " x='" as *u8)
221 ly[nl] = cr_attr(h, t0, t1, " y='" as *u8) - CR_LABEL_BASE
222 lfont[nl] = cr_attr(h, t0, t1, " font-size='" as *u8)
223 lbold[nl] = 0
224 if cr_find(h, t0, t1, " font-weight='700'" as *u8) != CR_ABSENT { lbold[nl] = 1 }
225 let e: i64 = cr_find(h, t1, s1, "</text>" as *u8)
226 var q: i64 = t1 + 1
227 var k: i64 = 0
228 let slot: i64 = nl * CR_NAME_W
229 while q < e { if k < CR_NAME_W - 1 { names[slot + k] = h[q]; k = k + 1 } q = q + 1 }
230 names[slot + k] = 0 as u8
231 nl = nl + 1
232 }
233 }
234 p = t1
235 }
236 }
237 }
238 return nl
239}
240// the width the emitter RESERVED for a label: its own estimate, so the heuristic critic re-derives the published grade
241func cr_w_heuristic(names: *u8, i: i64, font: i64) -> i64 { return cl_text_w(cr_slen(cr_name_at(names, i)), font) }
242// the width from real hmtx advances at this pixel size (no kerning: the width to reserve, not the width to draw)
243func cr_w_metric(fh: *i64, s: *u8, ph: i64) -> i64 {
244 var i: i64 = 0
245 var wpx: i64 = 0
246 while s[i] != (0 as u8) { wpx = wpx + tf_char_adv(fh, s[i] as i64, ph); i = i + 1 }
247 return wpx
248}
249// fill the label boxes (lw, lh) from either ruler: fhr/fhb = the regular and bold faces for the metric ruler, or 0 for the
250// heuristic. The height stays the emitter's 1.25 em either way so width is the one variable under test.
251func cr_boxes(n: i64, names: *u8, lfont: *i64, lbold: *i64, fhr: *i64, fhb: *i64, lw: *i64, lh: *i64) -> i64 {
252 var i: i64 = 0
253 while i < n {
254 lh[i] = cl_text_h(lfont[i])
255 if (fhr as i64) == 0 { lw[i] = cr_w_heuristic(names, i, lfont[i]) } else {
256 var fh: *i64 = fhr
257 if lbold[i] == 1 { if (fhb as i64) != 0 { fh = fhb } }
258 lw[i] = cr_w_metric(fh, cr_name_at(names, i), lfont[i])
259 }
260 i = i + 1
261 }
262 return 0
263}
264// the critic over the scanned figure with the given boxes; out = CL_CRIT_W slots, the same vector the emitter prints
265func cr_critic(n: i64, mx: *i64, my: *i64, mr: *i64, lx: *i64, ly: *i64, lw: *i64, lh: *i64, frame: *i64, out: *i64) -> i64 {
266 return cl_critic(n, mx, my, mr, lx, ly, lw, lh, frame[CR_F_X0], frame[CR_F_Y0], frame[CR_F_X1], frame[CR_F_Y1], out)
267}
268// the integer after `key` inside the named JSON block (the block name and key are matched as bare text so no double
269// quote has to be spelled in a literal); CR_ABSENT when the block or the key is missing
270func cr_json_int(j: *u8, n: i64, block: *u8, key: *u8) -> i64 {
271 let b: i64 = cr_find(j, 0, n, block)
272 if b == CR_ABSENT { return CR_ABSENT }
273 var bend: i64 = cr_find(j, b + cr_slen(block), n, "}" as *u8)
274 if bend == CR_ABSENT { bend = n }
275 let k: i64 = cr_find(j, b, bend, key)
276 if k == CR_ABSENT { return CR_ABSENT }
277 var p: i64 = k + cr_slen(key)
278 var go: i64 = 1
279 while go == 1 {
280 if p >= bend { go = 0 } else {
281 if (j[p] as i64) == CR_CH_COLON { go = 0 } else { p = p + 1 }
282 }
283 }
284 if p >= bend { return CR_ABSENT }
285 p = p + 1
286 go = 1
287 while go == 1 {
288 if p >= bend { go = 0 } else {
289 if (j[p] as i64) == CR_CH_SPACE { p = p + 1 } else { go = 0 }
290 }
291 }
292 return cr_int_at(j, p, bend)
293}
294// permil delta of the metric width against the heuristic one (positive = the real glyphs are wider than the reserve)
295func cr_delta_permil(heur: i64, metric: i64) -> i64 {
296 if heur <= 0 { return 0 }
297 return (metric - heur) * CR_PERMIL / heur
298}