mirror of
https://github.com/servo/servo.git
synced 2025-08-07 14:35:33 +01:00
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:
parent
cebb1619ae
commit
9781f1241a
23 changed files with 152 additions and 142 deletions
|
@ -572,6 +572,7 @@ impl Fragment {
|
|||
section: StackingContextSection,
|
||||
is_hit_test_for_scrollable_overflow: bool,
|
||||
is_collapsed_table_borders: bool,
|
||||
text_decorations: &Arc<Vec<FragmentTextDecoration>>,
|
||||
) {
|
||||
let spatial_id = builder.spatial_id(builder.current_scroll_node_id);
|
||||
let clip_chain_id = builder.clip_chain_id(builder.current_clip_id);
|
||||
|
@ -683,9 +684,12 @@ impl Fragment {
|
|||
.get_inherited_box()
|
||||
.visibility
|
||||
{
|
||||
Visibility::Visible => {
|
||||
self.build_display_list_for_text_fragment(text, builder, containing_block)
|
||||
},
|
||||
Visibility::Visible => self.build_display_list_for_text_fragment(
|
||||
text,
|
||||
builder,
|
||||
containing_block,
|
||||
text_decorations,
|
||||
),
|
||||
Visibility::Hidden => (),
|
||||
Visibility::Collapse => (),
|
||||
}
|
||||
|
@ -723,6 +727,7 @@ impl Fragment {
|
|||
fragment: &TextFragment,
|
||||
builder: &mut DisplayListBuilder,
|
||||
containing_block: &PhysicalRect<Au>,
|
||||
text_decorations: &Arc<Vec<FragmentTextDecoration>>,
|
||||
) {
|
||||
// NB: The order of painting text components (CSS Text Decoration Module Level 3) is:
|
||||
// shadows, underline, overline, text, text-emphasis, and then line-through.
|
||||
|
@ -774,23 +779,33 @@ impl Fragment {
|
|||
);
|
||||
}
|
||||
|
||||
if fragment
|
||||
.text_decoration_line
|
||||
.contains(TextDecorationLine::UNDERLINE)
|
||||
{
|
||||
let mut rect = rect;
|
||||
rect.origin.y += font_metrics.ascent - font_metrics.underline_offset;
|
||||
rect.size.height = Au::from_f32_px(font_metrics.underline_size.to_nearest_pixel(dppx));
|
||||
self.build_display_list_for_text_decoration(&parent_style, builder, &rect, &color);
|
||||
for text_decoration in text_decorations.iter() {
|
||||
if text_decoration.line.contains(TextDecorationLine::UNDERLINE) {
|
||||
let mut rect = rect;
|
||||
rect.origin.y += font_metrics.ascent - font_metrics.underline_offset;
|
||||
rect.size.height =
|
||||
Au::from_f32_px(font_metrics.underline_size.to_nearest_pixel(dppx));
|
||||
self.build_display_list_for_text_decoration(
|
||||
&parent_style,
|
||||
builder,
|
||||
&rect,
|
||||
text_decoration,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if fragment
|
||||
.text_decoration_line
|
||||
.contains(TextDecorationLine::OVERLINE)
|
||||
{
|
||||
let mut rect = rect;
|
||||
rect.size.height = Au::from_f32_px(font_metrics.underline_size.to_nearest_pixel(dppx));
|
||||
self.build_display_list_for_text_decoration(&parent_style, builder, &rect, &color);
|
||||
for text_decoration in text_decorations.iter() {
|
||||
if text_decoration.line.contains(TextDecorationLine::OVERLINE) {
|
||||
let mut rect = rect;
|
||||
rect.size.height =
|
||||
Au::from_f32_px(font_metrics.underline_size.to_nearest_pixel(dppx));
|
||||
self.build_display_list_for_text_decoration(
|
||||
&parent_style,
|
||||
builder,
|
||||
&rect,
|
||||
text_decoration,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This caret/text selection implementation currently does not account for vertical text
|
||||
|
@ -867,14 +882,22 @@ impl Fragment {
|
|||
None,
|
||||
);
|
||||
|
||||
if fragment
|
||||
.text_decoration_line
|
||||
.contains(TextDecorationLine::LINE_THROUGH)
|
||||
{
|
||||
let mut rect = rect;
|
||||
rect.origin.y += font_metrics.ascent - font_metrics.strikeout_offset;
|
||||
rect.size.height = Au::from_f32_px(font_metrics.strikeout_size.to_nearest_pixel(dppx));
|
||||
self.build_display_list_for_text_decoration(&parent_style, builder, &rect, &color);
|
||||
for text_decoration in text_decorations.iter() {
|
||||
if text_decoration
|
||||
.line
|
||||
.contains(TextDecorationLine::LINE_THROUGH)
|
||||
{
|
||||
let mut rect = rect;
|
||||
rect.origin.y += font_metrics.ascent - font_metrics.strikeout_offset;
|
||||
rect.size.height =
|
||||
Au::from_f32_px(font_metrics.strikeout_size.to_nearest_pixel(dppx));
|
||||
self.build_display_list_for_text_decoration(
|
||||
&parent_style,
|
||||
builder,
|
||||
&rect,
|
||||
text_decoration,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if !shadows.0.is_empty() {
|
||||
|
@ -887,15 +910,11 @@ impl Fragment {
|
|||
parent_style: &ServoArc<ComputedValues>,
|
||||
builder: &mut DisplayListBuilder,
|
||||
rect: &PhysicalRect<Au>,
|
||||
color: &AbsoluteColor,
|
||||
text_decoration: &FragmentTextDecoration,
|
||||
) {
|
||||
let rect = rect.to_webrender();
|
||||
let wavy_line_thickness = (0.33 * rect.size().height).ceil();
|
||||
let text_decoration_color = parent_style
|
||||
.clone_text_decoration_color()
|
||||
.resolve_to_absolute(color);
|
||||
let text_decoration_style = parent_style.clone_text_decoration_style();
|
||||
if text_decoration_style == ComputedTextDecorationStyle::MozNone {
|
||||
if text_decoration.style == ComputedTextDecorationStyle::MozNone {
|
||||
return;
|
||||
}
|
||||
let common_properties = builder.common_properties(rect, parent_style);
|
||||
|
@ -904,8 +923,8 @@ impl Fragment {
|
|||
&rect,
|
||||
wavy_line_thickness,
|
||||
wr::LineOrientation::Horizontal,
|
||||
&rgba(text_decoration_color),
|
||||
text_decoration_style.to_webrender(),
|
||||
&rgba(text_decoration.color),
|
||||
text_decoration.style.to_webrender(),
|
||||
);
|
||||
// XXX(ferjm) support text-decoration-style: double
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
use core::f32;
|
||||
use std::cell::RefCell;
|
||||
use std::mem;
|
||||
use std::sync::Arc;
|
||||
|
||||
use app_units::Au;
|
||||
use base::id::ScrollTreeNodeId;
|
||||
|
@ -18,13 +19,15 @@ use euclid::default::{Point2D, Rect, Size2D};
|
|||
use log::warn;
|
||||
use servo_config::opts::DebugOptions;
|
||||
use style::Zero;
|
||||
use style::color::AbsoluteColor;
|
||||
use style::computed_values::float::T as ComputedFloat;
|
||||
use style::computed_values::mix_blend_mode::T as ComputedMixBlendMode;
|
||||
use style::computed_values::overflow_x::T as ComputedOverflow;
|
||||
use style::computed_values::position::T as ComputedPosition;
|
||||
use style::computed_values::text_decoration_style::T as TextDecorationStyle;
|
||||
use style::values::computed::angle::Angle;
|
||||
use style::values::computed::basic_shape::ClipPath;
|
||||
use style::values::computed::{ClipRectOrAuto, Length};
|
||||
use style::values::computed::{ClipRectOrAuto, Length, TextDecorationLine};
|
||||
use style::values::generics::box_::Perspective;
|
||||
use style::values::generics::transform::{self, GenericRotate, GenericScale, GenericTranslate};
|
||||
use style::values::specified::box_::DisplayOutside;
|
||||
|
@ -168,12 +171,14 @@ impl StackingContextTree {
|
|||
};
|
||||
|
||||
let mut root_stacking_context = StackingContext::create_root(root_scroll_node_id, debug);
|
||||
let text_decorations = Default::default();
|
||||
for fragment in &fragment_tree.root_fragments {
|
||||
fragment.build_stacking_context_tree(
|
||||
&mut stacking_context_tree,
|
||||
&containing_block_info,
|
||||
&mut root_stacking_context,
|
||||
StackingContextBuildMode::SkipHoisted,
|
||||
&text_decorations,
|
||||
);
|
||||
}
|
||||
root_stacking_context.sort();
|
||||
|
@ -246,6 +251,14 @@ impl StackingContextTree {
|
|||
}
|
||||
}
|
||||
|
||||
/// The text decorations for a Fragment, collecting during [`StackingContextTree`] construction.
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct FragmentTextDecoration {
|
||||
pub line: TextDecorationLine,
|
||||
pub color: AbsoluteColor,
|
||||
pub style: TextDecorationStyle,
|
||||
}
|
||||
|
||||
/// A piece of content that directly belongs to a section of a stacking context.
|
||||
///
|
||||
/// This is generally part of a fragment, like its borders or foreground, but it
|
||||
|
@ -261,6 +274,7 @@ pub(crate) enum StackingContextContent {
|
|||
fragment: Fragment,
|
||||
is_hit_test_for_scrollable_overflow: bool,
|
||||
is_collapsed_table_borders: bool,
|
||||
text_decorations: Arc<Vec<FragmentTextDecoration>>,
|
||||
},
|
||||
|
||||
/// An index into [StackingContext::atomic_inline_stacking_containers].
|
||||
|
@ -292,6 +306,7 @@ impl StackingContextContent {
|
|||
fragment,
|
||||
is_hit_test_for_scrollable_overflow,
|
||||
is_collapsed_table_borders,
|
||||
text_decorations,
|
||||
} => {
|
||||
builder.current_scroll_node_id = *scroll_node_id;
|
||||
builder.current_reference_frame_scroll_node_id = *reference_frame_scroll_node_id;
|
||||
|
@ -302,6 +317,7 @@ impl StackingContextContent {
|
|||
*section,
|
||||
*is_hit_test_for_scrollable_overflow,
|
||||
*is_collapsed_table_borders,
|
||||
text_decorations,
|
||||
);
|
||||
},
|
||||
Self::AtomicInlineStackingContainer { index } => {
|
||||
|
@ -800,6 +816,7 @@ impl Fragment {
|
|||
containing_block_info: &ContainingBlockInfo,
|
||||
stacking_context: &mut StackingContext,
|
||||
mode: StackingContextBuildMode,
|
||||
text_decorations: &Arc<Vec<FragmentTextDecoration>>,
|
||||
) {
|
||||
let containing_block = containing_block_info.get_containing_block_for_fragment(self);
|
||||
let fragment_clone = self.clone();
|
||||
|
@ -812,6 +829,11 @@ impl Fragment {
|
|||
return;
|
||||
}
|
||||
|
||||
let text_decorations = match self {
|
||||
Fragment::Float(..) => &Default::default(),
|
||||
_ => text_decorations,
|
||||
};
|
||||
|
||||
// If this fragment has a transform applied that makes it take up no space
|
||||
// then we don't need to create any stacking contexts for it.
|
||||
let has_non_invertible_transform = fragment
|
||||
|
@ -828,6 +850,7 @@ impl Fragment {
|
|||
containing_block,
|
||||
containing_block_info,
|
||||
stacking_context,
|
||||
text_decorations,
|
||||
);
|
||||
},
|
||||
Fragment::AbsoluteOrFixedPositioned(fragment) => {
|
||||
|
@ -842,6 +865,7 @@ impl Fragment {
|
|||
containing_block_info,
|
||||
stacking_context,
|
||||
StackingContextBuildMode::IncludeHoisted,
|
||||
&Default::default(),
|
||||
);
|
||||
},
|
||||
Fragment::Positioning(fragment) => {
|
||||
|
@ -851,6 +875,7 @@ impl Fragment {
|
|||
containing_block,
|
||||
containing_block_info,
|
||||
stacking_context,
|
||||
text_decorations,
|
||||
);
|
||||
},
|
||||
Fragment::Text(_) | Fragment::Image(_) | Fragment::IFrame(_) => {
|
||||
|
@ -867,6 +892,7 @@ impl Fragment {
|
|||
fragment: fragment_clone,
|
||||
is_hit_test_for_scrollable_overflow: false,
|
||||
is_collapsed_table_borders: false,
|
||||
text_decorations: text_decorations.clone(),
|
||||
});
|
||||
},
|
||||
}
|
||||
|
@ -929,6 +955,7 @@ impl BoxFragment {
|
|||
containing_block: &ContainingBlock,
|
||||
containing_block_info: &ContainingBlockInfo,
|
||||
parent_stacking_context: &mut StackingContext,
|
||||
text_decorations: &Arc<Vec<FragmentTextDecoration>>,
|
||||
) {
|
||||
self.build_stacking_context_tree_maybe_creating_reference_frame(
|
||||
fragment,
|
||||
|
@ -936,6 +963,7 @@ impl BoxFragment {
|
|||
containing_block,
|
||||
containing_block_info,
|
||||
parent_stacking_context,
|
||||
text_decorations,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -946,6 +974,7 @@ impl BoxFragment {
|
|||
containing_block: &ContainingBlock,
|
||||
containing_block_info: &ContainingBlockInfo,
|
||||
parent_stacking_context: &mut StackingContext,
|
||||
text_decorations: &Arc<Vec<FragmentTextDecoration>>,
|
||||
) {
|
||||
let reference_frame_data =
|
||||
match self.reference_frame_data_if_necessary(&containing_block.rect) {
|
||||
|
@ -957,6 +986,7 @@ impl BoxFragment {
|
|||
containing_block,
|
||||
containing_block_info,
|
||||
parent_stacking_context,
|
||||
text_decorations,
|
||||
);
|
||||
},
|
||||
};
|
||||
|
@ -999,6 +1029,7 @@ impl BoxFragment {
|
|||
&adjusted_containing_block,
|
||||
&new_containing_block_info,
|
||||
parent_stacking_context,
|
||||
text_decorations,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1009,6 +1040,7 @@ impl BoxFragment {
|
|||
containing_block: &ContainingBlock,
|
||||
containing_block_info: &ContainingBlockInfo,
|
||||
parent_stacking_context: &mut StackingContext,
|
||||
text_decorations: &Arc<Vec<FragmentTextDecoration>>,
|
||||
) {
|
||||
let context_type = match self.get_stacking_context_type() {
|
||||
Some(context_type) => context_type,
|
||||
|
@ -1019,6 +1051,7 @@ impl BoxFragment {
|
|||
containing_block,
|
||||
containing_block_info,
|
||||
parent_stacking_context,
|
||||
text_decorations,
|
||||
);
|
||||
return;
|
||||
},
|
||||
|
@ -1072,6 +1105,7 @@ impl BoxFragment {
|
|||
containing_block,
|
||||
containing_block_info,
|
||||
&mut child_stacking_context,
|
||||
text_decorations,
|
||||
);
|
||||
|
||||
let mut stolen_children = vec![];
|
||||
|
@ -1097,6 +1131,7 @@ impl BoxFragment {
|
|||
containing_block: &ContainingBlock,
|
||||
containing_block_info: &ContainingBlockInfo,
|
||||
stacking_context: &mut StackingContext,
|
||||
text_decorations: &Arc<Vec<FragmentTextDecoration>>,
|
||||
) {
|
||||
let mut new_scroll_node_id = containing_block.scroll_node_id;
|
||||
let mut new_clip_id = containing_block.clip_id;
|
||||
|
@ -1164,6 +1199,7 @@ impl BoxFragment {
|
|||
fragment: fragment.clone(),
|
||||
is_hit_test_for_scrollable_overflow: false,
|
||||
is_collapsed_table_borders: false,
|
||||
text_decorations: text_decorations.clone(),
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -1198,6 +1234,7 @@ impl BoxFragment {
|
|||
fragment: fragment.clone(),
|
||||
is_hit_test_for_scrollable_overflow: true,
|
||||
is_collapsed_table_borders: false,
|
||||
text_decorations: text_decorations.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1239,12 +1276,46 @@ impl BoxFragment {
|
|||
containing_block_info.new_for_non_absolute_descendants(&for_non_absolute_descendants)
|
||||
};
|
||||
|
||||
// Text decorations are not propagated to atomic inline-level descendants.
|
||||
// From https://drafts.csswg.org/css2/#lining-striking-props:
|
||||
// > Note that text decorations are not propagated to floating and absolutely
|
||||
// > positioned descendants, nor to the contents of atomic inline-level descendants
|
||||
// > such as inline blocks and inline tables.
|
||||
let text_decorations = match self.is_atomic_inline_level() ||
|
||||
self.base
|
||||
.flags
|
||||
.contains(FragmentFlags::IS_OUTSIDE_LIST_ITEM_MARKER)
|
||||
{
|
||||
true => &Default::default(),
|
||||
false => text_decorations,
|
||||
};
|
||||
|
||||
let new_text_decoration;
|
||||
let text_decorations = match self.style.clone_text_decoration_line() {
|
||||
TextDecorationLine::NONE => text_decorations,
|
||||
line => {
|
||||
let mut new_vector = (**text_decorations).clone();
|
||||
let color = &self.style.get_inherited_text().color;
|
||||
new_vector.push(FragmentTextDecoration {
|
||||
line,
|
||||
color: self
|
||||
.style
|
||||
.clone_text_decoration_color()
|
||||
.resolve_to_absolute(color),
|
||||
style: self.style.clone_text_decoration_style(),
|
||||
});
|
||||
new_text_decoration = Arc::new(new_vector);
|
||||
&new_text_decoration
|
||||
},
|
||||
};
|
||||
|
||||
for child in &self.children {
|
||||
child.build_stacking_context_tree(
|
||||
stacking_context_tree,
|
||||
&new_containing_block_info,
|
||||
stacking_context,
|
||||
StackingContextBuildMode::SkipHoisted,
|
||||
text_decorations,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1263,6 +1334,7 @@ impl BoxFragment {
|
|||
fragment: fragment.clone(),
|
||||
is_hit_test_for_scrollable_overflow: false,
|
||||
is_collapsed_table_borders: true,
|
||||
text_decorations: text_decorations.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1646,6 +1718,7 @@ impl PositioningFragment {
|
|||
containing_block: &ContainingBlock,
|
||||
containing_block_info: &ContainingBlockInfo,
|
||||
stacking_context: &mut StackingContext,
|
||||
text_decorations: &Arc<Vec<FragmentTextDecoration>>,
|
||||
) {
|
||||
let rect = self
|
||||
.rect
|
||||
|
@ -1660,6 +1733,7 @@ impl PositioningFragment {
|
|||
&new_containing_block_info,
|
||||
stacking_context,
|
||||
StackingContextBuildMode::SkipHoisted,
|
||||
text_decorations,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue