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}