1use std::{
16 collections::{BTreeMap, BTreeSet, HashMap},
17 hash::{DefaultHasher, Hash, Hasher},
18};
19
20const CACHE_SIZE_EPSILON: f32 = 0.1;
23
24use azul_core::{
25 diff::NodeDataFingerprint,
26 dom::{FormattingContext, NodeId, NodeType},
27 geom::{LogicalPosition, LogicalRect, LogicalSize},
28 styled_dom::{StyledDom, StyledNode},
29};
30use azul_css::{
31 css::CssPropertyValue,
32 props::{
33 layout::{
34 LayoutDisplay, LayoutFlexWrap, LayoutHeight, LayoutJustifyContent, LayoutOverflow,
35 LayoutPosition, LayoutWritingMode,
36 },
37 property::{CssProperty, CssPropertyType},
38 style::StyleTextAlign,
39 },
40 LayoutDebugMessage, LayoutDebugMessageType,
41};
42
43use crate::{
44 font_traits::{FontLoaderTrait, ParsedFontTrait, TextLayoutCache},
45 solver3::{
46 fc::{self, layout_formatting_context, LayoutConstraints, OverflowBehavior},
47 geometry::PositionedRectangle,
48 getters::{
49 get_css_height, get_display_property, get_justify_content, get_overflow_x,
50 get_overflow_y, get_scrollbar_gutter_property, get_text_align, get_white_space_property, get_wrap, get_writing_mode,
51 MultiValue,
52 },
53 layout_tree::{
54 get_display_type, is_block_level, AnonymousBoxType, DirtyFlag, LayoutNode, LayoutNodeHot, LayoutTreeBuilder, SubtreeHash,
55 },
56 positioning::get_position_type,
57 scrollbar::ScrollbarRequirements,
58 sizing::calculate_used_size_for_node,
59 LayoutContext, LayoutError, LayoutTree, Result,
60 },
61 text3::cache::AvailableSpace as Text3AvailableSpace,
62};
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
86pub enum ComputeMode {
87 ComputeSize,
90 PerformLayout,
94}
95
96#[derive(Debug, Clone, Copy, PartialEq, Eq)]
102pub enum AvailableWidthType {
103 Definite,
105 MinContent,
107 MaxContent,
109}
110
111#[derive(Debug, Clone)]
117pub struct SizingCacheEntry {
118 pub available_size: LogicalSize,
120 pub result_size: LogicalSize,
122 pub baseline: Option<f32>,
124 pub escaped_top_margin: Option<f32>,
126 pub escaped_bottom_margin: Option<f32>,
128}
129
130#[derive(Debug, Clone)]
135pub struct LayoutCacheEntry {
136 pub available_size: LogicalSize,
138 pub result_size: LogicalSize,
140 pub content_size: LogicalSize,
142 pub child_positions: Vec<(usize, LogicalPosition)>,
144 pub escaped_top_margin: Option<f32>,
146 pub escaped_bottom_margin: Option<f32>,
148 pub scrollbar_info: ScrollbarRequirements,
150}
151
152#[derive(Debug, Clone)]
161pub struct NodeCache {
162 pub measure_entries: [Option<SizingCacheEntry>; 9],
168
169 pub layout_entry: Option<LayoutCacheEntry>,
172
173 pub is_empty: bool,
176}
177
178impl Default for NodeCache {
179 fn default() -> Self {
180 Self {
181 measure_entries: [None, None, None, None, None, None, None, None, None],
182 layout_entry: None,
183 is_empty: true, }
185 }
186}
187
188impl NodeCache {
189 pub fn clear(&mut self) {
191 self.measure_entries = [None, None, None, None, None, None, None, None, None];
192 self.layout_entry = None;
193 self.is_empty = true;
194 }
195
196 pub fn slot_index(
202 width_known: bool,
203 height_known: bool,
204 width_type: AvailableWidthType,
205 height_type: AvailableWidthType,
206 ) -> usize {
207 match (width_known, height_known) {
208 (true, true) => 0,
209 (true, false) => {
210 if width_type == AvailableWidthType::MinContent { 2 } else { 1 }
211 }
212 (false, true) => {
213 if height_type == AvailableWidthType::MinContent { 4 } else { 3 }
214 }
215 (false, false) => {
216 let w = if width_type == AvailableWidthType::MinContent { 1 } else { 0 };
217 let h = if height_type == AvailableWidthType::MinContent { 1 } else { 0 };
218 5 + w * 2 + h
219 }
220 }
221 }
222
223 pub fn get_size(&self, slot: usize, known_dims: LogicalSize) -> Option<&SizingCacheEntry> {
227 let entry = self.measure_entries[slot].as_ref()?;
228 if (known_dims.width - entry.available_size.width).abs() < CACHE_SIZE_EPSILON
230 && (known_dims.height - entry.available_size.height).abs() < CACHE_SIZE_EPSILON
231 {
232 return Some(entry);
233 }
234 if (known_dims.width - entry.result_size.width).abs() < CACHE_SIZE_EPSILON
239 && (known_dims.height - entry.result_size.height).abs() < CACHE_SIZE_EPSILON
240 {
241 return Some(entry);
242 }
243 None
244 }
245
246 pub fn store_size(&mut self, slot: usize, entry: SizingCacheEntry) {
248 self.measure_entries[slot] = Some(entry);
249 self.is_empty = false;
250 }
251
252 pub fn get_layout(&self, known_dims: LogicalSize) -> Option<&LayoutCacheEntry> {
254 let entry = self.layout_entry.as_ref()?;
255 if (known_dims.width - entry.available_size.width).abs() < CACHE_SIZE_EPSILON
256 && (known_dims.height - entry.available_size.height).abs() < CACHE_SIZE_EPSILON
257 {
258 return Some(entry);
259 }
260 if (known_dims.width - entry.result_size.width).abs() < CACHE_SIZE_EPSILON
262 && (known_dims.height - entry.result_size.height).abs() < CACHE_SIZE_EPSILON
263 {
264 return Some(entry);
265 }
266 None
267 }
268
269 pub fn store_layout(&mut self, entry: LayoutCacheEntry) {
271 self.layout_entry = Some(entry);
272 self.is_empty = false;
273 }
274}
275
276#[derive(Debug, Clone, Default)]
287pub struct LayoutCacheMap {
288 pub entries: Vec<NodeCache>,
289}
290
291impl LayoutCacheMap {
292 pub fn resize_to_tree(&mut self, tree_len: usize) {
295 self.entries.resize_with(tree_len, NodeCache::default);
296 }
297
298 #[inline]
300 pub fn get(&self, node_index: usize) -> &NodeCache {
301 &self.entries[node_index]
302 }
303
304 #[inline]
306 pub fn get_mut(&mut self, node_index: usize) -> &mut NodeCache {
307 &mut self.entries[node_index]
308 }
309
310 pub fn mark_dirty(&mut self, node_index: usize, tree: &[LayoutNodeHot]) {
317 if node_index >= self.entries.len() {
318 return;
319 }
320 let cache = &mut self.entries[node_index];
321 if cache.is_empty {
322 return; }
324 cache.clear();
325
326 let mut current = tree.get(node_index).and_then(|n| n.parent);
328 while let Some(parent_idx) = current {
329 if parent_idx >= self.entries.len() {
330 break;
331 }
332 let parent_cache = &mut self.entries[parent_idx];
333 if parent_cache.is_empty {
334 break; }
336 parent_cache.clear();
337 current = tree.get(parent_idx).and_then(|n| n.parent);
338 }
339 }
340}
341
342#[derive(Debug, Clone, Default)]
344pub struct LayoutCache {
345 pub tree: Option<LayoutTree>,
347 pub calculated_positions: super::PositionVec,
349 pub viewport: Option<LogicalRect>,
351 pub scroll_ids: HashMap<usize, u64>,
353 pub scroll_id_to_node_id: HashMap<u64, NodeId>,
355 pub counters: HashMap<(usize, String), i32>,
360 pub float_cache: HashMap<usize, fc::FloatingContext>,
365 pub cache_map: LayoutCacheMap,
369 pub previous_positions: super::PositionVec,
372 pub cached_display_list: Option<(SubtreeHash, LogicalRect, super::display_list::DisplayList)>,
380 pub prev_dom_ptr: usize,
384 pub prev_viewport: LogicalRect,
385}
386
387#[derive(Debug, Clone, Default)]
389pub struct Solver3CacheMemoryReport {
390 pub tree_bytes: usize,
391 pub tree_report: Option<super::layout_tree::LayoutTreeMemoryReport>,
392 pub calculated_positions_bytes: usize,
393 pub previous_positions_bytes: usize,
394 pub scroll_ids_bytes: usize,
395 pub scroll_id_to_node_id_bytes: usize,
396 pub counters_bytes: usize,
397 pub float_cache_bytes: usize,
398 pub cache_map_bytes: usize,
399 pub cached_display_list_bytes: usize,
400}
401
402impl Solver3CacheMemoryReport {
403 pub fn total_bytes(&self) -> usize {
404 self.tree_bytes
405 + self.calculated_positions_bytes
406 + self.previous_positions_bytes
407 + self.scroll_ids_bytes
408 + self.scroll_id_to_node_id_bytes
409 + self.counters_bytes
410 + self.float_cache_bytes
411 + self.cache_map_bytes
412 + self.cached_display_list_bytes
413 }
414}
415
416impl LayoutCache {
417 pub fn memory_report(&self) -> Solver3CacheMemoryReport {
419 let tree_report = self.tree.as_ref().map(|t| t.memory_report());
420 let tree_bytes = tree_report.as_ref().map(|r| r.total_bytes()).unwrap_or(0);
421 let mut cache_map_bytes = self.cache_map.entries.capacity()
424 * core::mem::size_of::<NodeCache>();
425 for e in &self.cache_map.entries {
426 if let Some(le) = &e.layout_entry {
427 cache_map_bytes += le.child_positions.capacity()
428 * core::mem::size_of::<(usize, LogicalPosition)>();
429 }
430 }
431 Solver3CacheMemoryReport {
432 tree_bytes,
433 tree_report,
434 calculated_positions_bytes: self.calculated_positions.len()
435 * core::mem::size_of::<LogicalPosition>(),
436 previous_positions_bytes: self.previous_positions.len()
437 * core::mem::size_of::<LogicalPosition>(),
438 scroll_ids_bytes: self.scroll_ids.len()
439 * (core::mem::size_of::<usize>() + core::mem::size_of::<u64>()),
440 scroll_id_to_node_id_bytes: self.scroll_id_to_node_id.len()
441 * (core::mem::size_of::<u64>() + core::mem::size_of::<NodeId>()),
442 counters_bytes: self.counters.iter().map(|((_, name), _)| {
443 core::mem::size_of::<(usize, String)>()
444 + core::mem::size_of::<i32>()
445 + name.capacity()
446 }).sum(),
447 float_cache_bytes: self.float_cache.len() * 256, cache_map_bytes,
449 cached_display_list_bytes: if self.cached_display_list.is_some() { 2048 } else { 0 },
450 }
451 }
452}
453
454#[derive(Debug, Default)]
456pub struct ReconciliationResult {
457 pub intrinsic_dirty: BTreeSet<usize>,
459 pub layout_roots: BTreeSet<usize>,
461 pub paint_dirty: BTreeSet<usize>,
463}
464
465impl ReconciliationResult {
466 pub fn is_clean(&self) -> bool {
468 self.intrinsic_dirty.is_empty()
469 && self.layout_roots.is_empty()
470 && self.paint_dirty.is_empty()
471 }
472
473 pub fn needs_layout(&self) -> bool {
475 !self.intrinsic_dirty.is_empty() || !self.layout_roots.is_empty()
476 }
477
478 pub fn needs_paint_only(&self) -> bool {
480 !self.needs_layout() && !self.paint_dirty.is_empty()
481 }
482}
483
484pub fn reposition_clean_subtrees(
492 styled_dom: &StyledDom,
493 tree: &LayoutTree,
494 layout_roots: &BTreeSet<usize>,
495 calculated_positions: &mut super::PositionVec,
496) {
497 let mut parents_to_reposition = BTreeSet::new();
500 for &root_idx in layout_roots {
501 if let Some(parent_idx) = tree.get(root_idx).and_then(|n| n.parent) {
502 parents_to_reposition.insert(parent_idx);
503 }
504 }
505
506 for parent_idx in parents_to_reposition {
507 let parent_node = match tree.get(parent_idx) {
508 Some(n) => n,
509 None => continue,
510 };
511
512 match parent_node.formatting_context {
514 FormattingContext::Block { .. } | FormattingContext::TableRowGroup => {
516 reposition_block_flow_siblings(
517 styled_dom,
518 parent_idx,
519 tree,
520 layout_roots,
521 calculated_positions,
522 );
523 }
524
525 FormattingContext::Flex | FormattingContext::Grid => {
526 }
530
531 FormattingContext::Table | FormattingContext::TableRow => {
532 }
536
537 _ => { }
541 }
542 }
543}
544
545pub fn to_overflow_behavior(overflow: MultiValue<LayoutOverflow>) -> fc::OverflowBehavior {
549 match overflow.unwrap_or(LayoutOverflow::Visible) {
550 LayoutOverflow::Visible => fc::OverflowBehavior::Visible,
551 LayoutOverflow::Hidden | LayoutOverflow::Clip => fc::OverflowBehavior::Hidden,
552 LayoutOverflow::Scroll => fc::OverflowBehavior::Scroll,
553 LayoutOverflow::Auto => fc::OverflowBehavior::Auto,
554 }
555}
556
557pub const fn style_text_align_to_fc(text_align: StyleTextAlign) -> fc::TextAlign {
560 match text_align {
561 StyleTextAlign::Start | StyleTextAlign::Left => fc::TextAlign::Start,
562 StyleTextAlign::End | StyleTextAlign::Right => fc::TextAlign::End,
563 StyleTextAlign::Center => fc::TextAlign::Center,
564 StyleTextAlign::Justify => fc::TextAlign::Justify,
565 }
566}
567
568pub fn collect_children_dom_ids(styled_dom: &StyledDom, parent_dom_id: NodeId) -> Vec<NodeId> {
573 let hierarchy_container = styled_dom.node_hierarchy.as_container();
574 let mut children = Vec::new();
575
576 let Some(hierarchy_item) = hierarchy_container.get(parent_dom_id) else {
577 return children;
578 };
579
580 let Some(mut child_id) = hierarchy_item.first_child_id(parent_dom_id) else {
581 return children;
582 };
583
584 if get_display_type(styled_dom, child_id) != LayoutDisplay::None {
587 children.push(child_id);
588 }
589 while let Some(hierarchy_item) = hierarchy_container.get(child_id) {
590 let Some(next) = hierarchy_item.next_sibling_id() else {
591 break;
592 };
593 if get_display_type(styled_dom, next) != LayoutDisplay::None {
594 children.push(next);
595 }
596 child_id = next;
597 }
598
599 children
600}
601
602pub fn is_simple_flex_stack(styled_dom: &StyledDom, dom_id: Option<NodeId>) -> bool {
605 let Some(id) = dom_id else { return false };
606 let binding = styled_dom.styled_nodes.as_container();
607 let styled_node = match binding.get(id) {
608 Some(styled_node) => styled_node,
609 None => return false,
610 };
611
612 let wrap = get_wrap(styled_dom, id, &styled_node.styled_node_state);
614
615 if wrap.unwrap_or_default() != LayoutFlexWrap::NoWrap {
616 return false;
617 }
618
619 let justify = get_justify_content(styled_dom, id, &styled_node.styled_node_state);
621
622 if !matches!(
623 justify.unwrap_or_default(),
624 LayoutJustifyContent::FlexStart | LayoutJustifyContent::Start
625 ) {
626 return false;
627 }
628
629 true
638}
639
640pub fn reposition_block_flow_siblings(
644 styled_dom: &StyledDom,
645 parent_idx: usize,
646 tree: &LayoutTree,
647 layout_roots: &BTreeSet<usize>,
648 calculated_positions: &mut super::PositionVec,
649) {
650 let parent_node = match tree.get(parent_idx) {
651 Some(n) => n,
652 None => return,
653 };
654 let dom_id = parent_node.dom_node_id.unwrap_or(NodeId::ZERO);
655 let styled_node_state = styled_dom
656 .styled_nodes
657 .as_container()
658 .get(dom_id)
659 .map(|n| n.styled_node_state.clone())
660 .unwrap_or_default();
661
662 let writing_mode = get_writing_mode(styled_dom, dom_id, &styled_node_state).unwrap_or_default();
663
664 let parent_pos = calculated_positions
665 .get(parent_idx)
666 .copied()
667 .unwrap_or_default();
668
669 let parent_bp = parent_node.box_props.unpack();
670 let content_box_origin = LogicalPosition::new(
671 parent_pos.x + parent_bp.padding.left,
672 parent_pos.y + parent_bp.padding.top,
673 );
674
675 let mut main_pen = 0.0;
676
677 for &child_idx in tree.children(parent_idx) {
678 let child_node = match tree.get(child_idx) {
679 Some(n) => n,
680 None => continue,
681 };
682
683 let child_size = child_node.used_size.unwrap_or_default();
684 let child_bp = child_node.box_props.unpack();
685 let child_main_sum = child_bp.margin.main_sum(writing_mode);
686 let margin_box_main_size = child_size.main(writing_mode) + child_main_sum;
687
688 if layout_roots.contains(&child_idx) {
689 let new_pos = match calculated_positions.get(child_idx) {
692 Some(p) => *p,
693 None => continue,
694 };
695
696 let main_axis_offset = if writing_mode.is_vertical() {
697 new_pos.x - content_box_origin.x
698 } else {
699 new_pos.y - content_box_origin.y
700 };
701
702 main_pen = main_axis_offset
703 + child_size.main(writing_mode)
704 + child_bp.margin.main_end(writing_mode);
705 } else {
706 let old_pos = match calculated_positions.get(child_idx) {
709 Some(p) => *p,
710 None => continue,
711 };
712
713 let child_main_start = child_bp.margin.main_start(writing_mode);
714 let new_main_pos = main_pen + child_main_start;
715 let old_relative_pos = tree.warm(child_idx)
716 .and_then(|w| w.relative_position)
717 .unwrap_or_default();
718 let cross_pos = if writing_mode.is_vertical() {
719 old_relative_pos.y
720 } else {
721 old_relative_pos.x
722 };
723 let new_relative_pos =
724 LogicalPosition::from_main_cross(new_main_pos, cross_pos, writing_mode);
725
726 let new_absolute_pos = LogicalPosition::new(
727 content_box_origin.x + new_relative_pos.x,
728 content_box_origin.y + new_relative_pos.y,
729 );
730
731 if old_pos != new_absolute_pos {
732 let delta = LogicalPosition::new(
733 new_absolute_pos.x - old_pos.x,
734 new_absolute_pos.y - old_pos.y,
735 );
736 shift_subtree_position(child_idx, delta, tree, calculated_positions);
737 }
738
739 main_pen += margin_box_main_size;
740 }
741 }
742}
743
744pub fn shift_subtree_position(
746 node_idx: usize,
747 delta: LogicalPosition,
748 tree: &LayoutTree,
749 calculated_positions: &mut super::PositionVec,
750) {
751 if let Some(pos) = calculated_positions.get_mut(node_idx) {
752 pos.x += delta.x;
753 pos.y += delta.y;
754 }
755
756 if let Some(node) = tree.get(node_idx) {
757 let children = tree.children(node_idx).to_vec();
758 for &child_idx in &children {
759 shift_subtree_position(child_idx, delta, tree, calculated_positions);
760 }
761 }
762}
763
764fn layout_relevant_child_count(
783 styled_dom: &azul_core::styled_dom::StyledDom,
784 children: &[NodeId],
785 parent_id: NodeId,
786) -> usize {
787 use super::getters::{get_display_property, MultiValue};
788 use super::layout_tree::{is_block_level, is_whitespace_only_text};
789
790 let parent_display = match get_display_property(styled_dom, Some(parent_id)) {
791 MultiValue::Exact(d) => d,
792 _ => azul_css::props::layout::display::LayoutDisplay::Block,
793 };
794 let is_table_structural = matches!(
795 parent_display,
796 azul_css::props::layout::display::LayoutDisplay::Table
797 | azul_css::props::layout::display::LayoutDisplay::InlineTable
798 | azul_css::props::layout::display::LayoutDisplay::TableRowGroup
799 | azul_css::props::layout::display::LayoutDisplay::TableHeaderGroup
800 | azul_css::props::layout::display::LayoutDisplay::TableFooterGroup
801 | azul_css::props::layout::display::LayoutDisplay::TableRow
802 );
803
804 let has_any_block_child = children
805 .iter()
806 .any(|&id| is_block_level(styled_dom, id));
807
808 let mut count = 0usize;
809 let collapse_inline_whitespace = has_any_block_child;
813 for &id in children {
814 let display = match get_display_property(styled_dom, Some(id)) {
816 MultiValue::Exact(d) => d,
817 _ => azul_css::props::layout::display::LayoutDisplay::Block,
818 };
819 if matches!(display, azul_css::props::layout::display::LayoutDisplay::None) {
820 continue;
821 }
822 if is_table_structural && is_whitespace_only_text(styled_dom, id) {
824 continue;
825 }
826 if collapse_inline_whitespace
828 && !is_block_level(styled_dom, id)
829 && is_whitespace_only_text(styled_dom, id)
830 {
831 continue;
832 }
833 count += 1;
834 }
835 count
836}
837
838pub fn reconcile_and_invalidate<T: ParsedFontTrait>(
839 ctx: &mut LayoutContext<'_, T>,
840 cache: &LayoutCache,
841 viewport: LogicalRect,
842) -> Result<(LayoutTree, ReconciliationResult)> {
843 let _probe_outer = crate::probe::Probe::span("reconcile_and_invalidate");
844 let mut new_tree_builder = LayoutTreeBuilder::new(ctx.viewport_size);
845 let mut recon_result = ReconciliationResult::default();
846 let old_tree = cache.tree.as_ref();
847
848 if cache.viewport.map_or(true, |v| v.size != viewport.size) {
850 recon_result.layout_roots.insert(0); }
852
853 let root_dom_id = ctx
854 .styled_dom
855 .root
856 .into_crate_internal()
857 .unwrap_or(NodeId::ZERO);
858 let root_idx = reconcile_recursive(
859 ctx.styled_dom,
860 root_dom_id,
861 old_tree.map(|t| t.root),
862 None,
863 old_tree,
864 &mut new_tree_builder,
865 &mut recon_result,
866 &mut ctx.debug_messages,
867 )?;
868
869 let final_layout_roots = recon_result
871 .layout_roots
872 .iter()
873 .filter(|&&idx| {
874 let mut current = new_tree_builder.get(idx).and_then(|n| n.parent);
875 while let Some(p_idx) = current {
876 if recon_result.layout_roots.contains(&p_idx) {
877 return false;
878 }
879 current = new_tree_builder.get(p_idx).and_then(|n| n.parent);
880 }
881 true
882 })
883 .copied()
884 .collect();
885 recon_result.layout_roots = final_layout_roots;
886
887 let new_tree = new_tree_builder.build(root_idx);
888 unsafe { core::ptr::write_volatile(0x400AC as *mut u32, 0xCC00_0001u32); }
892 Ok((new_tree, recon_result))
893}
894
895fn is_whitespace_only_inline_run(
906 styled_dom: &StyledDom,
907 inline_run: &[(usize, NodeId)],
908 parent_dom_id: NodeId,
909) -> bool {
910 use azul_css::props::style::text::StyleWhiteSpace;
911
912 if inline_run.is_empty() {
913 return true;
914 }
915
916 let parent_state = &styled_dom.styled_nodes.as_container()[parent_dom_id].styled_node_state;
918 let white_space = match get_white_space_property(styled_dom, parent_dom_id, parent_state) {
919 MultiValue::Exact(ws) => Some(ws),
920 _ => None,
921 };
922
923 if matches!(
925 white_space,
926 Some(StyleWhiteSpace::Pre) | Some(StyleWhiteSpace::PreWrap) | Some(StyleWhiteSpace::PreLine)
927 ) {
928 return false;
929 }
930
931 let binding = styled_dom.node_data.as_container();
933 for &(_, dom_id) in inline_run {
934 if let Some(data) = binding.get(dom_id) {
935 match data.get_node_type() {
936 NodeType::Text(text) => {
937 let s = text.as_str();
938 if !s.chars().all(|c| matches!(c, ' ' | '\t' | '\n' | '\r' | '\x0C')) {
939 return false; }
941 }
942 _ => {
943 return false; }
945 }
946 }
947 }
948
949 true }
951
952pub fn reconcile_recursive(
954 styled_dom: &StyledDom,
955 new_dom_id: NodeId,
956 old_tree_idx: Option<usize>,
957 new_parent_idx: Option<usize>,
958 old_tree: Option<&LayoutTree>,
959 new_tree_builder: &mut LayoutTreeBuilder,
960 recon: &mut ReconciliationResult,
961 debug_messages: &mut Option<Vec<LayoutDebugMessage>>,
962) -> Result<usize> {
963 let node_data = &styled_dom.node_data.as_container()[new_dom_id];
964
965 let old_cold = old_tree.and_then(|t| old_tree_idx.and_then(|idx| t.cold(idx)));
966 match (old_tree.is_some(), old_tree_idx.is_some(), old_cold.is_some()) {
967 (false, _, _) => drop(crate::probe::Probe::span("recon_old_tree_none")),
968 (true, false, _) => drop(crate::probe::Probe::span("recon_old_idx_none")),
969 (true, true, false) => drop(crate::probe::Probe::span("recon_cold_none")),
970 (true, true, true) => drop(crate::probe::Probe::span("recon_cold_some")),
971 }
972
973 let new_fingerprint = {
975 let _p = crate::probe::Probe::span("fingerprint_compute");
976 NodeDataFingerprint::compute(
977 node_data,
978 styled_dom.styled_nodes.as_container().get(new_dom_id).map(|n| &n.styled_node_state),
979 )
980 };
981
982 let dirty_flag = match old_cold {
984 None => {
985 drop(crate::probe::Probe::span("fp_new_node"));
986 DirtyFlag::Layout },
988 Some(old_c) => {
989 let change_set = old_c.node_data_fingerprint.diff(&new_fingerprint);
990 if change_set.needs_layout() {
991 drop(crate::probe::Probe::span("fp_needs_layout"));
992 static FP_DUMP_ENABLED: std::sync::OnceLock<bool> =
997 std::sync::OnceLock::new();
998 let enabled = *FP_DUMP_ENABLED.get_or_init(|| {
999 std::env::var_os("AZ_FP_DUMP").is_some()
1000 });
1001 if enabled {
1002 use std::sync::atomic::{AtomicUsize, Ordering};
1003 static DUMPED: AtomicUsize = AtomicUsize::new(0);
1004 let n = DUMPED.fetch_add(1, Ordering::Relaxed);
1005 if n < 10 {
1006 eprintln!(
1007 "[fp_diff {n}] dom={} old={:?} new={:?}",
1008 new_dom_id.index(),
1009 old_c.node_data_fingerprint,
1010 new_fingerprint,
1011 );
1012 }
1013 }
1014 DirtyFlag::Layout
1015 } else if change_set.needs_paint() {
1016 drop(crate::probe::Probe::span("fp_needs_paint"));
1017 DirtyFlag::Paint
1018 } else {
1019 drop(crate::probe::Probe::span("fp_clean"));
1020 DirtyFlag::None
1021 }
1022 }
1023 };
1024 let is_dirty = dirty_flag >= DirtyFlag::Paint;
1025
1026 let new_node_idx = if dirty_flag >= DirtyFlag::Layout || old_tree.is_none() {
1032 unsafe { core::ptr::write_volatile(0x400A8 as *mut u32, 0xBB00_0001u32); }
1033 new_tree_builder.create_node_from_dom(
1034 styled_dom,
1035 new_dom_id,
1036 new_parent_idx,
1037 debug_messages,
1038 )
1039 } else {
1040 unsafe { core::ptr::write_volatile(0x400A8 as *mut u32, 0xBB00_0002u32); }
1041 let old_full_node = old_tree
1043 .and_then(|t| old_tree_idx.and_then(|idx| t.get_full_node(idx)))
1044 .ok_or(LayoutError::InvalidTree)?;
1045 let mut idx = new_tree_builder.clone_node_from_old(&old_full_node, new_parent_idx);
1046 if dirty_flag == DirtyFlag::Paint {
1048 if let Some(cloned) = new_tree_builder.get_mut(idx) {
1049 cloned.node_data_fingerprint = new_fingerprint;
1050 cloned.dirty_flag = DirtyFlag::Paint;
1051 }
1052 }
1053 idx
1054 };
1055
1056 unsafe { core::ptr::write_volatile(0x400BC as *mut u32, 0xAB00_0000u32 | (new_node_idx as u32 & 0xffff)); }
1060
1061 {
1065 use crate::solver3::getters::get_display_property;
1066 let display = get_display_property(styled_dom, Some(new_dom_id))
1067 .exact();
1068
1069 if matches!(display, Some(LayoutDisplay::ListItem)) {
1070 new_tree_builder.create_marker_pseudo_element(styled_dom, new_dom_id, new_node_idx);
1072 }
1073 }
1074
1075 let mut new_children_dom_ids: Vec<_> = collect_children_dom_ids(styled_dom, new_dom_id);
1077
1078 {
1083 use super::getters::{get_display_property, MultiValue};
1084 let parent_display = match get_display_property(styled_dom, Some(new_dom_id)) {
1085 MultiValue::Exact(d) => d,
1086 _ => azul_css::props::layout::display::LayoutDisplay::Block,
1087 };
1088 if matches!(parent_display,
1089 azul_css::props::layout::display::LayoutDisplay::Table
1090 | azul_css::props::layout::display::LayoutDisplay::InlineTable
1091 | azul_css::props::layout::display::LayoutDisplay::TableRowGroup
1092 | azul_css::props::layout::display::LayoutDisplay::TableHeaderGroup
1093 | azul_css::props::layout::display::LayoutDisplay::TableFooterGroup
1094 | azul_css::props::layout::display::LayoutDisplay::TableRow
1095 ) {
1096 new_children_dom_ids.retain(|&id| {
1097 !super::layout_tree::is_whitespace_only_text(styled_dom, id)
1098 });
1099 }
1100 }
1101
1102 let old_children_indices: Vec<usize> = old_tree
1109 .and_then(|t| old_tree_idx.map(|idx| t.children(idx).to_vec()))
1110 .unwrap_or_default();
1111 let old_children_by_dom: alloc::collections::BTreeMap<NodeId, usize> = old_tree
1112 .and_then(|t| old_tree_idx.map(|idx| {
1113 t.children(idx).iter()
1114 .filter_map(|&cidx| t.get(cidx).and_then(|n| n.dom_node_id).map(|did| (did, cidx)))
1115 .collect()
1116 }))
1117 .unwrap_or_default();
1118
1119 let old_layout_relevant_count = old_children_by_dom.len();
1124
1125 let new_layout_relevant_count = layout_relevant_child_count(styled_dom, &new_children_dom_ids, new_dom_id);
1132
1133 let mut children_are_different = new_layout_relevant_count != old_layout_relevant_count;
1134 let mut new_child_hashes = Vec::new();
1135
1136 let has_block_child = new_children_dom_ids
1143 .iter()
1144 .any(|&id| is_block_level(styled_dom, id));
1145
1146 if !has_block_child {
1147 for (i, &new_child_dom_id) in new_children_dom_ids.iter().enumerate() {
1150 let old_child_idx = old_children_by_dom.get(&new_child_dom_id).copied();
1157
1158 let reconciled_child_idx = reconcile_recursive(
1159 styled_dom,
1160 new_child_dom_id,
1161 old_child_idx,
1162 Some(new_node_idx),
1163 old_tree,
1164 new_tree_builder,
1165 recon,
1166 debug_messages,
1167 )?;
1168 if let Some(child_node) = new_tree_builder.get(reconciled_child_idx) {
1169 new_child_hashes.push(child_node.subtree_hash.0);
1170 }
1171
1172 if old_tree.and_then(|t| t.cold(old_child_idx?).map(|n| n.subtree_hash))
1173 != new_tree_builder
1174 .get(reconciled_child_idx)
1175 .map(|n| n.subtree_hash)
1176 {
1177 children_are_different = true;
1178 }
1179 }
1180 } else {
1181 if let Some(msgs) = debug_messages.as_mut() {
1185 msgs.push(LayoutDebugMessage::info(format!(
1186 "[reconcile_recursive] Mixed content in node {}: creating anonymous IFC wrappers",
1187 new_dom_id.index()
1188 )));
1189 }
1190
1191 let mut inline_run: Vec<(usize, NodeId)> = Vec::new(); for (i, &new_child_dom_id) in new_children_dom_ids.iter().enumerate() {
1194 if is_block_level(styled_dom, new_child_dom_id) {
1195 if !inline_run.is_empty() {
1197 if is_whitespace_only_inline_run(styled_dom, &inline_run, new_dom_id) {
1203 if let Some(msgs) = debug_messages.as_mut() {
1204 msgs.push(LayoutDebugMessage::info(format!(
1205 "[reconcile_recursive] Skipping whitespace-only inline run ({} nodes) between blocks in node {}",
1206 inline_run.len(),
1207 new_dom_id.index()
1208 )));
1209 }
1210 inline_run.clear();
1211 } else {
1212 let anon_idx = new_tree_builder.create_anonymous_node(
1215 new_node_idx,
1216 AnonymousBoxType::InlineWrapper,
1217 FormattingContext::Inline, );
1219
1220 if let Some(msgs) = debug_messages.as_mut() {
1221 msgs.push(LayoutDebugMessage::info(format!(
1222 "[reconcile_recursive] Created anonymous IFC wrapper (layout_idx={}) for {} inline children: {:?}",
1223 anon_idx,
1224 inline_run.len(),
1225 inline_run.iter().map(|(_, id)| id.index()).collect::<Vec<_>>()
1226 )));
1227 }
1228
1229 for (pos, inline_dom_id) in inline_run.drain(..) {
1231 let old_child_idx = old_children_by_dom.get(&inline_dom_id).copied()
1239 .or_else(|| old_tree
1240 .and_then(|t| t.dom_to_layout.get(&inline_dom_id))
1241 .and_then(|v| v.first().copied()));
1242 let reconciled_child_idx = reconcile_recursive(
1243 styled_dom,
1244 inline_dom_id,
1245 old_child_idx,
1246 Some(anon_idx), old_tree,
1248 new_tree_builder,
1249 recon,
1250 debug_messages,
1251 )?;
1252 if let Some(child_node) = new_tree_builder.get(reconciled_child_idx) {
1253 new_child_hashes.push(child_node.subtree_hash.0);
1254 }
1255 }
1256
1257 children_are_different = true;
1268 } }
1270
1271 let old_child_idx = old_children_by_dom.get(&new_child_dom_id).copied()
1273 .or_else(|| old_children_indices.get(i).copied());
1274 let reconciled_child_idx = reconcile_recursive(
1275 styled_dom,
1276 new_child_dom_id,
1277 old_child_idx,
1278 Some(new_node_idx),
1279 old_tree,
1280 new_tree_builder,
1281 recon,
1282 debug_messages,
1283 )?;
1284 if let Some(child_node) = new_tree_builder.get(reconciled_child_idx) {
1285 new_child_hashes.push(child_node.subtree_hash.0);
1286 }
1287
1288 if old_tree.and_then(|t| t.cold(old_child_idx?).map(|n| n.subtree_hash))
1289 != new_tree_builder
1290 .get(reconciled_child_idx)
1291 .map(|n| n.subtree_hash)
1292 {
1293 children_are_different = true;
1294 }
1295 } else {
1296 inline_run.push((i, new_child_dom_id));
1298 }
1299 }
1300
1301 if !inline_run.is_empty() {
1303 if is_whitespace_only_inline_run(styled_dom, &inline_run, new_dom_id) {
1305 if let Some(msgs) = debug_messages.as_mut() {
1306 msgs.push(LayoutDebugMessage::info(format!(
1307 "[reconcile_recursive] Skipping trailing whitespace-only inline run ({} nodes) in node {}",
1308 inline_run.len(),
1309 new_dom_id.index()
1310 )));
1311 }
1312 } else {
1314 let anon_idx = new_tree_builder.create_anonymous_node(
1315 new_node_idx,
1316 AnonymousBoxType::InlineWrapper,
1317 FormattingContext::Inline, );
1319
1320 if let Some(msgs) = debug_messages.as_mut() {
1321 msgs.push(LayoutDebugMessage::info(format!(
1322 "[reconcile_recursive] Created trailing anonymous IFC wrapper (layout_idx={}) for {} inline children: {:?}",
1323 anon_idx,
1324 inline_run.len(),
1325 inline_run.iter().map(|(_, id)| id.index()).collect::<Vec<_>>()
1326 )));
1327 }
1328
1329 for (pos, inline_dom_id) in inline_run.drain(..) {
1330 let old_child_idx = old_children_by_dom.get(&inline_dom_id).copied();
1331 let reconciled_child_idx = reconcile_recursive(
1332 styled_dom,
1333 inline_dom_id,
1334 old_child_idx,
1335 Some(anon_idx),
1336 old_tree,
1337 new_tree_builder,
1338 recon,
1339 debug_messages,
1340 )?;
1341 if let Some(child_node) = new_tree_builder.get(reconciled_child_idx) {
1342 new_child_hashes.push(child_node.subtree_hash.0);
1343 }
1344 }
1345
1346 children_are_different = true;
1350 } }
1352 }
1353
1354 let node_self_hash = {
1357 use std::hash::{DefaultHasher, Hash, Hasher};
1358 let mut h = DefaultHasher::new();
1359 new_fingerprint.hash(&mut h);
1360 h.finish()
1361 };
1362 let final_subtree_hash = calculate_subtree_hash(node_self_hash, &new_child_hashes);
1363 if let Some(current_node) = new_tree_builder.get_mut(new_node_idx) {
1364 current_node.subtree_hash = final_subtree_hash;
1365 }
1366
1367 if dirty_flag >= DirtyFlag::Layout || children_are_different {
1369 recon.intrinsic_dirty.insert(new_node_idx);
1370 recon.layout_roots.insert(new_node_idx);
1371 } else if dirty_flag == DirtyFlag::Paint {
1372 recon.paint_dirty.insert(new_node_idx);
1373 }
1374
1375 Ok(new_node_idx)
1376}
1377
1378struct PreparedLayoutContext<'a> {
1381 constraints: LayoutConstraints<'a>,
1382 dom_id: Option<NodeId>,
1384 writing_mode: LayoutWritingMode,
1385 final_used_size: LogicalSize,
1386 box_props: crate::solver3::geometry::BoxProps,
1387}
1388
1389fn prepare_layout_context<'a, T: ParsedFontTrait>(
1395 ctx: &LayoutContext<'a, T>,
1396 tree: &LayoutTree,
1397 node_index: usize,
1398 containing_block_size: LogicalSize,
1399) -> Result<PreparedLayoutContext<'a>> {
1400 let node = tree.get(node_index).ok_or(LayoutError::InvalidTree)?;
1401 let warm = tree.warm(node_index).ok_or(LayoutError::InvalidTree)?;
1402 let dom_id = node.dom_node_id; let intrinsic = warm.intrinsic_sizes.clone().unwrap_or_default();
1409 let final_used_size = calculate_used_size_for_node(
1410 ctx.styled_dom,
1411 dom_id, containing_block_size,
1413 intrinsic,
1414 &node.box_props.unpack(),
1415 ctx.viewport_size,
1416 )?;
1417
1418 let writing_mode = warm.computed_style.writing_mode;
1421 let text_align = warm.computed_style.text_align;
1422 let display = warm.computed_style.display;
1423 let overflow_y = warm.computed_style.overflow_y;
1424
1425 let height_is_auto = warm.computed_style.height.is_none();
1427
1428 let available_size_for_children = if height_is_auto {
1429 let inner_size = node.box_props.inner_size(final_used_size, writing_mode);
1431
1432 let available_width = match display {
1436 LayoutDisplay::Inline => containing_block_size.width,
1437 _ => inner_size.width,
1438 };
1439
1440 LogicalSize {
1441 width: available_width,
1442 height: containing_block_size.height,
1444 }
1445 } else {
1446 node.box_props.inner_size(final_used_size, writing_mode)
1448 };
1449
1450 let wm_ctx = crate::solver3::geometry::WritingModeContext::new(
1456 writing_mode,
1457 warm.computed_style.direction,
1458 warm.computed_style.text_orientation,
1459 );
1460 let constraints = LayoutConstraints {
1461 available_size: available_size_for_children,
1462 bfc_state: None,
1463 writing_mode,
1464 writing_mode_ctx: wm_ctx,
1465 text_align: style_text_align_to_fc(text_align),
1466 containing_block_size,
1467 available_width_type: Text3AvailableSpace::Definite(available_size_for_children.width),
1468 };
1469
1470 Ok(PreparedLayoutContext {
1471 constraints,
1472 dom_id,
1473 writing_mode,
1474 final_used_size,
1475 box_props: node.box_props.unpack(),
1476 })
1477}
1478
1479pub fn compute_scrollbar_info_core<T: ParsedFontTrait>(
1488 ctx: &LayoutContext<'_, T>,
1489 dom_id: NodeId,
1490 styled_node_state: &azul_core::styled_dom::StyledNodeState,
1491 content_size: LogicalSize,
1492 container_size: LogicalSize,
1493) -> ScrollbarRequirements {
1494 if ctx.fragmentation_context.is_some() {
1496 return ScrollbarRequirements::default();
1497 }
1498
1499 let overflow_x = get_overflow_x(ctx.styled_dom, dom_id, styled_node_state);
1500 let overflow_y = get_overflow_y(ctx.styled_dom, dom_id, styled_node_state);
1501
1502 let scrollbar_style = crate::solver3::getters::get_scrollbar_style_cached(
1513 ctx, dom_id, styled_node_state,
1514 );
1515 let scrollbar_width_px = scrollbar_style.reserve_width_px;
1516
1517 let mut reqs = fc::check_scrollbar_necessity(
1518 content_size,
1519 container_size,
1520 to_overflow_behavior(overflow_x),
1521 to_overflow_behavior(overflow_y),
1522 scrollbar_width_px,
1523 );
1524 reqs.visual_width_px = scrollbar_style.visual_width_px;
1525
1526 let scrollbar_gutter = get_scrollbar_gutter_property(ctx.styled_dom, dom_id, styled_node_state)
1534 .unwrap_or(azul_css::props::layout::overflow::StyleScrollbarGutter::Auto);
1535 let ob_y = to_overflow_behavior(overflow_y);
1536 let is_scroll_container = matches!(ob_y, fc::OverflowBehavior::Scroll | fc::OverflowBehavior::Auto);
1537
1538 if is_scroll_container {
1539 use azul_css::props::layout::overflow::StyleScrollbarGutter;
1540 match scrollbar_gutter {
1541 StyleScrollbarGutter::Stable => {
1542 if !reqs.needs_vertical {
1544 reqs.scrollbar_width = scrollbar_width_px;
1545 }
1546 }
1547 StyleScrollbarGutter::StableBothEdges => {
1548 reqs.scrollbar_width = scrollbar_width_px * 2.0;
1550 }
1551 StyleScrollbarGutter::Auto => {
1552 }
1554 }
1555 }
1556
1557 reqs
1558}
1559
1560fn compute_scrollbar_info<T: ParsedFontTrait>(
1565 ctx: &LayoutContext<'_, T>,
1566 dom_id: NodeId,
1567 styled_node_state: &azul_core::styled_dom::StyledNodeState,
1568 content_size: LogicalSize,
1569 box_props: &crate::solver3::geometry::BoxProps,
1570 final_used_size: LogicalSize,
1571 writing_mode: LayoutWritingMode,
1572) -> ScrollbarRequirements {
1573 let container_size = box_props.inner_size(final_used_size, writing_mode);
1574 compute_scrollbar_info_core(ctx, dom_id, styled_node_state, content_size, container_size)
1575}
1576
1577fn check_scrollbar_change(
1584 tree: &LayoutTree,
1585 node_index: usize,
1586 scrollbar_info: &ScrollbarRequirements,
1587 skip_scrollbar_check: bool,
1588) -> bool {
1589 if skip_scrollbar_check {
1590 return false;
1591 }
1592
1593 let Some(warm_node) = tree.warm(node_index) else {
1594 return false;
1595 };
1596
1597 match &warm_node.scrollbar_info {
1598 None => scrollbar_info.needs_reflow(),
1599 Some(old_info) => {
1600 let horizontal_changed = old_info.needs_horizontal != scrollbar_info.needs_horizontal;
1602 let vertical_changed = old_info.needs_vertical != scrollbar_info.needs_vertical;
1603 horizontal_changed || vertical_changed
1604 }
1605 }
1606}
1607
1608fn merge_scrollbar_info(
1615 _tree: &LayoutTree,
1616 _node_index: usize,
1617 new_info: &ScrollbarRequirements,
1618) -> ScrollbarRequirements {
1619 new_info.clone()
1620}
1621
1622fn calculate_content_box_pos(
1627 containing_block_pos: LogicalPosition,
1628 box_props: &crate::solver3::geometry::BoxProps,
1629) -> LogicalPosition {
1630 LogicalPosition::new(
1631 containing_block_pos.x + box_props.border.left + box_props.padding.left,
1632 containing_block_pos.y + box_props.border.top + box_props.padding.top,
1633 )
1634}
1635
1636fn log_content_box_calculation<T: ParsedFontTrait>(
1638 ctx: &mut LayoutContext<'_, T>,
1639 node_index: usize,
1640 current_node: &LayoutNodeHot,
1641 containing_block_pos: LogicalPosition,
1642 self_content_box_pos: LogicalPosition,
1643) {
1644 let Some(debug_msgs) = ctx.debug_messages.as_mut() else {
1645 return;
1646 };
1647
1648 let dom_name = current_node
1649 .dom_node_id
1650 .and_then(|id| {
1651 ctx.styled_dom
1652 .node_data
1653 .as_container()
1654 .internal
1655 .get(id.index())
1656 })
1657 .map(|n| format!("{:?}", n.node_type))
1658 .unwrap_or_else(|| "Unknown".to_string());
1659
1660 let cbp = current_node.box_props.unpack();
1661 debug_msgs.push(LayoutDebugMessage::new(
1662 LayoutDebugMessageType::PositionCalculation,
1663 format!(
1664 "[CONTENT BOX {}] {} - margin-box pos=({:.2}, {:.2}) + border=({:.2},{:.2}) + \
1665 padding=({:.2},{:.2}) = content-box pos=({:.2}, {:.2})",
1666 node_index,
1667 dom_name,
1668 containing_block_pos.x,
1669 containing_block_pos.y,
1670 cbp.border.left,
1671 cbp.border.top,
1672 cbp.padding.left,
1673 cbp.padding.top,
1674 self_content_box_pos.x,
1675 self_content_box_pos.y
1676 ),
1677 ));
1678}
1679
1680fn log_child_positioning<T: ParsedFontTrait>(
1682 ctx: &mut LayoutContext<'_, T>,
1683 child_index: usize,
1684 child_node: &LayoutNodeHot,
1685 self_content_box_pos: LogicalPosition,
1686 child_relative_pos: LogicalPosition,
1687 child_absolute_pos: LogicalPosition,
1688) {
1689 let child_dom_name = child_node
1691 .dom_node_id
1692 .and_then(|id| {
1693 ctx.styled_dom
1694 .node_data
1695 .as_container()
1696 .internal
1697 .get(id.index())
1698 })
1699 .map(|n| format!("{:?}", n.node_type))
1700 .unwrap_or_else(|| "Unknown".to_string());
1701
1702 let Some(debug_msgs) = ctx.debug_messages.as_mut() else {
1703 return;
1704 };
1705
1706 debug_msgs.push(LayoutDebugMessage::new(
1707 LayoutDebugMessageType::PositionCalculation,
1708 format!(
1709 "[CHILD POS {}] {} - parent content-box=({:.2}, {:.2}) + relative=({:.2}, {:.2}) + \
1710 margin=({:.2}, {:.2}) = absolute=({:.2}, {:.2})",
1711 child_index,
1712 child_dom_name,
1713 self_content_box_pos.x,
1714 self_content_box_pos.y,
1715 child_relative_pos.x,
1716 child_relative_pos.y,
1717 child_node.box_props.unpack().margin.left,
1718 child_node.box_props.unpack().margin.top,
1719 child_absolute_pos.x,
1720 child_absolute_pos.y
1721 ),
1722 ));
1723}
1724
1725fn process_inflow_child<T: ParsedFontTrait>(
1732 ctx: &mut LayoutContext<'_, T>,
1733 tree: &mut LayoutTree,
1734 text_cache: &mut TextLayoutCache,
1735 child_index: usize,
1736 child_relative_pos: LogicalPosition,
1737 self_content_box_pos: LogicalPosition,
1738 inner_size_after_scrollbars: LogicalSize,
1739 writing_mode: LayoutWritingMode,
1740 is_flex_or_grid: bool,
1741 calculated_positions: &mut super::PositionVec,
1742 reflow_needed_for_scrollbars: &mut bool,
1743 float_cache: &mut HashMap<usize, fc::FloatingContext>,
1744) -> Result<()> {
1745 let child_warm = tree.warm_mut(child_index).ok_or(LayoutError::InvalidTree)?;
1748 child_warm.relative_position = Some(child_relative_pos);
1749
1750 let child_absolute_pos = LogicalPosition::new(
1754 self_content_box_pos.x + child_relative_pos.x,
1755 self_content_box_pos.y + child_relative_pos.y,
1756 );
1757
1758 {
1760 let child_node = tree.get(child_index).ok_or(LayoutError::InvalidTree)?;
1761 log_child_positioning(
1762 ctx,
1763 child_index,
1764 child_node,
1765 self_content_box_pos,
1766 child_relative_pos,
1767 child_absolute_pos,
1768 );
1769 }
1770
1771 super::pos_set(calculated_positions, child_index, child_absolute_pos);
1773
1774 let child_node = tree.get(child_index).ok_or(LayoutError::InvalidTree)?;
1776 let child_bp = child_node.box_props.unpack();
1777 let child_content_box_pos =
1778 calculate_content_box_pos(child_absolute_pos, &child_bp);
1779 let child_inner_size = child_bp
1780 .inner_size(child_node.used_size.unwrap_or_default(), writing_mode);
1781 let child_children: Vec<usize> = tree.children(child_index).to_vec();
1782 let child_fc = child_node.formatting_context.clone();
1783
1784 if !child_children.is_empty() {
1789 if is_flex_or_grid {
1790 position_flex_child_descendants(
1792 ctx,
1793 tree,
1794 text_cache,
1795 child_index,
1796 child_content_box_pos,
1797 child_inner_size,
1798 calculated_positions,
1799 reflow_needed_for_scrollbars,
1800 float_cache,
1801 )?;
1802 } else {
1803 position_bfc_child_descendants(
1806 tree,
1807 child_index,
1808 child_content_box_pos,
1809 calculated_positions,
1810 );
1811 }
1812 }
1813
1814 Ok(())
1815}
1816
1817fn position_bfc_child_descendants(
1821 tree: &LayoutTree,
1822 node_index: usize,
1823 content_box_pos: LogicalPosition,
1824 calculated_positions: &mut super::PositionVec,
1825) {
1826 let Some(node) = tree.get(node_index) else { return };
1827
1828 for &child_index in tree.children(node_index) {
1829 let Some(child_node) = tree.get(child_index) else { continue };
1830
1831 let child_rel_pos = tree.warm(child_index)
1833 .and_then(|w| w.relative_position)
1834 .unwrap_or_default();
1835 let child_abs_pos = LogicalPosition::new(
1836 content_box_pos.x + child_rel_pos.x,
1837 content_box_pos.y + child_rel_pos.y,
1838 );
1839
1840 super::pos_set(calculated_positions, child_index, child_abs_pos);
1841
1842 let cbp = child_node.box_props.unpack();
1844 let child_content_box_pos = LogicalPosition::new(
1845 child_abs_pos.x + cbp.border.left + cbp.padding.left,
1846 child_abs_pos.y + cbp.border.top + cbp.padding.top,
1847 );
1848
1849 position_bfc_child_descendants(tree, child_index, child_content_box_pos, calculated_positions);
1851 }
1852}
1853
1854fn process_out_of_flow_children<T: ParsedFontTrait>(
1860 ctx: &mut LayoutContext<'_, T>,
1861 tree: &mut LayoutTree,
1862 text_cache: &mut TextLayoutCache,
1863 node_index: usize,
1864 self_content_box_pos: LogicalPosition,
1865 containing_block_size: LogicalSize,
1866 calculated_positions: &mut super::PositionVec,
1867 reflow_needed_for_scrollbars: &mut bool,
1868 float_cache: &mut HashMap<usize, fc::FloatingContext>,
1869) -> Result<()> {
1870 let out_of_flow_children: Vec<(usize, Option<NodeId>)> = {
1872 let current_node = tree.get(node_index).ok_or(LayoutError::InvalidTree)?;
1873 tree.children(node_index)
1874 .iter()
1875 .filter_map(|&child_index| {
1876 if super::pos_contains(calculated_positions, child_index) {
1877 return None;
1878 }
1879 let child = tree.get(child_index)?;
1880 Some((child_index, child.dom_node_id))
1881 })
1882 .collect()
1883 };
1884
1885 for (child_index, child_dom_id_opt) in out_of_flow_children {
1886 let Some(child_dom_id) = child_dom_id_opt else {
1887 continue;
1888 };
1889
1890 let position_type = get_position_type(ctx.styled_dom, Some(child_dom_id));
1891 if position_type != LayoutPosition::Absolute && position_type != LayoutPosition::Fixed {
1892 continue;
1893 }
1894
1895 super::pos_set(calculated_positions, child_index, self_content_box_pos);
1897
1898 calculate_layout_for_subtree(
1902 ctx,
1903 tree,
1904 text_cache,
1905 child_index,
1906 self_content_box_pos,
1907 containing_block_size,
1908 calculated_positions,
1909 reflow_needed_for_scrollbars,
1910 float_cache,
1911 ComputeMode::PerformLayout,
1912 )?;
1913 }
1914
1915 Ok(())
1916}
1917
1918pub fn calculate_layout_for_subtree<T: ParsedFontTrait>(
1946 ctx: &mut LayoutContext<'_, T>,
1947 tree: &mut LayoutTree,
1948 text_cache: &mut TextLayoutCache,
1949 node_index: usize,
1950 containing_block_pos: LogicalPosition,
1951 containing_block_size: LogicalSize,
1952 calculated_positions: &mut super::PositionVec,
1953 reflow_needed_for_scrollbars: &mut bool,
1954 float_cache: &mut HashMap<usize, fc::FloatingContext>,
1955 compute_mode: ComputeMode,
1956) -> Result<()> {
1957 let _probe = match compute_mode {
1958 ComputeMode::ComputeSize => crate::probe::Probe::span("size_node"),
1959 ComputeMode::PerformLayout => crate::probe::Probe::span("pos_node"),
1960 };
1961 if node_index < ctx.cache_map.entries.len() {
1978 match compute_mode {
1979 ComputeMode::ComputeSize => {
1980 let sizing_hit = ctx.cache_map.entries[node_index]
1982 .get_size(0, containing_block_size)
1983 .cloned();
1984 if let Some(cached_sizing) = sizing_hit {
1985 drop(crate::probe::Probe::span("size_cache_hit_sizing"));
1988 if let Some(node) = tree.get_mut(node_index) {
1989 node.used_size = Some(cached_sizing.result_size);
1990 }
1991 if let Some(warm) = tree.warm_mut(node_index) {
1992 warm.escaped_top_margin = cached_sizing.escaped_top_margin;
1993 warm.escaped_bottom_margin = cached_sizing.escaped_bottom_margin;
1994 warm.baseline = cached_sizing.baseline;
1995 }
1996 return Ok(());
1997 }
1998 let layout_hit = ctx.cache_map.entries[node_index]
2000 .get_layout(containing_block_size)
2001 .cloned();
2002 if let Some(cached_layout) = layout_hit {
2003 drop(crate::probe::Probe::span("size_cache_hit_layout"));
2005 if let Some(node) = tree.get_mut(node_index) {
2006 node.used_size = Some(cached_layout.result_size);
2007 }
2008 if let Some(warm) = tree.warm_mut(node_index) {
2009 warm.overflow_content_size = Some(cached_layout.content_size);
2010 warm.scrollbar_info = Some(cached_layout.scrollbar_info.clone());
2011 }
2012 return Ok(());
2013 }
2014 drop(crate::probe::Probe::span("size_cache_miss"));
2015 }
2016 ComputeMode::PerformLayout => {
2017 let layout_hit = ctx.cache_map.entries[node_index]
2019 .get_layout(containing_block_size)
2020 .cloned();
2021 if let Some(cached_layout) = layout_hit {
2022 drop(crate::probe::Probe::span("pos_cache_hit"));
2023 if let Some(node) = tree.get_mut(node_index) {
2025 node.used_size = Some(cached_layout.result_size);
2026 }
2027 if let Some(warm) = tree.warm_mut(node_index) {
2028 warm.overflow_content_size = Some(cached_layout.content_size);
2029 warm.scrollbar_info = Some(cached_layout.scrollbar_info.clone());
2030 }
2031
2032 let box_props = tree.get(node_index)
2033 .map(|n| n.box_props.unpack())
2034 .unwrap_or_default();
2035 let self_content_box_pos = calculate_content_box_pos(containing_block_pos, &box_props);
2036
2037 let result_size = cached_layout.result_size;
2039 for (child_index, child_relative_pos) in &cached_layout.child_positions {
2040 let child_abs_pos = LogicalPosition::new(
2041 self_content_box_pos.x + child_relative_pos.x,
2042 self_content_box_pos.y + child_relative_pos.y,
2043 );
2044 super::pos_set(calculated_positions, *child_index, child_abs_pos);
2045
2046 let inner = box_props.inner_size(
2047 result_size,
2048 LayoutWritingMode::HorizontalTb,
2049 );
2050 let child_available_size =
2056 cached_layout.scrollbar_info.shrink_size(inner);
2057 calculate_layout_for_subtree(
2058 ctx,
2059 tree,
2060 text_cache,
2061 *child_index,
2062 child_abs_pos,
2063 child_available_size,
2064 calculated_positions,
2065 reflow_needed_for_scrollbars,
2066 float_cache,
2067 compute_mode,
2068 )?;
2069 }
2070
2071 return Ok(());
2072 }
2073 }
2074 }
2075 }
2076
2077 if compute_mode == ComputeMode::PerformLayout {
2079 drop(crate::probe::Probe::span("pos_cache_miss"));
2080 }
2081
2082 let PreparedLayoutContext {
2084 constraints,
2085 dom_id,
2086 writing_mode,
2087 mut final_used_size,
2088 box_props,
2089 } = {
2090 let _p = crate::probe::Probe::span("prepare_layout_context");
2091 prepare_layout_context(ctx, tree, node_index, containing_block_size)?
2092 };
2093
2094 {
2102 let is_table_cell = tree.get(node_index).map_or(false, |n| {
2103 matches!(n.formatting_context, FormattingContext::TableCell)
2104 });
2105 if !is_table_cell {
2106 if let Some(node) = tree.get_mut(node_index) {
2107 node.used_size = Some(final_used_size);
2108 }
2109 }
2110 }
2111
2112 let layout_result = {
2114 let _p = crate::probe::Probe::span("layout_formatting_context");
2115 layout_formatting_context(ctx, tree, text_cache, node_index, &constraints, float_cache)?
2116 };
2117 let content_size = layout_result.output.overflow_size;
2118
2119 if let Some(adjusted) = tree.get(node_index).and_then(|n| n.used_size) {
2125 final_used_size = adjusted;
2126 }
2127
2128 let styled_node_state = dom_id
2131 .and_then(|id| ctx.styled_dom.styled_nodes.as_container().get(id).cloned())
2132 .map(|n| n.styled_node_state)
2133 .unwrap_or_default();
2134
2135 let css_height: MultiValue<LayoutHeight> = match dom_id {
2136 Some(id) => get_css_height(ctx.styled_dom, id, &styled_node_state),
2137 None => MultiValue::Auto, };
2139
2140 let is_scroll_container = dom_id.map_or(false, |id| {
2148 let ov_x = get_overflow_x(ctx.styled_dom, id, &styled_node_state);
2149 let ov_y = get_overflow_y(ctx.styled_dom, id, &styled_node_state);
2150 matches!(ov_x, MultiValue::Exact(LayoutOverflow::Scroll) | MultiValue::Exact(LayoutOverflow::Auto))
2151 || matches!(ov_y, MultiValue::Exact(LayoutOverflow::Scroll) | MultiValue::Exact(LayoutOverflow::Auto))
2152 });
2153
2154 if should_use_content_height(&css_height) {
2155 let skip_expansion = is_scroll_container
2156 && containing_block_size.height.is_finite()
2157 && containing_block_size.height > 0.0;
2158
2159 if !skip_expansion {
2160 final_used_size = apply_content_based_height(
2161 final_used_size,
2162 content_size,
2163 tree,
2164 node_index,
2165 writing_mode,
2166 );
2167 }
2168 }
2169
2170 let skip_scrollbar_check = ctx.fragmentation_context.is_some();
2173 let scrollbar_info = match dom_id {
2174 Some(id) => compute_scrollbar_info(
2175 ctx,
2176 id,
2177 &styled_node_state,
2178 content_size,
2179 &box_props,
2180 final_used_size,
2181 writing_mode,
2182 ),
2183 None => ScrollbarRequirements::default(),
2184 };
2185
2186 if check_scrollbar_change(tree, node_index, &scrollbar_info, skip_scrollbar_check) {
2187 *reflow_needed_for_scrollbars = true;
2188 }
2189
2190 let merged_scrollbar_info = merge_scrollbar_info(tree, node_index, &scrollbar_info);
2191 let content_box_size = box_props.inner_size(final_used_size, writing_mode);
2192 let inner_size_after_scrollbars = merged_scrollbar_info.shrink_size(content_box_size);
2193
2194 let self_content_box_pos = {
2196 {
2197 let current_node = tree.get_mut(node_index).ok_or(LayoutError::InvalidTree)?;
2198
2199 let is_table_cell = matches!(
2201 current_node.formatting_context,
2202 FormattingContext::TableCell
2203 );
2204 if !is_table_cell || current_node.used_size.is_none() {
2205 current_node.used_size = Some(final_used_size);
2206 }
2207 }
2208
2209 if let Some(warm) = tree.warm_mut(node_index) {
2211 warm.scrollbar_info = Some(merged_scrollbar_info.clone());
2212 warm.overflow_content_size = Some(content_size);
2215 }
2216
2217 let current_node = tree.get(node_index).ok_or(LayoutError::InvalidTree)?;
2219 let current_bp = current_node.box_props.unpack();
2220 let pos = calculate_content_box_pos(containing_block_pos, ¤t_bp);
2221 log_content_box_calculation(ctx, node_index, current_node, containing_block_pos, pos);
2222 pos
2223 };
2224
2225 let is_flex_or_grid = {
2227 let node = tree.get(node_index).ok_or(LayoutError::InvalidTree)?;
2228 matches!(
2229 node.formatting_context,
2230 FormattingContext::Flex | FormattingContext::Grid
2231 )
2232 };
2233
2234 let positions: Vec<_> = layout_result
2237 .output
2238 .positions
2239 .iter()
2240 .map(|(&idx, &pos)| (idx, pos))
2241 .collect();
2242
2243 let child_positions_for_cache: Vec<(usize, LogicalPosition)> = positions.clone();
2245
2246 for (child_index, child_relative_pos) in positions {
2247 process_inflow_child(
2248 ctx,
2249 tree,
2250 text_cache,
2251 child_index,
2252 child_relative_pos,
2253 self_content_box_pos,
2254 inner_size_after_scrollbars,
2255 writing_mode,
2256 is_flex_or_grid,
2257 calculated_positions,
2258 reflow_needed_for_scrollbars,
2259 float_cache,
2260 )?;
2261 }
2262
2263 process_out_of_flow_children(
2265 ctx,
2266 tree,
2267 text_cache,
2268 node_index,
2269 self_content_box_pos,
2270 inner_size_after_scrollbars,
2271 calculated_positions,
2272 reflow_needed_for_scrollbars,
2273 float_cache,
2274 )?;
2275
2276 if node_index < ctx.cache_map.entries.len() {
2280 let warm_ref = tree.warm(node_index);
2281 let baseline = warm_ref.and_then(|n| n.baseline);
2282 let escaped_top = warm_ref.and_then(|n| n.escaped_top_margin);
2283 let escaped_bottom = warm_ref.and_then(|n| n.escaped_bottom_margin);
2284
2285 ctx.cache_map.get_mut(node_index).store_layout(LayoutCacheEntry {
2287 available_size: containing_block_size,
2288 result_size: final_used_size,
2289 content_size,
2290 child_positions: child_positions_for_cache.clone(),
2291 escaped_top_margin: escaped_top,
2292 escaped_bottom_margin: escaped_bottom,
2293 scrollbar_info: merged_scrollbar_info.clone(),
2294 });
2295
2296 ctx.cache_map.get_mut(node_index).store_size(0, SizingCacheEntry {
2300 available_size: containing_block_size,
2301 result_size: final_used_size,
2302 baseline,
2303 escaped_top_margin: escaped_top,
2304 escaped_bottom_margin: escaped_bottom,
2305 });
2306 }
2307
2308 Ok(())
2309}
2310
2311fn position_flex_child_descendants<T: ParsedFontTrait>(
2319 ctx: &mut LayoutContext<'_, T>,
2320 tree: &mut LayoutTree,
2321 text_cache: &mut TextLayoutCache,
2322 node_index: usize,
2323 content_box_pos: LogicalPosition,
2324 available_size: LogicalSize,
2325 calculated_positions: &mut super::PositionVec,
2326 reflow_needed_for_scrollbars: &mut bool,
2327 float_cache: &mut HashMap<usize, fc::FloatingContext>,
2328) -> Result<()> {
2329 let node = tree.get(node_index).ok_or(LayoutError::InvalidTree)?;
2330 let children: Vec<usize> = tree.children(node_index).to_vec();
2331 let fc = node.formatting_context.clone();
2332
2333 if matches!(fc, FormattingContext::Flex | FormattingContext::Grid) {
2336 for &child_index in &children {
2337 let child_node = tree.get(child_index).ok_or(LayoutError::InvalidTree)?;
2338 let child_rel_pos = tree.warm(child_index)
2339 .and_then(|w| w.relative_position)
2340 .unwrap_or_default();
2341 let child_abs_pos = LogicalPosition::new(
2342 content_box_pos.x + child_rel_pos.x,
2343 content_box_pos.y + child_rel_pos.y,
2344 );
2345
2346 super::pos_set(calculated_positions, child_index, child_abs_pos);
2348
2349 let cbp = child_node.box_props.unpack();
2351 let child_content_box = LogicalPosition::new(
2352 child_abs_pos.x
2353 + cbp.border.left
2354 + cbp.padding.left,
2355 child_abs_pos.y
2356 + cbp.border.top
2357 + cbp.padding.top,
2358 );
2359 let child_inner_size = cbp.inner_size(
2360 child_node.used_size.unwrap_or_default(),
2361 LayoutWritingMode::HorizontalTb,
2362 );
2363
2364 position_flex_child_descendants(
2366 ctx,
2367 tree,
2368 text_cache,
2369 child_index,
2370 child_content_box,
2371 child_inner_size,
2372 calculated_positions,
2373 reflow_needed_for_scrollbars,
2374 float_cache,
2375 )?;
2376 }
2377 } else {
2378 let node = tree.get(node_index).ok_or(LayoutError::InvalidTree)?;
2381 let children: Vec<usize> = tree.children(node_index).to_vec();
2382
2383 for &child_index in &children {
2384 let child_node = tree.get(child_index).ok_or(LayoutError::InvalidTree)?;
2385 let child_rel_pos = tree.warm(child_index)
2386 .and_then(|w| w.relative_position)
2387 .unwrap_or_default();
2388 let child_abs_pos = LogicalPosition::new(
2389 content_box_pos.x + child_rel_pos.x,
2390 content_box_pos.y + child_rel_pos.y,
2391 );
2392
2393 super::pos_set(calculated_positions, child_index, child_abs_pos);
2395
2396 let cbp = child_node.box_props.unpack();
2398 let child_content_box = LogicalPosition::new(
2399 child_abs_pos.x
2400 + cbp.border.left
2401 + cbp.padding.left,
2402 child_abs_pos.y
2403 + cbp.border.top
2404 + cbp.padding.top,
2405 );
2406 let child_inner_size = cbp.inner_size(
2407 child_node.used_size.unwrap_or_default(),
2408 LayoutWritingMode::HorizontalTb,
2409 );
2410
2411 position_flex_child_descendants(
2413 ctx,
2414 tree,
2415 text_cache,
2416 child_index,
2417 child_content_box,
2418 child_inner_size,
2419 calculated_positions,
2420 reflow_needed_for_scrollbars,
2421 float_cache,
2422 )?;
2423 }
2424 }
2425
2426 Ok(())
2427}
2428
2429fn should_use_content_height(css_height: &MultiValue<LayoutHeight>) -> bool {
2431 match css_height {
2432 MultiValue::Auto | MultiValue::Initial | MultiValue::Inherit => {
2433 true
2435 }
2436 MultiValue::Exact(height) => match height {
2437 LayoutHeight::Auto => {
2438 true
2440 }
2441 LayoutHeight::Px(px) => {
2442 use azul_css::props::basic::{pixel::PixelValue, SizeMetric};
2445 px == &PixelValue::zero()
2446 || (px.metric != SizeMetric::Px
2447 && px.metric != SizeMetric::Percent
2448 && px.metric != SizeMetric::Em
2449 && px.metric != SizeMetric::Rem)
2450 }
2451 LayoutHeight::MinContent | LayoutHeight::MaxContent | LayoutHeight::FitContent(_) => {
2452 true
2454 }
2455 LayoutHeight::Calc(_) => {
2456 false
2458 }
2459 },
2460 }
2461}
2462
2463fn apply_content_based_height(
2475 mut used_size: LogicalSize,
2476 content_size: LogicalSize,
2477 tree: &LayoutTree,
2478 node_index: usize,
2479 writing_mode: LayoutWritingMode,
2480) -> LogicalSize {
2481 let node_props = tree.get(node_index).unwrap().box_props.unpack();
2482 let main_axis_padding_border =
2483 node_props.padding.main_sum(writing_mode) + node_props.border.main_sum(writing_mode);
2484
2485 let old_main_size = used_size.main(writing_mode);
2487 let new_main_size = content_size.main(writing_mode) + main_axis_padding_border;
2488
2489 let final_main_size = old_main_size.max(new_main_size);
2492
2493 used_size = used_size.with_main(writing_mode, final_main_size);
2494
2495 used_size
2496}
2497
2498fn calculate_subtree_hash(node_self_hash: u64, child_hashes: &[u64]) -> SubtreeHash {
2501 let mut hasher = DefaultHasher::new();
2502 node_self_hash.hash(&mut hasher);
2503 child_hashes.hash(&mut hasher);
2504 SubtreeHash(hasher.finish())
2505}
2506
2507pub fn compute_counters(
2517 styled_dom: &StyledDom,
2518 tree: &LayoutTree,
2519 counters: &mut HashMap<(usize, String), i32>,
2520) {
2521 let mut counter_stacks: HashMap<String, Vec<i32>> = HashMap::new();
2524
2525 let mut scope_stack: Vec<Vec<String>> = Vec::new();
2528
2529 compute_counters_recursive(
2530 styled_dom,
2531 tree,
2532 tree.root,
2533 counters,
2534 &mut counter_stacks,
2535 &mut scope_stack,
2536 );
2537}
2538
2539fn compute_counters_recursive(
2540 styled_dom: &StyledDom,
2541 tree: &LayoutTree,
2542 node_idx: usize,
2543 counters: &mut HashMap<(usize, String), i32>,
2544 counter_stacks: &mut std::collections::HashMap<String, Vec<i32>>,
2545 scope_stack: &mut Vec<Vec<String>>,
2546) {
2547 let node = match tree.get(node_idx) {
2548 Some(n) => n,
2549 None => return,
2550 };
2551
2552 if tree.warm(node_idx).and_then(|w| w.pseudo_element.as_ref()).is_some() {
2556 if let Some(parent_idx) = node.parent {
2559 let parent_counters: Vec<_> = counters
2561 .iter()
2562 .filter(|((idx, _), _)| *idx == parent_idx)
2563 .map(|((_, name), &value)| (name.clone(), value))
2564 .collect();
2565
2566 for (counter_name, value) in parent_counters {
2567 counters.insert((node_idx, counter_name), value);
2568 }
2569 }
2570
2571 return;
2574 }
2575
2576 let dom_id = match node.dom_node_id {
2578 Some(id) => id,
2579 None => {
2580 for &child_idx in tree.children(node_idx) {
2582 compute_counters_recursive(
2583 styled_dom,
2584 tree,
2585 child_idx,
2586 counters,
2587 counter_stacks,
2588 scope_stack,
2589 );
2590 }
2591 return;
2592 }
2593 };
2594
2595 let node_data = &styled_dom.node_data.as_container()[dom_id];
2596 let node_state = &styled_dom.styled_nodes.as_container()[dom_id].styled_node_state;
2597 let cache = &styled_dom.css_property_cache.ptr;
2598
2599 let mut reset_counters_at_this_level = Vec::new();
2601
2602 let display = {
2605 use crate::solver3::getters::get_display_property;
2606 get_display_property(styled_dom, Some(dom_id)).exact()
2607 };
2608 let is_list_item = matches!(display, Some(LayoutDisplay::ListItem));
2609
2610 let has_counter_css = node_state.is_normal()
2613 && cache.compact_cache.as_ref().map_or(true, |cc| cc.has_counter(dom_id.index()));
2614
2615 let counter_reset = if has_counter_css {
2617 cache
2618 .get_counter_reset(node_data, &dom_id, node_state)
2619 .and_then(|v| v.get_property())
2620 } else {
2621 None
2622 };
2623
2624 if let Some(counter_reset) = counter_reset {
2625 let counter_name_str = counter_reset.counter_name.as_str();
2626 if counter_name_str != "none" {
2627 let counter_name = counter_name_str.to_string();
2628 let reset_value = counter_reset.value;
2629
2630 counter_stacks
2632 .entry(counter_name.clone())
2633 .or_default()
2634 .push(reset_value);
2635 reset_counters_at_this_level.push(counter_name);
2636 }
2637 }
2638
2639 let counter_inc = if has_counter_css {
2641 cache
2642 .get_counter_increment(node_data, &dom_id, node_state)
2643 .and_then(|v| v.get_property())
2644 } else {
2645 None
2646 };
2647
2648 if let Some(counter_inc) = counter_inc {
2649 let counter_name_str = counter_inc.counter_name.as_str();
2650 if counter_name_str != "none" {
2651 let counter_name = counter_name_str.to_string();
2652 let inc_value = counter_inc.value;
2653
2654 let stack = counter_stacks.entry(counter_name.clone()).or_default();
2656 if stack.is_empty() {
2657 stack.push(inc_value);
2659 } else if let Some(current) = stack.last_mut() {
2660 *current += inc_value;
2661 }
2662 }
2663 }
2664
2665 if is_list_item {
2667 let counter_name = "list-item".to_string();
2668 let stack = counter_stacks.entry(counter_name.clone()).or_default();
2669 if stack.is_empty() {
2670 stack.push(1);
2672 } else {
2673 if let Some(current) = stack.last_mut() {
2674 *current += 1;
2675 }
2676 }
2677 }
2678
2679 for (counter_name, stack) in counter_stacks.iter() {
2681 if let Some(&value) = stack.last() {
2682 counters.insert((node_idx, counter_name.clone()), value);
2683 }
2684 }
2685
2686 scope_stack.push(reset_counters_at_this_level.clone());
2688
2689 for &child_idx in tree.children(node_idx) {
2691 compute_counters_recursive(
2692 styled_dom,
2693 tree,
2694 child_idx,
2695 counters,
2696 counter_stacks,
2697 scope_stack,
2698 );
2699 }
2700
2701 if let Some(reset_counters) = scope_stack.pop() {
2703 for counter_name in reset_counters {
2704 if let Some(stack) = counter_stacks.get_mut(&counter_name) {
2705 stack.pop();
2706 }
2707 }
2708 }
2709}