code wiki / (root) / nx_paint_walk_layout.nx

nx_paint_walk_layout.nx source

↩ module page · 665 lines · 30524 B

1// nx_paint_walk_layout.nx -- walk a laid-out LayoutBox tree and 2// paint each box's `background-color` declaration into a Framebuffer. 3// Phase 4 second primitive of NISHI_BROWSER_ROADMAP. THE 4// integration primitive that proves the full bits-up pipeline 5// composes end-to-end: 6// 7// CSS bytes -> nx_css_tokenize -> nx_css_parse -> nx_css_apply 8// (cascade -> per-box decls) 9// HTML tree -> nx_layout_box -> nx_layout_block 10// (per-box x, y, w, h) 11// Cascade -> nx_css_lookup_property -> nx_css_color_decode 12// (per-box CssColor) 13// Box+color -> nx_paint_solid_rect 14// (pixels into Framebuffer) 15// 16// This primitive owns the walk + per-box lookup + color decode + 17// paint orchestration. Each individual step is a sovereign primitive 18// shipped prior; this is the connective tissue. 19// 20// What it does NOT handle yet (Phase 4b queued, explicit in header): 21// - background-image / gradient backgrounds 22// - foreground text rendering (waits for nx_paint_text + 23// nx_font_bitmap glyph rasterizer) 24// - border-color / border-style / border-width stroke 25// - box-shadow / outline / drop shadow 26// - opacity / alpha blending (currently solid overwrite) 27// - z-index stacking contexts (currently document-order paint) 28// - overflow:hidden clip-rect propagation to children 29// - sub-pixel positioning 30// 31// Per cardinal feedback-honest-perf-verdict-no-aspirational-claims: 32// gap list is EXACT. 33// 34// genealogy_id: w3c_css_backgrounds_module_level_3 + 35// substrate_browser_phase_4_walk_paint 36// lineage_id: nishi_browser_paint_walk_layout_v1 37 38import "nx_syscalls.nx" 39import "nx_css_tokenize.nx" 40import "nx_css_apply.nx" 41import "nx_css_color_decode.nx" 42import "nx_layout_box.nx" 43import "nx_paint_solid_rect.nx" 44import "nx_paint_text.nx" 45import "nx_text_wrap.nx" 46const NX_MAGIC_100000: i64 = 100000 47const NX_MAGIC_65536: i64 = 65536 48 49// Property-name table for the paint walk. Mirrors the structure of 50// LayoutPropTable in nx_layout_block but is narrower -- paint only 51// needs background-color today; future additions (color for text, 52// border-color, etc.) extend this struct without breaking callers. 53struct PaintPropTable { 54 src: *u8, 55 background_color_off: i64, 56 background_color_len: i64 57} 58 59const NX_PAINT_PROP_TABLE_BYTES: i64 = 24 60 61// Context bundle so the walk function signature stays compact. 62// 63// `text_src` and `default_text_color` are OPTIONAL. When set, TEXT 64// boxes (kind = NX_LAYOUT_BOX_TEXT) get their body bytes rendered 65// via nx_paint_text using the default color. text_src is the byte 66// buffer that the LayoutBox.text_off/text_len fields index into 67// (typically the HTML source buffer that nx_layout_from_dom walked). 68// Passing 0 for text_src OR 0 for default_text_color disables text 69// rendering — preserves backward compatibility with the 70// background-only walk. 71struct PaintCtx { 72 fb: *Framebuffer, 73 tree: *LayoutTree, 74 computed: *CssComputedDecl, 75 n_computed: i64, 76 cascade_src: *u8, 77 table: *PaintPropTable, 78 text_src: *u8, 79 default_text_color: *CssColor, 80 boxel: *CssElement // box-indexed elements (tag/id/class); enables list-marker bullets. 0 = off. 81} 82 83const NX_PAINT_CTX_BYTES: i64 = 72 84 85// ---- helpers ---- 86 87func _paint_put_byte(buf: *u8, i: i64, v: i64) -> i64 { 88 buf[i] = v as u8 89 return 0 90} 91 92// Initialize the property-name table. Caller supplies a writable 93// buffer of at least 16 bytes; this fn writes "background-color" 94// (16 bytes) into it. 95func nx_paint_prop_table_init(t: *PaintPropTable, buf: *u8) -> i64 { 96 t.src = buf 97 // "background-color" 98 _paint_put_byte(buf, 0, 98) // b 99 _paint_put_byte(buf, 1, 97) // a 100 _paint_put_byte(buf, 2, 99) // c 101 _paint_put_byte(buf, 3, 107) // k 102 _paint_put_byte(buf, 4, 103) // g 103 _paint_put_byte(buf, 5, 114) // r 104 _paint_put_byte(buf, 6, 111) // o 105 _paint_put_byte(buf, 7, 117) // u 106 _paint_put_byte(buf, 8, 110) // n 107 _paint_put_byte(buf, 9, 100) // d 108 _paint_put_byte(buf, 10, 45) // - 109 _paint_put_byte(buf, 11, 99) // c 110 _paint_put_byte(buf, 12, 111) // o 111 _paint_put_byte(buf, 13, 108) // l 112 _paint_put_byte(buf, 14, 111) // o 113 _paint_put_byte(buf, 15, 114) // r 114 t.background_color_off = 0 115 t.background_color_len = 16 116 return 0 117} 118 119// Compare two byte regions across two source buffers (the cascade 120// stores property names in cascade_src; the paint table stores its 121// canonical names in table.src). Returns 1 if equal-length and all 122// bytes match. 123func _paint_bytes_equal(a_src: *u8, a_off: i64, a_len: i64, 124 b_src: *u8, b_off: i64, b_len: i64) -> i64 { 125 if a_len != b_len { return 0 } 126 var i: i64 = 0 127 while i < a_len { 128 let ca: i64 = (a_src[a_off + i] as i64) & 255 129 let cb: i64 = (b_src[b_off + i] as i64) & 255 130 if ca != cb { return 0 } 131 i = i + 1 132 } 133 return 1 134} 135 136// Find the LAST CssComputedDecl matching (query_element, property 137// name in table). Returns the entry, or 0 if none. Reverse-scan 138// for source-order winner-take-all per CSS cascade. 139func _paint_lookup_decl(ctx: *PaintCtx, 140 element_idx: i64, 141 table_prop_off: i64, 142 table_prop_len: i64) -> *CssComputedDecl { 143 var i: i64 = ctx.n_computed - 1 144 while i >= 0 { 145 let cd: *CssComputedDecl = (ctx.computed as *u8 + (i as nx_size) * (NX_CSS_COMPUTED_DECL_BYTES as nx_size)) as *CssComputedDecl 146 if cd.element_idx == element_idx { 147 if _paint_bytes_equal(ctx.cascade_src, cd.prop_off, cd.prop_len, 148 ctx.table.src, table_prop_off, table_prop_len) == 1 { 149 return cd 150 } 151 } 152 i = i - 1 153 } 154 return 0 as *CssComputedDecl 155} 156 157// Extract the FIRST color-decoding token from a `background` shorthand value into `out`. Handles the common 158// real-world forms: `background:red` (named), `background:#fff center`, `background:url(x) no-repeat blue`. 159// Tries hex (#-led) / named / rgb() per space-separated token; returns 1 at the first that decodes. 160func _bg_extract_color(src: *u8, off: i64, len: i64, out: *CssColor) -> i64 { 161 var p: i64 = off 162 let pe: i64 = off + len 163 var found: i64 = 0 164 while p < pe { 165 var sk: i64 = 1 166 while sk == 1 { if p < pe { if (src[p]&0xff) == 32 { p = p + 1 } else { sk = 0 } } else { sk = 0 } } 167 var te: i64 = p 168 var go: i64 = 1 169 while go == 1 { if te < pe { if (src[te]&0xff) == 32 { go = 0 } else { te = te + 1 } } else { go = 0 } } 170 if te > p { 171 var ok: i64 = 0 172 if (src[p]&0xff) == 35 { ok = nx_css_color_decode(src, p, te - p, out) } 173 else { ok = nx_css_color_named(src, p, te - p, out); if ok != 1 { ok = nx_css_color_rgb_func(src, p, te - p, out) } } 174 if ok == 1 { found = 1; p = pe } else { p = te } 175 } else { p = pe } 176 } 177 return found 178} 179 180// Look up the `background` shorthand decl (prop_len 10) for an element. Local literal -> no table change. 181func _paint_lookup_bg(ctx: *PaintCtx, element_idx: i64) -> *CssComputedDecl { 182 let bname: *u8 = "background\x00" as *u8 183 var i: i64 = ctx.n_computed - 1 184 while i >= 0 { 185 let cd: *CssComputedDecl = (ctx.computed as *u8 + (i as nx_size) * (NX_CSS_COMPUTED_DECL_BYTES as nx_size)) as *CssComputedDecl 186 if cd.element_idx == element_idx { 187 if cd.prop_len == 10 { 188 var same: i64 = 1 189 var k: i64 = 0 190 while k < 10 { if (ctx.cascade_src[cd.prop_off + k] & 0xff) != (bname[k] & 0xff) { same = 0; k = 10 } else { k = k + 1 } } 191 if same == 1 { return cd } 192 } 193 } 194 i = i - 1 195 } 196 return 0 as *CssComputedDecl 197} 198 199// Paint one box's background, if it has a HEXCOLOR background-color 200// declaration in the cascade. Returns the number of pixels written 201// (0 if no background-color declared or value isn't HEXCOLOR). 202func _paint_box_background(ctx: *PaintCtx, box_idx: i64) -> i64 { 203 let b: *LayoutBox = (ctx.tree.boxes as *u8 + (box_idx as nx_size) * (NX_LAYOUT_BOX_BYTES as nx_size)) as *LayoutBox 204 let color: *CssColor = (sys_mmap(NX_CSS_COLOR_BYTES as nx_size)) as *CssColor 205 var ok: i64 = 0 206 // 1. background-color longhand (preferred -- it's the fallback color even when a `background` image is set) 207 let decl: *CssComputedDecl = _paint_lookup_decl(ctx, box_idx, 208 ctx.table.background_color_off, ctx.table.background_color_len) 209 if decl != (0 as *CssComputedDecl) { 210 if decl.val_kind == NX_CSS_VAL_HEXCOLOR { ok = nx_css_color_decode(ctx.cascade_src, decl.val_off, decl.val_len, color) } 211 else { if decl.val_kind == NX_CSS_VAL_IDENT { 212 ok = nx_css_color_named(ctx.cascade_src, decl.val_off, decl.val_len, color) 213 if ok != 1 { ok = nx_css_color_rgb_func(ctx.cascade_src, decl.val_off, decl.val_len, color) } 214 } } 215 } 216 // 2. `background` SHORTHAND fallback (background:red / #fff / url(x) blue) -- extract its color token. 217 if ok != 1 { 218 let bd: *CssComputedDecl = _paint_lookup_bg(ctx, box_idx) 219 if bd != (0 as *CssComputedDecl) { ok = _bg_extract_color(ctx.cascade_src, bd.val_off, bd.val_len, color) } 220 } 221 if ok != 1 { return 0 } 222 if color.a == 0 { return 0 } // transparent -> nothing to paint 223 224 return nx_paint_solid_rect(ctx.fb, b.x, b.y, b.w, b.h, color) 225} 226 227// Find a computed `color` declaration for `element_idx` (the CSS text color, NOT background-color). 228// Matched with a local "color" literal so this needs no PaintPropTable change. prop_len==5 excludes 229// "background-color" (len 16); the byte-compare excludes other 5-char props like "width". 230func _paint_color_decl(ctx: *PaintCtx, element_idx: i64) -> *CssComputedDecl { 231 let cname: *u8 = "color\x00" as *u8 232 var i: i64 = ctx.n_computed - 1 233 while i >= 0 { 234 let cd: *CssComputedDecl = (ctx.computed as *u8 + (i as nx_size) * (NX_CSS_COMPUTED_DECL_BYTES as nx_size)) as *CssComputedDecl 235 if cd.element_idx == element_idx { 236 if cd.prop_len == 5 { 237 var same: i64 = 1 238 var k: i64 = 0 239 while k < 5 { 240 if (ctx.cascade_src[cd.prop_off + k] & 0xff) != (cname[k] & 0xff) { same = 0; k = 5 } else { k = k + 1 } 241 } 242 if same == 1 { return cd } 243 } 244 } 245 i = i - 1 246 } 247 return 0 as *CssComputedDecl 248} 249 250// Effective text color for a box: nearest ANCESTOR (inclusive) with a computed `color` (CSS color 251// inherits), decoded into `out`. Returns 1 if found, 0 to fall back to the default ink. This is the 252// reusable "inherited computed property for a text run" walk (font-size will reuse the same shape). 253func _paint_text_color(ctx: *PaintCtx, box_idx: i64, out: *CssColor) -> i64 { 254 var cur: i64 = box_idx 255 var safety: i64 = 0 256 while safety < 256 { 257 safety = safety + 1 258 if cur < 0 { return 0 } 259 let decl: *CssComputedDecl = _paint_color_decl(ctx, cur) 260 if decl != (0 as *CssComputedDecl) { 261 if decl.val_kind == NX_CSS_VAL_HEXCOLOR { 262 if nx_css_color_decode(ctx.cascade_src, decl.val_off, decl.val_len, out) == 1 { return 1 } 263 } 264 // NAMED color (color:red / white / ...) or rgb()/rgba() -> the value is an IDENT, not a hex token. 265 if decl.val_kind == NX_CSS_VAL_IDENT { 266 if nx_css_color_named(ctx.cascade_src, decl.val_off, decl.val_len, out) == 1 { return 1 } 267 if nx_css_color_rgb_func(ctx.cascade_src, decl.val_off, decl.val_len, out) == 1 { return 1 } 268 } 269 } 270 let b: *LayoutBox = (ctx.tree.boxes as *u8 + (cur as nx_size) * (NX_LAYOUT_BOX_BYTES as nx_size)) as *LayoutBox 271 cur = b.parent_idx 272 } 273 return 0 274} 275 276// Find a computed `font-size` declaration for `element_idx` (DIMENSION, e.g. "32px"). Matched with a 277// local "font-size" literal (prop_len==9) so no PaintPropTable change. Returns the value in px, or -1. 278func _paint_fontsize_decl_px(ctx: *PaintCtx, element_idx: i64) -> i64 { 279 let fname: *u8 = "font-size\x00" as *u8 280 var i: i64 = ctx.n_computed - 1 281 while i >= 0 { 282 let cd: *CssComputedDecl = (ctx.computed as *u8 + (i as nx_size) * (NX_CSS_COMPUTED_DECL_BYTES as nx_size)) as *CssComputedDecl 283 if cd.element_idx == element_idx { 284 if cd.prop_len == 9 { 285 var same: i64 = 1 286 var k: i64 = 0 287 while k < 9 { 288 if (ctx.cascade_src[cd.prop_off + k] & 0xff) != (fname[k] & 0xff) { same = 0; k = 9 } else { k = k + 1 } 289 } 290 if same == 1 { 291 // parse the numeric run of the value (e.g. "32" of "32px"); unit is a separate field. 292 var v: i64 = 0 293 var any: i64 = 0 294 var j: i64 = 0 295 while j < cd.val_len { 296 let c: i64 = ctx.cascade_src[cd.val_off + j] & 0xff 297 if c >= 48 { if c <= 57 { v = v * 10 + (c - 48); any = 1 } } 298 j = j + 1 299 } 300 if any == 1 { return v } 301 } 302 } 303 } 304 i = i - 1 305 } 306 return 0 - 1 307} 308 309// Inherited effective font-size (px) for a box: nearest ANCESTOR with computed font-size, else 16. 310func _paint_fontsize_px(ctx: *PaintCtx, box_idx: i64) -> i64 { 311 var cur: i64 = box_idx 312 var safety: i64 = 0 313 while safety < 256 { 314 safety = safety + 1 315 if cur < 0 { return 16 } 316 let px: i64 = _paint_fontsize_decl_px(ctx, cur) 317 if px >= 0 { return px } 318 let b: *LayoutBox = (ctx.tree.boxes as *u8 + (cur as nx_size) * (NX_LAYOUT_BOX_BYTES as nx_size)) as *LayoutBox 319 cur = b.parent_idx 320 } 321 return 16 322} 323 324// Integer glyph scale for a font-size in px (5x7 bitmap -> crisp integer steps). MUST match 325// _layout_scale_for_px in nx_layout_block so painted glyph size == reserved box size (no overlap). 326func _paint_scale_for_px(px: i64) -> i64 { 327 if px < 24 { return 1 } 328 if px < 36 { return 2 } 329 return 3 330} 331 332// Does the box have computed `display: none`? If so the walk skips it AND its subtree (matches the 333// layout, which gave it zero size). Local "display"/"none" literals -> no PaintPropTable change. 334func _paint_display_none(ctx: *PaintCtx, box_idx: i64) -> i64 { 335 let dn: *u8 = "display\x00" as *u8 336 let nn: *u8 = "none\x00" as *u8 337 var i: i64 = ctx.n_computed - 1 338 while i >= 0 { 339 let cd: *CssComputedDecl = (ctx.computed as *u8 + (i as nx_size) * (NX_CSS_COMPUTED_DECL_BYTES as nx_size)) as *CssComputedDecl 340 if cd.element_idx == box_idx { 341 if cd.prop_len == 7 { 342 var sp: i64 = 1 343 var k: i64 = 0 344 while k < 7 { if (ctx.cascade_src[cd.prop_off+k]&0xff) != (dn[k]&0xff) { sp = 0; k = 7 } else { k = k + 1 } } 345 if sp == 1 { 346 if cd.val_len == 4 { 347 var sv: i64 = 1 348 var j: i64 = 0 349 while j < 4 { if (ctx.cascade_src[cd.val_off+j]&0xff) != (nn[j]&0xff) { sv = 0; j = 4 } else { j = j + 1 } } 350 if sv == 1 { return 1 } 351 } 352 } 353 } 354 } 355 i = i - 1 356 } 357 return 0 358} 359 360// Does box_idx have a computed `prop` whose IDENT value byte-equals `val`? Generic (font-weight:bold / 361// font-style:italic / ...). Reverse-scan = cascade winner first; prop present but != val -> 0. 362func _paint_prop_ident_is(ctx: *PaintCtx, box_idx: i64, prop: *u8, prop_len: i64, val: *u8, val_len: i64) -> i64 { 363 var i: i64 = ctx.n_computed - 1 364 while i >= 0 { 365 let cd: *CssComputedDecl = (ctx.computed as *u8 + (i as nx_size) * (NX_CSS_COMPUTED_DECL_BYTES as nx_size)) as *CssComputedDecl 366 if cd.element_idx == box_idx { 367 if cd.prop_len == prop_len { 368 var sp: i64 = 1 369 var k: i64 = 0 370 while k < prop_len { if (ctx.cascade_src[cd.prop_off+k]&0xff) != (prop[k]&0xff) { sp = 0; k = prop_len } else { k = k + 1 } } 371 if sp == 1 { 372 if cd.val_len == val_len { 373 var sv: i64 = 1 374 var j: i64 = 0 375 while j < val_len { if (ctx.cascade_src[cd.val_off+j]&0xff) != (val[j]&0xff) { sv = 0; j = val_len } else { j = j + 1 } } 376 if sv == 1 { return 1 } 377 } 378 return 0 379 } 380 } 381 } 382 i = i - 1 383 } 384 return 0 385} 386 387// Inherited boolean style: nearest ANCESTOR (inclusive) with prop==val. font-weight/font-style INHERIT, 388// so a text run under <b>/<strong> (or <i>/<em>) picks up bold/italic. Same ancestor-walk shape as 389// _paint_text_color / _paint_fontsize_px (the reusable inherited-computed-property spine). 390func _paint_inherited_style(ctx: *PaintCtx, box_idx: i64, prop: *u8, prop_len: i64, val: *u8, val_len: i64) -> i64 { 391 var cur: i64 = box_idx 392 var safety: i64 = 0 393 while safety < 256 { 394 safety = safety + 1 395 if cur < 0 { return 0 } 396 if _paint_prop_ident_is(ctx, cur, prop, prop_len, val, val_len) == 1 { return 1 } 397 let b: *LayoutBox = (ctx.tree.boxes as *u8 + (cur as nx_size) * (NX_LAYOUT_BOX_BYTES as nx_size)) as *LayoutBox 398 cur = b.parent_idx 399 } 400 return 0 401} 402 403// Paint a TEXT box's body bytes using its INHERITED computed `color` (blue <a> links) and INHERITED 404// font-size (scaled headings), falling back to default ink / 1x. Returns pixels written. 405func _paint_box_text(ctx: *PaintCtx, box_idx: i64) -> i64 { 406 if ctx.text_src == (0 as *u8) { return 0 } 407 if ctx.default_text_color == (0 as *CssColor) { return 0 } 408 let b: *LayoutBox = (ctx.tree.boxes as *u8 + (box_idx as nx_size) * (NX_LAYOUT_BOX_BYTES as nx_size)) as *LayoutBox 409 if b.kind != NX_LAYOUT_BOX_TEXT { return 0 } 410 if b.text_len <= 0 { return 0 } 411 let text_ptr: *u8 = (ctx.text_src as nx_int + b.text_off) as *u8 412 let col: *CssColor = (sys_mmap(NX_CSS_COLOR_BYTES as nx_size)) as *CssColor 413 var use_col: *CssColor = ctx.default_text_color 414 if _paint_text_color(ctx, b.parent_idx, col) == 1 { use_col = col } 415 let scale: i64 = _paint_scale_for_px(_paint_fontsize_px(ctx, b.parent_idx)) 416 let bold: i64 = _paint_inherited_style(ctx, b.parent_idx, "font-weight\x00" as *u8, 11, "bold\x00" as *u8, 4) 417 let italic: i64 = _paint_inherited_style(ctx, b.parent_idx, "font-style\x00" as *u8, 10, "italic\x00" as *u8, 6) 418 // WRAP text to the box width so long runs don't overflow the column (the Wikipedia text-overlap fix). 419 // Character-level wrap at line_chars = b.w/6, MATCHING nx_layout_block's TEXT-box height reservation 420 // (glyph_w=6, glyph_h=10, lines=ceil(text_len/line_chars)) -> paint fills EXACTLY the reserved lines, 421 // never spilling into sibling boxes. Word-level breaking is a later refinement. 422 let glyph_adv: i64 = 6 423 var line_chars: i64 = b.w / glyph_adv 424 if line_chars < 1 { line_chars = 1 } 425 let line_h: i64 = 10 426 var off2: i64 = 0 427 var ly: i64 = b.y 428 var total: i64 = 0 429 // WORD-level wrap via the shared breaker (same breaks the layout reserved height for) -- break at 430 // spaces, not mid-word. Skip a single leading space per line so words don't start indented. 431 while off2 < b.text_len { 432 let e: i64 = tw_next_break(text_ptr, b.text_len, line_chars, off2) 433 var s2: i64 = off2 434 if s2 < e { if (text_ptr[s2]&0xff)==32 { s2 = s2 + 1 } } 435 let n: i64 = e - s2 436 if n > 0 { 437 let chunk_ptr: *u8 = (text_ptr as nx_int + s2) as *u8 438 total = total + nx_paint_text_styled(ctx.fb, b.x, ly, chunk_ptr, n, use_col, scale, bold, italic) 439 } 440 off2 = e 441 ly = ly + line_h 442 } 443 return total 444} 445 446// ---- public API ---- 447 448func nx_paint_ctx_init(ctx: *PaintCtx, 449 fb: *Framebuffer, 450 tree: *LayoutTree, 451 computed: *CssComputedDecl, 452 n_computed: i64, 453 cascade_src: *u8, 454 table: *PaintPropTable) -> i64 { 455 ctx.fb = fb 456 ctx.tree = tree 457 ctx.computed = computed 458 ctx.n_computed = n_computed 459 ctx.cascade_src = cascade_src 460 ctx.table = table 461 ctx.text_src = 0 as *u8 // text rendering off by default 462 ctx.default_text_color = 0 as *CssColor // ditto 463 ctx.boxel = 0 as *CssElement // list-marker bullets off until set 464 return 0 465} 466 467// Enable list-marker bullets: supply the box-indexed CssElement array (tags). After this, a <li> box 468// whose parent is a <ul> gets a disc bullet painted in the list indent. 469func nx_paint_ctx_set_boxel(ctx: *PaintCtx, boxel: *CssElement) -> i64 { 470 ctx.boxel = boxel 471 return 0 472} 473 474// Enable TEXT-box rendering on top of the background-only walk. 475// Once set, encountering a LayoutBox of kind NX_LAYOUT_BOX_TEXT 476// during the walk paints its body bytes via nx_paint_text using 477// the given default color (inheritance of CSS `color` is queued 478// for Phase 3b). 479func nx_paint_ctx_set_text(ctx: *PaintCtx, 480 text_src: *u8, 481 default_text_color: *CssColor) -> i64 { 482 ctx.text_src = text_src 483 ctx.default_text_color = default_text_color 484 return 0 485} 486 487// If box_idx is a <li> whose parent is a <ul>, paint a small disc bullet in the list indent (left of 488// the content). Needs ctx.boxel (tags). <ol> items are skipped (they want numbers -- a later rung). 489func _paint_li_marker(ctx: *PaintCtx, box_idx: i64) -> i64 { 490 if ctx.boxel == (0 as *CssElement) { return 0 } 491 let e: *CssElement = (ctx.boxel as *u8 + (box_idx as nx_size) * (NX_CSS_ELEMENT_BYTES as nx_size)) as *CssElement 492 if e.tag_len != 2 { return 0 } 493 if (ctx.cascade_src[e.tag_off]&0xff) != 108 { return 0 } // 'l' 494 if (ctx.cascade_src[e.tag_off+1]&0xff) != 105 { return 0 } // 'i' 495 let b: *LayoutBox = (ctx.tree.boxes as *u8 + (box_idx as nx_size) * (NX_LAYOUT_BOX_BYTES as nx_size)) as *LayoutBox 496 let p: i64 = b.parent_idx 497 if p < 0 { return 0 } 498 let pe: *CssElement = (ctx.boxel as *u8 + (p as nx_size) * (NX_CSS_ELEMENT_BYTES as nx_size)) as *CssElement 499 if pe.tag_len != 2 { return 0 } 500 let p0: i64 = ctx.cascade_src[pe.tag_off] & 0xff 501 let p1: i64 = ctx.cascade_src[pe.tag_off+1] & 0xff 502 // <ul> -> disc bullet 503 if p0 == 117 { if p1 == 108 { // "ul" 504 let col: *CssColor = (sys_mmap(NX_CSS_COLOR_BYTES as nx_size)) as *CssColor 505 col.r = 64; col.g = 64; col.b = 64; col.a = 255 506 return nx_paint_solid_rect(ctx.fb, b.x - 8, b.y + 4, 3, 3, col) 507 } } 508 // <ol> -> 1-based number "N." right-aligned in the ol's left padding 509 if p0 == 111 { if p1 == 108 { // "ol" 510 let pb: *LayoutBox = (ctx.tree.boxes as *u8 + (p as nx_size) * (NX_LAYOUT_BOX_BYTES as nx_size)) as *LayoutBox 511 var n: i64 = 1 512 var sib: i64 = pb.first_child_idx 513 var stop: i64 = 0 514 var guard: i64 = 0 515 while stop == 0 { 516 if sib < 0 { stop = 1 } 517 else { if sib == box_idx { stop = 1 } 518 else { if guard >= NX_MAGIC_100000 { stop = 1 } 519 else { 520 let se: *CssElement = (ctx.boxel as *u8 + (sib as nx_size) * (NX_CSS_ELEMENT_BYTES as nx_size)) as *CssElement 521 if se.tag_len == 2 { if (ctx.cascade_src[se.tag_off]&0xff)==108 { if (ctx.cascade_src[se.tag_off+1]&0xff)==105 { n = n + 1 } } } 522 let sb: *LayoutBox = (ctx.tree.boxes as *u8 + (sib as nx_size) * (NX_LAYOUT_BOX_BYTES as nx_size)) as *LayoutBox 523 sib = sb.next_sibling_idx 524 guard = guard + 1 525 } } } 526 } 527 let tmp: *u8 = sys_mmap(24) 528 var tk: i64 = 0 529 var v: i64 = n 530 if v == 0 { tmp[0] = 48 as u8; tk = 1 } 531 while v > 0 { tmp[tk] = (48 + (v % 10)) as u8; v = v / 10; tk = tk + 1 } 532 let nb: *u8 = sys_mmap(24) 533 var j: i64 = 0 534 while j < tk { nb[j] = tmp[tk-1-j]; j = j + 1 } 535 nb[tk] = 46 as u8 // '.' 536 let slen: i64 = tk + 1 537 let col2: *CssColor = (sys_mmap(NX_CSS_COLOR_BYTES as nx_size)) as *CssColor 538 col2.r = 0; col2.g = 0; col2.b = 0; col2.a = 255 539 return nx_paint_text(ctx.fb, b.x - slen * 6 - 3, b.y + 2, nb, slen, col2) 540 } } 541 return 0 542} 543 544// Find the computed `border` SHORTHAND decl for box_idx (reverse cascade = winner first), or 0. The CSS 545// parser's multi-token capture stores the whole value span ("1px solid #cc0000") at val_off/val_len. 546func _paint_border_lookup(ctx: *PaintCtx, box_idx: i64) -> *CssComputedDecl { 547 let nm: *u8 = "border\x00" as *u8 548 var i: i64 = ctx.n_computed - 1 549 while i >= 0 { 550 let cd: *CssComputedDecl = (ctx.computed as *u8 + (i as nx_size) * (NX_CSS_COMPUTED_DECL_BYTES as nx_size)) as *CssComputedDecl 551 if cd.element_idx == box_idx { 552 if cd.prop_len == 6 { 553 var same: i64 = 1 554 var k: i64 = 0 555 while k < 6 { if (ctx.cascade_src[cd.prop_off + k] & 0xff) != (nm[k] & 0xff) { same = 0; k = 6 } else { k = k + 1 } } 556 if same == 1 { return cd } 557 } 558 } 559 i = i - 1 560 } 561 return 0 as *CssComputedDecl 562} 563func _hex_is(c: i64) -> i64 { 564 if c >= 48 { if c <= 57 { return 1 } } 565 if c >= 97 { if c <= 102 { return 1 } } 566 if c >= 65 { if c <= 70 { return 1 } } 567 return 0 568} 569// Paint a box's `border` shorthand as 4 solid edges INSIDE the box rect: width = leading integer of the 570// value; color = the first #hex token (default black if none); style assumed solid. Returns pixels 571// written (0 if no border). border-box REFLOW (border growing the box) + non-solid styles = later rungs; 572// this paints the edges within b's existing rect, composing nx_paint_solid_rect + nx_css_color_decode. 573func _paint_box_border(ctx: *PaintCtx, box_idx: i64) -> i64 { 574 let decl: *CssComputedDecl = _paint_border_lookup(ctx, box_idx) 575 if decl == (0 as *CssComputedDecl) { return 0 } 576 let src: *u8 = ctx.cascade_src 577 let end: i64 = decl.val_off + decl.val_len 578 var bw: i64 = 0 579 var any: i64 = 0 580 var p: i64 = decl.val_off 581 var rd: i64 = 1 582 while rd == 1 { if p < end { let c: i64 = src[p]&0xff; if c>=48 { if c<=57 { bw=bw*10+(c-48); any=1; p=p+1 } else { rd=0 } } else { rd=0 } } else { rd=0 } } 583 if any == 0 { bw = 1 } 584 if bw <= 0 { return 0 } 585 let color: *CssColor = (sys_mmap(NX_CSS_COLOR_BYTES as nx_size)) as *CssColor 586 color.r = 0; color.g = 0; color.b = 0; color.a = 255 587 var hp: i64 = decl.val_off 588 var fnd: i64 = 0 589 while fnd == 0 { 590 if hp >= end { fnd = 2 } 591 else { if (src[hp]&0xff)==35 { fnd = 1 } else { hp = hp + 1 } } 592 } 593 if fnd == 1 { 594 var hl: i64 = 0 595 var q: i64 = hp + 1 596 while q < end { if _hex_is(src[q]&0xff)==1 { hl = hl + 1; q = q + 1 } else { q = end } } 597 nx_css_color_decode(src, hp + 1, hl, color) 598 } else { 599 // No hex token -> scan the value's space-separated WORDS for a NAMED color ("1px solid gray"). 600 var wp: i64 = decl.val_off 601 var done: i64 = 0 602 while done == 0 { 603 var ws: i64 = 1 604 while ws == 1 { if wp >= end { ws = 0 } else { let c: i64 = src[wp]&0xff; if c==32 { wp=wp+1 } else { if c==9 { wp=wp+1 } else { ws=0 } } } } 605 if wp >= end { done = 1 } 606 else { 607 var we: i64 = wp 608 var sd: i64 = 0 609 while sd == 0 { if we >= end { sd = 1 } else { let c: i64 = src[we]&0xff; if c==32 { sd=1 } else { if c==9 { sd=1 } else { we=we+1 } } } } 610 if nx_css_color_named(src, wp, we - wp, color) == 1 { done = 1 } else { wp = we } 611 } 612 } 613 } 614 let b: *LayoutBox = (ctx.tree.boxes as *u8 + (box_idx as nx_size) * (NX_LAYOUT_BOX_BYTES as nx_size)) as *LayoutBox 615 if b.w <= 0 { return 0 } 616 if b.h <= 0 { return 0 } 617 var bwid: i64 = bw 618 if bwid > b.h { bwid = b.h } 619 if bwid > b.w { bwid = b.w } 620 var n: i64 = 0 621 n = n + nx_paint_solid_rect(ctx.fb, b.x, b.y, b.w, bwid, color) 622 n = n + nx_paint_solid_rect(ctx.fb, b.x, b.y + b.h - bwid, b.w, bwid, color) 623 n = n + nx_paint_solid_rect(ctx.fb, b.x, b.y, bwid, b.h, color) 624 n = n + nx_paint_solid_rect(ctx.fb, b.x + b.w - bwid, b.y, bwid, b.h, color) 625 return n 626} 627 628// Walk the layout tree in document order (DFS pre-order) starting at 629// `root_idx`, painting each box's background-color into the 630// framebuffer. Returns total pixels written across the whole tree. 631// 632// Document-order walk = ancestors paint first, then descendants 633// (children paint OVER parents). This implements the document-order 634// stacking semantics in lieu of z-index (queued for Phase 4b). 635func nx_paint_walk_layout(ctx: *PaintCtx, root_idx: i64) -> i64 { 636 if root_idx < 0 { return 0 } 637 if root_idx >= ctx.tree.count { return 0 } 638 if _paint_display_none(ctx, root_idx) == 1 { return 0 } // display:none -> skip box + whole subtree 639 640 let bg_written: i64 = _paint_box_background(ctx, root_idx) 641 let bd_written: i64 = _paint_box_border(ctx, root_idx) 642 let tx_written: i64 = _paint_box_text(ctx, root_idx) 643 let mk_written: i64 = _paint_li_marker(ctx, root_idx) 644 let written: i64 = bg_written + bd_written + tx_written + mk_written 645 646 let b: *LayoutBox = (ctx.tree.boxes as *u8 + (root_idx as nx_size) * (NX_LAYOUT_BOX_BYTES as nx_size)) as *LayoutBox 647 var total: i64 = written 648 var child_idx: i64 = b.first_child_idx 649 var safety: i64 = 0 650 let MAX_ITER: i64 = NX_MAGIC_65536 651 var keep: i64 = 1 652 while keep == 1 { 653 if safety >= MAX_ITER { keep = 0 } 654 else { 655 safety = safety + 1 656 if child_idx < 0 { keep = 0 } 657 else { 658 total = total + nx_paint_walk_layout(ctx, child_idx) 659 let c: *LayoutBox = (ctx.tree.boxes as *u8 + (child_idx as nx_size) * (NX_LAYOUT_BOX_BYTES as nx_size)) as *LayoutBox 660 child_idx = c.next_sibling_idx 661 } 662 } 663 } 664 return total 665}