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}