From 76e31cf963cc0ef6ace0415664cc987c6977b07f Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Sun, 2 Jun 2024 21:17:43 -0700 Subject: [PATCH 001/139] Use macros to derive implementations of Parse and ToCss for many enums --- Cargo.lock | 1 + Cargo.toml | 4 +- derive/Cargo.toml | 1 + derive/src/lib.rs | 295 ++------------------------------- derive/src/parse.rs | 213 ++++++++++++++++++++++++ derive/src/to_css.rs | 156 +++++++++++++++++ derive/src/visit.rs | 292 ++++++++++++++++++++++++++++++++ src/macros.rs | 57 +------ src/properties/align.rs | 203 +++-------------------- src/properties/animation.rs | 182 ++++++-------------- src/properties/background.rs | 24 +-- src/properties/border.rs | 33 +--- src/properties/border_image.rs | 34 +--- src/properties/contain.rs | 6 +- src/properties/custom.rs | 20 +-- src/properties/display.rs | 59 ++----- src/properties/flex.rs | 16 +- src/properties/font.rs | 182 +++----------------- src/properties/grid.rs | 58 +------ src/properties/list.rs | 171 +++++++------------ src/properties/masking.rs | 77 +++------ src/properties/outline.rs | 25 +-- src/properties/position.rs | 25 +-- src/properties/size.rs | 4 +- src/properties/svg.rs | 111 ++----------- src/properties/text.rs | 161 +++++------------- src/properties/transform.rs | 38 +---- src/properties/ui.rs | 97 +++++------ src/rules/keyframes.rs | 20 +-- src/rules/page.rs | 32 ++-- src/traits.rs | 37 +++++ src/values/calc.rs | 8 +- src/values/color.rs | 1 + src/values/easing.rs | 16 +- src/values/gradient.rs | 42 +---- src/values/image.rs | 38 +---- src/values/length.rs | 62 +------ src/values/percentage.rs | 32 +--- src/values/shape.rs | 30 +--- 39 files changed, 1100 insertions(+), 1763 deletions(-) create mode 100644 derive/src/parse.rs create mode 100644 derive/src/to_css.rs create mode 100644 derive/src/visit.rs diff --git a/Cargo.lock b/Cargo.lock index a5d8b853..07d5f7e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -804,6 +804,7 @@ dependencies = [ name = "lightningcss-derive" version = "1.0.0-alpha.42" dependencies = [ + "convert_case", "proc-macro2", "quote", "syn 1.0.109", diff --git a/Cargo.toml b/Cargo.toml index 7036f761..75bc397c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,7 +43,7 @@ jsonschema = ["schemars", "serde", "parcel_selectors/jsonschema"] nodejs = ["dep:serde"] serde = ["dep:serde", "smallvec/serde", "cssparser/serde", "parcel_selectors/serde", "into_owned"] sourcemap = ["parcel_sourcemap"] -visitor = ["lightningcss-derive"] +visitor = [] into_owned = ["static-self", "static-self/smallvec", "parcel_selectors/into_owned"] substitute_variables = ["visitor", "into_owned"] @@ -69,7 +69,7 @@ browserslist-rs = { version = "0.15.0", optional = true } rayon = { version = "1.5.1", optional = true } dashmap = { version = "5.0.0", optional = true } serde_json = { version = "1.0.78", optional = true } -lightningcss-derive = { version = "=1.0.0-alpha.42", path = "./derive", optional = true } +lightningcss-derive = { version = "=1.0.0-alpha.42", path = "./derive" } schemars = { version = "0.8.19", features = ["smallvec"], optional = true } static-self = { version = "0.1.0", path = "static-self", optional = true } diff --git a/derive/Cargo.toml b/derive/Cargo.toml index 1864748e..e1f82c70 100644 --- a/derive/Cargo.toml +++ b/derive/Cargo.toml @@ -14,3 +14,4 @@ proc-macro = true syn = { version = "1.0", features = ["extra-traits"] } quote = "1.0" proc-macro2 = "1.0" +convert_case = "0.6.0" diff --git a/derive/src/lib.rs b/derive/src/lib.rs index 00b77a26..12241491 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -1,293 +1,20 @@ -use std::collections::HashSet; +use proc_macro::TokenStream; -use proc_macro::{self, TokenStream}; -use proc_macro2::{Span, TokenStream as TokenStream2}; -use quote::quote; -use syn::{ - parse::Parse, parse_macro_input, parse_quote, Attribute, Data, DataEnum, DeriveInput, Field, Fields, - GenericParam, Generics, Ident, Member, Token, Type, Visibility, -}; +mod parse; +mod to_css; +mod visit; #[proc_macro_derive(Visit, attributes(visit, skip_visit, skip_type, visit_types))] pub fn derive_visit_children(input: TokenStream) -> TokenStream { - let DeriveInput { - ident, - data, - generics, - attrs, - .. - } = parse_macro_input!(input); - - let options: Vec = attrs - .iter() - .filter_map(|attr| { - if attr.path.is_ident("visit") { - let opts: VisitOptions = attr.parse_args().unwrap(); - Some(opts) - } else { - None - } - }) - .collect(); - - let visit_types = if let Some(attr) = attrs.iter().find(|attr| attr.path.is_ident("visit_types")) { - let types: VisitTypes = attr.parse_args().unwrap(); - let types = types.types; - Some(quote! { crate::visit_types!(#(#types)|*) }) - } else { - None - }; - - if options.is_empty() { - derive(&ident, &data, &generics, None, visit_types) - } else { - options - .into_iter() - .map(|options| derive(&ident, &data, &generics, Some(options), visit_types.clone())) - .collect() - } -} - -fn derive( - ident: &Ident, - data: &Data, - generics: &Generics, - options: Option, - visit_types: Option, -) -> TokenStream { - let mut impl_generics = generics.clone(); - let mut type_defs = quote! {}; - let generics = if let Some(VisitOptions { - generic: Some(generic), .. - }) = &options - { - let mappings = generics - .type_params() - .zip(generic.type_params()) - .map(|(a, b)| quote! { type #a = #b; }); - type_defs = quote! { #(#mappings)* }; - impl_generics.params.clear(); - generic - } else { - &generics - }; - - if impl_generics.lifetimes().next().is_none() { - impl_generics.params.insert(0, parse_quote! { 'i }) - } - - let lifetime = impl_generics.lifetimes().next().unwrap().clone(); - let t = impl_generics.type_params().find(|g| &g.ident.to_string() == "R"); - let v = quote! { __V }; - let t = if let Some(t) = t { - GenericParam::Type(t.ident.clone().into()) - } else { - let t: GenericParam = parse_quote! { __T }; - impl_generics - .params - .push(parse_quote! { #t: crate::visitor::Visit<#lifetime, __T, #v> }); - t - }; - - impl_generics - .params - .push(parse_quote! { #v: ?Sized + crate::visitor::Visitor<#lifetime, #t> }); - - for ty in generics.type_params() { - let name = &ty.ident; - impl_generics.make_where_clause().predicates.push(parse_quote! { - #name: Visit<#lifetime, #t, #v> - }) - } - - let mut seen_types = HashSet::new(); - let mut child_types = Vec::new(); - let mut visit = Vec::new(); - match data { - Data::Struct(s) => { - for ( - index, - Field { - vis, ty, ident, attrs, .. - }, - ) in s.fields.iter().enumerate() - { - if attrs.iter().any(|attr| attr.path.is_ident("skip_visit")) { - continue; - } - - if matches!(ty, Type::Reference(_)) || !matches!(vis, Visibility::Public(..)) { - continue; - } - - if visit_types.is_none() && !seen_types.contains(ty) && !skip_type(attrs) { - seen_types.insert(ty.clone()); - child_types.push(quote! { - <#ty as Visit<#lifetime, #t, #v>>::CHILD_TYPES.bits() - }); - } - - let name = ident - .as_ref() - .map_or_else(|| Member::Unnamed(index.into()), |ident| Member::Named(ident.clone())); - visit.push(quote! { self.#name.visit(visitor)?; }) - } - } - Data::Enum(DataEnum { variants, .. }) => { - let variants = variants - .iter() - .map(|variant| { - let name = &variant.ident; - let mut field_names = Vec::new(); - let mut visit_fields = Vec::new(); - for (index, Field { ty, ident, attrs, .. }) in variant.fields.iter().enumerate() { - let name = ident.as_ref().map_or_else( - || Ident::new(&format!("_{}", index), Span::call_site()), - |ident| ident.clone(), - ); - field_names.push(name.clone()); - - if matches!(ty, Type::Reference(_)) { - continue; - } - - if visit_types.is_none() && !seen_types.contains(ty) && !skip_type(attrs) && !skip_type(&variant.attrs) - { - seen_types.insert(ty.clone()); - child_types.push(quote! { - <#ty as Visit<#lifetime, #t, #v>>::CHILD_TYPES.bits() - }); - } - - visit_fields.push(quote! { #name.visit(visitor)?; }) - } - - match variant.fields { - Fields::Unnamed(_) => { - quote! { - Self::#name(#(#field_names),*) => { - #(#visit_fields)* - } - } - } - Fields::Named(_) => { - quote! { - Self::#name { #(#field_names),* } => { - #(#visit_fields)* - } - } - } - Fields::Unit => quote! {}, - } - }) - .collect::(); - - visit.push(quote! { - match self { - #variants - _ => {} - } - }) - } - _ => {} - } - - if visit_types.is_none() && child_types.is_empty() { - child_types.push(quote! { crate::visitor::VisitTypes::empty().bits() }); - } - - let (_, ty_generics, _) = generics.split_for_impl(); - let (impl_generics, _, where_clause) = impl_generics.split_for_impl(); - - let self_visit = if let Some(VisitOptions { - visit: Some(visit), - kind: Some(kind), - .. - }) = &options - { - child_types.push(quote! { crate::visitor::VisitTypes::#kind.bits() }); - - quote! { - fn visit(&mut self, visitor: &mut #v) -> Result<(), #v::Error> { - if visitor.visit_types().contains(crate::visitor::VisitTypes::#kind) { - visitor.#visit(self) - } else { - self.visit_children(visitor) - } - } - } - } else { - quote! {} - }; - - let child_types = visit_types.unwrap_or_else(|| { - quote! { - { - #type_defs - crate::visitor::VisitTypes::from_bits_retain(#(#child_types)|*) - } - } - }); - - let output = quote! { - impl #impl_generics Visit<#lifetime, #t, #v> for #ident #ty_generics #where_clause { - const CHILD_TYPES: crate::visitor::VisitTypes = #child_types; - - #self_visit - - fn visit_children(&mut self, visitor: &mut #v) -> Result<(), #v::Error> { - if !<#lifetime, #t, #v>>::CHILD_TYPES.intersects(visitor.visit_types()) { - return Ok(()) - } - - #(#visit)* - - Ok(()) - } - } - }; - - output.into() -} - -fn skip_type(attrs: &Vec) -> bool { - attrs.iter().any(|attr| attr.path.is_ident("skip_type")) -} - -struct VisitOptions { - visit: Option, - kind: Option, - generic: Option, -} - -impl Parse for VisitOptions { - fn parse(input: syn::parse::ParseStream) -> syn::Result { - let (visit, kind, comma) = if input.peek(Ident) { - let visit: Ident = input.parse()?; - let _: Token![,] = input.parse()?; - let kind: Ident = input.parse()?; - let comma: Result = input.parse(); - (Some(visit), Some(kind), comma.is_ok()) - } else { - (None, None, true) - }; - let generic: Option = if comma { Some(input.parse()?) } else { None }; - Ok(Self { visit, kind, generic }) - } + visit::derive_visit_children(input) } -struct VisitTypes { - types: Vec, +#[proc_macro_derive(Parse, attributes(css))] +pub fn derive_parse(input: TokenStream) -> TokenStream { + parse::derive_parse(input) } -impl Parse for VisitTypes { - fn parse(input: syn::parse::ParseStream) -> syn::Result { - let first: Ident = input.parse()?; - let mut types = vec![first]; - while input.parse::().is_ok() { - let id: Ident = input.parse()?; - types.push(id); - } - Ok(Self { types }) - } +#[proc_macro_derive(ToCss, attributes(css))] +pub fn derive_to_css(input: TokenStream) -> TokenStream { + to_css::derive_to_css(input) } diff --git a/derive/src/parse.rs b/derive/src/parse.rs new file mode 100644 index 00000000..995b344e --- /dev/null +++ b/derive/src/parse.rs @@ -0,0 +1,213 @@ +use convert_case::Casing; +use proc_macro::{self, TokenStream}; +use proc_macro2::{Literal, Span, TokenStream as TokenStream2}; +use quote::quote; +use syn::{ + parse::Parse, parse_macro_input, parse_quote, Attribute, Data, DataEnum, DeriveInput, Fields, Ident, Token, +}; + +pub fn derive_parse(input: TokenStream) -> TokenStream { + let DeriveInput { + ident, + data, + mut generics, + attrs, + .. + } = parse_macro_input!(input); + let opts = CssOptions::parse_attributes(&attrs).unwrap(); + let cloned_generics = generics.clone(); + let (_, ty_generics, _) = cloned_generics.split_for_impl(); + + if generics.lifetimes().next().is_none() { + generics.params.insert(0, parse_quote! { 'i }) + } + + let lifetime = generics.lifetimes().next().unwrap().clone(); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + + let imp = match &data { + Data::Enum(data) => derive_enum(&data, &ident, &opts), + _ => todo!(), + }; + + let output = quote! { + impl #impl_generics Parse<#lifetime> for #ident #ty_generics #where_clause { + fn parse<'t>(input: &mut Parser<#lifetime, 't>) -> Result<#lifetime, ParserError<#lifetime>>> { + #imp + } + } + }; + + output.into() +} + +fn derive_enum(data: &DataEnum, ident: &Ident, opts: &CssOptions) -> TokenStream2 { + let mut idents = Vec::new(); + let mut non_idents = Vec::new(); + for (index, variant) in data.variants.iter().enumerate() { + let name = &variant.ident; + let fields = variant + .fields + .iter() + .enumerate() + .map(|(index, field)| { + field.ident.as_ref().map_or_else( + || Ident::new(&format!("_{}", index), Span::call_site()), + |ident| ident.clone(), + ) + }) + .collect::<_>>(); + + let mut expr = match &variant.fields { + Fields::Unit => { + idents.push(( + Literal::string(&variant.ident.to_string().to_case(opts.case)), + name.clone(), + )); + continue; + } + Fields::Named(_) => { + quote! { + return Ok(#ident::#name { #(#fields),* }) + } + } + Fields::Unnamed(_) => { + quote! { + return Ok(#ident::#name(#(#fields),*)) + } + } + }; + + // Group multiple ident branches together. + if !idents.is_empty() { + if idents.len() == 1 { + let (s, name) = idents.remove(0); + non_idents.push(quote! { + if input.try_parse(|input| input.expect_ident_matching(#s)).is_ok() { + return Ok(#ident::#name) + } + }); + } else { + let matches = idents + .iter() + .map(|(s, name)| { + quote! { + #s => return Ok(#ident::#name), + } + }) + .collect::<_>>(); + non_idents.push(quote! { + { + let state = input.state(); + if let Ok(ident) = input.try_parse(|input| input.expect_ident_cloned()) { + cssparser::match_ignore_ascii_case! { &*ident, + #(#matches)* + _ => {} + } + input.reset(&state); + } + } + }); + idents.clear(); + } + } + + let is_last = index == data.variants.len() - 1; + + for (index, field) in variant.fields.iter().enumerate().rev() { + let ty = &field.ty; + let field_name = field.ident.as_ref().map_or_else( + || Ident::new(&format!("_{}", index), Span::call_site()), + |ident| ident.clone(), + ); + if is_last { + expr = quote! { + let #field_name = <#ty>::parse(input)?; + #expr + }; + } else { + expr = quote! { + if let Ok(#field_name) = input.try_parse(<#ty>::parse) { + #expr + } + }; + } + } + + non_idents.push(expr); + } + + let idents = if idents.is_empty() { + quote! {} + } else if idents.len() == 1 { + let (s, name) = idents.remove(0); + quote! { + input.expect_ident_matching(#s)?; + Ok(#ident::#name) + } + } else { + let idents = idents + .into_iter() + .map(|(s, name)| { + quote! { + #s => Ok(#ident::#name), + } + }) + .collect::<_>>(); + quote! { + let location = input.current_source_location(); + let ident = input.expect_ident()?; + cssparser::match_ignore_ascii_case! { &*ident, + #(#idents)* + _ => Err(location.new_unexpected_token_error( + cssparser::Token::Ident(ident.clone()) + )) + } + } + }; + + let output = quote! { + #(#non_idents)* + #idents + }; + + output.into() +} + +pub struct CssOptions { + pub case: convert_case::Case, +} + +impl CssOptions { + pub fn parse_attributes(attrs: &Vec) -> syn::Result { + for attr in attrs { + if attr.path.is_ident("css") { + let opts: CssOptions = attr.parse_args()?; + return Ok(opts); + } + } + + Ok(CssOptions { + case: convert_case::Case::Kebab, + }) + } +} + +impl Parse for CssOptions { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let mut case = convert_case::Case::Kebab; + while !input.is_empty() { + let k: Ident = input.parse()?; + let _: Token![=] = input.parse()?; + let v: Ident = input.parse()?; + + if k == "case" { + if v == "lower" { + case = convert_case::Case::Flat; + } + } + } + + Ok(Self { case }) + } +} diff --git a/derive/src/to_css.rs b/derive/src/to_css.rs new file mode 100644 index 00000000..739a16d4 --- /dev/null +++ b/derive/src/to_css.rs @@ -0,0 +1,156 @@ +use convert_case::Casing; +use proc_macro::{self, TokenStream}; +use proc_macro2::{Literal, Span, TokenStream as TokenStream2}; +use quote::quote; +use syn::{parse_macro_input, Data, DataEnum, DeriveInput, Fields, Ident, Type}; + +use crate::parse::CssOptions; + +pub fn derive_to_css(input: TokenStream) -> TokenStream { + let DeriveInput { + ident, + data, + generics, + attrs, + .. + } = parse_macro_input!(input); + + let opts = CssOptions::parse_attributes(&attrs).unwrap(); + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + let imp = match &data { + Data::Enum(data) => derive_enum(&data, &opts), + _ => todo!(), + }; + + let output = quote! { + impl #impl_generics ToCss for #ident #ty_generics #where_clause { + fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> + where + W: std::fmt::Write, + { + #imp + } + } + }; + + output.into() +} + +fn derive_enum(data: &DataEnum, opts: &CssOptions) -> TokenStream2 { + let variants = data + .variants + .iter() + .map(|variant| { + let name = &variant.ident; + let fields = variant + .fields + .iter() + .enumerate() + .map(|(index, field)| { + field.ident.as_ref().map_or_else( + || Ident::new(&format!("_{}", index), Span::call_site()), + |ident| ident.clone(), + ) + }) + .collect::<_>>(); + + #[derive(PartialEq)] + enum NeedsSpace { + Yes, + No, + Maybe, + } + + let mut needs_space = NeedsSpace::No; + let mut fields_iter = variant.fields.iter().zip(fields.iter()).peekable(); + let mut writes = Vec::new(); + let mut has_needs_space = false; + while let Some((field, name)) = fields_iter.next() { + writes.push(if fields.len() > 1 { + let space = match needs_space { + NeedsSpace::Yes => quote! { dest.write_char(' ')?; }, + NeedsSpace::No => quote! {}, + NeedsSpace::Maybe => { + has_needs_space = true; + quote! { + if needs_space { + dest.write_char(' ')?; + } + } + } + }; + + if is_option(&field.ty) { + needs_space = NeedsSpace::Maybe; + let after_space = if matches!(fields_iter.peek(), Some((field, _)) if !is_option(&field.ty)) { + // If the next field is non-optional, just insert the space here. + needs_space = NeedsSpace::No; + quote! { dest.write_char(' ')?; } + } else { + quote! {} + }; + quote! { + if let Some(v) = #name { + #space + v.to_css(dest)?; + #after_space + } + } + } else { + needs_space = NeedsSpace::Yes; + quote! { + #space + #name.to_css(dest)?; + } + } + } else { + quote! { #name.to_css(dest) } + }); + } + + if writes.len() > 1 { + writes.push(quote! { Ok(()) }); + } + + if has_needs_space { + writes.insert(0, quote! { let mut needs_space = false }); + } + + match variant.fields { + Fields::Unit => { + let s = Literal::string(&variant.ident.to_string().to_case(opts.case)); + quote! { + Self::#name => dest.write_str(#s) + } + } + Fields::Named(_) => { + quote! { + Self::#name { #(#fields),* } => { + #(#writes)* + } + } + } + Fields::Unnamed(_) => { + quote! { + Self::#name(#(#fields),*) => { + #(#writes)* + } + } + } + } + }) + .collect::<_>>(); + + let output = quote! { + match self { + #(#variants),* + } + }; + + output.into() +} + +fn is_option(ty: &Type) -> bool { + matches!(&ty, Type::Path(p) if p.qself.is_none() && p.path.segments.iter().next().unwrap().ident == "Option") +} diff --git a/derive/src/visit.rs b/derive/src/visit.rs new file mode 100644 index 00000000..02093364 --- /dev/null +++ b/derive/src/visit.rs @@ -0,0 +1,292 @@ +use std::collections::HashSet; + +use proc_macro::{self, TokenStream}; +use proc_macro2::{Span, TokenStream as TokenStream2}; +use quote::quote; +use syn::{ + parse::Parse, parse_macro_input, parse_quote, Attribute, Data, DataEnum, DeriveInput, Field, Fields, + GenericParam, Generics, Ident, Member, Token, Type, Visibility, +}; + +pub fn derive_visit_children(input: TokenStream) -> TokenStream { + let DeriveInput { + ident, + data, + generics, + attrs, + .. + } = parse_macro_input!(input); + + let options: Vec = attrs + .iter() + .filter_map(|attr| { + if attr.path.is_ident("visit") { + let opts: VisitOptions = attr.parse_args().unwrap(); + Some(opts) + } else { + None + } + }) + .collect(); + + let visit_types = if let Some(attr) = attrs.iter().find(|attr| attr.path.is_ident("visit_types")) { + let types: VisitTypes = attr.parse_args().unwrap(); + let types = types.types; + Some(quote! { crate::visit_types!(#(#types)|*) }) + } else { + None + }; + + if options.is_empty() { + derive(&ident, &data, &generics, None, visit_types) + } else { + options + .into_iter() + .map(|options| derive(&ident, &data, &generics, Some(options), visit_types.clone())) + .collect() + } +} + +fn derive( + ident: &Ident, + data: &Data, + generics: &Generics, + options: Option, + visit_types: Option, +) -> TokenStream { + let mut impl_generics = generics.clone(); + let mut type_defs = quote! {}; + let generics = if let Some(VisitOptions { + generic: Some(generic), .. + }) = &options + { + let mappings = generics + .type_params() + .zip(generic.type_params()) + .map(|(a, b)| quote! { type #a = #b; }); + type_defs = quote! { #(#mappings)* }; + impl_generics.params.clear(); + generic + } else { + &generics + }; + + if impl_generics.lifetimes().next().is_none() { + impl_generics.params.insert(0, parse_quote! { 'i }) + } + + let lifetime = impl_generics.lifetimes().next().unwrap().clone(); + let t = impl_generics.type_params().find(|g| &g.ident.to_string() == "R"); + let v = quote! { __V }; + let t = if let Some(t) = t { + GenericParam::Type(t.ident.clone().into()) + } else { + let t: GenericParam = parse_quote! { __T }; + impl_generics + .params + .push(parse_quote! { #t: crate::visitor::Visit<#lifetime, __T, #v> }); + t + }; + + impl_generics + .params + .push(parse_quote! { #v: ?Sized + crate::visitor::Visitor<#lifetime, #t> }); + + for ty in generics.type_params() { + let name = &ty.ident; + impl_generics.make_where_clause().predicates.push(parse_quote! { + #name: Visit<#lifetime, #t, #v> + }) + } + + let mut seen_types = HashSet::new(); + let mut child_types = Vec::new(); + let mut visit = Vec::new(); + match data { + Data::Struct(s) => { + for ( + index, + Field { + vis, ty, ident, attrs, .. + }, + ) in s.fields.iter().enumerate() + { + if attrs.iter().any(|attr| attr.path.is_ident("skip_visit")) { + continue; + } + + if matches!(ty, Type::Reference(_)) || !matches!(vis, Visibility::Public(..)) { + continue; + } + + if visit_types.is_none() && !seen_types.contains(ty) && !skip_type(attrs) { + seen_types.insert(ty.clone()); + child_types.push(quote! { + <#ty as Visit<#lifetime, #t, #v>>::CHILD_TYPES.bits() + }); + } + + let name = ident + .as_ref() + .map_or_else(|| Member::Unnamed(index.into()), |ident| Member::Named(ident.clone())); + visit.push(quote! { self.#name.visit(visitor)?; }) + } + } + Data::Enum(DataEnum { variants, .. }) => { + let variants = variants + .iter() + .map(|variant| { + let name = &variant.ident; + let mut field_names = Vec::new(); + let mut visit_fields = Vec::new(); + for (index, Field { ty, ident, attrs, .. }) in variant.fields.iter().enumerate() { + let name = ident.as_ref().map_or_else( + || Ident::new(&format!("_{}", index), Span::call_site()), + |ident| ident.clone(), + ); + field_names.push(name.clone()); + + if matches!(ty, Type::Reference(_)) { + continue; + } + + if visit_types.is_none() && !seen_types.contains(ty) && !skip_type(attrs) && !skip_type(&variant.attrs) + { + seen_types.insert(ty.clone()); + child_types.push(quote! { + <#ty as Visit<#lifetime, #t, #v>>::CHILD_TYPES.bits() + }); + } + + visit_fields.push(quote! { #name.visit(visitor)?; }) + } + + match variant.fields { + Fields::Unnamed(_) => { + quote! { + Self::#name(#(#field_names),*) => { + #(#visit_fields)* + } + } + } + Fields::Named(_) => { + quote! { + Self::#name { #(#field_names),* } => { + #(#visit_fields)* + } + } + } + Fields::Unit => quote! {}, + } + }) + .collect::(); + + visit.push(quote! { + match self { + #variants + _ => {} + } + }) + } + _ => {} + } + + if visit_types.is_none() && child_types.is_empty() { + child_types.push(quote! { crate::visitor::VisitTypes::empty().bits() }); + } + + let (_, ty_generics, _) = generics.split_for_impl(); + let (impl_generics, _, where_clause) = impl_generics.split_for_impl(); + + let self_visit = if let Some(VisitOptions { + visit: Some(visit), + kind: Some(kind), + .. + }) = &options + { + child_types.push(quote! { crate::visitor::VisitTypes::#kind.bits() }); + + quote! { + fn visit(&mut self, visitor: &mut #v) -> Result<(), #v::Error> { + if visitor.visit_types().contains(crate::visitor::VisitTypes::#kind) { + visitor.#visit(self) + } else { + self.visit_children(visitor) + } + } + } + } else { + quote! {} + }; + + let child_types = visit_types.unwrap_or_else(|| { + quote! { + { + #type_defs + crate::visitor::VisitTypes::from_bits_retain(#(#child_types)|*) + } + } + }); + + let output = quote! { + impl #impl_generics Visit<#lifetime, #t, #v> for #ident #ty_generics #where_clause { + const CHILD_TYPES: crate::visitor::VisitTypes = #child_types; + + #self_visit + + fn visit_children(&mut self, visitor: &mut #v) -> Result<(), #v::Error> { + if !<#lifetime, #t, #v>>::CHILD_TYPES.intersects(visitor.visit_types()) { + return Ok(()) + } + + #(#visit)* + + Ok(()) + } + } + }; + + output.into() +} + +fn skip_type(attrs: &Vec) -> bool { + attrs.iter().any(|attr| attr.path.is_ident("skip_type")) +} + +struct VisitOptions { + visit: Option, + kind: Option, + generic: Option, +} + +impl Parse for VisitOptions { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let (visit, kind, comma) = if input.peek(Ident) { + let visit: Ident = input.parse()?; + let _: Token![,] = input.parse()?; + let kind: Ident = input.parse()?; + let comma: Result = input.parse(); + (Some(visit), Some(kind), comma.is_ok()) + } else { + (None, None, true) + }; + let generic: Option = if comma { Some(input.parse()?) } else { None }; + Ok(Self { visit, kind, generic }) + } +} + +struct VisitTypes { + types: Vec, +} + +impl Parse for VisitTypes { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let first: Ident = input.parse()?; + let mut types = vec![first]; + while input.parse::().is_ok() { + let id: Ident = input.parse()?; + types.push(id); + } + Ok(Self { types }) + } +} diff --git a/src/macros.rs b/src/macros.rs index e102676f..7e573816 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -8,12 +8,12 @@ macro_rules! enum_property { )+ } ) => { - $(#[$outer])* - #[derive(Debug, Clone, Copy, PartialEq)] + #[derive(Debug, Clone, Copy, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(rename_all = "lowercase"))] #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] + $(#[$outer])* $vis enum $name { $( $(#[$meta])* @@ -21,50 +21,17 @@ macro_rules! enum_property { )+ } - impl<'i> Parse<'i> for $name { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - let location = input.current_source_location(); - let ident = input.expect_ident()?; - match &ident[..] { - $( - s if s.eq_ignore_ascii_case(stringify!($x)) => Ok($name::$x), - )+ - _ => Err(location.new_unexpected_token_error( - cssparser::Token::Ident(ident.clone()) - )) - } - } - - fn parse_string(input: &'i str) -> Result<'i, ParserError<'i>>> { - match input { - $( - s if s.eq_ignore_ascii_case(stringify!($x)) => Ok($name::$x), - )+ - _ => return Err(ParseError { - kind: ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken(cssparser::Token::Ident(input.into()))), - location: cssparser::SourceLocation { line: 0, column: 1 } - }) - } - } - } - impl $name { /// Returns a string representation of the value. pub fn as_str(&self) -> &str { use $name::*; match self { $( - $x => const_str::convert_ascii_case!(lower, stringify!($x)), + $x => const_str::convert_ascii_case!(kebab, stringify!($x)), )+ } } } - - impl ToCss for $name { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> where W: std::fmt::Write { - dest.write_str(self.as_str()) - } - } }; ( $(#[$outer:meta])* @@ -92,25 +59,13 @@ macro_rules! enum_property { fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { let location = input.current_source_location(); let ident = input.expect_ident()?; - match &ident[..] { + cssparser::match_ignore_ascii_case! { &*ident, $( - s if s.eq_ignore_ascii_case($str) => Ok($name::$id), + $str => Ok($name::$id), )+ _ => Err(location.new_unexpected_token_error( cssparser::Token::Ident(ident.clone()) - )) - } - } - - fn parse_string(input: &'i str) -> Result<'i, ParserError<'i>>> { - match input { - $( - s if s.eq_ignore_ascii_case($str) => Ok($name::$id), - )+ - _ => return Err(ParseError { - kind: ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken(cssparser::Token::Ident(input.into()))), - location: cssparser::SourceLocation { line: 0, column: 1 } - }) + )), } } } diff --git a/src/properties/align.rs b/src/properties/align.rs index 12917c18..819e9ac4 100644 --- a/src/properties/align.rs +++ b/src/properties/align.rs @@ -73,13 +73,13 @@ enum_property! { /// A [``](https://www.w3.org/TR/css-align-3/#typedef-content-distribution) value. pub enum ContentDistribution { /// Items are spaced evenly, with the first and last items against the edge of the container. - "space-between": SpaceBetween, + SpaceBetween, /// Items are spaced evenly, with half-size spaces at the start and end. - "space-around": SpaceAround, + SpaceAround, /// Items are spaced evenly, with full-size spaces at the start and end. - "space-evenly": SpaceEvenly, + SpaceEvenly, /// Items are stretched evenly to fill free space. - "stretch": Stretch, + Stretch, } } @@ -99,20 +99,20 @@ enum_property! { /// A [``](https://www.w3.org/TR/css-align-3/#typedef-content-position) value. pub enum ContentPosition { /// Content is centered within the container. - "center": Center, + Center, /// Content is aligned to the start of the container. - "start": Start, + Start, /// Content is aligned to the end of the container. - "end": End, + End, /// Same as `start` when within a flexbox container. - "flex-start": FlexStart, + FlexStart, /// Same as `end` when within a flexbox container. - "flex-end": FlexEnd, + FlexEnd, } } /// A value for the [align-content](https://www.w3.org/TR/css-align-3/#propdef-align-content) property. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -132,54 +132,13 @@ pub enum AlignContent { ContentDistribution(ContentDistribution), /// A content position keyword. ContentPosition { - /// A content position keyword. - value: ContentPosition, /// An overflow alignment mode. overflow: Option, + /// A content position keyword. + value: ContentPosition, }, } -impl<'i> Parse<'i> for AlignContent { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if input.try_parse(|input| input.expect_ident_matching("normal")).is_ok() { - return Ok(AlignContent::Normal); - } - - if let Ok(val) = input.try_parse(BaselinePosition::parse) { - return Ok(AlignContent::BaselinePosition(val)); - } - - if let Ok(val) = input.try_parse(ContentDistribution::parse) { - return Ok(AlignContent::ContentDistribution(val)); - } - - let overflow = input.try_parse(OverflowPosition::parse).ok(); - let value = ContentPosition::parse(input)?; - Ok(AlignContent::ContentPosition { overflow, value }) - } -} - -impl ToCss for AlignContent { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - AlignContent::Normal => dest.write_str("normal"), - AlignContent::BaselinePosition(val) => val.to_css(dest), - AlignContent::ContentDistribution(val) => val.to_css(dest), - AlignContent::ContentPosition { overflow, value } => { - if let Some(overflow) = overflow { - overflow.to_css(dest)?; - dest.write_str(" ")?; - } - - value.to_css(dest) - } - } - } -} - /// A value for the [justify-content](https://www.w3.org/TR/css-align-3/#propdef-justify-content) property. #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "visitor", derive(Visit))] @@ -349,24 +308,24 @@ enum_property! { /// A [``](https://www.w3.org/TR/css-align-3/#typedef-self-position) value. pub enum SelfPosition { /// Item is centered within the container. - "center": Center, + Center, /// Item is aligned to the start of the container. - "start": Start, + Start, /// Item is aligned to the end of the container. - "end": End, + End, /// Item is aligned to the edge of the container corresponding to the start side of the item. - "self-start": SelfStart, + SelfStart, /// Item is aligned to the edge of the container corresponding to the end side of the item. - "self-end": SelfEnd, + SelfEnd, /// Item is aligned to the start of the container, within flexbox layouts. - "flex-start": FlexStart, + FlexStart, /// Item is aligned to the end of the container, within flexbox layouts. - "flex-end": FlexEnd, + FlexEnd, } } /// A value for the [align-self](https://www.w3.org/TR/css-align-3/#align-self-property) property. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -387,59 +346,13 @@ pub enum AlignSelf { BaselinePosition(BaselinePosition), /// A self position keyword. SelfPosition { - /// A self position keyword. - value: SelfPosition, /// An overflow alignment mode. overflow: Option, + /// A self position keyword. + value: SelfPosition, }, } -impl<'i> Parse<'i> for AlignSelf { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if input.try_parse(|input| input.expect_ident_matching("auto")).is_ok() { - return Ok(AlignSelf::Auto); - } - - if input.try_parse(|input| input.expect_ident_matching("normal")).is_ok() { - return Ok(AlignSelf::Normal); - } - - if input.try_parse(|input| input.expect_ident_matching("stretch")).is_ok() { - return Ok(AlignSelf::Stretch); - } - - if let Ok(val) = input.try_parse(BaselinePosition::parse) { - return Ok(AlignSelf::BaselinePosition(val)); - } - - let overflow = input.try_parse(OverflowPosition::parse).ok(); - let value = SelfPosition::parse(input)?; - Ok(AlignSelf::SelfPosition { overflow, value }) - } -} - -impl ToCss for AlignSelf { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - AlignSelf::Auto => dest.write_str("auto"), - AlignSelf::Normal => dest.write_str("normal"), - AlignSelf::Stretch => dest.write_str("stretch"), - AlignSelf::BaselinePosition(val) => val.to_css(dest), - AlignSelf::SelfPosition { overflow, value } => { - if let Some(overflow) = overflow { - overflow.to_css(dest)?; - dest.write_str(" ")?; - } - - value.to_css(dest) - } - } - } -} - /// A value for the [justify-self](https://www.w3.org/TR/css-align-3/#justify-self-property) property. #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "visitor", derive(Visit))] @@ -615,7 +528,7 @@ impl ToCss for PlaceSelf { } /// A value for the [align-items](https://www.w3.org/TR/css-align-3/#align-items-property) property. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -634,54 +547,13 @@ pub enum AlignItems { BaselinePosition(BaselinePosition), /// A self position keyword. SelfPosition { - /// A self position keyword. - value: SelfPosition, /// An overflow alignment mode. overflow: Option, + /// A self position keyword. + value: SelfPosition, }, } -impl<'i> Parse<'i> for AlignItems { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if input.try_parse(|input| input.expect_ident_matching("normal")).is_ok() { - return Ok(AlignItems::Normal); - } - - if input.try_parse(|input| input.expect_ident_matching("stretch")).is_ok() { - return Ok(AlignItems::Stretch); - } - - if let Ok(val) = input.try_parse(BaselinePosition::parse) { - return Ok(AlignItems::BaselinePosition(val)); - } - - let overflow = input.try_parse(OverflowPosition::parse).ok(); - let value = SelfPosition::parse(input)?; - Ok(AlignItems::SelfPosition { overflow, value }) - } -} - -impl ToCss for AlignItems { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - AlignItems::Normal => dest.write_str("normal"), - AlignItems::Stretch => dest.write_str("stretch"), - AlignItems::BaselinePosition(val) => val.to_css(dest), - AlignItems::SelfPosition { overflow, value } => { - if let Some(overflow) = overflow { - overflow.to_css(dest)?; - dest.write_str(" ")?; - } - - value.to_css(dest) - } - } - } -} - /// A legacy justification keyword, as used in the `justify-items` property. #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "visitor", derive(Visit))] @@ -925,7 +797,7 @@ impl ToCss for PlaceItems { /// A [gap](https://www.w3.org/TR/css-align-3/#column-row-gap) value, as used in the /// `column-gap` and `row-gap` properties. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -941,29 +813,6 @@ pub enum GapValue { LengthPercentage(LengthPercentage), } -impl<'i> Parse<'i> for GapValue { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if input.try_parse(|input| input.expect_ident_matching("normal")).is_ok() { - return Ok(GapValue::Normal); - } - - let val = LengthPercentage::parse(input)?; - Ok(GapValue::LengthPercentage(val)) - } -} - -impl ToCss for GapValue { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - GapValue::Normal => dest.write_str("normal"), - GapValue::LengthPercentage(lp) => lp.to_css(dest), - } - } -} - define_shorthand! { /// A value for the [gap](https://www.w3.org/TR/css-align-3/#gap-shorthand) shorthand property. pub struct Gap { diff --git a/src/properties/animation.rs b/src/properties/animation.rs index 03b76166..de83b9fa 100644 --- a/src/properties/animation.rs +++ b/src/properties/animation.rs @@ -13,7 +13,7 @@ use crate::traits::{Parse, PropertyHandler, Shorthand, ToCss, Zero}; use crate::values::ident::DashedIdent; use crate::values::number::CSSNumber; use crate::values::size::Size2D; -use crate::values::string::CowArcStr; +use crate::values::string::CSSString; use crate::values::{easing::EasingFunction, ident::CustomIdent, time::Time}; #[cfg(feature = "visitor")] use crate::visitor::Visit; @@ -24,7 +24,7 @@ use smallvec::SmallVec; use super::LengthPercentageOrAuto; /// A value for the [animation-name](https://drafts.csswg.org/css-animations/#animation-name) property. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] #[cfg_attr( @@ -41,22 +41,7 @@ pub enum AnimationName<'i> { Ident(CustomIdent<'i>), /// A `` name of a `@keyframes` rule. #[cfg_attr(feature = "serde", serde(borrow))] - String(CowArcStr<'i>), -} - -impl<'i> Parse<'i> for AnimationName<'i> { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if input.try_parse(|input| input.expect_ident_matching("none")).is_ok() { - return Ok(AnimationName::None); - } - - if let Ok(s) = input.try_parse(|input| input.expect_string_cloned()) { - return Ok(AnimationName::String(s.into())); - } - - let ident = CustomIdent::parse(input)?; - Ok(AnimationName::Ident(ident)) - } + String(CSSString<'i>), } impl<'i> ToCss for AnimationName<'i> { @@ -103,7 +88,7 @@ impl<'i> ToCss for AnimationName<'i> { pub type AnimationNameList<'i> = SmallVec<[AnimationName<'i>; 1]>; /// A value for the [animation-iteration-count](https://drafts.csswg.org/css-animations/#animation-iteration-count) property. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -125,40 +110,17 @@ impl Default for AnimationIterationCount { } } -impl<'i> Parse<'i> for AnimationIterationCount { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if input.try_parse(|input| input.expect_ident_matching("infinite")).is_ok() { - return Ok(AnimationIterationCount::Infinite); - } - - let number = CSSNumber::parse(input)?; - return Ok(AnimationIterationCount::Number(number)); - } -} - -impl ToCss for AnimationIterationCount { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - AnimationIterationCount::Number(val) => val.to_css(dest), - AnimationIterationCount::Infinite => dest.write_str("infinite"), - } - } -} - enum_property! { /// A value for the [animation-direction](https://drafts.csswg.org/css-animations/#animation-direction) property. pub enum AnimationDirection { /// The animation is played as specified - "normal": Normal, + Normal, /// The animation is played in reverse. - "reverse": Reverse, + Reverse, /// The animation iterations alternate between forward and reverse. - "alternate": Alternate, + Alternate, /// The animation iterations alternate between forward and reverse, with reverse occurring first. - "alternate-reverse": AlternateReverse, + AlternateReverse, } } @@ -217,7 +179,7 @@ enum_property! { } /// A value for the [animation-timeline](https://drafts.csswg.org/css-animations-2/#animation-timeline) property. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -261,25 +223,28 @@ pub struct ScrollTimeline { impl<'i> Parse<'i> for ScrollTimeline { fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - let mut scroller = None; - let mut axis = None; - loop { - if scroller.is_none() { - scroller = input.try_parse(Scroller::parse).ok(); - } + input.expect_function_matching("scroll")?; + input.parse_nested_block(|input| { + let mut scroller = None; + let mut axis = None; + loop { + if scroller.is_none() { + scroller = input.try_parse(Scroller::parse).ok(); + } - if axis.is_none() { - axis = input.try_parse(ScrollAxis::parse).ok(); - if axis.is_some() { - continue; + if axis.is_none() { + axis = input.try_parse(ScrollAxis::parse).ok(); + if axis.is_some() { + continue; + } } + break; } - break; - } - Ok(ScrollTimeline { - scroller: scroller.unwrap_or_default(), - axis: axis.unwrap_or_default(), + Ok(ScrollTimeline { + scroller: scroller.unwrap_or_default(), + axis: axis.unwrap_or_default(), + }) }) } } @@ -289,6 +254,8 @@ impl ToCss for ScrollTimeline { where W: std::fmt::Write, { + dest.write_str("scroll(")?; + let mut needs_space = false; if self.scroller != Scroller::default() { self.scroller.to_css(dest)?; @@ -302,7 +269,7 @@ impl ToCss for ScrollTimeline { self.axis.to_css(dest)?; } - Ok(()) + dest.write_char(')') } } @@ -359,25 +326,28 @@ pub struct ViewTimeline { impl<'i> Parse<'i> for ViewTimeline { fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - let mut axis = None; - let mut inset = None; - loop { - if axis.is_none() { - axis = input.try_parse(ScrollAxis::parse).ok(); - } + input.expect_function_matching("view")?; + input.parse_nested_block(|input| { + let mut axis = None; + let mut inset = None; + loop { + if axis.is_none() { + axis = input.try_parse(ScrollAxis::parse).ok(); + } - if inset.is_none() { - inset = input.try_parse(Size2D::parse).ok(); - if inset.is_some() { - continue; + if inset.is_none() { + inset = input.try_parse(Size2D::parse).ok(); + if inset.is_some() { + continue; + } } + break; } - break; - } - Ok(ViewTimeline { - axis: axis.unwrap_or_default(), - inset: inset.unwrap_or(Size2D(LengthPercentageOrAuto::Auto, LengthPercentageOrAuto::Auto)), + Ok(ViewTimeline { + axis: axis.unwrap_or_default(), + inset: inset.unwrap_or(Size2D(LengthPercentageOrAuto::Auto, LengthPercentageOrAuto::Auto)), + }) }) } } @@ -387,6 +357,7 @@ impl ToCss for ViewTimeline { where W: std::fmt::Write, { + dest.write_str("view(")?; let mut needs_space = false; if self.axis != ScrollAxis::default() { self.axis.to_css(dest)?; @@ -400,56 +371,7 @@ impl ToCss for ViewTimeline { self.inset.to_css(dest)?; } - Ok(()) - } -} - -impl<'i> Parse<'i> for AnimationTimeline<'i> { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() { - return Ok(AnimationTimeline::Auto); - } - - if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() { - return Ok(AnimationTimeline::None); - } - - if let Ok(name) = input.try_parse(DashedIdent::parse) { - return Ok(AnimationTimeline::DashedIdent(name)); - } - - let location = input.current_source_location(); - let f = input.expect_function()?.clone(); - input.parse_nested_block(move |input| { - match_ignore_ascii_case! { &f, - "scroll" => ScrollTimeline::parse(input).map(AnimationTimeline::Scroll), - "view" => ViewTimeline::parse(input).map(AnimationTimeline::View), - _ => Err(location.new_custom_error(ParserError::UnexpectedToken(crate::properties::custom::Token::Function(f.into())))) - } - }) - } -} - -impl<'i> ToCss for AnimationTimeline<'i> { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - AnimationTimeline::Auto => dest.write_str("auto"), - AnimationTimeline::None => dest.write_str("none"), - AnimationTimeline::DashedIdent(name) => name.to_css(dest), - AnimationTimeline::Scroll(scroll) => { - dest.write_str("scroll(")?; - scroll.to_css(dest)?; - dest.write_char(')') - } - AnimationTimeline::View(view) => { - dest.write_str("view(")?; - view.to_css(dest)?; - dest.write_char(')') - } - } + dest.write_char(')') } } @@ -535,7 +457,7 @@ impl<'i> ToCss for Animation<'i> { { match &self.name { AnimationName::None => {} - AnimationName::Ident(CustomIdent(name)) | AnimationName::String(name) => { + AnimationName::Ident(CustomIdent(name)) | AnimationName::String(CSSString(name)) => { if !self.duration.is_zero() || !self.delay.is_zero() { self.duration.to_css(dest)?; dest.write_char(' ')?; @@ -709,7 +631,7 @@ impl<'i> PropertyHandler<'i> for AnimationHandler<'i> { } } TokenOrValue::Token(Token::String(s)) => { - *token = TokenOrValue::AnimationName(AnimationName::String(s.clone())); + *token = TokenOrValue::AnimationName(AnimationName::String(CSSString(s.clone()))); } _ => {} } diff --git a/src/properties/background.rs b/src/properties/background.rs index 1dc1aeb3..f47821db 100644 --- a/src/properties/background.rs +++ b/src/properties/background.rs @@ -113,13 +113,13 @@ enum_property! { /// See [BackgroundRepeat](BackgroundRepeat). pub enum BackgroundRepeatKeyword { /// The image is repeated in this direction. - "repeat": Repeat, + Repeat, /// The image is repeated so that it fits, and then spaced apart evenly. - "space": Space, + Space, /// The image is scaled so that it repeats an even number of times. - "round": Round, + Round, /// The image is placed once and not repeated in this direction. - "no-repeat": NoRepeat, + NoRepeat, } } @@ -214,11 +214,11 @@ enum_property! { /// A value for the [background-origin](https://www.w3.org/TR/css-backgrounds-3/#background-origin) property. pub enum BackgroundOrigin { /// The position is relative to the border box. - "border-box": BorderBox, + BorderBox, /// The position is relative to the padding box. - "padding-box": PaddingBox, + PaddingBox, /// The position is relative to the content box. - "content-box": ContentBox, + ContentBox, } } @@ -226,15 +226,15 @@ enum_property! { /// A value for the [background-clip](https://drafts.csswg.org/css-backgrounds-4/#background-clip) property. pub enum BackgroundClip { /// The background is clipped to the border box. - "border-box": BorderBox, + BorderBox, /// The background is clipped to the padding box. - "padding-box": PaddingBox, + PaddingBox, /// The background is clipped to the content box. - "content-box": ContentBox, + ContentBox, /// The background is clipped to the area painted by the border. - "border": Border, + Border, /// The background is clipped to the text content of the element. - "text": Text, + Text, } } diff --git a/src/properties/border.rs b/src/properties/border.rs index ee2c4102..9308eebb 100644 --- a/src/properties/border.rs +++ b/src/properties/border.rs @@ -23,7 +23,7 @@ use crate::visitor::Visit; use cssparser::*; /// A value for the [border-width](https://www.w3.org/TR/css-backgrounds-3/#border-width) property. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -58,37 +58,6 @@ impl IsCompatible for BorderSideWidth { } } -impl<'i> Parse<'i> for BorderSideWidth { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if let Ok(length) = input.try_parse(|i| Length::parse(i)) { - return Ok(BorderSideWidth::Length(length)); - } - let location = input.current_source_location(); - let ident = input.expect_ident()?; - match_ignore_ascii_case! { &ident, - "thin" => Ok(BorderSideWidth::Thin), - "medium" => Ok(BorderSideWidth::Medium), - "thick" => Ok(BorderSideWidth::Thick), - _ => return Err(location.new_unexpected_token_error(Token::Ident(ident.clone()))) - } - } -} - -impl ToCss for BorderSideWidth { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - use BorderSideWidth::*; - match self { - Thin => dest.write_str("thin"), - Medium => dest.write_str("medium"), - Thick => dest.write_str("thick"), - Length(length) => length.to_css(dest), - } - } -} - enum_property! { /// A [``](https://drafts.csswg.org/css-backgrounds/#typedef-line-style) value, used in the `border-style` property. pub enum LineStyle { diff --git a/src/properties/border_image.rs b/src/properties/border_image.rs index 8f444efe..300d458e 100644 --- a/src/properties/border_image.rs +++ b/src/properties/border_image.rs @@ -102,7 +102,7 @@ impl IsCompatible for BorderImageRepeat { } /// A value for the [border-image-width](https://www.w3.org/TR/css-backgrounds-3/#border-image-width) property. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -126,38 +126,6 @@ impl Default for BorderImageSideWidth { } } -impl<'i> Parse<'i> for BorderImageSideWidth { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() { - return Ok(BorderImageSideWidth::Auto); - } - - if let Ok(number) = input.try_parse(CSSNumber::parse) { - return Ok(BorderImageSideWidth::Number(number)); - } - - if let Ok(percent) = input.try_parse(|input| LengthPercentage::parse(input)) { - return Ok(BorderImageSideWidth::LengthPercentage(percent)); - } - - Err(input.new_error_for_next_token()) - } -} - -impl ToCss for BorderImageSideWidth { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - use BorderImageSideWidth::*; - match self { - Auto => dest.write_str("auto"), - LengthPercentage(l) => l.to_css(dest), - Number(n) => n.to_css(dest), - } - } -} - impl IsCompatible for BorderImageSideWidth { fn is_compatible(&self, browsers: Browsers) -> bool { match self { diff --git a/src/properties/contain.rs b/src/properties/contain.rs index 761e2d92..1c57bab4 100644 --- a/src/properties/contain.rs +++ b/src/properties/contain.rs @@ -25,11 +25,11 @@ enum_property! { pub enum ContainerType { /// The element is not a query container for any container size queries, /// but remains a query container for container style queries. - "normal": Normal, + Normal, /// Establishes a query container for container size queries on the container’s own inline axis. - "inline-size": InlineSize, + InlineSize, /// Establishes a query container for container size queries on both the inline and block axis. - "size": Size, + Size, } } diff --git a/src/properties/custom.rs b/src/properties/custom.rs index 3198422b..40b522e8 100644 --- a/src/properties/custom.rs +++ b/src/properties/custom.rs @@ -1329,25 +1329,25 @@ enum_property! { /// A UA-defined environment variable name. pub enum UAEnvironmentVariable { /// The safe area inset from the top of the viewport. - "safe-area-inset-top": SafeAreaInsetTop, + SafeAreaInsetTop, /// The safe area inset from the right of the viewport. - "safe-area-inset-right": SafeAreaInsetRight, + SafeAreaInsetRight, /// The safe area inset from the bottom of the viewport. - "safe-area-inset-bottom": SafeAreaInsetBottom, + SafeAreaInsetBottom, /// The safe area inset from the left of the viewport. - "safe-area-inset-left": SafeAreaInsetLeft, + SafeAreaInsetLeft, /// The viewport segment width. - "viewport-segment-width": ViewportSegmentWidth, + ViewportSegmentWidth, /// The viewport segment height. - "viewport-segment-height": ViewportSegmentHeight, + ViewportSegmentHeight, /// The viewport segment top position. - "viewport-segment-top": ViewportSegmentTop, + ViewportSegmentTop, /// The viewport segment left position. - "viewport-segment-left": ViewportSegmentLeft, + ViewportSegmentLeft, /// The viewport segment bottom position. - "viewport-segment-bottom": ViewportSegmentBottom, + ViewportSegmentBottom, /// The viewport segment right position. - "viewport-segment-right": ViewportSegmentRight, + ViewportSegmentRight, } } diff --git a/src/properties/display.rs b/src/properties/display.rs index d5a53d13..3df7c819 100644 --- a/src/properties/display.rs +++ b/src/properties/display.rs @@ -18,9 +18,9 @@ enum_property! { /// A [``](https://drafts.csswg.org/css-display-3/#typedef-display-outside) value. #[allow(missing_docs)] pub enum DisplayOutside { - "block": Block, - "inline": Inline, - "run-in": RunIn, + Block, + Inline, + RunIn, } } @@ -309,25 +309,25 @@ enum_property! { /// See [Display](Display). #[allow(missing_docs)] pub enum DisplayKeyword { - "none": None, - "contents": Contents, - "table-row-group": TableRowGroup, - "table-header-group": TableHeaderGroup, - "table-footer-group": TableFooterGroup, - "table-row": TableRow, - "table-cell": TableCell, - "table-column-group": TableColumnGroup, - "table-column": TableColumn, - "table-caption": TableCaption, - "ruby-base": RubyBase, - "ruby-text": RubyText, - "ruby-base-container": RubyBaseContainer, - "ruby-text-container": RubyTextContainer, + None, + Contents, + TableRowGroup, + TableHeaderGroup, + TableFooterGroup, + TableRow, + TableCell, + TableColumnGroup, + TableColumn, + TableCaption, + RubyBase, + RubyText, + RubyBaseContainer, + RubyTextContainer, } } /// A value for the [display](https://drafts.csswg.org/css-display-3/#the-display-properties) property. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -347,29 +347,6 @@ pub enum Display { Pair(DisplayPair), } -impl<'i> Parse<'i> for Display { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if let Ok(pair) = input.try_parse(DisplayPair::parse) { - return Ok(Display::Pair(pair)); - } - - let keyword = DisplayKeyword::parse(input)?; - Ok(Display::Keyword(keyword)) - } -} - -impl ToCss for Display { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - Display::Keyword(keyword) => keyword.to_css(dest), - Display::Pair(pair) => pair.to_css(dest), - } - } -} - enum_property! { /// A value for the [visibility](https://drafts.csswg.org/css-display-3/#visibility) property. pub enum Visibility { diff --git a/src/properties/flex.rs b/src/properties/flex.rs index cf631993..e885c597 100644 --- a/src/properties/flex.rs +++ b/src/properties/flex.rs @@ -25,13 +25,13 @@ enum_property! { /// A value for the [flex-direction](https://www.w3.org/TR/2018/CR-css-flexbox-1-20181119/#propdef-flex-direction) property. pub enum FlexDirection { /// Flex items are laid out in a row. - "row": Row, + Row, /// Flex items are laid out in a row, and reversed. - "row-reverse": RowReverse, + RowReverse, /// Flex items are laid out in a column. - "column": Column, + Column, /// Flex items are laid out in a column, and reversed. - "column-reverse": ColumnReverse, + ColumnReverse, } } @@ -233,13 +233,13 @@ enum_property! { /// Partially equivalent to `flex-direction` in the standard syntax. pub enum BoxOrient { /// Items are laid out horizontally. - "horizontal": Horizontal, + Horizontal, /// Items are laid out vertically. - "vertical": Vertical, + Vertical, /// Items are laid out along the inline axis, according to the writing direction. - "inline-axis": InlineAxis, + InlineAxis, /// Items are laid out along the block axis, according to the writing direction. - "block-axis": BlockAxis, + BlockAxis, } } diff --git a/src/properties/font.rs b/src/properties/font.rs index 84396cdb..377ea77c 100644 --- a/src/properties/font.rs +++ b/src/properties/font.rs @@ -20,7 +20,7 @@ use crate::visitor::Visit; use cssparser::*; /// A value for the [font-weight](https://www.w3.org/TR/css-fonts-4/#font-weight-prop) property. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -44,38 +44,6 @@ impl Default for FontWeight { } } -impl<'i> Parse<'i> for FontWeight { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if let Ok(val) = input.try_parse(AbsoluteFontWeight::parse) { - return Ok(FontWeight::Absolute(val)); - } - - let location = input.current_source_location(); - let ident = input.expect_ident()?; - match_ignore_ascii_case! { &*ident, - "bolder" => Ok(FontWeight::Bolder), - "lighter" => Ok(FontWeight::Lighter), - _ => Err(location.new_unexpected_token_error( - cssparser::Token::Ident(ident.clone()) - )) - } - } -} - -impl ToCss for FontWeight { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - use FontWeight::*; - match self { - Absolute(val) => val.to_css(dest), - Bolder => dest.write_str("bolder"), - Lighter => dest.write_str("lighter"), - } - } -} - impl IsCompatible for FontWeight { fn is_compatible(&self, browsers: crate::targets::Browsers) -> bool { match self { @@ -89,7 +57,7 @@ impl IsCompatible for FontWeight { /// as used in the `font-weight` property. /// /// See [FontWeight](FontWeight). -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -112,24 +80,6 @@ impl Default for AbsoluteFontWeight { } } -impl<'i> Parse<'i> for AbsoluteFontWeight { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if let Ok(val) = input.try_parse(CSSNumber::parse) { - return Ok(AbsoluteFontWeight::Weight(val)); - } - - let location = input.current_source_location(); - let ident = input.expect_ident()?; - match_ignore_ascii_case! { &*ident, - "normal" => Ok(AbsoluteFontWeight::Normal), - "bold" => Ok(AbsoluteFontWeight::Bold), - _ => Err(location.new_unexpected_token_error( - cssparser::Token::Ident(ident.clone()) - )) - } - } -} - impl ToCss for AbsoluteFontWeight { fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> where @@ -197,7 +147,7 @@ enum_property! { } /// A value for the [font-size](https://www.w3.org/TR/css-fonts-4/#font-size-prop) property. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -215,35 +165,6 @@ pub enum FontSize { Relative(RelativeFontSize), } -impl<'i> Parse<'i> for FontSize { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if let Ok(val) = input.try_parse(LengthPercentage::parse) { - return Ok(FontSize::Length(val)); - } - - if let Ok(val) = input.try_parse(AbsoluteFontSize::parse) { - return Ok(FontSize::Absolute(val)); - } - - let val = RelativeFontSize::parse(input)?; - Ok(FontSize::Relative(val)) - } -} - -impl ToCss for FontSize { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - use FontSize::*; - match self { - Absolute(val) => val.to_css(dest), - Length(val) => val.to_css(dest), - Relative(val) => val.to_css(dest), - } - } -} - impl IsCompatible for FontSize { fn is_compatible(&self, browsers: crate::targets::Browsers) -> bool { match self { @@ -309,7 +230,7 @@ impl Into for &FontStretchKeyword { } /// A value for the [font-stretch](https://www.w3.org/TR/css-fonts-4/#font-stretch-prop) property. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -331,17 +252,6 @@ impl Default for FontStretch { } } -impl<'i> Parse<'i> for FontStretch { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if let Ok(val) = input.try_parse(Percentage::parse) { - return Ok(FontStretch::Percentage(val)); - } - - let keyword = FontStretchKeyword::parse(input)?; - Ok(FontStretch::Keyword(keyword)) - } -} - impl Into for &FontStretch { fn into(self) -> Percentage { match self { @@ -601,19 +511,19 @@ enum_property! { /// A value for the [font-variant-caps](https://www.w3.org/TR/css-fonts-4/#font-variant-caps-prop) property. pub enum FontVariantCaps { /// No special capitalization features are applied. - "normal": Normal, + Normal, /// The small capitals feature is used for lower case letters. - "small-caps": SmallCaps, + SmallCaps, /// Small capitals are used for both upper and lower case letters. - "all-small-caps": AllSmallCaps, + AllSmallCaps, /// Petite capitals are used. - "petite-caps": PetiteCaps, + PetiteCaps, /// Petite capitals are used for both upper and lower case letters. - "all-petite-caps": AllPetiteCaps, + AllPetiteCaps, /// Enables display of mixture of small capitals for uppercase letters with normal lowercase letters. - "unicase": Unicase, + Unicase, /// Uses titling capitals. - "titling-caps": TitlingCaps, + TitlingCaps, } } @@ -644,7 +554,7 @@ impl IsCompatible for FontVariantCaps { } /// A value for the [line-height](https://www.w3.org/TR/2020/WD-css-inline-3-20200827/#propdef-line-height) property. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -668,33 +578,6 @@ impl Default for LineHeight { } } -impl<'i> Parse<'i> for LineHeight { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if input.try_parse(|input| input.expect_ident_matching("normal")).is_ok() { - return Ok(LineHeight::Normal); - } - - if let Ok(val) = input.try_parse(CSSNumber::parse) { - return Ok(LineHeight::Number(val)); - } - - Ok(LineHeight::Length(LengthPercentage::parse(input)?)) - } -} - -impl ToCss for LineHeight { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - LineHeight::Normal => dest.write_str("normal"), - LineHeight::Number(val) => val.to_css(dest), - LineHeight::Length(val) => val.to_css(dest), - } - } -} - impl IsCompatible for LineHeight { fn is_compatible(&self, browsers: crate::targets::Browsers) -> bool { match self { @@ -708,27 +591,27 @@ enum_property! { /// A keyword for the [vertical align](https://drafts.csswg.org/css2/#propdef-vertical-align) property. pub enum VerticalAlignKeyword { /// Align the baseline of the box with the baseline of the parent box. - "baseline": Baseline, + Baseline, /// Lower the baseline of the box to the proper position for subscripts of the parent’s box. - "sub": Sub, + Sub, /// Raise the baseline of the box to the proper position for superscripts of the parent’s box. - "super": Super, + Super, /// Align the top of the aligned subtree with the top of the line box. - "top": Top, + Top, /// Align the top of the box with the top of the parent’s content area. - "text-top": TextTop, + TextTop, /// Align the vertical midpoint of the box with the baseline of the parent box plus half the x-height of the parent. - "middle": Middle, + Middle, /// Align the bottom of the aligned subtree with the bottom of the line box. - "bottom": Bottom, + Bottom, /// Align the bottom of the box with the bottom of the parent’s content area. - "text-bottom": TextBottom, + TextBottom, } } /// A value for the [vertical align](https://drafts.csswg.org/css2/#propdef-vertical-align) property. // TODO: there is a more extensive spec in CSS3 but it doesn't seem any browser implements it? https://www.w3.org/TR/css-inline-3/#transverse-alignment -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -744,29 +627,6 @@ pub enum VerticalAlign { Length(LengthPercentage), } -impl<'i> Parse<'i> for VerticalAlign { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if let Ok(len) = input.try_parse(LengthPercentage::parse) { - return Ok(VerticalAlign::Length(len)); - } - - let kw = VerticalAlignKeyword::parse(input)?; - Ok(VerticalAlign::Keyword(kw)) - } -} - -impl ToCss for VerticalAlign { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - VerticalAlign::Keyword(kw) => kw.to_css(dest), - VerticalAlign::Length(len) => len.to_css(dest), - } - } -} - define_shorthand! { /// A value for the [font](https://www.w3.org/TR/css-fonts-4/#font-prop) shorthand property. pub struct Font<'i> { diff --git a/src/properties/grid.rs b/src/properties/grid.rs index 912f563d..6e706319 100644 --- a/src/properties/grid.rs +++ b/src/properties/grid.rs @@ -24,7 +24,7 @@ use crate::serialization::ValueWrapper; /// A [track sizing](https://drafts.csswg.org/css-grid-2/#track-sizing) value /// for the `grid-template-rows` and `grid-template-columns` properties. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] #[cfg_attr( @@ -178,7 +178,7 @@ pub struct TrackRepeat<'i> { /// used in the `repeat()` function. /// /// See [TrackRepeat](TrackRepeat). -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -365,37 +365,6 @@ impl<'i> ToCss for TrackRepeat<'i> { } } -impl<'i> Parse<'i> for RepeatCount { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if let Ok(num) = input.try_parse(CSSInteger::parse) { - return Ok(RepeatCount::Number(num)); - } - - let location = input.current_source_location(); - let ident = input.expect_ident()?; - match_ignore_ascii_case! { &*ident, - "auto-fill" => Ok(RepeatCount::AutoFill), - "auto-fit" => Ok(RepeatCount::AutoFit), - _ => Err(location.new_unexpected_token_error( - cssparser::Token::Ident(ident.clone()) - )) - } - } -} - -impl ToCss for RepeatCount { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - RepeatCount::AutoFill => dest.write_str("auto-fill"), - RepeatCount::AutoFit => dest.write_str("auto-fit"), - RepeatCount::Number(num) => num.to_css(dest), - } - } -} - fn parse_line_names<'i, 't>( input: &mut Parser<'i, 't>, ) -> Result<'i>, ParseError<'i, ParserError<'i>>> { @@ -519,29 +488,6 @@ impl<'i> TrackList<'i> { } } -impl<'i> Parse<'i> for TrackSizing<'i> { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if input.try_parse(|input| input.expect_ident_matching("none")).is_ok() { - return Ok(TrackSizing::None); - } - - let track_list = TrackList::parse(input)?; - Ok(TrackSizing::TrackList(track_list)) - } -} - -impl<'i> ToCss for TrackSizing<'i> { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - TrackSizing::None => dest.write_str("none"), - TrackSizing::TrackList(list) => list.to_css(dest), - } - } -} - impl<'i> TrackSizing<'i> { fn is_explicit(&self) -> bool { match self { diff --git a/src/properties/list.rs b/src/properties/list.rs index 4112ddfe..54a0c052 100644 --- a/src/properties/list.rs +++ b/src/properties/list.rs @@ -15,7 +15,7 @@ use crate::visitor::Visit; use cssparser::*; /// A value for the [list-style-type](https://www.w3.org/TR/2020/WD-css-lists-3-20201117/#text-markers) property. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] #[cfg_attr( @@ -40,34 +40,6 @@ impl Default for ListStyleType<'_> { } } -impl<'i> Parse<'i> for ListStyleType<'i> { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if input.try_parse(|input| input.expect_ident_matching("none")).is_ok() { - return Ok(ListStyleType::None); - } - - if let Ok(val) = input.try_parse(CounterStyle::parse) { - return Ok(ListStyleType::CounterStyle(val)); - } - - let s = CSSString::parse(input)?; - Ok(ListStyleType::String(s)) - } -} - -impl ToCss for ListStyleType<'_> { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - ListStyleType::None => dest.write_str("none"), - ListStyleType::CounterStyle(style) => style.to_css(dest), - ListStyleType::String(s) => s.to_css(dest), - } - } -} - impl IsCompatible for ListStyleType<'_> { fn is_compatible(&self, browsers: Browsers) -> bool { match self { @@ -117,7 +89,7 @@ macro_rules! counter_styles { $vis:vis enum $name:ident { $( $(#[$meta: meta])* - $str: literal: $id: ident, + $id: ident, )+ } ) => { @@ -127,7 +99,7 @@ macro_rules! counter_styles { pub enum PredefinedCounterStyle { $( $(#[$meta])* - $str: $id, + $id, )+ } } @@ -151,68 +123,68 @@ counter_styles! { #[allow(missing_docs)] pub enum PredefinedCounterStyle { // https://www.w3.org/TR/css-counter-styles-3/#simple-numeric - "decimal": Decimal, - "decimal-leading-zero": DecimalLeadingZero, - "arabic-indic": ArabicIndic, - "armenian": Armenian, - "upper-armenian": UpperArmenian, - "lower-armenian": LowerArmenian, - "bengali": Bengali, - "cambodian": Cambodian, - "khmer": Khmer, - "cjk-decimal": CjkDecimal, - "devanagari": Devanagari, - "georgian": Georgian, - "gujarati": Gujarati, - "gurmukhi": Gurmukhi, - "hebrew": Hebrew, - "kannada": Kannada, - "lao": Lao, - "malayalam": Malayalam, - "mongolian": Mongolian, - "myanmar": Myanmar, - "oriya": Oriya, - "persian": Persian, - "lower-roman": LowerRoman, - "upper-roman": UpperRoman, - "tamil": Tamil, - "telugu": Telugu, - "thai": Thai, - "tibetan": Tibetan, + Decimal, + DecimalLeadingZero, + ArabicIndic, + Armenian, + UpperArmenian, + LowerArmenian, + Bengali, + Cambodian, + Khmer, + CjkDecimal, + Devanagari, + Georgian, + Gujarati, + Gurmukhi, + Hebrew, + Kannada, + Lao, + Malayalam, + Mongolian, + Myanmar, + Oriya, + Persian, + LowerRoman, + UpperRoman, + Tamil, + Telugu, + Thai, + Tibetan, // https://www.w3.org/TR/css-counter-styles-3/#simple-alphabetic - "lower-alpha": LowerAlpha, - "lower-latin": LowerLatin, - "upper-alpha": UpperAlpha, - "upper-latin": UpperLatin, - "lower-greek": LowerGreek, - "hiragana": Hiragana, - "hiragana-iroha": HiraganaIroha, - "katakana": Katakana, - "katakana-iroha": KatakanaIroha, + LowerAlpha, + LowerLatin, + UpperAlpha, + UpperLatin, + LowerGreek, + Hiragana, + HiraganaIroha, + Katakana, + KatakanaIroha, // https://www.w3.org/TR/css-counter-styles-3/#simple-symbolic - "disc": Disc, - "circle": Circle, - "square": Square, - "disclosure-open": DisclosureOpen, - "disclosure-closed": DisclosureClosed, + Disc, + Circle, + Square, + DisclosureOpen, + DisclosureClosed, // https://www.w3.org/TR/css-counter-styles-3/#simple-fixed - "cjk-earthly-branch": CjkEarthlyBranch, - "cjk-heavenly-stem": CjkHeavenlyStem, + CjkEarthlyBranch, + CjkHeavenlyStem, // https://www.w3.org/TR/css-counter-styles-3/#complex-cjk - "japanese-informal": JapaneseInformal, - "japanese-formal": JapaneseFormal, - "korean-hangul-formal": KoreanHangulFormal, - "korean-hanja-informal": KoreanHanjaInformal, - "korean-hanja-formal": KoreanHanjaFormal, - "simp-chinese-informal": SimpChineseInformal, - "simp-chinese-formal": SimpChineseFormal, - "trad-chinese-informal": TradChineseInformal, - "trad-chinese-formal": TradChineseFormal, - "ethiopic-numeric": EthiopicNumeric, + JapaneseInformal, + JapaneseFormal, + KoreanHangulFormal, + KoreanHanjaInformal, + KoreanHanjaFormal, + SimpChineseInformal, + SimpChineseFormal, + TradChineseInformal, + TradChineseFormal, + EthiopicNumeric, } } @@ -309,7 +281,7 @@ impl Default for SymbolsType { /// `symbols()` function. /// /// See [CounterStyle](CounterStyle). -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] #[cfg_attr( @@ -326,29 +298,6 @@ pub enum Symbol<'i> { Image(Image<'i>), } -impl<'i> Parse<'i> for Symbol<'i> { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if let Ok(img) = input.try_parse(Image::parse) { - return Ok(Symbol::Image(img)); - } - - let s = CSSString::parse(input)?; - Ok(Symbol::String(s.into())) - } -} - -impl<'i> ToCss for Symbol<'i> { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - Symbol::String(s) => s.to_css(dest), - Symbol::Image(img) => img.to_css(dest), - } - } -} - enum_property! { /// A value for the [list-style-position](https://www.w3.org/TR/2020/WD-css-lists-3-20201117/#list-style-position-property) property. pub enum ListStylePosition { @@ -375,8 +324,8 @@ enum_property! { /// A value for the [marker-side](https://www.w3.org/TR/2020/WD-css-lists-3-20201117/#marker-side) property. #[allow(missing_docs)] pub enum MarkerSide { - "match-self": MatchSelf, - "match-parent": MatchParent, + MatchSelf, + MatchParent, } } diff --git a/src/properties/masking.rs b/src/properties/masking.rs index 35560c6e..ff05e1f6 100644 --- a/src/properties/masking.rs +++ b/src/properties/masking.rs @@ -37,11 +37,11 @@ enum_property! { /// A value for the [mask-mode](https://www.w3.org/TR/css-masking-1/#the-mask-mode) property. pub enum MaskMode { /// The luminance values of the mask image is used. - "luminance": Luminance, + Luminance, /// The alpha values of the mask image is used. - "alpha": Alpha, + Alpha, /// If an SVG source is used, the value matches the `mask-type` property. Otherwise, the alpha values are used. - "match-source": MatchSource, + MatchSource, } } @@ -58,11 +58,11 @@ enum_property! { /// See also [MaskMode](MaskMode). pub enum WebKitMaskSourceType { /// Equivalent to `match-source` in the standard `mask-mode` syntax. - "auto": Auto, + Auto, /// The luminance values of the mask image is used. - "luminance": Luminance, + Luminance, /// The alpha values of the mask image is used. - "alpha": Alpha, + Alpha, } } @@ -81,19 +81,19 @@ enum_property! { /// as used in the `mask-clip` and `clip-path` properties. pub enum GeometryBox { /// The painted content is clipped to the content box. - "border-box": BorderBox, + BorderBox, /// The painted content is clipped to the padding box. - "padding-box": PaddingBox, + PaddingBox, /// The painted content is clipped to the border box. - "content-box": ContentBox, + ContentBox, /// The painted content is clipped to the margin box. - "margin-box": MarginBox, + MarginBox, /// The painted content is clipped to the object bounding box. - "fill-box": FillBox, + FillBox, /// The painted content is clipped to the stroke bounding box. - "stroke-box": StrokeBox, + StrokeBox, /// Uses the nearest SVG viewport as reference box. - "view-box": ViewBox, + ViewBox, } } @@ -104,7 +104,7 @@ impl Default for GeometryBox { } /// A value for the [mask-clip](https://www.w3.org/TR/css-masking-1/#the-mask-clip) property. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -120,29 +120,6 @@ pub enum MaskClip { NoClip, } -impl<'i> Parse<'i> for MaskClip { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if let Ok(b) = input.try_parse(GeometryBox::parse) { - return Ok(MaskClip::GeometryBox(b)); - } - - input.expect_ident_matching("no-clip")?; - Ok(MaskClip::NoClip) - } -} - -impl ToCss for MaskClip { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - MaskClip::GeometryBox(b) => b.to_css(dest), - MaskClip::NoClip => dest.write_str("no-clip"), - } - } -} - impl IsCompatible for MaskClip { fn is_compatible(&self, browsers: Browsers) -> bool { match self { @@ -191,21 +168,21 @@ enum_property! { /// See also [MaskComposite](MaskComposite). #[allow(missing_docs)] pub enum WebKitMaskComposite { - "clear": Clear, - "copy": Copy, + Clear, + Copy, /// Equivalent to `add` in the standard `mask-composite` syntax. - "source-over": SourceOver, + SourceOver, /// Equivalent to `intersect` in the standard `mask-composite` syntax. - "source-in": SourceIn, + SourceIn, /// Equivalent to `subtract` in the standard `mask-composite` syntax. - "source-out": SourceOut, - "source-atop": SourceAtop, - "destination-over": DestinationOver, - "destination-in": DestinationIn, - "destination-out": DestinationOut, - "destination-atop": DestinationAtop, + SourceOut, + SourceAtop, + DestinationOver, + DestinationIn, + DestinationOut, + DestinationAtop, /// Equivalent to `exclude` in the standard `mask-composite` syntax. - "xor": Xor, + Xor, } } @@ -477,9 +454,9 @@ enum_property! { /// A value for the [mask-border-mode](https://www.w3.org/TR/css-masking-1/#the-mask-border-mode) property. pub enum MaskBorderMode { /// The luminance values of the mask image is used. - "luminance": Luminance, + Luminance, /// The alpha values of the mask image is used. - "alpha": Alpha, + Alpha, } } diff --git a/src/properties/outline.rs b/src/properties/outline.rs index e0a710e2..2ecdb202 100644 --- a/src/properties/outline.rs +++ b/src/properties/outline.rs @@ -15,7 +15,7 @@ use crate::visitor::Visit; use cssparser::*; /// A value for the [outline-style](https://drafts.csswg.org/css-ui/#outline-style) property. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -31,29 +31,6 @@ pub enum OutlineStyle { LineStyle(LineStyle), } -impl<'i> Parse<'i> for OutlineStyle { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if let Ok(border_style) = input.try_parse(LineStyle::parse) { - return Ok(OutlineStyle::LineStyle(border_style)); - } - - input.expect_ident_matching("auto")?; - Ok(OutlineStyle::Auto) - } -} - -impl ToCss for OutlineStyle { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - OutlineStyle::Auto => dest.write_str("auto"), - OutlineStyle::LineStyle(border_style) => border_style.to_css(dest), - } - } -} - impl Default for OutlineStyle { fn default() -> OutlineStyle { OutlineStyle::LineStyle(LineStyle::None) diff --git a/src/properties/position.rs b/src/properties/position.rs index ace9a40d..34a0cf3b 100644 --- a/src/properties/position.rs +++ b/src/properties/position.rs @@ -73,7 +73,7 @@ impl ToCss for Position { } /// A value for the [z-index](https://drafts.csswg.org/css2/#z-index) property. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -89,29 +89,6 @@ pub enum ZIndex { Integer(CSSInteger), } -impl<'i> Parse<'i> for ZIndex { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if let Ok(value) = input.expect_integer() { - return Ok(ZIndex::Integer(value)); - } - - input.expect_ident_matching("auto")?; - Ok(ZIndex::Auto) - } -} - -impl ToCss for ZIndex { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - ZIndex::Auto => dest.write_str("auto"), - ZIndex::Integer(value) => value.to_css(dest), - } - } -} - #[derive(Default)] pub(crate) struct PositionHandler { position: Option, diff --git a/src/properties/size.rs b/src/properties/size.rs index 54cc134d..2475773b 100644 --- a/src/properties/size.rs +++ b/src/properties/size.rs @@ -300,9 +300,9 @@ enum_property! { /// A value for the [box-sizing](https://drafts.csswg.org/css-sizing-3/#box-sizing) property. pub enum BoxSizing { /// Exclude the margin/border/padding from the width and height. - "content-box": ContentBox, + ContentBox, /// Include the padding and border (but not the margin) in the width and height. - "border-box": BorderBox, + BorderBox, } } diff --git a/src/properties/svg.rs b/src/properties/svg.rs index 26109941..150e5a25 100644 --- a/src/properties/svg.rs +++ b/src/properties/svg.rs @@ -13,7 +13,7 @@ use cssparser::*; /// An SVG [``](https://www.w3.org/TR/SVG2/painting.html#SpecifyingPaint) value /// used in the `fill` and `stroke` properties. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] #[cfg_attr( @@ -23,8 +23,6 @@ use cssparser::*; )] #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] pub enum SVGPaint<'i> { - /// No paint. - None, /// A URL reference to a paint server element, e.g. `linearGradient`, `radialGradient`, and `pattern`. Url { #[cfg_attr(feature = "serde", serde(borrow))] @@ -40,12 +38,14 @@ pub enum SVGPaint<'i> { ContextFill, /// Use the paint value of stroke from a context element. ContextStroke, + /// No paint. + None, } /// A fallback for an SVG paint in case a paint server `url()` cannot be resolved. /// /// See [SVGPaint](SVGPaint). -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -61,74 +61,6 @@ pub enum SVGPaintFallback { Color(CssColor), } -impl<'i> Parse<'i> for SVGPaint<'i> { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if let Ok(url) = input.try_parse(Url::parse) { - let fallback = input.try_parse(SVGPaintFallback::parse).ok(); - return Ok(SVGPaint::Url { url, fallback }); - } - - if let Ok(color) = input.try_parse(CssColor::parse) { - return Ok(SVGPaint::Color(color)); - } - - let location = input.current_source_location(); - let keyword = input.expect_ident()?; - match_ignore_ascii_case! { &keyword, - "none" => Ok(SVGPaint::None), - "context-fill" => Ok(SVGPaint::ContextFill), - "context-stroke" => Ok(SVGPaint::ContextStroke), - _ => Err(location.new_unexpected_token_error( - cssparser::Token::Ident(keyword.clone()) - )) - } - } -} - -impl<'i> ToCss for SVGPaint<'i> { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - SVGPaint::None => dest.write_str("none"), - SVGPaint::Url { url, fallback } => { - url.to_css(dest)?; - if let Some(fallback) = fallback { - dest.write_char(' ')?; - fallback.to_css(dest)?; - } - Ok(()) - } - SVGPaint::Color(color) => color.to_css(dest), - SVGPaint::ContextFill => dest.write_str("context-fill"), - SVGPaint::ContextStroke => dest.write_str("context-stroke"), - } - } -} - -impl<'i> Parse<'i> for SVGPaintFallback { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if input.try_parse(|input| input.expect_ident_matching("none")).is_ok() { - return Ok(SVGPaintFallback::None); - } - - Ok(SVGPaintFallback::Color(CssColor::parse(input)?)) - } -} - -impl ToCss for SVGPaintFallback { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - SVGPaintFallback::None => dest.write_str("none"), - SVGPaintFallback::Color(color) => color.to_css(dest), - } - } -} - impl<'i> FallbackValues for SVGPaint<'i> { fn get_fallbacks(&mut self, targets: Targets) -> Vec { match self { @@ -182,15 +114,15 @@ enum_property! { /// A value for the [stroke-linejoin](https://www.w3.org/TR/SVG2/painting.html#LineJoin) property. pub enum StrokeLinejoin { /// A sharp corner is to be used to join path segments. - "miter": Miter, + Miter, /// Same as `miter` but clipped beyond `stroke-miterlimit`. - "miter-clip": MiterClip, + MiterClip, /// A round corner is to be used to join path segments. - "round": Round, + Round, /// A bevelled corner is to be used to join path segments. - "bevel": Bevel, + Bevel, /// An arcs corner is to be used to join path segments. - "arcs": Arcs, + Arcs, } } @@ -260,7 +192,7 @@ impl ToCss for StrokeDasharray { } /// A value for the [marker](https://www.w3.org/TR/SVG2/painting.html#VertexMarkerProperties) properties. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] #[cfg_attr( @@ -277,29 +209,6 @@ pub enum Marker<'i> { Url(Url<'i>), } -impl<'i> Parse<'i> for Marker<'i> { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if let Ok(url) = input.try_parse(Url::parse) { - return Ok(Marker::Url(url)); - } - - input.expect_ident_matching("none")?; - Ok(Marker::None) - } -} - -impl<'i> ToCss for Marker<'i> { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - Marker::None => dest.write_str("none"), - Marker::Url(url) => url.to_css(dest), - } - } -} - enum_property! { /// A value for the [color-interpolation](https://www.w3.org/TR/SVG2/painting.html#ColorInterpolation) property. pub enum ColorInterpolation { diff --git a/src/properties/text.rs b/src/properties/text.rs index e22f6fab..622b870f 100644 --- a/src/properties/text.rs +++ b/src/properties/text.rs @@ -238,13 +238,13 @@ enum_property! { /// A value for the [word-break](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#word-break-property) property. pub enum WordBreak { /// Words break according to their customary rules. - "normal": Normal, + Normal, /// Breaking is forbidden within “words”. - "keep-all": KeepAll, + KeepAll, /// Breaking is allowed within “words”. - "break-all": BreakAll, + BreakAll, /// Breaking is allowed if there is no otherwise acceptable break points in a line. - "break-word": BreakWord, + BreakWord, } } @@ -279,12 +279,12 @@ enum_property! { /// A value for the [overflow-wrap](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#overflow-wrap-property) property. pub enum OverflowWrap { /// Lines may break only at allowed break points. - "normal": Normal, + Normal, /// Breaking is allowed if there is no otherwise acceptable break points in a line. - "anywhere": Anywhere, + Anywhere, /// As for anywhere except that soft wrap opportunities introduced by break-word are /// not considered when calculating min-content intrinsic sizes. - "break-word": BreakWord, + BreakWord, } } @@ -292,21 +292,21 @@ enum_property! { /// A value for the [text-align](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#text-align-property) property. pub enum TextAlign { /// Inline-level content is aligned to the start edge of the line box. - "start": Start, + Start, /// Inline-level content is aligned to the end edge of the line box. - "end": End, + End, /// Inline-level content is aligned to the line-left edge of the line box. - "left": Left, + Left, /// Inline-level content is aligned to the line-right edge of the line box. - "right": Right, + Right, /// Inline-level content is centered within the line box. - "center": Center, + Center, /// Text is justified according to the method specified by the text-justify property. - "justify": Justify, + Justify, /// Matches the parent element. - "match-parent": MatchParent, + MatchParent, /// Same as justify, but also justifies the last line. - "justify-all": JustifyAll, + JustifyAll, } } @@ -314,21 +314,21 @@ enum_property! { /// A value for the [text-align-last](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#text-align-last-property) property. pub enum TextAlignLast { /// Content on the affected line is aligned per `text-align-all` unless set to `justify`, in which case it is start-aligned. - "auto": Auto, + Auto, /// Inline-level content is aligned to the start edge of the line box. - "start": Start, + Start, /// Inline-level content is aligned to the end edge of the line box. - "end": End, + End, /// Inline-level content is aligned to the line-left edge of the line box. - "left": Left, + Left, /// Inline-level content is aligned to the line-right edge of the line box. - "right": Right, + Right, /// Inline-level content is centered within the line box. - "center": Center, + Center, /// Text is justified according to the method specified by the text-justify property. - "justify": Justify, + Justify, /// Matches the parent element. - "match-parent": MatchParent, + MatchParent, } } @@ -336,19 +336,19 @@ enum_property! { /// A value for the [text-justify](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#text-justify-property) property. pub enum TextJustify { /// The UA determines the justification algorithm to follow. - "auto": Auto, + Auto, /// Justification is disabled. - "none": None, + None, /// Justification adjusts spacing at word separators only. - "inter-word": InterWord, + InterWord, /// Justification adjusts spacing between each character. - "inter-character": InterCharacter, + InterCharacter, } } /// A value for the [word-spacing](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#word-spacing-property) /// and [letter-spacing](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#letter-spacing-property) properties. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -364,29 +364,6 @@ pub enum Spacing { Length(Length), } -impl<'i> Parse<'i> for Spacing { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if input.try_parse(|input| input.expect_ident_matching("normal")).is_ok() { - return Ok(Spacing::Normal); - } - - let length = Length::parse(input)?; - Ok(Spacing::Length(length)) - } -} - -impl ToCss for Spacing { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - Spacing::Normal => dest.write_str("normal"), - Spacing::Length(len) => len.to_css(dest), - } - } -} - /// A value for the [text-indent](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#text-indent-property) property. #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "visitor", derive(Visit))] @@ -466,7 +443,7 @@ impl ToCss for TextIndent { } /// A value for the [text-size-adjust](https://w3c.github.io/csswg-drafts/css-size-adjust/#adjustment-control) property. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -484,34 +461,6 @@ pub enum TextSizeAdjust { Percentage(Percentage), } -impl<'i> Parse<'i> for TextSizeAdjust { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if let Ok(p) = input.try_parse(Percentage::parse) { - return Ok(TextSizeAdjust::Percentage(p)); - } - - let ident = input.expect_ident_cloned()?; - match_ignore_ascii_case! {&*ident, - "auto" => Ok(TextSizeAdjust::Auto), - "none" => Ok(TextSizeAdjust::None), - _ => Err(input.new_unexpected_token_error(Token::Ident(ident.clone()))) - } - } -} - -impl ToCss for TextSizeAdjust { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - TextSizeAdjust::Auto => dest.write_str("auto"), - TextSizeAdjust::None => dest.write_str("none"), - TextSizeAdjust::Percentage(p) => p.to_css(dest), - } - } -} - bitflags! { /// A value for the [text-decoration-line](https://www.w3.org/TR/2020/WD-css-text-decor-4-20200506/#text-decoration-line-property) property. /// @@ -749,7 +698,7 @@ impl Default for TextDecorationStyle { } /// A value for the [text-decoration-thickness](https://www.w3.org/TR/2020/WD-css-text-decor-4-20200506/#text-decoration-width-property) property. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -773,34 +722,6 @@ impl Default for TextDecorationThickness { } } -impl<'i> Parse<'i> for TextDecorationThickness { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if input.try_parse(|input| input.expect_ident_matching("auto")).is_ok() { - return Ok(TextDecorationThickness::Auto); - } - - if input.try_parse(|input| input.expect_ident_matching("from-font")).is_ok() { - return Ok(TextDecorationThickness::FromFont); - } - - let lp = LengthPercentage::parse(input)?; - Ok(TextDecorationThickness::LengthPercentage(lp)) - } -} - -impl ToCss for TextDecorationThickness { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - TextDecorationThickness::Auto => dest.write_str("auto"), - TextDecorationThickness::FromFont => dest.write_str("from-font"), - TextDecorationThickness::LengthPercentage(lp) => lp.to_css(dest), - } - } -} - 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) { @@ -927,15 +848,15 @@ enum_property! { /// See [TextEmphasisStyle](TextEmphasisStyle). pub enum TextEmphasisShape { /// Display small circles as marks. - "dot": Dot, + Dot, /// Display large circles as marks. - "circle": Circle, + Circle, /// Display double circles as marks. - "double-circle": DoubleCircle, + DoubleCircle, /// Display triangles as marks. - "triangle": Triangle, + Triangle, /// Display sesames as marks. - "sesame": Sesame, + Sesame, } } @@ -1614,16 +1535,16 @@ enum_property! { /// A value for the [unicode-bidi](https://drafts.csswg.org/css-writing-modes-3/#unicode-bidi) property. pub enum UnicodeBidi { /// The box does not open an additional level of embedding. - "normal": Normal, + Normal, /// If the box is inline, this value creates a directional embedding by opening an additional level of embedding. - "embed": Embed, + Embed, /// On an inline box, this bidi-isolates its contents. - "isolate": Isolate, + Isolate, /// This value puts the box’s immediate inline content in a directional override. - "bidi-override": BidiOverride, + BidiOverride, /// This combines the isolation behavior of isolate with the directional override behavior of bidi-override. - "isolate-override": IsolateOverride, + IsolateOverride, /// This value behaves as isolate except that the base directionality is determined using a heuristic rather than the direction property. - "plaintext": Plaintext, + Plaintext, } } diff --git a/src/properties/transform.rs b/src/properties/transform.rs index d58a08fe..c8f5a7f7 100644 --- a/src/properties/transform.rs +++ b/src/properties/transform.rs @@ -1382,8 +1382,8 @@ enum_property! { /// A value for the [transform-style](https://drafts.csswg.org/css-transforms-2/#transform-style-property) property. #[allow(missing_docs)] pub enum TransformStyle { - "flat": Flat, - "preserve-3d": Preserve3d, + Flat, + Preserve3d, } } @@ -1391,15 +1391,15 @@ enum_property! { /// A value for the [transform-box](https://drafts.csswg.org/css-transforms-1/#transform-box) property. pub enum TransformBox { /// Uses the content box as reference box. - "content-box": ContentBox, + ContentBox, /// Uses the border box as reference box. - "border-box": BorderBox, + BorderBox, /// Uses the object bounding box as reference box. - "fill-box": FillBox, + FillBox, /// Uses the stroke bounding box as reference box. - "stroke-box": StrokeBox, + StrokeBox, /// Uses the nearest SVG viewport as reference box. - "view-box": ViewBox, + ViewBox, } } @@ -1413,7 +1413,7 @@ enum_property! { } /// A value for the [perspective](https://drafts.csswg.org/css-transforms-2/#perspective-property) property. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -1429,28 +1429,6 @@ pub enum Perspective { Length(Length), } -impl<'i> Parse<'i> for Perspective { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if input.try_parse(|input| input.expect_ident_matching("none")).is_ok() { - return Ok(Perspective::None); - } - - Ok(Perspective::Length(Length::parse(input)?)) - } -} - -impl ToCss for Perspective { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - Perspective::None => dest.write_str("none"), - Perspective::Length(len) => len.to_css(dest), - } - } -} - /// A value for the [translate](https://drafts.csswg.org/css-transforms-2/#propdef-translate) property. #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "visitor", derive(Visit))] diff --git a/src/properties/ui.rs b/src/properties/ui.rs index 4952082e..11e1eade 100644 --- a/src/properties/ui.rs +++ b/src/properties/ui.rs @@ -94,42 +94,42 @@ enum_property! { /// See [Cursor](Cursor). #[allow(missing_docs)] pub enum CursorKeyword { - "auto": Auto, - "default": Default, - "none": None, - "context-menu": ContextMenu, - "help": Help, - "pointer": Pointer, - "progress": Progress, - "wait": Wait, - "cell": Cell, - "crosshair": Crosshair, - "text": Text, - "vertical-text": VerticalText, - "alias": Alias, - "copy": Copy, - "move": Move, - "no-drop": NoDrop, - "not-allowed": NotAllowed, - "grab": Grab, - "grabbing": Grabbing, - "e-resize": EResize, - "n-resize": NResize, - "ne-resize": NeResize, - "nw-resize": NwResize, - "s-resize": SResize, - "se-resize": SeResize, - "sw-resize": SwResize, - "w-resize": WResize, - "ew-resize": EwResize, - "ns-resize": NsResize, - "nesw-resize": NeswResize, - "nwse-resize": NwseResize, - "col-resize": ColResize, - "row-resize": RowResize, - "all-scroll": AllScroll, - "zoom-in": ZoomIn, - "zoom-out": ZoomOut, + Auto, + Default, + None, + ContextMenu, + Help, + Pointer, + Progress, + Wait, + Cell, + Crosshair, + Text, + VerticalText, + Alias, + Copy, + Move, + NoDrop, + NotAllowed, + Grab, + Grabbing, + EResize, + NResize, + NeResize, + NwResize, + SResize, + SeResize, + SwResize, + WResize, + EwResize, + NsResize, + NeswResize, + NwseResize, + ColResize, + RowResize, + AllScroll, + ZoomIn, + ZoomOut, } } @@ -179,7 +179,7 @@ impl<'i> ToCss for Cursor<'i> { } /// A value for the [caret-color](https://www.w3.org/TR/2021/WD-css-ui-4-20210316/#caret-color) property. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -201,29 +201,6 @@ impl Default for ColorOrAuto { } } -impl<'i> Parse<'i> for ColorOrAuto { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if input.try_parse(|input| input.expect_ident_matching("auto")).is_ok() { - return Ok(ColorOrAuto::Auto); - } - - let color = CssColor::parse(input)?; - Ok(ColorOrAuto::Color(color)) - } -} - -impl ToCss for ColorOrAuto { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - ColorOrAuto::Auto => dest.write_str("auto"), - ColorOrAuto::Color(color) => color.to_css(dest), - } - } -} - impl FallbackValues for ColorOrAuto { fn get_fallbacks(&mut self, targets: Targets) -> Vec { match self { diff --git a/src/rules/keyframes.rs b/src/rules/keyframes.rs index 511a3172..adf2fb5b 100644 --- a/src/rules/keyframes.rs +++ b/src/rules/keyframes.rs @@ -267,7 +267,7 @@ impl<'i> ToCss for KeyframesRule<'i> { /// A [keyframe selector](https://drafts.csswg.org/css-animations/#typedef-keyframe-selector) /// within an `@keyframes` rule. -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone, Parse)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -285,24 +285,6 @@ pub enum KeyframeSelector { To, } -impl<'i> Parse<'i> for KeyframeSelector { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if let Ok(val) = input.try_parse(Percentage::parse) { - return Ok(KeyframeSelector::Percentage(val)); - } - - let location = input.current_source_location(); - let ident = input.expect_ident()?; - match_ignore_ascii_case! { &*ident, - "from" => Ok(KeyframeSelector::From), - "to" => Ok(KeyframeSelector::To), - _ => Err(location.new_unexpected_token_error( - cssparser::Token::Ident(ident.clone()) - )) - } - } -} - impl ToCss for KeyframeSelector { fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> where diff --git a/src/rules/page.rs b/src/rules/page.rs index e4897cd3..d95c94c2 100644 --- a/src/rules/page.rs +++ b/src/rules/page.rs @@ -81,41 +81,41 @@ enum_property! { /// A [page margin box](https://www.w3.org/TR/css-page-3/#margin-boxes). pub enum PageMarginBox { /// A fixed-size box defined by the intersection of the top and left margins of the page box. - "top-left-corner": TopLeftCorner, + TopLeftCorner, /// A variable-width box filling the top page margin between the top-left-corner and top-center page-margin boxes. - "top-left": TopLeft, + TopLeft, /// A variable-width box centered horizontally between the page’s left and right border edges and filling the /// page top margin between the top-left and top-right page-margin boxes. - "top-center": TopCenter, + TopCenter, /// A variable-width box filling the top page margin between the top-center and top-right-corner page-margin boxes. - "top-right": TopRight, + TopRight, /// A fixed-size box defined by the intersection of the top and right margins of the page box. - "top-right-corner": TopRightCorner, + TopRightCorner, /// A variable-height box filling the left page margin between the top-left-corner and left-middle page-margin boxes. - "left-top": LeftTop, + LeftTop, /// A variable-height box centered vertically between the page’s top and bottom border edges and filling the /// left page margin between the left-top and left-bottom page-margin boxes. - "left-middle": LeftMiddle, + LeftMiddle, /// A variable-height box filling the left page margin between the left-middle and bottom-left-corner page-margin boxes. - "left-bottom": LeftBottom, + LeftBottom, /// A variable-height box filling the right page margin between the top-right-corner and right-middle page-margin boxes. - "right-top": RightTop, + RightTop, /// A variable-height box centered vertically between the page’s top and bottom border edges and filling the right /// page margin between the right-top and right-bottom page-margin boxes. - "right-middle": RightMiddle, + RightMiddle, /// A variable-height box filling the right page margin between the right-middle and bottom-right-corner page-margin boxes. - "right-bottom": RightBottom, + RightBottom, /// A fixed-size box defined by the intersection of the bottom and left margins of the page box. - "bottom-left-corner": BottomLeftCorner, + BottomLeftCorner, /// A variable-width box filling the bottom page margin between the bottom-left-corner and bottom-center page-margin boxes. - "bottom-left": BottomLeft, + BottomLeft, /// A variable-width box centered horizontally between the page’s left and right border edges and filling the bottom /// page margin between the bottom-left and bottom-right page-margin boxes. - "bottom-center": BottomCenter, + BottomCenter, /// A variable-width box filling the bottom page margin between the bottom-center and bottom-right-corner page-margin boxes. - "bottom-right": BottomRight, + BottomRight, /// A fixed-size box defined by the intersection of the bottom and right margins of the page box. - "bottom-right-corner": BottomRightCorner, + BottomRightCorner, } } diff --git a/src/traits.rs b/src/traits.rs index 1d520421..4aecaf7c 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -30,6 +30,20 @@ pub trait Parse<'i>: Sized { } } +pub(crate) use lightningcss_derive::Parse; + +impl<'i, T: Parse<'i>> Parse<'i> for Option { + fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { + Ok(input.try_parse(T::parse).ok()) + } +} + +impl<'i, T: Parse<'i>> Parse<'i> for Box { + fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { + Ok(Box::new(T::parse(input)?)) + } +} + /// Trait for things that can be parsed from CSS syntax and require ParserOptions. pub trait ParseWithOptions<'i>: Sized { /// Parse a value of this type with the given options. @@ -78,6 +92,8 @@ pub trait ToCss { } } +pub(crate) use lightningcss_derive::ToCss; + impl<'a, T> ToCss for &'a T where T: ToCss + ?Sized, @@ -90,6 +106,27 @@ where } } +impl ToCss for Box { + fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> + where + W: std::fmt::Write, + { + (**self).to_css(dest) + } +} + +impl ToCss for Option { + fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> + where + W: std::fmt::Write, + { + if let Some(v) = self { + v.to_css(dest)?; + } + Ok(()) + } +} + pub(crate) trait PropertyHandler<'i>: Sized { fn handle_property( &mut self, diff --git a/src/values/calc.rs b/src/values/calc.rs index 95a0bb59..742c5184 100644 --- a/src/values/calc.rs +++ b/src/values/calc.rs @@ -92,13 +92,13 @@ enum_property! { /// as used in the `round()` function. pub enum RoundingStrategy { /// Round to the nearest integer. - "nearest": Nearest, + Nearest, /// Round up (ceil). - "up": Up, + Up, /// Round down (floor). - "down": Down, + Down, /// Round toward zero (truncate). - "to-zero": ToZero, + ToZero, } } diff --git a/src/values/color.rs b/src/values/color.rs index efa2bd26..b91c1426 100644 --- a/src/values/color.rs +++ b/src/values/color.rs @@ -3581,6 +3581,7 @@ impl<'i, V: ?Sized + Visitor<'i, T>, T: Visit<'i, T, V>> Visit<'i, T, V> for RGB } enum_property! { + #[css(case = lower)] /// A CSS [system color](https://drafts.csswg.org/css-color/#css-system-colors) keyword. pub enum SystemColor { /// Background of accented user interface controls. diff --git a/src/values/easing.rs b/src/values/easing.rs index c1e834a0..4db5c063 100644 --- a/src/values/easing.rs +++ b/src/values/easing.rs @@ -192,7 +192,7 @@ impl EasingFunction { } /// A [step position](https://www.w3.org/TR/css-easing-1/#step-position), used within the `steps()` function. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -233,17 +233,3 @@ impl<'i> Parse<'i> for StepPosition { Ok(keyword) } } - -impl ToCss for StepPosition { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - StepPosition::Start => dest.write_str("start"), - StepPosition::End => dest.write_str("end"), - StepPosition::JumpNone => dest.write_str("jump-none"), - StepPosition::JumpBoth => dest.write_str("jump-both"), - } - } -} diff --git a/src/values/gradient.rs b/src/values/gradient.rs index 01054b44..fec51938 100644 --- a/src/values/gradient.rs +++ b/src/values/gradient.rs @@ -533,7 +533,7 @@ impl LineDirection { /// A `radial-gradient()` [ending shape](https://www.w3.org/TR/css-images-3/#valdef-radial-gradient-ending-shape). /// /// See [RadialGradient](RadialGradient). -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -542,10 +542,11 @@ impl LineDirection { )] #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] pub enum EndingShape { - /// A circle. - Circle(Circle), + // Note: Ellipse::parse MUST run before Circle::parse for this to be correct. /// An ellipse. Ellipse(Ellipse), + /// A circle. + Circle(Circle), } impl Default for EndingShape { @@ -554,33 +555,6 @@ impl Default for EndingShape { } } -impl<'i> Parse<'i> for EndingShape { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - // Note: Ellipse::parse MUST run before Circle::parse for this to be correct. - if let Ok(ellipse) = input.try_parse(Ellipse::parse) { - return Ok(EndingShape::Ellipse(ellipse)); - } - - if let Ok(circle) = input.try_parse(Circle::parse) { - return Ok(EndingShape::Circle(circle)); - } - - return Err(input.new_error_for_next_token()); - } -} - -impl ToCss for EndingShape { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - EndingShape::Circle(circle) => circle.to_css(dest), - EndingShape::Ellipse(ellipse) => ellipse.to_css(dest), - } - } -} - /// A circle ending shape for a `radial-gradient()`. /// /// See [RadialGradient](RadialGradient). @@ -734,13 +708,13 @@ enum_property! { /// See [RadialGradient](RadialGradient). pub enum ShapeExtent { /// The closest side of the box to the gradient's center. - "closest-side": ClosestSide, + ClosestSide, /// The farthest side of the box from the gradient's center. - "farthest-side": FarthestSide, + FarthestSide, /// The closest cornder of the box to the gradient's center. - "closest-corner": ClosestCorner, + ClosestCorner, /// The farthest corner of the box from the gradient's center. - "farthest-corner": FarthestCorner, + FarthestCorner, } } diff --git a/src/values/image.rs b/src/values/image.rs index fa778cc2..e6ca26c5 100644 --- a/src/values/image.rs +++ b/src/values/image.rs @@ -19,7 +19,7 @@ use cssparser::*; use smallvec::SmallVec; /// A CSS [``](https://www.w3.org/TR/css-images-3/#image-values) value. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] #[cfg_attr(feature = "visitor", visit(visit_image, IMAGES))] @@ -309,42 +309,6 @@ impl<'i, T: ImageFallback<'i>> FallbackValues for SmallVec<[T; 1]> { } } -impl<'i> Parse<'i> for Image<'i> { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() { - return Ok(Image::None); - } - - if let Ok(url) = input.try_parse(Url::parse) { - return Ok(Image::Url(url)); - } - - if let Ok(grad) = input.try_parse(Gradient::parse) { - return Ok(Image::Gradient(Box::new(grad))); - } - - if let Ok(image_set) = input.try_parse(ImageSet::parse) { - return Ok(Image::ImageSet(image_set)); - } - - Err(input.new_error_for_next_token()) - } -} - -impl<'i> ToCss for Image<'i> { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - Image::None => dest.write_str("none"), - Image::Url(url) => url.to_css(dest), - Image::Gradient(grad) => grad.to_css(dest), - Image::ImageSet(image_set) => image_set.to_css(dest), - } - } -} - /// A CSS [`image-set()`](https://drafts.csswg.org/css-images-4/#image-set-notation) value. /// /// `image-set()` allows the user agent to choose between multiple versions of an image to diff --git a/src/values/length.rs b/src/values/length.rs index 58967f08..149f1dd1 100644 --- a/src/values/length.rs +++ b/src/values/length.rs @@ -49,7 +49,7 @@ impl IsCompatible for LengthPercentage { } /// Either a [``](https://www.w3.org/TR/css-values-4/#typedef-length-percentage), or the `auto` keyword. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -65,33 +65,6 @@ pub enum LengthPercentageOrAuto { LengthPercentage(LengthPercentage), } -impl<'i> Parse<'i> for LengthPercentageOrAuto { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() { - return Ok(LengthPercentageOrAuto::Auto); - } - - if let Ok(percent) = input.try_parse(|input| LengthPercentage::parse(input)) { - return Ok(LengthPercentageOrAuto::LengthPercentage(percent)); - } - - Err(input.new_error_for_next_token()) - } -} - -impl ToCss for LengthPercentageOrAuto { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - use LengthPercentageOrAuto::*; - match self { - Auto => dest.write_str("auto"), - LengthPercentage(l) => l.to_css(dest), - } - } -} - impl IsCompatible for LengthPercentageOrAuto { fn is_compatible(&self, browsers: Browsers) -> bool { match self { @@ -830,7 +803,7 @@ impl TrySign for Length { impl_try_from_angle!(Length); /// Either a [``](https://www.w3.org/TR/css-values-4/#lengths) or a [``](https://www.w3.org/TR/css-values-4/#numbers). -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -840,10 +813,10 @@ impl_try_from_angle!(Length); #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] pub enum LengthOrNumber { - /// A length. - Length(Length), /// A number. Number(CSSNumber), + /// A length. + Length(Length), } impl Default for LengthOrNumber { @@ -865,33 +838,6 @@ impl Zero for LengthOrNumber { } } -impl<'i> Parse<'i> for LengthOrNumber { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - // Parse number first so unitless numbers are not parsed as lengths. - if let Ok(number) = input.try_parse(CSSNumber::parse) { - return Ok(LengthOrNumber::Number(number)); - } - - if let Ok(length) = Length::parse(input) { - return Ok(LengthOrNumber::Length(length)); - } - - Err(input.new_error_for_next_token()) - } -} - -impl ToCss for LengthOrNumber { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - LengthOrNumber::Length(length) => length.to_css(dest), - LengthOrNumber::Number(number) => number.to_css(dest), - } - } -} - impl IsCompatible for LengthOrNumber { fn is_compatible(&self, browsers: Browsers) -> bool { match self { diff --git a/src/values/percentage.rs b/src/values/percentage.rs index c51be75e..54352750 100644 --- a/src/values/percentage.rs +++ b/src/values/percentage.rs @@ -144,7 +144,7 @@ impl_op!(Percentage, std::ops::Add, add); impl_try_from_angle!(Percentage); /// Either a `` or ``. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -154,36 +154,10 @@ impl_try_from_angle!(Percentage); #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] pub enum NumberOrPercentage { - /// A percentage. - Percentage(Percentage), /// A number. Number(CSSNumber), -} - -impl<'i> Parse<'i> for NumberOrPercentage { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if let Ok(number) = input.try_parse(CSSNumber::parse) { - return Ok(NumberOrPercentage::Number(number)); - } - - if let Ok(percent) = input.try_parse(|input| Percentage::parse(input)) { - return Ok(NumberOrPercentage::Percentage(percent)); - } - - Err(input.new_error_for_next_token()) - } -} - -impl ToCss for NumberOrPercentage { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - NumberOrPercentage::Percentage(percent) => percent.to_css(dest), - NumberOrPercentage::Number(number) => number.to_css(dest), - } - } + /// A percentage. + Percentage(Percentage), } impl std::convert::Into for &NumberOrPercentage { diff --git a/src/values/shape.rs b/src/values/shape.rs index c5f4cc31..9cb5f585 100644 --- a/src/values/shape.rs +++ b/src/values/shape.rs @@ -59,7 +59,7 @@ pub struct Circle { /// A [``](https://www.w3.org/TR/css-shapes-1/#typedef-shape-radius) value /// that defines the radius of a `circle()` or `ellipse()` shape. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -182,21 +182,6 @@ impl<'i> Parse<'i> for Circle { } } -impl<'i> Parse<'i> for ShapeRadius { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if let Ok(len) = input.try_parse(LengthPercentage::parse) { - return Ok(ShapeRadius::LengthPercentage(len)); - } - - if input.try_parse(|input| input.expect_ident_matching("closest-side")).is_ok() { - return Ok(ShapeRadius::ClosestSide); - } - - input.expect_ident_matching("farthest-side")?; - Ok(ShapeRadius::FarthestSide) - } -} - impl Default for ShapeRadius { fn default() -> ShapeRadius { ShapeRadius::ClosestSide @@ -315,19 +300,6 @@ impl ToCss for Circle { } } -impl ToCss for ShapeRadius { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - ShapeRadius::LengthPercentage(len) => len.to_css(dest), - ShapeRadius::ClosestSide => dest.write_str("closest-side"), - ShapeRadius::FarthestSide => dest.write_str("farthest-side"), - } - } -} - impl ToCss for Ellipse { fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> where From 39964f1d78d237bb90006d1517e05b15c4b10514 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Sun, 2 Jun 2024 23:36:22 -0700 Subject: [PATCH 002/139] Implement animation-range properties #572 --- node/ast.d.ts | 66 ++++++++++ src/lib.rs | 243 ++++++++++++++++++++++++++++++++++ src/properties/animation.rs | 252 +++++++++++++++++++++++++++++++++++- src/properties/mod.rs | 3 + 4 files changed, 563 insertions(+), 1 deletion(-) diff --git a/node/ast.d.ts b/node/ast.d.ts index 8bc02772..fba3f69e 100644 --- a/node/ast.d.ts +++ b/node/ast.d.ts @@ -1960,6 +1960,15 @@ export type PropertyId = | { property: "animation-timeline"; } + | { + property: "animation-range-start"; + } + | { + property: "animation-range-end"; + } + | { + property: "animation-range"; + } | { property: "animation"; vendorPrefix: VendorPrefix; @@ -3316,6 +3325,18 @@ export type Declaration = property: "animation-timeline"; value: AnimationTimeline[]; } + | { + property: "animation-range-start"; + value: AnimationRangeStart[]; + } + | { + property: "animation-range-end"; + value: AnimationRangeEnd[]; + } + | { + property: "animation-range"; + value: AnimationRange[]; + } | { property: "animation"; value: Animation[]; @@ -5541,6 +5562,38 @@ export type Scroller = "root" | "nearest" | "self"; * @maxItems 2 */ export type Size2DFor_LengthPercentageOrAuto = [LengthPercentageOrAuto, LengthPercentageOrAuto]; +/** + * A value for the [animation-range-start](https://drafts.csswg.org/scroll-animations/#animation-range-start) property. + */ +export type AnimationRangeStart = AnimationAttachmentRange; +/** + * A value for the [animation-range-start](https://drafts.csswg.org/scroll-animations/#animation-range-start) or [animation-range-end](https://drafts.csswg.org/scroll-animations/#animation-range-end) property. + */ +export type AnimationAttachmentRange = + | "Normal" + | { + LengthPercentage: DimensionPercentageFor_LengthValue; + } + | { + TimelineRange: { + /** + * The name of the timeline range. + */ + name: TimelineRangeName; + /** + * The offset from the start of the named timeline range. + */ + offset: DimensionPercentageFor_LengthValue; + }; + }; +/** + * A [view progress timeline range](https://drafts.csswg.org/scroll-animations/#view-timelines-ranges) + */ +export type TimelineRangeName = "cover" | "contain" | "entry" | "exit" | "entry-crossing" | "exit-crossing"; +/** + * A value for the [animation-range-end](https://drafts.csswg.org/scroll-animations/#animation-range-end) property. + */ +export type AnimationRangeEnd = AnimationAttachmentRange; /** * An individual [transform function](https://www.w3.org/TR/2019/CR-css-transforms-1-20190214/#two-d-transform-functions). */ @@ -8575,6 +8628,19 @@ export interface ViewTimeline { */ inset: Size2DFor_LengthPercentageOrAuto; } +/** + * A value for the [animation-range](https://drafts.csswg.org/scroll-animations/#animation-range) shorthand property. + */ +export interface AnimationRange { + /** + * The end of the animation's attachment range. + */ + end: AnimationRangeEnd; + /** + * The start of the animation's attachment range. + */ + start: AnimationRangeStart; +} /** * A value for the [animation](https://drafts.csswg.org/css-animations/#animation) shorthand property. */ diff --git a/src/lib.rs b/src/lib.rs index 27a7df16..8b9535f0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11585,6 +11585,249 @@ mod tests { ..Browsers::default() }, ); + + minify_test( + ".foo { animation-range-start: entry 10% }", + ".foo{animation-range-start:entry 10%}", + ); + minify_test( + ".foo { animation-range-start: entry 0% }", + ".foo{animation-range-start:entry}", + ); + minify_test( + ".foo { animation-range-start: entry }", + ".foo{animation-range-start:entry}", + ); + minify_test(".foo { animation-range-start: 50% }", ".foo{animation-range-start:50%}"); + minify_test( + ".foo { animation-range-end: exit 10% }", + ".foo{animation-range-end:exit 10%}", + ); + minify_test( + ".foo { animation-range-end: exit 100% }", + ".foo{animation-range-end:exit}", + ); + minify_test(".foo { animation-range-end: exit }", ".foo{animation-range-end:exit}"); + minify_test(".foo { animation-range-end: 50% }", ".foo{animation-range-end:50%}"); + minify_test( + ".foo { animation-range: entry 10% exit 90% }", + ".foo{animation-range:entry 10% exit 90%}", + ); + minify_test( + ".foo { animation-range: entry 0% exit 100% }", + ".foo{animation-range:entry exit}", + ); + minify_test(".foo { animation-range: entry }", ".foo{animation-range:entry}"); + minify_test( + ".foo { animation-range: entry 0% entry 100% }", + ".foo{animation-range:entry}", + ); + minify_test(".foo { animation-range: 50% normal }", ".foo{animation-range:50%}"); + minify_test( + ".foo { animation-range: normal normal }", + ".foo{animation-range:normal}", + ); + test( + r#" + .foo { + animation-range-start: entry 10%; + animation-range-end: exit 90%; + } + "#, + indoc! {r#" + .foo { + animation-range: entry 10% exit 90%; + } + "#}, + ); + test( + r#" + .foo { + animation-range-start: entry 0%; + animation-range-end: entry 100%; + } + "#, + indoc! {r#" + .foo { + animation-range: entry; + } + "#}, + ); + test( + r#" + .foo { + animation-range-start: entry 0%; + animation-range-end: exit 100%; + } + "#, + indoc! {r#" + .foo { + animation-range: entry exit; + } + "#}, + ); + test( + r#" + .foo { + animation-range-start: 10%; + animation-range-end: normal; + } + "#, + indoc! {r#" + .foo { + animation-range: 10%; + } + "#}, + ); + test( + r#" + .foo { + animation-range-start: 10%; + animation-range-end: 90%; + } + "#, + indoc! {r#" + .foo { + animation-range: 10% 90%; + } + "#}, + ); + test( + r#" + .foo { + animation-range-start: entry 10%; + animation-range-end: exit 100%; + } + "#, + indoc! {r#" + .foo { + animation-range: entry 10% exit; + } + "#}, + ); + test( + r#" + .foo { + animation-range-start: 10%; + animation-range-end: exit 90%; + } + "#, + indoc! {r#" + .foo { + animation-range: 10% exit 90%; + } + "#}, + ); + test( + r#" + .foo { + animation-range-start: entry 10%; + animation-range-end: 90%; + } + "#, + indoc! {r#" + .foo { + animation-range: entry 10% 90%; + } + "#}, + ); + test( + r#" + .foo { + animation-range: entry; + animation-range-end: 90%; + } + "#, + indoc! {r#" + .foo { + animation-range: entry 90%; + } + "#}, + ); + test( + r#" + .foo { + animation-range: entry; + animation-range-end: var(--end); + } + "#, + indoc! {r#" + .foo { + animation-range: entry; + animation-range-end: var(--end); + } + "#}, + ); + test( + r#" + .foo { + animation-range-start: entry 10%, entry 50%; + animation-range-end: exit 90%; + } + "#, + indoc! {r#" + .foo { + animation-range-start: entry 10%, entry 50%; + animation-range-end: exit 90%; + } + "#}, + ); + test( + r#" + .foo { + animation-range-start: entry 10%, entry 50%; + animation-range-end: exit 90%, exit 100%; + } + "#, + indoc! {r#" + .foo { + animation-range: entry 10% exit 90%, entry 50% exit; + } + "#}, + ); + test( + r#" + .foo { + animation-range: entry; + animation-range-end: 90%; + animation: spin 100ms; + } + "#, + indoc! {r#" + .foo { + animation: .1s spin; + } + "#}, + ); + test( + r#" + .foo { + animation: spin 100ms; + animation-range: entry; + animation-range-end: 90%; + } + "#, + indoc! {r#" + .foo { + animation: .1s spin; + animation-range: entry 90%; + } + "#}, + ); + test( + r#" + .foo { + animation-range: entry; + animation-range-end: 90%; + animation: var(--animation) 100ms; + } + "#, + indoc! {r#" + .foo { + animation: var(--animation) .1s; + } + "#}, + ); } #[test] diff --git a/src/properties/animation.rs b/src/properties/animation.rs index de83b9fa..9855127c 100644 --- a/src/properties/animation.rs +++ b/src/properties/animation.rs @@ -12,6 +12,7 @@ use crate::properties::{Property, PropertyId, TokenOrValue, VendorPrefix}; use crate::traits::{Parse, PropertyHandler, Shorthand, ToCss, Zero}; use crate::values::ident::DashedIdent; use crate::values::number::CSSNumber; +use crate::values::percentage::Percentage; use crate::values::size::Size2D; use crate::values::string::CSSString; use crate::values::{easing::EasingFunction, ident::CustomIdent, time::Time}; @@ -21,7 +22,7 @@ use cssparser::*; use itertools::izip; use smallvec::SmallVec; -use super::LengthPercentageOrAuto; +use super::{LengthPercentage, LengthPercentageOrAuto}; /// A value for the [animation-name](https://drafts.csswg.org/css-animations/#animation-name) property. #[derive(Debug, Clone, PartialEq, Parse)] @@ -375,6 +376,201 @@ impl ToCss for ViewTimeline { } } +/// A [view progress timeline range](https://drafts.csswg.org/scroll-animations/#view-timelines-ranges) +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] +#[cfg_attr(feature = "visitor", derive(Visit))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] +#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] +pub enum TimelineRangeName { + /// Represents the full range of the view progress timeline. + Cover, + /// Represents the range during which the principal box is either fully contained by, + /// or fully covers, its view progress visibility range within the scrollport. + Contain, + /// Represents the range during which the principal box is entering the view progress visibility range. + Entry, + /// Represents the range during which the principal box is exiting the view progress visibility range. + Exit, + /// Represents the range during which the principal box crosses the end border edge. + EntryCrossing, + /// Represents the range during which the principal box crosses the start border edge. + ExitCrossing, +} + +/// A value for the [animation-range-start](https://drafts.csswg.org/scroll-animations/#animation-range-start) +/// or [animation-range-end](https://drafts.csswg.org/scroll-animations/#animation-range-end) property. +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "visitor", derive(Visit))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] +#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] +pub enum AnimationAttachmentRange { + /// The start of the animation’s attachment range is the start of its associated timeline. + Normal, + /// The animation attachment range starts at the specified point on the timeline measuring from the start of the timeline. + LengthPercentage(LengthPercentage), + /// The animation attachment range starts at the specified point on the timeline measuring from the start of the specified named timeline range. + TimelineRange { + /// The name of the timeline range. + name: TimelineRangeName, + /// The offset from the start of the named timeline range. + offset: LengthPercentage, + }, +} + +impl<'i> AnimationAttachmentRange { + fn parse<'t>(input: &mut Parser<'i, 't>, default: f32) -> Result<'i, ParserError<'i>>> { + if input.try_parse(|input| input.expect_ident_matching("normal")).is_ok() { + return Ok(AnimationAttachmentRange::Normal); + } + + if let Ok(val) = input.try_parse(LengthPercentage::parse) { + return Ok(AnimationAttachmentRange::LengthPercentage(val)); + } + + let name = TimelineRangeName::parse(input)?; + let offset = input + .try_parse(LengthPercentage::parse) + .unwrap_or(LengthPercentage::Percentage(Percentage(default))); + Ok(AnimationAttachmentRange::TimelineRange { name, offset }) + } + + fn to_css(&self, dest: &mut Printer, default: f32) -> Result<(), PrinterError> + where + W: std::fmt::Write, + { + match self { + Self::Normal => dest.write_str("normal"), + Self::LengthPercentage(val) => val.to_css(dest), + Self::TimelineRange { name, offset } => { + name.to_css(dest)?; + if *offset != LengthPercentage::Percentage(Percentage(default)) { + dest.write_char(' ')?; + offset.to_css(dest)?; + } + Ok(()) + } + } + } +} + +impl Default for AnimationAttachmentRange { + fn default() -> Self { + AnimationAttachmentRange::Normal + } +} + +/// A value for the [animation-range-start](https://drafts.csswg.org/scroll-animations/#animation-range-start) property. +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "visitor", derive(Visit))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] +#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] +pub struct AnimationRangeStart(pub AnimationAttachmentRange); + +impl<'i> Parse<'i> for AnimationRangeStart { + fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { + let range = AnimationAttachmentRange::parse(input, 0.0)?; + Ok(Self(range)) + } +} + +impl ToCss for AnimationRangeStart { + fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> + where + W: std::fmt::Write, + { + self.0.to_css(dest, 0.0) + } +} + +/// A value for the [animation-range-end](https://drafts.csswg.org/scroll-animations/#animation-range-end) property. +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "visitor", derive(Visit))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] +#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] +pub struct AnimationRangeEnd(pub AnimationAttachmentRange); + +impl<'i> Parse<'i> for AnimationRangeEnd { + fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { + let range = AnimationAttachmentRange::parse(input, 1.0)?; + Ok(Self(range)) + } +} + +impl ToCss for AnimationRangeEnd { + fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> + where + W: std::fmt::Write, + { + self.0.to_css(dest, 1.0) + } +} + +/// A value for the [animation-range](https://drafts.csswg.org/scroll-animations/#animation-range) shorthand property. +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "visitor", derive(Visit))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] +#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] +pub struct AnimationRange { + /// The start of the animation's attachment range. + pub start: AnimationRangeStart, + /// The end of the animation's attachment range. + pub end: AnimationRangeEnd, +} + +impl<'i> Parse<'i> for AnimationRange { + fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { + let start = AnimationRangeStart::parse(input)?; + let end = input + .try_parse(AnimationRangeStart::parse) + .map(|r| AnimationRangeEnd(r.0)) + .unwrap_or_else(|_| { + // If <'animation-range-end'> is omitted and <'animation-range-start'> includes a component, then + // animation-range-end is set to that same and 100%. Otherwise, any omitted longhand is set to its initial value. + match &start.0 { + AnimationAttachmentRange::TimelineRange { name, .. } => { + AnimationRangeEnd(AnimationAttachmentRange::TimelineRange { + name: name.clone(), + offset: LengthPercentage::Percentage(Percentage(1.0)), + }) + } + _ => AnimationRangeEnd(AnimationAttachmentRange::default()), + } + }); + Ok(AnimationRange { start, end }) + } +} + +impl ToCss for AnimationRange { + fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> + where + W: std::fmt::Write, + { + self.start.to_css(dest)?; + + let omit_end = match (&self.start.0, &self.end.0) { + ( + AnimationAttachmentRange::TimelineRange { name: start_name, .. }, + AnimationAttachmentRange::TimelineRange { + name: end_name, + offset: end_offset, + }, + ) => start_name == end_name && *end_offset == LengthPercentage::Percentage(Percentage(1.0)), + (_, end) => *end == AnimationAttachmentRange::default(), + }; + + if !omit_end { + dest.write_char(' ')?; + self.end.to_css(dest)?; + } + Ok(()) + } +} + define_list_shorthand! { /// A value for the [animation](https://drafts.csswg.org/css-animations/#animation) shorthand property. pub struct Animation<'i>(VendorPrefix) { @@ -524,6 +720,8 @@ pub(crate) struct AnimationHandler<'i> { delays: Option<(SmallVec<[Time; 1]>, VendorPrefix)>, fill_modes: Option<(SmallVec<[AnimationFillMode; 1]>, VendorPrefix)>, timelines: Option<[AnimationTimeline<'i>; 1]>>, + range_starts: Option<[AnimationRangeStart; 1]>>, + range_ends: Option<[AnimationRangeEnd; 1]>>, has_any: bool, } @@ -574,6 +772,19 @@ impl<'i> PropertyHandler<'i> for AnimationHandler<'i> { self.timelines = Some(val.clone()); self.has_any = true; } + Property::AnimationRangeStart(val) => { + self.range_starts = Some(val.clone()); + self.has_any = true; + } + Property::AnimationRangeEnd(val) => { + self.range_ends = Some(val.clone()); + self.has_any = true; + } + Property::AnimationRange(val) => { + self.range_starts = Some(val.iter().map(|v| v.start.clone()).collect()); + self.range_ends = Some(val.iter().map(|v| v.end.clone()).collect()); + self.has_any = true; + } Property::Animation(val, vp) => { let names = val.iter().map(|b| b.name.clone()).collect(); maybe_flush!(names, &names, vp); @@ -609,6 +820,11 @@ impl<'i> PropertyHandler<'i> for AnimationHandler<'i> { property!(play_states, &play_states, vp); property!(delays, &delays, vp); property!(fill_modes, &fill_modes, vp); + + // The animation shorthand resets animation-range + // https://drafts.csswg.org/scroll-animations/#named-range-animation-declaration + self.range_starts = None; + self.range_ends = None; } Property::Unparsed(val) if is_animation_property(&val.property_id) => { let mut val = Cow::Borrowed(val); @@ -636,6 +852,9 @@ impl<'i> PropertyHandler<'i> for AnimationHandler<'i> { _ => {} } } + + self.range_starts = None; + self.range_ends = None; } self.flush(dest, context); @@ -671,6 +890,8 @@ impl<'i> AnimationHandler<'i> { let mut delays = std::mem::take(&mut self.delays); let mut fill_modes = std::mem::take(&mut self.fill_modes); let mut timelines_value = std::mem::take(&mut self.timelines); + let range_starts = std::mem::take(&mut self.range_starts); + let range_ends = std::mem::take(&mut self.range_ends); if let ( Some((names, names_vp)), @@ -813,6 +1034,32 @@ impl<'i> AnimationHandler<'i> { if let Some(val) = timelines_value { dest.push(Property::AnimationTimeline(val)); } + + match (range_starts, range_ends) { + (Some(range_starts), Some(range_ends)) => { + if range_starts.len() == range_ends.len() { + dest.push(Property::AnimationRange( + range_starts + .into_iter() + .zip(range_ends.into_iter()) + .map(|(start, end)| AnimationRange { start, end }) + .collect(), + )); + } else { + dest.push(Property::AnimationRangeStart(range_starts)); + dest.push(Property::AnimationRangeEnd(range_ends)); + } + } + (range_starts, range_ends) => { + if let Some(range_starts) = range_starts { + dest.push(Property::AnimationRangeStart(range_starts)); + } + + if let Some(range_ends) = range_ends { + dest.push(Property::AnimationRangeEnd(range_ends)); + } + } + } } } @@ -829,6 +1076,9 @@ fn is_animation_property(property_id: &PropertyId) -> bool { | PropertyId::AnimationFillMode(_) | PropertyId::AnimationComposition | PropertyId::AnimationTimeline + | PropertyId::AnimationRange + | PropertyId::AnimationRangeStart + | PropertyId::AnimationRangeEnd | PropertyId::Animation(_) => true, _ => false, } diff --git a/src/properties/mod.rs b/src/properties/mod.rs index 38ff8622..7c5f4071 100644 --- a/src/properties/mod.rs +++ b/src/properties/mod.rs @@ -1493,6 +1493,9 @@ define_properties! { "animation-fill-mode": AnimationFillMode(SmallVec<[AnimationFillMode; 1]>, VendorPrefix) / WebKit / Moz / O, "animation-composition": AnimationComposition(SmallVec<[AnimationComposition; 1]>), "animation-timeline": AnimationTimeline(SmallVec<[AnimationTimeline<'i>; 1]>), + "animation-range-start": AnimationRangeStart(SmallVec<[AnimationRangeStart; 1]>), + "animation-range-end": AnimationRangeEnd(SmallVec<[AnimationRangeEnd; 1]>), + "animation-range": AnimationRange(SmallVec<[AnimationRange; 1]>), "animation": Animation(AnimationList<'i>, VendorPrefix) / WebKit / Moz / O shorthand: true, // https://drafts.csswg.org/css-transforms-2/ From f7c883131c3463756ac935496cfe5da710eec601 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Fri, 2 Aug 2024 00:30:56 +0900 Subject: [PATCH 003/139] fix: Fix codegen of `:is(input:checked)` (#783) --- src/selector.rs | 7 ++++++- tests/cli_integration_tests.rs | 20 ++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/selector.rs b/src/selector.rs index 62550198..56df04c8 100644 --- a/src/selector.rs +++ b/src/selector.rs @@ -1624,8 +1624,13 @@ where #[inline] fn has_type_selector(selector: &Selector) -> bool { - let mut iter = selector.iter_raw_parse_order_from(0); + // For input:checked the component vector is + // [input, :checked] so we have to check it using matching order. + // + // This both happens for input:checked and is(input:checked) + let mut iter = selector.iter_raw_match_order(); let first = iter.next(); + if is_namespace(first) { is_type_selector(iter.next()) } else { diff --git a/tests/cli_integration_tests.rs b/tests/cli_integration_tests.rs index 744ac7ca..c57d26e8 100644 --- a/tests/cli_integration_tests.rs +++ b/tests/cli_integration_tests.rs @@ -759,3 +759,23 @@ fn browserslist_environment_from_browserslist_env() -> Result<(), Box Result<(), Box> { + let infile = assert_fs::NamedTempFile::new("test.css")?; + infile.write_str( + r#" + .cb:is(input:checked) { + margin: 3rem; + } + "#, + )?; + let outfile = assert_fs::NamedTempFile::new("test.out")?; + let mut cmd = Command::cargo_bin("lightningcss")?; + cmd.arg(infile.path()); + cmd.arg("--output-file").arg(outfile.path()); + cmd.assert().success(); + outfile.assert(predicate::str::contains(indoc! {r#".cb:is(input:checked)"#})); + + Ok(()) +} From 3e623473839c7fc76b277f97f88c649c6c94e44e Mon Sep 17 00:00:00 2001 From: neverland Date: Thu, 1 Aug 2024 23:31:42 +0800 Subject: [PATCH 004/139] Bump browserslist-rs 0.16.0 (#772) --- Cargo.lock | 156 ++++++++++++++++++++++++++----------------------- Cargo.toml | 2 +- c/Cargo.toml | 2 +- src/targets.rs | 4 +- 4 files changed, 88 insertions(+), 76 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 07d5f7e1..29c70813 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -57,12 +57,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" -[[package]] -name = "anyhow" -version = "1.0.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" - [[package]] name = "assert_cmd" version = "2.0.12" @@ -145,23 +139,19 @@ dependencies = [ [[package]] name = "browserslist-rs" -version = "0.15.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "405bbd46590a441abe5db3e5c8af005aa42e640803fecb51912703e93e4ce8d3" +checksum = "fdf0ca73de70c3da94e4194e4a01fe732378f55d47cf4c0588caab22a0dbfa14" dependencies = [ "ahash 0.8.7", - "anyhow", "chrono", "either", "indexmap 2.2.6", - "itertools 0.12.1", + "itertools 0.13.0", "nom", "once_cell", - "quote", "serde", "serde_json", - "string_cache", - "string_cache_codegen", "thiserror", ] @@ -246,14 +236,14 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.31" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", "num-traits", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -485,9 +475,9 @@ checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d" [[package]] name = "either" -version = "1.9.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "equivalent" @@ -699,9 +689,9 @@ dependencies = [ [[package]] name = "itertools" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] @@ -955,12 +945,6 @@ dependencies = [ "libloading", ] -[[package]] -name = "new_debug_unreachable" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" - [[package]] name = "nom" version = "7.1.3" @@ -1035,16 +1019,6 @@ dependencies = [ "vlq", ] -[[package]] -name = "parking_lot" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" -dependencies = [ - "lock_api", - "parking_lot_core", -] - [[package]] name = "parking_lot_core" version = "0.9.9" @@ -1055,7 +1029,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -1586,32 +1560,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "string_cache" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" -dependencies = [ - "new_debug_unreachable", - "once_cell", - "parking_lot", - "phf_shared 0.10.0", - "precomputed-hash", - "serde", -] - -[[package]] -name = "string_cache_codegen" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988" -dependencies = [ - "phf_generator 0.10.0", - "phf_shared 0.10.0", - "proc-macro2", - "quote", -] - [[package]] name = "strsim" version = "0.10.0" @@ -1880,7 +1828,7 @@ version = "0.51.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -1889,7 +1837,7 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -1898,13 +1846,29 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -1913,42 +1877,90 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + [[package]] name = "wyz" version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index 75bc397c..80e64af3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,7 +65,7 @@ paste = "1.0.12" # CLI deps atty = { version = "0.2", optional = true } clap = { version = "3.0.6", features = ["derive"], optional = true } -browserslist-rs = { version = "0.15.0", optional = true } +browserslist-rs = { version = "0.16.0", optional = true } rayon = { version = "1.5.1", optional = true } dashmap = { version = "5.0.0", optional = true } serde_json = { version = "1.0.78", optional = true } diff --git a/c/Cargo.toml b/c/Cargo.toml index 443d34ba..96641dc7 100644 --- a/c/Cargo.toml +++ b/c/Cargo.toml @@ -11,7 +11,7 @@ crate-type = ["cdylib"] [dependencies] lightningcss = { path = "../", features = ["browserslist"] } parcel_sourcemap = { version = "2.1.1", features = ["json"] } -browserslist-rs = { version = "0.15.0" } +browserslist-rs = { version = "0.16.0" } [build-dependencies] cbindgen = "0.24.3" diff --git a/src/targets.rs b/src/targets.rs index 3dd40061..4d44ddae 100644 --- a/src/targets.rs +++ b/src/targets.rs @@ -48,7 +48,7 @@ impl Browsers { ) -> Result