Skip to main content

style/values/generics/
basic_shape.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! CSS handling for the [`basic-shape`](https://drafts.csswg.org/css-shapes/#typedef-basic-shape)
6//! types that are generic over their `ToCss` implementations.
7
8use crate::derives::*;
9use crate::values::animated::{lists, Animate, Procedure, ToAnimatedZero};
10use crate::values::computed::Percentage;
11use crate::values::distance::{ComputeSquaredDistance, SquaredDistance};
12use crate::values::generics::{
13    border::GenericBorderRadius, position::GenericPositionOrAuto, rect::Rect, NonNegative, Optional,
14};
15use crate::values::specified::svg_path::{PathCommand, SVGPathData};
16use crate::Zero;
17use std::fmt::{self, Write};
18use style_traits::{CssWriter, ToCss};
19
20/// <https://drafts.fxtf.org/css-masking-1/#typedef-geometry-box>
21#[allow(missing_docs)]
22#[derive(
23    Animate,
24    Clone,
25    ComputeSquaredDistance,
26    Copy,
27    Debug,
28    MallocSizeOf,
29    PartialEq,
30    Parse,
31    SpecifiedValueInfo,
32    ToAnimatedValue,
33    ToComputedValue,
34    ToCss,
35    ToResolvedValue,
36    ToShmem,
37    ToTyped,
38)]
39#[repr(u8)]
40pub enum ShapeGeometryBox {
41    /// Depending on which kind of element this style value applied on, the
42    /// default value of the reference-box can be different.  For an HTML
43    /// element, the default value of reference-box is border-box; for an SVG
44    /// element, the default value is fill-box.  Since we can not determine the
45    /// default value at parsing time, we keep this value to make a decision on
46    /// it.
47    #[css(skip)]
48    ElementDependent,
49    FillBox,
50    StrokeBox,
51    ViewBox,
52    ShapeBox(ShapeBox),
53}
54
55impl Default for ShapeGeometryBox {
56    fn default() -> Self {
57        Self::ElementDependent
58    }
59}
60
61/// Skip the serialization if the author omits the box or specifies border-box.
62#[inline]
63fn is_default_box_for_clip_path(b: &ShapeGeometryBox) -> bool {
64    // Note: for clip-path, ElementDependent is always border-box, so we have to check both of them
65    // for serialization.
66    matches!(b, ShapeGeometryBox::ElementDependent)
67        || matches!(b, ShapeGeometryBox::ShapeBox(ShapeBox::BorderBox))
68}
69
70/// https://drafts.csswg.org/css-shapes-1/#typedef-shape-box
71#[allow(missing_docs)]
72#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
73#[derive(
74    Animate,
75    Clone,
76    Copy,
77    ComputeSquaredDistance,
78    Debug,
79    Eq,
80    MallocSizeOf,
81    Parse,
82    PartialEq,
83    SpecifiedValueInfo,
84    ToAnimatedValue,
85    ToComputedValue,
86    ToCss,
87    ToResolvedValue,
88    ToShmem,
89    ToTyped,
90)]
91#[repr(u8)]
92pub enum ShapeBox {
93    MarginBox,
94    BorderBox,
95    PaddingBox,
96    ContentBox,
97}
98
99impl Default for ShapeBox {
100    fn default() -> Self {
101        ShapeBox::MarginBox
102    }
103}
104
105/// A value for the `clip-path` property.
106#[allow(missing_docs)]
107#[derive(
108    Animate,
109    Clone,
110    ComputeSquaredDistance,
111    Debug,
112    MallocSizeOf,
113    PartialEq,
114    SpecifiedValueInfo,
115    ToAnimatedValue,
116    ToComputedValue,
117    ToCss,
118    ToResolvedValue,
119    ToShmem,
120    ToTyped,
121)]
122#[animation(no_bound(U))]
123#[repr(u8)]
124pub enum GenericClipPath<BasicShape, U> {
125    #[animation(error)]
126    None,
127    #[animation(error)]
128    // XXX This will likely change to skip since it seems Typed OM Level 1
129    // won't be updated to cover this case even though there's some preparation
130    // in WPT tests for this.
131    #[typed(todo)]
132    Url(U),
133    #[typed(skip)]
134    Shape(
135        #[animation(field_bound)] Box<BasicShape>,
136        #[css(skip_if = "is_default_box_for_clip_path")] ShapeGeometryBox,
137    ),
138    #[animation(error)]
139    Box(ShapeGeometryBox),
140}
141
142pub use self::GenericClipPath as ClipPath;
143
144/// A value for the `shape-outside` property.
145#[allow(missing_docs)]
146#[derive(
147    Animate,
148    Clone,
149    ComputeSquaredDistance,
150    Debug,
151    MallocSizeOf,
152    PartialEq,
153    SpecifiedValueInfo,
154    ToAnimatedValue,
155    ToComputedValue,
156    ToCss,
157    ToResolvedValue,
158    ToShmem,
159    ToTyped,
160)]
161#[animation(no_bound(I))]
162#[repr(u8)]
163pub enum GenericShapeOutside<BasicShape, I> {
164    #[animation(error)]
165    None,
166    #[animation(error)]
167    Image(I),
168    #[typed(skip)]
169    Shape(Box<BasicShape>, #[css(skip_if = "is_default")] ShapeBox),
170    #[animation(error)]
171    Box(ShapeBox),
172}
173
174pub use self::GenericShapeOutside as ShapeOutside;
175
176/// The <basic-shape>.
177///
178/// https://drafts.csswg.org/css-shapes-1/#supported-basic-shapes
179#[derive(
180    Animate,
181    Clone,
182    ComputeSquaredDistance,
183    Debug,
184    Deserialize,
185    MallocSizeOf,
186    PartialEq,
187    Serialize,
188    SpecifiedValueInfo,
189    ToAnimatedValue,
190    ToComputedValue,
191    ToCss,
192    ToResolvedValue,
193    ToShmem,
194)]
195#[repr(C, u8)]
196pub enum GenericBasicShape<Angle, Position, LengthPercentage, BasicShapeRect> {
197    /// The <basic-shape-rect>.
198    Rect(BasicShapeRect),
199    /// Defines a circle with a center and a radius.
200    Circle(
201        #[animation(field_bound)]
202        #[css(field_bound)]
203        #[shmem(field_bound)]
204        Circle<Position, LengthPercentage>,
205    ),
206    /// Defines an ellipse with a center and x-axis/y-axis radii.
207    Ellipse(
208        #[animation(field_bound)]
209        #[css(field_bound)]
210        #[shmem(field_bound)]
211        Ellipse<Position, LengthPercentage>,
212    ),
213    /// Defines a polygon with pair arguments.
214    Polygon(GenericPolygon<LengthPercentage>),
215    /// Defines a path() or shape().
216    PathOrShape(
217        #[animation(field_bound)]
218        #[css(field_bound)]
219        #[compute(field_bound)]
220        GenericPathOrShapeFunction<Angle, Position, LengthPercentage>,
221    ),
222}
223
224pub use self::GenericBasicShape as BasicShape;
225
226/// <https://drafts.csswg.org/css-shapes/#funcdef-inset>
227#[allow(missing_docs)]
228#[derive(
229    Animate,
230    Clone,
231    ComputeSquaredDistance,
232    Debug,
233    Deserialize,
234    MallocSizeOf,
235    PartialEq,
236    Serialize,
237    SpecifiedValueInfo,
238    ToAnimatedValue,
239    ToComputedValue,
240    ToResolvedValue,
241    ToShmem,
242)]
243#[css(function = "inset")]
244#[repr(C)]
245pub struct GenericInsetRect<LengthPercentage> {
246    pub rect: Rect<LengthPercentage>,
247    #[shmem(field_bound)]
248    #[animation(field_bound)]
249    pub round: GenericBorderRadius<NonNegative<LengthPercentage>>,
250}
251
252pub use self::GenericInsetRect as InsetRect;
253
254/// <https://drafts.csswg.org/css-shapes/#funcdef-circle>
255#[allow(missing_docs)]
256#[derive(
257    Animate,
258    Clone,
259    ComputeSquaredDistance,
260    Copy,
261    Debug,
262    Deserialize,
263    MallocSizeOf,
264    PartialEq,
265    Serialize,
266    SpecifiedValueInfo,
267    ToAnimatedValue,
268    ToComputedValue,
269    ToResolvedValue,
270    ToShmem,
271)]
272#[css(function)]
273#[repr(C)]
274pub struct Circle<Position, LengthPercentage> {
275    pub position: GenericPositionOrAuto<Position>,
276    #[animation(field_bound)]
277    pub radius: GenericShapeRadius<LengthPercentage>,
278}
279
280/// <https://drafts.csswg.org/css-shapes/#funcdef-ellipse>
281#[allow(missing_docs)]
282#[derive(
283    Animate,
284    Clone,
285    ComputeSquaredDistance,
286    Copy,
287    Debug,
288    Deserialize,
289    MallocSizeOf,
290    PartialEq,
291    Serialize,
292    SpecifiedValueInfo,
293    ToAnimatedValue,
294    ToComputedValue,
295    ToResolvedValue,
296    ToShmem,
297)]
298#[css(function)]
299#[repr(C)]
300pub struct Ellipse<Position, LengthPercentage> {
301    pub position: GenericPositionOrAuto<Position>,
302    #[animation(field_bound)]
303    pub semiaxis_x: GenericShapeRadius<LengthPercentage>,
304    #[animation(field_bound)]
305    pub semiaxis_y: GenericShapeRadius<LengthPercentage>,
306}
307
308/// <https://drafts.csswg.org/css-shapes/#typedef-shape-radius>
309#[allow(missing_docs)]
310#[derive(
311    Animate,
312    Clone,
313    ComputeSquaredDistance,
314    Copy,
315    Debug,
316    Deserialize,
317    MallocSizeOf,
318    Parse,
319    PartialEq,
320    Serialize,
321    SpecifiedValueInfo,
322    ToAnimatedValue,
323    ToComputedValue,
324    ToCss,
325    ToResolvedValue,
326    ToShmem,
327)]
328#[repr(C, u8)]
329pub enum GenericShapeRadius<LengthPercentage> {
330    Length(
331        #[animation(field_bound)]
332        #[parse(field_bound)]
333        NonNegative<LengthPercentage>,
334    ),
335    #[animation(error)]
336    ClosestSide,
337    #[animation(error)]
338    FarthestSide,
339    #[animation(error)]
340    FarthestCorner,
341    #[animation(error)]
342    ClosestCorner,
343}
344
345pub use self::GenericShapeRadius as ShapeRadius;
346
347/// A generic type for representing the `polygon()` function
348///
349/// <https://drafts.csswg.org/css-shapes/#funcdef-polygon>
350#[derive(
351    Clone,
352    Debug,
353    Deserialize,
354    MallocSizeOf,
355    PartialEq,
356    Serialize,
357    SpecifiedValueInfo,
358    ToAnimatedValue,
359    ToComputedValue,
360    ToCss,
361    ToResolvedValue,
362    ToShmem,
363)]
364#[css(comma, function = "polygon")]
365#[repr(C)]
366pub struct GenericPolygon<LengthPercentage> {
367    /// The filling rule for a polygon.
368    #[css(skip_if = "is_default")]
369    pub fill: FillRule,
370    /// A collection of (x, y) coordinates to draw the polygon.
371    #[css(iterable)]
372    pub coordinates: crate::OwnedSlice<PolygonCoord<LengthPercentage>>,
373}
374
375pub use self::GenericPolygon as Polygon;
376
377/// Coordinates for Polygon.
378#[derive(
379    Animate,
380    Clone,
381    ComputeSquaredDistance,
382    Debug,
383    Deserialize,
384    MallocSizeOf,
385    PartialEq,
386    Serialize,
387    SpecifiedValueInfo,
388    ToAnimatedValue,
389    ToComputedValue,
390    ToCss,
391    ToResolvedValue,
392    ToShmem,
393)]
394#[repr(C)]
395pub struct PolygonCoord<LengthPercentage>(pub LengthPercentage, pub LengthPercentage);
396
397/// path() function or shape() function.
398#[derive(
399    Clone,
400    ComputeSquaredDistance,
401    Debug,
402    Deserialize,
403    MallocSizeOf,
404    PartialEq,
405    Serialize,
406    SpecifiedValueInfo,
407    ToAnimatedValue,
408    ToComputedValue,
409    ToCss,
410    ToResolvedValue,
411    ToShmem,
412)]
413#[repr(C, u8)]
414pub enum GenericPathOrShapeFunction<Angle, Position, LengthPercentage> {
415    /// Defines a path with SVG path syntax.
416    Path(Path),
417    /// Defines a shape function, which is identical to path() but it uses the CSS syntax.
418    Shape(
419        #[css(field_bound)]
420        #[compute(field_bound)]
421        Shape<Angle, Position, LengthPercentage>,
422    ),
423}
424
425// https://drafts.csswg.org/css-shapes/#typedef-fill-rule
426// NOTE: Basic shapes spec says that these are the only two values, however
427// https://www.w3.org/TR/SVG/painting.html#FillRuleProperty
428// says that it can also be `inherit`
429#[allow(missing_docs)]
430#[derive(
431    Animate,
432    Clone,
433    ComputeSquaredDistance,
434    Copy,
435    Debug,
436    Deserialize,
437    Eq,
438    MallocSizeOf,
439    Parse,
440    PartialEq,
441    Serialize,
442    SpecifiedValueInfo,
443    ToAnimatedValue,
444    ToComputedValue,
445    ToCss,
446    ToResolvedValue,
447    ToShmem,
448    ToTyped,
449)]
450#[repr(u8)]
451pub enum FillRule {
452    Nonzero,
453    Evenodd,
454}
455
456/// The path function.
457///
458/// https://drafts.csswg.org/css-shapes-1/#funcdef-basic-shape-path
459#[derive(
460    Animate,
461    Clone,
462    ComputeSquaredDistance,
463    Debug,
464    Deserialize,
465    MallocSizeOf,
466    PartialEq,
467    Serialize,
468    SpecifiedValueInfo,
469    ToAnimatedValue,
470    ToComputedValue,
471    ToCss,
472    ToResolvedValue,
473    ToShmem,
474)]
475#[css(comma, function = "path")]
476#[repr(C)]
477pub struct Path {
478    /// The filling rule for the svg path.
479    #[css(skip_if = "is_default")]
480    pub fill: FillRule,
481    /// The svg path data.
482    pub path: SVGPathData,
483}
484
485impl Path {
486    /// Returns the slice of PathCommand.
487    #[inline]
488    pub fn commands(&self) -> &[PathCommand] {
489        self.path.commands()
490    }
491}
492
493impl<B, U> ToAnimatedZero for ClipPath<B, U> {
494    fn to_animated_zero(&self) -> Result<Self, ()> {
495        Err(())
496    }
497}
498
499impl<B, U> ToAnimatedZero for ShapeOutside<B, U> {
500    fn to_animated_zero(&self) -> Result<Self, ()> {
501        Err(())
502    }
503}
504
505impl<Length> ToCss for InsetRect<Length>
506where
507    Length: ToCss + PartialEq + Zero,
508{
509    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
510    where
511        W: Write,
512    {
513        dest.write_str("inset(")?;
514        self.rect.to_css(dest)?;
515        if !self.round.is_zero() {
516            dest.write_str(" round ")?;
517            self.round.to_css(dest)?;
518        }
519        dest.write_char(')')
520    }
521}
522
523impl<Position, LengthPercentage> ToCss for Circle<Position, LengthPercentage>
524where
525    LengthPercentage: ToCss + PartialEq,
526    Position: ToCss,
527{
528    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
529    where
530        W: Write,
531    {
532        let has_radius = self.radius != Default::default();
533
534        dest.write_str("circle(")?;
535        if has_radius {
536            self.radius.to_css(dest)?;
537        }
538
539        // Preserve the `at <position>` even if it specified the default value.
540        // https://github.com/w3c/csswg-drafts/issues/8695
541        if !matches!(self.position, GenericPositionOrAuto::Auto) {
542            if has_radius {
543                dest.write_char(' ')?;
544            }
545            dest.write_str("at ")?;
546            self.position.to_css(dest)?;
547        }
548        dest.write_char(')')
549    }
550}
551
552impl<Position, LengthPercentage> ToCss for Ellipse<Position, LengthPercentage>
553where
554    LengthPercentage: ToCss + PartialEq,
555    Position: ToCss,
556{
557    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
558    where
559        W: Write,
560    {
561        let has_radii =
562            self.semiaxis_x != Default::default() || self.semiaxis_y != Default::default();
563
564        dest.write_str("ellipse(")?;
565        if has_radii {
566            self.semiaxis_x.to_css(dest)?;
567            dest.write_char(' ')?;
568            self.semiaxis_y.to_css(dest)?;
569        }
570
571        // Preserve the `at <position>` even if it specified the default value.
572        // https://github.com/w3c/csswg-drafts/issues/8695
573        if !matches!(self.position, GenericPositionOrAuto::Auto) {
574            if has_radii {
575                dest.write_char(' ')?;
576            }
577            dest.write_str("at ")?;
578            self.position.to_css(dest)?;
579        }
580        dest.write_char(')')
581    }
582}
583
584impl<L> Default for ShapeRadius<L> {
585    #[inline]
586    fn default() -> Self {
587        ShapeRadius::ClosestSide
588    }
589}
590
591impl<L> Animate for Polygon<L>
592where
593    L: Animate,
594{
595    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
596        if self.fill != other.fill {
597            return Err(());
598        }
599        let coordinates =
600            lists::by_computed_value::animate(&self.coordinates, &other.coordinates, procedure)?;
601        Ok(Polygon {
602            fill: self.fill,
603            coordinates,
604        })
605    }
606}
607
608impl<L> ComputeSquaredDistance for Polygon<L>
609where
610    L: ComputeSquaredDistance,
611{
612    fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
613        if self.fill != other.fill {
614            return Err(());
615        }
616        lists::by_computed_value::squared_distance(&self.coordinates, &other.coordinates)
617    }
618}
619
620impl Default for FillRule {
621    #[inline]
622    fn default() -> Self {
623        FillRule::Nonzero
624    }
625}
626
627#[inline]
628fn is_default<T: Default + PartialEq>(fill: &T) -> bool {
629    *fill == Default::default()
630}
631
632/// The shape function defined in css-shape-2.
633/// shape() = shape(<fill-rule>? from <coordinate-pair>, <shape-command>#)
634///
635/// https://drafts.csswg.org/css-shapes-2/#shape-function
636#[derive(
637    Clone,
638    Debug,
639    Deserialize,
640    MallocSizeOf,
641    PartialEq,
642    Serialize,
643    SpecifiedValueInfo,
644    ToAnimatedValue,
645    ToComputedValue,
646    ToResolvedValue,
647    ToShmem,
648)]
649#[repr(C)]
650pub struct Shape<Angle, Position, LengthPercentage> {
651    /// The filling rule for this shape.
652    pub fill: FillRule,
653    /// The shape command data. Note that the starting point will be the first command in this
654    /// slice.
655    // Note: The first command is always GenericShapeCommand::Move.
656    #[compute(field_bound)]
657    pub commands: crate::OwnedSlice<GenericShapeCommand<Angle, Position, LengthPercentage>>,
658}
659
660impl<Angle, Position, LengthPercentage> Shape<Angle, Position, LengthPercentage> {
661    /// Returns the slice of GenericShapeCommand<..>.
662    #[inline]
663    pub fn commands(&self) -> &[GenericShapeCommand<Angle, Position, LengthPercentage>] {
664        &self.commands
665    }
666}
667
668impl<Angle, Position, LengthPercentage> Animate for Shape<Angle, Position, LengthPercentage>
669where
670    Angle: Animate,
671    Position: Animate,
672    LengthPercentage: Animate,
673{
674    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
675        if self.fill != other.fill {
676            return Err(());
677        }
678        let commands =
679            lists::by_computed_value::animate(&self.commands, &other.commands, procedure)?;
680        Ok(Self {
681            fill: self.fill,
682            commands,
683        })
684    }
685}
686
687impl<Angle, Position, LengthPercentage> ComputeSquaredDistance
688    for Shape<Angle, Position, LengthPercentage>
689where
690    Angle: ComputeSquaredDistance,
691    Position: ComputeSquaredDistance,
692    LengthPercentage: ComputeSquaredDistance,
693{
694    fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
695        if self.fill != other.fill {
696            return Err(());
697        }
698        lists::by_computed_value::squared_distance(&self.commands, &other.commands)
699    }
700}
701
702impl<Angle, Position, LengthPercentage> ToCss for Shape<Angle, Position, LengthPercentage>
703where
704    Angle: ToCss + Zero,
705    Position: ToCss,
706    LengthPercentage: PartialEq + ToCss,
707{
708    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
709    where
710        W: Write,
711    {
712        use style_traits::values::SequenceWriter;
713
714        // Per spec, we must have the first move command and at least one following command.
715        debug_assert!(self.commands.len() > 1);
716
717        dest.write_str("shape(")?;
718        if !is_default(&self.fill) {
719            self.fill.to_css(dest)?;
720            dest.write_char(' ')?;
721        }
722        dest.write_str("from ")?;
723        match &self.commands[0] {
724            ShapeCommand::Move {
725                point: CommandEndPoint::ToPosition(pos),
726            } => pos.to_css(dest)?,
727            ShapeCommand::Move {
728                point: CommandEndPoint::ByCoordinate(coord),
729            } => coord.to_css(dest)?,
730            _ => unreachable!("The first command must be move"),
731        }
732        dest.write_str(", ")?;
733        {
734            let mut writer = SequenceWriter::new(dest, ", ");
735            for command in self.commands.iter().skip(1) {
736                writer.item(command)?;
737            }
738        }
739        dest.write_char(')')
740    }
741}
742
743/// This is a more general shape(path) command type, for both shape() and path().
744///
745/// https://www.w3.org/TR/SVG11/paths.html#PathData
746/// https://drafts.csswg.org/css-shapes-2/#shape-function
747#[derive(
748    Animate,
749    Clone,
750    ComputeSquaredDistance,
751    Copy,
752    Debug,
753    Deserialize,
754    MallocSizeOf,
755    PartialEq,
756    Serialize,
757    SpecifiedValueInfo,
758    ToAnimatedValue,
759    ToAnimatedZero,
760    ToComputedValue,
761    ToResolvedValue,
762    ToShmem,
763)]
764#[allow(missing_docs)]
765#[repr(C, u8)]
766pub enum GenericShapeCommand<Angle, Position, LengthPercentage> {
767    /// The move command.
768    Move {
769        point: CommandEndPoint<Position, LengthPercentage>,
770    },
771    /// The line command.
772    Line {
773        point: CommandEndPoint<Position, LengthPercentage>,
774    },
775    /// The hline command.
776    HLine {
777        #[compute(field_bound)]
778        x: AxisEndPoint<LengthPercentage>,
779    },
780    /// The vline command.
781    VLine {
782        #[compute(field_bound)]
783        y: AxisEndPoint<LengthPercentage>,
784    },
785    /// The cubic Bézier curve command.
786    CubicCurve {
787        point: CommandEndPoint<Position, LengthPercentage>,
788        control1: ControlPoint<Position, LengthPercentage>,
789        control2: ControlPoint<Position, LengthPercentage>,
790    },
791    /// The quadratic Bézier curve command.
792    QuadCurve {
793        point: CommandEndPoint<Position, LengthPercentage>,
794        control1: ControlPoint<Position, LengthPercentage>,
795    },
796    /// The smooth command.
797    SmoothCubic {
798        point: CommandEndPoint<Position, LengthPercentage>,
799        control2: ControlPoint<Position, LengthPercentage>,
800    },
801    /// The smooth quadratic Bézier curve command.
802    SmoothQuad {
803        point: CommandEndPoint<Position, LengthPercentage>,
804    },
805    /// The arc command.
806    Arc {
807        point: CommandEndPoint<Position, LengthPercentage>,
808        radii: ArcRadii<LengthPercentage>,
809        arc_sweep: ArcSweep,
810        arc_size: ArcSize,
811        rotate: Angle,
812    },
813    /// The closepath command.
814    Close,
815}
816
817pub use self::GenericShapeCommand as ShapeCommand;
818
819impl<Angle, Position, LengthPercentage> ToCss for ShapeCommand<Angle, Position, LengthPercentage>
820where
821    Angle: ToCss + Zero,
822    Position: ToCss,
823    LengthPercentage: PartialEq + ToCss,
824{
825    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
826    where
827        W: fmt::Write,
828    {
829        use self::ShapeCommand::*;
830        match *self {
831            Move { ref point } => {
832                dest.write_str("move ")?;
833                point.to_css(dest)
834            },
835            Line { ref point } => {
836                dest.write_str("line ")?;
837                point.to_css(dest)
838            },
839            HLine { ref x } => {
840                dest.write_str("hline ")?;
841                x.to_css(dest)
842            },
843            VLine { ref y } => {
844                dest.write_str("vline ")?;
845                y.to_css(dest)
846            },
847            CubicCurve {
848                ref point,
849                ref control1,
850                ref control2,
851            } => {
852                dest.write_str("curve ")?;
853                point.to_css(dest)?;
854                dest.write_str(" with ")?;
855                control1.to_css(dest, point.is_abs())?;
856                dest.write_char(' ')?;
857                dest.write_char('/')?;
858                dest.write_char(' ')?;
859                control2.to_css(dest, point.is_abs())
860            },
861            QuadCurve {
862                ref point,
863                ref control1,
864            } => {
865                dest.write_str("curve ")?;
866                point.to_css(dest)?;
867                dest.write_str(" with ")?;
868                control1.to_css(dest, point.is_abs())
869            },
870            SmoothCubic {
871                ref point,
872                ref control2,
873            } => {
874                dest.write_str("smooth ")?;
875                point.to_css(dest)?;
876                dest.write_str(" with ")?;
877                control2.to_css(dest, point.is_abs())
878            },
879            SmoothQuad { ref point } => {
880                dest.write_str("smooth ")?;
881                point.to_css(dest)
882            },
883            Arc {
884                ref point,
885                ref radii,
886                arc_sweep,
887                arc_size,
888                ref rotate,
889            } => {
890                dest.write_str("arc ")?;
891                point.to_css(dest)?;
892                dest.write_str(" of ")?;
893                radii.to_css(dest)?;
894
895                if matches!(arc_sweep, ArcSweep::Cw) {
896                    dest.write_str(" cw")?;
897                }
898
899                if matches!(arc_size, ArcSize::Large) {
900                    dest.write_str(" large")?;
901                }
902
903                if !rotate.is_zero() {
904                    dest.write_str(" rotate ")?;
905                    rotate.to_css(dest)?;
906                }
907                Ok(())
908            },
909            Close => dest.write_str("close"),
910        }
911    }
912}
913
914/// Defines the end point of the command, which can be specified in absolute or relative coordinates,
915/// determined by their "to" or "by" components respectively.
916/// https://drafts.csswg.org/css-shapes/#typedef-shape-command-end-point
917#[allow(missing_docs)]
918#[derive(
919    Animate,
920    Clone,
921    ComputeSquaredDistance,
922    Copy,
923    Debug,
924    Deserialize,
925    MallocSizeOf,
926    PartialEq,
927    Serialize,
928    SpecifiedValueInfo,
929    ToAnimatedValue,
930    ToAnimatedZero,
931    ToComputedValue,
932    ToResolvedValue,
933    ToShmem,
934)]
935#[repr(C, u8)]
936pub enum CommandEndPoint<Position, LengthPercentage> {
937    ToPosition(Position),
938    ByCoordinate(CoordinatePair<LengthPercentage>),
939}
940
941impl<Position, LengthPercentage> CommandEndPoint<Position, LengthPercentage> {
942    /// Return true if it is absolute, i.e. it is To.
943    #[inline]
944    pub fn is_abs(&self) -> bool {
945        matches!(self, CommandEndPoint::ToPosition(_))
946    }
947}
948
949impl<Position, LengthPercentage> CommandEndPoint<Position, LengthPercentage> {
950    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
951    where
952        W: Write,
953        Position: ToCss,
954        LengthPercentage: ToCss,
955    {
956        match self {
957            CommandEndPoint::ToPosition(pos) => {
958                dest.write_str("to ")?;
959                pos.to_css(dest)
960            },
961            CommandEndPoint::ByCoordinate(coord) => {
962                dest.write_str("by ")?;
963                coord.to_css(dest)
964            },
965        }
966    }
967}
968
969/// Defines the end point for the commands <horizontal-line-command> and <vertical-line-command>, which
970/// can be specified in absolute or relative values, determined by their "to" or "by" components respectively.
971/// https://drafts.csswg.org/css-shapes-1/#typedef-shape-horizontal-line-command
972/// https://drafts.csswg.org/css-shapes-1/#typedef-shape-vertical-line-command
973#[allow(missing_docs)]
974#[derive(
975    Animate,
976    Clone,
977    Copy,
978    ComputeSquaredDistance,
979    Debug,
980    Deserialize,
981    MallocSizeOf,
982    PartialEq,
983    Parse,
984    Serialize,
985    SpecifiedValueInfo,
986    ToAnimatedValue,
987    ToAnimatedZero,
988    ToComputedValue,
989    ToResolvedValue,
990    ToShmem,
991)]
992#[repr(u8)]
993pub enum AxisEndPoint<LengthPercentage> {
994    ToPosition(#[compute(field_bound)] AxisPosition<LengthPercentage>),
995    ByCoordinate(LengthPercentage),
996}
997
998impl<LengthPercentage> AxisEndPoint<LengthPercentage> {
999    /// Return true if it is absolute, i.e. it is To.
1000    #[inline]
1001    pub fn is_abs(&self) -> bool {
1002        matches!(self, AxisEndPoint::ToPosition(_))
1003    }
1004}
1005
1006impl<LengthPercentage: ToCss> ToCss for AxisEndPoint<LengthPercentage> {
1007    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1008    where
1009        W: Write,
1010    {
1011        if self.is_abs() {
1012            dest.write_str("to ")?;
1013        } else {
1014            dest.write_str("by ")?;
1015        }
1016        match self {
1017            AxisEndPoint::ToPosition(pos) => pos.to_css(dest),
1018            AxisEndPoint::ByCoordinate(coord) => coord.to_css(dest),
1019        }
1020    }
1021}
1022
1023/// Defines how the absolutely positioned end point for <horizontal-line-command> and
1024/// <vertical-line-command> is positioned.
1025/// https://drafts.csswg.org/css-shapes-1/#typedef-shape-horizontal-line-command
1026/// https://drafts.csswg.org/css-shapes-1/#typedef-shape-vertical-line-command
1027#[allow(missing_docs)]
1028#[derive(
1029    Animate,
1030    Clone,
1031    ComputeSquaredDistance,
1032    Copy,
1033    Debug,
1034    Deserialize,
1035    MallocSizeOf,
1036    Parse,
1037    PartialEq,
1038    Serialize,
1039    SpecifiedValueInfo,
1040    ToAnimatedValue,
1041    ToAnimatedZero,
1042    ToCss,
1043    ToResolvedValue,
1044    ToShmem,
1045)]
1046#[repr(u8)]
1047pub enum AxisPosition<LengthPercentage> {
1048    LengthPercent(LengthPercentage),
1049    Keyword(AxisPositionKeyword),
1050}
1051
1052/// The set of position keywords used in <horizontal-line-command> and <vertical-line-command>
1053/// for absolute positioning. Note: this is the shared union list between hline and vline, so
1054/// not every value is valid for either. I.e. hline cannot be positioned with top or y-start.
1055/// https://drafts.csswg.org/css-shapes-1/#typedef-shape-horizontal-line-command
1056/// https://drafts.csswg.org/css-shapes-1/#typedef-shape-vertical-line-command
1057#[allow(missing_docs)]
1058#[derive(
1059    Animate,
1060    Clone,
1061    ComputeSquaredDistance,
1062    Copy,
1063    Debug,
1064    Deserialize,
1065    MallocSizeOf,
1066    Parse,
1067    PartialEq,
1068    Serialize,
1069    SpecifiedValueInfo,
1070    ToAnimatedValue,
1071    ToAnimatedZero,
1072    ToCss,
1073    ToResolvedValue,
1074    ToShmem,
1075)]
1076#[repr(u8)]
1077pub enum AxisPositionKeyword {
1078    Center,
1079    Left,
1080    Right,
1081    Top,
1082    Bottom,
1083    XStart,
1084    XEnd,
1085    YStart,
1086    YEnd,
1087}
1088
1089impl AxisPositionKeyword {
1090    /// Returns the axis position keyword as its corresponding percentage.
1091    #[inline]
1092    pub fn as_percentage(&self) -> Percentage {
1093        match self {
1094            Self::Center => Percentage(0.5),
1095            Self::Left | Self::Top | Self::XStart | Self::YStart => Percentage(0.),
1096            Self::Right | Self::Bottom | Self::XEnd | Self::YEnd => Percentage(1.),
1097        }
1098    }
1099}
1100
1101/// Defines a pair of coordinates, representing a rightward and downward offset, respectively, from
1102/// a specified reference point. Percentages are resolved against the width or height,
1103/// respectively, of the reference box.
1104/// https://drafts.csswg.org/css-shapes-2/#typedef-shape-coordinate-pair
1105#[allow(missing_docs)]
1106#[derive(
1107    AddAssign,
1108    Animate,
1109    Clone,
1110    ComputeSquaredDistance,
1111    Copy,
1112    Debug,
1113    Deserialize,
1114    MallocSizeOf,
1115    PartialEq,
1116    Serialize,
1117    SpecifiedValueInfo,
1118    ToAnimatedValue,
1119    ToAnimatedZero,
1120    ToComputedValue,
1121    ToCss,
1122    ToResolvedValue,
1123    ToShmem,
1124)]
1125#[repr(C)]
1126pub struct CoordinatePair<LengthPercentage> {
1127    pub x: LengthPercentage,
1128    pub y: LengthPercentage,
1129}
1130
1131impl<LengthPercentage> CoordinatePair<LengthPercentage> {
1132    /// Create a CoordinatePair.
1133    #[inline]
1134    pub fn new(x: LengthPercentage, y: LengthPercentage) -> Self {
1135        Self { x, y }
1136    }
1137}
1138
1139/// Defines a control point for a quadratic or cubic Bézier curve, which can be specified
1140/// in absolute or relative coordinates.
1141/// https://drafts.csswg.org/css-shapes/#typedef-shape-control-point
1142#[allow(missing_docs)]
1143#[derive(
1144    Animate,
1145    Clone,
1146    Copy,
1147    ComputeSquaredDistance,
1148    Debug,
1149    Deserialize,
1150    MallocSizeOf,
1151    PartialEq,
1152    Serialize,
1153    SpecifiedValueInfo,
1154    ToAnimatedValue,
1155    ToAnimatedZero,
1156    ToComputedValue,
1157    ToResolvedValue,
1158    ToShmem,
1159)]
1160#[repr(C, u8)]
1161pub enum ControlPoint<Position, LengthPercentage> {
1162    Absolute(Position),
1163    Relative(RelativeControlPoint<LengthPercentage>),
1164}
1165
1166impl<Position, LengthPercentage> ControlPoint<Position, LengthPercentage> {
1167    /// Serialize <control-point>
1168    pub fn to_css<W>(&self, dest: &mut CssWriter<W>, is_end_point_abs: bool) -> fmt::Result
1169    where
1170        W: Write,
1171        Position: ToCss,
1172        LengthPercentage: ToCss,
1173    {
1174        match self {
1175            ControlPoint::Absolute(pos) => pos.to_css(dest),
1176            ControlPoint::Relative(point) => point.to_css(dest, is_end_point_abs),
1177        }
1178    }
1179}
1180
1181/// Defines a relative control point to a quadratic or cubic Bézier curve, dependent on the
1182/// reference value. The default `None` is to be relative to the command’s starting point.
1183/// https://drafts.csswg.org/css-shapes/#typedef-shape-relative-control-point
1184#[allow(missing_docs)]
1185#[derive(
1186    Animate,
1187    Clone,
1188    Copy,
1189    Debug,
1190    Deserialize,
1191    MallocSizeOf,
1192    PartialEq,
1193    Serialize,
1194    SpecifiedValueInfo,
1195    ToAnimatedValue,
1196    ToAnimatedZero,
1197    ToComputedValue,
1198    ToResolvedValue,
1199    ToShmem,
1200)]
1201#[repr(C)]
1202pub struct RelativeControlPoint<LengthPercentage> {
1203    pub coord: CoordinatePair<LengthPercentage>,
1204    pub reference: ControlReference,
1205}
1206
1207impl<LengthPercentage: ToCss> RelativeControlPoint<LengthPercentage> {
1208    fn to_css<W>(&self, dest: &mut CssWriter<W>, is_end_point_abs: bool) -> fmt::Result
1209    where
1210        W: Write,
1211    {
1212        self.coord.to_css(dest)?;
1213        match self.reference {
1214            ControlReference::Origin if is_end_point_abs => Ok(()),
1215            ControlReference::Start if !is_end_point_abs => Ok(()),
1216            other => {
1217                dest.write_str(" from ")?;
1218                other.to_css(dest)
1219            },
1220        }
1221    }
1222}
1223
1224impl<LengthPercentage: ComputeSquaredDistance> ComputeSquaredDistance
1225    for RelativeControlPoint<LengthPercentage>
1226{
1227    fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
1228        self.coord.compute_squared_distance(&other.coord)
1229    }
1230}
1231
1232/// Defines the point of reference for a <relative-control-point>.
1233///
1234/// When a reference is not specified, depending on whether the associated
1235/// <command-end-point> is absolutely or relatively positioned, the default
1236/// will be `Origin` or `Start`, respectively.
1237/// https://drafts.csswg.org/css-shapes/#typedef-shape-relative-control-point
1238#[allow(missing_docs)]
1239#[derive(
1240    Animate,
1241    Clone,
1242    Copy,
1243    Debug,
1244    Deserialize,
1245    Eq,
1246    MallocSizeOf,
1247    PartialEq,
1248    Parse,
1249    Serialize,
1250    SpecifiedValueInfo,
1251    ToAnimatedValue,
1252    ToAnimatedZero,
1253    ToComputedValue,
1254    ToCss,
1255    ToResolvedValue,
1256    ToShmem,
1257)]
1258#[repr(C)]
1259pub enum ControlReference {
1260    Start,
1261    End,
1262    Origin,
1263}
1264
1265/// Defines the radiuses for an <arc-command>.
1266///
1267/// The first <length-percentage> is the ellipse's horizontal radius, and the second is
1268/// the vertical radius. If only one value is provided, it is used for both radii, and any
1269/// <percentage> is resolved against the direction-agnostic size of the reference box.
1270/// https://drafts.csswg.org/css-shapes-1/#typedef-shape-arc-command
1271#[allow(missing_docs)]
1272#[derive(
1273    Animate,
1274    Clone,
1275    ComputeSquaredDistance,
1276    Copy,
1277    Debug,
1278    Deserialize,
1279    MallocSizeOf,
1280    PartialEq,
1281    Serialize,
1282    SpecifiedValueInfo,
1283    ToAnimatedValue,
1284    ToAnimatedZero,
1285    ToComputedValue,
1286    ToCss,
1287    ToResolvedValue,
1288    ToShmem,
1289)]
1290#[repr(C)]
1291pub struct ArcRadii<LengthPercentage> {
1292    pub rx: LengthPercentage,
1293    pub ry: Optional<LengthPercentage>,
1294}
1295
1296/// This indicates that the arc that is traced around the ellipse clockwise or counter-clockwise
1297/// from the center.
1298/// https://drafts.csswg.org/css-shapes-2/#typedef-shape-arc-sweep
1299#[derive(
1300    Clone,
1301    Copy,
1302    Debug,
1303    Deserialize,
1304    FromPrimitive,
1305    MallocSizeOf,
1306    Parse,
1307    PartialEq,
1308    Serialize,
1309    SpecifiedValueInfo,
1310    ToAnimatedValue,
1311    ToAnimatedZero,
1312    ToComputedValue,
1313    ToCss,
1314    ToResolvedValue,
1315    ToShmem,
1316)]
1317#[repr(u8)]
1318pub enum ArcSweep {
1319    /// Counter-clockwise. The default value. (This also represents 0 in the svg path.)
1320    Ccw = 0,
1321    /// Clockwise. (This also represents 1 in the svg path.)
1322    Cw = 1,
1323}
1324
1325impl Animate for ArcSweep {
1326    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
1327        use num_traits::FromPrimitive;
1328        // If an arc command has different <arc-sweep> between its starting and ending list, then
1329        // the interpolated result uses cw for any progress value between 0 and 1.
1330        // Note: we cast progress from f64->f32->f64 to drop tiny noise near 0.0.
1331        let progress = procedure.weights().1 as f32 as f64;
1332        let procedure = Procedure::Interpolate { progress };
1333        (*self as i32 as f32)
1334            .animate(&(*other as i32 as f32), procedure)
1335            .map(|v| ArcSweep::from_u8((v > 0.) as u8).unwrap_or(ArcSweep::Ccw))
1336    }
1337}
1338
1339impl ComputeSquaredDistance for ArcSweep {
1340    fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
1341        (*self as i32).compute_squared_distance(&(*other as i32))
1342    }
1343}
1344
1345/// This indicates that the larger or smaller, respectively, of the two possible arcs must be
1346/// chosen.
1347/// https://drafts.csswg.org/css-shapes-2/#typedef-shape-arc-size
1348#[derive(
1349    Clone,
1350    Copy,
1351    Debug,
1352    Deserialize,
1353    FromPrimitive,
1354    MallocSizeOf,
1355    Parse,
1356    PartialEq,
1357    Serialize,
1358    SpecifiedValueInfo,
1359    ToAnimatedValue,
1360    ToAnimatedZero,
1361    ToComputedValue,
1362    ToCss,
1363    ToResolvedValue,
1364    ToShmem,
1365)]
1366#[repr(u8)]
1367pub enum ArcSize {
1368    /// Choose the small one. The default value. (This also represents 0 in the svg path.)
1369    Small = 0,
1370    /// Choose the large one. (This also represents 1 in the svg path.)
1371    Large = 1,
1372}
1373
1374impl Animate for ArcSize {
1375    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
1376        use num_traits::FromPrimitive;
1377        // If it has different <arc-size> keywords, then the interpolated result uses large for any
1378        // progress value between 0 and 1.
1379        // Note: we cast progress from f64->f32->f64 to drop tiny noise near 0.0.
1380        let progress = procedure.weights().1 as f32 as f64;
1381        let procedure = Procedure::Interpolate { progress };
1382        (*self as i32 as f32)
1383            .animate(&(*other as i32 as f32), procedure)
1384            .map(|v| ArcSize::from_u8((v > 0.) as u8).unwrap_or(ArcSize::Small))
1385    }
1386}
1387
1388impl ComputeSquaredDistance for ArcSize {
1389    fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
1390        (*self as i32).compute_squared_distance(&(*other as i32))
1391    }
1392}