Skip to content

Commit

Permalink
web/core: Implement architecture for replaced elements
Browse files Browse the repository at this point in the history
The exact positioning for replaced elements is almost certainly still
flawed, but this commit is large enough as it is. It mainly introduces
the concepts of "Independent formatting contexts" and "replaced
elements". (An independent formatting context is either a replaced
element or a normal formatting context)
  • Loading branch information
simonwuelker committed Dec 18, 2023
1 parent f53d0f5 commit a417de5
Show file tree
Hide file tree
Showing 9 changed files with 486 additions and 63 deletions.
4 changes: 2 additions & 2 deletions crates/web/core/src/css/layout/box_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use crate::{
TreeDebug, TreeFormatter,
};

use super::flow::{BlockFormattingContext, BoxTreeBuilder, InFlowBlockBox};
use super::flow::{BlockContainerBuilder, BlockFormattingContext, InFlowBlockBox};

#[derive(Clone)]
pub struct BoxTree {
Expand All @@ -40,7 +40,7 @@ impl BoxTree {
let element_style =
style_computer.get_computed_style(html.clone().upcast(), &ComputedStyle::default());

let contents = BoxTreeBuilder::build(
let contents = BlockContainerBuilder::build(
DomPtr::clone(&html).upcast(),
style_computer,
&element_style,
Expand Down
99 changes: 95 additions & 4 deletions crates/web/core/src/css/layout/flow/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::{
css::{
font_metrics::DEFAULT_FONT_SIZE,
fragment_tree::{BoxFragment, Fragment},
layout::{ContainingBlock, Pixels, Sides},
layout::{replaced::ReplacedElement, ContainingBlock, Pixels, Sides},
values::{self, length, AutoOr, Length, PercentageOr},
ComputedStyle, StyleComputer,
},
Expand All @@ -15,7 +15,7 @@ use crate::{
};

use super::{
positioning::AbsolutelyPositionedBox, BoxTreeBuilder, FloatContext, FloatingBox,
positioning::AbsolutelyPositionedBox, BlockContainerBuilder, FloatContext, FloatingBox,
InlineFormattingContext,
};

Expand Down Expand Up @@ -65,7 +65,7 @@ impl BlockFormattingContext {
element_style: ComputedStyle,
style_computer: StyleComputer<'_>,
) -> Self {
let contents = BoxTreeBuilder::build(
let contents = BlockContainerBuilder::build(
DomPtr::clone(&element).upcast(),
style_computer,
&element_style,
Expand Down Expand Up @@ -100,11 +100,13 @@ impl BlockFormattingContext {
/// A Box that participates in a [BlockFormattingContext]
/// <https://drafts.csswg.org/css2/#block-level-boxes>
#[derive(Clone)]
pub enum BlockLevelBox {
pub(crate) enum BlockLevelBox {
Floating(FloatingBox),
InFlow(InFlowBlockBox),
AbsolutelyPositioned(AbsolutelyPositionedBox),
Replaced(ReplacedElement),
}

#[derive(Clone)]
pub struct InFlowBlockBox {
style: ComputedStyle,
Expand Down Expand Up @@ -263,6 +265,12 @@ impl From<AbsolutelyPositionedBox> for BlockLevelBox {
}
}

impl From<ReplacedElement> for BlockLevelBox {
fn from(value: ReplacedElement) -> Self {
Self::Replaced(value)
}
}

#[derive(Clone, Debug)]
pub(crate) struct ContentLayoutInfo {
pub height: Pixels,
Expand Down Expand Up @@ -404,6 +412,9 @@ impl<'box_tree, 'formatting_context> BlockFlowState<'box_tree, 'formatting_conte
index: self.fragments_so_far.len(),
});
},
BlockLevelBox::Replaced(replaced_element) => {
self.layout_block_level_replaced_element(replaced_element);
},
}
}

Expand All @@ -427,6 +438,82 @@ impl<'box_tree, 'formatting_context> BlockFlowState<'box_tree, 'formatting_conte
has_in_flow_content: self.has_in_flow_content,
}
}

fn layout_block_level_replaced_element(&mut self, replaced_element: &ReplacedElement) {
let element_style = replaced_element.style();
self.respect_clearance(element_style.clear());

let content_size =
replaced_element.used_size_if_it_was_inline(self.containing_block, self.ctx);

let available_width = Length::pixels(self.containing_block.width());
let resolve_margin = |margin: &values::Margin| {
margin
.map(|p| p.resolve_against(available_width))
.as_ref()
.map(|length| length.absolutize(self.ctx))
};

// Choose horizontal margins such that the total width of the element is equal to the available space.
// This is similar to https://drafts.csswg.org/css2/#blockwidth, except padding and borders are always zero
let horizontal_margin_space = self.containing_block.width() - content_size.width;
let mut margin_left = resolve_margin(element_style.margin_left());
let mut margin_right = resolve_margin(element_style.margin_right());

if content_size.width + margin_left.unwrap_or_default() + margin_right.unwrap_or_default()
> self.containing_block.width()
{
margin_left = AutoOr::NotAuto(margin_left.unwrap_or_default());
margin_right = AutoOr::NotAuto(margin_right.unwrap_or_default());
}

let (margin_left, margin_right) = match (margin_left, margin_right) {
(AutoOr::Auto, AutoOr::Auto) => {
let margin = horizontal_margin_space / 2.;
(margin, margin)
},
(AutoOr::NotAuto(margin_left), AutoOr::Auto) => {
(margin_left, horizontal_margin_space - margin_left)
},
(AutoOr::Auto, AutoOr::NotAuto(margin_right)) => {
(horizontal_margin_space - margin_right, margin_right)
},
(AutoOr::NotAuto(margin_left), AutoOr::NotAuto(_)) => {
// Overconstrained case
(margin_left, horizontal_margin_space - margin_left)
},
};

let mut margins = Sides {
top: resolve_margin(element_style.margin_top()).unwrap_or_default(),
right: margin_right,
bottom: resolve_margin(element_style.margin_bottom()).unwrap_or_default(),
left: margin_left,
};

// Perform margin-collapse
margins.top = self
.block_formatting_context
.get_collapsed_margin(margins.top);

if content_size.height != Pixels::ZERO {
self.block_formatting_context.prevent_margin_collapse();
}

margins.bottom = self
.block_formatting_context
.get_collapsed_margin(margins.bottom);

// Advance the flow state
self.cursor.y += margins.vertical_sum() + content_size.height;

// Create a fragment for at the calculated position
let content_position = Vec2D::new(margins.top, margins.right);
let fragment = replaced_element
.content()
.create_fragment(content_position, content_size);
self.fragments_so_far.push(fragment);
}
}

#[derive(Clone, Copy, Debug)]
Expand Down Expand Up @@ -614,6 +701,10 @@ impl TreeDebug for BlockLevelBox {
Self::Floating(float_box) => float_box.tree_fmt(formatter),
Self::AbsolutelyPositioned(abs_box) => abs_box.tree_fmt(formatter),
Self::InFlow(block_box) => block_box.tree_fmt(formatter),
Self::Replaced(_) => {
formatter.indent()?;
write!(formatter, "Replaced Element")
},
}
}
}
Expand Down
73 changes: 50 additions & 23 deletions crates/web/core/src/css/layout/flow/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@
use crate::{
css::{
layout::flow::{
BlockContainer, BlockLevelBox, InFlowBlockBox, InlineBox, InlineFormattingContext,
InlineLevelBox,
layout::{
flow::{
BlockContainer, BlockLevelBox, InFlowBlockBox, InlineBox, InlineFormattingContext,
InlineLevelBox,
},
formatting_context::IndependentFormattingContext,
replaced::ReplacedElement,
},
ComputedStyle, StyleComputer,
},
Expand All @@ -17,15 +21,15 @@ use crate::{
use super::{float, positioning::AbsolutelyPositionedBox, TextRun};

#[derive(Clone)]
pub struct BoxTreeBuilder<'stylesheets, 'parent_style> {
pub struct BlockContainerBuilder<'stylesheets, 'parent_style> {
style_computer: StyleComputer<'stylesheets>,
style: &'parent_style ComputedStyle,
block_level_boxes: Vec<BlockLevelBox>,
current_inline_formatting_context: InlineFormattingContext,
inline_stack: Vec<InlineBox>,
}

impl<'stylesheets, 'parent_style> BoxTreeBuilder<'stylesheets, 'parent_style> {
impl<'stylesheets, 'parent_style> BlockContainerBuilder<'stylesheets, 'parent_style> {
pub fn build(
node: DomPtr<dom_objects::Node>,
style_computer: StyleComputer<'stylesheets>,
Expand Down Expand Up @@ -58,7 +62,7 @@ impl<'stylesheets, 'parent_style> BoxTreeBuilder<'stylesheets, 'parent_style> {
if let Some(element) = child.try_into_type::<dom_objects::Element>() {
let computed_style = self
.style_computer
.get_computed_style(element, parent_style);
.get_computed_style(element.clone(), parent_style);

if computed_style.display().is_none() {
continue;
Expand All @@ -67,7 +71,7 @@ impl<'stylesheets, 'parent_style> BoxTreeBuilder<'stylesheets, 'parent_style> {
if computed_style.display().is_inline() {
self.push_inline_box(child.clone(), computed_style);
} else {
self.push_block_box(child.clone(), computed_style);
self.push_block_box(element.clone(), computed_style);
}
} else if let Some(text) = child.try_into_type::<dom_objects::Text>() {
// Content that would later be collapsed away according to the white-space property
Expand Down Expand Up @@ -130,8 +134,7 @@ impl<'stylesheets, 'parent_style> BoxTreeBuilder<'stylesheets, 'parent_style> {
.push(populated_inline_box);
}
}

fn push_block_box(&mut self, node: DomPtr<dom_objects::Node>, style: ComputedStyle) {
fn push_block_box(&mut self, element: DomPtr<dom_objects::Element>, style: ComputedStyle) {
// Split all currently open inline boxes around the block box
if !self.inline_stack.is_empty() {
// Split each inline box - these will end up on the "right side" of the block box
Expand Down Expand Up @@ -159,24 +162,48 @@ impl<'stylesheets, 'parent_style> BoxTreeBuilder<'stylesheets, 'parent_style> {
}

// Push the actual box
let content = BoxTreeBuilder::build(node.clone(), self.style_computer, &style);

let position = style.position();
let block_box = match style.float().side() {
Some(side) => float::FloatingBox::new(node, style, side, content).into(),
None => {
if position.is_absolute() || position.is_fixed() {
AbsolutelyPositionedBox {
style,
node,
content,
}
.into()
let is_absolutely_positioned =
style.position().is_absolute() || style.position().is_fixed();

let block_box = match (style.float().side(), is_absolutely_positioned) {
(Some(side), _) => {
let content = IndependentFormattingContext::create(
element.clone(),
self.style_computer,
style.clone(),
);
float::FloatingBox::new(element.upcast(), style, side, content).into()
},
(None, true) => {
let content = IndependentFormattingContext::create(
element.clone(),
self.style_computer,
style.clone(),
);

AbsolutelyPositionedBox {
style,
node: element.upcast(),
content,
}
.into()
},
(None, false) => {
if let Some(replaced_element) =
ReplacedElement::try_from(element.clone(), style.clone())
{
replaced_element.into()
} else {
InFlowBlockBox::new(style, Some(node), content).into()
let content = BlockContainerBuilder::build(
element.clone().upcast(),
self.style_computer,
&style,
);
InFlowBlockBox::new(style, Some(element.upcast()), content).into()
}
},
};

self.block_level_boxes.push(block_box);
}
}
27 changes: 13 additions & 14 deletions crates/web/core/src/css/layout/flow/float.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ use crate::{
computed_style::ComputedStyle,
font_metrics::DEFAULT_FONT_SIZE,
fragment_tree::BoxFragment,
layout::{flow::BlockFormattingContextState, ContainingBlock, Pixels, Sides, Size},
layout::{
formatting_context::IndependentFormattingContext, ContainingBlock, Pixels, Sides, Size,
},
values::{
self,
length::{self, Length},
Expand All @@ -22,14 +24,12 @@ use crate::{

use std::{cmp, fmt, fmt::Write};

use super::BlockContainer;

#[derive(Clone)]
pub struct FloatingBox {
pub(crate) struct FloatingBox {
pub node: DomPtr<dom_objects::Node>,
pub style: ComputedStyle,
pub side: FloatSide,
pub contents: BlockContainer,
pub contents: IndependentFormattingContext,
}

impl FloatingBox {
Expand All @@ -38,7 +38,7 @@ impl FloatingBox {
node: DomPtr<dom_objects::Node>,
style: ComputedStyle,
side: FloatSide,
contents: BlockContainer,
contents: IndependentFormattingContext,
) -> Self {
Self {
node,
Expand Down Expand Up @@ -108,14 +108,13 @@ impl FloatingBox {
});

// Layout the floats contents to determine its size
let mut established_formatting_context = BlockFormattingContextState::new(containing_block);

let content_info = self.contents.layout(
containing_block,
// FIXME: this should be this elements font size
ctx.with_font_size(DEFAULT_FONT_SIZE),
&mut established_formatting_context,
);
let content_info = match &self.contents {
IndependentFormattingContext::Replaced(_) => todo!(),
IndependentFormattingContext::NonReplaced(bfc) => {
// FIXME: This should be the elements font size
bfc.layout(containing_block, ctx.with_font_size(DEFAULT_FONT_SIZE))
},
};

let total_width =
margin.horizontal_sum() + border.horizontal_sum() + padding.horizontal_sum() + width;
Expand Down
11 changes: 4 additions & 7 deletions crates/web/core/src/css/layout/flow/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@ mod float;
mod inline;
mod positioning;

pub use block::{
BlockContainer, BlockFormattingContext, BlockFormattingContextState, BlockLevelBox,
InFlowBlockBox,
};
pub use builder::BoxTreeBuilder;
pub use float::{FloatContext, FloatingBox};
pub use inline::{InlineBox, InlineFormattingContext, InlineLevelBox, TextRun};
pub(crate) use block::{BlockContainer, BlockFormattingContext, BlockLevelBox, InFlowBlockBox};
pub use builder::BlockContainerBuilder;
use float::{FloatContext, FloatingBox};
use inline::{InlineBox, InlineFormattingContext, InlineLevelBox, TextRun};
Loading

0 comments on commit a417de5

Please sign in to comment.