mirror of
https://github.com/servo/servo.git
synced 2025-08-13 01:15:34 +01:00
Remove legacy layout (layout 2013) (#35943)
We were already not compiling it and not running tests on it by default. So it's simpler to just completely remove it. Signed-off-by: Oriol Brufau <obrufau@igalia.com>
This commit is contained in:
parent
f93006af95
commit
7594dc6991
17224 changed files with 23 additions and 1584835 deletions
|
@ -1,341 +0,0 @@
|
|||
/* 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 app_units::Au;
|
||||
use euclid::default::{Point2D, Rect, SideOffsets2D, Size2D};
|
||||
use style::computed_values::background_attachment::single_value::T as BackgroundAttachment;
|
||||
use style::computed_values::background_clip::single_value::T as BackgroundClip;
|
||||
use style::computed_values::background_origin::single_value::T as BackgroundOrigin;
|
||||
use style::properties::style_structs::Background;
|
||||
use style::values::computed::{BackgroundSize, NonNegativeLengthPercentageOrAuto};
|
||||
use style::values::specified::background::BackgroundRepeatKeyword;
|
||||
use webrender_api::BorderRadius;
|
||||
|
||||
use crate::display_list::border;
|
||||
|
||||
/// Placment information for both image and gradient backgrounds.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct BackgroundPlacement {
|
||||
/// Rendering bounds. The background will start in the uppper-left corner
|
||||
/// and fill the whole area.
|
||||
pub bounds: Rect<Au>,
|
||||
/// Background tile size. Some backgrounds are repeated. These are the
|
||||
/// dimensions of a single image of the background.
|
||||
pub tile_size: Size2D<Au>,
|
||||
/// Spacing between tiles. Some backgrounds are not repeated seamless
|
||||
/// but have seams between them like tiles in real life.
|
||||
pub tile_spacing: Size2D<Au>,
|
||||
/// A clip area. While the background is rendered according to all the
|
||||
/// measures above it is only shown within these bounds.
|
||||
pub clip_rect: Rect<Au>,
|
||||
/// Rounded corners for the clip_rect.
|
||||
pub clip_radii: BorderRadius,
|
||||
}
|
||||
|
||||
/// Access element at index modulo the array length.
|
||||
///
|
||||
/// Obviously it does not work with empty arrays.
|
||||
///
|
||||
/// This is used for multiple layered background images.
|
||||
/// See: <https://drafts.csswg.org/css-backgrounds-3/#layering>
|
||||
pub fn get_cyclic<T>(arr: &[T], index: usize) -> &T {
|
||||
&arr[index % arr.len()]
|
||||
}
|
||||
|
||||
/// For a given area and an image compute how big the
|
||||
/// image should be displayed on the background.
|
||||
fn compute_background_image_size(
|
||||
bg_size: &BackgroundSize,
|
||||
bounds_size: Size2D<Au>,
|
||||
intrinsic_size: Option<Size2D<Au>>,
|
||||
) -> Size2D<Au> {
|
||||
match intrinsic_size {
|
||||
None => match bg_size {
|
||||
BackgroundSize::Cover | BackgroundSize::Contain => bounds_size,
|
||||
BackgroundSize::ExplicitSize { width, height } => Size2D::new(
|
||||
width
|
||||
.to_used_value(bounds_size.width)
|
||||
.unwrap_or(bounds_size.width),
|
||||
height
|
||||
.to_used_value(bounds_size.height)
|
||||
.unwrap_or(bounds_size.height),
|
||||
),
|
||||
},
|
||||
Some(own_size) => {
|
||||
// If `image_aspect_ratio` < `bounds_aspect_ratio`, the image is tall; otherwise, it is
|
||||
// wide.
|
||||
let image_aspect_ratio = own_size.width.to_f32_px() / own_size.height.to_f32_px();
|
||||
let bounds_aspect_ratio =
|
||||
bounds_size.width.to_f32_px() / bounds_size.height.to_f32_px();
|
||||
match (bg_size, image_aspect_ratio < bounds_aspect_ratio) {
|
||||
(BackgroundSize::Contain, false) | (BackgroundSize::Cover, true) => Size2D::new(
|
||||
bounds_size.width,
|
||||
bounds_size.width.scale_by(image_aspect_ratio.recip()),
|
||||
),
|
||||
(BackgroundSize::Contain, true) | (BackgroundSize::Cover, false) => Size2D::new(
|
||||
bounds_size.height.scale_by(image_aspect_ratio),
|
||||
bounds_size.height,
|
||||
),
|
||||
(
|
||||
BackgroundSize::ExplicitSize {
|
||||
width,
|
||||
height: NonNegativeLengthPercentageOrAuto::Auto,
|
||||
},
|
||||
_,
|
||||
) => {
|
||||
let width = width
|
||||
.to_used_value(bounds_size.width)
|
||||
.unwrap_or(own_size.width);
|
||||
Size2D::new(width, width.scale_by(image_aspect_ratio.recip()))
|
||||
},
|
||||
(
|
||||
BackgroundSize::ExplicitSize {
|
||||
width: NonNegativeLengthPercentageOrAuto::Auto,
|
||||
height,
|
||||
},
|
||||
_,
|
||||
) => {
|
||||
let height = height
|
||||
.to_used_value(bounds_size.height)
|
||||
.unwrap_or(own_size.height);
|
||||
Size2D::new(height.scale_by(image_aspect_ratio), height)
|
||||
},
|
||||
(BackgroundSize::ExplicitSize { width, height }, _) => Size2D::new(
|
||||
width
|
||||
.to_used_value(bounds_size.width)
|
||||
.unwrap_or(own_size.width),
|
||||
height
|
||||
.to_used_value(bounds_size.height)
|
||||
.unwrap_or(own_size.height),
|
||||
),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute a rounded clip rect for the background.
|
||||
pub fn clip(
|
||||
bg_clip: BackgroundClip,
|
||||
absolute_bounds: Rect<Au>,
|
||||
border: SideOffsets2D<Au>,
|
||||
border_padding: SideOffsets2D<Au>,
|
||||
border_radii: BorderRadius,
|
||||
) -> (Rect<Au>, BorderRadius) {
|
||||
match bg_clip {
|
||||
BackgroundClip::BorderBox => (absolute_bounds, border_radii),
|
||||
BackgroundClip::PaddingBox => (
|
||||
absolute_bounds.inner_rect(border),
|
||||
border::inner_radii(border_radii, border),
|
||||
),
|
||||
BackgroundClip::ContentBox => (
|
||||
absolute_bounds.inner_rect(border_padding),
|
||||
border::inner_radii(border_radii, border_padding),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Determines where to place an element background image or gradient.
|
||||
///
|
||||
/// Images have their resolution as intrinsic size while gradients have
|
||||
/// no intrinsic size.
|
||||
///
|
||||
/// Return `None` if the background size is zero, otherwise a [`BackgroundPlacement`].
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn placement(
|
||||
bg: &Background,
|
||||
viewport_size: Size2D<Au>,
|
||||
absolute_bounds: Rect<Au>,
|
||||
intrinsic_size: Option<Size2D<Au>>,
|
||||
border: SideOffsets2D<Au>,
|
||||
border_padding: SideOffsets2D<Au>,
|
||||
border_radii: BorderRadius,
|
||||
index: usize,
|
||||
) -> Option<BackgroundPlacement> {
|
||||
let bg_attachment = *get_cyclic(&bg.background_attachment.0, index);
|
||||
let bg_clip = *get_cyclic(&bg.background_clip.0, index);
|
||||
let bg_origin = *get_cyclic(&bg.background_origin.0, index);
|
||||
let bg_position_x = get_cyclic(&bg.background_position_x.0, index);
|
||||
let bg_position_y = get_cyclic(&bg.background_position_y.0, index);
|
||||
let bg_repeat = get_cyclic(&bg.background_repeat.0, index);
|
||||
let bg_size = get_cyclic(&bg.background_size.0, index);
|
||||
|
||||
let (clip_rect, clip_radii) = clip(
|
||||
bg_clip,
|
||||
absolute_bounds,
|
||||
border,
|
||||
border_padding,
|
||||
border_radii,
|
||||
);
|
||||
|
||||
let mut bounds = match bg_attachment {
|
||||
BackgroundAttachment::Scroll => match bg_origin {
|
||||
BackgroundOrigin::BorderBox => absolute_bounds,
|
||||
BackgroundOrigin::PaddingBox => absolute_bounds.inner_rect(border),
|
||||
BackgroundOrigin::ContentBox => absolute_bounds.inner_rect(border_padding),
|
||||
},
|
||||
BackgroundAttachment::Fixed => Rect::new(Point2D::origin(), viewport_size),
|
||||
};
|
||||
|
||||
let mut tile_size = compute_background_image_size(bg_size, bounds.size, intrinsic_size);
|
||||
if tile_size.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut tile_spacing = Size2D::zero();
|
||||
let own_position = bounds.size - tile_size;
|
||||
let pos_x = bg_position_x.to_used_value(own_position.width);
|
||||
let pos_y = bg_position_y.to_used_value(own_position.height);
|
||||
tile_image_axis(
|
||||
bg_repeat.0,
|
||||
&mut bounds.origin.x,
|
||||
&mut bounds.size.width,
|
||||
&mut tile_size.width,
|
||||
&mut tile_spacing.width,
|
||||
pos_x,
|
||||
clip_rect.origin.x,
|
||||
clip_rect.size.width,
|
||||
);
|
||||
tile_image_axis(
|
||||
bg_repeat.1,
|
||||
&mut bounds.origin.y,
|
||||
&mut bounds.size.height,
|
||||
&mut tile_size.height,
|
||||
&mut tile_spacing.height,
|
||||
pos_y,
|
||||
clip_rect.origin.y,
|
||||
clip_rect.size.height,
|
||||
);
|
||||
|
||||
if tile_size.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(BackgroundPlacement {
|
||||
bounds,
|
||||
tile_size,
|
||||
tile_spacing,
|
||||
clip_rect,
|
||||
clip_radii,
|
||||
})
|
||||
}
|
||||
|
||||
fn tile_image_round(
|
||||
position: &mut Au,
|
||||
size: &mut Au,
|
||||
absolute_anchor_origin: Au,
|
||||
image_size: &mut Au,
|
||||
) {
|
||||
if *size == Au(0) || *image_size == Au(0) {
|
||||
*position = Au(0);
|
||||
*size = Au(0);
|
||||
return;
|
||||
}
|
||||
|
||||
let number_of_tiles = (size.to_f32_px() / image_size.to_f32_px()).round().max(1.0);
|
||||
*image_size = *size / (number_of_tiles as i32);
|
||||
tile_image(position, size, absolute_anchor_origin, *image_size);
|
||||
}
|
||||
|
||||
fn tile_image_spaced(
|
||||
position: &mut Au,
|
||||
size: &mut Au,
|
||||
tile_spacing: &mut Au,
|
||||
absolute_anchor_origin: Au,
|
||||
image_size: Au,
|
||||
) {
|
||||
if *size == Au(0) || image_size == Au(0) {
|
||||
*position = Au(0);
|
||||
*size = Au(0);
|
||||
*tile_spacing = Au(0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Per the spec, if the space available is not enough for two images, just tile as
|
||||
// normal but only display a single tile.
|
||||
if image_size * 2 >= *size {
|
||||
tile_image(position, size, absolute_anchor_origin, image_size);
|
||||
*tile_spacing = Au(0);
|
||||
*size = image_size;
|
||||
return;
|
||||
}
|
||||
|
||||
// Take the box size, remove room for two tiles on the edges, and then calculate how many
|
||||
// other tiles fit in between them.
|
||||
let size_remaining = *size - (image_size * 2);
|
||||
let num_middle_tiles = (size_remaining.to_f32_px() / image_size.to_f32_px()).floor() as i32;
|
||||
|
||||
// Allocate the remaining space as padding between tiles. background-position is ignored
|
||||
// as per the spec, so the position is just the box origin. We are also ignoring
|
||||
// background-attachment here, which seems unspecced when combined with
|
||||
// background-repeat: space.
|
||||
let space_for_middle_tiles = image_size * num_middle_tiles;
|
||||
*tile_spacing = (size_remaining - space_for_middle_tiles) / (num_middle_tiles + 1);
|
||||
}
|
||||
|
||||
/// Tile an image
|
||||
fn tile_image(position: &mut Au, size: &mut Au, absolute_anchor_origin: Au, image_size: Au) {
|
||||
// Avoid division by zero below!
|
||||
// Images with a zero width or height are not displayed.
|
||||
// Therefore the positions do not matter and can be left unchanged.
|
||||
// NOTE: A possible optimization is not to build
|
||||
// display items in this case at all.
|
||||
if image_size == Au(0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let delta_pixels = absolute_anchor_origin - *position;
|
||||
let image_size_px = image_size.to_f32_px();
|
||||
let tile_count = ((delta_pixels.to_f32_px() + image_size_px - 1.0) / image_size_px).floor();
|
||||
let offset = image_size * (tile_count as i32);
|
||||
let new_position = absolute_anchor_origin - offset;
|
||||
*size = *position - new_position + *size;
|
||||
*position = new_position;
|
||||
}
|
||||
|
||||
/// For either the x or the y axis adjust various values to account for tiling.
|
||||
///
|
||||
/// This is done separately for both axes because the repeat keywords may differ.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn tile_image_axis(
|
||||
repeat: BackgroundRepeatKeyword,
|
||||
position: &mut Au,
|
||||
size: &mut Au,
|
||||
tile_size: &mut Au,
|
||||
tile_spacing: &mut Au,
|
||||
offset: Au,
|
||||
clip_origin: Au,
|
||||
clip_size: Au,
|
||||
) {
|
||||
let absolute_anchor_origin = *position + offset;
|
||||
match repeat {
|
||||
BackgroundRepeatKeyword::NoRepeat => {
|
||||
*position += offset;
|
||||
*size = *tile_size;
|
||||
},
|
||||
BackgroundRepeatKeyword::Repeat => {
|
||||
*position = clip_origin;
|
||||
*size = clip_size;
|
||||
tile_image(position, size, absolute_anchor_origin, *tile_size);
|
||||
},
|
||||
BackgroundRepeatKeyword::Space => {
|
||||
tile_image_spaced(
|
||||
position,
|
||||
size,
|
||||
tile_spacing,
|
||||
absolute_anchor_origin,
|
||||
*tile_size,
|
||||
);
|
||||
let combined_tile_size = *tile_size + *tile_spacing;
|
||||
*position = clip_origin;
|
||||
*size = clip_size;
|
||||
tile_image(position, size, absolute_anchor_origin, combined_tile_size);
|
||||
},
|
||||
BackgroundRepeatKeyword::Round => {
|
||||
tile_image_round(position, size, absolute_anchor_origin, tile_size);
|
||||
*position = clip_origin;
|
||||
*size = clip_size;
|
||||
tile_image(position, size, absolute_anchor_origin, *tile_size);
|
||||
},
|
||||
}
|
||||
}
|
|
@ -1,207 +0,0 @@
|
|||
/* 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 app_units::Au;
|
||||
use euclid::default::{Rect, SideOffsets2D as UntypedSideOffsets2D, Size2D as UntypedSize2D};
|
||||
use euclid::{SideOffsets2D, Size2D};
|
||||
use style::computed_values::border_image_outset::T as BorderImageOutset;
|
||||
use style::properties::style_structs::Border;
|
||||
use style::values::computed::{
|
||||
BorderCornerRadius, BorderImageSideWidth, BorderImageWidth, NonNegativeLengthOrNumber,
|
||||
NumberOrPercentage,
|
||||
};
|
||||
use style::values::generics::NonNegative;
|
||||
use style::values::generics::rect::Rect as StyleRect;
|
||||
use webrender_api::units::{LayoutSideOffsets, LayoutSize};
|
||||
use webrender_api::{BorderRadius, BorderSide, BorderStyle, ColorF, NormalBorder};
|
||||
|
||||
use crate::display_list::ToLayout;
|
||||
|
||||
/// Computes a border radius size against the containing size.
|
||||
///
|
||||
/// Note that percentages in `border-radius` are resolved against the relevant
|
||||
/// box dimension instead of only against the width per [1]:
|
||||
///
|
||||
/// > Percentages: Refer to corresponding dimension of the border box.
|
||||
///
|
||||
/// [1]: https://drafts.csswg.org/css-backgrounds-3/#border-radius
|
||||
fn corner_radius(
|
||||
radius: &BorderCornerRadius,
|
||||
containing_size: UntypedSize2D<Au>,
|
||||
) -> UntypedSize2D<Au> {
|
||||
let w = radius.0.width().to_used_value(containing_size.width);
|
||||
let h = radius.0.height().to_used_value(containing_size.height);
|
||||
Size2D::new(w, h)
|
||||
}
|
||||
|
||||
fn scaled_radii(radii: BorderRadius, factor: f32) -> BorderRadius {
|
||||
BorderRadius {
|
||||
top_left: radii.top_left * factor,
|
||||
top_right: radii.top_right * factor,
|
||||
bottom_left: radii.bottom_left * factor,
|
||||
bottom_right: radii.bottom_right * factor,
|
||||
}
|
||||
}
|
||||
|
||||
fn overlapping_radii(size: LayoutSize, radii: BorderRadius) -> BorderRadius {
|
||||
// No two corners' border radii may add up to more than the length of the edge
|
||||
// between them. To prevent that, all radii are scaled down uniformly.
|
||||
fn scale_factor(radius_a: f32, radius_b: f32, edge_length: f32) -> f32 {
|
||||
let required = radius_a + radius_b;
|
||||
|
||||
if required <= edge_length {
|
||||
1.0
|
||||
} else {
|
||||
edge_length / required
|
||||
}
|
||||
}
|
||||
|
||||
let top_factor = scale_factor(radii.top_left.width, radii.top_right.width, size.width);
|
||||
let bottom_factor = scale_factor(
|
||||
radii.bottom_left.width,
|
||||
radii.bottom_right.width,
|
||||
size.width,
|
||||
);
|
||||
let left_factor = scale_factor(radii.top_left.height, radii.bottom_left.height, size.height);
|
||||
let right_factor = scale_factor(
|
||||
radii.top_right.height,
|
||||
radii.bottom_right.height,
|
||||
size.height,
|
||||
);
|
||||
let min_factor = top_factor
|
||||
.min(bottom_factor)
|
||||
.min(left_factor)
|
||||
.min(right_factor);
|
||||
if min_factor < 1.0 {
|
||||
scaled_radii(radii, min_factor)
|
||||
} else {
|
||||
radii
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine the four corner radii of a border.
|
||||
///
|
||||
/// Radii may either be absolute or relative to the absolute bounds.
|
||||
/// Each corner radius has a width and a height which may differ.
|
||||
/// Lastly overlapping radii are shrank so they don't collide anymore.
|
||||
pub fn radii(abs_bounds: Rect<Au>, border_style: &Border) -> BorderRadius {
|
||||
// TODO(cgaebel): Support border radii even in the case of multiple border widths.
|
||||
// This is an extension of supporting elliptical radii. For now, all percentage
|
||||
// radii will be relative to the width.
|
||||
|
||||
overlapping_radii(
|
||||
abs_bounds.size.to_layout(),
|
||||
BorderRadius {
|
||||
top_left: corner_radius(&border_style.border_top_left_radius, abs_bounds.size)
|
||||
.to_layout(),
|
||||
top_right: corner_radius(&border_style.border_top_right_radius, abs_bounds.size)
|
||||
.to_layout(),
|
||||
bottom_right: corner_radius(&border_style.border_bottom_right_radius, abs_bounds.size)
|
||||
.to_layout(),
|
||||
bottom_left: corner_radius(&border_style.border_bottom_left_radius, abs_bounds.size)
|
||||
.to_layout(),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Calculates radii for the inner side.
|
||||
///
|
||||
/// Radii usually describe the outer side of a border but for the lines to look nice
|
||||
/// the inner radii need to be smaller depending on the line width.
|
||||
///
|
||||
/// This is used to determine clipping areas.
|
||||
pub fn inner_radii(mut radii: BorderRadius, offsets: UntypedSideOffsets2D<Au>) -> BorderRadius {
|
||||
fn inner_length(x: f32, offset: Au) -> f32 {
|
||||
0.0_f32.max(x - offset.to_f32_px())
|
||||
}
|
||||
radii.top_left.width = inner_length(radii.top_left.width, offsets.left);
|
||||
radii.bottom_left.width = inner_length(radii.bottom_left.width, offsets.left);
|
||||
|
||||
radii.top_right.width = inner_length(radii.top_right.width, offsets.right);
|
||||
radii.bottom_right.width = inner_length(radii.bottom_right.width, offsets.right);
|
||||
|
||||
radii.top_left.height = inner_length(radii.top_left.height, offsets.top);
|
||||
radii.top_right.height = inner_length(radii.top_right.height, offsets.top);
|
||||
|
||||
radii.bottom_left.height = inner_length(radii.bottom_left.height, offsets.bottom);
|
||||
radii.bottom_right.height = inner_length(radii.bottom_right.height, offsets.bottom);
|
||||
radii
|
||||
}
|
||||
|
||||
/// Creates a four-sided border with square corners and uniform color and width.
|
||||
pub fn simple(color: ColorF, style: BorderStyle) -> NormalBorder {
|
||||
let side = BorderSide { color, style };
|
||||
NormalBorder {
|
||||
left: side,
|
||||
right: side,
|
||||
top: side,
|
||||
bottom: side,
|
||||
radius: BorderRadius::zero(),
|
||||
do_aa: true,
|
||||
}
|
||||
}
|
||||
|
||||
fn side_image_outset(outset: NonNegativeLengthOrNumber, border_width: Au) -> Au {
|
||||
match outset {
|
||||
NonNegativeLengthOrNumber::Length(length) => length.into(),
|
||||
NonNegativeLengthOrNumber::Number(factor) => border_width.scale_by(factor.0),
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute the additional border-image area.
|
||||
pub fn image_outset(
|
||||
outset: BorderImageOutset,
|
||||
border: UntypedSideOffsets2D<Au>,
|
||||
) -> UntypedSideOffsets2D<Au> {
|
||||
SideOffsets2D::new(
|
||||
side_image_outset(outset.0, border.top),
|
||||
side_image_outset(outset.1, border.right),
|
||||
side_image_outset(outset.2, border.bottom),
|
||||
side_image_outset(outset.3, border.left),
|
||||
)
|
||||
}
|
||||
|
||||
fn side_image_width(
|
||||
border_image_width: &BorderImageSideWidth,
|
||||
border_width: f32,
|
||||
total_length: Au,
|
||||
) -> f32 {
|
||||
match border_image_width {
|
||||
BorderImageSideWidth::LengthPercentage(v) => v.to_used_value(total_length).to_f32_px(),
|
||||
BorderImageSideWidth::Number(x) => border_width * x.0,
|
||||
BorderImageSideWidth::Auto => border_width,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn image_width(
|
||||
width: &BorderImageWidth,
|
||||
border: LayoutSideOffsets,
|
||||
border_area: UntypedSize2D<Au>,
|
||||
) -> LayoutSideOffsets {
|
||||
LayoutSideOffsets::new(
|
||||
side_image_width(&width.0, border.top, border_area.height),
|
||||
side_image_width(&width.1, border.right, border_area.width),
|
||||
side_image_width(&width.2, border.bottom, border_area.height),
|
||||
side_image_width(&width.3, border.left, border_area.width),
|
||||
)
|
||||
}
|
||||
|
||||
fn resolve_percentage(value: NonNegative<NumberOrPercentage>, length: i32) -> i32 {
|
||||
match value.0 {
|
||||
NumberOrPercentage::Percentage(p) => (p.0 * length as f32).round() as i32,
|
||||
NumberOrPercentage::Number(n) => n.round() as i32,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn image_slice<U>(
|
||||
border_image_slice: &StyleRect<NonNegative<NumberOrPercentage>>,
|
||||
size: Size2D<i32, U>,
|
||||
) -> SideOffsets2D<i32, U> {
|
||||
SideOffsets2D::new(
|
||||
resolve_percentage(border_image_slice.0, size.height),
|
||||
resolve_percentage(border_image_slice.1, size.width),
|
||||
resolve_percentage(border_image_slice.2, size.height),
|
||||
resolve_percentage(border_image_slice.3, size.width),
|
||||
)
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,184 +0,0 @@
|
|||
/* 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 app_units::Au;
|
||||
use euclid::default::{Point2D, Rect, SideOffsets2D, Size2D, Vector2D};
|
||||
use style::color::{AbsoluteColor, ColorSpace};
|
||||
use style::computed_values::image_rendering::T as ImageRendering;
|
||||
use style::computed_values::mix_blend_mode::T as MixBlendMode;
|
||||
use style::computed_values::transform_style::T as TransformStyle;
|
||||
use style::values::computed::{BorderStyle, Filter};
|
||||
use style::values::specified::border::BorderImageRepeatKeyword;
|
||||
use webrender_api as wr;
|
||||
|
||||
pub trait ToLayout {
|
||||
type Type;
|
||||
fn to_layout(&self) -> Self::Type;
|
||||
}
|
||||
|
||||
pub trait FilterToLayout {
|
||||
type Type;
|
||||
fn to_layout(&self, current_color: &AbsoluteColor) -> Self::Type;
|
||||
}
|
||||
|
||||
impl ToLayout for BorderStyle {
|
||||
type Type = wr::BorderStyle;
|
||||
fn to_layout(&self) -> Self::Type {
|
||||
match *self {
|
||||
BorderStyle::None => wr::BorderStyle::None,
|
||||
BorderStyle::Solid => wr::BorderStyle::Solid,
|
||||
BorderStyle::Double => wr::BorderStyle::Double,
|
||||
BorderStyle::Dotted => wr::BorderStyle::Dotted,
|
||||
BorderStyle::Dashed => wr::BorderStyle::Dashed,
|
||||
BorderStyle::Hidden => wr::BorderStyle::Hidden,
|
||||
BorderStyle::Groove => wr::BorderStyle::Groove,
|
||||
BorderStyle::Ridge => wr::BorderStyle::Ridge,
|
||||
BorderStyle::Inset => wr::BorderStyle::Inset,
|
||||
BorderStyle::Outset => wr::BorderStyle::Outset,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FilterToLayout for Filter {
|
||||
type Type = wr::FilterOp;
|
||||
fn to_layout(&self, current_color: &AbsoluteColor) -> Self::Type {
|
||||
match *self {
|
||||
Filter::Blur(radius) => wr::FilterOp::Blur(radius.px(), radius.px()),
|
||||
Filter::Brightness(amount) => wr::FilterOp::Brightness(amount.0),
|
||||
Filter::Contrast(amount) => wr::FilterOp::Contrast(amount.0),
|
||||
Filter::Grayscale(amount) => wr::FilterOp::Grayscale(amount.0),
|
||||
Filter::HueRotate(angle) => wr::FilterOp::HueRotate(angle.radians()),
|
||||
Filter::Invert(amount) => wr::FilterOp::Invert(amount.0),
|
||||
Filter::Opacity(amount) => wr::FilterOp::Opacity(amount.0.into(), amount.0),
|
||||
Filter::Saturate(amount) => wr::FilterOp::Saturate(amount.0),
|
||||
Filter::Sepia(amount) => wr::FilterOp::Sepia(amount.0),
|
||||
Filter::DropShadow(ref shadow) => wr::FilterOp::DropShadow(wr::Shadow {
|
||||
blur_radius: shadow.blur.px(),
|
||||
offset: wr::units::LayoutVector2D::new(
|
||||
shadow.horizontal.px(),
|
||||
shadow.vertical.px(),
|
||||
),
|
||||
color: shadow
|
||||
.color
|
||||
.clone()
|
||||
.resolve_to_absolute(current_color)
|
||||
.to_layout(),
|
||||
}),
|
||||
// Statically check that Url is impossible.
|
||||
Filter::Url(ref url) => match *url {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToLayout for ImageRendering {
|
||||
type Type = wr::ImageRendering;
|
||||
fn to_layout(&self) -> Self::Type {
|
||||
match *self {
|
||||
ImageRendering::Auto => wr::ImageRendering::Auto,
|
||||
ImageRendering::CrispEdges => wr::ImageRendering::CrispEdges,
|
||||
ImageRendering::Pixelated => wr::ImageRendering::Pixelated,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToLayout for MixBlendMode {
|
||||
type Type = wr::MixBlendMode;
|
||||
fn to_layout(&self) -> Self::Type {
|
||||
match *self {
|
||||
MixBlendMode::Normal => wr::MixBlendMode::Normal,
|
||||
MixBlendMode::Multiply => wr::MixBlendMode::Multiply,
|
||||
MixBlendMode::Screen => wr::MixBlendMode::Screen,
|
||||
MixBlendMode::Overlay => wr::MixBlendMode::Overlay,
|
||||
MixBlendMode::Darken => wr::MixBlendMode::Darken,
|
||||
MixBlendMode::Lighten => wr::MixBlendMode::Lighten,
|
||||
MixBlendMode::ColorDodge => wr::MixBlendMode::ColorDodge,
|
||||
MixBlendMode::ColorBurn => wr::MixBlendMode::ColorBurn,
|
||||
MixBlendMode::HardLight => wr::MixBlendMode::HardLight,
|
||||
MixBlendMode::SoftLight => wr::MixBlendMode::SoftLight,
|
||||
MixBlendMode::Difference => wr::MixBlendMode::Difference,
|
||||
MixBlendMode::Exclusion => wr::MixBlendMode::Exclusion,
|
||||
MixBlendMode::Hue => wr::MixBlendMode::Hue,
|
||||
MixBlendMode::Saturation => wr::MixBlendMode::Saturation,
|
||||
MixBlendMode::Color => wr::MixBlendMode::Color,
|
||||
MixBlendMode::Luminosity => wr::MixBlendMode::Luminosity,
|
||||
MixBlendMode::PlusLighter => wr::MixBlendMode::PlusLighter,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToLayout for TransformStyle {
|
||||
type Type = wr::TransformStyle;
|
||||
fn to_layout(&self) -> Self::Type {
|
||||
match *self {
|
||||
TransformStyle::Flat => wr::TransformStyle::Flat,
|
||||
TransformStyle::Preserve3d => wr::TransformStyle::Preserve3D,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToLayout for AbsoluteColor {
|
||||
type Type = wr::ColorF;
|
||||
fn to_layout(&self) -> Self::Type {
|
||||
let rgba = self.to_color_space(ColorSpace::Srgb);
|
||||
wr::ColorF::new(
|
||||
rgba.components.0.clamp(0.0, 1.0),
|
||||
rgba.components.1.clamp(0.0, 1.0),
|
||||
rgba.components.2.clamp(0.0, 1.0),
|
||||
rgba.alpha,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToLayout for Point2D<Au> {
|
||||
type Type = wr::units::LayoutPoint;
|
||||
fn to_layout(&self) -> Self::Type {
|
||||
wr::units::LayoutPoint::new(self.x.to_f32_px(), self.y.to_f32_px())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToLayout for Rect<Au> {
|
||||
type Type = wr::units::LayoutRect;
|
||||
fn to_layout(&self) -> Self::Type {
|
||||
wr::units::LayoutRect::from_origin_and_size(self.origin.to_layout(), self.size.to_layout())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToLayout for SideOffsets2D<Au> {
|
||||
type Type = wr::units::LayoutSideOffsets;
|
||||
fn to_layout(&self) -> Self::Type {
|
||||
wr::units::LayoutSideOffsets::new(
|
||||
self.top.to_f32_px(),
|
||||
self.right.to_f32_px(),
|
||||
self.bottom.to_f32_px(),
|
||||
self.left.to_f32_px(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToLayout for Size2D<Au> {
|
||||
type Type = wr::units::LayoutSize;
|
||||
fn to_layout(&self) -> Self::Type {
|
||||
wr::units::LayoutSize::new(self.width.to_f32_px(), self.height.to_f32_px())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToLayout for Vector2D<Au> {
|
||||
type Type = wr::units::LayoutVector2D;
|
||||
fn to_layout(&self) -> Self::Type {
|
||||
wr::units::LayoutVector2D::new(self.x.to_f32_px(), self.y.to_f32_px())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToLayout for BorderImageRepeatKeyword {
|
||||
type Type = wr::RepeatMode;
|
||||
|
||||
fn to_layout(&self) -> Self::Type {
|
||||
match *self {
|
||||
BorderImageRepeatKeyword::Stretch => wr::RepeatMode::Stretch,
|
||||
BorderImageRepeatKeyword::Repeat => wr::RepeatMode::Repeat,
|
||||
BorderImageRepeatKeyword::Round => wr::RepeatMode::Round,
|
||||
BorderImageRepeatKeyword::Space => wr::RepeatMode::Space,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,349 +0,0 @@
|
|||
/* 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 app_units::Au;
|
||||
use euclid::default::{Point2D, Size2D, Vector2D};
|
||||
use style::color::mix::ColorInterpolationMethod;
|
||||
use style::properties::ComputedValues;
|
||||
use style::values::computed::image::{EndingShape, LineDirection};
|
||||
use style::values::computed::{Angle, Color, LengthPercentage, Percentage, Position};
|
||||
use style::values::generics::image::{
|
||||
Circle, ColorStop, Ellipse, GradientFlags, GradientItem, ShapeExtent,
|
||||
};
|
||||
use webrender_api::{ExtendMode, Gradient, GradientBuilder, GradientStop, RadialGradient};
|
||||
|
||||
use crate::display_list::ToLayout;
|
||||
|
||||
/// A helper data structure for gradients.
|
||||
#[derive(Clone, Copy)]
|
||||
struct StopRun {
|
||||
start_offset: f32,
|
||||
end_offset: f32,
|
||||
start_index: usize,
|
||||
stop_count: usize,
|
||||
}
|
||||
|
||||
/// Determines the radius of a circle if it was not explicitly provided.
|
||||
/// <https://drafts.csswg.org/css-images-3/#typedef-size>
|
||||
fn circle_size_keyword(
|
||||
keyword: ShapeExtent,
|
||||
size: &Size2D<Au>,
|
||||
center: &Point2D<Au>,
|
||||
) -> Size2D<Au> {
|
||||
let radius = match keyword {
|
||||
ShapeExtent::ClosestSide | ShapeExtent::Contain => {
|
||||
let dist = distance_to_sides(size, center, ::std::cmp::min);
|
||||
::std::cmp::min(dist.width, dist.height)
|
||||
},
|
||||
ShapeExtent::FarthestSide => {
|
||||
let dist = distance_to_sides(size, center, ::std::cmp::max);
|
||||
::std::cmp::max(dist.width, dist.height)
|
||||
},
|
||||
ShapeExtent::ClosestCorner => distance_to_corner(size, center, ::std::cmp::min),
|
||||
ShapeExtent::FarthestCorner | ShapeExtent::Cover => {
|
||||
distance_to_corner(size, center, ::std::cmp::max)
|
||||
},
|
||||
};
|
||||
Size2D::new(radius, radius)
|
||||
}
|
||||
|
||||
/// Returns the radius for an ellipse with the same ratio as if it was matched to the sides.
|
||||
fn ellipse_radius<F>(size: &Size2D<Au>, center: &Point2D<Au>, cmp: F) -> Size2D<Au>
|
||||
where
|
||||
F: Fn(Au, Au) -> Au,
|
||||
{
|
||||
let dist = distance_to_sides(size, center, cmp);
|
||||
Size2D::new(
|
||||
dist.width.scale_by(::std::f32::consts::FRAC_1_SQRT_2 * 2.0),
|
||||
dist.height
|
||||
.scale_by(::std::f32::consts::FRAC_1_SQRT_2 * 2.0),
|
||||
)
|
||||
}
|
||||
|
||||
/// Determines the radius of an ellipse if it was not explicitly provided.
|
||||
/// <https://drafts.csswg.org/css-images-3/#typedef-size>
|
||||
fn ellipse_size_keyword(
|
||||
keyword: ShapeExtent,
|
||||
size: &Size2D<Au>,
|
||||
center: &Point2D<Au>,
|
||||
) -> Size2D<Au> {
|
||||
match keyword {
|
||||
ShapeExtent::ClosestSide | ShapeExtent::Contain => {
|
||||
distance_to_sides(size, center, ::std::cmp::min)
|
||||
},
|
||||
ShapeExtent::FarthestSide => distance_to_sides(size, center, ::std::cmp::max),
|
||||
ShapeExtent::ClosestCorner => ellipse_radius(size, center, ::std::cmp::min),
|
||||
ShapeExtent::FarthestCorner | ShapeExtent::Cover => {
|
||||
ellipse_radius(size, center, ::std::cmp::max)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_gradient_stops(
|
||||
style: &ComputedValues,
|
||||
gradient_items: &[GradientItem<Color, LengthPercentage>],
|
||||
total_length: Au,
|
||||
) -> GradientBuilder {
|
||||
// Determine the position of each stop per CSS-IMAGES § 3.4.
|
||||
|
||||
// Only keep the color stops, discard the color interpolation hints.
|
||||
let mut stop_items = gradient_items
|
||||
.iter()
|
||||
.filter_map(|item| match item {
|
||||
GradientItem::SimpleColorStop(color) => Some(ColorStop {
|
||||
color,
|
||||
position: None,
|
||||
}),
|
||||
GradientItem::ComplexColorStop {
|
||||
color,
|
||||
ref position,
|
||||
} => Some(ColorStop {
|
||||
color,
|
||||
position: Some(position.clone()),
|
||||
}),
|
||||
_ => None,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert!(!stop_items.is_empty());
|
||||
|
||||
// Run the algorithm from
|
||||
// https://drafts.csswg.org/css-images-3/#color-stop-syntax
|
||||
|
||||
// Step 1:
|
||||
// If the first color stop does not have a position, set its position to 0%.
|
||||
{
|
||||
let first = stop_items.first_mut().unwrap();
|
||||
if first.position.is_none() {
|
||||
first.position = Some(LengthPercentage::new_percent(Percentage(0.)));
|
||||
}
|
||||
}
|
||||
// If the last color stop does not have a position, set its position to 100%.
|
||||
{
|
||||
let last = stop_items.last_mut().unwrap();
|
||||
if last.position.is_none() {
|
||||
last.position = Some(LengthPercentage::new_percent(Percentage(1.0)));
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: Move any stops placed before earlier stops to the
|
||||
// same position as the preceding stop.
|
||||
//
|
||||
// FIXME(emilio): Once we know the offsets, it seems like converting the
|
||||
// positions to absolute at once then process that would be cheaper.
|
||||
let mut last_stop_position = stop_items
|
||||
.first()
|
||||
.unwrap()
|
||||
.position
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.clone();
|
||||
for stop in stop_items.iter_mut().skip(1) {
|
||||
if let Some(ref pos) = stop.position {
|
||||
if position_to_offset(&last_stop_position, total_length) >
|
||||
position_to_offset(pos, total_length)
|
||||
{
|
||||
stop.position = Some(last_stop_position);
|
||||
}
|
||||
last_stop_position = stop.position.as_ref().unwrap().clone();
|
||||
}
|
||||
}
|
||||
|
||||
// Step 3: Evenly space stops without position.
|
||||
let mut stops = GradientBuilder::new();
|
||||
let mut stop_run = None;
|
||||
for (i, stop) in stop_items.iter().enumerate() {
|
||||
let offset = match stop.position {
|
||||
None => {
|
||||
if stop_run.is_none() {
|
||||
// Initialize a new stop run.
|
||||
// `unwrap()` here should never fail because this is the beginning of
|
||||
// a stop run, which is always bounded by a length or percentage.
|
||||
let start_offset = position_to_offset(
|
||||
stop_items[i - 1].position.as_ref().unwrap(),
|
||||
total_length,
|
||||
);
|
||||
// `unwrap()` here should never fail because this is the end of
|
||||
// a stop run, which is always bounded by a length or percentage.
|
||||
let (end_index, end_stop) = stop_items[(i + 1)..]
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, stop)| stop.position.is_some())
|
||||
.unwrap();
|
||||
let end_offset =
|
||||
position_to_offset(end_stop.position.as_ref().unwrap(), total_length);
|
||||
stop_run = Some(StopRun {
|
||||
start_offset,
|
||||
end_offset,
|
||||
start_index: i - 1,
|
||||
stop_count: end_index,
|
||||
})
|
||||
}
|
||||
|
||||
let stop_run = stop_run.unwrap();
|
||||
let stop_run_length = stop_run.end_offset - stop_run.start_offset;
|
||||
stop_run.start_offset +
|
||||
stop_run_length * (i - stop_run.start_index) as f32 /
|
||||
((2 + stop_run.stop_count) as f32)
|
||||
},
|
||||
Some(ref position) => {
|
||||
stop_run = None;
|
||||
position_to_offset(position, total_length)
|
||||
},
|
||||
};
|
||||
assert!(offset.is_finite());
|
||||
stops.push(GradientStop {
|
||||
offset,
|
||||
color: style.resolve_color(stop.color).to_layout(),
|
||||
})
|
||||
}
|
||||
if stop_items.len() == 1 {
|
||||
stops.push(stops.stops()[0])
|
||||
}
|
||||
stops
|
||||
}
|
||||
|
||||
fn extend_mode(repeating: bool) -> ExtendMode {
|
||||
if repeating {
|
||||
ExtendMode::Repeat
|
||||
} else {
|
||||
ExtendMode::Clamp
|
||||
}
|
||||
}
|
||||
/// Returns the the distance to the nearest or farthest corner depending on the comperator.
|
||||
fn distance_to_corner<F>(size: &Size2D<Au>, center: &Point2D<Au>, cmp: F) -> Au
|
||||
where
|
||||
F: Fn(Au, Au) -> Au,
|
||||
{
|
||||
let dist = distance_to_sides(size, center, cmp);
|
||||
Au::from_f32_px(dist.width.to_f32_px().hypot(dist.height.to_f32_px()))
|
||||
}
|
||||
|
||||
/// Returns the distance to the nearest or farthest sides depending on the comparator.
|
||||
///
|
||||
/// The first return value is horizontal distance the second vertical distance.
|
||||
fn distance_to_sides<F>(size: &Size2D<Au>, center: &Point2D<Au>, cmp: F) -> Size2D<Au>
|
||||
where
|
||||
F: Fn(Au, Au) -> Au,
|
||||
{
|
||||
let top_side = center.y;
|
||||
let right_side = size.width - center.x;
|
||||
let bottom_side = size.height - center.y;
|
||||
let left_side = center.x;
|
||||
Size2D::new(cmp(left_side, right_side), cmp(top_side, bottom_side))
|
||||
}
|
||||
|
||||
fn position_to_offset(position: &LengthPercentage, total_length: Au) -> f32 {
|
||||
if total_length == Au(0) {
|
||||
return 0.0;
|
||||
}
|
||||
position.to_used_value(total_length).0 as f32 / total_length.0 as f32
|
||||
}
|
||||
|
||||
pub fn linear(
|
||||
style: &ComputedValues,
|
||||
size: Size2D<Au>,
|
||||
stops: &[GradientItem<Color, LengthPercentage>],
|
||||
direction: LineDirection,
|
||||
_color_interpolation_method: &ColorInterpolationMethod,
|
||||
flags: GradientFlags,
|
||||
) -> (Gradient, Vec<GradientStop>) {
|
||||
use style::values::specified::position::HorizontalPositionKeyword::*;
|
||||
use style::values::specified::position::VerticalPositionKeyword::*;
|
||||
let repeating = flags.contains(GradientFlags::REPEATING);
|
||||
let angle = match direction {
|
||||
LineDirection::Angle(angle) => angle.radians(),
|
||||
LineDirection::Horizontal(x) => match x {
|
||||
Left => Angle::from_degrees(270.).radians(),
|
||||
Right => Angle::from_degrees(90.).radians(),
|
||||
},
|
||||
LineDirection::Vertical(y) => match y {
|
||||
Top => Angle::from_degrees(0.).radians(),
|
||||
Bottom => Angle::from_degrees(180.).radians(),
|
||||
},
|
||||
LineDirection::Corner(horizontal, vertical) => {
|
||||
// This the angle for one of the diagonals of the box. Our angle
|
||||
// will either be this one, this one + PI, or one of the other
|
||||
// two perpendicular angles.
|
||||
let atan = (size.height.to_f32_px() / size.width.to_f32_px()).atan();
|
||||
match (horizontal, vertical) {
|
||||
(Right, Bottom) => ::std::f32::consts::PI - atan,
|
||||
(Left, Bottom) => ::std::f32::consts::PI + atan,
|
||||
(Right, Top) => atan,
|
||||
(Left, Top) => -atan,
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// Get correct gradient line length, based on:
|
||||
// https://drafts.csswg.org/css-images-3/#linear-gradients
|
||||
let dir = Point2D::new(angle.sin(), -angle.cos());
|
||||
|
||||
let line_length =
|
||||
(dir.x * size.width.to_f32_px()).abs() + (dir.y * size.height.to_f32_px()).abs();
|
||||
|
||||
let inv_dir_length = 1.0 / (dir.x * dir.x + dir.y * dir.y).sqrt();
|
||||
|
||||
// This is the vector between the center and the ending point; i.e. half
|
||||
// of the distance between the starting point and the ending point.
|
||||
let delta = Vector2D::new(
|
||||
Au::from_f32_px(dir.x * inv_dir_length * line_length / 2.0),
|
||||
Au::from_f32_px(dir.y * inv_dir_length * line_length / 2.0),
|
||||
);
|
||||
|
||||
// This is the length of the gradient line.
|
||||
let length = Au::from_f32_px((delta.x.to_f32_px() * 2.0).hypot(delta.y.to_f32_px() * 2.0));
|
||||
|
||||
let mut builder = convert_gradient_stops(style, stops, length);
|
||||
|
||||
let center = Point2D::new(size.width / 2, size.height / 2);
|
||||
|
||||
(
|
||||
builder.gradient(
|
||||
(center - delta).to_layout(),
|
||||
(center + delta).to_layout(),
|
||||
extend_mode(repeating),
|
||||
),
|
||||
builder.into_stops(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn radial(
|
||||
style: &ComputedValues,
|
||||
size: Size2D<Au>,
|
||||
stops: &[GradientItem<Color, LengthPercentage>],
|
||||
shape: &EndingShape,
|
||||
center: &Position,
|
||||
_color_interpolation_method: &ColorInterpolationMethod,
|
||||
flags: GradientFlags,
|
||||
) -> (RadialGradient, Vec<GradientStop>) {
|
||||
let repeating = flags.contains(GradientFlags::REPEATING);
|
||||
let center = Point2D::new(
|
||||
center.horizontal.to_used_value(size.width),
|
||||
center.vertical.to_used_value(size.height),
|
||||
);
|
||||
let radius = match shape {
|
||||
EndingShape::Circle(Circle::Radius(length)) => {
|
||||
let length = Au::from(*length);
|
||||
Size2D::new(length, length)
|
||||
},
|
||||
EndingShape::Circle(Circle::Extent(extent)) => circle_size_keyword(*extent, &size, ¢er),
|
||||
EndingShape::Ellipse(Ellipse::Radii(x, y)) => {
|
||||
Size2D::new(x.to_used_value(size.width), y.to_used_value(size.height))
|
||||
},
|
||||
EndingShape::Ellipse(Ellipse::Extent(extent)) => {
|
||||
ellipse_size_keyword(*extent, &size, ¢er)
|
||||
},
|
||||
};
|
||||
|
||||
let mut builder = convert_gradient_stops(style, stops, radius.width);
|
||||
(
|
||||
builder.radial_gradient(
|
||||
center.to_layout(),
|
||||
radius.to_layout(),
|
||||
extend_mode(repeating),
|
||||
),
|
||||
builder.into_stops(),
|
||||
)
|
||||
}
|
|
@ -1,761 +0,0 @@
|
|||
/* 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/. */
|
||||
|
||||
//! Servo heavily uses display lists, which are retained-mode lists of painting commands to
|
||||
//! perform. Using a list instead of painting elements in immediate mode allows transforms, hit
|
||||
//! testing, and invalidation to be performed using the same primitives as painting. It also allows
|
||||
//! Servo to aggressively cull invisible and out-of-bounds painting elements, to reduce overdraw.
|
||||
//!
|
||||
//! Display items describe relatively high-level drawing operations (for example, entire borders
|
||||
//! and shadows instead of lines and blur operations), to reduce the amount of allocation required.
|
||||
//! They are therefore not exactly analogous to constructs like Skia pictures, which consist of
|
||||
//! low-level drawing primitives.
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::HashMap;
|
||||
use std::{f32, fmt};
|
||||
|
||||
use base::id::PipelineId;
|
||||
use base::print_tree::PrintTree;
|
||||
use embedder_traits::Cursor;
|
||||
use euclid::{SideOffsets2D, Vector2D};
|
||||
use pixels::Image;
|
||||
use serde::Serialize;
|
||||
use servo_geometry::MaxRect;
|
||||
use style::computed_values::_servo_top_layer::T as InTopLayer;
|
||||
pub use style::dom::OpaqueNode;
|
||||
use webrender_api as wr;
|
||||
use webrender_api::units::{LayoutPixel, LayoutRect, LayoutTransform};
|
||||
use webrender_api::{
|
||||
BorderRadius, ClipChainId, ClipMode, CommonItemProperties, ComplexClipRegion, ExternalScrollId,
|
||||
FilterOp, GlyphInstance, GradientStop, ImageKey, MixBlendMode, PrimitiveFlags, Shadow,
|
||||
SpatialId, StickyOffsetBounds, TransformStyle,
|
||||
};
|
||||
use webrender_traits::display_list::{AxesScrollSensitivity, ScrollTreeNodeId};
|
||||
|
||||
use super::StackingContextId;
|
||||
|
||||
/// The factor that we multiply the blur radius by in order to inflate the boundaries of display
|
||||
/// items that involve a blur. This ensures that the display item boundaries include all the ink.
|
||||
pub static BLUR_INFLATION_FACTOR: i32 = 3;
|
||||
|
||||
/// An index into the vector of ClipScrollNodes. During WebRender conversion these nodes
|
||||
/// are given ClipIds.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
|
||||
pub struct ClipScrollNodeIndex(usize);
|
||||
|
||||
impl ClipScrollNodeIndex {
|
||||
pub fn root_scroll_node() -> ClipScrollNodeIndex {
|
||||
ClipScrollNodeIndex(1)
|
||||
}
|
||||
|
||||
pub fn root_reference_frame() -> ClipScrollNodeIndex {
|
||||
ClipScrollNodeIndex(0)
|
||||
}
|
||||
|
||||
pub fn new(index: usize) -> ClipScrollNodeIndex {
|
||||
assert_ne!(index, 0, "Use the root_reference_frame constructor");
|
||||
assert_ne!(index, 1, "Use the root_scroll_node constructor");
|
||||
ClipScrollNodeIndex(index)
|
||||
}
|
||||
|
||||
pub fn is_root_scroll_node(&self) -> bool {
|
||||
*self == Self::root_scroll_node()
|
||||
}
|
||||
|
||||
pub fn to_define_item(&self) -> DisplayItem {
|
||||
DisplayItem::DefineClipScrollNode(Box::new(DefineClipScrollNodeItem {
|
||||
base: BaseDisplayItem::empty(),
|
||||
node_index: *self,
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn to_index(self) -> usize {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// A set of indices into the clip scroll node vector for a given item.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
|
||||
pub struct ClippingAndScrolling {
|
||||
pub scrolling: ClipScrollNodeIndex,
|
||||
pub clipping: Option<ClipScrollNodeIndex>,
|
||||
}
|
||||
|
||||
impl ClippingAndScrolling {
|
||||
pub fn simple(scrolling: ClipScrollNodeIndex) -> ClippingAndScrolling {
|
||||
ClippingAndScrolling {
|
||||
scrolling,
|
||||
clipping: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(scrolling: ClipScrollNodeIndex, clipping: ClipScrollNodeIndex) -> Self {
|
||||
ClippingAndScrolling {
|
||||
scrolling,
|
||||
clipping: Some(clipping),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct DisplayList {
|
||||
pub list: Vec<DisplayItem>,
|
||||
pub clip_scroll_nodes: Vec<ClipScrollNode>,
|
||||
}
|
||||
|
||||
impl DisplayList {
|
||||
/// Return the bounds of this display list based on the dimensions of the root
|
||||
/// stacking context.
|
||||
pub fn bounds(&self) -> LayoutRect {
|
||||
match self.list.first() {
|
||||
Some(DisplayItem::PushStackingContext(item)) => item.stacking_context.bounds,
|
||||
Some(_) => unreachable!("Root element of display list not stacking context."),
|
||||
None => LayoutRect::zero(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn print(&self) {
|
||||
let mut print_tree = PrintTree::new("Display List".to_owned());
|
||||
self.print_with_tree(&mut print_tree);
|
||||
}
|
||||
|
||||
pub fn print_with_tree(&self, print_tree: &mut PrintTree) {
|
||||
print_tree.new_level("ClipScrollNodes".to_owned());
|
||||
for node in &self.clip_scroll_nodes {
|
||||
print_tree.add_item(format!("{:?}", node));
|
||||
}
|
||||
print_tree.end_level();
|
||||
|
||||
print_tree.new_level("Items".to_owned());
|
||||
for item in &self.list {
|
||||
print_tree.add_item(format!(
|
||||
"{:?} StackingContext: {:?} {:?}",
|
||||
item,
|
||||
item.base().stacking_context_id,
|
||||
item.clipping_and_scrolling()
|
||||
));
|
||||
}
|
||||
print_tree.end_level();
|
||||
}
|
||||
}
|
||||
|
||||
/// Display list sections that make up a stacking context. Each section here refers
|
||||
/// to the steps in CSS 2.1 Appendix E.
|
||||
///
|
||||
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize)]
|
||||
pub enum DisplayListSection {
|
||||
BackgroundAndBorders,
|
||||
BlockBackgroundsAndBorders,
|
||||
Content,
|
||||
Outlines,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize)]
|
||||
pub enum StackingContextType {
|
||||
Real,
|
||||
PseudoPositioned,
|
||||
PseudoFloat,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize)]
|
||||
/// Represents one CSS stacking context, which may or may not have a hardware layer.
|
||||
pub struct StackingContext {
|
||||
/// The ID of this StackingContext for uniquely identifying it.
|
||||
pub id: StackingContextId,
|
||||
|
||||
/// The type of this StackingContext. Used for collecting and sorting.
|
||||
pub context_type: StackingContextType,
|
||||
|
||||
/// The position and size of this stacking context.
|
||||
pub bounds: LayoutRect,
|
||||
|
||||
/// The overflow rect for this stacking context in its coordinate system.
|
||||
pub overflow: LayoutRect,
|
||||
|
||||
/// The `z-index` for this stacking context.
|
||||
pub z_index: i32,
|
||||
|
||||
/// Whether this is the top layer.
|
||||
pub in_top_layer: InTopLayer,
|
||||
|
||||
/// CSS filters to be applied to this stacking context (including opacity).
|
||||
pub filters: Vec<FilterOp>,
|
||||
|
||||
/// The blend mode with which this stacking context blends with its backdrop.
|
||||
pub mix_blend_mode: MixBlendMode,
|
||||
|
||||
/// A transform to be applied to this stacking context.
|
||||
pub transform: Option<LayoutTransform>,
|
||||
|
||||
/// The transform style of this stacking context.
|
||||
pub transform_style: TransformStyle,
|
||||
|
||||
/// The perspective matrix to be applied to children.
|
||||
pub perspective: Option<LayoutTransform>,
|
||||
|
||||
/// The clip and scroll info for this StackingContext.
|
||||
pub parent_clipping_and_scrolling: ClippingAndScrolling,
|
||||
|
||||
/// The index of the reference frame that this stacking context establishes.
|
||||
pub established_reference_frame: Option<ClipScrollNodeIndex>,
|
||||
}
|
||||
|
||||
impl StackingContext {
|
||||
/// Creates a new stacking context.
|
||||
#[inline]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
id: StackingContextId,
|
||||
context_type: StackingContextType,
|
||||
bounds: LayoutRect,
|
||||
overflow: LayoutRect,
|
||||
z_index: i32,
|
||||
in_top_layer: InTopLayer,
|
||||
filters: Vec<FilterOp>,
|
||||
mix_blend_mode: MixBlendMode,
|
||||
transform: Option<LayoutTransform>,
|
||||
transform_style: TransformStyle,
|
||||
perspective: Option<LayoutTransform>,
|
||||
parent_clipping_and_scrolling: ClippingAndScrolling,
|
||||
established_reference_frame: Option<ClipScrollNodeIndex>,
|
||||
) -> StackingContext {
|
||||
if let Some(ref t) = transform {
|
||||
// These are used as scale values by webrender, and it can't handle
|
||||
// divisors of 0 when scaling.
|
||||
assert_ne!(t.m11, 0.);
|
||||
assert_ne!(t.m22, 0.);
|
||||
}
|
||||
StackingContext {
|
||||
id,
|
||||
context_type,
|
||||
bounds,
|
||||
overflow,
|
||||
z_index,
|
||||
in_top_layer,
|
||||
filters,
|
||||
mix_blend_mode,
|
||||
transform,
|
||||
transform_style,
|
||||
perspective,
|
||||
parent_clipping_and_scrolling,
|
||||
established_reference_frame,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn root() -> StackingContext {
|
||||
StackingContext::new(
|
||||
StackingContextId::root(),
|
||||
StackingContextType::Real,
|
||||
LayoutRect::zero(),
|
||||
LayoutRect::zero(),
|
||||
0,
|
||||
InTopLayer::None,
|
||||
vec![],
|
||||
MixBlendMode::Normal,
|
||||
None,
|
||||
TransformStyle::Flat,
|
||||
None,
|
||||
ClippingAndScrolling::simple(ClipScrollNodeIndex::root_scroll_node()),
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn to_display_list_items(self) -> (DisplayItem, DisplayItem) {
|
||||
let mut base_item = BaseDisplayItem::empty();
|
||||
base_item.stacking_context_id = self.id;
|
||||
base_item.clipping_and_scrolling = self.parent_clipping_and_scrolling;
|
||||
|
||||
let pop_item = DisplayItem::PopStackingContext(Box::new(PopStackingContextItem {
|
||||
base: base_item.clone(),
|
||||
stacking_context_id: self.id,
|
||||
established_reference_frame: self.established_reference_frame.is_some(),
|
||||
}));
|
||||
|
||||
let push_item = DisplayItem::PushStackingContext(Box::new(PushStackingContextItem {
|
||||
base: base_item,
|
||||
stacking_context: self,
|
||||
}));
|
||||
|
||||
(push_item, pop_item)
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for StackingContext {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
if self.in_top_layer == InTopLayer::Top {
|
||||
if other.in_top_layer == InTopLayer::Top {
|
||||
return Ordering::Equal;
|
||||
} else {
|
||||
return Ordering::Greater;
|
||||
}
|
||||
} else if other.in_top_layer == InTopLayer::Top {
|
||||
return Ordering::Less;
|
||||
}
|
||||
|
||||
if self.z_index != 0 || other.z_index != 0 {
|
||||
return self.z_index.cmp(&other.z_index);
|
||||
}
|
||||
|
||||
match (self.context_type, other.context_type) {
|
||||
(StackingContextType::PseudoFloat, StackingContextType::PseudoFloat) => Ordering::Equal,
|
||||
(StackingContextType::PseudoFloat, _) => Ordering::Less,
|
||||
(_, StackingContextType::PseudoFloat) => Ordering::Greater,
|
||||
(_, _) => Ordering::Equal,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for StackingContext {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for StackingContext {}
|
||||
impl PartialEq for StackingContext {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id == other.id
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for StackingContext {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let type_string = if self.context_type == StackingContextType::Real {
|
||||
"StackingContext"
|
||||
} else {
|
||||
"Pseudo-StackingContext"
|
||||
};
|
||||
|
||||
write!(
|
||||
f,
|
||||
"{} at {:?} with overflow {:?}: {:?}",
|
||||
type_string, self.bounds, self.overflow, self.id
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize)]
|
||||
pub struct StickyFrameData {
|
||||
pub margins: SideOffsets2D<Option<f32>, LayoutPixel>,
|
||||
pub vertical_offset_bounds: StickyOffsetBounds,
|
||||
pub horizontal_offset_bounds: StickyOffsetBounds,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
|
||||
pub enum ClipType {
|
||||
Rounded(ComplexClipRegion),
|
||||
Rect,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize)]
|
||||
pub enum ClipScrollNodeType {
|
||||
Placeholder,
|
||||
ScrollFrame(AxesScrollSensitivity, ExternalScrollId),
|
||||
StickyFrame(StickyFrameData),
|
||||
Clip(ClipType),
|
||||
}
|
||||
|
||||
/// Defines a clip scroll node.
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub struct ClipScrollNode {
|
||||
/// The index of the parent of this ClipScrollNode.
|
||||
pub parent_index: ClipScrollNodeIndex,
|
||||
|
||||
/// The position of this scroll root's frame in the parent stacking context.
|
||||
pub clip: ClippingRegion,
|
||||
|
||||
/// The rect of the contents that can be scrolled inside of the scroll root.
|
||||
pub content_rect: LayoutRect,
|
||||
|
||||
/// The type of this ClipScrollNode.
|
||||
pub node_type: ClipScrollNodeType,
|
||||
|
||||
/// The WebRender spatial id of this node assigned during WebRender conversion.
|
||||
pub scroll_node_id: Option<ScrollTreeNodeId>,
|
||||
|
||||
/// The WebRender clip id of this node assigned during WebRender conversion.
|
||||
pub clip_chain_id: Option<ClipChainId>,
|
||||
}
|
||||
|
||||
impl ClipScrollNode {
|
||||
pub fn placeholder() -> ClipScrollNode {
|
||||
ClipScrollNode {
|
||||
parent_index: ClipScrollNodeIndex(0),
|
||||
clip: ClippingRegion::from_rect(LayoutRect::zero()),
|
||||
content_rect: LayoutRect::zero(),
|
||||
node_type: ClipScrollNodeType::Placeholder,
|
||||
scroll_node_id: None,
|
||||
clip_chain_id: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_placeholder(&self) -> bool {
|
||||
self.node_type == ClipScrollNodeType::Placeholder
|
||||
}
|
||||
|
||||
pub fn rounded(
|
||||
clip_rect: LayoutRect,
|
||||
radii: BorderRadius,
|
||||
parent_index: ClipScrollNodeIndex,
|
||||
) -> ClipScrollNode {
|
||||
let complex_region = ComplexClipRegion {
|
||||
rect: clip_rect,
|
||||
radii,
|
||||
mode: ClipMode::Clip,
|
||||
};
|
||||
ClipScrollNode {
|
||||
parent_index,
|
||||
clip: ClippingRegion::from_rect(clip_rect),
|
||||
content_rect: LayoutRect::zero(), // content_rect isn't important for clips.
|
||||
node_type: ClipScrollNodeType::Clip(ClipType::Rounded(complex_region)),
|
||||
scroll_node_id: None,
|
||||
clip_chain_id: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// One drawing command in the list.
|
||||
#[derive(Clone, Serialize)]
|
||||
pub enum DisplayItem {
|
||||
Rectangle(Box<CommonDisplayItem<wr::RectangleDisplayItem>>),
|
||||
Text(Box<CommonDisplayItem<wr::TextDisplayItem, Vec<GlyphInstance>>>),
|
||||
Image(Box<CommonDisplayItem<wr::ImageDisplayItem>>),
|
||||
RepeatingImage(Box<CommonDisplayItem<wr::RepeatingImageDisplayItem>>),
|
||||
Border(Box<CommonDisplayItem<wr::BorderDisplayItem, Vec<GradientStop>>>),
|
||||
Gradient(Box<CommonDisplayItem<wr::GradientDisplayItem, Vec<GradientStop>>>),
|
||||
RadialGradient(Box<CommonDisplayItem<wr::RadialGradientDisplayItem, Vec<GradientStop>>>),
|
||||
Line(Box<CommonDisplayItem<wr::LineDisplayItem>>),
|
||||
BoxShadow(Box<CommonDisplayItem<wr::BoxShadowDisplayItem>>),
|
||||
PushTextShadow(Box<PushTextShadowDisplayItem>),
|
||||
PopAllTextShadows(Box<PopAllTextShadowsDisplayItem>),
|
||||
Iframe(Box<IframeDisplayItem>),
|
||||
PushStackingContext(Box<PushStackingContextItem>),
|
||||
PopStackingContext(Box<PopStackingContextItem>),
|
||||
DefineClipScrollNode(Box<DefineClipScrollNodeItem>),
|
||||
}
|
||||
|
||||
/// Information common to all display items.
|
||||
#[derive(Clone, Serialize)]
|
||||
pub struct BaseDisplayItem {
|
||||
/// Metadata attached to this display item.
|
||||
pub metadata: DisplayItemMetadata,
|
||||
|
||||
/// The clip rectangle to use for this item.
|
||||
pub clip_rect: LayoutRect,
|
||||
|
||||
/// The section of the display list that this item belongs to.
|
||||
pub section: DisplayListSection,
|
||||
|
||||
/// The id of the stacking context this item belongs to.
|
||||
pub stacking_context_id: StackingContextId,
|
||||
|
||||
/// The clip and scroll info for this item.
|
||||
pub clipping_and_scrolling: ClippingAndScrolling,
|
||||
}
|
||||
|
||||
impl BaseDisplayItem {
|
||||
#[inline(always)]
|
||||
pub fn new(
|
||||
metadata: DisplayItemMetadata,
|
||||
clip_rect: LayoutRect,
|
||||
section: DisplayListSection,
|
||||
stacking_context_id: StackingContextId,
|
||||
clipping_and_scrolling: ClippingAndScrolling,
|
||||
) -> BaseDisplayItem {
|
||||
BaseDisplayItem {
|
||||
metadata,
|
||||
clip_rect,
|
||||
section,
|
||||
stacking_context_id,
|
||||
clipping_and_scrolling,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn empty() -> BaseDisplayItem {
|
||||
BaseDisplayItem {
|
||||
metadata: DisplayItemMetadata {
|
||||
node: OpaqueNode(0),
|
||||
unique_id: 0,
|
||||
cursor: None,
|
||||
},
|
||||
// Create a rectangle of maximal size.
|
||||
clip_rect: LayoutRect::max_rect(),
|
||||
section: DisplayListSection::Content,
|
||||
stacking_context_id: StackingContextId::root(),
|
||||
clipping_and_scrolling: ClippingAndScrolling::simple(
|
||||
ClipScrollNodeIndex::root_scroll_node(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn empty_common_item_properties() -> CommonItemProperties {
|
||||
CommonItemProperties {
|
||||
clip_rect: LayoutRect::max_rect(),
|
||||
clip_chain_id: ClipChainId::INVALID,
|
||||
spatial_id: SpatialId::root_scroll_node(wr::PipelineId::dummy()),
|
||||
flags: PrimitiveFlags::empty(),
|
||||
}
|
||||
}
|
||||
|
||||
/// A clipping region for a display item. Currently, this can describe rectangles, rounded
|
||||
/// rectangles (for `border-radius`), or arbitrary intersections of the two. Arbitrary transforms
|
||||
/// are not supported because those are handled by the higher-level `StackingContext` abstraction.
|
||||
#[derive(Clone, PartialEq, Serialize)]
|
||||
pub struct ClippingRegion {
|
||||
/// The main rectangular region. This does not include any corners.
|
||||
pub main: LayoutRect,
|
||||
}
|
||||
|
||||
impl ClippingRegion {
|
||||
/// Returns an empty clipping region that, if set, will result in no pixels being visible.
|
||||
#[inline]
|
||||
pub fn empty() -> ClippingRegion {
|
||||
ClippingRegion {
|
||||
main: LayoutRect::zero(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an all-encompassing clipping region that clips no pixels out.
|
||||
#[inline]
|
||||
pub fn max() -> ClippingRegion {
|
||||
ClippingRegion {
|
||||
main: LayoutRect::max_rect(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a clipping region that represents the given rectangle.
|
||||
#[inline]
|
||||
pub fn from_rect(rect: LayoutRect) -> ClippingRegion {
|
||||
ClippingRegion { main: rect }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for ClippingRegion {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
if *self == ClippingRegion::max() {
|
||||
write!(f, "ClippingRegion::Max")
|
||||
} else if *self == ClippingRegion::empty() {
|
||||
write!(f, "ClippingRegion::Empty")
|
||||
} else {
|
||||
write!(f, "ClippingRegion(Rect={:?})", self.main,)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Metadata attached to each display item. This is useful for performing auxiliary threads with
|
||||
/// the display list involving hit testing: finding the originating DOM node and determining the
|
||||
/// cursor to use when the element is hovered over.
|
||||
#[derive(Clone, Copy, Serialize)]
|
||||
pub struct DisplayItemMetadata {
|
||||
/// The DOM node from which this display item originated.
|
||||
pub node: OpaqueNode,
|
||||
/// The unique fragment id of the fragment of this item.
|
||||
pub unique_id: u64,
|
||||
/// The value of the `cursor` property when the mouse hovers over this display item. If `None`,
|
||||
/// this display item is ineligible for pointer events (`pointer-events: none`).
|
||||
pub cursor: Option<Cursor>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Serialize)]
|
||||
pub enum TextOrientation {
|
||||
Upright,
|
||||
SidewaysLeft,
|
||||
SidewaysRight,
|
||||
}
|
||||
|
||||
/// Paints an iframe.
|
||||
#[derive(Clone, Serialize)]
|
||||
pub struct IframeDisplayItem {
|
||||
pub base: BaseDisplayItem,
|
||||
pub iframe: PipelineId,
|
||||
pub bounds: LayoutRect,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize)]
|
||||
pub struct CommonDisplayItem<T, U = ()> {
|
||||
pub base: BaseDisplayItem,
|
||||
pub item: T,
|
||||
pub data: U,
|
||||
}
|
||||
|
||||
impl<T> CommonDisplayItem<T> {
|
||||
pub fn new(base: BaseDisplayItem, item: T) -> Box<CommonDisplayItem<T>> {
|
||||
Box::new(CommonDisplayItem {
|
||||
base,
|
||||
item,
|
||||
data: (),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> CommonDisplayItem<T, U> {
|
||||
pub fn with_data(base: BaseDisplayItem, item: T, data: U) -> Box<CommonDisplayItem<T, U>> {
|
||||
Box::new(CommonDisplayItem { base, item, data })
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines a text shadow that affects all items until the paired PopTextShadow.
|
||||
#[derive(Clone, Serialize)]
|
||||
pub struct PushTextShadowDisplayItem {
|
||||
/// Fields common to all display items.
|
||||
pub base: BaseDisplayItem,
|
||||
|
||||
pub shadow: Shadow,
|
||||
}
|
||||
|
||||
/// Defines a text shadow that affects all items until the next PopTextShadow.
|
||||
#[derive(Clone, Serialize)]
|
||||
pub struct PopAllTextShadowsDisplayItem {
|
||||
/// Fields common to all display items.
|
||||
pub base: BaseDisplayItem,
|
||||
}
|
||||
|
||||
/// Defines a stacking context.
|
||||
#[derive(Clone, Serialize)]
|
||||
pub struct PushStackingContextItem {
|
||||
/// Fields common to all display items.
|
||||
pub base: BaseDisplayItem,
|
||||
|
||||
pub stacking_context: StackingContext,
|
||||
}
|
||||
|
||||
/// Defines a stacking context.
|
||||
#[derive(Clone, Serialize)]
|
||||
pub struct PopStackingContextItem {
|
||||
/// Fields common to all display items.
|
||||
pub base: BaseDisplayItem,
|
||||
|
||||
pub stacking_context_id: StackingContextId,
|
||||
|
||||
pub established_reference_frame: bool,
|
||||
}
|
||||
|
||||
/// Starts a group of items inside a particular scroll root.
|
||||
#[derive(Clone, Serialize)]
|
||||
pub struct DefineClipScrollNodeItem {
|
||||
/// Fields common to all display items.
|
||||
pub base: BaseDisplayItem,
|
||||
|
||||
/// The scroll root that this item starts.
|
||||
pub node_index: ClipScrollNodeIndex,
|
||||
}
|
||||
|
||||
impl DisplayItem {
|
||||
pub fn base(&self) -> &BaseDisplayItem {
|
||||
match *self {
|
||||
DisplayItem::Rectangle(ref rect) => &rect.base,
|
||||
DisplayItem::Text(ref text) => &text.base,
|
||||
DisplayItem::Image(ref image_item) => &image_item.base,
|
||||
DisplayItem::RepeatingImage(ref image_item) => &image_item.base,
|
||||
DisplayItem::Border(ref border) => &border.base,
|
||||
DisplayItem::Gradient(ref gradient) => &gradient.base,
|
||||
DisplayItem::RadialGradient(ref gradient) => &gradient.base,
|
||||
DisplayItem::Line(ref line) => &line.base,
|
||||
DisplayItem::BoxShadow(ref box_shadow) => &box_shadow.base,
|
||||
DisplayItem::PushTextShadow(ref push_text_shadow) => &push_text_shadow.base,
|
||||
DisplayItem::PopAllTextShadows(ref pop_text_shadow) => &pop_text_shadow.base,
|
||||
DisplayItem::Iframe(ref iframe) => &iframe.base,
|
||||
DisplayItem::PushStackingContext(ref stacking_context) => &stacking_context.base,
|
||||
DisplayItem::PopStackingContext(ref item) => &item.base,
|
||||
DisplayItem::DefineClipScrollNode(ref item) => &item.base,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clipping_and_scrolling(&self) -> ClippingAndScrolling {
|
||||
self.base().clipping_and_scrolling
|
||||
}
|
||||
|
||||
pub fn stacking_context_id(&self) -> StackingContextId {
|
||||
self.base().stacking_context_id
|
||||
}
|
||||
|
||||
pub fn section(&self) -> DisplayListSection {
|
||||
self.base().section
|
||||
}
|
||||
|
||||
pub fn bounds(&self) -> LayoutRect {
|
||||
match *self {
|
||||
DisplayItem::Rectangle(ref item) => item.item.common.clip_rect,
|
||||
DisplayItem::Text(ref item) => item.item.bounds,
|
||||
DisplayItem::Image(ref item) => item.item.bounds,
|
||||
DisplayItem::RepeatingImage(ref item) => item.item.bounds,
|
||||
DisplayItem::Border(ref item) => item.item.bounds,
|
||||
DisplayItem::Gradient(ref item) => item.item.bounds,
|
||||
DisplayItem::RadialGradient(ref item) => item.item.bounds,
|
||||
DisplayItem::Line(ref item) => item.item.area,
|
||||
DisplayItem::BoxShadow(ref item) => item.item.box_bounds,
|
||||
DisplayItem::PushTextShadow(_) => LayoutRect::zero(),
|
||||
DisplayItem::PopAllTextShadows(_) => LayoutRect::zero(),
|
||||
DisplayItem::Iframe(ref item) => item.bounds,
|
||||
DisplayItem::PushStackingContext(ref item) => item.stacking_context.bounds,
|
||||
DisplayItem::PopStackingContext(_) => LayoutRect::zero(),
|
||||
DisplayItem::DefineClipScrollNode(_) => LayoutRect::zero(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for DisplayItem {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
if let DisplayItem::PushStackingContext(ref item) = *self {
|
||||
return write!(f, "PushStackingContext({:?})", item.stacking_context);
|
||||
}
|
||||
|
||||
if let DisplayItem::PopStackingContext(ref item) = *self {
|
||||
return write!(f, "PopStackingContext({:?}", item.stacking_context_id);
|
||||
}
|
||||
|
||||
if let DisplayItem::DefineClipScrollNode(ref item) = *self {
|
||||
return write!(f, "DefineClipScrollNode({:?}", item.node_index);
|
||||
}
|
||||
|
||||
write!(
|
||||
f,
|
||||
"{} @ {:?} {:?}",
|
||||
match *self {
|
||||
DisplayItem::Rectangle(_) => "Rectangle",
|
||||
DisplayItem::Text(_) => "Text",
|
||||
DisplayItem::Image(_) => "Image",
|
||||
DisplayItem::RepeatingImage(_) => "RepeatingImage",
|
||||
DisplayItem::Border(_) => "Border",
|
||||
DisplayItem::Gradient(_) => "Gradient",
|
||||
DisplayItem::RadialGradient(_) => "RadialGradient",
|
||||
DisplayItem::Line(_) => "Line",
|
||||
DisplayItem::BoxShadow(_) => "BoxShadow",
|
||||
DisplayItem::PushTextShadow(_) => "PushTextShadow",
|
||||
DisplayItem::PopAllTextShadows(_) => "PopTextShadow",
|
||||
DisplayItem::Iframe(_) => "Iframe",
|
||||
DisplayItem::PushStackingContext(_) |
|
||||
DisplayItem::PopStackingContext(_) |
|
||||
DisplayItem::DefineClipScrollNode(_) => "",
|
||||
},
|
||||
self.bounds(),
|
||||
self.base().clip_rect
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Serialize)]
|
||||
pub struct WebRenderImageInfo {
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub key: Option<ImageKey>,
|
||||
}
|
||||
|
||||
impl WebRenderImageInfo {
|
||||
#[inline]
|
||||
pub fn from_image(image: &Image) -> WebRenderImageInfo {
|
||||
WebRenderImageInfo {
|
||||
width: image.width,
|
||||
height: image.height,
|
||||
key: image.id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The type of the scroll offset list. This is only populated if WebRender is in use.
|
||||
pub type ScrollOffsetMap = HashMap<ExternalScrollId, Vector2D<f32, LayoutPixel>>;
|
|
@ -1,41 +0,0 @@
|
|||
/* 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 malloc_size_of_derive::MallocSizeOf;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub use self::builder::{
|
||||
BorderPaintingMode, DisplayListBuildState, IndexableText, StackingContextCollectionFlags,
|
||||
StackingContextCollectionState,
|
||||
};
|
||||
pub use self::conversions::{FilterToLayout, ToLayout};
|
||||
|
||||
mod background;
|
||||
mod border;
|
||||
mod builder;
|
||||
pub(crate) mod conversions;
|
||||
mod gradient;
|
||||
pub mod items;
|
||||
mod webrender_helpers;
|
||||
|
||||
/// A unique ID for every stacking context.
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
|
||||
pub struct StackingContextId(
|
||||
/// The identifier for this StackingContext, derived from the Flow's memory address
|
||||
/// and fragment type. As a space optimization, these are combined into a single word.
|
||||
pub u64,
|
||||
);
|
||||
|
||||
impl StackingContextId {
|
||||
/// Returns the stacking context ID for the outer document/layout root.
|
||||
#[inline]
|
||||
pub fn root() -> StackingContextId {
|
||||
StackingContextId(0)
|
||||
}
|
||||
|
||||
pub fn next(&self) -> StackingContextId {
|
||||
let StackingContextId(id) = *self;
|
||||
StackingContextId(id + 1)
|
||||
}
|
||||
}
|
|
@ -1,553 +0,0 @@
|
|||
/* 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/. */
|
||||
|
||||
// TODO(gw): This contains helper traits and implementations for converting Servo display lists
|
||||
// into WebRender display lists. In the future, this step should be completely removed.
|
||||
// This might be achieved by sharing types between WR and Servo display lists, or
|
||||
// completely converting layout to directly generate WebRender display lists, for example.
|
||||
|
||||
use base::WebRenderEpochToU16;
|
||||
use base::id::PipelineId;
|
||||
use log::trace;
|
||||
use webrender_api::units::{LayoutPoint, LayoutSize, LayoutVector2D};
|
||||
use webrender_api::{
|
||||
self, ClipChainId, ClipId, CommonItemProperties, DisplayItem as WrDisplayItem,
|
||||
DisplayListBuilder, Epoch, HasScrollLinkedEffect, PrimitiveFlags, PropertyBinding, RasterSpace,
|
||||
ReferenceFrameKind, SpaceAndClipInfo, SpatialId, SpatialTreeItemKey,
|
||||
};
|
||||
use webrender_traits::display_list::{
|
||||
AxesScrollSensitivity, CompositorDisplayListInfo, ScrollSensitivity, ScrollTreeNodeId,
|
||||
ScrollableNodeInfo,
|
||||
};
|
||||
|
||||
use crate::display_list::items::{
|
||||
BaseDisplayItem, ClipScrollNode, ClipScrollNodeType, ClipType, DisplayItem, DisplayList,
|
||||
StackingContextType,
|
||||
};
|
||||
|
||||
struct ClipScrollState<'a> {
|
||||
clip_scroll_nodes: &'a mut Vec<ClipScrollNode>,
|
||||
compositor_info: CompositorDisplayListInfo,
|
||||
stacking_context_offset: Vec<LayoutVector2D>,
|
||||
}
|
||||
|
||||
impl<'a> ClipScrollState<'a> {
|
||||
fn new(
|
||||
clip_scroll_nodes: &'a mut Vec<ClipScrollNode>,
|
||||
compositor_info: CompositorDisplayListInfo,
|
||||
) -> Self {
|
||||
let mut state = ClipScrollState {
|
||||
clip_scroll_nodes,
|
||||
compositor_info,
|
||||
stacking_context_offset: Vec::new(),
|
||||
};
|
||||
|
||||
// We need to register the WebRender root reference frame and root scroll node ids
|
||||
// here manually, because WebRender and the CompositorDisplayListInfo create them
|
||||
// automatically. We also follow the "old" WebRender API for clip/scroll for now,
|
||||
// hence both arrays are initialized based on FIRST_SPATIAL_NODE_INDEX, while
|
||||
// FIRST_CLIP_NODE_INDEX is not taken into account.
|
||||
state.clip_scroll_nodes[0].scroll_node_id =
|
||||
Some(state.compositor_info.root_reference_frame_id);
|
||||
state.clip_scroll_nodes[1].scroll_node_id = Some(state.compositor_info.root_scroll_node_id);
|
||||
|
||||
let root_clip_chain = ClipChainId::INVALID;
|
||||
state.add_clip_node_mapping(0, root_clip_chain);
|
||||
state.add_clip_node_mapping(1, root_clip_chain);
|
||||
|
||||
state
|
||||
}
|
||||
|
||||
fn webrender_clip_id_for_index(&mut self, index: usize) -> ClipChainId {
|
||||
self.clip_scroll_nodes[index]
|
||||
.clip_chain_id
|
||||
.expect("Tried to access WebRender ClipId before definining it.")
|
||||
}
|
||||
|
||||
fn webrender_spatial_id_for_index(&mut self, index: usize) -> SpatialId {
|
||||
self.clip_scroll_nodes[index]
|
||||
.scroll_node_id
|
||||
.expect("Tried to use WebRender parent SpatialId before it was defined.")
|
||||
.spatial_id
|
||||
}
|
||||
|
||||
fn add_clip_node_mapping(&mut self, index: usize, webrender_id: ClipChainId) {
|
||||
self.clip_scroll_nodes[index].clip_chain_id = Some(webrender_id);
|
||||
}
|
||||
|
||||
fn scroll_node_id_from_index(&self, index: usize) -> ScrollTreeNodeId {
|
||||
self.clip_scroll_nodes[index]
|
||||
.scroll_node_id
|
||||
.expect("Tried to use WebRender parent SpatialId before it was defined.")
|
||||
}
|
||||
|
||||
fn register_spatial_node(
|
||||
&mut self,
|
||||
index: usize,
|
||||
spatial_id: SpatialId,
|
||||
parent_index: Option<usize>,
|
||||
scroll_info: Option<ScrollableNodeInfo>,
|
||||
) {
|
||||
let parent_scroll_node_id = parent_index.map(|index| self.scroll_node_id_from_index(index));
|
||||
self.clip_scroll_nodes[index].scroll_node_id =
|
||||
Some(self.compositor_info.scroll_tree.add_scroll_tree_node(
|
||||
parent_scroll_node_id.as_ref(),
|
||||
spatial_id,
|
||||
scroll_info,
|
||||
));
|
||||
}
|
||||
|
||||
fn add_spatial_node_mapping_to_parent_index(&mut self, index: usize, parent_index: usize) {
|
||||
self.clip_scroll_nodes[index].scroll_node_id =
|
||||
self.clip_scroll_nodes[parent_index].scroll_node_id
|
||||
}
|
||||
|
||||
pub fn define_clip_chain<I>(
|
||||
&self,
|
||||
builder: &mut DisplayListBuilder,
|
||||
parent: ClipChainId,
|
||||
clips: I,
|
||||
) -> ClipChainId
|
||||
where
|
||||
I: IntoIterator<Item = ClipId>,
|
||||
I::IntoIter: ExactSizeIterator + Clone,
|
||||
{
|
||||
// We use INVALID to mean "no clipping", but that cannot be passed as an argument
|
||||
// to `define_clip_chain()`, so it must be converted into `None`.
|
||||
let parent = match parent {
|
||||
ClipChainId::INVALID => None,
|
||||
parent => Some(parent),
|
||||
};
|
||||
builder.define_clip_chain(parent, clips)
|
||||
}
|
||||
|
||||
fn stacking_context_offset(&self) -> LayoutVector2D {
|
||||
self.stacking_context_offset
|
||||
.last()
|
||||
.cloned()
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn push_stacking_context_offset(&mut self, offset: LayoutVector2D) {
|
||||
self.stacking_context_offset.push(offset);
|
||||
}
|
||||
|
||||
fn pop_stacking_context_offset(&mut self) {
|
||||
self.stacking_context_offset.pop();
|
||||
}
|
||||
}
|
||||
|
||||
/// Contentful paint, for the purpose of
|
||||
/// <https://w3c.github.io/paint-timing/#first-contentful-paint>
|
||||
/// (i.e. the display list contains items of type text,
|
||||
/// image, non-white canvas or SVG). Used by metrics.
|
||||
pub struct IsContentful(pub bool);
|
||||
|
||||
impl DisplayList {
|
||||
pub fn convert_to_webrender(
|
||||
&mut self,
|
||||
pipeline_id: PipelineId,
|
||||
viewport_size: LayoutSize,
|
||||
epoch: Epoch,
|
||||
dump_display_list: bool,
|
||||
) -> (DisplayListBuilder, CompositorDisplayListInfo, IsContentful) {
|
||||
let webrender_pipeline = pipeline_id.into();
|
||||
let mut builder = DisplayListBuilder::new(webrender_pipeline);
|
||||
builder.begin();
|
||||
|
||||
if dump_display_list {
|
||||
builder.dump_serialized_display_list();
|
||||
}
|
||||
|
||||
let content_size = self.bounds().size();
|
||||
let mut state = ClipScrollState::new(
|
||||
&mut self.clip_scroll_nodes,
|
||||
CompositorDisplayListInfo::new(
|
||||
viewport_size,
|
||||
content_size,
|
||||
webrender_pipeline,
|
||||
epoch,
|
||||
AxesScrollSensitivity {
|
||||
x: ScrollSensitivity::ScriptAndInputEvents,
|
||||
y: ScrollSensitivity::ScriptAndInputEvents,
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
let mut is_contentful = IsContentful(false);
|
||||
for item in &mut self.list {
|
||||
is_contentful.0 |= item.convert_to_webrender(&mut state, &mut builder).0;
|
||||
}
|
||||
|
||||
(builder, state.compositor_info, is_contentful)
|
||||
}
|
||||
}
|
||||
|
||||
impl DisplayItem {
|
||||
fn get_spatial_tree_item_key(
|
||||
&self,
|
||||
builder: &DisplayListBuilder,
|
||||
node_index: usize,
|
||||
) -> SpatialTreeItemKey {
|
||||
let pipeline_tag = ((builder.pipeline_id.0 as u64) << 32) | builder.pipeline_id.1 as u64;
|
||||
SpatialTreeItemKey::new(pipeline_tag, node_index as u64)
|
||||
}
|
||||
|
||||
fn convert_to_webrender(
|
||||
&mut self,
|
||||
state: &mut ClipScrollState,
|
||||
builder: &mut DisplayListBuilder,
|
||||
) -> IsContentful {
|
||||
// Note: for each time of a display item, if we register one of `clip_ids` or `spatial_ids`,
|
||||
// we also register the other one as inherited from the current state or the stack.
|
||||
// This is not an ideal behavior, but it is compatible with the old WebRender model
|
||||
// of the clip-scroll tree.
|
||||
|
||||
let clip_and_scroll_indices = self.base().clipping_and_scrolling;
|
||||
trace!("converting {:?}", clip_and_scroll_indices);
|
||||
|
||||
let current_scrolling_index = clip_and_scroll_indices.scrolling.to_index();
|
||||
let current_scroll_node_id = state.scroll_node_id_from_index(current_scrolling_index);
|
||||
|
||||
let internal_clip_id = clip_and_scroll_indices
|
||||
.clipping
|
||||
.unwrap_or(clip_and_scroll_indices.scrolling);
|
||||
let current_clip_chain_id = state.webrender_clip_id_for_index(internal_clip_id.to_index());
|
||||
let hit_test_bounds = self.bounds().intersection(&self.base().clip_rect);
|
||||
let stacking_context_offset = state.stacking_context_offset();
|
||||
|
||||
let build_common_item_properties = |base: &BaseDisplayItem| {
|
||||
CommonItemProperties {
|
||||
clip_rect: base.clip_rect.translate(stacking_context_offset),
|
||||
spatial_id: current_scroll_node_id.spatial_id,
|
||||
clip_chain_id: current_clip_chain_id,
|
||||
// TODO(gw): Make use of the WR backface visibility functionality.
|
||||
flags: PrimitiveFlags::default(),
|
||||
}
|
||||
};
|
||||
|
||||
let mut push_hit_test = |base: &BaseDisplayItem| {
|
||||
let bounds = match hit_test_bounds {
|
||||
Some(bounds) => bounds,
|
||||
None => return,
|
||||
}
|
||||
.translate(stacking_context_offset);
|
||||
|
||||
let cursor = match base.metadata.cursor {
|
||||
Some(cursor) => cursor,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let hit_test_index = state.compositor_info.add_hit_test_info(
|
||||
base.metadata.node.0 as u64,
|
||||
Some(cursor),
|
||||
current_scroll_node_id,
|
||||
);
|
||||
|
||||
builder.push_hit_test(
|
||||
bounds,
|
||||
current_clip_chain_id,
|
||||
current_scroll_node_id.spatial_id,
|
||||
PrimitiveFlags::default(),
|
||||
(hit_test_index as u64, state.compositor_info.epoch.as_u16()),
|
||||
);
|
||||
};
|
||||
|
||||
match *self {
|
||||
DisplayItem::Rectangle(ref mut item) => {
|
||||
let mut rect_item = item.item;
|
||||
rect_item.common = build_common_item_properties(&item.base);
|
||||
rect_item.bounds = item.item.bounds.translate(stacking_context_offset);
|
||||
|
||||
push_hit_test(&item.base);
|
||||
builder.push_item(&WrDisplayItem::Rectangle(rect_item));
|
||||
IsContentful(false)
|
||||
},
|
||||
DisplayItem::Text(ref mut item) => {
|
||||
let mut text_item = item.item;
|
||||
text_item.bounds = text_item.bounds.translate(stacking_context_offset);
|
||||
text_item.common = build_common_item_properties(&item.base);
|
||||
|
||||
push_hit_test(&item.base);
|
||||
builder.push_text(
|
||||
&text_item.common,
|
||||
text_item.bounds,
|
||||
&item.data,
|
||||
text_item.font_key,
|
||||
text_item.color,
|
||||
text_item.glyph_options,
|
||||
);
|
||||
IsContentful(true)
|
||||
},
|
||||
DisplayItem::Image(ref mut item) => {
|
||||
let mut image_item = item.item;
|
||||
image_item.common = build_common_item_properties(&item.base);
|
||||
image_item.bounds = item.item.bounds.translate(stacking_context_offset);
|
||||
|
||||
push_hit_test(&item.base);
|
||||
builder.push_item(&WrDisplayItem::Image(image_item));
|
||||
IsContentful(true)
|
||||
},
|
||||
DisplayItem::RepeatingImage(ref mut item) => {
|
||||
let mut image_item = item.item;
|
||||
image_item.common = build_common_item_properties(&item.base);
|
||||
image_item.bounds = item.item.bounds.translate(stacking_context_offset);
|
||||
|
||||
push_hit_test(&item.base);
|
||||
builder.push_item(&WrDisplayItem::RepeatingImage(image_item));
|
||||
IsContentful(true)
|
||||
},
|
||||
DisplayItem::Border(ref mut item) => {
|
||||
let mut border_item = item.item;
|
||||
border_item.common = build_common_item_properties(&item.base);
|
||||
border_item.bounds = item.item.bounds.translate(stacking_context_offset);
|
||||
|
||||
push_hit_test(&item.base);
|
||||
if !item.data.is_empty() {
|
||||
builder.push_stops(item.data.as_ref());
|
||||
}
|
||||
builder.push_item(&WrDisplayItem::Border(border_item));
|
||||
IsContentful(false)
|
||||
},
|
||||
DisplayItem::Gradient(ref mut item) => {
|
||||
let mut gradient_item = item.item;
|
||||
gradient_item.common = build_common_item_properties(&item.base);
|
||||
gradient_item.bounds = item.item.bounds.translate(stacking_context_offset);
|
||||
|
||||
push_hit_test(&item.base);
|
||||
builder.push_stops(item.data.as_ref());
|
||||
builder.push_item(&WrDisplayItem::Gradient(gradient_item));
|
||||
IsContentful(false)
|
||||
},
|
||||
DisplayItem::RadialGradient(ref mut item) => {
|
||||
let mut gradient_item = item.item;
|
||||
gradient_item.common = build_common_item_properties(&item.base);
|
||||
gradient_item.bounds = item.item.bounds.translate(stacking_context_offset);
|
||||
|
||||
push_hit_test(&item.base);
|
||||
builder.push_stops(item.data.as_ref());
|
||||
builder.push_item(&WrDisplayItem::RadialGradient(gradient_item));
|
||||
IsContentful(false)
|
||||
},
|
||||
DisplayItem::Line(ref mut item) => {
|
||||
let mut line_item = item.item;
|
||||
line_item.common = build_common_item_properties(&item.base);
|
||||
line_item.area = item.item.area.translate(stacking_context_offset);
|
||||
|
||||
push_hit_test(&item.base);
|
||||
builder.push_item(&WrDisplayItem::Line(line_item));
|
||||
IsContentful(false)
|
||||
},
|
||||
DisplayItem::BoxShadow(ref mut item) => {
|
||||
let mut shadow_item = item.item;
|
||||
shadow_item.common = build_common_item_properties(&item.base);
|
||||
shadow_item.box_bounds = item.item.box_bounds.translate(stacking_context_offset);
|
||||
|
||||
push_hit_test(&item.base);
|
||||
builder.push_item(&WrDisplayItem::BoxShadow(shadow_item));
|
||||
IsContentful(false)
|
||||
},
|
||||
DisplayItem::PushTextShadow(ref mut item) => {
|
||||
let common = build_common_item_properties(&item.base);
|
||||
|
||||
push_hit_test(&item.base);
|
||||
builder.push_shadow(
|
||||
&SpaceAndClipInfo {
|
||||
spatial_id: common.spatial_id,
|
||||
clip_chain_id: common.clip_chain_id,
|
||||
},
|
||||
item.shadow,
|
||||
true,
|
||||
);
|
||||
IsContentful(false)
|
||||
},
|
||||
DisplayItem::PopAllTextShadows(_) => {
|
||||
builder.push_item(&WrDisplayItem::PopAllShadows);
|
||||
IsContentful(false)
|
||||
},
|
||||
DisplayItem::Iframe(ref mut item) => {
|
||||
let common = build_common_item_properties(&item.base);
|
||||
push_hit_test(&item.base);
|
||||
builder.push_iframe(
|
||||
item.bounds.translate(stacking_context_offset),
|
||||
common.clip_rect,
|
||||
&SpaceAndClipInfo {
|
||||
spatial_id: common.spatial_id,
|
||||
clip_chain_id: common.clip_chain_id,
|
||||
},
|
||||
item.iframe.into(),
|
||||
true,
|
||||
);
|
||||
IsContentful(false)
|
||||
},
|
||||
DisplayItem::PushStackingContext(ref item) => {
|
||||
let stacking_context = &item.stacking_context;
|
||||
debug_assert_eq!(stacking_context.context_type, StackingContextType::Real);
|
||||
|
||||
//let mut info = LayoutPrimitiveInfo::new(stacking_context.bounds);
|
||||
let mut bounds = stacking_context.bounds;
|
||||
let spatial_id =
|
||||
if let Some(frame_index) = stacking_context.established_reference_frame {
|
||||
let (transform, ref_frame) =
|
||||
match (stacking_context.transform, stacking_context.perspective) {
|
||||
(None, Some(p)) => (
|
||||
p,
|
||||
ReferenceFrameKind::Perspective {
|
||||
scrolling_relative_to: None,
|
||||
},
|
||||
),
|
||||
(Some(t), None) => (
|
||||
t,
|
||||
ReferenceFrameKind::Transform {
|
||||
is_2d_scale_translation: false,
|
||||
should_snap: false,
|
||||
paired_with_perspective: false,
|
||||
},
|
||||
),
|
||||
(Some(t), Some(p)) => (
|
||||
p.then(&t),
|
||||
ReferenceFrameKind::Perspective {
|
||||
scrolling_relative_to: None,
|
||||
},
|
||||
),
|
||||
(None, None) => unreachable!(),
|
||||
};
|
||||
|
||||
let index = frame_index.to_index();
|
||||
let new_spatial_id = builder.push_reference_frame(
|
||||
stacking_context.bounds.min + state.stacking_context_offset(),
|
||||
current_scroll_node_id.spatial_id,
|
||||
stacking_context.transform_style,
|
||||
PropertyBinding::Value(transform),
|
||||
ref_frame,
|
||||
self.get_spatial_tree_item_key(builder, index),
|
||||
);
|
||||
|
||||
state.add_clip_node_mapping(index, current_clip_chain_id);
|
||||
state.register_spatial_node(
|
||||
index,
|
||||
new_spatial_id,
|
||||
Some(current_scrolling_index),
|
||||
None,
|
||||
);
|
||||
|
||||
bounds.min = LayoutPoint::zero();
|
||||
new_spatial_id
|
||||
} else {
|
||||
current_scroll_node_id.spatial_id
|
||||
};
|
||||
|
||||
// TODO(jdm): WebRender now requires us to create stacking context items
|
||||
// with the IS_BLEND_CONTAINER flag enabled if any children
|
||||
// of the stacking context have a blend mode applied.
|
||||
// This will require additional tracking during layout
|
||||
// before we start collecting stacking contexts so that
|
||||
// information will be available when we reach this point.
|
||||
state.push_stacking_context_offset(
|
||||
(bounds.min + state.stacking_context_offset()).to_vector(),
|
||||
);
|
||||
builder.push_stacking_context(
|
||||
LayoutPoint::zero(),
|
||||
spatial_id,
|
||||
PrimitiveFlags::default(),
|
||||
None,
|
||||
stacking_context.transform_style,
|
||||
stacking_context.mix_blend_mode,
|
||||
&stacking_context.filters,
|
||||
&[],
|
||||
&[],
|
||||
RasterSpace::Screen,
|
||||
Default::default(),
|
||||
None, // snapshot
|
||||
);
|
||||
|
||||
IsContentful(false)
|
||||
},
|
||||
DisplayItem::PopStackingContext(ref item) => {
|
||||
state.pop_stacking_context_offset();
|
||||
builder.pop_stacking_context();
|
||||
if item.established_reference_frame {
|
||||
builder.pop_reference_frame();
|
||||
}
|
||||
IsContentful(false)
|
||||
},
|
||||
DisplayItem::DefineClipScrollNode(ref mut item) => {
|
||||
let index = item.node_index.to_index();
|
||||
let node = state.clip_scroll_nodes[index].clone();
|
||||
let item_rect = node.clip.main.translate(stacking_context_offset);
|
||||
|
||||
let parent_index = node.parent_index.to_index();
|
||||
let parent_spatial_id = state.webrender_spatial_id_for_index(parent_index);
|
||||
let parent_clip_chain_id = state.webrender_clip_id_for_index(parent_index);
|
||||
|
||||
match node.node_type {
|
||||
ClipScrollNodeType::Clip(clip_type) => {
|
||||
let clip_id = match clip_type {
|
||||
ClipType::Rect => {
|
||||
builder.define_clip_rect(parent_spatial_id, item_rect)
|
||||
},
|
||||
ClipType::Rounded(complex) => {
|
||||
builder.define_clip_rounded_rect(parent_spatial_id, complex)
|
||||
},
|
||||
};
|
||||
|
||||
let clip_chain_id =
|
||||
state.define_clip_chain(builder, parent_clip_chain_id, [clip_id]);
|
||||
state.add_clip_node_mapping(index, clip_chain_id);
|
||||
state.add_spatial_node_mapping_to_parent_index(index, parent_index);
|
||||
},
|
||||
ClipScrollNodeType::ScrollFrame(scroll_sensitivity, external_id) => {
|
||||
let clip_id = builder.define_clip_rect(parent_spatial_id, item_rect);
|
||||
let clip_chain_id =
|
||||
state.define_clip_chain(builder, parent_clip_chain_id, [clip_id]);
|
||||
state.add_clip_node_mapping(index, clip_chain_id);
|
||||
|
||||
let spatial_id = builder.define_scroll_frame(
|
||||
parent_spatial_id,
|
||||
external_id,
|
||||
node.content_rect,
|
||||
item_rect,
|
||||
LayoutVector2D::zero(), /* external_scroll_offset */
|
||||
0, /* scroll_offset_generation */
|
||||
HasScrollLinkedEffect::No,
|
||||
self.get_spatial_tree_item_key(builder, index),
|
||||
);
|
||||
|
||||
state.register_spatial_node(
|
||||
index,
|
||||
spatial_id,
|
||||
Some(parent_index),
|
||||
Some(ScrollableNodeInfo {
|
||||
external_id,
|
||||
scrollable_size: node.content_rect.size() - item_rect.size(),
|
||||
scroll_sensitivity,
|
||||
offset: LayoutVector2D::zero(),
|
||||
}),
|
||||
);
|
||||
},
|
||||
ClipScrollNodeType::StickyFrame(ref sticky_data) => {
|
||||
// TODO: Add define_sticky_frame_with_parent to WebRender.
|
||||
let id = builder.define_sticky_frame(
|
||||
parent_spatial_id,
|
||||
item_rect,
|
||||
sticky_data.margins,
|
||||
sticky_data.vertical_offset_bounds,
|
||||
sticky_data.horizontal_offset_bounds,
|
||||
LayoutVector2D::zero(), /* previously_applied_offset */
|
||||
self.get_spatial_tree_item_key(builder, index),
|
||||
None, /* transform */
|
||||
);
|
||||
|
||||
state.add_clip_node_mapping(index, parent_clip_chain_id);
|
||||
state.register_spatial_node(index, id, Some(current_scrolling_index), None);
|
||||
},
|
||||
ClipScrollNodeType::Placeholder => {
|
||||
unreachable!("Found DefineClipScrollNode for Placeholder type node.");
|
||||
},
|
||||
};
|
||||
IsContentful(false)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue