//! CSS properties related to backgrounds. use crate::context::PropertyHandlerContext; use crate::declaration::{DeclarationBlock, DeclarationList}; use crate::error::{ParserError, PrinterError}; use crate::macros::*; use crate::prefixes::Feature; use crate::printer::Printer; use crate::properties::{Property, PropertyId, VendorPrefix}; use crate::targets::Browsers; use crate::traits::{FallbackValues, Parse, PropertyHandler, Shorthand, ToCss}; use crate::values::color::ColorFallbackKind; use crate::values::image::ImageFallback; use crate::values::{color::CssColor, image::Image, length::LengthPercentageOrAuto, position::*}; #[cfg(feature = "visitor")] use crate::visitor::Visit; use cssparser::*; use itertools::izip; use smallvec::SmallVec; /// A value for the [background-size](https://www.w3.org/TR/css-backgrounds-3/#background-size) property. #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(tag = "type", rename_all = "kebab-case") )] #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] pub enum BackgroundSize { /// An explicit background size. Explicit { /// The width of the background. width: LengthPercentageOrAuto, /// The height of the background. height: LengthPercentageOrAuto, }, /// The `cover` keyword. Scales the background image to cover both the width and height of the element. Cover, /// The `contain` keyword. Scales the background image so that it fits within the element. Contain, } impl Default for BackgroundSize { fn default() -> BackgroundSize { BackgroundSize::Explicit { width: LengthPercentageOrAuto::Auto, height: LengthPercentageOrAuto::Auto, } } } impl<'i> Parse<'i> for BackgroundSize { fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { if let Ok(width) = input.try_parse(LengthPercentageOrAuto::parse) { let height = input .try_parse(LengthPercentageOrAuto::parse) .unwrap_or(LengthPercentageOrAuto::Auto); return Ok(BackgroundSize::Explicit { width, height }); } let location = input.current_source_location(); let ident = input.expect_ident()?; Ok(match_ignore_ascii_case! { ident, "cover" => BackgroundSize::Cover, "contain" => BackgroundSize::Contain, _ => return Err(location.new_unexpected_token_error( cssparser::Token::Ident(ident.clone()) )) }) } } impl ToCss for BackgroundSize { fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> where W: std::fmt::Write, { use BackgroundSize::*; match &self { Cover => dest.write_str("cover"), Contain => dest.write_str("contain"), Explicit { width, height } => { width.to_css(dest)?; if *height != LengthPercentageOrAuto::Auto { dest.write_str(" ")?; height.to_css(dest)?; } Ok(()) } } } } enum_property! { /// A [``](https://www.w3.org/TR/css-backgrounds-3/#typedef-repeat-style) value, /// used within the `background-repeat` property to represent how a background image is repeated /// in a single direction. /// /// See [BackgroundRepeat](BackgroundRepeat). pub enum BackgroundRepeatKeyword { /// The image is repeated in this direction. "repeat": Repeat, /// The image is repeated so that it fits, and then spaced apart evenly. "space": Space, /// The image is scaled so that it repeats an even number of times. "round": Round, /// The image is placed once and not repeated in this direction. "no-repeat": NoRepeat, } } /// A value for the [background-repeat](https://www.w3.org/TR/css-backgrounds-3/#background-repeat) property. #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] pub struct BackgroundRepeat { /// A repeat style for the x direction. pub x: BackgroundRepeatKeyword, /// A repeat style for the y direction. pub y: BackgroundRepeatKeyword, } impl Default for BackgroundRepeat { fn default() -> BackgroundRepeat { BackgroundRepeat { x: BackgroundRepeatKeyword::Repeat, y: BackgroundRepeatKeyword::Repeat, } } } impl<'i> Parse<'i> for BackgroundRepeat { fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { use BackgroundRepeatKeyword::*; let state = input.state(); let ident = input.expect_ident()?; match_ignore_ascii_case! { ident, "repeat-x" => return Ok(BackgroundRepeat { x: Repeat, y: NoRepeat }), "repeat-y" => return Ok(BackgroundRepeat { x: NoRepeat, y: Repeat }), _ => {} } input.reset(&state); let x = BackgroundRepeatKeyword::parse(input)?; let y = input.try_parse(BackgroundRepeatKeyword::parse).unwrap_or(x.clone()); Ok(BackgroundRepeat { x, y }) } } impl ToCss for BackgroundRepeat { fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> where W: std::fmt::Write, { use BackgroundRepeatKeyword::*; match (&self.x, &self.y) { (Repeat, NoRepeat) => dest.write_str("repeat-x"), (NoRepeat, Repeat) => dest.write_str("repeat-y"), (x, y) => { x.to_css(dest)?; if y != x { dest.write_str(" ")?; y.to_css(dest)?; } Ok(()) } } } } enum_property! { /// A value for the [background-attachment](https://www.w3.org/TR/css-backgrounds-3/#background-attachment) property. pub enum BackgroundAttachment { /// The background scrolls with the container. Scroll, /// The background is fixed to the viewport. Fixed, /// The background is fixed with regard to the element’s contents. Local, } } impl Default for BackgroundAttachment { fn default() -> BackgroundAttachment { BackgroundAttachment::Scroll } } enum_property! { /// A value for the [background-origin](https://www.w3.org/TR/css-backgrounds-3/#background-origin) property. pub enum BackgroundOrigin { /// The position is relative to the border box. "border-box": BorderBox, /// The position is relative to the padding box. "padding-box": PaddingBox, /// The position is relative to the content box. "content-box": ContentBox, } } enum_property! { /// A value for the [background-clip](https://drafts.csswg.org/css-backgrounds-4/#background-clip) property. pub enum BackgroundClip { /// The background is clipped to the border box. "border-box": BorderBox, /// The background is clipped to the padding box. "padding-box": PaddingBox, /// The background is clipped to the content box. "content-box": ContentBox, /// The background is clipped to the area painted by the border. "border": Border, /// The background is clipped to the text content of the element. "text": Text, } } impl PartialEq for BackgroundClip { fn eq(&self, other: &BackgroundOrigin) -> bool { match (self, other) { (BackgroundClip::BorderBox, BackgroundOrigin::BorderBox) | (BackgroundClip::PaddingBox, BackgroundOrigin::PaddingBox) | (BackgroundClip::ContentBox, BackgroundOrigin::ContentBox) => true, _ => false, } } } impl Into for BackgroundOrigin { fn into(self) -> BackgroundClip { match self { BackgroundOrigin::BorderBox => BackgroundClip::BorderBox, BackgroundOrigin::PaddingBox => BackgroundClip::PaddingBox, BackgroundOrigin::ContentBox => BackgroundClip::ContentBox, } } } impl Default for BackgroundClip { fn default() -> BackgroundClip { BackgroundClip::BorderBox } } impl BackgroundClip { fn is_background_box(&self) -> bool { matches!( self, BackgroundClip::BorderBox | BackgroundClip::PaddingBox | BackgroundClip::ContentBox ) } } define_list_shorthand! { /// A value for the [background-position](https://drafts.csswg.org/css-backgrounds/#background-position) shorthand property. pub struct BackgroundPosition { /// The x-position. x: BackgroundPositionX(HorizontalPosition), /// The y-position. y: BackgroundPositionY(VerticalPosition), } } impl From for BackgroundPosition { fn from(pos: Position) -> Self { BackgroundPosition { x: pos.x, y: pos.y } } } impl Into for &BackgroundPosition { fn into(self) -> Position { Position { x: self.x.clone(), y: self.y.clone(), } } } impl Default for BackgroundPosition { fn default() -> Self { Position::default().into() } } impl<'i> Parse<'i> for BackgroundPosition { fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { let pos = Position::parse(input)?; Ok(pos.into()) } } impl ToCss for BackgroundPosition { fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> where W: std::fmt::Write, { let pos: Position = self.into(); pos.to_css(dest) } } /// A value for the [background](https://www.w3.org/TR/css-backgrounds-3/#background) shorthand property. #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] pub struct Background<'i> { /// The background image. #[cfg_attr(feature = "serde", serde(borrow))] pub image: Image<'i>, /// The background color. pub color: CssColor, /// The background position. pub position: BackgroundPosition, /// How the background image should repeat. pub repeat: BackgroundRepeat, /// The size of the background image. pub size: BackgroundSize, /// The background attachment. pub attachment: BackgroundAttachment, /// The background origin. pub origin: BackgroundOrigin, /// How the background should be clipped. pub clip: BackgroundClip, } impl<'i> Parse<'i> for Background<'i> { fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { let mut color: Option = None; let mut position: Option = None; let mut size: Option = None; let mut image: Option = None; let mut repeat: Option = None; let mut attachment: Option = None; let mut origin: Option = None; let mut clip: Option = None; loop { // TODO: only allowed on the last background. if color.is_none() { if let Ok(value) = input.try_parse(CssColor::parse) { color = Some(value); continue; } } if position.is_none() { if let Ok(value) = input.try_parse(BackgroundPosition::parse) { position = Some(value); size = input .try_parse(|input| { input.expect_delim('/')?; BackgroundSize::parse(input) }) .ok(); continue; } } if image.is_none() { if let Ok(value) = input.try_parse(Image::parse) { image = Some(value); continue; } } if repeat.is_none() { if let Ok(value) = input.try_parse(BackgroundRepeat::parse) { repeat = Some(value); continue; } } if attachment.is_none() { if let Ok(value) = input.try_parse(BackgroundAttachment::parse) { attachment = Some(value); continue; } } if origin.is_none() { if let Ok(value) = input.try_parse(BackgroundOrigin::parse) { origin = Some(value); continue; } } if clip.is_none() { if let Ok(value) = input.try_parse(BackgroundClip::parse) { clip = Some(value); continue; } } break; } if clip.is_none() { if let Some(origin) = origin { clip = Some(origin.into()); } } Ok(Background { image: image.unwrap_or_default(), color: color.unwrap_or_default(), position: position.unwrap_or_default(), repeat: repeat.unwrap_or_default(), size: size.unwrap_or_default(), attachment: attachment.unwrap_or_default(), origin: origin.unwrap_or(BackgroundOrigin::PaddingBox), clip: clip.unwrap_or(BackgroundClip::BorderBox), }) } } impl<'i> ToCss for Background<'i> { fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> where W: std::fmt::Write, { let mut has_output = false; if self.color != CssColor::default() { self.color.to_css(dest)?; has_output = true; } if self.image != Image::default() { if has_output { dest.write_str(" ")?; } self.image.to_css(dest)?; has_output = true; } let position: Position = (&self.position).into(); if !position.is_zero() || self.size != BackgroundSize::default() { if has_output { dest.write_str(" ")?; } position.to_css(dest)?; if self.size != BackgroundSize::default() { dest.delim('/', true)?; self.size.to_css(dest)?; } has_output = true; } if self.repeat != BackgroundRepeat::default() { if has_output { dest.write_str(" ")?; } self.repeat.to_css(dest)?; has_output = true; } if self.attachment != BackgroundAttachment::default() { if has_output { dest.write_str(" ")?; } self.attachment.to_css(dest)?; has_output = true; } let output_padding_box = self.origin != BackgroundOrigin::PaddingBox || (self.clip != BackgroundOrigin::BorderBox && self.clip.is_background_box()); if output_padding_box { if has_output { dest.write_str(" ")?; } self.origin.to_css(dest)?; has_output = true; } if (output_padding_box && self.clip != self.origin) || self.clip != BackgroundOrigin::BorderBox { if has_output { dest.write_str(" ")?; } self.clip.to_css(dest)?; has_output = true; } // If nothing was output, then this is the initial value, e.g. background: transparent if !has_output { if dest.minify { // `0 0` is the shortest valid background value self.position.to_css(dest)?; } else { dest.write_str("none")?; } } Ok(()) } } impl<'i> ImageFallback<'i> for Background<'i> { #[inline] fn get_image(&self) -> &Image<'i> { &self.image } #[inline] fn with_image(&self, image: Image<'i>) -> Self { Background { image, ..self.clone() } } #[inline] fn get_necessary_fallbacks(&self, targets: Browsers) -> ColorFallbackKind { self.color.get_necessary_fallbacks(targets) | self.get_image().get_necessary_fallbacks(targets) } #[inline] fn get_fallback(&self, kind: ColorFallbackKind) -> Self { Background { color: self.color.get_fallback(kind), image: self.image.get_fallback(kind), ..self.clone() } } } impl<'i> Shorthand<'i> for SmallVec<[Background<'i>; 1]> { fn from_longhands(decls: &DeclarationBlock<'i>, vendor_prefix: VendorPrefix) -> Option<(Self, bool)> { let mut color = None; let mut images = None; let mut x_positions = None; let mut y_positions = None; let mut repeats = None; let mut sizes = None; let mut attachments = None; let mut origins = None; let mut clips = None; let mut count = 0; let mut important_count = 0; let mut length = None; for (property, important) in decls.iter() { let len = match property { Property::BackgroundColor(value) => { color = Some(value.clone()); count += 1; if important { important_count += 1; } continue; } Property::BackgroundImage(value) => { images = Some(value.clone()); value.len() } Property::BackgroundPosition(value) => { x_positions = Some(value.iter().map(|v| v.x.clone()).collect()); y_positions = Some(value.iter().map(|v| v.y.clone()).collect()); value.len() } Property::BackgroundPositionX(value) => { x_positions = Some(value.clone()); value.len() } Property::BackgroundPositionY(value) => { y_positions = Some(value.clone()); value.len() } Property::BackgroundRepeat(value) => { repeats = Some(value.clone()); value.len() } Property::BackgroundSize(value) => { sizes = Some(value.clone()); value.len() } Property::BackgroundAttachment(value) => { attachments = Some(value.clone()); value.len() } Property::BackgroundOrigin(value) => { origins = Some(value.clone()); value.len() } Property::BackgroundClip(value, vp) => { if *vp != vendor_prefix { return None; } clips = Some(value.clone()); value.len() } Property::Background(val) => { color = Some(val.last().unwrap().color.clone()); images = Some(val.iter().map(|b| b.image.clone()).collect()); x_positions = Some(val.iter().map(|b| b.position.x.clone()).collect()); y_positions = Some(val.iter().map(|b| b.position.y.clone()).collect()); repeats = Some(val.iter().map(|b| b.repeat.clone()).collect()); sizes = Some(val.iter().map(|b| b.size.clone()).collect()); attachments = Some(val.iter().map(|b| b.attachment.clone()).collect()); origins = Some(val.iter().map(|b| b.origin.clone()).collect()); clips = Some(val.iter().map(|b| b.clip.clone()).collect()); val.len() } _ => continue, }; // Lengths must be equal. if length.is_none() { length = Some(len); } else if length.unwrap() != len { return None; } count += 1; if important { important_count += 1; } } // !important flags must match to produce a shorthand. if important_count > 0 && important_count != count { return None; } if color.is_some() && images.is_some() && x_positions.is_some() && y_positions.is_some() && repeats.is_some() && sizes.is_some() && attachments.is_some() && origins.is_some() && clips.is_some() { let length = length.unwrap(); let values = izip!( images.unwrap().drain(..), x_positions.unwrap().drain(..), y_positions.unwrap().drain(..), repeats.unwrap().drain(..), sizes.unwrap().drain(..), attachments.unwrap().drain(..), origins.unwrap().drain(..), clips.unwrap().drain(..), ) .enumerate() .map( |(i, (image, x_position, y_position, repeat, size, attachment, origin, clip))| Background { color: if i == length - 1 { color.clone().unwrap() } else { CssColor::default() }, image, position: BackgroundPosition { x: x_position, y: y_position, }, repeat, size, attachment, origin, clip: clip, }, ) .collect(); return Some((values, important_count > 0)); } None } fn longhands(vendor_prefix: VendorPrefix) -> Vec> { vec![ PropertyId::BackgroundColor, PropertyId::BackgroundImage, PropertyId::BackgroundPositionX, PropertyId::BackgroundPositionY, PropertyId::BackgroundRepeat, PropertyId::BackgroundSize, PropertyId::BackgroundAttachment, PropertyId::BackgroundOrigin, PropertyId::BackgroundClip(vendor_prefix), ] } fn longhand(&self, property_id: &PropertyId) -> Option> { match property_id { PropertyId::BackgroundColor => Some(Property::BackgroundColor(self.last().unwrap().color.clone())), PropertyId::BackgroundImage => Some(Property::BackgroundImage( self.iter().map(|v| v.image.clone()).collect(), )), PropertyId::BackgroundPositionX => Some(Property::BackgroundPositionX( self.iter().map(|v| v.position.x.clone()).collect(), )), PropertyId::BackgroundPositionY => Some(Property::BackgroundPositionY( self.iter().map(|v| v.position.y.clone()).collect(), )), PropertyId::BackgroundRepeat => Some(Property::BackgroundRepeat( self.iter().map(|v| v.repeat.clone()).collect(), )), PropertyId::BackgroundSize => Some(Property::BackgroundSize(self.iter().map(|v| v.size.clone()).collect())), PropertyId::BackgroundAttachment => Some(Property::BackgroundAttachment( self.iter().map(|v| v.attachment.clone()).collect(), )), PropertyId::BackgroundOrigin => Some(Property::BackgroundOrigin( self.iter().map(|v| v.origin.clone()).collect(), )), PropertyId::BackgroundClip(vp) => Some(Property::BackgroundClip( self.iter().map(|v| v.clip.clone()).collect(), *vp, )), _ => None, } } fn set_longhand(&mut self, property: &Property<'i>) -> Result<(), ()> { macro_rules! longhand { ($value: ident, $key: ident $(.$k: ident)*) => {{ if $value.len() != self.len() { return Err(()); } for (i, item) in self.iter_mut().enumerate() { item.$key$(.$k)* = $value[i].clone(); } }}; } match property { Property::BackgroundColor(value) => self.last_mut().unwrap().color = value.clone(), Property::BackgroundImage(value) => longhand!(value, image), Property::BackgroundPositionX(value) => longhand!(value, position.x), Property::BackgroundPositionY(value) => longhand!(value, position.y), Property::BackgroundPosition(value) => longhand!(value, position), Property::BackgroundRepeat(value) => longhand!(value, repeat), Property::BackgroundSize(value) => longhand!(value, size), Property::BackgroundAttachment(value) => longhand!(value, attachment), Property::BackgroundOrigin(value) => longhand!(value, origin), Property::BackgroundClip(value, _vp) => longhand!(value, clip), _ => return Err(()), } Ok(()) } } #[derive(Default)] pub(crate) struct BackgroundHandler<'i> { targets: Option, color: Option, images: Option; 1]>>, has_prefix: bool, x_positions: Option>, y_positions: Option>, repeats: Option>, sizes: Option>, attachments: Option>, origins: Option>, clips: Option>, decls: Vec>, has_any: bool, } impl<'i> BackgroundHandler<'i> { pub fn new(targets: Option) -> Self { BackgroundHandler { targets, ..BackgroundHandler::default() } } } impl<'i> PropertyHandler<'i> for BackgroundHandler<'i> { fn handle_property( &mut self, property: &Property<'i>, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>, ) -> bool { macro_rules! background_image { ($val: ident) => { // If this is an image-set() and not all of our targets support it, preserve previous fallback. if Image::should_preserve_fallbacks(&$val, self.images.as_ref(), self.targets) { self.flush(dest); } // Store prefixed properties. Clear if we hit an unprefixed property and we have // targets. In this case, the necessary prefixes will be generated. self.has_prefix = $val.iter().any(|x| x.has_vendor_prefix()); if self.has_prefix { self.decls.push(property.clone()) } else if self.targets.is_some() { self.decls.clear(); } }; } match &property { Property::BackgroundColor(val) => self.color = Some(val.clone()), Property::BackgroundImage(val) => { background_image!(val); self.images = Some(val.clone()) } Property::BackgroundPosition(val) => { self.x_positions = Some(val.iter().map(|p| p.x.clone()).collect()); self.y_positions = Some(val.iter().map(|p| p.y.clone()).collect()); } Property::BackgroundPositionX(val) => self.x_positions = Some(val.clone()), Property::BackgroundPositionY(val) => self.y_positions = Some(val.clone()), Property::BackgroundRepeat(val) => self.repeats = Some(val.clone()), Property::BackgroundSize(val) => self.sizes = Some(val.clone()), Property::BackgroundAttachment(val) => self.attachments = Some(val.clone()), Property::BackgroundOrigin(val) => self.origins = Some(val.clone()), Property::BackgroundClip(val, vendor_prefix) => { if *vendor_prefix == VendorPrefix::None { self.clips = Some(val.clone()); } else { self.flush(dest); dest.push(property.clone()) } } Property::Background(val) => { let images: SmallVec<[Image; 1]> = val.iter().map(|b| b.image.clone()).collect(); background_image!(images); self.color = Some(val.last().unwrap().color.clone()); self.images = Some(images); self.x_positions = Some(val.iter().map(|b| b.position.x.clone()).collect()); self.y_positions = Some(val.iter().map(|b| b.position.y.clone()).collect()); self.repeats = Some(val.iter().map(|b| b.repeat.clone()).collect()); self.sizes = Some(val.iter().map(|b| b.size.clone()).collect()); self.attachments = Some(val.iter().map(|b| b.attachment.clone()).collect()); self.origins = Some(val.iter().map(|b| b.origin.clone()).collect()); self.clips = Some(val.iter().map(|b| b.clip.clone()).collect()); } Property::Unparsed(val) if is_background_property(&val.property_id) => { self.flush(dest); let mut unparsed = val.clone(); context.add_unparsed_fallbacks(&mut unparsed); dest.push(Property::Unparsed(unparsed)) } _ => return false, } self.has_any = true; true } fn finalize(&mut self, dest: &mut DeclarationList<'i>, _: &mut PropertyHandlerContext<'i, '_>) { // If the last declaration is prefixed, pop the last value // so it isn't duplicated when we flush. if self.has_prefix { self.decls.pop(); } dest.extend(self.decls.drain(..)); self.flush(dest); } } impl<'i> BackgroundHandler<'i> { fn flush(&mut self, dest: &mut DeclarationList<'i>) { if !self.has_any { return; } self.has_any = false; let color = std::mem::take(&mut self.color); let mut images = std::mem::take(&mut self.images); let mut x_positions = std::mem::take(&mut self.x_positions); let mut y_positions = std::mem::take(&mut self.y_positions); let mut repeats = std::mem::take(&mut self.repeats); let mut sizes = std::mem::take(&mut self.sizes); let mut attachments = std::mem::take(&mut self.attachments); let mut origins = std::mem::take(&mut self.origins); let mut clips = std::mem::take(&mut self.clips); if let ( Some(color), Some(images), Some(x_positions), Some(y_positions), Some(repeats), Some(sizes), Some(attachments), Some(origins), Some(clips), ) = ( &color, &mut images, &mut x_positions, &mut y_positions, &mut repeats, &mut sizes, &mut attachments, &mut origins, &mut clips, ) { // Only use shorthand syntax if the number of layers matches on all properties. let len = images.len(); if x_positions.len() == len && y_positions.len() == len && repeats.len() == len && sizes.len() == len && attachments.len() == len && origins.len() == len && clips.len() == len { let clip_prefixes = if let Some(targets) = self.targets { if clips.iter().any(|clip| *clip == BackgroundClip::Text) { Feature::BackgroundClip.prefixes_for(targets) } else { VendorPrefix::None } } else { VendorPrefix::None }; let clip_property = if clip_prefixes != VendorPrefix::None { Some(Property::BackgroundClip(clips.clone(), clip_prefixes)) } else { None }; let mut backgrounds: SmallVec<[Background<'i>; 1]> = izip!( images.drain(..), x_positions.drain(..), y_positions.drain(..), repeats.drain(..), sizes.drain(..), attachments.drain(..), origins.drain(..), clips.drain(..) ) .enumerate() .map( |(i, (image, x_position, y_position, repeat, size, attachment, origin, clip))| Background { color: if i == len - 1 { color.clone() } else { CssColor::default() }, image, position: BackgroundPosition { x: x_position, y: y_position, }, repeat, size, attachment, origin, clip: if clip_prefixes == VendorPrefix::None { clip } else { BackgroundClip::default() }, }, ) .collect(); if let Some(targets) = self.targets { for fallback in backgrounds.get_fallbacks(targets) { dest.push(Property::Background(fallback)); } } dest.push(Property::Background(backgrounds)); if let Some(clip) = clip_property { dest.push(clip) } self.reset(); return; } } if let Some(mut color) = color { if let Some(targets) = self.targets { for fallback in color.get_fallbacks(targets) { dest.push(Property::BackgroundColor(fallback)) } } dest.push(Property::BackgroundColor(color)) } if let Some(mut images) = images { if let Some(targets) = self.targets { for fallback in images.get_fallbacks(targets) { dest.push(Property::BackgroundImage(fallback)); } } dest.push(Property::BackgroundImage(images)) } match (&mut x_positions, &mut y_positions) { (Some(x_positions), Some(y_positions)) if x_positions.len() == y_positions.len() => { let positions = izip!(x_positions.drain(..), y_positions.drain(..)) .map(|(x, y)| BackgroundPosition { x, y }) .collect(); dest.push(Property::BackgroundPosition(positions)) } _ => { if let Some(x_positions) = x_positions { dest.push(Property::BackgroundPositionX(x_positions)) } if let Some(y_positions) = y_positions { dest.push(Property::BackgroundPositionY(y_positions)) } } } if let Some(repeats) = repeats { dest.push(Property::BackgroundRepeat(repeats)) } if let Some(sizes) = sizes { dest.push(Property::BackgroundSize(sizes)) } if let Some(attachments) = attachments { dest.push(Property::BackgroundAttachment(attachments)) } if let Some(origins) = origins { dest.push(Property::BackgroundOrigin(origins)) } if let Some(clips) = clips { let prefixes = if let Some(targets) = self.targets { if clips.iter().any(|clip| *clip == BackgroundClip::Text) { Feature::BackgroundClip.prefixes_for(targets) } else { VendorPrefix::None } } else { VendorPrefix::None }; dest.push(Property::BackgroundClip(clips, prefixes)) } self.reset(); } fn reset(&mut self) { self.color = None; self.images = None; self.x_positions = None; self.y_positions = None; self.repeats = None; self.sizes = None; self.attachments = None; self.origins = None; self.clips = None } } #[inline] fn is_background_property(property_id: &PropertyId) -> bool { match property_id { PropertyId::BackgroundColor | PropertyId::BackgroundImage | PropertyId::BackgroundPosition | PropertyId::BackgroundPositionX | PropertyId::BackgroundPositionY | PropertyId::BackgroundRepeat | PropertyId::BackgroundSize | PropertyId::BackgroundAttachment | PropertyId::BackgroundOrigin | PropertyId::BackgroundClip(_) | PropertyId::Background => true, _ => false, } }