1use crate::dom::{NodeData, NodeId};
8use crate::prop_cache::CssPropertyCache;
9
10use crate::styled_dom::StyledNodeState;
11#[allow(clippy::wildcard_imports)]
14use azul_css::compact_cache::*;
15use azul_css::css::CssPropertyValue;
16use azul_css::props::property::CssProperty;
17use azul_css::props::basic::length::SizeMetric;
18use azul_css::props::layout::dimensions::{LayoutHeight, LayoutWidth};
19use azul_css::props::layout::flex::LayoutFlexBasis;
20use azul_css::props::layout::position::LayoutZIndex;
21use core::hash::{Hash, Hasher};
22use alloc::vec::Vec;
23use crate::hash::DefaultHasher;
24
25impl CssPropertyCache {
26 #[allow(clippy::cast_possible_truncation)]
43 #[allow(clippy::similar_names)] #[allow(clippy::too_many_lines, clippy::cognitive_complexity)] pub fn build_compact_cache(
46 &self,
47 node_data: &[NodeData],
48 prev_font_hashes: &[u64],
49 ) -> CompactLayoutCache {
50 let node_count = self.node_count;
51 let default_state = StyledNodeState::default();
52 let mut result = CompactLayoutCache::with_capacity(node_count);
53
54 for (i, nd) in node_data.iter().enumerate().take(node_count) {
55 let node_id = NodeId::new(i);
56
57 let display = self
62 .get_display(nd, &node_id, &default_state)
63 .and_then(|v| v.get_property().copied())
64 .unwrap_or_default();
65 let position = self
66 .get_position(nd, &node_id, &default_state)
67 .and_then(|v| v.get_property().copied())
68 .unwrap_or_default();
69 let float = self
70 .get_float(nd, &node_id, &default_state)
71 .and_then(|v| v.get_property().copied())
72 .unwrap_or_default();
73 let overflow_x = self
74 .get_overflow_x(nd, &node_id, &default_state)
75 .and_then(|v| v.get_property().copied())
76 .unwrap_or_default();
77 let overflow_y = self
78 .get_overflow_y(nd, &node_id, &default_state)
79 .and_then(|v| v.get_property().copied())
80 .unwrap_or_default();
81 let box_sizing = self
82 .get_box_sizing(nd, &node_id, &default_state)
83 .and_then(|v| v.get_property().copied())
84 .unwrap_or_default();
85 let flex_direction = self
86 .get_flex_direction(nd, &node_id, &default_state)
87 .and_then(|v| v.get_property().copied())
88 .unwrap_or_default();
89 let flex_wrap = self
90 .get_flex_wrap(nd, &node_id, &default_state)
91 .and_then(|v| v.get_property().copied())
92 .unwrap_or_default();
93 let justify_content = self
94 .get_justify_content(nd, &node_id, &default_state)
95 .and_then(|v| v.get_property().copied())
96 .unwrap_or_default();
97 let align_items = self
98 .get_align_items(nd, &node_id, &default_state)
99 .and_then(|v| v.get_property().copied())
100 .unwrap_or_default();
101 let align_content = self
102 .get_align_content(nd, &node_id, &default_state)
103 .and_then(|v| v.get_property().copied())
104 .unwrap_or_default();
105 let writing_mode = self
106 .get_writing_mode(nd, &node_id, &default_state)
107 .and_then(|v| v.get_property().copied())
108 .unwrap_or_default();
109 let clear = self
110 .get_clear(nd, &node_id, &default_state)
111 .and_then(|v| v.get_property().copied())
112 .unwrap_or_default();
113 let font_weight = self
114 .get_font_weight(nd, &node_id, &default_state)
115 .and_then(|v| v.get_property().copied())
116 .unwrap_or_default();
117 let font_style = self
118 .get_font_style(nd, &node_id, &default_state)
119 .and_then(|v| v.get_property().copied())
120 .unwrap_or_default();
121 let text_align = self
122 .get_text_align(nd, &node_id, &default_state)
123 .and_then(|v| v.get_property().copied())
124 .unwrap_or_default();
125 let visibility = self
126 .get_visibility(nd, &node_id, &default_state)
127 .and_then(|v| v.get_property().copied())
128 .unwrap_or_default();
129 let white_space = self
130 .get_white_space(nd, &node_id, &default_state)
131 .and_then(|v| v.get_property().copied())
132 .unwrap_or_default();
133 let direction = self
134 .get_direction(nd, &node_id, &default_state)
135 .and_then(|v| v.get_property().copied())
136 .unwrap_or_default();
137 let vertical_align = self
138 .get_vertical_align(nd, &node_id, &default_state)
139 .and_then(|v| v.get_property().copied())
140 .unwrap_or_default();
141
142 let border_collapse = self
143 .get_border_collapse(nd, &node_id, &default_state)
144 .and_then(|v| v.get_property().copied())
145 .unwrap_or_default();
146
147 result.tier1_enums[i] = encode_tier1(
148 display,
149 position,
150 float,
151 overflow_x,
152 overflow_y,
153 box_sizing,
154 flex_direction,
155 flex_wrap,
156 justify_content,
157 align_items,
158 align_content,
159 writing_mode,
160 clear,
161 font_weight,
162 font_style,
163 text_align,
164 visibility,
165 white_space,
166 direction,
167 vertical_align,
168 border_collapse,
169 );
170
171 if let Some(val) = self.get_width(nd, &node_id, &default_state) {
177 result.tier2_dims[i].width = encode_layout_width(val);
178 }
179 if let Some(val) = self.get_height(nd, &node_id, &default_state) {
180 result.tier2_dims[i].height = encode_layout_height(val);
181 }
182
183 if let Some(val) = self.get_min_width(nd, &node_id, &default_state) {
185 result.tier2_dims[i].min_width = encode_pixel_prop(val);
186 }
187 if let Some(val) = self.get_max_width(nd, &node_id, &default_state) {
188 result.tier2_dims[i].max_width = encode_pixel_prop(val);
189 }
190 if let Some(val) = self.get_min_height(nd, &node_id, &default_state) {
191 result.tier2_dims[i].min_height = encode_pixel_prop(val);
192 }
193 if let Some(val) = self.get_max_height(nd, &node_id, &default_state) {
194 result.tier2_dims[i].max_height = encode_pixel_prop(val);
195 }
196
197 if let Some(val) = self.get_flex_basis(nd, &node_id, &default_state) {
199 result.tier2_dims[i].flex_basis = encode_flex_basis(val);
200 }
201
202 if let Some(val) = self.get_font_size(nd, &node_id, &default_state) {
204 result.tier2_dims[i].font_size = encode_pixel_prop(val);
205 }
206
207 if let Some(val) = self.get_padding_top(nd, &node_id, &default_state) {
209 result.tier2_dims[i].padding_top = encode_css_pixel_as_i16(val);
210 }
211 if let Some(val) = self.get_padding_right(nd, &node_id, &default_state) {
212 result.tier2_dims[i].padding_right = encode_css_pixel_as_i16(val);
213 }
214 if let Some(val) = self.get_padding_bottom(nd, &node_id, &default_state) {
215 result.tier2_dims[i].padding_bottom = encode_css_pixel_as_i16(val);
216 }
217 if let Some(val) = self.get_padding_left(nd, &node_id, &default_state) {
218 result.tier2_dims[i].padding_left = encode_css_pixel_as_i16(val);
219 }
220
221 if let Some(val) = self.get_margin_top(nd, &node_id, &default_state) {
223 result.tier2_dims[i].margin_top = encode_margin_i16(val);
224 }
225 if let Some(val) = self.get_margin_right(nd, &node_id, &default_state) {
226 result.tier2_dims[i].margin_right = encode_margin_i16(val);
227 }
228 if let Some(val) = self.get_margin_bottom(nd, &node_id, &default_state) {
229 result.tier2_dims[i].margin_bottom = encode_margin_i16(val);
230 }
231 if let Some(val) = self.get_margin_left(nd, &node_id, &default_state) {
232 result.tier2_dims[i].margin_left = encode_margin_i16(val);
233 }
234
235 if let Some(val) = self.get_border_top_width(nd, &node_id, &default_state) {
237 result.tier2_dims[i].border_top_width = encode_css_pixel_as_i16(val);
238 }
239 if let Some(val) = self.get_border_right_width(nd, &node_id, &default_state) {
240 result.tier2_dims[i].border_right_width = encode_css_pixel_as_i16(val);
241 }
242 if let Some(val) = self.get_border_bottom_width(nd, &node_id, &default_state) {
243 result.tier2_dims[i].border_bottom_width = encode_css_pixel_as_i16(val);
244 }
245 if let Some(val) = self.get_border_left_width(nd, &node_id, &default_state) {
246 result.tier2_dims[i].border_left_width = encode_css_pixel_as_i16(val);
247 }
248
249 if let Some(val) = self.get_top(nd, &node_id, &default_state) {
251 result.tier2_dims[i].top = encode_css_pixel_as_i16(val);
252 }
253 if let Some(val) = self.get_right(nd, &node_id, &default_state) {
254 result.tier2_dims[i].right = encode_css_pixel_as_i16(val);
255 }
256 if let Some(val) = self.get_bottom(nd, &node_id, &default_state) {
257 result.tier2_dims[i].bottom = encode_css_pixel_as_i16(val);
258 }
259 if let Some(val) = self.get_left(nd, &node_id, &default_state) {
260 result.tier2_dims[i].left = encode_css_pixel_as_i16(val);
261 }
262
263 if let Some(val) = self.get_flex_grow(nd, &node_id, &default_state) {
265 if let Some(exact) = val.get_property() {
266 result.tier2_dims[i].flex_grow = encode_flex_u16(exact.inner.get());
267 }
268 }
269 if let Some(val) = self.get_flex_shrink(nd, &node_id, &default_state) {
270 if let Some(exact) = val.get_property() {
271 result.tier2_dims[i].flex_shrink = encode_flex_u16(exact.inner.get());
272 }
273 }
274
275 if let Some(val) = self.get_z_index(nd, &node_id, &default_state) {
281 if let Some(exact) = val.get_property() {
282 match exact {
283 LayoutZIndex::Auto => result.tier2_cold[i].z_index = I16_AUTO,
284 LayoutZIndex::Integer(z) => {
285 result.tier2_cold[i].z_index =
290 if *z >= -32768 && *z < i32::from(I16_SENTINEL_THRESHOLD) {
291 *z as i16
292 } else {
293 I16_SENTINEL
294 };
295 }
296 }
297 }
298 }
299
300 {
302 let bts = self.get_border_top_style(nd, &node_id, &default_state)
303 .and_then(|v| v.get_property().copied())
304 .map(|v| v.inner)
305 .unwrap_or_default();
306 let brs = self.get_border_right_style(nd, &node_id, &default_state)
307 .and_then(|v| v.get_property().copied())
308 .map(|v| v.inner)
309 .unwrap_or_default();
310 let bbs = self.get_border_bottom_style(nd, &node_id, &default_state)
311 .and_then(|v| v.get_property().copied())
312 .map(|v| v.inner)
313 .unwrap_or_default();
314 let bls = self.get_border_left_style(nd, &node_id, &default_state)
315 .and_then(|v| v.get_property().copied())
316 .map(|v| v.inner)
317 .unwrap_or_default();
318 result.tier2_cold[i].border_styles_packed =
319 encode_border_styles_packed(bts, brs, bbs, bls);
320 }
321
322 if let Some(val) = self.get_border_top_color(nd, &node_id, &default_state) {
324 if let Some(color) = val.get_property() {
325 result.tier2_cold[i].border_top_color = encode_color_u32(&color.inner);
326 }
327 }
328 if let Some(val) = self.get_border_right_color(nd, &node_id, &default_state) {
329 if let Some(color) = val.get_property() {
330 result.tier2_cold[i].border_right_color = encode_color_u32(&color.inner);
331 }
332 }
333 if let Some(val) = self.get_border_bottom_color(nd, &node_id, &default_state) {
334 if let Some(color) = val.get_property() {
335 result.tier2_cold[i].border_bottom_color = encode_color_u32(&color.inner);
336 }
337 }
338 if let Some(val) = self.get_border_left_color(nd, &node_id, &default_state) {
339 if let Some(color) = val.get_property() {
340 result.tier2_cold[i].border_left_color = encode_color_u32(&color.inner);
341 }
342 }
343
344 if let Some(val) = self.get_border_spacing(nd, &node_id, &default_state) {
346 if let Some(spacing) = val.get_property() {
347 if spacing.horizontal.metric == SizeMetric::Px {
348 result.tier2_cold[i].border_spacing_h = encode_resolved_px_i16(spacing.horizontal.number.get());
349 }
350 if spacing.vertical.metric == SizeMetric::Px {
351 result.tier2_cold[i].border_spacing_v = encode_resolved_px_i16(spacing.vertical.number.get());
352 }
353 }
354 }
355
356 if let Some(val) = self.get_tab_size(nd, &node_id, &default_state) {
358 result.tier2_cold[i].tab_size = encode_css_pixel_as_i16(val);
359 }
360
361 if let Some(val) = self.get_text_color(nd, &node_id, &default_state) {
367 if let Some(color) = val.get_property() {
368 let c = &color.inner;
369 result.tier2b_text[i].text_color =
370 (u32::from(c.r) << 24) | (u32::from(c.g) << 16) | (u32::from(c.b) << 8) | u32::from(c.a);
371 }
372 }
373
374 if let Some(val) = self.get_font_family(nd, &node_id, &default_state) {
376 if let Some(families) = val.get_property() {
377 let mut hasher = DefaultHasher::new();
378 families.hash(&mut hasher);
379 let h = hasher.finish();
380 let h = if h == 0 { 1 } else { h };
381 result.tier2b_text[i].font_family_hash = h;
382 result.font_hash_to_families.insert(h, families.clone());
383 }
384 }
385
386 if let Some(val) = self.get_line_height(nd, &node_id, &default_state) {
397 if let Some(lh) = val.get_property() {
398 let n = lh.inner.normalized();
399 let stored = if n < 0.0 {
400 ((n * 10.0).round() as i32).max(-32768)
403 } else {
404 (n * 1000.0).round() as i32
405 };
406 if stored >= -32768 && stored < i32::from(I16_SENTINEL_THRESHOLD) {
407 result.tier2b_text[i].line_height = stored as i16;
408 } else {
409 result.tier2b_text[i].line_height = I16_SENTINEL;
410 }
411 }
412 }
413
414 if let Some(val) = self.get_letter_spacing(nd, &node_id, &default_state) {
416 result.tier2b_text[i].letter_spacing = encode_css_pixel_as_i16(val);
417 }
418
419 if let Some(val) = self.get_word_spacing(nd, &node_id, &default_state) {
421 result.tier2b_text[i].word_spacing = encode_css_pixel_as_i16(val);
422 }
423
424 if let Some(val) = self.get_text_indent(nd, &node_id, &default_state) {
426 result.tier2b_text[i].text_indent = encode_css_pixel_as_i16(val);
427 }
428 }
429
430 result.font_dirty_nodes.clear();
437 for i in 0..node_count {
438 let new_hash = result.tier2b_text[i].font_family_hash;
439 let old_hash = prev_font_hashes.get(i).copied().unwrap_or(0);
440 if new_hash != old_hash {
441 result.font_dirty_nodes.push(i);
442 }
443 }
444 result.prev_font_hashes = result.tier2b_text.iter().map(|t| t.font_family_hash).collect();
446
447 result
448 }
449
450 pub fn build_compact_cache_with_inheritance(
461 &self,
462 node_data: &[NodeData],
463 node_hierarchy: &[crate::styled_dom::NodeHierarchyItem],
464 prev_font_hashes: &[u64],
465 ) -> CompactLayoutCache {
466 self.build_compact_cache_with_inheritance_debug(node_data, node_hierarchy, prev_font_hashes, &mut None)
467 }
468
469 #[allow(clippy::too_many_lines, clippy::cognitive_complexity)] pub fn build_compact_cache_with_inheritance_debug(
472 &self,
473 node_data: &[NodeData],
474 node_hierarchy: &[crate::styled_dom::NodeHierarchyItem],
475 prev_font_hashes: &[u64],
476 debug_messages: &mut Option<Vec<azul_css::LayoutDebugMessage>>,
477 ) -> CompactLayoutCache {
478 const INHERITABLE_TIER1_MASK: u64 =
481 (FONT_WEIGHT_MASK << FONT_WEIGHT_SHIFT)
482 | (FONT_STYLE_MASK << FONT_STYLE_SHIFT)
483 | (TEXT_ALIGN_MASK << TEXT_ALIGN_SHIFT)
484 | (VISIBILITY_MASK << VISIBILITY_SHIFT)
485 | (WHITE_SPACE_MASK << WHITE_SPACE_SHIFT)
486 | (DIRECTION_MASK << DIRECTION_SHIFT)
487 | (BORDER_COLLAPSE_MASK << BORDER_COLLAPSE_SHIFT);
488
489 let node_count = self.node_count;
490 let default_state = StyledNodeState::default();
491 let mut result = CompactLayoutCache::with_capacity(node_count);
492
493 let mut global_tier1: u64 = 0;
496 let mut global_dims = CompactNodeProps::default();
497 let mut global_cold = CompactNodePropsCold::default();
498 let mut global_text = CompactTextProps::default();
499 let has_global = !self.global_css_props.is_empty();
500
501 if has_global {
502 use azul_css::props::property::CssProperty;
503
504 for prop in &self.global_css_props {
505 result.uses_viewport_units |= css_property_uses_viewport_units(prop);
506 macro_rules! global_tier1_enum {
508 ($variant:ident, $shift:ident, $mask:ident, $encoder:ident) => {
509 if let CssProperty::$variant(v) = prop {
510 if let Some(exact) = v.get_property() {
511 let encoded = u64::from($encoder(*exact));
512 let shifted_mask = $mask << $shift;
513 global_tier1 = (global_tier1 & !shifted_mask) | ((encoded & $mask) << $shift);
514 }
515 }
516 };
517 }
518
519 global_tier1_enum!(Display, DISPLAY_SHIFT, DISPLAY_MASK, layout_display_to_u8);
520 global_tier1_enum!(Position, POSITION_SHIFT, POSITION_MASK, layout_position_to_u8);
521 global_tier1_enum!(Float, FLOAT_SHIFT, FLOAT_MASK, layout_float_to_u8);
522 global_tier1_enum!(OverflowX, OVERFLOW_X_SHIFT, OVERFLOW_MASK, layout_overflow_to_u8);
523 global_tier1_enum!(OverflowY, OVERFLOW_Y_SHIFT, OVERFLOW_MASK, layout_overflow_to_u8);
524 global_tier1_enum!(BoxSizing, BOX_SIZING_SHIFT, BOX_SIZING_MASK, layout_box_sizing_to_u8);
525 global_tier1_enum!(FlexDirection, FLEX_DIRECTION_SHIFT, FLEX_DIR_MASK, layout_flex_direction_to_u8);
526 global_tier1_enum!(FlexWrap, FLEX_WRAP_SHIFT, FLEX_WRAP_MASK, layout_flex_wrap_to_u8);
527 global_tier1_enum!(JustifyContent, JUSTIFY_CONTENT_SHIFT, JUSTIFY_MASK, layout_justify_content_to_u8);
528 global_tier1_enum!(AlignItems, ALIGN_ITEMS_SHIFT, ALIGN_MASK, layout_align_items_to_u8);
529 global_tier1_enum!(AlignContent, ALIGN_CONTENT_SHIFT, ALIGN_MASK, layout_align_content_to_u8);
530 global_tier1_enum!(Clear, CLEAR_SHIFT, CLEAR_MASK, layout_clear_to_u8);
531 global_tier1_enum!(Visibility, VISIBILITY_SHIFT, VISIBILITY_MASK, style_visibility_to_u8);
532 global_tier1_enum!(WritingMode, WRITING_MODE_SHIFT, WRITING_MODE_MASK, layout_writing_mode_to_u8);
533 global_tier1_enum!(FontWeight, FONT_WEIGHT_SHIFT, FONT_WEIGHT_MASK, style_font_weight_to_u8);
534 global_tier1_enum!(FontStyle, FONT_STYLE_SHIFT, FONT_STYLE_MASK, style_font_style_to_u8);
535 global_tier1_enum!(TextAlign, TEXT_ALIGN_SHIFT, TEXT_ALIGN_MASK, style_text_align_to_u8);
536 global_tier1_enum!(WhiteSpace, WHITE_SPACE_SHIFT, WHITE_SPACE_MASK, style_white_space_to_u8);
537 global_tier1_enum!(Direction, DIRECTION_SHIFT, DIRECTION_MASK, style_direction_to_u8);
538 global_tier1_enum!(VerticalAlign, VERTICAL_ALIGN_SHIFT, VERTICAL_ALIGN_MASK, style_vertical_align_to_u8);
539 global_tier1_enum!(BorderCollapse, BORDER_COLLAPSE_SHIFT, BORDER_COLLAPSE_MASK, border_collapse_to_u8);
540
541 match prop {
543 CssProperty::PaddingTop(v) => { global_dims.padding_top = encode_css_pixel_as_i16(v); }
544 CssProperty::PaddingRight(v) => { global_dims.padding_right = encode_css_pixel_as_i16(v); }
545 CssProperty::PaddingBottom(v) => { global_dims.padding_bottom = encode_css_pixel_as_i16(v); }
546 CssProperty::PaddingLeft(v) => { global_dims.padding_left = encode_css_pixel_as_i16(v); }
547 CssProperty::MarginTop(v) => { global_dims.margin_top = encode_margin_i16(v); }
548 CssProperty::MarginRight(v) => { global_dims.margin_right = encode_margin_i16(v); }
549 CssProperty::MarginBottom(v) => { global_dims.margin_bottom = encode_margin_i16(v); }
550 CssProperty::MarginLeft(v) => { global_dims.margin_left = encode_margin_i16(v); }
551 CssProperty::Width(v) => { global_dims.width = encode_layout_width(v); }
552 CssProperty::Height(v) => { global_dims.height = encode_layout_height(v); }
553 CssProperty::FontSize(v) => { global_dims.font_size = encode_pixel_prop(v); }
554 CssProperty::BorderTopWidth(v) => { global_dims.border_top_width = encode_css_pixel_as_i16(v); }
555 CssProperty::BorderRightWidth(v) => { global_dims.border_right_width = encode_css_pixel_as_i16(v); }
556 CssProperty::BorderBottomWidth(v) => { global_dims.border_bottom_width = encode_css_pixel_as_i16(v); }
557 CssProperty::BorderLeftWidth(v) => { global_dims.border_left_width = encode_css_pixel_as_i16(v); }
558 _ => {}
559 }
560 }
561
562 if global_tier1 != 0 {
563 global_tier1 |= TIER1_POPULATED_BIT;
564 }
565 }
566
567 macro_rules! cascade_debug {
569 ($($arg:tt)*) => {
570 if let Some(ref mut msgs) = debug_messages {
571 msgs.push(azul_css::LayoutDebugMessage::css_getter(format!($($arg)*)));
572 }
573 };
574 }
575
576 for i in 0..node_count {
577 let node_id = NodeId::new(i);
578 let nd = &node_data[i];
579
580 let parent_id = node_hierarchy[i].parent_id();
594 if let Some(pid) = parent_id {
595 let pi = pid.index();
596
597 debug_assert!(
606 pi < i,
607 "compact cascade: non-pre-order arena — node {i}'s parent {pi} \
608 is not stored before it; inheritance would read default values",
609 );
610 if pi < i {
611 result.tier1_enums[i] = result.tier1_enums[pi] & INHERITABLE_TIER1_MASK;
613
614 result.tier2_dims[i].font_size = result.tier2_dims[pi].font_size;
616
617 result.tier2_cold[i].border_spacing_h = result.tier2_cold[pi].border_spacing_h;
619 result.tier2_cold[i].border_spacing_v = result.tier2_cold[pi].border_spacing_v;
620 result.tier2_cold[i].tab_size = result.tier2_cold[pi].tab_size;
621
622 result.tier2b_text[i] = result.tier2b_text[pi];
624 }
625 }
626
627 {
628 let d = &result.tier2_dims[i];
629 cascade_debug!("node[{}] {:?} after-inherit: pt={} pb={} pl={} pr={} mt={} mb={} ml={} mr={} w={} h={}",
630 i, nd.node_type, d.padding_top, d.padding_bottom, d.padding_left, d.padding_right,
631 d.margin_top, d.margin_bottom, d.margin_left, d.margin_right, d.width, d.height);
632 }
633
634 apply_ua_css_to_compact(
637 &nd.node_type,
638 &mut result.tier1_enums[i],
639 &mut result.tier2_dims[i],
640 &mut result.tier2_cold[i],
641 &mut result.tier2b_text[i],
642 &mut result.font_hash_to_families,
643 );
644
645 {
646 let d = &result.tier2_dims[i];
647 cascade_debug!("node[{}] {:?} after-UA: pt={} pb={} pl={} pr={} mt={} mb={} ml={} mr={}",
648 i, nd.node_type, d.padding_top, d.padding_bottom, d.padding_left, d.padding_right,
649 d.margin_top, d.margin_bottom, d.margin_left, d.margin_right);
650 }
651
652 if !nd.is_text_node() {
661 for prop in &self.global_css_props {
662 apply_css_property_to_compact(
664 prop,
665 &mut result.tier1_enums[i],
666 &mut result.tier2_dims[i],
667 &mut result.tier2_cold[i],
668 &mut result.tier2b_text[i],
669 &mut result.font_hash_to_families,
670 );
671 update_dom_declared_flags(prop, &mut result.dom_declared_flags);
672 }
673 }
674
675 {
676 let d = &result.tier2_dims[i];
677 cascade_debug!("node[{}] {:?} after-global-star: pt={} pb={} pl={} pr={} mt={} mb={} ml={} mr={}",
678 i, nd.node_type, d.padding_top, d.padding_bottom, d.padding_left, d.padding_right,
679 d.margin_top, d.margin_bottom, d.margin_left, d.margin_right);
680 let n_props = self.css_props.get_slice(i).len();
681 let n_inline = nd.style.iter_inline_properties().count();
682 cascade_debug!("node[{}] css_props={} entries, inline={} entries", i, n_props, n_inline);
683 for prop in self.css_props.get_slice(i) {
684 cascade_debug!("node[{}] css_prop: state={:?} type={:?}", i, prop.state, prop.prop_type);
685 }
686 }
687
688 for prop in self.css_props.get_slice(i) {
694 if prop.state != azul_css::dynamic_selector::PseudoStateType::Normal { continue; }
695 result.uses_viewport_units |= css_property_uses_viewport_units(&prop.property);
696 apply_css_property_to_compact(
697 &prop.property,
698 &mut result.tier1_enums[i],
699 &mut result.tier2_dims[i],
700 &mut result.tier2_cold[i],
701 &mut result.tier2b_text[i],
702 &mut result.font_hash_to_families,
703 );
704 update_dom_declared_flags(&prop.property, &mut result.dom_declared_flags);
705 }
706
707 {
708 let d = &result.tier2_dims[i];
709 cascade_debug!("node[{}] {:?} after-css-props: pt={} pb={} pl={} pr={} mt={} mb={} ml={} mr={}",
710 i, nd.node_type, d.padding_top, d.padding_bottom, d.padding_left, d.padding_right,
711 d.margin_top, d.margin_bottom, d.margin_left, d.margin_right);
712 }
713
714 for (prop, conds) in nd.style.iter_inline_properties() {
717 let is_normal = conds.as_slice().is_empty()
726 || conds.as_slice().iter().all(|c| match c {
727 azul_css::dynamic_selector::DynamicSelector::PseudoState(s) => {
728 *s == azul_css::dynamic_selector::PseudoStateType::Normal
729 }
730 non_pseudo => {
731 result.has_dynamic_conditions = true;
732 {
737 let mut w = Vec::new();
738 let mut h = Vec::new();
739 azul_css::dynamic_selector::collect_viewport_thresholds(
740 core::slice::from_ref(non_pseudo),
741 &mut w,
742 &mut h,
743 );
744 result
745 .inline_viewport_w
746 .extend(w.into_iter().map(f32::to_bits));
747 result
748 .inline_viewport_h
749 .extend(h.into_iter().map(f32::to_bits));
750 }
751 self.dynamic_context
752 .as_deref()
753 .is_some_and(|ctx| non_pseudo.matches(ctx))
754 }
755 });
756 if !is_normal { continue; }
757 result.uses_viewport_units |= css_property_uses_viewport_units(prop);
758 if let CssProperty::Width(v) = prop {
765 result.tier2_dims[i].width = encode_layout_width(v);
766 } else if let CssProperty::Height(v) = prop {
767 result.tier2_dims[i].height = encode_layout_height(v);
768 } else if let CssProperty::FlexGrow(v) = prop {
769 if let Some(e) = v.get_property() {
770 result.tier2_dims[i].flex_grow = encode_flex_u16(e.inner.get());
771 }
772 } else if let CssProperty::Display(v) = prop {
773 if let Some(e) = v.get_property() {
774 let enc = u64::from(layout_display_to_u8(*e));
775 let m = DISPLAY_MASK;
776 let s = DISPLAY_SHIFT;
777 result.tier1_enums[i] = (result.tier1_enums[i] & !(m << s)) | ((enc & m) << s);
778 }
779 } else {
780 apply_css_property_to_compact(
781 prop,
782 &mut result.tier1_enums[i],
783 &mut result.tier2_dims[i],
784 &mut result.tier2_cold[i],
785 &mut result.tier2b_text[i],
786 &mut result.font_hash_to_families,
787 );
788 }
789 update_dom_declared_flags(prop, &mut result.dom_declared_flags);
790 }
791
792 if let Some(user_props) = self.user_overridden_properties.get(i) {
803 for (_, prop) in user_props {
804 result.uses_viewport_units |= css_property_uses_viewport_units(prop);
805 if let CssProperty::Width(v) = prop {
806 result.tier2_dims[i].width = encode_layout_width(v);
807 } else if let CssProperty::Height(v) = prop {
808 result.tier2_dims[i].height = encode_layout_height(v);
809 } else if let CssProperty::FlexGrow(v) = prop {
810 if let Some(e) = v.get_property() {
811 result.tier2_dims[i].flex_grow = encode_flex_u16(e.inner.get());
812 }
813 } else if let CssProperty::Display(v) = prop {
814 if let Some(e) = v.get_property() {
815 let enc = u64::from(layout_display_to_u8(*e));
816 let m = DISPLAY_MASK;
817 let s = DISPLAY_SHIFT;
818 result.tier1_enums[i] =
819 (result.tier1_enums[i] & !(m << s)) | ((enc & m) << s);
820 }
821 } else {
822 apply_css_property_to_compact(
823 prop,
824 &mut result.tier1_enums[i],
825 &mut result.tier2_dims[i],
826 &mut result.tier2_cold[i],
827 &mut result.tier2b_text[i],
828 &mut result.font_hash_to_families,
829 );
830 }
831 update_dom_declared_flags(prop, &mut result.dom_declared_flags);
832 }
833 }
834
835 resolve_font_size_to_px(
839 &mut result.tier2_dims,
840 i,
841 parent_id,
842 );
843
844 if result.tier1_enums[i] != 0 {
846 result.tier1_enums[i] |= TIER1_POPULATED_BIT;
847 }
848 }
849
850 result.font_dirty_nodes.clear();
857 let first_build = prev_font_hashes.is_empty();
858 for i in 0..node_count {
859 let new_hash = result.tier2b_text[i].font_family_hash;
860 let old_hash = prev_font_hashes.get(i).copied().unwrap_or(0);
861 if first_build || new_hash != old_hash {
862 result.font_dirty_nodes.push(i);
863 }
864 }
865 result.prev_font_hashes = result.tier2b_text.iter().map(|t| t.font_family_hash).collect();
866
867 result.inline_viewport_w.sort_unstable();
870 result.inline_viewport_w.dedup();
871 result.inline_viewport_h.sort_unstable();
872 result.inline_viewport_h.dedup();
873
874 result
875 }
876}
877
878fn apply_ua_css_to_compact(
885 node_type: &crate::dom::NodeType,
886 tier1: &mut u64,
887 dims: &mut CompactNodeProps,
888 cold: &mut CompactNodePropsCold,
889 text: &mut CompactTextProps,
890 font_hash_map: &mut alloc::collections::BTreeMap<u64, azul_css::props::basic::font::StyleFontFamilyVec>,
891) {
892 use azul_css::props::property::CssPropertyType as PT2;
893 const UA_PROPERTY_TYPES: &[PT2] = &[
894 PT2::Display, PT2::Position, PT2::Float, PT2::Clear,
896 PT2::OverflowX, PT2::OverflowY, PT2::BoxSizing,
897 PT2::FlexDirection, PT2::FlexWrap, PT2::JustifyContent,
898 PT2::AlignItems, PT2::AlignContent, PT2::WritingMode,
899 PT2::FontWeight, PT2::FontStyle, PT2::TextAlign,
900 PT2::Visibility, PT2::WhiteSpace, PT2::Direction,
901 PT2::VerticalAlign, PT2::BorderCollapse,
902 PT2::Width, PT2::Height, PT2::FontSize,
904 PT2::MarginTop, PT2::MarginBottom, PT2::MarginLeft, PT2::MarginRight,
905 PT2::PaddingTop, PT2::PaddingBottom, PT2::PaddingLeft, PT2::PaddingRight,
906 PT2::BorderTopWidth, PT2::BorderTopStyle, PT2::BorderTopColor,
907 PT2::BorderRightWidth, PT2::BorderRightStyle, PT2::BorderRightColor,
908 PT2::BorderBottomWidth, PT2::BorderBottomStyle, PT2::BorderBottomColor,
909 PT2::BorderLeftWidth, PT2::BorderLeftStyle, PT2::BorderLeftColor,
910 PT2::TextColor, PT2::LineHeight, PT2::LetterSpacing, PT2::WordSpacing,
912 PT2::TextDecoration, PT2::Cursor, PT2::ListStyleType,
913 PT2::CounterReset, PT2::CounterIncrement,
919 ];
920 for pt in UA_PROPERTY_TYPES {
921 if let Some(ua_prop) = crate::ua_css::get_ua_property(node_type, *pt) {
922 apply_css_property_to_compact(ua_prop, tier1, dims, cold, text, font_hash_map);
923 }
924 }
925}
926
927fn resolve_font_size_to_px(
931 tier2_dims: &mut [CompactNodeProps],
932 node_idx: usize,
933 parent_id: Option<NodeId>,
934) {
935 let raw_fs = tier2_dims[node_idx].font_size;
936 if raw_fs == U32_SENTINEL || raw_fs >= U32_SENTINEL_THRESHOLD {
937 return;
938 }
939 let pv = match decode_pixel_value_u32(raw_fs) {
940 Some(pv) if pv.metric != SizeMetric::Px => pv,
941 _ => return,
942 };
943
944 let parent_font_size_px = parent_id
950 .map_or(16.0, |pid| {
951 let pi = pid.index();
952 debug_assert!(
953 pi < node_idx,
954 "compact font-size resolve: non-pre-order arena — node {node_idx}'s \
955 parent {pi} font-size is not yet resolved",
956 );
957 if pi < node_idx {
958 tier2_dims
959 .get(pi)
960 .and_then(|p| decode_pixel_value_u32(p.font_size))
961 .map_or(16.0, |ppv| ppv.number.get())
962 } else {
963 16.0
964 }
965 });
966
967 let resolved_px = match pv.metric {
968 SizeMetric::Em => pv.number.get() * parent_font_size_px,
969 SizeMetric::Percent => pv.number.get() / 100.0 * parent_font_size_px,
970 SizeMetric::Rem => {
971 let rem_base = if parent_id.is_none() {
978 16.0
979 } else {
980 tier2_dims
981 .first()
982 .and_then(|r| decode_pixel_value_u32(r.font_size))
983 .map_or(16.0, |rpv| rpv.number.get())
984 };
985 rem_base * pv.number.get()
986 }
987 SizeMetric::Pt => pv.number.get() * 96.0 / 72.0,
988 _ => pv.number.get(),
989 };
990 tier2_dims[node_idx].font_size =
991 encode_pixel_value_u32(&azul_css::props::basic::pixel::PixelValue::px(resolved_px));
992}
993
994fn css_property_uses_viewport_units(prop: &CssProperty) -> bool {
1009 use azul_css::props::basic::length::SizeMetric;
1010 use azul_css::props::basic::pixel::PixelValue;
1011 const fn pv(p: &PixelValue) -> bool {
1012 matches!(p.metric, SizeMetric::Vw | SizeMetric::Vh | SizeMetric::Vmin | SizeMetric::Vmax)
1013 }
1014 fn inner<T: HasInnerPixelValue>(v: &CssPropertyValue<T>) -> bool {
1015 matches!(v, CssPropertyValue::Exact(x) if pv(&x.get_inner_pixel()))
1016 }
1017 use azul_css::props::layout::dimensions::{LayoutHeight, LayoutWidth};
1018 use azul_css::props::layout::flex::LayoutFlexBasis;
1019 match prop {
1020 CssProperty::Width(v) => matches!(v, CssPropertyValue::Exact(w) if match w {
1021 LayoutWidth::Px(p) | LayoutWidth::FitContent(p) => pv(p),
1022 LayoutWidth::Calc(_) => true,
1023 _ => false,
1024 }),
1025 CssProperty::Height(v) => matches!(v, CssPropertyValue::Exact(h) if match h {
1026 LayoutHeight::Px(p) | LayoutHeight::FitContent(p) => pv(p),
1027 LayoutHeight::Calc(_) => true,
1028 _ => false,
1029 }),
1030 CssProperty::FlexBasis(v) => matches!(v, CssPropertyValue::Exact(LayoutFlexBasis::Exact(p)) if pv(p)),
1031 CssProperty::MinWidth(v) => inner(v),
1032 CssProperty::MaxWidth(v) => inner(v),
1033 CssProperty::MinHeight(v) => inner(v),
1034 CssProperty::MaxHeight(v) => inner(v),
1035 CssProperty::FontSize(v) => inner(v),
1036 CssProperty::PaddingTop(v) => inner(v),
1037 CssProperty::PaddingRight(v) => inner(v),
1038 CssProperty::PaddingBottom(v) => inner(v),
1039 CssProperty::PaddingLeft(v) => inner(v),
1040 CssProperty::MarginTop(v) => inner(v),
1041 CssProperty::MarginRight(v) => inner(v),
1042 CssProperty::MarginBottom(v) => inner(v),
1043 CssProperty::MarginLeft(v) => inner(v),
1044 CssProperty::BorderTopWidth(v) => inner(v),
1045 CssProperty::BorderRightWidth(v) => inner(v),
1046 CssProperty::BorderBottomWidth(v) => inner(v),
1047 CssProperty::BorderLeftWidth(v) => inner(v),
1048 CssProperty::Top(v) => inner(v),
1049 CssProperty::Right(v) => inner(v),
1050 CssProperty::Bottom(v) => inner(v),
1051 CssProperty::Left(v) => inner(v),
1052 CssProperty::LetterSpacing(v) => inner(v),
1053 CssProperty::WordSpacing(v) => inner(v),
1054 CssProperty::TextIndent(v) => inner(v),
1055 CssProperty::TabSize(v) => inner(v),
1056 _ => false,
1057 }
1058}
1059
1060#[inline]
1067#[allow(clippy::match_same_arms)]
1073#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
1076#[allow(clippy::too_many_lines, clippy::cognitive_complexity)] fn apply_css_property_to_compact(
1078 prop: &CssProperty,
1079 tier1: &mut u64,
1080 dims: &mut CompactNodeProps,
1081 cold: &mut CompactNodePropsCold,
1082 text: &mut CompactTextProps,
1083 font_hash_map: &mut alloc::collections::BTreeMap<u64, azul_css::props::basic::font::StyleFontFamilyVec>,
1084) {
1085 macro_rules! set_tier1 {
1086 ($v:expr, $shift:expr, $mask:expr, $encoder:ident) => {
1087 if let Some(exact) = $v.get_property() {
1088 let encoded = u64::from($encoder(*exact));
1089 let shifted_mask = $mask << $shift;
1090 *tier1 = (*tier1 & !shifted_mask) | ((encoded & $mask) << $shift);
1091 }
1092 };
1093 }
1094
1095 match prop {
1096 CssProperty::Display(v) => set_tier1!(v, DISPLAY_SHIFT, DISPLAY_MASK, layout_display_to_u8),
1098 CssProperty::Position(v) => set_tier1!(v, POSITION_SHIFT, POSITION_MASK, layout_position_to_u8),
1099 CssProperty::Float(v) => set_tier1!(v, FLOAT_SHIFT, FLOAT_MASK, layout_float_to_u8),
1100 CssProperty::OverflowX(v) => set_tier1!(v, OVERFLOW_X_SHIFT, OVERFLOW_MASK, layout_overflow_to_u8),
1101 CssProperty::OverflowY(v) => set_tier1!(v, OVERFLOW_Y_SHIFT, OVERFLOW_MASK, layout_overflow_to_u8),
1102 CssProperty::OverflowBlock(v) => {
1112 if let Some(val) = v.get_property() {
1113 let wm_bits = ((*tier1 >> WRITING_MODE_SHIFT) & WRITING_MODE_MASK) as u8;
1114 let vertical = wm_bits == layout_writing_mode_to_u8(
1115 azul_css::props::layout::wrapping::LayoutWritingMode::VerticalRl,
1116 ) || wm_bits == layout_writing_mode_to_u8(
1117 azul_css::props::layout::wrapping::LayoutWritingMode::VerticalLr,
1118 );
1119 let shift = if vertical { OVERFLOW_X_SHIFT } else { OVERFLOW_Y_SHIFT };
1120 let enc = u64::from(layout_overflow_to_u8(*val));
1121 *tier1 = (*tier1 & !(OVERFLOW_MASK << shift)) | ((enc & OVERFLOW_MASK) << shift);
1122 }
1123 }
1124 CssProperty::OverflowInline(v) => {
1125 if let Some(val) = v.get_property() {
1126 let wm_bits = ((*tier1 >> WRITING_MODE_SHIFT) & WRITING_MODE_MASK) as u8;
1127 let vertical = wm_bits == layout_writing_mode_to_u8(
1128 azul_css::props::layout::wrapping::LayoutWritingMode::VerticalRl,
1129 ) || wm_bits == layout_writing_mode_to_u8(
1130 azul_css::props::layout::wrapping::LayoutWritingMode::VerticalLr,
1131 );
1132 let shift = if vertical { OVERFLOW_Y_SHIFT } else { OVERFLOW_X_SHIFT };
1133 let enc = u64::from(layout_overflow_to_u8(*val));
1134 *tier1 = (*tier1 & !(OVERFLOW_MASK << shift)) | ((enc & OVERFLOW_MASK) << shift);
1135 }
1136 }
1137 CssProperty::BoxSizing(v) => set_tier1!(v, BOX_SIZING_SHIFT, BOX_SIZING_MASK, layout_box_sizing_to_u8),
1138 CssProperty::FlexDirection(v) => set_tier1!(v, FLEX_DIRECTION_SHIFT, FLEX_DIR_MASK, layout_flex_direction_to_u8),
1139 CssProperty::FlexWrap(v) => set_tier1!(v, FLEX_WRAP_SHIFT, FLEX_WRAP_MASK, layout_flex_wrap_to_u8),
1140 CssProperty::JustifyContent(v) => set_tier1!(v, JUSTIFY_CONTENT_SHIFT, JUSTIFY_MASK, layout_justify_content_to_u8),
1141 CssProperty::AlignItems(v) => set_tier1!(v, ALIGN_ITEMS_SHIFT, ALIGN_MASK, layout_align_items_to_u8),
1142 CssProperty::AlignContent(v) => set_tier1!(v, ALIGN_CONTENT_SHIFT, ALIGN_MASK, layout_align_content_to_u8),
1143 CssProperty::WritingMode(v) => set_tier1!(v, WRITING_MODE_SHIFT, WRITING_MODE_MASK, layout_writing_mode_to_u8),
1144 CssProperty::Clear(v) => set_tier1!(v, CLEAR_SHIFT, CLEAR_MASK, layout_clear_to_u8),
1145 CssProperty::FontWeight(v) => set_tier1!(v, FONT_WEIGHT_SHIFT, FONT_WEIGHT_MASK, style_font_weight_to_u8),
1146 CssProperty::FontStyle(v) => set_tier1!(v, FONT_STYLE_SHIFT, FONT_STYLE_MASK, style_font_style_to_u8),
1147 CssProperty::TextAlign(v) => set_tier1!(v, TEXT_ALIGN_SHIFT, TEXT_ALIGN_MASK, style_text_align_to_u8),
1148 CssProperty::Visibility(v) => set_tier1!(v, VISIBILITY_SHIFT, VISIBILITY_MASK, style_visibility_to_u8),
1149 CssProperty::WhiteSpace(v) => set_tier1!(v, WHITE_SPACE_SHIFT, WHITE_SPACE_MASK, style_white_space_to_u8),
1150 CssProperty::Direction(v) => set_tier1!(v, DIRECTION_SHIFT, DIRECTION_MASK, style_direction_to_u8),
1151 CssProperty::VerticalAlign(v) => set_tier1!(v, VERTICAL_ALIGN_SHIFT, VERTICAL_ALIGN_MASK, style_vertical_align_to_u8),
1152 CssProperty::BorderCollapse(v) => set_tier1!(v, BORDER_COLLAPSE_SHIFT, BORDER_COLLAPSE_MASK, border_collapse_to_u8),
1153 CssProperty::AlignSelf(v) => set_tier1!(v, ALIGN_SELF_SHIFT, ALIGN_SELF_MASK, layout_align_self_to_u8),
1154 CssProperty::JustifySelf(v) => set_tier1!(v, JUSTIFY_SELF_SHIFT, JUSTIFY_SELF_MASK, layout_justify_self_to_u8),
1155 CssProperty::GridAutoFlow(v) => set_tier1!(v, GRID_AUTO_FLOW_SHIFT, GRID_AUTO_FLOW_MASK, layout_grid_auto_flow_to_u8),
1156 CssProperty::JustifyItems(v) => set_tier1!(v, JUSTIFY_ITEMS_SHIFT, JUSTIFY_ITEMS_MASK, layout_justify_items_to_u8),
1157
1158 CssProperty::Width(v) => { dims.width = encode_layout_width(v); }
1160 CssProperty::Height(v) => { dims.height = encode_layout_height(v); }
1161 CssProperty::MinWidth(v) => { dims.min_width = encode_pixel_prop(v); }
1162 CssProperty::MaxWidth(v) => { dims.max_width = encode_pixel_prop(v); }
1163 CssProperty::MinHeight(v) => { dims.min_height = encode_pixel_prop(v); }
1164 CssProperty::MaxHeight(v) => { dims.max_height = encode_pixel_prop(v); }
1165 CssProperty::FlexBasis(v) => { dims.flex_basis = encode_flex_basis(v); }
1166 CssProperty::FontSize(v) => { dims.font_size = encode_pixel_prop(v); }
1167 CssProperty::PaddingTop(v) => { dims.padding_top = encode_css_pixel_as_i16(v); }
1168 CssProperty::PaddingRight(v) => { dims.padding_right = encode_css_pixel_as_i16(v); }
1169 CssProperty::PaddingBottom(v) => { dims.padding_bottom = encode_css_pixel_as_i16(v); }
1170 CssProperty::PaddingLeft(v) => { dims.padding_left = encode_css_pixel_as_i16(v); }
1171 CssProperty::MarginTop(v) => { dims.margin_top = encode_margin_i16(v); }
1172 CssProperty::MarginRight(v) => { dims.margin_right = encode_margin_i16(v); }
1173 CssProperty::MarginBottom(v) => { dims.margin_bottom = encode_margin_i16(v); }
1174 CssProperty::MarginLeft(v) => { dims.margin_left = encode_margin_i16(v); }
1175 CssProperty::BorderTopWidth(v) => { dims.border_top_width = encode_css_pixel_as_i16(v); }
1176 CssProperty::BorderRightWidth(v) => { dims.border_right_width = encode_css_pixel_as_i16(v); }
1177 CssProperty::BorderBottomWidth(v) => { dims.border_bottom_width = encode_css_pixel_as_i16(v); }
1178 CssProperty::BorderLeftWidth(v) => { dims.border_left_width = encode_css_pixel_as_i16(v); }
1179 CssProperty::Top(v) => { dims.top = encode_css_pixel_as_i16(v); }
1180 CssProperty::Right(v) => { dims.right = encode_css_pixel_as_i16(v); }
1181 CssProperty::Bottom(v) => { dims.bottom = encode_css_pixel_as_i16(v); }
1182 CssProperty::Left(v) => { dims.left = encode_css_pixel_as_i16(v); }
1183 CssProperty::FlexGrow(v) => {
1184 if let Some(exact) = v.get_property() {
1185 dims.flex_grow = encode_flex_u16(exact.inner.get());
1186 }
1187 }
1188 CssProperty::FlexShrink(v) => {
1189 if let Some(exact) = v.get_property() {
1190 dims.flex_shrink = encode_flex_u16(exact.inner.get());
1191 }
1192 }
1193
1194 CssProperty::RowGap(v) => {
1195 if let Some(g) = v.get_property() {
1196 if g.inner.metric == SizeMetric::Px {
1197 dims.row_gap = encode_resolved_px_i16(g.inner.number.get());
1198 }
1199 }
1200 }
1201 CssProperty::ColumnGap(v) => {
1202 if let Some(g) = v.get_property() {
1203 if g.inner.metric == SizeMetric::Px {
1204 dims.column_gap = encode_resolved_px_i16(g.inner.number.get());
1205 }
1206 }
1207 }
1208 CssProperty::Gap(v) => {
1209 if let Some(g) = v.get_property() {
1210 if g.inner.metric == SizeMetric::Px {
1211 let enc = encode_resolved_px_i16(g.inner.number.get());
1212 dims.row_gap = enc;
1213 dims.column_gap = enc;
1214 }
1215 }
1216 }
1217
1218 CssProperty::GridColumn(v) => {
1220 if let Some(gp) = v.get_property() {
1221 cold.grid_col_start = encode_grid_line(&gp.grid_start);
1222 cold.grid_col_end = encode_grid_line(&gp.grid_end);
1223 }
1224 }
1225 CssProperty::GridRow(v) => {
1226 if let Some(gp) = v.get_property() {
1227 cold.grid_row_start = encode_grid_line(&gp.grid_start);
1228 cold.grid_row_end = encode_grid_line(&gp.grid_end);
1229 }
1230 }
1231
1232 CssProperty::ZIndex(v) => {
1234 if let Some(exact) = v.get_property() {
1235 match exact {
1236 LayoutZIndex::Auto => cold.z_index = I16_AUTO,
1237 LayoutZIndex::Integer(z) => {
1238 cold.z_index = if *z >= -32768 && *z < i32::from(I16_SENTINEL_THRESHOLD) {
1241 *z as i16
1242 } else {
1243 I16_SENTINEL
1244 };
1245 }
1246 }
1247 }
1248 }
1249 CssProperty::BorderTopStyle(v) => {
1250 if let Some(exact) = v.get_property() {
1251 let bs = u16::from(border_style_to_u8(exact.inner));
1252 cold.border_styles_packed = (cold.border_styles_packed & !0x000F) | bs;
1253 }
1254 }
1255 CssProperty::BorderRightStyle(v) => {
1256 if let Some(exact) = v.get_property() {
1257 let bs = u16::from(border_style_to_u8(exact.inner));
1258 cold.border_styles_packed = (cold.border_styles_packed & !0x00F0) | (bs << 4);
1259 }
1260 }
1261 CssProperty::BorderBottomStyle(v) => {
1262 if let Some(exact) = v.get_property() {
1263 let bs = u16::from(border_style_to_u8(exact.inner));
1264 cold.border_styles_packed = (cold.border_styles_packed & !0x0F00) | (bs << 8);
1265 }
1266 }
1267 CssProperty::BorderLeftStyle(v) => {
1268 if let Some(exact) = v.get_property() {
1269 let bs = u16::from(border_style_to_u8(exact.inner));
1270 cold.border_styles_packed = (cold.border_styles_packed & !0xF000) | (bs << 12);
1271 }
1272 }
1273 CssProperty::BorderTopColor(v) => {
1274 if let Some(c) = v.get_property() { cold.border_top_color = encode_color_u32(&c.inner); }
1275 }
1276 CssProperty::BorderRightColor(v) => {
1277 if let Some(c) = v.get_property() { cold.border_right_color = encode_color_u32(&c.inner); }
1278 }
1279 CssProperty::BorderBottomColor(v) => {
1280 if let Some(c) = v.get_property() { cold.border_bottom_color = encode_color_u32(&c.inner); }
1281 }
1282 CssProperty::BorderLeftColor(v) => {
1283 if let Some(c) = v.get_property() { cold.border_left_color = encode_color_u32(&c.inner); }
1284 }
1285 CssProperty::BorderSpacing(v) => {
1286 if let Some(spacing) = v.get_property() {
1287 if spacing.horizontal.metric == SizeMetric::Px {
1288 cold.border_spacing_h = encode_resolved_px_i16(spacing.horizontal.number.get());
1289 }
1290 if spacing.vertical.metric == SizeMetric::Px {
1291 cold.border_spacing_v = encode_resolved_px_i16(spacing.vertical.number.get());
1292 }
1293 }
1294 }
1295 CssProperty::TabSize(v) => { cold.tab_size = encode_css_pixel_as_i16(v); }
1296
1297 CssProperty::TextColor(v) => {
1299 if let Some(color) = v.get_property() {
1300 let c = &color.inner;
1301 text.text_color = (u32::from(c.r) << 24) | (u32::from(c.g) << 16) | (u32::from(c.b) << 8) | u32::from(c.a);
1302 }
1303 }
1304 CssProperty::FontFamily(v) => {
1305 if let Some(families) = v.get_property() {
1306 let mut hasher = DefaultHasher::new();
1307 families.hash(&mut hasher);
1308 let h = hasher.finish();
1309 let h = if h == 0 { 1 } else { h };
1310 text.font_family_hash = h;
1311 font_hash_map.insert(h, families.clone());
1312 }
1313 }
1314 CssProperty::LineHeight(v) => {
1315 if let Some(lh) = v.get_property() {
1316 let n = lh.inner.normalized();
1323 let stored = if n < 0.0 {
1324 ((n * 10.0).round() as i32).max(-32768)
1325 } else {
1326 (n * 1000.0).round() as i32
1327 };
1328 if stored >= -32768 && stored < i32::from(I16_SENTINEL_THRESHOLD) {
1329 text.line_height = stored as i16;
1330 } else {
1331 text.line_height = I16_SENTINEL;
1332 }
1333 }
1334 }
1335 CssProperty::LetterSpacing(v) => { text.letter_spacing = encode_css_pixel_as_i16(v); }
1336 CssProperty::WordSpacing(v) => { text.word_spacing = encode_css_pixel_as_i16(v); }
1337 CssProperty::TextIndent(v) => { text.text_indent = encode_css_pixel_as_i16(v); }
1338
1339 CssProperty::BorderTopLeftRadius(v) => {
1341 if let Some(exact) = v.get_property() {
1342 if exact.inner.metric == SizeMetric::Px {
1343 cold.border_top_left_radius = encode_resolved_px_i16(exact.inner.number.get());
1344 }
1345 }
1346 }
1347 CssProperty::BorderTopRightRadius(v) => {
1348 if let Some(exact) = v.get_property() {
1349 if exact.inner.metric == SizeMetric::Px {
1350 cold.border_top_right_radius = encode_resolved_px_i16(exact.inner.number.get());
1351 }
1352 }
1353 }
1354 CssProperty::BorderBottomLeftRadius(v) => {
1355 if let Some(exact) = v.get_property() {
1356 if exact.inner.metric == SizeMetric::Px {
1357 cold.border_bottom_left_radius = encode_resolved_px_i16(exact.inner.number.get());
1358 }
1359 }
1360 }
1361 CssProperty::BorderBottomRightRadius(v) => {
1362 if let Some(exact) = v.get_property() {
1363 if exact.inner.metric == SizeMetric::Px {
1364 cold.border_bottom_right_radius = encode_resolved_px_i16(exact.inner.number.get());
1365 }
1366 }
1367 }
1368
1369 CssProperty::Opacity(v) => {
1371 if let Some(exact) = v.get_property() {
1372 let o = exact.inner.normalized().clamp(0.0, 1.0);
1373 let byte = (o * 254.0).round() as u8;
1374 cold.opacity = byte;
1376 }
1377 }
1378
1379 CssProperty::Transform(v) => {
1382 if v.get_property().is_some() { cold.hot_flags |= HOT_FLAG_HAS_TRANSFORM; }
1383 }
1384 CssProperty::TransformOrigin(v) => {
1385 if v.get_property().is_some() { cold.hot_flags |= HOT_FLAG_HAS_TRANSFORM_ORIGIN; }
1386 }
1387 CssProperty::BoxShadowTop(v)
1390 | CssProperty::BoxShadowBottom(v)
1391 | CssProperty::BoxShadowLeft(v)
1392 | CssProperty::BoxShadowRight(v) => {
1393 if v.get_property().is_some() { cold.hot_flags |= HOT_FLAG_HAS_BOX_SHADOW; }
1394 }
1395 CssProperty::TextDecoration(v) => {
1396 if v.get_property().is_some() { cold.hot_flags |= HOT_FLAG_HAS_TEXT_DECORATION; }
1397 }
1398 CssProperty::ScrollbarGutter(v) => {
1399 if let Some(exact) = v.get_property() {
1400 use azul_css::props::layout::overflow::StyleScrollbarGutter;
1401 let bits: u8 = match exact {
1402 StyleScrollbarGutter::Auto => SCROLLBAR_GUTTER_AUTO,
1403 StyleScrollbarGutter::Stable => SCROLLBAR_GUTTER_STABLE,
1404 StyleScrollbarGutter::StableBothEdges => SCROLLBAR_GUTTER_BOTH_EDGES,
1405 };
1406 cold.hot_flags = (cold.hot_flags & !HOT_FLAG_SCROLLBAR_GUTTER_MASK)
1407 | ((bits << HOT_FLAG_SCROLLBAR_GUTTER_SHIFT) & HOT_FLAG_SCROLLBAR_GUTTER_MASK);
1408 }
1409 }
1410 CssProperty::BackgroundContent(v) => {
1411 if v.get_property().is_some() { cold.hot_flags |= HOT_FLAG_HAS_BACKGROUND; }
1412 }
1413 CssProperty::ClipPath(v) => {
1414 if v.get_property().is_some() { cold.hot_flags |= HOT_FLAG_HAS_CLIP_PATH; }
1415 }
1416
1417 CssProperty::ScrollbarTrack(v) => {
1421 if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_SCROLLBAR_CSS; }
1422 }
1423 CssProperty::ScrollbarThumb(v) => {
1424 if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_SCROLLBAR_CSS; }
1425 }
1426 CssProperty::ScrollbarButton(v) => {
1427 if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_SCROLLBAR_CSS; }
1428 }
1429 CssProperty::ScrollbarCorner(v) => {
1430 if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_SCROLLBAR_CSS; }
1431 }
1432 CssProperty::ScrollbarWidth(v) => {
1433 if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_SCROLLBAR_CSS; }
1434 }
1435 CssProperty::ScrollbarColor(v) => {
1436 if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_SCROLLBAR_CSS; }
1437 }
1438 CssProperty::ScrollbarVisibility(v) => {
1439 if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_SCROLLBAR_CSS; }
1440 }
1441 CssProperty::ScrollbarFadeDelay(v) => {
1442 if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_SCROLLBAR_CSS; }
1443 }
1444 CssProperty::ScrollbarFadeDuration(v) => {
1445 if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_SCROLLBAR_CSS; }
1446 }
1447
1448 CssProperty::CounterReset(v) => {
1450 if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_COUNTER; }
1451 }
1452 CssProperty::CounterIncrement(v) => {
1453 if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_COUNTER; }
1454 }
1455 CssProperty::BreakBefore(v) | CssProperty::BreakAfter(v) => {
1457 if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_BREAK; }
1458 }
1459 CssProperty::TextOrientation(v) => {
1460 if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_TEXT_ORIENTATION; }
1461 }
1462 CssProperty::TextShadow(v) => {
1463 if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_TEXT_SHADOW; }
1464 }
1465 CssProperty::BackdropFilter(v) => {
1466 if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_BACKDROP_FILTER; }
1467 }
1468 CssProperty::Filter(v) => {
1469 if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_FILTER; }
1470 }
1471 CssProperty::MixBlendMode(v) => {
1472 if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_MIX_BLEND_MODE; }
1473 }
1474
1475 _ => {}
1477 }
1478}
1479
1480const fn update_dom_declared_flags(prop: &CssProperty, flags: &mut u32) {
1485 match prop {
1489 CssProperty::ShapeInside(v) => if v.get_property().is_some() { *flags |= DOM_HAS_SHAPE_INSIDE; }
1490 CssProperty::ShapeOutside(v) => if v.get_property().is_some() { *flags |= DOM_HAS_SHAPE_OUTSIDE; }
1491 CssProperty::TextJustify(v) => if v.get_property().is_some() { *flags |= DOM_HAS_TEXT_JUSTIFY; }
1492 CssProperty::TextIndent(v) => if v.get_property().is_some() { *flags |= DOM_HAS_TEXT_INDENT; }
1493 CssProperty::ColumnCount(v) => if v.get_property().is_some() { *flags |= DOM_HAS_COLUMN_COUNT; }
1494 CssProperty::ColumnGap(v) => if v.get_property().is_some() { *flags |= DOM_HAS_COLUMN_GAP; }
1495 CssProperty::ColumnWidth(v) => if v.get_property().is_some() { *flags |= DOM_HAS_COLUMN_WIDTH; }
1496 CssProperty::InitialLetter(v) => if v.get_property().is_some() { *flags |= DOM_HAS_INITIAL_LETTER; }
1497 CssProperty::InitialLetterAlign(v) => if v.get_property().is_some() { *flags |= DOM_HAS_INITIAL_LETTER_ALIGN; }
1498 CssProperty::LineClamp(v) => if v.get_property().is_some() { *flags |= DOM_HAS_LINE_CLAMP; }
1499 CssProperty::HangingPunctuation(v) => if v.get_property().is_some() { *flags |= DOM_HAS_HANGING_PUNCTUATION; }
1500 CssProperty::TextCombineUpright(v) => if v.get_property().is_some() { *flags |= DOM_HAS_TEXT_COMBINE_UPRIGHT; }
1501 CssProperty::ExclusionMargin(v) => if v.get_property().is_some() { *flags |= DOM_HAS_EXCLUSION_MARGIN; }
1502 CssProperty::ShapeMargin(v) => if v.get_property().is_some() { *flags |= DOM_HAS_SHAPE_MARGIN; }
1503 CssProperty::HyphenationLanguage(v) => if v.get_property().is_some() { *flags |= DOM_HAS_HYPHENATION_LANGUAGE; }
1504 CssProperty::UnicodeBidi(v) => if v.get_property().is_some() { *flags |= DOM_HAS_UNICODE_BIDI; }
1505 CssProperty::TextBoxTrim(v) => if v.get_property().is_some() { *flags |= DOM_HAS_TEXT_BOX_TRIM; }
1506 CssProperty::Hyphens(v) => if v.get_property().is_some() { *flags |= DOM_HAS_HYPHENS; }
1507 CssProperty::WordBreak(v) => if v.get_property().is_some() { *flags |= DOM_HAS_WORD_BREAK; }
1508 CssProperty::OverflowWrap(v) => if v.get_property().is_some() { *flags |= DOM_HAS_OVERFLOW_WRAP; }
1509 CssProperty::LineBreak(v) => if v.get_property().is_some() { *flags |= DOM_HAS_LINE_BREAK; }
1510 CssProperty::TextAlignLast(v) => if v.get_property().is_some() { *flags |= DOM_HAS_TEXT_ALIGN_LAST; }
1511 CssProperty::LineHeight(v) => if v.get_property().is_some() { *flags |= DOM_HAS_LINE_HEIGHT; }
1512 _ => {}
1513 }
1514}
1515
1516#[allow(clippy::cast_possible_truncation)]
1524const fn encode_grid_line(line: &azul_css::props::layout::grid::GridLine) -> i16 {
1525 use azul_css::props::layout::grid::GridLine;
1526 match line {
1527 GridLine::Auto => I16_AUTO,
1528 GridLine::Line(n) => {
1529 if *n >= -32000 && *n <= 32000 { *n as i16 } else { I16_SENTINEL }
1530 }
1531 GridLine::Span(n) => {
1532 if *n >= 1 && *n <= 32000 { -(*n as i16) } else { I16_SENTINEL }
1533 }
1534 GridLine::Named(_) => I16_SENTINEL,
1535 }
1536}
1537
1538fn encode_layout_width<T: LayoutWidthLike>(val: &CssPropertyValue<T>) -> u32 {
1540 match val {
1541 CssPropertyValue::Exact(w) => w.encode_compact_u32(),
1542 CssPropertyValue::Auto => U32_AUTO,
1543 CssPropertyValue::Initial => U32_INITIAL,
1544 CssPropertyValue::Inherit => U32_INHERIT,
1545 CssPropertyValue::None => U32_NONE,
1546 _ => U32_SENTINEL,
1547 }
1548}
1549
1550fn encode_layout_height<T: LayoutWidthLike>(val: &CssPropertyValue<T>) -> u32 {
1552 encode_layout_width(val)
1553}
1554
1555trait LayoutWidthLike {
1558 fn encode_compact_u32(&self) -> u32;
1559}
1560
1561impl LayoutWidthLike for LayoutWidth {
1562 fn encode_compact_u32(&self) -> u32 {
1563 match self {
1564 Self::Auto => U32_AUTO,
1565 Self::Px(pv) => encode_pixel_value_u32(pv),
1566 Self::MinContent => U32_MIN_CONTENT,
1567 Self::MaxContent => U32_MAX_CONTENT,
1568 Self::FitContent(_) | Self::Calc(_) => U32_SENTINEL,
1570 }
1571 }
1572}
1573
1574impl LayoutWidthLike for LayoutHeight {
1575 fn encode_compact_u32(&self) -> u32 {
1576 match self {
1577 Self::Auto => U32_AUTO,
1578 Self::Px(pv) => encode_pixel_value_u32(pv),
1579 Self::MinContent => U32_MIN_CONTENT,
1580 Self::MaxContent => U32_MAX_CONTENT,
1581 Self::FitContent(_) | Self::Calc(_) => U32_SENTINEL,
1583 }
1584 }
1585}
1586
1587fn encode_pixel_prop<T: HasInnerPixelValue>(val: &CssPropertyValue<T>) -> u32 {
1589 match val {
1590 CssPropertyValue::Exact(inner) => encode_pixel_value_u32(&inner.get_inner_pixel()),
1591 CssPropertyValue::Auto => U32_AUTO,
1592 CssPropertyValue::Initial => U32_INITIAL,
1593 CssPropertyValue::Inherit => U32_INHERIT,
1594 CssPropertyValue::None => U32_NONE,
1595 _ => U32_SENTINEL,
1596 }
1597}
1598
1599trait HasInnerPixelValue {
1601 fn get_inner_pixel(&self) -> azul_css::props::basic::pixel::PixelValue;
1602}
1603
1604macro_rules! impl_has_inner_pixel {
1605 ($($ty:ty),*) => {
1606 $(
1607 impl HasInnerPixelValue for $ty {
1608 fn get_inner_pixel(&self) -> azul_css::props::basic::pixel::PixelValue {
1609 self.inner
1610 }
1611 }
1612 )*
1613 };
1614}
1615
1616impl_has_inner_pixel!(
1617 azul_css::props::layout::dimensions::LayoutMinWidth,
1618 azul_css::props::layout::dimensions::LayoutMaxWidth,
1619 azul_css::props::layout::dimensions::LayoutMinHeight,
1620 azul_css::props::layout::dimensions::LayoutMaxHeight,
1621 azul_css::props::basic::font::StyleFontSize,
1622 azul_css::props::layout::spacing::LayoutPaddingTop,
1623 azul_css::props::layout::spacing::LayoutPaddingRight,
1624 azul_css::props::layout::spacing::LayoutPaddingBottom,
1625 azul_css::props::layout::spacing::LayoutPaddingLeft,
1626 azul_css::props::layout::spacing::LayoutMarginTop,
1627 azul_css::props::layout::spacing::LayoutMarginRight,
1628 azul_css::props::layout::spacing::LayoutMarginBottom,
1629 azul_css::props::layout::spacing::LayoutMarginLeft,
1630 azul_css::props::style::border::LayoutBorderTopWidth,
1631 azul_css::props::style::border::LayoutBorderRightWidth,
1632 azul_css::props::style::border::LayoutBorderBottomWidth,
1633 azul_css::props::style::border::LayoutBorderLeftWidth,
1634 azul_css::props::layout::position::LayoutTop,
1635 azul_css::props::layout::position::LayoutRight,
1636 azul_css::props::layout::position::LayoutInsetBottom,
1637 azul_css::props::layout::position::LayoutLeft,
1638 azul_css::props::style::text::StyleLetterSpacing,
1639 azul_css::props::style::text::StyleWordSpacing,
1640 azul_css::props::style::text::StyleTextIndent,
1641 azul_css::props::style::text::StyleTabSize
1642);
1643
1644fn encode_css_pixel_as_i16<T: HasInnerPixelValue>(val: &CssPropertyValue<T>) -> i16 {
1647 let mapped = match val {
1648 CssPropertyValue::Exact(inner) => CssPropertyValue::Exact(inner.get_inner_pixel()),
1649 CssPropertyValue::Auto => CssPropertyValue::Auto,
1650 CssPropertyValue::Initial => CssPropertyValue::Initial,
1651 CssPropertyValue::Inherit => CssPropertyValue::Inherit,
1652 CssPropertyValue::None => CssPropertyValue::None,
1653 _ => return I16_SENTINEL,
1654 };
1655 azul_css::compact_cache::encode_css_pixel_as_i16(&mapped)
1656}
1657
1658fn encode_margin_i16<T: HasInnerPixelValue>(val: &CssPropertyValue<T>) -> i16 {
1660 encode_css_pixel_as_i16(val)
1661}
1662
1663fn encode_flex_basis(val: &CssPropertyValue<LayoutFlexBasis>) -> u32 {
1665 match val {
1666 CssPropertyValue::Exact(fb) => match fb {
1667 LayoutFlexBasis::Auto => U32_AUTO,
1668 LayoutFlexBasis::Exact(pv) => encode_pixel_value_u32(pv),
1669 },
1670 CssPropertyValue::Auto => U32_AUTO,
1671 CssPropertyValue::Initial => U32_INITIAL,
1672 CssPropertyValue::Inherit => U32_INHERIT,
1673 CssPropertyValue::None => U32_NONE,
1674 _ => U32_SENTINEL,
1675 }
1676}
1677
1678#[cfg(test)]
1679mod audit_tests {
1680 use super::resolve_font_size_to_px;
1681 use crate::dom::NodeId;
1682 use azul_css::compact_cache::{
1683 decode_pixel_value_u32, encode_pixel_value_u32, CompactNodeProps,
1684 };
1685 use azul_css::props::basic::pixel::PixelValue;
1686
1687 #[test]
1689 fn resolve_font_size_em_from_parent() {
1690 let mut dims = vec![CompactNodeProps::default(); 2];
1691 dims[0].font_size = encode_pixel_value_u32(&PixelValue::px(20.0));
1692 dims[1].font_size = encode_pixel_value_u32(&PixelValue::em(2.0));
1693 resolve_font_size_to_px(&mut dims, 1, Some(NodeId::new(0)));
1694 let pv = decode_pixel_value_u32(dims[1].font_size).unwrap();
1695 assert!((pv.number.get() - 40.0).abs() < 0.01, "got {}", pv.number.get());
1696 }
1697
1698 #[test]
1700 fn resolve_font_size_root_em_uses_default() {
1701 let mut dims = vec![CompactNodeProps::default()];
1702 dims[0].font_size = encode_pixel_value_u32(&PixelValue::em(2.0));
1703 resolve_font_size_to_px(&mut dims, 0, None);
1704 let pv = decode_pixel_value_u32(dims[0].font_size).unwrap();
1705 assert!((pv.number.get() - 32.0).abs() < 0.01, "got {}", pv.number.get());
1706 }
1707
1708 #[test]
1711 fn resolve_font_size_rem_reads_root() {
1712 let mut dims = vec![CompactNodeProps::default(); 2];
1713 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)));
1716 let pv = decode_pixel_value_u32(dims[1].font_size).unwrap();
1717 assert!((pv.number.get() - 30.0).abs() < 0.01, "got {}", pv.number.get());
1718 }
1719}
1720
1721#[cfg(test)]
1735#[allow(
1736 clippy::float_cmp,
1737 clippy::unreadable_literal,
1738 clippy::too_many_lines,
1739 clippy::cast_lossless
1740)]
1741mod autotest_generated {
1742 use super::*;
1743
1744 use alloc::collections::BTreeMap;
1745
1746 use crate::dom::NodeType;
1747 use crate::styled_dom::NodeHierarchyItem;
1748 use azul_css::props::basic::color::ColorU;
1749 use azul_css::props::basic::font::{StyleFontFamily, StyleFontFamilyVec};
1750 use azul_css::props::basic::length::{FloatValue, PercentageValue};
1751 use azul_css::props::basic::pixel::PixelValue;
1752 use azul_css::props::layout::dimensions::LayoutMinWidth;
1753 use azul_css::props::layout::display::LayoutDisplay;
1754 use azul_css::props::layout::flex::{LayoutFlexGrow, LayoutFlexShrink};
1755 use azul_css::props::layout::grid::{GridLine, GridPlacement, LayoutGap, NamedGridLine};
1756 use azul_css::props::layout::overflow::StyleScrollbarGutter;
1757 use azul_css::props::layout::position::LayoutPosition;
1758 use azul_css::props::layout::spacing::{LayoutMarginTop, LayoutPaddingTop};
1759 use azul_css::props::layout::table::StyleBorderCollapse;
1760 use azul_css::props::style::border::{BorderStyle, StyleBorderTopStyle};
1761 use azul_css::props::style::effects::StyleOpacity;
1762 use azul_css::props::style::text::{
1763 StyleLineHeight, StyleTextColor, StyleTextDecoration, StyleTextIndent,
1764 };
1765
1766 struct Sink {
1773 tier1: u64,
1774 dims: CompactNodeProps,
1775 cold: CompactNodePropsCold,
1776 text: CompactTextProps,
1777 fonts: BTreeMap<u64, StyleFontFamilyVec>,
1778 }
1779
1780 impl Sink {
1781 fn new() -> Self {
1782 Self {
1783 tier1: 0,
1784 dims: CompactNodeProps::default(),
1785 cold: CompactNodePropsCold::default(),
1786 text: CompactTextProps::default(),
1787 fonts: BTreeMap::new(),
1788 }
1789 }
1790
1791 fn apply(&mut self, prop: &CssProperty) {
1792 apply_css_property_to_compact(
1793 prop,
1794 &mut self.tier1,
1795 &mut self.dims,
1796 &mut self.cold,
1797 &mut self.text,
1798 &mut self.fonts,
1799 );
1800 }
1801
1802 fn ua(&mut self, node_type: &NodeType) {
1803 apply_ua_css_to_compact(
1804 node_type,
1805 &mut self.tier1,
1806 &mut self.dims,
1807 &mut self.cold,
1808 &mut self.text,
1809 &mut self.fonts,
1810 );
1811 }
1812
1813 fn snapshot(&self) -> (u64, CompactNodeProps, CompactNodePropsCold, CompactTextProps) {
1814 (self.tier1, self.dims, self.cold, self.text)
1815 }
1816 }
1817
1818 fn div_nodes(n: usize) -> Vec<NodeData> {
1819 (0..n).map(|_| NodeData::create_node(NodeType::Div)).collect()
1820 }
1821
1822 fn linear_hierarchy(n: usize) -> Vec<NodeHierarchyItem> {
1825 (0..n)
1826 .map(|i| NodeHierarchyItem {
1827 parent: i, previous_sibling: 0,
1829 next_sibling: 0,
1830 last_child: if i + 1 < n { i + 2 } else { 0 },
1831 })
1832 .collect()
1833 }
1834
1835 fn padding(px: f32) -> CssPropertyValue<LayoutPaddingTop> {
1836 CssPropertyValue::Exact(LayoutPaddingTop { inner: PixelValue::px(px) })
1837 }
1838
1839 #[test]
1844 fn grid_line_auto_and_named_map_to_their_sentinels() {
1845 assert_eq!(encode_grid_line(&GridLine::Auto), I16_AUTO);
1846 let named = GridLine::Named(NamedGridLine {
1847 grid_line_name: "sidebar".into(),
1848 span_count: 0,
1849 });
1850 assert_eq!(encode_grid_line(&named), I16_SENTINEL);
1851 }
1852
1853 #[test]
1854 fn grid_line_number_boundaries_saturate_instead_of_truncating() {
1855 assert_eq!(encode_grid_line(&GridLine::Line(0)), 0);
1856 assert_eq!(encode_grid_line(&GridLine::Line(1)), 1);
1857 assert_eq!(encode_grid_line(&GridLine::Line(-1)), -1);
1858 assert_eq!(encode_grid_line(&GridLine::Line(32_000)), 32_000);
1859 assert_eq!(encode_grid_line(&GridLine::Line(-32_000)), -32_000);
1860 assert_eq!(encode_grid_line(&GridLine::Line(32_001)), I16_SENTINEL);
1862 assert_eq!(encode_grid_line(&GridLine::Line(-32_001)), I16_SENTINEL);
1863 assert_eq!(encode_grid_line(&GridLine::Line(i32::MAX)), I16_SENTINEL);
1864 assert_eq!(encode_grid_line(&GridLine::Line(i32::MIN)), I16_SENTINEL);
1865 }
1866
1867 #[test]
1868 fn grid_line_span_boundaries_and_nonsense_spans() {
1869 assert_eq!(encode_grid_line(&GridLine::Span(1)), -1);
1870 assert_eq!(encode_grid_line(&GridLine::Span(32_000)), -32_000);
1871 assert_eq!(encode_grid_line(&GridLine::Span(0)), I16_SENTINEL);
1874 assert_eq!(encode_grid_line(&GridLine::Span(-1)), I16_SENTINEL);
1875 assert_eq!(encode_grid_line(&GridLine::Span(32_001)), I16_SENTINEL);
1876 assert_eq!(encode_grid_line(&GridLine::Span(i32::MAX)), I16_SENTINEL);
1877 assert_eq!(encode_grid_line(&GridLine::Span(i32::MIN)), I16_SENTINEL);
1878 }
1879
1880 #[test]
1881 fn grid_line_in_range_values_never_alias_the_sentinel_band() {
1882 for n in [-32_000i32, -1_000, -1, 0, 1, 1_000, 32_000] {
1885 let e = encode_grid_line(&GridLine::Line(n));
1886 assert!(
1887 e < I16_SENTINEL_THRESHOLD,
1888 "Line({n}) encoded into the sentinel band as {e}"
1889 );
1890 }
1891 for n in [1i32, 2, 1_000, 32_000] {
1892 let e = encode_grid_line(&GridLine::Span(n));
1893 assert!(e < 0, "Span({n}) must encode as a negative value, got {e}");
1894 assert!(
1895 e < I16_SENTINEL_THRESHOLD,
1896 "Span({n}) encoded into the sentinel band as {e}"
1897 );
1898 }
1899 }
1900
1901 #[test]
1906 fn layout_width_keywords_map_to_distinct_sentinels() {
1907 let auto: CssPropertyValue<LayoutWidth> = CssPropertyValue::Auto;
1908 let none: CssPropertyValue<LayoutWidth> = CssPropertyValue::None;
1909 let initial: CssPropertyValue<LayoutWidth> = CssPropertyValue::Initial;
1910 let inherit: CssPropertyValue<LayoutWidth> = CssPropertyValue::Inherit;
1911 assert_eq!(encode_layout_width(&auto), U32_AUTO);
1912 assert_eq!(encode_layout_width(&none), U32_NONE);
1913 assert_eq!(encode_layout_width(&initial), U32_INITIAL);
1914 assert_eq!(encode_layout_width(&inherit), U32_INHERIT);
1915 }
1916
1917 #[test]
1918 fn layout_width_revert_and_unset_fall_back_to_the_overflow_sentinel() {
1919 let revert: CssPropertyValue<LayoutWidth> = CssPropertyValue::Revert;
1922 let unset: CssPropertyValue<LayoutWidth> = CssPropertyValue::Unset;
1923 assert_eq!(encode_layout_width(&revert), U32_SENTINEL);
1924 assert_eq!(encode_layout_width(&unset), U32_SENTINEL);
1925 assert_eq!(encode_layout_height(&revert), U32_SENTINEL);
1926 assert_eq!(encode_layout_height(&unset), U32_SENTINEL);
1927 }
1928
1929 #[test]
1930 fn layout_width_exact_keyword_variants() {
1931 assert_eq!(
1932 encode_layout_width(&CssPropertyValue::Exact(LayoutWidth::Auto)),
1933 U32_AUTO
1934 );
1935 assert_eq!(
1936 encode_layout_width(&CssPropertyValue::Exact(LayoutWidth::MinContent)),
1937 U32_MIN_CONTENT
1938 );
1939 assert_eq!(
1940 encode_layout_width(&CssPropertyValue::Exact(LayoutWidth::MaxContent)),
1941 U32_MAX_CONTENT
1942 );
1943 assert_eq!(
1945 encode_layout_width(&CssPropertyValue::Exact(LayoutWidth::FitContent(
1946 PixelValue::px(10.0)
1947 ))),
1948 U32_SENTINEL
1949 );
1950 }
1951
1952 #[test]
1953 fn layout_width_px_round_trips() {
1954 for px in [0.0f32, 0.5, 1.0, 100.0, 1234.567, -50.0] {
1955 let enc =
1956 encode_layout_width(&CssPropertyValue::Exact(LayoutWidth::Px(PixelValue::px(px))));
1957 let dec = decode_pixel_value_u32(enc)
1958 .expect("an in-range px value must not encode to a sentinel");
1959 assert_eq!(dec.metric, SizeMetric::Px);
1960 assert!(
1961 (dec.number.get() - px).abs() < 0.002,
1962 "round-trip of {px}px produced {}px",
1963 dec.number.get()
1964 );
1965 }
1966 }
1967
1968 #[test]
1969 fn layout_width_extreme_values_saturate_to_the_overflow_sentinel() {
1970 for px in [
1973 1.0e9f32,
1974 -1.0e9,
1975 f32::MAX,
1976 f32::MIN,
1977 f32::INFINITY,
1978 f32::NEG_INFINITY,
1979 ] {
1980 let enc =
1981 encode_layout_width(&CssPropertyValue::Exact(LayoutWidth::Px(PixelValue::px(px))));
1982 assert_eq!(
1983 enc, U32_SENTINEL,
1984 "width {px}px should overflow to U32_SENTINEL, got {enc:#x}"
1985 );
1986 }
1987 }
1988
1989 #[test]
1990 fn layout_width_nan_degrades_to_zero_without_panicking() {
1991 let enc = encode_layout_width(&CssPropertyValue::Exact(LayoutWidth::Px(PixelValue::px(
1994 f32::NAN,
1995 ))));
1996 let dec = decode_pixel_value_u32(enc).expect("NaN must degrade to a value, not a sentinel");
1997 assert!(dec.number.get().is_finite());
1998 assert_eq!(dec.number.get(), 0.0);
1999 }
2000
2001 #[test]
2002 fn layout_height_never_diverges_from_layout_width() {
2003 let vals = [
2004 CssPropertyValue::Exact(LayoutWidth::Auto),
2005 CssPropertyValue::Exact(LayoutWidth::MinContent),
2006 CssPropertyValue::Exact(LayoutWidth::MaxContent),
2007 CssPropertyValue::Exact(LayoutWidth::Px(PixelValue::px(42.0))),
2008 CssPropertyValue::Exact(LayoutWidth::Px(PixelValue::px(1.0e9))),
2009 CssPropertyValue::Unset,
2010 ];
2011 for v in &vals {
2012 assert_eq!(encode_layout_width(v), encode_layout_height(v));
2013 }
2014 }
2015
2016 #[test]
2021 fn pixel_prop_keywords_map_to_distinct_sentinels() {
2022 let auto: CssPropertyValue<LayoutMinWidth> = CssPropertyValue::Auto;
2023 let none: CssPropertyValue<LayoutMinWidth> = CssPropertyValue::None;
2024 let initial: CssPropertyValue<LayoutMinWidth> = CssPropertyValue::Initial;
2025 let inherit: CssPropertyValue<LayoutMinWidth> = CssPropertyValue::Inherit;
2026 let revert: CssPropertyValue<LayoutMinWidth> = CssPropertyValue::Revert;
2027 let unset: CssPropertyValue<LayoutMinWidth> = CssPropertyValue::Unset;
2028 assert_eq!(encode_pixel_prop(&auto), U32_AUTO);
2029 assert_eq!(encode_pixel_prop(&none), U32_NONE);
2030 assert_eq!(encode_pixel_prop(&initial), U32_INITIAL);
2031 assert_eq!(encode_pixel_prop(&inherit), U32_INHERIT);
2032 assert_eq!(encode_pixel_prop(&revert), U32_SENTINEL);
2033 assert_eq!(encode_pixel_prop(&unset), U32_SENTINEL);
2034 }
2035
2036 #[test]
2037 fn pixel_prop_round_trips_value_and_metric() {
2038 for pv in [
2039 PixelValue::px(50.0),
2040 PixelValue::em(1.5),
2041 PixelValue::percent(80.0),
2042 PixelValue::pt(12.0),
2043 PixelValue::rem(2.0),
2044 ] {
2045 let enc = encode_pixel_prop(&CssPropertyValue::Exact(LayoutMinWidth { inner: pv }));
2046 let dec = decode_pixel_value_u32(enc).expect("must round-trip");
2047 assert_eq!(dec.metric, pv.metric, "metric lost in the round-trip");
2048 assert!(
2049 (dec.number.get() - pv.number.get()).abs() < 0.002,
2050 "value lost in the round-trip: {} -> {}",
2051 pv.number.get(),
2052 dec.number.get()
2053 );
2054 }
2055 }
2056
2057 #[test]
2058 fn pixel_prop_overflow_saturates() {
2059 let enc = encode_pixel_prop(&CssPropertyValue::Exact(LayoutMinWidth {
2060 inner: PixelValue::px(1.0e9),
2061 }));
2062 assert_eq!(enc, U32_SENTINEL);
2063 }
2064
2065 #[test]
2066 fn pixel_prop_exact_value_never_aliases_a_semantic_sentinel() {
2067 for metric in [SizeMetric::Vh, SizeMetric::Vmin, SizeMetric::Vmax] {
2080 let pv = PixelValue::from_metric(metric, -0.001);
2081 let enc = encode_pixel_prop(&CssPropertyValue::Exact(LayoutMinWidth { inner: pv }));
2082 assert!(
2083 enc == U32_SENTINEL || enc < U32_SENTINEL_THRESHOLD,
2084 "an Exact viewport length encoded to {enc:#x}, which aliases a semantic sentinel",
2085 );
2086 }
2087 }
2088
2089 #[test]
2094 fn css_pixel_i16_scales_by_ten() {
2095 assert_eq!(encode_css_pixel_as_i16(&padding(0.0)), 0);
2096 assert_eq!(encode_css_pixel_as_i16(&padding(10.5)), 105);
2097 assert_eq!(encode_css_pixel_as_i16(&padding(-10.5)), -105);
2098 }
2099
2100 #[test]
2101 fn css_pixel_i16_boundaries() {
2102 assert_eq!(encode_css_pixel_as_i16(&padding(3276.3)), 32_763);
2104 assert_eq!(encode_css_pixel_as_i16(&padding(3276.4)), I16_SENTINEL);
2106 assert_eq!(encode_css_pixel_as_i16(&padding(-3276.8)), -32_768);
2108 assert_eq!(encode_css_pixel_as_i16(&padding(-3276.9)), I16_SENTINEL);
2109 }
2110
2111 #[test]
2112 fn css_pixel_i16_non_px_units_need_the_slow_path() {
2113 let em = CssPropertyValue::Exact(LayoutPaddingTop { inner: PixelValue::em(2.0) });
2114 let pct = CssPropertyValue::Exact(LayoutPaddingTop {
2115 inner: PixelValue::percent(50.0),
2116 });
2117 assert_eq!(encode_css_pixel_as_i16(&em), I16_SENTINEL);
2118 assert_eq!(encode_css_pixel_as_i16(&pct), I16_SENTINEL);
2119 }
2120
2121 #[test]
2122 fn css_pixel_i16_keywords_are_distinguishable() {
2123 let auto: CssPropertyValue<LayoutPaddingTop> = CssPropertyValue::Auto;
2124 let initial: CssPropertyValue<LayoutPaddingTop> = CssPropertyValue::Initial;
2125 let inherit: CssPropertyValue<LayoutPaddingTop> = CssPropertyValue::Inherit;
2126 let none: CssPropertyValue<LayoutPaddingTop> = CssPropertyValue::None;
2127 let revert: CssPropertyValue<LayoutPaddingTop> = CssPropertyValue::Revert;
2128 let unset: CssPropertyValue<LayoutPaddingTop> = CssPropertyValue::Unset;
2129 assert_eq!(encode_css_pixel_as_i16(&auto), I16_AUTO);
2130 assert_eq!(encode_css_pixel_as_i16(&initial), I16_INITIAL);
2131 assert_eq!(encode_css_pixel_as_i16(&inherit), I16_INHERIT);
2132 assert_eq!(encode_css_pixel_as_i16(&none), I16_SENTINEL);
2134 assert_eq!(encode_css_pixel_as_i16(&revert), I16_SENTINEL);
2135 assert_eq!(encode_css_pixel_as_i16(&unset), I16_SENTINEL);
2136 }
2137
2138 #[test]
2139 fn css_pixel_i16_nan_and_infinity_are_safe() {
2140 assert_eq!(encode_css_pixel_as_i16(&padding(f32::NAN)), 0);
2141 assert_eq!(encode_css_pixel_as_i16(&padding(f32::INFINITY)), I16_SENTINEL);
2142 assert_eq!(
2143 encode_css_pixel_as_i16(&padding(f32::NEG_INFINITY)),
2144 I16_SENTINEL
2145 );
2146 assert_eq!(encode_css_pixel_as_i16(&padding(f32::MAX)), I16_SENTINEL);
2147 assert_eq!(encode_css_pixel_as_i16(&padding(f32::MIN)), I16_SENTINEL);
2148 }
2149
2150 #[test]
2151 fn css_pixel_i16_exact_value_never_aliases_a_keyword_sentinel() {
2152 for px in [
2155 -3276.8f32, -100.0, -0.1, 0.0, 0.1, 100.0, 3276.3, 1.0e9, -1.0e9,
2156 ] {
2157 let e = encode_css_pixel_as_i16(&padding(px));
2158 assert!(
2159 e != I16_AUTO && e != I16_INHERIT && e != I16_INITIAL,
2160 "{px}px aliased a keyword sentinel ({e})"
2161 );
2162 }
2163 }
2164
2165 #[test]
2166 fn margin_i16_keeps_auto_and_otherwise_matches_the_pixel_encoder() {
2167 let auto: CssPropertyValue<LayoutMarginTop> = CssPropertyValue::Auto;
2168 assert_eq!(encode_margin_i16(&auto), I16_AUTO);
2169 for px in [-50.0f32, 0.0, 12.5, 3276.3, 5.0e9, f32::NAN] {
2170 let m = CssPropertyValue::Exact(LayoutMarginTop { inner: PixelValue::px(px) });
2171 assert_eq!(encode_margin_i16(&m), encode_css_pixel_as_i16(&padding(px)));
2172 }
2173 }
2174
2175 #[test]
2180 fn flex_basis_all_variants() {
2181 assert_eq!(
2182 encode_flex_basis(&CssPropertyValue::Exact(LayoutFlexBasis::Auto)),
2183 U32_AUTO
2184 );
2185 let enc = encode_flex_basis(&CssPropertyValue::Exact(LayoutFlexBasis::Exact(
2186 PixelValue::px(120.0),
2187 )));
2188 let dec = decode_pixel_value_u32(enc).expect("px flex-basis must round-trip");
2189 assert!((dec.number.get() - 120.0).abs() < 0.002);
2190
2191 assert_eq!(encode_flex_basis(&CssPropertyValue::Auto), U32_AUTO);
2192 assert_eq!(encode_flex_basis(&CssPropertyValue::None), U32_NONE);
2193 assert_eq!(encode_flex_basis(&CssPropertyValue::Initial), U32_INITIAL);
2194 assert_eq!(encode_flex_basis(&CssPropertyValue::Inherit), U32_INHERIT);
2195 assert_eq!(encode_flex_basis(&CssPropertyValue::Revert), U32_SENTINEL);
2196 assert_eq!(encode_flex_basis(&CssPropertyValue::Unset), U32_SENTINEL);
2197 }
2198
2199 #[test]
2200 fn flex_basis_overflow_saturates() {
2201 assert_eq!(
2202 encode_flex_basis(&CssPropertyValue::Exact(LayoutFlexBasis::Exact(
2203 PixelValue::px(1.0e9)
2204 ))),
2205 U32_SENTINEL
2206 );
2207 assert_eq!(
2208 encode_flex_basis(&CssPropertyValue::Exact(LayoutFlexBasis::Exact(
2209 PixelValue::px(f32::INFINITY)
2210 ))),
2211 U32_SENTINEL
2212 );
2213 }
2214
2215 fn text_indent_prop() -> CssProperty {
2220 CssProperty::TextIndent(CssPropertyValue::Exact(StyleTextIndent::default()))
2221 }
2222
2223 fn line_height_prop(pct: f32) -> CssProperty {
2224 CssProperty::LineHeight(CssPropertyValue::Exact(StyleLineHeight {
2225 inner: PercentageValue::new(pct),
2226 }))
2227 }
2228
2229 #[test]
2230 fn dom_flags_set_the_right_bit_from_zero() {
2231 let mut flags = 0u32;
2232 update_dom_declared_flags(&text_indent_prop(), &mut flags);
2233 assert_eq!(flags, DOM_HAS_TEXT_INDENT);
2234
2235 let mut flags2 = 0u32;
2236 update_dom_declared_flags(&line_height_prop(150.0), &mut flags2);
2237 assert_eq!(flags2, DOM_HAS_LINE_HEIGHT);
2238 }
2239
2240 #[test]
2241 fn dom_flags_only_ever_or_never_clear() {
2242 let mut flags = u32::MAX;
2244 update_dom_declared_flags(&text_indent_prop(), &mut flags);
2245 update_dom_declared_flags(&line_height_prop(150.0), &mut flags);
2246 update_dom_declared_flags(
2247 &CssProperty::Width(CssPropertyValue::Exact(LayoutWidth::px(10.0))),
2248 &mut flags,
2249 );
2250 assert_eq!(flags, u32::MAX);
2251 }
2252
2253 #[test]
2254 fn dom_flags_accumulate_and_are_idempotent() {
2255 let mut flags = 0u32;
2256 update_dom_declared_flags(&text_indent_prop(), &mut flags);
2257 update_dom_declared_flags(&line_height_prop(150.0), &mut flags);
2258 let after_two = flags;
2259 assert_eq!(after_two, DOM_HAS_TEXT_INDENT | DOM_HAS_LINE_HEIGHT);
2260 update_dom_declared_flags(&text_indent_prop(), &mut flags);
2262 update_dom_declared_flags(&line_height_prop(150.0), &mut flags);
2263 assert_eq!(flags, after_two);
2264 }
2265
2266 #[test]
2267 fn dom_flags_are_not_set_for_a_valueless_property() {
2268 let mut flags = 0u32;
2271 update_dom_declared_flags(&CssProperty::LineHeight(CssPropertyValue::Initial), &mut flags);
2272 update_dom_declared_flags(&CssProperty::TextIndent(CssPropertyValue::Auto), &mut flags);
2273 update_dom_declared_flags(&CssProperty::TextIndent(CssPropertyValue::Unset), &mut flags);
2274 assert_eq!(flags, 0);
2275 }
2276
2277 #[test]
2278 fn dom_flags_ignore_unrelated_properties() {
2279 let mut flags = 0u32;
2280 update_dom_declared_flags(
2281 &CssProperty::Width(CssPropertyValue::Exact(LayoutWidth::px(10.0))),
2282 &mut flags,
2283 );
2284 update_dom_declared_flags(
2285 &CssProperty::ZIndex(CssPropertyValue::Exact(LayoutZIndex::Integer(3))),
2286 &mut flags,
2287 );
2288 assert_eq!(flags, 0);
2289 }
2290
2291 #[test]
2296 fn apply_tier1_fields_do_not_bleed_into_each_other() {
2297 let mut s = Sink::new();
2298 s.apply(&CssProperty::Display(CssPropertyValue::Exact(
2299 LayoutDisplay::InlineBlock,
2300 )));
2301 s.apply(&CssProperty::Position(CssPropertyValue::Exact(
2302 LayoutPosition::Absolute,
2303 )));
2304 s.apply(&CssProperty::BorderCollapse(CssPropertyValue::Exact(
2306 StyleBorderCollapse::Collapse,
2307 )));
2308
2309 assert_eq!(
2310 (s.tier1 >> DISPLAY_SHIFT) & DISPLAY_MASK,
2311 u64::from(layout_display_to_u8(LayoutDisplay::InlineBlock))
2312 );
2313 assert_eq!(
2314 (s.tier1 >> POSITION_SHIFT) & POSITION_MASK,
2315 u64::from(layout_position_to_u8(LayoutPosition::Absolute))
2316 );
2317 assert_eq!(
2318 (s.tier1 >> BORDER_COLLAPSE_SHIFT) & BORDER_COLLAPSE_MASK,
2319 u64::from(border_collapse_to_u8(StyleBorderCollapse::Collapse))
2320 );
2321
2322 let known = (DISPLAY_MASK << DISPLAY_SHIFT)
2323 | (POSITION_MASK << POSITION_SHIFT)
2324 | (BORDER_COLLAPSE_MASK << BORDER_COLLAPSE_SHIFT);
2325 assert_eq!(
2326 s.tier1 & !known,
2327 0,
2328 "tier1 = {:#x} has bits set outside the three fields that were written",
2329 s.tier1
2330 );
2331 }
2332
2333 #[test]
2334 fn apply_tier1_overwrite_clears_only_its_own_field() {
2335 let mut s = Sink::new();
2338 s.tier1 = u64::MAX;
2339 s.apply(&CssProperty::Display(CssPropertyValue::Exact(
2340 LayoutDisplay::Block,
2341 )));
2342 assert_eq!(
2343 (s.tier1 >> DISPLAY_SHIFT) & DISPLAY_MASK,
2344 u64::from(layout_display_to_u8(LayoutDisplay::Block))
2345 );
2346 let others = !(DISPLAY_MASK << DISPLAY_SHIFT);
2347 assert_eq!(
2348 s.tier1 & others,
2349 u64::MAX & others,
2350 "neighbouring tier-1 fields were clobbered"
2351 );
2352 }
2353
2354 #[test]
2355 fn apply_tier1_ignores_a_valueless_property() {
2356 let mut s = Sink::new();
2357 s.apply(&CssProperty::Display(CssPropertyValue::Inherit));
2358 assert_eq!(s.tier1, 0, "`display: inherit` has no Exact payload to encode");
2359 }
2360
2361 #[test]
2366 fn apply_width_round_trips_and_touches_nothing_else() {
2367 let mut s = Sink::new();
2368 let before_cold = s.cold;
2369 let before_text = s.text;
2370 s.apply(&CssProperty::Width(CssPropertyValue::Exact(LayoutWidth::Px(
2371 PixelValue::px(320.0),
2372 ))));
2373 let dec = decode_pixel_value_u32(s.dims.width).expect("width must round-trip");
2374 assert!((dec.number.get() - 320.0).abs() < 0.002);
2375 assert_eq!(s.tier1, 0, "a tier-2 property must not touch the tier-1 bitfield");
2376 assert_eq!(s.cold, before_cold, "a tier-2 property must not touch tier-2 cold");
2377 assert_eq!(s.text, before_text, "a tier-2 property must not touch tier-2b text");
2378 assert!(s.fonts.is_empty());
2379 }
2380
2381 #[test]
2382 fn apply_flex_grow_saturates_and_rejects_negatives() {
2383 let mut s = Sink::new();
2384 s.apply(&CssProperty::FlexGrow(CssPropertyValue::Exact(LayoutFlexGrow {
2385 inner: FloatValue::new(2.5),
2386 })));
2387 assert_eq!(s.dims.flex_grow, 250);
2388
2389 let mut neg = Sink::new();
2391 neg.apply(&CssProperty::FlexGrow(CssPropertyValue::Exact(LayoutFlexGrow {
2392 inner: FloatValue::new(-1.0),
2393 })));
2394 assert_eq!(neg.dims.flex_grow, U16_SENTINEL);
2395
2396 let mut big = Sink::new();
2398 big.apply(&CssProperty::FlexGrow(CssPropertyValue::Exact(LayoutFlexGrow {
2399 inner: FloatValue::new(1.0e9),
2400 })));
2401 assert_eq!(big.dims.flex_grow, U16_SENTINEL);
2402
2403 let mut nan = Sink::new();
2405 nan.apply(&CssProperty::FlexShrink(CssPropertyValue::Exact(
2406 LayoutFlexShrink { inner: FloatValue::new(f32::NAN) },
2407 )));
2408 assert_eq!(nan.dims.flex_shrink, 0);
2409 }
2410
2411 #[test]
2412 fn apply_gap_px_sets_both_axes_and_ignores_unresolvable_units() {
2413 let mut s = Sink::new();
2414 s.apply(&CssProperty::Gap(CssPropertyValue::Exact(LayoutGap {
2415 inner: PixelValue::px(8.0),
2416 })));
2417 assert_eq!(s.dims.row_gap, 80);
2418 assert_eq!(s.dims.column_gap, 80);
2419
2420 let mut em = Sink::new();
2423 em.apply(&CssProperty::Gap(CssPropertyValue::Exact(LayoutGap {
2424 inner: PixelValue::em(2.0),
2425 })));
2426 assert_eq!(em.dims.row_gap, 0);
2427 assert_eq!(em.dims.column_gap, 0);
2428 }
2429
2430 #[test]
2435 fn apply_z_index_auto_and_in_range_values() {
2436 let mut s = Sink::new();
2437 s.apply(&CssProperty::ZIndex(CssPropertyValue::Exact(LayoutZIndex::Auto)));
2438 assert_eq!(s.cold.z_index, I16_AUTO);
2439 s.apply(&CssProperty::ZIndex(CssPropertyValue::Exact(
2440 LayoutZIndex::Integer(100),
2441 )));
2442 assert_eq!(s.cold.z_index, 100);
2443 s.apply(&CssProperty::ZIndex(CssPropertyValue::Exact(
2445 LayoutZIndex::Integer(32_763),
2446 )));
2447 assert_eq!(s.cold.z_index, 32_763);
2448 }
2449
2450 #[test]
2451 fn apply_z_index_large_positive_saturates() {
2452 for z in [32_764i32, 100_000, i32::MAX] {
2453 let mut s = Sink::new();
2454 s.apply(&CssProperty::ZIndex(CssPropertyValue::Exact(
2455 LayoutZIndex::Integer(z),
2456 )));
2457 assert_eq!(s.cold.z_index, I16_SENTINEL, "z-index {z} should saturate");
2458 }
2459 }
2460
2461 #[test]
2462 fn apply_z_index_large_negative_must_not_wrap_positive() {
2463 for z in [-32_769i32, -40_000, -99_999, i32::MIN] {
2471 let mut s = Sink::new();
2472 s.apply(&CssProperty::ZIndex(CssPropertyValue::Exact(
2473 LayoutZIndex::Integer(z),
2474 )));
2475 assert!(
2476 s.cold.z_index < 0 || s.cold.z_index == I16_SENTINEL,
2477 "z-index {z} encoded to {}: a negative z-index must stay negative (or \
2478 saturate to the sentinel), it must never wrap to a positive value",
2479 s.cold.z_index,
2480 );
2481 }
2482 }
2483
2484 #[test]
2485 fn apply_border_styles_pack_into_independent_nibbles() {
2486 let mut s = Sink::new();
2487 s.apply(&CssProperty::BorderTopStyle(CssPropertyValue::Exact(
2488 StyleBorderTopStyle { inner: BorderStyle::Solid },
2489 )));
2490 assert_eq!(
2491 s.cold.border_styles_packed & 0x000F,
2492 u16::from(border_style_to_u8(BorderStyle::Solid))
2493 );
2494 assert_eq!(
2495 s.cold.border_styles_packed & 0xFFF0,
2496 0,
2497 "the top-style nibble leaked into the other three sides"
2498 );
2499
2500 s.apply(&CssProperty::BorderTopStyle(CssPropertyValue::Exact(
2503 StyleBorderTopStyle { inner: BorderStyle::Double },
2504 )));
2505 assert_eq!(
2506 s.cold.border_styles_packed & 0x000F,
2507 u16::from(border_style_to_u8(BorderStyle::Double))
2508 );
2509 }
2510
2511 #[test]
2512 fn apply_opacity_clamps_into_the_0_254_range() {
2513 for (pct, expected) in [
2514 (-1.0e9f32, 0u8),
2515 (-100.0, 0),
2516 (0.0, 0),
2517 (50.0, 127),
2518 (100.0, 254),
2519 (500.0, 254),
2520 (1.0e9, 254),
2521 ] {
2522 let mut s = Sink::new();
2523 s.apply(&CssProperty::Opacity(CssPropertyValue::Exact(StyleOpacity {
2524 inner: PercentageValue::new(pct),
2525 })));
2526 assert_eq!(s.cold.opacity, expected, "opacity: {pct}%");
2527 assert_ne!(
2528 s.cold.opacity, OPACITY_SENTINEL,
2529 "an explicitly set opacity must never encode as the 'unset' sentinel"
2530 );
2531 }
2532 }
2533
2534 #[test]
2535 fn apply_grid_column_encodes_both_lines() {
2536 let mut s = Sink::new();
2537 s.apply(&CssProperty::GridColumn(CssPropertyValue::Exact(GridPlacement {
2538 grid_start: GridLine::Line(2),
2539 grid_end: GridLine::Span(3),
2540 })));
2541 assert_eq!(s.cold.grid_col_start, 2);
2542 assert_eq!(s.cold.grid_col_end, -3);
2543 assert_eq!(s.cold.grid_row_start, I16_AUTO);
2545 assert_eq!(s.cold.grid_row_end, I16_AUTO);
2546 }
2547
2548 #[test]
2549 fn apply_hot_flags_or_in_without_clobbering_each_other() {
2550 let mut s = Sink::new();
2551 s.apply(&CssProperty::TextDecoration(CssPropertyValue::Exact(
2552 StyleTextDecoration::Underline,
2553 )));
2554 assert_eq!(
2555 s.cold.hot_flags & HOT_FLAG_HAS_TEXT_DECORATION,
2556 HOT_FLAG_HAS_TEXT_DECORATION
2557 );
2558
2559 s.apply(&CssProperty::ScrollbarGutter(CssPropertyValue::Exact(
2562 StyleScrollbarGutter::Stable,
2563 )));
2564 assert_eq!(
2565 (s.cold.hot_flags & HOT_FLAG_SCROLLBAR_GUTTER_MASK) >> HOT_FLAG_SCROLLBAR_GUTTER_SHIFT,
2566 SCROLLBAR_GUTTER_STABLE
2567 );
2568 assert_eq!(
2569 s.cold.hot_flags & HOT_FLAG_HAS_TEXT_DECORATION,
2570 HOT_FLAG_HAS_TEXT_DECORATION,
2571 "scrollbar-gutter cleared the has-text-decoration bit"
2572 );
2573
2574 s.apply(&CssProperty::ScrollbarGutter(CssPropertyValue::Exact(
2576 StyleScrollbarGutter::Auto,
2577 )));
2578 assert_eq!(
2579 (s.cold.hot_flags & HOT_FLAG_SCROLLBAR_GUTTER_MASK) >> HOT_FLAG_SCROLLBAR_GUTTER_SHIFT,
2580 SCROLLBAR_GUTTER_AUTO
2581 );
2582 assert_eq!(
2583 s.cold.hot_flags & HOT_FLAG_HAS_TEXT_DECORATION,
2584 HOT_FLAG_HAS_TEXT_DECORATION
2585 );
2586 }
2587
2588 #[test]
2589 fn apply_valueless_property_does_not_set_a_has_flag() {
2590 let mut s = Sink::new();
2594 s.apply(&CssProperty::TextDecoration(CssPropertyValue::Initial));
2595 s.apply(&CssProperty::ScrollbarGutter(CssPropertyValue::Unset));
2596 assert_eq!(s.cold.hot_flags, 0);
2597 }
2598
2599 #[test]
2604 fn apply_text_color_packs_rgba_big_endian() {
2605 let mut s = Sink::new();
2606 s.apply(&CssProperty::TextColor(CssPropertyValue::Exact(StyleTextColor {
2607 inner: ColorU { r: 0x12, g: 0x34, b: 0x56, a: 0x78 },
2608 })));
2609 assert_eq!(s.text.text_color, 0x1234_5678);
2610
2611 let mut transparent = Sink::new();
2613 transparent.apply(&CssProperty::TextColor(CssPropertyValue::Exact(
2614 StyleTextColor { inner: ColorU { r: 0, g: 0, b: 0, a: 0 } },
2615 )));
2616 assert_eq!(transparent.text.text_color, 0);
2617 }
2618
2619 #[test]
2620 fn apply_line_height_round_trips_and_saturates_at_both_ends() {
2621 let mut s = Sink::new();
2622 s.apply(&line_height_prop(120.0));
2623 assert_eq!(s.text.line_height, 1200, "120% must encode as % x 10");
2624
2625 let mut big = Sink::new();
2632 big.apply(&line_height_prop(1.0e9f32));
2633 assert_eq!(
2634 big.text.line_height, I16_SENTINEL,
2635 "a huge unitless multiple saturates to the sentinel"
2636 );
2637 let mut neg = Sink::new();
2638 neg.apply(&line_height_prop(-1.0e9f32));
2639 assert_eq!(
2640 neg.text.line_height, -32768,
2641 "a huge absolute px line-height clamps instead of dropping to normal"
2642 );
2643
2644 let mut px48 = Sink::new();
2647 px48.apply(&line_height_prop(-4800.0));
2648 assert_eq!(px48.text.line_height, -480, "line-height: 48px stores as -px x 10");
2649 }
2650
2651 #[test]
2652 fn apply_font_family_hash_is_nonzero_stable_and_registered() {
2653 let arial = StyleFontFamilyVec::from_vec(vec![StyleFontFamily::System("Arial".into())]);
2654
2655 let mut s = Sink::new();
2656 s.apply(&CssProperty::FontFamily(CssPropertyValue::Exact(arial.clone())));
2657 let h = s.text.font_family_hash;
2658 assert_ne!(
2659 h, 0,
2660 "0 is the 'unset' sentinel — a set font-family must never hash to it"
2661 );
2662 assert!(
2663 s.fonts.contains_key(&h),
2664 "the hash must be registered in the reverse map, or consumers cannot resolve it"
2665 );
2666
2667 let mut same = Sink::new();
2669 same.apply(&CssProperty::FontFamily(CssPropertyValue::Exact(arial)));
2670 assert_eq!(same.text.font_family_hash, h);
2671
2672 let mut other = Sink::new();
2674 other.apply(&CssProperty::FontFamily(CssPropertyValue::Exact(
2675 StyleFontFamilyVec::from_vec(vec![StyleFontFamily::System("Times".into())]),
2676 )));
2677 assert_ne!(other.text.font_family_hash, h);
2678 }
2679
2680 #[test]
2685 fn ua_css_is_idempotent_for_every_representative_node_type() {
2686 let nodes = [
2687 NodeData::create_node(NodeType::Html),
2688 NodeData::create_node(NodeType::Body),
2689 NodeData::create_node(NodeType::Div),
2690 NodeData::create_node(NodeType::P),
2691 NodeData::create_node(NodeType::Br),
2692 NodeData::create_text("hello"),
2693 ];
2694 for nd in &nodes {
2695 let mut s = Sink::new();
2696 s.ua(&nd.node_type);
2697 let once = s.snapshot();
2698 s.ua(&nd.node_type);
2699 assert_eq!(
2700 s.snapshot(),
2701 once,
2702 "applying UA CSS twice must be a no-op the second time"
2703 );
2704 }
2705 }
2706
2707 #[test]
2708 fn ua_css_never_touches_the_tier1_populated_bit() {
2709 for nt in [NodeType::Html, NodeType::Body, NodeType::Div, NodeType::P] {
2711 let mut s = Sink::new();
2712 s.ua(&nt);
2713 assert_eq!(s.tier1 & TIER1_POPULATED_BIT, 0);
2714 }
2715 }
2716
2717 #[test]
2718 fn ua_css_survives_a_hostile_pre_filled_sink() {
2719 let mut s = Sink::new();
2722 s.tier1 = u64::MAX;
2723 s.dims.width = U32_SENTINEL;
2724 s.dims.font_size = U32_SENTINEL;
2725 s.dims.flex_grow = U16_SENTINEL;
2726 s.cold.z_index = i16::MIN;
2727 s.cold.opacity = OPACITY_SENTINEL;
2728 s.text.line_height = i16::MIN;
2729 s.ua(&NodeType::Div);
2730 assert_eq!(
2731 s.tier1 & TIER1_POPULATED_BIT,
2732 TIER1_POPULATED_BIT,
2733 "UA CSS must not clear bits it does not own"
2734 );
2735 }
2736
2737 #[test]
2742 fn build_compact_cache_handles_zero_nodes() {
2743 let cache = CssPropertyCache::empty(0);
2744 let r = cache.build_compact_cache(&[], &[]);
2745 assert_eq!(r.node_count(), 0);
2746 assert!(r.tier2_dims.is_empty());
2747 assert!(r.font_dirty_nodes.is_empty());
2748 assert!(r.prev_font_hashes.is_empty());
2749 }
2750
2751 #[test]
2752 fn build_compact_cache_tolerates_a_mismatched_prev_font_hash_slice() {
2753 let cache = CssPropertyCache::empty(3);
2754 let nodes = div_nodes(3);
2755 for prev in [vec![1u64, 2, 3, 4, 5, 6], vec![7u64], Vec::new()] {
2757 let r = cache.build_compact_cache(&nodes, &prev);
2758 assert_eq!(r.prev_font_hashes.len(), 3);
2759 assert_eq!(r.node_count(), 3);
2760 }
2761 }
2762
2763 #[test]
2764 fn build_compact_cache_tolerates_short_node_data() {
2765 let cache = CssPropertyCache::empty(4);
2768 let r = cache.build_compact_cache(&div_nodes(2), &[]);
2769 assert_eq!(r.node_count(), 4);
2770 assert_eq!(r.tier2_dims.len(), 4);
2771 assert_eq!(r.tier2_cold.len(), 4);
2772 assert_eq!(r.tier2b_text.len(), 4);
2773 assert_eq!(r.prev_font_hashes.len(), 4);
2774 }
2775
2776 #[test]
2777 fn build_compact_cache_honours_node_count_over_node_data_len() {
2778 let cache = CssPropertyCache::empty(2);
2779 let r = cache.build_compact_cache(&div_nodes(5), &[]);
2780 assert_eq!(r.node_count(), 2);
2781 }
2782
2783 #[test]
2784 fn build_compact_cache_rebuild_with_unchanged_fonts_is_not_dirty() {
2785 let cache = CssPropertyCache::empty(3);
2786 let nodes = div_nodes(3);
2787 let first = cache.build_compact_cache(&nodes, &[]);
2788 let second = cache.build_compact_cache(&nodes, &first.prev_font_hashes);
2789 assert!(
2790 second.font_dirty_nodes.is_empty(),
2791 "a rebuild with identical font hashes must not re-resolve any font chain"
2792 );
2793 }
2794
2795 #[test]
2800 fn build_with_inheritance_handles_zero_nodes() {
2801 let cache = CssPropertyCache::empty(0);
2802 let r = cache.build_compact_cache_with_inheritance(&[], &[], &[]);
2803 assert_eq!(r.node_count(), 0);
2804
2805 let mut msgs = None;
2806 let r2 = cache.build_compact_cache_with_inheritance_debug(&[], &[], &[], &mut msgs);
2807 assert_eq!(r2.node_count(), 0);
2808 assert!(msgs.is_none());
2809 }
2810
2811 #[test]
2812 fn build_with_inheritance_propagates_font_size_down_the_chain() {
2813 let n = 3;
2814 let cache = CssPropertyCache::empty(n);
2815 let r = cache.build_compact_cache_with_inheritance(
2816 &div_nodes(n),
2817 &linear_hierarchy(n),
2818 &[],
2819 );
2820 assert_eq!(r.node_count(), n);
2821 assert_eq!(r.tier2_dims[1].font_size, r.tier2_dims[0].font_size);
2823 assert_eq!(r.tier2_dims[2].font_size, r.tier2_dims[0].font_size);
2824 }
2825
2826 #[test]
2827 fn build_with_inheritance_marks_all_nodes_dirty_on_the_first_build() {
2828 let n = 3;
2829 let cache = CssPropertyCache::empty(n);
2830 let nodes = div_nodes(n);
2831 let hierarchy = linear_hierarchy(n);
2832
2833 let first = cache.build_compact_cache_with_inheritance(&nodes, &hierarchy, &[]);
2835 assert_eq!(first.font_dirty_nodes, vec![0, 1, 2]);
2836
2837 let second =
2839 cache.build_compact_cache_with_inheritance(&nodes, &hierarchy, &first.prev_font_hashes);
2840 assert!(second.font_dirty_nodes.is_empty());
2841 }
2842
2843 #[test]
2844 fn build_with_inheritance_global_star_rules_skip_text_nodes() {
2845 let mut cache = CssPropertyCache::empty(2);
2849 cache
2850 .global_css_props
2851 .push(CssProperty::PaddingTop(padding(5.0)));
2852
2853 let nodes = vec![
2854 NodeData::create_node(NodeType::Div),
2855 NodeData::create_text("hi"),
2856 ];
2857 let r = cache.build_compact_cache_with_inheritance(&nodes, &linear_hierarchy(2), &[]);
2858
2859 assert_eq!(
2860 r.tier2_dims[0].padding_top, 50,
2861 "the `*` rule must apply to the element"
2862 );
2863 assert_ne!(
2864 r.tier2_dims[1].padding_top, 50,
2865 "the `*` rule must NOT apply to a text node"
2866 );
2867 }
2868
2869 #[test]
2870 fn build_with_inheritance_debug_messages_are_opt_in() {
2871 let n = 2;
2872 let cache = CssPropertyCache::empty(n);
2873 let nodes = div_nodes(n);
2874 let hierarchy = linear_hierarchy(n);
2875
2876 let mut on = Some(Vec::new());
2877 let _ = cache.build_compact_cache_with_inheritance_debug(&nodes, &hierarchy, &[], &mut on);
2878 assert!(
2879 !on.expect("still Some").is_empty(),
2880 "debug logging must emit at least one cascade message"
2881 );
2882
2883 let mut off = None;
2884 let _ = cache.build_compact_cache_with_inheritance_debug(&nodes, &hierarchy, &[], &mut off);
2885 assert!(off.is_none(), "a None sink must stay None");
2886 }
2887
2888 #[test]
2893 fn resolve_font_size_percent_uses_the_parent() {
2894 let mut dims = vec![CompactNodeProps::default(); 2];
2895 dims[0].font_size = encode_pixel_value_u32(&PixelValue::px(20.0));
2896 dims[1].font_size = encode_pixel_value_u32(&PixelValue::percent(50.0));
2897 resolve_font_size_to_px(&mut dims, 1, Some(NodeId::new(0)));
2898 let pv = decode_pixel_value_u32(dims[1].font_size).expect("must resolve to px");
2899 assert_eq!(pv.metric, SizeMetric::Px);
2900 assert!((pv.number.get() - 10.0).abs() < 0.01, "got {}", pv.number.get());
2901 }
2902
2903 #[test]
2904 fn resolve_font_size_pt_converts_to_px() {
2905 let mut dims = vec![CompactNodeProps::default()];
2906 dims[0].font_size = encode_pixel_value_u32(&PixelValue::pt(12.0));
2907 resolve_font_size_to_px(&mut dims, 0, None);
2908 let pv = decode_pixel_value_u32(dims[0].font_size).expect("must resolve to px");
2909 assert!(
2910 (pv.number.get() - 16.0).abs() < 0.01,
2911 "12pt should be 16px, got {}",
2912 pv.number.get()
2913 );
2914 }
2915
2916 #[test]
2917 fn resolve_font_size_leaves_absolute_and_sentinel_values_alone() {
2918 let mut dims = vec![CompactNodeProps::default(); 2];
2920 dims[0].font_size = encode_pixel_value_u32(&PixelValue::px(20.0));
2921 dims[1].font_size = encode_pixel_value_u32(&PixelValue::px(13.0));
2922 let before = dims[1].font_size;
2923 resolve_font_size_to_px(&mut dims, 1, Some(NodeId::new(0)));
2924 assert_eq!(dims[1].font_size, before);
2925
2926 let mut sent = vec![CompactNodeProps::default(); 2];
2928 sent[0].font_size = encode_pixel_value_u32(&PixelValue::px(20.0));
2929 sent[1].font_size = U32_SENTINEL;
2930 resolve_font_size_to_px(&mut sent, 1, Some(NodeId::new(0)));
2931 assert_eq!(sent[1].font_size, U32_SENTINEL);
2932
2933 let mut def = vec![CompactNodeProps::default(); 2];
2935 assert_eq!(def[1].font_size, U32_INITIAL);
2936 resolve_font_size_to_px(&mut def, 1, Some(NodeId::new(0)));
2937 assert_eq!(def[1].font_size, U32_INITIAL);
2938 }
2939
2940 #[test]
2941 fn resolve_font_size_negative_em_is_deterministic() {
2942 let mut dims = vec![CompactNodeProps::default(); 2];
2943 dims[0].font_size = encode_pixel_value_u32(&PixelValue::px(20.0));
2944 dims[1].font_size = encode_pixel_value_u32(&PixelValue::em(-2.0));
2945 resolve_font_size_to_px(&mut dims, 1, Some(NodeId::new(0)));
2946 let pv = decode_pixel_value_u32(dims[1].font_size).expect("must stay decodable");
2947 assert!(
2948 (pv.number.get() + 40.0).abs() < 0.01,
2949 "-2em of 20px should be -40px, got {}",
2950 pv.number.get()
2951 );
2952 }
2953
2954 #[test]
2955 fn resolve_font_size_overflow_saturates_instead_of_wrapping() {
2956 let mut dims = vec![CompactNodeProps::default(); 2];
2957 dims[0].font_size = encode_pixel_value_u32(&PixelValue::px(20.0));
2958 dims[1].font_size = encode_pixel_value_u32(&PixelValue::em(100_000.0));
2960 resolve_font_size_to_px(&mut dims, 1, Some(NodeId::new(0)));
2961 assert_eq!(
2962 dims[1].font_size, U32_SENTINEL,
2963 "an overflowing font-size must land on the tier-3 sentinel, not wrap"
2964 );
2965 }
2966
2967 #[test]
2968 fn resolve_font_size_nan_em_degrades_to_zero() {
2969 let mut dims = vec![CompactNodeProps::default(); 2];
2970 dims[0].font_size = encode_pixel_value_u32(&PixelValue::px(20.0));
2971 dims[1].font_size = encode_pixel_value_u32(&PixelValue::em(f32::NAN));
2972 resolve_font_size_to_px(&mut dims, 1, Some(NodeId::new(0)));
2973 let pv = decode_pixel_value_u32(dims[1].font_size).expect("must stay decodable");
2974 assert!(pv.number.get().is_finite(), "a NaN font-size must not propagate");
2975 assert_eq!(pv.number.get(), 0.0);
2976 }
2977
2978 #[test]
2979 fn resolve_font_size_root_rem_uses_the_16px_initial_value() {
2980 let mut dims = vec![CompactNodeProps::default()];
2986 dims[0].font_size = encode_pixel_value_u32(&PixelValue::rem(2.0));
2987 resolve_font_size_to_px(&mut dims, 0, None);
2988 let pv = decode_pixel_value_u32(dims[0].font_size).expect("must resolve to px");
2989 assert!(
2990 (pv.number.get() - 32.0).abs() < 0.01,
2991 "root `font-size: 2rem` should resolve against the 16px initial value (= 32px), \
2992 got {}px",
2993 pv.number.get()
2994 );
2995 }
2996}