layout: Move text decoration propagation to stacking context tree construction (#37069)

Text decorations have a special kind of propagation. Instead of
propating these during box tree construction, move propagation to
stacking context tree construction. This will allow for using a very
easy type of incremental layout when text decorations change. For
instance, when a link changes color during hovering over it, we can skip
all of box and fragment tree construction.

In addition, propagation works a bit better now and color and style
properly move down from their originating `Fragment`s.

This introduces three new failures, because now we are drawing the
text-decoration with the correct color in more places, which exposes an
issue we have with text-decorations not being drawn in relation to the
baseline (taking into account `vertical-align`).

Testing: There are tests for these changes.
Fixes #31736.

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-21 18:38:29 +02:00 committed by GitHub
parent cebb1619ae
commit 9781f1241a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 152 additions and 142 deletions

View file

@ -103,7 +103,7 @@ use style::properties::style_structs::InheritedText;
use style::values::generics::box_::VerticalAlignKeyword;
use style::values::generics::font::LineHeight;
use style::values::specified::box_::BaselineSource;
use style::values::specified::text::{TextAlignKeyword, TextDecorationLine};
use style::values::specified::text::TextAlignKeyword;
use style::values::specified::{TextAlignLast, TextJustify};
use text_run::{
TextRun, XI_LINE_BREAKING_CLASS_GL, XI_LINE_BREAKING_CLASS_WJ, XI_LINE_BREAKING_CLASS_ZWJ,
@ -134,7 +134,7 @@ use crate::geom::{LogicalRect, LogicalVec2, ToLogical};
use crate::positioned::{AbsolutelyPositionedBox, PositioningContext};
use crate::sizing::{ComputeInlineContentSizes, ContentSizes, InlineContentSizesResult};
use crate::style_ext::{ComputedValuesExt, PaddingBorderMargin};
use crate::{ConstraintSpace, ContainingBlock, PropagatedBoxTreeData, SharedStyle};
use crate::{ConstraintSpace, ContainingBlock, SharedStyle};
// From gfxFontConstants.h in Firefox.
static FONT_SUBSCRIPT_OFFSET_RATIO: f32 = 0.20;
@ -163,8 +163,6 @@ pub(crate) struct InlineFormattingContext {
/// share styles with all [`TextRun`] children.
pub(super) shared_inline_styles: SharedInlineStyles,
pub(super) text_decoration_line: TextDecorationLine,
/// Whether this IFC contains the 1st formatted line of an element:
/// <https://www.w3.org/TR/css-pseudo-4/#first-formatted-line>.
pub(super) has_first_formatted_line: bool,
@ -628,12 +626,6 @@ pub(super) struct InlineContainerState {
/// this inline box on the current line OR any previous line.
has_content: RefCell<bool>,
/// Indicates whether this nesting level have text decorations in effect.
/// From <https://drafts.csswg.org/css-text-decor/#line-decoration>
// "When specified on or propagated to a block container that establishes
// an IFC..."
text_decoration_line: TextDecorationLine,
/// The block size contribution of this container's default font ie the size of the
/// "strut." Whether this is integrated into the [`Self::nested_strut_block_sizes`]
/// depends on the line-height quirk described in
@ -1461,7 +1453,6 @@ impl InlineFormattingContextLayout<'_> {
inline_styles: text_run.inline_styles.clone(),
font_metrics,
font_key: ifc_font_info.key,
text_decoration_line: self.current_inline_container_state().text_decoration_line,
bidi_level,
selection_range,
},
@ -1655,7 +1646,6 @@ impl InlineFormattingContext {
pub(super) fn new_with_builder(
builder: InlineFormattingContextBuilder,
layout_context: &LayoutContext,
propagated_data: PropagatedBoxTreeData,
has_first_formatted_line: bool,
is_single_line_text_input: bool,
starting_bidi_level: Level,
@ -1711,7 +1701,6 @@ impl InlineFormattingContext {
.last()
.expect("Should have at least one SharedInlineStyle for the root of an IFC")
.clone(),
text_decoration_line: propagated_data.text_decoration,
has_first_formatted_line,
contains_floats: builder.contains_floats,
is_single_line_text_input,
@ -1781,7 +1770,6 @@ impl InlineFormattingContext {
style.to_arc(),
inline_container_state_flags,
None, /* parent_container */
self.text_decoration_line,
default_font_metrics.as_ref(),
),
inline_box_state_stack: Vec::new(),
@ -1879,10 +1867,8 @@ impl InlineContainerState {
style: Arc<ComputedValues>,
flags: InlineContainerStateFlags,
parent_container: Option<&InlineContainerState>,
parent_text_decoration_line: TextDecorationLine,
font_metrics: Option<&FontMetrics>,
) -> Self {
let text_decoration_line = parent_text_decoration_line | style.clone_text_decoration_line();
let font_metrics = font_metrics.cloned().unwrap_or_else(FontMetrics::empty);
let line_height = line_height(
&style,
@ -1919,7 +1905,6 @@ impl InlineContainerState {
style,
flags,
has_content: RefCell::new(false),
text_decoration_line,
nested_strut_block_sizes: nested_block_sizes,
strut_block_sizes,
baseline_offset,