aboutsummaryrefslogtreecommitdiffstats
path: root/components/layout/display_list/clip.rs
diff options
context:
space:
mode:
Diffstat (limited to 'components/layout/display_list/clip.rs')
-rw-r--r--components/layout/display_list/clip.rs276
1 files changed, 276 insertions, 0 deletions
diff --git a/components/layout/display_list/clip.rs b/components/layout/display_list/clip.rs
new file mode 100644
index 00000000000..d5bd0f52b69
--- /dev/null
+++ b/components/layout/display_list/clip.rs
@@ -0,0 +1,276 @@
+/* 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 base::id::ScrollTreeNodeId;
+use style::values::computed::basic_shape::{BasicShape, ClipPath};
+use style::values::computed::length_percentage::NonNegativeLengthPercentage;
+use style::values::computed::position::Position;
+use style::values::generics::basic_shape::{GenericShapeRadius, ShapeBox, ShapeGeometryBox};
+use style::values::generics::position::GenericPositionOrAuto;
+use webrender_api::BorderRadius;
+use webrender_api::units::{LayoutRect, LayoutSideOffsets, LayoutSize};
+
+use super::{BuilderForBoxFragment, compute_margin_box_radius, normalize_radii};
+
+/// An identifier for a clip used during StackingContextTree construction. This is a simple index in
+/// a [`ClipStore`]s vector of clips.
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub(crate) struct ClipId(pub usize);
+
+impl ClipId {
+ /// Equivalent to [`ClipChainId::INVALID`]. This means "no clip."
+ pub(crate) const INVALID: ClipId = ClipId(usize::MAX);
+}
+
+/// All the information needed to create a clip on a WebRender display list. These are created at
+/// two times: during `StackingContextTree` creation and during WebRender display list construction.
+/// Only the former are stored in a [`ClipStore`].
+#[derive(Clone)]
+pub(crate) struct Clip {
+ pub id: ClipId,
+ pub radii: BorderRadius,
+ pub rect: LayoutRect,
+ pub parent_scroll_node_id: ScrollTreeNodeId,
+ pub parent_clip_id: ClipId,
+}
+
+/// A simple vector of [`Clip`] that is built during `StackingContextTree` construction.
+/// These are later turned into WebRender clips and clip chains during WebRender display
+/// list construction.
+#[derive(Clone, Default)]
+pub(crate) struct StackingContextTreeClipStore(pub Vec<Clip>);
+
+impl StackingContextTreeClipStore {
+ pub(crate) fn add(
+ &mut self,
+ radii: webrender_api::BorderRadius,
+ rect: LayoutRect,
+ parent_scroll_node_id: ScrollTreeNodeId,
+ parent_clip_id: ClipId,
+ ) -> ClipId {
+ let id = ClipId(self.0.len());
+ self.0.push(Clip {
+ id,
+ radii,
+ rect,
+ parent_scroll_node_id,
+ parent_clip_id,
+ });
+ id
+ }
+
+ pub(super) fn add_for_clip_path(
+ &mut self,
+ clip_path: ClipPath,
+ parent_scroll_node_id: &ScrollTreeNodeId,
+ parent_clip_chain_id: &ClipId,
+ fragment_builder: BuilderForBoxFragment,
+ ) -> Option<ClipId> {
+ let geometry_box = match clip_path {
+ ClipPath::Shape(_, ShapeGeometryBox::ShapeBox(shape_box)) => shape_box,
+ ClipPath::Shape(_, ShapeGeometryBox::ElementDependent) => ShapeBox::BorderBox,
+ ClipPath::Box(ShapeGeometryBox::ShapeBox(shape_box)) => shape_box,
+ ClipPath::Box(ShapeGeometryBox::ElementDependent) => ShapeBox::BorderBox,
+ _ => return None,
+ };
+ let layout_rect = match geometry_box {
+ ShapeBox::BorderBox => fragment_builder.border_rect,
+ ShapeBox::ContentBox => *fragment_builder.content_rect(),
+ ShapeBox::PaddingBox => *fragment_builder.padding_rect(),
+ ShapeBox::MarginBox => *fragment_builder.margin_rect(),
+ };
+ if let ClipPath::Shape(shape, _) = clip_path {
+ match *shape {
+ BasicShape::Circle(_) | BasicShape::Ellipse(_) | BasicShape::Rect(_) => self
+ .add_for_basic_shape(
+ *shape,
+ layout_rect,
+ parent_scroll_node_id,
+ parent_clip_chain_id,
+ ),
+ BasicShape::Polygon(_) | BasicShape::PathOrShape(_) => None,
+ }
+ } else {
+ Some(self.add(
+ match geometry_box {
+ ShapeBox::MarginBox => compute_margin_box_radius(
+ fragment_builder.border_radius,
+ layout_rect.size(),
+ fragment_builder.fragment,
+ ),
+ _ => fragment_builder.border_radius,
+ },
+ layout_rect,
+ *parent_scroll_node_id,
+ *parent_clip_chain_id,
+ ))
+ }
+ }
+
+ #[cfg_attr(
+ feature = "tracing",
+ tracing::instrument(
+ name = "StackingContextClipStore::add_for_basic_shape",
+ skip_all,
+ fields(servo_profiling = true),
+ level = "trace",
+ )
+ )]
+ fn add_for_basic_shape(
+ &mut self,
+ shape: BasicShape,
+ layout_box: LayoutRect,
+ parent_scroll_node_id: &ScrollTreeNodeId,
+ parent_clip_chain_id: &ClipId,
+ ) -> Option<ClipId> {
+ match shape {
+ BasicShape::Rect(rect) => {
+ let box_height = Au::from_f32_px(layout_box.height());
+ let box_width = Au::from_f32_px(layout_box.width());
+ let insets = LayoutSideOffsets::new(
+ rect.rect.0.to_used_value(box_height).to_f32_px(),
+ rect.rect.1.to_used_value(box_width).to_f32_px(),
+ rect.rect.2.to_used_value(box_height).to_f32_px(),
+ rect.rect.3.to_used_value(box_width).to_f32_px(),
+ );
+
+ // `inner_rect()` will cause an assertion failure if the insets are larger than the
+ // rectangle dimension.
+ let shape_rect = if insets.left + insets.right >= layout_box.width() ||
+ insets.top + insets.bottom > layout_box.height()
+ {
+ LayoutRect::from_origin_and_size(layout_box.min, LayoutSize::zero())
+ } else {
+ layout_box.to_rect().inner_rect(insets).to_box2d()
+ };
+
+ let corner = |corner: &style::values::computed::BorderCornerRadius| {
+ LayoutSize::new(
+ corner.0.width.0.to_used_value(box_width).to_f32_px(),
+ corner.0.height.0.to_used_value(box_height).to_f32_px(),
+ )
+ };
+ let mut radii = webrender_api::BorderRadius {
+ top_left: corner(&rect.round.top_left),
+ top_right: corner(&rect.round.top_right),
+ bottom_left: corner(&rect.round.bottom_left),
+ bottom_right: corner(&rect.round.bottom_right),
+ };
+ normalize_radii(&layout_box, &mut radii);
+ Some(self.add(
+ radii,
+ shape_rect,
+ *parent_scroll_node_id,
+ *parent_clip_chain_id,
+ ))
+ },
+ BasicShape::Circle(circle) => {
+ let center = match circle.position {
+ GenericPositionOrAuto::Position(position) => position,
+ GenericPositionOrAuto::Auto => Position::center(),
+ };
+ let anchor_x = center
+ .horizontal
+ .to_used_value(Au::from_f32_px(layout_box.width()));
+ let anchor_y = center
+ .vertical
+ .to_used_value(Au::from_f32_px(layout_box.height()));
+ let center = layout_box
+ .min
+ .add_size(&LayoutSize::new(anchor_x.to_f32_px(), anchor_y.to_f32_px()));
+
+ let horizontal = compute_shape_radius(
+ center.x,
+ &circle.radius,
+ layout_box.min.x,
+ layout_box.max.x,
+ );
+ let vertical = compute_shape_radius(
+ center.y,
+ &circle.radius,
+ layout_box.min.y,
+ layout_box.max.y,
+ );
+
+ // If the value is `Length` then both values should be the same at this point.
+ let radius = match circle.radius {
+ GenericShapeRadius::FarthestSide => horizontal.max(vertical),
+ GenericShapeRadius::ClosestSide => horizontal.min(vertical),
+ GenericShapeRadius::Length(_) => horizontal,
+ };
+ let radius = LayoutSize::new(radius, radius);
+ let mut radii = webrender_api::BorderRadius {
+ top_left: radius,
+ top_right: radius,
+ bottom_left: radius,
+ bottom_right: radius,
+ };
+ let start = center.add_size(&-radius);
+ let rect = LayoutRect::from_origin_and_size(start, radius * 2.);
+ normalize_radii(&layout_box, &mut radii);
+ Some(self.add(radii, rect, *parent_scroll_node_id, *parent_clip_chain_id))
+ },
+ BasicShape::Ellipse(ellipse) => {
+ let center = match ellipse.position {
+ GenericPositionOrAuto::Position(position) => position,
+ GenericPositionOrAuto::Auto => Position::center(),
+ };
+ let anchor_x = center
+ .horizontal
+ .to_used_value(Au::from_f32_px(layout_box.width()));
+ let anchor_y = center
+ .vertical
+ .to_used_value(Au::from_f32_px(layout_box.height()));
+ let center = layout_box
+ .min
+ .add_size(&LayoutSize::new(anchor_x.to_f32_px(), anchor_y.to_f32_px()));
+
+ let width = compute_shape_radius(
+ center.x,
+ &ellipse.semiaxis_x,
+ layout_box.min.x,
+ layout_box.max.x,
+ );
+ let height = compute_shape_radius(
+ center.y,
+ &ellipse.semiaxis_y,
+ layout_box.min.y,
+ layout_box.max.y,
+ );
+
+ let mut radii = webrender_api::BorderRadius {
+ top_left: LayoutSize::new(width, height),
+ top_right: LayoutSize::new(width, height),
+ bottom_left: LayoutSize::new(width, height),
+ bottom_right: LayoutSize::new(width, height),
+ };
+ let size = LayoutSize::new(width, height);
+ let start = center.add_size(&-size);
+ let rect = LayoutRect::from_origin_and_size(start, size * 2.);
+ normalize_radii(&rect, &mut radii);
+ Some(self.add(radii, rect, *parent_scroll_node_id, *parent_clip_chain_id))
+ },
+ _ => None,
+ }
+ }
+}
+
+fn compute_shape_radius(
+ center: f32,
+ radius: &GenericShapeRadius<NonNegativeLengthPercentage>,
+ min_edge: f32,
+ max_edge: f32,
+) -> f32 {
+ let distance_from_min_edge = (min_edge - center).abs();
+ let distance_from_max_edge = (max_edge - center).abs();
+ match radius {
+ GenericShapeRadius::FarthestSide => distance_from_min_edge.max(distance_from_max_edge),
+ GenericShapeRadius::ClosestSide => distance_from_min_edge.min(distance_from_max_edge),
+ GenericShapeRadius::Length(length) => length
+ .0
+ .to_used_value(Au::from_f32_px(max_edge - min_edge))
+ .to_f32_px(),
+ }
+}