Skip to main content

azul_css/
compact_cache.rs

1//! Compact layout property cache — three-tier numeric encoding
2//!
3//! Replaces BTreeMap-based CSS property lookups with cache-friendly arrays.
4//! See `scripts/COMPACT_CACHE_PLAN.md` for design rationale.
5//!
6//! - **Tier 1**: `Vec<u64>` — ALL 21 enum properties bitpacked (8 B/node)
7//! - **Tier 2**: `Vec<CompactNodeProps>` — numeric dimensions + border colors/styles (96 B/node)
8//! - **Tier 2b**: `Vec<CompactTextProps>` — text/IFC properties (24 B/node)
9//! - **Tier 3**: `Vec<Option<Box<CompactOverflowProps>>>` — rare/complex (8 B/node)
10
11use crate::props::basic::length::{FloatValue, SizeMetric};
12use crate::props::basic::pixel::PixelValue;
13use crate::props::layout::{
14    display::LayoutDisplay,
15    dimensions::{LayoutHeight, LayoutWidth, LayoutMaxHeight, LayoutMaxWidth, LayoutMinHeight, LayoutMinWidth},
16    flex::{
17        LayoutAlignContent, LayoutAlignItems, LayoutFlexDirection, LayoutFlexWrap,
18        LayoutJustifyContent,
19    },
20    overflow::LayoutOverflow,
21    position::LayoutPosition,
22    wrapping::{LayoutClear, LayoutWritingMode},
23    table::StyleBorderCollapse,
24};
25use crate::props::layout::display::LayoutFloat;
26use crate::props::layout::dimensions::LayoutBoxSizing;
27use crate::props::basic::font::{StyleFontStyle, StyleFontWeight};
28use crate::props::basic::color::ColorU;
29use crate::props::style::{StyleTextAlign, StyleVerticalAlign, StyleVisibility, StyleWhiteSpace, StyleDirection};
30use crate::props::style::border::BorderStyle;
31use crate::props::property::{CssProperty, CssPropertyType};
32use crate::css::CssPropertyValue;
33use alloc::boxed::Box;
34use alloc::collections::BTreeMap;
35use alloc::vec::Vec;
36
37// =============================================================================
38// Sentinel Constants
39// =============================================================================
40
41/// u16 sentinel values (for resolved-px ×10 and flex ×100)
42pub const U16_SENTINEL: u16 = 0xFFFF;
43pub const U16_AUTO: u16 = 0xFFFE;
44pub const U16_NONE: u16 = 0xFFFD;
45pub const U16_INHERIT: u16 = 0xFFFC;
46pub const U16_INITIAL: u16 = 0xFFFB;
47pub const U16_MIN_CONTENT: u16 = 0xFFFA;
48pub const U16_MAX_CONTENT: u16 = 0xFFF9;
49/// Any u16 value >= this threshold is a sentinel, not a real value
50pub const U16_SENTINEL_THRESHOLD: u16 = 0xFFF9;
51
52/// i16 sentinel values (for signed resolved-px ×10)
53pub const I16_SENTINEL: i16 = 0x7FFF;       // 32767
54pub const I16_AUTO: i16 = 0x7FFE;           // 32766
55pub const I16_INHERIT: i16 = 0x7FFD;        // 32765
56pub const I16_INITIAL: i16 = 0x7FFC;        // 32764
57/// Any i16 value >= this threshold is a sentinel
58pub const I16_SENTINEL_THRESHOLD: i16 = 0x7FFC; // 32764
59
60/// u32 sentinel values (for dimension properties with unit info)
61pub const U32_SENTINEL: u32 = 0xFFFFFFFF;
62pub const U32_AUTO: u32 = 0xFFFFFFFE;
63pub const U32_NONE: u32 = 0xFFFFFFFD;
64pub const U32_INHERIT: u32 = 0xFFFFFFFC;
65pub const U32_INITIAL: u32 = 0xFFFFFFFB;
66pub const U32_MIN_CONTENT: u32 = 0xFFFFFFFA;
67pub const U32_MAX_CONTENT: u32 = 0xFFFFFFF9;
68/// Any u32 value >= this threshold is a sentinel
69pub const U32_SENTINEL_THRESHOLD: u32 = 0xFFFFFFF9;
70
71// =============================================================================
72// Tier 1: u64 bitfield — ALL enum properties
73// =============================================================================
74//
75// Bit layout (52 bits used, 12 spare):
76//   [4:0]    display          5 bits  (22 variants)
77//   [7:5]    position         3 bits  (5 variants)
78//   [9:8]    float            2 bits  (3 variants)
79//   [12:10]  overflow_x       3 bits  (5 variants)
80//   [15:13]  overflow_y       3 bits  (5 variants)
81//   [16]     box_sizing       1 bit   (2 variants)
82//   [18:17]  flex_direction   2 bits  (4 variants)
83//   [20:19]  flex_wrap        2 bits  (3 variants)
84//   [23:21]  justify_content  3 bits  (8 variants)
85//   [26:24]  align_items      3 bits  (5 variants)
86//   [29:27]  align_content    3 bits  (6 variants)
87//   [31:30]  writing_mode     2 bits  (3 variants)
88//   [33:32]  clear            2 bits  (4 variants)
89//   [37:34]  font_weight      4 bits  (11 variants)
90//   [39:38]  font_style       2 bits  (3 variants)
91//   [42:40]  text_align       3 bits  (6 variants)
92//   [44:43]  visibility       2 bits  (3 variants)
93//   [47:45]  white_space      3 bits  (6 variants)
94//   [48]     direction        1 bit   (2 variants)
95//   [51:49]  vertical_align   3 bits  (8 variants)
96//   [52]     border_collapse  1 bit   (2 variants)
97//   [63:53]  (spare / sentinel flags)
98
99// Bit offsets within u64
100const DISPLAY_SHIFT: u32 = 0;
101const POSITION_SHIFT: u32 = 5;
102const FLOAT_SHIFT: u32 = 8;
103const OVERFLOW_X_SHIFT: u32 = 10;
104const OVERFLOW_Y_SHIFT: u32 = 13;
105const BOX_SIZING_SHIFT: u32 = 16;
106const FLEX_DIRECTION_SHIFT: u32 = 17;
107const FLEX_WRAP_SHIFT: u32 = 19;
108const JUSTIFY_CONTENT_SHIFT: u32 = 21;
109const ALIGN_ITEMS_SHIFT: u32 = 24;
110const ALIGN_CONTENT_SHIFT: u32 = 27;
111const WRITING_MODE_SHIFT: u32 = 30;
112const CLEAR_SHIFT: u32 = 32;
113const FONT_WEIGHT_SHIFT: u32 = 34;
114const FONT_STYLE_SHIFT: u32 = 38;
115const TEXT_ALIGN_SHIFT: u32 = 40;
116const VISIBILITY_SHIFT: u32 = 43;
117const WHITE_SPACE_SHIFT: u32 = 45;
118const DIRECTION_SHIFT: u32 = 48;
119const VERTICAL_ALIGN_SHIFT: u32 = 49;
120const BORDER_COLLAPSE_SHIFT: u32 = 52;
121
122// Bit masks
123const DISPLAY_MASK: u64 = 0x1F;     // 5 bits
124const POSITION_MASK: u64 = 0x07;    // 3 bits
125const FLOAT_MASK: u64 = 0x03;       // 2 bits
126const OVERFLOW_MASK: u64 = 0x07;    // 3 bits
127const BOX_SIZING_MASK: u64 = 0x01;  // 1 bit
128const FLEX_DIR_MASK: u64 = 0x03;    // 2 bits
129const FLEX_WRAP_MASK: u64 = 0x03;   // 2 bits
130const JUSTIFY_MASK: u64 = 0x07;     // 3 bits
131const ALIGN_MASK: u64 = 0x07;       // 3 bits
132const WRITING_MODE_MASK: u64 = 0x03;// 2 bits
133const CLEAR_MASK: u64 = 0x03;       // 2 bits
134const FONT_WEIGHT_MASK: u64 = 0x0F; // 4 bits
135const FONT_STYLE_MASK: u64 = 0x03;  // 2 bits
136const TEXT_ALIGN_MASK: u64 = 0x07;  // 3 bits
137const VISIBILITY_MASK: u64 = 0x03;  // 2 bits
138const WHITE_SPACE_MASK: u64 = 0x07; // 3 bits
139const DIRECTION_MASK: u64 = 0x01;   // 1 bit
140const VERTICAL_ALIGN_MASK: u64 = 0x07; // 3 bits
141const BORDER_COLLAPSE_MASK: u64 = 0x01; // 1 bit
142
143/// Special value stored in the spare bits [63:51] to indicate this node has
144/// NO tier-1 data (i.e., all defaults). 0 is a valid all-defaults encoding,
145/// so we use bit 63 as a "tier1 populated" flag. If bit 63 is 0 and all other
146/// bits are 0, it means "all defaults" (Display::Block, Position::Static, etc.).
147/// We set bit 63 = 1 to mark that the node HAS been populated.
148const TIER1_POPULATED_BIT: u64 = 1 << 63;
149
150// =============================================================================
151// Safe from_u8 conversion functions (no transmute!)
152// =============================================================================
153
154/// Convert raw bits back to LayoutDisplay. Returns default on invalid input.
155#[inline(always)]
156pub fn layout_display_from_u8(v: u8) -> LayoutDisplay {
157    match v {
158        0 => LayoutDisplay::None,
159        1 => LayoutDisplay::Block,
160        2 => LayoutDisplay::Inline,
161        3 => LayoutDisplay::InlineBlock,
162        4 => LayoutDisplay::Flex,
163        5 => LayoutDisplay::InlineFlex,
164        6 => LayoutDisplay::Table,
165        7 => LayoutDisplay::InlineTable,
166        8 => LayoutDisplay::TableRowGroup,
167        9 => LayoutDisplay::TableHeaderGroup,
168        10 => LayoutDisplay::TableFooterGroup,
169        11 => LayoutDisplay::TableRow,
170        12 => LayoutDisplay::TableColumnGroup,
171        13 => LayoutDisplay::TableColumn,
172        14 => LayoutDisplay::TableCell,
173        15 => LayoutDisplay::TableCaption,
174        16 => LayoutDisplay::FlowRoot,
175        17 => LayoutDisplay::ListItem,
176        18 => LayoutDisplay::RunIn,
177        19 => LayoutDisplay::Marker,
178        20 => LayoutDisplay::Grid,
179        21 => LayoutDisplay::InlineGrid,
180        _ => LayoutDisplay::Block, // safe fallback
181    }
182}
183
184/// Convert LayoutDisplay to its discriminant value (matching #[repr(C)] order).
185#[inline(always)]
186pub fn layout_display_to_u8(v: LayoutDisplay) -> u8 {
187    match v {
188        LayoutDisplay::None => 0,
189        LayoutDisplay::Block => 1,
190        LayoutDisplay::Inline => 2,
191        LayoutDisplay::InlineBlock => 3,
192        LayoutDisplay::Flex => 4,
193        LayoutDisplay::InlineFlex => 5,
194        LayoutDisplay::Table => 6,
195        LayoutDisplay::InlineTable => 7,
196        LayoutDisplay::TableRowGroup => 8,
197        LayoutDisplay::TableHeaderGroup => 9,
198        LayoutDisplay::TableFooterGroup => 10,
199        LayoutDisplay::TableRow => 11,
200        LayoutDisplay::TableColumnGroup => 12,
201        LayoutDisplay::TableColumn => 13,
202        LayoutDisplay::TableCell => 14,
203        LayoutDisplay::TableCaption => 15,
204        LayoutDisplay::FlowRoot => 16,
205        LayoutDisplay::ListItem => 17,
206        LayoutDisplay::RunIn => 18,
207        LayoutDisplay::Marker => 19,
208        LayoutDisplay::Grid => 20,
209        LayoutDisplay::InlineGrid => 21,
210    }
211}
212
213#[inline(always)]
214pub fn layout_position_from_u8(v: u8) -> LayoutPosition {
215    match v {
216        0 => LayoutPosition::Static,
217        1 => LayoutPosition::Relative,
218        2 => LayoutPosition::Absolute,
219        3 => LayoutPosition::Fixed,
220        4 => LayoutPosition::Sticky,
221        _ => LayoutPosition::Static,
222    }
223}
224
225#[inline(always)]
226pub fn layout_position_to_u8(v: LayoutPosition) -> u8 {
227    match v {
228        LayoutPosition::Static => 0,
229        LayoutPosition::Relative => 1,
230        LayoutPosition::Absolute => 2,
231        LayoutPosition::Fixed => 3,
232        LayoutPosition::Sticky => 4,
233    }
234}
235
236#[inline(always)]
237pub fn layout_float_from_u8(v: u8) -> LayoutFloat {
238    match v {
239        0 => LayoutFloat::Left,
240        1 => LayoutFloat::Right,
241        2 => LayoutFloat::None,
242        _ => LayoutFloat::None,
243    }
244}
245
246#[inline(always)]
247pub fn layout_float_to_u8(v: LayoutFloat) -> u8 {
248    match v {
249        LayoutFloat::Left => 0,
250        LayoutFloat::Right => 1,
251        LayoutFloat::None => 2,
252    }
253}
254
255#[inline(always)]
256pub fn layout_overflow_from_u8(v: u8) -> LayoutOverflow {
257    match v {
258        0 => LayoutOverflow::Scroll,
259        1 => LayoutOverflow::Auto,
260        2 => LayoutOverflow::Hidden,
261        3 => LayoutOverflow::Visible,
262        4 => LayoutOverflow::Clip,
263        _ => LayoutOverflow::Visible,
264    }
265}
266
267#[inline(always)]
268pub fn layout_overflow_to_u8(v: LayoutOverflow) -> u8 {
269    match v {
270        LayoutOverflow::Scroll => 0,
271        LayoutOverflow::Auto => 1,
272        LayoutOverflow::Hidden => 2,
273        LayoutOverflow::Visible => 3,
274        LayoutOverflow::Clip => 4,
275    }
276}
277
278#[inline(always)]
279pub fn layout_box_sizing_from_u8(v: u8) -> LayoutBoxSizing {
280    match v {
281        0 => LayoutBoxSizing::ContentBox,
282        1 => LayoutBoxSizing::BorderBox,
283        _ => LayoutBoxSizing::ContentBox,
284    }
285}
286
287#[inline(always)]
288pub fn layout_box_sizing_to_u8(v: LayoutBoxSizing) -> u8 {
289    match v {
290        LayoutBoxSizing::ContentBox => 0,
291        LayoutBoxSizing::BorderBox => 1,
292    }
293}
294
295#[inline(always)]
296pub fn layout_flex_direction_from_u8(v: u8) -> LayoutFlexDirection {
297    match v {
298        0 => LayoutFlexDirection::Row,
299        1 => LayoutFlexDirection::RowReverse,
300        2 => LayoutFlexDirection::Column,
301        3 => LayoutFlexDirection::ColumnReverse,
302        _ => LayoutFlexDirection::Row,
303    }
304}
305
306#[inline(always)]
307pub fn layout_flex_direction_to_u8(v: LayoutFlexDirection) -> u8 {
308    match v {
309        LayoutFlexDirection::Row => 0,
310        LayoutFlexDirection::RowReverse => 1,
311        LayoutFlexDirection::Column => 2,
312        LayoutFlexDirection::ColumnReverse => 3,
313    }
314}
315
316#[inline(always)]
317pub fn layout_flex_wrap_from_u8(v: u8) -> LayoutFlexWrap {
318    match v {
319        0 => LayoutFlexWrap::Wrap,
320        1 => LayoutFlexWrap::NoWrap,
321        2 => LayoutFlexWrap::WrapReverse,
322        _ => LayoutFlexWrap::NoWrap,
323    }
324}
325
326#[inline(always)]
327pub fn layout_flex_wrap_to_u8(v: LayoutFlexWrap) -> u8 {
328    match v {
329        LayoutFlexWrap::Wrap => 0,
330        LayoutFlexWrap::NoWrap => 1,
331        LayoutFlexWrap::WrapReverse => 2,
332    }
333}
334
335#[inline(always)]
336pub fn layout_justify_content_from_u8(v: u8) -> LayoutJustifyContent {
337    match v {
338        0 => LayoutJustifyContent::FlexStart,
339        1 => LayoutJustifyContent::FlexEnd,
340        2 => LayoutJustifyContent::Start,
341        3 => LayoutJustifyContent::End,
342        4 => LayoutJustifyContent::Center,
343        5 => LayoutJustifyContent::SpaceBetween,
344        6 => LayoutJustifyContent::SpaceAround,
345        7 => LayoutJustifyContent::SpaceEvenly,
346        _ => LayoutJustifyContent::FlexStart,
347    }
348}
349
350#[inline(always)]
351pub fn layout_justify_content_to_u8(v: LayoutJustifyContent) -> u8 {
352    match v {
353        LayoutJustifyContent::FlexStart => 0,
354        LayoutJustifyContent::FlexEnd => 1,
355        LayoutJustifyContent::Start => 2,
356        LayoutJustifyContent::End => 3,
357        LayoutJustifyContent::Center => 4,
358        LayoutJustifyContent::SpaceBetween => 5,
359        LayoutJustifyContent::SpaceAround => 6,
360        LayoutJustifyContent::SpaceEvenly => 7,
361    }
362}
363
364#[inline(always)]
365pub fn layout_align_items_from_u8(v: u8) -> LayoutAlignItems {
366    match v {
367        0 => LayoutAlignItems::Stretch,
368        1 => LayoutAlignItems::Center,
369        2 => LayoutAlignItems::Start,
370        3 => LayoutAlignItems::End,
371        4 => LayoutAlignItems::Baseline,
372        _ => LayoutAlignItems::Stretch,
373    }
374}
375
376#[inline(always)]
377pub fn layout_align_items_to_u8(v: LayoutAlignItems) -> u8 {
378    match v {
379        LayoutAlignItems::Stretch => 0,
380        LayoutAlignItems::Center => 1,
381        LayoutAlignItems::Start => 2,
382        LayoutAlignItems::End => 3,
383        LayoutAlignItems::Baseline => 4,
384    }
385}
386
387#[inline(always)]
388pub fn layout_align_content_from_u8(v: u8) -> LayoutAlignContent {
389    match v {
390        0 => LayoutAlignContent::Stretch,
391        1 => LayoutAlignContent::Center,
392        2 => LayoutAlignContent::Start,
393        3 => LayoutAlignContent::End,
394        4 => LayoutAlignContent::SpaceBetween,
395        5 => LayoutAlignContent::SpaceAround,
396        _ => LayoutAlignContent::Stretch,
397    }
398}
399
400#[inline(always)]
401pub fn layout_align_content_to_u8(v: LayoutAlignContent) -> u8 {
402    match v {
403        LayoutAlignContent::Stretch => 0,
404        LayoutAlignContent::Center => 1,
405        LayoutAlignContent::Start => 2,
406        LayoutAlignContent::End => 3,
407        LayoutAlignContent::SpaceBetween => 4,
408        LayoutAlignContent::SpaceAround => 5,
409    }
410}
411
412#[inline(always)]
413pub fn layout_writing_mode_from_u8(v: u8) -> LayoutWritingMode {
414    match v {
415        0 => LayoutWritingMode::HorizontalTb,
416        1 => LayoutWritingMode::VerticalRl,
417        2 => LayoutWritingMode::VerticalLr,
418        _ => LayoutWritingMode::HorizontalTb,
419    }
420}
421
422#[inline(always)]
423pub fn layout_writing_mode_to_u8(v: LayoutWritingMode) -> u8 {
424    match v {
425        LayoutWritingMode::HorizontalTb => 0,
426        LayoutWritingMode::VerticalRl => 1,
427        LayoutWritingMode::VerticalLr => 2,
428    }
429}
430
431#[inline(always)]
432pub fn layout_clear_from_u8(v: u8) -> LayoutClear {
433    match v {
434        0 => LayoutClear::None,
435        1 => LayoutClear::Left,
436        2 => LayoutClear::Right,
437        3 => LayoutClear::Both,
438        _ => LayoutClear::None,
439    }
440}
441
442#[inline(always)]
443pub fn layout_clear_to_u8(v: LayoutClear) -> u8 {
444    match v {
445        LayoutClear::None => 0,
446        LayoutClear::Left => 1,
447        LayoutClear::Right => 2,
448        LayoutClear::Both => 3,
449    }
450}
451
452#[inline(always)]
453pub fn style_font_weight_from_u8(v: u8) -> StyleFontWeight {
454    match v {
455        0 => StyleFontWeight::Lighter,
456        1 => StyleFontWeight::W100,
457        2 => StyleFontWeight::W200,
458        3 => StyleFontWeight::W300,
459        4 => StyleFontWeight::Normal,
460        5 => StyleFontWeight::W500,
461        6 => StyleFontWeight::W600,
462        7 => StyleFontWeight::Bold,
463        8 => StyleFontWeight::W800,
464        9 => StyleFontWeight::W900,
465        10 => StyleFontWeight::Bolder,
466        _ => StyleFontWeight::Normal,
467    }
468}
469
470#[inline(always)]
471pub fn style_font_weight_to_u8(v: StyleFontWeight) -> u8 {
472    match v {
473        StyleFontWeight::Lighter => 0,
474        StyleFontWeight::W100 => 1,
475        StyleFontWeight::W200 => 2,
476        StyleFontWeight::W300 => 3,
477        StyleFontWeight::Normal => 4,
478        StyleFontWeight::W500 => 5,
479        StyleFontWeight::W600 => 6,
480        StyleFontWeight::Bold => 7,
481        StyleFontWeight::W800 => 8,
482        StyleFontWeight::W900 => 9,
483        StyleFontWeight::Bolder => 10,
484    }
485}
486
487#[inline(always)]
488pub fn style_font_style_from_u8(v: u8) -> StyleFontStyle {
489    match v {
490        0 => StyleFontStyle::Normal,
491        1 => StyleFontStyle::Italic,
492        2 => StyleFontStyle::Oblique,
493        _ => StyleFontStyle::Normal,
494    }
495}
496
497#[inline(always)]
498pub fn style_font_style_to_u8(v: StyleFontStyle) -> u8 {
499    match v {
500        StyleFontStyle::Normal => 0,
501        StyleFontStyle::Italic => 1,
502        StyleFontStyle::Oblique => 2,
503    }
504}
505
506#[inline(always)]
507pub fn style_text_align_from_u8(v: u8) -> StyleTextAlign {
508    match v {
509        0 => StyleTextAlign::Left,
510        1 => StyleTextAlign::Center,
511        2 => StyleTextAlign::Right,
512        3 => StyleTextAlign::Justify,
513        4 => StyleTextAlign::Start,
514        5 => StyleTextAlign::End,
515        _ => StyleTextAlign::Left,
516    }
517}
518
519#[inline(always)]
520pub fn style_text_align_to_u8(v: StyleTextAlign) -> u8 {
521    match v {
522        StyleTextAlign::Left => 0,
523        StyleTextAlign::Center => 1,
524        StyleTextAlign::Right => 2,
525        StyleTextAlign::Justify => 3,
526        StyleTextAlign::Start => 4,
527        StyleTextAlign::End => 5,
528    }
529}
530
531#[inline(always)]
532pub fn style_visibility_from_u8(v: u8) -> StyleVisibility {
533    match v {
534        0 => StyleVisibility::Visible,
535        1 => StyleVisibility::Hidden,
536        2 => StyleVisibility::Collapse,
537        _ => StyleVisibility::Visible,
538    }
539}
540
541#[inline(always)]
542pub fn style_visibility_to_u8(v: StyleVisibility) -> u8 {
543    match v {
544        StyleVisibility::Visible => 0,
545        StyleVisibility::Hidden => 1,
546        StyleVisibility::Collapse => 2,
547    }
548}
549
550#[inline(always)]
551pub fn style_white_space_from_u8(v: u8) -> StyleWhiteSpace {
552    match v {
553        0 => StyleWhiteSpace::Normal,
554        1 => StyleWhiteSpace::Pre,
555        2 => StyleWhiteSpace::Nowrap,
556        3 => StyleWhiteSpace::PreWrap,
557        4 => StyleWhiteSpace::PreLine,
558        5 => StyleWhiteSpace::BreakSpaces,
559        _ => StyleWhiteSpace::Normal,
560    }
561}
562
563#[inline(always)]
564pub fn style_white_space_to_u8(v: StyleWhiteSpace) -> u8 {
565    match v {
566        StyleWhiteSpace::Normal => 0,
567        StyleWhiteSpace::Pre => 1,
568        StyleWhiteSpace::Nowrap => 2,
569        StyleWhiteSpace::PreWrap => 3,
570        StyleWhiteSpace::PreLine => 4,
571        StyleWhiteSpace::BreakSpaces => 5,
572    }
573}
574
575#[inline(always)]
576pub fn style_direction_from_u8(v: u8) -> StyleDirection {
577    match v {
578        0 => StyleDirection::Ltr,
579        1 => StyleDirection::Rtl,
580        _ => StyleDirection::Ltr,
581    }
582}
583
584#[inline(always)]
585pub fn style_direction_to_u8(v: StyleDirection) -> u8 {
586    match v {
587        StyleDirection::Ltr => 0,
588        StyleDirection::Rtl => 1,
589    }
590}
591
592#[inline(always)]
593pub fn style_vertical_align_from_u8(v: u8) -> StyleVerticalAlign {
594    match v {
595        0 => StyleVerticalAlign::Baseline,
596        1 => StyleVerticalAlign::Top,
597        2 => StyleVerticalAlign::Middle,
598        3 => StyleVerticalAlign::Bottom,
599        4 => StyleVerticalAlign::Sub,
600        5 => StyleVerticalAlign::Superscript,
601        6 => StyleVerticalAlign::TextTop,
602        7 => StyleVerticalAlign::TextBottom,
603        _ => StyleVerticalAlign::Baseline,
604    }
605}
606
607#[inline(always)]
608pub fn style_vertical_align_to_u8(v: StyleVerticalAlign) -> u8 {
609    match v {
610        StyleVerticalAlign::Baseline => 0,
611        StyleVerticalAlign::Top => 1,
612        StyleVerticalAlign::Middle => 2,
613        StyleVerticalAlign::Bottom => 3,
614        StyleVerticalAlign::Sub => 4,
615        StyleVerticalAlign::Superscript => 5,
616        StyleVerticalAlign::TextTop => 6,
617        StyleVerticalAlign::TextBottom => 7,
618    }
619}
620
621#[inline(always)]
622pub fn border_collapse_from_u8(v: u8) -> StyleBorderCollapse {
623    match v {
624        0 => StyleBorderCollapse::Separate,
625        1 => StyleBorderCollapse::Collapse,
626        _ => StyleBorderCollapse::Separate,
627    }
628}
629
630#[inline(always)]
631pub fn border_collapse_to_u8(v: StyleBorderCollapse) -> u8 {
632    match v {
633        StyleBorderCollapse::Separate => 0,
634        StyleBorderCollapse::Collapse => 1,
635    }
636}
637
638#[inline(always)]
639pub fn border_style_from_u8(v: u8) -> BorderStyle {
640    match v {
641        0 => BorderStyle::None,
642        1 => BorderStyle::Solid,
643        2 => BorderStyle::Double,
644        3 => BorderStyle::Dotted,
645        4 => BorderStyle::Dashed,
646        5 => BorderStyle::Hidden,
647        6 => BorderStyle::Groove,
648        7 => BorderStyle::Ridge,
649        8 => BorderStyle::Inset,
650        9 => BorderStyle::Outset,
651        _ => BorderStyle::None,
652    }
653}
654
655#[inline(always)]
656pub fn border_style_to_u8(v: BorderStyle) -> u8 {
657    match v {
658        BorderStyle::None => 0,
659        BorderStyle::Solid => 1,
660        BorderStyle::Double => 2,
661        BorderStyle::Dotted => 3,
662        BorderStyle::Dashed => 4,
663        BorderStyle::Hidden => 5,
664        BorderStyle::Groove => 6,
665        BorderStyle::Ridge => 7,
666        BorderStyle::Inset => 8,
667        BorderStyle::Outset => 9,
668    }
669}
670
671/// Encode 4 border styles into a u16: [3:0]=top, [7:4]=right, [11:8]=bottom, [15:12]=left
672#[inline]
673pub fn encode_border_styles_packed(top: BorderStyle, right: BorderStyle, bottom: BorderStyle, left: BorderStyle) -> u16 {
674    (border_style_to_u8(top) as u16)
675        | ((border_style_to_u8(right) as u16) << 4)
676        | ((border_style_to_u8(bottom) as u16) << 8)
677        | ((border_style_to_u8(left) as u16) << 12)
678}
679
680/// Decode border-top-style from packed u16
681#[inline(always)]
682pub fn decode_border_top_style(packed: u16) -> BorderStyle {
683    border_style_from_u8((packed & 0x0F) as u8)
684}
685
686/// Decode border-right-style from packed u16
687#[inline(always)]
688pub fn decode_border_right_style(packed: u16) -> BorderStyle {
689    border_style_from_u8(((packed >> 4) & 0x0F) as u8)
690}
691
692/// Decode border-bottom-style from packed u16
693#[inline(always)]
694pub fn decode_border_bottom_style(packed: u16) -> BorderStyle {
695    border_style_from_u8(((packed >> 8) & 0x0F) as u8)
696}
697
698/// Decode border-left-style from packed u16
699#[inline(always)]
700pub fn decode_border_left_style(packed: u16) -> BorderStyle {
701    border_style_from_u8(((packed >> 12) & 0x0F) as u8)
702}
703
704/// Encode a ColorU as u32 (0xRRGGBBAA). Returns 0 for sentinel/unset.
705#[inline(always)]
706pub fn encode_color_u32(c: &ColorU) -> u32 {
707    ((c.r as u32) << 24) | ((c.g as u32) << 16) | ((c.b as u32) << 8) | (c.a as u32)
708}
709
710/// Decode a u32 back to ColorU. Returns None if sentinel (0x00000000).
711#[inline(always)]
712pub fn decode_color_u32(v: u32) -> Option<ColorU> {
713    if v == 0 { return None; }
714    Some(ColorU {
715        r: ((v >> 24) & 0xFF) as u8,
716        g: ((v >> 16) & 0xFF) as u8,
717        b: ((v >> 8) & 0xFF) as u8,
718        a: (v & 0xFF) as u8,
719    })
720}
721
722// =============================================================================
723// Tier 1: Encode / Decode
724// =============================================================================
725
726/// Pack all 21 enum properties into a single u64.
727#[inline]
728pub fn encode_tier1(
729    display: LayoutDisplay,
730    position: LayoutPosition,
731    float: LayoutFloat,
732    overflow_x: LayoutOverflow,
733    overflow_y: LayoutOverflow,
734    box_sizing: LayoutBoxSizing,
735    flex_direction: LayoutFlexDirection,
736    flex_wrap: LayoutFlexWrap,
737    justify_content: LayoutJustifyContent,
738    align_items: LayoutAlignItems,
739    align_content: LayoutAlignContent,
740    writing_mode: LayoutWritingMode,
741    clear: LayoutClear,
742    font_weight: StyleFontWeight,
743    font_style: StyleFontStyle,
744    text_align: StyleTextAlign,
745    visibility: StyleVisibility,
746    white_space: StyleWhiteSpace,
747    direction: StyleDirection,
748    vertical_align: StyleVerticalAlign,
749    border_collapse: StyleBorderCollapse,
750) -> u64 {
751    let mut v: u64 = TIER1_POPULATED_BIT;
752    v |= (layout_display_to_u8(display) as u64) << DISPLAY_SHIFT;
753    v |= (layout_position_to_u8(position) as u64) << POSITION_SHIFT;
754    v |= (layout_float_to_u8(float) as u64) << FLOAT_SHIFT;
755    v |= (layout_overflow_to_u8(overflow_x) as u64) << OVERFLOW_X_SHIFT;
756    v |= (layout_overflow_to_u8(overflow_y) as u64) << OVERFLOW_Y_SHIFT;
757    v |= (layout_box_sizing_to_u8(box_sizing) as u64) << BOX_SIZING_SHIFT;
758    v |= (layout_flex_direction_to_u8(flex_direction) as u64) << FLEX_DIRECTION_SHIFT;
759    v |= (layout_flex_wrap_to_u8(flex_wrap) as u64) << FLEX_WRAP_SHIFT;
760    v |= (layout_justify_content_to_u8(justify_content) as u64) << JUSTIFY_CONTENT_SHIFT;
761    v |= (layout_align_items_to_u8(align_items) as u64) << ALIGN_ITEMS_SHIFT;
762    v |= (layout_align_content_to_u8(align_content) as u64) << ALIGN_CONTENT_SHIFT;
763    v |= (layout_writing_mode_to_u8(writing_mode) as u64) << WRITING_MODE_SHIFT;
764    v |= (layout_clear_to_u8(clear) as u64) << CLEAR_SHIFT;
765    v |= (style_font_weight_to_u8(font_weight) as u64) << FONT_WEIGHT_SHIFT;
766    v |= (style_font_style_to_u8(font_style) as u64) << FONT_STYLE_SHIFT;
767    v |= (style_text_align_to_u8(text_align) as u64) << TEXT_ALIGN_SHIFT;
768    v |= (style_visibility_to_u8(visibility) as u64) << VISIBILITY_SHIFT;
769    v |= (style_white_space_to_u8(white_space) as u64) << WHITE_SPACE_SHIFT;
770    v |= (style_direction_to_u8(direction) as u64) << DIRECTION_SHIFT;
771    v |= (style_vertical_align_to_u8(vertical_align) as u64) << VERTICAL_ALIGN_SHIFT;
772    v |= (border_collapse_to_u8(border_collapse) as u64) << BORDER_COLLAPSE_SHIFT;
773    v
774}
775
776/// Decode individual enum properties from a Tier 1 u64.
777/// Each function is `#[inline(always)]` for zero-cost extraction.
778
779#[inline(always)]
780pub fn decode_display(t1: u64) -> LayoutDisplay {
781    layout_display_from_u8(((t1 >> DISPLAY_SHIFT) & DISPLAY_MASK) as u8)
782}
783
784#[inline(always)]
785pub fn decode_position(t1: u64) -> LayoutPosition {
786    layout_position_from_u8(((t1 >> POSITION_SHIFT) & POSITION_MASK) as u8)
787}
788
789#[inline(always)]
790pub fn decode_float(t1: u64) -> LayoutFloat {
791    layout_float_from_u8(((t1 >> FLOAT_SHIFT) & FLOAT_MASK) as u8)
792}
793
794#[inline(always)]
795pub fn decode_overflow_x(t1: u64) -> LayoutOverflow {
796    layout_overflow_from_u8(((t1 >> OVERFLOW_X_SHIFT) & OVERFLOW_MASK) as u8)
797}
798
799#[inline(always)]
800pub fn decode_overflow_y(t1: u64) -> LayoutOverflow {
801    layout_overflow_from_u8(((t1 >> OVERFLOW_Y_SHIFT) & OVERFLOW_MASK) as u8)
802}
803
804#[inline(always)]
805pub fn decode_box_sizing(t1: u64) -> LayoutBoxSizing {
806    layout_box_sizing_from_u8(((t1 >> BOX_SIZING_SHIFT) & BOX_SIZING_MASK) as u8)
807}
808
809#[inline(always)]
810pub fn decode_flex_direction(t1: u64) -> LayoutFlexDirection {
811    layout_flex_direction_from_u8(((t1 >> FLEX_DIRECTION_SHIFT) & FLEX_DIR_MASK) as u8)
812}
813
814#[inline(always)]
815pub fn decode_flex_wrap(t1: u64) -> LayoutFlexWrap {
816    layout_flex_wrap_from_u8(((t1 >> FLEX_WRAP_SHIFT) & FLEX_WRAP_MASK) as u8)
817}
818
819#[inline(always)]
820pub fn decode_justify_content(t1: u64) -> LayoutJustifyContent {
821    layout_justify_content_from_u8(((t1 >> JUSTIFY_CONTENT_SHIFT) & JUSTIFY_MASK) as u8)
822}
823
824#[inline(always)]
825pub fn decode_align_items(t1: u64) -> LayoutAlignItems {
826    layout_align_items_from_u8(((t1 >> ALIGN_ITEMS_SHIFT) & ALIGN_MASK) as u8)
827}
828
829#[inline(always)]
830pub fn decode_align_content(t1: u64) -> LayoutAlignContent {
831    layout_align_content_from_u8(((t1 >> ALIGN_CONTENT_SHIFT) & ALIGN_MASK) as u8)
832}
833
834#[inline(always)]
835pub fn decode_writing_mode(t1: u64) -> LayoutWritingMode {
836    layout_writing_mode_from_u8(((t1 >> WRITING_MODE_SHIFT) & WRITING_MODE_MASK) as u8)
837}
838
839#[inline(always)]
840pub fn decode_clear(t1: u64) -> LayoutClear {
841    layout_clear_from_u8(((t1 >> CLEAR_SHIFT) & CLEAR_MASK) as u8)
842}
843
844#[inline(always)]
845pub fn decode_font_weight(t1: u64) -> StyleFontWeight {
846    style_font_weight_from_u8(((t1 >> FONT_WEIGHT_SHIFT) & FONT_WEIGHT_MASK) as u8)
847}
848
849#[inline(always)]
850pub fn decode_font_style(t1: u64) -> StyleFontStyle {
851    style_font_style_from_u8(((t1 >> FONT_STYLE_SHIFT) & FONT_STYLE_MASK) as u8)
852}
853
854#[inline(always)]
855pub fn decode_text_align(t1: u64) -> StyleTextAlign {
856    style_text_align_from_u8(((t1 >> TEXT_ALIGN_SHIFT) & TEXT_ALIGN_MASK) as u8)
857}
858
859#[inline(always)]
860pub fn decode_visibility(t1: u64) -> StyleVisibility {
861    style_visibility_from_u8(((t1 >> VISIBILITY_SHIFT) & VISIBILITY_MASK) as u8)
862}
863
864#[inline(always)]
865pub fn decode_white_space(t1: u64) -> StyleWhiteSpace {
866    style_white_space_from_u8(((t1 >> WHITE_SPACE_SHIFT) & WHITE_SPACE_MASK) as u8)
867}
868
869#[inline(always)]
870pub fn decode_direction(t1: u64) -> StyleDirection {
871    style_direction_from_u8(((t1 >> DIRECTION_SHIFT) & DIRECTION_MASK) as u8)
872}
873
874#[inline(always)]
875pub fn decode_vertical_align(t1: u64) -> StyleVerticalAlign {
876    style_vertical_align_from_u8(((t1 >> VERTICAL_ALIGN_SHIFT) & VERTICAL_ALIGN_MASK) as u8)
877}
878
879#[inline(always)]
880pub fn decode_border_collapse(t1: u64) -> StyleBorderCollapse {
881    border_collapse_from_u8(((t1 >> BORDER_COLLAPSE_SHIFT) & BORDER_COLLAPSE_MASK) as u8)
882}
883
884/// Returns true if the tier1 u64 was actually populated by `encode_tier1`.
885#[inline(always)]
886pub fn tier1_is_populated(t1: u64) -> bool {
887    (t1 & TIER1_POPULATED_BIT) != 0
888}
889
890// =============================================================================
891// Tier 2: CompactNodeProps — numeric dimensions (64 bytes/node)
892// =============================================================================
893
894/// u32 encoding for dimension properties (width, height, min-*, max-*, flex-basis, font-size).
895///
896/// Layout: `[3:0] SizeMetric (4 bits) | [31:4] signed fixed-point ×1000 (28 bits)`
897///
898/// This matches FloatValue's internal representation (isize × 1000).
899/// Range: ±134,217.727 at 0.001 precision (28-bit signed = ±2^27 = ±134,217,728 / 1000).
900///
901/// Sentinel values use the top of the u32 range (0xFFFFFFF9..0xFFFFFFFF).
902
903/// Encode a PixelValue into u32 with SizeMetric. Returns U32_SENTINEL if out of range.
904#[inline]
905pub fn encode_pixel_value_u32(pv: &PixelValue) -> u32 {
906    let metric = size_metric_to_u8(pv.metric) as u32;
907    let raw = pv.number.number; // already × 1000 (FloatValue internal repr)
908    // 28-bit signed range: -134_217_728 ..= +134_217_727
909    if raw < -134_217_728 || raw > 134_217_727 {
910        return U32_SENTINEL; // overflow → tier 3
911    }
912    // Pack: low 4 bits = metric, upper 28 bits = value (as unsigned offset)
913    let value_bits = ((raw as i32) as u32) << 4;
914    value_bits | metric
915}
916
917/// Decode a u32 back to PixelValue. Returns None for sentinel values.
918#[inline]
919pub fn decode_pixel_value_u32(encoded: u32) -> Option<PixelValue> {
920    if encoded >= U32_SENTINEL_THRESHOLD {
921        return None; // sentinel
922    }
923    let metric = size_metric_from_u8((encoded & 0xF) as u8);
924    // Cast to i32 FIRST, then arithmetic right-shift to preserve sign bit
925    let value_bits = (encoded as i32) >> 4;
926    let raw = value_bits as isize; // × 1000
927    Some(PixelValue {
928        metric,
929        number: FloatValue { number: raw },
930    })
931}
932
933/// Encode an i16 resolved px value (×10). Returns I16_SENTINEL if out of range.
934/// Range: -3276.8 ..= +3276.3 px at 0.1px precision.
935#[inline]
936pub fn encode_resolved_px_i16(px: f32) -> i16 {
937    let scaled = (px * 10.0).round() as i32;
938    if scaled < -32768 || scaled > I16_SENTINEL_THRESHOLD as i32 - 1 {
939        return I16_SENTINEL; // overflow or too large → tier 3
940    }
941    scaled as i16
942}
943
944/// Decode an i16 back to resolved px. Returns None for sentinel values.
945#[inline(always)]
946pub fn decode_resolved_px_i16(v: i16) -> Option<f32> {
947    if v >= I16_SENTINEL_THRESHOLD {
948        return None;
949    }
950    Some(v as f32 / 10.0)
951}
952
953/// Encode a u16 flex value (×100). Returns U16_SENTINEL if out of range.
954/// Range: 0.00 ..= 655.27 at 0.01 precision.
955#[inline]
956pub fn encode_flex_u16(value: f32) -> u16 {
957    let scaled = (value * 100.0).round() as i32;
958    if scaled < 0 || scaled >= U16_SENTINEL_THRESHOLD as i32 {
959        return U16_SENTINEL;
960    }
961    scaled as u16
962}
963
964/// Decode a u16 flex value back to f32. Returns None for sentinel values.
965#[inline(always)]
966pub fn decode_flex_u16(v: u16) -> Option<f32> {
967    if v >= U16_SENTINEL_THRESHOLD {
968        return None;
969    }
970    Some(v as f32 / 100.0)
971}
972
973/// SizeMetric → u8 (4 bits, 12 variants)
974#[inline(always)]
975pub fn size_metric_to_u8(m: SizeMetric) -> u8 {
976    match m {
977        SizeMetric::Px => 0,
978        SizeMetric::Pt => 1,
979        SizeMetric::Em => 2,
980        SizeMetric::Rem => 3,
981        SizeMetric::In => 4,
982        SizeMetric::Cm => 5,
983        SizeMetric::Mm => 6,
984        SizeMetric::Percent => 7,
985        SizeMetric::Vw => 8,
986        SizeMetric::Vh => 9,
987        SizeMetric::Vmin => 10,
988        SizeMetric::Vmax => 11,
989    }
990}
991
992/// u8 → SizeMetric
993#[inline(always)]
994pub fn size_metric_from_u8(v: u8) -> SizeMetric {
995    match v {
996        0 => SizeMetric::Px,
997        1 => SizeMetric::Pt,
998        2 => SizeMetric::Em,
999        3 => SizeMetric::Rem,
1000        4 => SizeMetric::In,
1001        5 => SizeMetric::Cm,
1002        6 => SizeMetric::Mm,
1003        7 => SizeMetric::Percent,
1004        8 => SizeMetric::Vw,
1005        9 => SizeMetric::Vh,
1006        10 => SizeMetric::Vmin,
1007        11 => SizeMetric::Vmax,
1008        _ => SizeMetric::Px,
1009    }
1010}
1011
1012/// Compact numeric properties for a single node (96 bytes).
1013/// All dimensions use MSB-sentinel encoding.
1014#[derive(Debug, Copy, Clone, PartialEq)]
1015#[repr(C)]
1016pub struct CompactNodeProps {
1017    // --- Dimensions needing unit (u32 MSB-sentinel) ---
1018    pub width: u32,
1019    pub height: u32,
1020    pub min_width: u32,
1021    pub max_width: u32,
1022    pub min_height: u32,
1023    pub max_height: u32,
1024    pub flex_basis: u32,
1025    pub font_size: u32,
1026
1027    // --- Border colors (u32 RGBA as 0xRRGGBBAA, 0 = unset sentinel) ---
1028    pub border_top_color: u32,
1029    pub border_right_color: u32,
1030    pub border_bottom_color: u32,
1031    pub border_left_color: u32,
1032
1033    // --- Resolved px values (i16 MSB-sentinel, ×10) ---
1034    pub padding_top: i16,
1035    pub padding_right: i16,
1036    pub padding_bottom: i16,
1037    pub padding_left: i16,
1038    pub margin_top: i16,
1039    pub margin_right: i16,
1040    pub margin_bottom: i16,
1041    pub margin_left: i16,
1042    pub border_top_width: i16,
1043    pub border_right_width: i16,
1044    pub border_bottom_width: i16,
1045    pub border_left_width: i16,
1046    pub top: i16,
1047    pub right: i16,
1048    pub bottom: i16,
1049    pub left: i16,
1050    pub border_spacing_h: i16,
1051    pub border_spacing_v: i16,
1052    pub tab_size: i16,
1053
1054    // --- Flex (u16 MSB-sentinel, ×100) ---
1055    pub flex_grow: u16,
1056    pub flex_shrink: u16,
1057
1058    // --- Other ---
1059    pub z_index: i16,   // range ±32764, sentinel = 0x7FFF
1060    /// Border styles packed: [3:0]=top, [7:4]=right, [11:8]=bottom, [15:12]=left
1061    pub border_styles_packed: u16,
1062    pub _pad: [u8; 2],
1063}
1064
1065impl Default for CompactNodeProps {
1066    fn default() -> Self {
1067        Self {
1068            // All dimensions default to Auto
1069            width: U32_AUTO,
1070            height: U32_AUTO,
1071            min_width: U32_AUTO,
1072            max_width: U32_NONE,
1073            min_height: U32_AUTO,
1074            max_height: U32_NONE,
1075            flex_basis: U32_AUTO,
1076            font_size: U32_INITIAL,
1077            // Border colors default to 0 (sentinel/unset)
1078            border_top_color: 0,
1079            border_right_color: 0,
1080            border_bottom_color: 0,
1081            border_left_color: 0,
1082            // All resolved px default to 0
1083            padding_top: 0,
1084            padding_right: 0,
1085            padding_bottom: 0,
1086            padding_left: 0,
1087            margin_top: 0,
1088            margin_right: 0,
1089            margin_bottom: 0,
1090            margin_left: 0,
1091            border_top_width: 0,
1092            border_right_width: 0,
1093            border_bottom_width: 0,
1094            border_left_width: 0,
1095            top: I16_AUTO,
1096            right: I16_AUTO,
1097            bottom: I16_AUTO,
1098            left: I16_AUTO,
1099            border_spacing_h: 0,
1100            border_spacing_v: 0,
1101            tab_size: I16_SENTINEL, // default is 8em, needs resolution → sentinel
1102            // Flex defaults
1103            flex_grow: 0,
1104            flex_shrink: encode_flex_u16(1.0), // CSS default: flex-shrink: 1
1105            // Other
1106            z_index: I16_AUTO,
1107            border_styles_packed: 0, // all BorderStyle::None
1108            _pad: [0; 2],
1109        }
1110    }
1111}
1112
1113// =============================================================================
1114// Tier 2b: CompactTextProps — IFC/text properties (24 bytes/node)
1115// =============================================================================
1116
1117/// Compact text/IFC properties for a single node (24 bytes).
1118#[derive(Debug, Copy, Clone, PartialEq)]
1119#[repr(C)]
1120pub struct CompactTextProps {
1121    pub text_color: u32,       // RGBA as 0xRRGGBBAA (0 = transparent/unset)
1122    pub font_family_hash: u64, // FxHash of font-family list (0 = sentinel/unset)
1123    pub line_height: i16,      // px × 10, sentinel = I16_SENTINEL
1124    pub letter_spacing: i16,   // px × 10
1125    pub word_spacing: i16,     // px × 10
1126    pub text_indent: i16,      // px × 10
1127}
1128
1129impl Default for CompactTextProps {
1130    fn default() -> Self {
1131        Self {
1132            text_color: 0,
1133            font_family_hash: 0,
1134            line_height: I16_SENTINEL, // "normal" → sentinel
1135            letter_spacing: 0,
1136            word_spacing: 0,
1137            text_indent: 0,
1138        }
1139    }
1140}
1141
1142// =============================================================================
1143// Tier 3: Overflow map — rare/complex properties
1144// =============================================================================
1145
1146/// Overflow properties that couldn't fit in Tier 1/2 encoding.
1147/// Contains the original `CssProperty` values for properties that:
1148/// - Have calc() expressions
1149/// - Exceed the numeric range of compact encoding
1150/// - Are rare CSS properties (grid, transforms, etc.)
1151pub type CompactOverflowProps = BTreeMap<CssPropertyType, CssProperty>;
1152
1153// =============================================================================
1154// CompactLayoutCache — the top-level container
1155// =============================================================================
1156
1157/// Three-tier compact layout property cache.
1158///
1159/// Allocated once per restyle, indexed by node index (same as NodeId).
1160/// Provides O(1) array-indexed access to all layout properties.
1161#[derive(Debug, Clone, PartialEq)]
1162pub struct CompactLayoutCache {
1163    /// Tier 1: ALL enum properties bitpacked into u64 per node (8 B/node)
1164    pub tier1_enums: Vec<u64>,
1165    /// Tier 2: Numeric dimensions per node (64 B/node)
1166    pub tier2_dims: Vec<CompactNodeProps>,
1167    /// Tier 2b: Text/IFC properties per node (24 B/node)
1168    pub tier2b_text: Vec<CompactTextProps>,
1169    /// Tier 3: Overflow map for rare/complex properties (8 B/node, usually None)
1170    pub tier3_overflow: Vec<Option<Box<CompactOverflowProps>>>,
1171}
1172
1173impl CompactLayoutCache {
1174    /// Create an empty cache (no nodes).
1175    pub fn empty() -> Self {
1176        Self {
1177            tier1_enums: Vec::new(),
1178            tier2_dims: Vec::new(),
1179            tier2b_text: Vec::new(),
1180            tier3_overflow: Vec::new(),
1181        }
1182    }
1183
1184    /// Create a cache pre-allocated for `node_count` nodes, filled with defaults.
1185    pub fn with_capacity(node_count: usize) -> Self {
1186        Self {
1187            tier1_enums: vec![0u64; node_count],
1188            tier2_dims: vec![CompactNodeProps::default(); node_count],
1189            tier2b_text: vec![CompactTextProps::default(); node_count],
1190            tier3_overflow: vec![None; node_count],
1191        }
1192    }
1193
1194    /// Number of nodes in this cache.
1195    #[inline]
1196    pub fn node_count(&self) -> usize {
1197        self.tier1_enums.len()
1198    }
1199
1200    // -- Tier 1 getters (enum properties) --
1201
1202    #[inline(always)]
1203    pub fn get_display(&self, node_idx: usize) -> LayoutDisplay {
1204        decode_display(self.tier1_enums[node_idx])
1205    }
1206
1207    #[inline(always)]
1208    pub fn get_position(&self, node_idx: usize) -> LayoutPosition {
1209        decode_position(self.tier1_enums[node_idx])
1210    }
1211
1212    #[inline(always)]
1213    pub fn get_float(&self, node_idx: usize) -> LayoutFloat {
1214        decode_float(self.tier1_enums[node_idx])
1215    }
1216
1217    #[inline(always)]
1218    pub fn get_overflow_x(&self, node_idx: usize) -> LayoutOverflow {
1219        decode_overflow_x(self.tier1_enums[node_idx])
1220    }
1221
1222    #[inline(always)]
1223    pub fn get_overflow_y(&self, node_idx: usize) -> LayoutOverflow {
1224        decode_overflow_y(self.tier1_enums[node_idx])
1225    }
1226
1227    #[inline(always)]
1228    pub fn get_box_sizing(&self, node_idx: usize) -> LayoutBoxSizing {
1229        decode_box_sizing(self.tier1_enums[node_idx])
1230    }
1231
1232    #[inline(always)]
1233    pub fn get_flex_direction(&self, node_idx: usize) -> LayoutFlexDirection {
1234        decode_flex_direction(self.tier1_enums[node_idx])
1235    }
1236
1237    #[inline(always)]
1238    pub fn get_flex_wrap(&self, node_idx: usize) -> LayoutFlexWrap {
1239        decode_flex_wrap(self.tier1_enums[node_idx])
1240    }
1241
1242    #[inline(always)]
1243    pub fn get_justify_content(&self, node_idx: usize) -> LayoutJustifyContent {
1244        decode_justify_content(self.tier1_enums[node_idx])
1245    }
1246
1247    #[inline(always)]
1248    pub fn get_align_items(&self, node_idx: usize) -> LayoutAlignItems {
1249        decode_align_items(self.tier1_enums[node_idx])
1250    }
1251
1252    #[inline(always)]
1253    pub fn get_align_content(&self, node_idx: usize) -> LayoutAlignContent {
1254        decode_align_content(self.tier1_enums[node_idx])
1255    }
1256
1257    #[inline(always)]
1258    pub fn get_writing_mode(&self, node_idx: usize) -> LayoutWritingMode {
1259        decode_writing_mode(self.tier1_enums[node_idx])
1260    }
1261
1262    #[inline(always)]
1263    pub fn get_clear(&self, node_idx: usize) -> LayoutClear {
1264        decode_clear(self.tier1_enums[node_idx])
1265    }
1266
1267    #[inline(always)]
1268    pub fn get_font_weight(&self, node_idx: usize) -> StyleFontWeight {
1269        decode_font_weight(self.tier1_enums[node_idx])
1270    }
1271
1272    #[inline(always)]
1273    pub fn get_font_style(&self, node_idx: usize) -> StyleFontStyle {
1274        decode_font_style(self.tier1_enums[node_idx])
1275    }
1276
1277    #[inline(always)]
1278    pub fn get_text_align(&self, node_idx: usize) -> StyleTextAlign {
1279        decode_text_align(self.tier1_enums[node_idx])
1280    }
1281
1282    #[inline(always)]
1283    pub fn get_visibility(&self, node_idx: usize) -> StyleVisibility {
1284        decode_visibility(self.tier1_enums[node_idx])
1285    }
1286
1287    #[inline(always)]
1288    pub fn get_white_space(&self, node_idx: usize) -> StyleWhiteSpace {
1289        decode_white_space(self.tier1_enums[node_idx])
1290    }
1291
1292    #[inline(always)]
1293    pub fn get_direction(&self, node_idx: usize) -> StyleDirection {
1294        decode_direction(self.tier1_enums[node_idx])
1295    }
1296
1297    #[inline(always)]
1298    pub fn get_vertical_align(&self, node_idx: usize) -> StyleVerticalAlign {
1299        decode_vertical_align(self.tier1_enums[node_idx])
1300    }
1301
1302    #[inline(always)]
1303    pub fn get_border_collapse(&self, node_idx: usize) -> StyleBorderCollapse {
1304        decode_border_collapse(self.tier1_enums[node_idx])
1305    }
1306
1307    // -- Tier 2 getters (numeric dimensions) --
1308
1309    /// Get width as encoded u32 (use `decode_pixel_value_u32` or check sentinel).
1310    #[inline(always)]
1311    pub fn get_width_raw(&self, node_idx: usize) -> u32 {
1312        self.tier2_dims[node_idx].width
1313    }
1314
1315    #[inline(always)]
1316    pub fn get_height_raw(&self, node_idx: usize) -> u32 {
1317        self.tier2_dims[node_idx].height
1318    }
1319
1320    #[inline(always)]
1321    pub fn get_min_width_raw(&self, node_idx: usize) -> u32 {
1322        self.tier2_dims[node_idx].min_width
1323    }
1324
1325    #[inline(always)]
1326    pub fn get_max_width_raw(&self, node_idx: usize) -> u32 {
1327        self.tier2_dims[node_idx].max_width
1328    }
1329
1330    #[inline(always)]
1331    pub fn get_min_height_raw(&self, node_idx: usize) -> u32 {
1332        self.tier2_dims[node_idx].min_height
1333    }
1334
1335    #[inline(always)]
1336    pub fn get_max_height_raw(&self, node_idx: usize) -> u32 {
1337        self.tier2_dims[node_idx].max_height
1338    }
1339
1340    #[inline(always)]
1341    pub fn get_font_size_raw(&self, node_idx: usize) -> u32 {
1342        self.tier2_dims[node_idx].font_size
1343    }
1344
1345    #[inline(always)]
1346    pub fn get_flex_basis_raw(&self, node_idx: usize) -> u32 {
1347        self.tier2_dims[node_idx].flex_basis
1348    }
1349
1350    /// Get padding-top as resolved px. Returns None if sentinel (needs slow path).
1351    #[inline(always)]
1352    pub fn get_padding_top(&self, node_idx: usize) -> Option<f32> {
1353        decode_resolved_px_i16(self.tier2_dims[node_idx].padding_top)
1354    }
1355
1356    #[inline(always)]
1357    pub fn get_padding_right(&self, node_idx: usize) -> Option<f32> {
1358        decode_resolved_px_i16(self.tier2_dims[node_idx].padding_right)
1359    }
1360
1361    #[inline(always)]
1362    pub fn get_padding_bottom(&self, node_idx: usize) -> Option<f32> {
1363        decode_resolved_px_i16(self.tier2_dims[node_idx].padding_bottom)
1364    }
1365
1366    #[inline(always)]
1367    pub fn get_padding_left(&self, node_idx: usize) -> Option<f32> {
1368        decode_resolved_px_i16(self.tier2_dims[node_idx].padding_left)
1369    }
1370
1371    #[inline(always)]
1372    pub fn get_margin_top(&self, node_idx: usize) -> Option<f32> {
1373        let v = self.tier2_dims[node_idx].margin_top;
1374        if v == I16_AUTO { return None; } // Auto for margin is special
1375        decode_resolved_px_i16(v)
1376    }
1377
1378    #[inline(always)]
1379    pub fn get_margin_right(&self, node_idx: usize) -> Option<f32> {
1380        let v = self.tier2_dims[node_idx].margin_right;
1381        if v == I16_AUTO { return None; }
1382        decode_resolved_px_i16(v)
1383    }
1384
1385    #[inline(always)]
1386    pub fn get_margin_bottom(&self, node_idx: usize) -> Option<f32> {
1387        let v = self.tier2_dims[node_idx].margin_bottom;
1388        if v == I16_AUTO { return None; }
1389        decode_resolved_px_i16(v)
1390    }
1391
1392    #[inline(always)]
1393    pub fn get_margin_left(&self, node_idx: usize) -> Option<f32> {
1394        let v = self.tier2_dims[node_idx].margin_left;
1395        if v == I16_AUTO { return None; }
1396        decode_resolved_px_i16(v)
1397    }
1398
1399    /// Check if margin is Auto (important for centering logic).
1400    #[inline(always)]
1401    pub fn is_margin_top_auto(&self, node_idx: usize) -> bool {
1402        self.tier2_dims[node_idx].margin_top == I16_AUTO
1403    }
1404
1405    #[inline(always)]
1406    pub fn is_margin_right_auto(&self, node_idx: usize) -> bool {
1407        self.tier2_dims[node_idx].margin_right == I16_AUTO
1408    }
1409
1410    #[inline(always)]
1411    pub fn is_margin_bottom_auto(&self, node_idx: usize) -> bool {
1412        self.tier2_dims[node_idx].margin_bottom == I16_AUTO
1413    }
1414
1415    #[inline(always)]
1416    pub fn is_margin_left_auto(&self, node_idx: usize) -> bool {
1417        self.tier2_dims[node_idx].margin_left == I16_AUTO
1418    }
1419
1420    #[inline(always)]
1421    pub fn get_border_top_width(&self, node_idx: usize) -> Option<f32> {
1422        decode_resolved_px_i16(self.tier2_dims[node_idx].border_top_width)
1423    }
1424
1425    #[inline(always)]
1426    pub fn get_border_right_width(&self, node_idx: usize) -> Option<f32> {
1427        decode_resolved_px_i16(self.tier2_dims[node_idx].border_right_width)
1428    }
1429
1430    #[inline(always)]
1431    pub fn get_border_bottom_width(&self, node_idx: usize) -> Option<f32> {
1432        decode_resolved_px_i16(self.tier2_dims[node_idx].border_bottom_width)
1433    }
1434
1435    #[inline(always)]
1436    pub fn get_border_left_width(&self, node_idx: usize) -> Option<f32> {
1437        decode_resolved_px_i16(self.tier2_dims[node_idx].border_left_width)
1438    }
1439
1440    // -- Raw i16 getters for macro fast paths --
1441
1442    #[inline(always)]
1443    pub fn get_padding_top_raw(&self, node_idx: usize) -> i16 {
1444        self.tier2_dims[node_idx].padding_top
1445    }
1446
1447    #[inline(always)]
1448    pub fn get_padding_right_raw(&self, node_idx: usize) -> i16 {
1449        self.tier2_dims[node_idx].padding_right
1450    }
1451
1452    #[inline(always)]
1453    pub fn get_padding_bottom_raw(&self, node_idx: usize) -> i16 {
1454        self.tier2_dims[node_idx].padding_bottom
1455    }
1456
1457    #[inline(always)]
1458    pub fn get_padding_left_raw(&self, node_idx: usize) -> i16 {
1459        self.tier2_dims[node_idx].padding_left
1460    }
1461
1462    #[inline(always)]
1463    pub fn get_margin_top_raw(&self, node_idx: usize) -> i16 {
1464        self.tier2_dims[node_idx].margin_top
1465    }
1466
1467    #[inline(always)]
1468    pub fn get_margin_right_raw(&self, node_idx: usize) -> i16 {
1469        self.tier2_dims[node_idx].margin_right
1470    }
1471
1472    #[inline(always)]
1473    pub fn get_margin_bottom_raw(&self, node_idx: usize) -> i16 {
1474        self.tier2_dims[node_idx].margin_bottom
1475    }
1476
1477    #[inline(always)]
1478    pub fn get_margin_left_raw(&self, node_idx: usize) -> i16 {
1479        self.tier2_dims[node_idx].margin_left
1480    }
1481
1482    #[inline(always)]
1483    pub fn get_border_top_width_raw(&self, node_idx: usize) -> i16 {
1484        self.tier2_dims[node_idx].border_top_width
1485    }
1486
1487    #[inline(always)]
1488    pub fn get_border_right_width_raw(&self, node_idx: usize) -> i16 {
1489        self.tier2_dims[node_idx].border_right_width
1490    }
1491
1492    #[inline(always)]
1493    pub fn get_border_bottom_width_raw(&self, node_idx: usize) -> i16 {
1494        self.tier2_dims[node_idx].border_bottom_width
1495    }
1496
1497    #[inline(always)]
1498    pub fn get_border_left_width_raw(&self, node_idx: usize) -> i16 {
1499        self.tier2_dims[node_idx].border_left_width
1500    }
1501
1502    #[inline(always)]
1503    pub fn get_top(&self, node_idx: usize) -> i16 {
1504        self.tier2_dims[node_idx].top
1505    }
1506
1507    #[inline(always)]
1508    pub fn get_right(&self, node_idx: usize) -> i16 {
1509        self.tier2_dims[node_idx].right
1510    }
1511
1512    #[inline(always)]
1513    pub fn get_bottom(&self, node_idx: usize) -> i16 {
1514        self.tier2_dims[node_idx].bottom
1515    }
1516
1517    #[inline(always)]
1518    pub fn get_left(&self, node_idx: usize) -> i16 {
1519        self.tier2_dims[node_idx].left
1520    }
1521
1522    #[inline(always)]
1523    pub fn get_flex_grow(&self, node_idx: usize) -> Option<f32> {
1524        decode_flex_u16(self.tier2_dims[node_idx].flex_grow)
1525    }
1526
1527    #[inline(always)]
1528    pub fn get_flex_shrink(&self, node_idx: usize) -> Option<f32> {
1529        decode_flex_u16(self.tier2_dims[node_idx].flex_shrink)
1530    }
1531
1532    #[inline(always)]
1533    pub fn get_z_index(&self, node_idx: usize) -> i16 {
1534        self.tier2_dims[node_idx].z_index
1535    }
1536
1537    // -- Border colors (u32 RGBA) --
1538
1539    #[inline(always)]
1540    pub fn get_border_top_color_raw(&self, node_idx: usize) -> u32 {
1541        self.tier2_dims[node_idx].border_top_color
1542    }
1543
1544    #[inline(always)]
1545    pub fn get_border_right_color_raw(&self, node_idx: usize) -> u32 {
1546        self.tier2_dims[node_idx].border_right_color
1547    }
1548
1549    #[inline(always)]
1550    pub fn get_border_bottom_color_raw(&self, node_idx: usize) -> u32 {
1551        self.tier2_dims[node_idx].border_bottom_color
1552    }
1553
1554    #[inline(always)]
1555    pub fn get_border_left_color_raw(&self, node_idx: usize) -> u32 {
1556        self.tier2_dims[node_idx].border_left_color
1557    }
1558
1559    // -- Border styles (packed u16) --
1560
1561    #[inline(always)]
1562    pub fn get_border_styles_packed(&self, node_idx: usize) -> u16 {
1563        self.tier2_dims[node_idx].border_styles_packed
1564    }
1565
1566    #[inline(always)]
1567    pub fn get_border_top_style(&self, node_idx: usize) -> BorderStyle {
1568        decode_border_top_style(self.tier2_dims[node_idx].border_styles_packed)
1569    }
1570
1571    #[inline(always)]
1572    pub fn get_border_right_style(&self, node_idx: usize) -> BorderStyle {
1573        decode_border_right_style(self.tier2_dims[node_idx].border_styles_packed)
1574    }
1575
1576    #[inline(always)]
1577    pub fn get_border_bottom_style(&self, node_idx: usize) -> BorderStyle {
1578        decode_border_bottom_style(self.tier2_dims[node_idx].border_styles_packed)
1579    }
1580
1581    #[inline(always)]
1582    pub fn get_border_left_style(&self, node_idx: usize) -> BorderStyle {
1583        decode_border_left_style(self.tier2_dims[node_idx].border_styles_packed)
1584    }
1585
1586    // -- Border spacing --
1587
1588    #[inline(always)]
1589    pub fn get_border_spacing_h_raw(&self, node_idx: usize) -> i16 {
1590        self.tier2_dims[node_idx].border_spacing_h
1591    }
1592
1593    #[inline(always)]
1594    pub fn get_border_spacing_v_raw(&self, node_idx: usize) -> i16 {
1595        self.tier2_dims[node_idx].border_spacing_v
1596    }
1597
1598    // -- Tab size --
1599
1600    #[inline(always)]
1601    pub fn get_tab_size_raw(&self, node_idx: usize) -> i16 {
1602        self.tier2_dims[node_idx].tab_size
1603    }
1604
1605    // -- Tier 2b getters (text props) --
1606
1607    #[inline(always)]
1608    pub fn get_text_color_raw(&self, node_idx: usize) -> u32 {
1609        self.tier2b_text[node_idx].text_color
1610    }
1611
1612    #[inline(always)]
1613    pub fn get_font_family_hash(&self, node_idx: usize) -> u64 {
1614        self.tier2b_text[node_idx].font_family_hash
1615    }
1616
1617    #[inline(always)]
1618    pub fn get_line_height(&self, node_idx: usize) -> Option<f32> {
1619        decode_resolved_px_i16(self.tier2b_text[node_idx].line_height)
1620    }
1621
1622    #[inline(always)]
1623    pub fn get_letter_spacing(&self, node_idx: usize) -> Option<f32> {
1624        decode_resolved_px_i16(self.tier2b_text[node_idx].letter_spacing)
1625    }
1626
1627    #[inline(always)]
1628    pub fn get_word_spacing(&self, node_idx: usize) -> Option<f32> {
1629        decode_resolved_px_i16(self.tier2b_text[node_idx].word_spacing)
1630    }
1631
1632    #[inline(always)]
1633    pub fn get_text_indent(&self, node_idx: usize) -> Option<f32> {
1634        decode_resolved_px_i16(self.tier2b_text[node_idx].text_indent)
1635    }
1636
1637    // -- Tier 3 access --
1638
1639    /// Get overflow properties for a node (usually None).
1640    #[inline]
1641    pub fn get_overflow_prop(&self, node_idx: usize, prop: &CssPropertyType) -> Option<&CssProperty> {
1642        self.tier3_overflow
1643            .get(node_idx)
1644            .and_then(|opt| opt.as_ref())
1645            .and_then(|map| map.get(prop))
1646    }
1647
1648    /// Insert a property into the Tier 3 overflow map for a node.
1649    pub fn set_overflow_prop(&mut self, node_idx: usize, prop: CssPropertyType, value: CssProperty) {
1650        if let Some(slot) = self.tier3_overflow.get_mut(node_idx) {
1651            slot.get_or_insert_with(|| Box::new(BTreeMap::new()))
1652                .insert(prop, value);
1653        }
1654    }
1655}
1656
1657// =============================================================================
1658// Helper: encode a CssPropertyValue<PixelValue> into i16 resolved-px
1659// =============================================================================
1660
1661/// Resolve a CssPropertyValue<PixelValue> to an i16 ×10 encoding.
1662/// Only handles `Exact(px(...))` values. Everything else → sentinel.
1663/// For the compact cache builder, we only pre-resolve absolute pixel values.
1664/// Relative units (em, %, etc.) get sentinel and fall back to the slow path.
1665#[inline]
1666pub fn encode_css_pixel_as_i16(prop: &CssPropertyValue<PixelValue>) -> i16 {
1667    match prop {
1668        CssPropertyValue::Exact(pv) => {
1669            if pv.metric == SizeMetric::Px {
1670                encode_resolved_px_i16(pv.number.get())
1671            } else {
1672                I16_SENTINEL // non-px units need resolution context → slow path
1673            }
1674        }
1675        CssPropertyValue::Auto => I16_AUTO,
1676        CssPropertyValue::Initial => I16_INITIAL,
1677        CssPropertyValue::Inherit => I16_INHERIT,
1678        _ => I16_SENTINEL,
1679    }
1680}
1681
1682#[cfg(test)]
1683mod tests {
1684    use super::*;
1685
1686    #[test]
1687    fn test_tier1_roundtrip() {
1688        let t1 = encode_tier1(
1689            LayoutDisplay::Flex,
1690            LayoutPosition::Relative,
1691            LayoutFloat::Left,
1692            LayoutOverflow::Hidden,
1693            LayoutOverflow::Scroll,
1694            LayoutBoxSizing::BorderBox,
1695            LayoutFlexDirection::Column,
1696            LayoutFlexWrap::Wrap,
1697            LayoutJustifyContent::SpaceBetween,
1698            LayoutAlignItems::Center,
1699            LayoutAlignContent::End,
1700            LayoutWritingMode::VerticalRl,
1701            LayoutClear::Both,
1702            StyleFontWeight::Bold,
1703            StyleFontStyle::Italic,
1704            StyleTextAlign::Center,
1705            StyleVisibility::Hidden,
1706            StyleWhiteSpace::Pre,
1707            StyleDirection::Rtl,
1708            StyleVerticalAlign::Middle,
1709            StyleBorderCollapse::Collapse,
1710        );
1711
1712        assert!(tier1_is_populated(t1));
1713        assert_eq!(decode_display(t1), LayoutDisplay::Flex);
1714        assert_eq!(decode_position(t1), LayoutPosition::Relative);
1715        assert_eq!(decode_float(t1), LayoutFloat::Left);
1716        assert_eq!(decode_overflow_x(t1), LayoutOverflow::Hidden);
1717        assert_eq!(decode_overflow_y(t1), LayoutOverflow::Scroll);
1718        assert_eq!(decode_box_sizing(t1), LayoutBoxSizing::BorderBox);
1719        assert_eq!(decode_flex_direction(t1), LayoutFlexDirection::Column);
1720        assert_eq!(decode_flex_wrap(t1), LayoutFlexWrap::Wrap);
1721        assert_eq!(decode_justify_content(t1), LayoutJustifyContent::SpaceBetween);
1722        assert_eq!(decode_align_items(t1), LayoutAlignItems::Center);
1723        assert_eq!(decode_align_content(t1), LayoutAlignContent::End);
1724        assert_eq!(decode_writing_mode(t1), LayoutWritingMode::VerticalRl);
1725        assert_eq!(decode_clear(t1), LayoutClear::Both);
1726        assert_eq!(decode_font_weight(t1), StyleFontWeight::Bold);
1727        assert_eq!(decode_font_style(t1), StyleFontStyle::Italic);
1728        assert_eq!(decode_text_align(t1), StyleTextAlign::Center);
1729        assert_eq!(decode_visibility(t1), StyleVisibility::Hidden);
1730        assert_eq!(decode_white_space(t1), StyleWhiteSpace::Pre);
1731        assert_eq!(decode_direction(t1), StyleDirection::Rtl);
1732        assert_eq!(decode_vertical_align(t1), StyleVerticalAlign::Middle);
1733        assert_eq!(decode_border_collapse(t1), StyleBorderCollapse::Collapse);
1734    }
1735
1736    #[test]
1737    fn test_tier1_defaults() {
1738        let t1 = encode_tier1(
1739            LayoutDisplay::Block,
1740            LayoutPosition::Static,
1741            LayoutFloat::None,
1742            LayoutOverflow::Visible,
1743            LayoutOverflow::Visible,
1744            LayoutBoxSizing::ContentBox,
1745            LayoutFlexDirection::Row,
1746            LayoutFlexWrap::NoWrap,
1747            LayoutJustifyContent::FlexStart,
1748            LayoutAlignItems::Stretch,
1749            LayoutAlignContent::Stretch,
1750            LayoutWritingMode::HorizontalTb,
1751            LayoutClear::None,
1752            StyleFontWeight::Normal,
1753            StyleFontStyle::Normal,
1754            StyleTextAlign::Left,
1755            StyleVisibility::Visible,
1756            StyleWhiteSpace::Normal,
1757            StyleDirection::Ltr,
1758            StyleVerticalAlign::Baseline,
1759            StyleBorderCollapse::Separate,
1760        );
1761
1762        assert!(tier1_is_populated(t1));
1763        assert_eq!(decode_display(t1), LayoutDisplay::Block);
1764        assert_eq!(decode_position(t1), LayoutPosition::Static);
1765    }
1766
1767    #[test]
1768    fn test_pixel_value_u32_roundtrip() {
1769        let pv = PixelValue::px(123.456);
1770        let encoded = encode_pixel_value_u32(&pv);
1771        assert!(encoded < U32_SENTINEL_THRESHOLD);
1772        let decoded = decode_pixel_value_u32(encoded).unwrap();
1773        assert_eq!(decoded.metric, SizeMetric::Px);
1774        // Check within precision (×1000)
1775        assert!((decoded.number.get() - 123.456).abs() < 0.002);
1776    }
1777
1778    #[test]
1779    fn test_pixel_value_u32_percent() {
1780        let pv = PixelValue {
1781            metric: SizeMetric::Percent,
1782            number: FloatValue::new(50.0),
1783        };
1784        let encoded = encode_pixel_value_u32(&pv);
1785        let decoded = decode_pixel_value_u32(encoded).unwrap();
1786        assert_eq!(decoded.metric, SizeMetric::Percent);
1787        assert!((decoded.number.get() - 50.0).abs() < 0.002);
1788    }
1789
1790    #[test]
1791    fn test_sentinel_values() {
1792        assert_eq!(decode_pixel_value_u32(U32_SENTINEL), None);
1793        assert_eq!(decode_pixel_value_u32(U32_AUTO), None);
1794        assert_eq!(decode_pixel_value_u32(U32_MIN_CONTENT), None);
1795        assert_eq!(decode_resolved_px_i16(I16_SENTINEL), None);
1796        assert_eq!(decode_resolved_px_i16(I16_AUTO), None);
1797    }
1798
1799    #[test]
1800    fn test_resolved_px_i16_roundtrip() {
1801        let px = 123.4f32;
1802        let encoded = encode_resolved_px_i16(px);
1803        let decoded = decode_resolved_px_i16(encoded).unwrap();
1804        assert!((decoded - 123.4).abs() < 0.11);
1805
1806        // Negative values
1807        let px = -50.7f32;
1808        let encoded = encode_resolved_px_i16(px);
1809        let decoded = decode_resolved_px_i16(encoded).unwrap();
1810        assert!((decoded - (-50.7)).abs() < 0.11);
1811    }
1812
1813    #[test]
1814    fn test_flex_u16_roundtrip() {
1815        let v = 2.5f32;
1816        let encoded = encode_flex_u16(v);
1817        let decoded = decode_flex_u16(encoded).unwrap();
1818        assert!((decoded - 2.5).abs() < 0.011);
1819    }
1820
1821    #[test]
1822    fn test_compact_node_props_size() {
1823        assert_eq!(core::mem::size_of::<CompactNodeProps>(), 96);
1824    }
1825
1826    #[test]
1827    fn test_compact_text_props_size() {
1828        assert_eq!(core::mem::size_of::<CompactTextProps>(), 24);
1829    }
1830}