1use crate::parser::{Parse, ParserContext};
11use crate::values::computed::basic_shape::InsetRect as ComputedInsetRect;
12use crate::values::computed::{Context, ToComputedValue};
13use crate::values::generics::basic_shape as generic;
14use crate::values::generics::basic_shape::{Path, PolygonCoord};
15use crate::values::generics::position::{GenericPosition, GenericPositionOrAuto};
16use crate::values::generics::rect::Rect;
17use crate::values::specified::angle::Angle;
18use crate::values::specified::border::BorderRadius;
19use crate::values::specified::image::Image;
20use crate::values::specified::length::LengthPercentageOrAuto;
21use crate::values::specified::url::SpecifiedUrl;
22use crate::values::specified::{LengthPercentage, NonNegativeLengthPercentage, SVGPathData};
23use crate::Zero;
24use cssparser::Parser;
25use std::fmt::{self, Write};
26use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
27
28pub use crate::values::generics::basic_shape::FillRule;
30
31pub type ClipPath = generic::GenericClipPath<BasicShape, SpecifiedUrl>;
33
34pub type ShapeOutside = generic::GenericShapeOutside<BasicShape, Image>;
36
37pub type ShapePosition = GenericPosition<LengthPercentage, LengthPercentage>;
41
42pub type BasicShape = generic::GenericBasicShape<
44 Angle,
45 ShapePosition,
46 LengthPercentage,
47 NonNegativeLengthPercentage,
48 BasicShapeRect,
49>;
50
51pub type InsetRect = generic::GenericInsetRect<LengthPercentage, NonNegativeLengthPercentage>;
53
54pub type Circle = generic::Circle<ShapePosition, NonNegativeLengthPercentage>;
56
57pub type Ellipse = generic::Ellipse<ShapePosition, NonNegativeLengthPercentage>;
59
60pub type ShapeRadius = generic::ShapeRadius<NonNegativeLengthPercentage>;
62
63pub type Polygon = generic::GenericPolygon<LengthPercentage>;
65
66pub type PathOrShapeFunction = generic::GenericPathOrShapeFunction<Angle, LengthPercentage>;
68
69pub type ShapeCommand = generic::GenericShapeCommand<Angle, LengthPercentage>;
71
72#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToShmem)]
82pub struct Xywh {
83 pub x: LengthPercentage,
85 pub y: LengthPercentage,
87 pub width: NonNegativeLengthPercentage,
89 pub height: NonNegativeLengthPercentage,
91 pub round: BorderRadius,
94}
95
96#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToShmem)]
100#[repr(C)]
101pub struct ShapeRectFunction {
102 pub rect: Rect<LengthPercentageOrAuto>,
111 pub round: BorderRadius,
114}
115
116#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
121pub enum BasicShapeRect {
122 Inset(InsetRect),
124 #[css(function)]
126 Xywh(Xywh),
127 #[css(function)]
129 Rect(ShapeRectFunction),
130}
131
132pub enum ShapeType {
139 Filled,
141 Outline,
143}
144
145bitflags! {
146 #[derive(Clone, Copy)]
163 #[repr(C)]
164 pub struct AllowedBasicShapes: u8 {
165 const INSET = 1 << 0;
167 const XYWH = 1 << 1;
169 const RECT = 1 << 2;
171 const CIRCLE = 1 << 3;
173 const ELLIPSE = 1 << 4;
175 const POLYGON = 1 << 5;
177 const PATH = 1 << 6;
179 const SHAPE = 1 << 7;
181
182 const ALL =
184 Self::INSET.bits() |
185 Self::XYWH.bits() |
186 Self::RECT.bits() |
187 Self::CIRCLE.bits() |
188 Self::ELLIPSE.bits() |
189 Self::POLYGON.bits() |
190 Self::PATH.bits() |
191 Self::SHAPE.bits();
192
193 const SHAPE_OUTSIDE =
195 Self::INSET.bits() |
196 Self::CIRCLE.bits() |
197 Self::ELLIPSE.bits() |
198 Self::POLYGON.bits();
199 }
200}
201
202fn parse_shape_or_box<'i, 't, R, ReferenceBox>(
204 context: &ParserContext,
205 input: &mut Parser<'i, 't>,
206 to_shape: impl FnOnce(Box<BasicShape>, ReferenceBox) -> R,
207 to_reference_box: impl FnOnce(ReferenceBox) -> R,
208 flags: AllowedBasicShapes,
209) -> Result<R, ParseError<'i>>
210where
211 ReferenceBox: Default + Parse,
212{
213 let mut shape = None;
214 let mut ref_box = None;
215 loop {
216 if shape.is_none() {
217 shape = input
218 .try_parse(|i| BasicShape::parse(context, i, flags, ShapeType::Filled))
219 .ok();
220 }
221
222 if ref_box.is_none() {
223 ref_box = input.try_parse(|i| ReferenceBox::parse(context, i)).ok();
224 if ref_box.is_some() {
225 continue;
226 }
227 }
228 break;
229 }
230
231 if let Some(shp) = shape {
232 return Ok(to_shape(Box::new(shp), ref_box.unwrap_or_default()));
233 }
234
235 match ref_box {
236 Some(r) => Ok(to_reference_box(r)),
237 None => Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
238 }
239}
240
241impl Parse for ClipPath {
242 #[inline]
243 fn parse<'i, 't>(
244 context: &ParserContext,
245 input: &mut Parser<'i, 't>,
246 ) -> Result<Self, ParseError<'i>> {
247 if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
248 return Ok(ClipPath::None);
249 }
250
251 if let Ok(url) = input.try_parse(|i| SpecifiedUrl::parse(context, i)) {
252 return Ok(ClipPath::Url(url));
253 }
254
255 parse_shape_or_box(
256 context,
257 input,
258 ClipPath::Shape,
259 ClipPath::Box,
260 AllowedBasicShapes::ALL,
261 )
262 }
263}
264
265impl Parse for ShapeOutside {
266 #[inline]
267 fn parse<'i, 't>(
268 context: &ParserContext,
269 input: &mut Parser<'i, 't>,
270 ) -> Result<Self, ParseError<'i>> {
271 if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
274 return Ok(ShapeOutside::None);
275 }
276
277 if let Ok(image) = input.try_parse(|i| Image::parse_with_cors_anonymous(context, i)) {
278 debug_assert_ne!(image, Image::None);
279 return Ok(ShapeOutside::Image(image));
280 }
281
282 parse_shape_or_box(
283 context,
284 input,
285 ShapeOutside::Shape,
286 ShapeOutside::Box,
287 AllowedBasicShapes::SHAPE_OUTSIDE,
288 )
289 }
290}
291
292impl BasicShape {
293 pub fn parse<'i, 't>(
298 context: &ParserContext,
299 input: &mut Parser<'i, 't>,
300 flags: AllowedBasicShapes,
301 shape_type: ShapeType,
302 ) -> Result<Self, ParseError<'i>> {
303 let location = input.current_source_location();
304 let function = input.expect_function()?.clone();
305 input.parse_nested_block(move |i| {
306 match_ignore_ascii_case! { &function,
307 "inset" if flags.contains(AllowedBasicShapes::INSET) => {
308 InsetRect::parse_function_arguments(context, i)
309 .map(BasicShapeRect::Inset)
310 .map(BasicShape::Rect)
311 },
312 "xywh" if flags.contains(AllowedBasicShapes::XYWH) => {
313 Xywh::parse_function_arguments(context, i)
314 .map(BasicShapeRect::Xywh)
315 .map(BasicShape::Rect)
316 },
317 "rect" if flags.contains(AllowedBasicShapes::RECT) => {
318 ShapeRectFunction::parse_function_arguments(context, i)
319 .map(BasicShapeRect::Rect)
320 .map(BasicShape::Rect)
321 },
322 "circle" if flags.contains(AllowedBasicShapes::CIRCLE) => {
323 Circle::parse_function_arguments(context, i)
324 .map(BasicShape::Circle)
325 },
326 "ellipse" if flags.contains(AllowedBasicShapes::ELLIPSE) => {
327 Ellipse::parse_function_arguments(context, i)
328 .map(BasicShape::Ellipse)
329 },
330 "polygon" if flags.contains(AllowedBasicShapes::POLYGON) => {
331 Polygon::parse_function_arguments(context, i, shape_type)
332 .map(BasicShape::Polygon)
333 },
334 "path" if flags.contains(AllowedBasicShapes::PATH) => {
335 Path::parse_function_arguments(i, shape_type)
336 .map(PathOrShapeFunction::Path)
337 .map(BasicShape::PathOrShape)
338 },
339 "shape"
340 if flags.contains(AllowedBasicShapes::SHAPE)
341 && static_prefs::pref!("layout.css.basic-shape-shape.enabled") =>
342 {
343 generic::Shape::parse_function_arguments(context, i, shape_type)
344 .map(PathOrShapeFunction::Shape)
345 .map(BasicShape::PathOrShape)
346 },
347 _ => Err(location
348 .new_custom_error(StyleParseErrorKind::UnexpectedFunction(function.clone()))),
349 }
350 })
351 }
352}
353
354impl Parse for InsetRect {
355 fn parse<'i, 't>(
356 context: &ParserContext,
357 input: &mut Parser<'i, 't>,
358 ) -> Result<Self, ParseError<'i>> {
359 input.expect_function_matching("inset")?;
360 input.parse_nested_block(|i| Self::parse_function_arguments(context, i))
361 }
362}
363
364fn parse_round<'i, 't>(
365 context: &ParserContext,
366 input: &mut Parser<'i, 't>,
367) -> Result<BorderRadius, ParseError<'i>> {
368 if input
369 .try_parse(|i| i.expect_ident_matching("round"))
370 .is_ok()
371 {
372 return BorderRadius::parse(context, input);
373 }
374
375 Ok(BorderRadius::zero())
376}
377
378impl InsetRect {
379 fn parse_function_arguments<'i, 't>(
381 context: &ParserContext,
382 input: &mut Parser<'i, 't>,
383 ) -> Result<Self, ParseError<'i>> {
384 let rect = Rect::parse_with(context, input, LengthPercentage::parse)?;
385 let round = parse_round(context, input)?;
386 Ok(generic::InsetRect { rect, round })
387 }
388}
389
390impl ToCss for ShapePosition {
391 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
392 where
393 W: Write,
394 {
395 self.horizontal.to_css(dest)?;
396 dest.write_char(' ')?;
397 self.vertical.to_css(dest)
398 }
399}
400
401fn parse_at_position<'i, 't>(
402 context: &ParserContext,
403 input: &mut Parser<'i, 't>,
404) -> Result<GenericPositionOrAuto<ShapePosition>, ParseError<'i>> {
405 use crate::values::specified::position::{Position, Side};
406 use crate::values::specified::{AllowedNumericType, Percentage, PositionComponent};
407
408 fn convert_to_length_percentage<S: Side>(c: PositionComponent<S>) -> LengthPercentage {
409 match c {
413 PositionComponent::Center => LengthPercentage::from(Percentage::new(0.5)),
416 PositionComponent::Side(keyword, None) => {
417 Percentage::new(if keyword.is_start() { 0. } else { 1. }).into()
418 },
419 PositionComponent::Side(keyword, Some(length)) => {
426 if keyword.is_start() {
427 length
428 } else {
429 length.hundred_percent_minus(AllowedNumericType::All)
430 }
431 },
432 PositionComponent::Length(length) => length,
433 }
434 }
435
436 if input.try_parse(|i| i.expect_ident_matching("at")).is_ok() {
437 Position::parse(context, input).map(|pos| {
438 GenericPositionOrAuto::Position(ShapePosition::new(
439 convert_to_length_percentage(pos.horizontal),
440 convert_to_length_percentage(pos.vertical),
441 ))
442 })
443 } else {
444 Ok(GenericPositionOrAuto::Auto)
446 }
447}
448
449impl Parse for Circle {
450 fn parse<'i, 't>(
451 context: &ParserContext,
452 input: &mut Parser<'i, 't>,
453 ) -> Result<Self, ParseError<'i>> {
454 input.expect_function_matching("circle")?;
455 input.parse_nested_block(|i| Self::parse_function_arguments(context, i))
456 }
457}
458
459impl Circle {
460 fn parse_function_arguments<'i, 't>(
461 context: &ParserContext,
462 input: &mut Parser<'i, 't>,
463 ) -> Result<Self, ParseError<'i>> {
464 let radius = input
465 .try_parse(|i| ShapeRadius::parse(context, i))
466 .unwrap_or_default();
467 let position = parse_at_position(context, input)?;
468
469 Ok(generic::Circle { radius, position })
470 }
471}
472
473impl Parse for Ellipse {
474 fn parse<'i, 't>(
475 context: &ParserContext,
476 input: &mut Parser<'i, 't>,
477 ) -> Result<Self, ParseError<'i>> {
478 input.expect_function_matching("ellipse")?;
479 input.parse_nested_block(|i| Self::parse_function_arguments(context, i))
480 }
481}
482
483impl Ellipse {
484 fn parse_function_arguments<'i, 't>(
485 context: &ParserContext,
486 input: &mut Parser<'i, 't>,
487 ) -> Result<Self, ParseError<'i>> {
488 let (semiaxis_x, semiaxis_y) = input
489 .try_parse(|i| -> Result<_, ParseError> {
490 Ok((
491 ShapeRadius::parse(context, i)?,
492 ShapeRadius::parse(context, i)?,
493 ))
494 })
495 .unwrap_or_default();
496 let position = parse_at_position(context, input)?;
497
498 Ok(generic::Ellipse {
499 semiaxis_x,
500 semiaxis_y,
501 position,
502 })
503 }
504}
505
506fn parse_fill_rule<'i, 't>(
507 input: &mut Parser<'i, 't>,
508 shape_type: ShapeType,
509 expect_comma: bool,
510) -> FillRule {
511 match shape_type {
512 ShapeType::Outline => Default::default(),
525 ShapeType::Filled => input
526 .try_parse(|i| -> Result<_, ParseError> {
527 let fill = FillRule::parse(i)?;
528 if expect_comma {
529 i.expect_comma()?;
530 }
531 Ok(fill)
532 })
533 .unwrap_or_default(),
534 }
535}
536
537impl Parse for Polygon {
538 fn parse<'i, 't>(
539 context: &ParserContext,
540 input: &mut Parser<'i, 't>,
541 ) -> Result<Self, ParseError<'i>> {
542 input.expect_function_matching("polygon")?;
543 input.parse_nested_block(|i| Self::parse_function_arguments(context, i, ShapeType::Filled))
544 }
545}
546
547impl Polygon {
548 fn parse_function_arguments<'i, 't>(
550 context: &ParserContext,
551 input: &mut Parser<'i, 't>,
552 shape_type: ShapeType,
553 ) -> Result<Self, ParseError<'i>> {
554 let fill = parse_fill_rule(input, shape_type, true );
555 let coordinates = input
556 .parse_comma_separated(|i| {
557 Ok(PolygonCoord(
558 LengthPercentage::parse(context, i)?,
559 LengthPercentage::parse(context, i)?,
560 ))
561 })?
562 .into();
563
564 Ok(Polygon { fill, coordinates })
565 }
566}
567
568impl Path {
569 fn parse_function_arguments<'i, 't>(
571 input: &mut Parser<'i, 't>,
572 shape_type: ShapeType,
573 ) -> Result<Self, ParseError<'i>> {
574 use crate::values::specified::svg_path::AllowEmpty;
575
576 let fill = parse_fill_rule(input, shape_type, true );
577 let path = SVGPathData::parse(input, AllowEmpty::No)?;
578 Ok(Path { fill, path })
579 }
580}
581
582fn round_to_css<W>(round: &BorderRadius, dest: &mut CssWriter<W>) -> fmt::Result
583where
584 W: Write,
585{
586 if !round.is_zero() {
587 dest.write_str(" round ")?;
588 round.to_css(dest)?;
589 }
590 Ok(())
591}
592
593impl ToCss for Xywh {
594 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
595 where
596 W: Write,
597 {
598 self.x.to_css(dest)?;
599 dest.write_char(' ')?;
600 self.y.to_css(dest)?;
601 dest.write_char(' ')?;
602 self.width.to_css(dest)?;
603 dest.write_char(' ')?;
604 self.height.to_css(dest)?;
605 round_to_css(&self.round, dest)
606 }
607}
608
609impl Xywh {
610 fn parse_function_arguments<'i, 't>(
612 context: &ParserContext,
613 input: &mut Parser<'i, 't>,
614 ) -> Result<Self, ParseError<'i>> {
615 let x = LengthPercentage::parse(context, input)?;
616 let y = LengthPercentage::parse(context, input)?;
617 let width = NonNegativeLengthPercentage::parse(context, input)?;
618 let height = NonNegativeLengthPercentage::parse(context, input)?;
619 let round = parse_round(context, input)?;
620 Ok(Xywh {
621 x,
622 y,
623 width,
624 height,
625 round,
626 })
627 }
628}
629
630impl ToCss for ShapeRectFunction {
631 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
632 where
633 W: Write,
634 {
635 self.rect.0.to_css(dest)?;
636 dest.write_char(' ')?;
637 self.rect.1.to_css(dest)?;
638 dest.write_char(' ')?;
639 self.rect.2.to_css(dest)?;
640 dest.write_char(' ')?;
641 self.rect.3.to_css(dest)?;
642 round_to_css(&self.round, dest)
643 }
644}
645
646impl ShapeRectFunction {
647 fn parse_function_arguments<'i, 't>(
649 context: &ParserContext,
650 input: &mut Parser<'i, 't>,
651 ) -> Result<Self, ParseError<'i>> {
652 let rect = Rect::parse_all_components_with(context, input, LengthPercentageOrAuto::parse)?;
653 let round = parse_round(context, input)?;
654 Ok(ShapeRectFunction { rect, round })
655 }
656}
657
658impl ToComputedValue for BasicShapeRect {
659 type ComputedValue = ComputedInsetRect;
660
661 #[inline]
662 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
663 use crate::values::computed::LengthPercentage;
664 use crate::values::computed::LengthPercentageOrAuto;
665 use style_traits::values::specified::AllowedNumericType;
666
667 match self {
668 Self::Inset(ref inset) => inset.to_computed_value(context),
669 Self::Xywh(ref xywh) => {
670 let x = xywh.x.to_computed_value(context);
676 let y = xywh.y.to_computed_value(context);
677 let w = xywh.width.to_computed_value(context);
678 let h = xywh.height.to_computed_value(context);
679 let right = LengthPercentage::hundred_percent_minus_list(
681 &[&x, &w.0],
682 AllowedNumericType::All,
683 );
684 let bottom = LengthPercentage::hundred_percent_minus_list(
686 &[&y, &h.0],
687 AllowedNumericType::All,
688 );
689
690 ComputedInsetRect {
691 rect: Rect::new(y, right, bottom, x),
692 round: xywh.round.to_computed_value(context),
693 }
694 },
695 Self::Rect(ref rect) => {
696 fn compute_top_or_left(v: LengthPercentageOrAuto) -> LengthPercentage {
701 match v {
702 LengthPercentageOrAuto::Auto => LengthPercentage::zero_percent(),
705 LengthPercentageOrAuto::LengthPercentage(lp) => lp,
706 }
707 }
708 fn compute_bottom_or_right(v: LengthPercentageOrAuto) -> LengthPercentage {
709 match v {
710 LengthPercentageOrAuto::Auto => LengthPercentage::zero_percent(),
714 LengthPercentageOrAuto::LengthPercentage(lp) => {
715 LengthPercentage::hundred_percent_minus(lp, AllowedNumericType::All)
716 },
717 }
718 }
719
720 let round = rect.round.to_computed_value(context);
721 let rect = rect.rect.to_computed_value(context);
722 let rect = Rect::new(
723 compute_top_or_left(rect.0),
724 compute_bottom_or_right(rect.1),
725 compute_bottom_or_right(rect.2),
726 compute_top_or_left(rect.3),
727 );
728
729 ComputedInsetRect { rect, round }
730 },
731 }
732 }
733
734 #[inline]
735 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
736 Self::Inset(ToComputedValue::from_computed_value(computed))
737 }
738}
739
740impl generic::Shape<Angle, LengthPercentage> {
741 fn parse_function_arguments<'i, 't>(
744 context: &ParserContext,
745 input: &mut Parser<'i, 't>,
746 shape_type: ShapeType,
747 ) -> Result<Self, ParseError<'i>> {
748 let fill = parse_fill_rule(input, shape_type, false );
749
750 let mut first = true;
751 let commands = input.parse_comma_separated(|i| {
752 if first {
753 first = false;
754
755 i.expect_ident_matching("from")?;
759 Ok(ShapeCommand::Move {
760 by_to: generic::ByTo::To,
761 point: generic::CoordinatePair::parse(context, i)?,
762 })
763 } else {
764 ShapeCommand::parse(context, i)
766 }
767 })?;
768
769 if commands.len() < 2 {
771 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
772 }
773
774 Ok(Self {
775 fill,
776 commands: commands.into(),
777 })
778 }
779}
780
781impl Parse for ShapeCommand {
782 fn parse<'i, 't>(
783 context: &ParserContext,
784 input: &mut Parser<'i, 't>,
785 ) -> Result<Self, ParseError<'i>> {
786 use crate::values::generics::basic_shape::{ArcSize, ArcSweep, ByTo, CoordinatePair};
787
788 Ok(try_match_ident_ignore_ascii_case! { input,
791 "close" => Self::Close,
792 "move" => {
793 let by_to = ByTo::parse(input)?;
794 let point = CoordinatePair::parse(context, input)?;
795 Self::Move { by_to, point }
796 },
797 "line" => {
798 let by_to = ByTo::parse(input)?;
799 let point = CoordinatePair::parse(context, input)?;
800 Self::Line { by_to, point }
801 },
802 "hline" => {
803 let by_to = ByTo::parse(input)?;
804 let x = LengthPercentage::parse(context, input)?;
805 Self::HLine { by_to, x }
806 },
807 "vline" => {
808 let by_to = ByTo::parse(input)?;
809 let y = LengthPercentage::parse(context, input)?;
810 Self::VLine { by_to, y }
811 },
812 "curve" => {
813 let by_to = ByTo::parse(input)?;
814 let point = CoordinatePair::parse(context, input)?;
815 input.expect_ident_matching("via")?;
816 let control1 = CoordinatePair::parse(context, input)?;
817 match input.try_parse(|i| CoordinatePair::parse(context, i)) {
818 Ok(control2) => Self::CubicCurve {
819 by_to,
820 point,
821 control1,
822 control2,
823 },
824 Err(_) => Self::QuadCurve {
825 by_to,
826 point,
827 control1,
828 },
829 }
830 },
831 "smooth" => {
832 let by_to = ByTo::parse(input)?;
833 let point = CoordinatePair::parse(context, input)?;
834 if input.try_parse(|i| i.expect_ident_matching("via")).is_ok() {
835 let control2 = CoordinatePair::parse(context, input)?;
836 Self::SmoothCubic {
837 by_to,
838 point,
839 control2,
840 }
841 } else {
842 Self::SmoothQuad { by_to, point }
843 }
844 },
845 "arc" => {
846 let by_to = ByTo::parse(input)?;
847 let point = CoordinatePair::parse(context, input)?;
848 input.expect_ident_matching("of")?;
849 let rx = LengthPercentage::parse(context, input)?;
850 let ry = input
851 .try_parse(|i| LengthPercentage::parse(context, i))
852 .unwrap_or(rx.clone());
853 let radii = CoordinatePair::new(rx, ry);
854
855 let mut arc_sweep = None;
857 let mut arc_size = None;
858 let mut rotate = None;
859 loop {
860 if arc_sweep.is_none() {
861 arc_sweep = input.try_parse(ArcSweep::parse).ok();
862 }
863
864 if arc_size.is_none() {
865 arc_size = input.try_parse(ArcSize::parse).ok();
866 if arc_size.is_some() {
867 continue;
868 }
869 }
870
871 if rotate.is_none()
872 && input
873 .try_parse(|i| i.expect_ident_matching("rotate"))
874 .is_ok()
875 {
876 rotate = Some(Angle::parse(context, input)?);
877 continue;
878 }
879 break;
880 }
881 Self::Arc {
882 by_to,
883 point,
884 radii,
885 arc_sweep: arc_sweep.unwrap_or(ArcSweep::Ccw),
886 arc_size: arc_size.unwrap_or(ArcSize::Small),
887 rotate: rotate.unwrap_or(Angle::zero()),
888 }
889 },
890 })
891 }
892}
893
894impl Parse for generic::CoordinatePair<LengthPercentage> {
895 fn parse<'i, 't>(
896 context: &ParserContext,
897 input: &mut Parser<'i, 't>,
898 ) -> Result<Self, ParseError<'i>> {
899 let x = LengthPercentage::parse(context, input)?;
900 let y = LengthPercentage::parse(context, input)?;
901 Ok(Self::new(x, y))
902 }
903}