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, Targets};
use crate::traits::{FallbackValues, IsCompatible, 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;
#[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))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
pub enum BackgroundSize {
Explicit {
width: LengthPercentageOrAuto,
height: LengthPercentageOrAuto,
},
Cover,
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<Self, ParseError<'i, ParserError<'i>>> {
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<W>(&self, dest: &mut Printer<W>) -> 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(())
}
}
}
}
impl IsCompatible for BackgroundSize {
fn is_compatible(&self, browsers: Browsers) -> bool {
match self {
BackgroundSize::Explicit { width, height } => {
width.is_compatible(browsers) && height.is_compatible(browsers)
}
BackgroundSize::Cover | BackgroundSize::Contain => true,
}
}
}
enum_property! {
pub enum BackgroundRepeatKeyword {
Repeat,
Space,
Round,
NoRepeat,
}
}
#[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))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
pub struct BackgroundRepeat {
pub x: BackgroundRepeatKeyword,
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<Self, ParseError<'i, ParserError<'i>>> {
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<W>(&self, dest: &mut Printer<W>) -> 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(())
}
}
}
}
impl IsCompatible for BackgroundRepeat {
fn is_compatible(&self, _browsers: Browsers) -> bool {
true
}
}
enum_property! {
pub enum BackgroundAttachment {
Scroll,
Fixed,
Local,
}
}
impl Default for BackgroundAttachment {
fn default() -> BackgroundAttachment {
BackgroundAttachment::Scroll
}
}
enum_property! {
pub enum BackgroundOrigin {
BorderBox,
PaddingBox,
ContentBox,
}
}
enum_property! {
pub enum BackgroundClip {
BorderBox,
PaddingBox,
ContentBox,
Border,
Text,
}
}
impl PartialEq<BackgroundOrigin> 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<BackgroundClip> 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! {
pub struct BackgroundPosition {
x: BackgroundPositionX(HorizontalPosition),
y: BackgroundPositionY(VerticalPosition),
}
}
impl From<Position> for BackgroundPosition {
fn from(pos: Position) -> Self {
BackgroundPosition { x: pos.x, y: pos.y }
}
}
impl Into<Position> 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<Self, ParseError<'i, ParserError<'i>>> {
let pos = Position::parse(input)?;
Ok(pos.into())
}
}
impl ToCss for BackgroundPosition {
fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
let pos: Position = self.into();
pos.to_css(dest)
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub struct Background<'i> {
#[cfg_attr(feature = "serde", serde(borrow))]
pub image: Image<'i>,
pub color: CssColor,
pub position: BackgroundPosition,
pub repeat: BackgroundRepeat,
pub size: BackgroundSize,
pub attachment: BackgroundAttachment,
pub origin: BackgroundOrigin,
pub clip: BackgroundClip,
}
impl<'i> Parse<'i> for Background<'i> {
fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
let mut color: Option<CssColor> = None;
let mut position: Option<BackgroundPosition> = None;
let mut size: Option<BackgroundSize> = None;
let mut image: Option<Image> = None;
let mut repeat: Option<BackgroundRepeat> = None;
let mut attachment: Option<BackgroundAttachment> = None;
let mut origin: Option<BackgroundOrigin> = None;
let mut clip: Option<BackgroundClip> = None;
loop {
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<W>(&self, dest: &mut Printer<W>) -> 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 !has_output {
if dest.minify {
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: Targets) -> 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,
};
if length.is_none() {
length = Some(len);
} else if length.unwrap() != len {
return None;
}
count += 1;
if important {
important_count += 1;
}
}
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<PropertyId<'static>> {
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<Property<'i>> {
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(())
}
}
property_bitflags! {
#[derive(Default)]
struct BackgroundProperty: u16 {
const BackgroundColor = 1 << 0;
const BackgroundImage = 1 << 1;
const BackgroundPositionX = 1 << 2;
const BackgroundPositionY = 1 << 3;
const BackgroundPosition = Self::BackgroundPositionX.bits() | Self::BackgroundPositionY.bits();
const BackgroundRepeat = 1 << 4;
const BackgroundSize = 1 << 5;
const BackgroundAttachment = 1 << 6;
const BackgroundOrigin = 1 << 7;
const BackgroundClip(_vp) = 1 << 8;
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();
}
}
#[derive(Default)]
pub(crate) struct BackgroundHandler<'i> {
color: Option<CssColor>,
images: Option<SmallVec<[Image<'i>; 1]>>,
has_prefix: bool,
x_positions: Option<SmallVec<[HorizontalPosition; 1]>>,
y_positions: Option<SmallVec<[VerticalPosition; 1]>>,
repeats: Option<SmallVec<[BackgroundRepeat; 1]>>,
sizes: Option<SmallVec<[BackgroundSize; 1]>>,
attachments: Option<SmallVec<[BackgroundAttachment; 1]>>,
origins: Option<SmallVec<[BackgroundOrigin; 1]>>,
clips: Option<(SmallVec<[BackgroundClip; 1]>, VendorPrefix)>,
decls: Vec<Property<'i>>,
flushed_properties: BackgroundProperty,
has_any: bool,
}
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: expr) => {
flush!(images, $val);
self.has_prefix = $val.iter().any(|x| x.has_vendor_prefix());
if self.has_prefix {
self.decls.push(property.clone())
} else if context.targets.browsers.is_some() {
self.decls.clear();
}
};
}
macro_rules! flush {
($key: ident, $val: expr) => {{
if self.$key.is_some() && self.$key.as_ref().unwrap() != $val && matches!(context.targets.browsers, Some(targets) if !$val.is_compatible(targets)) {
self.flush(dest, context);
}
}};
}
match &property {
Property::BackgroundColor(val) => {
flush!(color, 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 let Some((clips, vp)) = &mut self.clips {
if vendor_prefix != vp && val != clips {
self.flush(dest, context);
self.clips = Some((val.clone(), *vendor_prefix))
} else {
if val != clips {
*clips = val.clone();
}
*vp |= *vendor_prefix;
}
} else {
self.clips = Some((val.clone(), *vendor_prefix))
}
}
Property::Background(val) => {
let images: SmallVec<[Image; 1]> = val.iter().map(|b| b.image.clone()).collect();
background_image!(&images);
let color = val.last().unwrap().color.clone();
flush!(color, &color);
let clips = val.iter().map(|b| b.clip.clone()).collect();
let mut clips_vp = VendorPrefix::None;
if let Some((cur_clips, cur_clips_vp)) = &mut self.clips {
if clips_vp != *cur_clips_vp && *cur_clips != clips {
self.flush(dest, context);
} else {
clips_vp |= *cur_clips_vp;
}
}
self.color = Some(color);
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((clips, clips_vp));
}
Property::Unparsed(val) if is_background_property(&val.property_id) => {
self.flush(dest, context);
let mut unparsed = val.clone();
context.add_unparsed_fallbacks(&mut unparsed);
self
.flushed_properties
.insert(BackgroundProperty::try_from(&unparsed.property_id).unwrap());
dest.push(Property::Unparsed(unparsed));
}
_ => return false,
}
self.has_any = true;
true
}
fn finalize(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {
if self.has_prefix {
self.decls.pop();
}
dest.extend(self.decls.drain(..));
self.flush(dest, context);
self.flushed_properties = BackgroundProperty::empty();
}
}
impl<'i> BackgroundHandler<'i> {
fn flush(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {
if !self.has_any {
return;
}
self.has_any = false;
macro_rules! push {
($prop: ident, $val: expr) => {
dest.push(Property::$prop($val));
self.flushed_properties.insert(BackgroundProperty::$prop);
};
}
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,
) {
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.0.len() == len
{
let clip_prefixes = if clips.0.iter().any(|clip| *clip == BackgroundClip::Text) {
context.targets.prefixes(clips.1, Feature::BackgroundClip)
} else {
clips.1
};
let clip_property = if clip_prefixes != VendorPrefix::None {
Some(Property::BackgroundClip(clips.0.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.0.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 !self.flushed_properties.intersects(BackgroundProperty::Background) {
for fallback in backgrounds.get_fallbacks(context.targets) {
push!(Background, fallback);
}
}
push!(Background, backgrounds);
if let Some(clip) = clip_property {
dest.push(clip);
self.flushed_properties.insert(BackgroundProperty::BackgroundClip);
}
self.reset();
return;
}
}
if let Some(mut color) = color {
if !self.flushed_properties.contains(BackgroundProperty::BackgroundColor) {
for fallback in color.get_fallbacks(context.targets) {
push!(BackgroundColor, fallback);
}
}
push!(BackgroundColor, color);
}
if let Some(mut images) = images {
if !self.flushed_properties.contains(BackgroundProperty::BackgroundImage) {
for fallback in images.get_fallbacks(context.targets) {
push!(BackgroundImage, fallback);
}
}
push!(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();
push!(BackgroundPosition, positions);
}
_ => {
if let Some(x_positions) = x_positions {
push!(BackgroundPositionX, x_positions);
}
if let Some(y_positions) = y_positions {
push!(BackgroundPositionY, y_positions);
}
}
}
if let Some(repeats) = repeats {
push!(BackgroundRepeat, repeats);
}
if let Some(sizes) = sizes {
push!(BackgroundSize, sizes);
}
if let Some(attachments) = attachments {
push!(BackgroundAttachment, attachments);
}
if let Some(origins) = origins {
push!(BackgroundOrigin, origins);
}
if let Some((clips, vp)) = clips {
let prefixes = if clips.iter().any(|clip| *clip == BackgroundClip::Text) {
context.targets.prefixes(vp, Feature::BackgroundClip)
} else {
vp
};
dest.push(Property::BackgroundClip(clips, prefixes));
self.flushed_properties.insert(BackgroundProperty::BackgroundClip);
}
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,
}
}