1use 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#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq)]
21pub(crate) struct ClipId(pub usize);
22
23impl ClipId {
24 pub(crate) const INVALID: ClipId = ClipId(usize::MAX);
26}
27
28#[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#[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 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 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}