Bug 1399228 - stylo: Move font-size stuff to values::*::font ; r?emilio draft
authorManish Goregaokar <manishearth@gmail.com>
Mon, 18 Sep 2017 13:25:59 -0700
changeset 669459 79029a15ed190e253dda0c9f2716cf643c11df6e
parent 669458 cba3f3abab6d5fe7577b58d81dc39007fe1c0974
child 669460 1c6d0ec22002ed59b168060aa6b712191fccfe07
push id81335
push userbmo:manishearth@gmail.com
push dateSat, 23 Sep 2017 08:49:12 +0000
reviewersemilio
bugs1399228
milestone58.0a1
Bug 1399228 - stylo: Move font-size stuff to values::*::font ; r?emilio MozReview-Commit-ID: F4td5xnV2OO
servo/components/style/properties/gecko.mako.rs
servo/components/style/properties/longhand/font.mako.rs
servo/components/style/values/computed/font.rs
servo/components/style/values/computed/mod.rs
servo/components/style/values/mod.rs
servo/components/style/values/specified/font.rs
servo/components/style/values/specified/length.rs
servo/components/style/values/specified/mod.rs
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -2156,17 +2156,17 @@ fn static_assert() {
 
     pub fn unzoom_fonts(&mut self, device: &Device) {
         self.gecko.mSize = device.unzoom_text(Au(self.gecko.mSize)).0;
         self.gecko.mScriptUnconstrainedSize = device.unzoom_text(Au(self.gecko.mScriptUnconstrainedSize)).0;
         self.gecko.mFont.size = device.unzoom_text(Au(self.gecko.mFont.size)).0;
     }
 
     pub fn set_font_size(&mut self, v: longhands::font_size::computed_value::T) {
-        use self::longhands::font_size::KeywordSize;
+        use values::specified::font::KeywordSize;
         self.gecko.mSize = v.size().0;
         self.gecko.mScriptUnconstrainedSize = v.size().0;
         if let Some(info) = v.info {
             self.gecko.mFontSizeKeyword = match info.kw {
                 KeywordSize::XXSmall => structs::NS_STYLE_FONT_SIZE_XXSMALL,
                 KeywordSize::XSmall => structs::NS_STYLE_FONT_SIZE_XSMALL,
                 KeywordSize::Small => structs::NS_STYLE_FONT_SIZE_SMALL,
                 KeywordSize::Medium => structs::NS_STYLE_FONT_SIZE_MEDIUM,
@@ -2365,17 +2365,18 @@ fn static_assert() {
             self.gecko.mFontSizeFactor = 1.;
             self.gecko.mFontSizeOffset = 0;
             self.gecko.mScriptUnconstrainedSize = parent.gecko.mScriptUnconstrainedSize;
         }
         self.fixup_font_min_size(device);
     }
 
     pub fn clone_font_size(&self) -> longhands::font_size::computed_value::T {
-        use self::longhands::font_size::KeywordSize;
+        use values::computed::font::KeywordInfo;
+        use values::specified::font::KeywordSize;
         let size = Au(self.gecko.mSize).into();
         let kw = match self.gecko.mFontSizeKeyword as u32 {
             structs::NS_STYLE_FONT_SIZE_XXSMALL => KeywordSize::XXSmall,
             structs::NS_STYLE_FONT_SIZE_XSMALL => KeywordSize::XSmall,
             structs::NS_STYLE_FONT_SIZE_SMALL => KeywordSize::Small,
             structs::NS_STYLE_FONT_SIZE_MEDIUM => KeywordSize::Medium,
             structs::NS_STYLE_FONT_SIZE_LARGE => KeywordSize::Large,
             structs::NS_STYLE_FONT_SIZE_XLARGE => KeywordSize::XLarge,
@@ -2386,17 +2387,17 @@ fn static_assert() {
                     size: size,
                     info: None,
                 }
             }
             _ => unreachable!("mFontSizeKeyword should be an absolute keyword or NO_KEYWORD")
         };
         longhands::font_size::computed_value::T {
             size: size,
-            info: Some(longhands::font_size::computed_value::KeywordInfo {
+            info: Some(KeywordInfo {
                 kw: kw,
                 factor: self.gecko.mFontSizeFactor,
                 offset: Au(self.gecko.mFontSizeOffset).into()
             })
         }
     }
 
     pub fn set_font_weight(&mut self, v: longhands::font_weight::computed_value::T) {
--- a/servo/components/style/properties/longhand/font.mako.rs
+++ b/servo/components/style/properties/longhand/font.mako.rs
@@ -595,402 +595,46 @@ macro_rules! impl_gecko_keyword_conversi
         }
     }
 </%helpers:longhand>
 
 <%helpers:longhand name="font-size" animation_value_type="ComputedValue"
                    flags="APPLIES_TO_FIRST_LETTER APPLIES_TO_FIRST_LINE APPLIES_TO_PLACEHOLDER"
                    allow_quirks="True" spec="https://drafts.csswg.org/css-fonts/#propdef-font-size">
     use app_units::Au;
-    use properties::longhands::system_font::SystemFont;
-    use std::fmt;
-    use style_traits::ToCss;
-    use values::FONT_MEDIUM_PX;
-    use values::computed::NonNegativeLength;
-    use values::specified::{AllowQuirks, LengthOrPercentage, NoCalcLength};
+    use values::specified::AllowQuirks;
     use values::specified::length::FontBaseSize;
-
-    impl ToCss for SpecifiedValue {
-        fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-            match *self {
-                SpecifiedValue::Length(ref lop) => lop.to_css(dest),
-                SpecifiedValue::Keyword(kw, _, _) => kw.to_css(dest),
-                SpecifiedValue::Smaller => dest.write_str("smaller"),
-                SpecifiedValue::Larger => dest.write_str("larger"),
-                SpecifiedValue::System(sys) => sys.to_css(dest),
-            }
-        }
-    }
-
-    #[derive(Clone, Debug, PartialEq)]
-    #[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
-    #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-    pub enum SpecifiedValue {
-        Length(specified::LengthOrPercentage),
-        /// A keyword value, along with a ratio and absolute offset.
-        /// The ratio in any specified keyword value
-        /// will be 1 (with offset 0), but we cascade keywordness even
-        /// after font-relative (percent and em) values
-        /// have been applied, which is where the ratio
-        /// comes in. The offset comes in if we cascaded a calc value,
-        /// where the font-relative portion (em and percentage) will
-        /// go into the ratio, and the remaining units all computed together
-        /// will go into the offset.
-        /// See bug 1355707.
-        Keyword(KeywordSize, f32, NonNegativeLength),
-        Smaller,
-        Larger,
-        System(SystemFont)
-    }
-
-    impl From<specified::LengthOrPercentage> for SpecifiedValue {
-        fn from(other: specified::LengthOrPercentage) -> Self {
-            SpecifiedValue::Length(other)
-        }
-    }
+    use values::specified::font::{FONT_MEDIUM_PX, KeywordSize};
 
     pub mod computed_value {
-        use app_units::Au;
-        use std::fmt;
-        use style_traits::ToCss;
-        use values::computed::NonNegativeLength;
-
-        #[derive(Copy, Clone, PartialEq, Debug)]
-        #[derive(ToAnimatedValue, Animate, ToAnimatedZero, ComputeSquaredDistance)]
-        #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-        pub struct T {
-            pub size: NonNegativeLength,
-            pub info: Option<KeywordInfo>,
-        }
-
-        #[derive(Copy, Clone, PartialEq, Debug)]
-        #[derive(ToAnimatedValue, Animate, ToAnimatedZero, ComputeSquaredDistance)]
-        #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-        pub struct KeywordInfo {
-            pub kw: super::KeywordSize,
-            pub factor: f32,
-            pub offset: NonNegativeLength,
-        }
-
-        impl KeywordInfo {
-            /// Given a parent keyword info (self), apply an additional factor/offset to it
-            pub fn compose(self, factor: f32, offset: NonNegativeLength) -> Self {
-                KeywordInfo {
-                    kw: self.kw,
-                    factor: self.factor * factor,
-                    offset: self.offset.scale_by(factor) + offset,
-                }
-            }
-        }
-
-        impl T {
-            pub fn size(self) -> Au {
-                self.size.into()
-            }
-        }
-
-        impl ToCss for T {
-            fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-                self.size.to_css(dest)
-            }
-        }
-    }
-
-    /// CSS font keywords
-    #[derive(Clone, Copy, Debug, PartialEq)]
-    #[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
-    #[derive(ToAnimatedValue, Animate, ToAnimatedZero, ComputeSquaredDistance)]
-    #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-    pub enum KeywordSize {
-        XXSmall = 1, // This is to enable the NonZero optimization
-                     // which simplifies the representation of Option<KeywordSize>
-                     // in bindgen
-        XSmall,
-        Small,
-        Medium,
-        Large,
-        XLarge,
-        XXLarge,
-        // This is not a real font keyword and will not parse
-        // HTML font-size 7 corresponds to this value
-        XXXLarge,
-    }
-
-    pub use self::KeywordSize::*;
-
-    impl KeywordSize {
-        pub fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
-            try_match_ident_ignore_ascii_case! { input.expect_ident()?,
-                "xx-small" => Ok(XXSmall),
-                "x-small" => Ok(XSmall),
-                "small" => Ok(Small),
-                "medium" => Ok(Medium),
-                "large" => Ok(Large),
-                "x-large" => Ok(XLarge),
-                "xx-large" => Ok(XXLarge),
-            }
-        }
-
-        pub fn html_size(&self) -> u8 {
-            match *self {
-                KeywordSize::XXSmall => 0,
-                KeywordSize::XSmall => 1,
-                KeywordSize::Small => 2,
-                KeywordSize::Medium => 3,
-                KeywordSize::Large => 4,
-                KeywordSize::XLarge => 5,
-                KeywordSize::XXLarge => 6,
-                KeywordSize::XXXLarge => 7,
-            }
-        }
-    }
-
-    impl Default for KeywordSize {
-        fn default() -> Self {
-            Medium
-        }
-    }
-
-    impl ToCss for KeywordSize {
-        fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-            dest.write_str(match *self {
-                XXSmall => "xx-small",
-                XSmall => "x-small",
-                Small => "small",
-                Medium => "medium",
-                Large => "large",
-                XLarge => "x-large",
-                XXLarge => "xx-large",
-                XXXLarge => unreachable!("We should never serialize \
-                                          specified values set via
-                                          HTML presentation attributes"),
-            })
-        }
+        use values::computed::font;
+        pub type T = font::FontSize;
     }
 
-    % if product == "servo":
-        impl ToComputedValue for KeywordSize {
-            type ComputedValue = NonNegativeLength;
-            #[inline]
-            fn to_computed_value(&self, _: &Context) -> NonNegativeLength {
-                // https://drafts.csswg.org/css-fonts-3/#font-size-prop
-                use values::FONT_MEDIUM_PX;
-                match *self {
-                    XXSmall => Au::from_px(FONT_MEDIUM_PX) * 3 / 5,
-                    XSmall => Au::from_px(FONT_MEDIUM_PX) * 3 / 4,
-                    Small => Au::from_px(FONT_MEDIUM_PX) * 8 / 9,
-                    Medium => Au::from_px(FONT_MEDIUM_PX),
-                    Large => Au::from_px(FONT_MEDIUM_PX) * 6 / 5,
-                    XLarge => Au::from_px(FONT_MEDIUM_PX) * 3 / 2,
-                    XXLarge => Au::from_px(FONT_MEDIUM_PX) * 2,
-                    XXXLarge => Au::from_px(FONT_MEDIUM_PX) * 3,
-                }.into()
-            }
-
-            #[inline]
-            fn from_computed_value(_: &NonNegativeLength) -> Self {
-                unreachable!()
-            }
-        }
-    % else:
-        impl ToComputedValue for KeywordSize {
-            type ComputedValue = NonNegativeLength;
-            #[inline]
-            fn to_computed_value(&self, cx: &Context) -> NonNegativeLength {
-                use gecko_bindings::structs::nsIAtom;
-                use values::specified::length::au_to_int_px;
-                // Data from nsRuleNode.cpp in Gecko
-                // Mapping from base size and HTML size to pixels
-                // The first index is (base_size - 9), the second is the
-                // HTML size. "0" is CSS keyword xx-small, not HTML size 0,
-                // since HTML size 0 is the same as 1.
-                //
-                //  xxs   xs      s      m     l      xl     xxl   -
-                //  -     0/1     2      3     4      5      6     7
-                static FONT_SIZE_MAPPING: [[i32; 8]; 8] = [
-                    [9,    9,     9,     9,    11,    14,    18,    27],
-                    [9,    9,     9,    10,    12,    15,    20,    30],
-                    [9,    9,    10,    11,    13,    17,    22,    33],
-                    [9,    9,    10,    12,    14,    18,    24,    36],
-                    [9,   10,    12,    13,    16,    20,    26,    39],
-                    [9,   10,    12,    14,    17,    21,    28,    42],
-                    [9,   10,    13,    15,    18,    23,    30,    45],
-                    [9,   10,    13,    16,    18,    24,    32,    48]
-                ];
-
-                static FONT_SIZE_FACTORS: [i32; 8] = [60, 75, 89, 100, 120, 150, 200, 300];
-
-                // XXXManishearth handle quirks mode
-
-                let ref gecko_font = cx.style().get_font().gecko();
-                let base_size = unsafe { Atom::with(gecko_font.mLanguage.raw::<nsIAtom>(), |atom| {
-                    cx.font_metrics_provider.get_size(atom, gecko_font.mGenericID).0
-                }) };
-
-                let base_size_px = au_to_int_px(base_size as f32);
-                let html_size = self.html_size() as usize;
-                if base_size_px >= 9 && base_size_px <= 16 {
-                    NonNegativeLength::new(FONT_SIZE_MAPPING[(base_size_px - 9) as usize][html_size] as f32)
-                } else {
-                    Au(FONT_SIZE_FACTORS[html_size] * base_size / 100).into()
-                }
-            }
-
-            #[inline]
-            fn from_computed_value(_: &NonNegativeLength) -> Self {
-                unreachable!()
-            }
-        }
-    % endif
-
-    /// This is the ratio applied for font-size: larger
-    /// and smaller by both Firefox and Chrome
-    const LARGER_FONT_SIZE_RATIO: f32 = 1.2;
-
-    impl SpecifiedValue {
-        /// https://html.spec.whatwg.org/multipage/#rules-for-parsing-a-legacy-font-size
-        pub fn from_html_size(size: u8) -> Self {
-            SpecifiedValue::Keyword(match size {
-                // If value is less than 1, let it be 1.
-                0 | 1 => XSmall,
-                2 => Small,
-                3 => Medium,
-                4 => Large,
-                5 => XLarge,
-                6 => XXLarge,
-                // If value is greater than 7, let it be 7.
-                _ => XXXLarge,
-            }, 1., Au(0).into())
-        }
-
-        /// Compute it against a given base font size
-        pub fn to_computed_value_against(
-            &self,
-            context: &Context,
-            base_size: FontBaseSize,
-        ) -> computed_value::T {
-            use values::specified::length::FontRelativeLength;
-            let compose_keyword = |factor| {
-                context.style().get_parent_font()
-                       .clone_font_size().info
-                       .map(|i| i.compose(factor, Au(0).into()))
-            };
-            let mut info = None;
-            let size = match *self {
-                SpecifiedValue::Length(LengthOrPercentage::Length(
-                        NoCalcLength::FontRelative(value))) => {
-                    if let FontRelativeLength::Em(em) = value {
-                        // If the parent font was keyword-derived, this is too.
-                        // Tack the em unit onto the factor
-                        info = compose_keyword(em);
-                    }
-                    value.to_computed_value(context, base_size).into()
-                }
-                SpecifiedValue::Length(LengthOrPercentage::Length(
-                        NoCalcLength::ServoCharacterWidth(value))) => {
-                    value.to_computed_value(base_size.resolve(context)).into()
-                }
-                SpecifiedValue::Length(LengthOrPercentage::Length(
-                        NoCalcLength::Absolute(ref l))) => {
-                    context.maybe_zoom_text(l.to_computed_value(context).into())
-                }
-                SpecifiedValue::Length(LengthOrPercentage::Length(ref l)) => {
-                    l.to_computed_value(context).into()
-                }
-                SpecifiedValue::Length(LengthOrPercentage::Percentage(pc)) => {
-                    // If the parent font was keyword-derived, this is too.
-                    // Tack the % onto the factor
-                    info = compose_keyword(pc.0);
-                    base_size.resolve(context).scale_by(pc.0).into()
-                }
-                SpecifiedValue::Length(LengthOrPercentage::Calc(ref calc)) => {
-                    let parent = context.style().get_parent_font().clone_font_size();
-                    // if we contain em/% units and the parent was keyword derived, this is too
-                    // Extract the ratio/offset and compose it
-                    if (calc.em.is_some() || calc.percentage.is_some()) && parent.info.is_some() {
-                        let ratio = calc.em.unwrap_or(0.) + calc.percentage.map_or(0., |pc| pc.0);
-                        // Compute it, but shave off the font-relative part (em, %)
-                        // This will mean that other font-relative units like ex and ch will be computed against
-                        // the old font even when the font changes. There's no particular "right answer" for what
-                        // to do here -- Gecko recascades as if the font had changed, we instead track the changes
-                        // and reapply, which means that we carry over old computed ex/ch values whilst Gecko
-                        // recomputes new ones. This is enough of an edge case to not really matter.
-                        let abs = calc.to_computed_value_zoomed(context, FontBaseSize::Custom(Au(0).into()))
-                                      .length_component().into();
-                        info = parent.info.map(|i| i.compose(ratio, abs));
-                    }
-                    let calc = calc.to_computed_value_zoomed(context, base_size);
-                    calc.to_used_value(Some(base_size.resolve(context))).unwrap().into()
-                }
-                SpecifiedValue::Keyword(key, fraction, offset) => {
-                    // As a specified keyword, this is keyword derived
-                    info = Some(computed_value::KeywordInfo {
-                        kw: key,
-                        factor: fraction,
-                        offset: offset,
-                    });
-                    let key_len = key.to_computed_value(context).scale_by(fraction) + offset;
-                    context.maybe_zoom_text(key_len).into()
-                }
-                SpecifiedValue::Smaller => {
-                    info = compose_keyword(1. / LARGER_FONT_SIZE_RATIO);
-                    FontRelativeLength::Em(1. / LARGER_FONT_SIZE_RATIO)
-                        .to_computed_value(context, base_size).into()
-                }
-                SpecifiedValue::Larger => {
-                    info = compose_keyword(LARGER_FONT_SIZE_RATIO);
-                    FontRelativeLength::Em(LARGER_FONT_SIZE_RATIO)
-                        .to_computed_value(context, base_size).into()
-                }
-
-                SpecifiedValue::System(_) => {
-                    <%self:nongecko_unreachable>
-                        context.cached_system_font.as_ref().unwrap().font_size.size
-                    </%self:nongecko_unreachable>
-                }
-            };
-            computed_value::T { size, info }
-        }
-    }
+    pub use values::specified::font::FontSize as SpecifiedValue;
 
     #[inline]
     #[allow(missing_docs)]
     pub fn get_initial_value() -> computed_value::T {
         computed_value::T {
             size: Au::from_px(FONT_MEDIUM_PX).into(),
-            info: Some(computed_value::KeywordInfo {
+            info: Some(::values::computed::font::KeywordInfo {
                 kw: KeywordSize::Medium,
                 factor: 1.,
                 offset: Au(0).into(),
             })
         }
     }
 
     #[inline]
     pub fn get_initial_specified_value() -> SpecifiedValue {
-        SpecifiedValue::Keyword(Medium, 1., Au(0).into())
+        SpecifiedValue::Keyword(KeywordSize::Medium, 1., Au(0).into())
     }
 
 
-    impl ToComputedValue for SpecifiedValue {
-        type ComputedValue = computed_value::T;
-
-        #[inline]
-        fn to_computed_value(&self, context: &Context) -> computed_value::T {
-            self.to_computed_value_against(context, FontBaseSize::InheritedStyle)
-        }
-
-        #[inline]
-        fn from_computed_value(computed: &computed_value::T) -> Self {
-            SpecifiedValue::Length(LengthOrPercentage::Length(
-                ToComputedValue::from_computed_value(&computed.size.0)
-            ))
-        }
-    }
-
     /// <length> | <percentage> | <absolute-size> | <relative-size>
     pub fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
                          -> Result<SpecifiedValue, ParseError<'i>> {
         parse_quirky(context, input, AllowQuirks::No)
     }
 
     /// Parses a font-size, with quirks.
     pub fn parse_quirky<'i, 't>(context: &ParserContext,
@@ -1007,29 +651,16 @@ macro_rules! impl_gecko_keyword_conversi
         }
 
         try_match_ident_ignore_ascii_case! { input.expect_ident()?,
             "smaller" => Ok(SpecifiedValue::Smaller),
             "larger" => Ok(SpecifiedValue::Larger),
         }
     }
 
-    impl SpecifiedValue {
-        pub fn system_font(f: SystemFont) -> Self {
-            SpecifiedValue::System(f)
-        }
-        pub fn get_system(&self) -> Option<SystemFont> {
-            if let SpecifiedValue::System(s) = *self {
-                Some(s)
-            } else {
-                None
-            }
-        }
-    }
-
     #[allow(unused_mut)]
     pub fn cascade_specified_font_size(context: &mut Context,
                                        specified_value: &SpecifiedValue,
                                        mut computed: computed_value::T) {
         // we could use clone_language and clone_font_family() here but that's
         // expensive. Do it only in gecko mode for now.
         % if product == "gecko":
             use gecko_bindings::structs::nsIAtom;
new file mode 100644
--- /dev/null
+++ b/servo/components/style/values/computed/font.rs
@@ -0,0 +1,61 @@
+/* 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/. */
+
+//! Computed values for font properties
+
+use app_units::Au;
+use std::fmt;
+use style_traits::ToCss;
+use values::computed::NonNegativeLength;
+use values::specified::font as specified;
+
+#[derive(Copy, Clone, PartialEq, Debug)]
+#[derive(ToAnimatedValue, Animate, ToAnimatedZero, ComputeSquaredDistance)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
+/// The computed value of font-size
+pub struct FontSize {
+    /// The size.
+    pub size: NonNegativeLength,
+    /// If derived from a keyword, the keyword and additional transformations applied to it
+    pub info: Option<KeywordInfo>,
+}
+
+#[derive(Copy, Clone, PartialEq, Debug)]
+#[derive(ToAnimatedValue, Animate, ToAnimatedZero, ComputeSquaredDistance)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
+/// Additional information for keyword-derived font sizes.
+pub struct KeywordInfo {
+    /// The keyword used
+    pub kw: specified::KeywordSize,
+    /// A factor to be multiplied by the computed size of the keyword
+    pub factor: f32,
+    /// An additional Au offset to add to the kw*factor in the case of calcs
+    pub offset: NonNegativeLength,
+}
+
+impl KeywordInfo {
+    /// Given a parent keyword info (self), apply an additional factor/offset to it
+    pub fn compose(self, factor: f32, offset: NonNegativeLength) -> Self {
+        KeywordInfo {
+            kw: self.kw,
+            factor: self.factor * factor,
+            offset: self.offset.scale_by(factor) + offset,
+        }
+    }
+}
+
+impl FontSize {
+    /// The actual computed font size.
+    pub fn size(self) -> Au {
+        self.size.into()
+    }
+}
+
+impl ToCss for FontSize {
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        self.size.to_css(dest)
+    }
+}
--- a/servo/components/style/values/computed/mod.rs
+++ b/servo/components/style/values/computed/mod.rs
@@ -62,16 +62,17 @@ pub mod angle;
 pub mod background;
 pub mod basic_shape;
 pub mod border;
 #[path = "box.rs"]
 pub mod box_;
 pub mod color;
 pub mod effects;
 pub mod flex;
+pub mod font;
 pub mod image;
 #[cfg(feature = "gecko")]
 pub mod gecko;
 pub mod length;
 pub mod percentage;
 pub mod position;
 pub mod rect;
 pub mod svg;
--- a/servo/components/style/values/mod.rs
+++ b/servo/components/style/values/mod.rs
@@ -24,19 +24,16 @@ pub mod generics;
 pub mod specified;
 
 /// A CSS float value.
 pub type CSSFloat = f32;
 
 /// A CSS integer value.
 pub type CSSInteger = i32;
 
-/// The default font size.
-pub const FONT_MEDIUM_PX: i32 = 16;
-
 define_keyword_type!(None_, "none");
 define_keyword_type!(Auto, "auto");
 define_keyword_type!(Normal, "normal");
 
 /// Serialize a normalized value into percentage.
 pub fn serialize_percentage<W>(value: CSSFloat, dest: &mut W)
     -> fmt::Result where W: fmt::Write
 {
new file mode 100644
--- /dev/null
+++ b/servo/components/style/values/specified/font.rs
@@ -0,0 +1,355 @@
+/* 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/. */
+
+//! Specified values for font properties
+
+#[cfg(feature = "gecko")]
+use Atom;
+use app_units::Au;
+use cssparser::Parser;
+use properties::longhands::system_font::SystemFont;
+use std::fmt;
+use style_traits::{ToCss, ParseError};
+use values::computed::{font as computed, Context, NonNegativeLength, ToComputedValue};
+use values::specified::{LengthOrPercentage, NoCalcLength};
+use values::specified::length::FontBaseSize;
+
+#[derive(Clone, Debug, PartialEq)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
+/// A specified font-size value
+pub enum FontSize {
+    /// A length; e.g. 10px.
+    Length(LengthOrPercentage),
+    /// A keyword value, along with a ratio and absolute offset.
+    /// The ratio in any specified keyword value
+    /// will be 1 (with offset 0), but we cascade keywordness even
+    /// after font-relative (percent and em) values
+    /// have been applied, which is where the ratio
+    /// comes in. The offset comes in if we cascaded a calc value,
+    /// where the font-relative portion (em and percentage) will
+    /// go into the ratio, and the remaining units all computed together
+    /// will go into the offset.
+    /// See bug 1355707.
+    Keyword(KeywordSize, f32, NonNegativeLength),
+    /// font-size: smaller
+    Smaller,
+    /// font-size: larger
+    Larger,
+    /// Derived from a specified system font.
+    System(SystemFont)
+}
+
+impl ToCss for FontSize {
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        match *self {
+            FontSize::Length(ref lop) => lop.to_css(dest),
+            FontSize::Keyword(kw, _, _) => kw.to_css(dest),
+            FontSize::Smaller => dest.write_str("smaller"),
+            FontSize::Larger => dest.write_str("larger"),
+            FontSize::System(sys) => sys.to_css(dest),
+        }
+    }
+}
+
+impl From<LengthOrPercentage> for FontSize {
+    fn from(other: LengthOrPercentage) -> Self {
+        FontSize::Length(other)
+    }
+}
+
+/// CSS font keywords
+#[derive(Clone, Copy, Debug, PartialEq)]
+#[derive(ToAnimatedValue, Animate, ToAnimatedZero, ComputeSquaredDistance)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
+#[allow(missing_docs)]
+pub enum KeywordSize {
+    XXSmall = 1, // This is to enable the NonZero optimization
+                 // which simplifies the representation of Option<KeywordSize>
+                 // in bindgen
+    XSmall,
+    Small,
+    Medium,
+    Large,
+    XLarge,
+    XXLarge,
+    // This is not a real font keyword and will not parse
+    // HTML font-size 7 corresponds to this value
+    XXXLarge,
+}
+
+impl KeywordSize {
+    /// Parse a keyword size
+    pub fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
+        try_match_ident_ignore_ascii_case! { input.expect_ident()?,
+            "xx-small" => Ok(KeywordSize::XXSmall),
+            "x-small" => Ok(KeywordSize::XSmall),
+            "small" => Ok(KeywordSize::Small),
+            "medium" => Ok(KeywordSize::Medium),
+            "large" => Ok(KeywordSize::Large),
+            "x-large" => Ok(KeywordSize::XLarge),
+            "xx-large" => Ok(KeywordSize::XXLarge),
+        }
+    }
+
+    /// Convert to an HTML <font size> value
+    pub fn html_size(&self) -> u8 {
+        match *self {
+            KeywordSize::XXSmall => 0,
+            KeywordSize::XSmall => 1,
+            KeywordSize::Small => 2,
+            KeywordSize::Medium => 3,
+            KeywordSize::Large => 4,
+            KeywordSize::XLarge => 5,
+            KeywordSize::XXLarge => 6,
+            KeywordSize::XXXLarge => 7,
+        }
+    }
+}
+
+impl Default for KeywordSize {
+    fn default() -> Self {
+        KeywordSize::Medium
+    }
+}
+
+impl ToCss for KeywordSize {
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        dest.write_str(match *self {
+            KeywordSize::XXSmall => "xx-small",
+            KeywordSize::XSmall => "x-small",
+            KeywordSize::Small => "small",
+            KeywordSize::Medium => "medium",
+            KeywordSize::Large => "large",
+            KeywordSize::XLarge => "x-large",
+            KeywordSize::XXLarge => "xx-large",
+            KeywordSize::XXXLarge => unreachable!("We should never serialize \
+                                      specified values set via
+                                      HTML presentation attributes"),
+        })
+    }
+}
+
+/// This is the ratio applied for font-size: larger
+/// and smaller by both Firefox and Chrome
+const LARGER_FONT_SIZE_RATIO: f32 = 1.2;
+
+/// The default font size.
+pub const FONT_MEDIUM_PX: i32 = 16;
+
+#[cfg(feature = "servo")]
+impl ToComputedValue for KeywordSize {
+    type ComputedValue = NonNegativeLength;
+    #[inline]
+    fn to_computed_value(&self, _: &Context) -> NonNegativeLength {
+        // https://drafts.csswg.org/css-fonts-3/#font-size-prop
+        match *self {
+            KeywordSize::XXSmall => Au::from_px(FONT_MEDIUM_PX) * 3 / 5,
+            KeywordSize::XSmall => Au::from_px(FONT_MEDIUM_PX) * 3 / 4,
+            KeywordSize::Small => Au::from_px(FONT_MEDIUM_PX) * 8 / 9,
+            KeywordSize::Medium => Au::from_px(FONT_MEDIUM_PX),
+            KeywordSize::Large => Au::from_px(FONT_MEDIUM_PX) * 6 / 5,
+            KeywordSize::XLarge => Au::from_px(FONT_MEDIUM_PX) * 3 / 2,
+            KeywordSize::XXLarge => Au::from_px(FONT_MEDIUM_PX) * 2,
+            KeywordSize::XXXLarge => Au::from_px(FONT_MEDIUM_PX) * 3,
+        }.into()
+    }
+
+    #[inline]
+    fn from_computed_value(_: &NonNegativeLength) -> Self {
+        unreachable!()
+    }
+}
+
+#[cfg(feature = "gecko")]
+impl ToComputedValue for KeywordSize {
+    type ComputedValue = NonNegativeLength;
+    #[inline]
+    fn to_computed_value(&self, cx: &Context) -> NonNegativeLength {
+        use gecko_bindings::structs::nsIAtom;
+        use values::specified::length::au_to_int_px;
+        // Data from nsRuleNode.cpp in Gecko
+        // Mapping from base size and HTML size to pixels
+        // The first index is (base_size - 9), the second is the
+        // HTML size. "0" is CSS keyword xx-small, not HTML size 0,
+        // since HTML size 0 is the same as 1.
+        //
+        //  xxs   xs      s      m     l      xl     xxl   -
+        //  -     0/1     2      3     4      5      6     7
+        static FONT_SIZE_MAPPING: [[i32; 8]; 8] = [
+            [9,    9,     9,     9,    11,    14,    18,    27],
+            [9,    9,     9,    10,    12,    15,    20,    30],
+            [9,    9,    10,    11,    13,    17,    22,    33],
+            [9,    9,    10,    12,    14,    18,    24,    36],
+            [9,   10,    12,    13,    16,    20,    26,    39],
+            [9,   10,    12,    14,    17,    21,    28,    42],
+            [9,   10,    13,    15,    18,    23,    30,    45],
+            [9,   10,    13,    16,    18,    24,    32,    48]
+        ];
+
+        static FONT_SIZE_FACTORS: [i32; 8] = [60, 75, 89, 100, 120, 150, 200, 300];
+
+        // XXXManishearth handle quirks mode (bug 1401322)
+
+        let ref gecko_font = cx.style().get_font().gecko();
+        let base_size = unsafe { Atom::with(gecko_font.mLanguage.raw::<nsIAtom>(), |atom| {
+            cx.font_metrics_provider.get_size(atom, gecko_font.mGenericID).0
+        }) };
+
+        let base_size_px = au_to_int_px(base_size as f32);
+        let html_size = self.html_size() as usize;
+        if base_size_px >= 9 && base_size_px <= 16 {
+            Au::from_px(FONT_SIZE_MAPPING[(base_size_px - 9) as usize][html_size]).into()
+        } else {
+            Au(FONT_SIZE_FACTORS[html_size] * base_size / 100).into()
+        }
+    }
+
+    #[inline]
+    fn from_computed_value(_: &NonNegativeLength) -> Self {
+        unreachable!()
+    }
+}
+
+impl FontSize {
+    /// https://html.spec.whatwg.org/multipage/#rules-for-parsing-a-legacy-font-size
+    pub fn from_html_size(size: u8) -> Self {
+        FontSize::Keyword(match size {
+            // If value is less than 1, let it be 1.
+            0 | 1 => KeywordSize::XSmall,
+            2 => KeywordSize::Small,
+            3 => KeywordSize::Medium,
+            4 => KeywordSize::Large,
+            5 => KeywordSize::XLarge,
+            6 => KeywordSize::XXLarge,
+            // If value is greater than 7, let it be 7.
+            _ => KeywordSize::XXXLarge,
+        }, 1., Au(0).into())
+    }
+
+    /// Compute it against a given base font size
+    pub fn to_computed_value_against(
+        &self,
+        context: &Context,
+        base_size: FontBaseSize,
+    ) -> computed::FontSize {
+        use values::specified::length::FontRelativeLength;
+
+        let compose_keyword = |factor| {
+            context.style().get_parent_font()
+                   .clone_font_size().info
+                   .map(|i| i.compose(factor, Au(0).into()))
+        };
+        let mut info = None;
+        let size = match *self {
+            FontSize::Length(LengthOrPercentage::Length(
+                    NoCalcLength::FontRelative(value))) => {
+                if let FontRelativeLength::Em(em) = value {
+                    // If the parent font was keyword-derived, this is too.
+                    // Tack the em unit onto the factor
+                    info = compose_keyword(em);
+                }
+                value.to_computed_value(context, base_size).into()
+            }
+            FontSize::Length(LengthOrPercentage::Length(
+                    NoCalcLength::ServoCharacterWidth(value))) => {
+                value.to_computed_value(base_size.resolve(context)).into()
+            }
+            FontSize::Length(LengthOrPercentage::Length(
+                    NoCalcLength::Absolute(ref l))) => {
+                context.maybe_zoom_text(l.to_computed_value(context).into())
+            }
+            FontSize::Length(LengthOrPercentage::Length(ref l)) => {
+                l.to_computed_value(context).into()
+            }
+            FontSize::Length(LengthOrPercentage::Percentage(pc)) => {
+                // If the parent font was keyword-derived, this is too.
+                // Tack the % onto the factor
+                info = compose_keyword(pc.0);
+                base_size.resolve(context).scale_by(pc.0).into()
+            }
+            FontSize::Length(LengthOrPercentage::Calc(ref calc)) => {
+                let parent = context.style().get_parent_font().clone_font_size();
+                // if we contain em/% units and the parent was keyword derived, this is too
+                // Extract the ratio/offset and compose it
+                if (calc.em.is_some() || calc.percentage.is_some()) && parent.info.is_some() {
+                    let ratio = calc.em.unwrap_or(0.) + calc.percentage.map_or(0., |pc| pc.0);
+                    // Compute it, but shave off the font-relative part (em, %)
+                    // This will mean that other font-relative units like ex and ch will be computed against
+                    // the old font even when the font changes. There's no particular "right answer" for what
+                    // to do here -- Gecko recascades as if the font had changed, we instead track the changes
+                    // and reapply, which means that we carry over old computed ex/ch values whilst Gecko
+                    // recomputes new ones. This is enough of an edge case to not really matter.
+                    let abs = calc.to_computed_value_zoomed(context, FontBaseSize::Custom(Au(0).into()))
+                                  .length_component().into();
+                    info = parent.info.map(|i| i.compose(ratio, abs));
+                }
+                let calc = calc.to_computed_value_zoomed(context, base_size);
+                calc.to_used_value(Some(base_size.resolve(context))).unwrap().into()
+            }
+            FontSize::Keyword(key, fraction, offset) => {
+                // As a specified keyword, this is keyword derived
+                info = Some(computed::KeywordInfo {
+                    kw: key,
+                    factor: fraction,
+                    offset: offset,
+                });
+                context.maybe_zoom_text(key.to_computed_value(context).scale_by(fraction) + offset)
+            }
+            FontSize::Smaller => {
+                info = compose_keyword(1. / LARGER_FONT_SIZE_RATIO);
+                FontRelativeLength::Em(1. / LARGER_FONT_SIZE_RATIO)
+                    .to_computed_value(context, base_size).into()
+            }
+            FontSize::Larger => {
+                info = compose_keyword(LARGER_FONT_SIZE_RATIO);
+                FontRelativeLength::Em(LARGER_FONT_SIZE_RATIO)
+                    .to_computed_value(context, base_size).into()
+            }
+
+            FontSize::System(_) => {
+                #[cfg(feature = "servo")] {
+                    unreachable!()
+                }
+                #[cfg(feature = "gecko")] {
+                    context.cached_system_font.as_ref().unwrap().font_size.size
+                }
+            }
+        };
+        computed::FontSize { size, info }
+    }
+}
+
+impl ToComputedValue for FontSize {
+    type ComputedValue = computed::FontSize;
+
+    #[inline]
+    fn to_computed_value(&self, context: &Context) -> computed::FontSize {
+        self.to_computed_value_against(context, FontBaseSize::InheritedStyle)
+    }
+
+    #[inline]
+    fn from_computed_value(computed: &computed::FontSize) -> Self {
+        FontSize::Length(LengthOrPercentage::Length(
+            ToComputedValue::from_computed_value(&computed.size.0)
+        ))
+    }
+}
+
+impl FontSize {
+    /// Construct a system font value.
+    pub fn system_font(f: SystemFont) -> Self {
+        FontSize::System(f)
+    }
+
+    /// Obtain the system font, if any
+    pub fn get_system(&self) -> Option<SystemFont> {
+        if let FontSize::System(s) = *self {
+            Some(s)
+        } else {
+            None
+        }
+    }
+}
--- a/servo/components/style/values/specified/length.rs
+++ b/servo/components/style/values/specified/length.rs
@@ -13,17 +13,17 @@ use font_metrics::FontMetricsQueryResult
 use parser::{Parse, ParserContext};
 use std::{cmp, fmt, mem};
 use std::ascii::AsciiExt;
 use std::ops::{Add, Mul};
 use style_traits::{ToCss, ParseError, StyleParseError};
 use style_traits::values::specified::AllowedNumericType;
 use stylesheets::CssRuleType;
 use super::{AllowQuirks, Number, ToComputedValue, Percentage};
-use values::{Auto, CSSFloat, Either, FONT_MEDIUM_PX, None_, Normal};
+use values::{Auto, CSSFloat, Either, None_, Normal};
 use values::{ExtremumLength, serialize_dimension};
 use values::computed::{self, CSSPixelLength, Context};
 use values::generics::NonNegative;
 use values::specified::NonNegativeNumber;
 use values::specified::calc::CalcNode;
 
 pub use values::specified::calc::CalcLengthOrPercentage;
 pub use super::image::{ColorStop, EndingShape as GradientEndingShape, Gradient};
@@ -566,22 +566,16 @@ impl NoCalcLength {
         match *self {
             NoCalcLength::Absolute(length) => length.is_zero(),
             #[cfg(feature = "gecko")]
             NoCalcLength::Physical(length) => length.is_zero(),
             _ => false
         }
     }
 
-    #[inline]
-    /// Returns a `medium` length.
-    pub fn medium() -> NoCalcLength {
-        NoCalcLength::Absolute(AbsoluteLength::Px(FONT_MEDIUM_PX as f32))
-    }
-
     /// Get an absolute length from a px value.
     #[inline]
     pub fn from_px(px_value: CSSFloat) -> NoCalcLength {
         NoCalcLength::Absolute(AbsoluteLength::Px(px_value))
     }
 }
 
 /// An extension to `NoCalcLength` to parse `calc` expressions.
--- a/servo/components/style/values/specified/mod.rs
+++ b/servo/components/style/values/specified/mod.rs
@@ -60,16 +60,17 @@ pub mod background;
 pub mod basic_shape;
 pub mod border;
 #[path = "box.rs"]
 pub mod box_;
 pub mod calc;
 pub mod color;
 pub mod effects;
 pub mod flex;
+pub mod font;
 #[cfg(feature = "gecko")]
 pub mod gecko;
 pub mod grid;
 pub mod image;
 pub mod length;
 pub mod percentage;
 pub mod position;
 pub mod rect;