code wiki / (root) / nx_css_color_decode.nx

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}