nx_css_color_decode.nx source
↩ module page · 291 lines · 12515 B
1// nx_css_color_decode.nx -- decode a CSS HEXCOLOR byte range into
2// concrete (r, g, b, a) integers in [0, 255]. Phase 2.5 primitive
3// bridging the value-as-bytes representation (from nx_css_tokenize /
4// nx_css_parse / nx_css_apply) to the integer color consumed by the
5// eventual nx_layout + nx_paint pipeline.
6//
7// Per CSS spec section 4.4 (CSS Color Module Level 4), valid hex
8// color shapes are:
9// #RGB 3-char form: each digit doubled (#abc -> #aabbcc)
10// #RGBA 4-char form: same as above, last digit -> alpha
11// #RRGGBB 6-char form: full RGB
12// #RRGGBBAA 8-char form: full RGB + alpha
13//
14// All hex digits case-insensitive (0-9, a-f, A-F).
15//
16// What it does NOT handle (Phase 2b improvements):
17// - named colors (red, blue, ...) -- handled by separate
18// nx_css_color_named primitive (queued)
19// - rgb() / rgba() / hsl() / hsla() functional notation
20// - color-mix() / color() CSS-4 functions
21// - currentColor / system colors / transparent keyword
22//
23// genealogy_id: w3c_css_color_module_level_4 +
24// substrate_browser_phase_2_color_decode
25// lineage_id: nishi_browser_css_color_decode_v1
26//
27// nx_safety_envelope:
28// intended_use: "CSS hex color decode -- layout/paint input"
29// sil_target: SIL1
30// evidence: [W3C_CSS_Color_module_canonical_basis,
31// sealed_4_supported_lengths,
32// pure_function_no_allocation]
33// verdict: NOT_YET_EVALUATED
34
35import "nx_syscalls.nx"
36
37// Decoded color. Caller-supplied; primitive fills in-place. Alpha
38// defaults to 255 (opaque) for #RGB and #RRGGBB shapes.
39struct CssColor {
40 r: i64,
41 g: i64,
42 b: i64,
43 a: i64
44}
45
46const NX_CSS_COLOR_BYTES: i64 = 32
47
48// ---- helpers ----
49
50// Decode one hex digit to 0..15. Returns -1 on invalid byte.
51func _css_color_hex_digit(c: i64) -> i64 {
52 if c >= 48 { // '0'
53 if c <= 57 { return c - 48 }
54 }
55 if c >= 97 { // 'a'
56 if c <= 102 { return c - 87 }
57 }
58 if c >= 65 { // 'A'
59 if c <= 70 { return c - 55 }
60 }
61 return -1
62}
63
64// Decode two hex digits into one byte 0..255. Returns -1 if either
65// digit is invalid.
66func _css_color_hex_pair(c_hi: i64, c_lo: i64) -> i64 {
67 let hi: i64 = _css_color_hex_digit(c_hi)
68 if hi < 0 { return -1 }
69 let lo: i64 = _css_color_hex_digit(c_lo)
70 if lo < 0 { return -1 }
71 return (hi << 4) | lo
72}
73
74// ---- named colors ----
75
76// case-insensitive match of src[off..off+len) against the NUL-terminated lowercase literal `lit`.
77func _ccn_eq(src: *u8, off: i64, len: i64, lit: *u8) -> i64 {
78 var ll: i64 = 0
79 while lit[ll] != (0 as u8) { ll = ll + 1 }
80 if len != ll { return 0 }
81 var i: i64 = 0
82 while i < len {
83 var c: i64 = (src[off+i] as i64) & 255
84 if c >= 65 { if c <= 90 { c = c + 32 } } // ASCII lowercase
85 if c != ((lit[i] as i64) & 255) { return 0 }
86 i = i + 1
87 }
88 return 1
89}
90func _ccn_set(out: *CssColor, r: i64, g: i64, b: i64, a: i64) -> i64 { out.r=r; out.g=g; out.b=b; out.a=a; return 1 }
91
92// Decode a CSS NAMED color (the 16 basic + common extended + transparent) at src[off..off+len) into out.
93// Returns 1 if recognized, 0 otherwise (caller falls back to default ink). Covers the vast majority of
94// real-page named-color usage; the full 147-name CSS table is a later rung. Per CSS Color Module L4 §6.1.
95func nx_css_color_named(src: *u8, off: i64, len: i64, out: *CssColor) -> i64 {
96 if len < 3 { return 0 }
97 if _ccn_eq(src, off, len, "black\x00" as *u8)==1 { return _ccn_set(out, 0,0,0,255) }
98 if _ccn_eq(src, off, len, "white\x00" as *u8)==1 { return _ccn_set(out, 255,255,255,255) }
99 if _ccn_eq(src, off, len, "red\x00" as *u8)==1 { return _ccn_set(out, 255,0,0,255) }
100 if _ccn_eq(src, off, len, "green\x00" as *u8)==1 { return _ccn_set(out, 0,128,0,255) }
101 if _ccn_eq(src, off, len, "blue\x00" as *u8)==1 { return _ccn_set(out, 0,0,255,255) }
102 if _ccn_eq(src, off, len, "yellow\x00" as *u8)==1 { return _ccn_set(out, 255,255,0,255) }
103 if _ccn_eq(src, off, len, "gray\x00" as *u8)==1 { return _ccn_set(out, 128,128,128,255) }
104 if _ccn_eq(src, off, len, "grey\x00" as *u8)==1 { return _ccn_set(out, 128,128,128,255) }
105 if _ccn_eq(src, off, len, "silver\x00" as *u8)==1 { return _ccn_set(out, 192,192,192,255) }
106 if _ccn_eq(src, off, len, "maroon\x00" as *u8)==1 { return _ccn_set(out, 128,0,0,255) }
107 if _ccn_eq(src, off, len, "navy\x00" as *u8)==1 { return _ccn_set(out, 0,0,128,255) }
108 if _ccn_eq(src, off, len, "purple\x00" as *u8)==1 { return _ccn_set(out, 128,0,128,255) }
109 if _ccn_eq(src, off, len, "teal\x00" as *u8)==1 { return _ccn_set(out, 0,128,128,255) }
110 if _ccn_eq(src, off, len, "olive\x00" as *u8)==1 { return _ccn_set(out, 128,128,0,255) }
111 if _ccn_eq(src, off, len, "lime\x00" as *u8)==1 { return _ccn_set(out, 0,255,0,255) }
112 if _ccn_eq(src, off, len, "aqua\x00" as *u8)==1 { return _ccn_set(out, 0,255,255,255) }
113 if _ccn_eq(src, off, len, "cyan\x00" as *u8)==1 { return _ccn_set(out, 0,255,255,255) }
114 if _ccn_eq(src, off, len, "fuchsia\x00" as *u8)==1 { return _ccn_set(out, 255,0,255,255) }
115 if _ccn_eq(src, off, len, "magenta\x00" as *u8)==1 { return _ccn_set(out, 255,0,255,255) }
116 if _ccn_eq(src, off, len, "orange\x00" as *u8)==1 { return _ccn_set(out, 255,165,0,255) }
117 if _ccn_eq(src, off, len, "pink\x00" as *u8)==1 { return _ccn_set(out, 255,192,203,255) }
118 if _ccn_eq(src, off, len, "brown\x00" as *u8)==1 { return _ccn_set(out, 165,42,42,255) }
119 if _ccn_eq(src, off, len, "transparent\x00" as *u8)==1 { return _ccn_set(out, 255,255,255,0) }
120 return 0
121}
122
123// ---- rgb() / rgba() functional notation ----
124
125// Skip non-digits (spaces/commas) then parse a uint at src[*pp..end); stop at ')' or end. ok[0]=1 if a
126// digit was found. Advances pp[0] past the digits. No `break` (flag-controlled, matches the codebase).
127func _ccr_next_uint(src: *u8, end: i64, pp: *i64, ok: *i64) -> i64 {
128 var p: i64 = pp[0]
129 var found: i64 = 0
130 var stop: i64 = 0
131 while stop == 0 {
132 if p >= end { stop = 1 }
133 else {
134 let c: i64 = src[p] & 0xff
135 if c >= 48 { if c <= 57 { found = 1; stop = 1 } else { p = p + 1 } }
136 else { if c == 41 { stop = 1 } else { p = p + 1 } } // ')' stops
137 }
138 }
139 if found == 0 { ok[0] = 0; pp[0] = p; return 0 }
140 var v: i64 = 0
141 var rd: i64 = 1
142 while rd == 1 {
143 if p < end { let c: i64 = src[p]&0xff; if c >= 48 { if c <= 57 { v = v*10+(c-48); p = p+1 } else { rd = 0 } } else { rd = 0 } }
144 else { rd = 0 }
145 }
146 ok[0] = 1; pp[0] = p; return v
147}
148// Parse a CSS alpha (0..1, possibly fractional) at src[p0..end) into 0..255. "1"->255, "0"->0,
149// "0.5"->127, "0.8"->204 (up to 3 fractional digits).
150func _ccr_alpha(src: *u8, p0: i64, end: i64) -> i64 {
151 var p: i64 = p0
152 var sk: i64 = 1
153 while sk == 1 { if p < end { if (src[p]&0xff)==32 { p = p+1 } else { sk = 0 } } else { sk = 0 } }
154 var ip: i64 = 0
155 var rd: i64 = 1
156 while rd == 1 { if p < end { let c: i64=src[p]&0xff; if c>=48 { if c<=57 { ip=ip*10+(c-48); p=p+1 } else { rd=0 } } else { rd=0 } } else { rd=0 } }
157 if ip >= 1 { return 255 }
158 if p >= end { return 0 }
159 if (src[p]&0xff) != 46 { return 0 } // no '.', so alpha == 0 -> transparent
160 p = p + 1
161 var num: i64 = 0; var den: i64 = 1; var cnt: i64 = 0
162 var fr: i64 = 1
163 while fr == 1 {
164 if cnt >= 3 { fr = 0 }
165 else { if p < end { let c: i64=src[p]&0xff; if c>=48 { if c<=57 { num=num*10+(c-48); den=den*10; p=p+1; cnt=cnt+1 } else { fr=0 } } else { fr=0 } } else { fr=0 } }
166 }
167 if den <= 1 { return 0 }
168 return (num * 255) / den
169}
170// Decode `rgb(r, g, b)` / `rgba(r, g, b, a)` at src[off..off+len) into out. Returns 1 on success, 0 if it
171// isn't an rgb()/rgba() value. Per CSS Color Module L4 §5.1.
172func nx_css_color_rgb_func(src: *u8, off: i64, len: i64, out: *CssColor) -> i64 {
173 let end: i64 = off + len
174 var lp: i64 = 0 - 1
175 var i: i64 = off
176 while i < end { if (src[i]&0xff)==40 { lp = i; i = end } else { i = i + 1 } } // find '('
177 if lp < 0 { return 0 }
178 if lp - off < 3 { return 0 }
179 var l0: i64 = src[off]&0xff; if l0>=65 { if l0<=90 { l0=l0+32 } }
180 var l1: i64 = src[off+1]&0xff; if l1>=65 { if l1<=90 { l1=l1+32 } }
181 var l2: i64 = src[off+2]&0xff; if l2>=65 { if l2<=90 { l2=l2+32 } }
182 if l0 != 114 { return 0 } // 'r'
183 if l1 != 103 { return 0 } // 'g'
184 if l2 != 98 { return 0 } // 'b'
185 let pp: *i64 = sys_mmap(8) as *i64; pp[0] = lp + 1
186 let ok: *i64 = sys_mmap(8) as *i64
187 let r: i64 = _ccr_next_uint(src, end, pp, ok); if ok[0]==0 { return 0 }
188 let g: i64 = _ccr_next_uint(src, end, pp, ok); if ok[0]==0 { return 0 }
189 let b: i64 = _ccr_next_uint(src, end, pp, ok); if ok[0]==0 { return 0 }
190 out.r = r; out.g = g; out.b = b; out.a = 255
191 // optional alpha: find the comma after the blue component, then parse the fractional alpha
192 var p2: i64 = pp[0]
193 var hc: i64 = 0
194 var sc: i64 = 0
195 while sc == 0 { if p2 >= end { sc = 1 } else { let c: i64=src[p2]&0xff; if c==44 { hc=1; p2=p2+1; sc=1 } else { if c==41 { sc=1 } else { p2=p2+1 } } } }
196 if hc == 1 { out.a = _ccr_alpha(src, p2, end) }
197 return 1
198}
199
200// ---- public API ----
201
202// Decode the hex body bytes (without leading '#') at src[hex_off
203// .. hex_off + hex_len) into the caller-supplied CssColor struct.
204// Returns 1 on success, 0 on invalid length or invalid hex digit.
205//
206// Supported hex_len values: 3, 4, 6, 8. Anything else fails.
207func nx_css_color_decode(src: *u8, hex_off: i64, hex_len: i64,
208 out: *CssColor) -> i64 {
209 if hex_len == 3 {
210 let c0: i64 = (src[hex_off] as i64) & 255
211 let c1: i64 = (src[hex_off + 1] as i64) & 255
212 let c2: i64 = (src[hex_off + 2] as i64) & 255
213 let d0: i64 = _css_color_hex_digit(c0)
214 let d1: i64 = _css_color_hex_digit(c1)
215 let d2: i64 = _css_color_hex_digit(c2)
216 if d0 < 0 { return 0 }
217 if d1 < 0 { return 0 }
218 if d2 < 0 { return 0 }
219 out.r = (d0 << 4) | d0
220 out.g = (d1 << 4) | d1
221 out.b = (d2 << 4) | d2
222 out.a = 255
223 return 1
224 }
225
226 if hex_len == 4 {
227 let c0: i64 = (src[hex_off] as i64) & 255
228 let c1: i64 = (src[hex_off + 1] as i64) & 255
229 let c2: i64 = (src[hex_off + 2] as i64) & 255
230 let c3: i64 = (src[hex_off + 3] as i64) & 255
231 let d0: i64 = _css_color_hex_digit(c0)
232 let d1: i64 = _css_color_hex_digit(c1)
233 let d2: i64 = _css_color_hex_digit(c2)
234 let d3: i64 = _css_color_hex_digit(c3)
235 if d0 < 0 { return 0 }
236 if d1 < 0 { return 0 }
237 if d2 < 0 { return 0 }
238 if d3 < 0 { return 0 }
239 out.r = (d0 << 4) | d0
240 out.g = (d1 << 4) | d1
241 out.b = (d2 << 4) | d2
242 out.a = (d3 << 4) | d3
243 return 1
244 }
245
246 if hex_len == 6 {
247 let r: i64 = _css_color_hex_pair(
248 (src[hex_off] as i64) & 255,
249 (src[hex_off + 1] as i64) & 255)
250 let g: i64 = _css_color_hex_pair(
251 (src[hex_off + 2] as i64) & 255,
252 (src[hex_off + 3] as i64) & 255)
253 let b: i64 = _css_color_hex_pair(
254 (src[hex_off + 4] as i64) & 255,
255 (src[hex_off + 5] as i64) & 255)
256 if r < 0 { return 0 }
257 if g < 0 { return 0 }
258 if b < 0 { return 0 }
259 out.r = r
260 out.g = g
261 out.b = b
262 out.a = 255
263 return 1
264 }
265
266 if hex_len == 8 {
267 let r: i64 = _css_color_hex_pair(
268 (src[hex_off] as i64) & 255,
269 (src[hex_off + 1] as i64) & 255)
270 let g: i64 = _css_color_hex_pair(
271 (src[hex_off + 2] as i64) & 255,
272 (src[hex_off + 3] as i64) & 255)
273 let b: i64 = _css_color_hex_pair(
274 (src[hex_off + 4] as i64) & 255,
275 (src[hex_off + 5] as i64) & 255)
276 let a: i64 = _css_color_hex_pair(
277 (src[hex_off + 6] as i64) & 255,
278 (src[hex_off + 7] as i64) & 255)
279 if r < 0 { return 0 }
280 if g < 0 { return 0 }
281 if b < 0 { return 0 }
282 if a < 0 { return 0 }
283 out.r = r
284 out.g = g
285 out.b = b
286 out.a = a
287 return 1
288 }
289
290 return 0
291}