1use std::collections::{BTreeMap, HashMap};
14
15use azul_core::{
16 dom::{DomId, NodeId},
17 geom::{LogicalPosition, LogicalRect, LogicalSize},
18 hit_test::ScrollPosition,
19 resources::RendererResources,
20 selection::TextSelection,
21 styled_dom::StyledDom,
22};
23use azul_css::LayoutDebugMessage;
24
25use crate::{
26 font_traits::{ParsedFontTrait, TextLayoutCache},
27 paged::FragmentationContext,
28 solver3::{
29 cache::LayoutCache,
30 display_list::DisplayList,
31 pagination::FakePageConfig,
32 LayoutContext, LayoutError, Result,
33 },
34};
35
36pub struct FragmentationLayoutResult {
40 pub scroll_ids: HashMap<usize, u64>,
41}
42
43#[cfg(feature = "text_layout")]
67pub fn layout_document_paged<T, F>(
68 cache: &mut LayoutCache,
69 text_cache: &mut TextLayoutCache,
70 fragmentation_context: FragmentationContext,
71 new_dom: &StyledDom,
72 viewport: LogicalRect,
73 font_manager: &mut crate::font_traits::FontManager<T>,
74 scroll_offsets: &BTreeMap<NodeId, ScrollPosition>,
75 debug_messages: &mut Option<Vec<LayoutDebugMessage>>,
76 gpu_value_cache: Option<&azul_core::gpu::GpuValueCache>,
77 renderer_resources: &RendererResources,
78 id_namespace: azul_core::resources::IdNamespace,
79 dom_id: DomId,
80 font_loader: F,
81 image_cache: &azul_core::resources::ImageCache,
82 get_system_time_fn: azul_core::task::GetSystemTimeCallback,
83) -> Result<Vec<DisplayList>>
84where
85 T: ParsedFontTrait + Sync + 'static,
86 F: Fn(
87 std::sync::Arc<rust_fontconfig::FontBytes>,
88 usize,
89 ) -> std::result::Result<T, crate::text3::cache::LayoutError>,
90{
91 let page_config = FakePageConfig::new().with_footer_page_numbers();
93
94 layout_document_paged_with_config(
95 cache,
96 text_cache,
97 fragmentation_context,
98 new_dom,
99 viewport,
100 font_manager,
101 scroll_offsets,
102 debug_messages,
103 gpu_value_cache,
104 renderer_resources,
105 id_namespace,
106 dom_id,
107 font_loader,
108 page_config,
109 image_cache,
110 get_system_time_fn,
111 false,
112 )
113}
114
115#[cfg(feature = "text_layout")]
124pub fn layout_document_paged_with_config<T, F>(
125 cache: &mut LayoutCache,
126 text_cache: &mut TextLayoutCache,
127 mut fragmentation_context: FragmentationContext,
128 new_dom: &StyledDom,
129 viewport: LogicalRect,
130 font_manager: &mut crate::font_traits::FontManager<T>,
131 scroll_offsets: &BTreeMap<NodeId, ScrollPosition>,
132 debug_messages: &mut Option<Vec<LayoutDebugMessage>>,
133 gpu_value_cache: Option<&azul_core::gpu::GpuValueCache>,
134 renderer_resources: &RendererResources,
135 id_namespace: azul_core::resources::IdNamespace,
136 dom_id: DomId,
137 font_loader: F,
138 page_config: FakePageConfig,
139 image_cache: &azul_core::resources::ImageCache,
140 get_system_time_fn: azul_core::task::GetSystemTimeCallback,
141 print_timing: bool,
142) -> Result<Vec<DisplayList>>
143where
144 T: ParsedFontTrait + Sync + 'static,
145 F: Fn(
146 std::sync::Arc<rust_fontconfig::FontBytes>,
147 usize,
148 ) -> std::result::Result<T, crate::text3::cache::LayoutError>,
149{
150 {
152 use crate::solver3::getters::{
153 collect_and_resolve_font_chains_with_registration, collect_font_ids_from_chains,
154 compute_fonts_to_load, load_fonts_from_disk,
155 };
156
157 let platform = azul_css::system::Platform::current();
159
160 let chains = collect_and_resolve_font_chains_with_registration(
161 new_dom, &font_manager.fc_cache, font_manager, &platform,
162 );
163
164 let required_fonts = collect_font_ids_from_chains(&chains);
165 let already_loaded = font_manager.get_loaded_font_ids();
166 let fonts_to_load = compute_fonts_to_load(&required_fonts, &already_loaded);
167
168 if !fonts_to_load.is_empty() {
169 let load_result =
170 load_fonts_from_disk(&fonts_to_load, &font_manager.fc_cache, &font_loader);
171
172 font_manager.insert_fonts(load_result.loaded);
173 for (font_id, error) in &load_result.failed {
174 if let Some(msgs) = debug_messages {
175 msgs.push(LayoutDebugMessage::warning(format!(
176 "[FontLoading] Failed to load font {:?}: {}",
177 font_id, error
178 )));
179 }
180 }
181 }
182 font_manager.set_font_chain_cache(chains.into_fontconfig_chains());
183 }
184
185 let page_content_height = fragmentation_context.page_content_height();
187
188 if !fragmentation_context.is_paged() {
190 let _result = compute_layout_with_fragmentation(
191 cache,
192 text_cache,
193 &mut fragmentation_context,
194 new_dom,
195 viewport,
196 font_manager,
197 debug_messages,
198 image_cache,
199 get_system_time_fn,
200 print_timing,
201 )?;
202
203 let tree = cache.tree.as_ref().ok_or(LayoutError::InvalidTree)?;
205 let mut counter_values = cache.counters.clone();
206 let empty_text_selections: BTreeMap<DomId, TextSelection> = BTreeMap::new();
207 let mut ctx = LayoutContext {
208 scrollbar_style_cache: core::cell::RefCell::new(std::collections::HashMap::new()),
209 styled_dom: new_dom,
210 font_manager: &*font_manager,
211 text_selections: &empty_text_selections,
212 debug_messages,
213 counters: &mut counter_values,
214 viewport_size: viewport.size,
215 fragmentation_context: Some(&mut fragmentation_context),
216 cursor_is_visible: true,
217 cursor_locations: Vec::new(),
218 preedit_text: None,
219 dirty_text_overrides: BTreeMap::new(),
220 cache_map: std::mem::take(&mut cache.cache_map),
221 image_cache,
222 system_style: None,
223 get_system_time_fn,
224 };
225
226 use crate::solver3::display_list::generate_display_list;
227 let display_list = generate_display_list(
228 &mut ctx,
229 tree,
230 &cache.calculated_positions,
231 scroll_offsets,
232 &cache.scroll_ids,
233 gpu_value_cache,
234 renderer_resources,
235 id_namespace,
236 dom_id,
237 )?;
238 cache.cache_map = std::mem::take(&mut ctx.cache_map);
239 return Ok(vec![display_list]);
240 }
241
242 let _result = compute_layout_with_fragmentation(
246 cache,
247 text_cache,
248 &mut fragmentation_context,
249 new_dom,
250 viewport,
251 font_manager,
252 debug_messages,
253 image_cache,
254 get_system_time_fn,
255 print_timing,
256 )?;
257
258 let tree = cache.tree.as_ref().ok_or(LayoutError::InvalidTree)?;
260 let calculated_positions = &cache.calculated_positions;
261
262 if let Some(msgs) = debug_messages {
264 msgs.push(LayoutDebugMessage::info(format!(
265 "[PagedLayout] Page content height: {}",
266 page_content_height
267 )));
268 }
269
270 let scroll_ids = &cache.scroll_ids;
272
273 let mut counter_values = cache.counters.clone();
275 let empty_text_selections: BTreeMap<DomId, TextSelection> = BTreeMap::new();
276 let mut ctx = LayoutContext {
277 scrollbar_style_cache: core::cell::RefCell::new(std::collections::HashMap::new()),
278 styled_dom: new_dom,
279 font_manager: &*font_manager,
280 text_selections: &empty_text_selections,
281 debug_messages,
282 counters: &mut counter_values,
283 viewport_size: viewport.size,
284 fragmentation_context: Some(&mut fragmentation_context),
285 cursor_is_visible: true, cursor_locations: Vec::new(), preedit_text: None,
288 dirty_text_overrides: BTreeMap::new(),
289 cache_map: std::mem::take(&mut cache.cache_map),
290 image_cache,
291 system_style: None,
292 get_system_time_fn,
293 };
294
295 use crate::solver3::display_list::{
312 generate_display_list, paginate_display_list_with_slicer_and_breaks,
313 SlicerConfig,
314 };
315
316 let full_display_list = generate_display_list(
318 &mut ctx,
319 tree,
320 calculated_positions,
321 scroll_offsets,
322 scroll_ids,
323 gpu_value_cache,
324 renderer_resources,
325 id_namespace,
326 dom_id,
327 )?;
328
329 if let Some(msgs) = ctx.debug_messages {
330 msgs.push(LayoutDebugMessage::info(format!(
331 "[PagedLayout] Generated master display list with {} items",
332 full_display_list.items.len()
333 )));
334 }
335
336 let page_width = viewport.size.width;
338 let header_footer = page_config.to_header_footer_config();
339
340 if let Some(msgs) = ctx.debug_messages {
341 msgs.push(LayoutDebugMessage::info(format!(
342 "[PagedLayout] Page config: header={}, footer={}, skip_first={}",
343 header_footer.show_header, header_footer.show_footer, header_footer.skip_first_page
344 )));
345 }
346
347 let slicer_config = SlicerConfig {
348 page_content_height,
349 page_gap: 0.0,
350 allow_clipping: true,
351 header_footer,
352 page_width,
353 table_headers: Default::default(),
354 };
355
356 let pages = paginate_display_list_with_slicer_and_breaks(
358 full_display_list,
359 &slicer_config,
360 )?;
361
362 if let Some(msgs) = ctx.debug_messages {
363 msgs.push(LayoutDebugMessage::info(format!(
364 "[PagedLayout] Paginated into {} pages with CSS break support",
365 pages.len()
366 )));
367 }
368
369 cache.cache_map = std::mem::take(&mut ctx.cache_map);
370
371 Ok(pages)
372}
373
374#[cfg(feature = "text_layout")]
380fn compute_layout_with_fragmentation<T: ParsedFontTrait + Sync + 'static>(
381 cache: &mut LayoutCache,
382 text_cache: &mut TextLayoutCache,
383 fragmentation_context: &mut FragmentationContext,
384 new_dom: &StyledDom,
385 viewport: LogicalRect,
386 font_manager: &crate::font_traits::FontManager<T>,
387 debug_messages: &mut Option<Vec<LayoutDebugMessage>>,
388 image_cache: &azul_core::resources::ImageCache,
389 get_system_time_fn: azul_core::task::GetSystemTimeCallback,
390 _print_timing: bool,
391) -> Result<FragmentationLayoutResult> {
392 use crate::solver3::cache;
393
394 let mut counter_values = HashMap::new();
396 let empty_text_selections: BTreeMap<DomId, TextSelection> = BTreeMap::new();
397 let mut ctx_temp = LayoutContext {
398 scrollbar_style_cache: core::cell::RefCell::new(std::collections::HashMap::new()),
399 styled_dom: new_dom,
400 font_manager,
401 text_selections: &empty_text_selections,
402 debug_messages,
403 counters: &mut counter_values,
404 viewport_size: viewport.size,
405 fragmentation_context: Some(fragmentation_context),
406 cursor_is_visible: true, cursor_locations: Vec::new(), preedit_text: None,
409 dirty_text_overrides: BTreeMap::new(),
410 cache_map: cache::LayoutCacheMap::default(),
411 image_cache,
412 system_style: None,
413 get_system_time_fn,
414 };
415
416 let is_fresh_dom = cache.tree.is_none();
418 let (mut new_tree, mut recon_result) = if is_fresh_dom {
419 use crate::solver3::layout_tree::generate_layout_tree;
421 let new_tree = generate_layout_tree(&mut ctx_temp)?;
422 let n = new_tree.nodes.len();
423 let mut result = cache::ReconciliationResult::default();
424 result.layout_roots.insert(new_tree.root);
425 result.intrinsic_dirty = (0..n).collect::<std::collections::BTreeSet<_>>();
426 (new_tree, result)
427 } else {
428 cache::reconcile_and_invalidate(&mut ctx_temp, cache, viewport)?
430 };
431
432 for &node_idx in &recon_result.intrinsic_dirty {
434 if let Some(warm) = new_tree.warm_mut(node_idx) {
435 warm.taffy_cache.clear();
436 }
437 }
438
439 cache::compute_counters(new_dom, &new_tree, &mut counter_values);
441
442 let mut cache_map = std::mem::take(&mut cache.cache_map);
445 cache_map.resize_to_tree(new_tree.nodes.len());
446 for &node_idx in &recon_result.intrinsic_dirty {
447 cache_map.mark_dirty(node_idx, &new_tree.nodes);
448 }
449 for &node_idx in &recon_result.layout_roots {
450 cache_map.mark_dirty(node_idx, &new_tree.nodes);
451 }
452
453 let mut ctx = LayoutContext {
455 scrollbar_style_cache: core::cell::RefCell::new(std::collections::HashMap::new()),
456 styled_dom: new_dom,
457 font_manager,
458 text_selections: &empty_text_selections,
459 debug_messages,
460 counters: &mut counter_values,
461 viewport_size: viewport.size,
462 fragmentation_context: Some(fragmentation_context),
463 cursor_is_visible: true, cursor_locations: Vec::new(), preedit_text: None,
466 dirty_text_overrides: BTreeMap::new(),
467 cache_map,
468 image_cache,
469 system_style: None,
470 get_system_time_fn,
471 };
472
473 if recon_result.is_clean() {
475 ctx.debug_log("No changes, layout cache is clean");
476 let tree = cache.tree.as_ref().ok_or(LayoutError::InvalidTree)?;
477
478 use crate::window::LayoutWindow;
479 let (scroll_ids, scroll_id_to_node_id) = LayoutWindow::compute_scroll_ids(tree, new_dom);
480 cache.scroll_ids = scroll_ids.clone();
481 cache.scroll_id_to_node_id = scroll_id_to_node_id;
482
483 return Ok(FragmentationLayoutResult {
484 scroll_ids,
485 });
486 }
487
488 let mut calculated_positions = cache.calculated_positions.clone();
490 let mut loop_count = 0;
491 loop {
492 loop_count += 1;
493 if loop_count > 10 {
494 break;
495 }
496
497 calculated_positions = cache.calculated_positions.clone();
498 let mut reflow_needed_for_scrollbars = false;
499
500 crate::solver3::sizing::calculate_intrinsic_sizes(
501 &mut ctx,
502 &mut new_tree,
503 text_cache,
504 &recon_result.intrinsic_dirty,
505 )?;
506
507 for &root_idx in &recon_result.layout_roots {
508 let (cb_pos, cb_size) = get_containing_block_for_node(
509 &new_tree,
510 new_dom,
511 root_idx,
512 &calculated_positions,
513 viewport,
514 );
515
516 let root_node = &new_tree.nodes[root_idx];
520 let root_bp = root_node.box_props.unpack();
521 let is_root_with_margin = root_node.parent.is_none()
522 && (root_bp.margin.left != 0.0 || root_bp.margin.top != 0.0);
523
524 let adjusted_cb_pos = if is_root_with_margin {
525 LogicalPosition::new(
526 cb_pos.x + root_bp.margin.left,
527 cb_pos.y + root_bp.margin.top,
528 )
529 } else {
530 cb_pos
531 };
532
533 cache::calculate_layout_for_subtree(
534 &mut ctx,
535 &mut new_tree,
536 text_cache,
537 root_idx,
538 adjusted_cb_pos,
539 cb_size,
540 &mut calculated_positions,
541 &mut reflow_needed_for_scrollbars,
542 &mut cache.float_cache,
543 cache::ComputeMode::PerformLayout,
544 )?;
545
546 if !super::pos_contains(&calculated_positions, root_idx) {
550 let root_position = if is_root_with_margin {
551 adjusted_cb_pos
552 } else {
553 cb_pos
554 };
555 super::pos_set(&mut calculated_positions, root_idx, root_position);
556 }
557 }
558
559 cache::reposition_clean_subtrees(
560 new_dom,
561 &new_tree,
562 &recon_result.layout_roots,
563 &mut calculated_positions,
564 );
565
566 if reflow_needed_for_scrollbars {
567 ctx.debug_log("Scrollbars changed container size, starting full reflow...");
568 recon_result.layout_roots.clear();
569 recon_result.layout_roots.insert(new_tree.root);
570 recon_result.intrinsic_dirty = (0..new_tree.nodes.len()).collect();
571 continue;
572 }
573
574 break;
575 }
576
577 crate::solver3::positioning::adjust_relative_positions(
579 &mut ctx,
580 &new_tree,
581 &mut calculated_positions,
582 viewport,
583 )?;
584
585 crate::solver3::positioning::position_out_of_flow_elements(
586 &mut ctx,
587 &mut new_tree,
588 &mut calculated_positions,
589 viewport,
590 )?;
591
592 use crate::window::LayoutWindow;
594 let (scroll_ids, scroll_id_to_node_id) = LayoutWindow::compute_scroll_ids(&new_tree, new_dom);
595
596 let cache_map_back = std::mem::take(&mut ctx.cache_map);
598
599 cache.tree = Some(new_tree);
600 cache.previous_positions = std::mem::replace(&mut cache.calculated_positions, calculated_positions);
601 cache.viewport = Some(viewport);
602 cache.scroll_ids = scroll_ids.clone();
603 cache.scroll_id_to_node_id = scroll_id_to_node_id;
604 cache.counters = counter_values;
605 cache.cache_map = cache_map_back;
606
607 Ok(FragmentationLayoutResult {
608 scroll_ids,
609 })
610}
611
612fn get_containing_block_for_node(
614 tree: &crate::solver3::layout_tree::LayoutTree,
615 styled_dom: &StyledDom,
616 node_idx: usize,
617 calculated_positions: &super::PositionVec,
618 viewport: LogicalRect,
619) -> (LogicalPosition, LogicalSize) {
620 use crate::solver3::getters::get_writing_mode;
621
622 if let Some(parent_idx) = tree.get(node_idx).and_then(|n| n.parent) {
623 if let Some(parent_node) = tree.get(parent_idx) {
624 let pos = calculated_positions
625 .get(parent_idx)
626 .copied()
627 .unwrap_or_default();
628 let size = parent_node.used_size.unwrap_or_default();
629 let pbp = parent_node.box_props.unpack();
630 let content_pos = LogicalPosition::new(
631 pos.x + pbp.border.left + pbp.padding.left,
632 pos.y + pbp.border.top + pbp.padding.top,
633 );
634
635 if let Some(dom_id) = parent_node.dom_node_id {
636 let styled_node_state = &styled_dom
637 .styled_nodes
638 .as_container()
639 .get(dom_id)
640 .map(|n| &n.styled_node_state)
641 .cloned()
642 .unwrap_or_default();
643 let writing_mode =
644 get_writing_mode(styled_dom, dom_id, styled_node_state).unwrap_or_default();
645 let content_size = pbp.inner_size(size, writing_mode);
646 return (content_pos, content_size);
647 }
648
649 return (content_pos, size);
650 }
651 }
652
653 (viewport.origin, viewport.size)
656}