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 <mrobinson@igalia.com>
Co-authored-by: Rakhi Sharma <atbrakhi@igalia.com>
This commit is contained in:
Martin Robinson 2024-07-03 17:15:31 +02:00 committed by GitHub
parent 4357751f28
commit 4e79ac5701
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 943 additions and 690 deletions

View file

@ -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};

View file

@ -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;

View file

@ -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>>(

View file

@ -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<ComputedValues>,
/// 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<usize>,
}
impl InlineBox {
pub(crate) fn new<'dom, Node: NodeExt<'dom>>(info: &NodeAndStyleInfo<Node>) -> 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<ArcRefCell<InlineBox>>,
/// 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<InlineBoxTreePathToken>,
}
impl InlineBoxes {
pub(super) fn len(&self) -> usize {
self.inline_boxes.len()
}
pub(super) fn get(&self, identifier: &InlineBoxIdentifier) -> ArcRefCell<InlineBox> {
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<InlineBoxIdentifier>,
to: InlineBoxIdentifier,
) -> IntoIter<InlineBoxTreePathToken> {
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
}
}

File diff suppressed because it is too large Load diff

View file

@ -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<ComputedValues>,
/// 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<usize>,
}
impl InlineBox {
pub(crate) fn new<'dom, Node: NodeExt<'dom>>(info: &NodeAndStyleInfo<Node>) -> 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<ComputedValues>,
@ -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<bool>,
/// Indicates whether this nesting level have text decorations in effect.
/// From <https://drafts.csswg.org/css-text-decor/#line-decoration>
@ -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<FontKeyAndMetrics>,
@ -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<InlineBoxContainerState>,
inline_box_state_stack: Vec<Rc<InlineBoxContainerState>>,
/// 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<Rc<InlineBoxContainerState>>,
/// 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<InlineBoxIdentifier> {
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<ArcRefCell<InlineBox>>,
}
impl InlineBoxes {
pub(super) fn get(&self, identifier: &InlineBoxIdentifier) -> ArcRefCell<InlineBox> {
self.inline_boxes[identifier.index].clone()
}
pub(super) fn end_inline_box(&mut self) {}
pub(super) fn start_inline_box(&mut self, inline_box: InlineBox) -> InlineBoxIdentifier {
let identifier = InlineBoxIdentifier {
index: self.inline_boxes.len(),
};
self.inline_boxes.push(ArcRefCell::new(inline_box));
identifier
}
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
pub(crate) struct InlineBoxIdentifier {
pub index: usize,
}
impl InlineBoxIdentifier {
fn root() -> Self {
InlineBoxIdentifier { index: 0 }
}
}

View file

@ -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.