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