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