use crate::debug_log;
use std::{
collections::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, LayoutFlexDirection, LayoutFlexWrap, LayoutFloat, 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, IntrinsicSizes, WritingModeContext},
getters::{
get_css_box_sizing, get_css_height, get_css_width, get_display_property,
get_direction_property, get_element_font_size, get_flex_direction, get_float,
get_style_properties, get_text_orientation_property, get_writing_mode, MultiValue,
},
layout_tree::{LayoutNodeHot, LayoutTree, get_display_type},
positioning::get_position_type,
LayoutContext, LayoutError, Result,
},
};
const FALLBACK_MIN_CONTENT_WIDTH: f32 = 100.0;
const FALLBACK_MAX_CONTENT_WIDTH: f32 = 300.0;
const FALLBACK_MIN_CONTENT_HEIGHT: f32 = 20.0;
const FALLBACK_MAX_CONTENT_HEIGHT: f32 = 20.0;
fn resolve_px_with_box_model(
px: &PixelValue,
containing: f32,
box_props: &BoxProps,
is_horizontal: bool,
em: f32,
rem: f32,
) -> Option<f32> {
if let Some(v) = super::calc::resolve_pixel_value_no_percent(px, em, rem) {
return Some(v);
}
let percent = px.to_percent()?;
let (margin, border, padding) = if is_horizontal {
(
(box_props.margin.left, box_props.margin.right),
(box_props.border.left, box_props.border.right),
(box_props.padding.left, box_props.padding.right),
)
} else {
(
(box_props.margin.top, box_props.margin.bottom),
(box_props.border.top, box_props.border.bottom),
(box_props.padding.top, box_props.padding.bottom),
)
};
Some(resolve_percentage_with_box_model(
containing,
percent.get(),
margin,
border,
padding,
))
}
#[must_use] 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)
}
fn subtree_contains_text(styled_dom: &StyledDom, dom_id: NodeId) -> bool {
let node_hierarchy = styled_dom.node_hierarchy.as_container();
let node_data = styled_dom.node_data.as_container();
if matches!(node_data[dom_id].get_node_type(), NodeType::Text(_)) {
return true;
}
dom_id
.az_children(&node_hierarchy)
.any(|child| subtree_contains_text(styled_dom, child))
}
#[inline(never)]
#[allow(clippy::cast_possible_truncation)] pub fn calculate_intrinsic_sizes<T: ParsedFontTrait>(
ctx: &mut LayoutContext<'_, T>,
tree: &mut LayoutTree,
text_cache: &mut LayoutCache,
dirty_nodes: &BTreeSet<usize>,
) -> Result<()> {
unsafe { crate::az_mark(0x607B0_u32, (tree.nodes.len() as u32)); }
if dirty_nodes.is_empty() {
return Ok(());
}
debug_log!(ctx, "Starting intrinsic size calculation");
let dirty_closure = compute_dirty_ancestor_closure(tree, dirty_nodes);
unsafe { crate::az_mark(0x607B4_u32, (tree.nodes.len() as u32)); }
let mut calculator = IntrinsicSizeCalculator::new(ctx, text_cache);
calculator.dirty_closure = Some(dirty_closure);
unsafe {
crate::az_mark(0x60730_u32, (tree.root as u32));
crate::az_mark(0x60734_u32, (tree.nodes.len() as u32));
crate::az_mark(0x60738_u32, u32::from(tree.get(tree.root).is_some()));
crate::az_mark(0x6075C_u32, ((std::ptr::from_ref::<LayoutTree>(tree) as usize) as u32));
}
calculator.calculate_intrinsic_recursive(tree, tree.root, false)?;
debug_log!(ctx, "Finished intrinsic size calculation");
Ok(())
}
fn compute_dirty_ancestor_closure(
tree: &LayoutTree,
dirty_nodes: &BTreeSet<usize>,
) -> std::collections::HashSet<usize> {
let mut closure: std::collections::HashSet<usize> = std::collections::HashSet::new();
for &dirty in dirty_nodes {
let mut cur = Some(dirty);
while let Some(idx) = cur {
if !closure.insert(idx) {
break;
}
cur = tree.get(idx).and_then(|n| n.parent);
}
}
closure
}
struct IntrinsicSizeCalculator<'a, 'b, 'c, T: ParsedFontTrait> {
ctx: &'a mut LayoutContext<'b, T>,
text_cache: &'c mut LayoutCache,
dirty_closure: Option<std::collections::HashSet<usize>>,
}
impl<'a, 'b, 'c, T: ParsedFontTrait> IntrinsicSizeCalculator<'a, 'b, 'c, T> {
const fn new(ctx: &'a mut LayoutContext<'b, T>, text_cache: &'c mut LayoutCache) -> Self {
Self {
ctx,
text_cache,
dirty_closure: None,
}
}
#[allow(clippy::cast_possible_truncation)] fn calculate_intrinsic_recursive(
&mut self,
tree: &mut LayoutTree,
node_index: usize,
ancestor_is_stf: bool,
) -> Result<IntrinsicSizes> {
unsafe { crate::az_mark(0x60720_u32, (node_index as u32)); }
if let Some(closure) = self.dirty_closure.as_ref() {
if !closure.contains(&node_index) {
if let Some(cached) = tree
.warm(node_index)
.and_then(|w| w.intrinsic_sizes)
{
return Ok(cached);
}
}
}
if !ancestor_is_stf
&& tree
.subtree_needs_intrinsic
.get(node_index)
.copied()
.is_some_and(|v| !v)
{
let default = IntrinsicSizes::default();
if let Some(n) = tree.warm_mut(node_index) {
n.intrinsic_sizes = Some(default);
}
return Ok(default);
}
let dom_node_id = tree
.get(node_index)
.ok_or(LayoutError::InvalidTree)?
.dom_node_id;
let position = get_position_type(self.ctx.styled_dom, dom_node_id);
if position == LayoutPosition::Absolute || position == LayoutPosition::Fixed {
if let Some(n) = tree.warm_mut(node_index) {
n.intrinsic_sizes = Some(IntrinsicSizes::default());
}
return Ok(IntrinsicSizes::default());
}
let children_slice = tree.children(node_index);
let n = children_slice.len();
let mut stack_buf = [0usize; 32];
let heap_buf: Vec<usize>;
let children: &[usize] = if n <= 32 {
stack_buf[..n].copy_from_slice(children_slice);
&stack_buf[..n]
} else {
heap_buf = children_slice.to_vec();
&heap_buf
};
let self_is_stf = tree
.get(node_index)
.is_some_and(|n| {
crate::solver3::layout_tree::is_shrink_to_fit_context(
self.ctx.styled_dom,
n.dom_node_id,
n.formatting_context,
)
});
let child_ancestor_is_stf = ancestor_is_stf || self_is_stf;
let mut child_intrinsics = Vec::with_capacity(n);
for &child_index in children {
unsafe { crate::az_mark(0x60728_u32, (child_index as u32)); }
if tree.get(child_index).is_none() {
continue;
}
let child_intrinsic =
self.calculate_intrinsic_recursive(tree, child_index, child_ancestor_is_stf)?;
child_intrinsics.push((child_index, child_intrinsic));
}
let mut intrinsic = self.calculate_node_intrinsic_sizes(tree, node_index, &child_intrinsics)?;
if let Some(dom_id) = tree.get(node_index).and_then(|n| n.dom_node_id) {
use crate::solver3::getters::{get_css_min_width, get_css_min_height, MultiValue};
let node_state = &self.ctx.styled_dom.styled_nodes.as_container()[dom_id].styled_node_state;
let em = get_element_font_size(self.ctx.styled_dom, dom_id, node_state);
let rem = super::getters::get_root_font_size(self.ctx.styled_dom, node_state);
if let MultiValue::Exact(mw) = get_css_min_width(self.ctx.styled_dom, dom_id, node_state) {
if let Some(min_w) = super::calc::resolve_pixel_value_no_percent(&mw.inner, em, rem) {
intrinsic.min_content_width = intrinsic.min_content_width.max(min_w);
intrinsic.max_content_width = intrinsic.max_content_width.max(min_w);
}
}
if let MultiValue::Exact(mh) = get_css_min_height(self.ctx.styled_dom, dom_id, node_state) {
if let Some(min_h) = super::calc::resolve_pixel_value_no_percent(&mh.inner, em, rem) {
intrinsic.min_content_height = intrinsic.min_content_height.max(min_h);
intrinsic.max_content_height = intrinsic.max_content_height.max(min_h);
}
}
}
if let Some(n) = tree.warm_mut(node_index) {
n.intrinsic_sizes = Some(intrinsic);
}
Ok(intrinsic)
}
#[allow(clippy::too_many_lines)] fn calculate_node_intrinsic_sizes(
&mut self,
tree: &LayoutTree,
node_index: usize,
child_intrinsics: &[(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_virtual_view_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 has_intrinsic = size.width > 0.0 || size.height > 0.0;
let (width, height) = if size.width > 0.0 && size.height > 0.0 {
(size.width, size.height)
} else if size.width > 0.0 {
(size.width, size.width / 2.0)
} else if size.height > 0.0 {
(self.ctx.viewport_size.width, size.height)
} else {
let w = self.ctx.viewport_size.width.min(300.0);
(w, w / 2.0)
};
let (pref_w, pref_h) = if has_intrinsic {
(Some(width), Some(height))
} else {
(None, None)
};
return Ok(IntrinsicSizes {
min_content_width: width,
max_content_width: width,
preferred_width: pref_w,
min_content_height: height,
max_content_height: height,
preferred_height: pref_h,
});
}
}
match node.formatting_context {
FormattingContext::Block { .. } => {
let has_block_child = tree.children(node_index).iter().any(|&child_idx| {
tree.get(child_idx)
.and_then(|c| c.dom_node_id)
.is_some_and(|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);
display.creates_block_context()
})
});
let has_inline_child = tree.children(node_index).iter().any(|&child_idx| {
tree.get(child_idx)
.and_then(|c| c.dom_node_id)
.is_some_and(|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
)
})
});
let is_ifc_root = has_inline_child && !has_block_child;
let has_direct_text = if has_block_child {
false
} else 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_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_text_in_subtree = if let Some(dom_id) = node.dom_node_id {
subtree_contains_text(self.ctx.styled_dom, dom_id)
} else {
false
};
if is_text_node || has_text_in_subtree {
self.calculate_ifc_root_intrinsic_sizes(tree, node_index)
} else {
Ok(IntrinsicSizes::default())
}
}
FormattingContext::InlineBlock => {
let has_inline_children = tree.children(node_index).iter().any(|&child_idx| {
tree.get(child_idx)
.is_some_and(|c| matches!(c.formatting_context, FormattingContext::Inline))
});
let has_direct_text = 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 has_inline_children || has_direct_text {
let intrinsic = self.calculate_ifc_root_intrinsic_sizes(tree, node_index)?;
Ok(intrinsic)
} else {
self.calculate_block_intrinsic_sizes(tree, node_index, child_intrinsics)
}
}
FormattingContext::Table => {
Ok(self.calculate_table_intrinsic_sizes(tree, node_index, child_intrinsics))
}
FormattingContext::Flex => {
self.calculate_flex_intrinsic_sizes(tree, node_index, child_intrinsics)
}
_ => self.calculate_block_intrinsic_sizes(tree, node_index, child_intrinsics),
}
}
#[allow(clippy::cast_possible_truncation)] fn calculate_ifc_root_intrinsic_sizes(
&mut self,
tree: &LayoutTree,
node_index: usize,
) -> Result<IntrinsicSizes> {
unsafe {
let c = crate::az_mark_read(0x60758).wrapping_add(1);
crate::az_mark(0x60758_u32, (c));
crate::az_mark(0x6075C_u32, (node_index as u32));
}
let collect_result = collect_inline_content(self.ctx, tree, node_index);
#[cfg(feature = "web_lift")]
unsafe { crate::az_mark((0x60760) as u32, (if collect_result.is_ok() { 0x00000001u32 } else { 0x000000EEu32 }) as u32); }
let inline_content: Vec<InlineContent> = collect_result?;
if inline_content.is_empty() {
return Ok(IntrinsicSizes::default());
}
let loaded_fonts = self.ctx.font_manager.get_loaded_fonts();
let mut constraints = UnifiedConstraints::default();
if let Some(dom_id) = tree.get(node_index).and_then(|n| n.dom_node_id) {
use crate::solver3::getters::{get_white_space_property, MultiValue};
use azul_css::props::style::text::StyleWhiteSpace;
let node_state =
&self.ctx.styled_dom.styled_nodes.as_container()[dom_id].styled_node_state;
let ws = match get_white_space_property(self.ctx.styled_dom, dom_id, node_state) {
MultiValue::Exact(v) => v,
_ => StyleWhiteSpace::Normal,
};
constraints.white_space_mode = match ws {
StyleWhiteSpace::Normal => crate::text3::cache::WhiteSpaceMode::Normal,
StyleWhiteSpace::Nowrap => crate::text3::cache::WhiteSpaceMode::Nowrap,
StyleWhiteSpace::Pre => crate::text3::cache::WhiteSpaceMode::Pre,
StyleWhiteSpace::PreWrap => crate::text3::cache::WhiteSpaceMode::PreWrap,
StyleWhiteSpace::PreLine => crate::text3::cache::WhiteSpaceMode::PreLine,
StyleWhiteSpace::BreakSpaces => crate::text3::cache::WhiteSpaceMode::BreakSpaces,
};
}
#[cfg(feature = "web_lift")]
{
let cl = self.ctx.font_manager.font_chain_cache.len();
unsafe {
crate::az_mark((0x60768) as u32, (cl as u32) as u32);
crate::az_mark((0x6076C) as u32, (loaded_fonts.len() as u32) as u32);
crate::az_mark((0x60704) as u32, (0xA15u32) as u32);
}
let _ = (cl, loaded_fonts.len());
}
let Ok(intrinsic_text) = self.text_cache.measure_intrinsic_widths(
&inline_content,
&[],
&constraints,
&self.ctx.font_manager.font_chain_cache,
&self.ctx.font_manager.fc_cache,
&loaded_fonts,
self.ctx.debug_messages,
) else {
return Ok(IntrinsicSizes {
min_content_width: FALLBACK_MIN_CONTENT_WIDTH,
max_content_width: FALLBACK_MAX_CONTENT_WIDTH,
preferred_width: None,
min_content_height: FALLBACK_MIN_CONTENT_HEIGHT,
max_content_height: FALLBACK_MAX_CONTENT_HEIGHT,
preferred_height: None,
});
};
let min_width = intrinsic_text.min_content_width;
let max_width = intrinsic_text.max_content_width;
let max_content_height = intrinsic_text.max_content_height;
Ok(IntrinsicSizes {
min_content_width: min_width,
max_content_width: max_width,
preferred_width: None,
min_content_height: max_content_height,
max_content_height,
preferred_height: None,
})
}
fn calculate_block_intrinsic_sizes(
&self,
tree: &LayoutTree,
node_index: usize,
child_intrinsics: &[(usize, IntrinsicSizes)],
) -> Result<IntrinsicSizes> {
let node = tree.get(node_index).ok_or(LayoutError::InvalidTree)?;
let writing_mode = node.dom_node_id.map_or_else(LayoutWritingMode::default, |dom_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()
});
let mut max_child_min_cross = 0.0f32;
let mut max_child_max_cross = 0.0f32;
let mut total_main_size = 0.0;
let mut last_margin_main_end = 0.0f32;
let mut is_first_child = true;
for &child_index in tree.children(node_index) {
if let Some(child_intrinsic) = child_intrinsics.iter().find(|(k, _)| k == &child_index).map(|(_, v)| v) {
let child_node = tree.get(child_index);
let (cross_extras, main_border_padding, main_margin_start, main_margin_end) =
child_node.map_or((0.0, 0.0, 0.0, 0.0), |cn| {
let bp = cn.box_props.unpack();
let h = bp.margin.left + bp.margin.right
+ bp.border.left + bp.border.right
+ bp.padding.left + bp.padding.right;
let v_bp = bp.border.top + bp.border.bottom
+ bp.padding.top + bp.padding.bottom;
match writing_mode {
LayoutWritingMode::HorizontalTb => (h, v_bp, bp.margin.top, bp.margin.bottom),
_ => (v_bp, h, bp.margin.left, bp.margin.right),
}
});
let (child_min_cross, child_max_cross, child_border_box_main) = match writing_mode {
LayoutWritingMode::HorizontalTb => (
child_intrinsic.min_content_width + cross_extras,
child_intrinsic.max_content_width + cross_extras,
child_intrinsic.max_content_height + main_border_padding,
),
_ => (
child_intrinsic.min_content_height + cross_extras,
child_intrinsic.max_content_height + cross_extras,
child_intrinsic.max_content_width + main_border_padding,
),
};
max_child_min_cross = max_child_min_cross.max(child_min_cross);
max_child_max_cross = max_child_max_cross.max(child_max_cross);
if is_first_child {
is_first_child = false;
} else {
let collapsed_gap = crate::solver3::fc::collapse_margins(
last_margin_main_end, main_margin_start
);
total_main_size += collapsed_gap;
}
total_main_size += child_border_box_main;
last_margin_main_end = main_margin_end;
}
}
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_flex_intrinsic_sizes(
&self,
tree: &LayoutTree,
node_index: usize,
child_intrinsics: &[(usize, IntrinsicSizes)],
) -> Result<IntrinsicSizes> {
let node = tree.get(node_index).ok_or(LayoutError::InvalidTree)?;
let is_row = node.dom_node_id.is_none_or(|dom_id| {
let node_state =
&self.ctx.styled_dom.styled_nodes.as_container()[dom_id].styled_node_state;
match get_flex_direction(self.ctx.styled_dom, dom_id, node_state) {
MultiValue::Exact(dir) => matches!(dir, LayoutFlexDirection::Row | LayoutFlexDirection::RowReverse),
_ => true, }
});
let mut sum_main_min: f32 = 0.0;
let mut sum_main_max: f32 = 0.0;
let mut max_main_min: f32 = 0.0;
let mut max_cross_min: f32 = 0.0;
let mut max_cross_max: f32 = 0.0;
for &child_index in tree.children(node_index) {
if let Some(child_intrinsic) = child_intrinsics.iter().find(|(k, _)| k == &child_index).map(|(_, v)| v) {
let (child_main_min, child_main_max, child_cross_min, child_cross_max) = if is_row {
(
child_intrinsic.min_content_width,
child_intrinsic.max_content_width,
child_intrinsic.min_content_height,
child_intrinsic.max_content_height,
)
} else {
(
child_intrinsic.min_content_height,
child_intrinsic.max_content_height,
child_intrinsic.min_content_width,
child_intrinsic.max_content_width,
)
};
sum_main_max += child_main_max;
sum_main_min += child_main_min;
max_main_min = max_main_min.max(child_main_min);
max_cross_min = max_cross_min.max(child_cross_min);
max_cross_max = max_cross_max.max(child_cross_max);
}
}
let is_single_line = node.dom_node_id.is_none_or(|dom_id| {
let node_state =
&self.ctx.styled_dom.styled_nodes.as_container()[dom_id].styled_node_state;
let wrap_prop = crate::solver3::getters::get_flex_wrap_prop(
self.ctx.styled_dom, dom_id, node_state,
);
wrap_prop.is_none_or(|val| matches!(
val.get_property_or_default().unwrap_or_default(),
LayoutFlexWrap::NoWrap
))
});
let min_main = if is_single_line { sum_main_min } else { max_main_min };
let max_main = sum_main_max;
if is_row {
Ok(IntrinsicSizes {
min_content_width: min_main,
max_content_width: max_main,
preferred_width: None,
min_content_height: max_cross_min,
max_content_height: max_cross_max,
preferred_height: None,
})
} else {
Ok(IntrinsicSizes {
min_content_width: max_cross_min,
max_content_width: max_cross_max,
preferred_width: None,
min_content_height: min_main,
max_content_height: max_main,
preferred_height: None,
})
}
}
fn calculate_table_intrinsic_sizes(
&mut self,
tree: &LayoutTree,
node_index: usize,
child_intrinsics: &[(usize, IntrinsicSizes)],
) -> IntrinsicSizes {
let mut col_min: Vec<f32> = Vec::new();
let mut col_max: Vec<f32> = Vec::new();
let mut total_height = 0.0f32;
let mut rows: Vec<usize> = Vec::new();
for &child_idx in tree.children(node_index) {
let Some(child) = tree.get(child_idx) else { continue };
match child.formatting_context {
FormattingContext::TableRow => rows.push(child_idx),
FormattingContext::TableRowGroup => {
for &row_idx in tree.children(child_idx) {
if let Some(row) = tree.get(row_idx) {
if matches!(row.formatting_context, FormattingContext::TableRow) {
rows.push(row_idx);
}
}
}
}
_ => {}
}
}
for &row_idx in &rows {
let mut row_height = 0.0f32;
for (col, &cell_idx) in tree.children(row_idx).iter().enumerate() {
let cell_intrinsic = child_intrinsics.iter().find(|(k, _)| k == &cell_idx).map(|(_, v)| *v)
.unwrap_or_default();
let cell_is = if cell_intrinsic.max_content_width > 0.0 {
cell_intrinsic
} else {
self.calculate_ifc_root_intrinsic_sizes(tree, cell_idx)
.unwrap_or_default()
};
let cell_node = tree.get(cell_idx);
let (h_extras, v_extras) = cell_node.map_or((0.0, 0.0), |cn| {
let bp = cn.box_props.unpack();
(bp.padding.left + bp.padding.right + bp.border.left + bp.border.right,
bp.padding.top + bp.padding.bottom + bp.border.top + bp.border.bottom)
});
let cell_min = cell_is.min_content_width + h_extras;
let cell_max = cell_is.max_content_width + h_extras;
let cell_h = cell_is.max_content_height + v_extras;
if col >= col_min.len() {
col_min.push(cell_min);
col_max.push(cell_max);
} else {
col_min[col] = col_min[col].max(cell_min);
col_max[col] = col_max[col].max(cell_max);
}
row_height = row_height.max(cell_h);
}
total_height += row_height;
}
let min_width: f32 = col_min.iter().sum();
let max_width: f32 = col_max.iter().sum();
IntrinsicSizes {
min_content_width: min_width,
max_content_width: max_width,
min_content_height: total_height,
max_content_height: total_height,
preferred_width: None,
preferred_height: None,
}
}
}
fn collect_inline_content_for_sizing<T: ParsedFontTrait>(
ctx: &mut LayoutContext<'_, T>,
tree: &LayoutTree,
ifc_root_index: usize,
out: &mut Vec<InlineContent>,
) -> Result<()> {
debug_log!(ctx, "Collecting inline content from node {} for intrinsic sizing", ifc_root_index);
collect_inline_content_recursive(ctx, tree, ifc_root_index, out)?;
unsafe { crate::az_mark(0x6071C_u32, (0xB8u32)); }
debug_log!(ctx, "Collected {} inline content items from node {}", out.len(), ifc_root_index);
Ok(())
}
#[allow(clippy::cast_possible_truncation)] fn collect_inline_content_recursive<T: ParsedFontTrait>(
ctx: &mut LayoutContext<'_, T>,
tree: &LayoutTree,
node_index: usize,
content: &mut Vec<InlineContent>,
) -> Result<()> {
unsafe { crate::az_mark(0x60754_u32, (node_index as u32)); }
let Some(node) = tree.get(node_index) else {
unsafe { crate::az_mark(0x6071C_u32, (0xBADu32)); }
return Err(LayoutError::InvalidTree);
};
let Some(dom_id) = node.dom_node_id else {
return process_layout_children(ctx, tree, node_index, 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(), azul_css::props::basic::PhysicalSize::new(ctx.viewport_size.width, ctx.viewport_size.height)));
debug_log!(ctx, "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(), azul_css::props::basic::PhysicalSize::new(ctx.viewport_size.width, ctx.viewport_size.height)));
debug_log!(ctx, "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);
}
}
unsafe { crate::az_mark(0x6071C_u32, (0xB6u32)); }
process_layout_children(ctx, tree, node_index, content)
}
#[allow(clippy::cast_possible_truncation)] #[allow(clippy::match_same_arms)] fn process_layout_children<T: ParsedFontTrait>(
ctx: &mut LayoutContext<'_, T>,
tree: &LayoutTree,
node_index: usize,
content: &mut Vec<InlineContent>,
) -> Result<()> {
use azul_css::props::layout::{LayoutHeight, LayoutWidth};
unsafe { crate::az_mark(0x60708_u32, (0xC000_0000_u32 | (node_index as u32 & 0x00FF_FFFF))); }
for &child_index in tree.children(node_index) {
unsafe { crate::az_mark(0x6070C_u32, (child_index as u32)); }
let Some(child_node) = tree.get(child_index) else { continue; };
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 {
debug_log!(ctx, "Recursing into inline child at node {}", child_index);
collect_inline_content_recursive(ctx, tree, child_index, content)?;
} else {
let intrinsic_sizes = tree.warm(child_index).and_then(|w| w.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)) => {
let em = get_element_font_size(ctx.styled_dom, child_dom_id, node_state);
let rem = super::getters::get_root_font_size(ctx.styled_dom, node_state);
super::calc::resolve_pixel_value_no_percent(&px, em, rem)
.unwrap_or(intrinsic_sizes.max_content_width)
}
MultiValue::Exact(LayoutWidth::MinContent) => intrinsic_sizes.min_content_width,
MultiValue::Exact(LayoutWidth::MaxContent) => intrinsic_sizes.max_content_width,
MultiValue::Exact(LayoutWidth::FitContent(_)) => {
intrinsic_sizes.max_content_width
}
_ => intrinsic_sizes.max_content_width,
};
let used_height = match css_height {
MultiValue::Exact(LayoutHeight::Px(px)) => {
let em = get_element_font_size(ctx.styled_dom, child_dom_id, node_state);
let rem = super::getters::get_root_font_size(ctx.styled_dom, node_state);
super::calc::resolve_pixel_value_no_percent(&px, em, rem)
.unwrap_or(intrinsic_sizes.max_content_height)
}
MultiValue::Exact(LayoutHeight::MinContent) => intrinsic_sizes.max_content_height,
MultiValue::Exact(LayoutHeight::MaxContent) => intrinsic_sizes.max_content_height,
MultiValue::Exact(LayoutHeight::FitContent(_)) => intrinsic_sizes.max_content_height,
_ => intrinsic_sizes.max_content_height,
};
debug_log!(ctx, "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>> {
let mut out = Vec::new();
collect_inline_content_for_sizing(ctx, tree, ifc_root_index, &mut out)?;
Ok(out)
}
#[inline(never)]
#[allow(clippy::trivially_copy_pass_by_ref)] fn auto_block_inline_size(cb: &LogicalSize, bp: &BoxProps) -> f32 {
let aw = cb.width
- bp.margin.left
- bp.margin.right
- bp.border.left
- bp.border.right
- bp.padding.left
- bp.padding.right;
aw.max(0.0)
}
#[allow(clippy::match_same_arms)] #[allow(clippy::too_many_lines, clippy::cognitive_complexity)] 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 position = get_position_type(styled_dom, dom_id);
let wm_ctx = WritingModeContext::new(
writing_mode.unwrap_or_default(),
get_direction_property(styled_dom, id, node_state).unwrap_or_default(),
get_text_orientation_property(styled_dom, id, node_state).unwrap_or_default(),
);
let is_vertical = !wm_ctx.is_horizontal();
let node_data = &styled_dom.node_data.as_container()[id];
let is_replaced = matches!(node_data.get_node_type(), NodeType::Image(_))
|| node_data.is_virtual_view_node();
let css_width = if display.unwrap_or_default() == LayoutDisplay::Inline
&& !is_replaced
{
MultiValue::Exact(LayoutWidth::Auto)
} else {
css_width
};
let css_height = if display.unwrap_or_default() == LayoutDisplay::Inline
&& !is_replaced
{
MultiValue::Exact(LayoutHeight::Auto)
} else {
css_height
};
let width_is_auto = css_width.is_auto() || matches!(&css_width, MultiValue::Exact(LayoutWidth::Auto));
let height_is_auto = css_height.is_auto() || matches!(&css_height, MultiValue::Exact(LayoutHeight::Auto));
let width_is_quantitative = matches!(
&css_width,
MultiValue::Exact(LayoutWidth::Px(_) | LayoutWidth::FitContent(_) | LayoutWidth::Calc(_))
);
let height_is_quantitative = matches!(
&css_height,
MultiValue::Exact(LayoutHeight::Px(_) | LayoutHeight::FitContent(_) | LayoutHeight::Calc(_))
);
let resolved_width = match css_width.unwrap_or_default() {
LayoutWidth::Auto => {
if is_replaced {
intrinsic.max_content_width
}
else if get_float(styled_dom, id, node_state).unwrap_or(LayoutFloat::None) != LayoutFloat::None {
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)
.max(0.0);
let preferred_minimum = intrinsic.min_content_width;
let preferred = intrinsic.max_content_width;
preferred_minimum.max(available_width).min(preferred).max(0.0)
}
else if matches!(position, LayoutPosition::Absolute | LayoutPosition::Fixed) {
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)
.max(0.0);
let preferred_minimum = intrinsic.min_content_width;
let preferred = intrinsic.max_content_width;
preferred_minimum.max(available_width).min(preferred).max(0.0)
} else {
match display.unwrap_or_default() {
LayoutDisplay::Block
| LayoutDisplay::FlowRoot
| LayoutDisplay::ListItem
| LayoutDisplay::Flex
| LayoutDisplay::Grid => {
auto_block_inline_size(containing_block_size, box_props)
}
LayoutDisplay::InlineBlock | LayoutDisplay::InlineGrid | LayoutDisplay::InlineFlex => {
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)
.max(0.0);
let preferred_minimum = intrinsic.min_content_width;
let preferred = intrinsic.max_content_width;
preferred_minimum.max(available_width).min(preferred).max(0.0)
}
LayoutDisplay::Inline => {
intrinsic.max_content_width
}
LayoutDisplay::Table | LayoutDisplay::InlineTable => intrinsic.max_content_width,
LayoutDisplay::TableCell => {
if intrinsic.max_content_width > 0.0 {
intrinsic.max_content_width
} else {
(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)
.max(0.0)
}
}
_ => intrinsic.max_content_width,
}
}
}
LayoutWidth::Px(px) => {
let em = get_element_font_size(styled_dom, id, node_state);
let rem = super::getters::get_root_font_size(styled_dom, node_state);
let pixels_opt = super::calc::resolve_pixel_value_no_percent_with_viewport(
&px, em, rem,
viewport_size.width, viewport_size.height,
);
pixels_opt.unwrap_or_else(|| {
px.to_percent().map_or(intrinsic.max_content_width, |p| {
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),
)
})
})
}
LayoutWidth::MinContent => intrinsic.min_content_width,
LayoutWidth::MaxContent => intrinsic.max_content_width,
LayoutWidth::FitContent(px) => {
let em = get_element_font_size(styled_dom, id, node_state);
let rem = super::getters::get_root_font_size(styled_dom, node_state);
let arg = super::calc::resolve_pixel_value_with_viewport(
&px, containing_block_size.width, em, rem,
viewport_size.width, viewport_size.height,
);
intrinsic.max_content_width.min(intrinsic.min_content_width.max(arg))
}
LayoutWidth::Calc(items) => {
use azul_css::props::basic::pixel::DEFAULT_FONT_SIZE;
let em = get_element_font_size(styled_dom, id, node_state);
let calc_ctx = super::calc::CalcResolveContext {
items, em_size: em, rem_size: DEFAULT_FONT_SIZE,
};
super::calc::evaluate_calc(&calc_ctx, containing_block_size.width)
}
};
let resolved_width = resolved_width.max(0.0);
let resolved_height = match css_height.unwrap_or_default() {
LayoutHeight::Auto => {
let abs_stretch_fit = if matches!(
position,
LayoutPosition::Absolute | LayoutPosition::Fixed
) && !is_replaced
{
let off = crate::solver3::positioning::resolve_position_offsets(
styled_dom, dom_id, *containing_block_size, *viewport_size,
);
match (off.top, off.bottom) {
(Some(t), Some(b)) => Some(
(containing_block_size.height
- t
- b
- box_props.margin.top
- box_props.margin.bottom)
.max(0.0),
),
_ => None,
}
} else {
None
};
match abs_stretch_fit {
Some(h) => h,
None if is_replaced => intrinsic.max_content_height,
None => match display.unwrap_or_default() {
LayoutDisplay::Block
| LayoutDisplay::FlowRoot
| LayoutDisplay::ListItem
| LayoutDisplay::Flex
| LayoutDisplay::Grid => 0.0,
LayoutDisplay::Inline => 0.0,
_ => intrinsic.max_content_height,
},
}
}
LayoutHeight::Px(px) => {
let em = get_element_font_size(styled_dom, id, node_state);
let rem = super::getters::get_root_font_size(styled_dom, node_state);
let pixels_opt = super::calc::resolve_pixel_value_no_percent_with_viewport(
&px, em, rem,
viewport_size.width, viewport_size.height,
);
pixels_opt.unwrap_or_else(|| {
px.to_percent().map_or(intrinsic.max_content_height, |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),
)
})
})
}
LayoutHeight::MinContent => intrinsic.max_content_height,
LayoutHeight::MaxContent => intrinsic.max_content_height,
LayoutHeight::FitContent(px) => {
let em = get_element_font_size(styled_dom, id, node_state);
let rem = super::getters::get_root_font_size(styled_dom, node_state);
let arg = super::calc::resolve_pixel_value_with_viewport(
&px, containing_block_size.height, em, rem,
viewport_size.width, viewport_size.height,
);
let auto_height = intrinsic.max_content_height;
auto_height.min(auto_height.max(arg))
}
LayoutHeight::Calc(items) => {
use azul_css::props::basic::pixel::DEFAULT_FONT_SIZE;
let em = get_element_font_size(styled_dom, id, node_state);
let calc_ctx = super::calc::CalcResolveContext {
items, em_size: em, rem_size: DEFAULT_FONT_SIZE,
};
super::calc::evaluate_calc(&calc_ctx, containing_block_size.height)
}
};
let resolved_height = resolved_height.max(0.0);
let (resolved_width, resolved_height) = if is_replaced
&& width_is_auto
&& matches!(position, LayoutPosition::Absolute | LayoutPosition::Fixed)
{
let has_intrinsic_width = intrinsic.preferred_width.is_some_and(|w| w > 0.0);
let has_intrinsic_height = intrinsic.preferred_height.is_some_and(|h| h > 0.0);
let intrinsic_ratio = match (intrinsic.preferred_width, intrinsic.preferred_height) {
(Some(iw), Some(ih)) if ih > 0.0 => Some(iw / ih),
_ => None,
};
intrinsic_ratio.map_or((resolved_width, resolved_height), |ratio| if height_is_auto && !has_intrinsic_width && has_intrinsic_height {
(resolved_height * ratio, resolved_height)
} else if !height_is_auto {
(resolved_height * ratio, resolved_height)
} else if height_is_auto && !has_intrinsic_width && !has_intrinsic_height {
let block_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)
.max(0.0);
(block_width, block_width / ratio)
} else {
(resolved_width, resolved_height)
})
} else {
(resolved_width, resolved_height)
};
let has_intrinsic_ratio = intrinsic.preferred_width.is_some()
&& intrinsic.preferred_height.is_some()
&& intrinsic.preferred_width.unwrap_or(0.0) > 0.0
&& intrinsic.preferred_height.unwrap_or(0.0) > 0.0;
let (constrained_width, constrained_height) = if has_intrinsic_ratio {
apply_constraint_violation_table(
styled_dom,
id,
node_state,
resolved_width,
resolved_height,
containing_block_size.width,
containing_block_size.height,
box_props,
)
} else {
let cw = apply_width_constraints(
styled_dom,
id,
node_state,
resolved_width,
containing_block_size.width,
box_props,
);
let ch = apply_height_constraints(
styled_dom,
id,
node_state,
resolved_height,
containing_block_size.height,
box_props,
);
(cw, ch)
};
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 => {
let min_border_box_w = box_props.padding.left
+ box_props.padding.right
+ box_props.border.left
+ box_props.border.right;
let min_border_box_h = box_props.padding.top
+ box_props.padding.bottom
+ box_props.border.top
+ box_props.border.bottom;
let bw = if width_is_quantitative {
constrained_width.max(min_border_box_w)
} else {
constrained_width
+ box_props.padding.left
+ box_props.padding.right
+ box_props.border.left
+ box_props.border.right
};
let bh = if height_is_quantitative {
constrained_height.max(min_border_box_h)
} else {
constrained_height
+ box_props.padding.top
+ box_props.padding.bottom
+ box_props.border.top
+ box_props.border.bottom
};
(bw, bh)
}
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 (main_size, cross_size) = if is_vertical {
(border_box_width, border_box_height)
} else {
(border_box_height, border_box_width)
};
let result =
LogicalSize::from_main_cross(main_size, cross_size, writing_mode.unwrap_or_default());
Ok(result)
}
fn apply_constraint_violation_table(
styled_dom: &StyledDom,
id: NodeId,
node_state: &StyledNodeState,
w: f32, h: f32, containing_block_width: f32,
containing_block_height: f32,
box_props: &BoxProps,
) -> (f32, f32) {
use crate::solver3::getters::{
get_css_min_width, get_css_max_width, get_css_min_height, get_css_max_height, MultiValue,
};
let em = get_element_font_size(styled_dom, id, node_state);
let rem = super::getters::get_root_font_size(styled_dom, node_state);
let min_w = match get_css_min_width(styled_dom, id, node_state) {
MultiValue::Exact(mw) => resolve_px_with_box_model(&mw.inner, containing_block_width, box_props, true, em, rem).unwrap_or(0.0),
_ => 0.0,
};
let max_w = match get_css_max_width(styled_dom, id, node_state) {
MultiValue::Exact(mw) => {
if mw.inner.number.get() >= core::f32::MAX - 1.0 {
f32::MAX
} else {
resolve_px_with_box_model(&mw.inner, containing_block_width, box_props, true, em, rem).unwrap_or(f32::MAX)
}
}
_ => f32::MAX,
};
let min_h = match get_css_min_height(styled_dom, id, node_state) {
MultiValue::Exact(mh) => resolve_px_with_box_model(&mh.inner, containing_block_height, box_props, false, em, rem).unwrap_or(0.0),
_ => 0.0,
};
let max_h = match get_css_max_height(styled_dom, id, node_state) {
MultiValue::Exact(mh) => {
if mh.inner.number.get() >= core::f32::MAX - 1.0 {
f32::MAX
} else {
resolve_px_with_box_model(&mh.inner, containing_block_height, box_props, false, em, rem).unwrap_or(f32::MAX)
}
}
_ => f32::MAX,
};
let max_w = max_w.max(min_w);
let max_h = max_h.max(min_h);
if w <= 0.0 || h <= 0.0 {
return (w.max(min_w).min(max_w), h.max(min_h).min(max_h));
}
let w_over = w > max_w;
let w_under = w < min_w;
let h_over = h > max_h;
let h_under = h < min_h;
match (w_over, w_under, h_over, h_under) {
(false, false, false, false) => (w, h),
(true, false, false, false) => {
(max_w, (max_w * h / w).max(min_h))
}
(false, true, false, false) => {
(min_w, (min_w * h / w).min(max_h))
}
(false, false, true, false) => {
((max_h * w / h).max(min_w), max_h)
}
(false, false, false, true) => {
((min_h * w / h).min(max_w), min_h)
}
(true, false, true, false) => {
if max_w / w <= max_h / h {
(max_w, (max_w * h / w).max(min_h))
} else {
((max_h * w / h).max(min_w), max_h)
}
}
(false, true, false, true) => {
if min_w / w <= min_h / h {
((min_h * w / h).min(max_w), min_h)
} else {
(min_w, (min_w * h / w).min(max_h))
}
}
(false, true, true, false) => (min_w, max_h),
(true, false, false, true) => (max_w, min_h),
_ => (w.max(min_w).min(max_w), h.max(min_h).min(max_h)),
}
}
fn apply_width_constraints(
styled_dom: &StyledDom,
id: NodeId,
node_state: &StyledNodeState,
tentative_width: f32,
containing_block_width: f32,
box_props: &BoxProps,
) -> f32 {
use crate::solver3::getters::{get_css_max_width, get_css_min_width, MultiValue};
let em = get_element_font_size(styled_dom, id, node_state);
let rem = super::getters::get_root_font_size(styled_dom, node_state);
let min_width = match get_css_min_width(styled_dom, id, node_state) {
MultiValue::Exact(mw) => resolve_px_with_box_model(&mw.inner, containing_block_width, box_props, true, em, rem).unwrap_or(0.0),
_ => 0.0,
};
let max_width = match get_css_max_width(styled_dom, id, node_state) {
MultiValue::Exact(mw) => {
if mw.inner.number.get() >= core::f32::MAX - 1.0 {
None
} else {
resolve_px_with_box_model(&mw.inner, containing_block_width, box_props, true, em, rem)
}
}
_ => None,
};
let mut result = tentative_width;
if let Some(max) = max_width {
result = result.min(max);
}
result.max(min_width)
}
fn apply_height_constraints(
styled_dom: &StyledDom,
id: NodeId,
node_state: &StyledNodeState,
tentative_height: f32,
containing_block_height: f32,
box_props: &BoxProps,
) -> f32 {
use crate::solver3::getters::{get_css_max_height, get_css_min_height, MultiValue};
let em = get_element_font_size(styled_dom, id, node_state);
let rem = super::getters::get_root_font_size(styled_dom, node_state);
let min_height = match get_css_min_height(styled_dom, id, node_state) {
MultiValue::Exact(mh) => resolve_px_with_box_model(&mh.inner, containing_block_height, box_props, false, em, rem).unwrap_or(0.0),
_ => 0.0,
};
let max_height = match get_css_max_height(styled_dom, id, node_state) {
MultiValue::Exact(mh) => {
if mh.inner.number.get() >= core::f32::MAX - 1.0 {
None
} else {
resolve_px_with_box_model(&mh.inner, containing_block_height, box_props, false, em, rem)
}
}
_ => None,
};
let mut result = tentative_height;
if let Some(max) = max_height {
result = result.min(max);
}
result.max(min_height)
}
#[must_use] 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,
}
}