diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /servo/components/style/values/computed | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
34 files changed, 6348 insertions, 0 deletions
diff --git a/servo/components/style/values/computed/align.rs b/servo/components/style/values/computed/align.rs new file mode 100644 index 0000000000..94847fd80f --- /dev/null +++ b/servo/components/style/values/computed/align.rs @@ -0,0 +1,91 @@ +/* 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/. */ + +//! Values for CSS Box Alignment properties +//! +//! https://drafts.csswg.org/css-align/ + +use crate::values::computed::{Context, ToComputedValue}; +use crate::values::specified; + +pub use super::specified::{ + AlignContent, AlignItems, AlignTracks, ContentDistribution, JustifyContent, JustifyTracks, + SelfAlignment, +}; +pub use super::specified::{AlignSelf, JustifySelf}; + +/// The computed value for the `justify-items` property. +/// +/// Need to carry around both the specified and computed value to handle the +/// special legacy keyword without destroying style sharing. +/// +/// In particular, `justify-items` is a reset property, so we ought to be able +/// to share its computed representation across elements as long as they match +/// the same rules. Except that it's not true if the specified value for +/// `justify-items` is `legacy` and the computed value of the parent has the +/// `legacy` modifier. +/// +/// So instead of computing `legacy` "normally" looking at get_parent_position(), +/// marking it as uncacheable, we carry the specified value around and handle +/// the special case in `StyleAdjuster` instead, only when the result of the +/// computation would vary. +/// +/// Note that we also need to special-case this property in matching.rs, in +/// order to properly handle changes to the legacy keyword... This all kinda +/// sucks :(. +/// +/// See the discussion in https://bugzil.la/1384542. +#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToCss, ToResolvedValue)] +#[repr(C)] +pub struct ComputedJustifyItems { + /// The specified value for the property. Can contain the bare `legacy` + /// keyword. + #[css(skip)] + pub specified: specified::JustifyItems, + /// The computed value for the property. Cannot contain the bare `legacy` + /// keyword, but note that it could contain it in combination with other + /// keywords like `left`, `right` or `center`. + pub computed: specified::JustifyItems, +} + +pub use self::ComputedJustifyItems as JustifyItems; + +impl JustifyItems { + /// Returns the `legacy` value. + pub fn legacy() -> Self { + Self { + specified: specified::JustifyItems::legacy(), + computed: specified::JustifyItems::normal(), + } + } +} + +impl ToComputedValue for specified::JustifyItems { + type ComputedValue = JustifyItems; + + /// <https://drafts.csswg.org/css-align/#valdef-justify-items-legacy> + fn to_computed_value(&self, _context: &Context) -> JustifyItems { + use crate::values::specified::align; + let specified = *self; + let computed = if self.0 != align::AlignFlags::LEGACY { + *self + } else { + // If the inherited value of `justify-items` includes the + // `legacy` keyword, `legacy` computes to the inherited value, but + // we assume it computes to `normal`, and handle that special-case + // in StyleAdjuster. + Self::normal() + }; + + JustifyItems { + specified, + computed, + } + } + + #[inline] + fn from_computed_value(computed: &JustifyItems) -> Self { + computed.specified + } +} diff --git a/servo/components/style/values/computed/angle.rs b/servo/components/style/values/computed/angle.rs new file mode 100644 index 0000000000..ea321d2233 --- /dev/null +++ b/servo/components/style/values/computed/angle.rs @@ -0,0 +1,101 @@ +/* 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 angles. + +use crate::values::distance::{ComputeSquaredDistance, SquaredDistance}; +use crate::values::CSSFloat; +use crate::Zero; +use std::f64::consts::PI; +use std::fmt::{self, Write}; +use std::{f32, f64}; +use style_traits::{CssWriter, ToCss}; + +/// A computed angle in degrees. +#[derive( + Add, + Animate, + Clone, + Copy, + Debug, + Deserialize, + MallocSizeOf, + PartialEq, + PartialOrd, + Serialize, + ToAnimatedZero, + ToResolvedValue, +)] +#[repr(C)] +pub struct Angle(CSSFloat); + +impl ToCss for Angle { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + self.degrees().to_css(dest)?; + dest.write_str("deg") + } +} + +const RAD_PER_DEG: f64 = PI / 180.0; + +impl Angle { + /// Creates a computed `Angle` value from a radian amount. + pub fn from_radians(radians: CSSFloat) -> Self { + Angle(radians / RAD_PER_DEG as f32) + } + + /// Creates a computed `Angle` value from a degrees amount. + #[inline] + pub fn from_degrees(degrees: CSSFloat) -> Self { + Angle(degrees) + } + + /// Returns the amount of radians this angle represents. + #[inline] + pub fn radians(&self) -> CSSFloat { + self.radians64().min(f32::MAX as f64).max(f32::MIN as f64) as f32 + } + + /// Returns the amount of radians this angle represents as a `f64`. + /// + /// Gecko stores angles as singles, but does this computation using doubles. + /// + /// This is significant enough to mess up rounding to the nearest + /// quarter-turn for 225 degrees, for example. + #[inline] + pub fn radians64(&self) -> f64 { + self.0 as f64 * RAD_PER_DEG + } + + /// Return the value in degrees. + #[inline] + pub fn degrees(&self) -> CSSFloat { + self.0 + } +} + +impl Zero for Angle { + #[inline] + fn zero() -> Self { + Angle(0.0) + } + + #[inline] + fn is_zero(&self) -> bool { + self.0 == 0. + } +} + +impl ComputeSquaredDistance for Angle { + #[inline] + fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { + // Use the formula for calculating the distance between angles defined in SVG: + // https://www.w3.org/TR/SVG/animate.html#complexDistances + self.radians64() + .compute_squared_distance(&other.radians64()) + } +} diff --git a/servo/components/style/values/computed/animation.rs b/servo/components/style/values/computed/animation.rs new file mode 100644 index 0000000000..81e702a3b6 --- /dev/null +++ b/servo/components/style/values/computed/animation.rs @@ -0,0 +1,69 @@ +/* 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 for properties related to animations and transitions + +use crate::values::computed::{Context, LengthPercentage, ToComputedValue}; +use crate::values::generics::animation as generics; +use crate::values::specified::animation as specified; +use std::fmt::{self, Write}; +use style_traits::{CssWriter, ToCss}; + +pub use crate::values::specified::animation::{ + AnimationName, ScrollAxis, ScrollTimelineName, TransitionProperty, +}; + +/// A computed value for the `animation-iteration-count` property. +#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToResolvedValue, ToShmem)] +#[repr(C)] +pub struct AnimationIterationCount(pub f32); + +impl ToComputedValue for specified::AnimationIterationCount { + type ComputedValue = AnimationIterationCount; + + #[inline] + fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { + AnimationIterationCount(match *self { + specified::AnimationIterationCount::Number(n) => n.to_computed_value(context).0, + specified::AnimationIterationCount::Infinite => f32::INFINITY, + }) + } + + #[inline] + fn from_computed_value(computed: &Self::ComputedValue) -> Self { + use crate::values::specified::NonNegativeNumber; + if computed.0.is_infinite() { + specified::AnimationIterationCount::Infinite + } else { + specified::AnimationIterationCount::Number(NonNegativeNumber::new(computed.0)) + } + } +} + +impl AnimationIterationCount { + /// Returns the value `1.0`. + #[inline] + pub fn one() -> Self { + Self(1.0) + } +} + +impl ToCss for AnimationIterationCount { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + if self.0.is_infinite() { + dest.write_str("infinite") + } else { + self.0.to_css(dest) + } + } +} + +/// A computed value for the `animation-timeline` property. +pub type AnimationTimeline = generics::GenericAnimationTimeline<LengthPercentage>; + +/// A computed value for the `view-timeline-inset` property. +pub type ViewTimelineInset = generics::GenericViewTimelineInset<LengthPercentage>; diff --git a/servo/components/style/values/computed/background.rs b/servo/components/style/values/computed/background.rs new file mode 100644 index 0000000000..e2a58f8b74 --- /dev/null +++ b/servo/components/style/values/computed/background.rs @@ -0,0 +1,13 @@ +/* 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 types for CSS values related to backgrounds. + +use crate::values::computed::length::NonNegativeLengthPercentage; +use crate::values::generics::background::BackgroundSize as GenericBackgroundSize; + +pub use crate::values::specified::background::BackgroundRepeat; + +/// A computed value for the `background-size` property. +pub type BackgroundSize = GenericBackgroundSize<NonNegativeLengthPercentage>; diff --git a/servo/components/style/values/computed/basic_shape.rs b/servo/components/style/values/computed/basic_shape.rs new file mode 100644 index 0000000000..fa30220157 --- /dev/null +++ b/servo/components/style/values/computed/basic_shape.rs @@ -0,0 +1,42 @@ +/* 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/. */ + +//! CSS handling for the computed value of +//! [`basic-shape`][basic-shape]s +//! +//! [basic-shape]: https://drafts.csswg.org/css-shapes/#typedef-basic-shape + +use crate::values::computed::url::ComputedUrl; +use crate::values::computed::{Image, LengthPercentage, NonNegativeLengthPercentage}; +use crate::values::generics::basic_shape as generic; + +/// A computed alias for FillRule. +pub use crate::values::generics::basic_shape::FillRule; + +/// A computed `clip-path` value. +pub type ClipPath = generic::GenericClipPath<BasicShape, ComputedUrl>; + +/// A computed `shape-outside` value. +pub type ShapeOutside = generic::GenericShapeOutside<BasicShape, Image>; + +/// A computed basic shape. +pub type BasicShape = generic::GenericBasicShape< + LengthPercentage, + LengthPercentage, + LengthPercentage, + NonNegativeLengthPercentage, +>; + +/// The computed value of `inset()` +pub type InsetRect = generic::InsetRect<LengthPercentage, NonNegativeLengthPercentage>; + +/// A computed circle. +pub type Circle = generic::Circle<LengthPercentage, LengthPercentage, NonNegativeLengthPercentage>; + +/// A computed ellipse. +pub type Ellipse = + generic::Ellipse<LengthPercentage, LengthPercentage, NonNegativeLengthPercentage>; + +/// The computed value of `ShapeRadius` +pub type ShapeRadius = generic::GenericShapeRadius<NonNegativeLengthPercentage>; diff --git a/servo/components/style/values/computed/border.rs b/servo/components/style/values/computed/border.rs new file mode 100644 index 0000000000..e073f671b3 --- /dev/null +++ b/servo/components/style/values/computed/border.rs @@ -0,0 +1,84 @@ +/* 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 types for CSS values related to borders. + +use crate::values::computed::length::{NonNegativeLength, NonNegativeLengthPercentage}; +use crate::values::computed::{NonNegativeNumber, NonNegativeNumberOrPercentage}; +use crate::values::generics::border::BorderCornerRadius as GenericBorderCornerRadius; +use crate::values::generics::border::BorderImageSlice as GenericBorderImageSlice; +use crate::values::generics::border::BorderRadius as GenericBorderRadius; +use crate::values::generics::border::BorderSpacing as GenericBorderSpacing; +use crate::values::generics::border::GenericBorderImageSideWidth; +use crate::values::generics::rect::Rect; +use crate::values::generics::size::Size2D; +use crate::values::generics::NonNegative; +use crate::Zero; +use app_units::Au; + +pub use crate::values::specified::border::BorderImageRepeat; + +/// A computed value for -webkit-text-stroke-width. +pub type LineWidth = Au; + +/// A computed value for border-width (and the like). +pub type BorderSideWidth = Au; + +/// A computed value for the `border-image-width` property. +pub type BorderImageWidth = Rect<BorderImageSideWidth>; + +/// A computed value for a single side of a `border-image-width` property. +pub type BorderImageSideWidth = + GenericBorderImageSideWidth<NonNegativeLengthPercentage, NonNegativeNumber>; + +/// A computed value for the `border-image-slice` property. +pub type BorderImageSlice = GenericBorderImageSlice<NonNegativeNumberOrPercentage>; + +/// A computed value for the `border-radius` property. +pub type BorderRadius = GenericBorderRadius<NonNegativeLengthPercentage>; + +/// A computed value for the `border-*-radius` longhand properties. +pub type BorderCornerRadius = GenericBorderCornerRadius<NonNegativeLengthPercentage>; + +/// A computed value for the `border-spacing` longhand property. +pub type BorderSpacing = GenericBorderSpacing<NonNegativeLength>; + +impl BorderImageSideWidth { + /// Returns `1`. + #[inline] + pub fn one() -> Self { + GenericBorderImageSideWidth::Number(NonNegative(1.)) + } +} + +impl BorderImageSlice { + /// Returns the `100%` value. + #[inline] + pub fn hundred_percent() -> Self { + GenericBorderImageSlice { + offsets: Rect::all(NonNegativeNumberOrPercentage::hundred_percent()), + fill: false, + } + } +} + +impl BorderSpacing { + /// Returns `0 0`. + pub fn zero() -> Self { + GenericBorderSpacing(Size2D::new( + NonNegativeLength::zero(), + NonNegativeLength::zero(), + )) + } + + /// Returns the horizontal spacing. + pub fn horizontal(&self) -> Au { + Au::from(*self.0.width()) + } + + /// Returns the vertical spacing. + pub fn vertical(&self) -> Au { + Au::from(*self.0.height()) + } +} diff --git a/servo/components/style/values/computed/box.rs b/servo/components/style/values/computed/box.rs new file mode 100644 index 0000000000..e25cd10489 --- /dev/null +++ b/servo/components/style/values/computed/box.rs @@ -0,0 +1,254 @@ +/* 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 types for box properties. + +use crate::values::animated::{Animate, Procedure}; +use crate::values::computed::length::{LengthPercentage, NonNegativeLength}; +use crate::values::computed::{Context, Integer, ToComputedValue}; +use crate::values::generics::box_::{ + GenericContainIntrinsicSize, GenericLineClamp, GenericPerspective, GenericVerticalAlign, +}; +use crate::values::specified::box_ as specified; + +pub use crate::values::specified::box_::{ + Appearance, BaselineSource, BreakBetween, BreakWithin, Clear as SpecifiedClear, Contain, + ContainerName, ContainerType, ContentVisibility, Display, Float as SpecifiedFloat, Overflow, + OverflowAnchor, OverflowClipBox, OverscrollBehavior, ScrollSnapAlign, ScrollSnapAxis, + ScrollSnapStop, ScrollSnapStrictness, ScrollSnapType, ScrollbarGutter, TouchAction, WillChange, +}; + +/// A computed value for the `vertical-align` property. +pub type VerticalAlign = GenericVerticalAlign<LengthPercentage>; + +/// A computed value for the `contain-intrinsic-size` property. +pub type ContainIntrinsicSize = GenericContainIntrinsicSize<NonNegativeLength>; + +/// A computed value for the `line-clamp` property. +pub type LineClamp = GenericLineClamp<Integer>; + +impl Animate for LineClamp { + #[inline] + fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { + if self.is_none() != other.is_none() { + return Err(()); + } + if self.is_none() { + return Ok(Self::none()); + } + Ok(Self(self.0.animate(&other.0, procedure)?.max(1))) + } +} + +/// A computed value for the `perspective` property. +pub type Perspective = GenericPerspective<NonNegativeLength>; + +#[allow(missing_docs)] +#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] +#[derive( + Clone, + Copy, + Debug, + Eq, + FromPrimitive, + Hash, + MallocSizeOf, + Parse, + PartialEq, + SpecifiedValueInfo, + ToCss, + ToResolvedValue, +)] +#[repr(u8)] +/// A computed value for the `float` property. +pub enum Float { + Left, + Right, + None, +} + +impl Float { + /// Returns true if `self` is not `None`. + pub fn is_floating(self) -> bool { + self != Self::None + } +} + +impl ToComputedValue for SpecifiedFloat { + type ComputedValue = Float; + + #[inline] + fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { + let ltr = context.style().writing_mode.is_bidi_ltr(); + // https://drafts.csswg.org/css-logical-props/#float-clear + match *self { + SpecifiedFloat::InlineStart => { + context + .rule_cache_conditions + .borrow_mut() + .set_writing_mode_dependency(context.builder.writing_mode); + if ltr { + Float::Left + } else { + Float::Right + } + }, + SpecifiedFloat::InlineEnd => { + context + .rule_cache_conditions + .borrow_mut() + .set_writing_mode_dependency(context.builder.writing_mode); + if ltr { + Float::Right + } else { + Float::Left + } + }, + SpecifiedFloat::Left => Float::Left, + SpecifiedFloat::Right => Float::Right, + SpecifiedFloat::None => Float::None, + } + } + + #[inline] + fn from_computed_value(computed: &Self::ComputedValue) -> SpecifiedFloat { + match *computed { + Float::Left => SpecifiedFloat::Left, + Float::Right => SpecifiedFloat::Right, + Float::None => SpecifiedFloat::None, + } + } +} + +#[allow(missing_docs)] +#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] +#[derive( + Clone, + Copy, + Debug, + Eq, + FromPrimitive, + Hash, + MallocSizeOf, + Parse, + PartialEq, + SpecifiedValueInfo, + ToCss, + ToResolvedValue, +)] +/// A computed value for the `clear` property. +#[repr(u8)] +pub enum Clear { + None, + Left, + Right, + Both, +} + +impl ToComputedValue for SpecifiedClear { + type ComputedValue = Clear; + + #[inline] + fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { + let ltr = context.style().writing_mode.is_bidi_ltr(); + // https://drafts.csswg.org/css-logical-props/#float-clear + match *self { + SpecifiedClear::InlineStart => { + context + .rule_cache_conditions + .borrow_mut() + .set_writing_mode_dependency(context.builder.writing_mode); + if ltr { + Clear::Left + } else { + Clear::Right + } + }, + SpecifiedClear::InlineEnd => { + context + .rule_cache_conditions + .borrow_mut() + .set_writing_mode_dependency(context.builder.writing_mode); + if ltr { + Clear::Right + } else { + Clear::Left + } + }, + SpecifiedClear::None => Clear::None, + SpecifiedClear::Left => Clear::Left, + SpecifiedClear::Right => Clear::Right, + SpecifiedClear::Both => Clear::Both, + } + } + + #[inline] + fn from_computed_value(computed: &Self::ComputedValue) -> SpecifiedClear { + match *computed { + Clear::None => SpecifiedClear::None, + Clear::Left => SpecifiedClear::Left, + Clear::Right => SpecifiedClear::Right, + Clear::Both => SpecifiedClear::Both, + } + } +} + +/// A computed value for the `resize` property. +#[allow(missing_docs)] +#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] +#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, Parse, PartialEq, ToCss, ToResolvedValue)] +#[repr(u8)] +pub enum Resize { + None, + Both, + Horizontal, + Vertical, +} + +impl ToComputedValue for specified::Resize { + type ComputedValue = Resize; + + #[inline] + fn to_computed_value(&self, context: &Context) -> Resize { + let is_vertical = context.style().writing_mode.is_vertical(); + match self { + specified::Resize::Inline => { + context + .rule_cache_conditions + .borrow_mut() + .set_writing_mode_dependency(context.builder.writing_mode); + if is_vertical { + Resize::Vertical + } else { + Resize::Horizontal + } + }, + specified::Resize::Block => { + context + .rule_cache_conditions + .borrow_mut() + .set_writing_mode_dependency(context.builder.writing_mode); + if is_vertical { + Resize::Horizontal + } else { + Resize::Vertical + } + }, + specified::Resize::None => Resize::None, + specified::Resize::Both => Resize::Both, + specified::Resize::Horizontal => Resize::Horizontal, + specified::Resize::Vertical => Resize::Vertical, + } + } + + #[inline] + fn from_computed_value(computed: &Resize) -> specified::Resize { + match computed { + Resize::None => specified::Resize::None, + Resize::Both => specified::Resize::Both, + Resize::Horizontal => specified::Resize::Horizontal, + Resize::Vertical => specified::Resize::Vertical, + } + } +} diff --git a/servo/components/style/values/computed/color.rs b/servo/components/style/values/computed/color.rs new file mode 100644 index 0000000000..35cfdb9710 --- /dev/null +++ b/servo/components/style/values/computed/color.rs @@ -0,0 +1,104 @@ +/* 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 color values. + +use crate::color::AbsoluteColor; +use crate::values::animated::ToAnimatedZero; +use crate::values::computed::percentage::Percentage; +use crate::values::generics::color::{ + GenericCaretColor, GenericColor, GenericColorMix, GenericColorOrAuto, +}; +use cssparser::Color as CSSParserColor; +use std::fmt; +use style_traits::{CssWriter, ToCss}; + +pub use crate::values::specified::color::{ColorScheme, ForcedColorAdjust, PrintColorAdjust}; + +/// The computed value of the `color` property. +pub type ColorPropertyValue = AbsoluteColor; + +/// The computed value of `-moz-font-smoothing-background-color`. +pub type MozFontSmoothingBackgroundColor = AbsoluteColor; + +/// A computed value for `<color>`. +pub type Color = GenericColor<Percentage>; + +/// A computed color-mix(). +pub type ColorMix = GenericColorMix<Color, Percentage>; + +impl ToCss for Color { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: fmt::Write, + { + match *self { + Self::Absolute(ref c) => c.to_css(dest), + Self::CurrentColor => cssparser::ToCss::to_css(&CSSParserColor::CurrentColor, dest), + Self::ColorMix(ref m) => m.to_css(dest), + } + } +} + +impl Color { + /// Create a new computed [`Color`] from a given color-mix, simplifying it to an absolute color + /// if possible. + pub fn from_color_mix(color_mix: ColorMix) -> Self { + if let Some(absolute) = color_mix.mix_to_absolute() { + Self::Absolute(absolute) + } else { + Self::ColorMix(Box::new(color_mix)) + } + } + + /// Returns a complex color value representing transparent. + pub fn transparent() -> Color { + Color::Absolute(AbsoluteColor::transparent()) + } + + /// Returns opaque black. + pub fn black() -> Color { + Color::Absolute(AbsoluteColor::black()) + } + + /// Returns opaque white. + pub fn white() -> Color { + Color::Absolute(AbsoluteColor::white()) + } + + /// Combine this complex color with the given foreground color into an + /// absolute color. + pub fn resolve_to_absolute(&self, current_color: &AbsoluteColor) -> AbsoluteColor { + use crate::values::specified::percentage::ToPercentage; + + match *self { + Self::Absolute(c) => c, + Self::CurrentColor => *current_color, + Self::ColorMix(ref mix) => { + let left = mix.left.resolve_to_absolute(current_color); + let right = mix.right.resolve_to_absolute(current_color); + crate::color::mix::mix( + mix.interpolation, + &left, + mix.left_percentage.to_percentage(), + &right, + mix.right_percentage.to_percentage(), + mix.normalize_weights, + ) + }, + } + } +} + +impl ToAnimatedZero for AbsoluteColor { + fn to_animated_zero(&self) -> Result<Self, ()> { + Ok(Self::transparent()) + } +} + +/// auto | <color> +pub type ColorOrAuto = GenericColorOrAuto<Color>; + +/// caret-color +pub type CaretColor = GenericCaretColor<Color>; diff --git a/servo/components/style/values/computed/column.rs b/servo/components/style/values/computed/column.rs new file mode 100644 index 0000000000..38437ea110 --- /dev/null +++ b/servo/components/style/values/computed/column.rs @@ -0,0 +1,11 @@ +/* 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 types for the column properties. + +use crate::values::computed::PositiveInteger; +use crate::values::generics::column::ColumnCount as GenericColumnCount; + +/// A computed type for `column-count` values. +pub type ColumnCount = GenericColumnCount<PositiveInteger>; diff --git a/servo/components/style/values/computed/counters.rs b/servo/components/style/values/computed/counters.rs new file mode 100644 index 0000000000..fd5e915c4a --- /dev/null +++ b/servo/components/style/values/computed/counters.rs @@ -0,0 +1,26 @@ +/* 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 for counter properties + +use crate::values::computed::image::Image; +use crate::values::generics::counters as generics; +use crate::values::generics::counters::CounterIncrement as GenericCounterIncrement; +use crate::values::generics::counters::CounterReset as GenericCounterReset; +use crate::values::generics::counters::CounterSet as GenericCounterSet; + +/// A computed value for the `counter-increment` property. +pub type CounterIncrement = GenericCounterIncrement<i32>; + +/// A computed value for the `counter-reset` property. +pub type CounterReset = GenericCounterReset<i32>; + +/// A computed value for the `counter-set` property. +pub type CounterSet = GenericCounterSet<i32>; + +/// A computed value for the `content` property. +pub type Content = generics::GenericContent<Image>; + +/// A computed content item. +pub type ContentItem = generics::GenericContentItem<Image>; diff --git a/servo/components/style/values/computed/easing.rs b/servo/components/style/values/computed/easing.rs new file mode 100644 index 0000000000..d351b3c71d --- /dev/null +++ b/servo/components/style/values/computed/easing.rs @@ -0,0 +1,109 @@ +/* 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 types for CSS Easing functions. + +use euclid::approxeq::ApproxEq; + +use crate::bezier::Bezier; +use crate::piecewise_linear::PiecewiseLinearFunction; +use crate::values::computed::{Integer, Number}; +use crate::values::generics::easing::{self, BeforeFlag, StepPosition, TimingKeyword}; + +/// A computed timing function. +pub type ComputedTimingFunction = easing::TimingFunction<Integer, Number, PiecewiseLinearFunction>; + +/// An alias of the computed timing function. +pub type TimingFunction = ComputedTimingFunction; + +impl ComputedTimingFunction { + fn calculate_step_output( + steps: i32, + pos: StepPosition, + progress: f64, + before_flag: BeforeFlag, + ) -> f64 { + // User specified values can cause overflow (bug 1706157). Increments/decrements + // should be gravefully handled. + let mut current_step = (progress * (steps as f64)).floor() as i32; + + // Increment current step if it is jump-start or start. + if pos == StepPosition::Start || + pos == StepPosition::JumpStart || + pos == StepPosition::JumpBoth + { + current_step = current_step.checked_add(1).unwrap_or(current_step); + } + + // If the "before flag" is set and we are at a transition point, + // drop back a step + if before_flag == BeforeFlag::Set && + (progress * steps as f64).rem_euclid(1.0).approx_eq(&0.0) + { + current_step = current_step.checked_sub(1).unwrap_or(current_step); + } + + // We should not produce a result outside [0, 1] unless we have an + // input outside that range. This takes care of steps that would otherwise + // occur at boundaries. + if progress >= 0.0 && current_step < 0 { + current_step = 0; + } + + // |jumps| should always be in [1, i32::MAX]. + let jumps = if pos == StepPosition::JumpBoth { + steps.checked_add(1).unwrap_or(steps) + } else if pos == StepPosition::JumpNone { + steps.checked_sub(1).unwrap_or(steps) + } else { + steps + }; + + if progress <= 1.0 && current_step > jumps { + current_step = jumps; + } + + (current_step as f64) / (jumps as f64) + } + + /// The output of the timing function given the progress ratio of this animation. + pub fn calculate_output(&self, progress: f64, before_flag: BeforeFlag, epsilon: f64) -> f64 { + let progress = match self { + TimingFunction::CubicBezier { x1, y1, x2, y2 } => { + Bezier::calculate_bezier_output(progress, epsilon, *x1, *y1, *x2, *y2) + }, + TimingFunction::Steps(steps, pos) => { + Self::calculate_step_output(*steps, *pos, progress, before_flag) + }, + TimingFunction::LinearFunction(function) => function.at(progress as f32).into(), + TimingFunction::Keyword(keyword) => match keyword { + TimingKeyword::Linear => progress, + TimingKeyword::Ease => { + Bezier::calculate_bezier_output(progress, epsilon, 0.25, 0.1, 0.25, 1.) + }, + TimingKeyword::EaseIn => { + Bezier::calculate_bezier_output(progress, epsilon, 0.42, 0., 1., 1.) + }, + TimingKeyword::EaseOut => { + Bezier::calculate_bezier_output(progress, epsilon, 0., 0., 0.58, 1.) + }, + TimingKeyword::EaseInOut => { + Bezier::calculate_bezier_output(progress, epsilon, 0.42, 0., 0.58, 1.) + }, + }, + }; + + // The output progress value of an easing function is a real number in the range: + // [-inf, inf]. + // https://drafts.csswg.org/css-easing-1/#output-progress-value + // + // However, we expect to use the finite progress for interpolation and web-animations + // https://drafts.csswg.org/css-values-4/#interpolation + // https://drafts.csswg.org/web-animations-1/#dom-computedeffecttiming-progress + // + // So we clamp the infinite progress, per the spec issue: + // https://github.com/w3c/csswg-drafts/issues/8344 + progress.min(f64::MAX).max(f64::MIN) + } +} diff --git a/servo/components/style/values/computed/effects.rs b/servo/components/style/values/computed/effects.rs new file mode 100644 index 0000000000..b0a92024ca --- /dev/null +++ b/servo/components/style/values/computed/effects.rs @@ -0,0 +1,44 @@ +/* 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 types for CSS values related to effects. + +use crate::values::computed::color::Color; +use crate::values::computed::length::{Length, NonNegativeLength}; +#[cfg(feature = "gecko")] +use crate::values::computed::url::ComputedUrl; +use crate::values::computed::{Angle, NonNegativeNumber, ZeroToOneNumber}; +use crate::values::generics::effects::BoxShadow as GenericBoxShadow; +use crate::values::generics::effects::Filter as GenericFilter; +use crate::values::generics::effects::SimpleShadow as GenericSimpleShadow; +#[cfg(not(feature = "gecko"))] +use crate::values::Impossible; + +/// A computed value for a single shadow of the `box-shadow` property. +pub type BoxShadow = GenericBoxShadow<Color, Length, NonNegativeLength, Length>; + +/// A computed value for a single `filter`. +#[cfg(feature = "gecko")] +pub type Filter = GenericFilter< + Angle, + NonNegativeNumber, + ZeroToOneNumber, + NonNegativeLength, + SimpleShadow, + ComputedUrl, +>; + +/// A computed value for a single `filter`. +#[cfg(feature = "servo")] +pub type Filter = GenericFilter< + Angle, + NonNegativeNumber, + ZeroToOneNumber, + NonNegativeLength, + Impossible, + Impossible, +>; + +/// A computed value for the `drop-shadow()` filter. +pub type SimpleShadow = GenericSimpleShadow<Color, Length, NonNegativeLength>; diff --git a/servo/components/style/values/computed/flex.rs b/servo/components/style/values/computed/flex.rs new file mode 100644 index 0000000000..95c497ecf6 --- /dev/null +++ b/servo/components/style/values/computed/flex.rs @@ -0,0 +1,19 @@ +/* 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 types for CSS values related to flexbox. + +use crate::values::computed::Size; +use crate::values::generics::flex::FlexBasis as GenericFlexBasis; + +/// A computed value for the `flex-basis` property. +pub type FlexBasis = GenericFlexBasis<Size>; + +impl FlexBasis { + /// `auto` + #[inline] + pub fn auto() -> Self { + GenericFlexBasis::Size(Size::auto()) + } +} diff --git a/servo/components/style/values/computed/font.rs b/servo/components/style/values/computed/font.rs new file mode 100644 index 0000000000..725e527c0c --- /dev/null +++ b/servo/components/style/values/computed/font.rs @@ -0,0 +1,1239 @@ +/* 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 for font properties + +use crate::parser::{Parse, ParserContext}; +use crate::values::animated::ToAnimatedValue; +use crate::values::computed::{ + Angle, Context, Integer, Length, NonNegativeLength, NonNegativeNumber, Number, Percentage, + ToComputedValue, +}; +use crate::values::generics::font::{ + FeatureTagValue, FontSettings, TaggedFontValue, VariationValue, +}; +use crate::values::generics::{font as generics, NonNegative}; +use crate::values::specified::font::{ + self as specified, KeywordInfo, MAX_FONT_WEIGHT, MIN_FONT_WEIGHT, +}; +use crate::values::specified::length::{FontBaseSize, NoCalcLength}; +use crate::Atom; +use cssparser::{serialize_identifier, CssStringWriter, Parser}; +#[cfg(feature = "gecko")] +use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; +use std::fmt::{self, Write}; +use style_traits::{CssWriter, ParseError, ToCss}; + +pub use crate::values::computed::Length as MozScriptMinSize; +pub use crate::values::specified::font::MozScriptSizeMultiplier; +pub use crate::values::specified::font::{FontPalette, FontSynthesis}; +pub use crate::values::specified::font::{ + FontVariantAlternates, FontVariantEastAsian, FontVariantLigatures, FontVariantNumeric, XLang, + XTextScale, +}; +pub use crate::values::specified::Integer as SpecifiedInteger; +pub use crate::values::specified::Number as SpecifiedNumber; + +/// Generic template for font property type classes that use a fixed-point +/// internal representation with `FRACTION_BITS` for the fractional part. +/// +/// Values are constructed from and exposed as floating-point, but stored +/// internally as fixed point, so there will be a quantization effect on +/// fractional values, depending on the number of fractional bits used. +/// +/// Using (16-bit) fixed-point types rather than floats for these style +/// attributes reduces the memory footprint of gfxFontEntry and gfxFontStyle; it +/// will also tend to reduce the number of distinct font instances that get +/// created, particularly when styles are animated or set to arbitrary values +/// (e.g. by sliders in the UI), which should reduce pressure on graphics +/// resources and improve cache hit rates. +/// +/// cbindgen:derive-lt +/// cbindgen:derive-lte +/// cbindgen:derive-gt +/// cbindgen:derive-gte +#[repr(C)] +#[derive( + Clone, + ComputeSquaredDistance, + Copy, + Debug, + Hash, + MallocSizeOf, + PartialEq, + PartialOrd, + ToResolvedValue, +)] +pub struct FixedPoint<T, const FRACTION_BITS: u16> { + value: T, +} + +impl<T, const FRACTION_BITS: u16> FixedPoint<T, FRACTION_BITS> +where + T: num_traits::cast::AsPrimitive<f32>, + f32: num_traits::cast::AsPrimitive<T>, +{ + const SCALE: u16 = 1 << FRACTION_BITS; + const INVERSE_SCALE: f32 = 1.0 / Self::SCALE as f32; + + /// Returns a fixed-point bit from a floating-point context. + fn from_float(v: f32) -> Self { + use num_traits::cast::AsPrimitive; + Self { + value: (v * Self::SCALE as f32).round().as_(), + } + } + + /// Returns the floating-point representation. + fn to_float(&self) -> f32 { + self.value.as_() * Self::INVERSE_SCALE + } +} + +/// font-weight: range 1..1000, fractional values permitted; keywords +/// 'normal', 'bold' aliased to 400, 700 respectively. +/// +/// We use an unsigned 10.6 fixed-point value (range 0.0 - 1023.984375) +pub const FONT_WEIGHT_FRACTION_BITS: u16 = 6; + +/// This is an alias which is useful mostly as a cbindgen / C++ inference +/// workaround. +pub type FontWeightFixedPoint = FixedPoint<u16, FONT_WEIGHT_FRACTION_BITS>; + +/// A value for the font-weight property per: +/// +/// https://drafts.csswg.org/css-fonts-4/#propdef-font-weight +/// +/// cbindgen:derive-lt +/// cbindgen:derive-lte +/// cbindgen:derive-gt +/// cbindgen:derive-gte +#[derive( + Clone, + ComputeSquaredDistance, + Copy, + Debug, + Hash, + MallocSizeOf, + PartialEq, + PartialOrd, + ToResolvedValue, +)] +#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] +#[repr(C)] +pub struct FontWeight(FontWeightFixedPoint); +impl ToAnimatedValue for FontWeight { + type AnimatedValue = Number; + + #[inline] + fn to_animated_value(self) -> Self::AnimatedValue { + self.value() + } + + #[inline] + fn from_animated_value(animated: Self::AnimatedValue) -> Self { + FontWeight::from_float(animated) + } +} + +impl ToCss for FontWeight { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: fmt::Write, + { + self.value().to_css(dest) + } +} + +impl FontWeight { + /// The `normal` keyword. + pub const NORMAL: FontWeight = FontWeight(FontWeightFixedPoint { + value: 400 << FONT_WEIGHT_FRACTION_BITS, + }); + + /// The `bold` value. + pub const BOLD: FontWeight = FontWeight(FontWeightFixedPoint { + value: 700 << FONT_WEIGHT_FRACTION_BITS, + }); + + /// The threshold from which we consider a font bold. + pub const BOLD_THRESHOLD: FontWeight = FontWeight(FontWeightFixedPoint { + value: 600 << FONT_WEIGHT_FRACTION_BITS, + }); + + /// Returns the `normal` keyword value. + pub fn normal() -> Self { + Self::NORMAL + } + + /// Weither this weight is bold + pub fn is_bold(&self) -> bool { + *self >= Self::BOLD_THRESHOLD + } + + /// Returns the value as a float. + pub fn value(&self) -> f32 { + self.0.to_float() + } + + /// Construct a valid weight from a float value. + pub fn from_float(v: f32) -> Self { + Self(FixedPoint::from_float( + v.max(MIN_FONT_WEIGHT).min(MAX_FONT_WEIGHT), + )) + } + + /// Return the bolder weight. + /// + /// See the table in: + /// https://drafts.csswg.org/css-fonts-4/#font-weight-numeric-values + pub fn bolder(self) -> Self { + let value = self.value(); + if value < 350. { + return Self::NORMAL; + } + if value < 550. { + return Self::BOLD; + } + Self::from_float(value.max(900.)) + } + + /// Return the lighter weight. + /// + /// See the table in: + /// https://drafts.csswg.org/css-fonts-4/#font-weight-numeric-values + pub fn lighter(self) -> Self { + let value = self.value(); + if value < 550. { + return Self::from_float(value.min(100.)); + } + if value < 750. { + return Self::NORMAL; + } + Self::BOLD + } +} + +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + MallocSizeOf, + PartialEq, + ToAnimatedZero, + ToCss, + ToResolvedValue, +)] +#[cfg_attr(feature = "servo", derive(Serialize, Deserialize))] +/// The computed value of font-size +pub struct FontSize { + /// The computed size, that we use to compute ems etc. This accounts for + /// e.g., text-zoom. + pub computed_size: NonNegativeLength, + /// The actual used size. This is the computed font size, potentially + /// constrained by other factors like minimum font-size settings and so on. + #[css(skip)] + pub used_size: NonNegativeLength, + /// If derived from a keyword, the keyword and additional transformations applied to it + #[css(skip)] + pub keyword_info: KeywordInfo, +} + +impl FontSize { + /// The actual computed font size. + #[inline] + pub fn computed_size(&self) -> Length { + self.computed_size.0 + } + + /// The actual used font size. + #[inline] + pub fn used_size(&self) -> Length { + self.used_size.0 + } + + #[inline] + /// Get default value of font size. + pub fn medium() -> Self { + Self { + computed_size: NonNegative(Length::new(specified::FONT_MEDIUM_PX)), + used_size: NonNegative(Length::new(specified::FONT_MEDIUM_PX)), + keyword_info: KeywordInfo::medium(), + } + } +} + +impl ToAnimatedValue for FontSize { + type AnimatedValue = Length; + + #[inline] + fn to_animated_value(self) -> Self::AnimatedValue { + self.computed_size.0 + } + + #[inline] + fn from_animated_value(animated: Self::AnimatedValue) -> Self { + FontSize { + computed_size: NonNegative(animated.clamp_to_non_negative()), + used_size: NonNegative(animated.clamp_to_non_negative()), + keyword_info: KeywordInfo::none(), + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq, ToComputedValue, ToResolvedValue)] +#[cfg_attr(feature = "servo", derive(Hash, MallocSizeOf, Serialize, Deserialize))] +/// Specifies a prioritized list of font family names or generic family names. +#[repr(C)] +pub struct FontFamily { + /// The actual list of family names. + pub families: FontFamilyList, + /// Whether this font-family came from a specified system-font. + pub is_system_font: bool, + /// Whether this is the initial font-family that might react to language + /// changes. + pub is_initial: bool, +} + +macro_rules! static_font_family { + ($ident:ident, $family:expr) => { + lazy_static! { + static ref $ident: FontFamily = FontFamily { + families: FontFamilyList { + list: crate::ArcSlice::from_iter_leaked(std::iter::once($family)), + }, + is_system_font: false, + is_initial: false, + }; + } + }; +} + +impl FontFamily { + #[inline] + /// Get default font family as `serif` which is a generic font-family + pub fn serif() -> Self { + Self::generic(GenericFontFamily::Serif).clone() + } + + /// Returns the font family for `-moz-bullet-font`. + pub(crate) fn moz_bullet() -> &'static Self { + static_font_family!( + MOZ_BULLET, + SingleFontFamily::FamilyName(FamilyName { + name: atom!("-moz-bullet-font"), + syntax: FontFamilyNameSyntax::Identifiers, + }) + ); + + &*MOZ_BULLET + } + + /// Returns a font family for a single system font. + pub fn for_system_font(name: &str) -> Self { + Self { + families: FontFamilyList { + list: crate::ArcSlice::from_iter(std::iter::once(SingleFontFamily::FamilyName( + FamilyName { + name: Atom::from(name), + syntax: FontFamilyNameSyntax::Identifiers, + }, + ))), + }, + is_system_font: true, + is_initial: false, + } + } + + /// Returns a generic font family. + pub fn generic(generic: GenericFontFamily) -> &'static Self { + macro_rules! generic_font_family { + ($ident:ident, $family:ident) => { + static_font_family!( + $ident, + SingleFontFamily::Generic(GenericFontFamily::$family) + ) + }; + } + + generic_font_family!(SERIF, Serif); + generic_font_family!(SANS_SERIF, SansSerif); + generic_font_family!(MONOSPACE, Monospace); + generic_font_family!(CURSIVE, Cursive); + generic_font_family!(FANTASY, Fantasy); + generic_font_family!(MOZ_EMOJI, MozEmoji); + generic_font_family!(SYSTEM_UI, SystemUi); + + let family = match generic { + GenericFontFamily::None => { + debug_assert!(false, "Bogus caller!"); + &*SERIF + }, + GenericFontFamily::Serif => &*SERIF, + GenericFontFamily::SansSerif => &*SANS_SERIF, + GenericFontFamily::Monospace => &*MONOSPACE, + GenericFontFamily::Cursive => &*CURSIVE, + GenericFontFamily::Fantasy => &*FANTASY, + GenericFontFamily::MozEmoji => &*MOZ_EMOJI, + GenericFontFamily::SystemUi => &*SYSTEM_UI, + }; + debug_assert_eq!(*family.families.iter().next().unwrap(), SingleFontFamily::Generic(generic)); + family + } +} + +#[cfg(feature = "gecko")] +impl MallocSizeOf for FontFamily { + fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + use malloc_size_of::MallocUnconditionalSizeOf; + // SharedFontList objects are generally measured from the pointer stored + // in the specified value. So only count this if the SharedFontList is + // unshared. + let shared_font_list = &self.families.list; + if shared_font_list.is_unique() { + shared_font_list.unconditional_size_of(ops) + } else { + 0 + } + } +} + +impl ToCss for FontFamily { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: fmt::Write, + { + let mut iter = self.families.iter(); + match iter.next() { + Some(f) => f.to_css(dest)?, + None => return Ok(()), + } + for family in iter { + dest.write_str(", ")?; + family.to_css(dest)?; + } + Ok(()) + } +} + +/// The name of a font family of choice. +#[derive( + Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, +)] +#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] +#[repr(C)] +pub struct FamilyName { + /// Name of the font family. + pub name: Atom, + /// Syntax of the font family. + pub syntax: FontFamilyNameSyntax, +} + +impl FamilyName { + fn is_known_icon_font_family(&self) -> bool { + use crate::gecko_bindings::bindings; + unsafe { bindings::Gecko_IsKnownIconFontFamily(self.name.as_ptr()) } + } +} + +impl ToCss for FamilyName { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: fmt::Write, + { + match self.syntax { + FontFamilyNameSyntax::Quoted => { + dest.write_char('"')?; + write!(CssStringWriter::new(dest), "{}", self.name)?; + dest.write_char('"') + }, + FontFamilyNameSyntax::Identifiers => { + let mut first = true; + for ident in self.name.to_string().split(' ') { + if first { + first = false; + } else { + dest.write_char(' ')?; + } + debug_assert!( + !ident.is_empty(), + "Family name with leading, \ + trailing, or consecutive white spaces should \ + have been marked quoted by the parser" + ); + serialize_identifier(ident, dest)?; + } + Ok(()) + }, + } + } +} + +#[derive( + Clone, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, +)] +#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] +/// Font family names must either be given quoted as strings, +/// or unquoted as a sequence of one or more identifiers. +#[repr(u8)] +pub enum FontFamilyNameSyntax { + /// The family name was specified in a quoted form, e.g. "Font Name" + /// or 'Font Name'. + Quoted, + + /// The family name was specified in an unquoted form as a sequence of + /// identifiers. + Identifiers, +} + +/// A set of faces that vary in weight, width or slope. +/// cbindgen:derive-mut-casts=true +#[derive( + Clone, Debug, Eq, MallocSizeOf, PartialEq, ToCss, ToComputedValue, ToResolvedValue, ToShmem, +)] +#[cfg_attr(feature = "servo", derive(Deserialize, Serialize, Hash))] +#[repr(u8)] +pub enum SingleFontFamily { + /// The name of a font family of choice. + FamilyName(FamilyName), + /// Generic family name. + Generic(GenericFontFamily), +} + +fn system_ui_enabled(_: &ParserContext) -> bool { + static_prefs::pref!("layout.css.system-ui.enabled") +} + +/// A generic font-family name. +/// +/// The order here is important, if you change it make sure that +/// `gfxPlatformFontList.h`s ranged array and `gfxFontFamilyList`'s +/// sSingleGenerics are updated as well. +/// +/// NOTE(emilio): Should be u8, but it's a u32 because of ABI issues between GCC +/// and LLVM see https://bugs.llvm.org/show_bug.cgi?id=44228 / bug 1600735 / +/// bug 1726515. +#[derive( + Clone, + Copy, + Debug, + Eq, + Hash, + MallocSizeOf, + PartialEq, + Parse, + ToCss, + ToComputedValue, + ToResolvedValue, + ToShmem, +)] +#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] +#[repr(u32)] +#[allow(missing_docs)] +pub enum GenericFontFamily { + /// No generic family specified, only for internal usage. + /// + /// NOTE(emilio): Gecko code relies on this variant being zero. + #[css(skip)] + None = 0, + Serif, + SansSerif, + #[parse(aliases = "-moz-fixed")] + Monospace, + Cursive, + Fantasy, + #[parse(condition = "system_ui_enabled")] + SystemUi, + /// An internal value for emoji font selection. + #[css(skip)] + #[cfg(feature = "gecko")] + MozEmoji, +} + +impl GenericFontFamily { + /// When we disallow websites to override fonts, we ignore some generic + /// families that the website might specify, since they're not configured by + /// the user. See bug 789788 and bug 1730098. + pub(crate) fn valid_for_user_font_prioritization(self) -> bool { + match self { + Self::None | Self::Fantasy | Self::Cursive | Self::SystemUi | Self::MozEmoji => false, + + Self::Serif | Self::SansSerif | Self::Monospace => true, + } + } +} + +impl Parse for SingleFontFamily { + /// Parse a font-family value. + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + if let Ok(value) = input.try_parse(|i| i.expect_string_cloned()) { + return Ok(SingleFontFamily::FamilyName(FamilyName { + name: Atom::from(&*value), + syntax: FontFamilyNameSyntax::Quoted, + })); + } + + if let Ok(generic) = input.try_parse(|i| GenericFontFamily::parse(context, i)) { + return Ok(SingleFontFamily::Generic(generic)); + } + + let first_ident = input.expect_ident_cloned()?; + let reserved = match_ignore_ascii_case! { &first_ident, + // https://drafts.csswg.org/css-fonts/#propdef-font-family + // "Font family names that happen to be the same as a keyword value + // (`inherit`, `serif`, `sans-serif`, `monospace`, `fantasy`, and `cursive`) + // must be quoted to prevent confusion with the keywords with the same names. + // The keywords ‘initial’ and ‘default’ are reserved for future use + // and must also be quoted when used as font names. + // UAs must not consider these keywords as matching the <family-name> type." + "inherit" | "initial" | "unset" | "revert" | "default" => true, + _ => false, + }; + + let mut value = first_ident.as_ref().to_owned(); + let mut serialize_quoted = value.contains(' '); + + // These keywords are not allowed by themselves. + // The only way this value can be valid with with another keyword. + if reserved { + let ident = input.expect_ident()?; + serialize_quoted = serialize_quoted || ident.contains(' '); + value.push(' '); + value.push_str(&ident); + } + while let Ok(ident) = input.try_parse(|i| i.expect_ident_cloned()) { + serialize_quoted = serialize_quoted || ident.contains(' '); + value.push(' '); + value.push_str(&ident); + } + let syntax = if serialize_quoted { + // For font family names which contains special white spaces, e.g. + // `font-family: \ a\ \ b\ \ c\ ;`, it is tricky to serialize them + // as identifiers correctly. Just mark them quoted so we don't need + // to worry about them in serialization code. + FontFamilyNameSyntax::Quoted + } else { + FontFamilyNameSyntax::Identifiers + }; + Ok(SingleFontFamily::FamilyName(FamilyName { + name: Atom::from(value), + syntax, + })) + } +} + +#[cfg(feature = "servo")] +impl SingleFontFamily { + /// Get the corresponding font-family with Atom + pub fn from_atom(input: Atom) -> SingleFontFamily { + match input { + atom!("serif") => return SingleFontFamily::Generic(GenericFontFamily::Serif), + atom!("sans-serif") => return SingleFontFamily::Generic(GenericFontFamily::SansSerif), + atom!("cursive") => return SingleFontFamily::Generic(GenericFontFamily::Cursive), + atom!("fantasy") => return SingleFontFamily::Generic(GenericFontFamily::Fantasy), + atom!("monospace") => return SingleFontFamily::Generic(GenericFontFamily::Monospace), + _ => {}, + } + + match_ignore_ascii_case! { &input, + "serif" => return SingleFontFamily::Generic(GenericFontFamily::Serif), + "sans-serif" => return SingleFontFamily::Generic(GenericFontFamily::SansSerif), + "cursive" => return SingleFontFamily::Generic(GenericFontFamily::Cursive), + "fantasy" => return SingleFontFamily::Generic(GenericFontFamily::Fantasy), + "monospace" => return SingleFontFamily::Generic(GenericFontFamily::Monospace), + _ => {} + } + + // We don't know if it's quoted or not. So we set it to + // quoted by default. + SingleFontFamily::FamilyName(FamilyName { + name: input, + syntax: FontFamilyNameSyntax::Quoted, + }) + } +} + +/// A list of font families. +#[derive(Clone, Debug, ToComputedValue, ToResolvedValue, ToShmem, PartialEq, Eq)] +#[repr(C)] +pub struct FontFamilyList { + /// The actual list of font families specified. + pub list: crate::ArcSlice<SingleFontFamily>, +} + +impl FontFamilyList { + /// Return iterator of SingleFontFamily + pub fn iter(&self) -> impl Iterator<Item = &SingleFontFamily> { + self.list.iter() + } + + /// If there's a generic font family on the list which is suitable for user + /// font prioritization, then move it ahead of the other families in the list, + /// except for any families known to be ligature-based icon fonts, where using a + /// generic instead of the site's specified font may cause substantial breakage. + /// If no suitable generic is found in the list, insert the default generic ahead + /// of all the listed families except for known ligature-based icon fonts. + pub(crate) fn prioritize_first_generic_or_prepend(&mut self, generic: GenericFontFamily) { + let mut index_of_first_generic = None; + let mut target_index = None; + + for (i, f) in self.iter().enumerate() { + match &*f { + SingleFontFamily::Generic(f) => { + if index_of_first_generic.is_none() && f.valid_for_user_font_prioritization() { + // If we haven't found a target position, there's nothing to do; + // this entry is already ahead of everything except any whitelisted + // icon fonts. + if target_index.is_none() { + return; + } + index_of_first_generic = Some(i); + break; + } + // A non-prioritized generic (e.g. cursive, fantasy) becomes the target + // position for prioritization, just like arbitrary named families. + if target_index.is_none() { + target_index = Some(i); + } + }, + SingleFontFamily::FamilyName(fam) => { + // Target position for the first generic is in front of the first + // non-whitelisted icon font family we find. + if target_index.is_none() && !fam.is_known_icon_font_family() { + target_index = Some(i); + } + }, + } + } + + let mut new_list = self.list.iter().cloned().collect::<Vec<_>>(); + let first_generic = match index_of_first_generic { + Some(i) => new_list.remove(i), + None => SingleFontFamily::Generic(generic), + }; + + if let Some(i) = target_index { + new_list.insert(i, first_generic); + } else { + new_list.push(first_generic); + } + self.list = crate::ArcSlice::from_iter(new_list.into_iter()); + } + + /// Returns whether we need to prioritize user fonts. + pub(crate) fn needs_user_font_prioritization(&self) -> bool { + self.iter().next().map_or(true, |f| match f { + SingleFontFamily::Generic(f) => !f.valid_for_user_font_prioritization(), + _ => true, + }) + } + + /// Return the generic ID if it is a single generic font + pub fn single_generic(&self) -> Option<GenericFontFamily> { + let mut iter = self.iter(); + if let Some(SingleFontFamily::Generic(f)) = iter.next() { + if iter.next().is_none() { + return Some(*f); + } + } + None + } +} + +/// Preserve the readability of text when font fallback occurs +pub type FontSizeAdjust = generics::GenericFontSizeAdjust<NonNegativeNumber>; + +impl FontSizeAdjust { + #[inline] + /// Default value of font-size-adjust + pub fn none() -> Self { + FontSizeAdjust::None + } +} + +/// Use FontSettings as computed type of FontFeatureSettings. +pub type FontFeatureSettings = FontSettings<FeatureTagValue<Integer>>; + +/// The computed value for font-variation-settings. +pub type FontVariationSettings = FontSettings<VariationValue<Number>>; + +// The computed value of font-{feature,variation}-settings discards values +// with duplicate tags, keeping only the last occurrence of each tag. +fn dedup_font_settings<T>(settings_list: &mut Vec<T>) +where + T: TaggedFontValue, +{ + if settings_list.len() > 1 { + settings_list.sort_by_key(|k| k.tag().0); + // dedup() keeps the first of any duplicates, but we want the last, + // so we implement it manually here. + let mut prev_tag = settings_list.last().unwrap().tag(); + for i in (0..settings_list.len() - 1).rev() { + let cur_tag = settings_list[i].tag(); + if cur_tag == prev_tag { + settings_list.remove(i); + } + prev_tag = cur_tag; + } + } +} + +impl<T> ToComputedValue for FontSettings<T> +where + T: ToComputedValue, + <T as ToComputedValue>::ComputedValue: TaggedFontValue, +{ + type ComputedValue = FontSettings<T::ComputedValue>; + + fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { + let mut v = self + .0 + .iter() + .map(|item| item.to_computed_value(context)) + .collect::<Vec<_>>(); + dedup_font_settings(&mut v); + FontSettings(v.into_boxed_slice()) + } + + fn from_computed_value(computed: &Self::ComputedValue) -> Self { + Self( + computed + .0 + .iter() + .map(T::from_computed_value) + .collect::<Vec<_>>() + .into_boxed_slice(), + ) + } +} + +/// font-language-override can only have a single 1-4 ASCII character +/// OpenType "language system" tag, so we should be able to compute +/// it and store it as a 32-bit integer +/// (see http://www.microsoft.com/typography/otspec/languagetags.htm). +#[derive( + Clone, + Copy, + Debug, + Eq, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToResolvedValue, + ToShmem, +)] +#[repr(C)] +#[value_info(other_values = "normal")] +pub struct FontLanguageOverride(pub u32); + +impl FontLanguageOverride { + #[inline] + /// Get computed default value of `font-language-override` with 0 + pub fn normal() -> FontLanguageOverride { + FontLanguageOverride(0) + } + + /// Returns this value as a `&str`, backed by `storage`. + #[inline] + pub(crate) fn to_str(self, storage: &mut [u8; 4]) -> &str { + *storage = u32::to_be_bytes(self.0); + // Safe because we ensure it's ASCII during parsing + let slice = if cfg!(debug_assertions) { + std::str::from_utf8(&storage[..]).unwrap() + } else { + unsafe { std::str::from_utf8_unchecked(&storage[..]) } + }; + slice.trim_end() + } + + /// Unsafe because `Self::to_str` requires the value to represent a UTF-8 + /// string. + #[inline] + pub unsafe fn from_u32(value: u32) -> Self { + Self(value) + } +} + +impl ToCss for FontLanguageOverride { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: fmt::Write, + { + if self.0 == 0 { + return dest.write_str("normal"); + } + self.to_str(&mut [0; 4]).to_css(dest) + } +} + +// FIXME(emilio): Make Gecko use the cbindgen'd fontLanguageOverride, then +// remove this. +#[cfg(feature = "gecko")] +impl From<u32> for FontLanguageOverride { + fn from(v: u32) -> Self { + unsafe { Self::from_u32(v) } + } +} + +#[cfg(feature = "gecko")] +impl From<FontLanguageOverride> for u32 { + fn from(v: FontLanguageOverride) -> u32 { + v.0 + } +} + +impl ToComputedValue for specified::MozScriptMinSize { + type ComputedValue = MozScriptMinSize; + + fn to_computed_value(&self, cx: &Context) -> MozScriptMinSize { + // this value is used in the computation of font-size, so + // we use the parent size + let base_size = FontBaseSize::InheritedStyle; + match self.0 { + NoCalcLength::FontRelative(value) => value.to_computed_value(cx, base_size), + NoCalcLength::ServoCharacterWidth(value) => { + value.to_computed_value(base_size.resolve(cx).computed_size()) + }, + ref l => l.to_computed_value(cx), + } + } + + fn from_computed_value(other: &MozScriptMinSize) -> Self { + specified::MozScriptMinSize(ToComputedValue::from_computed_value(other)) + } +} + +/// The computed value of the math-depth property. +pub type MathDepth = i8; + +#[cfg(feature = "gecko")] +impl ToComputedValue for specified::MathDepth { + type ComputedValue = MathDepth; + + fn to_computed_value(&self, cx: &Context) -> i8 { + use crate::properties::longhands::math_style::SpecifiedValue as MathStyleValue; + use std::{cmp, i8}; + + let int = match *self { + specified::MathDepth::AutoAdd => { + let parent = cx.builder.get_parent_font().clone_math_depth() as i32; + let style = cx.builder.get_parent_font().clone_math_style(); + if style == MathStyleValue::Compact { + parent.saturating_add(1) + } else { + parent + } + }, + specified::MathDepth::Add(rel) => { + let parent = cx.builder.get_parent_font().clone_math_depth(); + (parent as i32).saturating_add(rel.to_computed_value(cx)) + }, + specified::MathDepth::Absolute(abs) => abs.to_computed_value(cx), + }; + cmp::min(int, i8::MAX as i32) as i8 + } + + fn from_computed_value(other: &i8) -> Self { + let computed_value = *other as i32; + specified::MathDepth::Absolute(SpecifiedInteger::from_computed_value(&computed_value)) + } +} + +/// - Use a signed 8.8 fixed-point value (representable range -128.0..128) +/// +/// Values of <angle> below -90 or above 90 not permitted, so we use out of +/// range values to represent normal | oblique +pub const FONT_STYLE_FRACTION_BITS: u16 = 8; + +/// This is an alias which is useful mostly as a cbindgen / C++ inference +/// workaround. +pub type FontStyleFixedPoint = FixedPoint<i16, FONT_STYLE_FRACTION_BITS>; + +/// The computed value of `font-style`. +/// +/// - Define out of range values min value (-128.0) as meaning 'normal' +/// - Define max value (127.99609375) as 'italic' +/// - Other values represent 'oblique <angle>' +/// - Note that 'oblique 0deg' is distinct from 'normal' (should it be?) +/// +/// cbindgen:derive-lt +/// cbindgen:derive-lte +/// cbindgen:derive-gt +/// cbindgen:derive-gte +#[derive( + Clone, + ComputeSquaredDistance, + Copy, + Debug, + Hash, + MallocSizeOf, + PartialEq, + PartialOrd, + ToResolvedValue, +)] +#[repr(C)] +pub struct FontStyle(FontStyleFixedPoint); + +impl FontStyle { + /// The normal keyword. + pub const NORMAL: FontStyle = FontStyle(FontStyleFixedPoint { + value: 100 << FONT_STYLE_FRACTION_BITS, + }); + /// The italic keyword. + pub const ITALIC: FontStyle = FontStyle(FontStyleFixedPoint { + value: 101 << FONT_STYLE_FRACTION_BITS, + }); + + /// The default angle for `font-style: oblique`. + /// See also https://github.com/w3c/csswg-drafts/issues/2295 + pub const DEFAULT_OBLIQUE_DEGREES: i16 = 14; + + /// The `oblique` keyword with the default degrees. + pub const OBLIQUE: FontStyle = FontStyle(FontStyleFixedPoint { + value: Self::DEFAULT_OBLIQUE_DEGREES << FONT_STYLE_FRACTION_BITS, + }); + + /// The `normal` value. + #[inline] + pub fn normal() -> Self { + Self::NORMAL + } + + /// Returns the oblique angle for this style. + pub fn oblique(degrees: f32) -> Self { + Self(FixedPoint::from_float( + degrees + .max(specified::FONT_STYLE_OBLIQUE_MIN_ANGLE_DEGREES) + .min(specified::FONT_STYLE_OBLIQUE_MAX_ANGLE_DEGREES), + )) + } + + /// Returns the oblique angle for this style. + pub fn oblique_degrees(&self) -> f32 { + debug_assert_ne!(*self, Self::NORMAL); + debug_assert_ne!(*self, Self::ITALIC); + self.0.to_float() + } +} + +impl ToCss for FontStyle { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: fmt::Write, + { + if *self == Self::NORMAL { + return dest.write_str("normal"); + } + if *self == Self::ITALIC { + return dest.write_str("italic"); + } + if *self == Self::OBLIQUE { + return dest.write_str("oblique"); + } + dest.write_str("oblique ")?; + let angle = Angle::from_degrees(self.oblique_degrees()); + angle.to_css(dest)?; + Ok(()) + } +} + +impl ToAnimatedValue for FontStyle { + type AnimatedValue = generics::FontStyle<Angle>; + + #[inline] + fn to_animated_value(self) -> Self::AnimatedValue { + if self == Self::NORMAL { + // This allows us to animate between normal and oblique values. Per spec, + // https://drafts.csswg.org/css-fonts-4/#font-style-prop: + // Animation type: by computed value type; 'normal' animates as 'oblique 0deg' + return generics::FontStyle::Oblique(Angle::from_degrees(0.0)); + } + if self == Self::ITALIC { + return generics::FontStyle::Italic; + } + generics::FontStyle::Oblique(Angle::from_degrees(self.oblique_degrees())) + } + + #[inline] + fn from_animated_value(animated: Self::AnimatedValue) -> Self { + match animated { + generics::FontStyle::Normal => Self::NORMAL, + generics::FontStyle::Italic => Self::ITALIC, + generics::FontStyle::Oblique(ref angle) => { + if angle.degrees() == 0.0 { + // Reverse the conversion done in to_animated_value() + Self::NORMAL + } else { + Self::oblique(angle.degrees()) + } + }, + } + } +} + +/// font-stretch is a percentage relative to normal. +/// +/// We use an unsigned 10.6 fixed-point value (range 0.0 - 1023.984375) +/// +/// We arbitrarily limit here to 1000%. (If that becomes a problem, we could +/// reduce the number of fractional bits and increase the limit.) +pub const FONT_STRETCH_FRACTION_BITS: u16 = 6; + +/// This is an alias which is useful mostly as a cbindgen / C++ inference +/// workaround. +pub type FontStretchFixedPoint = FixedPoint<u16, FONT_STRETCH_FRACTION_BITS>; + +/// A value for the font-stretch property per: +/// +/// https://drafts.csswg.org/css-fonts-4/#propdef-font-stretch +/// +/// cbindgen:derive-lt +/// cbindgen:derive-lte +/// cbindgen:derive-gt +/// cbindgen:derive-gte +#[derive( + Clone, ComputeSquaredDistance, Copy, Debug, MallocSizeOf, PartialEq, PartialOrd, ToResolvedValue, +)] +#[repr(C)] +pub struct FontStretch(pub FontStretchFixedPoint); + +impl FontStretch { + /// The fraction bits, as an easy-to-access-constant. + pub const FRACTION_BITS: u16 = FONT_STRETCH_FRACTION_BITS; + /// 0.5 in our floating point representation. + pub const HALF: u16 = 1 << (Self::FRACTION_BITS - 1); + + /// The `ultra-condensed` keyword. + pub const ULTRA_CONDENSED: FontStretch = FontStretch(FontStretchFixedPoint { + value: 50 << Self::FRACTION_BITS, + }); + /// The `extra-condensed` keyword. + pub const EXTRA_CONDENSED: FontStretch = FontStretch(FontStretchFixedPoint { + value: (62 << Self::FRACTION_BITS) + Self::HALF, + }); + /// The `condensed` keyword. + pub const CONDENSED: FontStretch = FontStretch(FontStretchFixedPoint { + value: 75 << Self::FRACTION_BITS, + }); + /// The `semi-condensed` keyword. + pub const SEMI_CONDENSED: FontStretch = FontStretch(FontStretchFixedPoint { + value: (87 << Self::FRACTION_BITS) + Self::HALF, + }); + /// The `normal` keyword. + pub const NORMAL: FontStretch = FontStretch(FontStretchFixedPoint { + value: 100 << Self::FRACTION_BITS, + }); + /// The `semi-expanded` keyword. + pub const SEMI_EXPANDED: FontStretch = FontStretch(FontStretchFixedPoint { + value: (112 << Self::FRACTION_BITS) + Self::HALF, + }); + /// The `expanded` keyword. + pub const EXPANDED: FontStretch = FontStretch(FontStretchFixedPoint { + value: 125 << Self::FRACTION_BITS, + }); + /// The `extra-expanded` keyword. + pub const EXTRA_EXPANDED: FontStretch = FontStretch(FontStretchFixedPoint { + value: 150 << Self::FRACTION_BITS, + }); + /// The `ultra-expanded` keyword. + pub const ULTRA_EXPANDED: FontStretch = FontStretch(FontStretchFixedPoint { + value: 200 << Self::FRACTION_BITS, + }); + + /// 100% + pub fn hundred() -> Self { + Self::NORMAL + } + + /// Converts to a computed percentage. + #[inline] + pub fn to_percentage(&self) -> Percentage { + Percentage(self.0.to_float() / 100.0) + } + + /// Converts from a computed percentage value. + pub fn from_percentage(p: f32) -> Self { + Self(FixedPoint::from_float((p * 100.).max(0.0).min(1000.0))) + } + + /// Returns a relevant stretch value from a keyword. + /// https://drafts.csswg.org/css-fonts-4/#font-stretch-prop + pub fn from_keyword(kw: specified::FontStretchKeyword) -> Self { + use specified::FontStretchKeyword::*; + match kw { + UltraCondensed => Self::ULTRA_CONDENSED, + ExtraCondensed => Self::EXTRA_CONDENSED, + Condensed => Self::CONDENSED, + SemiCondensed => Self::SEMI_CONDENSED, + Normal => Self::NORMAL, + SemiExpanded => Self::SEMI_EXPANDED, + Expanded => Self::EXPANDED, + ExtraExpanded => Self::EXTRA_EXPANDED, + UltraExpanded => Self::ULTRA_EXPANDED, + } + } + + /// Returns the stretch keyword if we map to one of the relevant values. + pub fn as_keyword(&self) -> Option<specified::FontStretchKeyword> { + use specified::FontStretchKeyword::*; + // TODO: Can we use match here? + if *self == Self::ULTRA_CONDENSED { + return Some(UltraCondensed); + } + if *self == Self::EXTRA_CONDENSED { + return Some(ExtraCondensed); + } + if *self == Self::CONDENSED { + return Some(Condensed); + } + if *self == Self::SEMI_CONDENSED { + return Some(SemiCondensed); + } + if *self == Self::NORMAL { + return Some(Normal); + } + if *self == Self::SEMI_EXPANDED { + return Some(SemiExpanded); + } + if *self == Self::EXPANDED { + return Some(Expanded); + } + if *self == Self::EXTRA_EXPANDED { + return Some(ExtraExpanded); + } + if *self == Self::ULTRA_EXPANDED { + return Some(UltraExpanded); + } + None + } +} + +impl ToCss for FontStretch { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: fmt::Write, + { + self.to_percentage().to_css(dest) + } +} + +impl ToAnimatedValue for FontStretch { + type AnimatedValue = Percentage; + + #[inline] + fn to_animated_value(self) -> Self::AnimatedValue { + self.to_percentage() + } + + #[inline] + fn from_animated_value(animated: Self::AnimatedValue) -> Self { + Self::from_percentage(animated.0) + } +} diff --git a/servo/components/style/values/computed/image.rs b/servo/components/style/values/computed/image.rs new file mode 100644 index 0000000000..266caca845 --- /dev/null +++ b/servo/components/style/values/computed/image.rs @@ -0,0 +1,209 @@ +/* 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/. */ + +//! CSS handling for the computed value of +//! [`image`][image]s +//! +//! [image]: https://drafts.csswg.org/css-images/#image-values + +use crate::values::computed::percentage::Percentage; +use crate::values::computed::position::Position; +use crate::values::computed::url::ComputedImageUrl; +#[cfg(feature = "gecko")] +use crate::values::computed::NumberOrPercentage; +use crate::values::computed::{Angle, Color, Context}; +use crate::values::computed::{ + AngleOrPercentage, LengthPercentage, NonNegativeLength, NonNegativeLengthPercentage, + Resolution, ToComputedValue, +}; +use crate::values::generics::image::{self as generic, GradientCompatMode}; +use crate::values::specified::image as specified; +use crate::values::specified::position::{HorizontalPositionKeyword, VerticalPositionKeyword}; +use std::f32::consts::PI; +use std::fmt::{self, Write}; +use style_traits::{CssWriter, ToCss}; + +pub use specified::ImageRendering; + +/// Computed values for an image according to CSS-IMAGES. +/// <https://drafts.csswg.org/css-images/#image-values> +pub type Image = + generic::GenericImage<Gradient, MozImageRect, ComputedImageUrl, Color, Percentage, Resolution>; + +// Images should remain small, see https://github.com/servo/servo/pull/18430 +size_of_test!(Image, 16); + +/// Computed values for a CSS gradient. +/// <https://drafts.csswg.org/css-images/#gradients> +pub type Gradient = generic::GenericGradient< + LineDirection, + LengthPercentage, + NonNegativeLength, + NonNegativeLengthPercentage, + Position, + Angle, + AngleOrPercentage, + Color, +>; + +/// Computed values for CSS cross-fade +/// <https://drafts.csswg.org/css-images-4/#cross-fade-function> +pub type CrossFade = generic::CrossFade<Image, Color, Percentage>; + +/// A computed radial gradient ending shape. +pub type EndingShape = generic::GenericEndingShape<NonNegativeLength, NonNegativeLengthPercentage>; + +/// A computed gradient line direction. +#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToResolvedValue)] +#[repr(C, u8)] +pub enum LineDirection { + /// An angle. + Angle(Angle), + /// A horizontal direction. + Horizontal(HorizontalPositionKeyword), + /// A vertical direction. + Vertical(VerticalPositionKeyword), + /// A corner. + Corner(HorizontalPositionKeyword, VerticalPositionKeyword), +} + +/// The computed value for an `image-set()` image. +pub type ImageSet = generic::GenericImageSet<Image, Resolution>; + +impl ToComputedValue for specified::ImageSet { + type ComputedValue = ImageSet; + + fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { + let items = self.items.to_computed_value(context); + let dpr = context.device().device_pixel_ratio().get(); + + let mut supported_image = false; + let mut selected_index = std::usize::MAX; + let mut selected_resolution = items[0].resolution.dppx(); + + for (i, item) in items.iter().enumerate() { + // If the MIME type is not supported, we discard the ImageSetItem + if item.has_mime_type && !context.device().is_supported_mime_type(&item.mime_type) { + continue; + } + + let candidate_resolution = item.resolution.dppx(); + + // https://drafts.csswg.org/css-images-4/#image-set-notation: + // + // Make a UA-specific choice of which to load, based on whatever + // criteria deemed relevant (such as the resolution of the + // display, connection speed, etc). + // + // For now, select the lowest resolution greater than display + // density, otherwise the greatest resolution available + let better_candidate = || { + if selected_resolution < dpr && candidate_resolution > selected_resolution { + return true; + } + if candidate_resolution < selected_resolution && candidate_resolution >= dpr { + return true; + } + false + }; + + // The first item with a supported MIME type is obviously the current best candidate + if !supported_image || better_candidate() { + supported_image = true; + selected_index = i; + selected_resolution = candidate_resolution; + } + } + + ImageSet { + selected_index, + items, + } + } + + fn from_computed_value(computed: &Self::ComputedValue) -> Self { + Self { + selected_index: std::usize::MAX, + items: ToComputedValue::from_computed_value(&computed.items), + } + } +} + +/// Computed values for `-moz-image-rect(...)`. +#[cfg(feature = "gecko")] +pub type MozImageRect = generic::GenericMozImageRect<NumberOrPercentage, ComputedImageUrl>; + +/// Empty enum on non-gecko +#[cfg(not(feature = "gecko"))] +pub type MozImageRect = specified::MozImageRect; + +impl generic::LineDirection for LineDirection { + fn points_downwards(&self, compat_mode: GradientCompatMode) -> bool { + match *self { + LineDirection::Angle(angle) => angle.radians() == PI, + LineDirection::Vertical(VerticalPositionKeyword::Bottom) => { + compat_mode == GradientCompatMode::Modern + }, + LineDirection::Vertical(VerticalPositionKeyword::Top) => { + compat_mode != GradientCompatMode::Modern + }, + _ => false, + } + } + + fn to_css<W>(&self, dest: &mut CssWriter<W>, compat_mode: GradientCompatMode) -> fmt::Result + where + W: Write, + { + match *self { + LineDirection::Angle(ref angle) => angle.to_css(dest), + LineDirection::Horizontal(x) => { + if compat_mode == GradientCompatMode::Modern { + dest.write_str("to ")?; + } + x.to_css(dest) + }, + LineDirection::Vertical(y) => { + if compat_mode == GradientCompatMode::Modern { + dest.write_str("to ")?; + } + y.to_css(dest) + }, + LineDirection::Corner(x, y) => { + if compat_mode == GradientCompatMode::Modern { + dest.write_str("to ")?; + } + x.to_css(dest)?; + dest.write_char(' ')?; + y.to_css(dest) + }, + } + } +} + +impl ToComputedValue for specified::LineDirection { + type ComputedValue = LineDirection; + + fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { + match *self { + specified::LineDirection::Angle(ref angle) => { + LineDirection::Angle(angle.to_computed_value(context)) + }, + specified::LineDirection::Horizontal(x) => LineDirection::Horizontal(x), + specified::LineDirection::Vertical(y) => LineDirection::Vertical(y), + specified::LineDirection::Corner(x, y) => LineDirection::Corner(x, y), + } + } + + fn from_computed_value(computed: &Self::ComputedValue) -> Self { + match *computed { + LineDirection::Angle(ref angle) => { + specified::LineDirection::Angle(ToComputedValue::from_computed_value(angle)) + }, + LineDirection::Horizontal(x) => specified::LineDirection::Horizontal(x), + LineDirection::Vertical(y) => specified::LineDirection::Vertical(y), + LineDirection::Corner(x, y) => specified::LineDirection::Corner(x, y), + } + } +} diff --git a/servo/components/style/values/computed/length.rs b/servo/components/style/values/computed/length.rs new file mode 100644 index 0000000000..2370bdd149 --- /dev/null +++ b/servo/components/style/values/computed/length.rs @@ -0,0 +1,515 @@ +/* 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/. */ + +//! `<length>` computed values, and related ones. + +use super::{Context, Number, ToComputedValue}; +use crate::values::animated::ToAnimatedValue; +use crate::values::computed::NonNegativeNumber; +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::{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, MulAssign, Neg, Sub}; +use style_traits::{CSSPixel, CssWriter, ToCss}; + +pub use super::image::Image; +pub use super::length_percentage::{LengthPercentage, NonNegativeLengthPercentage}; +pub use crate::values::specified::url::UrlOrNone; +pub use crate::values::specified::{Angle, BorderStyle, Time}; + +impl ToComputedValue for specified::NoCalcLength { + type ComputedValue = Length; + + #[inline] + fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { + self.to_computed_value_with_base_size(context, FontBaseSize::CurrentStyle) + } + + #[inline] + fn from_computed_value(computed: &Self::ComputedValue) -> Self { + Self::Absolute(AbsoluteLength::Px(computed.px())) + } +} + +impl specified::NoCalcLength { + /// Computes a length with a given font-relative base size. + pub fn to_computed_value_with_base_size( + &self, + context: &Context, + base_size: FontBaseSize, + ) -> Length { + match *self { + Self::Absolute(length) => length.to_computed_value(context), + Self::FontRelative(length) => length.to_computed_value(context, base_size), + Self::ViewportPercentage(length) => length.to_computed_value(context), + Self::ContainerRelative(length) => length.to_computed_value(context), + Self::ServoCharacterWidth(length) => length + .to_computed_value(context.style().get_font().clone_font_size().computed_size()), + } + } +} + +impl ToComputedValue for specified::Length { + type ComputedValue = Length; + + #[inline] + fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { + match *self { + Self::NoCalc(l) => l.to_computed_value(context), + Self::Calc(ref calc) => calc.to_computed_value(context).to_length().unwrap(), + } + } + + #[inline] + fn from_computed_value(computed: &Self::ComputedValue) -> Self { + Self::NoCalc(specified::NoCalcLength::from_computed_value(computed)) + } +} + +/// Some boilerplate to share between negative and non-negative +/// length-percentage or auto. +macro_rules! computed_length_percentage_or_auto { + ($inner:ty) => { + /// Returns the used value. + #[inline] + pub fn to_used_value(&self, percentage_basis: Au) -> Option<Au> { + match *self { + Self::Auto => None, + Self::LengthPercentage(ref lp) => Some(lp.to_used_value(percentage_basis)), + } + } + + /// Returns true if the computed value is absolute 0 or 0%. + #[inline] + pub fn is_definitely_zero(&self) -> bool { + use crate::values::generics::length::LengthPercentageOrAuto::*; + match *self { + LengthPercentage(ref l) => l.is_definitely_zero(), + Auto => false, + } + } + }; +} + +/// A computed type for `<length-percentage> | auto`. +pub type LengthPercentageOrAuto = generics::GenericLengthPercentageOrAuto<LengthPercentage>; + +impl LengthPercentageOrAuto { + /// Clamps the value to a non-negative value. + pub fn clamp_to_non_negative(self) -> Self { + use crate::values::generics::length::LengthPercentageOrAuto::*; + match self { + LengthPercentage(l) => LengthPercentage(l.clamp_to_non_negative()), + Auto => Auto, + } + } + + /// Convert to have a borrow inside the enum + pub fn as_ref(&self) -> generics::GenericLengthPercentageOrAuto<&LengthPercentage> { + use crate::values::generics::length::LengthPercentageOrAuto::*; + match *self { + LengthPercentage(ref lp) => LengthPercentage(lp), + Auto => Auto, + } + } + + computed_length_percentage_or_auto!(LengthPercentage); +} + +impl generics::GenericLengthPercentageOrAuto<&LengthPercentage> { + /// Resolves the percentage. + #[inline] + pub fn percentage_relative_to(&self, basis: Length) -> LengthOrAuto { + use crate::values::generics::length::LengthPercentageOrAuto::*; + match self { + LengthPercentage(length_percentage) => { + LengthPercentage(length_percentage.percentage_relative_to(basis)) + }, + Auto => Auto, + } + } + + /// Maybe resolves the percentage. + #[inline] + pub fn maybe_percentage_relative_to(&self, basis: Option<Length>) -> LengthOrAuto { + use crate::values::generics::length::LengthPercentageOrAuto::*; + match self { + LengthPercentage(length_percentage) => length_percentage + .maybe_percentage_relative_to(basis) + .map_or(Auto, LengthPercentage), + Auto => Auto, + } + } +} + +/// A wrapper of LengthPercentageOrAuto, whose value must be >= 0. +pub type NonNegativeLengthPercentageOrAuto = + generics::GenericLengthPercentageOrAuto<NonNegativeLengthPercentage>; + +impl NonNegativeLengthPercentageOrAuto { + computed_length_percentage_or_auto!(NonNegativeLengthPercentage); +} + +#[cfg(feature = "servo")] +impl MaxSize { + /// Convert the computed value into used value. + #[inline] + pub fn to_used_value(&self, percentage_basis: Au) -> Option<Au> { + match *self { + GenericMaxSize::None => None, + GenericMaxSize::LengthPercentage(ref lp) => Some(lp.to_used_value(percentage_basis)), + } + } +} + +impl Size { + /// Convert the computed value into used value. + #[inline] + #[cfg(feature = "servo")] + pub fn to_used_value(&self, percentage_basis: Au) -> Option<Au> { + match *self { + GenericSize::Auto => None, + GenericSize::LengthPercentage(ref lp) => Some(lp.to_used_value(percentage_basis)), + } + } + + /// Returns true if the computed value is absolute 0 or 0%. + #[inline] + pub fn is_definitely_zero(&self) -> bool { + match *self { + Self::Auto => false, + Self::LengthPercentage(ref lp) => lp.is_definitely_zero(), + #[cfg(feature = "gecko")] + Self::MinContent | + Self::MaxContent | + Self::FitContent | + Self::MozAvailable | + Self::FitContentFunction(_) => false, + } + } +} + +/// The computed `<length>` value. +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Deserialize, + MallocSizeOf, + PartialEq, + PartialOrd, + Serialize, + ToAnimatedValue, + ToAnimatedZero, + ToComputedValue, + ToResolvedValue, + ToShmem, +)] +#[repr(C)] +pub struct CSSPixelLength(CSSFloat); + +impl fmt::Debug for CSSPixelLength { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(f)?; + f.write_str(" px") + } +} + +impl CSSPixelLength { + /// Return a new CSSPixelLength. + #[inline] + pub fn new(px: CSSFloat) -> Self { + CSSPixelLength(px) + } + + /// Returns a normalized (NaN turned to zero) version of this length. + #[inline] + pub fn normalized(self) -> Self { + Self::new(crate::values::normalize(self.0)) + } + + /// Returns a finite (normalized and clamped to float min and max) version of this length. + #[inline] + pub fn finite(self) -> Self { + Self::new(crate::values::normalize(self.0).min(f32::MAX).max(f32::MIN)) + } + + /// Scale the length by a given amount. + #[inline] + pub fn scale_by(self, scale: CSSFloat) -> Self { + CSSPixelLength(self.0 * scale) + } + + /// Return the containing pixel value. + #[inline] + pub fn px(self) -> CSSFloat { + self.0 + } + + /// Return the length with app_unit i32 type. + #[inline] + pub fn to_i32_au(self) -> i32 { + Au::from(self).0 + } + + /// Return the absolute value of this length. + #[inline] + pub fn abs(self) -> Self { + CSSPixelLength::new(self.0.abs()) + } + + /// Return the clamped value of this length. + #[inline] + pub fn clamp_to_non_negative(self) -> Self { + CSSPixelLength::new(self.0.max(0.)) + } + + /// Returns the minimum between `self` and `other`. + #[inline] + pub fn min(self, other: Self) -> Self { + CSSPixelLength::new(self.0.min(other.0)) + } + + /// Returns the maximum between `self` and `other`. + #[inline] + pub fn max(self, other: Self) -> Self { + CSSPixelLength::new(self.0.max(other.0)) + } + + /// Sets `self` to the maximum between `self` and `other`. + #[inline] + pub fn max_assign(&mut self, other: Self) { + *self = self.max(other); + } + + /// Clamp the value to a lower bound and an optional upper bound. + /// + /// Can be used for example with `min-width` and `max-width`. + #[inline] + pub fn clamp_between_extremums(self, min_size: Self, max_size: Option<Self>) -> Self { + self.clamp_below_max(max_size).max(min_size) + } + + /// Clamp the value to an optional upper bound. + /// + /// Can be used for example with `max-width`. + #[inline] + pub fn clamp_below_max(self, max_size: Option<Self>) -> Self { + match max_size { + None => self, + Some(max_size) => self.min(max_size), + } + } +} + +impl num_traits::Zero for CSSPixelLength { + fn zero() -> Self { + CSSPixelLength::new(0.) + } + + fn is_zero(&self) -> bool { + self.px() == 0. + } +} + +impl ToCss for CSSPixelLength { + #[inline] + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + self.0.to_css(dest)?; + dest.write_str("px") + } +} + +impl std::iter::Sum for CSSPixelLength { + fn sum<I: Iterator<Item = Self>>(iter: I) -> Self { + iter.fold(Length::zero(), Add::add) + } +} + +impl Add for CSSPixelLength { + type Output = Self; + + #[inline] + fn add(self, other: Self) -> Self { + Self::new(self.px() + other.px()) + } +} + +impl AddAssign for CSSPixelLength { + #[inline] + fn add_assign(&mut self, other: Self) { + self.0 += other.0; + } +} + +impl Div for CSSPixelLength { + type Output = CSSFloat; + + #[inline] + fn div(self, other: Self) -> CSSFloat { + self.px() / other.px() + } +} + +impl Div<CSSFloat> for CSSPixelLength { + type Output = Self; + + #[inline] + fn div(self, other: CSSFloat) -> Self { + Self::new(self.px() / other) + } +} + +impl MulAssign<CSSFloat> for CSSPixelLength { + #[inline] + fn mul_assign(&mut self, other: CSSFloat) { + self.0 *= other; + } +} + +impl Mul<CSSFloat> for CSSPixelLength { + type Output = Self; + + #[inline] + fn mul(self, other: CSSFloat) -> Self { + Self::new(self.px() * other) + } +} + +impl Neg for CSSPixelLength { + type Output = Self; + + #[inline] + fn neg(self) -> Self { + CSSPixelLength::new(-self.0) + } +} + +impl Sub for CSSPixelLength { + type Output = Self; + + #[inline] + fn sub(self, other: Self) -> Self { + Self::new(self.px() - other.px()) + } +} + +impl From<CSSPixelLength> for Au { + #[inline] + fn from(len: CSSPixelLength) -> Self { + Au::from_f32_px(len.0) + } +} + +impl From<Au> for CSSPixelLength { + #[inline] + fn from(len: Au) -> Self { + CSSPixelLength::new(len.to_f32_px()) + } +} + +impl From<CSSPixelLength> for euclid::Length<CSSFloat, CSSPixel> { + #[inline] + fn from(length: CSSPixelLength) -> Self { + Self::new(length.0) + } +} + +/// An alias of computed `<length>` value. +pub type Length = CSSPixelLength; + +/// Either a computed `<length>` or the `auto` keyword. +pub type LengthOrAuto = generics::GenericLengthPercentageOrAuto<Length>; + +/// Either a non-negative `<length>` or the `auto` keyword. +pub type NonNegativeLengthOrAuto = generics::GenericLengthPercentageOrAuto<NonNegativeLength>; + +/// Either a computed `<length>` or a `<number>` value. +pub type LengthOrNumber = GenericLengthOrNumber<Length, Number>; + +/// A wrapper of Length, whose value must be >= 0. +pub type NonNegativeLength = NonNegative<Length>; + +impl ToAnimatedValue for NonNegativeLength { + type AnimatedValue = Length; + + #[inline] + fn to_animated_value(self) -> Self::AnimatedValue { + self.0 + } + + #[inline] + fn from_animated_value(animated: Self::AnimatedValue) -> Self { + NonNegativeLength::new(animated.px().max(0.)) + } +} + +impl NonNegativeLength { + /// Create a NonNegativeLength. + #[inline] + pub fn new(px: CSSFloat) -> Self { + NonNegative(Length::new(px.max(0.))) + } + + /// Return the pixel value of |NonNegativeLength|. + #[inline] + pub fn px(&self) -> CSSFloat { + self.0.px() + } + + #[inline] + /// Ensures it is non negative + pub fn clamp(self) -> Self { + if (self.0).0 < 0. { + Self::zero() + } else { + self + } + } +} + +impl From<Length> for NonNegativeLength { + #[inline] + fn from(len: Length) -> Self { + NonNegative(len) + } +} + +impl From<Au> for NonNegativeLength { + #[inline] + fn from(au: Au) -> Self { + NonNegative(au.into()) + } +} + +impl From<NonNegativeLength> for Au { + #[inline] + fn from(non_negative_len: NonNegativeLength) -> Self { + Au::from(non_negative_len.0) + } +} + +/// Either a computed NonNegativeLengthPercentage or the `normal` keyword. +pub type NonNegativeLengthPercentageOrNormal = + GenericLengthPercentageOrNormal<NonNegativeLengthPercentage>; + +/// Either a non-negative `<length>` or a `<number>`. +pub type NonNegativeLengthOrNumber = GenericLengthOrNumber<NonNegativeLength, NonNegativeNumber>; + +/// A computed value for `min-width`, `min-height`, `width` or `height` property. +pub type Size = GenericSize<NonNegativeLengthPercentage>; + +/// A computed value for `max-width` or `min-height` property. +pub type MaxSize = GenericMaxSize<NonNegativeLengthPercentage>; 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 0000000000..a8f5868b99 --- /dev/null +++ b/servo/components/style/values/computed/length_percentage.rs @@ -0,0 +1,899 @@ +/* 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/. */ + +//! `<length-percentage>` computed values, and related ones. +//! +//! The over-all design is a tagged pointer, with the lower bits of the pointer +//! being non-zero if it is a non-calc value. +//! +//! It is expected to take 64 bits both in x86 and x86-64. This is implemented +//! as a `union`, with 4 different variants: +//! +//! * The length and percentage variants have a { tag, f32 } (effectively) +//! layout. The tag has to overlap with the lower 2 bits of the calc variant. +//! +//! * The `calc()` variant is a { tag, pointer } in x86 (so same as the +//! others), or just a { pointer } in x86-64 (so that the two bits of the tag +//! can be obtained from the lower bits of the pointer). +//! +//! * There's a `tag` variant just to make clear when only the tag is intended +//! to be read. Note that the tag needs to be masked always by `TAG_MASK`, to +//! deal with the pointer variant in x86-64. +//! +//! The assertions in the constructor methods ensure that the tag getter matches +//! our expectations. + +use super::{Context, Length, Percentage, ToComputedValue}; +use crate::values::animated::{Animate, Procedure, ToAnimatedValue, ToAnimatedZero}; +use crate::values::distance::{ComputeSquaredDistance, SquaredDistance}; +use crate::values::generics::{calc, NonNegative}; +use crate::values::specified::length::FontBaseSize; +use crate::values::{specified, CSSFloat}; +use crate::{Zero, ZeroNoPercent}; +use app_units::Au; +use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; +use serde::{Deserialize, Serialize}; +use std::borrow::Cow; +use std::fmt::{self, Write}; +use style_traits::values::specified::AllowedNumericType; +use style_traits::{CssWriter, ToCss}; + +#[doc(hidden)] +#[derive(Clone, Copy)] +#[repr(C)] +pub struct LengthVariant { + tag: u8, + length: Length, +} + +#[doc(hidden)] +#[derive(Clone, Copy)] +#[repr(C)] +pub struct PercentageVariant { + tag: u8, + percentage: Percentage, +} + +// NOTE(emilio): cbindgen only understands the #[cfg] on the top level +// definition. +#[doc(hidden)] +#[derive(Clone, Copy)] +#[repr(C)] +#[cfg(target_pointer_width = "32")] +pub struct CalcVariant { + tag: u8, + ptr: *mut CalcLengthPercentage, +} + +#[doc(hidden)] +#[derive(Clone, Copy)] +#[repr(C)] +#[cfg(target_pointer_width = "64")] +pub struct CalcVariant { + ptr: usize, // In little-endian byte order +} + +// `CalcLengthPercentage` is `Send + Sync` as asserted below. +unsafe impl Send for CalcVariant {} +unsafe impl Sync for CalcVariant {} + +#[doc(hidden)] +#[derive(Clone, Copy)] +#[repr(C)] +pub struct TagVariant { + tag: u8, +} + +/// A `<length-percentage>` value. This can be either a `<length>`, a +/// `<percentage>`, 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 +/// +/// The tag is stored in the lower two bits. +/// +/// We need to use a struct instead of the union directly because unions with +/// Drop implementations are unstable, looks like. +/// +/// Also we need the union and the variants to be `pub` (even though the member +/// is private) so that cbindgen generates it. They're not part of the public +/// API otherwise. +#[repr(transparent)] +pub struct LengthPercentage(LengthPercentageUnion); + +#[doc(hidden)] +#[repr(C)] +pub union LengthPercentageUnion { + length: LengthVariant, + percentage: PercentageVariant, + calc: CalcVariant, + tag: TagVariant, +} + +impl LengthPercentageUnion { + #[doc(hidden)] // Need to be public so that cbindgen generates it. + pub const TAG_CALC: u8 = 0; + #[doc(hidden)] + pub const TAG_LENGTH: u8 = 1; + #[doc(hidden)] + pub const TAG_PERCENTAGE: u8 = 2; + #[doc(hidden)] + pub const TAG_MASK: u8 = 0b11; +} + +#[derive(Clone, Copy, Debug, PartialEq)] +#[repr(u8)] +enum Tag { + Calc = LengthPercentageUnion::TAG_CALC, + Length = LengthPercentageUnion::TAG_LENGTH, + Percentage = LengthPercentageUnion::TAG_PERCENTAGE, +} + +// All the members should be 64 bits, even in 32-bit builds. +#[allow(unused)] +unsafe fn static_assert() { + fn assert_send_and_sync<T: Send + Sync>() {} + std::mem::transmute::<u64, LengthVariant>(0u64); + std::mem::transmute::<u64, PercentageVariant>(0u64); + std::mem::transmute::<u64, CalcVariant>(0u64); + std::mem::transmute::<u64, LengthPercentage>(0u64); + assert_send_and_sync::<LengthVariant>(); + assert_send_and_sync::<PercentageVariant>(); + assert_send_and_sync::<CalcLengthPercentage>(); +} + +impl Drop for LengthPercentage { + fn drop(&mut self) { + if self.tag() == Tag::Calc { + let _ = unsafe { Box::from_raw(self.calc_ptr()) }; + } + } +} + +impl MallocSizeOf for LengthPercentage { + fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + match self.unpack() { + Unpacked::Length(..) | Unpacked::Percentage(..) => 0, + Unpacked::Calc(c) => unsafe { ops.malloc_size_of(c) }, + } + } +} + +/// An unpacked `<length-percentage>` that borrows the `calc()` variant. +#[derive(Clone, Debug, PartialEq, ToCss)] +enum Unpacked<'a> { + Calc(&'a CalcLengthPercentage), + Length(Length), + Percentage(Percentage), +} + +/// An unpacked `<length-percentage>` that mutably borrows the `calc()` variant. +enum UnpackedMut<'a> { + Calc(&'a mut CalcLengthPercentage), + Length(Length), + Percentage(Percentage), +} + +/// An unpacked `<length-percentage>` that owns the `calc()` variant, for +/// serialization purposes. +#[derive(Deserialize, PartialEq, Serialize)] +enum Serializable { + Calc(CalcLengthPercentage), + Length(Length), + Percentage(Percentage), +} + +impl LengthPercentage { + /// 1px length value for SVG defaults + #[inline] + pub fn one() -> Self { + Self::new_length(Length::new(1.)) + } + + /// 0% + #[inline] + pub fn zero_percent() -> Self { + Self::new_percent(Percentage::zero()) + } + + fn to_calc_node(&self) -> Cow<CalcNode> { + match self.unpack() { + Unpacked::Length(l) => Cow::Owned(CalcNode::Leaf(CalcLengthPercentageLeaf::Length(l))), + Unpacked::Percentage(p) => { + Cow::Owned(CalcNode::Leaf(CalcLengthPercentageLeaf::Percentage(p))) + }, + Unpacked::Calc(p) => Cow::Borrowed(&p.node), + } + } + + /// Constructs a length value. + #[inline] + pub fn new_length(length: Length) -> Self { + let length = Self(LengthPercentageUnion { + length: LengthVariant { + tag: LengthPercentageUnion::TAG_LENGTH, + length, + }, + }); + debug_assert_eq!(length.tag(), Tag::Length); + length + } + + /// Constructs a percentage value. + #[inline] + pub fn new_percent(percentage: Percentage) -> Self { + let percent = Self(LengthPercentageUnion { + percentage: PercentageVariant { + tag: LengthPercentageUnion::TAG_PERCENTAGE, + percentage, + }, + }); + debug_assert_eq!(percent.tag(), Tag::Percentage); + percent + } + + /// Given a `LengthPercentage` value `v`, construct the value representing + /// `calc(100% - v)`. + pub fn hundred_percent_minus(v: Self, clamping_mode: AllowedNumericType) -> Self { + // TODO: This could in theory take ownership of the calc node in `v` if + // possible instead of cloning. + let mut node = v.to_calc_node().into_owned(); + node.map(std::ops::Neg::neg); + + let new_node = CalcNode::Sum( + vec![ + CalcNode::Leaf(CalcLengthPercentageLeaf::Percentage(Percentage::hundred())), + node, + ] + .into(), + ); + + Self::new_calc(new_node, clamping_mode) + } + + /// Constructs a `calc()` value. + #[inline] + pub fn new_calc(mut node: CalcNode, clamping_mode: AllowedNumericType) -> Self { + node.simplify_and_sort(); + + match node { + CalcNode::Leaf(l) => { + return match l { + CalcLengthPercentageLeaf::Length(l) => { + Self::new_length(Length::new(clamping_mode.clamp(l.px())).normalized()) + }, + CalcLengthPercentageLeaf::Percentage(p) => Self::new_percent(Percentage( + clamping_mode.clamp(crate::values::normalize(p.0)), + )), + } + }, + _ => Self::new_calc_unchecked(Box::new(CalcLengthPercentage { + clamping_mode, + node, + })), + } + } + + /// Private version of new_calc() that constructs a calc() variant without + /// checking. + fn new_calc_unchecked(calc: Box<CalcLengthPercentage>) -> Self { + let ptr = Box::into_raw(calc); + + #[cfg(target_pointer_width = "32")] + let calc = CalcVariant { + tag: LengthPercentageUnion::TAG_CALC, + ptr, + }; + + #[cfg(target_pointer_width = "64")] + let calc = CalcVariant { + #[cfg(target_endian = "little")] + ptr: ptr as usize, + #[cfg(target_endian = "big")] + ptr: (ptr as usize).swap_bytes(), + }; + + let calc = Self(LengthPercentageUnion { calc }); + debug_assert_eq!(calc.tag(), Tag::Calc); + calc + } + + #[inline] + fn tag(&self) -> Tag { + match unsafe { self.0.tag.tag & LengthPercentageUnion::TAG_MASK } { + LengthPercentageUnion::TAG_CALC => Tag::Calc, + LengthPercentageUnion::TAG_LENGTH => Tag::Length, + LengthPercentageUnion::TAG_PERCENTAGE => Tag::Percentage, + _ => unsafe { debug_unreachable!("Bogus tag?") }, + } + } + + #[inline] + fn unpack_mut<'a>(&'a mut self) -> UnpackedMut<'a> { + unsafe { + match self.tag() { + Tag::Calc => UnpackedMut::Calc(&mut *self.calc_ptr()), + Tag::Length => UnpackedMut::Length(self.0.length.length), + Tag::Percentage => UnpackedMut::Percentage(self.0.percentage.percentage), + } + } + } + + #[inline] + fn unpack<'a>(&'a self) -> Unpacked<'a> { + unsafe { + match self.tag() { + Tag::Calc => Unpacked::Calc(&*self.calc_ptr()), + Tag::Length => Unpacked::Length(self.0.length.length), + Tag::Percentage => Unpacked::Percentage(self.0.percentage.percentage), + } + } + } + + #[inline] + unsafe fn calc_ptr(&self) -> *mut CalcLengthPercentage { + #[cfg(not(all(target_endian = "big", target_pointer_width = "64")))] + { + self.0.calc.ptr as *mut _ + } + #[cfg(all(target_endian = "big", target_pointer_width = "64"))] + { + self.0.calc.ptr.swap_bytes() as *mut _ + } + } + + #[inline] + fn to_serializable(&self) -> Serializable { + match self.unpack() { + Unpacked::Calc(c) => Serializable::Calc(c.clone()), + Unpacked::Length(l) => Serializable::Length(l), + Unpacked::Percentage(p) => Serializable::Percentage(p), + } + } + + #[inline] + fn from_serializable(s: Serializable) -> Self { + match s { + Serializable::Calc(c) => Self::new_calc_unchecked(Box::new(c)), + Serializable::Length(l) => Self::new_length(l), + Serializable::Percentage(p) => Self::new_percent(p), + } + } + + /// Returns true if the computed value is absolute 0 or 0%. + #[inline] + pub fn is_definitely_zero(&self) -> bool { + match self.unpack() { + Unpacked::Length(l) => l.px() == 0.0, + Unpacked::Percentage(p) => p.0 == 0.0, + Unpacked::Calc(..) => false, + } + } + + /// Resolves the percentage. + #[inline] + pub fn resolve(&self, basis: Length) -> Length { + match self.unpack() { + Unpacked::Length(l) => l, + Unpacked::Percentage(p) => (basis * p.0).normalized(), + Unpacked::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.unpack() { + Unpacked::Length(..) => false, + Unpacked::Percentage(..) | Unpacked::Calc(..) => true, + } + } + + /// Converts to a `<length>` if possible. + pub fn to_length(&self) -> Option<Length> { + match self.unpack() { + Unpacked::Length(l) => Some(l), + Unpacked::Percentage(..) | Unpacked::Calc(..) => { + debug_assert!(self.has_percentage()); + return None; + }, + } + } + + /// Converts to a `<percentage>` if possible. + #[inline] + pub fn to_percentage(&self) -> Option<Percentage> { + match self.unpack() { + Unpacked::Percentage(p) => Some(p), + Unpacked::Length(..) | Unpacked::Calc(..) => None, + } + } + + /// 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] + pub fn maybe_to_used_value(&self, container_len: Option<Length>) -> Option<Au> { + 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<Length>) -> Option<Length> { + if let Unpacked::Length(l) = self.unpack() { + return Some(l); + } + Some(self.resolve(container_len?)) + } + + /// Returns the clamped non-negative values. + #[inline] + pub fn clamp_to_non_negative(mut self) -> Self { + match self.unpack_mut() { + UnpackedMut::Length(l) => Self::new_length(l.clamp_to_non_negative()), + UnpackedMut::Percentage(p) => Self::new_percent(p.clamp_to_non_negative()), + UnpackedMut::Calc(ref mut c) => { + c.clamping_mode = AllowedNumericType::NonNegative; + self + }, + } + } +} + +impl PartialEq for LengthPercentage { + fn eq(&self, other: &Self) -> bool { + self.unpack() == other.unpack() + } +} + +impl fmt::Debug for LengthPercentage { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + self.unpack().fmt(formatter) + } +} + +impl ToAnimatedZero for LengthPercentage { + fn to_animated_zero(&self) -> Result<Self, ()> { + Ok(match self.unpack() { + Unpacked::Length(l) => Self::new_length(l.to_animated_zero()?), + Unpacked::Percentage(p) => Self::new_percent(p.to_animated_zero()?), + Unpacked::Calc(c) => Self::new_calc_unchecked(Box::new(c.to_animated_zero()?)), + }) + } +} + +impl Clone for LengthPercentage { + fn clone(&self) -> Self { + match self.unpack() { + Unpacked::Length(l) => Self::new_length(l), + Unpacked::Percentage(p) => Self::new_percent(p), + Unpacked::Calc(c) => Self::new_calc_unchecked(Box::new(c.clone())), + } + } +} + +impl ToComputedValue for specified::LengthPercentage { + type ComputedValue = LengthPercentage; + + fn to_computed_value(&self, context: &Context) -> LengthPercentage { + match *self { + specified::LengthPercentage::Length(ref value) => { + LengthPercentage::new_length(value.to_computed_value(context)) + }, + specified::LengthPercentage::Percentage(value) => LengthPercentage::new_percent(value), + specified::LengthPercentage::Calc(ref calc) => (**calc).to_computed_value(context), + } + } + + fn from_computed_value(computed: &LengthPercentage) -> Self { + match computed.unpack() { + Unpacked::Length(ref l) => { + specified::LengthPercentage::Length(ToComputedValue::from_computed_value(l)) + }, + Unpacked::Percentage(p) => specified::LengthPercentage::Percentage(p), + Unpacked::Calc(c) => { + // We simplify before constructing the LengthPercentage if + // needed, so this is always fine. + specified::LengthPercentage::Calc(Box::new( + specified::CalcLengthPercentage::from_computed_value(c), + )) + }, + } + } +} + +impl ComputeSquaredDistance for LengthPercentage { + #[inline] + fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { + // A somewhat arbitrary base, it doesn't really make sense to mix + // lengths with percentages, but we can't do much better here, and this + // ensures that the distance between length-only and percentage-only + // lengths makes sense. + let basis = Length::new(100.); + self.resolve(basis) + .compute_squared_distance(&other.resolve(basis)) + } +} + +impl ToCss for LengthPercentage { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + self.unpack().to_css(dest) + } +} + +impl Zero for LengthPercentage { + fn zero() -> Self { + LengthPercentage::new_length(Length::zero()) + } + + #[inline] + fn is_zero(&self) -> bool { + self.is_definitely_zero() + } +} + +impl ZeroNoPercent for LengthPercentage { + #[inline] + fn is_zero_no_percent(&self) -> bool { + self.is_definitely_zero() && !self.has_percentage() + } +} + +impl Serialize for LengthPercentage { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: serde::Serializer, + { + self.to_serializable().serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for LengthPercentage { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: serde::Deserializer<'de>, + { + Ok(Self::from_serializable(Serializable::deserialize( + deserializer, + )?)) + } +} + +/// The leaves of a `<length-percentage>` calc expression. +#[derive( + Clone, + Debug, + Deserialize, + MallocSizeOf, + PartialEq, + Serialize, + ToAnimatedZero, + ToCss, + ToResolvedValue, +)] +#[allow(missing_docs)] +#[repr(u8)] +pub enum CalcLengthPercentageLeaf { + Length(Length), + Percentage(Percentage), +} + +impl CalcLengthPercentageLeaf { + fn is_zero_length(&self) -> bool { + match *self { + Self::Length(ref l) => l.is_zero(), + Self::Percentage(..) => false, + } + } +} + +impl PartialOrd for CalcLengthPercentageLeaf { + fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { + use self::CalcLengthPercentageLeaf::*; + // NOTE: Percentages can't be compared reasonably here because the + // percentage basis might be negative, see bug 1709018. + match (self, other) { + (&Length(ref one), &Length(ref other)) => one.partial_cmp(other), + _ => None, + } + } +} + +impl calc::CalcNodeLeaf for CalcLengthPercentageLeaf { + fn unitless_value(&self) -> f32 { + match *self { + Self::Length(ref l) => l.px(), + Self::Percentage(ref p) => p.0, + } + } + + fn try_sum_in_place(&mut self, other: &Self) -> Result<(), ()> { + use self::CalcLengthPercentageLeaf::*; + + // 0px plus anything else is equal to the right hand side. + if self.is_zero_length() { + *self = other.clone(); + return Ok(()); + } + + if other.is_zero_length() { + return Ok(()); + } + + match (self, other) { + (&mut Length(ref mut one), &Length(ref other)) => { + *one += *other; + }, + (&mut Percentage(ref mut one), &Percentage(ref other)) => { + one.0 += other.0; + }, + _ => return Err(()), + } + + Ok(()) + } + + fn try_op<O>(&self, other: &Self, op: O) -> Result<Self, ()> + where + O: Fn(f32, f32) -> f32, + { + match (self, other) { + ( + &CalcLengthPercentageLeaf::Length(ref one), + &CalcLengthPercentageLeaf::Length(ref other), + ) => Ok(CalcLengthPercentageLeaf::Length(Length::new(op( + one.px(), + other.px(), + )))), + ( + &CalcLengthPercentageLeaf::Percentage(one), + &CalcLengthPercentageLeaf::Percentage(other), + ) => Ok(CalcLengthPercentageLeaf::Percentage(Percentage(op( + one.0, other.0, + )))), + _ => Err(()), + } + } + + fn map(&mut self, mut op: impl FnMut(f32) -> f32) { + match self { + CalcLengthPercentageLeaf::Length(value) => { + *value = Length::new(op(value.px())); + }, + CalcLengthPercentageLeaf::Percentage(value) => { + *value = Percentage(op(value.0)); + }, + } + } + + fn simplify(&mut self) {} + + fn sort_key(&self) -> calc::SortKey { + match *self { + Self::Length(..) => calc::SortKey::Px, + Self::Percentage(..) => calc::SortKey::Percentage, + } + } +} + +/// The computed version of a calc() node for `<length-percentage>` values. +pub type CalcNode = calc::GenericCalcNode<CalcLengthPercentageLeaf>; + +/// The representation of a calc() function with mixed lengths and percentages. +#[derive( + Clone, Debug, Deserialize, MallocSizeOf, Serialize, ToAnimatedZero, ToResolvedValue, ToCss, +)] +#[repr(C)] +pub struct CalcLengthPercentage { + #[animation(constant)] + #[css(skip)] + clamping_mode: AllowedNumericType, + node: CalcNode, +} + +impl CalcLengthPercentage { + /// Resolves the percentage. + #[inline] + fn resolve(&self, basis: Length) -> Length { + // unwrap() is fine because the conversion below is infallible. + let px = self + .node + .resolve(|l| { + Ok(match *l { + CalcLengthPercentageLeaf::Length(l) => l.px(), + CalcLengthPercentageLeaf::Percentage(ref p) => basis.px() * p.0, + }) + }) + .unwrap(); + Length::new(self.clamping_mode.clamp(px)).normalized() + } +} + +// 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.node == other.node + } +} + +impl specified::CalcLengthPercentage { + /// Compute the value, zooming any absolute units by the zoom function. + fn to_computed_value_with_zoom<F>( + &self, + context: &Context, + zoom_fn: F, + base_size: FontBaseSize, + ) -> LengthPercentage + where + F: Fn(Length) -> Length, + { + use crate::values::specified::calc::Leaf; + + let node = self.node.map_leaves(|leaf| match *leaf { + Leaf::Percentage(p) => CalcLengthPercentageLeaf::Percentage(Percentage(p)), + Leaf::Length(l) => CalcLengthPercentageLeaf::Length({ + let result = l.to_computed_value_with_base_size(context, base_size); + if l.should_zoom_text() { + zoom_fn(result) + } else { + result + } + }), + Leaf::Number(..) | Leaf::Angle(..) | Leaf::Time(..) | Leaf::Resolution(..) => { + unreachable!("Shouldn't have parsed") + }, + }); + + LengthPercentage::new_calc(node, 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, + ) -> LengthPercentage { + self.to_computed_value_with_zoom(context, |abs| context.maybe_zoom_text(abs), 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<CSSFloat, ()> { + use crate::values::specified::calc::Leaf; + use crate::values::specified::length::NoCalcLength; + + // Simplification should've turned this into an absolute length, + // otherwise it wouldn't have been able to. + match self.node { + calc::CalcNode::Leaf(Leaf::Length(NoCalcLength::Absolute(ref l))) => Ok(l.to_px()), + _ => Err(()), + } + } + + /// Compute the calc using the current font-size (and without text-zoom). + pub fn to_computed_value(&self, context: &Context) -> LengthPercentage { + self.to_computed_value_with_zoom(context, |abs| abs, FontBaseSize::CurrentStyle) + } + + #[inline] + fn from_computed_value(computed: &CalcLengthPercentage) -> Self { + use crate::values::specified::calc::Leaf; + use crate::values::specified::length::NoCalcLength; + + specified::CalcLengthPercentage { + clamping_mode: computed.clamping_mode, + node: computed.node.map_leaves(|l| match l { + CalcLengthPercentageLeaf::Length(ref l) => { + Leaf::Length(NoCalcLength::from_px(l.px())) + }, + CalcLengthPercentageLeaf::Percentage(ref p) => Leaf::Percentage(p.0), + }), + } + } +} + +/// https://drafts.csswg.org/css-transitions/#animtype-lpcalc +/// https://drafts.csswg.org/css-values-4/#combine-math +/// https://drafts.csswg.org/css-values-4/#combine-mixed +impl Animate for LengthPercentage { + #[inline] + fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { + Ok(match (self.unpack(), other.unpack()) { + (Unpacked::Length(one), Unpacked::Length(other)) => { + Self::new_length(one.animate(&other, procedure)?) + }, + (Unpacked::Percentage(one), Unpacked::Percentage(other)) => { + Self::new_percent(one.animate(&other, procedure)?) + }, + _ => { + let mut one = self.to_calc_node().into_owned(); + let mut other = other.to_calc_node().into_owned(); + let (l, r) = procedure.weights(); + + one.mul_by(l as f32); + other.mul_by(r as f32); + + Self::new_calc( + CalcNode::Sum(vec![one, other].into()), + AllowedNumericType::All, + ) + }, + }) + } +} + +/// A wrapper of LengthPercentage, whose value must be >= 0. +pub type NonNegativeLengthPercentage = NonNegative<LengthPercentage>; + +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<Au>) -> Option<Au> { + 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/list.rs b/servo/components/style/values/computed/list.rs new file mode 100644 index 0000000000..3e5d1eb220 --- /dev/null +++ b/servo/components/style/values/computed/list.rs @@ -0,0 +1,17 @@ +/* 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/. */ + +//! `list` computed values. + +#[cfg(feature = "gecko")] +pub use crate::values::specified::list::ListStyleType; +pub use crate::values::specified::list::Quotes; + +impl Quotes { + /// Initial value for `quotes`. + #[inline] + pub fn get_initial_value() -> Quotes { + Quotes::Auto + } +} diff --git a/servo/components/style/values/computed/mod.rs b/servo/components/style/values/computed/mod.rs new file mode 100644 index 0000000000..207e796a10 --- /dev/null +++ b/servo/components/style/values/computed/mod.rs @@ -0,0 +1,999 @@ +/* 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. + +use self::transform::DirectionVector; +use super::animated::ToAnimatedValue; +use super::generics::grid::GridTemplateComponent as GenericGridTemplateComponent; +use super::generics::grid::ImplicitGridTracks as GenericImplicitGridTracks; +use super::generics::grid::{GenericGridLine, GenericTrackBreadth}; +use super::generics::grid::{GenericTrackSize, TrackList as GenericTrackList}; +use super::generics::transform::IsParallelTo; +use super::generics::{self, GreaterThanOrEqualToOne, NonNegative, ZeroToOne}; +use super::specified; +use super::{CSSFloat, CSSInteger}; +use crate::computed_value_flags::ComputedValueFlags; +use crate::context::QuirksMode; +use crate::font_metrics::{FontMetrics, FontMetricsOrientation}; +use crate::media_queries::Device; +#[cfg(feature = "gecko")] +use crate::properties; +use crate::properties::{ComputedValues, LonghandId, StyleBuilder}; +use crate::rule_cache::RuleCacheConditions; +use crate::stylesheets::container_rule::{ + ContainerInfo, ContainerSizeQuery, ContainerSizeQueryResult, +}; +use crate::values::specified::length::FontBaseSize; +use crate::{ArcSlice, Atom, One}; +use euclid::{default, Point2D, Rect, Size2D}; +use servo_arc::Arc; +use std::cell::RefCell; +use std::cmp; +use std::f32; +use std::ops::{Add, Sub}; + +#[cfg(feature = "gecko")] +pub use self::align::{ + AlignContent, AlignItems, AlignTracks, JustifyContent, JustifyItems, JustifyTracks, + SelfAlignment, +}; +#[cfg(feature = "gecko")] +pub use self::align::{AlignSelf, JustifySelf}; +pub use self::angle::Angle; +pub use self::animation::{AnimationIterationCount, AnimationName, AnimationTimeline}; +pub use self::animation::{ScrollAxis, ScrollTimelineName, TransitionProperty, ViewTimelineInset}; +pub use self::background::{BackgroundRepeat, BackgroundSize}; +pub use self::basic_shape::FillRule; +pub use self::border::{ + BorderCornerRadius, BorderImageRepeat, BorderImageSideWidth, BorderImageSlice, + BorderImageWidth, BorderRadius, BorderSideWidth, BorderSpacing, LineWidth, +}; +pub use self::box_::{ + Appearance, BaselineSource, BreakBetween, BreakWithin, Clear, Contain, ContainIntrinsicSize, + ContainerName, ContainerType, ContentVisibility, Display, Float, LineClamp, Overflow, + OverflowAnchor, OverflowClipBox, OverscrollBehavior, Perspective, Resize, ScrollSnapAlign, + ScrollSnapAxis, ScrollSnapStop, ScrollSnapStrictness, ScrollSnapType, ScrollbarGutter, + TouchAction, VerticalAlign, WillChange, +}; +pub use self::color::{ + Color, ColorOrAuto, ColorPropertyValue, ColorScheme, ForcedColorAdjust, PrintColorAdjust, +}; +pub use self::column::ColumnCount; +pub use self::counters::{Content, ContentItem, CounterIncrement, CounterReset, CounterSet}; +pub use self::easing::TimingFunction; +pub use self::effects::{BoxShadow, Filter, SimpleShadow}; +pub use self::flex::FlexBasis; +pub use self::font::{FontFamily, FontLanguageOverride, FontPalette, FontStyle}; +pub use self::font::{FontFeatureSettings, FontVariantLigatures, FontVariantNumeric}; +pub use self::font::{FontSize, FontSizeAdjust, FontStretch, FontSynthesis}; +pub use self::font::{FontVariantAlternates, FontWeight}; +pub use self::font::{FontVariantEastAsian, FontVariationSettings}; +pub use self::font::{MathDepth, MozScriptMinSize, MozScriptSizeMultiplier, XLang, XTextScale}; +pub use self::image::{Gradient, Image, ImageRendering, LineDirection, MozImageRect}; +pub use self::length::{CSSPixelLength, NonNegativeLength}; +pub use self::length::{Length, LengthOrNumber, LengthPercentage, NonNegativeLengthOrNumber}; +pub use self::length::{LengthOrAuto, LengthPercentageOrAuto, MaxSize, Size}; +pub use self::length::{NonNegativeLengthPercentage, NonNegativeLengthPercentageOrAuto}; +#[cfg(feature = "gecko")] +pub use self::list::ListStyleType; +pub use self::list::Quotes; +pub use self::motion::{OffsetPath, OffsetPosition, OffsetRotate}; +pub use self::outline::OutlineStyle; +pub use self::page::{PageName, PageOrientation, PageSize, PageSizeOrientation, PaperSize}; +pub use self::percentage::{NonNegativePercentage, Percentage}; +pub use self::position::AspectRatio; +pub use self::position::{ + GridAutoFlow, GridTemplateAreas, MasonryAutoFlow, Position, PositionOrAuto, ZIndex, +}; +pub use self::ratio::Ratio; +pub use self::rect::NonNegativeLengthOrNumberRect; +pub use self::resolution::Resolution; +pub use self::svg::{DProperty, MozContextProperties}; +pub use self::svg::{SVGLength, SVGOpacity, SVGPaint, SVGPaintKind}; +pub use self::svg::{SVGPaintOrder, SVGStrokeDashArray, SVGWidth}; +pub use self::text::HyphenateCharacter; +pub use self::text::TextUnderlinePosition; +pub use self::text::{InitialLetter, LetterSpacing, LineBreak, LineHeight}; +pub use self::text::{OverflowWrap, RubyPosition, TextOverflow, WordBreak, WordSpacing}; +pub use self::text::{TextAlign, TextAlignLast, TextEmphasisPosition, TextEmphasisStyle}; +pub use self::text::{TextDecorationLength, TextDecorationSkipInk, TextJustify}; +pub use self::time::Time; +pub use self::transform::{Rotate, Scale, Transform, TransformOperation}; +pub use self::transform::{TransformOrigin, TransformStyle, Translate}; +#[cfg(feature = "gecko")] +pub use self::ui::CursorImage; +pub use self::ui::{BoolInteger, Cursor, UserSelect}; +pub use super::specified::TextTransform; +pub use super::specified::ViewportVariant; +pub use super::specified::{BorderStyle, TextDecorationLine}; +pub use app_units::Au; + +#[cfg(feature = "gecko")] +pub mod align; +pub mod angle; +pub mod animation; +pub mod background; +pub mod basic_shape; +pub mod border; +#[path = "box.rs"] +pub mod box_; +pub mod color; +pub mod column; +pub mod counters; +pub mod easing; +pub mod effects; +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; +pub mod page; +pub mod percentage; +pub mod position; +pub mod ratio; +pub mod rect; +pub mod resolution; +pub mod svg; +pub mod table; +pub mod text; +pub mod time; +pub mod transform; +pub mod ui; +pub mod url; + +/// A `Context` is all the data a specified value could ever need to compute +/// itself and be transformed to a computed value. +pub struct Context<'a> { + /// Values accessed 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 builder: StyleBuilder<'a>, + + /// A cached computed system font value, for use by gecko. + /// + /// See properties/longhands/font.mako.rs + #[cfg(feature = "gecko")] + pub cached_system_font: Option<properties::longhands::system_font::ComputedSystemFont>, + + /// A dummy option for servo so initializing a computed::Context isn't + /// painful. + /// + /// TODO(emilio): Make constructors for Context, and drop this. + #[cfg(feature = "servo")] + pub cached_system_font: Option<()>, + + /// Whether or not we are computing the media list in a media query. + pub in_media_query: bool, + + /// Whether or not we are computing the container query condition. + pub in_container_query: bool, + + /// The quirks mode of this context. + pub quirks_mode: QuirksMode, + + /// Whether this computation is being done for a SMIL animation. + /// + /// This is used to allow certain properties to generate out-of-range + /// values, which SMIL allows. + pub for_smil_animation: bool, + + /// Returns the container information to evaluate a given container query. + pub container_info: Option<ContainerInfo>, + + /// The property we are computing a value for, if it is a non-inherited + /// property. None if we are computed a value for an inherited property + /// or not computing for a property at all (e.g. in a media query + /// evaluation). + pub for_non_inherited_property: Option<LonghandId>, + + /// The conditions to cache a rule node on the rule cache. + /// + /// FIXME(emilio): Drop the refcell. + pub rule_cache_conditions: RefCell<&'a mut RuleCacheConditions>, + + /// Container size query for this context. + container_size_query: RefCell<ContainerSizeQuery<'a>>, +} + +impl<'a> Context<'a> { + /// Lazily evaluate the container size query, returning the result. + pub fn get_container_size_query(&self) -> ContainerSizeQueryResult { + let mut resolved = self.container_size_query.borrow_mut(); + resolved.get().clone() + } + + /// Creates a suitable context for media query evaluation, in which + /// font-relative units compute against the system_font, and executes `f` + /// with it. + pub fn for_media_query_evaluation<F, R>(device: &Device, quirks_mode: QuirksMode, f: F) -> R + where + F: FnOnce(&Context) -> R, + { + let mut conditions = RuleCacheConditions::default(); + let context = Context { + builder: StyleBuilder::for_inheritance(device, None, None), + cached_system_font: None, + in_media_query: true, + in_container_query: false, + quirks_mode, + for_smil_animation: false, + container_info: None, + for_non_inherited_property: None, + rule_cache_conditions: RefCell::new(&mut conditions), + container_size_query: RefCell::new(ContainerSizeQuery::none()), + }; + f(&context) + } + + /// Creates a suitable context for container query evaluation for the style + /// specified. + pub fn for_container_query_evaluation<F, R>( + device: &Device, + container_info_and_style: Option<(ContainerInfo, Arc<ComputedValues>)>, + container_size_query: ContainerSizeQuery, + f: F, + ) -> R + where + F: FnOnce(&Context) -> R, + { + let mut conditions = RuleCacheConditions::default(); + + let (container_info, style) = match container_info_and_style { + Some((ci, s)) => (Some(ci), Some(s)), + None => (None, None), + }; + + let style = style.as_ref().map(|s| &**s); + let quirks_mode = device.quirks_mode(); + let context = Context { + builder: StyleBuilder::for_inheritance(device, style, None), + cached_system_font: None, + in_media_query: false, + in_container_query: true, + quirks_mode, + for_smil_animation: false, + container_info, + for_non_inherited_property: None, + rule_cache_conditions: RefCell::new(&mut conditions), + container_size_query: RefCell::new(container_size_query), + }; + + f(&context) + } + + /// Creates a context suitable for more general cases. + pub fn new( + builder: StyleBuilder<'a>, + quirks_mode: QuirksMode, + rule_cache_conditions: &'a mut RuleCacheConditions, + container_size_query: ContainerSizeQuery<'a>, + ) -> Self { + Self { + builder, + cached_system_font: None, + in_media_query: false, + in_container_query: false, + quirks_mode, + container_info: None, + for_smil_animation: false, + for_non_inherited_property: None, + rule_cache_conditions: RefCell::new(rule_cache_conditions), + container_size_query: RefCell::new(container_size_query), + } + } + + /// Creates a context suitable for computing animations. + pub fn new_for_animation( + builder: StyleBuilder<'a>, + for_smil_animation: bool, + quirks_mode: QuirksMode, + rule_cache_conditions: &'a mut RuleCacheConditions, + container_size_query: ContainerSizeQuery<'a>, + ) -> Self { + Self { + builder, + cached_system_font: None, + in_media_query: false, + in_container_query: false, + quirks_mode, + container_info: None, + for_smil_animation, + for_non_inherited_property: None, + rule_cache_conditions: RefCell::new(rule_cache_conditions), + container_size_query: RefCell::new(container_size_query), + } + } + + /// The current device. + pub fn device(&self) -> &Device { + self.builder.device + } + + /// Queries font metrics. + pub fn query_font_metrics( + &self, + base_size: FontBaseSize, + orientation: FontMetricsOrientation, + retrieve_math_scales: bool, + ) -> FontMetrics { + if self.for_non_inherited_property.is_some() { + self.rule_cache_conditions.borrow_mut().set_uncacheable(); + } + self.builder.add_flags(match base_size { + FontBaseSize::CurrentStyle => ComputedValueFlags::DEPENDS_ON_SELF_FONT_METRICS, + FontBaseSize::InheritedStyle => ComputedValueFlags::DEPENDS_ON_INHERITED_FONT_METRICS, + }); + let size = base_size.resolve(self).used_size(); + let style = self.style(); + + let (wm, font) = match base_size { + FontBaseSize::CurrentStyle => (style.writing_mode, style.get_font()), + // This is only used for font-size computation. + FontBaseSize::InheritedStyle => { + (*style.inherited_writing_mode(), style.get_parent_font()) + }, + }; + + let vertical = match orientation { + FontMetricsOrientation::MatchContextPreferHorizontal => { + wm.is_vertical() && wm.is_upright() + }, + FontMetricsOrientation::MatchContextPreferVertical => { + wm.is_vertical() && !wm.is_sideways() + }, + FontMetricsOrientation::Horizontal => false, + }; + self.device().query_font_metrics( + vertical, + font, + size, + self.in_media_or_container_query(), + retrieve_math_scales, + ) + } + + /// The current viewport size, used to resolve viewport units. + pub fn viewport_size_for_viewport_unit_resolution( + &self, + variant: ViewportVariant, + ) -> default::Size2D<Au> { + self.builder + .add_flags(ComputedValueFlags::USES_VIEWPORT_UNITS); + self.builder + .device + .au_viewport_size_for_viewport_unit_resolution(variant) + } + + /// Whether we're in a media or container query. + pub fn in_media_or_container_query(&self) -> bool { + self.in_media_query || self.in_container_query + } + + /// The default computed style we're getting our reset style from. + pub fn default_style(&self) -> &ComputedValues { + self.builder.default_style() + } + + /// The current style. + pub fn style(&self) -> &StyleBuilder { + &self.builder + } + + /// Apply text-zoom if enabled. + #[cfg(feature = "gecko")] + pub fn maybe_zoom_text(&self, size: CSSPixelLength) -> CSSPixelLength { + if self + .style() + .get_font() + .clone__x_text_scale() + .text_zoom_enabled() + { + self.device().zoom_text(size) + } else { + size + } + } + + /// (Servo doesn't do text-zoom) + #[cfg(feature = "servo")] + pub fn maybe_zoom_text(&self, size: CSSPixelLength) -> CSSPixelLength { + size + } +} + +/// An iterator over a slice of computed values +#[derive(Clone)] +pub struct ComputedVecIter<'a, 'cx, 'cx_a: 'cx, S: ToComputedValue + 'a> { + cx: &'cx Context<'cx_a>, + values: &'a [S], +} + +impl<'a, 'cx, 'cx_a: 'cx, S: ToComputedValue + 'a> ComputedVecIter<'a, 'cx, 'cx_a, S> { + /// Construct an iterator from a slice of specified values and a context + pub fn new(cx: &'cx Context<'cx_a>, values: &'a [S]) -> Self { + ComputedVecIter { cx, values } + } +} + +impl<'a, 'cx, 'cx_a: 'cx, S: ToComputedValue + 'a> ExactSizeIterator + for ComputedVecIter<'a, 'cx, 'cx_a, S> +{ + fn len(&self) -> usize { + self.values.len() + } +} + +impl<'a, 'cx, 'cx_a: 'cx, S: ToComputedValue + 'a> Iterator for ComputedVecIter<'a, 'cx, 'cx_a, S> { + type Item = S::ComputedValue; + fn next(&mut self) -> Option<Self::Item> { + if let Some((next, rest)) = self.values.split_first() { + let ret = next.to_computed_value(self.cx); + self.values = rest; + Some(ret) + } else { + None + } + } + + fn size_hint(&self) -> (usize, Option<usize>) { + (self.values.len(), Some(self.values.len())) + } +} + +/// A trait to represent the conversion between computed and specified values. +/// +/// This trait is derivable with `#[derive(ToComputedValue)]`. The derived +/// implementation just calls `ToComputedValue::to_computed_value` on each field +/// of the passed value. The deriving code assumes that if the type isn't +/// generic, then the trait can be implemented as simple `Clone::clone` calls, +/// this means that a manual implementation with `ComputedValue = Self` is bogus +/// if it returns anything else than a clone. +pub trait ToComputedValue { + /// The computed value type we're going to be converted to. + type ComputedValue; + + /// Convert a specified value to a computed value, using itself and the data + /// inside the `Context`. + fn to_computed_value(&self, context: &Context) -> Self::ComputedValue; + + /// Convert a computed value to specified value form. + /// + /// This will be used for recascading during animation. + /// Such from_computed_valued values should recompute to the same value. + fn from_computed_value(computed: &Self::ComputedValue) -> Self; +} + +impl<A, B> ToComputedValue for (A, B) +where + A: ToComputedValue, + B: ToComputedValue, +{ + type ComputedValue = ( + <A as ToComputedValue>::ComputedValue, + <B as ToComputedValue>::ComputedValue, + ); + + #[inline] + fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { + ( + self.0.to_computed_value(context), + self.1.to_computed_value(context), + ) + } + + #[inline] + fn from_computed_value(computed: &Self::ComputedValue) -> Self { + ( + A::from_computed_value(&computed.0), + B::from_computed_value(&computed.1), + ) + } +} + +impl<T> ToComputedValue for Option<T> +where + T: ToComputedValue, +{ + type ComputedValue = Option<<T as ToComputedValue>::ComputedValue>; + + #[inline] + fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { + self.as_ref().map(|item| item.to_computed_value(context)) + } + + #[inline] + fn from_computed_value(computed: &Self::ComputedValue) -> Self { + computed.as_ref().map(T::from_computed_value) + } +} + +impl<T> ToComputedValue for default::Size2D<T> +where + T: ToComputedValue, +{ + type ComputedValue = default::Size2D<<T as ToComputedValue>::ComputedValue>; + + #[inline] + fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { + Size2D::new( + self.width.to_computed_value(context), + self.height.to_computed_value(context), + ) + } + + #[inline] + fn from_computed_value(computed: &Self::ComputedValue) -> Self { + Size2D::new( + T::from_computed_value(&computed.width), + T::from_computed_value(&computed.height), + ) + } +} + +impl<T> ToComputedValue for Vec<T> +where + T: ToComputedValue, +{ + type ComputedValue = Vec<<T as ToComputedValue>::ComputedValue>; + + #[inline] + fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { + self.iter() + .map(|item| item.to_computed_value(context)) + .collect() + } + + #[inline] + fn from_computed_value(computed: &Self::ComputedValue) -> Self { + computed.iter().map(T::from_computed_value).collect() + } +} + +impl<T> ToComputedValue for Box<T> +where + T: ToComputedValue, +{ + type ComputedValue = Box<<T as ToComputedValue>::ComputedValue>; + + #[inline] + fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { + Box::new(T::to_computed_value(self, context)) + } + + #[inline] + fn from_computed_value(computed: &Self::ComputedValue) -> Self { + Box::new(T::from_computed_value(computed)) + } +} + +impl<T> ToComputedValue for Box<[T]> +where + T: ToComputedValue, +{ + type ComputedValue = Box<[<T as ToComputedValue>::ComputedValue]>; + + #[inline] + fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { + self.iter() + .map(|item| item.to_computed_value(context)) + .collect::<Vec<_>>() + .into_boxed_slice() + } + + #[inline] + fn from_computed_value(computed: &Self::ComputedValue) -> Self { + computed + .iter() + .map(T::from_computed_value) + .collect::<Vec<_>>() + .into_boxed_slice() + } +} + +impl<T> ToComputedValue for crate::OwnedSlice<T> +where + T: ToComputedValue, +{ + type ComputedValue = crate::OwnedSlice<<T as ToComputedValue>::ComputedValue>; + + #[inline] + fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { + self.iter() + .map(|item| item.to_computed_value(context)) + .collect() + } + + #[inline] + fn from_computed_value(computed: &Self::ComputedValue) -> Self { + computed.iter().map(T::from_computed_value).collect() + } +} + +// NOTE(emilio): This is implementable more generically, but it's unlikely +// what you want there, as it forces you to have an extra allocation. +// +// We could do that if needed, ideally with specialization for the case where +// ComputedValue = T. But we don't need it for now. +impl<T> ToComputedValue for Arc<T> +where + T: ToComputedValue<ComputedValue = T>, +{ + type ComputedValue = Self; + + #[inline] + fn to_computed_value(&self, _: &Context) -> Self { + self.clone() + } + + #[inline] + fn from_computed_value(computed: &Self) -> Self { + computed.clone() + } +} + +// Same caveat as above applies. +impl<T> ToComputedValue for ArcSlice<T> +where + T: ToComputedValue<ComputedValue = T>, +{ + type ComputedValue = Self; + + #[inline] + fn to_computed_value(&self, _: &Context) -> Self { + self.clone() + } + + #[inline] + fn from_computed_value(computed: &Self) -> Self { + computed.clone() + } +} + +trivial_to_computed_value!(()); +trivial_to_computed_value!(bool); +trivial_to_computed_value!(f32); +trivial_to_computed_value!(i32); +trivial_to_computed_value!(u8); +trivial_to_computed_value!(u16); +trivial_to_computed_value!(u32); +trivial_to_computed_value!(usize); +trivial_to_computed_value!(Atom); +trivial_to_computed_value!(crate::values::AtomIdent); +#[cfg(feature = "servo")] +trivial_to_computed_value!(crate::Namespace); +#[cfg(feature = "servo")] +trivial_to_computed_value!(crate::Prefix); +trivial_to_computed_value!(String); +trivial_to_computed_value!(Box<str>); +trivial_to_computed_value!(crate::OwnedStr); +trivial_to_computed_value!(style_traits::values::specified::AllowedNumericType); + +#[allow(missing_docs)] +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + MallocSizeOf, + PartialEq, + ToAnimatedZero, + ToCss, + ToResolvedValue, +)] +#[repr(C, u8)] +pub enum AngleOrPercentage { + Percentage(Percentage), + Angle(Angle), +} + +impl ToComputedValue for specified::AngleOrPercentage { + type ComputedValue = AngleOrPercentage; + + #[inline] + fn to_computed_value(&self, context: &Context) -> AngleOrPercentage { + match *self { + specified::AngleOrPercentage::Percentage(percentage) => { + AngleOrPercentage::Percentage(percentage.to_computed_value(context)) + }, + specified::AngleOrPercentage::Angle(angle) => { + AngleOrPercentage::Angle(angle.to_computed_value(context)) + }, + } + } + #[inline] + fn from_computed_value(computed: &AngleOrPercentage) -> Self { + match *computed { + AngleOrPercentage::Percentage(percentage) => specified::AngleOrPercentage::Percentage( + ToComputedValue::from_computed_value(&percentage), + ), + AngleOrPercentage::Angle(angle) => { + specified::AngleOrPercentage::Angle(ToComputedValue::from_computed_value(&angle)) + }, + } + } +} + +/// A `<number>` value. +pub type Number = CSSFloat; + +impl IsParallelTo for (Number, Number, Number) { + fn is_parallel_to(&self, vector: &DirectionVector) -> bool { + use euclid::approxeq::ApproxEq; + // If a and b is parallel, the angle between them is 0deg, so + // a x b = |a|*|b|*sin(0)*n = 0 * n, |a x b| == 0. + let self_vector = DirectionVector::new(self.0, self.1, self.2); + self_vector + .cross(*vector) + .square_length() + .approx_eq(&0.0f32) + } +} + +/// A wrapper of Number, but the value >= 0. +pub type NonNegativeNumber = NonNegative<CSSFloat>; + +impl ToAnimatedValue for NonNegativeNumber { + type AnimatedValue = CSSFloat; + + #[inline] + fn to_animated_value(self) -> Self::AnimatedValue { + self.0 + } + + #[inline] + fn from_animated_value(animated: Self::AnimatedValue) -> Self { + animated.max(0.).into() + } +} + +impl From<CSSFloat> for NonNegativeNumber { + #[inline] + fn from(number: CSSFloat) -> NonNegativeNumber { + NonNegative::<CSSFloat>(number) + } +} + +impl From<NonNegativeNumber> for CSSFloat { + #[inline] + fn from(number: NonNegativeNumber) -> CSSFloat { + number.0 + } +} + +impl One for NonNegativeNumber { + #[inline] + fn one() -> Self { + NonNegative(1.0) + } + + #[inline] + fn is_one(&self) -> bool { + self.0 == 1.0 + } +} + +/// A wrapper of Number, but the value between 0 and 1 +pub type ZeroToOneNumber = ZeroToOne<CSSFloat>; + +impl ToAnimatedValue for ZeroToOneNumber { + type AnimatedValue = CSSFloat; + + #[inline] + fn to_animated_value(self) -> Self::AnimatedValue { + self.0 + } + + #[inline] + fn from_animated_value(animated: Self::AnimatedValue) -> Self { + Self(animated.max(0.).min(1.)) + } +} + +impl From<CSSFloat> for ZeroToOneNumber { + #[inline] + fn from(number: CSSFloat) -> Self { + Self(number) + } +} + +/// A wrapper of Number, but the value >= 1. +pub type GreaterThanOrEqualToOneNumber = GreaterThanOrEqualToOne<CSSFloat>; + +impl ToAnimatedValue for GreaterThanOrEqualToOneNumber { + type AnimatedValue = CSSFloat; + + #[inline] + fn to_animated_value(self) -> Self::AnimatedValue { + self.0 + } + + #[inline] + fn from_animated_value(animated: Self::AnimatedValue) -> Self { + animated.max(1.).into() + } +} + +impl From<CSSFloat> for GreaterThanOrEqualToOneNumber { + #[inline] + fn from(number: CSSFloat) -> GreaterThanOrEqualToOneNumber { + GreaterThanOrEqualToOne::<CSSFloat>(number) + } +} + +impl From<GreaterThanOrEqualToOneNumber> for CSSFloat { + #[inline] + fn from(number: GreaterThanOrEqualToOneNumber) -> CSSFloat { + number.0 + } +} + +#[allow(missing_docs)] +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + MallocSizeOf, + PartialEq, + ToAnimatedZero, + ToCss, + ToResolvedValue, +)] +#[repr(C, u8)] +pub enum NumberOrPercentage { + Percentage(Percentage), + Number(Number), +} + +impl NumberOrPercentage { + fn clamp_to_non_negative(self) -> Self { + match self { + NumberOrPercentage::Percentage(p) => { + NumberOrPercentage::Percentage(p.clamp_to_non_negative()) + }, + NumberOrPercentage::Number(n) => NumberOrPercentage::Number(n.max(0.)), + } + } +} + +impl ToComputedValue for specified::NumberOrPercentage { + type ComputedValue = NumberOrPercentage; + + #[inline] + fn to_computed_value(&self, context: &Context) -> NumberOrPercentage { + match *self { + specified::NumberOrPercentage::Percentage(percentage) => { + NumberOrPercentage::Percentage(percentage.to_computed_value(context)) + }, + specified::NumberOrPercentage::Number(number) => { + NumberOrPercentage::Number(number.to_computed_value(context)) + }, + } + } + #[inline] + fn from_computed_value(computed: &NumberOrPercentage) -> Self { + match *computed { + NumberOrPercentage::Percentage(percentage) => { + specified::NumberOrPercentage::Percentage(ToComputedValue::from_computed_value( + &percentage, + )) + }, + NumberOrPercentage::Number(number) => { + specified::NumberOrPercentage::Number(ToComputedValue::from_computed_value(&number)) + }, + } + } +} + +/// A non-negative <number-percentage>. +pub type NonNegativeNumberOrPercentage = NonNegative<NumberOrPercentage>; + +impl NonNegativeNumberOrPercentage { + /// Returns the `100%` value. + #[inline] + pub fn hundred_percent() -> Self { + NonNegative(NumberOrPercentage::Percentage(Percentage::hundred())) + } +} + +impl ToAnimatedValue for NonNegativeNumberOrPercentage { + type AnimatedValue = NumberOrPercentage; + + #[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()) + } +} + +/// A type used for opacity. +pub type Opacity = CSSFloat; + +/// A `<integer>` value. +pub type Integer = CSSInteger; + +/// A wrapper of Integer, but only accept a value >= 1. +pub type PositiveInteger = GreaterThanOrEqualToOne<CSSInteger>; + +impl ToAnimatedValue for PositiveInteger { + type AnimatedValue = CSSInteger; + + #[inline] + fn to_animated_value(self) -> Self::AnimatedValue { + self.0 + } + + #[inline] + fn from_animated_value(animated: Self::AnimatedValue) -> Self { + cmp::max(animated, 1).into() + } +} + +impl From<CSSInteger> for PositiveInteger { + #[inline] + fn from(int: CSSInteger) -> PositiveInteger { + GreaterThanOrEqualToOne::<CSSInteger>(int) + } +} + +/// rect(...) | auto +pub type ClipRect = generics::GenericClipRect<LengthOrAuto>; + +/// rect(...) | auto +pub type ClipRectOrAuto = generics::GenericClipRectOrAuto<ClipRect>; + +/// The computed value of a grid `<track-breadth>` +pub type TrackBreadth = GenericTrackBreadth<LengthPercentage>; + +/// The computed value of a grid `<track-size>` +pub type TrackSize = GenericTrackSize<LengthPercentage>; + +/// The computed value of a grid `<track-size>+` +pub type ImplicitGridTracks = GenericImplicitGridTracks<TrackSize>; + +/// The computed value of a grid `<track-list>` +/// (could also be `<auto-track-list>` or `<explicit-track-list>`) +pub type TrackList = GenericTrackList<LengthPercentage, Integer>; + +/// The computed value of a `<grid-line>`. +pub type GridLine = GenericGridLine<Integer>; + +/// `<grid-template-rows> | <grid-template-columns>` +pub type GridTemplateComponent = GenericGridTemplateComponent<LengthPercentage, Integer>; + +impl ClipRect { + /// Given a border box, resolves the clip rect against the border box + /// in the same space the border box is in + pub fn for_border_rect<T: Copy + From<Length> + Add<Output = T> + Sub<Output = T>, U>( + &self, + border_box: Rect<T, U>, + ) -> Rect<T, U> { + fn extract_clip_component<T: From<Length>>(p: &LengthOrAuto, or: T) -> T { + match *p { + LengthOrAuto::Auto => or, + LengthOrAuto::LengthPercentage(ref length) => T::from(*length), + } + } + + let clip_origin = Point2D::new( + From::from(self.left.auto_is(|| Length::new(0.))), + From::from(self.top.auto_is(|| Length::new(0.))), + ); + let right = extract_clip_component(&self.right, border_box.size.width); + let bottom = extract_clip_component(&self.bottom, border_box.size.height); + let clip_size = Size2D::new(right - clip_origin.x, bottom - clip_origin.y); + + Rect::new(clip_origin, clip_size).translate(border_box.origin.to_vector()) + } +} diff --git a/servo/components/style/values/computed/motion.rs b/servo/components/style/values/computed/motion.rs new file mode 100644 index 0000000000..704d10b68a --- /dev/null +++ b/servo/components/style/values/computed/motion.rs @@ -0,0 +1,60 @@ +/* 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 types for CSS values that are related to motion path. + +use crate::values::computed::{Angle, LengthPercentage}; +use crate::values::generics::motion::{GenericOffsetPath, GenericOffsetPosition}; +use crate::Zero; + +/// The computed value of `offset-path`. +pub type OffsetPath = GenericOffsetPath<Angle>; + +/// The computed value of `offset-position`. +pub type OffsetPosition = GenericOffsetPosition<LengthPercentage, LengthPercentage>; + +#[inline] +fn is_auto_zero_angle(auto: &bool, angle: &Angle) -> bool { + *auto && angle.is_zero() +} + +/// A computed offset-rotate. +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + Deserialize, + MallocSizeOf, + PartialEq, + Serialize, + ToAnimatedZero, + ToCss, + ToResolvedValue, +)] +#[repr(C)] +pub struct OffsetRotate { + /// If auto is false, this is a fixed angle which indicates a + /// constant clockwise rotation transformation applied to it by this + /// specified rotation angle. Otherwise, the angle will be added to + /// the angle of the direction in layout. + #[animation(constant)] + #[css(represents_keyword)] + pub auto: bool, + /// The angle value. + #[css(contextual_skip_if = "is_auto_zero_angle")] + pub angle: Angle, +} + +impl OffsetRotate { + /// Returns "auto 0deg". + #[inline] + pub fn auto() -> Self { + OffsetRotate { + auto: true, + angle: Zero::zero(), + } + } +} diff --git a/servo/components/style/values/computed/outline.rs b/servo/components/style/values/computed/outline.rs new file mode 100644 index 0000000000..f872176529 --- /dev/null +++ b/servo/components/style/values/computed/outline.rs @@ -0,0 +1,7 @@ +/* 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 for outline properties + +pub use crate::values::specified::OutlineStyle; diff --git a/servo/components/style/values/computed/page.rs b/servo/components/style/values/computed/page.rs new file mode 100644 index 0000000000..6f71c912cf --- /dev/null +++ b/servo/components/style/values/computed/page.rs @@ -0,0 +1,75 @@ +/* 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 @page at-rule properties and named-page style properties + +use crate::values::computed::length::NonNegativeLength; +use crate::values::computed::{Context, ToComputedValue}; +use crate::values::generics; +use crate::values::generics::size::Size2D; + +use crate::values::specified::page as specified; +pub use generics::page::GenericPageSize; +pub use generics::page::PageOrientation; +pub use generics::page::PageSizeOrientation; +pub use generics::page::PaperSize; +pub use specified::PageName; + +/// Computed value of the @page size descriptor +/// +/// The spec says that the computed value should be the same as the specified +/// value but with all absolute units, but it's not currently possibly observe +/// the computed value of page-size. +#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToCss, ToResolvedValue, ToShmem)] +#[repr(C, u8)] +pub enum PageSize { + /// Specified size, paper size, or paper size and orientation. + Size(Size2D<NonNegativeLength>), + /// `landscape` or `portrait` value, no specified size. + Orientation(PageSizeOrientation), + /// `auto` value + Auto, +} + +impl ToComputedValue for specified::PageSize { + type ComputedValue = PageSize; + + fn to_computed_value(&self, ctx: &Context) -> Self::ComputedValue { + match &*self { + Self::Size(s) => PageSize::Size(s.to_computed_value(ctx)), + Self::PaperSize(p, PageSizeOrientation::Landscape) => PageSize::Size(Size2D { + width: p.long_edge().to_computed_value(ctx), + height: p.short_edge().to_computed_value(ctx), + }), + Self::PaperSize(p, PageSizeOrientation::Portrait) => PageSize::Size(Size2D { + width: p.short_edge().to_computed_value(ctx), + height: p.long_edge().to_computed_value(ctx), + }), + Self::Orientation(o) => PageSize::Orientation(*o), + Self::Auto => PageSize::Auto, + } + } + + fn from_computed_value(computed: &Self::ComputedValue) -> Self { + match *computed { + PageSize::Size(s) => Self::Size(ToComputedValue::from_computed_value(&s)), + PageSize::Orientation(o) => Self::Orientation(o), + PageSize::Auto => Self::Auto, + } + } +} + +impl PageSize { + /// `auto` value. + #[inline] + pub fn auto() -> Self { + PageSize::Auto + } + + /// Whether this is the `auto` value. + #[inline] + pub fn is_auto(&self) -> bool { + matches!(*self, PageSize::Auto) + } +} diff --git a/servo/components/style/values/computed/percentage.rs b/servo/components/style/values/computed/percentage.rs new file mode 100644 index 0000000000..994c01594a --- /dev/null +++ b/servo/components/style/values/computed/percentage.rs @@ -0,0 +1,136 @@ +/* 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 percentages. + +use crate::values::animated::ToAnimatedValue; +use crate::values::generics::NonNegative; +use crate::values::specified::percentage::ToPercentage; +use crate::values::{serialize_normalized_percentage, CSSFloat}; +use crate::Zero; +use std::fmt; +use style_traits::{CssWriter, ToCss}; + +/// A computed percentage. +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + Default, + Deserialize, + MallocSizeOf, + PartialEq, + PartialOrd, + Serialize, + SpecifiedValueInfo, + ToAnimatedValue, + ToAnimatedZero, + ToComputedValue, + ToResolvedValue, + ToShmem, +)] +#[repr(C)] +pub struct Percentage(pub CSSFloat); + +impl Percentage { + /// 100% + #[inline] + pub fn hundred() -> Self { + Percentage(1.) + } + + /// Returns the absolute value for this percentage. + #[inline] + pub fn abs(&self) -> Self { + Percentage(self.0.abs()) + } + + /// Clamps this percentage to a non-negative percentage. + #[inline] + pub fn clamp_to_non_negative(self) -> Self { + Percentage(self.0.max(0.)) + } +} + +impl Zero for Percentage { + fn zero() -> Self { + Percentage(0.) + } + + fn is_zero(&self) -> bool { + self.0 == 0. + } +} + +impl ToPercentage for Percentage { + fn to_percentage(&self) -> CSSFloat { + self.0 + } +} + +impl std::ops::AddAssign for Percentage { + fn add_assign(&mut self, other: Self) { + self.0 += other.0 + } +} + +impl std::ops::Add for Percentage { + type Output = Self; + + fn add(self, other: Self) -> Self { + Percentage(self.0 + other.0) + } +} + +impl std::ops::Sub for Percentage { + type Output = Self; + + fn sub(self, other: Self) -> Self { + Percentage(self.0 - other.0) + } +} + +impl std::ops::Rem for Percentage { + type Output = Self; + + fn rem(self, other: Self) -> Self { + Percentage(self.0 % other.0) + } +} + +impl ToCss for Percentage { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: fmt::Write, + { + serialize_normalized_percentage(self.0, dest) + } +} + +/// A wrapper over a `Percentage`, whose value should be clamped to 0. +pub type NonNegativePercentage = NonNegative<Percentage>; + +impl NonNegativePercentage { + /// 100% + #[inline] + pub fn hundred() -> Self { + NonNegative(Percentage::hundred()) + } +} + +impl ToAnimatedValue for NonNegativePercentage { + type AnimatedValue = Percentage; + + #[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()) + } +} diff --git a/servo/components/style/values/computed/position.rs b/servo/components/style/values/computed/position.rs new file mode 100644 index 0000000000..5a10c0f23d --- /dev/null +++ b/servo/components/style/values/computed/position.rs @@ -0,0 +1,74 @@ +/* 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/. */ + +//! CSS handling for the computed value of +//! [`position`][position] values. +//! +//! [position]: https://drafts.csswg.org/css-backgrounds-3/#position + +use crate::values::computed::{Integer, LengthPercentage, NonNegativeNumber, Percentage}; +use crate::values::generics::position::AspectRatio as GenericAspectRatio; +use crate::values::generics::position::Position as GenericPosition; +use crate::values::generics::position::PositionComponent as GenericPositionComponent; +use crate::values::generics::position::PositionOrAuto as GenericPositionOrAuto; +use crate::values::generics::position::ZIndex as GenericZIndex; +pub use crate::values::specified::position::{GridAutoFlow, GridTemplateAreas, MasonryAutoFlow}; +use crate::Zero; +use std::fmt::{self, Write}; +use style_traits::{CssWriter, ToCss}; + +/// The computed value of a CSS `<position>` +pub type Position = GenericPosition<HorizontalPosition, VerticalPosition>; + +/// The computed value of an `auto | <position>` +pub type PositionOrAuto = GenericPositionOrAuto<Position>; + +/// The computed value of a CSS horizontal position. +pub type HorizontalPosition = LengthPercentage; + +/// The computed value of a CSS vertical position. +pub type VerticalPosition = LengthPercentage; + +impl Position { + /// `50% 50%` + #[inline] + pub fn center() -> Self { + Self::new( + LengthPercentage::new_percent(Percentage(0.5)), + LengthPercentage::new_percent(Percentage(0.5)), + ) + } + + /// `0% 0%` + #[inline] + pub fn zero() -> Self { + Self::new(LengthPercentage::zero(), LengthPercentage::zero()) + } +} + +impl ToCss for Position { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + self.horizontal.to_css(dest)?; + dest.write_char(' ')?; + self.vertical.to_css(dest) + } +} + +impl GenericPositionComponent for LengthPercentage { + fn is_center(&self) -> bool { + match self.to_percentage() { + Some(Percentage(per)) => per == 0.5, + _ => false, + } + } +} + +/// A computed value for the `z-index` property. +pub type ZIndex = GenericZIndex<Integer>; + +/// A computed value for the `aspect-ratio` property. +pub type AspectRatio = GenericAspectRatio<NonNegativeNumber>; diff --git a/servo/components/style/values/computed/ratio.rs b/servo/components/style/values/computed/ratio.rs new file mode 100644 index 0000000000..ae8997cfc0 --- /dev/null +++ b/servo/components/style/values/computed/ratio.rs @@ -0,0 +1,115 @@ +/* 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/. */ + +//! `<ratio>` computed values. + +use crate::values::animated::{Animate, Procedure}; +use crate::values::computed::NonNegativeNumber; +use crate::values::distance::{ComputeSquaredDistance, SquaredDistance}; +use crate::values::generics::ratio::Ratio as GenericRatio; +use crate::{One, Zero}; +use std::cmp::{Ordering, PartialOrd}; + +/// A computed <ratio> value. +pub type Ratio = GenericRatio<NonNegativeNumber>; + +impl PartialOrd for Ratio { + fn partial_cmp(&self, other: &Self) -> Option<Ordering> { + f64::partial_cmp( + &((self.0).0 as f64 * (other.1).0 as f64), + &((self.1).0 as f64 * (other.0).0 as f64), + ) + } +} + +/// https://drafts.csswg.org/css-values/#combine-ratio +impl Animate for Ratio { + fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { + // If either <ratio> is degenerate, the values cannot be interpolated. + if self.is_degenerate() || other.is_degenerate() { + return Err(()); + } + + // Addition of <ratio>s is not possible, and based on + // https://drafts.csswg.org/css-values-4/#not-additive, + // we simply use the first value as the result value. + // Besides, the procedure for accumulation should be identical to addition here. + if matches!(procedure, Procedure::Add | Procedure::Accumulate { .. }) { + return Ok(self.clone()); + } + + // The interpolation of a <ratio> is defined by converting each <ratio> to a number by + // dividing the first value by the second (so a ratio of 3 / 2 would become 1.5), taking + // the logarithm of that result (so the 1.5 would become approximately 0.176), then + // interpolating those values. + // + // The result during the interpolation is converted back to a <ratio> by inverting the + // logarithm, then interpreting the result as a <ratio> with the result as the first value + // and 1 as the second value. + let start = self.to_f32().ln(); + let end = other.to_f32().ln(); + let e = std::f32::consts::E; + let result = e.powf(start.animate(&end, procedure)?); + // The range of the result is [0, inf), based on the easing function. + if result.is_zero() || result.is_infinite() { + return Err(()); + } + Ok(Ratio::new(result, 1.0f32)) + } +} + +impl ComputeSquaredDistance for Ratio { + fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { + if self.is_degenerate() || other.is_degenerate() { + return Err(()); + } + // Use the distance of their logarithm values. (This is used by testing, so don't need to + // care about the base. Here we use the same base as that in animate().) + self.to_f32() + .ln() + .compute_squared_distance(&other.to_f32().ln()) + } +} + +impl Zero for Ratio { + fn zero() -> Self { + Self::new(Zero::zero(), One::one()) + } + + fn is_zero(&self) -> bool { + self.0.is_zero() + } +} + +impl Ratio { + /// Returns a new Ratio. + #[inline] + pub fn new(a: f32, b: f32) -> Self { + GenericRatio(a.into(), b.into()) + } + + /// Returns the used value. A ratio of 0/0 behaves as the ratio 1/0. + /// https://drafts.csswg.org/css-values-4/#ratios + pub fn used_value(self) -> Self { + if self.0.is_zero() && self.1.is_zero() { + Ratio::new(One::one(), Zero::zero()) + } else { + self + } + } + + /// Returns true if this is a degenerate ratio. + /// https://drafts.csswg.org/css-values/#degenerate-ratio + #[inline] + pub fn is_degenerate(&self) -> bool { + self.0.is_zero() || self.1.is_zero() + } + + /// Returns the f32 value by dividing the first value by the second one. + #[inline] + fn to_f32(&self) -> f32 { + debug_assert!(!self.is_degenerate()); + (self.0).0 / (self.1).0 + } +} diff --git a/servo/components/style/values/computed/rect.rs b/servo/components/style/values/computed/rect.rs new file mode 100644 index 0000000000..ec44360fc8 --- /dev/null +++ b/servo/components/style/values/computed/rect.rs @@ -0,0 +1,11 @@ +/* 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 types for CSS borders. + +use crate::values::computed::length::NonNegativeLengthOrNumber; +use crate::values::generics::rect::Rect; + +/// A specified rectangle made of four `<length-or-number>` values. +pub type NonNegativeLengthOrNumberRect = Rect<NonNegativeLengthOrNumber>; diff --git a/servo/components/style/values/computed/resolution.rs b/servo/components/style/values/computed/resolution.rs new file mode 100644 index 0000000000..54b03bd475 --- /dev/null +++ b/servo/components/style/values/computed/resolution.rs @@ -0,0 +1,56 @@ +/* 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/. */ + +//! Resolution values: +//! +//! https://drafts.csswg.org/css-values/#resolution + +use crate::values::computed::{Context, ToComputedValue}; +use crate::values::specified; +use crate::values::CSSFloat; +use std::fmt::{self, Write}; +use style_traits::{CssWriter, ToCss}; + +/// A computed `<resolution>`. +#[repr(C)] +#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToResolvedValue, ToShmem)] +pub struct Resolution(CSSFloat); + +impl Resolution { + /// Returns this resolution value as dppx. + #[inline] + pub fn dppx(&self) -> CSSFloat { + self.0 + } + + /// Return a computed `resolution` value from a dppx float value. + #[inline] + pub fn from_dppx(dppx: CSSFloat) -> Self { + Resolution(dppx) + } +} + +impl ToComputedValue for specified::Resolution { + type ComputedValue = Resolution; + + #[inline] + fn to_computed_value(&self, _: &Context) -> Self::ComputedValue { + Resolution(self.dppx()) + } + + #[inline] + fn from_computed_value(computed: &Self::ComputedValue) -> Self { + specified::Resolution::from_dppx(computed.dppx()) + } +} + +impl ToCss for Resolution { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: fmt::Write, + { + self.dppx().to_css(dest)?; + dest.write_str("dppx") + } +} diff --git a/servo/components/style/values/computed/svg.rs b/servo/components/style/values/computed/svg.rs new file mode 100644 index 0000000000..640c3bfda7 --- /dev/null +++ b/servo/components/style/values/computed/svg.rs @@ -0,0 +1,68 @@ +/* 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 types for SVG properties. + +use crate::values::computed::color::Color; +use crate::values::computed::url::ComputedUrl; +use crate::values::computed::{LengthPercentage, NonNegativeLengthPercentage, Opacity}; +use crate::values::generics::svg as generic; +use crate::Zero; + +pub use crate::values::specified::{DProperty, MozContextProperties, SVGPaintOrder}; + +/// Computed SVG Paint value +pub type SVGPaint = generic::GenericSVGPaint<Color, ComputedUrl>; + +/// Computed SVG Paint Kind value +pub type SVGPaintKind = generic::GenericSVGPaintKind<Color, ComputedUrl>; + +impl SVGPaint { + /// Opaque black color + pub fn black() -> Self { + SVGPaint { + kind: generic::SVGPaintKind::Color(Color::black()), + fallback: generic::SVGPaintFallback::Unset, + } + } +} + +/// <length> | <percentage> | <number> | context-value +pub type SVGLength = generic::GenericSVGLength<LengthPercentage>; + +impl SVGLength { + /// `0px` + pub fn zero() -> Self { + generic::SVGLength::LengthPercentage(LengthPercentage::zero()) + } +} + +/// An non-negative wrapper of SVGLength. +pub type SVGWidth = generic::GenericSVGLength<NonNegativeLengthPercentage>; + +impl SVGWidth { + /// `1px`. + pub fn one() -> Self { + use crate::values::generics::NonNegative; + generic::SVGLength::LengthPercentage(NonNegative(LengthPercentage::one())) + } +} + +/// [ <length> | <percentage> | <number> ]# | context-value +pub type SVGStrokeDashArray = generic::GenericSVGStrokeDashArray<NonNegativeLengthPercentage>; + +impl Default for SVGStrokeDashArray { + fn default() -> Self { + generic::SVGStrokeDashArray::Values(Default::default()) + } +} + +/// <opacity-value> | context-fill-opacity | context-stroke-opacity +pub type SVGOpacity = generic::GenericSVGOpacity<Opacity>; + +impl Default for SVGOpacity { + fn default() -> Self { + generic::SVGOpacity::Opacity(1.) + } +} diff --git a/servo/components/style/values/computed/table.rs b/servo/components/style/values/computed/table.rs new file mode 100644 index 0000000000..47109e20ec --- /dev/null +++ b/servo/components/style/values/computed/table.rs @@ -0,0 +1,7 @@ +/* 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 types for CSS values related to tables. + +pub use super::specified::table::CaptionSide; diff --git a/servo/components/style/values/computed/text.rs b/servo/components/style/values/computed/text.rs new file mode 100644 index 0000000000..5993c078dc --- /dev/null +++ b/servo/components/style/values/computed/text.rs @@ -0,0 +1,254 @@ +/* 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 types for text properties. + +#[cfg(feature = "servo")] +use crate::properties::StyleBuilder; +use crate::values::computed::length::{Length, LengthPercentage}; +use crate::values::computed::{Context, NonNegativeLength, NonNegativeNumber, ToComputedValue}; +use crate::values::generics::text::InitialLetter as GenericInitialLetter; +use crate::values::generics::text::LineHeight as GenericLineHeight; +use crate::values::generics::text::{GenericTextDecorationLength, Spacing}; +use crate::values::resolved::{Context as ResolvedContext, ToResolvedValue}; +use crate::values::specified::text::{self as specified, TextOverflowSide}; +use crate::values::specified::text::{TextEmphasisFillMode, TextEmphasisShapeKeyword}; +use crate::values::{CSSFloat, CSSInteger}; +use crate::Zero; +use std::fmt::{self, Write}; +use style_traits::{CssWriter, ToCss}; + +pub use crate::values::specified::text::{ + MozControlCharacterVisibility, TextAlignLast, TextUnderlinePosition, +}; +pub use crate::values::specified::HyphenateCharacter; +pub use crate::values::specified::{LineBreak, OverflowWrap, RubyPosition, WordBreak}; +pub use crate::values::specified::{TextDecorationLine, TextEmphasisPosition}; +pub use crate::values::specified::{TextDecorationSkipInk, TextJustify, TextTransform}; + +/// A computed value for the `initial-letter` property. +pub type InitialLetter = GenericInitialLetter<CSSFloat, CSSInteger>; + +/// Implements type for `text-decoration-thickness` property. +pub type TextDecorationLength = GenericTextDecorationLength<LengthPercentage>; + +/// The computed value of `text-align`. +pub type TextAlign = specified::TextAlignKeyword; + +/// A computed value for the `letter-spacing` property. +#[repr(transparent)] +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + MallocSizeOf, + PartialEq, + ToAnimatedValue, + ToAnimatedZero, + ToResolvedValue, +)] +pub struct LetterSpacing(pub Length); + +impl LetterSpacing { + /// Return the `normal` computed value, which is just zero. + #[inline] + pub fn normal() -> Self { + LetterSpacing(Length::zero()) + } +} + +impl ToCss for LetterSpacing { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + // https://drafts.csswg.org/css-text/#propdef-letter-spacing + // + // For legacy reasons, a computed letter-spacing of zero yields a + // resolved value (getComputedStyle() return value) of normal. + if self.0.is_zero() { + return dest.write_str("normal"); + } + self.0.to_css(dest) + } +} + +impl ToComputedValue for specified::LetterSpacing { + type ComputedValue = LetterSpacing; + fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { + match *self { + Spacing::Normal => LetterSpacing(Length::zero()), + Spacing::Value(ref v) => LetterSpacing(v.to_computed_value(context)), + } + } + + fn from_computed_value(computed: &Self::ComputedValue) -> Self { + if computed.0.is_zero() { + return Spacing::Normal; + } + Spacing::Value(ToComputedValue::from_computed_value(&computed.0)) + } +} + +/// A computed value for the `word-spacing` property. +pub type WordSpacing = LengthPercentage; + +impl ToComputedValue for specified::WordSpacing { + type ComputedValue = WordSpacing; + + fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { + match *self { + Spacing::Normal => LengthPercentage::zero(), + Spacing::Value(ref v) => v.to_computed_value(context), + } + } + + fn from_computed_value(computed: &Self::ComputedValue) -> Self { + Spacing::Value(ToComputedValue::from_computed_value(computed)) + } +} + +/// A computed value for the `line-height` property. +pub type LineHeight = GenericLineHeight<NonNegativeNumber, NonNegativeLength>; + +impl ToResolvedValue for LineHeight { + type ResolvedValue = Self; + + fn to_resolved_value(self, context: &ResolvedContext) -> Self::ResolvedValue { + // Resolve <number> to an absolute <length> based on font size. + if matches!(self, Self::Normal | Self::MozBlockHeight) { + return self; + } + let wm = context.style.writing_mode; + let vertical = wm.is_vertical() && !wm.is_sideways(); + Self::Length(context.device.calc_line_height( + &self, + vertical, + context.style.get_font(), + Some(context.element_info.element), + )) + } + + #[inline] + fn from_resolved_value(value: Self::ResolvedValue) -> Self { + value + } +} + +impl WordSpacing { + /// Return the `normal` computed value, which is just zero. + #[inline] + pub fn normal() -> Self { + LengthPercentage::zero() + } +} + +#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToResolvedValue)] +#[repr(C)] +/// text-overflow. +/// When the specified value only has one side, that's the "second" +/// side, and the sides are logical, so "second" means "end". The +/// start side is Clip in that case. +/// +/// When the specified value has two sides, those are our "first" +/// and "second" sides, and they are physical sides ("left" and +/// "right"). +pub struct TextOverflow { + /// First side + pub first: TextOverflowSide, + /// Second side + pub second: TextOverflowSide, + /// True if the specified value only has one side. + pub sides_are_logical: bool, +} + +impl TextOverflow { + /// Returns the initial `text-overflow` value + pub fn get_initial_value() -> TextOverflow { + TextOverflow { + first: TextOverflowSide::Clip, + second: TextOverflowSide::Clip, + sides_are_logical: true, + } + } +} + +impl ToCss for TextOverflow { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + if self.sides_are_logical { + debug_assert_eq!(self.first, TextOverflowSide::Clip); + self.second.to_css(dest)?; + } else { + self.first.to_css(dest)?; + dest.write_char(' ')?; + self.second.to_css(dest)?; + } + Ok(()) + } +} + +/// A struct that represents the _used_ value of the text-decoration property. +/// +/// FIXME(emilio): This is done at style resolution time, though probably should +/// be done at layout time, otherwise we need to account for display: contents +/// and similar stuff when we implement it. +/// +/// FIXME(emilio): Also, should be just a bitfield instead of three bytes. +#[derive(Clone, Copy, Debug, Default, MallocSizeOf, PartialEq, ToResolvedValue)] +pub struct TextDecorationsInEffect { + /// Whether an underline is in effect. + pub underline: bool, + /// Whether an overline decoration is in effect. + pub overline: bool, + /// Whether a line-through style is in effect. + pub line_through: bool, +} + +impl TextDecorationsInEffect { + /// Computes the text-decorations in effect for a given style. + #[cfg(feature = "servo")] + pub fn from_style(style: &StyleBuilder) -> Self { + // Start with no declarations if this is an atomic inline-level box; + // otherwise, start with the declarations in effect and add in the text + // decorations that this block specifies. + let mut result = if style.get_box().clone_display().is_atomic_inline_level() { + Self::default() + } else { + style + .get_parent_inherited_text() + .text_decorations_in_effect + .clone() + }; + + let line = style.get_text().clone_text_decoration_line(); + + result.underline |= line.contains(TextDecorationLine::UNDERLINE); + result.overline |= line.contains(TextDecorationLine::OVERLINE); + result.line_through |= line.contains(TextDecorationLine::LINE_THROUGH); + + result + } +} + +/// Computed value for the text-emphasis-style property +#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss, ToResolvedValue)] +#[allow(missing_docs)] +#[repr(C, u8)] +pub enum TextEmphasisStyle { + /// [ <fill> || <shape> ] + Keyword { + #[css(skip_if = "TextEmphasisFillMode::is_filled")] + fill: TextEmphasisFillMode, + shape: TextEmphasisShapeKeyword, + }, + /// `none` + None, + /// `<string>` (of which only the first grapheme cluster will be used). + String(crate::OwnedStr), +} diff --git a/servo/components/style/values/computed/time.rs b/servo/components/style/values/computed/time.rs new file mode 100644 index 0000000000..f542333a82 --- /dev/null +++ b/servo/components/style/values/computed/time.rs @@ -0,0 +1,45 @@ +/* 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 time values. + +use crate::values::CSSFloat; +use std::fmt::{self, Write}; +use style_traits::{CssWriter, ToCss}; + +/// A computed `<time>` value. +#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, PartialOrd, ToResolvedValue)] +#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] +#[repr(C)] +pub struct Time { + seconds: CSSFloat, +} + +impl Time { + /// Creates a time value from a seconds amount. + pub fn from_seconds(seconds: CSSFloat) -> Self { + Time { seconds } + } + + /// Returns `0s`. + pub fn zero() -> Self { + Self::from_seconds(0.0) + } + + /// Returns the amount of seconds this time represents. + #[inline] + pub fn seconds(&self) -> CSSFloat { + self.seconds + } +} + +impl ToCss for Time { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + self.seconds().to_css(dest)?; + dest.write_char('s') + } +} diff --git a/servo/components/style/values/computed/transform.rs b/servo/components/style/values/computed/transform.rs new file mode 100644 index 0000000000..d70349ee0f --- /dev/null +++ b/servo/components/style/values/computed/transform.rs @@ -0,0 +1,558 @@ +/* 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 types for CSS values that are related to transformations. + +use super::CSSFloat; +use crate::values::animated::transform::{Perspective, Scale3D, Translate3D}; +use crate::values::animated::ToAnimatedZero; +use crate::values::computed::{Angle, Integer, Length, LengthPercentage, Number, Percentage}; +use crate::values::generics::transform as generic; +use crate::Zero; +use euclid::default::{Transform3D, Vector3D}; + +pub use crate::values::generics::transform::TransformStyle; + +/// A single operation in a computed CSS `transform` +pub type TransformOperation = + generic::GenericTransformOperation<Angle, Number, Length, Integer, LengthPercentage>; +/// A computed CSS `transform` +pub type Transform = generic::GenericTransform<TransformOperation>; + +/// The computed value of a CSS `<transform-origin>` +pub type TransformOrigin = + generic::GenericTransformOrigin<LengthPercentage, LengthPercentage, Length>; + +/// The computed value of the `perspective()` transform function. +pub type PerspectiveFunction = generic::PerspectiveFunction<Length>; + +/// A vector to represent the direction vector (rotate axis) for Rotate3D. +pub type DirectionVector = Vector3D<CSSFloat>; + +impl TransformOrigin { + /// Returns the initial computed value for `transform-origin`. + #[inline] + pub fn initial_value() -> Self { + Self::new( + LengthPercentage::new_percent(Percentage(0.5)), + LengthPercentage::new_percent(Percentage(0.5)), + Length::new(0.), + ) + } +} + +/// computed value of matrix3d() +pub type Matrix3D = generic::Matrix3D<Number>; + +/// computed value of matrix() +pub type Matrix = generic::Matrix<Number>; + +// we rustfmt_skip here because we want the matrices to look like +// matrices instead of being split across lines +#[cfg_attr(rustfmt, rustfmt_skip)] +impl Matrix3D { + /// Get an identity matrix + #[inline] + pub fn identity() -> Self { + Self { + m11: 1.0, m12: 0.0, m13: 0.0, m14: 0.0, + m21: 0.0, m22: 1.0, m23: 0.0, m24: 0.0, + m31: 0.0, m32: 0.0, m33: 1.0, m34: 0.0, + m41: 0., m42: 0., m43: 0., m44: 1.0 + } + } + + /// Convert to a 2D Matrix + #[inline] + pub fn into_2d(self) -> Result<Matrix, ()> { + if self.m13 == 0. && self.m23 == 0. && + self.m31 == 0. && self.m32 == 0. && + self.m33 == 1. && self.m34 == 0. && + self.m14 == 0. && self.m24 == 0. && + self.m43 == 0. && self.m44 == 1. { + Ok(Matrix { + a: self.m11, c: self.m21, e: self.m41, + b: self.m12, d: self.m22, f: self.m42, + }) + } else { + Err(()) + } + } + + /// Return true if this has 3D components. + #[inline] + pub fn is_3d(&self) -> bool { + self.m13 != 0.0 || self.m14 != 0.0 || + self.m23 != 0.0 || self.m24 != 0.0 || + self.m31 != 0.0 || self.m32 != 0.0 || + self.m33 != 1.0 || self.m34 != 0.0 || + self.m43 != 0.0 || self.m44 != 1.0 + } + + /// Return determinant value. + #[inline] + pub fn determinant(&self) -> CSSFloat { + self.m14 * self.m23 * self.m32 * self.m41 - + self.m13 * self.m24 * self.m32 * self.m41 - + self.m14 * self.m22 * self.m33 * self.m41 + + self.m12 * self.m24 * self.m33 * self.m41 + + self.m13 * self.m22 * self.m34 * self.m41 - + self.m12 * self.m23 * self.m34 * self.m41 - + self.m14 * self.m23 * self.m31 * self.m42 + + self.m13 * self.m24 * self.m31 * self.m42 + + self.m14 * self.m21 * self.m33 * self.m42 - + self.m11 * self.m24 * self.m33 * self.m42 - + self.m13 * self.m21 * self.m34 * self.m42 + + self.m11 * self.m23 * self.m34 * self.m42 + + self.m14 * self.m22 * self.m31 * self.m43 - + self.m12 * self.m24 * self.m31 * self.m43 - + self.m14 * self.m21 * self.m32 * self.m43 + + self.m11 * self.m24 * self.m32 * self.m43 + + self.m12 * self.m21 * self.m34 * self.m43 - + self.m11 * self.m22 * self.m34 * self.m43 - + self.m13 * self.m22 * self.m31 * self.m44 + + self.m12 * self.m23 * self.m31 * self.m44 + + self.m13 * self.m21 * self.m32 * self.m44 - + self.m11 * self.m23 * self.m32 * self.m44 - + self.m12 * self.m21 * self.m33 * self.m44 + + self.m11 * self.m22 * self.m33 * self.m44 + } + + /// Transpose a matrix. + #[inline] + pub fn transpose(&self) -> Self { + Self { + m11: self.m11, m12: self.m21, m13: self.m31, m14: self.m41, + m21: self.m12, m22: self.m22, m23: self.m32, m24: self.m42, + m31: self.m13, m32: self.m23, m33: self.m33, m34: self.m43, + m41: self.m14, m42: self.m24, m43: self.m34, m44: self.m44, + } + } + + /// Return inverse matrix. + pub fn inverse(&self) -> Result<Matrix3D, ()> { + let mut det = self.determinant(); + + if det == 0.0 { + return Err(()); + } + + det = 1.0 / det; + let x = Matrix3D { + m11: det * + (self.m23 * self.m34 * self.m42 - self.m24 * self.m33 * self.m42 + + self.m24 * self.m32 * self.m43 - self.m22 * self.m34 * self.m43 - + self.m23 * self.m32 * self.m44 + self.m22 * self.m33 * self.m44), + m12: det * + (self.m14 * self.m33 * self.m42 - self.m13 * self.m34 * self.m42 - + self.m14 * self.m32 * self.m43 + self.m12 * self.m34 * self.m43 + + self.m13 * self.m32 * self.m44 - self.m12 * self.m33 * self.m44), + m13: det * + (self.m13 * self.m24 * self.m42 - self.m14 * self.m23 * self.m42 + + self.m14 * self.m22 * self.m43 - self.m12 * self.m24 * self.m43 - + self.m13 * self.m22 * self.m44 + self.m12 * self.m23 * self.m44), + m14: det * + (self.m14 * self.m23 * self.m32 - self.m13 * self.m24 * self.m32 - + self.m14 * self.m22 * self.m33 + self.m12 * self.m24 * self.m33 + + self.m13 * self.m22 * self.m34 - self.m12 * self.m23 * self.m34), + m21: det * + (self.m24 * self.m33 * self.m41 - self.m23 * self.m34 * self.m41 - + self.m24 * self.m31 * self.m43 + self.m21 * self.m34 * self.m43 + + self.m23 * self.m31 * self.m44 - self.m21 * self.m33 * self.m44), + m22: det * + (self.m13 * self.m34 * self.m41 - self.m14 * self.m33 * self.m41 + + self.m14 * self.m31 * self.m43 - self.m11 * self.m34 * self.m43 - + self.m13 * self.m31 * self.m44 + self.m11 * self.m33 * self.m44), + m23: det * + (self.m14 * self.m23 * self.m41 - self.m13 * self.m24 * self.m41 - + self.m14 * self.m21 * self.m43 + self.m11 * self.m24 * self.m43 + + self.m13 * self.m21 * self.m44 - self.m11 * self.m23 * self.m44), + m24: det * + (self.m13 * self.m24 * self.m31 - self.m14 * self.m23 * self.m31 + + self.m14 * self.m21 * self.m33 - self.m11 * self.m24 * self.m33 - + self.m13 * self.m21 * self.m34 + self.m11 * self.m23 * self.m34), + m31: det * + (self.m22 * self.m34 * self.m41 - self.m24 * self.m32 * self.m41 + + self.m24 * self.m31 * self.m42 - self.m21 * self.m34 * self.m42 - + self.m22 * self.m31 * self.m44 + self.m21 * self.m32 * self.m44), + m32: det * + (self.m14 * self.m32 * self.m41 - self.m12 * self.m34 * self.m41 - + self.m14 * self.m31 * self.m42 + self.m11 * self.m34 * self.m42 + + self.m12 * self.m31 * self.m44 - self.m11 * self.m32 * self.m44), + m33: det * + (self.m12 * self.m24 * self.m41 - self.m14 * self.m22 * self.m41 + + self.m14 * self.m21 * self.m42 - self.m11 * self.m24 * self.m42 - + self.m12 * self.m21 * self.m44 + self.m11 * self.m22 * self.m44), + m34: det * + (self.m14 * self.m22 * self.m31 - self.m12 * self.m24 * self.m31 - + self.m14 * self.m21 * self.m32 + self.m11 * self.m24 * self.m32 + + self.m12 * self.m21 * self.m34 - self.m11 * self.m22 * self.m34), + m41: det * + (self.m23 * self.m32 * self.m41 - self.m22 * self.m33 * self.m41 - + self.m23 * self.m31 * self.m42 + self.m21 * self.m33 * self.m42 + + self.m22 * self.m31 * self.m43 - self.m21 * self.m32 * self.m43), + m42: det * + (self.m12 * self.m33 * self.m41 - self.m13 * self.m32 * self.m41 + + self.m13 * self.m31 * self.m42 - self.m11 * self.m33 * self.m42 - + self.m12 * self.m31 * self.m43 + self.m11 * self.m32 * self.m43), + m43: det * + (self.m13 * self.m22 * self.m41 - self.m12 * self.m23 * self.m41 - + self.m13 * self.m21 * self.m42 + self.m11 * self.m23 * self.m42 + + self.m12 * self.m21 * self.m43 - self.m11 * self.m22 * self.m43), + m44: det * + (self.m12 * self.m23 * self.m31 - self.m13 * self.m22 * self.m31 + + self.m13 * self.m21 * self.m32 - self.m11 * self.m23 * self.m32 - + self.m12 * self.m21 * self.m33 + self.m11 * self.m22 * self.m33), + }; + + Ok(x) + } + + /// Multiply `pin * self`. + #[inline] + pub fn pre_mul_point4(&self, pin: &[f32; 4]) -> [f32; 4] { + [ + pin[0] * self.m11 + pin[1] * self.m21 + pin[2] * self.m31 + pin[3] * self.m41, + pin[0] * self.m12 + pin[1] * self.m22 + pin[2] * self.m32 + pin[3] * self.m42, + pin[0] * self.m13 + pin[1] * self.m23 + pin[2] * self.m33 + pin[3] * self.m43, + pin[0] * self.m14 + pin[1] * self.m24 + pin[2] * self.m34 + pin[3] * self.m44, + ] + } + + /// Return the multiplication of two 4x4 matrices. + #[inline] + pub fn multiply(&self, other: &Self) -> Self { + Matrix3D { + m11: self.m11 * other.m11 + self.m12 * other.m21 + + self.m13 * other.m31 + self.m14 * other.m41, + m12: self.m11 * other.m12 + self.m12 * other.m22 + + self.m13 * other.m32 + self.m14 * other.m42, + m13: self.m11 * other.m13 + self.m12 * other.m23 + + self.m13 * other.m33 + self.m14 * other.m43, + m14: self.m11 * other.m14 + self.m12 * other.m24 + + self.m13 * other.m34 + self.m14 * other.m44, + m21: self.m21 * other.m11 + self.m22 * other.m21 + + self.m23 * other.m31 + self.m24 * other.m41, + m22: self.m21 * other.m12 + self.m22 * other.m22 + + self.m23 * other.m32 + self.m24 * other.m42, + m23: self.m21 * other.m13 + self.m22 * other.m23 + + self.m23 * other.m33 + self.m24 * other.m43, + m24: self.m21 * other.m14 + self.m22 * other.m24 + + self.m23 * other.m34 + self.m24 * other.m44, + m31: self.m31 * other.m11 + self.m32 * other.m21 + + self.m33 * other.m31 + self.m34 * other.m41, + m32: self.m31 * other.m12 + self.m32 * other.m22 + + self.m33 * other.m32 + self.m34 * other.m42, + m33: self.m31 * other.m13 + self.m32 * other.m23 + + self.m33 * other.m33 + self.m34 * other.m43, + m34: self.m31 * other.m14 + self.m32 * other.m24 + + self.m33 * other.m34 + self.m34 * other.m44, + m41: self.m41 * other.m11 + self.m42 * other.m21 + + self.m43 * other.m31 + self.m44 * other.m41, + m42: self.m41 * other.m12 + self.m42 * other.m22 + + self.m43 * other.m32 + self.m44 * other.m42, + m43: self.m41 * other.m13 + self.m42 * other.m23 + + self.m43 * other.m33 + self.m44 * other.m43, + m44: self.m41 * other.m14 + self.m42 * other.m24 + + self.m43 * other.m34 + self.m44 * other.m44, + } + } + + /// Scale the matrix by a factor. + #[inline] + pub fn scale_by_factor(&mut self, scaling_factor: CSSFloat) { + self.m11 *= scaling_factor; + self.m12 *= scaling_factor; + self.m13 *= scaling_factor; + self.m14 *= scaling_factor; + self.m21 *= scaling_factor; + self.m22 *= scaling_factor; + self.m23 *= scaling_factor; + self.m24 *= scaling_factor; + self.m31 *= scaling_factor; + self.m32 *= scaling_factor; + self.m33 *= scaling_factor; + self.m34 *= scaling_factor; + self.m41 *= scaling_factor; + self.m42 *= scaling_factor; + self.m43 *= scaling_factor; + self.m44 *= scaling_factor; + } + + /// Return the matrix 3x3 part (top-left corner). + /// This is used by retrieving the scale and shear factors + /// during decomposing a 3d matrix. + #[inline] + pub fn get_matrix_3x3_part(&self) -> [[f32; 3]; 3] { + [ + [ self.m11, self.m12, self.m13 ], + [ self.m21, self.m22, self.m23 ], + [ self.m31, self.m32, self.m33 ], + ] + } + + /// Set perspective on the matrix. + #[inline] + pub fn set_perspective(&mut self, perspective: &Perspective) { + self.m14 = perspective.0; + self.m24 = perspective.1; + self.m34 = perspective.2; + self.m44 = perspective.3; + } + + /// Apply translate on the matrix. + #[inline] + pub fn apply_translate(&mut self, translate: &Translate3D) { + self.m41 += translate.0 * self.m11 + translate.1 * self.m21 + translate.2 * self.m31; + self.m42 += translate.0 * self.m12 + translate.1 * self.m22 + translate.2 * self.m32; + self.m43 += translate.0 * self.m13 + translate.1 * self.m23 + translate.2 * self.m33; + self.m44 += translate.0 * self.m14 + translate.1 * self.m24 + translate.2 * self.m34; + } + + /// Apply scale on the matrix. + #[inline] + pub fn apply_scale(&mut self, scale: &Scale3D) { + self.m11 *= scale.0; + self.m12 *= scale.0; + self.m13 *= scale.0; + self.m14 *= scale.0; + self.m21 *= scale.1; + self.m22 *= scale.1; + self.m23 *= scale.1; + self.m24 *= scale.1; + self.m31 *= scale.2; + self.m32 *= scale.2; + self.m33 *= scale.2; + self.m34 *= scale.2; + } +} + +#[cfg_attr(rustfmt, rustfmt_skip)] +impl Matrix { + #[inline] + /// Get an identity matrix + pub fn identity() -> Self { + Self { + a: 1., c: 0., /* 0 0*/ + b: 0., d: 1., /* 0 0*/ + /* 0 0 1 0 */ + e: 0., f: 0., /* 0 1 */ + } + } +} + +#[cfg_attr(rustfmt, rustfmt_skip)] +impl From<Matrix> for Matrix3D { + fn from(m: Matrix) -> Self { + Self { + m11: m.a, m12: m.b, m13: 0.0, m14: 0.0, + m21: m.c, m22: m.d, m23: 0.0, m24: 0.0, + m31: 0.0, m32: 0.0, m33: 1.0, m34: 0.0, + m41: m.e, m42: m.f, m43: 0.0, m44: 1.0 + } + } +} + +#[cfg_attr(rustfmt, rustfmt_skip)] +impl From<Transform3D<CSSFloat>> for Matrix3D { + #[inline] + fn from(m: Transform3D<CSSFloat>) -> Self { + Matrix3D { + m11: m.m11, m12: m.m12, m13: m.m13, m14: m.m14, + m21: m.m21, m22: m.m22, m23: m.m23, m24: m.m24, + m31: m.m31, m32: m.m32, m33: m.m33, m34: m.m34, + m41: m.m41, m42: m.m42, m43: m.m43, m44: m.m44 + } + } +} + +impl TransformOperation { + /// Convert to a Translate3D. + /// + /// Must be called on a Translate function + pub fn to_translate_3d(&self) -> Self { + match *self { + generic::TransformOperation::Translate3D(..) => self.clone(), + generic::TransformOperation::TranslateX(ref x) => { + generic::TransformOperation::Translate3D( + x.clone(), + LengthPercentage::zero(), + Length::zero(), + ) + }, + generic::TransformOperation::Translate(ref x, ref y) => { + generic::TransformOperation::Translate3D(x.clone(), y.clone(), Length::zero()) + }, + generic::TransformOperation::TranslateY(ref y) => { + generic::TransformOperation::Translate3D( + LengthPercentage::zero(), + y.clone(), + Length::zero(), + ) + }, + generic::TransformOperation::TranslateZ(ref z) => { + generic::TransformOperation::Translate3D( + LengthPercentage::zero(), + LengthPercentage::zero(), + z.clone(), + ) + }, + _ => unreachable!(), + } + } + + /// Convert to a Rotate3D. + /// + /// Must be called on a Rotate function. + pub fn to_rotate_3d(&self) -> Self { + match *self { + generic::TransformOperation::Rotate3D(..) => self.clone(), + generic::TransformOperation::RotateZ(ref angle) | + generic::TransformOperation::Rotate(ref angle) => { + generic::TransformOperation::Rotate3D(0., 0., 1., angle.clone()) + }, + generic::TransformOperation::RotateX(ref angle) => { + generic::TransformOperation::Rotate3D(1., 0., 0., angle.clone()) + }, + generic::TransformOperation::RotateY(ref angle) => { + generic::TransformOperation::Rotate3D(0., 1., 0., angle.clone()) + }, + _ => unreachable!(), + } + } + + /// Convert to a Scale3D. + /// + /// Must be called on a Scale function + pub fn to_scale_3d(&self) -> Self { + match *self { + generic::TransformOperation::Scale3D(..) => self.clone(), + generic::TransformOperation::Scale(x, y) => { + generic::TransformOperation::Scale3D(x, y, 1.) + }, + generic::TransformOperation::ScaleX(x) => { + generic::TransformOperation::Scale3D(x, 1., 1.) + }, + generic::TransformOperation::ScaleY(y) => { + generic::TransformOperation::Scale3D(1., y, 1.) + }, + generic::TransformOperation::ScaleZ(z) => { + generic::TransformOperation::Scale3D(1., 1., z) + }, + _ => unreachable!(), + } + } +} + +/// Build an equivalent 'identity transform function list' based +/// on an existing transform list. +/// http://dev.w3.org/csswg/css-transforms/#none-transform-animation +impl ToAnimatedZero for TransformOperation { + fn to_animated_zero(&self) -> Result<Self, ()> { + match *self { + generic::TransformOperation::Matrix3D(..) => { + Ok(generic::TransformOperation::Matrix3D(Matrix3D::identity())) + }, + generic::TransformOperation::Matrix(..) => { + Ok(generic::TransformOperation::Matrix(Matrix::identity())) + }, + generic::TransformOperation::Skew(sx, sy) => Ok(generic::TransformOperation::Skew( + sx.to_animated_zero()?, + sy.to_animated_zero()?, + )), + generic::TransformOperation::SkewX(s) => { + Ok(generic::TransformOperation::SkewX(s.to_animated_zero()?)) + }, + generic::TransformOperation::SkewY(s) => { + Ok(generic::TransformOperation::SkewY(s.to_animated_zero()?)) + }, + generic::TransformOperation::Translate3D(ref tx, ref ty, ref tz) => { + Ok(generic::TransformOperation::Translate3D( + tx.to_animated_zero()?, + ty.to_animated_zero()?, + tz.to_animated_zero()?, + )) + }, + generic::TransformOperation::Translate(ref tx, ref ty) => { + Ok(generic::TransformOperation::Translate( + tx.to_animated_zero()?, + ty.to_animated_zero()?, + )) + }, + generic::TransformOperation::TranslateX(ref t) => Ok( + generic::TransformOperation::TranslateX(t.to_animated_zero()?), + ), + generic::TransformOperation::TranslateY(ref t) => Ok( + generic::TransformOperation::TranslateY(t.to_animated_zero()?), + ), + generic::TransformOperation::TranslateZ(ref t) => Ok( + generic::TransformOperation::TranslateZ(t.to_animated_zero()?), + ), + generic::TransformOperation::Scale3D(..) => { + Ok(generic::TransformOperation::Scale3D(1.0, 1.0, 1.0)) + }, + generic::TransformOperation::Scale(_, _) => { + Ok(generic::TransformOperation::Scale(1.0, 1.0)) + }, + generic::TransformOperation::ScaleX(..) => Ok(generic::TransformOperation::ScaleX(1.0)), + generic::TransformOperation::ScaleY(..) => Ok(generic::TransformOperation::ScaleY(1.0)), + generic::TransformOperation::ScaleZ(..) => Ok(generic::TransformOperation::ScaleZ(1.0)), + generic::TransformOperation::Rotate3D(x, y, z, a) => { + let (x, y, z, _) = generic::get_normalized_vector_and_angle(x, y, z, a); + Ok(generic::TransformOperation::Rotate3D( + x, + y, + z, + Angle::zero(), + )) + }, + generic::TransformOperation::RotateX(_) => { + Ok(generic::TransformOperation::RotateX(Angle::zero())) + }, + generic::TransformOperation::RotateY(_) => { + Ok(generic::TransformOperation::RotateY(Angle::zero())) + }, + generic::TransformOperation::RotateZ(_) => { + Ok(generic::TransformOperation::RotateZ(Angle::zero())) + }, + generic::TransformOperation::Rotate(_) => { + Ok(generic::TransformOperation::Rotate(Angle::zero())) + }, + generic::TransformOperation::Perspective(_) => Ok( + generic::TransformOperation::Perspective(generic::PerspectiveFunction::None), + ), + generic::TransformOperation::AccumulateMatrix { .. } | + generic::TransformOperation::InterpolateMatrix { .. } => { + // AccumulateMatrix/InterpolateMatrix: We do interpolation on + // AccumulateMatrix/InterpolateMatrix by reading it as a ComputedMatrix + // (with layout information), and then do matrix interpolation. + // + // Therefore, we use an identity matrix to represent the identity transform list. + // http://dev.w3.org/csswg/css-transforms/#identity-transform-function + Ok(generic::TransformOperation::Matrix3D(Matrix3D::identity())) + }, + } + } +} + +impl ToAnimatedZero for Transform { + #[inline] + fn to_animated_zero(&self) -> Result<Self, ()> { + Ok(generic::Transform( + self.0 + .iter() + .map(|op| op.to_animated_zero()) + .collect::<Result<crate::OwnedSlice<_>, _>>()?, + )) + } +} + +/// A computed CSS `rotate` +pub type Rotate = generic::GenericRotate<Number, Angle>; + +/// A computed CSS `translate` +pub type Translate = generic::GenericTranslate<LengthPercentage, Length>; + +/// A computed CSS `scale` +pub type Scale = generic::GenericScale<Number>; diff --git a/servo/components/style/values/computed/ui.rs b/servo/components/style/values/computed/ui.rs new file mode 100644 index 0000000000..6fa5137adf --- /dev/null +++ b/servo/components/style/values/computed/ui.rs @@ -0,0 +1,22 @@ +/* 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 for UI properties + +use crate::values::computed::color::Color; +use crate::values::computed::image::Image; +use crate::values::computed::Number; +use crate::values::generics::ui as generics; + +pub use crate::values::specified::ui::CursorKind; +pub use crate::values::specified::ui::{BoolInteger, UserSelect}; + +/// A computed value for the `cursor` property. +pub type Cursor = generics::GenericCursor<CursorImage>; + +/// A computed value for item of `image cursors`. +pub type CursorImage = generics::GenericCursorImage<Image, Number>; + +/// A computed value for `scrollbar-color` property. +pub type ScrollbarColor = generics::GenericScrollbarColor<Color>; diff --git a/servo/components/style/values/computed/url.rs b/servo/components/style/values/computed/url.rs new file mode 100644 index 0000000000..9f0d8f5bb3 --- /dev/null +++ b/servo/components/style/values/computed/url.rs @@ -0,0 +1,15 @@ +/* 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/. */ + +//! Common handling for the computed value CSS url() values. + +use crate::values::generics::url::UrlOrNone as GenericUrlOrNone; + +#[cfg(feature = "gecko")] +pub use crate::gecko::url::{ComputedImageUrl, ComputedUrl}; +#[cfg(feature = "servo")] +pub use crate::servo::url::{ComputedImageUrl, ComputedUrl}; + +/// Computed <url> | <none> +pub type UrlOrNone = GenericUrlOrNone<ComputedUrl>; |