diff --git a/components/style/animation.rs b/components/style/animation.rs index 0e00cbca0ca6c..9c79aaf5a4e5e 100644 --- a/components/style/animation.rs +++ b/components/style/animation.rs @@ -403,6 +403,7 @@ fn compute_style_for_animation_step(context: &SharedStyleContext, previous_style, /* cascade_info = */ None, context.error_reporter.clone(), + /* Metrics provider */ None, CascadeFlags::empty()); computed } diff --git a/components/style/font_metrics.rs b/components/style/font_metrics.rs new file mode 100644 index 0000000000000..dac7a69ee299b --- /dev/null +++ b/components/style/font_metrics.rs @@ -0,0 +1,35 @@ +/* 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/. */ + +use Atom; +use app_units::Au; +use euclid::Size2D; +use std::fmt; + +/// Represents the font metrics that style needs from a font to compute the +/// value of certain CSS units like `ex`. +#[derive(Debug, PartialEq, Clone)] +pub struct FontMetrics { + pub x_height: Au, + pub zero_advance_measure: Size2D, +} + +#[derive(Debug, PartialEq, Clone)] +pub enum FontMetricsQueryResult { + Available(Option), + NotAvailable, +} + +/// A trait used to represent something capable of providing us font metrics. +pub trait FontMetricsProvider: Send + Sync + fmt::Debug { + /// Obtain the metrics for given font family. + /// + /// TODO: We could make this take the full list, I guess, and save a few + /// virtual calls. + /// + /// This is not too common in practice though. + fn query(&self, _font_name: &Atom) -> FontMetricsQueryResult { + FontMetricsQueryResult::NotAvailable + } +} diff --git a/components/style/gecko/conversions.rs b/components/style/gecko/conversions.rs index 6202b5f852fc6..950003a224913 100644 --- a/components/style/gecko/conversions.rs +++ b/components/style/gecko/conversions.rs @@ -38,7 +38,7 @@ impl From for nsStyleCoord_CalcValue { fn from(other: CalcLengthOrPercentage) -> nsStyleCoord_CalcValue { let has_percentage = other.percentage.is_some(); nsStyleCoord_CalcValue { - mLength: other.length.map_or(0, |l| l.0), + mLength: other.length.0, mPercent: other.percentage.unwrap_or(0.0), mHasPercent: has_percentage, } @@ -53,7 +53,7 @@ impl From for CalcLengthOrPercentage { None }; CalcLengthOrPercentage { - length: Some(Au(other.mLength)), + length: Au(other.mLength), percentage: percentage, } } diff --git a/components/style/lib.rs b/components/style/lib.rs index d7312bbb904d7..e7c764ba87c18 100644 --- a/components/style/lib.rs +++ b/components/style/lib.rs @@ -103,6 +103,7 @@ pub mod dom; pub mod element_state; pub mod error_reporting; pub mod font_face; +pub mod font_metrics; #[cfg(feature = "gecko")] #[allow(unsafe_code)] pub mod gecko; #[cfg(feature = "gecko")] #[allow(unsafe_code)] pub mod gecko_bindings; pub mod keyframes; diff --git a/components/style/media_queries.rs b/components/style/media_queries.rs index 28fc662d12618..569265b323f7a 100644 --- a/components/style/media_queries.rs +++ b/components/style/media_queries.rs @@ -10,10 +10,10 @@ use Atom; use app_units::Au; use cssparser::{Delimiter, Parser, Token}; use euclid::size::{Size2D, TypedSize2D}; -use properties::longhands; use serialize_comma_separated_list; use std::fmt::{self, Write}; use style_traits::{ToCss, ViewportPx}; +use values::computed::{self, ToComputedValue}; use values::specified; @@ -49,28 +49,11 @@ impl Range { fn to_computed_range(&self, viewport_size: Size2D) -> Range { // http://dev.w3.org/csswg/mediaqueries3/#units // em units are relative to the initial font-size. - let initial_font_size = longhands::font_size::get_initial_value(); - let compute_width = |&width| { - match width { - specified::Length::Absolute(value) => value, - specified::Length::FontRelative(value) - => value.to_computed_value(initial_font_size, initial_font_size), - specified::Length::ViewportPercentage(value) - => value.to_computed_value(viewport_size), - specified::Length::Calc(val, range) - => range.clamp( - val.compute_from_viewport_and_font_size(viewport_size, - initial_font_size, - initial_font_size) - .length()), - specified::Length::ServoCharacterWidth(..) - => unreachable!(), - } - }; + let context = computed::Context::initial(viewport_size, false); match *self { - Range::Min(ref width) => Range::Min(compute_width(width)), - Range::Max(ref width) => Range::Max(compute_width(width)), + Range::Min(ref width) => Range::Min(width.to_computed_value(&context)), + Range::Max(ref width) => Range::Max(width.to_computed_value(&context)), //Range::Eq(ref width) => Range::Eq(compute_width(width)) } } diff --git a/components/style/properties/gecko.mako.rs b/components/style/properties/gecko.mako.rs index a95822e758144..d37f55b24202c 100644 --- a/components/style/properties/gecko.mako.rs +++ b/components/style/properties/gecko.mako.rs @@ -908,6 +908,14 @@ fn static_assert() { } } + pub fn font_family_count(&self) -> usize { + 0 + } + + pub fn font_family_at(&self, _: usize) -> longhands::font_family::computed_value::FontFamily { + unimplemented!() + } + pub fn copy_font_family_from(&mut self, other: &Self) { unsafe { Gecko_CopyFontFamilyFrom(&mut self.gecko.mFont, &other.gecko.mFont); } } diff --git a/components/style/properties/helpers/animated_properties.mako.rs b/components/style/properties/helpers/animated_properties.mako.rs index 39f9abaf29bbe..b37efac2aae87 100644 --- a/components/style/properties/helpers/animated_properties.mako.rs +++ b/components/style/properties/helpers/animated_properties.mako.rs @@ -418,7 +418,7 @@ impl Interpolate for CalcLengthOrPercentage { } Ok(CalcLengthOrPercentage { - length: try!(interpolate_half(self.length, other.length, progress)), + length: try!(self.length.interpolate(&other.length, progress)), percentage: try!(interpolate_half(self.percentage, other.percentage, progress)), }) } diff --git a/components/style/properties/longhand/font.mako.rs b/components/style/properties/longhand/font.mako.rs index 4df0b9de90c71..37ecf55707378 100644 --- a/components/style/properties/longhand/font.mako.rs +++ b/components/style/properties/longhand/font.mako.rs @@ -8,7 +8,7 @@ <% data.new_style_struct("Font", inherited=True, additional_methods=[Method("compute_font_hash", is_mut=True)]) %> -<%helpers:longhand name="font-family" animatable="False"> +<%helpers:longhand name="font-family" animatable="False" need_index="True"> use self::computed_value::FontFamily; use values::NoViewportPercentage; use values::computed::ComputedValueAsSpecified; @@ -21,6 +21,7 @@ use std::fmt; use Atom; use style_traits::ToCss; + pub use self::FontFamily as SingleComputedValue; #[derive(Debug, PartialEq, Eq, Clone, Hash)] #[cfg_attr(feature = "servo", derive(HeapSizeOf, Deserialize, Serialize))] @@ -28,8 +29,8 @@ FamilyName(Atom), Generic(Atom), } - impl FontFamily { + impl FontFamily { #[inline] pub fn atom(&self) -> &Atom { match *self { @@ -67,11 +68,13 @@ FontFamily::FamilyName(input) } } + impl ToCss for FontFamily { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { self.atom().with_str(|s| dest.write_str(s)) } } + impl ToCss for T { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { let mut iter = self.0.iter(); @@ -83,6 +86,7 @@ Ok(()) } } + #[derive(Debug, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub struct T(pub Vec); @@ -307,8 +311,7 @@ ${helpers.single_keyword("font-variant", fn to_computed_value(&self, context: &Context) -> computed_value::T { match self.0 { LengthOrPercentage::Length(Length::FontRelative(value)) => { - value.to_computed_value(context.inherited_style().get_font().clone_font_size(), - context.style().root_font_size()) + value.to_computed_value(context, /* use inherited */ true) } LengthOrPercentage::Length(Length::ServoCharacterWidth(value)) => { value.to_computed_value(context.inherited_style().get_font().clone_font_size()) diff --git a/components/style/properties/properties.mako.rs b/components/style/properties/properties.mako.rs index 7fe88d7bebfd9..7d60426b736be 100644 --- a/components/style/properties/properties.mako.rs +++ b/components/style/properties/properties.mako.rs @@ -25,6 +25,7 @@ use url::Url; #[cfg(feature = "servo")] use euclid::side_offsets::SideOffsets2D; use euclid::size::Size2D; use computed_values; +use font_metrics::FontMetricsProvider; #[cfg(feature = "servo")] use logical_geometry::{LogicalMargin, PhysicalSide}; use logical_geometry::WritingMode; use parser::{ParserContext, ParserContextExtraData}; @@ -1460,6 +1461,7 @@ pub fn cascade(viewport_size: Size2D, inherited_style, cascade_info, error_reporter, + None, flags) } @@ -1471,6 +1473,7 @@ pub fn apply_declarations<'a, F, I>(viewport_size: Size2D, inherited_style: &ComputedValues, mut cascade_info: Option<<&mut CascadeInfo>, mut error_reporter: StdBox, + font_metrics_provider: Option<<&FontMetricsProvider>, flags: CascadeFlags) -> ComputedValues where F: Fn() -> I, I: Iterator @@ -1524,6 +1527,7 @@ pub fn apply_declarations<'a, F, I>(viewport_size: Size2D, viewport_size: viewport_size, inherited_style: inherited_style, style: starting_style, + font_metrics_provider: font_metrics_provider, }; // Set computed values, overwriting earlier declarations for the same @@ -1558,6 +1562,7 @@ pub fn apply_declarations<'a, F, I>(viewport_size: Size2D, // classification is correct. let is_early_property = matches!(*declaration, PropertyDeclaration::FontSize(_) | + PropertyDeclaration::FontFamily(_) | PropertyDeclaration::Color(_) | PropertyDeclaration::Position(_) | PropertyDeclaration::Float(_) | diff --git a/components/style/values/computed/length.rs b/components/style/values/computed/length.rs index f3524523756f2..4536041fdd468 100644 --- a/components/style/values/computed/length.rs +++ b/components/style/values/computed/length.rs @@ -17,14 +17,14 @@ pub use values::specified::{Angle, BorderStyle, Time, UrlOrNone}; #[derive(Clone, PartialEq, Copy, Debug)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub struct CalcLengthOrPercentage { - pub length: Option, + pub length: Au, pub percentage: Option, } impl CalcLengthOrPercentage { #[inline] pub fn length(&self) -> Au { - self.length.unwrap_or(Au(0)) + self.length } #[inline] @@ -38,13 +38,13 @@ impl From for CalcLengthOrPercentage { match len { LengthOrPercentage::Percentage(this) => { CalcLengthOrPercentage { - length: None, + length: Au(0), percentage: Some(this), } } LengthOrPercentage::Length(this) => { CalcLengthOrPercentage { - length: Some(this), + length: this, percentage: None, } } @@ -60,13 +60,13 @@ impl From for Option { match len { LengthOrPercentageOrAuto::Percentage(this) => { Some(CalcLengthOrPercentage { - length: None, + length: Au(0), percentage: Some(this), }) } LengthOrPercentageOrAuto::Length(this) => { Some(CalcLengthOrPercentage { - length: Some(this), + length: this, percentage: None, }) } @@ -83,10 +83,9 @@ impl From for Option { impl ToCss for CalcLengthOrPercentage { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { match (self.length, self.percentage) { - (None, Some(p)) => write!(dest, "{}%", p * 100.), - (Some(l), None) => write!(dest, "{}px", Au::to_px(l)), - (Some(l), Some(p)) => write!(dest, "calc({}px + {}%)", Au::to_px(l), p * 100.), - _ => unreachable!() + (l, Some(p)) if l == Au(0) => write!(dest, "{}%", p * 100.), + (l, Some(p)) => write!(dest, "calc({}px + {}%)", Au::to_px(l), p * 100.), + (l, None) => write!(dest, "{}px", Au::to_px(l)), } } } @@ -95,16 +94,34 @@ impl ToComputedValue for specified::CalcLengthOrPercentage { type ComputedValue = CalcLengthOrPercentage; fn to_computed_value(&self, context: &Context) -> CalcLengthOrPercentage { - self.compute_from_viewport_and_font_size(context.viewport_size(), - context.style().get_font().clone_font_size(), - context.style().root_font_size()) + let mut length = Au(0); + if let Some(absolute) = self.absolute { + length += absolute; + } + + for val in &[self.vw, self.vh, self.vmin, self.vmax] { + if let Some(val) = *val { + length += val.to_computed_value(context.viewport_size()); + } + } + + for val in &[self.ch, self.em, self.ex, self.rem] { + if let Some(val) = *val { + length += val.to_computed_value(context, /* use inherited */ false); + } + } + + CalcLengthOrPercentage { + length: length, + percentage: self.percentage.map(|p| p.0), + } } #[inline] fn from_computed_value(computed: &CalcLengthOrPercentage) -> Self { specified::CalcLengthOrPercentage { - absolute: computed.length, + absolute: Some(computed.length), percentage: computed.percentage.map(specified::Percentage), ..Default::default() } diff --git a/components/style/values/computed/mod.rs b/components/style/values/computed/mod.rs index 1e57673d457ff..7dfbb901ee8e5 100644 --- a/components/style/values/computed/mod.rs +++ b/components/style/values/computed/mod.rs @@ -4,6 +4,7 @@ use app_units::Au; use euclid::size::Size2D; +use font_metrics::FontMetricsProvider; use properties::ComputedValues; use std::fmt; use style_traits::ToCss; @@ -28,9 +29,11 @@ pub struct Context<'a> { pub viewport_size: Size2D, pub inherited_style: &'a ComputedValues, - /// Values access through this need to be in the properties "computed early": - /// color, text-decoration, font-size, display, position, float, border-*-style, outline-style + /// Values access through this need to be in the properties "computed + /// early": color, text-decoration, font-size, display, position, float, + /// border-*-style, outline-style, font-family, writing-mode... pub style: ComputedValues, + pub font_metrics_provider: Option<&'a FontMetricsProvider>, } impl<'a> Context<'a> { @@ -39,6 +42,20 @@ impl<'a> Context<'a> { pub fn inherited_style(&self) -> &ComputedValues { &self.inherited_style } pub fn style(&self) -> &ComputedValues { &self.style } pub fn mutate_style(&mut self) -> &mut ComputedValues { &mut self.style } + + /// Creates a dummy computed context for use in multiple places, like + /// evaluating media queries. + pub fn initial(viewport_size: Size2D, is_root_element: bool) -> Self { + let initial_style = ComputedValues::initial_values(); + // FIXME: Enforce a font metrics provider. + Context { + is_root_element: is_root_element, + viewport_size: viewport_size, + inherited_style: initial_style, + style: initial_style.clone(), + font_metrics_provider: None, + } + } } pub trait ToComputedValue { @@ -99,8 +116,7 @@ impl ToComputedValue for specified::Length { specified::Length::Absolute(length) => length, specified::Length::Calc(calc, range) => range.clamp(calc.to_computed_value(context).length()), specified::Length::FontRelative(length) => - length.to_computed_value(context.style().get_font().clone_font_size(), - context.style().root_font_size()), + length.to_computed_value(context, /* use inherited */ false), specified::Length::ViewportPercentage(length) => length.to_computed_value(context.viewport_size()), specified::Length::ServoCharacterWidth(length) => diff --git a/components/style/values/specified/length.rs b/components/style/values/specified/length.rs index a8b33cb041e63..f02958314beaf 100644 --- a/components/style/values/specified/length.rs +++ b/components/style/values/specified/length.rs @@ -5,6 +5,7 @@ use app_units::Au; use cssparser::{Parser, Token}; use euclid::size::Size2D; +use font_metrics::FontMetrics; use parser::Parse; use std::ascii::AsciiExt; use std::cmp; @@ -13,7 +14,8 @@ use std::ops::Mul; use style_traits::ToCss; use style_traits::values::specified::AllowedNumericType; use super::{Angle, Number, SimplifiedValueNode, SimplifiedSumNode, Time}; -use values::{CSSFloat, Either, FONT_MEDIUM_PX, HasViewportPercentage, None_, computed}; +use values::{CSSFloat, Either, FONT_MEDIUM_PX, HasViewportPercentage, None_}; +use values::computed::Context; pub use super::image::{AngleOrCorner, ColorStop, EndingShape as GradientEndingShape, Gradient}; pub use super::image::{GradientKind, HorizontalDirection, Image, LengthOrKeyword, LengthOrPercentageOrKeyword}; @@ -40,18 +42,77 @@ impl ToCss for FontRelativeLength { } impl FontRelativeLength { - pub fn to_computed_value(&self, - reference_font_size: Au, - root_font_size: Au) - -> Au - { + pub fn find_first_available_font_metrics(context: &Context) -> Option { + use font_metrics::FontMetricsQueryResult::*; + if let Some(ref metrics_provider) = context.font_metrics_provider { + for family in context.style().get_font().font_family_iter() { + if let Available(metrics) = metrics_provider.query(family.atom()) { + return metrics; + } + } + } + + None + } + + // NB: The use_inherited flag is used to special-case the computation of + // font-family. + pub fn to_computed_value(&self, context: &Context, use_inherited: bool) -> Au { + let reference_font_size = if use_inherited { + context.inherited_style().get_font().clone_font_size() + } else { + context.style().get_font().clone_font_size() + }; + + let root_font_size = context.style().root_font_size; match *self { FontRelativeLength::Em(length) => reference_font_size.scale_by(length), - FontRelativeLength::Ex(length) | FontRelativeLength::Ch(length) => { - // https://github.com/servo/servo/issues/7462 - let em_factor = 0.5; - reference_font_size.scale_by(length * em_factor) + FontRelativeLength::Ex(length) => { + match Self::find_first_available_font_metrics(context) { + Some(metrics) => metrics.x_height, + // https://drafts.csswg.org/css-values/#ex + // + // In the cases where it is impossible or impractical to + // determine the x-height, a value of 0.5em must be + // assumed. + // + None => reference_font_size.scale_by(0.5 * length), + } }, + FontRelativeLength::Ch(length) => { + let wm = context.style().writing_mode; + + // FIXME(emilio): text-orientation: upright is only in Gecko + // right now. + let vertical = wm.is_vertical() || wm.is_vertical_lr(); + + match Self::find_first_available_font_metrics(context) { + Some(metrics) => { + if vertical { + metrics.zero_advance_measure.height + } else { + metrics.zero_advance_measure.width + } + } + // https://drafts.csswg.org/css-values/#ch + // + // In the cases where it is impossible or impractical to + // determine the measure of the “0” glyph, it must be + // assumed to be 0.5em wide by 1em tall. Thus, the ch + // unit falls back to 0.5em in the general case, and to + // 1em when it would be typeset upright (i.e. + // writing-mode is vertical-rl or vertical-lr and + // text-orientation is upright). + // + None => { + if vertical { + reference_font_size.scale_by(length) + } else { + reference_font_size.scale_by(0.5 * length) + } + } + } + } FontRelativeLength::Rem(length) => root_font_size.scale_by(length) } } @@ -612,38 +673,6 @@ impl CalcLengthOrPercentage { _ => Err(()) } } - - pub fn compute_from_viewport_and_font_size(&self, - viewport_size: Size2D, - font_size: Au, - root_font_size: Au) - -> computed::CalcLengthOrPercentage - { - let mut length = None; - - if let Some(absolute) = self.absolute { - length = Some(length.unwrap_or(Au(0)) + absolute); - } - - for val in &[self.vw, self.vh, self.vmin, self.vmax] { - if let Some(val) = *val { - length = Some(length.unwrap_or(Au(0)) + - val.to_computed_value(viewport_size)); - } - } - - for val in &[self.ch, self.em, self.ex, self.rem] { - if let Some(val) = *val { - length = Some(length.unwrap_or(Au(0)) + val.to_computed_value( - font_size, root_font_size)); - } - } - - computed::CalcLengthOrPercentage { - length: length, - percentage: self.percentage.map(|p| p.0), - } - } } impl HasViewportPercentage for CalcLengthOrPercentage { diff --git a/components/style/values/specified/position.rs b/components/style/values/specified/position.rs index c856a002214d7..0d3a81d6580bb 100644 --- a/components/style/values/specified/position.rs +++ b/components/style/values/specified/position.rs @@ -7,6 +7,7 @@ //! //! [position]: https://drafts.csswg.org/css-backgrounds-3/#position +use app_units::Au; use cssparser::{Parser, Token}; use parser::Parse; use std::fmt; @@ -288,9 +289,9 @@ impl ToComputedValue for Position { Keyword::Right => { if let Some(x) = self.horiz_position { let (length, percentage) = match x { - LengthOrPercentage::Percentage(Percentage(y)) => (None, Some(1.0 - y)), - LengthOrPercentage::Length(y) => (Some(-y.to_computed_value(context)), Some(1.0)), - _ => (None, None), + LengthOrPercentage::Percentage(Percentage(y)) => (Au(0), Some(1.0 - y)), + LengthOrPercentage::Length(y) => (-y.to_computed_value(context), Some(1.0)), + _ => (Au(0), None), }; ComputedLengthOrPercentage::Calc(CalcLengthOrPercentage { length: length, @@ -314,9 +315,9 @@ impl ToComputedValue for Position { Keyword::Bottom => { if let Some(x) = self.vert_position { let (length, percentage) = match x { - LengthOrPercentage::Percentage(Percentage(y)) => (None, Some(1.0 - y)), - LengthOrPercentage::Length(y) => (Some(-y.to_computed_value(context)), Some(1.0)), - _ => (None, None), + LengthOrPercentage::Percentage(Percentage(y)) => (Au(0), Some(1.0 - y)), + LengthOrPercentage::Length(y) => (-y.to_computed_value(context), Some(1.0)), + _ => (Au(0), None), }; ComputedLengthOrPercentage::Calc(CalcLengthOrPercentage { length: length, diff --git a/components/style/viewport.rs b/components/style/viewport.rs index b08109c4af51a..a033a1c31d7d8 100644 --- a/components/style/viewport.rs +++ b/components/style/viewport.rs @@ -633,6 +633,7 @@ impl MaybeNew for ViewportConstraints { viewport_size: initial_viewport, inherited_style: ComputedValues::initial_values(), style: ComputedValues::initial_values().clone(), + font_metrics_provider: None, // TODO: Should have! }; // DEVICE-ADAPT § 9.3 Resolving 'extend-to-zoom' diff --git a/ports/geckolib/glue.rs b/ports/geckolib/glue.rs index 70131075f0cda..be42d076fc3ef 100644 --- a/ports/geckolib/glue.rs +++ b/ports/geckolib/glue.rs @@ -148,6 +148,7 @@ pub extern "C" fn Servo_RestyleWithAddedDeclaration(declarations: RawServoDeclar previous_style, None, Box::new(StdoutErrorReporter), + None, CascadeFlags::empty()); Arc::new(computed).into_strong() }