diff options
Diffstat (limited to 'components/layout/flow')
-rw-r--r-- | components/layout/flow/construct.rs | 21 | ||||
-rw-r--r-- | components/layout/flow/inline/construct.rs | 64 | ||||
-rw-r--r-- | components/layout/flow/inline/inline_box.rs | 16 | ||||
-rw-r--r-- | components/layout/flow/inline/mod.rs | 8 | ||||
-rw-r--r-- | components/layout/flow/mod.rs | 105 | ||||
-rw-r--r-- | components/layout/flow/root.rs | 32 |
6 files changed, 177 insertions, 69 deletions
diff --git a/components/layout/flow/construct.rs b/components/layout/flow/construct.rs index a6471756db8..5ed567f513b 100644 --- a/components/layout/flow/construct.rs +++ b/components/layout/flow/construct.rs @@ -458,15 +458,14 @@ where self.propagated_data.without_text_decorations(), ), ); - box_slot.set(LayoutBox::InlineLevel(atomic)); + box_slot.set(LayoutBox::InlineLevel(vec![atomic])); return; }; // Otherwise, this is just a normal inline box. Whatever happened before, all we need to do // before recurring is to remember this ongoing inline level box. - let inline_item = self - .inline_formatting_context_builder - .start_inline_box(InlineBox::new(info)); + self.inline_formatting_context_builder + .start_inline_box(InlineBox::new(info), None); if is_list_item { if let Some((marker_info, marker_contents)) = @@ -486,8 +485,14 @@ where self.finish_anonymous_table_if_needed(); - self.inline_formatting_context_builder.end_inline_box(); - box_slot.set(LayoutBox::InlineLevel(inline_item)); + // As we are ending this inline box, during the course of the `traverse()` above, the ongoing + // inline formatting context may have been split around block-level elements. In that case, + // more than a single inline box tree item may have been produced for this inline-level box. + // `InlineFormattingContextBuilder::end_inline_box()` is returning all of those box tree + // items. + box_slot.set(LayoutBox::InlineLevel( + self.inline_formatting_context_builder.end_inline_box(), + )); } fn handle_block_level_element( @@ -574,7 +579,7 @@ where display_inside, contents, )); - box_slot.set(LayoutBox::InlineLevel(inline_level_box)); + box_slot.set(LayoutBox::InlineLevel(vec![inline_level_box])); return; } @@ -607,7 +612,7 @@ where contents, self.propagated_data, )); - box_slot.set(LayoutBox::InlineLevel(inline_level_box)); + box_slot.set(LayoutBox::InlineLevel(vec![inline_level_box])); return; } diff --git a/components/layout/flow/inline/construct.rs b/components/layout/flow/inline/construct.rs index 7c668751ef6..61292701a9f 100644 --- a/components/layout/flow/inline/construct.rs +++ b/components/layout/flow/inline/construct.rs @@ -6,6 +6,7 @@ use std::borrow::Cow; use std::char::{ToLowercase, ToUppercase}; use icu_segmenter::WordSegmenter; +use itertools::izip; use servo_arc::Arc; use style::computed_values::white_space_collapse::T as WhiteSpaceCollapse; use style::values::specified::text::TextTransformCase; @@ -67,6 +68,16 @@ pub(crate) struct InlineFormattingContextBuilder { /// When an inline box ends, it's removed from this stack. inline_box_stack: Vec<InlineBoxIdentifier>, + /// Normally, an inline box produces a single box tree [`InlineItem`]. When a block + /// element causes an inline box [to be split], it can produce multiple + /// [`InlineItem`]s, all inserted into different [`InlineFormattingContext`]s. + /// [`Self::block_in_inline_splits`] is responsible for tracking all of these split + /// inline box results, so that they can be inserted into the [`crate::dom::BoxSlot`] + /// for the DOM element once it has been processed for BoxTree construction. + /// + /// [to be split]: https://www.w3.org/TR/CSS2/visuren.html#anonymous-block-level + block_in_inline_splits: Vec<Vec<ArcRefCell<InlineItem>>>, + /// Whether or not the inline formatting context under construction has any /// uncollapsible text content. pub has_uncollapsible_text_content: bool, @@ -162,29 +173,42 @@ impl InlineFormattingContextBuilder { inline_level_box } - pub(crate) fn start_inline_box(&mut self, inline_box: InlineBox) -> ArcRefCell<InlineItem> { + pub(crate) fn start_inline_box( + &mut self, + inline_box: InlineBox, + block_in_inline_splits: Option<Vec<ArcRefCell<InlineItem>>>, + ) { self.push_control_character_string(inline_box.base.style.bidi_control_chars().0); let (identifier, inline_box) = self.inline_boxes.start_inline_box(inline_box); let inline_level_box = ArcRefCell::new(InlineItem::StartInlineBox(inline_box)); self.inline_items.push(inline_level_box.clone()); self.inline_box_stack.push(identifier); - inline_level_box + + let mut block_in_inline_splits = block_in_inline_splits.unwrap_or_default(); + block_in_inline_splits.push(inline_level_box); + self.block_in_inline_splits.push(block_in_inline_splits); } - pub(crate) fn end_inline_box(&mut self) -> ArcRefCell<InlineBox> { - let identifier = self.end_inline_box_internal(); + /// End the ongoing inline box in this [`InlineFormattingContextBuilder`], returning + /// shared references to all of the box tree items that were created for it. More than + /// a single box tree items may be produced for a single inline box when that inline + /// box is split around a block-level element. + pub(crate) fn end_inline_box(&mut self) -> Vec<ArcRefCell<InlineItem>> { + let (identifier, block_in_inline_splits) = self.end_inline_box_internal(); let inline_level_box = self.inline_boxes.get(&identifier); - inline_level_box.borrow_mut().is_last_fragment = true; - - self.push_control_character_string( - inline_level_box.borrow().base.style.bidi_control_chars().1, - ); + { + let mut inline_level_box = inline_level_box.borrow_mut(); + inline_level_box.is_last_split = true; + self.push_control_character_string(inline_level_box.base.style.bidi_control_chars().1); + } - inline_level_box + block_in_inline_splits.unwrap_or_default() } - fn end_inline_box_internal(&mut self) -> InlineBoxIdentifier { + fn end_inline_box_internal( + &mut self, + ) -> (InlineBoxIdentifier, Option<Vec<ArcRefCell<InlineItem>>>) { let identifier = self .inline_box_stack .pop() @@ -193,7 +217,12 @@ impl InlineFormattingContextBuilder { .push(ArcRefCell::new(InlineItem::EndInlineBox)); self.inline_boxes.end_inline_box(identifier); - identifier + + // This might be `None` if this builder has already drained its block-in-inline-splits + // into the new builder on the other side of a new block-in-inline split. + let block_in_inline_splits = self.block_in_inline_splits.pop(); + + (identifier, block_in_inline_splits) } pub(crate) fn push_text<'dom, Node: NodeExt<'dom>>( @@ -295,12 +324,21 @@ impl InlineFormattingContextBuilder { // marked as not being the first fragment. No inline content is carried over to this new // builder. let mut new_builder = InlineFormattingContextBuilder::new(); - for identifier in self.inline_box_stack.iter() { + let block_in_inline_splits = std::mem::take(&mut self.block_in_inline_splits); + for (identifier, historical_inline_boxes) in + izip!(self.inline_box_stack.iter(), block_in_inline_splits) + { + // Start a new inline box for every ongoing inline box in this + // InlineFormattingContext once we are done processing this block element, + // being sure to give the block-in-inline-split to the new + // InlineFormattingContext. These will finally be inserted into the DOM's + // BoxSlot once the inline box has been fully processed. new_builder.start_inline_box( self.inline_boxes .get(identifier) .borrow() .split_around_block(), + Some(historical_inline_boxes), ); } let mut inline_builder_from_before_split = std::mem::replace(self, new_builder); diff --git a/components/layout/flow/inline/inline_box.rs b/components/layout/flow/inline/inline_box.rs index 97398d6e708..de79f876340 100644 --- a/components/layout/flow/inline/inline_box.rs +++ b/components/layout/flow/inline/inline_box.rs @@ -23,8 +23,12 @@ pub(crate) struct InlineBox { pub base: LayoutBoxBase, /// The identifier of this inline box in the containing [`super::InlineFormattingContext`]. pub(super) identifier: InlineBoxIdentifier, - pub is_first_fragment: bool, - pub is_last_fragment: bool, + /// Whether or not this is the first instance of an [`InlineBox`] before a possible + /// block-in-inline split. When no split occurs, this is always true. + pub is_first_split: bool, + /// Whether or not this is the last instance of an [`InlineBox`] before a possible + /// block-in-inline split. When no split occurs, this is always true. + pub is_last_split: bool, /// The index of the default font in the [`super::InlineFormattingContext`]'s font metrics store. /// This is initialized during IFC shaping. pub default_font_index: Option<usize>, @@ -36,8 +40,8 @@ impl InlineBox { base: LayoutBoxBase::new(info.into(), 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, + is_first_split: true, + is_last_split: false, default_font_index: None, } } @@ -45,8 +49,8 @@ impl InlineBox { pub(crate) fn split_around_block(&self) -> Self { Self { base: LayoutBoxBase::new(self.base.base_fragment_info, self.base.style.clone()), - is_first_fragment: false, - is_last_fragment: false, + is_first_split: false, + is_last_split: false, ..*self } } diff --git a/components/layout/flow/inline/mod.rs b/components/layout/flow/inline/mod.rs index 490917d95a3..dabb9773410 100644 --- a/components/layout/flow/inline/mod.rs +++ b/components/layout/flow/inline/mod.rs @@ -744,7 +744,7 @@ impl InlineFormattingContextLayout<'_> { self.containing_block, self.layout_context, self.current_inline_container_state(), - inline_box.is_last_fragment, + inline_box.is_last_split, inline_box .default_font_index .map(|index| &self.ifc.font_metrics[index].metrics), @@ -773,7 +773,7 @@ impl InlineFormattingContextLayout<'_> { ); } - if inline_box.is_first_fragment { + if inline_box.is_first_split { self.current_line_segment.inline_size += inline_box_state.pbm.padding.inline_start + inline_box_state.pbm.border.inline_start + inline_box_state.pbm.margin.inline_start.auto_is(Au::zero); @@ -2349,10 +2349,10 @@ impl<'layout_data> ContentSizesComputation<'layout_data> { .auto_is(Au::zero); let pbm = margin + padding + border; - if inline_box.is_first_fragment { + if inline_box.is_first_split { self.add_inline_size(pbm.inline_start); } - if inline_box.is_last_fragment { + if inline_box.is_last_split { self.ending_inline_pbm_stack.push(pbm.inline_end); } else { self.ending_inline_pbm_stack.push(Au::zero()); diff --git a/components/layout/flow/mod.rs b/components/layout/flow/mod.rs index f92650ef340..d983e8592c4 100644 --- a/components/layout/flow/mod.rs +++ b/components/layout/flow/mod.rs @@ -896,6 +896,7 @@ fn layout_in_flow_non_replaced_block_level_same_formatting_context( block_sizes, depends_on_block_constraints, available_block_size, + justify_self, } = solve_containing_block_padding_and_border_for_in_flow_box( containing_block, &layout_style, @@ -909,6 +910,7 @@ fn layout_in_flow_non_replaced_block_level_same_formatting_context( containing_block, &pbm, containing_block_for_children.size.inline, + justify_self, ); let computed_block_size = style.content_block_size(); @@ -1154,6 +1156,7 @@ impl IndependentNonReplacedContents { block_sizes, depends_on_block_constraints, available_block_size, + justify_self, } = solve_containing_block_padding_and_border_for_in_flow_box( containing_block, &layout_style, @@ -1185,7 +1188,7 @@ impl IndependentNonReplacedContents { let ResolvedMargins { margin, effective_margin_inline_start, - } = solve_margins(containing_block, &pbm, inline_size); + } = solve_margins(containing_block, &pbm, inline_size, justify_self); let content_rect = LogicalRect { start_corner: LogicalVec2 { @@ -1300,17 +1303,12 @@ impl IndependentNonReplacedContents { .sizes }; - // TODO: the automatic inline size should take `justify-self` into account. + let justify_self = resolve_justify_self(style, containing_block.style); let is_table = self.is_table(); - let automatic_inline_size = if is_table { - Size::FitContent - } else { - Size::Stretch - }; let compute_inline_size = |stretch_size| { content_box_sizes.inline.resolve( Direction::Inline, - automatic_inline_size, + automatic_inline_size(justify_self, is_table), Au::zero, Some(stretch_size), get_inline_content_sizes, @@ -1472,6 +1470,7 @@ impl IndependentNonReplacedContents { &pbm, content_size.inline + pbm.padding_border_sums.inline, placement_rect, + justify_self, ); let margin = LogicalSides { @@ -1558,6 +1557,7 @@ impl ReplacedContents { let effective_margin_inline_start; let (margin_block_start, margin_block_end) = solve_block_margins_for_in_flow_block_level(pbm); + let justify_self = resolve_justify_self(&base.style, containing_block.style); let containing_block_writing_mode = containing_block.style.writing_mode; let physical_content_size = content_size.to_physical_size(containing_block_writing_mode); @@ -1597,6 +1597,7 @@ impl ReplacedContents { pbm, size.inline, placement_rect, + justify_self, ); // Clearance prevents margin collapse between this block and previous ones, @@ -1620,6 +1621,7 @@ impl ReplacedContents { containing_block, pbm, content_size.inline, + justify_self, ); }; @@ -1671,6 +1673,7 @@ struct ContainingBlockPaddingAndBorder<'a> { block_sizes: Sizes, depends_on_block_constraints: bool, available_block_size: Option<Au>, + justify_self: AlignFlags, } struct ResolvedMargins { @@ -1719,6 +1722,9 @@ fn solve_containing_block_padding_and_border_for_in_flow_box<'a>( // The available block size may actually be definite, but it should be irrelevant // since the sizing properties are set to their initial value. available_block_size: None, + // The initial `justify-self` is `auto`, but use `normal` (behaving as `stretch`). + // This is being discussed in <https://github.com/w3c/csswg-drafts/issues/11461>. + justify_self: AlignFlags::NORMAL, }; } @@ -1755,16 +1761,11 @@ fn solve_containing_block_padding_and_border_for_in_flow_box<'a>( None, /* TODO: support preferred aspect ratios on non-replaced boxes */ )) }; - // TODO: the automatic inline size should take `justify-self` into account. + let justify_self = resolve_justify_self(style, containing_block.style); let is_table = layout_style.is_table(); - let automatic_inline_size = if is_table { - Size::FitContent - } else { - Size::Stretch - }; let inline_size = content_box_sizes.inline.resolve( Direction::Inline, - automatic_inline_size, + automatic_inline_size(justify_self, is_table), Au::zero, Some(available_inline_size), get_inline_content_sizes, @@ -1793,6 +1794,7 @@ fn solve_containing_block_padding_and_border_for_in_flow_box<'a>( block_sizes: content_box_sizes.block, depends_on_block_constraints, available_block_size, + justify_self, } } @@ -1804,9 +1806,15 @@ fn solve_margins( containing_block: &ContainingBlock<'_>, pbm: &PaddingBorderMargin, inline_size: Au, + justify_self: AlignFlags, ) -> ResolvedMargins { let (inline_margins, effective_margin_inline_start) = - solve_inline_margins_for_in_flow_block_level(containing_block, pbm, inline_size); + solve_inline_margins_for_in_flow_block_level( + containing_block, + pbm, + inline_size, + justify_self, + ); let block_margins = solve_block_margins_for_in_flow_block_level(pbm); ResolvedMargins { margin: LogicalSides { @@ -1829,14 +1837,63 @@ fn solve_block_margins_for_in_flow_block_level(pbm: &PaddingBorderMargin) -> (Au ) } -/// This is supposed to handle 'justify-self', but no browser supports it on block boxes. -/// Instead, `<center>` and `<div align>` are implemented via internal 'text-align' values. +/// Resolves the `justify-self` value, preserving flags. +fn resolve_justify_self(style: &ComputedValues, parent_style: &ComputedValues) -> AlignFlags { + let is_ltr = |style: &ComputedValues| style.writing_mode.line_left_is_inline_start(); + let alignment = match style.clone_justify_self().0.0 { + AlignFlags::AUTO => parent_style.clone_justify_items().computed.0, + alignment => alignment, + }; + let alignment_value = match alignment.value() { + AlignFlags::LEFT if is_ltr(parent_style) => AlignFlags::START, + AlignFlags::LEFT => AlignFlags::END, + AlignFlags::RIGHT if is_ltr(parent_style) => AlignFlags::END, + AlignFlags::RIGHT => AlignFlags::START, + AlignFlags::SELF_START if is_ltr(parent_style) == is_ltr(style) => AlignFlags::START, + AlignFlags::SELF_START => AlignFlags::END, + AlignFlags::SELF_END if is_ltr(parent_style) == is_ltr(style) => AlignFlags::END, + AlignFlags::SELF_END => AlignFlags::START, + alignment_value => alignment_value, + }; + alignment.flags() | alignment_value +} + +/// Determines the automatic size for the inline axis of a block-level box. +/// <https://drafts.csswg.org/css-sizing-3/#automatic-size> +#[inline] +fn automatic_inline_size<T>(justify_self: AlignFlags, is_table: bool) -> Size<T> { + match justify_self { + AlignFlags::STRETCH => Size::Stretch, + AlignFlags::NORMAL if !is_table => Size::Stretch, + _ => Size::FitContent, + } +} + +/// Justifies a block-level box, distributing the free space according to `justify-self`. +/// Note `<center>` and `<div align>` are implemented via internal 'text-align' values, +/// which are also handled here. /// The provided free space should already take margins into account. In particular, /// it should be zero if there is an auto margin. /// <https://drafts.csswg.org/css-align/#justify-block> -fn justify_self_alignment(containing_block: &ContainingBlock, free_space: Au) -> Au { +fn justify_self_alignment( + containing_block: &ContainingBlock, + free_space: Au, + justify_self: AlignFlags, +) -> Au { + let mut alignment = justify_self.value(); + let is_safe = justify_self.flags() == AlignFlags::SAFE || alignment == AlignFlags::NORMAL; + if is_safe && free_space <= Au::zero() { + alignment = AlignFlags::START + } + match alignment { + AlignFlags::NORMAL => {}, + AlignFlags::CENTER => return free_space / 2, + AlignFlags::END => return free_space, + _ => return Au::zero(), + } + + // For `justify-self: normal`, fall back to the special 'text-align' values. let style = containing_block.style; - debug_assert!(free_space >= Au::zero()); match style.clone_text_align() { TextAlignKeyword::MozCenter => free_space / 2, TextAlignKeyword::MozLeft if !style.writing_mode.line_left_is_inline_start() => free_space, @@ -1861,6 +1918,7 @@ fn solve_inline_margins_for_in_flow_block_level( containing_block: &ContainingBlock, pbm: &PaddingBorderMargin, inline_size: Au, + justify_self: AlignFlags, ) -> ((Au, Au), Au) { let free_space = containing_block.size.inline - pbm.padding_border_sums.inline - inline_size; let mut justification = Au::zero(); @@ -1878,8 +1936,8 @@ fn solve_inline_margins_for_in_flow_block_level( // But here we may still have some free space to perform 'justify-self' alignment. // This aligns the margin box within the containing block, or in other words, // aligns the border box within the margin-shrunken containing block. - let free_space = Au::zero().max(free_space - start - end); - justification = justify_self_alignment(containing_block, free_space); + justification = + justify_self_alignment(containing_block, free_space - start - end, justify_self); (start, end) }, }; @@ -1902,6 +1960,7 @@ fn solve_inline_margins_avoiding_floats( pbm: &PaddingBorderMargin, inline_size: Au, placement_rect: LogicalRect<Au>, + justify_self: AlignFlags, ) -> ((Au, Au), Au) { let free_space = placement_rect.size.inline - inline_size; debug_assert!(free_space >= Au::zero()); @@ -1922,7 +1981,7 @@ fn solve_inline_margins_avoiding_floats( // and Blink and WebKit are broken anyways. So we match Gecko instead: this aligns // the border box within the instersection of the float-shrunken containing-block // and the margin-shrunken containing-block. - justification = justify_self_alignment(containing_block, free_space); + justification = justify_self_alignment(containing_block, free_space, justify_self); (start, end) }, }; diff --git a/components/layout/flow/root.rs b/components/layout/flow/root.rs index 390b4664e60..187726595f8 100644 --- a/components/layout/flow/root.rs +++ b/components/layout/flow/root.rs @@ -195,16 +195,17 @@ impl BoxTree { }, _ => return None, }, - LayoutBox::InlineLevel(inline_level_box) => match &*inline_level_box.borrow() { - InlineItem::OutOfFlowAbsolutelyPositionedBox(_, text_offset_index) - if box_style.position.is_absolutely_positioned() => - { - UpdatePoint::AbsolutelyPositionedInlineLevelBox( - inline_level_box.clone(), - *text_offset_index, - ) - }, - _ => return None, + LayoutBox::InlineLevel(inline_level_items) => { + let inline_level_box = inline_level_items.first()?; + let InlineItem::OutOfFlowAbsolutelyPositionedBox(_, text_offset_index) = + &*inline_level_box.borrow() + else { + return None; + }; + UpdatePoint::AbsolutelyPositionedInlineLevelBox( + inline_level_box.clone(), + *text_offset_index, + ) }, LayoutBox::FlexLevel(flex_level_box) => match &*flex_level_box.borrow() { FlexLevelBox::OutOfFlowAbsolutelyPositionedBox(_) @@ -428,13 +429,14 @@ impl BoxTree { acc.union(&child_overflow) }); - FragmentTree { + FragmentTree::new( + layout_context, root_fragments, scrollable_overflow, - initial_containing_block: physical_containing_block, - canvas_background: self.canvas_background.clone(), - viewport_scroll_sensitivity: self.viewport_scroll_sensitivity, - } + physical_containing_block, + self.canvas_background.clone(), + self.viewport_scroll_sensitivity, + ) } } |