lightningcss/properties/
background.rs

1//! CSS properties related to backgrounds.
2
3use crate::context::PropertyHandlerContext;
4use crate::declaration::{DeclarationBlock, DeclarationList};
5use crate::error::{ParserError, PrinterError};
6use crate::macros::*;
7use crate::prefixes::Feature;
8use crate::printer::Printer;
9use crate::properties::{Property, PropertyId, VendorPrefix};
10use crate::targets::{Browsers, Targets};
11use crate::traits::{FallbackValues, IsCompatible, Parse, PropertyHandler, Shorthand, ToCss};
12use crate::values::color::ColorFallbackKind;
13use crate::values::image::ImageFallback;
14use crate::values::{color::CssColor, image::Image, length::LengthPercentageOrAuto, position::*};
15#[cfg(feature = "visitor")]
16use crate::visitor::Visit;
17use cssparser::*;
18use itertools::izip;
19use smallvec::SmallVec;
20
21/// A value for the [background-size](https://www.w3.org/TR/css-backgrounds-3/#background-size) property.
22#[derive(Debug, Clone, PartialEq)]
23#[cfg_attr(feature = "visitor", derive(Visit))]
24#[cfg_attr(
25  feature = "serde",
26  derive(serde::Serialize, serde::Deserialize),
27  serde(tag = "type", rename_all = "kebab-case")
28)]
29#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
30#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
31pub enum BackgroundSize {
32  /// An explicit background size.
33  Explicit {
34    /// The width of the background.
35    width: LengthPercentageOrAuto,
36    /// The height of the background.
37    height: LengthPercentageOrAuto,
38  },
39  /// The `cover` keyword. Scales the background image to cover both the width and height of the element.
40  Cover,
41  /// The `contain` keyword. Scales the background image so that it fits within the element.
42  Contain,
43}
44
45impl Default for BackgroundSize {
46  fn default() -> BackgroundSize {
47    BackgroundSize::Explicit {
48      width: LengthPercentageOrAuto::Auto,
49      height: LengthPercentageOrAuto::Auto,
50    }
51  }
52}
53
54impl<'i> Parse<'i> for BackgroundSize {
55  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
56    if let Ok(width) = input.try_parse(LengthPercentageOrAuto::parse) {
57      let height = input
58        .try_parse(LengthPercentageOrAuto::parse)
59        .unwrap_or(LengthPercentageOrAuto::Auto);
60      return Ok(BackgroundSize::Explicit { width, height });
61    }
62
63    let location = input.current_source_location();
64    let ident = input.expect_ident()?;
65    Ok(match_ignore_ascii_case! { ident,
66      "cover" => BackgroundSize::Cover,
67      "contain" => BackgroundSize::Contain,
68      _ => return Err(location.new_unexpected_token_error(
69        cssparser::Token::Ident(ident.clone())
70      ))
71    })
72  }
73}
74
75impl ToCss for BackgroundSize {
76  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
77  where
78    W: std::fmt::Write,
79  {
80    use BackgroundSize::*;
81
82    match &self {
83      Cover => dest.write_str("cover"),
84      Contain => dest.write_str("contain"),
85      Explicit { width, height } => {
86        width.to_css(dest)?;
87        if *height != LengthPercentageOrAuto::Auto {
88          dest.write_str(" ")?;
89          height.to_css(dest)?;
90        }
91        Ok(())
92      }
93    }
94  }
95}
96
97impl IsCompatible for BackgroundSize {
98  fn is_compatible(&self, browsers: Browsers) -> bool {
99    match self {
100      BackgroundSize::Explicit { width, height } => {
101        width.is_compatible(browsers) && height.is_compatible(browsers)
102      }
103      BackgroundSize::Cover | BackgroundSize::Contain => true,
104    }
105  }
106}
107
108enum_property! {
109  /// A [`<repeat-style>`](https://www.w3.org/TR/css-backgrounds-3/#typedef-repeat-style) value,
110  /// used within the `background-repeat` property to represent how a background image is repeated
111  /// in a single direction.
112  ///
113  /// See [BackgroundRepeat](BackgroundRepeat).
114  pub enum BackgroundRepeatKeyword {
115    /// The image is repeated in this direction.
116    Repeat,
117    /// The image is repeated so that it fits, and then spaced apart evenly.
118    Space,
119    /// The image is scaled so that it repeats an even number of times.
120    Round,
121    /// The image is placed once and not repeated in this direction.
122    NoRepeat,
123  }
124}
125
126/// A value for the [background-repeat](https://www.w3.org/TR/css-backgrounds-3/#background-repeat) property.
127#[derive(Debug, Clone, PartialEq)]
128#[cfg_attr(feature = "visitor", derive(Visit))]
129#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
130#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
131#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
132pub struct BackgroundRepeat {
133  /// A repeat style for the x direction.
134  pub x: BackgroundRepeatKeyword,
135  /// A repeat style for the y direction.
136  pub y: BackgroundRepeatKeyword,
137}
138
139impl Default for BackgroundRepeat {
140  fn default() -> BackgroundRepeat {
141    BackgroundRepeat {
142      x: BackgroundRepeatKeyword::Repeat,
143      y: BackgroundRepeatKeyword::Repeat,
144    }
145  }
146}
147
148impl<'i> Parse<'i> for BackgroundRepeat {
149  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
150    use BackgroundRepeatKeyword::*;
151    let state = input.state();
152    let ident = input.expect_ident()?;
153
154    match_ignore_ascii_case! { ident,
155      "repeat-x" => return Ok(BackgroundRepeat { x: Repeat, y: NoRepeat }),
156      "repeat-y" => return Ok(BackgroundRepeat { x: NoRepeat, y: Repeat }),
157      _ => {}
158    }
159
160    input.reset(&state);
161
162    let x = BackgroundRepeatKeyword::parse(input)?;
163    let y = input.try_parse(BackgroundRepeatKeyword::parse).unwrap_or(x.clone());
164    Ok(BackgroundRepeat { x, y })
165  }
166}
167
168impl ToCss for BackgroundRepeat {
169  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
170  where
171    W: std::fmt::Write,
172  {
173    use BackgroundRepeatKeyword::*;
174    match (&self.x, &self.y) {
175      (Repeat, NoRepeat) => dest.write_str("repeat-x"),
176      (NoRepeat, Repeat) => dest.write_str("repeat-y"),
177      (x, y) => {
178        x.to_css(dest)?;
179        if y != x {
180          dest.write_str(" ")?;
181          y.to_css(dest)?;
182        }
183        Ok(())
184      }
185    }
186  }
187}
188
189impl IsCompatible for BackgroundRepeat {
190  fn is_compatible(&self, _browsers: Browsers) -> bool {
191    true
192  }
193}
194
195enum_property! {
196  /// A value for the [background-attachment](https://www.w3.org/TR/css-backgrounds-3/#background-attachment) property.
197  pub enum BackgroundAttachment {
198    /// The background scrolls with the container.
199    Scroll,
200    /// The background is fixed to the viewport.
201    Fixed,
202    /// The background is fixed with regard to the element’s contents.
203    Local,
204  }
205}
206
207impl Default for BackgroundAttachment {
208  fn default() -> BackgroundAttachment {
209    BackgroundAttachment::Scroll
210  }
211}
212
213enum_property! {
214  /// A value for the [background-origin](https://www.w3.org/TR/css-backgrounds-3/#background-origin) property.
215  pub enum BackgroundOrigin {
216    /// The position is relative to the border box.
217    BorderBox,
218    /// The position is relative to the padding box.
219    PaddingBox,
220    /// The position is relative to the content box.
221    ContentBox,
222  }
223}
224
225enum_property! {
226  /// A value for the [background-clip](https://drafts.csswg.org/css-backgrounds-4/#background-clip) property.
227  pub enum BackgroundClip {
228    /// The background is clipped to the border box.
229    BorderBox,
230    /// The background is clipped to the padding box.
231    PaddingBox,
232    /// The background is clipped to the content box.
233    ContentBox,
234    /// The background is clipped to the area painted by the border.
235    Border,
236    /// The background is clipped to the text content of the element.
237    Text,
238  }
239}
240
241impl PartialEq<BackgroundOrigin> for BackgroundClip {
242  fn eq(&self, other: &BackgroundOrigin) -> bool {
243    match (self, other) {
244      (BackgroundClip::BorderBox, BackgroundOrigin::BorderBox)
245      | (BackgroundClip::PaddingBox, BackgroundOrigin::PaddingBox)
246      | (BackgroundClip::ContentBox, BackgroundOrigin::ContentBox) => true,
247      _ => false,
248    }
249  }
250}
251
252impl Into<BackgroundClip> for BackgroundOrigin {
253  fn into(self) -> BackgroundClip {
254    match self {
255      BackgroundOrigin::BorderBox => BackgroundClip::BorderBox,
256      BackgroundOrigin::PaddingBox => BackgroundClip::PaddingBox,
257      BackgroundOrigin::ContentBox => BackgroundClip::ContentBox,
258    }
259  }
260}
261
262impl Default for BackgroundClip {
263  fn default() -> BackgroundClip {
264    BackgroundClip::BorderBox
265  }
266}
267
268impl BackgroundClip {
269  fn is_background_box(&self) -> bool {
270    matches!(
271      self,
272      BackgroundClip::BorderBox | BackgroundClip::PaddingBox | BackgroundClip::ContentBox
273    )
274  }
275}
276
277define_list_shorthand! {
278  /// A value for the [background-position](https://drafts.csswg.org/css-backgrounds/#background-position) shorthand property.
279  pub struct BackgroundPosition {
280    /// The x-position.
281    x: BackgroundPositionX(HorizontalPosition),
282    /// The y-position.
283    y: BackgroundPositionY(VerticalPosition),
284  }
285}
286
287impl From<Position> for BackgroundPosition {
288  fn from(pos: Position) -> Self {
289    BackgroundPosition { x: pos.x, y: pos.y }
290  }
291}
292
293impl Into<Position> for &BackgroundPosition {
294  fn into(self) -> Position {
295    Position {
296      x: self.x.clone(),
297      y: self.y.clone(),
298    }
299  }
300}
301
302impl Default for BackgroundPosition {
303  fn default() -> Self {
304    Position::default().into()
305  }
306}
307
308impl<'i> Parse<'i> for BackgroundPosition {
309  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
310    let pos = Position::parse(input)?;
311    Ok(pos.into())
312  }
313}
314
315impl ToCss for BackgroundPosition {
316  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
317  where
318    W: std::fmt::Write,
319  {
320    let pos: Position = self.into();
321    pos.to_css(dest)
322  }
323}
324
325/// A value for the [background](https://www.w3.org/TR/css-backgrounds-3/#background) shorthand property.
326#[derive(Debug, Clone, PartialEq)]
327#[cfg_attr(feature = "visitor", derive(Visit))]
328#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
329#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
330#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
331pub struct Background<'i> {
332  /// The background image.
333  #[cfg_attr(feature = "serde", serde(borrow))]
334  pub image: Image<'i>,
335  /// The background color.
336  pub color: CssColor,
337  /// The background position.
338  pub position: BackgroundPosition,
339  /// How the background image should repeat.
340  pub repeat: BackgroundRepeat,
341  /// The size of the background image.
342  pub size: BackgroundSize,
343  /// The background attachment.
344  pub attachment: BackgroundAttachment,
345  /// The background origin.
346  pub origin: BackgroundOrigin,
347  /// How the background should be clipped.
348  pub clip: BackgroundClip,
349}
350
351impl<'i> Parse<'i> for Background<'i> {
352  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
353    let mut color: Option<CssColor> = None;
354    let mut position: Option<BackgroundPosition> = None;
355    let mut size: Option<BackgroundSize> = None;
356    let mut image: Option<Image> = None;
357    let mut repeat: Option<BackgroundRepeat> = None;
358    let mut attachment: Option<BackgroundAttachment> = None;
359    let mut origin: Option<BackgroundOrigin> = None;
360    let mut clip: Option<BackgroundClip> = None;
361
362    loop {
363      // TODO: only allowed on the last background.
364      if color.is_none() {
365        if let Ok(value) = input.try_parse(CssColor::parse) {
366          color = Some(value);
367          continue;
368        }
369      }
370
371      if position.is_none() {
372        if let Ok(value) = input.try_parse(BackgroundPosition::parse) {
373          position = Some(value);
374
375          size = input
376            .try_parse(|input| {
377              input.expect_delim('/')?;
378              BackgroundSize::parse(input)
379            })
380            .ok();
381
382          continue;
383        }
384      }
385
386      if image.is_none() {
387        if let Ok(value) = input.try_parse(Image::parse) {
388          image = Some(value);
389          continue;
390        }
391      }
392
393      if repeat.is_none() {
394        if let Ok(value) = input.try_parse(BackgroundRepeat::parse) {
395          repeat = Some(value);
396          continue;
397        }
398      }
399
400      if attachment.is_none() {
401        if let Ok(value) = input.try_parse(BackgroundAttachment::parse) {
402          attachment = Some(value);
403          continue;
404        }
405      }
406
407      if origin.is_none() {
408        if let Ok(value) = input.try_parse(BackgroundOrigin::parse) {
409          origin = Some(value);
410          continue;
411        }
412      }
413
414      if clip.is_none() {
415        if let Ok(value) = input.try_parse(BackgroundClip::parse) {
416          clip = Some(value);
417          continue;
418        }
419      }
420
421      break;
422    }
423
424    if clip.is_none() {
425      if let Some(origin) = origin {
426        clip = Some(origin.into());
427      }
428    }
429
430    Ok(Background {
431      image: image.unwrap_or_default(),
432      color: color.unwrap_or_default(),
433      position: position.unwrap_or_default(),
434      repeat: repeat.unwrap_or_default(),
435      size: size.unwrap_or_default(),
436      attachment: attachment.unwrap_or_default(),
437      origin: origin.unwrap_or(BackgroundOrigin::PaddingBox),
438      clip: clip.unwrap_or(BackgroundClip::BorderBox),
439    })
440  }
441}
442
443impl<'i> ToCss for Background<'i> {
444  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
445  where
446    W: std::fmt::Write,
447  {
448    let mut has_output = false;
449
450    if self.color != CssColor::default() {
451      self.color.to_css(dest)?;
452      has_output = true;
453    }
454
455    if self.image != Image::default() {
456      if has_output {
457        dest.write_str(" ")?;
458      }
459      self.image.to_css(dest)?;
460      has_output = true;
461    }
462
463    let position: Position = (&self.position).into();
464    if !position.is_zero() || self.size != BackgroundSize::default() {
465      if has_output {
466        dest.write_str(" ")?;
467      }
468      position.to_css(dest)?;
469
470      if self.size != BackgroundSize::default() {
471        dest.delim('/', true)?;
472        self.size.to_css(dest)?;
473      }
474
475      has_output = true;
476    }
477
478    if self.repeat != BackgroundRepeat::default() {
479      if has_output {
480        dest.write_str(" ")?;
481      }
482
483      self.repeat.to_css(dest)?;
484      has_output = true;
485    }
486
487    if self.attachment != BackgroundAttachment::default() {
488      if has_output {
489        dest.write_str(" ")?;
490      }
491
492      self.attachment.to_css(dest)?;
493      has_output = true;
494    }
495
496    let output_padding_box = self.origin != BackgroundOrigin::PaddingBox
497      || (self.clip != BackgroundOrigin::BorderBox && self.clip.is_background_box());
498    if output_padding_box {
499      if has_output {
500        dest.write_str(" ")?;
501      }
502
503      self.origin.to_css(dest)?;
504      has_output = true;
505    }
506
507    if (output_padding_box && self.clip != self.origin) || self.clip != BackgroundOrigin::BorderBox {
508      if has_output {
509        dest.write_str(" ")?;
510      }
511
512      self.clip.to_css(dest)?;
513      has_output = true;
514    }
515
516    // If nothing was output, then this is the initial value, e.g. background: transparent
517    if !has_output {
518      if dest.minify {
519        // `0 0` is the shortest valid background value
520        self.position.to_css(dest)?;
521      } else {
522        dest.write_str("none")?;
523      }
524    }
525
526    Ok(())
527  }
528}
529
530impl<'i> ImageFallback<'i> for Background<'i> {
531  #[inline]
532  fn get_image(&self) -> &Image<'i> {
533    &self.image
534  }
535
536  #[inline]
537  fn with_image(&self, image: Image<'i>) -> Self {
538    Background { image, ..self.clone() }
539  }
540
541  #[inline]
542  fn get_necessary_fallbacks(&self, targets: Targets) -> ColorFallbackKind {
543    self.color.get_necessary_fallbacks(targets) | self.get_image().get_necessary_fallbacks(targets)
544  }
545
546  #[inline]
547  fn get_fallback(&self, kind: ColorFallbackKind) -> Self {
548    Background {
549      color: self.color.get_fallback(kind),
550      image: self.image.get_fallback(kind),
551      ..self.clone()
552    }
553  }
554}
555
556impl<'i> Shorthand<'i> for SmallVec<[Background<'i>; 1]> {
557  fn from_longhands(decls: &DeclarationBlock<'i>, vendor_prefix: VendorPrefix) -> Option<(Self, bool)> {
558    let mut color = None;
559    let mut images = None;
560    let mut x_positions = None;
561    let mut y_positions = None;
562    let mut repeats = None;
563    let mut sizes = None;
564    let mut attachments = None;
565    let mut origins = None;
566    let mut clips = None;
567
568    let mut count = 0;
569    let mut important_count = 0;
570    let mut length = None;
571    for (property, important) in decls.iter() {
572      let len = match property {
573        Property::BackgroundColor(value) => {
574          color = Some(value.clone());
575          count += 1;
576          if important {
577            important_count += 1;
578          }
579          continue;
580        }
581        Property::BackgroundImage(value) => {
582          images = Some(value.clone());
583          value.len()
584        }
585        Property::BackgroundPosition(value) => {
586          x_positions = Some(value.iter().map(|v| v.x.clone()).collect());
587          y_positions = Some(value.iter().map(|v| v.y.clone()).collect());
588          value.len()
589        }
590        Property::BackgroundPositionX(value) => {
591          x_positions = Some(value.clone());
592          value.len()
593        }
594        Property::BackgroundPositionY(value) => {
595          y_positions = Some(value.clone());
596          value.len()
597        }
598        Property::BackgroundRepeat(value) => {
599          repeats = Some(value.clone());
600          value.len()
601        }
602        Property::BackgroundSize(value) => {
603          sizes = Some(value.clone());
604          value.len()
605        }
606        Property::BackgroundAttachment(value) => {
607          attachments = Some(value.clone());
608          value.len()
609        }
610        Property::BackgroundOrigin(value) => {
611          origins = Some(value.clone());
612          value.len()
613        }
614        Property::BackgroundClip(value, vp) => {
615          if *vp != vendor_prefix {
616            return None;
617          }
618          clips = Some(value.clone());
619          value.len()
620        }
621        Property::Background(val) => {
622          color = Some(val.last().unwrap().color.clone());
623          images = Some(val.iter().map(|b| b.image.clone()).collect());
624          x_positions = Some(val.iter().map(|b| b.position.x.clone()).collect());
625          y_positions = Some(val.iter().map(|b| b.position.y.clone()).collect());
626          repeats = Some(val.iter().map(|b| b.repeat.clone()).collect());
627          sizes = Some(val.iter().map(|b| b.size.clone()).collect());
628          attachments = Some(val.iter().map(|b| b.attachment.clone()).collect());
629          origins = Some(val.iter().map(|b| b.origin.clone()).collect());
630          clips = Some(val.iter().map(|b| b.clip.clone()).collect());
631          val.len()
632        }
633        _ => continue,
634      };
635
636      // Lengths must be equal.
637      if length.is_none() {
638        length = Some(len);
639      } else if length.unwrap() != len {
640        return None;
641      }
642
643      count += 1;
644      if important {
645        important_count += 1;
646      }
647    }
648
649    // !important flags must match to produce a shorthand.
650    if important_count > 0 && important_count != count {
651      return None;
652    }
653
654    if color.is_some()
655      && images.is_some()
656      && x_positions.is_some()
657      && y_positions.is_some()
658      && repeats.is_some()
659      && sizes.is_some()
660      && attachments.is_some()
661      && origins.is_some()
662      && clips.is_some()
663    {
664      let length = length.unwrap();
665      let values = izip!(
666        images.unwrap().drain(..),
667        x_positions.unwrap().drain(..),
668        y_positions.unwrap().drain(..),
669        repeats.unwrap().drain(..),
670        sizes.unwrap().drain(..),
671        attachments.unwrap().drain(..),
672        origins.unwrap().drain(..),
673        clips.unwrap().drain(..),
674      )
675      .enumerate()
676      .map(
677        |(i, (image, x_position, y_position, repeat, size, attachment, origin, clip))| Background {
678          color: if i == length - 1 {
679            color.clone().unwrap()
680          } else {
681            CssColor::default()
682          },
683          image,
684          position: BackgroundPosition {
685            x: x_position,
686            y: y_position,
687          },
688          repeat,
689          size,
690          attachment,
691          origin,
692          clip: clip,
693        },
694      )
695      .collect();
696      return Some((values, important_count > 0));
697    }
698
699    None
700  }
701
702  fn longhands(vendor_prefix: VendorPrefix) -> Vec<PropertyId<'static>> {
703    vec![
704      PropertyId::BackgroundColor,
705      PropertyId::BackgroundImage,
706      PropertyId::BackgroundPositionX,
707      PropertyId::BackgroundPositionY,
708      PropertyId::BackgroundRepeat,
709      PropertyId::BackgroundSize,
710      PropertyId::BackgroundAttachment,
711      PropertyId::BackgroundOrigin,
712      PropertyId::BackgroundClip(vendor_prefix),
713    ]
714  }
715
716  fn longhand(&self, property_id: &PropertyId) -> Option<Property<'i>> {
717    match property_id {
718      PropertyId::BackgroundColor => Some(Property::BackgroundColor(self.last().unwrap().color.clone())),
719      PropertyId::BackgroundImage => Some(Property::BackgroundImage(
720        self.iter().map(|v| v.image.clone()).collect(),
721      )),
722      PropertyId::BackgroundPositionX => Some(Property::BackgroundPositionX(
723        self.iter().map(|v| v.position.x.clone()).collect(),
724      )),
725      PropertyId::BackgroundPositionY => Some(Property::BackgroundPositionY(
726        self.iter().map(|v| v.position.y.clone()).collect(),
727      )),
728      PropertyId::BackgroundRepeat => Some(Property::BackgroundRepeat(
729        self.iter().map(|v| v.repeat.clone()).collect(),
730      )),
731      PropertyId::BackgroundSize => Some(Property::BackgroundSize(self.iter().map(|v| v.size.clone()).collect())),
732      PropertyId::BackgroundAttachment => Some(Property::BackgroundAttachment(
733        self.iter().map(|v| v.attachment.clone()).collect(),
734      )),
735      PropertyId::BackgroundOrigin => Some(Property::BackgroundOrigin(
736        self.iter().map(|v| v.origin.clone()).collect(),
737      )),
738      PropertyId::BackgroundClip(vp) => Some(Property::BackgroundClip(
739        self.iter().map(|v| v.clip.clone()).collect(),
740        *vp,
741      )),
742      _ => None,
743    }
744  }
745
746  fn set_longhand(&mut self, property: &Property<'i>) -> Result<(), ()> {
747    macro_rules! longhand {
748      ($value: ident, $key: ident $(.$k: ident)*) => {{
749        if $value.len() != self.len() {
750          return Err(());
751        }
752        for (i, item) in self.iter_mut().enumerate() {
753          item.$key$(.$k)* = $value[i].clone();
754        }
755      }};
756    }
757
758    match property {
759      Property::BackgroundColor(value) => self.last_mut().unwrap().color = value.clone(),
760      Property::BackgroundImage(value) => longhand!(value, image),
761      Property::BackgroundPositionX(value) => longhand!(value, position.x),
762      Property::BackgroundPositionY(value) => longhand!(value, position.y),
763      Property::BackgroundPosition(value) => longhand!(value, position),
764      Property::BackgroundRepeat(value) => longhand!(value, repeat),
765      Property::BackgroundSize(value) => longhand!(value, size),
766      Property::BackgroundAttachment(value) => longhand!(value, attachment),
767      Property::BackgroundOrigin(value) => longhand!(value, origin),
768      Property::BackgroundClip(value, _vp) => longhand!(value, clip),
769      _ => return Err(()),
770    }
771
772    Ok(())
773  }
774}
775
776property_bitflags! {
777  #[derive(Default)]
778  struct BackgroundProperty: u16 {
779    const BackgroundColor = 1 << 0;
780    const BackgroundImage = 1 << 1;
781    const BackgroundPositionX = 1 << 2;
782    const BackgroundPositionY = 1 << 3;
783    const BackgroundPosition = Self::BackgroundPositionX.bits() | Self::BackgroundPositionY.bits();
784    const BackgroundRepeat = 1 << 4;
785    const BackgroundSize = 1 << 5;
786    const BackgroundAttachment = 1 << 6;
787    const BackgroundOrigin = 1 << 7;
788    const BackgroundClip(_vp) = 1 << 8;
789    const Background = Self::BackgroundColor.bits() | Self::BackgroundImage.bits() | Self::BackgroundPosition.bits() | Self::BackgroundRepeat.bits() | Self::BackgroundSize.bits() | Self::BackgroundAttachment.bits() | Self::BackgroundOrigin.bits() | Self::BackgroundClip.bits();
790  }
791}
792
793#[derive(Default)]
794pub(crate) struct BackgroundHandler<'i> {
795  color: Option<CssColor>,
796  images: Option<SmallVec<[Image<'i>; 1]>>,
797  has_prefix: bool,
798  x_positions: Option<SmallVec<[HorizontalPosition; 1]>>,
799  y_positions: Option<SmallVec<[VerticalPosition; 1]>>,
800  repeats: Option<SmallVec<[BackgroundRepeat; 1]>>,
801  sizes: Option<SmallVec<[BackgroundSize; 1]>>,
802  attachments: Option<SmallVec<[BackgroundAttachment; 1]>>,
803  origins: Option<SmallVec<[BackgroundOrigin; 1]>>,
804  clips: Option<(SmallVec<[BackgroundClip; 1]>, VendorPrefix)>,
805  decls: Vec<Property<'i>>,
806  flushed_properties: BackgroundProperty,
807  has_any: bool,
808}
809
810impl<'i> PropertyHandler<'i> for BackgroundHandler<'i> {
811  fn handle_property(
812    &mut self,
813    property: &Property<'i>,
814    dest: &mut DeclarationList<'i>,
815    context: &mut PropertyHandlerContext<'i, '_>,
816  ) -> bool {
817    macro_rules! background_image {
818      ($val: expr) => {
819        flush!(images, $val);
820
821        // Store prefixed properties. Clear if we hit an unprefixed property and we have
822        // targets. In this case, the necessary prefixes will be generated.
823        self.has_prefix = $val.iter().any(|x| x.has_vendor_prefix());
824        if self.has_prefix {
825          self.decls.push(property.clone())
826        } else if context.targets.browsers.is_some() {
827          self.decls.clear();
828        }
829      };
830    }
831
832    macro_rules! flush {
833      ($key: ident, $val: expr) => {{
834        if self.$key.is_some() && self.$key.as_ref().unwrap() != $val && matches!(context.targets.browsers, Some(targets) if !$val.is_compatible(targets)) {
835          self.flush(dest, context);
836        }
837      }};
838    }
839
840    match &property {
841      Property::BackgroundColor(val) => {
842        flush!(color, val);
843        self.color = Some(val.clone());
844      }
845      Property::BackgroundImage(val) => {
846        background_image!(val);
847        self.images = Some(val.clone())
848      }
849      Property::BackgroundPosition(val) => {
850        self.x_positions = Some(val.iter().map(|p| p.x.clone()).collect());
851        self.y_positions = Some(val.iter().map(|p| p.y.clone()).collect());
852      }
853      Property::BackgroundPositionX(val) => self.x_positions = Some(val.clone()),
854      Property::BackgroundPositionY(val) => self.y_positions = Some(val.clone()),
855      Property::BackgroundRepeat(val) => self.repeats = Some(val.clone()),
856      Property::BackgroundSize(val) => self.sizes = Some(val.clone()),
857      Property::BackgroundAttachment(val) => self.attachments = Some(val.clone()),
858      Property::BackgroundOrigin(val) => self.origins = Some(val.clone()),
859      Property::BackgroundClip(val, vendor_prefix) => {
860        if let Some((clips, vp)) = &mut self.clips {
861          if vendor_prefix != vp && val != clips {
862            self.flush(dest, context);
863            self.clips = Some((val.clone(), *vendor_prefix))
864          } else {
865            if val != clips {
866              *clips = val.clone();
867            }
868            *vp |= *vendor_prefix;
869          }
870        } else {
871          self.clips = Some((val.clone(), *vendor_prefix))
872        }
873      }
874      Property::Background(val) => {
875        let images: SmallVec<[Image; 1]> = val.iter().map(|b| b.image.clone()).collect();
876        background_image!(&images);
877        let color = val.last().unwrap().color.clone();
878        flush!(color, &color);
879        let clips = val.iter().map(|b| b.clip.clone()).collect();
880        let mut clips_vp = VendorPrefix::None;
881        if let Some((cur_clips, cur_clips_vp)) = &mut self.clips {
882          if clips_vp != *cur_clips_vp && *cur_clips != clips {
883            self.flush(dest, context);
884          } else {
885            clips_vp |= *cur_clips_vp;
886          }
887        }
888        self.color = Some(color);
889        self.images = Some(images);
890        self.x_positions = Some(val.iter().map(|b| b.position.x.clone()).collect());
891        self.y_positions = Some(val.iter().map(|b| b.position.y.clone()).collect());
892        self.repeats = Some(val.iter().map(|b| b.repeat.clone()).collect());
893        self.sizes = Some(val.iter().map(|b| b.size.clone()).collect());
894        self.attachments = Some(val.iter().map(|b| b.attachment.clone()).collect());
895        self.origins = Some(val.iter().map(|b| b.origin.clone()).collect());
896        self.clips = Some((clips, clips_vp));
897      }
898      Property::Unparsed(val) if is_background_property(&val.property_id) => {
899        self.flush(dest, context);
900        let mut unparsed = val.clone();
901        context.add_unparsed_fallbacks(&mut unparsed);
902        self
903          .flushed_properties
904          .insert(BackgroundProperty::try_from(&unparsed.property_id).unwrap());
905        dest.push(Property::Unparsed(unparsed));
906      }
907      _ => return false,
908    }
909
910    self.has_any = true;
911    true
912  }
913
914  fn finalize(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {
915    // If the last declaration is prefixed, pop the last value
916    // so it isn't duplicated when we flush.
917    if self.has_prefix {
918      self.decls.pop();
919    }
920
921    dest.extend(self.decls.drain(..));
922    self.flush(dest, context);
923    self.flushed_properties = BackgroundProperty::empty();
924  }
925}
926
927impl<'i> BackgroundHandler<'i> {
928  fn flush(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {
929    if !self.has_any {
930      return;
931    }
932
933    self.has_any = false;
934
935    macro_rules! push {
936      ($prop: ident, $val: expr) => {
937        dest.push(Property::$prop($val));
938        self.flushed_properties.insert(BackgroundProperty::$prop);
939      };
940    }
941
942    let color = std::mem::take(&mut self.color);
943    let mut images = std::mem::take(&mut self.images);
944    let mut x_positions = std::mem::take(&mut self.x_positions);
945    let mut y_positions = std::mem::take(&mut self.y_positions);
946    let mut repeats = std::mem::take(&mut self.repeats);
947    let mut sizes = std::mem::take(&mut self.sizes);
948    let mut attachments = std::mem::take(&mut self.attachments);
949    let mut origins = std::mem::take(&mut self.origins);
950    let mut clips = std::mem::take(&mut self.clips);
951
952    if let (
953      Some(color),
954      Some(images),
955      Some(x_positions),
956      Some(y_positions),
957      Some(repeats),
958      Some(sizes),
959      Some(attachments),
960      Some(origins),
961      Some(clips),
962    ) = (
963      &color,
964      &mut images,
965      &mut x_positions,
966      &mut y_positions,
967      &mut repeats,
968      &mut sizes,
969      &mut attachments,
970      &mut origins,
971      &mut clips,
972    ) {
973      // Only use shorthand syntax if the number of layers matches on all properties.
974      let len = images.len();
975      if x_positions.len() == len
976        && y_positions.len() == len
977        && repeats.len() == len
978        && sizes.len() == len
979        && attachments.len() == len
980        && origins.len() == len
981        && clips.0.len() == len
982      {
983        let clip_prefixes = if clips.0.iter().any(|clip| *clip == BackgroundClip::Text) {
984          context.targets.prefixes(clips.1, Feature::BackgroundClip)
985        } else {
986          clips.1
987        };
988
989        let clip_property = if clip_prefixes != VendorPrefix::None {
990          Some(Property::BackgroundClip(clips.0.clone(), clip_prefixes))
991        } else {
992          None
993        };
994
995        let mut backgrounds: SmallVec<[Background<'i>; 1]> = izip!(
996          images.drain(..),
997          x_positions.drain(..),
998          y_positions.drain(..),
999          repeats.drain(..),
1000          sizes.drain(..),
1001          attachments.drain(..),
1002          origins.drain(..),
1003          clips.0.drain(..)
1004        )
1005        .enumerate()
1006        .map(
1007          |(i, (image, x_position, y_position, repeat, size, attachment, origin, clip))| Background {
1008            color: if i == len - 1 {
1009              color.clone()
1010            } else {
1011              CssColor::default()
1012            },
1013            image,
1014            position: BackgroundPosition {
1015              x: x_position,
1016              y: y_position,
1017            },
1018            repeat,
1019            size,
1020            attachment,
1021            origin,
1022            clip: if clip_prefixes == VendorPrefix::None {
1023              clip
1024            } else {
1025              BackgroundClip::default()
1026            },
1027          },
1028        )
1029        .collect();
1030
1031        if !self.flushed_properties.intersects(BackgroundProperty::Background) {
1032          for fallback in backgrounds.get_fallbacks(context.targets) {
1033            push!(Background, fallback);
1034          }
1035        }
1036
1037        push!(Background, backgrounds);
1038
1039        if let Some(clip) = clip_property {
1040          dest.push(clip);
1041          self.flushed_properties.insert(BackgroundProperty::BackgroundClip);
1042        }
1043
1044        self.reset();
1045        return;
1046      }
1047    }
1048
1049    if let Some(mut color) = color {
1050      if !self.flushed_properties.contains(BackgroundProperty::BackgroundColor) {
1051        for fallback in color.get_fallbacks(context.targets) {
1052          push!(BackgroundColor, fallback);
1053        }
1054      }
1055
1056      push!(BackgroundColor, color);
1057    }
1058
1059    if let Some(mut images) = images {
1060      if !self.flushed_properties.contains(BackgroundProperty::BackgroundImage) {
1061        for fallback in images.get_fallbacks(context.targets) {
1062          push!(BackgroundImage, fallback);
1063        }
1064      }
1065
1066      push!(BackgroundImage, images);
1067    }
1068
1069    match (&mut x_positions, &mut y_positions) {
1070      (Some(x_positions), Some(y_positions)) if x_positions.len() == y_positions.len() => {
1071        let positions = izip!(x_positions.drain(..), y_positions.drain(..))
1072          .map(|(x, y)| BackgroundPosition { x, y })
1073          .collect();
1074        push!(BackgroundPosition, positions);
1075      }
1076      _ => {
1077        if let Some(x_positions) = x_positions {
1078          push!(BackgroundPositionX, x_positions);
1079        }
1080
1081        if let Some(y_positions) = y_positions {
1082          push!(BackgroundPositionY, y_positions);
1083        }
1084      }
1085    }
1086
1087    if let Some(repeats) = repeats {
1088      push!(BackgroundRepeat, repeats);
1089    }
1090
1091    if let Some(sizes) = sizes {
1092      push!(BackgroundSize, sizes);
1093    }
1094
1095    if let Some(attachments) = attachments {
1096      push!(BackgroundAttachment, attachments);
1097    }
1098
1099    if let Some(origins) = origins {
1100      push!(BackgroundOrigin, origins);
1101    }
1102
1103    if let Some((clips, vp)) = clips {
1104      let prefixes = if clips.iter().any(|clip| *clip == BackgroundClip::Text) {
1105        context.targets.prefixes(vp, Feature::BackgroundClip)
1106      } else {
1107        vp
1108      };
1109      dest.push(Property::BackgroundClip(clips, prefixes));
1110      self.flushed_properties.insert(BackgroundProperty::BackgroundClip);
1111    }
1112
1113    self.reset();
1114  }
1115
1116  fn reset(&mut self) {
1117    self.color = None;
1118    self.images = None;
1119    self.x_positions = None;
1120    self.y_positions = None;
1121    self.repeats = None;
1122    self.sizes = None;
1123    self.attachments = None;
1124    self.origins = None;
1125    self.clips = None
1126  }
1127}
1128
1129#[inline]
1130fn is_background_property(property_id: &PropertyId) -> bool {
1131  match property_id {
1132    PropertyId::BackgroundColor
1133    | PropertyId::BackgroundImage
1134    | PropertyId::BackgroundPosition
1135    | PropertyId::BackgroundPositionX
1136    | PropertyId::BackgroundPositionY
1137    | PropertyId::BackgroundRepeat
1138    | PropertyId::BackgroundSize
1139    | PropertyId::BackgroundAttachment
1140    | PropertyId::BackgroundOrigin
1141    | PropertyId::BackgroundClip(_)
1142    | PropertyId::Background => true,
1143    _ => false,
1144  }
1145}