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 if *z >= i32::from(I16_SENTINEL_THRESHOLD) {
286 result.tier2_cold[i].z_index = I16_SENTINEL;
287 } else {
288 result.tier2_cold[i].z_index = *z as i16;
289 }
290 }
291 }
292 }
293 }
294
295 {
297 let bts = self.get_border_top_style(nd, &node_id, &default_state)
298 .and_then(|v| v.get_property().copied())
299 .map(|v| v.inner)
300 .unwrap_or_default();
301 let brs = self.get_border_right_style(nd, &node_id, &default_state)
302 .and_then(|v| v.get_property().copied())
303 .map(|v| v.inner)
304 .unwrap_or_default();
305 let bbs = self.get_border_bottom_style(nd, &node_id, &default_state)
306 .and_then(|v| v.get_property().copied())
307 .map(|v| v.inner)
308 .unwrap_or_default();
309 let bls = self.get_border_left_style(nd, &node_id, &default_state)
310 .and_then(|v| v.get_property().copied())
311 .map(|v| v.inner)
312 .unwrap_or_default();
313 result.tier2_cold[i].border_styles_packed =
314 encode_border_styles_packed(bts, brs, bbs, bls);
315 }
316
317 if let Some(val) = self.get_border_top_color(nd, &node_id, &default_state) {
319 if let Some(color) = val.get_property() {
320 result.tier2_cold[i].border_top_color = encode_color_u32(&color.inner);
321 }
322 }
323 if let Some(val) = self.get_border_right_color(nd, &node_id, &default_state) {
324 if let Some(color) = val.get_property() {
325 result.tier2_cold[i].border_right_color = encode_color_u32(&color.inner);
326 }
327 }
328 if let Some(val) = self.get_border_bottom_color(nd, &node_id, &default_state) {
329 if let Some(color) = val.get_property() {
330 result.tier2_cold[i].border_bottom_color = encode_color_u32(&color.inner);
331 }
332 }
333 if let Some(val) = self.get_border_left_color(nd, &node_id, &default_state) {
334 if let Some(color) = val.get_property() {
335 result.tier2_cold[i].border_left_color = encode_color_u32(&color.inner);
336 }
337 }
338
339 if let Some(val) = self.get_border_spacing(nd, &node_id, &default_state) {
341 if let Some(spacing) = val.get_property() {
342 if spacing.horizontal.metric == SizeMetric::Px {
343 result.tier2_cold[i].border_spacing_h = encode_resolved_px_i16(spacing.horizontal.number.get());
344 }
345 if spacing.vertical.metric == SizeMetric::Px {
346 result.tier2_cold[i].border_spacing_v = encode_resolved_px_i16(spacing.vertical.number.get());
347 }
348 }
349 }
350
351 if let Some(val) = self.get_tab_size(nd, &node_id, &default_state) {
353 result.tier2_cold[i].tab_size = encode_css_pixel_as_i16(val);
354 }
355
356 if let Some(val) = self.get_text_color(nd, &node_id, &default_state) {
362 if let Some(color) = val.get_property() {
363 let c = &color.inner;
364 result.tier2b_text[i].text_color =
365 (u32::from(c.r) << 24) | (u32::from(c.g) << 16) | (u32::from(c.b) << 8) | u32::from(c.a);
366 }
367 }
368
369 if let Some(val) = self.get_font_family(nd, &node_id, &default_state) {
371 if let Some(families) = val.get_property() {
372 let mut hasher = DefaultHasher::new();
373 families.hash(&mut hasher);
374 let h = hasher.finish();
375 let h = if h == 0 { 1 } else { h };
376 result.tier2b_text[i].font_family_hash = h;
377 result.font_hash_to_families.insert(h, families.clone());
378 }
379 }
380
381 if let Some(val) = self.get_line_height(nd, &node_id, &default_state) {
383 if let Some(lh) = val.get_property() {
384 let pct_x10 = (lh.inner.normalized() * 1000.0).round() as i32;
388 if pct_x10 >= -32768 && pct_x10 < i32::from(I16_SENTINEL_THRESHOLD) {
389 result.tier2b_text[i].line_height = pct_x10 as i16;
390 } else {
391 result.tier2b_text[i].line_height = I16_SENTINEL;
392 }
393 }
394 }
395
396 if let Some(val) = self.get_letter_spacing(nd, &node_id, &default_state) {
398 result.tier2b_text[i].letter_spacing = encode_css_pixel_as_i16(val);
399 }
400
401 if let Some(val) = self.get_word_spacing(nd, &node_id, &default_state) {
403 result.tier2b_text[i].word_spacing = encode_css_pixel_as_i16(val);
404 }
405
406 if let Some(val) = self.get_text_indent(nd, &node_id, &default_state) {
408 result.tier2b_text[i].text_indent = encode_css_pixel_as_i16(val);
409 }
410 }
411
412 result.font_dirty_nodes.clear();
419 for i in 0..node_count {
420 let new_hash = result.tier2b_text[i].font_family_hash;
421 let old_hash = prev_font_hashes.get(i).copied().unwrap_or(0);
422 if new_hash != old_hash {
423 result.font_dirty_nodes.push(i);
424 }
425 }
426 result.prev_font_hashes = result.tier2b_text.iter().map(|t| t.font_family_hash).collect();
428
429 result
430 }
431
432 pub fn build_compact_cache_with_inheritance(
443 &self,
444 node_data: &[NodeData],
445 node_hierarchy: &[crate::styled_dom::NodeHierarchyItem],
446 prev_font_hashes: &[u64],
447 ) -> CompactLayoutCache {
448 self.build_compact_cache_with_inheritance_debug(node_data, node_hierarchy, prev_font_hashes, &mut None)
449 }
450
451 #[allow(clippy::too_many_lines, clippy::cognitive_complexity)] pub fn build_compact_cache_with_inheritance_debug(
454 &self,
455 node_data: &[NodeData],
456 node_hierarchy: &[crate::styled_dom::NodeHierarchyItem],
457 prev_font_hashes: &[u64],
458 debug_messages: &mut Option<Vec<azul_css::LayoutDebugMessage>>,
459 ) -> CompactLayoutCache {
460 const INHERITABLE_TIER1_MASK: u64 =
463 (FONT_WEIGHT_MASK << FONT_WEIGHT_SHIFT)
464 | (FONT_STYLE_MASK << FONT_STYLE_SHIFT)
465 | (TEXT_ALIGN_MASK << TEXT_ALIGN_SHIFT)
466 | (VISIBILITY_MASK << VISIBILITY_SHIFT)
467 | (WHITE_SPACE_MASK << WHITE_SPACE_SHIFT)
468 | (DIRECTION_MASK << DIRECTION_SHIFT)
469 | (BORDER_COLLAPSE_MASK << BORDER_COLLAPSE_SHIFT);
470
471 let node_count = self.node_count;
472 let default_state = StyledNodeState::default();
473 let mut result = CompactLayoutCache::with_capacity(node_count);
474
475 let mut global_tier1: u64 = 0;
478 let mut global_dims = CompactNodeProps::default();
479 let mut global_cold = CompactNodePropsCold::default();
480 let mut global_text = CompactTextProps::default();
481 let has_global = !self.global_css_props.is_empty();
482
483 if has_global {
484 use azul_css::props::property::CssProperty;
485
486 for prop in &self.global_css_props {
487 macro_rules! global_tier1_enum {
489 ($variant:ident, $shift:ident, $mask:ident, $encoder:ident) => {
490 if let CssProperty::$variant(v) = prop {
491 if let Some(exact) = v.get_property() {
492 let encoded = u64::from($encoder(*exact));
493 let shifted_mask = $mask << $shift;
494 global_tier1 = (global_tier1 & !shifted_mask) | ((encoded & $mask) << $shift);
495 }
496 }
497 };
498 }
499
500 global_tier1_enum!(Display, DISPLAY_SHIFT, DISPLAY_MASK, layout_display_to_u8);
501 global_tier1_enum!(Position, POSITION_SHIFT, POSITION_MASK, layout_position_to_u8);
502 global_tier1_enum!(Float, FLOAT_SHIFT, FLOAT_MASK, layout_float_to_u8);
503 global_tier1_enum!(OverflowX, OVERFLOW_X_SHIFT, OVERFLOW_MASK, layout_overflow_to_u8);
504 global_tier1_enum!(OverflowY, OVERFLOW_Y_SHIFT, OVERFLOW_MASK, layout_overflow_to_u8);
505 global_tier1_enum!(BoxSizing, BOX_SIZING_SHIFT, BOX_SIZING_MASK, layout_box_sizing_to_u8);
506 global_tier1_enum!(FlexDirection, FLEX_DIRECTION_SHIFT, FLEX_DIR_MASK, layout_flex_direction_to_u8);
507 global_tier1_enum!(FlexWrap, FLEX_WRAP_SHIFT, FLEX_WRAP_MASK, layout_flex_wrap_to_u8);
508 global_tier1_enum!(JustifyContent, JUSTIFY_CONTENT_SHIFT, JUSTIFY_MASK, layout_justify_content_to_u8);
509 global_tier1_enum!(AlignItems, ALIGN_ITEMS_SHIFT, ALIGN_MASK, layout_align_items_to_u8);
510 global_tier1_enum!(AlignContent, ALIGN_CONTENT_SHIFT, ALIGN_MASK, layout_align_content_to_u8);
511 global_tier1_enum!(Clear, CLEAR_SHIFT, CLEAR_MASK, layout_clear_to_u8);
512 global_tier1_enum!(Visibility, VISIBILITY_SHIFT, VISIBILITY_MASK, style_visibility_to_u8);
513 global_tier1_enum!(WritingMode, WRITING_MODE_SHIFT, WRITING_MODE_MASK, layout_writing_mode_to_u8);
514 global_tier1_enum!(FontWeight, FONT_WEIGHT_SHIFT, FONT_WEIGHT_MASK, style_font_weight_to_u8);
515 global_tier1_enum!(FontStyle, FONT_STYLE_SHIFT, FONT_STYLE_MASK, style_font_style_to_u8);
516 global_tier1_enum!(TextAlign, TEXT_ALIGN_SHIFT, TEXT_ALIGN_MASK, style_text_align_to_u8);
517 global_tier1_enum!(WhiteSpace, WHITE_SPACE_SHIFT, WHITE_SPACE_MASK, style_white_space_to_u8);
518 global_tier1_enum!(Direction, DIRECTION_SHIFT, DIRECTION_MASK, style_direction_to_u8);
519 global_tier1_enum!(VerticalAlign, VERTICAL_ALIGN_SHIFT, VERTICAL_ALIGN_MASK, style_vertical_align_to_u8);
520 global_tier1_enum!(BorderCollapse, BORDER_COLLAPSE_SHIFT, BORDER_COLLAPSE_MASK, border_collapse_to_u8);
521
522 match prop {
524 CssProperty::PaddingTop(v) => { global_dims.padding_top = encode_css_pixel_as_i16(v); }
525 CssProperty::PaddingRight(v) => { global_dims.padding_right = encode_css_pixel_as_i16(v); }
526 CssProperty::PaddingBottom(v) => { global_dims.padding_bottom = encode_css_pixel_as_i16(v); }
527 CssProperty::PaddingLeft(v) => { global_dims.padding_left = encode_css_pixel_as_i16(v); }
528 CssProperty::MarginTop(v) => { global_dims.margin_top = encode_margin_i16(v); }
529 CssProperty::MarginRight(v) => { global_dims.margin_right = encode_margin_i16(v); }
530 CssProperty::MarginBottom(v) => { global_dims.margin_bottom = encode_margin_i16(v); }
531 CssProperty::MarginLeft(v) => { global_dims.margin_left = encode_margin_i16(v); }
532 CssProperty::Width(v) => { global_dims.width = encode_layout_width(v); }
533 CssProperty::Height(v) => { global_dims.height = encode_layout_height(v); }
534 CssProperty::FontSize(v) => { global_dims.font_size = encode_pixel_prop(v); }
535 CssProperty::BorderTopWidth(v) => { global_dims.border_top_width = encode_css_pixel_as_i16(v); }
536 CssProperty::BorderRightWidth(v) => { global_dims.border_right_width = encode_css_pixel_as_i16(v); }
537 CssProperty::BorderBottomWidth(v) => { global_dims.border_bottom_width = encode_css_pixel_as_i16(v); }
538 CssProperty::BorderLeftWidth(v) => { global_dims.border_left_width = encode_css_pixel_as_i16(v); }
539 _ => {}
540 }
541 }
542
543 if global_tier1 != 0 {
544 global_tier1 |= TIER1_POPULATED_BIT;
545 }
546 }
547
548 macro_rules! cascade_debug {
550 ($($arg:tt)*) => {
551 if let Some(ref mut msgs) = debug_messages {
552 msgs.push(azul_css::LayoutDebugMessage::css_getter(format!($($arg)*)));
553 }
554 };
555 }
556
557 for i in 0..node_count {
558 let node_id = NodeId::new(i);
559 let nd = &node_data[i];
560
561 let parent_id = node_hierarchy[i].parent_id();
575 if let Some(pid) = parent_id {
576 let pi = pid.index();
577
578 debug_assert!(
587 pi < i,
588 "compact cascade: non-pre-order arena — node {i}'s parent {pi} \
589 is not stored before it; inheritance would read default values",
590 );
591 if pi < i {
592 result.tier1_enums[i] = result.tier1_enums[pi] & INHERITABLE_TIER1_MASK;
594
595 result.tier2_dims[i].font_size = result.tier2_dims[pi].font_size;
597
598 result.tier2_cold[i].border_spacing_h = result.tier2_cold[pi].border_spacing_h;
600 result.tier2_cold[i].border_spacing_v = result.tier2_cold[pi].border_spacing_v;
601 result.tier2_cold[i].tab_size = result.tier2_cold[pi].tab_size;
602
603 result.tier2b_text[i] = result.tier2b_text[pi];
605 }
606 }
607
608 {
609 let d = &result.tier2_dims[i];
610 cascade_debug!("node[{}] {:?} after-inherit: pt={} pb={} pl={} pr={} mt={} mb={} ml={} mr={} w={} h={}",
611 i, nd.node_type, d.padding_top, d.padding_bottom, d.padding_left, d.padding_right,
612 d.margin_top, d.margin_bottom, d.margin_left, d.margin_right, d.width, d.height);
613 }
614
615 apply_ua_css_to_compact(
618 &nd.node_type,
619 &mut result.tier1_enums[i],
620 &mut result.tier2_dims[i],
621 &mut result.tier2_cold[i],
622 &mut result.tier2b_text[i],
623 &mut result.font_hash_to_families,
624 );
625
626 {
627 let d = &result.tier2_dims[i];
628 cascade_debug!("node[{}] {:?} after-UA: pt={} pb={} pl={} pr={} mt={} mb={} ml={} mr={}",
629 i, nd.node_type, d.padding_top, d.padding_bottom, d.padding_left, d.padding_right,
630 d.margin_top, d.margin_bottom, d.margin_left, d.margin_right);
631 }
632
633 if !nd.is_text_node() {
642 for prop in &self.global_css_props {
643 apply_css_property_to_compact(
644 prop,
645 &mut result.tier1_enums[i],
646 &mut result.tier2_dims[i],
647 &mut result.tier2_cold[i],
648 &mut result.tier2b_text[i],
649 &mut result.font_hash_to_families,
650 );
651 update_dom_declared_flags(prop, &mut result.dom_declared_flags);
652 }
653 }
654
655 {
656 let d = &result.tier2_dims[i];
657 cascade_debug!("node[{}] {:?} after-global-star: pt={} pb={} pl={} pr={} mt={} mb={} ml={} mr={}",
658 i, nd.node_type, d.padding_top, d.padding_bottom, d.padding_left, d.padding_right,
659 d.margin_top, d.margin_bottom, d.margin_left, d.margin_right);
660 let n_props = self.css_props.get_slice(i).len();
661 let n_inline = nd.style.iter_inline_properties().count();
662 cascade_debug!("node[{}] css_props={} entries, inline={} entries", i, n_props, n_inline);
663 for prop in self.css_props.get_slice(i) {
664 cascade_debug!("node[{}] css_prop: state={:?} type={:?}", i, prop.state, prop.prop_type);
665 }
666 }
667
668 for prop in self.css_props.get_slice(i) {
674 if prop.state != azul_css::dynamic_selector::PseudoStateType::Normal { continue; }
675 apply_css_property_to_compact(
676 &prop.property,
677 &mut result.tier1_enums[i],
678 &mut result.tier2_dims[i],
679 &mut result.tier2_cold[i],
680 &mut result.tier2b_text[i],
681 &mut result.font_hash_to_families,
682 );
683 update_dom_declared_flags(&prop.property, &mut result.dom_declared_flags);
684 }
685
686 {
687 let d = &result.tier2_dims[i];
688 cascade_debug!("node[{}] {:?} after-css-props: pt={} pb={} pl={} pr={} mt={} mb={} ml={} mr={}",
689 i, nd.node_type, d.padding_top, d.padding_bottom, d.padding_left, d.padding_right,
690 d.margin_top, d.margin_bottom, d.margin_left, d.margin_right);
691 }
692
693 for (prop, conds) in nd.style.iter_inline_properties() {
696 let is_normal = conds.as_slice().is_empty()
698 || conds.as_slice().iter().all(|c|
699 matches!(c, azul_css::dynamic_selector::DynamicSelector::PseudoState(
700 azul_css::dynamic_selector::PseudoStateType::Normal
701 ))
702 );
703 if !is_normal { continue; }
704 if let CssProperty::Width(v) = prop {
711 result.tier2_dims[i].width = encode_layout_width(v);
712 } else if let CssProperty::Height(v) = prop {
713 result.tier2_dims[i].height = encode_layout_height(v);
714 } else if let CssProperty::FlexGrow(v) = prop {
715 if let Some(e) = v.get_property() {
716 result.tier2_dims[i].flex_grow = encode_flex_u16(e.inner.get());
717 }
718 } else if let CssProperty::Display(v) = prop {
719 if let Some(e) = v.get_property() {
720 let enc = u64::from(layout_display_to_u8(*e));
721 let m = DISPLAY_MASK;
722 let s = DISPLAY_SHIFT;
723 result.tier1_enums[i] = (result.tier1_enums[i] & !(m << s)) | ((enc & m) << s);
724 }
725 } else {
726 apply_css_property_to_compact(
727 prop,
728 &mut result.tier1_enums[i],
729 &mut result.tier2_dims[i],
730 &mut result.tier2_cold[i],
731 &mut result.tier2b_text[i],
732 &mut result.font_hash_to_families,
733 );
734 }
735 update_dom_declared_flags(prop, &mut result.dom_declared_flags);
736 }
737
738 resolve_font_size_to_px(
742 &mut result.tier2_dims,
743 i,
744 parent_id,
745 );
746
747 if result.tier1_enums[i] != 0 {
749 result.tier1_enums[i] |= TIER1_POPULATED_BIT;
750 }
751 }
752
753 result.font_dirty_nodes.clear();
760 let first_build = prev_font_hashes.is_empty();
761 for i in 0..node_count {
762 let new_hash = result.tier2b_text[i].font_family_hash;
763 let old_hash = prev_font_hashes.get(i).copied().unwrap_or(0);
764 if first_build || new_hash != old_hash {
765 result.font_dirty_nodes.push(i);
766 }
767 }
768 result.prev_font_hashes = result.tier2b_text.iter().map(|t| t.font_family_hash).collect();
769
770 result
771 }
772}
773
774fn apply_ua_css_to_compact(
781 node_type: &crate::dom::NodeType,
782 tier1: &mut u64,
783 dims: &mut CompactNodeProps,
784 cold: &mut CompactNodePropsCold,
785 text: &mut CompactTextProps,
786 font_hash_map: &mut alloc::collections::BTreeMap<u64, azul_css::props::basic::font::StyleFontFamilyVec>,
787) {
788 use azul_css::props::property::CssPropertyType as PT2;
789 const UA_PROPERTY_TYPES: &[PT2] = &[
790 PT2::Display, PT2::Position, PT2::Float, PT2::Clear,
792 PT2::OverflowX, PT2::OverflowY, PT2::BoxSizing,
793 PT2::FlexDirection, PT2::FlexWrap, PT2::JustifyContent,
794 PT2::AlignItems, PT2::AlignContent, PT2::WritingMode,
795 PT2::FontWeight, PT2::FontStyle, PT2::TextAlign,
796 PT2::Visibility, PT2::WhiteSpace, PT2::Direction,
797 PT2::VerticalAlign, PT2::BorderCollapse,
798 PT2::Width, PT2::Height, PT2::FontSize,
800 PT2::MarginTop, PT2::MarginBottom, PT2::MarginLeft, PT2::MarginRight,
801 PT2::PaddingTop, PT2::PaddingBottom, PT2::PaddingLeft, PT2::PaddingRight,
802 PT2::BorderTopWidth, PT2::BorderTopStyle, PT2::BorderTopColor,
803 PT2::BorderRightWidth, PT2::BorderRightStyle, PT2::BorderRightColor,
804 PT2::BorderBottomWidth, PT2::BorderBottomStyle, PT2::BorderBottomColor,
805 PT2::BorderLeftWidth, PT2::BorderLeftStyle, PT2::BorderLeftColor,
806 PT2::TextColor, PT2::LineHeight, PT2::LetterSpacing, PT2::WordSpacing,
808 PT2::TextDecoration, PT2::Cursor, PT2::ListStyleType,
809 ];
810 for pt in UA_PROPERTY_TYPES {
811 if let Some(ua_prop) = crate::ua_css::get_ua_property(node_type, *pt) {
812 apply_css_property_to_compact(ua_prop, tier1, dims, cold, text, font_hash_map);
813 }
814 }
815}
816
817fn resolve_font_size_to_px(
821 tier2_dims: &mut [CompactNodeProps],
822 node_idx: usize,
823 parent_id: Option<NodeId>,
824) {
825 let raw_fs = tier2_dims[node_idx].font_size;
826 if raw_fs == U32_SENTINEL || raw_fs >= U32_SENTINEL_THRESHOLD {
827 return;
828 }
829 let pv = match decode_pixel_value_u32(raw_fs) {
830 Some(pv) if pv.metric != SizeMetric::Px => pv,
831 _ => return,
832 };
833
834 let parent_font_size_px = parent_id
840 .map_or(16.0, |pid| {
841 let pi = pid.index();
842 debug_assert!(
843 pi < node_idx,
844 "compact font-size resolve: non-pre-order arena — node {node_idx}'s \
845 parent {pi} font-size is not yet resolved",
846 );
847 if pi < node_idx {
848 tier2_dims
849 .get(pi)
850 .and_then(|p| decode_pixel_value_u32(p.font_size))
851 .map_or(16.0, |ppv| ppv.number.get())
852 } else {
853 16.0
854 }
855 });
856
857 let resolved_px = match pv.metric {
858 SizeMetric::Em => pv.number.get() * parent_font_size_px,
859 SizeMetric::Percent => pv.number.get() / 100.0 * parent_font_size_px,
860 SizeMetric::Rem => {
861 tier2_dims
862 .first()
863 .and_then(|r| decode_pixel_value_u32(r.font_size))
864 .map_or(16.0, |rpv| rpv.number.get())
865 * pv.number.get()
866 }
867 SizeMetric::Pt => pv.number.get() * 96.0 / 72.0,
868 _ => pv.number.get(),
869 };
870 tier2_dims[node_idx].font_size =
871 encode_pixel_value_u32(&azul_css::props::basic::pixel::PixelValue::px(resolved_px));
872}
873
874#[inline]
881#[allow(clippy::match_same_arms)]
887#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
890#[allow(clippy::too_many_lines, clippy::cognitive_complexity)] fn apply_css_property_to_compact(
892 prop: &CssProperty,
893 tier1: &mut u64,
894 dims: &mut CompactNodeProps,
895 cold: &mut CompactNodePropsCold,
896 text: &mut CompactTextProps,
897 font_hash_map: &mut alloc::collections::BTreeMap<u64, azul_css::props::basic::font::StyleFontFamilyVec>,
898) {
899 macro_rules! set_tier1 {
900 ($v:expr, $shift:expr, $mask:expr, $encoder:ident) => {
901 if let Some(exact) = $v.get_property() {
902 let encoded = u64::from($encoder(*exact));
903 let shifted_mask = $mask << $shift;
904 *tier1 = (*tier1 & !shifted_mask) | ((encoded & $mask) << $shift);
905 }
906 };
907 }
908
909 match prop {
910 CssProperty::Display(v) => set_tier1!(v, DISPLAY_SHIFT, DISPLAY_MASK, layout_display_to_u8),
912 CssProperty::Position(v) => set_tier1!(v, POSITION_SHIFT, POSITION_MASK, layout_position_to_u8),
913 CssProperty::Float(v) => set_tier1!(v, FLOAT_SHIFT, FLOAT_MASK, layout_float_to_u8),
914 CssProperty::OverflowX(v) => set_tier1!(v, OVERFLOW_X_SHIFT, OVERFLOW_MASK, layout_overflow_to_u8),
915 CssProperty::OverflowY(v) => set_tier1!(v, OVERFLOW_Y_SHIFT, OVERFLOW_MASK, layout_overflow_to_u8),
916 CssProperty::BoxSizing(v) => set_tier1!(v, BOX_SIZING_SHIFT, BOX_SIZING_MASK, layout_box_sizing_to_u8),
917 CssProperty::FlexDirection(v) => set_tier1!(v, FLEX_DIRECTION_SHIFT, FLEX_DIR_MASK, layout_flex_direction_to_u8),
918 CssProperty::FlexWrap(v) => set_tier1!(v, FLEX_WRAP_SHIFT, FLEX_WRAP_MASK, layout_flex_wrap_to_u8),
919 CssProperty::JustifyContent(v) => set_tier1!(v, JUSTIFY_CONTENT_SHIFT, JUSTIFY_MASK, layout_justify_content_to_u8),
920 CssProperty::AlignItems(v) => set_tier1!(v, ALIGN_ITEMS_SHIFT, ALIGN_MASK, layout_align_items_to_u8),
921 CssProperty::AlignContent(v) => set_tier1!(v, ALIGN_CONTENT_SHIFT, ALIGN_MASK, layout_align_content_to_u8),
922 CssProperty::WritingMode(v) => set_tier1!(v, WRITING_MODE_SHIFT, WRITING_MODE_MASK, layout_writing_mode_to_u8),
923 CssProperty::Clear(v) => set_tier1!(v, CLEAR_SHIFT, CLEAR_MASK, layout_clear_to_u8),
924 CssProperty::FontWeight(v) => set_tier1!(v, FONT_WEIGHT_SHIFT, FONT_WEIGHT_MASK, style_font_weight_to_u8),
925 CssProperty::FontStyle(v) => set_tier1!(v, FONT_STYLE_SHIFT, FONT_STYLE_MASK, style_font_style_to_u8),
926 CssProperty::TextAlign(v) => set_tier1!(v, TEXT_ALIGN_SHIFT, TEXT_ALIGN_MASK, style_text_align_to_u8),
927 CssProperty::Visibility(v) => set_tier1!(v, VISIBILITY_SHIFT, VISIBILITY_MASK, style_visibility_to_u8),
928 CssProperty::WhiteSpace(v) => set_tier1!(v, WHITE_SPACE_SHIFT, WHITE_SPACE_MASK, style_white_space_to_u8),
929 CssProperty::Direction(v) => set_tier1!(v, DIRECTION_SHIFT, DIRECTION_MASK, style_direction_to_u8),
930 CssProperty::VerticalAlign(v) => set_tier1!(v, VERTICAL_ALIGN_SHIFT, VERTICAL_ALIGN_MASK, style_vertical_align_to_u8),
931 CssProperty::BorderCollapse(v) => set_tier1!(v, BORDER_COLLAPSE_SHIFT, BORDER_COLLAPSE_MASK, border_collapse_to_u8),
932 CssProperty::AlignSelf(v) => set_tier1!(v, ALIGN_SELF_SHIFT, ALIGN_SELF_MASK, layout_align_self_to_u8),
933 CssProperty::JustifySelf(v) => set_tier1!(v, JUSTIFY_SELF_SHIFT, JUSTIFY_SELF_MASK, layout_justify_self_to_u8),
934 CssProperty::GridAutoFlow(v) => set_tier1!(v, GRID_AUTO_FLOW_SHIFT, GRID_AUTO_FLOW_MASK, layout_grid_auto_flow_to_u8),
935 CssProperty::JustifyItems(v) => set_tier1!(v, JUSTIFY_ITEMS_SHIFT, JUSTIFY_ITEMS_MASK, layout_justify_items_to_u8),
936
937 CssProperty::Width(v) => { dims.width = encode_layout_width(v); }
939 CssProperty::Height(v) => { dims.height = encode_layout_height(v); }
940 CssProperty::MinWidth(v) => { dims.min_width = encode_pixel_prop(v); }
941 CssProperty::MaxWidth(v) => { dims.max_width = encode_pixel_prop(v); }
942 CssProperty::MinHeight(v) => { dims.min_height = encode_pixel_prop(v); }
943 CssProperty::MaxHeight(v) => { dims.max_height = encode_pixel_prop(v); }
944 CssProperty::FlexBasis(v) => { dims.flex_basis = encode_flex_basis(v); }
945 CssProperty::FontSize(v) => { dims.font_size = encode_pixel_prop(v); }
946 CssProperty::PaddingTop(v) => { dims.padding_top = encode_css_pixel_as_i16(v); }
947 CssProperty::PaddingRight(v) => { dims.padding_right = encode_css_pixel_as_i16(v); }
948 CssProperty::PaddingBottom(v) => { dims.padding_bottom = encode_css_pixel_as_i16(v); }
949 CssProperty::PaddingLeft(v) => { dims.padding_left = encode_css_pixel_as_i16(v); }
950 CssProperty::MarginTop(v) => { dims.margin_top = encode_margin_i16(v); }
951 CssProperty::MarginRight(v) => { dims.margin_right = encode_margin_i16(v); }
952 CssProperty::MarginBottom(v) => { dims.margin_bottom = encode_margin_i16(v); }
953 CssProperty::MarginLeft(v) => { dims.margin_left = encode_margin_i16(v); }
954 CssProperty::BorderTopWidth(v) => { dims.border_top_width = encode_css_pixel_as_i16(v); }
955 CssProperty::BorderRightWidth(v) => { dims.border_right_width = encode_css_pixel_as_i16(v); }
956 CssProperty::BorderBottomWidth(v) => { dims.border_bottom_width = encode_css_pixel_as_i16(v); }
957 CssProperty::BorderLeftWidth(v) => { dims.border_left_width = encode_css_pixel_as_i16(v); }
958 CssProperty::Top(v) => { dims.top = encode_css_pixel_as_i16(v); }
959 CssProperty::Right(v) => { dims.right = encode_css_pixel_as_i16(v); }
960 CssProperty::Bottom(v) => { dims.bottom = encode_css_pixel_as_i16(v); }
961 CssProperty::Left(v) => { dims.left = encode_css_pixel_as_i16(v); }
962 CssProperty::FlexGrow(v) => {
963 if let Some(exact) = v.get_property() {
964 dims.flex_grow = encode_flex_u16(exact.inner.get());
965 }
966 }
967 CssProperty::FlexShrink(v) => {
968 if let Some(exact) = v.get_property() {
969 dims.flex_shrink = encode_flex_u16(exact.inner.get());
970 }
971 }
972
973 CssProperty::RowGap(v) => {
974 if let Some(g) = v.get_property() {
975 if g.inner.metric == SizeMetric::Px {
976 dims.row_gap = encode_resolved_px_i16(g.inner.number.get());
977 }
978 }
979 }
980 CssProperty::ColumnGap(v) => {
981 if let Some(g) = v.get_property() {
982 if g.inner.metric == SizeMetric::Px {
983 dims.column_gap = encode_resolved_px_i16(g.inner.number.get());
984 }
985 }
986 }
987 CssProperty::Gap(v) => {
988 if let Some(g) = v.get_property() {
989 if g.inner.metric == SizeMetric::Px {
990 let enc = encode_resolved_px_i16(g.inner.number.get());
991 dims.row_gap = enc;
992 dims.column_gap = enc;
993 }
994 }
995 }
996
997 CssProperty::GridColumn(v) => {
999 if let Some(gp) = v.get_property() {
1000 cold.grid_col_start = encode_grid_line(&gp.grid_start);
1001 cold.grid_col_end = encode_grid_line(&gp.grid_end);
1002 }
1003 }
1004 CssProperty::GridRow(v) => {
1005 if let Some(gp) = v.get_property() {
1006 cold.grid_row_start = encode_grid_line(&gp.grid_start);
1007 cold.grid_row_end = encode_grid_line(&gp.grid_end);
1008 }
1009 }
1010
1011 CssProperty::ZIndex(v) => {
1013 if let Some(exact) = v.get_property() {
1014 match exact {
1015 LayoutZIndex::Auto => cold.z_index = I16_AUTO,
1016 LayoutZIndex::Integer(z) => {
1017 cold.z_index = if *z >= i32::from(I16_SENTINEL_THRESHOLD) { I16_SENTINEL } else { *z as i16 };
1018 }
1019 }
1020 }
1021 }
1022 CssProperty::BorderTopStyle(v) => {
1023 if let Some(exact) = v.get_property() {
1024 let bs = u16::from(border_style_to_u8(exact.inner));
1025 cold.border_styles_packed = (cold.border_styles_packed & !0x000F) | bs;
1026 }
1027 }
1028 CssProperty::BorderRightStyle(v) => {
1029 if let Some(exact) = v.get_property() {
1030 let bs = u16::from(border_style_to_u8(exact.inner));
1031 cold.border_styles_packed = (cold.border_styles_packed & !0x00F0) | (bs << 4);
1032 }
1033 }
1034 CssProperty::BorderBottomStyle(v) => {
1035 if let Some(exact) = v.get_property() {
1036 let bs = u16::from(border_style_to_u8(exact.inner));
1037 cold.border_styles_packed = (cold.border_styles_packed & !0x0F00) | (bs << 8);
1038 }
1039 }
1040 CssProperty::BorderLeftStyle(v) => {
1041 if let Some(exact) = v.get_property() {
1042 let bs = u16::from(border_style_to_u8(exact.inner));
1043 cold.border_styles_packed = (cold.border_styles_packed & !0xF000) | (bs << 12);
1044 }
1045 }
1046 CssProperty::BorderTopColor(v) => {
1047 if let Some(c) = v.get_property() { cold.border_top_color = encode_color_u32(&c.inner); }
1048 }
1049 CssProperty::BorderRightColor(v) => {
1050 if let Some(c) = v.get_property() { cold.border_right_color = encode_color_u32(&c.inner); }
1051 }
1052 CssProperty::BorderBottomColor(v) => {
1053 if let Some(c) = v.get_property() { cold.border_bottom_color = encode_color_u32(&c.inner); }
1054 }
1055 CssProperty::BorderLeftColor(v) => {
1056 if let Some(c) = v.get_property() { cold.border_left_color = encode_color_u32(&c.inner); }
1057 }
1058 CssProperty::BorderSpacing(v) => {
1059 if let Some(spacing) = v.get_property() {
1060 if spacing.horizontal.metric == SizeMetric::Px {
1061 cold.border_spacing_h = encode_resolved_px_i16(spacing.horizontal.number.get());
1062 }
1063 if spacing.vertical.metric == SizeMetric::Px {
1064 cold.border_spacing_v = encode_resolved_px_i16(spacing.vertical.number.get());
1065 }
1066 }
1067 }
1068 CssProperty::TabSize(v) => { cold.tab_size = encode_css_pixel_as_i16(v); }
1069
1070 CssProperty::TextColor(v) => {
1072 if let Some(color) = v.get_property() {
1073 let c = &color.inner;
1074 text.text_color = (u32::from(c.r) << 24) | (u32::from(c.g) << 16) | (u32::from(c.b) << 8) | u32::from(c.a);
1075 }
1076 }
1077 CssProperty::FontFamily(v) => {
1078 if let Some(families) = v.get_property() {
1079 let mut hasher = DefaultHasher::new();
1080 families.hash(&mut hasher);
1081 let h = hasher.finish();
1082 let h = if h == 0 { 1 } else { h };
1083 text.font_family_hash = h;
1084 font_hash_map.insert(h, families.clone());
1085 }
1086 }
1087 CssProperty::LineHeight(v) => {
1088 if let Some(lh) = v.get_property() {
1089 let pct_x10 = (lh.inner.normalized() * 1000.0).round() as i32;
1090 if pct_x10 >= -32768 && pct_x10 < i32::from(I16_SENTINEL_THRESHOLD) {
1091 text.line_height = pct_x10 as i16;
1092 } else {
1093 text.line_height = I16_SENTINEL;
1094 }
1095 }
1096 }
1097 CssProperty::LetterSpacing(v) => { text.letter_spacing = encode_css_pixel_as_i16(v); }
1098 CssProperty::WordSpacing(v) => { text.word_spacing = encode_css_pixel_as_i16(v); }
1099 CssProperty::TextIndent(v) => { text.text_indent = encode_css_pixel_as_i16(v); }
1100
1101 CssProperty::BorderTopLeftRadius(v) => {
1103 if let Some(exact) = v.get_property() {
1104 if exact.inner.metric == SizeMetric::Px {
1105 cold.border_top_left_radius = encode_resolved_px_i16(exact.inner.number.get());
1106 }
1107 }
1108 }
1109 CssProperty::BorderTopRightRadius(v) => {
1110 if let Some(exact) = v.get_property() {
1111 if exact.inner.metric == SizeMetric::Px {
1112 cold.border_top_right_radius = encode_resolved_px_i16(exact.inner.number.get());
1113 }
1114 }
1115 }
1116 CssProperty::BorderBottomLeftRadius(v) => {
1117 if let Some(exact) = v.get_property() {
1118 if exact.inner.metric == SizeMetric::Px {
1119 cold.border_bottom_left_radius = encode_resolved_px_i16(exact.inner.number.get());
1120 }
1121 }
1122 }
1123 CssProperty::BorderBottomRightRadius(v) => {
1124 if let Some(exact) = v.get_property() {
1125 if exact.inner.metric == SizeMetric::Px {
1126 cold.border_bottom_right_radius = encode_resolved_px_i16(exact.inner.number.get());
1127 }
1128 }
1129 }
1130
1131 CssProperty::Opacity(v) => {
1133 if let Some(exact) = v.get_property() {
1134 let o = exact.inner.normalized().clamp(0.0, 1.0);
1135 let byte = (o * 254.0).round() as u8;
1136 cold.opacity = byte;
1138 }
1139 }
1140
1141 CssProperty::Transform(v) => {
1144 if v.get_property().is_some() { cold.hot_flags |= HOT_FLAG_HAS_TRANSFORM; }
1145 }
1146 CssProperty::TransformOrigin(v) => {
1147 if v.get_property().is_some() { cold.hot_flags |= HOT_FLAG_HAS_TRANSFORM_ORIGIN; }
1148 }
1149 CssProperty::BoxShadowTop(v)
1152 | CssProperty::BoxShadowBottom(v)
1153 | CssProperty::BoxShadowLeft(v)
1154 | CssProperty::BoxShadowRight(v) => {
1155 if v.get_property().is_some() { cold.hot_flags |= HOT_FLAG_HAS_BOX_SHADOW; }
1156 }
1157 CssProperty::TextDecoration(v) => {
1158 if v.get_property().is_some() { cold.hot_flags |= HOT_FLAG_HAS_TEXT_DECORATION; }
1159 }
1160 CssProperty::ScrollbarGutter(v) => {
1161 if let Some(exact) = v.get_property() {
1162 use azul_css::props::layout::overflow::StyleScrollbarGutter;
1163 let bits: u8 = match exact {
1164 StyleScrollbarGutter::Auto => SCROLLBAR_GUTTER_AUTO,
1165 StyleScrollbarGutter::Stable => SCROLLBAR_GUTTER_STABLE,
1166 StyleScrollbarGutter::StableBothEdges => SCROLLBAR_GUTTER_BOTH_EDGES,
1167 };
1168 cold.hot_flags = (cold.hot_flags & !HOT_FLAG_SCROLLBAR_GUTTER_MASK)
1169 | ((bits << HOT_FLAG_SCROLLBAR_GUTTER_SHIFT) & HOT_FLAG_SCROLLBAR_GUTTER_MASK);
1170 }
1171 }
1172 CssProperty::BackgroundContent(v) => {
1173 if v.get_property().is_some() { cold.hot_flags |= HOT_FLAG_HAS_BACKGROUND; }
1174 }
1175 CssProperty::ClipPath(v) => {
1176 if v.get_property().is_some() { cold.hot_flags |= HOT_FLAG_HAS_CLIP_PATH; }
1177 }
1178
1179 CssProperty::ScrollbarTrack(v) => {
1183 if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_SCROLLBAR_CSS; }
1184 }
1185 CssProperty::ScrollbarThumb(v) => {
1186 if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_SCROLLBAR_CSS; }
1187 }
1188 CssProperty::ScrollbarButton(v) => {
1189 if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_SCROLLBAR_CSS; }
1190 }
1191 CssProperty::ScrollbarCorner(v) => {
1192 if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_SCROLLBAR_CSS; }
1193 }
1194 CssProperty::ScrollbarWidth(v) => {
1195 if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_SCROLLBAR_CSS; }
1196 }
1197 CssProperty::ScrollbarColor(v) => {
1198 if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_SCROLLBAR_CSS; }
1199 }
1200 CssProperty::ScrollbarVisibility(v) => {
1201 if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_SCROLLBAR_CSS; }
1202 }
1203 CssProperty::ScrollbarFadeDelay(v) => {
1204 if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_SCROLLBAR_CSS; }
1205 }
1206 CssProperty::ScrollbarFadeDuration(v) => {
1207 if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_SCROLLBAR_CSS; }
1208 }
1209
1210 CssProperty::CounterReset(v) => {
1212 if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_COUNTER; }
1213 }
1214 CssProperty::CounterIncrement(v) => {
1215 if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_COUNTER; }
1216 }
1217 CssProperty::BreakBefore(v) | CssProperty::BreakAfter(v) => {
1219 if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_BREAK; }
1220 }
1221 CssProperty::TextOrientation(v) => {
1222 if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_TEXT_ORIENTATION; }
1223 }
1224 CssProperty::TextShadow(v) => {
1225 if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_TEXT_SHADOW; }
1226 }
1227 CssProperty::BackdropFilter(v) => {
1228 if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_BACKDROP_FILTER; }
1229 }
1230 CssProperty::Filter(v) => {
1231 if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_FILTER; }
1232 }
1233 CssProperty::MixBlendMode(v) => {
1234 if v.get_property().is_some() { cold.extra_flags |= EXTRA_FLAG_HAS_MIX_BLEND_MODE; }
1235 }
1236
1237 _ => {}
1239 }
1240}
1241
1242const fn update_dom_declared_flags(prop: &CssProperty, flags: &mut u32) {
1247 match prop {
1251 CssProperty::ShapeInside(v) => if v.get_property().is_some() { *flags |= DOM_HAS_SHAPE_INSIDE; }
1252 CssProperty::ShapeOutside(v) => if v.get_property().is_some() { *flags |= DOM_HAS_SHAPE_OUTSIDE; }
1253 CssProperty::TextJustify(v) => if v.get_property().is_some() { *flags |= DOM_HAS_TEXT_JUSTIFY; }
1254 CssProperty::TextIndent(v) => if v.get_property().is_some() { *flags |= DOM_HAS_TEXT_INDENT; }
1255 CssProperty::ColumnCount(v) => if v.get_property().is_some() { *flags |= DOM_HAS_COLUMN_COUNT; }
1256 CssProperty::ColumnGap(v) => if v.get_property().is_some() { *flags |= DOM_HAS_COLUMN_GAP; }
1257 CssProperty::ColumnWidth(v) => if v.get_property().is_some() { *flags |= DOM_HAS_COLUMN_WIDTH; }
1258 CssProperty::InitialLetter(v) => if v.get_property().is_some() { *flags |= DOM_HAS_INITIAL_LETTER; }
1259 CssProperty::InitialLetterAlign(v) => if v.get_property().is_some() { *flags |= DOM_HAS_INITIAL_LETTER_ALIGN; }
1260 CssProperty::LineClamp(v) => if v.get_property().is_some() { *flags |= DOM_HAS_LINE_CLAMP; }
1261 CssProperty::HangingPunctuation(v) => if v.get_property().is_some() { *flags |= DOM_HAS_HANGING_PUNCTUATION; }
1262 CssProperty::TextCombineUpright(v) => if v.get_property().is_some() { *flags |= DOM_HAS_TEXT_COMBINE_UPRIGHT; }
1263 CssProperty::ExclusionMargin(v) => if v.get_property().is_some() { *flags |= DOM_HAS_EXCLUSION_MARGIN; }
1264 CssProperty::ShapeMargin(v) => if v.get_property().is_some() { *flags |= DOM_HAS_SHAPE_MARGIN; }
1265 CssProperty::HyphenationLanguage(v) => if v.get_property().is_some() { *flags |= DOM_HAS_HYPHENATION_LANGUAGE; }
1266 CssProperty::UnicodeBidi(v) => if v.get_property().is_some() { *flags |= DOM_HAS_UNICODE_BIDI; }
1267 CssProperty::TextBoxTrim(v) => if v.get_property().is_some() { *flags |= DOM_HAS_TEXT_BOX_TRIM; }
1268 CssProperty::Hyphens(v) => if v.get_property().is_some() { *flags |= DOM_HAS_HYPHENS; }
1269 CssProperty::WordBreak(v) => if v.get_property().is_some() { *flags |= DOM_HAS_WORD_BREAK; }
1270 CssProperty::OverflowWrap(v) => if v.get_property().is_some() { *flags |= DOM_HAS_OVERFLOW_WRAP; }
1271 CssProperty::LineBreak(v) => if v.get_property().is_some() { *flags |= DOM_HAS_LINE_BREAK; }
1272 CssProperty::TextAlignLast(v) => if v.get_property().is_some() { *flags |= DOM_HAS_TEXT_ALIGN_LAST; }
1273 CssProperty::LineHeight(v) => if v.get_property().is_some() { *flags |= DOM_HAS_LINE_HEIGHT; }
1274 _ => {}
1275 }
1276}
1277
1278#[allow(clippy::cast_possible_truncation)]
1286const fn encode_grid_line(line: &azul_css::props::layout::grid::GridLine) -> i16 {
1287 use azul_css::props::layout::grid::GridLine;
1288 match line {
1289 GridLine::Auto => I16_AUTO,
1290 GridLine::Line(n) => {
1291 if *n >= -32000 && *n <= 32000 { *n as i16 } else { I16_SENTINEL }
1292 }
1293 GridLine::Span(n) => {
1294 if *n >= 1 && *n <= 32000 { -(*n as i16) } else { I16_SENTINEL }
1295 }
1296 GridLine::Named(_) => I16_SENTINEL,
1297 }
1298}
1299
1300fn encode_layout_width<T: LayoutWidthLike>(val: &CssPropertyValue<T>) -> u32 {
1302 match val {
1303 CssPropertyValue::Exact(w) => w.encode_compact_u32(),
1304 CssPropertyValue::Auto => U32_AUTO,
1305 CssPropertyValue::Initial => U32_INITIAL,
1306 CssPropertyValue::Inherit => U32_INHERIT,
1307 CssPropertyValue::None => U32_NONE,
1308 _ => U32_SENTINEL,
1309 }
1310}
1311
1312fn encode_layout_height<T: LayoutWidthLike>(val: &CssPropertyValue<T>) -> u32 {
1314 encode_layout_width(val)
1315}
1316
1317trait LayoutWidthLike {
1320 fn encode_compact_u32(&self) -> u32;
1321}
1322
1323impl LayoutWidthLike for LayoutWidth {
1324 fn encode_compact_u32(&self) -> u32 {
1325 match self {
1326 Self::Auto => U32_AUTO,
1327 Self::Px(pv) => encode_pixel_value_u32(pv),
1328 Self::MinContent => U32_MIN_CONTENT,
1329 Self::MaxContent => U32_MAX_CONTENT,
1330 Self::FitContent(_) | Self::Calc(_) => U32_SENTINEL,
1332 }
1333 }
1334}
1335
1336impl LayoutWidthLike for LayoutHeight {
1337 fn encode_compact_u32(&self) -> u32 {
1338 match self {
1339 Self::Auto => U32_AUTO,
1340 Self::Px(pv) => encode_pixel_value_u32(pv),
1341 Self::MinContent => U32_MIN_CONTENT,
1342 Self::MaxContent => U32_MAX_CONTENT,
1343 Self::FitContent(_) | Self::Calc(_) => U32_SENTINEL,
1345 }
1346 }
1347}
1348
1349fn encode_pixel_prop<T: HasInnerPixelValue>(val: &CssPropertyValue<T>) -> u32 {
1351 match val {
1352 CssPropertyValue::Exact(inner) => encode_pixel_value_u32(&inner.get_inner_pixel()),
1353 CssPropertyValue::Auto => U32_AUTO,
1354 CssPropertyValue::Initial => U32_INITIAL,
1355 CssPropertyValue::Inherit => U32_INHERIT,
1356 CssPropertyValue::None => U32_NONE,
1357 _ => U32_SENTINEL,
1358 }
1359}
1360
1361trait HasInnerPixelValue {
1363 fn get_inner_pixel(&self) -> azul_css::props::basic::pixel::PixelValue;
1364}
1365
1366macro_rules! impl_has_inner_pixel {
1367 ($($ty:ty),*) => {
1368 $(
1369 impl HasInnerPixelValue for $ty {
1370 fn get_inner_pixel(&self) -> azul_css::props::basic::pixel::PixelValue {
1371 self.inner
1372 }
1373 }
1374 )*
1375 };
1376}
1377
1378impl_has_inner_pixel!(
1379 azul_css::props::layout::dimensions::LayoutMinWidth,
1380 azul_css::props::layout::dimensions::LayoutMaxWidth,
1381 azul_css::props::layout::dimensions::LayoutMinHeight,
1382 azul_css::props::layout::dimensions::LayoutMaxHeight,
1383 azul_css::props::basic::font::StyleFontSize,
1384 azul_css::props::layout::spacing::LayoutPaddingTop,
1385 azul_css::props::layout::spacing::LayoutPaddingRight,
1386 azul_css::props::layout::spacing::LayoutPaddingBottom,
1387 azul_css::props::layout::spacing::LayoutPaddingLeft,
1388 azul_css::props::layout::spacing::LayoutMarginTop,
1389 azul_css::props::layout::spacing::LayoutMarginRight,
1390 azul_css::props::layout::spacing::LayoutMarginBottom,
1391 azul_css::props::layout::spacing::LayoutMarginLeft,
1392 azul_css::props::style::border::LayoutBorderTopWidth,
1393 azul_css::props::style::border::LayoutBorderRightWidth,
1394 azul_css::props::style::border::LayoutBorderBottomWidth,
1395 azul_css::props::style::border::LayoutBorderLeftWidth,
1396 azul_css::props::layout::position::LayoutTop,
1397 azul_css::props::layout::position::LayoutRight,
1398 azul_css::props::layout::position::LayoutInsetBottom,
1399 azul_css::props::layout::position::LayoutLeft,
1400 azul_css::props::style::text::StyleLetterSpacing,
1401 azul_css::props::style::text::StyleWordSpacing,
1402 azul_css::props::style::text::StyleTextIndent,
1403 azul_css::props::style::text::StyleTabSize
1404);
1405
1406fn encode_css_pixel_as_i16<T: HasInnerPixelValue>(val: &CssPropertyValue<T>) -> i16 {
1409 let mapped = match val {
1410 CssPropertyValue::Exact(inner) => CssPropertyValue::Exact(inner.get_inner_pixel()),
1411 CssPropertyValue::Auto => CssPropertyValue::Auto,
1412 CssPropertyValue::Initial => CssPropertyValue::Initial,
1413 CssPropertyValue::Inherit => CssPropertyValue::Inherit,
1414 CssPropertyValue::None => CssPropertyValue::None,
1415 _ => return I16_SENTINEL,
1416 };
1417 azul_css::compact_cache::encode_css_pixel_as_i16(&mapped)
1418}
1419
1420fn encode_margin_i16<T: HasInnerPixelValue>(val: &CssPropertyValue<T>) -> i16 {
1422 encode_css_pixel_as_i16(val)
1423}
1424
1425fn encode_flex_basis(val: &CssPropertyValue<LayoutFlexBasis>) -> u32 {
1427 match val {
1428 CssPropertyValue::Exact(fb) => match fb {
1429 LayoutFlexBasis::Auto => U32_AUTO,
1430 LayoutFlexBasis::Exact(pv) => encode_pixel_value_u32(pv),
1431 },
1432 CssPropertyValue::Auto => U32_AUTO,
1433 CssPropertyValue::Initial => U32_INITIAL,
1434 CssPropertyValue::Inherit => U32_INHERIT,
1435 CssPropertyValue::None => U32_NONE,
1436 _ => U32_SENTINEL,
1437 }
1438}
1439
1440#[cfg(test)]
1441mod audit_tests {
1442 use super::resolve_font_size_to_px;
1443 use crate::dom::NodeId;
1444 use azul_css::compact_cache::{
1445 decode_pixel_value_u32, encode_pixel_value_u32, CompactNodeProps,
1446 };
1447 use azul_css::props::basic::pixel::PixelValue;
1448
1449 #[test]
1451 fn resolve_font_size_em_from_parent() {
1452 let mut dims = vec![CompactNodeProps::default(); 2];
1453 dims[0].font_size = encode_pixel_value_u32(&PixelValue::px(20.0));
1454 dims[1].font_size = encode_pixel_value_u32(&PixelValue::em(2.0));
1455 resolve_font_size_to_px(&mut dims, 1, Some(NodeId::new(0)));
1456 let pv = decode_pixel_value_u32(dims[1].font_size).unwrap();
1457 assert!((pv.number.get() - 40.0).abs() < 0.01, "got {}", pv.number.get());
1458 }
1459
1460 #[test]
1462 fn resolve_font_size_root_em_uses_default() {
1463 let mut dims = vec![CompactNodeProps::default()];
1464 dims[0].font_size = encode_pixel_value_u32(&PixelValue::em(2.0));
1465 resolve_font_size_to_px(&mut dims, 0, None);
1466 let pv = decode_pixel_value_u32(dims[0].font_size).unwrap();
1467 assert!((pv.number.get() - 32.0).abs() < 0.01, "got {}", pv.number.get());
1468 }
1469
1470 #[test]
1473 fn resolve_font_size_rem_reads_root() {
1474 let mut dims = vec![CompactNodeProps::default(); 2];
1475 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)));
1478 let pv = decode_pixel_value_u32(dims[1].font_size).unwrap();
1479 assert!((pv.number.get() - 30.0).abs() < 0.01, "got {}", pv.number.get());
1480 }
1481}
1482
1483#[cfg(test)]
1497#[allow(
1498 clippy::float_cmp,
1499 clippy::unreadable_literal,
1500 clippy::too_many_lines,
1501 clippy::cast_lossless
1502)]
1503mod autotest_generated {
1504 use super::*;
1505
1506 use alloc::collections::BTreeMap;
1507
1508 use crate::dom::NodeType;
1509 use crate::styled_dom::NodeHierarchyItem;
1510 use azul_css::props::basic::color::ColorU;
1511 use azul_css::props::basic::font::{StyleFontFamily, StyleFontFamilyVec};
1512 use azul_css::props::basic::length::{FloatValue, PercentageValue};
1513 use azul_css::props::basic::pixel::PixelValue;
1514 use azul_css::props::layout::dimensions::LayoutMinWidth;
1515 use azul_css::props::layout::display::LayoutDisplay;
1516 use azul_css::props::layout::flex::{LayoutFlexGrow, LayoutFlexShrink};
1517 use azul_css::props::layout::grid::{GridLine, GridPlacement, LayoutGap, NamedGridLine};
1518 use azul_css::props::layout::overflow::StyleScrollbarGutter;
1519 use azul_css::props::layout::position::LayoutPosition;
1520 use azul_css::props::layout::spacing::{LayoutMarginTop, LayoutPaddingTop};
1521 use azul_css::props::layout::table::StyleBorderCollapse;
1522 use azul_css::props::style::border::{BorderStyle, StyleBorderTopStyle};
1523 use azul_css::props::style::effects::StyleOpacity;
1524 use azul_css::props::style::text::{
1525 StyleLineHeight, StyleTextColor, StyleTextDecoration, StyleTextIndent,
1526 };
1527
1528 struct Sink {
1535 tier1: u64,
1536 dims: CompactNodeProps,
1537 cold: CompactNodePropsCold,
1538 text: CompactTextProps,
1539 fonts: BTreeMap<u64, StyleFontFamilyVec>,
1540 }
1541
1542 impl Sink {
1543 fn new() -> Self {
1544 Self {
1545 tier1: 0,
1546 dims: CompactNodeProps::default(),
1547 cold: CompactNodePropsCold::default(),
1548 text: CompactTextProps::default(),
1549 fonts: BTreeMap::new(),
1550 }
1551 }
1552
1553 fn apply(&mut self, prop: &CssProperty) {
1554 apply_css_property_to_compact(
1555 prop,
1556 &mut self.tier1,
1557 &mut self.dims,
1558 &mut self.cold,
1559 &mut self.text,
1560 &mut self.fonts,
1561 );
1562 }
1563
1564 fn ua(&mut self, node_type: &NodeType) {
1565 apply_ua_css_to_compact(
1566 node_type,
1567 &mut self.tier1,
1568 &mut self.dims,
1569 &mut self.cold,
1570 &mut self.text,
1571 &mut self.fonts,
1572 );
1573 }
1574
1575 fn snapshot(&self) -> (u64, CompactNodeProps, CompactNodePropsCold, CompactTextProps) {
1576 (self.tier1, self.dims, self.cold, self.text)
1577 }
1578 }
1579
1580 fn div_nodes(n: usize) -> Vec<NodeData> {
1581 (0..n).map(|_| NodeData::create_node(NodeType::Div)).collect()
1582 }
1583
1584 fn linear_hierarchy(n: usize) -> Vec<NodeHierarchyItem> {
1587 (0..n)
1588 .map(|i| NodeHierarchyItem {
1589 parent: i, previous_sibling: 0,
1591 next_sibling: 0,
1592 last_child: if i + 1 < n { i + 2 } else { 0 },
1593 })
1594 .collect()
1595 }
1596
1597 fn padding(px: f32) -> CssPropertyValue<LayoutPaddingTop> {
1598 CssPropertyValue::Exact(LayoutPaddingTop { inner: PixelValue::px(px) })
1599 }
1600
1601 #[test]
1606 fn grid_line_auto_and_named_map_to_their_sentinels() {
1607 assert_eq!(encode_grid_line(&GridLine::Auto), I16_AUTO);
1608 let named = GridLine::Named(NamedGridLine {
1609 grid_line_name: "sidebar".into(),
1610 span_count: 0,
1611 });
1612 assert_eq!(encode_grid_line(&named), I16_SENTINEL);
1613 }
1614
1615 #[test]
1616 fn grid_line_number_boundaries_saturate_instead_of_truncating() {
1617 assert_eq!(encode_grid_line(&GridLine::Line(0)), 0);
1618 assert_eq!(encode_grid_line(&GridLine::Line(1)), 1);
1619 assert_eq!(encode_grid_line(&GridLine::Line(-1)), -1);
1620 assert_eq!(encode_grid_line(&GridLine::Line(32_000)), 32_000);
1621 assert_eq!(encode_grid_line(&GridLine::Line(-32_000)), -32_000);
1622 assert_eq!(encode_grid_line(&GridLine::Line(32_001)), I16_SENTINEL);
1624 assert_eq!(encode_grid_line(&GridLine::Line(-32_001)), I16_SENTINEL);
1625 assert_eq!(encode_grid_line(&GridLine::Line(i32::MAX)), I16_SENTINEL);
1626 assert_eq!(encode_grid_line(&GridLine::Line(i32::MIN)), I16_SENTINEL);
1627 }
1628
1629 #[test]
1630 fn grid_line_span_boundaries_and_nonsense_spans() {
1631 assert_eq!(encode_grid_line(&GridLine::Span(1)), -1);
1632 assert_eq!(encode_grid_line(&GridLine::Span(32_000)), -32_000);
1633 assert_eq!(encode_grid_line(&GridLine::Span(0)), I16_SENTINEL);
1636 assert_eq!(encode_grid_line(&GridLine::Span(-1)), I16_SENTINEL);
1637 assert_eq!(encode_grid_line(&GridLine::Span(32_001)), I16_SENTINEL);
1638 assert_eq!(encode_grid_line(&GridLine::Span(i32::MAX)), I16_SENTINEL);
1639 assert_eq!(encode_grid_line(&GridLine::Span(i32::MIN)), I16_SENTINEL);
1640 }
1641
1642 #[test]
1643 fn grid_line_in_range_values_never_alias_the_sentinel_band() {
1644 for n in [-32_000i32, -1_000, -1, 0, 1, 1_000, 32_000] {
1647 let e = encode_grid_line(&GridLine::Line(n));
1648 assert!(
1649 e < I16_SENTINEL_THRESHOLD,
1650 "Line({n}) encoded into the sentinel band as {e}"
1651 );
1652 }
1653 for n in [1i32, 2, 1_000, 32_000] {
1654 let e = encode_grid_line(&GridLine::Span(n));
1655 assert!(e < 0, "Span({n}) must encode as a negative value, got {e}");
1656 assert!(
1657 e < I16_SENTINEL_THRESHOLD,
1658 "Span({n}) encoded into the sentinel band as {e}"
1659 );
1660 }
1661 }
1662
1663 #[test]
1668 fn layout_width_keywords_map_to_distinct_sentinels() {
1669 let auto: CssPropertyValue<LayoutWidth> = CssPropertyValue::Auto;
1670 let none: CssPropertyValue<LayoutWidth> = CssPropertyValue::None;
1671 let initial: CssPropertyValue<LayoutWidth> = CssPropertyValue::Initial;
1672 let inherit: CssPropertyValue<LayoutWidth> = CssPropertyValue::Inherit;
1673 assert_eq!(encode_layout_width(&auto), U32_AUTO);
1674 assert_eq!(encode_layout_width(&none), U32_NONE);
1675 assert_eq!(encode_layout_width(&initial), U32_INITIAL);
1676 assert_eq!(encode_layout_width(&inherit), U32_INHERIT);
1677 }
1678
1679 #[test]
1680 fn layout_width_revert_and_unset_fall_back_to_the_overflow_sentinel() {
1681 let revert: CssPropertyValue<LayoutWidth> = CssPropertyValue::Revert;
1684 let unset: CssPropertyValue<LayoutWidth> = CssPropertyValue::Unset;
1685 assert_eq!(encode_layout_width(&revert), U32_SENTINEL);
1686 assert_eq!(encode_layout_width(&unset), U32_SENTINEL);
1687 assert_eq!(encode_layout_height(&revert), U32_SENTINEL);
1688 assert_eq!(encode_layout_height(&unset), U32_SENTINEL);
1689 }
1690
1691 #[test]
1692 fn layout_width_exact_keyword_variants() {
1693 assert_eq!(
1694 encode_layout_width(&CssPropertyValue::Exact(LayoutWidth::Auto)),
1695 U32_AUTO
1696 );
1697 assert_eq!(
1698 encode_layout_width(&CssPropertyValue::Exact(LayoutWidth::MinContent)),
1699 U32_MIN_CONTENT
1700 );
1701 assert_eq!(
1702 encode_layout_width(&CssPropertyValue::Exact(LayoutWidth::MaxContent)),
1703 U32_MAX_CONTENT
1704 );
1705 assert_eq!(
1707 encode_layout_width(&CssPropertyValue::Exact(LayoutWidth::FitContent(
1708 PixelValue::px(10.0)
1709 ))),
1710 U32_SENTINEL
1711 );
1712 }
1713
1714 #[test]
1715 fn layout_width_px_round_trips() {
1716 for px in [0.0f32, 0.5, 1.0, 100.0, 1234.567, -50.0] {
1717 let enc =
1718 encode_layout_width(&CssPropertyValue::Exact(LayoutWidth::Px(PixelValue::px(px))));
1719 let dec = decode_pixel_value_u32(enc)
1720 .expect("an in-range px value must not encode to a sentinel");
1721 assert_eq!(dec.metric, SizeMetric::Px);
1722 assert!(
1723 (dec.number.get() - px).abs() < 0.002,
1724 "round-trip of {px}px produced {}px",
1725 dec.number.get()
1726 );
1727 }
1728 }
1729
1730 #[test]
1731 fn layout_width_extreme_values_saturate_to_the_overflow_sentinel() {
1732 for px in [
1735 1.0e9f32,
1736 -1.0e9,
1737 f32::MAX,
1738 f32::MIN,
1739 f32::INFINITY,
1740 f32::NEG_INFINITY,
1741 ] {
1742 let enc =
1743 encode_layout_width(&CssPropertyValue::Exact(LayoutWidth::Px(PixelValue::px(px))));
1744 assert_eq!(
1745 enc, U32_SENTINEL,
1746 "width {px}px should overflow to U32_SENTINEL, got {enc:#x}"
1747 );
1748 }
1749 }
1750
1751 #[test]
1752 fn layout_width_nan_degrades_to_zero_without_panicking() {
1753 let enc = encode_layout_width(&CssPropertyValue::Exact(LayoutWidth::Px(PixelValue::px(
1756 f32::NAN,
1757 ))));
1758 let dec = decode_pixel_value_u32(enc).expect("NaN must degrade to a value, not a sentinel");
1759 assert!(dec.number.get().is_finite());
1760 assert_eq!(dec.number.get(), 0.0);
1761 }
1762
1763 #[test]
1764 fn layout_height_never_diverges_from_layout_width() {
1765 let vals = [
1766 CssPropertyValue::Exact(LayoutWidth::Auto),
1767 CssPropertyValue::Exact(LayoutWidth::MinContent),
1768 CssPropertyValue::Exact(LayoutWidth::MaxContent),
1769 CssPropertyValue::Exact(LayoutWidth::Px(PixelValue::px(42.0))),
1770 CssPropertyValue::Exact(LayoutWidth::Px(PixelValue::px(1.0e9))),
1771 CssPropertyValue::Unset,
1772 ];
1773 for v in &vals {
1774 assert_eq!(encode_layout_width(v), encode_layout_height(v));
1775 }
1776 }
1777
1778 #[test]
1783 fn pixel_prop_keywords_map_to_distinct_sentinels() {
1784 let auto: CssPropertyValue<LayoutMinWidth> = CssPropertyValue::Auto;
1785 let none: CssPropertyValue<LayoutMinWidth> = CssPropertyValue::None;
1786 let initial: CssPropertyValue<LayoutMinWidth> = CssPropertyValue::Initial;
1787 let inherit: CssPropertyValue<LayoutMinWidth> = CssPropertyValue::Inherit;
1788 let revert: CssPropertyValue<LayoutMinWidth> = CssPropertyValue::Revert;
1789 let unset: CssPropertyValue<LayoutMinWidth> = CssPropertyValue::Unset;
1790 assert_eq!(encode_pixel_prop(&auto), U32_AUTO);
1791 assert_eq!(encode_pixel_prop(&none), U32_NONE);
1792 assert_eq!(encode_pixel_prop(&initial), U32_INITIAL);
1793 assert_eq!(encode_pixel_prop(&inherit), U32_INHERIT);
1794 assert_eq!(encode_pixel_prop(&revert), U32_SENTINEL);
1795 assert_eq!(encode_pixel_prop(&unset), U32_SENTINEL);
1796 }
1797
1798 #[test]
1799 fn pixel_prop_round_trips_value_and_metric() {
1800 for pv in [
1801 PixelValue::px(50.0),
1802 PixelValue::em(1.5),
1803 PixelValue::percent(80.0),
1804 PixelValue::pt(12.0),
1805 PixelValue::rem(2.0),
1806 ] {
1807 let enc = encode_pixel_prop(&CssPropertyValue::Exact(LayoutMinWidth { inner: pv }));
1808 let dec = decode_pixel_value_u32(enc).expect("must round-trip");
1809 assert_eq!(dec.metric, pv.metric, "metric lost in the round-trip");
1810 assert!(
1811 (dec.number.get() - pv.number.get()).abs() < 0.002,
1812 "value lost in the round-trip: {} -> {}",
1813 pv.number.get(),
1814 dec.number.get()
1815 );
1816 }
1817 }
1818
1819 #[test]
1820 fn pixel_prop_overflow_saturates() {
1821 let enc = encode_pixel_prop(&CssPropertyValue::Exact(LayoutMinWidth {
1822 inner: PixelValue::px(1.0e9),
1823 }));
1824 assert_eq!(enc, U32_SENTINEL);
1825 }
1826
1827 #[test]
1828 fn pixel_prop_exact_value_never_aliases_a_semantic_sentinel() {
1829 for metric in [SizeMetric::Vh, SizeMetric::Vmin, SizeMetric::Vmax] {
1842 let pv = PixelValue::from_metric(metric, -0.001);
1843 let enc = encode_pixel_prop(&CssPropertyValue::Exact(LayoutMinWidth { inner: pv }));
1844 assert!(
1845 enc == U32_SENTINEL || enc < U32_SENTINEL_THRESHOLD,
1846 "an Exact viewport length encoded to {enc:#x}, which aliases a semantic sentinel",
1847 );
1848 }
1849 }
1850
1851 #[test]
1856 fn css_pixel_i16_scales_by_ten() {
1857 assert_eq!(encode_css_pixel_as_i16(&padding(0.0)), 0);
1858 assert_eq!(encode_css_pixel_as_i16(&padding(10.5)), 105);
1859 assert_eq!(encode_css_pixel_as_i16(&padding(-10.5)), -105);
1860 }
1861
1862 #[test]
1863 fn css_pixel_i16_boundaries() {
1864 assert_eq!(encode_css_pixel_as_i16(&padding(3276.3)), 32_763);
1866 assert_eq!(encode_css_pixel_as_i16(&padding(3276.4)), I16_SENTINEL);
1868 assert_eq!(encode_css_pixel_as_i16(&padding(-3276.8)), -32_768);
1870 assert_eq!(encode_css_pixel_as_i16(&padding(-3276.9)), I16_SENTINEL);
1871 }
1872
1873 #[test]
1874 fn css_pixel_i16_non_px_units_need_the_slow_path() {
1875 let em = CssPropertyValue::Exact(LayoutPaddingTop { inner: PixelValue::em(2.0) });
1876 let pct = CssPropertyValue::Exact(LayoutPaddingTop {
1877 inner: PixelValue::percent(50.0),
1878 });
1879 assert_eq!(encode_css_pixel_as_i16(&em), I16_SENTINEL);
1880 assert_eq!(encode_css_pixel_as_i16(&pct), I16_SENTINEL);
1881 }
1882
1883 #[test]
1884 fn css_pixel_i16_keywords_are_distinguishable() {
1885 let auto: CssPropertyValue<LayoutPaddingTop> = CssPropertyValue::Auto;
1886 let initial: CssPropertyValue<LayoutPaddingTop> = CssPropertyValue::Initial;
1887 let inherit: CssPropertyValue<LayoutPaddingTop> = CssPropertyValue::Inherit;
1888 let none: CssPropertyValue<LayoutPaddingTop> = CssPropertyValue::None;
1889 let revert: CssPropertyValue<LayoutPaddingTop> = CssPropertyValue::Revert;
1890 let unset: CssPropertyValue<LayoutPaddingTop> = CssPropertyValue::Unset;
1891 assert_eq!(encode_css_pixel_as_i16(&auto), I16_AUTO);
1892 assert_eq!(encode_css_pixel_as_i16(&initial), I16_INITIAL);
1893 assert_eq!(encode_css_pixel_as_i16(&inherit), I16_INHERIT);
1894 assert_eq!(encode_css_pixel_as_i16(&none), I16_SENTINEL);
1896 assert_eq!(encode_css_pixel_as_i16(&revert), I16_SENTINEL);
1897 assert_eq!(encode_css_pixel_as_i16(&unset), I16_SENTINEL);
1898 }
1899
1900 #[test]
1901 fn css_pixel_i16_nan_and_infinity_are_safe() {
1902 assert_eq!(encode_css_pixel_as_i16(&padding(f32::NAN)), 0);
1903 assert_eq!(encode_css_pixel_as_i16(&padding(f32::INFINITY)), I16_SENTINEL);
1904 assert_eq!(
1905 encode_css_pixel_as_i16(&padding(f32::NEG_INFINITY)),
1906 I16_SENTINEL
1907 );
1908 assert_eq!(encode_css_pixel_as_i16(&padding(f32::MAX)), I16_SENTINEL);
1909 assert_eq!(encode_css_pixel_as_i16(&padding(f32::MIN)), I16_SENTINEL);
1910 }
1911
1912 #[test]
1913 fn css_pixel_i16_exact_value_never_aliases_a_keyword_sentinel() {
1914 for px in [
1917 -3276.8f32, -100.0, -0.1, 0.0, 0.1, 100.0, 3276.3, 1.0e9, -1.0e9,
1918 ] {
1919 let e = encode_css_pixel_as_i16(&padding(px));
1920 assert!(
1921 e != I16_AUTO && e != I16_INHERIT && e != I16_INITIAL,
1922 "{px}px aliased a keyword sentinel ({e})"
1923 );
1924 }
1925 }
1926
1927 #[test]
1928 fn margin_i16_keeps_auto_and_otherwise_matches_the_pixel_encoder() {
1929 let auto: CssPropertyValue<LayoutMarginTop> = CssPropertyValue::Auto;
1930 assert_eq!(encode_margin_i16(&auto), I16_AUTO);
1931 for px in [-50.0f32, 0.0, 12.5, 3276.3, 5.0e9, f32::NAN] {
1932 let m = CssPropertyValue::Exact(LayoutMarginTop { inner: PixelValue::px(px) });
1933 assert_eq!(encode_margin_i16(&m), encode_css_pixel_as_i16(&padding(px)));
1934 }
1935 }
1936
1937 #[test]
1942 fn flex_basis_all_variants() {
1943 assert_eq!(
1944 encode_flex_basis(&CssPropertyValue::Exact(LayoutFlexBasis::Auto)),
1945 U32_AUTO
1946 );
1947 let enc = encode_flex_basis(&CssPropertyValue::Exact(LayoutFlexBasis::Exact(
1948 PixelValue::px(120.0),
1949 )));
1950 let dec = decode_pixel_value_u32(enc).expect("px flex-basis must round-trip");
1951 assert!((dec.number.get() - 120.0).abs() < 0.002);
1952
1953 assert_eq!(encode_flex_basis(&CssPropertyValue::Auto), U32_AUTO);
1954 assert_eq!(encode_flex_basis(&CssPropertyValue::None), U32_NONE);
1955 assert_eq!(encode_flex_basis(&CssPropertyValue::Initial), U32_INITIAL);
1956 assert_eq!(encode_flex_basis(&CssPropertyValue::Inherit), U32_INHERIT);
1957 assert_eq!(encode_flex_basis(&CssPropertyValue::Revert), U32_SENTINEL);
1958 assert_eq!(encode_flex_basis(&CssPropertyValue::Unset), U32_SENTINEL);
1959 }
1960
1961 #[test]
1962 fn flex_basis_overflow_saturates() {
1963 assert_eq!(
1964 encode_flex_basis(&CssPropertyValue::Exact(LayoutFlexBasis::Exact(
1965 PixelValue::px(1.0e9)
1966 ))),
1967 U32_SENTINEL
1968 );
1969 assert_eq!(
1970 encode_flex_basis(&CssPropertyValue::Exact(LayoutFlexBasis::Exact(
1971 PixelValue::px(f32::INFINITY)
1972 ))),
1973 U32_SENTINEL
1974 );
1975 }
1976
1977 fn text_indent_prop() -> CssProperty {
1982 CssProperty::TextIndent(CssPropertyValue::Exact(StyleTextIndent::default()))
1983 }
1984
1985 fn line_height_prop(pct: f32) -> CssProperty {
1986 CssProperty::LineHeight(CssPropertyValue::Exact(StyleLineHeight {
1987 inner: PercentageValue::new(pct),
1988 }))
1989 }
1990
1991 #[test]
1992 fn dom_flags_set_the_right_bit_from_zero() {
1993 let mut flags = 0u32;
1994 update_dom_declared_flags(&text_indent_prop(), &mut flags);
1995 assert_eq!(flags, DOM_HAS_TEXT_INDENT);
1996
1997 let mut flags2 = 0u32;
1998 update_dom_declared_flags(&line_height_prop(150.0), &mut flags2);
1999 assert_eq!(flags2, DOM_HAS_LINE_HEIGHT);
2000 }
2001
2002 #[test]
2003 fn dom_flags_only_ever_or_never_clear() {
2004 let mut flags = u32::MAX;
2006 update_dom_declared_flags(&text_indent_prop(), &mut flags);
2007 update_dom_declared_flags(&line_height_prop(150.0), &mut flags);
2008 update_dom_declared_flags(
2009 &CssProperty::Width(CssPropertyValue::Exact(LayoutWidth::px(10.0))),
2010 &mut flags,
2011 );
2012 assert_eq!(flags, u32::MAX);
2013 }
2014
2015 #[test]
2016 fn dom_flags_accumulate_and_are_idempotent() {
2017 let mut flags = 0u32;
2018 update_dom_declared_flags(&text_indent_prop(), &mut flags);
2019 update_dom_declared_flags(&line_height_prop(150.0), &mut flags);
2020 let after_two = flags;
2021 assert_eq!(after_two, DOM_HAS_TEXT_INDENT | DOM_HAS_LINE_HEIGHT);
2022 update_dom_declared_flags(&text_indent_prop(), &mut flags);
2024 update_dom_declared_flags(&line_height_prop(150.0), &mut flags);
2025 assert_eq!(flags, after_two);
2026 }
2027
2028 #[test]
2029 fn dom_flags_are_not_set_for_a_valueless_property() {
2030 let mut flags = 0u32;
2033 update_dom_declared_flags(&CssProperty::LineHeight(CssPropertyValue::Initial), &mut flags);
2034 update_dom_declared_flags(&CssProperty::TextIndent(CssPropertyValue::Auto), &mut flags);
2035 update_dom_declared_flags(&CssProperty::TextIndent(CssPropertyValue::Unset), &mut flags);
2036 assert_eq!(flags, 0);
2037 }
2038
2039 #[test]
2040 fn dom_flags_ignore_unrelated_properties() {
2041 let mut flags = 0u32;
2042 update_dom_declared_flags(
2043 &CssProperty::Width(CssPropertyValue::Exact(LayoutWidth::px(10.0))),
2044 &mut flags,
2045 );
2046 update_dom_declared_flags(
2047 &CssProperty::ZIndex(CssPropertyValue::Exact(LayoutZIndex::Integer(3))),
2048 &mut flags,
2049 );
2050 assert_eq!(flags, 0);
2051 }
2052
2053 #[test]
2058 fn apply_tier1_fields_do_not_bleed_into_each_other() {
2059 let mut s = Sink::new();
2060 s.apply(&CssProperty::Display(CssPropertyValue::Exact(
2061 LayoutDisplay::InlineBlock,
2062 )));
2063 s.apply(&CssProperty::Position(CssPropertyValue::Exact(
2064 LayoutPosition::Absolute,
2065 )));
2066 s.apply(&CssProperty::BorderCollapse(CssPropertyValue::Exact(
2068 StyleBorderCollapse::Collapse,
2069 )));
2070
2071 assert_eq!(
2072 (s.tier1 >> DISPLAY_SHIFT) & DISPLAY_MASK,
2073 u64::from(layout_display_to_u8(LayoutDisplay::InlineBlock))
2074 );
2075 assert_eq!(
2076 (s.tier1 >> POSITION_SHIFT) & POSITION_MASK,
2077 u64::from(layout_position_to_u8(LayoutPosition::Absolute))
2078 );
2079 assert_eq!(
2080 (s.tier1 >> BORDER_COLLAPSE_SHIFT) & BORDER_COLLAPSE_MASK,
2081 u64::from(border_collapse_to_u8(StyleBorderCollapse::Collapse))
2082 );
2083
2084 let known = (DISPLAY_MASK << DISPLAY_SHIFT)
2085 | (POSITION_MASK << POSITION_SHIFT)
2086 | (BORDER_COLLAPSE_MASK << BORDER_COLLAPSE_SHIFT);
2087 assert_eq!(
2088 s.tier1 & !known,
2089 0,
2090 "tier1 = {:#x} has bits set outside the three fields that were written",
2091 s.tier1
2092 );
2093 }
2094
2095 #[test]
2096 fn apply_tier1_overwrite_clears_only_its_own_field() {
2097 let mut s = Sink::new();
2100 s.tier1 = u64::MAX;
2101 s.apply(&CssProperty::Display(CssPropertyValue::Exact(
2102 LayoutDisplay::Block,
2103 )));
2104 assert_eq!(
2105 (s.tier1 >> DISPLAY_SHIFT) & DISPLAY_MASK,
2106 u64::from(layout_display_to_u8(LayoutDisplay::Block))
2107 );
2108 let others = !(DISPLAY_MASK << DISPLAY_SHIFT);
2109 assert_eq!(
2110 s.tier1 & others,
2111 u64::MAX & others,
2112 "neighbouring tier-1 fields were clobbered"
2113 );
2114 }
2115
2116 #[test]
2117 fn apply_tier1_ignores_a_valueless_property() {
2118 let mut s = Sink::new();
2119 s.apply(&CssProperty::Display(CssPropertyValue::Inherit));
2120 assert_eq!(s.tier1, 0, "`display: inherit` has no Exact payload to encode");
2121 }
2122
2123 #[test]
2128 fn apply_width_round_trips_and_touches_nothing_else() {
2129 let mut s = Sink::new();
2130 let before_cold = s.cold;
2131 let before_text = s.text;
2132 s.apply(&CssProperty::Width(CssPropertyValue::Exact(LayoutWidth::Px(
2133 PixelValue::px(320.0),
2134 ))));
2135 let dec = decode_pixel_value_u32(s.dims.width).expect("width must round-trip");
2136 assert!((dec.number.get() - 320.0).abs() < 0.002);
2137 assert_eq!(s.tier1, 0, "a tier-2 property must not touch the tier-1 bitfield");
2138 assert_eq!(s.cold, before_cold, "a tier-2 property must not touch tier-2 cold");
2139 assert_eq!(s.text, before_text, "a tier-2 property must not touch tier-2b text");
2140 assert!(s.fonts.is_empty());
2141 }
2142
2143 #[test]
2144 fn apply_flex_grow_saturates_and_rejects_negatives() {
2145 let mut s = Sink::new();
2146 s.apply(&CssProperty::FlexGrow(CssPropertyValue::Exact(LayoutFlexGrow {
2147 inner: FloatValue::new(2.5),
2148 })));
2149 assert_eq!(s.dims.flex_grow, 250);
2150
2151 let mut neg = Sink::new();
2153 neg.apply(&CssProperty::FlexGrow(CssPropertyValue::Exact(LayoutFlexGrow {
2154 inner: FloatValue::new(-1.0),
2155 })));
2156 assert_eq!(neg.dims.flex_grow, U16_SENTINEL);
2157
2158 let mut big = Sink::new();
2160 big.apply(&CssProperty::FlexGrow(CssPropertyValue::Exact(LayoutFlexGrow {
2161 inner: FloatValue::new(1.0e9),
2162 })));
2163 assert_eq!(big.dims.flex_grow, U16_SENTINEL);
2164
2165 let mut nan = Sink::new();
2167 nan.apply(&CssProperty::FlexShrink(CssPropertyValue::Exact(
2168 LayoutFlexShrink { inner: FloatValue::new(f32::NAN) },
2169 )));
2170 assert_eq!(nan.dims.flex_shrink, 0);
2171 }
2172
2173 #[test]
2174 fn apply_gap_px_sets_both_axes_and_ignores_unresolvable_units() {
2175 let mut s = Sink::new();
2176 s.apply(&CssProperty::Gap(CssPropertyValue::Exact(LayoutGap {
2177 inner: PixelValue::px(8.0),
2178 })));
2179 assert_eq!(s.dims.row_gap, 80);
2180 assert_eq!(s.dims.column_gap, 80);
2181
2182 let mut em = Sink::new();
2185 em.apply(&CssProperty::Gap(CssPropertyValue::Exact(LayoutGap {
2186 inner: PixelValue::em(2.0),
2187 })));
2188 assert_eq!(em.dims.row_gap, 0);
2189 assert_eq!(em.dims.column_gap, 0);
2190 }
2191
2192 #[test]
2197 fn apply_z_index_auto_and_in_range_values() {
2198 let mut s = Sink::new();
2199 s.apply(&CssProperty::ZIndex(CssPropertyValue::Exact(LayoutZIndex::Auto)));
2200 assert_eq!(s.cold.z_index, I16_AUTO);
2201 s.apply(&CssProperty::ZIndex(CssPropertyValue::Exact(
2202 LayoutZIndex::Integer(100),
2203 )));
2204 assert_eq!(s.cold.z_index, 100);
2205 s.apply(&CssProperty::ZIndex(CssPropertyValue::Exact(
2207 LayoutZIndex::Integer(32_763),
2208 )));
2209 assert_eq!(s.cold.z_index, 32_763);
2210 }
2211
2212 #[test]
2213 fn apply_z_index_large_positive_saturates() {
2214 for z in [32_764i32, 100_000, i32::MAX] {
2215 let mut s = Sink::new();
2216 s.apply(&CssProperty::ZIndex(CssPropertyValue::Exact(
2217 LayoutZIndex::Integer(z),
2218 )));
2219 assert_eq!(s.cold.z_index, I16_SENTINEL, "z-index {z} should saturate");
2220 }
2221 }
2222
2223 #[test]
2224 fn apply_z_index_large_negative_must_not_wrap_positive() {
2225 for z in [-32_769i32, -40_000, -99_999, i32::MIN] {
2233 let mut s = Sink::new();
2234 s.apply(&CssProperty::ZIndex(CssPropertyValue::Exact(
2235 LayoutZIndex::Integer(z),
2236 )));
2237 assert!(
2238 s.cold.z_index < 0 || s.cold.z_index == I16_SENTINEL,
2239 "z-index {z} encoded to {}: a negative z-index must stay negative (or \
2240 saturate to the sentinel), it must never wrap to a positive value",
2241 s.cold.z_index,
2242 );
2243 }
2244 }
2245
2246 #[test]
2247 fn apply_border_styles_pack_into_independent_nibbles() {
2248 let mut s = Sink::new();
2249 s.apply(&CssProperty::BorderTopStyle(CssPropertyValue::Exact(
2250 StyleBorderTopStyle { inner: BorderStyle::Solid },
2251 )));
2252 assert_eq!(
2253 s.cold.border_styles_packed & 0x000F,
2254 u16::from(border_style_to_u8(BorderStyle::Solid))
2255 );
2256 assert_eq!(
2257 s.cold.border_styles_packed & 0xFFF0,
2258 0,
2259 "the top-style nibble leaked into the other three sides"
2260 );
2261
2262 s.apply(&CssProperty::BorderTopStyle(CssPropertyValue::Exact(
2265 StyleBorderTopStyle { inner: BorderStyle::Double },
2266 )));
2267 assert_eq!(
2268 s.cold.border_styles_packed & 0x000F,
2269 u16::from(border_style_to_u8(BorderStyle::Double))
2270 );
2271 }
2272
2273 #[test]
2274 fn apply_opacity_clamps_into_the_0_254_range() {
2275 for (pct, expected) in [
2276 (-1.0e9f32, 0u8),
2277 (-100.0, 0),
2278 (0.0, 0),
2279 (50.0, 127),
2280 (100.0, 254),
2281 (500.0, 254),
2282 (1.0e9, 254),
2283 ] {
2284 let mut s = Sink::new();
2285 s.apply(&CssProperty::Opacity(CssPropertyValue::Exact(StyleOpacity {
2286 inner: PercentageValue::new(pct),
2287 })));
2288 assert_eq!(s.cold.opacity, expected, "opacity: {pct}%");
2289 assert_ne!(
2290 s.cold.opacity, OPACITY_SENTINEL,
2291 "an explicitly set opacity must never encode as the 'unset' sentinel"
2292 );
2293 }
2294 }
2295
2296 #[test]
2297 fn apply_grid_column_encodes_both_lines() {
2298 let mut s = Sink::new();
2299 s.apply(&CssProperty::GridColumn(CssPropertyValue::Exact(GridPlacement {
2300 grid_start: GridLine::Line(2),
2301 grid_end: GridLine::Span(3),
2302 })));
2303 assert_eq!(s.cold.grid_col_start, 2);
2304 assert_eq!(s.cold.grid_col_end, -3);
2305 assert_eq!(s.cold.grid_row_start, I16_AUTO);
2307 assert_eq!(s.cold.grid_row_end, I16_AUTO);
2308 }
2309
2310 #[test]
2311 fn apply_hot_flags_or_in_without_clobbering_each_other() {
2312 let mut s = Sink::new();
2313 s.apply(&CssProperty::TextDecoration(CssPropertyValue::Exact(
2314 StyleTextDecoration::Underline,
2315 )));
2316 assert_eq!(
2317 s.cold.hot_flags & HOT_FLAG_HAS_TEXT_DECORATION,
2318 HOT_FLAG_HAS_TEXT_DECORATION
2319 );
2320
2321 s.apply(&CssProperty::ScrollbarGutter(CssPropertyValue::Exact(
2324 StyleScrollbarGutter::Stable,
2325 )));
2326 assert_eq!(
2327 (s.cold.hot_flags & HOT_FLAG_SCROLLBAR_GUTTER_MASK) >> HOT_FLAG_SCROLLBAR_GUTTER_SHIFT,
2328 SCROLLBAR_GUTTER_STABLE
2329 );
2330 assert_eq!(
2331 s.cold.hot_flags & HOT_FLAG_HAS_TEXT_DECORATION,
2332 HOT_FLAG_HAS_TEXT_DECORATION,
2333 "scrollbar-gutter cleared the has-text-decoration bit"
2334 );
2335
2336 s.apply(&CssProperty::ScrollbarGutter(CssPropertyValue::Exact(
2338 StyleScrollbarGutter::Auto,
2339 )));
2340 assert_eq!(
2341 (s.cold.hot_flags & HOT_FLAG_SCROLLBAR_GUTTER_MASK) >> HOT_FLAG_SCROLLBAR_GUTTER_SHIFT,
2342 SCROLLBAR_GUTTER_AUTO
2343 );
2344 assert_eq!(
2345 s.cold.hot_flags & HOT_FLAG_HAS_TEXT_DECORATION,
2346 HOT_FLAG_HAS_TEXT_DECORATION
2347 );
2348 }
2349
2350 #[test]
2351 fn apply_valueless_property_does_not_set_a_has_flag() {
2352 let mut s = Sink::new();
2356 s.apply(&CssProperty::TextDecoration(CssPropertyValue::Initial));
2357 s.apply(&CssProperty::ScrollbarGutter(CssPropertyValue::Unset));
2358 assert_eq!(s.cold.hot_flags, 0);
2359 }
2360
2361 #[test]
2366 fn apply_text_color_packs_rgba_big_endian() {
2367 let mut s = Sink::new();
2368 s.apply(&CssProperty::TextColor(CssPropertyValue::Exact(StyleTextColor {
2369 inner: ColorU { r: 0x12, g: 0x34, b: 0x56, a: 0x78 },
2370 })));
2371 assert_eq!(s.text.text_color, 0x1234_5678);
2372
2373 let mut transparent = Sink::new();
2375 transparent.apply(&CssProperty::TextColor(CssPropertyValue::Exact(
2376 StyleTextColor { inner: ColorU { r: 0, g: 0, b: 0, a: 0 } },
2377 )));
2378 assert_eq!(transparent.text.text_color, 0);
2379 }
2380
2381 #[test]
2382 fn apply_line_height_round_trips_and_saturates_at_both_ends() {
2383 let mut s = Sink::new();
2384 s.apply(&line_height_prop(120.0));
2385 assert_eq!(s.text.line_height, 1200, "120% must encode as % x 10");
2386
2387 for pct in [1.0e9f32, -1.0e9] {
2389 let mut big = Sink::new();
2390 big.apply(&line_height_prop(pct));
2391 assert_eq!(
2392 big.text.line_height, I16_SENTINEL,
2393 "line-height {pct}% should saturate to the sentinel"
2394 );
2395 }
2396 }
2397
2398 #[test]
2399 fn apply_font_family_hash_is_nonzero_stable_and_registered() {
2400 let arial = StyleFontFamilyVec::from_vec(vec![StyleFontFamily::System("Arial".into())]);
2401
2402 let mut s = Sink::new();
2403 s.apply(&CssProperty::FontFamily(CssPropertyValue::Exact(arial.clone())));
2404 let h = s.text.font_family_hash;
2405 assert_ne!(
2406 h, 0,
2407 "0 is the 'unset' sentinel — a set font-family must never hash to it"
2408 );
2409 assert!(
2410 s.fonts.contains_key(&h),
2411 "the hash must be registered in the reverse map, or consumers cannot resolve it"
2412 );
2413
2414 let mut same = Sink::new();
2416 same.apply(&CssProperty::FontFamily(CssPropertyValue::Exact(arial)));
2417 assert_eq!(same.text.font_family_hash, h);
2418
2419 let mut other = Sink::new();
2421 other.apply(&CssProperty::FontFamily(CssPropertyValue::Exact(
2422 StyleFontFamilyVec::from_vec(vec![StyleFontFamily::System("Times".into())]),
2423 )));
2424 assert_ne!(other.text.font_family_hash, h);
2425 }
2426
2427 #[test]
2432 fn ua_css_is_idempotent_for_every_representative_node_type() {
2433 let nodes = [
2434 NodeData::create_node(NodeType::Html),
2435 NodeData::create_node(NodeType::Body),
2436 NodeData::create_node(NodeType::Div),
2437 NodeData::create_node(NodeType::P),
2438 NodeData::create_node(NodeType::Br),
2439 NodeData::create_text("hello"),
2440 ];
2441 for nd in &nodes {
2442 let mut s = Sink::new();
2443 s.ua(&nd.node_type);
2444 let once = s.snapshot();
2445 s.ua(&nd.node_type);
2446 assert_eq!(
2447 s.snapshot(),
2448 once,
2449 "applying UA CSS twice must be a no-op the second time"
2450 );
2451 }
2452 }
2453
2454 #[test]
2455 fn ua_css_never_touches_the_tier1_populated_bit() {
2456 for nt in [NodeType::Html, NodeType::Body, NodeType::Div, NodeType::P] {
2458 let mut s = Sink::new();
2459 s.ua(&nt);
2460 assert_eq!(s.tier1 & TIER1_POPULATED_BIT, 0);
2461 }
2462 }
2463
2464 #[test]
2465 fn ua_css_survives_a_hostile_pre_filled_sink() {
2466 let mut s = Sink::new();
2469 s.tier1 = u64::MAX;
2470 s.dims.width = U32_SENTINEL;
2471 s.dims.font_size = U32_SENTINEL;
2472 s.dims.flex_grow = U16_SENTINEL;
2473 s.cold.z_index = i16::MIN;
2474 s.cold.opacity = OPACITY_SENTINEL;
2475 s.text.line_height = i16::MIN;
2476 s.ua(&NodeType::Div);
2477 assert_eq!(
2478 s.tier1 & TIER1_POPULATED_BIT,
2479 TIER1_POPULATED_BIT,
2480 "UA CSS must not clear bits it does not own"
2481 );
2482 }
2483
2484 #[test]
2489 fn build_compact_cache_handles_zero_nodes() {
2490 let cache = CssPropertyCache::empty(0);
2491 let r = cache.build_compact_cache(&[], &[]);
2492 assert_eq!(r.node_count(), 0);
2493 assert!(r.tier2_dims.is_empty());
2494 assert!(r.font_dirty_nodes.is_empty());
2495 assert!(r.prev_font_hashes.is_empty());
2496 }
2497
2498 #[test]
2499 fn build_compact_cache_tolerates_a_mismatched_prev_font_hash_slice() {
2500 let cache = CssPropertyCache::empty(3);
2501 let nodes = div_nodes(3);
2502 for prev in [vec![1u64, 2, 3, 4, 5, 6], vec![7u64], Vec::new()] {
2504 let r = cache.build_compact_cache(&nodes, &prev);
2505 assert_eq!(r.prev_font_hashes.len(), 3);
2506 assert_eq!(r.node_count(), 3);
2507 }
2508 }
2509
2510 #[test]
2511 fn build_compact_cache_tolerates_short_node_data() {
2512 let cache = CssPropertyCache::empty(4);
2515 let r = cache.build_compact_cache(&div_nodes(2), &[]);
2516 assert_eq!(r.node_count(), 4);
2517 assert_eq!(r.tier2_dims.len(), 4);
2518 assert_eq!(r.tier2_cold.len(), 4);
2519 assert_eq!(r.tier2b_text.len(), 4);
2520 assert_eq!(r.prev_font_hashes.len(), 4);
2521 }
2522
2523 #[test]
2524 fn build_compact_cache_honours_node_count_over_node_data_len() {
2525 let cache = CssPropertyCache::empty(2);
2526 let r = cache.build_compact_cache(&div_nodes(5), &[]);
2527 assert_eq!(r.node_count(), 2);
2528 }
2529
2530 #[test]
2531 fn build_compact_cache_rebuild_with_unchanged_fonts_is_not_dirty() {
2532 let cache = CssPropertyCache::empty(3);
2533 let nodes = div_nodes(3);
2534 let first = cache.build_compact_cache(&nodes, &[]);
2535 let second = cache.build_compact_cache(&nodes, &first.prev_font_hashes);
2536 assert!(
2537 second.font_dirty_nodes.is_empty(),
2538 "a rebuild with identical font hashes must not re-resolve any font chain"
2539 );
2540 }
2541
2542 #[test]
2547 fn build_with_inheritance_handles_zero_nodes() {
2548 let cache = CssPropertyCache::empty(0);
2549 let r = cache.build_compact_cache_with_inheritance(&[], &[], &[]);
2550 assert_eq!(r.node_count(), 0);
2551
2552 let mut msgs = None;
2553 let r2 = cache.build_compact_cache_with_inheritance_debug(&[], &[], &[], &mut msgs);
2554 assert_eq!(r2.node_count(), 0);
2555 assert!(msgs.is_none());
2556 }
2557
2558 #[test]
2559 fn build_with_inheritance_propagates_font_size_down_the_chain() {
2560 let n = 3;
2561 let cache = CssPropertyCache::empty(n);
2562 let r = cache.build_compact_cache_with_inheritance(
2563 &div_nodes(n),
2564 &linear_hierarchy(n),
2565 &[],
2566 );
2567 assert_eq!(r.node_count(), n);
2568 assert_eq!(r.tier2_dims[1].font_size, r.tier2_dims[0].font_size);
2570 assert_eq!(r.tier2_dims[2].font_size, r.tier2_dims[0].font_size);
2571 }
2572
2573 #[test]
2574 fn build_with_inheritance_marks_all_nodes_dirty_on_the_first_build() {
2575 let n = 3;
2576 let cache = CssPropertyCache::empty(n);
2577 let nodes = div_nodes(n);
2578 let hierarchy = linear_hierarchy(n);
2579
2580 let first = cache.build_compact_cache_with_inheritance(&nodes, &hierarchy, &[]);
2582 assert_eq!(first.font_dirty_nodes, vec![0, 1, 2]);
2583
2584 let second =
2586 cache.build_compact_cache_with_inheritance(&nodes, &hierarchy, &first.prev_font_hashes);
2587 assert!(second.font_dirty_nodes.is_empty());
2588 }
2589
2590 #[test]
2591 fn build_with_inheritance_global_star_rules_skip_text_nodes() {
2592 let mut cache = CssPropertyCache::empty(2);
2596 cache
2597 .global_css_props
2598 .push(CssProperty::PaddingTop(padding(5.0)));
2599
2600 let nodes = vec![
2601 NodeData::create_node(NodeType::Div),
2602 NodeData::create_text("hi"),
2603 ];
2604 let r = cache.build_compact_cache_with_inheritance(&nodes, &linear_hierarchy(2), &[]);
2605
2606 assert_eq!(
2607 r.tier2_dims[0].padding_top, 50,
2608 "the `*` rule must apply to the element"
2609 );
2610 assert_ne!(
2611 r.tier2_dims[1].padding_top, 50,
2612 "the `*` rule must NOT apply to a text node"
2613 );
2614 }
2615
2616 #[test]
2617 fn build_with_inheritance_debug_messages_are_opt_in() {
2618 let n = 2;
2619 let cache = CssPropertyCache::empty(n);
2620 let nodes = div_nodes(n);
2621 let hierarchy = linear_hierarchy(n);
2622
2623 let mut on = Some(Vec::new());
2624 let _ = cache.build_compact_cache_with_inheritance_debug(&nodes, &hierarchy, &[], &mut on);
2625 assert!(
2626 !on.expect("still Some").is_empty(),
2627 "debug logging must emit at least one cascade message"
2628 );
2629
2630 let mut off = None;
2631 let _ = cache.build_compact_cache_with_inheritance_debug(&nodes, &hierarchy, &[], &mut off);
2632 assert!(off.is_none(), "a None sink must stay None");
2633 }
2634
2635 #[test]
2640 fn resolve_font_size_percent_uses_the_parent() {
2641 let mut dims = vec![CompactNodeProps::default(); 2];
2642 dims[0].font_size = encode_pixel_value_u32(&PixelValue::px(20.0));
2643 dims[1].font_size = encode_pixel_value_u32(&PixelValue::percent(50.0));
2644 resolve_font_size_to_px(&mut dims, 1, Some(NodeId::new(0)));
2645 let pv = decode_pixel_value_u32(dims[1].font_size).expect("must resolve to px");
2646 assert_eq!(pv.metric, SizeMetric::Px);
2647 assert!((pv.number.get() - 10.0).abs() < 0.01, "got {}", pv.number.get());
2648 }
2649
2650 #[test]
2651 fn resolve_font_size_pt_converts_to_px() {
2652 let mut dims = vec![CompactNodeProps::default()];
2653 dims[0].font_size = encode_pixel_value_u32(&PixelValue::pt(12.0));
2654 resolve_font_size_to_px(&mut dims, 0, None);
2655 let pv = decode_pixel_value_u32(dims[0].font_size).expect("must resolve to px");
2656 assert!(
2657 (pv.number.get() - 16.0).abs() < 0.01,
2658 "12pt should be 16px, got {}",
2659 pv.number.get()
2660 );
2661 }
2662
2663 #[test]
2664 fn resolve_font_size_leaves_absolute_and_sentinel_values_alone() {
2665 let mut dims = vec![CompactNodeProps::default(); 2];
2667 dims[0].font_size = encode_pixel_value_u32(&PixelValue::px(20.0));
2668 dims[1].font_size = encode_pixel_value_u32(&PixelValue::px(13.0));
2669 let before = dims[1].font_size;
2670 resolve_font_size_to_px(&mut dims, 1, Some(NodeId::new(0)));
2671 assert_eq!(dims[1].font_size, before);
2672
2673 let mut sent = vec![CompactNodeProps::default(); 2];
2675 sent[0].font_size = encode_pixel_value_u32(&PixelValue::px(20.0));
2676 sent[1].font_size = U32_SENTINEL;
2677 resolve_font_size_to_px(&mut sent, 1, Some(NodeId::new(0)));
2678 assert_eq!(sent[1].font_size, U32_SENTINEL);
2679
2680 let mut def = vec![CompactNodeProps::default(); 2];
2682 assert_eq!(def[1].font_size, U32_INITIAL);
2683 resolve_font_size_to_px(&mut def, 1, Some(NodeId::new(0)));
2684 assert_eq!(def[1].font_size, U32_INITIAL);
2685 }
2686
2687 #[test]
2688 fn resolve_font_size_negative_em_is_deterministic() {
2689 let mut dims = vec![CompactNodeProps::default(); 2];
2690 dims[0].font_size = encode_pixel_value_u32(&PixelValue::px(20.0));
2691 dims[1].font_size = encode_pixel_value_u32(&PixelValue::em(-2.0));
2692 resolve_font_size_to_px(&mut dims, 1, Some(NodeId::new(0)));
2693 let pv = decode_pixel_value_u32(dims[1].font_size).expect("must stay decodable");
2694 assert!(
2695 (pv.number.get() + 40.0).abs() < 0.01,
2696 "-2em of 20px should be -40px, got {}",
2697 pv.number.get()
2698 );
2699 }
2700
2701 #[test]
2702 fn resolve_font_size_overflow_saturates_instead_of_wrapping() {
2703 let mut dims = vec![CompactNodeProps::default(); 2];
2704 dims[0].font_size = encode_pixel_value_u32(&PixelValue::px(20.0));
2705 dims[1].font_size = encode_pixel_value_u32(&PixelValue::em(100_000.0));
2707 resolve_font_size_to_px(&mut dims, 1, Some(NodeId::new(0)));
2708 assert_eq!(
2709 dims[1].font_size, U32_SENTINEL,
2710 "an overflowing font-size must land on the tier-3 sentinel, not wrap"
2711 );
2712 }
2713
2714 #[test]
2715 fn resolve_font_size_nan_em_degrades_to_zero() {
2716 let mut dims = vec![CompactNodeProps::default(); 2];
2717 dims[0].font_size = encode_pixel_value_u32(&PixelValue::px(20.0));
2718 dims[1].font_size = encode_pixel_value_u32(&PixelValue::em(f32::NAN));
2719 resolve_font_size_to_px(&mut dims, 1, Some(NodeId::new(0)));
2720 let pv = decode_pixel_value_u32(dims[1].font_size).expect("must stay decodable");
2721 assert!(pv.number.get().is_finite(), "a NaN font-size must not propagate");
2722 assert_eq!(pv.number.get(), 0.0);
2723 }
2724
2725 #[test]
2726 fn resolve_font_size_root_rem_uses_the_16px_initial_value() {
2727 let mut dims = vec![CompactNodeProps::default()];
2733 dims[0].font_size = encode_pixel_value_u32(&PixelValue::rem(2.0));
2734 resolve_font_size_to_px(&mut dims, 0, None);
2735 let pv = decode_pixel_value_u32(dims[0].font_size).expect("must resolve to px");
2736 assert!(
2737 (pv.number.get() - 32.0).abs() < 0.01,
2738 "root `font-size: 2rem` should resolve against the 16px initial value (= 32px), \
2739 got {}px",
2740 pv.number.get()
2741 );
2742 }
2743}