1use std::{
17 collections::{BTreeMap, BTreeSet},
18 hash::{DefaultHasher, Hash, Hasher},
19};
20
21use azul_core::{
22 dom::{FormattingContext, NodeId, NodeType},
23 geom::{LogicalPosition, LogicalRect, LogicalSize},
24 styled_dom::{StyledDom, StyledNode},
25};
26use azul_css::{
27 css::CssPropertyValue,
28 props::{
29 layout::{
30 LayoutDisplay, LayoutFlexWrap, LayoutHeight, LayoutJustifyContent, LayoutOverflow,
31 LayoutPosition, LayoutWrap, LayoutWritingMode,
32 },
33 property::{CssProperty, CssPropertyType},
34 style::StyleTextAlign,
35 },
36 LayoutDebugMessage, LayoutDebugMessageType,
37};
38
39use crate::{
40 font_traits::{FontLoaderTrait, ParsedFontTrait, TextLayoutCache},
41 solver3::{
42 fc::{self, layout_formatting_context, LayoutConstraints, OverflowBehavior},
43 geometry::PositionedRectangle,
44 getters::{
45 get_css_height, get_display_property, get_justify_content, get_overflow_x,
46 get_overflow_y, get_text_align, get_white_space_property, get_wrap, get_writing_mode,
47 MultiValue,
48 },
49 layout_tree::{
50 is_block_level, AnonymousBoxType, LayoutNode, LayoutTreeBuilder, SubtreeHash,
51 },
52 positioning::get_position_type,
53 scrollbar::ScrollbarRequirements,
54 sizing::calculate_used_size_for_node,
55 LayoutContext, LayoutError, LayoutTree, Result,
56 },
57 text3::cache::AvailableSpace as Text3AvailableSpace,
58};
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
82pub enum ComputeMode {
83 ComputeSize,
86 PerformLayout,
90}
91
92#[derive(Debug, Clone, Copy, PartialEq, Eq)]
98pub enum AvailableWidthType {
99 Definite,
101 MinContent,
103 MaxContent,
105}
106
107#[derive(Debug, Clone)]
113pub struct SizingCacheEntry {
114 pub available_size: LogicalSize,
116 pub result_size: LogicalSize,
118 pub baseline: Option<f32>,
120 pub escaped_top_margin: Option<f32>,
122 pub escaped_bottom_margin: Option<f32>,
124}
125
126#[derive(Debug, Clone)]
131pub struct LayoutCacheEntry {
132 pub available_size: LogicalSize,
134 pub result_size: LogicalSize,
136 pub content_size: LogicalSize,
138 pub child_positions: Vec<(usize, LogicalPosition)>,
140 pub escaped_top_margin: Option<f32>,
142 pub escaped_bottom_margin: Option<f32>,
144 pub scrollbar_info: ScrollbarRequirements,
146}
147
148#[derive(Debug, Clone)]
157pub struct NodeCache {
158 pub measure_entries: [Option<SizingCacheEntry>; 9],
164
165 pub layout_entry: Option<LayoutCacheEntry>,
168
169 pub is_empty: bool,
172}
173
174impl Default for NodeCache {
175 fn default() -> Self {
176 Self {
177 measure_entries: [None, None, None, None, None, None, None, None, None],
178 layout_entry: None,
179 is_empty: true, }
181 }
182}
183
184impl NodeCache {
185 pub fn clear(&mut self) {
187 self.measure_entries = [None, None, None, None, None, None, None, None, None];
188 self.layout_entry = None;
189 self.is_empty = true;
190 }
191
192 pub fn slot_index(
198 width_known: bool,
199 height_known: bool,
200 width_type: AvailableWidthType,
201 height_type: AvailableWidthType,
202 ) -> usize {
203 match (width_known, height_known) {
204 (true, true) => 0,
205 (true, false) => {
206 if width_type == AvailableWidthType::MinContent { 2 } else { 1 }
207 }
208 (false, true) => {
209 if height_type == AvailableWidthType::MinContent { 4 } else { 3 }
210 }
211 (false, false) => {
212 let w = if width_type == AvailableWidthType::MinContent { 1 } else { 0 };
213 let h = if height_type == AvailableWidthType::MinContent { 1 } else { 0 };
214 5 + w * 2 + h
215 }
216 }
217 }
218
219 pub fn get_size(&self, slot: usize, known_dims: LogicalSize) -> Option<&SizingCacheEntry> {
223 let entry = self.measure_entries[slot].as_ref()?;
224 if (known_dims.width - entry.available_size.width).abs() < 0.1
226 && (known_dims.height - entry.available_size.height).abs() < 0.1
227 {
228 return Some(entry);
229 }
230 if (known_dims.width - entry.result_size.width).abs() < 0.1
235 && (known_dims.height - entry.result_size.height).abs() < 0.1
236 {
237 return Some(entry);
238 }
239 None
240 }
241
242 pub fn store_size(&mut self, slot: usize, entry: SizingCacheEntry) {
244 self.measure_entries[slot] = Some(entry);
245 self.is_empty = false;
246 }
247
248 pub fn get_layout(&self, known_dims: LogicalSize) -> Option<&LayoutCacheEntry> {
250 let entry = self.layout_entry.as_ref()?;
251 if (known_dims.width - entry.available_size.width).abs() < 0.1
252 && (known_dims.height - entry.available_size.height).abs() < 0.1
253 {
254 return Some(entry);
255 }
256 if (known_dims.width - entry.result_size.width).abs() < 0.1
258 && (known_dims.height - entry.result_size.height).abs() < 0.1
259 {
260 return Some(entry);
261 }
262 None
263 }
264
265 pub fn store_layout(&mut self, entry: LayoutCacheEntry) {
267 self.layout_entry = Some(entry);
268 self.is_empty = false;
269 }
270}
271
272#[derive(Debug, Clone, Default)]
283pub struct LayoutCacheMap {
284 pub entries: Vec<NodeCache>,
285}
286
287impl LayoutCacheMap {
288 pub fn resize_to_tree(&mut self, tree_len: usize) {
291 self.entries.resize_with(tree_len, NodeCache::default);
292 }
293
294 #[inline]
296 pub fn get(&self, node_index: usize) -> &NodeCache {
297 &self.entries[node_index]
298 }
299
300 #[inline]
302 pub fn get_mut(&mut self, node_index: usize) -> &mut NodeCache {
303 &mut self.entries[node_index]
304 }
305
306 pub fn mark_dirty(&mut self, node_index: usize, tree: &[LayoutNode]) {
313 if node_index >= self.entries.len() {
314 return;
315 }
316 let cache = &mut self.entries[node_index];
317 if cache.is_empty {
318 return; }
320 cache.clear();
321
322 let mut current = tree.get(node_index).and_then(|n| n.parent);
324 while let Some(parent_idx) = current {
325 if parent_idx >= self.entries.len() {
326 break;
327 }
328 let parent_cache = &mut self.entries[parent_idx];
329 if parent_cache.is_empty {
330 break; }
332 parent_cache.clear();
333 current = tree.get(parent_idx).and_then(|n| n.parent);
334 }
335 }
336}
337
338#[derive(Debug, Clone, Default)]
340pub struct LayoutCache {
341 pub tree: Option<LayoutTree>,
343 pub calculated_positions: super::PositionVec,
345 pub viewport: Option<LogicalRect>,
347 pub scroll_ids: BTreeMap<usize, u64>,
349 pub scroll_id_to_node_id: BTreeMap<u64, NodeId>,
351 pub counters: BTreeMap<(usize, String), i32>,
356 pub float_cache: BTreeMap<usize, fc::FloatingContext>,
361 pub cache_map: LayoutCacheMap,
365}
366
367#[derive(Debug, Default)]
369pub struct ReconciliationResult {
370 pub intrinsic_dirty: BTreeSet<usize>,
372 pub layout_roots: BTreeSet<usize>,
374}
375
376impl ReconciliationResult {
377 pub fn is_clean(&self) -> bool {
379 self.intrinsic_dirty.is_empty() && self.layout_roots.is_empty()
380 }
381}
382
383pub fn reposition_clean_subtrees(
391 styled_dom: &StyledDom,
392 tree: &LayoutTree,
393 layout_roots: &BTreeSet<usize>,
394 calculated_positions: &mut super::PositionVec,
395) {
396 let mut parents_to_reposition = BTreeSet::new();
399 for &root_idx in layout_roots {
400 if let Some(parent_idx) = tree.get(root_idx).and_then(|n| n.parent) {
401 parents_to_reposition.insert(parent_idx);
402 }
403 }
404
405 for parent_idx in parents_to_reposition {
406 let parent_node = match tree.get(parent_idx) {
407 Some(n) => n,
408 None => continue,
409 };
410
411 match parent_node.formatting_context {
413 FormattingContext::Block { .. } | FormattingContext::TableRowGroup => {
415 reposition_block_flow_siblings(
416 styled_dom,
417 parent_idx,
418 parent_node,
419 tree,
420 layout_roots,
421 calculated_positions,
422 );
423 }
424
425 FormattingContext::Flex | FormattingContext::Grid => {
426 }
430
431 FormattingContext::Table | FormattingContext::TableRow => {
432 }
436
437 _ => { }
441 }
442 }
443}
444
445pub fn to_overflow_behavior(overflow: MultiValue<LayoutOverflow>) -> fc::OverflowBehavior {
448 match overflow.unwrap_or(LayoutOverflow::Visible) {
449 LayoutOverflow::Visible => fc::OverflowBehavior::Visible,
450 LayoutOverflow::Hidden | LayoutOverflow::Clip => fc::OverflowBehavior::Hidden,
451 LayoutOverflow::Scroll => fc::OverflowBehavior::Scroll,
452 LayoutOverflow::Auto => fc::OverflowBehavior::Auto,
453 }
454}
455
456pub const fn style_text_align_to_fc(text_align: StyleTextAlign) -> fc::TextAlign {
458 match text_align {
459 StyleTextAlign::Start | StyleTextAlign::Left => fc::TextAlign::Start,
460 StyleTextAlign::End | StyleTextAlign::Right => fc::TextAlign::End,
461 StyleTextAlign::Center => fc::TextAlign::Center,
462 StyleTextAlign::Justify => fc::TextAlign::Justify,
463 }
464}
465
466pub fn collect_children_dom_ids(styled_dom: &StyledDom, parent_dom_id: NodeId) -> Vec<NodeId> {
470 let hierarchy_container = styled_dom.node_hierarchy.as_container();
471 let mut children = Vec::new();
472
473 let Some(hierarchy_item) = hierarchy_container.get(parent_dom_id) else {
474 return children;
475 };
476
477 let Some(mut child_id) = hierarchy_item.first_child_id(parent_dom_id) else {
478 return children;
479 };
480
481 children.push(child_id);
482 while let Some(hierarchy_item) = hierarchy_container.get(child_id) {
483 let Some(next) = hierarchy_item.next_sibling_id() else {
484 break;
485 };
486 children.push(next);
487 child_id = next;
488 }
489
490 children
491}
492
493pub fn is_simple_flex_stack(styled_dom: &StyledDom, dom_id: Option<NodeId>) -> bool {
496 let Some(id) = dom_id else { return false };
497 let binding = styled_dom.styled_nodes.as_container();
498 let styled_node = match binding.get(id) {
499 Some(styled_node) => styled_node,
500 None => return false,
501 };
502
503 let wrap = get_wrap(styled_dom, id, &styled_node.styled_node_state);
505
506 if wrap.unwrap_or_default() != LayoutFlexWrap::NoWrap {
507 return false;
508 }
509
510 let justify = get_justify_content(styled_dom, id, &styled_node.styled_node_state);
512
513 if !matches!(
514 justify.unwrap_or_default(),
515 LayoutJustifyContent::FlexStart | LayoutJustifyContent::Start
516 ) {
517 return false;
518 }
519
520 true
529}
530
531pub fn reposition_block_flow_siblings(
535 styled_dom: &StyledDom,
536 parent_idx: usize,
537 parent_node: &LayoutNode,
538 tree: &LayoutTree,
539 layout_roots: &BTreeSet<usize>,
540 calculated_positions: &mut super::PositionVec,
541) {
542 let dom_id = parent_node.dom_node_id.unwrap_or(NodeId::ZERO);
543 let styled_node_state = styled_dom
544 .styled_nodes
545 .as_container()
546 .get(dom_id)
547 .map(|n| n.styled_node_state.clone())
548 .unwrap_or_default();
549
550 let writing_mode = get_writing_mode(styled_dom, dom_id, &styled_node_state).unwrap_or_default();
551
552 let parent_pos = calculated_positions
553 .get(parent_idx)
554 .copied()
555 .unwrap_or_default();
556
557 let content_box_origin = LogicalPosition::new(
558 parent_pos.x + parent_node.box_props.padding.left,
559 parent_pos.y + parent_node.box_props.padding.top,
560 );
561
562 let mut main_pen = 0.0;
563
564 for &child_idx in &parent_node.children {
565 let child_node = match tree.get(child_idx) {
566 Some(n) => n,
567 None => continue,
568 };
569
570 let child_size = child_node.used_size.unwrap_or_default();
571 let child_main_sum = child_node.box_props.margin.main_sum(writing_mode);
572 let margin_box_main_size = child_size.main(writing_mode) + child_main_sum;
573
574 if layout_roots.contains(&child_idx) {
575 let new_pos = match calculated_positions.get(child_idx) {
578 Some(p) => *p,
579 None => continue,
580 };
581
582 let main_axis_offset = if writing_mode.is_vertical() {
583 new_pos.x - content_box_origin.x
584 } else {
585 new_pos.y - content_box_origin.y
586 };
587
588 main_pen = main_axis_offset
589 + child_size.main(writing_mode)
590 + child_node.box_props.margin.main_end(writing_mode);
591 } else {
592 let old_pos = match calculated_positions.get(child_idx) {
595 Some(p) => *p,
596 None => continue,
597 };
598
599 let child_main_start = child_node.box_props.margin.main_start(writing_mode);
600 let new_main_pos = main_pen + child_main_start;
601 let old_relative_pos = child_node.relative_position.unwrap_or_default();
602 let cross_pos = if writing_mode.is_vertical() {
603 old_relative_pos.y
604 } else {
605 old_relative_pos.x
606 };
607 let new_relative_pos =
608 LogicalPosition::from_main_cross(new_main_pos, cross_pos, writing_mode);
609
610 let new_absolute_pos = LogicalPosition::new(
611 content_box_origin.x + new_relative_pos.x,
612 content_box_origin.y + new_relative_pos.y,
613 );
614
615 if old_pos != new_absolute_pos {
616 let delta = LogicalPosition::new(
617 new_absolute_pos.x - old_pos.x,
618 new_absolute_pos.y - old_pos.y,
619 );
620 shift_subtree_position(child_idx, delta, tree, calculated_positions);
621 }
622
623 main_pen += margin_box_main_size;
624 }
625 }
626}
627
628pub fn shift_subtree_position(
630 node_idx: usize,
631 delta: LogicalPosition,
632 tree: &LayoutTree,
633 calculated_positions: &mut super::PositionVec,
634) {
635 if let Some(pos) = calculated_positions.get_mut(node_idx) {
636 pos.x += delta.x;
637 pos.y += delta.y;
638 }
639
640 if let Some(node) = tree.get(node_idx) {
641 for &child_idx in &node.children {
642 shift_subtree_position(child_idx, delta, tree, calculated_positions);
643 }
644 }
645}
646
647pub fn reconcile_and_invalidate<T: ParsedFontTrait>(
650 ctx: &mut LayoutContext<'_, T>,
651 cache: &LayoutCache,
652 viewport: LogicalRect,
653) -> Result<(LayoutTree, ReconciliationResult)> {
654 let mut new_tree_builder = LayoutTreeBuilder::new(ctx.viewport_size);
655 let mut recon_result = ReconciliationResult::default();
656 let old_tree = cache.tree.as_ref();
657
658 if cache.viewport.map_or(true, |v| v.size != viewport.size) {
660 recon_result.layout_roots.insert(0); }
662
663 let root_dom_id = ctx
664 .styled_dom
665 .root
666 .into_crate_internal()
667 .unwrap_or(NodeId::ZERO);
668 let root_idx = reconcile_recursive(
669 ctx.styled_dom,
670 root_dom_id,
671 old_tree.map(|t| t.root),
672 None,
673 old_tree,
674 &mut new_tree_builder,
675 &mut recon_result,
676 &mut ctx.debug_messages,
677 )?;
678
679 let final_layout_roots = recon_result
681 .layout_roots
682 .iter()
683 .filter(|&&idx| {
684 let mut current = new_tree_builder.get(idx).and_then(|n| n.parent);
685 while let Some(p_idx) = current {
686 if recon_result.layout_roots.contains(&p_idx) {
687 return false;
688 }
689 current = new_tree_builder.get(p_idx).and_then(|n| n.parent);
690 }
691 true
692 })
693 .copied()
694 .collect();
695 recon_result.layout_roots = final_layout_roots;
696
697 let new_tree = new_tree_builder.build(root_idx);
698 Ok((new_tree, recon_result))
699}
700
701fn is_whitespace_only_inline_run(
712 styled_dom: &StyledDom,
713 inline_run: &[(usize, NodeId)],
714 parent_dom_id: NodeId,
715) -> bool {
716 use azul_css::props::style::text::StyleWhiteSpace;
717
718 if inline_run.is_empty() {
719 return true;
720 }
721
722 let parent_state = &styled_dom.styled_nodes.as_container()[parent_dom_id].styled_node_state;
724 let white_space = match get_white_space_property(styled_dom, parent_dom_id, parent_state) {
725 MultiValue::Exact(ws) => Some(ws),
726 _ => None,
727 };
728
729 if matches!(
731 white_space,
732 Some(StyleWhiteSpace::Pre) | Some(StyleWhiteSpace::PreWrap) | Some(StyleWhiteSpace::PreLine)
733 ) {
734 return false;
735 }
736
737 let binding = styled_dom.node_data.as_container();
739 for &(_, dom_id) in inline_run {
740 if let Some(data) = binding.get(dom_id) {
741 match data.get_node_type() {
742 NodeType::Text(text) => {
743 let s = text.as_str();
744 if !s.chars().all(|c| c.is_whitespace()) {
745 return false; }
747 }
748 _ => {
749 return false; }
751 }
752 }
753 }
754
755 true }
757
758pub fn reconcile_recursive(
760 styled_dom: &StyledDom,
761 new_dom_id: NodeId,
762 old_tree_idx: Option<usize>,
763 new_parent_idx: Option<usize>,
764 old_tree: Option<&LayoutTree>,
765 new_tree_builder: &mut LayoutTreeBuilder,
766 recon: &mut ReconciliationResult,
767 debug_messages: &mut Option<Vec<LayoutDebugMessage>>,
768) -> Result<usize> {
769 let node_data = &styled_dom.node_data.as_container()[new_dom_id];
770
771 let old_node = old_tree.and_then(|t| old_tree_idx.and_then(|idx| t.get(idx)));
772 let new_node_data_hash = hash_styled_node_data(styled_dom, new_dom_id);
773
774 let is_dirty = old_node.map_or(true, |n| new_node_data_hash != n.node_data_hash);
777
778 let new_node_idx = if is_dirty {
779 new_tree_builder.create_node_from_dom(
780 styled_dom,
781 new_dom_id,
782 new_parent_idx,
783 debug_messages,
784 )?
785 } else {
786 new_tree_builder.clone_node_from_old(old_node.unwrap(), new_parent_idx)
787 };
788
789 {
793 use crate::solver3::getters::get_display_property;
794 let display = get_display_property(styled_dom, Some(new_dom_id))
795 .exact();
796
797 if matches!(display, Some(LayoutDisplay::ListItem)) {
798 new_tree_builder.create_marker_pseudo_element(styled_dom, new_dom_id, new_node_idx);
800 }
801 }
802
803 let new_children_dom_ids: Vec<_> = collect_children_dom_ids(styled_dom, new_dom_id);
805 let old_children_indices: Vec<_> = old_node.map(|n| n.children.clone()).unwrap_or_default();
806
807 let mut children_are_different = new_children_dom_ids.len() != old_children_indices.len();
808 let mut new_child_hashes = Vec::new();
809
810 let has_block_child = new_children_dom_ids
820 .iter()
821 .any(|&id| is_block_level(styled_dom, id));
822
823 if !has_block_child {
824 for (i, &new_child_dom_id) in new_children_dom_ids.iter().enumerate() {
827 let old_child_idx = old_children_indices.get(i).copied();
828
829 let reconciled_child_idx = reconcile_recursive(
830 styled_dom,
831 new_child_dom_id,
832 old_child_idx,
833 Some(new_node_idx),
834 old_tree,
835 new_tree_builder,
836 recon,
837 debug_messages,
838 )?;
839 if let Some(child_node) = new_tree_builder.get(reconciled_child_idx) {
840 new_child_hashes.push(child_node.subtree_hash.0);
841 }
842
843 if old_tree.and_then(|t| t.get(old_child_idx?).map(|n| n.subtree_hash))
844 != new_tree_builder
845 .get(reconciled_child_idx)
846 .map(|n| n.subtree_hash)
847 {
848 children_are_different = true;
849 }
850 }
851 } else {
852 if let Some(msgs) = debug_messages.as_mut() {
856 msgs.push(LayoutDebugMessage::info(format!(
857 "[reconcile_recursive] Mixed content in node {}: creating anonymous IFC wrappers",
858 new_dom_id.index()
859 )));
860 }
861
862 let mut inline_run: Vec<(usize, NodeId)> = Vec::new(); for (i, &new_child_dom_id) in new_children_dom_ids.iter().enumerate() {
865 if is_block_level(styled_dom, new_child_dom_id) {
866 if !inline_run.is_empty() {
868 if is_whitespace_only_inline_run(styled_dom, &inline_run, new_dom_id) {
873 if let Some(msgs) = debug_messages.as_mut() {
874 msgs.push(LayoutDebugMessage::info(format!(
875 "[reconcile_recursive] Skipping whitespace-only inline run ({} nodes) between blocks in node {}",
876 inline_run.len(),
877 new_dom_id.index()
878 )));
879 }
880 inline_run.clear();
881 } else {
882 let anon_idx = new_tree_builder.create_anonymous_node(
885 new_node_idx,
886 AnonymousBoxType::InlineWrapper,
887 FormattingContext::Inline, );
889
890 if let Some(msgs) = debug_messages.as_mut() {
891 msgs.push(LayoutDebugMessage::info(format!(
892 "[reconcile_recursive] Created anonymous IFC wrapper (layout_idx={}) for {} inline children: {:?}",
893 anon_idx,
894 inline_run.len(),
895 inline_run.iter().map(|(_, id)| id.index()).collect::<Vec<_>>()
896 )));
897 }
898
899 for (pos, inline_dom_id) in inline_run.drain(..) {
901 let old_child_idx = old_children_indices.get(pos).copied();
902 let reconciled_child_idx = reconcile_recursive(
903 styled_dom,
904 inline_dom_id,
905 old_child_idx,
906 Some(anon_idx), old_tree,
908 new_tree_builder,
909 recon,
910 debug_messages,
911 )?;
912 if let Some(child_node) = new_tree_builder.get(reconciled_child_idx) {
913 new_child_hashes.push(child_node.subtree_hash.0);
914 }
915 }
916
917 recon.intrinsic_dirty.insert(anon_idx);
919 children_are_different = true;
920 } }
922
923 let old_child_idx = old_children_indices.get(i).copied();
925 let reconciled_child_idx = reconcile_recursive(
926 styled_dom,
927 new_child_dom_id,
928 old_child_idx,
929 Some(new_node_idx),
930 old_tree,
931 new_tree_builder,
932 recon,
933 debug_messages,
934 )?;
935 if let Some(child_node) = new_tree_builder.get(reconciled_child_idx) {
936 new_child_hashes.push(child_node.subtree_hash.0);
937 }
938
939 if old_tree.and_then(|t| t.get(old_child_idx?).map(|n| n.subtree_hash))
940 != new_tree_builder
941 .get(reconciled_child_idx)
942 .map(|n| n.subtree_hash)
943 {
944 children_are_different = true;
945 }
946 } else {
947 inline_run.push((i, new_child_dom_id));
949 }
950 }
951
952 if !inline_run.is_empty() {
954 if is_whitespace_only_inline_run(styled_dom, &inline_run, new_dom_id) {
956 if let Some(msgs) = debug_messages.as_mut() {
957 msgs.push(LayoutDebugMessage::info(format!(
958 "[reconcile_recursive] Skipping trailing whitespace-only inline run ({} nodes) in node {}",
959 inline_run.len(),
960 new_dom_id.index()
961 )));
962 }
963 } else {
965 let anon_idx = new_tree_builder.create_anonymous_node(
966 new_node_idx,
967 AnonymousBoxType::InlineWrapper,
968 FormattingContext::Inline, );
970
971 if let Some(msgs) = debug_messages.as_mut() {
972 msgs.push(LayoutDebugMessage::info(format!(
973 "[reconcile_recursive] Created trailing anonymous IFC wrapper (layout_idx={}) for {} inline children: {:?}",
974 anon_idx,
975 inline_run.len(),
976 inline_run.iter().map(|(_, id)| id.index()).collect::<Vec<_>>()
977 )));
978 }
979
980 for (pos, inline_dom_id) in inline_run.drain(..) {
981 let old_child_idx = old_children_indices.get(pos).copied();
982 let reconciled_child_idx = reconcile_recursive(
983 styled_dom,
984 inline_dom_id,
985 old_child_idx,
986 Some(anon_idx),
987 old_tree,
988 new_tree_builder,
989 recon,
990 debug_messages,
991 )?;
992 if let Some(child_node) = new_tree_builder.get(reconciled_child_idx) {
993 new_child_hashes.push(child_node.subtree_hash.0);
994 }
995 }
996
997 recon.intrinsic_dirty.insert(anon_idx);
998 children_are_different = true;
999 } }
1001 }
1002
1003 let final_subtree_hash = calculate_subtree_hash(new_node_data_hash, &new_child_hashes);
1005 if let Some(current_node) = new_tree_builder.get_mut(new_node_idx) {
1006 current_node.subtree_hash = final_subtree_hash;
1007 }
1008
1009 if is_dirty || children_are_different {
1011 recon.intrinsic_dirty.insert(new_node_idx);
1012 recon.layout_roots.insert(new_node_idx);
1013 }
1014
1015 Ok(new_node_idx)
1016}
1017
1018struct PreparedLayoutContext<'a> {
1021 constraints: LayoutConstraints<'a>,
1022 dom_id: Option<NodeId>,
1024 writing_mode: LayoutWritingMode,
1025 final_used_size: LogicalSize,
1026 box_props: crate::solver3::geometry::BoxProps,
1027}
1028
1029fn prepare_layout_context<'a, T: ParsedFontTrait>(
1035 ctx: &LayoutContext<'a, T>,
1036 node: &LayoutNode,
1037 containing_block_size: LogicalSize,
1038) -> Result<PreparedLayoutContext<'a>> {
1039 let dom_id = node.dom_node_id; let intrinsic = node.intrinsic_sizes.clone().unwrap_or_default();
1046 let final_used_size = calculate_used_size_for_node(
1047 ctx.styled_dom,
1048 dom_id, containing_block_size,
1050 intrinsic,
1051 &node.box_props,
1052 ctx.viewport_size,
1053 )?;
1054
1055 let writing_mode = node.computed_style.writing_mode;
1058 let text_align = node.computed_style.text_align;
1059 let display = node.computed_style.display;
1060 let overflow_y = node.computed_style.overflow_y;
1061
1062 let height_is_auto = node.computed_style.height.is_none();
1064
1065 let available_size_for_children = if height_is_auto {
1066 let inner_size = node.box_props.inner_size(final_used_size, writing_mode);
1068
1069 let available_width = match display {
1073 LayoutDisplay::Inline => containing_block_size.width,
1074 _ => inner_size.width,
1075 };
1076
1077 LogicalSize {
1078 width: available_width,
1079 height: containing_block_size.height,
1081 }
1082 } else {
1083 node.box_props.inner_size(final_used_size, writing_mode)
1085 };
1086
1087 let scrollbar_reservation = match overflow_y {
1090 LayoutOverflow::Scroll | LayoutOverflow::Auto => {
1091 dom_id.map(|id| {
1092 let styled_node_state = ctx.styled_dom.styled_nodes.as_container()
1093 .get(id).map(|s| s.styled_node_state.clone()).unwrap_or_default();
1094 crate::solver3::getters::get_layout_scrollbar_width_px(ctx, id, &styled_node_state)
1095 }).unwrap_or(fc::DEFAULT_SCROLLBAR_WIDTH_PX)
1096 }
1097 _ => 0.0,
1098 };
1099
1100 let available_size_for_children = if scrollbar_reservation > 0.0 {
1102 LogicalSize {
1103 width: (available_size_for_children.width - scrollbar_reservation).max(0.0),
1104 height: available_size_for_children.height,
1105 }
1106 } else {
1107 available_size_for_children
1108 };
1109
1110 let constraints = LayoutConstraints {
1111 available_size: available_size_for_children,
1112 bfc_state: None,
1113 writing_mode,
1114 text_align: style_text_align_to_fc(text_align),
1115 containing_block_size,
1116 available_width_type: Text3AvailableSpace::Definite(available_size_for_children.width),
1117 };
1118
1119 Ok(PreparedLayoutContext {
1120 constraints,
1121 dom_id,
1122 writing_mode,
1123 final_used_size,
1124 box_props: node.box_props.clone(),
1125 })
1126}
1127
1128fn compute_scrollbar_info<T: ParsedFontTrait>(
1134 ctx: &LayoutContext<'_, T>,
1135 dom_id: NodeId,
1136 styled_node_state: &azul_core::styled_dom::StyledNodeState,
1137 content_size: LogicalSize,
1138 box_props: &crate::solver3::geometry::BoxProps,
1139 final_used_size: LogicalSize,
1140 writing_mode: LayoutWritingMode,
1141) -> ScrollbarRequirements {
1142 if ctx.fragmentation_context.is_some() {
1144 return ScrollbarRequirements::default();
1145 }
1146
1147 let overflow_x = get_overflow_x(ctx.styled_dom, dom_id, styled_node_state);
1148 let overflow_y = get_overflow_y(ctx.styled_dom, dom_id, styled_node_state);
1149
1150 let container_size = box_props.inner_size(final_used_size, writing_mode);
1151
1152 let scrollbar_width_px =
1154 crate::solver3::getters::get_layout_scrollbar_width_px(ctx, dom_id, styled_node_state);
1155
1156 fc::check_scrollbar_necessity(
1157 content_size,
1158 container_size,
1159 to_overflow_behavior(overflow_x),
1160 to_overflow_behavior(overflow_y),
1161 scrollbar_width_px,
1162 )
1163}
1164
1165fn check_scrollbar_change(
1174 tree: &LayoutTree,
1175 node_index: usize,
1176 scrollbar_info: &ScrollbarRequirements,
1177 skip_scrollbar_check: bool,
1178) -> bool {
1179 if skip_scrollbar_check {
1180 return false;
1181 }
1182
1183 let Some(current_node) = tree.get(node_index) else {
1184 return false;
1185 };
1186
1187 match ¤t_node.scrollbar_info {
1188 None => scrollbar_info.needs_reflow(),
1189 Some(old_info) => {
1190 let adding_horizontal = !old_info.needs_horizontal && scrollbar_info.needs_horizontal;
1192 let adding_vertical = !old_info.needs_vertical && scrollbar_info.needs_vertical;
1193 adding_horizontal || adding_vertical
1194 }
1195 }
1196}
1197
1198fn merge_scrollbar_info(
1203 tree: &LayoutTree,
1204 node_index: usize,
1205 new_info: &ScrollbarRequirements,
1206) -> ScrollbarRequirements {
1207 let Some(current_node) = tree.get(node_index) else {
1208 return new_info.clone();
1209 };
1210
1211 match ¤t_node.scrollbar_info {
1212 Some(old) => {
1213 let effective_width = if new_info.scrollbar_width > 0.0 {
1216 new_info.scrollbar_width
1217 } else {
1218 old.scrollbar_width
1219 };
1220 let effective_height = if new_info.scrollbar_height > 0.0 {
1221 new_info.scrollbar_height
1222 } else {
1223 old.scrollbar_height
1224 };
1225
1226 ScrollbarRequirements {
1227 needs_horizontal: old.needs_horizontal || new_info.needs_horizontal,
1228 needs_vertical: old.needs_vertical || new_info.needs_vertical,
1229 scrollbar_width: if old.needs_vertical || new_info.needs_vertical {
1230 effective_width
1231 } else {
1232 0.0
1233 },
1234 scrollbar_height: if old.needs_horizontal || new_info.needs_horizontal {
1235 effective_height
1236 } else {
1237 0.0
1238 },
1239 }
1240 }
1241 None => new_info.clone(),
1242 }
1243}
1244
1245fn calculate_content_box_pos(
1250 containing_block_pos: LogicalPosition,
1251 box_props: &crate::solver3::geometry::BoxProps,
1252) -> LogicalPosition {
1253 LogicalPosition::new(
1254 containing_block_pos.x + box_props.border.left + box_props.padding.left,
1255 containing_block_pos.y + box_props.border.top + box_props.padding.top,
1256 )
1257}
1258
1259fn log_content_box_calculation<T: ParsedFontTrait>(
1261 ctx: &mut LayoutContext<'_, T>,
1262 node_index: usize,
1263 current_node: &LayoutNode,
1264 containing_block_pos: LogicalPosition,
1265 self_content_box_pos: LogicalPosition,
1266) {
1267 let Some(debug_msgs) = ctx.debug_messages.as_mut() else {
1268 return;
1269 };
1270
1271 let dom_name = current_node
1272 .dom_node_id
1273 .and_then(|id| {
1274 ctx.styled_dom
1275 .node_data
1276 .as_container()
1277 .internal
1278 .get(id.index())
1279 })
1280 .map(|n| format!("{:?}", n.node_type))
1281 .unwrap_or_else(|| "Unknown".to_string());
1282
1283 debug_msgs.push(LayoutDebugMessage::new(
1284 LayoutDebugMessageType::PositionCalculation,
1285 format!(
1286 "[CONTENT BOX {}] {} - margin-box pos=({:.2}, {:.2}) + border=({:.2},{:.2}) + \
1287 padding=({:.2},{:.2}) = content-box pos=({:.2}, {:.2})",
1288 node_index,
1289 dom_name,
1290 containing_block_pos.x,
1291 containing_block_pos.y,
1292 current_node.box_props.border.left,
1293 current_node.box_props.border.top,
1294 current_node.box_props.padding.left,
1295 current_node.box_props.padding.top,
1296 self_content_box_pos.x,
1297 self_content_box_pos.y
1298 ),
1299 ));
1300}
1301
1302fn log_child_positioning<T: ParsedFontTrait>(
1304 ctx: &mut LayoutContext<'_, T>,
1305 child_index: usize,
1306 child_node: &LayoutNode,
1307 self_content_box_pos: LogicalPosition,
1308 child_relative_pos: LogicalPosition,
1309 child_absolute_pos: LogicalPosition,
1310) {
1311 let child_dom_name = child_node
1313 .dom_node_id
1314 .and_then(|id| {
1315 ctx.styled_dom
1316 .node_data
1317 .as_container()
1318 .internal
1319 .get(id.index())
1320 })
1321 .map(|n| format!("{:?}", n.node_type))
1322 .unwrap_or_else(|| "Unknown".to_string());
1323
1324 let Some(debug_msgs) = ctx.debug_messages.as_mut() else {
1325 return;
1326 };
1327
1328 debug_msgs.push(LayoutDebugMessage::new(
1329 LayoutDebugMessageType::PositionCalculation,
1330 format!(
1331 "[CHILD POS {}] {} - parent content-box=({:.2}, {:.2}) + relative=({:.2}, {:.2}) + \
1332 margin=({:.2}, {:.2}) = absolute=({:.2}, {:.2})",
1333 child_index,
1334 child_dom_name,
1335 self_content_box_pos.x,
1336 self_content_box_pos.y,
1337 child_relative_pos.x,
1338 child_relative_pos.y,
1339 child_node.box_props.margin.left,
1340 child_node.box_props.margin.top,
1341 child_absolute_pos.x,
1342 child_absolute_pos.y
1343 ),
1344 ));
1345}
1346
1347fn process_inflow_child<T: ParsedFontTrait>(
1354 ctx: &mut LayoutContext<'_, T>,
1355 tree: &mut LayoutTree,
1356 text_cache: &mut TextLayoutCache,
1357 child_index: usize,
1358 child_relative_pos: LogicalPosition,
1359 self_content_box_pos: LogicalPosition,
1360 inner_size_after_scrollbars: LogicalSize,
1361 writing_mode: LayoutWritingMode,
1362 is_flex_or_grid: bool,
1363 calculated_positions: &mut super::PositionVec,
1364 reflow_needed_for_scrollbars: &mut bool,
1365 float_cache: &mut BTreeMap<usize, fc::FloatingContext>,
1366) -> Result<()> {
1367 let child_node = tree.get_mut(child_index).ok_or(LayoutError::InvalidTree)?;
1370 child_node.relative_position = Some(child_relative_pos);
1371
1372 let child_absolute_pos = LogicalPosition::new(
1376 self_content_box_pos.x + child_relative_pos.x,
1377 self_content_box_pos.y + child_relative_pos.y,
1378 );
1379
1380 {
1382 let child_node = tree.get(child_index).ok_or(LayoutError::InvalidTree)?;
1383 log_child_positioning(
1384 ctx,
1385 child_index,
1386 child_node,
1387 self_content_box_pos,
1388 child_relative_pos,
1389 child_absolute_pos,
1390 );
1391 }
1392
1393 super::pos_set(calculated_positions, child_index, child_absolute_pos);
1395
1396 let child_node = tree.get(child_index).ok_or(LayoutError::InvalidTree)?;
1398 let child_content_box_pos =
1399 calculate_content_box_pos(child_absolute_pos, &child_node.box_props);
1400 let child_inner_size = child_node
1401 .box_props
1402 .inner_size(child_node.used_size.unwrap_or_default(), writing_mode);
1403 let child_children: Vec<usize> = child_node.children.clone();
1404 let child_fc = child_node.formatting_context.clone();
1405
1406 if !child_children.is_empty() {
1411 if is_flex_or_grid {
1412 position_flex_child_descendants(
1414 ctx,
1415 tree,
1416 text_cache,
1417 child_index,
1418 child_content_box_pos,
1419 child_inner_size,
1420 calculated_positions,
1421 reflow_needed_for_scrollbars,
1422 float_cache,
1423 )?;
1424 } else {
1425 position_bfc_child_descendants(
1428 tree,
1429 child_index,
1430 child_content_box_pos,
1431 calculated_positions,
1432 );
1433 }
1434 }
1435
1436 Ok(())
1437}
1438
1439fn position_bfc_child_descendants(
1443 tree: &LayoutTree,
1444 node_index: usize,
1445 content_box_pos: LogicalPosition,
1446 calculated_positions: &mut super::PositionVec,
1447) {
1448 let Some(node) = tree.get(node_index) else { return };
1449
1450 for &child_index in &node.children {
1451 let Some(child_node) = tree.get(child_index) else { continue };
1452
1453 let child_rel_pos = child_node.relative_position.unwrap_or_default();
1455 let child_abs_pos = LogicalPosition::new(
1456 content_box_pos.x + child_rel_pos.x,
1457 content_box_pos.y + child_rel_pos.y,
1458 );
1459
1460 super::pos_set(calculated_positions, child_index, child_abs_pos);
1461
1462 let child_content_box_pos = LogicalPosition::new(
1464 child_abs_pos.x + child_node.box_props.border.left + child_node.box_props.padding.left,
1465 child_abs_pos.y + child_node.box_props.border.top + child_node.box_props.padding.top,
1466 );
1467
1468 position_bfc_child_descendants(tree, child_index, child_content_box_pos, calculated_positions);
1470 }
1471}
1472
1473fn process_out_of_flow_children<T: ParsedFontTrait>(
1479 ctx: &mut LayoutContext<'_, T>,
1480 tree: &mut LayoutTree,
1481 text_cache: &mut TextLayoutCache,
1482 node_index: usize,
1483 self_content_box_pos: LogicalPosition,
1484 calculated_positions: &mut super::PositionVec,
1485) -> Result<()> {
1486 let out_of_flow_children: Vec<(usize, Option<NodeId>)> = {
1488 let current_node = tree.get(node_index).ok_or(LayoutError::InvalidTree)?;
1489 current_node
1490 .children
1491 .iter()
1492 .filter_map(|&child_index| {
1493 if super::pos_contains(calculated_positions, child_index) {
1494 return None;
1495 }
1496 let child = tree.get(child_index)?;
1497 Some((child_index, child.dom_node_id))
1498 })
1499 .collect()
1500 };
1501
1502 for (child_index, child_dom_id_opt) in out_of_flow_children {
1503 let Some(child_dom_id) = child_dom_id_opt else {
1504 continue;
1505 };
1506
1507 let position_type = get_position_type(ctx.styled_dom, Some(child_dom_id));
1508 if position_type != LayoutPosition::Absolute && position_type != LayoutPosition::Fixed {
1509 continue;
1510 }
1511
1512 super::pos_set(calculated_positions, child_index, self_content_box_pos);
1514
1515 set_static_positions_recursive(
1517 ctx,
1518 tree,
1519 text_cache,
1520 child_index,
1521 self_content_box_pos,
1522 calculated_positions,
1523 )?;
1524 }
1525
1526 Ok(())
1527}
1528
1529pub fn calculate_layout_for_subtree<T: ParsedFontTrait>(
1557 ctx: &mut LayoutContext<'_, T>,
1558 tree: &mut LayoutTree,
1559 text_cache: &mut TextLayoutCache,
1560 node_index: usize,
1561 containing_block_pos: LogicalPosition,
1562 containing_block_size: LogicalSize,
1563 calculated_positions: &mut super::PositionVec,
1564 reflow_needed_for_scrollbars: &mut bool,
1565 float_cache: &mut BTreeMap<usize, fc::FloatingContext>,
1566 compute_mode: ComputeMode,
1567) -> Result<()> {
1568 if node_index < ctx.cache_map.entries.len() {
1585 match compute_mode {
1586 ComputeMode::ComputeSize => {
1587 let sizing_hit = ctx.cache_map.entries[node_index]
1589 .get_size(0, containing_block_size)
1590 .cloned();
1591 if let Some(cached_sizing) = sizing_hit {
1592 if let Some(node) = tree.get_mut(node_index) {
1595 node.used_size = Some(cached_sizing.result_size);
1596 node.escaped_top_margin = cached_sizing.escaped_top_margin;
1597 node.escaped_bottom_margin = cached_sizing.escaped_bottom_margin;
1598 node.baseline = cached_sizing.baseline;
1599 }
1600 return Ok(());
1601 }
1602 let layout_hit = ctx.cache_map.entries[node_index]
1604 .get_layout(containing_block_size)
1605 .cloned();
1606 if let Some(cached_layout) = layout_hit {
1607 if let Some(node) = tree.get_mut(node_index) {
1609 node.used_size = Some(cached_layout.result_size);
1610 node.overflow_content_size = Some(cached_layout.content_size);
1611 node.scrollbar_info = Some(cached_layout.scrollbar_info.clone());
1612 }
1613 return Ok(());
1614 }
1615 }
1616 ComputeMode::PerformLayout => {
1617 let layout_hit = ctx.cache_map.entries[node_index]
1619 .get_layout(containing_block_size)
1620 .cloned();
1621 if let Some(cached_layout) = layout_hit {
1622 if let Some(node) = tree.get_mut(node_index) {
1624 node.used_size = Some(cached_layout.result_size);
1625 node.overflow_content_size = Some(cached_layout.content_size);
1626 node.scrollbar_info = Some(cached_layout.scrollbar_info.clone());
1627 }
1628
1629 let box_props = tree.get(node_index)
1630 .map(|n| n.box_props.clone())
1631 .unwrap_or_default();
1632 let self_content_box_pos = calculate_content_box_pos(containing_block_pos, &box_props);
1633
1634 let result_size = cached_layout.result_size;
1636 for (child_index, child_relative_pos) in &cached_layout.child_positions {
1637 let child_abs_pos = LogicalPosition::new(
1638 self_content_box_pos.x + child_relative_pos.x,
1639 self_content_box_pos.y + child_relative_pos.y,
1640 );
1641 super::pos_set(calculated_positions, *child_index, child_abs_pos);
1642
1643 let child_available_size = box_props.inner_size(
1644 result_size,
1645 LayoutWritingMode::HorizontalTb,
1646 );
1647 calculate_layout_for_subtree(
1648 ctx,
1649 tree,
1650 text_cache,
1651 *child_index,
1652 child_abs_pos,
1653 child_available_size,
1654 calculated_positions,
1655 reflow_needed_for_scrollbars,
1656 float_cache,
1657 compute_mode,
1658 )?;
1659 }
1660
1661 return Ok(());
1662 }
1663 }
1664 }
1665 }
1666
1667 let PreparedLayoutContext {
1671 constraints,
1672 dom_id,
1673 writing_mode,
1674 mut final_used_size,
1675 box_props,
1676 } = {
1677 let node = tree.get(node_index).ok_or(LayoutError::InvalidTree)?;
1678 prepare_layout_context(ctx, node, containing_block_size)?
1679 };
1680
1681 let layout_result =
1683 layout_formatting_context(ctx, tree, text_cache, node_index, &constraints, float_cache)?;
1684 let content_size = layout_result.output.overflow_size;
1685
1686 let styled_node_state = dom_id
1689 .and_then(|id| ctx.styled_dom.styled_nodes.as_container().get(id).cloned())
1690 .map(|n| n.styled_node_state)
1691 .unwrap_or_default();
1692
1693 let css_height: MultiValue<LayoutHeight> = match dom_id {
1694 Some(id) => get_css_height(ctx.styled_dom, id, &styled_node_state),
1695 None => MultiValue::Auto, };
1697
1698 let is_scroll_container = dom_id.map_or(false, |id| {
1705 let ov_x = get_overflow_x(ctx.styled_dom, id, &styled_node_state);
1706 let ov_y = get_overflow_y(ctx.styled_dom, id, &styled_node_state);
1707 matches!(ov_x, MultiValue::Exact(LayoutOverflow::Scroll) | MultiValue::Exact(LayoutOverflow::Auto))
1708 || matches!(ov_y, MultiValue::Exact(LayoutOverflow::Scroll) | MultiValue::Exact(LayoutOverflow::Auto))
1709 });
1710
1711 if should_use_content_height(&css_height) {
1712 let skip_expansion = is_scroll_container
1713 && containing_block_size.height.is_finite()
1714 && containing_block_size.height > 0.0;
1715
1716 if !skip_expansion {
1717 final_used_size = apply_content_based_height(
1718 final_used_size,
1719 content_size,
1720 tree,
1721 node_index,
1722 writing_mode,
1723 );
1724 }
1725 }
1726
1727 let skip_scrollbar_check = ctx.fragmentation_context.is_some();
1730 let scrollbar_info = match dom_id {
1731 Some(id) => compute_scrollbar_info(
1732 ctx,
1733 id,
1734 &styled_node_state,
1735 content_size,
1736 &box_props,
1737 final_used_size,
1738 writing_mode,
1739 ),
1740 None => ScrollbarRequirements::default(),
1741 };
1742
1743 if check_scrollbar_change(tree, node_index, &scrollbar_info, skip_scrollbar_check) {
1744 *reflow_needed_for_scrollbars = true;
1745 }
1746
1747 let merged_scrollbar_info = merge_scrollbar_info(tree, node_index, &scrollbar_info);
1748 let content_box_size = box_props.inner_size(final_used_size, writing_mode);
1749 let inner_size_after_scrollbars = merged_scrollbar_info.shrink_size(content_box_size);
1750
1751 let self_content_box_pos = {
1753 let current_node = tree.get_mut(node_index).ok_or(LayoutError::InvalidTree)?;
1754
1755 let is_table_cell = matches!(
1757 current_node.formatting_context,
1758 FormattingContext::TableCell
1759 );
1760 if !is_table_cell || current_node.used_size.is_none() {
1761 current_node.used_size = Some(final_used_size);
1762 }
1763 current_node.scrollbar_info = Some(merged_scrollbar_info.clone());
1764 current_node.overflow_content_size = Some(content_size);
1766
1767 let pos = calculate_content_box_pos(containing_block_pos, ¤t_node.box_props);
1769 log_content_box_calculation(ctx, node_index, current_node, containing_block_pos, pos);
1770 pos
1771 };
1772
1773 let is_flex_or_grid = {
1775 let node = tree.get(node_index).ok_or(LayoutError::InvalidTree)?;
1776 matches!(
1777 node.formatting_context,
1778 FormattingContext::Flex | FormattingContext::Grid
1779 )
1780 };
1781
1782 let positions: Vec<_> = layout_result
1785 .output
1786 .positions
1787 .iter()
1788 .map(|(&idx, &pos)| (idx, pos))
1789 .collect();
1790
1791 let child_positions_for_cache: Vec<(usize, LogicalPosition)> = positions.clone();
1793
1794 for (child_index, child_relative_pos) in positions {
1795 process_inflow_child(
1796 ctx,
1797 tree,
1798 text_cache,
1799 child_index,
1800 child_relative_pos,
1801 self_content_box_pos,
1802 inner_size_after_scrollbars,
1803 writing_mode,
1804 is_flex_or_grid,
1805 calculated_positions,
1806 reflow_needed_for_scrollbars,
1807 float_cache,
1808 )?;
1809 }
1810
1811 process_out_of_flow_children(
1813 ctx,
1814 tree,
1815 text_cache,
1816 node_index,
1817 self_content_box_pos,
1818 calculated_positions,
1819 )?;
1820
1821 if node_index < ctx.cache_map.entries.len() {
1825 let node_ref = tree.get(node_index);
1826 let baseline = node_ref.and_then(|n| n.baseline);
1827 let escaped_top = node_ref.and_then(|n| n.escaped_top_margin);
1828 let escaped_bottom = node_ref.and_then(|n| n.escaped_bottom_margin);
1829
1830 ctx.cache_map.get_mut(node_index).store_layout(LayoutCacheEntry {
1832 available_size: containing_block_size,
1833 result_size: final_used_size,
1834 content_size,
1835 child_positions: child_positions_for_cache.clone(),
1836 escaped_top_margin: escaped_top,
1837 escaped_bottom_margin: escaped_bottom,
1838 scrollbar_info: merged_scrollbar_info.clone(),
1839 });
1840
1841 ctx.cache_map.get_mut(node_index).store_size(0, SizingCacheEntry {
1845 available_size: containing_block_size,
1846 result_size: final_used_size,
1847 baseline,
1848 escaped_top_margin: escaped_top,
1849 escaped_bottom_margin: escaped_bottom,
1850 });
1851 }
1852
1853 Ok(())
1854}
1855
1856fn position_flex_child_descendants<T: ParsedFontTrait>(
1864 ctx: &mut LayoutContext<'_, T>,
1865 tree: &mut LayoutTree,
1866 text_cache: &mut TextLayoutCache,
1867 node_index: usize,
1868 content_box_pos: LogicalPosition,
1869 available_size: LogicalSize,
1870 calculated_positions: &mut super::PositionVec,
1871 reflow_needed_for_scrollbars: &mut bool,
1872 float_cache: &mut BTreeMap<usize, fc::FloatingContext>,
1873) -> Result<()> {
1874 let node = tree.get(node_index).ok_or(LayoutError::InvalidTree)?;
1875 let children: Vec<usize> = node.children.clone();
1876 let fc = node.formatting_context.clone();
1877
1878 if matches!(fc, FormattingContext::Flex | FormattingContext::Grid) {
1881 for &child_index in &children {
1882 let child_node = tree.get(child_index).ok_or(LayoutError::InvalidTree)?;
1883 let child_rel_pos = child_node.relative_position.unwrap_or_default();
1884 let child_abs_pos = LogicalPosition::new(
1885 content_box_pos.x + child_rel_pos.x,
1886 content_box_pos.y + child_rel_pos.y,
1887 );
1888
1889 super::pos_set(calculated_positions, child_index, child_abs_pos);
1891
1892 let child_content_box = LogicalPosition::new(
1894 child_abs_pos.x
1895 + child_node.box_props.border.left
1896 + child_node.box_props.padding.left,
1897 child_abs_pos.y
1898 + child_node.box_props.border.top
1899 + child_node.box_props.padding.top,
1900 );
1901 let child_inner_size = child_node.box_props.inner_size(
1902 child_node.used_size.unwrap_or_default(),
1903 LayoutWritingMode::HorizontalTb,
1904 );
1905
1906 position_flex_child_descendants(
1908 ctx,
1909 tree,
1910 text_cache,
1911 child_index,
1912 child_content_box,
1913 child_inner_size,
1914 calculated_positions,
1915 reflow_needed_for_scrollbars,
1916 float_cache,
1917 )?;
1918 }
1919 } else {
1920 let node = tree.get(node_index).ok_or(LayoutError::InvalidTree)?;
1923 let children: Vec<usize> = node.children.clone();
1924
1925 for &child_index in &children {
1926 let child_node = tree.get(child_index).ok_or(LayoutError::InvalidTree)?;
1927 let child_rel_pos = child_node.relative_position.unwrap_or_default();
1928 let child_abs_pos = LogicalPosition::new(
1929 content_box_pos.x + child_rel_pos.x,
1930 content_box_pos.y + child_rel_pos.y,
1931 );
1932
1933 super::pos_set(calculated_positions, child_index, child_abs_pos);
1935
1936 let child_content_box = LogicalPosition::new(
1938 child_abs_pos.x
1939 + child_node.box_props.border.left
1940 + child_node.box_props.padding.left,
1941 child_abs_pos.y
1942 + child_node.box_props.border.top
1943 + child_node.box_props.padding.top,
1944 );
1945 let child_inner_size = child_node.box_props.inner_size(
1946 child_node.used_size.unwrap_or_default(),
1947 LayoutWritingMode::HorizontalTb,
1948 );
1949
1950 position_flex_child_descendants(
1952 ctx,
1953 tree,
1954 text_cache,
1955 child_index,
1956 child_content_box,
1957 child_inner_size,
1958 calculated_positions,
1959 reflow_needed_for_scrollbars,
1960 float_cache,
1961 )?;
1962 }
1963 }
1964
1965 Ok(())
1966}
1967
1968fn set_static_positions_recursive<T: ParsedFontTrait>(
1969 ctx: &mut LayoutContext<'_, T>,
1970 tree: &mut LayoutTree,
1971 _text_cache: &mut TextLayoutCache,
1972 node_index: usize,
1973 parent_content_box_pos: LogicalPosition,
1974 calculated_positions: &mut super::PositionVec,
1975) -> Result<()> {
1976 let out_of_flow_children: Vec<(usize, Option<NodeId>)> = {
1977 let node = tree.get(node_index).ok_or(LayoutError::InvalidTree)?;
1978 node.children
1979 .iter()
1980 .filter_map(|&child_index| {
1981 if super::pos_contains(calculated_positions, child_index) {
1982 None
1983 } else {
1984 let child = tree.get(child_index)?;
1985 Some((child_index, child.dom_node_id))
1986 }
1987 })
1988 .collect()
1989 };
1990
1991 for (child_index, child_dom_id_opt) in out_of_flow_children {
1992 if let Some(child_dom_id) = child_dom_id_opt {
1993 let position_type = get_position_type(ctx.styled_dom, Some(child_dom_id));
1994 if position_type == LayoutPosition::Absolute || position_type == LayoutPosition::Fixed {
1995 super::pos_set(calculated_positions, child_index, parent_content_box_pos);
1996
1997 set_static_positions_recursive(
1999 ctx,
2000 tree,
2001 _text_cache,
2002 child_index,
2003 parent_content_box_pos,
2004 calculated_positions,
2005 )?;
2006 }
2007 }
2008 }
2009
2010 Ok(())
2011}
2012
2013fn should_use_content_height(css_height: &MultiValue<LayoutHeight>) -> bool {
2015 match css_height {
2016 MultiValue::Auto | MultiValue::Initial | MultiValue::Inherit => {
2017 true
2019 }
2020 MultiValue::Exact(height) => match height {
2021 LayoutHeight::Auto => {
2022 true
2024 }
2025 LayoutHeight::Px(px) => {
2026 use azul_css::props::basic::{pixel::PixelValue, SizeMetric};
2029 px == &PixelValue::zero()
2030 || (px.metric != SizeMetric::Px
2031 && px.metric != SizeMetric::Percent
2032 && px.metric != SizeMetric::Em
2033 && px.metric != SizeMetric::Rem)
2034 }
2035 LayoutHeight::MinContent | LayoutHeight::MaxContent => {
2036 true
2038 }
2039 LayoutHeight::Calc(_) => {
2040 false
2042 }
2043 },
2044 }
2045}
2046
2047fn apply_content_based_height(
2059 mut used_size: LogicalSize,
2060 content_size: LogicalSize,
2061 tree: &LayoutTree,
2062 node_index: usize,
2063 writing_mode: LayoutWritingMode,
2064) -> LogicalSize {
2065 let node_props = &tree.get(node_index).unwrap().box_props;
2066 let main_axis_padding_border =
2067 node_props.padding.main_sum(writing_mode) + node_props.border.main_sum(writing_mode);
2068
2069 let old_main_size = used_size.main(writing_mode);
2071 let new_main_size = content_size.main(writing_mode) + main_axis_padding_border;
2072
2073 let final_main_size = old_main_size.max(new_main_size);
2076
2077 used_size = used_size.with_main(writing_mode, final_main_size);
2078
2079 used_size
2080}
2081
2082fn hash_styled_node_data(dom: &StyledDom, node_id: NodeId) -> u64 {
2083 let mut hasher = DefaultHasher::new();
2084 if let Some(styled_node) = dom.styled_nodes.as_container().get(node_id) {
2085 styled_node.styled_node_state.hash(&mut hasher);
2086 }
2087 if let Some(node_data) = dom.node_data.as_container().get(node_id) {
2088 node_data.get_node_type().hash(&mut hasher);
2089 }
2090 hasher.finish()
2091}
2092
2093fn calculate_subtree_hash(node_self_hash: u64, child_hashes: &[u64]) -> SubtreeHash {
2094 let mut hasher = DefaultHasher::new();
2095 node_self_hash.hash(&mut hasher);
2096 child_hashes.hash(&mut hasher);
2097 SubtreeHash(hasher.finish())
2098}
2099
2100pub fn compute_counters(
2110 styled_dom: &StyledDom,
2111 tree: &LayoutTree,
2112 counters: &mut BTreeMap<(usize, String), i32>,
2113) {
2114 use std::collections::HashMap;
2115
2116 let mut counter_stacks: HashMap<String, Vec<i32>> = HashMap::new();
2119
2120 let mut scope_stack: Vec<Vec<String>> = Vec::new();
2123
2124 compute_counters_recursive(
2125 styled_dom,
2126 tree,
2127 tree.root,
2128 counters,
2129 &mut counter_stacks,
2130 &mut scope_stack,
2131 );
2132}
2133
2134fn compute_counters_recursive(
2135 styled_dom: &StyledDom,
2136 tree: &LayoutTree,
2137 node_idx: usize,
2138 counters: &mut BTreeMap<(usize, String), i32>,
2139 counter_stacks: &mut std::collections::HashMap<String, Vec<i32>>,
2140 scope_stack: &mut Vec<Vec<String>>,
2141) {
2142 let node = match tree.get(node_idx) {
2143 Some(n) => n,
2144 None => return,
2145 };
2146
2147 if node.pseudo_element.is_some() {
2151 if let Some(parent_idx) = node.parent {
2154 let parent_counters: Vec<_> = counters
2156 .iter()
2157 .filter(|((idx, _), _)| *idx == parent_idx)
2158 .map(|((_, name), &value)| (name.clone(), value))
2159 .collect();
2160
2161 for (counter_name, value) in parent_counters {
2162 counters.insert((node_idx, counter_name), value);
2163 }
2164 }
2165
2166 return;
2169 }
2170
2171 let dom_id = match node.dom_node_id {
2173 Some(id) => id,
2174 None => {
2175 for &child_idx in &node.children {
2177 compute_counters_recursive(
2178 styled_dom,
2179 tree,
2180 child_idx,
2181 counters,
2182 counter_stacks,
2183 scope_stack,
2184 );
2185 }
2186 return;
2187 }
2188 };
2189
2190 let node_data = &styled_dom.node_data.as_container()[dom_id];
2191 let node_state = &styled_dom.styled_nodes.as_container()[dom_id].styled_node_state;
2192 let cache = &styled_dom.css_property_cache.ptr;
2193
2194 let mut reset_counters_at_this_level = Vec::new();
2196
2197 let display = {
2200 use crate::solver3::getters::get_display_property;
2201 get_display_property(styled_dom, Some(dom_id)).exact()
2202 };
2203 let is_list_item = matches!(display, Some(LayoutDisplay::ListItem));
2204
2205 let counter_reset = cache
2207 .get_counter_reset(node_data, &dom_id, node_state)
2208 .and_then(|v| v.get_property());
2209
2210 if let Some(counter_reset) = counter_reset {
2211 let counter_name_str = counter_reset.counter_name.as_str();
2212 if counter_name_str != "none" {
2213 let counter_name = counter_name_str.to_string();
2214 let reset_value = counter_reset.value;
2215
2216 counter_stacks
2218 .entry(counter_name.clone())
2219 .or_default()
2220 .push(reset_value);
2221 reset_counters_at_this_level.push(counter_name);
2222 }
2223 }
2224
2225 let counter_inc = cache
2227 .get_counter_increment(node_data, &dom_id, node_state)
2228 .and_then(|v| v.get_property());
2229
2230 if let Some(counter_inc) = counter_inc {
2231 let counter_name_str = counter_inc.counter_name.as_str();
2232 if counter_name_str != "none" {
2233 let counter_name = counter_name_str.to_string();
2234 let inc_value = counter_inc.value;
2235
2236 let stack = counter_stacks.entry(counter_name.clone()).or_default();
2238 if stack.is_empty() {
2239 stack.push(inc_value);
2241 } else if let Some(current) = stack.last_mut() {
2242 *current += inc_value;
2243 }
2244 }
2245 }
2246
2247 if is_list_item {
2249 let counter_name = "list-item".to_string();
2250 let stack = counter_stacks.entry(counter_name.clone()).or_default();
2251 if stack.is_empty() {
2252 stack.push(1);
2254 } else {
2255 if let Some(current) = stack.last_mut() {
2256 *current += 1;
2257 }
2258 }
2259 }
2260
2261 for (counter_name, stack) in counter_stacks.iter() {
2263 if let Some(&value) = stack.last() {
2264 counters.insert((node_idx, counter_name.clone()), value);
2265 }
2266 }
2267
2268 scope_stack.push(reset_counters_at_this_level.clone());
2270
2271 for &child_idx in &node.children {
2273 compute_counters_recursive(
2274 styled_dom,
2275 tree,
2276 child_idx,
2277 counters,
2278 counter_stacks,
2279 scope_stack,
2280 );
2281 }
2282
2283 if let Some(reset_counters) = scope_stack.pop() {
2285 for counter_name in reset_counters {
2286 if let Some(stack) = counter_stacks.get_mut(&counter_name) {
2287 stack.pop();
2288 }
2289 }
2290 }
2291}