1
//! Compact layout property cache — three-tier numeric encoding
2
//!
3
//! Replaces BTreeMap-based CSS property lookups with cache-friendly arrays.
4
//!
5
//! - **Tier 1**: `Vec<u64>` — ALL 21 enum properties bitpacked (8 B/node)
6
//! - **Tier 2 hot**: `Vec<CompactNodeProps>` — layout-critical numeric dimensions (68 B/node)
7
//! - **Tier 2 cold**: `Vec<CompactNodePropsCold>` — paint-only properties (28 B/node)
8
//! - **Tier 2b**: `Vec<CompactTextProps>` — text/IFC properties (24 B/node)
9
//!
10
//! Non-compact properties (background, box-shadow, transform, etc.) are
11
//! resolved via the slow cascade path in `CssPropertyCache::get_property_slow()`.
12

            
13
use crate::props::basic::length::{FloatValue, SizeMetric};
14
use crate::props::basic::pixel::PixelValue;
15
use crate::props::layout::{
16
    display::LayoutDisplay,
17
    dimensions::{LayoutHeight, LayoutWidth, LayoutMaxHeight, LayoutMaxWidth, LayoutMinHeight, LayoutMinWidth},
18
    flex::{
19
        LayoutAlignContent, LayoutAlignItems, LayoutAlignSelf, LayoutFlexDirection, LayoutFlexWrap,
20
        LayoutJustifyContent,
21
    },
22
    grid::{LayoutGridAutoFlow, LayoutJustifySelf, LayoutJustifyItems},
23
    overflow::LayoutOverflow,
24
    position::LayoutPosition,
25
    wrapping::{LayoutClear, LayoutWritingMode},
26
    table::StyleBorderCollapse,
27
};
28
use crate::props::layout::display::LayoutFloat;
29
use crate::props::layout::dimensions::LayoutBoxSizing;
30
use crate::props::basic::font::{StyleFontStyle, StyleFontWeight};
31
use crate::props::basic::color::ColorU;
32
use crate::props::style::{StyleTextAlign, StyleVerticalAlign, StyleVisibility, StyleWhiteSpace, StyleDirection};
33
use crate::props::style::border::BorderStyle;
34
use crate::props::property::{CssProperty, CssPropertyType};
35
use crate::css::CssPropertyValue;
36
use alloc::boxed::Box;
37
use alloc::vec::Vec;
38

            
39
// =============================================================================
40
// Sentinel Constants
41
// =============================================================================
42

            
43
/// u16 sentinel values (for resolved-px ×10 and flex ×100)
44
pub const U16_SENTINEL: u16 = 0xFFFF;
45
/// Any u16 value >= this threshold is a sentinel, not a real value
46
pub const U16_SENTINEL_THRESHOLD: u16 = 0xFFF9;
47

            
48
/// i16 sentinel values (for signed resolved-px ×10)
49
pub const I16_SENTINEL: i16 = 0x7FFF;       // 32767
50
pub const I16_AUTO: i16 = 0x7FFE;           // 32766
51
pub const I16_INHERIT: i16 = 0x7FFD;        // 32765
52
pub const I16_INITIAL: i16 = 0x7FFC;        // 32764
53
/// Any i16 value >= this threshold is a sentinel
54
pub const I16_SENTINEL_THRESHOLD: i16 = 0x7FFC; // 32764
55

            
56
/// u32 sentinel values (for dimension properties with unit info)
57
pub const U32_SENTINEL: u32 = 0xFFFFFFFF;
58
pub const U32_AUTO: u32 = 0xFFFFFFFE;
59
pub const U32_NONE: u32 = 0xFFFFFFFD;
60
pub const U32_INHERIT: u32 = 0xFFFFFFFC;
61
pub const U32_INITIAL: u32 = 0xFFFFFFFB;
62
pub const U32_MIN_CONTENT: u32 = 0xFFFFFFFA;
63
pub const U32_MAX_CONTENT: u32 = 0xFFFFFFF9;
64
/// Any u32 value >= this threshold is a sentinel
65
pub const U32_SENTINEL_THRESHOLD: u32 = 0xFFFFFFF9;
66

            
67
// =============================================================================
68
// Tier 1: u64 bitfield — ALL enum properties
69
// =============================================================================
70
//
71
// Bit layout (52 bits used, 12 spare):
72
//   [4:0]    display          5 bits  (22 variants)
73
//   [7:5]    position         3 bits  (5 variants)
74
//   [9:8]    float            2 bits  (3 variants)
75
//   [12:10]  overflow_x       3 bits  (5 variants)
76
//   [15:13]  overflow_y       3 bits  (5 variants)
77
//   [16]     box_sizing       1 bit   (2 variants)
78
//   [18:17]  flex_direction   2 bits  (4 variants)
79
//   [20:19]  flex_wrap        2 bits  (3 variants)
80
//   [23:21]  justify_content  3 bits  (8 variants)
81
//   [26:24]  align_items      3 bits  (5 variants)
82
//   [29:27]  align_content    3 bits  (6 variants)
83
//   [31:30]  writing_mode     2 bits  (3 variants)
84
//   [33:32]  clear            2 bits  (4 variants)
85
//   [37:34]  font_weight      4 bits  (11 variants)
86
//   [39:38]  font_style       2 bits  (3 variants)
87
//   [42:40]  text_align       3 bits  (6 variants)
88
//   [44:43]  visibility       2 bits  (3 variants)
89
//   [47:45]  white_space      3 bits  (6 variants)
90
//   [48]     direction        1 bit   (2 variants)
91
//   [51:49]  vertical_align   3 bits  (8 variants)
92
//   [52]     border_collapse  1 bit   (2 variants)
93
//   [63:53]  (spare / sentinel flags)
94

            
95
// Bit offsets within u64
96
pub const DISPLAY_SHIFT: u32 = 0;
97
pub const POSITION_SHIFT: u32 = 5;
98
pub const FLOAT_SHIFT: u32 = 8;
99
pub const OVERFLOW_X_SHIFT: u32 = 10;
100
pub const OVERFLOW_Y_SHIFT: u32 = 13;
101
pub const BOX_SIZING_SHIFT: u32 = 16;
102
pub const FLEX_DIRECTION_SHIFT: u32 = 17;
103
pub const FLEX_WRAP_SHIFT: u32 = 19;
104
pub const JUSTIFY_CONTENT_SHIFT: u32 = 21;
105
pub const ALIGN_ITEMS_SHIFT: u32 = 24;
106
pub const ALIGN_CONTENT_SHIFT: u32 = 27;
107
pub const WRITING_MODE_SHIFT: u32 = 30;
108
pub const CLEAR_SHIFT: u32 = 32;
109
pub const FONT_WEIGHT_SHIFT: u32 = 34;
110
pub const FONT_STYLE_SHIFT: u32 = 38;
111
pub const TEXT_ALIGN_SHIFT: u32 = 40;
112
pub const VISIBILITY_SHIFT: u32 = 43;
113
pub const WHITE_SPACE_SHIFT: u32 = 45;
114
pub const DIRECTION_SHIFT: u32 = 48;
115
pub const VERTICAL_ALIGN_SHIFT: u32 = 49;
116
pub const BORDER_COLLAPSE_SHIFT: u32 = 52;
117

            
118
// Bit masks
119
pub const DISPLAY_MASK: u64 = 0x1F;     // 5 bits
120
pub const POSITION_MASK: u64 = 0x07;    // 3 bits
121
pub const FLOAT_MASK: u64 = 0x03;       // 2 bits
122
pub const OVERFLOW_MASK: u64 = 0x07;    // 3 bits
123
pub const BOX_SIZING_MASK: u64 = 0x01;  // 1 bit
124
pub const FLEX_DIR_MASK: u64 = 0x03;    // 2 bits
125
pub const FLEX_WRAP_MASK: u64 = 0x03;   // 2 bits
126
pub const JUSTIFY_MASK: u64 = 0x07;     // 3 bits
127
pub const ALIGN_MASK: u64 = 0x07;       // 3 bits
128
pub const WRITING_MODE_MASK: u64 = 0x03;// 2 bits
129
pub const CLEAR_MASK: u64 = 0x03;       // 2 bits
130
pub const FONT_WEIGHT_MASK: u64 = 0x0F; // 4 bits
131
pub const FONT_STYLE_MASK: u64 = 0x03;  // 2 bits
132
pub const TEXT_ALIGN_MASK: u64 = 0x07;  // 3 bits
133
pub const VISIBILITY_MASK: u64 = 0x03;  // 2 bits
134
pub const WHITE_SPACE_MASK: u64 = 0x07; // 3 bits
135
pub const DIRECTION_MASK: u64 = 0x01;   // 1 bit
136
pub const VERTICAL_ALIGN_MASK: u64 = 0x07; // 3 bits
137
pub const BORDER_COLLAPSE_MASK: u64 = 0x01; // 1 bit
138

            
139
pub const ALIGN_SELF_SHIFT: u32 = 53;
140
pub const JUSTIFY_SELF_SHIFT: u32 = 56;
141
pub const GRID_AUTO_FLOW_SHIFT: u32 = 59;
142
pub const JUSTIFY_ITEMS_SHIFT: u32 = 61;
143
pub const ALIGN_SELF_MASK: u64 = 0x07;     // 3 bits
144
pub const JUSTIFY_SELF_MASK: u64 = 0x07;   // 3 bits
145
pub const GRID_AUTO_FLOW_MASK: u64 = 0x03; // 2 bits (row/col × dense)
146
pub const JUSTIFY_ITEMS_MASK: u64 = 0x03;  // 2 bits (start/center/end/stretch)
147

            
148
/// Special value stored in the spare bits [63:51] to indicate this node has
149
/// NO tier-1 data (i.e., all defaults). 0 is a valid all-defaults encoding,
150
/// so we use bit 63 as a "tier1 populated" flag. If bit 63 is 0 and all other
151
/// bits are 0, it means "all defaults" (Display::Block, Position::Static, etc.).
152
/// We set bit 63 = 1 to mark that the node HAS been populated.
153
pub const TIER1_POPULATED_BIT: u64 = 1 << 63;
154

            
155
// =============================================================================
156
// Safe from_u8 conversion functions (no transmute!)
157
// =============================================================================
158

            
159
/// Decode display from u8. **0 = Block** (most common HTML default).
160
/// Value 31 (0x1F) = sentinel: look up in slow path for uncommon values.
161
/// Returns default (Block) on invalid input.
162
#[inline(always)]
163
525622
pub fn layout_display_from_u8(v: u8) -> LayoutDisplay {
164
525622
    match v {
165
190544
        0 => LayoutDisplay::Block,        // default when bits are 0
166
222356
        1 => LayoutDisplay::Inline,
167
1751
        2 => LayoutDisplay::InlineBlock,
168
4587
        3 => LayoutDisplay::Flex,
169
36
        4 => LayoutDisplay::None,
170
1
        5 => LayoutDisplay::InlineFlex,
171
14106
        6 => LayoutDisplay::Table,
172
1
        7 => LayoutDisplay::InlineTable,
173
1
        8 => LayoutDisplay::TableRowGroup,
174
1
        9 => LayoutDisplay::TableHeaderGroup,
175
1
        10 => LayoutDisplay::TableFooterGroup,
176
18726
        11 => LayoutDisplay::TableRow,
177
1
        12 => LayoutDisplay::TableColumnGroup,
178
1
        13 => LayoutDisplay::TableColumn,
179
73501
        14 => LayoutDisplay::TableCell,
180
1
        15 => LayoutDisplay::TableCaption,
181
1
        16 => LayoutDisplay::FlowRoot,
182
1
        17 => LayoutDisplay::ListItem,
183
1
        18 => LayoutDisplay::RunIn,
184
1
        19 => LayoutDisplay::Marker,
185
1
        20 => LayoutDisplay::Grid,
186
1
        21 => LayoutDisplay::InlineGrid,
187
1
        22 => LayoutDisplay::Contents,
188
        _ => LayoutDisplay::Block, // fallback + sentinel (31)
189
    }
190
525622
}
191

            
192
/// Encode display to u8. **0 = Block** (most common HTML default).
193
#[inline(always)]
194
47691
pub fn layout_display_to_u8(v: LayoutDisplay) -> u8 {
195
47691
    match v {
196
20721
        LayoutDisplay::Block => 0,         // 0 = default when bits unset
197
19189
        LayoutDisplay::Inline => 1,
198
85
        LayoutDisplay::InlineBlock => 2,
199
452
        LayoutDisplay::Flex => 3,
200
43
        LayoutDisplay::None => 4,
201
1
        LayoutDisplay::InlineFlex => 5,
202
1387
        LayoutDisplay::Table => 6,
203
1
        LayoutDisplay::InlineTable => 7,
204
1
        LayoutDisplay::TableRowGroup => 8,
205
1
        LayoutDisplay::TableHeaderGroup => 9,
206
1
        LayoutDisplay::TableFooterGroup => 10,
207
1429
        LayoutDisplay::TableRow => 11,
208
1
        LayoutDisplay::TableColumnGroup => 12,
209
1
        LayoutDisplay::TableColumn => 13,
210
3571
        LayoutDisplay::TableCell => 14,
211
1
        LayoutDisplay::TableCaption => 15,
212
1
        LayoutDisplay::FlowRoot => 16,
213
1
        LayoutDisplay::ListItem => 17,
214
1
        LayoutDisplay::RunIn => 18,
215
1
        LayoutDisplay::Marker => 19,
216
799
        LayoutDisplay::Grid => 20,
217
1
        LayoutDisplay::InlineGrid => 21,
218
2
        LayoutDisplay::Contents => 22,
219
    }
220
47691
}
221

            
222
#[inline(always)]
223
296494
pub fn layout_position_from_u8(v: u8) -> LayoutPosition {
224
296494
    match v {
225
295439
        0 => LayoutPosition::Static,
226
597
        1 => LayoutPosition::Relative,
227
456
        2 => LayoutPosition::Absolute,
228
1
        3 => LayoutPosition::Fixed,
229
1
        4 => LayoutPosition::Sticky,
230
        _ => LayoutPosition::Static,
231
    }
232
296494
}
233

            
234
#[inline(always)]
235
129
pub fn layout_position_to_u8(v: LayoutPosition) -> u8 {
236
129
    match v {
237
3
        LayoutPosition::Static => 0,
238
62
        LayoutPosition::Relative => 1,
239
61
        LayoutPosition::Absolute => 2,
240
1
        LayoutPosition::Fixed => 3,
241
2
        LayoutPosition::Sticky => 4,
242
    }
243
129
}
244

            
245
/// Decode float from u8. **0 = None** (CSS initial value).
246
#[inline(always)]
247
156946
pub fn layout_float_from_u8(v: u8) -> LayoutFloat {
248
156946
    match v {
249
150643
        0 => LayoutFloat::None,            // default when bits unset
250
4342
        1 => LayoutFloat::Left,
251
1961
        2 => LayoutFloat::Right,
252
        _ => LayoutFloat::None,
253
    }
254
156946
}
255

            
256
/// Encode float to u8. **0 = None** (CSS initial value).
257
#[inline(always)]
258
1141
pub fn layout_float_to_u8(v: LayoutFloat) -> u8 {
259
1141
    match v {
260
45
        LayoutFloat::None => 0,
261
758
        LayoutFloat::Left => 1,
262
338
        LayoutFloat::Right => 2,
263
    }
264
1141
}
265

            
266
/// Decode overflow from u8. **0 = Visible** (CSS initial value).
267
#[inline(always)]
268
369225
pub fn layout_overflow_from_u8(v: u8) -> LayoutOverflow {
269
369225
    match v {
270
367469
        0 => LayoutOverflow::Visible,      // default when bits unset
271
1752
        1 => LayoutOverflow::Hidden,
272
2
        2 => LayoutOverflow::Scroll,
273
1
        3 => LayoutOverflow::Auto,
274
1
        4 => LayoutOverflow::Clip,
275
        _ => LayoutOverflow::Visible,
276
    }
277
369225
}
278

            
279
/// Encode overflow to u8. **0 = Visible** (CSS initial value).
280
#[inline(always)]
281
335
pub fn layout_overflow_to_u8(v: LayoutOverflow) -> u8 {
282
335
    match v {
283
4
        LayoutOverflow::Visible => 0,      // 0 = default when bits unset
284
326
        LayoutOverflow::Hidden => 1,
285
2
        LayoutOverflow::Scroll => 2,
286
1
        LayoutOverflow::Auto => 3,
287
2
        LayoutOverflow::Clip => 4,
288
    }
289
335
}
290

            
291
#[inline(always)]
292
30315
pub fn layout_box_sizing_from_u8(v: u8) -> LayoutBoxSizing {
293
30315
    match v {
294
29788
        0 => LayoutBoxSizing::ContentBox,
295
527
        1 => LayoutBoxSizing::BorderBox,
296
        _ => LayoutBoxSizing::ContentBox,
297
    }
298
30315
}
299

            
300
#[inline(always)]
301
930
pub fn layout_box_sizing_to_u8(v: LayoutBoxSizing) -> u8 {
302
930
    match v {
303
3
        LayoutBoxSizing::ContentBox => 0,
304
927
        LayoutBoxSizing::BorderBox => 1,
305
    }
306
930
}
307

            
308
#[inline(always)]
309
987
pub fn layout_flex_direction_from_u8(v: u8) -> LayoutFlexDirection {
310
987
    match v {
311
633
        0 => LayoutFlexDirection::Row,
312
1
        1 => LayoutFlexDirection::RowReverse,
313
352
        2 => LayoutFlexDirection::Column,
314
1
        3 => LayoutFlexDirection::ColumnReverse,
315
        _ => LayoutFlexDirection::Row,
316
    }
317
987
}
318

            
319
#[inline(always)]
320
338
pub fn layout_flex_direction_to_u8(v: LayoutFlexDirection) -> u8 {
321
338
    match v {
322
87
        LayoutFlexDirection::Row => 0,
323
1
        LayoutFlexDirection::RowReverse => 1,
324
248
        LayoutFlexDirection::Column => 2,
325
2
        LayoutFlexDirection::ColumnReverse => 3,
326
    }
327
338
}
328

            
329
/// 0 = NoWrap (CSS initial value for flex-wrap)
330
#[inline(always)]
331
671
pub fn layout_flex_wrap_from_u8(v: u8) -> LayoutFlexWrap {
332
671
    match v {
333
668
        0 => LayoutFlexWrap::NoWrap,       // CSS initial
334
2
        1 => LayoutFlexWrap::Wrap,
335
1
        2 => LayoutFlexWrap::WrapReverse,
336
        _ => LayoutFlexWrap::NoWrap,
337
    }
338
671
}
339

            
340
#[inline(always)]
341
43
pub fn layout_flex_wrap_to_u8(v: LayoutFlexWrap) -> u8 {
342
43
    match v {
343
3
        LayoutFlexWrap::NoWrap => 0,
344
38
        LayoutFlexWrap::Wrap => 1,
345
2
        LayoutFlexWrap::WrapReverse => 2,
346
    }
347
43
}
348

            
349
#[inline(always)]
350
676
pub fn layout_justify_content_from_u8(v: u8) -> LayoutJustifyContent {
351
676
    match v {
352
633
        0 => LayoutJustifyContent::FlexStart,
353
1
        1 => LayoutJustifyContent::FlexEnd,
354
1
        2 => LayoutJustifyContent::Start,
355
1
        3 => LayoutJustifyContent::End,
356
36
        4 => LayoutJustifyContent::Center,
357
2
        5 => LayoutJustifyContent::SpaceBetween,
358
1
        6 => LayoutJustifyContent::SpaceAround,
359
1
        7 => LayoutJustifyContent::SpaceEvenly,
360
        _ => LayoutJustifyContent::FlexStart,
361
    }
362
676
}
363

            
364
#[inline(always)]
365
72
pub fn layout_justify_content_to_u8(v: LayoutJustifyContent) -> u8 {
366
72
    match v {
367
3
        LayoutJustifyContent::FlexStart => 0,
368
1
        LayoutJustifyContent::FlexEnd => 1,
369
1
        LayoutJustifyContent::Start => 2,
370
1
        LayoutJustifyContent::End => 3,
371
61
        LayoutJustifyContent::Center => 4,
372
2
        LayoutJustifyContent::SpaceBetween => 5,
373
1
        LayoutJustifyContent::SpaceAround => 6,
374
2
        LayoutJustifyContent::SpaceEvenly => 7,
375
    }
376
72
}
377

            
378
#[inline(always)]
379
673
pub fn layout_align_items_from_u8(v: u8) -> LayoutAlignItems {
380
673
    match v {
381
633
        0 => LayoutAlignItems::Stretch,
382
37
        1 => LayoutAlignItems::Center,
383
1
        2 => LayoutAlignItems::Start,
384
1
        3 => LayoutAlignItems::End,
385
1
        4 => LayoutAlignItems::Baseline,
386
        _ => LayoutAlignItems::Stretch,
387
    }
388
673
}
389

            
390
#[inline(always)]
391
69
pub fn layout_align_items_to_u8(v: LayoutAlignItems) -> u8 {
392
69
    match v {
393
3
        LayoutAlignItems::Stretch => 0,
394
62
        LayoutAlignItems::Center => 1,
395
1
        LayoutAlignItems::Start => 2,
396
1
        LayoutAlignItems::End => 3,
397
2
        LayoutAlignItems::Baseline => 4,
398
    }
399
69
}
400

            
401
#[inline(always)]
402
50
pub fn layout_align_self_to_u8(v: LayoutAlignSelf) -> u8 {
403
50
    match v {
404
2
        LayoutAlignSelf::Auto => 0,
405
43
        LayoutAlignSelf::Stretch => 1,
406
1
        LayoutAlignSelf::Center => 2,
407
1
        LayoutAlignSelf::Start => 3,
408
1
        LayoutAlignSelf::End => 4,
409
2
        LayoutAlignSelf::Baseline => 5,
410
    }
411
50
}
412

            
413
#[inline(always)]
414
672
pub fn layout_align_self_from_u8(v: u8) -> LayoutAlignSelf {
415
672
    match v {
416
632
        0 => LayoutAlignSelf::Auto,
417
36
        1 => LayoutAlignSelf::Stretch,
418
1
        2 => LayoutAlignSelf::Center,
419
1
        3 => LayoutAlignSelf::Start,
420
1
        4 => LayoutAlignSelf::End,
421
1
        5 => LayoutAlignSelf::Baseline,
422
        _ => LayoutAlignSelf::Auto,
423
    }
424
672
}
425

            
426
#[inline(always)]
427
7
pub fn layout_justify_self_to_u8(v: LayoutJustifySelf) -> u8 {
428
7
    match v {
429
2
        LayoutJustifySelf::Auto => 0,
430
1
        LayoutJustifySelf::Start => 1,
431
1
        LayoutJustifySelf::End => 2,
432
1
        LayoutJustifySelf::Center => 3,
433
2
        LayoutJustifySelf::Stretch => 4,
434
    }
435
7
}
436

            
437
#[inline(always)]
438
671
pub fn layout_justify_self_from_u8(v: u8) -> LayoutJustifySelf {
439
671
    match v {
440
667
        0 => LayoutJustifySelf::Auto,
441
1
        1 => LayoutJustifySelf::Start,
442
1
        2 => LayoutJustifySelf::End,
443
1
        3 => LayoutJustifySelf::Center,
444
1
        4 => LayoutJustifySelf::Stretch,
445
        _ => LayoutJustifySelf::Auto,
446
    }
447
671
}
448

            
449
// Tier1 uses 0 as the "unset" sentinel for every enum. For justify-items
450
// the CSS default is `normal` which behaves as `stretch` on grid items,
451
// so 0 must decode to Stretch (not Start). Getting this wrong leaves
452
// every unset grid container reporting justify-items: Start, which
453
// forces taffy to content-size items instead of stretching them across
454
// their column tracks — exactly the calc.c regression.
455
#[inline(always)]
456
7
pub fn layout_justify_items_to_u8(v: LayoutJustifyItems) -> u8 {
457
7
    match v {
458
3
        LayoutJustifyItems::Stretch => 0,
459
1
        LayoutJustifyItems::Start => 1,
460
1
        LayoutJustifyItems::End => 2,
461
2
        LayoutJustifyItems::Center => 3,
462
    }
463
7
}
464

            
465
#[inline(always)]
466
671
pub fn layout_justify_items_from_u8(v: u8) -> LayoutJustifyItems {
467
671
    match v {
468
668
        0 => LayoutJustifyItems::Stretch,
469
1
        1 => LayoutJustifyItems::Start,
470
1
        2 => LayoutJustifyItems::End,
471
1
        3 => LayoutJustifyItems::Center,
472
        _ => LayoutJustifyItems::Stretch,
473
    }
474
671
}
475

            
476
#[inline(always)]
477
6
pub fn layout_grid_auto_flow_to_u8(v: LayoutGridAutoFlow) -> u8 {
478
6
    match v {
479
2
        LayoutGridAutoFlow::Row => 0,
480
1
        LayoutGridAutoFlow::Column => 1,
481
1
        LayoutGridAutoFlow::RowDense => 2,
482
2
        LayoutGridAutoFlow::ColumnDense => 3,
483
    }
484
6
}
485

            
486
#[inline(always)]
487
5
pub fn layout_grid_auto_flow_from_u8(v: u8) -> LayoutGridAutoFlow {
488
5
    match v {
489
2
        0 => LayoutGridAutoFlow::Row,
490
1
        1 => LayoutGridAutoFlow::Column,
491
1
        2 => LayoutGridAutoFlow::RowDense,
492
1
        3 => LayoutGridAutoFlow::ColumnDense,
493
        _ => LayoutGridAutoFlow::Row,
494
    }
495
5
}
496

            
497
#[inline(always)]
498
674
pub fn layout_align_content_from_u8(v: u8) -> LayoutAlignContent {
499
674
    match v {
500
668
        0 => LayoutAlignContent::Stretch,
501
1
        1 => LayoutAlignContent::Center,
502
1
        2 => LayoutAlignContent::Start,
503
2
        3 => LayoutAlignContent::End,
504
1
        4 => LayoutAlignContent::SpaceBetween,
505
1
        5 => LayoutAlignContent::SpaceAround,
506
        _ => LayoutAlignContent::Stretch,
507
    }
508
674
}
509

            
510
#[inline(always)]
511
10
pub fn layout_align_content_to_u8(v: LayoutAlignContent) -> u8 {
512
10
    match v {
513
3
        LayoutAlignContent::Stretch => 0,
514
1
        LayoutAlignContent::Center => 1,
515
1
        LayoutAlignContent::Start => 2,
516
2
        LayoutAlignContent::End => 3,
517
1
        LayoutAlignContent::SpaceBetween => 4,
518
2
        LayoutAlignContent::SpaceAround => 5,
519
    }
520
10
}
521

            
522
#[inline(always)]
523
122611
pub fn layout_writing_mode_from_u8(v: u8) -> LayoutWritingMode {
524
122611
    match v {
525
122608
        0 => LayoutWritingMode::HorizontalTb,
526
2
        1 => LayoutWritingMode::VerticalRl,
527
1
        2 => LayoutWritingMode::VerticalLr,
528
        _ => LayoutWritingMode::HorizontalTb,
529
    }
530
122611
}
531

            
532
#[inline(always)]
533
7
pub fn layout_writing_mode_to_u8(v: LayoutWritingMode) -> u8 {
534
7
    match v {
535
3
        LayoutWritingMode::HorizontalTb => 0,
536
2
        LayoutWritingMode::VerticalRl => 1,
537
2
        LayoutWritingMode::VerticalLr => 2,
538
    }
539
7
}
540

            
541
#[inline(always)]
542
15722
pub fn layout_clear_from_u8(v: u8) -> LayoutClear {
543
15722
    match v {
544
15543
        0 => LayoutClear::None,
545
106
        1 => LayoutClear::Left,
546
36
        2 => LayoutClear::Right,
547
37
        3 => LayoutClear::Both,
548
        _ => LayoutClear::None,
549
    }
550
15722
}
551

            
552
#[inline(always)]
553
218
pub fn layout_clear_to_u8(v: LayoutClear) -> u8 {
554
218
    match v {
555
3
        LayoutClear::None => 0,
556
127
        LayoutClear::Left => 1,
557
43
        LayoutClear::Right => 2,
558
45
        LayoutClear::Both => 3,
559
    }
560
218
}
561

            
562
#[inline(always)]
563
/// 0 = Normal/400 (CSS initial value for font-weight)
564
36519
pub fn style_font_weight_from_u8(v: u8) -> StyleFontWeight {
565
36519
    match v {
566
36333
        0 => StyleFontWeight::Normal,     // CSS initial (400)
567
1
        1 => StyleFontWeight::W100,
568
1
        2 => StyleFontWeight::W200,
569
1
        3 => StyleFontWeight::W300,
570
1
        4 => StyleFontWeight::W500,
571
1
        5 => StyleFontWeight::W600,
572
177
        6 => StyleFontWeight::Bold,       // 700
573
1
        7 => StyleFontWeight::W800,
574
1
        8 => StyleFontWeight::W900,
575
1
        9 => StyleFontWeight::Lighter,
576
1
        10 => StyleFontWeight::Bolder,
577
        _ => StyleFontWeight::Normal,
578
    }
579
36519
}
580

            
581
#[inline(always)]
582
/// 0 = Normal/400 (CSS initial value for font-weight)
583
363
pub fn style_font_weight_to_u8(v: StyleFontWeight) -> u8 {
584
363
    match v {
585
3
        StyleFontWeight::Normal => 0,     // CSS initial (400)
586
1
        StyleFontWeight::W100 => 1,
587
1
        StyleFontWeight::W200 => 2,
588
1
        StyleFontWeight::W300 => 3,
589
1
        StyleFontWeight::W500 => 4,
590
1
        StyleFontWeight::W600 => 5,
591
350
        StyleFontWeight::Bold => 6,       // 700
592
1
        StyleFontWeight::W800 => 7,
593
1
        StyleFontWeight::W900 => 8,
594
1
        StyleFontWeight::Lighter => 9,
595
2
        StyleFontWeight::Bolder => 10,
596
    }
597
363
}
598

            
599
#[inline(always)]
600
36511
pub fn style_font_style_from_u8(v: u8) -> StyleFontStyle {
601
36511
    match v {
602
36508
        0 => StyleFontStyle::Normal,
603
2
        1 => StyleFontStyle::Italic,
604
1
        2 => StyleFontStyle::Oblique,
605
        _ => StyleFontStyle::Normal,
606
    }
607
36511
}
608

            
609
#[inline(always)]
610
7
pub fn style_font_style_to_u8(v: StyleFontStyle) -> u8 {
611
7
    match v {
612
3
        StyleFontStyle::Normal => 0,
613
2
        StyleFontStyle::Italic => 1,
614
2
        StyleFontStyle::Oblique => 2,
615
    }
616
7
}
617

            
618
#[inline(always)]
619
32349
pub fn style_text_align_from_u8(v: u8) -> StyleTextAlign {
620
32349
    match v {
621
32343
        0 => StyleTextAlign::Left,
622
2
        1 => StyleTextAlign::Center,
623
1
        2 => StyleTextAlign::Right,
624
1
        3 => StyleTextAlign::Justify,
625
1
        4 => StyleTextAlign::Start,
626
1
        5 => StyleTextAlign::End,
627
        _ => StyleTextAlign::Left,
628
    }
629
32349
}
630

            
631
#[inline(always)]
632
10
pub fn style_text_align_to_u8(v: StyleTextAlign) -> u8 {
633
10
    match v {
634
3
        StyleTextAlign::Left => 0,
635
2
        StyleTextAlign::Center => 1,
636
1
        StyleTextAlign::Right => 2,
637
1
        StyleTextAlign::Justify => 3,
638
1
        StyleTextAlign::Start => 4,
639
2
        StyleTextAlign::End => 5,
640
    }
641
10
}
642

            
643
#[inline(always)]
644
86806
pub fn style_visibility_from_u8(v: u8) -> StyleVisibility {
645
86806
    match v {
646
86803
        0 => StyleVisibility::Visible,
647
2
        1 => StyleVisibility::Hidden,
648
1
        2 => StyleVisibility::Collapse,
649
        _ => StyleVisibility::Visible,
650
    }
651
86806
}
652

            
653
#[inline(always)]
654
25
pub fn style_visibility_to_u8(v: StyleVisibility) -> u8 {
655
25
    match v {
656
3
        StyleVisibility::Visible => 0,
657
20
        StyleVisibility::Hidden => 1,
658
2
        StyleVisibility::Collapse => 2,
659
    }
660
25
}
661

            
662
#[inline(always)]
663
41904
pub fn style_white_space_from_u8(v: u8) -> StyleWhiteSpace {
664
41904
    match v {
665
40533
        0 => StyleWhiteSpace::Normal,
666
702
        1 => StyleWhiteSpace::Pre,
667
246
        2 => StyleWhiteSpace::Nowrap,
668
211
        3 => StyleWhiteSpace::PreWrap,
669
211
        4 => StyleWhiteSpace::PreLine,
670
1
        5 => StyleWhiteSpace::BreakSpaces,
671
        _ => StyleWhiteSpace::Normal,
672
    }
673
41904
}
674

            
675
#[inline(always)]
676
766
pub fn style_white_space_to_u8(v: StyleWhiteSpace) -> u8 {
677
766
    match v {
678
297
        StyleWhiteSpace::Normal => 0,
679
212
        StyleWhiteSpace::Pre => 1,
680
85
        StyleWhiteSpace::Nowrap => 2,
681
85
        StyleWhiteSpace::PreWrap => 3,
682
85
        StyleWhiteSpace::PreLine => 4,
683
2
        StyleWhiteSpace::BreakSpaces => 5,
684
    }
685
766
}
686

            
687
#[inline(always)]
688
75360
pub fn style_direction_from_u8(v: u8) -> StyleDirection {
689
75360
    match v {
690
75358
        0 => StyleDirection::Ltr,
691
2
        1 => StyleDirection::Rtl,
692
        _ => StyleDirection::Ltr,
693
    }
694
75360
}
695

            
696
#[inline(always)]
697
6
pub fn style_direction_to_u8(v: StyleDirection) -> u8 {
698
6
    match v {
699
3
        StyleDirection::Ltr => 0,
700
3
        StyleDirection::Rtl => 1,
701
    }
702
6
}
703

            
704
#[inline(always)]
705
11
pub fn style_vertical_align_from_u8(v: u8) -> StyleVerticalAlign {
706
11
    match v {
707
3
        0 => StyleVerticalAlign::Baseline,
708
1
        1 => StyleVerticalAlign::Top,
709
2
        2 => StyleVerticalAlign::Middle,
710
1
        3 => StyleVerticalAlign::Bottom,
711
1
        4 => StyleVerticalAlign::Sub,
712
1
        5 => StyleVerticalAlign::Superscript,
713
1
        6 => StyleVerticalAlign::TextTop,
714
1
        7 => StyleVerticalAlign::TextBottom,
715
        _ => StyleVerticalAlign::Baseline,
716
    }
717
11
}
718

            
719
#[inline(always)]
720
4884
pub fn style_vertical_align_to_u8(v: StyleVerticalAlign) -> u8 {
721
4884
    match v {
722
3
        StyleVerticalAlign::Baseline => 0,
723
1
        StyleVerticalAlign::Top => 1,
724
4874
        StyleVerticalAlign::Middle => 2,
725
1
        StyleVerticalAlign::Bottom => 3,
726
1
        StyleVerticalAlign::Sub => 4,
727
1
        StyleVerticalAlign::Superscript => 5,
728
1
        StyleVerticalAlign::TextTop => 6,
729
2
        StyleVerticalAlign::TextBottom => 7,
730
        // Percentage/Length cannot be stored in the 3-bit compact cache field;
731
        // fall back to 0 (Baseline). Callers must use the slow cascade path
732
        // for vertical-align values with length/percentage units.
733
        StyleVerticalAlign::Percentage(_) | StyleVerticalAlign::Length(_) => 0,
734
    }
735
4884
}
736

            
737
#[inline(always)]
738
1160
pub fn border_collapse_from_u8(v: u8) -> StyleBorderCollapse {
739
1160
    match v {
740
1158
        0 => StyleBorderCollapse::Separate,
741
2
        1 => StyleBorderCollapse::Collapse,
742
        _ => StyleBorderCollapse::Separate,
743
    }
744
1160
}
745

            
746
#[inline(always)]
747
6
pub fn border_collapse_to_u8(v: StyleBorderCollapse) -> u8 {
748
6
    match v {
749
3
        StyleBorderCollapse::Separate => 0,
750
3
        StyleBorderCollapse::Collapse => 1,
751
    }
752
6
}
753

            
754
#[inline(always)]
755
294840
pub fn border_style_from_u8(v: u8) -> BorderStyle {
756
294840
    match v {
757
289870
        0 => BorderStyle::None,
758
4970
        1 => BorderStyle::Solid,
759
        2 => BorderStyle::Double,
760
        3 => BorderStyle::Dotted,
761
        4 => BorderStyle::Dashed,
762
        5 => BorderStyle::Hidden,
763
        6 => BorderStyle::Groove,
764
        7 => BorderStyle::Ridge,
765
        8 => BorderStyle::Inset,
766
        9 => BorderStyle::Outset,
767
        _ => BorderStyle::None,
768
    }
769
294840
}
770

            
771
#[inline(always)]
772
1866
pub fn border_style_to_u8(v: BorderStyle) -> u8 {
773
1866
    match v {
774
        BorderStyle::None => 0,
775
1866
        BorderStyle::Solid => 1,
776
        BorderStyle::Double => 2,
777
        BorderStyle::Dotted => 3,
778
        BorderStyle::Dashed => 4,
779
        BorderStyle::Hidden => 5,
780
        BorderStyle::Groove => 6,
781
        BorderStyle::Ridge => 7,
782
        BorderStyle::Inset => 8,
783
        BorderStyle::Outset => 9,
784
    }
785
1866
}
786

            
787
/// Encode 4 border styles into a u16: [3:0]=top, [7:4]=right, [11:8]=bottom, [15:12]=left
788
#[inline]
789
pub fn encode_border_styles_packed(top: BorderStyle, right: BorderStyle, bottom: BorderStyle, left: BorderStyle) -> u16 {
790
    (border_style_to_u8(top) as u16)
791
        | ((border_style_to_u8(right) as u16) << 4)
792
        | ((border_style_to_u8(bottom) as u16) << 8)
793
        | ((border_style_to_u8(left) as u16) << 12)
794
}
795

            
796
/// Decode border-top-style from packed u16
797
#[inline(always)]
798
73710
pub fn decode_border_top_style(packed: u16) -> BorderStyle {
799
73710
    border_style_from_u8((packed & 0x0F) as u8)
800
73710
}
801

            
802
/// Decode border-right-style from packed u16
803
#[inline(always)]
804
73710
pub fn decode_border_right_style(packed: u16) -> BorderStyle {
805
73710
    border_style_from_u8(((packed >> 4) & 0x0F) as u8)
806
73710
}
807

            
808
/// Decode border-bottom-style from packed u16
809
#[inline(always)]
810
73710
pub fn decode_border_bottom_style(packed: u16) -> BorderStyle {
811
73710
    border_style_from_u8(((packed >> 8) & 0x0F) as u8)
812
73710
}
813

            
814
/// Decode border-left-style from packed u16
815
#[inline(always)]
816
73710
pub fn decode_border_left_style(packed: u16) -> BorderStyle {
817
73710
    border_style_from_u8(((packed >> 12) & 0x0F) as u8)
818
73710
}
819

            
820
/// Encode a ColorU as u32 (0xRRGGBBAA). Returns 0 for sentinel/unset.
821
#[inline(always)]
822
1866
pub fn encode_color_u32(c: &ColorU) -> u32 {
823
1866
    ((c.r as u32) << 24) | ((c.g as u32) << 16) | ((c.b as u32) << 8) | (c.a as u32)
824
1866
}
825

            
826
/// Decode a u32 back to ColorU. Returns `None` if sentinel (`0x00000000`).
827
///
828
/// **Limitation:** `rgba(0,0,0,0)` (fully transparent black) also encodes as
829
/// `0x00000000` and will be decoded as `None` (unset). This is acceptable
830
/// because fully transparent black is visually indistinguishable from unset.
831
#[inline(always)]
832
pub fn decode_color_u32(v: u32) -> Option<ColorU> {
833
    if v == 0 { return None; }
834
    Some(ColorU {
835
        r: ((v >> 24) & 0xFF) as u8,
836
        g: ((v >> 16) & 0xFF) as u8,
837
        b: ((v >> 8) & 0xFF) as u8,
838
        a: (v & 0xFF) as u8,
839
    })
840
}
841

            
842
// =============================================================================
843
// Tier 1: Encode / Decode
844
// =============================================================================
845

            
846
/// Pack all 21 enum properties into a single u64.
847
#[inline]
848
2
pub fn encode_tier1(
849
2
    display: LayoutDisplay,
850
2
    position: LayoutPosition,
851
2
    float: LayoutFloat,
852
2
    overflow_x: LayoutOverflow,
853
2
    overflow_y: LayoutOverflow,
854
2
    box_sizing: LayoutBoxSizing,
855
2
    flex_direction: LayoutFlexDirection,
856
2
    flex_wrap: LayoutFlexWrap,
857
2
    justify_content: LayoutJustifyContent,
858
2
    align_items: LayoutAlignItems,
859
2
    align_content: LayoutAlignContent,
860
2
    writing_mode: LayoutWritingMode,
861
2
    clear: LayoutClear,
862
2
    font_weight: StyleFontWeight,
863
2
    font_style: StyleFontStyle,
864
2
    text_align: StyleTextAlign,
865
2
    visibility: StyleVisibility,
866
2
    white_space: StyleWhiteSpace,
867
2
    direction: StyleDirection,
868
2
    vertical_align: StyleVerticalAlign,
869
2
    border_collapse: StyleBorderCollapse,
870
2
) -> u64 {
871
2
    let mut v: u64 = TIER1_POPULATED_BIT;
872
2
    v |= (layout_display_to_u8(display) as u64) << DISPLAY_SHIFT;
873
2
    v |= (layout_position_to_u8(position) as u64) << POSITION_SHIFT;
874
2
    v |= (layout_float_to_u8(float) as u64) << FLOAT_SHIFT;
875
2
    v |= (layout_overflow_to_u8(overflow_x) as u64) << OVERFLOW_X_SHIFT;
876
2
    v |= (layout_overflow_to_u8(overflow_y) as u64) << OVERFLOW_Y_SHIFT;
877
2
    v |= (layout_box_sizing_to_u8(box_sizing) as u64) << BOX_SIZING_SHIFT;
878
2
    v |= (layout_flex_direction_to_u8(flex_direction) as u64) << FLEX_DIRECTION_SHIFT;
879
2
    v |= (layout_flex_wrap_to_u8(flex_wrap) as u64) << FLEX_WRAP_SHIFT;
880
2
    v |= (layout_justify_content_to_u8(justify_content) as u64) << JUSTIFY_CONTENT_SHIFT;
881
2
    v |= (layout_align_items_to_u8(align_items) as u64) << ALIGN_ITEMS_SHIFT;
882
2
    v |= (layout_align_content_to_u8(align_content) as u64) << ALIGN_CONTENT_SHIFT;
883
2
    v |= (layout_writing_mode_to_u8(writing_mode) as u64) << WRITING_MODE_SHIFT;
884
2
    v |= (layout_clear_to_u8(clear) as u64) << CLEAR_SHIFT;
885
2
    v |= (style_font_weight_to_u8(font_weight) as u64) << FONT_WEIGHT_SHIFT;
886
2
    v |= (style_font_style_to_u8(font_style) as u64) << FONT_STYLE_SHIFT;
887
2
    v |= (style_text_align_to_u8(text_align) as u64) << TEXT_ALIGN_SHIFT;
888
2
    v |= (style_visibility_to_u8(visibility) as u64) << VISIBILITY_SHIFT;
889
2
    v |= (style_white_space_to_u8(white_space) as u64) << WHITE_SPACE_SHIFT;
890
2
    v |= (style_direction_to_u8(direction) as u64) << DIRECTION_SHIFT;
891
2
    v |= (style_vertical_align_to_u8(vertical_align) as u64) << VERTICAL_ALIGN_SHIFT;
892
2
    v |= (border_collapse_to_u8(border_collapse) as u64) << BORDER_COLLAPSE_SHIFT;
893
2
    v
894
2
}
895

            
896
/// Decode individual enum properties from a Tier 1 u64.
897
/// Each function is `#[inline(always)]` for zero-cost extraction.
898

            
899
#[inline(always)]
900
525598
pub fn decode_display(t1: u64) -> LayoutDisplay {
901
525598
    layout_display_from_u8(((t1 >> DISPLAY_SHIFT) & DISPLAY_MASK) as u8)
902
525598
}
903

            
904
#[inline(always)]
905
296488
pub fn decode_position(t1: u64) -> LayoutPosition {
906
296488
    layout_position_from_u8(((t1 >> POSITION_SHIFT) & POSITION_MASK) as u8)
907
296488
}
908

            
909
#[inline(always)]
910
156942
pub fn decode_float(t1: u64) -> LayoutFloat {
911
156942
    layout_float_from_u8(((t1 >> FLOAT_SHIFT) & FLOAT_MASK) as u8)
912
156942
}
913

            
914
#[inline(always)]
915
178467
pub fn decode_overflow_x(t1: u64) -> LayoutOverflow {
916
178467
    layout_overflow_from_u8(((t1 >> OVERFLOW_X_SHIFT) & OVERFLOW_MASK) as u8)
917
178467
}
918

            
919
#[inline(always)]
920
190752
pub fn decode_overflow_y(t1: u64) -> LayoutOverflow {
921
190752
    layout_overflow_from_u8(((t1 >> OVERFLOW_Y_SHIFT) & OVERFLOW_MASK) as u8)
922
190752
}
923

            
924
#[inline(always)]
925
30312
pub fn decode_box_sizing(t1: u64) -> LayoutBoxSizing {
926
30312
    layout_box_sizing_from_u8(((t1 >> BOX_SIZING_SHIFT) & BOX_SIZING_MASK) as u8)
927
30312
}
928

            
929
#[inline(always)]
930
982
pub fn decode_flex_direction(t1: u64) -> LayoutFlexDirection {
931
982
    layout_flex_direction_from_u8(((t1 >> FLEX_DIRECTION_SHIFT) & FLEX_DIR_MASK) as u8)
932
982
}
933

            
934
#[inline(always)]
935
667
pub fn decode_flex_wrap(t1: u64) -> LayoutFlexWrap {
936
667
    layout_flex_wrap_from_u8(((t1 >> FLEX_WRAP_SHIFT) & FLEX_WRAP_MASK) as u8)
937
667
}
938

            
939
#[inline(always)]
940
2
pub fn decode_justify_content(t1: u64) -> LayoutJustifyContent {
941
2
    layout_justify_content_from_u8(((t1 >> JUSTIFY_CONTENT_SHIFT) & JUSTIFY_MASK) as u8)
942
2
}
943

            
944
#[inline(always)]
945
667
pub fn decode_align_items(t1: u64) -> LayoutAlignItems {
946
667
    layout_align_items_from_u8(((t1 >> ALIGN_ITEMS_SHIFT) & ALIGN_MASK) as u8)
947
667
}
948

            
949
#[inline(always)]
950
667
pub fn decode_align_content(t1: u64) -> LayoutAlignContent {
951
667
    layout_align_content_from_u8(((t1 >> ALIGN_CONTENT_SHIFT) & ALIGN_MASK) as u8)
952
667
}
953

            
954
#[inline(always)]
955
122607
pub fn decode_writing_mode(t1: u64) -> LayoutWritingMode {
956
122607
    layout_writing_mode_from_u8(((t1 >> WRITING_MODE_SHIFT) & WRITING_MODE_MASK) as u8)
957
122607
}
958

            
959
#[inline(always)]
960
15717
pub fn decode_clear(t1: u64) -> LayoutClear {
961
15717
    layout_clear_from_u8(((t1 >> CLEAR_SHIFT) & CLEAR_MASK) as u8)
962
15717
}
963

            
964
#[inline(always)]
965
36507
pub fn decode_font_weight(t1: u64) -> StyleFontWeight {
966
36507
    style_font_weight_from_u8(((t1 >> FONT_WEIGHT_SHIFT) & FONT_WEIGHT_MASK) as u8)
967
36507
}
968

            
969
#[inline(always)]
970
36507
pub fn decode_font_style(t1: u64) -> StyleFontStyle {
971
36507
    style_font_style_from_u8(((t1 >> FONT_STYLE_SHIFT) & FONT_STYLE_MASK) as u8)
972
36507
}
973

            
974
#[inline(always)]
975
32342
pub fn decode_text_align(t1: u64) -> StyleTextAlign {
976
32342
    style_text_align_from_u8(((t1 >> TEXT_ALIGN_SHIFT) & TEXT_ALIGN_MASK) as u8)
977
32342
}
978

            
979
#[inline(always)]
980
86802
pub fn decode_visibility(t1: u64) -> StyleVisibility {
981
86802
    style_visibility_from_u8(((t1 >> VISIBILITY_SHIFT) & VISIBILITY_MASK) as u8)
982
86802
}
983

            
984
#[inline(always)]
985
41897
pub fn decode_white_space(t1: u64) -> StyleWhiteSpace {
986
41897
    style_white_space_from_u8(((t1 >> WHITE_SPACE_SHIFT) & WHITE_SPACE_MASK) as u8)
987
41897
}
988

            
989
#[inline(always)]
990
75357
pub fn decode_direction(t1: u64) -> StyleDirection {
991
75357
    style_direction_from_u8(((t1 >> DIRECTION_SHIFT) & DIRECTION_MASK) as u8)
992
75357
}
993

            
994
#[inline(always)]
995
2
pub fn decode_vertical_align(t1: u64) -> StyleVerticalAlign {
996
2
    style_vertical_align_from_u8(((t1 >> VERTICAL_ALIGN_SHIFT) & VERTICAL_ALIGN_MASK) as u8)
997
2
}
998

            
999
#[inline(always)]
1157
pub fn decode_border_collapse(t1: u64) -> StyleBorderCollapse {
1157
    border_collapse_from_u8(((t1 >> BORDER_COLLAPSE_SHIFT) & BORDER_COLLAPSE_MASK) as u8)
1157
}
/// Returns true if the tier1 u64 was actually populated by `encode_tier1`.
#[inline(always)]
#[cfg(test)]
3
pub fn tier1_is_populated(t1: u64) -> bool {
3
    (t1 & TIER1_POPULATED_BIT) != 0
3
}
// =============================================================================
// Tier 2: CompactNodeProps — numeric dimensions (64 bytes/node)
// =============================================================================
/// u32 encoding for dimension properties (width, height, min-*, max-*, flex-basis, font-size).
///
/// Layout: `[3:0] SizeMetric (4 bits) | [31:4] signed fixed-point ×1000 (28 bits)`
///
/// This matches FloatValue's internal representation (isize × 1000).
/// Range: ±134,217.727 at 0.001 precision (28-bit signed = ±2^27 = ±134,217,728 / 1000).
///
/// Sentinel values use the top of the u32 range (0xFFFFFFF9..0xFFFFFFFF).
/// Encode a PixelValue into u32 with SizeMetric. Returns U32_SENTINEL if out of range.
#[inline]
17720
pub fn encode_pixel_value_u32(pv: &PixelValue) -> u32 {
17720
    let metric = size_metric_to_u8(pv.metric) as u32;
17720
    let raw = pv.number.number; // already × 1000 (FloatValue internal repr)
    // 28-bit signed range: -134_217_728 ..= +134_217_727
17720
    if !(-134_217_728..=134_217_727).contains(&raw) {
        return U32_SENTINEL; // overflow → tier 3
17720
    }
    // Pack: low 4 bits = metric, upper 28 bits = value (as unsigned offset)
17720
    let value_bits = ((raw as i32) as u32) << 4;
17720
    value_bits | metric
17720
}
/// Decode a u32 back to PixelValue. Returns None for sentinel values.
#[inline]
48041
pub fn decode_pixel_value_u32(encoded: u32) -> Option<PixelValue> {
48041
    if encoded >= U32_SENTINEL_THRESHOLD {
117
        return None; // sentinel
47924
    }
47924
    let metric = size_metric_from_u8((encoded & 0xF) as u8);
    // Cast to i32 FIRST, then arithmetic right-shift to preserve sign bit
47924
    let value_bits = (encoded as i32) >> 4;
47924
    let raw = value_bits as isize; // × 1000
47924
    Some(PixelValue {
47924
        metric,
47924
        number: FloatValue { number: raw },
47924
    })
48041
}
/// Encode an i16 resolved px value (×10). Returns I16_SENTINEL if out of range.
/// Range: -3276.8 ..= +3276.3 px at 0.1px precision.
#[inline]
141290
pub fn encode_resolved_px_i16(px: f32) -> i16 {
141290
    let scaled = (px * 10.0).round() as i32;
141290
    if scaled < -32768 || scaled > I16_SENTINEL_THRESHOLD as i32 - 1 {
        return I16_SENTINEL; // overflow or too large → tier 3
141290
    }
141290
    scaled as i16
141290
}
/// Decode an i16 back to resolved px. Returns None for sentinel values.
#[inline(always)]
98914
pub fn decode_resolved_px_i16(v: i16) -> Option<f32> {
98914
    if v >= I16_SENTINEL_THRESHOLD {
32972
        return None;
65942
    }
65942
    Some(v as f32 / 10.0)
98914
}
/// Encode a u16 flex value (×100). Returns U16_SENTINEL if out of range.
/// Range: 0.00 ..= 655.27 at 0.01 precision.
#[inline]
18016
pub fn encode_flex_u16(value: f32) -> u16 {
18016
    let scaled = (value * 100.0).round() as i32;
18016
    if scaled < 0 || scaled >= U16_SENTINEL_THRESHOLD as i32 {
        return U16_SENTINEL;
18016
    }
18016
    scaled as u16
18016
}
/// Decode a u16 flex value back to f32. Returns None for sentinel values.
#[inline(always)]
1331
pub fn decode_flex_u16(v: u16) -> Option<f32> {
1331
    if v >= U16_SENTINEL_THRESHOLD {
        return None;
1331
    }
1331
    Some(v as f32 / 100.0)
1331
}
/// SizeMetric → u8 (4 bits, 12 variants)
#[inline(always)]
17720
pub fn size_metric_to_u8(m: SizeMetric) -> u8 {
17720
    match m {
13249
        SizeMetric::Px => 0,
        SizeMetric::Pt => 1,
282
        SizeMetric::Em => 2,
        SizeMetric::Rem => 3,
        SizeMetric::In => 4,
        SizeMetric::Cm => 5,
        SizeMetric::Mm => 6,
4189
        SizeMetric::Percent => 7,
        SizeMetric::Vw => 8,
        SizeMetric::Vh => 9,
        SizeMetric::Vmin => 10,
        SizeMetric::Vmax => 11,
    }
17720
}
/// u8 → SizeMetric
#[inline(always)]
47924
pub fn size_metric_from_u8(v: u8) -> SizeMetric {
47924
    match v {
39499
        0 => SizeMetric::Px,
        1 => SizeMetric::Pt,
282
        2 => SizeMetric::Em,
        3 => SizeMetric::Rem,
        4 => SizeMetric::In,
        5 => SizeMetric::Cm,
        6 => SizeMetric::Mm,
8143
        7 => SizeMetric::Percent,
        8 => SizeMetric::Vw,
        9 => SizeMetric::Vh,
        10 => SizeMetric::Vmin,
        11 => SizeMetric::Vmax,
        _ => SizeMetric::Px,
    }
47924
}
/// Layout-hot compact numeric properties for a single node (68 bytes).
/// Only fields accessed during the constraint-solving loop.
/// All dimensions use MSB-sentinel encoding.
#[derive(Debug, Copy, Clone, PartialEq)]
#[repr(C)]
pub struct CompactNodeProps {
    // --- Dimensions needing unit (u32 MSB-sentinel) ---
    pub width: u32,
    pub height: u32,
    pub min_width: u32,
    pub max_width: u32,
    pub min_height: u32,
    pub max_height: u32,
    pub flex_basis: u32,
    pub font_size: u32,
    // --- Resolved px values (i16 MSB-sentinel, ×10) ---
    pub padding_top: i16,
    pub padding_right: i16,
    pub padding_bottom: i16,
    pub padding_left: i16,
    pub margin_top: i16,
    pub margin_right: i16,
    pub margin_bottom: i16,
    pub margin_left: i16,
    pub border_top_width: i16,
    pub border_right_width: i16,
    pub border_bottom_width: i16,
    pub border_left_width: i16,
    pub top: i16,
    pub right: i16,
    pub bottom: i16,
    pub left: i16,
    // --- Flex (u16 MSB-sentinel, ×100) ---
    pub flex_grow: u16,
    pub flex_shrink: u16,
    // --- Gap (i16 px×10, 0 = default) ---
    pub row_gap: i16,
    pub column_gap: i16,
}
/// Paint-cold compact properties for a single node.
/// Only accessed during display list generation, table layout, or text shaping.
#[derive(Debug, Copy, Clone, PartialEq)]
#[repr(C)]
pub struct CompactNodePropsCold {
    // --- Border colors (u32 RGBA as 0xRRGGBBAA, 0 = unset sentinel) ---
    pub border_top_color: u32,
    pub border_right_color: u32,
    pub border_bottom_color: u32,
    pub border_left_color: u32,
    // --- Border radii (i16 px × 10, I16_SENTINEL = unset/default = 0) ---
    pub border_top_left_radius: i16,
    pub border_top_right_radius: i16,
    pub border_bottom_left_radius: i16,
    pub border_bottom_right_radius: i16,
    // --- Other ---
    pub z_index: i16,   // range ±32764, sentinel = 0x7FFF
    /// Border styles packed: [3:0]=top, [7:4]=right, [11:8]=bottom, [15:12]=left
    pub border_styles_packed: u16,
    pub border_spacing_h: i16,
    pub border_spacing_v: i16,
    pub tab_size: i16,
    /// Grid column start (I16_AUTO = auto, positive = line number, negative = span)
    pub grid_col_start: i16,
    /// Grid column end
    pub grid_col_end: i16,
    /// Grid row start
    pub grid_row_start: i16,
    /// Grid row end
    pub grid_row_end: i16,
    // --- GPU / hot paint props ---
    /// Opacity × 254 (0 = fully transparent, 254 = opaque). 255 = unset/default (= 1.0).
    pub opacity: u8,
    /// Bitflags for properties that are usually unset. Lets the getter
    /// short-circuit without a cascade walk when the value is the default.
    ///
    /// bit 0: has_transform                (slow-walk only when set)
    /// bit 1: has_transform_origin
    /// bit 2: has_box_shadow
    /// bit 3: has_text_decoration          (slow-walk only when set)
    /// bits 4-5: scrollbar_gutter (0 = auto default, 1 = stable, 2 = both-edges, 3 = mirror)
    /// bit 6: has_background                (slow-walk only when set; ≈ negative fast path)
    /// bit 7: has_clip_path                 (slow-walk only when set)
    pub hot_flags: u8,
    /// Second byte of flags for rarely-set properties.
    ///
    /// bit 0: has_any_scrollbar_css
    ///        OR of all -azul-scrollbar-* / scrollbar-color / scrollbar-width props.
    ///        When clear, `get_scrollbar_style` can skip 8 cascade walks and use
    ///        the UA-default result.
    /// bit 1: has_counter      (counter-reset OR counter-increment)
    /// bit 2: has_break        (break-before OR break-after)
    /// bit 3: has_text_orientation
    /// bit 4: has_text_shadow
    /// bit 5: has_backdrop_filter
    /// bit 6: has_filter
    /// bit 7: has_mix_blend_mode
    pub extra_flags: u8,
}
pub const OPACITY_SENTINEL: u8 = 255;
pub const HOT_FLAG_HAS_TRANSFORM: u8 = 1 << 0;
pub const HOT_FLAG_HAS_TRANSFORM_ORIGIN: u8 = 1 << 1;
pub const HOT_FLAG_HAS_BOX_SHADOW: u8 = 1 << 2;
pub const HOT_FLAG_HAS_TEXT_DECORATION: u8 = 1 << 3;
pub const HOT_FLAG_SCROLLBAR_GUTTER_SHIFT: u8 = 4;
pub const HOT_FLAG_SCROLLBAR_GUTTER_MASK: u8 = 0b0011_0000;
pub const HOT_FLAG_HAS_BACKGROUND: u8 = 1 << 6;
pub const HOT_FLAG_HAS_CLIP_PATH: u8 = 1 << 7;
pub const EXTRA_FLAG_HAS_SCROLLBAR_CSS: u8 = 1 << 0;
pub const EXTRA_FLAG_HAS_COUNTER: u8 = 1 << 1;
pub const EXTRA_FLAG_HAS_BREAK: u8 = 1 << 2;
pub const EXTRA_FLAG_HAS_TEXT_ORIENTATION: u8 = 1 << 3;
pub const EXTRA_FLAG_HAS_TEXT_SHADOW: u8 = 1 << 4;
pub const EXTRA_FLAG_HAS_BACKDROP_FILTER: u8 = 1 << 5;
pub const EXTRA_FLAG_HAS_FILTER: u8 = 1 << 6;
pub const EXTRA_FLAG_HAS_MIX_BLEND_MODE: u8 = 1 << 7;
// ---- DOM-level rare text prop flags (stored on CompactLayoutCache) ----
// Each bit = "some node in this DOM declared this property".
// When clear, cascade walks for that prop anywhere in the DOM
// necessarily return None → callers can skip the walk and use
// the default value. Eliminates ~N × IFC-count walks per layout
// in typical pages where these props are never declared.
pub const DOM_HAS_SHAPE_INSIDE: u32 = 1 << 0;
pub const DOM_HAS_SHAPE_OUTSIDE: u32 = 1 << 1;
pub const DOM_HAS_TEXT_JUSTIFY: u32 = 1 << 2;
pub const DOM_HAS_TEXT_INDENT: u32 = 1 << 3;
pub const DOM_HAS_COLUMN_COUNT: u32 = 1 << 4;
pub const DOM_HAS_COLUMN_GAP: u32 = 1 << 5;
pub const DOM_HAS_INITIAL_LETTER: u32 = 1 << 6;
pub const DOM_HAS_INITIAL_LETTER_ALIGN: u32 = 1 << 7;
pub const DOM_HAS_LINE_CLAMP: u32 = 1 << 8;
pub const DOM_HAS_HANGING_PUNCTUATION: u32 = 1 << 9;
pub const DOM_HAS_TEXT_COMBINE_UPRIGHT: u32 = 1 << 10;
pub const DOM_HAS_EXCLUSION_MARGIN: u32 = 1 << 11;
pub const DOM_HAS_HYPHENATION_LANGUAGE: u32 = 1 << 12;
pub const DOM_HAS_UNICODE_BIDI: u32 = 1 << 13;
pub const DOM_HAS_TEXT_BOX_TRIM: u32 = 1 << 14;
pub const DOM_HAS_HYPHENS: u32 = 1 << 15;
pub const DOM_HAS_WORD_BREAK: u32 = 1 << 16;
pub const DOM_HAS_OVERFLOW_WRAP: u32 = 1 << 17;
pub const DOM_HAS_LINE_BREAK: u32 = 1 << 18;
pub const DOM_HAS_TEXT_ALIGN_LAST: u32 = 1 << 19;
pub const DOM_HAS_LINE_HEIGHT: u32 = 1 << 20;
pub const DOM_HAS_COLUMN_WIDTH: u32 = 1 << 21;
pub const DOM_HAS_SHAPE_MARGIN: u32 = 1 << 22;
pub const SCROLLBAR_GUTTER_AUTO: u8 = 0;
pub const SCROLLBAR_GUTTER_STABLE: u8 = 1;
pub const SCROLLBAR_GUTTER_BOTH_EDGES: u8 = 2;
pub const SCROLLBAR_GUTTER_MIRROR: u8 = 3;
impl Default for CompactNodeProps {
17847
    fn default() -> Self {
17847
        Self {
17847
            // All dimensions default to Auto
17847
            width: U32_AUTO,
17847
            height: U32_AUTO,
17847
            min_width: U32_AUTO,
17847
            max_width: U32_NONE,
17847
            min_height: U32_AUTO,
17847
            max_height: U32_NONE,
17847
            flex_basis: U32_AUTO,
17847
            font_size: U32_INITIAL,
17847
            // All resolved px default to 0
17847
            padding_top: 0,
17847
            padding_right: 0,
17847
            padding_bottom: 0,
17847
            padding_left: 0,
17847
            margin_top: 0,
17847
            margin_right: 0,
17847
            margin_bottom: 0,
17847
            margin_left: 0,
17847
            border_top_width: 0,
17847
            border_right_width: 0,
17847
            border_bottom_width: 0,
17847
            border_left_width: 0,
17847
            top: I16_AUTO,
17847
            right: I16_AUTO,
17847
            bottom: I16_AUTO,
17847
            left: I16_AUTO,
17847
            // Flex defaults
17847
            flex_grow: 0,
17847
            flex_shrink: encode_flex_u16(1.0), // CSS default: flex-shrink: 1
17847

            
17847
            // Gap defaults
17847
            row_gap: 0,
17847
            column_gap: 0,
17847
        }
17847
    }
}
impl Default for CompactNodePropsCold {
17847
    fn default() -> Self {
17847
        Self {
17847
            // Border colors default to 0 (sentinel/unset)
17847
            border_top_color: 0,
17847
            border_right_color: 0,
17847
            border_bottom_color: 0,
17847
            border_left_color: 0,
17847
            // Border radii: I16_SENTINEL means "no rounded corner" (skip slow walk)
17847
            border_top_left_radius: I16_SENTINEL,
17847
            border_top_right_radius: I16_SENTINEL,
17847
            border_bottom_left_radius: I16_SENTINEL,
17847
            border_bottom_right_radius: I16_SENTINEL,
17847
            // Other
17847
            z_index: I16_AUTO,
17847
            border_styles_packed: 0, // all BorderStyle::None
17847
            border_spacing_h: 0,
17847
            border_spacing_v: 0,
17847
            tab_size: I16_SENTINEL, // default is 8em, needs resolution → sentinel
17847
            grid_col_start: I16_AUTO,
17847
            grid_col_end: I16_AUTO,
17847
            grid_row_start: I16_AUTO,
17847
            grid_row_end: I16_AUTO,
17847
            opacity: OPACITY_SENTINEL,
17847
            hot_flags: 0,
17847
            extra_flags: 0,
17847
        }
17847
    }
}
// =============================================================================
// Tier 2b: CompactTextProps — IFC/text properties (24 bytes/node)
// =============================================================================
/// Compact text/IFC properties for a single node (24 bytes).
#[derive(Debug, Copy, Clone, PartialEq)]
#[repr(C)]
pub struct CompactTextProps {
    pub text_color: u32,       // RGBA as 0xRRGGBBAA (0 = transparent/unset)
    pub font_family_hash: u64, // FxHash of font-family list (0 = sentinel/unset)
    pub line_height: i16,      // px × 10, sentinel = I16_SENTINEL
    pub letter_spacing: i16,   // px × 10
    pub word_spacing: i16,     // px × 10
    pub text_indent: i16,      // px × 10
}
impl Default for CompactTextProps {
17847
    fn default() -> Self {
17847
        Self {
17847
            text_color: 0,
17847
            font_family_hash: 0,
17847
            line_height: I16_SENTINEL, // "normal" → sentinel
17847
            letter_spacing: 0,
17847
            word_spacing: 0,
17847
            text_indent: 0,
17847
        }
17847
    }
}
// =============================================================================
// Tier 3: Overflow map — rare/complex properties
// =============================================================================
/// Overflow properties that couldn't fit in Tier 1/2 encoding.
/// Contains the original `CssProperty` values for properties that:
/// - Have calc() expressions
/// - Exceed the numeric range of compact encoding
/// - Are rare CSS properties (grid, transforms, etc.)
// =============================================================================
// CompactLayoutCache — the top-level container
// =============================================================================
/// Three-tier compact layout property cache.
///
/// Allocated once per restyle, indexed by node index (same as NodeId).
/// Provides O(1) array-indexed access to all layout properties.
///
/// Non-compact properties (background, box-shadow, transform, etc.) are
/// resolved via the slow cascade path in `CssPropertyCache::get_property_slow()`.
#[derive(Debug, Clone, PartialEq)]
pub struct CompactLayoutCache {
    /// Tier 1: ALL enum properties bitpacked into u64 per node (8 B/node)
    pub tier1_enums: Vec<u64>,
    /// Tier 2 hot: Layout-critical numeric dimensions per node (68 B/node)
    pub tier2_dims: Vec<CompactNodeProps>,
    /// Tier 2 cold: Paint-only properties per node (28 B/node)
    pub tier2_cold: Vec<CompactNodePropsCold>,
    /// Tier 2b: Text/IFC properties per node (24 B/node)
    pub tier2b_text: Vec<CompactTextProps>,
    /// Indices of nodes whose `font_family_hash` changed since the last frame.
    ///
    /// Enables **per-node** font dirty tracking instead of the global all-or-nothing
    /// `font_stacks_hash` XOR approach. When this list is non-empty, only the
    /// font chains for these specific nodes need to be re-resolved, avoiding O(N)
    /// re-resolution when a single node's `font-family` changes.
    ///
    /// Populated during `build_compact_cache()` by comparing each node's
    /// `font_family_hash` against `prev_font_hashes`.
    pub font_dirty_nodes: Vec<usize>,
    /// Previous frame's per-node `font_family_hash` values.
    ///
    /// Stored after each compact cache build so that the next build can detect
    /// which specific nodes' font-family changed (rather than relying on a
    /// collision-prone global XOR hash).
    pub prev_font_hashes: Vec<u64>,
    /// Reverse map: `font_family_hash` (u64) → actual `StyleFontFamilyVec`.
    ///
    /// Populated during `build_compact_cache()` as a byproduct of hash computation.
    /// Consumers use this to look up font family names from the compact cache hash
    /// without going through `get_property_slow()` (which fails for inherited values
    /// on text nodes).
    pub font_hash_to_families: alloc::collections::BTreeMap<u64, crate::props::basic::font::StyleFontFamilyVec>,
    /// Bitfield tracking which rare text props are declared *anywhere* in the DOM.
    /// Built once during `build_compact_cache_with_inheritance`. When a bit is
    /// clear, callers (e.g. `translate_to_text3_constraints`) can skip the
    /// cascade walk for that property — its slow path would always return
    /// `None` and fall back to the default. See `DOM_HAS_*` constants.
    pub dom_declared_flags: u32,
}
impl CompactLayoutCache {
    /// Create an empty cache (no nodes).
    pub fn empty() -> Self {
        Self {
            tier1_enums: Vec::new(),
            tier2_dims: Vec::new(),
            tier2_cold: Vec::new(),
            tier2b_text: Vec::new(),
            font_dirty_nodes: Vec::new(),
            prev_font_hashes: Vec::new(),
            font_hash_to_families: alloc::collections::BTreeMap::new(),
            dom_declared_flags: 0,
        }
    }
    /// Create a cache pre-allocated for `node_count` nodes, filled with defaults.
8933
    pub fn with_capacity(node_count: usize) -> Self {
8933
        Self {
8933
            tier1_enums: vec![0u64; node_count],
8933
            tier2_dims: vec![CompactNodeProps::default(); node_count],
8933
            tier2_cold: vec![CompactNodePropsCold::default(); node_count],
8933
            tier2b_text: vec![CompactTextProps::default(); node_count],
8933
            font_dirty_nodes: Vec::new(),
8933
            prev_font_hashes: vec![0u64; node_count],
8933
            font_hash_to_families: alloc::collections::BTreeMap::new(),
8933
            dom_declared_flags: 0,
8933
        }
8933
    }
    /// Number of nodes in this cache.
    #[inline]
    pub fn node_count(&self) -> usize {
        self.tier1_enums.len()
    }
    // -- Tier 1 getters (enum properties) --
    #[inline(always)]
525595
    pub fn get_display(&self, node_idx: usize) -> LayoutDisplay {
525595
        decode_display(self.tier1_enums[node_idx])
525595
    }
    #[inline(always)]
296485
    pub fn get_position(&self, node_idx: usize) -> LayoutPosition {
296485
        decode_position(self.tier1_enums[node_idx])
296485
    }
    #[inline(always)]
156940
    pub fn get_float(&self, node_idx: usize) -> LayoutFloat {
156940
        decode_float(self.tier1_enums[node_idx])
156940
    }
    #[inline(always)]
178465
    pub fn get_overflow_x(&self, node_idx: usize) -> LayoutOverflow {
178465
        decode_overflow_x(self.tier1_enums[node_idx])
178465
    }
    #[inline(always)]
190750
    pub fn get_overflow_y(&self, node_idx: usize) -> LayoutOverflow {
190750
        decode_overflow_y(self.tier1_enums[node_idx])
190750
    }
    #[inline(always)]
30310
    pub fn get_box_sizing(&self, node_idx: usize) -> LayoutBoxSizing {
30310
        decode_box_sizing(self.tier1_enums[node_idx])
30310
    }
    #[inline(always)]
980
    pub fn get_flex_direction(&self, node_idx: usize) -> LayoutFlexDirection {
980
        decode_flex_direction(self.tier1_enums[node_idx])
980
    }
    #[inline(always)]
665
    pub fn get_flex_wrap(&self, node_idx: usize) -> LayoutFlexWrap {
665
        decode_flex_wrap(self.tier1_enums[node_idx])
665
    }
    #[inline(always)]
    pub fn get_justify_content(&self, node_idx: usize) -> LayoutJustifyContent {
        decode_justify_content(self.tier1_enums[node_idx])
    }
    #[inline(always)]
665
    pub fn get_align_items(&self, node_idx: usize) -> LayoutAlignItems {
665
        decode_align_items(self.tier1_enums[node_idx])
665
    }
    #[inline(always)]
665
    pub fn get_align_content(&self, node_idx: usize) -> LayoutAlignContent {
665
        decode_align_content(self.tier1_enums[node_idx])
665
    }
    #[inline(always)]
122605
    pub fn get_writing_mode(&self, node_idx: usize) -> LayoutWritingMode {
122605
        decode_writing_mode(self.tier1_enums[node_idx])
122605
    }
    #[inline(always)]
15715
    pub fn get_clear(&self, node_idx: usize) -> LayoutClear {
15715
        decode_clear(self.tier1_enums[node_idx])
15715
    }
    #[inline(always)]
36505
    pub fn get_font_weight(&self, node_idx: usize) -> StyleFontWeight {
36505
        decode_font_weight(self.tier1_enums[node_idx])
36505
    }
    #[inline(always)]
36505
    pub fn get_font_style(&self, node_idx: usize) -> StyleFontStyle {
36505
        decode_font_style(self.tier1_enums[node_idx])
36505
    }
    #[inline(always)]
32340
    pub fn get_text_align(&self, node_idx: usize) -> StyleTextAlign {
32340
        decode_text_align(self.tier1_enums[node_idx])
32340
    }
    #[inline(always)]
86800
    pub fn get_visibility(&self, node_idx: usize) -> StyleVisibility {
86800
        decode_visibility(self.tier1_enums[node_idx])
86800
    }
    #[inline(always)]
41895
    pub fn get_white_space(&self, node_idx: usize) -> StyleWhiteSpace {
41895
        decode_white_space(self.tier1_enums[node_idx])
41895
    }
    #[inline(always)]
75355
    pub fn get_direction(&self, node_idx: usize) -> StyleDirection {
75355
        decode_direction(self.tier1_enums[node_idx])
75355
    }
    #[inline(always)]
    pub fn get_vertical_align(&self, node_idx: usize) -> StyleVerticalAlign {
        decode_vertical_align(self.tier1_enums[node_idx])
    }
    #[inline(always)]
1155
    pub fn get_border_collapse(&self, node_idx: usize) -> StyleBorderCollapse {
1155
        decode_border_collapse(self.tier1_enums[node_idx])
1155
    }
    // -- Tier 2 getters (numeric dimensions) --
    /// Get width as encoded u32 (use `decode_pixel_value_u32` or check sentinel).
    #[inline(always)]
57435
    pub fn get_width_raw(&self, node_idx: usize) -> u32 {
57435
        self.tier2_dims[node_idx].width
57435
    }
    #[inline(always)]
85925
    pub fn get_height_raw(&self, node_idx: usize) -> u32 {
85925
        self.tier2_dims[node_idx].height
85925
    }
    #[inline(always)]
74620
    pub fn get_min_width_raw(&self, node_idx: usize) -> u32 {
74620
        self.tier2_dims[node_idx].min_width
74620
    }
    #[inline(always)]
55580
    pub fn get_max_width_raw(&self, node_idx: usize) -> u32 {
55580
        self.tier2_dims[node_idx].max_width
55580
    }
    #[inline(always)]
74620
    pub fn get_min_height_raw(&self, node_idx: usize) -> u32 {
74620
        self.tier2_dims[node_idx].min_height
74620
    }
    #[inline(always)]
55580
    pub fn get_max_height_raw(&self, node_idx: usize) -> u32 {
55580
        self.tier2_dims[node_idx].max_height
55580
    }
    #[inline(always)]
32970
    pub fn get_font_size_raw(&self, node_idx: usize) -> u32 {
32970
        self.tier2_dims[node_idx].font_size
32970
    }
    #[inline(always)]
665
    pub fn get_flex_basis_raw(&self, node_idx: usize) -> u32 {
665
        self.tier2_dims[node_idx].flex_basis
665
    }
    /// Get padding-top as resolved px. Returns None if sentinel (needs slow path).
    #[inline(always)]
    pub fn get_padding_top(&self, node_idx: usize) -> Option<f32> {
        decode_resolved_px_i16(self.tier2_dims[node_idx].padding_top)
    }
    #[inline(always)]
    pub fn get_padding_right(&self, node_idx: usize) -> Option<f32> {
        decode_resolved_px_i16(self.tier2_dims[node_idx].padding_right)
    }
    #[inline(always)]
    pub fn get_padding_bottom(&self, node_idx: usize) -> Option<f32> {
        decode_resolved_px_i16(self.tier2_dims[node_idx].padding_bottom)
    }
    #[inline(always)]
    pub fn get_padding_left(&self, node_idx: usize) -> Option<f32> {
        decode_resolved_px_i16(self.tier2_dims[node_idx].padding_left)
    }
    #[inline(always)]
    pub fn get_margin_top(&self, node_idx: usize) -> Option<f32> {
        let v = self.tier2_dims[node_idx].margin_top;
        if v == I16_AUTO { return None; } // Auto for margin is special
        decode_resolved_px_i16(v)
    }
    #[inline(always)]
    pub fn get_margin_right(&self, node_idx: usize) -> Option<f32> {
        let v = self.tier2_dims[node_idx].margin_right;
        if v == I16_AUTO { return None; }
        decode_resolved_px_i16(v)
    }
    #[inline(always)]
    pub fn get_margin_bottom(&self, node_idx: usize) -> Option<f32> {
        let v = self.tier2_dims[node_idx].margin_bottom;
        if v == I16_AUTO { return None; }
        decode_resolved_px_i16(v)
    }
    #[inline(always)]
    pub fn get_margin_left(&self, node_idx: usize) -> Option<f32> {
        let v = self.tier2_dims[node_idx].margin_left;
        if v == I16_AUTO { return None; }
        decode_resolved_px_i16(v)
    }
    /// Check if margin is Auto (important for centering logic).
    #[inline(always)]
    pub fn is_margin_top_auto(&self, node_idx: usize) -> bool {
        self.tier2_dims[node_idx].margin_top == I16_AUTO
    }
    #[inline(always)]
    pub fn is_margin_right_auto(&self, node_idx: usize) -> bool {
        self.tier2_dims[node_idx].margin_right == I16_AUTO
    }
    #[inline(always)]
    pub fn is_margin_bottom_auto(&self, node_idx: usize) -> bool {
        self.tier2_dims[node_idx].margin_bottom == I16_AUTO
    }
    #[inline(always)]
    pub fn is_margin_left_auto(&self, node_idx: usize) -> bool {
        self.tier2_dims[node_idx].margin_left == I16_AUTO
    }
    #[inline(always)]
    pub fn get_border_top_width(&self, node_idx: usize) -> Option<f32> {
        decode_resolved_px_i16(self.tier2_dims[node_idx].border_top_width)
    }
    #[inline(always)]
    pub fn get_border_right_width(&self, node_idx: usize) -> Option<f32> {
        decode_resolved_px_i16(self.tier2_dims[node_idx].border_right_width)
    }
    #[inline(always)]
    pub fn get_border_bottom_width(&self, node_idx: usize) -> Option<f32> {
        decode_resolved_px_i16(self.tier2_dims[node_idx].border_bottom_width)
    }
    #[inline(always)]
    pub fn get_border_left_width(&self, node_idx: usize) -> Option<f32> {
        decode_resolved_px_i16(self.tier2_dims[node_idx].border_left_width)
    }
    // -- Raw i16 getters for macro fast paths --
    #[inline(always)]
58905
    pub fn get_padding_top_raw(&self, node_idx: usize) -> i16 {
58905
        self.tier2_dims[node_idx].padding_top
58905
    }
    #[inline(always)]
58905
    pub fn get_padding_right_raw(&self, node_idx: usize) -> i16 {
58905
        self.tier2_dims[node_idx].padding_right
58905
    }
    #[inline(always)]
58905
    pub fn get_padding_bottom_raw(&self, node_idx: usize) -> i16 {
58905
        self.tier2_dims[node_idx].padding_bottom
58905
    }
    #[inline(always)]
58905
    pub fn get_padding_left_raw(&self, node_idx: usize) -> i16 {
58905
        self.tier2_dims[node_idx].padding_left
58905
    }
    #[inline(always)]
25935
    pub fn get_margin_top_raw(&self, node_idx: usize) -> i16 {
25935
        self.tier2_dims[node_idx].margin_top
25935
    }
    #[inline(always)]
25935
    pub fn get_margin_right_raw(&self, node_idx: usize) -> i16 {
25935
        self.tier2_dims[node_idx].margin_right
25935
    }
    #[inline(always)]
25935
    pub fn get_margin_bottom_raw(&self, node_idx: usize) -> i16 {
25935
        self.tier2_dims[node_idx].margin_bottom
25935
    }
    #[inline(always)]
25935
    pub fn get_margin_left_raw(&self, node_idx: usize) -> i16 {
25935
        self.tier2_dims[node_idx].margin_left
25935
    }
    #[inline(always)]
74375
    pub fn get_border_top_width_raw(&self, node_idx: usize) -> i16 {
74375
        self.tier2_dims[node_idx].border_top_width
74375
    }
    #[inline(always)]
74375
    pub fn get_border_right_width_raw(&self, node_idx: usize) -> i16 {
74375
        self.tier2_dims[node_idx].border_right_width
74375
    }
    #[inline(always)]
74375
    pub fn get_border_bottom_width_raw(&self, node_idx: usize) -> i16 {
74375
        self.tier2_dims[node_idx].border_bottom_width
74375
    }
    #[inline(always)]
74375
    pub fn get_border_left_width_raw(&self, node_idx: usize) -> i16 {
74375
        self.tier2_dims[node_idx].border_left_width
74375
    }
    #[inline(always)]
735
    pub fn get_top(&self, node_idx: usize) -> i16 {
735
        self.tier2_dims[node_idx].top
735
    }
    #[inline(always)]
735
    pub fn get_right(&self, node_idx: usize) -> i16 {
735
        self.tier2_dims[node_idx].right
735
    }
    #[inline(always)]
735
    pub fn get_bottom(&self, node_idx: usize) -> i16 {
735
        self.tier2_dims[node_idx].bottom
735
    }
    #[inline(always)]
735
    pub fn get_left(&self, node_idx: usize) -> i16 {
735
        self.tier2_dims[node_idx].left
735
    }
    #[inline(always)]
665
    pub fn get_flex_grow(&self, node_idx: usize) -> Option<f32> {
665
        decode_flex_u16(self.tier2_dims[node_idx].flex_grow)
665
    }
    #[inline(always)]
665
    pub fn get_flex_shrink(&self, node_idx: usize) -> Option<f32> {
665
        decode_flex_u16(self.tier2_dims[node_idx].flex_shrink)
665
    }
    #[inline(always)]
48650
    pub fn get_z_index(&self, node_idx: usize) -> i16 {
48650
        self.tier2_cold[node_idx].z_index
48650
    }
    // -- Border colors (u32 RGBA) — cold tier --
    #[inline(always)]
48440
    pub fn get_border_top_color_raw(&self, node_idx: usize) -> u32 {
48440
        self.tier2_cold[node_idx].border_top_color
48440
    }
    #[inline(always)]
48440
    pub fn get_border_right_color_raw(&self, node_idx: usize) -> u32 {
48440
        self.tier2_cold[node_idx].border_right_color
48440
    }
    #[inline(always)]
48440
    pub fn get_border_bottom_color_raw(&self, node_idx: usize) -> u32 {
48440
        self.tier2_cold[node_idx].border_bottom_color
48440
    }
    #[inline(always)]
48440
    pub fn get_border_left_color_raw(&self, node_idx: usize) -> u32 {
48440
        self.tier2_cold[node_idx].border_left_color
48440
    }
    // -- Border styles (packed u16) — cold tier --
    #[inline(always)]
    pub fn get_border_styles_packed(&self, node_idx: usize) -> u16 {
        self.tier2_cold[node_idx].border_styles_packed
    }
    #[inline(always)]
73710
    pub fn get_border_top_style(&self, node_idx: usize) -> BorderStyle {
73710
        decode_border_top_style(self.tier2_cold[node_idx].border_styles_packed)
73710
    }
    #[inline(always)]
73710
    pub fn get_border_right_style(&self, node_idx: usize) -> BorderStyle {
73710
        decode_border_right_style(self.tier2_cold[node_idx].border_styles_packed)
73710
    }
    #[inline(always)]
73710
    pub fn get_border_bottom_style(&self, node_idx: usize) -> BorderStyle {
73710
        decode_border_bottom_style(self.tier2_cold[node_idx].border_styles_packed)
73710
    }
    #[inline(always)]
73710
    pub fn get_border_left_style(&self, node_idx: usize) -> BorderStyle {
73710
        decode_border_left_style(self.tier2_cold[node_idx].border_styles_packed)
73710
    }
    // -- Border spacing — cold tier --
    #[inline(always)]
1155
    pub fn get_border_spacing_h_raw(&self, node_idx: usize) -> i16 {
1155
        self.tier2_cold[node_idx].border_spacing_h
1155
    }
    #[inline(always)]
1155
    pub fn get_border_spacing_v_raw(&self, node_idx: usize) -> i16 {
1155
        self.tier2_cold[node_idx].border_spacing_v
1155
    }
    // -- Tab size — cold tier --
    #[inline(always)]
32970
    pub fn get_tab_size_raw(&self, node_idx: usize) -> i16 {
32970
        self.tier2_cold[node_idx].tab_size
32970
    }
    // -- Border radii — cold tier (i16 px × 10, I16_SENTINEL = unset = 0) --
    #[inline(always)]
85820
    pub fn get_border_top_left_radius_raw(&self, node_idx: usize) -> i16 {
85820
        self.tier2_cold[node_idx].border_top_left_radius
85820
    }
    #[inline(always)]
85820
    pub fn get_border_top_right_radius_raw(&self, node_idx: usize) -> i16 {
85820
        self.tier2_cold[node_idx].border_top_right_radius
85820
    }
    #[inline(always)]
85820
    pub fn get_border_bottom_left_radius_raw(&self, node_idx: usize) -> i16 {
85820
        self.tier2_cold[node_idx].border_bottom_left_radius
85820
    }
    #[inline(always)]
85820
    pub fn get_border_bottom_right_radius_raw(&self, node_idx: usize) -> i16 {
85820
        self.tier2_cold[node_idx].border_bottom_right_radius
85820
    }
    // -- Opacity / transform / hot flags --
    /// Raw opacity byte. `OPACITY_SENTINEL` (255) = unset (default = 1.0).
    /// Otherwise value / 254.0 yields the opacity in [0.0, 1.0].
    #[inline(always)]
76650
    pub fn get_opacity_raw(&self, node_idx: usize) -> u8 {
76650
        self.tier2_cold[node_idx].opacity
76650
    }
    #[inline(always)]
    pub fn get_hot_flags(&self, node_idx: usize) -> u8 {
        self.tier2_cold[node_idx].hot_flags
    }
    #[inline(always)]
70350
    pub fn has_transform(&self, node_idx: usize) -> bool {
70350
        self.tier2_cold[node_idx].hot_flags & HOT_FLAG_HAS_TRANSFORM != 0
70350
    }
    #[inline(always)]
    pub fn has_transform_origin(&self, node_idx: usize) -> bool {
        self.tier2_cold[node_idx].hot_flags & HOT_FLAG_HAS_TRANSFORM_ORIGIN != 0
    }
    #[inline(always)]
61600
    pub fn has_box_shadow(&self, node_idx: usize) -> bool {
61600
        self.tier2_cold[node_idx].hot_flags & HOT_FLAG_HAS_BOX_SHADOW != 0
61600
    }
    #[inline(always)]
32970
    pub fn has_text_decoration(&self, node_idx: usize) -> bool {
32970
        self.tier2_cold[node_idx].hot_flags & HOT_FLAG_HAS_TEXT_DECORATION != 0
32970
    }
    #[inline(always)]
95620
    pub fn has_background(&self, node_idx: usize) -> bool {
95620
        self.tier2_cold[node_idx].hot_flags & HOT_FLAG_HAS_BACKGROUND != 0
95620
    }
    #[inline(always)]
26915
    pub fn has_clip_path(&self, node_idx: usize) -> bool {
26915
        self.tier2_cold[node_idx].hot_flags & HOT_FLAG_HAS_CLIP_PATH != 0
26915
    }
    #[inline(always)]
26810
    pub fn has_scrollbar_css(&self, node_idx: usize) -> bool {
26810
        self.tier2_cold[node_idx].extra_flags & EXTRA_FLAG_HAS_SCROLLBAR_CSS != 0
26810
    }
    #[inline(always)]
25270
    pub fn has_counter(&self, node_idx: usize) -> bool {
25270
        self.tier2_cold[node_idx].extra_flags & EXTRA_FLAG_HAS_COUNTER != 0
25270
    }
    #[inline(always)]
53620
    pub fn has_break(&self, node_idx: usize) -> bool {
53620
        self.tier2_cold[node_idx].extra_flags & EXTRA_FLAG_HAS_BREAK != 0
53620
    }
    #[inline(always)]
60865
    pub fn has_text_orientation(&self, node_idx: usize) -> bool {
60865
        self.tier2_cold[node_idx].extra_flags & EXTRA_FLAG_HAS_TEXT_ORIENTATION != 0
60865
    }
    #[inline(always)]
    pub fn has_text_shadow(&self, node_idx: usize) -> bool {
        self.tier2_cold[node_idx].extra_flags & EXTRA_FLAG_HAS_TEXT_SHADOW != 0
    }
    #[inline(always)]
    pub fn has_backdrop_filter(&self, node_idx: usize) -> bool {
        self.tier2_cold[node_idx].extra_flags & EXTRA_FLAG_HAS_BACKDROP_FILTER != 0
    }
    #[inline(always)]
    pub fn has_filter(&self, node_idx: usize) -> bool {
        self.tier2_cold[node_idx].extra_flags & EXTRA_FLAG_HAS_FILTER != 0
    }
    #[inline(always)]
    pub fn has_mix_blend_mode(&self, node_idx: usize) -> bool {
        self.tier2_cold[node_idx].extra_flags & EXTRA_FLAG_HAS_MIX_BLEND_MODE != 0
    }
    /// DOM-level fast-path check: returns `true` if the given flag bit is set
    /// (some node in this DOM declared the corresponding property).
    #[inline(always)]
    pub fn dom_declared(&self, flag: u32) -> bool {
        self.dom_declared_flags & flag != 0
    }
    /// Scrollbar-gutter: 0 = auto (default), 1 = stable, 2 = both-edges, 3 = mirror.
    #[inline(always)]
47635
    pub fn get_scrollbar_gutter_bits(&self, node_idx: usize) -> u8 {
47635
        (self.tier2_cold[node_idx].hot_flags & HOT_FLAG_SCROLLBAR_GUTTER_MASK)
47635
            >> HOT_FLAG_SCROLLBAR_GUTTER_SHIFT
47635
    }
    // -- Tier 2b getters (text props) --
    #[inline(always)]
32970
    pub fn get_text_color_raw(&self, node_idx: usize) -> u32 {
32970
        self.tier2b_text[node_idx].text_color
32970
    }
    #[inline(always)]
    pub fn get_font_family_hash(&self, node_idx: usize) -> u64 {
        self.tier2b_text[node_idx].font_family_hash
    }
    #[inline(always)]
32970
    pub fn get_line_height(&self, node_idx: usize) -> Option<f32> {
32970
        decode_resolved_px_i16(self.tier2b_text[node_idx].line_height)
32970
    }
    #[inline(always)]
32970
    pub fn get_letter_spacing(&self, node_idx: usize) -> Option<f32> {
32970
        decode_resolved_px_i16(self.tier2b_text[node_idx].letter_spacing)
32970
    }
    #[inline(always)]
32970
    pub fn get_word_spacing(&self, node_idx: usize) -> Option<f32> {
32970
        decode_resolved_px_i16(self.tier2b_text[node_idx].word_spacing)
32970
    }
    #[inline(always)]
    pub fn get_text_indent(&self, node_idx: usize) -> Option<f32> {
        decode_resolved_px_i16(self.tier2b_text[node_idx].text_indent)
    }
}
// =============================================================================
// Helper: encode a CssPropertyValue<PixelValue> into i16 resolved-px
// =============================================================================
/// Resolve a CssPropertyValue<PixelValue> to an i16 ×10 encoding.
/// Only handles `Exact(px(...))` values. Everything else → sentinel.
/// For the compact cache builder, we only pre-resolve absolute pixel values.
/// Relative units (em, %, etc.) get sentinel and fall back to the slow path.
#[inline]
144210
pub fn encode_css_pixel_as_i16(prop: &CssPropertyValue<PixelValue>) -> i16 {
144210
    match prop {
144126
        CssPropertyValue::Exact(pv) => {
144126
            if pv.metric == SizeMetric::Px {
141288
                encode_resolved_px_i16(pv.number.get())
            } else {
2838
                I16_SENTINEL // non-px units need resolution context → slow path
            }
        }
84
        CssPropertyValue::Auto => I16_AUTO,
        CssPropertyValue::Initial => I16_INITIAL,
        CssPropertyValue::Inherit => I16_INHERIT,
        _ => I16_SENTINEL,
    }
144210
}
#[cfg(test)]
mod tests {
    use super::*;
    #[test]
1
    fn test_tier1_roundtrip() {
1
        let t1 = encode_tier1(
1
            LayoutDisplay::Flex,
1
            LayoutPosition::Relative,
1
            LayoutFloat::Left,
1
            LayoutOverflow::Hidden,
1
            LayoutOverflow::Scroll,
1
            LayoutBoxSizing::BorderBox,
1
            LayoutFlexDirection::Column,
1
            LayoutFlexWrap::Wrap,
1
            LayoutJustifyContent::SpaceBetween,
1
            LayoutAlignItems::Center,
1
            LayoutAlignContent::End,
1
            LayoutWritingMode::VerticalRl,
1
            LayoutClear::Both,
1
            StyleFontWeight::Bold,
1
            StyleFontStyle::Italic,
1
            StyleTextAlign::Center,
1
            StyleVisibility::Hidden,
1
            StyleWhiteSpace::Pre,
1
            StyleDirection::Rtl,
1
            StyleVerticalAlign::Middle,
1
            StyleBorderCollapse::Collapse,
        );
1
        assert!(tier1_is_populated(t1));
1
        assert_eq!(decode_display(t1), LayoutDisplay::Flex);
1
        assert_eq!(decode_position(t1), LayoutPosition::Relative);
1
        assert_eq!(decode_float(t1), LayoutFloat::Left);
1
        assert_eq!(decode_overflow_x(t1), LayoutOverflow::Hidden);
1
        assert_eq!(decode_overflow_y(t1), LayoutOverflow::Scroll);
1
        assert_eq!(decode_box_sizing(t1), LayoutBoxSizing::BorderBox);
1
        assert_eq!(decode_flex_direction(t1), LayoutFlexDirection::Column);
1
        assert_eq!(decode_flex_wrap(t1), LayoutFlexWrap::Wrap);
1
        assert_eq!(decode_justify_content(t1), LayoutJustifyContent::SpaceBetween);
1
        assert_eq!(decode_align_items(t1), LayoutAlignItems::Center);
1
        assert_eq!(decode_align_content(t1), LayoutAlignContent::End);
1
        assert_eq!(decode_writing_mode(t1), LayoutWritingMode::VerticalRl);
1
        assert_eq!(decode_clear(t1), LayoutClear::Both);
1
        assert_eq!(decode_font_weight(t1), StyleFontWeight::Bold);
1
        assert_eq!(decode_font_style(t1), StyleFontStyle::Italic);
1
        assert_eq!(decode_text_align(t1), StyleTextAlign::Center);
1
        assert_eq!(decode_visibility(t1), StyleVisibility::Hidden);
1
        assert_eq!(decode_white_space(t1), StyleWhiteSpace::Pre);
1
        assert_eq!(decode_direction(t1), StyleDirection::Rtl);
1
        assert_eq!(decode_vertical_align(t1), StyleVerticalAlign::Middle);
1
        assert_eq!(decode_border_collapse(t1), StyleBorderCollapse::Collapse);
1
    }
    #[test]
1
    fn test_tier1_defaults() {
1
        let t1 = encode_tier1(
1
            LayoutDisplay::Block,
1
            LayoutPosition::Static,
1
            LayoutFloat::None,
1
            LayoutOverflow::Visible,
1
            LayoutOverflow::Visible,
1
            LayoutBoxSizing::ContentBox,
1
            LayoutFlexDirection::Row,
1
            LayoutFlexWrap::NoWrap,
1
            LayoutJustifyContent::FlexStart,
1
            LayoutAlignItems::Stretch,
1
            LayoutAlignContent::Stretch,
1
            LayoutWritingMode::HorizontalTb,
1
            LayoutClear::None,
1
            StyleFontWeight::Normal,
1
            StyleFontStyle::Normal,
1
            StyleTextAlign::Left,
1
            StyleVisibility::Visible,
1
            StyleWhiteSpace::Normal,
1
            StyleDirection::Ltr,
1
            StyleVerticalAlign::Baseline,
1
            StyleBorderCollapse::Separate,
        );
1
        assert!(tier1_is_populated(t1));
1
        assert_eq!(decode_display(t1), LayoutDisplay::Block);
1
        assert_eq!(decode_position(t1), LayoutPosition::Static);
1
    }
    #[test]
1
    fn test_pixel_value_u32_roundtrip() {
1
        let pv = PixelValue::px(123.456);
1
        let encoded = encode_pixel_value_u32(&pv);
1
        assert!(encoded < U32_SENTINEL_THRESHOLD);
1
        let decoded = decode_pixel_value_u32(encoded).unwrap();
1
        assert_eq!(decoded.metric, SizeMetric::Px);
        // Check within precision (×1000)
1
        assert!((decoded.number.get() - 123.456).abs() < 0.002);
1
    }
    #[test]
1
    fn test_pixel_value_u32_percent() {
1
        let pv = PixelValue {
1
            metric: SizeMetric::Percent,
1
            number: FloatValue::new(50.0),
1
        };
1
        let encoded = encode_pixel_value_u32(&pv);
1
        let decoded = decode_pixel_value_u32(encoded).unwrap();
1
        assert_eq!(decoded.metric, SizeMetric::Percent);
1
        assert!((decoded.number.get() - 50.0).abs() < 0.002);
1
    }
    #[test]
1
    fn test_sentinel_values() {
1
        assert_eq!(decode_pixel_value_u32(U32_SENTINEL), None);
1
        assert_eq!(decode_pixel_value_u32(U32_AUTO), None);
1
        assert_eq!(decode_pixel_value_u32(U32_MIN_CONTENT), None);
1
        assert_eq!(decode_resolved_px_i16(I16_SENTINEL), None);
1
        assert_eq!(decode_resolved_px_i16(I16_AUTO), None);
1
    }
    #[test]
1
    fn test_resolved_px_i16_roundtrip() {
1
        let px = 123.4f32;
1
        let encoded = encode_resolved_px_i16(px);
1
        let decoded = decode_resolved_px_i16(encoded).unwrap();
1
        assert!((decoded - 123.4).abs() < 0.11);
        // Negative values
1
        let px = -50.7f32;
1
        let encoded = encode_resolved_px_i16(px);
1
        let decoded = decode_resolved_px_i16(encoded).unwrap();
1
        assert!((decoded - (-50.7)).abs() < 0.11);
1
    }
    #[test]
1
    fn test_flex_u16_roundtrip() {
1
        let v = 2.5f32;
1
        let encoded = encode_flex_u16(v);
1
        let decoded = decode_flex_u16(encoded).unwrap();
1
        assert!((decoded - 2.5).abs() < 0.011);
1
    }
    #[test]
1
    fn test_compact_node_props_size() {
        // 72B hot props: 8×u32 dimensions (32B) + 16×i16 box model (32B)
        // + 2×u16 flex (4B) + 1×i16 order + align/pos tier1 bits (4B).
1
        assert_eq!(core::mem::size_of::<CompactNodeProps>(), 72);
1
    }
    #[test]
1
    fn test_compact_node_props_cold_size() {
        // 48B cold props: 4×u32 border colors (16B) + 4×i16 border radii (8B)
        // + 1×i16 z_index + 1×u16 border_styles_packed + 2×i16 border_spacing
        // + 1×i16 tab_size + 4×i16 grid placement (8B) + 3×u8 (opacity,
        // hot_flags, extra_flags) = 45B, padded to 48B for u32 alignment.
1
        assert_eq!(core::mem::size_of::<CompactNodePropsCold>(), 48);
1
    }
    #[test]
1
    fn test_compact_text_props_size() {
1
        assert_eq!(core::mem::size_of::<CompactTextProps>(), 24);
1
    }
    // ========================================================================
    // Tier1 enum 0-sentinel contract
    //
    // Tier1 packs 21 enums into a single u64. A bit run that is all zeros
    // must decode to the CSS initial value of that property, because an
    // unpopulated tier1 field is all zeros. If any encoder shifts so that
    // `0 -> something-other-than-initial`, every node that didn't explicitly
    // set the property silently gets the wrong default — which is exactly
    // how the calc.c grid stretch regression shipped (Start encoded as 0,
    // so every grid container reported justify-items: Start instead of
    // the CSS default Stretch-for-grid, collapsing the calc button grid).
    //
    // Test invariant: decoding a u8 of 0 for every enum yields the CSS
    // initial value of that property.
    // ========================================================================
    #[test]
1
    fn test_justify_items_zero_is_stretch() {
        // CSS initial for justify-items is `normal`, which on a grid item
        // behaves as `stretch`. The tier1 bit pattern 0 must round-trip to
        // Stretch so unset grid containers don't collapse their items.
1
        assert_eq!(layout_justify_items_from_u8(0), LayoutJustifyItems::Stretch);
1
        assert_eq!(layout_justify_items_to_u8(LayoutJustifyItems::Stretch), 0);
1
    }
    #[test]
1
    fn test_tier1_enum_zero_sentinel_is_css_initial() {
1
        assert_eq!(layout_display_from_u8(0), LayoutDisplay::Block);
1
        assert_eq!(layout_position_from_u8(0), LayoutPosition::Static);
1
        assert_eq!(layout_float_from_u8(0), LayoutFloat::None);
1
        assert_eq!(layout_overflow_from_u8(0), LayoutOverflow::Visible);
1
        assert_eq!(layout_box_sizing_from_u8(0), LayoutBoxSizing::ContentBox);
1
        assert_eq!(layout_flex_direction_from_u8(0), LayoutFlexDirection::Row);
1
        assert_eq!(layout_flex_wrap_from_u8(0), LayoutFlexWrap::NoWrap);
1
        assert_eq!(layout_justify_content_from_u8(0), LayoutJustifyContent::FlexStart);
1
        assert_eq!(layout_align_items_from_u8(0), LayoutAlignItems::Stretch);
1
        assert_eq!(layout_align_content_from_u8(0), LayoutAlignContent::Stretch);
1
        assert_eq!(layout_align_self_from_u8(0), LayoutAlignSelf::Auto);
1
        assert_eq!(layout_justify_self_from_u8(0), LayoutJustifySelf::Auto);
1
        assert_eq!(layout_justify_items_from_u8(0), LayoutJustifyItems::Stretch);
1
        assert_eq!(layout_grid_auto_flow_from_u8(0), LayoutGridAutoFlow::Row);
1
        assert_eq!(layout_writing_mode_from_u8(0), LayoutWritingMode::HorizontalTb);
1
        assert_eq!(layout_clear_from_u8(0), LayoutClear::None);
1
        assert_eq!(style_font_weight_from_u8(0), StyleFontWeight::Normal);
1
        assert_eq!(style_font_style_from_u8(0), StyleFontStyle::Normal);
        // text-align initial is `start`; we collapse `start` → `left` on
        // LTR runs at encode time, so the 0 slot decodes to Left.
1
        assert_eq!(style_text_align_from_u8(0), StyleTextAlign::Left);
1
        assert_eq!(style_visibility_from_u8(0), StyleVisibility::Visible);
1
        assert_eq!(style_white_space_from_u8(0), StyleWhiteSpace::Normal);
1
        assert_eq!(style_direction_from_u8(0), StyleDirection::Ltr);
1
        assert_eq!(style_vertical_align_from_u8(0), StyleVerticalAlign::Baseline);
1
        assert_eq!(border_collapse_from_u8(0), StyleBorderCollapse::Separate);
1
    }
    #[test]
1
    fn test_tier1_enum_initial_encodes_to_zero() {
        // Mirror of the above — encoding the CSS initial value must
        // produce 0, otherwise an `all-zeros` tier1 bit run would encode
        // a non-initial value and nodes without explicit properties would
        // silently inherit the wrong default.
1
        assert_eq!(layout_display_to_u8(LayoutDisplay::Block), 0);
1
        assert_eq!(layout_position_to_u8(LayoutPosition::Static), 0);
1
        assert_eq!(layout_float_to_u8(LayoutFloat::None), 0);
1
        assert_eq!(layout_overflow_to_u8(LayoutOverflow::Visible), 0);
1
        assert_eq!(layout_box_sizing_to_u8(LayoutBoxSizing::ContentBox), 0);
1
        assert_eq!(layout_flex_direction_to_u8(LayoutFlexDirection::Row), 0);
1
        assert_eq!(layout_flex_wrap_to_u8(LayoutFlexWrap::NoWrap), 0);
1
        assert_eq!(layout_justify_content_to_u8(LayoutJustifyContent::FlexStart), 0);
1
        assert_eq!(layout_align_items_to_u8(LayoutAlignItems::Stretch), 0);
1
        assert_eq!(layout_align_content_to_u8(LayoutAlignContent::Stretch), 0);
1
        assert_eq!(layout_align_self_to_u8(LayoutAlignSelf::Auto), 0);
1
        assert_eq!(layout_justify_self_to_u8(LayoutJustifySelf::Auto), 0);
1
        assert_eq!(layout_justify_items_to_u8(LayoutJustifyItems::Stretch), 0);
1
        assert_eq!(layout_grid_auto_flow_to_u8(LayoutGridAutoFlow::Row), 0);
1
        assert_eq!(layout_writing_mode_to_u8(LayoutWritingMode::HorizontalTb), 0);
1
        assert_eq!(layout_clear_to_u8(LayoutClear::None), 0);
1
        assert_eq!(style_font_weight_to_u8(StyleFontWeight::Normal), 0);
1
        assert_eq!(style_font_style_to_u8(StyleFontStyle::Normal), 0);
1
        assert_eq!(style_text_align_to_u8(StyleTextAlign::Left), 0);
1
        assert_eq!(style_visibility_to_u8(StyleVisibility::Visible), 0);
1
        assert_eq!(style_white_space_to_u8(StyleWhiteSpace::Normal), 0);
1
        assert_eq!(style_direction_to_u8(StyleDirection::Ltr), 0);
1
        assert_eq!(style_vertical_align_to_u8(StyleVerticalAlign::Baseline), 0);
1
        assert_eq!(border_collapse_to_u8(StyleBorderCollapse::Separate), 0);
1
    }
    // ========================================================================
    // Exhaustive round-trip: every variant of every enum must survive
    // encode → decode unchanged. Catches any reordering that maps two
    // different variants to the same u8, or any mask-width mismatch.
    // ========================================================================
    macro_rules! roundtrip_all {
        ($name:ident, $to:ident, $from:ident, [$($variant:expr),+ $(,)?]) => {
            #[test]
24
            fn $name() {
155
                for v in [$($variant),+] {
131
                    let u = $to(v);
131
                    let decoded = $from(u);
131
                    assert_eq!(decoded, v, "{:?} != {:?} (via u8 = {})", decoded, v, u);
                }
24
            }
        };
    }
    roundtrip_all!(rt_display, layout_display_to_u8, layout_display_from_u8, [
        LayoutDisplay::Block, LayoutDisplay::Inline, LayoutDisplay::InlineBlock,
        LayoutDisplay::Flex, LayoutDisplay::None, LayoutDisplay::InlineFlex,
        LayoutDisplay::Table, LayoutDisplay::InlineTable, LayoutDisplay::TableRowGroup,
        LayoutDisplay::TableHeaderGroup, LayoutDisplay::TableFooterGroup,
        LayoutDisplay::TableRow, LayoutDisplay::TableColumnGroup,
        LayoutDisplay::TableColumn, LayoutDisplay::TableCell,
        LayoutDisplay::TableCaption, LayoutDisplay::FlowRoot,
        LayoutDisplay::ListItem, LayoutDisplay::RunIn, LayoutDisplay::Marker,
        LayoutDisplay::Grid, LayoutDisplay::InlineGrid, LayoutDisplay::Contents,
    ]);
    roundtrip_all!(rt_position, layout_position_to_u8, layout_position_from_u8, [
        LayoutPosition::Static, LayoutPosition::Relative, LayoutPosition::Absolute,
        LayoutPosition::Fixed, LayoutPosition::Sticky,
    ]);
    roundtrip_all!(rt_float, layout_float_to_u8, layout_float_from_u8, [
        LayoutFloat::None, LayoutFloat::Left, LayoutFloat::Right,
    ]);
    roundtrip_all!(rt_overflow, layout_overflow_to_u8, layout_overflow_from_u8, [
        LayoutOverflow::Visible, LayoutOverflow::Hidden, LayoutOverflow::Scroll,
        LayoutOverflow::Auto, LayoutOverflow::Clip,
    ]);
    roundtrip_all!(rt_box_sizing, layout_box_sizing_to_u8, layout_box_sizing_from_u8, [
        LayoutBoxSizing::ContentBox, LayoutBoxSizing::BorderBox,
    ]);
    roundtrip_all!(rt_flex_direction, layout_flex_direction_to_u8, layout_flex_direction_from_u8, [
        LayoutFlexDirection::Row, LayoutFlexDirection::RowReverse,
        LayoutFlexDirection::Column, LayoutFlexDirection::ColumnReverse,
    ]);
    roundtrip_all!(rt_flex_wrap, layout_flex_wrap_to_u8, layout_flex_wrap_from_u8, [
        LayoutFlexWrap::NoWrap, LayoutFlexWrap::Wrap, LayoutFlexWrap::WrapReverse,
    ]);
    roundtrip_all!(rt_justify_content, layout_justify_content_to_u8, layout_justify_content_from_u8, [
        LayoutJustifyContent::FlexStart, LayoutJustifyContent::FlexEnd,
        LayoutJustifyContent::Start, LayoutJustifyContent::End,
        LayoutJustifyContent::Center, LayoutJustifyContent::SpaceBetween,
        LayoutJustifyContent::SpaceAround, LayoutJustifyContent::SpaceEvenly,
    ]);
    roundtrip_all!(rt_align_items, layout_align_items_to_u8, layout_align_items_from_u8, [
        LayoutAlignItems::Stretch, LayoutAlignItems::Center, LayoutAlignItems::Start,
        LayoutAlignItems::End, LayoutAlignItems::Baseline,
    ]);
    roundtrip_all!(rt_align_self, layout_align_self_to_u8, layout_align_self_from_u8, [
        LayoutAlignSelf::Auto, LayoutAlignSelf::Stretch, LayoutAlignSelf::Center,
        LayoutAlignSelf::Start, LayoutAlignSelf::End, LayoutAlignSelf::Baseline,
    ]);
    roundtrip_all!(rt_justify_self, layout_justify_self_to_u8, layout_justify_self_from_u8, [
        LayoutJustifySelf::Auto, LayoutJustifySelf::Start, LayoutJustifySelf::End,
        LayoutJustifySelf::Center, LayoutJustifySelf::Stretch,
    ]);
    roundtrip_all!(rt_justify_items, layout_justify_items_to_u8, layout_justify_items_from_u8, [
        LayoutJustifyItems::Stretch, LayoutJustifyItems::Start,
        LayoutJustifyItems::End, LayoutJustifyItems::Center,
    ]);
    roundtrip_all!(rt_grid_auto_flow, layout_grid_auto_flow_to_u8, layout_grid_auto_flow_from_u8, [
        LayoutGridAutoFlow::Row, LayoutGridAutoFlow::Column,
        LayoutGridAutoFlow::RowDense, LayoutGridAutoFlow::ColumnDense,
    ]);
    roundtrip_all!(rt_align_content, layout_align_content_to_u8, layout_align_content_from_u8, [
        LayoutAlignContent::Stretch, LayoutAlignContent::Center,
        LayoutAlignContent::Start, LayoutAlignContent::End,
        LayoutAlignContent::SpaceBetween, LayoutAlignContent::SpaceAround,
    ]);
    roundtrip_all!(rt_writing_mode, layout_writing_mode_to_u8, layout_writing_mode_from_u8, [
        LayoutWritingMode::HorizontalTb, LayoutWritingMode::VerticalRl,
        LayoutWritingMode::VerticalLr,
    ]);
    roundtrip_all!(rt_clear, layout_clear_to_u8, layout_clear_from_u8, [
        LayoutClear::None, LayoutClear::Left, LayoutClear::Right, LayoutClear::Both,
    ]);
    roundtrip_all!(rt_font_weight, style_font_weight_to_u8, style_font_weight_from_u8, [
        StyleFontWeight::Normal, StyleFontWeight::W100, StyleFontWeight::W200,
        StyleFontWeight::W300, StyleFontWeight::W500, StyleFontWeight::W600,
        StyleFontWeight::Bold, StyleFontWeight::W800, StyleFontWeight::W900,
        StyleFontWeight::Lighter, StyleFontWeight::Bolder,
    ]);
    roundtrip_all!(rt_font_style, style_font_style_to_u8, style_font_style_from_u8, [
        StyleFontStyle::Normal, StyleFontStyle::Italic, StyleFontStyle::Oblique,
    ]);
    roundtrip_all!(rt_text_align, style_text_align_to_u8, style_text_align_from_u8, [
        StyleTextAlign::Left, StyleTextAlign::Center, StyleTextAlign::Right,
        StyleTextAlign::Justify, StyleTextAlign::Start, StyleTextAlign::End,
    ]);
    roundtrip_all!(rt_visibility, style_visibility_to_u8, style_visibility_from_u8, [
        StyleVisibility::Visible, StyleVisibility::Hidden, StyleVisibility::Collapse,
    ]);
    roundtrip_all!(rt_white_space, style_white_space_to_u8, style_white_space_from_u8, [
        StyleWhiteSpace::Normal, StyleWhiteSpace::Pre, StyleWhiteSpace::Nowrap,
        StyleWhiteSpace::PreWrap, StyleWhiteSpace::PreLine, StyleWhiteSpace::BreakSpaces,
    ]);
    roundtrip_all!(rt_direction, style_direction_to_u8, style_direction_from_u8, [
        StyleDirection::Ltr, StyleDirection::Rtl,
    ]);
    roundtrip_all!(rt_vertical_align, style_vertical_align_to_u8, style_vertical_align_from_u8, [
        StyleVerticalAlign::Baseline, StyleVerticalAlign::Top, StyleVerticalAlign::Middle,
        StyleVerticalAlign::Bottom, StyleVerticalAlign::Sub, StyleVerticalAlign::Superscript,
        StyleVerticalAlign::TextTop, StyleVerticalAlign::TextBottom,
    ]);
    roundtrip_all!(rt_border_collapse, border_collapse_to_u8, border_collapse_from_u8, [
        StyleBorderCollapse::Separate, StyleBorderCollapse::Collapse,
    ]);
    // ========================================================================
    // Bit-layout safety: every encoder must produce a u8 whose bits all
    // fit inside the mask allocated for that property in the tier1 u64.
    // If an enum grows a new variant that overflows its mask, the encoded
    // bits would leak into the next property's slot and silently corrupt
    // unrelated state.
    // ========================================================================
    #[test]
1
    fn test_encoded_u8_fits_in_tier1_mask() {
24
        fn assert_fits(name: &str, val: u8, mask: u64) {
24
            assert!(
24
                (val as u64) & !mask == 0,
                "{}: encoded u8 {} overflows mask {:b}",
                name, val, mask,
            );
24
        }
1
        assert_fits("display", layout_display_to_u8(LayoutDisplay::Contents), DISPLAY_MASK);
1
        assert_fits("position", layout_position_to_u8(LayoutPosition::Sticky), POSITION_MASK);
1
        assert_fits("float", layout_float_to_u8(LayoutFloat::Right), FLOAT_MASK);
1
        assert_fits("overflow", layout_overflow_to_u8(LayoutOverflow::Clip), OVERFLOW_MASK);
1
        assert_fits("box_sizing", layout_box_sizing_to_u8(LayoutBoxSizing::BorderBox), BOX_SIZING_MASK);
1
        assert_fits("flex_direction", layout_flex_direction_to_u8(LayoutFlexDirection::ColumnReverse), FLEX_DIR_MASK);
1
        assert_fits("flex_wrap", layout_flex_wrap_to_u8(LayoutFlexWrap::WrapReverse), FLEX_WRAP_MASK);
1
        assert_fits("justify_content", layout_justify_content_to_u8(LayoutJustifyContent::SpaceEvenly), JUSTIFY_MASK);
1
        assert_fits("align_items", layout_align_items_to_u8(LayoutAlignItems::Baseline), ALIGN_MASK);
1
        assert_fits("align_self", layout_align_self_to_u8(LayoutAlignSelf::Baseline), ALIGN_SELF_MASK);
1
        assert_fits("justify_self", layout_justify_self_to_u8(LayoutJustifySelf::Stretch), JUSTIFY_SELF_MASK);
1
        assert_fits("justify_items", layout_justify_items_to_u8(LayoutJustifyItems::Center), JUSTIFY_ITEMS_MASK);
1
        assert_fits("grid_auto_flow", layout_grid_auto_flow_to_u8(LayoutGridAutoFlow::ColumnDense), GRID_AUTO_FLOW_MASK);
1
        assert_fits("align_content", layout_align_content_to_u8(LayoutAlignContent::SpaceAround), ALIGN_MASK);
1
        assert_fits("writing_mode", layout_writing_mode_to_u8(LayoutWritingMode::VerticalLr), WRITING_MODE_MASK);
1
        assert_fits("clear", layout_clear_to_u8(LayoutClear::Both), CLEAR_MASK);
1
        assert_fits("font_weight", style_font_weight_to_u8(StyleFontWeight::Bolder), FONT_WEIGHT_MASK);
1
        assert_fits("font_style", style_font_style_to_u8(StyleFontStyle::Oblique), FONT_STYLE_MASK);
1
        assert_fits("text_align", style_text_align_to_u8(StyleTextAlign::End), TEXT_ALIGN_MASK);
1
        assert_fits("visibility", style_visibility_to_u8(StyleVisibility::Collapse), VISIBILITY_MASK);
1
        assert_fits("white_space", style_white_space_to_u8(StyleWhiteSpace::BreakSpaces), WHITE_SPACE_MASK);
1
        assert_fits("direction", style_direction_to_u8(StyleDirection::Rtl), DIRECTION_MASK);
1
        assert_fits("vertical_align", style_vertical_align_to_u8(StyleVerticalAlign::TextBottom), VERTICAL_ALIGN_MASK);
1
        assert_fits("border_collapse", border_collapse_to_u8(StyleBorderCollapse::Collapse), BORDER_COLLAPSE_MASK);
1
    }
    // ========================================================================
    // Empty tier1 decodes to all-initial — this is the core contract that
    // was violated by the pre-fix justify_items encoding. An empty u64 with
    // only TIER1_POPULATED_BIT set must decode every property to its CSS
    // initial value; this is how `build_compact_cache` can leave unspecified
    // properties at 0 and still produce the correct cascade result.
    // ========================================================================
    #[test]
1
    fn test_empty_tier1_decodes_to_initial_values() {
1
        let t1 = TIER1_POPULATED_BIT; // populated, but zero content
1
        assert!(tier1_is_populated(t1));
1
        assert_eq!(decode_display(t1), LayoutDisplay::Block);
1
        assert_eq!(decode_position(t1), LayoutPosition::Static);
1
        assert_eq!(decode_float(t1), LayoutFloat::None);
1
        assert_eq!(decode_overflow_x(t1), LayoutOverflow::Visible);
1
        assert_eq!(decode_overflow_y(t1), LayoutOverflow::Visible);
1
        assert_eq!(decode_box_sizing(t1), LayoutBoxSizing::ContentBox);
1
        assert_eq!(decode_flex_direction(t1), LayoutFlexDirection::Row);
1
        assert_eq!(decode_flex_wrap(t1), LayoutFlexWrap::NoWrap);
1
        assert_eq!(decode_justify_content(t1), LayoutJustifyContent::FlexStart);
1
        assert_eq!(decode_align_items(t1), LayoutAlignItems::Stretch);
1
        assert_eq!(decode_align_content(t1), LayoutAlignContent::Stretch);
1
        assert_eq!(decode_writing_mode(t1), LayoutWritingMode::HorizontalTb);
1
        assert_eq!(decode_clear(t1), LayoutClear::None);
1
        assert_eq!(decode_font_weight(t1), StyleFontWeight::Normal);
1
        assert_eq!(decode_font_style(t1), StyleFontStyle::Normal);
1
        assert_eq!(decode_text_align(t1), StyleTextAlign::Left);
1
        assert_eq!(decode_visibility(t1), StyleVisibility::Visible);
1
        assert_eq!(decode_white_space(t1), StyleWhiteSpace::Normal);
1
        assert_eq!(decode_direction(t1), StyleDirection::Ltr);
1
        assert_eq!(decode_vertical_align(t1), StyleVerticalAlign::Baseline);
1
        assert_eq!(decode_border_collapse(t1), StyleBorderCollapse::Separate);
1
    }
}