Skip to main content

style/values/generics/
basic_shape.rs

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