layout/display_list/
clip.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use app_units::Au;
6use base::id::ScrollTreeNodeId;
7use malloc_size_of_derive::MallocSizeOf;
8use style::values::computed::basic_shape::{BasicShape, ClipPath};
9use style::values::computed::length_percentage::NonNegativeLengthPercentage;
10use style::values::computed::position::Position;
11use style::values::generics::basic_shape::{GenericShapeRadius, ShapeBox, ShapeGeometryBox};
12use style::values::generics::position::GenericPositionOrAuto;
13use webrender_api::BorderRadius;
14use webrender_api::units::{LayoutRect, LayoutSideOffsets, LayoutSize};
15
16use super::{BuilderForBoxFragment, compute_margin_box_radius, normalize_radii};
17
18/// An identifier for a clip used during StackingContextTree construction. This is a simple index in
19/// a [`ClipStore`]s vector of clips.
20#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq)]
21pub(crate) struct ClipId(pub usize);
22
23impl ClipId {
24    /// Equivalent to [`ClipChainId::INVALID`]. This means "no clip."
25    pub(crate) const INVALID: ClipId = ClipId(usize::MAX);
26}
27
28/// All the information needed to create a clip on a WebRender display list. These are created at
29/// two times: during `StackingContextTree` creation and during WebRender display list construction.
30/// Only the former are stored in a [`ClipStore`].
31#[derive(Clone, MallocSizeOf)]
32pub(crate) struct Clip {
33    pub id: ClipId,
34    pub radii: BorderRadius,
35    pub rect: LayoutRect,
36    pub parent_scroll_node_id: ScrollTreeNodeId,
37    pub parent_clip_id: ClipId,
38}
39
40/// A simple vector of [`Clip`] that is built during `StackingContextTree` construction.
41/// These are later turned into WebRender clips and clip chains during WebRender display
42/// list construction.
43#[derive(Clone, Default, MallocSizeOf)]
44pub(crate) struct StackingContextTreeClipStore(pub Vec<Clip>);
45
46impl StackingContextTreeClipStore {
47    pub(super) fn get(&self, clip_id: ClipId) -> &Clip {
48        &self.0[clip_id.0]
49    }
50
51    pub(crate) fn add(
52        &mut self,
53        radii: webrender_api::BorderRadius,
54        rect: LayoutRect,
55        parent_scroll_node_id: ScrollTreeNodeId,
56        parent_clip_id: ClipId,
57    ) -> ClipId {
58        let id = ClipId(self.0.len());
59        self.0.push(Clip {
60            id,
61            radii,
62            rect,
63            parent_scroll_node_id,
64            parent_clip_id,
65        });
66        id
67    }
68
69    pub(super) fn add_for_clip_path(
70        &mut self,
71        clip_path: ClipPath,
72        parent_scroll_node_id: &ScrollTreeNodeId,
73        parent_clip_chain_id: &ClipId,
74        fragment_builder: BuilderForBoxFragment,
75    ) -> Option<ClipId> {
76        let geometry_box = match clip_path {
77            ClipPath::Shape(_, ShapeGeometryBox::ShapeBox(shape_box)) => shape_box,
78            ClipPath::Shape(_, ShapeGeometryBox::ElementDependent) => ShapeBox::BorderBox,
79            ClipPath::Box(ShapeGeometryBox::ShapeBox(shape_box)) => shape_box,
80            ClipPath::Box(ShapeGeometryBox::ElementDependent) => ShapeBox::BorderBox,
81            _ => return None,
82        };
83        let layout_rect = match geometry_box {
84            ShapeBox::BorderBox => fragment_builder.border_rect,
85            ShapeBox::ContentBox => *fragment_builder.content_rect(),
86            ShapeBox::PaddingBox => *fragment_builder.padding_rect(),
87            ShapeBox::MarginBox => *fragment_builder.margin_rect(),
88        };
89        if let ClipPath::Shape(shape, _) = clip_path {
90            match *shape {
91                BasicShape::Circle(_) | BasicShape::Ellipse(_) | BasicShape::Rect(_) => self
92                    .add_for_basic_shape(
93                        *shape,
94                        layout_rect,
95                        parent_scroll_node_id,
96                        parent_clip_chain_id,
97                    ),
98                BasicShape::Polygon(_) | BasicShape::PathOrShape(_) => None,
99            }
100        } else {
101            Some(self.add(
102                match geometry_box {
103                    ShapeBox::MarginBox => compute_margin_box_radius(
104                        fragment_builder.border_radius,
105                        layout_rect.size(),
106                        fragment_builder.fragment,
107                    ),
108                    _ => fragment_builder.border_radius,
109                },
110                layout_rect,
111                *parent_scroll_node_id,
112                *parent_clip_chain_id,
113            ))
114        }
115    }
116
117    #[servo_tracing::instrument(name = "StackingContextClipStore::add_for_basic_shape", skip_all)]
118    fn add_for_basic_shape(
119        &mut self,
120        shape: BasicShape,
121        layout_box: LayoutRect,
122        parent_scroll_node_id: &ScrollTreeNodeId,
123        parent_clip_chain_id: &ClipId,
124    ) -> Option<ClipId> {
125        match shape {
126            BasicShape::Rect(rect) => {
127                let box_height = Au::from_f32_px(layout_box.height());
128                let box_width = Au::from_f32_px(layout_box.width());
129                let insets = LayoutSideOffsets::new(
130                    rect.rect.0.to_used_value(box_height).to_f32_px(),
131                    rect.rect.1.to_used_value(box_width).to_f32_px(),
132                    rect.rect.2.to_used_value(box_height).to_f32_px(),
133                    rect.rect.3.to_used_value(box_width).to_f32_px(),
134                );
135
136                // `inner_rect()` will cause an assertion failure if the insets are larger than the
137                // rectangle dimension.
138                let shape_rect = if insets.left + insets.right >= layout_box.width() ||
139                    insets.top + insets.bottom > layout_box.height()
140                {
141                    LayoutRect::from_origin_and_size(layout_box.min, LayoutSize::zero())
142                } else {
143                    layout_box.to_rect().inner_rect(insets).to_box2d()
144                };
145
146                let corner = |corner: &style::values::computed::BorderCornerRadius| {
147                    LayoutSize::new(
148                        corner.0.width.0.to_used_value(box_width).to_f32_px(),
149                        corner.0.height.0.to_used_value(box_height).to_f32_px(),
150                    )
151                };
152                let mut radii = webrender_api::BorderRadius {
153                    top_left: corner(&rect.round.top_left),
154                    top_right: corner(&rect.round.top_right),
155                    bottom_left: corner(&rect.round.bottom_left),
156                    bottom_right: corner(&rect.round.bottom_right),
157                };
158                normalize_radii(&layout_box, &mut radii);
159                Some(self.add(
160                    radii,
161                    shape_rect,
162                    *parent_scroll_node_id,
163                    *parent_clip_chain_id,
164                ))
165            },
166            BasicShape::Circle(circle) => {
167                let center = match circle.position {
168                    GenericPositionOrAuto::Position(position) => position,
169                    GenericPositionOrAuto::Auto => Position::center(),
170                };
171                let anchor_x = center
172                    .horizontal
173                    .to_used_value(Au::from_f32_px(layout_box.width()));
174                let anchor_y = center
175                    .vertical
176                    .to_used_value(Au::from_f32_px(layout_box.height()));
177                let center = layout_box
178                    .min
179                    .add_size(&LayoutSize::new(anchor_x.to_f32_px(), anchor_y.to_f32_px()));
180
181                let horizontal = compute_shape_radius(
182                    center.x,
183                    &circle.radius,
184                    layout_box.min.x,
185                    layout_box.max.x,
186                );
187                let vertical = compute_shape_radius(
188                    center.y,
189                    &circle.radius,
190                    layout_box.min.y,
191                    layout_box.max.y,
192                );
193
194                // If the value is `Length` then both values should be the same at this point.
195                let radius = match circle.radius {
196                    GenericShapeRadius::FarthestSide => horizontal.max(vertical),
197                    GenericShapeRadius::ClosestSide => horizontal.min(vertical),
198                    GenericShapeRadius::Length(_) => horizontal,
199                };
200                let radius = LayoutSize::new(radius, radius);
201                let mut radii = webrender_api::BorderRadius {
202                    top_left: radius,
203                    top_right: radius,
204                    bottom_left: radius,
205                    bottom_right: radius,
206                };
207                let start = center.add_size(&-radius);
208                let rect = LayoutRect::from_origin_and_size(start, radius * 2.);
209                normalize_radii(&layout_box, &mut radii);
210                Some(self.add(radii, rect, *parent_scroll_node_id, *parent_clip_chain_id))
211            },
212            BasicShape::Ellipse(ellipse) => {
213                let center = match ellipse.position {
214                    GenericPositionOrAuto::Position(position) => position,
215                    GenericPositionOrAuto::Auto => Position::center(),
216                };
217                let anchor_x = center
218                    .horizontal
219                    .to_used_value(Au::from_f32_px(layout_box.width()));
220                let anchor_y = center
221                    .vertical
222                    .to_used_value(Au::from_f32_px(layout_box.height()));
223                let center = layout_box
224                    .min
225                    .add_size(&LayoutSize::new(anchor_x.to_f32_px(), anchor_y.to_f32_px()));
226
227                let width = compute_shape_radius(
228                    center.x,
229                    &ellipse.semiaxis_x,
230                    layout_box.min.x,
231                    layout_box.max.x,
232                );
233                let height = compute_shape_radius(
234                    center.y,
235                    &ellipse.semiaxis_y,
236                    layout_box.min.y,
237                    layout_box.max.y,
238                );
239
240                let mut radii = webrender_api::BorderRadius {
241                    top_left: LayoutSize::new(width, height),
242                    top_right: LayoutSize::new(width, height),
243                    bottom_left: LayoutSize::new(width, height),
244                    bottom_right: LayoutSize::new(width, height),
245                };
246                let size = LayoutSize::new(width, height);
247                let start = center.add_size(&-size);
248                let rect = LayoutRect::from_origin_and_size(start, size * 2.);
249                normalize_radii(&rect, &mut radii);
250                Some(self.add(radii, rect, *parent_scroll_node_id, *parent_clip_chain_id))
251            },
252            _ => None,
253        }
254    }
255}
256
257fn compute_shape_radius(
258    center: f32,
259    radius: &GenericShapeRadius<NonNegativeLengthPercentage>,
260    min_edge: f32,
261    max_edge: f32,
262) -> f32 {
263    let distance_from_min_edge = (min_edge - center).abs();
264    let distance_from_max_edge = (max_edge - center).abs();
265    match radius {
266        GenericShapeRadius::FarthestSide => distance_from_min_edge.max(distance_from_max_edge),
267        GenericShapeRadius::ClosestSide => distance_from_min_edge.min(distance_from_max_edge),
268        GenericShapeRadius::Length(length) => length
269            .0
270            .to_used_value(Au::from_f32_px(max_edge - min_edge))
271            .to_f32_px(),
272    }
273}