From 1b947b480674a170fcac22af0a730b70cfce93a0 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Mon, 2 May 2022 15:21:20 -0400 Subject: [PATCH] Foundations for CSS OM (#165) --- src/declaration.rs | 195 +++++++++- src/lib.rs | 5 +- src/logical.rs | 18 +- src/macros.rs | 486 +++++++++++++++++++++++- src/properties/align.rs | 64 ++-- src/properties/animation.rs | 43 +-- src/properties/background.rs | 287 ++++++++++++++- src/properties/border.rs | 395 +++++++++++++++----- src/properties/border_image.rs | 100 +++-- src/properties/border_radius.rs | 28 +- src/properties/custom.rs | 7 +- src/properties/flex.rs | 36 +- src/properties/font.rs | 94 +++-- src/properties/grid.rs | 203 +++++++---- src/properties/list.rs | 12 +- src/properties/margin_padding.rs | 198 +++++++++- src/properties/masking.rs | 131 ++++--- src/properties/mod.rs | 609 +++++++++++++++++++------------ src/properties/outline.rs | 28 +- src/properties/overflow.rs | 21 +- src/properties/svg.rs | 2 +- src/properties/text.rs | 44 +-- src/properties/transition.rs | 29 +- src/properties/ui.rs | 10 +- src/rules/counter_style.rs | 2 +- src/rules/font_face.rs | 2 +- src/rules/font_palette_values.rs | 5 +- src/rules/keyframes.rs | 2 +- src/rules/page.rs | 2 +- src/rules/viewport.rs | 2 +- src/stylesheet.rs | 19 +- src/traits.rs | 20 +- tests/test_cssom.rs | 508 ++++++++++++++++++++++++++ 33 files changed, 2860 insertions(+), 747 deletions(-) create mode 100644 tests/test_cssom.rs diff --git a/src/declaration.rs b/src/declaration.rs index e14e671b..9ed84e33 100644 --- a/src/declaration.rs +++ b/src/declaration.rs @@ -1,12 +1,13 @@ //! CSS declarations. +use std::borrow::Cow; + use crate::context::PropertyHandlerContext; use crate::error::{ParserError, PrinterError}; use crate::parser::ParserOptions; use crate::printer::Printer; use crate::properties::box_shadow::BoxShadowHandler; use crate::properties::masking::MaskHandler; -use crate::properties::Property; use crate::properties::{ align::AlignHandler, animation::AnimationHandler, @@ -27,8 +28,10 @@ use crate::properties::{ transform::TransformHandler, transition::TransitionHandler, }; +use crate::properties::{Property, PropertyId}; use crate::targets::Browsers; use crate::traits::{PropertyHandler, ToCss}; +use crate::values::string::CowArcStr; use cssparser::*; /// A CSS declaration block. @@ -71,10 +74,46 @@ impl<'i> DeclarationBlock<'i> { declarations, }) } + + /// Parses a declaration block from a string. + pub fn parse_string(input: &'i str, options: ParserOptions) -> Result>> { + let mut input = ParserInput::new(input); + let mut parser = Parser::new(&mut input); + let result = Self::parse(&mut parser, &options)?; + parser.expect_exhausted()?; + Ok(result) + } } impl<'i> ToCss for DeclarationBlock<'i> { fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> + where + W: std::fmt::Write, + { + let len = self.declarations.len() + self.important_declarations.len(); + let mut i = 0; + + macro_rules! write { + ($decls: expr, $important: literal) => { + for decl in &$decls { + decl.to_css(dest, $important)?; + if i != len - 1 { + dest.write_char(';')?; + dest.whitespace()?; + } + i += 1; + } + }; + } + + write!(self.declarations, false); + write!(self.important_declarations, true); + Ok(()) + } +} + +impl<'i> DeclarationBlock<'i> { + pub(crate) fn to_css_block(&self, dest: &mut Printer) -> Result<(), PrinterError> where W: std::fmt::Write, { @@ -142,6 +181,156 @@ impl<'i> DeclarationBlock<'i> { } } +impl<'i> DeclarationBlock<'i> { + /// Returns an iterator over all properties in the declaration. + pub fn iter(&self) -> impl std::iter::DoubleEndedIterator, bool)> { + self + .declarations + .iter() + .map(|property| (property, false)) + .chain(self.important_declarations.iter().map(|property| (property, true))) + } + + /// Returns a mutable iterator over all properties in the declaration. + pub fn iter_mut(&mut self) -> impl std::iter::DoubleEndedIterator> { + self.declarations.iter_mut().chain(self.important_declarations.iter_mut()) + } + + /// Returns the value for a given property id based on the properties in this declaration block. + /// + /// If the property is a shorthand, the result will be a combined value of all of the included + /// longhands, or `None` if some of the longhands are not declared. Otherwise, the value will be + /// either an explicitly declared longhand, or a value extracted from a shorthand property. + pub fn get<'a>(&'a self, property_id: &PropertyId) -> Option<(Cow<'a, Property<'i>>, bool)> { + if property_id.is_shorthand() { + if let Some((shorthand, important)) = property_id.shorthand_value(&self) { + return Some((Cow::Owned(shorthand), important)); + } + } else { + for (property, important) in self.iter().rev() { + if property.property_id() == *property_id { + return Some((Cow::Borrowed(property), important)); + } + + if let Some(val) = property.longhand(&property_id) { + return Some((Cow::Owned(val), important)); + } + } + } + + None + } + + /// Sets the value and importance for a given property, replacing any existing declarations. + /// + /// If the property already exists within the declaration block, it is updated in place. Otherwise, + /// a new declaration is appended. When updating a longhand property and a shorthand is defined which + /// includes the longhand, the shorthand will be updated rather than appending a new declaration. + pub fn set(&mut self, property: Property<'i>, important: bool) { + let property_id = property.property_id(); + let declarations = if important { + // Remove any non-important properties with this id. + self.declarations.retain(|decl| decl.property_id() != property_id); + &mut self.important_declarations + } else { + // Remove any important properties with this id. + self.important_declarations.retain(|decl| decl.property_id() != property_id); + &mut self.declarations + }; + + let longhands = property_id.longhands().unwrap_or_else(|| vec![property.property_id()]); + + for decl in declarations.iter_mut().rev() { + { + // If any of the longhands being set are in the same logical property group as any of the + // longhands in this property, but in a different category (i.e. logical or physical), + // then we cannot modify in place, and need to append a new property. + let id = decl.property_id(); + let id_longhands = id.longhands().unwrap_or_else(|| vec![id]); + if longhands.iter().any(|longhand| { + let logical_group = longhand.logical_group(); + let category = longhand.category(); + + logical_group.is_some() + && id_longhands.iter().any(|id_longhand| { + logical_group == id_longhand.logical_group() && category != id_longhand.category() + }) + }) { + break; + } + } + + if decl.property_id() == property_id { + *decl = property; + return; + } + + // Update shorthand. + if decl.set_longhand(&property).is_ok() { + return; + } + } + + declarations.push(property) + } + + /// Removes all declarations of the given property id from the declaration block. + /// + /// When removing a longhand property and a shorthand is defined which includes the longhand, + /// the shorthand will be split apart into its component longhand properties, minus the property + /// to remove. When removing a shorthand, all included longhand properties are also removed. + pub fn remove(&mut self, property_id: &PropertyId) { + fn remove<'i, 'a>(declarations: &mut Vec>, property_id: &PropertyId<'a>) { + let longhands = property_id.longhands().unwrap_or(vec![]); + let mut i = 0; + while i < declarations.len() { + let replacement = { + let property = &declarations[i]; + let id = property.property_id(); + if id == *property_id || longhands.contains(&id) { + // If the property matches the requested property id, or is a longhand + // property that is included in the requested shorthand, remove it. + None + } else if longhands.is_empty() && id.longhands().unwrap_or(vec![]).contains(&property_id) { + // If this is a shorthand property that includes the requested longhand, + // split it apart into its component longhands, excluding the requested one. + Some( + id.longhands() + .unwrap() + .iter() + .filter_map(|longhand| { + if *longhand == *property_id { + None + } else { + property.longhand(longhand) + } + }) + .collect::>(), + ) + } else { + i += 1; + continue; + } + }; + + match replacement { + Some(properties) => { + let count = properties.len(); + declarations.splice(i..i + 1, properties); + i += count; + } + None => { + declarations.remove(i); + } + } + } + } + + remove(&mut self.declarations, property_id); + remove(&mut self.important_declarations, property_id); + } +} + struct PropertyDeclarationParser<'a, 'i> { important_declarations: &'a mut Vec>, declarations: &'a mut Vec>, @@ -182,7 +371,9 @@ pub(crate) fn parse_declaration<'i, 't>( important_declarations: &mut DeclarationList<'i>, options: &ParserOptions, ) -> Result<(), cssparser::ParseError<'i, ParserError<'i>>> { - let property = input.parse_until_before(Delimiter::Bang, |input| Property::parse(name, input, options))?; + let property = input.parse_until_before(Delimiter::Bang, |input| { + Property::parse(PropertyId::from(CowArcStr::from(name)), input, options) + })?; let important = input .try_parse(|input| { input.expect_delim('!')?; diff --git a/src/lib.rs b/src/lib.rs index a08b5524..ef335d6e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -566,8 +566,7 @@ mod tests { indoc! {r#" .foo { border-block-width: 1px; - border-inline-start-width: 2px; - border-inline-end-width: 3px; + border-inline-width: 2px 3px; } "# }, @@ -17798,7 +17797,7 @@ mod tests { }"#} ); - let property = Property::parse_string("color", "#f0f", ParserOptions::default()).unwrap(); + let property = Property::parse_string("color".into(), "#f0f", ParserOptions::default()).unwrap(); assert_eq!( property.to_css_string(false, PrinterOptions::default()).unwrap(), "color: #f0f" diff --git a/src/logical.rs b/src/logical.rs index c675a19d..f662d2f1 100644 --- a/src/logical.rs +++ b/src/logical.rs @@ -1,5 +1,5 @@ #[derive(Debug, PartialEq)] -pub(crate) enum PropertyCategory { +pub enum PropertyCategory { Logical, Physical, } @@ -9,3 +9,19 @@ impl Default for PropertyCategory { PropertyCategory::Physical } } + +#[derive(PartialEq)] +pub enum LogicalGroup { + BorderColor, + BorderStyle, + BorderWidth, + BorderRadius, + Margin, + ScrollMargin, + Padding, + ScrollPadding, + Inset, + Size, + MinSize, + MaxSize, +} diff --git a/src/macros.rs b/src/macros.rs index eab0b136..e93b023a 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -120,22 +120,23 @@ macro_rules! shorthand_property { $(#[$outer:meta])* $vis:vis struct $name: ident$(<$l: lifetime>)? { $(#[$first_meta: meta])* - $first_key: ident: $first_type: ty, + $first_key: ident: $first_prop: ident($first_type: ty $(, $first_vp: ty)?), $( $(#[$meta: meta])* - $key: ident: $type: ty, + $key: ident: $prop: ident($type: ty $(, $vp: ty)?), )* } ) => { - $(#[$outer])* - #[derive(Debug, Clone, PartialEq)] - pub struct $name$(<$l>)? { - $(#[$first_meta])* - pub $first_key: $first_type, - $( - $(#[$meta])* - pub $key: $type, - )* + define_shorthand! { + $(#[$outer])* + pub struct $name$(<$l>)? { + $(#[$first_meta])* + $first_key: $first_prop($first_type $($first_vp)?), + $( + $(#[$meta])* + $key: $prop($type $($vp)?), + )* + } } impl<'i> Parse<'i> for $name$(<$l>)? { @@ -305,3 +306,466 @@ macro_rules! shorthand_handler { } pub(crate) use shorthand_handler; + +macro_rules! define_shorthand { + ( + $(#[$outer:meta])* + $vis:vis struct $name: ident$(<$l: lifetime>)?$(($prefix: ty))? { + $( + $(#[$meta: meta])* + $key: ident: $prop: ident($type: ty $(, $vp: ty)?), + )+ + } + ) => { + $(#[$outer])* + #[derive(Debug, Clone, PartialEq)] + pub struct $name$(<$l>)? { + $( + $(#[$meta])* + pub $key: $type, + )+ + } + + crate::macros::impl_shorthand! { + $name($name$(<$l>)? $(, $prefix)?) { + $( + $key: [ $prop$(($vp))?, ], + )+ + } + } + }; +} + +pub(crate) use define_shorthand; + +macro_rules! impl_shorthand { + ( + $name: ident($t: ty $(, $prefix: ty)?) { + $( + $key: ident: [ $( $prop: ident$(($vp: ty))? $(,)?)+ ], + )+ + } + + $( + fn is_valid($v: ident) { + $($body: tt)+ + } + )? + ) => { + #[allow(unused_macros)] + macro_rules! vp_name { + ($x: ty, $n: ident) => { + $n + }; + ($x: ty, $n: expr) => { + $n + }; + } + + impl<'i> Shorthand<'i> for $t { + #[allow(unused_variables)] + fn from_longhands(decls: &DeclarationBlock<'i>, vendor_prefix: crate::vendor_prefix::VendorPrefix) -> Option<(Self, bool)> { + $( + $( + #[allow(non_snake_case)] + let mut $prop = None; + )+ + )+ + + let mut count = 0; + let mut important_count = 0; + for (property, important) in decls.iter() { + match property { + $( + $( + Property::$prop(val $(, vp_name!($vp, p))?) => { + $( + if *vp_name!($vp, p) != vendor_prefix { + return None + } + )? + + $prop = Some(val.clone()); + count += 1; + if important { + important_count += 1; + } + } + )+ + )+ + Property::$name(val $(, vp_name!($prefix, p))?) => { + $( + if *vp_name!($prefix, p) != vendor_prefix { + return None + } + )? + + $( + $( + $prop = Some(val.$key.clone()); + count += 1; + if important { + important_count += 1; + } + )+ + )+ + } + _ => { + $( + $( + if let Some(Property::$prop(longhand $(, vp_name!($vp, _p))?)) = property.longhand(&PropertyId::$prop$((vp_name!($vp, vendor_prefix)))?) { + $prop = Some(longhand); + 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 $($($prop.is_some() &&)+)+ true { + // All properties in the group must have a matching value to produce a shorthand. + $( + let mut $key = None; + $( + if $key == None { + $key = $prop; + } else if $key != $prop { + return None + } + )+ + )+ + + let value = $name { + $( + $key: $key.unwrap(), + )+ + }; + + $( + #[inline] + fn is_valid($v: &$name) -> bool { + $($body)+ + } + + if !is_valid(&value) { + return None + } + )? + + return Some((value, important_count > 0)); + } + + None + } + + #[allow(unused_variables)] + fn longhands(vendor_prefix: crate::vendor_prefix::VendorPrefix) -> Vec> { + vec![$($(PropertyId::$prop$((vp_name!($vp, vendor_prefix)))?, )+)+] + } + + fn longhand(&self, property_id: &PropertyId) -> Option> { + match property_id { + $( + $( + PropertyId::$prop$((vp_name!($vp, p)))? => { + Some(Property::$prop(self.$key.clone() $(, *vp_name!($vp, p))?)) + } + )+ + )+ + _ => None + } + } + + fn set_longhand(&mut self, property: &Property<'i>) -> Result<(), ()> { + macro_rules! count { + ($p: ident) => { + 1 + } + } + + $( + #[allow(non_upper_case_globals)] + const $key: u8 = 0 $( + count!($prop))+; + )+ + + match property { + $( + $( + Property::$prop(val $(, vp_name!($vp, _p))?) => { + // If more than one longhand maps to this key, bail. + if $key > 1 { + return Err(()) + } + self.$key = val.clone(); + return Ok(()) + } + )+ + )+ + _ => {} + } + Err(()) + } + } + } +} + +pub(crate) use impl_shorthand; + +macro_rules! define_list_shorthand { + ( + $(#[$outer:meta])* + $vis:vis struct $name: ident$(<$l: lifetime>)?$(($prefix: ty))? { + $( + $(#[$meta: meta])* + $key: ident: $prop: ident($type: ty $(, $vp: ty)?), + )+ + } + ) => { + $(#[$outer])* + #[derive(Debug, Clone, PartialEq)] + pub struct $name$(<$l>)? { + $( + $(#[$meta])* + pub $key: $type, + )+ + } + + #[allow(unused_macros)] + macro_rules! vp_name { + ($x: ty, $n: ident) => { + $n + }; + ($x: ty, $n: expr) => { + $n + }; + } + + impl<'i> Shorthand<'i> for SmallVec<[$name$(<$l>)?; 1]> { + #[allow(unused_variables)] + fn from_longhands(decls: &DeclarationBlock<'i>, vendor_prefix: crate::vendor_prefix::VendorPrefix) -> Option<(Self, bool)> { + $( + let mut $key = None; + )+ + + let mut count = 0; + let mut important_count = 0; + let mut length = None; + for (property, important) in decls.iter() { + let mut len = 0; + match property { + $( + Property::$prop(val $(, vp_name!($vp, p))?) => { + $( + if *vp_name!($vp, p) != vendor_prefix { + return None + } + )? + + $key = Some(val.clone()); + len = val.len(); + count += 1; + if important { + important_count += 1; + } + } + )+ + Property::$name(val $(, vp_name!($prefix, p))?) => { + $( + if *vp_name!($prefix, p) != vendor_prefix { + return None + } + )? + $( + $key = Some(val.iter().map(|b| b.$key.clone()).collect()); + )+ + len = val.len(); + count += 1; + if important { + important_count += 1; + } + } + _ => { + $( + if let Some(Property::$prop(longhand $(, vp_name!($vp, _p))?)) = property.longhand(&PropertyId::$prop$((vp_name!($vp, vendor_prefix)))?) { + len = longhand.len(); + $key = Some(longhand); + count += 1; + if important { + important_count += 1; + } + } + )+ + } + } + + // Lengths must be equal. + if length.is_none() { + length = Some(len); + } else if length.unwrap() != len { + return None + } + } + + // !important flags must match to produce a shorthand. + if important_count > 0 && important_count != count { + return None + } + + if $($key.is_some() &&)+ true { + let values = izip!( + $( + $key.unwrap().drain(..), + )+ + ).map(|($($key,)+)| { + $name { + $( + $key, + )+ + } + }).collect(); + return Some((values, important_count > 0)) + } + + None + } + + #[allow(unused_variables)] + fn longhands(vendor_prefix: crate::vendor_prefix::VendorPrefix) -> Vec> { + vec![$(PropertyId::$prop$((vp_name!($vp, vendor_prefix)))?, )+] + } + + fn longhand(&self, property_id: &PropertyId) -> Option> { + match property_id { + $( + PropertyId::$prop$((vp_name!($vp, p)))? => { + Some(Property::$prop(self.iter().map(|v| v.$key.clone()).collect() $(, *vp_name!($vp, p))?)) + } + )+ + _ => None + } + } + + fn set_longhand(&mut self, property: &Property<'i>) -> Result<(), ()> { + match property { + $( + Property::$prop(val $(, vp_name!($vp, _p))?) => { + if val.len() != self.len() { + return Err(()) + } + + for (i, item) in self.iter_mut().enumerate() { + item.$key = val[i].clone(); + } + return Ok(()) + } + )+ + _ => {} + } + Err(()) + } + } + }; +} + +pub(crate) use define_list_shorthand; + +macro_rules! rect_shorthand { + ( + $(#[$meta: meta])* + $vis:vis struct $name: ident<$t: ty> { + $top: ident, + $right: ident, + $bottom: ident, + $left: ident + } + ) => { + define_shorthand! { + $(#[$meta])* + pub struct $name { + /// The top value. + top: $top($t), + /// The right value. + right: $right($t), + /// The bottom value. + bottom: $bottom($t), + /// The left value. + left: $left($t), + } + } + + impl<'i> Parse<'i> for $name { + fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { + let rect = Rect::parse(input)?; + Ok(Self { + top: rect.0, + right: rect.1, + bottom: rect.2, + left: rect.3, + }) + } + } + + impl ToCss for $name { + fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> + where + W: std::fmt::Write, + { + Rect::new(&self.top, &self.right, &self.bottom, &self.left).to_css(dest) + } + } + }; +} + +pub(crate) use rect_shorthand; + +macro_rules! size_shorthand { + ( + $(#[$outer:meta])* + $vis:vis struct $name: ident<$t: ty> { + $(#[$a_meta: meta])* + $a_key: ident: $a_prop: ident, + $(#[$b_meta: meta])* + $b_key: ident: $b_prop: ident, + } + ) => { + define_shorthand! { + $(#[$outer])* + $vis struct $name { + $(#[$a_meta])* + $a_key: $a_prop($t), + $(#[$b_meta])* + $b_key: $b_prop($t), + } + } + + impl<'i> Parse<'i> for $name { + fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { + let size = Size2D::parse(input)?; + Ok(Self { + $a_key: size.0, + $b_key: size.1, + }) + } + } + + impl ToCss for $name { + fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> + where + W: std::fmt::Write, + { + Size2D(&self.$a_key, &self.$b_key).to_css(dest) + } + } + }; +} + +pub(crate) use size_shorthand; diff --git a/src/properties/align.rs b/src/properties/align.rs index 01cd8806..0ae6145a 100644 --- a/src/properties/align.rs +++ b/src/properties/align.rs @@ -4,13 +4,13 @@ use super::flex::{BoxAlign, BoxPack, FlexAlign, FlexItemAlign, FlexLinePack, Fle use super::{Property, PropertyId}; use crate::compat; use crate::context::PropertyHandlerContext; -use crate::declaration::DeclarationList; +use crate::declaration::{DeclarationBlock, DeclarationList}; use crate::error::{ParserError, PrinterError}; use crate::macros::*; use crate::prefixes::{is_flex_2009, Feature}; use crate::printer::Printer; use crate::targets::Browsers; -use crate::traits::{FromStandard, Parse, PropertyHandler, ToCss}; +use crate::traits::{FromStandard, Parse, PropertyHandler, Shorthand, ToCss}; use crate::values::length::LengthPercentage; use crate::vendor_prefix::VendorPrefix; use cssparser::*; @@ -232,13 +232,14 @@ impl ToCss for JustifyContent { } } -/// A value for the [place-content](https://www.w3.org/TR/css-align-3/#place-content) shorthand property. -#[derive(Debug, Clone, PartialEq)] -pub struct PlaceContent { - /// The content alignment. - pub align: AlignContent, - /// The content justification. - pub justify: JustifyContent, +define_shorthand! { + /// A value for the [place-content](https://www.w3.org/TR/css-align-3/#place-content) shorthand property. + pub struct PlaceContent { + /// The content alignment. + align: AlignContent(AlignContent, VendorPrefix), + /// The content justification. + justify: JustifyContent(JustifyContent, VendorPrefix), + } } impl<'i> Parse<'i> for PlaceContent { @@ -462,13 +463,14 @@ impl ToCss for JustifySelf { } } -/// A value for the [place-self](https://www.w3.org/TR/css-align-3/#place-self-property) shorthand property. -#[derive(Debug, Clone, PartialEq)] -pub struct PlaceSelf { - /// The item alignment. - pub align: AlignSelf, - /// The item justification. - pub justify: JustifySelf, +define_shorthand! { + /// A value for the [place-self](https://www.w3.org/TR/css-align-3/#place-self-property) shorthand property. + pub struct PlaceSelf { + /// The item alignment. + align: AlignSelf(AlignSelf, VendorPrefix), + /// The item justification. + justify: JustifySelf(JustifySelf), + } } impl<'i> Parse<'i> for PlaceSelf { @@ -727,13 +729,14 @@ impl ToCss for JustifyItems { } } -/// A value for the [place-items](https://www.w3.org/TR/css-align-3/#place-items-property) shorthand property. -#[derive(Debug, Clone, PartialEq)] -pub struct PlaceItems { - /// The item alignment. - pub align: AlignItems, - /// The item justification. - pub justify: JustifyItems, +define_shorthand! { + /// A value for the [place-items](https://www.w3.org/TR/css-align-3/#place-items-property) shorthand property. + pub struct PlaceItems { + /// The item alignment. + align: AlignItems(AlignItems, VendorPrefix), + /// The item justification. + justify: JustifyItems(JustifyItems), + } } impl<'i> Parse<'i> for PlaceItems { @@ -816,13 +819,14 @@ impl ToCss for GapValue { } } -/// A value for the [gap](https://www.w3.org/TR/css-align-3/#gap-shorthand) shorthand property. -#[derive(Debug, Clone, PartialEq)] -pub struct Gap { - /// The row gap. - pub row: GapValue, - /// The column gap. - pub column: GapValue, +define_shorthand! { + /// A value for the [gap](https://www.w3.org/TR/css-align-3/#gap-shorthand) shorthand property. + pub struct Gap { + /// The row gap. + row: RowGap(GapValue), + /// The column gap. + column: ColumnGap(GapValue), + } } impl<'i> Parse<'i> for Gap { diff --git a/src/properties/animation.rs b/src/properties/animation.rs index 4025ed8d..8adcbb67 100644 --- a/src/properties/animation.rs +++ b/src/properties/animation.rs @@ -1,14 +1,14 @@ //! CSS properties related to keyframe animations. use crate::context::PropertyHandlerContext; -use crate::declaration::DeclarationList; +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::{Parse, PropertyHandler, ToCss}; +use crate::traits::{Parse, PropertyHandler, Shorthand, ToCss}; use crate::values::number::CSSNumber; use crate::values::{easing::EasingFunction, ident::CustomIdent, time::Time}; use cssparser::*; @@ -130,25 +130,26 @@ enum_property! { } } -/// A value for the [animation](https://drafts.csswg.org/css-animations/#animation) shorthand property. -#[derive(Debug, Clone, PartialEq)] -pub struct Animation<'i> { - /// The animation name. - pub name: AnimationName<'i>, - /// The animation duration. - pub duration: Time, - /// The easing function for the animation. - pub timing_function: EasingFunction, - /// The number of times the animation will run. - pub iteration_count: AnimationIterationCount, - /// The direction of the animation. - pub direction: AnimationDirection, - /// The current play state of the animation. - pub play_state: AnimationPlayState, - /// The animation delay. - pub delay: Time, - /// The animation fill mode. - pub fill_mode: AnimationFillMode, +define_list_shorthand! { + /// A value for the [animation](https://drafts.csswg.org/css-animations/#animation) shorthand property. + pub struct Animation<'i>(VendorPrefix) { + /// The animation name. + name: AnimationName(AnimationName<'i>, VendorPrefix), + /// The animation duration. + duration: AnimationDuration(Time, VendorPrefix), + /// The easing function for the animation. + timing_function: AnimationTimingFunction(EasingFunction, VendorPrefix), + /// The number of times the animation will run. + iteration_count: AnimationIterationCount(AnimationIterationCount, VendorPrefix), + /// The direction of the animation. + direction: AnimationDirection(AnimationDirection, VendorPrefix), + /// The current play state of the animation. + play_state: AnimationPlayState(AnimationPlayState, VendorPrefix), + /// The animation delay. + delay: AnimationDelay(Time, VendorPrefix), + /// The animation fill mode. + fill_mode: AnimationFillMode(AnimationFillMode, VendorPrefix), + } } impl<'i> Parse<'i> for Animation<'i> { diff --git a/src/properties/background.rs b/src/properties/background.rs index bb12ce4e..8655ef78 100644 --- a/src/properties/background.rs +++ b/src/properties/background.rs @@ -1,14 +1,14 @@ //! CSS properties related to backgrounds. use crate::context::PropertyHandlerContext; -use crate::declaration::DeclarationList; +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, ToCss}; +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::*}; @@ -243,6 +243,54 @@ impl BackgroundClip { } } +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)] pub struct Background<'i> { @@ -251,7 +299,7 @@ pub struct Background<'i> { /// The background color. pub color: CssColor, /// The background position. - pub position: Position, + pub position: BackgroundPosition, /// How the background image should repeat. pub repeat: BackgroundRepeat, /// The size of the background image. @@ -267,7 +315,7 @@ pub struct Background<'i> { 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 position: Option = None; let mut size: Option = None; let mut image: Option = None; let mut repeat: Option = None; @@ -285,7 +333,7 @@ impl<'i> Parse<'i> for Background<'i> { } if position.is_none() { - if let Ok(value) = input.try_parse(Position::parse) { + if let Ok(value) = input.try_parse(BackgroundPosition::parse) { position = Some(value); size = input @@ -376,11 +424,12 @@ impl<'i> ToCss for Background<'i> { has_output = true; } - if !self.position.is_zero() || self.size != BackgroundSize::default() { + let position: Position = (&self.position).into(); + if !position.is_zero() || self.size != BackgroundSize::default() { if has_output { dest.write_str(" ")?; } - self.position.to_css(dest)?; + position.to_css(dest)?; if self.size != BackgroundSize::default() { dest.delim('/', true)?; @@ -468,6 +517,226 @@ impl<'i> ImageFallback<'i> for Background<'i> { } } +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, @@ -660,7 +929,7 @@ impl<'i> BackgroundHandler<'i> { CssColor::default() }, image, - position: Position { + position: BackgroundPosition { x: x_position, y: y_position, }, @@ -717,7 +986,7 @@ impl<'i> BackgroundHandler<'i> { 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)| Position { x, y }) + .map(|(x, y)| BackgroundPosition { x, y }) .collect(); dest.push(Property::BackgroundPosition(positions)) } diff --git a/src/properties/border.rs b/src/properties/border.rs index f71daf79..1271fe02 100644 --- a/src/properties/border.rs +++ b/src/properties/border.rs @@ -4,7 +4,7 @@ use super::border_image::*; use super::border_radius::*; use crate::compat::Feature; use crate::context::PropertyHandlerContext; -use crate::declaration::DeclarationList; +use crate::declaration::{DeclarationBlock, DeclarationList}; use crate::error::{ParserError, PrinterError}; use crate::logical::PropertyCategory; use crate::macros::*; @@ -12,10 +12,11 @@ use crate::printer::Printer; use crate::properties::custom::UnparsedProperty; use crate::properties::{Property, PropertyId}; use crate::targets::Browsers; -use crate::traits::{FallbackValues, Parse, PropertyHandler, ToCss}; +use crate::traits::{FallbackValues, Parse, PropertyHandler, Shorthand, ToCss}; use crate::values::color::{ColorFallbackKind, CssColor}; use crate::values::length::*; use crate::values::rect::Rect; +use crate::values::size::Size2D; use cssparser::*; /// A value for the [border-width](https://www.w3.org/TR/css-backgrounds-3/#border-width) property. @@ -69,8 +70,8 @@ impl ToCss for BorderSideWidth { } enum_property! { - /// A value for the [border-style](https://www.w3.org/TR/css-backgrounds-3/#border-style) property. - pub enum BorderStyle { + /// A [``](https://drafts.csswg.org/css-backgrounds/#typedef-line-style) value, used in the `border-style` property. + pub enum LineStyle { /// No border. None, /// Similar to `none` but with different rules for tables. @@ -94,15 +95,15 @@ enum_property! { } } -impl Default for BorderStyle { - fn default() -> BorderStyle { - BorderStyle::None +impl Default for LineStyle { + fn default() -> LineStyle { + LineStyle::None } } /// A generic type that represents the `border` and `outline` shorthand properties. #[derive(Debug, Clone, PartialEq)] -pub struct GenericBorder { +pub struct GenericBorder { /// The width of the border. pub width: BorderSideWidth, /// The border style. @@ -111,8 +112,8 @@ pub struct GenericBorder { pub color: CssColor, } -impl Default for GenericBorder { - fn default() -> GenericBorder { +impl Default for GenericBorder { + fn default() -> GenericBorder { GenericBorder { width: BorderSideWidth::Medium, style: S::default(), @@ -121,7 +122,7 @@ impl Default for GenericBorder { } } -impl<'i, S: Parse<'i> + Default> Parse<'i> for GenericBorder { +impl<'i, S: Parse<'i> + Default, const P: u8> Parse<'i> for GenericBorder { fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { // Order doesn't matter... let mut color = None; @@ -163,7 +164,7 @@ impl<'i, S: Parse<'i> + Default> Parse<'i> for GenericBorder { } } -impl ToCss for GenericBorder { +impl ToCss for GenericBorder { fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> where W: std::fmt::Write, @@ -195,7 +196,7 @@ impl ToCss for GenericBorder { } } -impl FallbackValues for GenericBorder { +impl FallbackValues for GenericBorder { fn get_fallbacks(&mut self, targets: Browsers) -> Vec { self .color @@ -210,56 +211,258 @@ impl FallbackValues for GenericBorder { } } -impl FallbackValues for Rect { - fn get_fallbacks(&mut self, targets: Browsers) -> Vec { - let mut fallbacks = ColorFallbackKind::empty(); - fallbacks |= self.0.get_necessary_fallbacks(targets); - fallbacks |= self.1.get_necessary_fallbacks(targets); - fallbacks |= self.2.get_necessary_fallbacks(targets); - fallbacks |= self.3.get_necessary_fallbacks(targets); - - let mut res = Vec::new(); - if fallbacks.contains(ColorFallbackKind::RGB) { - res.push(Rect::new( - self.0.get_fallback(ColorFallbackKind::RGB), - self.1.get_fallback(ColorFallbackKind::RGB), - self.2.get_fallback(ColorFallbackKind::RGB), - self.3.get_fallback(ColorFallbackKind::RGB), - )); - } +/// A value for the [border-top](https://www.w3.org/TR/css-backgrounds-3/#propdef-border-top) shorthand property. +pub type BorderTop = GenericBorder; +/// A value for the [border-right](https://www.w3.org/TR/css-backgrounds-3/#propdef-border-right) shorthand property. +pub type BorderRight = GenericBorder; +/// A value for the [border-bottom](https://www.w3.org/TR/css-backgrounds-3/#propdef-border-bottom) shorthand property. +pub type BorderBottom = GenericBorder; +/// A value for the [border-left](https://www.w3.org/TR/css-backgrounds-3/#propdef-border-left) shorthand property. +pub type BorderLeft = GenericBorder; +/// A value for the [border-block-start](https://drafts.csswg.org/css-logical/#propdef-border-block-start) shorthand property. +pub type BorderBlockStart = GenericBorder; +/// A value for the [border-block-end](https://drafts.csswg.org/css-logical/#propdef-border-block-end) shorthand property. +pub type BorderBlockEnd = GenericBorder; +/// A value for the [border-inline-start](https://drafts.csswg.org/css-logical/#propdef-border-inline-start) shorthand property. +pub type BorderInlineStart = GenericBorder; +/// A value for the [border-inline-end](https://drafts.csswg.org/css-logical/#propdef-border-inline-end) shorthand property. +pub type BorderInlineEnd = GenericBorder; +/// A value for the [border-block](https://drafts.csswg.org/css-logical/#propdef-border-block) shorthand property. +pub type BorderBlock = GenericBorder; +/// A value for the [border-inline](https://drafts.csswg.org/css-logical/#propdef-border-inline) shorthand property. +pub type BorderInline = GenericBorder; +/// A value for the [border](https://www.w3.org/TR/css-backgrounds-3/#propdef-border) shorthand property. +pub type Border = GenericBorder; - if fallbacks.contains(ColorFallbackKind::P3) { - res.push(Rect::new( - self.0.get_fallback(ColorFallbackKind::P3), - self.1.get_fallback(ColorFallbackKind::P3), - self.2.get_fallback(ColorFallbackKind::P3), - self.3.get_fallback(ColorFallbackKind::P3), - )); - } +impl_shorthand! { + BorderTop(BorderTop) { + width: [BorderTopWidth], + style: [BorderTopStyle], + color: [BorderTopColor], + } +} - if fallbacks.contains(ColorFallbackKind::LAB) { - self.0 = self.0.get_fallback(ColorFallbackKind::LAB); - self.1 = self.1.get_fallback(ColorFallbackKind::LAB); - self.2 = self.2.get_fallback(ColorFallbackKind::LAB); - self.3 = self.3.get_fallback(ColorFallbackKind::LAB); - } +impl_shorthand! { + BorderRight(BorderRight) { + width: [BorderRightWidth], + style: [BorderRightStyle], + color: [BorderRightColor], + } +} - res +impl_shorthand! { + BorderBottom(BorderBottom) { + width: [BorderBottomWidth], + style: [BorderBottomStyle], + color: [BorderBottomColor], } } -/// A value for the [border](https://www.w3.org/TR/css-backgrounds-3/#propdef-border) shorthand property. -pub type Border = GenericBorder; +impl_shorthand! { + BorderLeft(BorderLeft) { + width: [BorderLeftWidth], + style: [BorderLeftStyle], + color: [BorderLeftColor], + } +} + +impl_shorthand! { + BorderBlockStart(BorderBlockStart) { + width: [BorderBlockStartWidth], + style: [BorderBlockStartStyle], + color: [BorderBlockStartColor], + } +} + +impl_shorthand! { + BorderBlockEnd(BorderBlockEnd) { + width: [BorderBlockEndWidth], + style: [BorderBlockEndStyle], + color: [BorderBlockEndColor], + } +} + +impl_shorthand! { + BorderInlineStart(BorderInlineStart) { + width: [BorderInlineStartWidth], + style: [BorderInlineStartStyle], + color: [BorderInlineStartColor], + } +} + +impl_shorthand! { + BorderInlineEnd(BorderInlineEnd) { + width: [BorderInlineEndWidth], + style: [BorderInlineEndStyle], + color: [BorderInlineEndColor], + } +} + +impl_shorthand! { + BorderBlock(BorderBlock) { + width: [BorderBlockStartWidth, BorderBlockEndWidth], + style: [BorderBlockStartStyle, BorderBlockEndStyle], + color: [BorderBlockStartColor, BorderBlockEndColor], + } +} + +impl_shorthand! { + BorderInline(BorderInline) { + width: [BorderInlineStartWidth, BorderInlineEndWidth], + style: [BorderInlineStartStyle, BorderInlineEndStyle], + color: [BorderInlineStartColor, BorderInlineEndColor], + } +} + +impl_shorthand! { + Border(Border) { + width: [BorderTopWidth, BorderRightWidth, BorderBottomWidth, BorderLeftWidth], + style: [BorderTopStyle, BorderRightStyle, BorderBottomStyle, BorderLeftStyle], + color: [BorderTopColor, BorderRightColor, BorderBottomColor, BorderLeftColor], + } +} + +size_shorthand! { + /// A value for the [border-block-color](https://drafts.csswg.org/css-logical/#propdef-border-block-color) shorthand property. + pub struct BorderBlockColor { + /// The block start value. + start: BorderBlockStartColor, + /// The block end value. + end: BorderBlockEndColor, + } +} + +size_shorthand! { + /// A value for the [border-block-style](https://drafts.csswg.org/css-logical/#propdef-border-block-style) shorthand property. + pub struct BorderBlockStyle { + /// The block start value. + start: BorderBlockStartStyle, + /// The block end value. + end: BorderBlockEndStyle, + } +} + +size_shorthand! { + /// A value for the [border-block-width](https://drafts.csswg.org/css-logical/#propdef-border-block-width) shorthand property. + pub struct BorderBlockWidth { + /// The block start value. + start: BorderBlockStartWidth, + /// The block end value. + end: BorderBlockEndWidth, + } +} + +size_shorthand! { + /// A value for the [border-inline-color](https://drafts.csswg.org/css-logical/#propdef-border-inline-color) shorthand property. + pub struct BorderInlineColor { + /// The inline start value. + start: BorderInlineStartColor, + /// The inline end value. + end: BorderInlineEndColor, + } +} + +size_shorthand! { + /// A value for the [border-inline-style](https://drafts.csswg.org/css-logical/#propdef-border-inline-style) shorthand property. + pub struct BorderInlineStyle { + /// The inline start value. + start: BorderInlineStartStyle, + /// The inline end value. + end: BorderInlineEndStyle, + } +} + +size_shorthand! { + /// A value for the [border-inline-width](https://drafts.csswg.org/css-logical/#propdef-border-inline-width) shorthand property. + pub struct BorderInlineWidth { + /// The inline start value. + start: BorderInlineStartWidth, + /// The inline end value. + end: BorderInlineEndWidth, + } +} + +rect_shorthand! { + /// A value for the [border-color](https://drafts.csswg.org/css-backgrounds/#propdef-border-color) shorthand property. + pub struct BorderColor { + BorderTopColor, + BorderRightColor, + BorderBottomColor, + BorderLeftColor + } +} + +rect_shorthand! { + /// A value for the [border-style](https://drafts.csswg.org/css-backgrounds/#propdef-border-style) shorthand property. + pub struct BorderStyle { + BorderTopStyle, + BorderRightStyle, + BorderBottomStyle, + BorderLeftStyle + } +} + +rect_shorthand! { + /// A value for the [border-width](https://drafts.csswg.org/css-backgrounds/#propdef-border-width) shorthand property. + pub struct BorderWidth { + BorderTopWidth, + BorderRightWidth, + BorderBottomWidth, + BorderLeftWidth + } +} + +macro_rules! impl_fallbacks { + ($t: ident $(, $name: ident)+) => { + impl FallbackValues for $t { + fn get_fallbacks(&mut self, targets: Browsers) -> Vec { + let mut fallbacks = ColorFallbackKind::empty(); + $( + fallbacks |= self.$name.get_necessary_fallbacks(targets); + )+ + + let mut res = Vec::new(); + if fallbacks.contains(ColorFallbackKind::RGB) { + res.push($t { + $( + $name: self.$name.get_fallback(ColorFallbackKind::RGB), + )+ + }); + } + + if fallbacks.contains(ColorFallbackKind::P3) { + res.push($t { + $( + $name: self.$name.get_fallback(ColorFallbackKind::P3), + )+ + }); + } + + if fallbacks.contains(ColorFallbackKind::LAB) { + $( + self.$name = self.$name.get_fallback(ColorFallbackKind::LAB); + )+ + } + + res + } + } + } +} + +impl_fallbacks!(BorderBlockColor, start, end); +impl_fallbacks!(BorderInlineColor, start, end); +impl_fallbacks!(BorderColor, top, right, bottom, left); #[derive(Default, Debug, PartialEq)] struct BorderShorthand { pub width: Option, - pub style: Option, + pub style: Option, pub color: Option, } impl BorderShorthand { - pub fn set_border(&mut self, border: &Border) { + pub fn set_border(&mut self, border: &GenericBorder) { self.width = Some(border.width.clone()); self.style = Some(border.style.clone()); self.color = Some(border.color.clone()); @@ -275,8 +478,8 @@ impl BorderShorthand { self.color = None; } - pub fn to_border(&self) -> Border { - Border { + pub fn to_border(&self) -> GenericBorder { + GenericBorder { width: self.width.clone().unwrap(), style: self.style.clone().unwrap(), color: self.color.clone().unwrap(), @@ -331,7 +534,7 @@ impl<'i> PropertyHandler<'i> for BorderHandler<'i> { use Property::*; macro_rules! property { - ($key: ident, $prop: ident, $val: ident, $category: ident) => {{ + ($key: ident, $prop: ident, $val: expr, $category: ident) => {{ if PropertyCategory::$category != self.category { self.flush(dest, context); } @@ -360,14 +563,14 @@ impl<'i> PropertyHandler<'i> for BorderHandler<'i> { BorderBlockStartColor(val) => property!(border_block_start, color, val, Logical), BorderBlockEndColor(val) => property!(border_block_end, color, val, Logical), BorderBlockColor(val) => { - property!(border_block_start, color, val, Logical); - property!(border_block_end, color, val, Logical); + property!(border_block_start, color, val.start, Logical); + property!(border_block_end, color, val.end, Logical); } BorderInlineStartColor(val) => property!(border_inline_start, color, val, Logical), BorderInlineEndColor(val) => property!(border_inline_end, color, val, Logical), BorderInlineColor(val) => { - property!(border_inline_start, color, val, Logical); - property!(border_inline_end, color, val, Logical); + property!(border_inline_start, color, val.start, Logical); + property!(border_inline_end, color, val.end, Logical); } BorderTopWidth(val) => property!(border_top, width, val, Physical), BorderBottomWidth(val) => property!(border_bottom, width, val, Physical), @@ -376,14 +579,14 @@ impl<'i> PropertyHandler<'i> for BorderHandler<'i> { BorderBlockStartWidth(val) => property!(border_block_start, width, val, Logical), BorderBlockEndWidth(val) => property!(border_block_end, width, val, Logical), BorderBlockWidth(val) => { - property!(border_block_start, width, val, Logical); - property!(border_block_end, width, val, Logical); + property!(border_block_start, width, val.start, Logical); + property!(border_block_end, width, val.end, Logical); } BorderInlineStartWidth(val) => property!(border_inline_start, width, val, Logical), BorderInlineEndWidth(val) => property!(border_inline_end, width, val, Logical), BorderInlineWidth(val) => { - property!(border_inline_start, width, val, Logical); - property!(border_inline_end, width, val, Logical); + property!(border_inline_start, width, val.start, Logical); + property!(border_inline_end, width, val.end, Logical); } BorderTopStyle(val) => property!(border_top, style, val, Physical), BorderBottomStyle(val) => property!(border_bottom, style, val, Physical), @@ -392,14 +595,14 @@ impl<'i> PropertyHandler<'i> for BorderHandler<'i> { BorderBlockStartStyle(val) => property!(border_block_start, style, val, Logical), BorderBlockEndStyle(val) => property!(border_block_end, style, val, Logical), BorderBlockStyle(val) => { - property!(border_block_start, style, val, Logical); - property!(border_block_end, style, val, Logical); + property!(border_block_start, style, val.start, Logical); + property!(border_block_end, style, val.end, Logical); } BorderInlineStartStyle(val) => property!(border_inline_start, style, val, Logical), BorderInlineEndStyle(val) => property!(border_inline_end, style, val, Logical), BorderInlineStyle(val) => { - property!(border_inline_start, style, val, Logical); - property!(border_inline_end, style, val, Logical); + property!(border_inline_start, style, val.start, Logical); + property!(border_inline_end, style, val.end, Logical); } BorderTop(val) => set_border!(border_top, val, Physical), BorderBottom(val) => set_border!(border_bottom, val, Physical), @@ -418,10 +621,10 @@ impl<'i> PropertyHandler<'i> for BorderHandler<'i> { set_border!(border_inline_end, val, Logical); } BorderWidth(val) => { - self.border_top.width = Some(val.0.clone()); - self.border_right.width = Some(val.1.clone()); - self.border_bottom.width = Some(val.2.clone()); - self.border_left.width = Some(val.3.clone()); + self.border_top.width = Some(val.top.clone()); + self.border_right.width = Some(val.right.clone()); + self.border_bottom.width = Some(val.bottom.clone()); + self.border_left.width = Some(val.left.clone()); self.border_block_start.width = None; self.border_block_end.width = None; self.border_inline_start.width = None; @@ -429,10 +632,10 @@ impl<'i> PropertyHandler<'i> for BorderHandler<'i> { self.has_any = true; } BorderStyle(val) => { - self.border_top.style = Some(val.0.clone()); - self.border_right.style = Some(val.1.clone()); - self.border_bottom.style = Some(val.2.clone()); - self.border_left.style = Some(val.3.clone()); + self.border_top.style = Some(val.top.clone()); + self.border_right.style = Some(val.right.clone()); + self.border_bottom.style = Some(val.bottom.clone()); + self.border_left.style = Some(val.left.clone()); self.border_block_start.style = None; self.border_block_end.style = None; self.border_inline_start.style = None; @@ -440,10 +643,10 @@ impl<'i> PropertyHandler<'i> for BorderHandler<'i> { self.has_any = true; } BorderColor(val) => { - self.border_top.color = Some(val.0.clone()); - self.border_right.color = Some(val.1.clone()); - self.border_bottom.color = Some(val.2.clone()); - self.border_left.color = Some(val.3.clone()); + self.border_top.color = Some(val.top.clone()); + self.border_right.color = Some(val.right.clone()); + self.border_bottom.color = Some(val.bottom.clone()); + self.border_left.color = Some(val.left.clone()); self.border_block_start.color = None; self.border_block_end.color = None; self.border_inline_start.color = None; @@ -714,12 +917,12 @@ impl<'i> BorderHandler<'i> { let has_prop = $block_start.$key.is_some() && $block_end.$key.is_some() && $inline_start.$key.is_some() && $inline_end.$key.is_some(); if has_prop { if !$is_logical || ($block_start.$key == $block_end.$key && $block_end.$key == $inline_start.$key && $inline_start.$key == $inline_end.$key) { - let rect = Rect::new( - std::mem::take(&mut $block_start.$key).unwrap(), - std::mem::take(&mut $inline_end.$key).unwrap(), - std::mem::take(&mut $block_end.$key).unwrap(), - std::mem::take(&mut $inline_start.$key).unwrap() - ); + let rect = $prop { + top: std::mem::take(&mut $block_start.$key).unwrap(), + right: std::mem::take(&mut $inline_end.$key).unwrap(), + bottom: std::mem::take(&mut $block_end.$key).unwrap(), + left: std::mem::take(&mut $inline_start.$key).unwrap() + }; prop!($prop => rect); } } @@ -728,9 +931,12 @@ impl<'i> BorderHandler<'i> { macro_rules! logical_shorthand { ($prop: ident, $key: ident, $start: expr, $end: expr) => {{ - let has_prop = $start.$key.is_some() && $start.$key == $end.$key; + let has_prop = $start.$key.is_some() && $end.$key.is_some(); if has_prop { - prop!($prop => std::mem::take(&mut $start.$key).unwrap()); + prop!($prop => $prop { + start: std::mem::take(&mut $start.$key).unwrap(), + end: std::mem::take(&mut $end.$key).unwrap(), + }); $end.$key = None; } has_prop @@ -824,14 +1030,23 @@ impl<'i> BorderHandler<'i> { } if diff == 1 { - if $inline_start.width != $block_start.width && $inline_start.width == $inline_end.width { - prop!(BorderInlineWidth => $inline_start.width.clone().unwrap()); + if $inline_start.width != $block_start.width { + prop!(BorderInlineWidth => BorderInlineWidth { + start: $inline_start.width.clone().unwrap(), + end: $inline_end.width.clone().unwrap(), + }); handled = true; - } else if $inline_start.style != $block_start.style && $inline_start.style == $inline_end.style { - prop!(BorderInlineStyle => $inline_start.style.clone().unwrap()); + } else if $inline_start.style != $block_start.style { + prop!(BorderInlineStyle => BorderInlineStyle { + start: $inline_start.style.clone().unwrap(), + end: $inline_end.style.clone().unwrap() + }); handled = true; - } else if $inline_start.color != $block_start.color && $inline_start.color == $inline_end.color { - prop!(BorderInlineColor => $inline_start.color.clone().unwrap()); + } else if $inline_start.color != $block_start.color { + prop!(BorderInlineColor => BorderInlineColor { + start: $inline_start.color.clone().unwrap(), + end: $inline_end.color.clone().unwrap() + }); handled = true; } } else if diff > 1 && $inline_start.width == $inline_end.width && $inline_start.style == $inline_end.style && $inline_start.color == $inline_end.color { diff --git a/src/properties/border_image.rs b/src/properties/border_image.rs index decb7535..200714f3 100644 --- a/src/properties/border_image.rs +++ b/src/properties/border_image.rs @@ -1,14 +1,14 @@ //! CSS properties related to border images. use crate::context::PropertyHandlerContext; -use crate::declaration::DeclarationList; +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::{Parse, PropertyHandler, ToCss}; +use crate::traits::{Parse, PropertyHandler, Shorthand, ToCss}; use crate::values::image::Image; use crate::values::number::CSSNumber; use crate::values::rect::Rect; @@ -37,16 +37,19 @@ enum_property! { /// A value for the [border-image-repeat](https://www.w3.org/TR/css-backgrounds-3/#border-image-repeat) property. #[derive(Debug, Clone, PartialEq)] -pub struct BorderImageRepeat( - /// The top and bottom repeat value. - pub BorderImageRepeatKeyword, - /// The left and right repeat value. - pub BorderImageRepeatKeyword, -); +pub struct BorderImageRepeat { + /// The horizontal repeat value. + pub horizontal: BorderImageRepeatKeyword, + /// The vertical repeat value. + pub vertical: BorderImageRepeatKeyword, +} impl Default for BorderImageRepeat { fn default() -> BorderImageRepeat { - BorderImageRepeat(BorderImageRepeatKeyword::Stretch, BorderImageRepeatKeyword::Stretch) + BorderImageRepeat { + horizontal: BorderImageRepeatKeyword::Stretch, + vertical: BorderImageRepeatKeyword::Stretch, + } } } @@ -54,7 +57,10 @@ impl<'i> Parse<'i> for BorderImageRepeat { fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { let horizontal = BorderImageRepeatKeyword::parse(input)?; let vertical = input.try_parse(BorderImageRepeatKeyword::parse).ok(); - Ok(BorderImageRepeat(horizontal, vertical.unwrap_or(horizontal))) + Ok(BorderImageRepeat { + horizontal, + vertical: vertical.unwrap_or(horizontal), + }) } } @@ -63,10 +69,10 @@ impl ToCss for BorderImageRepeat { where W: std::fmt::Write, { - self.0.to_css(dest)?; - if self.0 != self.1 { + self.horizontal.to_css(dest)?; + if self.horizontal != self.vertical { dest.write_str(" ")?; - self.1.to_css(dest)?; + self.vertical.to_css(dest)?; } Ok(()) } @@ -163,19 +169,21 @@ impl ToCss for BorderImageSlice { } } -/// A value for the [border-image](https://www.w3.org/TR/css-backgrounds-3/#border-image) shorthand property. -#[derive(Debug, Clone, PartialEq, Default)] -pub struct BorderImage<'i> { - /// The border image. - pub source: Image<'i>, - /// The offsets that define where the image is sliced. - pub slice: BorderImageSlice, - /// The width of the border image. - pub width: Rect, - /// The amount that the image extends beyond the border box. - pub outset: Rect, - /// How the border image is scaled and tiled. - pub repeat: BorderImageRepeat, +define_shorthand! { + /// A value for the [border-image](https://www.w3.org/TR/css-backgrounds-3/#border-image) shorthand property. + #[derive(Default)] + pub struct BorderImage<'i>(VendorPrefix) { + /// The border image. + source: BorderImageSource(Image<'i>), + /// The offsets that define where the image is sliced. + slice: BorderImageSlice(BorderImageSlice), + /// The width of the border image. + width: BorderImageWidth(Rect), + /// The amount that the image extends beyond the border box. + outset: BorderImageOutset(Rect), + /// How the border image is scaled and tiled. + repeat: BorderImageRepeat(BorderImageRepeat), + } } impl<'i> Parse<'i> for BorderImage<'i> { @@ -263,44 +271,58 @@ impl<'i> BorderImage<'i> { Err(input.new_custom_error(ParserError::InvalidDeclaration)) } } -} -impl<'i> ToCss for BorderImage<'i> { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> + pub(crate) fn to_css_internal( + source: &Image<'i>, + slice: &BorderImageSlice, + width: &Rect, + outset: &Rect, + repeat: &BorderImageRepeat, + dest: &mut Printer, + ) -> Result<(), PrinterError> where W: std::fmt::Write, { - if self.source != Image::default() { - self.source.to_css(dest)?; + if *source != Image::default() { + source.to_css(dest)?; } - let has_slice = self.slice != BorderImageSlice::default(); - let has_width = self.width != Rect::all(BorderImageSideWidth::default()); - let has_outset = self.outset != Rect::all(LengthOrNumber::Number(0.0)); + let has_slice = *slice != BorderImageSlice::default(); + let has_width = *width != Rect::all(BorderImageSideWidth::default()); + let has_outset = *outset != Rect::all(LengthOrNumber::Number(0.0)); if has_slice || has_width || has_outset { dest.write_str(" ")?; - self.slice.to_css(dest)?; + slice.to_css(dest)?; if has_width || has_outset { dest.delim('/', true)?; } if has_width { - self.width.to_css(dest)?; + width.to_css(dest)?; } if has_outset { dest.delim('/', true)?; - self.outset.to_css(dest)?; + outset.to_css(dest)?; } } - if self.repeat != BorderImageRepeat::default() { + if *repeat != BorderImageRepeat::default() { dest.write_str(" ")?; - self.repeat.to_css(dest)?; + repeat.to_css(dest)?; } Ok(()) } } +impl<'i> ToCss for BorderImage<'i> { + fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> + where + W: std::fmt::Write, + { + BorderImage::to_css_internal(&self.source, &self.slice, &self.width, &self.outset, &self.repeat, dest) + } +} + impl<'i> FallbackValues for BorderImage<'i> { fn get_fallbacks(&mut self, targets: Browsers) -> Vec { self diff --git a/src/properties/border_radius.rs b/src/properties/border_radius.rs index e1a8802c..5d048f5e 100644 --- a/src/properties/border_radius.rs +++ b/src/properties/border_radius.rs @@ -2,30 +2,32 @@ use crate::compat; use crate::context::PropertyHandlerContext; -use crate::declaration::DeclarationList; +use crate::declaration::{DeclarationBlock, DeclarationList}; use crate::error::{ParserError, PrinterError}; use crate::logical::PropertyCategory; +use crate::macros::define_shorthand; use crate::prefixes::Feature; use crate::printer::Printer; use crate::properties::{Property, PropertyId, VendorPrefix}; use crate::targets::Browsers; -use crate::traits::{Parse, PropertyHandler, ToCss}; +use crate::traits::{Parse, PropertyHandler, Shorthand, ToCss}; use crate::values::length::*; use crate::values::rect::Rect; use crate::values::size::Size2D; use cssparser::*; -/// A value for the [border-radius](https://www.w3.org/TR/css-backgrounds-3/#border-radius) property. -#[derive(Debug, Clone, PartialEq)] -pub struct BorderRadius { - /// The x and y radius values for the top left corner. - pub top_left: Size2D, - /// The x and y radius values for the top right corner. - pub top_right: Size2D, - /// The x and y radius values for the bottom left corner. - pub bottom_left: Size2D, - /// The x and y radius values for the bottom right corner. - pub bottom_right: Size2D, +define_shorthand! { + /// A value for the [border-radius](https://www.w3.org/TR/css-backgrounds-3/#border-radius) property. + pub struct BorderRadius(VendorPrefix) { + /// The x and y radius values for the top left corner. + top_left: BorderTopLeftRadius(Size2D, VendorPrefix), + /// The x and y radius values for the top right corner. + top_right: BorderTopRightRadius(Size2D, VendorPrefix), + /// The x and y radius values for the bottom left corner. + bottom_left: BorderBottomLeftRadius(Size2D, VendorPrefix), + /// The x and y radius values for the bottom right corner. + bottom_right: BorderBottomRightRadius(Size2D, VendorPrefix), + } } impl Default for BorderRadius { diff --git a/src/properties/custom.rs b/src/properties/custom.rs index 0b09a70b..031035c0 100644 --- a/src/properties/custom.rs +++ b/src/properties/custom.rs @@ -26,14 +26,11 @@ pub struct CustomProperty<'i> { impl<'i> CustomProperty<'i> { /// Parses a custom property with the given name. pub fn parse<'t>( - name: CowRcStr<'i>, + name: CowArcStr<'i>, input: &mut Parser<'i, 't>, ) -> Result>> { let value = TokenList::parse(input)?; - Ok(CustomProperty { - name: name.into(), - value, - }) + Ok(CustomProperty { name, value }) } } diff --git a/src/properties/flex.rs b/src/properties/flex.rs index cfee529f..8583910f 100644 --- a/src/properties/flex.rs +++ b/src/properties/flex.rs @@ -5,13 +5,13 @@ use super::align::{ }; use super::{Property, PropertyId}; use crate::context::PropertyHandlerContext; -use crate::declaration::DeclarationList; +use crate::declaration::{DeclarationBlock, DeclarationList}; use crate::error::{ParserError, PrinterError}; use crate::macros::*; use crate::prefixes::{is_flex_2009, Feature}; use crate::printer::Printer; use crate::targets::Browsers; -use crate::traits::{FromStandard, Parse, PropertyHandler, ToCss}; +use crate::traits::{FromStandard, Parse, PropertyHandler, Shorthand, ToCss}; use crate::values::number::{CSSInteger, CSSNumber}; use crate::values::{ length::{LengthPercentage, LengthPercentageOrAuto}, @@ -64,13 +64,14 @@ impl FromStandard for FlexWrap { } } -/// A value for the [flex-flow](https://www.w3.org/TR/2018/CR-css-flexbox-1-20181119/#flex-flow-property) shorthand property. -#[derive(Debug, Clone, PartialEq)] -pub struct FlexFlow { - /// The direction that flex items flow. - pub direction: FlexDirection, - /// How the flex items wrap. - pub wrap: FlexWrap, +define_shorthand! { + /// A value for the [flex-flow](https://www.w3.org/TR/2018/CR-css-flexbox-1-20181119/#flex-flow-property) shorthand property. + pub struct FlexFlow(VendorPrefix) { + /// The direction that flex items flow. + direction: FlexDirection(FlexDirection, VendorPrefix), + /// How the flex items wrap. + wrap: FlexWrap(FlexWrap, VendorPrefix), + } } impl<'i> Parse<'i> for FlexFlow { @@ -122,15 +123,16 @@ impl ToCss for FlexFlow { } } +define_shorthand! { /// A value for the [flex](https://www.w3.org/TR/2018/CR-css-flexbox-1-20181119/#flex-property) shorthand property. -#[derive(Debug, Clone, PartialEq)] -pub struct Flex { - /// The flex grow factor. - pub grow: CSSNumber, - /// The flex shrink factor. - pub shrink: CSSNumber, - /// The flex basis. - pub basis: LengthPercentageOrAuto, + pub struct Flex(VendorPrefix) { + /// The flex grow factor. + grow: FlexGrow(CSSNumber, VendorPrefix), + /// The flex shrink factor. + shrink: FlexShrink(CSSNumber, VendorPrefix), + /// The flex basis. + basis: FlexBasis(LengthPercentageOrAuto, VendorPrefix), + } } impl<'i> Parse<'i> for Flex { diff --git a/src/properties/font.rs b/src/properties/font.rs index 8c923d67..af858443 100644 --- a/src/properties/font.rs +++ b/src/properties/font.rs @@ -2,11 +2,11 @@ use super::{Property, PropertyId}; use crate::context::PropertyHandlerContext; -use crate::declaration::DeclarationList; +use crate::declaration::{DeclarationBlock, DeclarationList}; use crate::error::{ParserError, PrinterError}; use crate::macros::*; use crate::printer::Printer; -use crate::traits::{Parse, PropertyHandler, ToCss}; +use crate::traits::{Parse, PropertyHandler, Shorthand, ToCss}; use crate::values::number::CSSNumber; use crate::values::string::CowArcStr; use crate::values::{angle::Angle, length::LengthPercentage, percentage::Percentage}; @@ -473,40 +473,23 @@ enum_property! { } } -impl FontVariantCaps { - fn to_css2(&self) -> Option { - match self { - FontVariantCaps::Normal => Some(FontVariantCapsCSS2::Normal), - FontVariantCaps::SmallCaps => Some(FontVariantCapsCSS2::SmallCaps), - _ => None, - } - } -} - -enum_property! { - /// The CSS 2.1 values for the `font-variant-caps` property, as used in the `font` shorthand. - /// - /// See [Font](Font). - pub enum FontVariantCapsCSS2 { - /// No special capitalization features are applied. - "normal": Normal, - /// Small capitals are used for lower case letters. - "small-caps": SmallCaps, +impl Default for FontVariantCaps { + fn default() -> FontVariantCaps { + FontVariantCaps::Normal } } -impl Default for FontVariantCapsCSS2 { - fn default() -> FontVariantCapsCSS2 { - FontVariantCapsCSS2::Normal +impl FontVariantCaps { + fn is_css2(&self) -> bool { + matches!(self, FontVariantCaps::Normal | FontVariantCaps::SmallCaps) } -} -impl FontVariantCapsCSS2 { - fn to_font_variant_caps(&self) -> FontVariantCaps { - match self { - FontVariantCapsCSS2::Normal => FontVariantCaps::Normal, - FontVariantCapsCSS2::SmallCaps => FontVariantCaps::SmallCaps, + fn parse_css2<'i, 't>(input: &mut Parser<'i, 't>) -> Result>> { + let value = Self::parse(input)?; + if !value.is_css2() { + return Err(input.new_custom_error(ParserError::InvalidValue)); } + Ok(value) } } @@ -609,23 +592,24 @@ impl ToCss for VerticalAlign { } } -/// A value for the [font](https://www.w3.org/TR/css-fonts-4/#font-prop) shorthand property. -#[derive(Debug, Clone, PartialEq)] -pub struct Font<'i> { - /// The font family. - pub family: Vec>, - /// The font size. - pub size: FontSize, - /// The font style. - pub style: FontStyle, - /// The font weight. - pub weight: FontWeight, - /// The font stretch. - pub stretch: FontStretch, - /// The line height. - pub line_height: LineHeight, - /// How the text should be capitalized. Only CSS 2.1 values are supported. - pub variant_caps: FontVariantCapsCSS2, +define_shorthand! { + /// A value for the [font](https://www.w3.org/TR/css-fonts-4/#font-prop) shorthand property. + pub struct Font<'i> { + /// The font family. + family: FontFamily(Vec>), + /// The font size. + size: FontSize(FontSize), + /// The font style. + style: FontStyle(FontStyle), + /// The font weight. + weight: FontWeight(FontWeight), + /// The font stretch. + stretch: FontStretch(FontStretch), + /// The line height. + line_height: LineHeight(LineHeight), + /// How the text should be capitalized. Only CSS 2.1 values are supported. + variant_caps: FontVariantCaps(FontVariantCaps), + } } impl<'i> Parse<'i> for Font<'i> { @@ -658,7 +642,7 @@ impl<'i> Parse<'i> for Font<'i> { } } if variant_caps.is_none() { - if let Ok(value) = input.try_parse(FontVariantCapsCSS2::parse) { + if let Ok(value) = input.try_parse(FontVariantCaps::parse_css2) { variant_caps = Some(value); count += 1; continue; @@ -714,7 +698,7 @@ impl<'i> ToCss for Font<'i> { dest.write_char(' ')?; } - if self.variant_caps != FontVariantCapsCSS2::default() { + if self.variant_caps != FontVariantCaps::default() { self.variant_caps.to_css(dest)?; dest.write_char(' ')?; } @@ -793,7 +777,7 @@ impl<'i> PropertyHandler<'i> for FontHandler<'i> { self.weight = Some(val.weight.clone()); self.stretch = Some(val.stretch.clone()); self.line_height = Some(val.line_height.clone()); - self.variant_caps = Some(val.variant_caps.to_font_variant_caps()); + self.variant_caps = Some(val.variant_caps.clone()); self.has_any = true; // TODO: reset other properties } @@ -830,7 +814,7 @@ impl<'i> PropertyHandler<'i> for FontHandler<'i> { && line_height.is_some() && variant_caps.is_some() { - let caps = variant_caps.unwrap().to_css2(); + let caps = variant_caps.unwrap(); decls.push(Property::Font(Font { family: family.unwrap(), size: size.unwrap(), @@ -838,12 +822,16 @@ impl<'i> PropertyHandler<'i> for FontHandler<'i> { weight: weight.unwrap(), stretch: stretch.unwrap(), line_height: line_height.unwrap(), - variant_caps: caps.unwrap_or_default(), + variant_caps: if caps.is_css2() { + caps + } else { + FontVariantCaps::default() + }, })); // The `font` property only accepts CSS 2.1 values for font-variant caps. // If we have a CSS 3+ value, we need to add a separate property. - if caps == None { + if !caps.is_css2() { decls.push(Property::FontVariantCaps(variant_caps.unwrap())) } } else { diff --git a/src/properties/grid.rs b/src/properties/grid.rs index d4f50faa..668079ae 100644 --- a/src/properties/grid.rs +++ b/src/properties/grid.rs @@ -3,11 +3,12 @@ #![allow(non_upper_case_globals)] use crate::context::PropertyHandlerContext; -use crate::declaration::DeclarationList; +use crate::declaration::{DeclarationBlock, DeclarationList}; use crate::error::{ParserError, PrinterError}; +use crate::macros::{define_shorthand, impl_shorthand}; use crate::printer::Printer; use crate::properties::{Property, PropertyId}; -use crate::traits::{Parse, PropertyHandler, ToCss}; +use crate::traits::{Parse, PropertyHandler, Shorthand, ToCss}; use crate::values::ident::CustomIdent; use crate::values::length::serialize_dimension; use crate::values::number::{CSSInteger, CSSNumber}; @@ -846,6 +847,28 @@ impl GridTemplate<'_> { } } +impl<'i> GridTemplate<'i> { + #[inline] + fn is_valid(rows: &TrackSizing, columns: &TrackSizing, areas: &GridTemplateAreas) -> bool { + // The `grid-template` shorthand supports only explicit track values (i.e. no `repeat()`) + // combined with grid-template-areas. If there are no areas, then any track values are allowed. + *areas == GridTemplateAreas::None + || (*rows != TrackSizing::None && rows.is_explicit() && columns.is_explicit()) + } +} + +impl_shorthand! { + GridTemplate(GridTemplate<'i>) { + rows: [GridTemplateRows], + columns: [GridTemplateColumns], + areas: [GridTemplateAreas], + } + + fn is_valid(shorthand) { + GridTemplate::is_valid(&shorthand.rows, &shorthand.columns, &shorthand.areas) + } +} + bitflags! { /// A value for the [grid-auto-flow](https://drafts.csswg.org/css-grid-2/#grid-auto-flow-property) property. /// @@ -1084,6 +1107,49 @@ impl ToCss for Grid<'_> { } } +impl<'i> Grid<'i> { + #[inline] + fn is_valid( + rows: &TrackSizing, + columns: &TrackSizing, + areas: &GridTemplateAreas, + auto_rows: &TrackSizeList, + auto_columns: &TrackSizeList, + auto_flow: &GridAutoFlow, + ) -> bool { + // The `grid` shorthand can either be fully explicit (e.g. same as `grid-template`), + // or explicit along a single axis. If there are auto rows, then there cannot be explicit rows, for example. + let is_template = GridTemplate::is_valid(rows, columns, areas); + let default_track_size_list = TrackSizeList::default(); + let is_explicit = *auto_rows == default_track_size_list + && *auto_columns == default_track_size_list + && *auto_flow == GridAutoFlow::default(); + let is_auto_rows = auto_flow.direction() == GridAutoFlow::Row + && *rows == TrackSizing::None + && *auto_columns == default_track_size_list; + let is_auto_columns = auto_flow.direction() == GridAutoFlow::Column + && *columns == TrackSizing::None + && *auto_rows == default_track_size_list; + + (is_template && is_explicit) || is_auto_rows || is_auto_columns + } +} + +impl_shorthand! { + Grid(Grid<'i>) { + rows: [GridTemplateRows], + columns: [GridTemplateColumns], + areas: [GridTemplateAreas], + auto_rows: [GridAutoRows], + auto_columns: [GridAutoColumns], + auto_flow: [GridAutoFlow], + } + + fn is_valid(grid) { + Grid::is_valid(&grid.rows, &grid.columns, &grid.areas, &grid.auto_rows, &grid.auto_columns, &grid.auto_flow) + } +} + /// A [``](https://drafts.csswg.org/css-grid-2/#typedef-grid-row-start-grid-line) value, /// used in the `grid-row-start`, `grid-row-end`, `grid-column-start`, and `grid-column-end` properties. #[derive(Debug, Clone, PartialEq)] @@ -1197,55 +1263,73 @@ impl<'i> GridLine<'i> { } } -/// A [grid placement](https://drafts.csswg.org/css-grid-2/#placement-shorthands) value for the -/// `grid-row` and `grid-column` shorthand properties. -#[derive(Debug, Clone, PartialEq)] -pub struct GridPlacement<'i> { - /// The starting line. - pub start: GridLine<'i>, - /// The ending line. - pub end: GridLine<'i>, -} +macro_rules! impl_grid_placement { + ($name: ident) => { + impl<'i> Parse<'i> for $name<'i> { + fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { + let start = GridLine::parse(input)?; + let end = if input.try_parse(|input| input.expect_delim('/')).is_ok() { + GridLine::parse(input)? + } else { + start.default_end_value() + }; -impl<'i> Parse<'i> for GridPlacement<'i> { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { - let start = GridLine::parse(input)?; - let end = if input.try_parse(|input| input.expect_delim('/')).is_ok() { - GridLine::parse(input)? - } else { - start.default_end_value() - }; + Ok($name { start, end }) + } + } - Ok(GridPlacement { start, end }) - } + impl ToCss for $name<'_> { + fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> + where + W: std::fmt::Write, + { + self.start.to_css(dest)?; + + if !self.start.can_omit_end(&self.end) { + dest.delim('/', true)?; + self.end.to_css(dest)?; + } + Ok(()) + } + } + }; } -impl ToCss for GridPlacement<'_> { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - self.start.to_css(dest)?; +define_shorthand! { + /// A value for the [grid-row](https://drafts.csswg.org/css-grid-2/#propdef-grid-row) shorthand property. + pub struct GridRow<'i> { + /// The starting line. + start: GridRowStart(GridLine<'i>), + /// The ending line. + end: GridRowEnd(GridLine<'i>), + } +} - if !self.start.can_omit_end(&self.end) { - dest.delim('/', true)?; - self.end.to_css(dest)?; - } - Ok(()) +define_shorthand! { + /// A value for the [grid-row](https://drafts.csswg.org/css-grid-2/#propdef-grid-column) shorthand property. + pub struct GridColumn<'i> { + /// The starting line. + start: GridColumnStart(GridLine<'i>), + /// The ending line. + end: GridColumnEnd(GridLine<'i>), } } -/// A value for the [grid-area](https://drafts.csswg.org/css-grid-2/#propdef-grid-area) property. -#[derive(Debug, Clone, PartialEq)] -pub struct GridArea<'i> { - /// The grid row start placement. - pub row_start: GridLine<'i>, - /// The grid column start placement. - pub column_start: GridLine<'i>, - /// The grid row end placement. - pub row_end: GridLine<'i>, - /// The grid column end placement. - pub column_end: GridLine<'i>, +impl_grid_placement!(GridRow); +impl_grid_placement!(GridColumn); + +define_shorthand! { + /// A value for the [grid-area](https://drafts.csswg.org/css-grid-2/#propdef-grid-area) shorthand property. + pub struct GridArea<'i> { + /// The grid row start placement. + row_start: GridRowStart(GridLine<'i>), + /// The grid column start placement. + column_start: GridColumnStart(GridLine<'i>), + /// The grid row end placement. + row_end: GridRowEnd(GridLine<'i>), + /// The grid column end placement. + column_end: GridColumnEnd(GridLine<'i>), + } } impl<'i> Parse<'i> for GridArea<'i> { @@ -1419,29 +1503,20 @@ impl<'i> PropertyHandler<'i> for GridHandler<'i> { let mut column_end = std::mem::take(&mut self.column_end); if let (Some(rows_val), Some(columns_val), Some(areas_val)) = (&rows, &columns, &areas) { - // The `grid-template` shorthand supports only explicit track values (i.e. no `repeat()`) - // combined with grid-template-areas. If there are no areas, then any track values are allowed. - let is_template = *areas_val == GridTemplateAreas::None - || (*rows_val != TrackSizing::None && rows_val.is_explicit() && columns_val.is_explicit()); - let mut has_template = true; if let (Some(auto_rows_val), Some(auto_columns_val), Some(auto_flow_val)) = (&auto_rows, &auto_columns, &auto_flow) { // The `grid` shorthand can either be fully explicit (e.g. same as `grid-template`), // or explicit along a single axis. If there are auto rows, then there cannot be explicit rows, for example. - let default_track_size_list = TrackSizeList::default(); - let is_explicit = *auto_rows_val == default_track_size_list - && *auto_columns_val == default_track_size_list - && *auto_flow_val == GridAutoFlow::default(); - let is_auto_rows = auto_flow_val.direction() == GridAutoFlow::Row - && *rows_val == TrackSizing::None - && *auto_columns_val == default_track_size_list; - let is_auto_columns = auto_flow_val.direction() == GridAutoFlow::Column - && *columns_val == TrackSizing::None - && *auto_rows_val == default_track_size_list; - - if (is_template && is_explicit) || is_auto_rows || is_auto_columns { + if Grid::is_valid( + rows_val, + columns_val, + areas_val, + auto_rows_val, + auto_columns_val, + auto_flow_val, + ) { dest.push(Property::Grid(Grid { rows: rows_val.clone(), columns: columns_val.clone(), @@ -1458,7 +1533,9 @@ impl<'i> PropertyHandler<'i> for GridHandler<'i> { } } - if is_template && has_template { + // The `grid-template` shorthand supports only explicit track values (i.e. no `repeat()`) + // combined with grid-template-areas. If there are no areas, then any track values are allowed. + if has_template && GridTemplate::is_valid(rows_val, columns_val, areas_val) { dest.push(Property::GridTemplate(GridTemplate { rows: rows_val.clone(), columns: columns_val.clone(), @@ -1484,14 +1561,14 @@ impl<'i> PropertyHandler<'i> for GridHandler<'i> { })) } else { if row_start.is_some() && row_end.is_some() { - dest.push(Property::GridRow(GridPlacement { + dest.push(Property::GridRow(GridRow { start: std::mem::take(&mut row_start).unwrap(), end: std::mem::take(&mut row_end).unwrap(), })) } if column_start.is_some() && column_end.is_some() { - dest.push(Property::GridColumn(GridPlacement { + dest.push(Property::GridColumn(GridColumn { start: std::mem::take(&mut column_start).unwrap(), end: std::mem::take(&mut column_end).unwrap(), })) diff --git a/src/properties/list.rs b/src/properties/list.rs index 3e7153e2..9150bfb8 100644 --- a/src/properties/list.rs +++ b/src/properties/list.rs @@ -2,12 +2,12 @@ use super::{Property, PropertyId}; use crate::context::PropertyHandlerContext; -use crate::declaration::DeclarationList; +use crate::declaration::{DeclarationBlock, DeclarationList}; use crate::error::{ParserError, PrinterError}; -use crate::macros::{enum_property, shorthand_handler, shorthand_property}; +use crate::macros::{define_shorthand, enum_property, shorthand_handler, shorthand_property}; use crate::printer::Printer; use crate::targets::Browsers; -use crate::traits::{FallbackValues, Parse, PropertyHandler, ToCss}; +use crate::traits::{FallbackValues, Parse, PropertyHandler, Shorthand, ToCss}; use crate::values::string::CowArcStr; use crate::values::{ident::CustomIdent, image::Image}; use cssparser::*; @@ -281,11 +281,11 @@ shorthand_property! { /// A value for the [list-style](https://www.w3.org/TR/2020/WD-css-lists-3-20201117/#list-style-property) shorthand property. pub struct ListStyle<'i> { /// The list style type. - list_style_type: ListStyleType<'i>, + list_style_type: ListStyleType(ListStyleType<'i>), /// The list marker image. - image: Image<'i>, + image: ListStyleImage(Image<'i>), /// The position of the list marker. - position: ListStylePosition, + position: ListStylePosition(ListStylePosition), } } diff --git a/src/properties/margin_padding.rs b/src/properties/margin_padding.rs index 71c436b8..7138e817 100644 --- a/src/properties/margin_padding.rs +++ b/src/properties/margin_padding.rs @@ -1,10 +1,164 @@ use crate::compat::Feature; use crate::context::PropertyHandlerContext; -use crate::declaration::DeclarationList; +use crate::declaration::{DeclarationBlock, DeclarationList}; +use crate::error::{ParserError, PrinterError}; use crate::logical::PropertyCategory; +use crate::macros::{define_shorthand, rect_shorthand, size_shorthand}; +use crate::printer::Printer; use crate::properties::{Property, PropertyId}; -use crate::traits::PropertyHandler; +use crate::traits::{Parse, PropertyHandler, Shorthand, ToCss}; use crate::values::{length::LengthPercentageOrAuto, rect::Rect, size::Size2D}; +use cssparser::*; + +rect_shorthand! { + /// A value for the [margin](https://drafts.csswg.org/css-box-4/#propdef-margin) shorthand property. + pub struct Margin { + MarginTop, + MarginRight, + MarginBottom, + MarginLeft + } +} + +rect_shorthand! { + /// A value for the [padding](https://drafts.csswg.org/css-box-4/#propdef-padding) shorthand property. + pub struct Padding { + PaddingTop, + PaddingRight, + PaddingBottom, + PaddingLeft + } +} + +rect_shorthand! { + /// A value for the [scroll-margin](https://drafts.csswg.org/css-scroll-snap/#scroll-margin) shorthand property. + pub struct ScrollMargin { + ScrollMarginTop, + ScrollMarginRight, + ScrollMarginBottom, + ScrollMarginLeft + } +} + +rect_shorthand! { + /// A value for the [scroll-padding](https://drafts.csswg.org/css-scroll-snap/#scroll-padding) shorthand property. + pub struct ScrollPadding { + ScrollPaddingTop, + ScrollPaddingRight, + ScrollPaddingBottom, + ScrollPaddingLeft + } +} + +rect_shorthand! { + /// A value for the [inset](https://drafts.csswg.org/css-logical/#propdef-inset) shorthand property. + pub struct Inset { + Top, + Right, + Bottom, + Left + } +} + +size_shorthand! { + /// A value for the [margin-block](https://drafts.csswg.org/css-logical/#propdef-margin-block) shorthand property. + pub struct MarginBlock { + /// The block start value. + block_start: MarginBlockStart, + /// The block end value. + block_end: MarginBlockEnd, + } +} + +size_shorthand! { + /// A value for the [margin-inline](https://drafts.csswg.org/css-logical/#propdef-margin-inline) shorthand property. + pub struct MarginInline { + /// The inline start value. + inline_start: MarginInlineStart, + /// The inline end value. + inline_end: MarginInlineEnd, + } +} + +size_shorthand! { + /// A value for the [padding-block](https://drafts.csswg.org/css-logical/#propdef-padding-block) shorthand property. + pub struct PaddingBlock { + /// The block start value. + block_start: PaddingBlockStart, + /// The block end value. + block_end: PaddingBlockEnd, + } +} + +size_shorthand! { + /// A value for the [padding-inline](https://drafts.csswg.org/css-logical/#propdef-padding-inline) shorthand property. + pub struct PaddingInline { + /// The inline start value. + inline_start: PaddingInlineStart, + /// The inline end value. + inline_end: PaddingInlineEnd, + } +} + +size_shorthand! { + /// A value for the [scroll-margin-block](https://drafts.csswg.org/css-scroll-snap/#propdef-scroll-margin-block) shorthand property. + pub struct ScrollMarginBlock { + /// The block start value. + block_start: ScrollMarginBlockStart, + /// The block end value. + block_end: ScrollMarginBlockEnd, + } +} + +size_shorthand! { + /// A value for the [scroll-margin-inline](https://drafts.csswg.org/css-scroll-snap/#propdef-scroll-margin-inline) shorthand property. + pub struct ScrollMarginInline { + /// The inline start value. + inline_start: ScrollMarginInlineStart, + /// The inline end value. + inline_end: ScrollMarginInlineEnd, + } +} + +size_shorthand! { + /// A value for the [scroll-padding-block](https://drafts.csswg.org/css-scroll-snap/#propdef-scroll-padding-block) shorthand property. + pub struct ScrollPaddingBlock { + /// The block start value. + block_start: ScrollPaddingBlockStart, + /// The block end value. + block_end: ScrollPaddingBlockEnd, + } +} + +size_shorthand! { + /// A value for the [scroll-padding-inline](https://drafts.csswg.org/css-scroll-snap/#propdef-scroll-padding-inline) shorthand property. + pub struct ScrollPaddingInline { + /// The inline start value. + inline_start: ScrollPaddingInlineStart, + /// The inline end value. + inline_end: ScrollPaddingInlineEnd, + } +} + +size_shorthand! { + /// A value for the [inset-block](https://drafts.csswg.org/css-logical/#propdef-inset-block) shorthand property. + pub struct InsetBlock { + /// The block start value. + block_start: InsetBlockStart, + /// The block end value. + block_end: InsetBlockEnd, + } +} + +size_shorthand! { + /// A value for the [inset-inline](https://drafts.csswg.org/css-logical/#propdef-inset-inline) shorthand property. + pub struct InsetInline { + /// The inline start value. + inline_start: InsetInlineStart, + /// The inline end value. + inline_end: InsetInlineEnd, + } +} macro_rules! side_handler { ($name: ident, $top: ident, $bottom: ident, $left: ident, $right: ident, $block_start: ident, $block_end: ident, $inline_start: ident, $inline_end: ident, $shorthand: ident, $block_shorthand: ident, $inline_shorthand: ident, $logical_shorthand: literal $(, $feature: ident)?) => { @@ -59,19 +213,19 @@ macro_rules! side_handler { $inline_start(_) => logical_property!(inline_start, property.clone()), $inline_end(_) => logical_property!(inline_end, property.clone()), $block_shorthand(val) => { - logical_property!(block_start, Property::$block_start(val.0.clone())); - logical_property!(block_end, Property::$block_end(val.1.clone())); + logical_property!(block_start, Property::$block_start(val.block_start.clone())); + logical_property!(block_end, Property::$block_end(val.block_end.clone())); }, $inline_shorthand(val) => { - logical_property!(inline_start, Property::$inline_start(val.0.clone())); - logical_property!(inline_end, Property::$inline_end(val.1.clone())); + logical_property!(inline_start, Property::$inline_start(val.inline_start.clone())); + logical_property!(inline_end, Property::$inline_end(val.inline_end.clone())); }, $shorthand(val) => { // dest.clear(); - self.top = Some(val.0.clone()); - self.right = Some(val.1.clone()); - self.bottom = Some(val.2.clone()); - self.left = Some(val.3.clone()); + self.top = Some(val.top.clone()); + self.right = Some(val.right.clone()); + self.bottom = Some(val.bottom.clone()); + self.left = Some(val.left.clone()); self.block_start = None; self.block_end = None; self.inline_start = None; @@ -105,8 +259,6 @@ macro_rules! side_handler { impl<'i> $name<'i> { fn flush(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i>) { - use Property::*; - if !self.has_any { return } @@ -120,23 +272,27 @@ macro_rules! side_handler { let logical_supported = true $(&& context.is_supported(Feature::$feature))?; if (!$logical_shorthand || logical_supported) && top.is_some() && bottom.is_some() && left.is_some() && right.is_some() { - let rect = Rect::new(top.unwrap(), right.unwrap(), bottom.unwrap(), left.unwrap()); - dest.push($shorthand(rect)); + dest.push(Property::$shorthand($shorthand { + top: top.unwrap(), + right: right.unwrap(), + bottom: bottom.unwrap(), + left: left.unwrap() + })); } else { if let Some(val) = top { - dest.push($top(val)); + dest.push(Property::$top(val)); } if let Some(val) = bottom { - dest.push($bottom(val)); + dest.push(Property::$bottom(val)); } if let Some(val) = left { - dest.push($left(val)); + dest.push(Property::$left(val)); } if let Some(val) = right { - dest.push($right(val)); + dest.push(Property::$right(val)); } } @@ -148,8 +304,10 @@ macro_rules! side_handler { macro_rules! logical_side { ($start: ident, $end: ident, $shorthand_prop: ident, $start_prop: ident, $end_prop: ident) => { if let (Some(Property::$start_prop(start)), Some(Property::$end_prop(end))) = (&$start, &$end) { - let size = Size2D(start.clone(), end.clone()); - dest.push($shorthand_prop(size)); + dest.push(Property::$shorthand_prop($shorthand_prop { + $start: start.clone(), + $end: end.clone() + })); } else { if let Some(val) = $start { dest.push(val); diff --git a/src/properties/masking.rs b/src/properties/masking.rs index aa8b3797..92f099ab 100644 --- a/src/properties/masking.rs +++ b/src/properties/masking.rs @@ -4,13 +4,14 @@ use super::background::{BackgroundRepeat, BackgroundSize}; use super::border_image::{BorderImage, BorderImageRepeat, BorderImageSideWidth, BorderImageSlice}; use super::PropertyId; use crate::context::PropertyHandlerContext; -use crate::declaration::DeclarationList; +use crate::declaration::{DeclarationBlock, DeclarationList}; use crate::error::{ParserError, PrinterError}; -use crate::macros::enum_property; +use crate::macros::{define_list_shorthand, define_shorthand, enum_property}; use crate::prefixes::Feature; use crate::printer::Printer; use crate::properties::Property; -use crate::traits::{FallbackValues, Parse, PropertyHandler, ToCss}; +use crate::targets::Browsers; +use crate::traits::{FallbackValues, Parse, PropertyHandler, Shorthand, ToCss}; use crate::values::image::ImageFallback; use crate::values::length::LengthOrNumber; use crate::values::rect::Rect; @@ -194,25 +195,26 @@ impl From for WebKitMaskComposite { } } -/// A value for the [mask](https://www.w3.org/TR/css-masking-1/#the-mask) shorthand property. -#[derive(Debug, Clone, PartialEq)] -pub struct Mask<'i> { - /// The mask image. - pub image: Image<'i>, - /// The position of the mask. - pub position: Position, - /// The size of the mask image. - pub size: BackgroundSize, - /// How the mask repeats. - pub repeat: BackgroundRepeat, - /// The box in which the mask is clipped. - pub clip: MaskClip, - /// The origin of the mask. - pub origin: GeometryBox, - /// How the mask is composited with the element. - pub composite: MaskComposite, - /// How the mask image is interpreted. - pub mode: MaskMode, +define_list_shorthand! { + /// A value for the [mask](https://www.w3.org/TR/css-masking-1/#the-mask) shorthand property. + pub struct Mask<'i>(VendorPrefix) { + /// The mask image. + image: MaskImage(Image<'i>, VendorPrefix), + /// The position of the mask. + position: MaskPosition(Position, VendorPrefix), + /// The size of the mask image. + size: MaskSize(BackgroundSize, VendorPrefix), + /// How the mask repeats. + repeat: MaskRepeat(BackgroundRepeat, VendorPrefix), + /// The box in which the mask is clipped. + clip: MaskClip(MaskClip, VendorPrefix), + /// The origin of the mask. + origin: MaskOrigin(GeometryBox, VendorPrefix), + /// How the mask is composited with the element. + composite: MaskComposite(MaskComposite), + /// How the mask image is interpreted. + mode: MaskMode(MaskMode), + } } impl<'i> Parse<'i> for Mask<'i> { @@ -436,13 +438,23 @@ impl Default for MaskBorderMode { } } -/// A value for the [mask-border](https://www.w3.org/TR/css-masking-1/#the-mask-border) shorthand property. -#[derive(Debug, Clone, PartialEq)] -pub struct MaskBorder<'i> { - /// The border image shorthand. - pub border_image: BorderImage<'i>, - /// How the mask image is interpreted. - pub mode: MaskBorderMode, +define_shorthand! { + /// A value for the [mask-border](https://www.w3.org/TR/css-masking-1/#the-mask-border) shorthand property. + #[derive(Default)] + pub struct MaskBorder<'i> { + /// The mask image. + source: MaskBorderSource(Image<'i>), + /// The offsets that define where the image is sliced. + slice: MaskBorderSlice(BorderImageSlice), + /// The width of the mask image. + width: MaskBorderWidth(Rect), + /// The amount that the image extends beyond the border box. + outset: MaskBorderOutset(Rect), + /// How the mask image is scaled and tiled. + repeat: MaskBorderRepeat(BorderImageRepeat), + /// How the mask image is interpreted. + mode: MaskBorderMode(MaskBorderMode), + } } impl<'i> Parse<'i> for MaskBorder<'i> { @@ -459,8 +471,13 @@ impl<'i> Parse<'i> for MaskBorder<'i> { }); if border_image.is_ok() || mode.is_some() { + let border_image = border_image.unwrap_or_default(); Ok(MaskBorder { - border_image: border_image.unwrap_or_default(), + source: border_image.source, + slice: border_image.slice, + width: border_image.width, + outset: border_image.outset, + repeat: border_image.repeat, mode: mode.unwrap_or_default(), }) } else { @@ -474,7 +491,7 @@ impl<'i> ToCss for MaskBorder<'i> { where W: std::fmt::Write, { - self.border_image.to_css(dest)?; + BorderImage::to_css_internal(&self.source, &self.slice, &self.width, &self.outset, &self.repeat, dest)?; if self.mode != MaskBorderMode::default() { dest.write_char(' ')?; self.mode.to_css(dest)?; @@ -483,6 +500,29 @@ impl<'i> ToCss for MaskBorder<'i> { } } +impl<'i> FallbackValues for MaskBorder<'i> { + fn get_fallbacks(&mut self, targets: Browsers) -> Vec { + self + .source + .get_fallbacks(targets) + .into_iter() + .map(|source| MaskBorder { source, ..self.clone() }) + .collect() + } +} + +impl<'i> Into> for MaskBorder<'i> { + fn into(self) -> BorderImage<'i> { + BorderImage { + source: self.source, + slice: self.slice, + width: self.width, + outset: self.outset, + repeat: self.repeat, + } + } +} + #[derive(Default)] pub(crate) struct MaskHandler<'i> { images: Option<(SmallVec<[Image<'i>; 1]>, VendorPrefix)>, @@ -616,7 +656,7 @@ impl<'i> PropertyHandler<'i> for MaskHandler<'i> { Property::MaskBorderRepeat(val) => property!(border_repeat, val, &VendorPrefix::None), Property::WebKitMaskBoxImageRepeat(val, _) => property!(border_repeat, val, &VendorPrefix::WebKit), Property::MaskBorder(val) => { - border_shorthand!(val.border_image, VendorPrefix::None); + border_shorthand!(val, VendorPrefix::None); self.border_mode = Some(val.mode.clone()); } Property::WebKitMaskBoxImage(val, _) => { @@ -928,12 +968,13 @@ impl<'i> MaskHandler<'i> { { let intersection = *source_vp & *slice_vp & *width_vp & *outset_vp & *repeat_vp; if !intersection.is_empty() && (!intersection.contains(VendorPrefix::None) || mode.is_some()) { - let mut border_image = BorderImage { + let mut mask_border = MaskBorder { source: source.clone(), slice: slice.clone(), width: width.clone(), outset: outset.clone(), repeat: repeat.clone(), + mode: mode.unwrap_or_default(), }; let mut prefix = intersection; @@ -945,7 +986,7 @@ impl<'i> MaskHandler<'i> { if let Some(targets) = context.targets { // Get vendor prefix and color fallbacks. - let fallbacks = border_image.get_fallbacks(targets); + let fallbacks = mask_border.get_fallbacks(targets); for fallback in fallbacks { let mut p = fallback.source.get_vendor_prefix() - VendorPrefix::None & prefix; if p.is_empty() { @@ -953,32 +994,32 @@ impl<'i> MaskHandler<'i> { } if p.contains(VendorPrefix::WebKit) { - dest.push(Property::WebKitMaskBoxImage(fallback.clone(), VendorPrefix::WebKit)); + dest.push(Property::WebKitMaskBoxImage( + fallback.clone().into(), + VendorPrefix::WebKit, + )); } if p.contains(VendorPrefix::None) { - dest.push(Property::MaskBorder(MaskBorder { - border_image: fallback.clone(), - mode: mode.unwrap().clone(), - })) + dest.push(Property::MaskBorder(fallback)); } } } - let p = border_image.source.get_vendor_prefix() - VendorPrefix::None & prefix; + let p = mask_border.source.get_vendor_prefix() - VendorPrefix::None & prefix; if !p.is_empty() { prefix = p; } if prefix.contains(VendorPrefix::WebKit) { - dest.push(Property::WebKitMaskBoxImage(border_image.clone(), VendorPrefix::WebKit)); + dest.push(Property::WebKitMaskBoxImage( + mask_border.clone().into(), + VendorPrefix::WebKit, + )); } if prefix.contains(VendorPrefix::None) { - dest.push(Property::MaskBorder(MaskBorder { - border_image: border_image.clone(), - mode: mode.unwrap(), - })); + dest.push(Property::MaskBorder(mask_border)); mode = None; } diff --git a/src/properties/mod.rs b/src/properties/mod.rs index 9743d7d7..9bffad52 100644 --- a/src/properties/mod.rs +++ b/src/properties/mod.rs @@ -26,13 +26,13 @@ //! ``` //! use smallvec::smallvec; //! use parcel_css::{ -//! properties::{Property, background::*}, +//! properties::{Property, PropertyId, background::*}, //! values::{url::Url, image::Image, color::CssColor, position::*, length::*}, //! stylesheet::{ParserOptions, PrinterOptions}, //! }; //! //! let background = Property::parse_string( -//! "background", +//! PropertyId::from("background"), //! "url('img.png') repeat fixed 20px 10px / 50px 100px", //! ParserOptions::default() //! ).unwrap(); @@ -50,7 +50,7 @@ //! blue: 0, //! alpha: 0 //! }), -//! position: Position { +//! position: BackgroundPosition { //! x: HorizontalPosition::Length(LengthPercentage::px(20.0)), //! y: VerticalPosition::Length(LengthPercentage::px(10.0)), //! }, @@ -118,13 +118,15 @@ pub mod transform; pub mod transition; pub mod ui; +use crate::declaration::DeclarationBlock; use crate::error::{ParserError, PrinterError}; +use crate::logical::{LogicalGroup, PropertyCategory}; use crate::parser::starts_with_ignore_ascii_case; use crate::parser::ParserOptions; use crate::prefixes::Feature; use crate::printer::{Printer, PrinterOptions}; use crate::targets::Browsers; -use crate::traits::{Parse, ToCss}; +use crate::traits::{Parse, Shorthand, ToCss}; use crate::values::number::{CSSInteger, CSSNumber}; use crate::values::string::CowArcStr; use crate::values::{ @@ -149,6 +151,7 @@ use font::*; #[cfg(feature = "grid")] use grid::*; use list::*; +use margin_padding::*; use masking::*; use outline::*; use overflow::*; @@ -164,7 +167,7 @@ macro_rules! define_properties { ( $( $(#[$meta: meta])* - $name: literal: $property: ident($type: ty $(, $vp: ty)?) $( / $prefix: ident )* $( unprefixed: $unprefixed: literal )? $( if $condition: ident )?, + $name: literal: $property: ident($type: ty $(, $vp: ty)?) $( / $prefix: ident )* $( unprefixed: $unprefixed: literal )? $( shorthand: $shorthand: literal )? $( [ logical_group: $logical_group: ident, category: $logical_category: ident ] )? $( if $condition: ident )?, )+ ) => { /// A CSS property id. @@ -185,11 +188,13 @@ macro_rules! define_properties { ($x: ty, $n: ident) => { $n }; + ($x: ty, $n: expr) => { + $n + }; } - impl<'i> Parse<'i> for PropertyId<'i> { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { - let name = input.expect_ident()?; + impl<'i> From> for PropertyId<'i> { + fn from(name: CowArcStr<'i>) -> PropertyId<'i> { let name_ref = name.as_ref(); let (prefix, name_ref) = if starts_with_ignore_ascii_case(name_ref, "-webkit-") { (VendorPrefix::WebKit, &name_ref[8..]) @@ -227,14 +232,28 @@ macro_rules! define_properties { let allowed_prefixes = get_allowed_prefixes!($($unprefixed)?) $(| VendorPrefix::$prefix)*; if allowed_prefixes.contains(prefix) { - return Ok(get_propertyid!($($vp)?)) + return get_propertyid!($($vp)?) } }, )+ - "all" => return Ok(PropertyId::All), + "all" => return PropertyId::All, _ => {} } - Ok(PropertyId::Custom(name.into())) + PropertyId::Custom(name) + } + } + + impl<'i> From<&'i str> for PropertyId<'i> { + #[inline] + fn from(name: &'i str) -> PropertyId<'i> { + PropertyId::from(CowArcStr::from(name)) + } + } + + impl<'i> Parse<'i> for PropertyId<'i> { + fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { + let name = input.expect_ident()?; + Ok(CowArcStr::from(name).into()) } } @@ -364,8 +383,8 @@ macro_rules! define_properties { } } - #[allow(dead_code)] - pub(crate) fn name(&self) -> &str { + /// Returns the property name, without any vendor prefixes. + pub fn name(&self) -> &str { use PropertyId::*; match self { @@ -377,6 +396,123 @@ macro_rules! define_properties { Custom(name) => &name } } + + /// Returns whether a property is a shorthand. + pub fn is_shorthand(&self) -> bool { + $( + macro_rules! shorthand { + ($s: literal) => { + if let PropertyId::$property$((vp_name!($vp, _prefix)))? = self { + return true + } + }; + () => {} + } + + shorthand!($($shorthand)?); + )+ + + false + } + + /// Returns a shorthand value for this property id from the given declaration block. + pub(crate) fn shorthand_value<'a>(&self, decls: &DeclarationBlock<'a>) -> Option<(Property<'a>, bool)> { + // Inline function to remap lifetime names. + #[inline] + fn shorthand_value<'a, 'i>(property_id: &PropertyId<'a>, decls: &DeclarationBlock<'i>) -> Option<(Property<'i>, bool)> { + $( + #[allow(unused_macros)] + macro_rules! prefix { + ($v: ty, $p: ident) => { + *$p + }; + ($p: ident) => { + VendorPrefix::None + }; + } + + macro_rules! shorthand { + ($s: literal) => { + if let PropertyId::$property$((vp_name!($vp, prefix)))? = &property_id { + if let Some((val, important)) = <$type>::from_longhands(decls, prefix!($($vp,)? prefix)) { + return Some((Property::$property(val $(, *vp_name!($vp, prefix))?), important)) + } + } + }; + () => {} + } + + shorthand!($($shorthand)?); + )+ + + None + } + + shorthand_value(self, decls) + } + + /// Returns a list of longhand property ids for a shorthand. + pub fn longhands(&self) -> Option>> { + macro_rules! prefix_default { + ($x: ty, $p: ident) => { + *$p + }; + () => { + VendorPrefix::None + }; + } + + $( + macro_rules! shorthand { + ($s: literal) => { + if let PropertyId::$property$((vp_name!($vp, prefix)))? = self { + return Some(<$type>::longhands(prefix_default!($($vp, prefix)?))); + } + }; + () => {} + } + + shorthand!($($shorthand)?); + )+ + + None + } + + /// Returns the logical property group for this property. + pub(crate) fn logical_group(&self) -> Option { + $( + macro_rules! group { + ($g: ident) => { + if let PropertyId::$property$((vp_name!($vp, _prefix)))? = self { + return Some(LogicalGroup::$g) + } + }; + () => {} + } + + group!($($logical_group)?); + )+ + + None + } + + /// Returns whether the property is logical or physical. + pub(crate) fn category(&self) -> Option { + $( + macro_rules! category { + ($c: ident) => { + if let PropertyId::$property$((vp_name!($vp, _prefix)))? = self { + return Some(PropertyCategory::$c) + } + }; + () => {} + } + + category!($($logical_category)?); + )+ + + None + } } /// A CSS property. @@ -395,67 +531,22 @@ macro_rules! define_properties { impl<'i> Property<'i> { /// Parses a CSS property by name. - pub fn parse<'t>(name: CowRcStr<'i>, input: &mut Parser<'i, 't>, options: &ParserOptions) -> Result, ParseError<'i, ParserError<'i>>> { + pub fn parse<'t>(property_id: PropertyId<'i>, input: &mut Parser<'i, 't>, options: &ParserOptions) -> Result, ParseError<'i, ParserError<'i>>> { let state = input.state(); - let name_ref = name.as_ref(); - let (prefix, name_ref) = if starts_with_ignore_ascii_case(name_ref, "-webkit-") { - (VendorPrefix::WebKit, &name_ref[8..]) - } else if starts_with_ignore_ascii_case(name_ref, "-moz-") { - (VendorPrefix::Moz, &name_ref[5..]) - } else if starts_with_ignore_ascii_case(name_ref, "-o-") { - (VendorPrefix::O, &name_ref[3..]) - } else if starts_with_ignore_ascii_case(name_ref, "-ms-") { - (VendorPrefix::Ms, &name_ref[4..]) - } else { - (VendorPrefix::None, name_ref) - }; - macro_rules! get_allowed_prefixes { - ($v: literal) => { - VendorPrefix::empty() - }; - () => { - VendorPrefix::None - }; - } - - let property_id = match_ignore_ascii_case! { name_ref, + match property_id { $( $(#[$meta])* - $name $(if options.$condition)? => { - let allowed_prefixes = get_allowed_prefixes!($($unprefixed)?) $(| VendorPrefix::$prefix)*; - if allowed_prefixes.contains(prefix) { - if let Ok(c) = <$type>::parse(input) { - if input.expect_exhausted().is_ok() { - macro_rules! get_property { - ($v: ty) => { - Property::$property(c, prefix) - }; - () => { - Property::$property(c) - }; - } - - return Ok(get_property!($($vp)?)) - } - } - - macro_rules! get_propertyid { - ($v: ty) => { - PropertyId::$property(prefix) - }; - () => { - PropertyId::$property - }; + PropertyId::$property$((vp_name!($vp, prefix)))? $(if options.$condition)? => { + if let Ok(c) = <$type>::parse(input) { + if input.expect_exhausted().is_ok() { + return Ok(Property::$property(c $(, vp_name!($vp, prefix))?)) } - - get_propertyid!($($vp)?) - } else { - return Ok(Property::Custom(CustomProperty::parse(name, input)?)) } }, )+ - _ => return Ok(Property::Custom(CustomProperty::parse(name, input)?)) + PropertyId::Custom(name) => return Ok(Property::Custom(CustomProperty::parse(name, input)?)), + _ => {} }; // If a value was unable to be parsed, treat as an unparsed property. @@ -466,25 +557,25 @@ macro_rules! define_properties { return Ok(Property::Unparsed(UnparsedProperty::parse(property_id, input)?)) } - #[allow(dead_code)] - pub(crate) fn name(&self) -> &str { + /// Returns the property id for this property. + pub fn property_id(&self) -> PropertyId<'i> { use Property::*; match self { $( $(#[$meta])* - $property(_, $(vp_name!($vp, _p))?) => $name, + $property(_, $(vp_name!($vp, p))?) => PropertyId::$property$((*vp_name!($vp, p)))?, )+ - Unparsed(unparsed) => unparsed.property_id.name(), - Custom(custom) => &custom.name, + Unparsed(unparsed) => unparsed.property_id.clone(), + Custom(custom) => PropertyId::Custom(custom.name.clone()) } } /// Parses a CSS property from a string. - pub fn parse_string(name: &'i str, input: &'i str, options: ParserOptions) -> Result>> { + pub fn parse_string(property_id: PropertyId<'i>, input: &'i str, options: ParserOptions) -> Result>> { let mut input = ParserInput::new(input); let mut parser = Parser::new(&mut input); - Self::parse(CowRcStr::from(name), &mut parser, &options) + Self::parse(property_id, &mut parser, &options) } /// Serializes the value of a CSS property without its name or `!important` flag. @@ -507,6 +598,14 @@ macro_rules! define_properties { } } + /// Serializes the value of a CSS property as a string. + pub fn value_to_css_string(&self, options: PrinterOptions) -> Result { + let mut s = String::new(); + let mut printer = Printer::new(&mut s, options); + self.value_to_css(&mut printer)?; + Ok(s) + } + /// Serializes the CSS property, with an optional `!important` flag. pub fn to_css(&self, dest: &mut Printer, important: bool) -> Result<(), PrinterError> where W: std::fmt::Write { use Property::*; @@ -588,6 +687,51 @@ macro_rules! define_properties { self.to_css(&mut printer, important)?; Ok(s) } + + /// Returns the given longhand property for a shorthand. + pub fn longhand(&self, property_id: &PropertyId) -> Option> { + $( + macro_rules! shorthand { + ($s: literal) => { + if let Property::$property(val $(, vp_name!($vp, prefix))?) = self { + $( + if *vp_name!($vp, prefix) != property_id.prefix() { + return None + } + )? + return val.longhand(property_id) + } + }; + () => {} + } + + shorthand!($($shorthand)?); + )+ + + None + } + + /// Updates this shorthand from a longhand property. + pub fn set_longhand(&mut self, property: &Property<'i>) -> Result<(), ()> { + $( + macro_rules! shorthand { + ($s: literal) => { + if let Property::$property(val $(, vp_name!($vp, prefix))?) = self { + $( + if *vp_name!($vp, prefix) != property.property_id().prefix() { + return Err(()) + } + )? + return val.set_longhand(property) + } + }; + () => {} + } + + shorthand!($($shorthand)?); + )+ + Err(()) + } } }; } @@ -597,13 +741,13 @@ define_properties! { "background-image": BackgroundImage(SmallVec<[Image<'i>; 1]>), "background-position-x": BackgroundPositionX(SmallVec<[HorizontalPosition; 1]>), "background-position-y": BackgroundPositionY(SmallVec<[VerticalPosition; 1]>), - "background-position": BackgroundPosition(SmallVec<[Position; 1]>), + "background-position": BackgroundPosition(SmallVec<[BackgroundPosition; 1]>) shorthand: true, "background-size": BackgroundSize(SmallVec<[BackgroundSize; 1]>), "background-repeat": BackgroundRepeat(SmallVec<[BackgroundRepeat; 1]>), "background-attachment": BackgroundAttachment(SmallVec<[BackgroundAttachment; 1]>), "background-clip": BackgroundClip(SmallVec<[BackgroundClip; 1]>, VendorPrefix) / WebKit / Moz, "background-origin": BackgroundOrigin(SmallVec<[BackgroundOrigin; 1]>), - "background": Background(SmallVec<[Background<'i>; 1]>), + "background": Background(SmallVec<[Background<'i>; 1]>) shorthand: true, "box-shadow": BoxShadow(SmallVec<[BoxShadow; 1]>, VendorPrefix) / WebKit / Moz, "opacity": Opacity(AlphaValue), @@ -611,108 +755,108 @@ define_properties! { "display": Display(Display), "visibility": Visibility(Visibility), - "width": Width(Size), - "height": Height(Size), - "min-width": MinWidth(MinMaxSize), - "min-height": MinHeight(MinMaxSize), - "max-width": MaxWidth(MinMaxSize), - "max-height": MaxHeight(MinMaxSize), - "block-size": BlockSize(Size), - "inline-size": InlineSize(Size), - "min-block-size": MinBlockSize(MinMaxSize), - "min-inline-size": MinInlineSize(MinMaxSize), - "max-block-size": MaxBlockSize(MinMaxSize), - "max-inline-size": MaxInlineSize(MinMaxSize), + "width": Width(Size) [logical_group: Size, category: Physical], + "height": Height(Size) [logical_group: Size, category: Physical], + "min-width": MinWidth(MinMaxSize) [logical_group: MinSize, category: Physical], + "min-height": MinHeight(MinMaxSize) [logical_group: MinSize, category: Physical], + "max-width": MaxWidth(MinMaxSize) [logical_group: MaxSize, category: Physical], + "max-height": MaxHeight(MinMaxSize) [logical_group: MaxSize, category: Physical], + "block-size": BlockSize(Size) [logical_group: Size, category: Logical], + "inline-size": InlineSize(Size) [logical_group: Size, category: Logical], + "min-block-size": MinBlockSize(MinMaxSize) [logical_group: MinSize, category: Logical], + "min-inline-size": MinInlineSize(MinMaxSize) [logical_group: MinSize, category: Logical], + "max-block-size": MaxBlockSize(MinMaxSize) [logical_group: MaxSize, category: Logical], + "max-inline-size": MaxInlineSize(MinMaxSize) [logical_group: MaxSize, category: Logical], "box-sizing": BoxSizing(BoxSizing, VendorPrefix) / WebKit / Moz, - "overflow": Overflow(Overflow), + "overflow": Overflow(Overflow) shorthand: true, "overflow-x": OverflowX(OverflowKeyword), "overflow-y": OverflowY(OverflowKeyword), "text-overflow": TextOverflow(TextOverflow, VendorPrefix) / O, // https://www.w3.org/TR/2020/WD-css-position-3-20200519 "position": Position(position::Position), - "top": Top(LengthPercentageOrAuto), - "bottom": Bottom(LengthPercentageOrAuto), - "left": Left(LengthPercentageOrAuto), - "right": Right(LengthPercentageOrAuto), - "inset-block-start": InsetBlockStart(LengthPercentageOrAuto), - "inset-block-end": InsetBlockEnd(LengthPercentageOrAuto), - "inset-inline-start": InsetInlineStart(LengthPercentageOrAuto), - "inset-inline-end": InsetInlineEnd(LengthPercentageOrAuto), - "inset-block": InsetBlock(Size2D), - "inset-inline": InsetInline(Size2D), - "inset": Inset(Rect), - - "border-top-color": BorderTopColor(CssColor), - "border-bottom-color": BorderBottomColor(CssColor), - "border-left-color": BorderLeftColor(CssColor), - "border-right-color": BorderRightColor(CssColor), - "border-block-start-color": BorderBlockStartColor(CssColor), - "border-block-end-color": BorderBlockEndColor(CssColor), - "border-inline-start-color": BorderInlineStartColor(CssColor), - "border-inline-end-color": BorderInlineEndColor(CssColor), - - "border-top-style": BorderTopStyle(BorderStyle), - "border-bottom-style": BorderBottomStyle(BorderStyle), - "border-left-style": BorderLeftStyle(BorderStyle), - "border-right-style": BorderRightStyle(BorderStyle), - "border-block-start-style": BorderBlockStartStyle(BorderStyle), - "border-block-end-style": BorderBlockEndStyle(BorderStyle), - "border-inline-start-style": BorderInlineStartStyle(BorderStyle), - "border-inline-end-style": BorderInlineEndStyle(BorderStyle), - - "border-top-width": BorderTopWidth(BorderSideWidth), - "border-bottom-width": BorderBottomWidth(BorderSideWidth), - "border-left-width": BorderLeftWidth(BorderSideWidth), - "border-right-width": BorderRightWidth(BorderSideWidth), - "border-block-start-width": BorderBlockStartWidth(BorderSideWidth), - "border-block-end-width": BorderBlockEndWidth(BorderSideWidth), - "border-inline-start-width": BorderInlineStartWidth(BorderSideWidth), - "border-inline-end-width": BorderInlineEndWidth(BorderSideWidth), - - "border-top-left-radius": BorderTopLeftRadius(Size2D, VendorPrefix) / WebKit / Moz, - "border-top-right-radius": BorderTopRightRadius(Size2D, VendorPrefix) / WebKit / Moz, - "border-bottom-left-radius": BorderBottomLeftRadius(Size2D, VendorPrefix) / WebKit / Moz, - "border-bottom-right-radius": BorderBottomRightRadius(Size2D, VendorPrefix) / WebKit / Moz, - "border-start-start-radius": BorderStartStartRadius(Size2D), - "border-start-end-radius": BorderStartEndRadius(Size2D), - "border-end-start-radius": BorderEndStartRadius(Size2D), - "border-end-end-radius": BorderEndEndRadius(Size2D), - "border-radius": BorderRadius(BorderRadius, VendorPrefix) / WebKit / Moz, + "top": Top(LengthPercentageOrAuto) [logical_group: Inset, category: Physical], + "bottom": Bottom(LengthPercentageOrAuto) [logical_group: Inset, category: Physical], + "left": Left(LengthPercentageOrAuto) [logical_group: Inset, category: Physical], + "right": Right(LengthPercentageOrAuto) [logical_group: Inset, category: Physical], + "inset-block-start": InsetBlockStart(LengthPercentageOrAuto) [logical_group: Inset, category: Logical], + "inset-block-end": InsetBlockEnd(LengthPercentageOrAuto) [logical_group: Inset, category: Logical], + "inset-inline-start": InsetInlineStart(LengthPercentageOrAuto) [logical_group: Inset, category: Logical], + "inset-inline-end": InsetInlineEnd(LengthPercentageOrAuto) [logical_group: Inset, category: Logical], + "inset-block": InsetBlock(InsetBlock) shorthand: true, + "inset-inline": InsetInline(InsetInline) shorthand: true, + "inset": Inset(Inset) shorthand: true, + + "border-top-color": BorderTopColor(CssColor) [logical_group: BorderColor, category: Physical], + "border-bottom-color": BorderBottomColor(CssColor) [logical_group: BorderColor, category: Physical], + "border-left-color": BorderLeftColor(CssColor) [logical_group: BorderColor, category: Physical], + "border-right-color": BorderRightColor(CssColor) [logical_group: BorderColor, category: Physical], + "border-block-start-color": BorderBlockStartColor(CssColor) [logical_group: BorderColor, category: Logical], + "border-block-end-color": BorderBlockEndColor(CssColor) [logical_group: BorderColor, category: Logical], + "border-inline-start-color": BorderInlineStartColor(CssColor) [logical_group: BorderColor, category: Logical], + "border-inline-end-color": BorderInlineEndColor(CssColor) [logical_group: BorderColor, category: Logical], + + "border-top-style": BorderTopStyle(LineStyle) [logical_group: BorderStyle, category: Physical], + "border-bottom-style": BorderBottomStyle(LineStyle) [logical_group: BorderStyle, category: Physical], + "border-left-style": BorderLeftStyle(LineStyle) [logical_group: BorderStyle, category: Physical], + "border-right-style": BorderRightStyle(LineStyle) [logical_group: BorderStyle, category: Physical], + "border-block-start-style": BorderBlockStartStyle(LineStyle) [logical_group: BorderStyle, category: Logical], + "border-block-end-style": BorderBlockEndStyle(LineStyle) [logical_group: BorderStyle, category: Logical], + "border-inline-start-style": BorderInlineStartStyle(LineStyle) [logical_group: BorderStyle, category: Logical], + "border-inline-end-style": BorderInlineEndStyle(LineStyle) [logical_group: BorderStyle, category: Logical], + + "border-top-width": BorderTopWidth(BorderSideWidth) [logical_group: BorderWidth, category: Physical], + "border-bottom-width": BorderBottomWidth(BorderSideWidth) [logical_group: BorderWidth, category: Physical], + "border-left-width": BorderLeftWidth(BorderSideWidth) [logical_group: BorderWidth, category: Physical], + "border-right-width": BorderRightWidth(BorderSideWidth) [logical_group: BorderWidth, category: Physical], + "border-block-start-width": BorderBlockStartWidth(BorderSideWidth) [logical_group: BorderWidth, category: Logical], + "border-block-end-width": BorderBlockEndWidth(BorderSideWidth) [logical_group: BorderWidth, category: Logical], + "border-inline-start-width": BorderInlineStartWidth(BorderSideWidth) [logical_group: BorderWidth, category: Logical], + "border-inline-end-width": BorderInlineEndWidth(BorderSideWidth) [logical_group: BorderWidth, category: Logical], + + "border-top-left-radius": BorderTopLeftRadius(Size2D, VendorPrefix) / WebKit / Moz [logical_group: BorderRadius, category: Physical], + "border-top-right-radius": BorderTopRightRadius(Size2D, VendorPrefix) / WebKit / Moz [logical_group: BorderRadius, category: Physical], + "border-bottom-left-radius": BorderBottomLeftRadius(Size2D, VendorPrefix) / WebKit / Moz [logical_group: BorderRadius, category: Physical], + "border-bottom-right-radius": BorderBottomRightRadius(Size2D, VendorPrefix) / WebKit / Moz [logical_group: BorderRadius, category: Physical], + "border-start-start-radius": BorderStartStartRadius(Size2D) [logical_group: BorderRadius, category: Logical], + "border-start-end-radius": BorderStartEndRadius(Size2D) [logical_group: BorderRadius, category: Logical], + "border-end-start-radius": BorderEndStartRadius(Size2D) [logical_group: BorderRadius, category: Logical], + "border-end-end-radius": BorderEndEndRadius(Size2D) [logical_group: BorderRadius, category: Logical], + "border-radius": BorderRadius(BorderRadius, VendorPrefix) / WebKit / Moz shorthand: true, "border-image-source": BorderImageSource(Image<'i>), "border-image-outset": BorderImageOutset(Rect), "border-image-repeat": BorderImageRepeat(BorderImageRepeat), "border-image-width": BorderImageWidth(Rect), "border-image-slice": BorderImageSlice(BorderImageSlice), - "border-image": BorderImage(BorderImage<'i>, VendorPrefix) / WebKit / Moz / O, - - "border-color": BorderColor(Rect), - "border-style": BorderStyle(Rect), - "border-width": BorderWidth(Rect), - - "border-block-color": BorderBlockColor(CssColor), - "border-block-style": BorderBlockStyle(BorderStyle), - "border-block-width": BorderBlockWidth(BorderSideWidth), - - "border-inline-color": BorderInlineColor(CssColor), - "border-inline-style": BorderInlineStyle(BorderStyle), - "border-inline-width": BorderInlineWidth(BorderSideWidth), - - "border": Border(Border), - "border-top": BorderTop(Border), - "border-bottom": BorderBottom(Border), - "border-left": BorderLeft(Border), - "border-right": BorderRight(Border), - "border-block": BorderBlock(Border), - "border-block-start": BorderBlockStart(Border), - "border-block-end": BorderBlockEnd(Border), - "border-inline": BorderInline(Border), - "border-inline-start": BorderInlineStart(Border), - "border-inline-end": BorderInlineEnd(Border), - - "outline": Outline(Outline), + "border-image": BorderImage(BorderImage<'i>, VendorPrefix) / WebKit / Moz / O shorthand: true, + + "border-color": BorderColor(BorderColor) shorthand: true, + "border-style": BorderStyle(BorderStyle) shorthand: true, + "border-width": BorderWidth(BorderWidth) shorthand: true, + + "border-block-color": BorderBlockColor(BorderBlockColor) shorthand: true, + "border-block-style": BorderBlockStyle(BorderBlockStyle) shorthand: true, + "border-block-width": BorderBlockWidth(BorderBlockWidth) shorthand: true, + + "border-inline-color": BorderInlineColor(BorderInlineColor) shorthand: true, + "border-inline-style": BorderInlineStyle(BorderInlineStyle) shorthand: true, + "border-inline-width": BorderInlineWidth(BorderInlineWidth) shorthand: true, + + "border": Border(Border) shorthand: true, + "border-top": BorderTop(BorderTop) shorthand: true, + "border-bottom": BorderBottom(BorderBottom) shorthand: true, + "border-left": BorderLeft(BorderLeft) shorthand: true, + "border-right": BorderRight(BorderRight) shorthand: true, + "border-block": BorderBlock(BorderBlock) shorthand: true, + "border-block-start": BorderBlockStart(BorderBlockStart) shorthand: true, + "border-block-end": BorderBlockEnd(BorderBlockEnd) shorthand: true, + "border-inline": BorderInline(BorderInline) shorthand: true, + "border-inline-start": BorderInlineStart(BorderInlineStart) shorthand: true, + "border-inline-end": BorderInlineEnd(BorderInlineEnd) shorthand: true, + + "outline": Outline(Outline) shorthand: true, "outline-color": OutlineColor(CssColor), "outline-style": OutlineStyle(OutlineStyle), "outline-width": OutlineWidth(BorderSideWidth), @@ -720,26 +864,26 @@ define_properties! { // Flex properties: https://www.w3.org/TR/2018/CR-css-flexbox-1-20181119 "flex-direction": FlexDirection(FlexDirection, VendorPrefix) / WebKit / Ms, "flex-wrap": FlexWrap(FlexWrap, VendorPrefix) / WebKit / Ms, - "flex-flow": FlexFlow(FlexFlow, VendorPrefix) / WebKit / Ms, + "flex-flow": FlexFlow(FlexFlow, VendorPrefix) / WebKit / Ms shorthand: true, "flex-grow": FlexGrow(CSSNumber, VendorPrefix) / WebKit, "flex-shrink": FlexShrink(CSSNumber, VendorPrefix) / WebKit, "flex-basis": FlexBasis(LengthPercentageOrAuto, VendorPrefix) / WebKit, - "flex": Flex(Flex, VendorPrefix) / WebKit / Ms, + "flex": Flex(Flex, VendorPrefix) / WebKit / Ms shorthand: true, "order": Order(CSSInteger, VendorPrefix) / WebKit, // Align properties: https://www.w3.org/TR/2020/WD-css-align-3-20200421 "align-content": AlignContent(AlignContent, VendorPrefix) / WebKit, "justify-content": JustifyContent(JustifyContent, VendorPrefix) / WebKit, - "place-content": PlaceContent(PlaceContent), + "place-content": PlaceContent(PlaceContent) shorthand: true, "align-self": AlignSelf(AlignSelf, VendorPrefix) / WebKit, "justify-self": JustifySelf(JustifySelf), - "place-self": PlaceSelf(PlaceSelf), + "place-self": PlaceSelf(PlaceSelf) shorthand: true, "align-items": AlignItems(AlignItems, VendorPrefix) / WebKit, "justify-items": JustifyItems(JustifyItems), - "place-items": PlaceItems(PlaceItems), + "place-items": PlaceItems(PlaceItems) shorthand: true, "row-gap": RowGap(GapValue), "column-gap": ColumnGap(GapValue), - "gap": Gap(Gap), + "gap": Gap(Gap) shorthand: true, // Old flex (2009): https://www.w3.org/TR/2009/WD-css3-flexbox-20090723/ "box-orient": BoxOrient(BoxOrient, VendorPrefix) / WebKit / Moz unprefixed: false, @@ -776,9 +920,9 @@ define_properties! { #[cfg(feature = "grid")] "grid-template-areas": GridTemplateAreas(GridTemplateAreas), #[cfg(feature = "grid")] - "grid-template": GridTemplate(GridTemplate<'i>), + "grid-template": GridTemplate(GridTemplate<'i>) shorthand: true, #[cfg(feature = "grid")] - "grid": Grid(Grid<'i>), + "grid": Grid(Grid<'i>) shorthand: true, #[cfg(feature = "grid")] "grid-row-start": GridRowStart(GridLine<'i>), #[cfg(feature = "grid")] @@ -788,62 +932,59 @@ define_properties! { #[cfg(feature = "grid")] "grid-column-end": GridColumnEnd(GridLine<'i>), #[cfg(feature = "grid")] - "grid-row": GridRow(GridPlacement<'i>), + "grid-row": GridRow(GridRow<'i>) shorthand: true, #[cfg(feature = "grid")] - "grid-column": GridColumn(GridPlacement<'i>), + "grid-column": GridColumn(GridColumn<'i>) shorthand: true, #[cfg(feature = "grid")] - "grid-area": GridArea(GridArea<'i>), - - "margin-top": MarginTop(LengthPercentageOrAuto), - "margin-bottom": MarginBottom(LengthPercentageOrAuto), - "margin-left": MarginLeft(LengthPercentageOrAuto), - "margin-right": MarginRight(LengthPercentageOrAuto), - "margin-block-start": MarginBlockStart(LengthPercentageOrAuto), - "margin-block-end": MarginBlockEnd(LengthPercentageOrAuto), - "margin-inline-start": MarginInlineStart(LengthPercentageOrAuto), - "margin-inline-end": MarginInlineEnd(LengthPercentageOrAuto), - "margin-block": MarginBlock(Size2D), - "margin-inline": MarginInline(Size2D), - "margin": Margin(Rect), - - "padding-top": PaddingTop(LengthPercentageOrAuto), - "padding-bottom": PaddingBottom(LengthPercentageOrAuto), - "padding-left": PaddingLeft(LengthPercentageOrAuto), - "padding-right": PaddingRight(LengthPercentageOrAuto), - "padding-block-start": PaddingBlockStart(LengthPercentageOrAuto), - "padding-block-end": PaddingBlockEnd(LengthPercentageOrAuto), - "padding-inline-start": PaddingInlineStart(LengthPercentageOrAuto), - "padding-inline-end": PaddingInlineEnd(LengthPercentageOrAuto), - "padding-block": PaddingBlock(Size2D), - "padding-inline": PaddingInline(Size2D), - "padding": Padding(Rect), - - "scroll-margin-top": ScrollMarginTop(LengthPercentageOrAuto), - "scroll-margin-bottom": ScrollMarginBottom(LengthPercentageOrAuto), - "scroll-margin-left": ScrollMarginLeft(LengthPercentageOrAuto), - "scroll-margin-right": ScrollMarginRight(LengthPercentageOrAuto), - "scroll-margin-block-start": ScrollMarginBlockStart(LengthPercentageOrAuto), - "scroll-margin-block-end": ScrollMarginBlockEnd(LengthPercentageOrAuto), - "scroll-margin-inline-start": ScrollMarginInlineStart(LengthPercentageOrAuto), - "scroll-margin-inline-end": ScrollMarginInlineEnd(LengthPercentageOrAuto), - "scroll-margin-block": ScrollMarginBlock(Size2D), - "scroll-margin-inline": ScrollMarginInline(Size2D), - "scroll-margin": ScrollMargin(Rect), - - "scroll-padding-top": ScrollPaddingTop(LengthPercentageOrAuto), - "scroll-padding-bottom": ScrollPaddingBottom(LengthPercentageOrAuto), - "scroll-padding-left": ScrollPaddingLeft(LengthPercentageOrAuto), - "scroll-padding-right": ScrollPaddingRight(LengthPercentageOrAuto), - "scroll-padding-block-start": ScrollPaddingBlockStart(LengthPercentageOrAuto), - "scroll-padding-block-end": ScrollPaddingBlockEnd(LengthPercentageOrAuto), - "scroll-padding-inline-start": ScrollPaddingInlineStart(LengthPercentageOrAuto), - "scroll-padding-inline-end": ScrollPaddingInlineEnd(LengthPercentageOrAuto), - "scroll-padding-block": ScrollPaddingBlock(Size2D), - "scroll-padding-inline": ScrollPaddingInline(Size2D), - "scroll-padding": ScrollPadding(Rect), - - // shorthands: columns, list-style - // grid, inset + "grid-area": GridArea(GridArea<'i>) shorthand: true, + + "margin-top": MarginTop(LengthPercentageOrAuto) [logical_group: Margin, category: Physical], + "margin-bottom": MarginBottom(LengthPercentageOrAuto) [logical_group: Margin, category: Physical], + "margin-left": MarginLeft(LengthPercentageOrAuto) [logical_group: Margin, category: Physical], + "margin-right": MarginRight(LengthPercentageOrAuto) [logical_group: Margin, category: Physical], + "margin-block-start": MarginBlockStart(LengthPercentageOrAuto) [logical_group: Margin, category: Logical], + "margin-block-end": MarginBlockEnd(LengthPercentageOrAuto) [logical_group: Margin, category: Logical], + "margin-inline-start": MarginInlineStart(LengthPercentageOrAuto) [logical_group: Margin, category: Logical], + "margin-inline-end": MarginInlineEnd(LengthPercentageOrAuto) [logical_group: Margin, category: Logical], + "margin-block": MarginBlock(MarginBlock) shorthand: true, + "margin-inline": MarginInline(MarginInline) shorthand: true, + "margin": Margin(Margin) shorthand: true, + + "padding-top": PaddingTop(LengthPercentageOrAuto) [logical_group: Padding, category: Physical], + "padding-bottom": PaddingBottom(LengthPercentageOrAuto) [logical_group: Padding, category: Physical], + "padding-left": PaddingLeft(LengthPercentageOrAuto) [logical_group: Padding, category: Physical], + "padding-right": PaddingRight(LengthPercentageOrAuto) [logical_group: Padding, category: Physical], + "padding-block-start": PaddingBlockStart(LengthPercentageOrAuto) [logical_group: Padding, category: Logical], + "padding-block-end": PaddingBlockEnd(LengthPercentageOrAuto) [logical_group: Padding, category: Logical], + "padding-inline-start": PaddingInlineStart(LengthPercentageOrAuto) [logical_group: Padding, category: Logical], + "padding-inline-end": PaddingInlineEnd(LengthPercentageOrAuto) [logical_group: Padding, category: Logical], + "padding-block": PaddingBlock(PaddingBlock) shorthand: true, + "padding-inline": PaddingInline(PaddingInline) shorthand: true, + "padding": Padding(Padding) shorthand: true, + + "scroll-margin-top": ScrollMarginTop(LengthPercentageOrAuto) [logical_group: ScrollMargin, category: Physical], + "scroll-margin-bottom": ScrollMarginBottom(LengthPercentageOrAuto) [logical_group: ScrollMargin, category: Physical], + "scroll-margin-left": ScrollMarginLeft(LengthPercentageOrAuto) [logical_group: ScrollMargin, category: Physical], + "scroll-margin-right": ScrollMarginRight(LengthPercentageOrAuto) [logical_group: ScrollMargin, category: Physical], + "scroll-margin-block-start": ScrollMarginBlockStart(LengthPercentageOrAuto) [logical_group: ScrollMargin, category: Logical], + "scroll-margin-block-end": ScrollMarginBlockEnd(LengthPercentageOrAuto) [logical_group: ScrollMargin, category: Logical], + "scroll-margin-inline-start": ScrollMarginInlineStart(LengthPercentageOrAuto) [logical_group: ScrollMargin, category: Logical], + "scroll-margin-inline-end": ScrollMarginInlineEnd(LengthPercentageOrAuto) [logical_group: ScrollMargin, category: Logical], + "scroll-margin-block": ScrollMarginBlock(ScrollMarginBlock) shorthand: true, + "scroll-margin-inline": ScrollMarginInline(ScrollMarginInline) shorthand: true, + "scroll-margin": ScrollMargin(ScrollMargin) shorthand: true, + + "scroll-padding-top": ScrollPaddingTop(LengthPercentageOrAuto) [logical_group: ScrollPadding, category: Physical], + "scroll-padding-bottom": ScrollPaddingBottom(LengthPercentageOrAuto) [logical_group: ScrollPadding, category: Physical], + "scroll-padding-left": ScrollPaddingLeft(LengthPercentageOrAuto) [logical_group: ScrollPadding, category: Physical], + "scroll-padding-right": ScrollPaddingRight(LengthPercentageOrAuto) [logical_group: ScrollPadding, category: Physical], + "scroll-padding-block-start": ScrollPaddingBlockStart(LengthPercentageOrAuto) [logical_group: ScrollPadding, category: Logical], + "scroll-padding-block-end": ScrollPaddingBlockEnd(LengthPercentageOrAuto) [logical_group: ScrollPadding, category: Logical], + "scroll-padding-inline-start": ScrollPaddingInlineStart(LengthPercentageOrAuto) [logical_group: ScrollPadding, category: Logical], + "scroll-padding-inline-end": ScrollPaddingInlineEnd(LengthPercentageOrAuto) [logical_group: ScrollPadding, category: Logical], + "scroll-padding-block": ScrollPaddingBlock(ScrollPaddingBlock) shorthand: true, + "scroll-padding-inline": ScrollPaddingInline(ScrollPaddingInline) shorthand: true, + "scroll-padding": ScrollPadding(ScrollPadding) shorthand: true, "font-weight": FontWeight(FontWeight), "font-size": FontSize(FontSize), @@ -852,7 +993,7 @@ define_properties! { "font-style": FontStyle(FontStyle), "font-variant-caps": FontVariantCaps(FontVariantCaps), "line-height": LineHeight(LineHeight), - "font": Font(Font<'i>), + "font": Font(Font<'i>) shorthand: true, "vertical-align": VerticalAlign(VerticalAlign), "font-palette": FontPalette(DashedIdent<'i>), @@ -860,7 +1001,7 @@ define_properties! { "transition-duration": TransitionDuration(SmallVec<[Time; 1]>, VendorPrefix) / WebKit / Moz / Ms, "transition-delay": TransitionDelay(SmallVec<[Time; 1]>, VendorPrefix) / WebKit / Moz / Ms, "transition-timing-function": TransitionTimingFunction(SmallVec<[EasingFunction; 1]>, VendorPrefix) / WebKit / Moz / Ms, - "transition": Transition(SmallVec<[Transition<'i>; 1]>, VendorPrefix) / WebKit / Moz / Ms, + "transition": Transition(SmallVec<[Transition<'i>; 1]>, VendorPrefix) / WebKit / Moz / Ms shorthand: true, "animation-name": AnimationName(AnimationNameList<'i>, VendorPrefix) / WebKit / Moz / O, "animation-duration": AnimationDuration(SmallVec<[Time; 1]>, VendorPrefix) / WebKit / Moz / O, @@ -870,7 +1011,7 @@ define_properties! { "animation-play-state": AnimationPlayState(SmallVec<[AnimationPlayState; 1]>, VendorPrefix) / WebKit / Moz / O, "animation-delay": AnimationDelay(SmallVec<[Time; 1]>, VendorPrefix) / WebKit / Moz / O, "animation-fill-mode": AnimationFillMode(SmallVec<[AnimationFillMode; 1]>, VendorPrefix) / WebKit / Moz / O, - "animation": Animation(AnimationList<'i>, VendorPrefix) / WebKit / Moz / O, + "animation": Animation(AnimationList<'i>, VendorPrefix) / WebKit / Moz / O shorthand: true, // https://drafts.csswg.org/css-transforms-2/ "transform": Transform(TransformList, VendorPrefix) / WebKit / Moz / Ms / O, @@ -905,11 +1046,11 @@ define_properties! { "text-decoration-style": TextDecorationStyle(TextDecorationStyle, VendorPrefix) / WebKit / Moz, "text-decoration-color": TextDecorationColor(CssColor, VendorPrefix) / WebKit / Moz, "text-decoration-thickness": TextDecorationThickness(TextDecorationThickness), - "text-decoration": TextDecoration(TextDecoration, VendorPrefix) / WebKit / Moz, + "text-decoration": TextDecoration(TextDecoration, VendorPrefix) / WebKit / Moz shorthand: true, "text-decoration-skip-ink": TextDecorationSkipInk(TextDecorationSkipInk, VendorPrefix) / WebKit, "text-emphasis-style": TextEmphasisStyle(TextEmphasisStyle<'i>, VendorPrefix) / WebKit, "text-emphasis-color": TextEmphasisColor(CssColor, VendorPrefix) / WebKit, - "text-emphasis": TextEmphasis(TextEmphasis<'i>, VendorPrefix) / WebKit, + "text-emphasis": TextEmphasis(TextEmphasis<'i>, VendorPrefix) / WebKit shorthand: true, "text-emphasis-position": TextEmphasisPosition(TextEmphasisPosition, VendorPrefix) / WebKit, "text-shadow": TextShadow(SmallVec<[TextShadow; 1]>), @@ -918,7 +1059,7 @@ define_properties! { "cursor": Cursor(Cursor<'i>), "caret-color": CaretColor(ColorOrAuto), "caret-shape": CaretShape(CaretShape), - "caret": Caret(Caret), + "caret": Caret(Caret) shorthand: true, "user-select": UserSelect(UserSelect, VendorPrefix) / WebKit / Moz / Ms, "accent-color": AccentColor(ColorOrAuto), "appearance": Appearance(Appearance<'i>, VendorPrefix) / WebKit / Moz / Ms, @@ -927,7 +1068,7 @@ define_properties! { "list-style-type": ListStyleType(ListStyleType<'i>), "list-style-image": ListStyleImage(Image<'i>), "list-style-position": ListStylePosition(ListStylePosition), - "list-style": ListStyle(ListStyle<'i>), + "list-style": ListStyle(ListStyle<'i>) shorthand: true, "marker-side": MarkerSide(MarkerSide), // CSS modules @@ -970,14 +1111,14 @@ define_properties! { "mask-size": MaskSize(SmallVec<[BackgroundSize; 1]>, VendorPrefix) / WebKit, "mask-composite": MaskComposite(SmallVec<[MaskComposite; 1]>), "mask-type": MaskType(MaskType), - "mask": Mask(SmallVec<[Mask<'i>; 1]>, VendorPrefix) / WebKit, + "mask": Mask(SmallVec<[Mask<'i>; 1]>, VendorPrefix) / WebKit shorthand: true, "mask-border-source": MaskBorderSource(Image<'i>), "mask-border-mode": MaskBorderMode(MaskBorderMode), "mask-border-slice": MaskBorderSlice(BorderImageSlice), "mask-border-width": MaskBorderWidth(Rect), "mask-border-outset": MaskBorderOutset(Rect), "mask-border-repeat": MaskBorderRepeat(BorderImageRepeat), - "mask-border": MaskBorder(MaskBorder<'i>), + "mask-border": MaskBorder(MaskBorder<'i>) shorthand: true, // WebKit additions "-webkit-mask-composite": WebKitMaskComposite(SmallVec<[WebKitMaskComposite; 1]>), diff --git a/src/properties/outline.rs b/src/properties/outline.rs index 6ffc37b2..95d5c82c 100644 --- a/src/properties/outline.rs +++ b/src/properties/outline.rs @@ -1,14 +1,14 @@ //! CSS properties related to outlines. -use super::border::{BorderSideWidth, BorderStyle, GenericBorder}; +use super::border::{BorderSideWidth, GenericBorder, LineStyle}; use super::{Property, PropertyId}; use crate::context::PropertyHandlerContext; -use crate::declaration::DeclarationList; +use crate::declaration::{DeclarationBlock, DeclarationList}; use crate::error::{ParserError, PrinterError}; -use crate::macros::shorthand_handler; +use crate::macros::{impl_shorthand, shorthand_handler}; use crate::printer::Printer; use crate::targets::Browsers; -use crate::traits::{FallbackValues, Parse, PropertyHandler, ToCss}; +use crate::traits::{FallbackValues, Parse, PropertyHandler, Shorthand, ToCss}; use crate::values::color::CssColor; use cssparser::*; @@ -18,13 +18,13 @@ pub enum OutlineStyle { /// The `auto` keyword. Auto, /// A value equivalent to the `border-style` property. - BorderStyle(BorderStyle), + LineStyle(LineStyle), } impl<'i> Parse<'i> for OutlineStyle { fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { - if let Ok(border_style) = input.try_parse(BorderStyle::parse) { - return Ok(OutlineStyle::BorderStyle(border_style)); + if let Ok(border_style) = input.try_parse(LineStyle::parse) { + return Ok(OutlineStyle::LineStyle(border_style)); } input.expect_ident_matching("auto")?; @@ -39,19 +39,27 @@ impl ToCss for OutlineStyle { { match self { OutlineStyle::Auto => dest.write_str("auto"), - OutlineStyle::BorderStyle(border_style) => border_style.to_css(dest), + OutlineStyle::LineStyle(border_style) => border_style.to_css(dest), } } } impl Default for OutlineStyle { fn default() -> OutlineStyle { - OutlineStyle::BorderStyle(BorderStyle::None) + OutlineStyle::LineStyle(LineStyle::None) } } /// A value for the [outline](https://drafts.csswg.org/css-ui/#outline) shorthand property. -pub type Outline = GenericBorder; +pub type Outline = GenericBorder; + +impl_shorthand! { + Outline(Outline) { + width: [OutlineWidth], + style: [OutlineStyle], + color: [OutlineColor], + } +} shorthand_handler!(OutlineHandler -> Outline { width: OutlineWidth(BorderSideWidth), diff --git a/src/properties/overflow.rs b/src/properties/overflow.rs index 3083e808..d9e005a7 100644 --- a/src/properties/overflow.rs +++ b/src/properties/overflow.rs @@ -3,12 +3,12 @@ use super::{Property, PropertyId}; use crate::compat::Feature; use crate::context::PropertyHandlerContext; -use crate::declaration::DeclarationList; +use crate::declaration::{DeclarationBlock, DeclarationList}; use crate::error::{ParserError, PrinterError}; -use crate::macros::enum_property; +use crate::macros::{define_shorthand, enum_property}; use crate::printer::Printer; use crate::targets::Browsers; -use crate::traits::{Parse, PropertyHandler, ToCss}; +use crate::traits::{Parse, PropertyHandler, Shorthand, ToCss}; use cssparser::*; enum_property! { @@ -28,13 +28,14 @@ enum_property! { } } -/// A value for the [overflow](https://www.w3.org/TR/css-overflow-3/#overflow-properties) shorthand property. -#[derive(Debug, Clone, PartialEq)] -pub struct Overflow { - /// The overflow mode for the x direction. - pub x: OverflowKeyword, - /// The overflow mode for the y direction. - pub y: OverflowKeyword, +define_shorthand! { + /// A value for the [overflow](https://www.w3.org/TR/css-overflow-3/#overflow-properties) shorthand property. + pub struct Overflow { + /// The overflow mode for the x direction. + x: OverflowX(OverflowKeyword), + /// The overflow mode for the y direction. + y: OverflowY(OverflowKeyword), + } } impl<'i> Parse<'i> for Overflow { diff --git a/src/properties/svg.rs b/src/properties/svg.rs index af38e876..38f402eb 100644 --- a/src/properties/svg.rs +++ b/src/properties/svg.rs @@ -208,7 +208,7 @@ impl ToCss for StrokeDasharray { } } -/// A value for the [marker](https://www.w3.org/TR/SVG2/painting.html#VertexMarkerProperties) shorthand property. +/// A value for the [marker](https://www.w3.org/TR/SVG2/painting.html#VertexMarkerProperties) properties. #[derive(Debug, Clone, PartialEq)] pub enum Marker<'i> { /// No marker. diff --git a/src/properties/text.rs b/src/properties/text.rs index a0520851..5ab53c77 100644 --- a/src/properties/text.rs +++ b/src/properties/text.rs @@ -5,13 +5,13 @@ use super::{Property, PropertyId}; use crate::compat; use crate::context::PropertyHandlerContext; -use crate::declaration::DeclarationList; +use crate::declaration::{DeclarationBlock, DeclarationList}; use crate::error::{ParserError, PrinterError}; -use crate::macros::enum_property; +use crate::macros::{define_shorthand, enum_property}; use crate::prefixes::Feature; use crate::printer::Printer; use crate::targets::Browsers; -use crate::traits::{FallbackValues, Parse, PropertyHandler, ToCss}; +use crate::traits::{FallbackValues, Parse, PropertyHandler, Shorthand, ToCss}; use crate::values::calc::{Calc, MathFunction}; use crate::values::color::{ColorFallbackKind, CssColor}; use crate::values::length::{Length, LengthPercentage, LengthValue}; @@ -557,17 +557,18 @@ impl ToCss for TextDecorationThickness { } } -/// A value for the [text-decoration](https://www.w3.org/TR/2020/WD-css-text-decor-4-20200506/#text-decoration-property) shorthand property. -#[derive(Debug, Clone, PartialEq)] -pub struct TextDecoration { - /// The lines to display. - pub line: TextDecorationLine, - /// The thickness of the lines. - pub thickness: TextDecorationThickness, - /// The style of the lines. - pub style: TextDecorationStyle, - /// The color of the lines. - pub color: CssColor, +define_shorthand! { + /// A value for the [text-decoration](https://www.w3.org/TR/2020/WD-css-text-decor-4-20200506/#text-decoration-property) shorthand property. + pub struct TextDecoration(VendorPrefix) { + /// The lines to display. + line: TextDecorationLine(TextDecorationLine, VendorPrefix), + /// The thickness of the lines. + thickness: TextDecorationThickness(TextDecorationThickness), + /// The style of the lines. + style: TextDecorationStyle(TextDecorationStyle, VendorPrefix), + /// The color of the lines. + color: TextDecorationColor(CssColor, VendorPrefix), + } } impl<'i> Parse<'i> for TextDecoration { @@ -771,13 +772,14 @@ impl<'i> ToCss for TextEmphasisStyle<'i> { } } -/// A value for the [text-emphasis](https://www.w3.org/TR/2020/WD-css-text-decor-4-20200506/#text-emphasis-property) shorthand property. -#[derive(Debug, Clone, PartialEq)] -pub struct TextEmphasis<'i> { - /// The text emphasis style. - pub style: TextEmphasisStyle<'i>, - /// The text emphasis color. - pub color: CssColor, +define_shorthand! { + /// A value for the [text-emphasis](https://www.w3.org/TR/2020/WD-css-text-decor-4-20200506/#text-emphasis-property) shorthand property. + pub struct TextEmphasis<'i>(VendorPrefix) { + /// The text emphasis style. + style: TextEmphasisStyle(TextEmphasisStyle<'i>, VendorPrefix), + /// The text emphasis color. + color: TextEmphasisColor(CssColor, VendorPrefix), + } } impl<'i> Parse<'i> for TextEmphasis<'i> { diff --git a/src/properties/transition.rs b/src/properties/transition.rs index 49639876..252bb088 100644 --- a/src/properties/transition.rs +++ b/src/properties/transition.rs @@ -3,29 +3,32 @@ use super::{Property, PropertyId}; use crate::compat; use crate::context::PropertyHandlerContext; -use crate::declaration::DeclarationList; +use crate::declaration::{DeclarationBlock, DeclarationList}; use crate::error::{ParserError, PrinterError}; +use crate::macros::define_list_shorthand; use crate::prefixes::Feature; use crate::printer::Printer; use crate::properties::masking::get_webkit_mask_property; use crate::targets::Browsers; -use crate::traits::{Parse, PropertyHandler, ToCss}; +use crate::traits::{Parse, PropertyHandler, Shorthand, ToCss}; use crate::values::{easing::EasingFunction, time::Time}; use crate::vendor_prefix::VendorPrefix; use cssparser::*; +use itertools::izip; use smallvec::SmallVec; -/// A value for the [transition](https://www.w3.org/TR/2018/WD-css-transitions-1-20181011/#transition-shorthand-property) property. -#[derive(Debug, Clone, PartialEq)] -pub struct Transition<'i> { - /// The property to transition. - pub property: PropertyId<'i>, - /// The duration of the transition. - pub duration: Time, - /// The delay before the transition starts. - pub delay: Time, - /// The easing function for the transition. - pub timing_function: EasingFunction, +define_list_shorthand! { + /// A value for the [transition](https://www.w3.org/TR/2018/WD-css-transitions-1-20181011/#transition-shorthand-property) property. + pub struct Transition<'i>(VendorPrefix) { + /// The property to transition. + property: TransitionProperty(PropertyId<'i>, VendorPrefix), + /// The duration of the transition. + duration: TransitionDuration(Time, VendorPrefix), + /// The delay before the transition starts. + delay: TransitionDelay(Time, VendorPrefix), + /// The easing function for the transition. + timing_function: TransitionTimingFunction(EasingFunction, VendorPrefix), + } } impl<'i> Parse<'i> for Transition<'i> { diff --git a/src/properties/ui.rs b/src/properties/ui.rs index afb00d0d..36928b84 100644 --- a/src/properties/ui.rs +++ b/src/properties/ui.rs @@ -1,10 +1,12 @@ //! CSS properties related to user interface. +use crate::declaration::DeclarationBlock; use crate::error::{ParserError, PrinterError}; -use crate::macros::{enum_property, shorthand_property}; +use crate::macros::{define_shorthand, enum_property, shorthand_property}; use crate::printer::Printer; +use crate::properties::{Property, PropertyId}; use crate::targets::Browsers; -use crate::traits::{FallbackValues, Parse, ToCss}; +use crate::traits::{FallbackValues, Parse, Shorthand, ToCss}; use crate::values::color::CssColor; use crate::values::number::CSSNumber; use crate::values::string::CowArcStr; @@ -233,9 +235,9 @@ shorthand_property! { /// A value for the [caret](https://www.w3.org/TR/2021/WD-css-ui-4-20210316/#caret) shorthand property. pub struct Caret { /// The caret color. - color: ColorOrAuto, + color: CaretColor(ColorOrAuto), /// The caret shape. - shape: CaretShape, + shape: CaretShape(CaretShape), } } diff --git a/src/rules/counter_style.rs b/src/rules/counter_style.rs index 7bfa6bf9..bc89455d 100644 --- a/src/rules/counter_style.rs +++ b/src/rules/counter_style.rs @@ -27,6 +27,6 @@ impl<'i> ToCss for CounterStyleRule<'i> { dest.add_mapping(self.loc); dest.write_str("@counter-style ")?; self.name.to_css(dest)?; - self.declarations.to_css(dest) + self.declarations.to_css_block(dest) } } diff --git a/src/rules/font_face.rs b/src/rules/font_face.rs index ec06f4b1..ada6414c 100644 --- a/src/rules/font_face.rs +++ b/src/rules/font_face.rs @@ -422,7 +422,7 @@ impl<'i> cssparser::DeclarationParser<'i> for FontFaceDeclarationParser { } input.reset(&state); - return Ok(FontFaceProperty::Custom(CustomProperty::parse(name, input)?)); + return Ok(FontFaceProperty::Custom(CustomProperty::parse(name.into(), input)?)); } } diff --git a/src/rules/font_palette_values.rs b/src/rules/font_palette_values.rs index 1203b11a..7894e0c7 100644 --- a/src/rules/font_palette_values.rs +++ b/src/rules/font_palette_values.rs @@ -99,7 +99,10 @@ impl<'i> cssparser::DeclarationParser<'i> for FontPaletteValuesDeclarationParser } input.reset(&state); - return Ok(FontPaletteValuesProperty::Custom(CustomProperty::parse(name, input)?)); + return Ok(FontPaletteValuesProperty::Custom(CustomProperty::parse( + name.into(), + input, + )?)); } } diff --git a/src/rules/keyframes.rs b/src/rules/keyframes.rs index 5980cf0a..93d9043a 100644 --- a/src/rules/keyframes.rs +++ b/src/rules/keyframes.rs @@ -260,7 +260,7 @@ impl<'i> ToCss for Keyframe<'i> { selector.to_css(dest)?; } - self.declarations.to_css(dest) + self.declarations.to_css_block(dest) } } diff --git a/src/rules/page.rs b/src/rules/page.rs index 7bd6db0f..e336acc6 100644 --- a/src/rules/page.rs +++ b/src/rules/page.rs @@ -99,7 +99,7 @@ impl<'i> ToCss for PageRule<'i> { selector.to_css(dest)?; } } - self.declarations.to_css(dest) + self.declarations.to_css_block(dest) } } diff --git a/src/rules/viewport.rs b/src/rules/viewport.rs index 61367e85..22809576 100644 --- a/src/rules/viewport.rs +++ b/src/rules/viewport.rs @@ -27,6 +27,6 @@ impl<'i> ToCss for ViewportRule<'i> { dest.write_char('@')?; self.vendor_prefix.to_css(dest)?; dest.write_str("viewport")?; - self.declarations.to_css(dest) + self.declarations.to_css_block(dest) } } diff --git a/src/stylesheet.rs b/src/stylesheet.rs index 598334c3..b39d20a0 100644 --- a/src/stylesheet.rs +++ b/src/stylesheet.rs @@ -269,24 +269,7 @@ impl<'i> StyleAttribute<'i> { let mut dest = String::with_capacity(1); let mut printer = Printer::new(&mut dest, options); - let len = self.declarations.declarations.len() + self.declarations.important_declarations.len(); - let mut i = 0; - - macro_rules! write { - ($decls: expr, $important: literal) => { - for decl in &$decls { - decl.to_css(&mut printer, $important)?; - if i != len - 1 { - printer.write_char(';')?; - printer.whitespace()?; - } - i += 1; - } - }; - } - - write!(self.declarations.declarations, false); - write!(self.declarations.important_declarations, true); + self.declarations.to_css(&mut printer)?; Ok(ToCssResult { dependencies: printer.dependencies, diff --git a/src/traits.rs b/src/traits.rs index ab7b7582..e276fd9b 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,12 +1,13 @@ //! Traits for parsing and serializing CSS. use crate::context::PropertyHandlerContext; -use crate::declaration::DeclarationList; +use crate::declaration::{DeclarationBlock, DeclarationList}; use crate::error::{ParserError, PrinterError}; use crate::printer::Printer; -use crate::properties::Property; +use crate::properties::{Property, PropertyId}; use crate::stylesheet::PrinterOptions; use crate::targets::Browsers; +use crate::vendor_prefix::VendorPrefix; use cssparser::*; /// Trait for things that can be parsed from CSS syntax. @@ -80,3 +81,18 @@ pub(crate) trait FromStandard: Sized { pub(crate) trait FallbackValues: Sized { fn get_fallbacks(&mut self, targets: Browsers) -> Vec; } + +/// Trait for shorthand properties. +pub(crate) trait Shorthand<'i>: Sized { + /// Returns a shorthand from the longhand properties defined in the given declaration block. + fn from_longhands(decls: &DeclarationBlock<'i>, vendor_prefix: VendorPrefix) -> Option<(Self, bool)>; + + /// Returns a list of longhand property ids for this shorthand. + fn longhands(vendor_prefix: VendorPrefix) -> Vec>; + + /// Returns a longhand property for this shorthand. + fn longhand(&self, property_id: &PropertyId) -> Option>; + + /// Updates this shorthand from a longhand property. + fn set_longhand(&mut self, property: &Property<'i>) -> Result<(), ()>; +} diff --git a/tests/test_cssom.rs b/tests/test_cssom.rs new file mode 100644 index 00000000..9a3b2fe6 --- /dev/null +++ b/tests/test_cssom.rs @@ -0,0 +1,508 @@ +use parcel_css::{ + declaration::DeclarationBlock, + properties::{Property, PropertyId}, + stylesheet::{ParserOptions, PrinterOptions}, + traits::ToCss, + vendor_prefix::VendorPrefix, +}; + +fn get_test(decls: &str, property_id: PropertyId, expected: Option<(&str, bool)>) { + let decls = DeclarationBlock::parse_string(decls, ParserOptions::default()).unwrap(); + let v = decls.get(&property_id); + if let Some((expected, important)) = expected { + let (value, is_important) = v.unwrap(); + assert_eq!( + *value, + Property::parse_string(property_id, expected, ParserOptions::default()).unwrap() + ); + assert_eq!(is_important, important); + } else { + assert_eq!(v, None) + } +} + +#[test] +fn test_get() { + get_test("color: red", PropertyId::Color, Some(("red", false))); + get_test("color: red !important", PropertyId::Color, Some(("red", true))); + get_test("color: green; color: red", PropertyId::Color, Some(("red", false))); + get_test( + r#" + margin-top: 5px; + margin-bottom: 5px; + margin-left: 5px; + margin-right: 5px; + "#, + PropertyId::Margin, + Some(("5px", false)), + ); + get_test( + r#" + margin-top: 5px; + margin-bottom: 5px; + margin-left: 6px; + margin-right: 6px; + "#, + PropertyId::Margin, + Some(("5px 6px", false)), + ); + get_test( + r#" + margin-top: 5px; + margin-bottom: 5px; + margin-left: 6px; + margin-right: 6px; + "#, + PropertyId::Margin, + Some(("5px 6px", false)), + ); + get_test( + r#" + margin-top: 5px; + margin-bottom: 5px; + "#, + PropertyId::Margin, + None, + ); + get_test( + r#" + margin-top: 5px; + margin-bottom: 5px; + margin-left: 5px !important; + margin-right: 5px; + "#, + PropertyId::Margin, + None, + ); + get_test( + r#" + margin-top: 5px !important; + margin-bottom: 5px !important; + margin-left: 5px !important; + margin-right: 5px !important; + "#, + PropertyId::Margin, + Some(("5px", true)), + ); + get_test( + "margin: 5px 6px 7px 8px", + PropertyId::Margin, + Some(("5px 6px 7px 8px", false)), + ); + get_test("margin: 5px 6px 7px 8px", PropertyId::MarginTop, Some(("5px", false))); + get_test( + r#" + border: 1px solid red; + border-color: green; + "#, + PropertyId::Border, + Some(("1px solid green", false)), + ); + get_test( + r#" + border: 1px solid red; + border-left-color: green; + "#, + PropertyId::Border, + None, + ); + get_test("background: red", PropertyId::Background, Some(("red", false))); + get_test("background: red", PropertyId::BackgroundColor, Some(("red", false))); + get_test( + "background: red url(foo.png)", + PropertyId::BackgroundColor, + Some(("red", false)), + ); + get_test( + "background: url(foo.png), url(bar.png) red", + PropertyId::BackgroundColor, + Some(("red", false)), + ); + get_test( + "background: url(foo.png) green, url(bar.png) red", + PropertyId::BackgroundColor, + Some(("red", false)), + ); + get_test( + "background: linear-gradient(red, green)", + PropertyId::BackgroundImage, + Some(("linear-gradient(red, green)", false)), + ); + get_test( + "background: linear-gradient(red, green), linear-gradient(#fff, #000)", + PropertyId::BackgroundImage, + Some(("linear-gradient(red, green), linear-gradient(#fff, #000)", false)), + ); + get_test( + "background: linear-gradient(red, green) repeat-x, linear-gradient(#fff, #000) repeat-y", + PropertyId::BackgroundImage, + Some(("linear-gradient(red, green), linear-gradient(#fff, #000)", false)), + ); + get_test( + "background: linear-gradient(red, green) repeat-x, linear-gradient(#fff, #000) repeat-y", + PropertyId::BackgroundRepeat, + Some(("repeat-x, repeat-y", false)), + ); + get_test( + r#" + background: linear-gradient(red, green); + background-position-x: 20px; + background-position-y: 10px; + background-size: 50px 100px; + background-repeat: repeat no-repeat; + "#, + PropertyId::Background, + Some(("linear-gradient(red, green) 20px 10px / 50px 100px repeat-x", false)), + ); + get_test( + r#" + background: linear-gradient(red, green); + background-position-x: 20px; + background-position-y: 10px !important; + background-size: 50px 100px; + background-repeat: repeat no-repeat; + "#, + PropertyId::Background, + None, + ); + get_test( + r#" + background: linear-gradient(red, green), linear-gradient(#fff, #000) gray; + background-position-x: right 20px, 10px; + background-position-y: top 20px, 15px; + background-size: 50px 50px, auto; + background-repeat: repeat no-repeat, no-repeat; + "#, + PropertyId::Background, + Some(("linear-gradient(red, green) right 20px top 20px / 50px 50px repeat-x, gray linear-gradient(#fff, #000) 10px 15px no-repeat", false)), + ); + get_test( + r#" + background: linear-gradient(red, green); + background-position-x: right 20px, 10px; + background-position-y: top 20px, 15px; + background-size: 50px 50px, auto; + background-repeat: repeat no-repeat, no-repeat; + "#, + PropertyId::Background, + None, + ); + get_test( + r#" + background: linear-gradient(red, green); + background-position: 20px 10px; + background-size: 50px 100px; + background-repeat: repeat no-repeat; + "#, + PropertyId::Background, + Some(("linear-gradient(red, green) 20px 10px / 50px 100px repeat-x", false)), + ); + get_test( + r#" + background-position-x: 20px; + background-position-y: 10px; + "#, + PropertyId::BackgroundPosition, + Some(("20px 10px", false)), + ); + get_test( + r#" + background: linear-gradient(red, green) 20px 10px; + "#, + PropertyId::BackgroundPosition, + Some(("20px 10px", false)), + ); + get_test( + r#" + background: linear-gradient(red, green) 20px 10px; + "#, + PropertyId::BackgroundPositionX, + Some(("20px", false)), + ); + get_test( + r#" + background: linear-gradient(red, green) 20px 10px; + "#, + PropertyId::BackgroundPositionY, + Some(("10px", false)), + ); + get_test( + "mask-border: linear-gradient(red, green) 25", + PropertyId::MaskBorderSource, + Some(("linear-gradient(red, green)", false)), + ); + get_test("grid-area: a / b / c / d", PropertyId::GridRowStart, Some(("a", false))); + get_test("grid-area: a / b / c / d", PropertyId::GridRowEnd, Some(("c", false))); + get_test("grid-area: a / b / c / d", PropertyId::GridRow, Some(("a / c", false))); + get_test( + "grid-area: a / b / c / d", + PropertyId::GridColumn, + Some(("b / d", false)), + ); + get_test( + r#" + grid-template-rows: auto 1fr; + grid-template-columns: auto 1fr auto; + grid-template-areas: none; + "#, + PropertyId::GridTemplate, + Some(("auto 1fr / auto 1fr auto", false)), + ); + get_test( + r#" + grid-template-areas: ". a a ." + ". b b ."; + grid-template-rows: auto 1fr; + grid-template-columns: 10px 1fr 1fr 10px; + "#, + PropertyId::GridTemplate, + Some(( + r#" + ". a a ." + ". b b ." 1fr + / 10px 1fr 1fr 10px + "#, + false, + )), + ); + get_test( + r#" + grid-template-areas: "a a a" + "b b b"; + grid-template-columns: repeat(3, 1fr); + grid-template-rows: auto 1fr; + "#, + PropertyId::GridTemplate, + None, + ); + get_test( + r#" + grid-template-areas: "a a a" + "b b b"; + grid-template-rows: [header-top] auto [header-bottom main-top] 1fr [main-bottom]; + grid-template-columns: auto 1fr auto; + grid-auto-flow: row; + grid-auto-rows: auto; + grid-auto-columns: auto; + "#, + PropertyId::Grid, + Some(( + r#" + [header-top] "a a a" [header-bottom] + [main-top] "b b b" 1fr [main-bottom] + / auto 1fr auto + "#, + false, + )), + ); + get_test( + r#" + grid-template-areas: "a a a" + "b b b"; + grid-template-rows: [header-top] auto [header-bottom main-top] 1fr [main-bottom]; + grid-template-columns: auto 1fr auto; + grid-auto-flow: column; + grid-auto-rows: 1fr; + grid-auto-columns: 1fr; + "#, + PropertyId::Grid, + None, + ); + get_test( + r#" + flex-direction: row; + flex-wrap: wrap; + "#, + PropertyId::FlexFlow(VendorPrefix::None), + Some(("row wrap", false)), + ); + get_test( + r#" + -webkit-flex-direction: row; + -webkit-flex-wrap: wrap; + "#, + PropertyId::FlexFlow(VendorPrefix::WebKit), + Some(("row wrap", false)), + ); + get_test( + r#" + flex-direction: row; + flex-wrap: wrap; + "#, + PropertyId::FlexFlow(VendorPrefix::WebKit), + None, + ); + get_test( + r#" + -webkit-flex-direction: row; + flex-wrap: wrap; + "#, + PropertyId::FlexFlow(VendorPrefix::WebKit), + None, + ); + get_test( + r#" + -webkit-flex-direction: row; + flex-wrap: wrap; + "#, + PropertyId::FlexFlow(VendorPrefix::None), + None, + ); + get_test( + r#" + -webkit-flex-flow: row; + "#, + PropertyId::FlexDirection(VendorPrefix::WebKit), + Some(("row", false)), + ); + get_test( + r#" + -webkit-flex-flow: row; + "#, + PropertyId::FlexDirection(VendorPrefix::None), + None, + ); +} + +fn set_test(orig: &str, property: &str, value: &str, important: bool, expected: &str) { + let mut decls = DeclarationBlock::parse_string(orig, ParserOptions::default()).unwrap(); + decls.set( + Property::parse_string(property.into(), value, ParserOptions::default()).unwrap(), + important, + ); + assert_eq!(decls.to_css_string(PrinterOptions::default()).unwrap(), expected); +} + +#[test] +fn test_set() { + set_test("color: red", "color", "green", false, "color: green"); + set_test("color: red !important", "color", "green", false, "color: green"); + set_test("color: red", "color", "green", true, "color: green !important"); + set_test("margin: 5px", "margin", "10px", false, "margin: 10px"); + set_test("margin: 5px", "margin-top", "8px", false, "margin: 8px 5px 5px"); + set_test( + "margin: 5px", + "margin-inline-start", + "8px", + false, + "margin: 5px; margin-inline-start: 8px", + ); + set_test( + "margin-inline-start: 5px; margin-top: 10px", + "margin-inline-start", + "8px", + false, + "margin-inline-start: 5px; margin-top: 10px; margin-inline-start: 8px", + ); + set_test( + "margin: 5px; margin-inline-start: 8px", + "margin-left", + "10px", + false, + "margin: 5px; margin-inline-start: 8px; margin-left: 10px", + ); + set_test( + "border: 1px solid red", + "border-right", + "1px solid green", + false, + "border: 1px solid red; border-right: 1px solid green", + ); + set_test( + "border: 1px solid red", + "border-right-color", + "green", + false, + "border: 1px solid red; border-right-color: green", + ); + set_test( + "animation: foo 2s", + "animation-name", + "foo, bar", + false, + "animation: foo 2s; animation-name: foo, bar", + ); + set_test("animation: foo 2s", "animation-name", "bar", false, "animation: bar 2s"); + set_test( + "background: linear-gradient(red, green)", + "background-position-x", + "20px", + false, + "background: linear-gradient(red, green) 20px 0", + ); + set_test( + "background: linear-gradient(red, green)", + "background-position", + "20px 10px", + false, + "background: linear-gradient(red, green) 20px 10px", + ); + set_test( + "flex-flow: row wrap", + "flex-direction", + "column", + false, + "flex-flow: column wrap", + ); + set_test( + "-webkit-flex-flow: row wrap", + "-webkit-flex-direction", + "column", + false, + "-webkit-flex-flow: column wrap", + ); + set_test( + "flex-flow: row wrap", + "-webkit-flex-direction", + "column", + false, + "flex-flow: wrap; -webkit-flex-direction: column", + ); +} + +fn remove_test(orig: &str, property_id: PropertyId, expected: &str) { + let mut decls = DeclarationBlock::parse_string(orig, ParserOptions::default()).unwrap(); + decls.remove(&property_id); + assert_eq!(decls.to_css_string(PrinterOptions::default()).unwrap(), expected); +} + +#[test] +fn test_remove() { + remove_test("margin-top: 10px", PropertyId::MarginTop, ""); + remove_test( + "margin-top: 10px; margin-left: 5px", + PropertyId::MarginTop, + "margin-left: 5px", + ); + remove_test( + "margin-top: 10px !important; margin-left: 5px", + PropertyId::MarginTop, + "margin-left: 5px", + ); + remove_test( + "margin: 10px", + PropertyId::MarginTop, + "margin-right: 10px; margin-bottom: 10px; margin-left: 10px", + ); + remove_test("margin: 10px", PropertyId::Margin, ""); + remove_test( + "margin-top: 10px; margin-right: 10px; margin-bottom: 10px; margin-left: 10px", + PropertyId::Margin, + "", + ); + remove_test( + "flex-flow: column wrap", + PropertyId::FlexDirection(VendorPrefix::None), + "flex-wrap: wrap", + ); + remove_test( + "flex-flow: column wrap", + PropertyId::FlexDirection(VendorPrefix::WebKit), + "flex-flow: column wrap", + ); + remove_test( + "-webkit-flex-flow: column wrap", + PropertyId::FlexDirection(VendorPrefix::WebKit), + "-webkit-flex-wrap: wrap", + ); +}