aboutsummaryrefslogtreecommitdiffstats
path: root/components/layout_2020/flow/construct.rs
diff options
context:
space:
mode:
Diffstat (limited to 'components/layout_2020/flow/construct.rs')
-rw-r--r--components/layout_2020/flow/construct.rs393
1 files changed, 147 insertions, 246 deletions
diff --git a/components/layout_2020/flow/construct.rs b/components/layout_2020/flow/construct.rs
index 586a109e918..bba6e2eec1f 100644
--- a/components/layout_2020/flow/construct.rs
+++ b/components/layout_2020/flow/construct.rs
@@ -13,6 +13,7 @@ use style::selector_parser::PseudoElement;
use style::str::char_is_whitespace;
use style::values::specified::text::TextDecorationLine;
+use super::inline::construct::InlineFormattingContextBuilder;
use super::OutsideMarker;
use crate::cell::ArcRefCell;
use crate::context::LayoutContext;
@@ -21,8 +22,7 @@ use crate::dom_traversal::{
Contents, NodeAndStyleInfo, NonReplacedContents, PseudoElementContentItem, TraversalHandler,
};
use crate::flow::float::FloatBox;
-use crate::flow::inline::{InlineBox, InlineFormattingContext, InlineLevelBox};
-use crate::flow::text_run::TextRun;
+use crate::flow::inline::{InlineFormattingContext, InlineLevelBox};
use crate::flow::{BlockContainer, BlockFormattingContext, BlockLevelBox};
use crate::formatting_contexts::IndependentFormattingContext;
use crate::positioned::AbsolutelyPositionedBox;
@@ -56,28 +56,6 @@ impl BlockFormattingContext {
contains_floats,
}
}
-
- pub fn construct_for_text_runs(
- runs: impl Iterator<Item = TextRun>,
- layout_context: &LayoutContext,
- text_decoration_line: TextDecorationLine,
- ) -> Self {
- let inline_level_boxes = runs
- .map(|run| ArcRefCell::new(InlineLevelBox::TextRun(run)))
- .collect();
-
- let ifc = InlineFormattingContext {
- inline_level_boxes,
- font_metrics: Vec::new(),
- text_decoration_line,
- has_first_formatted_line: true,
- contains_floats: false,
- };
- Self {
- contents: BlockContainer::construct_inline_formatting_context(layout_context, ifc),
- contains_floats: false,
- }
- }
}
struct BlockLevelJob<'dom, Node> {
@@ -151,29 +129,14 @@ pub(crate) struct BlockContainerBuilder<'dom, 'style, Node> {
/// (see `handle_block_level_element`).
block_level_boxes: Vec<BlockLevelJob<'dom, Node>>,
- /// 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>,
+ /// Whether or not this builder has yet produced a block which would be
+ /// be considered the first line for the purposes of `text-indent`.
+ have_already_seen_first_line_for_text_indent: bool,
+
+ /// The propagated [`TextDecorationLine`].
+ text_decoration_line: TextDecorationLine,
+
+ inline_formatting_context_builder: InlineFormattingContextBuilder,
/// The style of the anonymous block boxes pushed to the list of block-level
/// boxes, if any (see `end_ongoing_inline_formatting_context`).
@@ -215,16 +178,6 @@ impl BlockContainer {
contents.traverse(context, info, &mut builder);
builder.finish()
}
-
- pub(super) fn construct_inline_formatting_context(
- layout_context: &LayoutContext,
- mut ifc: InlineFormattingContext,
- ) -> Self {
- // TODO(mrobinson): Perhaps it would be better to iteratively break and shape the contents
- // of the IFC, and not wait until it is completely built.
- ifc.break_and_shape_text(layout_context);
- BlockContainer::InlineFormattingContext(ifc)
- }
}
impl<'dom, 'style, Node> BlockContainerBuilder<'dom, 'style, Node>
@@ -242,29 +195,33 @@ where
context,
info,
block_level_boxes: Vec::new(),
- ongoing_inline_formatting_context: InlineFormattingContext::new(
- text_decoration_line,
- /* has_first_formatted_line = */ true,
- ),
- ongoing_inline_boxes_stack: Vec::new(),
+ text_decoration_line,
+ have_already_seen_first_line_for_text_indent: false,
anonymous_style: None,
anonymous_table_content: Vec::new(),
+ inline_formatting_context_builder: InlineFormattingContextBuilder::new(),
}
}
pub(crate) fn finish(mut self) -> BlockContainer {
- debug_assert!(self.ongoing_inline_boxes_stack.is_empty());
+ debug_assert!(!self
+ .inline_formatting_context_builder
+ .currently_processing_inline_box());
self.finish_anonymous_table_if_needed();
- if !self.ongoing_inline_formatting_context.is_empty() {
+ if let Some(inline_formatting_context) = self.inline_formatting_context_builder.finish(
+ self.context,
+ self.text_decoration_line,
+ !self.have_already_seen_first_line_for_text_indent,
+ ) {
+ // There are two options here. This block was composed of both one or more inline formatting contexts
+ // and child blocks OR this block was a single inline formatting context. In the latter case, we
+ // just return the inline formatting context as the block itself.
if self.block_level_boxes.is_empty() {
- return BlockContainer::construct_inline_formatting_context(
- self.context,
- self.ongoing_inline_formatting_context,
- );
+ return BlockContainer::InlineFormattingContext(inline_formatting_context);
}
- self.end_ongoing_inline_formatting_context();
+ self.push_block_level_job_for_inline_formatting_context(inline_formatting_context);
}
let context = self.context;
@@ -288,16 +245,26 @@ where
return;
}
+ // From https://drafts.csswg.org/css-tables/#fixup-algorithm:
+ // > If the box’s parent is an inline, run-in, or ruby box (or any box that would perform
+ // > inlinification of its children), then an inline-table box must be generated; otherwise
+ // > it must be a table box.
+ //
+ // Note that text content in the inline formatting context isn't enough to force the
+ // creation of an inline table. It requires the parent to be an inline box.
+ let inline_table = self
+ .inline_formatting_context_builder
+ .currently_processing_inline_box();
+
// Text decorations are not propagated to atomic inline-level descendants.
// From https://drafts.csswg.org/css2/#lining-striking-props:
// > Note that text decorations are not propagated to floating and absolutely
// > positioned descendants, nor to the contents of atomic inline-level descendants
// > such as inline blocks and inline tables.
- let inline_table = !self.ongoing_inline_boxes_stack.is_empty();
let propagated_text_decoration_line = if inline_table {
TextDecorationLine::NONE
} else {
- self.ongoing_inline_formatting_context.text_decoration_line
+ self.text_decoration_line
};
let contents: Vec<AnonymousTableContent<'dom, Node>> =
@@ -315,12 +282,11 @@ where
);
if inline_table {
- self.current_inline_level_boxes()
- .push(ArcRefCell::new(InlineLevelBox::Atomic(ifc)));
+ self.inline_formatting_context_builder.push_atomic(ifc);
} else {
- self.end_ongoing_inline_formatting_context();
let anonymous_info = self.info.new_anonymous(ifc.style().clone());
let table_block = ArcRefCell::new(BlockLevelBox::Independent(ifc));
+ self.end_ongoing_inline_formatting_context();
self.block_level_boxes.push(BlockLevelJob {
info: anonymous_info,
box_slot: BoxSlot::dummy(),
@@ -390,39 +356,23 @@ where
}
}
- fn handle_text(&mut self, info: &NodeAndStyleInfo<Node>, input: Cow<'dom, str>) {
- if input.is_empty() {
+ fn handle_text(&mut self, info: &NodeAndStyleInfo<Node>, text: Cow<'dom, str>) {
+ if text.is_empty() {
return;
}
// If we are building an anonymous table ie this text directly followed internal
// table elements that did not have a `<table>` ancestor, then we forward all
// whitespace to the table builder.
- if !self.anonymous_table_content.is_empty() && input.chars().all(char_is_whitespace) {
+ if !self.anonymous_table_content.is_empty() && text.chars().all(char_is_whitespace) {
self.anonymous_table_content
- .push(AnonymousTableContent::Text(info.clone(), input));
+ .push(AnonymousTableContent::Text(info.clone(), text));
return;
} else {
self.finish_anonymous_table_if_needed();
}
- // TODO: We can do better here than `push_str` and wait until we are breaking and
- // shaping text to allocate space big enough for the final text. It would require
- // collecting all Cow strings into a vector and passing them along to text breaking
- // and shaping during final InlineFormattingContext construction.
- let inlines = self.current_inline_level_boxes();
- if let Some(mut last_box) = inlines.last_mut().map(|last| last.borrow_mut()) {
- if let InlineLevelBox::TextRun(ref mut text_run) = *last_box {
- text_run.text.push_str(&input);
- return;
- }
- }
-
- inlines.push(ArcRefCell::new(InlineLevelBox::TextRun(TextRun::new(
- info.into(),
- Arc::clone(&info.style),
- input.into(),
- ))));
+ self.inline_formatting_context_builder.push_text(text, info);
}
}
@@ -471,45 +421,11 @@ where
display_inside: DisplayInside,
contents: Contents,
) -> ArcRefCell<InlineLevelBox> {
- let box_ = if let (DisplayInside::Flow { is_list_item }, false) =
+ let (DisplayInside::Flow { is_list_item }, false) =
(display_inside, contents.is_replaced())
- {
- // We found un inline box.
- // Whatever happened before, all we need to do before recurring
- // is to remember this ongoing inline level box.
- self.ongoing_inline_boxes_stack.push(InlineBox {
- base_fragment_info: info.into(),
- style: info.style.clone(),
- is_first_fragment: true,
- is_last_fragment: false,
- children: vec![],
- default_font_index: None,
- });
-
- if is_list_item {
- if let Some(marker_contents) = crate::lists::make_marker(self.context, info) {
- // Ignore `list-style-position` here:
- // “If the list item is an inline box: this value is equivalent to `inside`.”
- // https://drafts.csswg.org/css-lists/#list-style-position-outside
- self.handle_list_item_marker_inside(info, marker_contents)
- }
- }
-
- // `unwrap` doesn’t panic here because `is_replaced` returned `false`.
- NonReplacedContents::try_from(contents)
- .unwrap()
- .traverse(self.context, info, self);
-
- self.finish_anonymous_table_if_needed();
-
- let mut inline_box = self
- .ongoing_inline_boxes_stack
- .pop()
- .expect("no ongoing inline level box found");
- inline_box.is_last_fragment = true;
- ArcRefCell::new(InlineLevelBox::InlineBox(inline_box))
- } else {
- ArcRefCell::new(InlineLevelBox::Atomic(
+ else {
+ // If this inline element is an atomic, handle it and return.
+ return self.inline_formatting_context_builder.push_atomic(
IndependentFormattingContext::construct(
self.context,
info,
@@ -518,10 +434,32 @@ where
// Text decorations are not propagated to atomic inline-level descendants.
TextDecorationLine::NONE,
),
- ))
+ );
};
- self.current_inline_level_boxes().push(box_.clone());
- box_
+
+ // Otherwise, this is just a normal inline box. Whatever happened before, all we need to do
+ // before recurring is to remember this ongoing inline level box.
+ self.inline_formatting_context_builder
+ .start_inline_box(info);
+
+ if is_list_item {
+ if let Some(marker_contents) = crate::lists::make_marker(self.context, info) {
+ // Ignore `list-style-position` here:
+ // “If the list item is an inline box: this value is equivalent to `inside`.”
+ // https://drafts.csswg.org/css-lists/#list-style-position-outside
+ self.handle_list_item_marker_inside(info, marker_contents)
+ }
+ }
+
+ // `unwrap` doesn’t panic here because `is_replaced` returned `false`.
+ NonReplacedContents::try_from(contents)
+ .unwrap()
+ .traverse(self.context, info, self);
+
+ self.finish_anonymous_table_if_needed();
+
+ // Finish the inline box in our IFC builder and return it for `display: contents`.
+ self.inline_formatting_context_builder.end_inline_box()
}
fn handle_block_level_element(
@@ -532,53 +470,23 @@ where
box_slot: BoxSlot<'dom>,
) {
// 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 {
- base_fragment_info: ongoing.base_fragment_info,
- style: ongoing.style.clone(),
- is_first_fragment: ongoing.is_first_fragment,
- // The fragmented boxes before the block level element
- // are obviously not the last fragment.
- is_last_fragment: false,
- children: std::mem::take(&mut ongoing.children),
- default_font_index: None,
- };
- ongoing.is_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(ArcRefCell::new(fragmented_inline));
- fragmented_inline = InlineLevelBox::InlineBox(fragmented_parent_inline_box);
- }
-
- self.ongoing_inline_formatting_context
- .inline_level_boxes
- .push(ArcRefCell::new(fragmented_inline));
+ // need to be split around it.
+ //
+ // After calling `split_around_block_and_finish`,
+ // `self.inline_formatting_context_builder` is set up with the state
+ // that we want to have after we push the block below.
+ if let Some(inline_formatting_context) = self
+ .inline_formatting_context_builder
+ .split_around_block_and_finish(
+ self.context,
+ self.text_decoration_line,
+ !self.have_already_seen_first_line_for_text_indent,
+ )
+ {
+ self.push_block_level_job_for_inline_formatting_context(inline_formatting_context);
}
- let propagated_text_decoration_line =
- self.ongoing_inline_formatting_context.text_decoration_line;
-
- // We found a block level element, so the ongoing inline formatting
- // context needs to be ended.
- self.end_ongoing_inline_formatting_context();
-
+ let propagated_text_decoration_line = self.text_decoration_line;
let kind = match contents.try_into() {
Ok(contents) => match display_inside {
DisplayInside::Flow { is_list_item }
@@ -612,6 +520,10 @@ where
box_slot,
kind,
});
+
+ // Any block also counts as the first line for the purposes of text indent. Even if
+ // they don't actually indent.
+ self.have_already_seen_first_line_for_text_indent = true;
}
fn handle_absolutely_positioned_element(
@@ -621,28 +533,28 @@ where
contents: Contents,
box_slot: BoxSlot<'dom>,
) {
- if !self.has_ongoing_inline_formatting_context() {
- let kind = BlockLevelCreator::OutOfFlowAbsolutelyPositionedBox {
- contents,
- display_inside,
- };
- self.block_level_boxes.push(BlockLevelJob {
- info: info.clone(),
- box_slot,
- kind,
- });
- } else {
- let box_ = ArcRefCell::new(InlineLevelBox::OutOfFlowAbsolutelyPositionedBox(
- ArcRefCell::new(AbsolutelyPositionedBox::construct(
+ if !self.inline_formatting_context_builder.is_empty() {
+ let inline_level_box = self
+ .inline_formatting_context_builder
+ .push_absolutely_positioned_box(AbsolutelyPositionedBox::construct(
self.context,
info,
display_inside,
contents,
- )),
- ));
- self.current_inline_level_boxes().push(box_.clone());
- box_slot.set(LayoutBox::InlineLevel(box_))
+ ));
+ box_slot.set(LayoutBox::InlineLevel(inline_level_box));
+ return;
}
+
+ let kind = BlockLevelCreator::OutOfFlowAbsolutelyPositionedBox {
+ contents,
+ display_inside,
+ };
+ self.block_level_boxes.push(BlockLevelJob {
+ info: info.clone(),
+ box_slot,
+ kind,
+ });
}
fn handle_float_element(
@@ -652,56 +564,57 @@ where
contents: Contents,
box_slot: BoxSlot<'dom>,
) {
- if !self.has_ongoing_inline_formatting_context() {
- let kind = BlockLevelCreator::OutOfFlowFloatBox {
- contents,
- display_inside,
- };
- self.block_level_boxes.push(BlockLevelJob {
- info: info.clone(),
- box_slot,
- kind,
- });
- } else {
- let box_ = ArcRefCell::new(InlineLevelBox::OutOfFlowFloatBox(FloatBox::construct(
- self.context,
- info,
- display_inside,
- contents,
- )));
- self.ongoing_inline_formatting_context.contains_floats = true;
- self.current_inline_level_boxes().push(box_.clone());
- box_slot.set(LayoutBox::InlineLevel(box_))
+ if !self.inline_formatting_context_builder.is_empty() {
+ let inline_level_box =
+ self.inline_formatting_context_builder
+ .push_float_box(FloatBox::construct(
+ self.context,
+ info,
+ display_inside,
+ contents,
+ ));
+ box_slot.set(LayoutBox::InlineLevel(inline_level_box));
+ return;
}
+
+ let kind = BlockLevelCreator::OutOfFlowFloatBox {
+ contents,
+ display_inside,
+ };
+ self.block_level_boxes.push(BlockLevelJob {
+ info: info.clone(),
+ box_slot,
+ kind,
+ });
}
fn end_ongoing_inline_formatting_context(&mut self) {
- if self.ongoing_inline_formatting_context.is_empty() {
- // There should never be an empty inline formatting context.
- self.ongoing_inline_formatting_context
- .has_first_formatted_line = false;
- return;
+ if let Some(inline_formatting_context) = self.inline_formatting_context_builder.finish(
+ self.context,
+ self.text_decoration_line,
+ !self.have_already_seen_first_line_for_text_indent,
+ ) {
+ self.push_block_level_job_for_inline_formatting_context(inline_formatting_context);
}
+ }
- let context = self.context;
+ fn push_block_level_job_for_inline_formatting_context(
+ &mut self,
+ inline_formatting_context: InlineFormattingContext,
+ ) {
let block_container_style = &self.info.style;
+ let layout_context = self.context;
let anonymous_style = self.anonymous_style.get_or_insert_with(|| {
- context
+ layout_context
.shared_context()
.stylist
.style_for_anonymous::<Node::ConcreteElement>(
- &context.shared_context().guards,
+ &layout_context.shared_context().guards,
&PseudoElement::ServoAnonymousBox,
block_container_style,
)
});
- let mut ifc = InlineFormattingContext::new(
- self.ongoing_inline_formatting_context.text_decoration_line,
- /* has_first_formatted_line = */ false,
- );
- std::mem::swap(&mut self.ongoing_inline_formatting_context, &mut ifc);
-
let info = self.info.new_anonymous(anonymous_style.clone());
self.block_level_boxes.push(BlockLevelJob {
info,
@@ -709,24 +622,12 @@ where
box_slot: BoxSlot::dummy(),
kind: BlockLevelCreator::SameFormattingContextBlock(
IntermediateBlockContainer::InlineFormattingContext(
- BlockContainer::construct_inline_formatting_context(self.context, ifc),
+ BlockContainer::InlineFormattingContext(inline_formatting_context),
),
),
});
- }
-
- // Retrieves the mutable reference of inline boxes either from the last
- // element of a stack or directly from the formatting context, depending on the situation.
- fn current_inline_level_boxes(&mut self) -> &mut Vec<ArcRefCell<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.is_empty() ||
- !self.ongoing_inline_boxes_stack.is_empty()
+ self.have_already_seen_first_line_for_text_indent = true;
}
}