code wiki / (root) / nx_css_dimension_to_px.nx

nx_css_dimension_to_px.nx source

↩ module page · 164 lines · 6167 B

1// nx_css_dimension_to_px.nx -- convert a CSS dimension (NUMBER body 2// + UNIT body) into absolute integer pixels. Phase 2.5 bridge 3// primitive completing the value-decoder layer between Phase 2 (CSS 4// apply emits VAL_DIMENSION as byte ranges) and Phase 3 layout. 5// 6// Supports the 4 most-common CSS length units (per W3C CSS Values 7// Module Level 4 table of distance units): 8// px absolute device pixels (direct) 9// em relative to parent font-size in px 10// rem relative to root font-size in px 11// % percentage of parent dimension (caller-supplied) 12// 13// Math is integer-only Q3 (thousandths) per the nx_css_number_parse 14// contract; this primitive ROUNDS toward zero to get whole pixels 15// for the layout pipeline (sub-pixel positioning is Phase 4b 16// queued). 17// 18// What it does NOT handle (Phase 2b improvements): 19// - pt / pc / in / cm / mm / Q absolute length units 20// - vw / vh / vmin / vmax viewport-relative units 21// - ex / ch font-relative units (require font metrics from 22// nx_text_render) 23// - fr fractional flex units (require flexbox; Phase 3b) 24// - calc() / clamp() / min() / max() functional values 25// - negative dimensions (margin: -10px) -- caller computes sign 26// 27// Per cardinal feedback-honest-perf-verdict-no-aspirational-claims: 28// gap list is EXACT. 29// 30// genealogy_id: w3c_css_values_module_level_4_distance_units 31// lineage_id: nishi_browser_css_dimension_to_px_v1 32// 33// nx_safety_envelope: 34// intended_use: "CSS dimension -> integer px -- layout input" 35// sil_target: SIL1 36// evidence: [W3C_CSS_Values_canonical_basis, 37// bounded_4_supported_units, 38// integer_Q3_arithmetic_no_FP] 39// verdict: NOT_YET_EVALUATED 40 41import "nx_syscalls.nx" 42import "nx_css_number_parse.nx" 43const NX_MAGIC_100000: i64 = 100000 44 45// Sealed unit kind enum. 46const NX_CSS_UNIT_UNKNOWN: i64 = 0 47const NX_CSS_UNIT_PX: i64 = 1 48const NX_CSS_UNIT_EM: i64 = 2 49const NX_CSS_UNIT_REM: i64 = 3 50const NX_CSS_UNIT_PERCENT: i64 = 4 51const NX_CSS_UNIT_N: i64 = 5 52 53// Resolution context the caller supplies. Sizes carried in absolute 54// integer pixels (NOT thousandths -- this is the "computed style" 55// frame of reference). PERCENT calculations rely on 56// parent_dimension; if the parent dimension is unknown (e.g., 57// height: 100% of an auto-sized parent), caller passes -1 and we 58// fall back to returning 0 px. 59struct CssResolveCtx { 60 root_font_size_px: i64, // typically 16 61 parent_font_size_px: i64, // typically inherited 62 parent_dimension_px: i64 // for % calculations; -1 = unknown 63} 64 65const NX_CSS_RESOLVE_CTX_BYTES: i64 = 24 66 67const NX_CSS_DIMENSION_PARSE_ERROR: i64 = 9223372036854775807 // sentinel matches nx_css_number_parse 68 69// ---- helpers ---- 70 71// Compare two-byte-ish unit identifiers case-insensitively. Returns 72// 1 if `len == match_len` and src[off..off+len] equals `m` (always 73// lower-case ASCII). 74func _css_unit_match2(src: *u8, off: i64, len: i64, 75 m0: i64, m1: i64) -> i64 { 76 if len != 2 { return 0 } 77 let c0: i64 = (src[off] as i64) & 255 78 let c1: i64 = (src[off + 1] as i64) & 255 79 var l0: i64 = c0 80 if l0 >= 65 { 81 if l0 <= 90 { l0 = l0 + 32 } // upper -> lower 82 } 83 var l1: i64 = c1 84 if l1 >= 65 { 85 if l1 <= 90 { l1 = l1 + 32 } 86 } 87 if l0 != m0 { return 0 } 88 if l1 != m1 { return 0 } 89 return 1 90} 91 92func _css_unit_match3(src: *u8, off: i64, len: i64, 93 m0: i64, m1: i64, m2: i64) -> i64 { 94 if len != 3 { return 0 } 95 let c0: i64 = (src[off] as i64) & 255 96 let c1: i64 = (src[off + 1] as i64) & 255 97 let c2: i64 = (src[off + 2] as i64) & 255 98 var l0: i64 = c0 99 if l0 >= 65 { if l0 <= 90 { l0 = l0 + 32 } } 100 var l1: i64 = c1 101 if l1 >= 65 { if l1 <= 90 { l1 = l1 + 32 } } 102 var l2: i64 = c2 103 if l2 >= 65 { if l2 <= 90 { l2 = l2 + 32 } } 104 if l0 != m0 { return 0 } 105 if l1 != m1 { return 0 } 106 if l2 != m2 { return 0 } 107 return 1 108} 109 110// ---- public API ---- 111 112// Classify a unit body into the sealed enum. Returns NX_CSS_UNIT_UNKNOWN 113// for unrecognized units. 114func nx_css_unit_classify(src: *u8, off: i64, len: i64) -> i64 { 115 if len == 1 { 116 let c: i64 = (src[off] as i64) & 255 117 if c == 37 { return NX_CSS_UNIT_PERCENT } // '%' 118 return NX_CSS_UNIT_UNKNOWN 119 } 120 // Two-letter units: px, em. 121 if _css_unit_match2(src, off, len, 112, 120) == 1 { return NX_CSS_UNIT_PX } // "px" 122 if _css_unit_match2(src, off, len, 101, 109) == 1 { return NX_CSS_UNIT_EM } // "em" 123 // Three-letter units: rem. 124 if _css_unit_match3(src, off, len, 114, 101, 109) == 1 { return NX_CSS_UNIT_REM } // "rem" 125 return NX_CSS_UNIT_UNKNOWN 126} 127 128// Resolve a parsed dimension (number_off/len + unit_off/len) to 129// integer pixels using the provided context. Returns 130// NX_CSS_DIMENSION_PARSE_ERROR on bad number or unknown unit. 131// 132// Rounding: integer-division of Q3 thousandths by 1000 (truncation 133// toward zero -- Phase 4b will refine to sub-pixel for paint). 134func nx_css_dimension_to_px(src: *u8, 135 num_off: i64, num_len: i64, 136 unit_off: i64, unit_len: i64, 137 ctx: *CssResolveCtx) -> i64 { 138 let num_q3: i64 = nx_css_number_parse(src, num_off, num_len) 139 if num_q3 == NX_CSS_NUMBER_PARSE_ERROR { return NX_CSS_DIMENSION_PARSE_ERROR } 140 141 let unit: i64 = nx_css_unit_classify(src, unit_off, unit_len) 142 143 if unit == NX_CSS_UNIT_PX { 144 // value_thousandths / 1000 -- integer px. 145 return num_q3 / 1000 146 } 147 148 if unit == NX_CSS_UNIT_EM { 149 return (num_q3 * ctx.parent_font_size_px) / 1000 150 } 151 152 if unit == NX_CSS_UNIT_REM { 153 return (num_q3 * ctx.root_font_size_px) / 1000 154 } 155 156 if unit == NX_CSS_UNIT_PERCENT { 157 if ctx.parent_dimension_px < 0 { return 0 } 158 // num_q3 is "percent value * 1000"; final value = num_q3 * 159 // parent / 100 / 1000 = num_q3 * parent / 100000. 160 return (num_q3 * ctx.parent_dimension_px) / NX_MAGIC_100000 161 } 162 163 return NX_CSS_DIMENSION_PARSE_ERROR 164}