1pub mod cache;
6pub mod calc;
7pub mod counters;
8pub mod display_list;
9pub mod fc;
10pub mod geometry;
11pub mod getters;
12pub mod layout_tree;
13pub mod paged_layout;
14pub mod pagination;
15pub mod positioning;
16pub mod scrollbar;
17pub mod sizing;
18pub mod taffy_bridge;
19
20#[macro_export]
22macro_rules! debug_info {
23 ($ctx:expr, $($arg:tt)*) => {
24 if $ctx.debug_messages.is_some() {
25 $ctx.debug_info_inner(format!($($arg)*));
26 }
27 };
28}
29
30#[macro_export]
32macro_rules! debug_warning {
33 ($ctx:expr, $($arg:tt)*) => {
34 if $ctx.debug_messages.is_some() {
35 $ctx.debug_warning_inner(format!($($arg)*));
36 }
37 };
38}
39
40#[macro_export]
42macro_rules! debug_error {
43 ($ctx:expr, $($arg:tt)*) => {
44 if $ctx.debug_messages.is_some() {
45 $ctx.debug_error_inner(format!($($arg)*));
46 }
47 };
48}
49
50#[macro_export]
52macro_rules! debug_log {
53 ($ctx:expr, $($arg:tt)*) => {
54 if $ctx.debug_messages.is_some() {
55 $ctx.debug_log_inner(format!($($arg)*));
56 }
57 };
58}
59
60#[macro_export]
62macro_rules! debug_box_props {
63 ($ctx:expr, $($arg:tt)*) => {
64 if $ctx.debug_messages.is_some() {
65 $ctx.debug_box_props_inner(format!($($arg)*));
66 }
67 };
68}
69
70#[macro_export]
72macro_rules! debug_css_getter {
73 ($ctx:expr, $($arg:tt)*) => {
74 if $ctx.debug_messages.is_some() {
75 $ctx.debug_css_getter_inner(format!($($arg)*));
76 }
77 };
78}
79
80#[macro_export]
82macro_rules! debug_bfc_layout {
83 ($ctx:expr, $($arg:tt)*) => {
84 if $ctx.debug_messages.is_some() {
85 $ctx.debug_bfc_layout_inner(format!($($arg)*));
86 }
87 };
88}
89
90#[macro_export]
92macro_rules! debug_ifc_layout {
93 ($ctx:expr, $($arg:tt)*) => {
94 if $ctx.debug_messages.is_some() {
95 $ctx.debug_ifc_layout_inner(format!($($arg)*));
96 }
97 };
98}
99
100#[macro_export]
102macro_rules! debug_table_layout {
103 ($ctx:expr, $($arg:tt)*) => {
104 if $ctx.debug_messages.is_some() {
105 $ctx.debug_table_layout_inner(format!($($arg)*));
106 }
107 };
108}
109
110#[macro_export]
112macro_rules! debug_display_type {
113 ($ctx:expr, $($arg:tt)*) => {
114 if $ctx.debug_messages.is_some() {
115 $ctx.debug_display_type_inner(format!($($arg)*));
116 }
117 };
118}
119
120use std::{collections::{BTreeMap, HashMap}, sync::Arc};
121
122use azul_core::{
123 dom::{DomId, NodeId},
124 geom::{LogicalPosition, LogicalRect, LogicalSize},
125 hit_test::{DocumentId, ScrollPosition},
126 resources::RendererResources,
127 selection::{TextCursor, TextSelection},
128 styled_dom::StyledDom,
129};
130
131pub const POSITION_UNSET: LogicalPosition = LogicalPosition { x: f32::MIN, y: f32::MIN };
133
134const MAX_SCROLLBAR_REFLOW_ITERATIONS: usize = 10;
138
139pub type PositionVec = Vec<LogicalPosition>;
142
143#[inline]
145pub fn new_position_vec(num_nodes: usize) -> PositionVec {
146 vec![POSITION_UNSET; num_nodes]
147}
148
149#[inline(always)]
155pub fn pos_get(positions: &PositionVec, idx: usize) -> Option<LogicalPosition> {
156 positions.get(idx).copied().filter(|p| p.x != f32::MIN)
157}
158
159#[inline(always)]
161pub fn pos_set(positions: &mut PositionVec, idx: usize, pos: LogicalPosition) {
162 if idx >= positions.len() {
163 positions.resize(idx + 1, POSITION_UNSET);
164 }
165 positions[idx] = pos;
166}
167
168#[inline(always)]
170pub fn pos_contains(positions: &PositionVec, idx: usize) -> bool {
171 positions.get(idx).map_or(false, |p| p.x != f32::MIN)
172}
173use azul_css::{
174 props::property::{CssProperty, CssPropertyCategory},
175 LayoutDebugMessage, LayoutDebugMessageType,
176};
177
178use self::{
179 display_list::generate_display_list,
180 geometry::IntrinsicSizes,
181 getters::get_writing_mode,
182 layout_tree::{generate_layout_tree, LayoutTree},
183 sizing::calculate_intrinsic_sizes,
184};
185#[cfg(feature = "text_layout")]
186pub use crate::font_traits::TextLayoutCache;
187use crate::{
188 font_traits::ParsedFontTrait,
189 solver3::{
190 cache::LayoutCache,
191 display_list::DisplayList,
192 fc::{LayoutConstraints, LayoutResult},
193 layout_tree::DirtyFlag,
194 },
195};
196
197pub type NodeHashMap = BTreeMap<usize, u64>;
199
200pub struct LayoutContext<'a, T: ParsedFontTrait> {
202 pub styled_dom: &'a StyledDom,
203 #[cfg(feature = "text_layout")]
204 pub font_manager: &'a crate::font_traits::FontManager<T>,
205 #[cfg(not(feature = "text_layout"))]
206 pub font_manager: core::marker::PhantomData<&'a T>,
207 pub text_selections: &'a BTreeMap<DomId, TextSelection>,
209 pub debug_messages: &'a mut Option<Vec<LayoutDebugMessage>>,
210 pub counters: &'a mut HashMap<(usize, String), i32>,
211 pub viewport_size: LogicalSize,
212 pub fragmentation_context: Option<&'a mut crate::paged::FragmentationContext>,
215 pub cursor_is_visible: bool,
219 pub cursor_locations: Vec<(DomId, NodeId, TextCursor)>,
223 pub preedit_text: Option<String>,
226 pub dirty_text_overrides: BTreeMap<(DomId, NodeId), String>,
231 pub cache_map: cache::LayoutCacheMap,
235 pub image_cache: &'a azul_core::resources::ImageCache,
237 pub system_style: Option<std::sync::Arc<azul_css::system::SystemStyle>>,
240 pub get_system_time_fn: azul_core::task::GetSystemTimeCallback,
244 pub scrollbar_style_cache:
257 core::cell::RefCell<std::collections::HashMap<NodeId, crate::solver3::getters::ComputedScrollbarStyle>>,
258}
259
260impl<'a, T: ParsedFontTrait> LayoutContext<'a, T> {
261 #[inline]
263 pub fn has_debug(&self) -> bool {
264 self.debug_messages.is_some()
265 }
266
267 #[inline]
269 pub fn debug_log_inner(&mut self, message: String) {
270 if let Some(messages) = self.debug_messages.as_mut() {
271 messages.push(LayoutDebugMessage {
272 message: message.into(),
273 location: "solver3".into(),
274 message_type: Default::default(),
275 });
276 }
277 }
278
279 #[inline]
281 pub fn debug_info_inner(&mut self, message: String) {
282 if let Some(messages) = self.debug_messages.as_mut() {
283 messages.push(LayoutDebugMessage::info(message));
284 }
285 }
286
287 #[inline]
289 pub fn debug_warning_inner(&mut self, message: String) {
290 if let Some(messages) = self.debug_messages.as_mut() {
291 messages.push(LayoutDebugMessage::warning(message));
292 }
293 }
294
295 #[inline]
297 pub fn debug_error_inner(&mut self, message: String) {
298 if let Some(messages) = self.debug_messages.as_mut() {
299 messages.push(LayoutDebugMessage::error(message));
300 }
301 }
302
303 #[inline]
305 pub fn debug_box_props_inner(&mut self, message: String) {
306 if let Some(messages) = self.debug_messages.as_mut() {
307 messages.push(LayoutDebugMessage::box_props(message));
308 }
309 }
310
311 #[inline]
313 pub fn debug_css_getter_inner(&mut self, message: String) {
314 if let Some(messages) = self.debug_messages.as_mut() {
315 messages.push(LayoutDebugMessage::css_getter(message));
316 }
317 }
318
319 #[inline]
321 pub fn debug_bfc_layout_inner(&mut self, message: String) {
322 if let Some(messages) = self.debug_messages.as_mut() {
323 messages.push(LayoutDebugMessage::bfc_layout(message));
324 }
325 }
326
327 #[inline]
329 pub fn debug_ifc_layout_inner(&mut self, message: String) {
330 if let Some(messages) = self.debug_messages.as_mut() {
331 messages.push(LayoutDebugMessage::ifc_layout(message));
332 }
333 }
334
335 #[inline]
337 pub fn debug_table_layout_inner(&mut self, message: String) {
338 if let Some(messages) = self.debug_messages.as_mut() {
339 messages.push(LayoutDebugMessage::table_layout(message));
340 }
341 }
342
343 #[inline]
345 pub fn debug_display_type_inner(&mut self, message: String) {
346 if let Some(messages) = self.debug_messages.as_mut() {
347 messages.push(LayoutDebugMessage::display_type(message));
348 }
349 }
350
351 #[inline]
354 pub fn debug_info(&mut self, message: impl Into<String>) {
355 self.debug_info_inner(message.into());
356 }
357 #[inline]
358 pub fn debug_warning(&mut self, message: impl Into<String>) {
359 self.debug_warning_inner(message.into());
360 }
361 #[inline]
362 pub fn debug_error(&mut self, message: impl Into<String>) {
363 self.debug_error_inner(message.into());
364 }
365 #[inline]
366 pub fn debug_log(&mut self, message: &str) {
367 self.debug_log_inner(message.to_string());
368 }
369 #[inline]
370 pub fn debug_box_props(&mut self, message: impl Into<String>) {
371 self.debug_box_props_inner(message.into());
372 }
373 #[inline]
374 pub fn debug_css_getter(&mut self, message: impl Into<String>) {
375 self.debug_css_getter_inner(message.into());
376 }
377 #[inline]
378 pub fn debug_bfc_layout(&mut self, message: impl Into<String>) {
379 self.debug_bfc_layout_inner(message.into());
380 }
381 #[inline]
382 pub fn debug_ifc_layout(&mut self, message: impl Into<String>) {
383 self.debug_ifc_layout_inner(message.into());
384 }
385 #[inline]
386 pub fn debug_table_layout(&mut self, message: impl Into<String>) {
387 self.debug_table_layout_inner(message.into());
388 }
389 #[inline]
390 pub fn debug_display_type(&mut self, message: impl Into<String>) {
391 self.debug_display_type_inner(message.into());
392 }
393}
394
395#[cfg(feature = "text_layout")]
402pub static SKIP_DISPLAY_LIST: core::sync::atomic::AtomicBool =
416 core::sync::atomic::AtomicBool::new(false);
417
418pub fn set_skip_display_list(skip: bool) {
425 SKIP_DISPLAY_LIST.store(skip, core::sync::atomic::Ordering::Relaxed);
426}
427
428#[inline(never)]
435pub fn layout_document<T: ParsedFontTrait + Sync + 'static>(
436 cache: &mut LayoutCache,
437 text_cache: &mut TextLayoutCache,
438 new_dom: &StyledDom,
439 viewport: LogicalRect,
440 font_manager: &crate::font_traits::FontManager<T>,
441 scroll_offsets: &BTreeMap<NodeId, ScrollPosition>,
442 text_selections: &BTreeMap<DomId, TextSelection>,
443 debug_messages: &mut Option<Vec<LayoutDebugMessage>>,
444 gpu_value_cache: Option<&azul_core::gpu::GpuValueCache>,
445 renderer_resources: &azul_core::resources::RendererResources,
446 id_namespace: azul_core::resources::IdNamespace,
447 dom_id: azul_core::dom::DomId,
448 cursor_is_visible: bool,
449 cursor_locations: Vec<(DomId, NodeId, TextCursor)>,
450 preedit_text: Option<String>,
451 image_cache: &azul_core::resources::ImageCache,
452 system_style: Option<std::sync::Arc<azul_css::system::SystemStyle>>,
453 get_system_time_fn: azul_core::task::GetSystemTimeCallback,
454) -> Result<DisplayList> {
455 crate::solver3::layout_tree::IfcId::reset_counter();
458 unsafe { core::ptr::write_volatile(0x400A4 as *mut u32, 0xDD00_0001u32); }
462
463 if let Some(msgs) = debug_messages.as_mut() {
464 msgs.push(LayoutDebugMessage::info(format!(
465 "[Layout] layout_document called - viewport: ({:.1}, {:.1}) size ({:.1}x{:.1})",
466 viewport.origin.x, viewport.origin.y, viewport.size.width, viewport.size.height
467 )));
468 msgs.push(LayoutDebugMessage::info(format!(
469 "[Layout] DOM has {} nodes",
470 new_dom.node_data.len()
471 )));
472 }
473
474 let mut counter_values = HashMap::new();
476 let mut ctx_temp = LayoutContext {
477 scrollbar_style_cache: core::cell::RefCell::new(std::collections::HashMap::new()),
478 styled_dom: new_dom,
479 font_manager,
480 text_selections,
481 debug_messages,
482 counters: &mut counter_values,
483 viewport_size: viewport.size,
484 fragmentation_context: None,
485 cursor_is_visible,
486 cursor_locations: cursor_locations.clone(),
487 preedit_text: preedit_text.clone(),
488 dirty_text_overrides: BTreeMap::new(),
489 cache_map: cache::LayoutCacheMap::default(), image_cache,
491 system_style: system_style.clone(),
492 get_system_time_fn,
493 };
494
495 crate::probe::sample_peak_rss("rss:enter_layout_document");
496
497 let dom_ptr = new_dom as *const StyledDom as usize;
501 if dom_ptr == cache.prev_dom_ptr
502 && viewport == cache.prev_viewport
503 && cache.cached_display_list.is_some()
504 {
505 let _p = crate::probe::Probe::span("dom_ptr_cache_hit");
506 let (_, _, cached_dl) = cache.cached_display_list.as_ref().unwrap();
507 return Ok(cached_dl.clone());
508 }
509 cache.prev_dom_ptr = dom_ptr;
510 cache.prev_viewport = viewport;
511
512 crate::probe::reset_peak();
514 let (mut new_tree, mut recon_result) =
515 cache::reconcile_and_invalidate(&mut ctx_temp, cache, viewport)?;
516 unsafe { core::ptr::write_volatile(0x400A4 as *mut u32, 0xDD00_0002u32); }
517 crate::probe::sample_peak_rss("rss:after_reconcile");
518 crate::probe::sample_phase_peak("rss:peak_during_reconcile");
519
520 if let Some((cached_hash, cached_viewport, cached_dl)) = &cache.cached_display_list {
531 let new_root_hash = new_tree
532 .cold(new_tree.root)
533 .map(|c| c.subtree_hash);
534 if new_root_hash == Some(*cached_hash) && *cached_viewport == viewport {
535 let _p = crate::probe::Probe::span("display_list_cache_hit");
536 return Ok(cached_dl.clone());
537 }
538 }
539
540 for &node_idx in &recon_result.intrinsic_dirty {
542 if let Some(warm) = new_tree.warm_mut(node_idx) {
543 warm.taffy_cache.clear();
544 }
545 }
546
547 {
551 let _p = crate::probe::Probe::span("compute_counters");
552 cache::compute_counters(&new_dom, &new_tree, &mut counter_values);
553 }
554
555 let mut cache_map = std::mem::take(&mut cache.cache_map);
571 let _probe_cache_remap = Some(crate::probe::Probe::span("cache_map_remap"));
572 if let Some(old_tree) = cache.tree.as_ref() {
573 let mut remapped = cache::LayoutCacheMap::default();
574 remapped.entries.resize_with(new_tree.nodes.len(), Default::default);
575
576 for (dom_id, new_indices) in new_tree.dom_to_layout.iter() {
579 let Some(old_indices) = old_tree.dom_to_layout.get(dom_id) else {
580 continue;
581 };
582 for (pair_idx, &new_layout_idx) in new_indices.iter().enumerate() {
583 let Some(&old_layout_idx) = old_indices.get(pair_idx) else {
584 continue;
585 };
586 if old_layout_idx >= cache_map.entries.len()
587 || new_layout_idx >= remapped.entries.len()
588 {
589 continue;
590 }
591 remapped.entries[new_layout_idx] =
592 core::mem::take(&mut cache_map.entries[old_layout_idx]);
593 }
594 }
595
596 fn collect_anon_children_by_parent(
604 tree: &LayoutTree,
605 ) -> std::collections::HashMap<usize, Vec<usize>> {
606 let mut map: std::collections::HashMap<usize, Vec<usize>> =
607 std::collections::HashMap::new();
608 for (idx, node) in tree.nodes.iter().enumerate() {
609 if node.dom_node_id.is_some() {
610 continue;
611 }
612 if let Some(parent) = node.parent {
613 map.entry(parent).or_default().push(idx);
614 }
615 }
616 map
617 }
618
619 let old_anon_by_parent = collect_anon_children_by_parent(old_tree);
622 let new_anon_by_parent = collect_anon_children_by_parent(&new_tree);
623
624 let mut new_to_old_layout_idx: std::collections::HashMap<usize, usize> =
629 std::collections::HashMap::new();
630 for (dom_id, new_indices) in new_tree.dom_to_layout.iter() {
631 let Some(old_indices) = old_tree.dom_to_layout.get(dom_id) else {
632 continue;
633 };
634 for (pair_idx, &new_layout_idx) in new_indices.iter().enumerate() {
635 if let Some(&old_layout_idx) = old_indices.get(pair_idx) {
636 new_to_old_layout_idx.insert(new_layout_idx, old_layout_idx);
637 }
638 }
639 }
640
641 for (new_parent_idx, new_anon_children) in new_anon_by_parent {
642 let Some(&old_parent_idx) = new_to_old_layout_idx.get(&new_parent_idx) else {
643 continue;
644 };
645 let Some(old_anon_children) = old_anon_by_parent.get(&old_parent_idx) else {
646 continue;
647 };
648 for (ord, &new_anon_idx) in new_anon_children.iter().enumerate() {
649 let Some(&old_anon_idx) = old_anon_children.get(ord) else {
650 continue;
651 };
652 if old_anon_idx >= cache_map.entries.len()
653 || new_anon_idx >= remapped.entries.len()
654 {
655 continue;
656 }
657 remapped.entries[new_anon_idx] =
658 core::mem::take(&mut cache_map.entries[old_anon_idx]);
659 }
660 }
661
662 cache_map = remapped;
663 } else {
664 cache_map.resize_to_tree(new_tree.nodes.len());
665 }
666 drop(_probe_cache_remap);
667 crate::probe::sample_peak_rss("rss:after_cache_remap");
668 for &node_idx in &recon_result.intrinsic_dirty {
669 cache_map.mark_dirty(node_idx, &new_tree.nodes);
670 }
671 for &node_idx in &recon_result.layout_roots {
672 cache_map.mark_dirty(node_idx, &new_tree.nodes);
673 }
674
675 let mut ctx = LayoutContext {
677 scrollbar_style_cache: core::cell::RefCell::new(std::collections::HashMap::new()),
678 styled_dom: &new_dom,
679 font_manager,
680 text_selections,
681 debug_messages,
682 counters: &mut counter_values,
683 viewport_size: viewport.size,
684 fragmentation_context: None,
685 cursor_is_visible,
686 cursor_locations,
687 preedit_text,
688 dirty_text_overrides: BTreeMap::new(),
689 cache_map, image_cache,
691 system_style,
692 get_system_time_fn,
693 };
694
695 if recon_result.is_clean() && cache.tree.is_some() {
704 debug_log!(ctx, "No changes, returning existing display list");
705 let tree = cache.tree.as_ref().ok_or(LayoutError::InvalidTree)?;
706
707 let scroll_ids = if cache.scroll_ids.is_empty() {
709 use crate::window::LayoutWindow;
710 let (scroll_ids, scroll_id_to_node_id) =
711 LayoutWindow::compute_scroll_ids(tree, &new_dom);
712 cache.scroll_ids = scroll_ids.clone();
713 cache.scroll_id_to_node_id = scroll_id_to_node_id;
714 scroll_ids
715 } else {
716 cache.scroll_ids.clone()
717 };
718
719 if SKIP_DISPLAY_LIST.load(core::sync::atomic::Ordering::Relaxed) {
720 return Ok(DisplayList::default());
721 }
722 return generate_display_list(
723 &mut ctx,
724 tree,
725 &cache.calculated_positions,
726 scroll_offsets,
727 &scroll_ids,
728 gpu_value_cache,
729 renderer_resources,
730 id_namespace,
731 dom_id,
732 );
733 }
734
735 unsafe { core::ptr::write_volatile(0x400A4 as *mut u32, 0xDD00_0003u32); }
737
738 let mut calculated_positions = cache.calculated_positions.clone();
740 let mut loop_count = 0;
741 loop {
742 loop_count += 1;
743 if loop_count > MAX_SCROLLBAR_REFLOW_ITERATIONS {
744 debug_warning!(ctx, "Scrollbar reflow loop hit limit of {} iterations, breaking to avoid infinite loop", MAX_SCROLLBAR_REFLOW_ITERATIONS);
745 break;
746 }
747
748 calculated_positions = {
749 let _p = crate::probe::Probe::span("clone_calculated_positions");
750 cache.calculated_positions.clone()
751 };
752 let mut reflow_needed_for_scrollbars = false;
753
754 {
755 crate::probe::reset_peak();
756 let _p = crate::probe::Probe::span("calc_intrinsic_sizes");
757 calculate_intrinsic_sizes(
758 &mut ctx,
759 &mut new_tree,
760 text_cache,
761 &recon_result.intrinsic_dirty,
762 )?;
763 }
764 crate::probe::sample_peak_rss("rss:after_calc_intrinsic");
765 crate::probe::sample_phase_peak("rss:peak_during_intrinsic");
766 unsafe { core::ptr::write_volatile(0x400A4 as *mut u32, 0xDD00_0005u32); }
769
770 for &root_idx in &recon_result.layout_roots {
771 let (cb_pos, cb_size) = get_containing_block_for_node(
772 &new_tree,
773 &new_dom,
774 root_idx,
775 &calculated_positions,
776 viewport,
777 );
778 unsafe { core::ptr::write_volatile(0x400A4 as *mut u32, 0xDD00_0053u32); }
782
783 let root_node = &new_tree.nodes[root_idx];
788 let root_bp = root_node.box_props.unpack();
789 unsafe { core::ptr::write_volatile(0x400A4 as *mut u32, 0xDD00_0054u32); }
790
791 let is_root_with_margin = root_node.parent.is_none()
792 && (root_bp.margin.left != 0.0 || root_bp.margin.top != 0.0);
793
794 let adjusted_cb_pos = if is_root_with_margin {
795 LogicalPosition::new(
796 cb_pos.x + root_bp.margin.left,
797 cb_pos.y + root_bp.margin.top,
798 )
799 } else {
800 cb_pos
801 };
802 unsafe { core::ptr::write_volatile(0x400A4 as *mut u32, 0xDD00_0056u32); }
803
804 if let Some(debug_msgs) = ctx.debug_messages.as_mut() {
806 let dom_name = root_node
807 .dom_node_id
808 .and_then(|id| new_dom.node_data.as_container().internal.get(id.index()))
809 .map(|n| format!("{:?}", n.node_type))
810 .unwrap_or_else(|| "Unknown".to_string());
811
812 debug_msgs.push(LayoutDebugMessage::new(
813 LayoutDebugMessageType::PositionCalculation,
814 format!(
815 "[LAYOUT ROOT {}] {} - CB pos=({:.2}, {:.2}), adjusted=({:.2}, {:.2}), \
816 CB size=({:.2}x{:.2}), viewport=({:.2}x{:.2}), margin=({:.2}, {:.2})",
817 root_idx,
818 dom_name,
819 cb_pos.x,
820 cb_pos.y,
821 adjusted_cb_pos.x,
822 adjusted_cb_pos.y,
823 cb_size.width,
824 cb_size.height,
825 viewport.size.width,
826 viewport.size.height,
827 root_bp.margin.left,
828 root_bp.margin.top
829 ),
830 ));
831 }
832
833 crate::probe::hint_purge_allocator();
836 crate::probe::sample_peak_rss("rss:before_root_layout");
837 crate::probe::reset_peak();
838 unsafe { core::ptr::write_volatile(0x400A4 as *mut u32, 0xDD00_0055u32); }
841 let _clr = {
846 let _p = crate::probe::Probe::span("root_layout_pass");
847 cache::calculate_layout_for_subtree(
848 &mut ctx,
849 &mut new_tree,
850 text_cache,
851 root_idx,
852 adjusted_cb_pos,
853 cb_size,
854 &mut calculated_positions,
855 &mut reflow_needed_for_scrollbars,
856 &mut cache.float_cache,
857 cache::ComputeMode::PerformLayout,
858 )
859 };
860 unsafe { core::ptr::write_volatile(0x400A4 as *mut u32, if _clr.is_ok() { 0xDD00_0057u32 } else { 0xDD00_005Eu32 }); }
861 crate::probe::sample_peak_rss("rss:after_root_layout");
862 crate::probe::sample_phase_peak("rss:peak_during_root_layout");
863
864 if !pos_contains(&calculated_positions, root_idx) {
872 let root_node = &new_tree.nodes[root_idx];
873 let root_bp2 = root_node.box_props.unpack();
874
875 let root_position = LogicalPosition::new(
879 cb_pos.x + root_bp2.margin.left,
880 cb_pos.y + root_bp2.margin.top,
881 );
882
883 if let Some(debug_msgs) = ctx.debug_messages.as_mut() {
885 let dom_name = root_node
886 .dom_node_id
887 .and_then(|id| new_dom.node_data.as_container().internal.get(id.index()))
888 .map(|n| format!("{:?}", n.node_type))
889 .unwrap_or_else(|| "Unknown".to_string());
890
891 debug_msgs.push(LayoutDebugMessage::new(
892 LayoutDebugMessageType::PositionCalculation,
893 format!(
894 "[ROOT POSITION {}] {} - Inserting position=({:.2}, {:.2}) (viewport origin + margin), \
895 margin=({:.2}, {:.2}, {:.2}, {:.2})",
896 root_idx,
897 dom_name,
898 root_position.x,
899 root_position.y,
900 root_bp2.margin.top,
901 root_bp2.margin.right,
902 root_bp2.margin.bottom,
903 root_bp2.margin.left
904 ),
905 ));
906 }
907
908 pos_set(&mut calculated_positions, root_idx, root_position);
909 }
910 }
911 unsafe { core::ptr::write_volatile(0x400A4 as *mut u32, 0xDD00_0006u32); }
914
915 {
916 let _p = crate::probe::Probe::span("reposition_clean_subtrees");
917 cache::reposition_clean_subtrees(
918 &new_dom,
919 &new_tree,
920 &recon_result.layout_roots,
921 &mut calculated_positions,
922 );
923 }
924
925 if reflow_needed_for_scrollbars {
926 debug_log!(ctx,
927 "Scrollbars changed container size, starting full reflow (loop {})",
928 loop_count
929 );
930 recon_result.layout_roots.clear();
931 recon_result.layout_roots.insert(new_tree.root);
932 recon_result.intrinsic_dirty = (0..new_tree.nodes.len()).collect();
933 continue;
934 }
935
936 break;
937 }
938
939 {
954 let _p = crate::probe::Probe::span("adjust_relative_positions");
955 positioning::adjust_relative_positions(
956 &mut ctx,
957 &new_tree,
958 &mut calculated_positions,
959 viewport,
960 )?;
961 }
962
963 {
969 let _p = crate::probe::Probe::span("adjust_sticky_positions");
970 positioning::adjust_sticky_positions(
971 &mut ctx,
972 &new_tree,
973 &mut calculated_positions,
974 scroll_offsets,
975 viewport,
976 )?;
977 }
978
979 {
984 let _p = crate::probe::Probe::span("position_out_of_flow");
985 positioning::position_out_of_flow_elements(
986 &mut ctx,
987 &mut new_tree,
988 &mut calculated_positions,
989 viewport,
990 )?;
991 }
992
993 use crate::window::LayoutWindow;
996 let (scroll_ids, scroll_id_to_node_id) = {
997 let _p = crate::probe::Probe::span("compute_scroll_ids");
998 LayoutWindow::compute_scroll_ids(&new_tree, &new_dom)
999 };
1000
1001 crate::probe::sample_peak_rss("rss:before_display_list");
1002 crate::probe::reset_peak();
1003 let display_list = if SKIP_DISPLAY_LIST.load(core::sync::atomic::Ordering::Relaxed) {
1005 DisplayList::default()
1007 } else {
1008 let _p = crate::probe::Probe::span("generate_display_list");
1009 generate_display_list(
1010 &mut ctx,
1011 &new_tree,
1012 &calculated_positions,
1013 scroll_offsets,
1014 &scroll_ids,
1015 gpu_value_cache,
1016 renderer_resources,
1017 id_namespace,
1018 dom_id,
1019 )?
1020 };
1021 crate::probe::sample_phase_peak("rss:peak_during_display_list");
1022
1023 let _p_writeback = crate::probe::Probe::span("cache_writeback");
1025 let cache_map_back = std::mem::take(&mut ctx.cache_map);
1026
1027 let root_subtree_hash = new_tree
1032 .cold(new_tree.root)
1033 .map(|c| c.subtree_hash)
1034 .unwrap_or(crate::solver3::layout_tree::SubtreeHash(0));
1035 cache.cached_display_list = Some((root_subtree_hash, viewport, display_list.clone()));
1036
1037 cache.tree = Some(new_tree);
1038 cache.previous_positions = std::mem::replace(&mut cache.calculated_positions, calculated_positions);
1039 cache.viewport = Some(viewport);
1040 cache.scroll_ids = scroll_ids;
1041 cache.scroll_id_to_node_id = scroll_id_to_node_id;
1042 unsafe { core::ptr::write_volatile(0x400A4 as *mut u32, 0xDD00_0004u32 | ((cache.calculated_positions.len() as u32 & 0xfff) << 4)); }
1045 cache.counters = counter_values;
1046 cache.cache_map = cache_map_back;
1047 crate::probe::sample_peak_rss("rss:after_layout_document");
1048
1049 Ok(display_list)
1050}
1051
1052fn get_containing_block_for_node(
1065 tree: &LayoutTree,
1066 styled_dom: &StyledDom,
1067 node_idx: usize,
1068 calculated_positions: &PositionVec,
1069 viewport: LogicalRect,
1070) -> (LogicalPosition, LogicalSize) {
1071 if let Some(parent_idx) = tree.get(node_idx).and_then(|n| n.parent) {
1072 if let Some(parent_node) = tree.get(parent_idx) {
1073 let pos = calculated_positions
1074 .get(parent_idx)
1075 .copied()
1076 .unwrap_or_default();
1077 let size = parent_node.used_size.unwrap_or_default();
1078 let pbp = parent_node.box_props.unpack();
1081 let content_pos = LogicalPosition::new(
1082 pos.x + pbp.border.left + pbp.padding.left,
1083 pos.y + pbp.border.top + pbp.padding.top,
1084 );
1085
1086 if let Some(dom_id) = parent_node.dom_node_id {
1087 let styled_node_state = &styled_dom
1088 .styled_nodes
1089 .as_container()
1090 .get(dom_id)
1091 .map(|n| &n.styled_node_state)
1092 .cloned()
1093 .unwrap_or_default();
1094 let writing_mode =
1096 get_writing_mode(styled_dom, dom_id, styled_node_state).unwrap_or_default();
1097 let content_size = pbp.inner_size(size, writing_mode);
1098 return (content_pos, content_size);
1099 }
1100
1101 return (content_pos, size);
1102 }
1103 }
1104
1105 (viewport.origin, viewport.size)
1122}
1123
1124#[derive(Debug)]
1125pub enum LayoutError {
1126 InvalidTree,
1127 SizingFailed,
1128 PositioningFailed,
1129 DisplayListFailed,
1130 Text(crate::font_traits::LayoutError),
1131}
1132
1133impl std::fmt::Display for LayoutError {
1134 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1135 match self {
1136 LayoutError::InvalidTree => write!(f, "Invalid layout tree"),
1137 LayoutError::SizingFailed => write!(f, "Sizing calculation failed"),
1138 LayoutError::PositioningFailed => write!(f, "Position calculation failed"),
1139 LayoutError::DisplayListFailed => write!(f, "Display list generation failed"),
1140 LayoutError::Text(e) => write!(f, "Text layout error: {:?}", e),
1141 }
1142 }
1143}
1144
1145impl From<crate::font_traits::LayoutError> for LayoutError {
1146 fn from(err: crate::font_traits::LayoutError) -> Self {
1147 LayoutError::Text(err)
1148 }
1149}
1150
1151impl std::error::Error for LayoutError {}
1152
1153pub type Result<T> = std::result::Result<T, LayoutError>;