use crate::node::{ImageData, NodeData, SpecialElementData};
use crate::{document::BaseDocument, dom_node_id, node::Node, taffy_node_id};
use markup5ever::{LocalName, local_name};
use std::cell::Ref;
use std::sync::Arc;
use style::Atom;
use style::values::computed::CSSPixelLength;
use style::values::computed::length_percentage::CalcLengthPercentage;
use taffy::{
BlockContext, FlexDirection, LayoutPartialTree, NodeId, ResolveOrZero, RoundTree, Style,
TraversePartialTree, TraverseTree, compute_block_layout, compute_cached_layout,
compute_flexbox_layout, compute_grid_layout, compute_leaf_layout, prelude::*,
};
pub(crate) mod construct;
pub(crate) mod damage;
pub(crate) mod inline;
pub(crate) mod list;
pub(crate) mod replaced;
pub(crate) mod table;
use self::replaced::{
IntrinsicSizes, ReplacedContext, compute_replaced_layout, is_replaced_element,
};
use self::table::TableTreeWrapper;
const DEFAULT_OBJECT_SIZE: taffy::Size<f32> = taffy::Size {
width: 300.0,
height: 150.0,
};
fn tag_intrinsic_sizes(
tag_name: &LocalName,
attr_size: taffy::Size<Option<f32>>,
) -> (IntrinsicSizes, taffy::Size<f32>) {
if *tag_name == local_name!("img") || *tag_name == local_name!("svg") {
return (IntrinsicSizes::default(), taffy::Size::ZERO);
}
if *tag_name == local_name!("canvas") {
let width = attr_size.width.unwrap_or(300.0);
let height = attr_size.height.unwrap_or(150.0);
return (
IntrinsicSizes {
width: Some(width),
height: Some(height),
ratio: Some(width / height),
},
DEFAULT_OBJECT_SIZE,
);
}
(IntrinsicSizes::default(), DEFAULT_OBJECT_SIZE)
}
pub(crate) fn resolve_calc_value(calc_ptr: *const (), parent_size: f32) -> f32 {
let calc = unsafe { &*(calc_ptr as *const CalcLengthPercentage) };
let result = calc.resolve(CSSPixelLength::new(parent_size));
result.px()
}
impl BaseDocument {
fn node_from_id(&self, node_id: taffy::prelude::NodeId) -> &Node {
&self.nodes[dom_node_id(node_id)]
}
fn node_from_id_mut(&mut self, node_id: taffy::prelude::NodeId) -> &mut Node {
&mut self.nodes[dom_node_id(node_id)]
}
}
impl BaseDocument {
fn compute_child_layout_internal(
&mut self,
node_id: NodeId,
inputs: taffy::tree::LayoutInput,
block_ctx: Option<&mut BlockContext<'_>>,
) -> taffy::tree::LayoutOutput {
let node = &mut self.nodes[dom_node_id(node_id)];
let font_styles = node.primary_styles().map(|style| {
use style::values::computed::font::LineHeight;
let font_size = style.clone_font_size().used_size().px();
let line_height = match style.clone_line_height() {
LineHeight::Normal => font_size * 1.2,
LineHeight::Number(num) => font_size * num.0,
LineHeight::Length(value) => value.0.px(),
};
(font_size, line_height)
});
let font_size = font_styles.map(|s| s.0);
let resolved_line_height = font_styles.map(|s| s.1);
match &mut node.data {
NodeData::Text(data) => {
#[cfg(feature = "tracing")]
tracing::error!(
node_id = ?dom_node_id(node_id),
data = ?data,
"Tried to lay out text node individually",
);
#[cfg(not(feature = "tracing"))]
let _ = data;
taffy::LayoutOutput::HIDDEN
}
NodeData::Element(element_data) | NodeData::AnonymousBlock(element_data) => {
if *element_data.name.local == *"textarea" {
let rows = element_data
.attr(local_name!("rows"))
.and_then(|val| val.parse::<f32>().ok())
.unwrap_or(2.0);
let cols = element_data
.attr(local_name!("cols"))
.and_then(|val| val.parse::<f32>().ok());
return compute_leaf_layout(
inputs,
node.style(),
resolve_calc_value,
|_known_size, _available_space| taffy::Size {
width: cols
.map(|cols| cols * font_size.unwrap_or(16.0) * 0.6)
.unwrap_or(300.0),
height: resolved_line_height.unwrap_or(16.0) * rows,
},
);
}
if *element_data.name.local == *"input" {
match element_data.attr(local_name!("type")) {
Some("hidden") => {
node.style_mut().display = Display::None;
return taffy::LayoutOutput::HIDDEN;
}
Some("checkbox") => {
return compute_leaf_layout(
inputs,
node.style(),
resolve_calc_value,
|_known_size, _available_space| {
let width = node.style().size.width.resolve_or_zero(
inputs.parent_size.width,
resolve_calc_value,
);
let height = node.style().size.height.resolve_or_zero(
inputs.parent_size.height,
resolve_calc_value,
);
let min_size = width.min(height);
taffy::Size {
width: min_size,
height: min_size,
}
},
);
}
None | Some("text" | "password" | "email" | "tel" | "url" | "search") => {
return compute_leaf_layout(
inputs,
node.style(),
resolve_calc_value,
|_known_size, _available_space| taffy::Size {
width: match inputs.available_space.width {
AvailableSpace::Definite(limit) => limit.min(300.0),
AvailableSpace::MinContent => 0.0,
AvailableSpace::MaxContent => 300.0,
},
height: resolved_line_height.unwrap_or(16.0),
},
);
}
_ => {}
}
}
if is_replaced_element(&element_data.name.local) {
let attr_size = taffy::Size {
width: element_data
.attr(local_name!("width"))
.and_then(|val| val.parse::<f32>().ok()),
height: element_data
.attr(local_name!("height"))
.and_then(|val| val.parse::<f32>().ok()),
};
let (intrinsic_sizes, default_object_size) = match &element_data.special_data {
SpecialElementData::Image(image_data) => match &**image_data {
ImageData::Raster(image) => {
let (width, height) = (image.width as f32, image.height as f32);
(
IntrinsicSizes {
width: Some(width),
height: Some(height),
ratio: Some(width / height),
},
DEFAULT_OBJECT_SIZE,
)
}
#[cfg(feature = "svg")]
ImageData::Svg(svg) => {
let mut width = svg.intrinsic_width();
let mut height = svg.intrinsic_height();
if width.is_none()
&& height.is_none()
&& svg.viewbox_aspect_ratio().is_none()
{
let size = svg.tree.size();
width = Some(size.width());
height = Some(size.height());
}
(
IntrinsicSizes {
width,
height,
ratio: Some(svg.aspect_ratio()),
},
DEFAULT_OBJECT_SIZE,
)
}
ImageData::None => (IntrinsicSizes::default(), taffy::Size::ZERO),
},
SpecialElementData::Canvas(_)
| SpecialElementData::SubDocument(_)
| SpecialElementData::None => {
tag_intrinsic_sizes(&element_data.name.local, attr_size)
}
#[cfg(feature = "custom-widget")]
SpecialElementData::CustomWidget(widget_data) => {
let (fallback, default_object_size) =
tag_intrinsic_sizes(&element_data.name.local, attr_size);
let attr_intrinsic =
if *element_data.name.local == local_name!("canvas") {
attr_size
} else {
taffy::Size::NONE
};
let attr_ratio = match (attr_intrinsic.width, attr_intrinsic.height) {
(Some(w), Some(h)) => Some(w / h),
_ => None,
};
let widget_sizes = widget_data.widget.intrinsic_sizes();
(
IntrinsicSizes {
width: attr_intrinsic
.width
.or(widget_sizes.width)
.or(fallback.width),
height: attr_intrinsic
.height
.or(widget_sizes.height)
.or(fallback.height),
ratio: attr_ratio.or(widget_sizes.ratio).or(fallback.ratio),
},
default_object_size,
)
}
_ => unreachable!(),
};
let replaced_context = ReplacedContext {
intrinsic_sizes,
default_object_size,
};
return compute_replaced_layout(
inputs,
node.style(),
resolve_calc_value,
&replaced_context,
);
}
if node.flags.is_table_root() {
let SpecialElementData::TableRoot(context) = &self.nodes[dom_node_id(node_id)]
.data
.downcast_element()
.unwrap()
.special_data
else {
panic!("Node marked as table root but doesn't have TableContext");
};
let context = Arc::clone(context);
let mut table_wrapper = TableTreeWrapper {
doc: self,
ctx: context,
};
let mut output = compute_grid_layout(&mut table_wrapper, node_id, inputs);
output.scrollable_overflow_rect.left = 0.0;
output.scrollable_overflow_rect.top = 0.0;
output.scrollable_overflow_rect.right =
output.scrollable_overflow_rect.right.min(output.size.width);
output.scrollable_overflow_rect.bottom = output
.scrollable_overflow_rect
.bottom
.min(output.size.height);
return output;
}
if node.flags.is_inline_root() {
return self.compute_inline_layout(dom_node_id(node_id), inputs, block_ctx);
}
match node.style().display {
Display::Block => compute_block_layout(self, node_id, inputs, block_ctx),
Display::FlowRoot => compute_block_layout(self, node_id, inputs, None),
Display::Flex => compute_flexbox_layout(self, node_id, inputs),
Display::Grid => compute_grid_layout(self, node_id, inputs),
Display::None => taffy::LayoutOutput::HIDDEN,
}
}
NodeData::Document(_) => compute_block_layout(self, node_id, inputs, None),
_ => taffy::LayoutOutput::HIDDEN,
}
}
}
impl TraversePartialTree for BaseDocument {
type ChildIter<'a> = RefCellChildIter<'a>;
fn child_ids(&self, node_id: NodeId) -> Self::ChildIter<'_> {
let layout_children = self.node_from_id(node_id).layout_children.borrow(); RefCellChildIter::new(Ref::map(layout_children, |children| {
children.as_ref().map(|c| c.as_slice()).unwrap_or(&[])
}))
}
fn child_count(&self, node_id: NodeId) -> usize {
self.node_from_id(node_id)
.layout_children
.borrow()
.as_ref()
.map(|c| c.len())
.unwrap_or(0)
}
fn get_child_id(&self, node_id: NodeId, index: usize) -> NodeId {
taffy_node_id(
self.node_from_id(node_id)
.layout_children
.borrow()
.as_ref()
.unwrap()[index],
)
}
}
impl TraverseTree for BaseDocument {}
impl LayoutPartialTree for BaseDocument {
type CoreContainerStyle<'a>
= &'a taffy::Style<Atom>
where
Self: 'a;
type CustomIdent = Atom;
fn get_core_container_style(&self, node_id: NodeId) -> &Style<Atom> {
self.node_from_id(node_id).style()
}
fn set_unrounded_layout(&mut self, node_id: NodeId, layout: &Layout) {
*self.node_from_id_mut(node_id).unrounded_layout_mut() = *layout;
}
fn resolve_calc_value(&self, calc_ptr: *const (), parent_size: f32) -> f32 {
resolve_calc_value(calc_ptr, parent_size)
}
#[inline(always)]
fn compute_child_layout(
&mut self,
node_id: NodeId,
inputs: taffy::LayoutInput,
) -> taffy::LayoutOutput {
compute_cached_layout(self, node_id, inputs, |tree, node_id, inputs| {
tree.compute_child_layout_internal(node_id, inputs, None)
})
}
}
impl taffy::CacheTree for BaseDocument {
#[inline]
fn cache_get(
&mut self,
node_id: NodeId,
inputs: &taffy::LayoutInput,
) -> Option<taffy::LayoutOutput> {
self.node_from_id_mut(node_id).cache_mut().get(inputs)
}
#[inline]
fn cache_store(
&mut self,
node_id: NodeId,
inputs: &taffy::LayoutInput,
layout_output: taffy::LayoutOutput,
) {
self.node_from_id_mut(node_id)
.cache_mut()
.store(inputs, layout_output);
}
#[inline]
fn cache_clear(&mut self, node_id: NodeId) {
self.node_from_id_mut(node_id).cache_mut().clear();
}
}
impl taffy::LayoutBlockContainer for BaseDocument {
type BlockContainerStyle<'a>
= &'a Style<Atom>
where
Self: 'a;
type BlockItemStyle<'a>
= &'a Style<Atom>
where
Self: 'a;
fn get_block_container_style(&self, node_id: NodeId) -> Self::BlockContainerStyle<'_> {
self.get_core_container_style(node_id)
}
fn get_block_child_style(&self, child_node_id: NodeId) -> Self::BlockItemStyle<'_> {
self.get_core_container_style(child_node_id)
}
#[inline(always)]
fn compute_block_child_layout(
&mut self,
node_id: NodeId,
inputs: taffy::LayoutInput,
block_ctx: Option<&mut BlockContext<'_>>,
) -> taffy::LayoutOutput {
compute_cached_layout(self, node_id, inputs, |tree, node_id, inputs| {
tree.compute_child_layout_internal(node_id, inputs, block_ctx)
})
}
}
impl taffy::LayoutFlexboxContainer for BaseDocument {
type FlexboxContainerStyle<'a>
= &'a Style<Atom>
where
Self: 'a;
type FlexboxItemStyle<'a>
= &'a Style<Atom>
where
Self: 'a;
fn get_flexbox_container_style(&self, node_id: NodeId) -> Self::FlexboxContainerStyle<'_> {
self.get_core_container_style(node_id)
}
fn get_flexbox_child_style(&self, child_node_id: NodeId) -> Self::FlexboxItemStyle<'_> {
self.get_core_container_style(child_node_id)
}
}
impl taffy::LayoutGridContainer for BaseDocument {
type GridContainerStyle<'a>
= &'a Style<Atom>
where
Self: 'a;
type GridItemStyle<'a>
= &'a Style<Atom>
where
Self: 'a;
fn get_grid_container_style(&self, node_id: NodeId) -> Self::GridContainerStyle<'_> {
self.get_core_container_style(node_id)
}
fn get_grid_child_style(&self, child_node_id: NodeId) -> Self::GridItemStyle<'_> {
self.get_core_container_style(child_node_id)
}
fn set_detailed_grid_info(
&mut self,
node_id: NodeId,
detailed_grid_info: taffy::DetailedGridInfo<Atom>,
) {
let node = self.node_from_id_mut(node_id);
if let Some(element) = node.element_data_mut() {
element.detailed_grid_info = Some(Box::new(detailed_grid_info));
}
}
}
impl RoundTree for BaseDocument {
fn get_unrounded_layout(&self, node_id: NodeId) -> Layout {
*self.node_from_id(node_id).unrounded_layout()
}
fn set_final_layout(&mut self, node_id: NodeId, layout: &Layout) {
*self.node_from_id_mut(node_id).final_layout_mut() = *layout;
}
}
impl PrintTree for BaseDocument {
fn get_debug_label(&self, node_id: NodeId) -> &'static str {
let node = &self.node_from_id(node_id);
match node.data {
NodeData::Document(_) => "DOCUMENT",
NodeData::Text { .. } => node.node_debug_str().leak(),
NodeData::Comment { .. } => "COMMENT",
NodeData::AnonymousBlock(_) => "ANONYMOUS BLOCK",
NodeData::Element(_) => {
let style = node.style();
let display = match style.display {
Display::Flex => match style.flex_direction {
FlexDirection::Row | FlexDirection::RowReverse => "FLEX ROW",
FlexDirection::Column | FlexDirection::ColumnReverse => "FLEX COL",
},
Display::Grid => "GRID",
Display::Block => "BLOCK",
Display::FlowRoot => "FLOW ROOT",
Display::None => "NONE",
};
format!("{} ({})", node.node_debug_str(), display).leak()
} }
}
fn get_final_layout(&self, node_id: NodeId) -> Layout {
*self.node_from_id(node_id).final_layout()
}
}
pub struct RefCellChildIter<'a> {
items: Ref<'a, [crate::NodeId]>,
idx: usize,
}
impl<'a> RefCellChildIter<'a> {
fn new(items: Ref<'a, [crate::NodeId]>) -> RefCellChildIter<'a> {
RefCellChildIter { items, idx: 0 }
}
}
impl Iterator for RefCellChildIter<'_> {
type Item = NodeId;
fn next(&mut self) -> Option<Self::Item> {
self.items.get(self.idx).map(|id| {
self.idx += 1;
taffy_node_id(*id)
})
}
}