aboutsummaryrefslogtreecommitdiffstats
path: root/components/layout_2020/flow/inline/line.rs
diff options
context:
space:
mode:
Diffstat (limited to 'components/layout_2020/flow/inline/line.rs')
-rw-r--r--components/layout_2020/flow/inline/line.rs933
1 files changed, 545 insertions, 388 deletions
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,366 +16,373 @@ 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 `<span>` 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<InlineBoxIdentifier>,
+
+ /// The fragments that are laid out into this inline container on a line.
+ pub fragments: Vec<Fragment>,
+
+ /// 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<Length>,
+ pub parent_offset: LogicalVec2<Au>,
/// 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,
-
- /// The amount of space to add to each justification opportunity in order to implement
- /// `text-align: justify`.
- pub justification_adjustment: Length,
-
- /// The metrics of this line, which should remain constant throughout the
- /// layout process.
- pub line_metrics: &'a LineMetrics,
-}
-
-pub(super) fn layout_line_items(
- iterator: &mut IntoIter<LineItem>,
- layout_context: &LayoutContext,
- state: &mut LineItemLayoutState,
- saw_end: &mut bool,
-) -> Vec<Fragment> {
- 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));
- }
- },
- LineItem::StartInlineBox(box_line_item) => {
- if let Some(fragment) = box_line_item.layout(iterator, layout_context, state) {
- fragments.push(Fragment::Box(fragment))
- }
- },
- LineItem::EndInlineBox => {
- *saw_end = true;
- break;
- },
- LineItem::Atomic(atomic_line_item) => {
- fragments.push(Fragment::Box(atomic_line_item.layout(state)));
- },
- 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)));
- },
- }
- }
- fragments
-}
-
-pub(super) enum LineItem {
- TextRun(TextRunLineItem),
- StartInlineBox(InlineBoxLineItem),
- EndInlineBox,
- Atomic(AtomicLineItem),
- AbsolutelyPositioned(AbsolutelyPositionedLineItem),
- Float(FloatLineItem),
+ /// 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<PositioningContext, PositioningContextLength>,
}
-impl LineItem {
- 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,
+impl LineItemLayoutInlineContainerState {
+ fn new(
+ identifier: Option<InlineBoxIdentifier>,
+ parent_offset: LogicalVec2<Au>,
+ 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,
}
}
- 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,
- }
+ 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
}
}
-pub(super) struct TextRunLineItem {
- pub base_fragment_info: BaseFragmentInfo,
- pub parent_style: Arc<ComputedValues>,
- pub text: Vec<std::sync::Arc<GlyphStore>>,
- pub font_metrics: FontMetrics,
- pub font_key: FontInstanceKey,
- pub text_decoration_line: TextDecorationLine,
-}
+/// 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,
-impl TextRunLineItem {
- fn trim_whitespace_at_end(&mut self, whitespace_trimmed: &mut Length) -> bool {
- if matches!(
- self.parent_style.get_inherited_text().white_space_collapse,
- WhiteSpaceCollapse::Preserve | WhiteSpaceCollapse::BreakSpaces
- ) {
- return false;
- }
+ /// 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<InlineBoxContainerState>],
- let index_of_last_non_whitespace = self
- .text
- .iter()
- .rev()
- .position(|glyph| !glyph.is_whitespace())
- .map(|offset_from_end| self.text.len() - offset_from_end);
+ /// 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<LineItemLayoutInlineContainerState>,
- let first_whitespace_index = index_of_last_non_whitespace.unwrap_or(0);
- *whitespace_trimmed += self
- .text
- .drain(first_whitespace_index..)
- .map(|glyph| Length::from(glyph.total_advance()))
- .sum();
+ /// The current [`super::LineItemLayoutInlineContainerState`].
+ pub state: LineItemLayoutInlineContainerState,
- // Only keep going if we only encountered whitespace.
- index_of_last_non_whitespace.is_none()
- }
+ /// The [`LayoutContext`] to use for laying out absolutely positioned line items.
+ pub layout_context: &'a LayoutContext<'a>,
- fn trim_whitespace_at_start(&mut self, whitespace_trimmed: &mut Length) -> bool {
- if matches!(
- self.parent_style.get_inherited_text().white_space_collapse,
- WhiteSpaceCollapse::Preserve | WhiteSpaceCollapse::BreakSpaces
- ) {
- return false;
- }
+ /// The root positioning context for this layout.
+ pub root_positioning_context: &'a mut PositioningContext,
- let index_of_first_non_whitespace = self
- .text
- .iter()
- .position(|glyph| !glyph.is_whitespace())
- .unwrap_or(self.text.len());
+ /// The [`ContainingBlock`] of the parent [`super::InlineFormattingContext`] of the line being
+ /// laid out.
+ pub ifc_containing_block: &'a ContainingBlock<'a>,
- *whitespace_trimmed += self
- .text
- .drain(0..index_of_first_non_whitespace)
- .map(|glyph| Length::from(glyph.total_advance()))
- .sum();
+ /// The metrics of this line, which should remain constant throughout the
+ /// layout process.
+ pub line_metrics: LineMetrics,
- // Only keep going if we only encountered whitespace.
- self.text.is_empty()
- }
+ /// The amount of space to add to each justification opportunity in order to implement
+ /// `text-align: justify`.
+ pub justification_adjustment: Au,
+}
- fn layout(self, state: &mut LineItemLayoutState) -> Option<TextFragment> {
- if self.text.is_empty() {
- return None;
+impl<'a> LineItemLayout<'a> {
+ pub(super) fn layout_line_items(
+ state: &mut InlineFormattingContextState,
+ iterator: &mut IntoIter<LineItem>,
+ start_position: LogicalVec2<Au>,
+ effective_block_advance: &LineBlockSizes,
+ justification_adjustment: Au,
+ ) -> Vec<Fragment> {
+ 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)
+ }
- 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();
+ /// 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<InlineBoxIdentifier>) {
+ // 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;
+ };
- if !state.justification_adjustment.is_zero() {
- inline_advance +=
- state.justification_adjustment * number_of_justification_opportunities as f32;
+ // 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(),
+ }
}
+ }
- // 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;
+ pub(super) fn layout(&mut self, iterator: &mut IntoIter<LineItem>) -> Vec<Fragment> {
+ 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),
+ }
+ }
- let rect = LogicalRect {
- start_corner,
- size: LogicalVec2 {
- block: self.font_metrics.line_gap.into(),
- inline: inline_advance,
- },
- };
+ // 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)
+ }
- 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(),
- })
+ 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)
}
-}
-#[derive(Clone)]
-pub(super) struct InlineBoxLineItem {
- pub base_fragment_info: BaseFragmentInfo,
- pub style: Arc<ComputedValues>,
- pub pbm: PaddingBorderMargin,
+ 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());
- /// 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,
+ 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);
- /// 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,
+ 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()),
+ };
- /// The FontMetrics for the default font used in this inline box.
- pub font_metrics: FontMetrics,
+ let parent_offset = LogicalVec2 {
+ inline: self.state.inline_advance + self.state.parent_offset.inline,
+ block: block_start_offset,
+ };
- /// 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,
-}
+ 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,
+ ),
+ );
-impl InlineBoxLineItem {
- fn layout(
- self,
- iterator: &mut IntoIter<LineItem>,
- layout_context: &LayoutContext,
- state: &mut LineItemLayoutState,
- ) -> Option<BoxFragment> {
- 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 {
+ 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 !self.is_last_fragment_of_ib_split {
+ if !inner_state
+ .flags
+ .contains(LineLayoutInlineContainerFlags::HAD_END_PBM)
+ {
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();
- }
+ // 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 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
+ if inner_state.fragments.is_empty() &&
+ !inner_state
+ .flags
+ .contains(LineLayoutInlineContainerFlags::HAD_START_PBM) &&
+ pbm_sums.inline_sum().is_zero()
{
- return None;
+ return;
}
+ // Make `content_rect` relative to the parent Fragment.
let mut content_rect = LogicalRect {
start_corner: LogicalVec2 {
- inline: state.inline_position,
- block: block_start_offset.into(),
+ inline: self.state.inline_advance + pbm_sums.inline_start,
+ block: inner_state.parent_offset.block - self.state.parent_offset.block,
},
size: LogicalVec2 {
- inline: nested_state.inline_position - state.inline_position,
- block: self.font_metrics.line_gap.into(),
+ inline: inner_state.inline_advance,
+ block: inline_box_state.base.font_metrics.line_gap,
},
};
- // Make `content_rect` relative to the parent Fragment.
- content_rect.start_corner -= state.parent_offset;
+ 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, state.ifc_containing_block).into();
+ content_rect.start_corner += relative_adjustement(style, self.ifc_containing_block);
}
let mut fragment = BoxFragment::new(
- self.base_fragment_info,
- self.style.clone(),
- fragments,
- content_rect.into(),
+ inline_box.base_fragment_info,
+ style.clone(),
+ inner_state.fragments,
+ content_rect,
padding,
border,
margin,
@@ -382,136 +390,139 @@ impl InlineBoxLineItem {
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(
+ 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,
- original_nested_positioning_context_length,
+ PositioningContextLength::zero(),
);
+ self.current_positioning_context_mut()
+ .append(positioning_context);
+ },
+ Either::Second(start_offset) => {
+ self.current_positioning_context_mut()
+ .adjust_static_position_of_hoisted_fragments_with_offset(
+ &fragment.content_rect.start_corner,
+ start_offset,
+ );
+ },
}
- Some(fragment)
+ self.state.inline_advance += inner_state.inline_advance + pbm_sums.inline_sum();
+ self.state.fragments.push(Fragment::Box(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;
+ 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 self.style.clone_vertical_align() {
+ match inline_box_state.base.style.clone_vertical_align() {
GenericVerticalAlign::Keyword(VerticalAlignKeyword::Top) => {
- let line_height: Au = line_height(&self.style, &self.font_metrics).into();
+ let line_height: Au = line_height(style, 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 line_height: Au = line_height(style, 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
+ self.line_metrics.block_size - line_height + half_leading
},
_ => {
- state.line_metrics.baseline_block_offset + self.baseline_offset -
+ self.line_metrics.baseline_block_offset + inline_box_state.base.baseline_offset -
space_above_baseline
},
}
}
-}
-pub(super) struct AtomicLineItem {
- pub fragment: BoxFragment,
- pub size: LogicalVec2<Au>,
- pub positioning_context: Option<PositioningContext>,
+ fn layout_text_run(&mut self, text_item: TextRunLineItem) {
+ if text_item.text.is_empty() {
+ return;
+ }
- /// The block offset of this items' 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_in_parent: Au,
+ 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();
- /// The offset of the baseline inside this item.
- pub baseline_offset_in_item: Au,
-}
+ if !self.justification_adjustment.is_zero() {
+ inline_advance += self
+ .justification_adjustment
+ .scale_by(number_of_justification_opportunities as f32);
+ }
-impl AtomicLineItem {
- fn layout(mut self, state: &mut LineItemLayoutState) -> BoxFragment {
+ // 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.
- 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();
+ 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 self.fragment.style.clone_position().is_relative() {
- self.fragment.content_rect.start_corner +=
- relative_adjustement(&self.fragment.style, state.ifc_containing_block);
+ if atomic.fragment.style.clone_position().is_relative() {
+ atomic.fragment.content_rect.start_corner +=
+ relative_adjustement(&atomic.fragment.style, self.ifc_containing_block);
}
- state.inline_position += self.size.inline.into();
-
- if let Some(mut positioning_context) = self.positioning_context {
+ if let Some(mut positioning_context) = atomic.positioning_context {
positioning_context.adjust_static_position_of_hoisted_fragments_with_offset(
- &self.fragment.content_rect.start_corner,
+ &atomic.fragment.content_rect.start_corner,
PositioningContextLength::zero(),
);
- state.positioning_context.append(positioning_context);
+ self.current_positioning_context_mut()
+ .append(positioning_context);
}
- self.fragment
+ self.state.inline_advance += atomic.size.inline;
+ self.state.fragments.push(Fragment::Box(atomic.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 {
- match self.fragment.style.clone_vertical_align() {
- GenericVerticalAlign::Keyword(VerticalAlignKeyword::Top) => Length::zero(),
- GenericVerticalAlign::Keyword(VerticalAlignKeyword::Bottom) => {
- line_metrics.block_size - self.size.block.into()
- },
-
- // 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)
- },
- }
- }
-}
-
-pub(super) struct AbsolutelyPositionedLineItem {
- pub absolutely_positioned_box: ArcRefCell<AbsolutelyPositionedBox>,
-}
-
-impl AbsolutelyPositionedLineItem {
- fn layout(self, state: &mut LineItemLayoutState) -> ArcRefCell<HoistedSharedFragment> {
- let box_ = self.absolutely_positioned_box;
- let style = AtomicRef::map(box_.borrow(), |box_| box_.context.style());
+ 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
@@ -529,52 +540,198 @@ impl AbsolutelyPositionedLineItem {
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,
+ 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: Length::zero(),
- block: state.line_metrics.block_size - state.parent_offset.block,
+ inline: -self.state.parent_offset.inline,
+ block: self.line_metrics.block_size - self.state.parent_offset.block,
}
};
let hoisted_box = AbsolutelyPositionedBox::to_hoisted(
- box_.clone(),
- initial_start_corner,
- state.ifc_containing_block,
+ absolute.absolutely_positioned_box.clone(),
+ initial_start_corner.into(),
+ self.ifc_containing_block,
);
let hoisted_fragment = hoisted_box.fragment.clone();
- state.positioning_context.push(hoisted_box);
- hoisted_fragment
+ self.current_positioning_context_mut().push(hoisted_box);
+ self.state
+ .fragments
+ .push(Fragment::AbsoluteOrFixedPositioned(hoisted_fragment));
}
-}
-pub(super) struct FloatLineItem {
- pub fragment: BoxFragment,
- /// Whether or not this float Fragment has been placed yet. Fragments that
- /// do not fit on a line need to be placed after the hypothetical block start
- /// of the next line.
- pub needs_placement: bool,
-}
+ fn layout_float(&mut self, mut float: FloatLineItem) {
+ self.state
+ .flags
+ .insert(LineLayoutInlineContainerFlags::HAD_ANY_FLOATS);
-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
+ // 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: state.parent_offset.inline,
- block: state.line_metrics.block_offset + state.parent_offset.block,
+ inline: self.state.parent_offset.inline,
+ block: self.line_metrics.block_offset + self.state.parent_offset.block,
};
- self.fragment.content_rect.start_corner -= distance_from_parent_to_ifc.into();
- self.fragment
+ float.fragment.content_rect.start_corner -= distance_from_parent_to_ifc;
+ self.state.fragments.push(Fragment::Float(float.fragment));
+ }
+}
+
+pub(super) enum LineItem {
+ StartInlineBoxPaddingBorderMargin(InlineBoxIdentifier),
+ EndInlineBoxPaddingBorderMargin(InlineBoxIdentifier),
+ TextRun(Option<InlineBoxIdentifier>, TextRunLineItem),
+ Atomic(Option<InlineBoxIdentifier>, AtomicLineItem),
+ AbsolutelyPositioned(Option<InlineBoxIdentifier>, AbsolutelyPositionedLineItem),
+ Float(Option<InlineBoxIdentifier>, FloatLineItem),
+}
+
+impl LineItem {
+ fn inline_box_identifier(&self) -> Option<InlineBoxIdentifier> {
+ 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::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::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,
+ }
+ }
+}
+
+pub(super) struct TextRunLineItem {
+ pub base_fragment_info: BaseFragmentInfo,
+ pub parent_style: Arc<ComputedValues>,
+ pub text: Vec<std::sync::Arc<GlyphStore>>,
+ pub font_metrics: FontMetrics,
+ pub font_key: FontInstanceKey,
+ pub text_decoration_line: TextDecorationLine,
+}
+
+impl TextRunLineItem {
+ fn trim_whitespace_at_end(&mut self, whitespace_trimmed: &mut Length) -> bool {
+ if matches!(
+ self.parent_style.get_inherited_text().white_space_collapse,
+ WhiteSpaceCollapse::Preserve | WhiteSpaceCollapse::BreakSpaces
+ ) {
+ return false;
+ }
+
+ let index_of_last_non_whitespace = self
+ .text
+ .iter()
+ .rev()
+ .position(|glyph| !glyph.is_whitespace())
+ .map(|offset_from_end| self.text.len() - offset_from_end);
+
+ let first_whitespace_index = index_of_last_non_whitespace.unwrap_or(0);
+ *whitespace_trimmed += self
+ .text
+ .drain(first_whitespace_index..)
+ .map(|glyph| Length::from(glyph.total_advance()))
+ .sum();
+
+ // Only keep going if we only encountered whitespace.
+ index_of_last_non_whitespace.is_none()
+ }
+
+ fn trim_whitespace_at_start(&mut self, whitespace_trimmed: &mut Length) -> bool {
+ if matches!(
+ self.parent_style.get_inherited_text().white_space_collapse,
+ WhiteSpaceCollapse::Preserve | WhiteSpaceCollapse::BreakSpaces
+ ) {
+ return false;
+ }
+
+ let index_of_first_non_whitespace = self
+ .text
+ .iter()
+ .position(|glyph| !glyph.is_whitespace())
+ .unwrap_or(self.text.len());
+
+ *whitespace_trimmed += self
+ .text
+ .drain(0..index_of_first_non_whitespace)
+ .map(|glyph| Length::from(glyph.total_advance()))
+ .sum();
+
+ // Only keep going if we only encountered whitespace.
+ self.text.is_empty()
}
}
+pub(super) struct AtomicLineItem {
+ pub fragment: BoxFragment,
+ pub size: LogicalVec2<Au>,
+ pub positioning_context: Option<PositioningContext>,
+
+ /// The block offset of this items' 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_in_parent: Au,
+
+ /// The offset of the baseline inside this item.
+ pub baseline_offset_in_item: Au,
+}
+
+impl AtomicLineItem {
+ /// 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) -> Au {
+ match self.fragment.style.clone_vertical_align() {
+ GenericVerticalAlign::Keyword(VerticalAlignKeyword::Top) => Au::zero(),
+ GenericVerticalAlign::Keyword(VerticalAlignKeyword::Bottom) => {
+ 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;
+ baseline - self.baseline_offset_in_item
+ },
+ }
+ }
+}
+
+pub(super) struct AbsolutelyPositionedLineItem {
+ pub absolutely_positioned_box: ArcRefCell<AbsolutelyPositionedBox>,
+}
+
+pub(super) struct FloatLineItem {
+ pub fragment: BoxFragment,
+ /// Whether or not this float Fragment has been placed yet. Fragments that
+ /// do not fit on a line need to be placed after the hypothetical block start
+ /// of the next line.
+ pub needs_placement: bool,
+}
+
fn line_height(parent_style: &ComputedValues, font_metrics: &FontMetrics) -> Length {
let font = parent_style.get_font();
let font_size = font.font_size.computed_size();