use std::{
collections::{BTreeMap, BTreeSet},
sync::Arc,
};
use azul_core::{
dom::{FormattingContext, NodeId, NodeType},
geom::LogicalSize,
resources::RendererResources,
styled_dom::{StyledDom, StyledNodeState},
};
use azul_css::{
css::CssPropertyValue,
props::{
basic::PixelValue,
layout::{LayoutDisplay, LayoutHeight, LayoutPosition, LayoutWidth, LayoutWritingMode},
property::{CssProperty, CssPropertyType},
},
LayoutDebugMessage,
};
use rust_fontconfig::FcFontCache;
#[cfg(feature = "text_layout")]
use crate::text3;
use crate::{
font::parsed::ParsedFont,
font_traits::{
AvailableSpace, FontLoaderTrait, FontManager, ImageSource, InlineContent, InlineImage,
InlineShape, LayoutCache, LayoutFragment, ObjectFit, ParsedFontTrait, ShapeDefinition,
StyleProperties, UnifiedConstraints,
},
solver3::{
fc::split_text_for_whitespace,
geometry::{BoxProps, BoxSizing, IntrinsicSizes},
getters::{
get_css_box_sizing, get_css_height, get_css_width, get_display_property,
get_style_properties, get_writing_mode, MultiValue,
},
layout_tree::{AnonymousBoxType, LayoutNode, LayoutTree, get_display_type},
positioning::get_position_type,
LayoutContext, LayoutError, Result,
},
};
pub fn resolve_percentage_with_box_model(
containing_block_dimension: f32,
percentage: f32,
_margins: (f32, f32),
_borders: (f32, f32),
_paddings: (f32, f32),
) -> f32 {
(containing_block_dimension * percentage).max(0.0)
}
pub fn calculate_intrinsic_sizes<T: ParsedFontTrait>(
ctx: &mut LayoutContext<'_, T>,
tree: &mut LayoutTree,
dirty_nodes: &BTreeSet<usize>,
) -> Result<()> {
if dirty_nodes.is_empty() {
return Ok(());
}
ctx.debug_log("Starting intrinsic size calculation");
let mut calculator = IntrinsicSizeCalculator::new(ctx);
calculator.calculate_intrinsic_recursive(tree, tree.root)?;
ctx.debug_log("Finished intrinsic size calculation");
Ok(())
}
struct IntrinsicSizeCalculator<'a, 'b, T: ParsedFontTrait> {
ctx: &'a mut LayoutContext<'b, T>,
text_cache: LayoutCache,
}
impl<'a, 'b, T: ParsedFontTrait> IntrinsicSizeCalculator<'a, 'b, T> {
fn new(ctx: &'a mut LayoutContext<'b, T>) -> Self {
Self {
ctx,
text_cache: LayoutCache::new(),
}
}
fn calculate_intrinsic_recursive(
&mut self,
tree: &mut LayoutTree,
node_index: usize,
) -> Result<IntrinsicSizes> {
static COUNTER: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
let count = COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
if count % 50 == 0 {}
let node = tree
.get(node_index)
.cloned()
.ok_or(LayoutError::InvalidTree)?;
let position = get_position_type(self.ctx.styled_dom, node.dom_node_id);
if position == LayoutPosition::Absolute || position == LayoutPosition::Fixed {
if let Some(n) = tree.get_mut(node_index) {
n.intrinsic_sizes = Some(IntrinsicSizes::default());
}
return Ok(IntrinsicSizes::default());
}
let mut child_intrinsics = BTreeMap::new();
for &child_index in &node.children {
let child_intrinsic = self.calculate_intrinsic_recursive(tree, child_index)?;
child_intrinsics.insert(child_index, child_intrinsic);
}
let intrinsic = self.calculate_node_intrinsic_sizes(tree, node_index, &child_intrinsics)?;
if let Some(n) = tree.get_mut(node_index) {
n.intrinsic_sizes = Some(intrinsic);
}
Ok(intrinsic)
}
fn calculate_node_intrinsic_sizes(
&mut self,
tree: &LayoutTree,
node_index: usize,
child_intrinsics: &BTreeMap<usize, IntrinsicSizes>,
) -> Result<IntrinsicSizes> {
let node = tree.get(node_index).ok_or(LayoutError::InvalidTree)?;
if let Some(dom_id) = node.dom_node_id {
let node_data = &self.ctx.styled_dom.node_data.as_container()[dom_id];
if node_data.is_iframe_node() {
return Ok(IntrinsicSizes {
min_content_width: 300.0,
max_content_width: 300.0,
preferred_width: None, min_content_height: 150.0,
max_content_height: 150.0,
preferred_height: None, });
}
if let NodeType::Image(image_ref) = node_data.get_node_type() {
let size = image_ref.get_size();
let width = if size.width > 0.0 { size.width } else { 100.0 };
let height = if size.height > 0.0 { size.height } else { 100.0 };
return Ok(IntrinsicSizes {
min_content_width: width,
max_content_width: width,
preferred_width: Some(width),
min_content_height: height,
max_content_height: height,
preferred_height: Some(height),
});
}
}
match node.formatting_context {
FormattingContext::Block { .. } => {
let has_block_child = node.children.iter().any(|&child_idx| {
tree.get(child_idx)
.and_then(|c| c.dom_node_id)
.map(|dom_id| {
let node_data = &self.ctx.styled_dom.node_data.as_container()[dom_id];
if matches!(node_data.get_node_type(), NodeType::Text(_)) {
return false;
}
let display = get_display_type(self.ctx.styled_dom, dom_id);
matches!(display,
LayoutDisplay::Block
| LayoutDisplay::Flex
| LayoutDisplay::Grid
| LayoutDisplay::Table
| LayoutDisplay::ListItem
| LayoutDisplay::FlowRoot
)
})
.unwrap_or(false)
});
let has_inline_child = node.children.iter().any(|&child_idx| {
tree.get(child_idx)
.and_then(|c| c.dom_node_id)
.map(|dom_id| {
let node_data = &self.ctx.styled_dom.node_data.as_container()[dom_id];
if matches!(node_data.get_node_type(), NodeType::Text(_)) {
return true;
}
let display = get_display_type(self.ctx.styled_dom, dom_id);
matches!(display,
LayoutDisplay::Inline
| LayoutDisplay::InlineBlock
| LayoutDisplay::InlineFlex
| LayoutDisplay::InlineGrid
| LayoutDisplay::InlineTable
)
})
.unwrap_or(false)
});
let is_ifc_root = has_inline_child && !has_block_child;
let has_direct_text = if !has_block_child {
if let Some(dom_id) = node.dom_node_id {
let node_hierarchy = &self.ctx.styled_dom.node_hierarchy.as_container();
dom_id.az_children(node_hierarchy).any(|child_id| {
let child_node_data = &self.ctx.styled_dom.node_data.as_container()[child_id];
matches!(child_node_data.get_node_type(), NodeType::Text(_))
})
} else {
false
}
} else {
false
};
if is_ifc_root || has_direct_text {
self.calculate_ifc_root_intrinsic_sizes(tree, node_index)
} else {
self.calculate_block_intrinsic_sizes(tree, node_index, child_intrinsics)
}
}
FormattingContext::Inline => {
let is_text_node = if let Some(dom_id) = node.dom_node_id {
let node_data = &self.ctx.styled_dom.node_data.as_container()[dom_id];
matches!(node_data.get_node_type(), NodeType::Text(_))
} else {
false
};
let has_direct_text_children = if let Some(dom_id) = node.dom_node_id {
let node_hierarchy = &self.ctx.styled_dom.node_hierarchy.as_container();
dom_id.az_children(node_hierarchy).any(|child_id| {
let child_node_data = &self.ctx.styled_dom.node_data.as_container()[child_id];
matches!(child_node_data.get_node_type(), NodeType::Text(_))
})
} else {
false
};
if is_text_node || has_direct_text_children {
self.calculate_ifc_root_intrinsic_sizes(tree, node_index)
} else {
Ok(IntrinsicSizes::default())
}
}
FormattingContext::InlineBlock => {
let has_inline_children = node.children.iter().any(|&child_idx| {
tree.get(child_idx)
.map(|c| matches!(c.formatting_context, FormattingContext::Inline))
.unwrap_or(false)
});
if has_inline_children {
let mut intrinsic = self.calculate_ifc_root_intrinsic_sizes(tree, node_index)?;
let h_extras = node.box_props.padding.left + node.box_props.padding.right
+ node.box_props.border.left + node.box_props.border.right;
let v_extras = node.box_props.padding.top + node.box_props.padding.bottom
+ node.box_props.border.top + node.box_props.border.bottom;
intrinsic.min_content_width += h_extras;
intrinsic.max_content_width += h_extras;
intrinsic.min_content_height += v_extras;
intrinsic.max_content_height += v_extras;
Ok(intrinsic)
} else {
self.calculate_block_intrinsic_sizes(tree, node_index, child_intrinsics)
}
}
FormattingContext::Table => {
self.calculate_table_intrinsic_sizes(tree, node_index, child_intrinsics)
}
_ => self.calculate_block_intrinsic_sizes(tree, node_index, child_intrinsics),
}
}
fn calculate_ifc_root_intrinsic_sizes(
&mut self,
tree: &LayoutTree,
node_index: usize,
) -> Result<IntrinsicSizes> {
let inline_content = collect_inline_content(&mut self.ctx, tree, node_index)?;
if inline_content.is_empty() {
return Ok(IntrinsicSizes::default());
}
let loaded_fonts = self.ctx.font_manager.get_loaded_fonts();
let min_fragments = vec![LayoutFragment {
id: "min".to_string(),
constraints: UnifiedConstraints {
available_width: AvailableSpace::MinContent,
..Default::default()
},
}];
let min_layout = match self.text_cache.layout_flow(
&inline_content,
&[],
&min_fragments,
&self.ctx.font_manager.font_chain_cache,
&self.ctx.font_manager.fc_cache,
&loaded_fonts,
self.ctx.debug_messages,
) {
Ok(layout) => layout,
Err(_) => {
return Ok(IntrinsicSizes {
min_content_width: 100.0,
max_content_width: 300.0,
preferred_width: None,
min_content_height: 20.0,
max_content_height: 20.0,
preferred_height: None,
});
}
};
let max_fragments = vec![LayoutFragment {
id: "max".to_string(),
constraints: UnifiedConstraints {
available_width: AvailableSpace::MaxContent,
..Default::default()
},
}];
let max_layout = match self.text_cache.layout_flow(
&inline_content,
&[],
&max_fragments,
&self.ctx.font_manager.font_chain_cache,
&self.ctx.font_manager.fc_cache,
&loaded_fonts,
self.ctx.debug_messages,
) {
Ok(layout) => layout,
Err(_) => min_layout.clone(),
};
let min_width = min_layout
.fragment_layouts
.get("min")
.map(|l| l.bounds().width)
.unwrap_or(0.0);
let max_width = max_layout
.fragment_layouts
.get("max")
.map(|l| l.bounds().width)
.unwrap_or(0.0);
let min_content_height = min_layout
.fragment_layouts
.get("min")
.map(|l| l.bounds().height)
.unwrap_or(0.0);
let max_content_height = max_layout
.fragment_layouts
.get("max")
.map(|l| l.bounds().height)
.unwrap_or(0.0);
Ok(IntrinsicSizes {
min_content_width: min_width,
max_content_width: max_width,
preferred_width: None,
min_content_height,
max_content_height,
preferred_height: None,
})
}
fn calculate_block_intrinsic_sizes(
&mut self,
tree: &LayoutTree,
node_index: usize,
child_intrinsics: &BTreeMap<usize, IntrinsicSizes>,
) -> Result<IntrinsicSizes> {
let node = tree.get(node_index).ok_or(LayoutError::InvalidTree)?;
let writing_mode = if let Some(dom_id) = node.dom_node_id {
let node_state =
&self.ctx.styled_dom.styled_nodes.as_container()[dom_id].styled_node_state;
get_writing_mode(self.ctx.styled_dom, dom_id, node_state).unwrap_or_default()
} else {
LayoutWritingMode::default()
};
let mut max_child_min_cross = 0.0f32;
let mut max_child_max_cross = 0.0f32;
let mut total_main_size = 0.0;
for &child_index in &node.children {
if let Some(child_intrinsic) = child_intrinsics.get(&child_index) {
let (child_min_cross, child_max_cross, child_main_size) = match writing_mode {
LayoutWritingMode::HorizontalTb => (
child_intrinsic.min_content_width,
child_intrinsic.max_content_width,
child_intrinsic.max_content_height,
),
_ => (
child_intrinsic.min_content_height,
child_intrinsic.max_content_height,
child_intrinsic.max_content_width,
),
};
max_child_min_cross = max_child_min_cross.max(child_min_cross);
max_child_max_cross = max_child_max_cross.max(child_max_cross);
total_main_size += child_main_size;
}
}
let (min_width, max_width, min_height, max_height) = match writing_mode {
LayoutWritingMode::HorizontalTb => (
max_child_min_cross,
max_child_max_cross,
total_main_size,
total_main_size,
),
_ => (
total_main_size,
total_main_size,
max_child_min_cross,
max_child_max_cross,
),
};
Ok(IntrinsicSizes {
min_content_width: min_width,
max_content_width: max_width,
preferred_width: None,
min_content_height: min_height,
max_content_height: max_height,
preferred_height: None,
})
}
fn calculate_inline_intrinsic_sizes(
&mut self,
tree: &LayoutTree,
node_index: usize,
) -> Result<IntrinsicSizes> {
self.ctx.debug_log(&format!(
"Calculating inline intrinsic sizes for node {}",
node_index
));
let inline_content = collect_inline_content(&mut self.ctx, tree, node_index)?;
if inline_content.is_empty() {
self.ctx
.debug_log("No inline content found, returning default sizes");
return Ok(IntrinsicSizes::default());
}
self.ctx.debug_log(&format!(
"Found {} inline content items",
inline_content.len()
));
let min_fragments = vec![LayoutFragment {
id: "min".to_string(),
constraints: UnifiedConstraints {
available_width: AvailableSpace::MinContent,
..Default::default()
},
}];
let loaded_fonts = self.ctx.font_manager.get_loaded_fonts();
let min_layout = match self.text_cache.layout_flow(
&inline_content,
&[],
&min_fragments,
&self.ctx.font_manager.font_chain_cache,
&self.ctx.font_manager.fc_cache,
&loaded_fonts,
self.ctx.debug_messages,
) {
Ok(layout) => layout,
Err(e) => {
self.ctx.debug_log(&format!(
"Warning: Sizing failed during min-content layout: {:?}",
e
));
self.ctx
.debug_log("Using fallback: returning default intrinsic sizes");
return Ok(IntrinsicSizes {
min_content_width: 100.0, max_content_width: 300.0,
preferred_width: None,
min_content_height: 20.0, max_content_height: 20.0,
preferred_height: None,
});
}
};
let max_fragments = vec![LayoutFragment {
id: "max".to_string(),
constraints: UnifiedConstraints {
available_width: AvailableSpace::MaxContent,
..Default::default()
},
}];
let max_layout = match self.text_cache.layout_flow(
&inline_content,
&[],
&max_fragments,
&self.ctx.font_manager.font_chain_cache,
&self.ctx.font_manager.fc_cache,
&loaded_fonts,
self.ctx.debug_messages,
) {
Ok(layout) => layout,
Err(e) => {
self.ctx.debug_log(&format!(
"Warning: Sizing failed during max-content layout: {:?}",
e
));
self.ctx.debug_log("Using fallback from min-content layout");
min_layout.clone()
}
};
let min_width = min_layout
.fragment_layouts
.get("min")
.map(|l| l.bounds().width)
.unwrap_or(0.0);
let max_width = max_layout
.fragment_layouts
.get("max")
.map(|l| l.bounds().width)
.unwrap_or(0.0);
let height = max_layout
.fragment_layouts
.get("max")
.map(|l| l.bounds().height)
.unwrap_or(0.0);
Ok(IntrinsicSizes {
min_content_width: min_width,
max_content_width: max_width,
preferred_width: None, min_content_height: height, max_content_height: height,
preferred_height: None,
})
}
fn calculate_table_intrinsic_sizes(
&self,
_tree: &LayoutTree,
_node_index: usize,
_child_intrinsics: &BTreeMap<usize, IntrinsicSizes>,
) -> Result<IntrinsicSizes> {
Ok(IntrinsicSizes::default())
}
}
fn collect_inline_content_for_sizing<T: ParsedFontTrait>(
ctx: &mut LayoutContext<'_, T>,
tree: &LayoutTree,
ifc_root_index: usize,
) -> Result<Vec<InlineContent>> {
ctx.debug_log(&format!(
"Collecting inline content from node {} for intrinsic sizing",
ifc_root_index
));
let mut content = Vec::new();
collect_inline_content_recursive(ctx, tree, ifc_root_index, &mut content)?;
ctx.debug_log(&format!(
"Collected {} inline content items from node {}",
content.len(),
ifc_root_index
));
Ok(content)
}
fn collect_inline_content_recursive<T: ParsedFontTrait>(
ctx: &mut LayoutContext<'_, T>,
tree: &LayoutTree,
node_index: usize,
content: &mut Vec<InlineContent>,
) -> Result<()> {
let node = tree.get(node_index).ok_or(LayoutError::InvalidTree)?;
let Some(dom_id) = node.dom_node_id else {
return process_layout_children(ctx, tree, node, content);
};
if let Some(text) = extract_text_from_node(ctx.styled_dom, dom_id) {
let style_props = Arc::new(get_style_properties(ctx.styled_dom, dom_id, ctx.system_style.as_ref()));
ctx.debug_log(&format!("Found text in node {}: '{}'", node_index, text));
let text_items = split_text_for_whitespace(
ctx.styled_dom,
dom_id,
&text,
style_props,
);
content.extend(text_items);
}
let node_hierarchy = &ctx.styled_dom.node_hierarchy.as_container();
for child_id in dom_id.az_children(node_hierarchy) {
if tree.dom_to_layout.contains_key(&child_id) {
continue;
}
let child_dom_node = &ctx.styled_dom.node_data.as_container()[child_id];
if let NodeType::Text(text_data) = child_dom_node.get_node_type() {
let text = text_data.as_str().to_string();
let style_props = Arc::new(get_style_properties(ctx.styled_dom, child_id, ctx.system_style.as_ref()));
ctx.debug_log(&format!(
"Found text in DOM child of node {}: '{}'",
node_index, text
));
let text_items = split_text_for_whitespace(
ctx.styled_dom,
child_id,
&text,
style_props,
);
content.extend(text_items);
}
}
process_layout_children(ctx, tree, node, content)
}
fn process_layout_children<T: ParsedFontTrait>(
ctx: &mut LayoutContext<'_, T>,
tree: &LayoutTree,
node: &LayoutNode,
content: &mut Vec<InlineContent>,
) -> Result<()> {
use azul_css::props::basic::SizeMetric;
use azul_css::props::layout::{LayoutHeight, LayoutWidth};
for &child_index in &node.children {
let child_node = tree.get(child_index).ok_or(LayoutError::InvalidTree)?;
let Some(child_dom_id) = child_node.dom_node_id else {
continue;
};
let display = get_display_property(ctx.styled_dom, Some(child_dom_id));
if display.unwrap_or_default() == LayoutDisplay::Inline {
ctx.debug_log(&format!(
"Recursing into inline child at node {}",
child_index
));
collect_inline_content_recursive(ctx, tree, child_index, content)?;
} else {
let intrinsic_sizes = child_node.intrinsic_sizes.unwrap_or_default();
let node_state =
&ctx.styled_dom.styled_nodes.as_container()[child_dom_id].styled_node_state;
let css_width = get_css_width(ctx.styled_dom, child_dom_id, node_state);
let css_height = get_css_height(ctx.styled_dom, child_dom_id, node_state);
let used_width = match css_width {
MultiValue::Exact(LayoutWidth::Px(px)) => {
use azul_css::props::basic::pixel::{DEFAULT_FONT_SIZE, PT_TO_PX};
match px.metric {
SizeMetric::Px => px.number.get(),
SizeMetric::Pt => px.number.get() * PT_TO_PX,
SizeMetric::In => px.number.get() * 96.0,
SizeMetric::Cm => px.number.get() * 96.0 / 2.54,
SizeMetric::Mm => px.number.get() * 96.0 / 25.4,
SizeMetric::Em | SizeMetric::Rem => px.number.get() * DEFAULT_FONT_SIZE,
_ => intrinsic_sizes.max_content_width,
}
}
MultiValue::Exact(LayoutWidth::MinContent) => intrinsic_sizes.min_content_width,
MultiValue::Exact(LayoutWidth::MaxContent) => intrinsic_sizes.max_content_width,
_ => intrinsic_sizes.max_content_width,
};
let used_height = match css_height {
MultiValue::Exact(LayoutHeight::Px(px)) => {
use azul_css::props::basic::pixel::{DEFAULT_FONT_SIZE, PT_TO_PX};
match px.metric {
SizeMetric::Px => px.number.get(),
SizeMetric::Pt => px.number.get() * PT_TO_PX,
SizeMetric::In => px.number.get() * 96.0,
SizeMetric::Cm => px.number.get() * 96.0 / 2.54,
SizeMetric::Mm => px.number.get() * 96.0 / 25.4,
SizeMetric::Em | SizeMetric::Rem => px.number.get() * DEFAULT_FONT_SIZE,
_ => intrinsic_sizes.max_content_height,
}
}
MultiValue::Exact(LayoutHeight::MinContent) => intrinsic_sizes.min_content_height,
MultiValue::Exact(LayoutHeight::MaxContent) => intrinsic_sizes.max_content_height,
_ => intrinsic_sizes.max_content_height,
};
ctx.debug_log(&format!(
"Found atomic inline child at node {}: display={:?}, intrinsic_width={}, used_width={}, css_width={:?}",
child_index, display, intrinsic_sizes.max_content_width, used_width, css_width
));
content.push(InlineContent::Shape(InlineShape {
shape_def: ShapeDefinition::Rectangle {
size: crate::text3::cache::Size {
width: used_width,
height: used_height,
},
corner_radius: None,
},
fill: None,
stroke: None,
baseline_offset: used_height,
alignment: crate::solver3::getters::get_vertical_align_for_node(ctx.styled_dom, child_dom_id),
source_node_id: Some(child_dom_id),
}));
}
}
Ok(())
}
pub fn collect_inline_content<T: ParsedFontTrait>(
ctx: &mut LayoutContext<'_, T>,
tree: &LayoutTree,
ifc_root_index: usize,
) -> Result<Vec<InlineContent>> {
collect_inline_content_for_sizing(ctx, tree, ifc_root_index)
}
fn calculate_intrinsic_recursive<T: ParsedFontTrait>(
ctx: &mut LayoutContext<'_, T>,
tree: &mut LayoutTree,
node_index: usize,
) -> Result<IntrinsicSizes> {
let node = tree
.get(node_index)
.cloned()
.ok_or(LayoutError::InvalidTree)?;
let position = get_position_type(ctx.styled_dom, node.dom_node_id);
if position == LayoutPosition::Absolute || position == LayoutPosition::Fixed {
if let Some(n) = tree.get_mut(node_index) {
n.intrinsic_sizes = Some(IntrinsicSizes::default());
}
return Ok(IntrinsicSizes::default());
}
let mut child_intrinsics = BTreeMap::new();
for &child_index in &node.children {
let child_intrinsic = calculate_intrinsic_recursive(ctx, tree, child_index)?;
child_intrinsics.insert(child_index, child_intrinsic);
}
let intrinsic = calculate_node_intrinsic_sizes_stub(ctx, &node, &child_intrinsics);
if let Some(n) = tree.get_mut(node_index) {
n.intrinsic_sizes = Some(intrinsic.clone());
}
Ok(intrinsic)
}
fn calculate_node_intrinsic_sizes_stub<T: ParsedFontTrait>(
_ctx: &LayoutContext<'_, T>,
_node: &LayoutNode,
child_intrinsics: &BTreeMap<usize, IntrinsicSizes>,
) -> IntrinsicSizes {
let mut max_width: f32 = 0.0;
let mut max_height: f32 = 0.0;
let mut total_width: f32 = 0.0;
let mut total_height: f32 = 0.0;
for intrinsic in child_intrinsics.values() {
max_width = max_width.max(intrinsic.max_content_width);
max_height = max_height.max(intrinsic.max_content_height);
total_width += intrinsic.max_content_width;
total_height += intrinsic.max_content_height;
}
IntrinsicSizes {
min_content_width: total_width.min(max_width),
min_content_height: total_height.min(max_height),
max_content_width: max_width.max(total_width),
max_content_height: max_height.max(total_height),
preferred_width: None,
preferred_height: None,
}
}
pub fn calculate_used_size_for_node(
styled_dom: &StyledDom,
dom_id: Option<NodeId>,
containing_block_size: LogicalSize,
intrinsic: IntrinsicSizes,
_box_props: &BoxProps,
viewport_size: LogicalSize,
) -> Result<LogicalSize> {
let Some(id) = dom_id else {
return Ok(LogicalSize::new(
containing_block_size.width,
if intrinsic.max_content_height > 0.0 {
intrinsic.max_content_height
} else {
0.0
},
));
};
let node_state = &styled_dom.styled_nodes.as_container()[id].styled_node_state;
let css_width = get_css_width(styled_dom, id, node_state);
let css_height = get_css_height(styled_dom, id, node_state);
let writing_mode = get_writing_mode(styled_dom, id, node_state);
let display = get_display_property(styled_dom, Some(id));
let resolved_width = match css_width.unwrap_or_default() {
LayoutWidth::Auto => {
match display.unwrap_or_default() {
LayoutDisplay::Block
| LayoutDisplay::FlowRoot
| LayoutDisplay::ListItem
| LayoutDisplay::Flex
| LayoutDisplay::Grid => {
let available_width = containing_block_size.width
- _box_props.margin.left
- _box_props.margin.right
- _box_props.border.left
- _box_props.border.right
- _box_props.padding.left
- _box_props.padding.right;
available_width.max(0.0)
}
LayoutDisplay::Inline | LayoutDisplay::InlineBlock => {
intrinsic.max_content_width
}
_ => intrinsic.max_content_width,
}
}
LayoutWidth::Px(px) => {
use azul_css::props::basic::{
pixel::{DEFAULT_FONT_SIZE, PT_TO_PX},
SizeMetric,
};
let pixels_opt = match px.metric {
SizeMetric::Px => Some(px.number.get()),
SizeMetric::Pt => Some(px.number.get() * PT_TO_PX),
SizeMetric::In => Some(px.number.get() * 96.0),
SizeMetric::Cm => Some(px.number.get() * 96.0 / 2.54),
SizeMetric::Mm => Some(px.number.get() * 96.0 / 25.4),
SizeMetric::Em | SizeMetric::Rem => Some(px.number.get() * DEFAULT_FONT_SIZE),
SizeMetric::Vw => Some(px.number.get() / 100.0 * viewport_size.width),
SizeMetric::Vh => Some(px.number.get() / 100.0 * viewport_size.height),
SizeMetric::Vmin => Some(px.number.get() / 100.0 * viewport_size.width.min(viewport_size.height)),
SizeMetric::Vmax => Some(px.number.get() / 100.0 * viewport_size.width.max(viewport_size.height)),
SizeMetric::Percent => None,
};
match pixels_opt {
Some(pixels) => pixels,
None => match px.to_percent() {
Some(p) => {
let result = resolve_percentage_with_box_model(
containing_block_size.width,
p.get(),
(_box_props.margin.left, _box_props.margin.right),
(_box_props.border.left, _box_props.border.right),
(_box_props.padding.left, _box_props.padding.right),
);
result
}
None => intrinsic.max_content_width,
},
}
}
LayoutWidth::MinContent => intrinsic.min_content_width,
LayoutWidth::MaxContent => intrinsic.max_content_width,
LayoutWidth::Calc(_) => intrinsic.max_content_width, };
let resolved_height = match css_height.unwrap_or_default() {
LayoutHeight::Auto => {
intrinsic.max_content_height
}
LayoutHeight::Px(px) => {
use azul_css::props::basic::{
pixel::{DEFAULT_FONT_SIZE, PT_TO_PX},
SizeMetric,
};
let pixels_opt = match px.metric {
SizeMetric::Px => Some(px.number.get()),
SizeMetric::Pt => Some(px.number.get() * PT_TO_PX),
SizeMetric::In => Some(px.number.get() * 96.0),
SizeMetric::Cm => Some(px.number.get() * 96.0 / 2.54),
SizeMetric::Mm => Some(px.number.get() * 96.0 / 25.4),
SizeMetric::Em | SizeMetric::Rem => Some(px.number.get() * DEFAULT_FONT_SIZE),
SizeMetric::Vw => Some(px.number.get() / 100.0 * viewport_size.width),
SizeMetric::Vh => Some(px.number.get() / 100.0 * viewport_size.height),
SizeMetric::Vmin => Some(px.number.get() / 100.0 * viewport_size.width.min(viewport_size.height)),
SizeMetric::Vmax => Some(px.number.get() / 100.0 * viewport_size.width.max(viewport_size.height)),
SizeMetric::Percent => None,
};
match pixels_opt {
Some(pixels) => pixels,
None => match px.to_percent() {
Some(p) => resolve_percentage_with_box_model(
containing_block_size.height,
p.get(),
(_box_props.margin.top, _box_props.margin.bottom),
(_box_props.border.top, _box_props.border.bottom),
(_box_props.padding.top, _box_props.padding.bottom),
),
None => intrinsic.max_content_height,
},
}
}
LayoutHeight::MinContent => intrinsic.min_content_height,
LayoutHeight::MaxContent => intrinsic.max_content_height,
LayoutHeight::Calc(_) => intrinsic.max_content_height, };
let constrained_width = apply_width_constraints(
styled_dom,
id,
node_state,
resolved_width,
containing_block_size.width,
_box_props,
);
let constrained_height = apply_height_constraints(
styled_dom,
id,
node_state,
resolved_height,
containing_block_size.height,
_box_props,
);
let box_sizing = match get_css_box_sizing(styled_dom, id, node_state) {
MultiValue::Exact(bs) => bs,
MultiValue::Auto | MultiValue::Initial | MultiValue::Inherit => {
azul_css::props::layout::LayoutBoxSizing::ContentBox
}
};
let (border_box_width, border_box_height) = match box_sizing {
azul_css::props::layout::LayoutBoxSizing::BorderBox => {
(constrained_width, constrained_height)
}
azul_css::props::layout::LayoutBoxSizing::ContentBox => {
let border_box_width = constrained_width
+ _box_props.padding.left
+ _box_props.padding.right
+ _box_props.border.left
+ _box_props.border.right;
let border_box_height = constrained_height
+ _box_props.padding.top
+ _box_props.padding.bottom
+ _box_props.border.top
+ _box_props.border.bottom;
(border_box_width, border_box_height)
}
};
let cross_size = border_box_width;
let main_size = border_box_height;
let result =
LogicalSize::from_main_cross(main_size, cross_size, writing_mode.unwrap_or_default());
Ok(result)
}
fn apply_width_constraints(
styled_dom: &StyledDom,
id: NodeId,
node_state: &StyledNodeState,
tentative_width: f32,
containing_block_width: f32,
box_props: &BoxProps,
) -> f32 {
use azul_css::props::basic::{
pixel::{DEFAULT_FONT_SIZE, PT_TO_PX},
SizeMetric,
};
use crate::solver3::getters::{get_css_max_width, get_css_min_width, MultiValue};
let min_width = match get_css_min_width(styled_dom, id, node_state) {
MultiValue::Exact(mw) => {
let px = &mw.inner;
let pixels_opt = match px.metric {
SizeMetric::Px => Some(px.number.get()),
SizeMetric::Pt => Some(px.number.get() * PT_TO_PX),
SizeMetric::In => Some(px.number.get() * 96.0),
SizeMetric::Cm => Some(px.number.get() * 96.0 / 2.54),
SizeMetric::Mm => Some(px.number.get() * 96.0 / 25.4),
SizeMetric::Em | SizeMetric::Rem => Some(px.number.get() * DEFAULT_FONT_SIZE),
SizeMetric::Percent => None,
_ => None,
};
match pixels_opt {
Some(pixels) => pixels,
None => px
.to_percent()
.map(|p| {
resolve_percentage_with_box_model(
containing_block_width,
p.get(),
(box_props.margin.left, box_props.margin.right),
(box_props.border.left, box_props.border.right),
(box_props.padding.left, box_props.padding.right),
)
})
.unwrap_or(0.0),
}
}
_ => 0.0,
};
let max_width = match get_css_max_width(styled_dom, id, node_state) {
MultiValue::Exact(mw) => {
let px = &mw.inner;
if px.number.get() >= core::f32::MAX - 1.0 {
None
} else {
let pixels_opt = match px.metric {
SizeMetric::Px => Some(px.number.get()),
SizeMetric::Pt => Some(px.number.get() * PT_TO_PX),
SizeMetric::In => Some(px.number.get() * 96.0),
SizeMetric::Cm => Some(px.number.get() * 96.0 / 2.54),
SizeMetric::Mm => Some(px.number.get() * 96.0 / 25.4),
SizeMetric::Em | SizeMetric::Rem => Some(px.number.get() * DEFAULT_FONT_SIZE),
SizeMetric::Percent => None,
_ => None,
};
match pixels_opt {
Some(pixels) => Some(pixels),
None => px.to_percent().map(|p| {
resolve_percentage_with_box_model(
containing_block_width,
p.get(),
(box_props.margin.left, box_props.margin.right),
(box_props.border.left, box_props.border.right),
(box_props.padding.left, box_props.padding.right),
)
}),
}
}
}
_ => None,
};
let mut result = tentative_width;
if let Some(max) = max_width {
result = result.min(max);
}
result = result.max(min_width);
result
}
fn apply_height_constraints(
styled_dom: &StyledDom,
id: NodeId,
node_state: &StyledNodeState,
tentative_height: f32,
containing_block_height: f32,
box_props: &BoxProps,
) -> f32 {
use azul_css::props::basic::{
pixel::{DEFAULT_FONT_SIZE, PT_TO_PX},
SizeMetric,
};
use crate::solver3::getters::{get_css_max_height, get_css_min_height, MultiValue};
let min_height = match get_css_min_height(styled_dom, id, node_state) {
MultiValue::Exact(mh) => {
let px = &mh.inner;
let pixels_opt = match px.metric {
SizeMetric::Px => Some(px.number.get()),
SizeMetric::Pt => Some(px.number.get() * PT_TO_PX),
SizeMetric::In => Some(px.number.get() * 96.0),
SizeMetric::Cm => Some(px.number.get() * 96.0 / 2.54),
SizeMetric::Mm => Some(px.number.get() * 96.0 / 25.4),
SizeMetric::Em | SizeMetric::Rem => Some(px.number.get() * DEFAULT_FONT_SIZE),
SizeMetric::Percent => None,
_ => None,
};
match pixels_opt {
Some(pixels) => pixels,
None => px
.to_percent()
.map(|p| {
resolve_percentage_with_box_model(
containing_block_height,
p.get(),
(box_props.margin.top, box_props.margin.bottom),
(box_props.border.top, box_props.border.bottom),
(box_props.padding.top, box_props.padding.bottom),
)
})
.unwrap_or(0.0),
}
}
_ => 0.0,
};
let max_height = match get_css_max_height(styled_dom, id, node_state) {
MultiValue::Exact(mh) => {
let px = &mh.inner;
if px.number.get() >= core::f32::MAX - 1.0 {
None
} else {
let pixels_opt = match px.metric {
SizeMetric::Px => Some(px.number.get()),
SizeMetric::Pt => Some(px.number.get() * PT_TO_PX),
SizeMetric::In => Some(px.number.get() * 96.0),
SizeMetric::Cm => Some(px.number.get() * 96.0 / 2.54),
SizeMetric::Mm => Some(px.number.get() * 96.0 / 25.4),
SizeMetric::Em | SizeMetric::Rem => Some(px.number.get() * DEFAULT_FONT_SIZE),
SizeMetric::Percent => None,
_ => None,
};
match pixels_opt {
Some(pixels) => Some(pixels),
None => px.to_percent().map(|p| {
resolve_percentage_with_box_model(
containing_block_height,
p.get(),
(box_props.margin.top, box_props.margin.bottom),
(box_props.border.top, box_props.border.bottom),
(box_props.padding.top, box_props.padding.bottom),
)
}),
}
}
}
_ => None,
};
let mut result = tentative_height;
if let Some(max) = max_height {
result = result.min(max);
}
result = result.max(min_height);
result
}
pub fn extract_text_from_node(styled_dom: &StyledDom, node_id: NodeId) -> Option<String> {
match &styled_dom.node_data.as_container()[node_id].get_node_type() {
NodeType::Text(text_data) => Some(text_data.as_str().to_string()),
_ => None,
}
}
fn debug_log(debug_messages: &mut Option<Vec<LayoutDebugMessage>>, message: &str) {
if let Some(messages) = debug_messages {
messages.push(LayoutDebugMessage::info(message));
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_resolve_percentage_with_box_model_basic() {
let result = resolve_percentage_with_box_model(
595.0,
1.0, (0.0, 0.0),
(0.0, 0.0),
(0.0, 0.0),
);
assert_eq!(result, 595.0);
}
#[test]
fn test_resolve_percentage_with_box_model_with_margins() {
let result = resolve_percentage_with_box_model(
595.0,
1.0, (20.0, 20.0),
(0.0, 0.0),
(0.0, 0.0),
);
assert_eq!(result, 595.0);
}
#[test]
fn test_resolve_percentage_with_box_model_with_all_box_properties() {
let result = resolve_percentage_with_box_model(
500.0,
1.0, (10.0, 10.0),
(5.0, 5.0),
(8.0, 8.0),
);
assert_eq!(result, 500.0);
}
#[test]
fn test_resolve_percentage_with_box_model_50_percent() {
let result = resolve_percentage_with_box_model(
600.0,
0.5, (20.0, 20.0),
(0.0, 0.0),
(0.0, 0.0),
);
assert_eq!(result, 300.0);
}
#[test]
fn test_resolve_percentage_with_box_model_asymmetric() {
let result = resolve_percentage_with_box_model(
1000.0,
1.0,
(100.0, 50.0),
(10.0, 20.0),
(5.0, 15.0),
);
assert_eq!(result, 1000.0);
}
#[test]
fn test_resolve_percentage_with_box_model_negative_clamping() {
let result = resolve_percentage_with_box_model(
100.0,
1.0,
(60.0, 60.0), (0.0, 0.0),
(0.0, 0.0),
);
assert_eq!(result, 100.0);
}
#[test]
fn test_resolve_percentage_with_box_model_zero_percent() {
let result = resolve_percentage_with_box_model(
1000.0,
0.0, (100.0, 100.0),
(10.0, 10.0),
(5.0, 5.0),
);
assert_eq!(result, 0.0);
}
}