From 4e79ac57018039b2d3f76e4a4616574e5d90505f Mon Sep 17 00:00:00 2001 From: Martin Robinson Date: Wed, 3 Jul 2024 17:15:31 +0200 Subject: [PATCH] layout: Allow rendering LineItems independent of inline box (#32666) Refactor inline layout to allow rendering line items in the second stage to be rendered in any order, independent of their parent inline box. This will allow line items to be reordered, effectively allowing the splitting of inline boxes, for the purposes of BiDi and any other inline reordering feature. Signed-off-by: Martin Robinson Co-authored-by: Rakhi Sharma --- components/layout_2020/dom.rs | 3 +- components/layout_2020/flow/construct.rs | 3 +- .../layout_2020/flow/inline/construct.rs | 11 +- .../layout_2020/flow/inline/inline_box.rs | 245 +++++ components/layout_2020/flow/inline/line.rs | 951 ++++++++++-------- components/layout_2020/flow/inline/mod.rs | 418 +++----- components/layout_2020/positioned.rs | 2 +- 7 files changed, 943 insertions(+), 690 deletions(-) create mode 100644 components/layout_2020/flow/inline/inline_box.rs diff --git a/components/layout_2020/dom.rs b/components/layout_2020/dom.rs index e30b22cb376..24ad7f16ed7 100644 --- a/components/layout_2020/dom.rs +++ b/components/layout_2020/dom.rs @@ -22,7 +22,8 @@ use crate::cell::ArcRefCell; use crate::context::LayoutContext; use crate::dom_traversal::WhichPseudoElement; use crate::flexbox::FlexLevelBox; -use crate::flow::inline::{InlineBox, InlineItem}; +use crate::flow::inline::inline_box::InlineBox; +use crate::flow::inline::InlineItem; use crate::flow::BlockLevelBox; use crate::geom::PhysicalSize; use crate::replaced::{CanvasInfo, CanvasSource}; diff --git a/components/layout_2020/flow/construct.rs b/components/layout_2020/flow/construct.rs index 986ae3e46d7..c52790d6348 100644 --- a/components/layout_2020/flow/construct.rs +++ b/components/layout_2020/flow/construct.rs @@ -14,7 +14,8 @@ use style::str::char_is_whitespace; use style::values::specified::text::TextDecorationLine; use super::inline::construct::InlineFormattingContextBuilder; -use super::inline::{InlineBox, InlineFormattingContext}; +use super::inline::inline_box::InlineBox; +use super::inline::InlineFormattingContext; use super::OutsideMarker; use crate::cell::ArcRefCell; use crate::context::LayoutContext; diff --git a/components/layout_2020/flow/inline/construct.rs b/components/layout_2020/flow/inline/construct.rs index 88b86961564..02751e356de 100644 --- a/components/layout_2020/flow/inline/construct.rs +++ b/components/layout_2020/flow/inline/construct.rs @@ -165,12 +165,15 @@ impl InlineFormattingContextBuilder { } fn end_inline_box_internal(&mut self) -> InlineBoxIdentifier { - self.inline_boxes.end_inline_box(); + let identifier = self + .inline_box_stack + .pop() + .expect("Ended non-existent inline box"); self.inline_items .push(ArcRefCell::new(InlineItem::EndInlineBox)); - self.inline_box_stack - .pop() - .expect("Ended non-existent inline box") + + self.inline_boxes.end_inline_box(identifier); + identifier } pub(crate) fn push_text<'dom, Node: NodeExt<'dom>>( diff --git a/components/layout_2020/flow/inline/inline_box.rs b/components/layout_2020/flow/inline/inline_box.rs new file mode 100644 index 00000000000..a8406611098 --- /dev/null +++ b/components/layout_2020/flow/inline/inline_box.rs @@ -0,0 +1,245 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use std::vec::IntoIter; + +use app_units::Au; +use fonts::FontMetrics; +use serde::Serialize; +use servo_arc::Arc; +use style::properties::ComputedValues; + +use super::{inline_container_needs_strut, InlineContainerState, InlineContainerStateFlags}; +use crate::cell::ArcRefCell; +use crate::context::LayoutContext; +use crate::dom::NodeExt; +use crate::dom_traversal::NodeAndStyleInfo; +use crate::fragment_tree::BaseFragmentInfo; +use crate::style_ext::{ComputedValuesExt, PaddingBorderMargin}; +use crate::ContainingBlock; + +#[derive(Debug, Serialize)] +pub(crate) struct InlineBox { + pub base_fragment_info: BaseFragmentInfo, + #[serde(skip_serializing)] + pub style: Arc, + /// The identifier of this inline box in the containing [`InlineFormattingContext`]. + pub(super) identifier: InlineBoxIdentifier, + pub is_first_fragment: bool, + pub is_last_fragment: bool, + /// The index of the default font in the [`InlineFormattingContext`]'s font metrics store. + /// This is initialized during IFC shaping. + pub default_font_index: Option, +} + +impl InlineBox { + pub(crate) fn new<'dom, Node: NodeExt<'dom>>(info: &NodeAndStyleInfo) -> Self { + Self { + base_fragment_info: info.into(), + style: info.style.clone(), + // This will be assigned later, when the box is actually added to the IFC. + identifier: InlineBoxIdentifier::default(), + is_first_fragment: true, + is_last_fragment: false, + default_font_index: None, + } + } + + pub(crate) fn split_around_block(&self) -> Self { + Self { + style: self.style.clone(), + is_first_fragment: false, + is_last_fragment: false, + ..*self + } + } +} + +#[derive(Debug, Default, Serialize)] +pub(crate) struct InlineBoxes { + /// A collection of all inline boxes in a particular [`InlineFormattingContext`]. + inline_boxes: Vec>, + + /// A list of tokens that represent the actual tree of inline boxes, while allowing + /// easy traversal forward and backwards through the tree. This structure is also + /// stored in the [`InlineFormattingContext::inline_items`], but this version is + /// faster to iterate. + inline_box_tree: Vec, +} + +impl InlineBoxes { + pub(super) fn len(&self) -> usize { + self.inline_boxes.len() + } + + pub(super) fn get(&self, identifier: &InlineBoxIdentifier) -> ArcRefCell { + self.inline_boxes[identifier.index_in_inline_boxes as usize].clone() + } + + pub(super) fn end_inline_box(&mut self, identifier: InlineBoxIdentifier) { + self.inline_box_tree + .push(InlineBoxTreePathToken::End(identifier)); + } + + pub(super) fn start_inline_box(&mut self, mut inline_box: InlineBox) -> InlineBoxIdentifier { + assert!(self.inline_boxes.len() <= u32::MAX as usize); + assert!(self.inline_box_tree.len() <= u32::MAX as usize); + + let index_in_inline_boxes = self.inline_boxes.len() as u32; + let index_of_start_in_tree = self.inline_box_tree.len() as u32; + + let identifier = InlineBoxIdentifier { + index_of_start_in_tree, + index_in_inline_boxes, + }; + inline_box.identifier = identifier; + + self.inline_boxes.push(ArcRefCell::new(inline_box)); + self.inline_box_tree + .push(InlineBoxTreePathToken::Start(identifier)); + identifier + } + + pub(super) fn get_path( + &self, + from: Option, + to: InlineBoxIdentifier, + ) -> IntoIter { + if from == Some(to) { + return Vec::new().into_iter(); + } + + let mut from_index = match from { + Some(InlineBoxIdentifier { + index_of_start_in_tree, + .. + }) => index_of_start_in_tree as usize, + None => 0, + }; + let mut to_index = to.index_of_start_in_tree as usize; + let is_reversed = to_index < from_index; + + // Do not include the first or final token, depending on direction. These can be equal + // if we are starting or going to the the root of the inline formatting context, in which + // case we don't want to adjust. + if to_index > from_index && from.is_some() { + from_index += 1; + } else if to_index < from_index { + to_index += 1; + } + + let mut path = Vec::with_capacity(from_index.abs_diff(to_index)); + let min = from_index.min(to_index); + let max = from_index.max(to_index); + + for token in &self.inline_box_tree[min..=max] { + // Skip useless recursion into inline boxes; we are looking for a direct path. + if Some(&token.reverse()) == path.last() { + path.pop(); + } else { + path.push(*token); + } + } + + if is_reversed { + path.reverse(); + for token in path.iter_mut() { + *token = token.reverse(); + } + } + + path.into_iter() + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Serialize)] +pub(super) enum InlineBoxTreePathToken { + Start(InlineBoxIdentifier), + End(InlineBoxIdentifier), +} + +impl InlineBoxTreePathToken { + fn reverse(&self) -> Self { + match self { + Self::Start(index) => Self::End(*index), + Self::End(index) => Self::Start(*index), + } + } +} + +/// An identifier for a particular [`InlineBox`] to be used to fetch it from an [`InlineBoxes`] +/// store of inline boxes. +/// +/// [`u32`] is used for the index, in order to save space. The value refers to the token +/// in the start tree data structure which can be fetched to find the actual index of +/// of the [`InlineBox`] in [`InlineBoxes::inline_boxes`]. +#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, Serialize)] +pub(crate) struct InlineBoxIdentifier { + pub index_of_start_in_tree: u32, + pub index_in_inline_boxes: u32, +} + +pub(super) struct InlineBoxContainerState { + /// The container state common to both [`InlineBox`] and the root of the + /// [`InlineFormattingContext`]. + pub base: InlineContainerState, + + /// The [`InlineBoxIdentifier`] of this inline container state. If this is the root + /// the identifier is [`None`]. + pub identifier: InlineBoxIdentifier, + + /// The [`BaseFragmentInfo`] of the [`InlineBox`] that this state tracks. + pub base_fragment_info: BaseFragmentInfo, + + /// The [`PaddingBorderMargin`] of the [`InlineBox`] that this state tracks. + pub pbm: PaddingBorderMargin, + + /// Whether this is the last fragment of this InlineBox. This may not be the case if + /// the InlineBox is split due to an block-in-inline-split and this is not the last of + /// that split. + pub is_last_fragment: bool, +} + +impl InlineBoxContainerState { + pub(super) fn new( + inline_box: &InlineBox, + containing_block: &ContainingBlock, + layout_context: &LayoutContext, + parent_container: &InlineContainerState, + is_last_fragment: bool, + font_metrics: Option<&FontMetrics>, + ) -> Self { + let style = inline_box.style.clone(); + let pbm = style.padding_border_margin(containing_block); + + let mut flags = InlineContainerStateFlags::empty(); + if inline_container_needs_strut(&style, layout_context, Some(&pbm)) { + flags.insert(InlineContainerStateFlags::CREATE_STRUT); + } + + Self { + base: InlineContainerState::new( + style, + flags, + Some(parent_container), + parent_container.text_decoration_line, + font_metrics, + ), + identifier: inline_box.identifier, + base_fragment_info: inline_box.base_fragment_info, + pbm, + is_last_fragment, + } + } + + pub(super) fn calculate_space_above_baseline(&self) -> Au { + let (ascent, descent, line_gap) = ( + self.base.font_metrics.ascent, + self.base.font_metrics.descent, + self.base.font_metrics.line_gap, + ); + let leading = line_gap - (ascent + descent); + leading.scale_by(0.5) + ascent + } +} diff --git a/components/layout_2020/flow/inline/line.rs b/components/layout_2020/flow/inline/line.rs index 08d645fcb23..57d6c183082 100644 --- a/components/layout_2020/flow/inline/line.rs +++ b/components/layout_2020/flow/inline/line.rs @@ -2,10 +2,11 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +use std::rc::Rc; use std::vec::IntoIter; use app_units::Au; -use atomic_refcell::AtomicRef; +use bitflags::bitflags; use fonts::{FontMetrics, GlyphStore}; use servo_arc::Arc; use style::computed_values::white_space_collapse::T as WhiteSpaceCollapse; @@ -15,127 +16,612 @@ use style::values::generics::box_::{GenericVerticalAlign, VerticalAlignKeyword}; use style::values::generics::font::LineHeight; use style::values::specified::box_::DisplayOutside; use style::values::specified::text::TextDecorationLine; +use style::values::Either; use style::Zero; use webrender_api::FontInstanceKey; +use super::inline_box::{ + InlineBoxContainerState, InlineBoxIdentifier, InlineBoxTreePathToken, InlineBoxes, +}; +use super::{InlineFormattingContextState, LineBlockSizes}; use crate::cell::ArcRefCell; use crate::context::LayoutContext; use crate::fragment_tree::{ - BaseFragmentInfo, BoxFragment, CollapsedBlockMargins, Fragment, HoistedSharedFragment, - TextFragment, + BaseFragmentInfo, BoxFragment, CollapsedBlockMargins, Fragment, TextFragment, }; use crate::geom::{LogicalRect, LogicalVec2}; use crate::positioned::{ relative_adjustement, AbsolutelyPositionedBox, PositioningContext, PositioningContextLength, }; -use crate::style_ext::PaddingBorderMargin; use crate::ContainingBlock; pub(super) struct LineMetrics { /// The block offset of the line start in the containing /// [`crate::flow::InlineFormattingContext`]. - pub block_offset: Length, + pub block_offset: Au, /// The block size of this line. - pub block_size: Length, + pub block_size: Au, /// The block offset of this line's baseline from [`Self::block_offset`]. pub baseline_block_offset: Au, } -/// State used when laying out the [`LineItem`]s collected for the line currently being -/// laid out. -pub(super) struct LineItemLayoutState<'a> { - pub inline_position: Length, +bitflags! { + struct LineLayoutInlineContainerFlags: u8 { + /// Whether or not any line items were processed for this inline box, this includes + /// any child inline boxes. + const HAD_ANY_LINE_ITEMS = 1 << 0; + /// Whether or not the starting inline border, padding, or margin of the inline box + /// was encountered. + const HAD_START_PBM = 1 << 2; + /// Whether or not the ending inline border, padding, or margin of the inline box + /// was encountered. + const HAD_END_PBM = 1 << 3; + /// Whether or not any floats were encountered while laying out this inline box. + const HAD_ANY_FLOATS = 1 << 4; + } +} + +/// The state used when laying out a collection of [`LineItem`]s into a line. This state is stored +/// per-inline container. For instance, when laying out the conents of a `` a fresh +/// [`LineItemLayoutInlineContainerState`] is pushed onto [`LineItemLayout`]'s stack of states. +pub(super) struct LineItemLayoutInlineContainerState { + /// If this inline container is not the root inline container, the identifier of the [`super::InlineBox`] + /// that is currently being laid out. + pub identifier: Option, + + /// The fragments that are laid out into this inline container on a line. + pub fragments: Vec, + + /// The current inline adavnce of the layout in the coordinates of this inline box. + pub inline_advance: Au, + + /// Flags which track various features during layout. + flags: LineLayoutInlineContainerFlags, /// The offset of the parent, relative to the start position of the line. - pub parent_offset: LogicalVec2, + pub parent_offset: LogicalVec2, /// The block offset of the parent's baseline relative to the block start of the line. This /// is often the same as [`Self::parent_offset`], but can be different for the root /// element. pub baseline_offset: Au, - pub ifc_containing_block: &'a ContainingBlock<'a>, - pub positioning_context: &'a mut PositioningContext, + /// If this inline box establishes a containing block for positioned elements, this + /// is a fresh positioning context to contain them. Otherwise, this holds the starting + /// offset in the *parent* positioning context so that static positions can be updated + /// at the end of layout. + pub positioning_context_or_start_offset_in_parent: + Either, +} - /// The amount of space to add to each justification opportunity in order to implement - /// `text-align: justify`. - pub justification_adjustment: Length, +impl LineItemLayoutInlineContainerState { + fn new( + identifier: Option, + parent_offset: LogicalVec2, + baseline_offset: Au, + positioning_context_or_start_offset_in_parent: Either< + PositioningContext, + PositioningContextLength, + >, + ) -> Self { + Self { + identifier, + fragments: Vec::new(), + inline_advance: Au::zero(), + flags: LineLayoutInlineContainerFlags::empty(), + parent_offset, + baseline_offset, + positioning_context_or_start_offset_in_parent, + } + } + + fn root(starting_inline_advance: Au, baseline_offset: Au) -> Self { + let mut state = Self::new( + None, + LogicalVec2::zero(), + baseline_offset, + Either::Second(PositioningContextLength::zero()), + ); + state.inline_advance = starting_inline_advance; + state + } +} + +/// The second phase of [`super::InlineFormattingContext`] layout: once items are gathered +/// for a line, we must lay them out and create fragments for them, properly positioning them +/// according to their baselines and also handling absolutely positioned children. +pub(super) struct LineItemLayout<'a> { + /// The set of [`super::InlineBox`]es for the [`super::InlineFormattingContext`]. This + /// does *not* include any state from during phase one of layout. + pub inline_boxes: &'a InlineBoxes, + + /// The set of [`super::InlineBoxContainerState`] from phase one of IFC layout. There is + /// one of these for every inline box, *not* for the root inline container. + pub inline_box_states: &'a [Rc], + + /// The set of [`super::LineItemLayoutInlineContainerState`] created while laying out items + /// on this line. This does not include the current level of recursion. + pub state_stack: Vec, + + /// The current [`super::LineItemLayoutInlineContainerState`]. + pub state: LineItemLayoutInlineContainerState, + + /// The [`LayoutContext`] to use for laying out absolutely positioned line items. + pub layout_context: &'a LayoutContext<'a>, + + /// The root positioning context for this layout. + pub root_positioning_context: &'a mut PositioningContext, + + /// The [`ContainingBlock`] of the parent [`super::InlineFormattingContext`] of the line being + /// laid out. + pub ifc_containing_block: &'a ContainingBlock<'a>, /// The metrics of this line, which should remain constant throughout the /// layout process. - pub line_metrics: &'a LineMetrics, + pub line_metrics: LineMetrics, + + /// The amount of space to add to each justification opportunity in order to implement + /// `text-align: justify`. + pub justification_adjustment: Au, } -pub(super) fn layout_line_items( - iterator: &mut IntoIter, - layout_context: &LayoutContext, - state: &mut LineItemLayoutState, - saw_end: &mut bool, -) -> Vec { - let mut fragments = vec![]; - while let Some(item) = iterator.next() { - match item { - LineItem::TextRun(text_line_item) => { - if let Some(fragment) = text_line_item.layout(state) { - fragments.push(Fragment::Text(fragment)); +impl<'a> LineItemLayout<'a> { + pub(super) fn layout_line_items( + state: &mut InlineFormattingContextState, + iterator: &mut IntoIter, + start_position: LogicalVec2, + effective_block_advance: &LineBlockSizes, + justification_adjustment: Au, + ) -> Vec { + let baseline_offset = effective_block_advance.find_baseline_offset(); + LineItemLayout { + inline_boxes: state.inline_boxes, + inline_box_states: &state.inline_box_states, + state_stack: Vec::new(), + root_positioning_context: state.positioning_context, + layout_context: state.layout_context, + state: LineItemLayoutInlineContainerState::root(start_position.inline, baseline_offset), + ifc_containing_block: state.containing_block, + line_metrics: LineMetrics { + block_offset: start_position.block, + block_size: effective_block_advance.resolve(), + baseline_block_offset: baseline_offset, + }, + justification_adjustment, + } + .layout(iterator) + } + + /// Start and end inline boxes in tree order, so that it reflects the given inline box. + fn prepare_layout_for_inline_box(&mut self, new_inline_box: Option) { + // Optimize the case where we are moving to the root of the inline box stack. + let Some(new_inline_box) = new_inline_box else { + while !self.state_stack.is_empty() { + self.end_inline_box(); + } + return; + }; + + // Otherwise, follow the path given to us by our collection of inline boxes, so we know which + // inline boxes to start and end. + let path = self + .inline_boxes + .get_path(self.state.identifier, new_inline_box); + for token in path { + match token { + InlineBoxTreePathToken::Start(ref identifier) => self.start_inline_box(identifier), + InlineBoxTreePathToken::End(_) => self.end_inline_box(), + } + } + } + + pub(super) fn layout(&mut self, iterator: &mut IntoIter) -> Vec { + for item in iterator.by_ref() { + // When preparing to lay out a new line item, start and end inline boxes, so that the current + // inline box state reflects the item's parent. Items in the line are not necessarily in tree + // order due to BiDi and other reordering so the inline box of the item could potentially be + // any in the inline formatting context. + self.prepare_layout_for_inline_box(item.inline_box_identifier()); + + self.state + .flags + .insert(LineLayoutInlineContainerFlags::HAD_ANY_LINE_ITEMS); + match item { + LineItem::StartInlineBoxPaddingBorderMargin(_) => { + self.state + .flags + .insert(LineLayoutInlineContainerFlags::HAD_START_PBM); + }, + LineItem::EndInlineBoxPaddingBorderMargin(_) => { + self.state + .flags + .insert(LineLayoutInlineContainerFlags::HAD_END_PBM); + }, + LineItem::TextRun(_, text_run) => self.layout_text_run(text_run), + LineItem::Atomic(_, atomic) => self.layout_atomic(atomic), + LineItem::AbsolutelyPositioned(_, absolute) => self.layout_absolute(absolute), + LineItem::Float(_, float) => self.layout_float(float), + } + } + + // Move back to the root of the inline box tree, so that all boxes are ended. + self.prepare_layout_for_inline_box(None); + std::mem::take(&mut self.state.fragments) + } + + fn current_positioning_context_mut(&mut self) -> &mut PositioningContext { + if let Either::First(ref mut positioning_context) = + self.state.positioning_context_or_start_offset_in_parent + { + return positioning_context; + } + self.state_stack + .iter_mut() + .rev() + .find_map( + |state| match state.positioning_context_or_start_offset_in_parent { + Either::First(ref mut positioning_context) => Some(positioning_context), + Either::Second(_) => None, + }, + ) + .unwrap_or(self.root_positioning_context) + } + + fn start_inline_box(&mut self, identifier: &InlineBoxIdentifier) { + let inline_box_state = &*self.inline_box_states[identifier.index_in_inline_boxes as usize]; + let inline_box = self.inline_boxes.get(identifier); + let inline_box = &*(inline_box.borrow()); + + let style = &inline_box.style; + let space_above_baseline = inline_box_state.calculate_space_above_baseline(); + let block_start_offset = + self.calculate_inline_box_block_start(inline_box_state, space_above_baseline); + + let positioning_context_or_start_offset_in_parent = + match PositioningContext::new_for_style(style) { + Some(positioning_context) => Either::First(positioning_context), + None => Either::Second(self.current_positioning_context_mut().len()), + }; + + let parent_offset = LogicalVec2 { + inline: self.state.inline_advance + self.state.parent_offset.inline, + block: block_start_offset, + }; + + let outer_state = std::mem::replace( + &mut self.state, + LineItemLayoutInlineContainerState::new( + Some(*identifier), + parent_offset, + block_start_offset + space_above_baseline, + positioning_context_or_start_offset_in_parent, + ), + ); + + self.state_stack.push(outer_state); + } + + fn end_inline_box(&mut self) { + let outer_state = self.state_stack.pop().expect("Ended unknown inline box 11"); + let mut inner_state = std::mem::replace(&mut self.state, outer_state); + + let identifier = inner_state.identifier.expect("Ended unknown inline box 22"); + let inline_box_state = &*self.inline_box_states[identifier.index_in_inline_boxes as usize]; + let inline_box = self.inline_boxes.get(&identifier); + let inline_box = &*(inline_box.borrow()); + + let mut padding = inline_box_state.pbm.padding; + let mut border = inline_box_state.pbm.border; + let mut margin = inline_box_state.pbm.margin.auto_is(Au::zero); + if !inner_state + .flags + .contains(LineLayoutInlineContainerFlags::HAD_START_PBM) + { + padding.inline_start = Au::zero(); + border.inline_start = Au::zero(); + margin.inline_start = Au::zero(); + } + if !inner_state + .flags + .contains(LineLayoutInlineContainerFlags::HAD_END_PBM) + { + padding.inline_end = Au::zero(); + border.inline_end = Au::zero(); + margin.inline_end = Au::zero(); + } + + // If the inline box didn't have any content at all and it isn't the first fragment for + // an element (needed for layout queries currently) and it didn't have any padding, border, + // or margin do not make a fragment for it. + // + // Note: This is an optimization, but also has side effects. Any fragments on a line will + // force the baseline to advance in the parent IFC. + let pbm_sums = padding + border + margin; + if inner_state.fragments.is_empty() && + !inner_state + .flags + .contains(LineLayoutInlineContainerFlags::HAD_START_PBM) && + pbm_sums.inline_sum().is_zero() + { + return; + } + + // Make `content_rect` relative to the parent Fragment. + let mut content_rect = LogicalRect { + start_corner: LogicalVec2 { + inline: self.state.inline_advance + pbm_sums.inline_start, + block: inner_state.parent_offset.block - self.state.parent_offset.block, + }, + size: LogicalVec2 { + inline: inner_state.inline_advance, + block: inline_box_state.base.font_metrics.line_gap, + }, + }; + + if inner_state + .flags + .contains(LineLayoutInlineContainerFlags::HAD_ANY_FLOATS) + { + for fragment in inner_state.fragments.iter_mut() { + if let Fragment::Float(box_fragment) = fragment { + box_fragment.content_rect.start_corner -= pbm_sums.start_offset(); } + } + } + + // Relative adjustment should not affect the rest of line layout, so we can + // do it right before creating the Fragment. + let style = &inline_box.style; + if style.clone_position().is_relative() { + content_rect.start_corner += relative_adjustement(style, self.ifc_containing_block); + } + + let mut fragment = BoxFragment::new( + inline_box.base_fragment_info, + style.clone(), + inner_state.fragments, + content_rect, + padding, + border, + margin, + None, /* clearance */ + CollapsedBlockMargins::zero(), + ); + + match inner_state.positioning_context_or_start_offset_in_parent { + Either::First(mut positioning_context) => { + positioning_context.layout_collected_children(self.layout_context, &mut fragment); + positioning_context.adjust_static_position_of_hoisted_fragments_with_offset( + &fragment.content_rect.start_corner, + PositioningContextLength::zero(), + ); + self.current_positioning_context_mut() + .append(positioning_context); }, - LineItem::StartInlineBox(box_line_item) => { - if let Some(fragment) = box_line_item.layout(iterator, layout_context, state) { - fragments.push(Fragment::Box(fragment)) - } + Either::Second(start_offset) => { + self.current_positioning_context_mut() + .adjust_static_position_of_hoisted_fragments_with_offset( + &fragment.content_rect.start_corner, + start_offset, + ); }, - LineItem::EndInlineBox => { - *saw_end = true; - break; + } + + self.state.inline_advance += inner_state.inline_advance + pbm_sums.inline_sum(); + self.state.fragments.push(Fragment::Box(fragment)); + } + + fn calculate_inline_box_block_start( + &self, + inline_box_state: &InlineBoxContainerState, + space_above_baseline: Au, + ) -> Au { + let font_metrics = &inline_box_state.base.font_metrics; + let style = &inline_box_state.base.style; + let line_gap = font_metrics.line_gap; + + // The baseline offset that we have in `Self::baseline_offset` is relative to the line + // baseline, so we need to make it relative to the line block start. + match inline_box_state.base.style.clone_vertical_align() { + GenericVerticalAlign::Keyword(VerticalAlignKeyword::Top) => { + let line_height: Au = line_height(style, font_metrics).into(); + (line_height - line_gap).scale_by(0.5) }, - LineItem::Atomic(atomic_line_item) => { - fragments.push(Fragment::Box(atomic_line_item.layout(state))); + GenericVerticalAlign::Keyword(VerticalAlignKeyword::Bottom) => { + let line_height: Au = line_height(style, font_metrics).into(); + let half_leading = (line_height - line_gap).scale_by(0.5); + self.line_metrics.block_size - line_height + half_leading }, - LineItem::AbsolutelyPositioned(absolute_line_item) => { - fragments.push(Fragment::AbsoluteOrFixedPositioned( - absolute_line_item.layout(state), - )); - }, - LineItem::Float(float_line_item) => { - fragments.push(Fragment::Float(float_line_item.layout(state))); + _ => { + self.line_metrics.baseline_block_offset + inline_box_state.base.baseline_offset - + space_above_baseline }, } } - fragments + + fn layout_text_run(&mut self, text_item: TextRunLineItem) { + if text_item.text.is_empty() { + return; + } + + let mut number_of_justification_opportunities = 0; + let mut inline_advance = text_item + .text + .iter() + .map(|glyph_store| { + number_of_justification_opportunities += glyph_store.total_word_separators(); + glyph_store.total_advance() + }) + .sum(); + + if !self.justification_adjustment.is_zero() { + inline_advance += self + .justification_adjustment + .scale_by(number_of_justification_opportunities as f32); + } + + // The block start of the TextRun is often zero (meaning it has the same font metrics as the + // inline box's strut), but for children of the inline formatting context root or for + // fallback fonts that use baseline relative alignment, it might be different. + let start_corner = LogicalVec2 { + inline: self.state.inline_advance, + block: self.state.baseline_offset - + text_item.font_metrics.ascent - + self.state.parent_offset.block, + }; + + let rect = LogicalRect { + start_corner, + size: LogicalVec2 { + block: text_item.font_metrics.line_gap, + inline: inline_advance, + }, + }; + + self.state.inline_advance += inline_advance; + self.state.fragments.push(Fragment::Text(TextFragment { + base: text_item.base_fragment_info.into(), + parent_style: text_item.parent_style, + rect, + font_metrics: text_item.font_metrics, + font_key: text_item.font_key, + glyphs: text_item.text, + text_decoration_line: text_item.text_decoration_line, + justification_adjustment: self.justification_adjustment, + })); + } + + fn layout_atomic(&mut self, mut atomic: AtomicLineItem) { + // The initial `start_corner` of the Fragment is only the PaddingBorderMargin sum start + // offset, which is the sum of the start component of the padding, border, and margin. + // This needs to be added to the calculated block and inline positions. + // Make the final result relative to the parent box. + atomic.fragment.content_rect.start_corner.inline += self.state.inline_advance; + atomic.fragment.content_rect.start_corner.block += + atomic.calculate_block_start(&self.line_metrics) - self.state.parent_offset.block; + + if atomic.fragment.style.clone_position().is_relative() { + atomic.fragment.content_rect.start_corner += + relative_adjustement(&atomic.fragment.style, self.ifc_containing_block); + } + + if let Some(mut positioning_context) = atomic.positioning_context { + positioning_context.adjust_static_position_of_hoisted_fragments_with_offset( + &atomic.fragment.content_rect.start_corner, + PositioningContextLength::zero(), + ); + self.current_positioning_context_mut() + .append(positioning_context); + } + + self.state.inline_advance += atomic.size.inline; + self.state.fragments.push(Fragment::Box(atomic.fragment)); + } + + fn layout_absolute(&mut self, absolute: AbsolutelyPositionedLineItem) { + let absolutely_positioned_box = (*absolute.absolutely_positioned_box).borrow(); + let style = absolutely_positioned_box.context.style(); + + // From https://drafts.csswg.org/css2/#abs-non-replaced-width + // > The static-position containing block is the containing block of a + // > hypothetical box that would have been the first box of the element if its + // > specified position value had been static and its specified float had been + // > none. (Note that due to the rules in section 9.7 this hypothetical + // > calculation might require also assuming a different computed value for + // > display.) + // + // This box is different based on the original `display` value of the + // absolutely positioned element. If it's `inline` it would be placed inline + // at the top of the line, but if it's block it would be placed in a new + // block position after the linebox established by this line. + let initial_start_corner = + if style.get_box().original_display.outside() == DisplayOutside::Inline { + // Top of the line at the current inline position. + LogicalVec2 { + inline: self.state.inline_advance, + block: -self.state.parent_offset.block, + } + } else { + // After the bottom of the line at the start of the inline formatting context. + LogicalVec2 { + inline: -self.state.parent_offset.inline, + block: self.line_metrics.block_size - self.state.parent_offset.block, + } + }; + + let hoisted_box = AbsolutelyPositionedBox::to_hoisted( + absolute.absolutely_positioned_box.clone(), + initial_start_corner.into(), + self.ifc_containing_block, + ); + let hoisted_fragment = hoisted_box.fragment.clone(); + self.current_positioning_context_mut().push(hoisted_box); + self.state + .fragments + .push(Fragment::AbsoluteOrFixedPositioned(hoisted_fragment)); + } + + fn layout_float(&mut self, mut float: FloatLineItem) { + self.state + .flags + .insert(LineLayoutInlineContainerFlags::HAD_ANY_FLOATS); + + // The `BoxFragment` for this float is positioned relative to the IFC, so we need + // to move it to be positioned relative to our parent InlineBox line item. Float + // fragments are children of these InlineBoxes and not children of the inline + // formatting context, so that they are parented properly for StackingContext + // properties such as opacity & filters. + let distance_from_parent_to_ifc = LogicalVec2 { + inline: self.state.parent_offset.inline, + block: self.line_metrics.block_offset + self.state.parent_offset.block, + }; + float.fragment.content_rect.start_corner -= distance_from_parent_to_ifc; + self.state.fragments.push(Fragment::Float(float.fragment)); + } } pub(super) enum LineItem { - TextRun(TextRunLineItem), - StartInlineBox(InlineBoxLineItem), - EndInlineBox, - Atomic(AtomicLineItem), - AbsolutelyPositioned(AbsolutelyPositionedLineItem), - Float(FloatLineItem), + StartInlineBoxPaddingBorderMargin(InlineBoxIdentifier), + EndInlineBoxPaddingBorderMargin(InlineBoxIdentifier), + TextRun(Option, TextRunLineItem), + Atomic(Option, AtomicLineItem), + AbsolutelyPositioned(Option, AbsolutelyPositionedLineItem), + Float(Option, FloatLineItem), } impl LineItem { + fn inline_box_identifier(&self) -> Option { + match self { + LineItem::StartInlineBoxPaddingBorderMargin(identifier) => Some(*identifier), + LineItem::EndInlineBoxPaddingBorderMargin(identifier) => Some(*identifier), + LineItem::TextRun(identifier, _) => *identifier, + LineItem::Atomic(identifier, _) => *identifier, + LineItem::AbsolutelyPositioned(identifier, _) => *identifier, + LineItem::Float(identifier, _) => *identifier, + } + } + pub(super) fn trim_whitespace_at_end(&mut self, whitespace_trimmed: &mut Length) -> bool { match self { - LineItem::TextRun(ref mut item) => item.trim_whitespace_at_end(whitespace_trimmed), - LineItem::StartInlineBox(_) => true, - LineItem::EndInlineBox => true, - LineItem::Atomic(_) => false, - LineItem::AbsolutelyPositioned(_) => true, - LineItem::Float(_) => true, + LineItem::StartInlineBoxPaddingBorderMargin(_) => true, + LineItem::EndInlineBoxPaddingBorderMargin(_) => true, + LineItem::TextRun(_, ref mut item) => item.trim_whitespace_at_end(whitespace_trimmed), + LineItem::Atomic(..) => false, + LineItem::AbsolutelyPositioned(..) => true, + LineItem::Float(..) => true, } } pub(super) fn trim_whitespace_at_start(&mut self, whitespace_trimmed: &mut Length) -> bool { match self { - LineItem::TextRun(ref mut item) => item.trim_whitespace_at_start(whitespace_trimmed), - LineItem::StartInlineBox(_) => true, - LineItem::EndInlineBox => true, - LineItem::Atomic(_) => false, - LineItem::AbsolutelyPositioned(_) => true, - LineItem::Float(_) => true, + LineItem::StartInlineBoxPaddingBorderMargin(_) => true, + LineItem::EndInlineBoxPaddingBorderMargin(_) => true, + LineItem::TextRun(_, ref mut item) => item.trim_whitespace_at_start(whitespace_trimmed), + LineItem::Atomic(..) => false, + LineItem::AbsolutelyPositioned(..) => true, + LineItem::Float(..) => true, } } } @@ -199,247 +685,6 @@ impl TextRunLineItem { // Only keep going if we only encountered whitespace. self.text.is_empty() } - - fn layout(self, state: &mut LineItemLayoutState) -> Option { - if self.text.is_empty() { - return None; - } - - let mut number_of_justification_opportunities = 0; - let mut inline_advance: Length = self - .text - .iter() - .map(|glyph_store| { - number_of_justification_opportunities += glyph_store.total_word_separators(); - Length::from(glyph_store.total_advance()) - }) - .sum(); - - if !state.justification_adjustment.is_zero() { - inline_advance += - state.justification_adjustment * number_of_justification_opportunities as f32; - } - - // The block start of the TextRun is often zero (meaning it has the same font metrics as the - // inline box's strut), but for children of the inline formatting context root or for - // fallback fonts that use baseline relatve alignment, it might be different. - let start_corner = LogicalVec2 { - inline: state.inline_position, - block: (state.baseline_offset - self.font_metrics.ascent).into(), - } - state.parent_offset; - - let rect = LogicalRect { - start_corner, - size: LogicalVec2 { - block: self.font_metrics.line_gap.into(), - inline: inline_advance, - }, - }; - - state.inline_position += inline_advance; - Some(TextFragment { - base: self.base_fragment_info.into(), - parent_style: self.parent_style, - rect: rect.into(), - font_metrics: self.font_metrics, - font_key: self.font_key, - glyphs: self.text, - text_decoration_line: self.text_decoration_line, - justification_adjustment: state.justification_adjustment.into(), - }) - } -} - -#[derive(Clone)] -pub(super) struct InlineBoxLineItem { - pub base_fragment_info: BaseFragmentInfo, - pub style: Arc, - pub pbm: PaddingBorderMargin, - - /// Whether this is the first fragment for this inline box. This means that it's the - /// first potentially split box of a block-in-inline-split (or only if there's no - /// split) and also the first appearance of this fragment on any line. - pub is_first_fragment: bool, - - /// Whether this is the last fragment for this inline box. This means that it's the - /// last potentially split box of a block-in-inline-split (or the only fragment if - /// there's no split). - pub is_last_fragment_of_ib_split: bool, - - /// The FontMetrics for the default font used in this inline box. - pub font_metrics: FontMetrics, - - /// The block offset of this baseline relative to the baseline of the line. This will be - /// zero for boxes with `vertical-align: top` and `vertical-align: bottom` since their - /// baselines are calculated late in layout. - pub baseline_offset: Au, -} - -impl InlineBoxLineItem { - fn layout( - self, - iterator: &mut IntoIter, - layout_context: &LayoutContext, - state: &mut LineItemLayoutState, - ) -> Option { - let style = self.style.clone(); - let mut padding = self.pbm.padding; - let mut border = self.pbm.border; - let mut margin = self.pbm.margin.auto_is(Au::zero); - - if !self.is_first_fragment { - padding.inline_start = Au::zero(); - border.inline_start = Au::zero(); - margin.inline_start = Au::zero(); - } - if !self.is_last_fragment_of_ib_split { - padding.inline_end = Au::zero(); - border.inline_end = Au::zero(); - margin.inline_end = Au::zero(); - } - let pbm_sums = padding + border + margin; - state.inline_position += pbm_sums.inline_start.into(); - - let space_above_baseline = self.calculate_space_above_baseline(); - let block_start_offset = self.calculate_block_start(state, space_above_baseline); - - let mut positioning_context = PositioningContext::new_for_style(&style); - let nested_positioning_context = match positioning_context.as_mut() { - Some(positioning_context) => positioning_context, - None => &mut state.positioning_context, - }; - let original_nested_positioning_context_length = nested_positioning_context.len(); - - let mut nested_state = LineItemLayoutState { - inline_position: state.inline_position, - parent_offset: LogicalVec2 { - inline: state.inline_position, - block: block_start_offset.into(), - }, - ifc_containing_block: state.ifc_containing_block, - positioning_context: nested_positioning_context, - justification_adjustment: state.justification_adjustment, - line_metrics: state.line_metrics, - baseline_offset: block_start_offset + space_above_baseline, - }; - - let mut saw_end = false; - let fragments = - layout_line_items(iterator, layout_context, &mut nested_state, &mut saw_end); - - // Only add ending padding, border, margin if this is the last fragment of a - // potential block-in-inline split and this line included the actual end of this - // fragment (it doesn't continue on the next line). - if !self.is_last_fragment_of_ib_split || !saw_end { - padding.inline_end = Au::zero(); - border.inline_end = Au::zero(); - margin.inline_end = Au::zero(); - } - let pbm_sums = padding + border + margin; - - // If the inline box didn't have any content at all, don't add a Fragment for it. - let box_has_padding_border_or_margin = pbm_sums.inline_sum() > Au::zero(); - let box_had_absolutes = - original_nested_positioning_context_length != nested_state.positioning_context.len(); - if !self.is_first_fragment && - fragments.is_empty() && - !box_has_padding_border_or_margin && - !box_had_absolutes - { - return None; - } - - let mut content_rect = LogicalRect { - start_corner: LogicalVec2 { - inline: state.inline_position, - block: block_start_offset.into(), - }, - size: LogicalVec2 { - inline: nested_state.inline_position - state.inline_position, - block: self.font_metrics.line_gap.into(), - }, - }; - - // Make `content_rect` relative to the parent Fragment. - content_rect.start_corner -= state.parent_offset; - - // Relative adjustment should not affect the rest of line layout, so we can - // do it right before creating the Fragment. - if style.clone_position().is_relative() { - content_rect.start_corner += - relative_adjustement(&style, state.ifc_containing_block).into(); - } - - let mut fragment = BoxFragment::new( - self.base_fragment_info, - self.style.clone(), - fragments, - content_rect.into(), - padding, - border, - margin, - None, /* clearance */ - CollapsedBlockMargins::zero(), - ); - - state.inline_position = nested_state.inline_position + pbm_sums.inline_end.into(); - - if let Some(mut positioning_context) = positioning_context.take() { - assert!(original_nested_positioning_context_length == PositioningContextLength::zero()); - positioning_context.layout_collected_children(layout_context, &mut fragment); - positioning_context.adjust_static_position_of_hoisted_fragments_with_offset( - &fragment.content_rect.start_corner, - PositioningContextLength::zero(), - ); - state.positioning_context.append(positioning_context); - } else { - state - .positioning_context - .adjust_static_position_of_hoisted_fragments_with_offset( - &fragment.content_rect.start_corner, - original_nested_positioning_context_length, - ); - } - - Some(fragment) - } - - /// Given our font metrics, calculate the space above the baseline we need for our content. - /// Note that this space does not include space for any content in child inline boxes, as - /// they are not included in our content rect. - fn calculate_space_above_baseline(&self) -> Au { - let (ascent, descent, line_gap) = ( - self.font_metrics.ascent, - self.font_metrics.descent, - self.font_metrics.line_gap, - ); - let leading = line_gap - (ascent + descent); - leading.scale_by(0.5) + ascent - } - - /// Given the state for a line item layout and the space above the baseline for this inline - /// box, find the block start position relative to the line block start position. - fn calculate_block_start(&self, state: &LineItemLayoutState, space_above_baseline: Au) -> Au { - let line_gap = self.font_metrics.line_gap; - - // The baseline offset that we have in `Self::baseline_offset` is relative to the line - // baseline, so we need to make it relative to the line block start. - match self.style.clone_vertical_align() { - GenericVerticalAlign::Keyword(VerticalAlignKeyword::Top) => { - let line_height: Au = line_height(&self.style, &self.font_metrics).into(); - (line_height - line_gap).scale_by(0.5) - }, - GenericVerticalAlign::Keyword(VerticalAlignKeyword::Bottom) => { - let line_height: Au = line_height(&self.style, &self.font_metrics).into(); - let half_leading = (line_height - line_gap).scale_by(0.5); - Au::from(state.line_metrics.block_size) - line_height + half_leading - }, - _ => { - state.line_metrics.baseline_block_offset + self.baseline_offset - - space_above_baseline - }, - } - } } pub(super) struct AtomicLineItem { @@ -457,48 +702,19 @@ pub(super) struct AtomicLineItem { } impl AtomicLineItem { - fn layout(mut self, state: &mut LineItemLayoutState) -> BoxFragment { - // The initial `start_corner` of the Fragment is only the PaddingBorderMargin sum start - // offset, which is the sum of the start component of the padding, border, and margin. - // This needs to be added to the calculated block and inline positions. - self.fragment.content_rect.start_corner.inline += state.inline_position.into(); - self.fragment.content_rect.start_corner.block += - self.calculate_block_start(state.line_metrics).into(); - - // Make the final result relative to the parent box. - self.fragment.content_rect.start_corner -= state.parent_offset.into(); - - if self.fragment.style.clone_position().is_relative() { - self.fragment.content_rect.start_corner += - relative_adjustement(&self.fragment.style, state.ifc_containing_block); - } - - state.inline_position += self.size.inline.into(); - - if let Some(mut positioning_context) = self.positioning_context { - positioning_context.adjust_static_position_of_hoisted_fragments_with_offset( - &self.fragment.content_rect.start_corner, - PositioningContextLength::zero(), - ); - state.positioning_context.append(positioning_context); - } - - self.fragment - } - /// Given the metrics for a line, our vertical alignment, and our block size, find a block start /// position relative to the top of the line. - fn calculate_block_start(&self, line_metrics: &LineMetrics) -> Length { + fn calculate_block_start(&self, line_metrics: &LineMetrics) -> Au { match self.fragment.style.clone_vertical_align() { - GenericVerticalAlign::Keyword(VerticalAlignKeyword::Top) => Length::zero(), + GenericVerticalAlign::Keyword(VerticalAlignKeyword::Top) => Au::zero(), GenericVerticalAlign::Keyword(VerticalAlignKeyword::Bottom) => { - line_metrics.block_size - self.size.block.into() + line_metrics.block_size - self.size.block }, // This covers all baseline-relative vertical alignment. _ => { let baseline = line_metrics.baseline_block_offset + self.baseline_offset_in_parent; - Length::from(baseline - self.baseline_offset_in_item) + baseline - self.baseline_offset_in_item }, } } @@ -508,49 +724,6 @@ pub(super) struct AbsolutelyPositionedLineItem { pub absolutely_positioned_box: ArcRefCell, } -impl AbsolutelyPositionedLineItem { - fn layout(self, state: &mut LineItemLayoutState) -> ArcRefCell { - let box_ = self.absolutely_positioned_box; - let style = AtomicRef::map(box_.borrow(), |box_| box_.context.style()); - - // From https://drafts.csswg.org/css2/#abs-non-replaced-width - // > The static-position containing block is the containing block of a - // > hypothetical box that would have been the first box of the element if its - // > specified position value had been static and its specified float had been - // > none. (Note that due to the rules in section 9.7 this hypothetical - // > calculation might require also assuming a different computed value for - // > display.) - // - // This box is different based on the original `display` value of the - // absolutely positioned element. If it's `inline` it would be placed inline - // at the top of the line, but if it's block it would be placed in a new - // block position after the linebox established by this line. - let initial_start_corner = - if style.get_box().original_display.outside() == DisplayOutside::Inline { - // Top of the line at the current inline position. - LogicalVec2 { - inline: state.inline_position - state.parent_offset.inline, - block: -state.parent_offset.block, - } - } else { - // After the bottom of the line at the start of the inline formatting context. - LogicalVec2 { - inline: Length::zero(), - block: state.line_metrics.block_size - state.parent_offset.block, - } - }; - - let hoisted_box = AbsolutelyPositionedBox::to_hoisted( - box_.clone(), - initial_start_corner, - state.ifc_containing_block, - ); - let hoisted_fragment = hoisted_box.fragment.clone(); - state.positioning_context.push(hoisted_box); - hoisted_fragment - } -} - pub(super) struct FloatLineItem { pub fragment: BoxFragment, /// Whether or not this float Fragment has been placed yet. Fragments that @@ -559,22 +732,6 @@ pub(super) struct FloatLineItem { pub needs_placement: bool, } -impl FloatLineItem { - fn layout(mut self, state: &mut LineItemLayoutState<'_>) -> BoxFragment { - // The `BoxFragment` for this float is positioned relative to the IFC, so we need - // to move it to be positioned relative to our parent InlineBox line item. Floats - // fragments are children of these InlineBoxes and not children of the inline - // formatting context, so that they are parented properly for StackingContext - // properties such as opacity & filters. - let distance_from_parent_to_ifc = LogicalVec2 { - inline: state.parent_offset.inline, - block: state.line_metrics.block_offset + state.parent_offset.block, - }; - self.fragment.content_rect.start_corner -= distance_from_parent_to_ifc.into(); - self.fragment - } -} - fn line_height(parent_style: &ComputedValues, font_metrics: &FontMetrics) -> Length { let font = parent_style.get_font(); let font_size = font.font_size.computed_size(); diff --git a/components/layout_2020/flow/inline/mod.rs b/components/layout_2020/flow/inline/mod.rs index 9a890865b0f..ebd04694999 100644 --- a/components/layout_2020/flow/inline/mod.rs +++ b/components/layout_2020/flow/inline/mod.rs @@ -48,9 +48,9 @@ //! a linear series of items that describe the line's hierarchy of inline boxes and content. The //! item types are: //! +//! - [`LineItem::StartInlineBoxPaddingBorderMargin`] +//! - [`LineItem::EndInlineBoxPaddingBorderMargin`] //! - [`LineItem::TextRun`] -//! - [`LineItem::StartInlineBox`] -//! - [`LineItem::EndInlineBox`] //! - [`LineItem::Atomic`] //! - [`LineItem::AbsolutelyPositioned`] //! - [`LineItem::Float`] @@ -69,20 +69,23 @@ //! pub mod construct; +pub mod inline_box; pub mod line; mod line_breaker; pub mod text_run; -use std::cell::OnceCell; +use std::cell::{OnceCell, RefCell}; use std::mem; +use std::rc::Rc; use app_units::Au; use bitflags::bitflags; use construct::InlineFormattingContextBuilder; use fonts::{FontMetrics, GlyphStore}; +use inline_box::{InlineBox, InlineBoxContainerState, InlineBoxIdentifier, InlineBoxes}; use line::{ - layout_line_items, AbsolutelyPositionedLineItem, AtomicLineItem, FloatLineItem, - InlineBoxLineItem, LineItem, LineItemLayoutState, LineMetrics, TextRunLineItem, + AbsolutelyPositionedLineItem, AtomicLineItem, FloatLineItem, LineItem, LineItemLayout, + TextRunLineItem, }; use line_breaker::LineBreaker; use serde::Serialize; @@ -107,15 +110,13 @@ use webrender_api::FontInstanceKey; use super::float::PlacementAmongFloats; use crate::cell::ArcRefCell; use crate::context::LayoutContext; -use crate::dom::NodeExt; -use crate::dom_traversal::NodeAndStyleInfo; use crate::flow::float::{FloatBox, SequentialLayoutState}; use crate::flow::{CollapsibleWithParentStartMargin, FlowLayout}; use crate::formatting_contexts::{ Baselines, IndependentFormattingContext, NonReplacedFormattingContextContents, }; use crate::fragment_tree::{ - BaseFragmentInfo, BoxFragment, CollapsedBlockMargins, CollapsedMargin, Fragment, FragmentFlags, + BoxFragment, CollapsedBlockMargins, CollapsedMargin, Fragment, FragmentFlags, PositioningFragment, }; use crate::geom::{LogicalRect, LogicalVec2}; @@ -178,42 +179,6 @@ pub(crate) enum InlineItem { Atomic(IndependentFormattingContext), } -#[derive(Debug, Serialize)] -pub(crate) struct InlineBox { - pub base_fragment_info: BaseFragmentInfo, - #[serde(skip_serializing)] - pub style: Arc, - /// The identifier of this inline box in the containing [`InlineFormattingContext`]. - identifier: InlineBoxIdentifier, - pub is_first_fragment: bool, - pub is_last_fragment: bool, - /// The index of the default font in the [`InlineFormattingContext`]'s font metrics store. - /// This is initialized during IFC shaping. - pub default_font_index: Option, -} - -impl InlineBox { - pub(crate) fn new<'dom, Node: NodeExt<'dom>>(info: &NodeAndStyleInfo) -> Self { - Self { - base_fragment_info: info.into(), - style: info.style.clone(), - identifier: InlineBoxIdentifier::root(), - is_first_fragment: true, - is_last_fragment: false, - default_font_index: None, - } - } - - pub(crate) fn split_around_block(&self) -> Self { - Self { - style: self.style.clone(), - is_first_fragment: false, - is_last_fragment: false, - ..*self - } - } -} - /// Information about the current line under construction for a particular /// [`InlineFormattingContextState`]. This tracks position and size information while /// [`LineItem`]s are collected and is used as input when those [`LineItem`]s are @@ -300,7 +265,7 @@ impl LineUnderConstruction { self.line_items .iter() .filter_map(|item| match item { - LineItem::TextRun(text_run) => Some( + LineItem::TextRun(_, text_run) => Some( text_run .text .iter() @@ -523,58 +488,6 @@ impl UnbreakableSegmentUnderConstruction { } self.inline_size -= whitespace_trimmed; } - - /// Prepare this segment for placement on a new and empty line. This happens when the - /// segment is too large to fit on the current line and needs to be placed on a new - /// one. - fn prepare_for_placement_on_empty_line( - &mut self, - line: &LineUnderConstruction, - current_hierarchy_depth: usize, - ) { - self.trim_leading_whitespace(); - - // The segment may start in the middle of an already processed inline box. In that - // case we need to duplicate the `StartInlineBox` tokens as a prefix of the new - // lines. For instance if the following segment is going to be placed on a new line: - // - // line = [StartInlineBox "every"] - // segment = ["good" EndInlineBox "boy"] - // - // Then the segment must be prefixed with `StartInlineBox` before it is committed - // to the empty line. - let mut hierarchy_depth = self - .inline_box_hierarchy_depth - .unwrap_or(current_hierarchy_depth); - if hierarchy_depth == 0 { - return; - } - let mut hierarchy = Vec::new(); - let mut skip_depth = 0; - for item in line.line_items.iter().rev() { - match item { - // We need to skip over any inline boxes that are not in our hierarchy. If - // any inline box ends, we skip until it starts. - LineItem::StartInlineBox(_) if skip_depth > 0 => skip_depth -= 1, - LineItem::EndInlineBox => skip_depth += 1, - - // Otherwise copy the inline box to the hierarchy we are collecting. - LineItem::StartInlineBox(inline_box) => { - let mut cloned_inline_box = inline_box.clone(); - cloned_inline_box.is_first_fragment = false; - hierarchy.push(LineItem::StartInlineBox(cloned_inline_box)); - hierarchy_depth -= 1; - if hierarchy_depth == 0 { - break; - } - }, - _ => {}, - } - } - - let segment_items = mem::take(&mut self.line_items); - self.line_items = hierarchy.into_iter().rev().chain(segment_items).collect(); - } } bitflags! { @@ -584,7 +497,7 @@ bitflags! { } } -struct InlineContainerState { +pub(super) struct InlineContainerState { /// The style of this inline container. style: Arc, @@ -593,7 +506,7 @@ struct InlineContainerState { /// Whether or not we have processed any content (an atomic element or text) for /// this inline box on the current line OR any previous line. - has_content: bool, + has_content: RefCell, /// Indicates whether this nesting level have text decorations in effect. /// From @@ -617,35 +530,22 @@ struct InlineContainerState { /// `vertical-align` property a positive value indicates an offset "below" the /// baseline while a negative value indicates one "above" it (when the block direction /// is vertical). - baseline_offset: Au, + pub baseline_offset: Au, /// The font metrics of the non-fallback font for this container. font_metrics: FontMetrics, } -struct InlineBoxContainerState { - /// The container state common to both [`InlineBox`] and the root of the - /// [`InlineFormattingContext`]. - base: InlineContainerState, - - /// The [`BaseFragmentInfo`] of the [`InlineBox`] that this state tracks. - base_fragment_info: BaseFragmentInfo, - - /// The [`PaddingBorderMargin`] of the [`InlineBox`] that this state tracks. - pbm: PaddingBorderMargin, - - /// Whether this is the last fragment of this InlineBox. This may not be the case if - /// the InlineBox is split due to an block-in-inline-split and this is not the last of - /// that split. - is_last_fragment: bool, -} - pub(super) struct InlineFormattingContextState<'a, 'b> { positioning_context: &'a mut PositioningContext, containing_block: &'b ContainingBlock<'b>, sequential_layout_state: Option<&'a mut SequentialLayoutState>, layout_context: &'b LayoutContext<'b>, + /// The inline boxes collection of the [`InlineFormattingContext`] that this + /// state is laying out. + inline_boxes: &'a InlineBoxes, + /// The list of [`FontMetrics`] used by the [`InlineFormattingContext`] that /// we are laying out. fonts: &'a Vec, @@ -664,7 +564,13 @@ pub(super) struct InlineFormattingContextState<'a, 'b> { /// A stack of [`InlineBoxContainerState`] that is used to produce [`LineItem`]s either when we /// reach the end of an inline box or when we reach the end of a line. Only at the end /// of the inline box is the state popped from the stack. - inline_box_state_stack: Vec, + inline_box_state_stack: Vec>, + + /// A collection of [`InlineBoxContainerState`] of all the inlines that are present + /// in this inline formatting context. We keep this as well as the stack, so that we + /// can access them during line layout, which may happen after relevant [`InlineBoxContainerState`]s + /// have been popped of the the stack. + inline_box_states: Vec>, /// A vector of fragment that are laid out. This includes one [`Fragment::Positioning`] /// per line that is currently laid out plus fragments for all floats, which @@ -742,11 +648,10 @@ impl<'a, 'b> InlineFormattingContextState<'a, 'b> { } } - fn current_inline_container_state_mut(&mut self) -> &mut InlineContainerState { - match self.inline_box_state_stack.last_mut() { - Some(inline_box_state) => &mut inline_box_state.base, - None => &mut self.root_nesting_level, - } + fn current_inline_box_identifier(&self) -> Option { + self.inline_box_state_stack + .last() + .map(|state| state.identifier) } fn current_line_max_block_size_including_nested_containers(&self) -> LineBlockSizes { @@ -780,7 +685,7 @@ impl<'a, 'b> InlineFormattingContextState<'a, 'b> { /// Start laying out a particular [`InlineBox`] into line items. This will push /// a new [`InlineBoxContainerState`] onto [`Self::inline_box_state_stack`]. fn start_inline_box(&mut self, inline_box: &InlineBox) { - let mut inline_box_state = InlineBoxContainerState::new( + let inline_box_state = InlineBoxContainerState::new( inline_box, self.containing_block, self.layout_context, @@ -813,12 +718,24 @@ impl<'a, 'b> InlineFormattingContextState<'a, 'b> { .margin .inline_start .auto_is(Au::zero) - .into() + .into(); + self.current_line_segment + .line_items + .push(LineItem::StartInlineBoxPaddingBorderMargin( + inline_box.identifier, + )); } - let line_item = inline_box_state - .layout_into_line_item(inline_box.is_first_fragment, inline_box.is_last_fragment); - self.push_line_item_to_unbreakable_segment(LineItem::StartInlineBox(line_item)); + let inline_box_state = Rc::new(inline_box_state); + + // Push the state onto the IFC-wide collection of states. Inline boxes are numbered in + // the order that they are encountered, so this should correspond to the order they + // are pushed onto `self.inline_box_states`. + assert_eq!( + self.inline_box_states.len(), + inline_box.identifier.index_in_inline_boxes as usize + ); + self.inline_box_states.push(inline_box_state.clone()); self.inline_box_state_stack.push(inline_box_state); } @@ -831,7 +748,6 @@ impl<'a, 'b> InlineFormattingContextState<'a, 'b> { None => return, // We are at the root. }; - self.push_line_item_to_unbreakable_segment(LineItem::EndInlineBox); self.current_line_segment .max_block_size .max_assign(&inline_box_state.base.nested_strut_block_sizes); @@ -840,7 +756,7 @@ impl<'a, 'b> InlineFormattingContextState<'a, 'b> { // the `white-space` property of its parent to future inline children. This is because // when a soft wrap opportunity is defined by the boundary between two elements, the // `white-space` used is that of their nearest common ancestor. - if inline_box_state.base.has_content { + if *inline_box_state.base.has_content.borrow() { self.propagate_current_nesting_level_white_space_style(); } @@ -854,6 +770,11 @@ impl<'a, 'b> InlineFormattingContextState<'a, 'b> { .auto_is(Au::zero) .into(); self.current_line_segment.inline_size += pbm_end; + self.current_line_segment + .line_items + .push(LineItem::EndInlineBoxPaddingBorderMargin( + inline_box_state.identifier, + )) } } @@ -920,47 +841,38 @@ impl<'a, 'b> InlineFormattingContextState<'a, 'b> { self.deferred_br_clear = Clear::None; } - let mut line_items = std::mem::take(&mut self.current_line.line_items); - if self.current_line.has_floats_waiting_to_be_placed { - place_pending_floats(self, &mut line_items); - } - // Set up the new line now that we no longer need the old one. - self.current_line = LineUnderConstruction::new(LogicalVec2 { - inline: Length::zero(), - block: block_end_position.into(), - }); - - let baseline_offset = effective_block_advance.find_baseline_offset(); - - let mut state = LineItemLayoutState { - inline_position: inline_start_position, - parent_offset: LogicalVec2::zero(), - baseline_offset, - ifc_containing_block: self.containing_block, - positioning_context: self.positioning_context, - justification_adjustment, - line_metrics: &LineMetrics { - block_offset: block_start_position.into(), - block_size: effective_block_advance.resolve().into(), - baseline_block_offset: baseline_offset, - }, - }; - - let positioning_context_length = state.positioning_context.len(); - let mut saw_end = false; - let fragments = layout_line_items( - &mut line_items.into_iter(), - self.layout_context, - &mut state, - &mut saw_end, + let mut line_to_layout = std::mem::replace( + &mut self.current_line, + LineUnderConstruction::new(LogicalVec2 { + inline: Length::zero(), + block: block_end_position.into(), + }), ); - let line_had_content = - !fragments.is_empty() || state.positioning_context.len() != positioning_context_length; + if line_to_layout.has_floats_waiting_to_be_placed { + place_pending_floats(self, &mut line_to_layout.line_items); + } + + let start_position = LogicalVec2 { + block: block_start_position, + inline: inline_start_position, + }; + + let baseline_offset = effective_block_advance.find_baseline_offset(); + let start_positioning_context_length = self.positioning_context.len(); + let fragments = LineItemLayout::layout_line_items( + self, + &mut line_to_layout.line_items.into_iter(), + start_position, + &effective_block_advance, + justification_adjustment, + ); // If the line doesn't have any fragments, we don't need to add a containing fragment for it. - if !line_had_content { + if fragments.is_empty() && + self.positioning_context.len() == start_positioning_context_length + { return; } @@ -982,11 +894,10 @@ impl<'a, 'b> InlineFormattingContextState<'a, 'b> { }, }; - state - .positioning_context + self.positioning_context .adjust_static_position_of_hoisted_fragments_with_offset( &line_rect.start_corner, - positioning_context_length, + start_positioning_context_length, ); self.fragments @@ -1005,7 +916,7 @@ impl<'a, 'b> InlineFormattingContextState<'a, 'b> { &self, whitespace_trimmed: Length, last_line_or_forced_line_break: bool, - ) -> (Length, Length) { + ) -> (Au, Au) { enum TextAlign { Start, Center, @@ -1099,7 +1010,7 @@ impl<'a, 'b> InlineFormattingContextState<'a, 'b> { // that case, do not make any adjustment for justification. let justification_adjustment = justification_adjustment.max(Length::zero()); - (adjusted_line_start, justification_adjustment) + (adjusted_line_start.into(), justification_adjustment.into()) } fn place_float_fragment(&mut self, fragment: &mut BoxFragment) { @@ -1392,22 +1303,29 @@ impl<'a, 'b> InlineFormattingContextState<'a, 'b> { }; self.update_unbreakable_segment_for_new_content(&strut_size, inline_advance, flags); + let current_inline_box_identifier = self.current_inline_box_identifier(); match self.current_line_segment.line_items.last_mut() { - Some(LineItem::TextRun(line_item)) if ifc_font_info.key == line_item.font_key => { + Some(LineItem::TextRun(inline_box_identifier, line_item)) + if ifc_font_info.key == line_item.font_key && + *inline_box_identifier == current_inline_box_identifier => + { line_item.text.push(glyph_store); return; }, _ => {}, } - self.push_line_item_to_unbreakable_segment(LineItem::TextRun(TextRunLineItem { - text: vec![glyph_store], - base_fragment_info: text_run.base_fragment_info, - parent_style: text_run.parent_style.clone(), - font_metrics, - font_key: ifc_font_info.key, - text_decoration_line: self.current_inline_container_state().text_decoration_line, - })); + self.push_line_item_to_unbreakable_segment(LineItem::TextRun( + current_inline_box_identifier, + TextRunLineItem { + text: vec![glyph_store], + base_fragment_info: text_run.base_fragment_info, + parent_style: text_run.parent_style.clone(), + font_metrics, + font_key: ifc_font_info.key, + text_decoration_line: self.current_inline_container_state().text_decoration_line, + }, + )); } fn update_unbreakable_segment_for_new_content( @@ -1441,17 +1359,15 @@ impl<'a, 'b> InlineFormattingContextState<'a, 'b> { self.current_line_segment.inline_size += inline_size; // Propagate the whitespace setting to the current nesting level. - let current_nesting_level = self.current_inline_container_state_mut(); - current_nesting_level.has_content = true; + *self + .current_inline_container_state() + .has_content + .borrow_mut() = true; self.propagate_current_nesting_level_white_space_style(); } fn process_line_break(&mut self, forced_line_break: bool) { - self.current_line_segment - .prepare_for_placement_on_empty_line( - &self.current_line, - self.inline_box_state_stack.len(), - ); + self.current_line_segment.trim_leading_whitespace(); self.finish_current_line_and_reset(forced_line_break); } @@ -1506,7 +1422,7 @@ impl<'a, 'b> InlineFormattingContextState<'a, 'b> { // Place all floats in this unbreakable segment. let mut segment_items = mem::take(&mut self.current_line_segment.line_items); for item in segment_items.iter_mut() { - if let LineItem::Float(float_item) = item { + if let LineItem::Float(_, float_item) = item { self.place_float_line_item_for_commit_to_line( float_item, line_inline_size_without_trailing_whitespace, @@ -1532,9 +1448,11 @@ impl<'a, 'b> InlineFormattingContextState<'a, 'b> { segment_items.first_mut(), ) { ( - Some(LineItem::TextRun(last_line_item)), - Some(LineItem::TextRun(first_segment_item)), - ) if last_line_item.font_key == first_segment_item.font_key => { + Some(LineItem::TextRun(last_inline_box_identifier, last_line_item)), + Some(LineItem::TextRun(first_inline_box_identifier, first_segment_item)), + ) if last_line_item.font_key == first_segment_item.font_key && + last_inline_box_identifier == first_inline_box_identifier => + { last_line_item.text.append(&mut first_segment_item.text); 1 }, @@ -1695,6 +1613,7 @@ impl InlineFormattingContext { containing_block, sequential_layout_state, layout_context, + inline_boxes: &self.inline_boxes, fonts: &self.font_metrics, fragments: Vec::new(), current_line: LineUnderConstruction::new(LogicalVec2 { @@ -1709,6 +1628,7 @@ impl InlineFormattingContext { default_font_metrics.as_ref(), ), inline_box_state_stack: Vec::new(), + inline_box_states: Vec::with_capacity(self.inline_boxes.len()), current_line_segment: UnbreakableSegmentUnderConstruction::new(), linebreak_before_new_content: false, deferred_br_clear: Clear::None, @@ -1747,6 +1667,7 @@ impl InlineFormattingContext { }, InlineItem::OutOfFlowAbsolutelyPositionedBox(positioned_box) => { ifc.push_line_item_to_unbreakable_segment(LineItem::AbsolutelyPositioned( + ifc.current_inline_box_identifier(), AbsolutelyPositionedLineItem { absolutely_positioned_box: positioned_box.clone(), }, @@ -1819,7 +1740,7 @@ impl InlineContainerState { Self { style, flags, - has_content: false, + has_content: RefCell::new(false), text_decoration_line, nested_strut_block_sizes: nested_block_sizes, strut_block_sizes, @@ -1969,54 +1890,6 @@ impl InlineContainerState { } } -impl InlineBoxContainerState { - fn new( - inline_box: &InlineBox, - containing_block: &ContainingBlock, - layout_context: &LayoutContext, - parent_container: &InlineContainerState, - is_last_fragment: bool, - font_metrics: Option<&FontMetrics>, - ) -> Self { - let style = inline_box.style.clone(); - let pbm = style.padding_border_margin(containing_block); - - let mut flags = InlineContainerStateFlags::empty(); - if inline_container_needs_strut(&style, layout_context, Some(&pbm)) { - flags.insert(InlineContainerStateFlags::CREATE_STRUT); - } - - Self { - base: InlineContainerState::new( - style, - flags, - Some(parent_container), - parent_container.text_decoration_line, - font_metrics, - ), - base_fragment_info: inline_box.base_fragment_info, - pbm, - is_last_fragment, - } - } - - fn layout_into_line_item( - &mut self, - is_first_fragment: bool, - is_last_fragment_of_ib_split: bool, - ) -> InlineBoxLineItem { - InlineBoxLineItem { - base_fragment_info: self.base_fragment_info, - style: self.base.style.clone(), - pbm: self.pbm.clone(), - is_first_fragment, - is_last_fragment_of_ib_split, - font_metrics: self.base.font_metrics.clone(), - baseline_offset: self.base.baseline_offset, - } - } -} - impl IndependentFormattingContext { fn layout_into_line_items( &mut self, @@ -2169,13 +2042,16 @@ impl IndependentFormattingContext { size.inline.into(), SegmentContentFlags::empty(), ); - ifc.push_line_item_to_unbreakable_segment(LineItem::Atomic(AtomicLineItem { - fragment, - size, - positioning_context: child_positioning_context, - baseline_offset_in_parent, - baseline_offset_in_item: baseline_offset, - })); + ifc.push_line_item_to_unbreakable_segment(LineItem::Atomic( + ifc.current_inline_box_identifier(), + AtomicLineItem { + fragment, + size, + positioning_context: child_positioning_context, + baseline_offset_in_parent, + baseline_offset_in_item: baseline_offset, + }, + )); // Defer a soft wrap opportunity for when we next process text content. ifc.have_deferred_soft_wrap_opportunity = true; @@ -2245,16 +2121,19 @@ impl FloatBox { ifc.positioning_context, ifc.containing_block, ); - ifc.push_line_item_to_unbreakable_segment(LineItem::Float(FloatLineItem { - fragment, - needs_placement: true, - })); + ifc.push_line_item_to_unbreakable_segment(LineItem::Float( + ifc.current_inline_box_identifier(), + FloatLineItem { + fragment, + needs_placement: true, + }, + )); } } fn place_pending_floats(ifc: &mut InlineFormattingContextState, line_items: &mut [LineItem]) { for item in line_items.iter_mut() { - if let LineItem::Float(float_line_item) = item { + if let LineItem::Float(_, float_line_item) = item { if float_line_item.needs_placement { ifc.place_float_fragment(&mut float_line_item.fragment); } @@ -2387,7 +2266,7 @@ impl<'a> ContentSizesComputation<'a> { // for determining intrinsic size contributions. // 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 inline_box = (*inline_box).borrow(); let zero = Length::zero(); let padding = inline_box .style @@ -2525,36 +2404,3 @@ impl<'a> ContentSizesComputation<'a> { .traverse(inline_formatting_context) } } - -#[derive(Debug, Default, Serialize)] -pub(crate) struct InlineBoxes { - inline_boxes: Vec>, -} - -impl InlineBoxes { - pub(super) fn get(&self, identifier: &InlineBoxIdentifier) -> ArcRefCell { - 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 } - } -} diff --git a/components/layout_2020/positioned.rs b/components/layout_2020/positioned.rs index 184b73a233b..b23f76db69b 100644 --- a/components/layout_2020/positioned.rs +++ b/components/layout_2020/positioned.rs @@ -403,7 +403,7 @@ impl PositioningContext { } /// A data structure which stores the size of a positioning context. -#[derive(PartialEq)] +#[derive(Clone, Copy, PartialEq)] pub(crate) struct PositioningContextLength { /// The number of boxes that will be hoisted the the nearest positioned ancestor for /// layout.