aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--components/layout_2020/dom_traversal.rs344
-rw-r--r--components/layout_2020/element_data.rs19
-rw-r--r--components/layout_2020/flow/construct.rs625
-rw-r--r--components/layout_2020/flow/float.rs18
-rw-r--r--components/layout_2020/flow/inline.rs345
-rw-r--r--components/layout_2020/flow/mod.rs439
-rw-r--r--components/layout_2020/flow/root.rs122
-rw-r--r--components/layout_2020/fragments.rs116
-rw-r--r--components/layout_2020/geom.rs330
-rw-r--r--components/layout_2020/positioned.rs328
-rw-r--r--components/layout_2020/primitives.rs28
-rw-r--r--components/layout_2020/replaced.rs14
12 files changed, 2728 insertions, 0 deletions
diff --git a/components/layout_2020/dom_traversal.rs b/components/layout_2020/dom_traversal.rs
new file mode 100644
index 00000000000..848e89ba564
--- /dev/null
+++ b/components/layout_2020/dom_traversal.rs
@@ -0,0 +1,344 @@
+use super::*;
+use crate::dom::{Document, NodeData, NodeId};
+use crate::style::StyleSet;
+use atomic_refcell::AtomicRefMut;
+
+pub(super) struct Context<'a> {
+ pub document: &'a Document,
+ pub author_styles: &'a StyleSet,
+}
+
+#[derive(Copy, Clone)]
+pub(super) enum WhichPseudoElement {
+ Before,
+ After,
+}
+
+pub(super) enum Contents {
+ /// Refers to a DOM subtree, plus `::before` and `::after` pseudo-elements.
+ OfElement(NodeId),
+
+ /// Example: an `<img src=…>` element.
+ /// <https://drafts.csswg.org/css2/conform.html#replaced-element>
+ Replaced(ReplacedContent),
+
+ /// Content of a `::before` or `::after` pseudo-element this is being generated.
+ /// <https://drafts.csswg.org/css2/generate.html#content>
+ OfPseudoElement(Vec<PseudoElementContentItem>),
+}
+
+pub(super) enum NonReplacedContents {
+ OfElement(NodeId),
+ OfPseudoElement(Vec<PseudoElementContentItem>),
+}
+
+pub(super) enum PseudoElementContentItem {
+ Text(String),
+ Replaced(ReplacedContent),
+}
+
+pub(super) trait TraversalHandler<'dom> {
+ fn handle_text(&mut self, text: &str, parent_style: &Arc<ComputedValues>);
+
+ /// Or pseudo-element
+ fn handle_element(
+ &mut self,
+ style: &Arc<ComputedValues>,
+ display: DisplayGeneratingBox,
+ contents: Contents,
+ box_slot: BoxSlot<'dom>,
+ );
+}
+
+fn traverse_children_of<'dom>(
+ parent_element: NodeId,
+ parent_element_style: &Arc<ComputedValues>,
+ context: &'dom Context,
+ handler: &mut impl TraversalHandler<'dom>,
+) {
+ traverse_pseudo_element(
+ WhichPseudoElement::Before,
+ parent_element,
+ parent_element_style,
+ context,
+ handler,
+ );
+
+ let mut next = context.document[parent_element].first_child;
+ while let Some(child) = next {
+ match &context.document[child].data {
+ NodeData::Document
+ | NodeData::Doctype { .. }
+ | NodeData::Comment { .. }
+ | NodeData::ProcessingInstruction { .. } => {}
+ NodeData::Text { contents } => {
+ handler.handle_text(contents, parent_element_style);
+ }
+ NodeData::Element(_) => traverse_element(child, parent_element_style, context, handler),
+ }
+ next = context.document[child].next_sibling
+ }
+
+ traverse_pseudo_element(
+ WhichPseudoElement::After,
+ parent_element,
+ &parent_element_style,
+ context,
+ handler,
+ );
+}
+
+fn traverse_element<'dom>(
+ element_id: NodeId,
+ parent_element_style: &ComputedValues,
+ context: &'dom Context,
+ handler: &mut impl TraversalHandler<'dom>,
+) {
+ let style = style_for_element(
+ context.author_styles,
+ context.document,
+ element_id,
+ Some(parent_element_style),
+ );
+ match style.box_.display {
+ Display::None => context.unset_boxes_in_subtree(element_id),
+ Display::Contents => {
+ if ReplacedContent::for_element(element_id, context).is_some() {
+ // `display: content` on a replaced element computes to `display: none`
+ // <https://drafts.csswg.org/css-display-3/#valdef-display-contents>
+ context.unset_boxes_in_subtree(element_id)
+ } else {
+ context.layout_data_mut(element_id).self_box = Some(LayoutBox::DisplayContents);
+ traverse_children_of(element_id, &style, context, handler)
+ }
+ }
+ Display::GeneratingBox(display) => handler.handle_element(
+ &style,
+ display,
+ match ReplacedContent::for_element(element_id, context) {
+ Some(replaced) => Contents::Replaced(replaced),
+ None => Contents::OfElement(element_id),
+ },
+ context.element_box_slot(element_id),
+ ),
+ }
+}
+
+fn traverse_pseudo_element<'dom>(
+ which: WhichPseudoElement,
+ element: NodeId,
+ element_style: &ComputedValues,
+ context: &'dom Context,
+ handler: &mut impl TraversalHandler<'dom>,
+) {
+ if let Some(style) = pseudo_element_style(which, element, element_style, context) {
+ match style.box_.display {
+ Display::None => context.unset_pseudo_element_box(element, which),
+ Display::Contents => {
+ context.unset_pseudo_element_box(element, which);
+ let items = generate_pseudo_element_content(&style, element, context);
+ traverse_pseudo_element_contents(&style, items, handler);
+ }
+ Display::GeneratingBox(display) => {
+ let items = generate_pseudo_element_content(&style, element, context);
+ let contents = Contents::OfPseudoElement(items);
+ let box_slot = context.pseudo_element_box_slot(element, which);
+ handler.handle_element(&style, display, contents, box_slot);
+ }
+ }
+ }
+}
+
+fn traverse_pseudo_element_contents<'dom>(
+ pseudo_element_style: &Arc<ComputedValues>,
+ items: Vec<PseudoElementContentItem>,
+ handler: &mut impl TraversalHandler<'dom>,
+) {
+ let mut anonymous_style = None;
+ for item in items {
+ match item {
+ PseudoElementContentItem::Text(text) => {
+ handler.handle_text(&text, pseudo_element_style)
+ }
+ PseudoElementContentItem::Replaced(contents) => {
+ let item_style = anonymous_style.get_or_insert_with(|| {
+ ComputedValues::anonymous_inheriting_from(Some(pseudo_element_style))
+ });
+ let display_inline = DisplayGeneratingBox::OutsideInside {
+ outside: DisplayOutside::Inline,
+ inside: DisplayInside::Flow,
+ };
+ // `display` is not inherited, so we get the initial value
+ debug_assert!(item_style.box_.display == Display::GeneratingBox(display_inline));
+ handler.handle_element(
+ item_style,
+ display_inline,
+ Contents::Replaced(contents),
+ // We don’t keep pointers to boxes generated by contents of pseudo-elements
+ BoxSlot::dummy(),
+ )
+ }
+ }
+ }
+}
+
+impl std::convert::TryFrom<Contents> for NonReplacedContents {
+ type Error = ReplacedContent;
+
+ fn try_from(contents: Contents) -> Result<Self, Self::Error> {
+ match contents {
+ Contents::OfElement(id) => Ok(NonReplacedContents::OfElement(id)),
+ Contents::OfPseudoElement(items) => Ok(NonReplacedContents::OfPseudoElement(items)),
+ Contents::Replaced(replaced) => Err(replaced),
+ }
+ }
+}
+
+impl std::convert::From<NonReplacedContents> for Contents {
+ fn from(contents: NonReplacedContents) -> Self {
+ match contents {
+ NonReplacedContents::OfElement(id) => Contents::OfElement(id),
+ NonReplacedContents::OfPseudoElement(items) => Contents::OfPseudoElement(items),
+ }
+ }
+}
+
+impl NonReplacedContents {
+ pub fn traverse<'dom>(
+ self,
+ inherited_style: &Arc<ComputedValues>,
+ context: &'dom Context,
+ handler: &mut impl TraversalHandler<'dom>,
+ ) {
+ match self {
+ NonReplacedContents::OfElement(id) => {
+ traverse_children_of(id, inherited_style, context, handler)
+ }
+ NonReplacedContents::OfPseudoElement(items) => {
+ traverse_pseudo_element_contents(inherited_style, items, handler)
+ }
+ }
+ }
+}
+
+fn pseudo_element_style(
+ _which: WhichPseudoElement,
+ _element: NodeId,
+ _element_style: &ComputedValues,
+ _context: &Context,
+) -> Option<Arc<ComputedValues>> {
+ // FIXME: run the cascade, then return None for `content: normal` or `content: none`
+ // https://drafts.csswg.org/css2/generate.html#content
+ None
+}
+
+fn generate_pseudo_element_content(
+ _pseudo_element_style: &ComputedValues,
+ _element: NodeId,
+ _context: &Context,
+) -> Vec<PseudoElementContentItem> {
+ let _ = PseudoElementContentItem::Text;
+ let _ = PseudoElementContentItem::Replaced;
+ unimplemented!()
+}
+
+pub(super) struct BoxSlot<'dom> {
+ slot: Option<AtomicRefMut<'dom, Option<LayoutBox>>>,
+}
+
+impl<'dom> BoxSlot<'dom> {
+ pub fn new(mut slot: AtomicRefMut<'dom, Option<LayoutBox>>) -> Self {
+ *slot = None;
+ Self { slot: Some(slot) }
+ }
+
+ pub fn dummy() -> Self {
+ Self { slot: None }
+ }
+
+ pub fn set(mut self, box_: LayoutBox) {
+ if let Some(slot) = &mut self.slot {
+ **slot = Some(box_)
+ }
+ }
+}
+
+impl Drop for BoxSlot<'_> {
+ fn drop(&mut self) {
+ if let Some(slot) = &mut self.slot {
+ assert!(slot.is_some(), "failed to set a layout box")
+ }
+ }
+}
+
+impl Context<'_> {
+ fn layout_data_mut(&self, element_id: NodeId) -> AtomicRefMut<LayoutDataForElement> {
+ self.document[element_id]
+ .as_element()
+ .unwrap()
+ .layout_data
+ .borrow_mut()
+ }
+
+ fn element_box_slot(&self, element_id: NodeId) -> BoxSlot {
+ BoxSlot::new(AtomicRefMut::map(
+ self.layout_data_mut(element_id),
+ |data| &mut data.self_box,
+ ))
+ }
+
+ fn pseudo_element_box_slot(&self, element_id: NodeId, which: WhichPseudoElement) -> BoxSlot {
+ BoxSlot::new(AtomicRefMut::map(
+ self.layout_data_mut(element_id),
+ |data| {
+ let pseudos = data.pseudo_elements.get_or_insert_with(Default::default);
+ match which {
+ WhichPseudoElement::Before => &mut pseudos.before,
+ WhichPseudoElement::After => &mut pseudos.after,
+ }
+ },
+ ))
+ }
+
+ fn unset_pseudo_element_box(&self, element_id: NodeId, which: WhichPseudoElement) {
+ if let Some(pseudos) = &mut self.layout_data_mut(element_id).pseudo_elements {
+ match which {
+ WhichPseudoElement::Before => pseudos.before = None,
+ WhichPseudoElement::After => pseudos.after = None,
+ }
+ }
+ }
+
+ fn unset_boxes_in_subtree(&self, base_element: NodeId) {
+ let mut node_id = base_element;
+ loop {
+ let node = &self.document[node_id];
+ if let Some(element_data) = node.as_element() {
+ let mut layout_data = element_data.layout_data.borrow_mut();
+ layout_data.pseudo_elements = None;
+ if layout_data.self_box.take().is_some() {
+ // Only descend into children if we removed a box.
+ // If there wasn’t one, then descendants don’t have boxes either.
+ if let Some(child) = node.first_child {
+ node_id = child;
+ continue;
+ }
+ }
+ }
+ let mut next_is_a_sibling_of = node_id;
+ node_id = loop {
+ if let Some(sibling) = self.document[next_is_a_sibling_of].next_sibling {
+ break sibling;
+ } else {
+ next_is_a_sibling_of = node
+ .parent
+ .expect("reached the root while traversing only a subtree");
+ }
+ };
+ if next_is_a_sibling_of == base_element {
+ // Don’t go outside the subtree
+ return;
+ }
+ }
+ }
+}
diff --git a/components/layout_2020/element_data.rs b/components/layout_2020/element_data.rs
new file mode 100644
index 00000000000..bda0d3cbe9e
--- /dev/null
+++ b/components/layout_2020/element_data.rs
@@ -0,0 +1,19 @@
+use super::*;
+
+#[derive(Default)]
+pub(crate) struct LayoutDataForElement {
+ pub(super) self_box: Option<LayoutBox>,
+ pub(super) pseudo_elements: Option<Box<PseudoElementBoxes>>,
+}
+
+#[derive(Default)]
+pub(super) struct PseudoElementBoxes {
+ pub before: Option<LayoutBox>,
+ pub after: Option<LayoutBox>,
+}
+
+pub(super) enum LayoutBox {
+ DisplayContents,
+ BlockLevel(Arc<BlockLevelBox>),
+ InlineLevel(Arc<InlineLevelBox>),
+}
diff --git a/components/layout_2020/flow/construct.rs b/components/layout_2020/flow/construct.rs
new file mode 100644
index 00000000000..4acfc93ce92
--- /dev/null
+++ b/components/layout_2020/flow/construct.rs
@@ -0,0 +1,625 @@
+use super::*;
+
+impl BlockFormattingContext {
+ pub fn construct<'a>(
+ context: &'a Context<'a>,
+ style: &'a Arc<ComputedValues>,
+ contents: NonReplacedContents,
+ ) -> Self {
+ let (contents, contains_floats) = BlockContainer::construct(context, style, contents);
+ Self {
+ contents,
+ contains_floats: contains_floats == ContainsFloats::Yes,
+ }
+ }
+}
+
+enum IntermediateBlockLevelBox {
+ SameFormattingContextBlock {
+ style: Arc<ComputedValues>,
+ contents: IntermediateBlockContainer,
+ },
+ Independent {
+ style: Arc<ComputedValues>,
+ display_inside: DisplayInside,
+ contents: Contents,
+ },
+ OutOfFlowAbsolutelyPositionedBox {
+ style: Arc<ComputedValues>,
+ display_inside: DisplayInside,
+ contents: Contents,
+ },
+ OutOfFlowFloatBox {
+ style: Arc<ComputedValues>,
+ display_inside: DisplayInside,
+ contents: Contents,
+ },
+}
+
+/// A block container that may still have to be constructed.
+///
+/// Represents either the inline formatting context of an anonymous block
+/// box or the yet-to-be-computed block container generated from the children
+/// of a given element.
+///
+/// Deferring allows using rayon’s `into_par_iter`.
+enum IntermediateBlockContainer {
+ InlineFormattingContext(InlineFormattingContext),
+ Deferred { contents: NonReplacedContents },
+}
+
+/// A builder for a block container.
+///
+/// This builder starts from the first child of a given DOM node
+/// and does a preorder traversal of all of its inclusive siblings.
+struct BlockContainerBuilder<'a> {
+ context: &'a Context<'a>,
+ block_container_style: &'a Arc<ComputedValues>,
+
+ /// The list of block-level boxes of the final block container.
+ ///
+ /// Contains all the complete block level boxes we found traversing the tree
+ /// so far, if this is empty at the end of the traversal and the ongoing
+ /// inline formatting context is not empty, the block container establishes
+ /// an inline formatting context (see end of `build`).
+ ///
+ /// DOM nodes which represent block-level boxes are immediately pushed
+ /// to this list with their style without ever being traversed at this
+ /// point, instead we just move to their next sibling. If the DOM node
+ /// doesn't have a next sibling, we either reached the end of the container
+ /// root or there are ongoing inline-level boxes
+ /// (see `handle_block_level_element`).
+ block_level_boxes: Vec<(IntermediateBlockLevelBox, BoxSlot<'a>)>,
+
+ /// The ongoing inline formatting context of the builder.
+ ///
+ /// Contains all the complete inline level boxes we found traversing the
+ /// tree so far. If a block-level box is found during traversal,
+ /// this inline formatting context is pushed as a block level box to
+ /// the list of block-level boxes of the builder
+ /// (see `end_ongoing_inline_formatting_context`).
+ ongoing_inline_formatting_context: InlineFormattingContext,
+
+ /// The ongoing stack of inline boxes stack of the builder.
+ ///
+ /// Contains all the currently ongoing inline boxes we entered so far.
+ /// The traversal is at all times as deep in the tree as this stack is,
+ /// which is why the code doesn't need to keep track of the actual
+ /// container root (see `handle_inline_level_element`).
+ ///
+ /// Whenever the end of a DOM element that represents an inline box is
+ /// reached, the inline box at the top of this stack is complete and ready
+ /// to be pushed to the children of the next last ongoing inline box
+ /// the ongoing inline formatting context if the stack is now empty,
+ /// which means we reached the end of a child of the actual
+ /// container root (see `move_to_next_sibling`).
+ ongoing_inline_boxes_stack: Vec<InlineBox>,
+
+ /// The style of the anonymous block boxes pushed to the list of block-level
+ /// boxes, if any (see `end_ongoing_inline_formatting_context`).
+ anonymous_style: Option<Arc<ComputedValues>>,
+
+ /// Whether the resulting block container contains any float box.
+ contains_floats: ContainsFloats,
+}
+
+impl BlockContainer {
+ pub fn construct<'a>(
+ context: &'a Context<'a>,
+ block_container_style: &'a Arc<ComputedValues>,
+ contents: NonReplacedContents,
+ ) -> (BlockContainer, ContainsFloats) {
+ let mut builder = BlockContainerBuilder {
+ context,
+ block_container_style,
+ block_level_boxes: Default::default(),
+ ongoing_inline_formatting_context: Default::default(),
+ ongoing_inline_boxes_stack: Default::default(),
+ anonymous_style: Default::default(),
+ contains_floats: Default::default(),
+ };
+
+ contents.traverse(block_container_style, context, &mut builder);
+
+ debug_assert!(builder.ongoing_inline_boxes_stack.is_empty());
+
+ if !builder
+ .ongoing_inline_formatting_context
+ .inline_level_boxes
+ .is_empty()
+ {
+ if builder.block_level_boxes.is_empty() {
+ let container = BlockContainer::InlineFormattingContext(
+ builder.ongoing_inline_formatting_context,
+ );
+ return (container, builder.contains_floats);
+ }
+ builder.end_ongoing_inline_formatting_context();
+ }
+
+ let mut contains_floats = builder.contains_floats;
+ let container = BlockContainer::BlockLevelBoxes(
+ builder
+ .block_level_boxes
+ .into_par_iter()
+ .mapfold_reduce_into(
+ &mut contains_floats,
+ |contains_floats, (intermediate, box_slot)| {
+ let (block_level_box, box_contains_floats) = intermediate.finish(context);
+ *contains_floats |= box_contains_floats;
+ box_slot.set(LayoutBox::BlockLevel(block_level_box.clone()));
+ block_level_box
+ },
+ |left, right| *left |= right,
+ )
+ .collect(),
+ );
+ (container, contains_floats)
+ }
+}
+
+impl<'a> TraversalHandler<'a> for BlockContainerBuilder<'a> {
+ fn handle_element(
+ &mut self,
+ style: &Arc<ComputedValues>,
+ display: DisplayGeneratingBox,
+ contents: Contents,
+ box_slot: BoxSlot<'a>,
+ ) {
+ match display {
+ DisplayGeneratingBox::OutsideInside { outside, inside } => match outside {
+ DisplayOutside::Inline => box_slot.set(LayoutBox::InlineLevel(
+ self.handle_inline_level_element(style, inside, contents),
+ )),
+ DisplayOutside::Block => {
+ // Floats and abspos cause blockification, so they only happen in this case.
+ // https://drafts.csswg.org/css2/visuren.html#dis-pos-flo
+ if style.box_.position.is_absolutely_positioned() {
+ self.handle_absolutely_positioned_element(
+ style.clone(),
+ inside,
+ contents,
+ box_slot,
+ )
+ } else if style.box_.float.is_floating() {
+ self.handle_float_element(style.clone(), inside, contents, box_slot)
+ } else {
+ self.handle_block_level_element(style.clone(), inside, contents, box_slot)
+ }
+ }
+ },
+ }
+ }
+
+ fn handle_text(&mut self, input: &str, parent_style: &Arc<ComputedValues>) {
+ let (leading_whitespace, mut input) = self.handle_leading_whitespace(input);
+ if leading_whitespace || !input.is_empty() {
+ // This text node should be pushed either to the next ongoing
+ // inline level box with the parent style of that inline level box
+ // that will be ended, or directly to the ongoing inline formatting
+ // context with the parent style of that builder.
+ let inlines = self.current_inline_level_boxes();
+
+ fn last_text(inlines: &mut [Arc<InlineLevelBox>]) -> Option<&mut String> {
+ let last = inlines.last_mut()?;
+ if let InlineLevelBox::TextRun(_) = &**last {
+ // We never clone text run boxes, so the refcount is 1 and unwrap succeeds:
+ let last = Arc::get_mut(last).unwrap();
+ if let InlineLevelBox::TextRun(TextRun { text, .. }) = last {
+ Some(text)
+ } else {
+ unreachable!()
+ }
+ } else {
+ None
+ }
+ }
+
+ let mut new_text_run_contents;
+ let output;
+ if let Some(text) = last_text(inlines) {
+ // Append to the existing text run
+ new_text_run_contents = None;
+ output = text;
+ } else {
+ new_text_run_contents = Some(String::new());
+ output = new_text_run_contents.as_mut().unwrap();
+ }
+
+ if leading_whitespace {
+ output.push(' ')
+ }
+ loop {
+ if let Some(i) = input.bytes().position(|b| b.is_ascii_whitespace()) {
+ let (non_whitespace, rest) = input.split_at(i);
+ output.push_str(non_whitespace);
+ output.push(' ');
+ if let Some(i) = rest.bytes().position(|b| !b.is_ascii_whitespace()) {
+ input = &rest[i..];
+ } else {
+ break;
+ }
+ } else {
+ output.push_str(input);
+ break;
+ }
+ }
+
+ if let Some(text) = new_text_run_contents {
+ let parent_style = parent_style.clone();
+ inlines.push(Arc::new(InlineLevelBox::TextRun(TextRun {
+ parent_style,
+ text,
+ })))
+ }
+ }
+ }
+}
+
+impl<'a> BlockContainerBuilder<'a> {
+ /// Returns:
+ ///
+ /// * Whether this text run has preserved (non-collapsible) leading whitespace
+ /// * The contents starting at the first non-whitespace character (or the empty string)
+ fn handle_leading_whitespace<'text>(&mut self, text: &'text str) -> (bool, &'text str) {
+ // FIXME: this is only an approximation of
+ // https://drafts.csswg.org/css2/text.html#white-space-model
+ if !text.starts_with(|c: char| c.is_ascii_whitespace()) {
+ return (false, text);
+ }
+ let mut inline_level_boxes = self.current_inline_level_boxes().iter().rev();
+ let mut stack = Vec::new();
+ let preserved = loop {
+ match inline_level_boxes.next().map(|b| &**b) {
+ Some(InlineLevelBox::TextRun(r)) => break !r.text.ends_with(' '),
+ Some(InlineLevelBox::Atomic { .. }) => break false,
+ Some(InlineLevelBox::OutOfFlowAbsolutelyPositionedBox(_))
+ | Some(InlineLevelBox::OutOfFlowFloatBox(_)) => {}
+ Some(InlineLevelBox::InlineBox(b)) => {
+ stack.push(inline_level_boxes);
+ inline_level_boxes = b.children.iter().rev()
+ }
+ None => {
+ if let Some(iter) = stack.pop() {
+ inline_level_boxes = iter
+ } else {
+ break false; // Paragraph start
+ }
+ }
+ }
+ };
+ let text = text.trim_start_matches(|c: char| c.is_ascii_whitespace());
+ (preserved, text)
+ }
+
+ fn handle_inline_level_element(
+ &mut self,
+ style: &Arc<ComputedValues>,
+ display_inside: DisplayInside,
+ contents: Contents,
+ ) -> Arc<InlineLevelBox> {
+ let box_ = match contents.try_into() {
+ Err(replaced) => Arc::new(InlineLevelBox::Atomic {
+ style: style.clone(),
+ contents: replaced,
+ }),
+ Ok(non_replaced) => match display_inside {
+ DisplayInside::Flow => {
+ // Whatever happened before, we just found an inline level element, so
+ // all we need to do is to remember this ongoing inline level box.
+ self.ongoing_inline_boxes_stack.push(InlineBox {
+ style: style.clone(),
+ first_fragment: true,
+ last_fragment: false,
+ children: vec![],
+ });
+
+ NonReplacedContents::traverse(non_replaced, &style, self.context, self);
+
+ let mut inline_box = self
+ .ongoing_inline_boxes_stack
+ .pop()
+ .expect("no ongoing inline level box found");
+ inline_box.last_fragment = true;
+ Arc::new(InlineLevelBox::InlineBox(inline_box))
+ }
+ DisplayInside::FlowRoot => {
+ // a.k.a. `inline-block`
+ unimplemented!()
+ }
+ },
+ };
+ self.current_inline_level_boxes().push(box_.clone());
+ box_
+ }
+
+ fn handle_block_level_element(
+ &mut self,
+ style: Arc<ComputedValues>,
+ display_inside: DisplayInside,
+ contents: Contents,
+ box_slot: BoxSlot<'a>,
+ ) {
+ // We just found a block level element, all ongoing inline level boxes
+ // need to be split around it. We iterate on the fragmented inline
+ // level box stack to take their contents and set their first_fragment
+ // field to false, for the fragmented inline level boxes that will
+ // come after the block level element.
+ let mut fragmented_inline_boxes =
+ self.ongoing_inline_boxes_stack
+ .iter_mut()
+ .rev()
+ .map(|ongoing| {
+ let fragmented = InlineBox {
+ style: ongoing.style.clone(),
+ first_fragment: ongoing.first_fragment,
+ // The fragmented boxes before the block level element
+ // are obviously not the last fragment.
+ last_fragment: false,
+ children: take(&mut ongoing.children),
+ };
+ ongoing.first_fragment = false;
+ fragmented
+ });
+
+ if let Some(last) = fragmented_inline_boxes.next() {
+ // There were indeed some ongoing inline level boxes before
+ // the block, we accumulate them as a single inline level box
+ // to be pushed to the ongoing inline formatting context.
+ let mut fragmented_inline = InlineLevelBox::InlineBox(last);
+ for mut fragmented_parent_inline_box in fragmented_inline_boxes {
+ fragmented_parent_inline_box
+ .children
+ .push(Arc::new(fragmented_inline));
+ fragmented_inline = InlineLevelBox::InlineBox(fragmented_parent_inline_box);
+ }
+
+ self.ongoing_inline_formatting_context
+ .inline_level_boxes
+ .push(Arc::new(fragmented_inline));
+ }
+
+ // We found a block level element, so the ongoing inline formatting
+ // context needs to be ended.
+ self.end_ongoing_inline_formatting_context();
+
+ let intermediate_box = match contents.try_into() {
+ Ok(contents) => match display_inside {
+ DisplayInside::Flow => IntermediateBlockLevelBox::SameFormattingContextBlock {
+ style,
+ contents: IntermediateBlockContainer::Deferred { contents },
+ },
+ _ => IntermediateBlockLevelBox::Independent {
+ style,
+ display_inside,
+ contents: contents.into(),
+ },
+ },
+ Err(contents) => {
+ let contents = Contents::Replaced(contents);
+ IntermediateBlockLevelBox::Independent {
+ style,
+ display_inside,
+ contents,
+ }
+ }
+ };
+ self.block_level_boxes.push((intermediate_box, box_slot))
+ }
+
+ fn handle_absolutely_positioned_element(
+ &mut self,
+ style: Arc<ComputedValues>,
+ display_inside: DisplayInside,
+ contents: Contents,
+ box_slot: BoxSlot<'a>,
+ ) {
+ if !self.has_ongoing_inline_formatting_context() {
+ let box_ = IntermediateBlockLevelBox::OutOfFlowAbsolutelyPositionedBox {
+ style,
+ contents,
+ display_inside,
+ };
+ self.block_level_boxes.push((box_, box_slot))
+ } else {
+ let box_ = Arc::new(InlineLevelBox::OutOfFlowAbsolutelyPositionedBox(
+ AbsolutelyPositionedBox {
+ contents: IndependentFormattingContext::construct(
+ self.context,
+ &style,
+ display_inside,
+ contents,
+ ),
+ style,
+ },
+ ));
+ self.current_inline_level_boxes().push(box_.clone());
+ box_slot.set(LayoutBox::InlineLevel(box_))
+ }
+ }
+
+ fn handle_float_element(
+ &mut self,
+ style: Arc<ComputedValues>,
+ display_inside: DisplayInside,
+ contents: Contents,
+ box_slot: BoxSlot<'a>,
+ ) {
+ self.contains_floats = ContainsFloats::Yes;
+
+ if !self.has_ongoing_inline_formatting_context() {
+ let box_ = IntermediateBlockLevelBox::OutOfFlowFloatBox {
+ style,
+ contents,
+ display_inside,
+ };
+ self.block_level_boxes.push((box_, box_slot));
+ } else {
+ let box_ = Arc::new(InlineLevelBox::OutOfFlowFloatBox(FloatBox {
+ contents: IndependentFormattingContext::construct(
+ self.context,
+ &style,
+ display_inside,
+ contents,
+ ),
+ style,
+ }));
+ self.current_inline_level_boxes().push(box_.clone());
+ box_slot.set(LayoutBox::InlineLevel(box_))
+ }
+ }
+
+ fn end_ongoing_inline_formatting_context(&mut self) {
+ assert!(
+ self.ongoing_inline_boxes_stack.is_empty(),
+ "there should be no ongoing inline level boxes",
+ );
+
+ if self
+ .ongoing_inline_formatting_context
+ .inline_level_boxes
+ .is_empty()
+ {
+ // There should never be an empty inline formatting context.
+ return;
+ }
+
+ let block_container_style = self.block_container_style;
+ let anonymous_style = self.anonymous_style.get_or_insert_with(|| {
+ // If parent_style is None, the parent is the document node,
+ // in which case anonymous inline boxes should inherit their
+ // styles from initial values.
+ ComputedValues::anonymous_inheriting_from(Some(block_container_style))
+ });
+
+ let box_ = IntermediateBlockLevelBox::SameFormattingContextBlock {
+ style: anonymous_style.clone(),
+ contents: IntermediateBlockContainer::InlineFormattingContext(take(
+ &mut self.ongoing_inline_formatting_context,
+ )),
+ };
+ self.block_level_boxes.push((box_, BoxSlot::dummy()))
+ }
+
+ fn current_inline_level_boxes(&mut self) -> &mut Vec<Arc<InlineLevelBox>> {
+ match self.ongoing_inline_boxes_stack.last_mut() {
+ Some(last) => &mut last.children,
+ None => &mut self.ongoing_inline_formatting_context.inline_level_boxes,
+ }
+ }
+
+ fn has_ongoing_inline_formatting_context(&self) -> bool {
+ !self
+ .ongoing_inline_formatting_context
+ .inline_level_boxes
+ .is_empty()
+ || !self.ongoing_inline_boxes_stack.is_empty()
+ }
+}
+
+impl IntermediateBlockLevelBox {
+ fn finish(self, context: &Context) -> (Arc<BlockLevelBox>, ContainsFloats) {
+ match self {
+ IntermediateBlockLevelBox::SameFormattingContextBlock { style, contents } => {
+ let (contents, contains_floats) = contents.finish(context, &style);
+ let block_level_box =
+ Arc::new(BlockLevelBox::SameFormattingContextBlock { contents, style });
+ (block_level_box, contains_floats)
+ }
+ IntermediateBlockLevelBox::Independent {
+ style,
+ display_inside,
+ contents,
+ } => {
+ let contents = IndependentFormattingContext::construct(
+ context,
+ &style,
+ display_inside,
+ contents,
+ );
+ (
+ Arc::new(BlockLevelBox::Independent { style, contents }),
+ ContainsFloats::No,
+ )
+ }
+ IntermediateBlockLevelBox::OutOfFlowAbsolutelyPositionedBox {
+ style,
+ display_inside,
+ contents,
+ } => {
+ let block_level_box = Arc::new(BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(
+ AbsolutelyPositionedBox {
+ contents: IndependentFormattingContext::construct(
+ context,
+ &style,
+ display_inside,
+ contents,
+ ),
+ style: style,
+ },
+ ));
+ (block_level_box, ContainsFloats::No)
+ }
+ IntermediateBlockLevelBox::OutOfFlowFloatBox {
+ style,
+ display_inside,
+ contents,
+ } => {
+ let contents = IndependentFormattingContext::construct(
+ context,
+ &style,
+ display_inside,
+ contents,
+ );
+ let block_level_box = Arc::new(BlockLevelBox::OutOfFlowFloatBox(FloatBox {
+ contents,
+ style,
+ }));
+ (block_level_box, ContainsFloats::Yes)
+ }
+ }
+ }
+}
+
+impl IntermediateBlockContainer {
+ fn finish(
+ self,
+ context: &Context,
+ style: &Arc<ComputedValues>,
+ ) -> (BlockContainer, ContainsFloats) {
+ match self {
+ IntermediateBlockContainer::Deferred { contents } => {
+ BlockContainer::construct(context, style, contents)
+ }
+ IntermediateBlockContainer::InlineFormattingContext(ifc) => {
+ // If that inline formatting context contained any float, those
+ // were already taken into account during the first phase of
+ // box construction.
+ (
+ BlockContainer::InlineFormattingContext(ifc),
+ ContainsFloats::No,
+ )
+ }
+ }
+ }
+}
+
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub(in crate::layout) enum ContainsFloats {
+ No,
+ Yes,
+}
+
+impl std::ops::BitOrAssign for ContainsFloats {
+ fn bitor_assign(&mut self, other: Self) {
+ if other == ContainsFloats::Yes {
+ *self = ContainsFloats::Yes;
+ }
+ }
+}
+
+impl Default for ContainsFloats {
+ fn default() -> Self {
+ ContainsFloats::No
+ }
+}
diff --git a/components/layout_2020/flow/float.rs b/components/layout_2020/flow/float.rs
new file mode 100644
index 00000000000..1c06b5cf2da
--- /dev/null
+++ b/components/layout_2020/flow/float.rs
@@ -0,0 +1,18 @@
+use super::*;
+
+#[derive(Debug)]
+pub(in crate::layout) struct FloatBox {
+ pub style: Arc<ComputedValues>,
+ pub contents: IndependentFormattingContext,
+}
+
+/// Data kept during layout about the floats in a given block formatting context.
+pub(in crate::layout) struct FloatContext {
+ // TODO
+}
+
+impl FloatContext {
+ pub fn new() -> Self {
+ FloatContext {}
+ }
+}
diff --git a/components/layout_2020/flow/inline.rs b/components/layout_2020/flow/inline.rs
new file mode 100644
index 00000000000..5c39bb9385b
--- /dev/null
+++ b/components/layout_2020/flow/inline.rs
@@ -0,0 +1,345 @@
+use super::*;
+use crate::fonts::BITSTREAM_VERA_SANS;
+use crate::text::ShapedSegment;
+
+#[derive(Debug, Default)]
+pub(in crate::layout) struct InlineFormattingContext {
+ pub(super) inline_level_boxes: Vec<Arc<InlineLevelBox>>,
+}
+
+#[derive(Debug)]
+pub(in crate::layout) enum InlineLevelBox {
+ InlineBox(InlineBox),
+ TextRun(TextRun),
+ OutOfFlowAbsolutelyPositionedBox(AbsolutelyPositionedBox),
+ OutOfFlowFloatBox(FloatBox),
+ Atomic {
+ style: Arc<ComputedValues>,
+ // FIXME: this should be IndependentFormattingContext:
+ contents: ReplacedContent,
+ },
+}
+
+#[derive(Debug)]
+pub(in crate::layout) struct InlineBox {
+ pub style: Arc<ComputedValues>,
+ pub first_fragment: bool,
+ pub last_fragment: bool,
+ pub children: Vec<Arc<InlineLevelBox>>,
+}
+
+/// https://www.w3.org/TR/css-display-3/#css-text-run
+#[derive(Debug)]
+pub(in crate::layout) struct TextRun {
+ pub parent_style: Arc<ComputedValues>,
+ pub text: String,
+}
+
+struct InlineNestingLevelState<'box_tree> {
+ remaining_boxes: std::slice::Iter<'box_tree, Arc<InlineLevelBox>>,
+ fragments_so_far: Vec<Fragment>,
+ inline_start: Length,
+ max_block_size_of_fragments_so_far: Length,
+}
+
+struct PartialInlineBoxFragment<'box_tree> {
+ style: Arc<ComputedValues>,
+ start_corner: Vec2<Length>,
+ padding: Sides<Length>,
+ border: Sides<Length>,
+ margin: Sides<Length>,
+ last_box_tree_fragment: bool,
+ parent_nesting_level: InlineNestingLevelState<'box_tree>,
+}
+
+struct InlineFormattingContextState<'box_tree, 'cb> {
+ containing_block: &'cb ContainingBlock,
+ line_boxes: LinesBoxes,
+ inline_position: Length,
+ partial_inline_boxes_stack: Vec<PartialInlineBoxFragment<'box_tree>>,
+ current_nesting_level: InlineNestingLevelState<'box_tree>,
+}
+
+struct LinesBoxes {
+ boxes: Vec<Fragment>,
+ next_line_block_position: Length,
+}
+
+impl InlineFormattingContext {
+ pub(super) fn layout<'a>(
+ &'a self,
+ containing_block: &ContainingBlock,
+ tree_rank: usize,
+ absolutely_positioned_fragments: &mut Vec<AbsolutelyPositionedFragment<'a>>,
+ ) -> FlowChildren {
+ let mut ifc = InlineFormattingContextState {
+ containing_block,
+ partial_inline_boxes_stack: Vec::new(),
+ line_boxes: LinesBoxes {
+ boxes: Vec::new(),
+ next_line_block_position: Length::zero(),
+ },
+ inline_position: Length::zero(),
+ current_nesting_level: InlineNestingLevelState {
+ remaining_boxes: self.inline_level_boxes.iter(),
+ fragments_so_far: Vec::with_capacity(self.inline_level_boxes.len()),
+ inline_start: Length::zero(),
+ max_block_size_of_fragments_so_far: Length::zero(),
+ },
+ };
+ loop {
+ if let Some(child) = ifc.current_nesting_level.remaining_boxes.next() {
+ match &**child {
+ InlineLevelBox::InlineBox(inline) => {
+ let partial = inline.start_layout(&mut ifc);
+ ifc.partial_inline_boxes_stack.push(partial)
+ }
+ InlineLevelBox::TextRun(run) => run.layout(&mut ifc),
+ InlineLevelBox::Atomic { style: _, contents } => {
+ // FIXME
+ match *contents {}
+ }
+ InlineLevelBox::OutOfFlowAbsolutelyPositionedBox(box_) => {
+ let initial_start_corner = match box_.style.specified_display {
+ Display::GeneratingBox(DisplayGeneratingBox::OutsideInside {
+ outside,
+ inside: _,
+ }) => Vec2 {
+ inline: match outside {
+ DisplayOutside::Inline => ifc.inline_position,
+ DisplayOutside::Block => Length::zero(),
+ },
+ block: ifc.line_boxes.next_line_block_position,
+ },
+ Display::Contents => {
+ panic!("display:contents does not generate an abspos box")
+ }
+ Display::None => panic!("display:none does not generate an abspos box"),
+ };
+ absolutely_positioned_fragments
+ .push(box_.layout(initial_start_corner, tree_rank));
+ }
+ InlineLevelBox::OutOfFlowFloatBox(_box_) => {
+ // TODO
+ continue;
+ }
+ }
+ } else
+ // Reached the end of ifc.remaining_boxes
+ if let Some(mut partial) = ifc.partial_inline_boxes_stack.pop() {
+ partial.finish_layout(
+ &mut ifc.current_nesting_level,
+ &mut ifc.inline_position,
+ false,
+ );
+ ifc.current_nesting_level = partial.parent_nesting_level
+ } else {
+ ifc.line_boxes
+ .finish_line(&mut ifc.current_nesting_level, containing_block);
+ return FlowChildren {
+ fragments: ifc.line_boxes.boxes,
+ block_size: ifc.line_boxes.next_line_block_position,
+ collapsible_margins_in_children: CollapsedBlockMargins::zero(),
+ };
+ }
+ }
+ }
+}
+
+impl LinesBoxes {
+ fn finish_line(
+ &mut self,
+ top_nesting_level: &mut InlineNestingLevelState,
+ containing_block: &ContainingBlock,
+ ) {
+ let start_corner = Vec2 {
+ inline: Length::zero(),
+ block: self.next_line_block_position,
+ };
+ let size = Vec2 {
+ inline: containing_block.inline_size,
+ block: std::mem::replace(
+ &mut top_nesting_level.max_block_size_of_fragments_so_far,
+ Length::zero(),
+ ),
+ };
+ self.next_line_block_position += size.block;
+ self.boxes.push(Fragment::Anonymous(AnonymousFragment {
+ children: take(&mut top_nesting_level.fragments_so_far),
+ rect: Rect { start_corner, size },
+ mode: containing_block.mode,
+ }))
+ }
+}
+
+impl InlineBox {
+ fn start_layout<'box_tree>(
+ &'box_tree self,
+ ifc: &mut InlineFormattingContextState<'box_tree, '_>,
+ ) -> PartialInlineBoxFragment<'box_tree> {
+ let style = self.style.clone();
+ let cbis = ifc.containing_block.inline_size;
+ let mut padding = style.padding().percentages_relative_to(cbis);
+ let mut border = style.border_width().percentages_relative_to(cbis);
+ let mut margin = style
+ .margin()
+ .percentages_relative_to(cbis)
+ .auto_is(Length::zero);
+ if self.first_fragment {
+ ifc.inline_position += padding.inline_start + border.inline_start + margin.inline_start;
+ } else {
+ padding.inline_start = Length::zero();
+ border.inline_start = Length::zero();
+ margin.inline_start = Length::zero();
+ }
+ let mut start_corner = Vec2 {
+ block: padding.block_start + border.block_start + margin.block_start,
+ inline: ifc.inline_position - ifc.current_nesting_level.inline_start,
+ };
+ start_corner += &relative_adjustement(
+ &style,
+ ifc.containing_block.inline_size,
+ ifc.containing_block.block_size,
+ );
+ PartialInlineBoxFragment {
+ style,
+ start_corner,
+ padding,
+ border,
+ margin,
+ last_box_tree_fragment: self.last_fragment,
+ parent_nesting_level: std::mem::replace(
+ &mut ifc.current_nesting_level,
+ InlineNestingLevelState {
+ remaining_boxes: self.children.iter(),
+ fragments_so_far: Vec::with_capacity(self.children.len()),
+ inline_start: ifc.inline_position,
+ max_block_size_of_fragments_so_far: Length::zero(),
+ },
+ ),
+ }
+ }
+}
+
+impl<'box_tree> PartialInlineBoxFragment<'box_tree> {
+ fn finish_layout(
+ &mut self,
+ nesting_level: &mut InlineNestingLevelState,
+ inline_position: &mut Length,
+ at_line_break: bool,
+ ) {
+ let mut fragment = BoxFragment {
+ style: self.style.clone(),
+ children: take(&mut nesting_level.fragments_so_far),
+ content_rect: Rect {
+ size: Vec2 {
+ inline: *inline_position - self.start_corner.inline,
+ block: nesting_level.max_block_size_of_fragments_so_far,
+ },
+ start_corner: self.start_corner.clone(),
+ },
+ padding: self.padding.clone(),
+ border: self.border.clone(),
+ margin: self.margin.clone(),
+ block_margins_collapsed_with_children: CollapsedBlockMargins::zero(),
+ };
+ let last_fragment = self.last_box_tree_fragment && !at_line_break;
+ if last_fragment {
+ *inline_position += fragment.padding.inline_end
+ + fragment.border.inline_end
+ + fragment.margin.inline_end;
+ } else {
+ fragment.padding.inline_end = Length::zero();
+ fragment.border.inline_end = Length::zero();
+ fragment.margin.inline_end = Length::zero();
+ }
+ self.parent_nesting_level
+ .max_block_size_of_fragments_so_far
+ .max_assign(
+ fragment.content_rect.size.block
+ + fragment.padding.block_sum()
+ + fragment.border.block_sum()
+ + fragment.margin.block_sum(),
+ );
+ self.parent_nesting_level
+ .fragments_so_far
+ .push(Fragment::Box(fragment));
+ }
+}
+
+impl TextRun {
+ fn layout(&self, ifc: &mut InlineFormattingContextState) {
+ let available = ifc.containing_block.inline_size - ifc.inline_position;
+ let mut chars = self.text.chars();
+ loop {
+ let mut shaped = ShapedSegment::new_with_naive_shaping(BITSTREAM_VERA_SANS.clone());
+ let mut last_break_opportunity = None;
+ loop {
+ let next = chars.next();
+ if matches!(next, Some(' ') | None) {
+ let inline_size = self.parent_style.font.font_size * shaped.advance_width;
+ if inline_size > available {
+ if let Some((state, iter)) = last_break_opportunity.take() {
+ shaped.restore(&state);
+ chars = iter;
+ }
+ break;
+ }
+ }
+ if let Some(ch) = next {
+ if ch == ' ' {
+ last_break_opportunity = Some((shaped.save(), chars.clone()))
+ }
+ shaped.append_char(ch).unwrap()
+ } else {
+ break;
+ }
+ }
+ let inline_size = self.parent_style.font.font_size * shaped.advance_width;
+ // https://www.w3.org/TR/CSS2/visudet.html#propdef-line-height
+ // 'normal':
+ // “set the used value to a "reasonable" value based on the font of the element.”
+ let line_height = self.parent_style.font.font_size.0 * 1.2;
+ let content_rect = Rect {
+ start_corner: Vec2 {
+ block: Length::zero(),
+ inline: ifc.inline_position - ifc.current_nesting_level.inline_start,
+ },
+ size: Vec2 {
+ block: line_height,
+ inline: inline_size,
+ },
+ };
+ ifc.inline_position += inline_size;
+ ifc.current_nesting_level
+ .max_block_size_of_fragments_so_far
+ .max_assign(line_height);
+ ifc.current_nesting_level
+ .fragments_so_far
+ .push(Fragment::Text(TextFragment {
+ parent_style: self.parent_style.clone(),
+ content_rect,
+ text: shaped,
+ }));
+ if chars.as_str().is_empty() {
+ break;
+ } else {
+ // New line
+ ifc.current_nesting_level.inline_start = Length::zero();
+ let mut nesting_level = &mut ifc.current_nesting_level;
+ for partial in ifc.partial_inline_boxes_stack.iter_mut().rev() {
+ partial.finish_layout(nesting_level, &mut ifc.inline_position, true);
+ partial.start_corner.inline = Length::zero();
+ partial.padding.inline_start = Length::zero();
+ partial.border.inline_start = Length::zero();
+ partial.margin.inline_start = Length::zero();
+ partial.parent_nesting_level.inline_start = Length::zero();
+ nesting_level = &mut partial.parent_nesting_level;
+ }
+ ifc.line_boxes
+ .finish_line(nesting_level, ifc.containing_block);
+ ifc.inline_position = Length::zero();
+ }
+ }
+ }
+}
diff --git a/components/layout_2020/flow/mod.rs b/components/layout_2020/flow/mod.rs
new file mode 100644
index 00000000000..a810dfb37fa
--- /dev/null
+++ b/components/layout_2020/flow/mod.rs
@@ -0,0 +1,439 @@
+//! Flow layout, also known as block-and-inline layout.
+
+use super::*;
+use rayon::prelude::*;
+use rayon_croissant::ParallelIteratorExt;
+
+mod construct;
+mod float;
+mod inline;
+mod root;
+
+pub(super) use construct::*;
+pub(super) use float::*;
+pub(super) use inline::*;
+
+#[derive(Debug)]
+pub(super) struct BlockFormattingContext {
+ pub contents: BlockContainer,
+ pub contains_floats: bool,
+}
+
+#[derive(Debug)]
+pub(super) enum BlockContainer {
+ BlockLevelBoxes(Vec<Arc<BlockLevelBox>>),
+ InlineFormattingContext(InlineFormattingContext),
+}
+
+#[derive(Debug)]
+pub(super) enum BlockLevelBox {
+ SameFormattingContextBlock {
+ style: Arc<ComputedValues>,
+ contents: BlockContainer,
+ },
+ OutOfFlowAbsolutelyPositionedBox(AbsolutelyPositionedBox),
+ OutOfFlowFloatBox(FloatBox),
+ Independent {
+ style: Arc<ComputedValues>,
+ contents: IndependentFormattingContext,
+ },
+}
+
+pub(super) struct FlowChildren {
+ pub fragments: Vec<Fragment>,
+ pub block_size: Length,
+ pub collapsible_margins_in_children: CollapsedBlockMargins,
+}
+
+#[derive(Clone, Copy)]
+struct CollapsibleWithParentStartMargin(bool);
+
+impl BlockFormattingContext {
+ pub(super) fn layout<'a>(
+ &'a self,
+ containing_block: &ContainingBlock,
+ tree_rank: usize,
+ absolutely_positioned_fragments: &mut Vec<AbsolutelyPositionedFragment<'a>>,
+ ) -> FlowChildren {
+ let mut float_context;
+ let float_context = if self.contains_floats {
+ float_context = FloatContext::new();
+ Some(&mut float_context)
+ } else {
+ None
+ };
+ let mut flow_children = self.contents.layout(
+ containing_block,
+ tree_rank,
+ absolutely_positioned_fragments,
+ float_context,
+ CollapsibleWithParentStartMargin(false),
+ );
+ flow_children.block_size += flow_children.collapsible_margins_in_children.end.solve();
+ flow_children
+ .collapsible_margins_in_children
+ .collapsed_through = false;
+ flow_children.collapsible_margins_in_children.end = CollapsedMargin::zero();
+ flow_children
+ }
+}
+
+impl BlockContainer {
+ fn layout<'a>(
+ &'a self,
+ containing_block: &ContainingBlock,
+ tree_rank: usize,
+ absolutely_positioned_fragments: &mut Vec<AbsolutelyPositionedFragment<'a>>,
+ float_context: Option<&mut FloatContext>,
+ collapsible_with_parent_start_margin: CollapsibleWithParentStartMargin,
+ ) -> FlowChildren {
+ match self {
+ BlockContainer::BlockLevelBoxes(child_boxes) => layout_block_level_children(
+ child_boxes,
+ containing_block,
+ tree_rank,
+ absolutely_positioned_fragments,
+ float_context,
+ collapsible_with_parent_start_margin,
+ ),
+ BlockContainer::InlineFormattingContext(ifc) => {
+ ifc.layout(containing_block, tree_rank, absolutely_positioned_fragments)
+ }
+ }
+ }
+}
+
+fn layout_block_level_children<'a>(
+ child_boxes: &'a [Arc<BlockLevelBox>],
+ containing_block: &ContainingBlock,
+ tree_rank: usize,
+ absolutely_positioned_fragments: &mut Vec<AbsolutelyPositionedFragment<'a>>,
+ float_context: Option<&mut FloatContext>,
+ collapsible_with_parent_start_margin: CollapsibleWithParentStartMargin,
+) -> FlowChildren {
+ fn place_block_level_fragment(fragment: &mut Fragment, placement_state: &mut PlacementState) {
+ match fragment {
+ Fragment::Box(fragment) => {
+ let fragment_block_margins = &fragment.block_margins_collapsed_with_children;
+ let fragment_block_size = fragment.padding.block_sum()
+ + fragment.border.block_sum()
+ + fragment.content_rect.size.block;
+
+ if placement_state.next_in_flow_margin_collapses_with_parent_start_margin {
+ assert_eq!(placement_state.current_margin.solve(), Length::zero());
+ placement_state
+ .start_margin
+ .adjoin_assign(&fragment_block_margins.start);
+ if fragment_block_margins.collapsed_through {
+ placement_state
+ .start_margin
+ .adjoin_assign(&fragment_block_margins.end);
+ return;
+ }
+ placement_state.next_in_flow_margin_collapses_with_parent_start_margin = false;
+ } else {
+ placement_state
+ .current_margin
+ .adjoin_assign(&fragment_block_margins.start);
+ }
+ fragment.content_rect.start_corner.block += placement_state.current_margin.solve()
+ + placement_state.current_block_direction_position;
+ if fragment_block_margins.collapsed_through {
+ placement_state
+ .current_margin
+ .adjoin_assign(&fragment_block_margins.end);
+ return;
+ }
+ placement_state.current_block_direction_position +=
+ placement_state.current_margin.solve() + fragment_block_size;
+ placement_state.current_margin = fragment_block_margins.end;
+ }
+ Fragment::Anonymous(fragment) => {
+ // FIXME(nox): Margin collapsing for hypothetical boxes of
+ // abspos elements is probably wrong.
+ assert!(fragment.children.is_empty());
+ assert_eq!(fragment.rect.size.block, Length::zero());
+ fragment.rect.start_corner.block +=
+ placement_state.current_block_direction_position;
+ }
+ _ => unreachable!(),
+ }
+ }
+
+ struct PlacementState {
+ next_in_flow_margin_collapses_with_parent_start_margin: bool,
+ start_margin: CollapsedMargin,
+ current_margin: CollapsedMargin,
+ current_block_direction_position: Length,
+ }
+
+ let abspos_so_far = absolutely_positioned_fragments.len();
+ let mut placement_state = PlacementState {
+ next_in_flow_margin_collapses_with_parent_start_margin:
+ collapsible_with_parent_start_margin.0,
+ start_margin: CollapsedMargin::zero(),
+ current_margin: CollapsedMargin::zero(),
+ current_block_direction_position: Length::zero(),
+ };
+ let mut fragments: Vec<_>;
+ if let Some(float_context) = float_context {
+ // Because floats are involved, we do layout for this block formatting context
+ // in tree order without parallelism. This enables mutable access
+ // to a `FloatContext` that tracks every float encountered so far (again in tree order).
+ fragments = child_boxes
+ .iter()
+ .enumerate()
+ .map(|(tree_rank, box_)| {
+ let mut fragment = box_.layout(
+ containing_block,
+ tree_rank,
+ absolutely_positioned_fragments,
+ Some(float_context),
+ );
+ place_block_level_fragment(&mut fragment, &mut placement_state);
+ fragment
+ })
+ .collect()
+ } else {
+ fragments = child_boxes
+ .par_iter()
+ .enumerate()
+ .mapfold_reduce_into(
+ absolutely_positioned_fragments,
+ |abspos_fragments, (tree_rank, box_)| {
+ box_.layout(
+ containing_block,
+ tree_rank,
+ abspos_fragments,
+ /* float_context = */ None,
+ )
+ },
+ |left_abspos_fragments, mut right_abspos_fragments| {
+ left_abspos_fragments.append(&mut right_abspos_fragments);
+ },
+ )
+ .collect();
+ for fragment in &mut fragments {
+ place_block_level_fragment(fragment, &mut placement_state)
+ }
+ }
+
+ adjust_static_positions(
+ &mut absolutely_positioned_fragments[abspos_so_far..],
+ &mut fragments,
+ tree_rank,
+ );
+
+ FlowChildren {
+ fragments,
+ block_size: placement_state.current_block_direction_position,
+ collapsible_margins_in_children: CollapsedBlockMargins {
+ collapsed_through: placement_state
+ .next_in_flow_margin_collapses_with_parent_start_margin,
+ start: placement_state.start_margin,
+ end: placement_state.current_margin,
+ },
+ }
+}
+
+impl BlockLevelBox {
+ fn layout<'a>(
+ &'a self,
+ containing_block: &ContainingBlock,
+ tree_rank: usize,
+ absolutely_positioned_fragments: &mut Vec<AbsolutelyPositionedFragment<'a>>,
+ float_context: Option<&mut FloatContext>,
+ ) -> Fragment {
+ match self {
+ BlockLevelBox::SameFormattingContextBlock { style, contents } => {
+ Fragment::Box(layout_in_flow_non_replaced_block_level(
+ containing_block,
+ absolutely_positioned_fragments,
+ style,
+ BlockLevelKind::SameFormattingContextBlock,
+ |containing_block, nested_abspos, collapsible_with_parent_start_margin| {
+ contents.layout(
+ containing_block,
+ tree_rank,
+ nested_abspos,
+ float_context,
+ collapsible_with_parent_start_margin,
+ )
+ },
+ ))
+ }
+ BlockLevelBox::Independent { style, contents } => match contents.as_replaced() {
+ Ok(replaced) => {
+ // FIXME
+ match *replaced {}
+ }
+ Err(contents) => Fragment::Box(layout_in_flow_non_replaced_block_level(
+ containing_block,
+ absolutely_positioned_fragments,
+ style,
+ BlockLevelKind::EstablishesAnIndependentFormattingContext,
+ |containing_block, nested_abspos, _| {
+ contents.layout(containing_block, tree_rank, nested_abspos)
+ },
+ )),
+ },
+ BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(box_) => {
+ absolutely_positioned_fragments.push(box_.layout(Vec2::zero(), tree_rank));
+ Fragment::Anonymous(AnonymousFragment::no_op(containing_block.mode))
+ }
+ BlockLevelBox::OutOfFlowFloatBox(_box_) => {
+ // TODO
+ Fragment::Anonymous(AnonymousFragment::no_op(containing_block.mode))
+ }
+ }
+ }
+}
+
+#[derive(PartialEq)]
+enum BlockLevelKind {
+ SameFormattingContextBlock,
+ EstablishesAnIndependentFormattingContext,
+}
+
+/// https://drafts.csswg.org/css2/visudet.html#blockwidth
+/// https://drafts.csswg.org/css2/visudet.html#normal-block
+fn layout_in_flow_non_replaced_block_level<'a>(
+ containing_block: &ContainingBlock,
+ absolutely_positioned_fragments: &mut Vec<AbsolutelyPositionedFragment<'a>>,
+ style: &Arc<ComputedValues>,
+ block_level_kind: BlockLevelKind,
+ layout_contents: impl FnOnce(
+ &ContainingBlock,
+ &mut Vec<AbsolutelyPositionedFragment<'a>>,
+ CollapsibleWithParentStartMargin,
+ ) -> FlowChildren,
+) -> BoxFragment {
+ let cbis = containing_block.inline_size;
+ let padding = style.padding().percentages_relative_to(cbis);
+ let border = style.border_width().percentages_relative_to(cbis);
+ let mut computed_margin = style.margin().percentages_relative_to(cbis);
+ let pb = &padding + &border;
+ let box_size = style.box_size();
+ let inline_size = box_size.inline.percentage_relative_to(cbis);
+ if let LengthOrAuto::Length(is) = inline_size {
+ let inline_margins = cbis - is - pb.inline_sum();
+ use LengthOrAuto::*;
+ match (
+ &mut computed_margin.inline_start,
+ &mut computed_margin.inline_end,
+ ) {
+ (s @ &mut Auto, e @ &mut Auto) => {
+ *s = Length(inline_margins / 2.);
+ *e = Length(inline_margins / 2.);
+ }
+ (s @ &mut Auto, _) => {
+ *s = Length(inline_margins);
+ }
+ (_, e @ &mut Auto) => {
+ *e = Length(inline_margins);
+ }
+ (_, e @ _) => {
+ // Either the inline-end margin is auto,
+ // or we’re over-constrained and we do as if it were.
+ *e = Length(inline_margins);
+ }
+ }
+ }
+ let margin = computed_margin.auto_is(Length::zero);
+ let mut block_margins_collapsed_with_children = CollapsedBlockMargins::from_margin(&margin);
+ let inline_size = inline_size.auto_is(|| cbis - pb.inline_sum() - margin.inline_sum());
+ let block_size = match box_size.block {
+ LengthOrPercentageOrAuto::Length(l) => LengthOrAuto::Length(l),
+ LengthOrPercentageOrAuto::Percentage(p) => containing_block.block_size.map(|cbbs| cbbs * p),
+ LengthOrPercentageOrAuto::Auto => LengthOrAuto::Auto,
+ };
+ let containing_block_for_children = ContainingBlock {
+ inline_size,
+ block_size,
+ mode: style.writing_mode(),
+ };
+ // https://drafts.csswg.org/css-writing-modes/#orthogonal-flows
+ assert_eq!(
+ containing_block.mode, containing_block_for_children.mode,
+ "Mixed writing modes are not supported yet"
+ );
+ let this_start_margin_can_collapse_with_children = CollapsibleWithParentStartMargin(
+ block_level_kind == BlockLevelKind::SameFormattingContextBlock
+ && pb.block_start == Length::zero(),
+ );
+ let this_end_margin_can_collapse_with_children = (block_level_kind, pb.block_end, block_size)
+ == (
+ BlockLevelKind::SameFormattingContextBlock,
+ Length::zero(),
+ LengthOrAuto::Auto,
+ );
+ let mut nested_abspos = vec![];
+ let mut flow_children = layout_contents(
+ &containing_block_for_children,
+ if style.box_.position.is_relatively_positioned() {
+ &mut nested_abspos
+ } else {
+ absolutely_positioned_fragments
+ },
+ this_start_margin_can_collapse_with_children,
+ );
+ if this_start_margin_can_collapse_with_children.0 {
+ block_margins_collapsed_with_children
+ .start
+ .adjoin_assign(&flow_children.collapsible_margins_in_children.start);
+ if flow_children
+ .collapsible_margins_in_children
+ .collapsed_through
+ {
+ block_margins_collapsed_with_children
+ .start
+ .adjoin_assign(&std::mem::replace(
+ &mut flow_children.collapsible_margins_in_children.end,
+ CollapsedMargin::zero(),
+ ));
+ }
+ }
+ if this_end_margin_can_collapse_with_children {
+ block_margins_collapsed_with_children
+ .end
+ .adjoin_assign(&flow_children.collapsible_margins_in_children.end);
+ } else {
+ flow_children.block_size += flow_children.collapsible_margins_in_children.end.solve();
+ }
+ block_margins_collapsed_with_children.collapsed_through =
+ this_start_margin_can_collapse_with_children.0
+ && this_end_margin_can_collapse_with_children
+ && flow_children
+ .collapsible_margins_in_children
+ .collapsed_through;
+ let relative_adjustement = relative_adjustement(style, inline_size, block_size);
+ let block_size = block_size.auto_is(|| flow_children.block_size);
+ let content_rect = Rect {
+ start_corner: Vec2 {
+ block: pb.block_start + relative_adjustement.block,
+ inline: pb.inline_start + relative_adjustement.inline + margin.inline_start,
+ },
+ size: Vec2 {
+ block: block_size,
+ inline: inline_size,
+ },
+ };
+ if style.box_.position.is_relatively_positioned() {
+ AbsolutelyPositionedFragment::in_positioned_containing_block(
+ &nested_abspos,
+ &mut flow_children.fragments,
+ &content_rect.size,
+ &padding,
+ containing_block_for_children.mode,
+ )
+ }
+ BoxFragment {
+ style: style.clone(),
+ children: flow_children.fragments,
+ content_rect,
+ padding,
+ border,
+ margin,
+ block_margins_collapsed_with_children,
+ }
+}
diff --git a/components/layout_2020/flow/root.rs b/components/layout_2020/flow/root.rs
new file mode 100644
index 00000000000..443969c2acb
--- /dev/null
+++ b/components/layout_2020/flow/root.rs
@@ -0,0 +1,122 @@
+use super::*;
+
+impl crate::dom::Document {
+ pub(crate) fn layout(
+ &self,
+ viewport: crate::primitives::Size<crate::primitives::CssPx>,
+ ) -> Vec<Fragment> {
+ BoxTreeRoot::construct(self).layout(viewport)
+ }
+}
+
+struct BoxTreeRoot(BlockFormattingContext);
+
+impl BoxTreeRoot {
+ pub fn construct(document: &dom::Document) -> Self {
+ let author_styles = &document.parse_stylesheets();
+ let context = Context {
+ document,
+ author_styles,
+ };
+ let root_element = document.root_element();
+ let style = style_for_element(context.author_styles, context.document, root_element, None);
+ let (contains_floats, boxes) = construct_for_root_element(&context, root_element, style);
+ Self(BlockFormattingContext {
+ contains_floats: contains_floats == ContainsFloats::Yes,
+ contents: BlockContainer::BlockLevelBoxes(boxes),
+ })
+ }
+}
+
+fn construct_for_root_element(
+ context: &Context,
+ root_element: dom::NodeId,
+ style: Arc<ComputedValues>,
+) -> (ContainsFloats, Vec<Arc<BlockLevelBox>>) {
+ let replaced = ReplacedContent::for_element(root_element, context);
+
+ let display_inside = match style.box_.display {
+ Display::None => return (ContainsFloats::No, Vec::new()),
+ Display::Contents if replaced.is_some() => {
+ // 'display: contents' computes to 'none' for replaced elements
+ return (ContainsFloats::No, Vec::new());
+ }
+ // https://drafts.csswg.org/css-display-3/#transformations
+ Display::Contents => DisplayInside::Flow,
+ // The root element is blockified, ignore DisplayOutside
+ Display::GeneratingBox(DisplayGeneratingBox::OutsideInside { inside, .. }) => inside,
+ };
+
+ if let Some(replaced) = replaced {
+ let _box = match replaced {};
+ #[allow(unreachable_code)]
+ {
+ return (ContainsFloats::No, vec![Arc::new(_box)]);
+ }
+ }
+
+ let contents = IndependentFormattingContext::construct(
+ context,
+ &style,
+ display_inside,
+ Contents::OfElement(root_element),
+ );
+ if style.box_.position.is_absolutely_positioned() {
+ (
+ ContainsFloats::No,
+ vec![Arc::new(BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(
+ AbsolutelyPositionedBox { style, contents },
+ ))],
+ )
+ } else if style.box_.float.is_floating() {
+ (
+ ContainsFloats::Yes,
+ vec![Arc::new(BlockLevelBox::OutOfFlowFloatBox(FloatBox {
+ contents,
+ style,
+ }))],
+ )
+ } else {
+ (
+ ContainsFloats::No,
+ vec![Arc::new(BlockLevelBox::Independent { style, contents })],
+ )
+ }
+}
+
+impl BoxTreeRoot {
+ fn layout(&self, viewport: crate::primitives::Size<crate::primitives::CssPx>) -> Vec<Fragment> {
+ let initial_containing_block_size = Vec2 {
+ inline: Length { px: viewport.width },
+ block: Length {
+ px: viewport.height,
+ },
+ };
+
+ let initial_containing_block = ContainingBlock {
+ inline_size: initial_containing_block_size.inline,
+ block_size: LengthOrAuto::Length(initial_containing_block_size.block),
+ // FIXME: use the document’s mode:
+ // https://drafts.csswg.org/css-writing-modes/#principal-flow
+ mode: (WritingMode::HorizontalTb, Direction::Ltr),
+ };
+ let dummy_tree_rank = 0;
+ let mut absolutely_positioned_fragments = vec![];
+ let mut flow_children = self.0.layout(
+ &initial_containing_block,
+ dummy_tree_rank,
+ &mut absolutely_positioned_fragments,
+ );
+
+ let initial_containing_block = DefiniteContainingBlock {
+ size: initial_containing_block_size,
+ mode: initial_containing_block.mode,
+ };
+ flow_children.fragments.par_extend(
+ absolutely_positioned_fragments
+ .par_iter()
+ .map(|a| a.layout(&initial_containing_block)),
+ );
+ flow_children.fragments
+ }
+}
diff --git a/components/layout_2020/fragments.rs b/components/layout_2020/fragments.rs
new file mode 100644
index 00000000000..f2a4dcd2986
--- /dev/null
+++ b/components/layout_2020/fragments.rs
@@ -0,0 +1,116 @@
+use super::*;
+use crate::text::ShapedSegment;
+
+pub(crate) enum Fragment {
+ Box(BoxFragment),
+ Anonymous(AnonymousFragment),
+ Text(TextFragment),
+}
+
+pub(crate) struct BoxFragment {
+ pub style: Arc<ComputedValues>,
+ pub children: Vec<Fragment>,
+
+ /// From the containing block’s start corner…?
+ /// This might be broken when the containing block is in a different writing mode:
+ /// https://drafts.csswg.org/css-writing-modes/#orthogonal-flows
+ pub content_rect: Rect<Length>,
+
+ pub padding: Sides<Length>,
+ pub border: Sides<Length>,
+ pub margin: Sides<Length>,
+
+ pub block_margins_collapsed_with_children: CollapsedBlockMargins,
+}
+
+pub(crate) struct CollapsedBlockMargins {
+ pub collapsed_through: bool,
+ pub start: CollapsedMargin,
+ pub end: CollapsedMargin,
+}
+
+#[derive(Clone, Copy)]
+pub(crate) struct CollapsedMargin {
+ max_positive: Length,
+ min_negative: Length,
+}
+
+/// Can contain child fragments with relative coordinates, but does not contribute to painting itself.
+pub(crate) struct AnonymousFragment {
+ pub rect: Rect<Length>,
+ pub children: Vec<Fragment>,
+ pub mode: (WritingMode, Direction),
+}
+
+pub(crate) struct TextFragment {
+ pub parent_style: Arc<ComputedValues>,
+ pub content_rect: Rect<Length>,
+ pub text: ShapedSegment,
+}
+
+impl AnonymousFragment {
+ pub fn no_op(mode: (WritingMode, Direction)) -> Self {
+ Self {
+ children: vec![],
+ rect: Rect::zero(),
+ mode,
+ }
+ }
+}
+
+impl BoxFragment {
+ pub fn border_rect(&self) -> Rect<Length> {
+ self.content_rect
+ .inflate(&self.padding)
+ .inflate(&self.border)
+ }
+}
+
+impl CollapsedBlockMargins {
+ pub fn from_margin(margin: &Sides<Length>) -> Self {
+ Self {
+ collapsed_through: false,
+ start: CollapsedMargin::new(margin.block_start),
+ end: CollapsedMargin::new(margin.block_end),
+ }
+ }
+
+ pub fn zero() -> Self {
+ Self {
+ collapsed_through: false,
+ start: CollapsedMargin::zero(),
+ end: CollapsedMargin::zero(),
+ }
+ }
+}
+
+impl CollapsedMargin {
+ pub fn zero() -> Self {
+ Self {
+ max_positive: Length::zero(),
+ min_negative: Length::zero(),
+ }
+ }
+
+ pub fn new(margin: Length) -> Self {
+ Self {
+ max_positive: margin.max(Length::zero()),
+ min_negative: margin.min(Length::zero()),
+ }
+ }
+
+ pub fn adjoin(&self, other: &Self) -> Self {
+ Self {
+ max_positive: self.max_positive.max(other.max_positive),
+ min_negative: self.min_negative.min(other.min_negative),
+ }
+ }
+
+ pub fn adjoin_assign(&mut self, other: &Self) {
+ *self = self.adjoin(other);
+ }
+
+ pub fn solve(&self) -> Length {
+ self.max_positive + self.min_negative
+ }
+}
diff --git a/components/layout_2020/geom.rs b/components/layout_2020/geom.rs
new file mode 100644
index 00000000000..2032a3e72ab
--- /dev/null
+++ b/components/layout_2020/geom.rs
@@ -0,0 +1,330 @@
+pub(crate) use crate::style::values::Length;
+use crate::style::values::{LengthOrAuto, LengthOrPercentage, LengthOrPercentageOrAuto};
+
+pub(crate) mod physical {
+ #[derive(Debug, Clone)]
+ pub(crate) struct Vec2<T> {
+ pub x: T,
+ pub y: T,
+ }
+
+ #[derive(Debug, Clone)]
+ pub(crate) struct Rect<T> {
+ pub top_left: Vec2<T>,
+ pub size: Vec2<T>,
+ }
+
+ #[derive(Debug, Clone)]
+ pub(crate) struct Sides<T> {
+ pub top: T,
+ pub left: T,
+ pub bottom: T,
+ pub right: T,
+ }
+}
+
+pub(crate) mod flow_relative {
+ #[derive(Debug, Clone)]
+ pub(crate) struct Vec2<T> {
+ pub inline: T,
+ pub block: T,
+ }
+
+ #[derive(Debug, Clone)]
+ pub(crate) struct Rect<T> {
+ pub start_corner: Vec2<T>,
+ pub size: Vec2<T>,
+ }
+
+ #[derive(Debug, Clone)]
+ pub(crate) struct Sides<T> {
+ pub inline_start: T,
+ pub inline_end: T,
+ pub block_start: T,
+ pub block_end: T,
+ }
+}
+
+use crate::style::values::{Direction, WritingMode};
+use std::ops::{Add, AddAssign, Sub};
+
+impl<T> Add<&'_ physical::Vec2<T>> for &'_ physical::Vec2<T>
+where
+ T: Add<Output = T> + Copy,
+{
+ type Output = physical::Vec2<T>;
+
+ fn add(self, other: &'_ physical::Vec2<T>) -> Self::Output {
+ physical::Vec2 {
+ x: self.x + other.x,
+ y: self.y + other.y,
+ }
+ }
+}
+
+impl<T: Clone> physical::Vec2<T> {
+ pub fn size_to_flow_relative(&self, mode: (WritingMode, Direction)) -> flow_relative::Vec2<T> {
+ // https://drafts.csswg.org/css-writing-modes/#logical-to-physical
+ let (i, b) = if let (WritingMode::HorizontalTb, _) = mode {
+ (&self.x, &self.y)
+ } else {
+ (&self.y, &self.x)
+ };
+ flow_relative::Vec2 {
+ inline: i.clone(),
+ block: b.clone(),
+ }
+ }
+}
+
+impl<T> Add<&'_ flow_relative::Vec2<T>> for &'_ flow_relative::Vec2<T>
+where
+ T: Add<Output = T> + Copy,
+{
+ type Output = flow_relative::Vec2<T>;
+
+ fn add(self, other: &'_ flow_relative::Vec2<T>) -> Self::Output {
+ flow_relative::Vec2 {
+ inline: self.inline + other.inline,
+ block: self.block + other.block,
+ }
+ }
+}
+
+impl<T> AddAssign<&'_ flow_relative::Vec2<T>> for flow_relative::Vec2<T>
+where
+ T: AddAssign<T> + Copy,
+{
+ fn add_assign(&mut self, other: &'_ flow_relative::Vec2<T>) {
+ self.inline += other.inline;
+ self.block += other.block;
+ }
+}
+
+impl flow_relative::Vec2<Length> {
+ pub fn zero() -> Self {
+ Self {
+ inline: Length::zero(),
+ block: Length::zero(),
+ }
+ }
+}
+
+impl flow_relative::Sides<Length> {
+ pub fn zero() -> Self {
+ Self {
+ inline_start: Length::zero(),
+ inline_end: Length::zero(),
+ block_start: Length::zero(),
+ block_end: Length::zero(),
+ }
+ }
+}
+
+impl flow_relative::Rect<Length> {
+ pub fn zero() -> Self {
+ Self {
+ start_corner: flow_relative::Vec2::zero(),
+ size: flow_relative::Vec2::zero(),
+ }
+ }
+}
+
+impl<T: Clone> flow_relative::Vec2<T> {
+ pub fn size_to_physical(&self, mode: (WritingMode, Direction)) -> physical::Vec2<T> {
+ // https://drafts.csswg.org/css-writing-modes/#logical-to-physical
+ let (x, y) = if let (WritingMode::HorizontalTb, _) = mode {
+ (&self.inline, &self.block)
+ } else {
+ (&self.block, &self.inline)
+ };
+ physical::Vec2 {
+ x: x.clone(),
+ y: y.clone(),
+ }
+ }
+}
+
+impl From<physical::Vec2<Length>> for crate::primitives::Point<crate::primitives::CssPx> {
+ fn from(v: physical::Vec2<Length>) -> Self {
+ crate::primitives::Point::from_lengths(v.x.into(), v.y.into())
+ }
+}
+
+impl<T: Clone> physical::Sides<T> {
+ pub fn to_flow_relative(&self, mode: (WritingMode, Direction)) -> flow_relative::Sides<T> {
+ use Direction::*;
+ use WritingMode::*;
+
+ // https://drafts.csswg.org/css-writing-modes/#logical-to-physical
+ let (bs, be) = match mode.0 {
+ HorizontalTb => (&self.top, &self.bottom),
+ VerticalRl | SidewaysRl => (&self.right, &self.left),
+ VerticalLr | SidewaysLr => (&self.left, &self.right),
+ };
+ let (is, ie) = match mode {
+ (HorizontalTb, Ltr) => (&self.left, &self.right),
+ (HorizontalTb, Rtl) => (&self.right, &self.left),
+ (VerticalRl, Ltr) | (SidewaysRl, Ltr) | (VerticalLr, Ltr) | (SidewaysLr, Rtl) => {
+ (&self.top, &self.bottom)
+ }
+ (VerticalRl, Rtl) | (SidewaysRl, Rtl) | (VerticalLr, Rtl) | (SidewaysLr, Ltr) => {
+ (&self.bottom, &self.top)
+ }
+ };
+ flow_relative::Sides {
+ inline_start: is.clone(),
+ inline_end: ie.clone(),
+ block_start: bs.clone(),
+ block_end: be.clone(),
+ }
+ }
+}
+
+impl<T> flow_relative::Sides<T> {
+ pub fn map<U>(&self, f: impl Fn(&T) -> U) -> flow_relative::Sides<U> {
+ flow_relative::Sides {
+ inline_start: f(&self.inline_start),
+ inline_end: f(&self.inline_end),
+ block_start: f(&self.block_start),
+ block_end: f(&self.block_end),
+ }
+ }
+
+ pub fn map_inline_and_block_axes<U>(
+ &self,
+ inline_f: impl Fn(&T) -> U,
+ block_f: impl Fn(&T) -> U,
+ ) -> flow_relative::Sides<U> {
+ flow_relative::Sides {
+ inline_start: inline_f(&self.inline_start),
+ inline_end: inline_f(&self.inline_end),
+ block_start: block_f(&self.block_start),
+ block_end: block_f(&self.block_end),
+ }
+ }
+
+ pub fn inline_sum(&self) -> T::Output
+ where
+ T: Add + Copy,
+ {
+ self.inline_start + self.inline_end
+ }
+
+ pub fn block_sum(&self) -> T::Output
+ where
+ T: Add + Copy,
+ {
+ self.block_start + self.block_end
+ }
+
+ pub fn start_corner(&self) -> flow_relative::Vec2<T>
+ where
+ T: Clone,
+ {
+ flow_relative::Vec2 {
+ inline: self.inline_start.clone(),
+ block: self.block_start.clone(),
+ }
+ }
+}
+
+impl flow_relative::Sides<LengthOrPercentage> {
+ pub fn percentages_relative_to(&self, basis: Length) -> flow_relative::Sides<Length> {
+ self.map(|s| s.percentage_relative_to(basis))
+ }
+}
+
+impl flow_relative::Sides<LengthOrPercentageOrAuto> {
+ pub fn percentages_relative_to(&self, basis: Length) -> flow_relative::Sides<LengthOrAuto> {
+ self.map(|s| s.percentage_relative_to(basis))
+ }
+}
+
+impl flow_relative::Sides<LengthOrAuto> {
+ pub fn auto_is(&self, f: impl Fn() -> Length) -> flow_relative::Sides<Length> {
+ self.map(|s| s.auto_is(&f))
+ }
+}
+
+impl<T> Add<&'_ flow_relative::Sides<T>> for &'_ flow_relative::Sides<T>
+where
+ T: Add<Output = T> + Copy,
+{
+ type Output = flow_relative::Sides<T>;
+
+ fn add(self, other: &'_ flow_relative::Sides<T>) -> Self::Output {
+ flow_relative::Sides {
+ inline_start: self.inline_start + other.inline_start,
+ inline_end: self.inline_end + other.inline_end,
+ block_start: self.block_start + other.block_start,
+ block_end: self.block_end + other.block_end,
+ }
+ }
+}
+
+impl<T> flow_relative::Rect<T> {
+ pub fn inflate(&self, sides: &flow_relative::Sides<T>) -> Self
+ where
+ T: Add<Output = T> + Copy,
+ T: Sub<Output = T> + Copy,
+ {
+ flow_relative::Rect {
+ start_corner: flow_relative::Vec2 {
+ inline: self.start_corner.inline - sides.inline_start,
+ block: self.start_corner.block - sides.block_start,
+ },
+ size: flow_relative::Vec2 {
+ inline: self.size.inline + sides.inline_sum(),
+ block: self.size.block + sides.block_sum(),
+ },
+ }
+ }
+
+ pub fn to_physical(
+ &self,
+ mode: (WritingMode, Direction),
+ // Will be needed for other writing modes
+ // FIXME: what if the containing block has a different mode?
+ // https://drafts.csswg.org/css-writing-modes/#orthogonal-flows
+ _containing_block: &physical::Rect<T>,
+ ) -> physical::Rect<T>
+ where
+ T: Clone,
+ {
+ // Top-left corner
+ let (tl_x, tl_y) = if let (WritingMode::HorizontalTb, Direction::Ltr) = mode {
+ (&self.start_corner.inline, &self.start_corner.block)
+ } else {
+ unimplemented!()
+ };
+ physical::Rect {
+ top_left: physical::Vec2 {
+ x: tl_x.clone(),
+ y: tl_y.clone(),
+ },
+ size: self.size.size_to_physical(mode),
+ }
+ }
+}
+
+impl<T> physical::Rect<T> {
+ pub fn translate(&self, by: &physical::Vec2<T>) -> Self
+ where
+ T: Add<Output = T> + Copy,
+ {
+ physical::Rect {
+ top_left: &self.top_left + by,
+ size: self.size.clone(),
+ }
+ }
+}
+
+impl From<physical::Rect<Length>> for crate::primitives::Rect<crate::primitives::CssPx> {
+ fn from(r: physical::Rect<Length>) -> Self {
+ crate::primitives::Rect {
+ origin: crate::primitives::Point::new(r.top_left.x.px, r.top_left.y.px),
+ size: crate::primitives::Size::new(r.size.x.px, r.size.y.px),
+ }
+ }
+}
diff --git a/components/layout_2020/positioned.rs b/components/layout_2020/positioned.rs
new file mode 100644
index 00000000000..9794d1acdae
--- /dev/null
+++ b/components/layout_2020/positioned.rs
@@ -0,0 +1,328 @@
+use super::*;
+use rayon::prelude::*;
+
+#[derive(Debug)]
+pub(super) struct AbsolutelyPositionedBox {
+ pub style: Arc<ComputedValues>,
+ pub contents: IndependentFormattingContext,
+}
+
+#[derive(Debug)]
+pub(super) struct AbsolutelyPositionedFragment<'box_> {
+ absolutely_positioned_box: &'box_ AbsolutelyPositionedBox,
+
+ /// The rank of the child from which this absolutely positioned fragment
+ /// came from, when doing the layout of a block container. Used to compute
+ /// static positions when going up the tree.
+ pub(super) tree_rank: usize,
+
+ pub(super) inline_start: AbsoluteBoxOffsets<LengthOrPercentage>,
+ inline_size: LengthOrPercentageOrAuto,
+
+ pub(super) block_start: AbsoluteBoxOffsets<LengthOrPercentage>,
+ block_size: LengthOrPercentageOrAuto,
+}
+
+#[derive(Clone, Copy, Debug)]
+pub(super) enum AbsoluteBoxOffsets<NonStatic> {
+ StaticStart { start: Length },
+ Start { start: NonStatic },
+ End { end: NonStatic },
+ Both { start: NonStatic, end: NonStatic },
+}
+
+impl AbsolutelyPositionedBox {
+ pub(super) fn layout<'a>(
+ &'a self,
+ initial_start_corner: Vec2<Length>,
+ tree_rank: usize,
+ ) -> AbsolutelyPositionedFragment {
+ let style = &self.style;
+ let box_offsets = style.box_offsets();
+ let box_size = style.box_size();
+
+ let inline_size = box_size.inline;
+ let block_size = box_size.block;
+
+ fn absolute_box_offsets(
+ initial_static_start: Length,
+ start: LengthOrPercentageOrAuto,
+ end: LengthOrPercentageOrAuto,
+ ) -> AbsoluteBoxOffsets<LengthOrPercentage> {
+ match (start.non_auto(), end.non_auto()) {
+ (None, None) => AbsoluteBoxOffsets::StaticStart {
+ start: initial_static_start,
+ },
+ (Some(start), Some(end)) => AbsoluteBoxOffsets::Both { start, end },
+ (None, Some(end)) => AbsoluteBoxOffsets::End { end },
+ (Some(start), None) => AbsoluteBoxOffsets::Start { start },
+ }
+ }
+
+ let inline_start = absolute_box_offsets(
+ initial_start_corner.inline,
+ box_offsets.inline_start,
+ box_offsets.inline_end,
+ );
+ let block_start = absolute_box_offsets(
+ initial_start_corner.block,
+ box_offsets.block_start,
+ box_offsets.block_end,
+ );
+
+ AbsolutelyPositionedFragment {
+ absolutely_positioned_box: self,
+ tree_rank,
+ inline_start,
+ inline_size,
+ block_start,
+ block_size,
+ }
+ }
+}
+
+impl<'a> AbsolutelyPositionedFragment<'a> {
+ pub(super) fn in_positioned_containing_block(
+ absolute: &[Self],
+ fragments: &mut Vec<Fragment>,
+ content_rect_size: &Vec2<Length>,
+ padding: &Sides<Length>,
+ mode: (WritingMode, Direction),
+ ) {
+ if absolute.is_empty() {
+ return;
+ }
+ let padding_rect = Rect {
+ size: content_rect_size.clone(),
+ // Ignore the content rect’s position in its own containing block:
+ start_corner: Vec2::zero(),
+ }
+ .inflate(&padding);
+ let containing_block = DefiniteContainingBlock {
+ size: padding_rect.size.clone(),
+ mode,
+ };
+ fragments.push(Fragment::Anonymous(AnonymousFragment {
+ children: absolute
+ .par_iter()
+ .map(|a| a.layout(&containing_block))
+ .collect(),
+ rect: padding_rect,
+ mode,
+ }))
+ }
+
+ pub(super) fn layout(&self, containing_block: &DefiniteContainingBlock) -> Fragment {
+ let style = &self.absolutely_positioned_box.style;
+ let cbis = containing_block.size.inline;
+ let cbbs = containing_block.size.block;
+
+ let padding = style.padding().percentages_relative_to(cbis);
+ let border = style.border_width().percentages_relative_to(cbis);
+ let computed_margin = style.margin().percentages_relative_to(cbis);
+ let pb = &padding + &border;
+
+ enum Anchor {
+ Start(Length),
+ End(Length),
+ }
+
+ fn solve_axis(
+ containing_size: Length,
+ padding_border_sum: Length,
+ computed_margin_start: LengthOrAuto,
+ computed_margin_end: LengthOrAuto,
+ solve_margins: impl FnOnce(Length) -> (Length, Length),
+ box_offsets: AbsoluteBoxOffsets<LengthOrPercentage>,
+ size: LengthOrPercentageOrAuto,
+ ) -> (Anchor, LengthOrAuto, Length, Length) {
+ let size = size.percentage_relative_to(containing_size);
+ match box_offsets {
+ AbsoluteBoxOffsets::StaticStart { start } => (
+ Anchor::Start(start),
+ size,
+ computed_margin_start.auto_is(Length::zero),
+ computed_margin_end.auto_is(Length::zero),
+ ),
+ AbsoluteBoxOffsets::Start { start } => (
+ Anchor::Start(start.percentage_relative_to(containing_size)),
+ size,
+ computed_margin_start.auto_is(Length::zero),
+ computed_margin_end.auto_is(Length::zero),
+ ),
+ AbsoluteBoxOffsets::End { end } => (
+ Anchor::End(end.percentage_relative_to(containing_size)),
+ size,
+ computed_margin_start.auto_is(Length::zero),
+ computed_margin_end.auto_is(Length::zero),
+ ),
+ AbsoluteBoxOffsets::Both { start, end } => {
+ let start = start.percentage_relative_to(containing_size);
+ let end = end.percentage_relative_to(containing_size);
+
+ let mut margin_start = computed_margin_start.auto_is(Length::zero);
+ let mut margin_end = computed_margin_end.auto_is(Length::zero);
+
+ let size = if let LengthOrAuto::Length(size) = size {
+ use LengthOrAuto::Auto;
+ let margins = containing_size - start - end - padding_border_sum - size;
+ match (computed_margin_start, computed_margin_end) {
+ (Auto, Auto) => {
+ let (s, e) = solve_margins(margins);
+ margin_start = s;
+ margin_end = e;
+ }
+ (Auto, LengthOrAuto::Length(end)) => {
+ margin_start = margins - end;
+ }
+ (LengthOrAuto::Length(start), Auto) => {
+ margin_end = margins - start;
+ }
+ (LengthOrAuto::Length(_), LengthOrAuto::Length(_)) => {}
+ }
+ size
+ } else {
+ // FIXME(nox): What happens if that is negative?
+ containing_size
+ - start
+ - end
+ - padding_border_sum
+ - margin_start
+ - margin_end
+ };
+ (
+ Anchor::Start(start),
+ LengthOrAuto::Length(size),
+ margin_start,
+ margin_end,
+ )
+ }
+ }
+ }
+
+ let (inline_anchor, inline_size, margin_inline_start, margin_inline_end) = solve_axis(
+ cbis,
+ pb.inline_sum(),
+ computed_margin.inline_start,
+ computed_margin.inline_end,
+ |margins| {
+ if margins.px >= 0. {
+ (margins / 2., margins / 2.)
+ } else {
+ (Length::zero(), margins)
+ }
+ },
+ self.inline_start,
+ self.inline_size,
+ );
+
+ let (block_anchor, block_size, margin_block_start, margin_block_end) = solve_axis(
+ cbis,
+ pb.block_sum(),
+ computed_margin.block_start,
+ computed_margin.block_end,
+ |margins| (margins / 2., margins / 2.),
+ self.block_start,
+ self.block_size,
+ );
+
+ let margin = Sides {
+ inline_start: margin_inline_start,
+ inline_end: margin_inline_end,
+ block_start: margin_block_start,
+ block_end: margin_block_end,
+ };
+
+ let inline_size = inline_size.auto_is(|| {
+ let available_size = match inline_anchor {
+ Anchor::Start(start) => cbis - start - pb.inline_sum() - margin.inline_sum(),
+ Anchor::End(end) => cbis - end - pb.inline_sum() - margin.inline_sum(),
+ };
+
+ // FIXME(nox): shrink-to-fit.
+ available_size
+ });
+
+ let containing_block_for_children = ContainingBlock {
+ inline_size,
+ block_size,
+ mode: style.writing_mode(),
+ };
+ // https://drafts.csswg.org/css-writing-modes/#orthogonal-flows
+ assert_eq!(
+ containing_block.mode, containing_block_for_children.mode,
+ "Mixed writing modes are not supported yet"
+ );
+ let dummy_tree_rank = 0;
+ let mut absolutely_positioned_fragments = vec![];
+ let mut flow_children = self.absolutely_positioned_box.contents.layout(
+ &containing_block_for_children,
+ dummy_tree_rank,
+ &mut absolutely_positioned_fragments,
+ );
+
+ let inline_start = match inline_anchor {
+ Anchor::Start(start) => start + pb.inline_start + margin.inline_start,
+ Anchor::End(end) => cbbs - end - pb.inline_end - margin.inline_end - inline_size,
+ };
+
+ let block_size = block_size.auto_is(|| flow_children.block_size);
+ let block_start = match block_anchor {
+ Anchor::Start(start) => start + pb.block_start + margin.block_start,
+ Anchor::End(end) => cbbs - end - pb.block_end - margin.block_end - block_size,
+ };
+
+ let content_rect = Rect {
+ start_corner: Vec2 {
+ inline: inline_start,
+ block: block_start,
+ },
+ size: Vec2 {
+ inline: inline_size,
+ block: block_size,
+ },
+ };
+
+ AbsolutelyPositionedFragment::in_positioned_containing_block(
+ &absolutely_positioned_fragments,
+ &mut flow_children.fragments,
+ &content_rect.size,
+ &padding,
+ containing_block_for_children.mode,
+ );
+
+ Fragment::Box(BoxFragment {
+ style: style.clone(),
+ children: flow_children.fragments,
+ content_rect,
+ padding,
+ border,
+ margin,
+ block_margins_collapsed_with_children: CollapsedBlockMargins::zero(),
+ })
+ }
+}
+
+pub(super) fn adjust_static_positions(
+ absolutely_positioned_fragments: &mut [AbsolutelyPositionedFragment],
+ child_fragments: &mut [Fragment],
+ tree_rank_in_parent: usize,
+) {
+ for abspos_fragment in absolutely_positioned_fragments {
+ let child_fragment_rect = match &child_fragments[abspos_fragment.tree_rank] {
+ Fragment::Box(b) => &b.content_rect,
+ Fragment::Anonymous(a) => &a.rect,
+ _ => unreachable!(),
+ };
+
+ abspos_fragment.tree_rank = tree_rank_in_parent;
+
+ if let AbsoluteBoxOffsets::StaticStart { start } = &mut abspos_fragment.inline_start {
+ *start += child_fragment_rect.start_corner.inline;
+ }
+
+ if let AbsoluteBoxOffsets::StaticStart { start } = &mut abspos_fragment.block_start {
+ *start += child_fragment_rect.start_corner.block;
+ }
+ }
+}
diff --git a/components/layout_2020/primitives.rs b/components/layout_2020/primitives.rs
new file mode 100644
index 00000000000..0991478ac06
--- /dev/null
+++ b/components/layout_2020/primitives.rs
@@ -0,0 +1,28 @@
+use crate::text;
+
+/// Origin at top-left corner, unit `1px`
+pub struct CssPx;
+
+pub use euclid::point2 as point;
+pub use euclid::rect;
+pub type Length<U> = euclid::Length<f32, U>;
+pub type Point<U> = euclid::TypedPoint2D<f32, U>;
+pub type Size<U> = euclid::TypedSize2D<f32, U>;
+pub type Rect<U> = euclid::TypedRect<f32, U>;
+pub type SideOffsets<U> = euclid::TypedSideOffsets2D<f32, U>;
+pub type Scale<Src, Dest> = euclid::TypedScale<f32, Src, Dest>;
+
+#[derive(Copy, Clone, PartialEq)]
+pub struct RGBA(pub f32, pub f32, pub f32, pub f32);
+
+pub struct TextRun<'a> {
+ pub segment: &'a text::ShapedSegment,
+ pub font_size: Length<CssPx>,
+ pub origin: Point<CssPx>,
+}
+
+impl From<cssparser::RGBA> for RGBA {
+ fn from(c: cssparser::RGBA) -> Self {
+ RGBA(c.red_f32(), c.green_f32(), c.blue_f32(), c.alpha_f32())
+ }
+}
diff --git a/components/layout_2020/replaced.rs b/components/layout_2020/replaced.rs
new file mode 100644
index 00000000000..312326c84af
--- /dev/null
+++ b/components/layout_2020/replaced.rs
@@ -0,0 +1,14 @@
+use super::*;
+use crate::dom::NodeId;
+
+#[derive(Debug)]
+pub(super) enum ReplacedContent {
+ // Not implemented yet
+}
+
+impl ReplacedContent {
+ pub fn for_element(_element: NodeId, _context: &Context) -> Option<Self> {
+ // FIXME: implement <img> etc.
+ None
+ }
+}