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