/* 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 http://mozilla.org/MPL/2.0/. */ <%namespace name="helpers" file="/helpers.mako.rs" /> <% from data import to_idl_name, SYSTEM_FONT_LONGHANDS from itertools import groupby %> use cssparser::Parser; #[cfg(feature = "gecko")] use gecko_bindings::bindings::RawServoAnimationValueMap; #[cfg(feature = "gecko")] use gecko_bindings::structs::RawGeckoGfxMatrix4x4; #[cfg(feature = "gecko")] use gecko_bindings::structs::nsCSSPropertyID; #[cfg(feature = "gecko")] use gecko_bindings::sugar::ownership::{HasFFI, HasSimpleFFI}; use itertools::{EitherOrBoth, Itertools}; use num_traits::Zero; use properties::{CSSWideKeyword, PropertyDeclaration}; use properties::longhands; use properties::longhands::font_weight::computed_value::T as FontWeight; use properties::longhands::font_stretch::computed_value::T as FontStretch; use properties::longhands::visibility::computed_value::T as Visibility; #[cfg(feature = "gecko")] use properties::PropertyId; use properties::{LonghandId, ShorthandId}; use selectors::parser::SelectorParseErrorKind; use servo_arc::Arc; use smallvec::SmallVec; use std::{cmp, mem, ptr}; use std::fmt::{self, Write}; #[cfg(feature = "gecko")] use hash::FnvHashMap; use style_traits::{CssWriter, ParseError, ToCss}; use super::ComputedValues; use values::{CSSFloat, CustomIdent, Either}; use values::animated::{Animate, Procedure, ToAnimatedValue, ToAnimatedZero}; use values::animated::color::RGBA as AnimatedRGBA; use values::animated::effects::Filter as AnimatedFilter; use values::animated::effects::FilterList as AnimatedFilterList; use values::computed::{Angle, CalcLengthOrPercentage}; use values::computed::{ClipRect, Context, ComputedUrl}; use values::computed::{Length, LengthOrPercentage, LengthOrPercentageOrAuto}; use values::computed::{LengthOrPercentageOrNone, MaxLength}; use values::computed::{NonNegativeNumber, Number, NumberOrPercentage, Percentage}; use values::computed::length::NonNegativeLengthOrPercentage; use values::computed::ToComputedValue; use values::computed::transform::{DirectionVector, Matrix, Matrix3D}; use values::computed::transform::TransformOperation as ComputedTransformOperation; use values::computed::transform::Transform as ComputedTransform; use values::computed::transform::Rotate as ComputedRotate; use values::computed::transform::Translate as ComputedTranslate; use values::computed::transform::Scale as ComputedScale; use values::generics::transform::{self, Rotate, Translate, Scale, Transform, TransformOperation}; use values::distance::{ComputeSquaredDistance, SquaredDistance}; use values::generics::font::FontSettings as GenericFontSettings; use values::computed::font::FontVariationSettings; use values::generics::font::VariationValue; use values::generics::NonNegative; use values::generics::effects::Filter; use values::generics::position as generic_position; use values::generics::svg::{SVGLength, SvgLengthOrPercentageOrNumber, SVGPaint}; use values::generics::svg::{SVGPaintKind, SVGStrokeDashArray, SVGOpacity}; use values::specified::font::FontTag; use void::{self, Void}; /// pub trait RepeatableListAnimatable: Animate {} /// Returns true if this nsCSSPropertyID is one of the animatable properties. #[cfg(feature = "gecko")] pub fn nscsspropertyid_is_animatable(property: nsCSSPropertyID) -> bool { match property { % for prop in data.longhands + data.shorthands_except_all(): % if prop.animatable: ${helpers.to_nscsspropertyid(prop.ident)} => true, % endif % endfor _ => false } } /// A given transition property, that is either `All`, a transitionable longhand property, /// a shorthand with at least one transitionable longhand component, or an unsupported property. // NB: This needs to be here because it needs all the longhands generated // beforehand. #[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq)] pub enum TransitionProperty { /// All, any transitionable property changing should generate a transition. /// /// FIXME(emilio): Can we remove this and just use /// Shorthand(ShorthandId::All)? All, /// A shorthand. Shorthand(ShorthandId), /// A longhand transitionable property. Longhand(LonghandId), /// Unrecognized property which could be any non-transitionable, custom property, or /// unknown property. Unsupported(CustomIdent), } impl ToCss for TransitionProperty { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: Write, { match *self { TransitionProperty::All => dest.write_str("all"), TransitionProperty::Shorthand(ref id) => dest.write_str(id.name()), TransitionProperty::Longhand(ref id) => dest.write_str(id.name()), TransitionProperty::Unsupported(ref id) => id.to_css(dest), } } } trivial_to_computed_value!(TransitionProperty); impl TransitionProperty { /// Iterates over each longhand property. pub fn each ()>(mut cb: F) { % for prop in data.longhands: % if prop.transitionable: cb(&LonghandId::${prop.camel_case}); % endif % endfor } /// Iterates over every longhand property that is not /// TransitionProperty::All, stopping and returning true when the provided /// callback returns true for the first time. pub fn any bool>(mut cb: F) -> bool { % for prop in data.longhands: % if prop.transitionable: if cb(&LonghandId::${prop.camel_case}) { return true; } % endif % endfor false } /// Parse a transition-property value. pub fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result> { let location = input.current_source_location(); let ident = input.expect_ident()?; match_ignore_ascii_case! { &ident, "all" => Ok(TransitionProperty::All), % for prop in data.shorthands_except_all(): "${prop.name}" => Ok(TransitionProperty::Shorthand(ShorthandId::${prop.camel_case})), % endfor % for prop in data.longhands: "${prop.name}" => Ok(TransitionProperty::Longhand(LonghandId::${prop.camel_case})), % endfor "none" => Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone()))), _ => CustomIdent::from_ident(location, ident, &[]).map(TransitionProperty::Unsupported), } } /// Convert TransitionProperty to nsCSSPropertyID. #[cfg(feature = "gecko")] pub fn to_nscsspropertyid(&self) -> Result { Ok(match *self { TransitionProperty::All => nsCSSPropertyID::eCSSPropertyExtra_all_properties, TransitionProperty::Shorthand(ref id) => id.to_nscsspropertyid(), TransitionProperty::Longhand(ref id) => id.to_nscsspropertyid(), TransitionProperty::Unsupported(..) => return Err(()), }) } } /// Convert nsCSSPropertyID to TransitionProperty #[cfg(feature = "gecko")] #[allow(non_upper_case_globals)] impl From for TransitionProperty { fn from(property: nsCSSPropertyID) -> TransitionProperty { match property { % for prop in data.longhands: ${helpers.to_nscsspropertyid(prop.ident)} => { TransitionProperty::Longhand(LonghandId::${prop.camel_case}) } % endfor % for prop in data.shorthands_except_all(): ${helpers.to_nscsspropertyid(prop.ident)} => { TransitionProperty::Shorthand(ShorthandId::${prop.camel_case}) } % endfor nsCSSPropertyID::eCSSPropertyExtra_all_properties => TransitionProperty::All, _ => { panic!("non-convertible nsCSSPropertyID") } } } } /// Returns true if this nsCSSPropertyID is one of the transitionable properties. #[cfg(feature = "gecko")] pub fn nscsspropertyid_is_transitionable(property: nsCSSPropertyID) -> bool { match property { % for prop in data.longhands + data.shorthands_except_all(): % if prop.transitionable: ${helpers.to_nscsspropertyid(prop.ident)} => true, % endif % endfor _ => false } } /// An animated property interpolation between two computed values for that /// property. #[cfg(feature = "servo")] #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "servo", derive(MallocSizeOf))] pub enum AnimatedProperty { % for prop in data.longhands: % if prop.animatable: <% value_type = "longhands::{}::computed_value::T".format(prop.ident) if not prop.is_animatable_with_computed_value: value_type = "<{} as ToAnimatedValue>::AnimatedValue".format(value_type) %> /// ${prop.name} ${prop.camel_case}(${value_type}, ${value_type}), % endif % endfor } #[cfg(feature = "servo")] impl AnimatedProperty { /// Get the name of this property. pub fn name(&self) -> &'static str { match *self { % for prop in data.longhands: % if prop.animatable: AnimatedProperty::${prop.camel_case}(..) => "${prop.name}", % endif % endfor } } /// Whether this interpolation does animate, that is, whether the start and /// end values are different. pub fn does_animate(&self) -> bool { match *self { % for prop in data.longhands: % if prop.animatable: AnimatedProperty::${prop.camel_case}(ref from, ref to) => from != to, % endif % endfor } } /// Whether an animated property has the same end value as another. pub fn has_the_same_end_value_as(&self, other: &Self) -> bool { match (self, other) { % for prop in data.longhands: % if prop.animatable: (&AnimatedProperty::${prop.camel_case}(_, ref this_end_value), &AnimatedProperty::${prop.camel_case}(_, ref other_end_value)) => { this_end_value == other_end_value } % endif % endfor _ => false, } } /// Update `style` with the proper computed style corresponding to this /// animation at `progress`. pub fn update(&self, style: &mut ComputedValues, progress: f64) { match *self { % for prop in data.longhands: % if prop.animatable: AnimatedProperty::${prop.camel_case}(ref from, ref to) => { // https://drafts.csswg.org/web-animations/#discrete-animation-type % if prop.animation_value_type == "discrete": let value = if progress < 0.5 { from.clone() } else { to.clone() }; % else: let value = match from.animate(to, Procedure::Interpolate { progress }) { Ok(value) => value, Err(()) => return, }; % endif % if not prop.is_animatable_with_computed_value: let value: longhands::${prop.ident}::computed_value::T = ToAnimatedValue::from_animated_value(value); % endif style.mutate_${prop.style_struct.name_lower}().set_${prop.ident}(value); } % endif % endfor } } /// Get an animatable value from a transition-property, an old style, and a /// new style. pub fn from_longhand( property: &LonghandId, old_style: &ComputedValues, new_style: &ComputedValues, ) -> Option { Some(match *property { % for prop in data.longhands: % if prop.animatable: LonghandId::${prop.camel_case} => { let old_computed = old_style.get_${prop.style_struct.ident.strip("_")}().clone_${prop.ident}(); let new_computed = new_style.get_${prop.style_struct.ident.strip("_")}().clone_${prop.ident}(); AnimatedProperty::${prop.camel_case}( % if prop.is_animatable_with_computed_value: old_computed, new_computed, % else: old_computed.to_animated_value(), new_computed.to_animated_value(), % endif ) } % endif % endfor _ => return None, }) } } /// A collection of AnimationValue that were composed on an element. /// This HashMap stores the values that are the last AnimationValue to be /// composed for each TransitionProperty. #[cfg(feature = "gecko")] pub type AnimationValueMap = FnvHashMap; #[cfg(feature = "gecko")] unsafe impl HasFFI for AnimationValueMap { type FFIType = RawServoAnimationValueMap; } #[cfg(feature = "gecko")] unsafe impl HasSimpleFFI for AnimationValueMap {} /// An enum to represent a single computed value belonging to an animated /// property in order to be interpolated with another one. When interpolating, /// both values need to belong to the same property. /// /// This is different to AnimatedProperty in the sense that AnimatedProperty /// also knows the final value to be used during the animation. /// /// This is to be used in Gecko integration code. /// /// FIXME: We need to add a path for custom properties, but that's trivial after /// this (is a similar path to that of PropertyDeclaration). #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "servo", derive(MallocSizeOf))] #[repr(u16)] pub enum AnimationValue { % for prop in data.longhands: % if prop.animatable: /// `${prop.name}` ${prop.camel_case}(${prop.animated_type()}), % else: /// `${prop.name}` (not animatable) ${prop.camel_case}(Void), % endif % endfor } <% animated = [] unanimated = [] for prop in data.longhands: if prop.animatable: animated.append(prop) else: unanimated.append(prop) %> impl AnimationValue { /// Returns the longhand id this animated value corresponds to. #[inline] pub fn id(&self) -> LonghandId { let id = unsafe { *(self as *const _ as *const LonghandId) }; debug_assert_eq!(id, match *self { % for prop in data.longhands: % if prop.animatable: AnimationValue::${prop.camel_case}(..) => LonghandId::${prop.camel_case}, % else: AnimationValue::${prop.camel_case}(void) => void::unreachable(void), % endif % endfor }); id } /// "Uncompute" this animation value in order to be used inside the CSS /// cascade. pub fn uncompute(&self) -> PropertyDeclaration { use properties::longhands; use self::AnimationValue::*; use super::PropertyDeclarationVariantRepr; match *self { <% keyfunc = lambda x: (x.base_type(), x.specified_type(), x.boxed, x.is_animatable_with_computed_value) %> % for (ty, specified, boxed, computed), props in groupby(animated, key=keyfunc): <% props = list(props) %> ${" |\n".join("{}(ref value)".format(prop.camel_case) for prop in props)} => { % if not computed: let ref value = ToAnimatedValue::from_animated_value(value.clone()); % endif let value = ${ty}::from_computed_value(&value); % if boxed: let value = Box::new(value); % endif % if len(props) == 1: PropertyDeclaration::${props[0].camel_case}(value) % else: unsafe { let mut out = mem::uninitialized(); ptr::write( &mut out as *mut _ as *mut PropertyDeclarationVariantRepr<${specified}>, PropertyDeclarationVariantRepr { tag: *(self as *const _ as *const u16), value, }, ); out } % endif } % endfor ${" |\n".join("{}(void)".format(prop.camel_case) for prop in unanimated)} => { void::unreachable(void) } } } /// Construct an AnimationValue from a property declaration. pub fn from_declaration( decl: &PropertyDeclaration, context: &mut Context, extra_custom_properties: Option<<&Arc<::custom_properties::CustomPropertiesMap>>, initial: &ComputedValues ) -> Option { use properties::LonghandId; let animatable = match *decl { % for prop in data.longhands: % if prop.animatable: PropertyDeclaration::${prop.camel_case}(ref val) => { context.for_non_inherited_property = % if prop.style_struct.inherited: None; % else: Some(LonghandId::${prop.camel_case}); % endif % if prop.ident in SYSTEM_FONT_LONGHANDS and product == "gecko": if let Some(sf) = val.get_system() { longhands::system_font::resolve_system_font(sf, context); } % endif % if prop.boxed: let computed = (**val).to_computed_value(context); % else: let computed = val.to_computed_value(context); % endif AnimationValue::${prop.camel_case}( % if prop.is_animatable_with_computed_value: computed % else: computed.to_animated_value() % endif ) }, % endif % endfor PropertyDeclaration::CSSWideKeyword(ref declaration) => { match declaration.id { // We put all the animatable properties first in the hopes // that it might increase match locality. % for prop in data.longhands: % if prop.animatable: LonghandId::${prop.camel_case} => { let style_struct = match declaration.keyword { % if not prop.style_struct.inherited: CSSWideKeyword::Unset | % endif CSSWideKeyword::Initial => { initial.get_${prop.style_struct.name_lower}() }, % if prop.style_struct.inherited: CSSWideKeyword::Unset | % endif CSSWideKeyword::Inherit => { context.builder .get_parent_${prop.style_struct.name_lower}() }, }; let computed = style_struct.clone_${prop.ident}(); % if not prop.is_animatable_with_computed_value: let computed = computed.to_animated_value(); % endif AnimationValue::${prop.camel_case}(computed) }, % endif % endfor % for prop in data.longhands: % if not prop.animatable: LonghandId::${prop.camel_case} => return None, % endif % endfor } }, PropertyDeclaration::WithVariables(ref declaration) => { let substituted = { let custom_properties = extra_custom_properties.or_else(|| context.style().custom_properties()); declaration.value.substitute_variables( declaration.id, custom_properties, context.quirks_mode, ) }; return AnimationValue::from_declaration( &substituted, context, extra_custom_properties, initial, ) }, _ => return None // non animatable properties will get included because of shorthands. ignore. }; Some(animatable) } /// Get an AnimationValue for an AnimatableLonghand from a given computed values. pub fn from_computed_values( property: &LonghandId, computed_values: &ComputedValues ) -> Option { Some(match *property { % for prop in data.longhands: % if prop.animatable: LonghandId::${prop.camel_case} => { let computed = computed_values .get_${prop.style_struct.ident.strip("_")}() .clone_${prop.ident}(); AnimationValue::${prop.camel_case}( % if prop.is_animatable_with_computed_value: computed % else: computed.to_animated_value() % endif ) } % endif % endfor _ => return None, }) } } fn animate_discrete(this: &T, other: &T, procedure: Procedure) -> Result { if let Procedure::Interpolate { progress } = procedure { Ok(if progress < 0.5 { this.clone() } else { other.clone() }) } else { Err(()) } } #[repr(C)] struct AnimationValueVariantRepr { tag: u16, value: T } impl Animate for AnimationValue { fn animate(&self, other: &Self, procedure: Procedure) -> Result { Ok(unsafe { use self::AnimationValue::*; let this_tag = *(self as *const _ as *const u16); let other_tag = *(other as *const _ as *const u16); if this_tag != other_tag { panic!("Unexpected AnimationValue::animate call"); } match *self { <% keyfunc = lambda x: (x.animated_type(), x.animation_value_type == "discrete") %> % for (ty, discrete), props in groupby(animated, key=keyfunc): ${" |\n".join("{}(ref this)".format(prop.camel_case) for prop in props)} => { let other_repr = &*(other as *const _ as *const AnimationValueVariantRepr<${ty}>); % if discrete: let value = animate_discrete(this, &other_repr.value, procedure)?; % else: let value = this.animate(&other_repr.value, procedure)?; % endif let mut out = mem::uninitialized(); ptr::write( &mut out as *mut _ as *mut AnimationValueVariantRepr<${ty}>, AnimationValueVariantRepr { tag: this_tag, value, }, ); out } % endfor ${" |\n".join("{}(void)".format(prop.camel_case) for prop in unanimated)} => { void::unreachable(void) } } }) } } <% nondiscrete = [] for prop in animated: if prop.animation_value_type != "discrete": nondiscrete.append(prop) %> impl ComputeSquaredDistance for AnimationValue { fn compute_squared_distance(&self, other: &Self) -> Result { unsafe { use self::AnimationValue::*; let this_tag = *(self as *const _ as *const u16); let other_tag = *(other as *const _ as *const u16); if this_tag != other_tag { panic!("Unexpected AnimationValue::compute_squared_distance call"); } match *self { % for ty, props in groupby(nondiscrete, key=lambda x: x.animated_type()): ${" |\n".join("{}(ref this)".format(prop.camel_case) for prop in props)} => { let other_repr = &*(other as *const _ as *const AnimationValueVariantRepr<${ty}>); this.compute_squared_distance(&other_repr.value) } % endfor _ => Err(()), } } } } impl ToAnimatedZero for AnimationValue { #[inline] fn to_animated_zero(&self) -> Result { match *self { % for prop in data.longhands: % if prop.animatable and prop.animation_value_type != "discrete": AnimationValue::${prop.camel_case}(ref base) => { Ok(AnimationValue::${prop.camel_case}(base.to_animated_zero()?)) }, % endif % endfor _ => Err(()), } } } impl RepeatableListAnimatable for LengthOrPercentage {} impl RepeatableListAnimatable for Either {} impl RepeatableListAnimatable for Either {} impl RepeatableListAnimatable for SvgLengthOrPercentageOrNumber {} macro_rules! repeated_vec_impl { ($($ty:ty),*) => { $(impl Animate for $ty where T: RepeatableListAnimatable, { fn animate(&self, other: &Self, procedure: Procedure) -> Result { // If the length of either list is zero, the least common multiple is undefined. if self.is_empty() || other.is_empty() { return Err(()); } use num_integer::lcm; let len = lcm(self.len(), other.len()); self.iter().cycle().zip(other.iter().cycle()).take(len).map(|(this, other)| { this.animate(other, procedure) }).collect() } } impl ComputeSquaredDistance for $ty where T: ComputeSquaredDistance + RepeatableListAnimatable, { #[inline] fn compute_squared_distance(&self, other: &Self) -> Result { if self.is_empty() || other.is_empty() { return Err(()); } use num_integer::lcm; let len = lcm(self.len(), other.len()); self.iter().cycle().zip(other.iter().cycle()).take(len).map(|(this, other)| { this.compute_squared_distance(other) }).sum() } })* }; } repeated_vec_impl!(SmallVec<[T; 1]>, Vec); /// impl Animate for Visibility { #[inline] fn animate(&self, other: &Self, procedure: Procedure) -> Result { let (this_weight, other_weight) = procedure.weights(); match (*self, *other) { (Visibility::Visible, _) => { Ok(if this_weight > 0.0 { *self } else { *other }) }, (_, Visibility::Visible) => { Ok(if other_weight > 0.0 { *other } else { *self }) }, _ => Err(()), } } } impl ComputeSquaredDistance for Visibility { #[inline] fn compute_squared_distance(&self, other: &Self) -> Result { Ok(SquaredDistance::Value(if *self == *other { 0. } else { 1. })) } } impl ToAnimatedZero for Visibility { #[inline] fn to_animated_zero(&self) -> Result { Err(()) } } /// impl Animate for CalcLengthOrPercentage { #[inline] fn animate(&self, other: &Self, procedure: Procedure) -> Result { let animate_percentage_half = |this: Option, other: Option| { if this.is_none() && other.is_none() { return Ok(None); } let this = this.unwrap_or_default(); let other = other.unwrap_or_default(); Ok(Some(this.animate(&other, procedure)?)) }; let length = self.unclamped_length().animate(&other.unclamped_length(), procedure)?; let percentage = animate_percentage_half(self.percentage, other.percentage)?; Ok(CalcLengthOrPercentage::with_clamping_mode(length, percentage, self.clamping_mode)) } } impl ToAnimatedZero for LengthOrPercentageOrAuto { #[inline] fn to_animated_zero(&self) -> Result { match *self { LengthOrPercentageOrAuto::Length(_) | LengthOrPercentageOrAuto::Percentage(_) | LengthOrPercentageOrAuto::Calc(_) => { Ok(LengthOrPercentageOrAuto::Length(Length::new(0.))) }, LengthOrPercentageOrAuto::Auto => Err(()), } } } impl ToAnimatedZero for LengthOrPercentageOrNone { #[inline] fn to_animated_zero(&self) -> Result { match *self { LengthOrPercentageOrNone::Length(_) | LengthOrPercentageOrNone::Percentage(_) | LengthOrPercentageOrNone::Calc(_) => { Ok(LengthOrPercentageOrNone::Length(Length::new(0.))) }, LengthOrPercentageOrNone::None => Err(()), } } } impl ToAnimatedZero for MaxLength { #[inline] fn to_animated_zero(&self) -> Result { Err(()) } } /// impl Animate for FontWeight { #[inline] fn animate(&self, other: &Self, procedure: Procedure) -> Result { let a = self.0 as f64; let b = other.0 as f64; const NORMAL: f64 = 400.; let (this_weight, other_weight) = procedure.weights(); let weight = (a - NORMAL) * this_weight + (b - NORMAL) * other_weight + NORMAL; let weight = (weight.max(100.).min(900.) / 100.).round() * 100.; Ok(FontWeight(weight as u16)) } } impl ToAnimatedZero for FontWeight { #[inline] fn to_animated_zero(&self) -> Result { Ok(FontWeight::normal()) } } /// impl Animate for FontStretch { #[inline] fn animate(&self, other: &Self, procedure: Procedure) -> Result { let from = f64::from(*self); let to = f64::from(*other); let normal = f64::from(FontStretch::Normal); let (this_weight, other_weight) = procedure.weights(); let result = (from - normal) * this_weight + (to - normal) * other_weight + normal; Ok(result.into()) } } impl ComputeSquaredDistance for FontStretch { #[inline] fn compute_squared_distance(&self, other: &Self) -> Result { f64::from(*self).compute_squared_distance(&(*other).into()) } } impl ToAnimatedZero for FontStretch { #[inline] fn to_animated_zero(&self) -> Result { Err(()) } } /// We should treat font stretch as real number in order to interpolate this property. /// impl From for f64 { fn from(stretch: FontStretch) -> f64 { use self::FontStretch::*; match stretch { UltraCondensed => 1.0, ExtraCondensed => 2.0, Condensed => 3.0, SemiCondensed => 4.0, Normal => 5.0, SemiExpanded => 6.0, Expanded => 7.0, ExtraExpanded => 8.0, UltraExpanded => 9.0, } } } impl Into for f64 { fn into(self) -> FontStretch { use properties::longhands::font_stretch::computed_value::T::*; let index = (self + 0.5).floor().min(9.0).max(1.0); static FONT_STRETCH_ENUM_MAP: [FontStretch; 9] = [ UltraCondensed, ExtraCondensed, Condensed, SemiCondensed, Normal, SemiExpanded, Expanded, ExtraExpanded, UltraExpanded ]; FONT_STRETCH_ENUM_MAP[(index - 1.0) as usize] } } /// impl Animate for FontVariationSettings { #[inline] fn animate(&self, other: &Self, procedure: Procedure) -> Result { FontSettingTagIter::new(self, other)? .map(|r| r.and_then(|(st, ot)| st.animate(&ot, procedure))) .collect::, ()>>() .map(|v| GenericFontSettings(v.into_boxed_slice())) } } impl ComputeSquaredDistance for FontVariationSettings { #[inline] fn compute_squared_distance(&self, other: &Self) -> Result { FontSettingTagIter::new(self, other)? .map(|r| r.and_then(|(st, ot)| st.compute_squared_distance(&ot))) .sum() } } impl ToAnimatedZero for FontVariationSettings { #[inline] fn to_animated_zero(&self) -> Result { Err(()) } } type ComputedVariationValue = VariationValue; // FIXME: Could do a rename, this is only used for font variations. struct FontSettingTagIterState<'a> { tags: Vec<(&'a ComputedVariationValue)>, index: usize, prev_tag: FontTag, } impl<'a> FontSettingTagIterState<'a> { fn new(tags: Vec<<&'a ComputedVariationValue>) -> FontSettingTagIterState<'a> { FontSettingTagIterState { index: tags.len(), tags, prev_tag: FontTag(0), } } } /// Iterator for font-variation-settings tag lists /// /// [CSS fonts level 4](https://drafts.csswg.org/css-fonts-4/#descdef-font-face-font-variation-settings) /// defines the animation of font-variation-settings as follows: /// /// Two declarations of font-feature-settings[sic] can be animated between if /// they are "like". "Like" declarations are ones where the same set of /// properties appear (in any order). Because succesive[sic] duplicate /// properties are applied instead of prior duplicate properties, two /// declarations can be "like" even if they have differing number of /// properties. If two declarations are "like" then animation occurs pairwise /// between corresponding values in the declarations. /// /// In other words if we have the following lists: /// /// "wght" 1.4, "wdth" 5, "wght" 2 /// "wdth" 8, "wght" 4, "wdth" 10 /// /// We should animate between: /// /// "wdth" 5, "wght" 2 /// "wght" 4, "wdth" 10 /// /// This iterator supports this by sorting the two lists, then iterating them in /// reverse, and skipping entries with repeated tag names. It will return /// Some(Err()) if it reaches the end of one list before the other, or if the /// tag names do not match. /// /// For the above example, this iterator would return: /// /// Some(Ok("wght" 2, "wght" 4)) /// Some(Ok("wdth" 5, "wdth" 10)) /// None /// struct FontSettingTagIter<'a> { a_state: FontSettingTagIterState<'a>, b_state: FontSettingTagIterState<'a>, } impl<'a> FontSettingTagIter<'a> { fn new( a_settings: &'a FontVariationSettings, b_settings: &'a FontVariationSettings, ) -> Result, ()> { if a_settings.0.is_empty() || b_settings.0.is_empty() { return Err(()); } fn as_new_sorted_tags(tags: &[ComputedVariationValue]) -> Vec<<&ComputedVariationValue> { use std::iter::FromIterator; let mut sorted_tags = Vec::from_iter(tags.iter()); sorted_tags.sort_by_key(|k| k.tag.0); sorted_tags }; Ok(FontSettingTagIter { a_state: FontSettingTagIterState::new(as_new_sorted_tags(&a_settings.0)), b_state: FontSettingTagIterState::new(as_new_sorted_tags(&b_settings.0)), }) } fn next_tag(state: &mut FontSettingTagIterState<'a>) -> Option<<&'a ComputedVariationValue> { if state.index == 0 { return None; } state.index -= 1; let tag = state.tags[state.index]; if tag.tag == state.prev_tag { FontSettingTagIter::next_tag(state) } else { state.prev_tag = tag.tag; Some(tag) } } } impl<'a> Iterator for FontSettingTagIter<'a> { type Item = Result<(&'a ComputedVariationValue, &'a ComputedVariationValue), ()>; fn next(&mut self) -> Option> { match ( FontSettingTagIter::next_tag(&mut self.a_state), FontSettingTagIter::next_tag(&mut self.b_state), ) { (Some(at), Some(bt)) if at.tag == bt.tag => Some(Ok((at, bt))), (None, None) => None, _ => Some(Err(())), // Mismatch number of unique tags or tag names. } } } impl RepeatableListAnimatable for generic_position::Position where H: RepeatableListAnimatable, V: RepeatableListAnimatable {} /// impl Animate for ClipRect { #[inline] fn animate(&self, other: &Self, procedure: Procedure) -> Result { use values::computed::Length; let animate_component = |this: &Option, other: &Option| { match (this.animate(other, procedure)?, procedure) { (None, Procedure::Interpolate { .. }) => Ok(None), (None, _) => Err(()), (result, _) => Ok(result), } }; Ok(ClipRect { top: animate_component(&self.top, &other.top)?, right: animate_component(&self.right, &other.right)?, bottom: animate_component(&self.bottom, &other.bottom)?, left: animate_component(&self.left, &other.left)?, }) } } impl ToAnimatedZero for ClipRect { #[inline] fn to_animated_zero(&self) -> Result { Err(()) } } fn animate_multiplicative_factor( this: CSSFloat, other: CSSFloat, procedure: Procedure, ) -> Result { Ok((this - 1.).animate(&(other - 1.), procedure)? + 1.) } /// impl Animate for ComputedTransformOperation { fn animate(&self, other: &Self, procedure: Procedure) -> Result { match (self, other) { ( &TransformOperation::Matrix3D(ref this), &TransformOperation::Matrix3D(ref other), ) => { Ok(TransformOperation::Matrix3D( this.animate(other, procedure)?, )) }, ( &TransformOperation::Matrix(ref this), &TransformOperation::Matrix(ref other), ) => { Ok(TransformOperation::Matrix( this.animate(other, procedure)?, )) }, ( &TransformOperation::Skew(ref fx, None), &TransformOperation::Skew(ref tx, None), ) => { Ok(TransformOperation::Skew( fx.animate(tx, procedure)?, None, )) }, ( &TransformOperation::Skew(ref fx, ref fy), &TransformOperation::Skew(ref tx, ref ty), ) => { Ok(TransformOperation::Skew( fx.animate(tx, procedure)?, Some(fy.unwrap_or(Angle::zero()).animate(&ty.unwrap_or(Angle::zero()), procedure)?) )) }, ( &TransformOperation::SkewX(ref f), &TransformOperation::SkewX(ref t), ) => { Ok(TransformOperation::SkewX( f.animate(t, procedure)?, )) }, ( &TransformOperation::SkewY(ref f), &TransformOperation::SkewY(ref t), ) => { Ok(TransformOperation::SkewY( f.animate(t, procedure)?, )) }, ( &TransformOperation::Translate3D(ref fx, ref fy, ref fz), &TransformOperation::Translate3D(ref tx, ref ty, ref tz), ) => { Ok(TransformOperation::Translate3D( fx.animate(tx, procedure)?, fy.animate(ty, procedure)?, fz.animate(tz, procedure)?, )) }, ( &TransformOperation::Translate(ref fx, None), &TransformOperation::Translate(ref tx, None), ) => { Ok(TransformOperation::Translate( fx.animate(tx, procedure)?, None )) }, ( &TransformOperation::Translate(ref fx, ref fy), &TransformOperation::Translate(ref tx, ref ty), ) => { Ok(TransformOperation::Translate( fx.animate(tx, procedure)?, Some(fy.unwrap_or(*fx).animate(&ty.unwrap_or(*tx), procedure)?) )) }, ( &TransformOperation::TranslateX(ref f), &TransformOperation::TranslateX(ref t), ) => { Ok(TransformOperation::TranslateX( f.animate(t, procedure)? )) }, ( &TransformOperation::TranslateY(ref f), &TransformOperation::TranslateY(ref t), ) => { Ok(TransformOperation::TranslateY( f.animate(t, procedure)? )) }, ( &TransformOperation::TranslateZ(ref f), &TransformOperation::TranslateZ(ref t), ) => { Ok(TransformOperation::TranslateZ( f.animate(t, procedure)? )) }, ( &TransformOperation::Scale3D(ref fx, ref fy, ref fz), &TransformOperation::Scale3D(ref tx, ref ty, ref tz), ) => { Ok(TransformOperation::Scale3D( animate_multiplicative_factor(*fx, *tx, procedure)?, animate_multiplicative_factor(*fy, *ty, procedure)?, animate_multiplicative_factor(*fz, *tz, procedure)?, )) }, ( &TransformOperation::ScaleX(ref f), &TransformOperation::ScaleX(ref t), ) => { Ok(TransformOperation::ScaleX( animate_multiplicative_factor(*f, *t, procedure)? )) }, ( &TransformOperation::ScaleY(ref f), &TransformOperation::ScaleY(ref t), ) => { Ok(TransformOperation::ScaleY( animate_multiplicative_factor(*f, *t, procedure)? )) }, ( &TransformOperation::ScaleZ(ref f), &TransformOperation::ScaleZ(ref t), ) => { Ok(TransformOperation::ScaleZ( animate_multiplicative_factor(*f, *t, procedure)? )) }, ( &TransformOperation::Scale(ref f, None), &TransformOperation::Scale(ref t, None), ) => { Ok(TransformOperation::Scale( animate_multiplicative_factor(*f, *t, procedure)?, None )) }, ( &TransformOperation::Scale(ref fx, ref fy), &TransformOperation::Scale(ref tx, ref ty), ) => { Ok(TransformOperation::Scale( animate_multiplicative_factor(*fx, *tx, procedure)?, Some(animate_multiplicative_factor(fy.unwrap_or(*fx), ty.unwrap_or(*tx), procedure)?), )) }, ( &TransformOperation::Rotate3D(fx, fy, fz, fa), &TransformOperation::Rotate3D(tx, ty, tz, ta), ) => { let (fx, fy, fz, fa) = transform::get_normalized_vector_and_angle(fx, fy, fz, fa); let (tx, ty, tz, ta) = transform::get_normalized_vector_and_angle(tx, ty, tz, ta); if (fx, fy, fz) == (tx, ty, tz) { let ia = fa.animate(&ta, procedure)?; Ok(TransformOperation::Rotate3D(fx, fy, fz, ia)) } else { let matrix_f = rotate_to_matrix(fx, fy, fz, fa); let matrix_t = rotate_to_matrix(tx, ty, tz, ta); Ok(TransformOperation::Matrix3D( matrix_f.animate(&matrix_t, procedure)?, )) } }, ( &TransformOperation::RotateX(fa), &TransformOperation::RotateX(ta), ) => { Ok(TransformOperation::RotateX( fa.animate(&ta, procedure)? )) }, ( &TransformOperation::RotateY(fa), &TransformOperation::RotateY(ta), ) => { Ok(TransformOperation::RotateY( fa.animate(&ta, procedure)? )) }, ( &TransformOperation::RotateZ(fa), &TransformOperation::RotateZ(ta), ) => { Ok(TransformOperation::RotateZ( fa.animate(&ta, procedure)? )) }, ( &TransformOperation::Rotate(fa), &TransformOperation::Rotate(ta), ) => { Ok(TransformOperation::Rotate( fa.animate(&ta, procedure)? )) }, ( &TransformOperation::Rotate(fa), &TransformOperation::RotateZ(ta), ) => { Ok(TransformOperation::Rotate( fa.animate(&ta, procedure)? )) }, ( &TransformOperation::RotateZ(fa), &TransformOperation::Rotate(ta), ) => { Ok(TransformOperation::Rotate( fa.animate(&ta, procedure)? )) }, ( &TransformOperation::Perspective(ref fd), &TransformOperation::Perspective(ref td), ) => { let mut fd_matrix = Matrix3D::identity(); let mut td_matrix = Matrix3D::identity(); if fd.px() > 0. { fd_matrix.m34 = -1. / fd.px(); } if td.px() > 0. { td_matrix.m34 = -1. / td.px(); } Ok(TransformOperation::Matrix3D( fd_matrix.animate(&td_matrix, procedure)?, )) }, _ if self.is_translate() && other.is_translate() => { self.to_translate_3d().animate(&other.to_translate_3d(), procedure) } _ if self.is_scale() && other.is_scale() => { self.to_scale_3d().animate(&other.to_scale_3d(), procedure) } _ => Err(()), } } } fn is_matched_operation(first: &ComputedTransformOperation, second: &ComputedTransformOperation) -> bool { match (first, second) { (&TransformOperation::Matrix(..), &TransformOperation::Matrix(..)) | (&TransformOperation::Matrix3D(..), &TransformOperation::Matrix3D(..)) | (&TransformOperation::Skew(..), &TransformOperation::Skew(..)) | (&TransformOperation::SkewX(..), &TransformOperation::SkewX(..)) | (&TransformOperation::SkewY(..), &TransformOperation::SkewY(..)) | (&TransformOperation::Rotate(..), &TransformOperation::Rotate(..)) | (&TransformOperation::Rotate3D(..), &TransformOperation::Rotate3D(..)) | (&TransformOperation::RotateX(..), &TransformOperation::RotateX(..)) | (&TransformOperation::RotateY(..), &TransformOperation::RotateY(..)) | (&TransformOperation::RotateZ(..), &TransformOperation::RotateZ(..)) | (&TransformOperation::Perspective(..), &TransformOperation::Perspective(..)) => true, // we animate scale and translate operations against each other (a, b) if a.is_translate() && b.is_translate() => true, (a, b) if a.is_scale() && b.is_scale() => true, // InterpolateMatrix and AccumulateMatrix are for mismatched transform. _ => false } } /// fn rotate_to_matrix(x: f32, y: f32, z: f32, a: Angle) -> Matrix3D { let half_rad = a.radians() / 2.0; let sc = (half_rad).sin() * (half_rad).cos(); let sq = (half_rad).sin().powi(2); Matrix3D { m11: 1.0 - 2.0 * (y * y + z * z) * sq, m12: 2.0 * (x * y * sq + z * sc), m13: 2.0 * (x * z * sq - y * sc), m14: 0.0, m21: 2.0 * (x * y * sq - z * sc), m22: 1.0 - 2.0 * (x * x + z * z) * sq, m23: 2.0 * (y * z * sq + x * sc), m24: 0.0, m31: 2.0 * (x * z * sq + y * sc), m32: 2.0 * (y * z * sq - x * sc), m33: 1.0 - 2.0 * (x * x + y * y) * sq, m34: 0.0, m41: 0.0, m42: 0.0, m43: 0.0, m44: 1.0 } } /// A 2d matrix for interpolation. #[derive(Clone, ComputeSquaredDistance, Copy, Debug)] #[cfg_attr(feature = "servo", derive(MallocSizeOf))] #[allow(missing_docs)] // FIXME: We use custom derive for ComputeSquaredDistance. However, If possible, we should convert // the InnerMatrix2D into types with physical meaning. This custom derive computes the squared // distance from each matrix item, and this makes the result different from that in Gecko if we // have skew factor in the Matrix3D. pub struct InnerMatrix2D { pub m11: CSSFloat, pub m12: CSSFloat, pub m21: CSSFloat, pub m22: CSSFloat, } /// A 2d translation function. #[cfg_attr(feature = "servo", derive(MallocSizeOf))] #[derive(Animate, Clone, ComputeSquaredDistance, Copy, Debug)] pub struct Translate2D(f32, f32); /// A 2d scale function. #[derive(Clone, ComputeSquaredDistance, Copy, Debug)] #[cfg_attr(feature = "servo", derive(MallocSizeOf))] pub struct Scale2D(f32, f32); /// A decomposed 2d matrix. #[derive(Clone, Copy, Debug)] #[cfg_attr(feature = "servo", derive(MallocSizeOf))] pub struct MatrixDecomposed2D { /// The translation function. pub translate: Translate2D, /// The scale function. pub scale: Scale2D, /// The rotation angle. pub angle: f32, /// The inner matrix. pub matrix: InnerMatrix2D, } impl Animate for InnerMatrix2D { fn animate(&self, other: &Self, procedure: Procedure) -> Result { Ok(InnerMatrix2D { m11: animate_multiplicative_factor(self.m11, other.m11, procedure)?, m12: self.m12.animate(&other.m12, procedure)?, m21: self.m21.animate(&other.m21, procedure)?, m22: animate_multiplicative_factor(self.m22, other.m22, procedure)?, }) } } impl Animate for Scale2D { fn animate(&self, other: &Self, procedure: Procedure) -> Result { Ok(Scale2D( animate_multiplicative_factor(self.0, other.0, procedure)?, animate_multiplicative_factor(self.1, other.1, procedure)?, )) } } impl Animate for MatrixDecomposed2D { /// fn animate(&self, other: &Self, procedure: Procedure) -> Result { // If x-axis of one is flipped, and y-axis of the other, // convert to an unflipped rotation. let mut scale = self.scale; let mut angle = self.angle; let mut other_angle = other.angle; if (scale.0 < 0.0 && other.scale.1 < 0.0) || (scale.1 < 0.0 && other.scale.0 < 0.0) { scale.0 = -scale.0; scale.1 = -scale.1; angle += if angle < 0.0 {180.} else {-180.}; } // Don't rotate the long way around. if angle == 0.0 { angle = 360. } if other_angle == 0.0 { other_angle = 360. } if (angle - other_angle).abs() > 180. { if angle > other_angle { angle -= 360. } else{ other_angle -= 360. } } // Interpolate all values. let translate = self.translate.animate(&other.translate, procedure)?; let scale = scale.animate(&other.scale, procedure)?; let angle = angle.animate(&other_angle, procedure)?; let matrix = self.matrix.animate(&other.matrix, procedure)?; Ok(MatrixDecomposed2D { translate: translate, scale: scale, angle: angle, matrix: matrix, }) } } impl ComputeSquaredDistance for MatrixDecomposed2D { #[inline] fn compute_squared_distance(&self, other: &Self) -> Result { // Use Radian to compute the distance. const RAD_PER_DEG: f64 = ::std::f64::consts::PI / 180.0; let angle1 = self.angle as f64 * RAD_PER_DEG; let angle2 = other.angle as f64 * RAD_PER_DEG; Ok(self.translate.compute_squared_distance(&other.translate)? + self.scale.compute_squared_distance(&other.scale)? + angle1.compute_squared_distance(&angle2)? + self.matrix.compute_squared_distance(&other.matrix)?) } } impl Animate for Matrix3D { #[cfg(feature = "servo")] fn animate(&self, other: &Self, procedure: Procedure) -> Result { if self.is_3d() || other.is_3d() { let decomposed_from = decompose_3d_matrix(*self); let decomposed_to = decompose_3d_matrix(*other); match (decomposed_from, decomposed_to) { (Ok(this), Ok(other)) => { Ok(Matrix3D::from(this.animate(&other, procedure)?)) }, // Matrices can be undecomposable due to couple reasons, e.g., // non-invertible matrices. In this case, we should report Err // here, and let the caller do the fallback procedure. _ => Err(()) } } else { let this = MatrixDecomposed2D::from(*self); let other = MatrixDecomposed2D::from(*other); Ok(Matrix3D::from(this.animate(&other, procedure)?)) } } #[cfg(feature = "gecko")] // Gecko doesn't exactly follow the spec here; we use a different procedure // to match it fn animate(&self, other: &Self, procedure: Procedure) -> Result { let (from, to) = if self.is_3d() || other.is_3d() { (decompose_3d_matrix(*self), decompose_3d_matrix(*other)) } else { (decompose_2d_matrix(self), decompose_2d_matrix(other)) }; match (from, to) { (Ok(from), Ok(to)) => { Ok(Matrix3D::from(from.animate(&to, procedure)?)) }, // Matrices can be undecomposable due to couple reasons, e.g., // non-invertible matrices. In this case, we should report Err here, // and let the caller do the fallback procedure. _ => Err(()) } } } impl Animate for Matrix { #[cfg(feature = "servo")] fn animate(&self, other: &Self, procedure: Procedure) -> Result { let this = Matrix3D::from(*self); let other = Matrix3D::from(*other); let this = MatrixDecomposed2D::from(this); let other = MatrixDecomposed2D::from(other); Ok(Matrix3D::from(this.animate(&other, procedure)?).into_2d()?) } #[cfg(feature = "gecko")] // Gecko doesn't exactly follow the spec here; we use a different procedure // to match it fn animate(&self, other: &Self, procedure: Procedure) -> Result { let from = decompose_2d_matrix(&(*self).into()); let to = decompose_2d_matrix(&(*other).into()); match (from, to) { (Ok(from), Ok(to)) => { Matrix3D::from(from.animate(&to, procedure)?).into_2d() }, // Matrices can be undecomposable due to couple reasons, e.g., // non-invertible matrices. In this case, we should report Err here, // and let the caller do the fallback procedure. _ => Err(()) } } } impl ComputeSquaredDistance for Matrix3D { #[inline] #[cfg(feature = "servo")] fn compute_squared_distance(&self, other: &Self) -> Result { if self.is_3d() || other.is_3d() { let from = decompose_3d_matrix(*self)?; let to = decompose_3d_matrix(*other)?; from.compute_squared_distance(&to) } else { let from = MatrixDecomposed2D::from(*self); let to = MatrixDecomposed2D::from(*other); from.compute_squared_distance(&to) } } #[inline] #[cfg(feature = "gecko")] fn compute_squared_distance(&self, other: &Self) -> Result { let (from, to) = if self.is_3d() || other.is_3d() { (decompose_3d_matrix(*self)?, decompose_3d_matrix(*other)?) } else { (decompose_2d_matrix(self)?, decompose_2d_matrix(other)?) }; from.compute_squared_distance(&to) } } impl From for MatrixDecomposed2D { /// Decompose a 2D matrix. /// fn from(matrix: Matrix3D) -> MatrixDecomposed2D { let mut row0x = matrix.m11; let mut row0y = matrix.m12; let mut row1x = matrix.m21; let mut row1y = matrix.m22; let translate = Translate2D(matrix.m41, matrix.m42); let mut scale = Scale2D((row0x * row0x + row0y * row0y).sqrt(), (row1x * row1x + row1y * row1y).sqrt()); // If determinant is negative, one axis was flipped. let determinant = row0x * row1y - row0y * row1x; if determinant < 0. { if row0x < row1y { scale.0 = -scale.0; } else { scale.1 = -scale.1; } } // Renormalize matrix to remove scale. if scale.0 != 0.0 { row0x *= 1. / scale.0; row0y *= 1. / scale.0; } if scale.1 != 0.0 { row1x *= 1. / scale.1; row1y *= 1. / scale.1; } // Compute rotation and renormalize matrix. let mut angle = row0y.atan2(row0x); if angle != 0.0 { let sn = -row0y; let cs = row0x; let m11 = row0x; let m12 = row0y; let m21 = row1x; let m22 = row1y; row0x = cs * m11 + sn * m21; row0y = cs * m12 + sn * m22; row1x = -sn * m11 + cs * m21; row1y = -sn * m12 + cs * m22; } let m = InnerMatrix2D { m11: row0x, m12: row0y, m21: row1x, m22: row1y, }; // Convert into degrees because our rotation functions expect it. angle = angle.to_degrees(); MatrixDecomposed2D { translate: translate, scale: scale, angle: angle, matrix: m, } } } impl From for Matrix3D { /// Recompose a 2D matrix. /// fn from(decomposed: MatrixDecomposed2D) -> Matrix3D { let mut computed_matrix = Matrix3D::identity(); computed_matrix.m11 = decomposed.matrix.m11; computed_matrix.m12 = decomposed.matrix.m12; computed_matrix.m21 = decomposed.matrix.m21; computed_matrix.m22 = decomposed.matrix.m22; // Translate matrix. computed_matrix.m41 = decomposed.translate.0; computed_matrix.m42 = decomposed.translate.1; // Rotate matrix. let angle = decomposed.angle.to_radians(); let cos_angle = angle.cos(); let sin_angle = angle.sin(); let mut rotate_matrix = Matrix3D::identity(); rotate_matrix.m11 = cos_angle; rotate_matrix.m12 = sin_angle; rotate_matrix.m21 = -sin_angle; rotate_matrix.m22 = cos_angle; // Multiplication of computed_matrix and rotate_matrix computed_matrix = multiply(rotate_matrix, computed_matrix); // Scale matrix. computed_matrix.m11 *= decomposed.scale.0; computed_matrix.m12 *= decomposed.scale.0; computed_matrix.m21 *= decomposed.scale.1; computed_matrix.m22 *= decomposed.scale.1; computed_matrix } } #[cfg(feature = "gecko")] impl<'a> From< &'a RawGeckoGfxMatrix4x4> for Matrix3D { fn from(m: &'a RawGeckoGfxMatrix4x4) -> Matrix3D { Matrix3D { m11: m[0], m12: m[1], m13: m[2], m14: m[3], m21: m[4], m22: m[5], m23: m[6], m24: m[7], m31: m[8], m32: m[9], m33: m[10], m34: m[11], m41: m[12], m42: m[13], m43: m[14], m44: m[15], } } } #[cfg(feature = "gecko")] impl From for RawGeckoGfxMatrix4x4 { fn from(matrix: Matrix3D) -> RawGeckoGfxMatrix4x4 { [ matrix.m11, matrix.m12, matrix.m13, matrix.m14, matrix.m21, matrix.m22, matrix.m23, matrix.m24, matrix.m31, matrix.m32, matrix.m33, matrix.m34, matrix.m41, matrix.m42, matrix.m43, matrix.m44 ] } } /// A 3d translation. #[cfg_attr(feature = "servo", derive(MallocSizeOf))] #[derive(Animate, Clone, ComputeSquaredDistance, Copy, Debug)] pub struct Translate3D(f32, f32, f32); /// A 3d scale function. #[derive(Clone, ComputeSquaredDistance, Copy, Debug)] #[cfg_attr(feature = "servo", derive(MallocSizeOf))] pub struct Scale3D(f32, f32, f32); /// A 3d skew function. #[cfg_attr(feature = "servo", derive(MallocSizeOf))] #[derive(Animate, Clone, Copy, Debug)] pub struct Skew(f32, f32, f32); /// A 3d perspective transformation. #[derive(Clone, ComputeSquaredDistance, Copy, Debug)] #[cfg_attr(feature = "servo", derive(MallocSizeOf))] pub struct Perspective(f32, f32, f32, f32); /// A quaternion used to represent a rotation. #[derive(Clone, Copy, Debug)] #[cfg_attr(feature = "servo", derive(MallocSizeOf))] pub struct Quaternion(f64, f64, f64, f64); /// A decomposed 3d matrix. #[derive(Animate, Clone, ComputeSquaredDistance, Copy, Debug)] #[cfg_attr(feature = "servo", derive(MallocSizeOf))] pub struct MatrixDecomposed3D { /// A translation function. pub translate: Translate3D, /// A scale function. pub scale: Scale3D, /// The skew component of the transformation. pub skew: Skew, /// The perspective component of the transformation. pub perspective: Perspective, /// The quaternion used to represent the rotation. pub quaternion: Quaternion, } impl Quaternion { /// Return a quaternion from a unit direction vector and angle (unit: radian). #[inline] fn from_direction_and_angle(vector: &DirectionVector, angle: f64) -> Self { debug_assert!((vector.length() - 1.).abs() < 0.0001, "Only accept an unit direction vector to create a quaternion"); // Reference: // https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation // // if the direction axis is (x, y, z) = xi + yj + zk, // and the angle is |theta|, this formula can be done using // an extension of Euler's formula: // q = cos(theta/2) + (xi + yj + zk)(sin(theta/2)) // = cos(theta/2) + // x*sin(theta/2)i + y*sin(theta/2)j + z*sin(theta/2)k Quaternion(vector.x as f64 * (angle / 2.).sin(), vector.y as f64 * (angle / 2.).sin(), vector.z as f64 * (angle / 2.).sin(), (angle / 2.).cos()) } /// Calculate the dot product. #[inline] fn dot(&self, other: &Self) -> f64 { self.0 * other.0 + self.1 * other.1 + self.2 * other.2 + self.3 * other.3 } } impl Animate for Quaternion { fn animate(&self, other: &Self, procedure: Procedure) -> Result { use std::f64; let (this_weight, other_weight) = procedure.weights(); debug_assert!((this_weight + other_weight - 1.0f64).abs() <= f64::EPSILON || other_weight == 1.0f64 || other_weight == 0.0f64, "animate should only be used for interpolating or accumulating transforms"); // We take a specialized code path for accumulation (where other_weight is 1) if other_weight == 1.0 { if this_weight == 0.0 { return Ok(*other); } let clamped_w = self.3.min(1.0).max(-1.0); // Determine the scale factor. let mut theta = clamped_w.acos(); let mut scale = if theta == 0.0 { 0.0 } else { 1.0 / theta.sin() }; theta *= this_weight; scale *= theta.sin(); // Scale the self matrix by this_weight. let mut scaled_self = *self; % for i in range(3): scaled_self.${i} *= scale; % endfor scaled_self.3 = theta.cos(); // Multiply scaled-self by other. let a = &scaled_self; let b = other; return Ok(Quaternion( a.3 * b.0 + a.0 * b.3 + a.1 * b.2 - a.2 * b.1, a.3 * b.1 - a.0 * b.2 + a.1 * b.3 + a.2 * b.0, a.3 * b.2 + a.0 * b.1 - a.1 * b.0 + a.2 * b.3, a.3 * b.3 - a.0 * b.0 - a.1 * b.1 - a.2 * b.2, )); } let mut product = self.0 * other.0 + self.1 * other.1 + self.2 * other.2 + self.3 * other.3; // Clamp product to -1.0 <= product <= 1.0 product = product.min(1.0); product = product.max(-1.0); if product == 1.0 { return Ok(*self); } let theta = product.acos(); let w = (other_weight * theta).sin() * 1.0 / (1.0 - product * product).sqrt(); let mut a = *self; let mut b = *other; let mut result = Quaternion(0., 0., 0., 0.,); % for i in range(4): a.${i} *= (other_weight * theta).cos() - product * w; b.${i} *= w; result.${i} = a.${i} + b.${i}; % endfor Ok(result) } } impl ComputeSquaredDistance for Quaternion { #[inline] fn compute_squared_distance(&self, other: &Self) -> Result { // Use quaternion vectors to get the angle difference. Both q1 and q2 are unit vectors, // so we can get their angle difference by: // cos(theta/2) = (q1 dot q2) / (|q1| * |q2|) = q1 dot q2. let distance = self.dot(other).max(-1.0).min(1.0).acos() * 2.0; Ok(SquaredDistance::Value(distance * distance)) } } /// Decompose a 3D matrix. /// fn decompose_3d_matrix(mut matrix: Matrix3D) -> Result { // Normalize the matrix. if matrix.m44 == 0.0 { return Err(()); } let scaling_factor = matrix.m44; % for i in range(1, 5): % for j in range(1, 5): matrix.m${i}${j} /= scaling_factor; % endfor % endfor // perspective_matrix is used to solve for perspective, but it also provides // an easy way to test for singularity of the upper 3x3 component. let mut perspective_matrix = matrix; % for i in range(1, 4): perspective_matrix.m${i}4 = 0.0; % endfor perspective_matrix.m44 = 1.0; if perspective_matrix.determinant() == 0.0 { return Err(()); } // First, isolate perspective. let perspective = if matrix.m14 != 0.0 || matrix.m24 != 0.0 || matrix.m34 != 0.0 { let right_hand_side: [f32; 4] = [ matrix.m14, matrix.m24, matrix.m34, matrix.m44 ]; perspective_matrix = perspective_matrix.inverse().unwrap(); // Transpose perspective_matrix perspective_matrix = Matrix3D { % for i in range(1, 5): % for j in range(1, 5): m${i}${j}: perspective_matrix.m${j}${i}, % endfor % endfor }; // Multiply right_hand_side with perspective_matrix let mut tmp: [f32; 4] = [0.0; 4]; % for i in range(1, 5): tmp[${i - 1}] = (right_hand_side[0] * perspective_matrix.m1${i}) + (right_hand_side[1] * perspective_matrix.m2${i}) + (right_hand_side[2] * perspective_matrix.m3${i}) + (right_hand_side[3] * perspective_matrix.m4${i}); % endfor Perspective(tmp[0], tmp[1], tmp[2], tmp[3]) } else { Perspective(0.0, 0.0, 0.0, 1.0) }; // Next take care of translation let translate = Translate3D ( matrix.m41, matrix.m42, matrix.m43 ); // Now get scale and shear. 'row' is a 3 element array of 3 component vectors let mut row: [[f32; 3]; 3] = [[0.0; 3]; 3]; % for i in range(1, 4): row[${i - 1}][0] = matrix.m${i}1; row[${i - 1}][1] = matrix.m${i}2; row[${i - 1}][2] = matrix.m${i}3; % endfor // Compute X scale factor and normalize first row. let row0len = (row[0][0] * row[0][0] + row[0][1] * row[0][1] + row[0][2] * row[0][2]).sqrt(); let mut scale = Scale3D(row0len, 0.0, 0.0); row[0] = [row[0][0] / row0len, row[0][1] / row0len, row[0][2] / row0len]; // Compute XY shear factor and make 2nd row orthogonal to 1st. let mut skew = Skew(dot(row[0], row[1]), 0.0, 0.0); row[1] = combine(row[1], row[0], 1.0, -skew.0); // Now, compute Y scale and normalize 2nd row. let row1len = (row[1][0] * row[1][0] + row[1][1] * row[1][1] + row[1][2] * row[1][2]).sqrt(); scale.1 = row1len; row[1] = [row[1][0] / row1len, row[1][1] / row1len, row[1][2] / row1len]; skew.0 /= scale.1; // Compute XZ and YZ shears, orthogonalize 3rd row skew.1 = dot(row[0], row[2]); row[2] = combine(row[2], row[0], 1.0, -skew.1); skew.2 = dot(row[1], row[2]); row[2] = combine(row[2], row[1], 1.0, -skew.2); // Next, get Z scale and normalize 3rd row. let row2len = (row[2][0] * row[2][0] + row[2][1] * row[2][1] + row[2][2] * row[2][2]).sqrt(); scale.2 = row2len; row[2] = [row[2][0] / row2len, row[2][1] / row2len, row[2][2] / row2len]; skew.1 /= scale.2; skew.2 /= scale.2; // At this point, the matrix (in rows) is orthonormal. // Check for a coordinate system flip. If the determinant // is -1, then negate the matrix and the scaling factors. let pdum3 = cross(row[1], row[2]); if dot(row[0], pdum3) < 0.0 { % for i in range(3): scale.${i} *= -1.0; row[${i}][0] *= -1.0; row[${i}][1] *= -1.0; row[${i}][2] *= -1.0; % endfor } // Now, get the rotations out let mut quaternion = Quaternion ( 0.5 * ((1.0 + row[0][0] - row[1][1] - row[2][2]).max(0.0) as f64).sqrt(), 0.5 * ((1.0 - row[0][0] + row[1][1] - row[2][2]).max(0.0) as f64).sqrt(), 0.5 * ((1.0 - row[0][0] - row[1][1] + row[2][2]).max(0.0) as f64).sqrt(), 0.5 * ((1.0 + row[0][0] + row[1][1] + row[2][2]).max(0.0) as f64).sqrt() ); if row[2][1] > row[1][2] { quaternion.0 = -quaternion.0 } if row[0][2] > row[2][0] { quaternion.1 = -quaternion.1 } if row[1][0] > row[0][1] { quaternion.2 = -quaternion.2 } Ok(MatrixDecomposed3D { translate: translate, scale: scale, skew: skew, perspective: perspective, quaternion: quaternion }) } /// Decompose a 2D matrix for Gecko. // Use the algorithm from nsStyleTransformMatrix::Decompose2DMatrix() in Gecko. #[cfg(feature = "gecko")] fn decompose_2d_matrix(matrix: &Matrix3D) -> Result { // The index is column-major, so the equivalent transform matrix is: // | m11 m21 0 m41 | => | m11 m21 | and translate(m41, m42) // | m12 m22 0 m42 | | m12 m22 | // | 0 0 1 0 | // | 0 0 0 1 | let (mut m11, mut m12) = (matrix.m11, matrix.m12); let (mut m21, mut m22) = (matrix.m21, matrix.m22); // Check if this is a singular matrix. if m11 * m22 == m12 * m21 { return Err(()); } let mut scale_x = (m11 * m11 + m12 * m12).sqrt(); m11 /= scale_x; m12 /= scale_x; let mut shear_xy = m11 * m21 + m12 * m22; m21 -= m11 * shear_xy; m22 -= m12 * shear_xy; let scale_y = (m21 * m21 + m22 * m22).sqrt(); m21 /= scale_y; m22 /= scale_y; shear_xy /= scale_y; let determinant = m11 * m22 - m12 * m21; // Determinant should now be 1 or -1. if 0.99 > determinant.abs() || determinant.abs() > 1.01 { return Err(()); } if determinant < 0. { m11 = -m11; m12 = -m12; shear_xy = -shear_xy; scale_x = -scale_x; } Ok(MatrixDecomposed3D { translate: Translate3D(matrix.m41, matrix.m42, 0.), scale: Scale3D(scale_x, scale_y, 1.), skew: Skew(shear_xy, 0., 0.), perspective: Perspective(0., 0., 0., 1.), quaternion: Quaternion::from_direction_and_angle(&DirectionVector::new(0., 0., 1.), m12.atan2(m11) as f64) }) } // Combine 2 point. fn combine(a: [f32; 3], b: [f32; 3], ascl: f32, bscl: f32) -> [f32; 3] { [ (ascl * a[0]) + (bscl * b[0]), (ascl * a[1]) + (bscl * b[1]), (ascl * a[2]) + (bscl * b[2]) ] } // Dot product. fn dot(a: [f32; 3], b: [f32; 3]) -> f32 { a[0] * b[0] + a[1] * b[1] + a[2] * b[2] } // Cross product. fn cross(row1: [f32; 3], row2: [f32; 3]) -> [f32; 3] { [ row1[1] * row2[2] - row1[2] * row2[1], row1[2] * row2[0] - row1[0] * row2[2], row1[0] * row2[1] - row1[1] * row2[0] ] } impl Animate for Scale3D { fn animate(&self, other: &Self, procedure: Procedure) -> Result { Ok(Scale3D( animate_multiplicative_factor(self.0, other.0, procedure)?, animate_multiplicative_factor(self.1, other.1, procedure)?, animate_multiplicative_factor(self.2, other.2, procedure)?, )) } } impl ComputeSquaredDistance for Skew { // We have to use atan() to convert the skew factors into skew angles, so implement // ComputeSquaredDistance manually. #[inline] fn compute_squared_distance(&self, other: &Self) -> Result { Ok(self.0.atan().compute_squared_distance(&other.0.atan())? + self.1.atan().compute_squared_distance(&other.1.atan())? + self.2.atan().compute_squared_distance(&other.2.atan())?) } } impl Animate for Perspective { fn animate(&self, other: &Self, procedure: Procedure) -> Result { Ok(Perspective( self.0.animate(&other.0, procedure)?, self.1.animate(&other.1, procedure)?, self.2.animate(&other.2, procedure)?, animate_multiplicative_factor(self.3, other.3, procedure)?, )) } } impl From for Matrix3D { /// Recompose a 3D matrix. /// fn from(decomposed: MatrixDecomposed3D) -> Matrix3D { let mut matrix = Matrix3D::identity(); // Apply perspective % for i in range(1, 5): matrix.m${i}4 = decomposed.perspective.${i - 1}; % endfor // Apply translation % for i in range(1, 4): % for j in range(1, 4): matrix.m4${i} += decomposed.translate.${j - 1} * matrix.m${j}${i}; % endfor % endfor // Apply rotation let x = decomposed.quaternion.0; let y = decomposed.quaternion.1; let z = decomposed.quaternion.2; let w = decomposed.quaternion.3; // Construct a composite rotation matrix from the quaternion values // rotationMatrix is a identity 4x4 matrix initially let mut rotation_matrix = Matrix3D::identity(); rotation_matrix.m11 = 1.0 - 2.0 * (y * y + z * z) as f32; rotation_matrix.m12 = 2.0 * (x * y + z * w) as f32; rotation_matrix.m13 = 2.0 * (x * z - y * w) as f32; rotation_matrix.m21 = 2.0 * (x * y - z * w) as f32; rotation_matrix.m22 = 1.0 - 2.0 * (x * x + z * z) as f32; rotation_matrix.m23 = 2.0 * (y * z + x * w) as f32; rotation_matrix.m31 = 2.0 * (x * z + y * w) as f32; rotation_matrix.m32 = 2.0 * (y * z - x * w) as f32; rotation_matrix.m33 = 1.0 - 2.0 * (x * x + y * y) as f32; matrix = multiply(rotation_matrix, matrix); // Apply skew let mut temp = Matrix3D::identity(); if decomposed.skew.2 != 0.0 { temp.m32 = decomposed.skew.2; matrix = multiply(temp, matrix); } if decomposed.skew.1 != 0.0 { temp.m32 = 0.0; temp.m31 = decomposed.skew.1; matrix = multiply(temp, matrix); } if decomposed.skew.0 != 0.0 { temp.m31 = 0.0; temp.m21 = decomposed.skew.0; matrix = multiply(temp, matrix); } // Apply scale % for i in range(1, 4): % for j in range(1, 4): matrix.m${i}${j} *= decomposed.scale.${i - 1}; % endfor % endfor matrix } } // Multiplication of two 4x4 matrices. fn multiply(a: Matrix3D, b: Matrix3D) -> Matrix3D { let mut a_clone = a; % for i in range(1, 5): % for j in range(1, 5): a_clone.m${i}${j} = (a.m${i}1 * b.m1${j}) + (a.m${i}2 * b.m2${j}) + (a.m${i}3 * b.m3${j}) + (a.m${i}4 * b.m4${j}); % endfor % endfor a_clone } impl Matrix3D { fn is_3d(&self) -> bool { self.m13 != 0.0 || self.m14 != 0.0 || self.m23 != 0.0 || self.m24 != 0.0 || self.m31 != 0.0 || self.m32 != 0.0 || self.m33 != 1.0 || self.m34 != 0.0 || self.m43 != 0.0 || self.m44 != 1.0 } fn determinant(&self) -> CSSFloat { self.m14 * self.m23 * self.m32 * self.m41 - self.m13 * self.m24 * self.m32 * self.m41 - self.m14 * self.m22 * self.m33 * self.m41 + self.m12 * self.m24 * self.m33 * self.m41 + self.m13 * self.m22 * self.m34 * self.m41 - self.m12 * self.m23 * self.m34 * self.m41 - self.m14 * self.m23 * self.m31 * self.m42 + self.m13 * self.m24 * self.m31 * self.m42 + self.m14 * self.m21 * self.m33 * self.m42 - self.m11 * self.m24 * self.m33 * self.m42 - self.m13 * self.m21 * self.m34 * self.m42 + self.m11 * self.m23 * self.m34 * self.m42 + self.m14 * self.m22 * self.m31 * self.m43 - self.m12 * self.m24 * self.m31 * self.m43 - self.m14 * self.m21 * self.m32 * self.m43 + self.m11 * self.m24 * self.m32 * self.m43 + self.m12 * self.m21 * self.m34 * self.m43 - self.m11 * self.m22 * self.m34 * self.m43 - self.m13 * self.m22 * self.m31 * self.m44 + self.m12 * self.m23 * self.m31 * self.m44 + self.m13 * self.m21 * self.m32 * self.m44 - self.m11 * self.m23 * self.m32 * self.m44 - self.m12 * self.m21 * self.m33 * self.m44 + self.m11 * self.m22 * self.m33 * self.m44 } fn inverse(&self) -> Option { let mut det = self.determinant(); if det == 0.0 { return None; } det = 1.0 / det; let x = Matrix3D { m11: det * (self.m23*self.m34*self.m42 - self.m24*self.m33*self.m42 + self.m24*self.m32*self.m43 - self.m22*self.m34*self.m43 - self.m23*self.m32*self.m44 + self.m22*self.m33*self.m44), m12: det * (self.m14*self.m33*self.m42 - self.m13*self.m34*self.m42 - self.m14*self.m32*self.m43 + self.m12*self.m34*self.m43 + self.m13*self.m32*self.m44 - self.m12*self.m33*self.m44), m13: det * (self.m13*self.m24*self.m42 - self.m14*self.m23*self.m42 + self.m14*self.m22*self.m43 - self.m12*self.m24*self.m43 - self.m13*self.m22*self.m44 + self.m12*self.m23*self.m44), m14: det * (self.m14*self.m23*self.m32 - self.m13*self.m24*self.m32 - self.m14*self.m22*self.m33 + self.m12*self.m24*self.m33 + self.m13*self.m22*self.m34 - self.m12*self.m23*self.m34), m21: det * (self.m24*self.m33*self.m41 - self.m23*self.m34*self.m41 - self.m24*self.m31*self.m43 + self.m21*self.m34*self.m43 + self.m23*self.m31*self.m44 - self.m21*self.m33*self.m44), m22: det * (self.m13*self.m34*self.m41 - self.m14*self.m33*self.m41 + self.m14*self.m31*self.m43 - self.m11*self.m34*self.m43 - self.m13*self.m31*self.m44 + self.m11*self.m33*self.m44), m23: det * (self.m14*self.m23*self.m41 - self.m13*self.m24*self.m41 - self.m14*self.m21*self.m43 + self.m11*self.m24*self.m43 + self.m13*self.m21*self.m44 - self.m11*self.m23*self.m44), m24: det * (self.m13*self.m24*self.m31 - self.m14*self.m23*self.m31 + self.m14*self.m21*self.m33 - self.m11*self.m24*self.m33 - self.m13*self.m21*self.m34 + self.m11*self.m23*self.m34), m31: det * (self.m22*self.m34*self.m41 - self.m24*self.m32*self.m41 + self.m24*self.m31*self.m42 - self.m21*self.m34*self.m42 - self.m22*self.m31*self.m44 + self.m21*self.m32*self.m44), m32: det * (self.m14*self.m32*self.m41 - self.m12*self.m34*self.m41 - self.m14*self.m31*self.m42 + self.m11*self.m34*self.m42 + self.m12*self.m31*self.m44 - self.m11*self.m32*self.m44), m33: det * (self.m12*self.m24*self.m41 - self.m14*self.m22*self.m41 + self.m14*self.m21*self.m42 - self.m11*self.m24*self.m42 - self.m12*self.m21*self.m44 + self.m11*self.m22*self.m44), m34: det * (self.m14*self.m22*self.m31 - self.m12*self.m24*self.m31 - self.m14*self.m21*self.m32 + self.m11*self.m24*self.m32 + self.m12*self.m21*self.m34 - self.m11*self.m22*self.m34), m41: det * (self.m23*self.m32*self.m41 - self.m22*self.m33*self.m41 - self.m23*self.m31*self.m42 + self.m21*self.m33*self.m42 + self.m22*self.m31*self.m43 - self.m21*self.m32*self.m43), m42: det * (self.m12*self.m33*self.m41 - self.m13*self.m32*self.m41 + self.m13*self.m31*self.m42 - self.m11*self.m33*self.m42 - self.m12*self.m31*self.m43 + self.m11*self.m32*self.m43), m43: det * (self.m13*self.m22*self.m41 - self.m12*self.m23*self.m41 - self.m13*self.m21*self.m42 + self.m11*self.m23*self.m42 + self.m12*self.m21*self.m43 - self.m11*self.m22*self.m43), m44: det * (self.m12*self.m23*self.m31 - self.m13*self.m22*self.m31 + self.m13*self.m21*self.m32 - self.m11*self.m23*self.m32 - self.m12*self.m21*self.m33 + self.m11*self.m22*self.m33), }; Some(x) } } /// impl ComputedRotate { fn fill_unspecified(rotate: &ComputedRotate) -> Result<(Number, Number, Number, Angle), ()> { // According to the spec: // https://drafts.csswg.org/css-transforms-2/#individual-transforms // // If the axis is unspecified, it defaults to "0 0 1" match *rotate { Rotate::None => Ok((0., 0., 1., Angle::zero())), Rotate::Rotate3D(rx, ry, rz, angle) => Ok((rx, ry, rz, angle)), Rotate::Rotate(angle) => Ok((0., 0., 1., angle)), } } } impl Animate for ComputedRotate { #[inline] fn animate( &self, other: &Self, procedure: Procedure, ) -> Result { let from = ComputedRotate::fill_unspecified(self)?; let to = ComputedRotate::fill_unspecified(other)?; let (fx, fy, fz, fa) = transform::get_normalized_vector_and_angle(from.0, from.1, from.2, from.3); let (tx, ty, tz, ta) = transform::get_normalized_vector_and_angle(to.0, to.1, to.2, to.3); if (fx, fy, fz) == (tx, ty, tz) { return Ok(Rotate::Rotate3D(fx, fy, fz, fa.animate(&ta, procedure)?)); } let fv = DirectionVector::new(fx, fy, fz); let tv = DirectionVector::new(tx, ty, tz); let fq = Quaternion::from_direction_and_angle(&fv, fa.radians64()); let tq = Quaternion::from_direction_and_angle(&tv, ta.radians64()); let rq = Quaternion::animate(&fq, &tq, procedure)?; let (x, y, z, angle) = transform::get_normalized_vector_and_angle(rq.0 as f32, rq.1 as f32, rq.2 as f32, rq.3.acos() as f32 *2.0); Ok(Rotate::Rotate3D(x, y, z, Angle::from_radians(angle))) } } /// impl ComputedTranslate { fn fill_unspecified(translate: &ComputedTranslate) -> Result<(LengthOrPercentage, LengthOrPercentage, Length), ()> { // According to the spec: // https://drafts.csswg.org/css-transforms-2/#individual-transforms // // Unspecified translations default to 0px match *translate { Translate::None => { Ok((LengthOrPercentage::Length(Length::zero()), LengthOrPercentage::Length(Length::zero()), Length::zero())) }, Translate::Translate3D(tx, ty, tz) => Ok((tx, ty, tz)), Translate::Translate(tx, ty) => Ok((tx, ty, Length::zero())), Translate::TranslateX(tx) => Ok((tx, LengthOrPercentage::Length(Length::zero()), Length::zero())), } } } impl Animate for ComputedTranslate { #[inline] fn animate( &self, other: &Self, procedure: Procedure, ) -> Result { let from = ComputedTranslate::fill_unspecified(self)?; let to = ComputedTranslate::fill_unspecified(other)?; Ok(Translate::Translate3D(from.0.animate(&to.0, procedure)?, from.1.animate(&to.1, procedure)?, from.2.animate(&to.2, procedure)?)) } } /// impl ComputedScale { fn fill_unspecified(scale: &ComputedScale) -> Result<(Number, Number, Number), ()> { // According to the spec: // https://drafts.csswg.org/css-transforms-2/#individual-transforms // // Unspecified scales default to 1 match *scale { Scale::None => Ok((1.0, 1.0, 1.0)), Scale::Scale3D(sx, sy, sz) => Ok((sx, sy, sz)), Scale::Scale(sx, sy) => Ok((sx, sy, 1.)), Scale::ScaleX(sx) => Ok((sx, 1., 1.)), } } } impl Animate for ComputedScale { #[inline] fn animate( &self, other: &Self, procedure: Procedure, ) -> Result { let from = ComputedScale::fill_unspecified(self)?; let to = ComputedScale::fill_unspecified(other)?; if procedure == Procedure::Add { // scale(x1,y1,z1)*scale(x2,y2,z2) = scale(x1*x2, y1*y2, z1*z2) return Ok(Scale::Scale3D(from.0 * to.0, from.1 * to.1, from.2 * to.2)); } Ok(Scale::Scale3D(animate_multiplicative_factor(from.0, to.0, procedure)?, animate_multiplicative_factor(from.1, to.1, procedure)?, animate_multiplicative_factor(from.2, to.2, procedure)?)) } } /// impl Animate for ComputedTransform { #[inline] fn animate( &self, other_: &Self, procedure: Procedure, ) -> Result { let animate_equal_lists = |this: &[ComputedTransformOperation], other: &[ComputedTransformOperation]| -> Result { Ok(Transform(this.iter().zip(other) .map(|(this, other)| this.animate(other, procedure)) .collect::, _>>()?)) // If we can't animate for a pair of matched transform lists // this means we have at least one undecomposable matrix, // so we should bubble out Err here, and let the caller do // the fallback procedure. }; if self.0.is_empty() && other_.0.is_empty() { return Ok(Transform(vec![])); } let this = &self.0; let other = &other_.0; if procedure == Procedure::Add { let result = this.iter().chain(other).cloned().collect::>(); return Ok(Transform(result)); } // For matched transform lists. { if this.len() == other.len() { let is_matched_transforms = this.iter().zip(other).all(|(this, other)| { is_matched_operation(this, other) }); if is_matched_transforms { return animate_equal_lists(this, other); } } } // For mismatched transform lists. let mut owned_this = this.clone(); let mut owned_other = other.clone(); if this.is_empty() { let this = other_.to_animated_zero()?.0; if this.iter().zip(other).all(|(this, other)| is_matched_operation(this, other)) { return animate_equal_lists(&this, other) } owned_this = this; } if other.is_empty() { let other = self.to_animated_zero()?.0; if this.iter().zip(&other).all(|(this, other)| is_matched_operation(this, other)) { return animate_equal_lists(this, &other) } owned_other = other; } match procedure { Procedure::Add => Err(()), Procedure::Interpolate { progress } => { Ok(Transform(vec![TransformOperation::InterpolateMatrix { from_list: Transform(owned_this), to_list: Transform(owned_other), progress: Percentage(progress as f32), }])) }, Procedure::Accumulate { count } => { Ok(Transform(vec![TransformOperation::AccumulateMatrix { from_list: Transform(owned_this), to_list: Transform(owned_other), count: cmp::min(count, i32::max_value() as u64) as i32, }])) }, } } } // This might not be the most useful definition of distance. It might be better, for example, // to trace the distance travelled by a point as its transform is interpolated between the two // lists. That, however, proves to be quite complicated so we take a simple approach for now. // See https://bugzilla.mozilla.org/show_bug.cgi?id=1318591#c0. impl ComputeSquaredDistance for ComputedTransformOperation { fn compute_squared_distance(&self, other: &Self) -> Result { // For translate, We don't want to require doing layout in order to calculate the result, so // drop the percentage part. However, dropping percentage makes us impossible to // compute the distance for the percentage-percentage case, but Gecko uses the // same formula, so it's fine for now. // Note: We use pixel value to compute the distance for translate, so we have to // convert Au into px. let extract_pixel_length = |lop: &LengthOrPercentage| { match *lop { LengthOrPercentage::Length(px) => px.px(), LengthOrPercentage::Percentage(_) => 0., LengthOrPercentage::Calc(calc) => calc.length().px(), } }; match (self, other) { ( &TransformOperation::Matrix3D(ref this), &TransformOperation::Matrix3D(ref other), ) => { this.compute_squared_distance(other) }, ( &TransformOperation::Matrix(ref this), &TransformOperation::Matrix(ref other), ) => { let this: Matrix3D = (*this).into(); let other: Matrix3D = (*other).into(); this.compute_squared_distance(&other) }, ( &TransformOperation::Skew(ref fx, ref fy), &TransformOperation::Skew(ref tx, ref ty), ) => { Ok( fx.compute_squared_distance(&tx)? + fy.compute_squared_distance(&ty)?, ) }, ( &TransformOperation::SkewX(ref f), &TransformOperation::SkewX(ref t), ) | ( &TransformOperation::SkewY(ref f), &TransformOperation::SkewY(ref t), ) => { f.compute_squared_distance(&t) }, ( &TransformOperation::Translate3D(ref fx, ref fy, ref fz), &TransformOperation::Translate3D(ref tx, ref ty, ref tz), ) => { let fx = extract_pixel_length(&fx); let fy = extract_pixel_length(&fy); let tx = extract_pixel_length(&tx); let ty = extract_pixel_length(&ty); Ok( fx.compute_squared_distance(&tx)? + fy.compute_squared_distance(&ty)? + fz.compute_squared_distance(&tz)?, ) }, ( &TransformOperation::Scale3D(ref fx, ref fy, ref fz), &TransformOperation::Scale3D(ref tx, ref ty, ref tz), ) => { Ok( fx.compute_squared_distance(&tx)? + fy.compute_squared_distance(&ty)? + fz.compute_squared_distance(&tz)?, ) }, ( &TransformOperation::Rotate3D(fx, fy, fz, fa), &TransformOperation::Rotate3D(tx, ty, tz, ta), ) => { let (fx, fy, fz, angle1) = transform::get_normalized_vector_and_angle(fx, fy, fz, fa); let (tx, ty, tz, angle2) = transform::get_normalized_vector_and_angle(tx, ty, tz, ta); if (fx, fy, fz) == (tx, ty, tz) { angle1.compute_squared_distance(&angle2) } else { let v1 = DirectionVector::new(fx, fy, fz); let v2 = DirectionVector::new(tx, ty, tz); let q1 = Quaternion::from_direction_and_angle(&v1, angle1.radians64()); let q2 = Quaternion::from_direction_and_angle(&v2, angle2.radians64()); q1.compute_squared_distance(&q2) } } ( &TransformOperation::RotateX(fa), &TransformOperation::RotateX(ta), ) | ( &TransformOperation::RotateY(fa), &TransformOperation::RotateY(ta), ) | ( &TransformOperation::RotateZ(fa), &TransformOperation::RotateZ(ta), ) | ( &TransformOperation::Rotate(fa), &TransformOperation::Rotate(ta), ) => { fa.compute_squared_distance(&ta) } ( &TransformOperation::Perspective(ref fd), &TransformOperation::Perspective(ref td), ) => { let mut fd_matrix = Matrix3D::identity(); let mut td_matrix = Matrix3D::identity(); if fd.px() > 0. { fd_matrix.m34 = -1. / fd.px(); } if td.px() > 0. { td_matrix.m34 = -1. / td.px(); } fd_matrix.compute_squared_distance(&td_matrix) } ( &TransformOperation::Perspective(ref p), &TransformOperation::Matrix3D(ref m), ) | ( &TransformOperation::Matrix3D(ref m), &TransformOperation::Perspective(ref p), ) => { let mut p_matrix = Matrix3D::identity(); if p.px() > 0. { p_matrix.m34 = -1. / p.px(); } p_matrix.compute_squared_distance(&m) } // Gecko cross-interpolates amongst all translate and all scale // functions (See ToPrimitive in layout/style/StyleAnimationValue.cpp) // without falling back to InterpolateMatrix _ if self.is_translate() && other.is_translate() => { self.to_translate_3d().compute_squared_distance(&other.to_translate_3d()) } _ if self.is_scale() && other.is_scale() => { self.to_scale_3d().compute_squared_distance(&other.to_scale_3d()) } _ => Err(()), } } } impl ComputeSquaredDistance for ComputedTransform { #[inline] fn compute_squared_distance(&self, other: &Self) -> Result { let list1 = &self.0; let list2 = &other.0; let squared_dist: Result = list1.iter().zip_longest(list2).map(|it| { match it { EitherOrBoth::Both(this, other) => { this.compute_squared_distance(other) }, EitherOrBoth::Left(list) | EitherOrBoth::Right(list) => { list.to_animated_zero()?.compute_squared_distance(list) }, } }).sum(); // Roll back to matrix interpolation if there is any Err(()) in the transform lists, such // as mismatched transform functions. if let Err(_) = squared_dist { let matrix1: Matrix3D = self.to_transform_3d_matrix(None)?.0.into(); let matrix2: Matrix3D = other.to_transform_3d_matrix(None)?.0.into(); return matrix1.compute_squared_distance(&matrix2); } squared_dist } } /// Animated SVGPaint pub type IntermediateSVGPaint = SVGPaint; /// Animated SVGPaintKind pub type IntermediateSVGPaintKind = SVGPaintKind; impl ToAnimatedZero for IntermediateSVGPaint { #[inline] fn to_animated_zero(&self) -> Result { Ok(IntermediateSVGPaint { kind: self.kind.to_animated_zero()?, fallback: self.fallback.and_then(|v| v.to_animated_zero().ok()), }) } } impl From for NumberOrPercentage { fn from(lop: NonNegativeLengthOrPercentage) -> NumberOrPercentage { lop.0.into() } } impl From for NumberOrPercentage { fn from(num: NonNegativeNumber) -> NumberOrPercentage { num.0.into() } } impl From for NumberOrPercentage { fn from(lop: LengthOrPercentage) -> NumberOrPercentage { match lop { LengthOrPercentage::Length(len) => NumberOrPercentage::Number(len.px()), LengthOrPercentage::Percentage(p) => NumberOrPercentage::Percentage(p), LengthOrPercentage::Calc(_) => { panic!("We dont't expected calc interpolation for SvgLengthOrPercentageOrNumber"); }, } } } impl From for NumberOrPercentage { fn from(num: Number) -> NumberOrPercentage { NumberOrPercentage::Number(num) } } fn convert_to_number_or_percentage( from: SvgLengthOrPercentageOrNumber) -> NumberOrPercentage where LengthOrPercentageType: Into, NumberType: Into { match from { SvgLengthOrPercentageOrNumber::LengthOrPercentage(lop) => { lop.into() } SvgLengthOrPercentageOrNumber::Number(num) => { num.into() } } } fn convert_from_number_or_percentage( from: NumberOrPercentage) -> SvgLengthOrPercentageOrNumber where LengthOrPercentageType: From, NumberType: From { match from { NumberOrPercentage::Number(num) => SvgLengthOrPercentageOrNumber::Number(num.into()), NumberOrPercentage::Percentage(p) => SvgLengthOrPercentageOrNumber::LengthOrPercentage( (LengthOrPercentage::Percentage(p)).into()) } } impl Animate for SvgLengthOrPercentageOrNumber where L: Animate + From + Into + Copy, N: Animate + From + Into, LengthOrPercentage: From, Self: Copy, { #[inline] fn animate(&self, other: &Self, procedure: Procedure) -> Result { if self.has_calc() || other.has_calc() { // TODO: We need to treat calc value. // https://bugzilla.mozilla.org/show_bug.cgi?id=1386967 return Err(()); } let this = convert_to_number_or_percentage(*self); let other = convert_to_number_or_percentage(*other); match (this, other) { ( NumberOrPercentage::Number(ref this), NumberOrPercentage::Number(ref other), ) => { Ok(convert_from_number_or_percentage( NumberOrPercentage::Number(this.animate(other, procedure)?) )) }, ( NumberOrPercentage::Percentage(ref this), NumberOrPercentage::Percentage(ref other), ) => { Ok(convert_from_number_or_percentage( NumberOrPercentage::Percentage(this.animate(other, procedure)?) )) }, _ => Err(()), } } } impl Animate for SVGLength where L: Animate + Clone, { #[inline] fn animate(&self, other: &Self, procedure: Procedure) -> Result { match (self, other) { (&SVGLength::Length(ref this), &SVGLength::Length(ref other)) => { Ok(SVGLength::Length(this.animate(other, procedure)?)) }, _ => Err(()), } } } /// impl Animate for SVGStrokeDashArray where L: Clone + RepeatableListAnimatable, { #[inline] fn animate(&self, other: &Self, procedure: Procedure) -> Result { if matches!(procedure, Procedure::Add | Procedure::Accumulate { .. }) { // Non-additive. return Err(()); } match (self, other) { (&SVGStrokeDashArray::Values(ref this), &SVGStrokeDashArray::Values(ref other)) => { Ok(SVGStrokeDashArray::Values(this.animate(other, procedure)?)) }, _ => Err(()), } } } impl ToAnimatedZero for SVGStrokeDashArray where L: ToAnimatedZero, { #[inline] fn to_animated_zero(&self) -> Result { match *self { SVGStrokeDashArray::Values(ref values) => { Ok(SVGStrokeDashArray::Values( values.iter().map(ToAnimatedZero::to_animated_zero).collect::, _>>()?, )) } SVGStrokeDashArray::ContextValue => Ok(SVGStrokeDashArray::ContextValue), } } } impl Animate for SVGOpacity where O: Animate + Clone, { #[inline] fn animate(&self, other: &Self, procedure: Procedure) -> Result { match (self, other) { (&SVGOpacity::Opacity(ref this), &SVGOpacity::Opacity(ref other)) => { Ok(SVGOpacity::Opacity(this.animate(other, procedure)?)) }, _ => Err(()), } } } <% FILTER_FUNCTIONS = [ 'Blur', 'Brightness', 'Contrast', 'Grayscale', 'HueRotate', 'Invert', 'Opacity', 'Saturate', 'Sepia' ] %> /// impl Animate for AnimatedFilter { fn animate( &self, other: &Self, procedure: Procedure, ) -> Result { match (self, other) { % for func in ['Blur', 'Grayscale', 'HueRotate', 'Invert', 'Sepia']: (&Filter::${func}(ref this), &Filter::${func}(ref other)) => { Ok(Filter::${func}(this.animate(other, procedure)?)) }, % endfor % for func in ['Brightness', 'Contrast', 'Opacity', 'Saturate']: (&Filter::${func}(ref this), &Filter::${func}(ref other)) => { Ok(Filter::${func}(NonNegative(animate_multiplicative_factor( this.0, other.0, procedure, )?))) }, % endfor % if product == "gecko": (&Filter::DropShadow(ref this), &Filter::DropShadow(ref other)) => { Ok(Filter::DropShadow(this.animate(other, procedure)?)) }, % endif _ => Err(()), } } } /// impl ToAnimatedZero for AnimatedFilter { fn to_animated_zero(&self) -> Result { match *self { % for func in ['Blur', 'Grayscale', 'HueRotate', 'Invert', 'Sepia']: Filter::${func}(ref this) => Ok(Filter::${func}(this.to_animated_zero()?)), % endfor % for func in ['Brightness', 'Contrast', 'Opacity', 'Saturate']: Filter::${func}(_) => Ok(Filter::${func}(NonNegative(1.))), % endfor % if product == "gecko": Filter::DropShadow(ref this) => Ok(Filter::DropShadow(this.to_animated_zero()?)), % endif _ => Err(()), } } } impl Animate for AnimatedFilterList { #[inline] fn animate( &self, other: &Self, procedure: Procedure, ) -> Result { if procedure == Procedure::Add { return Ok(AnimatedFilterList( self.0.iter().chain(other.0.iter()).cloned().collect(), )); } Ok(AnimatedFilterList(self.0.iter().zip_longest(other.0.iter()).map(|it| { match it { EitherOrBoth::Both(this, other) => { this.animate(other, procedure) }, EitherOrBoth::Left(this) => { this.animate(&this.to_animated_zero()?, procedure) }, EitherOrBoth::Right(other) => { other.to_animated_zero()?.animate(other, procedure) }, } }).collect::, _>>()?)) } } impl ComputeSquaredDistance for AnimatedFilterList { #[inline] fn compute_squared_distance(&self, other: &Self) -> Result { self.0.iter().zip_longest(other.0.iter()).map(|it| { match it { EitherOrBoth::Both(this, other) => { this.compute_squared_distance(other) }, EitherOrBoth::Left(list) | EitherOrBoth::Right(list) => { list.to_animated_zero()?.compute_squared_distance(list) }, } }).sum() } } /// A comparator to sort PropertyIds such that longhands are sorted before shorthands, /// shorthands with fewer components are sorted before shorthands with more components, /// and otherwise shorthands are sorted by IDL name as defined by [Web Animations][property-order]. /// /// Using this allows us to prioritize values specified by longhands (or smaller /// shorthand subsets) when longhands and shorthands are both specified on the one keyframe. /// /// Example orderings that result from this: /// /// margin-left, margin /// /// and: /// /// border-top-color, border-color, border-top, border /// /// [property-order] https://drafts.csswg.org/web-animations/#calculating-computed-keyframes #[cfg(feature = "gecko")] pub fn compare_property_priority(a: &PropertyId, b: &PropertyId) -> cmp::Ordering { match (a.as_shorthand(), b.as_shorthand()) { // Within shorthands, sort by the number of subproperties, then by IDL name. (Ok(a), Ok(b)) => { let subprop_count_a = a.longhands().len(); let subprop_count_b = b.longhands().len(); subprop_count_a.cmp(&subprop_count_b).then_with( || get_idl_name_sort_order(&a).cmp(&get_idl_name_sort_order(&b))) }, // Longhands go before shorthands. (Ok(_), Err(_)) => cmp::Ordering::Greater, (Err(_), Ok(_)) => cmp::Ordering::Less, // Both are longhands or custom properties in which case they don't overlap and should // sort equally. _ => cmp::Ordering::Equal, } } #[cfg(feature = "gecko")] fn get_idl_name_sort_order(shorthand: &ShorthandId) -> u32 { <% # Sort by IDL name. sorted_shorthands = sorted(data.shorthands, key=lambda p: to_idl_name(p.ident)) # Annotate with sorted position sorted_shorthands = [(p, position) for position, p in enumerate(sorted_shorthands)] %> match *shorthand { % for property, position in sorted_shorthands: ShorthandId::${property.camel_case} => ${position}, % endfor } }