1
//! Builder function to convert CssPropertyCache → CompactLayoutCache.
2
//!
3
//! Called once after restyle + apply_ua_css + compute_inherited_values.
4
//! Uses typed getters on CssPropertyCache (which cascade through all sources)
5
//! to resolve each property for the "normal" state (all pseudo-states = false).
6

            
7
use crate::dom::{NodeData, NodeId};
8
use crate::prop_cache::CssPropertyCache;
9

            
10
use crate::styled_dom::StyledNodeState;
11
use azul_css::compact_cache::*;
12
use azul_css::css::CssPropertyValue;
13
use azul_css::props::property::CssProperty;
14
use azul_css::props::basic::length::SizeMetric;
15
use azul_css::props::layout::dimensions::{LayoutHeight, LayoutWidth};
16
use azul_css::props::layout::flex::LayoutFlexBasis;
17
use azul_css::props::layout::position::LayoutZIndex;
18
use core::hash::{Hash, Hasher};
19
use alloc::vec::Vec;
20
use crate::hash::DefaultHasher;
21

            
22
impl CssPropertyCache {
23
    /// Build a CompactLayoutCache from the current property cache state.
24
    ///
25
    /// Must be called after `restyle()`, `apply_ua_css()`, and `compute_inherited_values()`.
26
    /// Resolves all layout-relevant properties for every node in the "normal" state
27
    /// (no hover/active/focus) and encodes them into compact arrays.
28
    ///
29
    /// Tier 1/2/2b provide fast-path access for layout-hot properties.
30
    /// Non-compact properties (background, transform, box-shadow, etc.) are
31
    /// resolved via the slow cascade path in `get_property_slow()`.
32
    ///
33
    /// `prev_font_hashes` is the per-node font hash array from the previous frame.
34
    /// When non-empty, each node's new `font_family_hash` is compared against the
35
    /// previous value, and differing nodes are recorded in `font_dirty_nodes`.
36
    /// On the first build (empty slice), ALL text nodes are marked dirty.
37
    pub fn build_compact_cache(
38
        &self,
39
        node_data: &[NodeData],
40
        prev_font_hashes: &[u64],
41
    ) -> CompactLayoutCache {
42
        let node_count = self.node_count;
43
        let default_state = StyledNodeState::default();
44
        let mut result = CompactLayoutCache::with_capacity(node_count);
45

            
46
        for i in 0..node_count {
47
            let node_id = NodeId::new(i);
48
            let nd = &node_data[i];
49

            
50
            // =====================================================================
51
            // Tier 1: Encode all 20 enum properties into u64
52
            // =====================================================================
53

            
54
            let display = self
55
                .get_display(nd, &node_id, &default_state)
56
                .and_then(|v| v.get_property().copied())
57
                .unwrap_or_default();
58
            let position = self
59
                .get_position(nd, &node_id, &default_state)
60
                .and_then(|v| v.get_property().copied())
61
                .unwrap_or_default();
62
            let float = self
63
                .get_float(nd, &node_id, &default_state)
64
                .and_then(|v| v.get_property().copied())
65
                .unwrap_or_default();
66
            let overflow_x = self
67
                .get_overflow_x(nd, &node_id, &default_state)
68
                .and_then(|v| v.get_property().copied())
69
                .unwrap_or_default();
70
            let overflow_y = self
71
                .get_overflow_y(nd, &node_id, &default_state)
72
                .and_then(|v| v.get_property().copied())
73
                .unwrap_or_default();
74
            let box_sizing = self
75
                .get_box_sizing(nd, &node_id, &default_state)
76
                .and_then(|v| v.get_property().copied())
77
                .unwrap_or_default();
78
            let flex_direction = self
79
                .get_flex_direction(nd, &node_id, &default_state)
80
                .and_then(|v| v.get_property().copied())
81
                .unwrap_or_default();
82
            let flex_wrap = self
83
                .get_flex_wrap(nd, &node_id, &default_state)
84
                .and_then(|v| v.get_property().copied())
85
                .unwrap_or_default();
86
            let justify_content = self
87
                .get_justify_content(nd, &node_id, &default_state)
88
                .and_then(|v| v.get_property().copied())
89
                .unwrap_or_default();
90
            let align_items = self
91
                .get_align_items(nd, &node_id, &default_state)
92
                .and_then(|v| v.get_property().copied())
93
                .unwrap_or_default();
94
            let align_content = self
95
                .get_align_content(nd, &node_id, &default_state)
96
                .and_then(|v| v.get_property().copied())
97
                .unwrap_or_default();
98
            let writing_mode = self
99
                .get_writing_mode(nd, &node_id, &default_state)
100
                .and_then(|v| v.get_property().copied())
101
                .unwrap_or_default();
102
            let clear = self
103
                .get_clear(nd, &node_id, &default_state)
104
                .and_then(|v| v.get_property().copied())
105
                .unwrap_or_default();
106
            let font_weight = self
107
                .get_font_weight(nd, &node_id, &default_state)
108
                .and_then(|v| v.get_property().copied())
109
                .unwrap_or_default();
110
            let font_style = self
111
                .get_font_style(nd, &node_id, &default_state)
112
                .and_then(|v| v.get_property().copied())
113
                .unwrap_or_default();
114
            let text_align = self
115
                .get_text_align(nd, &node_id, &default_state)
116
                .and_then(|v| v.get_property().copied())
117
                .unwrap_or_default();
118
            let visibility = self
119
                .get_visibility(nd, &node_id, &default_state)
120
                .and_then(|v| v.get_property().copied())
121
                .unwrap_or_default();
122
            let white_space = self
123
                .get_white_space(nd, &node_id, &default_state)
124
                .and_then(|v| v.get_property().copied())
125
                .unwrap_or_default();
126
            let direction = self
127
                .get_direction(nd, &node_id, &default_state)
128
                .and_then(|v| v.get_property().copied())
129
                .unwrap_or_default();
130
            let vertical_align = self
131
                .get_vertical_align(nd, &node_id, &default_state)
132
                .and_then(|v| v.get_property().copied())
133
                .unwrap_or_default();
134

            
135
            let border_collapse = self
136
                .get_border_collapse(nd, &node_id, &default_state)
137
                .and_then(|v| v.get_property().copied())
138
                .unwrap_or_default();
139

            
140
            result.tier1_enums[i] = encode_tier1(
141
                display,
142
                position,
143
                float,
144
                overflow_x,
145
                overflow_y,
146
                box_sizing,
147
                flex_direction,
148
                flex_wrap,
149
                justify_content,
150
                align_items,
151
                align_content,
152
                writing_mode,
153
                clear,
154
                font_weight,
155
                font_style,
156
                text_align,
157
                visibility,
158
                white_space,
159
                direction,
160
                vertical_align,
161
                border_collapse,
162
            );
163

            
164
            // =====================================================================
165
            // Tier 2: Encode numeric dimension properties
166
            // =====================================================================
167

            
168
            // Width/Height are enums: Auto | Px(PixelValue) | MinContent | MaxContent | Calc
169
            if let Some(val) = self.get_width(nd, &node_id, &default_state) {
170
                result.tier2_dims[i].width = encode_layout_width(val);
171
            }
172
            if let Some(val) = self.get_height(nd, &node_id, &default_state) {
173
                result.tier2_dims[i].height = encode_layout_height(val);
174
            }
175

            
176
            // Min/Max Width/Height are simple PixelValue wrappers
177
            if let Some(val) = self.get_min_width(nd, &node_id, &default_state) {
178
                result.tier2_dims[i].min_width = encode_pixel_prop(val);
179
            }
180
            if let Some(val) = self.get_max_width(nd, &node_id, &default_state) {
181
                result.tier2_dims[i].max_width = encode_pixel_prop(val);
182
            }
183
            if let Some(val) = self.get_min_height(nd, &node_id, &default_state) {
184
                result.tier2_dims[i].min_height = encode_pixel_prop(val);
185
            }
186
            if let Some(val) = self.get_max_height(nd, &node_id, &default_state) {
187
                result.tier2_dims[i].max_height = encode_pixel_prop(val);
188
            }
189

            
190
            // Flex basis (enum: Auto | Exact(PixelValue))
191
            if let Some(val) = self.get_flex_basis(nd, &node_id, &default_state) {
192
                result.tier2_dims[i].flex_basis = encode_flex_basis(val);
193
            }
194

            
195
            // Font size
196
            if let Some(val) = self.get_font_size(nd, &node_id, &default_state) {
197
                result.tier2_dims[i].font_size = encode_pixel_prop(val);
198
            }
199

            
200
            // Padding (i16 × 10 resolved px)
201
            if let Some(val) = self.get_padding_top(nd, &node_id, &default_state) {
202
                result.tier2_dims[i].padding_top = encode_css_pixel_as_i16(val);
203
            }
204
            if let Some(val) = self.get_padding_right(nd, &node_id, &default_state) {
205
                result.tier2_dims[i].padding_right = encode_css_pixel_as_i16(val);
206
            }
207
            if let Some(val) = self.get_padding_bottom(nd, &node_id, &default_state) {
208
                result.tier2_dims[i].padding_bottom = encode_css_pixel_as_i16(val);
209
            }
210
            if let Some(val) = self.get_padding_left(nd, &node_id, &default_state) {
211
                result.tier2_dims[i].padding_left = encode_css_pixel_as_i16(val);
212
            }
213

            
214
            // Margin (i16, auto is special)
215
            if let Some(val) = self.get_margin_top(nd, &node_id, &default_state) {
216
                result.tier2_dims[i].margin_top = encode_margin_i16(val);
217
            }
218
            if let Some(val) = self.get_margin_right(nd, &node_id, &default_state) {
219
                result.tier2_dims[i].margin_right = encode_margin_i16(val);
220
            }
221
            if let Some(val) = self.get_margin_bottom(nd, &node_id, &default_state) {
222
                result.tier2_dims[i].margin_bottom = encode_margin_i16(val);
223
            }
224
            if let Some(val) = self.get_margin_left(nd, &node_id, &default_state) {
225
                result.tier2_dims[i].margin_left = encode_margin_i16(val);
226
            }
227

            
228
            // Border widths (i16 × 10 resolved px)
229
            if let Some(val) = self.get_border_top_width(nd, &node_id, &default_state) {
230
                result.tier2_dims[i].border_top_width = encode_css_pixel_as_i16(val);
231
            }
232
            if let Some(val) = self.get_border_right_width(nd, &node_id, &default_state) {
233
                result.tier2_dims[i].border_right_width = encode_css_pixel_as_i16(val);
234
            }
235
            if let Some(val) = self.get_border_bottom_width(nd, &node_id, &default_state) {
236
                result.tier2_dims[i].border_bottom_width = encode_css_pixel_as_i16(val);
237
            }
238
            if let Some(val) = self.get_border_left_width(nd, &node_id, &default_state) {
239
                result.tier2_dims[i].border_left_width = encode_css_pixel_as_i16(val);
240
            }
241

            
242
            // Position offsets (top/right/bottom/left)
243
            if let Some(val) = self.get_top(nd, &node_id, &default_state) {
244
                result.tier2_dims[i].top = encode_css_pixel_as_i16(val);
245
            }
246
            if let Some(val) = self.get_right(nd, &node_id, &default_state) {
247
                result.tier2_dims[i].right = encode_css_pixel_as_i16(val);
248
            }
249
            if let Some(val) = self.get_bottom(nd, &node_id, &default_state) {
250
                result.tier2_dims[i].bottom = encode_css_pixel_as_i16(val);
251
            }
252
            if let Some(val) = self.get_left(nd, &node_id, &default_state) {
253
                result.tier2_dims[i].left = encode_css_pixel_as_i16(val);
254
            }
255

            
256
            // Flex grow/shrink (u16 × 100)
257
            if let Some(val) = self.get_flex_grow(nd, &node_id, &default_state) {
258
                if let Some(exact) = val.get_property() {
259
                    result.tier2_dims[i].flex_grow = encode_flex_u16(exact.inner.get());
260
                }
261
            }
262
            if let Some(val) = self.get_flex_shrink(nd, &node_id, &default_state) {
263
                if let Some(exact) = val.get_property() {
264
                    result.tier2_dims[i].flex_shrink = encode_flex_u16(exact.inner.get());
265
                }
266
            }
267

            
268
            // =====================================================================
269
            // Tier 2 cold: Paint-only properties
270
            // =====================================================================
271

            
272
            // Z-index
273
            if let Some(val) = self.get_z_index(nd, &node_id, &default_state) {
274
                if let Some(exact) = val.get_property() {
275
                    match exact {
276
                        LayoutZIndex::Auto => result.tier2_cold[i].z_index = I16_AUTO,
277
                        LayoutZIndex::Integer(z) => {
278
                            if *z >= I16_SENTINEL_THRESHOLD as i32 {
279
                                result.tier2_cold[i].z_index = I16_SENTINEL;
280
                            } else {
281
                                result.tier2_cold[i].z_index = *z as i16;
282
                            }
283
                        }
284
                    }
285
                }
286
            }
287

            
288
            // Border styles (packed into u16)
289
            {
290
                let bts = self.get_border_top_style(nd, &node_id, &default_state)
291
                    .and_then(|v| v.get_property().copied())
292
                    .map(|v| v.inner)
293
                    .unwrap_or_default();
294
                let brs = self.get_border_right_style(nd, &node_id, &default_state)
295
                    .and_then(|v| v.get_property().copied())
296
                    .map(|v| v.inner)
297
                    .unwrap_or_default();
298
                let bbs = self.get_border_bottom_style(nd, &node_id, &default_state)
299
                    .and_then(|v| v.get_property().copied())
300
                    .map(|v| v.inner)
301
                    .unwrap_or_default();
302
                let bls = self.get_border_left_style(nd, &node_id, &default_state)
303
                    .and_then(|v| v.get_property().copied())
304
                    .map(|v| v.inner)
305
                    .unwrap_or_default();
306
                result.tier2_cold[i].border_styles_packed =
307
                    encode_border_styles_packed(bts, brs, bbs, bls);
308
            }
309

            
310
            // Border colors (ColorU → u32 as 0xRRGGBBAA)
311
            if let Some(val) = self.get_border_top_color(nd, &node_id, &default_state) {
312
                if let Some(color) = val.get_property() {
313
                    result.tier2_cold[i].border_top_color = encode_color_u32(&color.inner);
314
                }
315
            }
316
            if let Some(val) = self.get_border_right_color(nd, &node_id, &default_state) {
317
                if let Some(color) = val.get_property() {
318
                    result.tier2_cold[i].border_right_color = encode_color_u32(&color.inner);
319
                }
320
            }
321
            if let Some(val) = self.get_border_bottom_color(nd, &node_id, &default_state) {
322
                if let Some(color) = val.get_property() {
323
                    result.tier2_cold[i].border_bottom_color = encode_color_u32(&color.inner);
324
                }
325
            }
326
            if let Some(val) = self.get_border_left_color(nd, &node_id, &default_state) {
327
                if let Some(color) = val.get_property() {
328
                    result.tier2_cold[i].border_left_color = encode_color_u32(&color.inner);
329
                }
330
            }
331

            
332
            // Border spacing (two PixelValue → i16 × 10 resolved px)
333
            if let Some(val) = self.get_border_spacing(nd, &node_id, &default_state) {
334
                if let Some(spacing) = val.get_property() {
335
                    if spacing.horizontal.metric == SizeMetric::Px {
336
                        result.tier2_cold[i].border_spacing_h = encode_resolved_px_i16(spacing.horizontal.number.get());
337
                    }
338
                    if spacing.vertical.metric == SizeMetric::Px {
339
                        result.tier2_cold[i].border_spacing_v = encode_resolved_px_i16(spacing.vertical.number.get());
340
                    }
341
                }
342
            }
343

            
344
            // Tab size (PixelValue → i16 × 10 resolved px)
345
            if let Some(val) = self.get_tab_size(nd, &node_id, &default_state) {
346
                result.tier2_cold[i].tab_size = encode_css_pixel_as_i16(val);
347
            }
348

            
349
            // =====================================================================
350
            // Tier 2b: Text properties
351
            // =====================================================================
352

            
353
            // Text color (ColorU → u32 as 0xRRGGBBAA)
354
            if let Some(val) = self.get_text_color(nd, &node_id, &default_state) {
355
                if let Some(color) = val.get_property() {
356
                    let c = &color.inner;
357
                    result.tier2b_text[i].text_color =
358
                        ((c.r as u32) << 24) | ((c.g as u32) << 16) | ((c.b as u32) << 8) | (c.a as u32);
359
                }
360
            }
361

            
362
            // Font-family (hash the whole StyleFontFamilyVec for fast comparison)
363
            if let Some(val) = self.get_font_family(nd, &node_id, &default_state) {
364
                if let Some(families) = val.get_property() {
365
                    let mut hasher = DefaultHasher::new();
366
                    families.hash(&mut hasher);
367
                    let h = hasher.finish();
368
                    let h = if h == 0 { 1 } else { h };
369
                    result.tier2b_text[i].font_family_hash = h;
370
                    result.font_hash_to_families.insert(h, families.clone());
371
                }
372
            }
373

            
374
            // Line-height (PercentageValue: internal number is value × 1000, we store % × 10)
375
            if let Some(val) = self.get_line_height(nd, &node_id, &default_state) {
376
                if let Some(lh) = val.get_property() {
377
                    // lh.inner is PercentageValue, normalized() = value/100.
378
                    // Internal number = percentage × 1000 (e.g. 120% → 120_000).
379
                    // We store percentage × 10 as i16 (e.g. 120% → 1200).
380
                    let pct_x10 = (lh.inner.normalized() * 1000.0).round() as i32;
381
                    if pct_x10 >= -32768 && pct_x10 < I16_SENTINEL_THRESHOLD as i32 {
382
                        result.tier2b_text[i].line_height = pct_x10 as i16;
383
                    } else {
384
                        result.tier2b_text[i].line_height = I16_SENTINEL;
385
                    }
386
                }
387
            }
388

            
389
            // Letter-spacing (PixelValue wrapper → i16 × 10 resolved px)
390
            if let Some(val) = self.get_letter_spacing(nd, &node_id, &default_state) {
391
                result.tier2b_text[i].letter_spacing = encode_css_pixel_as_i16(val);
392
            }
393

            
394
            // Word-spacing (PixelValue wrapper → i16 × 10 resolved px)
395
            if let Some(val) = self.get_word_spacing(nd, &node_id, &default_state) {
396
                result.tier2b_text[i].word_spacing = encode_css_pixel_as_i16(val);
397
            }
398

            
399
            // Text-indent (PixelValue wrapper → i16 × 10 resolved px)
400
            if let Some(val) = self.get_text_indent(nd, &node_id, &default_state) {
401
                result.tier2b_text[i].text_indent = encode_css_pixel_as_i16(val);
402
            }
403
        }
404

            
405
        // =====================================================================
406
        // Per-node font dirty tracking (P4)
407
        // Compare each node's font_family_hash against the previous frame's hash.
408
        // Nodes whose hash changed are recorded in font_dirty_nodes for
409
        // incremental font chain re-resolution instead of all-or-nothing.
410
        // =====================================================================
411
        result.font_dirty_nodes.clear();
412
        for i in 0..node_count {
413
            let new_hash = result.tier2b_text[i].font_family_hash;
414
            let old_hash = prev_font_hashes.get(i).copied().unwrap_or(0);
415
            if new_hash != old_hash {
416
                result.font_dirty_nodes.push(i);
417
            }
418
        }
419
        // Save current hashes as prev_font_hashes for next frame comparison
420
        result.prev_font_hashes = result.tier2b_text.iter().map(|t| t.font_family_hash).collect();
421

            
422
        result
423
    }
424

            
425
    /// Build compact cache with inheritance in a single pass.
426
    ///
427
    /// Replaces the separate `compute_inherited_values()` + `build_compact_cache()` calls.
428
    /// For each node (in DOM index order, which is pre-order = parents before children):
429
    ///   1. Copy parent's compact values for INHERITABLE properties
430
    ///   2. Apply this node's CSS properties on top (from css_props + inline + UA)
431
    ///   3. Write directly to compact arrays
432
    ///
433
    /// This eliminates 50K Vec clones from compute_inherited_values and
434
    /// avoids re-reading properties from 5 separate data structures.
435
8682
    pub fn build_compact_cache_with_inheritance(
436
8682
        &self,
437
8682
        node_data: &[NodeData],
438
8682
        node_hierarchy: &[crate::styled_dom::NodeHierarchyItem],
439
8682
        prev_font_hashes: &[u64],
440
8682
    ) -> CompactLayoutCache {
441
8682
        self.build_compact_cache_with_inheritance_debug(node_data, node_hierarchy, prev_font_hashes, &mut None)
442
8682
    }
443

            
444
    /// Same as `build_compact_cache_with_inheritance` but with optional debug logging.
445
8682
    pub fn build_compact_cache_with_inheritance_debug(
446
8682
        &self,
447
8682
        node_data: &[NodeData],
448
8682
        node_hierarchy: &[crate::styled_dom::NodeHierarchyItem],
449
8682
        prev_font_hashes: &[u64],
450
8682
        debug_messages: &mut Option<Vec<azul_css::LayoutDebugMessage>>,
451
8682
    ) -> CompactLayoutCache {
452
8682
        let node_count = self.node_count;
453
8682
        let default_state = StyledNodeState::default();
454
8682
        let mut result = CompactLayoutCache::with_capacity(node_count);
455

            
456
        // Pre-encode global CSS properties (from `*` rules) into compact form.
457
        // These are applied as baseline for every node before inheritance.
458
8682
        let mut global_tier1: u64 = 0;
459
8682
        let mut global_dims = CompactNodeProps::default();
460
8682
        let mut global_cold = CompactNodePropsCold::default();
461
8682
        let mut global_text = CompactTextProps::default();
462
8682
        let has_global = !self.global_css_props.is_empty();
463

            
464
8682
        if has_global {
465
            use azul_css::props::property::CssProperty;
466

            
467
20777
            for prop in &self.global_css_props {
468
                // Apply each global property to the pre-encoded compact values
469
                macro_rules! global_tier1_enum {
470
                    ($variant:ident, $shift:ident, $mask:ident, $encoder:ident) => {
471
                        if let CssProperty::$variant(v) = prop {
472
                            if let Some(exact) = v.get_property() {
473
                                let encoded = $encoder(*exact) as u64;
474
                                let shifted_mask = $mask << $shift;
475
                                global_tier1 = (global_tier1 & !shifted_mask) | ((encoded & $mask) << $shift);
476
                            }
477
                        }
478
                    };
479
                }
480

            
481
18122
                global_tier1_enum!(Display, DISPLAY_SHIFT, DISPLAY_MASK, layout_display_to_u8);
482
18122
                global_tier1_enum!(Position, POSITION_SHIFT, POSITION_MASK, layout_position_to_u8);
483
18122
                global_tier1_enum!(Float, FLOAT_SHIFT, FLOAT_MASK, layout_float_to_u8);
484
18122
                global_tier1_enum!(OverflowX, OVERFLOW_X_SHIFT, OVERFLOW_MASK, layout_overflow_to_u8);
485
18122
                global_tier1_enum!(OverflowY, OVERFLOW_Y_SHIFT, OVERFLOW_MASK, layout_overflow_to_u8);
486
18122
                global_tier1_enum!(BoxSizing, BOX_SIZING_SHIFT, BOX_SIZING_MASK, layout_box_sizing_to_u8);
487
18122
                global_tier1_enum!(FlexDirection, FLEX_DIRECTION_SHIFT, FLEX_DIR_MASK, layout_flex_direction_to_u8);
488
18122
                global_tier1_enum!(FlexWrap, FLEX_WRAP_SHIFT, FLEX_WRAP_MASK, layout_flex_wrap_to_u8);
489
18122
                global_tier1_enum!(JustifyContent, JUSTIFY_CONTENT_SHIFT, JUSTIFY_MASK, layout_justify_content_to_u8);
490
18122
                global_tier1_enum!(AlignItems, ALIGN_ITEMS_SHIFT, ALIGN_MASK, layout_align_items_to_u8);
491
18122
                global_tier1_enum!(AlignContent, ALIGN_CONTENT_SHIFT, ALIGN_MASK, layout_align_content_to_u8);
492
18122
                global_tier1_enum!(Clear, CLEAR_SHIFT, CLEAR_MASK, layout_clear_to_u8);
493
18122
                global_tier1_enum!(Visibility, VISIBILITY_SHIFT, VISIBILITY_MASK, style_visibility_to_u8);
494
18122
                global_tier1_enum!(WritingMode, WRITING_MODE_SHIFT, WRITING_MODE_MASK, layout_writing_mode_to_u8);
495
18122
                global_tier1_enum!(FontWeight, FONT_WEIGHT_SHIFT, FONT_WEIGHT_MASK, style_font_weight_to_u8);
496
18122
                global_tier1_enum!(FontStyle, FONT_STYLE_SHIFT, FONT_STYLE_MASK, style_font_style_to_u8);
497
18122
                global_tier1_enum!(TextAlign, TEXT_ALIGN_SHIFT, TEXT_ALIGN_MASK, style_text_align_to_u8);
498
18122
                global_tier1_enum!(WhiteSpace, WHITE_SPACE_SHIFT, WHITE_SPACE_MASK, style_white_space_to_u8);
499
18122
                global_tier1_enum!(Direction, DIRECTION_SHIFT, DIRECTION_MASK, style_direction_to_u8);
500
18122
                global_tier1_enum!(VerticalAlign, VERTICAL_ALIGN_SHIFT, VERTICAL_ALIGN_MASK, style_vertical_align_to_u8);
501
18122
                global_tier1_enum!(BorderCollapse, BORDER_COLLAPSE_SHIFT, BORDER_COLLAPSE_MASK, border_collapse_to_u8);
502

            
503
                // Tier 2 dims
504
18122
                match prop {
505
2512
                    CssProperty::PaddingTop(v) => { global_dims.padding_top = encode_css_pixel_as_i16(v); }
506
2512
                    CssProperty::PaddingRight(v) => { global_dims.padding_right = encode_css_pixel_as_i16(v); }
507
2512
                    CssProperty::PaddingBottom(v) => { global_dims.padding_bottom = encode_css_pixel_as_i16(v); }
508
2512
                    CssProperty::PaddingLeft(v) => { global_dims.padding_left = encode_css_pixel_as_i16(v); }
509
1924
                    CssProperty::MarginTop(v) => { global_dims.margin_top = encode_margin_i16(v); }
510
1924
                    CssProperty::MarginRight(v) => { global_dims.margin_right = encode_margin_i16(v); }
511
1924
                    CssProperty::MarginBottom(v) => { global_dims.margin_bottom = encode_margin_i16(v); }
512
1924
                    CssProperty::MarginLeft(v) => { global_dims.margin_left = encode_margin_i16(v); }
513
                    CssProperty::Width(v) => { global_dims.width = encode_layout_width(v); }
514
                    CssProperty::Height(v) => { global_dims.height = encode_layout_height(v); }
515
                    CssProperty::FontSize(v) => { global_dims.font_size = encode_pixel_prop(v); }
516
                    CssProperty::BorderTopWidth(v) => { global_dims.border_top_width = encode_css_pixel_as_i16(v); }
517
                    CssProperty::BorderRightWidth(v) => { global_dims.border_right_width = encode_css_pixel_as_i16(v); }
518
                    CssProperty::BorderBottomWidth(v) => { global_dims.border_bottom_width = encode_css_pixel_as_i16(v); }
519
                    CssProperty::BorderLeftWidth(v) => { global_dims.border_left_width = encode_css_pixel_as_i16(v); }
520
378
                    _ => {}
521
                }
522
            }
523

            
524
2655
            if global_tier1 != 0 {
525
336
                global_tier1 |= TIER1_POPULATED_BIT;
526
2319
            }
527
6027
        }
528

            
529
        // Helper: push debug message if debug_messages is Some
530
        macro_rules! cascade_debug {
531
            ($($arg:tt)*) => {
532
                if let Some(ref mut msgs) = debug_messages {
533
                    msgs.push(azul_css::LayoutDebugMessage::css_getter(format!($($arg)*)));
534
                }
535
            };
536
        }
537

            
538
45735
        for i in 0..node_count {
539
45735
            let node_id = NodeId::new(i);
540
45735
            let nd = &node_data[i];
541

            
542
            // Step 0: Apply UA CSS defaults first (lowest priority).
543
            // Then global `*` rules override UA (higher priority).
544
            // Then per-node CSS (Step 3) overrides both.
545
            //
546
            // CSS cascade priority: UA < author `*` < author specific < inline
547

            
548
            // Step 1: Inherit from parent's COMPACT values (not computed_values)
549
            // Parent index is always < i in pre-order arena, so already computed.
550
            //
551
            // Step 1: Inherit ONLY inheritable CSS properties from parent.
552
            // Non-inheritable fields (display, position, float, overflow, box-sizing,
553
            // flex-*, clear, vertical-align, writing-mode) stay at 0 (CSS initial value).
554
            // They get set by UA CSS (Step 2) and author CSS (Step 3).
555
            const INHERITABLE_TIER1_MASK: u64 =
556
                (FONT_WEIGHT_MASK << FONT_WEIGHT_SHIFT)
557
                | (FONT_STYLE_MASK << FONT_STYLE_SHIFT)
558
                | (TEXT_ALIGN_MASK << TEXT_ALIGN_SHIFT)
559
                | (VISIBILITY_MASK << VISIBILITY_SHIFT)
560
                | (WHITE_SPACE_MASK << WHITE_SPACE_SHIFT)
561
                | (DIRECTION_MASK << DIRECTION_SHIFT)
562
                | (BORDER_COLLAPSE_MASK << BORDER_COLLAPSE_SHIFT);
563

            
564
45735
            let parent_id = node_hierarchy[i].parent_id();
565
45735
            if let Some(pid) = parent_id {
566
37053
                let pi = pid.index();
567
37053

            
568
37053
                // Copy only inheritable tier1 fields from parent
569
37053
                result.tier1_enums[i] = result.tier1_enums[pi] & INHERITABLE_TIER1_MASK;
570
37053

            
571
37053
                // Inheritable tier2: font_size
572
37053
                result.tier2_dims[i].font_size = result.tier2_dims[pi].font_size;
573
37053

            
574
37053
                // Inheritable tier2_cold: border_spacing, tab_size
575
37053
                result.tier2_cold[i].border_spacing_h = result.tier2_cold[pi].border_spacing_h;
576
37053
                result.tier2_cold[i].border_spacing_v = result.tier2_cold[pi].border_spacing_v;
577
37053
                result.tier2_cold[i].tab_size = result.tier2_cold[pi].tab_size;
578
37053

            
579
37053
                // Inheritable tier2b: all text properties
580
37053
                result.tier2b_text[i] = result.tier2b_text[pi];
581
37053
            }
582

            
583
            {
584
45735
                let d = &result.tier2_dims[i];
585
45735
                cascade_debug!("node[{}] {:?} after-inherit: pt={} pb={} pl={} pr={} mt={} mb={} ml={} mr={} w={} h={}",
586
                    i, nd.node_type, d.padding_top, d.padding_bottom, d.padding_left, d.padding_right,
587
                    d.margin_top, d.margin_bottom, d.margin_left, d.margin_right, d.width, d.height);
588
            }
589

            
590
            // Step 2: Apply UA CSS defaults for this node type directly to compact values.
591
            // UA defaults have lowest cascade priority — overridden by author CSS below.
592
45735
            apply_ua_css_to_compact(
593
45735
                &nd.node_type,
594
45735
                &mut result.tier1_enums[i],
595
45735
                &mut result.tier2_dims[i],
596
45735
                &mut result.tier2_cold[i],
597
45735
                &mut result.tier2b_text[i],
598
45735
                &mut result.font_hash_to_families,
599
            );
600

            
601
            {
602
45735
                let d = &result.tier2_dims[i];
603
45735
                cascade_debug!("node[{}] {:?} after-UA: pt={} pb={} pl={} pr={} mt={} mb={} ml={} mr={}",
604
                    i, nd.node_type, d.padding_top, d.padding_bottom, d.padding_left, d.padding_right,
605
                    d.margin_top, d.margin_bottom, d.margin_left, d.margin_right);
606
            }
607

            
608
            // Step 2.5: Apply global `*` author CSS (overrides UA, overridden by specific rules)
609
            // Apply each `*` rule property individually (not bulk-assign) so we only
610
            // override properties the `*` rule actually set, preserving UA CSS for others.
611
            //
612
            // Per CSS spec, `*` matches all ELEMENTS. Text nodes are not elements —
613
            // they must only inherit from their parent. Without this check, `* { color: #666 }`
614
            // would overwrite the inherited `color: red` on a Text child of `<p>`,
615
            // even though `<p>` correctly got red from `p { color: red }`.
616
45735
            if !nd.is_text_node() {
617
72535
                for prop in self.global_css_props.iter() {
618
69182
                    apply_css_property_to_compact(
619
69182
                        prop,
620
69182
                        &mut result.tier1_enums[i],
621
69182
                        &mut result.tier2_dims[i],
622
69182
                        &mut result.tier2_cold[i],
623
69182
                        &mut result.tier2b_text[i],
624
69182
                        &mut result.font_hash_to_families,
625
69182
                    );
626
69182
                    update_dom_declared_flags(prop, &mut result.dom_declared_flags);
627
69182
                }
628
12806
            }
629

            
630
            {
631
45735
                let d = &result.tier2_dims[i];
632
45735
                cascade_debug!("node[{}] {:?} after-global-star: pt={} pb={} pl={} pr={} mt={} mb={} ml={} mr={}",
633
                    i, nd.node_type, d.padding_top, d.padding_bottom, d.padding_left, d.padding_right,
634
                    d.margin_top, d.margin_bottom, d.margin_left, d.margin_right);
635
45735
                let n_props = self.css_props.get_slice(i).len();
636
45735
                let n_inline = nd.style.iter_inline_properties().count();
637
45735
                cascade_debug!("node[{}] css_props={} entries, inline={} entries", i, n_props, n_inline);
638
50993
                for prop in self.css_props.get_slice(i) {
639
47389
                    cascade_debug!("node[{}]   css_prop: state={:?} type={:?}", i, prop.state, prop.prop_type);
640
                }
641
            }
642

            
643
            // Step 3: Apply this node's CSS properties directly to compact values.
644
            // Per-node author CSS has higher specificity than global `*`.
645

            
646
            // Scan css_props (stylesheet rules, sorted by (state, prop_type))
647
            // Typically 5-15 entries per node. Only Normal state matters for layout.
648
50993
            for prop in self.css_props.get_slice(i) {
649
47389
                if prop.state != azul_css::dynamic_selector::PseudoStateType::Normal { continue; }
650
47389
                apply_css_property_to_compact(
651
47389
                    &prop.property,
652
47389
                    &mut result.tier1_enums[i],
653
47389
                    &mut result.tier2_dims[i],
654
47389
                    &mut result.tier2_cold[i],
655
47389
                    &mut result.tier2b_text[i],
656
47389
                    &mut result.font_hash_to_families,
657
                );
658
47389
                update_dom_declared_flags(&prop.property, &mut result.dom_declared_flags);
659
            }
660

            
661
            {
662
45735
                let d = &result.tier2_dims[i];
663
45735
                cascade_debug!("node[{}] {:?} after-css-props: pt={} pb={} pl={} pr={} mt={} mb={} ml={} mr={}",
664
                    i, nd.node_type, d.padding_top, d.padding_bottom, d.padding_left, d.padding_right,
665
                    d.margin_top, d.margin_bottom, d.margin_left, d.margin_right);
666
            }
667

            
668
            // Scan inline CSS (node_data.style — typically 0-3 properties).
669
            // Inline CSS has highest specificity — applied last to override stylesheet.
670
45735
            for (prop, conds) in nd.style.iter_inline_properties() {
671
                // Only apply Normal state (no pseudo-selectors like :hover)
672
3298
                let is_normal = conds.as_slice().is_empty()
673
2023
                    || conds.as_slice().iter().all(|c|
674
2023
                        matches!(c, azul_css::dynamic_selector::DynamicSelector::PseudoState(
675
                            azul_css::dynamic_selector::PseudoStateType::Normal
676
                        ))
677
                    );
678
3298
                if !is_normal { continue; }
679
1275
                apply_css_property_to_compact(
680
1275
                    prop,
681
1275
                    &mut result.tier1_enums[i],
682
1275
                    &mut result.tier2_dims[i],
683
1275
                    &mut result.tier2_cold[i],
684
1275
                    &mut result.tier2b_text[i],
685
1275
                    &mut result.font_hash_to_families,
686
                );
687
1275
                update_dom_declared_flags(prop, &mut result.dom_declared_flags);
688
            }
689

            
690
            // Resolve font-size from em/percent/pt/etc. to px.
691
            // CSS 2.1: inherited font-size is the COMPUTED (px) value, not the specified value.
692
            // Pre-order traversal guarantees parent's font_size is already resolved.
693
45735
            resolve_font_size_to_px(
694
45735
                &mut result.tier2_dims,
695
45735
                i,
696
45735
                parent_id,
697
            );
698

            
699
            // Set populated bit
700
45735
            if result.tier1_enums[i] != 0 {
701
28999
                result.tier1_enums[i] |= TIER1_POPULATED_BIT;
702
30890
            }
703
        }
704

            
705
        // Font dirty tracking.
706
        // When prev_font_hashes is empty (first build for this DOM), mark ALL
707
        // text nodes dirty to force font resolution. Without this, a DOM with
708
        // no explicit font-family (all hashes 0) would compare 0==0 and skip
709
        // resolution, even though font-weight/font-style may differ from the
710
        // cached chains of a previous DOM.
711
8682
        result.font_dirty_nodes.clear();
712
8682
        let first_build = prev_font_hashes.is_empty();
713
45735
        for i in 0..node_count {
714
45735
            let new_hash = result.tier2b_text[i].font_family_hash;
715
45735
            let old_hash = prev_font_hashes.get(i).copied().unwrap_or(0);
716
45735
            if first_build || new_hash != old_hash {
717
45733
                result.font_dirty_nodes.push(i);
718
45733
            }
719
        }
720
8682
        result.prev_font_hashes = result.tier2b_text.iter().map(|t| t.font_family_hash).collect();
721

            
722
8682
        result
723
8682
    }
724
}
725

            
726
// =============================================================================
727
// Helpers extracted from build_compact_cache_with_inheritance_debug
728
// =============================================================================
729

            
730
/// Apply UA CSS defaults for a node type directly to compact values.
731
/// UA defaults have lowest cascade priority — overridden by author CSS.
732
45735
fn apply_ua_css_to_compact(
733
45735
    node_type: &crate::dom::NodeType,
734
45735
    tier1: &mut u64,
735
45735
    dims: &mut CompactNodeProps,
736
45735
    cold: &mut CompactNodePropsCold,
737
45735
    text: &mut CompactTextProps,
738
45735
    font_hash_map: &mut alloc::collections::BTreeMap<u64, azul_css::props::basic::font::StyleFontFamilyVec>,
739
45735
) {
740
    use azul_css::props::property::CssPropertyType as PT2;
741
    const UA_PROPERTY_TYPES: &[PT2] = &[
742
        // Tier1 enum properties
743
        PT2::Display, PT2::Position, PT2::Float, PT2::Clear,
744
        PT2::OverflowX, PT2::OverflowY, PT2::BoxSizing,
745
        PT2::FlexDirection, PT2::FlexWrap, PT2::JustifyContent,
746
        PT2::AlignItems, PT2::AlignContent, PT2::WritingMode,
747
        PT2::FontWeight, PT2::FontStyle, PT2::TextAlign,
748
        PT2::Visibility, PT2::WhiteSpace, PT2::Direction,
749
        PT2::VerticalAlign, PT2::BorderCollapse,
750
        // Tier2 dimension properties
751
        PT2::Width, PT2::Height, PT2::FontSize,
752
        PT2::MarginTop, PT2::MarginBottom, PT2::MarginLeft, PT2::MarginRight,
753
        PT2::PaddingTop, PT2::PaddingBottom, PT2::PaddingLeft, PT2::PaddingRight,
754
        PT2::BorderTopWidth, PT2::BorderTopStyle, PT2::BorderTopColor,
755
        PT2::BorderRightWidth, PT2::BorderRightStyle, PT2::BorderRightColor,
756
        PT2::BorderBottomWidth, PT2::BorderBottomStyle, PT2::BorderBottomColor,
757
        PT2::BorderLeftWidth, PT2::BorderLeftStyle, PT2::BorderLeftColor,
758
        // Text properties
759
        PT2::TextColor, PT2::LineHeight, PT2::LetterSpacing, PT2::WordSpacing,
760
        PT2::TextDecoration, PT2::Cursor, PT2::ListStyleType,
761
    ];
762
2378220
    for pt in UA_PROPERTY_TYPES {
763
2332485
        if let Some(ua_prop) = crate::ua_css::get_ua_property(node_type, *pt) {
764
106949
            apply_css_property_to_compact(ua_prop, tier1, dims, cold, text, font_hash_map);
765
2225536
        }
766
    }
767
45735
}
768

            
769
/// Resolve a node's font-size from relative units (em, %, rem, pt) to absolute px.
770
/// CSS 2.1: inherited font-size is the COMPUTED (px) value, not the specified value.
771
/// Pre-order traversal guarantees parent's font_size is already resolved.
772
45735
fn resolve_font_size_to_px(
773
45735
    tier2_dims: &mut [CompactNodeProps],
774
45735
    node_idx: usize,
775
45735
    parent_id: Option<NodeId>,
776
45735
) {
777
45735
    let raw_fs = tier2_dims[node_idx].font_size;
778
45735
    if raw_fs == U32_SENTINEL || raw_fs >= U32_SENTINEL_THRESHOLD {
779
41279
        return;
780
4456
    }
781
4456
    let pv = match decode_pixel_value_u32(raw_fs) {
782
4456
        Some(pv) if pv.metric != SizeMetric::Px => pv,
783
4151
        _ => return,
784
    };
785

            
786
305
    let parent_font_size_px = parent_id
787
305
        .map(|pid| {
788
271
            decode_pixel_value_u32(tier2_dims[pid.index()].font_size)
789
271
                .map(|ppv| ppv.number.get())
790
271
                .unwrap_or(16.0)
791
271
        })
792
305
        .unwrap_or(16.0);
793

            
794
305
    let resolved_px = match pv.metric {
795
271
        SizeMetric::Em => pv.number.get() * parent_font_size_px,
796
34
        SizeMetric::Percent => pv.number.get() / 100.0 * parent_font_size_px,
797
        SizeMetric::Rem => {
798
            decode_pixel_value_u32(tier2_dims[0].font_size)
799
                .map(|rpv| rpv.number.get())
800
                .unwrap_or(16.0)
801
                * pv.number.get()
802
        }
803
        SizeMetric::Pt => pv.number.get() * 96.0 / 72.0,
804
        _ => pv.number.get(),
805
    };
806
305
    tier2_dims[node_idx].font_size =
807
305
        encode_pixel_value_u32(&azul_css::props::basic::pixel::PixelValue::px(resolved_px));
808
45735
}
809

            
810
// =============================================================================
811
// Direct CssProperty → compact field writer
812
// =============================================================================
813

            
814
/// Apply a single CssProperty directly to the compact representation.
815
/// Called once per property per node — replaces the old 56+ getter approach.
816
#[inline]
817
224795
fn apply_css_property_to_compact(
818
224795
    prop: &CssProperty,
819
224795
    tier1: &mut u64,
820
224795
    dims: &mut CompactNodeProps,
821
224795
    cold: &mut CompactNodePropsCold,
822
224795
    text: &mut CompactTextProps,
823
224795
    font_hash_map: &mut alloc::collections::BTreeMap<u64, azul_css::props::basic::font::StyleFontFamilyVec>,
824
224795
) {
825
    macro_rules! set_tier1 {
826
        ($v:expr, $shift:expr, $mask:expr, $encoder:ident) => {
827
            if let Some(exact) = $v.get_property() {
828
                let encoded = $encoder(*exact) as u64;
829
                let shifted_mask = $mask << $shift;
830
                *tier1 = (*tier1 & !shifted_mask) | ((encoded & $mask) << $shift);
831
            }
832
        };
833
    }
834

            
835
224795
    match prop {
836
        // Tier 1 enums
837
46475
        CssProperty::Display(v) => set_tier1!(v, DISPLAY_SHIFT, DISPLAY_MASK, layout_display_to_u8),
838
118
        CssProperty::Position(v) => set_tier1!(v, POSITION_SHIFT, POSITION_MASK, layout_position_to_u8),
839
1134
        CssProperty::Float(v) => set_tier1!(v, FLOAT_SHIFT, FLOAT_MASK, layout_float_to_u8),
840
143
        CssProperty::OverflowX(v) => set_tier1!(v, OVERFLOW_X_SHIFT, OVERFLOW_MASK, layout_overflow_to_u8),
841
143
        CssProperty::OverflowY(v) => set_tier1!(v, OVERFLOW_Y_SHIFT, OVERFLOW_MASK, layout_overflow_to_u8),
842
630
        CssProperty::BoxSizing(v) => set_tier1!(v, BOX_SIZING_SHIFT, BOX_SIZING_MASK, layout_box_sizing_to_u8),
843
311
        CssProperty::FlexDirection(v) => set_tier1!(v, FLEX_DIRECTION_SHIFT, FLEX_DIR_MASK, layout_flex_direction_to_u8),
844
17
        CssProperty::FlexWrap(v) => set_tier1!(v, FLEX_WRAP_SHIFT, FLEX_WRAP_MASK, layout_flex_wrap_to_u8),
845
59
        CssProperty::JustifyContent(v) => set_tier1!(v, JUSTIFY_CONTENT_SHIFT, JUSTIFY_MASK, layout_justify_content_to_u8),
846
59
        CssProperty::AlignItems(v) => set_tier1!(v, ALIGN_ITEMS_SHIFT, ALIGN_MASK, layout_align_items_to_u8),
847
        CssProperty::AlignContent(v) => set_tier1!(v, ALIGN_CONTENT_SHIFT, ALIGN_MASK, layout_align_content_to_u8),
848
        CssProperty::WritingMode(v) => set_tier1!(v, WRITING_MODE_SHIFT, WRITING_MODE_MASK, layout_writing_mode_to_u8),
849
210
        CssProperty::Clear(v) => set_tier1!(v, CLEAR_SHIFT, CLEAR_MASK, layout_clear_to_u8),
850
296
        CssProperty::FontWeight(v) => set_tier1!(v, FONT_WEIGHT_SHIFT, FONT_WEIGHT_MASK, style_font_weight_to_u8),
851
        CssProperty::FontStyle(v) => set_tier1!(v, FONT_STYLE_SHIFT, FONT_STYLE_MASK, style_font_style_to_u8),
852
        CssProperty::TextAlign(v) => set_tier1!(v, TEXT_ALIGN_SHIFT, TEXT_ALIGN_MASK, style_text_align_to_u8),
853
17
        CssProperty::Visibility(v) => set_tier1!(v, VISIBILITY_SHIFT, VISIBILITY_MASK, style_visibility_to_u8),
854
756
        CssProperty::WhiteSpace(v) => set_tier1!(v, WHITE_SPACE_SHIFT, WHITE_SPACE_MASK, style_white_space_to_u8),
855
        CssProperty::Direction(v) => set_tier1!(v, DIRECTION_SHIFT, DIRECTION_MASK, style_direction_to_u8),
856
4872
        CssProperty::VerticalAlign(v) => set_tier1!(v, VERTICAL_ALIGN_SHIFT, VERTICAL_ALIGN_MASK, style_vertical_align_to_u8),
857
        CssProperty::BorderCollapse(v) => set_tier1!(v, BORDER_COLLAPSE_SHIFT, BORDER_COLLAPSE_MASK, border_collapse_to_u8),
858
42
        CssProperty::AlignSelf(v) => set_tier1!(v, ALIGN_SELF_SHIFT, ALIGN_SELF_MASK, layout_align_self_to_u8),
859
        CssProperty::JustifySelf(v) => set_tier1!(v, JUSTIFY_SELF_SHIFT, JUSTIFY_SELF_MASK, layout_justify_self_to_u8),
860
        CssProperty::GridAutoFlow(v) => set_tier1!(v, GRID_AUTO_FLOW_SHIFT, GRID_AUTO_FLOW_MASK, layout_grid_auto_flow_to_u8),
861
        CssProperty::JustifyItems(v) => set_tier1!(v, JUSTIFY_ITEMS_SHIFT, JUSTIFY_ITEMS_MASK, layout_justify_items_to_u8),
862

            
863
        // Tier 2 dimensions
864
5421
        CssProperty::Width(v) => { dims.width = encode_layout_width(v); }
865
9702
        CssProperty::Height(v) => { dims.height = encode_layout_height(v); }
866
        CssProperty::MinWidth(v) => { dims.min_width = encode_pixel_prop(v); }
867
42
        CssProperty::MaxWidth(v) => { dims.max_width = encode_pixel_prop(v); }
868
336
        CssProperty::MinHeight(v) => { dims.min_height = encode_pixel_prop(v); }
869
        CssProperty::MaxHeight(v) => { dims.max_height = encode_pixel_prop(v); }
870
        CssProperty::FlexBasis(v) => { dims.flex_basis = encode_flex_basis(v); }
871
1888
        CssProperty::FontSize(v) => { dims.font_size = encode_pixel_prop(v); }
872
15106
        CssProperty::PaddingTop(v) => { dims.padding_top = encode_css_pixel_as_i16(v); }
873
15022
        CssProperty::PaddingRight(v) => { dims.padding_right = encode_css_pixel_as_i16(v); }
874
15022
        CssProperty::PaddingBottom(v) => { dims.padding_bottom = encode_css_pixel_as_i16(v); }
875
15039
        CssProperty::PaddingLeft(v) => { dims.padding_left = encode_css_pixel_as_i16(v); }
876
17279
        CssProperty::MarginTop(v) => { dims.margin_top = encode_margin_i16(v); }
877
14804
        CssProperty::MarginRight(v) => { dims.margin_right = encode_margin_i16(v); }
878
16817
        CssProperty::MarginBottom(v) => { dims.margin_bottom = encode_margin_i16(v); }
879
14804
        CssProperty::MarginLeft(v) => { dims.margin_left = encode_margin_i16(v); }
880
496
        CssProperty::BorderTopWidth(v) => { dims.border_top_width = encode_css_pixel_as_i16(v); }
881
454
        CssProperty::BorderRightWidth(v) => { dims.border_right_width = encode_css_pixel_as_i16(v); }
882
454
        CssProperty::BorderBottomWidth(v) => { dims.border_bottom_width = encode_css_pixel_as_i16(v); }
883
454
        CssProperty::BorderLeftWidth(v) => { dims.border_left_width = encode_css_pixel_as_i16(v); }
884
59
        CssProperty::Top(v) => { dims.top = encode_css_pixel_as_i16(v); }
885
59
        CssProperty::Right(v) => { dims.right = encode_css_pixel_as_i16(v); }
886
17
        CssProperty::Bottom(v) => { dims.bottom = encode_css_pixel_as_i16(v); }
887
17
        CssProperty::Left(v) => { dims.left = encode_css_pixel_as_i16(v); }
888
168
        CssProperty::FlexGrow(v) => {
889
168
            if let Some(exact) = v.get_property() {
890
168
                dims.flex_grow = encode_flex_u16(exact.inner.get());
891
168
            }
892
        }
893
        CssProperty::FlexShrink(v) => {
894
            if let Some(exact) = v.get_property() {
895
                dims.flex_shrink = encode_flex_u16(exact.inner.get());
896
            }
897
        }
898

            
899
        CssProperty::RowGap(v) => {
900
            if let Some(g) = v.get_property() {
901
                if g.inner.metric == SizeMetric::Px {
902
                    dims.row_gap = encode_resolved_px_i16(g.inner.number.get());
903
                }
904
            }
905
        }
906
        CssProperty::ColumnGap(v) => {
907
            if let Some(g) = v.get_property() {
908
                if g.inner.metric == SizeMetric::Px {
909
                    dims.column_gap = encode_resolved_px_i16(g.inner.number.get());
910
                }
911
            }
912
        }
913
        CssProperty::Gap(v) => {
914
            if let Some(g) = v.get_property() {
915
                if g.inner.metric == SizeMetric::Px {
916
                    let enc = encode_resolved_px_i16(g.inner.number.get());
917
                    dims.row_gap = enc;
918
                    dims.column_gap = enc;
919
                }
920
            }
921
        }
922

            
923
        // Grid placement (compact encoding for common Auto/Line cases)
924
        CssProperty::GridColumn(v) => {
925
            if let Some(gp) = v.get_property() {
926
                cold.grid_col_start = encode_grid_line(&gp.grid_start);
927
                cold.grid_col_end = encode_grid_line(&gp.grid_end);
928
            }
929
        }
930
        CssProperty::GridRow(v) => {
931
            if let Some(gp) = v.get_property() {
932
                cold.grid_row_start = encode_grid_line(&gp.grid_start);
933
                cold.grid_row_end = encode_grid_line(&gp.grid_end);
934
            }
935
        }
936

            
937
        // Tier 2 cold
938
17
        CssProperty::ZIndex(v) => {
939
17
            if let Some(exact) = v.get_property() {
940
17
                match exact {
941
                    LayoutZIndex::Auto => cold.z_index = I16_AUTO,
942
17
                    LayoutZIndex::Integer(z) => {
943
17
                        cold.z_index = if *z >= I16_SENTINEL_THRESHOLD as i32 { I16_SENTINEL } else { *z as i16 };
944
                    }
945
                }
946
            }
947
        }
948
496
        CssProperty::BorderTopStyle(v) => {
949
496
            if let Some(exact) = v.get_property() {
950
496
                let bs = border_style_to_u8(exact.inner) as u16;
951
496
                cold.border_styles_packed = (cold.border_styles_packed & !0x000F) | bs;
952
496
            }
953
        }
954
454
        CssProperty::BorderRightStyle(v) => {
955
454
            if let Some(exact) = v.get_property() {
956
454
                let bs = border_style_to_u8(exact.inner) as u16;
957
454
                cold.border_styles_packed = (cold.border_styles_packed & !0x00F0) | (bs << 4);
958
454
            }
959
        }
960
454
        CssProperty::BorderBottomStyle(v) => {
961
454
            if let Some(exact) = v.get_property() {
962
454
                let bs = border_style_to_u8(exact.inner) as u16;
963
454
                cold.border_styles_packed = (cold.border_styles_packed & !0x0F00) | (bs << 8);
964
454
            }
965
        }
966
454
        CssProperty::BorderLeftStyle(v) => {
967
454
            if let Some(exact) = v.get_property() {
968
454
                let bs = border_style_to_u8(exact.inner) as u16;
969
454
                cold.border_styles_packed = (cold.border_styles_packed & !0xF000) | (bs << 12);
970
454
            }
971
        }
972
496
        CssProperty::BorderTopColor(v) => {
973
496
            if let Some(c) = v.get_property() { cold.border_top_color = encode_color_u32(&c.inner); }
974
        }
975
454
        CssProperty::BorderRightColor(v) => {
976
454
            if let Some(c) = v.get_property() { cold.border_right_color = encode_color_u32(&c.inner); }
977
        }
978
454
        CssProperty::BorderBottomColor(v) => {
979
454
            if let Some(c) = v.get_property() { cold.border_bottom_color = encode_color_u32(&c.inner); }
980
        }
981
454
        CssProperty::BorderLeftColor(v) => {
982
454
            if let Some(c) = v.get_property() { cold.border_left_color = encode_color_u32(&c.inner); }
983
        }
984
        CssProperty::BorderSpacing(v) => {
985
            if let Some(spacing) = v.get_property() {
986
                if spacing.horizontal.metric == SizeMetric::Px {
987
                    cold.border_spacing_h = encode_resolved_px_i16(spacing.horizontal.number.get());
988
                }
989
                if spacing.vertical.metric == SizeMetric::Px {
990
                    cold.border_spacing_v = encode_resolved_px_i16(spacing.vertical.number.get());
991
                }
992
            }
993
        }
994
        CssProperty::TabSize(v) => { cold.tab_size = encode_css_pixel_as_i16(v); }
995

            
996
        // Tier 2b text
997
346
        CssProperty::TextColor(v) => {
998
346
            if let Some(color) = v.get_property() {
999
346
                let c = &color.inner;
346
                text.text_color = ((c.r as u32) << 24) | ((c.g as u32) << 16) | ((c.b as u32) << 8) | (c.a as u32);
346
            }
        }
1059
        CssProperty::FontFamily(v) => {
1059
            if let Some(families) = v.get_property() {
1059
                let mut hasher = DefaultHasher::new();
1059
                families.hash(&mut hasher);
1059
                let h = hasher.finish();
1059
                let h = if h == 0 { 1 } else { h };
1059
                text.font_family_hash = h;
1059
                font_hash_map.insert(h, families.clone());
            }
        }
34
        CssProperty::LineHeight(v) => {
34
            if let Some(lh) = v.get_property() {
34
                let pct_x10 = (lh.inner.normalized() * 1000.0).round() as i32;
34
                if pct_x10 >= -32768 && pct_x10 < I16_SENTINEL_THRESHOLD as i32 {
34
                    text.line_height = pct_x10 as i16;
34
                } else {
                    text.line_height = I16_SENTINEL;
                }
            }
        }
        CssProperty::LetterSpacing(v) => { text.letter_spacing = encode_css_pixel_as_i16(v); }
        CssProperty::WordSpacing(v) => { text.word_spacing = encode_css_pixel_as_i16(v); }
        CssProperty::TextIndent(v) => { text.text_indent = encode_css_pixel_as_i16(v); }
        // Border radii (cold): encode px × 10 into i16; sentinel stays = unset/0
        CssProperty::BorderTopLeftRadius(v) => {
            if let Some(exact) = v.get_property() {
                if exact.inner.metric == SizeMetric::Px {
                    cold.border_top_left_radius = encode_resolved_px_i16(exact.inner.number.get());
                }
            }
        }
        CssProperty::BorderTopRightRadius(v) => {
            if let Some(exact) = v.get_property() {
                if exact.inner.metric == SizeMetric::Px {
                    cold.border_top_right_radius = encode_resolved_px_i16(exact.inner.number.get());
                }
            }
        }
        CssProperty::BorderBottomLeftRadius(v) => {
            if let Some(exact) = v.get_property() {
                if exact.inner.metric == SizeMetric::Px {
                    cold.border_bottom_left_radius = encode_resolved_px_i16(exact.inner.number.get());
                }
            }
        }
        CssProperty::BorderBottomRightRadius(v) => {
            if let Some(exact) = v.get_property() {
                if exact.inner.metric == SizeMetric::Px {
                    cold.border_bottom_right_radius = encode_resolved_px_i16(exact.inner.number.get());
                }
            }
        }
        // Opacity: encode as 0-254, 255 = sentinel (unset/default = 1.0)
143
        CssProperty::Opacity(v) => {
143
            if let Some(exact) = v.get_property() {
143
                let o = exact.inner.normalized().clamp(0.0, 1.0);
143
                let byte = (o * 254.0).round() as u8;
143
                // byte is in [0, 254], never collides with OPACITY_SENTINEL=255
143
                cold.opacity = byte;
143
            }
        }
        // has-flags: set bit whenever property is set (regardless of value).
        // Getter uses this as a fast "is the default" bail-out.
126
        CssProperty::Transform(v) => {
126
            if v.get_property().is_some() { cold.hot_flags |= HOT_FLAG_HAS_TRANSFORM; }
        }
        CssProperty::TransformOrigin(v) => {
            if v.get_property().is_some() { cold.hot_flags |= HOT_FLAG_HAS_TRANSFORM_ORIGIN; }
        }
42
        CssProperty::BoxShadowTop(v) => {
42
            if v.get_property().is_some() { cold.hot_flags |= HOT_FLAG_HAS_BOX_SHADOW; }
        }
42
        CssProperty::BoxShadowBottom(v) => {
42
            if v.get_property().is_some() { cold.hot_flags |= HOT_FLAG_HAS_BOX_SHADOW; }
        }
42
        CssProperty::BoxShadowLeft(v) => {
42
            if v.get_property().is_some() { cold.hot_flags |= HOT_FLAG_HAS_BOX_SHADOW; }
        }
42
        CssProperty::BoxShadowRight(v) => {
42
            if v.get_property().is_some() { cold.hot_flags |= HOT_FLAG_HAS_BOX_SHADOW; }
        }
2058
        CssProperty::TextDecoration(v) => {
2058
            if v.get_property().is_some() { cold.hot_flags |= HOT_FLAG_HAS_TEXT_DECORATION; }
        }
        CssProperty::ScrollbarGutter(v) => {
            if let Some(exact) = v.get_property() {
                use azul_css::props::layout::overflow::StyleScrollbarGutter;
                let bits: u8 = match exact {
                    StyleScrollbarGutter::Auto => SCROLLBAR_GUTTER_AUTO,
                    StyleScrollbarGutter::Stable => SCROLLBAR_GUTTER_STABLE,
                    StyleScrollbarGutter::StableBothEdges => SCROLLBAR_GUTTER_BOTH_EDGES,
                };
                cold.hot_flags = (cold.hot_flags & !HOT_FLAG_SCROLLBAR_GUTTER_MASK)
                    | ((bits << HOT_FLAG_SCROLLBAR_GUTTER_SHIFT) & HOT_FLAG_SCROLLBAR_GUTTER_MASK);
            }
        }
5538
        CssProperty::BackgroundContent(v) => {
5538
            if v.get_property().is_some() { cold.hot_flags |= HOT_FLAG_HAS_BACKGROUND; }
        }
        CssProperty::ClipPath(v) => {
            if v.get_property().is_some() { cold.hot_flags |= HOT_FLAG_HAS_CLIP_PATH; }
        }
        // Any scrollbar customisation sets the single `has_any_scrollbar_css`
        // bit. When unset, get_scrollbar_style can bail to UA defaults without
        // doing 8 cascade walks.
        CssProperty::ScrollbarTrack(v) => {
            if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_SCROLLBAR_CSS; }
        }
        CssProperty::ScrollbarThumb(v) => {
            if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_SCROLLBAR_CSS; }
        }
        CssProperty::ScrollbarButton(v) => {
            if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_SCROLLBAR_CSS; }
        }
        CssProperty::ScrollbarCorner(v) => {
            if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_SCROLLBAR_CSS; }
        }
        CssProperty::ScrollbarWidth(v) => {
            if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_SCROLLBAR_CSS; }
        }
        CssProperty::ScrollbarColor(v) => {
            if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_SCROLLBAR_CSS; }
        }
        CssProperty::ScrollbarVisibility(v) => {
            if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_SCROLLBAR_CSS; }
        }
        CssProperty::ScrollbarFadeDelay(v) => {
            if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_SCROLLBAR_CSS; }
        }
        CssProperty::ScrollbarFadeDuration(v) => {
            if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_SCROLLBAR_CSS; }
        }
        // Rare paint/layout props with dedicated fast-path bits.
        CssProperty::CounterReset(v) => {
            if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_COUNTER; }
        }
        CssProperty::CounterIncrement(v) => {
            if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_COUNTER; }
        }
        CssProperty::BreakBefore(v) => {
            if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_BREAK; }
        }
        CssProperty::BreakAfter(v) => {
            if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_BREAK; }
        }
        CssProperty::TextOrientation(v) => {
            if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_TEXT_ORIENTATION; }
        }
        CssProperty::TextShadow(v) => {
            if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_TEXT_SHADOW; }
        }
        CssProperty::BackdropFilter(v) => {
            if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_BACKDROP_FILTER; }
        }
        CssProperty::Filter(v) => {
            if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_FILTER; }
        }
        CssProperty::MixBlendMode(v) => {
            if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_MIX_BLEND_MODE; }
        }
        // Non-compact properties (background, etc.) — handled by get_property_slow fallback
12848
        _ => {}
    }
224795
}
/// OR the DOM-level declared-flag for rarely-set text properties. Called once
/// per property per node so that when a flag bit is clear, callers
/// (e.g. `translate_to_text3_constraints`) can skip the cascade walk and use
/// the default value — the slow walk would never find a declaration anyway.
117846
fn update_dom_declared_flags(prop: &CssProperty, flags: &mut u32) {
    // Only mark if the property value is actually "set" (not Auto/Initial/etc.).
    // Using `get_property().is_some()` mirrors the pattern used elsewhere in
    // this builder for has-X bits.
117846
    match prop {
        CssProperty::ShapeInside(v) => if v.get_property().is_some() { *flags |= DOM_HAS_SHAPE_INSIDE; }
        CssProperty::ShapeOutside(v) => if v.get_property().is_some() { *flags |= DOM_HAS_SHAPE_OUTSIDE; }
        CssProperty::TextJustify(v) => if v.get_property().is_some() { *flags |= DOM_HAS_TEXT_JUSTIFY; }
        CssProperty::TextIndent(v) => if v.get_property().is_some() { *flags |= DOM_HAS_TEXT_INDENT; }
        CssProperty::ColumnCount(v) => if v.get_property().is_some() { *flags |= DOM_HAS_COLUMN_COUNT; }
        CssProperty::ColumnGap(v) => if v.get_property().is_some() { *flags |= DOM_HAS_COLUMN_GAP; }
        CssProperty::ColumnWidth(v) => if v.get_property().is_some() { *flags |= DOM_HAS_COLUMN_WIDTH; }
        CssProperty::InitialLetter(v) => if v.get_property().is_some() { *flags |= DOM_HAS_INITIAL_LETTER; }
        CssProperty::InitialLetterAlign(v) => if v.get_property().is_some() { *flags |= DOM_HAS_INITIAL_LETTER_ALIGN; }
        CssProperty::LineClamp(v) => if v.get_property().is_some() { *flags |= DOM_HAS_LINE_CLAMP; }
        CssProperty::HangingPunctuation(v) => if v.get_property().is_some() { *flags |= DOM_HAS_HANGING_PUNCTUATION; }
        CssProperty::TextCombineUpright(v) => if v.get_property().is_some() { *flags |= DOM_HAS_TEXT_COMBINE_UPRIGHT; }
        CssProperty::ExclusionMargin(v) => if v.get_property().is_some() { *flags |= DOM_HAS_EXCLUSION_MARGIN; }
        CssProperty::ShapeMargin(v) => if v.get_property().is_some() { *flags |= DOM_HAS_SHAPE_MARGIN; }
        CssProperty::HyphenationLanguage(v) => if v.get_property().is_some() { *flags |= DOM_HAS_HYPHENATION_LANGUAGE; }
        CssProperty::UnicodeBidi(v) => if v.get_property().is_some() { *flags |= DOM_HAS_UNICODE_BIDI; }
        CssProperty::TextBoxTrim(v) => if v.get_property().is_some() { *flags |= DOM_HAS_TEXT_BOX_TRIM; }
        CssProperty::Hyphens(v) => if v.get_property().is_some() { *flags |= DOM_HAS_HYPHENS; }
        CssProperty::WordBreak(v) => if v.get_property().is_some() { *flags |= DOM_HAS_WORD_BREAK; }
42
        CssProperty::OverflowWrap(v) => if v.get_property().is_some() { *flags |= DOM_HAS_OVERFLOW_WRAP; }
        CssProperty::LineBreak(v) => if v.get_property().is_some() { *flags |= DOM_HAS_LINE_BREAK; }
        CssProperty::TextAlignLast(v) => if v.get_property().is_some() { *flags |= DOM_HAS_TEXT_ALIGN_LAST; }
34
        CssProperty::LineHeight(v) => if v.get_property().is_some() { *flags |= DOM_HAS_LINE_HEIGHT; }
117770
        _ => {}
    }
117846
}
// =============================================================================
// Helper encoders for dimension properties
// =============================================================================
/// Encode a GridLine into i16: Auto=I16_AUTO, Line(n)=n, Span(n)=-(n).
/// Named lines fall back to I16_SENTINEL (not compact-encodable).
fn encode_grid_line(line: &azul_css::props::layout::grid::GridLine) -> i16 {
    use azul_css::props::layout::grid::GridLine;
    match line {
        GridLine::Auto => I16_AUTO,
        GridLine::Line(n) => {
            if *n >= -32000 && *n <= 32000 { *n as i16 } else { I16_SENTINEL }
        }
        GridLine::Span(n) => {
            if *n >= 1 && *n <= 32000 { -(*n as i16) } else { I16_SENTINEL }
        }
        GridLine::Named(_) => I16_SENTINEL,
    }
}
/// Encode a CssPropertyValue<LayoutWidth> into u32 compact form.
15123
fn encode_layout_width<T: LayoutWidthLike>(val: &CssPropertyValue<T>) -> u32 {
15123
    match val {
15039
        CssPropertyValue::Exact(w) => w.encode_compact_u32(),
84
        CssPropertyValue::Auto => U32_AUTO,
        CssPropertyValue::Initial => U32_INITIAL,
        CssPropertyValue::Inherit => U32_INHERIT,
        CssPropertyValue::None => U32_NONE,
        _ => U32_SENTINEL,
    }
15123
}
/// Encode a CssPropertyValue<LayoutHeight> into u32 compact form.
9702
fn encode_layout_height<T: LayoutWidthLike>(val: &CssPropertyValue<T>) -> u32 {
9702
    encode_layout_width(val)
9702
}
/// Trait for types that can be encoded as compact u32 dimension values.
/// Implemented for LayoutWidth, LayoutHeight (which are Auto|Px|MinContent|MaxContent|Calc enums).
trait LayoutWidthLike {
    fn encode_compact_u32(&self) -> u32;
}
impl LayoutWidthLike for LayoutWidth {
5337
    fn encode_compact_u32(&self) -> u32 {
5337
        match self {
            LayoutWidth::Auto => U32_AUTO,
5320
            LayoutWidth::Px(pv) => encode_pixel_value_u32(pv),
            LayoutWidth::MinContent => U32_MIN_CONTENT,
            LayoutWidth::MaxContent => U32_MAX_CONTENT,
            LayoutWidth::FitContent(_) => U32_SENTINEL,
17
            LayoutWidth::Calc(_) => U32_SENTINEL, // Calc → overflow to tier 3
        }
5337
    }
}
impl LayoutWidthLike for LayoutHeight {
9702
    fn encode_compact_u32(&self) -> u32 {
9702
        match self {
            LayoutHeight::Auto => U32_AUTO,
9702
            LayoutHeight::Px(pv) => encode_pixel_value_u32(pv),
            LayoutHeight::MinContent => U32_MIN_CONTENT,
            LayoutHeight::MaxContent => U32_MAX_CONTENT,
            LayoutHeight::FitContent(_) => U32_SENTINEL,
            LayoutHeight::Calc(_) => U32_SENTINEL,
        }
9702
    }
}
/// Encode a CssPropertyValue wrapping a simple PixelValue struct (LayoutMinWidth, etc.)
2266
fn encode_pixel_prop<T: HasInnerPixelValue>(val: &CssPropertyValue<T>) -> u32 {
2266
    match val {
2266
        CssPropertyValue::Exact(inner) => encode_pixel_value_u32(&inner.get_inner_pixel()),
        CssPropertyValue::Auto => U32_AUTO,
        CssPropertyValue::Initial => U32_INITIAL,
        CssPropertyValue::Inherit => U32_INHERIT,
        CssPropertyValue::None => U32_NONE,
        _ => U32_SENTINEL,
    }
2266
}
/// Trait for dimension structs wrapping `inner: PixelValue`.
trait HasInnerPixelValue {
    fn get_inner_pixel(&self) -> azul_css::props::basic::pixel::PixelValue;
}
macro_rules! impl_has_inner_pixel {
    ($($ty:ty),*) => {
        $(
            impl HasInnerPixelValue for $ty {
145829
                fn get_inner_pixel(&self) -> azul_css::props::basic::pixel::PixelValue {
145829
                    self.inner
145829
                }
            }
        )*
    };
}
impl_has_inner_pixel!(
    azul_css::props::layout::dimensions::LayoutMinWidth,
    azul_css::props::layout::dimensions::LayoutMaxWidth,
    azul_css::props::layout::dimensions::LayoutMinHeight,
    azul_css::props::layout::dimensions::LayoutMaxHeight,
    azul_css::props::basic::font::StyleFontSize,
    azul_css::props::layout::spacing::LayoutPaddingTop,
    azul_css::props::layout::spacing::LayoutPaddingRight,
    azul_css::props::layout::spacing::LayoutPaddingBottom,
    azul_css::props::layout::spacing::LayoutPaddingLeft,
    azul_css::props::layout::spacing::LayoutMarginTop,
    azul_css::props::layout::spacing::LayoutMarginRight,
    azul_css::props::layout::spacing::LayoutMarginBottom,
    azul_css::props::layout::spacing::LayoutMarginLeft,
    azul_css::props::style::border::LayoutBorderTopWidth,
    azul_css::props::style::border::LayoutBorderRightWidth,
    azul_css::props::style::border::LayoutBorderBottomWidth,
    azul_css::props::style::border::LayoutBorderLeftWidth,
    azul_css::props::layout::position::LayoutTop,
    azul_css::props::layout::position::LayoutRight,
    azul_css::props::layout::position::LayoutInsetBottom,
    azul_css::props::layout::position::LayoutLeft,
    azul_css::props::style::text::StyleLetterSpacing,
    azul_css::props::style::text::StyleWordSpacing,
    azul_css::props::style::text::StyleTextIndent,
    azul_css::props::style::text::StyleTabSize
);
/// Encode a CssPropertyValue<T> where T wraps a PixelValue, as i16 (×10 resolved px).
/// Delegates to the canonical `azul_css::compact_cache::encode_css_pixel_as_i16`.
143647
fn encode_css_pixel_as_i16<T: HasInnerPixelValue>(val: &CssPropertyValue<T>) -> i16 {
143647
    let mapped = match val {
143563
        CssPropertyValue::Exact(inner) => CssPropertyValue::Exact(inner.get_inner_pixel()),
84
        CssPropertyValue::Auto => CssPropertyValue::Auto,
        CssPropertyValue::Initial => CssPropertyValue::Initial,
        CssPropertyValue::Inherit => CssPropertyValue::Inherit,
        CssPropertyValue::None => CssPropertyValue::None,
        _ => return I16_SENTINEL,
    };
143647
    azul_css::compact_cache::encode_css_pixel_as_i16(&mapped)
143647
}
/// Encode margin: same as encode_css_pixel_as_i16 but Auto is a distinct value.
71400
fn encode_margin_i16<T: HasInnerPixelValue>(val: &CssPropertyValue<T>) -> i16 {
71400
    encode_css_pixel_as_i16(val)
71400
}
/// Encode CssPropertyValue<LayoutFlexBasis> — LayoutFlexBasis is Auto | Exact(PixelValue).
fn encode_flex_basis(val: &CssPropertyValue<LayoutFlexBasis>) -> u32 {
    match val {
        CssPropertyValue::Exact(fb) => match fb {
            LayoutFlexBasis::Auto => U32_AUTO,
            LayoutFlexBasis::Exact(pv) => encode_pixel_value_u32(pv),
        },
        CssPropertyValue::Auto => U32_AUTO,
        CssPropertyValue::Initial => U32_INITIAL,
        CssPropertyValue::Inherit => U32_INHERIT,
        CssPropertyValue::None => U32_NONE,
        _ => U32_SENTINEL,
    }
}