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