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