use std::{
collections::{BTreeMap, HashMap},
hash::{Hash, Hasher},
sync::{
atomic::{AtomicU32, Ordering},
Arc,
},
};
use azul_core::diff::NodeDataFingerprint;
use crate::text3::cache::UnifiedConstraints;
static IFC_ID_COUNTER: AtomicU32 = AtomicU32::new(0);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct IfcId(pub u32);
impl IfcId {
pub fn unique() -> Self {
Self(IFC_ID_COUNTER.fetch_add(1, Ordering::Relaxed))
}
pub fn reset_counter() {
IFC_ID_COUNTER.store(0, Ordering::Relaxed);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct IfcMembership {
pub ifc_id: IfcId,
pub ifc_root_layout_index: usize,
pub run_index: u32,
}
use azul_core::{
dom::{FormattingContext, NodeData, NodeId, NodeType},
geom::{LogicalPosition, LogicalRect, LogicalSize},
styled_dom::StyledDom,
};
use azul_css::{
corety::LayoutDebugMessage,
css::CssPropertyValue,
format_rust_code::GetHash,
props::{
basic::{
pixel::DEFAULT_FONT_SIZE, PhysicalSize, PixelValue, PropertyContext, ResolutionContext,
},
layout::{
LayoutDisplay, LayoutFloat, LayoutHeight, LayoutMaxHeight, LayoutMaxWidth,
LayoutMinHeight, LayoutMinWidth, LayoutOverflow, LayoutPosition, LayoutWidth,
LayoutWritingMode,
},
property::{CssProperty, CssPropertyType},
style::{StyleTextAlign, StyleWhiteSpace},
},
};
use taffy::{Cache as TaffyCache, Layout, LayoutInput, LayoutOutput};
#[cfg(feature = "text_layout")]
use crate::text3;
use crate::{
debug_log,
font::parsed::ParsedFont,
font_traits::{FontLoaderTrait, ParsedFontTrait, UnifiedLayout},
solver3::{
geometry::{BoxProps, IntrinsicSizes, PositionedRectangle},
getters::{
get_css_height, get_css_max_height, get_css_max_width, get_css_min_height,
get_css_min_width, get_css_width, get_direction_property as get_direction,
get_display_property, get_float, get_overflow_x,
get_overflow_y, get_position, get_text_align,
get_text_orientation_property as get_text_orientation,
get_white_space_property, get_writing_mode, MultiValue,
},
scrollbar::ScrollbarRequirements,
LayoutContext, Result,
},
text3::cache::AvailableSpace,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
pub enum DirtyFlag {
#[default]
None,
Paint,
Layout,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)]
pub struct SubtreeHash(pub u64);
#[derive(Debug, Clone)]
pub struct InlineItemMetrics {
pub source_node_id: Option<NodeId>,
pub advance_width: f32,
pub line_height_contribution: f32,
pub can_break: bool,
pub line_index: u32,
pub x_offset: f32,
}
#[derive(Debug, Clone)]
pub struct CachedInlineLayout {
pub layout: Arc<UnifiedLayout>,
pub available_width: AvailableSpace,
pub has_floats: bool,
pub constraints: Option<UnifiedConstraints>,
pub item_metrics: Vec<InlineItemMetrics>,
pub line_breaks: Option<crate::text3::cache::CachedLineBreaks>,
}
impl CachedInlineLayout {
pub fn new(
layout: Arc<UnifiedLayout>,
available_width: AvailableSpace,
has_floats: bool,
) -> Self {
let item_metrics = Self::extract_item_metrics(&layout);
Self {
layout,
available_width,
has_floats,
constraints: None,
item_metrics,
line_breaks: None,
}
}
pub fn new_with_constraints(
layout: Arc<UnifiedLayout>,
available_width: AvailableSpace,
has_floats: bool,
constraints: UnifiedConstraints,
) -> Self {
let item_metrics = Self::extract_item_metrics(&layout);
let available_width_px = match available_width {
AvailableSpace::Definite(w) => w,
_ => f32::MAX,
};
let line_breaks = Some(crate::text3::cache::extract_line_breaks(
&layout.items, available_width_px,
));
Self {
layout,
available_width,
has_floats,
constraints: Some(constraints),
item_metrics,
line_breaks,
}
}
fn extract_item_metrics(layout: &UnifiedLayout) -> Vec<InlineItemMetrics> {
use crate::text3::cache::{ShapedItem, get_item_vertical_metrics_approx};
layout.items.iter().map(|positioned_item| {
let bounds = positioned_item.item.bounds();
let (ascent, descent) = get_item_vertical_metrics_approx(&positioned_item.item);
let source_node_id = match &positioned_item.item {
ShapedItem::Cluster(c) => c.source_node_id,
ShapedItem::Object { .. }
| ShapedItem::CombinedBlock { .. }
| ShapedItem::Tab { .. }
| ShapedItem::Break { .. } => None,
};
let can_break = !matches!(&positioned_item.item, ShapedItem::Break { .. });
InlineItemMetrics {
source_node_id,
advance_width: bounds.width,
line_height_contribution: ascent + descent,
can_break,
line_index: positioned_item.line_index as u32,
x_offset: positioned_item.position.x,
}
}).collect()
}
pub fn is_valid_for(&self, new_width: AvailableSpace, new_has_floats: bool) -> bool {
if self.has_floats && !new_has_floats {
return self.width_constraint_matches(new_width);
}
self.width_constraint_matches(new_width)
}
const LAYOUT_WIDTH_EPSILON: f32 = 0.1;
fn width_constraint_matches(&self, new_width: AvailableSpace) -> bool {
match (self.available_width, new_width) {
(AvailableSpace::Definite(old), AvailableSpace::Definite(new)) => {
(old - new).abs() < Self::LAYOUT_WIDTH_EPSILON
}
(AvailableSpace::MinContent, AvailableSpace::MinContent) => true,
(AvailableSpace::MaxContent, AvailableSpace::MaxContent) => true,
_ => false,
}
}
pub fn should_replace_with(&self, new_width: AvailableSpace, new_has_floats: bool) -> bool {
if new_has_floats && !self.has_floats {
return true;
}
!self.width_constraint_matches(new_width)
}
#[inline]
pub fn get_layout(&self) -> &Arc<UnifiedLayout> {
&self.layout
}
#[inline]
pub fn clone_layout(&self) -> Arc<UnifiedLayout> {
self.layout.clone()
}
}
#[derive(Debug, Clone)]
#[repr(C)]
pub struct LayoutNode {
pub box_props: BoxProps,
pub dom_node_id: Option<NodeId>,
pub children: Vec<usize>,
pub used_size: Option<LogicalSize>,
pub formatting_context: FormattingContext,
pub parent: Option<usize>,
pub intrinsic_sizes: Option<IntrinsicSizes>,
pub baseline: Option<f32>,
pub inline_layout_result: Option<CachedInlineLayout>,
pub scrollbar_info: Option<ScrollbarRequirements>,
pub relative_position: Option<LogicalPosition>,
pub overflow_content_size: Option<LogicalSize>,
pub taffy_cache: TaffyCache,
pub computed_style: ComputedLayoutStyle,
pub pseudo_element: Option<PseudoElement>,
pub escaped_top_margin: Option<f32>,
pub escaped_bottom_margin: Option<f32>,
pub parent_formatting_context: Option<FormattingContext>,
pub ifc_membership: Option<IfcMembership>,
pub containing_block_index: Option<usize>,
pub anonymous_type: Option<AnonymousBoxType>,
pub node_data_fingerprint: NodeDataFingerprint,
pub subtree_hash: SubtreeHash,
pub dirty_flag: DirtyFlag,
pub unresolved_box_props: crate::solver3::geometry::UnresolvedBoxProps,
pub ifc_id: Option<IfcId>,
}
#[derive(Debug, Clone, Default)]
pub struct ComputedLayoutStyle {
pub display: LayoutDisplay,
pub position: LayoutPosition,
pub float: LayoutFloat,
pub overflow_x: LayoutOverflow,
pub overflow_y: LayoutOverflow,
pub writing_mode: azul_css::props::layout::LayoutWritingMode,
pub direction: azul_css::props::style::StyleDirection,
pub text_orientation: azul_css::props::style::effects::StyleTextOrientation,
pub width: Option<azul_css::props::layout::LayoutWidth>,
pub height: Option<azul_css::props::layout::LayoutHeight>,
pub min_width: Option<azul_css::props::layout::LayoutMinWidth>,
pub min_height: Option<azul_css::props::layout::LayoutMinHeight>,
pub max_width: Option<azul_css::props::layout::LayoutMaxWidth>,
pub max_height: Option<azul_css::props::layout::LayoutMaxHeight>,
pub text_align: azul_css::props::style::StyleTextAlign,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PseudoElement {
Marker,
Before,
After,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum AnonymousBoxType {
InlineWrapper,
ListItemMarker,
TableWrapper,
TableRowGroup,
TableRow,
TableCell,
}
#[derive(Debug, Clone)]
pub struct LayoutNodeHot {
pub box_props: crate::solver3::geometry::PackedBoxProps,
pub dom_node_id: Option<NodeId>,
pub used_size: Option<LogicalSize>,
pub formatting_context: FormattingContext,
pub parent: Option<usize>,
}
#[derive(Debug, Clone, Default)]
pub struct LayoutNodeWarm {
pub intrinsic_sizes: Option<IntrinsicSizes>,
pub baseline: Option<f32>,
pub inline_layout_result: Option<CachedInlineLayout>,
pub scrollbar_info: Option<ScrollbarRequirements>,
pub relative_position: Option<LogicalPosition>,
pub overflow_content_size: Option<LogicalSize>,
pub taffy_cache: TaffyCache,
pub computed_style: ComputedLayoutStyle,
pub pseudo_element: Option<PseudoElement>,
pub escaped_top_margin: Option<f32>,
pub escaped_bottom_margin: Option<f32>,
pub parent_formatting_context: Option<FormattingContext>,
pub ifc_membership: Option<IfcMembership>,
pub containing_block_index: Option<usize>,
}
#[derive(Debug, Clone)]
pub struct LayoutNodeCold {
pub anonymous_type: Option<AnonymousBoxType>,
pub node_data_fingerprint: NodeDataFingerprint,
pub subtree_hash: SubtreeHash,
pub dirty_flag: DirtyFlag,
pub unresolved_box_props: crate::solver3::geometry::UnresolvedBoxProps,
pub ifc_id: Option<IfcId>,
}
impl Default for LayoutNodeCold {
fn default() -> Self {
Self {
anonymous_type: None,
node_data_fingerprint: NodeDataFingerprint::default(),
subtree_hash: SubtreeHash::default(),
dirty_flag: DirtyFlag::default(),
unresolved_box_props: Default::default(),
ifc_id: None,
}
}
}
impl LayoutNode {
pub fn split(self) -> (LayoutNodeHot, LayoutNodeWarm, LayoutNodeCold) {
(
LayoutNodeHot {
box_props: crate::solver3::geometry::PackedBoxProps::pack(&self.box_props),
dom_node_id: self.dom_node_id,
used_size: self.used_size,
formatting_context: self.formatting_context,
parent: self.parent,
},
LayoutNodeWarm {
intrinsic_sizes: self.intrinsic_sizes,
baseline: self.baseline,
inline_layout_result: self.inline_layout_result,
scrollbar_info: self.scrollbar_info,
relative_position: self.relative_position,
overflow_content_size: self.overflow_content_size,
taffy_cache: self.taffy_cache,
computed_style: self.computed_style,
pseudo_element: self.pseudo_element,
escaped_top_margin: self.escaped_top_margin,
escaped_bottom_margin: self.escaped_bottom_margin,
parent_formatting_context: self.parent_formatting_context,
ifc_membership: self.ifc_membership,
containing_block_index: self.containing_block_index,
},
LayoutNodeCold {
anonymous_type: self.anonymous_type,
node_data_fingerprint: self.node_data_fingerprint,
subtree_hash: self.subtree_hash,
dirty_flag: self.dirty_flag,
unresolved_box_props: self.unresolved_box_props,
ifc_id: self.ifc_id,
},
)
}
}
#[derive(Debug, Clone)]
pub struct LayoutTree {
pub nodes: Vec<LayoutNodeHot>,
pub warm: Vec<LayoutNodeWarm>,
pub cold: Vec<LayoutNodeCold>,
pub root: usize,
pub dom_to_layout: HashMap<NodeId, Vec<usize>>,
pub children_arena: Vec<usize>,
pub children_offsets: Vec<(u32, u32)>,
pub subtree_needs_intrinsic: Vec<bool>,
}
#[derive(Debug, Clone, Default)]
pub struct LayoutTreeMemoryReport {
pub node_count: usize,
pub hot_bytes: usize,
pub warm_bytes: usize,
pub warm_inline_layout_bytes: usize,
pub warm_taffy_cache_bytes: usize,
pub cold_bytes: usize,
pub dom_to_layout_bytes: usize,
pub children_arena_bytes: usize,
pub children_offsets_bytes: usize,
}
impl LayoutTreeMemoryReport {
pub fn total_bytes(&self) -> usize {
self.hot_bytes
+ self.warm_bytes
+ self.warm_inline_layout_bytes
+ self.warm_taffy_cache_bytes
+ self.cold_bytes
+ self.dom_to_layout_bytes
+ self.children_arena_bytes
+ self.children_offsets_bytes
}
}
impl LayoutTree {
pub fn memory_report(&self) -> LayoutTreeMemoryReport {
let mut report = LayoutTreeMemoryReport {
node_count: self.nodes.len(),
hot_bytes: self.nodes.capacity() * core::mem::size_of::<LayoutNodeHot>(),
warm_bytes: self.warm.capacity() * core::mem::size_of::<LayoutNodeWarm>(),
cold_bytes: self.cold.capacity() * core::mem::size_of::<LayoutNodeCold>(),
children_arena_bytes: self.children_arena.capacity() * core::mem::size_of::<usize>(),
children_offsets_bytes: self.children_offsets.capacity() * core::mem::size_of::<(u32, u32)>(),
dom_to_layout_bytes: 0,
warm_inline_layout_bytes: 0,
warm_taffy_cache_bytes: 0,
};
let entries = self.dom_to_layout.len();
report.dom_to_layout_bytes = entries * (core::mem::size_of::<NodeId>() + core::mem::size_of::<Vec<usize>>());
for v in self.dom_to_layout.values() {
report.dom_to_layout_bytes += v.capacity() * core::mem::size_of::<usize>();
}
for w in &self.warm {
if let Some(cached) = &w.inline_layout_result {
report.warm_inline_layout_bytes += core::mem::size_of::<crate::text3::cache::UnifiedLayout>();
report.warm_inline_layout_bytes += cached.layout.items.capacity()
* core::mem::size_of::<crate::text3::cache::PositionedItem>();
report.warm_inline_layout_bytes += cached.item_metrics.capacity()
* core::mem::size_of::<InlineItemMetrics>();
for item in cached.layout.items.iter() {
if let crate::text3::cache::ShapedItem::Cluster(c) = &item.item {
report.warm_inline_layout_bytes += c.glyphs.capacity()
* core::mem::size_of::<crate::text3::cache::ShapedGlyph>();
report.warm_inline_layout_bytes += c.text.capacity();
}
}
}
report.warm_taffy_cache_bytes += core::mem::size_of::<TaffyCache>();
}
report
}
#[inline]
pub fn children(&self, index: usize) -> &[usize] {
if let Some(&(start, len)) = self.children_offsets.get(index) {
&self.children_arena[(start as usize)..((start as usize) + (len as usize))]
} else {
&[]
}
}
#[inline]
pub fn get(&self, index: usize) -> Option<&LayoutNodeHot> {
self.nodes.get(index)
}
#[inline]
pub fn get_mut(&mut self, index: usize) -> Option<&mut LayoutNodeHot> {
self.nodes.get_mut(index)
}
#[inline]
pub fn warm(&self, index: usize) -> Option<&LayoutNodeWarm> {
self.warm.get(index)
}
#[inline]
pub fn warm_mut(&mut self, index: usize) -> Option<&mut LayoutNodeWarm> {
self.warm.get_mut(index)
}
#[inline]
pub fn cold(&self, index: usize) -> Option<&LayoutNodeCold> {
self.cold.get(index)
}
#[inline]
pub fn cold_mut(&mut self, index: usize) -> Option<&mut LayoutNodeCold> {
self.cold.get_mut(index)
}
pub fn root_node(&self) -> &LayoutNodeHot {
&self.nodes[self.root]
}
pub fn get_full_node(&self, index: usize) -> Option<LayoutNode> {
let hot = self.nodes.get(index)?;
let warm = self.warm.get(index).cloned().unwrap_or_default();
let cold = self.cold.get(index).cloned().unwrap_or_default();
let children = self.children(index).to_vec();
Some(LayoutNode {
box_props: hot.box_props.unpack(),
dom_node_id: hot.dom_node_id,
children,
used_size: hot.used_size,
formatting_context: hot.formatting_context.clone(),
parent: hot.parent,
intrinsic_sizes: warm.intrinsic_sizes,
baseline: warm.baseline,
inline_layout_result: warm.inline_layout_result,
scrollbar_info: warm.scrollbar_info,
relative_position: warm.relative_position,
overflow_content_size: warm.overflow_content_size,
taffy_cache: warm.taffy_cache,
computed_style: warm.computed_style,
pseudo_element: warm.pseudo_element,
escaped_top_margin: warm.escaped_top_margin,
escaped_bottom_margin: warm.escaped_bottom_margin,
parent_formatting_context: warm.parent_formatting_context,
ifc_membership: warm.ifc_membership,
containing_block_index: warm.containing_block_index,
anonymous_type: cold.anonymous_type,
node_data_fingerprint: cold.node_data_fingerprint,
subtree_hash: cold.subtree_hash,
dirty_flag: cold.dirty_flag,
unresolved_box_props: cold.unresolved_box_props,
ifc_id: cold.ifc_id,
})
}
pub fn resolve_box_props(
&mut self,
node_index: usize,
containing_block: LogicalSize,
viewport_size: LogicalSize,
element_font_size: f32,
root_font_size: f32,
) {
let params = crate::solver3::geometry::ResolutionParams {
containing_block,
viewport_size,
element_font_size,
root_font_size,
};
if let (Some(hot), Some(cold)) = (self.nodes.get_mut(node_index), self.cold.get(node_index)) {
hot.box_props = crate::solver3::geometry::PackedBoxProps::pack(&cold.unresolved_box_props.resolve(¶ms));
}
}
pub fn mark_dirty(&mut self, start_index: usize, flag: DirtyFlag) {
if flag == DirtyFlag::None {
return;
}
let mut current_index = Some(start_index);
while let Some(index) = current_index {
let cold = match self.cold.get_mut(index) {
Some(c) => c,
None => break,
};
if cold.dirty_flag >= flag {
break;
}
cold.dirty_flag = flag;
current_index = self.nodes.get(index).and_then(|n| n.parent);
}
}
pub fn mark_subtree_dirty(&mut self, start_index: usize, flag: DirtyFlag) {
if flag == DirtyFlag::None {
return;
}
let mut stack = vec![start_index];
while let Some(index) = stack.pop() {
let children = self.children(index).to_vec();
if let Some(cold) = self.cold.get_mut(index) {
if cold.dirty_flag < flag {
cold.dirty_flag = flag;
}
stack.extend_from_slice(&children);
}
}
}
pub fn clear_all_dirty_flags(&mut self) {
for cold in &mut self.cold {
cold.dirty_flag = DirtyFlag::None;
}
}
pub fn get_inline_layout_for_node(&self, layout_index: usize) -> Option<&std::sync::Arc<UnifiedLayout>> {
let warm = self.warm.get(layout_index)?;
if let Some(cached) = &warm.inline_layout_result {
return Some(cached.get_layout());
}
if let Some(ifc_membership) = &warm.ifc_membership {
let ifc_root_warm = self.warm.get(ifc_membership.ifc_root_layout_index)?;
if let Some(cached) = &ifc_root_warm.inline_layout_result {
return Some(cached.get_layout());
}
}
None
}
pub fn get_content_size(&self, index: usize) -> LogicalSize {
let warm = match self.warm.get(index) {
Some(w) => w,
None => return LogicalSize::default(),
};
if let Some(content_size) = warm.overflow_content_size {
return content_size;
}
let hot = match self.nodes.get(index) {
Some(h) => h,
None => return LogicalSize::default(),
};
let mut content_size = hot.used_size.unwrap_or_default();
if let Some(ref cached_layout) = warm.inline_layout_result {
let text_layout = &cached_layout.layout;
let mut max_x: f32 = 0.0;
let mut max_y: f32 = 0.0;
for positioned_item in &text_layout.items {
let item_bounds = positioned_item.item.bounds();
max_x = max_x.max(positioned_item.position.x + item_bounds.width);
max_y = max_y.max(positioned_item.position.y + item_bounds.height);
}
content_size.width = content_size.width.max(max_x);
content_size.height = content_size.height.max(max_y);
}
content_size
}
}
pub fn generate_layout_tree<T: ParsedFontTrait>(
ctx: &mut LayoutContext<'_, T>,
) -> Result<LayoutTree> {
let mut builder = LayoutTreeBuilder::new(ctx.viewport_size);
let root_id = ctx
.styled_dom
.root
.into_crate_internal()
.unwrap_or(NodeId::ZERO);
let root_index =
builder.process_node(ctx.styled_dom, root_id, None, &mut ctx.debug_messages)?;
let mut layout_tree = builder.build(root_index);
layout_tree.subtree_needs_intrinsic = compute_subtree_needs_intrinsic(ctx.styled_dom, &layout_tree);
debug_log!(
ctx,
"Generated layout tree with {} nodes (incl. anonymous)",
layout_tree.nodes.len()
);
Ok(layout_tree)
}
pub(crate) fn is_shrink_to_fit_context(
styled_dom: &StyledDom,
dom_node_id: Option<NodeId>,
fc: &FormattingContext,
) -> bool {
use crate::solver3::getters::{get_float, MultiValue};
use crate::solver3::positioning::get_position_type;
use azul_css::props::layout::{LayoutFloat, LayoutPosition};
match fc {
FormattingContext::Flex
| FormattingContext::Grid
| FormattingContext::Table
| FormattingContext::InlineBlock => return true,
_ => {}
}
let Some(dom_id) = dom_node_id else { return false; };
let node_state = &styled_dom.styled_nodes.as_container()[dom_id].styled_node_state;
let float_val = match get_float(styled_dom, dom_id, node_state) {
MultiValue::Exact(v) => v,
_ => LayoutFloat::None,
};
if float_val != LayoutFloat::None {
return true;
}
let pos = get_position_type(styled_dom, Some(dom_id));
if pos == LayoutPosition::Absolute || pos == LayoutPosition::Fixed {
return true;
}
false
}
fn compute_subtree_needs_intrinsic(
styled_dom: &StyledDom,
tree: &LayoutTree,
) -> Vec<bool> {
let n = tree.nodes.len();
let mut out = vec![false; n];
for idx in (0..n).rev() {
let hot = &tree.nodes[idx];
let self_stf = is_shrink_to_fit_context(styled_dom, hot.dom_node_id, &hot.formatting_context);
let mut any = self_stf;
if !any {
for &child in tree.children(idx) {
if out.get(child).copied().unwrap_or(false) {
any = true;
break;
}
}
}
out[idx] = any;
}
out
}
pub struct LayoutTreeBuilder {
nodes: Vec<LayoutNode>,
dom_to_layout: HashMap<NodeId, Vec<usize>>,
viewport_size: LogicalSize,
}
impl LayoutTreeBuilder {
pub fn new(viewport_size: LogicalSize) -> Self {
Self {
nodes: Vec::new(),
dom_to_layout: HashMap::new(),
viewport_size,
}
}
pub fn get(&self, index: usize) -> Option<&LayoutNode> {
self.nodes.get(index)
}
pub fn get_mut(&mut self, index: usize) -> Option<&mut LayoutNode> {
self.nodes.get_mut(index)
}
pub fn process_node(
&mut self,
styled_dom: &StyledDom,
dom_id: NodeId,
parent_idx: Option<usize>,
debug_messages: &mut Option<Vec<LayoutDebugMessage>>,
) -> Result<usize> {
let node_data = &styled_dom.node_data.as_container()[dom_id];
let node_idx = self.create_node_from_dom(styled_dom, dom_id, parent_idx, debug_messages);
let raw_display = get_display_type(styled_dom, dom_id);
let raw_display = if raw_display.is_layout_internal() && is_replaced_element(node_data) {
LayoutDisplay::Inline
} else {
raw_display
};
let node_position = self.nodes.get(node_idx).map(|n| n.computed_style.position).unwrap_or_default();
let node_float = self.nodes.get(node_idx).map(|n| n.computed_style.float).unwrap_or_default();
let is_absolute_or_fixed = matches!(node_position, LayoutPosition::Absolute | LayoutPosition::Fixed);
let is_floated = node_float != LayoutFloat::None;
let is_root = parent_idx.is_none();
if is_absolute_or_fixed && is_floated {
if let Some(node) = self.nodes.get_mut(node_idx) {
node.computed_style.float = LayoutFloat::None;
}
}
let is_flex_grid_child = parent_idx
.and_then(|p| self.nodes.get(p).map(|n| matches!(n.formatting_context, FormattingContext::Flex | FormattingContext::Grid)))
.unwrap_or(false);
let display_type = crate::solver3::getters::get_computed_display(
raw_display, is_absolute_or_fixed, is_floated, is_root, is_flex_grid_child,
);
if display_type != raw_display {
if let Some(node) = self.nodes.get_mut(node_idx) {
node.computed_style.display = display_type;
node.formatting_context = determine_formatting_context_for_display(
styled_dom, dom_id, display_type,
);
}
}
if is_absolute_or_fixed {
let cb_index = if matches!(node_position, LayoutPosition::Fixed) {
None
} else {
let mut ancestor = parent_idx;
loop {
match ancestor {
Some(idx) => {
let pos = self.nodes.get(idx)
.map(|n| n.computed_style.position)
.unwrap_or_default();
if pos.is_positioned() {
break Some(idx);
}
ancestor = self.nodes.get(idx).and_then(|n| n.parent);
}
None => break None, }
}
};
if let Some(node) = self.nodes.get_mut(node_idx) {
node.containing_block_index = cb_index;
}
}
if parent_idx.is_none() {
if let Some(node) = self.nodes.get_mut(node_idx) {
if let FormattingContext::Block { ref mut establishes_new_context } = node.formatting_context {
*establishes_new_context = true;
}
}
}
if display_type == LayoutDisplay::ListItem {
self.create_marker_pseudo_element(styled_dom, dom_id, node_idx);
}
if display_type == LayoutDisplay::Contents && is_replaced_element(node_data) {
if let Some(parent) = parent_idx {
if let Some(p) = self.nodes.get_mut(parent) {
p.children.retain(|&c| c != node_idx);
}
}
if let Some(node) = self.nodes.get_mut(node_idx) {
node.computed_style.display = LayoutDisplay::None;
node.formatting_context = FormattingContext::None;
}
return Ok(node_idx);
}
if display_type == LayoutDisplay::Contents {
if let Some(parent) = parent_idx {
if let Some(p) = self.nodes.get_mut(parent) {
p.children.retain(|&c| c != node_idx);
}
}
let effective_parent = parent_idx.unwrap_or(node_idx);
for child_dom_id in dom_id.az_children(&styled_dom.node_hierarchy.as_container()) {
self.process_node(styled_dom, child_dom_id, Some(effective_parent), debug_messages)?;
}
return Ok(node_idx);
}
match display_type {
LayoutDisplay::Block
| LayoutDisplay::InlineBlock
| LayoutDisplay::FlowRoot
| LayoutDisplay::ListItem => {
self.process_block_children(styled_dom, dom_id, node_idx, debug_messages)?
}
LayoutDisplay::Table | LayoutDisplay::InlineTable => {
self.process_table_children(styled_dom, dom_id, node_idx, debug_messages)?
}
LayoutDisplay::TableRowGroup
| LayoutDisplay::TableHeaderGroup
| LayoutDisplay::TableFooterGroup => {
self.process_table_row_group_children(styled_dom, dom_id, node_idx, debug_messages)?
}
LayoutDisplay::TableRow => {
self.process_table_row_children(styled_dom, dom_id, node_idx, debug_messages)?
}
LayoutDisplay::TableColumn => {
}
LayoutDisplay::TableColumnGroup => {
for child_dom_id in dom_id.az_children(&styled_dom.node_hierarchy.as_container()) {
let child_display = get_display_type(styled_dom, child_dom_id);
if child_display == LayoutDisplay::TableColumn {
self.process_node(styled_dom, child_dom_id, Some(node_idx), debug_messages)?;
}
}
}
_ => {
let children: Vec<NodeId> = dom_id
.az_children(&styled_dom.node_hierarchy.as_container())
.filter(|&child_id| {
if get_display_type(styled_dom, child_id) == LayoutDisplay::None {
return false;
}
let node_data = &styled_dom.node_data.as_container()[child_id];
if let NodeType::Text(text) = node_data.get_node_type() {
return !text.as_str().trim().is_empty();
}
true
})
.collect();
let is_flex_or_grid = matches!(
display_type,
LayoutDisplay::Flex | LayoutDisplay::InlineFlex
| LayoutDisplay::Grid | LayoutDisplay::InlineGrid
);
for child_dom_id in children {
let child_display = get_display_type(styled_dom, child_dom_id);
if is_flex_or_grid && child_display.creates_table_context() {
let wrapper_idx = self.create_anonymous_node(
node_idx,
AnonymousBoxType::TableWrapper,
FormattingContext::Block { establishes_new_context: true },
);
self.process_node(styled_dom, child_dom_id, Some(wrapper_idx), debug_messages)?;
} else {
let child_idx = self.process_node(styled_dom, child_dom_id, Some(node_idx), debug_messages)?;
if is_flex_or_grid {
blockify_flex_item_if_table_internal(&mut self.nodes, child_idx);
}
}
}
}
}
Ok(node_idx)
}
fn process_block_children(
&mut self,
styled_dom: &StyledDom,
parent_dom_id: NodeId,
parent_idx: usize,
debug_messages: &mut Option<Vec<LayoutDebugMessage>>,
) -> Result<()> {
let children: Vec<NodeId> = parent_dom_id
.az_children(&styled_dom.node_hierarchy.as_container())
.filter(|&child_id| get_display_type(styled_dom, child_id) != LayoutDisplay::None)
.collect();
if let Some(msgs) = debug_messages.as_mut() {
msgs.push(LayoutDebugMessage::info(format!(
"[process_block_children] DOM node {} has {} children: {:?}",
parent_dom_id.index(),
children.len(),
children.iter().map(|c| c.index()).collect::<Vec<_>>()
)));
}
let has_block_child = children.iter().any(|&id| is_block_level(styled_dom, id));
if let Some(msgs) = debug_messages.as_mut() {
msgs.push(LayoutDebugMessage::info(format!(
"[process_block_children] has_block_child={}, children display types: {:?}",
has_block_child,
children
.iter()
.map(|c| {
let dt = get_display_type(styled_dom, *c);
let is_block = is_block_level(styled_dom, *c);
format!("{}:{:?}(block={})", c.index(), dt, is_block)
})
.collect::<Vec<_>>()
)));
}
if !has_block_child {
if let Some(msgs) = debug_messages.as_mut() {
msgs.push(LayoutDebugMessage::info(format!(
"[process_block_children] All inline, processing {} children directly",
children.len()
)));
}
for child_id in children {
self.process_node(styled_dom, child_id, Some(parent_idx), debug_messages)?;
}
return Ok(());
}
let mut inline_run = Vec::new();
for child_id in children {
if is_block_level(styled_dom, child_id) {
if !inline_run.is_empty() {
let all_whitespace = inline_run
.iter()
.all(|id| is_whitespace_only_text(styled_dom, *id));
if all_whitespace {
if let Some(msgs) = debug_messages.as_mut() {
msgs.push(LayoutDebugMessage::info(format!(
"[process_block_children] Skipping whitespace-only inline run between blocks: {:?}",
inline_run.iter().map(|c: &NodeId| c.index()).collect::<Vec<_>>()
)));
}
inline_run.clear();
} else {
if let Some(msgs) = debug_messages.as_mut() {
msgs.push(LayoutDebugMessage::info(format!(
"[process_block_children] Creating anon wrapper for inline run: {:?}",
inline_run
.iter()
.map(|c: &NodeId| c.index())
.collect::<Vec<_>>()
)));
}
let anon_idx = self.create_anonymous_node(
parent_idx,
AnonymousBoxType::InlineWrapper,
FormattingContext::Block {
establishes_new_context: true,
},
);
for inline_child_id in inline_run.drain(..) {
self.process_node(
styled_dom,
inline_child_id,
Some(anon_idx),
debug_messages,
)?;
}
}
}
if let Some(msgs) = debug_messages.as_mut() {
msgs.push(LayoutDebugMessage::info(format!(
"[process_block_children] Processing block child DOM {}",
child_id.index()
)));
}
self.process_node(styled_dom, child_id, Some(parent_idx), debug_messages)?;
} else {
inline_run.push(child_id);
}
}
if !inline_run.is_empty() {
let all_whitespace = inline_run
.iter()
.all(|id| is_whitespace_only_text(styled_dom, *id));
if all_whitespace {
if let Some(msgs) = debug_messages.as_mut() {
msgs.push(LayoutDebugMessage::info(format!(
"[process_block_children] Skipping trailing whitespace-only inline run: {:?}",
inline_run.iter().map(|c| c.index()).collect::<Vec<_>>()
)));
}
} else {
if let Some(msgs) = debug_messages.as_mut() {
msgs.push(LayoutDebugMessage::info(format!(
"[process_block_children] Creating anon wrapper for remaining inline run: {:?}",
inline_run.iter().map(|c| c.index()).collect::<Vec<_>>()
)));
}
let anon_idx = self.create_anonymous_node(
parent_idx,
AnonymousBoxType::InlineWrapper,
FormattingContext::Block {
establishes_new_context: true, },
);
for inline_child_id in inline_run {
self.process_node(
styled_dom,
inline_child_id,
Some(anon_idx),
debug_messages,
)?;
}
}
}
Ok(())
}
fn process_table_children(
&mut self,
styled_dom: &StyledDom,
parent_dom_id: NodeId,
parent_idx: usize,
debug_messages: &mut Option<Vec<LayoutDebugMessage>>,
) -> Result<()> {
let parent_display = get_display_type(styled_dom, parent_dom_id);
let mut non_proper_children = Vec::new();
for child_id in parent_dom_id.az_children(&styled_dom.node_hierarchy.as_container()) {
if should_skip_for_table_structure(styled_dom, child_id, parent_display) {
continue;
}
let child_display = get_display_type(styled_dom, child_id);
if is_proper_table_child(child_display) {
if !non_proper_children.is_empty() {
let anon_row_idx = self.create_anonymous_node(
parent_idx,
AnonymousBoxType::TableRow,
FormattingContext::TableRow,
);
for np_id in non_proper_children.drain(..) {
self.process_node(styled_dom, np_id, Some(anon_row_idx), debug_messages)?;
}
}
self.process_node(styled_dom, child_id, Some(parent_idx), debug_messages)?;
} else {
non_proper_children.push(child_id);
}
}
if !non_proper_children.is_empty() {
let anon_row_idx = self.create_anonymous_node(
parent_idx,
AnonymousBoxType::TableRow,
FormattingContext::TableRow,
);
for np_id in non_proper_children {
self.process_node(styled_dom, np_id, Some(anon_row_idx), debug_messages)?;
}
}
Ok(())
}
fn process_table_row_group_children(
&mut self,
styled_dom: &StyledDom,
parent_dom_id: NodeId,
parent_idx: usize,
debug_messages: &mut Option<Vec<LayoutDebugMessage>>,
) -> Result<()> {
let parent_display = get_display_type(styled_dom, parent_dom_id);
let mut non_row_children = Vec::new();
for child_id in parent_dom_id.az_children(&styled_dom.node_hierarchy.as_container()) {
if should_skip_for_table_structure(styled_dom, child_id, parent_display) {
continue;
}
let child_display = get_display_type(styled_dom, child_id);
if child_display == LayoutDisplay::TableRow {
if !non_row_children.is_empty() {
let anon_row_idx = self.create_anonymous_node(
parent_idx,
AnonymousBoxType::TableRow,
FormattingContext::TableRow,
);
for nr_id in non_row_children.drain(..) {
self.process_node(styled_dom, nr_id, Some(anon_row_idx), debug_messages)?;
}
}
self.process_node(styled_dom, child_id, Some(parent_idx), debug_messages)?;
} else {
non_row_children.push(child_id);
}
}
if !non_row_children.is_empty() {
let anon_row_idx = self.create_anonymous_node(
parent_idx,
AnonymousBoxType::TableRow,
FormattingContext::TableRow,
);
for nr_id in non_row_children {
self.process_node(styled_dom, nr_id, Some(anon_row_idx), debug_messages)?;
}
}
Ok(())
}
fn process_table_row_children(
&mut self,
styled_dom: &StyledDom,
parent_dom_id: NodeId,
parent_idx: usize,
debug_messages: &mut Option<Vec<LayoutDebugMessage>>,
) -> Result<()> {
let parent_display = get_display_type(styled_dom, parent_dom_id);
let mut non_cell_children = Vec::new();
for child_id in parent_dom_id.az_children(&styled_dom.node_hierarchy.as_container()) {
if should_skip_for_table_structure(styled_dom, child_id, parent_display) {
continue;
}
let child_display = get_display_type(styled_dom, child_id);
if child_display == LayoutDisplay::TableCell {
if !non_cell_children.is_empty() {
let anon_cell_idx = self.create_anonymous_node(
parent_idx,
AnonymousBoxType::TableCell,
FormattingContext::Block {
establishes_new_context: true,
},
);
for nc_id in non_cell_children.drain(..) {
self.process_node(styled_dom, nc_id, Some(anon_cell_idx), debug_messages)?;
}
}
self.process_node(styled_dom, child_id, Some(parent_idx), debug_messages)?;
} else {
non_cell_children.push(child_id);
}
}
if !non_cell_children.is_empty() {
let anon_cell_idx = self.create_anonymous_node(
parent_idx,
AnonymousBoxType::TableCell,
FormattingContext::Block {
establishes_new_context: true,
},
);
for nc_id in non_cell_children {
self.process_node(styled_dom, nc_id, Some(anon_cell_idx), debug_messages)?;
}
}
Ok(())
}
pub fn create_anonymous_node(
&mut self,
parent: usize,
anon_type: AnonymousBoxType,
fc: FormattingContext,
) -> usize {
let index = self.nodes.len();
let parent_fc = self.nodes.get(parent).map(|n| n.formatting_context.clone());
self.nodes.push(LayoutNode {
box_props: BoxProps::default(),
dom_node_id: None,
children: Vec::new(),
used_size: None,
formatting_context: fc,
parent: Some(parent),
intrinsic_sizes: None,
baseline: None,
inline_layout_result: None,
scrollbar_info: None,
relative_position: None,
overflow_content_size: None,
taffy_cache: TaffyCache::new(),
computed_style: ComputedLayoutStyle::default(),
pseudo_element: None,
escaped_top_margin: None,
escaped_bottom_margin: None,
parent_formatting_context: parent_fc,
ifc_membership: None,
containing_block_index: None,
anonymous_type: Some(anon_type),
node_data_fingerprint: NodeDataFingerprint::default(),
subtree_hash: SubtreeHash(0),
dirty_flag: DirtyFlag::Layout,
unresolved_box_props: crate::solver3::geometry::UnresolvedBoxProps::default(),
ifc_id: None,
});
self.nodes[parent].children.push(index);
index
}
pub fn create_marker_pseudo_element(
&mut self,
styled_dom: &StyledDom,
list_item_dom_id: NodeId,
list_item_idx: usize,
) -> usize {
let index = self.nodes.len();
let parent_fc = self
.nodes
.get(list_item_idx)
.map(|n| n.formatting_context.clone());
self.nodes.push(LayoutNode {
box_props: BoxProps::default(),
dom_node_id: Some(list_item_dom_id),
children: Vec::new(),
used_size: None,
formatting_context: FormattingContext::Inline,
parent: Some(list_item_idx),
intrinsic_sizes: None,
baseline: None,
inline_layout_result: None,
scrollbar_info: None,
relative_position: None,
overflow_content_size: None,
taffy_cache: TaffyCache::new(),
computed_style: ComputedLayoutStyle::default(),
pseudo_element: Some(PseudoElement::Marker),
escaped_top_margin: None,
escaped_bottom_margin: None,
parent_formatting_context: parent_fc,
ifc_membership: None,
containing_block_index: None,
anonymous_type: None,
node_data_fingerprint: NodeDataFingerprint::default(),
subtree_hash: SubtreeHash(0),
dirty_flag: DirtyFlag::Layout,
unresolved_box_props: crate::solver3::geometry::UnresolvedBoxProps::default(),
ifc_id: None,
});
self.nodes[list_item_idx].children.insert(0, index);
self.dom_to_layout
.entry(list_item_dom_id)
.or_default()
.push(index);
index
}
pub fn create_node_from_dom(
&mut self,
styled_dom: &StyledDom,
dom_id: NodeId,
parent: Option<usize>,
debug_messages: &mut Option<Vec<LayoutDebugMessage>>,
) -> usize {
let index = self.nodes.len();
unsafe { core::ptr::write_volatile(0x400B4 as *mut u32, 0xCE00_0000u32 | (index as u32 & 0xffff)); }
let parent_fc =
parent.and_then(|p| self.nodes.get(p).map(|n| n.formatting_context.clone()));
unsafe { core::ptr::write_volatile(0x400CC as *mut u32, 0xCD00_0001u32 | ((parent_fc.is_some() as u32) << 8)); }
let collected = collect_box_props(styled_dom, dom_id, debug_messages, self.viewport_size);
unsafe { core::ptr::write_volatile(0x400C0 as *mut u32, 0xCA00_0001u32); }
self.nodes.push(LayoutNode {
box_props: collected.resolved,
dom_node_id: Some(dom_id),
children: Vec::new(),
used_size: None,
formatting_context: determine_formatting_context(styled_dom, dom_id),
parent,
intrinsic_sizes: None,
baseline: None,
inline_layout_result: None,
scrollbar_info: None,
relative_position: None,
overflow_content_size: None,
taffy_cache: TaffyCache::new(),
computed_style: {
let mut style = compute_layout_style(styled_dom, dom_id);
if parent.is_none() {
use azul_css::props::layout::LayoutOverflow;
if style.overflow_x == LayoutOverflow::Visible {
style.overflow_x = LayoutOverflow::Auto;
} else if style.overflow_x == LayoutOverflow::Clip {
style.overflow_x = LayoutOverflow::Hidden;
}
if style.overflow_y == LayoutOverflow::Visible {
style.overflow_y = LayoutOverflow::Auto;
} else if style.overflow_y == LayoutOverflow::Clip {
style.overflow_y = LayoutOverflow::Hidden;
}
}
style
},
pseudo_element: None,
escaped_top_margin: None,
escaped_bottom_margin: None,
parent_formatting_context: parent_fc,
ifc_membership: None,
containing_block_index: None,
anonymous_type: None,
node_data_fingerprint: NodeDataFingerprint::compute(
&styled_dom.node_data.as_container()[dom_id],
styled_dom.styled_nodes.as_container().get(dom_id).map(|n| &n.styled_node_state),
),
subtree_hash: SubtreeHash(0),
dirty_flag: DirtyFlag::Layout,
unresolved_box_props: collected.unresolved,
ifc_id: None,
});
unsafe { core::ptr::write_volatile(0x400C4 as *mut u32, 0xCB00_0001u32 | ((self.nodes.len() as u32 & 0xff) << 8)); }
if let Some(p) = parent {
self.nodes[p].children.push(index);
}
self.dom_to_layout.entry(dom_id).or_default().push(index);
unsafe { core::ptr::write_volatile(0x400B8 as *mut u32, 0xCF00_0000u32 | (self.nodes.len() as u32 & 0xffff)); }
index
}
pub fn clone_node_from_old(&mut self, old_node: &LayoutNode, parent: Option<usize>) -> usize {
let index = self.nodes.len();
let mut new_node = old_node.clone();
new_node.parent = parent;
new_node.parent_formatting_context =
parent.and_then(|p| self.nodes.get(p).map(|n| n.formatting_context.clone()));
new_node.children = Vec::new();
new_node.dirty_flag = DirtyFlag::None;
self.nodes.push(new_node);
if let Some(p) = parent {
self.nodes[p].children.push(index);
}
if let Some(dom_id) = old_node.dom_node_id {
self.dom_to_layout.entry(dom_id).or_default().push(index);
}
index
}
pub fn build(self, root_idx: usize) -> LayoutTree {
let nodes = self.nodes;
let node_count = nodes.len();
let total_children: usize = nodes.iter().map(|n| n.children.len()).sum();
let mut arena = Vec::with_capacity(total_children);
let mut offsets = Vec::with_capacity(node_count);
let mut hot_nodes = Vec::with_capacity(node_count);
let mut warm_nodes = Vec::with_capacity(node_count);
let mut cold_nodes = Vec::with_capacity(node_count);
for node in nodes {
let start = arena.len() as u32;
let len = node.children.len() as u32;
arena.extend_from_slice(&node.children);
offsets.push((start, len));
let (hot, warm, cold) = node.split();
hot_nodes.push(hot);
warm_nodes.push(warm);
cold_nodes.push(cold);
}
unsafe {
core::ptr::write_volatile(
0x400B0 as *mut u32,
0xBD00_0000u32 | (((hot_nodes.len() as u32) & 0xff) << 8) | (root_idx as u32 & 0xff),
);
}
LayoutTree {
nodes: hot_nodes,
warm: warm_nodes,
cold: cold_nodes,
root: root_idx,
dom_to_layout: self.dom_to_layout,
children_arena: arena,
children_offsets: offsets,
subtree_needs_intrinsic: Vec::new(),
}
}
}
pub fn is_block_level(styled_dom: &StyledDom, node_id: NodeId) -> bool {
matches!(
get_display_type(styled_dom, node_id),
LayoutDisplay::Block
| LayoutDisplay::FlowRoot
| LayoutDisplay::Flex
| LayoutDisplay::Grid
| LayoutDisplay::Table
| LayoutDisplay::TableCaption
| LayoutDisplay::TableRow
| LayoutDisplay::TableRowGroup
| LayoutDisplay::TableHeaderGroup
| LayoutDisplay::TableFooterGroup
| LayoutDisplay::TableCell
| LayoutDisplay::ListItem
)
}
fn is_inline_level(styled_dom: &StyledDom, node_id: NodeId) -> bool {
let node_data = &styled_dom.node_data.as_container()[node_id];
if matches!(node_data.get_node_type(), NodeType::Text(_)) {
return true;
}
matches!(
get_display_type(styled_dom, node_id),
LayoutDisplay::Inline
| LayoutDisplay::InlineBlock
| LayoutDisplay::InlineTable
| LayoutDisplay::InlineFlex
| LayoutDisplay::InlineGrid
)
}
fn has_only_inline_children(styled_dom: &StyledDom, node_id: NodeId) -> bool {
let hierarchy = styled_dom.node_hierarchy.as_container();
let node_hier = match hierarchy.get(node_id) {
Some(n) => n,
None => {
return false;
}
};
let mut current_child = node_hier.first_child_id(node_id);
if current_child.is_none() {
return false;
}
while let Some(child_id) = current_child {
let is_inline = is_inline_level(styled_dom, child_id);
if !is_inline {
return false;
}
if let Some(child_hier) = hierarchy.get(child_id) {
current_child = child_hier.next_sibling_id();
} else {
break;
}
}
true
}
fn compute_layout_style(styled_dom: &StyledDom, dom_id: NodeId) -> ComputedLayoutStyle {
let styled_node_state = styled_dom
.styled_nodes
.as_container()
.get(dom_id)
.map(|n| n.styled_node_state.clone())
.unwrap_or_default();
let display = match get_display_property(styled_dom, Some(dom_id)) {
MultiValue::Exact(d) => d,
MultiValue::Auto | MultiValue::Initial | MultiValue::Inherit => LayoutDisplay::Block,
};
let position = get_position(styled_dom, dom_id, &styled_node_state).unwrap_or_default();
let float = get_float(styled_dom, dom_id, &styled_node_state).unwrap_or_default();
let is_replaced = matches!(
styled_dom.node_data.as_container()[dom_id].get_node_type(),
NodeType::Image(_) | NodeType::VirtualView
);
let overflow_x = {
let v = get_overflow_x(styled_dom, dom_id, &styled_node_state).unwrap_or_default();
if is_replaced && v == LayoutOverflow::Hidden { LayoutOverflow::Clip } else { v }
};
let overflow_y = {
let v = get_overflow_y(styled_dom, dom_id, &styled_node_state).unwrap_or_default();
if is_replaced && v == LayoutOverflow::Hidden { LayoutOverflow::Clip } else { v }
};
let writing_mode = {
let own_wm = get_writing_mode(styled_dom, dom_id, &styled_node_state).unwrap_or_default();
let nd = &styled_dom.node_data.as_container()[dom_id];
if matches!(nd.node_type, NodeType::Html) {
styled_dom
.node_hierarchy
.as_container()
.get(dom_id)
.and_then(|node| node.first_child_id(dom_id))
.and_then(|child_id| {
let child_data = &styled_dom.node_data.as_container()[child_id];
if matches!(child_data.node_type, NodeType::Body) {
let child_state = &styled_dom
.styled_nodes
.as_container()[child_id]
.styled_node_state;
Some(get_writing_mode(styled_dom, child_id, child_state)
.unwrap_or_default())
} else {
None
}
})
.unwrap_or(own_wm)
} else {
own_wm
}
};
let direction = get_direction(styled_dom, dom_id, &styled_node_state).unwrap_or_default();
let text_orientation = get_text_orientation(styled_dom, dom_id, &styled_node_state).unwrap_or_default();
let text_align = get_text_align(styled_dom, dom_id, &styled_node_state).unwrap_or_default();
let width = match get_css_width(styled_dom, dom_id, &styled_node_state) {
MultiValue::Exact(w) => Some(w),
_ => None,
};
let height = match get_css_height(styled_dom, dom_id, &styled_node_state) {
MultiValue::Exact(h) => Some(h),
_ => None,
};
let min_width = match get_css_min_width(styled_dom, dom_id, &styled_node_state) {
MultiValue::Exact(v) => Some(v),
_ => None,
};
let min_height = match get_css_min_height(styled_dom, dom_id, &styled_node_state) {
MultiValue::Exact(v) => Some(v),
_ => None,
};
let max_width = match get_css_max_width(styled_dom, dom_id, &styled_node_state) {
MultiValue::Exact(v) => Some(v),
_ => None,
};
let max_height = match get_css_max_height(styled_dom, dom_id, &styled_node_state) {
MultiValue::Exact(v) => Some(v),
_ => None,
};
ComputedLayoutStyle {
display,
position,
float,
overflow_x,
overflow_y,
writing_mode,
direction,
text_orientation,
width,
height,
min_width,
min_height,
max_width,
max_height,
text_align,
}
}
fn get_element_font_size(styled_dom: &StyledDom, dom_id: NodeId) -> f32 {
unsafe { core::ptr::write_volatile(0x400E0 as *mut u32, 0xC3_000001u32); } let node_state = styled_dom
.styled_nodes
.as_container()
.get(dom_id)
.map(|n| &n.styled_node_state)
.cloned()
.unwrap_or_default();
unsafe { core::ptr::write_volatile(0x400E0 as *mut u32, 0xC3_000002u32); }
crate::solver3::getters::get_element_font_size(styled_dom, dom_id, &node_state)
}
fn get_parent_font_size(styled_dom: &StyledDom, dom_id: NodeId) -> f32 {
styled_dom
.node_hierarchy
.as_container()
.get(dom_id)
.and_then(|node| node.parent_id())
.map(|parent_id| get_element_font_size(styled_dom, parent_id))
.unwrap_or(azul_css::props::basic::pixel::DEFAULT_FONT_SIZE)
}
fn get_root_font_size(styled_dom: &StyledDom) -> f32 {
get_element_font_size(styled_dom, NodeId::new(0))
}
fn create_resolution_context(
styled_dom: &StyledDom,
dom_id: NodeId,
containing_block_size: Option<azul_css::props::basic::PhysicalSize>,
viewport_size: LogicalSize,
) -> azul_css::props::basic::ResolutionContext {
unsafe { core::ptr::write_volatile(0x400D8 as *mut u32, 0xC1_000001u32); } let element_font_size = get_element_font_size(styled_dom, dom_id);
unsafe { core::ptr::write_volatile(0x400D8 as *mut u32, 0xC1_000002u32); } let parent_font_size = get_parent_font_size(styled_dom, dom_id);
unsafe { core::ptr::write_volatile(0x400D8 as *mut u32, 0xC1_000003u32); } let root_font_size = get_root_font_size(styled_dom);
unsafe { core::ptr::write_volatile(0x400D8 as *mut u32, 0xC1_000004u32); }
ResolutionContext {
element_font_size,
parent_font_size,
root_font_size,
containing_block_size: containing_block_size.unwrap_or(PhysicalSize::new(0.0, 0.0)),
element_size: None, viewport_size: PhysicalSize::new(viewport_size.width, viewport_size.height),
}
}
struct CollectedBoxProps {
unresolved: crate::solver3::geometry::UnresolvedBoxProps,
resolved: BoxProps,
}
fn collect_box_props(
styled_dom: &StyledDom,
dom_id: NodeId,
debug_messages: &mut Option<Vec<LayoutDebugMessage>>,
viewport_size: LogicalSize,
) -> CollectedBoxProps {
use crate::solver3::geometry::{UnresolvedBoxProps, UnresolvedEdge, UnresolvedMargin};
use crate::solver3::getters::*;
unsafe { core::ptr::write_volatile(0x400D0 as *mut u32, 0xC0_000001u32); }
let node_data = &styled_dom.node_data.as_container()[dom_id];
let node_state = styled_dom
.styled_nodes
.as_container()
.get(dom_id)
.map(|n| &n.styled_node_state)
.cloned()
.unwrap_or_default();
unsafe { core::ptr::write_volatile(0x400D0 as *mut u32, 0xC0_000002u32); }
let context = create_resolution_context(styled_dom, dom_id, None, viewport_size);
unsafe { core::ptr::write_volatile(0x400D0 as *mut u32, 0xC0_000003u32); }
let margin_top_mv = get_css_margin_top(styled_dom, dom_id, &node_state);
unsafe { core::ptr::write_volatile(0x400D0 as *mut u32, 0xC0_000004u32); } let margin_right_mv = get_css_margin_right(styled_dom, dom_id, &node_state);
let margin_bottom_mv = get_css_margin_bottom(styled_dom, dom_id, &node_state);
let margin_left_mv = get_css_margin_left(styled_dom, dom_id, &node_state);
let to_unresolved_margin = |mv: &MultiValue<PixelValue>| -> UnresolvedMargin {
match mv {
MultiValue::Auto => UnresolvedMargin::Auto,
MultiValue::Exact(pv) => UnresolvedMargin::Length(*pv),
_ => UnresolvedMargin::Zero,
}
};
let unresolved_margin = UnresolvedEdge {
top: to_unresolved_margin(&margin_top_mv),
right: to_unresolved_margin(&margin_right_mv),
bottom: to_unresolved_margin(&margin_bottom_mv),
left: to_unresolved_margin(&margin_left_mv),
};
unsafe { core::ptr::write_volatile(0x400D0 as *mut u32, 0xC0_000005u32); }
let padding_top_mv = get_css_padding_top(styled_dom, dom_id, &node_state);
let padding_right_mv = get_css_padding_right(styled_dom, dom_id, &node_state);
let padding_bottom_mv = get_css_padding_bottom(styled_dom, dom_id, &node_state);
let padding_left_mv = get_css_padding_left(styled_dom, dom_id, &node_state);
let to_pixel_value = |mv: MultiValue<PixelValue>| -> PixelValue {
match mv {
MultiValue::Exact(pv) => pv,
_ => PixelValue::const_px(0),
}
};
let unresolved_padding = UnresolvedEdge {
top: to_pixel_value(padding_top_mv),
right: to_pixel_value(padding_right_mv),
bottom: to_pixel_value(padding_bottom_mv),
left: to_pixel_value(padding_left_mv),
};
unsafe { core::ptr::write_volatile(0x400D0 as *mut u32, 0xC0_000056u32); }
let unresolved_padding = match get_display_type(styled_dom, dom_id) {
LayoutDisplay::TableRow
| LayoutDisplay::TableRowGroup
| LayoutDisplay::TableHeaderGroup
| LayoutDisplay::TableFooterGroup
| LayoutDisplay::TableColumn
| LayoutDisplay::TableColumnGroup => UnresolvedEdge {
top: PixelValue::const_px(0),
right: PixelValue::const_px(0),
bottom: PixelValue::const_px(0),
left: PixelValue::const_px(0),
},
_ => unresolved_padding,
};
unsafe { core::ptr::write_volatile(0x400D0 as *mut u32, 0xC0_000006u32); }
let border_top_mv = get_css_border_top_width(styled_dom, dom_id, &node_state);
let border_right_mv = get_css_border_right_width(styled_dom, dom_id, &node_state);
let border_bottom_mv = get_css_border_bottom_width(styled_dom, dom_id, &node_state);
let border_left_mv = get_css_border_left_width(styled_dom, dom_id, &node_state);
use azul_css::props::style::border::BorderStyle;
let style_zeroes_width = |s: BorderStyle| matches!(s, BorderStyle::None | BorderStyle::Hidden);
let (bs_top, bs_right, bs_bottom, bs_left) = {
let cache_ptr = &styled_dom.css_property_cache.ptr;
if node_state.is_normal() {
if let Some(ref cc) = cache_ptr.compact_cache {
let idx = dom_id.index();
(cc.get_border_top_style(idx), cc.get_border_right_style(idx),
cc.get_border_bottom_style(idx), cc.get_border_left_style(idx))
} else {
(
cache_ptr.get_border_top_style(node_data, &dom_id, &node_state)
.and_then(|v| v.get_property()).map(|s| s.inner).unwrap_or(BorderStyle::None),
cache_ptr.get_border_right_style(node_data, &dom_id, &node_state)
.and_then(|v| v.get_property()).map(|s| s.inner).unwrap_or(BorderStyle::None),
cache_ptr.get_border_bottom_style(node_data, &dom_id, &node_state)
.and_then(|v| v.get_property()).map(|s| s.inner).unwrap_or(BorderStyle::None),
cache_ptr.get_border_left_style(node_data, &dom_id, &node_state)
.and_then(|v| v.get_property()).map(|s| s.inner).unwrap_or(BorderStyle::None),
)
}
} else {
(
cache_ptr.get_border_top_style(node_data, &dom_id, &node_state)
.and_then(|v| v.get_property()).map(|s| s.inner).unwrap_or(BorderStyle::None),
cache_ptr.get_border_right_style(node_data, &dom_id, &node_state)
.and_then(|v| v.get_property()).map(|s| s.inner).unwrap_or(BorderStyle::None),
cache_ptr.get_border_bottom_style(node_data, &dom_id, &node_state)
.and_then(|v| v.get_property()).map(|s| s.inner).unwrap_or(BorderStyle::None),
cache_ptr.get_border_left_style(node_data, &dom_id, &node_state)
.and_then(|v| v.get_property()).map(|s| s.inner).unwrap_or(BorderStyle::None),
)
}
};
let unresolved_border = UnresolvedEdge {
top: if style_zeroes_width(bs_top) { PixelValue::const_px(0) } else { to_pixel_value(border_top_mv) },
right: if style_zeroes_width(bs_right) { PixelValue::const_px(0) } else { to_pixel_value(border_right_mv) },
bottom: if style_zeroes_width(bs_bottom) { PixelValue::const_px(0) } else { to_pixel_value(border_bottom_mv) },
left: if style_zeroes_width(bs_left) { PixelValue::const_px(0) } else { to_pixel_value(border_left_mv) },
};
unsafe { core::ptr::write_volatile(0x400D0 as *mut u32, 0xC0_000007u32); }
let display_type = get_display_type(styled_dom, dom_id);
let unresolved_margin = match display_type {
LayoutDisplay::TableRow
| LayoutDisplay::TableRowGroup
| LayoutDisplay::TableHeaderGroup
| LayoutDisplay::TableFooterGroup
| LayoutDisplay::TableCell
| LayoutDisplay::TableColumn
| LayoutDisplay::TableColumnGroup => UnresolvedEdge {
top: UnresolvedMargin::Zero,
right: UnresolvedMargin::Zero,
bottom: UnresolvedMargin::Zero,
left: UnresolvedMargin::Zero,
},
LayoutDisplay::Inline => {
let is_replaced = matches!(
node_data.get_node_type(),
NodeType::Image(_) | NodeType::VirtualView
);
if is_replaced {
unresolved_margin
} else {
UnresolvedEdge {
top: UnresolvedMargin::Zero,
bottom: UnresolvedMargin::Zero,
..unresolved_margin
}
}
},
_ => unresolved_margin,
};
let unresolved = UnresolvedBoxProps {
margin: unresolved_margin,
padding: unresolved_padding,
border: unresolved_border,
};
let params = crate::solver3::geometry::ResolutionParams {
containing_block: viewport_size,
viewport_size,
element_font_size: context.parent_font_size,
root_font_size: context.root_font_size,
};
let resolved = unresolved.resolve(¶ms);
if let Some(msgs) = debug_messages.as_mut() {
msgs.push(LayoutDebugMessage::box_props(format!(
"[BOX] node[{}] {:?} pad=[{:.1} {:.1} {:.1} {:.1}] mar=[{:.1} {:.1} {:.1} {:.1}] bor=[{:.1} {:.1} {:.1} {:.1}]",
dom_id.index(), node_data.node_type,
resolved.padding.top, resolved.padding.right, resolved.padding.bottom, resolved.padding.left,
resolved.margin.top, resolved.margin.right, resolved.margin.bottom, resolved.margin.left,
resolved.border.top, resolved.border.right, resolved.border.bottom, resolved.border.left,
)));
}
if let Some(msgs) = debug_messages.as_mut() {
let has_vh = match &unresolved_margin.top {
UnresolvedMargin::Length(pv) => pv.metric == azul_css::props::basic::SizeMetric::Vh,
_ => false,
};
if has_vh || resolved.margin.top > 0.0 || resolved.margin.left > 0.0 {
msgs.push(LayoutDebugMessage::box_props(format!(
"NodeId {:?} ({:?}): unresolved_margin_top={:?}, resolved_margin_top={:.2}, viewport_size={:?}",
dom_id, node_data.node_type,
unresolved_margin.top,
resolved.margin.top,
viewport_size
)));
}
}
if let Some(msgs) = debug_messages.as_mut() {
msgs.push(LayoutDebugMessage::box_props(format!(
"NodeId {:?} ({:?}): margin_auto: left={}, right={}, top={}, bottom={} | margin_left={:?}",
dom_id, node_data.node_type,
resolved.margin_auto.left, resolved.margin_auto.right,
resolved.margin_auto.top, resolved.margin_auto.bottom,
unresolved_margin.left
)));
}
if matches!(node_data.node_type, azul_core::dom::NodeType::Body) {
if let Some(msgs) = debug_messages.as_mut() {
msgs.push(LayoutDebugMessage::box_props(format!(
"Body margin resolved: top={:.2}, right={:.2}, bottom={:.2}, left={:.2}",
resolved.margin.top, resolved.margin.right,
resolved.margin.bottom, resolved.margin.left
)));
}
}
CollectedBoxProps { unresolved, resolved }
}
pub fn is_whitespace_only_text(styled_dom: &StyledDom, node_id: NodeId) -> bool {
let binding = styled_dom.node_data.as_container();
let node_data = binding.get(node_id);
if let Some(data) = node_data {
if let NodeType::Text(text) = data.get_node_type() {
if !text.chars().all(|c| matches!(c, ' ' | '\t' | '\n' | '\r' | '\x0C')) {
return false;
}
let white_space = styled_dom
.styled_nodes
.as_container()
.get(node_id)
.map(|n| {
match get_white_space_property(styled_dom, node_id, &n.styled_node_state) {
MultiValue::Exact(ws) => ws,
_ => StyleWhiteSpace::Normal,
}
})
.unwrap_or(StyleWhiteSpace::Normal);
return match white_space {
StyleWhiteSpace::Normal | StyleWhiteSpace::Nowrap | StyleWhiteSpace::PreLine => true,
StyleWhiteSpace::Pre | StyleWhiteSpace::PreWrap | StyleWhiteSpace::BreakSpaces => false,
};
}
}
false
}
fn should_skip_for_table_structure(
styled_dom: &StyledDom,
node_id: NodeId,
parent_display: LayoutDisplay,
) -> bool {
matches!(
parent_display,
LayoutDisplay::Table
| LayoutDisplay::InlineTable
| LayoutDisplay::TableRowGroup
| LayoutDisplay::TableHeaderGroup
| LayoutDisplay::TableFooterGroup
| LayoutDisplay::TableRow
) && is_whitespace_only_text(styled_dom, node_id)
}
fn is_proper_table_child(display: LayoutDisplay) -> bool {
matches!(
display,
LayoutDisplay::TableRowGroup
| LayoutDisplay::TableHeaderGroup
| LayoutDisplay::TableFooterGroup
| LayoutDisplay::TableRow
| LayoutDisplay::TableColumnGroup
| LayoutDisplay::TableColumn
| LayoutDisplay::TableCaption
)
}
pub fn get_display_type(styled_dom: &StyledDom, node_id: NodeId) -> LayoutDisplay {
use crate::solver3::getters::get_display_property;
get_display_property(styled_dom, Some(node_id)).unwrap_or(LayoutDisplay::Inline)
}
fn blockify_flex_item_if_table_internal(nodes: &mut Vec<LayoutNode>, node_idx: usize) {
if let Some(node) = nodes.get_mut(node_idx) {
let is_table_internal = matches!(
node.formatting_context,
FormattingContext::TableCell
| FormattingContext::TableRow
| FormattingContext::TableRowGroup
| FormattingContext::TableColumnGroup
| FormattingContext::TableCaption
| FormattingContext::Table
);
if is_table_internal {
node.formatting_context = FormattingContext::Block {
establishes_new_context: true,
};
}
}
}
fn is_replaced_element(node_data: &NodeData) -> bool {
matches!(
node_data.get_node_type(),
NodeType::Image(_)
| NodeType::VirtualView
| NodeType::Br
| NodeType::Wbr
| NodeType::Meter
| NodeType::Progress
| NodeType::Canvas
| NodeType::Embed
| NodeType::Object
| NodeType::Audio
| NodeType::Video
| NodeType::Input
| NodeType::TextArea
| NodeType::Select
)
}
fn establishes_new_block_formatting_context(styled_dom: &StyledDom, node_id: NodeId) -> bool {
let display = get_display_type(styled_dom, node_id);
if matches!(
display,
LayoutDisplay::InlineBlock | LayoutDisplay::TableCell | LayoutDisplay::TableCaption | LayoutDisplay::FlowRoot
) {
return true;
}
if let Some(styled_node) = styled_dom.styled_nodes.as_container().get(node_id) {
let overflow_x = get_overflow_x(styled_dom, node_id, &styled_node.styled_node_state);
if !overflow_x.is_visible_or_clip() {
return true;
}
let overflow_y = get_overflow_y(styled_dom, node_id, &styled_node.styled_node_state);
if !overflow_y.is_visible_or_clip() {
return true;
}
let position = get_position(styled_dom, node_id, &styled_node.styled_node_state);
if position.is_absolute_or_fixed() {
return true;
}
let float = get_float(styled_dom, node_id, &styled_node.styled_node_state);
if !float.is_none() {
return true;
}
}
if let Some(styled_node) = styled_dom.styled_nodes.as_container().get(node_id) {
let hierarchy = styled_dom.node_hierarchy.as_container();
if let Some(parent_dom_id) = hierarchy[node_id].parent_id() {
let parent_state = &styled_dom.styled_nodes.as_container()[parent_dom_id].styled_node_state;
let child_wm = get_writing_mode(styled_dom, node_id, &styled_node.styled_node_state).unwrap_or_default();
let parent_wm = get_writing_mode(styled_dom, parent_dom_id, parent_state).unwrap_or_default();
if child_wm != parent_wm {
return true;
}
}
}
let node_data = &styled_dom.node_data.as_container()[node_id];
if is_replaced_element(node_data) {
return true;
}
if styled_dom.root.into_crate_internal() == Some(node_id) {
return true;
}
false
}
fn determine_formatting_context_for_display(
styled_dom: &StyledDom,
node_id: NodeId,
display_type: LayoutDisplay,
) -> FormattingContext {
let node_data = &styled_dom.node_data.as_container()[node_id];
if matches!(node_data.get_node_type(), NodeType::Text(_)) {
return FormattingContext::Inline;
}
match display_type {
LayoutDisplay::Inline => FormattingContext::Inline,
LayoutDisplay::FlowRoot => FormattingContext::Block {
establishes_new_context: true,
},
LayoutDisplay::Block | LayoutDisplay::ListItem => {
if has_only_inline_children(styled_dom, node_id) {
FormattingContext::Inline
} else {
FormattingContext::Block {
establishes_new_context: establishes_new_block_formatting_context(
styled_dom, node_id,
),
}
}
}
LayoutDisplay::InlineBlock => FormattingContext::InlineBlock,
LayoutDisplay::Table | LayoutDisplay::InlineTable => FormattingContext::Table,
LayoutDisplay::TableRowGroup
| LayoutDisplay::TableHeaderGroup
| LayoutDisplay::TableFooterGroup => FormattingContext::TableRowGroup,
LayoutDisplay::TableRow => FormattingContext::TableRow,
LayoutDisplay::TableCell => FormattingContext::TableCell,
LayoutDisplay::None => FormattingContext::None,
LayoutDisplay::Flex | LayoutDisplay::InlineFlex => FormattingContext::Flex,
LayoutDisplay::TableColumnGroup => FormattingContext::TableColumnGroup,
LayoutDisplay::TableCaption => FormattingContext::TableCaption,
LayoutDisplay::Grid | LayoutDisplay::InlineGrid => FormattingContext::Grid,
LayoutDisplay::TableColumn => FormattingContext::None,
LayoutDisplay::Contents => FormattingContext::Contents,
LayoutDisplay::RunIn | LayoutDisplay::Marker => {
FormattingContext::Block {
establishes_new_context: true,
}
}
}
}
fn determine_formatting_context(styled_dom: &StyledDom, node_id: NodeId) -> FormattingContext {
let node_data = &styled_dom.node_data.as_container()[node_id];
if matches!(node_data.get_node_type(), NodeType::Text(_)) {
return FormattingContext::Inline;
}
let display_type = get_display_type(styled_dom, node_id);
determine_formatting_context_for_display(styled_dom, node_id, display_type)
}