style/values/generics/
basic_shape.rs

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