mirror of
https://github.com/servo/servo.git
synced 2025-08-03 12:40:06 +01:00
layout: Implement node geometry queries against BoxTree
's Fragment
(#36663)
This is a followup to #36629, continuing to implement script-based layout queries using the `Fragment`s attached to the `BoxTree`. In this change, geometry queris (apart from parent offset) are calculated using `Fragment`s hanging of the `BoxTree`. In order to make this work, all `Fragment`s for inlines split by blocks, need to be accessible in the `BoxTree`. This required some changes to the way that box tree items were stored in DOM `BoxSlot`s. Now every inline level item can have more than a single `BoxTree` item. These are carefully collected by the `InlineFormattingContextBuilder` -- currently a bit fragile, but with more documentation. Testing: There are tests for these changes. Signed-off-by: Martin Robinson <mrobinson@igalia.com> Co-authored-by: Oriol Brufau <obrufau@igalia.com>
This commit is contained in:
parent
cc91395397
commit
b63a1818c4
13 changed files with 197 additions and 170 deletions
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
self.push_control_character_string(
|
||||
inline_level_box.borrow().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);
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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(_)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue