use std::collections::BTreeMap;
use azul_core::{
dom::{DomId, NodeId},
geom::{LogicalPosition, LogicalRect, LogicalSize},
hit_test::ScrollPosition,
resources::RendererResources,
selection::{SelectionState, TextSelection},
styled_dom::StyledDom,
};
use azul_css::LayoutDebugMessage;
use crate::{
font_traits::{ParsedFontTrait, TextLayoutCache},
fragmentation::PageMargins,
paged::FragmentationContext,
solver3::{
cache::LayoutCache,
display_list::DisplayList,
getters::{get_break_after, get_break_before, get_break_inside},
pagination::FakePageConfig,
LayoutContext, LayoutError, Result,
},
};
pub struct FragmentationLayoutResult {
pub scroll_ids: BTreeMap<usize, u64>,
}
#[cfg(feature = "text_layout")]
pub fn layout_document_paged<T, F>(
cache: &mut LayoutCache,
text_cache: &mut TextLayoutCache,
fragmentation_context: FragmentationContext,
new_dom: &StyledDom,
viewport: LogicalRect,
font_manager: &mut crate::font_traits::FontManager<T>,
scroll_offsets: &BTreeMap<NodeId, ScrollPosition>,
selections: &BTreeMap<DomId, SelectionState>,
debug_messages: &mut Option<Vec<LayoutDebugMessage>>,
gpu_value_cache: Option<&azul_core::gpu::GpuValueCache>,
renderer_resources: &RendererResources,
id_namespace: azul_core::resources::IdNamespace,
dom_id: DomId,
font_loader: F,
get_system_time_fn: azul_core::task::GetSystemTimeCallback,
) -> Result<Vec<DisplayList>>
where
T: ParsedFontTrait + Sync + 'static,
F: Fn(&[u8], usize) -> std::result::Result<T, crate::text3::cache::LayoutError>,
{
let page_config = FakePageConfig::new().with_footer_page_numbers();
layout_document_paged_with_config(
cache,
text_cache,
fragmentation_context,
new_dom,
viewport,
font_manager,
scroll_offsets,
selections,
debug_messages,
gpu_value_cache,
renderer_resources,
id_namespace,
dom_id,
font_loader,
page_config,
get_system_time_fn,
)
}
#[cfg(feature = "text_layout")]
pub fn layout_document_paged_with_config<T, F>(
cache: &mut LayoutCache,
text_cache: &mut TextLayoutCache,
mut fragmentation_context: FragmentationContext,
new_dom: &StyledDom,
viewport: LogicalRect,
font_manager: &mut crate::font_traits::FontManager<T>,
scroll_offsets: &BTreeMap<NodeId, ScrollPosition>,
selections: &BTreeMap<DomId, SelectionState>,
debug_messages: &mut Option<Vec<LayoutDebugMessage>>,
gpu_value_cache: Option<&azul_core::gpu::GpuValueCache>,
renderer_resources: &RendererResources,
id_namespace: azul_core::resources::IdNamespace,
dom_id: DomId,
font_loader: F,
page_config: FakePageConfig,
get_system_time_fn: azul_core::task::GetSystemTimeCallback,
) -> Result<Vec<DisplayList>>
where
T: ParsedFontTrait + Sync + 'static,
F: Fn(&[u8], usize) -> std::result::Result<T, crate::text3::cache::LayoutError>,
{
{
use crate::solver3::getters::{
collect_and_resolve_font_chains, collect_font_ids_from_chains, compute_fonts_to_load,
load_fonts_from_disk, register_embedded_fonts_from_styled_dom,
};
let platform = azul_css::system::Platform::current();
register_embedded_fonts_from_styled_dom(new_dom, font_manager, &platform);
let chains = collect_and_resolve_font_chains(new_dom, &font_manager.fc_cache, &platform);
let required_fonts = collect_font_ids_from_chains(&chains);
let already_loaded = font_manager.get_loaded_font_ids();
let fonts_to_load = compute_fonts_to_load(&required_fonts, &already_loaded);
if !fonts_to_load.is_empty() {
let load_result =
load_fonts_from_disk(&fonts_to_load, &font_manager.fc_cache, &font_loader);
font_manager.insert_fonts(load_result.loaded);
for (font_id, error) in &load_result.failed {
if let Some(msgs) = debug_messages {
msgs.push(LayoutDebugMessage::warning(format!(
"[FontLoading] Failed to load font {:?}: {}",
font_id, error
)));
}
}
}
font_manager.set_font_chain_cache(chains.into_fontconfig_chains());
}
let page_content_height = fragmentation_context.page_content_height();
if !fragmentation_context.is_paged() {
let _result = compute_layout_with_fragmentation(
cache,
text_cache,
&mut fragmentation_context,
new_dom,
viewport,
font_manager,
selections,
debug_messages,
get_system_time_fn,
)?;
let tree = cache.tree.as_ref().ok_or(LayoutError::InvalidTree)?;
let mut counter_values = cache.counters.clone();
let empty_text_selections: BTreeMap<DomId, TextSelection> = BTreeMap::new();
let mut ctx = LayoutContext {
styled_dom: new_dom,
font_manager: &*font_manager,
selections,
text_selections: &empty_text_selections,
debug_messages,
counters: &mut counter_values,
viewport_size: viewport.size,
fragmentation_context: Some(&mut fragmentation_context),
cursor_is_visible: true,
cursor_location: None,
cache_map: std::mem::take(&mut cache.cache_map),
system_style: None,
get_system_time_fn,
};
use crate::solver3::display_list::generate_display_list;
let display_list = generate_display_list(
&mut ctx,
tree,
&cache.calculated_positions,
scroll_offsets,
&cache.scroll_ids,
gpu_value_cache,
renderer_resources,
id_namespace,
dom_id,
)?;
cache.cache_map = std::mem::take(&mut ctx.cache_map);
return Ok(vec![display_list]);
}
let _result = compute_layout_with_fragmentation(
cache,
text_cache,
&mut fragmentation_context,
new_dom,
viewport,
font_manager,
selections,
debug_messages,
get_system_time_fn,
)?;
let tree = cache.tree.as_ref().ok_or(LayoutError::InvalidTree)?;
let calculated_positions = &cache.calculated_positions;
if let Some(msgs) = debug_messages {
msgs.push(LayoutDebugMessage::info(format!(
"[PagedLayout] Page content height: {}",
page_content_height
)));
}
let scroll_ids = &cache.scroll_ids;
let mut counter_values = cache.counters.clone();
let empty_text_selections: BTreeMap<DomId, TextSelection> = BTreeMap::new();
let mut ctx = LayoutContext {
styled_dom: new_dom,
font_manager: &*font_manager,
selections,
text_selections: &empty_text_selections,
debug_messages,
counters: &mut counter_values,
viewport_size: viewport.size,
fragmentation_context: Some(&mut fragmentation_context),
cursor_is_visible: true, cursor_location: None, cache_map: std::mem::take(&mut cache.cache_map),
system_style: None,
get_system_time_fn,
};
use crate::solver3::display_list::{
generate_display_list, paginate_display_list_with_slicer_and_breaks,
SlicerConfig,
};
let full_display_list = generate_display_list(
&mut ctx,
tree,
calculated_positions,
scroll_offsets,
scroll_ids,
gpu_value_cache,
renderer_resources,
id_namespace,
dom_id,
)?;
if let Some(msgs) = ctx.debug_messages {
msgs.push(LayoutDebugMessage::info(format!(
"[PagedLayout] Generated master display list with {} items",
full_display_list.items.len()
)));
}
let page_width = viewport.size.width;
let header_footer = page_config.to_header_footer_config();
if let Some(msgs) = ctx.debug_messages {
msgs.push(LayoutDebugMessage::info(format!(
"[PagedLayout] Page config: header={}, footer={}, skip_first={}",
header_footer.show_header, header_footer.show_footer, header_footer.skip_first_page
)));
}
let slicer_config = SlicerConfig {
page_content_height,
page_gap: 0.0,
allow_clipping: true,
header_footer,
page_width,
table_headers: Default::default(),
};
let pages = paginate_display_list_with_slicer_and_breaks(
full_display_list,
&slicer_config,
)?;
if let Some(msgs) = ctx.debug_messages {
msgs.push(LayoutDebugMessage::info(format!(
"[PagedLayout] Paginated into {} pages with CSS break support",
pages.len()
)));
}
cache.cache_map = std::mem::take(&mut ctx.cache_map);
Ok(pages)
}
#[cfg(feature = "text_layout")]
fn compute_layout_with_fragmentation<T: ParsedFontTrait + Sync + 'static>(
cache: &mut LayoutCache,
text_cache: &mut TextLayoutCache,
fragmentation_context: &mut FragmentationContext,
new_dom: &StyledDom,
viewport: LogicalRect,
font_manager: &crate::font_traits::FontManager<T>,
selections: &BTreeMap<DomId, SelectionState>,
debug_messages: &mut Option<Vec<LayoutDebugMessage>>,
get_system_time_fn: azul_core::task::GetSystemTimeCallback,
) -> Result<FragmentationLayoutResult> {
use crate::solver3::{
cache, getters::get_writing_mode,
layout_tree::DirtyFlag,
};
let mut counter_values = BTreeMap::new();
let empty_text_selections: BTreeMap<DomId, TextSelection> = BTreeMap::new();
let mut ctx_temp = LayoutContext {
styled_dom: new_dom,
font_manager,
selections,
text_selections: &empty_text_selections,
debug_messages,
counters: &mut counter_values,
viewport_size: viewport.size,
fragmentation_context: Some(fragmentation_context),
cursor_is_visible: true, cursor_location: None, cache_map: cache::LayoutCacheMap::default(),
system_style: None,
get_system_time_fn,
};
let is_fresh_dom = cache.tree.is_none();
let (mut new_tree, mut recon_result) = if is_fresh_dom {
use crate::solver3::layout_tree::generate_layout_tree;
let new_tree = generate_layout_tree(&mut ctx_temp)?;
let n = new_tree.nodes.len();
let mut result = cache::ReconciliationResult::default();
result.layout_roots.insert(new_tree.root);
result.intrinsic_dirty = (0..n).collect::<std::collections::BTreeSet<_>>();
(new_tree, result)
} else {
cache::reconcile_and_invalidate(&mut ctx_temp, cache, viewport)?
};
for &node_idx in &recon_result.intrinsic_dirty {
if let Some(node) = new_tree.get_mut(node_idx) {
node.taffy_cache.clear();
}
}
cache::compute_counters(new_dom, &new_tree, &mut counter_values);
let mut cache_map = std::mem::take(&mut cache.cache_map);
cache_map.resize_to_tree(new_tree.nodes.len());
for &node_idx in &recon_result.intrinsic_dirty {
cache_map.mark_dirty(node_idx, &new_tree.nodes);
}
for &node_idx in &recon_result.layout_roots {
cache_map.mark_dirty(node_idx, &new_tree.nodes);
}
let mut ctx = LayoutContext {
styled_dom: new_dom,
font_manager,
selections,
text_selections: &empty_text_selections,
debug_messages,
counters: &mut counter_values,
viewport_size: viewport.size,
fragmentation_context: Some(fragmentation_context),
cursor_is_visible: true, cursor_location: None, cache_map,
system_style: None,
get_system_time_fn,
};
if recon_result.is_clean() {
ctx.debug_log("No changes, layout cache is clean");
let tree = cache.tree.as_ref().ok_or(LayoutError::InvalidTree)?;
use crate::window::LayoutWindow;
let (scroll_ids, scroll_id_to_node_id) = LayoutWindow::compute_scroll_ids(tree, new_dom);
cache.scroll_ids = scroll_ids.clone();
cache.scroll_id_to_node_id = scroll_id_to_node_id;
return Ok(FragmentationLayoutResult {
scroll_ids,
});
}
let mut calculated_positions = cache.calculated_positions.clone();
let mut loop_count = 0;
loop {
loop_count += 1;
if loop_count > 10 {
break;
}
calculated_positions = cache.calculated_positions.clone();
let mut reflow_needed_for_scrollbars = false;
crate::solver3::sizing::calculate_intrinsic_sizes(
&mut ctx,
&mut new_tree,
&recon_result.intrinsic_dirty,
)?;
for &root_idx in &recon_result.layout_roots {
let (cb_pos, cb_size) = get_containing_block_for_node(
&new_tree,
new_dom,
root_idx,
&calculated_positions,
viewport,
);
let root_node = &new_tree.nodes[root_idx];
let is_root_with_margin = root_node.parent.is_none()
&& (root_node.box_props.margin.left != 0.0 || root_node.box_props.margin.top != 0.0);
let adjusted_cb_pos = if is_root_with_margin {
LogicalPosition::new(
cb_pos.x + root_node.box_props.margin.left,
cb_pos.y + root_node.box_props.margin.top,
)
} else {
cb_pos
};
cache::calculate_layout_for_subtree(
&mut ctx,
&mut new_tree,
text_cache,
root_idx,
adjusted_cb_pos,
cb_size,
&mut calculated_positions,
&mut reflow_needed_for_scrollbars,
&mut cache.float_cache,
cache::ComputeMode::PerformLayout,
)?;
if !super::pos_contains(&calculated_positions, root_idx) {
let root_position = if is_root_with_margin {
adjusted_cb_pos
} else {
cb_pos
};
super::pos_set(&mut calculated_positions, root_idx, root_position);
}
}
cache::reposition_clean_subtrees(
new_dom,
&new_tree,
&recon_result.layout_roots,
&mut calculated_positions,
);
if reflow_needed_for_scrollbars {
ctx.debug_log("Scrollbars changed container size, starting full reflow...");
recon_result.layout_roots.clear();
recon_result.layout_roots.insert(new_tree.root);
recon_result.intrinsic_dirty = (0..new_tree.nodes.len()).collect();
continue;
}
break;
}
crate::solver3::positioning::adjust_relative_positions(
&mut ctx,
&new_tree,
&mut calculated_positions,
viewport,
)?;
crate::solver3::positioning::position_out_of_flow_elements(
&mut ctx,
&mut new_tree,
&mut calculated_positions,
viewport,
)?;
use crate::window::LayoutWindow;
let (scroll_ids, scroll_id_to_node_id) = LayoutWindow::compute_scroll_ids(&new_tree, new_dom);
let cache_map_back = std::mem::take(&mut ctx.cache_map);
cache.tree = Some(new_tree);
cache.calculated_positions = calculated_positions;
cache.viewport = Some(viewport);
cache.scroll_ids = scroll_ids.clone();
cache.scroll_id_to_node_id = scroll_id_to_node_id;
cache.counters = counter_values;
cache.cache_map = cache_map_back;
Ok(FragmentationLayoutResult {
scroll_ids,
})
}
fn get_containing_block_for_node(
tree: &crate::solver3::layout_tree::LayoutTree,
styled_dom: &StyledDom,
node_idx: usize,
calculated_positions: &super::PositionVec,
viewport: LogicalRect,
) -> (LogicalPosition, LogicalSize) {
use crate::solver3::getters::get_writing_mode;
if let Some(parent_idx) = tree.get(node_idx).and_then(|n| n.parent) {
if let Some(parent_node) = tree.get(parent_idx) {
let pos = calculated_positions
.get(parent_idx)
.copied()
.unwrap_or_default();
let size = parent_node.used_size.unwrap_or_default();
let content_pos = LogicalPosition::new(
pos.x + parent_node.box_props.border.left + parent_node.box_props.padding.left,
pos.y + parent_node.box_props.border.top + parent_node.box_props.padding.top,
);
if let Some(dom_id) = parent_node.dom_node_id {
let styled_node_state = &styled_dom
.styled_nodes
.as_container()
.get(dom_id)
.map(|n| &n.styled_node_state)
.cloned()
.unwrap_or_default();
let writing_mode =
get_writing_mode(styled_dom, dom_id, styled_node_state).unwrap_or_default();
let content_size = parent_node.box_props.inner_size(size, writing_mode);
return (content_pos, content_size);
}
return (content_pos, size);
}
}
(viewport.origin, viewport.size)
}