1use app_units::Au;
6use euclid::{Size2D, Vector2D};
7use style::computed_values::background_attachment::SingleComputedValue as BackgroundAttachment;
8use style::computed_values::background_blend_mode::SingleComputedValue as BackgroundBlendMode;
9use style::computed_values::background_clip::single_value::T as Clip;
10use style::computed_values::background_origin::single_value::T as Origin;
11use style::properties::ComputedValues;
12use style::values::computed::LengthPercentage;
13use style::values::computed::background::BackgroundSize as Size;
14use style::values::specified::background::{
15 BackgroundRepeat as RepeatXY, BackgroundRepeatKeyword as Repeat,
16};
17use webrender_api::{self as wr, units};
18use wr::ClipChainId;
19
20use crate::replaced::NaturalSizes;
21
22pub(super) struct BackgroundLayer {
23 pub common: wr::CommonItemProperties,
24 pub bounds: units::LayoutRect,
25 pub tile_size: units::LayoutSize,
26 pub tile_spacing: units::LayoutSize,
27 pub repeat: bool,
28 pub blend_mode: BackgroundBlendMode,
29}
30
31#[derive(Debug)]
32struct Layout1DResult {
33 repeat: bool,
34 bounds_origin: f32,
35 bounds_size: f32,
36 tile_spacing: f32,
37}
38
39pub(crate) fn get_cyclic<T>(values: &[T], layer_index: usize) -> &T {
40 &values[layer_index % values.len()]
41}
42
43pub(super) struct BackgroundPainter<'a> {
44 pub style: &'a ComputedValues,
45 pub positioning_area_override: Option<units::LayoutRect>,
46 pub painting_area_override: Option<units::LayoutRect>,
47}
48
49impl<'a> BackgroundPainter<'a> {
50 pub(super) fn painting_area(
53 &self,
54 fragment_builder: &'a super::BuilderForBoxFragment,
55 builder: &mut super::DisplayListBuilder,
56 layer_index: usize,
57 ) -> units::LayoutRect {
58 let fb = fragment_builder;
59 if let Some(painting_area_override) = self.painting_area_override.as_ref() {
60 return *painting_area_override;
61 }
62 if self.positioning_area_override.is_some() {
63 return fb.border_rect;
64 }
65
66 let background = self.style.get_background();
67 if &BackgroundAttachment::Fixed ==
68 get_cyclic(&background.background_attachment.0, layer_index)
69 {
70 return builder.paint_info.viewport_details.layout_size().into();
71 }
72
73 match get_cyclic(&background.background_clip.0, layer_index) {
74 Clip::ContentBox => *fragment_builder.content_rect(),
75 Clip::PaddingBox => *fragment_builder.padding_rect(),
76 Clip::BorderBox => fragment_builder.border_rect,
77 }
78 }
79
80 fn clip(
81 &self,
82 fragment_builder: &'a super::BuilderForBoxFragment,
83 builder: &mut super::DisplayListBuilder,
84 layer_index: usize,
85 ) -> Option<ClipChainId> {
86 if self.painting_area_override.is_some() {
87 return None;
88 }
89
90 if self.positioning_area_override.is_some() {
91 return fragment_builder.border_edge_clip(builder, false);
92 }
93
94 let background = self.style.get_background();
96 let force_clip_creation = get_cyclic(&background.background_attachment.0, layer_index) ==
97 &BackgroundAttachment::Fixed;
98 match get_cyclic(&background.background_clip.0, layer_index) {
99 Clip::ContentBox => fragment_builder.content_edge_clip(builder, force_clip_creation),
100 Clip::PaddingBox => fragment_builder.padding_edge_clip(builder, force_clip_creation),
101 Clip::BorderBox => fragment_builder.border_edge_clip(builder, force_clip_creation),
102 }
103 }
104
105 pub(super) fn common_properties(
109 &self,
110 fragment_builder: &'a super::BuilderForBoxFragment,
111 builder: &mut super::DisplayListBuilder,
112 layer_index: usize,
113 painting_area: units::LayoutRect,
114 ) -> wr::CommonItemProperties {
115 let clip = self.clip(fragment_builder, builder, layer_index);
116 let style = fragment_builder.fragment.style();
117 let mut common = builder.common_properties(painting_area, &style);
118 if let Some(clip_chain_id) = clip {
119 common.clip_chain_id = clip_chain_id;
120 }
121 if &BackgroundAttachment::Fixed ==
122 get_cyclic(&style.get_background().background_attachment.0, layer_index)
123 {
124 common.spatial_id = builder.spatial_id(builder.current_reference_frame_scroll_node_id);
125 }
126 common
127 }
128
129 pub(super) fn positioning_area(
133 &self,
134 fragment_builder: &'a super::BuilderForBoxFragment,
135 builder: &mut super::DisplayListBuilder,
136 layer_index: usize,
137 ) -> units::LayoutRect {
138 if let Some(positioning_area_override) = self.positioning_area_override {
139 return positioning_area_override;
140 }
141
142 match get_cyclic(
143 &self.style.get_background().background_attachment.0,
144 layer_index,
145 ) {
146 BackgroundAttachment::Scroll => match get_cyclic(
147 &self.style.get_background().background_origin.0,
148 layer_index,
149 ) {
150 Origin::ContentBox => *fragment_builder.content_rect(),
151 Origin::PaddingBox => *fragment_builder.padding_rect(),
152 Origin::BorderBox => fragment_builder.border_rect,
153 },
154 BackgroundAttachment::Fixed => builder.paint_info.viewport_details.layout_size().into(),
155 }
156 }
157}
158
159pub(super) fn layout_layer(
160 fragment_builder: &mut super::BuilderForBoxFragment,
161 painter: &BackgroundPainter,
162 builder: &mut super::DisplayListBuilder,
163 layer_index: usize,
164 natural_sizes: NaturalSizes,
165) -> Option<BackgroundLayer> {
166 let painting_area = painter.painting_area(fragment_builder, builder, layer_index);
167 let positioning_area = painter.positioning_area(fragment_builder, builder, layer_index);
168 let common = painter.common_properties(fragment_builder, builder, layer_index, painting_area);
169
170 enum ContainOrCover {
172 Contain,
173 Cover,
174 }
175 let size_contain_or_cover = |background_size| {
176 let mut tile_size = positioning_area.size();
177 if let Some(natural_ratio) = natural_sizes.ratio {
178 let positioning_ratio = positioning_area.size().width / positioning_area.size().height;
179 let fit_width = match background_size {
182 ContainOrCover::Contain => positioning_ratio <= natural_ratio,
183 ContainOrCover::Cover => positioning_ratio > natural_ratio,
184 };
185 if fit_width {
187 tile_size.height = tile_size.width / natural_ratio
188 } else {
189 tile_size.width = tile_size.height * natural_ratio
190 }
191 }
192 tile_size
193 };
194
195 let b = painter.style.get_background();
196 let mut tile_size = match get_cyclic(&b.background_size.0, layer_index) {
197 Size::Contain => size_contain_or_cover(ContainOrCover::Contain),
198 Size::Cover => size_contain_or_cover(ContainOrCover::Cover),
199 Size::ExplicitSize { width, height } => {
200 let mut width = width.non_auto().map(|lp| {
201 lp.0.to_used_value(Au::from_f32_px(positioning_area.size().width))
202 });
203 let mut height = height.non_auto().map(|lp| {
204 lp.0.to_used_value(Au::from_f32_px(positioning_area.size().height))
205 });
206
207 if width.is_none() && height.is_none() {
208 width = natural_sizes.width;
211 height = natural_sizes.height;
212 }
213
214 match (width, height) {
215 (Some(w), Some(h)) => units::LayoutSize::new(w.to_f32_px(), h.to_f32_px()),
216 (Some(w), None) => {
217 let h = if let Some(natural_ratio) = natural_sizes.ratio {
218 w.scale_by(1.0 / natural_ratio)
219 } else if let Some(natural_height) = natural_sizes.height {
220 natural_height
221 } else {
222 Au::from_f32_px(positioning_area.size().height)
224 };
225 units::LayoutSize::new(w.to_f32_px(), h.to_f32_px())
226 },
227 (None, Some(h)) => {
228 let w = if let Some(natural_ratio) = natural_sizes.ratio {
229 h.scale_by(natural_ratio)
230 } else if let Some(natural_width) = natural_sizes.width {
231 natural_width
232 } else {
233 Au::from_f32_px(positioning_area.size().width)
235 };
236 units::LayoutSize::new(w.to_f32_px(), h.to_f32_px())
237 },
238 (None, None) => size_contain_or_cover(ContainOrCover::Contain),
240 }
241 },
242 };
243
244 if tile_size.width == 0.0 || tile_size.height == 0.0 {
245 return None;
246 }
247
248 let RepeatXY(repeat_x, repeat_y) = *get_cyclic(&b.background_repeat.0, layer_index);
249 let result_x = layout_1d(
250 &mut tile_size.width,
251 repeat_x,
252 get_cyclic(&b.background_position_x.0, layer_index),
253 painting_area.min.x - positioning_area.min.x,
254 painting_area.size().width,
255 positioning_area.size().width,
256 );
257 let result_y = layout_1d(
258 &mut tile_size.height,
259 repeat_y,
260 get_cyclic(&b.background_position_y.0, layer_index),
261 painting_area.min.y - positioning_area.min.y,
262 painting_area.size().height,
263 positioning_area.size().height,
264 );
265 let bounds = units::LayoutRect::from_origin_and_size(
266 positioning_area.min + Vector2D::new(result_x.bounds_origin, result_y.bounds_origin),
267 Size2D::new(result_x.bounds_size, result_y.bounds_size),
268 );
269 let tile_spacing = units::LayoutSize::new(result_x.tile_spacing, result_y.tile_spacing);
270
271 let blend_mode = *get_cyclic(&b.background_blend_mode.0, layer_index);
272 Some(BackgroundLayer {
273 common,
274 bounds,
275 tile_size,
276 tile_spacing,
277 repeat: result_x.repeat || result_y.repeat,
278 blend_mode,
279 })
280}
281
282fn layout_1d(
285 tile_size: &mut f32,
286 mut repeat: Repeat,
287 position: &LengthPercentage,
288 painting_area_origin: f32,
289 painting_area_size: f32,
290 positioning_area_size: f32,
291) -> Layout1DResult {
292 if let Repeat::Round = repeat {
303 let round = |number: f32| number.round().max(1.0);
304 if positioning_area_size != 0.0 {
305 *tile_size = positioning_area_size / round(positioning_area_size / *tile_size);
306 }
307 }
308 let mut position = position
310 .to_used_value(Au::from_f32_px(positioning_area_size - *tile_size))
311 .to_f32_px();
312 let mut tile_spacing = 0.0;
313 if let Repeat::Space = repeat {
315 let tile_count = (positioning_area_size / *tile_size).floor();
317 if tile_count >= 2.0 {
318 position = 0.0;
319 let total_space = positioning_area_size - *tile_size * tile_count;
322 let spaces_count = tile_count - 1.0;
323 tile_spacing = total_space / spaces_count;
324 } else {
325 repeat = Repeat::NoRepeat
326 }
327 }
328 match repeat {
329 Repeat::Repeat | Repeat::Round | Repeat::Space => {
330 let tile_stride = *tile_size + tile_spacing;
345 let offset = position - painting_area_origin;
346 let bounds_origin = position - tile_stride * (offset / tile_stride).ceil();
347 let bounds_end = painting_area_origin + painting_area_size;
348 let bounds_size = bounds_end - bounds_origin;
349 Layout1DResult {
350 repeat: true,
351 bounds_origin,
352 bounds_size,
353 tile_spacing,
354 }
355 },
356 Repeat::NoRepeat => {
357 Layout1DResult {
362 repeat: false,
363 bounds_origin: position,
364 bounds_size: *tile_size,
365 tile_spacing: 0.0,
366 }
367 },
368 }
369}