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