nx_paint_box_borders.nx source
↩ module page · 338 lines · 15595 B
1// nx_paint_box_borders.nx -- paint a box's 4 borders (top / right /
2// bottom / left) into a Framebuffer. Phase 4 third primitive of
3// NISHI_BROWSER_ROADMAP. Composes nx_css_apply (cascade) +
4// nx_css_dimension_to_px (border-width) + nx_css_color_decode
5// (border-color) + nx_paint_solid_rect (actual pixel fill).
6//
7// Per W3C CSS Backgrounds and Borders Module Level 3, each border
8// has independent width + color + style. This primitive supports
9// solid borders only (style: solid). Other border-styles (dashed,
10// dotted, double) are Phase 4b queued.
11//
12// Geometry: borders are painted OUTSIDE the box's content/padding
13// rect in the CSS box model, but since nx_layout_block currently
14// uses content-box width (b.w = content_w) and mixed-axis height
15// (b.h = padding_top + content_h + padding_bottom), border placement
16// here treats b as the BORDER box (top edge at b.y - border_top_w,
17// etc.). Until nx_layout_block grows border-box semantics in
18// Phase 3b, the simpler convention is borders drawn INSIDE b's
19// rectangle along each edge.
20//
21// What it does NOT handle yet (Phase 4b queued, explicit in header):
22// - border-style values other than solid (dashed, dotted, double,
23// groove, ridge, inset, outset)
24// - border-radius (rounded corners)
25// - border-image
26// - corner blending where two adjacent borders meet (currently
27// the right/bottom borders paint OVER the top/left at corners --
28// last-wins, source-order; W3C spec wants mitred joins)
29// - independent border-width directional shorthand parsing
30// (border-width: 2px 1px 3px 1px) -- only longhands today
31//
32// Per cardinal feedback-honest-perf-verdict-no-aspirational-claims:
33// gap list is EXACT.
34//
35// genealogy_id: w3c_css_backgrounds_borders_module_level_3 +
36// substrate_browser_phase_4_box_borders
37// lineage_id: nishi_browser_paint_box_borders_v1
38
39import "nx_syscalls.nx"
40import "nx_css_tokenize.nx"
41import "nx_css_apply.nx"
42import "nx_css_color_decode.nx"
43import "nx_css_number_parse.nx"
44import "nx_css_dimension_to_px.nx"
45import "nx_layout_box.nx"
46import "nx_paint_solid_rect.nx"
47
48// Property-name table -- 8 entries (4 widths + 4 colors). Each
49// off/len pair points into the table.src buffer which the
50// nx_border_prop_table_init function populates.
51struct BorderPropTable {
52 src: *u8,
53 bt_width_off: i64,
54 bt_width_len: i64,
55 br_width_off: i64,
56 br_width_len: i64,
57 bb_width_off: i64,
58 bb_width_len: i64,
59 bl_width_off: i64,
60 bl_width_len: i64,
61 bt_color_off: i64,
62 bt_color_len: i64,
63 br_color_off: i64,
64 br_color_len: i64,
65 bb_color_off: i64,
66 bb_color_len: i64,
67 bl_color_off: i64,
68 bl_color_len: i64
69}
70
71const NX_BORDER_PROP_TABLE_BYTES: i64 = 136
72
73// ---- helpers ----
74
75func _border_put_byte(buf: *u8, i: i64, v: i64) -> i64 {
76 buf[i] = v as u8
77 return 0
78}
79
80// Initialize the property-name table. Caller supplies a 192-byte
81// buffer; this function writes the 8 canonical property names and
82// records their offsets/lengths.
83func nx_border_prop_table_init(t: *BorderPropTable, buf: *u8) -> i64 {
84 t.src = buf
85
86 // "border-top-width" 16 bytes at offset 0
87 let bt_w_str: *u8 = buf
88 _border_put_byte(bt_w_str, 0, 98) _border_put_byte(bt_w_str, 1, 111)
89 _border_put_byte(bt_w_str, 2, 114) _border_put_byte(bt_w_str, 3, 100)
90 _border_put_byte(bt_w_str, 4, 101) _border_put_byte(bt_w_str, 5, 114)
91 _border_put_byte(bt_w_str, 6, 45) _border_put_byte(bt_w_str, 7, 116)
92 _border_put_byte(bt_w_str, 8, 111) _border_put_byte(bt_w_str, 9, 112)
93 _border_put_byte(bt_w_str, 10, 45) _border_put_byte(bt_w_str, 11, 119)
94 _border_put_byte(bt_w_str, 12, 105) _border_put_byte(bt_w_str, 13, 100)
95 _border_put_byte(bt_w_str, 14, 116) _border_put_byte(bt_w_str, 15, 104)
96 t.bt_width_off = 0
97 t.bt_width_len = 16
98
99 // "border-right-width" 18 bytes at offset 16
100 let off1: i64 = 16
101 _border_put_byte(buf, off1 + 0, 98) _border_put_byte(buf, off1 + 1, 111)
102 _border_put_byte(buf, off1 + 2, 114) _border_put_byte(buf, off1 + 3, 100)
103 _border_put_byte(buf, off1 + 4, 101) _border_put_byte(buf, off1 + 5, 114)
104 _border_put_byte(buf, off1 + 6, 45) _border_put_byte(buf, off1 + 7, 114)
105 _border_put_byte(buf, off1 + 8, 105) _border_put_byte(buf, off1 + 9, 103)
106 _border_put_byte(buf, off1 + 10, 104) _border_put_byte(buf, off1 + 11, 116)
107 _border_put_byte(buf, off1 + 12, 45) _border_put_byte(buf, off1 + 13, 119)
108 _border_put_byte(buf, off1 + 14, 105) _border_put_byte(buf, off1 + 15, 100)
109 _border_put_byte(buf, off1 + 16, 116) _border_put_byte(buf, off1 + 17, 104)
110 t.br_width_off = off1
111 t.br_width_len = 18
112
113 // "border-bottom-width" 19 bytes at offset 34
114 let off2: i64 = 34
115 _border_put_byte(buf, off2 + 0, 98) _border_put_byte(buf, off2 + 1, 111)
116 _border_put_byte(buf, off2 + 2, 114) _border_put_byte(buf, off2 + 3, 100)
117 _border_put_byte(buf, off2 + 4, 101) _border_put_byte(buf, off2 + 5, 114)
118 _border_put_byte(buf, off2 + 6, 45) _border_put_byte(buf, off2 + 7, 98)
119 _border_put_byte(buf, off2 + 8, 111) _border_put_byte(buf, off2 + 9, 116)
120 _border_put_byte(buf, off2 + 10, 116) _border_put_byte(buf, off2 + 11, 111)
121 _border_put_byte(buf, off2 + 12, 109) _border_put_byte(buf, off2 + 13, 45)
122 _border_put_byte(buf, off2 + 14, 119) _border_put_byte(buf, off2 + 15, 105)
123 _border_put_byte(buf, off2 + 16, 100) _border_put_byte(buf, off2 + 17, 116)
124 _border_put_byte(buf, off2 + 18, 104)
125 t.bb_width_off = off2
126 t.bb_width_len = 19
127
128 // "border-left-width" 17 bytes at offset 53
129 let off3: i64 = 53
130 _border_put_byte(buf, off3 + 0, 98) _border_put_byte(buf, off3 + 1, 111)
131 _border_put_byte(buf, off3 + 2, 114) _border_put_byte(buf, off3 + 3, 100)
132 _border_put_byte(buf, off3 + 4, 101) _border_put_byte(buf, off3 + 5, 114)
133 _border_put_byte(buf, off3 + 6, 45) _border_put_byte(buf, off3 + 7, 108)
134 _border_put_byte(buf, off3 + 8, 101) _border_put_byte(buf, off3 + 9, 102)
135 _border_put_byte(buf, off3 + 10, 116) _border_put_byte(buf, off3 + 11, 45)
136 _border_put_byte(buf, off3 + 12, 119) _border_put_byte(buf, off3 + 13, 105)
137 _border_put_byte(buf, off3 + 14, 100) _border_put_byte(buf, off3 + 15, 116)
138 _border_put_byte(buf, off3 + 16, 104)
139 t.bl_width_off = off3
140 t.bl_width_len = 17
141
142 // "border-top-color" 16 bytes at offset 70
143 let off4: i64 = 70
144 _border_put_byte(buf, off4 + 0, 98) _border_put_byte(buf, off4 + 1, 111)
145 _border_put_byte(buf, off4 + 2, 114) _border_put_byte(buf, off4 + 3, 100)
146 _border_put_byte(buf, off4 + 4, 101) _border_put_byte(buf, off4 + 5, 114)
147 _border_put_byte(buf, off4 + 6, 45) _border_put_byte(buf, off4 + 7, 116)
148 _border_put_byte(buf, off4 + 8, 111) _border_put_byte(buf, off4 + 9, 112)
149 _border_put_byte(buf, off4 + 10, 45) _border_put_byte(buf, off4 + 11, 99)
150 _border_put_byte(buf, off4 + 12, 111) _border_put_byte(buf, off4 + 13, 108)
151 _border_put_byte(buf, off4 + 14, 111) _border_put_byte(buf, off4 + 15, 114)
152 t.bt_color_off = off4
153 t.bt_color_len = 16
154
155 // "border-right-color" 18 bytes at offset 86
156 let off5: i64 = 86
157 _border_put_byte(buf, off5 + 0, 98) _border_put_byte(buf, off5 + 1, 111)
158 _border_put_byte(buf, off5 + 2, 114) _border_put_byte(buf, off5 + 3, 100)
159 _border_put_byte(buf, off5 + 4, 101) _border_put_byte(buf, off5 + 5, 114)
160 _border_put_byte(buf, off5 + 6, 45) _border_put_byte(buf, off5 + 7, 114)
161 _border_put_byte(buf, off5 + 8, 105) _border_put_byte(buf, off5 + 9, 103)
162 _border_put_byte(buf, off5 + 10, 104) _border_put_byte(buf, off5 + 11, 116)
163 _border_put_byte(buf, off5 + 12, 45) _border_put_byte(buf, off5 + 13, 99)
164 _border_put_byte(buf, off5 + 14, 111) _border_put_byte(buf, off5 + 15, 108)
165 _border_put_byte(buf, off5 + 16, 111) _border_put_byte(buf, off5 + 17, 114)
166 t.br_color_off = off5
167 t.br_color_len = 18
168
169 // "border-bottom-color" 19 bytes at offset 104
170 let off6: i64 = 104
171 _border_put_byte(buf, off6 + 0, 98) _border_put_byte(buf, off6 + 1, 111)
172 _border_put_byte(buf, off6 + 2, 114) _border_put_byte(buf, off6 + 3, 100)
173 _border_put_byte(buf, off6 + 4, 101) _border_put_byte(buf, off6 + 5, 114)
174 _border_put_byte(buf, off6 + 6, 45) _border_put_byte(buf, off6 + 7, 98)
175 _border_put_byte(buf, off6 + 8, 111) _border_put_byte(buf, off6 + 9, 116)
176 _border_put_byte(buf, off6 + 10, 116) _border_put_byte(buf, off6 + 11, 111)
177 _border_put_byte(buf, off6 + 12, 109) _border_put_byte(buf, off6 + 13, 45)
178 _border_put_byte(buf, off6 + 14, 99) _border_put_byte(buf, off6 + 15, 111)
179 _border_put_byte(buf, off6 + 16, 108) _border_put_byte(buf, off6 + 17, 111)
180 _border_put_byte(buf, off6 + 18, 114)
181 t.bb_color_off = off6
182 t.bb_color_len = 19
183
184 // "border-left-color" 17 bytes at offset 123
185 let off7: i64 = 123
186 _border_put_byte(buf, off7 + 0, 98) _border_put_byte(buf, off7 + 1, 111)
187 _border_put_byte(buf, off7 + 2, 114) _border_put_byte(buf, off7 + 3, 100)
188 _border_put_byte(buf, off7 + 4, 101) _border_put_byte(buf, off7 + 5, 114)
189 _border_put_byte(buf, off7 + 6, 45) _border_put_byte(buf, off7 + 7, 108)
190 _border_put_byte(buf, off7 + 8, 101) _border_put_byte(buf, off7 + 9, 102)
191 _border_put_byte(buf, off7 + 10, 116) _border_put_byte(buf, off7 + 11, 45)
192 _border_put_byte(buf, off7 + 12, 99) _border_put_byte(buf, off7 + 13, 111)
193 _border_put_byte(buf, off7 + 14, 108) _border_put_byte(buf, off7 + 15, 111)
194 _border_put_byte(buf, off7 + 16, 114)
195 t.bl_color_off = off7
196 t.bl_color_len = 17
197
198 return 0
199}
200
201// Byte-equal compare across two source buffers.
202func _border_bytes_equal(a_src: *u8, a_off: i64, a_len: i64,
203 b_src: *u8, b_off: i64, b_len: i64) -> i64 {
204 if a_len != b_len { return 0 }
205 var i: i64 = 0
206 while i < a_len {
207 let ca: i64 = (a_src[a_off + i] as i64) & 255
208 let cb: i64 = (b_src[b_off + i] as i64) & 255
209 if ca != cb { return 0 }
210 i = i + 1
211 }
212 return 1
213}
214
215// Look up the last CssComputedDecl matching (element_idx, property)
216// across two source buffers.
217func _border_lookup(computed: *CssComputedDecl, n_computed: i64,
218 cascade_src: *u8,
219 table_src: *u8, prop_off: i64, prop_len: i64,
220 element_idx: i64) -> *CssComputedDecl {
221 var i: i64 = n_computed - 1
222 while i >= 0 {
223 let cd: *CssComputedDecl = (computed as *u8 + (i as nx_size) * (NX_CSS_COMPUTED_DECL_BYTES as nx_size)) as *CssComputedDecl
224 if cd.element_idx == element_idx {
225 if _border_bytes_equal(cascade_src, cd.prop_off, cd.prop_len,
226 table_src, prop_off, prop_len) == 1 {
227 return cd
228 }
229 }
230 i = i - 1
231 }
232 return 0 as *CssComputedDecl
233}
234
235// Read a border-width property: cascade lookup -> DIMENSION decode.
236// Returns the integer px width, or 0 if not declared / wrong kind.
237func _border_width(computed: *CssComputedDecl, n_computed: i64,
238 cascade_src: *u8, table_src: *u8,
239 prop_off: i64, prop_len: i64,
240 element_idx: i64,
241 resolve_ctx: *CssResolveCtx) -> i64 {
242 let cd: *CssComputedDecl = _border_lookup(computed, n_computed,
243 cascade_src, table_src, prop_off, prop_len, element_idx)
244 if cd == (0 as *CssComputedDecl) { return 0 }
245 if cd.val_kind != NX_CSS_VAL_DIMENSION { return 0 }
246 let px: i64 = nx_css_dimension_to_px(cascade_src,
247 cd.val_off, cd.val_len, cd.unit_off, cd.unit_len, resolve_ctx)
248 if px < 0 { return 0 }
249 return px
250}
251
252// Read a border-color property: cascade lookup -> HEXCOLOR decode.
253// Returns 1 on success and fills *out; 0 if not declared / wrong kind.
254func _border_color(computed: *CssComputedDecl, n_computed: i64,
255 cascade_src: *u8, table_src: *u8,
256 prop_off: i64, prop_len: i64,
257 element_idx: i64,
258 out: *CssColor) -> i64 {
259 let cd: *CssComputedDecl = _border_lookup(computed, n_computed,
260 cascade_src, table_src, prop_off, prop_len, element_idx)
261 if cd == (0 as *CssComputedDecl) { return 0 }
262 if cd.val_kind != NX_CSS_VAL_HEXCOLOR { return 0 }
263 return nx_css_color_decode(cascade_src, cd.val_off, cd.val_len, out)
264}
265
266// ---- public API ----
267
268// Paint one box's 4 borders. Returns total pixels written. Each side
269// only paints if BOTH its width AND its color are declared.
270//
271// Paint order: top, left, right, bottom -- right/bottom corners are
272// last-wins (W3C mitred-join semantics deferred to Phase 4b).
273func nx_paint_box_borders(fb: *Framebuffer,
274 box: *LayoutBox,
275 computed: *CssComputedDecl, n_computed: i64,
276 cascade_src: *u8,
277 table: *BorderPropTable,
278 element_idx: i64,
279 resolve_ctx: *CssResolveCtx) -> i64 {
280 let bt_w: i64 = _border_width(computed, n_computed, cascade_src,
281 table.src, table.bt_width_off, table.bt_width_len, element_idx, resolve_ctx)
282 let br_w: i64 = _border_width(computed, n_computed, cascade_src,
283 table.src, table.br_width_off, table.br_width_len, element_idx, resolve_ctx)
284 let bb_w: i64 = _border_width(computed, n_computed, cascade_src,
285 table.src, table.bb_width_off, table.bb_width_len, element_idx, resolve_ctx)
286 let bl_w: i64 = _border_width(computed, n_computed, cascade_src,
287 table.src, table.bl_width_off, table.bl_width_len, element_idx, resolve_ctx)
288
289 let bt_color: *CssColor = (sys_mmap(NX_CSS_COLOR_BYTES as nx_size)) as *CssColor
290 let br_color: *CssColor = (sys_mmap(NX_CSS_COLOR_BYTES as nx_size)) as *CssColor
291 let bb_color: *CssColor = (sys_mmap(NX_CSS_COLOR_BYTES as nx_size)) as *CssColor
292 let bl_color: *CssColor = (sys_mmap(NX_CSS_COLOR_BYTES as nx_size)) as *CssColor
293
294 let bt_ok: i64 = _border_color(computed, n_computed, cascade_src,
295 table.src, table.bt_color_off, table.bt_color_len, element_idx, bt_color)
296 let br_ok: i64 = _border_color(computed, n_computed, cascade_src,
297 table.src, table.br_color_off, table.br_color_len, element_idx, br_color)
298 let bb_ok: i64 = _border_color(computed, n_computed, cascade_src,
299 table.src, table.bb_color_off, table.bb_color_len, element_idx, bb_color)
300 let bl_ok: i64 = _border_color(computed, n_computed, cascade_src,
301 table.src, table.bl_color_off, table.bl_color_len, element_idx, bl_color)
302
303 var total: i64 = 0
304
305 // Top border (full width, height = bt_w).
306 if bt_w > 0 {
307 if bt_ok == 1 {
308 total = total + nx_paint_solid_rect(fb, box.x, box.y,
309 box.w, bt_w, bt_color)
310 }
311 }
312
313 // Left border (full height, width = bl_w).
314 if bl_w > 0 {
315 if bl_ok == 1 {
316 total = total + nx_paint_solid_rect(fb, box.x, box.y,
317 bl_w, box.h, bl_color)
318 }
319 }
320
321 // Right border (right edge inset by br_w).
322 if br_w > 0 {
323 if br_ok == 1 {
324 total = total + nx_paint_solid_rect(fb,
325 box.x + box.w - br_w, box.y, br_w, box.h, br_color)
326 }
327 }
328
329 // Bottom border (bottom edge inset by bb_w).
330 if bb_w > 0 {
331 if bb_ok == 1 {
332 total = total + nx_paint_solid_rect(fb,
333 box.x, box.y + box.h - bb_w, box.w, bb_w, bb_color)
334 }
335 }
336
337 return total
338}