Bug 1390702 - Part 2: Implement Animatable for FontSettings. r=birtles draft
authorDaisuke Akatsuka <dakatsuka@mozilla.com>
Tue, 05 Sep 2017 14:45:23 +0900
changeset 658918 e9b91672d9b2dc77fb5f3027b59e3069d436e987
parent 658917 c3d3da7ea80f5e64f12b60cef891452d6203513b
child 658919 4406d1e3bde65ba728804cf104a9c7412a17af28
push id77928
push userbmo:dakatsuka@mozilla.com
push dateTue, 05 Sep 2017 05:46:09 +0000
reviewersbirtles
bugs1390702
milestone57.0a1
Bug 1390702 - Part 2: Implement Animatable for FontSettings. r=birtles MozReview-Commit-ID: 7xAGglOloUN
servo/components/style/properties/helpers/animated_properties.mako.rs
servo/components/style/values/generics/mod.rs
--- a/servo/components/style/properties/helpers/animated_properties.mako.rs
+++ b/servo/components/style/properties/helpers/animated_properties.mako.rs
@@ -15,16 +15,17 @@ use cssparser::Parser;
 #[cfg(feature = "gecko")] use gecko_string_cache::Atom;
 use itertools::{EitherOrBoth, Itertools};
 use properties::{CSSWideKeyword, PropertyDeclaration};
 use properties::longhands;
 use properties::longhands::background_size::computed_value::T as BackgroundSizeList;
 use properties::longhands::border_spacing::computed_value::T as BorderSpacing;
 use properties::longhands::font_weight::computed_value::T as FontWeight;
 use properties::longhands::font_stretch::computed_value::T as FontStretch;
+use properties::longhands::font_variation_settings::computed_value::T as FontVariationSettings;
 use properties::longhands::line_height::computed_value::T as LineHeight;
 use properties::longhands::transform::computed_value::ComputedMatrix;
 use properties::longhands::transform::computed_value::ComputedOperation as TransformOperation;
 use properties::longhands::transform::computed_value::T as TransformList;
 use properties::longhands::visibility::computed_value::T as Visibility;
 #[cfg(feature = "gecko")] use properties::{PropertyId, PropertyDeclarationId, LonghandId};
 #[cfg(feature = "gecko")] use properties::{ShorthandId};
 use selectors::parser::SelectorParseError;
@@ -49,16 +50,19 @@ use values::computed::{LengthOrPercentag
 use values::computed::{LengthOrPercentageOrNone, MaxLength, NonNegativeAu};
 use values::computed::{NonNegativeNumber, Number, NumberOrPercentage, Percentage};
 use values::computed::{PositiveIntegerOrAuto, ToComputedValue};
 #[cfg(feature = "gecko")] use values::computed::MozLength;
 use values::computed::length::{NonNegativeLengthOrAuto, NonNegativeLengthOrNormal};
 use values::computed::length::NonNegativeLengthOrPercentage;
 use values::computed::transform::DirectionVector;
 use values::distance::{ComputeSquaredDistance, SquaredDistance};
+use values::generics::FontSettings as GenericFontSettings;
+use values::generics::FontSettingTag as GenericFontSettingTag;
+use values::generics::FontSettingTagFloat;
 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};
 
 /// https://drafts.csswg.org/css-transitions/#animtype-repeatable-list
 pub trait RepeatableListAnimatable: Animate {}
@@ -910,16 +914,176 @@ impl Into<FontStretch> for f64 {
         let index = (self + 0.5).floor().min(9.0).max(1.0);
         static FONT_STRETCH_ENUM_MAP: [FontStretch; 9] =
             [ ultra_condensed, extra_condensed, condensed, semi_condensed, normal,
               semi_expanded, expanded, extra_expanded, ultra_expanded ];
         FONT_STRETCH_ENUM_MAP[(index - 1.0) as usize]
     }
 }
 
+/// https://drafts.csswg.org/css-fonts-4/#font-variation-settings-def
+impl Animate for FontVariationSettings {
+    #[inline]
+    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
+        FontSettingTagIter::new(self, other)?
+            .map(|r| r.and_then(|(st, ot)| st.animate(&ot, procedure)))
+            .collect::<Result<Vec<FontSettingTag>, ()>>()
+            .map(GenericFontSettings::Tag)
+    }
+}
+
+impl ComputeSquaredDistance for FontVariationSettings {
+    #[inline]
+    fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
+        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<Self, ()> {
+        Err(())
+    }
+}
+
+impl Animate for FontSettingTag {
+    #[inline]
+    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
+        if self.tag != other.tag {
+            return Err(());
+        }
+        let value = self.value.animate(&other.value, procedure)?;
+        Ok(FontSettingTag {
+            tag: self.tag,
+            value,
+        })
+    }
+}
+
+impl ComputeSquaredDistance for FontSettingTag {
+    #[inline]
+    fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
+        if self.tag != other.tag {
+            return Err(());
+        }
+        self.value.compute_squared_distance(&other.value)
+    }
+}
+
+type FontSettingTag = GenericFontSettingTag<FontSettingTagFloat>;
+
+struct FontSettingTagIterState<'a> {
+    tags: Vec<(&'a FontSettingTag)>,
+    index: usize,
+    prev_tag: u32,
+}
+
+impl<'a> FontSettingTagIterState<'a> {
+    fn new(tags: Vec<(&'a FontSettingTag)>) -> FontSettingTagIterState<'a> {
+        FontSettingTagIterState {
+            index: tags.len(),
+            tags,
+            prev_tag: 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<FontSettingTagIter<'a>, ()> {
+        if let (&GenericFontSettings::Tag(ref a_tags), &GenericFontSettings::Tag(ref b_tags)) = (a_settings, b_settings)
+        {
+            fn as_new_sorted_tags(tags: &Vec<FontSettingTag>) -> Vec<(&FontSettingTag)> {
+                use std::iter::FromIterator;
+                let mut sorted_tags: Vec<(&FontSettingTag)> = Vec::from_iter(tags.iter());
+                sorted_tags.sort_by_key(|k| k.tag);
+                sorted_tags
+            };
+
+            Ok(FontSettingTagIter {
+                a_state: FontSettingTagIterState::new(as_new_sorted_tags(a_tags)),
+                b_state: FontSettingTagIterState::new(as_new_sorted_tags(b_tags)),
+            })
+        } else {
+            Err(())
+        }
+    }
+
+    fn next_tag(state: &mut FontSettingTagIterState<'a>) -> Option<(&'a FontSettingTag)> {
+        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 FontSettingTag, &'a FontSettingTag), ()>;
+
+    fn next(&mut self) -> Option<Result<(&'a FontSettingTag, &'a FontSettingTag), ()>> {
+        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<H, V> RepeatableListAnimatable for generic_position::Position<H, V>
     where H: RepeatableListAnimatable, V: RepeatableListAnimatable {}
 
 /// https://drafts.csswg.org/css-transitions/#animtype-rect
 impl Animate for ClipRect {
     #[inline]
     fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
         let animate_component = |this: &Option<Au>, other: &Option<Au>| {
--- a/servo/components/style/values/generics/mod.rs
+++ b/servo/components/style/values/generics/mod.rs
@@ -212,17 +212,17 @@ impl<T: Parse> Parse for FontSettings<T>
 /// because it serializes with the preceding space
 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub struct FontSettingTagInt(pub u32);
 /// A number value to be used for font-variation-settings
 ///
 /// Do not use this type anywhere except within FontSettings
 /// because it serializes with the preceding space
-#[derive(Clone, Debug, PartialEq)]
+#[derive(Animate, Clone, ComputeSquaredDistance, Debug, PartialEq)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub struct FontSettingTagFloat(pub f32);
 
 impl ToCss for FontSettingTagInt {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
         match self.0 {
             1 => Ok(()),
             0 => dest.write_str(" off"),