use crate::dom::{NodeData, NodeId};
use crate::prop_cache::CssPropertyCache;
use crate::styled_dom::StyledNodeState;
#[allow(clippy::wildcard_imports)]
use azul_css::compact_cache::*;
use azul_css::css::CssPropertyValue;
use azul_css::props::property::CssProperty;
use azul_css::props::basic::length::SizeMetric;
use azul_css::props::layout::dimensions::{LayoutHeight, LayoutWidth};
use azul_css::props::layout::flex::LayoutFlexBasis;
use azul_css::props::layout::position::LayoutZIndex;
use core::hash::{Hash, Hasher};
use alloc::vec::Vec;
use crate::hash::DefaultHasher;
impl CssPropertyCache {
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::similar_names)] #[allow(clippy::too_many_lines, clippy::cognitive_complexity)] pub fn build_compact_cache(
&self,
node_data: &[NodeData],
prev_font_hashes: &[u64],
) -> CompactLayoutCache {
let node_count = self.node_count;
let default_state = StyledNodeState::default();
let mut result = CompactLayoutCache::with_capacity(node_count);
for (i, nd) in node_data.iter().enumerate().take(node_count) {
let node_id = NodeId::new(i);
let display = self
.get_display(nd, &node_id, &default_state)
.and_then(|v| v.get_property().copied())
.unwrap_or_default();
let position = self
.get_position(nd, &node_id, &default_state)
.and_then(|v| v.get_property().copied())
.unwrap_or_default();
let float = self
.get_float(nd, &node_id, &default_state)
.and_then(|v| v.get_property().copied())
.unwrap_or_default();
let overflow_x = self
.get_overflow_x(nd, &node_id, &default_state)
.and_then(|v| v.get_property().copied())
.unwrap_or_default();
let overflow_y = self
.get_overflow_y(nd, &node_id, &default_state)
.and_then(|v| v.get_property().copied())
.unwrap_or_default();
let box_sizing = self
.get_box_sizing(nd, &node_id, &default_state)
.and_then(|v| v.get_property().copied())
.unwrap_or_default();
let flex_direction = self
.get_flex_direction(nd, &node_id, &default_state)
.and_then(|v| v.get_property().copied())
.unwrap_or_default();
let flex_wrap = self
.get_flex_wrap(nd, &node_id, &default_state)
.and_then(|v| v.get_property().copied())
.unwrap_or_default();
let justify_content = self
.get_justify_content(nd, &node_id, &default_state)
.and_then(|v| v.get_property().copied())
.unwrap_or_default();
let align_items = self
.get_align_items(nd, &node_id, &default_state)
.and_then(|v| v.get_property().copied())
.unwrap_or_default();
let align_content = self
.get_align_content(nd, &node_id, &default_state)
.and_then(|v| v.get_property().copied())
.unwrap_or_default();
let writing_mode = self
.get_writing_mode(nd, &node_id, &default_state)
.and_then(|v| v.get_property().copied())
.unwrap_or_default();
let clear = self
.get_clear(nd, &node_id, &default_state)
.and_then(|v| v.get_property().copied())
.unwrap_or_default();
let font_weight = self
.get_font_weight(nd, &node_id, &default_state)
.and_then(|v| v.get_property().copied())
.unwrap_or_default();
let font_style = self
.get_font_style(nd, &node_id, &default_state)
.and_then(|v| v.get_property().copied())
.unwrap_or_default();
let text_align = self
.get_text_align(nd, &node_id, &default_state)
.and_then(|v| v.get_property().copied())
.unwrap_or_default();
let visibility = self
.get_visibility(nd, &node_id, &default_state)
.and_then(|v| v.get_property().copied())
.unwrap_or_default();
let white_space = self
.get_white_space(nd, &node_id, &default_state)
.and_then(|v| v.get_property().copied())
.unwrap_or_default();
let direction = self
.get_direction(nd, &node_id, &default_state)
.and_then(|v| v.get_property().copied())
.unwrap_or_default();
let vertical_align = self
.get_vertical_align(nd, &node_id, &default_state)
.and_then(|v| v.get_property().copied())
.unwrap_or_default();
let border_collapse = self
.get_border_collapse(nd, &node_id, &default_state)
.and_then(|v| v.get_property().copied())
.unwrap_or_default();
result.tier1_enums[i] = encode_tier1(
display,
position,
float,
overflow_x,
overflow_y,
box_sizing,
flex_direction,
flex_wrap,
justify_content,
align_items,
align_content,
writing_mode,
clear,
font_weight,
font_style,
text_align,
visibility,
white_space,
direction,
vertical_align,
border_collapse,
);
if let Some(val) = self.get_width(nd, &node_id, &default_state) {
result.tier2_dims[i].width = encode_layout_width(val);
}
if let Some(val) = self.get_height(nd, &node_id, &default_state) {
result.tier2_dims[i].height = encode_layout_height(val);
}
if let Some(val) = self.get_min_width(nd, &node_id, &default_state) {
result.tier2_dims[i].min_width = encode_pixel_prop(val);
}
if let Some(val) = self.get_max_width(nd, &node_id, &default_state) {
result.tier2_dims[i].max_width = encode_pixel_prop(val);
}
if let Some(val) = self.get_min_height(nd, &node_id, &default_state) {
result.tier2_dims[i].min_height = encode_pixel_prop(val);
}
if let Some(val) = self.get_max_height(nd, &node_id, &default_state) {
result.tier2_dims[i].max_height = encode_pixel_prop(val);
}
if let Some(val) = self.get_flex_basis(nd, &node_id, &default_state) {
result.tier2_dims[i].flex_basis = encode_flex_basis(val);
}
if let Some(val) = self.get_font_size(nd, &node_id, &default_state) {
result.tier2_dims[i].font_size = encode_pixel_prop(val);
}
if let Some(val) = self.get_padding_top(nd, &node_id, &default_state) {
result.tier2_dims[i].padding_top = encode_css_pixel_as_i16(val);
}
if let Some(val) = self.get_padding_right(nd, &node_id, &default_state) {
result.tier2_dims[i].padding_right = encode_css_pixel_as_i16(val);
}
if let Some(val) = self.get_padding_bottom(nd, &node_id, &default_state) {
result.tier2_dims[i].padding_bottom = encode_css_pixel_as_i16(val);
}
if let Some(val) = self.get_padding_left(nd, &node_id, &default_state) {
result.tier2_dims[i].padding_left = encode_css_pixel_as_i16(val);
}
if let Some(val) = self.get_margin_top(nd, &node_id, &default_state) {
result.tier2_dims[i].margin_top = encode_margin_i16(val);
}
if let Some(val) = self.get_margin_right(nd, &node_id, &default_state) {
result.tier2_dims[i].margin_right = encode_margin_i16(val);
}
if let Some(val) = self.get_margin_bottom(nd, &node_id, &default_state) {
result.tier2_dims[i].margin_bottom = encode_margin_i16(val);
}
if let Some(val) = self.get_margin_left(nd, &node_id, &default_state) {
result.tier2_dims[i].margin_left = encode_margin_i16(val);
}
if let Some(val) = self.get_border_top_width(nd, &node_id, &default_state) {
result.tier2_dims[i].border_top_width = encode_css_pixel_as_i16(val);
}
if let Some(val) = self.get_border_right_width(nd, &node_id, &default_state) {
result.tier2_dims[i].border_right_width = encode_css_pixel_as_i16(val);
}
if let Some(val) = self.get_border_bottom_width(nd, &node_id, &default_state) {
result.tier2_dims[i].border_bottom_width = encode_css_pixel_as_i16(val);
}
if let Some(val) = self.get_border_left_width(nd, &node_id, &default_state) {
result.tier2_dims[i].border_left_width = encode_css_pixel_as_i16(val);
}
if let Some(val) = self.get_top(nd, &node_id, &default_state) {
result.tier2_dims[i].top = encode_css_pixel_as_i16(val);
}
if let Some(val) = self.get_right(nd, &node_id, &default_state) {
result.tier2_dims[i].right = encode_css_pixel_as_i16(val);
}
if let Some(val) = self.get_bottom(nd, &node_id, &default_state) {
result.tier2_dims[i].bottom = encode_css_pixel_as_i16(val);
}
if let Some(val) = self.get_left(nd, &node_id, &default_state) {
result.tier2_dims[i].left = encode_css_pixel_as_i16(val);
}
if let Some(val) = self.get_flex_grow(nd, &node_id, &default_state) {
if let Some(exact) = val.get_property() {
result.tier2_dims[i].flex_grow = encode_flex_u16(exact.inner.get());
}
}
if let Some(val) = self.get_flex_shrink(nd, &node_id, &default_state) {
if let Some(exact) = val.get_property() {
result.tier2_dims[i].flex_shrink = encode_flex_u16(exact.inner.get());
}
}
if let Some(val) = self.get_z_index(nd, &node_id, &default_state) {
if let Some(exact) = val.get_property() {
match exact {
LayoutZIndex::Auto => result.tier2_cold[i].z_index = I16_AUTO,
LayoutZIndex::Integer(z) => {
if *z >= i32::from(I16_SENTINEL_THRESHOLD) {
result.tier2_cold[i].z_index = I16_SENTINEL;
} else {
result.tier2_cold[i].z_index = *z as i16;
}
}
}
}
}
{
let bts = self.get_border_top_style(nd, &node_id, &default_state)
.and_then(|v| v.get_property().copied())
.map(|v| v.inner)
.unwrap_or_default();
let brs = self.get_border_right_style(nd, &node_id, &default_state)
.and_then(|v| v.get_property().copied())
.map(|v| v.inner)
.unwrap_or_default();
let bbs = self.get_border_bottom_style(nd, &node_id, &default_state)
.and_then(|v| v.get_property().copied())
.map(|v| v.inner)
.unwrap_or_default();
let bls = self.get_border_left_style(nd, &node_id, &default_state)
.and_then(|v| v.get_property().copied())
.map(|v| v.inner)
.unwrap_or_default();
result.tier2_cold[i].border_styles_packed =
encode_border_styles_packed(bts, brs, bbs, bls);
}
if let Some(val) = self.get_border_top_color(nd, &node_id, &default_state) {
if let Some(color) = val.get_property() {
result.tier2_cold[i].border_top_color = encode_color_u32(&color.inner);
}
}
if let Some(val) = self.get_border_right_color(nd, &node_id, &default_state) {
if let Some(color) = val.get_property() {
result.tier2_cold[i].border_right_color = encode_color_u32(&color.inner);
}
}
if let Some(val) = self.get_border_bottom_color(nd, &node_id, &default_state) {
if let Some(color) = val.get_property() {
result.tier2_cold[i].border_bottom_color = encode_color_u32(&color.inner);
}
}
if let Some(val) = self.get_border_left_color(nd, &node_id, &default_state) {
if let Some(color) = val.get_property() {
result.tier2_cold[i].border_left_color = encode_color_u32(&color.inner);
}
}
if let Some(val) = self.get_border_spacing(nd, &node_id, &default_state) {
if let Some(spacing) = val.get_property() {
if spacing.horizontal.metric == SizeMetric::Px {
result.tier2_cold[i].border_spacing_h = encode_resolved_px_i16(spacing.horizontal.number.get());
}
if spacing.vertical.metric == SizeMetric::Px {
result.tier2_cold[i].border_spacing_v = encode_resolved_px_i16(spacing.vertical.number.get());
}
}
}
if let Some(val) = self.get_tab_size(nd, &node_id, &default_state) {
result.tier2_cold[i].tab_size = encode_css_pixel_as_i16(val);
}
if let Some(val) = self.get_text_color(nd, &node_id, &default_state) {
if let Some(color) = val.get_property() {
let c = &color.inner;
result.tier2b_text[i].text_color =
(u32::from(c.r) << 24) | (u32::from(c.g) << 16) | (u32::from(c.b) << 8) | u32::from(c.a);
}
}
if let Some(val) = self.get_font_family(nd, &node_id, &default_state) {
if let Some(families) = val.get_property() {
let mut hasher = DefaultHasher::new();
families.hash(&mut hasher);
let h = hasher.finish();
let h = if h == 0 { 1 } else { h };
result.tier2b_text[i].font_family_hash = h;
result.font_hash_to_families.insert(h, families.clone());
}
}
if let Some(val) = self.get_line_height(nd, &node_id, &default_state) {
if let Some(lh) = val.get_property() {
let pct_x10 = (lh.inner.normalized() * 1000.0).round() as i32;
if pct_x10 >= -32768 && pct_x10 < i32::from(I16_SENTINEL_THRESHOLD) {
result.tier2b_text[i].line_height = pct_x10 as i16;
} else {
result.tier2b_text[i].line_height = I16_SENTINEL;
}
}
}
if let Some(val) = self.get_letter_spacing(nd, &node_id, &default_state) {
result.tier2b_text[i].letter_spacing = encode_css_pixel_as_i16(val);
}
if let Some(val) = self.get_word_spacing(nd, &node_id, &default_state) {
result.tier2b_text[i].word_spacing = encode_css_pixel_as_i16(val);
}
if let Some(val) = self.get_text_indent(nd, &node_id, &default_state) {
result.tier2b_text[i].text_indent = encode_css_pixel_as_i16(val);
}
}
result.font_dirty_nodes.clear();
for i in 0..node_count {
let new_hash = result.tier2b_text[i].font_family_hash;
let old_hash = prev_font_hashes.get(i).copied().unwrap_or(0);
if new_hash != old_hash {
result.font_dirty_nodes.push(i);
}
}
result.prev_font_hashes = result.tier2b_text.iter().map(|t| t.font_family_hash).collect();
result
}
pub fn build_compact_cache_with_inheritance(
&self,
node_data: &[NodeData],
node_hierarchy: &[crate::styled_dom::NodeHierarchyItem],
prev_font_hashes: &[u64],
) -> CompactLayoutCache {
self.build_compact_cache_with_inheritance_debug(node_data, node_hierarchy, prev_font_hashes, &mut None)
}
#[allow(clippy::too_many_lines, clippy::cognitive_complexity)] pub fn build_compact_cache_with_inheritance_debug(
&self,
node_data: &[NodeData],
node_hierarchy: &[crate::styled_dom::NodeHierarchyItem],
prev_font_hashes: &[u64],
debug_messages: &mut Option<Vec<azul_css::LayoutDebugMessage>>,
) -> CompactLayoutCache {
const INHERITABLE_TIER1_MASK: u64 =
(FONT_WEIGHT_MASK << FONT_WEIGHT_SHIFT)
| (FONT_STYLE_MASK << FONT_STYLE_SHIFT)
| (TEXT_ALIGN_MASK << TEXT_ALIGN_SHIFT)
| (VISIBILITY_MASK << VISIBILITY_SHIFT)
| (WHITE_SPACE_MASK << WHITE_SPACE_SHIFT)
| (DIRECTION_MASK << DIRECTION_SHIFT)
| (BORDER_COLLAPSE_MASK << BORDER_COLLAPSE_SHIFT);
let node_count = self.node_count;
let default_state = StyledNodeState::default();
let mut result = CompactLayoutCache::with_capacity(node_count);
let mut global_tier1: u64 = 0;
let mut global_dims = CompactNodeProps::default();
let mut global_cold = CompactNodePropsCold::default();
let mut global_text = CompactTextProps::default();
let has_global = !self.global_css_props.is_empty();
if has_global {
use azul_css::props::property::CssProperty;
for prop in &self.global_css_props {
macro_rules! global_tier1_enum {
($variant:ident, $shift:ident, $mask:ident, $encoder:ident) => {
if let CssProperty::$variant(v) = prop {
if let Some(exact) = v.get_property() {
let encoded = u64::from($encoder(*exact));
let shifted_mask = $mask << $shift;
global_tier1 = (global_tier1 & !shifted_mask) | ((encoded & $mask) << $shift);
}
}
};
}
global_tier1_enum!(Display, DISPLAY_SHIFT, DISPLAY_MASK, layout_display_to_u8);
global_tier1_enum!(Position, POSITION_SHIFT, POSITION_MASK, layout_position_to_u8);
global_tier1_enum!(Float, FLOAT_SHIFT, FLOAT_MASK, layout_float_to_u8);
global_tier1_enum!(OverflowX, OVERFLOW_X_SHIFT, OVERFLOW_MASK, layout_overflow_to_u8);
global_tier1_enum!(OverflowY, OVERFLOW_Y_SHIFT, OVERFLOW_MASK, layout_overflow_to_u8);
global_tier1_enum!(BoxSizing, BOX_SIZING_SHIFT, BOX_SIZING_MASK, layout_box_sizing_to_u8);
global_tier1_enum!(FlexDirection, FLEX_DIRECTION_SHIFT, FLEX_DIR_MASK, layout_flex_direction_to_u8);
global_tier1_enum!(FlexWrap, FLEX_WRAP_SHIFT, FLEX_WRAP_MASK, layout_flex_wrap_to_u8);
global_tier1_enum!(JustifyContent, JUSTIFY_CONTENT_SHIFT, JUSTIFY_MASK, layout_justify_content_to_u8);
global_tier1_enum!(AlignItems, ALIGN_ITEMS_SHIFT, ALIGN_MASK, layout_align_items_to_u8);
global_tier1_enum!(AlignContent, ALIGN_CONTENT_SHIFT, ALIGN_MASK, layout_align_content_to_u8);
global_tier1_enum!(Clear, CLEAR_SHIFT, CLEAR_MASK, layout_clear_to_u8);
global_tier1_enum!(Visibility, VISIBILITY_SHIFT, VISIBILITY_MASK, style_visibility_to_u8);
global_tier1_enum!(WritingMode, WRITING_MODE_SHIFT, WRITING_MODE_MASK, layout_writing_mode_to_u8);
global_tier1_enum!(FontWeight, FONT_WEIGHT_SHIFT, FONT_WEIGHT_MASK, style_font_weight_to_u8);
global_tier1_enum!(FontStyle, FONT_STYLE_SHIFT, FONT_STYLE_MASK, style_font_style_to_u8);
global_tier1_enum!(TextAlign, TEXT_ALIGN_SHIFT, TEXT_ALIGN_MASK, style_text_align_to_u8);
global_tier1_enum!(WhiteSpace, WHITE_SPACE_SHIFT, WHITE_SPACE_MASK, style_white_space_to_u8);
global_tier1_enum!(Direction, DIRECTION_SHIFT, DIRECTION_MASK, style_direction_to_u8);
global_tier1_enum!(VerticalAlign, VERTICAL_ALIGN_SHIFT, VERTICAL_ALIGN_MASK, style_vertical_align_to_u8);
global_tier1_enum!(BorderCollapse, BORDER_COLLAPSE_SHIFT, BORDER_COLLAPSE_MASK, border_collapse_to_u8);
match prop {
CssProperty::PaddingTop(v) => { global_dims.padding_top = encode_css_pixel_as_i16(v); }
CssProperty::PaddingRight(v) => { global_dims.padding_right = encode_css_pixel_as_i16(v); }
CssProperty::PaddingBottom(v) => { global_dims.padding_bottom = encode_css_pixel_as_i16(v); }
CssProperty::PaddingLeft(v) => { global_dims.padding_left = encode_css_pixel_as_i16(v); }
CssProperty::MarginTop(v) => { global_dims.margin_top = encode_margin_i16(v); }
CssProperty::MarginRight(v) => { global_dims.margin_right = encode_margin_i16(v); }
CssProperty::MarginBottom(v) => { global_dims.margin_bottom = encode_margin_i16(v); }
CssProperty::MarginLeft(v) => { global_dims.margin_left = encode_margin_i16(v); }
CssProperty::Width(v) => { global_dims.width = encode_layout_width(v); }
CssProperty::Height(v) => { global_dims.height = encode_layout_height(v); }
CssProperty::FontSize(v) => { global_dims.font_size = encode_pixel_prop(v); }
CssProperty::BorderTopWidth(v) => { global_dims.border_top_width = encode_css_pixel_as_i16(v); }
CssProperty::BorderRightWidth(v) => { global_dims.border_right_width = encode_css_pixel_as_i16(v); }
CssProperty::BorderBottomWidth(v) => { global_dims.border_bottom_width = encode_css_pixel_as_i16(v); }
CssProperty::BorderLeftWidth(v) => { global_dims.border_left_width = encode_css_pixel_as_i16(v); }
_ => {}
}
}
if global_tier1 != 0 {
global_tier1 |= TIER1_POPULATED_BIT;
}
}
macro_rules! cascade_debug {
($($arg:tt)*) => {
if let Some(ref mut msgs) = debug_messages {
msgs.push(azul_css::LayoutDebugMessage::css_getter(format!($($arg)*)));
}
};
}
for i in 0..node_count {
let node_id = NodeId::new(i);
let nd = &node_data[i];
let parent_id = node_hierarchy[i].parent_id();
if let Some(pid) = parent_id {
let pi = pid.index();
debug_assert!(
pi < i,
"compact cascade: non-pre-order arena — node {i}'s parent {pi} \
is not stored before it; inheritance would read default values",
);
if pi < i {
result.tier1_enums[i] = result.tier1_enums[pi] & INHERITABLE_TIER1_MASK;
result.tier2_dims[i].font_size = result.tier2_dims[pi].font_size;
result.tier2_cold[i].border_spacing_h = result.tier2_cold[pi].border_spacing_h;
result.tier2_cold[i].border_spacing_v = result.tier2_cold[pi].border_spacing_v;
result.tier2_cold[i].tab_size = result.tier2_cold[pi].tab_size;
result.tier2b_text[i] = result.tier2b_text[pi];
}
}
{
let d = &result.tier2_dims[i];
cascade_debug!("node[{}] {:?} after-inherit: pt={} pb={} pl={} pr={} mt={} mb={} ml={} mr={} w={} h={}",
i, nd.node_type, d.padding_top, d.padding_bottom, d.padding_left, d.padding_right,
d.margin_top, d.margin_bottom, d.margin_left, d.margin_right, d.width, d.height);
}
apply_ua_css_to_compact(
&nd.node_type,
&mut result.tier1_enums[i],
&mut result.tier2_dims[i],
&mut result.tier2_cold[i],
&mut result.tier2b_text[i],
&mut result.font_hash_to_families,
);
{
let d = &result.tier2_dims[i];
cascade_debug!("node[{}] {:?} after-UA: pt={} pb={} pl={} pr={} mt={} mb={} ml={} mr={}",
i, nd.node_type, d.padding_top, d.padding_bottom, d.padding_left, d.padding_right,
d.margin_top, d.margin_bottom, d.margin_left, d.margin_right);
}
if !nd.is_text_node() {
for prop in &self.global_css_props {
apply_css_property_to_compact(
prop,
&mut result.tier1_enums[i],
&mut result.tier2_dims[i],
&mut result.tier2_cold[i],
&mut result.tier2b_text[i],
&mut result.font_hash_to_families,
);
update_dom_declared_flags(prop, &mut result.dom_declared_flags);
}
}
{
let d = &result.tier2_dims[i];
cascade_debug!("node[{}] {:?} after-global-star: pt={} pb={} pl={} pr={} mt={} mb={} ml={} mr={}",
i, nd.node_type, d.padding_top, d.padding_bottom, d.padding_left, d.padding_right,
d.margin_top, d.margin_bottom, d.margin_left, d.margin_right);
let n_props = self.css_props.get_slice(i).len();
let n_inline = nd.style.iter_inline_properties().count();
cascade_debug!("node[{}] css_props={} entries, inline={} entries", i, n_props, n_inline);
for prop in self.css_props.get_slice(i) {
cascade_debug!("node[{}] css_prop: state={:?} type={:?}", i, prop.state, prop.prop_type);
}
}
for prop in self.css_props.get_slice(i) {
if prop.state != azul_css::dynamic_selector::PseudoStateType::Normal { continue; }
apply_css_property_to_compact(
&prop.property,
&mut result.tier1_enums[i],
&mut result.tier2_dims[i],
&mut result.tier2_cold[i],
&mut result.tier2b_text[i],
&mut result.font_hash_to_families,
);
update_dom_declared_flags(&prop.property, &mut result.dom_declared_flags);
}
{
let d = &result.tier2_dims[i];
cascade_debug!("node[{}] {:?} after-css-props: pt={} pb={} pl={} pr={} mt={} mb={} ml={} mr={}",
i, nd.node_type, d.padding_top, d.padding_bottom, d.padding_left, d.padding_right,
d.margin_top, d.margin_bottom, d.margin_left, d.margin_right);
}
for (prop, conds) in nd.style.iter_inline_properties() {
let is_normal = conds.as_slice().is_empty()
|| conds.as_slice().iter().all(|c|
matches!(c, azul_css::dynamic_selector::DynamicSelector::PseudoState(
azul_css::dynamic_selector::PseudoStateType::Normal
))
);
if !is_normal { continue; }
if let CssProperty::Width(v) = prop {
result.tier2_dims[i].width = encode_layout_width(v);
} else if let CssProperty::Height(v) = prop {
result.tier2_dims[i].height = encode_layout_height(v);
} else if let CssProperty::FlexGrow(v) = prop {
if let Some(e) = v.get_property() {
result.tier2_dims[i].flex_grow = encode_flex_u16(e.inner.get());
}
} else if let CssProperty::Display(v) = prop {
if let Some(e) = v.get_property() {
let enc = u64::from(layout_display_to_u8(*e));
let m = DISPLAY_MASK;
let s = DISPLAY_SHIFT;
result.tier1_enums[i] = (result.tier1_enums[i] & !(m << s)) | ((enc & m) << s);
}
} else {
apply_css_property_to_compact(
prop,
&mut result.tier1_enums[i],
&mut result.tier2_dims[i],
&mut result.tier2_cold[i],
&mut result.tier2b_text[i],
&mut result.font_hash_to_families,
);
}
update_dom_declared_flags(prop, &mut result.dom_declared_flags);
}
resolve_font_size_to_px(
&mut result.tier2_dims,
i,
parent_id,
);
if result.tier1_enums[i] != 0 {
result.tier1_enums[i] |= TIER1_POPULATED_BIT;
}
}
result.font_dirty_nodes.clear();
let first_build = prev_font_hashes.is_empty();
for i in 0..node_count {
let new_hash = result.tier2b_text[i].font_family_hash;
let old_hash = prev_font_hashes.get(i).copied().unwrap_or(0);
if first_build || new_hash != old_hash {
result.font_dirty_nodes.push(i);
}
}
result.prev_font_hashes = result.tier2b_text.iter().map(|t| t.font_family_hash).collect();
result
}
}
fn apply_ua_css_to_compact(
node_type: &crate::dom::NodeType,
tier1: &mut u64,
dims: &mut CompactNodeProps,
cold: &mut CompactNodePropsCold,
text: &mut CompactTextProps,
font_hash_map: &mut alloc::collections::BTreeMap<u64, azul_css::props::basic::font::StyleFontFamilyVec>,
) {
use azul_css::props::property::CssPropertyType as PT2;
const UA_PROPERTY_TYPES: &[PT2] = &[
PT2::Display, PT2::Position, PT2::Float, PT2::Clear,
PT2::OverflowX, PT2::OverflowY, PT2::BoxSizing,
PT2::FlexDirection, PT2::FlexWrap, PT2::JustifyContent,
PT2::AlignItems, PT2::AlignContent, PT2::WritingMode,
PT2::FontWeight, PT2::FontStyle, PT2::TextAlign,
PT2::Visibility, PT2::WhiteSpace, PT2::Direction,
PT2::VerticalAlign, PT2::BorderCollapse,
PT2::Width, PT2::Height, PT2::FontSize,
PT2::MarginTop, PT2::MarginBottom, PT2::MarginLeft, PT2::MarginRight,
PT2::PaddingTop, PT2::PaddingBottom, PT2::PaddingLeft, PT2::PaddingRight,
PT2::BorderTopWidth, PT2::BorderTopStyle, PT2::BorderTopColor,
PT2::BorderRightWidth, PT2::BorderRightStyle, PT2::BorderRightColor,
PT2::BorderBottomWidth, PT2::BorderBottomStyle, PT2::BorderBottomColor,
PT2::BorderLeftWidth, PT2::BorderLeftStyle, PT2::BorderLeftColor,
PT2::TextColor, PT2::LineHeight, PT2::LetterSpacing, PT2::WordSpacing,
PT2::TextDecoration, PT2::Cursor, PT2::ListStyleType,
];
for pt in UA_PROPERTY_TYPES {
if let Some(ua_prop) = crate::ua_css::get_ua_property(node_type, *pt) {
apply_css_property_to_compact(ua_prop, tier1, dims, cold, text, font_hash_map);
}
}
}
fn resolve_font_size_to_px(
tier2_dims: &mut [CompactNodeProps],
node_idx: usize,
parent_id: Option<NodeId>,
) {
let raw_fs = tier2_dims[node_idx].font_size;
if raw_fs == U32_SENTINEL || raw_fs >= U32_SENTINEL_THRESHOLD {
return;
}
let pv = match decode_pixel_value_u32(raw_fs) {
Some(pv) if pv.metric != SizeMetric::Px => pv,
_ => return,
};
let parent_font_size_px = parent_id
.map_or(16.0, |pid| {
let pi = pid.index();
debug_assert!(
pi < node_idx,
"compact font-size resolve: non-pre-order arena — node {node_idx}'s \
parent {pi} font-size is not yet resolved",
);
if pi < node_idx {
tier2_dims
.get(pi)
.and_then(|p| decode_pixel_value_u32(p.font_size))
.map_or(16.0, |ppv| ppv.number.get())
} else {
16.0
}
});
let resolved_px = match pv.metric {
SizeMetric::Em => pv.number.get() * parent_font_size_px,
SizeMetric::Percent => pv.number.get() / 100.0 * parent_font_size_px,
SizeMetric::Rem => {
tier2_dims
.first()
.and_then(|r| decode_pixel_value_u32(r.font_size))
.map_or(16.0, |rpv| rpv.number.get())
* pv.number.get()
}
SizeMetric::Pt => pv.number.get() * 96.0 / 72.0,
_ => pv.number.get(),
};
tier2_dims[node_idx].font_size =
encode_pixel_value_u32(&azul_css::props::basic::pixel::PixelValue::px(resolved_px));
}
#[inline]
#[allow(clippy::match_same_arms)]
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
#[allow(clippy::too_many_lines, clippy::cognitive_complexity)] fn apply_css_property_to_compact(
prop: &CssProperty,
tier1: &mut u64,
dims: &mut CompactNodeProps,
cold: &mut CompactNodePropsCold,
text: &mut CompactTextProps,
font_hash_map: &mut alloc::collections::BTreeMap<u64, azul_css::props::basic::font::StyleFontFamilyVec>,
) {
macro_rules! set_tier1 {
($v:expr, $shift:expr, $mask:expr, $encoder:ident) => {
if let Some(exact) = $v.get_property() {
let encoded = u64::from($encoder(*exact));
let shifted_mask = $mask << $shift;
*tier1 = (*tier1 & !shifted_mask) | ((encoded & $mask) << $shift);
}
};
}
match prop {
CssProperty::Display(v) => set_tier1!(v, DISPLAY_SHIFT, DISPLAY_MASK, layout_display_to_u8),
CssProperty::Position(v) => set_tier1!(v, POSITION_SHIFT, POSITION_MASK, layout_position_to_u8),
CssProperty::Float(v) => set_tier1!(v, FLOAT_SHIFT, FLOAT_MASK, layout_float_to_u8),
CssProperty::OverflowX(v) => set_tier1!(v, OVERFLOW_X_SHIFT, OVERFLOW_MASK, layout_overflow_to_u8),
CssProperty::OverflowY(v) => set_tier1!(v, OVERFLOW_Y_SHIFT, OVERFLOW_MASK, layout_overflow_to_u8),
CssProperty::BoxSizing(v) => set_tier1!(v, BOX_SIZING_SHIFT, BOX_SIZING_MASK, layout_box_sizing_to_u8),
CssProperty::FlexDirection(v) => set_tier1!(v, FLEX_DIRECTION_SHIFT, FLEX_DIR_MASK, layout_flex_direction_to_u8),
CssProperty::FlexWrap(v) => set_tier1!(v, FLEX_WRAP_SHIFT, FLEX_WRAP_MASK, layout_flex_wrap_to_u8),
CssProperty::JustifyContent(v) => set_tier1!(v, JUSTIFY_CONTENT_SHIFT, JUSTIFY_MASK, layout_justify_content_to_u8),
CssProperty::AlignItems(v) => set_tier1!(v, ALIGN_ITEMS_SHIFT, ALIGN_MASK, layout_align_items_to_u8),
CssProperty::AlignContent(v) => set_tier1!(v, ALIGN_CONTENT_SHIFT, ALIGN_MASK, layout_align_content_to_u8),
CssProperty::WritingMode(v) => set_tier1!(v, WRITING_MODE_SHIFT, WRITING_MODE_MASK, layout_writing_mode_to_u8),
CssProperty::Clear(v) => set_tier1!(v, CLEAR_SHIFT, CLEAR_MASK, layout_clear_to_u8),
CssProperty::FontWeight(v) => set_tier1!(v, FONT_WEIGHT_SHIFT, FONT_WEIGHT_MASK, style_font_weight_to_u8),
CssProperty::FontStyle(v) => set_tier1!(v, FONT_STYLE_SHIFT, FONT_STYLE_MASK, style_font_style_to_u8),
CssProperty::TextAlign(v) => set_tier1!(v, TEXT_ALIGN_SHIFT, TEXT_ALIGN_MASK, style_text_align_to_u8),
CssProperty::Visibility(v) => set_tier1!(v, VISIBILITY_SHIFT, VISIBILITY_MASK, style_visibility_to_u8),
CssProperty::WhiteSpace(v) => set_tier1!(v, WHITE_SPACE_SHIFT, WHITE_SPACE_MASK, style_white_space_to_u8),
CssProperty::Direction(v) => set_tier1!(v, DIRECTION_SHIFT, DIRECTION_MASK, style_direction_to_u8),
CssProperty::VerticalAlign(v) => set_tier1!(v, VERTICAL_ALIGN_SHIFT, VERTICAL_ALIGN_MASK, style_vertical_align_to_u8),
CssProperty::BorderCollapse(v) => set_tier1!(v, BORDER_COLLAPSE_SHIFT, BORDER_COLLAPSE_MASK, border_collapse_to_u8),
CssProperty::AlignSelf(v) => set_tier1!(v, ALIGN_SELF_SHIFT, ALIGN_SELF_MASK, layout_align_self_to_u8),
CssProperty::JustifySelf(v) => set_tier1!(v, JUSTIFY_SELF_SHIFT, JUSTIFY_SELF_MASK, layout_justify_self_to_u8),
CssProperty::GridAutoFlow(v) => set_tier1!(v, GRID_AUTO_FLOW_SHIFT, GRID_AUTO_FLOW_MASK, layout_grid_auto_flow_to_u8),
CssProperty::JustifyItems(v) => set_tier1!(v, JUSTIFY_ITEMS_SHIFT, JUSTIFY_ITEMS_MASK, layout_justify_items_to_u8),
CssProperty::Width(v) => { dims.width = encode_layout_width(v); }
CssProperty::Height(v) => { dims.height = encode_layout_height(v); }
CssProperty::MinWidth(v) => { dims.min_width = encode_pixel_prop(v); }
CssProperty::MaxWidth(v) => { dims.max_width = encode_pixel_prop(v); }
CssProperty::MinHeight(v) => { dims.min_height = encode_pixel_prop(v); }
CssProperty::MaxHeight(v) => { dims.max_height = encode_pixel_prop(v); }
CssProperty::FlexBasis(v) => { dims.flex_basis = encode_flex_basis(v); }
CssProperty::FontSize(v) => { dims.font_size = encode_pixel_prop(v); }
CssProperty::PaddingTop(v) => { dims.padding_top = encode_css_pixel_as_i16(v); }
CssProperty::PaddingRight(v) => { dims.padding_right = encode_css_pixel_as_i16(v); }
CssProperty::PaddingBottom(v) => { dims.padding_bottom = encode_css_pixel_as_i16(v); }
CssProperty::PaddingLeft(v) => { dims.padding_left = encode_css_pixel_as_i16(v); }
CssProperty::MarginTop(v) => { dims.margin_top = encode_margin_i16(v); }
CssProperty::MarginRight(v) => { dims.margin_right = encode_margin_i16(v); }
CssProperty::MarginBottom(v) => { dims.margin_bottom = encode_margin_i16(v); }
CssProperty::MarginLeft(v) => { dims.margin_left = encode_margin_i16(v); }
CssProperty::BorderTopWidth(v) => { dims.border_top_width = encode_css_pixel_as_i16(v); }
CssProperty::BorderRightWidth(v) => { dims.border_right_width = encode_css_pixel_as_i16(v); }
CssProperty::BorderBottomWidth(v) => { dims.border_bottom_width = encode_css_pixel_as_i16(v); }
CssProperty::BorderLeftWidth(v) => { dims.border_left_width = encode_css_pixel_as_i16(v); }
CssProperty::Top(v) => { dims.top = encode_css_pixel_as_i16(v); }
CssProperty::Right(v) => { dims.right = encode_css_pixel_as_i16(v); }
CssProperty::Bottom(v) => { dims.bottom = encode_css_pixel_as_i16(v); }
CssProperty::Left(v) => { dims.left = encode_css_pixel_as_i16(v); }
CssProperty::FlexGrow(v) => {
if let Some(exact) = v.get_property() {
dims.flex_grow = encode_flex_u16(exact.inner.get());
}
}
CssProperty::FlexShrink(v) => {
if let Some(exact) = v.get_property() {
dims.flex_shrink = encode_flex_u16(exact.inner.get());
}
}
CssProperty::RowGap(v) => {
if let Some(g) = v.get_property() {
if g.inner.metric == SizeMetric::Px {
dims.row_gap = encode_resolved_px_i16(g.inner.number.get());
}
}
}
CssProperty::ColumnGap(v) => {
if let Some(g) = v.get_property() {
if g.inner.metric == SizeMetric::Px {
dims.column_gap = encode_resolved_px_i16(g.inner.number.get());
}
}
}
CssProperty::Gap(v) => {
if let Some(g) = v.get_property() {
if g.inner.metric == SizeMetric::Px {
let enc = encode_resolved_px_i16(g.inner.number.get());
dims.row_gap = enc;
dims.column_gap = enc;
}
}
}
CssProperty::GridColumn(v) => {
if let Some(gp) = v.get_property() {
cold.grid_col_start = encode_grid_line(&gp.grid_start);
cold.grid_col_end = encode_grid_line(&gp.grid_end);
}
}
CssProperty::GridRow(v) => {
if let Some(gp) = v.get_property() {
cold.grid_row_start = encode_grid_line(&gp.grid_start);
cold.grid_row_end = encode_grid_line(&gp.grid_end);
}
}
CssProperty::ZIndex(v) => {
if let Some(exact) = v.get_property() {
match exact {
LayoutZIndex::Auto => cold.z_index = I16_AUTO,
LayoutZIndex::Integer(z) => {
cold.z_index = if *z >= i32::from(I16_SENTINEL_THRESHOLD) { I16_SENTINEL } else { *z as i16 };
}
}
}
}
CssProperty::BorderTopStyle(v) => {
if let Some(exact) = v.get_property() {
let bs = u16::from(border_style_to_u8(exact.inner));
cold.border_styles_packed = (cold.border_styles_packed & !0x000F) | bs;
}
}
CssProperty::BorderRightStyle(v) => {
if let Some(exact) = v.get_property() {
let bs = u16::from(border_style_to_u8(exact.inner));
cold.border_styles_packed = (cold.border_styles_packed & !0x00F0) | (bs << 4);
}
}
CssProperty::BorderBottomStyle(v) => {
if let Some(exact) = v.get_property() {
let bs = u16::from(border_style_to_u8(exact.inner));
cold.border_styles_packed = (cold.border_styles_packed & !0x0F00) | (bs << 8);
}
}
CssProperty::BorderLeftStyle(v) => {
if let Some(exact) = v.get_property() {
let bs = u16::from(border_style_to_u8(exact.inner));
cold.border_styles_packed = (cold.border_styles_packed & !0xF000) | (bs << 12);
}
}
CssProperty::BorderTopColor(v) => {
if let Some(c) = v.get_property() { cold.border_top_color = encode_color_u32(&c.inner); }
}
CssProperty::BorderRightColor(v) => {
if let Some(c) = v.get_property() { cold.border_right_color = encode_color_u32(&c.inner); }
}
CssProperty::BorderBottomColor(v) => {
if let Some(c) = v.get_property() { cold.border_bottom_color = encode_color_u32(&c.inner); }
}
CssProperty::BorderLeftColor(v) => {
if let Some(c) = v.get_property() { cold.border_left_color = encode_color_u32(&c.inner); }
}
CssProperty::BorderSpacing(v) => {
if let Some(spacing) = v.get_property() {
if spacing.horizontal.metric == SizeMetric::Px {
cold.border_spacing_h = encode_resolved_px_i16(spacing.horizontal.number.get());
}
if spacing.vertical.metric == SizeMetric::Px {
cold.border_spacing_v = encode_resolved_px_i16(spacing.vertical.number.get());
}
}
}
CssProperty::TabSize(v) => { cold.tab_size = encode_css_pixel_as_i16(v); }
CssProperty::TextColor(v) => {
if let Some(color) = v.get_property() {
let c = &color.inner;
text.text_color = (u32::from(c.r) << 24) | (u32::from(c.g) << 16) | (u32::from(c.b) << 8) | u32::from(c.a);
}
}
CssProperty::FontFamily(v) => {
if let Some(families) = v.get_property() {
let mut hasher = DefaultHasher::new();
families.hash(&mut hasher);
let h = hasher.finish();
let h = if h == 0 { 1 } else { h };
text.font_family_hash = h;
font_hash_map.insert(h, families.clone());
}
}
CssProperty::LineHeight(v) => {
if let Some(lh) = v.get_property() {
let pct_x10 = (lh.inner.normalized() * 1000.0).round() as i32;
if pct_x10 >= -32768 && pct_x10 < i32::from(I16_SENTINEL_THRESHOLD) {
text.line_height = pct_x10 as i16;
} else {
text.line_height = I16_SENTINEL;
}
}
}
CssProperty::LetterSpacing(v) => { text.letter_spacing = encode_css_pixel_as_i16(v); }
CssProperty::WordSpacing(v) => { text.word_spacing = encode_css_pixel_as_i16(v); }
CssProperty::TextIndent(v) => { text.text_indent = encode_css_pixel_as_i16(v); }
CssProperty::BorderTopLeftRadius(v) => {
if let Some(exact) = v.get_property() {
if exact.inner.metric == SizeMetric::Px {
cold.border_top_left_radius = encode_resolved_px_i16(exact.inner.number.get());
}
}
}
CssProperty::BorderTopRightRadius(v) => {
if let Some(exact) = v.get_property() {
if exact.inner.metric == SizeMetric::Px {
cold.border_top_right_radius = encode_resolved_px_i16(exact.inner.number.get());
}
}
}
CssProperty::BorderBottomLeftRadius(v) => {
if let Some(exact) = v.get_property() {
if exact.inner.metric == SizeMetric::Px {
cold.border_bottom_left_radius = encode_resolved_px_i16(exact.inner.number.get());
}
}
}
CssProperty::BorderBottomRightRadius(v) => {
if let Some(exact) = v.get_property() {
if exact.inner.metric == SizeMetric::Px {
cold.border_bottom_right_radius = encode_resolved_px_i16(exact.inner.number.get());
}
}
}
CssProperty::Opacity(v) => {
if let Some(exact) = v.get_property() {
let o = exact.inner.normalized().clamp(0.0, 1.0);
let byte = (o * 254.0).round() as u8;
cold.opacity = byte;
}
}
CssProperty::Transform(v) => {
if v.get_property().is_some() { cold.hot_flags |= HOT_FLAG_HAS_TRANSFORM; }
}
CssProperty::TransformOrigin(v) => {
if v.get_property().is_some() { cold.hot_flags |= HOT_FLAG_HAS_TRANSFORM_ORIGIN; }
}
CssProperty::BoxShadowTop(v)
| CssProperty::BoxShadowBottom(v)
| CssProperty::BoxShadowLeft(v)
| CssProperty::BoxShadowRight(v) => {
if v.get_property().is_some() { cold.hot_flags |= HOT_FLAG_HAS_BOX_SHADOW; }
}
CssProperty::TextDecoration(v) => {
if v.get_property().is_some() { cold.hot_flags |= HOT_FLAG_HAS_TEXT_DECORATION; }
}
CssProperty::ScrollbarGutter(v) => {
if let Some(exact) = v.get_property() {
use azul_css::props::layout::overflow::StyleScrollbarGutter;
let bits: u8 = match exact {
StyleScrollbarGutter::Auto => SCROLLBAR_GUTTER_AUTO,
StyleScrollbarGutter::Stable => SCROLLBAR_GUTTER_STABLE,
StyleScrollbarGutter::StableBothEdges => SCROLLBAR_GUTTER_BOTH_EDGES,
};
cold.hot_flags = (cold.hot_flags & !HOT_FLAG_SCROLLBAR_GUTTER_MASK)
| ((bits << HOT_FLAG_SCROLLBAR_GUTTER_SHIFT) & HOT_FLAG_SCROLLBAR_GUTTER_MASK);
}
}
CssProperty::BackgroundContent(v) => {
if v.get_property().is_some() { cold.hot_flags |= HOT_FLAG_HAS_BACKGROUND; }
}
CssProperty::ClipPath(v) => {
if v.get_property().is_some() { cold.hot_flags |= HOT_FLAG_HAS_CLIP_PATH; }
}
CssProperty::ScrollbarTrack(v) => {
if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_SCROLLBAR_CSS; }
}
CssProperty::ScrollbarThumb(v) => {
if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_SCROLLBAR_CSS; }
}
CssProperty::ScrollbarButton(v) => {
if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_SCROLLBAR_CSS; }
}
CssProperty::ScrollbarCorner(v) => {
if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_SCROLLBAR_CSS; }
}
CssProperty::ScrollbarWidth(v) => {
if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_SCROLLBAR_CSS; }
}
CssProperty::ScrollbarColor(v) => {
if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_SCROLLBAR_CSS; }
}
CssProperty::ScrollbarVisibility(v) => {
if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_SCROLLBAR_CSS; }
}
CssProperty::ScrollbarFadeDelay(v) => {
if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_SCROLLBAR_CSS; }
}
CssProperty::ScrollbarFadeDuration(v) => {
if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_SCROLLBAR_CSS; }
}
CssProperty::CounterReset(v) => {
if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_COUNTER; }
}
CssProperty::CounterIncrement(v) => {
if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_COUNTER; }
}
CssProperty::BreakBefore(v) | CssProperty::BreakAfter(v) => {
if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_BREAK; }
}
CssProperty::TextOrientation(v) => {
if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_TEXT_ORIENTATION; }
}
CssProperty::TextShadow(v) => {
if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_TEXT_SHADOW; }
}
CssProperty::BackdropFilter(v) => {
if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_BACKDROP_FILTER; }
}
CssProperty::Filter(v) => {
if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_FILTER; }
}
CssProperty::MixBlendMode(v) => {
if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_MIX_BLEND_MODE; }
}
_ => {}
}
}
const fn update_dom_declared_flags(prop: &CssProperty, flags: &mut u32) {
match prop {
CssProperty::ShapeInside(v) => if v.get_property().is_some() { *flags |= DOM_HAS_SHAPE_INSIDE; }
CssProperty::ShapeOutside(v) => if v.get_property().is_some() { *flags |= DOM_HAS_SHAPE_OUTSIDE; }
CssProperty::TextJustify(v) => if v.get_property().is_some() { *flags |= DOM_HAS_TEXT_JUSTIFY; }
CssProperty::TextIndent(v) => if v.get_property().is_some() { *flags |= DOM_HAS_TEXT_INDENT; }
CssProperty::ColumnCount(v) => if v.get_property().is_some() { *flags |= DOM_HAS_COLUMN_COUNT; }
CssProperty::ColumnGap(v) => if v.get_property().is_some() { *flags |= DOM_HAS_COLUMN_GAP; }
CssProperty::ColumnWidth(v) => if v.get_property().is_some() { *flags |= DOM_HAS_COLUMN_WIDTH; }
CssProperty::InitialLetter(v) => if v.get_property().is_some() { *flags |= DOM_HAS_INITIAL_LETTER; }
CssProperty::InitialLetterAlign(v) => if v.get_property().is_some() { *flags |= DOM_HAS_INITIAL_LETTER_ALIGN; }
CssProperty::LineClamp(v) => if v.get_property().is_some() { *flags |= DOM_HAS_LINE_CLAMP; }
CssProperty::HangingPunctuation(v) => if v.get_property().is_some() { *flags |= DOM_HAS_HANGING_PUNCTUATION; }
CssProperty::TextCombineUpright(v) => if v.get_property().is_some() { *flags |= DOM_HAS_TEXT_COMBINE_UPRIGHT; }
CssProperty::ExclusionMargin(v) => if v.get_property().is_some() { *flags |= DOM_HAS_EXCLUSION_MARGIN; }
CssProperty::ShapeMargin(v) => if v.get_property().is_some() { *flags |= DOM_HAS_SHAPE_MARGIN; }
CssProperty::HyphenationLanguage(v) => if v.get_property().is_some() { *flags |= DOM_HAS_HYPHENATION_LANGUAGE; }
CssProperty::UnicodeBidi(v) => if v.get_property().is_some() { *flags |= DOM_HAS_UNICODE_BIDI; }
CssProperty::TextBoxTrim(v) => if v.get_property().is_some() { *flags |= DOM_HAS_TEXT_BOX_TRIM; }
CssProperty::Hyphens(v) => if v.get_property().is_some() { *flags |= DOM_HAS_HYPHENS; }
CssProperty::WordBreak(v) => if v.get_property().is_some() { *flags |= DOM_HAS_WORD_BREAK; }
CssProperty::OverflowWrap(v) => if v.get_property().is_some() { *flags |= DOM_HAS_OVERFLOW_WRAP; }
CssProperty::LineBreak(v) => if v.get_property().is_some() { *flags |= DOM_HAS_LINE_BREAK; }
CssProperty::TextAlignLast(v) => if v.get_property().is_some() { *flags |= DOM_HAS_TEXT_ALIGN_LAST; }
CssProperty::LineHeight(v) => if v.get_property().is_some() { *flags |= DOM_HAS_LINE_HEIGHT; }
_ => {}
}
}
#[allow(clippy::cast_possible_truncation)]
const fn encode_grid_line(line: &azul_css::props::layout::grid::GridLine) -> i16 {
use azul_css::props::layout::grid::GridLine;
match line {
GridLine::Auto => I16_AUTO,
GridLine::Line(n) => {
if *n >= -32000 && *n <= 32000 { *n as i16 } else { I16_SENTINEL }
}
GridLine::Span(n) => {
if *n >= 1 && *n <= 32000 { -(*n as i16) } else { I16_SENTINEL }
}
GridLine::Named(_) => I16_SENTINEL,
}
}
fn encode_layout_width<T: LayoutWidthLike>(val: &CssPropertyValue<T>) -> u32 {
match val {
CssPropertyValue::Exact(w) => w.encode_compact_u32(),
CssPropertyValue::Auto => U32_AUTO,
CssPropertyValue::Initial => U32_INITIAL,
CssPropertyValue::Inherit => U32_INHERIT,
CssPropertyValue::None => U32_NONE,
_ => U32_SENTINEL,
}
}
fn encode_layout_height<T: LayoutWidthLike>(val: &CssPropertyValue<T>) -> u32 {
encode_layout_width(val)
}
trait LayoutWidthLike {
fn encode_compact_u32(&self) -> u32;
}
impl LayoutWidthLike for LayoutWidth {
fn encode_compact_u32(&self) -> u32 {
match self {
Self::Auto => U32_AUTO,
Self::Px(pv) => encode_pixel_value_u32(pv),
Self::MinContent => U32_MIN_CONTENT,
Self::MaxContent => U32_MAX_CONTENT,
Self::FitContent(_) | Self::Calc(_) => U32_SENTINEL,
}
}
}
impl LayoutWidthLike for LayoutHeight {
fn encode_compact_u32(&self) -> u32 {
match self {
Self::Auto => U32_AUTO,
Self::Px(pv) => encode_pixel_value_u32(pv),
Self::MinContent => U32_MIN_CONTENT,
Self::MaxContent => U32_MAX_CONTENT,
Self::FitContent(_) | Self::Calc(_) => U32_SENTINEL,
}
}
}
fn encode_pixel_prop<T: HasInnerPixelValue>(val: &CssPropertyValue<T>) -> u32 {
match val {
CssPropertyValue::Exact(inner) => encode_pixel_value_u32(&inner.get_inner_pixel()),
CssPropertyValue::Auto => U32_AUTO,
CssPropertyValue::Initial => U32_INITIAL,
CssPropertyValue::Inherit => U32_INHERIT,
CssPropertyValue::None => U32_NONE,
_ => U32_SENTINEL,
}
}
trait HasInnerPixelValue {
fn get_inner_pixel(&self) -> azul_css::props::basic::pixel::PixelValue;
}
macro_rules! impl_has_inner_pixel {
($($ty:ty),*) => {
$(
impl HasInnerPixelValue for $ty {
fn get_inner_pixel(&self) -> azul_css::props::basic::pixel::PixelValue {
self.inner
}
}
)*
};
}
impl_has_inner_pixel!(
azul_css::props::layout::dimensions::LayoutMinWidth,
azul_css::props::layout::dimensions::LayoutMaxWidth,
azul_css::props::layout::dimensions::LayoutMinHeight,
azul_css::props::layout::dimensions::LayoutMaxHeight,
azul_css::props::basic::font::StyleFontSize,
azul_css::props::layout::spacing::LayoutPaddingTop,
azul_css::props::layout::spacing::LayoutPaddingRight,
azul_css::props::layout::spacing::LayoutPaddingBottom,
azul_css::props::layout::spacing::LayoutPaddingLeft,
azul_css::props::layout::spacing::LayoutMarginTop,
azul_css::props::layout::spacing::LayoutMarginRight,
azul_css::props::layout::spacing::LayoutMarginBottom,
azul_css::props::layout::spacing::LayoutMarginLeft,
azul_css::props::style::border::LayoutBorderTopWidth,
azul_css::props::style::border::LayoutBorderRightWidth,
azul_css::props::style::border::LayoutBorderBottomWidth,
azul_css::props::style::border::LayoutBorderLeftWidth,
azul_css::props::layout::position::LayoutTop,
azul_css::props::layout::position::LayoutRight,
azul_css::props::layout::position::LayoutInsetBottom,
azul_css::props::layout::position::LayoutLeft,
azul_css::props::style::text::StyleLetterSpacing,
azul_css::props::style::text::StyleWordSpacing,
azul_css::props::style::text::StyleTextIndent,
azul_css::props::style::text::StyleTabSize
);
fn encode_css_pixel_as_i16<T: HasInnerPixelValue>(val: &CssPropertyValue<T>) -> i16 {
let mapped = match val {
CssPropertyValue::Exact(inner) => CssPropertyValue::Exact(inner.get_inner_pixel()),
CssPropertyValue::Auto => CssPropertyValue::Auto,
CssPropertyValue::Initial => CssPropertyValue::Initial,
CssPropertyValue::Inherit => CssPropertyValue::Inherit,
CssPropertyValue::None => CssPropertyValue::None,
_ => return I16_SENTINEL,
};
azul_css::compact_cache::encode_css_pixel_as_i16(&mapped)
}
fn encode_margin_i16<T: HasInnerPixelValue>(val: &CssPropertyValue<T>) -> i16 {
encode_css_pixel_as_i16(val)
}
fn encode_flex_basis(val: &CssPropertyValue<LayoutFlexBasis>) -> u32 {
match val {
CssPropertyValue::Exact(fb) => match fb {
LayoutFlexBasis::Auto => U32_AUTO,
LayoutFlexBasis::Exact(pv) => encode_pixel_value_u32(pv),
},
CssPropertyValue::Auto => U32_AUTO,
CssPropertyValue::Initial => U32_INITIAL,
CssPropertyValue::Inherit => U32_INHERIT,
CssPropertyValue::None => U32_NONE,
_ => U32_SENTINEL,
}
}
#[cfg(test)]
mod audit_tests {
use super::resolve_font_size_to_px;
use crate::dom::NodeId;
use azul_css::compact_cache::{
decode_pixel_value_u32, encode_pixel_value_u32, CompactNodeProps,
};
use azul_css::props::basic::pixel::PixelValue;
#[test]
fn resolve_font_size_em_from_parent() {
let mut dims = vec![CompactNodeProps::default(); 2];
dims[0].font_size = encode_pixel_value_u32(&PixelValue::px(20.0));
dims[1].font_size = encode_pixel_value_u32(&PixelValue::em(2.0));
resolve_font_size_to_px(&mut dims, 1, Some(NodeId::new(0)));
let pv = decode_pixel_value_u32(dims[1].font_size).unwrap();
assert!((pv.number.get() - 40.0).abs() < 0.01, "got {}", pv.number.get());
}
#[test]
fn resolve_font_size_root_em_uses_default() {
let mut dims = vec![CompactNodeProps::default()];
dims[0].font_size = encode_pixel_value_u32(&PixelValue::em(2.0));
resolve_font_size_to_px(&mut dims, 0, None);
let pv = decode_pixel_value_u32(dims[0].font_size).unwrap();
assert!((pv.number.get() - 32.0).abs() < 0.01, "got {}", pv.number.get());
}
#[test]
fn resolve_font_size_rem_reads_root() {
let mut dims = vec![CompactNodeProps::default(); 2];
dims[0].font_size = encode_pixel_value_u32(&PixelValue::px(10.0)); dims[1].font_size = encode_pixel_value_u32(&PixelValue::rem(3.0)); resolve_font_size_to_px(&mut dims, 1, Some(NodeId::new(0)));
let pv = decode_pixel_value_u32(dims[1].font_size).unwrap();
assert!((pv.number.get() - 30.0).abs() < 0.01, "got {}", pv.number.get());
}
}
#[cfg(test)]
#[allow(
clippy::float_cmp,
clippy::unreadable_literal,
clippy::too_many_lines,
clippy::cast_lossless
)]
mod autotest_generated {
use super::*;
use alloc::collections::BTreeMap;
use crate::dom::NodeType;
use crate::styled_dom::NodeHierarchyItem;
use azul_css::props::basic::color::ColorU;
use azul_css::props::basic::font::{StyleFontFamily, StyleFontFamilyVec};
use azul_css::props::basic::length::{FloatValue, PercentageValue};
use azul_css::props::basic::pixel::PixelValue;
use azul_css::props::layout::dimensions::LayoutMinWidth;
use azul_css::props::layout::display::LayoutDisplay;
use azul_css::props::layout::flex::{LayoutFlexGrow, LayoutFlexShrink};
use azul_css::props::layout::grid::{GridLine, GridPlacement, LayoutGap, NamedGridLine};
use azul_css::props::layout::overflow::StyleScrollbarGutter;
use azul_css::props::layout::position::LayoutPosition;
use azul_css::props::layout::spacing::{LayoutMarginTop, LayoutPaddingTop};
use azul_css::props::layout::table::StyleBorderCollapse;
use azul_css::props::style::border::{BorderStyle, StyleBorderTopStyle};
use azul_css::props::style::effects::StyleOpacity;
use azul_css::props::style::text::{
StyleLineHeight, StyleTextColor, StyleTextDecoration, StyleTextIndent,
};
struct Sink {
tier1: u64,
dims: CompactNodeProps,
cold: CompactNodePropsCold,
text: CompactTextProps,
fonts: BTreeMap<u64, StyleFontFamilyVec>,
}
impl Sink {
fn new() -> Self {
Self {
tier1: 0,
dims: CompactNodeProps::default(),
cold: CompactNodePropsCold::default(),
text: CompactTextProps::default(),
fonts: BTreeMap::new(),
}
}
fn apply(&mut self, prop: &CssProperty) {
apply_css_property_to_compact(
prop,
&mut self.tier1,
&mut self.dims,
&mut self.cold,
&mut self.text,
&mut self.fonts,
);
}
fn ua(&mut self, node_type: &NodeType) {
apply_ua_css_to_compact(
node_type,
&mut self.tier1,
&mut self.dims,
&mut self.cold,
&mut self.text,
&mut self.fonts,
);
}
fn snapshot(&self) -> (u64, CompactNodeProps, CompactNodePropsCold, CompactTextProps) {
(self.tier1, self.dims, self.cold, self.text)
}
}
fn div_nodes(n: usize) -> Vec<NodeData> {
(0..n).map(|_| NodeData::create_node(NodeType::Div)).collect()
}
fn linear_hierarchy(n: usize) -> Vec<NodeHierarchyItem> {
(0..n)
.map(|i| NodeHierarchyItem {
parent: i, previous_sibling: 0,
next_sibling: 0,
last_child: if i + 1 < n { i + 2 } else { 0 },
})
.collect()
}
fn padding(px: f32) -> CssPropertyValue<LayoutPaddingTop> {
CssPropertyValue::Exact(LayoutPaddingTop { inner: PixelValue::px(px) })
}
#[test]
fn grid_line_auto_and_named_map_to_their_sentinels() {
assert_eq!(encode_grid_line(&GridLine::Auto), I16_AUTO);
let named = GridLine::Named(NamedGridLine {
grid_line_name: "sidebar".into(),
span_count: 0,
});
assert_eq!(encode_grid_line(&named), I16_SENTINEL);
}
#[test]
fn grid_line_number_boundaries_saturate_instead_of_truncating() {
assert_eq!(encode_grid_line(&GridLine::Line(0)), 0);
assert_eq!(encode_grid_line(&GridLine::Line(1)), 1);
assert_eq!(encode_grid_line(&GridLine::Line(-1)), -1);
assert_eq!(encode_grid_line(&GridLine::Line(32_000)), 32_000);
assert_eq!(encode_grid_line(&GridLine::Line(-32_000)), -32_000);
assert_eq!(encode_grid_line(&GridLine::Line(32_001)), I16_SENTINEL);
assert_eq!(encode_grid_line(&GridLine::Line(-32_001)), I16_SENTINEL);
assert_eq!(encode_grid_line(&GridLine::Line(i32::MAX)), I16_SENTINEL);
assert_eq!(encode_grid_line(&GridLine::Line(i32::MIN)), I16_SENTINEL);
}
#[test]
fn grid_line_span_boundaries_and_nonsense_spans() {
assert_eq!(encode_grid_line(&GridLine::Span(1)), -1);
assert_eq!(encode_grid_line(&GridLine::Span(32_000)), -32_000);
assert_eq!(encode_grid_line(&GridLine::Span(0)), I16_SENTINEL);
assert_eq!(encode_grid_line(&GridLine::Span(-1)), I16_SENTINEL);
assert_eq!(encode_grid_line(&GridLine::Span(32_001)), I16_SENTINEL);
assert_eq!(encode_grid_line(&GridLine::Span(i32::MAX)), I16_SENTINEL);
assert_eq!(encode_grid_line(&GridLine::Span(i32::MIN)), I16_SENTINEL);
}
#[test]
fn grid_line_in_range_values_never_alias_the_sentinel_band() {
for n in [-32_000i32, -1_000, -1, 0, 1, 1_000, 32_000] {
let e = encode_grid_line(&GridLine::Line(n));
assert!(
e < I16_SENTINEL_THRESHOLD,
"Line({n}) encoded into the sentinel band as {e}"
);
}
for n in [1i32, 2, 1_000, 32_000] {
let e = encode_grid_line(&GridLine::Span(n));
assert!(e < 0, "Span({n}) must encode as a negative value, got {e}");
assert!(
e < I16_SENTINEL_THRESHOLD,
"Span({n}) encoded into the sentinel band as {e}"
);
}
}
#[test]
fn layout_width_keywords_map_to_distinct_sentinels() {
let auto: CssPropertyValue<LayoutWidth> = CssPropertyValue::Auto;
let none: CssPropertyValue<LayoutWidth> = CssPropertyValue::None;
let initial: CssPropertyValue<LayoutWidth> = CssPropertyValue::Initial;
let inherit: CssPropertyValue<LayoutWidth> = CssPropertyValue::Inherit;
assert_eq!(encode_layout_width(&auto), U32_AUTO);
assert_eq!(encode_layout_width(&none), U32_NONE);
assert_eq!(encode_layout_width(&initial), U32_INITIAL);
assert_eq!(encode_layout_width(&inherit), U32_INHERIT);
}
#[test]
fn layout_width_revert_and_unset_fall_back_to_the_overflow_sentinel() {
let revert: CssPropertyValue<LayoutWidth> = CssPropertyValue::Revert;
let unset: CssPropertyValue<LayoutWidth> = CssPropertyValue::Unset;
assert_eq!(encode_layout_width(&revert), U32_SENTINEL);
assert_eq!(encode_layout_width(&unset), U32_SENTINEL);
assert_eq!(encode_layout_height(&revert), U32_SENTINEL);
assert_eq!(encode_layout_height(&unset), U32_SENTINEL);
}
#[test]
fn layout_width_exact_keyword_variants() {
assert_eq!(
encode_layout_width(&CssPropertyValue::Exact(LayoutWidth::Auto)),
U32_AUTO
);
assert_eq!(
encode_layout_width(&CssPropertyValue::Exact(LayoutWidth::MinContent)),
U32_MIN_CONTENT
);
assert_eq!(
encode_layout_width(&CssPropertyValue::Exact(LayoutWidth::MaxContent)),
U32_MAX_CONTENT
);
assert_eq!(
encode_layout_width(&CssPropertyValue::Exact(LayoutWidth::FitContent(
PixelValue::px(10.0)
))),
U32_SENTINEL
);
}
#[test]
fn layout_width_px_round_trips() {
for px in [0.0f32, 0.5, 1.0, 100.0, 1234.567, -50.0] {
let enc =
encode_layout_width(&CssPropertyValue::Exact(LayoutWidth::Px(PixelValue::px(px))));
let dec = decode_pixel_value_u32(enc)
.expect("an in-range px value must not encode to a sentinel");
assert_eq!(dec.metric, SizeMetric::Px);
assert!(
(dec.number.get() - px).abs() < 0.002,
"round-trip of {px}px produced {}px",
dec.number.get()
);
}
}
#[test]
fn layout_width_extreme_values_saturate_to_the_overflow_sentinel() {
for px in [
1.0e9f32,
-1.0e9,
f32::MAX,
f32::MIN,
f32::INFINITY,
f32::NEG_INFINITY,
] {
let enc =
encode_layout_width(&CssPropertyValue::Exact(LayoutWidth::Px(PixelValue::px(px))));
assert_eq!(
enc, U32_SENTINEL,
"width {px}px should overflow to U32_SENTINEL, got {enc:#x}"
);
}
}
#[test]
fn layout_width_nan_degrades_to_zero_without_panicking() {
let enc = encode_layout_width(&CssPropertyValue::Exact(LayoutWidth::Px(PixelValue::px(
f32::NAN,
))));
let dec = decode_pixel_value_u32(enc).expect("NaN must degrade to a value, not a sentinel");
assert!(dec.number.get().is_finite());
assert_eq!(dec.number.get(), 0.0);
}
#[test]
fn layout_height_never_diverges_from_layout_width() {
let vals = [
CssPropertyValue::Exact(LayoutWidth::Auto),
CssPropertyValue::Exact(LayoutWidth::MinContent),
CssPropertyValue::Exact(LayoutWidth::MaxContent),
CssPropertyValue::Exact(LayoutWidth::Px(PixelValue::px(42.0))),
CssPropertyValue::Exact(LayoutWidth::Px(PixelValue::px(1.0e9))),
CssPropertyValue::Unset,
];
for v in &vals {
assert_eq!(encode_layout_width(v), encode_layout_height(v));
}
}
#[test]
fn pixel_prop_keywords_map_to_distinct_sentinels() {
let auto: CssPropertyValue<LayoutMinWidth> = CssPropertyValue::Auto;
let none: CssPropertyValue<LayoutMinWidth> = CssPropertyValue::None;
let initial: CssPropertyValue<LayoutMinWidth> = CssPropertyValue::Initial;
let inherit: CssPropertyValue<LayoutMinWidth> = CssPropertyValue::Inherit;
let revert: CssPropertyValue<LayoutMinWidth> = CssPropertyValue::Revert;
let unset: CssPropertyValue<LayoutMinWidth> = CssPropertyValue::Unset;
assert_eq!(encode_pixel_prop(&auto), U32_AUTO);
assert_eq!(encode_pixel_prop(&none), U32_NONE);
assert_eq!(encode_pixel_prop(&initial), U32_INITIAL);
assert_eq!(encode_pixel_prop(&inherit), U32_INHERIT);
assert_eq!(encode_pixel_prop(&revert), U32_SENTINEL);
assert_eq!(encode_pixel_prop(&unset), U32_SENTINEL);
}
#[test]
fn pixel_prop_round_trips_value_and_metric() {
for pv in [
PixelValue::px(50.0),
PixelValue::em(1.5),
PixelValue::percent(80.0),
PixelValue::pt(12.0),
PixelValue::rem(2.0),
] {
let enc = encode_pixel_prop(&CssPropertyValue::Exact(LayoutMinWidth { inner: pv }));
let dec = decode_pixel_value_u32(enc).expect("must round-trip");
assert_eq!(dec.metric, pv.metric, "metric lost in the round-trip");
assert!(
(dec.number.get() - pv.number.get()).abs() < 0.002,
"value lost in the round-trip: {} -> {}",
pv.number.get(),
dec.number.get()
);
}
}
#[test]
fn pixel_prop_overflow_saturates() {
let enc = encode_pixel_prop(&CssPropertyValue::Exact(LayoutMinWidth {
inner: PixelValue::px(1.0e9),
}));
assert_eq!(enc, U32_SENTINEL);
}
#[test]
fn pixel_prop_exact_value_never_aliases_a_semantic_sentinel() {
for metric in [SizeMetric::Vh, SizeMetric::Vmin, SizeMetric::Vmax] {
let pv = PixelValue::from_metric(metric, -0.001);
let enc = encode_pixel_prop(&CssPropertyValue::Exact(LayoutMinWidth { inner: pv }));
assert!(
enc == U32_SENTINEL || enc < U32_SENTINEL_THRESHOLD,
"an Exact viewport length encoded to {enc:#x}, which aliases a semantic sentinel",
);
}
}
#[test]
fn css_pixel_i16_scales_by_ten() {
assert_eq!(encode_css_pixel_as_i16(&padding(0.0)), 0);
assert_eq!(encode_css_pixel_as_i16(&padding(10.5)), 105);
assert_eq!(encode_css_pixel_as_i16(&padding(-10.5)), -105);
}
#[test]
fn css_pixel_i16_boundaries() {
assert_eq!(encode_css_pixel_as_i16(&padding(3276.3)), 32_763);
assert_eq!(encode_css_pixel_as_i16(&padding(3276.4)), I16_SENTINEL);
assert_eq!(encode_css_pixel_as_i16(&padding(-3276.8)), -32_768);
assert_eq!(encode_css_pixel_as_i16(&padding(-3276.9)), I16_SENTINEL);
}
#[test]
fn css_pixel_i16_non_px_units_need_the_slow_path() {
let em = CssPropertyValue::Exact(LayoutPaddingTop { inner: PixelValue::em(2.0) });
let pct = CssPropertyValue::Exact(LayoutPaddingTop {
inner: PixelValue::percent(50.0),
});
assert_eq!(encode_css_pixel_as_i16(&em), I16_SENTINEL);
assert_eq!(encode_css_pixel_as_i16(&pct), I16_SENTINEL);
}
#[test]
fn css_pixel_i16_keywords_are_distinguishable() {
let auto: CssPropertyValue<LayoutPaddingTop> = CssPropertyValue::Auto;
let initial: CssPropertyValue<LayoutPaddingTop> = CssPropertyValue::Initial;
let inherit: CssPropertyValue<LayoutPaddingTop> = CssPropertyValue::Inherit;
let none: CssPropertyValue<LayoutPaddingTop> = CssPropertyValue::None;
let revert: CssPropertyValue<LayoutPaddingTop> = CssPropertyValue::Revert;
let unset: CssPropertyValue<LayoutPaddingTop> = CssPropertyValue::Unset;
assert_eq!(encode_css_pixel_as_i16(&auto), I16_AUTO);
assert_eq!(encode_css_pixel_as_i16(&initial), I16_INITIAL);
assert_eq!(encode_css_pixel_as_i16(&inherit), I16_INHERIT);
assert_eq!(encode_css_pixel_as_i16(&none), I16_SENTINEL);
assert_eq!(encode_css_pixel_as_i16(&revert), I16_SENTINEL);
assert_eq!(encode_css_pixel_as_i16(&unset), I16_SENTINEL);
}
#[test]
fn css_pixel_i16_nan_and_infinity_are_safe() {
assert_eq!(encode_css_pixel_as_i16(&padding(f32::NAN)), 0);
assert_eq!(encode_css_pixel_as_i16(&padding(f32::INFINITY)), I16_SENTINEL);
assert_eq!(
encode_css_pixel_as_i16(&padding(f32::NEG_INFINITY)),
I16_SENTINEL
);
assert_eq!(encode_css_pixel_as_i16(&padding(f32::MAX)), I16_SENTINEL);
assert_eq!(encode_css_pixel_as_i16(&padding(f32::MIN)), I16_SENTINEL);
}
#[test]
fn css_pixel_i16_exact_value_never_aliases_a_keyword_sentinel() {
for px in [
-3276.8f32, -100.0, -0.1, 0.0, 0.1, 100.0, 3276.3, 1.0e9, -1.0e9,
] {
let e = encode_css_pixel_as_i16(&padding(px));
assert!(
e != I16_AUTO && e != I16_INHERIT && e != I16_INITIAL,
"{px}px aliased a keyword sentinel ({e})"
);
}
}
#[test]
fn margin_i16_keeps_auto_and_otherwise_matches_the_pixel_encoder() {
let auto: CssPropertyValue<LayoutMarginTop> = CssPropertyValue::Auto;
assert_eq!(encode_margin_i16(&auto), I16_AUTO);
for px in [-50.0f32, 0.0, 12.5, 3276.3, 5.0e9, f32::NAN] {
let m = CssPropertyValue::Exact(LayoutMarginTop { inner: PixelValue::px(px) });
assert_eq!(encode_margin_i16(&m), encode_css_pixel_as_i16(&padding(px)));
}
}
#[test]
fn flex_basis_all_variants() {
assert_eq!(
encode_flex_basis(&CssPropertyValue::Exact(LayoutFlexBasis::Auto)),
U32_AUTO
);
let enc = encode_flex_basis(&CssPropertyValue::Exact(LayoutFlexBasis::Exact(
PixelValue::px(120.0),
)));
let dec = decode_pixel_value_u32(enc).expect("px flex-basis must round-trip");
assert!((dec.number.get() - 120.0).abs() < 0.002);
assert_eq!(encode_flex_basis(&CssPropertyValue::Auto), U32_AUTO);
assert_eq!(encode_flex_basis(&CssPropertyValue::None), U32_NONE);
assert_eq!(encode_flex_basis(&CssPropertyValue::Initial), U32_INITIAL);
assert_eq!(encode_flex_basis(&CssPropertyValue::Inherit), U32_INHERIT);
assert_eq!(encode_flex_basis(&CssPropertyValue::Revert), U32_SENTINEL);
assert_eq!(encode_flex_basis(&CssPropertyValue::Unset), U32_SENTINEL);
}
#[test]
fn flex_basis_overflow_saturates() {
assert_eq!(
encode_flex_basis(&CssPropertyValue::Exact(LayoutFlexBasis::Exact(
PixelValue::px(1.0e9)
))),
U32_SENTINEL
);
assert_eq!(
encode_flex_basis(&CssPropertyValue::Exact(LayoutFlexBasis::Exact(
PixelValue::px(f32::INFINITY)
))),
U32_SENTINEL
);
}
fn text_indent_prop() -> CssProperty {
CssProperty::TextIndent(CssPropertyValue::Exact(StyleTextIndent::default()))
}
fn line_height_prop(pct: f32) -> CssProperty {
CssProperty::LineHeight(CssPropertyValue::Exact(StyleLineHeight {
inner: PercentageValue::new(pct),
}))
}
#[test]
fn dom_flags_set_the_right_bit_from_zero() {
let mut flags = 0u32;
update_dom_declared_flags(&text_indent_prop(), &mut flags);
assert_eq!(flags, DOM_HAS_TEXT_INDENT);
let mut flags2 = 0u32;
update_dom_declared_flags(&line_height_prop(150.0), &mut flags2);
assert_eq!(flags2, DOM_HAS_LINE_HEIGHT);
}
#[test]
fn dom_flags_only_ever_or_never_clear() {
let mut flags = u32::MAX;
update_dom_declared_flags(&text_indent_prop(), &mut flags);
update_dom_declared_flags(&line_height_prop(150.0), &mut flags);
update_dom_declared_flags(
&CssProperty::Width(CssPropertyValue::Exact(LayoutWidth::px(10.0))),
&mut flags,
);
assert_eq!(flags, u32::MAX);
}
#[test]
fn dom_flags_accumulate_and_are_idempotent() {
let mut flags = 0u32;
update_dom_declared_flags(&text_indent_prop(), &mut flags);
update_dom_declared_flags(&line_height_prop(150.0), &mut flags);
let after_two = flags;
assert_eq!(after_two, DOM_HAS_TEXT_INDENT | DOM_HAS_LINE_HEIGHT);
update_dom_declared_flags(&text_indent_prop(), &mut flags);
update_dom_declared_flags(&line_height_prop(150.0), &mut flags);
assert_eq!(flags, after_two);
}
#[test]
fn dom_flags_are_not_set_for_a_valueless_property() {
let mut flags = 0u32;
update_dom_declared_flags(&CssProperty::LineHeight(CssPropertyValue::Initial), &mut flags);
update_dom_declared_flags(&CssProperty::TextIndent(CssPropertyValue::Auto), &mut flags);
update_dom_declared_flags(&CssProperty::TextIndent(CssPropertyValue::Unset), &mut flags);
assert_eq!(flags, 0);
}
#[test]
fn dom_flags_ignore_unrelated_properties() {
let mut flags = 0u32;
update_dom_declared_flags(
&CssProperty::Width(CssPropertyValue::Exact(LayoutWidth::px(10.0))),
&mut flags,
);
update_dom_declared_flags(
&CssProperty::ZIndex(CssPropertyValue::Exact(LayoutZIndex::Integer(3))),
&mut flags,
);
assert_eq!(flags, 0);
}
#[test]
fn apply_tier1_fields_do_not_bleed_into_each_other() {
let mut s = Sink::new();
s.apply(&CssProperty::Display(CssPropertyValue::Exact(
LayoutDisplay::InlineBlock,
)));
s.apply(&CssProperty::Position(CssPropertyValue::Exact(
LayoutPosition::Absolute,
)));
s.apply(&CssProperty::BorderCollapse(CssPropertyValue::Exact(
StyleBorderCollapse::Collapse,
)));
assert_eq!(
(s.tier1 >> DISPLAY_SHIFT) & DISPLAY_MASK,
u64::from(layout_display_to_u8(LayoutDisplay::InlineBlock))
);
assert_eq!(
(s.tier1 >> POSITION_SHIFT) & POSITION_MASK,
u64::from(layout_position_to_u8(LayoutPosition::Absolute))
);
assert_eq!(
(s.tier1 >> BORDER_COLLAPSE_SHIFT) & BORDER_COLLAPSE_MASK,
u64::from(border_collapse_to_u8(StyleBorderCollapse::Collapse))
);
let known = (DISPLAY_MASK << DISPLAY_SHIFT)
| (POSITION_MASK << POSITION_SHIFT)
| (BORDER_COLLAPSE_MASK << BORDER_COLLAPSE_SHIFT);
assert_eq!(
s.tier1 & !known,
0,
"tier1 = {:#x} has bits set outside the three fields that were written",
s.tier1
);
}
#[test]
fn apply_tier1_overwrite_clears_only_its_own_field() {
let mut s = Sink::new();
s.tier1 = u64::MAX;
s.apply(&CssProperty::Display(CssPropertyValue::Exact(
LayoutDisplay::Block,
)));
assert_eq!(
(s.tier1 >> DISPLAY_SHIFT) & DISPLAY_MASK,
u64::from(layout_display_to_u8(LayoutDisplay::Block))
);
let others = !(DISPLAY_MASK << DISPLAY_SHIFT);
assert_eq!(
s.tier1 & others,
u64::MAX & others,
"neighbouring tier-1 fields were clobbered"
);
}
#[test]
fn apply_tier1_ignores_a_valueless_property() {
let mut s = Sink::new();
s.apply(&CssProperty::Display(CssPropertyValue::Inherit));
assert_eq!(s.tier1, 0, "`display: inherit` has no Exact payload to encode");
}
#[test]
fn apply_width_round_trips_and_touches_nothing_else() {
let mut s = Sink::new();
let before_cold = s.cold;
let before_text = s.text;
s.apply(&CssProperty::Width(CssPropertyValue::Exact(LayoutWidth::Px(
PixelValue::px(320.0),
))));
let dec = decode_pixel_value_u32(s.dims.width).expect("width must round-trip");
assert!((dec.number.get() - 320.0).abs() < 0.002);
assert_eq!(s.tier1, 0, "a tier-2 property must not touch the tier-1 bitfield");
assert_eq!(s.cold, before_cold, "a tier-2 property must not touch tier-2 cold");
assert_eq!(s.text, before_text, "a tier-2 property must not touch tier-2b text");
assert!(s.fonts.is_empty());
}
#[test]
fn apply_flex_grow_saturates_and_rejects_negatives() {
let mut s = Sink::new();
s.apply(&CssProperty::FlexGrow(CssPropertyValue::Exact(LayoutFlexGrow {
inner: FloatValue::new(2.5),
})));
assert_eq!(s.dims.flex_grow, 250);
let mut neg = Sink::new();
neg.apply(&CssProperty::FlexGrow(CssPropertyValue::Exact(LayoutFlexGrow {
inner: FloatValue::new(-1.0),
})));
assert_eq!(neg.dims.flex_grow, U16_SENTINEL);
let mut big = Sink::new();
big.apply(&CssProperty::FlexGrow(CssPropertyValue::Exact(LayoutFlexGrow {
inner: FloatValue::new(1.0e9),
})));
assert_eq!(big.dims.flex_grow, U16_SENTINEL);
let mut nan = Sink::new();
nan.apply(&CssProperty::FlexShrink(CssPropertyValue::Exact(
LayoutFlexShrink { inner: FloatValue::new(f32::NAN) },
)));
assert_eq!(nan.dims.flex_shrink, 0);
}
#[test]
fn apply_gap_px_sets_both_axes_and_ignores_unresolvable_units() {
let mut s = Sink::new();
s.apply(&CssProperty::Gap(CssPropertyValue::Exact(LayoutGap {
inner: PixelValue::px(8.0),
})));
assert_eq!(s.dims.row_gap, 80);
assert_eq!(s.dims.column_gap, 80);
let mut em = Sink::new();
em.apply(&CssProperty::Gap(CssPropertyValue::Exact(LayoutGap {
inner: PixelValue::em(2.0),
})));
assert_eq!(em.dims.row_gap, 0);
assert_eq!(em.dims.column_gap, 0);
}
#[test]
fn apply_z_index_auto_and_in_range_values() {
let mut s = Sink::new();
s.apply(&CssProperty::ZIndex(CssPropertyValue::Exact(LayoutZIndex::Auto)));
assert_eq!(s.cold.z_index, I16_AUTO);
s.apply(&CssProperty::ZIndex(CssPropertyValue::Exact(
LayoutZIndex::Integer(100),
)));
assert_eq!(s.cold.z_index, 100);
s.apply(&CssProperty::ZIndex(CssPropertyValue::Exact(
LayoutZIndex::Integer(32_763),
)));
assert_eq!(s.cold.z_index, 32_763);
}
#[test]
fn apply_z_index_large_positive_saturates() {
for z in [32_764i32, 100_000, i32::MAX] {
let mut s = Sink::new();
s.apply(&CssProperty::ZIndex(CssPropertyValue::Exact(
LayoutZIndex::Integer(z),
)));
assert_eq!(s.cold.z_index, I16_SENTINEL, "z-index {z} should saturate");
}
}
#[test]
fn apply_z_index_large_negative_must_not_wrap_positive() {
for z in [-32_769i32, -40_000, -99_999, i32::MIN] {
let mut s = Sink::new();
s.apply(&CssProperty::ZIndex(CssPropertyValue::Exact(
LayoutZIndex::Integer(z),
)));
assert!(
s.cold.z_index < 0 || s.cold.z_index == I16_SENTINEL,
"z-index {z} encoded to {}: a negative z-index must stay negative (or \
saturate to the sentinel), it must never wrap to a positive value",
s.cold.z_index,
);
}
}
#[test]
fn apply_border_styles_pack_into_independent_nibbles() {
let mut s = Sink::new();
s.apply(&CssProperty::BorderTopStyle(CssPropertyValue::Exact(
StyleBorderTopStyle { inner: BorderStyle::Solid },
)));
assert_eq!(
s.cold.border_styles_packed & 0x000F,
u16::from(border_style_to_u8(BorderStyle::Solid))
);
assert_eq!(
s.cold.border_styles_packed & 0xFFF0,
0,
"the top-style nibble leaked into the other three sides"
);
s.apply(&CssProperty::BorderTopStyle(CssPropertyValue::Exact(
StyleBorderTopStyle { inner: BorderStyle::Double },
)));
assert_eq!(
s.cold.border_styles_packed & 0x000F,
u16::from(border_style_to_u8(BorderStyle::Double))
);
}
#[test]
fn apply_opacity_clamps_into_the_0_254_range() {
for (pct, expected) in [
(-1.0e9f32, 0u8),
(-100.0, 0),
(0.0, 0),
(50.0, 127),
(100.0, 254),
(500.0, 254),
(1.0e9, 254),
] {
let mut s = Sink::new();
s.apply(&CssProperty::Opacity(CssPropertyValue::Exact(StyleOpacity {
inner: PercentageValue::new(pct),
})));
assert_eq!(s.cold.opacity, expected, "opacity: {pct}%");
assert_ne!(
s.cold.opacity, OPACITY_SENTINEL,
"an explicitly set opacity must never encode as the 'unset' sentinel"
);
}
}
#[test]
fn apply_grid_column_encodes_both_lines() {
let mut s = Sink::new();
s.apply(&CssProperty::GridColumn(CssPropertyValue::Exact(GridPlacement {
grid_start: GridLine::Line(2),
grid_end: GridLine::Span(3),
})));
assert_eq!(s.cold.grid_col_start, 2);
assert_eq!(s.cold.grid_col_end, -3);
assert_eq!(s.cold.grid_row_start, I16_AUTO);
assert_eq!(s.cold.grid_row_end, I16_AUTO);
}
#[test]
fn apply_hot_flags_or_in_without_clobbering_each_other() {
let mut s = Sink::new();
s.apply(&CssProperty::TextDecoration(CssPropertyValue::Exact(
StyleTextDecoration::Underline,
)));
assert_eq!(
s.cold.hot_flags & HOT_FLAG_HAS_TEXT_DECORATION,
HOT_FLAG_HAS_TEXT_DECORATION
);
s.apply(&CssProperty::ScrollbarGutter(CssPropertyValue::Exact(
StyleScrollbarGutter::Stable,
)));
assert_eq!(
(s.cold.hot_flags & HOT_FLAG_SCROLLBAR_GUTTER_MASK) >> HOT_FLAG_SCROLLBAR_GUTTER_SHIFT,
SCROLLBAR_GUTTER_STABLE
);
assert_eq!(
s.cold.hot_flags & HOT_FLAG_HAS_TEXT_DECORATION,
HOT_FLAG_HAS_TEXT_DECORATION,
"scrollbar-gutter cleared the has-text-decoration bit"
);
s.apply(&CssProperty::ScrollbarGutter(CssPropertyValue::Exact(
StyleScrollbarGutter::Auto,
)));
assert_eq!(
(s.cold.hot_flags & HOT_FLAG_SCROLLBAR_GUTTER_MASK) >> HOT_FLAG_SCROLLBAR_GUTTER_SHIFT,
SCROLLBAR_GUTTER_AUTO
);
assert_eq!(
s.cold.hot_flags & HOT_FLAG_HAS_TEXT_DECORATION,
HOT_FLAG_HAS_TEXT_DECORATION
);
}
#[test]
fn apply_valueless_property_does_not_set_a_has_flag() {
let mut s = Sink::new();
s.apply(&CssProperty::TextDecoration(CssPropertyValue::Initial));
s.apply(&CssProperty::ScrollbarGutter(CssPropertyValue::Unset));
assert_eq!(s.cold.hot_flags, 0);
}
#[test]
fn apply_text_color_packs_rgba_big_endian() {
let mut s = Sink::new();
s.apply(&CssProperty::TextColor(CssPropertyValue::Exact(StyleTextColor {
inner: ColorU { r: 0x12, g: 0x34, b: 0x56, a: 0x78 },
})));
assert_eq!(s.text.text_color, 0x1234_5678);
let mut transparent = Sink::new();
transparent.apply(&CssProperty::TextColor(CssPropertyValue::Exact(
StyleTextColor { inner: ColorU { r: 0, g: 0, b: 0, a: 0 } },
)));
assert_eq!(transparent.text.text_color, 0);
}
#[test]
fn apply_line_height_round_trips_and_saturates_at_both_ends() {
let mut s = Sink::new();
s.apply(&line_height_prop(120.0));
assert_eq!(s.text.line_height, 1200, "120% must encode as % x 10");
for pct in [1.0e9f32, -1.0e9] {
let mut big = Sink::new();
big.apply(&line_height_prop(pct));
assert_eq!(
big.text.line_height, I16_SENTINEL,
"line-height {pct}% should saturate to the sentinel"
);
}
}
#[test]
fn apply_font_family_hash_is_nonzero_stable_and_registered() {
let arial = StyleFontFamilyVec::from_vec(vec![StyleFontFamily::System("Arial".into())]);
let mut s = Sink::new();
s.apply(&CssProperty::FontFamily(CssPropertyValue::Exact(arial.clone())));
let h = s.text.font_family_hash;
assert_ne!(
h, 0,
"0 is the 'unset' sentinel — a set font-family must never hash to it"
);
assert!(
s.fonts.contains_key(&h),
"the hash must be registered in the reverse map, or consumers cannot resolve it"
);
let mut same = Sink::new();
same.apply(&CssProperty::FontFamily(CssPropertyValue::Exact(arial)));
assert_eq!(same.text.font_family_hash, h);
let mut other = Sink::new();
other.apply(&CssProperty::FontFamily(CssPropertyValue::Exact(
StyleFontFamilyVec::from_vec(vec![StyleFontFamily::System("Times".into())]),
)));
assert_ne!(other.text.font_family_hash, h);
}
#[test]
fn ua_css_is_idempotent_for_every_representative_node_type() {
let nodes = [
NodeData::create_node(NodeType::Html),
NodeData::create_node(NodeType::Body),
NodeData::create_node(NodeType::Div),
NodeData::create_node(NodeType::P),
NodeData::create_node(NodeType::Br),
NodeData::create_text("hello"),
];
for nd in &nodes {
let mut s = Sink::new();
s.ua(&nd.node_type);
let once = s.snapshot();
s.ua(&nd.node_type);
assert_eq!(
s.snapshot(),
once,
"applying UA CSS twice must be a no-op the second time"
);
}
}
#[test]
fn ua_css_never_touches_the_tier1_populated_bit() {
for nt in [NodeType::Html, NodeType::Body, NodeType::Div, NodeType::P] {
let mut s = Sink::new();
s.ua(&nt);
assert_eq!(s.tier1 & TIER1_POPULATED_BIT, 0);
}
}
#[test]
fn ua_css_survives_a_hostile_pre_filled_sink() {
let mut s = Sink::new();
s.tier1 = u64::MAX;
s.dims.width = U32_SENTINEL;
s.dims.font_size = U32_SENTINEL;
s.dims.flex_grow = U16_SENTINEL;
s.cold.z_index = i16::MIN;
s.cold.opacity = OPACITY_SENTINEL;
s.text.line_height = i16::MIN;
s.ua(&NodeType::Div);
assert_eq!(
s.tier1 & TIER1_POPULATED_BIT,
TIER1_POPULATED_BIT,
"UA CSS must not clear bits it does not own"
);
}
#[test]
fn build_compact_cache_handles_zero_nodes() {
let cache = CssPropertyCache::empty(0);
let r = cache.build_compact_cache(&[], &[]);
assert_eq!(r.node_count(), 0);
assert!(r.tier2_dims.is_empty());
assert!(r.font_dirty_nodes.is_empty());
assert!(r.prev_font_hashes.is_empty());
}
#[test]
fn build_compact_cache_tolerates_a_mismatched_prev_font_hash_slice() {
let cache = CssPropertyCache::empty(3);
let nodes = div_nodes(3);
for prev in [vec![1u64, 2, 3, 4, 5, 6], vec![7u64], Vec::new()] {
let r = cache.build_compact_cache(&nodes, &prev);
assert_eq!(r.prev_font_hashes.len(), 3);
assert_eq!(r.node_count(), 3);
}
}
#[test]
fn build_compact_cache_tolerates_short_node_data() {
let cache = CssPropertyCache::empty(4);
let r = cache.build_compact_cache(&div_nodes(2), &[]);
assert_eq!(r.node_count(), 4);
assert_eq!(r.tier2_dims.len(), 4);
assert_eq!(r.tier2_cold.len(), 4);
assert_eq!(r.tier2b_text.len(), 4);
assert_eq!(r.prev_font_hashes.len(), 4);
}
#[test]
fn build_compact_cache_honours_node_count_over_node_data_len() {
let cache = CssPropertyCache::empty(2);
let r = cache.build_compact_cache(&div_nodes(5), &[]);
assert_eq!(r.node_count(), 2);
}
#[test]
fn build_compact_cache_rebuild_with_unchanged_fonts_is_not_dirty() {
let cache = CssPropertyCache::empty(3);
let nodes = div_nodes(3);
let first = cache.build_compact_cache(&nodes, &[]);
let second = cache.build_compact_cache(&nodes, &first.prev_font_hashes);
assert!(
second.font_dirty_nodes.is_empty(),
"a rebuild with identical font hashes must not re-resolve any font chain"
);
}
#[test]
fn build_with_inheritance_handles_zero_nodes() {
let cache = CssPropertyCache::empty(0);
let r = cache.build_compact_cache_with_inheritance(&[], &[], &[]);
assert_eq!(r.node_count(), 0);
let mut msgs = None;
let r2 = cache.build_compact_cache_with_inheritance_debug(&[], &[], &[], &mut msgs);
assert_eq!(r2.node_count(), 0);
assert!(msgs.is_none());
}
#[test]
fn build_with_inheritance_propagates_font_size_down_the_chain() {
let n = 3;
let cache = CssPropertyCache::empty(n);
let r = cache.build_compact_cache_with_inheritance(
&div_nodes(n),
&linear_hierarchy(n),
&[],
);
assert_eq!(r.node_count(), n);
assert_eq!(r.tier2_dims[1].font_size, r.tier2_dims[0].font_size);
assert_eq!(r.tier2_dims[2].font_size, r.tier2_dims[0].font_size);
}
#[test]
fn build_with_inheritance_marks_all_nodes_dirty_on_the_first_build() {
let n = 3;
let cache = CssPropertyCache::empty(n);
let nodes = div_nodes(n);
let hierarchy = linear_hierarchy(n);
let first = cache.build_compact_cache_with_inheritance(&nodes, &hierarchy, &[]);
assert_eq!(first.font_dirty_nodes, vec![0, 1, 2]);
let second =
cache.build_compact_cache_with_inheritance(&nodes, &hierarchy, &first.prev_font_hashes);
assert!(second.font_dirty_nodes.is_empty());
}
#[test]
fn build_with_inheritance_global_star_rules_skip_text_nodes() {
let mut cache = CssPropertyCache::empty(2);
cache
.global_css_props
.push(CssProperty::PaddingTop(padding(5.0)));
let nodes = vec![
NodeData::create_node(NodeType::Div),
NodeData::create_text("hi"),
];
let r = cache.build_compact_cache_with_inheritance(&nodes, &linear_hierarchy(2), &[]);
assert_eq!(
r.tier2_dims[0].padding_top, 50,
"the `*` rule must apply to the element"
);
assert_ne!(
r.tier2_dims[1].padding_top, 50,
"the `*` rule must NOT apply to a text node"
);
}
#[test]
fn build_with_inheritance_debug_messages_are_opt_in() {
let n = 2;
let cache = CssPropertyCache::empty(n);
let nodes = div_nodes(n);
let hierarchy = linear_hierarchy(n);
let mut on = Some(Vec::new());
let _ = cache.build_compact_cache_with_inheritance_debug(&nodes, &hierarchy, &[], &mut on);
assert!(
!on.expect("still Some").is_empty(),
"debug logging must emit at least one cascade message"
);
let mut off = None;
let _ = cache.build_compact_cache_with_inheritance_debug(&nodes, &hierarchy, &[], &mut off);
assert!(off.is_none(), "a None sink must stay None");
}
#[test]
fn resolve_font_size_percent_uses_the_parent() {
let mut dims = vec![CompactNodeProps::default(); 2];
dims[0].font_size = encode_pixel_value_u32(&PixelValue::px(20.0));
dims[1].font_size = encode_pixel_value_u32(&PixelValue::percent(50.0));
resolve_font_size_to_px(&mut dims, 1, Some(NodeId::new(0)));
let pv = decode_pixel_value_u32(dims[1].font_size).expect("must resolve to px");
assert_eq!(pv.metric, SizeMetric::Px);
assert!((pv.number.get() - 10.0).abs() < 0.01, "got {}", pv.number.get());
}
#[test]
fn resolve_font_size_pt_converts_to_px() {
let mut dims = vec![CompactNodeProps::default()];
dims[0].font_size = encode_pixel_value_u32(&PixelValue::pt(12.0));
resolve_font_size_to_px(&mut dims, 0, None);
let pv = decode_pixel_value_u32(dims[0].font_size).expect("must resolve to px");
assert!(
(pv.number.get() - 16.0).abs() < 0.01,
"12pt should be 16px, got {}",
pv.number.get()
);
}
#[test]
fn resolve_font_size_leaves_absolute_and_sentinel_values_alone() {
let mut dims = vec![CompactNodeProps::default(); 2];
dims[0].font_size = encode_pixel_value_u32(&PixelValue::px(20.0));
dims[1].font_size = encode_pixel_value_u32(&PixelValue::px(13.0));
let before = dims[1].font_size;
resolve_font_size_to_px(&mut dims, 1, Some(NodeId::new(0)));
assert_eq!(dims[1].font_size, before);
let mut sent = vec![CompactNodeProps::default(); 2];
sent[0].font_size = encode_pixel_value_u32(&PixelValue::px(20.0));
sent[1].font_size = U32_SENTINEL;
resolve_font_size_to_px(&mut sent, 1, Some(NodeId::new(0)));
assert_eq!(sent[1].font_size, U32_SENTINEL);
let mut def = vec![CompactNodeProps::default(); 2];
assert_eq!(def[1].font_size, U32_INITIAL);
resolve_font_size_to_px(&mut def, 1, Some(NodeId::new(0)));
assert_eq!(def[1].font_size, U32_INITIAL);
}
#[test]
fn resolve_font_size_negative_em_is_deterministic() {
let mut dims = vec![CompactNodeProps::default(); 2];
dims[0].font_size = encode_pixel_value_u32(&PixelValue::px(20.0));
dims[1].font_size = encode_pixel_value_u32(&PixelValue::em(-2.0));
resolve_font_size_to_px(&mut dims, 1, Some(NodeId::new(0)));
let pv = decode_pixel_value_u32(dims[1].font_size).expect("must stay decodable");
assert!(
(pv.number.get() + 40.0).abs() < 0.01,
"-2em of 20px should be -40px, got {}",
pv.number.get()
);
}
#[test]
fn resolve_font_size_overflow_saturates_instead_of_wrapping() {
let mut dims = vec![CompactNodeProps::default(); 2];
dims[0].font_size = encode_pixel_value_u32(&PixelValue::px(20.0));
dims[1].font_size = encode_pixel_value_u32(&PixelValue::em(100_000.0));
resolve_font_size_to_px(&mut dims, 1, Some(NodeId::new(0)));
assert_eq!(
dims[1].font_size, U32_SENTINEL,
"an overflowing font-size must land on the tier-3 sentinel, not wrap"
);
}
#[test]
fn resolve_font_size_nan_em_degrades_to_zero() {
let mut dims = vec![CompactNodeProps::default(); 2];
dims[0].font_size = encode_pixel_value_u32(&PixelValue::px(20.0));
dims[1].font_size = encode_pixel_value_u32(&PixelValue::em(f32::NAN));
resolve_font_size_to_px(&mut dims, 1, Some(NodeId::new(0)));
let pv = decode_pixel_value_u32(dims[1].font_size).expect("must stay decodable");
assert!(pv.number.get().is_finite(), "a NaN font-size must not propagate");
assert_eq!(pv.number.get(), 0.0);
}
#[test]
fn resolve_font_size_root_rem_uses_the_16px_initial_value() {
let mut dims = vec![CompactNodeProps::default()];
dims[0].font_size = encode_pixel_value_u32(&PixelValue::rem(2.0));
resolve_font_size_to_px(&mut dims, 0, None);
let pv = decode_pixel_value_u32(dims[0].font_size).expect("must resolve to px");
assert!(
(pv.number.get() - 32.0).abs() < 0.01,
"root `font-size: 2rem` should resolve against the 16px initial value (= 32px), \
got {}px",
pv.number.get()
);
}
}