1use std::{
5 collections::BTreeMap,
6 hash::{Hash, Hasher},
7 sync::{
8 atomic::{AtomicU32, Ordering},
9 Arc,
10 },
11};
12
13use crate::text3::cache::UnifiedConstraints;
14
15static IFC_ID_COUNTER: AtomicU32 = AtomicU32::new(0);
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
29pub struct IfcId(pub u32);
30
31impl IfcId {
32 pub fn unique() -> Self {
34 Self(IFC_ID_COUNTER.fetch_add(1, Ordering::Relaxed))
35 }
36
37 pub fn reset_counter() {
39 IFC_ID_COUNTER.store(0, Ordering::Relaxed);
40 }
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71pub struct IfcMembership {
72 pub ifc_id: IfcId,
74 pub ifc_root_layout_index: usize,
77 pub run_index: u32,
80}
81
82use azul_core::{
83 dom::{FormattingContext, NodeId, NodeType},
84 geom::{LogicalPosition, LogicalRect, LogicalSize},
85 styled_dom::StyledDom,
86};
87use azul_css::{
88 corety::LayoutDebugMessage,
89 css::CssPropertyValue,
90 format_rust_code::GetHash,
91 props::{
92 basic::{
93 pixel::DEFAULT_FONT_SIZE, PhysicalSize, PixelValue, PropertyContext, ResolutionContext,
94 },
95 layout::{
96 LayoutDisplay, LayoutFloat, LayoutHeight, LayoutMaxHeight, LayoutMaxWidth,
97 LayoutMinHeight, LayoutMinWidth, LayoutOverflow, LayoutPosition, LayoutWidth,
98 LayoutWritingMode,
99 },
100 property::{CssProperty, CssPropertyType},
101 style::StyleTextAlign,
102 },
103};
104use taffy::{Cache as TaffyCache, Layout, LayoutInput, LayoutOutput};
105
106#[cfg(feature = "text_layout")]
107use crate::text3;
108use crate::{
109 debug_log,
110 font::parsed::ParsedFont,
111 font_traits::{FontLoaderTrait, ParsedFontTrait, UnifiedLayout},
112 solver3::{
113 geometry::{BoxProps, IntrinsicSizes, PositionedRectangle},
114 getters::{
115 get_css_height, get_css_max_height, get_css_max_width, get_css_min_height,
116 get_css_min_width, get_css_width, get_display_property, get_float, get_overflow_x,
117 get_overflow_y, get_position, get_text_align, get_writing_mode, MultiValue,
118 },
119 scrollbar::ScrollbarRequirements,
120 LayoutContext, Result,
121 },
122 text3::cache::AvailableSpace,
123};
124
125#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
136pub enum DirtyFlag {
137 #[default]
139 None,
140 Paint,
143 Layout,
146}
147
148#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)]
152pub struct SubtreeHash(pub u64);
153
154#[derive(Debug, Clone)]
163pub struct InlineItemMetrics {
164 pub source_node_id: Option<NodeId>,
167 pub advance_width: f32,
169 pub line_height_contribution: f32,
171 pub can_break: bool,
174 pub line_index: u32,
176 pub x_offset: f32,
178}
179
180#[derive(Debug, Clone)]
199pub struct CachedInlineLayout {
200 pub layout: Arc<UnifiedLayout>,
202 pub available_width: AvailableSpace,
205 pub has_floats: bool,
208 pub constraints: Option<UnifiedConstraints>,
211 pub item_metrics: Vec<InlineItemMetrics>,
219}
220
221impl CachedInlineLayout {
222 pub fn new(
224 layout: Arc<UnifiedLayout>,
225 available_width: AvailableSpace,
226 has_floats: bool,
227 ) -> Self {
228 let item_metrics = Self::extract_item_metrics(&layout);
229 Self {
230 layout,
231 available_width,
232 has_floats,
233 constraints: None,
234 item_metrics,
235 }
236 }
237
238 pub fn new_with_constraints(
240 layout: Arc<UnifiedLayout>,
241 available_width: AvailableSpace,
242 has_floats: bool,
243 constraints: UnifiedConstraints,
244 ) -> Self {
245 let item_metrics = Self::extract_item_metrics(&layout);
246 Self {
247 layout,
248 available_width,
249 has_floats,
250 constraints: Some(constraints),
251 item_metrics,
252 }
253 }
254
255 fn extract_item_metrics(layout: &UnifiedLayout) -> Vec<InlineItemMetrics> {
262 use crate::text3::cache::{ShapedItem, get_item_vertical_metrics};
263
264 layout.items.iter().map(|positioned_item| {
265 let bounds = positioned_item.item.bounds();
266 let (ascent, descent) = get_item_vertical_metrics(&positioned_item.item);
267
268 let source_node_id = match &positioned_item.item {
269 ShapedItem::Cluster(c) => c.source_node_id,
270 ShapedItem::Object { .. }
274 | ShapedItem::CombinedBlock { .. }
275 | ShapedItem::Tab { .. }
276 | ShapedItem::Break { .. } => None,
277 };
278
279 let can_break = !matches!(&positioned_item.item, ShapedItem::Break { .. });
285
286 InlineItemMetrics {
287 source_node_id,
288 advance_width: bounds.width,
289 line_height_contribution: ascent + descent,
290 can_break,
291 line_index: positioned_item.line_index as u32,
292 x_offset: positioned_item.position.x,
293 }
294 }).collect()
295 }
296
297 pub fn is_valid_for(&self, new_width: AvailableSpace, new_has_floats: bool) -> bool {
307 if self.has_floats && !new_has_floats {
310 return self.width_constraint_matches(new_width);
312 }
313
314 self.width_constraint_matches(new_width)
316 }
317
318 fn width_constraint_matches(&self, new_width: AvailableSpace) -> bool {
320 match (self.available_width, new_width) {
321 (AvailableSpace::Definite(old), AvailableSpace::Definite(new)) => {
323 (old - new).abs() < 0.1
324 }
325 (AvailableSpace::MinContent, AvailableSpace::MinContent) => true,
327 (AvailableSpace::MaxContent, AvailableSpace::MaxContent) => true,
329 _ => false,
331 }
332 }
333
334 pub fn should_replace_with(&self, new_width: AvailableSpace, new_has_floats: bool) -> bool {
338 if new_has_floats && !self.has_floats {
340 return true;
341 }
342
343 !self.width_constraint_matches(new_width)
345 }
346
347 #[inline]
352 pub fn get_layout(&self) -> &Arc<UnifiedLayout> {
353 &self.layout
354 }
355
356 #[inline]
361 pub fn clone_layout(&self) -> Arc<UnifiedLayout> {
362 self.layout.clone()
363 }
364}
365
366#[derive(Debug, Clone)]
373pub struct LayoutNode {
374 pub dom_node_id: Option<NodeId>,
376 pub pseudo_element: Option<PseudoElement>,
378 pub is_anonymous: bool,
380 pub anonymous_type: Option<AnonymousBoxType>,
382 pub children: Vec<usize>,
384 pub parent: Option<usize>,
386 pub dirty_flag: DirtyFlag,
388 pub unresolved_box_props: crate::solver3::geometry::UnresolvedBoxProps,
391 pub box_props: BoxProps,
394 pub taffy_cache: TaffyCache, pub node_data_hash: u64,
399 pub subtree_hash: SubtreeHash,
402 pub formatting_context: FormattingContext,
404 pub parent_formatting_context: Option<FormattingContext>,
406 pub intrinsic_sizes: Option<IntrinsicSizes>,
408 pub used_size: Option<LogicalSize>,
410 pub relative_position: Option<LogicalPosition>,
412 pub baseline: Option<f32>,
414 pub inline_layout_result: Option<CachedInlineLayout>,
425 pub escaped_top_margin: Option<f32>,
429 pub escaped_bottom_margin: Option<f32>,
433 pub scrollbar_info: Option<ScrollbarRequirements>,
436 pub overflow_content_size: Option<LogicalSize>,
440 pub ifc_id: Option<IfcId>,
443 pub ifc_membership: Option<IfcMembership>,
447 pub computed_style: ComputedLayoutStyle,
450}
451
452#[derive(Debug, Clone, Default)]
460pub struct ComputedLayoutStyle {
461 pub display: LayoutDisplay,
463 pub position: LayoutPosition,
465 pub float: LayoutFloat,
467 pub overflow_x: LayoutOverflow,
469 pub overflow_y: LayoutOverflow,
471 pub writing_mode: azul_css::props::layout::LayoutWritingMode,
473 pub width: Option<azul_css::props::layout::LayoutWidth>,
475 pub height: Option<azul_css::props::layout::LayoutHeight>,
477 pub min_width: Option<azul_css::props::layout::LayoutMinWidth>,
479 pub min_height: Option<azul_css::props::layout::LayoutMinHeight>,
481 pub max_width: Option<azul_css::props::layout::LayoutMaxWidth>,
483 pub max_height: Option<azul_css::props::layout::LayoutMaxHeight>,
485 pub text_align: azul_css::props::style::StyleTextAlign,
487}
488
489impl LayoutNode {
490 pub fn resolve_box_props_with_containing_block(
502 &mut self,
503 containing_block: LogicalSize,
504 viewport_size: LogicalSize,
505 element_font_size: f32,
506 root_font_size: f32,
507 ) {
508 let params = crate::solver3::geometry::ResolutionParams {
509 containing_block,
510 viewport_size,
511 element_font_size,
512 root_font_size,
513 };
514 self.box_props = self.unresolved_box_props.resolve(¶ms);
515 }
516
517 pub fn get_content_size(&self) -> LogicalSize {
520 if let Some(content_size) = self.overflow_content_size {
522 return content_size;
523 }
524
525 let mut content_size = self.used_size.unwrap_or_default();
527
528 if let Some(ref cached_layout) = self.inline_layout_result {
530 let text_layout = &cached_layout.layout;
531 let mut max_x: f32 = 0.0;
533 let mut max_y: f32 = 0.0;
534
535 for positioned_item in &text_layout.items {
536 let item_bounds = positioned_item.item.bounds();
537 let item_right = positioned_item.position.x + item_bounds.width;
538 let item_bottom = positioned_item.position.y + item_bounds.height;
539
540 max_x = max_x.max(item_right);
541 max_y = max_y.max(item_bottom);
542 }
543
544 content_size.width = content_size.width.max(max_x);
546 content_size.height = content_size.height.max(max_y);
547 }
548
549 content_size
553 }
554}
555
556#[derive(Debug, Clone, Copy, PartialEq, Eq)]
558pub enum PseudoElement {
559 Marker,
561 Before,
563 After,
565}
566
567#[derive(Debug, Clone, Copy, PartialEq)]
569pub enum AnonymousBoxType {
570 InlineWrapper,
572 ListItemMarker,
575 TableWrapper,
577 TableRowGroup,
579 TableRow,
581 TableCell,
583}
584
585#[derive(Debug, Clone)]
587pub struct LayoutTree {
588 pub nodes: Vec<LayoutNode>,
590 pub root: usize,
592 pub dom_to_layout: BTreeMap<NodeId, Vec<usize>>,
594}
595
596impl LayoutTree {
597 pub fn get(&self, index: usize) -> Option<&LayoutNode> {
598 self.nodes.get(index)
599 }
600
601 pub fn get_mut(&mut self, index: usize) -> Option<&mut LayoutNode> {
602 self.nodes.get_mut(index)
603 }
604
605 pub fn root_node(&self) -> &LayoutNode {
606 &self.nodes[self.root]
607 }
608
609 pub fn resolve_box_props(
615 &mut self,
616 node_index: usize,
617 containing_block: LogicalSize,
618 viewport_size: LogicalSize,
619 element_font_size: f32,
620 root_font_size: f32,
621 ) {
622 if let Some(node) = self.nodes.get_mut(node_index) {
623 node.resolve_box_props_with_containing_block(
624 containing_block,
625 viewport_size,
626 element_font_size,
627 root_font_size,
628 );
629 }
630 }
631
632 pub fn mark_dirty(&mut self, start_index: usize, flag: DirtyFlag) {
638 if flag == DirtyFlag::None {
640 return;
641 }
642
643 let mut current_index = Some(start_index);
644 while let Some(index) = current_index {
645 if let Some(node) = self.get_mut(index) {
646 if node.dirty_flag >= flag {
649 break;
650 }
651
652 node.dirty_flag = flag;
654 current_index = node.parent;
655 } else {
656 break;
657 }
658 }
659 }
660
661 pub fn mark_subtree_dirty(&mut self, start_index: usize, flag: DirtyFlag) {
666 if flag == DirtyFlag::None {
668 return;
669 }
670
671 let mut stack = vec![start_index];
674 while let Some(index) = stack.pop() {
675 if let Some(node) = self.get_mut(index) {
676 if node.dirty_flag < flag {
678 node.dirty_flag = flag;
679 }
680 stack.extend_from_slice(&node.children);
682 }
683 }
684 }
685
686 pub fn clear_all_dirty_flags(&mut self) {
688 for node in &mut self.nodes {
689 node.dirty_flag = DirtyFlag::None;
690 }
691 }
692
693 pub fn get_inline_layout_for_node(&self, layout_index: usize) -> Option<&std::sync::Arc<UnifiedLayout>> {
712 let layout_node = self.nodes.get(layout_index)?;
713
714 if let Some(cached) = &layout_node.inline_layout_result {
716 return Some(cached.get_layout());
717 }
718
719 if let Some(ifc_membership) = &layout_node.ifc_membership {
721 let ifc_root_node = self.nodes.get(ifc_membership.ifc_root_layout_index)?;
722 if let Some(cached) = &ifc_root_node.inline_layout_result {
723 return Some(cached.get_layout());
724 }
725 }
726
727 None
728 }
729}
730
731pub fn generate_layout_tree<T: ParsedFontTrait>(
733 ctx: &mut LayoutContext<'_, T>,
734) -> Result<LayoutTree> {
735 let mut builder = LayoutTreeBuilder::new(ctx.viewport_size);
736 let root_id = ctx
737 .styled_dom
738 .root
739 .into_crate_internal()
740 .unwrap_or(NodeId::ZERO);
741 let root_index =
742 builder.process_node(ctx.styled_dom, root_id, None, &mut ctx.debug_messages)?;
743 let layout_tree = builder.build(root_index);
744
745 debug_log!(
746 ctx,
747 "Generated layout tree with {} nodes (incl. anonymous)",
748 layout_tree.nodes.len()
749 );
750
751 Ok(layout_tree)
752}
753
754pub struct LayoutTreeBuilder {
755 nodes: Vec<LayoutNode>,
756 dom_to_layout: BTreeMap<NodeId, Vec<usize>>,
757 viewport_size: LogicalSize,
758}
759
760impl LayoutTreeBuilder {
761 pub fn new(viewport_size: LogicalSize) -> Self {
762 Self {
763 nodes: Vec::new(),
764 dom_to_layout: BTreeMap::new(),
765 viewport_size,
766 }
767 }
768
769 pub fn get(&self, index: usize) -> Option<&LayoutNode> {
770 self.nodes.get(index)
771 }
772
773 pub fn get_mut(&mut self, index: usize) -> Option<&mut LayoutNode> {
774 self.nodes.get_mut(index)
775 }
776
777 pub fn process_node(
781 &mut self,
782 styled_dom: &StyledDom,
783 dom_id: NodeId,
784 parent_idx: Option<usize>,
785 debug_messages: &mut Option<Vec<LayoutDebugMessage>>,
786 ) -> Result<usize> {
787 let node_data = &styled_dom.node_data.as_container()[dom_id];
788 let node_idx = self.create_node_from_dom(styled_dom, dom_id, parent_idx, debug_messages)?;
789 let display_type = get_display_type(styled_dom, dom_id);
790
791 if display_type == LayoutDisplay::ListItem {
794 self.create_marker_pseudo_element(styled_dom, dom_id, node_idx);
795 }
796
797 match display_type {
798 LayoutDisplay::Block
799 | LayoutDisplay::InlineBlock
800 | LayoutDisplay::FlowRoot
801 | LayoutDisplay::ListItem => {
802 self.process_block_children(styled_dom, dom_id, node_idx, debug_messages)?
803 }
804 LayoutDisplay::Table => {
805 self.process_table_children(styled_dom, dom_id, node_idx, debug_messages)?
806 }
807 LayoutDisplay::TableRowGroup => {
808 self.process_table_row_group_children(styled_dom, dom_id, node_idx, debug_messages)?
809 }
810 LayoutDisplay::TableRow => {
811 self.process_table_row_children(styled_dom, dom_id, node_idx, debug_messages)?
812 }
813 _ => {
816 let children: Vec<NodeId> = dom_id
820 .az_children(&styled_dom.node_hierarchy.as_container())
821 .filter(|&child_id| {
822 if get_display_type(styled_dom, child_id) == LayoutDisplay::None {
823 return false;
824 }
825 let node_data = &styled_dom.node_data.as_container()[child_id];
827 if let NodeType::Text(text) = node_data.get_node_type() {
828 return !text.as_str().trim().is_empty();
830 }
831 true
832 })
833 .collect();
834
835 for child_dom_id in children {
836 self.process_node(styled_dom, child_dom_id, Some(node_idx), debug_messages)?;
837 }
838 }
839 }
840 Ok(node_idx)
841 }
842
843 fn process_block_children(
846 &mut self,
847 styled_dom: &StyledDom,
848 parent_dom_id: NodeId,
849 parent_idx: usize,
850 debug_messages: &mut Option<Vec<LayoutDebugMessage>>,
851 ) -> Result<()> {
852 let children: Vec<NodeId> = parent_dom_id
854 .az_children(&styled_dom.node_hierarchy.as_container())
855 .filter(|&child_id| get_display_type(styled_dom, child_id) != LayoutDisplay::None)
856 .collect();
857
858 if let Some(msgs) = debug_messages.as_mut() {
860 msgs.push(LayoutDebugMessage::info(format!(
861 "[process_block_children] DOM node {} has {} children: {:?}",
862 parent_dom_id.index(),
863 children.len(),
864 children.iter().map(|c| c.index()).collect::<Vec<_>>()
865 )));
866 }
867
868 let has_block_child = children.iter().any(|&id| is_block_level(styled_dom, id));
869
870 if let Some(msgs) = debug_messages.as_mut() {
871 msgs.push(LayoutDebugMessage::info(format!(
872 "[process_block_children] has_block_child={}, children display types: {:?}",
873 has_block_child,
874 children
875 .iter()
876 .map(|c| {
877 let dt = get_display_type(styled_dom, *c);
878 let is_block = is_block_level(styled_dom, *c);
879 format!("{}:{:?}(block={})", c.index(), dt, is_block)
880 })
881 .collect::<Vec<_>>()
882 )));
883 }
884
885 if !has_block_child {
886 if let Some(msgs) = debug_messages.as_mut() {
888 msgs.push(LayoutDebugMessage::info(format!(
889 "[process_block_children] All inline, processing {} children directly",
890 children.len()
891 )));
892 }
893 for child_id in children {
894 self.process_node(styled_dom, child_id, Some(parent_idx), debug_messages)?;
895 }
896 return Ok(());
897 }
898
899 let mut inline_run = Vec::new();
901
902 for child_id in children {
903 if is_block_level(styled_dom, child_id) {
904 if !inline_run.is_empty() {
906 if let Some(msgs) = debug_messages.as_mut() {
907 msgs.push(LayoutDebugMessage::info(format!(
908 "[process_block_children] Creating anon wrapper for inline run: {:?}",
909 inline_run
910 .iter()
911 .map(|c: &NodeId| c.index())
912 .collect::<Vec<_>>()
913 )));
914 }
915 let anon_idx = self.create_anonymous_node(
916 parent_idx,
917 AnonymousBoxType::InlineWrapper,
918 FormattingContext::Block {
919 establishes_new_context: true,
921 },
922 );
923 for inline_child_id in inline_run.drain(..) {
924 self.process_node(
925 styled_dom,
926 inline_child_id,
927 Some(anon_idx),
928 debug_messages,
929 )?;
930 }
931 }
932 if let Some(msgs) = debug_messages.as_mut() {
934 msgs.push(LayoutDebugMessage::info(format!(
935 "[process_block_children] Processing block child DOM {}",
936 child_id.index()
937 )));
938 }
939 self.process_node(styled_dom, child_id, Some(parent_idx), debug_messages)?;
940 } else {
941 inline_run.push(child_id);
942 }
943 }
944 if !inline_run.is_empty() {
946 if let Some(msgs) = debug_messages.as_mut() {
947 msgs.push(LayoutDebugMessage::info(format!(
948 "[process_block_children] Creating anon wrapper for remaining inline run: {:?}",
949 inline_run.iter().map(|c| c.index()).collect::<Vec<_>>()
950 )));
951 }
952 let anon_idx = self.create_anonymous_node(
953 parent_idx,
954 AnonymousBoxType::InlineWrapper,
955 FormattingContext::Block {
956 establishes_new_context: true, },
958 );
959 for inline_child_id in inline_run {
960 self.process_node(styled_dom, inline_child_id, Some(anon_idx), debug_messages)?;
961 }
962 }
963
964 Ok(())
965 }
966
967 fn process_table_children(
979 &mut self,
980 styled_dom: &StyledDom,
981 parent_dom_id: NodeId,
982 parent_idx: usize,
983 debug_messages: &mut Option<Vec<LayoutDebugMessage>>,
984 ) -> Result<()> {
985 let parent_display = get_display_type(styled_dom, parent_dom_id);
986 let mut row_children = Vec::new();
987
988 for child_id in parent_dom_id.az_children(&styled_dom.node_hierarchy.as_container()) {
989 if should_skip_for_table_structure(styled_dom, child_id, parent_display) {
993 continue;
994 }
995
996 let child_display = get_display_type(styled_dom, child_id);
997
998 if child_display == LayoutDisplay::TableCell {
1001 row_children.push(child_id);
1003 } else {
1004 if !row_children.is_empty() {
1007 let anon_row_idx = self.create_anonymous_node(
1008 parent_idx,
1009 AnonymousBoxType::TableRow,
1010 FormattingContext::TableRow,
1011 );
1012
1013 for cell_id in row_children.drain(..) {
1014 self.process_node(styled_dom, cell_id, Some(anon_row_idx), debug_messages)?;
1015 }
1016 }
1017
1018 self.process_node(styled_dom, child_id, Some(parent_idx), debug_messages)?;
1020 }
1021 }
1022
1023 if !row_children.is_empty() {
1026 let anon_row_idx = self.create_anonymous_node(
1027 parent_idx,
1028 AnonymousBoxType::TableRow,
1029 FormattingContext::TableRow,
1030 );
1031
1032 for cell_id in row_children {
1033 self.process_node(styled_dom, cell_id, Some(anon_row_idx), debug_messages)?;
1034 }
1035 }
1036
1037 Ok(())
1038 }
1039
1040 fn process_table_row_group_children(
1049 &mut self,
1050 styled_dom: &StyledDom,
1051 parent_dom_id: NodeId,
1052 parent_idx: usize,
1053 debug_messages: &mut Option<Vec<LayoutDebugMessage>>,
1054 ) -> Result<()> {
1055 self.process_table_children(styled_dom, parent_dom_id, parent_idx, debug_messages)
1058 }
1059
1060 fn process_table_row_children(
1068 &mut self,
1069 styled_dom: &StyledDom,
1070 parent_dom_id: NodeId,
1071 parent_idx: usize,
1072 debug_messages: &mut Option<Vec<LayoutDebugMessage>>,
1073 ) -> Result<()> {
1074 let parent_display = get_display_type(styled_dom, parent_dom_id);
1075
1076 for child_id in parent_dom_id.az_children(&styled_dom.node_hierarchy.as_container()) {
1077 if should_skip_for_table_structure(styled_dom, child_id, parent_display) {
1079 continue;
1080 }
1081
1082 let child_display = get_display_type(styled_dom, child_id);
1083
1084 if child_display == LayoutDisplay::TableCell {
1088 self.process_node(styled_dom, child_id, Some(parent_idx), debug_messages)?;
1090 } else {
1091 let anon_cell_idx = self.create_anonymous_node(
1094 parent_idx,
1095 AnonymousBoxType::TableCell,
1096 FormattingContext::Block {
1097 establishes_new_context: true,
1098 },
1099 );
1100
1101 self.process_node(styled_dom, child_id, Some(anon_cell_idx), debug_messages)?;
1102 }
1103 }
1104
1105 Ok(())
1106 }
1107 pub fn create_anonymous_node(
1116 &mut self,
1117 parent: usize,
1118 anon_type: AnonymousBoxType,
1119 fc: FormattingContext,
1120 ) -> usize {
1121 let index = self.nodes.len();
1122
1123 let parent_fc = self.nodes.get(parent).map(|n| n.formatting_context.clone());
1126
1127 self.nodes.push(LayoutNode {
1128 dom_node_id: None,
1130 pseudo_element: None,
1131 parent: Some(parent),
1132 formatting_context: fc,
1133 parent_formatting_context: parent_fc,
1134 unresolved_box_props: crate::solver3::geometry::UnresolvedBoxProps::default(),
1136 box_props: BoxProps::default(),
1137 taffy_cache: TaffyCache::new(),
1138 is_anonymous: true,
1139 anonymous_type: Some(anon_type),
1140 children: Vec::new(),
1141 dirty_flag: DirtyFlag::Layout,
1142 node_data_hash: 0,
1144 subtree_hash: SubtreeHash(0),
1145 intrinsic_sizes: None,
1146 used_size: None,
1147 relative_position: None,
1148 baseline: None,
1149 inline_layout_result: None,
1150 escaped_top_margin: None,
1151 escaped_bottom_margin: None,
1152 scrollbar_info: None,
1153 overflow_content_size: None,
1154 ifc_id: None,
1155 ifc_membership: None,
1156 computed_style: ComputedLayoutStyle::default(),
1157 });
1158
1159 self.nodes[parent].children.push(index);
1160 index
1161 }
1162
1163 pub fn create_marker_pseudo_element(
1172 &mut self,
1173 styled_dom: &StyledDom,
1174 list_item_dom_id: NodeId,
1175 list_item_idx: usize,
1176 ) -> usize {
1177 let index = self.nodes.len();
1178
1179 let parent_fc = self
1182 .nodes
1183 .get(list_item_idx)
1184 .map(|n| n.formatting_context.clone());
1185 self.nodes.push(LayoutNode {
1186 dom_node_id: Some(list_item_dom_id),
1187 pseudo_element: Some(PseudoElement::Marker),
1188 parent: Some(list_item_idx),
1189 formatting_context: FormattingContext::Inline,
1191 parent_formatting_context: parent_fc,
1192 unresolved_box_props: crate::solver3::geometry::UnresolvedBoxProps::default(),
1194 box_props: BoxProps::default(),
1195 taffy_cache: TaffyCache::new(),
1196 is_anonymous: false,
1198 anonymous_type: None,
1199 children: Vec::new(),
1200 dirty_flag: DirtyFlag::Layout,
1201 node_data_hash: 0,
1203 subtree_hash: SubtreeHash(0),
1204 intrinsic_sizes: None,
1205 used_size: None,
1206 relative_position: None,
1207 baseline: None,
1208 inline_layout_result: None,
1209 escaped_top_margin: None,
1210 escaped_bottom_margin: None,
1211 scrollbar_info: None,
1212 overflow_content_size: None,
1213 ifc_id: None,
1214 ifc_membership: None,
1215 computed_style: ComputedLayoutStyle::default(),
1216 });
1217
1218 self.nodes[list_item_idx].children.insert(0, index);
1220
1221 self.dom_to_layout
1223 .entry(list_item_dom_id)
1224 .or_default()
1225 .push(index);
1226
1227 index
1228 }
1229
1230 pub fn create_node_from_dom(
1231 &mut self,
1232 styled_dom: &StyledDom,
1233 dom_id: NodeId,
1234 parent: Option<usize>,
1235 debug_messages: &mut Option<Vec<LayoutDebugMessage>>,
1236 ) -> Result<usize> {
1237 let index = self.nodes.len();
1238 let parent_fc =
1239 parent.and_then(|p| self.nodes.get(p).map(|n| n.formatting_context.clone()));
1240 let collected = collect_box_props(styled_dom, dom_id, debug_messages, self.viewport_size);
1241 self.nodes.push(LayoutNode {
1242 dom_node_id: Some(dom_id),
1243 pseudo_element: None,
1244 parent,
1245 formatting_context: determine_formatting_context(styled_dom, dom_id),
1246 parent_formatting_context: parent_fc,
1247 unresolved_box_props: collected.unresolved,
1248 box_props: collected.resolved,
1249 taffy_cache: TaffyCache::new(),
1250 is_anonymous: false,
1251 anonymous_type: None,
1252 children: Vec::new(),
1253 dirty_flag: DirtyFlag::Layout,
1254 node_data_hash: hash_node_data(styled_dom, dom_id),
1255 subtree_hash: SubtreeHash(0),
1256 intrinsic_sizes: None,
1257 used_size: None,
1258 relative_position: None,
1259 baseline: None,
1260 inline_layout_result: None,
1261 escaped_top_margin: None,
1262 escaped_bottom_margin: None,
1263 scrollbar_info: None,
1264 overflow_content_size: None,
1265 ifc_id: None,
1266 ifc_membership: None,
1267 computed_style: compute_layout_style(styled_dom, dom_id),
1268 });
1269 if let Some(p) = parent {
1270 self.nodes[p].children.push(index);
1271 }
1272 self.dom_to_layout.entry(dom_id).or_default().push(index);
1273 Ok(index)
1274 }
1275
1276 pub fn clone_node_from_old(&mut self, old_node: &LayoutNode, parent: Option<usize>) -> usize {
1277 let index = self.nodes.len();
1278 let mut new_node = old_node.clone();
1279 new_node.parent = parent;
1280 new_node.parent_formatting_context =
1281 parent.and_then(|p| self.nodes.get(p).map(|n| n.formatting_context.clone()));
1282 new_node.children = Vec::new();
1283 new_node.dirty_flag = DirtyFlag::None;
1284 self.nodes.push(new_node);
1285 if let Some(p) = parent {
1286 self.nodes[p].children.push(index);
1287 }
1288 if let Some(dom_id) = old_node.dom_node_id {
1289 self.dom_to_layout.entry(dom_id).or_default().push(index);
1290 }
1291 index
1292 }
1293
1294 pub fn build(self, root_idx: usize) -> LayoutTree {
1295 LayoutTree {
1296 nodes: self.nodes,
1297 root: root_idx,
1298 dom_to_layout: self.dom_to_layout,
1299 }
1300 }
1301}
1302
1303pub fn is_block_level(styled_dom: &StyledDom, node_id: NodeId) -> bool {
1304 matches!(
1305 get_display_type(styled_dom, node_id),
1306 LayoutDisplay::Block
1307 | LayoutDisplay::FlowRoot
1308 | LayoutDisplay::Flex
1309 | LayoutDisplay::Grid
1310 | LayoutDisplay::Table
1311 | LayoutDisplay::TableCaption
1312 | LayoutDisplay::TableRow
1313 | LayoutDisplay::TableRowGroup
1314 | LayoutDisplay::ListItem
1315 )
1316}
1317
1318fn is_inline_level(styled_dom: &StyledDom, node_id: NodeId) -> bool {
1325 let node_data = &styled_dom.node_data.as_container()[node_id];
1327 if matches!(node_data.get_node_type(), NodeType::Text(_)) {
1328 return true;
1329 }
1330
1331 matches!(
1333 get_display_type(styled_dom, node_id),
1334 LayoutDisplay::Inline
1335 | LayoutDisplay::InlineBlock
1336 | LayoutDisplay::InlineTable
1337 | LayoutDisplay::InlineFlex
1338 | LayoutDisplay::InlineGrid
1339 )
1340}
1341
1342fn has_only_inline_children(styled_dom: &StyledDom, node_id: NodeId) -> bool {
1346 let hierarchy = styled_dom.node_hierarchy.as_container();
1347 let node_hier = match hierarchy.get(node_id) {
1348 Some(n) => n,
1349 None => {
1350 return false;
1351 }
1352 };
1353
1354 let mut current_child = node_hier.first_child_id(node_id);
1356
1357 if current_child.is_none() {
1359 return false;
1360 }
1361
1362 while let Some(child_id) = current_child {
1364 let is_inline = is_inline_level(styled_dom, child_id);
1365
1366 if !is_inline {
1367 return false;
1369 }
1370
1371 if let Some(child_hier) = hierarchy.get(child_id) {
1373 current_child = child_hier.next_sibling_id();
1374 } else {
1375 break;
1376 }
1377 }
1378
1379 true
1381}
1382
1383fn compute_layout_style(styled_dom: &StyledDom, dom_id: NodeId) -> ComputedLayoutStyle {
1388 let styled_node_state = styled_dom
1389 .styled_nodes
1390 .as_container()
1391 .get(dom_id)
1392 .map(|n| n.styled_node_state.clone())
1393 .unwrap_or_default();
1394
1395 let display = match get_display_property(styled_dom, Some(dom_id)) {
1397 MultiValue::Exact(d) => d,
1398 MultiValue::Auto | MultiValue::Initial | MultiValue::Inherit => LayoutDisplay::Block,
1399 };
1400
1401 let position = get_position(styled_dom, dom_id, &styled_node_state).unwrap_or_default();
1403
1404 let float = get_float(styled_dom, dom_id, &styled_node_state).unwrap_or_default();
1406
1407 let overflow_x = get_overflow_x(styled_dom, dom_id, &styled_node_state).unwrap_or_default();
1409 let overflow_y = get_overflow_y(styled_dom, dom_id, &styled_node_state).unwrap_or_default();
1410
1411 let writing_mode = get_writing_mode(styled_dom, dom_id, &styled_node_state).unwrap_or_default();
1413
1414 let text_align = get_text_align(styled_dom, dom_id, &styled_node_state).unwrap_or_default();
1416
1417 let width = match get_css_width(styled_dom, dom_id, &styled_node_state) {
1419 MultiValue::Exact(w) => Some(w),
1420 _ => None,
1421 };
1422 let height = match get_css_height(styled_dom, dom_id, &styled_node_state) {
1423 MultiValue::Exact(h) => Some(h),
1424 _ => None,
1425 };
1426
1427 let min_width = match get_css_min_width(styled_dom, dom_id, &styled_node_state) {
1429 MultiValue::Exact(v) => Some(v),
1430 _ => None,
1431 };
1432 let min_height = match get_css_min_height(styled_dom, dom_id, &styled_node_state) {
1433 MultiValue::Exact(v) => Some(v),
1434 _ => None,
1435 };
1436 let max_width = match get_css_max_width(styled_dom, dom_id, &styled_node_state) {
1437 MultiValue::Exact(v) => Some(v),
1438 _ => None,
1439 };
1440 let max_height = match get_css_max_height(styled_dom, dom_id, &styled_node_state) {
1441 MultiValue::Exact(v) => Some(v),
1442 _ => None,
1443 };
1444
1445 ComputedLayoutStyle {
1446 display,
1447 position,
1448 float,
1449 overflow_x,
1450 overflow_y,
1451 writing_mode,
1452 width,
1453 height,
1454 min_width,
1455 min_height,
1456 max_width,
1457 max_height,
1458 text_align,
1459 }
1460}
1461
1462fn hash_node_data(dom: &StyledDom, node_id: NodeId) -> u64 {
1463 let mut hasher = std::hash::DefaultHasher::new();
1464 if let Some(styled_node) = dom.node_data.as_container().get(node_id) {
1466 styled_node.get_hash().hash(&mut hasher);
1467 }
1468 hasher.finish()
1469}
1470
1471fn get_element_font_size(styled_dom: &StyledDom, dom_id: NodeId) -> f32 {
1473 let node_state = styled_dom
1474 .styled_nodes
1475 .as_container()
1476 .get(dom_id)
1477 .map(|n| &n.styled_node_state)
1478 .cloned()
1479 .unwrap_or_default();
1480
1481 crate::solver3::getters::get_element_font_size(styled_dom, dom_id, &node_state)
1482}
1483
1484fn get_parent_font_size(styled_dom: &StyledDom, dom_id: NodeId) -> f32 {
1486 styled_dom
1487 .node_hierarchy
1488 .as_container()
1489 .get(dom_id)
1490 .and_then(|node| node.parent_id())
1491 .map(|parent_id| get_element_font_size(styled_dom, parent_id))
1492 .unwrap_or(azul_css::props::basic::pixel::DEFAULT_FONT_SIZE)
1493}
1494
1495fn get_root_font_size(styled_dom: &StyledDom) -> f32 {
1497 get_element_font_size(styled_dom, NodeId::new(0))
1499}
1500
1501fn create_resolution_context(
1503 styled_dom: &StyledDom,
1504 dom_id: NodeId,
1505 containing_block_size: Option<azul_css::props::basic::PhysicalSize>,
1506 viewport_size: LogicalSize,
1507) -> azul_css::props::basic::ResolutionContext {
1508 let element_font_size = get_element_font_size(styled_dom, dom_id);
1509 let parent_font_size = get_parent_font_size(styled_dom, dom_id);
1510 let root_font_size = get_root_font_size(styled_dom);
1511
1512 ResolutionContext {
1513 element_font_size,
1514 parent_font_size,
1515 root_font_size,
1516 containing_block_size: containing_block_size.unwrap_or(PhysicalSize::new(0.0, 0.0)),
1517 element_size: None, viewport_size: PhysicalSize::new(viewport_size.width, viewport_size.height),
1519 }
1520}
1521
1522struct CollectedBoxProps {
1524 unresolved: crate::solver3::geometry::UnresolvedBoxProps,
1525 resolved: BoxProps,
1526}
1527
1528fn collect_box_props(
1534 styled_dom: &StyledDom,
1535 dom_id: NodeId,
1536 debug_messages: &mut Option<Vec<LayoutDebugMessage>>,
1537 viewport_size: LogicalSize,
1538) -> CollectedBoxProps {
1539 use crate::solver3::geometry::{UnresolvedBoxProps, UnresolvedEdge, UnresolvedMargin};
1540 use crate::solver3::getters::*;
1541
1542 let node_data = &styled_dom.node_data.as_container()[dom_id];
1543
1544 let node_state = styled_dom
1546 .styled_nodes
1547 .as_container()
1548 .get(dom_id)
1549 .map(|n| &n.styled_node_state)
1550 .cloned()
1551 .unwrap_or_default();
1552
1553 let context = create_resolution_context(styled_dom, dom_id, None, viewport_size);
1557
1558 let margin_top_mv = get_css_margin_top(styled_dom, dom_id, &node_state);
1560 let margin_right_mv = get_css_margin_right(styled_dom, dom_id, &node_state);
1561 let margin_bottom_mv = get_css_margin_bottom(styled_dom, dom_id, &node_state);
1562 let margin_left_mv = get_css_margin_left(styled_dom, dom_id, &node_state);
1563
1564 let to_unresolved_margin = |mv: &MultiValue<PixelValue>| -> UnresolvedMargin {
1566 match mv {
1567 MultiValue::Auto => UnresolvedMargin::Auto,
1568 MultiValue::Exact(pv) => UnresolvedMargin::Length(*pv),
1569 _ => UnresolvedMargin::Zero,
1570 }
1571 };
1572
1573 let unresolved_margin = UnresolvedEdge {
1575 top: to_unresolved_margin(&margin_top_mv),
1576 right: to_unresolved_margin(&margin_right_mv),
1577 bottom: to_unresolved_margin(&margin_bottom_mv),
1578 left: to_unresolved_margin(&margin_left_mv),
1579 };
1580
1581 let padding_top_mv = get_css_padding_top(styled_dom, dom_id, &node_state);
1583 let padding_right_mv = get_css_padding_right(styled_dom, dom_id, &node_state);
1584 let padding_bottom_mv = get_css_padding_bottom(styled_dom, dom_id, &node_state);
1585 let padding_left_mv = get_css_padding_left(styled_dom, dom_id, &node_state);
1586
1587 let to_pixel_value = |mv: MultiValue<PixelValue>| -> PixelValue {
1589 match mv {
1590 MultiValue::Exact(pv) => pv,
1591 _ => PixelValue::const_px(0),
1592 }
1593 };
1594
1595 let unresolved_padding = UnresolvedEdge {
1597 top: to_pixel_value(padding_top_mv),
1598 right: to_pixel_value(padding_right_mv),
1599 bottom: to_pixel_value(padding_bottom_mv),
1600 left: to_pixel_value(padding_left_mv),
1601 };
1602
1603 let border_top_mv = get_css_border_top_width(styled_dom, dom_id, &node_state);
1605 let border_right_mv = get_css_border_right_width(styled_dom, dom_id, &node_state);
1606 let border_bottom_mv = get_css_border_bottom_width(styled_dom, dom_id, &node_state);
1607 let border_left_mv = get_css_border_left_width(styled_dom, dom_id, &node_state);
1608
1609 let unresolved_border = UnresolvedEdge {
1611 top: to_pixel_value(border_top_mv),
1612 right: to_pixel_value(border_right_mv),
1613 bottom: to_pixel_value(border_bottom_mv),
1614 left: to_pixel_value(border_left_mv),
1615 };
1616
1617 let unresolved = UnresolvedBoxProps {
1619 margin: unresolved_margin,
1620 padding: unresolved_padding,
1621 border: unresolved_border,
1622 };
1623
1624 let params = crate::solver3::geometry::ResolutionParams {
1626 containing_block: viewport_size,
1627 viewport_size,
1628 element_font_size: context.parent_font_size,
1629 root_font_size: context.root_font_size,
1630 };
1631
1632 let resolved = unresolved.resolve(¶ms);
1634
1635 if let Some(msgs) = debug_messages.as_mut() {
1637 let has_vh = match &unresolved_margin.top {
1639 UnresolvedMargin::Length(pv) => pv.metric == azul_css::props::basic::SizeMetric::Vh,
1640 _ => false,
1641 };
1642 if has_vh || resolved.margin.top > 0.0 || resolved.margin.left > 0.0 {
1643 msgs.push(LayoutDebugMessage::box_props(format!(
1644 "NodeId {:?} ({:?}): unresolved_margin_top={:?}, resolved_margin_top={:.2}, viewport_size={:?}",
1645 dom_id, node_data.node_type,
1646 unresolved_margin.top,
1647 resolved.margin.top,
1648 viewport_size
1649 )));
1650 }
1651 }
1652
1653 if let Some(msgs) = debug_messages.as_mut() {
1655 msgs.push(LayoutDebugMessage::box_props(format!(
1656 "NodeId {:?} ({:?}): margin_auto: left={}, right={}, top={}, bottom={} | margin_left={:?}",
1657 dom_id, node_data.node_type,
1658 resolved.margin_auto.left, resolved.margin_auto.right,
1659 resolved.margin_auto.top, resolved.margin_auto.bottom,
1660 unresolved_margin.left
1661 )));
1662 }
1663
1664 if matches!(node_data.node_type, azul_core::dom::NodeType::Body) {
1666 if let Some(msgs) = debug_messages.as_mut() {
1667 msgs.push(LayoutDebugMessage::box_props(format!(
1668 "Body margin resolved: top={:.2}, right={:.2}, bottom={:.2}, left={:.2}",
1669 resolved.margin.top, resolved.margin.right,
1670 resolved.margin.bottom, resolved.margin.left
1671 )));
1672 }
1673 }
1674
1675 CollectedBoxProps { unresolved, resolved }
1676}
1677
1678fn is_whitespace_only_text(styled_dom: &StyledDom, node_id: NodeId) -> bool {
1686 let binding = styled_dom.node_data.as_container();
1687 let node_data = binding.get(node_id);
1688 if let Some(data) = node_data {
1689 if let NodeType::Text(text) = data.get_node_type() {
1690 return text.chars().all(|c| c.is_whitespace());
1693 }
1694 }
1695
1696 false
1697}
1698
1699fn should_skip_for_table_structure(
1707 styled_dom: &StyledDom,
1708 node_id: NodeId,
1709 parent_display: LayoutDisplay,
1710) -> bool {
1711 matches!(
1714 parent_display,
1715 LayoutDisplay::Table
1716 | LayoutDisplay::TableRowGroup
1717 | LayoutDisplay::TableHeaderGroup
1718 | LayoutDisplay::TableFooterGroup
1719 | LayoutDisplay::TableRow
1720 ) && is_whitespace_only_text(styled_dom, node_id)
1721}
1722
1723fn needs_table_parent_wrapper(
1735 styled_dom: &StyledDom,
1736 node_id: NodeId,
1737 parent_display: LayoutDisplay,
1738) -> Option<AnonymousBoxType> {
1739 let child_display = get_display_type(styled_dom, node_id);
1740
1741 if child_display == LayoutDisplay::TableCell {
1744 match parent_display {
1745 LayoutDisplay::TableRow
1746 | LayoutDisplay::TableRowGroup
1747 | LayoutDisplay::TableHeaderGroup
1748 | LayoutDisplay::TableFooterGroup => {
1749 None
1751 }
1752 _ => Some(AnonymousBoxType::TableRow),
1753 }
1754 }
1755 else if matches!(child_display, LayoutDisplay::TableRow) {
1757 match parent_display {
1758 LayoutDisplay::Table
1759 | LayoutDisplay::TableRowGroup
1760 | LayoutDisplay::TableHeaderGroup
1761 | LayoutDisplay::TableFooterGroup => {
1762 None }
1764 _ => Some(AnonymousBoxType::TableWrapper),
1765 }
1766 }
1767 else if matches!(
1769 child_display,
1770 LayoutDisplay::TableRowGroup
1771 | LayoutDisplay::TableHeaderGroup
1772 | LayoutDisplay::TableFooterGroup
1773 ) {
1774 match parent_display {
1775 LayoutDisplay::Table => None,
1776 _ => Some(AnonymousBoxType::TableWrapper),
1777 }
1778 } else {
1779 None
1780 }
1781}
1782
1783pub fn get_display_type(styled_dom: &StyledDom, node_id: NodeId) -> LayoutDisplay {
1786 use crate::solver3::getters::get_display_property;
1787 get_display_property(styled_dom, Some(node_id)).unwrap_or(LayoutDisplay::Inline)
1788}
1789
1790fn establishes_new_block_formatting_context(styled_dom: &StyledDom, node_id: NodeId) -> bool {
1793 let display = get_display_type(styled_dom, node_id);
1794 if matches!(
1795 display,
1796 LayoutDisplay::InlineBlock | LayoutDisplay::TableCell | LayoutDisplay::FlowRoot
1797 ) {
1798 return true;
1799 }
1800
1801 if let Some(styled_node) = styled_dom.styled_nodes.as_container().get(node_id) {
1802 let overflow_x = get_overflow_x(styled_dom, node_id, &styled_node.styled_node_state);
1805 if !overflow_x.is_visible_or_clip() {
1806 return true;
1807 }
1808
1809 let overflow_y = get_overflow_y(styled_dom, node_id, &styled_node.styled_node_state);
1810 if !overflow_y.is_visible_or_clip() {
1811 return true;
1812 }
1813
1814 let position = get_position(styled_dom, node_id, &styled_node.styled_node_state);
1816
1817 if position.is_absolute_or_fixed() {
1818 return true;
1819 }
1820
1821 let float = get_float(styled_dom, node_id, &styled_node.styled_node_state);
1823 if !float.is_none() {
1824 return true;
1825 }
1826 }
1827
1828 if styled_dom.root.into_crate_internal() == Some(node_id) {
1830 return true;
1831 }
1832
1833 false
1834}
1835
1836fn determine_formatting_context(styled_dom: &StyledDom, node_id: NodeId) -> FormattingContext {
1838 let node_data = &styled_dom.node_data.as_container()[node_id];
1841
1842 if matches!(node_data.get_node_type(), NodeType::Text(_)) {
1843 return FormattingContext::Inline;
1845 }
1846
1847 let display_type = get_display_type(styled_dom, node_id);
1848
1849 match display_type {
1850 LayoutDisplay::Inline => FormattingContext::Inline,
1851
1852 LayoutDisplay::Block | LayoutDisplay::FlowRoot | LayoutDisplay::ListItem => {
1856 if has_only_inline_children(styled_dom, node_id) {
1857 FormattingContext::Inline
1859 } else {
1860 FormattingContext::Block {
1862 establishes_new_context: establishes_new_block_formatting_context(
1863 styled_dom, node_id,
1864 ),
1865 }
1866 }
1867 }
1868 LayoutDisplay::InlineBlock => FormattingContext::InlineBlock,
1869 LayoutDisplay::Table | LayoutDisplay::InlineTable => FormattingContext::Table,
1870 LayoutDisplay::TableRowGroup
1871 | LayoutDisplay::TableHeaderGroup
1872 | LayoutDisplay::TableFooterGroup => FormattingContext::TableRowGroup,
1873 LayoutDisplay::TableRow => FormattingContext::TableRow,
1874 LayoutDisplay::TableCell => FormattingContext::TableCell,
1875 LayoutDisplay::None => FormattingContext::None,
1876 LayoutDisplay::Flex | LayoutDisplay::InlineFlex => FormattingContext::Flex,
1877 LayoutDisplay::TableColumnGroup => FormattingContext::TableColumnGroup,
1878 LayoutDisplay::TableCaption => FormattingContext::TableCaption,
1879 LayoutDisplay::Grid | LayoutDisplay::InlineGrid => FormattingContext::Grid,
1880
1881 LayoutDisplay::TableColumn | LayoutDisplay::RunIn | LayoutDisplay::Marker => {
1883 FormattingContext::Block {
1884 establishes_new_context: true,
1885 }
1886 }
1887 }
1888}