diff options
Diffstat (limited to '')
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(¢er) { + Some(o) => o, + None => return, + }; + + // So if we can prove that min is more than center, then we won, + // as that's what we should always return. + if matches!(min_cmp_center, cmp::Ordering::Greater) { + return replace_self_with!(&mut **min); + } + + // Otherwise try with max. + let max_cmp_center = match max.partial_cmp(¢er) { + Some(o) => o, + None => return, + }; + + if matches!(max_cmp_center, cmp::Ordering::Less) { + // max is less than center, so we need to return effectively + // `max(min, max)`. + let max_cmp_min = match max.partial_cmp(&min) { + Some(o) => o, + None => { + debug_assert!( + false, + "We compared center with min and max, how are \ + min / max not comparable with each other?" + ); + return; + }, + }; + + if matches!(max_cmp_min, cmp::Ordering::Less) { + return replace_self_with!(&mut **min); + } + + return replace_self_with!(&mut **max); + } + + // Otherwise we're the center node. + return replace_self_with!(&mut **center); + }, + Self::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, + } + } +} |