summaryrefslogtreecommitdiffstats
path: root/servo/components/style/values/generics
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--servo/components/style/values/generics/animation.rs140
-rw-r--r--servo/components/style/values/generics/background.rs54
-rw-r--r--servo/components/style/values/generics/basic_shape.rs513
-rw-r--r--servo/components/style/values/generics/border.rs257
-rw-r--r--servo/components/style/values/generics/box.rs208
-rw-r--r--servo/components/style/values/generics/calc.rs1343
-rw-r--r--servo/components/style/values/generics/color.rs196
-rw-r--r--servo/components/style/values/generics/column.rs45
-rw-r--r--servo/components/style/values/generics/counters.rs295
-rw-r--r--servo/components/style/values/generics/easing.rs137
-rw-r--r--servo/components/style/values/generics/effects.rs121
-rw-r--r--servo/components/style/values/generics/flex.rs33
-rw-r--r--servo/components/style/values/generics/font.rs271
-rw-r--r--servo/components/style/values/generics/grid.rs829
-rw-r--r--servo/components/style/values/generics/image.rs614
-rw-r--r--servo/components/style/values/generics/length.rs304
-rw-r--r--servo/components/style/values/generics/mod.rs386
-rw-r--r--servo/components/style/values/generics/motion.rs174
-rw-r--r--servo/components/style/values/generics/page.rs162
-rw-r--r--servo/components/style/values/generics/position.rs231
-rw-r--r--servo/components/style/values/generics/ratio.rs50
-rw-r--r--servo/components/style/values/generics/rect.rs126
-rw-r--r--servo/components/style/values/generics/size.rs99
-rw-r--r--servo/components/style/values/generics/svg.rs221
-rw-r--r--servo/components/style/values/generics/text.rs156
-rw-r--r--servo/components/style/values/generics/transform.rs879
-rw-r--r--servo/components/style/values/generics/ui.rs129
-rw-r--r--servo/components/style/values/generics/url.rs47
28 files changed, 8020 insertions, 0 deletions
diff --git a/servo/components/style/values/generics/animation.rs b/servo/components/style/values/generics/animation.rs
new file mode 100644
index 0000000000..edee9e9f25
--- /dev/null
+++ b/servo/components/style/values/generics/animation.rs
@@ -0,0 +1,140 @@
+/* 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 properties related to animations and transitions.
+
+use crate::values::generics::length::GenericLengthPercentageOrAuto;
+use crate::values::specified::animation::{ScrollAxis, ScrollFunction};
+use crate::values::TimelineName;
+use std::fmt::{self, Write};
+use style_traits::{CssWriter, ToCss};
+
+/// The view() notation.
+/// https://drafts.csswg.org/scroll-animations-1/#view-notation
+#[derive(
+ Clone,
+ Debug,
+ MallocSizeOf,
+ PartialEq,
+ SpecifiedValueInfo,
+ ToComputedValue,
+ ToCss,
+ ToResolvedValue,
+ ToShmem,
+)]
+#[css(function = "view")]
+#[repr(C)]
+pub struct GenericViewFunction<LengthPercent> {
+ /// The axis of scrolling that drives the progress of the timeline.
+ #[css(skip_if = "ScrollAxis::is_default")]
+ pub axis: ScrollAxis,
+ /// An adjustment of the view progress visibility range.
+ #[css(skip_if = "GenericViewTimelineInset::is_auto")]
+ #[css(field_bound)]
+ pub inset: GenericViewTimelineInset<LengthPercent>,
+}
+
+pub use self::GenericViewFunction as ViewFunction;
+
+/// A value for the <single-animation-timeline>.
+///
+/// https://drafts.csswg.org/css-animations-2/#typedef-single-animation-timeline
+#[derive(
+ Clone,
+ Debug,
+ MallocSizeOf,
+ PartialEq,
+ SpecifiedValueInfo,
+ ToComputedValue,
+ ToCss,
+ ToResolvedValue,
+ ToShmem,
+)]
+#[repr(C, u8)]
+pub enum GenericAnimationTimeline<LengthPercent> {
+ /// Use default timeline. The animation’s timeline is a DocumentTimeline.
+ Auto,
+ /// The scroll-timeline name or view-timeline-name.
+ /// https://drafts.csswg.org/scroll-animations-1/#scroll-timelines-named
+ /// https://drafts.csswg.org/scroll-animations-1/#view-timeline-name
+ Timeline(TimelineName),
+ /// The scroll() notation.
+ /// https://drafts.csswg.org/scroll-animations-1/#scroll-notation
+ Scroll(ScrollFunction),
+ /// The view() notation.
+ /// https://drafts.csswg.org/scroll-animations-1/#view-notation
+ View(#[css(field_bound)] GenericViewFunction<LengthPercent>),
+}
+
+pub use self::GenericAnimationTimeline as AnimationTimeline;
+
+impl<LengthPercent> AnimationTimeline<LengthPercent> {
+ /// Returns the `auto` value.
+ pub fn auto() -> Self {
+ Self::Auto
+ }
+
+ /// Returns true if it is auto (i.e. the default value).
+ pub fn is_auto(&self) -> bool {
+ matches!(self, Self::Auto)
+ }
+}
+
+/// A generic value for the `[ [ auto | <length-percentage> ]{1,2} ]`.
+///
+/// https://drafts.csswg.org/scroll-animations-1/#view-timeline-inset
+#[derive(
+ Clone,
+ Copy,
+ Debug,
+ MallocSizeOf,
+ PartialEq,
+ SpecifiedValueInfo,
+ ToComputedValue,
+ ToResolvedValue,
+ ToShmem,
+)]
+#[repr(C)]
+pub struct GenericViewTimelineInset<LengthPercent> {
+ /// The start inset in the relevant axis.
+ pub start: GenericLengthPercentageOrAuto<LengthPercent>,
+ /// The end inset.
+ pub end: GenericLengthPercentageOrAuto<LengthPercent>,
+}
+
+pub use self::GenericViewTimelineInset as ViewTimelineInset;
+
+impl<LengthPercent> ViewTimelineInset<LengthPercent> {
+ /// Returns true if it is auto.
+ #[inline]
+ fn is_auto(&self) -> bool {
+ self.start.is_auto() && self.end.is_auto()
+ }
+}
+
+impl<LengthPercent> ToCss for ViewTimelineInset<LengthPercent>
+where
+ LengthPercent: PartialEq + ToCss,
+{
+ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: Write,
+ {
+ self.start.to_css(dest)?;
+ if self.end != self.start {
+ dest.write_char(' ')?;
+ self.end.to_css(dest)?;
+ }
+ Ok(())
+ }
+}
+
+impl<LengthPercent> Default for ViewTimelineInset<LengthPercent> {
+ fn default() -> Self {
+ Self {
+ start: GenericLengthPercentageOrAuto::auto(),
+ end: GenericLengthPercentageOrAuto::auto(),
+ }
+ }
+}
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..00d682a169
--- /dev/null
+++ b/servo/components/style/values/generics/basic_shape.rs
@@ -0,0 +1,513 @@
+/* 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::{lists, 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)]
+#[derive(
+ Animate,
+ Clone,
+ ComputeSquaredDistance,
+ Debug,
+ MallocSizeOf,
+ PartialEq,
+ SpecifiedValueInfo,
+ ToAnimatedValue,
+ ToComputedValue,
+ ToCss,
+ ToResolvedValue,
+ ToShmem,
+)]
+#[animation(no_bound(U))]
+#[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)]
+#[derive(
+ Animate,
+ Clone,
+ ComputeSquaredDistance,
+ Debug,
+ MallocSizeOf,
+ PartialEq,
+ SpecifiedValueInfo,
+ ToAnimatedValue,
+ ToComputedValue,
+ ToCss,
+ ToResolvedValue,
+ ToShmem,
+)]
+#[animation(no_bound(I))]
+#[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)]
+#[derive(
+ Animate,
+ Clone,
+ ComputeSquaredDistance,
+ Debug,
+ MallocSizeOf,
+ PartialEq,
+ SpecifiedValueInfo,
+ ToAnimatedValue,
+ ToComputedValue,
+ ToResolvedValue,
+ ToShmem,
+)]
+#[css(function = "inset")]
+#[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)]
+#[derive(
+ Animate,
+ Clone,
+ ComputeSquaredDistance,
+ Copy,
+ Debug,
+ MallocSizeOf,
+ PartialEq,
+ SpecifiedValueInfo,
+ ToAnimatedValue,
+ ToComputedValue,
+ ToResolvedValue,
+ ToShmem,
+)]
+#[css(function)]
+#[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)]
+#[derive(
+ Animate,
+ Clone,
+ ComputeSquaredDistance,
+ Copy,
+ Debug,
+ MallocSizeOf,
+ PartialEq,
+ SpecifiedValueInfo,
+ ToAnimatedValue,
+ ToComputedValue,
+ ToResolvedValue,
+ ToShmem,
+)]
+#[css(function)]
+#[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>
+#[derive(
+ Clone,
+ Debug,
+ MallocSizeOf,
+ PartialEq,
+ SpecifiedValueInfo,
+ ToAnimatedValue,
+ ToComputedValue,
+ ToCss,
+ ToResolvedValue,
+ ToShmem,
+)]
+#[css(comma, function = "polygon")]
+#[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(
+ Animate,
+ Clone,
+ ComputeSquaredDistance,
+ 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
+#[derive(
+ Animate,
+ Clone,
+ ComputeSquaredDistance,
+ Debug,
+ MallocSizeOf,
+ PartialEq,
+ SpecifiedValueInfo,
+ ToAnimatedValue,
+ ToComputedValue,
+ ToCss,
+ ToResolvedValue,
+ ToShmem,
+)]
+#[css(comma)]
+#[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_char(')')
+ }
+}
+
+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_char(' ')?;
+ }
+ dest.write_str("at ")?;
+ self.position.to_css(dest)?;
+ dest.write_char(')')
+ }
+}
+
+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_char(' ')?;
+ self.semiaxis_y.to_css(dest)?;
+ dest.write_char(' ')?;
+ }
+ dest.write_str("at ")?;
+ self.position.to_css(dest)?;
+ dest.write_char(')')
+ }
+}
+
+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(());
+ }
+ let coordinates =
+ lists::by_computed_value::animate(&self.coordinates, &other.coordinates, procedure)?;
+ 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(());
+ }
+ lists::by_computed_value::squared_distance(&self.coordinates, &other.coordinates)
+ }
+}
+
+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..e4e78e3122
--- /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` 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..3cc2b05145
--- /dev/null
+++ b/servo/components/style/values/generics/box.rs
@@ -0,0 +1,208 @@
+/* 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;
+use std::fmt::{self, Write};
+use style_traits::{CssWriter, ToCss};
+
+#[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-sizing-4/#intrinsic-size-override
+#[derive(
+ Animate,
+ Clone,
+ ComputeSquaredDistance,
+ Debug,
+ MallocSizeOf,
+ PartialEq,
+ SpecifiedValueInfo,
+ ToComputedValue,
+ ToAnimatedValue,
+ ToAnimatedZero,
+ ToResolvedValue,
+ ToShmem,
+)]
+#[value_info(other_values = "auto")]
+#[repr(C, u8)]
+pub enum GenericContainIntrinsicSize<L> {
+ /// The keyword `none`.
+ None,
+ /// A non-negative length.
+ Length(L),
+ /// "auto <Length>"
+ AutoLength(L),
+}
+
+pub use self::GenericContainIntrinsicSize as ContainIntrinsicSize;
+
+impl<L: ToCss> ToCss for ContainIntrinsicSize<L> {
+ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: Write,
+ {
+ match *self {
+ Self::None => dest.write_str("none"),
+ Self::Length(ref l) => l.to_css(dest),
+ Self::AutoLength(ref l) => {
+ dest.write_str("auto ")?;
+ l.to_css(dest)
+ },
+ }
+ }
+}
+
+/// Note that we only implement -webkit-line-clamp as a single, longhand
+/// property for now, but the spec defines line-clamp as a shorthand for
+/// separate max-lines, block-ellipsis, and continue properties.
+///
+/// https://drafts.csswg.org/css-overflow-3/#line-clamp
+#[derive(
+ Clone,
+ ComputeSquaredDistance,
+ Copy,
+ Debug,
+ MallocSizeOf,
+ PartialEq,
+ SpecifiedValueInfo,
+ ToComputedValue,
+ ToAnimatedValue,
+ ToAnimatedZero,
+ ToResolvedValue,
+ ToShmem,
+)]
+#[repr(transparent)]
+#[value_info(other_values = "none")]
+pub struct GenericLineClamp<I>(pub I);
+
+pub use self::GenericLineClamp as LineClamp;
+
+impl<I: crate::Zero> LineClamp<I> {
+ /// Returns the `none` value.
+ pub fn none() -> Self {
+ Self(crate::Zero::zero())
+ }
+
+ /// Returns whether we're the `none` value.
+ pub fn is_none(&self) -> bool {
+ self.0.is_zero()
+ }
+}
+
+impl<I: crate::Zero + ToCss> ToCss for LineClamp<I> {
+ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: Write,
+ {
+ if self.is_none() {
+ return dest.write_str("none");
+ }
+ self.0.to_css(dest)
+ }
+}
+
+/// 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..3132e56342
--- /dev/null
+++ b/servo/components/style/values/generics/calc.rs
@@ -0,0 +1,1343 @@
+/* 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 num_traits::{Float, Zero};
+use smallvec::SmallVec;
+use std::fmt::{self, Write};
+use std::ops::{Add, Div, Mul, Neg, Rem, Sub};
+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,
+}
+
+/// Whether we're a `mod` or `rem` function.
+#[derive(
+ Clone,
+ Copy,
+ Debug,
+ Deserialize,
+ MallocSizeOf,
+ PartialEq,
+ Serialize,
+ ToAnimatedZero,
+ ToResolvedValue,
+ ToShmem,
+)]
+#[repr(u8)]
+pub enum ModRemOp {
+ /// `mod()`
+ Mod,
+ /// `rem()`
+ Rem,
+}
+
+/// The strategy used in `round()`
+#[derive(
+ Clone,
+ Copy,
+ Debug,
+ Deserialize,
+ MallocSizeOf,
+ PartialEq,
+ Serialize,
+ ToAnimatedZero,
+ ToResolvedValue,
+ ToShmem,
+)]
+#[repr(u8)]
+pub enum RoundingStrategy {
+ /// `round(nearest, a, b)`
+ /// round a to the nearest multiple of b
+ Nearest,
+ /// `round(up, a, b)`
+ /// round a up to the nearest multiple of b
+ Up,
+ /// `round(down, a, b)`
+ /// round a down to the nearest multiple of b
+ Down,
+ /// `round(to-zero, a, b)`
+ /// round a to the nearest multiple of b that is towards zero
+ ToZero,
+}
+
+/// 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,
+ Cap,
+ Ch,
+ Cqb,
+ Cqh,
+ Cqi,
+ Cqmax,
+ Cqmin,
+ Cqw,
+ Deg,
+ Dppx,
+ Dvb,
+ Dvh,
+ Dvi,
+ Dvmax,
+ Dvmin,
+ Dvw,
+ Em,
+ Ex,
+ Ic,
+ Lvb,
+ Lvh,
+ Lvi,
+ Lvmax,
+ Lvmin,
+ Lvw,
+ Px,
+ Rem,
+ Sec,
+ Svb,
+ Svh,
+ Svi,
+ Svmax,
+ Svmin,
+ Svw,
+ Vb,
+ Vh,
+ Vi,
+ 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 node that negates its children, e.g. Negate(1) == -1.
+ Negate(Box<GenericCalcNode<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>>,
+ },
+ /// A `round()` function.
+ Round {
+ /// The rounding strategy.
+ strategy: RoundingStrategy,
+ /// The value to round.
+ value: Box<GenericCalcNode<L>>,
+ /// The step value.
+ step: Box<GenericCalcNode<L>>,
+ },
+ /// A `mod()` or `rem()` function.
+ ModRem {
+ /// The dividend calculation.
+ dividend: Box<GenericCalcNode<L>>,
+ /// The divisor calculation.
+ divisor: Box<GenericCalcNode<L>>,
+ /// Is the function mod or rem?
+ op: ModRemOp,
+ },
+ /// A `hypot()` function
+ Hypot(crate::OwnedSlice<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 {
+ /// Returns the unitless value of this leaf.
+ fn unitless_value(&self) -> f32;
+
+ /// Whether this value is known-negative.
+ fn is_negative(&self) -> bool {
+ self.unitless_value().is_sign_negative()
+ }
+
+ /// Whether this value is infinite.
+ fn is_infinite(&self) -> bool {
+ self.unitless_value().is_infinite()
+ }
+
+ /// Whether this value is zero.
+ fn is_zero(&self) -> bool {
+ self.unitless_value().is_zero()
+ }
+
+ /// Whether this value is NaN.
+ fn is_nan(&self) -> bool {
+ self.unitless_value().is_nan()
+ }
+
+ /// Tries to merge one sum to another, that is, perform `x` + `y`.
+ fn try_sum_in_place(&mut self, other: &Self) -> Result<(), ()>;
+
+ /// Tries a generic arithmetic operation.
+ fn try_op<O>(&self, other: &Self, op: O) -> Result<Self, ()>
+ where
+ O: Fn(f32, f32) -> f32;
+
+ /// Map the value of this node with the given operation.
+ fn map(&mut self, op: impl FnMut(f32) -> f32);
+
+ /// Negates the leaf.
+ fn negate(&mut self) {
+ self.map(std::ops::Neg::neg);
+ }
+
+ /// Canonicalizes the expression if necessary.
+ fn simplify(&mut self);
+
+ /// Returns the sort key for simplification.
+ fn sort_key(&self) -> SortKey;
+}
+
+/// The level of any argument being serialized in `to_css_impl`.
+enum ArgumentLevel {
+ /// The root of a calculation tree.
+ CalculationRoot,
+ /// The root of an operand node's argument, e.g. `min(10, 20)`, `10` and `20` will have this
+ /// level, but min in this case will have `TopMost`.
+ ArgumentRoot,
+ /// Any other values serialized in the tree.
+ Nested,
+}
+
+impl<L: CalcNodeLeaf> CalcNode<L> {
+ /// Negate the node inline. If the node is distributive, it is replaced by the result,
+ /// otherwise the node is wrapped in a [`Negate`] node.
+ pub fn negate(&mut self) {
+ match *self {
+ CalcNode::Leaf(ref mut leaf) => leaf.map(|l| l.neg()),
+ CalcNode::Negate(ref mut value) => {
+ // Don't negate the value here. Replace `self` with it's child.
+ let result = mem::replace(
+ value.as_mut(),
+ Self::MinMax(Default::default(), MinMaxOp::Max),
+ );
+ *self = result;
+ },
+ CalcNode::Sum(ref mut children) => {
+ for child in children.iter_mut() {
+ child.negate();
+ }
+ },
+ CalcNode::MinMax(ref mut children, ref mut op) => {
+ for child in children.iter_mut() {
+ child.negate();
+ }
+
+ // Negating min-max means the operation is swapped.
+ *op = match *op {
+ MinMaxOp::Min => MinMaxOp::Max,
+ MinMaxOp::Max => MinMaxOp::Min,
+ };
+ },
+ CalcNode::Clamp {
+ ref mut min,
+ ref mut center,
+ ref mut max,
+ } => {
+ min.negate();
+ center.negate();
+ max.negate();
+
+ mem::swap(min, max);
+ },
+ CalcNode::Round {
+ ref mut value,
+ ref mut step,
+ ..
+ } => {
+ value.negate();
+ step.negate();
+ },
+ CalcNode::ModRem {
+ ref mut dividend,
+ ref mut divisor,
+ ..
+ } => {
+ dividend.negate();
+ divisor.negate();
+ },
+ CalcNode::Hypot(ref mut children) => {
+ for child in children.iter_mut() {
+ child.negate();
+ }
+ },
+ }
+ }
+
+ fn sort_key(&self) -> SortKey {
+ match *self {
+ Self::Leaf(ref l) => l.sort_key(),
+ _ => SortKey::Other,
+ }
+ }
+
+ /// Returns the leaf if we can (if simplification has allowed it).
+ pub fn as_leaf(&self) -> Option<&L> {
+ match *self {
+ Self::Leaf(ref l) => Some(l),
+ _ => None,
+ }
+ }
+
+ /// 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(()),
+ }
+ }
+
+ /// Tries to apply a generic arithmentic operator
+ fn try_op<O>(&self, other: &Self, op: O) -> Result<Self, ()>
+ where
+ O: Fn(f32, f32) -> f32,
+ {
+ match (self, other) {
+ (&CalcNode::Leaf(ref one), &CalcNode::Leaf(ref other)) => {
+ Ok(CalcNode::Leaf(one.try_op(other, op)?))
+ },
+ _ => Err(()),
+ }
+ }
+
+ /// Map the value of this node with the given operation.
+ pub fn map(&mut self, mut op: impl FnMut(f32) -> f32) {
+ fn map_internal<L: CalcNodeLeaf>(node: &mut CalcNode<L>, op: &mut impl FnMut(f32) -> f32) {
+ match node {
+ CalcNode::Leaf(l) => l.map(op),
+ CalcNode::Negate(v) => map_internal(v, op),
+ CalcNode::Sum(children) => {
+ for node in &mut **children {
+ map_internal(node, op);
+ }
+ },
+ CalcNode::MinMax(children, _) => {
+ for node in &mut **children {
+ map_internal(node, op);
+ }
+ },
+ CalcNode::Clamp { min, center, max } => {
+ map_internal(min, op);
+ map_internal(center, op);
+ map_internal(max, op);
+ },
+ CalcNode::Round { value, step, .. } => {
+ map_internal(value, op);
+ map_internal(step, op);
+ },
+ CalcNode::ModRem {
+ dividend, divisor, ..
+ } => {
+ map_internal(dividend, op);
+ map_internal(divisor, op);
+ },
+ CalcNode::Hypot(children) => {
+ for node in &mut **children {
+ map_internal(node, op);
+ }
+ },
+ }
+ }
+
+ map_internal(self, &mut op);
+ }
+
+ /// 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::Negate(ref c) => CalcNode::Negate(Box::new(c.map_leaves_internal(map))),
+ 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 }
+ },
+ Self::Round {
+ strategy,
+ ref value,
+ ref step,
+ } => {
+ let value = Box::new(value.map_leaves_internal(map));
+ let step = Box::new(step.map_leaves_internal(map));
+ CalcNode::Round {
+ strategy,
+ value,
+ step,
+ }
+ },
+ Self::ModRem {
+ ref dividend,
+ ref divisor,
+ op,
+ } => {
+ let dividend = Box::new(dividend.map_leaves_internal(map));
+ let divisor = Box::new(divisor.map_leaves_internal(map));
+ CalcNode::ModRem {
+ dividend,
+ divisor,
+ op,
+ }
+ },
+ Self::Hypot(ref c) => CalcNode::Hypot(map_children(c, map)),
+ }
+ }
+
+ /// 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>
+ + Mul<Output = O>
+ + Div<Output = O>
+ + Sub<Output = O>
+ + Zero
+ + Float
+ + Copy,
+ {
+ 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>
+ + Mul<Output = O>
+ + Div<Output = O>
+ + Sub<Output = O>
+ + Zero
+ + Float
+ + Copy,
+ F: FnMut(&L) -> Result<O, ()>,
+ {
+ Ok(match *self {
+ Self::Leaf(ref l) => return leaf_to_output_fn(l),
+ Self::Negate(ref c) => c.resolve_internal(leaf_to_output_fn)?.neg(),
+ 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)?;
+
+ if result.is_nan() {
+ return Ok(result);
+ }
+
+ for node in nodes.iter().skip(1) {
+ let candidate = node.resolve_internal(leaf_to_output_fn)?;
+
+ if candidate.is_nan() {
+ result = candidate;
+ break;
+ }
+
+ 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
+ }
+
+ if min.is_nan() || center.is_nan() || max.is_nan() {
+ result = <O as Float>::nan();
+ }
+
+ result
+ },
+ Self::Round {
+ strategy,
+ ref value,
+ ref step,
+ } => {
+ let value = value.resolve_internal(leaf_to_output_fn)?;
+ let step = step.resolve_internal(leaf_to_output_fn)?;
+
+ // TODO(emilio): Seems like at least a few of these
+ // special-cases could be removed if we do the math in a
+ // particular order.
+ if step.is_zero() {
+ return Ok(<O as Float>::nan());
+ }
+
+ if value.is_infinite() && step.is_infinite() {
+ return Ok(<O as Float>::nan());
+ }
+
+ if value.is_infinite() {
+ return Ok(value);
+ }
+
+ if step.is_infinite() {
+ match strategy {
+ RoundingStrategy::Nearest | RoundingStrategy::ToZero => {
+ return if value.is_sign_negative() {
+ Ok(<O as Float>::neg_zero())
+ } else {
+ Ok(<O as Zero>::zero())
+ }
+ },
+ RoundingStrategy::Up => {
+ return if !value.is_sign_negative() && !value.is_zero() {
+ Ok(<O as Float>::infinity())
+ } else if !value.is_sign_negative() && value.is_zero() {
+ Ok(value)
+ } else {
+ Ok(<O as Float>::neg_zero())
+ }
+ },
+ RoundingStrategy::Down => {
+ return if value.is_sign_negative() && !value.is_zero() {
+ Ok(<O as Float>::neg_infinity())
+ } else if value.is_sign_negative() && value.is_zero() {
+ Ok(value)
+ } else {
+ Ok(<O as Zero>::zero())
+ }
+ },
+ }
+ }
+
+ let div = value / step;
+ let lower_bound = div.floor() * step;
+ let upper_bound = div.ceil() * step;
+
+ match strategy {
+ RoundingStrategy::Nearest => {
+ // In case of a tie, use the upper bound
+ if value - lower_bound < upper_bound - value {
+ lower_bound
+ } else {
+ upper_bound
+ }
+ },
+ RoundingStrategy::Up => upper_bound,
+ RoundingStrategy::Down => lower_bound,
+ RoundingStrategy::ToZero => {
+ // In case of a tie, use the upper bound
+ if lower_bound.abs() < upper_bound.abs() {
+ lower_bound
+ } else {
+ upper_bound
+ }
+ },
+ }
+ },
+ Self::ModRem {
+ ref dividend,
+ ref divisor,
+ op,
+ } => {
+ let dividend = dividend.resolve_internal(leaf_to_output_fn)?;
+ let divisor = divisor.resolve_internal(leaf_to_output_fn)?;
+
+ // In mod(A, B) only, if B is infinite and A has opposite sign to B
+ // (including an oppositely-signed zero), the result is NaN.
+ // https://drafts.csswg.org/css-values/#round-infinities
+ if matches!(op, ModRemOp::Mod) &&
+ divisor.is_infinite() &&
+ dividend.is_sign_negative() != divisor.is_sign_negative()
+ {
+ return Ok(<O as Float>::nan());
+ }
+
+ match op {
+ ModRemOp::Mod => dividend - divisor * (dividend / divisor).floor(),
+ ModRemOp::Rem => dividend - divisor * (dividend / divisor).trunc(),
+ }
+ },
+ Self::Hypot(ref c) => {
+ let mut result: O = Zero::zero();
+ for child in &**c {
+ result = result + child.resolve_internal(leaf_to_output_fn)?.powi(2);
+ }
+ result.sqrt()
+ },
+ })
+ }
+
+ fn is_negative_leaf(&self) -> bool {
+ match *self {
+ Self::Leaf(ref l) => l.is_negative(),
+ _ => false,
+ }
+ }
+
+ fn is_zero_leaf(&self) -> bool {
+ match *self {
+ Self::Leaf(ref l) => l.is_zero(),
+ _ => false,
+ }
+ }
+
+ fn is_infinite_leaf(&self) -> bool {
+ match *self {
+ Self::Leaf(ref l) => l.is_infinite(),
+ _ => false,
+ }
+ }
+
+ /// Multiplies the node by a scalar.
+ pub fn mul_by(&mut self, scalar: f32) {
+ match *self {
+ Self::Leaf(ref mut l) => l.map(|v| v * scalar),
+ Self::Negate(ref mut value) => value.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);
+ }
+ },
+ Self::Round {
+ ref mut value,
+ ref mut step,
+ ..
+ } => {
+ value.mul_by(scalar);
+ step.mul_by(scalar);
+ },
+ Self::ModRem {
+ ref mut dividend,
+ ref mut divisor,
+ ..
+ } => {
+ dividend.mul_by(scalar);
+ divisor.mul_by(scalar);
+ },
+ // Not possible to handle negatives in this case, see: https://bugzil.la/1815448
+ Self::Hypot(ref mut children) => {
+ for node in &mut **children {
+ node.mul_by(scalar);
+ }
+ },
+ }
+ }
+
+ /// 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::Round {
+ ref mut value,
+ ref mut step,
+ ..
+ } => {
+ value.visit_depth_first_internal(f);
+ step.visit_depth_first_internal(f);
+ },
+ Self::ModRem {
+ ref mut dividend,
+ ref mut divisor,
+ ..
+ } => {
+ dividend.visit_depth_first_internal(f);
+ divisor.visit_depth_first_internal(f);
+ },
+ Self::Sum(ref mut children) |
+ Self::MinMax(ref mut children, _) |
+ Self::Hypot(ref mut children) => {
+ for child in &mut **children {
+ child.visit_depth_first_internal(f);
+ }
+ },
+ Self::Negate(ref mut value) => {
+ value.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.
+ ///
+ /// <https://drafts.csswg.org/css-values-4/#calc-simplification>
+ 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(&center) {
+ 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(&center) {
+ 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::Round {
+ strategy,
+ ref mut value,
+ ref mut step,
+ } => {
+ if step.is_zero_leaf() {
+ value.mul_by(f32::NAN);
+ return replace_self_with!(&mut **value);
+ }
+
+ if value.is_infinite_leaf() && step.is_infinite_leaf() {
+ value.mul_by(f32::NAN);
+ return replace_self_with!(&mut **value);
+ }
+
+ if value.is_infinite_leaf() {
+ return replace_self_with!(&mut **value);
+ }
+
+ if step.is_infinite_leaf() {
+ match strategy {
+ RoundingStrategy::Nearest | RoundingStrategy::ToZero => {
+ value.mul_by(0.);
+ return replace_self_with!(&mut **value);
+ },
+ RoundingStrategy::Up => {
+ if !value.is_negative_leaf() && !value.is_zero_leaf() {
+ value.mul_by(f32::INFINITY);
+ return replace_self_with!(&mut **value);
+ } else if !value.is_negative_leaf() && value.is_zero_leaf() {
+ return replace_self_with!(&mut **value);
+ } else {
+ value.mul_by(0.);
+ return replace_self_with!(&mut **value);
+ }
+ },
+ RoundingStrategy::Down => {
+ if value.is_negative_leaf() && !value.is_zero_leaf() {
+ value.mul_by(f32::INFINITY);
+ return replace_self_with!(&mut **value);
+ } else if value.is_negative_leaf() && value.is_zero_leaf() {
+ return replace_self_with!(&mut **value);
+ } else {
+ value.mul_by(0.);
+ return replace_self_with!(&mut **value);
+ }
+ },
+ }
+ }
+
+ if step.is_negative_leaf() {
+ step.negate();
+ }
+
+ let remainder = match value.try_op(step, Rem::rem) {
+ Ok(res) => res,
+ Err(..) => return,
+ };
+
+ let (mut lower_bound, mut upper_bound) = if value.is_negative_leaf() {
+ let upper_bound = match value.try_op(&remainder, Sub::sub) {
+ Ok(res) => res,
+ Err(..) => return,
+ };
+
+ let lower_bound = match upper_bound.try_op(&step, Sub::sub) {
+ Ok(res) => res,
+ Err(..) => return,
+ };
+
+ (lower_bound, upper_bound)
+ } else {
+ let lower_bound = match value.try_op(&remainder, Sub::sub) {
+ Ok(res) => res,
+ Err(..) => return,
+ };
+
+ let upper_bound = match lower_bound.try_op(&step, Add::add) {
+ Ok(res) => res,
+ Err(..) => return,
+ };
+
+ (lower_bound, upper_bound)
+ };
+
+ match strategy {
+ RoundingStrategy::Nearest => {
+ let lower_diff = match value.try_op(&lower_bound, Sub::sub) {
+ Ok(res) => res,
+ Err(..) => return,
+ };
+
+ let upper_diff = match upper_bound.try_op(value, Sub::sub) {
+ Ok(res) => res,
+ Err(..) => return,
+ };
+
+ // In case of a tie, use the upper bound
+ if lower_diff < upper_diff {
+ return replace_self_with!(&mut lower_bound);
+ } else {
+ return replace_self_with!(&mut upper_bound);
+ }
+ },
+ RoundingStrategy::Up => return replace_self_with!(&mut upper_bound),
+ RoundingStrategy::Down => return replace_self_with!(&mut lower_bound),
+ RoundingStrategy::ToZero => {
+ let mut lower_diff = lower_bound.clone();
+ let mut upper_diff = upper_bound.clone();
+
+ if lower_diff.is_negative_leaf() {
+ lower_diff.negate();
+ }
+
+ if upper_diff.is_negative_leaf() {
+ upper_diff.negate();
+ }
+
+ // In case of a tie, use the upper bound
+ if lower_diff < upper_diff {
+ return replace_self_with!(&mut lower_bound);
+ } else {
+ return replace_self_with!(&mut upper_bound);
+ }
+ },
+ };
+ },
+ Self::ModRem {
+ ref dividend,
+ ref divisor,
+ op,
+ } => {
+ let mut result = dividend.clone();
+
+ // In mod(A, B) only, if B is infinite and A has opposite sign to B
+ // (including an oppositely-signed zero), the result is NaN.
+ // https://drafts.csswg.org/css-values/#round-infinities
+ if matches!(op, ModRemOp::Mod) &&
+ divisor.is_infinite_leaf() &&
+ dividend.is_negative_leaf() != divisor.is_negative_leaf()
+ {
+ result.mul_by(f32::NAN);
+ return replace_self_with!(&mut *result);
+ }
+
+ let result = match op {
+ ModRemOp::Mod => dividend.try_op(divisor, |a, b| a - b * (a / b).floor()),
+ ModRemOp::Rem => dividend.try_op(divisor, |a, b| a - b * (a / b).trunc()),
+ };
+
+ let mut result = match result {
+ Ok(res) => res,
+ Err(..) => return,
+ };
+
+ return replace_self_with!(&mut result);
+ },
+ 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::Hypot(ref children) => {
+ let mut result = match children[0].try_op(&children[0], Mul::mul) {
+ Ok(res) => res,
+ Err(..) => return,
+ };
+
+ for child in children.iter().skip(1) {
+ let square = match child.try_op(&child, Mul::mul) {
+ Ok(res) => res,
+ Err(..) => return,
+ };
+ result = match result.try_op(&square, Add::add) {
+ Ok(res) => res,
+ Err(..) => return,
+ }
+ }
+
+ result = match result.try_op(&result, |a, _| a.sqrt()) {
+ Ok(res) => res,
+ Err(..) => return,
+ };
+
+ replace_self_with!(&mut result);
+ },
+ Self::Negate(ref mut child) => {
+ // Step 6.
+ match &mut **child {
+ CalcNode::Leaf(_) => {
+ // 1. If root’s child is a numeric value, return an equivalent numeric value, but
+ // with the value negated (0 - value).
+ child.negate();
+ replace_self_with!(&mut **child);
+ },
+ CalcNode::Negate(value) => {
+ // 2. If root’s child is a Negate node, return the child’s child.
+ replace_self_with!(&mut **value);
+ },
+ _ => {
+ // 3. Return root.
+ },
+ }
+ },
+ 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>, level: ArgumentLevel) -> 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
+ },
+ Self::Round { strategy, .. } => {
+ match strategy {
+ RoundingStrategy::Nearest => dest.write_str("round("),
+ RoundingStrategy::Up => dest.write_str("round(up, "),
+ RoundingStrategy::Down => dest.write_str("round(down, "),
+ RoundingStrategy::ToZero => dest.write_str("round(to-zero, "),
+ }?;
+
+ true
+ },
+ Self::ModRem { op, .. } => {
+ dest.write_str(match op {
+ ModRemOp::Mod => "mod(",
+ ModRemOp::Rem => "rem(",
+ })?;
+
+ true
+ },
+ Self::Hypot(_) => {
+ dest.write_str("hypot(")?;
+ true
+ },
+ Self::Negate(_) => {
+ // We never generate a [`Negate`] node as the root of a calculation, only inside
+ // [`Sum`] nodes as a child. Because negate nodes are handled by the [`Sum`] node
+ // directly (see below), this node will never be serialized.
+ debug_assert!(
+ false,
+ "We never serialize Negate nodes as they are handled inside Sum nodes."
+ );
+ dest.write_str("(-1 * ")?;
+ true
+ },
+ Self::Sum(_) => match level {
+ ArgumentLevel::CalculationRoot => {
+ dest.write_str("calc(")?;
+ true
+ },
+ ArgumentLevel::ArgumentRoot => false,
+ ArgumentLevel::Nested => {
+ dest.write_str("(")?;
+ true
+ },
+ },
+ Self::Leaf(_) => match level {
+ ArgumentLevel::CalculationRoot => {
+ dest.write_str("calc(")?;
+ true
+ },
+ ArgumentLevel::ArgumentRoot | ArgumentLevel::Nested => false,
+ },
+ };
+
+ match *self {
+ Self::MinMax(ref children, _) | Self::Hypot(ref children) => {
+ let mut first = true;
+ for child in &**children {
+ if !first {
+ dest.write_str(", ")?;
+ }
+ first = false;
+ child.to_css_impl(dest, ArgumentLevel::ArgumentRoot)?;
+ }
+ },
+ Self::Negate(ref value) => value.to_css_impl(dest, ArgumentLevel::Nested)?,
+ Self::Sum(ref children) => {
+ let mut first = true;
+ for child in &**children {
+ if !first {
+ match child {
+ Self::Leaf(l) => {
+ if l.is_negative() {
+ dest.write_str(" - ")?;
+ let mut negated = l.clone();
+ negated.negate();
+ negated.to_css(dest)?;
+ } else {
+ dest.write_str(" + ")?;
+ l.to_css(dest)?;
+ }
+ },
+ Self::Negate(n) => {
+ dest.write_str(" - ")?;
+ n.to_css_impl(dest, ArgumentLevel::Nested)?;
+ },
+ _ => {
+ dest.write_str(" + ")?;
+ child.to_css_impl(dest, ArgumentLevel::Nested)?;
+ },
+ }
+ } else {
+ first = false;
+ child.to_css_impl(dest, ArgumentLevel::Nested)?;
+ }
+ }
+ },
+ Self::Clamp {
+ ref min,
+ ref center,
+ ref max,
+ } => {
+ min.to_css_impl(dest, ArgumentLevel::ArgumentRoot)?;
+ dest.write_str(", ")?;
+ center.to_css_impl(dest, ArgumentLevel::ArgumentRoot)?;
+ dest.write_str(", ")?;
+ max.to_css_impl(dest, ArgumentLevel::ArgumentRoot)?;
+ },
+ Self::Round {
+ ref value,
+ ref step,
+ ..
+ } => {
+ value.to_css_impl(dest, ArgumentLevel::ArgumentRoot)?;
+ dest.write_str(", ")?;
+ step.to_css_impl(dest, ArgumentLevel::ArgumentRoot)?;
+ },
+ Self::ModRem {
+ ref dividend,
+ ref divisor,
+ ..
+ } => {
+ dividend.to_css_impl(dest, ArgumentLevel::ArgumentRoot)?;
+ dest.write_str(", ")?;
+ divisor.to_css_impl(dest, ArgumentLevel::ArgumentRoot)?;
+ },
+ Self::Leaf(ref l) => l.to_css(dest)?,
+ }
+
+ if write_closing_paren {
+ dest.write_char(')')?;
+ }
+ 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, ArgumentLevel::CalculationRoot)
+ }
+}
diff --git a/servo/components/style/values/generics/color.rs b/servo/components/style/values/generics/color.rs
new file mode 100644
index 0000000000..d143e9d3c8
--- /dev/null
+++ b/servo/components/style/values/generics/color.rs
@@ -0,0 +1,196 @@
+/* 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.
+
+use crate::color::mix::ColorInterpolationMethod;
+use crate::color::AbsoluteColor;
+use crate::values::specified::percentage::ToPercentage;
+use std::fmt::{self, Write};
+use style_traits::{CssWriter, ToCss};
+
+/// This struct represents a combined color from a numeric color and
+/// the current foreground color (currentcolor keyword).
+#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToAnimatedValue, ToShmem)]
+#[repr(C)]
+pub enum GenericColor<Percentage> {
+ /// The actual numeric color.
+ Absolute(AbsoluteColor),
+ /// The `CurrentColor` keyword.
+ CurrentColor,
+ /// The color-mix() function.
+ ColorMix(Box<GenericColorMix<Self, Percentage>>),
+}
+
+/// A restricted version of the css `color-mix()` function, which only supports
+/// percentages.
+///
+/// https://drafts.csswg.org/css-color-5/#color-mix
+#[derive(
+ Clone,
+ Debug,
+ MallocSizeOf,
+ PartialEq,
+ ToAnimatedValue,
+ ToComputedValue,
+ ToResolvedValue,
+ ToShmem,
+)]
+#[allow(missing_docs)]
+#[repr(C)]
+pub struct GenericColorMix<Color, Percentage> {
+ pub interpolation: ColorInterpolationMethod,
+ pub left: Color,
+ pub left_percentage: Percentage,
+ pub right: Color,
+ pub right_percentage: Percentage,
+ pub normalize_weights: bool,
+}
+
+pub use self::GenericColorMix as ColorMix;
+
+impl<Color: ToCss, Percentage: ToCss + ToPercentage> ToCss for ColorMix<Color, Percentage> {
+ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: Write,
+ {
+ fn can_omit<Percentage: ToPercentage>(
+ percent: &Percentage,
+ other: &Percentage,
+ is_left: bool,
+ ) -> bool {
+ if percent.is_calc() {
+ return false;
+ }
+ if percent.to_percentage() == 0.5 {
+ return other.to_percentage() == 0.5;
+ }
+ if is_left {
+ return false;
+ }
+ (1.0 - percent.to_percentage() - other.to_percentage()).abs() <= f32::EPSILON
+ }
+
+ dest.write_str("color-mix(")?;
+ self.interpolation.to_css(dest)?;
+ dest.write_str(", ")?;
+ self.left.to_css(dest)?;
+ if !can_omit(&self.left_percentage, &self.right_percentage, true) {
+ dest.write_char(' ')?;
+ self.left_percentage.to_css(dest)?;
+ }
+ dest.write_str(", ")?;
+ self.right.to_css(dest)?;
+ if !can_omit(&self.right_percentage, &self.left_percentage, false) {
+ dest.write_char(' ')?;
+ self.right_percentage.to_css(dest)?;
+ }
+ dest.write_char(')')
+ }
+}
+
+impl<Percentage> ColorMix<GenericColor<Percentage>, Percentage> {
+ /// Mix the colors so that we get a single color. If any of the 2 colors are
+ /// not mixable (perhaps not absolute?), then return None.
+ pub fn mix_to_absolute(&self) -> Option<AbsoluteColor>
+ where
+ Percentage: ToPercentage,
+ {
+ let left = self.left.as_absolute()?;
+ let right = self.right.as_absolute()?;
+
+ Some(crate::color::mix::mix(
+ self.interpolation,
+ &left,
+ self.left_percentage.to_percentage(),
+ &right,
+ self.right_percentage.to_percentage(),
+ self.normalize_weights,
+ ))
+ }
+}
+
+pub use self::GenericColor as Color;
+
+impl<Percentage> Color<Percentage> {
+ /// If this color is absolute return it's value, otherwise return None.
+ pub fn as_absolute(&self) -> Option<&AbsoluteColor> {
+ match *self {
+ Self::Absolute(ref absolute) => Some(absolute),
+ _ => None,
+ }
+ }
+
+ /// Returns a color value representing currentcolor.
+ pub fn currentcolor() -> Self {
+ Self::CurrentColor
+ }
+
+ /// Whether it is a currentcolor value (no numeric color component).
+ pub fn is_currentcolor(&self) -> bool {
+ matches!(*self, Self::CurrentColor)
+ }
+
+ /// Whether this color is an absolute color.
+ pub fn is_absolute(&self) -> bool {
+ matches!(*self, Self::Absolute(..))
+ }
+}
+
+/// Either `<color>` or `auto`.
+#[derive(
+ Animate,
+ Clone,
+ ComputeSquaredDistance,
+ Copy,
+ Debug,
+ MallocSizeOf,
+ PartialEq,
+ Parse,
+ SpecifiedValueInfo,
+ ToAnimatedValue,
+ ToAnimatedZero,
+ ToComputedValue,
+ ToResolvedValue,
+ ToCss,
+ ToShmem,
+)]
+#[repr(C, u8)]
+pub enum GenericColorOrAuto<C> {
+ /// A `<color>`.
+ Color(C),
+ /// `auto`
+ Auto,
+}
+
+pub use self::GenericColorOrAuto as ColorOrAuto;
+
+/// Caret color is effectively a ColorOrAuto, but resolves `auto` to
+/// currentColor.
+#[derive(
+ Animate,
+ Clone,
+ ComputeSquaredDistance,
+ Copy,
+ Debug,
+ MallocSizeOf,
+ PartialEq,
+ SpecifiedValueInfo,
+ ToAnimatedValue,
+ ToAnimatedZero,
+ ToComputedValue,
+ ToCss,
+ ToShmem,
+)]
+#[repr(transparent)]
+pub struct GenericCaretColor<C>(pub GenericColorOrAuto<C>);
+
+impl<C> GenericCaretColor<C> {
+ /// Returns the `auto` value.
+ pub fn auto() -> Self {
+ GenericCaretColor(GenericColorOrAuto::Auto)
+ }
+}
+
+pub use self::GenericCaretColor as CaretColor;
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..1d4518c57b
--- /dev/null
+++ b/servo/components/style/values/generics/counters.rs
@@ -0,0 +1,295 @@
+/* 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-layout-2013")]
+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::fmt::{self, Write};
+use std::ops::Deref;
+use style_traits::{CssWriter, ToCss};
+
+/// A name / value pair for counters.
+#[derive(
+ Clone,
+ Debug,
+ MallocSizeOf,
+ PartialEq,
+ SpecifiedValueInfo,
+ ToComputedValue,
+ 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,
+ /// If true, then this represents `reversed(name)`.
+ /// NOTE: It can only be true on `counter-reset` values.
+ pub is_reversed: bool,
+}
+pub use self::GenericCounterPair as CounterPair;
+
+impl<Integer> ToCss for CounterPair<Integer>
+where
+ Integer: ToCss + PartialEq<i32>,
+{
+ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: Write,
+ {
+ if self.is_reversed {
+ dest.write_str("reversed(")?;
+ }
+ self.name.to_css(dest)?;
+ if self.is_reversed {
+ dest.write_char(')')?;
+ if self.value == i32::min_value() {
+ return Ok(());
+ }
+ }
+ dest.write_char(' ')?;
+ self.value.to_css(dest)
+ }
+}
+
+/// 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>(#[css(field_bound)] 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` property.
+#[derive(
+ Clone,
+ Debug,
+ Default,
+ MallocSizeOf,
+ PartialEq,
+ SpecifiedValueInfo,
+ ToComputedValue,
+ ToCss,
+ ToResolvedValue,
+ ToShmem,
+)]
+#[repr(transparent)]
+pub struct GenericCounterSet<I>(#[css(field_bound)] pub GenericCounters<I>);
+pub use self::GenericCounterSet as CounterSet;
+
+impl<I> CounterSet<I> {
+ /// Returns a new value for `counter-set`.
+ #[inline]
+ pub fn new(counters: Vec<CounterPair<I>>) -> Self {
+ CounterSet(Counters(counters.into()))
+ }
+}
+
+impl<I> Deref for CounterSet<I> {
+ type Target = [CounterPair<I>];
+
+ #[inline]
+ fn deref(&self) -> &Self::Target {
+ &(self.0).0
+ }
+}
+
+/// A generic value for the `counter-reset` property.
+#[derive(
+ Clone,
+ Debug,
+ Default,
+ MallocSizeOf,
+ PartialEq,
+ SpecifiedValueInfo,
+ ToComputedValue,
+ ToCss,
+ ToResolvedValue,
+ ToShmem,
+)]
+#[repr(transparent)]
+pub struct GenericCounterReset<I>(#[css(field_bound)] pub GenericCounters<I>);
+pub use self::GenericCounterReset as CounterReset;
+
+impl<I> CounterReset<I> {
+ /// Returns a new value for `counter-reset`.
+ #[inline]
+ pub fn new(counters: Vec<CounterPair<I>>) -> Self {
+ CounterReset(Counters(counters.into()))
+ }
+}
+
+impl<I> Deref for CounterReset<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(field_bound)]
+ #[css(iterable, if_empty = "none")]
+ crate::OwnedSlice<GenericCounterPair<I>>,
+);
+pub use self::GenericCounters as Counters;
+
+#[cfg(feature = "servo-layout-2013")]
+type CounterStyleType = ListStyleType;
+
+#[cfg(feature = "gecko")]
+type CounterStyleType = CounterStyle;
+
+#[cfg(feature = "servo-layout-2013")]
+#[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<Image> {
+ /// `normal` reserved keyword.
+ Normal,
+ /// `none` reserved keyword.
+ None,
+ /// Content items.
+ Items(#[css(iterable)] crate::OwnedSlice<GenericContentItem<Image>>),
+}
+
+pub use self::GenericContent as Content;
+
+impl<Image> Content<Image> {
+ /// 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,
+ ToComputedValue,
+ SpecifiedValueInfo,
+ ToCss,
+ ToResolvedValue,
+ ToShmem,
+)]
+#[repr(u8)]
+pub enum GenericContentItem<I> {
+ /// Literal string content.
+ String(crate::OwnedStr),
+ /// `counter(name, style)`.
+ #[cfg(any(feature = "gecko", feature = "servo-layout-2013"))]
+ #[css(comma, function)]
+ Counter(CustomIdent, #[css(skip_if = "is_decimal")] CounterStyleType),
+ /// `counters(name, separator, style)`.
+ #[cfg(any(feature = "gecko", feature = "servo-layout-2013"))]
+ #[css(comma, function)]
+ Counters(
+ CustomIdent,
+ crate::OwnedStr,
+ #[css(skip_if = "is_decimal")] CounterStyleType,
+ ),
+ /// `open-quote`.
+ #[cfg(any(feature = "gecko", feature = "servo-layout-2013"))]
+ OpenQuote,
+ /// `close-quote`.
+ #[cfg(any(feature = "gecko", feature = "servo-layout-2013"))]
+ CloseQuote,
+ /// `no-open-quote`.
+ #[cfg(any(feature = "gecko", feature = "servo-layout-2013"))]
+ NoOpenQuote,
+ /// `no-close-quote`.
+ #[cfg(any(feature = "gecko", feature = "servo-layout-2013"))]
+ NoCloseQuote,
+ /// `-moz-alt-content`.
+ #[cfg(feature = "gecko")]
+ MozAltContent,
+ /// `-moz-label-content`.
+ /// This is needed to make `accesskey` work for XUL labels. It's basically
+ /// attr(value) otherwise.
+ #[cfg(feature = "gecko")]
+ MozLabelContent,
+ /// `attr([namespace? `|`]? ident)`
+ #[cfg(any(feature = "gecko", feature = "servo-layout-2020"))]
+ Attr(Attr),
+ /// image-set(url) | url(url)
+ Image(I),
+}
+
+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..43680da5fd
--- /dev/null
+++ b/servo/components/style/values/generics/easing.rs
@@ -0,0 +1,137 @@
+/* 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;
+
+/// A generic easing function.
+#[derive(
+ Clone,
+ Debug,
+ MallocSizeOf,
+ PartialEq,
+ SpecifiedValueInfo,
+ ToCss,
+ ToShmem,
+ Serialize,
+ Deserialize,
+)]
+#[value_info(ty = "TIMING_FUNCTION")]
+#[repr(u8, C)]
+pub enum TimingFunction<Integer, Number, LinearStops> {
+ /// `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),
+ /// linear([<linear-stop>]#)
+ /// <linear-stop> = <output> && <linear-stop-length>?
+ /// <linear-stop-length> = <percentage>{1, 2}
+ #[css(function = "linear")]
+ LinearFunction(LinearStops),
+}
+
+#[allow(missing_docs)]
+#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
+#[derive(
+ Clone,
+ Copy,
+ Debug,
+ Eq,
+ MallocSizeOf,
+ Parse,
+ PartialEq,
+ SpecifiedValueInfo,
+ ToComputedValue,
+ ToCss,
+ ToResolvedValue,
+ ToShmem,
+ Serialize,
+ Deserialize,
+)]
+#[repr(u8)]
+pub enum TimingKeyword {
+ Linear,
+ Ease,
+ EaseIn,
+ EaseOut,
+ EaseInOut,
+}
+
+/// Before flag, defined as per https://drafts.csswg.org/css-easing/#before-flag
+/// This flag is never user-specified.
+#[allow(missing_docs)]
+#[derive(PartialEq)]
+#[repr(u8)]
+pub enum BeforeFlag {
+ Unset,
+ Set,
+}
+
+#[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,
+ Serialize,
+ Deserialize,
+)]
+#[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, LinearStops> TimingFunction<Integer, Number, LinearStops> {
+ /// `ease`
+ #[inline]
+ pub fn ease() -> Self {
+ TimingFunction::Keyword(TimingKeyword::Ease)
+ }
+}
diff --git a/servo/components/style/values/generics/effects.rs b/servo/components/style/values/generics/effects.rs
new file mode 100644
index 0000000000..f5666f3055
--- /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))]
+#[derive(
+ Clone,
+ ComputeSquaredDistance,
+ Debug,
+ MallocSizeOf,
+ PartialEq,
+ SpecifiedValueInfo,
+ ToAnimatedValue,
+ ToComputedValue,
+ ToCss,
+ ToResolvedValue,
+ ToShmem,
+)]
+#[animation(no_bound(U))]
+#[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..09ed542e97
--- /dev/null
+++ b/servo/components/style/values/generics/font.rs
@@ -0,0 +1,271 @@
+/* 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};
+
+/// A trait for values that are labelled with a FontTag (for feature and
+/// variation settings).
+pub trait TaggedFontValue {
+ /// The value's tag.
+ fn tag(&self) -> FontTag;
+}
+
+/// 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<T> TaggedFontValue for FeatureTagValue<T> {
+ fn tag(&self) -> FontTag {
+ self.tag
+ }
+}
+
+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,
+}
+
+impl<T> TaggedFontValue for VariationValue<T> {
+ fn tag(&self) -> FontTag {
+ self.tag
+ }
+}
+
+/// A value both for font-variation-settings and font-feature-settings.
+#[derive(
+ Clone, Debug, Eq, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToResolvedValue, ToShmem,
+)]
+#[css(comma)]
+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),
+}
+
+/// A generic value for the `font-size-adjust` property.
+///
+/// https://www.w3.org/TR/css-fonts-4/#font-size-adjust-prop
+/// https://github.com/w3c/csswg-drafts/issues/6160
+/// https://github.com/w3c/csswg-drafts/issues/6288
+#[allow(missing_docs)]
+#[repr(u8)]
+#[derive(
+ Animate,
+ Clone,
+ ComputeSquaredDistance,
+ Copy,
+ Debug,
+ Hash,
+ MallocSizeOf,
+ PartialEq,
+ SpecifiedValueInfo,
+ ToAnimatedValue,
+ ToAnimatedZero,
+ ToComputedValue,
+ ToResolvedValue,
+ ToShmem,
+)]
+pub enum GenericFontSizeAdjust<Number> {
+ #[animation(error)]
+ None,
+ // 'ex-height' is the implied basis, so the keyword can be omitted
+ ExHeight(Number),
+ #[value_info(starts_with_keyword)]
+ CapHeight(Number),
+ #[value_info(starts_with_keyword)]
+ ChWidth(Number),
+ #[value_info(starts_with_keyword)]
+ IcWidth(Number),
+ #[value_info(starts_with_keyword)]
+ IcHeight(Number),
+}
+
+impl<Number: ToCss> ToCss for GenericFontSizeAdjust<Number> {
+ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: Write,
+ {
+ let (prefix, value) = match self {
+ Self::None => return dest.write_str("none"),
+ Self::ExHeight(v) => ("", v),
+ Self::CapHeight(v) => ("cap-height ", v),
+ Self::ChWidth(v) => ("ch-width ", v),
+ Self::IcWidth(v) => ("ic-width ", v),
+ Self::IcHeight(v) => ("ic-height ", v),
+ };
+
+ dest.write_str(prefix)?;
+ value.to_css(dest)
+ }
+}
diff --git a/servo/components/style/values/generics/grid.rs b/servo/components/style/values/generics/grid.rs
new file mode 100644
index 0000000000..e35c96a28c
--- /dev/null
+++ b/servo/components/style/values/generics/grid.rs
@@ -0,0 +1,829 @@
+/* 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_char(' ')?;
+ }
+ self.line_num.to_css(dest)?;
+ }
+
+ if self.ident != atom!("") {
+ if self.is_span || !self.line_num.is_zero() {
+ dest.write_char(' ')?;
+ }
+ 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_char(')')
+ },
+ TrackSize::FitContent(ref lp) => {
+ dest.write_str("fit-content(")?;
+ lp.to_css(dest)?;
+ dest.write_char(')')
+ },
+ }
+ }
+}
+
+/// 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_char(' ')?;
+ }
+
+ 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_char(')')?;
+
+ 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_char(' ')?;
+ }
+
+ 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_char(' ')?;
+ }
+ }
+
+ 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;
+ // Rather than truncating the result after inserting values, just
+ // have a maximum number of values. This gives us an early out on very
+ // large name lists, but more importantly prevents OOM on huge repeat
+ // expansions. (bug 1583429)
+ let mut max_remaining = MAX_GRID_LINE as usize;
+
+ 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 {
+ let mut handle_size = |n| {
+ let n = cmp::min(n, max_remaining);
+ max_remaining -= n;
+ n
+ };
+ match count {
+ // FIXME(emilio): we shouldn't expand repeat() at
+ // parse time for subgrid. (bug 1583429)
+ RepeatCount::Number(num) => {
+ let n = handle_size(num.value() as usize * names_list.len());
+ line_names.extend(names_list.iter().cloned().cycle().take(n));
+ },
+ 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));
+ let n = handle_size(fill_len);
+ line_names.extend(names_list.into_iter().take(n));
+ },
+ _ => return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
+ }
+ } else if let Ok(names) = input.try_parse(parse_line_names) {
+ if max_remaining > 0 {
+ line_names.push(names);
+ max_remaining -= 1;
+ }
+ } else {
+ break;
+ }
+ }
+
+ debug_assert!(line_names.len() <= 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_char(' ')?;
+ name.to_css(dest)?;
+ }
+ }
+
+ dest.write_char(']')?;
+ if fill_len > 0 && i == fill_start + fill_len - 1 {
+ dest.write_char(')')?;
+ }
+ }
+
+ Ok(())
+ }
+}
+
+/// Variants for `<grid-template-rows> | <grid-template-columns>`
+#[derive(
+ Animate,
+ Clone,
+ Debug,
+ MallocSizeOf,
+ PartialEq,
+ SpecifiedValueInfo,
+ ToComputedValue,
+ ToCss,
+ ToResolvedValue,
+ ToShmem,
+)]
+#[value_info(other_values = "subgrid")]
+#[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..532920581f
--- /dev/null
+++ b/servo/components/style/values/generics/image.rs
@@ -0,0 +1,614 @@
+/* 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::generics::Optional;
+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>
+#[derive(
+ Clone, Debug, MallocSizeOf, PartialEq, ToResolvedValue, ToShmem, ToCss, ToComputedValue,
+)]
+#[css(comma, function = "cross-fade")]
+#[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>>,
+}
+
+/// 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: Optional<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
+#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss, ToResolvedValue, ToShmem)]
+#[css(comma, function = "image-set")]
+#[repr(C)]
+pub struct GenericImageSet<Image, Resolution> {
+ /// The index of the selected candidate. usize::MAX for specified values or invalid images.
+ #[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)]
+#[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,
+
+ /// The `type(<string>)`
+ /// (Optional) Specify the image's MIME type
+ pub mime_type: crate::OwnedStr,
+
+ /// True if mime_type has been specified
+ pub has_mime_type: bool,
+}
+
+impl<I: style_traits::ToCss, R: style_traits::ToCss> ToCss for GenericImageSetItem<I, R> {
+ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: fmt::Write,
+ {
+ self.image.to_css(dest)?;
+ dest.write_char(' ')?;
+ self.resolution.to_css(dest)?;
+
+ if self.has_mime_type {
+ dest.write_char(' ')?;
+ dest.write_str("type(")?;
+ self.mime_type.to_css(dest)?;
+ dest.write_char(')')?;
+ }
+ Ok(())
+ }
+}
+
+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_char(')')
+ }
+}
+
+/// Values for `moz-image-rect`.
+///
+/// `-moz-image-rect(<uri>, top, right, bottom, left);`
+#[allow(missing_docs)]
+#[derive(
+ Clone,
+ Debug,
+ MallocSizeOf,
+ PartialEq,
+ SpecifiedValueInfo,
+ ToComputedValue,
+ ToCss,
+ ToResolvedValue,
+ ToShmem,
+)]
+#[css(comma, function = "-moz-image-rect")]
+#[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_char(')')
+ },
+ 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_char(' ')?;
+ }
+ }
+ 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_char(' ')?;
+ }
+ }
+ 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_char(')')
+ }
+}
+
+/// 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..de0dd7fbc1
--- /dev/null
+++ b/servo/components/style/values/generics/length.rs
@@ -0,0 +1,304 @@
+/* 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::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 FnOnce() -> 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,
+ #[animation(error)]
+ MaxContent,
+ #[animation(error)]
+ MinContent,
+ #[animation(error)]
+ FitContent,
+ #[animation(error)]
+ MozAvailable,
+ #[animation(error)]
+ #[css(function = "fit-content")]
+ FitContentFunction(LengthPercent),
+}
+
+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,
+ #[animation(error)]
+ MaxContent,
+ #[animation(error)]
+ MinContent,
+ #[animation(error)]
+ FitContent,
+ #[animation(error)]
+ MozAvailable,
+ #[animation(error)]
+ #[css(function = "fit-content")]
+ FitContentFunction(LengthPercent),
+}
+
+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 `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..237d3c16a3
--- /dev/null
+++ b/servo/components/style/values/generics/mod.rs
@@ -0,0 +1,386 @@
+/* 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, SpecifiedValueInfo, StyleParseErrorKind};
+
+pub mod animation;
+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 page;
+pub mod position;
+pub mod ratio;
+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")))
+ }
+
+ /// Is this a bullet? (i.e. `list-style-type: disc|circle|square|disclosure-closed|disclosure-open`)
+ #[inline]
+ pub fn is_bullet(&self) -> bool {
+ match self {
+ CounterStyle::Name(CustomIdent(ref name)) => {
+ name == &atom!("disc") ||
+ name == &atom!("circle") ||
+ name == &atom!("square") ||
+ name == &atom!("disclosure-closed") ||
+ name == &atom!("disclosure-open")
+ },
+ _ => false,
+ }
+ }
+}
+
+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)
+ }
+}
+
+pub use page::PageSize;
+
+/// An optional value, much like `Option<T>`, but with a defined struct layout
+/// to be able to use it from C++ as well.
+///
+/// Note that this is relatively inefficient, struct-layout-wise, as you have
+/// one byte for the tag, but padding to the alignment of T. If you have
+/// multiple optional values and care about struct compactness, you might be
+/// better off "coalescing" the combinations into a parent enum. But that
+/// shouldn't matter for most use cases.
+#[allow(missing_docs)]
+#[derive(
+ Animate,
+ Clone,
+ ComputeSquaredDistance,
+ Copy,
+ Debug,
+ MallocSizeOf,
+ Parse,
+ PartialEq,
+ SpecifiedValueInfo,
+ ToAnimatedValue,
+ ToAnimatedZero,
+ ToComputedValue,
+ ToCss,
+ ToResolvedValue,
+ ToShmem,
+ Serialize,
+ Deserialize,
+)]
+#[repr(C, u8)]
+pub enum Optional<T> {
+ #[css(skip)]
+ None,
+ Some(T),
+}
+
+impl<T> Optional<T> {
+ /// Returns whether this value is present.
+ pub fn is_some(&self) -> bool {
+ matches!(*self, Self::Some(..))
+ }
+
+ /// Returns whether this value is not present.
+ pub fn is_none(&self) -> bool {
+ matches!(*self, Self::None)
+ }
+
+ /// Turns this Optional<> into a regular rust Option<>.
+ pub fn into_rust(self) -> Option<T> {
+ match self {
+ Self::Some(v) => Some(v),
+ Self::None => None,
+ }
+ }
+
+ /// Return a reference to the containing value, if any, as a plain rust
+ /// Option<>.
+ pub fn as_ref(&self) -> Option<&T> {
+ match *self {
+ Self::Some(ref v) => Some(v),
+ Self::None => None,
+ }
+ }
+}
+
+impl<T> From<Option<T>> for Optional<T> {
+ fn from(rust: Option<T>) -> Self {
+ match rust {
+ Some(t) => Self::Some(t),
+ None => Self::None,
+ }
+ }
+}
diff --git a/servo/components/style/values/generics/motion.rs b/servo/components/style/values/generics/motion.rs
new file mode 100644
index 0000000000..768ef52ff7
--- /dev/null
+++ b/servo/components/style/values/generics/motion.rs
@@ -0,0 +1,174 @@
+/* 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::generics::position::GenericPosition;
+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,
+}
+
+impl RaySize {
+ /// Returns true if it is the default value.
+ #[inline]
+ pub fn is_default(&self) -> bool {
+ *self == RaySize::ClosestSide
+ }
+}
+
+/// 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)]
+ #[css(skip_if = "RaySize::is_default")]
+ 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
+#[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
+ }
+}
+
+/// The offset-position property, which specifies the offset starting position that is used by the
+/// <offset-path> functions if they don’t specify their own starting position.
+///
+/// https://drafts.fxtf.org/motion-1/#offset-position-property
+#[derive(
+ Animate,
+ Clone,
+ ComputeSquaredDistance,
+ Copy,
+ Debug,
+ Deserialize,
+ MallocSizeOf,
+ Parse,
+ PartialEq,
+ Serialize,
+ SpecifiedValueInfo,
+ ToAnimatedValue,
+ ToAnimatedZero,
+ ToComputedValue,
+ ToCss,
+ ToResolvedValue,
+ ToShmem,
+)]
+#[repr(C, u8)]
+pub enum GenericOffsetPosition<H, V> {
+ /// The element does not have an offset starting position.
+ Normal,
+ /// The offset starting position is the top-left corner of the box.
+ Auto,
+ /// The offset starting position is the result of using the <position> to position a 0x0 object
+ /// area within the box’s containing block.
+ Position(
+ #[css(field_bound)]
+ #[parse(field_bound)]
+ GenericPosition<H, V>,
+ ),
+}
+
+pub use self::GenericOffsetPosition as OffsetPosition;
+
+impl<H, V> OffsetPosition<H, V> {
+ /// Returns the initial value, auto.
+ #[inline]
+ pub fn auto() -> Self {
+ Self::Auto
+ }
+}
diff --git a/servo/components/style/values/generics/page.rs b/servo/components/style/values/generics/page.rs
new file mode 100644
index 0000000000..91f02bc4b3
--- /dev/null
+++ b/servo/components/style/values/generics/page.rs
@@ -0,0 +1,162 @@
+/* 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/. */
+
+//! @page at-rule properties
+
+use crate::values::generics::NonNegative;
+use crate::values::specified::length::AbsoluteLength;
+
+/// Page size names.
+///
+/// https://drafts.csswg.org/css-page-3/#typedef-page-size-page-size
+#[derive(
+ Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem,
+)]
+#[repr(u8)]
+pub enum PaperSize {
+ /// ISO A5 media
+ A5,
+ /// ISO A4 media
+ A4,
+ /// ISO A3 media
+ A3,
+ /// ISO B5 media
+ B5,
+ /// ISO B4 media
+ B4,
+ /// JIS B5 media
+ JisB5,
+ /// JIS B4 media
+ JisB4,
+ /// North American Letter size
+ Letter,
+ /// North American Legal size
+ Legal,
+ /// North American Ledger size
+ Ledger,
+}
+
+impl PaperSize {
+ /// Gets the long edge length of the paper size
+ pub fn long_edge(&self) -> NonNegative<AbsoluteLength> {
+ NonNegative(match *self {
+ PaperSize::A5 => AbsoluteLength::Mm(210.0),
+ PaperSize::A4 => AbsoluteLength::Mm(297.0),
+ PaperSize::A3 => AbsoluteLength::Mm(420.0),
+ PaperSize::B5 => AbsoluteLength::Mm(250.0),
+ PaperSize::B4 => AbsoluteLength::Mm(353.0),
+ PaperSize::JisB5 => AbsoluteLength::Mm(257.0),
+ PaperSize::JisB4 => AbsoluteLength::Mm(364.0),
+ PaperSize::Letter => AbsoluteLength::In(11.0),
+ PaperSize::Legal => AbsoluteLength::In(14.0),
+ PaperSize::Ledger => AbsoluteLength::In(17.0),
+ })
+ }
+ /// Gets the short edge length of the paper size
+ pub fn short_edge(&self) -> NonNegative<AbsoluteLength> {
+ NonNegative(match *self {
+ PaperSize::A5 => AbsoluteLength::Mm(148.0),
+ PaperSize::A4 => AbsoluteLength::Mm(210.0),
+ PaperSize::A3 => AbsoluteLength::Mm(297.0),
+ PaperSize::B5 => AbsoluteLength::Mm(176.0),
+ PaperSize::B4 => AbsoluteLength::Mm(250.0),
+ PaperSize::JisB5 => AbsoluteLength::Mm(182.0),
+ PaperSize::JisB4 => AbsoluteLength::Mm(257.0),
+ PaperSize::Letter => AbsoluteLength::In(8.5),
+ PaperSize::Legal => AbsoluteLength::In(8.5),
+ PaperSize::Ledger => AbsoluteLength::In(11.0),
+ })
+ }
+}
+
+/// Page orientation names.
+///
+/// https://drafts.csswg.org/css-page-3/#page-orientation-prop
+#[derive(
+ Clone,
+ Copy,
+ Debug,
+ Eq,
+ MallocSizeOf,
+ Parse,
+ PartialEq,
+ SpecifiedValueInfo,
+ ToComputedValue,
+ ToCss,
+ ToResolvedValue,
+ ToShmem,
+)]
+#[repr(u8)]
+pub enum PageOrientation {
+ /// upright
+ Upright,
+ /// rotate-left (counter-clockwise)
+ RotateLeft,
+ /// rotate-right (clockwise)
+ RotateRight,
+}
+
+/// Paper orientation
+///
+/// https://drafts.csswg.org/css-page-3/#page-size-prop
+#[derive(
+ Clone,
+ Copy,
+ Debug,
+ Eq,
+ MallocSizeOf,
+ Parse,
+ PartialEq,
+ SpecifiedValueInfo,
+ ToCss,
+ ToResolvedValue,
+ ToShmem,
+)]
+#[repr(u8)]
+pub enum PageSizeOrientation {
+ /// Portrait orientation
+ Portrait,
+ /// Landscape orientation
+ Landscape,
+}
+
+#[inline]
+fn is_portrait(orientation: &PageSizeOrientation) -> bool {
+ *orientation == PageSizeOrientation::Portrait
+}
+
+/// Page size property
+///
+/// https://drafts.csswg.org/css-page-3/#page-size-prop
+#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
+#[repr(C, u8)]
+pub enum GenericPageSize<S> {
+ /// `auto` value.
+ Auto,
+ /// Page dimensions.
+ Size(S),
+ /// An orientation with no size.
+ Orientation(PageSizeOrientation),
+ /// Paper size by name
+ PaperSize(
+ PaperSize,
+ #[css(skip_if = "is_portrait")] PageSizeOrientation,
+ ),
+}
+
+pub use self::GenericPageSize as PageSize;
+
+impl<S> PageSize<S> {
+ /// `auto` value.
+ #[inline]
+ pub fn auto() -> Self {
+ PageSize::Auto
+ }
+
+ /// Whether this is the `auto` value.
+ #[inline]
+ pub fn is_auto(&self) -> bool {
+ matches!(*self, PageSize::Auto)
+ }
+}
diff --git a/servo/components/style/values/generics/position.rs b/servo/components/style/values/generics/position.rs
new file mode 100644
index 0000000000..5832833fa3
--- /dev/null
+++ b/servo/components/style/values/generics/position.rs
@@ -0,0 +1,231 @@
+/* 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 crate::values::animated::ToAnimatedZero;
+use crate::values::generics::ratio::Ratio;
+
+/// 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
+#[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,
+ }
+ }
+}
+
+/// Ratio or None.
+#[derive(
+ Animate,
+ Clone,
+ ComputeSquaredDistance,
+ Copy,
+ Debug,
+ MallocSizeOf,
+ PartialEq,
+ SpecifiedValueInfo,
+ ToComputedValue,
+ ToCss,
+ ToResolvedValue,
+ ToShmem,
+)]
+#[repr(C, u8)]
+pub enum PreferredRatio<N> {
+ /// Without specified ratio
+ #[css(skip)]
+ None,
+ /// With specified ratio
+ Ratio(
+ #[animation(field_bound)]
+ #[css(field_bound)]
+ #[distance(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,
+ 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.
+ #[animation(field_bound)]
+ #[css(field_bound)]
+ #[distance(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,
+ }
+ }
+}
+
+impl<N> ToAnimatedZero for AspectRatio<N> {
+ #[inline]
+ fn to_animated_zero(&self) -> Result<Self, ()> {
+ Err(())
+ }
+}
diff --git a/servo/components/style/values/generics/ratio.rs b/servo/components/style/values/generics/ratio.rs
new file mode 100644
index 0000000000..8c66fed602
--- /dev/null
+++ b/servo/components/style/values/generics/ratio.rs
@@ -0,0 +1,50 @@
+/* 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 <ratio>.
+//! https://drafts.csswg.org/css-values/#ratios
+
+use std::fmt::{self, Write};
+use style_traits::{CssWriter, ToCss};
+
+/// A generic value for the `<ratio>` value.
+#[derive(
+ Clone,
+ Copy,
+ Debug,
+ MallocSizeOf,
+ PartialEq,
+ SpecifiedValueInfo,
+ 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(())
+ }
+}
diff --git a/servo/components/style/values/generics/rect.rs b/servo/components/style/values/generics/rect.rs
new file mode 100644
index 0000000000..e6358373d6
--- /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_char(' ')?;
+ self.1.to_css(dest)?;
+ if same_vertical && same_horizontal {
+ return Ok(());
+ }
+ dest.write_char(' ')?;
+ self.2.to_css(dest)?;
+ if same_horizontal {
+ return Ok(());
+ }
+ dest.write_char(' ')?;
+ 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..0027245e48
--- /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_char(' ')?;
+ 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..43ba77f1ff
--- /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>
+#[derive(
+ Animate,
+ Clone,
+ ComputeSquaredDistance,
+ Debug,
+ MallocSizeOf,
+ PartialEq,
+ SpecifiedValueInfo,
+ ToAnimatedValue,
+ ToAnimatedZero,
+ ToComputedValue,
+ ToCss,
+ ToResolvedValue,
+ ToShmem,
+)]
+#[animation(no_bound(Url))]
+#[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.
+#[derive(
+ Animate,
+ Clone,
+ ComputeSquaredDistance,
+ Debug,
+ MallocSizeOf,
+ PartialEq,
+ Parse,
+ SpecifiedValueInfo,
+ ToAnimatedValue,
+ ToAnimatedZero,
+ ToComputedValue,
+ ToCss,
+ ToResolvedValue,
+ ToShmem,
+)]
+#[animation(no_bound(U))]
+#[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..9b59ff0dc3
--- /dev/null
+++ b/servo/components/style/values/generics/text.rs
@@ -0,0 +1,156 @@
+/* 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,
+ 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..3a65c460a7
--- /dev/null
+++ b/servo/components/style/values/generics/transform.rs
@@ -0,0 +1,879 @@
+/* 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, ZeroNoPercent};
+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)]
+#[derive(
+ Clone,
+ Copy,
+ Debug,
+ Deserialize,
+ MallocSizeOf,
+ PartialEq,
+ Serialize,
+ SpecifiedValueInfo,
+ ToComputedValue,
+ ToCss,
+ ToResolvedValue,
+ ToShmem,
+)]
+#[css(comma, function = "matrix3d")]
+#[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
+}
+
+/// A value for the `perspective()` transform function, which is either a
+/// non-negative `<length>` or `none`.
+#[derive(
+ Clone,
+ Debug,
+ Deserialize,
+ MallocSizeOf,
+ PartialEq,
+ Serialize,
+ SpecifiedValueInfo,
+ ToComputedValue,
+ ToCss,
+ ToResolvedValue,
+ ToShmem,
+)]
+#[repr(C, u8)]
+pub enum GenericPerspectiveFunction<L> {
+ /// `none`
+ None,
+ /// A `<length>`.
+ Length(L),
+}
+
+impl<L> GenericPerspectiveFunction<L> {
+ /// Returns `f32::INFINITY` or the result of a function on the length value.
+ pub fn infinity_or(&self, f: impl FnOnce(&L) -> f32) -> f32 {
+ match *self {
+ Self::None => f32::INFINITY,
+ Self::Length(ref l) => f(l),
+ }
+ }
+}
+
+pub use self::GenericPerspectiveFunction as PerspectiveFunction;
+
+#[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 + ZeroNoPercent,
+ 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 = "ZeroNoPercent::is_zero_no_percent")] 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(GenericPerspectiveFunction<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 + ZeroNoPercent,
+ 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, ()> {
+ Ok(self
+ .maybe_percentage_relative_to(containing_len)
+ .ok_or(())?
+ .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 + ZeroNoPercent,
+{
+ #[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::*;
+
+ 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 p) => {
+ let px = match p {
+ PerspectiveFunction::None => f32::INFINITY,
+ PerspectiveFunction::Length(ref p) => p.to_pixel_length(None)?,
+ };
+ create_perspective_matrix(px).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), ()> {
+ Self::components_to_transform_3d_matrix(&self.0, reference_box)
+ }
+
+ /// Converts a series of components to a 3d matrix.
+ #[cfg_attr(rustfmt, rustfmt_skip)]
+ pub fn components_to_transform_3d_matrix(
+ ops: &[T],
+ 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::components_to_transform_3d_matrix_f64(ops, reference_box)?;
+ Ok((cast_3d_transform(m), is_3d))
+ }
+
+ /// Same as Transform::to_transform_3d_matrix but a f64 version.
+ fn components_to_transform_3d_matrix_f64(
+ ops: &[T],
+ 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 ops {
+ let matrix = operation.to_3d_matrix(reference_box)?;
+ contain_3d = 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> {
+ if d.is_finite() {
+ Transform3D::perspective(d.max(1.))
+ } else {
+ Transform3D::identity()
+ }
+}
+
+/// 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>
+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 + Zero,
+ 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 the axis is parallel with the x or y axes, it must serialize as the
+ // appropriate keyword. If a rotation about the z axis (that is, in 2D) is
+ // specified, the property must serialize as just an <angle>
+ //
+ // https://drafts.csswg.org/css-transforms-2/#individual-transform-serialization
+ let v = (x, y, z);
+ let axis = if x.is_zero() && y.is_zero() && z.is_zero() {
+ // The zero length vector is parallel to every other vector, so
+ // is_parallel_to() returns true for it. However, it is definitely different
+ // from x axis, y axis, or z axis, and it's meaningless to perform a rotation
+ // using that direction vector. So we *have* to serialize it using that same
+ // vector - we can't simplify to some theoretically parallel axis-aligned
+ // vector.
+ None
+ } else if v.is_parallel_to(&DirectionVector::new(1., 0., 0.)) {
+ Some("x ")
+ } else if v.is_parallel_to(&DirectionVector::new(0., 1., 0.)) {
+ Some("y ")
+ } else if v.is_parallel_to(&DirectionVector::new(0., 0., 1.)) {
+ // When we're parallel to the z-axis, we can just serialize the angle.
+ return angle.to_css(dest);
+ } else {
+ None
+ };
+ match axis {
+ Some(a) => dest.write_str(a)?,
+ None => {
+ 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>
+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 + ZeroNoPercent, Length: Zero>(
+ _: &LengthPercentage,
+ y: &LengthPercentage,
+ z: &Length,
+) -> bool {
+ y.is_zero_no_percent() && 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; however if 0% is the second value, it is included).
+///
+/// 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>
+pub enum GenericTranslate<LengthPercentage, Length>
+where
+ LengthPercentage: Zero + ZeroNoPercent,
+ 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..87c8674182
--- /dev/null
+++ b/servo/components/style/values/generics/ui.rs
@@ -0,0 +1,129 @@
+/* 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, ToComputedValue, ToResolvedValue, ToShmem)]
+#[repr(C)]
+pub struct GenericCursorImage<Image, Number> {
+ /// The url to parse images from.
+ pub image: Image,
+ /// 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<Image: ToCss, Number: ToCss> ToCss for CursorImage<Image, Number> {
+ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: Write,
+ {
+ self.image.to_css(dest)?;
+ if self.has_hotspot {
+ dest.write_char(' ')?;
+ self.hotspot_x.to_css(dest)?;
+ dest.write_char(' ')?;
+ 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,
+ }
+ }
+}