diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /servo/components/style/values/generics | |
parent | Initial commit. (diff) | |
download | firefox-upstream.tar.xz firefox-upstream.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
25 files changed, 6432 insertions, 0 deletions
diff --git a/servo/components/style/values/generics/background.rs b/servo/components/style/values/generics/background.rs new file mode 100644 index 0000000000..d9b6624595 --- /dev/null +++ b/servo/components/style/values/generics/background.rs @@ -0,0 +1,54 @@ +/* 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/. */ + +//! Generic types for CSS values related to backgrounds. + +use crate::values::generics::length::{GenericLengthPercentageOrAuto, LengthPercentageOrAuto}; + +/// A generic value for the `background-size` property. +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToAnimatedValue, + ToAnimatedZero, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C, u8)] +pub enum GenericBackgroundSize<LengthPercent> { + /// `<width> <height>` + ExplicitSize { + /// Explicit width. + width: GenericLengthPercentageOrAuto<LengthPercent>, + /// Explicit height. + #[css(skip_if = "GenericLengthPercentageOrAuto::is_auto")] + height: GenericLengthPercentageOrAuto<LengthPercent>, + }, + /// `cover` + #[animation(error)] + Cover, + /// `contain` + #[animation(error)] + Contain, +} + +pub use self::GenericBackgroundSize as BackgroundSize; + +impl<LengthPercentage> BackgroundSize<LengthPercentage> { + /// Returns `auto auto`. + pub fn auto() -> Self { + GenericBackgroundSize::ExplicitSize { + width: LengthPercentageOrAuto::Auto, + height: LengthPercentageOrAuto::Auto, + } + } +} diff --git a/servo/components/style/values/generics/basic_shape.rs b/servo/components/style/values/generics/basic_shape.rs new file mode 100644 index 0000000000..60619b46e2 --- /dev/null +++ b/servo/components/style/values/generics/basic_shape.rs @@ -0,0 +1,535 @@ +/* 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 [`basic-shape`](https://drafts.csswg.org/css-shapes/#typedef-basic-shape) +//! types that are generic over their `ToCss` implementations. + +use crate::values::animated::{Animate, Procedure, ToAnimatedZero}; +use crate::values::distance::{ComputeSquaredDistance, SquaredDistance}; +use crate::values::generics::border::GenericBorderRadius; +use crate::values::generics::position::GenericPosition; +use crate::values::generics::rect::Rect; +use crate::values::specified::SVGPathData; +use crate::Zero; +use std::fmt::{self, Write}; +use style_traits::{CssWriter, ToCss}; + +/// <https://drafts.fxtf.org/css-masking-1/#typedef-geometry-box> +#[allow(missing_docs)] +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + MallocSizeOf, + PartialEq, + Parse, + SpecifiedValueInfo, + ToAnimatedValue, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(u8)] +pub enum ShapeGeometryBox { + /// Depending on which kind of element this style value applied on, the + /// default value of the reference-box can be different. For an HTML + /// element, the default value of reference-box is border-box; for an SVG + /// element, the default value is fill-box. Since we can not determine the + /// default value at parsing time, we keep this value to make a decision on + /// it. + #[css(skip)] + ElementDependent, + FillBox, + StrokeBox, + ViewBox, + ShapeBox(ShapeBox), +} + +impl Default for ShapeGeometryBox { + fn default() -> Self { + Self::ElementDependent + } +} + +/// https://drafts.csswg.org/css-shapes-1/#typedef-shape-box +#[allow(missing_docs)] +#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] +#[derive( + Animate, + Clone, + Copy, + ComputeSquaredDistance, + Debug, + Eq, + MallocSizeOf, + Parse, + PartialEq, + SpecifiedValueInfo, + ToAnimatedValue, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(u8)] +pub enum ShapeBox { + MarginBox, + BorderBox, + PaddingBox, + ContentBox, +} + +impl Default for ShapeBox { + fn default() -> Self { + ShapeBox::MarginBox + } +} + +/// A value for the `clip-path` property. +#[allow(missing_docs)] +#[animation(no_bound(U))] +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToAnimatedValue, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(u8)] +pub enum GenericClipPath<BasicShape, U> { + #[animation(error)] + None, + #[animation(error)] + Url(U), + #[css(function)] + Path(Path), + Shape( + Box<BasicShape>, + #[css(skip_if = "is_default")] ShapeGeometryBox, + ), + #[animation(error)] + Box(ShapeGeometryBox), +} + +pub use self::GenericClipPath as ClipPath; + +/// A value for the `shape-outside` property. +#[allow(missing_docs)] +#[animation(no_bound(I))] +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToAnimatedValue, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(u8)] +pub enum GenericShapeOutside<BasicShape, I> { + #[animation(error)] + None, + #[animation(error)] + Image(I), + Shape(Box<BasicShape>, #[css(skip_if = "is_default")] ShapeBox), + #[animation(error)] + Box(ShapeBox), +} + +pub use self::GenericShapeOutside as ShapeOutside; + +#[allow(missing_docs)] +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToAnimatedValue, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C, u8)] +pub enum GenericBasicShape<H, V, LengthPercentage, NonNegativeLengthPercentage> { + Inset( + #[css(field_bound)] + #[shmem(field_bound)] + InsetRect<LengthPercentage, NonNegativeLengthPercentage>, + ), + Circle( + #[css(field_bound)] + #[shmem(field_bound)] + Circle<H, V, NonNegativeLengthPercentage>, + ), + Ellipse( + #[css(field_bound)] + #[shmem(field_bound)] + Ellipse<H, V, NonNegativeLengthPercentage>, + ), + Polygon(GenericPolygon<LengthPercentage>), +} + +pub use self::GenericBasicShape as BasicShape; + +/// <https://drafts.csswg.org/css-shapes/#funcdef-inset> +#[allow(missing_docs)] +#[css(function = "inset")] +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToAnimatedValue, + ToComputedValue, + ToResolvedValue, + ToShmem, +)] +#[repr(C)] +pub struct InsetRect<LengthPercentage, NonNegativeLengthPercentage> { + pub rect: Rect<LengthPercentage>, + #[shmem(field_bound)] + pub round: GenericBorderRadius<NonNegativeLengthPercentage>, +} + +/// <https://drafts.csswg.org/css-shapes/#funcdef-circle> +#[allow(missing_docs)] +#[css(function)] +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToAnimatedValue, + ToComputedValue, + ToResolvedValue, + ToShmem, +)] +#[repr(C)] +pub struct Circle<H, V, NonNegativeLengthPercentage> { + pub position: GenericPosition<H, V>, + pub radius: GenericShapeRadius<NonNegativeLengthPercentage>, +} + +/// <https://drafts.csswg.org/css-shapes/#funcdef-ellipse> +#[allow(missing_docs)] +#[css(function)] +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToAnimatedValue, + ToComputedValue, + ToResolvedValue, + ToShmem, +)] +#[repr(C)] +pub struct Ellipse<H, V, NonNegativeLengthPercentage> { + pub position: GenericPosition<H, V>, + pub semiaxis_x: GenericShapeRadius<NonNegativeLengthPercentage>, + pub semiaxis_y: GenericShapeRadius<NonNegativeLengthPercentage>, +} + +/// <https://drafts.csswg.org/css-shapes/#typedef-shape-radius> +#[allow(missing_docs)] +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + MallocSizeOf, + Parse, + PartialEq, + SpecifiedValueInfo, + ToAnimatedValue, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C, u8)] +pub enum GenericShapeRadius<NonNegativeLengthPercentage> { + Length(NonNegativeLengthPercentage), + #[animation(error)] + ClosestSide, + #[animation(error)] + FarthestSide, +} + +pub use self::GenericShapeRadius as ShapeRadius; + +/// A generic type for representing the `polygon()` function +/// +/// <https://drafts.csswg.org/css-shapes/#funcdef-polygon> +#[css(comma, function = "polygon")] +#[derive( + Clone, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToAnimatedValue, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C)] +pub struct GenericPolygon<LengthPercentage> { + /// The filling rule for a polygon. + #[css(skip_if = "is_default")] + pub fill: FillRule, + /// A collection of (x, y) coordinates to draw the polygon. + #[css(iterable)] + pub coordinates: crate::OwnedSlice<PolygonCoord<LengthPercentage>>, +} + +pub use self::GenericPolygon as Polygon; + +/// Coordinates for Polygon. +#[derive( + Clone, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToAnimatedValue, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C)] +pub struct PolygonCoord<LengthPercentage>(pub LengthPercentage, pub LengthPercentage); + +// https://drafts.csswg.org/css-shapes/#typedef-fill-rule +// NOTE: Basic shapes spec says that these are the only two values, however +// https://www.w3.org/TR/SVG/painting.html#FillRuleProperty +// says that it can also be `inherit` +#[allow(missing_docs)] +#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] +#[derive( + Clone, + Copy, + Debug, + Eq, + MallocSizeOf, + Parse, + PartialEq, + SpecifiedValueInfo, + ToAnimatedValue, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(u8)] +pub enum FillRule { + Nonzero, + Evenodd, +} + +/// The path function defined in css-shape-2. +/// +/// https://drafts.csswg.org/css-shapes-2/#funcdef-path +#[css(comma)] +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToAnimatedValue, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C)] +pub struct Path { + /// The filling rule for the svg path. + #[css(skip_if = "is_default")] + #[animation(constant)] + pub fill: FillRule, + /// The svg path data. + pub path: SVGPathData, +} + +impl<B, U> ToAnimatedZero for ClipPath<B, U> { + fn to_animated_zero(&self) -> Result<Self, ()> { + Err(()) + } +} + +impl<B, U> ToAnimatedZero for ShapeOutside<B, U> { + fn to_animated_zero(&self) -> Result<Self, ()> { + Err(()) + } +} + +impl<Length, NonNegativeLength> ToCss for InsetRect<Length, NonNegativeLength> +where + Length: ToCss + PartialEq, + NonNegativeLength: ToCss + PartialEq + Zero, +{ + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + dest.write_str("inset(")?; + self.rect.to_css(dest)?; + if !self.round.is_zero() { + dest.write_str(" round ")?; + self.round.to_css(dest)?; + } + dest.write_str(")") + } +} + +impl<H, V, NonNegativeLengthPercentage> ToCss for Circle<H, V, NonNegativeLengthPercentage> +where + GenericPosition<H, V>: ToCss, + NonNegativeLengthPercentage: ToCss + PartialEq, +{ + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + dest.write_str("circle(")?; + if self.radius != Default::default() { + self.radius.to_css(dest)?; + dest.write_str(" ")?; + } + dest.write_str("at ")?; + self.position.to_css(dest)?; + dest.write_str(")") + } +} + +impl<H, V, NonNegativeLengthPercentage> ToCss for Ellipse<H, V, NonNegativeLengthPercentage> +where + GenericPosition<H, V>: ToCss, + NonNegativeLengthPercentage: ToCss + PartialEq, +{ + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + dest.write_str("ellipse(")?; + if self.semiaxis_x != Default::default() || self.semiaxis_y != Default::default() { + self.semiaxis_x.to_css(dest)?; + dest.write_str(" ")?; + self.semiaxis_y.to_css(dest)?; + dest.write_str(" ")?; + } + dest.write_str("at ")?; + self.position.to_css(dest)?; + dest.write_str(")") + } +} + +impl<L> Default for ShapeRadius<L> { + #[inline] + fn default() -> Self { + ShapeRadius::ClosestSide + } +} + +impl<L> Animate for Polygon<L> +where + L: Animate, +{ + fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { + if self.fill != other.fill { + return Err(()); + } + if self.coordinates.len() != other.coordinates.len() { + return Err(()); + } + let coordinates = self + .coordinates + .iter() + .zip(other.coordinates.iter()) + .map(|(this, other)| { + Ok(PolygonCoord( + this.0.animate(&other.0, procedure)?, + this.1.animate(&other.1, procedure)?, + )) + }) + .collect::<Result<Vec<_>, _>>()? + .into(); + Ok(Polygon { + fill: self.fill, + coordinates, + }) + } +} + +impl<L> ComputeSquaredDistance for Polygon<L> +where + L: ComputeSquaredDistance, +{ + fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { + if self.fill != other.fill { + return Err(()); + } + if self.coordinates.len() != other.coordinates.len() { + return Err(()); + } + self.coordinates + .iter() + .zip(other.coordinates.iter()) + .map(|(this, other)| { + let d1 = this.0.compute_squared_distance(&other.0)?; + let d2 = this.1.compute_squared_distance(&other.1)?; + Ok(d1 + d2) + }) + .sum() + } +} + +impl Default for FillRule { + #[inline] + fn default() -> Self { + FillRule::Nonzero + } +} + +#[inline] +fn is_default<T: Default + PartialEq>(fill: &T) -> bool { + *fill == Default::default() +} diff --git a/servo/components/style/values/generics/border.rs b/servo/components/style/values/generics/border.rs new file mode 100644 index 0000000000..37d91464b0 --- /dev/null +++ b/servo/components/style/values/generics/border.rs @@ -0,0 +1,257 @@ +/* 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/. */ + +//! Generic types for CSS values related to borders. + +use crate::values::generics::rect::Rect; +use crate::values::generics::size::Size2D; +use crate::Zero; +use std::fmt::{self, Write}; +use style_traits::{CssWriter, ToCss}; + +/// A generic value for a single side of a `border-image-width` property. +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + MallocSizeOf, + Parse, + PartialEq, + SpecifiedValueInfo, + ToAnimatedValue, + ToAnimatedZero, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C, u8)] +pub enum GenericBorderImageSideWidth<LP, N> { + /// `<number>` + /// + /// NOTE: Numbers need to be before length-percentagess, in order to parse + /// them first, since `0` should be a number, not the `0px` length. + Number(N), + /// `<length-or-percentage>` + LengthPercentage(LP), + /// `auto` + Auto, +} + +pub use self::GenericBorderImageSideWidth as BorderImageSideWidth; + +/// A generic value for the `border-image-slice` property. +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToAnimatedValue, + ToAnimatedZero, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C)] +pub struct GenericBorderImageSlice<NumberOrPercentage> { + /// The offsets. + #[css(field_bound)] + pub offsets: Rect<NumberOrPercentage>, + /// Whether to fill the middle part. + #[animation(constant)] + #[css(represents_keyword)] + pub fill: bool, +} + +pub use self::GenericBorderImageSlice as BorderImageSlice; + +/// A generic value for the `border-*-radius` longhand properties. +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToAnimatedValue, + ToAnimatedZero, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C)] +pub struct GenericBorderCornerRadius<L>( + #[css(field_bound)] + #[shmem(field_bound)] + pub Size2D<L>, +); + +pub use self::GenericBorderCornerRadius as BorderCornerRadius; + +impl<L> BorderCornerRadius<L> { + /// Trivially create a `BorderCornerRadius`. + pub fn new(w: L, h: L) -> Self { + BorderCornerRadius(Size2D::new(w, h)) + } +} + +impl<L: Zero> Zero for BorderCornerRadius<L> { + fn zero() -> Self { + BorderCornerRadius(Size2D::zero()) + } + + fn is_zero(&self) -> bool { + self.0.is_zero() + } +} + +/// A generic value for the `border-spacing` property. +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToAnimatedValue, + ToAnimatedZero, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(transparent)] +pub struct BorderSpacing<L>( + #[css(field_bound)] + #[shmem(field_bound)] + pub Size2D<L>, +); + +impl<L> BorderSpacing<L> { + /// Trivially create a `BorderCornerRadius`. + pub fn new(w: L, h: L) -> Self { + BorderSpacing(Size2D::new(w, h)) + } +} + +/// A generic value for `border-radius`, `outline-radius` and `inset()`. +/// +/// <https://drafts.csswg.org/css-backgrounds-3/#border-radius> +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToAnimatedValue, + ToComputedValue, + ToResolvedValue, + ToShmem, +)] +#[repr(C)] +pub struct GenericBorderRadius<LengthPercentage> { + /// The top left radius. + #[shmem(field_bound)] + pub top_left: GenericBorderCornerRadius<LengthPercentage>, + /// The top right radius. + pub top_right: GenericBorderCornerRadius<LengthPercentage>, + /// The bottom right radius. + pub bottom_right: GenericBorderCornerRadius<LengthPercentage>, + /// The bottom left radius. + pub bottom_left: GenericBorderCornerRadius<LengthPercentage>, +} + +pub use self::GenericBorderRadius as BorderRadius; + +impl<L> BorderRadius<L> { + /// Returns a new `BorderRadius<L>`. + #[inline] + pub fn new( + tl: BorderCornerRadius<L>, + tr: BorderCornerRadius<L>, + br: BorderCornerRadius<L>, + bl: BorderCornerRadius<L>, + ) -> Self { + BorderRadius { + top_left: tl, + top_right: tr, + bottom_right: br, + bottom_left: bl, + } + } + + /// Serialises two given rects following the syntax of the `border-radius`` + /// property. + pub fn serialize_rects<W>( + widths: Rect<&L>, + heights: Rect<&L>, + dest: &mut CssWriter<W>, + ) -> fmt::Result + where + L: PartialEq + ToCss, + W: Write, + { + widths.to_css(dest)?; + if widths != heights { + dest.write_str(" / ")?; + heights.to_css(dest)?; + } + Ok(()) + } +} + +impl<L: Zero> Zero for BorderRadius<L> { + fn zero() -> Self { + Self::new( + BorderCornerRadius::<L>::zero(), + BorderCornerRadius::<L>::zero(), + BorderCornerRadius::<L>::zero(), + BorderCornerRadius::<L>::zero(), + ) + } + + fn is_zero(&self) -> bool { + self.top_left.is_zero() && + self.top_right.is_zero() && + self.bottom_right.is_zero() && + self.bottom_left.is_zero() + } +} + +impl<L> ToCss for BorderRadius<L> +where + L: PartialEq + ToCss, +{ + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + let BorderRadius { + top_left: BorderCornerRadius(ref tl), + top_right: BorderCornerRadius(ref tr), + bottom_right: BorderCornerRadius(ref br), + bottom_left: BorderCornerRadius(ref bl), + } = *self; + + let widths = Rect::new(&tl.width, &tr.width, &br.width, &bl.width); + let heights = Rect::new(&tl.height, &tr.height, &br.height, &bl.height); + + Self::serialize_rects(widths, heights, dest) + } +} diff --git a/servo/components/style/values/generics/box.rs b/servo/components/style/values/generics/box.rs new file mode 100644 index 0000000000..5d6e8e2503 --- /dev/null +++ b/servo/components/style/values/generics/box.rs @@ -0,0 +1,132 @@ +/* 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/. */ + +//! Generic types for box properties. + +use crate::values::animated::ToAnimatedZero; + +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + FromPrimitive, + MallocSizeOf, + Parse, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(u8)] +#[allow(missing_docs)] +pub enum VerticalAlignKeyword { + Baseline, + Sub, + Super, + Top, + TextTop, + Middle, + Bottom, + TextBottom, + #[cfg(feature = "gecko")] + MozMiddleWithBaseline, +} + +/// A generic value for the `vertical-align` property. +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C, u8)] +pub enum GenericVerticalAlign<LengthPercentage> { + /// One of the vertical-align keywords. + Keyword(VerticalAlignKeyword), + /// `<length-percentage>` + Length(LengthPercentage), +} + +pub use self::GenericVerticalAlign as VerticalAlign; + +impl<L> VerticalAlign<L> { + /// Returns `baseline`. + #[inline] + pub fn baseline() -> Self { + VerticalAlign::Keyword(VerticalAlignKeyword::Baseline) + } +} + +impl<L> ToAnimatedZero for VerticalAlign<L> { + fn to_animated_zero(&self) -> Result<Self, ()> { + Err(()) + } +} + +/// https://drafts.csswg.org/css-animations/#animation-iteration-count +#[derive( + Clone, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +pub enum AnimationIterationCount<Number> { + /// A `<number>` value. + Number(Number), + /// The `infinite` keyword. + Infinite, +} + +/// A generic value for the `perspective` property. +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + MallocSizeOf, + Parse, + PartialEq, + SpecifiedValueInfo, + ToAnimatedValue, + ToAnimatedZero, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C, u8)] +pub enum GenericPerspective<NonNegativeLength> { + /// A non-negative length. + Length(NonNegativeLength), + /// The keyword `none`. + None, +} + +pub use self::GenericPerspective as Perspective; + +impl<L> Perspective<L> { + /// Returns `none`. + #[inline] + pub fn none() -> Self { + Perspective::None + } +} diff --git a/servo/components/style/values/generics/calc.rs b/servo/components/style/values/generics/calc.rs new file mode 100644 index 0000000000..0d791e5d00 --- /dev/null +++ b/servo/components/style/values/generics/calc.rs @@ -0,0 +1,580 @@ +/* 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/. */ + +//! [Calc expressions][calc]. +//! +//! [calc]: https://drafts.csswg.org/css-values/#calc-notation + +use crate::Zero; +use smallvec::SmallVec; +use std::fmt::{self, Write}; +use std::ops::Add; +use std::{cmp, mem}; +use style_traits::{CssWriter, ToCss}; + +/// Whether we're a `min` or `max` function. +#[derive( + Clone, + Copy, + Debug, + Deserialize, + MallocSizeOf, + PartialEq, + Serialize, + ToAnimatedZero, + ToResolvedValue, + ToShmem, +)] +#[repr(u8)] +pub enum MinMaxOp { + /// `min()` + Min, + /// `max()` + Max, +} + +/// This determines the order in which we serialize members of a calc() sum. +/// +/// See https://drafts.csswg.org/css-values-4/#sort-a-calculations-children +#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] +#[allow(missing_docs)] +pub enum SortKey { + Number, + Percentage, + Ch, + Deg, + Em, + Ex, + Px, + Rem, + Sec, + Vh, + Vmax, + Vmin, + Vw, + Other, +} + +/// A generic node in a calc expression. +/// +/// FIXME: This would be much more elegant if we used `Self` in the types below, +/// but we can't because of https://github.com/serde-rs/serde/issues/1565. +/// +/// FIXME: The following annotations are to workaround an LLVM inlining bug, see +/// bug 1631929. +/// +/// cbindgen:destructor-attributes=MOZ_NEVER_INLINE +/// cbindgen:copy-constructor-attributes=MOZ_NEVER_INLINE +/// cbindgen:eq-attributes=MOZ_NEVER_INLINE +#[repr(u8)] +#[derive( + Clone, + Debug, + Deserialize, + MallocSizeOf, + PartialEq, + Serialize, + ToAnimatedZero, + ToResolvedValue, + ToShmem, +)] +pub enum GenericCalcNode<L> { + /// A leaf node. + Leaf(L), + /// A sum node, representing `a + b + c` where a, b, and c are the + /// arguments. + Sum(crate::OwnedSlice<GenericCalcNode<L>>), + /// A `min` or `max` function. + MinMax(crate::OwnedSlice<GenericCalcNode<L>>, MinMaxOp), + /// A `clamp()` function. + Clamp { + /// The minimum value. + min: Box<GenericCalcNode<L>>, + /// The central value. + center: Box<GenericCalcNode<L>>, + /// The maximum value. + max: Box<GenericCalcNode<L>>, + }, +} + +pub use self::GenericCalcNode as CalcNode; + +/// A trait that represents all the stuff a valid leaf of a calc expression. +pub trait CalcNodeLeaf: Clone + Sized + PartialOrd + PartialEq + ToCss { + /// Whether this value is known-negative. + fn is_negative(&self) -> bool; + + /// Tries to merge one sum to another, that is, perform `x` + `y`. + fn try_sum_in_place(&mut self, other: &Self) -> Result<(), ()>; + + /// Multiplies the leaf by a given scalar number. + fn mul_by(&mut self, scalar: f32); + + /// Negates the leaf. + fn negate(&mut self) { + self.mul_by(-1.); + } + + /// Canonicalizes the expression if necessary. + fn simplify(&mut self); + + /// Returns the sort key for simplification. + fn sort_key(&self) -> SortKey; +} + +impl<L: CalcNodeLeaf> CalcNode<L> { + /// Negates the node. + pub fn negate(&mut self) { + self.mul_by(-1.); + } + + fn sort_key(&self) -> SortKey { + match *self { + Self::Leaf(ref l) => l.sort_key(), + _ => SortKey::Other, + } + } + + /// Tries to merge one sum to another, that is, perform `x` + `y`. + fn try_sum_in_place(&mut self, other: &Self) -> Result<(), ()> { + match (self, other) { + (&mut CalcNode::Leaf(ref mut one), &CalcNode::Leaf(ref other)) => { + one.try_sum_in_place(other) + }, + _ => Err(()), + } + } + + /// Convert this `CalcNode` into a `CalcNode` with a different leaf kind. + pub fn map_leaves<O, F>(&self, mut map: F) -> CalcNode<O> + where + O: CalcNodeLeaf, + F: FnMut(&L) -> O, + { + self.map_leaves_internal(&mut map) + } + + fn map_leaves_internal<O, F>(&self, map: &mut F) -> CalcNode<O> + where + O: CalcNodeLeaf, + F: FnMut(&L) -> O, + { + fn map_children<L, O, F>( + children: &[CalcNode<L>], + map: &mut F, + ) -> crate::OwnedSlice<CalcNode<O>> + where + L: CalcNodeLeaf, + O: CalcNodeLeaf, + F: FnMut(&L) -> O, + { + children + .iter() + .map(|c| c.map_leaves_internal(map)) + .collect() + } + + match *self { + Self::Leaf(ref l) => CalcNode::Leaf(map(l)), + Self::Sum(ref c) => CalcNode::Sum(map_children(c, map)), + Self::MinMax(ref c, op) => CalcNode::MinMax(map_children(c, map), op), + Self::Clamp { + ref min, + ref center, + ref max, + } => { + let min = Box::new(min.map_leaves_internal(map)); + let center = Box::new(center.map_leaves_internal(map)); + let max = Box::new(max.map_leaves_internal(map)); + CalcNode::Clamp { min, center, max } + }, + } + } + + /// Resolves the expression returning a value of `O`, given a function to + /// turn a leaf into the relevant value. + pub fn resolve<O>( + &self, + mut leaf_to_output_fn: impl FnMut(&L) -> Result<O, ()>, + ) -> Result<O, ()> + where + O: PartialOrd + PartialEq + Add<Output = O> + Zero, + { + self.resolve_internal(&mut leaf_to_output_fn) + } + + fn resolve_internal<O, F>(&self, leaf_to_output_fn: &mut F) -> Result<O, ()> + where + O: PartialOrd + PartialEq + Add<Output = O> + Zero, + F: FnMut(&L) -> Result<O, ()>, + { + Ok(match *self { + Self::Leaf(ref l) => return leaf_to_output_fn(l), + Self::Sum(ref c) => { + let mut result = Zero::zero(); + for child in &**c { + result = result + child.resolve_internal(leaf_to_output_fn)?; + } + result + }, + Self::MinMax(ref nodes, op) => { + let mut result = nodes[0].resolve_internal(leaf_to_output_fn)?; + for node in nodes.iter().skip(1) { + let candidate = node.resolve_internal(leaf_to_output_fn)?; + let candidate_wins = match op { + MinMaxOp::Min => candidate < result, + MinMaxOp::Max => candidate > result, + }; + if candidate_wins { + result = candidate; + } + } + result + }, + Self::Clamp { + ref min, + ref center, + ref max, + } => { + let min = min.resolve_internal(leaf_to_output_fn)?; + let center = center.resolve_internal(leaf_to_output_fn)?; + let max = max.resolve_internal(leaf_to_output_fn)?; + + let mut result = center; + if result > max { + result = max; + } + if result < min { + result = min + } + result + }, + }) + } + + fn is_negative_leaf(&self) -> bool { + match *self { + Self::Leaf(ref l) => l.is_negative(), + _ => false, + } + } + + /// Multiplies the node by a scalar. + pub fn mul_by(&mut self, scalar: f32) { + match *self { + Self::Leaf(ref mut l) => l.mul_by(scalar), + // Multiplication is distributive across this. + Self::Sum(ref mut children) => { + for node in &mut **children { + node.mul_by(scalar); + } + }, + // This one is a bit trickier. + Self::MinMax(ref mut children, ref mut op) => { + for node in &mut **children { + node.mul_by(scalar); + } + + // For negatives we need to invert the operation. + if scalar < 0. { + *op = match *op { + MinMaxOp::Min => MinMaxOp::Max, + MinMaxOp::Max => MinMaxOp::Min, + } + } + }, + // This one is slightly tricky too. + Self::Clamp { + ref mut min, + ref mut center, + ref mut max, + } => { + min.mul_by(scalar); + center.mul_by(scalar); + max.mul_by(scalar); + // For negatives we need to swap min / max. + if scalar < 0. { + mem::swap(min, max); + } + }, + } + } + + /// Visits all the nodes in this calculation tree recursively, starting by + /// the leaves and bubbling all the way up. + /// + /// This is useful for simplification, but can also be used for validation + /// and such. + pub fn visit_depth_first(&mut self, mut f: impl FnMut(&mut Self)) { + self.visit_depth_first_internal(&mut f); + } + + fn visit_depth_first_internal(&mut self, f: &mut impl FnMut(&mut Self)) { + match *self { + Self::Clamp { + ref mut min, + ref mut center, + ref mut max, + } => { + min.visit_depth_first_internal(f); + center.visit_depth_first_internal(f); + max.visit_depth_first_internal(f); + }, + Self::Sum(ref mut children) | Self::MinMax(ref mut children, _) => { + for child in &mut **children { + child.visit_depth_first_internal(f); + } + }, + Self::Leaf(..) => {}, + } + f(self); + } + + /// Simplifies and sorts the calculation of a given node. All the nodes + /// below it should be simplified already, this only takes care of + /// simplifying directly nested nodes. So, probably should always be used in + /// combination with `visit_depth_first()`. + /// + /// This is only needed if it's going to be preserved after parsing (so, for + /// `<length-percentage>`). Otherwise we can just evaluate it using + /// `resolve()`, and we'll come up with a simplified value anyways. + pub fn simplify_and_sort_direct_children(&mut self) { + macro_rules! replace_self_with { + ($slot:expr) => {{ + let dummy = Self::MinMax(Default::default(), MinMaxOp::Max); + let result = mem::replace($slot, dummy); + *self = result; + }}; + } + match *self { + Self::Clamp { + ref mut min, + ref mut center, + ref mut max, + } => { + // NOTE: clamp() is max(min, min(center, max)) + let min_cmp_center = match min.partial_cmp(¢er) { + Some(o) => o, + None => return, + }; + + // So if we can prove that min is more than center, then we won, + // as that's what we should always return. + if matches!(min_cmp_center, cmp::Ordering::Greater) { + return replace_self_with!(&mut **min); + } + + // Otherwise try with max. + let max_cmp_center = match max.partial_cmp(¢er) { + Some(o) => o, + None => return, + }; + + if matches!(max_cmp_center, cmp::Ordering::Less) { + // max is less than center, so we need to return effectively + // `max(min, max)`. + let max_cmp_min = match max.partial_cmp(&min) { + Some(o) => o, + None => { + debug_assert!( + false, + "We compared center with min and max, how are \ + min / max not comparable with each other?" + ); + return; + }, + }; + + if matches!(max_cmp_min, cmp::Ordering::Less) { + return replace_self_with!(&mut **min); + } + + return replace_self_with!(&mut **max); + } + + // Otherwise we're the center node. + return replace_self_with!(&mut **center); + }, + Self::MinMax(ref mut children, op) => { + let winning_order = match op { + MinMaxOp::Min => cmp::Ordering::Less, + MinMaxOp::Max => cmp::Ordering::Greater, + }; + + let mut result = 0; + for i in 1..children.len() { + let o = match children[i].partial_cmp(&children[result]) { + // We can't compare all the children, so we can't + // know which one will actually win. Bail out and + // keep ourselves as a min / max function. + // + // TODO: Maybe we could simplify compatible children, + // see https://github.com/w3c/csswg-drafts/issues/4756 + None => return, + Some(o) => o, + }; + + if o == winning_order { + result = i; + } + } + + replace_self_with!(&mut children[result]); + }, + Self::Sum(ref mut children_slot) => { + let mut sums_to_merge = SmallVec::<[_; 3]>::new(); + let mut extra_kids = 0; + for (i, child) in children_slot.iter().enumerate() { + if let Self::Sum(ref children) = *child { + extra_kids += children.len(); + sums_to_merge.push(i); + } + } + + // If we only have one kid, we've already simplified it, and it + // doesn't really matter whether it's a sum already or not, so + // lift it up and continue. + if children_slot.len() == 1 { + return replace_self_with!(&mut children_slot[0]); + } + + let mut children = mem::replace(children_slot, Default::default()).into_vec(); + + if !sums_to_merge.is_empty() { + children.reserve(extra_kids - sums_to_merge.len()); + // Merge all our nested sums, in reverse order so that the + // list indices are not invalidated. + for i in sums_to_merge.drain(..).rev() { + let kid_children = match children.swap_remove(i) { + Self::Sum(c) => c, + _ => unreachable!(), + }; + + // This would be nicer with + // https://github.com/rust-lang/rust/issues/59878 fixed. + children.extend(kid_children.into_vec()); + } + } + + debug_assert!(children.len() >= 2, "Should still have multiple kids!"); + + // Sort by spec order. + children.sort_unstable_by_key(|c| c.sort_key()); + + // NOTE: if the function returns true, by the docs of dedup_by, + // a is removed. + children.dedup_by(|a, b| b.try_sum_in_place(a).is_ok()); + + if children.len() == 1 { + // If only one children remains, lift it up, and carry on. + replace_self_with!(&mut children[0]); + } else { + // Else put our simplified children back. + *children_slot = children.into_boxed_slice().into(); + } + }, + Self::Leaf(ref mut l) => { + l.simplify(); + }, + } + } + + /// Simplifies and sorts the kids in the whole calculation subtree. + pub fn simplify_and_sort(&mut self) { + self.visit_depth_first(|node| node.simplify_and_sort_direct_children()) + } + + fn to_css_impl<W>(&self, dest: &mut CssWriter<W>, is_outermost: bool) -> fmt::Result + where + W: Write, + { + let write_closing_paren = match *self { + Self::MinMax(_, op) => { + dest.write_str(match op { + MinMaxOp::Max => "max(", + MinMaxOp::Min => "min(", + })?; + true + }, + Self::Clamp { .. } => { + dest.write_str("clamp(")?; + true + }, + _ => { + if is_outermost { + dest.write_str("calc(")?; + } + is_outermost + }, + }; + + match *self { + Self::MinMax(ref children, _) => { + let mut first = true; + for child in &**children { + if !first { + dest.write_str(", ")?; + } + first = false; + child.to_css_impl(dest, false)?; + } + }, + Self::Sum(ref children) => { + let mut first = true; + for child in &**children { + if !first { + if child.is_negative_leaf() { + dest.write_str(" - ")?; + let mut c = child.clone(); + c.negate(); + c.to_css_impl(dest, false)?; + } else { + dest.write_str(" + ")?; + child.to_css_impl(dest, false)?; + } + } else { + first = false; + child.to_css_impl(dest, false)?; + } + } + }, + Self::Clamp { + ref min, + ref center, + ref max, + } => { + min.to_css_impl(dest, false)?; + dest.write_str(", ")?; + center.to_css_impl(dest, false)?; + dest.write_str(", ")?; + max.to_css_impl(dest, false)?; + }, + Self::Leaf(ref l) => l.to_css(dest)?, + } + + if write_closing_paren { + dest.write_str(")")?; + } + Ok(()) + } +} + +impl<L: CalcNodeLeaf> PartialOrd for CalcNode<L> { + fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> { + match (self, other) { + (&CalcNode::Leaf(ref one), &CalcNode::Leaf(ref other)) => one.partial_cmp(other), + _ => None, + } + } +} + +impl<L: CalcNodeLeaf> ToCss for CalcNode<L> { + /// <https://drafts.csswg.org/css-values/#calc-serialize> + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + self.to_css_impl(dest, /* is_outermost = */ true) + } +} diff --git a/servo/components/style/values/generics/color.rs b/servo/components/style/values/generics/color.rs new file mode 100644 index 0000000000..b4f2e7445e --- /dev/null +++ b/servo/components/style/values/generics/color.rs @@ -0,0 +1,112 @@ +/* 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/. */ + +//! Generic types for color properties. + +/// Ratios representing the contribution of color and currentcolor to +/// the final color value. +#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToAnimatedValue, ToShmem)] +#[repr(C)] +pub struct ComplexColorRatios { + /// Numeric color contribution. + pub bg: f32, + /// currentcolor contribution. + pub fg: f32, +} + +impl ComplexColorRatios { + /// Ratios representing a `Numeric` color. + pub const NUMERIC: ComplexColorRatios = ComplexColorRatios { bg: 1., fg: 0. }; + /// Ratios representing the `CurrentColor` color. + pub const CURRENT_COLOR: ComplexColorRatios = ComplexColorRatios { bg: 0., fg: 1. }; +} + +/// This enum represents a combined color from a numeric color and +/// the current foreground color (currentcolor keyword). +#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToAnimatedValue, ToShmem)] +#[repr(C, u8)] +pub enum GenericColor<RGBA> { + /// Numeric RGBA color. + Numeric(RGBA), + + /// The current foreground color. + CurrentColor, + + /// A linear combination of numeric color and currentcolor. + /// The formula is: `color * ratios.bg + currentcolor * ratios.fg`. + Complex { + /// The actual numeric color. + color: RGBA, + /// The ratios of mixing between numeric and currentcolor. + ratios: ComplexColorRatios, + }, +} + +pub use self::GenericColor as Color; + +impl<RGBA> Color<RGBA> { + /// Create a color based upon the specified ratios. + pub fn with_ratios(color: RGBA, ratios: ComplexColorRatios) -> Self { + if ratios == ComplexColorRatios::NUMERIC { + Color::Numeric(color) + } else if ratios == ComplexColorRatios::CURRENT_COLOR { + Color::CurrentColor + } else { + Color::Complex { color, ratios } + } + } + + /// Returns a numeric color representing the given RGBA value. + pub fn rgba(color: RGBA) -> Self { + Color::Numeric(color) + } + + /// Returns a complex color value representing currentcolor. + pub fn currentcolor() -> Self { + Color::CurrentColor + } + + /// Whether it is a numeric color (no currentcolor component). + pub fn is_numeric(&self) -> bool { + matches!(*self, Color::Numeric(..)) + } + + /// Whether it is a currentcolor value (no numeric color component). + pub fn is_currentcolor(&self) -> bool { + matches!(*self, Color::CurrentColor) + } +} + +impl<RGBA> From<RGBA> for Color<RGBA> { + fn from(color: RGBA) -> Self { + Self::rgba(color) + } +} + +/// Either `<color>` or `auto`. +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + MallocSizeOf, + PartialEq, + Parse, + SpecifiedValueInfo, + ToAnimatedValue, + ToAnimatedZero, + ToComputedValue, + ToCss, + ToShmem, +)] +#[repr(C, u8)] +pub enum GenericColorOrAuto<C> { + /// A `<color>`. + Color(C), + /// `auto` + Auto, +} + +pub use self::GenericColorOrAuto as ColorOrAuto; diff --git a/servo/components/style/values/generics/column.rs b/servo/components/style/values/generics/column.rs new file mode 100644 index 0000000000..4b5f0e0399 --- /dev/null +++ b/servo/components/style/values/generics/column.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/. */ + +//! Generic types for the column properties. + +/// A generic type for `column-count` values. +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + MallocSizeOf, + Parse, + PartialEq, + SpecifiedValueInfo, + ToAnimatedValue, + ToAnimatedZero, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +pub enum ColumnCount<PositiveInteger> { + /// A positive integer. + Integer(PositiveInteger), + /// The keyword `auto`. + #[animation(error)] + Auto, +} + +impl<I> ColumnCount<I> { + /// Returns `auto`. + #[inline] + pub fn auto() -> Self { + ColumnCount::Auto + } + + /// Returns whether this value is `auto`. + #[inline] + pub fn is_auto(self) -> bool { + matches!(self, ColumnCount::Auto) + } +} diff --git a/servo/components/style/values/generics/counters.rs b/servo/components/style/values/generics/counters.rs new file mode 100644 index 0000000000..fa7e77a8b5 --- /dev/null +++ b/servo/components/style/values/generics/counters.rs @@ -0,0 +1,221 @@ +/* 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/. */ + +//! Generic types for counters-related CSS values. + +#[cfg(feature = "servo")] +use crate::computed_values::list_style_type::T as ListStyleType; +#[cfg(feature = "gecko")] +use crate::values::generics::CounterStyle; +#[cfg(any(feature = "gecko", feature = "servo-layout-2020"))] +use crate::values::specified::Attr; +use crate::values::CustomIdent; +use std::ops::Deref; + +/// A name / value pair for counters. +#[derive( + Clone, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C)] +pub struct GenericCounterPair<Integer> { + /// The name of the counter. + pub name: CustomIdent, + /// The value of the counter / increment / etc. + pub value: Integer, +} +pub use self::GenericCounterPair as CounterPair; + +/// A generic value for the `counter-increment` property. +#[derive( + Clone, + Debug, + Default, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(transparent)] +pub struct GenericCounterIncrement<I>(pub GenericCounters<I>); +pub use self::GenericCounterIncrement as CounterIncrement; + +impl<I> CounterIncrement<I> { + /// Returns a new value for `counter-increment`. + #[inline] + pub fn new(counters: Vec<CounterPair<I>>) -> Self { + CounterIncrement(Counters(counters.into())) + } +} + +impl<I> Deref for CounterIncrement<I> { + type Target = [CounterPair<I>]; + + #[inline] + fn deref(&self) -> &Self::Target { + &(self.0).0 + } +} + +/// A generic value for the `counter-set` and `counter-reset` properties. +#[derive( + Clone, + Debug, + Default, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(transparent)] +pub struct GenericCounterSetOrReset<I>(pub GenericCounters<I>); +pub use self::GenericCounterSetOrReset as CounterSetOrReset; + +impl<I> CounterSetOrReset<I> { + /// Returns a new value for `counter-set` / `counter-reset`. + #[inline] + pub fn new(counters: Vec<CounterPair<I>>) -> Self { + CounterSetOrReset(Counters(counters.into())) + } +} + +impl<I> Deref for CounterSetOrReset<I> { + type Target = [CounterPair<I>]; + + #[inline] + fn deref(&self) -> &Self::Target { + &(self.0).0 + } +} + +/// A generic value for lists of counters. +/// +/// Keyword `none` is represented by an empty vector. +#[derive( + Clone, + Debug, + Default, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(transparent)] +pub struct GenericCounters<I>( + #[css(iterable, if_empty = "none")] crate::OwnedSlice<GenericCounterPair<I>>, +); +pub use self::GenericCounters as Counters; + +#[cfg(feature = "servo")] +type CounterStyleType = ListStyleType; + +#[cfg(feature = "gecko")] +type CounterStyleType = CounterStyle; + +#[cfg(feature = "servo")] +#[inline] +fn is_decimal(counter_type: &CounterStyleType) -> bool { + *counter_type == ListStyleType::Decimal +} + +#[cfg(feature = "gecko")] +#[inline] +fn is_decimal(counter_type: &CounterStyleType) -> bool { + *counter_type == CounterStyle::decimal() +} + +/// The specified value for the `content` property. +/// +/// https://drafts.csswg.org/css-content/#propdef-content +#[derive( + Clone, Debug, Eq, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue, ToCss, ToShmem, +)] +#[repr(u8)] +pub enum GenericContent<ImageUrl> { + /// `normal` reserved keyword. + Normal, + /// `none` reserved keyword. + None, + /// Content items. + Items(#[css(iterable)] crate::OwnedSlice<GenericContentItem<ImageUrl>>), +} + +pub use self::GenericContent as Content; + +impl<ImageUrl> Content<ImageUrl> { + /// Whether `self` represents list of items. + #[inline] + pub fn is_items(&self) -> bool { + matches!(*self, Self::Items(..)) + } + + /// Set `content` property to `normal`. + #[inline] + pub fn normal() -> Self { + Content::Normal + } +} + +/// Items for the `content` property. +#[derive( + Clone, + Debug, + Eq, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(u8)] +pub enum GenericContentItem<ImageUrl> { + /// Literal string content. + String(crate::OwnedStr), + /// `counter(name, style)`. + #[css(comma, function)] + Counter(CustomIdent, #[css(skip_if = "is_decimal")] CounterStyleType), + /// `counters(name, separator, style)`. + #[css(comma, function)] + Counters( + CustomIdent, + crate::OwnedStr, + #[css(skip_if = "is_decimal")] CounterStyleType, + ), + /// `open-quote`. + OpenQuote, + /// `close-quote`. + CloseQuote, + /// `no-open-quote`. + NoOpenQuote, + /// `no-close-quote`. + NoCloseQuote, + /// `-moz-alt-content`. + #[cfg(feature = "gecko")] + MozAltContent, + /// `attr([namespace? `|`]? ident)` + #[cfg(any(feature = "gecko", feature = "servo-layout-2020"))] + Attr(Attr), + /// `url(url)` + Url(ImageUrl), +} + +pub use self::GenericContentItem as ContentItem; diff --git a/servo/components/style/values/generics/easing.rs b/servo/components/style/values/generics/easing.rs new file mode 100644 index 0000000000..b234b6a54e --- /dev/null +++ b/servo/components/style/values/generics/easing.rs @@ -0,0 +1,135 @@ +/* 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/. */ + +//! Generic types for CSS Easing Functions. +//! https://drafts.csswg.org/css-easing/#timing-functions + +use crate::parser::ParserContext; +use crate::values::CSSFloat; + +/// A generic easing function. +#[derive( + Clone, + Copy, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[value_info(ty = "TIMING_FUNCTION")] +#[repr(u8, C)] +pub enum TimingFunction<Integer, Number> { + /// `linear | ease | ease-in | ease-out | ease-in-out` + Keyword(TimingKeyword), + /// `cubic-bezier(<number>, <number>, <number>, <number>)` + #[allow(missing_docs)] + #[css(comma, function)] + CubicBezier { + x1: Number, + y1: Number, + x2: Number, + y2: Number, + }, + /// `step-start | step-end | steps(<integer>, [ <step-position> ]?)` + /// `<step-position> = jump-start | jump-end | jump-none | jump-both | start | end` + #[css(comma, function)] + #[value_info(other_values = "step-start,step-end")] + Steps(Integer, #[css(skip_if = "is_end")] StepPosition), +} + +#[allow(missing_docs)] +#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] +#[derive( + Clone, + Copy, + Debug, + Eq, + MallocSizeOf, + Parse, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(u8)] +pub enum TimingKeyword { + Linear, + Ease, + EaseIn, + EaseOut, + EaseInOut, +} + +#[cfg(feature = "gecko")] +fn step_position_jump_enabled(_context: &ParserContext) -> bool { + static_prefs::pref!("layout.css.step-position-jump.enabled") +} + +#[cfg(feature = "servo")] +fn step_position_jump_enabled(_context: &ParserContext) -> bool { + false +} + +#[allow(missing_docs)] +#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] +#[derive( + Clone, + Copy, + Debug, + Eq, + MallocSizeOf, + Parse, + PartialEq, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(u8)] +pub enum StepPosition { + #[parse(condition = "step_position_jump_enabled")] + JumpStart, + #[parse(condition = "step_position_jump_enabled")] + JumpEnd, + #[parse(condition = "step_position_jump_enabled")] + JumpNone, + #[parse(condition = "step_position_jump_enabled")] + JumpBoth, + Start, + End, +} + +#[inline] +fn is_end(position: &StepPosition) -> bool { + *position == StepPosition::JumpEnd || *position == StepPosition::End +} + +impl<Integer, Number> TimingFunction<Integer, Number> { + /// `ease` + #[inline] + pub fn ease() -> Self { + TimingFunction::Keyword(TimingKeyword::Ease) + } +} + +impl TimingKeyword { + /// Returns the keyword as a quadruplet of Bezier point coordinates + /// `(x1, y1, x2, y2)`. + #[inline] + pub fn to_bezier(self) -> (CSSFloat, CSSFloat, CSSFloat, CSSFloat) { + match self { + TimingKeyword::Linear => (0., 0., 1., 1.), + TimingKeyword::Ease => (0.25, 0.1, 0.25, 1.), + TimingKeyword::EaseIn => (0.42, 0., 1., 1.), + TimingKeyword::EaseOut => (0., 0., 0.58, 1.), + TimingKeyword::EaseInOut => (0.42, 0., 0.58, 1.), + } + } +} diff --git a/servo/components/style/values/generics/effects.rs b/servo/components/style/values/generics/effects.rs new file mode 100644 index 0000000000..dd9da8759b --- /dev/null +++ b/servo/components/style/values/generics/effects.rs @@ -0,0 +1,121 @@ +/* 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/. */ + +//! Generic types for CSS values related to effects. + +/// A generic value for a single `box-shadow`. +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToAnimatedValue, + ToAnimatedZero, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C)] +pub struct GenericBoxShadow<Color, SizeLength, BlurShapeLength, ShapeLength> { + /// The base shadow. + pub base: GenericSimpleShadow<Color, SizeLength, BlurShapeLength>, + /// The spread radius. + pub spread: ShapeLength, + /// Whether this is an inset box shadow. + #[animation(constant)] + #[css(represents_keyword)] + pub inset: bool, +} + +pub use self::GenericBoxShadow as BoxShadow; + +/// A generic value for a single `filter`. +#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] +#[animation(no_bound(U))] +#[derive( + Clone, + ComputeSquaredDistance, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToAnimatedValue, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C, u8)] +pub enum GenericFilter<Angle, NonNegativeFactor, ZeroToOneFactor, Length, Shadow, U> { + /// `blur(<length>)` + #[css(function)] + Blur(Length), + /// `brightness(<factor>)` + #[css(function)] + Brightness(NonNegativeFactor), + /// `contrast(<factor>)` + #[css(function)] + Contrast(NonNegativeFactor), + /// `grayscale(<factor>)` + #[css(function)] + Grayscale(ZeroToOneFactor), + /// `hue-rotate(<angle>)` + #[css(function)] + HueRotate(Angle), + /// `invert(<factor>)` + #[css(function)] + Invert(ZeroToOneFactor), + /// `opacity(<factor>)` + #[css(function)] + Opacity(ZeroToOneFactor), + /// `saturate(<factor>)` + #[css(function)] + Saturate(NonNegativeFactor), + /// `sepia(<factor>)` + #[css(function)] + Sepia(ZeroToOneFactor), + /// `drop-shadow(...)` + #[css(function)] + DropShadow(Shadow), + /// `<url>` + #[animation(error)] + Url(U), +} + +pub use self::GenericFilter as Filter; + +/// A generic value for the `drop-shadow()` filter and the `text-shadow` property. +/// +/// Contrary to the canonical order from the spec, the color is serialised +/// first, like in Gecko and Webkit. +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToAnimatedValue, + ToAnimatedZero, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C)] +pub struct GenericSimpleShadow<Color, SizeLength, ShapeLength> { + /// Color. + pub color: Color, + /// Horizontal radius. + pub horizontal: SizeLength, + /// Vertical radius. + pub vertical: SizeLength, + /// Blur radius. + pub blur: ShapeLength, +} + +pub use self::GenericSimpleShadow as SimpleShadow; diff --git a/servo/components/style/values/generics/flex.rs b/servo/components/style/values/generics/flex.rs new file mode 100644 index 0000000000..85b64000f2 --- /dev/null +++ b/servo/components/style/values/generics/flex.rs @@ -0,0 +1,33 @@ +/* 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/. */ + +//! Generic types for CSS values related to flexbox. + +/// A generic value for the `flex-basis` property. +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + MallocSizeOf, + Parse, + PartialEq, + SpecifiedValueInfo, + ToAnimatedValue, + ToAnimatedZero, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C)] +pub enum GenericFlexBasis<S> { + /// `content` + Content, + /// `<width>` + Size(S), +} + +pub use self::GenericFlexBasis as FlexBasis; diff --git a/servo/components/style/values/generics/font.rs b/servo/components/style/values/generics/font.rs new file mode 100644 index 0000000000..e19c796d17 --- /dev/null +++ b/servo/components/style/values/generics/font.rs @@ -0,0 +1,204 @@ +/* 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/. */ + +//! Generic types for font stuff. + +use crate::parser::{Parse, ParserContext}; +use crate::One; +use byteorder::{BigEndian, ReadBytesExt}; +use cssparser::Parser; +use std::fmt::{self, Write}; +use std::io::Cursor; +use style_traits::{CssWriter, ParseError}; +use style_traits::{StyleParseErrorKind, ToCss}; + +/// https://drafts.csswg.org/css-fonts-4/#feature-tag-value +#[derive( + Clone, + Debug, + Eq, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToResolvedValue, + ToShmem, +)] +pub struct FeatureTagValue<Integer> { + /// A four-character tag, packed into a u32 (one byte per character). + pub tag: FontTag, + /// The actual value. + pub value: Integer, +} + +impl<Integer> ToCss for FeatureTagValue<Integer> +where + Integer: One + ToCss + PartialEq, +{ + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + self.tag.to_css(dest)?; + // Don't serialize the default value. + if !self.value.is_one() { + dest.write_char(' ')?; + self.value.to_css(dest)?; + } + + Ok(()) + } +} + +/// Variation setting for a single feature, see: +/// +/// https://drafts.csswg.org/css-fonts-4/#font-variation-settings-def +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Debug, + Eq, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +pub struct VariationValue<Number> { + /// A four-character tag, packed into a u32 (one byte per character). + #[animation(constant)] + pub tag: FontTag, + /// The actual value. + pub value: Number, +} + +/// A value both for font-variation-settings and font-feature-settings. +#[css(comma)] +#[derive( + Clone, + Debug, + Eq, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +pub struct FontSettings<T>(#[css(if_empty = "normal", iterable)] pub Box<[T]>); + +impl<T> FontSettings<T> { + /// Default value of font settings as `normal`. + #[inline] + pub fn normal() -> Self { + FontSettings(vec![].into_boxed_slice()) + } +} + +impl<T: Parse> Parse for FontSettings<T> { + /// https://drafts.csswg.org/css-fonts-4/#descdef-font-face-font-feature-settings + /// https://drafts.csswg.org/css-fonts-4/#font-variation-settings-def + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + if input + .try_parse(|i| i.expect_ident_matching("normal")) + .is_ok() + { + return Ok(Self::normal()); + } + + Ok(FontSettings( + input + .parse_comma_separated(|i| T::parse(context, i))? + .into_boxed_slice(), + )) + } +} + +/// A font four-character tag, represented as a u32 for convenience. +/// +/// See: +/// https://drafts.csswg.org/css-fonts-4/#font-variation-settings-def +/// https://drafts.csswg.org/css-fonts-4/#descdef-font-face-font-feature-settings +/// +#[derive( + Clone, + Copy, + Debug, + Eq, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToResolvedValue, + ToShmem, +)] +pub struct FontTag(pub u32); + +impl ToCss for FontTag { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + use byteorder::ByteOrder; + use std::str; + + let mut raw = [0u8; 4]; + BigEndian::write_u32(&mut raw, self.0); + str::from_utf8(&raw).unwrap_or_default().to_css(dest) + } +} + +impl Parse for FontTag { + fn parse<'i, 't>( + _context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + let location = input.current_source_location(); + let tag = input.expect_string()?; + + // allowed strings of length 4 containing chars: <U+20, U+7E> + if tag.len() != 4 || tag.as_bytes().iter().any(|c| *c < b' ' || *c > b'~') { + return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + + let mut raw = Cursor::new(tag.as_bytes()); + Ok(FontTag(raw.read_u32::<BigEndian>().unwrap())) + } +} + +/// A generic value for the `font-style` property. +/// +/// https://drafts.csswg.org/css-fonts-4/#font-style-prop +#[allow(missing_docs)] +#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + Hash, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToAnimatedValue, + ToAnimatedZero, + ToResolvedValue, + ToShmem, +)] +pub enum FontStyle<Angle> { + #[animation(error)] + Normal, + #[animation(error)] + Italic, + #[value_info(starts_with_keyword)] + Oblique(Angle), +} diff --git a/servo/components/style/values/generics/grid.rs b/servo/components/style/values/generics/grid.rs new file mode 100644 index 0000000000..7995bf044a --- /dev/null +++ b/servo/components/style/values/generics/grid.rs @@ -0,0 +1,819 @@ +/* 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/. */ + +//! Generic types for the handling of +//! [grids](https://drafts.csswg.org/css-grid/). + +use crate::parser::{Parse, ParserContext}; +use crate::values::specified; +use crate::values::specified::grid::parse_line_names; +use crate::values::{CSSFloat, CustomIdent}; +use crate::{Atom, Zero}; +use cssparser::Parser; +use std::fmt::{self, Write}; +use std::{cmp, usize}; +use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; + +/// These are the limits that we choose to clamp grid line numbers to. +/// http://drafts.csswg.org/css-grid/#overlarge-grids +/// line_num is clamped to this range at parse time. +pub const MIN_GRID_LINE: i32 = -10000; +/// See above. +pub const MAX_GRID_LINE: i32 = 10000; + +/// A `<grid-line>` type. +/// +/// <https://drafts.csswg.org/css-grid/#typedef-grid-row-start-grid-line> +#[derive( + Clone, + Debug, + Default, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToResolvedValue, + ToShmem, +)] +#[repr(C)] +pub struct GenericGridLine<Integer> { + /// A custom identifier for named lines, or the empty atom otherwise. + /// + /// <https://drafts.csswg.org/css-grid/#grid-placement-slot> + pub ident: Atom, + /// Denotes the nth grid line from grid item's placement. + /// + /// This is clamped by MIN_GRID_LINE and MAX_GRID_LINE. + /// + /// NOTE(emilio): If we ever allow animating these we need to either do + /// something more complicated for the clamping, or do this clamping at + /// used-value time. + pub line_num: Integer, + /// Flag to check whether it's a `span` keyword. + pub is_span: bool, +} + +pub use self::GenericGridLine as GridLine; + +impl<Integer> GridLine<Integer> +where + Integer: PartialEq + Zero, +{ + /// The `auto` value. + pub fn auto() -> Self { + Self { + is_span: false, + line_num: Zero::zero(), + ident: atom!(""), + } + } + + /// Check whether this `<grid-line>` represents an `auto` value. + pub fn is_auto(&self) -> bool { + self.ident == atom!("") && self.line_num.is_zero() && !self.is_span + } + + /// Check whether this `<grid-line>` represents a `<custom-ident>` value. + pub fn is_ident_only(&self) -> bool { + self.ident != atom!("") && self.line_num.is_zero() && !self.is_span + } + + /// Check if `self` makes `other` omittable according to the rules at: + /// https://drafts.csswg.org/css-grid/#propdef-grid-column + /// https://drafts.csswg.org/css-grid/#propdef-grid-area + pub fn can_omit(&self, other: &Self) -> bool { + if self.is_ident_only() { + self == other + } else { + other.is_auto() + } + } +} + +impl<Integer> ToCss for GridLine<Integer> +where + Integer: ToCss + PartialEq + Zero, +{ + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + if self.is_auto() { + return dest.write_str("auto"); + } + + if self.is_span { + dest.write_str("span")?; + } + + if !self.line_num.is_zero() { + if self.is_span { + dest.write_str(" ")?; + } + self.line_num.to_css(dest)?; + } + + if self.ident != atom!("") { + if self.is_span || !self.line_num.is_zero() { + dest.write_str(" ")?; + } + CustomIdent(self.ident.clone()).to_css(dest)?; + } + + Ok(()) + } +} + +impl Parse for GridLine<specified::Integer> { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + let mut grid_line = Self::auto(); + if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() { + return Ok(grid_line); + } + + // <custom-ident> | [ <integer> && <custom-ident>? ] | [ span && [ <integer> || <custom-ident> ] ] + // This <grid-line> horror is simply, + // [ span? && [ <custom-ident> || <integer> ] ] + // And, for some magical reason, "span" should be the first or last value and not in-between. + let mut val_before_span = false; + + for _ in 0..3 { + // Maximum possible entities for <grid-line> + let location = input.current_source_location(); + if input.try_parse(|i| i.expect_ident_matching("span")).is_ok() { + if grid_line.is_span { + return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + + if !grid_line.line_num.is_zero() || grid_line.ident != atom!("") { + val_before_span = true; + } + + grid_line.is_span = true; + } else if let Ok(i) = input.try_parse(|i| specified::Integer::parse(context, i)) { + // FIXME(emilio): Probably shouldn't reject if it's calc()... + let value = i.value(); + if value == 0 || val_before_span || !grid_line.line_num.is_zero() { + return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + + grid_line.line_num = specified::Integer::new(cmp::max( + MIN_GRID_LINE, + cmp::min(value, MAX_GRID_LINE), + )); + } else if let Ok(name) = input.try_parse(|i| i.expect_ident_cloned()) { + if val_before_span || grid_line.ident != atom!("") { + return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + // NOTE(emilio): `span` is consumed above, so we only need to + // reject `auto`. + grid_line.ident = CustomIdent::from_ident(location, &name, &["auto"])?.0; + } else { + break; + } + } + + if grid_line.is_auto() { + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + + if grid_line.is_span { + if !grid_line.line_num.is_zero() { + if grid_line.line_num.value() <= 0 { + // disallow negative integers for grid spans + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + } else if grid_line.ident == atom!("") { + // integer could be omitted + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + } + + Ok(grid_line) + } +} + +/// A track breadth for explicit grid track sizing. It's generic solely to +/// avoid re-implementing it for the computed type. +/// +/// <https://drafts.csswg.org/css-grid/#typedef-track-breadth> +#[derive( + Animate, + Clone, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C, u8)] +pub enum GenericTrackBreadth<L> { + /// The generic type is almost always a non-negative `<length-percentage>` + Breadth(L), + /// A flex fraction specified in `fr` units. + #[css(dimension)] + Fr(CSSFloat), + /// `auto` + Auto, + /// `min-content` + MinContent, + /// `max-content` + MaxContent, +} + +pub use self::GenericTrackBreadth as TrackBreadth; + +impl<L> TrackBreadth<L> { + /// Check whether this is a `<fixed-breadth>` (i.e., it only has `<length-percentage>`) + /// + /// <https://drafts.csswg.org/css-grid/#typedef-fixed-breadth> + #[inline] + pub fn is_fixed(&self) -> bool { + matches!(*self, TrackBreadth::Breadth(..)) + } +} + +/// A `<track-size>` type for explicit grid track sizing. Like `<track-breadth>`, this is +/// generic only to avoid code bloat. It only takes `<length-percentage>` +/// +/// <https://drafts.csswg.org/css-grid/#typedef-track-size> +#[derive( + Clone, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToResolvedValue, + ToShmem, +)] +#[repr(C, u8)] +pub enum GenericTrackSize<L> { + /// A flexible `<track-breadth>` + Breadth(GenericTrackBreadth<L>), + /// A `minmax` function for a range over an inflexible `<track-breadth>` + /// and a flexible `<track-breadth>` + /// + /// <https://drafts.csswg.org/css-grid/#valdef-grid-template-columns-minmax> + #[css(function)] + Minmax(GenericTrackBreadth<L>, GenericTrackBreadth<L>), + /// A `fit-content` function. + /// + /// This stores a TrackBreadth<L> for convenience, but it can only be a + /// LengthPercentage. + /// + /// <https://drafts.csswg.org/css-grid/#valdef-grid-template-columns-fit-content> + #[css(function)] + FitContent(GenericTrackBreadth<L>), +} + +pub use self::GenericTrackSize as TrackSize; + +impl<L> TrackSize<L> { + /// The initial value. + const INITIAL_VALUE: Self = TrackSize::Breadth(TrackBreadth::Auto); + + /// Returns the initial value. + pub const fn initial_value() -> Self { + Self::INITIAL_VALUE + } + + /// Returns true if `self` is the initial value. + pub fn is_initial(&self) -> bool { + matches!(*self, TrackSize::Breadth(TrackBreadth::Auto)) // FIXME: can't use Self::INITIAL_VALUE here yet: https://github.com/rust-lang/rust/issues/66585 + } + + /// Check whether this is a `<fixed-size>` + /// + /// <https://drafts.csswg.org/css-grid/#typedef-fixed-size> + pub fn is_fixed(&self) -> bool { + match *self { + TrackSize::Breadth(ref breadth) => breadth.is_fixed(), + // For minmax function, it could be either + // minmax(<fixed-breadth>, <track-breadth>) or minmax(<inflexible-breadth>, <fixed-breadth>), + // and since both variants are a subset of minmax(<inflexible-breadth>, <track-breadth>), we only + // need to make sure that they're fixed. So, we don't have to modify the parsing function. + TrackSize::Minmax(ref breadth_1, ref breadth_2) => { + if breadth_1.is_fixed() { + return true; // the second value is always a <track-breadth> + } + + match *breadth_1 { + TrackBreadth::Fr(_) => false, // should be <inflexible-breadth> at this point + _ => breadth_2.is_fixed(), + } + }, + TrackSize::FitContent(_) => false, + } + } +} + +impl<L> Default for TrackSize<L> { + fn default() -> Self { + Self::initial_value() + } +} + +impl<L: ToCss> ToCss for TrackSize<L> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + match *self { + TrackSize::Breadth(ref breadth) => breadth.to_css(dest), + TrackSize::Minmax(ref min, ref max) => { + // According to gecko minmax(auto, <flex>) is equivalent to <flex>, + // and both are serialized as <flex>. + if let TrackBreadth::Auto = *min { + if let TrackBreadth::Fr(_) = *max { + return max.to_css(dest); + } + } + + dest.write_str("minmax(")?; + min.to_css(dest)?; + dest.write_str(", ")?; + max.to_css(dest)?; + dest.write_str(")") + }, + TrackSize::FitContent(ref lp) => { + dest.write_str("fit-content(")?; + lp.to_css(dest)?; + dest.write_str(")") + }, + } + } +} + +/// A `<track-size>+`. +/// We use the empty slice as `auto`, and always parse `auto` as an empty slice. +/// This means it's impossible to have a slice containing only one auto item. +#[derive( + Clone, + Debug, + Default, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(transparent)] +pub struct GenericImplicitGridTracks<T>( + #[css(if_empty = "auto", iterable)] pub crate::OwnedSlice<T>, +); + +pub use self::GenericImplicitGridTracks as ImplicitGridTracks; + +impl<T: fmt::Debug + Default + PartialEq> ImplicitGridTracks<T> { + /// Returns true if current value is same as its initial value (i.e. auto). + pub fn is_initial(&self) -> bool { + debug_assert_ne!( + *self, + ImplicitGridTracks(crate::OwnedSlice::from(vec![Default::default()])) + ); + self.0.is_empty() + } +} + +/// Helper function for serializing identifiers with a prefix and suffix, used +/// for serializing <line-names> (in grid). +pub fn concat_serialize_idents<W>( + prefix: &str, + suffix: &str, + slice: &[CustomIdent], + sep: &str, + dest: &mut CssWriter<W>, +) -> fmt::Result +where + W: Write, +{ + if let Some((ref first, rest)) = slice.split_first() { + dest.write_str(prefix)?; + first.to_css(dest)?; + for thing in rest { + dest.write_str(sep)?; + thing.to_css(dest)?; + } + + dest.write_str(suffix)?; + } + + Ok(()) +} + +/// The initial argument of the `repeat` function. +/// +/// <https://drafts.csswg.org/css-grid/#typedef-track-repeat> +#[derive( + Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem, +)] +#[repr(C, u8)] +pub enum RepeatCount<Integer> { + /// A positive integer. This is allowed only for `<track-repeat>` and `<fixed-repeat>` + Number(Integer), + /// An `<auto-fill>` keyword allowed only for `<auto-repeat>` + AutoFill, + /// An `<auto-fit>` keyword allowed only for `<auto-repeat>` + AutoFit, +} + +impl Parse for RepeatCount<specified::Integer> { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + if let Ok(mut i) = input.try_parse(|i| specified::Integer::parse_positive(context, i)) { + if i.value() > MAX_GRID_LINE { + i = specified::Integer::new(MAX_GRID_LINE); + } + return Ok(RepeatCount::Number(i)); + } + try_match_ident_ignore_ascii_case! { input, + "auto-fill" => Ok(RepeatCount::AutoFill), + "auto-fit" => Ok(RepeatCount::AutoFit), + } + } +} + +/// The structure containing `<line-names>` and `<track-size>` values. +/// +/// It can also hold `repeat()` function parameters, which expands into the respective +/// values in its computed form. +#[derive( + Clone, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToResolvedValue, + ToShmem, +)] +#[css(function = "repeat")] +#[repr(C)] +pub struct GenericTrackRepeat<L, I> { + /// The number of times for the value to be repeated (could also be `auto-fit` or `auto-fill`) + pub count: RepeatCount<I>, + /// `<line-names>` accompanying `<track_size>` values. + /// + /// If there's no `<line-names>`, then it's represented by an empty vector. + /// For N `<track-size>` values, there will be N+1 `<line-names>`, and so this vector's + /// length is always one value more than that of the `<track-size>`. + pub line_names: crate::OwnedSlice<crate::OwnedSlice<CustomIdent>>, + /// `<track-size>` values. + pub track_sizes: crate::OwnedSlice<GenericTrackSize<L>>, +} + +pub use self::GenericTrackRepeat as TrackRepeat; + +impl<L: ToCss, I: ToCss> ToCss for TrackRepeat<L, I> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + dest.write_str("repeat(")?; + self.count.to_css(dest)?; + dest.write_str(", ")?; + + let mut line_names_iter = self.line_names.iter(); + for (i, (ref size, ref names)) in self + .track_sizes + .iter() + .zip(&mut line_names_iter) + .enumerate() + { + if i > 0 { + dest.write_str(" ")?; + } + + concat_serialize_idents("[", "] ", names, " ", dest)?; + size.to_css(dest)?; + } + + if let Some(line_names_last) = line_names_iter.next() { + concat_serialize_idents(" [", "]", line_names_last, " ", dest)?; + } + + dest.write_str(")")?; + + Ok(()) + } +} + +/// Track list values. Can be <track-size> or <track-repeat> +#[derive( + Animate, + Clone, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C, u8)] +pub enum GenericTrackListValue<LengthPercentage, Integer> { + /// A <track-size> value. + TrackSize(#[animation(field_bound)] GenericTrackSize<LengthPercentage>), + /// A <track-repeat> value. + TrackRepeat(#[animation(field_bound)] GenericTrackRepeat<LengthPercentage, Integer>), +} + +pub use self::GenericTrackListValue as TrackListValue; + +impl<L, I> TrackListValue<L, I> { + // FIXME: can't use TrackSize::initial_value() here b/c rustc error "is not yet stable as a const fn" + const INITIAL_VALUE: Self = TrackListValue::TrackSize(TrackSize::Breadth(TrackBreadth::Auto)); + + fn is_repeat(&self) -> bool { + matches!(*self, TrackListValue::TrackRepeat(..)) + } + + /// Returns true if `self` is the initial value. + pub fn is_initial(&self) -> bool { + matches!( + *self, + TrackListValue::TrackSize(TrackSize::Breadth(TrackBreadth::Auto)) + ) // FIXME: can't use Self::INITIAL_VALUE here yet: https://github.com/rust-lang/rust/issues/66585 + } +} + +impl<L, I> Default for TrackListValue<L, I> { + #[inline] + fn default() -> Self { + Self::INITIAL_VALUE + } +} + +/// A grid `<track-list>` type. +/// +/// <https://drafts.csswg.org/css-grid/#typedef-track-list> +#[derive( + Clone, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToResolvedValue, + ToShmem, +)] +#[repr(C)] +pub struct GenericTrackList<LengthPercentage, Integer> { + /// The index in `values` where our `<auto-repeat>` value is, if in bounds. + #[css(skip)] + pub auto_repeat_index: usize, + /// A vector of `<track-size> | <track-repeat>` values. + pub values: crate::OwnedSlice<GenericTrackListValue<LengthPercentage, Integer>>, + /// `<line-names>` accompanying `<track-size> | <track-repeat>` values. + /// + /// If there's no `<line-names>`, then it's represented by an empty vector. + /// For N values, there will be N+1 `<line-names>`, and so this vector's + /// length is always one value more than that of the `<track-size>`. + pub line_names: crate::OwnedSlice<crate::OwnedSlice<CustomIdent>>, +} + +pub use self::GenericTrackList as TrackList; + +impl<L, I> TrackList<L, I> { + /// Whether this track list is an explicit track list (that is, doesn't have + /// any repeat values). + pub fn is_explicit(&self) -> bool { + !self.values.iter().any(|v| v.is_repeat()) + } + + /// Whether this track list has an `<auto-repeat>` value. + pub fn has_auto_repeat(&self) -> bool { + self.auto_repeat_index < self.values.len() + } +} + +impl<L: ToCss, I: ToCss> ToCss for TrackList<L, I> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + let mut values_iter = self.values.iter().peekable(); + let mut line_names_iter = self.line_names.iter().peekable(); + + for idx in 0.. { + let names = line_names_iter.next().unwrap(); // This should exist! + concat_serialize_idents("[", "]", names, " ", dest)?; + + match values_iter.next() { + Some(value) => { + if !names.is_empty() { + dest.write_str(" ")?; + } + + value.to_css(dest)?; + }, + None => break, + } + + if values_iter.peek().is_some() || + line_names_iter.peek().map_or(false, |v| !v.is_empty()) || + (idx + 1 == self.auto_repeat_index) + { + dest.write_str(" ")?; + } + } + + Ok(()) + } +} + +/// The `<line-name-list>` for subgrids. +/// +/// `subgrid [ <line-names> | repeat(<positive-integer> | auto-fill, <line-names>+) ]+` +/// +/// https://drafts.csswg.org/css-grid-2/#typedef-line-name-list +#[derive( + Clone, + Debug, + Default, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToResolvedValue, + ToShmem, +)] +#[repr(C)] +pub struct LineNameList { + /// The optional `<line-name-list>` + pub names: crate::OwnedSlice<crate::OwnedSlice<CustomIdent>>, + /// Indicates the starting line names that requires `auto-fill`, if in bounds. + pub fill_start: usize, + /// Indicates the number of line names in the auto-fill + pub fill_len: usize, +} + +impl Parse for LineNameList { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + input.expect_ident_matching("subgrid")?; + let mut line_names = vec![]; + let mut fill_data = None; + + loop { + let repeat_parse_result = input.try_parse(|input| { + input.expect_function_matching("repeat")?; + input.parse_nested_block(|input| { + let count = RepeatCount::parse(context, input)?; + input.expect_comma()?; + let mut names_list = vec![]; + names_list.push(parse_line_names(input)?); // there should be at least one + while let Ok(names) = input.try_parse(parse_line_names) { + names_list.push(names); + } + Ok((names_list, count)) + }) + }); + if let Ok((names_list, count)) = repeat_parse_result { + match count { + // FIXME(emilio): we shouldn't expand repeat() at + // parse time for subgrid. (bug 1583429) + RepeatCount::Number(num) => line_names.extend( + names_list + .iter() + .cloned() + .cycle() + .take(num.value() as usize * names_list.len()), + ), + RepeatCount::AutoFill if fill_data.is_none() => { + let fill_idx = line_names.len(); + let fill_len = names_list.len(); + fill_data = Some((fill_idx, fill_len)); + line_names.extend(names_list.into_iter()); + }, + _ => return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)), + } + } else if let Ok(names) = input.try_parse(parse_line_names) { + line_names.push(names); + } else { + break; + } + } + + if line_names.len() > MAX_GRID_LINE as usize { + line_names.truncate(MAX_GRID_LINE as usize); + } + + let (fill_start, fill_len) = fill_data.unwrap_or((0, 0)); + + Ok(LineNameList { + names: line_names.into(), + fill_start: fill_start, + fill_len: fill_len, + }) + } +} + +impl ToCss for LineNameList { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + dest.write_str("subgrid")?; + let fill_start = self.fill_start; + let fill_len = self.fill_len; + for (i, names) in self.names.iter().enumerate() { + if fill_len > 0 && i == fill_start { + dest.write_str(" repeat(auto-fill,")?; + } + + dest.write_str(" [")?; + + if let Some((ref first, rest)) = names.split_first() { + first.to_css(dest)?; + for name in rest { + dest.write_str(" ")?; + name.to_css(dest)?; + } + } + + dest.write_str("]")?; + if fill_len > 0 && i == fill_start + fill_len - 1 { + dest.write_str(")")?; + } + } + + Ok(()) + } +} + +/// Variants for `<grid-template-rows> | <grid-template-columns>` +#[derive( + Animate, + Clone, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C, u8)] +pub enum GenericGridTemplateComponent<L, I> { + /// `none` value. + None, + /// The grid `<track-list>` + TrackList( + #[animation(field_bound)] + #[compute(field_bound)] + #[resolve(field_bound)] + #[shmem(field_bound)] + Box<GenericTrackList<L, I>>, + ), + /// A `subgrid <line-name-list>?` + /// TODO: Support animations for this after subgrid is addressed in [grid-2] spec. + #[animation(error)] + Subgrid(Box<LineNameList>), + /// `masonry` value. + /// https://github.com/w3c/csswg-drafts/issues/4650 + Masonry, +} + +pub use self::GenericGridTemplateComponent as GridTemplateComponent; + +impl<L, I> GridTemplateComponent<L, I> { + /// The initial value. + const INITIAL_VALUE: Self = Self::None; + + /// Returns length of the <track-list>s <track-size> + pub fn track_list_len(&self) -> usize { + match *self { + GridTemplateComponent::TrackList(ref tracklist) => tracklist.values.len(), + _ => 0, + } + } + + /// Returns true if `self` is the initial value. + pub fn is_initial(&self) -> bool { + matches!(*self, Self::None) // FIXME: can't use Self::INITIAL_VALUE here yet: https://github.com/rust-lang/rust/issues/66585 + } +} + +impl<L, I> Default for GridTemplateComponent<L, I> { + #[inline] + fn default() -> Self { + Self::INITIAL_VALUE + } +} diff --git a/servo/components/style/values/generics/image.rs b/servo/components/style/values/generics/image.rs new file mode 100644 index 0000000000..c9b8f8c0f8 --- /dev/null +++ b/servo/components/style/values/generics/image.rs @@ -0,0 +1,602 @@ +/* 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/. */ + +//! Generic types for the handling of [images]. +//! +//! [images]: https://drafts.csswg.org/css-images/#image-values + +use crate::custom_properties; +use crate::values::generics::position::PositionComponent; +use crate::values::serialize_atom_identifier; +use crate::Atom; +use crate::Zero; +use servo_arc::Arc; +use std::fmt::{self, Write}; +use style_traits::{CssWriter, ToCss}; + +/// An `<image> | none` value. +/// +/// https://drafts.csswg.org/css-images/#image-values +#[derive( + Clone, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem, +)] +#[repr(C, u8)] +pub enum GenericImage<G, MozImageRect, ImageUrl, Color, Percentage, Resolution> { + /// `none` variant. + None, + /// A `<url()>` image. + Url(ImageUrl), + + /// A `<gradient>` image. Gradients are rather large, and not nearly as + /// common as urls, so we box them here to keep the size of this enum sane. + Gradient(Box<G>), + /// A `-moz-image-rect` image. Also fairly large and rare. + // not cfgโed out on non-Gecko to avoid `error[E0392]: parameter `MozImageRect` is never used` + // Instead we make MozImageRect an empty enum + Rect(Box<MozImageRect>), + + /// A `-moz-element(# <element-id>)` + #[cfg(feature = "gecko")] + #[css(function = "-moz-element")] + Element(Atom), + + /// A paint worklet image. + /// <https://drafts.css-houdini.org/css-paint-api/> + #[cfg(feature = "servo-layout-2013")] + PaintWorklet(PaintWorklet), + + /// A `<cross-fade()>` image. Storing this directly inside of + /// GenericImage increases the size by 8 bytes so we box it here + /// and store images directly inside of cross-fade instead of + /// boxing them there. + CrossFade(Box<GenericCrossFade<Self, Color, Percentage>>), + + /// An `image-set()` function. + ImageSet(#[compute(field_bound)] Box<GenericImageSet<Self, Resolution>>), +} + +pub use self::GenericImage as Image; + +/// <https://drafts.csswg.org/css-images-4/#cross-fade-function> +#[css(comma, function = "cross-fade")] +#[derive( + Clone, Debug, MallocSizeOf, PartialEq, ToResolvedValue, ToShmem, ToCss, ToComputedValue, +)] +#[repr(C)] +pub struct GenericCrossFade<Image, Color, Percentage> { + /// All of the image percent pairings passed as arguments to + /// cross-fade. + #[css(iterable)] + pub elements: crate::OwnedSlice<GenericCrossFadeElement<Image, Color, Percentage>>, +} + +/// A `<percent> | none` value. Represents optional percentage values +/// assosicated with cross-fade images. +#[derive( + Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, ToCss, +)] +#[repr(C, u8)] +pub enum PercentOrNone<Percentage> { + /// `none` variant. + #[css(skip)] + None, + /// A percentage variant. + Percent(Percentage), +} + +/// An optional percent and a cross fade image. +#[derive( + Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, ToCss, +)] +#[repr(C)] +pub struct GenericCrossFadeElement<Image, Color, Percentage> { + /// The percent of the final image that `image` will be. + pub percent: PercentOrNone<Percentage>, + /// A color or image that will be blended when cross-fade is + /// evaluated. + pub image: GenericCrossFadeImage<Image, Color>, +} + +/// An image or a color. `cross-fade` takes either when blending +/// images together. +#[derive( + Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, ToCss, +)] +#[repr(C, u8)] +pub enum GenericCrossFadeImage<I, C> { + /// A boxed image value. Boxing provides indirection so images can + /// be cross-fades and cross-fades can be images. + Image(I), + /// A color value. + Color(C), +} + +pub use self::GenericCrossFade as CrossFade; +pub use self::GenericCrossFadeElement as CrossFadeElement; +pub use self::GenericCrossFadeImage as CrossFadeImage; + +/// https://drafts.csswg.org/css-images-4/#image-set-notation +#[css(comma, function = "image-set")] +#[derive( + Clone, Debug, MallocSizeOf, PartialEq, ToResolvedValue, ToShmem, ToCss, +)] +#[repr(C)] +pub struct GenericImageSet<Image, Resolution> { + /// The index of the selected candidate. Zero for specified values. + #[css(skip)] + pub selected_index: usize, + + /// All of the image and resolution pairs. + #[css(iterable)] + pub items: crate::OwnedSlice<GenericImageSetItem<Image, Resolution>>, +} + +/// An optional percent and a cross fade image. +#[derive( + Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, ToCss, +)] +#[repr(C)] +pub struct GenericImageSetItem<Image, Resolution> { + /// `<image>`. `<string>` is converted to `Image::Url` at parse time. + pub image: Image, + /// The `<resolution>`. + /// + /// TODO: Skip serialization if it is 1x. + pub resolution: Resolution, + // TODO: type() function. +} + +pub use self::GenericImageSet as ImageSet; +pub use self::GenericImageSetItem as ImageSetItem; + +/// A CSS gradient. +/// <https://drafts.csswg.org/css-images/#gradients> +#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem)] +#[repr(C)] +pub enum GenericGradient< + LineDirection, + LengthPercentage, + NonNegativeLength, + NonNegativeLengthPercentage, + Position, + Angle, + AngleOrPercentage, + Color, +> { + /// A linear gradient. + Linear { + /// Line direction + direction: LineDirection, + /// The color stops and interpolation hints. + items: crate::OwnedSlice<GenericGradientItem<Color, LengthPercentage>>, + /// True if this is a repeating gradient. + repeating: bool, + /// Compatibility mode. + compat_mode: GradientCompatMode, + }, + /// A radial gradient. + Radial { + /// Shape of gradient + shape: GenericEndingShape<NonNegativeLength, NonNegativeLengthPercentage>, + /// Center of gradient + position: Position, + /// The color stops and interpolation hints. + items: crate::OwnedSlice<GenericGradientItem<Color, LengthPercentage>>, + /// True if this is a repeating gradient. + repeating: bool, + /// Compatibility mode. + compat_mode: GradientCompatMode, + }, + /// A conic gradient. + Conic { + /// Start angle of gradient + angle: Angle, + /// Center of gradient + position: Position, + /// The color stops and interpolation hints. + items: crate::OwnedSlice<GenericGradientItem<Color, AngleOrPercentage>>, + /// True if this is a repeating gradient. + repeating: bool, + }, +} + +pub use self::GenericGradient as Gradient; + +#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem)] +#[repr(u8)] +/// Whether we used the modern notation or the compatibility `-webkit`, `-moz` prefixes. +pub enum GradientCompatMode { + /// Modern syntax. + Modern, + /// `-webkit` prefix. + WebKit, + /// `-moz` prefix + Moz, +} + +/// A radial gradient's ending shape. +#[derive( + Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem, +)] +#[repr(C, u8)] +pub enum GenericEndingShape<NonNegativeLength, NonNegativeLengthPercentage> { + /// A circular gradient. + Circle(GenericCircle<NonNegativeLength>), + /// An elliptic gradient. + Ellipse(GenericEllipse<NonNegativeLengthPercentage>), +} + +pub use self::GenericEndingShape as EndingShape; + +/// A circle shape. +#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem)] +#[repr(C, u8)] +pub enum GenericCircle<NonNegativeLength> { + /// A circle radius. + Radius(NonNegativeLength), + /// A circle extent. + Extent(ShapeExtent), +} + +pub use self::GenericCircle as Circle; + +/// An ellipse shape. +#[derive( + Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem, +)] +#[repr(C, u8)] +pub enum GenericEllipse<NonNegativeLengthPercentage> { + /// An ellipse pair of radii. + Radii(NonNegativeLengthPercentage, NonNegativeLengthPercentage), + /// An ellipse extent. + Extent(ShapeExtent), +} + +pub use self::GenericEllipse as Ellipse; + +/// <https://drafts.csswg.org/css-images/#typedef-extent-keyword> +#[allow(missing_docs)] +#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] +#[derive( + Clone, + Copy, + Debug, + Eq, + MallocSizeOf, + Parse, + PartialEq, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(u8)] +pub enum ShapeExtent { + ClosestSide, + FarthestSide, + ClosestCorner, + FarthestCorner, + Contain, + Cover, +} + +/// A gradient item. +/// <https://drafts.csswg.org/css-images-4/#color-stop-syntax> +#[derive( + Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem, +)] +#[repr(C, u8)] +pub enum GenericGradientItem<Color, T> { + /// A simple color stop, without position. + SimpleColorStop(Color), + /// A complex color stop, with a position. + ComplexColorStop { + /// The color for the stop. + color: Color, + /// The position for the stop. + position: T, + }, + /// An interpolation hint. + InterpolationHint(T), +} + +pub use self::GenericGradientItem as GradientItem; + +/// A color stop. +/// <https://drafts.csswg.org/css-images/#typedef-color-stop-list> +#[derive( + Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem, +)] +pub struct ColorStop<Color, T> { + /// The color of this stop. + pub color: Color, + /// The position of this stop. + pub position: Option<T>, +} + +impl<Color, T> ColorStop<Color, T> { + /// Convert the color stop into an appropriate `GradientItem`. + #[inline] + pub fn into_item(self) -> GradientItem<Color, T> { + match self.position { + Some(position) => GradientItem::ComplexColorStop { + color: self.color, + position, + }, + None => GradientItem::SimpleColorStop(self.color), + } + } +} + +/// Specified values for a paint worklet. +/// <https://drafts.css-houdini.org/css-paint-api/> +#[cfg_attr(feature = "servo", derive(MallocSizeOf))] +#[derive(Clone, Debug, PartialEq, ToComputedValue, ToResolvedValue, ToShmem)] +pub struct PaintWorklet { + /// The name the worklet was registered with. + pub name: Atom, + /// The arguments for the worklet. + /// TODO: store a parsed representation of the arguments. + #[cfg_attr(feature = "servo", ignore_malloc_size_of = "Arc")] + #[compute(no_field_bound)] + #[resolve(no_field_bound)] + pub arguments: Vec<Arc<custom_properties::SpecifiedValue>>, +} + +impl ::style_traits::SpecifiedValueInfo for PaintWorklet {} + +impl ToCss for PaintWorklet { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + dest.write_str("paint(")?; + serialize_atom_identifier(&self.name, dest)?; + for argument in &self.arguments { + dest.write_str(", ")?; + argument.to_css(dest)?; + } + dest.write_str(")") + } +} + +/// Values for `moz-image-rect`. +/// +/// `-moz-image-rect(<uri>, top, right, bottom, left);` +#[allow(missing_docs)] +#[css(comma, function = "-moz-image-rect")] +#[derive( + Clone, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C)] +pub struct GenericMozImageRect<NumberOrPercentage, MozImageRectUrl> { + pub url: MozImageRectUrl, + pub top: NumberOrPercentage, + pub right: NumberOrPercentage, + pub bottom: NumberOrPercentage, + pub left: NumberOrPercentage, +} + +pub use self::GenericMozImageRect as MozImageRect; + +impl<G, R, U, C, P, Resolution> fmt::Debug for Image<G, R, U, C, P, Resolution> +where + Image<G, R, U, C, P, Resolution>: ToCss, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.to_css(&mut CssWriter::new(f)) + } +} + +impl<G, R, U, C, P, Resolution> ToCss for Image<G, R, U, C, P, Resolution> +where + G: ToCss, + R: ToCss, + U: ToCss, + C: ToCss, + P: ToCss, + Resolution: ToCss, +{ + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + match *self { + Image::None => dest.write_str("none"), + Image::Url(ref url) => url.to_css(dest), + Image::Gradient(ref gradient) => gradient.to_css(dest), + Image::Rect(ref rect) => rect.to_css(dest), + #[cfg(feature = "servo-layout-2013")] + Image::PaintWorklet(ref paint_worklet) => paint_worklet.to_css(dest), + #[cfg(feature = "gecko")] + Image::Element(ref selector) => { + dest.write_str("-moz-element(#")?; + serialize_atom_identifier(selector, dest)?; + dest.write_str(")") + }, + Image::ImageSet(ref is) => is.to_css(dest), + Image::CrossFade(ref cf) => cf.to_css(dest), + } + } +} + +impl<D, LP, NL, NLP, P, A: Zero, AoP, C> ToCss for Gradient<D, LP, NL, NLP, P, A, AoP, C> +where + D: LineDirection, + LP: ToCss, + NL: ToCss, + NLP: ToCss, + P: PositionComponent + ToCss, + A: ToCss, + AoP: ToCss, + C: ToCss, +{ + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + let (compat_mode, repeating) = match *self { + Gradient::Linear { + compat_mode, + repeating, + .. + } => (compat_mode, repeating), + Gradient::Radial { + compat_mode, + repeating, + .. + } => (compat_mode, repeating), + Gradient::Conic { repeating, .. } => (GradientCompatMode::Modern, repeating), + }; + + match compat_mode { + GradientCompatMode::WebKit => dest.write_str("-webkit-")?, + GradientCompatMode::Moz => dest.write_str("-moz-")?, + _ => {}, + } + + if repeating { + dest.write_str("repeating-")?; + } + + match *self { + Gradient::Linear { + ref direction, + ref items, + compat_mode, + .. + } => { + dest.write_str("linear-gradient(")?; + let mut skip_comma = if !direction.points_downwards(compat_mode) { + direction.to_css(dest, compat_mode)?; + false + } else { + true + }; + for item in &**items { + if !skip_comma { + dest.write_str(", ")?; + } + skip_comma = false; + item.to_css(dest)?; + } + }, + Gradient::Radial { + ref shape, + ref position, + ref items, + compat_mode, + .. + } => { + dest.write_str("radial-gradient(")?; + let omit_shape = match *shape { + EndingShape::Ellipse(Ellipse::Extent(ShapeExtent::Cover)) | + EndingShape::Ellipse(Ellipse::Extent(ShapeExtent::FarthestCorner)) => true, + _ => false, + }; + let omit_position = position.is_center(); + if compat_mode == GradientCompatMode::Modern { + if !omit_shape { + shape.to_css(dest)?; + if !omit_position { + dest.write_str(" ")?; + } + } + if !omit_position { + dest.write_str("at ")?; + position.to_css(dest)?; + } + } else { + if !omit_position { + position.to_css(dest)?; + if !omit_shape { + dest.write_str(", ")?; + } + } + if !omit_shape { + shape.to_css(dest)?; + } + } + let mut skip_comma = omit_shape && omit_position; + for item in &**items { + if !skip_comma { + dest.write_str(", ")?; + } + skip_comma = false; + item.to_css(dest)?; + } + }, + Gradient::Conic { + ref angle, + ref position, + ref items, + .. + } => { + dest.write_str("conic-gradient(")?; + let omit_angle = angle.is_zero(); + let omit_position = position.is_center(); + if !omit_angle { + dest.write_str("from ")?; + angle.to_css(dest)?; + if !omit_position { + dest.write_str(" ")?; + } + } + if !omit_position { + dest.write_str("at ")?; + position.to_css(dest)?; + } + let mut skip_comma = omit_angle && omit_position; + for item in &**items { + if !skip_comma { + dest.write_str(", ")?; + } + skip_comma = false; + item.to_css(dest)?; + } + }, + } + dest.write_str(")") + } +} + +/// The direction of a linear gradient. +pub trait LineDirection { + /// Whether this direction points towards, and thus can be omitted. + fn points_downwards(&self, compat_mode: GradientCompatMode) -> bool; + + /// Serialises this direction according to the compatibility mode. + fn to_css<W>(&self, dest: &mut CssWriter<W>, compat_mode: GradientCompatMode) -> fmt::Result + where + W: Write; +} + +impl<L> ToCss for Circle<L> +where + L: ToCss, +{ + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + match *self { + Circle::Extent(ShapeExtent::FarthestCorner) | Circle::Extent(ShapeExtent::Cover) => { + dest.write_str("circle") + }, + Circle::Extent(keyword) => { + dest.write_str("circle ")?; + keyword.to_css(dest) + }, + Circle::Radius(ref length) => length.to_css(dest), + } + } +} diff --git a/servo/components/style/values/generics/length.rs b/servo/components/style/values/generics/length.rs new file mode 100644 index 0000000000..3ac358e781 --- /dev/null +++ b/servo/components/style/values/generics/length.rs @@ -0,0 +1,289 @@ +/* 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/. */ + +//! Generic types for CSS values related to length. + +use crate::parser::{Parse, ParserContext}; +#[cfg(feature = "gecko")] +use crate::values::computed::ExtremumLength; +use crate::Zero; +use cssparser::Parser; +use style_traits::ParseError; + +/// A `<length-percentage> | auto` value. +#[allow(missing_docs)] +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToAnimatedValue, + ToAnimatedZero, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C, u8)] +pub enum GenericLengthPercentageOrAuto<LengthPercent> { + LengthPercentage(LengthPercent), + Auto, +} + +pub use self::GenericLengthPercentageOrAuto as LengthPercentageOrAuto; + +impl<LengthPercentage> LengthPercentageOrAuto<LengthPercentage> { + /// `auto` value. + #[inline] + pub fn auto() -> Self { + LengthPercentageOrAuto::Auto + } + + /// Whether this is the `auto` value. + #[inline] + pub fn is_auto(&self) -> bool { + matches!(*self, LengthPercentageOrAuto::Auto) + } + + /// A helper function to parse this with quirks or not and so forth. + pub fn parse_with<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + parser: impl FnOnce( + &ParserContext, + &mut Parser<'i, 't>, + ) -> Result<LengthPercentage, ParseError<'i>>, + ) -> Result<Self, ParseError<'i>> { + if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() { + return Ok(LengthPercentageOrAuto::Auto); + } + + Ok(LengthPercentageOrAuto::LengthPercentage(parser( + context, input, + )?)) + } +} + +impl<LengthPercentage> LengthPercentageOrAuto<LengthPercentage> +where + LengthPercentage: Clone, +{ + /// Resolves `auto` values by calling `f`. + #[inline] + pub fn auto_is(&self, f: impl Fn() -> LengthPercentage) -> LengthPercentage { + match self { + LengthPercentageOrAuto::LengthPercentage(length) => length.clone(), + LengthPercentageOrAuto::Auto => f(), + } + } + + /// Returns the non-`auto` value, if any. + #[inline] + pub fn non_auto(&self) -> Option<LengthPercentage> { + match self { + LengthPercentageOrAuto::LengthPercentage(length) => Some(length.clone()), + LengthPercentageOrAuto::Auto => None, + } + } + + /// Maps the length of this value. + pub fn map<T>(&self, f: impl FnOnce(LengthPercentage) -> T) -> LengthPercentageOrAuto<T> { + match self { + LengthPercentageOrAuto::LengthPercentage(l) => { + LengthPercentageOrAuto::LengthPercentage(f(l.clone())) + }, + LengthPercentageOrAuto::Auto => LengthPercentageOrAuto::Auto, + } + } +} + +impl<LengthPercentage: Zero> Zero for LengthPercentageOrAuto<LengthPercentage> { + fn zero() -> Self { + LengthPercentageOrAuto::LengthPercentage(Zero::zero()) + } + + fn is_zero(&self) -> bool { + match *self { + LengthPercentageOrAuto::LengthPercentage(ref l) => l.is_zero(), + LengthPercentageOrAuto::Auto => false, + } + } +} + +impl<LengthPercentage: Parse> Parse for LengthPercentageOrAuto<LengthPercentage> { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + Self::parse_with(context, input, LengthPercentage::parse) + } +} + +/// A generic value for the `width`, `height`, `min-width`, or `min-height` property. +/// +/// Unlike `max-width` or `max-height` properties, a Size can be `auto`, +/// and cannot be `none`. +/// +/// Note that it only accepts non-negative values. +#[allow(missing_docs)] +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToAnimatedValue, + ToAnimatedZero, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C, u8)] +pub enum GenericSize<LengthPercent> { + LengthPercentage(LengthPercent), + Auto, + #[cfg(feature = "gecko")] + #[animation(error)] + ExtremumLength(ExtremumLength), +} + +pub use self::GenericSize as Size; + +impl<LengthPercentage> Size<LengthPercentage> { + /// `auto` value. + #[inline] + pub fn auto() -> Self { + Size::Auto + } + + /// Returns whether we're the auto value. + #[inline] + pub fn is_auto(&self) -> bool { + matches!(*self, Size::Auto) + } +} + +/// A generic value for the `max-width` or `max-height` property. +#[allow(missing_docs)] +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToAnimatedValue, + ToAnimatedZero, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C, u8)] +pub enum GenericMaxSize<LengthPercent> { + LengthPercentage(LengthPercent), + None, + #[cfg(feature = "gecko")] + #[animation(error)] + ExtremumLength(ExtremumLength), +} + +pub use self::GenericMaxSize as MaxSize; + +impl<LengthPercentage> MaxSize<LengthPercentage> { + /// `none` value. + #[inline] + pub fn none() -> Self { + MaxSize::None + } +} + +/// A generic `<length>` | `<number>` value for the `-moz-tab-size` property. +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + MallocSizeOf, + Parse, + PartialEq, + SpecifiedValueInfo, + ToAnimatedValue, + ToAnimatedZero, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C, u8)] +pub enum GenericLengthOrNumber<L, N> { + /// A number. + /// + /// NOTE: Numbers need to be before lengths, in order to parse them + /// first, since `0` should be a number, not the `0px` length. + Number(N), + /// A length. + Length(L), +} + +pub use self::GenericLengthOrNumber as LengthOrNumber; + +impl<L, N: Zero> Zero for LengthOrNumber<L, N> { + fn zero() -> Self { + LengthOrNumber::Number(Zero::zero()) + } + + fn is_zero(&self) -> bool { + match *self { + LengthOrNumber::Number(ref n) => n.is_zero(), + LengthOrNumber::Length(..) => false, + } + } +} + +/// A generic `<length-percentage>` | normal` value. +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + MallocSizeOf, + Parse, + PartialEq, + SpecifiedValueInfo, + ToAnimatedValue, + ToAnimatedZero, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C, u8)] +#[allow(missing_docs)] +pub enum GenericLengthPercentageOrNormal<LengthPercent> { + LengthPercentage(LengthPercent), + Normal, +} + +pub use self::GenericLengthPercentageOrNormal as LengthPercentageOrNormal; + +impl<LengthPercent> LengthPercentageOrNormal<LengthPercent> { + /// Returns the normal value. + #[inline] + pub fn normal() -> Self { + LengthPercentageOrNormal::Normal + } +} diff --git a/servo/components/style/values/generics/mod.rs b/servo/components/style/values/generics/mod.rs new file mode 100644 index 0000000000..09a99432e1 --- /dev/null +++ b/servo/components/style/values/generics/mod.rs @@ -0,0 +1,294 @@ +/* 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/. */ + +//! Generic types that share their serialization implementations +//! for both specified and computed values. + +use super::CustomIdent; +use crate::counter_style::{parse_counter_style_name, Symbols}; +use crate::parser::{Parse, ParserContext}; +use crate::Zero; +use cssparser::Parser; +use std::ops::Add; +use style_traits::{KeywordsCollectFn, ParseError}; +use style_traits::{SpecifiedValueInfo, StyleParseErrorKind}; + +pub mod background; +pub mod basic_shape; +pub mod border; +#[path = "box.rs"] +pub mod box_; +pub mod calc; +pub mod color; +pub mod column; +pub mod counters; +pub mod easing; +pub mod effects; +pub mod flex; +pub mod font; +pub mod grid; +pub mod image; +pub mod length; +pub mod motion; +pub mod position; +pub mod rect; +pub mod size; +pub mod svg; +pub mod text; +pub mod transform; +pub mod ui; +pub mod url; + +/// https://drafts.csswg.org/css-counter-styles/#typedef-symbols-type +#[allow(missing_docs)] +#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] +#[derive( + Clone, + Copy, + Debug, + Eq, + MallocSizeOf, + Parse, + PartialEq, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(u8)] +pub enum SymbolsType { + Cyclic, + Numeric, + Alphabetic, + Symbolic, + Fixed, +} + +/// <https://drafts.csswg.org/css-counter-styles/#typedef-counter-style> +/// +/// Note that 'none' is not a valid name. +#[cfg_attr(feature = "gecko", derive(MallocSizeOf))] +#[derive(Clone, Debug, Eq, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem)] +#[repr(u8)] +pub enum CounterStyle { + /// `<counter-style-name>` + Name(CustomIdent), + /// `symbols()` + #[css(function)] + Symbols(#[css(skip_if = "is_symbolic")] SymbolsType, Symbols), +} + +#[inline] +fn is_symbolic(symbols_type: &SymbolsType) -> bool { + *symbols_type == SymbolsType::Symbolic +} + +impl CounterStyle { + /// disc value + pub fn disc() -> Self { + CounterStyle::Name(CustomIdent(atom!("disc"))) + } + + /// decimal value + pub fn decimal() -> Self { + CounterStyle::Name(CustomIdent(atom!("decimal"))) + } +} + +impl Parse for CounterStyle { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + if let Ok(name) = input.try_parse(|i| parse_counter_style_name(i)) { + return Ok(CounterStyle::Name(name)); + } + input.expect_function_matching("symbols")?; + input.parse_nested_block(|input| { + let symbols_type = input + .try_parse(SymbolsType::parse) + .unwrap_or(SymbolsType::Symbolic); + let symbols = Symbols::parse(context, input)?; + // There must be at least two symbols for alphabetic or + // numeric system. + if (symbols_type == SymbolsType::Alphabetic || symbols_type == SymbolsType::Numeric) && + symbols.0.len() < 2 + { + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + // Identifier is not allowed in symbols() function. + if symbols.0.iter().any(|sym| !sym.is_allowed_in_symbols()) { + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + Ok(CounterStyle::Symbols(symbols_type, symbols)) + }) + } +} + +impl SpecifiedValueInfo for CounterStyle { + fn collect_completion_keywords(f: KeywordsCollectFn) { + // XXX The best approach for implementing this is probably + // having a CounterStyleName type wrapping CustomIdent, and + // put the predefined list for that type in counter_style mod. + // But that's a non-trivial change itself, so we use a simpler + // approach here. + macro_rules! predefined { + ($($name:expr,)+) => { + f(&["symbols", $($name,)+]); + } + } + include!("../../counter_style/predefined.rs"); + } +} + +/// A wrapper of Non-negative values. +#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + Hash, + MallocSizeOf, + PartialEq, + PartialOrd, + SpecifiedValueInfo, + ToAnimatedZero, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(transparent)] +pub struct NonNegative<T>(pub T); + +impl<T: Add<Output = T>> Add<NonNegative<T>> for NonNegative<T> { + type Output = Self; + + fn add(self, other: Self) -> Self { + NonNegative(self.0 + other.0) + } +} + +impl<T: Zero> Zero for NonNegative<T> { + fn is_zero(&self) -> bool { + self.0.is_zero() + } + + fn zero() -> Self { + NonNegative(T::zero()) + } +} + +/// A wrapper of greater-than-or-equal-to-one values. +#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + MallocSizeOf, + PartialEq, + PartialOrd, + SpecifiedValueInfo, + ToAnimatedZero, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +pub struct GreaterThanOrEqualToOne<T>(pub T); + +/// A wrapper of values between zero and one. +#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + Hash, + MallocSizeOf, + PartialEq, + PartialOrd, + SpecifiedValueInfo, + ToAnimatedZero, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(transparent)] +pub struct ZeroToOne<T>(pub T); + +/// A clip rect for clip and image-region +#[allow(missing_docs)] +#[derive( + Clone, + ComputeSquaredDistance, + Copy, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToAnimatedValue, + ToAnimatedZero, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[css(function = "rect", comma)] +#[repr(C)] +pub struct GenericClipRect<LengthOrAuto> { + pub top: LengthOrAuto, + pub right: LengthOrAuto, + pub bottom: LengthOrAuto, + pub left: LengthOrAuto, +} + +pub use self::GenericClipRect as ClipRect; + +/// Either a clip-rect or `auto`. +#[allow(missing_docs)] +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + MallocSizeOf, + Parse, + PartialEq, + SpecifiedValueInfo, + ToAnimatedValue, + ToAnimatedZero, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C, u8)] +pub enum GenericClipRectOrAuto<R> { + Auto, + Rect(R), +} + +pub use self::GenericClipRectOrAuto as ClipRectOrAuto; + +impl<L> ClipRectOrAuto<L> { + /// Returns the `auto` value. + #[inline] + pub fn auto() -> Self { + ClipRectOrAuto::Auto + } + + /// Returns whether this value is the `auto` value. + #[inline] + pub fn is_auto(&self) -> bool { + matches!(*self, ClipRectOrAuto::Auto) + } +} diff --git a/servo/components/style/values/generics/motion.rs b/servo/components/style/values/generics/motion.rs new file mode 100644 index 0000000000..25ba5873ad --- /dev/null +++ b/servo/components/style/values/generics/motion.rs @@ -0,0 +1,117 @@ +/* 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/. */ + +//! Generic types for CSS Motion Path. + +use crate::values::specified::SVGPathData; + +/// The <size> in ray() function. +/// +/// https://drafts.fxtf.org/motion-1/#valdef-offsetpath-size +#[allow(missing_docs)] +#[derive( + Clone, + Copy, + Debug, + Deserialize, + MallocSizeOf, + Parse, + PartialEq, + Serialize, + SpecifiedValueInfo, + ToAnimatedZero, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(u8)] +pub enum RaySize { + ClosestSide, + ClosestCorner, + FarthestSide, + FarthestCorner, + Sides, +} + +/// The `ray()` function, `ray( [ <angle> && <size> && contain? ] )` +/// +/// https://drafts.fxtf.org/motion-1/#valdef-offsetpath-ray +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Debug, + Deserialize, + MallocSizeOf, + PartialEq, + Serialize, + SpecifiedValueInfo, + ToAnimatedZero, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C)] +pub struct RayFunction<Angle> { + /// The bearing angle with `0deg` pointing up and positive angles + /// representing clockwise rotation. + pub angle: Angle, + /// Decide the path length used when `offset-distance` is expressed + /// as a percentage. + #[animation(constant)] + pub size: RaySize, + /// Clamp `offset-distance` so that the box is entirely contained + /// within the path. + #[animation(constant)] + #[css(represents_keyword)] + pub contain: bool, +} + +/// The offset-path value. +/// +/// https://drafts.fxtf.org/motion-1/#offset-path-property +/// cbindgen:private-default-tagged-enum-constructor=false +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Debug, + Deserialize, + MallocSizeOf, + PartialEq, + Serialize, + SpecifiedValueInfo, + ToAnimatedZero, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C, u8)] +pub enum GenericOffsetPath<Angle> { + // We could merge SVGPathData into ShapeSource, so we could reuse them. However, + // we don't want to support other value for offset-path, so use SVGPathData only for now. + /// Path value for path(<string>). + #[css(function)] + Path(SVGPathData), + /// ray() function, which defines a path in the polar coordinate system. + #[css(function)] + Ray(RayFunction<Angle>), + /// None value. + #[animation(error)] + None, + // Bug 1186329: Implement <basic-shape>, <geometry-box>, and <url>. +} + +pub use self::GenericOffsetPath as OffsetPath; + +impl<Angle> OffsetPath<Angle> { + /// Return None. + #[inline] + pub fn none() -> Self { + OffsetPath::None + } +} diff --git a/servo/components/style/values/generics/position.rs b/servo/components/style/values/generics/position.rs new file mode 100644 index 0000000000..45605b1b8c --- /dev/null +++ b/servo/components/style/values/generics/position.rs @@ -0,0 +1,264 @@ +/* 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/. */ + +//! Generic types for CSS handling of specified and computed values of +//! [`position`](https://drafts.csswg.org/css-backgrounds-3/#position) + +use std::fmt::{self, Write}; +use style_traits::{CssWriter, ToCss}; + +/// A generic type for representing a CSS [position](https://drafts.csswg.org/css-values/#position). +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + Deserialize, + MallocSizeOf, + PartialEq, + Serialize, + SpecifiedValueInfo, + ToAnimatedValue, + ToAnimatedZero, + ToComputedValue, + ToResolvedValue, + ToShmem, +)] +#[repr(C)] +pub struct GenericPosition<H, V> { + /// The horizontal component of position. + pub horizontal: H, + /// The vertical component of position. + pub vertical: V, +} + +impl<H, V> PositionComponent for Position<H, V> +where + H: PositionComponent, + V: PositionComponent, +{ + #[inline] + fn is_center(&self) -> bool { + self.horizontal.is_center() && self.vertical.is_center() + } +} + +pub use self::GenericPosition as Position; + +impl<H, V> Position<H, V> { + /// Returns a new position. + pub fn new(horizontal: H, vertical: V) -> Self { + Self { + horizontal, + vertical, + } + } +} + +/// Implements a method that checks if the position is centered. +pub trait PositionComponent { + /// Returns if the position component is 50% or center. + /// For pixel lengths, it always returns false. + fn is_center(&self) -> bool; +} + +/// A generic type for representing an `Auto | <position>`. +/// This is used by <offset-anchor> for now. +/// https://drafts.fxtf.org/motion-1/#offset-anchor-property +/// cbindgen:private-default-tagged-enum-constructor=false +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + Deserialize, + MallocSizeOf, + Parse, + PartialEq, + Serialize, + SpecifiedValueInfo, + ToAnimatedZero, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C, u8)] +pub enum GenericPositionOrAuto<Pos> { + /// The <position> value. + Position(Pos), + /// The keyword `auto`. + Auto, +} + +pub use self::GenericPositionOrAuto as PositionOrAuto; + +impl<Pos> PositionOrAuto<Pos> { + /// Return `auto`. + #[inline] + pub fn auto() -> Self { + PositionOrAuto::Auto + } +} + +/// A generic value for the `z-index` property. +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + MallocSizeOf, + PartialEq, + Parse, + SpecifiedValueInfo, + ToAnimatedZero, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C, u8)] +pub enum GenericZIndex<I> { + /// An integer value. + Integer(I), + /// The keyword `auto`. + Auto, +} + +pub use self::GenericZIndex as ZIndex; + +impl<Integer> ZIndex<Integer> { + /// Returns `auto` + #[inline] + pub fn auto() -> Self { + ZIndex::Auto + } + + /// Returns whether `self` is `auto`. + #[inline] + pub fn is_auto(self) -> bool { + matches!(self, ZIndex::Auto) + } + + /// Returns the integer value if it is an integer, or `auto`. + #[inline] + pub fn integer_or(self, auto: Integer) -> Integer { + match self { + ZIndex::Integer(n) => n, + ZIndex::Auto => auto, + } + } +} + +/// A generic value for the `<ratio>` value. +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToAnimatedZero, + ToComputedValue, + ToResolvedValue, + ToShmem, +)] +#[repr(C)] +pub struct Ratio<N>(pub N, pub N); + +impl<N> ToCss for Ratio<N> +where + N: ToCss, +{ + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + self.0.to_css(dest)?; + // Even though 1 could be omitted, we don't per + // https://drafts.csswg.org/css-values-4/#ratio-value: + // + // The second <number> is optional, defaulting to 1. However, + // <ratio> is always serialized with both components. + // + // And for compat reasons, see bug 1669742. + // + // We serialize with spaces for consistency with all other + // slash-delimited things, see + // https://github.com/w3c/csswg-drafts/issues/4282 + dest.write_str(" / ")?; + self.1.to_css(dest)?; + Ok(()) + } +} + +/// Ratio or None. +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToAnimatedZero, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C, u8)] +pub enum PreferredRatio<N> { + /// Without specified ratio + #[css(skip)] + None, + /// With specified ratio + Ratio(#[css(field_bound)] Ratio<N>), +} + +/// A generic value for the `aspect-ratio` property, the value is `auto || <ratio>`. +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToAnimatedZero, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C)] +pub struct GenericAspectRatio<N> { + /// Specifiy auto or not. + #[animation(constant)] + #[css(represents_keyword)] + pub auto: bool, + /// The preferred aspect-ratio value. + #[css(field_bound)] + pub ratio: PreferredRatio<N>, +} + +pub use self::GenericAspectRatio as AspectRatio; + +impl<N> AspectRatio<N> { + /// Returns `auto` + #[inline] + pub fn auto() -> Self { + AspectRatio { + auto: true, + ratio: PreferredRatio::None, + } + } +} diff --git a/servo/components/style/values/generics/rect.rs b/servo/components/style/values/generics/rect.rs new file mode 100644 index 0000000000..dee21459a7 --- /dev/null +++ b/servo/components/style/values/generics/rect.rs @@ -0,0 +1,126 @@ +/* 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/. */ + +//! Generic types for CSS values that are composed of four sides. + +use crate::parser::{Parse, ParserContext}; +use cssparser::Parser; +use std::fmt::{self, Write}; +use style_traits::{CssWriter, ParseError, ToCss}; + +/// A CSS value made of four components, where its `ToCss` impl will try to +/// serialize as few components as possible, like for example in `border-width`. +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToAnimatedValue, + ToAnimatedZero, + ToComputedValue, + ToResolvedValue, + ToShmem, +)] +#[repr(C)] +pub struct Rect<T>(pub T, pub T, pub T, pub T); + +impl<T> Rect<T> { + /// Returns a new `Rect<T>` value. + pub fn new(first: T, second: T, third: T, fourth: T) -> Self { + Rect(first, second, third, fourth) + } +} + +impl<T> Rect<T> +where + T: Clone, +{ + /// Returns a rect with all the values equal to `v`. + pub fn all(v: T) -> Self { + Rect::new(v.clone(), v.clone(), v.clone(), v) + } + + /// Parses a new `Rect<T>` value with the given parse function. + pub fn parse_with<'i, 't, Parse>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + parse: Parse, + ) -> Result<Self, ParseError<'i>> + where + Parse: Fn(&ParserContext, &mut Parser<'i, 't>) -> Result<T, ParseError<'i>>, + { + let first = parse(context, input)?; + let second = if let Ok(second) = input.try_parse(|i| parse(context, i)) { + second + } else { + // <first> + return Ok(Self::new( + first.clone(), + first.clone(), + first.clone(), + first, + )); + }; + let third = if let Ok(third) = input.try_parse(|i| parse(context, i)) { + third + } else { + // <first> <second> + return Ok(Self::new(first.clone(), second.clone(), first, second)); + }; + let fourth = if let Ok(fourth) = input.try_parse(|i| parse(context, i)) { + fourth + } else { + // <first> <second> <third> + return Ok(Self::new(first, second.clone(), third, second)); + }; + // <first> <second> <third> <fourth> + Ok(Self::new(first, second, third, fourth)) + } +} + +impl<T> Parse for Rect<T> +where + T: Clone + Parse, +{ + #[inline] + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + Self::parse_with(context, input, T::parse) + } +} + +impl<T> ToCss for Rect<T> +where + T: PartialEq + ToCss, +{ + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + self.0.to_css(dest)?; + let same_vertical = self.0 == self.2; + let same_horizontal = self.1 == self.3; + if same_vertical && same_horizontal && self.0 == self.1 { + return Ok(()); + } + dest.write_str(" ")?; + self.1.to_css(dest)?; + if same_vertical && same_horizontal { + return Ok(()); + } + dest.write_str(" ")?; + self.2.to_css(dest)?; + if same_horizontal { + return Ok(()); + } + dest.write_str(" ")?; + self.3.to_css(dest) + } +} diff --git a/servo/components/style/values/generics/size.rs b/servo/components/style/values/generics/size.rs new file mode 100644 index 0000000000..e00eef28b9 --- /dev/null +++ b/servo/components/style/values/generics/size.rs @@ -0,0 +1,99 @@ +/* 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/. */ + +//! Generic type for CSS properties that are composed by two dimensions. + +use crate::parser::ParserContext; +use crate::Zero; +use cssparser::Parser; +use std::fmt::{self, Write}; +use style_traits::{CssWriter, ParseError, ToCss}; + +/// A generic size, for `border-*-radius` longhand properties, or +/// `border-spacing`. +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToAnimatedZero, + ToAnimatedValue, + ToComputedValue, + ToResolvedValue, + ToShmem, +)] +#[allow(missing_docs)] +#[repr(C)] +pub struct Size2D<L> { + pub width: L, + pub height: L, +} + +impl<L> Size2D<L> { + #[inline] + /// Create a new `Size2D` for an area of given width and height. + pub fn new(width: L, height: L) -> Self { + Self { width, height } + } + + /// Returns the width component. + pub fn width(&self) -> &L { + &self.width + } + + /// Returns the height component. + pub fn height(&self) -> &L { + &self.height + } + + /// Parse a `Size2D` with a given parsing function. + pub fn parse_with<'i, 't, F>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + parse_one: F, + ) -> Result<Self, ParseError<'i>> + where + L: Clone, + F: Fn(&ParserContext, &mut Parser<'i, 't>) -> Result<L, ParseError<'i>>, + { + let first = parse_one(context, input)?; + let second = input + .try_parse(|i| parse_one(context, i)) + .unwrap_or_else(|_| first.clone()); + Ok(Self::new(first, second)) + } +} + +impl<L> ToCss for Size2D<L> +where + L: ToCss + PartialEq, +{ + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + self.width.to_css(dest)?; + + if self.height != self.width { + dest.write_str(" ")?; + self.height.to_css(dest)?; + } + + Ok(()) + } +} + +impl<L: Zero> Zero for Size2D<L> { + fn zero() -> Self { + Self::new(L::zero(), L::zero()) + } + + fn is_zero(&self) -> bool { + self.width.is_zero() && self.height.is_zero() + } +} diff --git a/servo/components/style/values/generics/svg.rs b/servo/components/style/values/generics/svg.rs new file mode 100644 index 0000000000..26a33228e8 --- /dev/null +++ b/servo/components/style/values/generics/svg.rs @@ -0,0 +1,221 @@ +/* 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/. */ + +//! Generic types for CSS values in SVG + +use crate::parser::{Parse, ParserContext}; +use cssparser::Parser; +use style_traits::ParseError; + +/// The fallback of an SVG paint server value. +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Debug, + MallocSizeOf, + PartialEq, + Parse, + SpecifiedValueInfo, + ToAnimatedValue, + ToAnimatedZero, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C, u8)] +pub enum GenericSVGPaintFallback<C> { + /// The `none` keyword. + None, + /// A magic value that represents no fallback specified and serializes to + /// the empty string. + #[css(skip)] + Unset, + /// A color. + Color(C), +} + +pub use self::GenericSVGPaintFallback as SVGPaintFallback; + +/// An SVG paint value +/// +/// <https://www.w3.org/TR/SVG2/painting.html#SpecifyingPaint> +#[animation(no_bound(Url))] +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToAnimatedValue, + ToAnimatedZero, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C)] +pub struct GenericSVGPaint<Color, Url> { + /// The paint source. + pub kind: GenericSVGPaintKind<Color, Url>, + /// The fallback color. + pub fallback: GenericSVGPaintFallback<Color>, +} + +pub use self::GenericSVGPaint as SVGPaint; + +impl<C, U> Default for SVGPaint<C, U> { + fn default() -> Self { + Self { + kind: SVGPaintKind::None, + fallback: SVGPaintFallback::Unset, + } + } +} + +/// An SVG paint value without the fallback. +/// +/// Whereas the spec only allows PaintServer to have a fallback, Gecko lets the +/// context properties have a fallback as well. +#[animation(no_bound(U))] +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Debug, + MallocSizeOf, + PartialEq, + Parse, + SpecifiedValueInfo, + ToAnimatedValue, + ToAnimatedZero, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C, u8)] +pub enum GenericSVGPaintKind<C, U> { + /// `none` + #[animation(error)] + None, + /// `<color>` + Color(C), + /// `url(...)` + #[animation(error)] + PaintServer(U), + /// `context-fill` + ContextFill, + /// `context-stroke` + ContextStroke, +} + +pub use self::GenericSVGPaintKind as SVGPaintKind; + +impl<C: Parse, U: Parse> Parse for SVGPaint<C, U> { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + let kind = SVGPaintKind::parse(context, input)?; + if matches!(kind, SVGPaintKind::None | SVGPaintKind::Color(..)) { + return Ok(SVGPaint { + kind, + fallback: SVGPaintFallback::Unset, + }); + } + let fallback = input + .try_parse(|i| SVGPaintFallback::parse(context, i)) + .unwrap_or(SVGPaintFallback::Unset); + Ok(SVGPaint { kind, fallback }) + } +} + +/// An SVG length value supports `context-value` in addition to length. +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToAnimatedValue, + ToAnimatedZero, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C, u8)] +pub enum GenericSVGLength<L> { + /// `<length> | <percentage> | <number>` + LengthPercentage(L), + /// `context-value` + #[animation(error)] + ContextValue, +} + +pub use self::GenericSVGLength as SVGLength; + +/// Generic value for stroke-dasharray. +#[derive( + Clone, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToAnimatedValue, + ToAnimatedZero, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C, u8)] +pub enum GenericSVGStrokeDashArray<L> { + /// `[ <length> | <percentage> | <number> ]#` + #[css(comma)] + Values(#[css(if_empty = "none", iterable)] crate::OwnedSlice<L>), + /// `context-value` + ContextValue, +} + +pub use self::GenericSVGStrokeDashArray as SVGStrokeDashArray; + +/// An SVG opacity value accepts `context-{fill,stroke}-opacity` in +/// addition to opacity value. +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + MallocSizeOf, + PartialEq, + Parse, + SpecifiedValueInfo, + ToAnimatedZero, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C, u8)] +pub enum GenericSVGOpacity<OpacityType> { + /// `<opacity-value>` + Opacity(OpacityType), + /// `context-fill-opacity` + #[animation(error)] + ContextFillOpacity, + /// `context-stroke-opacity` + #[animation(error)] + ContextStrokeOpacity, +} + +pub use self::GenericSVGOpacity as SVGOpacity; diff --git a/servo/components/style/values/generics/text.rs b/servo/components/style/values/generics/text.rs new file mode 100644 index 0000000000..0062f30470 --- /dev/null +++ b/servo/components/style/values/generics/text.rs @@ -0,0 +1,157 @@ +/* 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/. */ + +//! Generic types for text properties. + +use crate::parser::ParserContext; +use crate::values::animated::ToAnimatedZero; +use cssparser::Parser; +use style_traits::ParseError; + +/// A generic value for the `initial-letter` property. +#[derive( + Clone, + Copy, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +pub enum InitialLetter<Number, Integer> { + /// `normal` + Normal, + /// `<number> <integer>?` + Specified(Number, Option<Integer>), +} + +impl<N, I> InitialLetter<N, I> { + /// Returns `normal`. + #[inline] + pub fn normal() -> Self { + InitialLetter::Normal + } +} + +/// A generic spacing value for the `letter-spacing` and `word-spacing` properties. +#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] +pub enum Spacing<Value> { + /// `normal` + Normal, + /// `<value>` + Value(Value), +} + +impl<Value> Spacing<Value> { + /// Returns `normal`. + #[inline] + pub fn normal() -> Self { + Spacing::Normal + } + + /// Parses. + #[inline] + pub fn parse_with<'i, 't, F>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + parse: F, + ) -> Result<Self, ParseError<'i>> + where + F: FnOnce(&ParserContext, &mut Parser<'i, 't>) -> Result<Value, ParseError<'i>>, + { + if input + .try_parse(|i| i.expect_ident_matching("normal")) + .is_ok() + { + return Ok(Spacing::Normal); + } + parse(context, input).map(Spacing::Value) + } +} + +#[cfg(feature = "gecko")] +fn line_height_moz_block_height_enabled(context: &ParserContext) -> bool { + context.in_ua_sheet() || + static_prefs::pref!("layout.css.line-height-moz-block-height.content.enabled") +} + +/// A generic value for the `line-height` property. +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToAnimatedValue, + ToCss, + ToShmem, + ToResolvedValue, + Parse, +)] +#[repr(C, u8)] +pub enum GenericLineHeight<N, L> { + /// `normal` + Normal, + /// `-moz-block-height` + #[cfg(feature = "gecko")] + #[parse(condition = "line_height_moz_block_height_enabled")] + MozBlockHeight, + /// `<number>` + Number(N), + /// `<length-percentage>` + Length(L), +} + +pub use self::GenericLineHeight as LineHeight; + +impl<N, L> ToAnimatedZero for LineHeight<N, L> { + #[inline] + fn to_animated_zero(&self) -> Result<Self, ()> { + Err(()) + } +} + +impl<N, L> LineHeight<N, L> { + /// Returns `normal`. + #[inline] + pub fn normal() -> Self { + LineHeight::Normal + } +} + +/// Implements type for text-decoration-thickness +/// which takes the grammar of auto | from-font | <length> | <percentage> +/// +/// https://drafts.csswg.org/css-text-decor-4/ +#[repr(C, u8)] +#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] +#[derive( + Animate, + Clone, + Copy, + ComputeSquaredDistance, + ToAnimatedZero, + Debug, + Eq, + MallocSizeOf, + Parse, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[allow(missing_docs)] +pub enum GenericTextDecorationLength<L> { + LengthPercentage(L), + Auto, + FromFont, +} diff --git a/servo/components/style/values/generics/transform.rs b/servo/components/style/values/generics/transform.rs new file mode 100644 index 0000000000..07ae2fa0dc --- /dev/null +++ b/servo/components/style/values/generics/transform.rs @@ -0,0 +1,830 @@ +/* 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/. */ + +//! Generic types for CSS values that are related to transformations. + +use crate::values::computed::length::Length as ComputedLength; +use crate::values::computed::length::LengthPercentage as ComputedLengthPercentage; +use crate::values::specified::angle::Angle as SpecifiedAngle; +use crate::values::specified::length::Length as SpecifiedLength; +use crate::values::specified::length::LengthPercentage as SpecifiedLengthPercentage; +use crate::values::{computed, CSSFloat}; +use crate::Zero; +use euclid; +use euclid::default::{Rect, Transform3D}; +use std::fmt::{self, Write}; +use style_traits::{CssWriter, ToCss}; + +/// A generic 2D transformation matrix. +#[allow(missing_docs)] +#[derive( + Clone, + Copy, + Debug, + Deserialize, + MallocSizeOf, + PartialEq, + Serialize, + SpecifiedValueInfo, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[css(comma, function = "matrix")] +#[repr(C)] +pub struct GenericMatrix<T> { + pub a: T, + pub b: T, + pub c: T, + pub d: T, + pub e: T, + pub f: T, +} + +pub use self::GenericMatrix as Matrix; + +#[allow(missing_docs)] +#[cfg_attr(rustfmt, rustfmt_skip)] +#[css(comma, function = "matrix3d")] +#[derive( + Clone, + Copy, + Debug, + Deserialize, + MallocSizeOf, + PartialEq, + Serialize, + SpecifiedValueInfo, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C)] +pub struct GenericMatrix3D<T> { + pub m11: T, pub m12: T, pub m13: T, pub m14: T, + pub m21: T, pub m22: T, pub m23: T, pub m24: T, + pub m31: T, pub m32: T, pub m33: T, pub m34: T, + pub m41: T, pub m42: T, pub m43: T, pub m44: T, +} + +pub use self::GenericMatrix3D as Matrix3D; + +#[cfg_attr(rustfmt, rustfmt_skip)] +impl<T: Into<f64>> From<Matrix<T>> for Transform3D<f64> { + #[inline] + fn from(m: Matrix<T>) -> Self { + Transform3D::new( + m.a.into(), m.b.into(), 0.0, 0.0, + m.c.into(), m.d.into(), 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + m.e.into(), m.f.into(), 0.0, 1.0, + ) + } +} + +#[cfg_attr(rustfmt, rustfmt_skip)] +impl<T: Into<f64>> From<Matrix3D<T>> for Transform3D<f64> { + #[inline] + fn from(m: Matrix3D<T>) -> Self { + Transform3D::new( + m.m11.into(), m.m12.into(), m.m13.into(), m.m14.into(), + m.m21.into(), m.m22.into(), m.m23.into(), m.m24.into(), + m.m31.into(), m.m32.into(), m.m33.into(), m.m34.into(), + m.m41.into(), m.m42.into(), m.m43.into(), m.m44.into(), + ) + } +} + +/// A generic transform origin. +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToAnimatedZero, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C)] +pub struct GenericTransformOrigin<H, V, Depth> { + /// The horizontal origin. + pub horizontal: H, + /// The vertical origin. + pub vertical: V, + /// The depth. + pub depth: Depth, +} + +pub use self::GenericTransformOrigin as TransformOrigin; + +impl<H, V, D> TransformOrigin<H, V, D> { + /// Returns a new transform origin. + pub fn new(horizontal: H, vertical: V, depth: D) -> Self { + Self { + horizontal, + vertical, + depth, + } + } +} + +fn is_same<N: PartialEq>(x: &N, y: &N) -> bool { + x == y +} + +#[derive( + Clone, + Debug, + Deserialize, + MallocSizeOf, + PartialEq, + Serialize, + SpecifiedValueInfo, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C, u8)] +/// A single operation in the list of a `transform` value +pub enum GenericTransformOperation<Angle, Number, Length, Integer, LengthPercentage> +where + Angle: Zero, + LengthPercentage: Zero, + Number: PartialEq, +{ + /// Represents a 2D 2x3 matrix. + Matrix(GenericMatrix<Number>), + /// Represents a 3D 4x4 matrix. + Matrix3D(GenericMatrix3D<Number>), + /// A 2D skew. + /// + /// If the second angle is not provided it is assumed zero. + /// + /// Syntax can be skew(angle) or skew(angle, angle) + #[css(comma, function)] + Skew(Angle, #[css(skip_if = "Zero::is_zero")] Angle), + /// skewX(angle) + #[css(function = "skewX")] + SkewX(Angle), + /// skewY(angle) + #[css(function = "skewY")] + SkewY(Angle), + /// translate(x, y) or translate(x) + #[css(comma, function)] + Translate( + LengthPercentage, + #[css(skip_if = "Zero::is_zero")] LengthPercentage, + ), + /// translateX(x) + #[css(function = "translateX")] + TranslateX(LengthPercentage), + /// translateY(y) + #[css(function = "translateY")] + TranslateY(LengthPercentage), + /// translateZ(z) + #[css(function = "translateZ")] + TranslateZ(Length), + /// translate3d(x, y, z) + #[css(comma, function = "translate3d")] + Translate3D(LengthPercentage, LengthPercentage, Length), + /// A 2D scaling factor. + /// + /// Syntax can be scale(factor) or scale(factor, factor) + #[css(comma, function)] + Scale(Number, #[css(contextual_skip_if = "is_same")] Number), + /// scaleX(factor) + #[css(function = "scaleX")] + ScaleX(Number), + /// scaleY(factor) + #[css(function = "scaleY")] + ScaleY(Number), + /// scaleZ(factor) + #[css(function = "scaleZ")] + ScaleZ(Number), + /// scale3D(factorX, factorY, factorZ) + #[css(comma, function = "scale3d")] + Scale3D(Number, Number, Number), + /// Describes a 2D Rotation. + /// + /// In a 3D scene `rotate(angle)` is equivalent to `rotateZ(angle)`. + #[css(function)] + Rotate(Angle), + /// Rotation in 3D space around the x-axis. + #[css(function = "rotateX")] + RotateX(Angle), + /// Rotation in 3D space around the y-axis. + #[css(function = "rotateY")] + RotateY(Angle), + /// Rotation in 3D space around the z-axis. + #[css(function = "rotateZ")] + RotateZ(Angle), + /// Rotation in 3D space. + /// + /// Generalization of rotateX, rotateY and rotateZ. + #[css(comma, function = "rotate3d")] + Rotate3D(Number, Number, Number, Angle), + /// Specifies a perspective projection matrix. + /// + /// Part of CSS Transform Module Level 2 and defined at + /// [ยง 13.1. 3D Transform Function](https://drafts.csswg.org/css-transforms-2/#funcdef-perspective). + /// + /// The value must be greater than or equal to zero. + #[css(function)] + Perspective(Length), + /// A intermediate type for interpolation of mismatched transform lists. + #[allow(missing_docs)] + #[css(comma, function = "interpolatematrix")] + InterpolateMatrix { + from_list: GenericTransform< + GenericTransformOperation<Angle, Number, Length, Integer, LengthPercentage>, + >, + to_list: GenericTransform< + GenericTransformOperation<Angle, Number, Length, Integer, LengthPercentage>, + >, + progress: computed::Percentage, + }, + /// A intermediate type for accumulation of mismatched transform lists. + #[allow(missing_docs)] + #[css(comma, function = "accumulatematrix")] + AccumulateMatrix { + from_list: GenericTransform< + GenericTransformOperation<Angle, Number, Length, Integer, LengthPercentage>, + >, + to_list: GenericTransform< + GenericTransformOperation<Angle, Number, Length, Integer, LengthPercentage>, + >, + count: Integer, + }, +} + +pub use self::GenericTransformOperation as TransformOperation; + +#[derive( + Clone, + Debug, + Deserialize, + MallocSizeOf, + PartialEq, + Serialize, + SpecifiedValueInfo, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C)] +/// A value of the `transform` property +pub struct GenericTransform<T>(#[css(if_empty = "none", iterable)] pub crate::OwnedSlice<T>); + +pub use self::GenericTransform as Transform; + +impl<Angle, Number, Length, Integer, LengthPercentage> + TransformOperation<Angle, Number, Length, Integer, LengthPercentage> +where + Angle: Zero, + LengthPercentage: Zero, + Number: PartialEq, +{ + /// Check if it is any rotate function. + pub fn is_rotate(&self) -> bool { + use self::TransformOperation::*; + matches!( + *self, + Rotate(..) | Rotate3D(..) | RotateX(..) | RotateY(..) | RotateZ(..) + ) + } + + /// Check if it is any translate function + pub fn is_translate(&self) -> bool { + use self::TransformOperation::*; + match *self { + Translate(..) | Translate3D(..) | TranslateX(..) | TranslateY(..) | TranslateZ(..) => { + true + }, + _ => false, + } + } + + /// Check if it is any scale function + pub fn is_scale(&self) -> bool { + use self::TransformOperation::*; + match *self { + Scale(..) | Scale3D(..) | ScaleX(..) | ScaleY(..) | ScaleZ(..) => true, + _ => false, + } + } +} + +/// Convert a length type into the absolute lengths. +pub trait ToAbsoluteLength { + /// Returns the absolute length as pixel value. + fn to_pixel_length(&self, containing_len: Option<ComputedLength>) -> Result<CSSFloat, ()>; +} + +impl ToAbsoluteLength for SpecifiedLength { + // This returns Err(()) if there is any relative length or percentage. We use this when + // parsing a transform list of DOMMatrix because we want to return a DOM Exception + // if there is relative length. + #[inline] + fn to_pixel_length(&self, _containing_len: Option<ComputedLength>) -> Result<CSSFloat, ()> { + match *self { + SpecifiedLength::NoCalc(len) => len.to_computed_pixel_length_without_context(), + SpecifiedLength::Calc(ref calc) => calc.to_computed_pixel_length_without_context(), + } + } +} + +impl ToAbsoluteLength for SpecifiedLengthPercentage { + // This returns Err(()) if there is any relative length or percentage. We use this when + // parsing a transform list of DOMMatrix because we want to return a DOM Exception + // if there is relative length. + #[inline] + fn to_pixel_length(&self, _containing_len: Option<ComputedLength>) -> Result<CSSFloat, ()> { + use self::SpecifiedLengthPercentage::*; + match *self { + Length(len) => len.to_computed_pixel_length_without_context(), + Calc(ref calc) => calc.to_computed_pixel_length_without_context(), + Percentage(..) => Err(()), + } + } +} + +impl ToAbsoluteLength for ComputedLength { + #[inline] + fn to_pixel_length(&self, _containing_len: Option<ComputedLength>) -> Result<CSSFloat, ()> { + Ok(self.px()) + } +} + +impl ToAbsoluteLength for ComputedLengthPercentage { + #[inline] + fn to_pixel_length(&self, containing_len: Option<ComputedLength>) -> Result<CSSFloat, ()> { + match containing_len { + Some(relative_len) => Ok(self.resolve(relative_len).px()), + // If we don't have reference box, we cannot resolve the used value, + // so only retrieve the length part. This will be used for computing + // distance without any layout info. + // + // FIXME(emilio): This looks wrong. + None => Ok(self.resolve(Zero::zero()).px()), + } + } +} + +/// Support the conversion to a 3d matrix. +pub trait ToMatrix { + /// Check if it is a 3d transform function. + fn is_3d(&self) -> bool; + + /// Return the equivalent 3d matrix. + fn to_3d_matrix( + &self, + reference_box: Option<&Rect<ComputedLength>>, + ) -> Result<Transform3D<f64>, ()>; +} + +/// A little helper to deal with both specified and computed angles. +pub trait ToRadians { + /// Return the radians value as a 64-bit floating point value. + fn radians64(&self) -> f64; +} + +impl ToRadians for computed::angle::Angle { + #[inline] + fn radians64(&self) -> f64 { + computed::angle::Angle::radians64(self) + } +} + +impl ToRadians for SpecifiedAngle { + #[inline] + fn radians64(&self) -> f64 { + computed::angle::Angle::from_degrees(self.degrees()).radians64() + } +} + +impl<Angle, Number, Length, Integer, LoP> ToMatrix + for TransformOperation<Angle, Number, Length, Integer, LoP> +where + Angle: Zero + ToRadians + Copy, + Number: PartialEq + Copy + Into<f32> + Into<f64>, + Length: ToAbsoluteLength, + LoP: Zero + ToAbsoluteLength, +{ + #[inline] + fn is_3d(&self) -> bool { + use self::TransformOperation::*; + match *self { + Translate3D(..) | TranslateZ(..) | Rotate3D(..) | RotateX(..) | RotateY(..) | + RotateZ(..) | Scale3D(..) | ScaleZ(..) | Perspective(..) | Matrix3D(..) => true, + _ => false, + } + } + + /// If |reference_box| is None, we will drop the percent part from translate because + /// we cannot resolve it without the layout info, for computed TransformOperation. + /// However, for specified TransformOperation, we will return Err(()) if there is any relative + /// lengths because the only caller, DOMMatrix, doesn't accept relative lengths. + #[inline] + fn to_3d_matrix( + &self, + reference_box: Option<&Rect<ComputedLength>>, + ) -> Result<Transform3D<f64>, ()> { + use self::TransformOperation::*; + use std::f64; + + let reference_width = reference_box.map(|v| v.size.width); + let reference_height = reference_box.map(|v| v.size.height); + let matrix = match *self { + Rotate3D(ax, ay, az, theta) => { + let theta = theta.radians64(); + let (ax, ay, az, theta) = + get_normalized_vector_and_angle(ax.into(), ay.into(), az.into(), theta); + Transform3D::rotation( + ax as f64, + ay as f64, + az as f64, + euclid::Angle::radians(theta), + ) + }, + RotateX(theta) => { + let theta = euclid::Angle::radians(theta.radians64()); + Transform3D::rotation(1., 0., 0., theta) + }, + RotateY(theta) => { + let theta = euclid::Angle::radians(theta.radians64()); + Transform3D::rotation(0., 1., 0., theta) + }, + RotateZ(theta) | Rotate(theta) => { + let theta = euclid::Angle::radians(theta.radians64()); + Transform3D::rotation(0., 0., 1., theta) + }, + Perspective(ref d) => { + let m = create_perspective_matrix(d.to_pixel_length(None)?); + m.cast() + }, + Scale3D(sx, sy, sz) => Transform3D::scale(sx.into(), sy.into(), sz.into()), + Scale(sx, sy) => Transform3D::scale(sx.into(), sy.into(), 1.), + ScaleX(s) => Transform3D::scale(s.into(), 1., 1.), + ScaleY(s) => Transform3D::scale(1., s.into(), 1.), + ScaleZ(s) => Transform3D::scale(1., 1., s.into()), + Translate3D(ref tx, ref ty, ref tz) => { + let tx = tx.to_pixel_length(reference_width)? as f64; + let ty = ty.to_pixel_length(reference_height)? as f64; + Transform3D::translation(tx, ty, tz.to_pixel_length(None)? as f64) + }, + Translate(ref tx, ref ty) => { + let tx = tx.to_pixel_length(reference_width)? as f64; + let ty = ty.to_pixel_length(reference_height)? as f64; + Transform3D::translation(tx, ty, 0.) + }, + TranslateX(ref t) => { + let t = t.to_pixel_length(reference_width)? as f64; + Transform3D::translation(t, 0., 0.) + }, + TranslateY(ref t) => { + let t = t.to_pixel_length(reference_height)? as f64; + Transform3D::translation(0., t, 0.) + }, + TranslateZ(ref z) => Transform3D::translation(0., 0., z.to_pixel_length(None)? as f64), + Skew(theta_x, theta_y) => Transform3D::skew( + euclid::Angle::radians(theta_x.radians64()), + euclid::Angle::radians(theta_y.radians64()), + ), + SkewX(theta) => Transform3D::skew( + euclid::Angle::radians(theta.radians64()), + euclid::Angle::radians(0.), + ), + SkewY(theta) => Transform3D::skew( + euclid::Angle::radians(0.), + euclid::Angle::radians(theta.radians64()), + ), + Matrix3D(m) => m.into(), + Matrix(m) => m.into(), + InterpolateMatrix { .. } | AccumulateMatrix { .. } => { + // TODO: Convert InterpolateMatrix/AccumulateMatrix into a valid Transform3D by + // the reference box and do interpolation on these two Transform3D matrices. + // Both Gecko and Servo don't support this for computing distance, and Servo + // doesn't support animations on InterpolateMatrix/AccumulateMatrix, so + // return an identity matrix. + // Note: DOMMatrix doesn't go into this arm. + Transform3D::identity() + }, + }; + Ok(matrix) + } +} + +impl<T> Transform<T> { + /// `none` + pub fn none() -> Self { + Transform(Default::default()) + } +} + +impl<T: ToMatrix> Transform<T> { + /// Return the equivalent 3d matrix of this transform list. + /// We return a pair: the first one is the transform matrix, and the second one + /// indicates if there is any 3d transform function in this transform list. + #[cfg_attr(rustfmt, rustfmt_skip)] + pub fn to_transform_3d_matrix( + &self, + reference_box: Option<&Rect<ComputedLength>> + ) -> Result<(Transform3D<CSSFloat>, bool), ()> { + let cast_3d_transform = |m: Transform3D<f64>| -> Transform3D<CSSFloat> { + use std::{f32, f64}; + let cast = |v: f64| { v.min(f32::MAX as f64).max(f32::MIN as f64) as f32 }; + Transform3D::new( + cast(m.m11), cast(m.m12), cast(m.m13), cast(m.m14), + cast(m.m21), cast(m.m22), cast(m.m23), cast(m.m24), + cast(m.m31), cast(m.m32), cast(m.m33), cast(m.m34), + cast(m.m41), cast(m.m42), cast(m.m43), cast(m.m44), + ) + }; + + let (m, is_3d) = self.to_transform_3d_matrix_f64(reference_box)?; + Ok((cast_3d_transform(m), is_3d)) + } + + /// Same as Transform::to_transform_3d_matrix but a f64 version. + pub fn to_transform_3d_matrix_f64( + &self, + reference_box: Option<&Rect<ComputedLength>>, + ) -> Result<(Transform3D<f64>, bool), ()> { + // We intentionally use Transform3D<f64> during computation to avoid error propagation + // because using f32 to compute triangle functions (e.g. in rotation()) is not + // accurate enough. In Gecko, we also use "double" to compute the triangle functions. + // Therefore, let's use Transform3D<f64> during matrix computation and cast it into f32 + // in the end. + let mut transform = Transform3D::<f64>::identity(); + let mut contain_3d = false; + + for operation in &*self.0 { + let matrix = operation.to_3d_matrix(reference_box)?; + contain_3d |= operation.is_3d(); + transform = matrix.then(&transform); + } + + Ok((transform, contain_3d)) + } +} + +/// Return the transform matrix from a perspective length. +#[inline] +pub fn create_perspective_matrix(d: CSSFloat) -> Transform3D<CSSFloat> { + // TODO(gw): The transforms spec says that perspective length must + // be positive. However, there is some confusion between the spec + // and browser implementations as to handling the case of 0 for the + // perspective value. Until the spec bug is resolved, at least ensure + // that a provided perspective value of <= 0.0 doesn't cause panics + // and behaves as it does in other browsers. + // See https://lists.w3.org/Archives/Public/www-style/2016Jan/0020.html for more details. + if d <= 0.0 { + Transform3D::identity() + } else { + Transform3D::perspective(d) + } +} + +/// Return the normalized direction vector and its angle for Rotate3D. +pub fn get_normalized_vector_and_angle<T: Zero>( + x: CSSFloat, + y: CSSFloat, + z: CSSFloat, + angle: T, +) -> (CSSFloat, CSSFloat, CSSFloat, T) { + use crate::values::computed::transform::DirectionVector; + use euclid::approxeq::ApproxEq; + let vector = DirectionVector::new(x, y, z); + if vector.square_length().approx_eq(&f32::zero()) { + // https://www.w3.org/TR/css-transforms-1/#funcdef-rotate3d + // A direction vector that cannot be normalized, such as [0, 0, 0], will cause the + // rotation to not be applied, so we use identity matrix (i.e. rotate3d(0, 0, 1, 0)). + (0., 0., 1., T::zero()) + } else { + let vector = vector.robust_normalize(); + (vector.x, vector.y, vector.z, angle) + } +} + +#[derive( + Clone, + Copy, + Debug, + Deserialize, + MallocSizeOf, + PartialEq, + Serialize, + SpecifiedValueInfo, + ToAnimatedZero, + ToComputedValue, + ToResolvedValue, + ToShmem, +)] +#[repr(C, u8)] +/// A value of the `Rotate` property +/// +/// <https://drafts.csswg.org/css-transforms-2/#individual-transforms> +/// cbindgen:private-default-tagged-enum-constructor=false +pub enum GenericRotate<Number, Angle> { + /// 'none' + None, + /// '<angle>' + Rotate(Angle), + /// '<number>{3} <angle>' + Rotate3D(Number, Number, Number, Angle), +} + +pub use self::GenericRotate as Rotate; + +/// A trait to check if the current 3D vector is parallel to the DirectionVector. +/// This is especially for serialization on Rotate. +pub trait IsParallelTo { + /// Returns true if this is parallel to the vector. + fn is_parallel_to(&self, vector: &computed::transform::DirectionVector) -> bool; +} + +impl<Number, Angle> ToCss for Rotate<Number, Angle> +where + Number: Copy + ToCss, + Angle: ToCss, + (Number, Number, Number): IsParallelTo, +{ + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: fmt::Write, + { + use crate::values::computed::transform::DirectionVector; + match *self { + Rotate::None => dest.write_str("none"), + Rotate::Rotate(ref angle) => angle.to_css(dest), + Rotate::Rotate3D(x, y, z, ref angle) => { + // If a 3d rotation is specified, the property must serialize with an axis + // specified. If the axis is parallel with the x, y, or z axises, it must + // serialize as the appropriate keyword. + // https://drafts.csswg.org/css-transforms-2/#individual-transform-serialization + let v = (x, y, z); + if v.is_parallel_to(&DirectionVector::new(1., 0., 0.)) { + dest.write_char('x')?; + } else if v.is_parallel_to(&DirectionVector::new(0., 1., 0.)) { + dest.write_char('y')?; + } else if v.is_parallel_to(&DirectionVector::new(0., 0., 1.)) { + dest.write_char('z')?; + } else { + x.to_css(dest)?; + dest.write_char(' ')?; + y.to_css(dest)?; + dest.write_char(' ')?; + z.to_css(dest)?; + } + dest.write_char(' ')?; + angle.to_css(dest) + }, + } + } +} + +#[derive( + Clone, + Copy, + Debug, + Deserialize, + MallocSizeOf, + PartialEq, + Serialize, + SpecifiedValueInfo, + ToAnimatedZero, + ToComputedValue, + ToResolvedValue, + ToShmem, +)] +#[repr(C, u8)] +/// A value of the `Scale` property +/// +/// <https://drafts.csswg.org/css-transforms-2/#individual-transforms> +/// cbindgen:private-default-tagged-enum-constructor=false +pub enum GenericScale<Number> { + /// 'none' + None, + /// '<number>{1,3}' + Scale(Number, Number, Number), +} + +pub use self::GenericScale as Scale; + +impl<Number> ToCss for Scale<Number> +where + Number: ToCss + PartialEq + Copy, + f32: From<Number>, +{ + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: fmt::Write, + f32: From<Number>, + { + match *self { + Scale::None => dest.write_str("none"), + Scale::Scale(ref x, ref y, ref z) => { + x.to_css(dest)?; + + let is_3d = f32::from(*z) != 1.0; + if is_3d || x != y { + dest.write_char(' ')?; + y.to_css(dest)?; + } + + if is_3d { + dest.write_char(' ')?; + z.to_css(dest)?; + } + Ok(()) + }, + } + } +} + +#[inline] +fn y_axis_and_z_axis_are_zero<LengthPercentage: Zero, Length: Zero>( + _: &LengthPercentage, + y: &LengthPercentage, + z: &Length, +) -> bool { + y.is_zero() && z.is_zero() +} + +#[derive( + Clone, + Debug, + Deserialize, + MallocSizeOf, + PartialEq, + Serialize, + SpecifiedValueInfo, + ToAnimatedZero, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C, u8)] +/// A value of the `translate` property +/// +/// https://drafts.csswg.org/css-transforms-2/#individual-transform-serialization: +/// +/// If a 2d translation is specified, the property must serialize with only one +/// or two values (per usual, if the second value is 0px, the default, it must +/// be omitted when serializing). +/// +/// If a 3d translation is specified and the value can be expressed as 2d, we treat as 2d and +/// serialize accoringly. Otherwise, we serialize all three values. +/// https://github.com/w3c/csswg-drafts/issues/3305 +/// +/// <https://drafts.csswg.org/css-transforms-2/#individual-transforms> +/// cbindgen:private-default-tagged-enum-constructor=false +pub enum GenericTranslate<LengthPercentage, Length> +where + LengthPercentage: Zero, + Length: Zero, +{ + /// 'none' + None, + /// <length-percentage> [ <length-percentage> <length>? ]? + Translate( + LengthPercentage, + #[css(contextual_skip_if = "y_axis_and_z_axis_are_zero")] LengthPercentage, + #[css(skip_if = "Zero::is_zero")] Length, + ), +} + +pub use self::GenericTranslate as Translate; + +#[allow(missing_docs)] +#[derive( + Clone, + Copy, + Debug, + MallocSizeOf, + Parse, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(u8)] +pub enum TransformStyle { + Flat, + #[css(keyword = "preserve-3d")] + Preserve3d, +} diff --git a/servo/components/style/values/generics/ui.rs b/servo/components/style/values/generics/ui.rs new file mode 100644 index 0000000000..04c2951c70 --- /dev/null +++ b/servo/components/style/values/generics/ui.rs @@ -0,0 +1,138 @@ +/* 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/. */ + +//! Generic values for UI properties. + +use crate::values::specified::ui::CursorKind; +use std::fmt::{self, Write}; +use style_traits::{CssWriter, ToCss}; + +/// A generic value for the `cursor` property. +/// +/// https://drafts.csswg.org/css-ui/#cursor +#[derive( + Clone, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToResolvedValue, + ToShmem, +)] +#[repr(C)] +pub struct GenericCursor<Image> { + /// The parsed images for the cursor. + pub images: crate::OwnedSlice<Image>, + /// The kind of the cursor [default | help | ...]. + pub keyword: CursorKind, +} + +pub use self::GenericCursor as Cursor; + +impl<Image> Cursor<Image> { + /// Set `cursor` to `auto` + #[inline] + pub fn auto() -> Self { + Self { + images: Default::default(), + keyword: CursorKind::Auto, + } + } +} + +impl<Image: ToCss> ToCss for Cursor<Image> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + for image in &*self.images { + image.to_css(dest)?; + dest.write_str(", ")?; + } + self.keyword.to_css(dest) + } +} + +/// A generic value for item of `image cursors`. +#[derive( + Clone, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToResolvedValue, + ToShmem, +)] +#[repr(C)] +pub struct GenericCursorImage<ImageUrl, Number> { + /// The url to parse images from. + pub url: ImageUrl, + /// Whether the image has a hotspot or not. + pub has_hotspot: bool, + /// The x coordinate. + pub hotspot_x: Number, + /// The y coordinate. + pub hotspot_y: Number, +} + +pub use self::GenericCursorImage as CursorImage; + +impl<ImageUrl: ToCss, Number: ToCss> ToCss for CursorImage<ImageUrl, Number> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + self.url.to_css(dest)?; + if self.has_hotspot { + dest.write_str(" ")?; + self.hotspot_x.to_css(dest)?; + dest.write_str(" ")?; + self.hotspot_y.to_css(dest)?; + } + Ok(()) + } +} + +/// A generic value for `scrollbar-color` property. +/// +/// https://drafts.csswg.org/css-scrollbars-1/#scrollbar-color +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToAnimatedValue, + ToAnimatedZero, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C, u8)] +pub enum GenericScrollbarColor<Color> { + /// `auto` + Auto, + /// `<color>{2}` + Colors { + /// First `<color>`, for color of the scrollbar thumb. + thumb: Color, + /// Second `<color>`, for color of the scrollbar track. + track: Color, + }, +} + +pub use self::GenericScrollbarColor as ScrollbarColor; + +impl<Color> Default for ScrollbarColor<Color> { + #[inline] + fn default() -> Self { + ScrollbarColor::Auto + } +} diff --git a/servo/components/style/values/generics/url.rs b/servo/components/style/values/generics/url.rs new file mode 100644 index 0000000000..46ed453e82 --- /dev/null +++ b/servo/components/style/values/generics/url.rs @@ -0,0 +1,47 @@ +/* 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/. */ + +//! Generic types for url properties. + +/// An image url or none, used for example in list-style-image +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Debug, + MallocSizeOf, + PartialEq, + Parse, + SpecifiedValueInfo, + ToAnimatedValue, + ToAnimatedZero, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C, u8)] +pub enum GenericUrlOrNone<U> { + /// `none` + None, + /// A URL. + Url(U), +} + +pub use self::GenericUrlOrNone as UrlOrNone; + +impl<Url> UrlOrNone<Url> { + /// Initial "none" value for properties such as `list-style-image` + pub fn none() -> Self { + UrlOrNone::None + } + + /// Returns whether the value is `none`. + pub fn is_none(&self) -> bool { + match *self { + UrlOrNone::None => true, + UrlOrNone::Url(..) => false, + } + } +} |