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