code wiki / _hdl_build / nx_browser_render_gif_t139.nx

nx_browser_render_gif_t139.nx source

↩ module page · 1771 lines · 103053 B

1// nx_browser_render.nx -- REUSABLE sovereign CSS render core. Holds nx_browser's OS-AGNOSTIC pipeline: 2// br_layout (HTML body -> CSS cascade -> box/flex/float layout -> LayoutTree + computed styles) and 3// br_draw_fb / br_shot_png (paint the laid-out page into an RGBA framebuffer / PNG). NO X11, NO network -- 4// pure compute over sys_mmap, so it compiles into the native-Windows GUI PE exactly as into the WSL/X11 5// browser. The window/blit/input + fetch live in the consumers. CSS-on-native-Windows arc R2. 6// NOTE (R3 dedup pending): these fns are still ALSO defined in runtime/_hdl_build/nx_browser.nx; once this 7// organ is proven, nx_browser.nx imports this and drops its copies. license_tier: ORIGINAL 8import "nx_syscalls.nx" 9import "nx_font8x8.nx" 10import "nx_html_tokenizer.nx" 11import "nx_dom_query.nx" 12import "nx_srcset_lib.nx" // BR14: bi_img_source/bi_srcset -- WHICH url an <img> actually points at 13import "nx_html_entities.nx" 14import "nx_render_html.nx" 15import "nx_css_color_decode.nx" 16import "nx_layout_box.nx" 17import "nx_layout_default_display.nx" 18import "nx_layout_from_dom.nx" 19import "nx_layout_block.nx" 20import "nx_paint_solid_rect.nx" 21import "nx_paint_text.nx" 22import "nx_png_write.nx" 23import "nx_font.nx" // sovereign stroke-VECTOR font (modern heading typeface) + metrics 24import "nx_aa_raster.nx" // AA contour rasterizer for the vector glyphs 25import "nx_ttf_fontlib.nx" // REAL TrueType faces (BR4, 2026-09-02): outlines + hmtx + kern through the same atlas 26 27struct Page { 28 buf: *u8, 29 tree: *LayoutTree, 30 computed: *CssComputedDecl, 31 ncomp: i64, 32 page_h: i64, 33 bhref_off: *i64, // per box_idx: href byte-offset into buf, or -1 34 bhref_len: *i64, 35 ok: i64, 36 raw: *u8, // fetched response body (kept so resize can re-layout WITHOUT re-fetching) 37 raw_len: i64, 38 bsrc_off: *i64, // per box_idx: <img src> byte-offset into buf, or -1 39 bsrc_len: *i64, 40 bimg: *i64, // per box_idx: decoded RGB (w*h*3) pointer, or 0 (filled by the consumer) 41 bimg_w: *i64, // per box_idx: decoded image width 42 bimg_h: *i64, // per box_idx: decoded image height 43 find_ptr: i64, // FIND-IN-PAGE: search-term bytes (as i64 ptr), 0 = no active find 44 find_len: i64, // search-term length 45 vec_headings: i64, // 1 = paint headings with the sovereign stroke-VECTOR font (modern typeface); 46 // 0 = bitmap (default; native-GUI 60fps stays alloc-free). Set by once-render organs. 47 dark_mode: i64, // 1 = dark reader theme (the OLED look): dark bg + light text default stylesheet. 48 // Set BEFORE br_layout (it selects the injected default CSS). 0 = light (default). 49 bcanvas: *i64 // per box_idx: 1 = <canvas> -- a FIRST-CLASS SOURCELESS render surface (seq103): 50 // the consumer fills page.bimg[i]/bimg_w/bimg_h each FRAME and repaints; the 51 // image pass blits the CURRENT buffer at the laid-out box (the game substrate). 52 bsrc_lazy: *i64, // per box_idx: 1 = the <img> declared loading="lazy" (BR14). A REPORT, never a 53 // filter: a renderer painting the whole document must still fetch these, and 54 // suppressing them is precisely how a media page renders blank. Its use is 55 // ORDERING -- a consumer with a BOUNDED fetch budget spends it eager-first. 56 dpr_permil: i64 // device pixels per CSS pixel, in permil, for srcset density selection. 57 // 0 = UNSET, which this core reads as 1x (BI_PERMIL_ONE) -- the identity, not 58 // a tuned default. A consumer that paints at scale N sets N*BI_PERMIL_ONE. 59 bimg_channels: *i64, // 0/3 legacy RGB;4 straight RGBA 60 bimg_owned_bytes: *i64 // exact releasable mapping length;0 borrowed/legacy-unmanaged 61} 62// COUNT THE FIELDS ABOVE, do not hand-count the bytes. Every Page field is one i64 or one pointer, so 63// the size is DERIVED from the field count -- the previous literal 160 was a second copy of the struct's 64// shape that could (and now would) drift the moment a field was added. 65const NX_PAGE_FIELDS: i64 = 24 66const NX_PAGE_FIELD_BYTES: i64 = 8 67const NX_PAGE_BYTES: i64 = NX_PAGE_FIELDS * NX_PAGE_FIELD_BYTES 68const NX_CHROME_H: i64 = 44 // browser chrome (toolbar + address bar) height; page viewport is below it 69 70func br_slen(s: *u8) -> i64 { var k: i64=0; while s[k]!=(0 as u8){k=k+1} return k } 71 72// ---- PER-BOX COMPUTED-DECL INDEX (the eagler paint-cliff fix): br_comp_* used to scan ALL ncomp decls 73// per lookup, per box, per ancestor hop -- O(boxes x ancestors x ncomp) exploded on Next.js-scale CSS 74// (layout <20s, paint >170s). br_cidx_build buckets the computed array BY ELEMENT (stable copy = cascade 75// order preserved within each element, so reverse scan = the same last-wins winner), and every br_comp_* 76// then scans only that box's bucket. OFF by default (statics 0) -> gates with hand-built arrays are 77// byte-identical; br_layout switches it off during its run and rebuilds at the end. ---- 78static br_cidx_on: i64 79static br_cidx_arr: i64 // *CssComputedDecl grouped copy (alloc once, reused) 80static br_cidx_first: i64 // *i64 per box: bucket start 81static br_cidx_cnt: i64 // *i64 per box: bucket len 82static br_cidx_cur: i64 // *i64 scratch cursor 83static br_cidx_nbox: i64 84static br_cidx_src: i64 // the computed-array pointer the index was built FROM: every window guard 85 // checks it, so querying a DIFFERENT page's array (multi-page gates) falls 86 // back to the full scan instead of reading a stale index (self-validating) 87 88func br_cidx_build(tree: *LayoutTree, computed: *CssComputedDecl, ncomp: i64) -> i64 { 89 let nbox: i64 = tree.count 90 if br_cidx_arr == 0 { 91 br_cidx_arr = sys_mmap((NX_CSS_COMPUTED_DECL_BYTES * 262144) as nx_size) as i64 92 br_cidx_first = sys_mmap((16384 * 8) as nx_size) as i64 93 br_cidx_cnt = sys_mmap((16384 * 8) as nx_size) as i64 94 br_cidx_cur = sys_mmap((16384 * 8) as nx_size) as i64 95 } 96 if nbox > 16384 { br_cidx_on = 0; return 0 } 97 if ncomp > 262144 { br_cidx_on = 0; return 0 } 98 let ff: *i64 = br_cidx_first as *i64 99 let cc: *i64 = br_cidx_cnt as *i64 100 let cu: *i64 = br_cidx_cur as *i64 101 var i: i64 = 0 102 while i < nbox { cc[i] = 0; i = i + 1 } 103 var j: i64 = 0 104 while j < ncomp { 105 let cd: *CssComputedDecl = ((computed as i64) + j * NX_CSS_COMPUTED_DECL_BYTES) as *CssComputedDecl 106 let e: i64 = cd.element_idx 107 if e >= 0 { if e < nbox { cc[e] = cc[e] + 1 } } 108 j = j + 1 109 } 110 var acc: i64 = 0 111 i = 0 112 while i < nbox { ff[i] = acc; cu[i] = acc; acc = acc + cc[i]; i = i + 1 } 113 // stable grouped copy (struct copy field-by-field; 64B decls) 114 let dst0: i64 = br_cidx_arr 115 j = 0 116 while j < ncomp { 117 let cd2: *CssComputedDecl = ((computed as i64) + j * NX_CSS_COMPUTED_DECL_BYTES) as *CssComputedDecl 118 let e2: i64 = cd2.element_idx 119 if e2 >= 0 { if e2 < nbox { 120 let dp: *CssComputedDecl = (dst0 + cu[e2] * NX_CSS_COMPUTED_DECL_BYTES) as *CssComputedDecl 121 dp.element_idx = cd2.element_idx 122 dp.prop_off = cd2.prop_off 123 dp.prop_len = cd2.prop_len 124 dp.val_kind = cd2.val_kind 125 dp.val_off = cd2.val_off 126 dp.val_len = cd2.val_len 127 dp.unit_off = cd2.unit_off 128 dp.unit_len = cd2.unit_len 129 cu[e2] = cu[e2] + 1 130 } } 131 j = j + 1 132 } 133 br_cidx_nbox = nbox 134 br_cidx_src = computed as i64 135 br_cidx_on = 1 136 return 0 137} 138func br_comp_int(src: *u8, computed: *CssComputedDecl, ncomp: i64, box_idx: i64, prop: *u8, plen: i64, dflt: i64) -> i64 { 139 var base: i64 = computed as i64 140 var lo: i64 = 0 141 var hi: i64 = ncomp 142 if br_cidx_on == 1 { if (computed as i64) == br_cidx_src { if box_idx >= 0 { if box_idx < br_cidx_nbox { 143 let ff: *i64 = br_cidx_first as *i64 144 let cc: *i64 = br_cidx_cnt as *i64 145 base = br_cidx_arr 146 lo = ff[box_idx] 147 hi = lo + cc[box_idx] 148 } } } } 149 var j: i64 = hi - 1 150 while j >= lo { 151 let cd: *CssComputedDecl = (base + j * NX_CSS_COMPUTED_DECL_BYTES) as *CssComputedDecl 152 if cd.element_idx == box_idx { if cd.prop_len == plen { 153 var ok: i64 = 1; var k: i64 = 0 154 while k < plen { if (src[cd.prop_off+k]&0xff) != (prop[k]&0xff) { ok=0; k=plen } else { k=k+1 } } 155 if ok == 1 { 156 // integer-truncate at the decimal point ("13.5px" is 13, NOT 135 -- the same skip-the-dot 157 // digit-accumulator bug fixed in _layout_fontsize_decl_px; measure==paint requires both) 158 var v: i64 = 0; var any: i64 = 0; var i: i64 = 0 159 while i < cd.val_len { 160 let c: i64 = src[cd.val_off+i]&0xff 161 if c == 46 { i = cd.val_len } 162 else { if c>=48 { if c<=57 { v=v*10+(c-48); any=1 } } i=i+1 } 163 } 164 if any == 1 { return v } 165 return dflt 166 } 167 } } 168 j = j - 1 169 } 170 return dflt 171} 172func br_comp_color(src: *u8, computed: *CssComputedDecl, ncomp: i64, box_idx: i64, prop: *u8, plen: i64) -> i64 { 173 var base: i64 = computed as i64 174 var lo: i64 = 0 175 var hi: i64 = ncomp 176 if br_cidx_on == 1 { if (computed as i64) == br_cidx_src { if box_idx >= 0 { if box_idx < br_cidx_nbox { 177 let ff: *i64 = br_cidx_first as *i64 178 let cc: *i64 = br_cidx_cnt as *i64 179 base = br_cidx_arr 180 lo = ff[box_idx] 181 hi = lo + cc[box_idx] 182 } } } } 183 var j: i64 = hi - 1 184 while j >= lo { 185 let cd: *CssComputedDecl = (base + j * NX_CSS_COMPUTED_DECL_BYTES) as *CssComputedDecl 186 if cd.element_idx == box_idx { if cd.prop_len == plen { 187 var ok: i64 = 1; var k: i64 = 0 188 while k < plen { if (src[cd.prop_off+k]&0xff) != (prop[k]&0xff) { ok=0; k=plen } else { k=k+1 } } 189 if ok == 1 { 190 let col: *CssColor = (sys_mmap(NX_CSS_COLOR_BYTES as nx_size)) as *CssColor 191 var got: i64 = 0 192 if cd.val_kind == NX_CSS_VAL_HEXCOLOR { got = nx_css_color_decode(src, cd.val_off, cd.val_len, col) } 193 else { if cd.val_kind == NX_CSS_VAL_IDENT { got = nx_css_color_named(src, cd.val_off, cd.val_len, col); if got != 1 { got = nx_css_color_rgb_func(src, cd.val_off, cd.val_len, col) } } } 194 if got == 1 { if col.a >= 128 { return (col.r << 16) | (col.g << 8) | col.b } } // SOLID painter: <50% alpha = tint, not paint (linkedin rgba(0,0,0,0.08) painted BLACK, 2026-07-28) 195 return 0 - 1 196 } 197 } } 198 j = j - 1 199 } 200 return 0 - 1 201} 202 203// INHERITED color resolve (CSS: `color` inherits): walk up the parent chain to the nearest box with a 204// computed color decl. Without this, author colors on body/.doc never reached p/li/td TEXT (the UA sheet 205// only "worked" because it fakes inheritance with explicit p{color}/li{color} rules -- measured 07-09). 206// FONT-SIZE resolver for one element: matches `font-size` (integer-truncating parse) OR the `font` 207// SHORTHAND when the value leads with a size ("15px/1.55 system-ui") -- one bucketed reverse scan across 208// both = last-wins cascade between them. MIRRORS _layout_fontsize_decl_px exactly (measure==paint law). 209// `pct_out[0]` <- 1 when the declared value is a PERCENTAGE; the return is then the percentage 210// number itself, which br_comp_fontsize_inh resolves against the parent. Mirrors the layout-side 211// _layout_fontsize_decl_px signature exactly (measure==paint law: if only one side learned about 212// percentages, every styled run would overflow its reserved box and split mid-word). 213func br_comp_fontsize_own(src: *u8, computed: *CssComputedDecl, ncomp: i64, box_idx: i64, pct_out: *i64) -> i64 { 214 var base: i64 = computed as i64 215 var lo: i64 = 0 216 var hi: i64 = ncomp 217 if br_cidx_on == 1 { if (computed as i64) == br_cidx_src { if box_idx >= 0 { if box_idx < br_cidx_nbox { 218 let ff: *i64 = br_cidx_first as *i64 219 let cc: *i64 = br_cidx_cnt as *i64 220 base = br_cidx_arr 221 lo = ff[box_idx] 222 hi = lo + cc[box_idx] 223 } } } } 224 var j: i64 = hi - 1 225 while j >= lo { 226 let cd: *CssComputedDecl = (base + j * NX_CSS_COMPUTED_DECL_BYTES) as *CssComputedDecl 227 if cd.element_idx == box_idx { 228 if cd.prop_len == 4 { if br_prop_eq(src, cd.prop_off, "font\x00" as *u8, 4) == 1 { 229 let c0: i64 = (src[cd.val_off] as i64) & 255 230 if c0 >= 48 { if c0 <= 57 { 231 var v4: i64 = 0 232 var j4: i64 = 0 233 var g4: i64 = 1 234 while g4 == 1 { 235 if j4 >= cd.val_len { g4 = 0 } 236 else { 237 let c4: i64 = (src[cd.val_off + j4] as i64) & 255 238 if c4 >= 48 { if c4 <= 57 { v4 = v4 * 10 + (c4 - 48); j4 = j4 + 1 } else { g4 = 0 } } else { g4 = 0 } 239 } 240 } 241 if v4 > 0 { return v4 } 242 } } 243 } } 244 if cd.prop_len == 9 { if br_prop_eq(src, cd.prop_off, "font-size\x00" as *u8, 9) == 1 { 245 // ONE value resolver, shared with the measure side (nx_fontsize_value_px lives in 246 // nx_layout_block.nx which this file imports): px/%/em/rem/fractions, calc()/keywords 247 // dropped (-1) -> fall through to the older cascade decl. measure==paint BY CONSTRUCTION. 248 let fv: i64 = nx_fontsize_value_px(src, cd.val_off, cd.val_len, cd.unit_off, cd.unit_len, pct_out) 249 if fv >= 0 { return fv } 250 } } 251 } 252 j = j - 1 253 } 254 return 0 - 1 255} 256// LINE-HEIGHT resolver (paint mirror of _layout_lineheight_decl_px/_layout_lineheight_px -- measure==paint 257// law): `line-height: 1.55` unitless multiplier of fs / `line-height: 24px` / the `font` shorthand's /N. 258// Bucketed reverse scan per element; nearest ancestor with a decl wins. Returns px or -1. 259func br_comp_lineheight_own(src: *u8, computed: *CssComputedDecl, ncomp: i64, box_idx: i64, fs: i64) -> i64 { 260 var base: i64 = computed as i64 261 var lo: i64 = 0 262 var hi: i64 = ncomp 263 if br_cidx_on == 1 { if (computed as i64) == br_cidx_src { if box_idx >= 0 { if box_idx < br_cidx_nbox { 264 let ff: *i64 = br_cidx_first as *i64 265 let cc: *i64 = br_cidx_cnt as *i64 266 base = br_cidx_arr 267 lo = ff[box_idx] 268 hi = lo + cc[box_idx] 269 } } } } 270 var j: i64 = hi - 1 271 while j >= lo { 272 let cd: *CssComputedDecl = (base + j * NX_CSS_COMPUTED_DECL_BYTES) as *CssComputedDecl 273 if cd.element_idx == box_idx { 274 if cd.prop_len == 11 { if br_prop_eq(src, cd.prop_off, "line-height\x00" as *u8, 11) == 1 { 275 var nl: i64 = 0 276 var ns: i64 = 1 277 while ns == 1 { 278 if nl >= cd.val_len { ns = 0 } 279 else { 280 let c: i64 = (src[cd.val_off + nl] as i64) & 255 281 var isn: i64 = 0 282 if c >= 48 { if c <= 57 { isn = 1 } } 283 if c == 46 { isn = 1 } 284 if isn == 1 { nl = nl + 1 } else { ns = 0 } 285 } 286 } 287 if nl == 0 { return 0 - 1 } 288 let q3: i64 = nx_css_number_parse(src, cd.val_off, nl) 289 if q3 == NX_CSS_NUMBER_PARSE_ERROR { return 0 - 1 } 290 // px unit: DIMENSION tokens carry it in the SEPARATE unit fields (val = digits only); 291 // swept spans carry it inline after the digits. Check both. Unitless requires the value 292 // to be EXACTLY the number (no trailing unit we don't understand -> miss, not 16x). 293 if cd.unit_len == 2 { 294 if ((src[cd.unit_off] as i64) & 255) == 112 { if ((src[cd.unit_off + 1] as i64) & 255) == 120 { 295 return q3 / 1000 296 } } 297 } 298 if nl + 1 < cd.val_len { 299 if ((src[cd.val_off + nl] as i64) & 255) == 112 { if ((src[cd.val_off + nl + 1] as i64) & 255) == 120 { 300 return q3 / 1000 301 } } 302 } 303 if cd.unit_len == 0 { if nl == cd.val_len { return (q3 * fs) / 1000 } } 304 return 0 - 1 305 } } 306 if cd.prop_len == 4 { if br_prop_eq(src, cd.prop_off, "font\x00" as *u8, 4) == 1 { 307 let c0: i64 = (src[cd.val_off] as i64) & 255 308 if c0 >= 48 { if c0 <= 57 { 309 var j2: i64 = 0 310 var sl: i64 = 0 - 1 311 var g: i64 = 1 312 while g == 1 { 313 if j2 >= cd.val_len { g = 0 } 314 else { 315 let cj: i64 = (src[cd.val_off + j2] as i64) & 255 316 if cj == 47 { sl = j2; g = 0 } 317 else { if cj == 32 { g = 0 } else { j2 = j2 + 1 } } 318 } 319 } 320 if sl >= 0 { 321 let vo: i64 = cd.val_off + sl + 1 322 let vmax: i64 = cd.val_len - sl - 1 323 var n2: i64 = 0 324 var g2: i64 = 1 325 while g2 == 1 { 326 if n2 >= vmax { g2 = 0 } 327 else { 328 let c2: i64 = (src[vo + n2] as i64) & 255 329 var isn2: i64 = 0 330 if c2 >= 48 { if c2 <= 57 { isn2 = 1 } } 331 if c2 == 46 { isn2 = 1 } 332 if isn2 == 1 { n2 = n2 + 1 } else { g2 = 0 } 333 } 334 } 335 if n2 > 0 { 336 let q32: i64 = nx_css_number_parse(src, vo, n2) 337 if q32 != NX_CSS_NUMBER_PARSE_ERROR { 338 if n2 + 1 < vmax { 339 if ((src[vo + n2] as i64) & 255) == 112 { if ((src[vo + n2 + 1] as i64) & 255) == 120 { 340 return q32 / 1000 341 } } 342 } 343 return (q32 * fs) / 1000 344 } 345 } 346 } 347 } } 348 } } 349 } 350 j = j - 1 351 } 352 return 0 - 1 353} 354func br_comp_lineheight_px(src: *u8, computed: *CssComputedDecl, ncomp: i64, tree: *LayoutTree, box_idx: i64, fs: i64) -> i64 { 355 var cur: i64 = box_idx 356 var guard: i64 = 0 357 while cur >= 0 { 358 if guard > 64 { return 0 - 1 } 359 guard = guard + 1 360 let v: i64 = br_comp_lineheight_own(src, computed, ncomp, cur, fs) 361 if v >= 0 { return v } 362 let b: *LayoutBox = ((tree.boxes as i64) + cur * NX_LAYOUT_BOX_BYTES) as *LayoutBox 363 cur = b.parent_idx 364 } 365 return 0 - 1 366} 367// INHERITED font-size: nearest ancestor with a font-size OR font-shorthand decl, else dflt. 368func br_comp_fontsize_inh(src: *u8, computed: *CssComputedDecl, ncomp: i64, tree: *LayoutTree, box_idx: i64, dflt: i64) -> i64 { 369 let pct: *i64 = sys_mmap(16) as *i64 370 var cur: i64 = box_idx 371 var guard: i64 = 0 372 while cur >= 0 { 373 if guard > 64 { return dflt } 374 guard = guard + 1 375 pct[0] = 0 376 let v: i64 = br_comp_fontsize_own(src, computed, ncomp, cur, pct) 377 if v >= 0 { 378 if pct[0] == 0 { return v } 379 let bp: *LayoutBox = ((tree.boxes as i64) + cur * NX_LAYOUT_BOX_BYTES) as *LayoutBox 380 let parent_px: i64 = br_comp_fontsize_inh(src, computed, ncomp, tree, bp.parent_idx, dflt) 381 var r: i64 = (parent_px * v) / 100 382 if r < 1 { r = 1 } 383 return r 384 } 385 let b: *LayoutBox = ((tree.boxes as i64) + cur * NX_LAYOUT_BOX_BYTES) as *LayoutBox 386 cur = b.parent_idx 387 } 388 return dflt 389} 390func br_comp_color_inh(src: *u8, computed: *CssComputedDecl, ncomp: i64, tree: *LayoutTree, box_idx: i64) -> i64 { 391 var cur: i64 = box_idx 392 var guard: i64 = 0 393 while cur >= 0 { 394 if guard > 64 { return 0 - 1 } 395 guard = guard + 1 396 let c: i64 = br_comp_color(src, computed, ncomp, cur, "color\x00" as *u8, 5) 397 if c >= 0 { return c } 398 let b: *LayoutBox = ((tree.boxes as i64) + cur * NX_LAYOUT_BOX_BYTES) as *LayoutBox 399 cur = b.parent_idx 400 } 401 return 0 - 1 402} 403 404// background resolve: background-color, else the `background` SHORTHAND's hex value (a single-hex shorthand 405// like background:#eef1f5 tokenizes as HEXCOLOR and decodes; complex shorthands fail closed to -1). bg does 406// NOT inherit in CSS, so no parent walk. 407func br_prop_eq(src: *u8, off: i64, lit: *u8, len: i64) -> i64 { 408 var k: i64 = 0 409 while k < len { if (src[off+k]&0xff) != (lit[k]&0xff) { return 0 } k=k+1 } 410 return 1 411} 412// S21 fix: background must respect CASCADE ORDER across BOTH `background-color` and the `background` shorthand. 413// The old version tried `background-color` first -> the UA sheet's body{background-color:#fff} ALWAYS beat an 414// author body{background:var(--bg)} shorthand -> our dark-theme pages rendered light-text-on-white (invisible). 415// Now: scan from the END (highest cascade) for the LAST decl of EITHER property and decode that. 416func br_comp_bg(src: *u8, computed: *CssComputedDecl, ncomp: i64, box_idx: i64) -> i64 { 417 var base: i64 = computed as i64 418 var lo: i64 = 0 419 var hi: i64 = ncomp 420 if br_cidx_on == 1 { if (computed as i64) == br_cidx_src { if box_idx >= 0 { if box_idx < br_cidx_nbox { 421 let ff: *i64 = br_cidx_first as *i64 422 let cc: *i64 = br_cidx_cnt as *i64 423 base = br_cidx_arr 424 lo = ff[box_idx] 425 hi = lo + cc[box_idx] 426 } } } } 427 var j: i64 = hi - 1 428 while j >= lo { 429 let cd: *CssComputedDecl = (base + j * NX_CSS_COMPUTED_DECL_BYTES) as *CssComputedDecl 430 if cd.element_idx == box_idx { 431 var isbg: i64 = 0 432 if cd.prop_len == 16 { if br_prop_eq(src, cd.prop_off, "background-color\x00" as *u8, 16)==1 { isbg=1 } } 433 if isbg == 0 { if cd.prop_len == 10 { if br_prop_eq(src, cd.prop_off, "background\x00" as *u8, 10)==1 { isbg=1 } } } 434 if isbg == 1 { 435 let col: *CssColor = (sys_mmap(NX_CSS_COLOR_BYTES as nx_size)) as *CssColor 436 var got: i64 = 0 437 if cd.val_kind == NX_CSS_VAL_HEXCOLOR { got = nx_css_color_decode(src, cd.val_off, cd.val_len, col) } 438 else { if cd.val_kind == NX_CSS_VAL_IDENT { got = nx_css_color_named(src, cd.val_off, cd.val_len, col); if got != 1 { got = nx_css_color_rgb_func(src, cd.val_off, cd.val_len, col) } } } 439 if got == 1 { if col.a >= 128 { return (col.r << 16) | (col.g << 8) | col.b } } // SOLID painter: <50% alpha = tint, not paint (linkedin rgba(0,0,0,0.08) painted BLACK, 2026-07-28) 440 return 0 - 1 441 } 442 } 443 j = j - 1 444 } 445 return 0 - 1 446} 447 448// `border` SHORTHAND resolve: the computed value is a swept span like "1px solid #cbd5e1" -- parse the 449// leading width int + the #hex color out of the span. Returns the color (wout[0]=width px, clamped 1..8) 450// or -1 when the box has no parseable border. (border-style variants beyond solid paint as solid -- v1.) 451func br_comp_border(src: *u8, computed: *CssComputedDecl, ncomp: i64, box_idx: i64, wout: *i64) -> i64 { 452 var base: i64 = computed as i64 453 var lo: i64 = 0 454 var hi: i64 = ncomp 455 if br_cidx_on == 1 { if (computed as i64) == br_cidx_src { if box_idx >= 0 { if box_idx < br_cidx_nbox { 456 let ff: *i64 = br_cidx_first as *i64 457 let cc: *i64 = br_cidx_cnt as *i64 458 base = br_cidx_arr 459 lo = ff[box_idx] 460 hi = lo + cc[box_idx] 461 } } } } 462 var j: i64 = hi - 1 463 while j >= lo { 464 let cd: *CssComputedDecl = (base + j * NX_CSS_COMPUTED_DECL_BYTES) as *CssComputedDecl 465 if cd.element_idx == box_idx { if cd.prop_len == 6 { 466 let P: *u8 = "border\x00" as *u8 467 var ok: i64 = 1 468 var k: i64 = 0 469 while k < 6 { if (src[cd.prop_off+k]&0xff) != (P[k]&0xff) { ok=0; k=6 } else { k=k+1 } } 470 if ok == 1 { 471 var wv: i64 = 0 472 var i2: i64 = cd.val_off 473 let e2: i64 = cd.val_off + cd.val_len 474 while i2 < e2 { let ch: i64 = src[i2]&0xff; if ch>=48 { if ch<=57 { wv=wv*10+(ch-48); i2=i2+1 } else { i2=e2 } } else { i2=e2 } } 475 if wv <= 0 { wv = 1 } 476 if wv > 8 { wv = 8 } 477 var hp: i64 = 0 - 1 478 var i3: i64 = cd.val_off 479 while i3 < e2 { if src[i3]==(35 as u8) { hp=i3; i3=e2 } else { i3=i3+1 } } 480 if hp >= 0 { 481 let col: *CssColor = (sys_mmap(NX_CSS_COLOR_BYTES as nx_size)) as *CssColor 482 if nx_css_color_decode(src, hp+1, 6, col) == 1 { 483 wout[0] = wv 484 return (col.r << 16) | (col.g << 8) | col.b 485 } 486 } 487 return 0 - 1 488 } 489 } } 490 j = j - 1 491 } 492 return 0 - 1 493} 494 495// Extract elements (tag/id/class) AND per-element href, mirroring rh_extract's chrome-skip EXACTLY (so the 496// element order == the layout box order). ehref_off/ehref_len get the <a href> span, or -1. 497func br_extract(html: *u8, hlen: i64, elements: *CssElement, ehref_off: *i64, ehref_len: *i64, esrc_off: *i64, esrc_len: *i64, elazy: *i64, vw: i64, dpr_permil: i64, maxn: i64) -> i64 { 498 let c: *HtmlCursor=sys_mmap(24) as *HtmlCursor; nx_html_cursor_init(c,html,hlen) 499 let tok: *HtmlToken=sys_mmap(56) as *HtmlToken 500 let ao: *i64=sys_mmap(8) as *i64; let al: *i64=sys_mmap(8) as *i64; let bo: *i64=sys_mmap(8) as *i64; let bl: *i64=sys_mmap(8) as *i64 501 let ho: *i64=sys_mmap(8) as *i64; let hl: *i64=sys_mmap(8) as *i64 502 // BR14 scratch, allocated ONCE for the whole token walk. nx_srcset_lib deliberately takes its 503 // scratch from the caller so that selecting a source costs no allocation per <img> -- a page with 504 // a thousand figures must not buy a thousand mmaps to answer the same question. 505 let bisc: *i64=sys_mmap(8 * BI_SCRATCH_SLOTS) as *i64 506 var n: i64=0 507 var cskip: i64=0 508 while 1==1 { 509 nx_html_next_token(c,tok) 510 if tok.kind==NX_HTML_TOK_EOF { return n } 511 if n>=maxn { return n } 512 var ie: i64=0 513 if cskip > 0 { 514 // void START tags never get an END -- exempt from depth (MIRRORS nx_layout_from_dom exactly) 515 if tok.kind==NX_HTML_TOK_START_TAG { if _lfd_is_void_tag(html, tok.name_off, tok.name_len) == 0 { cskip = cskip + 1 } } 516 if tok.kind==NX_HTML_TOK_END_TAG { cskip = cskip - 1 } 517 } else { 518 if tok.kind==NX_HTML_TOK_START_TAG { 519 if _lfd_is_chrome_start(html, tok.name_off, tok.name_len, tok.src_off, tok.src_len) == 1 { cskip = 1 } 520 else { ie = 1 } 521 } 522 if tok.kind==NX_HTML_TOK_SELF_CLOSING { ie = 1 } 523 } 524 // RAW-TEXT bodies (script/style/textarea/title) must be consumed with the HTML5 raw-text 525 // scanner, EXACTLY as nx_layout_from_dom does -- br_extract was the consumer that forgot. 526 // Without this, tag-like text inside a JS string ("<div class=...>") mints PHANTOM elements 527 // here that from_dom never boxed, and the positional box<->element pairing in br_layout 528 // SKEWS at the first such script: every later box wears the WRONG identity (wrong classes -> 529 // wrong widths/heights/colors). Control-probe proven 2026-07-28: one phantom-bearing script 530 // before a div shifted even <body> off its own UA padding (x=44 -> 0). The k-map's two 531 // walkers must see the SAME element stream, so this mirror is a CORRECTNESS invariant. 532 if tok.kind==NX_HTML_TOK_START_TAG { 533 if nx_html_is_raw_text_tag(html, tok.name_off, tok.name_len) == 1 { 534 let rawtok: *HtmlToken = sys_mmap(56) as *HtmlToken 535 nx_html_consume_raw_text(c, ((html as i64) + tok.name_off) as *u8, tok.name_len, rawtok) 536 // cursor is left AT the close tag (tokenizer contract) -- the arriving END_TAG token 537 // balances cskip naturally, so NO depth fixup here (a fixup would double-decrement). 538 } 539 } 540 if ie==1 { 541 let e: *CssElement=((elements as i64)+n*NX_CSS_ELEMENT_BYTES) as *CssElement 542 e.src=html; e.tag_off=tok.name_off; e.tag_len=tok.name_len 543 if nx_dom_find_attr(html,tok.src_off,tok.src_len,"id\x00" as *u8,ao,al)==1 { e.id_off=ao[0]; e.id_len=al[0] } else { e.id_off=0; e.id_len=0 } 544 if nx_dom_find_attr(html,tok.src_off,tok.src_len,"class\x00" as *u8,bo,bl)==1 { e.class_off=bo[0]; e.class_len=bl[0] } else { e.class_off=0; e.class_len=0 } 545 ehref_off[n] = 0 - 1; ehref_len[n] = 0 - 1 546 if tok.name_len==1 { if (html[tok.name_off]&0xff)==97 { // <a> 547 if nx_dom_find_attr(html,tok.src_off,tok.src_len,"href\x00" as *u8,ho,hl)==1 { ehref_off[n]=ho[0]; ehref_len[n]=hl[0] } 548 } } 549 esrc_off[n] = 0 - 1; esrc_len[n] = 0 - 1; elazy[n] = 0 550 if tok.name_len==3 { if (html[tok.name_off]&0xff)==105 { if (html[tok.name_off+1]&0xff)==109 { if (html[tok.name_off+2]&0xff)==103 { // <img> 551 // BR14. This line used to read `src` AND NOTHING ELSE, which is the whole of the 552 // `images-lazy` defect: a lazy-loaded <img> parks a data: URI in src and keeps the real 553 // URL in srcset / data-src, so the renderer was handed a placeholder and painted 554 // nothing -- with byte-exact decoders sitting one correct URL away. bi_img_source asks 555 // the browser's question instead ("which source does THIS element resolve to at THIS 556 // viewport") and answers with a byte range into this same buffer, so nothing downstream 557 // of esrc_off changes shape. An <img> carrying only a plain src still takes the src 558 // branch and selects exactly the bytes this line selected before BR14 existed. 559 bi_img_source(html, tok.src_off, tok.src_len, vw, dpr_permil, bisc, ho, hl) 560 esrc_off[n]=ho[0]; esrc_len[n]=hl[0] 561 elazy[n] = bi_img_lazy(html, tok.src_off, tok.src_len, bisc) 562 } } } } 563 n=n+1 564 } 565 } 566 return n 567} 568 569// layout-only pass: (re)build the style buffer + lay out page.raw at viewport width `vw`. 570// Split from the fetch so a window RESIZE re-lays-out at the new width with NO network round-trip. 571func br_layout(page: *Page, vw: i64) -> i64 { 572 if br_release_image_metadata(page)!=0 { page.ok=0;return 0-1 } 573 page.ok = 0 574 br_cidx_on = 0 // index describes the PREVIOUS layout's array -- off until rebuilt below 575 let body: *u8 = page.raw 576 let body_len: i64 = page.raw_len 577 578 // UA sheet note (07-09): headings + p/li carry NO color -- like real UA sheets they INHERIT body color 579 // (br_comp_color_inh), so author header{color:#fff} / body{color:...} reach them. body keeps the default. 580 var css: *u8 = "h1{margin-top:30px;margin-bottom:16px;font-size:40px}h2{margin-top:28px;margin-bottom:12px;font-size:28px}h3{margin-top:20px;margin-bottom:8px;font-size:22px}body{background-color:#ffffff;color:#2a2a33;padding-left:44px;padding-right:44px;padding-top:22px}p{margin-top:14px;margin-bottom:14px}ul{padding-left:20px;margin-top:6px;margin-bottom:6px}ol{padding-left:28px;margin-top:6px;margin-bottom:6px}li{margin-bottom:5px}table{display:table}tr{display:flex}th{font-weight:bold;padding-left:4px;padding-right:4px}td{padding-left:4px;padding-right:4px}h1{font-weight:bold}h2{font-weight:bold}h3{font-weight:bold}h4{font-weight:bold}h5{font-weight:bold}h6{font-weight:bold}a{color:#2563eb}b{font-weight:bold}strong{font-weight:bold}i{font-style:italic}em{font-style:italic}.vector-menu-content-list{display:none}.vector-dropdown{display:none}.mw-interlanguage-selector{display:none}.wbc-editpage{display:none}.vector-toc{display:none}.vector-toc-list{display:none}.vector-toc-list-item{display:none}.vector-toc-link{display:none}.vector-page-toolbar{display:none}.infobox{float:right;width:300px;margin-left:8px;background-color:#f7f7fa}.nishi-buyguard{background-color:#fff3cd;color:#332701;padding-left:14px;padding-right:14px;padding-top:8px;padding-bottom:8px;margin-bottom:10px;font-size:18px}\x00" 581 // DARK READER THEME (the OLED look): near-black bg + light text; same structure so layout is identical. 582 if page.dark_mode == 1 { 583 css = "h1{margin-top:30px;margin-bottom:16px;font-size:40px}h2{margin-top:28px;margin-bottom:12px;font-size:28px}h3{margin-top:20px;margin-bottom:8px;font-size:22px}body{background-color:#0e1116;color:#d4dae3;padding-left:44px;padding-right:44px;padding-top:22px}p{margin-top:14px;margin-bottom:14px}ul{padding-left:20px;margin-top:6px;margin-bottom:6px}ol{padding-left:28px;margin-top:6px;margin-bottom:6px}li{margin-bottom:5px}table{display:table}tr{display:flex}th{font-weight:bold;padding-left:4px;padding-right:4px}td{padding-left:4px;padding-right:4px}h1{font-weight:bold}h2{font-weight:bold}h3{font-weight:bold}h4{font-weight:bold}h5{font-weight:bold}h6{font-weight:bold}a{color:#6aa3ff}b{font-weight:bold}strong{font-weight:bold}i{font-style:italic}em{font-style:italic}.vector-menu-content-list{display:none}.vector-dropdown{display:none}.mw-interlanguage-selector{display:none}.wbc-editpage{display:none}.vector-toc{display:none}.vector-toc-list{display:none}.vector-toc-list-item{display:none}.vector-toc-link{display:none}.vector-page-toolbar{display:none}.infobox{float:right;width:300px;margin-left:8px;background-color:#1a1f28}.nishi-buyguard{background-color:#2a2410;color:#ffe08a;padding-left:14px;padding-right:14px;padding-top:8px;padding-bottom:8px;margin-bottom:10px;font-size:18px}\x00" 584 } 585 let clen: i64 = br_slen(css) 586 let STYCAP: i64 = 262144 587 let buf: *u8 = sys_mmap(body_len + STYCAP + clen + 16) 588 var bi: i64 = 0 589 while bi < body_len { buf[bi]=body[bi]; bi=bi+1 } 590 // CASCADE ORIGIN ORDER (07-09 root fix): UA defaults FIRST, author <style> AFTER -- the resolvers take 591 // the LAST matching decl, so author rules now OVERRIDE the defaults with UA as the fallback (correct CSS 592 // origin order). Before this swap the appended defaults silently beat every author color on styled pages 593 // (measured: identical palettes with and without author colors -- the dfb probes, 2026-07-09). 594 var ci: i64 = 0 595 while ci < clen { buf[body_len + ci] = css[ci]; ci=ci+1 } 596 let rawsty: *u8 = sys_mmap(STYCAP) 597 let rawstylen: i64 = rh_extract_styles(body, body_len, rawsty, STYCAP) 598 var stylen: i64 = rh_filter_media(rawsty, 0, rawstylen, ((buf as i64)+body_len+clen) as *u8, STYCAP, vw) 599 // STYLED-PAGE UA CORRECTION (2026-07-28): our UA body padding (44px) is a READABILITY choice for 600 // unstyled pages; Chrome's real UA default is body margin 8px. On a page that ships author CSS, 601 // keeping 44px shifted ALL content right of Chrome (measured: chrome first-ink x=24, ours 68 on 602 // styled wikipedia). Inject the Chrome-parity body box AFTER our UA sheet (wins over it) but 603 // BEFORE author styles (so the page's own body rules still win over both). +padding-bottom for 604 // symmetry; sites that set body padding override all of this normally. 605 if stylen > 0 { if stylen + 100 < STYCAP { 606 let uafix: *u8 = "body{padding-left:8px;padding-right:8px;padding-top:8px;padding-bottom:8px}\x00" as *u8 607 var ufl: i64 = 0 608 while uafix[ufl] != (0 as u8) { ufl = ufl + 1 } 609 // shift the author block right by ufl and place the fix between UA and author 610 var mv: i64 = stylen - 1 611 while mv >= 0 { buf[body_len + clen + ufl + mv] = buf[body_len + clen + mv]; mv = mv - 1 } 612 var uc: i64 = 0 613 while uc < ufl { buf[body_len + clen + uc] = uafix[uc]; uc = uc + 1 } 614 stylen = stylen + ufl 615 } } 616 let total: i64 = body_len + clen + stylen 617 618 var VW: i64 = vw 619 if VW < 200 { VW = 200 } 620 let boxes: *LayoutBox = (sys_mmap(NX_LAYOUT_BOX_BYTES*16384+16)) as *LayoutBox 621 let tree: *LayoutTree = (sys_mmap(64)) as *LayoutTree 622 nx_layout_tree_init(tree, boxes, 16384) 623 let stk: *LayoutFromDomStack = (sys_mmap(NX_LAYOUT_FROM_DOM_STACK_BYTES+8)) as *LayoutFromDomStack 624 let sidx: *i64 = (sys_mmap(8*128)) as *i64 625 nx_layout_from_dom_stack_init(stk, sidx, 128) 626 nx_layout_from_dom(buf, body_len, tree, stk) 627 628 let ext: *CssElement = (sys_mmap(NX_CSS_ELEMENT_BYTES*16384 as nx_size)) as *CssElement 629 let ehoff: *i64 = sys_mmap(8*16384) as *i64 630 let ehlen: *i64 = sys_mmap(8*16384) as *i64 631 let esoff: *i64 = sys_mmap(8*16384) as *i64 632 let eslen: *i64 = sys_mmap(8*16384) as *i64 633 let eslazy: *i64 = sys_mmap(8*16384) as *i64 634 // BR14: the density the fetched image has to cover. 0 means the consumer never declared one, and 635 // this core then reads it as the IDENTITY (one device pixel per CSS pixel) rather than inventing a 636 // display -- a consumer that paints at scale N sets page.dpr_permil = N * BI_PERMIL_ONE. 637 var dpr: i64 = page.dpr_permil 638 if dpr <= 0 { dpr = BI_PERMIL_ONE } 639 let n_ext: i64 = br_extract(buf, body_len, ext, ehoff, ehlen, esoff, eslen, eslazy, VW, dpr, 16384) 640 let boxel: *CssElement = (sys_mmap((NX_CSS_ELEMENT_BYTES*(tree.count+1)) as nx_size)) as *CssElement 641 let bhoff: *i64 = sys_mmap(8*(tree.count+1)) as *i64 642 let bhlen: *i64 = sys_mmap(8*(tree.count+1)) as *i64 643 let bsoff: *i64 = sys_mmap(8*(tree.count+1)) as *i64 644 let bslen: *i64 = sys_mmap(8*(tree.count+1)) as *i64 645 let bslazy: *i64 = sys_mmap(8*(tree.count+1)) as *i64 646 let image_meta_bytes: i64=br_image_meta_bytes(tree.count) 647 let image_channels: *i64=sys_mmap_shared(image_meta_bytes) as *i64 648 if (image_channels as i64)<=0 { return 0-1 } 649 let image_owned: *i64=sys_mmap_shared(image_meta_bytes) as *i64 650 if (image_owned as i64)<=0 { sys_munmap(image_channels as *u8,image_meta_bytes);return 0-1 } 651 let bimg: *i64 = sys_mmap(8*(tree.count+1)) as *i64 652 let bimw: *i64 = sys_mmap(8*(tree.count+1)) as *i64 653 let bimh: *i64 = sys_mmap(8*(tree.count+1)) as *i64 654 let bcvs: *i64 = sys_mmap(8*(tree.count+1)) as *i64 655 var k: i64 = 0 656 var ii: i64 = 0 657 while ii < tree.count { 658 let b: *LayoutBox = ((tree.boxes as i64)+ii*NX_LAYOUT_BOX_BYTES) as *LayoutBox 659 let e: *CssElement = ((boxel as i64)+ii*NX_CSS_ELEMENT_BYTES) as *CssElement 660 e.src = buf 661 bhoff[ii] = 0 - 1; bhlen[ii] = 0 - 1 662 bsoff[ii] = 0 - 1; bslen[ii] = 0 - 1; bslazy[ii] = 0 663 bimg[ii] = 0; bimw[ii] = 0; bimh[ii] = 0; bcvs[ii] = 0 664 var mapped: i64 = 0 665 if rh_is_elem(b.kind)==1 { if b.parent_idx != (0-1) { if k < n_ext { 666 let s: *CssElement = ((ext as i64)+k*NX_CSS_ELEMENT_BYTES) as *CssElement 667 e.tag_off=s.tag_off; e.tag_len=s.tag_len; e.id_off=s.id_off; e.id_len=s.id_len; e.class_off=s.class_off; e.class_len=s.class_len 668 bhoff[ii] = ehoff[k]; bhlen[ii] = ehlen[k] 669 bsoff[ii] = esoff[k]; bslen[ii] = eslen[k]; bslazy[ii] = eslazy[k] 670 // <canvas> = sourceless render surface (seq103): match the element's own tag bytes in buf 671 if s.tag_len==6 { if (buf[s.tag_off]&0xff)==99 { if (buf[s.tag_off+1]&0xff)==97 { if (buf[s.tag_off+2]&0xff)==110 { if (buf[s.tag_off+3]&0xff)==118 { if (buf[s.tag_off+4]&0xff)==97 { if (buf[s.tag_off+5]&0xff)==115 { bcvs[ii]=1 } } } } } } } 672 k=k+1; mapped=1 673 } } } 674 if mapped==0 { e.tag_off=0; e.tag_len=0; e.id_off=0; e.id_len=0; e.class_off=0; e.class_len=0 } 675 ii = ii + 1 676 } 677 678 let cur: *CssCursor = (sys_mmap(NX_CSS_CURSOR_BYTES) as nx_size) as *CssCursor 679 nx_css_cursor_init(cur, buf, total); cur.pos = body_len 680 let tok: *CssToken = (sys_mmap(NX_CSS_TOKEN_BYTES) as nx_size) as *CssToken 681 let rules: *CssRule = (sys_mmap(NX_CSS_RULE_BYTES*8192 as nx_size)) as *CssRule 682 let decls: *CssDeclaration = (sys_mmap(NX_CSS_DECLARATION_BYTES*32768 as nx_size)) as *CssDeclaration 683 let st: *CssParseState = (sys_mmap(128) as nx_size) as *CssParseState 684 nx_css_parse_state_init(st, cur, tok, rules, 8192, decls, 32768) 685 nx_css_parse(st) 686 let computed: *CssComputedDecl = (sys_mmap(NX_CSS_COMPUTED_DECL_BYTES*262144 as nx_size)) as *CssComputedDecl 687 let cb: *i64 = (sys_mmap(8)) as *i64 688 rh_cascade(buf, rules, st.rule_count, decls, st.decl_count, boxel, tree.count, tree, computed, 262144, cb) 689 let ncomp: i64 = cb[0] 690 rh_promote_display_kinds(tree, computed, ncomp, buf) // inline + display:flex/grid/block -> BLOCK (sized + painted) 691 692 let pbuf: *u8 = sys_mmap(256) 693 let ltable: *LayoutPropTable = (sys_mmap(NX_LAYOUT_PROP_TABLE_BYTES)) as *LayoutPropTable 694 nx_layout_prop_table_init(ltable, pbuf) 695 let resolve: *CssResolveCtx = (sys_mmap(NX_CSS_RESOLVE_CTX_BYTES)) as *CssResolveCtx 696 resolve.root_font_size_px = 16; resolve.parent_font_size_px = 16; resolve.parent_dimension_px = VW 697 let lctx: *LayoutCtx = (sys_mmap(NX_LAYOUT_CTX_BYTES)) as *LayoutCtx 698 // ---- FOLD-BEFORE-LAYOUT (2026-08-25): THE ROOT FIX FOR measure != paint ---- 699 // Layout used to measure the RAW DOM bytes while paint decoded entities and folded to ASCII 700 // before drawing. `caf&eacute;` was therefore ELEVEN characters to layout and FOUR to paint, 701 // and any run holding an accent, a curly quote or an em-dash FAILED layout's coverage test 702 // (font_run_has_ext over raw bytes) while PASSING paint's (the same test over folded bytes) -- 703 // so layout reserved 8x8 BITMAP widths for text that paint then drew in the VECTOR font. That 704 // split is the ceiling on every other metric fix in this file: the two stages did not agree on 705 // what the text IS, so they could never agree on how wide it is. Folding HERE, once, before a 706 // single width is measured, makes both stages read the SAME BYTES by construction. 707 // IN-PLACE IS SAFE: decode and fold both SHRINK-or-equal (&eacute; -> e; one multi-byte UTF-8 708 // codepoint -> one ASCII byte), so the result never outgrows the run it overwrites, and only 709 // that run's own extent is touched -- the href/src offsets recorded elsewhere in buf, and the 710 // cascade that already ran above, are untouched. 711 // PAINT NEEDS NO EDIT: its decode+fold is IDEMPOTENT on this output (no '&' remains and the 712 // bytes are ASCII, which nx_fold_ascii documents as identity for ASCII), so it becomes a no-op. 713 // The `fd1 <= text_len` guard is fail-safe, not decoration: if the shrink property were ever 714 // violated the run is left RAW rather than overflowing its extent. 715 // PROVEN by nx_inline_vec_measure_gate T5/T6 (GREEN 6/6): a caf&eacute; run measures 28px at 716 // text_len=4 -- the decoded length -- where it previously measured the 11 raw bytes. 717 var fli: i64 = 0 718 var flcap: i64 = 1 719 while fli < tree.count { 720 let flb0: *LayoutBox = ((tree.boxes as i64) + fli * NX_LAYOUT_BOX_BYTES) as *LayoutBox 721 if flb0.text_len > flcap { flcap = flb0.text_len } 722 fli = fli + 1 723 } 724 flcap = flcap + 2 725 let flscratch: *u8 = sys_mmap(flcap) 726 fli = 0 727 while fli < tree.count { 728 let flb: *LayoutBox = ((tree.boxes as i64) + fli * NX_LAYOUT_BOX_BYTES) as *LayoutBox 729 if flb.text_len > 0 { 730 let fd0: i64 = nx_html_decode_entities(buf, flb.text_off, flb.text_len, flscratch, flcap) 731 let fd1: i64 = br_fold_text(flscratch, 0, fd0, flscratch, flcap) // BR30: fold only what the face lacks (same policy as paint) 732 if fd1 <= flb.text_len { 733 var flk: i64 = 0 734 while flk < fd1 { buf[flb.text_off + flk] = flscratch[flk]; flk = flk + 1 } 735 flb.text_len = fd1 736 } 737 } 738 fli = fli + 1 739 } 740 nx_layout_ctx_init(lctx, tree, computed, ncomp, buf, VW, resolve) 741 // PROPORTIONAL text. text_measured=1 selects the 8x8 BITMAP metric; this line's comment used 742 // to claim it measured "with the same font metrics the paint draws with", and that was FALSE: 743 // paint runs the VECTOR font (page.vec_headings = 2 below, font_adv_em), so layout measured 744 // with one table and painted with another INSIDE THIS FUNCTION. vec-measure makes the claim 745 // true -- layout now reserves and BREAKS on the same advances paint draws. It is guarded per 746 // run by font_run_has_ext, the same coverage test the paint side uses, so any run the vector 747 // font does not cover keeps its bitmap metric and measure==paint holds in BOTH modes. 748 lctx.text_measured = 1 749 nx_layout_default_vec_measure(1) 750 let page_h: i64 = nx_layout_block_layout(lctx, ltable, 0) 751 752 page.buf = buf; page.tree = tree; page.computed = computed; page.ncomp = ncomp 753 br_cidx_build(tree, computed, ncomp) // paint-cliff fix: per-box decl buckets for br_comp_* lookups 754 page.page_h = page_h; page.bhref_off = bhoff; page.bhref_len = bhlen; page.ok = 1 755 page.bsrc_off = bsoff; page.bsrc_len = bslen; page.bimg = bimg; page.bimg_w = bimw; page.bimg_h = bimh 756 page.bimg_channels=image_channels;page.bimg_owned_bytes=image_owned 757 page.bsrc_lazy = bslazy 758 page.bcanvas = bcvs 759 page.find_ptr = 0; page.find_len = 0 760 // MATCH THE DAILY DRIVER (2026-07-27). This was hardcoded 0 = the crude 8x8 BITMAP font for 761 // EVERYTHING, while nishi.exe (nishi_gui.nx:530) paints vec_headings=2 = the proportional 762 // VECTOR font. So every headless shot taken through this path UNDERSOLD the real browser -- 763 // it already produced one wrong conclusion in this ecosystem ("body font is THE rock", 764 // later DISPROVEN: the vector body font is at Chrome parity). An instrument that does not 765 // render what the user sees is a liar; default to what ships. 766 // Callers that deliberately want the bitmap font set vec_headings themselves AFTER this 767 // call (nx_browser_bg_gate does exactly that at :20/:59/:97), so they are unaffected. 768 page.vec_headings = 2 769 return 0 770} 771 772// find the next <canvas> box at or after `from` (-1 = none). The canvas CONTRACT (seq103, the game substrate): 773// the consumer renders into its OWN RGB buffer each frame, points page.bimg[idx] (+bimg_w/bimg_h) at it, and 774// repaints (br_draw_fb / br_shot_png); the image pass blits the CURRENT buffer at the box's laid-out position. 775func br_find_canvas(page: *Page, from: i64) -> i64 { 776 var i: i64 = from 777 if i < 0 { i = 0 } 778 while i < page.tree.count { if page.bcanvas[i] == 1 { return i } i = i + 1 } 779 return 0 - 1 780} 781 782// paint a heading run with the sovereign stroke-VECTOR font (modern typeface). Word-wraps by the vector 783// metrics within avail_w px, renders each line via font_text -> aa_render -> alpha-blend onto the RGBA fb 784// at (x0,y0), em size = empx px. Vector advances are TIGHTER than the bitmap the layout reserved with, so 785// the run always fits the box (never overflows). Allocation-bearing -> only called on opt-in once-render 786// paths. Returns the drawn width of the FIRST line (for the link underline; headings rarely link). 787// REUSED vector-paint scratch (was sys_mmap PER LINE -> per-box -> gigabytes committed under the PE's 788// VirtualAlloc, hanging the native GUI on a full page). Allocated ONCE, reused across every box + line. 789static bpv_xs: i64 790static bpv_ys: i64 791static bpv_cst: i64 792static bpv_cln: i64 793static bpv_cov: i64 794static bpv_cs2: i64 795static bpv_sn2: i64 796static bpv_np: i64 797static bpv_nc: i64 798// ---- GLYPH-ATLAS FAST PATH (GUI-only, gated by bpv_fast_on) ------------------------------------------ 799// Rasterize each (char,size) glyph ONCE into a cached coverage cell, then just alpha-blend the cell per 800// draw. Turns O(glyphs drawn) rasterization into O(unique glyph x size). Pixel-snapped positioning (no 801// sub-pixel phase) -> crisp, ~visually-identical, MUCH faster. The EXACT per-line path (used by the shot / 802// NishiOS / OCR / polish gates) is untouched: they never set bpv_fast_on, so the dispatch below is a no-op 803// for them -> zero gate regression. Only nishi_gui (br_set_fast_vec(1)) opts in. 804static bpv_fast_on: i64 805static bpv_line_adv: i64 // wrapped-line advance override (px at paint scale; 0 = legacy (empx*12)/10+2). 806 // Set by the TEXT paint pass from the CSS line-height so paint fills exactly 807 // the lines layout reserved (measure==paint); single-threaded paint -> safe. 808static atk: i64 // hash keys (ch*100000+empx), -1 = empty slot 809static atcov: i64 // per-slot coverage buffer ptr 810static atgw: i64 // per-slot cell width (px) 811static atgh: i64 // per-slot cell height (px) 812static atmg: i64 // per-slot left margin (px) baked into the cell 813static atcap: i64 814func br_set_fast_vec(v: i64) -> i64 { bpv_fast_on = v; return 0 } 815// ---- REAL FACE (2026-09-02, BR4 first half: /compare/browser "Real font files + shaping") ------------- 816// A loaded TrueType face replaces the parametric stroke glyphs for every byte it covers, and hands its 817// hmtx advances + kern pairs to the ONE measure ruler in nx_font (font_set_face), so layout, wrap and 818// paint agree by construction. Cells cache in the same atlas keyed (ch, empx); a face is set ONCE before 819// the first layout, so a key can never mean two glyphs. With NO face set every path below is byte-unchanged. 820// The face lands advances AND outlines AND kern TOGETHER -- the 07-29 A/B proved advances alone under 821// the stroke glyphs wreck legibility (OCR 881 -> 591); this is the pair that A/B asked for. 822static br_face: i64 // tf handle of the SELECTED face (0 = no face) 823static br_faces: i64 // *i64[FS_FACE_MAX] tf handles by slot (BR27); 0 = slot empty 824static br_face_cur: i64 // the selected slot (effective) 825static br_face_adv: i64 // *i64[BR_FACE_BYTES] 1000-em advances handed to nx_font 826static br_face_kern: i64 // *i64[BR_FACE_BYTES^2] 1000-em pair kern handed to nx_font 827const BR_FACE_BYTES: i64 = 256 828// register a parsed face into slot idx (font_face_pick order: +1 bold, +2 italic, +4 serif); slot 0 is the 829// default and is selected on registration. Each slot owns its own advance and kern tables. 830func br_face_register(idx: i64, fh: *i64) -> i64 { 831 if (fh as i64) == 0 { return 0 } 832 if idx < 0 { return 0 } 833 if idx >= FS_FACE_MAX { return 0 } 834 if br_faces == 0 { br_faces = sys_mmap(8*FS_FACE_MAX) as i64 } 835 let adv: *i64 = sys_mmap(8*FS_FACE_CPS) as *i64 // BR30: indexed by CODEPOINT (U+0000..FS_FACE_CPS-1) 836 let kern: *i64 = sys_mmap(8*BR_FACE_BYTES*BR_FACE_BYTES) as *i64 837 var i: i64 = 0 838 while i < FS_FACE_CPS { adv[i] = 0; i = i + 1 } 839 i = 0 840 while i < BR_FACE_BYTES*BR_FACE_BYTES { kern[i] = 0; i = i + 1 } 841 var a: i64 = TF_ASCII_LO 842 while a <= TF_ASCII_HI { 843 adv[a] = tf_adv_em1000(fh, a) 844 if adv[a] <= 0 { adv[a] = 1 } // a covered byte with a zero advance still counts as COVERED (never fall back mid-run) 845 var c: i64 = TF_ASCII_LO 846 while c <= TF_ASCII_HI { kern[a*BR_FACE_BYTES + c] = tf_kern_em1000(fh, a, c); c = c + 1 } 847 a = a + 1 848 } 849 // BR30: every codepoint the face's cmap maps (up to FS_FACE_CPS) is COVERED with its real advance; an 850 // unmapped one stays 0 so the run folds THAT character to ASCII -- the same rule as an ASCII gap. 851 var u: i64 = TF_ASCII_HI + 1 852 while u < FS_FACE_CPS { 853 if tf_gid(fh, u) > 0 { adv[u] = tf_adv_em1000(fh, u); if adv[u] <= 0 { adv[u] = 1 } } 854 u = u + 1 855 } 856 // BR39: slot 0 = the notdef (gid 0) advance, so a codepoint beyond the table measures and paints as the 857 // face box. A face whose notdef has no advance leaves the slot 0 and such codepoints fold as before. 858 adv[0] = (tf_adv_units_gid(fh, 0) * 1000) / tf_upem(fh) 859 if adv[0] < 0 { adv[0] = 0 } 860 let hs: *i64 = br_faces as *i64 861 hs[idx] = fh as i64 862 font_face_register(idx, adv, kern) 863 if idx == 0 { br_face = fh as i64; br_face_cur = 0 } 864 return 1 865} 866// select the face for the run about to be painted (the SAME rule layout used); returns the effective slot 867func br_face_select(idx: i64) -> i64 { 868 if br_faces == 0 { return 0 } 869 let e: i64 = font_face_select(idx) 870 let hs: *i64 = br_faces as *i64 871 br_face = hs[e] 872 br_face_cur = e 873 return e 874} 875// compatibility: ONE face = slot 0; 0 clears everything 876func br_set_face(fh: *i64) -> i64 { 877 if (fh as i64) == 0 { br_face = 0; br_face_cur = 0; if br_faces != 0 { let hs: *i64 = br_faces as *i64; var i: i64 = 0; while i < FS_FACE_MAX { hs[i] = 0; i = i + 1 } } font_set_face(0 as *i64, 0 as *i64); return 0 } 878 let r: i64 = br_face_register(0, fh) 879 br_face_select(0) 880 return r 881} 882// BR27 contract symbol: resolve the face for the text under box_idx by the ONE rule layout used 883// (nx_layout_face_for_box: font-weight / font-style / font-family, inherited) and SELECT it for paint. 884// Returns the effective slot (0 when no family is registered). 885func br_face_resolve(src: *u8, computed: *CssComputedDecl, ncomp: i64, tree: *LayoutTree, box_idx: i64) -> i64 { 886 if br_faces == 0 { return 0 } 887 return br_face_select(nx_layout_face_for_box(src, computed, ncomp, tree, box_idx)) 888} 889func br_face_load_idx(idx: i64, path: *u8) -> i64 { 890 let fh: *i64 = tf_load(path) 891 if (fh as i64) == 0 { return 0 } 892 return br_face_register(idx, fh) 893} 894// load a .ttf from a path and make it THE face; 0 when absent/unparseable (the stroke font stays) 895func br_face_load(path: *u8) -> i64 { 896 let fh: *i64 = tf_load(path) 897 if (fh as i64) == 0 { return 0 } 898 return br_set_face(fh) 899} 900func br_face_on() -> i64 { if br_face != 0 { return 1 } return 0 } 901// the CONTENT height of a text line at em size px -- the face (ascender - descender) when one is on, 902// else the stroke font 1.2em+2 band. CSS centres this inside the line box (half-leading); the caller 903// uses it for both stages so measure and paint place the baseline identically. 904func br_face_line_h(px: i64) -> i64 { if br_face == 0 { return (px * 12) / 10 + 2 } return tf_content_px(br_face as *i64, px) } 905static bpv_fold_cp: i64 906// BR30 (2026-09-02): the fold policy BOTH stages call. With NO face every non-ASCII codepoint folds to 907// ASCII (nx_fold_ascii, the legacy behaviour). With a face, a codepoint the face COVERS keeps its UTF-8 908// bytes (measured and painted from the face's own outline) and only an uncovered one folds. Layout's 909// pre-pass and paint call THIS, so the bytes layout measured are the bytes paint draws, and paint's 910// second call is idempotent on the first's output. In-place safe: the output cursor never runs ahead 911// of the input cursor (copy or shrink per codepoint). Coverage is read from the SELECTED slot; the 912// faces of one family share a cmap, so bold/italic/serif answer alike. 913func br_fold_text(src: *u8, off: i64, len: i64, out: *u8, out_cap: i64) -> i64 { 914 if font_face_on() == 0 { return nx_fold_ascii(src, off, len, out, out_cap) } 915 if bpv_fold_cp == 0 { bpv_fold_cp = sys_mmap(8) as i64 } 916 let cpo: *i64 = bpv_fold_cp as *i64 917 let end: i64 = off + len 918 var i: i64 = off 919 var o: i64 = 0 920 while i < end { 921 let i0: i64 = i 922 i = font_next_cp(src, i, end, cpo) 923 let cp: i64 = cpo[0] 924 if cp < 128 { if o < out_cap { out[o] = src[i0]; o = o + 1 } } 925 else { if font_face_has(cp) == 1 { 926 var k: i64 = i0 927 while k < i { if o < out_cap { out[o] = src[k]; o = o + 1 } k = k + 1 } 928 } else { 929 o = o + nx_fold_ascii(src, i0, i - i0, ((out as i64) + o) as *u8, out_cap - o) 930 } } 931 } 932 return o 933} 934// BR30 contract symbol (browser board, "Unicode text pipeline beyond ASCII"): the ONE place paint turns 935// bytes into the codepoint it draws. It delegates to the measure ruler's decoder (font_next_cp) so a run 936// is decoded identically for measure and paint; a malformed sequence yields its lead byte, which no face 937// covers, so the run folds rather than painting garbage. Returns the index after the codepoint. 938func br_text_cp(text: *u8, i: i64, len: i64, cp: *i64) -> i64 { return font_next_cp(text, i, len, cp) } 939func bpv_atlas_init() -> i64 { 940 if bpv_xs == 0 { 941 bpv_xs = sys_mmap(8*200000) as i64; bpv_ys = sys_mmap(8*200000) as i64 942 bpv_cst = sys_mmap(8*20000) as i64; bpv_cln = sys_mmap(8*20000) as i64 943 bpv_cov = sys_mmap(4194304) as i64 944 bpv_cs2 = sys_mmap(8*16) as i64; bpv_sn2 = sys_mmap(8*16) as i64 945 bpv_np = sys_mmap(16) as i64; bpv_nc = sys_mmap(16) as i64 946 } 947 if atk == 0 { 948 atcap = 8192 949 atk = sys_mmap(8*atcap) as i64; atcov = sys_mmap(8*atcap) as i64 950 atgw = sys_mmap(8*atcap) as i64; atgh = sys_mmap(8*atcap) as i64; atmg = sys_mmap(8*atcap) as i64 951 let ka: *i64 = atk as *i64; var z: i64 = 0 952 while z < atcap { ka[z] = 0 - 1; z = z + 1 } 953 } 954 return 0 955} 956// get-or-build the cached coverage cell for (ch,empx). out[0]=covptr out[1]=gw out[2]=gh out[3]=leftmargin_px 957func bpv_cell(ch: i64, empx: i64, cs: *i64, sn: *i64, out: *i64) -> i64 { 958 let ka: *i64 = atk as *i64 959 var kc: i64 = ch 960 if ch >= FS_FACE_CPS { kc = 0 } // BR39: every beyond-table codepoint shares the notdef cell, keyed on codepoint 0 961 if ch >= FS_FACE_LATIN_END { if ch < FS_FACE_CPS { if font_face_has(ch) == 1 { let fta: *i64 = fs_face_adv_ptr(); if fta[ch] == 0 { kc = 0 } } } } // in-table unmapped non-Latin: the same notdef cell 962 let key: i64 = (br_face_cur * FS_FACE_CPS + kc) * 100000 + empx // slot 0 / no face keeps the legacy key; BR30: ch is a CODEPOINT 963 var h: i64 = key % atcap 964 if h < 0 { h = 0 - h } 965 var probes: i64 = 0 966 while probes < atcap { 967 let cova: *i64 = atcov as *i64; let gwa: *i64 = atgw as *i64; let gha: *i64 = atgh as *i64; let mga: *i64 = atmg as *i64 968 if ka[h] == key { out[0]=cova[h]; out[1]=gwa[h]; out[2]=gha[h]; out[3]=mga[h]; return 0 } 969 if ka[h] == (0 - 1) { 970 if br_face != 0 { if font_face_has(ch) == 1 { 971 // REAL FACE cell: the TTF engine coverage record (row 0 = ascender line, left margin = 972 // negative lsb) is adopted into the atlas as-is -- no re-raster, no second copy. 973 var frec: *i64 = 0 as *i64 974 if ch >= FS_FACE_CPS { frec = tf_glyph_notdef(br_face as *i64, empx) } // BR39: the face box for a codepoint the table cannot hold 975 else { if ch <= TF_ASCII_HI { frec = tf_glyph(br_face as *i64, ch, empx) } else { frec = tf_glyph_cp(br_face as *i64, ch, empx) } } // BR30: non-ASCII cells raster once and cache HERE (the engine caches ASCII only) 976 if frec == (0 as *i64) { if ch >= FS_FACE_LATIN_END { frec = tf_glyph_notdef(br_face as *i64, empx) } } // BR39: an in-table non-Latin codepoint the face lacks draws the box too (measure reserved the notdef advance) 977 if frec != (0 as *i64) { 978 ka[h]=key; cova[h]=frec[TF_R_COV]; gwa[h]=frec[TF_R_CW]; gha[h]=frec[TF_R_CHH]; mga[h]=frec[TF_R_LM] 979 out[0]=frec[TF_R_COV]; out[1]=frec[TF_R_CW]; out[2]=frec[TF_R_CHH]; out[3]=frec[TF_R_LM] 980 return 0 981 } 982 } } 983 let ss: i64 = 3 984 let S: i64 = empx * ss 985 let mgn: i64 = 4 986 let adv: i64 = font_vec_adv_px2(ch, empx) 987 var gw: i64 = adv + 2*mgn 988 if gw < empx + 2*mgn { gw = empx + 2*mgn } 989 let gh: i64 = empx + (empx*35)/100 + 2 990 let xs: *i64 = bpv_xs as *i64; let ys: *i64 = bpv_ys as *i64 991 let cstart: *i64 = bpv_cst as *i64; let clen: *i64 = bpv_cln as *i64 992 let np: *i64 = bpv_np as *i64; np[0]=0 993 let nc: *i64 = bpv_nc as *i64; nc[0]=0 994 font_emit(ch, xs, ys, cstart, clen, np, nc, cs, sn, mgn*ss, 2*ss, S) 995 let cov: *u8 = sys_mmap(gw*gh + 16) 996 var zi: i64 = 0 997 while zi < gw*gh { cov[zi]=0 as u8; zi=zi+1 } 998 aa_render(xs, ys, cstart, clen, nc[0], cov, gw, gh, ss) 999 ka[h]=key; cova[h]=cov as i64; gwa[h]=gw; gha[h]=gh; mga[h]=mgn 1000 out[0]=cov as i64; out[1]=gw; out[2]=gh; out[3]=mgn 1001 return 0 1002 } 1003 h = h + 1; if h >= atcap { h = 0 } 1004 probes = probes + 1 1005 } 1006 out[0]=0; return 1 1007} 1008// alpha-blend a coverage cell onto the RGBA framebuffer (matches br_paint_vec's blend exactly) 1009func bpv_blit_rgba(fb: *Framebuffer, ox: i64, oy: i64, cov: *u8, gw: i64, gh: i64, cr: i64, cg: i64, cb: i64) -> i64 { 1010 let fw: i64 = fb.width; let fh: i64 = fb.height; let pxb: *u8 = fb.pixels 1011 var y: i64 = 0 1012 while y < gh { 1013 let fyr: i64 = oy + y 1014 if fyr >= 0 { if fyr < fh { 1015 var x: i64 = 0 1016 while x < gw { 1017 let a: i64 = cov[y*gw + x] & 0xff 1018 if a > 0 { 1019 let fxr: i64 = ox + x 1020 if fxr >= 0 { if fxr < fw { 1021 let o: i64 = (fyr*fw + fxr) * 4 1022 let ia: i64 = 255 - a 1023 pxb[o] = (((pxb[o] as i64)*ia + cr*a)/255) as u8 1024 pxb[o+1] = (((pxb[o+1] as i64)*ia + cg*a)/255) as u8 1025 pxb[o+2] = (((pxb[o+2] as i64)*ia + cb*a)/255) as u8 1026 pxb[o+3] = 255 as u8 1027 } } 1028 } 1029 x = x + 1 1030 } 1031 } } 1032 y = y + 1 1033 } 1034 return 0 1035} 1036func br_paint_vec_atlas(fb: *Framebuffer, x0: i64, y0: i64, text: *u8, text_len: i64, empx: i64, color: *CssColor, avail_w: i64) -> i64 { 1037 bpv_atlas_init() 1038 let cs: *i64 = bpv_cs2 as *i64 1039 let sn: *i64 = bpv_sn2 as *i64 1040 font_trig_init(cs, sn) 1041 let cr: i64 = color.r & 255 1042 let cg: i64 = color.g & 255 1043 let cb: i64 = color.b & 255 1044 var lineh: i64 = br_face_line_h(empx) 1045 if bpv_line_adv >= 8 { lineh = bpv_line_adv } // CSS line-height (already paint-scaled by the caller) 1046 var aw: i64 = avail_w 1047 if aw < empx { aw = empx } 1048 let cell: *i64 = sys_mmap(64) as *i64 1049 let bpv_cpo: *i64 = ((cell as i64) + 56) as *i64 // slot 7 of the cell scratch: the decoded codepoint (no per-glyph allocation) 1050 var pos: i64 = 0 1051 var li: i64 = 0 1052 var w1: i64 = 0 1053 while pos < text_len { 1054 var e: i64 = font_vec_next_break_px(text, text_len, aw, pos, empx) 1055 if e <= pos { e = text_len } 1056 let basey: i64 = y0 + li * lineh 1057 var penpx: i64 = x0 1058 var prev: i64 = 0 - 1 1059 var k: i64 = pos 1060 while k < e { 1061 var ch: i64 = text[k] & 0xff 1062 if br_face != 0 { k = br_text_cp(text, k, e, bpv_cpo) - 1; ch = bpv_cpo[0] } // BR30: a face paints CODEPOINTS; k lands on the run's last byte and the k+1 below steps past it 1063 penpx = penpx + (font_pair_kern_em(prev, ch) * empx) / 1000 // pair kern: the SAME term the measure added, in the same order 1064 bpv_cell(ch, empx, cs, sn, cell) 1065 let covp: i64 = cell[0] 1066 var shp: i64 = (((((font_sp_shift_disp(ch) * font_ws()) / 100) * font_wdth()) / 100) * empx) / 1000 1067 if font_face_has(ch) == 1 { shp = 0 } // a real face carries its side bearings inside the outline 1068 if covp != 0 { bpv_blit_rgba(fb, penpx + shp - cell[3], basey, covp as *u8, cell[1], cell[2], cr, cg, cb) } 1069 penpx = penpx + font_vec_adv_px2(ch, empx) 1070 prev = ch 1071 k = k + 1 1072 } 1073 if li == 0 { w1 = penpx - x0 } 1074 pos = e 1075 li = li + 1 1076 // DERIVED loop bound (see the note at the paint wrap): termination insurance, not a 1077 // line budget. A line consumes >= 1 char, so li > text_len is structurally impossible. 1078 if li > text_len { pos = text_len } 1079 } 1080 return w1 1081} 1082func br_paint_vec(fb: *Framebuffer, x0: i64, y0: i64, text: *u8, text_len: i64, empx: i64, color: *CssColor, avail_w: i64) -> i64 { 1083 if bpv_fast_on == 1 { return br_paint_vec_atlas(fb, x0, y0, text, text_len, empx, color, avail_w) } 1084 if br_face != 0 { return br_paint_vec_atlas(fb, x0, y0, text, text_len, empx, color, avail_w) } // a real face always paints through the (deterministic) atlas 1085 let ss: i64 = 3 1086 let S: i64 = empx * ss 1087 if bpv_xs == 0 { 1088 bpv_xs = sys_mmap(8*200000) as i64 1089 bpv_ys = sys_mmap(8*200000) as i64 1090 bpv_cst = sys_mmap(8*20000) as i64 1091 bpv_cln = sys_mmap(8*20000) as i64 1092 bpv_cov = sys_mmap(4194304) as i64 1093 bpv_cs2 = sys_mmap(8*16) as i64 1094 bpv_sn2 = sys_mmap(8*16) as i64 1095 bpv_np = sys_mmap(16) as i64 1096 bpv_nc = sys_mmap(16) as i64 1097 } 1098 let cs: *i64 = bpv_cs2 as *i64 1099 let sn: *i64 = bpv_sn2 as *i64 1100 font_trig_init(cs, sn) 1101 let fw: i64 = fb.width 1102 let fh: i64 = fb.height 1103 let pxb: *u8 = fb.pixels 1104 let cr: i64 = color.r & 255 1105 let cg: i64 = color.g & 255 1106 let cb: i64 = color.b & 255 1107 let lineh: i64 = (empx * 12) / 10 + 2 // ~1.2em line height 1108 var aw: i64 = avail_w 1109 if aw < empx { aw = empx } 1110 var pos: i64 = 0 1111 var li: i64 = 0 1112 var w1: i64 = 0 1113 while pos < text_len { 1114 var e: i64 = font_vec_next_break_px(text, text_len, aw, pos, empx) 1115 if e <= pos { e = text_len } 1116 // render this line [pos,e) into a coverage buffer sized to its width x (em band) 1117 let lw: i64 = font_vec_text_w_px(text, pos, e, empx) + empx 1118 let GH: i64 = empx + (empx*35)/100 // ascender..descender band 1119 var GW: i64 = lw 1120 if GW < empx { GW = empx } 1121 if GW > fw { GW = fw } 1122 let xs: *i64 = bpv_xs as *i64 // reused (was sys_mmap per line) 1123 let ys: *i64 = bpv_ys as *i64 1124 let cstart: *i64 = bpv_cst as *i64 1125 let clen: *i64 = bpv_cln as *i64 1126 let np: *i64 = bpv_np as *i64; np[0]=0 1127 let nc: *i64 = bpv_nc as *i64; nc[0]=0 1128 font_text(((text as i64)+pos) as *u8, e-pos, xs, ys, cstart, clen, np, nc, cs, sn, 2*ss, 2*ss, S) 1129 if GW*GH > 4194288 { GW = 4194288 / GH } // clamp to the reused cov buffer (4 MB) -- defensive 1130 let cov: *u8 = bpv_cov as *u8 1131 var zi: i64 = 0 1132 while zi < GW*GH { cov[zi]=0 as u8; zi=zi+1 } 1133 aa_render(xs, ys, cstart, clen, nc[0], cov, GW, GH, ss) 1134 // alpha-blend coverage onto the RGBA fb 1135 let basey: i64 = y0 + li * lineh 1136 var gy: i64 = 0 1137 while gy < GH { 1138 let fyr: i64 = basey + gy 1139 if fyr >= 0 { if fyr < fh { 1140 var gx: i64 = 0 1141 while gx < GW { 1142 let a: i64 = cov[gy*GW+gx] & 0xff 1143 if a > 0 { 1144 let fxr: i64 = x0 + gx 1145 if fxr >= 0 { if fxr < fw { 1146 let o: i64 = (fyr*fw+fxr)*4 1147 let ia: i64 = 255 - a 1148 pxb[o] = (((pxb[o] as i64)*ia + cr*a)/255) as u8 1149 pxb[o+1] = (((pxb[o+1] as i64)*ia + cg*a)/255) as u8 1150 pxb[o+2] = (((pxb[o+2] as i64)*ia + cb*a)/255) as u8 1151 pxb[o+3] = 255 as u8 1152 } } 1153 } 1154 gx = gx + 1 1155 } 1156 } } 1157 gy = gy + 1 1158 } 1159 if li == 0 { w1 = lw } 1160 pos = e 1161 li = li + 1 1162 // DERIVED loop bound (see the note at the paint wrap): termination insurance, not a 1163 // line budget. A line consumes >= 1 char, so li > text_len is structurally impossible. 1164 if li > text_len { pos = text_len } 1165 } 1166 return w1 1167} 1168// vector metrics in absolute px (em size empx), for the paint's own wrap 1169func font_vec_adv_px2(ch: i64, empx: i64) -> i64 { return (font_adv_em(ch) * empx) / 1000 } 1170// DELEGATED 2026-09-02: these were a second copy of the nx_font width/break arithmetic (font_adv_em*px/1000); 1171// pair kerning landed in the ONE ruler and a copy here would have drifted the instant it did. 1172func font_vec_text_w_px(text: *u8, start: i64, end: i64, empx: i64) -> i64 { return font_vec_text_w_fs(text, start, end, empx) } 1173func font_vec_next_break_px(text: *u8, len: i64, avail_px: i64, start: i64, empx: i64) -> i64 { return font_vec_next_break_fs(text, len, avail_px, start, empx) } 1174 1175// FIND-IN-PAGE (Ctrl+F, a universally-expected browser feature): case-insensitive search of the laid-out 1176// text for `term`. Sets page.find_ptr/find_len so br_draw_fb_at highlights every matching text box; returns 1177// the match count and, via yb, the page-y of the FIRST match (for scroll-to). A term absent from the page 1178// returns 0 (highlights nothing). Matching is per-text-box substring (the layout already split text into 1179// boxes at inline boundaries) -- the same units the render + hit-test use. 1180func br_ci(a: i64) -> i64 { if a >= 65 { if a <= 90 { return a + 32 } } return a } 1181func br_box_has_term(buf: *u8, off: i64, len: i64, term: *u8, tlen: i64) -> i64 { 1182 if tlen <= 0 { return 0 } 1183 var i: i64 = 0 1184 while i + tlen <= len { 1185 var j: i64 = 0 1186 var ok: i64 = 1 1187 while j < tlen { if br_ci(buf[off+i+j]&0xff) != br_ci(term[j]&0xff) { ok=0; j=tlen } else { j=j+1 } } 1188 if ok == 1 { return 1 } 1189 i = i + 1 1190 } 1191 return 0 1192} 1193func br_find(page: *Page, term: *u8, tlen: i64, yb: *i64) -> i64 { 1194 page.find_ptr = term as i64 1195 page.find_len = tlen 1196 yb[0] = 0 - 1 1197 if tlen <= 0 { page.find_ptr = 0; return 0 } 1198 let tree: *LayoutTree = page.tree 1199 var matches: i64 = 0 1200 var i: i64 = 0 1201 while i < tree.count { 1202 let b: *LayoutBox = ((tree.boxes as i64) + i * NX_LAYOUT_BOX_BYTES) as *LayoutBox 1203 if b.kind == NX_LAYOUT_BOX_TEXT { if b.text_len > 0 { 1204 if br_box_has_term(page.buf, b.text_off, b.text_len, term, tlen) == 1 { 1205 matches = matches + 1 1206 if yb[0] < 0 { yb[0] = b.y } 1207 } 1208 } } 1209 i = i + 1 1210 } 1211 if matches == 0 { page.find_ptr = 0 } // nothing to highlight 1212 return matches 1213} 1214 1215func br_color_from_int(c: i64) -> *CssColor { 1216 let col: *CssColor = sys_mmap(NX_CSS_COLOR_BYTES) as *CssColor 1217 col.r = (c >> 16) & 0xff 1218 col.g = (c >> 8) & 0xff 1219 col.b = c & 0xff 1220 col.a = 255 1221 return col 1222} 1223 1224func br_bit(ft8: *u8, go: i64, col: i64, row: i64) -> i64 { 1225 if col < 0 { return 0 } 1226 if col > 7 { return 0 } 1227 if row < 0 { return 0 } 1228 if row > 7 { return 0 } 1229 if ((ft8[go + row] >> col) & 1) == 1 { return 255 } 1230 return 0 1231} 1232// bilinear coverage of glyph `go` at bit-space (fx256,fy256) in 1/256 fixed point (bit centers on integers) 1233func br_glyph_cov(ft8: *u8, go: i64, fx256: i64, fy256: i64) -> i64 { 1234 let x0: i64 = fx256 >> 8 1235 let y0: i64 = fy256 >> 8 1236 let fxr: i64 = fx256 & 255 1237 let fyr: i64 = fy256 & 255 1238 let v00: i64 = br_bit(ft8, go, x0, y0) 1239 let v10: i64 = br_bit(ft8, go, x0+1, y0) 1240 let v01: i64 = br_bit(ft8, go, x0, y0+1) 1241 let v11: i64 = br_bit(ft8, go, x0+1, y0+1) 1242 let top: i64 = v00*(256-fxr) + v10*fxr 1243 let bot: i64 = v01*(256-fxr) + v11*fxr 1244 return (top*(256-fyr) + bot*fyr) >> 16 1245} 1246// SHARPEN the raw bilinear coverage: solidify stroke interiors (>= BR_HI -> 255, keeps thin 1-bit stems 1247// crisp = legibility) while KEEPING the outer edge ramp as intermediate gray (= anti-aliasing). This is 1248// the knob that makes both the OCR judge (legibility) AND the polish census (AA) happy at once. 1249const BR_LO: i64 = 40 1250const BR_HI: i64 = 165 1251func br_sharpen(c: i64) -> i64 { 1252 if c <= BR_LO { return 0 } 1253 if c >= BR_HI { return 255 } 1254 return ((c - BR_LO) * 255) / (BR_HI - BR_LO) 1255} 1256// coverage for one output pixel: INTERIOR pixels (nearest bit + its 4 orthogonal neighbors all set) stay 1257// SOLID 255 -- pixel-exact, no resample shift, so legibility is preserved; only EDGE pixels take the 1258// sharpened bilinear ramp = anti-aliasing. This is what holds 984 OCR AND closes the polish gap. 1259func br_cov_hybrid(ft8: *u8, go: i64, fx256: i64, fy256: i64) -> i64 { 1260 let cc: i64 = (fx256 + 128) >> 8 // nearest bit col 1261 let rr: i64 = (fy256 + 128) >> 8 // nearest bit row 1262 if br_bit(ft8, go, cc, rr) == 255 { 1263 if br_bit(ft8, go, cc-1, rr) == 255 { if br_bit(ft8, go, cc+1, rr) == 255 { 1264 if br_bit(ft8, go, cc, rr-1) == 255 { if br_bit(ft8, go, cc, rr+1) == 255 { 1265 return 255 1266 } } 1267 } } 1268 } 1269 return br_sharpen(br_glyph_cov(ft8, go, fx256, fy256)) 1270} 1271 1272// PROPORTIONAL + ANTI-ALIASED bitmap text: bilinear coverage of the glyph field (smooth edges) + a 1273// sharpening curve (solid interiors), alpha-blended onto the RGBA framebuffer. Closes the polish gap vs 1274// Chrome AND holds the 984 legibility. Pen advance = font8x8_adv (measured-layout SSOT), layout truthful. 1275func br_paint_text2x(fb: *Framebuffer, x: i64, y: i64, text: *u8, text_len: i64, color: *CssColor, scale: i64) -> i64 { 1276 let ft8: *u8 = font8x8_table() 1277 let blk: i64 = scale 1278 let GH: i64 = 8 * blk // glyph render height in px 1279 let fw: i64 = fb.width 1280 let fh: i64 = fb.height 1281 let pxb: *u8 = fb.pixels 1282 let cr: i64 = color.r & 255 1283 let cg: i64 = color.g & 255 1284 let cb: i64 = color.b & 255 1285 var pen: i64 = x 1286 var i: i64 = 0 1287 while i < text_len { 1288 let ch: i64 = text[i] & 0xff 1289 if ch > 0x20 { if ch <= 0x7E { 1290 let go: i64 = (ch - 0x20) * 8 1291 let iw: i64 = font8x8_ink_w(ft8, ch) // ink columns (proportional) 1292 var GW: i64 = iw * blk 1293 if GW < blk { GW = blk } 1294 let sxn: i64 = (iw * 256) / GW // bit-cols per output px * 256 1295 let syn: i64 = (8 * 256) / GH // bit-rows per output px * 256 1296 var oy: i64 = 0 1297 while oy < GH { 1298 let fy256: i64 = ((oy * 2 + 1) * syn) / 2 - 128 // (oy+0.5)*syn - 0.5 bit 1299 let fyr: i64 = y + oy 1300 if fyr >= 0 { if fyr < fh { 1301 var ox: i64 = 0 1302 while ox < GW { 1303 let fx256: i64 = ((ox * 2 + 1) * sxn) / 2 - 128 1304 let a: i64 = br_cov_hybrid(ft8, go, fx256, fy256) 1305 if a > 0 { 1306 let fxr: i64 = pen + ox 1307 if fxr >= 0 { if fxr < fw { 1308 let o: i64 = (fyr * fw + fxr) * 4 1309 let ia: i64 = 255 - a 1310 pxb[o] = (((pxb[o] as i64) * ia + cr * a) / 255) as u8 1311 pxb[o+1] = (((pxb[o+1] as i64) * ia + cg * a) / 255) as u8 1312 pxb[o+2] = (((pxb[o+2] as i64) * ia + cb * a) / 255) as u8 1313 pxb[o+3] = 255 as u8 1314 } } 1315 } 1316 ox = ox + 1 1317 } 1318 } } 1319 oy = oy + 1 1320 } 1321 } } 1322 pen = pen + font8x8_adv(ft8, ch) * blk 1323 i = i + 1 1324 } 1325 return pen - x 1326} 1327 1328// Blit a decoded RGB image (src = w*h*3, row-major) into the RGBA framebuffer at (dx,dy), nearest-neighbor 1329// scaled to boxW x boxH, clipped to fb bounds. The missing image-paint primitive (nx_paint_solid_rect does 1330// solid fills; this does image copy). A=255 (opaque). RGB source because nx_img_to_rgb/JPEG emit w*h*3. 1331func br_blit_pixels(fb: *Framebuffer, dx: i64, dy: i64, boxW: i64, boxH: i64, src: *u8, sw: i64, sh: i64,channels: i64) -> i64 { 1332 if channels!=3 && channels!=4 { return 0-1 } 1333 if boxW <= 0 { return 0 } 1334 if boxH <= 0 { return 0 } 1335 if sw <= 0 { return 0 } 1336 if sh <= 0 { return 0 } 1337 let fw: i64 = fb.width 1338 let fh: i64 = fb.height 1339 let px: *u8 = fb.pixels 1340 var ty: i64 = 0 1341 while ty < boxH { 1342 let py: i64 = dy + ty 1343 if py >= 0 { if py < fh { 1344 let sy: i64 = (ty * sh) / boxH 1345 var tx: i64 = 0 1346 while tx < boxW { 1347 let pxx: i64 = dx + tx 1348 if pxx >= 0 { if pxx < fw { 1349 let sx: i64 = (tx * sw) / boxW 1350 let so: i64 = (sy * sw + sx) * channels 1351 let dof: i64 = (py * fw + pxx) * 4 1352 if channels==3 { 1353 px[dof + 0] = src[so + 0] 1354 px[dof + 1] = src[so + 1] 1355 px[dof + 2] = src[so + 2] 1356 px[dof + 3] = 255 as u8 1357 } else { 1358 let sa: i64=src[so+3] as i64;let da: i64=px[dof+3] as i64 1359 let alpha_num: i64=sa*255+da*(255-sa) 1360 var c: i64=0;while c<3 {var value: i64=0;if alpha_num>0 {value=((src[so+c] as i64)*sa*255+(px[dof+c] as i64)*da*(255-sa)+alpha_num/2)/alpha_num}px[dof+c]=value as u8;c=c+1} 1361 px[dof+3]=((alpha_num+127)/255) as u8 1362 } 1363 } } 1364 tx = tx + 1 1365 } 1366 } } 1367 ty = ty + 1 1368 } 1369 return 0 1370} 1371 1372func br_blit_rgb(fb: *Framebuffer,dx: i64,dy: i64,boxW: i64,boxH: i64,src: *u8,sw: i64,sh: i64) -> i64 {return br_blit_pixels(fb,dx,dy,boxW,boxH,src,sw,sh,3)} 1373func br_blit_rgba(fb: *Framebuffer,dx: i64,dy: i64,boxW: i64,boxH: i64,src: *u8,sw: i64,sh: i64) -> i64 {return br_blit_pixels(fb,dx,dy,boxW,boxH,src,sw,sh,4)} 1374 1375// OFF-SCREEN render: rasterize the laid-out page into an in-memory RGBA framebuffer (no X server). Uses the 1376// 5x7 bitmap-font paint primitives (for verifying colors/layout/chrome). The same fn the GUI calls to paint. 1377// draw with a vertical SCROLL offset (page-space px): content shifts up by scroll_y, chrome stays fixed. 1378// br_draw_fb delegates with scroll 0, so existing callers (incl the parallel-owned GUI) are unaffected. 1379func br_draw_fb_at(fb: *Framebuffer, page: *Page, win_w: i64, cur_url: *u8, ulen: i64, scale: i64, scroll_y: i64) -> i64 { 1380 let tree: *LayoutTree = page.tree 1381 let src: *u8 = page.buf 1382 let computed: *CssComputedDecl = page.computed 1383 let ncomp: i64 = page.ncomp 1384 // DERIVED decode buffer (2026-08-25). Sized from the LONGEST text run actually in this tree, so 1385 // no run can be truncated. The bound is EXACT, not a guess: entity decoding and ASCII folding 1386 // both SHRINK-or-equal (&amp;->&, a multi-byte UTF-8 codepoint -> one ASCII byte), so a decoded 1387 // run is never longer than its source. This replaces a hand-picked TRIO -- sys_mmap(1100), a 1388 // 1024 cap and a 960 input clamp -- that silently truncated any text box over 960 bytes while 1389 // LAYOUT had measured and reserved the full height for it, so paint dropped text the page had 1390 // already made room for. mmap faults pages in on demand, so the headroom costs address space, 1391 // not resident memory. 1392 var fdcap: i64 = 1 1393 var fdi: i64 = 0 1394 while fdi < tree.count { 1395 let fdb: *LayoutBox = ((tree.boxes as i64) + fdi * NX_LAYOUT_BOX_BYTES) as *LayoutBox 1396 if fdb.text_len > fdcap { fdcap = fdb.text_len } 1397 fdi = fdi + 1 1398 } 1399 fdcap = fdcap + 2 1400 let fdec: *u8 = sys_mmap(fdcap) // reused per text box: decode HTML entities before painting 1401 var base_bg: i64 = 0x00FFFFFF 1402 if page.dark_mode == 1 { base_bg = 0x000E1116 } // dark base so uncovered margin areas match the theme 1403 // CSS BACKGROUND PROPAGATION (SPEC semantics): the canvas takes the background of the ROOT elements 1404 // ONLY -- html/body (boxes 0..1) -- exactly like Chrome. The first version scanned ALL blocks for the 1405 // first non-white bg; the top-20 suite caught it painting google.com's whole canvas dark off a banner 1406 // div (coarse parity 0.0%). var() bgs on body resolve through cx_expand, so /experiential's dark body 1407 // still propagates. Gated on dark_mode==0 (explicit reader theme keeps its base). 1408 if page.dark_mode == 0 { 1409 // walk the ROOT CHAIN: from the parentless root, descend while the current box has exactly ONE 1410 // full-width BLOCK child (html -> wrapper -> body in our tree); the first background on that chain 1411 // is the canvas (spec: html/body propagate). A box with SIBLING blocks (google's banner divs) ends 1412 // the chain -- content never propagates. (The first-non-white-anywhere version painted google's 1413 // canvas dark off a banner; the parentless-root-only version missed body at depth 2 -- both caught 1414 // by the top-20 suite + the /experiential void-luma regression check.) 1415 var cur: i64 = 0 - 1 1416 var cbi: i64 = 0 1417 while cbi < tree.count { 1418 if cur < 0 { 1419 let cb0: *LayoutBox = ((tree.boxes as i64) + cbi * NX_LAYOUT_BOX_BYTES) as *LayoutBox 1420 if cb0.kind == NX_LAYOUT_BOX_BLOCK { if cb0.parent_idx < 0 { cur = cbi } } 1421 } 1422 cbi = cbi + 1 1423 } 1424 var hops: i64 = 0 1425 while cur >= 0 { 1426 if hops >= 6 { cur = 0 - 1 } 1427 else { 1428 hops = hops + 1 1429 let cb: *LayoutBox = ((tree.boxes as i64) + cur * NX_LAYOUT_BOX_BYTES) as *LayoutBox 1430 let cbg: i64 = br_comp_bg(src, computed, ncomp, cur) 1431 if cbg >= 0 { if cbg != 0x00FFFFFF { base_bg = cbg; cur = 0 - 1 } } 1432 if cur >= 0 { 1433 // exactly one full-width BLOCK child continues the chain 1434 var only: i64 = 0 - 1 1435 var nfull: i64 = 0 1436 var ci: i64 = cb.first_child_idx 1437 var guard: i64 = 0 1438 while ci >= 0 { 1439 if guard >= 4096 { ci = 0 - 1 } 1440 else { 1441 guard = guard + 1 1442 let cc: *LayoutBox = ((tree.boxes as i64) + ci * NX_LAYOUT_BOX_BYTES) as *LayoutBox 1443 if cc.kind == NX_LAYOUT_BOX_BLOCK { if cc.w == cb.w { nfull = nfull + 1; only = ci } } 1444 ci = cc.next_sibling_idx 1445 } 1446 } 1447 if nfull == 1 { cur = only } else { cur = 0 - 1 } 1448 } 1449 } 1450 } 1451 } 1452 nx_paint_solid_rect(fb, 0, 0, win_w * scale, fb.height, br_color_from_int(base_bg)) 1453 var i: i64 = 0 1454 let bwp: *i64 = sys_mmap(16) as *i64 // border width out-slot (hoisted: no mmap in the loop) 1455 while i < tree.count { 1456 let b: *LayoutBox = ((tree.boxes as i64) + i * NX_LAYOUT_BOX_BYTES) as *LayoutBox 1457 var _pbx: i64 = 0 1458 if b.kind == NX_LAYOUT_BOX_BLOCK { _pbx = 1 } 1459 if b.kind == NX_LAYOUT_BOX_INLINE_BLOCK { _pbx = 1 } // atomic inline-block (chips/tags/buttons) now paint bg+border 1460 if _pbx == 1 { if b.w > 0 { if b.h > 0 { 1461 let bg: i64 = br_comp_bg(src, computed, ncomp, i) 1462 if bg >= 0 { if bg != 0x00FFFFFF { nx_paint_solid_rect(fb, b.x * scale, (b.y - scroll_y + NX_CHROME_H) * scale, b.w * scale, b.h * scale, br_color_from_int(bg)) } } 1463 // CSS borders (the `border` shorthand): stroke the 4 edges -- tables/cards get real lines 1464 let bc: i64 = br_comp_border(src, computed, ncomp, i, bwp) 1465 if bc >= 0 { 1466 let bw2: i64 = bwp[0] 1467 nx_paint_solid_rect(fb, b.x * scale, (b.y - scroll_y + NX_CHROME_H) * scale, b.w * scale, bw2 * scale, br_color_from_int(bc)) 1468 nx_paint_solid_rect(fb, b.x * scale, (b.y - scroll_y + NX_CHROME_H + b.h - bw2) * scale, b.w * scale, bw2 * scale, br_color_from_int(bc)) 1469 nx_paint_solid_rect(fb, b.x * scale, (b.y - scroll_y + NX_CHROME_H) * scale, bw2 * scale, b.h * scale, br_color_from_int(bc)) 1470 nx_paint_solid_rect(fb, (b.x + b.w - bw2) * scale, (b.y - scroll_y + NX_CHROME_H) * scale, bw2 * scale, b.h * scale, br_color_from_int(bc)) 1471 } 1472 } } } 1473 i = i + 1 1474 } 1475 // FIND-IN-PAGE highlight pass: a yellow band behind every text box matching the active search term 1476 // (drawn under the text so the glyphs stay readable). Only runs when a find is active. 1477 if page.find_ptr != 0 { 1478 let fterm: *u8 = page.find_ptr as *u8 1479 i = 0 1480 while i < tree.count { 1481 let b: *LayoutBox = ((tree.boxes as i64) + i * NX_LAYOUT_BOX_BYTES) as *LayoutBox 1482 if b.kind == NX_LAYOUT_BOX_TEXT { if b.text_len * b.h > 0 { // h==0 = never laid out (hidden subtree) -> no highlight 1483 if br_box_has_term(src, b.text_off, b.text_len, fterm, page.find_len) == 1 { 1484 var hw: i64 = b.w 1485 if hw < 8 { hw = 8 } 1486 nx_paint_solid_rect(fb, b.x * scale, (b.y - scroll_y + NX_CHROME_H) * scale, hw * scale, 17 * scale, br_color_from_int(0x00FFE066)) 1487 } 1488 } } 1489 i = i + 1 1490 } 1491 } 1492 i = 0 1493 while i < tree.count { 1494 let b: *LayoutBox = ((tree.boxes as i64) + i * NX_LAYOUT_BOX_BYTES) as *LayoutBox 1495 let syt: i64 = (b.y - scroll_y + NX_CHROME_H) * scale // VIEWPORT CULL: off-screen text boxes skip the 1496 var vis: i64 = 1 // expensive vector/bitmap raster (fb.height = full 1497 if syt > fb.height { vis = 0 } // page for the shot path -> renders all; = 720 for 1498 if syt + (b.h + 80) * scale < 0 { vis = 0 } // the GUI -> only ~visible boxes -> fast + no hang) 1499 if vis == 1 { 1500 // NEVER-LAID GUARD (seq1130): a text box under a display:none ancestor is never reached by 1501 // _layout_block_recurse -- it keeps x=0,y=0,w=0,h=0 from tree init. Painting it wraps the run 1502 // at the w=0 minimum and stacks every hidden run at the origin = the "blob rail". Layout is the 1503 // only writer of b.h and always sets it > 0 for a placed text box, so h==0 means NOT LAID OUT. 1504 // text_len*h > 0 == (text_len > 0 AND h > 0) for the non-negative fields. 1505 if b.kind == NX_LAYOUT_BOX_TEXT { if b.text_len * b.h > 0 { 1506 let par: i64 = b.parent_idx 1507 var col: i64 = br_comp_color_inh(src, computed, ncomp, tree, par) // CSS-inherited color 1508 if col < 0 { col = 0 } 1509 let nn: i64 = b.text_len 1510 let dn0: i64 = nx_html_decode_entities(src, b.text_off, nn, fdec, fdcap) 1511 let dn: i64 = br_fold_text(fdec, 0, dn0, fdec, fdcap) // BR30: fold only what the face lacks (same policy as layout's pre-pass); no face: UTF-8 -> renderable ASCII (accents->base, curly->straight, mdash->-): real pages render in the vector font. Identity for ASCII -> gate pages byte-unchanged. 1512 let fs: i64 = br_comp_fontsize_inh(src, computed, ncomp, tree, par, 16) // PROPORTIONAL: size text by the INHERITED cascade incl the `font` shorthand (same rule as layout -> measure==paint) 1513 let lh_run: i64 = br_comp_lineheight_px(src, computed, ncomp, tree, par, fs) // CSS line-height (same resolver rule as layout) 1514 var tscale: i64 = 1 1515 if fs >= 24 { tscale = 2 } // MUST match _layout_scale_for_px (24/36 buckets) 1516 if fs >= 36 { tscale = 3 } // so painted glyph size == the layout's reserved metrics 1517 // VECTOR TEXT: opt-in; any run (headings AND body -- vector is now OCR-legible to 14px, proven by 1518 // nx_vecfont_body_bench's size ladder) whose chars all have vector glyphs is painted with the 1519 // proportional stroke font instead of the bitmap monospace (the "typewriter" body). empx = the 1520 // cascade font-size x paint scale (exact). Vector is proportional+tighter than the bitmap the 1521 // layout reserved -> fits the box. vec_headings==2 forces body-too; ==1 keeps headings-only. 1522 var did_vec: i64 = 0 1523 var vec_ok: i64 = 0 1524 if page.vec_headings == 1 { if tscale >= 2 { vec_ok = 1 } } 1525 if page.vec_headings == 2 { vec_ok = 1 } 1526 if vec_ok == 1 { 1527 br_face_resolve(src, computed, ncomp, tree, par) // BR27: the same face layout measured with, selected for paint 1528 var run_ok: i64 = font_run_has(fdec, dn) 1529 if bpv_fast_on == 1 { run_ok = font_run_has_ext(fdec, dn) } // GUI/atlas covers % [ ] ; " = + too 1530 if br_face != 0 { run_ok = font_run_vec_ok(fdec, dn) } // a real face covers every printable ASCII byte -- the SAME test layout used 1531 if run_ok == 1 { 1532 let empx: i64 = fs * scale 1533 // HALF-LEADING: CSS centers the glyph line inside the line box; without this, text 1534 // sits at box top and every line lands ~leading/2 higher than Chrome. 1535 var hl: i64 = 0 1536 if lh_run >= 8 { 1537 let gl: i64 = br_face_line_h(fs) // the face (asc - desc) when a face is on, else the stroke band 1538 if lh_run > gl { hl = (lh_run - gl) / 2 } 1539 } 1540 if lh_run >= 8 { bpv_line_adv = lh_run * scale } else { bpv_line_adv = 0 } 1541 br_paint_vec(fb, (b.x + 4) * scale, (b.y + hl - scroll_y + NX_CHROME_H) * scale, fdec, dn, empx, br_color_from_int(col), b.w * scale) 1542 bpv_line_adv = 0 1543 did_vec = 1 1544 } 1545 } 1546 if did_vec == 0 { 1547 // WORD-WRAP long runs by MEASURED advances -- the SAME font8x8_next_break the measured layout 1548 // reserved lines with, over the same avail budget (b.w/tscale in 1x units), so painted lines 1549 // exactly fill the reserved box. Short runs take the single-line path unchanged. 1550 let wtab: *u8 = font8x8_table() 1551 var avail_u: i64 = b.w / tscale 1552 if avail_u < 4 { avail_u = 4 } 1553 var lhadv: i64 = 17 * tscale 1554 if lh_run >= 8 { lhadv = lh_run } // CSS line-height: advance wrapped lines exactly as layout reserved 1555 var hl2: i64 = 0 1556 if lhadv > 17 * tscale { hl2 = (lhadv - 17 * tscale) / 2 } // half-leading (center in the line box) 1557 var pos: i64 = 0 1558 var li: i64 = 0 1559 var w1: i64 = 0 // drawn width of the first line (underline = text width) 1560 while pos < dn { 1561 var e: i64 = font8x8_next_break(wtab, fdec, dn, avail_u, pos) 1562 if e <= pos { e = dn } 1563 let dw: i64 = br_paint_text2x(fb, (b.x + 4) * scale, (b.y + hl2 - scroll_y + NX_CHROME_H + 1 + li * lhadv) * scale, ((fdec as i64)+pos) as *u8, e - pos, br_color_from_int(col), tscale * scale) 1564 if li == 0 { w1 = dw } 1565 pos = e 1566 li = li + 1 1567 // DERIVED loop bound: a line consumes at least one character, so li can never 1568 // legitimately exceed dn. It is termination insurance, NOT a line budget -- the 1569 // hand-picked 60 here silently dropped every line past the 60th that layout had 1570 // already reserved height for. 1571 if li > dn { pos = dn } 1572 } 1573 var is_link: i64 = 0 1574 if col == 0x2563eb { is_link = 1 } // light-theme link blue 1575 if col == 0x6aa3ff { is_link = 1 } // dark-theme link blue 1576 if is_link == 1 { nx_paint_solid_rect(fb, (b.x + 4) * scale, (b.y - scroll_y + NX_CHROME_H + 1 + 14 * tscale) * scale, w1, tscale * scale, br_color_from_int(col)) } // underline links (drawn-text width) 1577 } 1578 } } } 1579 i = i + 1 1580 } 1581 // IMAGE pass: blit decoded <img> pixels (the consumer fills page.bimg via fetch+decode) at each img 1582 // box's laid-out position. ADDITIVE -- the layout algorithm is untouched; images paint over the flow. 1583 i = 0 1584 while i < tree.count { 1585 let ib: *LayoutBox = ((tree.boxes as i64) + i * NX_LAYOUT_BOX_BYTES) as *LayoutBox 1586 let ip: i64 = page.bimg[i] 1587 if ip != 0 { 1588 let iw: i64 = page.bimg_w[i] 1589 let ih: i64 = page.bimg_h[i] 1590 var dw: i64 = iw 1591 // HIDDEN-SUBTREE GUARD, CORRECTED (2026-07-29). The seq1130 rule "h<=0 ⇒ never laid ⇒ 1592 // don't paint" is right for TEXT but WRONG for <img>: an image with no CSS width/height 1593 // legitimately has h=0 until its intrinsic size is known (only after fetch+decode), so 1594 // that guard silently killed 100% of images on every page (measured: imgsrc=11, 1595 // imgsrc_laid=0). Ask the RIGHT question instead — is the image's CONTAINER laid out? A 1596 // hidden subtree has a 0-height PARENT; a not-yet-sized <img> sits inside a laid parent. 1597 if ib.h <= 0 { 1598 var par_laid: i64 = 0 1599 if ib.parent_idx >= 0 { 1600 let ipb: *LayoutBox = ((tree.boxes as i64) + ib.parent_idx * NX_LAYOUT_BOX_BYTES) as *LayoutBox 1601 if ipb.h > 0 { par_laid = 1 } 1602 } 1603 if par_laid == 0 { dw = 0 } 1604 } 1605 let avail: i64 = win_w - ib.x - 8 1606 if dw > avail { dw = avail } // cap to viewport width 1607 var dh: i64 = ih 1608 if dw < iw { if iw > 0 { dh = ih * dw / iw } } // preserve aspect on downscale 1609 if dw > 0 { if dh > 0 { 1610 var channels: i64=3;if page.bimg_channels!=(0 as *i64){if page.bimg_channels[i]==4{channels=4}} 1611 br_blit_pixels(fb, ib.x * scale, (ib.y - scroll_y + NX_CHROME_H) * scale, dw * scale, dh * scale, ip as *u8, iw, ih,channels) 1612 } } 1613 } 1614 i = i + 1 1615 } 1616 // chrome (all coords * scale) -- Chrome-palette grays, same geometry (hit-tests unchanged). Dark theme 1617 // swaps the palette (near-black toolbar + light glyphs) so the whole browser is coherent in dark mode. 1618 var c_bar: i64 = 0x00F1F3F4 1619 var c_edge: i64 = 0x00DADCE0 1620 var c_btn: i64 = 0x00F6F8FA 1621 var c_field: i64 = 0x00FFFFFF 1622 var c_ink: i64 = 0x00202124 1623 if page.dark_mode == 1 { c_bar=0x001A1F28; c_edge=0x00303845; c_btn=0x00232833; c_field=0x000E1116; c_ink=0x00D4DAE3 } 1624 nx_paint_solid_rect(fb, 0, 0, win_w * scale, NX_CHROME_H * scale, br_color_from_int(c_bar)) 1625 nx_paint_solid_rect(fb, 0, (NX_CHROME_H - 1) * scale, win_w * scale, scale, br_color_from_int(c_edge)) 1626 nx_paint_solid_rect(fb, 8 * scale, 8 * scale, 30 * scale, 28 * scale, br_color_from_int(c_btn)) 1627 nx_paint_solid_rect(fb, 44 * scale, 8 * scale, 30 * scale, 28 * scale, br_color_from_int(c_btn)) 1628 nx_paint_solid_rect(fb, 80 * scale, 8 * scale, 30 * scale, 28 * scale, br_color_from_int(c_btn)) 1629 br_paint_text2x(fb, 18 * scale, 16 * scale, "<\x00" as *u8, 1, br_color_from_int(c_ink), scale) 1630 br_paint_text2x(fb, 54 * scale, 16 * scale, ">\x00" as *u8, 1, br_color_from_int(c_ink), scale) 1631 br_paint_text2x(fb, 90 * scale, 16 * scale, "R\x00" as *u8, 1, br_color_from_int(c_ink), scale) 1632 let bx: i64 = 118 1633 nx_paint_solid_rect(fb, bx * scale, 8 * scale, (win_w - bx - 10) * scale, 28 * scale, br_color_from_int(c_field)) 1634 var un: i64 = ulen 1635 if un > 110 { un = 110 } 1636 br_paint_text2x(fb, (bx + 6) * scale, 14 * scale, cur_url, un, br_color_from_int(c_ink), scale) 1637 return 0 1638} 1639 1640// the un-scrolled draw every existing caller uses (delegates; page top at the viewport top). 1641func br_draw_fb(fb: *Framebuffer, page: *Page, win_w: i64, cur_url: *u8, ulen: i64, scale: i64) -> i64 { 1642 return br_draw_fb_at(fb, page, win_w, cur_url, ulen, scale, 0) 1643} 1644 1645// render the laid-out page to a 1000xSH RGBA framebuffer and write it as a PNG at `path` 1646// FULL-PAGE SHOT (2026-07-27). SH was hardcoded 1000 = ONE SCREENFUL, while Chrome's 1647// --screenshot captures the WHOLE document. Comparing the two graded Nishi on ~21% of a 1648// page against Chrome on 100% of it -- so the only honest fixes are to raise Nishi to 1649// Chrome's behaviour (this) or to cripple the oracle (never). Capture the full laid-out 1650// height, clamped: floor 1000 (an empty/short page still yields a normal-looking shot), 1651// ceiling 20000 (a runaway page_h can't ask for a gigabyte framebuffer). 1652func br_shot_png(page: *Page, cur_url: *u8, ulen: i64, path: *u8) -> i64 { 1653 let SW: i64 = 1000 1654 var SH: i64 = page.page_h 1655 if SH < 1000 { SH = 1000 } 1656 if SH > 20000 { SH = 20000 } 1657 let fb: *Framebuffer = sys_mmap(NX_FRAMEBUFFER_BYTES) as *Framebuffer 1658 let px: *u8 = sys_mmap(SW * SH * 4 + 64) 1659 nx_framebuffer_init(fb, px, SW, SH) 1660 br_draw_fb(fb, page, SW, cur_url, ulen, 1) 1661 let rgb: *u8 = sys_mmap(SW * SH * 3 + 64) 1662 var p: i64 = 0 1663 while p < SW * SH { 1664 rgb[p * 3 + 0] = px[p * 4 + 0] 1665 rgb[p * 3 + 1] = px[p * 4 + 1] 1666 rgb[p * 3 + 2] = px[p * 4 + 2] 1667 p = p + 1 1668 } 1669 nx_png_write_rgb(path, rgb, SW, SH) 1670 return 0 1671} 1672 1673// hit-test: find a box with an href whose rect contains doc-point (dx,dy). 1 + sets off/len, else 0. 1674// CONTAINER ANCHORS (2026-07-03): an <a> wrapping block children (e.g. a card link: <a><h3>..</h3> 1675// <p>..</p></a>) lays out with a ZERO own-rect -- its geometry lives in its DESCENDANT boxes. Such 1676// links were dead to clicks (found by nx_aw_browser_gate on the real andelinwest pages). For an href 1677// box with no height, hit-test the UNION of its descendants' rects instead. Purely additive: boxes 1678// with real rects behave exactly as before; zero-rect href boxes previously matched NOTHING. 1679func br_hit_link(page: *Page, dx: i64, dy: i64, off: *i64, len: *i64) -> i64 { 1680 let tree: *LayoutTree = page.tree 1681 var i: i64 = 0 1682 while i < tree.count { 1683 if page.bhref_off[i] >= 0 { 1684 let b: *LayoutBox = ((tree.boxes as i64) + i * NX_LAYOUT_BOX_BYTES) as *LayoutBox 1685 var bx: i64 = b.x 1686 var by: i64 = b.y 1687 var bw: i64 = b.w 1688 var bh: i64 = b.h 1689 if bh <= 0 { 1690 // SIBLING-SPAN effective rect: the layout FLATTENS a container anchor's children to 1691 // its SIBLINGS (observed: card content boxes follow the zero-rect anchor box under 1692 // the same parent). Union the rects of the boxes AFTER i while they stay inside i's 1693 // parent's subtree, stopping at the next href-carrying box (= the next link). 1694 let par: i64 = b.parent_idx 1695 var x0: i64 = 0 1696 var y0: i64 = 0 1697 var x1: i64 = 0 1698 var y1: i64 = 0 1699 var any: i64 = 0 1700 var j: i64 = i + 1 1701 var span: i64 = 1 1702 while span == 1 { 1703 if j >= tree.count { span = 0 } 1704 else { if page.bhref_off[j] >= 0 { span = 0 } 1705 else { 1706 let cb: *LayoutBox = ((tree.boxes as i64) + j * NX_LAYOUT_BOX_BYTES) as *LayoutBox 1707 // is j inside par's subtree? (bounded parent-chain walk, cycle-safe) 1708 var p: i64 = cb.parent_idx 1709 var hop: i64 = 0 1710 var inside: i64 = 0 1711 while hop < 64 { 1712 if p == (0 - 1) { hop = 64 } 1713 else { if p == par { inside = 1; hop = 64 } 1714 else { let pb: *LayoutBox = ((tree.boxes as i64) + p * NX_LAYOUT_BOX_BYTES) as *LayoutBox; p = pb.parent_idx; hop = hop + 1 } } 1715 } 1716 if inside == 0 { span = 0 } 1717 else { 1718 if cb.w > 0 { if cb.h > 0 { 1719 if any == 0 { x0 = cb.x; y0 = cb.y; x1 = cb.x + cb.w; y1 = cb.y + cb.h; any = 1 } 1720 else { 1721 if cb.x < x0 { x0 = cb.x } 1722 if cb.y < y0 { y0 = cb.y } 1723 if cb.x + cb.w > x1 { x1 = cb.x + cb.w } 1724 if cb.y + cb.h > y1 { y1 = cb.y + cb.h } 1725 } 1726 } } 1727 j = j + 1 1728 } 1729 } } 1730 } 1731 if any == 1 { bx = x0; by = y0; bw = x1 - x0; bh = y1 - y0 } 1732 } 1733 var hitw: i64 = bw // layout metric now matches the 9px render font, so b.w is the true width 1734 if hitw < 12 { hitw = 12 } 1735 if dx >= bx { if dx < bx + hitw { if dy >= by { if dy < by + bh { 1736 off[0] = page.bhref_off[i]; len[0] = page.bhref_len[i]; return 1 1737 } } } } 1738 } 1739 i = i + 1 1740 } 1741 return 0 1742} 1743 1744func br_image_meta_bytes(count: i64) -> i64 {let n: i64=(count+1)*8;if n<=NXA_SMALL_MAX{return NXA_SMALL_MAX+1}return n} 1745func br_release_owned_images(page: *Page) -> i64 { 1746 if page.tree==(0 as *LayoutTree) || page.bimg_owned_bytes==(0 as *i64) {return 0} 1747 var i: i64=0;var rc: i64=0;while i<page.tree.count { 1748 if page.bimg_owned_bytes[i]>0 && page.bimg[i]!=0 { 1749 if sys_munmap(page.bimg[i] as *u8,page.bimg_owned_bytes[i])!=0 {rc=0-1}else{page.bimg[i]=0;page.bimg_w[i]=0;page.bimg_h[i]=0;page.bimg_owned_bytes[i]=0;if page.bimg_channels!=(0 as *i64){page.bimg_channels[i]=0}} 1750 }i=i+1 1751 }return rc 1752} 1753func br_release_image_metadata(page: *Page) -> i64 { 1754 if br_release_owned_images(page)!=0{return 0-1} 1755 if page.tree==(0 as *LayoutTree){return 0} 1756 let bytes: i64=br_image_meta_bytes(page.tree.count) 1757 if page.bimg_channels!=(0 as *i64){if sys_munmap(page.bimg_channels as *u8,bytes)!=0{return 0-1}page.bimg_channels=0 as *i64} 1758 if page.bimg_owned_bytes!=(0 as *i64){if sys_munmap(page.bimg_owned_bytes as *u8,bytes)!=0{return 0-1}page.bimg_owned_bytes=0 as *i64} 1759 return 0 1760} 1761// Move an existing chokepoint result into the page. Borrowed canvas assignment 1762// remains unchanged; only explicitly tagged mappings are ever released. 1763func br_image_take_result(page: *Page,i: i64,result: *i64) -> i64 { 1764 if page.tree==(0 as *LayoutTree) || i<0 || i>=page.tree.count{return 0-1} 1765 if page.bimg_channels==(0 as *i64) || page.bimg_owned_bytes==(0 as *i64){return 0-1} 1766 if result[0]==0 || result[1]<=0 || result[2]<=0{return 0-1} 1767 if result[3]!=3 && result[3]!=4{return 0-1} 1768 if page.bimg_owned_bytes[i]>0 && page.bimg[i]!=0{if sys_munmap(page.bimg[i] as *u8,page.bimg_owned_bytes[i])!=0{return 0-1}} 1769 page.bimg[i]=result[0];page.bimg_w[i]=result[1];page.bimg_h[i]=result[2];page.bimg_channels[i]=result[3];page.bimg_owned_bytes[i]=result[5] 1770 result[0]=0;result[5]=0;return 0 1771}