Skip to main content

style/values/generics/
basic_shape.rs

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