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"
313 if flags.contains(AllowedBasicShapes::XYWH)
314 && static_prefs::pref!("layout.css.basic-shape-xywh.enabled") =>
315 {
316 Xywh::parse_function_arguments(context, i)
317 .map(BasicShapeRect::Xywh)
318 .map(BasicShape::Rect)
319 },
320 "rect"
321 if flags.contains(AllowedBasicShapes::RECT)
322 && static_prefs::pref!("layout.css.basic-shape-rect.enabled") =>
323 {
324 ShapeRectFunction::parse_function_arguments(context, i)
325 .map(BasicShapeRect::Rect)
326 .map(BasicShape::Rect)
327 },
328 "circle" if flags.contains(AllowedBasicShapes::CIRCLE) => {
329 Circle::parse_function_arguments(context, i)
330 .map(BasicShape::Circle)
331 },
332 "ellipse" if flags.contains(AllowedBasicShapes::ELLIPSE) => {
333 Ellipse::parse_function_arguments(context, i)
334 .map(BasicShape::Ellipse)
335 },
336 "polygon" if flags.contains(AllowedBasicShapes::POLYGON) => {
337 Polygon::parse_function_arguments(context, i, shape_type)
338 .map(BasicShape::Polygon)
339 },
340 "path" if flags.contains(AllowedBasicShapes::PATH) => {
341 Path::parse_function_arguments(i, shape_type)
342 .map(PathOrShapeFunction::Path)
343 .map(BasicShape::PathOrShape)
344 },
345 "shape"
346 if flags.contains(AllowedBasicShapes::SHAPE)
347 && static_prefs::pref!("layout.css.basic-shape-shape.enabled") =>
348 {
349 generic::Shape::parse_function_arguments(context, i, shape_type)
350 .map(PathOrShapeFunction::Shape)
351 .map(BasicShape::PathOrShape)
352 },
353 _ => Err(location
354 .new_custom_error(StyleParseErrorKind::UnexpectedFunction(function.clone()))),
355 }
356 })
357 }
358}
359
360impl Parse for InsetRect {
361 fn parse<'i, 't>(
362 context: &ParserContext,
363 input: &mut Parser<'i, 't>,
364 ) -> Result<Self, ParseError<'i>> {
365 input.expect_function_matching("inset")?;
366 input.parse_nested_block(|i| Self::parse_function_arguments(context, i))
367 }
368}
369
370fn parse_round<'i, 't>(
371 context: &ParserContext,
372 input: &mut Parser<'i, 't>,
373) -> Result<BorderRadius, ParseError<'i>> {
374 if input
375 .try_parse(|i| i.expect_ident_matching("round"))
376 .is_ok()
377 {
378 return BorderRadius::parse(context, input);
379 }
380
381 Ok(BorderRadius::zero())
382}
383
384impl InsetRect {
385 fn parse_function_arguments<'i, 't>(
387 context: &ParserContext,
388 input: &mut Parser<'i, 't>,
389 ) -> Result<Self, ParseError<'i>> {
390 let rect = Rect::parse_with(context, input, LengthPercentage::parse)?;
391 let round = parse_round(context, input)?;
392 Ok(generic::InsetRect { rect, round })
393 }
394}
395
396impl ToCss for ShapePosition {
397 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
398 where
399 W: Write,
400 {
401 self.horizontal.to_css(dest)?;
402 dest.write_char(' ')?;
403 self.vertical.to_css(dest)
404 }
405}
406
407fn parse_at_position<'i, 't>(
408 context: &ParserContext,
409 input: &mut Parser<'i, 't>,
410) -> Result<GenericPositionOrAuto<ShapePosition>, ParseError<'i>> {
411 use crate::values::specified::position::{Position, Side};
412 use crate::values::specified::{AllowedNumericType, Percentage, PositionComponent};
413
414 fn convert_to_length_percentage<S: Side>(c: PositionComponent<S>) -> LengthPercentage {
415 match c {
419 PositionComponent::Center => LengthPercentage::from(Percentage::new(0.5)),
422 PositionComponent::Side(keyword, None) => {
423 Percentage::new(if keyword.is_start() { 0. } else { 1. }).into()
424 },
425 PositionComponent::Side(keyword, Some(length)) => {
432 if keyword.is_start() {
433 length
434 } else {
435 length.hundred_percent_minus(AllowedNumericType::All)
436 }
437 },
438 PositionComponent::Length(length) => length,
439 }
440 }
441
442 if input.try_parse(|i| i.expect_ident_matching("at")).is_ok() {
443 Position::parse(context, input).map(|pos| {
444 GenericPositionOrAuto::Position(ShapePosition::new(
445 convert_to_length_percentage(pos.horizontal),
446 convert_to_length_percentage(pos.vertical),
447 ))
448 })
449 } else {
450 Ok(GenericPositionOrAuto::Auto)
452 }
453}
454
455impl Parse for Circle {
456 fn parse<'i, 't>(
457 context: &ParserContext,
458 input: &mut Parser<'i, 't>,
459 ) -> Result<Self, ParseError<'i>> {
460 input.expect_function_matching("circle")?;
461 input.parse_nested_block(|i| Self::parse_function_arguments(context, i))
462 }
463}
464
465impl Circle {
466 fn parse_function_arguments<'i, 't>(
467 context: &ParserContext,
468 input: &mut Parser<'i, 't>,
469 ) -> Result<Self, ParseError<'i>> {
470 let radius = input
471 .try_parse(|i| ShapeRadius::parse(context, i))
472 .unwrap_or_default();
473 let position = parse_at_position(context, input)?;
474
475 Ok(generic::Circle { radius, position })
476 }
477}
478
479impl Parse for Ellipse {
480 fn parse<'i, 't>(
481 context: &ParserContext,
482 input: &mut Parser<'i, 't>,
483 ) -> Result<Self, ParseError<'i>> {
484 input.expect_function_matching("ellipse")?;
485 input.parse_nested_block(|i| Self::parse_function_arguments(context, i))
486 }
487}
488
489impl Ellipse {
490 fn parse_function_arguments<'i, 't>(
491 context: &ParserContext,
492 input: &mut Parser<'i, 't>,
493 ) -> Result<Self, ParseError<'i>> {
494 let (semiaxis_x, semiaxis_y) = input
495 .try_parse(|i| -> Result<_, ParseError> {
496 Ok((
497 ShapeRadius::parse(context, i)?,
498 ShapeRadius::parse(context, i)?,
499 ))
500 })
501 .unwrap_or_default();
502 let position = parse_at_position(context, input)?;
503
504 Ok(generic::Ellipse {
505 semiaxis_x,
506 semiaxis_y,
507 position,
508 })
509 }
510}
511
512fn parse_fill_rule<'i, 't>(
513 input: &mut Parser<'i, 't>,
514 shape_type: ShapeType,
515 expect_comma: bool,
516) -> FillRule {
517 match shape_type {
518 ShapeType::Outline => Default::default(),
531 ShapeType::Filled => input
532 .try_parse(|i| -> Result<_, ParseError> {
533 let fill = FillRule::parse(i)?;
534 if expect_comma {
535 i.expect_comma()?;
536 }
537 Ok(fill)
538 })
539 .unwrap_or_default(),
540 }
541}
542
543impl Parse for Polygon {
544 fn parse<'i, 't>(
545 context: &ParserContext,
546 input: &mut Parser<'i, 't>,
547 ) -> Result<Self, ParseError<'i>> {
548 input.expect_function_matching("polygon")?;
549 input.parse_nested_block(|i| Self::parse_function_arguments(context, i, ShapeType::Filled))
550 }
551}
552
553impl Polygon {
554 fn parse_function_arguments<'i, 't>(
556 context: &ParserContext,
557 input: &mut Parser<'i, 't>,
558 shape_type: ShapeType,
559 ) -> Result<Self, ParseError<'i>> {
560 let fill = parse_fill_rule(input, shape_type, true );
561 let coordinates = input
562 .parse_comma_separated(|i| {
563 Ok(PolygonCoord(
564 LengthPercentage::parse(context, i)?,
565 LengthPercentage::parse(context, i)?,
566 ))
567 })?
568 .into();
569
570 Ok(Polygon { fill, coordinates })
571 }
572}
573
574impl Path {
575 fn parse_function_arguments<'i, 't>(
577 input: &mut Parser<'i, 't>,
578 shape_type: ShapeType,
579 ) -> Result<Self, ParseError<'i>> {
580 use crate::values::specified::svg_path::AllowEmpty;
581
582 let fill = parse_fill_rule(input, shape_type, true );
583 let path = SVGPathData::parse(input, AllowEmpty::No)?;
584 Ok(Path { fill, path })
585 }
586}
587
588fn round_to_css<W>(round: &BorderRadius, dest: &mut CssWriter<W>) -> fmt::Result
589where
590 W: Write,
591{
592 if !round.is_zero() {
593 dest.write_str(" round ")?;
594 round.to_css(dest)?;
595 }
596 Ok(())
597}
598
599impl ToCss for Xywh {
600 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
601 where
602 W: Write,
603 {
604 self.x.to_css(dest)?;
605 dest.write_char(' ')?;
606 self.y.to_css(dest)?;
607 dest.write_char(' ')?;
608 self.width.to_css(dest)?;
609 dest.write_char(' ')?;
610 self.height.to_css(dest)?;
611 round_to_css(&self.round, dest)
612 }
613}
614
615impl Xywh {
616 fn parse_function_arguments<'i, 't>(
618 context: &ParserContext,
619 input: &mut Parser<'i, 't>,
620 ) -> Result<Self, ParseError<'i>> {
621 let x = LengthPercentage::parse(context, input)?;
622 let y = LengthPercentage::parse(context, input)?;
623 let width = NonNegativeLengthPercentage::parse(context, input)?;
624 let height = NonNegativeLengthPercentage::parse(context, input)?;
625 let round = parse_round(context, input)?;
626 Ok(Xywh {
627 x,
628 y,
629 width,
630 height,
631 round,
632 })
633 }
634}
635
636impl ToCss for ShapeRectFunction {
637 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
638 where
639 W: Write,
640 {
641 self.rect.0.to_css(dest)?;
642 dest.write_char(' ')?;
643 self.rect.1.to_css(dest)?;
644 dest.write_char(' ')?;
645 self.rect.2.to_css(dest)?;
646 dest.write_char(' ')?;
647 self.rect.3.to_css(dest)?;
648 round_to_css(&self.round, dest)
649 }
650}
651
652impl ShapeRectFunction {
653 fn parse_function_arguments<'i, 't>(
655 context: &ParserContext,
656 input: &mut Parser<'i, 't>,
657 ) -> Result<Self, ParseError<'i>> {
658 let rect = Rect::parse_all_components_with(context, input, LengthPercentageOrAuto::parse)?;
659 let round = parse_round(context, input)?;
660 Ok(ShapeRectFunction { rect, round })
661 }
662}
663
664impl ToComputedValue for BasicShapeRect {
665 type ComputedValue = ComputedInsetRect;
666
667 #[inline]
668 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
669 use crate::values::computed::LengthPercentage;
670 use crate::values::computed::LengthPercentageOrAuto;
671 use style_traits::values::specified::AllowedNumericType;
672
673 match self {
674 Self::Inset(ref inset) => inset.to_computed_value(context),
675 Self::Xywh(ref xywh) => {
676 let x = xywh.x.to_computed_value(context);
682 let y = xywh.y.to_computed_value(context);
683 let w = xywh.width.to_computed_value(context);
684 let h = xywh.height.to_computed_value(context);
685 let right = LengthPercentage::hundred_percent_minus_list(
687 &[&x, &w.0],
688 AllowedNumericType::All,
689 );
690 let bottom = LengthPercentage::hundred_percent_minus_list(
692 &[&y, &h.0],
693 AllowedNumericType::All,
694 );
695
696 ComputedInsetRect {
697 rect: Rect::new(y, right, bottom, x),
698 round: xywh.round.to_computed_value(context),
699 }
700 },
701 Self::Rect(ref rect) => {
702 fn compute_top_or_left(v: LengthPercentageOrAuto) -> LengthPercentage {
707 match v {
708 LengthPercentageOrAuto::Auto => LengthPercentage::zero_percent(),
711 LengthPercentageOrAuto::LengthPercentage(lp) => lp,
712 }
713 }
714 fn compute_bottom_or_right(v: LengthPercentageOrAuto) -> LengthPercentage {
715 match v {
716 LengthPercentageOrAuto::Auto => LengthPercentage::zero_percent(),
720 LengthPercentageOrAuto::LengthPercentage(lp) => {
721 LengthPercentage::hundred_percent_minus(lp, AllowedNumericType::All)
722 },
723 }
724 }
725
726 let round = rect.round.to_computed_value(context);
727 let rect = rect.rect.to_computed_value(context);
728 let rect = Rect::new(
729 compute_top_or_left(rect.0),
730 compute_bottom_or_right(rect.1),
731 compute_bottom_or_right(rect.2),
732 compute_top_or_left(rect.3),
733 );
734
735 ComputedInsetRect { rect, round }
736 },
737 }
738 }
739
740 #[inline]
741 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
742 Self::Inset(ToComputedValue::from_computed_value(computed))
743 }
744}
745
746impl generic::Shape<Angle, LengthPercentage> {
747 fn parse_function_arguments<'i, 't>(
750 context: &ParserContext,
751 input: &mut Parser<'i, 't>,
752 shape_type: ShapeType,
753 ) -> Result<Self, ParseError<'i>> {
754 let fill = parse_fill_rule(input, shape_type, false );
755
756 let mut first = true;
757 let commands = input.parse_comma_separated(|i| {
758 if first {
759 first = false;
760
761 i.expect_ident_matching("from")?;
765 Ok(ShapeCommand::Move {
766 by_to: generic::ByTo::To,
767 point: generic::CoordinatePair::parse(context, i)?,
768 })
769 } else {
770 ShapeCommand::parse(context, i)
772 }
773 })?;
774
775 if commands.len() < 2 {
777 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
778 }
779
780 Ok(Self {
781 fill,
782 commands: commands.into(),
783 })
784 }
785}
786
787impl Parse for ShapeCommand {
788 fn parse<'i, 't>(
789 context: &ParserContext,
790 input: &mut Parser<'i, 't>,
791 ) -> Result<Self, ParseError<'i>> {
792 use crate::values::generics::basic_shape::{ArcSize, ArcSweep, ByTo, CoordinatePair};
793
794 Ok(try_match_ident_ignore_ascii_case! { input,
797 "close" => Self::Close,
798 "move" => {
799 let by_to = ByTo::parse(input)?;
800 let point = CoordinatePair::parse(context, input)?;
801 Self::Move { by_to, point }
802 },
803 "line" => {
804 let by_to = ByTo::parse(input)?;
805 let point = CoordinatePair::parse(context, input)?;
806 Self::Line { by_to, point }
807 },
808 "hline" => {
809 let by_to = ByTo::parse(input)?;
810 let x = LengthPercentage::parse(context, input)?;
811 Self::HLine { by_to, x }
812 },
813 "vline" => {
814 let by_to = ByTo::parse(input)?;
815 let y = LengthPercentage::parse(context, input)?;
816 Self::VLine { by_to, y }
817 },
818 "curve" => {
819 let by_to = ByTo::parse(input)?;
820 let point = CoordinatePair::parse(context, input)?;
821 input.expect_ident_matching("via")?;
822 let control1 = CoordinatePair::parse(context, input)?;
823 match input.try_parse(|i| CoordinatePair::parse(context, i)) {
824 Ok(control2) => Self::CubicCurve {
825 by_to,
826 point,
827 control1,
828 control2,
829 },
830 Err(_) => Self::QuadCurve {
831 by_to,
832 point,
833 control1,
834 },
835 }
836 },
837 "smooth" => {
838 let by_to = ByTo::parse(input)?;
839 let point = CoordinatePair::parse(context, input)?;
840 if input.try_parse(|i| i.expect_ident_matching("via")).is_ok() {
841 let control2 = CoordinatePair::parse(context, input)?;
842 Self::SmoothCubic {
843 by_to,
844 point,
845 control2,
846 }
847 } else {
848 Self::SmoothQuad { by_to, point }
849 }
850 },
851 "arc" => {
852 let by_to = ByTo::parse(input)?;
853 let point = CoordinatePair::parse(context, input)?;
854 input.expect_ident_matching("of")?;
855 let rx = LengthPercentage::parse(context, input)?;
856 let ry = input
857 .try_parse(|i| LengthPercentage::parse(context, i))
858 .unwrap_or(rx.clone());
859 let radii = CoordinatePair::new(rx, ry);
860
861 let mut arc_sweep = None;
863 let mut arc_size = None;
864 let mut rotate = None;
865 loop {
866 if arc_sweep.is_none() {
867 arc_sweep = input.try_parse(ArcSweep::parse).ok();
868 }
869
870 if arc_size.is_none() {
871 arc_size = input.try_parse(ArcSize::parse).ok();
872 if arc_size.is_some() {
873 continue;
874 }
875 }
876
877 if rotate.is_none()
878 && input
879 .try_parse(|i| i.expect_ident_matching("rotate"))
880 .is_ok()
881 {
882 rotate = Some(Angle::parse(context, input)?);
883 continue;
884 }
885 break;
886 }
887 Self::Arc {
888 by_to,
889 point,
890 radii,
891 arc_sweep: arc_sweep.unwrap_or(ArcSweep::Ccw),
892 arc_size: arc_size.unwrap_or(ArcSize::Small),
893 rotate: rotate.unwrap_or(Angle::zero()),
894 }
895 },
896 })
897 }
898}
899
900impl Parse for generic::CoordinatePair<LengthPercentage> {
901 fn parse<'i, 't>(
902 context: &ParserContext,
903 input: &mut Parser<'i, 't>,
904 ) -> Result<Self, ParseError<'i>> {
905 let x = LengthPercentage::parse(context, input)?;
906 let y = LengthPercentage::parse(context, input)?;
907 Ok(Self::new(x, y))
908 }
909}