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