diff --git a/components/style/properties/gecko.mako.rs b/components/style/properties/gecko.mako.rs index 6197306879b..87f5ad0d418 100644 --- a/components/style/properties/gecko.mako.rs +++ b/components/style/properties/gecko.mako.rs @@ -1352,11 +1352,170 @@ fn static_assert() { pub fn set_font_size(&mut self, v: longhands::font_size::computed_value::T) { self.gecko.mFont.size = v.0; self.gecko.mSize = v.0; + self.gecko.mScriptUnconstrainedSize = v.0; } - pub fn copy_font_size_from(&mut self, other: &Self) { - self.gecko.mFont.size = other.gecko.mFont.size; - self.gecko.mSize = other.gecko.mSize; + + /// Set font size, taking into account scriptminsize and scriptlevel + /// Returns Some(size) if we have to recompute the script unconstrained size + pub fn apply_font_size(&mut self, v: longhands::font_size::computed_value::T, + parent: &Self) -> Option { + let (adjusted_size, adjusted_unconstrained_size) + = self.calculate_script_level_size(parent); + // In this case, we have been unaffected by scriptminsize, ignore it + if parent.gecko.mSize == parent.gecko.mScriptUnconstrainedSize && + adjusted_size == adjusted_unconstrained_size { + self.set_font_size(v); + None + } else { + self.gecko.mFont.size = v.0; + self.gecko.mSize = v.0; + Some(Au(parent.gecko.mScriptUnconstrainedSize)) + } } + + pub fn apply_unconstrained_font_size(&mut self, v: Au) { + self.gecko.mScriptUnconstrainedSize = v.0; + } + + /// Calculates the constrained and unconstrained font sizes to be inherited + /// from the parent. + /// + /// See ComputeScriptLevelSize in Gecko's nsRuleNode.cpp + /// + /// scriptlevel is a property that affects how font-size is inherited. If scriptlevel is + /// +1, for example, it will inherit as the script size multiplier times + /// the parent font. This does not affect cases where the font-size is + /// explicitly set. + /// + /// However, this transformation is not allowed to reduce the size below + /// scriptminsize. If this inheritance will reduce it to below + /// scriptminsize, it will be set to scriptminsize or the parent size, + /// whichever is smaller (the parent size could be smaller than the min size + /// because it was explicitly specified). + /// + /// Now, within a node that has inherited a font-size which was + /// crossing scriptminsize once the scriptlevel was applied, a negative + /// scriptlevel may be used to increase the size again. + /// + /// This should work, however if we have already been capped by the + /// scriptminsize multiple times, this can lead to a jump in the size. + /// + /// For example, if we have text of the form: + /// + /// huge large medium small tiny reallytiny tiny small medium huge + /// + /// which is represented by progressive nesting and scriptlevel values of + /// +1 till the center after which the scriptlevel is -1, the "tiny"s should + /// be the same size, as should be the "small"s and "medium"s, etc. + /// + /// However, if scriptminsize kicked it at around "medium", then + /// medium/tiny/reallytiny will all be the same size (the min size). + /// A -1 scriptlevel change after this will increase the min size by the + /// multiplier, making the second tiny larger than medium. + /// + /// Instead, we wish for the second "tiny" to still be capped by the script + /// level, and when we reach the second "large", it should be the same size + /// as the original one. + /// + /// We do this by cascading two separate font sizes. The font size (mSize) + /// is the actual displayed font size. The unconstrained font size + /// (mScriptUnconstrainedSize) is the font size in the situation where + /// scriptminsize never applied. + /// + /// We calculate the proposed inherited font size based on scriptlevel and + /// the parent unconstrained size, instead of using the parent font size. + /// This is stored in the node's unconstrained size and will also be stored + /// in the font size provided that it is above the min size. + /// + /// All of this only applies when inheriting. When the font size is + /// manually set, scriptminsize does not apply, and both the real and + /// unconstrained size are set to the explicit value. However, if the font + /// size is manually set to an em or percent unit, the unconstrained size + /// will be set to the value of that unit computed against the parent + /// unconstrained size, whereas the font size will be set computing against + /// the parent font size. + pub fn calculate_script_level_size(&self, parent: &Self) -> (Au, Au) { + use std::cmp; + + let delta = self.gecko.mScriptLevel - parent.gecko.mScriptLevel; + + let parent_size = Au(parent.gecko.mSize); + let parent_unconstrained_size = Au(parent.gecko.mScriptUnconstrainedSize); + + if delta == 0 { + return (parent_size, parent_unconstrained_size) + } + + /// XXXManishearth this should also handle text zoom + let min = Au(parent.gecko.mScriptMinSize); + + let scale = (parent.gecko.mScriptSizeMultiplier as f32).powi(delta as i32); + + let new_size = parent_size.scale_by(scale); + let new_unconstrained_size = parent_unconstrained_size.scale_by(scale); + + if scale < 1. { + // The parent size can be smaller than scriptminsize, + // e.g. if it was specified explicitly. Don't scale + // in this case, but we don't want to set it to scriptminsize + // either since that will make it larger. + if parent_size < min { + (parent_size, new_unconstrained_size) + } else { + (cmp::max(min, new_size), new_unconstrained_size) + } + } else { + // If the new unconstrained size is larger than the min size, + // this means we have escaped the grasp of scriptminsize + // and can revert to using the unconstrained size. + // However, if the new size is even larger (perhaps due to usage + // of em units), use that instead. + (cmp::min(new_size, cmp::max(new_unconstrained_size, min)), + new_unconstrained_size) + } + } + + /// This function will also handle scriptminsize and scriptlevel + /// so should not be called when you just want the font sizes to be copied. + /// Hence the different name. + pub fn inherit_font_size_from(&mut self, parent: &Self, + kw_inherited_size: Option) { + let (adjusted_size, adjusted_unconstrained_size) + = self.calculate_script_level_size(parent); + if adjusted_size.0 != parent.gecko.mSize || + adjusted_unconstrained_size.0 != parent.gecko.mScriptUnconstrainedSize { + // This is incorrect. When there is both a keyword size being inherited + // and a scriptlevel change, we must handle the keyword size the same + // way we handle em units. This complicates things because we now have + // to keep track of the adjusted and unadjusted ratios in the kw font size. + // This only affects the use case of a generic font being used in MathML. + // + // If we were to fix this I would prefer doing it by removing the + // ruletree walk on the Gecko side in nsRuleNode::SetGenericFont + // and instead using extra bookkeeping in the mSize and mScriptUnconstrainedSize + // values, and reusing those instead of font_size_keyword. + + + // In the case that MathML has given us an adjusted size, apply it. + // Keep track of the unconstrained adjusted size. + self.gecko.mFont.size = adjusted_size.0; + self.gecko.mSize = adjusted_size.0; + self.gecko.mScriptUnconstrainedSize = adjusted_unconstrained_size.0; + } else if let Some(size) = kw_inherited_size { + // Parent element was a keyword-derived size. + self.gecko.mFont.size = size.0; + self.gecko.mSize = size.0; + // MathML constraints didn't apply here, so we can ignore this. + self.gecko.mScriptUnconstrainedSize = size.0; + } else { + // MathML isn't affecting us, and our parent element does not + // have a keyword-derived size. Set things normally. + self.gecko.mFont.size = parent.gecko.mFont.size; + self.gecko.mSize = parent.gecko.mSize; + self.gecko.mScriptUnconstrainedSize = parent.gecko.mScriptUnconstrainedSize; + } + } + pub fn clone_font_size(&self) -> longhands::font_size::computed_value::T { Au(self.gecko.mSize) } diff --git a/components/style/properties/helpers.mako.rs b/components/style/properties/helpers.mako.rs index 49a751bd59f..a6cb68f160f 100644 --- a/components/style/properties/helpers.mako.rs +++ b/components/style/properties/helpers.mako.rs @@ -267,31 +267,18 @@ DeclaredValue::Value(ref specified_value) => { let computed = specified_value.to_computed_value(context); % if property.ident == "font_size": - if let longhands::font_size::SpecifiedValue::Keyword(kw, fraction) - = **specified_value { - context.mutate_style().font_size_keyword = Some((kw, fraction)); - } else if let Some(ratio) = specified_value.as_font_ratio() { - // In case a font-size-relative value was applied to a keyword - // value, we must preserve this fact in case the generic font family - // changes. relative values (em and %) applied to keywords must be - // recomputed from the base size for the keyword and the relative size. - // - // See bug 1355707 - if let Some((kw, fraction)) = context.inherited_style().font_size_keyword { - context.mutate_style().font_size_keyword = Some((kw, fraction * ratio)); - } else { - context.mutate_style().font_size_keyword = None; - } - } else { - context.mutate_style().font_size_keyword = None; - } - % endif - % if property.has_uncacheable_values: - context.mutate_style().mutate_${data.current_style_struct.name_lower}() - .set_${property.ident}(computed, cacheable ${maybe_wm}); + longhands::font_size::cascade_specified_font_size(context, + specified_value, + computed, + inherited_style.get_font()); % else: - context.mutate_style().mutate_${data.current_style_struct.name_lower}() - .set_${property.ident}(computed ${maybe_wm}); + % if property.has_uncacheable_values: + context.mutate_style().mutate_${data.current_style_struct.name_lower}() + .set_${property.ident}(computed, cacheable ${maybe_wm}); + % else: + context.mutate_style().mutate_${data.current_style_struct.name_lower}() + .set_${property.ident}(computed ${maybe_wm}); + % endif % endif } DeclaredValue::WithVariables(_) => unreachable!(), @@ -301,13 +288,7 @@ % endif CSSWideKeyword::Initial => { % if property.ident == "font_size": - // font-size's default ("medium") does not always - // compute to the same value and depends on the font - let computed = longhands::font_size::get_initial_specified_value() - .to_computed_value(context); - context.mutate_style().mutate_${data.current_style_struct.name_lower}() - .set_font_size(computed); - context.mutate_style().font_size_keyword = Some((Default::default(), 1.)); + longhands::font_size::cascade_initial_font_size(context); % else: // We assume that it's faster to use copy_*_from rather than // set_*(get_initial_value()); @@ -328,11 +309,12 @@ *cacheable = false; let inherited_struct = inherited_style.get_${data.current_style_struct.name_lower}(); - context.mutate_style().mutate_${data.current_style_struct.name_lower}() - .copy_${property.ident}_from(inherited_struct ${maybe_wm}); + % if property.ident == "font_size": - context.mutate_style().font_size_keyword = - context.inherited_style.font_size_keyword; + longhands::font_size::cascade_inherit_font_size(context, inherited_struct); + % else: + context.mutate_style().mutate_${data.current_style_struct.name_lower}() + .copy_${property.ident}_from(inherited_struct ${maybe_wm}); % endif } } diff --git a/components/style/properties/longhand/font.mako.rs b/components/style/properties/longhand/font.mako.rs index df95d9f464c..e9ad7208c76 100644 --- a/components/style/properties/longhand/font.mako.rs +++ b/components/style/properties/longhand/font.mako.rs @@ -413,11 +413,13 @@ ${helpers.single_keyword("font-variant-caps", <%helpers:longhand name="font-size" need_clone="True" animation_type="normal" spec="https://drafts.csswg.org/css-fonts/#propdef-font-size"> use app_units::Au; + use properties::style_structs::Font; use std::fmt; use style_traits::ToCss; use values::{FONT_MEDIUM_PX, HasViewportPercentage}; use values::specified::{FontRelativeLength, LengthOrPercentage, Length}; use values::specified::{NoCalcLength, Percentage}; + use values::specified::length::FontBaseSize; impl ToCss for SpecifiedValue { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { @@ -627,6 +629,43 @@ ${helpers.single_keyword("font-variant-caps", } None } + + /// Compute it against a given base font size + pub fn to_computed_value_against(&self, context: &Context, base_size: FontBaseSize) -> Au { + use values::specified::length::FontRelativeLength; + match *self { + SpecifiedValue::Length(LengthOrPercentage::Length( + NoCalcLength::FontRelative(value))) => { + value.to_computed_value(context, base_size) + } + SpecifiedValue::Length(LengthOrPercentage::Length( + NoCalcLength::ServoCharacterWidth(value))) => { + value.to_computed_value(base_size.resolve(context)) + } + SpecifiedValue::Length(LengthOrPercentage::Length(ref l)) => { + l.to_computed_value(context) + } + SpecifiedValue::Length(LengthOrPercentage::Percentage(Percentage(value))) => { + base_size.resolve(context).scale_by(value) + } + SpecifiedValue::Length(LengthOrPercentage::Calc(ref calc)) => { + let calc = calc.to_computed_value(context); + calc.length() + base_size.resolve(context) + .scale_by(calc.percentage()) + } + SpecifiedValue::Keyword(ref key, fraction) => { + key.to_computed_value(context).scale_by(fraction) + } + SpecifiedValue::Smaller => { + FontRelativeLength::Em(0.85) + .to_computed_value(context, base_size) + } + SpecifiedValue::Larger => { + FontRelativeLength::Em(1.2) + .to_computed_value(context, base_size) + } + } + } } #[inline] @@ -640,44 +679,13 @@ ${helpers.single_keyword("font-variant-caps", SpecifiedValue::Keyword(Medium, 1.) } + impl ToComputedValue for SpecifiedValue { type ComputedValue = computed_value::T; #[inline] fn to_computed_value(&self, context: &Context) -> computed_value::T { - use values::specified::length::FontRelativeLength; - match *self { - SpecifiedValue::Length(LengthOrPercentage::Length( - NoCalcLength::FontRelative(value))) => { - value.to_computed_value(context, /* use inherited */ true) - } - SpecifiedValue::Length(LengthOrPercentage::Length( - NoCalcLength::ServoCharacterWidth(value))) => { - value.to_computed_value(context.inherited_style().get_font().clone_font_size()) - } - SpecifiedValue::Length(LengthOrPercentage::Length(ref l)) => { - l.to_computed_value(context) - } - SpecifiedValue::Length(LengthOrPercentage::Percentage(Percentage(value))) => { - context.inherited_style().get_font().clone_font_size().scale_by(value) - } - SpecifiedValue::Length(LengthOrPercentage::Calc(ref calc)) => { - let calc = calc.to_computed_value(context); - calc.length() + context.inherited_style().get_font().clone_font_size() - .scale_by(calc.percentage()) - } - SpecifiedValue::Keyword(ref key, fraction) => { - key.to_computed_value(context).scale_by(fraction) - } - SpecifiedValue::Smaller => { - FontRelativeLength::Em(0.85).to_computed_value(context, - /* use_inherited */ true) - } - SpecifiedValue::Larger => { - FontRelativeLength::Em(1.2).to_computed_value(context, - /* use_inherited */ true) - } - } + self.to_computed_value_against(context, FontBaseSize::InheritedStyle) } #[inline] @@ -703,6 +711,66 @@ ${helpers.single_keyword("font-variant-caps", _ => Err(()) } } + + pub fn cascade_specified_font_size(context: &mut Context, + specified_value: &SpecifiedValue, + computed: Au, + parent: &Font) { + if let SpecifiedValue::Keyword(kw, fraction) + = *specified_value { + context.mutate_style().font_size_keyword = Some((kw, fraction)); + } else if let Some(ratio) = specified_value.as_font_ratio() { + // In case a font-size-relative value was applied to a keyword + // value, we must preserve this fact in case the generic font family + // changes. relative values (em and %) applied to keywords must be + // recomputed from the base size for the keyword and the relative size. + // + // See bug 1355707 + if let Some((kw, fraction)) = context.inherited_style().font_size_keyword { + context.mutate_style().font_size_keyword = Some((kw, fraction * ratio)); + } else { + context.mutate_style().font_size_keyword = None; + } + } else { + context.mutate_style().font_size_keyword = None; + } + + let parent_unconstrained = context.mutate_style() + .mutate_font() + .apply_font_size(computed, + parent); + + if let Some(parent) = parent_unconstrained { + let new_unconstrained = specified_value + .to_computed_value_against(context, FontBaseSize::Custom(parent)); + context.mutate_style() + .mutate_font() + .apply_unconstrained_font_size(new_unconstrained); + } + } + + pub fn cascade_inherit_font_size(context: &mut Context, parent: &Font) { + // If inheriting, we must recompute font-size in case of language changes + // using the font_size_keyword. We also need to do this to handle + // mathml scriptlevel changes + let kw_inherited_size = context.style().font_size_keyword.map(|(kw, ratio)| { + SpecifiedValue::Keyword(kw, ratio).to_computed_value(context) + }); + context.mutate_style().mutate_font() + .inherit_font_size_from(parent, kw_inherited_size); + context.mutate_style().font_size_keyword = + context.inherited_style.font_size_keyword; + } + + pub fn cascade_initial_font_size(context: &mut Context) { + // font-size's default ("medium") does not always + // compute to the same value and depends on the font + let computed = longhands::font_size::get_initial_specified_value() + .to_computed_value(context); + context.mutate_style().mutate_${data.current_style_struct.name_lower}() + .set_font_size(computed); + context.mutate_style().font_size_keyword = Some((Default::default(), 1.)); + } <%helpers:longhand products="gecko" name="font-size-adjust" animation_type="normal" diff --git a/components/style/properties/properties.mako.rs b/components/style/properties/properties.mako.rs index bb5edb137aa..b53c9254659 100644 --- a/components/style/properties/properties.mako.rs +++ b/components/style/properties/properties.mako.rs @@ -1429,6 +1429,7 @@ pub use gecko_properties::style_structs; /// The module where all the style structs are defined. #[cfg(feature = "servo")] pub mod style_structs { + use app_units::Au; use fnv::FnvHasher; use super::longhands; use std::hash::{Hash, Hasher}; @@ -1526,6 +1527,23 @@ pub mod style_structs { self.font_family.hash(&mut hasher); self.hash = hasher.finish() } + + /// (Servo does not handle MathML, so this just calls copy_font_size_from) + pub fn inherit_font_size_from(&mut self, parent: &Self, + _: Option) { + self.copy_font_size_from(parent); + } + /// (Servo does not handle MathML, so this just calls set_font_size) + pub fn apply_font_size(&mut self, + v: longhands::font_size::computed_value::T, + _: &Self) -> Option { + self.set_font_size(v); + None + } + /// (Servo does not handle MathML, so this does nothing) + pub fn apply_unconstrained_font_size(&mut self, _: Au) { + } + % elif style_struct.name == "Outline": /// Whether the outline-width property is non-zero. #[inline] @@ -2229,6 +2247,7 @@ pub fn apply_declarations<'a, F, I>(device: &Device, | LonghandId::AnimationName | LonghandId::TransitionProperty | LonghandId::XLang + | LonghandId::MozScriptLevel % endif ); if @@ -2300,12 +2319,12 @@ pub fn apply_declarations<'a, F, I>(device: &Device, &mut cacheable, &mut cascade_info, error_reporter); - } else if let Some((kw, fraction)) = inherited_style.font_size_keyword { - // Font size keywords will inherit as keywords and be recomputed - // each time. + } else { + // Font size must be explicitly inherited to handle keyword + // sizes and scriptlevel let discriminant = LonghandId::FontSize as usize; - let size = PropertyDeclaration::FontSize( - longhands::font_size::SpecifiedValue::Keyword(kw, fraction) + let size = PropertyDeclaration::CSSWideKeyword( + LonghandId::FontSize, CSSWideKeyword::Inherit ); (CASCADE_PROPERTY[discriminant])(&size, inherited_style, diff --git a/components/style/values/computed/length.rs b/components/style/values/computed/length.rs index c21a03c1ca6..be5f70e0807 100644 --- a/components/style/values/computed/length.rs +++ b/components/style/values/computed/length.rs @@ -10,7 +10,7 @@ use std::fmt; use style_traits::ToCss; use super::{Number, ToComputedValue, Context}; use values::{Auto, CSSFloat, Either, ExtremumLength, None_, Normal, specified}; -use values::specified::length::{AbsoluteLength, FontRelativeLength, ViewportPercentageLength}; +use values::specified::length::{AbsoluteLength, FontBaseSize, FontRelativeLength, ViewportPercentageLength}; pub use super::image::{EndingShape as GradientShape, Gradient, GradientKind, Image}; pub use super::image::{LengthOrKeyword, LengthOrPercentageOrKeyword}; @@ -25,7 +25,7 @@ impl ToComputedValue for specified::NoCalcLength { specified::NoCalcLength::Absolute(length) => length.to_computed_value(context), specified::NoCalcLength::FontRelative(length) => - length.to_computed_value(context, /* use inherited */ false), + length.to_computed_value(context, FontBaseSize::CurrentStyle), specified::NoCalcLength::ViewportPercentage(length) => length.to_computed_value(context.viewport_size()), specified::NoCalcLength::ServoCharacterWidth(length) => @@ -159,7 +159,7 @@ impl ToComputedValue for specified::CalcLengthOrPercentage { self.ex.map(FontRelativeLength::Ex), self.rem.map(FontRelativeLength::Rem)] { if let Some(val) = *val { - length += val.to_computed_value(context, /* use inherited */ false); + length += val.to_computed_value(context, FontBaseSize::CurrentStyle); } } diff --git a/components/style/values/specified/length.rs b/components/style/values/specified/length.rs index 4d41be3e07e..7897b781504 100644 --- a/components/style/values/specified/length.rs +++ b/components/style/values/specified/length.rs @@ -76,10 +76,31 @@ impl ToCss for FontRelativeLength { } } +/// A source to resolve font-relative units against +pub enum FontBaseSize { + /// Use the font-size of the current element + CurrentStyle, + /// Use the inherited font-size + InheritedStyle, + /// Use a custom base size + Custom(Au), +} + +impl FontBaseSize { + /// Calculate the actual size for a given context + pub fn resolve(&self, context: &Context) -> Au { + match *self { + FontBaseSize::Custom(size) => size, + FontBaseSize::CurrentStyle => context.style().get_font().clone_font_size(), + FontBaseSize::InheritedStyle => context.inherited_style().get_font().clone_font_size(), + } + } +} + impl FontRelativeLength { - /// Computes the font-relative length. We use the use_inherited flag to - /// special-case the computation of font-size. - pub fn to_computed_value(&self, context: &Context, use_inherited: bool) -> Au { + /// Computes the font-relative length. We use the base_size + /// flag to pass a different size for computing font-size and unconstrained font-size + pub fn to_computed_value(&self, context: &Context, base_size: FontBaseSize) -> Au { fn query_font_metrics(context: &Context, reference_font_size: Au) -> FontMetricsQueryResult { context.font_metrics_provider.query(context.style().get_font(), reference_font_size, @@ -88,11 +109,7 @@ impl FontRelativeLength { context.device) } - let reference_font_size = if use_inherited { - context.inherited_style().get_font().clone_font_size() - } else { - context.style().get_font().clone_font_size() - }; + let reference_font_size = base_size.resolve(context); let root_font_size = context.style().root_font_size; match *self {