mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
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>
408 lines
15 KiB
Rust
408 lines
15 KiB
Rust
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
|
|
use std::sync::Arc;
|
|
|
|
use app_units::Au;
|
|
use base::id::PipelineId;
|
|
use base::print_tree::PrintTree;
|
|
use euclid::{Point2D, Rect, Size2D, UnknownUnit};
|
|
use fonts::{ByteIndex, FontMetrics, GlyphStore};
|
|
use malloc_size_of_derive::MallocSizeOf;
|
|
use range::Range as ServoRange;
|
|
use servo_arc::Arc as ServoArc;
|
|
use style::Zero;
|
|
use style::properties::ComputedValues;
|
|
use webrender_api::{FontInstanceKey, ImageKey};
|
|
|
|
use super::{
|
|
BaseFragment, BoxFragment, ContainingBlockManager, HoistedSharedFragment, PositioningFragment,
|
|
Tag,
|
|
};
|
|
use crate::cell::ArcRefCell;
|
|
use crate::flow::inline::SharedInlineStyles;
|
|
use crate::geom::{LogicalSides, PhysicalPoint, PhysicalRect};
|
|
use crate::style_ext::ComputedValuesExt;
|
|
|
|
#[derive(Clone, MallocSizeOf)]
|
|
pub(crate) enum Fragment {
|
|
Box(ArcRefCell<BoxFragment>),
|
|
/// Floating content. A floated fragment is very similar to a normal
|
|
/// [BoxFragment] but it isn't positioned using normal in block flow
|
|
/// positioning rules (margin collapse, etc). Instead, they are laid
|
|
/// out by the [crate::flow::float::SequentialLayoutState] of their
|
|
/// float containing block formatting context.
|
|
Float(ArcRefCell<BoxFragment>),
|
|
Positioning(ArcRefCell<PositioningFragment>),
|
|
/// Absolute and fixed position fragments are hoisted up so that they
|
|
/// are children of the BoxFragment that establishes their containing
|
|
/// blocks, so that they can be laid out properly. When this happens
|
|
/// an `AbsoluteOrFixedPositioned` fragment is left at the original tree
|
|
/// position. This allows these hoisted fragments to be painted with
|
|
/// regard to their original tree order during stacking context tree /
|
|
/// display list construction.
|
|
AbsoluteOrFixedPositioned(ArcRefCell<HoistedSharedFragment>),
|
|
Text(ArcRefCell<TextFragment>),
|
|
Image(ArcRefCell<ImageFragment>),
|
|
IFrame(ArcRefCell<IFrameFragment>),
|
|
}
|
|
|
|
#[derive(Clone, MallocSizeOf)]
|
|
pub(crate) struct CollapsedBlockMargins {
|
|
pub collapsed_through: bool,
|
|
pub start: CollapsedMargin,
|
|
pub end: CollapsedMargin,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, MallocSizeOf)]
|
|
pub(crate) struct CollapsedMargin {
|
|
max_positive: Au,
|
|
min_negative: Au,
|
|
}
|
|
|
|
#[derive(MallocSizeOf)]
|
|
pub(crate) struct TextFragment {
|
|
pub base: BaseFragment,
|
|
pub inline_styles: SharedInlineStyles,
|
|
pub rect: PhysicalRect<Au>,
|
|
pub font_metrics: FontMetrics,
|
|
pub font_key: FontInstanceKey,
|
|
#[conditional_malloc_size_of]
|
|
pub glyphs: Vec<Arc<GlyphStore>>,
|
|
|
|
/// Extra space to add for each justification opportunity.
|
|
pub justification_adjustment: Au,
|
|
pub selection_range: Option<ServoRange<ByteIndex>>,
|
|
}
|
|
|
|
#[derive(MallocSizeOf)]
|
|
pub(crate) struct ImageFragment {
|
|
pub base: BaseFragment,
|
|
pub style: ServoArc<ComputedValues>,
|
|
pub rect: PhysicalRect<Au>,
|
|
pub clip: PhysicalRect<Au>,
|
|
pub image_key: Option<ImageKey>,
|
|
}
|
|
|
|
#[derive(MallocSizeOf)]
|
|
pub(crate) struct IFrameFragment {
|
|
pub base: BaseFragment,
|
|
pub pipeline_id: PipelineId,
|
|
pub rect: PhysicalRect<Au>,
|
|
pub style: ServoArc<ComputedValues>,
|
|
}
|
|
|
|
impl Fragment {
|
|
pub fn base(&self) -> Option<BaseFragment> {
|
|
Some(match self {
|
|
Fragment::Box(fragment) => fragment.borrow().base.clone(),
|
|
Fragment::Text(fragment) => fragment.borrow().base.clone(),
|
|
Fragment::AbsoluteOrFixedPositioned(_) => return None,
|
|
Fragment::Positioning(fragment) => fragment.borrow().base.clone(),
|
|
Fragment::Image(fragment) => fragment.borrow().base.clone(),
|
|
Fragment::IFrame(fragment) => fragment.borrow().base.clone(),
|
|
Fragment::Float(fragment) => fragment.borrow().base.clone(),
|
|
})
|
|
}
|
|
|
|
pub(crate) fn mutate_content_rect(&mut self, callback: impl FnOnce(&mut PhysicalRect<Au>)) {
|
|
match self {
|
|
Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => {
|
|
callback(&mut box_fragment.borrow_mut().content_rect)
|
|
},
|
|
Fragment::Positioning(_) | Fragment::AbsoluteOrFixedPositioned(_) => {},
|
|
Fragment::Text(text_fragment) => callback(&mut text_fragment.borrow_mut().rect),
|
|
Fragment::Image(image_fragment) => callback(&mut image_fragment.borrow_mut().rect),
|
|
Fragment::IFrame(iframe_fragment) => callback(&mut iframe_fragment.borrow_mut().rect),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn set_containing_block(&self, containing_block: &PhysicalRect<Au>) {
|
|
match self {
|
|
Fragment::Box(box_fragment) => box_fragment
|
|
.borrow_mut()
|
|
.set_containing_block(containing_block),
|
|
Fragment::Float(float_fragment) => float_fragment
|
|
.borrow_mut()
|
|
.set_containing_block(containing_block),
|
|
Fragment::Positioning(positioning_fragment) => positioning_fragment
|
|
.borrow_mut()
|
|
.set_containing_block(containing_block),
|
|
Fragment::AbsoluteOrFixedPositioned(hoisted_shared_fragment) => {
|
|
if let Some(ref fragment) = hoisted_shared_fragment.borrow().fragment {
|
|
fragment.set_containing_block(containing_block);
|
|
}
|
|
},
|
|
Fragment::Text(_) => {},
|
|
Fragment::Image(_) => {},
|
|
Fragment::IFrame(_) => {},
|
|
}
|
|
}
|
|
|
|
pub fn tag(&self) -> Option<Tag> {
|
|
self.base().and_then(|base| base.tag)
|
|
}
|
|
|
|
pub fn print(&self, tree: &mut PrintTree) {
|
|
match self {
|
|
Fragment::Box(fragment) => fragment.borrow().print(tree),
|
|
Fragment::Float(fragment) => {
|
|
tree.new_level("Float".to_string());
|
|
fragment.borrow().print(tree);
|
|
tree.end_level();
|
|
},
|
|
Fragment::AbsoluteOrFixedPositioned(_) => {
|
|
tree.add_item("AbsoluteOrFixedPositioned".to_string());
|
|
},
|
|
Fragment::Positioning(fragment) => fragment.borrow().print(tree),
|
|
Fragment::Text(fragment) => fragment.borrow().print(tree),
|
|
Fragment::Image(fragment) => fragment.borrow().print(tree),
|
|
Fragment::IFrame(fragment) => fragment.borrow().print(tree),
|
|
}
|
|
}
|
|
|
|
pub fn unclipped_scrolling_area(&self) -> PhysicalRect<Au> {
|
|
match self {
|
|
Fragment::Box(fragment) | Fragment::Float(fragment) => {
|
|
let fragment = fragment.borrow();
|
|
fragment.offset_by_containing_block(&fragment.scrollable_overflow())
|
|
},
|
|
_ => self.scrollable_overflow_for_parent(),
|
|
}
|
|
}
|
|
|
|
pub fn scrolling_area(&self) -> PhysicalRect<Au> {
|
|
match self {
|
|
Fragment::Box(fragment) | Fragment::Float(fragment) => {
|
|
let fragment = fragment.borrow();
|
|
fragment
|
|
.offset_by_containing_block(&fragment.reachable_scrollable_overflow_region())
|
|
},
|
|
_ => self.scrollable_overflow_for_parent(),
|
|
}
|
|
}
|
|
|
|
pub fn scrollable_overflow_for_parent(&self) -> PhysicalRect<Au> {
|
|
match self {
|
|
Fragment::Box(fragment) | Fragment::Float(fragment) => {
|
|
fragment.borrow().scrollable_overflow_for_parent()
|
|
},
|
|
Fragment::AbsoluteOrFixedPositioned(_) => PhysicalRect::zero(),
|
|
Fragment::Positioning(fragment) => fragment.borrow().scrollable_overflow,
|
|
Fragment::Text(fragment) => fragment.borrow().rect,
|
|
Fragment::Image(fragment) => fragment.borrow().rect,
|
|
Fragment::IFrame(fragment) => fragment.borrow().rect,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn cumulative_border_box_rect(&self) -> Option<PhysicalRect<Au>> {
|
|
match self {
|
|
Fragment::Box(fragment) | Fragment::Float(fragment) => {
|
|
let fragment = fragment.borrow();
|
|
Some(fragment.offset_by_containing_block(&fragment.border_rect()))
|
|
},
|
|
Fragment::Positioning(fragment) => {
|
|
let fragment = fragment.borrow();
|
|
Some(fragment.offset_by_containing_block(&fragment.rect))
|
|
},
|
|
Fragment::Text(_) |
|
|
Fragment::AbsoluteOrFixedPositioned(_) |
|
|
Fragment::Image(_) |
|
|
Fragment::IFrame(_) => None,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn client_rect(&self) -> Rect<i32, UnknownUnit> {
|
|
let rect = match self {
|
|
Fragment::Box(fragment) | Fragment::Float(fragment) => {
|
|
// https://drafts.csswg.org/cssom-view/#dom-element-clienttop
|
|
// " If the element has no associated CSS layout box or if the
|
|
// CSS layout box is inline, return zero." For this check we
|
|
// also explicitly ignore the list item portion of the display
|
|
// style.
|
|
let fragment = fragment.borrow();
|
|
if fragment.is_inline_box() {
|
|
return Rect::zero();
|
|
}
|
|
|
|
if fragment.is_table_wrapper() {
|
|
// For tables the border actually belongs to the table grid box,
|
|
// so we need to include it in the dimension of the table wrapper box.
|
|
let mut rect = fragment.border_rect();
|
|
rect.origin = PhysicalPoint::zero();
|
|
rect
|
|
} else {
|
|
let mut rect = fragment.padding_rect();
|
|
rect.origin = PhysicalPoint::new(fragment.border.left, fragment.border.top);
|
|
rect
|
|
}
|
|
},
|
|
_ => return Rect::zero(),
|
|
}
|
|
.to_untyped();
|
|
|
|
let rect = Rect::new(
|
|
Point2D::new(rect.origin.x.to_f32_px(), rect.origin.y.to_f32_px()),
|
|
Size2D::new(rect.size.width.to_f32_px(), rect.size.height.to_f32_px()),
|
|
);
|
|
rect.round().to_i32()
|
|
}
|
|
|
|
pub(crate) fn find<T>(
|
|
&self,
|
|
manager: &ContainingBlockManager<PhysicalRect<Au>>,
|
|
level: usize,
|
|
process_func: &mut impl FnMut(&Fragment, usize, &PhysicalRect<Au>) -> Option<T>,
|
|
) -> Option<T> {
|
|
let containing_block = manager.get_containing_block_for_fragment(self);
|
|
if let Some(result) = process_func(self, level, containing_block) {
|
|
return Some(result);
|
|
}
|
|
|
|
match self {
|
|
Fragment::Box(fragment) | Fragment::Float(fragment) => {
|
|
let fragment = fragment.borrow();
|
|
let content_rect = fragment
|
|
.content_rect
|
|
.translate(containing_block.origin.to_vector());
|
|
let padding_rect = fragment
|
|
.padding_rect()
|
|
.translate(containing_block.origin.to_vector());
|
|
let new_manager = if fragment
|
|
.style
|
|
.establishes_containing_block_for_all_descendants(fragment.base.flags)
|
|
{
|
|
manager.new_for_absolute_and_fixed_descendants(&content_rect, &padding_rect)
|
|
} else if fragment
|
|
.style
|
|
.establishes_containing_block_for_absolute_descendants(fragment.base.flags)
|
|
{
|
|
manager.new_for_absolute_descendants(&content_rect, &padding_rect)
|
|
} else {
|
|
manager.new_for_non_absolute_descendants(&content_rect)
|
|
};
|
|
|
|
fragment
|
|
.children
|
|
.iter()
|
|
.find_map(|child| child.find(&new_manager, level + 1, process_func))
|
|
},
|
|
Fragment::Positioning(fragment) => {
|
|
let fragment = fragment.borrow();
|
|
let content_rect = fragment.rect.translate(containing_block.origin.to_vector());
|
|
let new_manager = manager.new_for_non_absolute_descendants(&content_rect);
|
|
fragment
|
|
.children
|
|
.iter()
|
|
.find_map(|child| child.find(&new_manager, level + 1, process_func))
|
|
},
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn repair_style(&self, style: &ServoArc<ComputedValues>) {
|
|
match self {
|
|
Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => {
|
|
box_fragment.borrow_mut().style = style.clone()
|
|
},
|
|
Fragment::Positioning(positioning_fragment) => {
|
|
positioning_fragment.borrow_mut().style = style.clone();
|
|
},
|
|
Fragment::AbsoluteOrFixedPositioned(positioned_fragment) => {
|
|
if let Some(ref fragment) = positioned_fragment.borrow().fragment {
|
|
fragment.repair_style(style);
|
|
}
|
|
},
|
|
Fragment::Text(..) => unreachable!("Should never try to repair style of TextFragment"),
|
|
Fragment::Image(image_fragment) => image_fragment.borrow_mut().style = style.clone(),
|
|
Fragment::IFrame(iframe_fragment) => iframe_fragment.borrow_mut().style = style.clone(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TextFragment {
|
|
pub fn print(&self, tree: &mut PrintTree) {
|
|
tree.add_item(format!(
|
|
"Text num_glyphs={} box={:?}",
|
|
self.glyphs
|
|
.iter()
|
|
.map(|glyph_store| glyph_store.len().0)
|
|
.sum::<isize>(),
|
|
self.rect,
|
|
));
|
|
}
|
|
|
|
pub fn has_selection(&self) -> bool {
|
|
self.selection_range.is_some()
|
|
}
|
|
}
|
|
|
|
impl ImageFragment {
|
|
pub fn print(&self, tree: &mut PrintTree) {
|
|
tree.add_item(format!(
|
|
"Image\
|
|
\nrect={:?}",
|
|
self.rect
|
|
));
|
|
}
|
|
}
|
|
|
|
impl IFrameFragment {
|
|
pub fn print(&self, tree: &mut PrintTree) {
|
|
tree.add_item(format!(
|
|
"IFrame\
|
|
\npipeline={:?} rect={:?}",
|
|
self.pipeline_id, self.rect
|
|
));
|
|
}
|
|
}
|
|
|
|
impl CollapsedBlockMargins {
|
|
pub fn from_margin(margin: &LogicalSides<Au>) -> Self {
|
|
Self {
|
|
collapsed_through: false,
|
|
start: CollapsedMargin::new(margin.block_start),
|
|
end: CollapsedMargin::new(margin.block_end),
|
|
}
|
|
}
|
|
|
|
pub fn zero() -> Self {
|
|
Self {
|
|
collapsed_through: false,
|
|
start: CollapsedMargin::zero(),
|
|
end: CollapsedMargin::zero(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl CollapsedMargin {
|
|
pub fn zero() -> Self {
|
|
Self {
|
|
max_positive: Au::zero(),
|
|
min_negative: Au::zero(),
|
|
}
|
|
}
|
|
|
|
pub fn new(margin: Au) -> Self {
|
|
Self {
|
|
max_positive: margin.max(Au::zero()),
|
|
min_negative: margin.min(Au::zero()),
|
|
}
|
|
}
|
|
|
|
pub fn adjoin(&self, other: &Self) -> Self {
|
|
Self {
|
|
max_positive: self.max_positive.max(other.max_positive),
|
|
min_negative: self.min_negative.min(other.min_negative),
|
|
}
|
|
}
|
|
|
|
pub fn adjoin_assign(&mut self, other: &Self) {
|
|
*self = self.adjoin(other);
|
|
}
|
|
|
|
pub fn solve(&self) -> Au {
|
|
self.max_positive + self.min_negative
|
|
}
|
|
}
|