1use std::cell::LazyCell;
6
7use app_units::Au;
8use malloc_size_of_derive::MallocSizeOf;
9use servo_base::id::ScrollTreeNodeId;
10use style::values::computed::basic_shape::{BasicShape, ClipPath};
11use style::values::computed::position::Position;
12use style::values::computed::{Length, LengthPercentage};
13use style::values::generics::basic_shape::{GenericShapeRadius, ShapeBox, ShapeGeometryBox};
14use style::values::generics::position::GenericPositionOrAuto;
15use webrender_api::BorderRadius;
16use webrender_api::units::{LayoutPoint, LayoutRect, LayoutSideOffsets, LayoutSize};
17
18use super::{BuilderForBoxFragment, compute_margin_box_radius};
19use crate::fragment_tree::BoxFragmentWithStyle;
20use crate::geom::PhysicalPoint;
21
22#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq)]
25pub(crate) struct ClipId(pub usize);
26
27impl ClipId {
28 pub(crate) const INVALID: ClipId = ClipId(usize::MAX);
30}
31
32impl Default for ClipId {
33 fn default() -> Self {
34 Self::INVALID
35 }
36}
37
38#[derive(Clone, MallocSizeOf)]
42pub(crate) struct Clip {
43 pub id: ClipId,
44 pub radii: BorderRadius,
45 pub rect: LayoutRect,
46 pub parent_scroll_node_id: ScrollTreeNodeId,
47 pub parent_clip_id: ClipId,
48}
49
50#[derive(Clone, Default, MallocSizeOf)]
54pub(crate) struct StackingContextTreeClipStore(pub Vec<Clip>);
55
56impl StackingContextTreeClipStore {
57 pub(super) fn get(&self, clip_id: ClipId) -> &Clip {
58 &self.0[clip_id.0]
59 }
60
61 pub(crate) fn add(
62 &mut self,
63 radii: webrender_api::BorderRadius,
64 rect: LayoutRect,
65 parent_scroll_node_id: ScrollTreeNodeId,
66 parent_clip_id: ClipId,
67 ) -> ClipId {
68 let id = ClipId(self.0.len());
69 self.0.push(Clip {
70 id,
71 radii,
72 rect,
73 parent_scroll_node_id,
74 parent_clip_id,
75 });
76 id
77 }
78
79 pub(super) fn add_for_clip_path(
80 &mut self,
81 clip_path: &ClipPath,
82 parent_scroll_node_id: ScrollTreeNodeId,
83 parent_clip_chain_id: ClipId,
84 box_fragment: &BoxFragmentWithStyle<'_>,
85 containing_block_origin: PhysicalPoint<Au>,
86 ) -> Option<ClipId> {
87 let geometry_box = match clip_path {
88 ClipPath::Shape(_, ShapeGeometryBox::ShapeBox(shape_box)) => *shape_box,
89 ClipPath::Shape(_, ShapeGeometryBox::ElementDependent) => ShapeBox::BorderBox,
90 ClipPath::Box(ShapeGeometryBox::ShapeBox(shape_box)) => *shape_box,
91 ClipPath::Box(ShapeGeometryBox::ElementDependent) => ShapeBox::BorderBox,
92 _ => return None,
93 };
94 let fragment_builder = BuilderForBoxFragment::new(box_fragment, containing_block_origin);
95 let layout_rect = match geometry_box {
96 ShapeBox::BorderBox => fragment_builder.border_rect,
97 ShapeBox::ContentBox => *fragment_builder.content_rect(),
98 ShapeBox::PaddingBox => *fragment_builder.padding_rect(),
99 ShapeBox::MarginBox => *fragment_builder.margin_rect(),
100 };
101 if let ClipPath::Shape(shape, _) = clip_path {
102 match **shape {
103 BasicShape::Circle(_) | BasicShape::Ellipse(_) | BasicShape::Rect(_) => self
104 .add_for_basic_shape(
105 shape,
106 layout_rect,
107 parent_scroll_node_id,
108 parent_clip_chain_id,
109 ),
110 BasicShape::Polygon(_) | BasicShape::PathOrShape(_) => None,
111 }
112 } else {
113 Some(self.add(
114 match geometry_box {
115 ShapeBox::MarginBox => compute_margin_box_radius(
116 fragment_builder.border_radius(),
117 layout_rect.size(),
118 fragment_builder.fragment,
119 ),
120 _ => fragment_builder.border_radius(),
121 },
122 layout_rect,
123 parent_scroll_node_id,
124 parent_clip_chain_id,
125 ))
126 }
127 }
128
129 #[servo_tracing::instrument(name = "StackingContextClipStore::add_for_basic_shape", skip_all)]
130 fn add_for_basic_shape(
131 &mut self,
132 shape: &BasicShape,
133 layout_box: LayoutRect,
134 parent_scroll_node_id: ScrollTreeNodeId,
135 parent_clip_chain_id: ClipId,
136 ) -> Option<ClipId> {
137 match shape {
138 BasicShape::Rect(rect) => {
139 let box_height = Length::new(layout_box.height());
140 let box_width = Length::new(layout_box.width());
141 let insets = LayoutSideOffsets::new(
142 rect.rect.0.resolve(box_height).px(),
143 rect.rect.1.resolve(box_width).px(),
144 rect.rect.2.resolve(box_height).px(),
145 rect.rect.3.resolve(box_width).px(),
146 );
147
148 let shape_rect = if insets.left + insets.right >= layout_box.width() ||
151 insets.top + insets.bottom > layout_box.height()
152 {
153 LayoutRect::from_origin_and_size(layout_box.min, LayoutSize::zero())
154 } else {
155 layout_box.to_rect().inner_rect(insets).to_box2d()
156 };
157
158 let corner = |corner: &style::values::computed::BorderCornerRadius| {
159 LayoutSize::new(
160 corner.0.width.0.resolve(box_width).px(),
161 corner.0.height.0.resolve(box_height).px(),
162 )
163 };
164 let radii = webrender_api::BorderRadius {
165 top_left: corner(&rect.round.top_left),
166 top_right: corner(&rect.round.top_right),
167 bottom_left: corner(&rect.round.bottom_left),
168 bottom_right: corner(&rect.round.bottom_right),
169 };
170 Some(self.add(
171 radii,
172 shape_rect,
173 parent_scroll_node_id,
174 parent_clip_chain_id,
175 ))
176 },
177 BasicShape::Circle(circle) => {
178 let center = match &circle.position {
179 GenericPositionOrAuto::Position(position) => position.clone(),
180 GenericPositionOrAuto::Auto => Position::center(),
181 };
182 let anchor_x = center.horizontal.resolve(Length::new(layout_box.width()));
183 let anchor_y = center.vertical.resolve(Length::new(layout_box.height()));
184 let center = layout_box
185 .min
186 .add_size(&LayoutSize::new(anchor_x.px(), anchor_y.px()));
187
188 let radius = compute_shape_radius_for_circle(
189 center,
190 &circle.radius,
191 layout_box.min,
192 layout_box.max,
193 );
194 let radius = LayoutSize::new(radius, radius);
195 let radii = webrender_api::BorderRadius {
196 top_left: radius,
197 top_right: radius,
198 bottom_left: radius,
199 bottom_right: radius,
200 };
201 let start = center.add_size(&-radius);
202 let rect = LayoutRect::from_origin_and_size(start, radius * 2.);
203 Some(self.add(radii, rect, parent_scroll_node_id, parent_clip_chain_id))
204 },
205 BasicShape::Ellipse(ellipse) => {
206 let center = match &ellipse.position {
207 GenericPositionOrAuto::Position(position) => position.clone(),
208 GenericPositionOrAuto::Auto => Position::center(),
209 };
210 let anchor_x = center.horizontal.resolve(Length::new(layout_box.width()));
211 let anchor_y = center.vertical.resolve(Length::new(layout_box.height()));
212 let center = layout_box
213 .min
214 .add_size(&LayoutSize::new(anchor_x.px(), anchor_y.px()));
215
216 let radius = compute_shape_radius_for_ellipse(
217 center,
218 (&ellipse.semiaxis_x, &ellipse.semiaxis_y),
219 layout_box.min,
220 layout_box.max,
221 );
222
223 let radii = webrender_api::BorderRadius {
224 top_left: radius,
225 top_right: radius,
226 bottom_left: radius,
227 bottom_right: radius,
228 };
229 let start = center.add_size(&-radius);
230 let rect = LayoutRect::from_origin_and_size(start, radius * 2.);
231 Some(self.add(radii, rect, parent_scroll_node_id, parent_clip_chain_id))
232 },
233 _ => None,
234 }
235 }
236}
237
238fn compute_shape_radius_for_circle(
239 center: LayoutPoint,
240 radius: &GenericShapeRadius<LengthPercentage>,
241 min_edge: LayoutPoint,
242 max_edge: LayoutPoint,
243) -> f32 {
244 let distance_from_min_edge = (min_edge - center).abs();
245 let distance_from_max_edge = (max_edge - center).abs();
246 match radius {
247 GenericShapeRadius::FarthestSide => {
248 let x = distance_from_min_edge.x.max(distance_from_max_edge.x);
249 let y = distance_from_min_edge.y.max(distance_from_max_edge.y);
250 x.max(y)
251 },
252 GenericShapeRadius::ClosestSide => {
253 let x = distance_from_min_edge.x.min(distance_from_max_edge.x);
254 let y = distance_from_min_edge.y.min(distance_from_max_edge.y);
255 x.min(y)
256 },
257 GenericShapeRadius::FarthestCorner => {
258 let x = distance_from_min_edge.x.max(distance_from_max_edge.x);
259 let y = distance_from_min_edge.y.max(distance_from_max_edge.y);
260 x.hypot(y)
261 },
262 GenericShapeRadius::ClosestCorner => {
263 let x = distance_from_min_edge.x.min(distance_from_max_edge.x);
264 let y = distance_from_min_edge.y.min(distance_from_max_edge.y);
265 x.hypot(y)
266 },
267 GenericShapeRadius::Length(length) => {
268 let size = max_edge - min_edge;
270 let basis = size.x.hypot(size.y) / std::f32::consts::SQRT_2;
271 length.0.resolve(Length::new(basis)).px()
272 },
273 }
274}
275
276fn compute_shape_radius_for_ellipse(
277 center: LayoutPoint,
278 radius: (
279 &GenericShapeRadius<LengthPercentage>,
280 &GenericShapeRadius<LengthPercentage>,
281 ),
282 min_edge: LayoutPoint,
283 max_edge: LayoutPoint,
284) -> LayoutSize {
285 let distance_from_min_edge = (min_edge - center).abs();
286 let distance_from_max_edge = (max_edge - center).abs();
287
288 let farthest_corner = LazyCell::new(|| {
293 let x = distance_from_min_edge.x.max(distance_from_max_edge.x);
294 let y = distance_from_min_edge.y.max(distance_from_max_edge.y);
295 x.hypot(y)
296 });
297 let closest_corner = LazyCell::new(|| {
298 let x = distance_from_min_edge.x.min(distance_from_max_edge.x);
299 let y = distance_from_min_edge.y.min(distance_from_max_edge.y);
300 x.hypot(y)
301 });
302
303 let radius_x = match radius.0 {
304 GenericShapeRadius::FarthestSide => distance_from_min_edge.x.max(distance_from_max_edge.x),
305 GenericShapeRadius::ClosestSide => distance_from_min_edge.x.min(distance_from_max_edge.x),
306 GenericShapeRadius::FarthestCorner => *farthest_corner,
307 GenericShapeRadius::ClosestCorner => *closest_corner,
308 GenericShapeRadius::Length(length) => {
309 length.0.resolve(Length::new(max_edge.x - min_edge.x)).px()
310 },
311 };
312 let radius_y = match radius.1 {
313 GenericShapeRadius::FarthestSide => distance_from_min_edge.y.max(distance_from_max_edge.y),
314 GenericShapeRadius::ClosestSide => distance_from_min_edge.y.min(distance_from_max_edge.y),
315 GenericShapeRadius::FarthestCorner => *farthest_corner,
316 GenericShapeRadius::ClosestCorner => *closest_corner,
317 GenericShapeRadius::Length(length) => {
318 length.0.resolve(Length::new(max_edge.y - min_edge.y)).px()
319 },
320 };
321 LayoutSize::new(radius_x, radius_y)
322}