code wiki / _hdl_build / nx_css_vars.nx
nx_css_vars.nx source
↩ module page · 242 lines · 12590 B
1// nx_css_vars.nx -- CSS custom-property (var()) resolution for the SOVEREIGN render engine, so Nishi Browser
2// + the reader + NishiOS render the fleet-standard --nx-color-* design tokens NATIVELY (operator 2026-07-10:
3// "build nishi os and nishi browser first, not into javascript until last-mile on 3rd-party"). The native
4// engine parsed only #hex/rgb(); every fleet-bar page is built on var(--token), so without this our own
5// browser renders them colorless while Waterfox looks perfect -- the exact inversion to kill.
6// PURE LIB (no main). One entry: cx_expand(inp,n,out,outcap) -> a byte copy of the CSS with every var(--x)
7// substituted by its :root definition (FIRST-wins, so base/light tokens beat later dark overrides; recursive
8// so a token defined as var() of another resolves; var(--x,fallback) supported; nested rgb() parens balanced).
9// GUARANTEED no-op when the CSS has no var() -> existing pages/gates stay byte-identical. Wired as the final
10// pass of rh_filter_media (nx_render_html) so all three native surfaces get it from one point.
11// Consumers: nx_render_html (via rh_filter_media) + gate nx_css_vars_gate. license_tier: ORIGINAL
12import "nx_syscalls.nx"
13const K_MAGIC_1024: i64 = 1024
14
15func cx_ch(s: *u8, i: i64) -> i64 { return s[i] as i64 }
16func cx_is_ws(c: i64) -> i64 { if c == 32 { return 1 } if c == 9 { return 1 } if c == 10 { return 1 } if c == 13 { return 1 } return 0 }
17func cx_name_eq(s: *u8, o1: i64, l1: i64, o2: i64, l2: i64) -> i64 {
18 if l1 != l2 { return 0 }
19 var i: i64 = 0
20 while i < l1 { if cx_ch(s, o1 + i) != cx_ch(s, o2 + i) { return 0 } i = i + 1 }
21 return 1
22}
23// index of custom prop named s[no..no+nl) in the table (first match), or -1
24func cx_find(s: *u8, noff: *i64, nlen: *i64, cnt: i64, no: i64, nl: i64) -> i64 {
25 var i: i64 = 0
26 while i < cnt { if cx_name_eq(s, noff[i], nlen[i], no, nl) == 1 { return i } i = i + 1 }
27 return 0 - 1
28}
29// collect --name:value declarations into parallel arrays (FIRST-wins). returns count.
30func cx_collect(s: *u8, n: i64, noff: *i64, nlen: *i64, voff: *i64, vlen: *i64, cap: i64) -> i64 {
31 var cnt: i64 = 0
32 var i: i64 = 0
33 var propstart: i64 = 0
34 var inblock: i64 = 0
35 while i < n {
36 let c: i64 = cx_ch(s, i)
37 var handled: i64 = 0
38 if c == 123 { inblock = 1; propstart = 1; i = i + 1; handled = 1 }
39 if handled == 0 { if c == 125 { inblock = 0; propstart = 0; i = i + 1; handled = 1 } }
40 if handled == 0 { if c == 59 { propstart = 1; i = i + 1; handled = 1 } }
41 if handled == 0 { if cx_is_ws(c) == 1 { i = i + 1; handled = 1 } }
42 if handled == 0 {
43 var isvar: i64 = 0
44 if inblock == 1 { if propstart == 1 { if c == 45 { if i + 1 < n { if cx_ch(s, i + 1) == 45 { isvar = 1 } } } } }
45 if isvar == 1 {
46 let ns: i64 = i
47 // name runs until ':' (or aborts at ';'/'}' for a malformed decl)
48 var d: i64 = i
49 while d < n { let dc: i64 = cx_ch(s, d); if dc == 58 { break } if dc == 59 { break } if dc == 125 { break } d = d + 1 }
50 var newi: i64 = d
51 if d < n { if cx_ch(s, d) == 58 {
52 let ne: i64 = d
53 var vv: i64 = d + 1
54 while vv < n { if cx_is_ws(cx_ch(s, vv)) == 1 { vv = vv + 1 } else { break } }
55 let vs: i64 = vv
56 while vv < n { let vc: i64 = cx_ch(s, vv); if vc == 59 { break } if vc == 125 { break } vv = vv + 1 }
57 var ve: i64 = vv
58 while ve > vs { if cx_is_ws(cx_ch(s, ve - 1)) == 1 { ve = ve - 1 } else { break } }
59 if cnt < cap { if cx_find(s, noff, nlen, cnt, ns, ne - ns) < 0 {
60 noff[cnt] = ns; nlen[cnt] = ne - ns; voff[cnt] = vs; vlen[cnt] = ve - vs; cnt = cnt + 1
61 } }
62 newi = vv
63 } }
64 i = newi
65 propstart = 0
66 } else {
67 propstart = 0
68 i = i + 1
69 }
70 }
71 }
72 return cnt
73}
74// resolve a var name span -> emit its definition's value (forward-calls cx_emit_span, defined below --
75// mutual recursion; nx_cc resolves forward calls). returns 1 if the name was defined, else 0.
76func cx_emit_name(s: *u8, noff: *i64, nlen: *i64, voff: *i64, vlen: *i64, cnt: i64, no: i64, nl: i64, out: *u8, op: *i64, outcap: i64, depth: i64) -> i64 {
77 if depth > 16 { return 1 }
78 let idx: i64 = cx_find(s, noff, nlen, cnt, no, nl)
79 if idx < 0 { return 0 }
80 cx_emit_span(s, noff, nlen, voff, vlen, cnt, voff[idx], vlen[idx], out, op, outcap, depth + 1)
81 return 1
82}
83func cx_emit_span(s: *u8, noff: *i64, nlen: *i64, voff: *i64, vlen: *i64, cnt: i64, so: i64, sl: i64, out: *u8, op: *i64, outcap: i64, depth: i64) -> i64 {
84 var i: i64 = so
85 let end: i64 = so + sl
86 while i < end {
87 var isvar: i64 = 0
88 if i + 4 <= end { if cx_ch(s, i) == 118 { if cx_ch(s, i + 1) == 97 { if cx_ch(s, i + 2) == 114 { if cx_ch(s, i + 3) == 40 { isvar = 1 } } } } }
89 if isvar == 1 {
90 // balance parens from the '(' at i+3 to find the matching ')'
91 var pdepth: i64 = 0
92 var p: i64 = i + 3
93 var close: i64 = 0 - 1
94 while p < end { let pc: i64 = cx_ch(s, p); if pc == 40 { pdepth = pdepth + 1 } if pc == 41 { pdepth = pdepth - 1; if pdepth == 0 { close = p; p = end } } if p < end { p = p + 1 } }
95 // parse the --name up to ',' or the closing ')' (skip leading ws after "var(")
96 var j: i64 = i + 4
97 while j < end { if cx_is_ws(cx_ch(s, j)) == 1 { j = j + 1 } else { break } }
98 let no: i64 = j
99 var ncur: i64 = j
100 while ncur < end { let nc: i64 = cx_ch(s, ncur); if nc == 44 { break } if nc == 41 { break } ncur = ncur + 1 }
101 var ne: i64 = ncur
102 while ne > no { if cx_is_ws(cx_ch(s, ne - 1)) == 1 { ne = ne - 1 } else { break } }
103 var fbs: i64 = 0 - 1
104 if ncur < end { if cx_ch(s, ncur) == 44 {
105 var f: i64 = ncur + 1
106 while f < end { if cx_is_ws(cx_ch(s, f)) == 1 { f = f + 1 } else { break } }
107 fbs = f
108 } }
109 let got: i64 = cx_emit_name(s, noff, nlen, voff, vlen, cnt, no, ne - no, out, op, outcap, depth)
110 if got == 0 { if fbs >= 0 { if close >= 0 { cx_emit_span(s, noff, nlen, voff, vlen, cnt, fbs, close - fbs, out, op, outcap, depth + 1) } } }
111 if close < 0 { i = end } else { i = close + 1 }
112 } else {
113 let oo: i64 = op[0]
114 if oo < outcap - 1 { out[oo] = s[i]; op[0] = oo + 1 }
115 i = i + 1
116 }
117 }
118 return 0
119}
120// ---- clamp() resolution: the fleet uses font-size:clamp(MIN,PREFERRED,MAX) for fluid type; the native engine
121// can't evaluate it, so type sizing falls back. Resolve at the render viewport width to a concrete <px>. ----
122func cx_isdig(c: i64) -> i64 { if c >= 48 { if c <= 57 { return 1 } } return 0 }
123// parse one term (number[.frac]<unit>) at s[i]; return px value in MILLI-px at viewport vw; ni[0]=index after.
124// units: px/unitless=x1 · vw/vh = x(vw/100) · rem/em = x16. (vh has no height ctx here -> treated as vw.)
125func clamp_term(s: *u8, i: i64, b: i64, vw: i64, ni: *i64) -> i64 {
126 var whole: i64 = 0
127 var j: i64 = i
128 while j < b { if cx_isdig(cx_ch(s, j)) == 1 { whole = whole * 10 + (cx_ch(s, j) - 48); j = j + 1 } else { break } }
129 var frac: i64 = 0
130 var fdiv: i64 = 1
131 if j < b { if cx_ch(s, j) == 46 {
132 j = j + 1
133 while j < b { if cx_isdig(cx_ch(s, j)) == 1 { if fdiv < 1000 { frac = frac * 10 + (cx_ch(s, j) - 48); fdiv = fdiv * 10 } j = j + 1 } else { break } }
134 } }
135 let nummilli: i64 = whole * 1000 + (frac * 1000) / fdiv
136 let us: i64 = j
137 var je: i64 = j
138 while je < b { let uc2: i64 = cx_ch(s, je); if uc2 >= 97 { if uc2 <= 122 { je = je + 1 } else { break } } else { break } }
139 var pxmilli: i64 = nummilli
140 if je >= us + 2 {
141 let u0: i64 = cx_ch(s, us)
142 let u1: i64 = cx_ch(s, us + 1)
143 if u0 == 118 { pxmilli = nummilli * vw / 100 } // v (vw/vh)
144 if u0 == 114 { if u1 == 101 { pxmilli = nummilli * 16 } } // rem
145 if u0 == 101 { if u1 == 109 { pxmilli = nummilli * 16 } } // em
146 }
147 ni[0] = je
148 return pxmilli
149}
150// evaluate a clamp arg span s[a,b) (terms joined by + / -) to MILLI-px at vw.
151func clamp_eval(s: *u8, a: i64, b: i64, vw: i64) -> i64 {
152 var milli: i64 = 0
153 var sign: i64 = 1
154 var i: i64 = a
155 let ni: *i64 = sys_mmap(8) as *i64
156 while i < b {
157 let c: i64 = cx_ch(s, i)
158 if cx_is_ws(c) == 1 { i = i + 1 } else {
159 if c == 43 { sign = 1; i = i + 1 } else {
160 if c == 45 { sign = 0 - 1; i = i + 1 } else {
161 let start: i64 = i
162 let t: i64 = clamp_term(s, i, b, vw, ni)
163 milli = milli + sign * t
164 sign = 1
165 if ni[0] > start { i = ni[0] } else { i = i + 1 }
166 } } }
167 }
168 return milli
169}
170func cl_emit_int(out: *u8, op: i64, cap: i64, v: i64) -> i64 {
171 var o: i64 = op
172 var m: i64 = v
173 let t: *u8 = sys_mmap(24)
174 var k: i64 = 0
175 if m == 0 { t[0] = 48 as u8; k = 1 }
176 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
177 var z: i64 = 0
178 while z < k { if o < cap - 1 { out[o] = t[k - 1 - z]; o = o + 1 } z = z + 1 }
179 return o
180}
181// THE clamp entry: replace every clamp(MIN,PREF,MAX) in inp[0..n) with the computed <px> at vw. No-op (byte
182// copy) when there is no clamp(). Malformed / non-3-arg clamp is copied verbatim (fail-safe).
183func clamp_resolve(inp: *u8, n: i64, out: *u8, outcap: i64, vw: i64) -> i64 {
184 var i: i64 = 0
185 var op: i64 = 0
186 while i < n {
187 var isclamp: i64 = 0
188 if i + 6 <= n { if cx_ch(inp, i) == 99 { if cx_ch(inp, i+1) == 108 { if cx_ch(inp, i+2) == 97 { if cx_ch(inp, i+3) == 109 { if cx_ch(inp, i+4) == 112 { if cx_ch(inp, i+5) == 40 { isclamp = 1 } } } } } } }
189 if isclamp == 1 {
190 var depth: i64 = 0
191 var pp: i64 = i + 5
192 var close: i64 = 0 - 1
193 while pp < n { let pc: i64 = cx_ch(inp, pp); if pc == 40 { depth = depth + 1 } if pc == 41 { depth = depth - 1; if depth == 0 { close = pp; pp = n } } if pp < n { pp = pp + 1 } }
194 if close < 0 { i = n } else {
195 let a0: i64 = i + 6
196 var d: i64 = 0
197 var c1: i64 = 0 - 1
198 var c2: i64 = 0 - 1
199 var q: i64 = a0
200 while q < close { let qc: i64 = cx_ch(inp, q); if qc == 40 { d = d + 1 } if qc == 41 { d = d - 1 } if qc == 44 { if d == 0 { if c1 < 0 { c1 = q } else { if c2 < 0 { c2 = q } } } } q = q + 1 }
201 var did: i64 = 0
202 if c1 >= 0 { if c2 >= 0 {
203 let mn: i64 = clamp_eval(inp, a0, c1, vw)
204 let pr: i64 = clamp_eval(inp, c1 + 1, c2, vw)
205 let mx: i64 = clamp_eval(inp, c2 + 1, close, vw)
206 var used: i64 = pr
207 if used > mx { used = mx }
208 if used < mn { used = mn }
209 var pxv: i64 = (used + 500) / 1000
210 if pxv < 1 { pxv = 1 }
211 op = cl_emit_int(out, op, outcap, pxv)
212 if op < outcap - 2 { out[op] = 112 as u8; op = op + 1; out[op] = 120 as u8; op = op + 1 }
213 i = close + 1
214 did = 1
215 } }
216 if did == 0 {
217 var cp: i64 = i
218 while cp <= close { if op < outcap - 1 { out[op] = inp[cp]; op = op + 1 } cp = cp + 1 }
219 i = close + 1
220 }
221 }
222 } else {
223 if op < outcap - 1 { out[op] = inp[i]; op = op + 1 }
224 i = i + 1
225 }
226 }
227 return op
228}
229// THE entry: expand every var(--x) in inp[0..n) into out; returns output length. No-op (byte copy) when
230// the CSS has no var(). out must be sized generously (expansion grows the text).
231func cx_expand(inp: *u8, n: i64, out: *u8, outcap: i64) -> i64 {
232 let CAP: i64 = K_MAGIC_1024
233 let noff: *i64 = sys_mmap(CAP * 8) as *i64
234 let nlen: *i64 = sys_mmap(CAP * 8) as *i64
235 let voff: *i64 = sys_mmap(CAP * 8) as *i64
236 let vlen: *i64 = sys_mmap(CAP * 8) as *i64
237 let cnt: i64 = cx_collect(inp, n, noff, nlen, voff, vlen, CAP)
238 let op: *i64 = sys_mmap(8) as *i64
239 op[0] = 0
240 cx_emit_span(inp, noff, nlen, voff, vlen, cnt, 0, n, out, op, outcap, 0)
241 return op[0]
242}