Skip to content

Commit

Permalink
layout: Add support for table rows, columns, rowgroups and colgroups (#…
Browse files Browse the repository at this point in the history
…31341)

This adds support for table rows, columns, rowgroups and colgroups.
There are few additions here:

1. The createion of fragments, which allows script queries and hit
   testing to work properly. These fragments are empty as all cells are
   still direct descendants of the table fragment.
2. Properly handling size information from tracks and track groups as
   well as frustrating rules about reordering rowgroups.
3. Painting a background seemlessly across track groups and groups. This
   is a thing that isn't done in legacy layout (nor WebKit)!

Co-authored-by: Oriol Brufau <obrufau@igalia.com>
  • Loading branch information
mrobinson and Loirooriol committed Feb 20, 2024
1 parent 74c07db commit 02ae1f4
Show file tree
Hide file tree
Showing 57 changed files with 4,514 additions and 21,240 deletions.
4 changes: 2 additions & 2 deletions components/layout/table_cell.rs
Expand Up @@ -73,8 +73,8 @@ impl TableCellFlow {
TableCellFlow {
block_flow: BlockFlow::from_fragment(fragment),
collapsed_borders: CollapsedBordersForCell::new(),
column_span: node.get_colspan(),
row_span: node.get_rowspan(),
column_span: node.get_colspan().unwrap_or(1),
row_span: node.get_rowspan().unwrap_or(1),
visible,
}
}
Expand Down
96 changes: 52 additions & 44 deletions components/layout_2020/display_list/background.rs
Expand Up @@ -36,62 +36,68 @@ fn get_cyclic<T>(values: &[T], layer_index: usize) -> &T {
&values[layer_index % values.len()]
}

pub(super) enum Source<'a> {
Fragment,
Canvas {
style: &'a ComputedValues,

// Theoretically the painting area is the infinite 2D plane,
// but WebRender doesn’t really do infinite so this is the part of it that can be visible.
painting_area: units::LayoutRect,
},
pub(super) struct BackgroundPainter<'a> {
pub style: &'a ComputedValues,
pub positioning_area_override: Option<units::LayoutRect>,
pub painting_area_override: Option<units::LayoutRect>,
}

pub(super) fn painting_area<'a>(
fragment_builder: &'a super::BuilderForBoxFragment,
source: &'a Source,
builder: &mut super::DisplayListBuilder,
layer_index: usize,
) -> (&'a units::LayoutRect, wr::CommonItemProperties) {
let fb = fragment_builder;
let (painting_area, clip) = match source {
Source::Canvas { painting_area, .. } => (painting_area, None),
Source::Fragment => {
let b = fb.fragment.style.get_background();
match get_cyclic(&b.background_clip.0, layer_index) {
Clip::ContentBox => (fb.content_rect(), fb.content_edge_clip(builder)),
Clip::PaddingBox => (fb.padding_rect(), fb.padding_edge_clip(builder)),
Clip::BorderBox => (&fb.border_rect, fb.border_edge_clip(builder)),
impl<'a> BackgroundPainter<'a> {
pub(super) fn painting_area(
&self,
fragment_builder: &'a super::BuilderForBoxFragment,
builder: &mut super::DisplayListBuilder,
layer_index: usize,
) -> (&units::LayoutRect, wr::CommonItemProperties) {
let fb = fragment_builder;
let (painting_area, clip) = match self.painting_area_override {
Some(ref painting_area) => (painting_area, None),
None if self.positioning_area_override.is_none() => {
let b = self.style.get_background();
match get_cyclic(&b.background_clip.0, layer_index) {
Clip::ContentBox => (fb.content_rect(), fb.content_edge_clip(builder)),
Clip::PaddingBox => (fb.padding_rect(), fb.padding_edge_clip(builder)),
Clip::BorderBox => (&fb.border_rect, fb.border_edge_clip(builder)),
}
},
None => (&fb.border_rect, fb.border_edge_clip(builder)),
};

// The 'backgound-clip' property maps directly to `clip_rect` in `CommonItemProperties`:
let mut common = builder.common_properties(*painting_area, &fb.fragment.style);
if let Some(clip_chain_id) = clip {
common.clip_id = wr::ClipId::ClipChain(clip_chain_id)
}
(painting_area, common)
}

pub(super) fn positioning_area(
&self,
fragment_builder: &'a super::BuilderForBoxFragment,
layer_index: usize,
) -> &units::LayoutRect {
self.positioning_area_override.as_ref().unwrap_or_else(|| {
match get_cyclic(
&self.style.get_background().background_origin.0,
layer_index,
) {
Origin::ContentBox => fragment_builder.content_rect(),
Origin::PaddingBox => fragment_builder.padding_rect(),
Origin::BorderBox => &fragment_builder.border_rect,
}
},
};
// The 'backgound-clip' property maps directly to `clip_rect` in `CommonItemProperties`:
let mut common = builder.common_properties(*painting_area, &fb.fragment.style);
if let Some(clip_chain_id) = clip {
common.clip_id = wr::ClipId::ClipChain(clip_chain_id)
})
}
(painting_area, common)
}

pub(super) fn layout_layer(
fragment_builder: &mut super::BuilderForBoxFragment,
source: &Source,
painter: &BackgroundPainter,
builder: &mut super::DisplayListBuilder,
layer_index: usize,
intrinsic: IntrinsicSizes,
) -> Option<BackgroundLayer> {
let style = match *source {
Source::Canvas { style, .. } => style,
Source::Fragment => &fragment_builder.fragment.style,
};
let b = style.get_background();
let (painting_area, common) = painting_area(fragment_builder, source, builder, layer_index);

let positioning_area = match get_cyclic(&b.background_origin.0, layer_index) {
Origin::ContentBox => fragment_builder.content_rect(),
Origin::PaddingBox => fragment_builder.padding_rect(),
Origin::BorderBox => &fragment_builder.border_rect,
};
let (painting_area, common) = painter.painting_area(fragment_builder, builder, layer_index);
let positioning_area = painter.positioning_area(fragment_builder, layer_index);

// https://drafts.csswg.org/css-backgrounds/#background-size
enum ContainOrCover {
Expand All @@ -117,6 +123,8 @@ pub(super) fn layout_layer(
}
tile_size
};

let b = painter.style.get_background();
let mut tile_size = match get_cyclic(&b.background_size.0, layer_index) {
Size::Contain => size_contain_or_cover(ContainOrCover::Contain),
Size::Cover => size_contain_or_cover(ContainOrCover::Cover),
Expand Down
22 changes: 22 additions & 0 deletions components/layout_2020/display_list/conversions.rs
Expand Up @@ -2,6 +2,7 @@
* 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 app_units::Au;
use style::color::AbsoluteColor;
use style::computed_values::mix_blend_mode::T as ComputedMixBlendMode;
use style::computed_values::text_decoration_style::T as ComputedTextDecorationStyle;
Expand Down Expand Up @@ -86,20 +87,41 @@ impl ToWebRender for PhysicalPoint<Length> {
}
}

impl ToWebRender for PhysicalPoint<Au> {
type Type = units::LayoutPoint;
fn to_webrender(&self) -> Self::Type {
units::LayoutPoint::new(self.x.to_f32_px(), self.y.to_f32_px())
}
}

impl ToWebRender for PhysicalSize<Length> {
type Type = units::LayoutSize;
fn to_webrender(&self) -> Self::Type {
units::LayoutSize::new(self.width.px(), self.height.px())
}
}

impl ToWebRender for PhysicalSize<Au> {
type Type = units::LayoutSize;
fn to_webrender(&self) -> Self::Type {
units::LayoutSize::new(self.width.to_f32_px(), self.height.to_f32_px())
}
}

impl ToWebRender for PhysicalRect<Length> {
type Type = units::LayoutRect;
fn to_webrender(&self) -> Self::Type {
units::LayoutRect::new(self.origin.to_webrender(), self.size.to_webrender())
}
}

impl ToWebRender for PhysicalRect<Au> {
type Type = units::LayoutRect;
fn to_webrender(&self) -> Self::Type {
units::LayoutRect::new(self.origin.to_webrender(), self.size.to_webrender())
}
}

impl ToWebRender for PhysicalSides<Length> {
type Type = units::LayoutSideOffsets;
fn to_webrender(&self) -> Self::Type {
Expand Down
133 changes: 95 additions & 38 deletions components/layout_2020/display_list/mod.rs
Expand Up @@ -28,7 +28,7 @@ use crate::context::LayoutContext;
use crate::display_list::conversions::ToWebRender;
use crate::display_list::stacking_context::StackingContextSection;
use crate::fragment_tree::{BoxFragment, Fragment, FragmentTree, Tag, TextFragment};
use crate::geom::{PhysicalPoint, PhysicalRect};
use crate::geom::{LogicalRect, PhysicalPoint, PhysicalRect};
use crate::replaced::IntrinsicSizes;
use crate::style_ext::ComputedValuesExt;

Expand All @@ -37,6 +37,7 @@ mod conversions;
mod gradient;
mod stacking_context;

use background::BackgroundPainter;
pub use stacking_context::*;

#[derive(Clone, Copy)]
Expand Down Expand Up @@ -205,7 +206,21 @@ impl Fragment {
Visibility::Collapse => (),
},
Fragment::AbsoluteOrFixedPositioned(_) => {},
Fragment::Anonymous(_) => {},
Fragment::Positioning(positioning_fragment) => {
if let Some(style) = positioning_fragment.style.as_ref() {
let rect = positioning_fragment
.rect
.to_physical(style.writing_mode, containing_block)
.translate(containing_block.origin.to_vector());
self.maybe_push_hit_test_for_style_and_tag(
builder,
style,
positioning_fragment.base.tag,
rect,
Cursor::Default,
);
}
},
Fragment::Image(i) => match i.style.get_inherited_box().visibility {
Visibility::Visible => {
builder.is_contentful = true;
Expand Down Expand Up @@ -265,6 +280,33 @@ impl Fragment {
}
}

fn maybe_push_hit_test_for_style_and_tag(
&self,
builder: &mut DisplayListBuilder,
style: &ComputedValues,
tag: Option<Tag>,
rect: PhysicalRect<Length>,
cursor: Cursor,
) {
let hit_info = builder.hit_info(style, tag, cursor);
let hit_info = match hit_info {
Some(hit_info) => hit_info,
None => return,
};

let clip_chain_id = builder.current_clip_chain_id;
let spatial_id = builder.current_scroll_node_id.spatial_id;
builder.wr().push_hit_test(
&CommonItemProperties {
clip_rect: rect.to_webrender(),
clip_id: ClipId::ClipChain(clip_chain_id),
spatial_id,
flags: style.get_webrender_primitive_flags(),
},
hit_info,
);
}

fn build_display_list_for_text_fragment(
&self,
fragment: &TextFragment,
Expand All @@ -291,21 +333,13 @@ impl Fragment {
return;
}

if let Some(hit_info) =
builder.hit_info(&fragment.parent_style, fragment.base.tag, Cursor::Text)
{
let clip_chain_id = builder.current_clip_chain_id;
let spatial_id = builder.current_scroll_node_id.spatial_id;
builder.wr().push_hit_test(
&CommonItemProperties {
clip_rect: rect.to_webrender(),
clip_id: ClipId::ClipChain(clip_chain_id),
spatial_id,
flags: fragment.parent_style.get_webrender_primitive_flags(),
},
hit_info,
);
}
self.maybe_push_hit_test_for_style_and_tag(
builder,
&fragment.parent_style,
fragment.base.tag,
rect,
Cursor::Text,
);

let color = fragment.parent_style.clone_color();
let font_metrics = &fragment.font_metrics;
Expand Down Expand Up @@ -540,6 +574,27 @@ impl<'a> BuilderForBoxFragment<'a> {
builder.wr().push_hit_test(&common, hit_info);
}

fn build_background_for_painter(
&mut self,
builder: &mut DisplayListBuilder,
painter: &BackgroundPainter,
) {
let b = painter.style.get_background();
let background_color = painter.style.resolve_color(b.background_color.clone());
if background_color.alpha > 0.0 {
// https://drafts.csswg.org/css-backgrounds/#background-color
// “The background color is clipped according to the background-clip
// value associated with the bottom-most background image layer.”
let layer_index = b.background_image.0.len() - 1;
let (bounds, common) = painter.painting_area(self, builder, layer_index);
builder
.wr()
.push_rect(&common, *bounds, rgba(background_color))
}

self.build_background_image(builder, painter);
}

fn build_background(&mut self, builder: &mut DisplayListBuilder) {
if self
.fragment
Expand All @@ -550,34 +605,36 @@ impl<'a> BuilderForBoxFragment<'a> {
return;
}

let source = background::Source::Fragment;
let style = &self.fragment.style;
let b = style.get_background();
let background_color = style.resolve_color(b.background_color.clone());
if background_color.alpha > 0.0 {
// https://drafts.csswg.org/css-backgrounds/#background-color
// “The background color is clipped according to the background-clip
// value associated with the bottom-most background image layer.”
let layer_index = b.background_image.0.len() - 1;
let (bounds, common) = background::painting_area(self, &source, builder, layer_index);
builder
.wr()
.push_rect(&common, *bounds, rgba(background_color))
for extra_background in self.fragment.extra_backgrounds.iter() {
let positioning_area: LogicalRect<Length> = extra_background.rect.clone().into();
let painter = BackgroundPainter {
style: &extra_background.style,
painting_area_override: None,
positioning_area_override: Some(
positioning_area
.to_physical(self.fragment.style.writing_mode, self.containing_block)
.translate(self.containing_block.origin.to_vector())
.to_webrender(),
),
};
self.build_background_for_painter(builder, &painter);
}

self.build_background_image(builder, source);
let painter = BackgroundPainter {
style: &self.fragment.style,
painting_area_override: None,
positioning_area_override: None,
};
self.build_background_for_painter(builder, &painter);
}

fn build_background_image(
&mut self,
builder: &mut DisplayListBuilder,
source: background::Source<'a>,
painter: &BackgroundPainter,
) {
use style::values::computed::image::Image;
let style = match source {
background::Source::Canvas { style, .. } => style,
background::Source::Fragment => &self.fragment.style,
};
let style = painter.style;
let b = style.get_background();
// Reverse because the property is top layer first, we want to paint bottom layer first.
for (index, image) in b.background_image.0.iter().enumerate().rev() {
Expand All @@ -586,7 +643,7 @@ impl<'a> BuilderForBoxFragment<'a> {
Image::Gradient(ref gradient) => {
let intrinsic = IntrinsicSizes::empty();
if let Some(layer) =
&background::layout_layer(self, &source, builder, index, intrinsic)
&background::layout_layer(self, painter, builder, index, intrinsic)
{
gradient::build(style, gradient, layer, builder)
}
Expand Down Expand Up @@ -627,7 +684,7 @@ impl<'a> BuilderForBoxFragment<'a> {
);

if let Some(layer) =
background::layout_layer(self, &source, builder, index, intrinsic)
background::layout_layer(self, &painter, builder, index, intrinsic)
{
let image_rendering = image_rendering(style.clone_image_rendering());
if layer.repeat {
Expand Down

0 comments on commit 02ae1f4

Please sign in to comment.