layout: Share styles to inline box children via SharedInlineStyles (#36896)

`TextRun`s use their parent style to render. Previously, these styles
were cloned and stored directly in the box tree `TextRun` and resulting
`TextFragment`s. This presents a problem for incremental layout.
Wrapping the style in another layer of shared ownership and mutability
will allow updating all `TextFragment`s during repaint-only incremental
layout by simply updating the box tree styles of the original text
parents.

This adds a new set of borrows when accessing text styles, but also
makes it so that during box tree block construction
`InlineFormattingContext`s are created lazily and now
`InlineFormattingContextBuilder::finish` consumes the builder, making
the API make a bit more sense. This should also improve performance of
box tree block construction slightly.

Testing: This should not change observable behavior and thus is covered
by existing WPT tests.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
Co-authored-by: Oriol Brufau <obrufau@igalia.com>
This commit is contained in:
Martin Robinson 2025-05-12 11:38:50 +02:00 committed by GitHub
parent db83601b62
commit a0dd2c1beb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 369 additions and 187 deletions

View file

@ -26,7 +26,7 @@ use unicode_script::Script;
use xi_unicode::linebreak_property;
use super::line_breaker::LineBreaker;
use super::{FontKeyAndMetrics, InlineFormattingContextLayout};
use super::{FontKeyAndMetrics, InlineFormattingContextLayout, SharedInlineStyles};
use crate::fragment_tree::BaseFragmentInfo;
// These constants are the xi-unicode line breaking classes that are defined in
@ -37,22 +37,6 @@ pub(crate) const XI_LINE_BREAKING_CLASS_ZW: u8 = 28;
pub(crate) const XI_LINE_BREAKING_CLASS_WJ: u8 = 30;
pub(crate) const XI_LINE_BREAKING_CLASS_ZWJ: u8 = 42;
/// <https://www.w3.org/TR/css-display-3/#css-text-run>
#[derive(Debug, MallocSizeOf)]
pub(crate) struct TextRun {
pub base_fragment_info: BaseFragmentInfo,
#[conditional_malloc_size_of]
pub parent_style: Arc<ComputedValues>,
pub text_range: Range<usize>,
/// The text of this [`TextRun`] with a font selected, broken into unbreakable
/// segments, and shaped.
pub shaped_text: Vec<TextRunSegment>,
pub selection_range: Option<ServoRange<ByteIndex>>,
#[conditional_malloc_size_of]
pub selected_style: Arc<ComputedValues>,
}
// There are two reasons why we might want to break at the start:
//
// 1. The line breaker told us that a break was necessary between two separate
@ -334,21 +318,49 @@ impl TextRunSegment {
}
}
/// A single [`TextRun`] for the box tree. These are all descendants of
/// [`super::InlineBox`] or the root of the [`super::InlineFormattingContext`]. During
/// box tree construction, text is split into [`TextRun`]s based on their font, script,
/// etc. When these are created text is already shaped.
///
/// <https://www.w3.org/TR/css-display-3/#css-text-run>
#[derive(Debug, MallocSizeOf)]
pub(crate) struct TextRun {
/// The [`BaseFragmentInfo`] for this [`TextRun`]. Usually this comes from the
/// original text node in the DOM for the text.
pub base_fragment_info: BaseFragmentInfo,
/// The [`crate::SharedStyle`] from this [`TextRun`]s parent element. This is
/// shared so that incremental layout can simply update the parent element and
/// this [`TextRun`] will be updated automatically.
pub inline_styles: SharedInlineStyles,
/// The range of text in [`super::InlineFormattingContext::text_content`] of the
/// [`super::InlineFormattingContext`] that owns this [`TextRun`]. These are UTF-8 offsets.
pub text_range: Range<usize>,
/// The text of this [`TextRun`] with a font selected, broken into unbreakable
/// segments, and shaped.
pub shaped_text: Vec<TextRunSegment>,
/// The selection range for the DOM text node that originated this [`TextRun`]. This
/// comes directly from the DOM.
pub selection_range: Option<ServoRange<ByteIndex>>,
}
impl TextRun {
pub(crate) fn new(
base_fragment_info: BaseFragmentInfo,
parent_style: Arc<ComputedValues>,
inline_styles: SharedInlineStyles,
text_range: Range<usize>,
selection_range: Option<ServoRange<ByteIndex>>,
selected_style: Arc<ComputedValues>,
) -> Self {
Self {
base_fragment_info,
parent_style,
inline_styles,
text_range,
shaped_text: Vec::new(),
selection_range,
selected_style,
}
}
@ -360,11 +372,12 @@ impl TextRun {
font_cache: &mut Vec<FontKeyAndMetrics>,
bidi_info: &BidiInfo,
) {
let inherited_text_style = self.parent_style.get_inherited_text().clone();
let parent_style = self.inline_styles.style.borrow().clone();
let inherited_text_style = parent_style.get_inherited_text().clone();
let letter_spacing = inherited_text_style
.letter_spacing
.0
.resolve(self.parent_style.clone_font().font_size.computed_size());
.resolve(parent_style.clone_font().font_size.computed_size());
let letter_spacing = if letter_spacing.px() != 0. {
Some(app_units::Au::from(letter_spacing))
} else {
@ -384,7 +397,13 @@ impl TextRun {
let style_word_spacing: Option<Au> = specified_word_spacing.to_length().map(|l| l.into());
let segments = self
.segment_text_by_font(formatting_context_text, font_context, font_cache, bidi_info)
.segment_text_by_font(
formatting_context_text,
font_context,
font_cache,
bidi_info,
&parent_style,
)
.into_iter()
.map(|(mut segment, font)| {
let word_spacing = style_word_spacing.unwrap_or_else(|| {
@ -407,7 +426,7 @@ impl TextRun {
};
segment.shape_text(
&self.parent_style,
&parent_style,
formatting_context_text,
linebreaker,
&shaping_options,
@ -430,8 +449,9 @@ impl TextRun {
font_context: &FontContext,
font_cache: &mut Vec<FontKeyAndMetrics>,
bidi_info: &BidiInfo,
parent_style: &Arc<ComputedValues>,
) -> Vec<(TextRunSegment, FontRef)> {
let font_group = font_context.font_group(self.parent_style.clone_font());
let font_group = font_context.font_group(parent_style.clone_font());
let mut current: Option<(TextRunSegment, FontRef)> = None;
let mut results = Vec::new();