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::values::animated::{lists, Animate, Procedure, ToAnimatedZero};
9use crate::values::distance::{ComputeSquaredDistance, SquaredDistance};
10use crate::values::generics::border::GenericBorderRadius;
11use crate::values::generics::position::GenericPositionOrAuto;
12use crate::values::generics::rect::Rect;
13use crate::values::specified::svg_path::{PathCommand, SVGPathData};
14use crate::Zero;
15use std::fmt::{self, Write};
16use style_traits::{CssWriter, ToCss};
17
18/// <https://drafts.fxtf.org/css-masking-1/#typedef-geometry-box>
19#[allow(missing_docs)]
20#[derive(
21    Animate,
22    Clone,
23    ComputeSquaredDistance,
24    Copy,
25    Debug,
26    MallocSizeOf,
27    PartialEq,
28    Parse,
29    SpecifiedValueInfo,
30    ToAnimatedValue,
31    ToComputedValue,
32    ToCss,
33    ToResolvedValue,
34    ToShmem,
35)]
36#[repr(u8)]
37pub enum ShapeGeometryBox {
38    /// Depending on which kind of element this style value applied on, the
39    /// default value of the reference-box can be different.  For an HTML
40    /// element, the default value of reference-box is border-box; for an SVG
41    /// element, the default value is fill-box.  Since we can not determine the
42    /// default value at parsing time, we keep this value to make a decision on
43    /// it.
44    #[css(skip)]
45    ElementDependent,
46    FillBox,
47    StrokeBox,
48    ViewBox,
49    ShapeBox(ShapeBox),
50}
51
52impl Default for ShapeGeometryBox {
53    fn default() -> Self {
54        Self::ElementDependent
55    }
56}
57
58/// Skip the serialization if the author omits the box or specifies border-box.
59#[inline]
60fn is_default_box_for_clip_path(b: &ShapeGeometryBox) -> bool {
61    // Note: for clip-path, ElementDependent is always border-box, so we have to check both of them
62    // for serialization.
63    matches!(b, ShapeGeometryBox::ElementDependent)
64        || matches!(b, ShapeGeometryBox::ShapeBox(ShapeBox::BorderBox))
65}
66
67/// https://drafts.csswg.org/css-shapes-1/#typedef-shape-box
68#[allow(missing_docs)]
69#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
70#[derive(
71    Animate,
72    Clone,
73    Copy,
74    ComputeSquaredDistance,
75    Debug,
76    Eq,
77    MallocSizeOf,
78    Parse,
79    PartialEq,
80    SpecifiedValueInfo,
81    ToAnimatedValue,
82    ToComputedValue,
83    ToCss,
84    ToResolvedValue,
85    ToShmem,
86)]
87#[repr(u8)]
88pub enum ShapeBox {
89    MarginBox,
90    BorderBox,
91    PaddingBox,
92    ContentBox,
93}
94
95impl Default for ShapeBox {
96    fn default() -> Self {
97        ShapeBox::MarginBox
98    }
99}
100
101/// A value for the `clip-path` property.
102#[allow(missing_docs)]
103#[derive(
104    Animate,
105    Clone,
106    ComputeSquaredDistance,
107    Debug,
108    MallocSizeOf,
109    PartialEq,
110    SpecifiedValueInfo,
111    ToAnimatedValue,
112    ToComputedValue,
113    ToCss,
114    ToResolvedValue,
115    ToShmem,
116)]
117#[animation(no_bound(U))]
118#[repr(u8)]
119pub enum GenericClipPath<BasicShape, U> {
120    #[animation(error)]
121    None,
122    #[animation(error)]
123    Url(U),
124    Shape(
125        Box<BasicShape>,
126        #[css(skip_if = "is_default_box_for_clip_path")] ShapeGeometryBox,
127    ),
128    #[animation(error)]
129    Box(ShapeGeometryBox),
130}
131
132pub use self::GenericClipPath as ClipPath;
133
134/// A value for the `shape-outside` property.
135#[allow(missing_docs)]
136#[derive(
137    Animate,
138    Clone,
139    ComputeSquaredDistance,
140    Debug,
141    MallocSizeOf,
142    PartialEq,
143    SpecifiedValueInfo,
144    ToAnimatedValue,
145    ToComputedValue,
146    ToCss,
147    ToResolvedValue,
148    ToShmem,
149)]
150#[animation(no_bound(I))]
151#[repr(u8)]
152pub enum GenericShapeOutside<BasicShape, I> {
153    #[animation(error)]
154    None,
155    #[animation(error)]
156    Image(I),
157    Shape(Box<BasicShape>, #[css(skip_if = "is_default")] ShapeBox),
158    #[animation(error)]
159    Box(ShapeBox),
160}
161
162pub use self::GenericShapeOutside as ShapeOutside;
163
164/// The <basic-shape>.
165///
166/// https://drafts.csswg.org/css-shapes-1/#supported-basic-shapes
167#[derive(
168    Animate,
169    Clone,
170    ComputeSquaredDistance,
171    Debug,
172    Deserialize,
173    MallocSizeOf,
174    PartialEq,
175    Serialize,
176    SpecifiedValueInfo,
177    ToAnimatedValue,
178    ToComputedValue,
179    ToCss,
180    ToResolvedValue,
181    ToShmem,
182)]
183#[repr(C, u8)]
184pub enum GenericBasicShape<
185    Angle,
186    Position,
187    LengthPercentage,
188    NonNegativeLengthPercentage,
189    BasicShapeRect,
190> {
191    /// The <basic-shape-rect>.
192    Rect(BasicShapeRect),
193    /// Defines a circle with a center and a radius.
194    Circle(
195        #[css(field_bound)]
196        #[shmem(field_bound)]
197        Circle<Position, NonNegativeLengthPercentage>,
198    ),
199    /// Defines an ellipse with a center and x-axis/y-axis radii.
200    Ellipse(
201        #[css(field_bound)]
202        #[shmem(field_bound)]
203        Ellipse<Position, NonNegativeLengthPercentage>,
204    ),
205    /// Defines a polygon with pair arguments.
206    Polygon(GenericPolygon<LengthPercentage>),
207    /// Defines a path() or shape().
208    PathOrShape(
209        #[animation(field_bound)]
210        #[css(field_bound)]
211        GenericPathOrShapeFunction<Angle, LengthPercentage>,
212    ),
213}
214
215pub use self::GenericBasicShape as BasicShape;
216
217/// <https://drafts.csswg.org/css-shapes/#funcdef-inset>
218#[allow(missing_docs)]
219#[derive(
220    Animate,
221    Clone,
222    ComputeSquaredDistance,
223    Debug,
224    Deserialize,
225    MallocSizeOf,
226    PartialEq,
227    Serialize,
228    SpecifiedValueInfo,
229    ToAnimatedValue,
230    ToComputedValue,
231    ToResolvedValue,
232    ToShmem,
233)]
234#[css(function = "inset")]
235#[repr(C)]
236pub struct GenericInsetRect<LengthPercentage, NonNegativeLengthPercentage> {
237    pub rect: Rect<LengthPercentage>,
238    #[shmem(field_bound)]
239    pub round: GenericBorderRadius<NonNegativeLengthPercentage>,
240}
241
242pub use self::GenericInsetRect as InsetRect;
243
244/// <https://drafts.csswg.org/css-shapes/#funcdef-circle>
245#[allow(missing_docs)]
246#[derive(
247    Animate,
248    Clone,
249    ComputeSquaredDistance,
250    Copy,
251    Debug,
252    Deserialize,
253    MallocSizeOf,
254    PartialEq,
255    Serialize,
256    SpecifiedValueInfo,
257    ToAnimatedValue,
258    ToComputedValue,
259    ToResolvedValue,
260    ToShmem,
261)]
262#[css(function)]
263#[repr(C)]
264pub struct Circle<Position, NonNegativeLengthPercentage> {
265    pub position: GenericPositionOrAuto<Position>,
266    pub radius: GenericShapeRadius<NonNegativeLengthPercentage>,
267}
268
269/// <https://drafts.csswg.org/css-shapes/#funcdef-ellipse>
270#[allow(missing_docs)]
271#[derive(
272    Animate,
273    Clone,
274    ComputeSquaredDistance,
275    Copy,
276    Debug,
277    Deserialize,
278    MallocSizeOf,
279    PartialEq,
280    Serialize,
281    SpecifiedValueInfo,
282    ToAnimatedValue,
283    ToComputedValue,
284    ToResolvedValue,
285    ToShmem,
286)]
287#[css(function)]
288#[repr(C)]
289pub struct Ellipse<Position, NonNegativeLengthPercentage> {
290    pub position: GenericPositionOrAuto<Position>,
291    pub semiaxis_x: GenericShapeRadius<NonNegativeLengthPercentage>,
292    pub semiaxis_y: GenericShapeRadius<NonNegativeLengthPercentage>,
293}
294
295/// <https://drafts.csswg.org/css-shapes/#typedef-shape-radius>
296#[allow(missing_docs)]
297#[derive(
298    Animate,
299    Clone,
300    ComputeSquaredDistance,
301    Copy,
302    Debug,
303    Deserialize,
304    MallocSizeOf,
305    Parse,
306    PartialEq,
307    Serialize,
308    SpecifiedValueInfo,
309    ToAnimatedValue,
310    ToComputedValue,
311    ToCss,
312    ToResolvedValue,
313    ToShmem,
314)]
315#[repr(C, u8)]
316pub enum GenericShapeRadius<NonNegativeLengthPercentage> {
317    Length(NonNegativeLengthPercentage),
318    #[animation(error)]
319    ClosestSide,
320    #[animation(error)]
321    FarthestSide,
322}
323
324pub use self::GenericShapeRadius as ShapeRadius;
325
326/// A generic type for representing the `polygon()` function
327///
328/// <https://drafts.csswg.org/css-shapes/#funcdef-polygon>
329#[derive(
330    Clone,
331    Debug,
332    Deserialize,
333    MallocSizeOf,
334    PartialEq,
335    Serialize,
336    SpecifiedValueInfo,
337    ToAnimatedValue,
338    ToComputedValue,
339    ToCss,
340    ToResolvedValue,
341    ToShmem,
342)]
343#[css(comma, function = "polygon")]
344#[repr(C)]
345pub struct GenericPolygon<LengthPercentage> {
346    /// The filling rule for a polygon.
347    #[css(skip_if = "is_default")]
348    pub fill: FillRule,
349    /// A collection of (x, y) coordinates to draw the polygon.
350    #[css(iterable)]
351    pub coordinates: crate::OwnedSlice<PolygonCoord<LengthPercentage>>,
352}
353
354pub use self::GenericPolygon as Polygon;
355
356/// Coordinates for Polygon.
357#[derive(
358    Animate,
359    Clone,
360    ComputeSquaredDistance,
361    Debug,
362    Deserialize,
363    MallocSizeOf,
364    PartialEq,
365    Serialize,
366    SpecifiedValueInfo,
367    ToAnimatedValue,
368    ToComputedValue,
369    ToCss,
370    ToResolvedValue,
371    ToShmem,
372)]
373#[repr(C)]
374pub struct PolygonCoord<LengthPercentage>(pub LengthPercentage, pub LengthPercentage);
375
376/// path() function or shape() function.
377#[derive(
378    Clone,
379    ComputeSquaredDistance,
380    Debug,
381    Deserialize,
382    MallocSizeOf,
383    PartialEq,
384    Serialize,
385    SpecifiedValueInfo,
386    ToAnimatedValue,
387    ToComputedValue,
388    ToCss,
389    ToResolvedValue,
390    ToShmem,
391)]
392#[repr(C, u8)]
393pub enum GenericPathOrShapeFunction<Angle, LengthPercentage> {
394    /// Defines a path with SVG path syntax.
395    Path(Path),
396    /// Defines a shape function, which is identical to path() but it uses the CSS syntax.
397    Shape(#[css(field_bound)] Shape<Angle, LengthPercentage>),
398}
399
400// https://drafts.csswg.org/css-shapes/#typedef-fill-rule
401// NOTE: Basic shapes spec says that these are the only two values, however
402// https://www.w3.org/TR/SVG/painting.html#FillRuleProperty
403// says that it can also be `inherit`
404#[allow(missing_docs)]
405#[derive(
406    Animate,
407    Clone,
408    ComputeSquaredDistance,
409    Copy,
410    Debug,
411    Deserialize,
412    Eq,
413    MallocSizeOf,
414    Parse,
415    PartialEq,
416    Serialize,
417    SpecifiedValueInfo,
418    ToAnimatedValue,
419    ToComputedValue,
420    ToCss,
421    ToResolvedValue,
422    ToShmem,
423)]
424#[repr(u8)]
425pub enum FillRule {
426    Nonzero,
427    Evenodd,
428}
429
430/// The path function.
431///
432/// https://drafts.csswg.org/css-shapes-1/#funcdef-basic-shape-path
433#[derive(
434    Animate,
435    Clone,
436    ComputeSquaredDistance,
437    Debug,
438    Deserialize,
439    MallocSizeOf,
440    PartialEq,
441    Serialize,
442    SpecifiedValueInfo,
443    ToAnimatedValue,
444    ToComputedValue,
445    ToCss,
446    ToResolvedValue,
447    ToShmem,
448)]
449#[css(comma, function = "path")]
450#[repr(C)]
451pub struct Path {
452    /// The filling rule for the svg path.
453    #[css(skip_if = "is_default")]
454    pub fill: FillRule,
455    /// The svg path data.
456    pub path: SVGPathData,
457}
458
459impl Path {
460    /// Returns the slice of PathCommand.
461    #[inline]
462    pub fn commands(&self) -> &[PathCommand] {
463        self.path.commands()
464    }
465}
466
467impl<B, U> ToAnimatedZero for ClipPath<B, U> {
468    fn to_animated_zero(&self) -> Result<Self, ()> {
469        Err(())
470    }
471}
472
473impl<B, U> ToAnimatedZero for ShapeOutside<B, U> {
474    fn to_animated_zero(&self) -> Result<Self, ()> {
475        Err(())
476    }
477}
478
479impl<Length, NonNegativeLength> ToCss for InsetRect<Length, NonNegativeLength>
480where
481    Length: ToCss + PartialEq,
482    NonNegativeLength: ToCss + PartialEq + Zero,
483{
484    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
485    where
486        W: Write,
487    {
488        dest.write_str("inset(")?;
489        self.rect.to_css(dest)?;
490        if !self.round.is_zero() {
491            dest.write_str(" round ")?;
492            self.round.to_css(dest)?;
493        }
494        dest.write_char(')')
495    }
496}
497
498impl<Position, NonNegativeLengthPercentage> ToCss for Circle<Position, NonNegativeLengthPercentage>
499where
500    Position: ToCss,
501    NonNegativeLengthPercentage: ToCss + PartialEq,
502{
503    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
504    where
505        W: Write,
506    {
507        let has_radius = self.radius != Default::default();
508
509        dest.write_str("circle(")?;
510        if has_radius {
511            self.radius.to_css(dest)?;
512        }
513
514        // Preserve the `at <position>` even if it specified the default value.
515        // https://github.com/w3c/csswg-drafts/issues/8695
516        if !matches!(self.position, GenericPositionOrAuto::Auto) {
517            if has_radius {
518                dest.write_char(' ')?;
519            }
520            dest.write_str("at ")?;
521            self.position.to_css(dest)?;
522        }
523        dest.write_char(')')
524    }
525}
526
527impl<Position, NonNegativeLengthPercentage> ToCss for Ellipse<Position, NonNegativeLengthPercentage>
528where
529    Position: ToCss,
530    NonNegativeLengthPercentage: ToCss + PartialEq,
531{
532    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
533    where
534        W: Write,
535    {
536        let has_radii =
537            self.semiaxis_x != Default::default() || self.semiaxis_y != Default::default();
538
539        dest.write_str("ellipse(")?;
540        if has_radii {
541            self.semiaxis_x.to_css(dest)?;
542            dest.write_char(' ')?;
543            self.semiaxis_y.to_css(dest)?;
544        }
545
546        // Preserve the `at <position>` even if it specified the default value.
547        // https://github.com/w3c/csswg-drafts/issues/8695
548        if !matches!(self.position, GenericPositionOrAuto::Auto) {
549            if has_radii {
550                dest.write_char(' ')?;
551            }
552            dest.write_str("at ")?;
553            self.position.to_css(dest)?;
554        }
555        dest.write_char(')')
556    }
557}
558
559impl<L> Default for ShapeRadius<L> {
560    #[inline]
561    fn default() -> Self {
562        ShapeRadius::ClosestSide
563    }
564}
565
566impl<L> Animate for Polygon<L>
567where
568    L: Animate,
569{
570    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
571        if self.fill != other.fill {
572            return Err(());
573        }
574        let coordinates =
575            lists::by_computed_value::animate(&self.coordinates, &other.coordinates, procedure)?;
576        Ok(Polygon {
577            fill: self.fill,
578            coordinates,
579        })
580    }
581}
582
583impl<L> ComputeSquaredDistance for Polygon<L>
584where
585    L: ComputeSquaredDistance,
586{
587    fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
588        if self.fill != other.fill {
589            return Err(());
590        }
591        lists::by_computed_value::squared_distance(&self.coordinates, &other.coordinates)
592    }
593}
594
595impl Default for FillRule {
596    #[inline]
597    fn default() -> Self {
598        FillRule::Nonzero
599    }
600}
601
602#[inline]
603fn is_default<T: Default + PartialEq>(fill: &T) -> bool {
604    *fill == Default::default()
605}
606
607/// The shape function defined in css-shape-2.
608/// shape() = shape(<fill-rule>? from <coordinate-pair>, <shape-command>#)
609///
610/// https://drafts.csswg.org/css-shapes-2/#shape-function
611#[derive(
612    Clone,
613    Debug,
614    Deserialize,
615    MallocSizeOf,
616    PartialEq,
617    Serialize,
618    SpecifiedValueInfo,
619    ToAnimatedValue,
620    ToComputedValue,
621    ToResolvedValue,
622    ToShmem,
623)]
624#[repr(C)]
625pub struct Shape<Angle, LengthPercentage> {
626    /// The filling rule for this shape.
627    pub fill: FillRule,
628    /// The shape command data. Note that the starting point will be the first command in this
629    /// slice.
630    // Note: The first command is always GenericShapeCommand::Move.
631    pub commands: crate::OwnedSlice<GenericShapeCommand<Angle, LengthPercentage>>,
632}
633
634impl<Angle, LengthPercentage> Shape<Angle, LengthPercentage> {
635    /// Returns the slice of GenericShapeCommand<..>.
636    #[inline]
637    pub fn commands(&self) -> &[GenericShapeCommand<Angle, LengthPercentage>] {
638        &self.commands
639    }
640}
641
642impl<Angle, LengthPercentage> Animate for Shape<Angle, LengthPercentage>
643where
644    Angle: Animate,
645    LengthPercentage: Animate,
646{
647    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
648        if self.fill != other.fill {
649            return Err(());
650        }
651        let commands =
652            lists::by_computed_value::animate(&self.commands, &other.commands, procedure)?;
653        Ok(Self {
654            fill: self.fill,
655            commands,
656        })
657    }
658}
659
660impl<Angle, LengthPercentage> ComputeSquaredDistance for Shape<Angle, LengthPercentage>
661where
662    Angle: ComputeSquaredDistance,
663    LengthPercentage: ComputeSquaredDistance,
664{
665    fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
666        if self.fill != other.fill {
667            return Err(());
668        }
669        lists::by_computed_value::squared_distance(&self.commands, &other.commands)
670    }
671}
672
673impl<Angle, LengthPercentage> ToCss for Shape<Angle, LengthPercentage>
674where
675    Angle: ToCss + Zero,
676    LengthPercentage: PartialEq + ToCss,
677{
678    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
679    where
680        W: Write,
681    {
682        use style_traits::values::SequenceWriter;
683
684        // Per spec, we must have the first move command and at least one following command.
685        debug_assert!(self.commands.len() > 1);
686
687        dest.write_str("shape(")?;
688        if !is_default(&self.fill) {
689            self.fill.to_css(dest)?;
690            dest.write_char(' ')?;
691        }
692        dest.write_str("from ")?;
693        match self.commands[0] {
694            ShapeCommand::Move {
695                by_to: _,
696                ref point,
697            } => point.to_css(dest)?,
698            _ => unreachable!("The first command must be move"),
699        }
700        dest.write_str(", ")?;
701        {
702            let mut writer = SequenceWriter::new(dest, ", ");
703            for command in self.commands.iter().skip(1) {
704                writer.item(command)?;
705            }
706        }
707        dest.write_char(')')
708    }
709}
710
711/// This is a more general shape(path) command type, for both shape() and path().
712///
713/// https://www.w3.org/TR/SVG11/paths.html#PathData
714/// https://drafts.csswg.org/css-shapes-2/#shape-function
715#[derive(
716    Animate,
717    Clone,
718    ComputeSquaredDistance,
719    Copy,
720    Debug,
721    Deserialize,
722    MallocSizeOf,
723    PartialEq,
724    Serialize,
725    SpecifiedValueInfo,
726    ToAnimatedValue,
727    ToAnimatedZero,
728    ToComputedValue,
729    ToResolvedValue,
730    ToShmem,
731)]
732#[allow(missing_docs)]
733#[repr(C, u8)]
734pub enum GenericShapeCommand<Angle, LengthPercentage> {
735    /// The move command.
736    Move {
737        by_to: ByTo,
738        point: CoordinatePair<LengthPercentage>,
739    },
740    /// The line command.
741    Line {
742        by_to: ByTo,
743        point: CoordinatePair<LengthPercentage>,
744    },
745    /// The hline command.
746    HLine { by_to: ByTo, x: LengthPercentage },
747    /// The vline command.
748    VLine { by_to: ByTo, y: LengthPercentage },
749    /// The cubic Bézier curve command.
750    CubicCurve {
751        by_to: ByTo,
752        point: CoordinatePair<LengthPercentage>,
753        control1: CoordinatePair<LengthPercentage>,
754        control2: CoordinatePair<LengthPercentage>,
755    },
756    /// The quadratic Bézier curve command.
757    QuadCurve {
758        by_to: ByTo,
759        point: CoordinatePair<LengthPercentage>,
760        control1: CoordinatePair<LengthPercentage>,
761    },
762    /// The smooth command.
763    SmoothCubic {
764        by_to: ByTo,
765        point: CoordinatePair<LengthPercentage>,
766        control2: CoordinatePair<LengthPercentage>,
767    },
768    /// The smooth quadratic Bézier curve command.
769    SmoothQuad {
770        by_to: ByTo,
771        point: CoordinatePair<LengthPercentage>,
772    },
773    /// The arc command.
774    Arc {
775        by_to: ByTo,
776        point: CoordinatePair<LengthPercentage>,
777        radii: CoordinatePair<LengthPercentage>,
778        arc_sweep: ArcSweep,
779        arc_size: ArcSize,
780        rotate: Angle,
781    },
782    /// The closepath command.
783    Close,
784}
785
786pub use self::GenericShapeCommand as ShapeCommand;
787
788impl<Angle, LengthPercentage> ToCss for ShapeCommand<Angle, LengthPercentage>
789where
790    Angle: ToCss + Zero,
791    LengthPercentage: PartialEq + ToCss,
792{
793    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
794    where
795        W: fmt::Write,
796    {
797        use self::ShapeCommand::*;
798        match *self {
799            Move { by_to, ref point } => {
800                dest.write_str("move ")?;
801                by_to.to_css(dest)?;
802                dest.write_char(' ')?;
803                point.to_css(dest)
804            },
805            Line { by_to, ref point } => {
806                dest.write_str("line ")?;
807                by_to.to_css(dest)?;
808                dest.write_char(' ')?;
809                point.to_css(dest)
810            },
811            HLine { by_to, ref x } => {
812                dest.write_str("hline ")?;
813                by_to.to_css(dest)?;
814                dest.write_char(' ')?;
815                x.to_css(dest)
816            },
817            VLine { by_to, ref y } => {
818                dest.write_str("vline ")?;
819                by_to.to_css(dest)?;
820                dest.write_char(' ')?;
821                y.to_css(dest)
822            },
823            CubicCurve {
824                by_to,
825                ref point,
826                ref control1,
827                ref control2,
828            } => {
829                dest.write_str("curve ")?;
830                by_to.to_css(dest)?;
831                dest.write_char(' ')?;
832                point.to_css(dest)?;
833                dest.write_str(" via ")?;
834                control1.to_css(dest)?;
835                dest.write_char(' ')?;
836                control2.to_css(dest)
837            },
838            QuadCurve {
839                by_to,
840                ref point,
841                ref control1,
842            } => {
843                dest.write_str("curve ")?;
844                by_to.to_css(dest)?;
845                dest.write_char(' ')?;
846                point.to_css(dest)?;
847                dest.write_str(" via ")?;
848                control1.to_css(dest)
849            },
850            SmoothCubic {
851                by_to,
852                ref point,
853                ref control2,
854            } => {
855                dest.write_str("smooth ")?;
856                by_to.to_css(dest)?;
857                dest.write_char(' ')?;
858                point.to_css(dest)?;
859                dest.write_str(" via ")?;
860                control2.to_css(dest)
861            },
862            SmoothQuad { by_to, ref point } => {
863                dest.write_str("smooth ")?;
864                by_to.to_css(dest)?;
865                dest.write_char(' ')?;
866                point.to_css(dest)
867            },
868            Arc {
869                by_to,
870                ref point,
871                ref radii,
872                arc_sweep,
873                arc_size,
874                ref rotate,
875            } => {
876                dest.write_str("arc ")?;
877                by_to.to_css(dest)?;
878                dest.write_char(' ')?;
879                point.to_css(dest)?;
880                dest.write_str(" of ")?;
881                radii.x.to_css(dest)?;
882                if radii.x != radii.y {
883                    dest.write_char(' ')?;
884                    radii.y.to_css(dest)?;
885                }
886
887                if matches!(arc_sweep, ArcSweep::Cw) {
888                    dest.write_str(" cw")?;
889                }
890
891                if matches!(arc_size, ArcSize::Large) {
892                    dest.write_str(" large")?;
893                }
894
895                if !rotate.is_zero() {
896                    dest.write_str(" rotate ")?;
897                    rotate.to_css(dest)?;
898                }
899                Ok(())
900            },
901            Close => dest.write_str("close"),
902        }
903    }
904}
905
906/// This indicates the command is absolute or relative.
907/// https://drafts.csswg.org/css-shapes-2/#typedef-shape-by-to
908#[derive(
909    Animate,
910    Clone,
911    ComputeSquaredDistance,
912    Copy,
913    Debug,
914    Deserialize,
915    MallocSizeOf,
916    Parse,
917    PartialEq,
918    Serialize,
919    SpecifiedValueInfo,
920    ToAnimatedValue,
921    ToAnimatedZero,
922    ToComputedValue,
923    ToCss,
924    ToResolvedValue,
925    ToShmem,
926)]
927#[repr(u8)]
928pub enum ByTo {
929    /// This indicates that the <coordinate-pair>s are relative to the command’s starting point.
930    By,
931    /// This relative to the top-left corner of the reference box.
932    To,
933}
934
935impl ByTo {
936    /// Return true if it is absolute, i.e. it is To.
937    #[inline]
938    pub fn is_abs(&self) -> bool {
939        matches!(self, ByTo::To)
940    }
941
942    /// Create ByTo based on the flag if it is absolute.
943    #[inline]
944    pub fn new(is_abs: bool) -> Self {
945        if is_abs {
946            Self::To
947        } else {
948            Self::By
949        }
950    }
951}
952
953/// Defines a pair of coordinates, representing a rightward and downward offset, respectively, from
954/// a specified reference point. Percentages are resolved against the width or height,
955/// respectively, of the reference box.
956/// https://drafts.csswg.org/css-shapes-2/#typedef-shape-coordinate-pair
957#[allow(missing_docs)]
958#[derive(
959    AddAssign,
960    Animate,
961    Clone,
962    ComputeSquaredDistance,
963    Copy,
964    Debug,
965    Deserialize,
966    MallocSizeOf,
967    PartialEq,
968    Serialize,
969    SpecifiedValueInfo,
970    ToAnimatedValue,
971    ToAnimatedZero,
972    ToComputedValue,
973    ToCss,
974    ToResolvedValue,
975    ToShmem,
976)]
977#[repr(C)]
978pub struct CoordinatePair<LengthPercentage> {
979    pub x: LengthPercentage,
980    pub y: LengthPercentage,
981}
982
983impl<LengthPercentage> CoordinatePair<LengthPercentage> {
984    /// Create a CoordinatePair.
985    #[inline]
986    pub fn new(x: LengthPercentage, y: LengthPercentage) -> Self {
987        Self { x, y }
988    }
989}
990
991/// This indicates that the arc that is traced around the ellipse clockwise or counter-clockwise
992/// from the center.
993/// https://drafts.csswg.org/css-shapes-2/#typedef-shape-arc-sweep
994#[derive(
995    Clone,
996    Copy,
997    Debug,
998    Deserialize,
999    FromPrimitive,
1000    MallocSizeOf,
1001    Parse,
1002    PartialEq,
1003    Serialize,
1004    SpecifiedValueInfo,
1005    ToAnimatedValue,
1006    ToAnimatedZero,
1007    ToComputedValue,
1008    ToCss,
1009    ToResolvedValue,
1010    ToShmem,
1011)]
1012#[repr(u8)]
1013pub enum ArcSweep {
1014    /// Counter-clockwise. The default value. (This also represents 0 in the svg path.)
1015    Ccw = 0,
1016    /// Clockwise. (This also represents 1 in the svg path.)
1017    Cw = 1,
1018}
1019
1020impl Animate for ArcSweep {
1021    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
1022        use num_traits::FromPrimitive;
1023        // If an arc command has different <arc-sweep> between its starting and ending list, then
1024        // the interpolated result uses cw for any progress value between 0 and 1.
1025        (*self as i32)
1026            .animate(&(*other as i32), procedure)
1027            .map(|v| ArcSweep::from_u8((v > 0) as u8).unwrap_or(ArcSweep::Ccw))
1028    }
1029}
1030
1031impl ComputeSquaredDistance for ArcSweep {
1032    fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
1033        (*self as i32).compute_squared_distance(&(*other as i32))
1034    }
1035}
1036
1037/// This indicates that the larger or smaller, respectively, of the two possible arcs must be
1038/// chosen.
1039/// https://drafts.csswg.org/css-shapes-2/#typedef-shape-arc-size
1040#[derive(
1041    Clone,
1042    Copy,
1043    Debug,
1044    Deserialize,
1045    FromPrimitive,
1046    MallocSizeOf,
1047    Parse,
1048    PartialEq,
1049    Serialize,
1050    SpecifiedValueInfo,
1051    ToAnimatedValue,
1052    ToAnimatedZero,
1053    ToComputedValue,
1054    ToCss,
1055    ToResolvedValue,
1056    ToShmem,
1057)]
1058#[repr(u8)]
1059pub enum ArcSize {
1060    /// Choose the small one. The default value. (This also represents 0 in the svg path.)
1061    Small = 0,
1062    /// Choose the large one. (This also represents 1 in the svg path.)
1063    Large = 1,
1064}
1065
1066impl Animate for ArcSize {
1067    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
1068        use num_traits::FromPrimitive;
1069        // If it has different <arc-size> keywords, then the interpolated result uses large for any
1070        // progress value between 0 and 1.
1071        (*self as i32)
1072            .animate(&(*other as i32), procedure)
1073            .map(|v| ArcSize::from_u8((v > 0) as u8).unwrap_or(ArcSize::Small))
1074    }
1075}
1076
1077impl ComputeSquaredDistance for ArcSize {
1078    fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
1079        (*self as i32).compute_squared_distance(&(*other as i32))
1080    }
1081}