Skip to main content

azul_core/
prop_cache.rs

1//! CSS property cache for efficient style resolution and animation.
2//!
3//! This module implements a cache layer between the raw CSS stylesheet and the rendered DOM.
4//! It resolves CSS properties for each node, handling:
5//!
6//! - **Cascade resolution**: Computes final values from CSS rules, inline styles, and inheritance
7//! - **Pseudo-class states**: Caches styles for `:hover`, `:active`, `:focus`, etc.
8//! - **Animation support**: Tracks animating properties for smooth interpolation
9//! - **Performance**: Avoids re-parsing and re-resolving unchanged properties
10//!
11//! # Architecture
12//!
13//! The cache is organized per-node and per-property-type. Each property has a dedicated
14//! getter method that:
15//!
16//! 1. Checks if the property is cached
17//! 2. If not, resolves it from CSS rules + inline styles
18//! 3. Caches the result for subsequent frames
19//!
20//! # Thread Safety
21//!
22//! Not thread-safe. Each window has its own cache instance.
23
24extern crate alloc;
25
26use alloc::{boxed::Box, string::String, vec::Vec};
27
28use crate::dom::NodeType;
29
30/// Tracks the origin of a CSS property value.
31/// Used to correctly implement the CSS cascade and inheritance rules.
32#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
33pub enum CssPropertyOrigin {
34    /// Property was inherited from parent node (only for inheritable properties)
35    Inherited,
36    /// Property is the node's own value (from UA CSS, CSS file, inline style, or user override)
37    Own,
38}
39
40/// A CSS property with its origin tracking.
41#[derive(Debug, Clone, PartialEq)]
42pub struct CssPropertyWithOrigin {
43    pub property: CssProperty,
44    pub origin: CssPropertyOrigin,
45}
46
47use azul_css::{
48    css::{Css, CssPath},
49    props::{
50        basic::{StyleFontFamily, StyleFontFamilyVec, StyleFontSize},
51        layout::{LayoutDisplay, LayoutHeight, LayoutWidth},
52        property::{
53            BoxDecorationBreakValue, BreakInsideValue, CaretAnimationDurationValue,
54            CaretColorValue, CaretWidthValue, ClipPathValue, ColumnCountValue, ColumnFillValue,
55            ColumnRuleColorValue, ColumnRuleStyleValue, ColumnRuleWidthValue, ColumnSpanValue,
56            ColumnWidthValue, ContentValue, CounterIncrementValue, CounterResetValue, CssProperty,
57            CssPropertyType, FlowFromValue, FlowIntoValue, LayoutAlignContentValue,
58            LayoutAlignItemsValue, LayoutAlignSelfValue, LayoutBorderBottomWidthValue,
59            LayoutBorderLeftWidthValue, LayoutBorderRightWidthValue, LayoutBorderSpacingValue,
60            LayoutBorderTopWidthValue, LayoutBoxSizingValue, LayoutClearValue,
61            LayoutColumnGapValue, LayoutDisplayValue, LayoutFlexBasisValue,
62            LayoutFlexDirectionValue, LayoutFlexGrowValue, LayoutFlexShrinkValue,
63            LayoutFlexWrapValue, LayoutFloatValue, LayoutGapValue, LayoutGridAutoColumnsValue,
64            LayoutGridAutoFlowValue, LayoutGridAutoRowsValue, LayoutGridColumnValue,
65            LayoutGridRowValue, LayoutGridTemplateColumnsValue, LayoutGridTemplateRowsValue,
66            LayoutHeightValue, LayoutInsetBottomValue, LayoutJustifyContentValue,
67            LayoutJustifyItemsValue, LayoutJustifySelfValue, LayoutLeftValue,
68            LayoutMarginBottomValue, LayoutMarginLeftValue, LayoutMarginRightValue,
69            LayoutMarginTopValue, LayoutMaxHeightValue, LayoutMaxWidthValue, LayoutMinHeightValue,
70            LayoutMinWidthValue, LayoutOverflowValue, LayoutPaddingBottomValue,
71            LayoutPaddingLeftValue, LayoutPaddingRightValue, LayoutPaddingTopValue,
72            LayoutPositionValue, LayoutRightValue, LayoutRowGapValue, LayoutScrollbarWidthValue,
73            LayoutTableLayoutValue, LayoutTextJustifyValue, LayoutTopValue, LayoutWidthValue,
74            LayoutWritingModeValue, LayoutZIndexValue, OrphansValue, PageBreakValue,
75            StyleBackgroundContentValue, ScrollbarFadeDelayValue, ScrollbarFadeDurationValue,
76            ScrollbarVisibilityModeValue, SelectionBackgroundColorValue, SelectionColorValue,
77            SelectionRadiusValue, ShapeImageThresholdValue, ShapeInsideValue, ShapeMarginValue,
78            ShapeOutsideValue, StringSetValue, StyleBackfaceVisibilityValue,
79            StyleBackgroundContentVecValue, StyleBackgroundPositionVecValue,
80            StyleBackgroundRepeatVecValue, StyleBackgroundSizeVecValue,
81            StyleBorderBottomColorValue, StyleBorderBottomLeftRadiusValue,
82            StyleBorderBottomRightRadiusValue, StyleBorderBottomStyleValue,
83            StyleBorderCollapseValue, StyleBorderLeftColorValue, StyleBorderLeftStyleValue,
84            StyleBorderRightColorValue, StyleBorderRightStyleValue, StyleBorderTopColorValue,
85            StyleBorderTopLeftRadiusValue, StyleBorderTopRightRadiusValue,
86            StyleBorderTopStyleValue, StyleBoxShadowValue, StyleCaptionSideValue, StyleCursorValue,
87            StyleDirectionValue, StyleEmptyCellsValue, StyleExclusionMarginValue,
88            StyleFilterVecValue, StyleFontFamilyVecValue, StyleFontSizeValue, StyleFontStyleValue,
89            StyleFontValue, StyleFontWeightValue, StyleHangingPunctuationValue,
90            StyleHyphenationLanguageValue, StyleHyphensValue, StyleInitialLetterValue,
91            StyleLetterSpacingValue, StyleLineBreakValue, StyleLineClampValue, StyleLineHeightValue,
92            StyleListStylePositionValue, StyleListStyleTypeValue, StyleMixBlendModeValue,
93            StyleAspectRatioValue, StyleObjectFitValue, StyleObjectPositionValue,
94            StyleOpacityValue, StylePerspectiveOriginValue,
95            StyleScrollbarColorValue, StyleOverflowWrapValue, StyleTabSizeValue,
96            StyleTextAlignLastValue, StyleTextOrientationValue,
97            StyleTextAlignValue, StyleTextColorValue,
98            StyleTextCombineUprightValue, StyleUnicodeBidiValue,
99            StyleTextBoxTrimValue, StyleTextBoxEdgeValue,
100            StyleDominantBaselineValue, StyleAlignmentBaselineValue,
101            StyleInitialLetterAlignValue, StyleInitialLetterWrapValue,
102            StyleScrollbarGutterValue, StyleOverflowClipMarginValue, StyleClipRectValue,
103            StyleTextDecorationValue, StyleTextIndentValue,
104            StyleTransformOriginValue, StyleTransformVecValue, StyleUserSelectValue,
105            StyleVerticalAlignValue, StyleVisibilityValue, StyleWhiteSpaceValue,
106            StyleWordBreakValue, StyleWordSpacingValue, WidowsValue,
107        },
108        style::{StyleCursor, StyleTextColor, StyleTransformOrigin},
109    },
110    AzString,
111};
112
113use crate::{
114    dom::{NodeData, NodeId, TabIndex, TagId},
115    id::{NodeDataContainer, NodeDataContainerRef},
116    style::CascadeInfo,
117    styled_dom::{
118        NodeHierarchyItem, NodeHierarchyItemId, NodeHierarchyItemVec, ParentWithNodeDepth,
119        ParentWithNodeDepthVec, StyledNodeState, TagIdToNodeIdMapping,
120    },
121};
122
123use azul_css::dynamic_selector::{
124    CssPropertyWithConditions, CssPropertyWithConditionsVec, DynamicSelectorContext,
125};
126
127#[cfg(feature = "std")]
128std::thread_local! {
129    static PROP_COUNTS: core::cell::RefCell<
130        std::collections::HashMap<&'static str, usize>
131    > = core::cell::RefCell::new(std::collections::HashMap::new());
132}
133
134/// Drain the per-thread CSS cascade-walk counter populated by
135/// [`CssPropertyCache::get_property`] when `AZ_PROP_COUNT=1` is set
136/// in the environment. Returns `(property_label, count)` pairs
137/// sorted by count descending. Layout-side instrumentation calls
138/// this after each `layout_document` to print which properties
139/// drove the most cascade walks.
140#[cfg(feature = "std")]
141pub fn drain_css_prop_counts() -> Vec<(&'static str, usize)> {
142    // try_with: no real TLS in the lifted-to-wasm web backend (see the
143    // get_property recording site) — return empty rather than panic.
144    PROP_COUNTS
145        .try_with(|c| {
146            let map = core::mem::take(&mut *c.borrow_mut());
147            let mut v: Vec<_> = map.into_iter().collect();
148            v.sort_by(|a, b| b.1.cmp(&a.1));
149            v
150        })
151        .unwrap_or_default()
152}
153
154// Unit conversion constants (CSS absolute units → pixels)
155const PT_TO_PX: f32 = 1.333333;
156const IN_TO_PX: f32 = 96.0;
157const CM_TO_PX: f32 = 37.7952755906;
158const MM_TO_PX: f32 = 3.7795275591;
159
160/// Match on any CssProperty variant and access the inner CssPropertyValue<T>.
161#[allow(unused_macros)]
162macro_rules! match_property_value {
163    ($property:expr, $value:ident, $expr:expr) => {
164        match $property {
165            CssProperty::CaretColor($value) => $expr,
166            CssProperty::CaretAnimationDuration($value) => $expr,
167            CssProperty::SelectionBackgroundColor($value) => $expr,
168            CssProperty::SelectionColor($value) => $expr,
169            CssProperty::SelectionRadius($value) => $expr,
170            CssProperty::TextColor($value) => $expr,
171            CssProperty::FontSize($value) => $expr,
172            CssProperty::FontFamily($value) => $expr,
173            CssProperty::FontWeight($value) => $expr,
174            CssProperty::FontStyle($value) => $expr,
175            CssProperty::TextAlign($value) => $expr,
176            CssProperty::TextJustify($value) => $expr,
177            CssProperty::VerticalAlign($value) => $expr,
178            CssProperty::LetterSpacing($value) => $expr,
179            CssProperty::TextIndent($value) => $expr,
180            CssProperty::InitialLetter($value) => $expr,
181            CssProperty::LineClamp($value) => $expr,
182            CssProperty::HangingPunctuation($value) => $expr,
183            CssProperty::TextCombineUpright($value) => $expr,
184            CssProperty::UnicodeBidi($value) => $expr,
185            CssProperty::TextBoxTrim($value) => $expr,
186            CssProperty::TextBoxEdge($value) => $expr,
187            CssProperty::DominantBaseline($value) => $expr,
188            CssProperty::AlignmentBaseline($value) => $expr,
189            CssProperty::InitialLetterAlign($value) => $expr,
190            CssProperty::InitialLetterWrap($value) => $expr,
191            CssProperty::ScrollbarGutter($value) => $expr,
192            CssProperty::OverflowClipMargin($value) => $expr,
193            CssProperty::Clip($value) => $expr,
194            CssProperty::ExclusionMargin($value) => $expr,
195            CssProperty::HyphenationLanguage($value) => $expr,
196            CssProperty::LineHeight($value) => $expr,
197            CssProperty::WordSpacing($value) => $expr,
198            CssProperty::TabSize($value) => $expr,
199            CssProperty::WhiteSpace($value) => $expr,
200            CssProperty::Hyphens($value) => $expr,
201            CssProperty::Direction($value) => $expr,
202            CssProperty::UserSelect($value) => $expr,
203            CssProperty::TextDecoration($value) => $expr,
204            CssProperty::Cursor($value) => $expr,
205            CssProperty::Display($value) => $expr,
206            CssProperty::Float($value) => $expr,
207            CssProperty::BoxSizing($value) => $expr,
208            CssProperty::Width($value) => $expr,
209            CssProperty::Height($value) => $expr,
210            CssProperty::MinWidth($value) => $expr,
211            CssProperty::MinHeight($value) => $expr,
212            CssProperty::MaxWidth($value) => $expr,
213            CssProperty::MaxHeight($value) => $expr,
214            CssProperty::Position($value) => $expr,
215            CssProperty::Top($value) => $expr,
216            CssProperty::Right($value) => $expr,
217            CssProperty::Left($value) => $expr,
218            CssProperty::Bottom($value) => $expr,
219            CssProperty::ZIndex($value) => $expr,
220            CssProperty::FlexWrap($value) => $expr,
221            CssProperty::FlexDirection($value) => $expr,
222            CssProperty::FlexGrow($value) => $expr,
223            CssProperty::FlexShrink($value) => $expr,
224            CssProperty::FlexBasis($value) => $expr,
225            CssProperty::JustifyContent($value) => $expr,
226            CssProperty::AlignItems($value) => $expr,
227            CssProperty::AlignContent($value) => $expr,
228            CssProperty::AlignSelf($value) => $expr,
229            CssProperty::JustifyItems($value) => $expr,
230            CssProperty::JustifySelf($value) => $expr,
231            CssProperty::BackgroundContent($value) => $expr,
232            CssProperty::BackgroundPosition($value) => $expr,
233            CssProperty::BackgroundSize($value) => $expr,
234            CssProperty::BackgroundRepeat($value) => $expr,
235            CssProperty::OverflowX($value) => $expr,
236            CssProperty::OverflowY($value) => $expr,
237            CssProperty::OverflowBlock($value) => $expr,
238            CssProperty::OverflowInline($value) => $expr,
239            CssProperty::PaddingTop($value) => $expr,
240            CssProperty::PaddingLeft($value) => $expr,
241            CssProperty::PaddingRight($value) => $expr,
242            CssProperty::PaddingBottom($value) => $expr,
243            CssProperty::MarginTop($value) => $expr,
244            CssProperty::MarginLeft($value) => $expr,
245            CssProperty::MarginRight($value) => $expr,
246            CssProperty::MarginBottom($value) => $expr,
247            CssProperty::BorderTopLeftRadius($value) => $expr,
248            CssProperty::BorderTopRightRadius($value) => $expr,
249            CssProperty::BorderBottomLeftRadius($value) => $expr,
250            CssProperty::BorderBottomRightRadius($value) => $expr,
251            CssProperty::BorderTopColor($value) => $expr,
252            CssProperty::BorderRightColor($value) => $expr,
253            CssProperty::BorderLeftColor($value) => $expr,
254            CssProperty::BorderBottomColor($value) => $expr,
255            CssProperty::BorderTopStyle($value) => $expr,
256            CssProperty::BorderRightStyle($value) => $expr,
257            CssProperty::BorderLeftStyle($value) => $expr,
258            CssProperty::BorderBottomStyle($value) => $expr,
259            CssProperty::BorderTopWidth($value) => $expr,
260            CssProperty::BorderRightWidth($value) => $expr,
261            CssProperty::BorderLeftWidth($value) => $expr,
262            CssProperty::BorderBottomWidth($value) => $expr,
263            CssProperty::BoxShadow($value) => $expr,
264            CssProperty::Opacity($value) => $expr,
265            CssProperty::Transform($value) => $expr,
266            CssProperty::TransformOrigin($value) => $expr,
267            CssProperty::PerspectiveOrigin($value) => $expr,
268            CssProperty::BackfaceVisibility($value) => $expr,
269            CssProperty::MixBlendMode($value) => $expr,
270            CssProperty::Filter($value) => $expr,
271            CssProperty::Visibility($value) => $expr,
272            CssProperty::WritingMode($value) => $expr,
273            CssProperty::GridTemplateColumns($value) => $expr,
274            CssProperty::GridTemplateRows($value) => $expr,
275            CssProperty::GridAutoColumns($value) => $expr,
276            CssProperty::GridAutoRows($value) => $expr,
277            CssProperty::GridAutoFlow($value) => $expr,
278            CssProperty::GridColumn($value) => $expr,
279            CssProperty::GridRow($value) => $expr,
280            CssProperty::GridTemplateAreas($value) => $expr,
281            CssProperty::Gap($value) => $expr,
282            CssProperty::ColumnGap($value) => $expr,
283            CssProperty::RowGap($value) => $expr,
284            CssProperty::Clear($value) => $expr,
285            CssProperty::ScrollbarTrack($value) => $expr,
286            CssProperty::ScrollbarThumb($value) => $expr,
287            CssProperty::ScrollbarButton($value) => $expr,
288            CssProperty::ScrollbarCorner($value) => $expr,
289            CssProperty::ScrollbarResizer($value) => $expr,
290            CssProperty::ScrollbarWidth($value) => $expr,
291            CssProperty::ScrollbarColor($value) => $expr,
292            CssProperty::ListStyleType($value) => $expr,
293            CssProperty::ListStylePosition($value) => $expr,
294            CssProperty::Font($value) => $expr,
295            CssProperty::ColumnCount($value) => $expr,
296            CssProperty::ColumnWidth($value) => $expr,
297            CssProperty::ColumnSpan($value) => $expr,
298            CssProperty::ColumnFill($value) => $expr,
299            CssProperty::ColumnRuleStyle($value) => $expr,
300            CssProperty::ColumnRuleWidth($value) => $expr,
301            CssProperty::ColumnRuleColor($value) => $expr,
302            CssProperty::FlowInto($value) => $expr,
303            CssProperty::FlowFrom($value) => $expr,
304            CssProperty::ShapeOutside($value) => $expr,
305            CssProperty::ShapeInside($value) => $expr,
306            CssProperty::ShapeImageThreshold($value) => $expr,
307            CssProperty::ShapeMargin($value) => $expr,
308            CssProperty::ClipPath($value) => $expr,
309            CssProperty::Content($value) => $expr,
310            CssProperty::CounterIncrement($value) => $expr,
311            CssProperty::CounterReset($value) => $expr,
312            CssProperty::StringSet($value) => $expr,
313            CssProperty::Orphans($value) => $expr,
314            CssProperty::Widows($value) => $expr,
315            CssProperty::PageBreakBefore($value) => $expr,
316            CssProperty::PageBreakAfter($value) => $expr,
317            CssProperty::PageBreakInside($value) => $expr,
318            CssProperty::BreakInside($value) => $expr,
319            CssProperty::BoxDecorationBreak($value) => $expr,
320            CssProperty::TableLayout($value) => $expr,
321            CssProperty::BorderCollapse($value) => $expr,
322            CssProperty::BorderSpacing($value) => $expr,
323            CssProperty::CaptionSide($value) => $expr,
324            CssProperty::EmptyCells($value) => $expr,
325        }
326    };
327}
328
329/// A CSS property tagged with its pseudo-state and property type.
330/// Replaces the per-pseudo-state BTreeMap approach: instead of 6 BTreeMaps
331/// per node (Normal/Hover/Active/Focus/Dragging/DragOver), we store one Vec
332/// per node and tag each property with its state. Lookups use `.iter().find()`.
333#[derive(Debug, Clone, PartialEq)]
334pub struct StatefulCssProperty {
335    pub state: azul_css::dynamic_selector::PseudoStateType,
336    pub prop_type: CssPropertyType,
337    pub property: CssProperty,
338}
339
340// =============================================================================
341// FlatVecVec: Cache-friendly replacement for Vec<Vec<T>>
342// =============================================================================
343
344/// A flat, cache-friendly replacement for `Vec<Vec<T>>`.
345///
346/// During the **build phase**, items are pushed into per-node inner Vecs
347/// (same as before). After building is complete, `flatten()` compacts all
348/// inner Vecs into a single contiguous `Vec<T>` with a `(start, len)` offset
349/// table per node. All subsequent reads use the flat layout, eliminating
350/// N heap allocations and pointer chasing.
351///
352/// ## Lifecycle
353///
354/// ```text
355/// new(n) → push_to(idx, item)* → sort_each_and_flatten(key_fn) → get_slice(idx)*
356///          ── build phase ──       ── transition ──                ── read phase ──
357/// ```
358#[derive(Debug, Clone)]
359pub struct FlatVecVec<T> {
360    /// Per-node inner Vecs (used during build phase, empty after flatten).
361    build: Vec<Vec<T>>,
362    /// Flat contiguous storage (populated after flatten).
363    data: Vec<T>,
364    /// `(start, len)` offsets into `data` for each node (populated after flatten).
365    offsets: Vec<(u32, u32)>,
366}
367
368impl<T: PartialEq> PartialEq for FlatVecVec<T> {
369    fn eq(&self, other: &Self) -> bool {
370        let self_in_build = !self.build.is_empty() && self.offsets.is_empty();
371        let other_in_build = !other.build.is_empty() && other.offsets.is_empty();
372        debug_assert!(
373            self_in_build == other_in_build,
374            "FlatVecVec::eq called across phases (one build, one flattened)"
375        );
376        if self_in_build || other_in_build {
377            self.build == other.build
378        } else {
379            self.data == other.data && self.offsets == other.offsets
380        }
381    }
382}
383
384impl<T> Default for FlatVecVec<T> {
385    fn default() -> Self {
386        Self {
387            build: Vec::new(),
388            data: Vec::new(),
389            offsets: Vec::new(),
390        }
391    }
392}
393
394impl<T> FlatVecVec<T> {
395    /// Approximate heap bytes retained. Sums capacity of the
396    /// flattened `data` + `offsets` tables and the per-node build
397    /// Vecs (in case `sort_each_and_flatten` hasn't been called
398    /// yet). `per_element_size` should be `size_of::<T>()`.
399    pub fn heap_bytes(&self, per_element_size: usize) -> usize {
400        let data_bytes = self.data.capacity() * per_element_size;
401        let offsets_bytes =
402            self.offsets.capacity() * core::mem::size_of::<(u32, u32)>();
403        let mut build_bytes = self.build.capacity() * core::mem::size_of::<Vec<T>>();
404        for v in &self.build {
405            build_bytes += v.capacity() * per_element_size;
406        }
407        data_bytes + offsets_bytes + build_bytes
408    }
409
410    /// Create a new `FlatVecVec` with `node_count` empty slots (build phase).
411    pub fn new(node_count: usize) -> Self {
412        let mut build = Vec::with_capacity(node_count);
413        for _ in 0..node_count {
414            build.push(Vec::new());
415        }
416        Self {
417            build,
418            data: Vec::new(),
419            offsets: Vec::new(),
420        }
421    }
422
423    /// Push an item to the inner Vec at `node_index` (build phase).
424    ///
425    /// # Panics
426    /// Panics if already flattened or if `node_index >= len()`.
427    #[inline]
428    pub fn push_to(&mut self, node_index: usize, item: T) {
429        self.build[node_index].push(item);
430    }
431
432    /// Get a mutable reference to the inner Vec at `node_index` (build phase).
433    #[inline]
434    pub fn build_mut(&mut self, node_index: usize) -> &mut Vec<T> {
435        &mut self.build[node_index]
436    }
437
438    /// Iterate mutably over all inner Vecs (build phase, e.g. for clearing).
439    #[inline]
440    pub fn build_iter_mut(&mut self) -> core::slice::IterMut<'_, Vec<T>> {
441        self.build.iter_mut()
442    }
443
444    /// Get a reference to the inner Vec at `node_index` during build phase.
445    /// During read phase, returns None (use `get_slice` instead).
446    #[inline]
447    pub fn build_get(&self, node_index: usize) -> Option<&Vec<T>> {
448        self.build.get(node_index)
449    }
450
451    /// Number of node slots.
452    #[inline]
453    pub fn len(&self) -> usize {
454        if !self.offsets.is_empty() {
455            self.offsets.len()
456        } else {
457            self.build.len()
458        }
459    }
460
461    /// Returns true if this is in read (flattened) mode.
462    #[inline]
463    pub fn is_flattened(&self) -> bool {
464        !self.offsets.is_empty() || self.build.is_empty()
465    }
466
467    /// Get a slice for the node at `node_index` (read phase).
468    /// Returns empty slice if index is out of bounds or not yet flattened
469    /// (falls back to build-phase data if not yet flattened).
470    #[inline]
471    pub fn get_slice(&self, node_index: usize) -> &[T] {
472        if !self.offsets.is_empty() {
473            // Read phase: use flat data
474            if let Some(&(start, len)) = self.offsets.get(node_index) {
475                let s = start as usize;
476                let l = len as usize;
477                &self.data[s..s + l]
478            } else {
479                &[]
480            }
481        } else {
482            // Build phase fallback: use inner Vecs
483            self.build.get(node_index).map(|v| v.as_slice()).unwrap_or(&[])
484        }
485    }
486
487    /// Flatten: sort each inner Vec by key, deduplicate by keeping the last
488    /// occurrence of each key (CSS cascade: later source order wins among
489    /// equal specificity), then compact into flat storage.
490    /// Drains all build-phase Vecs. After this call, only `get_slice()` works.
491    pub fn sort_each_and_flatten<K: Ord + Eq>(&mut self, key_fn: impl Fn(&T) -> K) {
492        let node_count = self.build.len();
493        let total: usize = self.build.iter().map(|v| v.len()).sum();
494
495        let mut flat_data = Vec::with_capacity(total);
496        let mut offsets = Vec::with_capacity(node_count);
497
498        for inner in self.build.iter_mut() {
499            inner.sort_by(|a, b| key_fn(a).cmp(&key_fn(b)));
500
501            // Deduplicate: keep last of each consecutive-key group (CSS cascade).
502            let n = inner.len();
503            let mut keep = vec![false; n];
504            for i in 0..n {
505                if i + 1 >= n || key_fn(&inner[i]) != key_fn(&inner[i + 1]) {
506                    keep[i] = true;
507                }
508            }
509
510            let start = flat_data.len() as u32;
511            // Drain inner and push only kept items
512            for (i, item) in inner.drain(..).enumerate() {
513                if keep[i] {
514                    flat_data.push(item);
515                }
516            }
517
518            let len = (flat_data.len() as u32) - start;
519            offsets.push((start, len));
520        }
521
522        flat_data.shrink_to_fit();
523        self.data = flat_data;
524        self.offsets = offsets;
525        self.build = Vec::new();
526    }
527
528    /// Flatten without sorting (for data that's already sorted).
529    pub fn flatten(&mut self) {
530        let node_count = self.build.len();
531        let total: usize = self.build.iter().map(|v| v.len()).sum();
532
533        let mut flat_data = Vec::with_capacity(total);
534        let mut offsets = Vec::with_capacity(node_count);
535
536        for inner in self.build.iter_mut() {
537            let start = flat_data.len() as u32;
538            let len = inner.len() as u32;
539            offsets.push((start, len));
540            flat_data.append(inner);
541        }
542
543        self.data = flat_data;
544        self.offsets = offsets;
545        self.build = Vec::new();
546    }
547
548    /// Rebuild flat storage, keeping only items matching `predicate`.
549    /// Must be called after flatten. Preserves per-node ordering.
550    pub fn retain(&mut self, predicate: impl Fn(&T) -> bool) where T: Clone {
551        if self.offsets.is_empty() { return; }
552        let node_count = self.offsets.len();
553        let mut new_data = Vec::new();
554        let mut new_offsets = Vec::with_capacity(node_count);
555        for &(start, len) in &self.offsets {
556            let s = start as usize;
557            let l = len as usize;
558            let new_start = new_data.len() as u32;
559            let slice = &self.data[s..s + l];
560            let mut kept = 0u32;
561            for item in slice {
562                if predicate(item) {
563                    new_data.push((*item).clone());
564                    kept += 1;
565                }
566            }
567            new_offsets.push((new_start, kept));
568        }
569        new_data.shrink_to_fit();
570        self.data = new_data;
571        self.offsets = new_offsets;
572    }
573
574    /// Like `retain`, but passes each item's owning node index to the predicate.
575    /// Must be called after flatten. Preserves per-node ordering.
576    pub fn retain_with_node_index(
577        &mut self,
578        predicate: impl Fn(usize, &T) -> bool,
579    ) where T: Clone {
580        if self.offsets.is_empty() { return; }
581        let node_count = self.offsets.len();
582        let mut new_data = Vec::new();
583        let mut new_offsets = Vec::with_capacity(node_count);
584        for (node_idx, &(start, len)) in self.offsets.iter().enumerate() {
585            let s = start as usize;
586            let l = len as usize;
587            let new_start = new_data.len() as u32;
588            let slice = &self.data[s..s + l];
589            let mut kept = 0u32;
590            for item in slice {
591                if predicate(node_idx, item) {
592                    new_data.push((*item).clone());
593                    kept += 1;
594                }
595            }
596            new_offsets.push((new_start, kept));
597        }
598        new_data.shrink_to_fit();
599        self.data = new_data;
600        self.offsets = new_offsets;
601    }
602
603    /// Iterate over all nodes, yielding (node_index, &[T]) for each.
604    /// Works in both build and flattened phases.
605    pub(crate) fn iter_node_slices(&self) -> FlatVecVecIter<'_, T> {
606        FlatVecVecIter {
607            fvv: self,
608            idx: 0,
609            count: self.len(),
610        }
611    }
612
613    /// Extend this FlatVecVec with all nodes from `other` (append for DOM merge).
614    /// Both must be in build phase, or both must be flattened.
615    pub fn extend_from(&mut self, other: &mut Self) {
616        if !self.offsets.is_empty() && !other.offsets.is_empty() {
617            // Both flattened: extend flat data with offset adjustment
618            let base = self.data.len() as u32;
619            self.data.extend(other.data.drain(..));
620            self.offsets.extend(other.offsets.drain(..).map(|(s, l)| (s + base, l)));
621        } else {
622            // At least one in build phase: extend build vecs
623            self.build.extend(other.build.drain(..));
624            // Invalidate flat data if it existed
625            self.data.clear();
626            self.offsets.clear();
627        }
628    }
629}
630
631/// Iterator over (node_index, &[T]) pairs from a `FlatVecVec`.
632pub(crate) struct FlatVecVecIter<'a, T> {
633    fvv: &'a FlatVecVec<T>,
634    idx: usize,
635    count: usize,
636}
637
638impl<'a, T> Iterator for FlatVecVecIter<'a, T> {
639    type Item = (usize, &'a [T]);
640
641    #[inline]
642    fn next(&mut self) -> Option<Self::Item> {
643        if self.idx >= self.count {
644            return None;
645        }
646        let i = self.idx;
647        self.idx += 1;
648        Some((i, self.fvv.get_slice(i)))
649    }
650
651    fn size_hint(&self) -> (usize, Option<usize>) {
652        let rem = self.count - self.idx;
653        (rem, Some(rem))
654    }
655}
656
657impl<'a, T> ExactSizeIterator for FlatVecVecIter<'a, T> {}
658
659// NOTE: To avoid large memory allocations, this is a "cache" that stores all the CSS properties
660// found in the DOM. This cache exists on a per-DOM basis, so it scales independent of how many
661// nodes are in the DOM.
662//
663// If each node would carry its own CSS properties, that would unnecessarily consume memory
664// because most nodes use the default properties or override only one or two properties.
665//
666// The cache can compute the property of any node at any given time, given the current node
667// state (hover, active, focused, normal). This way we don't have to duplicate the CSS properties
668// onto every single node and exchange them when the style changes. Two caches can be appended
669// to each other by simply merging their NodeIds.
670#[derive(Debug, Default, Clone, PartialEq)]
671pub struct CssPropertyCache {
672    // number of nodes in the current DOM
673    pub node_count: usize,
674
675    // properties that were overridden in callbacks (not specific to any node state)
676    pub user_overridden_properties: Vec<Vec<(CssPropertyType, CssProperty)>>,
677
678    // non-default CSS properties that were cascaded from the parent,
679    // unified across all pseudo-states (Normal, Hover, Active, Focus, Dragging, DragOver).
680    // Stored in a flat cache-friendly layout after sort_and_flatten().
681    pub cascaded_props: FlatVecVec<StatefulCssProperty>,
682
683    // non-default CSS properties that were set via a CSS file,
684    // unified across all pseudo-states.
685    pub css_props: FlatVecVec<StatefulCssProperty>,
686
687    // Pre-resolved inherited properties (sorted Vec per node, keyed by CssPropertyType)
688    pub computed_values: Vec<Vec<(CssPropertyType, CssPropertyWithOrigin)>>,
689
690    // Compact layout cache: three-tier numeric encoding for O(1) layout lookups.
691    // Built once after restyle + apply_ua_css + compute_inherited_values.
692    // Non-compact properties (background, shadow, transform) use get_property_slow().
693    pub compact_cache: Option<azul_css::compact_cache::CompactLayoutCache>,
694
695    // Global CSS properties from `*` rules — shared across all nodes.
696    // Applied during build_compact_cache_with_inheritance instead of being
697    // cloned into each node's css_props (saves 50K×N clones).
698    pub global_css_props: Vec<CssProperty>,
699
700    /// Per-node resolved font-size, in pixels, for the `Normal`
701    /// pseudo-state. Populated lazily on first call to
702    /// [`crate::styled_dom::StyledDom::resolved_font_size_px`] via a
703    /// single bottom-up DOM walk; subsequent reads are O(1) Vec
704    /// index by `NodeId::index()`.
705    ///
706    /// Motivation: `get_font_size` is called ~730× per node per
707    /// layout pass (see `AZ_PROP_COUNT=1` report — 329 629
708    /// cascade walks on excel.html alone). Each resolution
709    /// recursively reads the parent's font-size (for `em`) plus
710    /// the root's font-size (for `rem`), multiplying the walk
711    /// count. Caching the pre-resolved pixel value collapses that
712    /// to a single `Vec<f32>` indexed lookup.
713    pub resolved_font_sizes_px: std::sync::OnceLock<Vec<f32>>,
714}
715
716/// Heap-size breakdown of a `CssPropertyCache`, produced by
717/// [`CssPropertyCache::memory_breakdown`]. All values in bytes.
718///
719/// Primarily a diagnostic — the numbers are capacity-based and
720/// don't chase into property-variant payloads (e.g. the `Vec`
721/// inside a `FontFamily(...)`). Intended for "which subfield is
722/// eating RSS" triage, not for precise accounting.
723#[derive(Debug, Clone, Copy, Default)]
724pub struct CssPropertyCacheBreakdown {
725    pub node_count: usize,
726    pub cascaded_props_bytes: usize,
727    pub css_props_bytes: usize,
728    pub computed_values_bytes: usize,
729    pub user_overridden_bytes: usize,
730    pub global_css_props_bytes: usize,
731    pub compact_cache_bytes: usize,
732    pub resolved_font_sizes_bytes: usize,
733}
734
735impl CssPropertyCacheBreakdown {
736    /// Sum of all subfields.
737    pub fn total_bytes(&self) -> usize {
738        self.cascaded_props_bytes
739            + self.css_props_bytes
740            + self.computed_values_bytes
741            + self.user_overridden_bytes
742            + self.global_css_props_bytes
743            + self.compact_cache_bytes
744            + self.resolved_font_sizes_bytes
745    }
746}
747
748impl CssPropertyCache {
749    /// Approximate heap bytes retained by this cache, broken out by
750    /// subfield. Used by `StyledDom::memory_breakdown` + the
751    /// `AZ_MEM_BREAKDOWN=1` reporter. Sums capacity × element size
752    /// for each Vec and adds a coarse allowance for the inner Vec
753    /// headers inside `computed_values`.
754    ///
755    /// This is a measurement helper, not a tight bound — it doesn't
756    /// chase into the `CssProperty` enum variants that carry their
757    /// own `Vec`/`String` allocations (notably `FontFamily` →
758    /// `StyleFontFamilyVec` → `Vec<StyleFontFamily>`), so the real
759    /// heap footprint for a property-rich DOM can be 2-3× these
760    /// numbers. Still useful for spotting gross duplication between
761    /// the pre-compact and compact caches.
762    pub fn memory_breakdown(&self) -> CssPropertyCacheBreakdown {
763        let stateful_sz = core::mem::size_of::<StatefulCssProperty>();
764        let computed_entry_sz =
765            core::mem::size_of::<(CssPropertyType, CssPropertyWithOrigin)>();
766        let outer_vec_sz = core::mem::size_of::<Vec<(CssPropertyType, CssPropertyWithOrigin)>>();
767
768        let cascaded_bytes = self.cascaded_props.heap_bytes(stateful_sz);
769        let css_bytes = self.css_props.heap_bytes(stateful_sz);
770
771        let mut computed_bytes = self.computed_values.capacity() * outer_vec_sz;
772        for v in &self.computed_values {
773            computed_bytes += v.capacity() * computed_entry_sz;
774        }
775
776        let user_overridden_bytes = {
777            let mut b = self.user_overridden_properties.capacity() * outer_vec_sz;
778            for v in &self.user_overridden_properties {
779                b += v.capacity()
780                    * core::mem::size_of::<(CssPropertyType, CssProperty)>();
781            }
782            b
783        };
784
785        let global_bytes = self.global_css_props.capacity()
786            * core::mem::size_of::<CssProperty>();
787
788        let compact_bytes = self
789            .compact_cache
790            .as_ref()
791            .map(|c| {
792                c.tier1_enums.capacity() * 8
793                    + c.tier2_dims.capacity() * 68
794                    + c.tier2_cold.capacity() * 28
795                    + c.tier2b_text.capacity() * 24
796                    + c.prev_font_hashes.capacity() * 8
797                    + c.font_dirty_nodes.capacity() * 8
798            })
799            .unwrap_or(0);
800
801        let resolved_font_sizes_bytes = self
802            .resolved_font_sizes_px
803            .get()
804            .map(|v| v.capacity() * core::mem::size_of::<f32>())
805            .unwrap_or(0);
806
807        CssPropertyCacheBreakdown {
808            node_count: self.node_count,
809            cascaded_props_bytes: cascaded_bytes,
810            css_props_bytes: css_bytes,
811            computed_values_bytes: computed_bytes,
812            user_overridden_bytes,
813            global_css_props_bytes: global_bytes,
814            compact_cache_bytes: compact_bytes,
815            resolved_font_sizes_bytes,
816        }
817    }
818
819    /// Drop Normal-state properties that have compact encodings from
820    /// `css_props` and `cascaded_props`. After `build_compact_cache_with_inheritance`,
821    /// these are redundant — the compact cache is the source of truth for layout.
822    /// Non-Normal entries (hover/active/focus) and non-compact properties
823    /// (background, box-shadow, transform, etc.) are kept for `get_property_slow`.
824    pub fn prune_compact_normal_props(&mut self) {
825        use azul_css::dynamic_selector::PseudoStateType;
826
827        static PRUNE_DBG: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
828        let dbg = *PRUNE_DBG.get_or_init(crate::profile::memory_enabled);
829        if dbg {
830            let mut normal_compact = 0usize;
831            let mut normal_noncompact = 0usize;
832            let mut nonnormal = 0usize;
833            for i in 0..self.css_props.len() {
834                for p in self.css_props.get_slice(i) {
835                    if p.state != PseudoStateType::Normal {
836                        nonnormal += 1;
837                    } else if p.prop_type.has_compact_encoding() {
838                        normal_compact += 1;
839                    } else {
840                        normal_noncompact += 1;
841                    }
842                }
843            }
844            let ssp_sz = core::mem::size_of::<StatefulCssProperty>();
845            let mut casc_normal_compact = 0usize;
846            let mut casc_total = 0usize;
847            for i in 0..self.cascaded_props.len() {
848                for p in self.cascaded_props.get_slice(i) {
849                    casc_total += 1;
850                    if p.state == PseudoStateType::Normal && p.prop_type.has_compact_encoding() {
851                        casc_normal_compact += 1;
852                    }
853                }
854            }
855            eprintln!("[PRUNE] css_props: norm+compact={} norm+other={} nonnorm={} SSP={}B | cascaded: total={} norm+compact={}",
856                normal_compact, normal_noncompact, nonnormal, ssp_sz, casc_total, casc_normal_compact);
857        }
858
859        // The compact cache stores SENTINEL for pixel-valued properties whose inner
860        // value is Exact with a non-px metric (vh, vw, %, em, rem, calc(), ...).
861        // Those need the slow `css_props` walk at layout time because the compact
862        // cache has nothing usable. We must keep them here or the slow path falls
863        // back to UA CSS and silently clobbers the author's rule.
864        let keep = |p: &StatefulCssProperty| -> bool {
865            if p.state != PseudoStateType::Normal {
866                return true;
867            }
868            if !p.prop_type.has_compact_encoding() {
869                return true;
870            }
871            // Compact-encoded AND Normal: drop only if the compact cache fully
872            // captured the value (px metric, or Auto/Initial/Inherit/None).
873            if property_needs_slow_path_after_compact(&p.property) {
874                return true;
875            }
876            false
877        };
878        self.css_props.retain(keep);
879        if !self.cascaded_props.is_flattened() {
880            self.cascaded_props.sort_each_and_flatten(|p| (p.state, p.prop_type));
881        }
882        self.cascaded_props.retain(keep);
883    }
884
885    /// Look up a CSS property for a specific pseudo-state in a stateful property vec.
886    /// Requires the vec to be sorted by (state, prop_type).
887    #[inline]
888    fn find_in_stateful<'a>(
889        props: &'a [StatefulCssProperty],
890        state: azul_css::dynamic_selector::PseudoStateType,
891        prop_type: &CssPropertyType,
892    ) -> Option<&'a CssProperty> {
893        let key = (state, *prop_type);
894        props.binary_search_by_key(&key, |p| (p.state, p.prop_type))
895            .ok()
896            .map(|idx| &props[idx].property)
897    }
898
899    /// Check if any properties exist for a specific pseudo-state in a stateful property vec.
900    /// Requires the vec to be sorted by (state, prop_type).
901    #[inline]
902    fn has_state_props(
903        props: &[StatefulCssProperty],
904        state: azul_css::dynamic_selector::PseudoStateType,
905    ) -> bool {
906        // All entries with the same state are contiguous. Use partition_point
907        // to find the first entry >= state, then check if it matches.
908        let i = props.partition_point(|p| p.state < state);
909        i < props.len() && props[i].state == state
910    }
911
912    /// Collect all property types for a specific pseudo-state.
913    pub(crate) fn prop_types_for_state<'a>(
914        props: &'a [StatefulCssProperty],
915        state: azul_css::dynamic_selector::PseudoStateType,
916    ) -> impl Iterator<Item = &'a CssPropertyType> + 'a {
917        props.iter().filter(move |p| p.state == state).map(|p| &p.prop_type)
918    }
919}
920
921/// Returns true if `prop`'s value cannot be fully represented in the compact
922/// cache and therefore needs to survive `prune_compact_normal_props` so the
923/// slow `css_props` walk can still find it at layout time.
924///
925/// Pixel-valued properties (margin, padding, width, height, ...) are the only
926/// case: `Exact(pv)` with `pv.metric != Px` (vh, vw, %, em, rem, ...) encodes
927/// to the compact cache's SENTINEL slot, which loses the value. All other
928/// compact-encoded types (tier1 enums, colors, hashes, etc.) always round-trip
929/// through the compact encoding.
930fn property_needs_slow_path_after_compact(prop: &CssProperty) -> bool {
931    use azul_css::css::CssPropertyValue;
932    use azul_css::props::{
933        basic::length::SizeMetric,
934        layout::{
935            dimensions::{LayoutHeight, LayoutWidth},
936            flex::LayoutFlexBasis,
937        },
938    };
939
940    // `inner: PixelValue` wrapper types — check metric directly.
941    macro_rules! check_plain {
942        ($v:expr) => {{
943            if let CssPropertyValue::Exact(ref inner) = $v {
944                return inner.inner.metric != SizeMetric::Px;
945            }
946            false
947        }};
948    }
949
950    match prop {
951        // LayoutWidth / LayoutHeight: enum with `Px(PixelValue)` variant.
952        // Non-pixel variants (Auto / MinContent / MaxContent / FitContent / Calc)
953        // are already handled by the tier1 fast path or don't exist as i16 dims.
954        CssProperty::Width(v) => {
955            if let CssPropertyValue::Exact(LayoutWidth::Px(pv)) = v {
956                return pv.metric != SizeMetric::Px;
957            }
958            false
959        }
960        CssProperty::Height(v) => {
961            if let CssPropertyValue::Exact(LayoutHeight::Px(pv)) = v {
962                return pv.metric != SizeMetric::Px;
963            }
964            false
965        }
966
967        // LayoutFlexBasis: enum with `Exact(PixelValue)` variant.
968        CssProperty::FlexBasis(v) => {
969            if let CssPropertyValue::Exact(LayoutFlexBasis::Exact(pv)) = v {
970                return pv.metric != SizeMetric::Px;
971            }
972            false
973        }
974
975        // `inner: PixelValue` wrappers
976        CssProperty::MinWidth(v) => check_plain!(v),
977        CssProperty::MaxWidth(v) => check_plain!(v),
978        CssProperty::MinHeight(v) => check_plain!(v),
979        CssProperty::MaxHeight(v) => check_plain!(v),
980        CssProperty::FontSize(v) => check_plain!(v),
981        CssProperty::PaddingTop(v) => check_plain!(v),
982        CssProperty::PaddingRight(v) => check_plain!(v),
983        CssProperty::PaddingBottom(v) => check_plain!(v),
984        CssProperty::PaddingLeft(v) => check_plain!(v),
985        CssProperty::MarginTop(v) => check_plain!(v),
986        CssProperty::MarginRight(v) => check_plain!(v),
987        CssProperty::MarginBottom(v) => check_plain!(v),
988        CssProperty::MarginLeft(v) => check_plain!(v),
989        CssProperty::BorderTopWidth(v) => check_plain!(v),
990        CssProperty::BorderRightWidth(v) => check_plain!(v),
991        CssProperty::BorderBottomWidth(v) => check_plain!(v),
992        CssProperty::BorderLeftWidth(v) => check_plain!(v),
993        CssProperty::Top(v) => check_plain!(v),
994        CssProperty::Right(v) => check_plain!(v),
995        CssProperty::Bottom(v) => check_plain!(v),
996        CssProperty::Left(v) => check_plain!(v),
997        CssProperty::ColumnGap(v) => check_plain!(v),
998        CssProperty::RowGap(v) => check_plain!(v),
999        CssProperty::LetterSpacing(v) => check_plain!(v),
1000        CssProperty::WordSpacing(v) => check_plain!(v),
1001        CssProperty::TextIndent(v) => check_plain!(v),
1002        CssProperty::TabSize(v) => check_plain!(v),
1003
1004        // All other compact-encoded types round-trip through the compact cache.
1005        _ => false,
1006    }
1007}
1008
1009impl CssPropertyCache {
1010    /// Match CSS selectors to nodes and populate css_props.
1011    /// Returns tag IDs for hit-testing. If compact_cache is available,
1012    /// uses it for fast display/overflow checks; otherwise falls back to slow path.
1013    #[must_use]
1014    pub fn restyle(
1015        &mut self,
1016        css: &mut Css,
1017        node_data: &NodeDataContainerRef<NodeData>,
1018        node_hierarchy: &NodeHierarchyItemVec,
1019        non_leaf_nodes: &ParentWithNodeDepthVec,
1020        html_tree: &NodeDataContainerRef<CascadeInfo>,
1021    ) -> Vec<TagIdToNodeIdMapping> {
1022        use azul_css::{
1023            css::{CssDeclaration, CssPathPseudoSelector::*},
1024            props::layout::LayoutDisplay,
1025        };
1026
1027        let css_is_empty = css.is_empty();
1028
1029        if !css_is_empty {
1030            css.sort_by_specificity();
1031
1032            // Separate CSS rules into "global only" (just `*`) vs "has specific selector".
1033            // Global-only rules apply to ALL nodes — push directly into css_props
1034            // without per-node selector matching (avoids m×n for these rules).
1035            // Specific rules still go through matches_html_element per-node.
1036            use azul_css::css::{CssPathSelector, CssRuleBlock};
1037
1038            let mut global_only_rules: Vec<&CssRuleBlock> = Vec::new();
1039            let mut specific_rules: Vec<&CssRuleBlock> = Vec::new();
1040
1041            for rule in css.rules() {
1042                let selectors = rule.path.selectors.as_ref();
1043                let is_global_only = selectors.len() == 1
1044                    && matches!(selectors.first(), Some(CssPathSelector::Global));
1045                if is_global_only {
1046                    global_only_rules.push(rule);
1047                } else {
1048                    specific_rules.push(rule);
1049                }
1050            }
1051
1052            // Clear all css_props before assigning
1053            for entry in self.css_props.build_iter_mut() { entry.clear(); }
1054
1055            use azul_css::dynamic_selector::PseudoStateType;
1056
1057            // Collect global-only rule declarations ONCE (not per-node).
1058            // These are stored in self.global_css_props and applied during
1059            // build_compact_cache_with_inheritance for each node, avoiding
1060            // 50K × N clones into per-node css_props Vecs.
1061            self.global_css_props.clear();
1062            for rule in &global_only_rules {
1063                if crate::style::rule_ends_with(&rule.path, None) {
1064                    for d in rule.declarations.iter() {
1065                        if let CssDeclaration::Static(s) = d {
1066                            self.global_css_props.push(s.clone());
1067                        }
1068                    }
1069                }
1070            }
1071
1072            // Phase 2: Match specific rules per-node (only non-global rules)
1073            if !specific_rules.is_empty() {
1074
1075            // Per-node "which declarations match" lists are built as
1076            // `(rule_idx, decl_idx)` pairs — 4 bytes per entry instead of
1077            // cloning a 140-byte `CssProperty`. The clone only happens at
1078            // the final push_to step, so the transient peak is ~35× smaller.
1079            //
1080            // rule_idx indexes into `specific_rules` (Vec<&CssRuleBlock>),
1081            // decl_idx indexes into `rule.declarations.as_slice()`. Both
1082            // fit in u16 since real stylesheets have far fewer than 65k
1083            // rules and declarations per rule.
1084            macro_rules! filter_rules {($expected_pseudo_selector:expr, $node_id:expr) => {{
1085                let mut out: Vec<(u16, u16)> = Vec::new();
1086                for (rule_idx, rule_block) in specific_rules.iter().enumerate() {
1087                    if !crate::style::rule_ends_with(&rule_block.path, $expected_pseudo_selector) {
1088                        continue;
1089                    }
1090                    if !crate::style::matches_html_element(
1091                        &rule_block.path,
1092                        $node_id,
1093                        &node_hierarchy.as_container(),
1094                        &node_data,
1095                        &html_tree,
1096                        $expected_pseudo_selector,
1097                    ) {
1098                        continue;
1099                    }
1100                    for (decl_idx, decl) in rule_block.declarations.as_slice().iter().enumerate() {
1101                        if matches!(decl, CssDeclaration::Static(_)) {
1102                            out.push((rule_idx as u16, decl_idx as u16));
1103                        }
1104                    }
1105                }
1106                out
1107            }};}
1108
1109            // Pre-check which pseudo-states have any matching rules at all.
1110            // This avoids iterating 50K nodes for pseudo-states with zero rules
1111            // (common: most stylesheets have no :hover/:focus/:active rules).
1112            let has_normal = specific_rules.iter().any(|r| crate::style::rule_ends_with(&r.path, None));
1113            let has_hover = specific_rules.iter().any(|r| crate::style::rule_ends_with(&r.path, Some(Hover)));
1114            let has_active = specific_rules.iter().any(|r| crate::style::rule_ends_with(&r.path, Some(Active)));
1115            let has_focus = specific_rules.iter().any(|r| crate::style::rule_ends_with(&r.path, Some(Focus)));
1116            let has_dragging = specific_rules.iter().any(|r| crate::style::rule_ends_with(&r.path, Some(Dragging)));
1117            let has_drag_over = specific_rules.iter().any(|r| crate::style::rule_ends_with(&r.path, Some(DragOver)));
1118
1119            macro_rules! collect_and_assign {
1120                ($pseudo:expr, $state:expr, $has_any:expr) => {
1121                    if $has_any {
1122                        let indices: NodeDataContainer<(NodeId, Vec<(u16, u16)>)> = node_data
1123                            .transform_nodeid_optional(|node_id| {
1124                                let r = filter_rules!($pseudo, node_id);
1125                                if r.is_empty() { None } else { Some((node_id, r)) }
1126                            });
1127                        for (n, pairs) in indices.internal.into_iter() {
1128                            for (rule_idx, decl_idx) in pairs {
1129                                let decl = &specific_rules[rule_idx as usize]
1130                                    .declarations
1131                                    .as_slice()[decl_idx as usize];
1132                                if let CssDeclaration::Static(prop) = decl {
1133                                    self.css_props.push_to(n.index(), StatefulCssProperty {
1134                                        state: $state,
1135                                        prop_type: prop.get_type(),
1136                                        property: prop.clone(),
1137                                    });
1138                                }
1139                            }
1140                        }
1141                    }
1142                };
1143            }
1144
1145            collect_and_assign!(None, PseudoStateType::Normal, has_normal);
1146            collect_and_assign!(Some(Hover), PseudoStateType::Hover, has_hover);
1147            collect_and_assign!(Some(Active), PseudoStateType::Active, has_active);
1148            collect_and_assign!(Some(Focus), PseudoStateType::Focus, has_focus);
1149            collect_and_assign!(Some(Dragging), PseudoStateType::Dragging, has_dragging);
1150            collect_and_assign!(Some(DragOver), PseudoStateType::DragOver, has_drag_over);
1151
1152            } // end if !specific_rules.is_empty()
1153        }
1154
1155        // Inheritance: Inherit all values of the parent to the children, but
1156        // only if the property is inheritable and isn't yet set
1157        for ParentWithNodeDepth { depth: _, node_id } in non_leaf_nodes.iter() {
1158            let parent_id = match node_id.into_crate_internal() {
1159                Some(s) => s,
1160                None => continue,
1161            };
1162
1163            use azul_css::dynamic_selector::{DynamicSelector, PseudoStateType};
1164
1165            let all_states = [
1166                PseudoStateType::Normal,
1167                PseudoStateType::Hover,
1168                PseudoStateType::Active,
1169                PseudoStateType::Focus,
1170                PseudoStateType::Dragging,
1171                PseudoStateType::DragOver,
1172            ];
1173
1174            for &state in &all_states {
1175                // 1. Inherit inline CSS properties from parent for this pseudo-state
1176                let parent_inheritable_inline: Vec<(CssPropertyType, CssProperty)> = node_data[parent_id]
1177                    .style
1178                    .iter_inline_properties()
1179                    .filter(|(_prop, conds)| {
1180                        let conditions = conds.as_slice();
1181                        if conditions.is_empty() {
1182                            state == PseudoStateType::Normal
1183                        } else {
1184                            conditions.iter().all(|c| {
1185                                matches!(c, DynamicSelector::PseudoState(s) if *s == state)
1186                            })
1187                        }
1188                    })
1189                    .map(|(prop, _)| prop)
1190                    .filter(|prop| prop.get_type().is_inheritable())
1191                    .map(|p| (p.get_type(), p.clone()))
1192                    .collect();
1193
1194                // 2. Inherit CSS stylesheet properties from parent for this pseudo-state
1195                let parent_inheritable_css: Vec<(CssPropertyType, CssProperty)> = if !css_is_empty {
1196                    self.css_props.get_slice(parent_id.index())
1197                        .iter()
1198                        .filter(|p| p.state == state && p.prop_type.is_inheritable())
1199                        .map(|p| (p.prop_type, p.property.clone()))
1200                        .collect()
1201                } else {
1202                    Vec::new()
1203                };
1204
1205                // 3. Inherit cascaded properties from parent for this pseudo-state
1206                let parent_inheritable_cascaded: Vec<(CssPropertyType, CssProperty)> =
1207                    self.cascaded_props.get_slice(parent_id.index())
1208                        .iter()
1209                        .filter(|p| p.state == state && p.prop_type.is_inheritable())
1210                        .map(|p| (p.prop_type, p.property.clone()))
1211                        .collect();
1212
1213                // Combine all inheritable props (inline first = strongest, cascaded last)
1214                // Only insert if child doesn't already have that (state, prop_type) combo
1215                if parent_inheritable_inline.is_empty()
1216                    && parent_inheritable_css.is_empty()
1217                    && parent_inheritable_cascaded.is_empty()
1218                {
1219                    continue;
1220                }
1221
1222                for child_id in parent_id.az_children(&node_hierarchy.as_container()) {
1223                    let child_vec = self.cascaded_props.build_mut(child_id.index());
1224                    for (prop_type, prop_value) in parent_inheritable_inline
1225                        .iter()
1226                        .chain(parent_inheritable_css.iter())
1227                        .chain(parent_inheritable_cascaded.iter())
1228                    {
1229                        // or_insert: only insert if child doesn't already have this (state, prop_type)
1230                        if !child_vec.iter().any(|p| p.state == state && p.prop_type == *prop_type) {
1231                            child_vec.push(StatefulCssProperty {
1232                                state,
1233                                prop_type: *prop_type,
1234                                property: prop_value.clone(),
1235                            });
1236                        }
1237                    }
1238                }
1239            }
1240        }
1241
1242        // Sort css_props by (state, prop_type) for binary search lookups,
1243        // then flatten into contiguous memory for cache-friendly reads.
1244        self.css_props.sort_each_and_flatten(|p| (p.state, p.prop_type));
1245
1246        self.generate_tag_ids(node_data, node_hierarchy)
1247    }
1248
1249    /// Generate hit-test tag IDs for nodes that need event handling.
1250    /// Uses compact cache (if available) for fast display/overflow reads.
1251    /// Can be called separately after build_compact_cache_with_inheritance.
1252    pub fn generate_tag_ids(
1253        &self,
1254        node_data: &NodeDataContainerRef<NodeData>,
1255        node_hierarchy: &NodeHierarchyItemVec,
1256    ) -> Vec<TagIdToNodeIdMapping> {
1257
1258        // Tag ID generation: determine which nodes need hit-test tags for
1259        // hover/click/scroll events. Uses compact cache for display/overflow
1260        // checks instead of get_property_slow (which searches 6 data structures).
1261        use azul_css::compact_cache::{
1262            DISPLAY_SHIFT, DISPLAY_MASK,
1263            OVERFLOW_X_SHIFT, OVERFLOW_Y_SHIFT, OVERFLOW_MASK,
1264        };
1265
1266        let compact_cache = self.compact_cache.as_ref();
1267        let node_data_container = &node_data.internal;
1268
1269        let tag_ids = node_data
1270            .internal
1271            .iter()
1272            .enumerate()
1273            .filter_map(|(node_idx, node_data)| {
1274                let node_id = NodeId::new(node_idx);
1275
1276                let should_auto_insert_tabindex = node_data
1277                    .get_callbacks()
1278                    .iter()
1279                    .any(|cb| cb.event.is_focus_callback());
1280
1281                let tab_index = match node_data.get_tab_index() {
1282                    Some(s) => Some(s),
1283                    None => {
1284                        if should_auto_insert_tabindex {
1285                            Some(TabIndex::Auto)
1286                        } else {
1287                            None
1288                        }
1289                    }
1290                };
1291
1292                let mut need_tag = false;
1293
1294                loop {
1295                    // display:none check — read directly from compact tier1 (fast u64 read)
1296                    if let Some(cc) = compact_cache.as_ref() {
1297                        let t1 = cc.tier1_enums[node_idx];
1298                        let display_val = ((t1 >> DISPLAY_SHIFT) & DISPLAY_MASK) as u8;
1299                        if display_val == 4 { break; } // 4 = LayoutDisplay::None (new encoding)
1300                    }
1301
1302                    if node_data.has_context_menu() || node_data.get_context_menu().is_some() {
1303                        need_tag = true; break;
1304                    }
1305                    if tab_index.is_some() { need_tag = true; break; }
1306
1307                    // Pseudo-state property checks (hover/active/focus/dragging/drag-over)
1308                    {
1309                        use azul_css::dynamic_selector::{DynamicSelector, PseudoStateType};
1310                        let has_pseudo = |state: PseudoStateType| -> bool {
1311                            node_data.style.iter_inline_properties().any(|(_p, conds)| {
1312                                conds.as_slice().iter().any(|c|
1313                                    matches!(c, DynamicSelector::PseudoState(s) if *s == state)
1314                                )
1315                            }) || Self::has_state_props(self.css_props.get_slice(node_idx), state)
1316                        };
1317
1318                        if has_pseudo(PseudoStateType::Hover)
1319                            || has_pseudo(PseudoStateType::Active)
1320                            || has_pseudo(PseudoStateType::Focus)
1321                            || has_pseudo(PseudoStateType::Dragging)
1322                            || has_pseudo(PseudoStateType::DragOver)
1323                        {
1324                            need_tag = true; break;
1325                        }
1326                    }
1327
1328                    // Non-window callbacks
1329                    let has_non_window_cb = !node_data.get_callbacks().is_empty()
1330                        && !node_data.get_callbacks().iter().all(|cb| cb.event.is_window_callback());
1331                    if has_non_window_cb { need_tag = true; break; }
1332
1333                    // Cursor check — read from cached css_props or inline style.
1334                    if self.css_props.get_slice(node_idx).iter().any(|p|
1335                        p.state == azul_css::dynamic_selector::PseudoStateType::Normal
1336                        && p.prop_type == azul_css::props::property::CssPropertyType::Cursor
1337                    ) || node_data.style.iter_inline_properties().any(|(p, _)|
1338                        p.get_type() == azul_css::props::property::CssPropertyType::Cursor
1339                    ) {
1340                        need_tag = true; break;
1341                    }
1342
1343                    // Overflow scroll check — read from compact tier1
1344                    if let Some(cc) = compact_cache.as_ref() {
1345                        let t1 = cc.tier1_enums[node_idx];
1346                        let ox = ((t1 >> OVERFLOW_X_SHIFT) & OVERFLOW_MASK) as u8;
1347                        let oy = ((t1 >> OVERFLOW_Y_SHIFT) & OVERFLOW_MASK) as u8;
1348                        // 2 = Scroll, 3 = Auto in layout_overflow_to_u8 (new encoding)
1349                        if ox == 2 || ox == 3 || oy == 2 || oy == 3 {
1350                            need_tag = true; break;
1351                        }
1352                    }
1353
1354                    // Selectable text check
1355                    {
1356                        use crate::dom::NodeType;
1357                        let hier = node_hierarchy.as_container()[node_id];
1358                        let mut has_text = false;
1359                        if let Some(first_child) = hier.first_child_id(node_id) {
1360                            let mut child_id = Some(first_child);
1361                            while let Some(cid) = child_id {
1362                                if matches!(node_data_container[cid.index()].get_node_type(), NodeType::Text(_)) {
1363                                    has_text = true; break;
1364                                }
1365                                child_id = node_hierarchy.as_container()[cid].next_sibling_id();
1366                            }
1367                        }
1368                        if has_text { need_tag = true; break; }
1369                    }
1370
1371                    break;
1372                }
1373
1374                if !need_tag {
1375                    None
1376                } else {
1377                    Some(TagIdToNodeIdMapping {
1378                        tag_id: TagId::from_crate_internal(TagId::unique()),
1379                        node_id: NodeHierarchyItemId::from_crate_internal(Some(node_id)),
1380                        tab_index: tab_index.into(),
1381                    })
1382                }
1383            })
1384            .collect::<Vec<_>>();
1385
1386        tag_ids
1387    }
1388
1389    pub fn get_computed_css_style_string(
1390        &self,
1391        node_data: &NodeData,
1392        node_id: &NodeId,
1393        node_state: &StyledNodeState,
1394    ) -> String {
1395        let mut s = String::new();
1396        if let Some(p) = self.get_background_content(&node_data, node_id, node_state) {
1397            s.push_str(&format!("background: {};", p.get_css_value_fmt()));
1398        }
1399        if let Some(p) = self.get_background_position(&node_data, node_id, node_state) {
1400            s.push_str(&format!("background-position: {};", p.get_css_value_fmt()));
1401        }
1402        if let Some(p) = self.get_background_size(&node_data, node_id, node_state) {
1403            s.push_str(&format!("background-size: {};", p.get_css_value_fmt()));
1404        }
1405        if let Some(p) = self.get_background_repeat(&node_data, node_id, node_state) {
1406            s.push_str(&format!("background-repeat: {};", p.get_css_value_fmt()));
1407        }
1408        if let Some(p) = self.get_font_size(&node_data, node_id, node_state) {
1409            s.push_str(&format!("font-size: {};", p.get_css_value_fmt()));
1410        }
1411        if let Some(p) = self.get_font_family(&node_data, node_id, node_state) {
1412            s.push_str(&format!("font-family: {};", p.get_css_value_fmt()));
1413        }
1414        if let Some(p) = self.get_text_color(&node_data, node_id, node_state) {
1415            s.push_str(&format!("color: {};", p.get_css_value_fmt()));
1416        }
1417        if let Some(p) = self.get_text_align(&node_data, node_id, node_state) {
1418            s.push_str(&format!("text-align: {};", p.get_css_value_fmt()));
1419        }
1420        if let Some(p) = self.get_line_height(&node_data, node_id, node_state) {
1421            s.push_str(&format!("line-height: {};", p.get_css_value_fmt()));
1422        }
1423        if let Some(p) = self.get_letter_spacing(&node_data, node_id, node_state) {
1424            s.push_str(&format!("letter-spacing: {};", p.get_css_value_fmt()));
1425        }
1426        if let Some(p) = self.get_word_spacing(&node_data, node_id, node_state) {
1427            s.push_str(&format!("word-spacing: {};", p.get_css_value_fmt()));
1428        }
1429        if let Some(p) = self.get_tab_size(&node_data, node_id, node_state) {
1430            s.push_str(&format!("tab-size: {};", p.get_css_value_fmt()));
1431        }
1432        if let Some(p) = self.get_cursor(&node_data, node_id, node_state) {
1433            s.push_str(&format!("cursor: {};", p.get_css_value_fmt()));
1434        }
1435        if let Some(p) = self.get_box_shadow_left(&node_data, node_id, node_state) {
1436            s.push_str(&format!(
1437                "-azul-box-shadow-left: {};",
1438                p.get_css_value_fmt()
1439            ));
1440        }
1441        if let Some(p) = self.get_box_shadow_right(&node_data, node_id, node_state) {
1442            s.push_str(&format!(
1443                "-azul-box-shadow-right: {};",
1444                p.get_css_value_fmt()
1445            ));
1446        }
1447        if let Some(p) = self.get_box_shadow_top(&node_data, node_id, node_state) {
1448            s.push_str(&format!("-azul-box-shadow-top: {};", p.get_css_value_fmt()));
1449        }
1450        if let Some(p) = self.get_box_shadow_bottom(&node_data, node_id, node_state) {
1451            s.push_str(&format!(
1452                "-azul-box-shadow-bottom: {};",
1453                p.get_css_value_fmt()
1454            ));
1455        }
1456        if let Some(p) = self.get_border_top_color(&node_data, node_id, node_state) {
1457            s.push_str(&format!("border-top-color: {};", p.get_css_value_fmt()));
1458        }
1459        if let Some(p) = self.get_border_left_color(&node_data, node_id, node_state) {
1460            s.push_str(&format!("border-left-color: {};", p.get_css_value_fmt()));
1461        }
1462        if let Some(p) = self.get_border_right_color(&node_data, node_id, node_state) {
1463            s.push_str(&format!("border-right-color: {};", p.get_css_value_fmt()));
1464        }
1465        if let Some(p) = self.get_border_bottom_color(&node_data, node_id, node_state) {
1466            s.push_str(&format!("border-bottom-color: {};", p.get_css_value_fmt()));
1467        }
1468        if let Some(p) = self.get_border_top_style(&node_data, node_id, node_state) {
1469            s.push_str(&format!("border-top-style: {};", p.get_css_value_fmt()));
1470        }
1471        if let Some(p) = self.get_border_left_style(&node_data, node_id, node_state) {
1472            s.push_str(&format!("border-left-style: {};", p.get_css_value_fmt()));
1473        }
1474        if let Some(p) = self.get_border_right_style(&node_data, node_id, node_state) {
1475            s.push_str(&format!("border-right-style: {};", p.get_css_value_fmt()));
1476        }
1477        if let Some(p) = self.get_border_bottom_style(&node_data, node_id, node_state) {
1478            s.push_str(&format!("border-bottom-style: {};", p.get_css_value_fmt()));
1479        }
1480        if let Some(p) = self.get_border_top_left_radius(&node_data, node_id, node_state) {
1481            s.push_str(&format!(
1482                "border-top-left-radius: {};",
1483                p.get_css_value_fmt()
1484            ));
1485        }
1486        if let Some(p) = self.get_border_top_right_radius(&node_data, node_id, node_state) {
1487            s.push_str(&format!(
1488                "border-top-right-radius: {};",
1489                p.get_css_value_fmt()
1490            ));
1491        }
1492        if let Some(p) = self.get_border_bottom_left_radius(&node_data, node_id, node_state) {
1493            s.push_str(&format!(
1494                "border-bottom-left-radius: {};",
1495                p.get_css_value_fmt()
1496            ));
1497        }
1498        if let Some(p) = self.get_border_bottom_right_radius(&node_data, node_id, node_state) {
1499            s.push_str(&format!(
1500                "border-bottom-right-radius: {};",
1501                p.get_css_value_fmt()
1502            ));
1503        }
1504        if let Some(p) = self.get_opacity(&node_data, node_id, node_state) {
1505            s.push_str(&format!("opacity: {};", p.get_css_value_fmt()));
1506        }
1507        if let Some(p) = self.get_transform(&node_data, node_id, node_state) {
1508            s.push_str(&format!("transform: {};", p.get_css_value_fmt()));
1509        }
1510        if let Some(p) = self.get_transform_origin(&node_data, node_id, node_state) {
1511            s.push_str(&format!("transform-origin: {};", p.get_css_value_fmt()));
1512        }
1513        if let Some(p) = self.get_perspective_origin(&node_data, node_id, node_state) {
1514            s.push_str(&format!("perspective-origin: {};", p.get_css_value_fmt()));
1515        }
1516        if let Some(p) = self.get_backface_visibility(&node_data, node_id, node_state) {
1517            s.push_str(&format!("backface-visibility: {};", p.get_css_value_fmt()));
1518        }
1519        if let Some(p) = self.get_hyphens(&node_data, node_id, node_state) {
1520            s.push_str(&format!("hyphens: {};", p.get_css_value_fmt()));
1521        }
1522        if let Some(p) = self.get_direction(&node_data, node_id, node_state) {
1523            s.push_str(&format!("direction: {};", p.get_css_value_fmt()));
1524        }
1525        if let Some(p) = self.get_unicode_bidi(&node_data, node_id, node_state) {
1526            s.push_str(&format!("unicode-bidi: {};", p.get_css_value_fmt()));
1527        }
1528        if let Some(p) = self.get_text_box_trim(&node_data, node_id, node_state) {
1529            s.push_str(&format!("text-box-trim: {};", p.get_css_value_fmt()));
1530        }
1531        if let Some(p) = self.get_text_box_edge(&node_data, node_id, node_state) {
1532            s.push_str(&format!("text-box-edge: {};", p.get_css_value_fmt()));
1533        }
1534        if let Some(p) = self.get_dominant_baseline(&node_data, node_id, node_state) {
1535            s.push_str(&format!("dominant-baseline: {};", p.get_css_value_fmt()));
1536        }
1537        if let Some(p) = self.get_alignment_baseline(&node_data, node_id, node_state) {
1538            s.push_str(&format!("alignment-baseline: {};", p.get_css_value_fmt()));
1539        }
1540        if let Some(p) = self.get_initial_letter_align(&node_data, node_id, node_state) {
1541            s.push_str(&format!("initial-letter-align: {};", p.get_css_value_fmt()));
1542        }
1543        if let Some(p) = self.get_initial_letter_wrap(&node_data, node_id, node_state) {
1544            s.push_str(&format!("initial-letter-wrap: {};", p.get_css_value_fmt()));
1545        }
1546        if let Some(p) = self.get_scrollbar_gutter(&node_data, node_id, node_state) {
1547            s.push_str(&format!("scrollbar-gutter: {};", p.get_css_value_fmt()));
1548        }
1549        if let Some(p) = self.get_overflow_clip_margin(&node_data, node_id, node_state) {
1550            s.push_str(&format!("overflow-clip-margin: {};", p.get_css_value_fmt()));
1551        }
1552        if let Some(p) = self.get_clip(&node_data, node_id, node_state) {
1553            s.push_str(&format!("clip: {};", p.get_css_value_fmt()));
1554        }
1555        if let Some(p) = self.get_white_space(&node_data, node_id, node_state) {
1556            s.push_str(&format!("white-space: {};", p.get_css_value_fmt()));
1557        }
1558        if let Some(p) = self.get_display(&node_data, node_id, node_state) {
1559            s.push_str(&format!("display: {};", p.get_css_value_fmt()));
1560        }
1561        if let Some(p) = self.get_float(&node_data, node_id, node_state) {
1562            s.push_str(&format!("float: {};", p.get_css_value_fmt()));
1563        }
1564        if let Some(p) = self.get_box_sizing(&node_data, node_id, node_state) {
1565            s.push_str(&format!("box-sizing: {};", p.get_css_value_fmt()));
1566        }
1567        if let Some(p) = self.get_width(&node_data, node_id, node_state) {
1568            s.push_str(&format!("width: {};", p.get_css_value_fmt()));
1569        }
1570        if let Some(p) = self.get_height(&node_data, node_id, node_state) {
1571            s.push_str(&format!("height: {};", p.get_css_value_fmt()));
1572        }
1573        if let Some(p) = self.get_min_width(&node_data, node_id, node_state) {
1574            s.push_str(&format!("min-width: {};", p.get_css_value_fmt()));
1575        }
1576        if let Some(p) = self.get_min_height(&node_data, node_id, node_state) {
1577            s.push_str(&format!("min-height: {};", p.get_css_value_fmt()));
1578        }
1579        if let Some(p) = self.get_max_width(&node_data, node_id, node_state) {
1580            s.push_str(&format!("max-width: {};", p.get_css_value_fmt()));
1581        }
1582        if let Some(p) = self.get_max_height(&node_data, node_id, node_state) {
1583            s.push_str(&format!("max-height: {};", p.get_css_value_fmt()));
1584        }
1585        if let Some(p) = self.get_position(&node_data, node_id, node_state) {
1586            s.push_str(&format!("position: {};", p.get_css_value_fmt()));
1587        }
1588        if let Some(p) = self.get_top(&node_data, node_id, node_state) {
1589            s.push_str(&format!("top: {};", p.get_css_value_fmt()));
1590        }
1591        if let Some(p) = self.get_bottom(&node_data, node_id, node_state) {
1592            s.push_str(&format!("bottom: {};", p.get_css_value_fmt()));
1593        }
1594        if let Some(p) = self.get_right(&node_data, node_id, node_state) {
1595            s.push_str(&format!("right: {};", p.get_css_value_fmt()));
1596        }
1597        if let Some(p) = self.get_left(&node_data, node_id, node_state) {
1598            s.push_str(&format!("left: {};", p.get_css_value_fmt()));
1599        }
1600        if let Some(p) = self.get_padding_top(&node_data, node_id, node_state) {
1601            s.push_str(&format!("padding-top: {};", p.get_css_value_fmt()));
1602        }
1603        if let Some(p) = self.get_padding_bottom(&node_data, node_id, node_state) {
1604            s.push_str(&format!("padding-bottom: {};", p.get_css_value_fmt()));
1605        }
1606        if let Some(p) = self.get_padding_left(&node_data, node_id, node_state) {
1607            s.push_str(&format!("padding-left: {};", p.get_css_value_fmt()));
1608        }
1609        if let Some(p) = self.get_padding_right(&node_data, node_id, node_state) {
1610            s.push_str(&format!("padding-right: {};", p.get_css_value_fmt()));
1611        }
1612        if let Some(p) = self.get_margin_top(&node_data, node_id, node_state) {
1613            s.push_str(&format!("margin-top: {};", p.get_css_value_fmt()));
1614        }
1615        if let Some(p) = self.get_margin_bottom(&node_data, node_id, node_state) {
1616            s.push_str(&format!("margin-bottom: {};", p.get_css_value_fmt()));
1617        }
1618        if let Some(p) = self.get_margin_left(&node_data, node_id, node_state) {
1619            s.push_str(&format!("margin-left: {};", p.get_css_value_fmt()));
1620        }
1621        if let Some(p) = self.get_margin_right(&node_data, node_id, node_state) {
1622            s.push_str(&format!("margin-right: {};", p.get_css_value_fmt()));
1623        }
1624        if let Some(p) = self.get_border_top_width(&node_data, node_id, node_state) {
1625            s.push_str(&format!("border-top-width: {};", p.get_css_value_fmt()));
1626        }
1627        if let Some(p) = self.get_border_left_width(&node_data, node_id, node_state) {
1628            s.push_str(&format!("border-left-width: {};", p.get_css_value_fmt()));
1629        }
1630        if let Some(p) = self.get_border_right_width(&node_data, node_id, node_state) {
1631            s.push_str(&format!("border-right-width: {};", p.get_css_value_fmt()));
1632        }
1633        if let Some(p) = self.get_border_bottom_width(&node_data, node_id, node_state) {
1634            s.push_str(&format!("border-bottom-width: {};", p.get_css_value_fmt()));
1635        }
1636        if let Some(p) = self.get_overflow_x(&node_data, node_id, node_state) {
1637            s.push_str(&format!("overflow-x: {};", p.get_css_value_fmt()));
1638        }
1639        if let Some(p) = self.get_overflow_y(&node_data, node_id, node_state) {
1640            s.push_str(&format!("overflow-y: {};", p.get_css_value_fmt()));
1641        }
1642        if let Some(p) = self.get_flex_direction(&node_data, node_id, node_state) {
1643            s.push_str(&format!("flex-direction: {};", p.get_css_value_fmt()));
1644        }
1645        if let Some(p) = self.get_flex_wrap(&node_data, node_id, node_state) {
1646            s.push_str(&format!("flex-wrap: {};", p.get_css_value_fmt()));
1647        }
1648        if let Some(p) = self.get_flex_grow(&node_data, node_id, node_state) {
1649            s.push_str(&format!("flex-grow: {};", p.get_css_value_fmt()));
1650        }
1651        if let Some(p) = self.get_flex_shrink(&node_data, node_id, node_state) {
1652            s.push_str(&format!("flex-shrink: {};", p.get_css_value_fmt()));
1653        }
1654        if let Some(p) = self.get_justify_content(&node_data, node_id, node_state) {
1655            s.push_str(&format!("justify-content: {};", p.get_css_value_fmt()));
1656        }
1657        if let Some(p) = self.get_align_items(&node_data, node_id, node_state) {
1658            s.push_str(&format!("align-items: {};", p.get_css_value_fmt()));
1659        }
1660        if let Some(p) = self.get_align_content(&node_data, node_id, node_state) {
1661            s.push_str(&format!("align-content: {};", p.get_css_value_fmt()));
1662        }
1663        s
1664    }
1665}
1666
1667#[repr(C)]
1668#[derive(Debug, PartialEq, Clone)]
1669pub struct CssPropertyCachePtr {
1670    pub ptr: Box<CssPropertyCache>,
1671    pub run_destructor: bool,
1672}
1673
1674impl CssPropertyCachePtr {
1675    pub fn new(cache: CssPropertyCache) -> Self {
1676        Self {
1677            ptr: Box::new(cache),
1678            run_destructor: true,
1679        }
1680    }
1681    pub fn downcast_mut<'a>(&'a mut self) -> &'a mut CssPropertyCache {
1682        &mut *self.ptr
1683    }
1684}
1685
1686impl Drop for CssPropertyCachePtr {
1687    fn drop(&mut self) {
1688        self.run_destructor = false;
1689    }
1690}
1691
1692impl CssPropertyCache {
1693    pub fn empty(node_count: usize) -> Self {
1694        Self {
1695            node_count,
1696            user_overridden_properties: Vec::new(),
1697
1698            cascaded_props: FlatVecVec::new(node_count),
1699            css_props: FlatVecVec::new(node_count),
1700
1701            computed_values: Vec::new(),
1702            compact_cache: None,
1703            global_css_props: Vec::new(),
1704            resolved_font_sizes_px: std::sync::OnceLock::new(),
1705        }
1706    }
1707
1708    /// Clear the lazily-populated font-size cache. Call after any
1709    /// mutation that could change resolved font-sizes (restyle,
1710    /// DOM mutation, `append`, etc.). The next
1711    /// [`crate::styled_dom::StyledDom::resolved_font_size_px`] call
1712    /// repopulates via a single bottom-up tree walk.
1713    pub fn invalidate_resolved_font_sizes(&mut self) {
1714        self.resolved_font_sizes_px = std::sync::OnceLock::new();
1715    }
1716
1717    pub fn append(&mut self, other: &mut Self) {
1718        self.user_overridden_properties.extend(other.user_overridden_properties.drain(..));
1719        self.cascaded_props.extend_from(&mut other.cascaded_props);
1720        self.css_props.extend_from(&mut other.css_props);
1721        self.computed_values.extend(other.computed_values.drain(..));
1722
1723        self.node_count += other.node_count;
1724        // Indices shifted — invalidate the font-size cache too.
1725        self.resolved_font_sizes_px = std::sync::OnceLock::new();
1726
1727        // Invalidate compact cache since node IDs shifted
1728        self.compact_cache = None;
1729    }
1730
1731    pub fn is_horizontal_overflow_visible(
1732        &self,
1733        node_data: &NodeData,
1734        node_id: &NodeId,
1735        node_state: &StyledNodeState,
1736    ) -> bool {
1737        self.get_overflow_x(node_data, node_id, node_state)
1738            .and_then(|p| p.get_property_or_default())
1739            .unwrap_or_default()
1740            .is_overflow_visible()
1741    }
1742
1743    pub fn is_vertical_overflow_visible(
1744        &self,
1745        node_data: &NodeData,
1746        node_id: &NodeId,
1747        node_state: &StyledNodeState,
1748    ) -> bool {
1749        self.get_overflow_y(node_data, node_id, node_state)
1750            .and_then(|p| p.get_property_or_default())
1751            .unwrap_or_default()
1752            .is_overflow_visible()
1753    }
1754
1755    pub fn is_horizontal_overflow_hidden(
1756        &self,
1757        node_data: &NodeData,
1758        node_id: &NodeId,
1759        node_state: &StyledNodeState,
1760    ) -> bool {
1761        self.get_overflow_x(node_data, node_id, node_state)
1762            .and_then(|p| p.get_property_or_default())
1763            .unwrap_or_default()
1764            .is_overflow_hidden()
1765    }
1766
1767    pub fn is_vertical_overflow_hidden(
1768        &self,
1769        node_data: &NodeData,
1770        node_id: &NodeId,
1771        node_state: &StyledNodeState,
1772    ) -> bool {
1773        self.get_overflow_y(node_data, node_id, node_state)
1774            .and_then(|p| p.get_property_or_default())
1775            .unwrap_or_default()
1776            .is_overflow_hidden()
1777    }
1778
1779    pub fn get_text_color_or_default(
1780        &self,
1781        node_data: &NodeData,
1782        node_id: &NodeId,
1783        node_state: &StyledNodeState,
1784    ) -> StyleTextColor {
1785        use azul_css::defaults::DEFAULT_TEXT_COLOR;
1786        self.get_text_color(node_data, node_id, node_state)
1787            .and_then(|fs| fs.get_property().cloned())
1788            .unwrap_or(DEFAULT_TEXT_COLOR)
1789    }
1790
1791    /// Returns the font family of the node, or the default font family if none is set.
1792    pub fn get_font_id_or_default(
1793        &self,
1794        node_data: &NodeData,
1795        node_id: &NodeId,
1796        node_state: &StyledNodeState,
1797    ) -> StyleFontFamilyVec {
1798        use azul_css::defaults::DEFAULT_FONT_ID;
1799        let default_font_id = vec![StyleFontFamily::System(AzString::from_const_str(
1800            DEFAULT_FONT_ID,
1801        ))]
1802        .into();
1803        let font_family_opt = self.get_font_family(node_data, node_id, node_state);
1804
1805        font_family_opt
1806            .as_ref()
1807            .and_then(|family| Some(family.get_property()?.clone()))
1808            .unwrap_or(default_font_id)
1809    }
1810
1811    pub fn get_font_size_or_default(
1812        &self,
1813        node_data: &NodeData,
1814        node_id: &NodeId,
1815        node_state: &StyledNodeState,
1816    ) -> StyleFontSize {
1817        use azul_css::defaults::DEFAULT_FONT_SIZE;
1818        self.get_font_size(node_data, node_id, node_state)
1819            .and_then(|fs| fs.get_property().cloned())
1820            .unwrap_or(DEFAULT_FONT_SIZE)
1821    }
1822
1823    pub fn has_border(
1824        &self,
1825        node_data: &NodeData,
1826        node_id: &NodeId,
1827        node_state: &StyledNodeState,
1828    ) -> bool {
1829        self.get_border_left_width(node_data, node_id, node_state)
1830            .is_some()
1831            || self
1832                .get_border_right_width(node_data, node_id, node_state)
1833                .is_some()
1834            || self
1835                .get_border_top_width(node_data, node_id, node_state)
1836                .is_some()
1837            || self
1838                .get_border_bottom_width(node_data, node_id, node_state)
1839                .is_some()
1840    }
1841
1842    pub fn has_box_shadow(
1843        &self,
1844        node_data: &NodeData,
1845        node_id: &NodeId,
1846        node_state: &StyledNodeState,
1847    ) -> bool {
1848        self.get_box_shadow_left(node_data, node_id, node_state)
1849            .is_some()
1850            || self
1851                .get_box_shadow_right(node_data, node_id, node_state)
1852                .is_some()
1853            || self
1854                .get_box_shadow_top(node_data, node_id, node_state)
1855                .is_some()
1856            || self
1857                .get_box_shadow_bottom(node_data, node_id, node_state)
1858                .is_some()
1859    }
1860
1861    pub fn get_property<'a>(
1862        &'a self,
1863        node_data: &'a NodeData,
1864        node_id: &NodeId,
1865        node_state: &StyledNodeState,
1866        css_property_type: &CssPropertyType,
1867    ) -> Option<&CssProperty> {
1868        // Thread-local counter of cascade walks, broken down by
1869        // property type. Drain with `drain_css_prop_counts` (free
1870        // fn below) when `AZ_PROP_COUNT=1` is set to see which
1871        // properties dominate the cold layout path.
1872        //
1873        // Env check is read ONCE at process start and cached in a
1874        // `OnceLock<bool>`. Before this, the env check ran per
1875        // `get_property` call — and the function fires 710k+ times
1876        // per cold layout on excel.html. `std::env::var_os` takes
1877        // ~100 ns per call on macOS (env lock + hashmap lookup), so
1878        // the naive check added ~70 ms of pure noise to every
1879        // single layout, regardless of whether the env var was set.
1880        // Using a one-time cached bool removes that overhead.
1881        static PROP_COUNT_ENABLED: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1882        let enabled = *PROP_COUNT_ENABLED.get_or_init(crate::profile::cascade_enabled);
1883        if enabled {
1884            // `try_with` (not `with`): the lifted-to-wasm web backend has no
1885            // real TLS, so `with` would hit `panic_access_error` (the layout
1886            // path reads CSS props via these getters → would trap). `try_with`
1887            // returns Err and we skip the profiling-only increment (and its
1888            // inner Mutex-guarded label table). Desktop behaviour unchanged —
1889            // when the env var is unset the whole block is gated off anyway.
1890            let _ = PROP_COUNTS.try_with(|c| {
1891                *c.borrow_mut()
1892                    .entry(Self::css_prop_type_label(css_property_type))
1893                    .or_insert(0) += 1;
1894            });
1895        }
1896
1897        // Always use full cascade resolution.
1898        // Tier 1/2/2b handle layout-hot properties via direct typed getters.
1899        // This path is only used for paint-time reads (background, shadow, etc.)
1900        self.get_property_slow(node_data, node_id, node_state, css_property_type)
1901    }
1902
1903    fn css_prop_type_label(t: &CssPropertyType) -> &'static str {
1904        // Intern Debug-format labels under a mutex-guarded map so
1905        // we leak at most one `&'static str` per distinct
1906        // `CssPropertyType` variant (bounded at ≤ 178 total). Only
1907        // triggered when `AZ_PROP_COUNT=1`, so zero cost normally.
1908        use std::sync::{Mutex, OnceLock};
1909        static TABLE: OnceLock<Mutex<std::collections::HashMap<CssPropertyType, &'static str>>> =
1910            OnceLock::new();
1911        let m = TABLE.get_or_init(|| Mutex::new(std::collections::HashMap::new()));
1912        let mut g = m.lock().expect("AZ_PROP_COUNT label table poisoned");
1913        if let Some(s) = g.get(t) {
1914            return *s;
1915        }
1916        let s: String = std::format!("{:?}", t);
1917        let leaked: &'static str = std::boxed::Box::leak(s.into_boxed_str());
1918        g.insert(*t, leaked);
1919        leaked
1920    }
1921
1922    /// Full cascade resolution for any CSS property type.
1923    /// Walks all cascade layers: user overrides → inline → stylesheet → cascaded → computed → UA.
1924    /// Also used by restyle functions that need state-aware lookups.
1925    pub(crate) fn get_property_slow<'a>(
1926        &'a self,
1927        node_data: &'a NodeData,
1928        node_id: &NodeId,
1929        node_state: &StyledNodeState,
1930        css_property_type: &CssPropertyType,
1931    ) -> Option<&CssProperty> {
1932
1933        use azul_css::dynamic_selector::{DynamicSelector, PseudoStateType};
1934
1935        // First test if there is some user-defined override for the property
1936        if let Some(v) = self.user_overridden_properties.get(node_id.index()) {
1937            if let Ok(idx) = v.binary_search_by_key(css_property_type, |(k, _)| *k) {
1938                return Some(&v[idx].1);
1939            }
1940        }
1941
1942        // Helper: do these conditions identify a rule that applies in `state`?
1943        // Empty conditions = Normal-only. Otherwise all conditions must be
1944        // PseudoState(state).
1945        fn matches_pseudo_state(
1946            conds: &azul_css::dynamic_selector::DynamicSelectorVec,
1947            state: PseudoStateType,
1948        ) -> bool {
1949            let conditions = conds.as_slice();
1950            if conditions.is_empty() {
1951                state == PseudoStateType::Normal
1952            } else {
1953                conditions
1954                    .iter()
1955                    .all(|c| matches!(c, DynamicSelector::PseudoState(s) if *s == state))
1956            }
1957        }
1958
1959        // If that fails, see if there is an inline CSS property that matches
1960        // :focus > :active > :hover > normal (fallback)
1961        if node_state.focused {
1962            // PRIORITY 1: Inline CSS properties (highest priority per CSS spec)
1963            if let Some(p) = node_data.style.iter_inline_properties().find_map(|(prop, conds)| {
1964                if matches_pseudo_state(conds,PseudoStateType::Focus)
1965                    && prop.get_type() == *css_property_type
1966                {
1967                    Some(prop)
1968                } else {
1969                    None
1970                }
1971            }) {
1972                return Some(p);
1973            }
1974
1975            // PRIORITY 2: CSS stylesheet properties
1976            if let Some(p) = Self::find_in_stateful(
1977                self.css_props.get_slice(node_id.index()),
1978                PseudoStateType::Focus,
1979                css_property_type,
1980            ) {
1981                return Some(p);
1982            }
1983
1984            // PRIORITY 3: Cascaded/inherited properties
1985            if let Some(p) = Self::find_in_stateful(
1986                self.cascaded_props.get_slice(node_id.index()),
1987                PseudoStateType::Focus,
1988                css_property_type,
1989            ) {
1990                return Some(p);
1991            }
1992        }
1993
1994        if node_state.active {
1995            // PRIORITY 1: Inline CSS properties (highest priority per CSS spec)
1996            if let Some(p) = node_data.style.iter_inline_properties().find_map(|(prop, conds)| {
1997                if matches_pseudo_state(conds,PseudoStateType::Active)
1998                    && prop.get_type() == *css_property_type
1999                {
2000                    Some(prop)
2001                } else {
2002                    None
2003                }
2004            }) {
2005                return Some(p);
2006            }
2007
2008            // PRIORITY 2: CSS stylesheet properties
2009            if let Some(p) = Self::find_in_stateful(
2010                self.css_props.get_slice(node_id.index()),
2011                PseudoStateType::Active,
2012                css_property_type,
2013            ) {
2014                return Some(p);
2015            }
2016
2017            // PRIORITY 3: Cascaded/inherited properties
2018            if let Some(p) = Self::find_in_stateful(
2019                self.cascaded_props.get_slice(node_id.index()),
2020                PseudoStateType::Active,
2021                css_property_type,
2022            ) {
2023                return Some(p);
2024            }
2025        }
2026
2027        // :dragging pseudo-state (higher priority than :hover)
2028        if node_state.dragging {
2029            if let Some(p) = node_data.style.iter_inline_properties().find_map(|(prop, conds)| {
2030                if matches_pseudo_state(conds,PseudoStateType::Dragging)
2031                    && prop.get_type() == *css_property_type
2032                {
2033                    Some(prop)
2034                } else {
2035                    None
2036                }
2037            }) {
2038                return Some(p);
2039            }
2040
2041            if let Some(p) = Self::find_in_stateful(
2042                self.css_props.get_slice(node_id.index()),
2043                PseudoStateType::Dragging,
2044                css_property_type,
2045            ) {
2046                return Some(p);
2047            }
2048
2049            if let Some(p) = Self::find_in_stateful(
2050                self.cascaded_props.get_slice(node_id.index()),
2051                PseudoStateType::Dragging,
2052                css_property_type,
2053            ) {
2054                return Some(p);
2055            }
2056        }
2057
2058        // :drag-over pseudo-state (higher priority than :hover)
2059        if node_state.drag_over {
2060            if let Some(p) = node_data.style.iter_inline_properties().find_map(|(prop, conds)| {
2061                if matches_pseudo_state(conds,PseudoStateType::DragOver)
2062                    && prop.get_type() == *css_property_type
2063                {
2064                    Some(prop)
2065                } else {
2066                    None
2067                }
2068            }) {
2069                return Some(p);
2070            }
2071
2072            if let Some(p) = Self::find_in_stateful(
2073                self.css_props.get_slice(node_id.index()),
2074                PseudoStateType::DragOver,
2075                css_property_type,
2076            ) {
2077                return Some(p);
2078            }
2079
2080            if let Some(p) = Self::find_in_stateful(
2081                self.cascaded_props.get_slice(node_id.index()),
2082                PseudoStateType::DragOver,
2083                css_property_type,
2084            ) {
2085                return Some(p);
2086            }
2087        }
2088
2089        if node_state.hover {
2090            // PRIORITY 1: Inline CSS properties (highest priority per CSS spec)
2091            if let Some(p) = node_data.style.iter_inline_properties().find_map(|(prop, conds)| {
2092                if matches_pseudo_state(conds,PseudoStateType::Hover)
2093                    && prop.get_type() == *css_property_type
2094                {
2095                    Some(prop)
2096                } else {
2097                    None
2098                }
2099            }) {
2100                return Some(p);
2101            }
2102
2103            // PRIORITY 2: CSS stylesheet properties
2104            if let Some(p) = Self::find_in_stateful(
2105                self.css_props.get_slice(node_id.index()),
2106                PseudoStateType::Hover,
2107                css_property_type,
2108            ) {
2109                return Some(p);
2110            }
2111
2112            // PRIORITY 3: Cascaded/inherited properties
2113            if let Some(p) = Self::find_in_stateful(
2114                self.cascaded_props.get_slice(node_id.index()),
2115                PseudoStateType::Hover,
2116                css_property_type,
2117            ) {
2118                return Some(p);
2119            }
2120        }
2121
2122        // Normal/fallback properties - always apply as base layer
2123        // PRIORITY 1: Inline CSS properties (highest priority per CSS spec)
2124        if let Some(p) = node_data.style.iter_inline_properties().find_map(|(prop, conds)| {
2125            if matches_pseudo_state(conds, PseudoStateType::Normal)
2126                && prop.get_type() == *css_property_type
2127            {
2128                Some(prop)
2129            } else {
2130                None
2131            }
2132        }) {
2133            return Some(p);
2134        }
2135
2136        // PRIORITY 2: CSS stylesheet properties
2137        if let Some(p) = Self::find_in_stateful(
2138            self.css_props.get_slice(node_id.index()),
2139            PseudoStateType::Normal,
2140            css_property_type,
2141        ) {
2142            return Some(p);
2143        }
2144
2145        // PRIORITY 2b: Global `*` selector properties (specificity 0,0,0)
2146        // These are collected once during restyle and apply to all nodes.
2147        // Lower priority than per-node rules but higher than inheritance/UA.
2148        if let Some(p) = self.global_css_props.iter().find(|p| p.get_type() == *css_property_type) {
2149            return Some(p);
2150        }
2151
2152        // PRIORITY 3: Cascaded/inherited properties
2153        if let Some(p) = Self::find_in_stateful(
2154            self.cascaded_props.get_slice(node_id.index()),
2155            PseudoStateType::Normal,
2156            css_property_type,
2157        ) {
2158            return Some(p);
2159        }
2160
2161        // Check computed values cache for inherited properties
2162        // Sorted Vec with binary search
2163        if css_property_type.is_inheritable() {
2164            if let Some(vec) = self.computed_values.get(node_id.index()) {
2165                if let Ok(idx) = vec.binary_search_by_key(css_property_type, |(k, _)| *k) {
2166                    return Some(&vec[idx].1.property);
2167                }
2168            }
2169        }
2170
2171        // User-agent stylesheet fallback (lowest precedence)
2172        // Check if the node type has a default value for this property
2173        crate::ua_css::get_ua_property(&node_data.node_type, *css_property_type)
2174    }
2175
2176    /// Get a CSS property using DynamicSelectorContext for evaluation.
2177    ///
2178    /// This is the new API that supports @media queries, @container queries,
2179    /// OS-specific styles, and all pseudo-states via `CssPropertyWithConditions`.
2180    ///
2181    /// The evaluation follows "last wins" semantics - properties are evaluated
2182    /// in reverse order and the first matching property wins.
2183    pub(crate) fn get_property_with_context<'a>(
2184        &'a self,
2185        node_data: &'a NodeData,
2186        node_id: &NodeId,
2187        context: &DynamicSelectorContext,
2188        css_property_type: &CssPropertyType,
2189    ) -> Option<&CssProperty> {
2190        // First test if there is some user-defined override for the property
2191        if let Some(v) = self.user_overridden_properties.get(node_id.index()) {
2192            if let Ok(idx) = v.binary_search_by_key(css_property_type, |(k, _)| *k) {
2193                return Some(&v[idx].1);
2194            }
2195        }
2196
2197        // Check inline CSS properties with DynamicSelectorContext evaluation.
2198        // Iterate in REVERSE order across the flat (prop, conds) view —
2199        // "last found wins" semantics, replacing the old Focus > Active >
2200        // Hover > Normal priority chain.
2201        let inline_props_rev: Vec<_> = node_data
2202            .style
2203            .iter_inline_properties()
2204            .collect::<Vec<_>>();
2205        if let Some(prop) = inline_props_rev.into_iter().rev().find_map(|(prop, conds)| {
2206            let conditions_match = conds.as_slice().iter().all(|c| c.matches(context));
2207            if prop.get_type() == *css_property_type && conditions_match {
2208                Some(prop)
2209            } else {
2210                None
2211            }
2212        }) {
2213            return Some(prop);
2214        }
2215
2216        // Fall back to CSS file and cascaded properties
2217        let legacy_state = StyledNodeState::from_pseudo_state_flags(&context.pseudo_state);
2218        if let Some(p) = self.get_property(node_data, node_id, &legacy_state, css_property_type) {
2219            return Some(p);
2220        }
2221
2222        None
2223    }
2224
2225    /// Check if any properties with conditions would change between two contexts.
2226    /// This is used for re-layout detection on viewport/container resize.
2227    pub(crate) fn check_properties_changed(
2228        node_data: &NodeData,
2229        old_context: &DynamicSelectorContext,
2230        new_context: &DynamicSelectorContext,
2231    ) -> bool {
2232        for (_prop, conds) in node_data.style.iter_inline_properties() {
2233            let was_active = conds.as_slice().iter().all(|c| c.matches(old_context));
2234            let is_active = conds.as_slice().iter().all(|c| c.matches(new_context));
2235            if was_active != is_active {
2236                return true;
2237            }
2238        }
2239        false
2240    }
2241
2242    /// Check if any layout-affecting properties would change between two contexts.
2243    /// This is a more targeted check for re-layout detection.
2244    pub(crate) fn check_layout_properties_changed(
2245        node_data: &NodeData,
2246        old_context: &DynamicSelectorContext,
2247        new_context: &DynamicSelectorContext,
2248    ) -> bool {
2249        for (prop, conds) in node_data.style.iter_inline_properties() {
2250            // Skip non-layout-affecting properties
2251            if !prop.get_type().can_trigger_relayout() {
2252                continue;
2253            }
2254
2255            let was_active = conds.as_slice().iter().all(|c| c.matches(old_context));
2256            let is_active = conds.as_slice().iter().all(|c| c.matches(new_context));
2257            if was_active != is_active {
2258                return true;
2259            }
2260        }
2261        false
2262    }
2263
2264    pub fn get_background_content<'a>(
2265        &'a self,
2266        node_data: &'a NodeData,
2267        node_id: &NodeId,
2268        node_state: &StyledNodeState,
2269    ) -> Option<&'a StyleBackgroundContentVecValue> {
2270        self.get_property(
2271            node_data,
2272            node_id,
2273            node_state,
2274            &CssPropertyType::BackgroundContent,
2275        )
2276        .and_then(|p| p.as_background_content())
2277    }
2278
2279    /// Method for getting hyphens property
2280    pub fn get_hyphens<'a>(
2281        &'a self,
2282        node_data: &'a NodeData,
2283        node_id: &NodeId,
2284        node_state: &StyledNodeState,
2285    ) -> Option<&'a StyleHyphensValue> {
2286        self.get_property(node_data, node_id, node_state, &CssPropertyType::Hyphens)
2287            .and_then(|p| p.as_hyphens())
2288    }
2289
2290    /// Method for getting word-break property
2291    pub fn get_word_break<'a>(
2292        &'a self,
2293        node_data: &'a NodeData,
2294        node_id: &NodeId,
2295        node_state: &StyledNodeState,
2296    ) -> Option<&'a StyleWordBreakValue> {
2297        self.get_property(node_data, node_id, node_state, &CssPropertyType::WordBreak)
2298            .and_then(|p| p.as_word_break())
2299    }
2300
2301    /// Method for getting overflow-wrap property
2302    pub fn get_overflow_wrap<'a>(
2303        &'a self,
2304        node_data: &'a NodeData,
2305        node_id: &NodeId,
2306        node_state: &StyledNodeState,
2307    ) -> Option<&'a StyleOverflowWrapValue> {
2308        self.get_property(node_data, node_id, node_state, &CssPropertyType::OverflowWrap)
2309            .and_then(|p| p.as_overflow_wrap())
2310    }
2311
2312    /// Method for getting line-break property
2313    pub fn get_line_break<'a>(
2314        &'a self,
2315        node_data: &'a NodeData,
2316        node_id: &NodeId,
2317        node_state: &StyledNodeState,
2318    ) -> Option<&'a StyleLineBreakValue> {
2319        self.get_property(node_data, node_id, node_state, &CssPropertyType::LineBreak)
2320            .and_then(|p| p.as_line_break())
2321    }
2322
2323    /// Method for getting text-align-last property
2324    pub fn get_text_align_last<'a>(
2325        &'a self,
2326        node_data: &'a NodeData,
2327        node_id: &NodeId,
2328        node_state: &StyledNodeState,
2329    ) -> Option<&'a StyleTextAlignLastValue> {
2330        self.get_property(node_data, node_id, node_state, &CssPropertyType::TextAlignLast)
2331            .and_then(|p| p.as_text_align_last())
2332    }
2333
2334    /// Method for getting object-fit property
2335    pub fn get_object_fit<'a>(
2336        &'a self,
2337        node_data: &'a NodeData,
2338        node_id: &NodeId,
2339        node_state: &StyledNodeState,
2340    ) -> Option<&'a StyleObjectFitValue> {
2341        self.get_property(node_data, node_id, node_state, &CssPropertyType::ObjectFit)
2342            .and_then(|p| p.as_object_fit())
2343    }
2344
2345    /// Method for getting text-orientation property
2346    pub fn get_text_orientation<'a>(
2347        &'a self,
2348        node_data: &'a NodeData,
2349        node_id: &NodeId,
2350        node_state: &StyledNodeState,
2351    ) -> Option<&'a StyleTextOrientationValue> {
2352        self.get_property(node_data, node_id, node_state, &CssPropertyType::TextOrientation)
2353            .and_then(|p| p.as_text_orientation())
2354    }
2355
2356    /// Method for getting object-position property
2357    pub fn get_object_position<'a>(
2358        &'a self,
2359        node_data: &'a NodeData,
2360        node_id: &NodeId,
2361        node_state: &StyledNodeState,
2362    ) -> Option<&'a StyleObjectPositionValue> {
2363        self.get_property(node_data, node_id, node_state, &CssPropertyType::ObjectPosition)
2364            .and_then(|p| p.as_object_position())
2365    }
2366
2367    /// Method for getting aspect-ratio property
2368    pub fn get_aspect_ratio<'a>(
2369        &'a self,
2370        node_data: &'a NodeData,
2371        node_id: &NodeId,
2372        node_state: &StyledNodeState,
2373    ) -> Option<&'a StyleAspectRatioValue> {
2374        self.get_property(node_data, node_id, node_state, &CssPropertyType::AspectRatio)
2375            .and_then(|p| p.as_aspect_ratio())
2376    }
2377
2378    /// Method for getting direction property
2379    pub fn get_direction<'a>(
2380        &'a self,
2381        node_data: &'a NodeData,
2382        node_id: &NodeId,
2383        node_state: &StyledNodeState,
2384    ) -> Option<&'a StyleDirectionValue> {
2385        self.get_property(node_data, node_id, node_state, &CssPropertyType::Direction)
2386            .and_then(|p| p.as_direction())
2387    }
2388
2389    pub fn get_unicode_bidi<'a>(
2390        &'a self,
2391        node_data: &'a NodeData,
2392        node_id: &NodeId,
2393        node_state: &StyledNodeState,
2394    ) -> Option<&'a StyleUnicodeBidiValue> {
2395        self.get_property(node_data, node_id, node_state, &CssPropertyType::UnicodeBidi)
2396            .and_then(|p| p.as_unicode_bidi())
2397    }
2398
2399    pub fn get_text_box_trim<'a>(
2400        &'a self,
2401        node_data: &'a NodeData,
2402        node_id: &NodeId,
2403        node_state: &StyledNodeState,
2404    ) -> Option<&'a StyleTextBoxTrimValue> {
2405        self.get_property(node_data, node_id, node_state, &CssPropertyType::TextBoxTrim)
2406            .and_then(|p| p.as_text_box_trim())
2407    }
2408
2409    pub fn get_text_box_edge<'a>(
2410        &'a self,
2411        node_data: &'a NodeData,
2412        node_id: &NodeId,
2413        node_state: &StyledNodeState,
2414    ) -> Option<&'a StyleTextBoxEdgeValue> {
2415        self.get_property(node_data, node_id, node_state, &CssPropertyType::TextBoxEdge)
2416            .and_then(|p| p.as_text_box_edge())
2417    }
2418
2419    pub fn get_dominant_baseline<'a>(
2420        &'a self,
2421        node_data: &'a NodeData,
2422        node_id: &NodeId,
2423        node_state: &StyledNodeState,
2424    ) -> Option<&'a StyleDominantBaselineValue> {
2425        self.get_property(node_data, node_id, node_state, &CssPropertyType::DominantBaseline)
2426            .and_then(|p| p.as_dominant_baseline())
2427    }
2428
2429    pub fn get_alignment_baseline<'a>(
2430        &'a self,
2431        node_data: &'a NodeData,
2432        node_id: &NodeId,
2433        node_state: &StyledNodeState,
2434    ) -> Option<&'a StyleAlignmentBaselineValue> {
2435        self.get_property(node_data, node_id, node_state, &CssPropertyType::AlignmentBaseline)
2436            .and_then(|p| p.as_alignment_baseline())
2437    }
2438
2439    pub fn get_initial_letter_align<'a>(
2440        &'a self,
2441        node_data: &'a NodeData,
2442        node_id: &NodeId,
2443        node_state: &StyledNodeState,
2444    ) -> Option<&'a StyleInitialLetterAlignValue> {
2445        self.get_property(node_data, node_id, node_state, &CssPropertyType::InitialLetterAlign)
2446            .and_then(|p| p.as_initial_letter_align())
2447    }
2448
2449    pub fn get_initial_letter_wrap<'a>(
2450        &'a self,
2451        node_data: &'a NodeData,
2452        node_id: &NodeId,
2453        node_state: &StyledNodeState,
2454    ) -> Option<&'a StyleInitialLetterWrapValue> {
2455        self.get_property(node_data, node_id, node_state, &CssPropertyType::InitialLetterWrap)
2456            .and_then(|p| p.as_initial_letter_wrap())
2457    }
2458
2459    pub fn get_scrollbar_gutter<'a>(
2460        &'a self,
2461        node_data: &'a NodeData,
2462        node_id: &NodeId,
2463        node_state: &StyledNodeState,
2464    ) -> Option<&'a StyleScrollbarGutterValue> {
2465        self.get_property(node_data, node_id, node_state, &CssPropertyType::ScrollbarGutter)
2466            .and_then(|p| p.as_scrollbar_gutter())
2467    }
2468
2469    pub fn get_overflow_clip_margin<'a>(
2470        &'a self,
2471        node_data: &'a NodeData,
2472        node_id: &NodeId,
2473        node_state: &StyledNodeState,
2474    ) -> Option<&'a StyleOverflowClipMarginValue> {
2475        self.get_property(node_data, node_id, node_state, &CssPropertyType::OverflowClipMargin)
2476            .and_then(|p| p.as_overflow_clip_margin())
2477    }
2478
2479    pub fn get_clip<'a>(
2480        &'a self,
2481        node_data: &'a NodeData,
2482        node_id: &NodeId,
2483        node_state: &StyledNodeState,
2484    ) -> Option<&'a StyleClipRectValue> {
2485        self.get_property(node_data, node_id, node_state, &CssPropertyType::Clip)
2486            .and_then(|p| p.as_clip())
2487    }
2488
2489    /// Method for getting white-space property
2490    pub fn get_white_space<'a>(
2491        &'a self,
2492        node_data: &'a NodeData,
2493        node_id: &NodeId,
2494        node_state: &StyledNodeState,
2495    ) -> Option<&'a StyleWhiteSpaceValue> {
2496        self.get_property(node_data, node_id, node_state, &CssPropertyType::WhiteSpace)
2497            .and_then(|p| p.as_white_space())
2498    }
2499    pub fn get_background_position<'a>(
2500        &'a self,
2501        node_data: &'a NodeData,
2502        node_id: &NodeId,
2503        node_state: &StyledNodeState,
2504    ) -> Option<&'a StyleBackgroundPositionVecValue> {
2505        self.get_property(
2506            node_data,
2507            node_id,
2508            node_state,
2509            &CssPropertyType::BackgroundPosition,
2510        )
2511        .and_then(|p| p.as_background_position())
2512    }
2513    pub fn get_background_size<'a>(
2514        &'a self,
2515        node_data: &'a NodeData,
2516        node_id: &NodeId,
2517        node_state: &StyledNodeState,
2518    ) -> Option<&'a StyleBackgroundSizeVecValue> {
2519        self.get_property(
2520            node_data,
2521            node_id,
2522            node_state,
2523            &CssPropertyType::BackgroundSize,
2524        )
2525        .and_then(|p| p.as_background_size())
2526    }
2527    pub fn get_background_repeat<'a>(
2528        &'a self,
2529        node_data: &'a NodeData,
2530        node_id: &NodeId,
2531        node_state: &StyledNodeState,
2532    ) -> Option<&'a StyleBackgroundRepeatVecValue> {
2533        self.get_property(
2534            node_data,
2535            node_id,
2536            node_state,
2537            &CssPropertyType::BackgroundRepeat,
2538        )
2539        .and_then(|p| p.as_background_repeat())
2540    }
2541    pub fn get_font_size<'a>(
2542        &'a self,
2543        node_data: &'a NodeData,
2544        node_id: &NodeId,
2545        node_state: &StyledNodeState,
2546    ) -> Option<&'a StyleFontSizeValue> {
2547        self.get_property(node_data, node_id, node_state, &CssPropertyType::FontSize)
2548            .and_then(|p| p.as_font_size())
2549    }
2550    pub fn get_font_family<'a>(
2551        &'a self,
2552        node_data: &'a NodeData,
2553        node_id: &NodeId,
2554        node_state: &StyledNodeState,
2555    ) -> Option<&'a StyleFontFamilyVecValue> {
2556        self.get_property(node_data, node_id, node_state, &CssPropertyType::FontFamily)
2557            .and_then(|p| p.as_font_family())
2558    }
2559    pub fn get_font_weight<'a>(
2560        &'a self,
2561        node_data: &'a NodeData,
2562        node_id: &NodeId,
2563        node_state: &StyledNodeState,
2564    ) -> Option<&'a StyleFontWeightValue> {
2565        self.get_property(node_data, node_id, node_state, &CssPropertyType::FontWeight)
2566            .and_then(|p| p.as_font_weight())
2567    }
2568    pub fn get_font_style<'a>(
2569        &'a self,
2570        node_data: &'a NodeData,
2571        node_id: &NodeId,
2572        node_state: &StyledNodeState,
2573    ) -> Option<&'a StyleFontStyleValue> {
2574        self.get_property(node_data, node_id, node_state, &CssPropertyType::FontStyle)
2575            .and_then(|p| p.as_font_style())
2576    }
2577    pub fn get_text_color<'a>(
2578        &'a self,
2579        node_data: &'a NodeData,
2580        node_id: &NodeId,
2581        node_state: &StyledNodeState,
2582    ) -> Option<&'a StyleTextColorValue> {
2583        self.get_property(node_data, node_id, node_state, &CssPropertyType::TextColor)
2584            .and_then(|p| p.as_text_color())
2585    }
2586    /// Method for getting text-indent property
2587    pub fn get_text_indent<'a>(
2588        &'a self,
2589        node_data: &'a NodeData,
2590        node_id: &NodeId,
2591        node_state: &StyledNodeState,
2592    ) -> Option<&'a StyleTextIndentValue> {
2593        self.get_property(node_data, node_id, node_state, &CssPropertyType::TextIndent)
2594            .and_then(|p| p.as_text_indent())
2595    }
2596    /// Method for getting initial-letter property
2597    pub fn get_initial_letter<'a>(
2598        &'a self,
2599        node_data: &'a NodeData,
2600        node_id: &NodeId,
2601        node_state: &StyledNodeState,
2602    ) -> Option<&'a StyleInitialLetterValue> {
2603        self.get_property(
2604            node_data,
2605            node_id,
2606            node_state,
2607            &CssPropertyType::InitialLetter,
2608        )
2609        .and_then(|p| p.as_initial_letter())
2610    }
2611    /// Method for getting line-clamp property
2612    pub fn get_line_clamp<'a>(
2613        &'a self,
2614        node_data: &'a NodeData,
2615        node_id: &NodeId,
2616        node_state: &StyledNodeState,
2617    ) -> Option<&'a StyleLineClampValue> {
2618        self.get_property(node_data, node_id, node_state, &CssPropertyType::LineClamp)
2619            .and_then(|p| p.as_line_clamp())
2620    }
2621    /// Method for getting hanging-punctuation property
2622    pub fn get_hanging_punctuation<'a>(
2623        &'a self,
2624        node_data: &'a NodeData,
2625        node_id: &NodeId,
2626        node_state: &StyledNodeState,
2627    ) -> Option<&'a StyleHangingPunctuationValue> {
2628        self.get_property(
2629            node_data,
2630            node_id,
2631            node_state,
2632            &CssPropertyType::HangingPunctuation,
2633        )
2634        .and_then(|p| p.as_hanging_punctuation())
2635    }
2636    /// Method for getting text-combine-upright property
2637    pub fn get_text_combine_upright<'a>(
2638        &'a self,
2639        node_data: &'a NodeData,
2640        node_id: &NodeId,
2641        node_state: &StyledNodeState,
2642    ) -> Option<&'a StyleTextCombineUprightValue> {
2643        self.get_property(
2644            node_data,
2645            node_id,
2646            node_state,
2647            &CssPropertyType::TextCombineUpright,
2648        )
2649        .and_then(|p| p.as_text_combine_upright())
2650    }
2651    /// Method for getting -azul-exclusion-margin property
2652    pub fn get_exclusion_margin<'a>(
2653        &'a self,
2654        node_data: &'a NodeData,
2655        node_id: &NodeId,
2656        node_state: &StyledNodeState,
2657    ) -> Option<&'a StyleExclusionMarginValue> {
2658        self.get_property(
2659            node_data,
2660            node_id,
2661            node_state,
2662            &CssPropertyType::ExclusionMargin,
2663        )
2664        .and_then(|p| p.as_exclusion_margin())
2665    }
2666    /// Method for getting -azul-hyphenation-language property
2667    pub fn get_hyphenation_language<'a>(
2668        &'a self,
2669        node_data: &'a NodeData,
2670        node_id: &NodeId,
2671        node_state: &StyledNodeState,
2672    ) -> Option<&'a StyleHyphenationLanguageValue> {
2673        self.get_property(
2674            node_data,
2675            node_id,
2676            node_state,
2677            &CssPropertyType::HyphenationLanguage,
2678        )
2679        .and_then(|p| p.as_hyphenation_language())
2680    }
2681    /// Method for getting caret-color property
2682    pub fn get_caret_color<'a>(
2683        &'a self,
2684        node_data: &'a NodeData,
2685        node_id: &NodeId,
2686        node_state: &StyledNodeState,
2687    ) -> Option<&'a CaretColorValue> {
2688        self.get_property(node_data, node_id, node_state, &CssPropertyType::CaretColor)
2689            .and_then(|p| p.as_caret_color())
2690    }
2691
2692    /// Method for getting -azul-caret-width property
2693    pub fn get_caret_width<'a>(
2694        &'a self,
2695        node_data: &'a NodeData,
2696        node_id: &NodeId,
2697        node_state: &StyledNodeState,
2698    ) -> Option<&'a CaretWidthValue> {
2699        self.get_property(node_data, node_id, node_state, &CssPropertyType::CaretWidth)
2700            .and_then(|p| p.as_caret_width())
2701    }
2702
2703    /// Method for getting caret-animation-duration property
2704    pub fn get_caret_animation_duration<'a>(
2705        &'a self,
2706        node_data: &'a NodeData,
2707        node_id: &NodeId,
2708        node_state: &StyledNodeState,
2709    ) -> Option<&'a CaretAnimationDurationValue> {
2710        self.get_property(
2711            node_data,
2712            node_id,
2713            node_state,
2714            &CssPropertyType::CaretAnimationDuration,
2715        )
2716        .and_then(|p| p.as_caret_animation_duration())
2717    }
2718
2719    /// Method for getting selection-background-color property
2720    pub fn get_selection_background_color<'a>(
2721        &'a self,
2722        node_data: &'a NodeData,
2723        node_id: &NodeId,
2724        node_state: &StyledNodeState,
2725    ) -> Option<&'a SelectionBackgroundColorValue> {
2726        self.get_property(
2727            node_data,
2728            node_id,
2729            node_state,
2730            &CssPropertyType::SelectionBackgroundColor,
2731        )
2732        .and_then(|p| p.as_selection_background_color())
2733    }
2734
2735    /// Method for getting selection-color property
2736    pub fn get_selection_color<'a>(
2737        &'a self,
2738        node_data: &'a NodeData,
2739        node_id: &NodeId,
2740        node_state: &StyledNodeState,
2741    ) -> Option<&'a SelectionColorValue> {
2742        self.get_property(
2743            node_data,
2744            node_id,
2745            node_state,
2746            &CssPropertyType::SelectionColor,
2747        )
2748        .and_then(|p| p.as_selection_color())
2749    }
2750
2751    /// Method for getting -azul-selection-radius property
2752    pub fn get_selection_radius<'a>(
2753        &'a self,
2754        node_data: &'a NodeData,
2755        node_id: &NodeId,
2756        node_state: &StyledNodeState,
2757    ) -> Option<&'a SelectionRadiusValue> {
2758        self.get_property(
2759            node_data,
2760            node_id,
2761            node_state,
2762            &CssPropertyType::SelectionRadius,
2763        )
2764        .and_then(|p| p.as_selection_radius())
2765    }
2766
2767    /// Method for getting text-justify property
2768    pub fn get_text_justify<'a>(
2769        &'a self,
2770        node_data: &'a NodeData,
2771        node_id: &NodeId,
2772        node_state: &StyledNodeState,
2773    ) -> Option<&'a LayoutTextJustifyValue> {
2774        self.get_property(
2775            node_data,
2776            node_id,
2777            node_state,
2778            &CssPropertyType::TextJustify,
2779        )
2780        .and_then(|p| p.as_text_justify())
2781    }
2782
2783    /// Method for getting z-index property
2784    pub fn get_z_index<'a>(
2785        &'a self,
2786        node_data: &'a NodeData,
2787        node_id: &NodeId,
2788        node_state: &StyledNodeState,
2789    ) -> Option<&'a LayoutZIndexValue> {
2790        self.get_property(node_data, node_id, node_state, &CssPropertyType::ZIndex)
2791            .and_then(|p| p.as_z_index())
2792    }
2793
2794    /// Method for getting flex-basis property
2795    pub fn get_flex_basis<'a>(
2796        &'a self,
2797        node_data: &'a NodeData,
2798        node_id: &NodeId,
2799        node_state: &StyledNodeState,
2800    ) -> Option<&'a LayoutFlexBasisValue> {
2801        self.get_property(node_data, node_id, node_state, &CssPropertyType::FlexBasis)
2802            .and_then(|p| p.as_flex_basis())
2803    }
2804
2805    /// Method for getting column-gap property
2806    pub fn get_column_gap<'a>(
2807        &'a self,
2808        node_data: &'a NodeData,
2809        node_id: &NodeId,
2810        node_state: &StyledNodeState,
2811    ) -> Option<&'a LayoutColumnGapValue> {
2812        self.get_property(node_data, node_id, node_state, &CssPropertyType::ColumnGap)
2813            .and_then(|p| p.as_column_gap())
2814    }
2815
2816    /// Method for getting row-gap property
2817    pub fn get_row_gap<'a>(
2818        &'a self,
2819        node_data: &'a NodeData,
2820        node_id: &NodeId,
2821        node_state: &StyledNodeState,
2822    ) -> Option<&'a LayoutRowGapValue> {
2823        self.get_property(node_data, node_id, node_state, &CssPropertyType::RowGap)
2824            .and_then(|p| p.as_row_gap())
2825    }
2826
2827    /// Method for getting grid-template-columns property
2828    pub fn get_grid_template_columns<'a>(
2829        &'a self,
2830        node_data: &'a NodeData,
2831        node_id: &NodeId,
2832        node_state: &StyledNodeState,
2833    ) -> Option<&'a LayoutGridTemplateColumnsValue> {
2834        self.get_property(
2835            node_data,
2836            node_id,
2837            node_state,
2838            &CssPropertyType::GridTemplateColumns,
2839        )
2840        .and_then(|p| p.as_grid_template_columns())
2841    }
2842
2843    /// Method for getting grid-template-rows property
2844    pub fn get_grid_template_rows<'a>(
2845        &'a self,
2846        node_data: &'a NodeData,
2847        node_id: &NodeId,
2848        node_state: &StyledNodeState,
2849    ) -> Option<&'a LayoutGridTemplateRowsValue> {
2850        self.get_property(
2851            node_data,
2852            node_id,
2853            node_state,
2854            &CssPropertyType::GridTemplateRows,
2855        )
2856        .and_then(|p| p.as_grid_template_rows())
2857    }
2858
2859    /// Method for getting grid-auto-columns property
2860    pub fn get_grid_auto_columns<'a>(
2861        &'a self,
2862        node_data: &'a NodeData,
2863        node_id: &NodeId,
2864        node_state: &StyledNodeState,
2865    ) -> Option<&'a LayoutGridAutoColumnsValue> {
2866        self.get_property(
2867            node_data,
2868            node_id,
2869            node_state,
2870            &CssPropertyType::GridAutoColumns,
2871        )
2872        .and_then(|p| p.as_grid_auto_columns())
2873    }
2874
2875    /// Method for getting grid-auto-rows property
2876    pub fn get_grid_auto_rows<'a>(
2877        &'a self,
2878        node_data: &'a NodeData,
2879        node_id: &NodeId,
2880        node_state: &StyledNodeState,
2881    ) -> Option<&'a LayoutGridAutoRowsValue> {
2882        self.get_property(
2883            node_data,
2884            node_id,
2885            node_state,
2886            &CssPropertyType::GridAutoRows,
2887        )
2888        .and_then(|p| p.as_grid_auto_rows())
2889    }
2890
2891    /// Method for getting grid-column property
2892    pub fn get_grid_column<'a>(
2893        &'a self,
2894        node_data: &'a NodeData,
2895        node_id: &NodeId,
2896        node_state: &StyledNodeState,
2897    ) -> Option<&'a LayoutGridColumnValue> {
2898        self.get_property(node_data, node_id, node_state, &CssPropertyType::GridColumn)
2899            .and_then(|p| p.as_grid_column())
2900    }
2901
2902    /// Method for getting grid-row property
2903    pub fn get_grid_row<'a>(
2904        &'a self,
2905        node_data: &'a NodeData,
2906        node_id: &NodeId,
2907        node_state: &StyledNodeState,
2908    ) -> Option<&'a LayoutGridRowValue> {
2909        self.get_property(node_data, node_id, node_state, &CssPropertyType::GridRow)
2910            .and_then(|p| p.as_grid_row())
2911    }
2912
2913    /// Method for getting grid-auto-flow property
2914    pub fn get_grid_auto_flow<'a>(
2915        &'a self,
2916        node_data: &'a NodeData,
2917        node_id: &NodeId,
2918        node_state: &StyledNodeState,
2919    ) -> Option<&'a LayoutGridAutoFlowValue> {
2920        self.get_property(
2921            node_data,
2922            node_id,
2923            node_state,
2924            &CssPropertyType::GridAutoFlow,
2925        )
2926        .and_then(|p| p.as_grid_auto_flow())
2927    }
2928
2929    /// Method for getting justify-self property
2930    pub fn get_justify_self<'a>(
2931        &'a self,
2932        node_data: &'a NodeData,
2933        node_id: &NodeId,
2934        node_state: &StyledNodeState,
2935    ) -> Option<&'a LayoutJustifySelfValue> {
2936        self.get_property(
2937            node_data,
2938            node_id,
2939            node_state,
2940            &CssPropertyType::JustifySelf,
2941        )
2942        .and_then(|p| p.as_justify_self())
2943    }
2944
2945    /// Method for getting justify-items property
2946    pub fn get_justify_items<'a>(
2947        &'a self,
2948        node_data: &'a NodeData,
2949        node_id: &NodeId,
2950        node_state: &StyledNodeState,
2951    ) -> Option<&'a LayoutJustifyItemsValue> {
2952        self.get_property(
2953            node_data,
2954            node_id,
2955            node_state,
2956            &CssPropertyType::JustifyItems,
2957        )
2958        .and_then(|p| p.as_justify_items())
2959    }
2960
2961    /// Method for getting gap property
2962    pub fn get_gap<'a>(
2963        &'a self,
2964        node_data: &'a NodeData,
2965        node_id: &NodeId,
2966        node_state: &StyledNodeState,
2967    ) -> Option<&'a LayoutGapValue> {
2968        self.get_property(node_data, node_id, node_state, &CssPropertyType::Gap)
2969            .and_then(|p| p.as_gap())
2970    }
2971
2972    /// Method for getting grid-gap property
2973    pub(crate) fn get_grid_gap<'a>(
2974        &'a self,
2975        node_data: &'a NodeData,
2976        node_id: &NodeId,
2977        node_state: &StyledNodeState,
2978    ) -> Option<&'a LayoutGapValue> {
2979        self.get_property(node_data, node_id, node_state, &CssPropertyType::GridGap)
2980            .and_then(|p| p.as_grid_gap())
2981    }
2982
2983    /// Method for getting align-self property
2984    pub fn get_align_self<'a>(
2985        &'a self,
2986        node_data: &'a NodeData,
2987        node_id: &NodeId,
2988        node_state: &StyledNodeState,
2989    ) -> Option<&'a LayoutAlignSelfValue> {
2990        self.get_property(node_data, node_id, node_state, &CssPropertyType::AlignSelf)
2991            .and_then(|p| p.as_align_self())
2992    }
2993
2994    /// Method for getting font property
2995    pub fn get_font<'a>(
2996        &'a self,
2997        node_data: &'a NodeData,
2998        node_id: &NodeId,
2999        node_state: &StyledNodeState,
3000    ) -> Option<&'a StyleFontValue> {
3001        self.get_property(node_data, node_id, node_state, &CssPropertyType::Font)
3002            .and_then(|p| p.as_font())
3003    }
3004
3005    /// Method for getting writing-mode property
3006    pub fn get_writing_mode<'a>(
3007        &'a self,
3008        node_data: &'a NodeData,
3009        node_id: &NodeId,
3010        node_state: &StyledNodeState,
3011    ) -> Option<&'a LayoutWritingModeValue> {
3012        self.get_property(
3013            node_data,
3014            node_id,
3015            node_state,
3016            &CssPropertyType::WritingMode,
3017        )
3018        .and_then(|p| p.as_writing_mode())
3019    }
3020
3021    /// Method for getting clear property
3022    pub fn get_clear<'a>(
3023        &'a self,
3024        node_data: &'a NodeData,
3025        node_id: &NodeId,
3026        node_state: &StyledNodeState,
3027    ) -> Option<&'a LayoutClearValue> {
3028        self.get_property(node_data, node_id, node_state, &CssPropertyType::Clear)
3029            .and_then(|p| p.as_clear())
3030    }
3031
3032    /// Method for getting shape-outside property
3033    pub fn get_shape_outside<'a>(
3034        &'a self,
3035        node_data: &'a NodeData,
3036        node_id: &NodeId,
3037        node_state: &StyledNodeState,
3038    ) -> Option<&'a ShapeOutsideValue> {
3039        self.get_property(
3040            node_data,
3041            node_id,
3042            node_state,
3043            &CssPropertyType::ShapeOutside,
3044        )
3045        .and_then(|p| p.as_shape_outside())
3046    }
3047
3048    /// Method for getting shape-inside property
3049    pub fn get_shape_inside<'a>(
3050        &'a self,
3051        node_data: &'a NodeData,
3052        node_id: &NodeId,
3053        node_state: &StyledNodeState,
3054    ) -> Option<&'a ShapeInsideValue> {
3055        self.get_property(
3056            node_data,
3057            node_id,
3058            node_state,
3059            &CssPropertyType::ShapeInside,
3060        )
3061        .and_then(|p| p.as_shape_inside())
3062    }
3063
3064    /// Method for getting clip-path property
3065    pub fn get_clip_path<'a>(
3066        &'a self,
3067        node_data: &'a NodeData,
3068        node_id: &NodeId,
3069        node_state: &StyledNodeState,
3070    ) -> Option<&'a ClipPathValue> {
3071        self.get_property(node_data, node_id, node_state, &CssPropertyType::ClipPath)
3072            .and_then(|p| p.as_clip_path())
3073    }
3074
3075    /// Method for getting scrollbar track background
3076    pub fn get_scrollbar_track<'a>(
3077        &'a self,
3078        node_data: &'a NodeData,
3079        node_id: &NodeId,
3080        node_state: &StyledNodeState,
3081    ) -> Option<&'a StyleBackgroundContentValue> {
3082        self.get_property(node_data, node_id, node_state, &CssPropertyType::ScrollbarTrack)
3083            .and_then(|p| p.as_scrollbar_track())
3084    }
3085
3086    /// Method for getting scrollbar thumb background
3087    pub fn get_scrollbar_thumb<'a>(
3088        &'a self,
3089        node_data: &'a NodeData,
3090        node_id: &NodeId,
3091        node_state: &StyledNodeState,
3092    ) -> Option<&'a StyleBackgroundContentValue> {
3093        self.get_property(node_data, node_id, node_state, &CssPropertyType::ScrollbarThumb)
3094            .and_then(|p| p.as_scrollbar_thumb())
3095    }
3096
3097    /// Method for getting scrollbar button background
3098    pub fn get_scrollbar_button<'a>(
3099        &'a self,
3100        node_data: &'a NodeData,
3101        node_id: &NodeId,
3102        node_state: &StyledNodeState,
3103    ) -> Option<&'a StyleBackgroundContentValue> {
3104        self.get_property(node_data, node_id, node_state, &CssPropertyType::ScrollbarButton)
3105            .and_then(|p| p.as_scrollbar_button())
3106    }
3107
3108    /// Method for getting scrollbar corner background
3109    pub fn get_scrollbar_corner<'a>(
3110        &'a self,
3111        node_data: &'a NodeData,
3112        node_id: &NodeId,
3113        node_state: &StyledNodeState,
3114    ) -> Option<&'a StyleBackgroundContentValue> {
3115        self.get_property(node_data, node_id, node_state, &CssPropertyType::ScrollbarCorner)
3116            .and_then(|p| p.as_scrollbar_corner())
3117    }
3118
3119    /// Method for getting scrollbar resizer background
3120    pub fn get_scrollbar_resizer<'a>(
3121        &'a self,
3122        node_data: &'a NodeData,
3123        node_id: &NodeId,
3124        node_state: &StyledNodeState,
3125    ) -> Option<&'a StyleBackgroundContentValue> {
3126        self.get_property(node_data, node_id, node_state, &CssPropertyType::ScrollbarResizer)
3127            .and_then(|p| p.as_scrollbar_resizer())
3128    }
3129
3130    /// Method for getting scrollbar-width property
3131    pub fn get_scrollbar_width<'a>(
3132        &'a self,
3133        node_data: &'a NodeData,
3134        node_id: &NodeId,
3135        node_state: &StyledNodeState,
3136    ) -> Option<&'a LayoutScrollbarWidthValue> {
3137        self.get_property(
3138            node_data,
3139            node_id,
3140            node_state,
3141            &CssPropertyType::ScrollbarWidth,
3142        )
3143        .and_then(|p| p.as_scrollbar_width())
3144    }
3145
3146    /// Method for getting scrollbar-color property
3147    pub fn get_scrollbar_color<'a>(
3148        &'a self,
3149        node_data: &'a NodeData,
3150        node_id: &NodeId,
3151        node_state: &StyledNodeState,
3152    ) -> Option<&'a StyleScrollbarColorValue> {
3153        self.get_property(
3154            node_data,
3155            node_id,
3156            node_state,
3157            &CssPropertyType::ScrollbarColor,
3158        )
3159        .and_then(|p| p.as_scrollbar_color())
3160    }
3161
3162    /// Method for getting -azul-scrollbar-visibility property
3163    pub fn get_scrollbar_visibility<'a>(
3164        &'a self,
3165        node_data: &'a NodeData,
3166        node_id: &NodeId,
3167        node_state: &StyledNodeState,
3168    ) -> Option<&'a ScrollbarVisibilityModeValue> {
3169        self.get_property(
3170            node_data,
3171            node_id,
3172            node_state,
3173            &CssPropertyType::ScrollbarVisibility,
3174        )
3175        .and_then(|p| p.as_scrollbar_visibility())
3176    }
3177
3178    /// Method for getting -azul-scrollbar-fade-delay property
3179    pub fn get_scrollbar_fade_delay<'a>(
3180        &'a self,
3181        node_data: &'a NodeData,
3182        node_id: &NodeId,
3183        node_state: &StyledNodeState,
3184    ) -> Option<&'a ScrollbarFadeDelayValue> {
3185        self.get_property(
3186            node_data,
3187            node_id,
3188            node_state,
3189            &CssPropertyType::ScrollbarFadeDelay,
3190        )
3191        .and_then(|p| p.as_scrollbar_fade_delay())
3192    }
3193
3194    /// Method for getting -azul-scrollbar-fade-duration property
3195    pub fn get_scrollbar_fade_duration<'a>(
3196        &'a self,
3197        node_data: &'a NodeData,
3198        node_id: &NodeId,
3199        node_state: &StyledNodeState,
3200    ) -> Option<&'a ScrollbarFadeDurationValue> {
3201        self.get_property(
3202            node_data,
3203            node_id,
3204            node_state,
3205            &CssPropertyType::ScrollbarFadeDuration,
3206        )
3207        .and_then(|p| p.as_scrollbar_fade_duration())
3208    }
3209
3210    /// Method for getting visibility property
3211    pub fn get_visibility<'a>(
3212        &'a self,
3213        node_data: &'a NodeData,
3214        node_id: &NodeId,
3215        node_state: &StyledNodeState,
3216    ) -> Option<&'a StyleVisibilityValue> {
3217        self.get_property(node_data, node_id, node_state, &CssPropertyType::Visibility)
3218            .and_then(|p| p.as_visibility())
3219    }
3220
3221    /// Method for getting break-before property
3222    pub fn get_break_before<'a>(
3223        &'a self,
3224        node_data: &'a NodeData,
3225        node_id: &NodeId,
3226        node_state: &StyledNodeState,
3227    ) -> Option<&'a PageBreakValue> {
3228        self.get_property(
3229            node_data,
3230            node_id,
3231            node_state,
3232            &CssPropertyType::BreakBefore,
3233        )
3234        .and_then(|p| p.as_break_before())
3235    }
3236
3237    /// Method for getting break-after property
3238    pub fn get_break_after<'a>(
3239        &'a self,
3240        node_data: &'a NodeData,
3241        node_id: &NodeId,
3242        node_state: &StyledNodeState,
3243    ) -> Option<&'a PageBreakValue> {
3244        self.get_property(node_data, node_id, node_state, &CssPropertyType::BreakAfter)
3245            .and_then(|p| p.as_break_after())
3246    }
3247
3248    /// Method for getting break-inside property
3249    pub fn get_break_inside<'a>(
3250        &'a self,
3251        node_data: &'a NodeData,
3252        node_id: &NodeId,
3253        node_state: &StyledNodeState,
3254    ) -> Option<&'a BreakInsideValue> {
3255        self.get_property(
3256            node_data,
3257            node_id,
3258            node_state,
3259            &CssPropertyType::BreakInside,
3260        )
3261        .and_then(|p| p.as_break_inside())
3262    }
3263
3264    /// Method for getting orphans property
3265    pub fn get_orphans<'a>(
3266        &'a self,
3267        node_data: &'a NodeData,
3268        node_id: &NodeId,
3269        node_state: &StyledNodeState,
3270    ) -> Option<&'a OrphansValue> {
3271        self.get_property(node_data, node_id, node_state, &CssPropertyType::Orphans)
3272            .and_then(|p| p.as_orphans())
3273    }
3274
3275    /// Method for getting widows property
3276    pub fn get_widows<'a>(
3277        &'a self,
3278        node_data: &'a NodeData,
3279        node_id: &NodeId,
3280        node_state: &StyledNodeState,
3281    ) -> Option<&'a WidowsValue> {
3282        self.get_property(node_data, node_id, node_state, &CssPropertyType::Widows)
3283            .and_then(|p| p.as_widows())
3284    }
3285
3286    /// Method for getting box-decoration-break property
3287    pub fn get_box_decoration_break<'a>(
3288        &'a self,
3289        node_data: &'a NodeData,
3290        node_id: &NodeId,
3291        node_state: &StyledNodeState,
3292    ) -> Option<&'a BoxDecorationBreakValue> {
3293        self.get_property(
3294            node_data,
3295            node_id,
3296            node_state,
3297            &CssPropertyType::BoxDecorationBreak,
3298        )
3299        .and_then(|p| p.as_box_decoration_break())
3300    }
3301
3302    /// Method for getting column-count property
3303    pub fn get_column_count<'a>(
3304        &'a self,
3305        node_data: &'a NodeData,
3306        node_id: &NodeId,
3307        node_state: &StyledNodeState,
3308    ) -> Option<&'a ColumnCountValue> {
3309        self.get_property(
3310            node_data,
3311            node_id,
3312            node_state,
3313            &CssPropertyType::ColumnCount,
3314        )
3315        .and_then(|p| p.as_column_count())
3316    }
3317
3318    /// Method for getting column-width property
3319    pub fn get_column_width<'a>(
3320        &'a self,
3321        node_data: &'a NodeData,
3322        node_id: &NodeId,
3323        node_state: &StyledNodeState,
3324    ) -> Option<&'a ColumnWidthValue> {
3325        self.get_property(
3326            node_data,
3327            node_id,
3328            node_state,
3329            &CssPropertyType::ColumnWidth,
3330        )
3331        .and_then(|p| p.as_column_width())
3332    }
3333
3334    /// Method for getting column-span property
3335    pub fn get_column_span<'a>(
3336        &'a self,
3337        node_data: &'a NodeData,
3338        node_id: &NodeId,
3339        node_state: &StyledNodeState,
3340    ) -> Option<&'a ColumnSpanValue> {
3341        self.get_property(node_data, node_id, node_state, &CssPropertyType::ColumnSpan)
3342            .and_then(|p| p.as_column_span())
3343    }
3344
3345    /// Method for getting column-fill property
3346    pub fn get_column_fill<'a>(
3347        &'a self,
3348        node_data: &'a NodeData,
3349        node_id: &NodeId,
3350        node_state: &StyledNodeState,
3351    ) -> Option<&'a ColumnFillValue> {
3352        self.get_property(node_data, node_id, node_state, &CssPropertyType::ColumnFill)
3353            .and_then(|p| p.as_column_fill())
3354    }
3355
3356    /// Method for getting column-rule-width property
3357    pub fn get_column_rule_width<'a>(
3358        &'a self,
3359        node_data: &'a NodeData,
3360        node_id: &NodeId,
3361        node_state: &StyledNodeState,
3362    ) -> Option<&'a ColumnRuleWidthValue> {
3363        self.get_property(
3364            node_data,
3365            node_id,
3366            node_state,
3367            &CssPropertyType::ColumnRuleWidth,
3368        )
3369        .and_then(|p| p.as_column_rule_width())
3370    }
3371
3372    /// Method for getting column-rule-style property
3373    pub fn get_column_rule_style<'a>(
3374        &'a self,
3375        node_data: &'a NodeData,
3376        node_id: &NodeId,
3377        node_state: &StyledNodeState,
3378    ) -> Option<&'a ColumnRuleStyleValue> {
3379        self.get_property(
3380            node_data,
3381            node_id,
3382            node_state,
3383            &CssPropertyType::ColumnRuleStyle,
3384        )
3385        .and_then(|p| p.as_column_rule_style())
3386    }
3387
3388    /// Method for getting column-rule-color property
3389    pub fn get_column_rule_color<'a>(
3390        &'a self,
3391        node_data: &'a NodeData,
3392        node_id: &NodeId,
3393        node_state: &StyledNodeState,
3394    ) -> Option<&'a ColumnRuleColorValue> {
3395        self.get_property(
3396            node_data,
3397            node_id,
3398            node_state,
3399            &CssPropertyType::ColumnRuleColor,
3400        )
3401        .and_then(|p| p.as_column_rule_color())
3402    }
3403
3404    /// Method for getting flow-into property
3405    pub fn get_flow_into<'a>(
3406        &'a self,
3407        node_data: &'a NodeData,
3408        node_id: &NodeId,
3409        node_state: &StyledNodeState,
3410    ) -> Option<&'a FlowIntoValue> {
3411        self.get_property(node_data, node_id, node_state, &CssPropertyType::FlowInto)
3412            .and_then(|p| p.as_flow_into())
3413    }
3414
3415    /// Method for getting flow-from property
3416    pub fn get_flow_from<'a>(
3417        &'a self,
3418        node_data: &'a NodeData,
3419        node_id: &NodeId,
3420        node_state: &StyledNodeState,
3421    ) -> Option<&'a FlowFromValue> {
3422        self.get_property(node_data, node_id, node_state, &CssPropertyType::FlowFrom)
3423            .and_then(|p| p.as_flow_from())
3424    }
3425
3426    /// Method for getting shape-margin property
3427    pub fn get_shape_margin<'a>(
3428        &'a self,
3429        node_data: &'a NodeData,
3430        node_id: &NodeId,
3431        node_state: &StyledNodeState,
3432    ) -> Option<&'a ShapeMarginValue> {
3433        self.get_property(
3434            node_data,
3435            node_id,
3436            node_state,
3437            &CssPropertyType::ShapeMargin,
3438        )
3439        .and_then(|p| p.as_shape_margin())
3440    }
3441
3442    /// Method for getting shape-image-threshold property
3443    pub fn get_shape_image_threshold<'a>(
3444        &'a self,
3445        node_data: &'a NodeData,
3446        node_id: &NodeId,
3447        node_state: &StyledNodeState,
3448    ) -> Option<&'a ShapeImageThresholdValue> {
3449        self.get_property(
3450            node_data,
3451            node_id,
3452            node_state,
3453            &CssPropertyType::ShapeImageThreshold,
3454        )
3455        .and_then(|p| p.as_shape_image_threshold())
3456    }
3457
3458    /// Method for getting content property
3459    pub fn get_content<'a>(
3460        &'a self,
3461        node_data: &'a NodeData,
3462        node_id: &NodeId,
3463        node_state: &StyledNodeState,
3464    ) -> Option<&'a ContentValue> {
3465        self.get_property(node_data, node_id, node_state, &CssPropertyType::Content)
3466            .and_then(|p| p.as_content())
3467    }
3468
3469    /// Method for getting counter-reset property
3470    pub fn get_counter_reset<'a>(
3471        &'a self,
3472        node_data: &'a NodeData,
3473        node_id: &NodeId,
3474        node_state: &StyledNodeState,
3475    ) -> Option<&'a CounterResetValue> {
3476        self.get_property(
3477            node_data,
3478            node_id,
3479            node_state,
3480            &CssPropertyType::CounterReset,
3481        )
3482        .and_then(|p| p.as_counter_reset())
3483    }
3484
3485    /// Method for getting counter-increment property
3486    pub fn get_counter_increment<'a>(
3487        &'a self,
3488        node_data: &'a NodeData,
3489        node_id: &NodeId,
3490        node_state: &StyledNodeState,
3491    ) -> Option<&'a CounterIncrementValue> {
3492        self.get_property(
3493            node_data,
3494            node_id,
3495            node_state,
3496            &CssPropertyType::CounterIncrement,
3497        )
3498        .and_then(|p| p.as_counter_increment())
3499    }
3500
3501    /// Method for getting string-set property
3502    pub fn get_string_set<'a>(
3503        &'a self,
3504        node_data: &'a NodeData,
3505        node_id: &NodeId,
3506        node_state: &StyledNodeState,
3507    ) -> Option<&'a StringSetValue> {
3508        self.get_property(node_data, node_id, node_state, &CssPropertyType::StringSet)
3509            .and_then(|p| p.as_string_set())
3510    }
3511    pub fn get_text_align<'a>(
3512        &'a self,
3513        node_data: &'a NodeData,
3514        node_id: &NodeId,
3515        node_state: &StyledNodeState,
3516    ) -> Option<&'a StyleTextAlignValue> {
3517        self.get_property(node_data, node_id, node_state, &CssPropertyType::TextAlign)
3518            .and_then(|p| p.as_text_align())
3519    }
3520    pub fn get_user_select<'a>(
3521        &'a self,
3522        node_data: &'a NodeData,
3523        node_id: &NodeId,
3524        node_state: &StyledNodeState,
3525    ) -> Option<&'a StyleUserSelectValue> {
3526        self.get_property(node_data, node_id, node_state, &CssPropertyType::UserSelect)
3527            .and_then(|p| p.as_user_select())
3528    }
3529    pub fn get_text_decoration<'a>(
3530        &'a self,
3531        node_data: &'a NodeData,
3532        node_id: &NodeId,
3533        node_state: &StyledNodeState,
3534    ) -> Option<&'a StyleTextDecorationValue> {
3535        self.get_property(
3536            node_data,
3537            node_id,
3538            node_state,
3539            &CssPropertyType::TextDecoration,
3540        )
3541        .and_then(|p| p.as_text_decoration())
3542    }
3543    pub fn get_vertical_align<'a>(
3544        &'a self,
3545        node_data: &'a NodeData,
3546        node_id: &NodeId,
3547        node_state: &StyledNodeState,
3548    ) -> Option<&'a StyleVerticalAlignValue> {
3549        self.get_property(
3550            node_data,
3551            node_id,
3552            node_state,
3553            &CssPropertyType::VerticalAlign,
3554        )
3555        .and_then(|p| p.as_vertical_align())
3556    }
3557    pub fn get_line_height<'a>(
3558        &'a self,
3559        node_data: &'a NodeData,
3560        node_id: &NodeId,
3561        node_state: &StyledNodeState,
3562    ) -> Option<&'a StyleLineHeightValue> {
3563        self.get_property(node_data, node_id, node_state, &CssPropertyType::LineHeight)
3564            .and_then(|p| p.as_line_height())
3565    }
3566    pub fn get_letter_spacing<'a>(
3567        &'a self,
3568        node_data: &'a NodeData,
3569        node_id: &NodeId,
3570        node_state: &StyledNodeState,
3571    ) -> Option<&'a StyleLetterSpacingValue> {
3572        self.get_property(
3573            node_data,
3574            node_id,
3575            node_state,
3576            &CssPropertyType::LetterSpacing,
3577        )
3578        .and_then(|p| p.as_letter_spacing())
3579    }
3580    pub fn get_word_spacing<'a>(
3581        &'a self,
3582        node_data: &'a NodeData,
3583        node_id: &NodeId,
3584        node_state: &StyledNodeState,
3585    ) -> Option<&'a StyleWordSpacingValue> {
3586        self.get_property(
3587            node_data,
3588            node_id,
3589            node_state,
3590            &CssPropertyType::WordSpacing,
3591        )
3592        .and_then(|p| p.as_word_spacing())
3593    }
3594    pub fn get_tab_size<'a>(
3595        &'a self,
3596        node_data: &'a NodeData,
3597        node_id: &NodeId,
3598        node_state: &StyledNodeState,
3599    ) -> Option<&'a StyleTabSizeValue> {
3600        self.get_property(node_data, node_id, node_state, &CssPropertyType::TabSize)
3601            .and_then(|p| p.as_tab_size())
3602    }
3603    pub fn get_cursor<'a>(
3604        &'a self,
3605        node_data: &'a NodeData,
3606        node_id: &NodeId,
3607        node_state: &StyledNodeState,
3608    ) -> Option<&'a StyleCursorValue> {
3609        self.get_property(node_data, node_id, node_state, &CssPropertyType::Cursor)
3610            .and_then(|p| p.as_cursor())
3611    }
3612    pub fn get_box_shadow_left<'a>(
3613        &'a self,
3614        node_data: &'a NodeData,
3615        node_id: &NodeId,
3616        node_state: &StyledNodeState,
3617    ) -> Option<&'a StyleBoxShadowValue> {
3618        self.get_property(
3619            node_data,
3620            node_id,
3621            node_state,
3622            &CssPropertyType::BoxShadowLeft,
3623        )
3624        .and_then(|p| p.as_box_shadow_left())
3625    }
3626    pub fn get_box_shadow_right<'a>(
3627        &'a self,
3628        node_data: &'a NodeData,
3629        node_id: &NodeId,
3630        node_state: &StyledNodeState,
3631    ) -> Option<&'a StyleBoxShadowValue> {
3632        self.get_property(
3633            node_data,
3634            node_id,
3635            node_state,
3636            &CssPropertyType::BoxShadowRight,
3637        )
3638        .and_then(|p| p.as_box_shadow_right())
3639    }
3640    pub fn get_box_shadow_top<'a>(
3641        &'a self,
3642        node_data: &'a NodeData,
3643        node_id: &NodeId,
3644        node_state: &StyledNodeState,
3645    ) -> Option<&'a StyleBoxShadowValue> {
3646        self.get_property(
3647            node_data,
3648            node_id,
3649            node_state,
3650            &CssPropertyType::BoxShadowTop,
3651        )
3652        .and_then(|p| p.as_box_shadow_top())
3653    }
3654    pub fn get_box_shadow_bottom<'a>(
3655        &'a self,
3656        node_data: &'a NodeData,
3657        node_id: &NodeId,
3658        node_state: &StyledNodeState,
3659    ) -> Option<&'a StyleBoxShadowValue> {
3660        self.get_property(
3661            node_data,
3662            node_id,
3663            node_state,
3664            &CssPropertyType::BoxShadowBottom,
3665        )
3666        .and_then(|p| p.as_box_shadow_bottom())
3667    }
3668    pub fn get_border_top_color<'a>(
3669        &'a self,
3670        node_data: &'a NodeData,
3671        node_id: &NodeId,
3672        node_state: &StyledNodeState,
3673    ) -> Option<&'a StyleBorderTopColorValue> {
3674        self.get_property(
3675            node_data,
3676            node_id,
3677            node_state,
3678            &CssPropertyType::BorderTopColor,
3679        )
3680        .and_then(|p| p.as_border_top_color())
3681    }
3682    pub fn get_border_left_color<'a>(
3683        &'a self,
3684        node_data: &'a NodeData,
3685        node_id: &NodeId,
3686        node_state: &StyledNodeState,
3687    ) -> Option<&'a StyleBorderLeftColorValue> {
3688        self.get_property(
3689            node_data,
3690            node_id,
3691            node_state,
3692            &CssPropertyType::BorderLeftColor,
3693        )
3694        .and_then(|p| p.as_border_left_color())
3695    }
3696    pub fn get_border_right_color<'a>(
3697        &'a self,
3698        node_data: &'a NodeData,
3699        node_id: &NodeId,
3700        node_state: &StyledNodeState,
3701    ) -> Option<&'a StyleBorderRightColorValue> {
3702        self.get_property(
3703            node_data,
3704            node_id,
3705            node_state,
3706            &CssPropertyType::BorderRightColor,
3707        )
3708        .and_then(|p| p.as_border_right_color())
3709    }
3710    pub fn get_border_bottom_color<'a>(
3711        &'a self,
3712        node_data: &'a NodeData,
3713        node_id: &NodeId,
3714        node_state: &StyledNodeState,
3715    ) -> Option<&'a StyleBorderBottomColorValue> {
3716        self.get_property(
3717            node_data,
3718            node_id,
3719            node_state,
3720            &CssPropertyType::BorderBottomColor,
3721        )
3722        .and_then(|p| p.as_border_bottom_color())
3723    }
3724    pub fn get_border_top_style<'a>(
3725        &'a self,
3726        node_data: &'a NodeData,
3727        node_id: &NodeId,
3728        node_state: &StyledNodeState,
3729    ) -> Option<&'a StyleBorderTopStyleValue> {
3730        self.get_property(
3731            node_data,
3732            node_id,
3733            node_state,
3734            &CssPropertyType::BorderTopStyle,
3735        )
3736        .and_then(|p| p.as_border_top_style())
3737    }
3738    pub fn get_border_left_style<'a>(
3739        &'a self,
3740        node_data: &'a NodeData,
3741        node_id: &NodeId,
3742        node_state: &StyledNodeState,
3743    ) -> Option<&'a StyleBorderLeftStyleValue> {
3744        self.get_property(
3745            node_data,
3746            node_id,
3747            node_state,
3748            &CssPropertyType::BorderLeftStyle,
3749        )
3750        .and_then(|p| p.as_border_left_style())
3751    }
3752    pub fn get_border_right_style<'a>(
3753        &'a self,
3754        node_data: &'a NodeData,
3755        node_id: &NodeId,
3756        node_state: &StyledNodeState,
3757    ) -> Option<&'a StyleBorderRightStyleValue> {
3758        self.get_property(
3759            node_data,
3760            node_id,
3761            node_state,
3762            &CssPropertyType::BorderRightStyle,
3763        )
3764        .and_then(|p| p.as_border_right_style())
3765    }
3766    pub fn get_border_bottom_style<'a>(
3767        &'a self,
3768        node_data: &'a NodeData,
3769        node_id: &NodeId,
3770        node_state: &StyledNodeState,
3771    ) -> Option<&'a StyleBorderBottomStyleValue> {
3772        self.get_property(
3773            node_data,
3774            node_id,
3775            node_state,
3776            &CssPropertyType::BorderBottomStyle,
3777        )
3778        .and_then(|p| p.as_border_bottom_style())
3779    }
3780    pub fn get_border_top_left_radius<'a>(
3781        &'a self,
3782        node_data: &'a NodeData,
3783        node_id: &NodeId,
3784        node_state: &StyledNodeState,
3785    ) -> Option<&'a StyleBorderTopLeftRadiusValue> {
3786        self.get_property(
3787            node_data,
3788            node_id,
3789            node_state,
3790            &CssPropertyType::BorderTopLeftRadius,
3791        )
3792        .and_then(|p| p.as_border_top_left_radius())
3793    }
3794    pub fn get_border_top_right_radius<'a>(
3795        &'a self,
3796        node_data: &'a NodeData,
3797        node_id: &NodeId,
3798        node_state: &StyledNodeState,
3799    ) -> Option<&'a StyleBorderTopRightRadiusValue> {
3800        self.get_property(
3801            node_data,
3802            node_id,
3803            node_state,
3804            &CssPropertyType::BorderTopRightRadius,
3805        )
3806        .and_then(|p| p.as_border_top_right_radius())
3807    }
3808    pub fn get_border_bottom_left_radius<'a>(
3809        &'a self,
3810        node_data: &'a NodeData,
3811        node_id: &NodeId,
3812        node_state: &StyledNodeState,
3813    ) -> Option<&'a StyleBorderBottomLeftRadiusValue> {
3814        self.get_property(
3815            node_data,
3816            node_id,
3817            node_state,
3818            &CssPropertyType::BorderBottomLeftRadius,
3819        )
3820        .and_then(|p| p.as_border_bottom_left_radius())
3821    }
3822    pub fn get_border_bottom_right_radius<'a>(
3823        &'a self,
3824        node_data: &'a NodeData,
3825        node_id: &NodeId,
3826        node_state: &StyledNodeState,
3827    ) -> Option<&'a StyleBorderBottomRightRadiusValue> {
3828        self.get_property(
3829            node_data,
3830            node_id,
3831            node_state,
3832            &CssPropertyType::BorderBottomRightRadius,
3833        )
3834        .and_then(|p| p.as_border_bottom_right_radius())
3835    }
3836    pub fn get_opacity<'a>(
3837        &'a self,
3838        node_data: &'a NodeData,
3839        node_id: &NodeId,
3840        node_state: &StyledNodeState,
3841    ) -> Option<&'a StyleOpacityValue> {
3842        self.get_property(node_data, node_id, node_state, &CssPropertyType::Opacity)
3843            .and_then(|p| p.as_opacity())
3844    }
3845    pub fn get_transform<'a>(
3846        &'a self,
3847        node_data: &'a NodeData,
3848        node_id: &NodeId,
3849        node_state: &StyledNodeState,
3850    ) -> Option<&'a StyleTransformVecValue> {
3851        self.get_property(node_data, node_id, node_state, &CssPropertyType::Transform)
3852            .and_then(|p| p.as_transform())
3853    }
3854    pub fn get_transform_origin<'a>(
3855        &'a self,
3856        node_data: &'a NodeData,
3857        node_id: &NodeId,
3858        node_state: &StyledNodeState,
3859    ) -> Option<&'a StyleTransformOriginValue> {
3860        self.get_property(
3861            node_data,
3862            node_id,
3863            node_state,
3864            &CssPropertyType::TransformOrigin,
3865        )
3866        .and_then(|p| p.as_transform_origin())
3867    }
3868    pub fn get_perspective_origin<'a>(
3869        &'a self,
3870        node_data: &'a NodeData,
3871        node_id: &NodeId,
3872        node_state: &StyledNodeState,
3873    ) -> Option<&'a StylePerspectiveOriginValue> {
3874        self.get_property(
3875            node_data,
3876            node_id,
3877            node_state,
3878            &CssPropertyType::PerspectiveOrigin,
3879        )
3880        .and_then(|p| p.as_perspective_origin())
3881    }
3882    pub fn get_backface_visibility<'a>(
3883        &'a self,
3884        node_data: &'a NodeData,
3885        node_id: &NodeId,
3886        node_state: &StyledNodeState,
3887    ) -> Option<&'a StyleBackfaceVisibilityValue> {
3888        self.get_property(
3889            node_data,
3890            node_id,
3891            node_state,
3892            &CssPropertyType::BackfaceVisibility,
3893        )
3894        .and_then(|p| p.as_backface_visibility())
3895    }
3896    pub fn get_display<'a>(
3897        &'a self,
3898        node_data: &'a NodeData,
3899        node_id: &NodeId,
3900        node_state: &StyledNodeState,
3901    ) -> Option<&'a LayoutDisplayValue> {
3902        self.get_property(node_data, node_id, node_state, &CssPropertyType::Display)
3903            .and_then(|p| p.as_display())
3904    }
3905    pub fn get_float<'a>(
3906        &'a self,
3907        node_data: &'a NodeData,
3908        node_id: &NodeId,
3909        node_state: &StyledNodeState,
3910    ) -> Option<&'a LayoutFloatValue> {
3911        self.get_property(node_data, node_id, node_state, &CssPropertyType::Float)
3912            .and_then(|p| p.as_float())
3913    }
3914    pub fn get_box_sizing<'a>(
3915        &'a self,
3916        node_data: &'a NodeData,
3917        node_id: &NodeId,
3918        node_state: &StyledNodeState,
3919    ) -> Option<&'a LayoutBoxSizingValue> {
3920        self.get_property(node_data, node_id, node_state, &CssPropertyType::BoxSizing)
3921            .and_then(|p| p.as_box_sizing())
3922    }
3923    pub fn get_width<'a>(
3924        &'a self,
3925        node_data: &'a NodeData,
3926        node_id: &NodeId,
3927        node_state: &StyledNodeState,
3928    ) -> Option<&'a LayoutWidthValue> {
3929        self.get_property(node_data, node_id, node_state, &CssPropertyType::Width)
3930            .and_then(|p| p.as_width())
3931    }
3932    pub fn get_height<'a>(
3933        &'a self,
3934        node_data: &'a NodeData,
3935        node_id: &NodeId,
3936        node_state: &StyledNodeState,
3937    ) -> Option<&'a LayoutHeightValue> {
3938        self.get_property(node_data, node_id, node_state, &CssPropertyType::Height)
3939            .and_then(|p| p.as_height())
3940    }
3941    pub fn get_min_width<'a>(
3942        &'a self,
3943        node_data: &'a NodeData,
3944        node_id: &NodeId,
3945        node_state: &StyledNodeState,
3946    ) -> Option<&'a LayoutMinWidthValue> {
3947        self.get_property(node_data, node_id, node_state, &CssPropertyType::MinWidth)
3948            .and_then(|p| p.as_min_width())
3949    }
3950    pub fn get_min_height<'a>(
3951        &'a self,
3952        node_data: &'a NodeData,
3953        node_id: &NodeId,
3954        node_state: &StyledNodeState,
3955    ) -> Option<&'a LayoutMinHeightValue> {
3956        self.get_property(node_data, node_id, node_state, &CssPropertyType::MinHeight)
3957            .and_then(|p| p.as_min_height())
3958    }
3959    pub fn get_max_width<'a>(
3960        &'a self,
3961        node_data: &'a NodeData,
3962        node_id: &NodeId,
3963        node_state: &StyledNodeState,
3964    ) -> Option<&'a LayoutMaxWidthValue> {
3965        self.get_property(node_data, node_id, node_state, &CssPropertyType::MaxWidth)
3966            .and_then(|p| p.as_max_width())
3967    }
3968    pub fn get_max_height<'a>(
3969        &'a self,
3970        node_data: &'a NodeData,
3971        node_id: &NodeId,
3972        node_state: &StyledNodeState,
3973    ) -> Option<&'a LayoutMaxHeightValue> {
3974        self.get_property(node_data, node_id, node_state, &CssPropertyType::MaxHeight)
3975            .and_then(|p| p.as_max_height())
3976    }
3977    pub fn get_position<'a>(
3978        &'a self,
3979        node_data: &'a NodeData,
3980        node_id: &NodeId,
3981        node_state: &StyledNodeState,
3982    ) -> Option<&'a LayoutPositionValue> {
3983        self.get_property(node_data, node_id, node_state, &CssPropertyType::Position)
3984            .and_then(|p| p.as_position())
3985    }
3986    pub fn get_top<'a>(
3987        &'a self,
3988        node_data: &'a NodeData,
3989        node_id: &NodeId,
3990        node_state: &StyledNodeState,
3991    ) -> Option<&'a LayoutTopValue> {
3992        self.get_property(node_data, node_id, node_state, &CssPropertyType::Top)
3993            .and_then(|p| p.as_top())
3994    }
3995    pub fn get_bottom<'a>(
3996        &'a self,
3997        node_data: &'a NodeData,
3998        node_id: &NodeId,
3999        node_state: &StyledNodeState,
4000    ) -> Option<&'a LayoutInsetBottomValue> {
4001        self.get_property(node_data, node_id, node_state, &CssPropertyType::Bottom)
4002            .and_then(|p| p.as_bottom())
4003    }
4004    pub fn get_right<'a>(
4005        &'a self,
4006        node_data: &'a NodeData,
4007        node_id: &NodeId,
4008        node_state: &StyledNodeState,
4009    ) -> Option<&'a LayoutRightValue> {
4010        self.get_property(node_data, node_id, node_state, &CssPropertyType::Right)
4011            .and_then(|p| p.as_right())
4012    }
4013    pub fn get_left<'a>(
4014        &'a self,
4015        node_data: &'a NodeData,
4016        node_id: &NodeId,
4017        node_state: &StyledNodeState,
4018    ) -> Option<&'a LayoutLeftValue> {
4019        self.get_property(node_data, node_id, node_state, &CssPropertyType::Left)
4020            .and_then(|p| p.as_left())
4021    }
4022    pub fn get_padding_top<'a>(
4023        &'a self,
4024        node_data: &'a NodeData,
4025        node_id: &NodeId,
4026        node_state: &StyledNodeState,
4027    ) -> Option<&'a LayoutPaddingTopValue> {
4028        self.get_property(node_data, node_id, node_state, &CssPropertyType::PaddingTop)
4029            .and_then(|p| p.as_padding_top())
4030    }
4031    pub fn get_padding_bottom<'a>(
4032        &'a self,
4033        node_data: &'a NodeData,
4034        node_id: &NodeId,
4035        node_state: &StyledNodeState,
4036    ) -> Option<&'a LayoutPaddingBottomValue> {
4037        self.get_property(
4038            node_data,
4039            node_id,
4040            node_state,
4041            &CssPropertyType::PaddingBottom,
4042        )
4043        .and_then(|p| p.as_padding_bottom())
4044    }
4045    pub fn get_padding_left<'a>(
4046        &'a self,
4047        node_data: &'a NodeData,
4048        node_id: &NodeId,
4049        node_state: &StyledNodeState,
4050    ) -> Option<&'a LayoutPaddingLeftValue> {
4051        self.get_property(
4052            node_data,
4053            node_id,
4054            node_state,
4055            &CssPropertyType::PaddingLeft,
4056        )
4057        .and_then(|p| p.as_padding_left())
4058    }
4059    pub fn get_padding_right<'a>(
4060        &'a self,
4061        node_data: &'a NodeData,
4062        node_id: &NodeId,
4063        node_state: &StyledNodeState,
4064    ) -> Option<&'a LayoutPaddingRightValue> {
4065        self.get_property(
4066            node_data,
4067            node_id,
4068            node_state,
4069            &CssPropertyType::PaddingRight,
4070        )
4071        .and_then(|p| p.as_padding_right())
4072    }
4073    pub fn get_margin_top<'a>(
4074        &'a self,
4075        node_data: &'a NodeData,
4076        node_id: &NodeId,
4077        node_state: &StyledNodeState,
4078    ) -> Option<&'a LayoutMarginTopValue> {
4079        self.get_property(node_data, node_id, node_state, &CssPropertyType::MarginTop)
4080            .and_then(|p| p.as_margin_top())
4081    }
4082    pub fn get_margin_bottom<'a>(
4083        &'a self,
4084        node_data: &'a NodeData,
4085        node_id: &NodeId,
4086        node_state: &StyledNodeState,
4087    ) -> Option<&'a LayoutMarginBottomValue> {
4088        self.get_property(
4089            node_data,
4090            node_id,
4091            node_state,
4092            &CssPropertyType::MarginBottom,
4093        )
4094        .and_then(|p| p.as_margin_bottom())
4095    }
4096    pub fn get_margin_left<'a>(
4097        &'a self,
4098        node_data: &'a NodeData,
4099        node_id: &NodeId,
4100        node_state: &StyledNodeState,
4101    ) -> Option<&'a LayoutMarginLeftValue> {
4102        self.get_property(node_data, node_id, node_state, &CssPropertyType::MarginLeft)
4103            .and_then(|p| p.as_margin_left())
4104    }
4105    pub fn get_margin_right<'a>(
4106        &'a self,
4107        node_data: &'a NodeData,
4108        node_id: &NodeId,
4109        node_state: &StyledNodeState,
4110    ) -> Option<&'a LayoutMarginRightValue> {
4111        self.get_property(
4112            node_data,
4113            node_id,
4114            node_state,
4115            &CssPropertyType::MarginRight,
4116        )
4117        .and_then(|p| p.as_margin_right())
4118    }
4119    pub fn get_border_top_width<'a>(
4120        &'a self,
4121        node_data: &'a NodeData,
4122        node_id: &NodeId,
4123        node_state: &StyledNodeState,
4124    ) -> Option<&'a LayoutBorderTopWidthValue> {
4125        self.get_property(
4126            node_data,
4127            node_id,
4128            node_state,
4129            &CssPropertyType::BorderTopWidth,
4130        )
4131        .and_then(|p| p.as_border_top_width())
4132    }
4133    pub fn get_border_left_width<'a>(
4134        &'a self,
4135        node_data: &'a NodeData,
4136        node_id: &NodeId,
4137        node_state: &StyledNodeState,
4138    ) -> Option<&'a LayoutBorderLeftWidthValue> {
4139        self.get_property(
4140            node_data,
4141            node_id,
4142            node_state,
4143            &CssPropertyType::BorderLeftWidth,
4144        )
4145        .and_then(|p| p.as_border_left_width())
4146    }
4147    pub fn get_border_right_width<'a>(
4148        &'a self,
4149        node_data: &'a NodeData,
4150        node_id: &NodeId,
4151        node_state: &StyledNodeState,
4152    ) -> Option<&'a LayoutBorderRightWidthValue> {
4153        self.get_property(
4154            node_data,
4155            node_id,
4156            node_state,
4157            &CssPropertyType::BorderRightWidth,
4158        )
4159        .and_then(|p| p.as_border_right_width())
4160    }
4161    pub fn get_border_bottom_width<'a>(
4162        &'a self,
4163        node_data: &'a NodeData,
4164        node_id: &NodeId,
4165        node_state: &StyledNodeState,
4166    ) -> Option<&'a LayoutBorderBottomWidthValue> {
4167        self.get_property(
4168            node_data,
4169            node_id,
4170            node_state,
4171            &CssPropertyType::BorderBottomWidth,
4172        )
4173        .and_then(|p| p.as_border_bottom_width())
4174    }
4175    pub fn get_overflow_x<'a>(
4176        &'a self,
4177        node_data: &'a NodeData,
4178        node_id: &NodeId,
4179        node_state: &StyledNodeState,
4180    ) -> Option<&'a LayoutOverflowValue> {
4181        self.get_property(node_data, node_id, node_state, &CssPropertyType::OverflowX)
4182            .and_then(|p| p.as_overflow_x())
4183    }
4184    pub fn get_overflow_y<'a>(
4185        &'a self,
4186        node_data: &'a NodeData,
4187        node_id: &NodeId,
4188        node_state: &StyledNodeState,
4189    ) -> Option<&'a LayoutOverflowValue> {
4190        self.get_property(node_data, node_id, node_state, &CssPropertyType::OverflowY)
4191            .and_then(|p| p.as_overflow_y())
4192    }
4193    pub fn get_overflow_block<'a>(
4194        &'a self,
4195        node_data: &'a NodeData,
4196        node_id: &NodeId,
4197        node_state: &StyledNodeState,
4198    ) -> Option<&'a LayoutOverflowValue> {
4199        self.get_property(node_data, node_id, node_state, &CssPropertyType::OverflowBlock)
4200            .and_then(|p| p.as_overflow_block())
4201    }
4202    pub fn get_overflow_inline<'a>(
4203        &'a self,
4204        node_data: &'a NodeData,
4205        node_id: &NodeId,
4206        node_state: &StyledNodeState,
4207    ) -> Option<&'a LayoutOverflowValue> {
4208        self.get_property(node_data, node_id, node_state, &CssPropertyType::OverflowInline)
4209            .and_then(|p| p.as_overflow_inline())
4210    }
4211    pub fn get_flex_direction<'a>(
4212        &'a self,
4213        node_data: &'a NodeData,
4214        node_id: &NodeId,
4215        node_state: &StyledNodeState,
4216    ) -> Option<&'a LayoutFlexDirectionValue> {
4217        self.get_property(
4218            node_data,
4219            node_id,
4220            node_state,
4221            &CssPropertyType::FlexDirection,
4222        )
4223        .and_then(|p| p.as_flex_direction())
4224    }
4225    pub fn get_flex_wrap<'a>(
4226        &'a self,
4227        node_data: &'a NodeData,
4228        node_id: &NodeId,
4229        node_state: &StyledNodeState,
4230    ) -> Option<&'a LayoutFlexWrapValue> {
4231        self.get_property(node_data, node_id, node_state, &CssPropertyType::FlexWrap)
4232            .and_then(|p| p.as_flex_wrap())
4233    }
4234    pub fn get_flex_grow<'a>(
4235        &'a self,
4236        node_data: &'a NodeData,
4237        node_id: &NodeId,
4238        node_state: &StyledNodeState,
4239    ) -> Option<&'a LayoutFlexGrowValue> {
4240        self.get_property(node_data, node_id, node_state, &CssPropertyType::FlexGrow)
4241            .and_then(|p| p.as_flex_grow())
4242    }
4243    pub fn get_flex_shrink<'a>(
4244        &'a self,
4245        node_data: &'a NodeData,
4246        node_id: &NodeId,
4247        node_state: &StyledNodeState,
4248    ) -> Option<&'a LayoutFlexShrinkValue> {
4249        self.get_property(node_data, node_id, node_state, &CssPropertyType::FlexShrink)
4250            .and_then(|p| p.as_flex_shrink())
4251    }
4252    pub fn get_justify_content<'a>(
4253        &'a self,
4254        node_data: &'a NodeData,
4255        node_id: &NodeId,
4256        node_state: &StyledNodeState,
4257    ) -> Option<&'a LayoutJustifyContentValue> {
4258        self.get_property(
4259            node_data,
4260            node_id,
4261            node_state,
4262            &CssPropertyType::JustifyContent,
4263        )
4264        .and_then(|p| p.as_justify_content())
4265    }
4266    pub fn get_align_items<'a>(
4267        &'a self,
4268        node_data: &'a NodeData,
4269        node_id: &NodeId,
4270        node_state: &StyledNodeState,
4271    ) -> Option<&'a LayoutAlignItemsValue> {
4272        self.get_property(node_data, node_id, node_state, &CssPropertyType::AlignItems)
4273            .and_then(|p| p.as_align_items())
4274    }
4275    pub fn get_align_content<'a>(
4276        &'a self,
4277        node_data: &'a NodeData,
4278        node_id: &NodeId,
4279        node_state: &StyledNodeState,
4280    ) -> Option<&'a LayoutAlignContentValue> {
4281        self.get_property(
4282            node_data,
4283            node_id,
4284            node_state,
4285            &CssPropertyType::AlignContent,
4286        )
4287        .and_then(|p| p.as_align_content())
4288    }
4289    pub fn get_mix_blend_mode<'a>(
4290        &'a self,
4291        node_data: &'a NodeData,
4292        node_id: &NodeId,
4293        node_state: &StyledNodeState,
4294    ) -> Option<&'a StyleMixBlendModeValue> {
4295        self.get_property(
4296            node_data,
4297            node_id,
4298            node_state,
4299            &CssPropertyType::MixBlendMode,
4300        )
4301        .and_then(|p| p.as_mix_blend_mode())
4302    }
4303    pub fn get_filter<'a>(
4304        &'a self,
4305        node_data: &'a NodeData,
4306        node_id: &NodeId,
4307        node_state: &StyledNodeState,
4308    ) -> Option<&'a StyleFilterVecValue> {
4309        self.get_property(node_data, node_id, node_state, &CssPropertyType::Filter)
4310            .and_then(|p| p.as_filter())
4311    }
4312    pub fn get_backdrop_filter<'a>(
4313        &'a self,
4314        node_data: &'a NodeData,
4315        node_id: &NodeId,
4316        node_state: &StyledNodeState,
4317    ) -> Option<&'a StyleFilterVecValue> {
4318        self.get_property(node_data, node_id, node_state, &CssPropertyType::BackdropFilter)
4319            .and_then(|p| p.as_backdrop_filter())
4320    }
4321    pub fn get_text_shadow<'a>(
4322        &'a self,
4323        node_data: &'a NodeData,
4324        node_id: &NodeId,
4325        node_state: &StyledNodeState,
4326    ) -> Option<&'a StyleBoxShadowValue> {
4327        self.get_property(node_data, node_id, node_state, &CssPropertyType::TextShadow)
4328            .and_then(|p| p.as_text_shadow())
4329    }
4330    pub fn get_list_style_type<'a>(
4331        &'a self,
4332        node_data: &'a NodeData,
4333        node_id: &NodeId,
4334        node_state: &StyledNodeState,
4335    ) -> Option<&'a StyleListStyleTypeValue> {
4336        self.get_property(
4337            node_data,
4338            node_id,
4339            node_state,
4340            &CssPropertyType::ListStyleType,
4341        )
4342        .and_then(|p| p.as_list_style_type())
4343    }
4344    pub fn get_list_style_position<'a>(
4345        &'a self,
4346        node_data: &'a NodeData,
4347        node_id: &NodeId,
4348        node_state: &StyledNodeState,
4349    ) -> Option<&'a StyleListStylePositionValue> {
4350        self.get_property(
4351            node_data,
4352            node_id,
4353            node_state,
4354            &CssPropertyType::ListStylePosition,
4355        )
4356        .and_then(|p| p.as_list_style_position())
4357    }
4358    pub fn get_table_layout<'a>(
4359        &'a self,
4360        node_data: &'a NodeData,
4361        node_id: &NodeId,
4362        node_state: &StyledNodeState,
4363    ) -> Option<&'a LayoutTableLayoutValue> {
4364        self.get_property(
4365            node_data,
4366            node_id,
4367            node_state,
4368            &CssPropertyType::TableLayout,
4369        )
4370        .and_then(|p| p.as_table_layout())
4371    }
4372    pub fn get_border_collapse<'a>(
4373        &'a self,
4374        node_data: &'a NodeData,
4375        node_id: &NodeId,
4376        node_state: &StyledNodeState,
4377    ) -> Option<&'a StyleBorderCollapseValue> {
4378        self.get_property(
4379            node_data,
4380            node_id,
4381            node_state,
4382            &CssPropertyType::BorderCollapse,
4383        )
4384        .and_then(|p| p.as_border_collapse())
4385    }
4386    pub fn get_border_spacing<'a>(
4387        &'a self,
4388        node_data: &'a NodeData,
4389        node_id: &NodeId,
4390        node_state: &StyledNodeState,
4391    ) -> Option<&'a LayoutBorderSpacingValue> {
4392        self.get_property(
4393            node_data,
4394            node_id,
4395            node_state,
4396            &CssPropertyType::BorderSpacing,
4397        )
4398        .and_then(|p| p.as_border_spacing())
4399    }
4400    pub fn get_caption_side<'a>(
4401        &'a self,
4402        node_data: &'a NodeData,
4403        node_id: &NodeId,
4404        node_state: &StyledNodeState,
4405    ) -> Option<&'a StyleCaptionSideValue> {
4406        self.get_property(
4407            node_data,
4408            node_id,
4409            node_state,
4410            &CssPropertyType::CaptionSide,
4411        )
4412        .and_then(|p| p.as_caption_side())
4413    }
4414    pub fn get_empty_cells<'a>(
4415        &'a self,
4416        node_data: &'a NodeData,
4417        node_id: &NodeId,
4418        node_state: &StyledNodeState,
4419    ) -> Option<&'a StyleEmptyCellsValue> {
4420        self.get_property(node_data, node_id, node_state, &CssPropertyType::EmptyCells)
4421            .and_then(|p| p.as_empty_cells())
4422    }
4423
4424    // Width calculation methods
4425    pub fn calc_width(
4426        &self,
4427        node_data: &NodeData,
4428        node_id: &NodeId,
4429        styled_node_state: &StyledNodeState,
4430        reference_width: f32,
4431    ) -> f32 {
4432        self.get_width(node_data, node_id, styled_node_state)
4433            .and_then(|w| match w.get_property()? {
4434                LayoutWidth::Px(px) => Some(px.to_pixels_internal(
4435                    reference_width,
4436                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
4437                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
4438                )),
4439                _ => Some(0.0), // min-content/max-content not resolved here
4440            })
4441            .unwrap_or(0.0)
4442    }
4443
4444    pub fn calc_min_width(
4445        &self,
4446        node_data: &NodeData,
4447        node_id: &NodeId,
4448        styled_node_state: &StyledNodeState,
4449        reference_width: f32,
4450    ) -> f32 {
4451        self.get_min_width(node_data, node_id, styled_node_state)
4452            .and_then(|w| {
4453                Some(w.get_property()?.inner.to_pixels_internal(
4454                    reference_width,
4455                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
4456                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
4457                ))
4458            })
4459            .unwrap_or(0.0)
4460    }
4461
4462    pub fn calc_max_width(
4463        &self,
4464        node_data: &NodeData,
4465        node_id: &NodeId,
4466        styled_node_state: &StyledNodeState,
4467        reference_width: f32,
4468    ) -> Option<f32> {
4469        self.get_max_width(node_data, node_id, styled_node_state)
4470            .and_then(|w| {
4471                Some(w.get_property()?.inner.to_pixels_internal(
4472                    reference_width,
4473                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
4474                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
4475                ))
4476            })
4477    }
4478
4479    // Height calculation methods
4480    pub fn calc_height(
4481        &self,
4482        node_data: &NodeData,
4483        node_id: &NodeId,
4484        styled_node_state: &StyledNodeState,
4485        reference_height: f32,
4486    ) -> f32 {
4487        self.get_height(node_data, node_id, styled_node_state)
4488            .and_then(|h| match h.get_property()? {
4489                LayoutHeight::Px(px) => Some(px.to_pixels_internal(
4490                    reference_height,
4491                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
4492                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
4493                )),
4494                _ => Some(0.0), // min-content/max-content not resolved here
4495            })
4496            .unwrap_or(0.0)
4497    }
4498
4499    pub fn calc_min_height(
4500        &self,
4501        node_data: &NodeData,
4502        node_id: &NodeId,
4503        styled_node_state: &StyledNodeState,
4504        reference_height: f32,
4505    ) -> f32 {
4506        self.get_min_height(node_data, node_id, styled_node_state)
4507            .and_then(|h| {
4508                Some(h.get_property()?.inner.to_pixels_internal(
4509                    reference_height,
4510                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
4511                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
4512                ))
4513            })
4514            .unwrap_or(0.0)
4515    }
4516
4517    pub fn calc_max_height(
4518        &self,
4519        node_data: &NodeData,
4520        node_id: &NodeId,
4521        styled_node_state: &StyledNodeState,
4522        reference_height: f32,
4523    ) -> Option<f32> {
4524        self.get_max_height(node_data, node_id, styled_node_state)
4525            .and_then(|h| {
4526                Some(h.get_property()?.inner.to_pixels_internal(
4527                    reference_height,
4528                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
4529                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
4530                ))
4531            })
4532    }
4533
4534    // Position calculation methods
4535    pub fn calc_left(
4536        &self,
4537        node_data: &NodeData,
4538        node_id: &NodeId,
4539        styled_node_state: &StyledNodeState,
4540        reference_width: f32,
4541    ) -> Option<f32> {
4542        self.get_left(node_data, node_id, styled_node_state)
4543            .and_then(|l| {
4544                Some(l.get_property()?.inner.to_pixels_internal(
4545                    reference_width,
4546                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
4547                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
4548                ))
4549            })
4550    }
4551
4552    pub fn calc_right(
4553        &self,
4554        node_data: &NodeData,
4555        node_id: &NodeId,
4556        styled_node_state: &StyledNodeState,
4557        reference_width: f32,
4558    ) -> Option<f32> {
4559        self.get_right(node_data, node_id, styled_node_state)
4560            .and_then(|r| {
4561                Some(r.get_property()?.inner.to_pixels_internal(
4562                    reference_width,
4563                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
4564                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
4565                ))
4566            })
4567    }
4568
4569    pub fn calc_top(
4570        &self,
4571        node_data: &NodeData,
4572        node_id: &NodeId,
4573        styled_node_state: &StyledNodeState,
4574        reference_height: f32,
4575    ) -> Option<f32> {
4576        self.get_top(node_data, node_id, styled_node_state)
4577            .and_then(|t| {
4578                Some(t.get_property()?.inner.to_pixels_internal(
4579                    reference_height,
4580                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
4581                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
4582                ))
4583            })
4584    }
4585
4586    pub fn calc_bottom(
4587        &self,
4588        node_data: &NodeData,
4589        node_id: &NodeId,
4590        styled_node_state: &StyledNodeState,
4591        reference_height: f32,
4592    ) -> Option<f32> {
4593        self.get_bottom(node_data, node_id, styled_node_state)
4594            .and_then(|b| {
4595                Some(b.get_property()?.inner.to_pixels_internal(
4596                    reference_height,
4597                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
4598                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
4599                ))
4600            })
4601    }
4602
4603    // Border calculation methods
4604    pub fn calc_border_left_width(
4605        &self,
4606        node_data: &NodeData,
4607        node_id: &NodeId,
4608        styled_node_state: &StyledNodeState,
4609        reference_width: f32,
4610    ) -> f32 {
4611        self.get_border_left_width(node_data, node_id, styled_node_state)
4612            .and_then(|b| {
4613                Some(b.get_property()?.inner.to_pixels_internal(
4614                    reference_width,
4615                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
4616                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
4617                ))
4618            })
4619            .unwrap_or(0.0)
4620    }
4621
4622    pub fn calc_border_right_width(
4623        &self,
4624        node_data: &NodeData,
4625        node_id: &NodeId,
4626        styled_node_state: &StyledNodeState,
4627        reference_width: f32,
4628    ) -> f32 {
4629        self.get_border_right_width(node_data, node_id, styled_node_state)
4630            .and_then(|b| {
4631                Some(b.get_property()?.inner.to_pixels_internal(
4632                    reference_width,
4633                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
4634                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
4635                ))
4636            })
4637            .unwrap_or(0.0)
4638    }
4639
4640    pub fn calc_border_top_width(
4641        &self,
4642        node_data: &NodeData,
4643        node_id: &NodeId,
4644        styled_node_state: &StyledNodeState,
4645        reference_height: f32,
4646    ) -> f32 {
4647        self.get_border_top_width(node_data, node_id, styled_node_state)
4648            .and_then(|b| {
4649                Some(b.get_property()?.inner.to_pixels_internal(
4650                    reference_height,
4651                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
4652                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
4653                ))
4654            })
4655            .unwrap_or(0.0)
4656    }
4657
4658    pub fn calc_border_bottom_width(
4659        &self,
4660        node_data: &NodeData,
4661        node_id: &NodeId,
4662        styled_node_state: &StyledNodeState,
4663        reference_height: f32,
4664    ) -> f32 {
4665        self.get_border_bottom_width(node_data, node_id, styled_node_state)
4666            .and_then(|b| {
4667                Some(b.get_property()?.inner.to_pixels_internal(
4668                    reference_height,
4669                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
4670                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
4671                ))
4672            })
4673            .unwrap_or(0.0)
4674    }
4675
4676    // Padding calculation methods
4677    pub fn calc_padding_left(
4678        &self,
4679        node_data: &NodeData,
4680        node_id: &NodeId,
4681        styled_node_state: &StyledNodeState,
4682        reference_width: f32,
4683    ) -> f32 {
4684        self.get_padding_left(node_data, node_id, styled_node_state)
4685            .and_then(|p| {
4686                Some(p.get_property()?.inner.to_pixels_internal(
4687                    reference_width,
4688                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
4689                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
4690                ))
4691            })
4692            .unwrap_or(0.0)
4693    }
4694
4695    pub fn calc_padding_right(
4696        &self,
4697        node_data: &NodeData,
4698        node_id: &NodeId,
4699        styled_node_state: &StyledNodeState,
4700        reference_width: f32,
4701    ) -> f32 {
4702        self.get_padding_right(node_data, node_id, styled_node_state)
4703            .and_then(|p| {
4704                Some(p.get_property()?.inner.to_pixels_internal(
4705                    reference_width,
4706                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
4707                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
4708                ))
4709            })
4710            .unwrap_or(0.0)
4711    }
4712
4713    pub fn calc_padding_top(
4714        &self,
4715        node_data: &NodeData,
4716        node_id: &NodeId,
4717        styled_node_state: &StyledNodeState,
4718        reference_height: f32,
4719    ) -> f32 {
4720        self.get_padding_top(node_data, node_id, styled_node_state)
4721            .and_then(|p| {
4722                Some(p.get_property()?.inner.to_pixels_internal(
4723                    reference_height,
4724                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
4725                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
4726                ))
4727            })
4728            .unwrap_or(0.0)
4729    }
4730
4731    pub fn calc_padding_bottom(
4732        &self,
4733        node_data: &NodeData,
4734        node_id: &NodeId,
4735        styled_node_state: &StyledNodeState,
4736        reference_height: f32,
4737    ) -> f32 {
4738        self.get_padding_bottom(node_data, node_id, styled_node_state)
4739            .and_then(|p| {
4740                Some(p.get_property()?.inner.to_pixels_internal(
4741                    reference_height,
4742                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
4743                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
4744                ))
4745            })
4746            .unwrap_or(0.0)
4747    }
4748
4749    // Margin calculation methods
4750    pub fn calc_margin_left(
4751        &self,
4752        node_data: &NodeData,
4753        node_id: &NodeId,
4754        styled_node_state: &StyledNodeState,
4755        reference_width: f32,
4756    ) -> f32 {
4757        self.get_margin_left(node_data, node_id, styled_node_state)
4758            .and_then(|m| {
4759                Some(m.get_property()?.inner.to_pixels_internal(
4760                    reference_width,
4761                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
4762                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
4763                ))
4764            })
4765            .unwrap_or(0.0)
4766    }
4767
4768    pub fn calc_margin_right(
4769        &self,
4770        node_data: &NodeData,
4771        node_id: &NodeId,
4772        styled_node_state: &StyledNodeState,
4773        reference_width: f32,
4774    ) -> f32 {
4775        self.get_margin_right(node_data, node_id, styled_node_state)
4776            .and_then(|m| {
4777                Some(m.get_property()?.inner.to_pixels_internal(
4778                    reference_width,
4779                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
4780                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
4781                ))
4782            })
4783            .unwrap_or(0.0)
4784    }
4785
4786    pub fn calc_margin_top(
4787        &self,
4788        node_data: &NodeData,
4789        node_id: &NodeId,
4790        styled_node_state: &StyledNodeState,
4791        reference_height: f32,
4792    ) -> f32 {
4793        self.get_margin_top(node_data, node_id, styled_node_state)
4794            .and_then(|m| {
4795                Some(m.get_property()?.inner.to_pixels_internal(
4796                    reference_height,
4797                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
4798                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
4799                ))
4800            })
4801            .unwrap_or(0.0)
4802    }
4803
4804    pub fn calc_margin_bottom(
4805        &self,
4806        node_data: &NodeData,
4807        node_id: &NodeId,
4808        styled_node_state: &StyledNodeState,
4809        reference_height: f32,
4810    ) -> f32 {
4811        self.get_margin_bottom(node_data, node_id, styled_node_state)
4812            .and_then(|m| {
4813                Some(m.get_property()?.inner.to_pixels_internal(
4814                    reference_height,
4815                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
4816                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
4817                ))
4818            })
4819            .unwrap_or(0.0)
4820    }
4821
4822    fn resolve_property_dependency(
4823        target_property: &CssProperty,
4824        reference_property: &CssProperty,
4825    ) -> Option<CssProperty> {
4826        use azul_css::{
4827            css::CssPropertyValue,
4828            props::{
4829                basic::{font::StyleFontSize, length::SizeMetric, pixel::PixelValue},
4830                layout::*,
4831                style::{SelectionRadius, StyleLetterSpacing, StyleWordSpacing},
4832            },
4833        };
4834
4835        // Extract PixelValue from various property types (returns owned value)
4836        let get_pixel_value = |prop: &CssProperty| -> Option<PixelValue> {
4837            match prop {
4838                CssProperty::FontSize(val) => val.get_property().map(|v| v.inner),
4839                CssProperty::LetterSpacing(val) => val.get_property().map(|v| v.inner),
4840                CssProperty::WordSpacing(val) => val.get_property().map(|v| v.inner),
4841                CssProperty::PaddingLeft(val) => val.get_property().map(|v| v.inner),
4842                CssProperty::PaddingRight(val) => val.get_property().map(|v| v.inner),
4843                CssProperty::PaddingTop(val) => val.get_property().map(|v| v.inner),
4844                CssProperty::PaddingBottom(val) => val.get_property().map(|v| v.inner),
4845                CssProperty::MarginLeft(val) => val.get_property().map(|v| v.inner),
4846                CssProperty::MarginRight(val) => val.get_property().map(|v| v.inner),
4847                CssProperty::MarginTop(val) => val.get_property().map(|v| v.inner),
4848                CssProperty::MarginBottom(val) => val.get_property().map(|v| v.inner),
4849                CssProperty::MinWidth(val) => val.get_property().map(|v| v.inner),
4850                CssProperty::MinHeight(val) => val.get_property().map(|v| v.inner),
4851                CssProperty::MaxWidth(val) => val.get_property().map(|v| v.inner),
4852                CssProperty::MaxHeight(val) => val.get_property().map(|v| v.inner),
4853                CssProperty::SelectionRadius(val) => val.get_property().map(|v| v.inner),
4854                _ => None,
4855            }
4856        };
4857
4858        let target_pixel_value = get_pixel_value(target_property)?;
4859        let reference_pixel_value = get_pixel_value(reference_property)?;
4860
4861        // Convert reference to absolute pixels first
4862        let reference_px = match reference_pixel_value.metric {
4863            SizeMetric::Px => reference_pixel_value.number.get(),
4864            SizeMetric::Pt => reference_pixel_value.number.get() * PT_TO_PX,
4865            SizeMetric::In => reference_pixel_value.number.get() * IN_TO_PX,
4866            SizeMetric::Cm => reference_pixel_value.number.get() * CM_TO_PX,
4867            SizeMetric::Mm => reference_pixel_value.number.get() * MM_TO_PX,
4868            SizeMetric::Em => return None, // Reference can't be relative
4869            SizeMetric::Rem => return None, // Reference can't be relative
4870            SizeMetric::Percent => return None, // Reference can't be relative
4871            // Reference can't be viewport-relative
4872            SizeMetric::Vw | SizeMetric::Vh | SizeMetric::Vmin | SizeMetric::Vmax => return None,
4873        };
4874
4875        // Resolve target based on reference
4876        let resolved_px = match target_pixel_value.metric {
4877            SizeMetric::Px => target_pixel_value.number.get(),
4878            SizeMetric::Pt => target_pixel_value.number.get() * PT_TO_PX,
4879            SizeMetric::In => target_pixel_value.number.get() * IN_TO_PX,
4880            SizeMetric::Cm => target_pixel_value.number.get() * CM_TO_PX,
4881            SizeMetric::Mm => target_pixel_value.number.get() * MM_TO_PX,
4882            SizeMetric::Em => target_pixel_value.number.get() * reference_px,
4883            // Use reference as root font-size
4884            SizeMetric::Rem => target_pixel_value.number.get() * reference_px,
4885            SizeMetric::Percent => target_pixel_value.number.get() / 100.0 * reference_px,
4886            // Need viewport context
4887            SizeMetric::Vw | SizeMetric::Vh | SizeMetric::Vmin | SizeMetric::Vmax => return None,
4888        };
4889
4890        // Create a new property with the resolved value
4891        let resolved_pixel_value = PixelValue::px(resolved_px);
4892
4893        match target_property {
4894            CssProperty::FontSize(_) => Some(CssProperty::FontSize(CssPropertyValue::Exact(
4895                StyleFontSize {
4896                    inner: resolved_pixel_value,
4897                },
4898            ))),
4899            CssProperty::LetterSpacing(_) => Some(CssProperty::LetterSpacing(
4900                CssPropertyValue::Exact(StyleLetterSpacing {
4901                    inner: resolved_pixel_value,
4902                }),
4903            )),
4904            CssProperty::WordSpacing(_) => Some(CssProperty::WordSpacing(CssPropertyValue::Exact(
4905                StyleWordSpacing {
4906                    inner: resolved_pixel_value,
4907                },
4908            ))),
4909            CssProperty::PaddingLeft(_) => Some(CssProperty::PaddingLeft(CssPropertyValue::Exact(
4910                LayoutPaddingLeft {
4911                    inner: resolved_pixel_value,
4912                },
4913            ))),
4914            CssProperty::PaddingRight(_) => Some(CssProperty::PaddingRight(
4915                CssPropertyValue::Exact(LayoutPaddingRight {
4916                    inner: resolved_pixel_value,
4917                }),
4918            )),
4919            CssProperty::PaddingTop(_) => Some(CssProperty::PaddingTop(CssPropertyValue::Exact(
4920                LayoutPaddingTop {
4921                    inner: resolved_pixel_value,
4922                },
4923            ))),
4924            CssProperty::PaddingBottom(_) => Some(CssProperty::PaddingBottom(
4925                CssPropertyValue::Exact(LayoutPaddingBottom {
4926                    inner: resolved_pixel_value,
4927                }),
4928            )),
4929            CssProperty::MarginLeft(_) => Some(CssProperty::MarginLeft(CssPropertyValue::Exact(
4930                LayoutMarginLeft {
4931                    inner: resolved_pixel_value,
4932                },
4933            ))),
4934            CssProperty::MarginRight(_) => Some(CssProperty::MarginRight(CssPropertyValue::Exact(
4935                LayoutMarginRight {
4936                    inner: resolved_pixel_value,
4937                },
4938            ))),
4939            CssProperty::MarginTop(_) => Some(CssProperty::MarginTop(CssPropertyValue::Exact(
4940                LayoutMarginTop {
4941                    inner: resolved_pixel_value,
4942                },
4943            ))),
4944            CssProperty::MarginBottom(_) => Some(CssProperty::MarginBottom(
4945                CssPropertyValue::Exact(LayoutMarginBottom {
4946                    inner: resolved_pixel_value,
4947                }),
4948            )),
4949            CssProperty::MinWidth(_) => Some(CssProperty::MinWidth(CssPropertyValue::Exact(
4950                LayoutMinWidth {
4951                    inner: resolved_pixel_value,
4952                },
4953            ))),
4954            CssProperty::MinHeight(_) => Some(CssProperty::MinHeight(CssPropertyValue::Exact(
4955                LayoutMinHeight {
4956                    inner: resolved_pixel_value,
4957                },
4958            ))),
4959            CssProperty::MaxWidth(_) => Some(CssProperty::MaxWidth(CssPropertyValue::Exact(
4960                LayoutMaxWidth {
4961                    inner: resolved_pixel_value,
4962                },
4963            ))),
4964            CssProperty::MaxHeight(_) => Some(CssProperty::MaxHeight(CssPropertyValue::Exact(
4965                LayoutMaxHeight {
4966                    inner: resolved_pixel_value,
4967                },
4968            ))),
4969            CssProperty::SelectionRadius(_) => Some(CssProperty::SelectionRadius(
4970                CssPropertyValue::Exact(SelectionRadius {
4971                    inner: resolved_pixel_value,
4972                }),
4973            )),
4974            _ => None,
4975        }
4976    }
4977
4978    /// Applies user-agent (UA) CSS properties to the cascade before inheritance.
4979    ///
4980    /// UA CSS has the lowest priority in the cascade, so it should only be applied
4981    /// if the node doesn't already have the property from inline styles or author CSS.
4982    ///
4983    /// This is critical for text nodes: UA CSS properties (like font-weight: bold for H1)
4984    /// must be in the cascade maps so they can be inherited by child text nodes.
4985    ///
4986    /// Uses a bitset per node to avoid O(n²) scanning of property vecs.
4987    pub fn apply_ua_css(&mut self, node_data: &[NodeData]) {
4988        use azul_css::props::property::CssPropertyType;
4989        use azul_css::dynamic_selector::PseudoStateType;
4990
4991        let node_count = node_data.len();
4992        if node_count == 0 {
4993            return;
4994        }
4995
4996        // Build a bitset per node: which CssPropertyType values are already set (Normal state).
4997        // CssPropertyType has ~178 variants, so we need [u128; 2] per node (256 bits).
4998        let mut prop_set: Vec<[u128; 2]> = vec![[0u128; 2]; node_count];
4999
5000        // Mark properties from css_props (author CSS, Normal state)
5001        for (node_idx, props) in self.css_props.iter_node_slices() {
5002            for p in props.iter() {
5003                if p.state == PseudoStateType::Normal {
5004                    let d = p.prop_type as u16 as usize;
5005                    if d < 128 {
5006                        prop_set[node_idx][0] |= 1u128 << d;
5007                    } else {
5008                        prop_set[node_idx][1] |= 1u128 << (d - 128);
5009                    }
5010                }
5011            }
5012        }
5013
5014        // Mark properties from cascaded_props (Normal state)
5015        for (node_idx, props) in self.cascaded_props.iter_node_slices() {
5016            for p in props.iter() {
5017                if p.state == PseudoStateType::Normal {
5018                    let d = p.prop_type as u16 as usize;
5019                    if d < 128 {
5020                        prop_set[node_idx][0] |= 1u128 << d;
5021                    } else {
5022                        prop_set[node_idx][1] |= 1u128 << (d - 128);
5023                    }
5024                }
5025            }
5026        }
5027
5028        // Mark properties from inline CSS (NodeData.style, unconditional = Normal)
5029        for (node_idx, node) in node_data.iter().enumerate() {
5030            for (prop, conds) in node.style.iter_inline_properties() {
5031                let is_normal = conds.as_slice().is_empty();
5032                if is_normal {
5033                    let d = prop.get_type() as u16 as usize;
5034                    if d < 128 {
5035                        prop_set[node_idx][0] |= 1u128 << d;
5036                    } else {
5037                        prop_set[node_idx][1] |= 1u128 << (d - 128);
5038                    }
5039                }
5040            }
5041        }
5042
5043        // All UA property types that get_ua_property() may return Some for
5044        let property_types = [
5045            CssPropertyType::Display,
5046            CssPropertyType::Width,
5047            CssPropertyType::Height,
5048            CssPropertyType::FontSize,
5049            CssPropertyType::FontWeight,
5050            CssPropertyType::FontFamily,
5051            CssPropertyType::MarginTop,
5052            CssPropertyType::MarginBottom,
5053            CssPropertyType::MarginLeft,
5054            CssPropertyType::MarginRight,
5055            CssPropertyType::PaddingTop,
5056            CssPropertyType::PaddingBottom,
5057            CssPropertyType::PaddingLeft,
5058            CssPropertyType::PaddingRight,
5059            CssPropertyType::BorderTopStyle,
5060            CssPropertyType::BorderTopWidth,
5061            CssPropertyType::BorderTopColor,
5062            CssPropertyType::BreakInside,
5063            CssPropertyType::BreakAfter,
5064            CssPropertyType::ListStyleType,
5065            CssPropertyType::CounterReset,
5066            CssPropertyType::TextDecoration,
5067            CssPropertyType::TextAlign,
5068            CssPropertyType::VerticalAlign,
5069            CssPropertyType::Cursor,
5070        ];
5071
5072        // Apply UA CSS: only insert for property types not yet set (bitset check = O(1))
5073        for (node_index, node) in node_data.iter().enumerate() {
5074            let node_type = &node.node_type;
5075
5076            for prop_type in &property_types {
5077                // Check bitset: if already set, skip entirely
5078                let d = *prop_type as u16 as usize;
5079                let has_prop = if d < 128 {
5080                    (prop_set[node_index][0] & (1u128 << d)) != 0
5081                } else {
5082                    (prop_set[node_index][1] & (1u128 << (d - 128))) != 0
5083                };
5084
5085                if has_prop {
5086                    continue;
5087                }
5088
5089                // Check if UA CSS defines this property for this node type
5090                if let Some(ua_prop) = crate::ua_css::get_ua_property(node_type, *prop_type) {
5091                    self.cascaded_props.push_to(node_index, StatefulCssProperty {
5092                        state: PseudoStateType::Normal,
5093                        prop_type: *prop_type,
5094                        property: ua_prop.clone(),
5095                    });
5096
5097                    // Mark as set in the bitset (prevent duplicate insertion for same node)
5098                    if d < 128 {
5099                        prop_set[node_index][0] |= 1u128 << d;
5100                    } else {
5101                        prop_set[node_index][1] |= 1u128 << (d - 128);
5102                    }
5103                }
5104            }
5105        }
5106    }
5107
5108    /// Sort cascaded_props by (state, prop_type) and flatten into contiguous memory.
5109    /// Must be called after apply_ua_css() which adds entries to cascaded_props.
5110    pub fn sort_cascaded_props(&mut self) {
5111        self.cascaded_props.sort_each_and_flatten(|p| (p.state, p.prop_type));
5112    }
5113
5114    /// Compute inherited values for all nodes in the DOM tree.
5115    ///
5116    /// Implements CSS inheritance: walk tree depth-first, apply cascade priority
5117    /// (inherited → cascaded → css → inline → user), create dependency chains for
5118    /// relative values. Call `apply_ua_css()` before this function.
5119    pub fn compute_inherited_values(
5120        &mut self,
5121        node_hierarchy: &[NodeHierarchyItem],
5122        node_data: &[NodeData],
5123    ) -> Vec<NodeId> {
5124        if self.computed_values.len() < node_hierarchy.len() {
5125            self.computed_values.resize(node_hierarchy.len(), Vec::new());
5126        }
5127        node_hierarchy
5128            .iter()
5129            .enumerate()
5130            .filter_map(|(node_index, hierarchy_item)| {
5131                let node_id = NodeId::new(node_index);
5132                let parent_id = hierarchy_item.parent_id();
5133                let parent_computed: Option<Vec<(CssPropertyType, CssPropertyWithOrigin)>> =
5134                    parent_id.and_then(|pid| self.computed_values.get(pid.index()).cloned());
5135
5136                let mut ctx = InheritanceContext {
5137                    node_id,
5138                    parent_id,
5139                    computed_values: Vec::new(),
5140                };
5141
5142                // Step 1: Inherit from parent
5143                if let Some(ref parent_values) = parent_computed {
5144                    self.inherit_from_parent(&mut ctx, parent_values);
5145                }
5146
5147                // Steps 2-5: Apply cascade in priority order
5148                self.apply_cascade_properties(
5149                    &mut ctx,
5150                    node_id,
5151                    &parent_computed,
5152                    node_data,
5153                    node_index,
5154                );
5155
5156                // Check for changes and store
5157                let changed = self.store_if_changed(&ctx);
5158                changed.then_some(node_id)
5159            })
5160            .collect()
5161    }
5162
5163    /// Inherit inheritable properties from parent node
5164    fn inherit_from_parent(
5165        &self,
5166        ctx: &mut InheritanceContext,
5167        parent_values: &[(CssPropertyType, CssPropertyWithOrigin)],
5168    ) {
5169        for (prop_type, prop_with_origin) in
5170            parent_values.iter().filter(|(pt, _)| pt.is_inheritable())
5171        {
5172            let entry = (*prop_type, CssPropertyWithOrigin {
5173                property: prop_with_origin.property.clone(),
5174                origin: CssPropertyOrigin::Inherited,
5175            });
5176            // Insert into sorted vec
5177            match ctx.computed_values.binary_search_by_key(prop_type, |(k, _)| *k) {
5178                Ok(idx) => ctx.computed_values[idx] = entry,
5179                Err(idx) => ctx.computed_values.insert(idx, entry),
5180            }
5181        }
5182    }
5183
5184    /// Apply all cascade properties in priority order
5185    fn apply_cascade_properties(
5186        &self,
5187        ctx: &mut InheritanceContext,
5188        node_id: NodeId,
5189        parent_computed: &Option<Vec<(CssPropertyType, CssPropertyWithOrigin)>>,
5190        node_data: &[NodeData],
5191        node_index: usize,
5192    ) {
5193        // Step 2: Cascaded properties (UA CSS)
5194        {
5195            let cascaded_slice = self.cascaded_props.get_slice(node_id.index());
5196            for p in cascaded_slice.iter() {
5197                if p.state == azul_css::dynamic_selector::PseudoStateType::Normal {
5198                    if self.should_apply_cascaded(&ctx.computed_values, p.prop_type, &p.property) {
5199                        self.process_property(ctx, &p.property, parent_computed);
5200                    }
5201                }
5202            }
5203        }
5204
5205        // Step 3: CSS properties (stylesheets)
5206        {
5207            let css_slice = self.css_props.get_slice(node_id.index());
5208            for p in css_slice.iter() {
5209                if p.state == azul_css::dynamic_selector::PseudoStateType::Normal {
5210                    self.process_property(ctx, &p.property, parent_computed);
5211                }
5212            }
5213        }
5214
5215        // Step 4: Inline CSS properties
5216        for (prop, conds) in node_data[node_index].style.iter_inline_properties() {
5217            // Only apply unconditional (normal) properties
5218            if conds.as_slice().is_empty() {
5219                self.process_property(ctx, prop, parent_computed);
5220            }
5221        }
5222
5223        // Step 5: User-overridden properties
5224        if let Some(user_props) = self.user_overridden_properties.get(node_id.index()) {
5225            for (_, prop) in user_props.iter() {
5226                self.process_property(ctx, &prop, parent_computed);
5227            }
5228        }
5229    }
5230
5231    /// Check if a cascaded property should be applied
5232    fn should_apply_cascaded(
5233        &self,
5234        computed: &[(CssPropertyType, CssPropertyWithOrigin)],
5235        prop_type: CssPropertyType,
5236        prop: &CssProperty,
5237    ) -> bool {
5238        // Skip relative font-size if we already have inherited resolved value
5239        if prop_type == CssPropertyType::FontSize {
5240            if let Ok(idx) = computed.binary_search_by_key(&prop_type, |(k, _)| *k) {
5241                if computed[idx].1.origin == CssPropertyOrigin::Inherited
5242                    && Self::has_relative_font_size_unit(prop)
5243                {
5244                    return false;
5245                }
5246            }
5247        }
5248
5249        match computed.binary_search_by_key(&prop_type, |(k, _)| *k) {
5250            Err(_) => true,
5251            Ok(idx) => computed[idx].1.origin == CssPropertyOrigin::Inherited,
5252        }
5253    }
5254
5255    /// Process a single property: resolve and store
5256    fn process_property(
5257        &self,
5258        ctx: &mut InheritanceContext,
5259        prop: &CssProperty,
5260        parent_computed: &Option<Vec<(CssPropertyType, CssPropertyWithOrigin)>>,
5261    ) {
5262        let prop_type = prop.get_type();
5263
5264        let resolved = if prop_type == CssPropertyType::FontSize {
5265            self.resolve_font_size_property(prop, parent_computed)
5266        } else {
5267            self.resolve_other_property(prop, &ctx.computed_values)
5268        };
5269
5270        let entry = (prop_type, CssPropertyWithOrigin {
5271            property: resolved,
5272            origin: CssPropertyOrigin::Own,
5273        });
5274        match ctx.computed_values.binary_search_by_key(&prop_type, |(k, _)| *k) {
5275            Ok(idx) => ctx.computed_values[idx] = entry,
5276            Err(idx) => ctx.computed_values.insert(idx, entry),
5277        }
5278    }
5279
5280    /// Resolve font-size property (uses parent's font-size as reference)
5281    fn resolve_font_size_property(
5282        &self,
5283        prop: &CssProperty,
5284        parent_computed: &Option<Vec<(CssPropertyType, CssPropertyWithOrigin)>>,
5285    ) -> CssProperty {
5286        let parent_font_size = parent_computed
5287            .as_ref()
5288            .and_then(|p| {
5289                p.binary_search_by_key(&CssPropertyType::FontSize, |(k, _)| *k)
5290                    .ok()
5291                    .map(|idx| &p[idx].1)
5292            });
5293
5294        match parent_font_size {
5295            Some(pfs) => Self::resolve_property_dependency(prop, &pfs.property).unwrap_or_else(
5296                || {
5297                    Self::resolve_font_size_to_pixels(
5298                        prop,
5299                        azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
5300                    )
5301                },
5302            ),
5303            None => Self::resolve_font_size_to_pixels(
5304                prop,
5305                azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
5306            ),
5307        }
5308    }
5309
5310    /// Resolve other properties (uses current node's font-size as reference)
5311    fn resolve_other_property(
5312        &self,
5313        prop: &CssProperty,
5314        computed: &[(CssPropertyType, CssPropertyWithOrigin)],
5315    ) -> CssProperty {
5316        computed
5317            .binary_search_by_key(&CssPropertyType::FontSize, |(k, _)| *k)
5318            .ok()
5319            .and_then(|idx| Self::resolve_property_dependency(prop, &computed[idx].1.property))
5320            .unwrap_or_else(|| prop.clone())
5321    }
5322
5323    /// Convert font-size to absolute pixels
5324    fn resolve_font_size_to_pixels(prop: &CssProperty, reference_px: f32) -> CssProperty {
5325        use azul_css::{
5326            css::CssPropertyValue,
5327            props::basic::{font::StyleFontSize, length::SizeMetric, pixel::PixelValue},
5328        };
5329
5330        let CssProperty::FontSize(css_val) = prop else {
5331            return prop.clone();
5332        };
5333
5334        let Some(font_size) = css_val.get_property() else {
5335            return prop.clone();
5336        };
5337
5338        let resolved_px = match font_size.inner.metric {
5339            SizeMetric::Px => font_size.inner.number.get(),
5340            SizeMetric::Pt => font_size.inner.number.get() * PT_TO_PX,
5341            SizeMetric::In => font_size.inner.number.get() * IN_TO_PX,
5342            SizeMetric::Cm => font_size.inner.number.get() * CM_TO_PX,
5343            SizeMetric::Mm => font_size.inner.number.get() * MM_TO_PX,
5344            SizeMetric::Em => font_size.inner.number.get() * reference_px,
5345            SizeMetric::Rem => {
5346                font_size.inner.number.get() * azul_css::props::basic::pixel::DEFAULT_FONT_SIZE
5347            }
5348            SizeMetric::Percent => font_size.inner.number.get() / 100.0 * reference_px,
5349            SizeMetric::Vw | SizeMetric::Vh | SizeMetric::Vmin | SizeMetric::Vmax => {
5350                return prop.clone();
5351            }
5352        };
5353
5354        CssProperty::FontSize(CssPropertyValue::Exact(StyleFontSize {
5355            inner: PixelValue::px(resolved_px),
5356        }))
5357    }
5358
5359    /// Check if font-size has relative unit (em, rem, %)
5360    fn has_relative_font_size_unit(prop: &CssProperty) -> bool {
5361        use azul_css::props::basic::length::SizeMetric;
5362
5363        let CssProperty::FontSize(css_val) = prop else {
5364            return false;
5365        };
5366
5367        css_val
5368            .get_property()
5369            .map(|fs| {
5370                matches!(
5371                    fs.inner.metric,
5372                    SizeMetric::Em | SizeMetric::Rem | SizeMetric::Percent
5373                )
5374            })
5375            .unwrap_or(false)
5376    }
5377
5378    /// Store computed values if changed, returns true if values were updated
5379    fn store_if_changed(&mut self, ctx: &InheritanceContext) -> bool {
5380        let values_changed = self
5381            .computed_values
5382            .get(ctx.node_id.index())
5383            .map(|old| old != &ctx.computed_values)
5384            .unwrap_or(true);
5385
5386        self.computed_values[ctx.node_id.index()] = ctx.computed_values.clone();
5387
5388        values_changed
5389    }
5390}
5391
5392/// Context for computing inherited values for a single node
5393struct InheritanceContext {
5394    node_id: NodeId,
5395    parent_id: Option<NodeId>,
5396    computed_values: Vec<(CssPropertyType, CssPropertyWithOrigin)>,
5397}
5398
5399impl CssPropertyCache {
5400
5401    /// Clear the entire compact cache. Call after major DOM changes.
5402    pub(crate) fn invalidate_resolved_cache(&mut self) {
5403        self.compact_cache = None;
5404    }
5405}