diff --git a/servo/components/style/values/animated/length.rs b/servo/components/style/values/animated/length.rs index ece26a9c4911..837b3ee86c3a 100644 --- a/servo/components/style/values/animated/length.rs +++ b/servo/components/style/values/animated/length.rs @@ -5,7 +5,7 @@ //! Animation implementation for various length-related types. use super::{Animate, Procedure}; -use crate::values::computed::length::{LengthPercentage, CalcLengthPercentage}; +use crate::values::computed::length::LengthPercentage; use crate::values::computed::Percentage; /// @@ -29,6 +29,6 @@ impl Animate for LengthPercentage { // Gets clamped as needed after the animation, so no need to specify any // particular AllowedNumericType. - Ok(CalcLengthPercentage::new(length, percentage).to_length_percentge()) + Ok(LengthPercentage::new_calc(length, percentage)) } } diff --git a/servo/components/style/values/computed/length.rs b/servo/components/style/values/computed/length.rs index 7c36dc34d7d2..e02462d12dd3 100644 --- a/servo/components/style/values/computed/length.rs +++ b/servo/components/style/values/computed/length.rs @@ -4,28 +4,26 @@ //! `` computed values, and related ones. -use super::{Context, Number, Percentage, ToComputedValue}; +use super::{Context, Number, ToComputedValue}; use crate::values::animated::ToAnimatedValue; use crate::values::computed::NonNegativeNumber; -use crate::values::distance::{ComputeSquaredDistance, SquaredDistance}; use crate::values::generics::length as generics; use crate::values::generics::length::{ GenericLengthOrNumber, GenericLengthPercentageOrNormal, GenericMaxSize, GenericSize, }; use crate::values::generics::NonNegative; -use crate::values::specified::length::ViewportPercentageLength; -use crate::values::specified::length::{AbsoluteLength, FontBaseSize, FontRelativeLength}; +use crate::values::specified::length::{AbsoluteLength, FontBaseSize}; use crate::values::{specified, CSSFloat}; use crate::Zero; use app_units::Au; use std::fmt::{self, Write}; use std::ops::{Add, AddAssign, Div, Mul, Neg, Sub}; -use style_traits::values::specified::AllowedNumericType; use style_traits::{CSSPixel, CssWriter, ToCss}; pub use super::image::Image; pub use crate::values::specified::url::UrlOrNone; pub use crate::values::specified::{Angle, BorderStyle, Time}; +pub use super::length_percentage::{LengthPercentage, NonNegativeLengthPercentage}; impl ToComputedValue for specified::NoCalcLength { type ComputedValue = Length; @@ -69,502 +67,6 @@ impl ToComputedValue for specified::Length { } } -/// A `` value. This can be either a ``, a -/// ``, or a combination of both via `calc()`. -/// -/// cbindgen:private-default-tagged-enum-constructor=false -/// cbindgen:derive-mut-casts=true -/// -/// https://drafts.csswg.org/css-values-4/#typedef-length-percentage -#[allow(missing_docs)] -#[derive(Clone, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize, ToAnimatedZero, ToResolvedValue)] -#[repr(u8)] -pub enum LengthPercentage { - Length(Length), - Percentage(Percentage), - Calc(Box), -} - -/// The representation of a calc() function. -#[derive( - Clone, Debug, Deserialize, MallocSizeOf, Serialize, ToAnimatedZero, ToResolvedValue, -)] -#[repr(C)] -pub struct CalcLengthPercentage { - length: Length, - - percentage: Percentage, - - #[animation(constant)] - clamping_mode: AllowedNumericType, - - /// Whether we specified a percentage or not. - #[animation(constant)] - pub has_percentage: bool, -} - -// NOTE(emilio): We don't compare `clamping_mode` since we want to preserve the -// invariant that `from_computed_value(length).to_computed_value(..) == length`. -// -// Right now for e.g. a non-negative length, we set clamping_mode to `All` -// unconditionally for non-calc values, and to `NonNegative` for calc. -// -// If we determine that it's sound, from_computed_value() can generate an -// absolute length, which then would get `All` as the clamping mode. -// -// We may want to just eagerly-detect whether we can clamp in -// `LengthPercentage::new` and switch to `AllowedNumericType::NonNegative` then, -// maybe. -impl PartialEq for CalcLengthPercentage { - fn eq(&self, other: &Self) -> bool { - self.length == other.length && - self.percentage == other.percentage && - self.has_percentage == other.has_percentage - } -} - -impl ComputeSquaredDistance for LengthPercentage { - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result { - // FIXME(nox): This looks incorrect to me, to add a distance between lengths - // with a distance between percentages. - Ok(self - .unclamped_length() - .compute_squared_distance(&other.unclamped_length())? + - self.percentage() - .compute_squared_distance(&other.percentage())?) - } -} - -impl ToCss for LengthPercentage { - fn to_css(&self, dest: &mut CssWriter) -> fmt::Result - where - W: Write, - { - specified::LengthPercentage::from_computed_value(self).to_css(dest) - } -} - -impl specified::CalcLengthPercentage { - /// Compute the value, zooming any absolute units by the zoom function. - fn to_computed_value_with_zoom( - &self, - context: &Context, - zoom_fn: F, - base_size: FontBaseSize, - ) -> CalcLengthPercentage - where - F: Fn(Length) -> Length, - { - use std::f32; - let mut length = 0.; - - if let Some(absolute) = self.absolute { - length += zoom_fn(absolute.to_computed_value(context)).px(); - } - - for val in &[ - self.vw.map(ViewportPercentageLength::Vw), - self.vh.map(ViewportPercentageLength::Vh), - self.vmin.map(ViewportPercentageLength::Vmin), - self.vmax.map(ViewportPercentageLength::Vmax), - ] { - if let Some(val) = *val { - let viewport_size = context.viewport_size_for_viewport_unit_resolution(); - length += val.to_computed_value(viewport_size).px(); - } - } - - for val in &[ - self.ch.map(FontRelativeLength::Ch), - self.em.map(FontRelativeLength::Em), - self.ex.map(FontRelativeLength::Ex), - self.rem.map(FontRelativeLength::Rem), - ] { - if let Some(val) = *val { - length += val.to_computed_value(context, base_size).px(); - } - } - - CalcLengthPercentage::with_clamping_mode( - Length::new(length.min(f32::MAX).max(f32::MIN)), - self.percentage, - self.clamping_mode, - ) - } - - /// Compute font-size or line-height taking into account text-zoom if necessary. - pub fn to_computed_value_zoomed( - &self, - context: &Context, - base_size: FontBaseSize, - ) -> CalcLengthPercentage { - self.to_computed_value_with_zoom( - context, - |abs| context.maybe_zoom_text(abs.into()), - base_size, - ) - } - - /// Compute the value into pixel length as CSSFloat without context, - /// so it returns Err(()) if there is any non-absolute unit. - pub fn to_computed_pixel_length_without_context(&self) -> Result { - if self.vw.is_some() || - self.vh.is_some() || - self.vmin.is_some() || - self.vmax.is_some() || - self.em.is_some() || - self.ex.is_some() || - self.ch.is_some() || - self.rem.is_some() || - self.percentage.is_some() - { - return Err(()); - } - - match self.absolute { - Some(abs) => Ok(abs.to_px()), - None => { - debug_assert!(false, "Someone forgot to handle an unit here: {:?}", self); - Err(()) - }, - } - } - - fn to_computed_value(&self, context: &Context) -> CalcLengthPercentage { - // normal properties don't zoom, and compute em units against the current style's font-size - self.to_computed_value_with_zoom(context, |abs| abs, FontBaseSize::CurrentStyle) - } - - #[inline] - fn from_computed_value(computed: &CalcLengthPercentage) -> Self { - specified::CalcLengthPercentage { - clamping_mode: computed.clamping_mode, - absolute: Some(AbsoluteLength::from_computed_value(&computed.length)), - percentage: computed.specified_percentage(), - ..Default::default() - } - } -} - -impl LengthPercentage { - /// 1px length value for SVG defaults - #[inline] - pub fn one() -> Self { - Self::Length(Length::new(1.)) - } - - /// Constructs a length value. - #[inline] - pub fn new_length(l: Length) -> Self { - Self::Length(l) - } - - /// Constructs a percentage value. - #[inline] - pub fn new_percent(p: Percentage) -> Self { - Self::Percentage(p) - } - - /// Constructs a `calc()` value. - #[inline] - pub fn new_calc(l: Length, percentage: Percentage) -> Self { - CalcLengthPercentage::new(l, Some(percentage)).to_length_percentge() - } - - /// Returns true if the computed value is absolute 0 or 0%. - #[inline] - pub fn is_definitely_zero(&self) -> bool { - match *self { - Self::Length(l) => l.px() == 0.0, - Self::Percentage(p) => p.0 == 0.0, - Self::Calc(ref c) => c.is_definitely_zero(), - } - } - - /// Returns the `` component of this `calc()`, unclamped. - #[inline] - pub fn unclamped_length(&self) -> Length { - match *self { - Self::Length(l) => l, - Self::Percentage(..) => Zero::zero(), - Self::Calc(ref c) => c.unclamped_length(), - } - } - - /// Returns this `calc()` as a ``. - /// - /// Panics in debug mode if a percentage is present in the expression. - #[inline] - fn length(&self) -> Length { - debug_assert!(!self.has_percentage()); - self.length_component() - } - - /// Returns the `` component of this `calc()`, clamped. - #[inline] - pub fn length_component(&self) -> Length { - match *self { - Self::Length(l) => l, - Self::Percentage(..) => Zero::zero(), - Self::Calc(ref c) => c.length_component(), - } - } - - /// Returns the `` component of this `calc()`, unclamped, as a - /// float. - /// - /// FIXME: This are very different semantics from length(), we should - /// probably rename this. - #[inline] - pub fn percentage(&self) -> CSSFloat { - match *self { - Self::Length(..) => 0., - Self::Percentage(p) => p.0, - Self::Calc(ref c) => c.percentage.0, - } - } - - /// Returns the `` component of this `calc()`, clamped. - #[inline] - pub fn as_percentage(&self) -> Option { - match *self { - Self::Length(..) => None, - Self::Percentage(p) => Some(p), - Self::Calc(ref c) => c.as_percentage(), - } - } - - /// Resolves the percentage. - #[inline] - pub fn resolve(&self, basis: Length) -> Length { - match *self { - Self::Length(l) => l, - Self::Percentage(p) => Length::new(basis.0 * p.0), - Self::Calc(ref c) => c.resolve(basis), - } - } - - /// Resolves the percentage. Just an alias of resolve(). - #[inline] - pub fn percentage_relative_to(&self, basis: Length) -> Length { - self.resolve(basis) - } - - /// Return whether there's any percentage in this value. - #[inline] - pub fn has_percentage(&self) -> bool { - match *self { - Self::Length(..) => false, - Self::Percentage(..) => true, - Self::Calc(ref c) => c.has_percentage, - } - } - - /// Return the specified percentage if any. - #[inline] - pub fn specified_percentage(&self) -> Option { - match *self { - Self::Length(..) => None, - Self::Percentage(p) => Some(p), - Self::Calc(ref c) => c.specified_percentage(), - } - } - - /// Returns the used value. - #[inline] - pub fn to_used_value(&self, containing_length: Au) -> Au { - Au::from(self.to_pixel_length(containing_length)) - } - - /// Returns the used value as CSSPixelLength. - #[inline] - pub fn to_pixel_length(&self, containing_length: Au) -> Length { - self.resolve(containing_length.into()) - } - - /// Convert the computed value into used value. - #[inline] - fn maybe_to_used_value(&self, container_len: Option) -> Option { - self.maybe_percentage_relative_to(container_len).map(Au::from) - } - - /// If there are special rules for computing percentages in a value (e.g. - /// the height property), they apply whenever a calc() expression contains - /// percentages. - pub fn maybe_percentage_relative_to(&self, container_len: Option) -> Option { - if self.has_percentage() { - return Some(self.resolve(container_len?)); - } - Some(self.length()) - } - - /// Returns the clamped non-negative values. - #[inline] - pub fn clamp_to_non_negative(self) -> Self { - match self { - Self::Length(l) => Self::Length(l.clamp_to_non_negative()), - Self::Percentage(p) => Self::Percentage(p.clamp_to_non_negative()), - Self::Calc(c) => c.clamp_to_non_negative().to_length_percentge(), - } - } -} - -impl CalcLengthPercentage { - /// Returns a new `LengthPercentage`. - #[inline] - pub fn new(length: Length, percentage: Option) -> Self { - Self::with_clamping_mode(length, percentage, AllowedNumericType::All) - } - - /// Converts this to a `LengthPercentage`, simplifying if possible. - #[inline] - pub fn to_length_percentge(self) -> LengthPercentage { - if !self.has_percentage { - return LengthPercentage::Length(self.length_component()) - } - if self.length.is_zero() { - return LengthPercentage::Percentage(Percentage(self.clamping_mode.clamp(self.percentage.0))); - } - LengthPercentage::Calc(Box::new(self)) - } - - fn specified_percentage(&self) -> Option { - if self.has_percentage { - Some(self.percentage) - } else { - None - } - } - - /// Returns a new `LengthPercentage` with a specific clamping mode. - #[inline] - fn with_clamping_mode( - length: Length, - percentage: Option, - clamping_mode: AllowedNumericType, - ) -> Self { - Self { - clamping_mode, - length, - percentage: percentage.unwrap_or_default(), - has_percentage: percentage.is_some(), - } - } - - /// Returns the length component of this `calc()` - #[inline] - fn length_component(&self) -> CSSPixelLength { - Length::new(self.clamping_mode.clamp(self.length.px())) - } - - /// Returns the percentage component if this could be represented as a - /// non-calc percentage. - fn as_percentage(&self) -> Option { - if !self.has_percentage || self.length.px() != 0. { - return None; - } - - Some(Percentage(self.clamping_mode.clamp(self.percentage.0))) - } - - /// Resolves the percentage. - #[inline] - pub fn resolve(&self, basis: Length) -> Length { - let length = self.length.0 + basis.0 * self.percentage.0; - Length::new(self.clamping_mode.clamp(length)) - } - - /// Resolves the percentage. - #[inline] - pub fn percentage_relative_to(&self, basis: Length) -> Length { - self.resolve(basis) - } - - /// Returns the length, without clamping. - #[inline] - pub fn unclamped_length(&self) -> Length { - self.length - } - - /// Returns true if the computed value is absolute 0 or 0%. - #[inline] - fn is_definitely_zero(&self) -> bool { - self.length.px() == 0.0 && self.percentage.0 == 0.0 - } - - /// Returns the clamped non-negative values. - #[inline] - fn clamp_to_non_negative(self) -> Self { - if self.has_percentage { - // If we can eagerly clamp the percentage then just do that. - if self.length.is_zero() { - return Self::with_clamping_mode( - Length::zero(), - Some(self.percentage.clamp_to_non_negative()), - AllowedNumericType::NonNegative, - ); - } - return Self::with_clamping_mode(self.length, Some(self.percentage), AllowedNumericType::NonNegative); - } - - Self::with_clamping_mode( - self.length.clamp_to_non_negative(), - None, - AllowedNumericType::NonNegative, - ) - } -} - -impl ToComputedValue for specified::LengthPercentage { - type ComputedValue = LengthPercentage; - - fn to_computed_value(&self, context: &Context) -> LengthPercentage { - match *self { - specified::LengthPercentage::Length(ref value) => { - LengthPercentage::Length(value.to_computed_value(context)) - }, - specified::LengthPercentage::Percentage(value) => { - LengthPercentage::Percentage(value) - }, - specified::LengthPercentage::Calc(ref calc) => { - (**calc).to_computed_value(context).to_length_percentge() - }, - } - } - - fn from_computed_value(computed: &LengthPercentage) -> Self { - match *computed { - LengthPercentage::Length(ref l) => { - specified::LengthPercentage::Length(ToComputedValue::from_computed_value(l)) - } - LengthPercentage::Percentage(p) => { - specified::LengthPercentage::Percentage(p) - } - LengthPercentage::Calc(ref c) => { - if let Some(p) = c.as_percentage() { - return specified::LengthPercentage::Percentage(p) - } - if !c.has_percentage { - return specified::LengthPercentage::Length(ToComputedValue::from_computed_value(&c.length_component())) - } - specified::LengthPercentage::Calc(Box::new(specified::CalcLengthPercentage::from_computed_value(c))) - } - } - } -} - -impl Zero for LengthPercentage { - fn zero() -> Self { - LengthPercentage::Length(Length::zero()) - } - - #[inline] - fn is_zero(&self) -> bool { - self.is_definitely_zero() - } -} - /// Some boilerplate to share between negative and non-negative /// length-percentage or auto. macro_rules! computed_length_percentage_or_auto { @@ -640,70 +142,6 @@ impl NonNegativeLengthPercentageOrAuto { computed_length_percentage_or_auto!(NonNegativeLengthPercentage); } -/// A wrapper of LengthPercentage, whose value must be >= 0. -pub type NonNegativeLengthPercentage = NonNegative; - -impl ToAnimatedValue for NonNegativeLengthPercentage { - type AnimatedValue = LengthPercentage; - - #[inline] - fn to_animated_value(self) -> Self::AnimatedValue { - self.0 - } - - #[inline] - fn from_animated_value(animated: Self::AnimatedValue) -> Self { - NonNegative(animated.clamp_to_non_negative()) - } -} - -impl From for NonNegativeLengthPercentage { - #[inline] - fn from(length: NonNegativeLength) -> Self { - NonNegative(LengthPercentage::new_length(length.0)) - } -} - -impl From for NonNegativeLengthPercentage { - #[inline] - fn from(lp: LengthPercentage) -> Self { - NonNegative(lp) - } -} - -// TODO(emilio): This is a really generic impl which is only needed to implement -// Animated and co for Spacing<>. Get rid of this, probably? -impl From for LengthPercentage { - #[inline] - fn from(length: Au) -> Self { - LengthPercentage::new_length(length.into()) - } -} - -impl NonNegativeLengthPercentage { - /// Returns true if the computed value is absolute 0 or 0%. - #[inline] - pub fn is_definitely_zero(&self) -> bool { - self.0.is_definitely_zero() - } - - /// Returns the used value. - #[inline] - pub fn to_used_value(&self, containing_length: Au) -> Au { - let resolved = self.0.to_used_value(containing_length); - std::cmp::max(resolved, Au(0)) - } - - /// Convert the computed value into used value. - #[inline] - pub fn maybe_to_used_value(&self, containing_length: Option) -> Option { - let resolved = self - .0 - .maybe_to_used_value(containing_length.map(|v| v.into()))?; - Some(std::cmp::max(resolved, Au(0))) - } -} - #[cfg(feature = "servo")] impl MaxSize { /// Convert the computed value into used value. diff --git a/servo/components/style/values/computed/length_percentage.rs b/servo/components/style/values/computed/length_percentage.rs new file mode 100644 index 000000000000..ad108b9487af --- /dev/null +++ b/servo/components/style/values/computed/length_percentage.rs @@ -0,0 +1,559 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +//! `` computed values, and related ones. + +use super::{Context, Length, Percentage, ToComputedValue}; +use crate::values::animated::ToAnimatedValue; +use crate::values::distance::{ComputeSquaredDistance, SquaredDistance}; +use crate::values::generics::NonNegative; +use crate::values::specified::length::FontBaseSize; +use crate::values::{specified, CSSFloat}; +use crate::Zero; +use app_units::Au; +use std::fmt::{self, Write}; +use style_traits::values::specified::AllowedNumericType; +use style_traits::{CssWriter, ToCss}; + +/// A `` value. This can be either a ``, a +/// ``, or a combination of both via `calc()`. +/// +/// cbindgen:private-default-tagged-enum-constructor=false +/// cbindgen:derive-mut-casts=true +/// +/// https://drafts.csswg.org/css-values-4/#typedef-length-percentage +#[allow(missing_docs)] +#[derive(Clone, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize, ToAnimatedZero, ToResolvedValue)] +#[repr(u8)] +pub enum LengthPercentage { + Length(Length), + Percentage(Percentage), + Calc(Box), +} + +impl LengthPercentage { + /// 1px length value for SVG defaults + #[inline] + pub fn one() -> Self { + Self::new_length(Length::new(1.)) + } + + /// Constructs a length value. + #[inline] + pub fn new_length(l: Length) -> Self { + Self::Length(l) + } + + /// Constructs a percentage value. + #[inline] + pub fn new_percent(p: Percentage) -> Self { + Self::Percentage(p) + } + + /// Constructs a `calc()` value. + #[inline] + pub fn new_calc(l: Length, percentage: Option) -> Self { + CalcLengthPercentage::new(l, percentage).to_length_percentge() + } + + /// Returns true if the computed value is absolute 0 or 0%. + #[inline] + pub fn is_definitely_zero(&self) -> bool { + match *self { + Self::Length(l) => l.px() == 0.0, + Self::Percentage(p) => p.0 == 0.0, + Self::Calc(ref c) => c.is_definitely_zero(), + } + } + + /// Returns the `` component of this `calc()`, unclamped. + #[inline] + pub fn unclamped_length(&self) -> Length { + match *self { + Self::Length(l) => l, + Self::Percentage(..) => Zero::zero(), + Self::Calc(ref c) => c.unclamped_length(), + } + } + + /// Returns this `calc()` as a ``. + /// + /// Panics in debug mode if a percentage is present in the expression. + #[inline] + fn length(&self) -> Length { + debug_assert!(!self.has_percentage()); + self.length_component() + } + + /// Returns the `` component of this `calc()`, clamped. + #[inline] + pub fn length_component(&self) -> Length { + match *self { + Self::Length(l) => l, + Self::Percentage(..) => Zero::zero(), + Self::Calc(ref c) => c.length_component(), + } + } + + /// Returns the `` component of this `calc()`, unclamped, as a + /// float. + /// + /// FIXME: This are very different semantics from length(), we should + /// probably rename this. + #[inline] + pub fn percentage(&self) -> CSSFloat { + match *self { + Self::Length(..) => 0., + Self::Percentage(p) => p.0, + Self::Calc(ref c) => c.percentage.0, + } + } + + /// Returns the `` component of this `calc()`, clamped. + #[inline] + pub fn as_percentage(&self) -> Option { + match *self { + Self::Length(..) => None, + Self::Percentage(p) => Some(p), + Self::Calc(ref c) => c.as_percentage(), + } + } + + /// Resolves the percentage. + #[inline] + pub fn resolve(&self, basis: Length) -> Length { + match *self { + Self::Length(l) => l, + Self::Percentage(p) => Length::new(basis.px() * p.0), + Self::Calc(ref c) => c.resolve(basis), + } + } + + /// Resolves the percentage. Just an alias of resolve(). + #[inline] + pub fn percentage_relative_to(&self, basis: Length) -> Length { + self.resolve(basis) + } + + /// Return whether there's any percentage in this value. + #[inline] + pub fn has_percentage(&self) -> bool { + match *self { + Self::Length(..) => false, + Self::Percentage(..) => true, + Self::Calc(ref c) => c.has_percentage, + } + } + + /// Return the specified percentage if any. + #[inline] + pub fn specified_percentage(&self) -> Option { + match *self { + Self::Length(..) => None, + Self::Percentage(p) => Some(p), + Self::Calc(ref c) => c.specified_percentage(), + } + } + + /// Returns the used value. + #[inline] + pub fn to_used_value(&self, containing_length: Au) -> Au { + Au::from(self.to_pixel_length(containing_length)) + } + + /// Returns the used value as CSSPixelLength. + #[inline] + pub fn to_pixel_length(&self, containing_length: Au) -> Length { + self.resolve(containing_length.into()) + } + + /// Convert the computed value into used value. + #[inline] + fn maybe_to_used_value(&self, container_len: Option) -> Option { + self.maybe_percentage_relative_to(container_len).map(Au::from) + } + + /// If there are special rules for computing percentages in a value (e.g. + /// the height property), they apply whenever a calc() expression contains + /// percentages. + pub fn maybe_percentage_relative_to(&self, container_len: Option) -> Option { + if self.has_percentage() { + return Some(self.resolve(container_len?)); + } + Some(self.length()) + } + + /// Returns the clamped non-negative values. + #[inline] + pub fn clamp_to_non_negative(self) -> Self { + match self { + Self::Length(l) => Self::Length(l.clamp_to_non_negative()), + Self::Percentage(p) => Self::Percentage(p.clamp_to_non_negative()), + Self::Calc(c) => c.clamp_to_non_negative().to_length_percentge(), + } + } +} + +impl ToComputedValue for specified::LengthPercentage { + type ComputedValue = LengthPercentage; + + fn to_computed_value(&self, context: &Context) -> LengthPercentage { + match *self { + specified::LengthPercentage::Length(ref value) => { + LengthPercentage::Length(value.to_computed_value(context)) + }, + specified::LengthPercentage::Percentage(value) => { + LengthPercentage::Percentage(value) + }, + specified::LengthPercentage::Calc(ref calc) => { + (**calc).to_computed_value(context).to_length_percentge() + }, + } + } + + fn from_computed_value(computed: &LengthPercentage) -> Self { + match *computed { + LengthPercentage::Length(ref l) => { + specified::LengthPercentage::Length(ToComputedValue::from_computed_value(l)) + } + LengthPercentage::Percentage(p) => { + specified::LengthPercentage::Percentage(p) + } + LengthPercentage::Calc(ref c) => { + if let Some(p) = c.as_percentage() { + return specified::LengthPercentage::Percentage(p) + } + if !c.has_percentage { + return specified::LengthPercentage::Length(ToComputedValue::from_computed_value(&c.length_component())) + } + specified::LengthPercentage::Calc(Box::new(specified::CalcLengthPercentage::from_computed_value(c))) + } + } + } +} + +impl ComputeSquaredDistance for LengthPercentage { + #[inline] + fn compute_squared_distance(&self, other: &Self) -> Result { + // FIXME(nox): This looks incorrect to me, to add a distance between lengths + // with a distance between percentages. + Ok(self + .unclamped_length() + .compute_squared_distance(&other.unclamped_length())? + + self.percentage() + .compute_squared_distance(&other.percentage())?) + } +} + +impl ToCss for LengthPercentage { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: Write, + { + specified::LengthPercentage::from_computed_value(self).to_css(dest) + } +} + +impl Zero for LengthPercentage { + fn zero() -> Self { + LengthPercentage::Length(Length::zero()) + } + + #[inline] + fn is_zero(&self) -> bool { + self.is_definitely_zero() + } +} + +/// The representation of a calc() function. +#[derive( + Clone, Debug, Deserialize, MallocSizeOf, Serialize, ToAnimatedZero, ToResolvedValue, +)] +#[repr(C)] +pub struct CalcLengthPercentage { + length: Length, + + percentage: Percentage, + + #[animation(constant)] + clamping_mode: AllowedNumericType, + + /// Whether we specified a percentage or not. + #[animation(constant)] + pub has_percentage: bool, +} + +impl CalcLengthPercentage { + /// Returns a new `LengthPercentage`. + #[inline] + pub fn new(length: Length, percentage: Option) -> Self { + Self::with_clamping_mode(length, percentage, AllowedNumericType::All) + } + + /// Converts this to a `LengthPercentage`, simplifying if possible. + #[inline] + pub fn to_length_percentge(self) -> LengthPercentage { + if !self.has_percentage { + return LengthPercentage::Length(self.length_component()) + } + if self.length.is_zero() { + return LengthPercentage::Percentage(Percentage(self.clamping_mode.clamp(self.percentage.0))); + } + LengthPercentage::Calc(Box::new(self)) + } + + fn specified_percentage(&self) -> Option { + if self.has_percentage { + Some(self.percentage) + } else { + None + } + } + + /// Returns a new `LengthPercentage` with a specific clamping mode. + #[inline] + fn with_clamping_mode( + length: Length, + percentage: Option, + clamping_mode: AllowedNumericType, + ) -> Self { + Self { + clamping_mode, + length, + percentage: percentage.unwrap_or_default(), + has_percentage: percentage.is_some(), + } + } + + /// Returns the length component of this `calc()`, clamped. + #[inline] + pub fn length_component(&self) -> Length { + Length::new(self.clamping_mode.clamp(self.length.px())) + } + + /// Returns the percentage component if this could be represented as a + /// non-calc percentage. + fn as_percentage(&self) -> Option { + if !self.has_percentage || self.length.px() != 0. { + return None; + } + + Some(Percentage(self.clamping_mode.clamp(self.percentage.0))) + } + + /// Resolves the percentage. + #[inline] + pub fn resolve(&self, basis: Length) -> Length { + let length = self.length.px() + basis.px() * self.percentage.0; + Length::new(self.clamping_mode.clamp(length)) + } + + /// Resolves the percentage. + #[inline] + pub fn percentage_relative_to(&self, basis: Length) -> Length { + self.resolve(basis) + } + + /// Returns the length, without clamping. + #[inline] + pub fn unclamped_length(&self) -> Length { + self.length + } + + /// Returns true if the computed value is absolute 0 or 0%. + #[inline] + fn is_definitely_zero(&self) -> bool { + self.length.px() == 0.0 && self.percentage.0 == 0.0 + } + + /// Returns the clamped non-negative values. + #[inline] + fn clamp_to_non_negative(self) -> Self { + if self.has_percentage { + // If we can eagerly clamp the percentage then just do that. + if self.length.is_zero() { + return Self::with_clamping_mode( + Length::zero(), + Some(self.percentage.clamp_to_non_negative()), + AllowedNumericType::NonNegative, + ); + } + return Self::with_clamping_mode(self.length, Some(self.percentage), AllowedNumericType::NonNegative); + } + + Self::with_clamping_mode( + self.length.clamp_to_non_negative(), + None, + AllowedNumericType::NonNegative, + ) + } +} + +// NOTE(emilio): We don't compare `clamping_mode` since we want to preserve the +// invariant that `from_computed_value(length).to_computed_value(..) == length`. +// +// Right now for e.g. a non-negative length, we set clamping_mode to `All` +// unconditionally for non-calc values, and to `NonNegative` for calc. +// +// If we determine that it's sound, from_computed_value() can generate an +// absolute length, which then would get `All` as the clamping mode. +// +// We may want to just eagerly-detect whether we can clamp in +// `LengthPercentage::new` and switch to `AllowedNumericType::NonNegative` then, +// maybe. +impl PartialEq for CalcLengthPercentage { + fn eq(&self, other: &Self) -> bool { + self.length == other.length && + self.percentage == other.percentage && + self.has_percentage == other.has_percentage + } +} + +impl specified::CalcLengthPercentage { + /// Compute the value, zooming any absolute units by the zoom function. + fn to_computed_value_with_zoom( + &self, + context: &Context, + zoom_fn: F, + base_size: FontBaseSize, + ) -> CalcLengthPercentage + where + F: Fn(Length) -> Length, + { + use std::f32; + use crate::values::specified::length::{ViewportPercentageLength, FontRelativeLength}; + + let mut length = 0.; + + if let Some(absolute) = self.absolute { + length += zoom_fn(absolute.to_computed_value(context)).px(); + } + + for val in &[ + self.vw.map(ViewportPercentageLength::Vw), + self.vh.map(ViewportPercentageLength::Vh), + self.vmin.map(ViewportPercentageLength::Vmin), + self.vmax.map(ViewportPercentageLength::Vmax), + ] { + if let Some(val) = *val { + let viewport_size = context.viewport_size_for_viewport_unit_resolution(); + length += val.to_computed_value(viewport_size).px(); + } + } + + for val in &[ + self.ch.map(FontRelativeLength::Ch), + self.em.map(FontRelativeLength::Em), + self.ex.map(FontRelativeLength::Ex), + self.rem.map(FontRelativeLength::Rem), + ] { + if let Some(val) = *val { + length += val.to_computed_value(context, base_size).px(); + } + } + + CalcLengthPercentage::with_clamping_mode( + Length::new(length.min(f32::MAX).max(f32::MIN)), + self.percentage, + self.clamping_mode, + ) + } + + /// Compute font-size or line-height taking into account text-zoom if necessary. + pub fn to_computed_value_zoomed( + &self, + context: &Context, + base_size: FontBaseSize, + ) -> CalcLengthPercentage { + self.to_computed_value_with_zoom( + context, + |abs| context.maybe_zoom_text(abs.into()), + base_size, + ) + } + + /// Compute the value into pixel length as CSSFloat without context, + /// so it returns Err(()) if there is any non-absolute unit. + pub fn to_computed_pixel_length_without_context(&self) -> Result { + if self.vw.is_some() || + self.vh.is_some() || + self.vmin.is_some() || + self.vmax.is_some() || + self.em.is_some() || + self.ex.is_some() || + self.ch.is_some() || + self.rem.is_some() || + self.percentage.is_some() + { + return Err(()); + } + + match self.absolute { + Some(abs) => Ok(abs.to_px()), + None => { + debug_assert!(false, "Someone forgot to handle an unit here: {:?}", self); + Err(()) + }, + } + } + + /// Compute the calc using the current font-size (and without text-zoom). + pub fn to_computed_value(&self, context: &Context) -> CalcLengthPercentage { + self.to_computed_value_with_zoom(context, |abs| abs, FontBaseSize::CurrentStyle) + } + + #[inline] + fn from_computed_value(computed: &CalcLengthPercentage) -> Self { + use crate::values::specified::length::AbsoluteLength; + + specified::CalcLengthPercentage { + clamping_mode: computed.clamping_mode, + absolute: Some(AbsoluteLength::from_computed_value(&computed.length)), + percentage: computed.specified_percentage(), + ..Default::default() + } + } +} + +/// A wrapper of LengthPercentage, whose value must be >= 0. +pub type NonNegativeLengthPercentage = NonNegative; + +impl ToAnimatedValue for NonNegativeLengthPercentage { + type AnimatedValue = LengthPercentage; + + #[inline] + fn to_animated_value(self) -> Self::AnimatedValue { + self.0 + } + + #[inline] + fn from_animated_value(animated: Self::AnimatedValue) -> Self { + NonNegative(animated.clamp_to_non_negative()) + } +} + +impl NonNegativeLengthPercentage { + /// Returns true if the computed value is absolute 0 or 0%. + #[inline] + pub fn is_definitely_zero(&self) -> bool { + self.0.is_definitely_zero() + } + + /// Returns the used value. + #[inline] + pub fn to_used_value(&self, containing_length: Au) -> Au { + let resolved = self.0.to_used_value(containing_length); + std::cmp::max(resolved, Au(0)) + } + + /// Convert the computed value into used value. + #[inline] + pub fn maybe_to_used_value(&self, containing_length: Option) -> Option { + let resolved = self + .0 + .maybe_to_used_value(containing_length.map(|v| v.into()))?; + Some(std::cmp::max(resolved, Au(0))) + } +} + diff --git a/servo/components/style/values/computed/mod.rs b/servo/components/style/values/computed/mod.rs index 958deaaf44e3..87c430fb2654 100644 --- a/servo/components/style/values/computed/mod.rs +++ b/servo/components/style/values/computed/mod.rs @@ -108,6 +108,7 @@ pub mod flex; pub mod font; pub mod image; pub mod length; +pub mod length_percentage; pub mod list; pub mod motion; pub mod outline; diff --git a/servo/components/style/values/specified/position.rs b/servo/components/style/values/specified/position.rs index 420133810edb..57a6715b0a62 100644 --- a/servo/components/style/values/specified/position.rs +++ b/servo/components/style/values/specified/position.rs @@ -297,7 +297,7 @@ impl ToComputedValue for PositionComponent { let p = Percentage(1. - length.percentage()); let l = -length.unclamped_length(); // We represent ` ` as `calc(100% - )`. - ComputedLengthPercentage::new_calc(l, p) + ComputedLengthPercentage::new_calc(l, Some(p)) }, PositionComponent::Side(_, Some(ref length)) | PositionComponent::Length(ref length) => length.to_computed_value(context),