diff options
Diffstat (limited to 'servo/components/style/properties/helpers.mako.rs')
-rw-r--r-- | servo/components/style/properties/helpers.mako.rs | 1031 |
1 files changed, 1031 insertions, 0 deletions
diff --git a/servo/components/style/properties/helpers.mako.rs b/servo/components/style/properties/helpers.mako.rs new file mode 100644 index 0000000000..3fe92c967d --- /dev/null +++ b/servo/components/style/properties/helpers.mako.rs @@ -0,0 +1,1031 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +<%! + from data import Keyword, to_rust_ident, to_phys, to_camel_case, SYSTEM_FONT_LONGHANDS + from data import (LOGICAL_CORNERS, PHYSICAL_CORNERS, LOGICAL_SIDES, + PHYSICAL_SIDES, LOGICAL_SIZES, LOGICAL_AXES) +%> + +<%def name="predefined_type(name, type, initial_value, parse_method='parse', + vector=False, initial_specified_value=None, + allow_quirks='No', allow_empty=False, **kwargs)"> + <%def name="predefined_type_inner(name, type, initial_value, parse_method)"> + #[allow(unused_imports)] + use app_units::Au; + #[allow(unused_imports)] + use crate::values::specified::AllowQuirks; + #[allow(unused_imports)] + use crate::Zero; + #[allow(unused_imports)] + use smallvec::SmallVec; + pub use crate::values::specified::${type} as SpecifiedValue; + pub mod computed_value { + pub use crate::values::computed::${type} as T; + } + % if initial_value: + #[inline] pub fn get_initial_value() -> computed_value::T { ${initial_value} } + % endif + % if initial_specified_value: + #[inline] pub fn get_initial_specified_value() -> SpecifiedValue { ${initial_specified_value} } + % endif + #[allow(unused_variables)] + #[inline] + pub fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<SpecifiedValue, ParseError<'i>> { + % if allow_quirks != "No": + specified::${type}::${parse_method}_quirky(context, input, AllowQuirks::${allow_quirks}) + % elif parse_method != "parse": + specified::${type}::${parse_method}(context, input) + % else: + <specified::${type} as crate::parser::Parse>::parse(context, input) + % endif + } + </%def> + % if vector: + <%call + expr="vector_longhand(name, predefined_type=type, allow_empty=allow_empty or not initial_value, **kwargs)" + > + ${predefined_type_inner(name, type, initial_value, parse_method)} + % if caller: + ${caller.body()} + % endif + </%call> + % else: + <%call expr="longhand(name, predefined_type=type, **kwargs)"> + ${predefined_type_inner(name, type, initial_value, parse_method)} + % if caller: + ${caller.body()} + % endif + </%call> + % endif +</%def> + +// FIXME (Manishearth): Add computed_value_as_specified argument +// and handle the empty case correctly +<%doc> + To be used in cases where we have a grammar like "<thing> [ , <thing> ]*". + + Setting allow_empty to False allows for cases where the vector + is empty. The grammar for these is usually "none | <thing> [ , <thing> ]*". + We assume that the default/initial value is an empty vector for these. + `initial_value` need not be defined for these. +</%doc> + +// The setup here is roughly: +// +// * UnderlyingList is the list that is stored in the computed value. This may +// be a shared ArcSlice if the property is inherited. +// * UnderlyingOwnedList is the list that is used for animation. +// * Specified values always use OwnedSlice, since it's more compact. +// * computed_value::List is just a convenient alias that you can use for the +// computed value list, since this is in the computed_value module. +// +// If simple_vector_bindings is true, then we don't use the complex iterator +// machinery and set_foo_from, and just compute the value like any other +// longhand. +<%def name="vector_longhand(name, animation_value_type=None, + vector_animation_type=None, allow_empty=False, + simple_vector_bindings=False, + separator='Comma', + **kwargs)"> + <%call expr="longhand(name, animation_value_type=animation_value_type, vector=True, + simple_vector_bindings=simple_vector_bindings, **kwargs)"> + #[allow(unused_imports)] + use smallvec::SmallVec; + + pub mod single_value { + #[allow(unused_imports)] + use cssparser::{Parser, BasicParseError}; + #[allow(unused_imports)] + use crate::parser::{Parse, ParserContext}; + #[allow(unused_imports)] + use crate::properties::ShorthandId; + #[allow(unused_imports)] + use selectors::parser::SelectorParseErrorKind; + #[allow(unused_imports)] + use style_traits::{ParseError, StyleParseErrorKind}; + #[allow(unused_imports)] + use crate::values::computed::{Context, ToComputedValue}; + #[allow(unused_imports)] + use crate::values::{computed, specified}; + ${caller.body()} + } + + /// The definition of the computed value for ${name}. + pub mod computed_value { + #[allow(unused_imports)] + use crate::values::animated::ToAnimatedValue; + #[allow(unused_imports)] + use crate::values::resolved::ToResolvedValue; + pub use super::single_value::computed_value as single_value; + pub use self::single_value::T as SingleComputedValue; + % if not allow_empty or allow_empty == "NotInitial": + use smallvec::SmallVec; + % endif + use crate::values::computed::ComputedVecIter; + + <% + is_shared_list = allow_empty and allow_empty != "NotInitial" and \ + data.longhands_by_name[name].style_struct.inherited + %> + + // FIXME(emilio): Add an OwnedNonEmptySlice type, and figure out + // something for transition-name, which is the only remaining user + // of NotInitial. + pub type UnderlyingList<T> = + % if allow_empty and allow_empty != "NotInitial": + % if data.longhands_by_name[name].style_struct.inherited: + crate::ArcSlice<T>; + % else: + crate::OwnedSlice<T>; + % endif + % else: + SmallVec<[T; 1]>; + % endif + + pub type UnderlyingOwnedList<T> = + % if allow_empty and allow_empty != "NotInitial": + crate::OwnedSlice<T>; + % else: + SmallVec<[T; 1]>; + % endif + + + /// The generic type defining the animated and resolved values for + /// this property. + /// + /// Making this type generic allows the compiler to figure out the + /// animated value for us, instead of having to implement it + /// manually for every type we care about. + #[derive( + Clone, + Debug, + MallocSizeOf, + PartialEq, + ToAnimatedValue, + ToResolvedValue, + ToCss, + )] + % if separator == "Comma": + #[css(comma)] + % endif + pub struct OwnedList<T>( + % if not allow_empty: + #[css(iterable)] + % else: + #[css(if_empty = "none", iterable)] + % endif + pub UnderlyingOwnedList<T>, + ); + + /// The computed value for this property. + % if not is_shared_list: + pub type ComputedList = OwnedList<single_value::T>; + pub use self::OwnedList as List; + % else: + pub use self::ComputedList as List; + + #[derive( + Clone, + Debug, + MallocSizeOf, + PartialEq, + ToCss, + )] + % if separator == "Comma": + #[css(comma)] + % endif + pub struct ComputedList( + % if not allow_empty: + #[css(iterable)] + % else: + #[css(if_empty = "none", iterable)] + % endif + % if is_shared_list: + #[ignore_malloc_size_of = "Arc"] + % endif + pub UnderlyingList<single_value::T>, + ); + + type ResolvedList = OwnedList<<single_value::T as ToResolvedValue>::ResolvedValue>; + impl ToResolvedValue for ComputedList { + type ResolvedValue = ResolvedList; + + fn to_resolved_value(self, context: &crate::values::resolved::Context) -> Self::ResolvedValue { + OwnedList( + self.0 + .iter() + .cloned() + .map(|v| v.to_resolved_value(context)) + .collect() + ) + } + + fn from_resolved_value(resolved: Self::ResolvedValue) -> Self { + % if not is_shared_list: + use std::iter::FromIterator; + % endif + let iter = + resolved.0.into_iter().map(ToResolvedValue::from_resolved_value); + ComputedList(UnderlyingList::from_iter(iter)) + } + } + % endif + + % if simple_vector_bindings: + impl From<ComputedList> for UnderlyingList<single_value::T> { + #[inline] + fn from(l: ComputedList) -> Self { + l.0 + } + } + impl From<UnderlyingList<single_value::T>> for ComputedList { + #[inline] + fn from(l: UnderlyingList<single_value::T>) -> Self { + List(l) + } + } + % endif + + % if vector_animation_type: + % if not animation_value_type: + Sorry, this is stupid but needed for now. + % endif + + use crate::values::animated::{Animate, ToAnimatedZero, Procedure, lists}; + use crate::values::distance::{SquaredDistance, ComputeSquaredDistance}; + + // FIXME(emilio): For some reason rust thinks that this alias is + // unused, even though it's clearly used below? + #[allow(unused)] + type AnimatedList = OwnedList<<single_value::T as ToAnimatedValue>::AnimatedValue>; + + % if is_shared_list: + impl ToAnimatedValue for ComputedList { + type AnimatedValue = AnimatedList; + + fn to_animated_value(self) -> Self::AnimatedValue { + OwnedList( + self.0.iter().map(|v| v.clone().to_animated_value()).collect() + ) + } + + fn from_animated_value(animated: Self::AnimatedValue) -> Self { + let iter = + animated.0.into_iter().map(ToAnimatedValue::from_animated_value); + ComputedList(UnderlyingList::from_iter(iter)) + } + } + % endif + + impl ToAnimatedZero for AnimatedList { + fn to_animated_zero(&self) -> Result<Self, ()> { Err(()) } + } + + impl Animate for AnimatedList { + fn animate( + &self, + other: &Self, + procedure: Procedure, + ) -> Result<Self, ()> { + Ok(OwnedList( + lists::${vector_animation_type}::animate(&self.0, &other.0, procedure)? + )) + } + } + impl ComputeSquaredDistance for AnimatedList { + fn compute_squared_distance( + &self, + other: &Self, + ) -> Result<SquaredDistance, ()> { + lists::${vector_animation_type}::squared_distance(&self.0, &other.0) + } + } + % endif + + /// The computed value, effectively a list of single values. + pub use self::ComputedList as T; + + pub type Iter<'a, 'cx, 'cx_a> = ComputedVecIter<'a, 'cx, 'cx_a, super::single_value::SpecifiedValue>; + } + + /// The specified value of ${name}. + #[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] + % if separator == "Comma": + #[css(comma)] + % endif + pub struct SpecifiedValue( + % if not allow_empty: + #[css(iterable)] + % else: + #[css(if_empty = "none", iterable)] + % endif + pub crate::OwnedSlice<single_value::SpecifiedValue>, + ); + + pub fn get_initial_value() -> computed_value::T { + % if allow_empty and allow_empty != "NotInitial": + computed_value::List(Default::default()) + % else: + let mut v = SmallVec::new(); + v.push(single_value::get_initial_value()); + computed_value::List(v) + % endif + } + + pub fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<SpecifiedValue, ParseError<'i>> { + use style_traits::Separator; + + % if allow_empty: + if input.try_parse(|input| input.expect_ident_matching("none")).is_ok() { + return Ok(SpecifiedValue(Default::default())) + } + % endif + + let v = style_traits::${separator}::parse(input, |parser| { + single_value::parse(context, parser) + })?; + Ok(SpecifiedValue(v.into())) + } + + pub use self::single_value::SpecifiedValue as SingleSpecifiedValue; + + % if not simple_vector_bindings and engine == "gecko": + impl SpecifiedValue { + fn compute_iter<'a, 'cx, 'cx_a>( + &'a self, + context: &'cx Context<'cx_a>, + ) -> computed_value::Iter<'a, 'cx, 'cx_a> { + computed_value::Iter::new(context, &self.0) + } + } + % endif + + impl ToComputedValue for SpecifiedValue { + type ComputedValue = computed_value::T; + + #[inline] + fn to_computed_value(&self, context: &Context) -> computed_value::T { + % if not is_shared_list: + use std::iter::FromIterator; + % endif + computed_value::List(computed_value::UnderlyingList::from_iter( + self.0.iter().map(|i| i.to_computed_value(context)) + )) + } + + #[inline] + fn from_computed_value(computed: &computed_value::T) -> Self { + let iter = computed.0.iter().map(ToComputedValue::from_computed_value); + SpecifiedValue(iter.collect()) + } + } + </%call> +</%def> +<%def name="longhand(*args, **kwargs)"> + <% + property = data.declare_longhand(*args, **kwargs) + if property is None: + return "" + %> + /// ${property.spec} + pub mod ${property.ident} { + #[allow(unused_imports)] + use cssparser::{Parser, BasicParseError, Token}; + #[allow(unused_imports)] + use crate::parser::{Parse, ParserContext}; + #[allow(unused_imports)] + use crate::properties::{UnparsedValue, ShorthandId}; + #[allow(unused_imports)] + use crate::error_reporting::ParseErrorReporter; + #[allow(unused_imports)] + use crate::properties::longhands; + #[allow(unused_imports)] + use crate::properties::{LonghandId, LonghandIdSet}; + #[allow(unused_imports)] + use crate::properties::{CSSWideKeyword, ComputedValues, PropertyDeclaration}; + #[allow(unused_imports)] + use crate::properties::style_structs; + #[allow(unused_imports)] + use selectors::parser::SelectorParseErrorKind; + #[allow(unused_imports)] + use servo_arc::Arc; + #[allow(unused_imports)] + use style_traits::{ParseError, StyleParseErrorKind}; + #[allow(unused_imports)] + use crate::values::computed::{Context, ToComputedValue}; + #[allow(unused_imports)] + use crate::values::{computed, generics, specified}; + #[allow(unused_imports)] + use crate::Atom; + ${caller.body()} + #[allow(unused_variables)] + pub fn cascade_property( + declaration: &PropertyDeclaration, + context: &mut computed::Context, + ) { + context.for_non_inherited_property = + % if property.style_struct.inherited: + None; + % else: + Some(LonghandId::${property.camel_case}); + % endif + + let specified_value = match *declaration { + PropertyDeclaration::${property.camel_case}(ref value) => value, + PropertyDeclaration::CSSWideKeyword(ref declaration) => { + debug_assert_eq!(declaration.id, LonghandId::${property.camel_case}); + match declaration.keyword { + % if not property.style_struct.inherited: + CSSWideKeyword::Unset | + % endif + CSSWideKeyword::Initial => { + % if not property.style_struct.inherited: + debug_assert!(false, "Should be handled in apply_properties"); + % else: + context.builder.reset_${property.ident}(); + % endif + }, + % if property.style_struct.inherited: + CSSWideKeyword::Unset | + % endif + CSSWideKeyword::Inherit => { + % if property.style_struct.inherited: + debug_assert!(false, "Should be handled in apply_properties"); + % else: + context.rule_cache_conditions.borrow_mut().set_uncacheable(); + context.builder.inherit_${property.ident}(); + % endif + } + CSSWideKeyword::RevertLayer | + CSSWideKeyword::Revert => unreachable!("Should never get here"), + } + return; + } + PropertyDeclaration::WithVariables(..) => { + panic!("variables should already have been substituted") + } + _ => panic!("entered the wrong cascade_property() implementation"), + }; + + % if property.ident in SYSTEM_FONT_LONGHANDS and engine == "gecko": + if let Some(sf) = specified_value.get_system() { + longhands::system_font::resolve_system_font(sf, context); + } + % endif + + % if not property.style_struct.inherited and property.logical: + context.rule_cache_conditions.borrow_mut() + .set_writing_mode_dependency(context.builder.writing_mode); + % endif + + % if property.is_vector and not property.simple_vector_bindings and engine == "gecko": + // In the case of a vector property we want to pass down an + // iterator so that this can be computed without allocation. + // + // However, computing requires a context, but the style struct + // being mutated is on the context. We temporarily remove it, + // mutate it, and then put it back. Vector longhands cannot + // touch their own style struct whilst computing, else this will + // panic. + let mut s = + context.builder.take_${data.current_style_struct.name_lower}(); + { + let iter = specified_value.compute_iter(context); + s.set_${property.ident}(iter); + } + context.builder.put_${data.current_style_struct.name_lower}(s); + % else: + % if property.boxed: + let computed = (**specified_value).to_computed_value(context); + % else: + let computed = specified_value.to_computed_value(context); + % endif + context.builder.set_${property.ident}(computed) + % endif + } + + pub fn parse_declared<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<PropertyDeclaration, ParseError<'i>> { + % if property.allow_quirks != "No": + parse_quirky(context, input, specified::AllowQuirks::${property.allow_quirks}) + % else: + parse(context, input) + % endif + % if property.boxed: + .map(Box::new) + % endif + .map(PropertyDeclaration::${property.camel_case}) + } + } +</%def> + +<%def name="gecko_keyword_conversion(keyword, values=None, type='SpecifiedValue', cast_to=None)"> + <% + if not values: + values = keyword.values_for(engine) + maybe_cast = "as %s" % cast_to if cast_to else "" + const_type = cast_to if cast_to else "u32" + %> + #[cfg(feature = "gecko")] + impl ${type} { + /// Obtain a specified value from a Gecko keyword value + /// + /// Intended for use with presentation attributes, not style structs + pub fn from_gecko_keyword(kw: u32) -> Self { + use crate::gecko_bindings::structs; + % for value in values: + // We can't match on enum values if we're matching on a u32 + const ${to_rust_ident(value).upper()}: ${const_type} + = structs::${keyword.gecko_constant(value)} as ${const_type}; + % endfor + match kw ${maybe_cast} { + % for value in values: + ${to_rust_ident(value).upper()} => ${type}::${to_camel_case(value)}, + % endfor + _ => panic!("Found unexpected value in style struct for ${keyword.name} property"), + } + } + } +</%def> + +<%def name="gecko_bitflags_conversion(bit_map, gecko_bit_prefix, type, kw_type='u8')"> + #[cfg(feature = "gecko")] + impl ${type} { + /// Obtain a specified value from a Gecko keyword value + /// + /// Intended for use with presentation attributes, not style structs + pub fn from_gecko_keyword(kw: ${kw_type}) -> Self { + % for gecko_bit in bit_map.values(): + use crate::gecko_bindings::structs::${gecko_bit_prefix}${gecko_bit}; + % endfor + + let mut bits = ${type}::empty(); + % for servo_bit, gecko_bit in bit_map.items(): + if kw & (${gecko_bit_prefix}${gecko_bit} as ${kw_type}) != 0 { + bits |= ${servo_bit}; + } + % endfor + bits + } + + pub fn to_gecko_keyword(self) -> ${kw_type} { + % for gecko_bit in bit_map.values(): + use crate::gecko_bindings::structs::${gecko_bit_prefix}${gecko_bit}; + % endfor + + let mut bits: ${kw_type} = 0; + // FIXME: if we ensure that the Servo bitflags storage is the same + // as Gecko's one, we can just copy it. + % for servo_bit, gecko_bit in bit_map.items(): + if self.contains(${servo_bit}) { + bits |= ${gecko_bit_prefix}${gecko_bit} as ${kw_type}; + } + % endfor + bits + } + } +</%def> + +<%def name="single_keyword(name, values, vector=False, + needs_conversion=False, **kwargs)"> + <% + keyword_kwargs = {a: kwargs.pop(a, None) for a in [ + 'gecko_constant_prefix', + 'gecko_enum_prefix', + 'extra_gecko_values', + 'extra_servo_2013_values', + 'extra_servo_2020_values', + 'gecko_aliases', + 'servo_2013_aliases', + 'servo_2020_aliases', + 'custom_consts', + 'gecko_inexhaustive', + 'gecko_strip_moz_prefix', + ]} + %> + + <%def name="inner_body(keyword, needs_conversion=False)"> + pub use self::computed_value::T as SpecifiedValue; + pub mod computed_value { + #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] + #[derive(Clone, Copy, Debug, Eq, FromPrimitive, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToComputedValue, ToCss, ToResolvedValue, ToShmem)] + pub enum T { + % for variant in keyword.values_for(engine): + <% + aliases = [] + for alias, v in keyword.aliases_for(engine).items(): + if variant == v: + aliases.append(alias) + %> + % if aliases: + #[parse(aliases = "${','.join(sorted(aliases))}")] + % endif + ${to_camel_case(variant)}, + % endfor + } + } + #[inline] + pub fn get_initial_value() -> computed_value::T { + computed_value::T::${to_camel_case(values.split()[0])} + } + #[inline] + pub fn get_initial_specified_value() -> SpecifiedValue { + SpecifiedValue::${to_camel_case(values.split()[0])} + } + #[inline] + pub fn parse<'i, 't>(_context: &ParserContext, input: &mut Parser<'i, 't>) + -> Result<SpecifiedValue, ParseError<'i>> { + SpecifiedValue::parse(input) + } + + % if needs_conversion: + <% + conversion_values = keyword.values_for(engine) + list(keyword.aliases_for(engine).keys()) + %> + ${gecko_keyword_conversion(keyword, values=conversion_values)} + % endif + </%def> + % if vector: + <%call expr="vector_longhand(name, keyword=Keyword(name, values, **keyword_kwargs), **kwargs)"> + ${inner_body(Keyword(name, values, **keyword_kwargs))} + % if caller: + ${caller.body()} + % endif + </%call> + % else: + <%call expr="longhand(name, keyword=Keyword(name, values, **keyword_kwargs), **kwargs)"> + ${inner_body(Keyword(name, values, **keyword_kwargs), + needs_conversion=needs_conversion)} + % if caller: + ${caller.body()} + % endif + </%call> + % endif +</%def> + +<%def name="shorthand(name, sub_properties, derive_serialize=False, + derive_value_info=True, **kwargs)"> +<% + shorthand = data.declare_shorthand(name, sub_properties.split(), **kwargs) + # mako doesn't accept non-string value in parameters with <% %> form, so + # we have to workaround it this way. + if not isinstance(derive_value_info, bool): + derive_value_info = eval(derive_value_info) +%> + % if shorthand: + /// ${shorthand.spec} + pub mod ${shorthand.ident} { + use cssparser::Parser; + use crate::parser::ParserContext; + use crate::properties::{PropertyDeclaration, SourcePropertyDeclaration, MaybeBoxed, longhands}; + #[allow(unused_imports)] + use selectors::parser::SelectorParseErrorKind; + #[allow(unused_imports)] + use std::fmt::{self, Write}; + #[allow(unused_imports)] + use style_traits::{ParseError, StyleParseErrorKind}; + #[allow(unused_imports)] + use style_traits::{CssWriter, KeywordsCollectFn, SpecifiedValueInfo, ToCss}; + + % if derive_value_info: + #[derive(SpecifiedValueInfo)] + % endif + pub struct Longhands { + % for sub_property in shorthand.sub_properties: + pub ${sub_property.ident}: + % if sub_property.boxed: + Box< + % endif + longhands::${sub_property.ident}::SpecifiedValue + % if sub_property.boxed: + > + % endif + , + % endfor + } + + /// Represents a serializable set of all of the longhand properties that + /// correspond to a shorthand. + % if derive_serialize: + #[derive(ToCss)] + % endif + pub struct LonghandsToSerialize<'a> { + % for sub_property in shorthand.sub_properties: + pub ${sub_property.ident}: + % if sub_property.may_be_disabled_in(shorthand, engine): + Option< + % endif + &'a longhands::${sub_property.ident}::SpecifiedValue, + % if sub_property.may_be_disabled_in(shorthand, engine): + >, + % endif + % endfor + } + + impl<'a> LonghandsToSerialize<'a> { + /// Tries to get a serializable set of longhands given a set of + /// property declarations. + pub fn from_iter(iter: impl Iterator<Item = &'a PropertyDeclaration>) -> Result<Self, ()> { + // Define all of the expected variables that correspond to the shorthand + % for sub_property in shorthand.sub_properties: + let mut ${sub_property.ident} = + None::< &'a longhands::${sub_property.ident}::SpecifiedValue>; + % endfor + + // Attempt to assign the incoming declarations to the expected variables + for declaration in iter { + match *declaration { + % for sub_property in shorthand.sub_properties: + PropertyDeclaration::${sub_property.camel_case}(ref value) => { + ${sub_property.ident} = Some(value) + }, + % endfor + _ => {} + }; + } + + // If any of the expected variables are missing, return an error + match ( + % for sub_property in shorthand.sub_properties: + ${sub_property.ident}, + % endfor + ) { + + ( + % for sub_property in shorthand.sub_properties: + % if sub_property.may_be_disabled_in(shorthand, engine): + ${sub_property.ident}, + % else: + Some(${sub_property.ident}), + % endif + % endfor + ) => + Ok(LonghandsToSerialize { + % for sub_property in shorthand.sub_properties: + ${sub_property.ident}, + % endfor + }), + _ => Err(()) + } + } + } + + /// Parse the given shorthand and fill the result into the + /// `declarations` vector. + pub fn parse_into<'i, 't>( + declarations: &mut SourcePropertyDeclaration, + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<(), ParseError<'i>> { + #[allow(unused_imports)] + use crate::properties::{NonCustomPropertyId, LonghandId}; + input.parse_entirely(|input| parse_value(context, input)).map(|longhands| { + % for sub_property in shorthand.sub_properties: + % if sub_property.may_be_disabled_in(shorthand, engine): + if NonCustomPropertyId::from(LonghandId::${sub_property.camel_case}) + .allowed_in_ignoring_rule_type(context) { + % endif + declarations.push(PropertyDeclaration::${sub_property.camel_case}( + longhands.${sub_property.ident} + )); + % if sub_property.may_be_disabled_in(shorthand, engine): + } + % endif + % endfor + }) + } + + /// Try to serialize a given shorthand to a string. + pub fn to_css(declarations: &[&PropertyDeclaration], dest: &mut crate::str::CssStringWriter) -> fmt::Result { + match LonghandsToSerialize::from_iter(declarations.iter().cloned()) { + Ok(longhands) => longhands.to_css(&mut CssWriter::new(dest)), + Err(_) => Ok(()) + } + } + + ${caller.body()} + } + % endif +</%def> + +// A shorthand of kind `<property-1> <property-2>?` where both properties have +// the same type. +<%def name="two_properties_shorthand( + name, + first_property, + second_property, + parser_function='crate::parser::Parse::parse', + **kwargs +)"> +<%call expr="self.shorthand(name, sub_properties=' '.join([first_property, second_property]), **kwargs)"> + #[allow(unused_imports)] + use crate::parser::Parse; + #[allow(unused_imports)] + use crate::values::specified; + + fn parse_value<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Longhands, ParseError<'i>> { + let parse_one = |c: &ParserContext, input: &mut Parser<'i, 't>| -> Result< + crate::properties::longhands::${to_rust_ident(first_property)}::SpecifiedValue, + ParseError<'i> + > { + ${parser_function}(c, input) + }; + + let first = parse_one(context, input)?; + let second = + input.try_parse(|input| parse_one(context, input)).unwrap_or_else(|_| first.clone()); + Ok(expanded! { + ${to_rust_ident(first_property)}: first, + ${to_rust_ident(second_property)}: second, + }) + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { + let first = &self.${to_rust_ident(first_property)}; + let second = &self.${to_rust_ident(second_property)}; + + first.to_css(dest)?; + if first != second { + dest.write_char(' ')?; + second.to_css(dest)?; + } + Ok(()) + } + } +</%call> +</%def> + +<%def name="four_sides_shorthand(name, sub_property_pattern, + parser_function='crate::parser::Parse::parse', + allow_quirks='No', **kwargs)"> + <% sub_properties=' '.join(sub_property_pattern % side for side in PHYSICAL_SIDES) %> + <%call expr="self.shorthand(name, sub_properties=sub_properties, **kwargs)"> + #[allow(unused_imports)] + use crate::parser::Parse; + use crate::values::generics::rect::Rect; + #[allow(unused_imports)] + use crate::values::specified; + + fn parse_value<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Longhands, ParseError<'i>> { + let rect = Rect::parse_with(context, input, |c, i| -> Result< + crate::properties::longhands::${to_rust_ident(sub_property_pattern % "top")}::SpecifiedValue, + ParseError<'i> + > { + % if allow_quirks != "No": + ${parser_function}_quirky(c, i, specified::AllowQuirks::${allow_quirks}) + % else: + ${parser_function}(c, i) + % endif + })?; + Ok(expanded! { + % for index, side in enumerate(["top", "right", "bottom", "left"]): + ${to_rust_ident(sub_property_pattern % side)}: rect.${index}, + % endfor + }) + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + let rect = Rect::new( + % for side in ["top", "right", "bottom", "left"]: + &self.${to_rust_ident(sub_property_pattern % side)}, + % endfor + ); + rect.to_css(dest) + } + } + </%call> +</%def> + +<%def name="logical_setter_helper(name)"> + <% + side = None + size = None + corner = None + axis = None + maybe_side = [s for s in LOGICAL_SIDES if s in name] + maybe_size = [s for s in LOGICAL_SIZES if s in name] + maybe_corner = [s for s in LOGICAL_CORNERS if s in name] + maybe_axis = [s for s in LOGICAL_AXES if name.endswith(s)] + if len(maybe_side) == 1: + side = maybe_side[0] + elif len(maybe_size) == 1: + size = maybe_size[0] + elif len(maybe_corner) == 1: + corner = maybe_corner[0] + elif len(maybe_axis) == 1: + axis = maybe_axis[0] + def phys_ident(side, phy_side): + return to_rust_ident(to_phys(name, side, phy_side)) + %> + % if side is not None: + use crate::logical_geometry::PhysicalSide; + match wm.${to_rust_ident(side)}_physical_side() { + % for phy_side in PHYSICAL_SIDES: + PhysicalSide::${phy_side.title()} => { + ${caller.inner(physical_ident=phys_ident(side, phy_side))} + } + % endfor + } + % elif corner is not None: + use crate::logical_geometry::PhysicalCorner; + match wm.${to_rust_ident(corner)}_physical_corner() { + % for phy_corner in PHYSICAL_CORNERS: + PhysicalCorner::${to_camel_case(phy_corner)} => { + ${caller.inner(physical_ident=phys_ident(corner, phy_corner))} + } + % endfor + } + % elif size is not None: + <% + # (horizontal, vertical) + physical_size = ("height", "width") + if size == "inline-size": + physical_size = ("width", "height") + %> + if wm.is_vertical() { + ${caller.inner(physical_ident=phys_ident(size, physical_size[1]))} + } else { + ${caller.inner(physical_ident=phys_ident(size, physical_size[0]))} + } + % elif axis is not None: + <% + if axis == "inline": + me, other = "x", "y" + else: + assert(axis == "block") + me, other = "y", "x" + %> + if wm.is_vertical() { + ${caller.inner(physical_ident=phys_ident(axis, other))} + } else { + ${caller.inner(physical_ident=phys_ident(axis, me))} + } + % else: + <% raise Exception("Don't know what to do with logical property %s" % name) %> + % endif +</%def> + +<%def name="logical_setter(name)"> + /// Set the appropriate physical property for ${name} given a writing mode. + pub fn set_${to_rust_ident(name)}(&mut self, + v: longhands::${to_rust_ident(name)}::computed_value::T, + wm: WritingMode) { + <%self:logical_setter_helper name="${name}"> + <%def name="inner(physical_ident)"> + self.set_${physical_ident}(v) + </%def> + </%self:logical_setter_helper> + } + + /// Copy the appropriate physical property from another struct for ${name} + /// given a writing mode. + pub fn copy_${to_rust_ident(name)}_from(&mut self, + other: &Self, + wm: WritingMode) { + <%self:logical_setter_helper name="${name}"> + <%def name="inner(physical_ident)"> + self.copy_${physical_ident}_from(other) + </%def> + </%self:logical_setter_helper> + } + + /// Copy the appropriate physical property from another struct for ${name} + /// given a writing mode. + pub fn reset_${to_rust_ident(name)}(&mut self, + other: &Self, + wm: WritingMode) { + self.copy_${to_rust_ident(name)}_from(other, wm) + } + + /// Get the computed value for the appropriate physical property for + /// ${name} given a writing mode. + pub fn clone_${to_rust_ident(name)}(&self, wm: WritingMode) + -> longhands::${to_rust_ident(name)}::computed_value::T { + <%self:logical_setter_helper name="${name}"> + <%def name="inner(physical_ident)"> + self.clone_${physical_ident}() + </%def> + </%self:logical_setter_helper> + } +</%def> |