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}