code wiki / (root) / nx_layout_box.nx

nx_layout_box.nx source

↩ module page · 241 lines · 8448 B

1// nx_layout_box.nx -- LayoutBox tree foundation for the Nishi 2// browser. Phase 3 first primitive of docs/NISHI_BROWSER_ROADMAP.md, 3// composes the Phase 2 CSS pipeline (nx_css_apply + value decoders) 4// with the Phase 1 DOM tree (nx_html_tokenizer + nx_dom). 5// 6// This primitive owns ONLY the box-tree data structure -- no 7// layout math here. nx_layout_block (next primitive, queued) walks 8// this tree top-down in normal-flow order to assign concrete 9// (x, y, w, h) pixels. nx_layout_inline (queued after that) handles 10// line wrapping for inline boxes. 11// 12// LayoutBox kinds (sealed enum): 13// BLOCK block-level box: <div>, <p>, <h1>...<h6>, <header>, etc. 14// INLINE inline box: <span>, <a>, <em>, etc. 15// INLINE_BLOCK inline-block: like inline externally, block internally 16// TEXT terminal text run -- no children; content is bytes 17// ANONYMOUS anonymous block wrapper inserted by spec when 18// a block contains both block and inline children 19// 20// Tree layout (caller-owns-memory contract): 21// The caller passes a `boxes: *LayoutBox` array + `max_boxes`. 22// Boxes are appended in document order; parent/child/sibling links 23// are indices into this array (-1 = none). Document root is at 24// index 0 by convention. 25// 26// What this primitive does NOT do (queued for nx_layout_block): 27// - assign x, y, w, h pixels (left as 0 until layout pass) 28// - compute margin / padding / border from computed_style 29// - resolve `width: auto` / `height: auto` 30// - line-box generation for inline children 31// - positioned-box layout (relative / absolute / fixed) 32// - float / clear 33// 34// Per cardinal feedback-honest-perf-verdict-no-aspirational-claims: 35// gap list is EXACT in this header. 36// 37// genealogy_id: w3c_css_box_model_module_level_3 + 38// substrate_browser_phase_3_box_tree 39// lineage_id: nishi_browser_layout_box_v1 40// 41// nx_safety_envelope: 42// intended_use: "Layout box tree -- browser layout substrate" 43// sil_target: SIL1 44// evidence: [W3C_CSS_Box_Model_canonical_basis, 45// sealed_kind_enum, 46// bounded_array_arena, 47// no_internal_allocation] 48// verdict: NOT_YET_EVALUATED 49 50import "nx_syscalls.nx" 51const NX_MAGIC_65536: i64 = 65536 52 53// Sealed box-kind enum. 54const NX_LAYOUT_BOX_UNKNOWN: i64 = 0 55const NX_LAYOUT_BOX_BLOCK: i64 = 1 56const NX_LAYOUT_BOX_INLINE: i64 = 2 57const NX_LAYOUT_BOX_INLINE_BLOCK: i64 = 3 58const NX_LAYOUT_BOX_TEXT: i64 = 4 59const NX_LAYOUT_BOX_ANONYMOUS: i64 = 5 60const NX_LAYOUT_BOX_N: i64 = 6 61 62// One layout box. 63// 64// Tree links: parent_idx, first_child_idx, next_sibling_idx are 65// indices into the caller-supplied LayoutBox array. -1 = no link. 66// 67// Geometry: x, y are box origin in viewport coords; w, h are border- 68// box dimensions (caller's choice -- box-sizing: border-box is the 69// substrate default per cardinal feedback-honest-perf-verdict; the 70// content-box variant is Phase 3b queued). 71// 72// Style: computed_decl_first / computed_decl_count point into a flat 73// CssComputedDecl array (from nx_css_apply). 0/0 = no style. 74// 75// Source: source_node_idx points into the caller-supplied DOM (-1 = 76// anonymous / synthetic box not backed by a DOM node). 77struct LayoutBox { 78 kind: i64, 79 parent_idx: i64, 80 first_child_idx: i64, 81 next_sibling_idx: i64, 82 x: i64, 83 y: i64, 84 w: i64, 85 h: i64, 86 computed_decl_first: i64, 87 computed_decl_count: i64, 88 source_node_idx: i64, 89 text_off: i64, // for TEXT boxes: source-buffer offset 90 text_len: i64 // for TEXT boxes: byte length 91} 92 93const NX_LAYOUT_BOX_BYTES: i64 = 104 94 95// Arena bundling the array + count so signatures stay small. 96struct LayoutTree { 97 boxes: *LayoutBox, 98 max_boxes: i64, 99 count: i64 100} 101 102const NX_LAYOUT_TREE_BYTES: i64 = 24 103 104// ---- helpers ---- 105 106// Get pointer to box at index. Caller is responsible for bounds. 107func _layout_box_at(tree: *LayoutTree, idx: i64) -> *LayoutBox { 108 return (tree.boxes as *u8 + (idx as nx_size) * (NX_LAYOUT_BOX_BYTES as nx_size)) as *LayoutBox 109} 110 111// ---- public API ---- 112 113// Initialize a LayoutTree wrapping a caller-supplied boxes array. 114// box_count starts at 0; caller appends via nx_layout_box_append. 115func nx_layout_tree_init(tree: *LayoutTree, boxes: *LayoutBox, 116 max_boxes: i64) -> i64 { 117 tree.boxes = boxes 118 tree.max_boxes = max_boxes 119 tree.count = 0 120 return 0 121} 122 123// Append a new box, returning its index. Returns -1 if the arena 124// is full. Initializes all fields to safe defaults (-1 for links, 125// 0 for geometry, kind = caller-supplied). 126// 127// The new box is NOT attached as a child of any other box; caller 128// uses nx_layout_box_attach_child or nx_layout_box_attach_sibling 129// to wire it into the tree. 130func nx_layout_box_append(tree: *LayoutTree, kind: i64, 131 source_node_idx: i64) -> i64 { 132 if tree.count >= tree.max_boxes { return -1 } 133 let idx: i64 = tree.count 134 let b: *LayoutBox = _layout_box_at(tree, idx) 135 b.kind = kind 136 b.parent_idx = -1 137 b.first_child_idx = -1 138 b.next_sibling_idx = -1 139 b.x = 0 140 b.y = 0 141 b.w = 0 142 b.h = 0 143 b.computed_decl_first = 0 144 b.computed_decl_count = 0 145 b.source_node_idx = source_node_idx 146 b.text_off = 0 147 b.text_len = 0 148 tree.count = tree.count + 1 149 return idx 150} 151 152// Attach `child_idx` as the LAST child of `parent_idx`. Walks the 153// parent's existing children to find the tail. O(N) per attach; 154// acceptable at andelinwest.com scale. Returns 0 on success, -1 155// on invalid index. 156func nx_layout_box_attach_child(tree: *LayoutTree, 157 parent_idx: i64, child_idx: i64) -> i64 { 158 if parent_idx < 0 { return -1 } 159 if parent_idx >= tree.count { return -1 } 160 if child_idx < 0 { return -1 } 161 if child_idx >= tree.count { return -1 } 162 if parent_idx == child_idx { return -1 } 163 164 let parent: *LayoutBox = _layout_box_at(tree, parent_idx) 165 let child: *LayoutBox = _layout_box_at(tree, child_idx) 166 child.parent_idx = parent_idx 167 child.next_sibling_idx = -1 168 169 if parent.first_child_idx < 0 { 170 parent.first_child_idx = child_idx 171 return 0 172 } 173 174 // Walk the sibling chain to find the tail. 175 var cur_idx: i64 = parent.first_child_idx 176 var safety: i64 = 0 177 let MAX_ITER: i64 = NX_MAGIC_65536 178 var keep: i64 = 1 179 while keep == 1 { 180 if safety >= MAX_ITER { return -1 } 181 safety = safety + 1 182 let cur: *LayoutBox = _layout_box_at(tree, cur_idx) 183 if cur.next_sibling_idx < 0 { 184 cur.next_sibling_idx = child_idx 185 keep = 0 186 } else { 187 cur_idx = cur.next_sibling_idx 188 } 189 } 190 return 0 191} 192 193// Set the computed-style slice for a box. (`first` and `count` are 194// indices into the caller-supplied CssComputedDecl array from 195// nx_css_apply.) 196func nx_layout_box_set_style(tree: *LayoutTree, idx: i64, 197 first: i64, count: i64) -> i64 { 198 if idx < 0 { return -1 } 199 if idx >= tree.count { return -1 } 200 let b: *LayoutBox = _layout_box_at(tree, idx) 201 b.computed_decl_first = first 202 b.computed_decl_count = count 203 return 0 204} 205 206// Set the text body for a TEXT box. 207func nx_layout_box_set_text(tree: *LayoutTree, idx: i64, 208 text_off: i64, text_len: i64) -> i64 { 209 if idx < 0 { return -1 } 210 if idx >= tree.count { return -1 } 211 let b: *LayoutBox = _layout_box_at(tree, idx) 212 b.text_off = text_off 213 b.text_len = text_len 214 return 0 215} 216 217// Count children of a box. O(N) child walk. Returns count, or -1 if 218// the parent index is invalid. 219func nx_layout_box_child_count(tree: *LayoutTree, parent_idx: i64) -> i64 { 220 if parent_idx < 0 { return -1 } 221 if parent_idx >= tree.count { return -1 } 222 let parent: *LayoutBox = _layout_box_at(tree, parent_idx) 223 if parent.first_child_idx < 0 { return 0 } 224 225 var n: i64 = 0 226 var cur_idx: i64 = parent.first_child_idx 227 var safety: i64 = 0 228 let MAX_ITER: i64 = NX_MAGIC_65536 229 var keep: i64 = 1 230 while keep == 1 { 231 if safety >= MAX_ITER { return n } 232 safety = safety + 1 233 if cur_idx < 0 { keep = 0 } 234 else { 235 n = n + 1 236 let cur: *LayoutBox = _layout_box_at(tree, cur_idx) 237 cur_idx = cur.next_sibling_idx 238 } 239 } 240 return n 241}