layout: Flatten inline box storage in InlineFormattingContexts (#32539)

This accomplishes two things:

1. Makes it easier to iterate through all inline formatting context
   items.
2. Will make it possible to easily move back and forth through the tree
   of inline boxes, in order to enable reordering and splitting inline
   boxes on lines -- necessary for BiDi.

Co-authored-by: Rakhi Sharma <atbrakhi@igalia.com>
This commit is contained in:
Martin Robinson 2024-06-19 10:51:10 +02:00 committed by GitHub
parent 4803514196
commit e74075255b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 225 additions and 253 deletions

View file

@ -22,7 +22,7 @@ use crate::cell::ArcRefCell;
use crate::context::LayoutContext; use crate::context::LayoutContext;
use crate::dom_traversal::WhichPseudoElement; use crate::dom_traversal::WhichPseudoElement;
use crate::flexbox::FlexLevelBox; use crate::flexbox::FlexLevelBox;
use crate::flow::inline::InlineLevelBox; use crate::flow::inline::{InlineBox, InlineItem};
use crate::flow::BlockLevelBox; use crate::flow::BlockLevelBox;
use crate::geom::PhysicalSize; use crate::geom::PhysicalSize;
use crate::replaced::{CanvasInfo, CanvasSource}; use crate::replaced::{CanvasInfo, CanvasSource};
@ -39,7 +39,9 @@ pub struct InnerDOMLayoutData {
pub(super) enum LayoutBox { pub(super) enum LayoutBox {
DisplayContents, DisplayContents,
BlockLevel(ArcRefCell<BlockLevelBox>), BlockLevel(ArcRefCell<BlockLevelBox>),
InlineLevel(ArcRefCell<InlineLevelBox>), #[allow(dead_code)]
InlineBox(ArcRefCell<InlineBox>),
InlineLevel(ArcRefCell<InlineItem>),
FlexLevel(ArcRefCell<FlexLevelBox>), FlexLevel(ArcRefCell<FlexLevelBox>),
} }

View file

@ -14,6 +14,7 @@ use style::str::char_is_whitespace;
use style::values::specified::text::TextDecorationLine; use style::values::specified::text::TextDecorationLine;
use super::inline::construct::InlineFormattingContextBuilder; use super::inline::construct::InlineFormattingContextBuilder;
use super::inline::{InlineBox, InlineFormattingContext};
use super::OutsideMarker; use super::OutsideMarker;
use crate::cell::ArcRefCell; use crate::cell::ArcRefCell;
use crate::context::LayoutContext; use crate::context::LayoutContext;
@ -22,7 +23,6 @@ use crate::dom_traversal::{
Contents, NodeAndStyleInfo, NonReplacedContents, PseudoElementContentItem, TraversalHandler, Contents, NodeAndStyleInfo, NonReplacedContents, PseudoElementContentItem, TraversalHandler,
}; };
use crate::flow::float::FloatBox; use crate::flow::float::FloatBox;
use crate::flow::inline::{InlineFormattingContext, InlineLevelBox};
use crate::flow::{BlockContainer, BlockFormattingContext, BlockLevelBox}; use crate::flow::{BlockContainer, BlockFormattingContext, BlockLevelBox};
use crate::formatting_contexts::IndependentFormattingContext; use crate::formatting_contexts::IndependentFormattingContext;
use crate::positioned::AbsolutelyPositionedBox; use crate::positioned::AbsolutelyPositionedBox;
@ -325,9 +325,9 @@ where
self.finish_anonymous_table_if_needed(); self.finish_anonymous_table_if_needed();
match outside { match outside {
DisplayOutside::Inline => box_slot.set(LayoutBox::InlineLevel( DisplayOutside::Inline => {
self.handle_inline_level_element(info, inside, contents), self.handle_inline_level_element(info, inside, contents, box_slot)
)), },
DisplayOutside::Block => { DisplayOutside::Block => {
let box_style = info.style.get_box(); let box_style = info.style.get_box();
// Floats and abspos cause blockification, so they only happen in this case. // Floats and abspos cause blockification, so they only happen in this case.
@ -400,6 +400,7 @@ where
is_list_item: false, is_list_item: false,
}, },
Contents::OfPseudoElement(contents), Contents::OfPseudoElement(contents),
BoxSlot::dummy(),
); );
} }
@ -420,12 +421,13 @@ where
info: &NodeAndStyleInfo<Node>, info: &NodeAndStyleInfo<Node>,
display_inside: DisplayInside, display_inside: DisplayInside,
contents: Contents, contents: Contents,
) -> ArcRefCell<InlineLevelBox> { box_slot: BoxSlot<'dom>,
) {
let (DisplayInside::Flow { is_list_item }, false) = let (DisplayInside::Flow { is_list_item }, false) =
(display_inside, contents.is_replaced()) (display_inside, contents.is_replaced())
else { else {
// If this inline element is an atomic, handle it and return. // If this inline element is an atomic, handle it and return.
return self.inline_formatting_context_builder.push_atomic( let atomic = self.inline_formatting_context_builder.push_atomic(
IndependentFormattingContext::construct( IndependentFormattingContext::construct(
self.context, self.context,
info, info,
@ -435,12 +437,14 @@ where
TextDecorationLine::NONE, TextDecorationLine::NONE,
), ),
); );
box_slot.set(LayoutBox::InlineLevel(atomic));
return;
}; };
// Otherwise, this is just a normal inline box. Whatever happened before, all we need to do // 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. // before recurring is to remember this ongoing inline level box.
self.inline_formatting_context_builder self.inline_formatting_context_builder
.start_inline_box(info); .start_inline_box(InlineBox::new(info));
if is_list_item { if is_list_item {
if let Some(marker_contents) = crate::lists::make_marker(self.context, info) { if let Some(marker_contents) = crate::lists::make_marker(self.context, info) {
@ -458,8 +462,9 @@ where
self.finish_anonymous_table_if_needed(); self.finish_anonymous_table_if_needed();
// Finish the inline box in our IFC builder and return it for `display: contents`. box_slot.set(LayoutBox::InlineBox(
self.inline_formatting_context_builder.end_inline_box() self.inline_formatting_context_builder.end_inline_box(),
));
} }
fn handle_block_level_element( fn handle_block_level_element(

View file

@ -11,7 +11,7 @@ use style::values::specified::text::TextTransformCase;
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
use super::text_run::TextRun; use super::text_run::TextRun;
use super::{InlineBox, InlineFormattingContext, InlineLevelBox}; use super::{InlineBox, InlineBoxIdentifier, InlineBoxes, InlineFormattingContext, InlineItem};
use crate::cell::ArcRefCell; use crate::cell::ArcRefCell;
use crate::context::LayoutContext; use crate::context::LayoutContext;
use crate::dom::NodeExt; use crate::dom::NodeExt;
@ -22,7 +22,12 @@ use crate::positioned::AbsolutelyPositionedBox;
#[derive(Default)] #[derive(Default)]
pub(crate) struct InlineFormattingContextBuilder { pub(crate) struct InlineFormattingContextBuilder {
/// The collection of text strings that make up this [`InlineFormattingContext`] under
/// construction.
pub text_segments: Vec<String>, pub text_segments: Vec<String>,
/// The current offset in the final text string of this [`InlineFormattingContext`],
/// used to properly set the text range of new [`InlineItem::TextRun`]s.
current_text_offset: usize, current_text_offset: usize,
/// Whether the last processed node ended with whitespace. This is used to /// Whether the last processed node ended with whitespace. This is used to
@ -41,13 +46,13 @@ pub(crate) struct InlineFormattingContextBuilder {
/// Whether or not this inline formatting context will contain floats. /// Whether or not this inline formatting context will contain floats.
pub contains_floats: bool, pub contains_floats: bool,
/// Inline elements are direct descendants of the element that establishes /// The current list of [`InlineItem`]s in this [`InlineFormattingContext`] under
/// the inline formatting context that this builder builds. /// construction. This is stored in a flat list to make it easy to access the last
pub root_inline_boxes: Vec<ArcRefCell<InlineLevelBox>>, /// item.
pub inline_items: Vec<ArcRefCell<InlineItem>>,
/// Whether or not the inline formatting context under construction has any /// The current [`InlineBox`] tree of this [`InlineFormattingContext`] under construction.
/// uncollapsible text content. pub inline_boxes: InlineBoxes,
pub has_uncollapsible_text_content: bool,
/// The ongoing stack of inline boxes stack of the builder. /// The ongoing stack of inline boxes stack of the builder.
/// ///
@ -56,16 +61,12 @@ pub(crate) struct InlineFormattingContextBuilder {
/// which is why the code doesn't need to keep track of the actual /// which is why the code doesn't need to keep track of the actual
/// container root (see `handle_inline_level_element`). /// container root (see `handle_inline_level_element`).
/// ///
/// Whenever the end of a DOM element that represents an inline box is /// When an inline box ends, it's removed from this stack.
/// reached, the inline box at the top of this stack is complete and ready inline_box_stack: Vec<InlineBoxIdentifier>,
/// to be pushed to the children of the next last ongoing inline box
/// the ongoing inline formatting context if the stack is now empty, /// Whether or not the inline formatting context under construction has any
/// which means we reached the end of a child of the actual /// uncollapsible text content.
/// container root (see `move_to_next_sibling`). pub has_uncollapsible_text_content: bool,
///
/// When an inline box ends, it's removed from this stack and added to
/// [`Self::root_inline_boxes`].
inline_box_stack: Vec<InlineBox>,
} }
impl InlineFormattingContextBuilder { impl InlineFormattingContextBuilder {
@ -93,44 +94,30 @@ impl InlineFormattingContextBuilder {
return false; return false;
} }
fn inline_level_boxes_are_empty(boxes: &[ArcRefCell<InlineLevelBox>]) -> bool { fn inline_level_box_is_empty(inline_level_box: &InlineItem) -> bool {
boxes
.iter()
.all(|inline_level_box| inline_level_box_is_empty(&inline_level_box.borrow()))
}
fn inline_level_box_is_empty(inline_level_box: &InlineLevelBox) -> bool {
match inline_level_box { match inline_level_box {
InlineLevelBox::InlineBox(_) => false, InlineItem::StartInlineBox(_) => false,
InlineItem::EndInlineBox => false,
// Text content is handled by `self.has_uncollapsible_text` content above in order // Text content is handled by `self.has_uncollapsible_text` content above in order
// to avoid having to iterate through the character once again. // to avoid having to iterate through the character once again.
InlineLevelBox::TextRun(_) => true, InlineItem::TextRun(_) => true,
InlineLevelBox::OutOfFlowAbsolutelyPositionedBox(_) => false, InlineItem::OutOfFlowAbsolutelyPositionedBox(_) => false,
InlineLevelBox::OutOfFlowFloatBox(_) => false, InlineItem::OutOfFlowFloatBox(_) => false,
InlineLevelBox::Atomic(_) => false, InlineItem::Atomic(_) => false,
} }
} }
inline_level_boxes_are_empty(&self.root_inline_boxes) self.inline_items
} .iter()
.all(|inline_level_box| inline_level_box_is_empty(&inline_level_box.borrow()))
// 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.inline_box_stack.last_mut() {
Some(last) => &mut last.children,
None => &mut self.root_inline_boxes,
}
} }
pub(crate) fn push_atomic( pub(crate) fn push_atomic(
&mut self, &mut self,
independent_formatting_context: IndependentFormattingContext, independent_formatting_context: IndependentFormattingContext,
) -> ArcRefCell<InlineLevelBox> { ) -> ArcRefCell<InlineItem> {
let inline_level_box = let inline_level_box = ArcRefCell::new(InlineItem::Atomic(independent_formatting_context));
ArcRefCell::new(InlineLevelBox::Atomic(independent_formatting_context)); self.inline_items.push(inline_level_box.clone());
self.current_inline_level_boxes()
.push(inline_level_box.clone());
// Push an object replacement character for this atomic, which will ensure that the line breaker // Push an object replacement character for this atomic, which will ensure that the line breaker
// inserts a line breaking opportunity here. // inserts a line breaking opportunity here.
@ -147,49 +134,43 @@ impl InlineFormattingContextBuilder {
pub(crate) fn push_absolutely_positioned_box( pub(crate) fn push_absolutely_positioned_box(
&mut self, &mut self,
absolutely_positioned_box: AbsolutelyPositionedBox, absolutely_positioned_box: AbsolutelyPositionedBox,
) -> ArcRefCell<InlineLevelBox> { ) -> ArcRefCell<InlineItem> {
let absolutely_positioned_box = ArcRefCell::new(absolutely_positioned_box); let absolutely_positioned_box = ArcRefCell::new(absolutely_positioned_box);
let inline_level_box = ArcRefCell::new(InlineLevelBox::OutOfFlowAbsolutelyPositionedBox( let inline_level_box = ArcRefCell::new(InlineItem::OutOfFlowAbsolutelyPositionedBox(
absolutely_positioned_box, absolutely_positioned_box,
)); ));
self.current_inline_level_boxes() self.inline_items.push(inline_level_box.clone());
.push(inline_level_box.clone());
inline_level_box inline_level_box
} }
pub(crate) fn push_float_box(&mut self, float_box: FloatBox) -> ArcRefCell<InlineLevelBox> { pub(crate) fn push_float_box(&mut self, float_box: FloatBox) -> ArcRefCell<InlineItem> {
let inline_level_box = ArcRefCell::new(InlineLevelBox::OutOfFlowFloatBox(float_box)); let inline_level_box = ArcRefCell::new(InlineItem::OutOfFlowFloatBox(float_box));
self.current_inline_level_boxes() self.inline_items.push(inline_level_box.clone());
.push(inline_level_box.clone());
self.contains_floats = true; self.contains_floats = true;
inline_level_box inline_level_box
} }
pub(crate) fn start_inline_box<'dom, Node: NodeExt<'dom>>( pub(crate) fn start_inline_box(&mut self, inline_box: InlineBox) {
&mut self, let identifier = self.inline_boxes.start_inline_box(inline_box);
info: &NodeAndStyleInfo<Node>, self.inline_items
) { .push(ArcRefCell::new(InlineItem::StartInlineBox(identifier)));
self.inline_box_stack.push(InlineBox::new(info)) self.inline_box_stack.push(identifier);
} }
pub(crate) fn end_inline_box(&mut self) -> ArcRefCell<InlineLevelBox> { pub(crate) fn end_inline_box(&mut self) -> ArcRefCell<InlineBox> {
self.end_inline_box_internal(true) let identifier = self.end_inline_box_internal();
let inline_level_box = self.inline_boxes.get(&identifier);
inline_level_box.borrow_mut().is_last_fragment = true;
inline_level_box
} }
fn end_inline_box_internal(&mut self, is_last_fragment: bool) -> ArcRefCell<InlineLevelBox> { fn end_inline_box_internal(&mut self) -> InlineBoxIdentifier {
let mut inline_box = self self.inline_boxes.end_inline_box();
.inline_box_stack self.inline_items
.push(ArcRefCell::new(InlineItem::EndInlineBox));
self.inline_box_stack
.pop() .pop()
.expect("no ongoing inline level box found"); .expect("Ended non-existent inline box")
if is_last_fragment {
inline_box.is_last_fragment = true;
}
let inline_box = ArcRefCell::new(InlineLevelBox::InlineBox(inline_box));
self.current_inline_level_boxes().push(inline_box.clone());
inline_box
} }
pub(crate) fn push_text<'dom, Node: NodeExt<'dom>>( pub(crate) fn push_text<'dom, Node: NodeExt<'dom>>(
@ -251,15 +232,15 @@ impl InlineFormattingContextBuilder {
self.current_text_offset = new_range.end; self.current_text_offset = new_range.end;
self.text_segments.push(new_text); self.text_segments.push(new_text);
let inlines = self.current_inline_level_boxes(); if let Some(inline_item) = self.inline_items.last() {
if let Some(mut last_box) = inlines.last_mut().map(|last| last.borrow_mut()) { if let InlineItem::TextRun(text_run) = &mut *inline_item.borrow_mut() {
if let InlineLevelBox::TextRun(ref mut text_run) = *last_box {
text_run.text_range.end = new_range.end; text_run.text_range.end = new_range.end;
return; return;
} }
} }
inlines.push(ArcRefCell::new(InlineLevelBox::TextRun(TextRun::new( self.inline_items
.push(ArcRefCell::new(InlineItem::TextRun(TextRun::new(
info.into(), info.into(),
info.style.clone(), info.style.clone(),
new_range, new_range,
@ -280,27 +261,25 @@ impl InlineFormattingContextBuilder {
// context. It has the same inline box structure as this builder, except the boxes are // context. It has the same inline box structure as this builder, except the boxes are
// marked as not being the first fragment. No inline content is carried over to this new // marked as not being the first fragment. No inline content is carried over to this new
// builder. // builder.
let mut inline_buidler_from_before_split = std::mem::replace( let mut new_builder = InlineFormattingContextBuilder::new();
self, for identifier in self.inline_box_stack.iter() {
InlineFormattingContextBuilder { new_builder.start_inline_box(
on_word_boundary: true, self.inline_boxes
inline_box_stack: self .get(&identifier)
.inline_box_stack .borrow()
.iter() .split_around_block(),
.map(|inline_box| inline_box.split_around_block())
.collect(),
..Default::default()
},
); );
}
let mut inline_builder_from_before_split = std::mem::replace(self, new_builder);
// End all ongoing inline boxes in the first builder, but ensure that they are not // End all ongoing inline boxes in the first builder, but ensure that they are not
// marked as the final fragments, so that they do not get inline end margin, borders, // marked as the final fragments, so that they do not get inline end margin, borders,
// and padding. // and padding.
while !inline_buidler_from_before_split.inline_box_stack.is_empty() { while !inline_builder_from_before_split.inline_box_stack.is_empty() {
inline_buidler_from_before_split.end_inline_box_internal(false); inline_builder_from_before_split.end_inline_box_internal();
} }
inline_buidler_from_before_split.finish( inline_builder_from_before_split.finish(
layout_context, layout_context,
text_decoration_line, text_decoration_line,
has_first_formatted_line, has_first_formatted_line,

View file

@ -131,7 +131,15 @@ static FONT_SUPERSCRIPT_OFFSET_RATIO: f32 = 0.34;
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
pub(crate) struct InlineFormattingContext { pub(crate) struct InlineFormattingContext {
pub(super) inline_level_boxes: Vec<ArcRefCell<InlineLevelBox>>, /// All [`InlineItem`]s in this [`InlineFormattingContext`] stored in a flat array.
/// [`InlineItem::StartInlineBox`] and [`InlineItem::EndInlineBox`] allow representing
/// the tree of inline boxes within the formatting context, but a flat array allows
/// easy iteration through all inline items.
pub(super) inline_items: Vec<ArcRefCell<InlineItem>>,
/// The tree of inline boxes in this [`InlineFormattingContext`]. These are stored in
/// a flat array with each being given a [`InlineBoxIdentifier`].
pub(super) inline_boxes: InlineBoxes,
/// The text content of this inline formatting context. /// The text content of this inline formatting context.
pub(super) text_content: String, pub(super) text_content: String,
@ -159,8 +167,9 @@ pub(crate) struct FontKeyAndMetrics {
} }
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
pub(crate) enum InlineLevelBox { pub(crate) enum InlineItem {
InlineBox(InlineBox), StartInlineBox(InlineBoxIdentifier),
EndInlineBox,
TextRun(TextRun), TextRun(TextRun),
OutOfFlowAbsolutelyPositionedBox(ArcRefCell<AbsolutelyPositionedBox>), OutOfFlowAbsolutelyPositionedBox(ArcRefCell<AbsolutelyPositionedBox>),
OutOfFlowFloatBox(FloatBox), OutOfFlowFloatBox(FloatBox),
@ -172,9 +181,10 @@ pub(crate) struct InlineBox {
pub base_fragment_info: BaseFragmentInfo, pub base_fragment_info: BaseFragmentInfo,
#[serde(skip_serializing)] #[serde(skip_serializing)]
pub style: Arc<ComputedValues>, pub style: Arc<ComputedValues>,
/// The identifier of this inline box in the containing [`InlineFormattingContext`].
identifier: InlineBoxIdentifier,
pub is_first_fragment: bool, pub is_first_fragment: bool,
pub is_last_fragment: bool, pub is_last_fragment: bool,
pub children: Vec<ArcRefCell<InlineLevelBox>>,
/// The index of the default font in the [`InlineFormattingContext`]'s font metrics store. /// The index of the default font in the [`InlineFormattingContext`]'s font metrics store.
/// This is initialized during IFC shaping. /// This is initialized during IFC shaping.
pub default_font_index: Option<usize>, pub default_font_index: Option<usize>,
@ -185,21 +195,19 @@ impl InlineBox {
Self { Self {
base_fragment_info: info.into(), base_fragment_info: info.into(),
style: info.style.clone(), style: info.style.clone(),
identifier: InlineBoxIdentifier::root(),
is_first_fragment: true, is_first_fragment: true,
is_last_fragment: false, is_last_fragment: false,
children: vec![],
default_font_index: None, default_font_index: None,
} }
} }
pub(crate) fn split_around_block(&self) -> Self { pub(crate) fn split_around_block(&self) -> Self {
Self { Self {
base_fragment_info: self.base_fragment_info,
style: self.style.clone(), style: self.style.clone(),
is_first_fragment: false, is_first_fragment: false,
is_last_fragment: false, is_last_fragment: false,
children: vec![], ..*self
default_font_index: None,
} }
} }
} }
@ -1570,11 +1578,6 @@ impl From<&InheritedText> for SegmentContentFlags {
} }
} }
enum InlineFormattingContextIterItem<'a> {
Item(&'a mut InlineLevelBox),
EndInlineBox,
}
impl InlineFormattingContext { impl InlineFormattingContext {
pub(super) fn new_with_builder( pub(super) fn new_with_builder(
builder: InlineFormattingContextBuilder, builder: InlineFormattingContextBuilder,
@ -1582,22 +1585,14 @@ impl InlineFormattingContext {
text_decoration_line: TextDecorationLine, text_decoration_line: TextDecorationLine,
has_first_formatted_line: bool, has_first_formatted_line: bool,
) -> Self { ) -> Self {
let mut inline_formatting_context = InlineFormattingContext {
text_content: String::new(),
inline_level_boxes: builder.root_inline_boxes,
font_metrics: Vec::new(),
text_decoration_line,
has_first_formatted_line,
contains_floats: builder.contains_floats,
};
// This is to prevent a double borrow. // This is to prevent a double borrow.
let text_content: String = builder.text_segments.into_iter().collect(); let text_content: String = builder.text_segments.into_iter().collect();
let mut font_metrics = Vec::new(); let mut font_metrics = Vec::new();
let mut new_linebreaker = LineBreaker::new(text_content.as_str()); let mut new_linebreaker = LineBreaker::new(text_content.as_str());
inline_formatting_context.foreach(|iter_item| match iter_item { for item in builder.inline_items.iter() {
InlineFormattingContextIterItem::Item(InlineLevelBox::TextRun(ref mut text_run)) => { match &mut *item.borrow_mut() {
InlineItem::TextRun(ref mut text_run) => {
text_run.segment_and_shape( text_run.segment_and_shape(
&text_content, &text_content,
&layout_context.font_context, &layout_context.font_context,
@ -1605,84 +1600,29 @@ impl InlineFormattingContext {
&mut font_metrics, &mut font_metrics,
); );
}, },
InlineFormattingContextIterItem::Item(InlineLevelBox::InlineBox(inline_box)) => { InlineItem::StartInlineBox(identifier) => {
let inline_box = builder.inline_boxes.get(identifier);
let inline_box = &mut *inline_box.borrow_mut();
if let Some(font) = get_font_for_first_font_for_style( if let Some(font) = get_font_for_first_font_for_style(
&inline_box.style, &inline_box.style,
&layout_context.font_context, &layout_context.font_context,
) { ) {
inline_box.default_font_index = Some(add_or_get_font(&font, &mut font_metrics)); inline_box.default_font_index =
Some(add_or_get_font(&font, &mut font_metrics));
} }
}, },
InlineFormattingContextIterItem::Item(InlineLevelBox::Atomic(_)) => {},
_ => {}, _ => {},
});
inline_formatting_context.text_content = text_content;
inline_formatting_context.font_metrics = font_metrics;
inline_formatting_context
}
fn foreach(&self, mut func: impl FnMut(InlineFormattingContextIterItem)) {
// TODO(mrobinson): Using OwnedRef here we could maybe avoid the second borrow when
// iterating through members of each inline box.
struct InlineFormattingContextChildBoxIter {
inline_level_box: ArcRefCell<InlineLevelBox>,
next_child_index: usize,
}
impl InlineFormattingContextChildBoxIter {
fn next(&mut self) -> Option<ArcRefCell<InlineLevelBox>> {
let borrowed_item = self.inline_level_box.borrow();
let inline_box = match *borrowed_item {
InlineLevelBox::InlineBox(ref inline_box) => inline_box,
_ => unreachable!(
"InlineFormattingContextChildBoxIter created for non-InlineBox."
),
};
let item = inline_box.children.get(self.next_child_index).cloned();
if item.is_some() {
self.next_child_index += 1;
}
item
} }
} }
let mut inline_box_iterator_stack: Vec<InlineFormattingContextChildBoxIter> = Vec::new(); InlineFormattingContext {
let mut root_iterator = self.inline_level_boxes.iter(); text_content,
loop { inline_items: builder.inline_items,
let mut item = None; inline_boxes: builder.inline_boxes,
font_metrics,
// First try to get the next item in the current inline box. text_decoration_line,
if !inline_box_iterator_stack.is_empty() { has_first_formatted_line,
item = inline_box_iterator_stack contains_floats: builder.contains_floats,
.last_mut()
.and_then(|iter| iter.next());
if item.is_none() {
func(InlineFormattingContextIterItem::EndInlineBox);
inline_box_iterator_stack.pop();
continue;
}
}
// If there is no current inline box, then try to get the next item from the root of the IFC.
item = item.or_else(|| root_iterator.next().cloned());
// If there is no item left, we are done iterating.
let item = match item {
Some(item) => item,
None => return,
};
let borrowed_item = &mut *item.borrow_mut();
func(InlineFormattingContextIterItem::Item(borrowed_item));
if matches!(borrowed_item, InlineLevelBox::InlineBox(_)) {
inline_box_iterator_stack.push(InlineFormattingContextChildBoxIter {
inline_level_box: item.clone(),
next_child_index: 0,
})
}
} }
} }
@ -1764,35 +1704,35 @@ impl InlineFormattingContext {
// FIXME(mrobinson): Collapse margins in the containing block offsets as well?? // FIXME(mrobinson): Collapse margins in the containing block offsets as well??
} }
self.foreach(|item| match item { for item in self.inline_items.iter() {
InlineFormattingContextIterItem::Item(item) => { let item = &mut *item.borrow_mut();
// Any new box should flush a pending hard line break. // Any new box should flush a pending hard line break.
if !matches!(item, InlineItem::EndInlineBox) {
ifc.possibly_flush_deferred_forced_line_break(); ifc.possibly_flush_deferred_forced_line_break();
}
match item { match item {
InlineLevelBox::InlineBox(ref inline_box) => { InlineItem::StartInlineBox(identifier) => {
ifc.start_inline_box(inline_box); ifc.start_inline_box(&*self.inline_boxes.get(identifier).borrow());
}, },
InlineLevelBox::TextRun(ref run) => run.layout_into_line_items(&mut ifc), InlineItem::EndInlineBox => ifc.finish_inline_box(),
InlineLevelBox::Atomic(ref mut atomic_formatting_context) => { InlineItem::TextRun(run) => run.layout_into_line_items(&mut ifc),
InlineItem::Atomic(atomic_formatting_context) => {
atomic_formatting_context.layout_into_line_items(layout_context, &mut ifc); atomic_formatting_context.layout_into_line_items(layout_context, &mut ifc);
}, },
InlineLevelBox::OutOfFlowAbsolutelyPositionedBox(ref positioned_box) => { InlineItem::OutOfFlowAbsolutelyPositionedBox(positioned_box) => {
ifc.push_line_item_to_unbreakable_segment(LineItem::AbsolutelyPositioned( ifc.push_line_item_to_unbreakable_segment(LineItem::AbsolutelyPositioned(
AbsolutelyPositionedLineItem { AbsolutelyPositionedLineItem {
absolutely_positioned_box: positioned_box.clone(), absolutely_positioned_box: positioned_box.clone(),
}, },
)); ));
}, },
InlineLevelBox::OutOfFlowFloatBox(ref mut float_box) => { InlineItem::OutOfFlowFloatBox(ref mut float_box) => {
float_box.layout_into_line_items(layout_context, &mut ifc); float_box.layout_into_line_items(layout_context, &mut ifc);
}, },
} }
}, }
InlineFormattingContextIterItem::EndInlineBox => {
ifc.finish_inline_box();
},
});
ifc.finish_last_line(); ifc.finish_last_line();
@ -2377,11 +2317,26 @@ struct ContentSizesComputation<'a> {
impl<'a> ContentSizesComputation<'a> { impl<'a> ContentSizesComputation<'a> {
fn traverse(mut self, inline_formatting_context: &InlineFormattingContext) -> ContentSizes { fn traverse(mut self, inline_formatting_context: &InlineFormattingContext) -> ContentSizes {
inline_formatting_context.foreach(|iter_item| match iter_item { for inline_item in inline_formatting_context.inline_items.iter() {
InlineFormattingContextIterItem::Item(InlineLevelBox::InlineBox(inline_box)) => { self.process_item(&mut *inline_item.borrow_mut(), inline_formatting_context);
}
self.forced_line_break();
self.paragraph
}
fn process_item(
&mut self,
inline_item: &mut InlineItem,
inline_formatting_context: &InlineFormattingContext,
) {
match inline_item {
InlineItem::StartInlineBox(identifier) => {
// For margins and paddings, a cyclic percentage is resolved against zero // For margins and paddings, a cyclic percentage is resolved against zero
// for determining intrinsic size contributions. // for determining intrinsic size contributions.
// https://drafts.csswg.org/css-sizing-3/#min-percentage-contribution // https://drafts.csswg.org/css-sizing-3/#min-percentage-contribution
let inline_box = inline_formatting_context.inline_boxes.get(identifier);
let inline_box = inline_box.borrow();
let zero = Length::zero(); let zero = Length::zero();
let padding = inline_box let padding = inline_box
.style .style
@ -2406,14 +2361,14 @@ impl<'a> ContentSizesComputation<'a> {
self.ending_inline_pbm_stack.push(Length::zero()); self.ending_inline_pbm_stack.push(Length::zero());
} }
}, },
InlineFormattingContextIterItem::EndInlineBox => { InlineItem::EndInlineBox => {
let length = self let length = self
.ending_inline_pbm_stack .ending_inline_pbm_stack
.pop() .pop()
.unwrap_or_else(Length::zero); .unwrap_or_else(Length::zero);
self.add_length(length); self.add_length(length);
}, },
InlineFormattingContextIterItem::Item(InlineLevelBox::TextRun(text_run)) => { InlineItem::TextRun(text_run) => {
for segment in text_run.shaped_text.iter() { for segment in text_run.shaped_text.iter() {
// TODO: This should take account whether or not the first and last character prevent // TODO: This should take account whether or not the first and last character prevent
// linebreaks after atomics as in layout. // linebreaks after atomics as in layout.
@ -2460,7 +2415,7 @@ impl<'a> ContentSizesComputation<'a> {
} }
} }
}, },
InlineFormattingContextIterItem::Item(InlineLevelBox::Atomic(atomic)) => { InlineItem::Atomic(atomic) => {
let outer = atomic.outer_inline_content_sizes( let outer = atomic.outer_inline_content_sizes(
self.layout_context, self.layout_context,
self.containing_block_writing_mode, self.containing_block_writing_mode,
@ -2471,10 +2426,7 @@ impl<'a> ContentSizesComputation<'a> {
self.had_content_yet = true; self.had_content_yet = true;
}, },
_ => {}, _ => {},
}); }
self.forced_line_break();
self.paragraph
} }
fn add_length(&mut self, l: Length) { fn add_length(&mut self, l: Length) {
@ -2522,3 +2474,36 @@ impl<'a> ContentSizesComputation<'a> {
.traverse(inline_formatting_context) .traverse(inline_formatting_context)
} }
} }
#[derive(Debug, Default, Serialize)]
pub(crate) struct InlineBoxes {
inline_boxes: Vec<ArcRefCell<InlineBox>>,
}
impl InlineBoxes {
pub(super) fn get(&self, identifier: &InlineBoxIdentifier) -> ArcRefCell<InlineBox> {
self.inline_boxes[identifier.index].clone()
}
pub(super) fn end_inline_box(&mut self) {}
pub(super) fn start_inline_box(&mut self, inline_box: InlineBox) -> InlineBoxIdentifier {
let identifier = InlineBoxIdentifier {
index: self.inline_boxes.len(),
};
self.inline_boxes.push(ArcRefCell::new(inline_box));
identifier
}
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
pub(crate) struct InlineBoxIdentifier {
pub index: usize,
}
impl InlineBoxIdentifier {
fn root() -> Self {
InlineBoxIdentifier { index: 0 }
}
}

View file

@ -21,7 +21,7 @@ use crate::dom::{LayoutBox, NodeExt};
use crate::dom_traversal::{iter_child_nodes, Contents, NodeAndStyleInfo}; use crate::dom_traversal::{iter_child_nodes, Contents, NodeAndStyleInfo};
use crate::flexbox::FlexLevelBox; use crate::flexbox::FlexLevelBox;
use crate::flow::float::FloatBox; use crate::flow::float::FloatBox;
use crate::flow::inline::InlineLevelBox; use crate::flow::inline::InlineItem;
use crate::flow::{BlockContainer, BlockFormattingContext, BlockLevelBox}; use crate::flow::{BlockContainer, BlockFormattingContext, BlockLevelBox};
use crate::formatting_contexts::IndependentFormattingContext; use crate::formatting_contexts::IndependentFormattingContext;
use crate::fragment_tree::FragmentTree; use crate::fragment_tree::FragmentTree;
@ -122,7 +122,7 @@ impl BoxTree {
#[allow(clippy::enum_variant_names)] #[allow(clippy::enum_variant_names)]
enum UpdatePoint { enum UpdatePoint {
AbsolutelyPositionedBlockLevelBox(ArcRefCell<BlockLevelBox>), AbsolutelyPositionedBlockLevelBox(ArcRefCell<BlockLevelBox>),
AbsolutelyPositionedInlineLevelBox(ArcRefCell<InlineLevelBox>), AbsolutelyPositionedInlineLevelBox(ArcRefCell<InlineItem>),
AbsolutelyPositionedFlexLevelBox(ArcRefCell<FlexLevelBox>), AbsolutelyPositionedFlexLevelBox(ArcRefCell<FlexLevelBox>),
} }
@ -180,8 +180,9 @@ impl BoxTree {
}, },
_ => return None, _ => return None,
}, },
LayoutBox::InlineBox(_) => return None,
LayoutBox::InlineLevel(inline_level_box) => match &*inline_level_box.borrow() { LayoutBox::InlineLevel(inline_level_box) => match &*inline_level_box.borrow() {
InlineLevelBox::OutOfFlowAbsolutelyPositionedBox(_) InlineItem::OutOfFlowAbsolutelyPositionedBox(_)
if box_style.position.is_absolutely_positioned() => if box_style.position.is_absolutely_positioned() =>
{ {
UpdatePoint::AbsolutelyPositionedInlineLevelBox( UpdatePoint::AbsolutelyPositionedInlineLevelBox(
@ -219,7 +220,7 @@ impl BoxTree {
}, },
UpdatePoint::AbsolutelyPositionedInlineLevelBox(inline_level_box) => { UpdatePoint::AbsolutelyPositionedInlineLevelBox(inline_level_box) => {
*inline_level_box.borrow_mut() = *inline_level_box.borrow_mut() =
InlineLevelBox::OutOfFlowAbsolutelyPositionedBox( InlineItem::OutOfFlowAbsolutelyPositionedBox(
out_of_flow_absolutely_positioned_box, out_of_flow_absolutely_positioned_box,
); );
}, },