servo: Merge #18380 - Make font-variation-settings animatable (from dadaa:make-font-variation-settings-animatable); r=birtles
authorDaisuke Akatsuka <dakatsuka@mozilla.com>
Tue, 05 Sep 2017 06:20:33 -0500
changeset 428384 e6f46ebcbd90cd87a8e8a66252d2c831f9037970
parent 428383 6f458218dda9687d1405cb6a894f89ddfc2336f8
child 428385 c939207a9436dd7ee5dc38fba76abed5b6219e0d
push id7761
push userjlund@mozilla.com
push dateFri, 15 Sep 2017 00:19:52 +0000
treeherdermozilla-beta@c38455951db4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbirtles
milestone57.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
servo: Merge #18380 - Make font-variation-settings animatable (from dadaa:make-font-variation-settings-animatable); r=birtles <!-- Please describe your changes on the following line: --> https://bugzilla.mozilla.org/show_bug.cgi?id=1390702 r=birtles --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [X] `./mach build -d` does not report any errors - [X] `./mach build-geckolib` does not report any errors - [X] `./mach test-tidy` does not report any errors - [X] `./mach test-tidy --stylo` does not report any errors - [X] `./mach test-unit -p style` does not report any errors <!-- Either: --> - [X] There are tests for these changes. The test codes are patch part 4 in https://bugzilla.mozilla.org/show_bug.cgi?id=1390702 <!-- Also, please make sure that "Allow edits from maintainers" checkbox is checked, so that we can help you if you get stuck somewhere along the way.--> <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> Source-Repo: https://github.com/servo/servo Source-Revision: 449ffb23acdd0098193458856de88ec4198724fc
servo/components/style/properties/gecko.mako.rs
servo/components/style/properties/helpers/animated_properties.mako.rs
servo/components/style/properties/longhand/font.mako.rs
servo/components/style/values/generics/mod.rs
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -1,16 +1,16 @@
 /* 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/. */
 
 // `data` comes from components/style/properties.mako.rs; see build.rs for more details.
 
 <%!
-    from data import to_rust_ident, to_camel_case
+    from data import to_rust_ident, to_camel_case, to_camel_case_lower
     from data import Keyword
 %>
 <%namespace name="helpers" file="/helpers.mako.rs" />
 
 use app_units::Au;
 use custom_properties::CustomPropertiesMap;
 use gecko_bindings::bindings;
 % for style_struct in data.style_structs:
@@ -1065,16 +1065,77 @@ impl Clone for ${style_struct.gecko_stru
     <% impl_simple_copy(ident, gecko_ffi_name) %>
 
     #[allow(non_snake_case)]
     pub fn clone_${ident}(&self) -> longhands::${ident}::computed_value::T {
         From::from(self.gecko.${gecko_ffi_name})
     }
 </%def>
 
+<%def name="impl_font_settings(ident, tag_type)">
+    <%
+    gecko_ffi_name = to_camel_case_lower(ident)
+    %>
+
+    pub fn set_${ident}(&mut self, v: longhands::${ident}::computed_value::T) {
+        use values::generics::FontSettings;
+
+        let current_settings = &mut self.gecko.mFont.${gecko_ffi_name};
+        current_settings.clear_pod();
+
+        match v {
+            FontSettings::Normal => (), // do nothing, length is already 0
+
+            FontSettings::Tag(other_settings) => {
+                unsafe { current_settings.set_len_pod(other_settings.len() as u32) };
+
+                for (current, other) in current_settings.iter_mut().zip(other_settings) {
+                    current.mTag = other.tag;
+                    current.mValue = other.value.0;
+                }
+            }
+        };
+    }
+
+    pub fn copy_${ident}_from(&mut self, other: &Self) {
+        let current_settings = &mut self.gecko.mFont.${gecko_ffi_name};
+        let other_settings = &other.gecko.mFont.${gecko_ffi_name};
+        let settings_length = other_settings.len() as u32;
+
+        current_settings.clear_pod();
+        unsafe { current_settings.set_len_pod(settings_length) };
+
+        for (current, other) in current_settings.iter_mut().zip(other_settings.iter()) {
+            current.mTag = other.mTag;
+            current.mValue = other.mValue;
+        }
+    }
+
+    pub fn reset_${ident}(&mut self, other: &Self) {
+        self.copy_${ident}_from(other)
+    }
+
+    pub fn clone_${ident}(&self) -> longhands::${ident}::computed_value::T {
+        use values::generics::{FontSettings, FontSettingTag, ${tag_type}} ;
+
+        if self.gecko.mFont.${gecko_ffi_name}.len() == 0 {
+            FontSettings::Normal
+        } else {
+            FontSettings::Tag(
+                self.gecko.mFont.${gecko_ffi_name}.iter().map(|gecko_font_setting| {
+                    FontSettingTag {
+                        tag: gecko_font_setting.mTag,
+                        value: ${tag_type}(gecko_font_setting.mValue),
+                    }
+                }).collect()
+            )
+        }
+    }
+</%def>
+
 <%def name="raw_impl_trait(style_struct, skip_longhands='', skip_additionals='')">
 <%
     longhands = [x for x in style_struct.longhands
                 if not (skip_longhands == "*" or x.name in skip_longhands.split())]
 
     #
     # Make a list of types we can't auto-generate.
     #
@@ -2064,108 +2125,18 @@ fn static_assert() {
                              font-variant-numeric font-language-override
                              font-feature-settings font-variation-settings
                              -moz-min-font-size-ratio -x-text-zoom"""
 %>
 <%self:impl_trait style_struct_name="Font"
     skip_longhands="${skip_font_longhands}"
     skip_additionals="*">
 
-    pub fn set_font_feature_settings(&mut self, v: longhands::font_feature_settings::computed_value::T) {
-        use values::generics::FontSettings;
-
-        let current_settings = &mut self.gecko.mFont.fontFeatureSettings;
-        current_settings.clear_pod();
-
-        match v {
-            FontSettings::Normal => (), // do nothing, length is already 0
-
-            FontSettings::Tag(feature_settings) => {
-                unsafe { current_settings.set_len_pod(feature_settings.len() as u32) };
-
-                for (current, feature) in current_settings.iter_mut().zip(feature_settings) {
-                    current.mTag = feature.tag;
-                    current.mValue = feature.value.0;
-                }
-            }
-        };
-    }
-
-    pub fn copy_font_feature_settings_from(&mut self, other: &Self) {
-        let current_settings = &mut self.gecko.mFont.fontFeatureSettings;
-        let feature_settings = &other.gecko.mFont.fontFeatureSettings;
-        let settings_length = feature_settings.len() as u32;
-
-        current_settings.clear_pod();
-        unsafe { current_settings.set_len_pod(settings_length) };
-
-        for (current, feature) in current_settings.iter_mut().zip(feature_settings.iter()) {
-            current.mTag = feature.mTag;
-            current.mValue = feature.mValue;
-        }
-    }
-
-    pub fn reset_font_feature_settings(&mut self, other: &Self) {
-        self.copy_font_feature_settings_from(other)
-    }
-
-    pub fn clone_font_feature_settings(&self) -> longhands::font_feature_settings::computed_value::T {
-        use values::generics::{FontSettings, FontSettingTag, FontSettingTagInt} ;
-
-        if self.gecko.mFont.fontFeatureSettings.len() == 0 {
-            FontSettings::Normal
-        } else {
-            FontSettings::Tag(
-                self.gecko.mFont.fontFeatureSettings.iter().map(|gecko_font_feature_setting| {
-                    FontSettingTag {
-                        tag: gecko_font_feature_setting.mTag,
-                        value: FontSettingTagInt(gecko_font_feature_setting.mValue),
-                    }
-                }).collect()
-            )
-        }
-    }
-
-    pub fn set_font_variation_settings(&mut self, v: longhands::font_variation_settings::computed_value::T) {
-        use values::generics::FontSettings;
-
-        let current_settings = &mut self.gecko.mFont.fontVariationSettings;
-        current_settings.clear_pod();
-
-        match v {
-            FontSettings::Normal => (), // do nothing, length is already 0
-
-            FontSettings::Tag(feature_settings) => {
-                unsafe { current_settings.set_len_pod(feature_settings.len() as u32) };
-
-                for (current, feature) in current_settings.iter_mut().zip(feature_settings) {
-                    current.mTag = feature.tag;
-                    current.mValue = feature.value.0;
-                }
-            }
-        };
-    }
-
-    pub fn copy_font_variation_settings_from(&mut self, other: &Self ) {
-        let current_settings = &mut self.gecko.mFont.fontVariationSettings;
-        let feature_settings = &other.gecko.mFont.fontVariationSettings;
-        let settings_length = feature_settings.len() as u32;
-
-        current_settings.clear_pod();
-        unsafe { current_settings.set_len_pod(settings_length) };
-
-        for (current, feature) in current_settings.iter_mut().zip(feature_settings.iter()) {
-            current.mTag = feature.mTag;
-            current.mValue = feature.mValue;
-        }
-    }
-
-    pub fn reset_font_variation_settings(&mut self, other: &Self) {
-        self.copy_font_variation_settings_from(other)
-    }
+    <% impl_font_settings("font_feature_settings", "FontSettingTagInt") %>
+    <% impl_font_settings("font_variation_settings", "FontSettingTagFloat") %>
 
     pub fn fixup_none_generic(&mut self, device: &Device) {
         self.gecko.mFont.systemFont = false;
         unsafe {
             bindings::Gecko_nsStyleFont_FixupNoneGeneric(&mut self.gecko, device.pres_context())
         }
     }
 
--- a/servo/components/style/properties/helpers/animated_properties.mako.rs
+++ b/servo/components/style/properties/helpers/animated_properties.mako.rs
@@ -15,16 +15,18 @@ 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;
+#[cfg(feature = "gecko")]
+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 +51,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};
+#[cfg(feature = "gecko")] use values::generics::FontSettings as GenericFontSettings;
+#[cfg(feature = "gecko")] use values::generics::FontSettingTag as GenericFontSettingTag;
+#[cfg(feature = "gecko")] 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 +915,187 @@ 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
+#[cfg(feature = "gecko")]
+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)
+    }
+}
+
+#[cfg(feature = "gecko")]
+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()
+    }
+}
+
+#[cfg(feature = "gecko")]
+impl ToAnimatedZero for FontVariationSettings {
+    #[inline]
+    fn to_animated_zero(&self) -> Result<Self, ()> {
+        Err(())
+    }
+}
+
+#[cfg(feature = "gecko")]
+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,
+        })
+    }
+}
+
+#[cfg(feature = "gecko")]
+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)
+    }
+}
+
+#[cfg(feature = "gecko")]
+type FontSettingTag = GenericFontSettingTag<FontSettingTagFloat>;
+
+#[cfg(feature = "gecko")]
+struct FontSettingTagIterState<'a> {
+    tags: Vec<(&'a FontSettingTag)>,
+    index: usize,
+    prev_tag: u32,
+}
+
+#[cfg(feature = "gecko")]
+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
+///
+#[cfg(feature = "gecko")]
+struct FontSettingTagIter<'a> {
+    a_state: FontSettingTagIterState<'a>,
+    b_state: FontSettingTagIterState<'a>,
+}
+
+#[cfg(feature = "gecko")]
+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)
+        }
+    }
+}
+
+#[cfg(feature = "gecko")]
+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/properties/longhand/font.mako.rs
+++ b/servo/components/style/properties/longhand/font.mako.rs
@@ -1990,17 +1990,18 @@ macro_rules! exclusive_value {
 </%helpers:longhand>
 
 <%
 # This spec link is too long to fit elsewhere
 variation_spec = """\
 https://drafts.csswg.org/css-fonts-4/#low-level-font-variation-settings-control-the-font-variation-settings-property\
 """
 %>
-<%helpers:longhand name="font-variation-settings" products="gecko" animation_value_type="none"
+<%helpers:longhand name="font-variation-settings" products="gecko"
+                   animation_value_type="ComputedValue"
                    flags="APPLIES_TO_FIRST_LETTER APPLIES_TO_FIRST_LINE APPLIES_TO_PLACEHOLDER"
                    spec="${variation_spec}">
     use values::computed::ComputedValueAsSpecified;
     use values::generics::FontSettings;
 
     impl ComputedValueAsSpecified for SpecifiedValue {}
 
     pub type SpecifiedValue = computed_value::T;
--- a/servo/components/style/values/generics/mod.rs
+++ b/servo/components/style/values/generics/mod.rs
@@ -214,16 +214,17 @@ impl<T: Parse> Parse for FontSettings<T>
 #[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)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+#[cfg_attr(feature = "gecko", derive(Animate, ComputeSquaredDistance))]
 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"),
             x => {