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::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::{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 RadialPosition = generic::ShapePosition<LengthPercentage>;
43
44pub type BasicShape = generic::GenericBasicShape<Angle, Position, LengthPercentage, BasicShapeRect>;
46
47pub type InsetRect = generic::GenericInsetRect<LengthPercentage>;
49
50pub type Circle = generic::Circle<LengthPercentage>;
52
53pub type Ellipse = generic::Ellipse<LengthPercentage>;
55
56pub type ShapeRadius = generic::ShapeRadius<LengthPercentage>;
58
59pub type Polygon = generic::GenericPolygon<LengthPercentage>;
61
62pub type PathOrShapeFunction =
64 generic::GenericPathOrShapeFunction<Angle, Position, LengthPercentage>;
65
66pub type ShapeCommand = generic::GenericShapeCommand<Angle, Position, LengthPercentage>;
68
69#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToShmem)]
79pub struct Xywh {
80 pub x: LengthPercentage,
82 pub y: LengthPercentage,
84 pub width: NonNegativeLengthPercentage,
86 pub height: NonNegativeLengthPercentage,
88 pub round: BorderRadius,
91}
92
93#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToShmem)]
97#[repr(C)]
98pub struct ShapeRectFunction {
99 pub rect: Rect<LengthPercentageOrAuto>,
108 pub round: BorderRadius,
111}
112
113#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
118pub enum BasicShapeRect {
119 Inset(InsetRect),
121 #[css(function)]
123 Xywh(Xywh),
124 #[css(function)]
126 Rect(ShapeRectFunction),
127}
128
129pub enum ShapeType {
136 Filled,
138 Outline,
140}
141
142bitflags! {
143 #[derive(Clone, Copy)]
160 #[repr(C)]
161 pub struct AllowedBasicShapes: u8 {
162 const INSET = 1 << 0;
164 const XYWH = 1 << 1;
166 const RECT = 1 << 2;
168 const CIRCLE = 1 << 3;
170 const ELLIPSE = 1 << 4;
172 const POLYGON = 1 << 5;
174 const PATH = 1 << 6;
176 const SHAPE = 1 << 7;
178
179 const ALL =
181 Self::INSET.bits() |
182 Self::XYWH.bits() |
183 Self::RECT.bits() |
184 Self::CIRCLE.bits() |
185 Self::ELLIPSE.bits() |
186 Self::POLYGON.bits() |
187 Self::PATH.bits() |
188 Self::SHAPE.bits();
189
190 const SHAPE_OUTSIDE =
192 Self::INSET.bits() |
193 Self::CIRCLE.bits() |
194 Self::ELLIPSE.bits() |
195 Self::POLYGON.bits();
196 }
197}
198
199fn parse_shape_or_box<'i, 't, R, ReferenceBox>(
201 context: &ParserContext,
202 input: &mut Parser<'i, 't>,
203 to_shape: impl FnOnce(Box<BasicShape>, ReferenceBox) -> R,
204 to_reference_box: impl FnOnce(ReferenceBox) -> R,
205 flags: AllowedBasicShapes,
206) -> Result<R, ParseError<'i>>
207where
208 ReferenceBox: Default + Parse,
209{
210 let mut shape = None;
211 let mut ref_box = None;
212 loop {
213 if shape.is_none() {
214 shape = input
215 .try_parse(|i| BasicShape::parse(context, i, flags, ShapeType::Filled))
216 .ok();
217 }
218
219 if ref_box.is_none() {
220 ref_box = input.try_parse(|i| ReferenceBox::parse(context, i)).ok();
221 if ref_box.is_some() {
222 continue;
223 }
224 }
225 break;
226 }
227
228 if let Some(shp) = shape {
229 return Ok(to_shape(Box::new(shp), ref_box.unwrap_or_default()));
230 }
231
232 match ref_box {
233 Some(r) => Ok(to_reference_box(r)),
234 None => Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
235 }
236}
237
238impl Parse for ClipPath {
239 #[inline]
240 fn parse<'i, 't>(
241 context: &ParserContext,
242 input: &mut Parser<'i, 't>,
243 ) -> Result<Self, ParseError<'i>> {
244 if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
245 return Ok(ClipPath::None);
246 }
247
248 if let Ok(url) = input.try_parse(|i| SpecifiedUrl::parse(context, i)) {
249 return Ok(ClipPath::Url(url));
250 }
251
252 parse_shape_or_box(
253 context,
254 input,
255 ClipPath::Shape,
256 ClipPath::Box,
257 AllowedBasicShapes::ALL,
258 )
259 }
260}
261
262impl Parse for ShapeOutside {
263 #[inline]
264 fn parse<'i, 't>(
265 context: &ParserContext,
266 input: &mut Parser<'i, 't>,
267 ) -> Result<Self, ParseError<'i>> {
268 if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
271 return Ok(ShapeOutside::None);
272 }
273
274 if let Ok(image) = input.try_parse(|i| Image::parse_with_cors_anonymous(context, i)) {
275 debug_assert_ne!(image, Image::None);
276 return Ok(ShapeOutside::Image(image));
277 }
278
279 parse_shape_or_box(
280 context,
281 input,
282 ShapeOutside::Shape,
283 ShapeOutside::Box,
284 AllowedBasicShapes::SHAPE_OUTSIDE,
285 )
286 }
287}
288
289impl BasicShape {
290 pub fn parse<'i, 't>(
295 context: &ParserContext,
296 input: &mut Parser<'i, 't>,
297 flags: AllowedBasicShapes,
298 shape_type: ShapeType,
299 ) -> Result<Self, ParseError<'i>> {
300 let location = input.current_source_location();
301 let function = input.expect_function()?.clone();
302 input.parse_nested_block(move |i| {
303 match_ignore_ascii_case! { &function,
304 "inset" if flags.contains(AllowedBasicShapes::INSET) => {
305 InsetRect::parse_function_arguments(context, i)
306 .map(BasicShapeRect::Inset)
307 .map(BasicShape::Rect)
308 },
309 "xywh" if flags.contains(AllowedBasicShapes::XYWH) => {
310 Xywh::parse_function_arguments(context, i)
311 .map(BasicShapeRect::Xywh)
312 .map(BasicShape::Rect)
313 },
314 "rect" if flags.contains(AllowedBasicShapes::RECT) => {
315 ShapeRectFunction::parse_function_arguments(context, i)
316 .map(BasicShapeRect::Rect)
317 .map(BasicShape::Rect)
318 },
319 "circle" if flags.contains(AllowedBasicShapes::CIRCLE) => {
320 Circle::parse_function_arguments(context, i)
321 .map(BasicShape::Circle)
322 },
323 "ellipse" if flags.contains(AllowedBasicShapes::ELLIPSE) => {
324 Ellipse::parse_function_arguments(context, i)
325 .map(BasicShape::Ellipse)
326 },
327 "polygon" if flags.contains(AllowedBasicShapes::POLYGON) => {
328 Polygon::parse_function_arguments(context, i, shape_type)
329 .map(BasicShape::Polygon)
330 },
331 "path" if flags.contains(AllowedBasicShapes::PATH) => {
332 Path::parse_function_arguments(i, shape_type)
333 .map(PathOrShapeFunction::Path)
334 .map(BasicShape::PathOrShape)
335 },
336 "shape"
337 if flags.contains(AllowedBasicShapes::SHAPE)
338 && static_prefs::pref!("layout.css.basic-shape-shape.enabled") =>
339 {
340 generic::Shape::parse_function_arguments(context, i, shape_type)
341 .map(PathOrShapeFunction::Shape)
342 .map(BasicShape::PathOrShape)
343 },
344 _ => Err(location
345 .new_custom_error(StyleParseErrorKind::UnexpectedFunction(function.clone()))),
346 }
347 })
348 }
349}
350
351impl Parse for InsetRect {
352 fn parse<'i, 't>(
353 context: &ParserContext,
354 input: &mut Parser<'i, 't>,
355 ) -> Result<Self, ParseError<'i>> {
356 input.expect_function_matching("inset")?;
357 input.parse_nested_block(|i| Self::parse_function_arguments(context, i))
358 }
359}
360
361fn parse_round<'i, 't>(
362 context: &ParserContext,
363 input: &mut Parser<'i, 't>,
364) -> Result<BorderRadius, ParseError<'i>> {
365 if input
366 .try_parse(|i| i.expect_ident_matching("round"))
367 .is_ok()
368 {
369 return BorderRadius::parse(context, input);
370 }
371
372 Ok(BorderRadius::zero())
373}
374
375impl InsetRect {
376 fn parse_function_arguments<'i, 't>(
378 context: &ParserContext,
379 input: &mut Parser<'i, 't>,
380 ) -> Result<Self, ParseError<'i>> {
381 let rect = Rect::parse_with(context, input, LengthPercentage::parse)?;
382 let round = parse_round(context, input)?;
383 Ok(generic::InsetRect { rect, round })
384 }
385}
386
387impl ToCss for RadialPosition {
388 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
389 where
390 W: Write,
391 {
392 self.horizontal.to_css(dest)?;
393 dest.write_char(' ')?;
394 self.vertical.to_css(dest)
395 }
396}
397
398fn convert_to_length_percentage<S: Side>(c: PositionComponent<S>) -> LengthPercentage {
399 use crate::values::specified::{AllowedNumericType, Percentage};
400 match c {
404 PositionComponent::Center => LengthPercentage::from(Percentage::new(0.5)),
407 PositionComponent::Side(keyword, None) => {
408 Percentage::new(if keyword.is_start() { 0. } else { 1. }).into()
409 },
410 PositionComponent::Side(keyword, Some(length)) => {
417 if keyword.is_start() {
418 length
419 } else {
420 length.hundred_percent_minus(AllowedNumericType::All)
421 }
422 },
423 PositionComponent::Length(length) => length,
424 }
425}
426
427fn parse_at_position<'i, 't>(
428 context: &ParserContext,
429 input: &mut Parser<'i, 't>,
430) -> Result<GenericPositionOrAuto<RadialPosition>, ParseError<'i>> {
431 use crate::values::specified::position::Position;
432 if input.try_parse(|i| i.expect_ident_matching("at")).is_ok() {
433 Position::parse(context, input).map(|pos| {
434 GenericPositionOrAuto::Position(RadialPosition::new(
435 convert_to_length_percentage(pos.horizontal),
436 convert_to_length_percentage(pos.vertical),
437 ))
438 })
439 } else {
440 Ok(GenericPositionOrAuto::Auto)
442 }
443}
444
445impl Parse for Circle {
446 fn parse<'i, 't>(
447 context: &ParserContext,
448 input: &mut Parser<'i, 't>,
449 ) -> Result<Self, ParseError<'i>> {
450 input.expect_function_matching("circle")?;
451 input.parse_nested_block(|i| Self::parse_function_arguments(context, i))
452 }
453}
454
455impl Circle {
456 fn parse_function_arguments<'i, 't>(
457 context: &ParserContext,
458 input: &mut Parser<'i, 't>,
459 ) -> Result<Self, ParseError<'i>> {
460 let radius = input
461 .try_parse(|i| ShapeRadius::parse(context, i))
462 .unwrap_or_default();
463 let position = parse_at_position(context, input)?;
464
465 Ok(generic::Circle { radius, position })
466 }
467}
468
469impl Parse for Ellipse {
470 fn parse<'i, 't>(
471 context: &ParserContext,
472 input: &mut Parser<'i, 't>,
473 ) -> Result<Self, ParseError<'i>> {
474 input.expect_function_matching("ellipse")?;
475 input.parse_nested_block(|i| Self::parse_function_arguments(context, i))
476 }
477}
478
479impl Ellipse {
480 fn parse_function_arguments<'i, 't>(
481 context: &ParserContext,
482 input: &mut Parser<'i, 't>,
483 ) -> Result<Self, ParseError<'i>> {
484 let (semiaxis_x, semiaxis_y) = input
485 .try_parse(|i| -> Result<_, ParseError> {
486 Ok((
487 ShapeRadius::parse(context, i)?,
488 ShapeRadius::parse(context, i)?,
489 ))
490 })
491 .unwrap_or_default();
492 let position = parse_at_position(context, input)?;
493
494 Ok(generic::Ellipse {
495 semiaxis_x,
496 semiaxis_y,
497 position,
498 })
499 }
500}
501
502fn parse_fill_rule<'i, 't>(
503 input: &mut Parser<'i, 't>,
504 shape_type: ShapeType,
505 expect_comma: bool,
506) -> FillRule {
507 match shape_type {
508 ShapeType::Outline => Default::default(),
521 ShapeType::Filled => input
522 .try_parse(|i| -> Result<_, ParseError> {
523 let fill = FillRule::parse(i)?;
524 if expect_comma {
525 i.expect_comma()?;
526 }
527 Ok(fill)
528 })
529 .unwrap_or_default(),
530 }
531}
532
533impl Parse for Polygon {
534 fn parse<'i, 't>(
535 context: &ParserContext,
536 input: &mut Parser<'i, 't>,
537 ) -> Result<Self, ParseError<'i>> {
538 input.expect_function_matching("polygon")?;
539 input.parse_nested_block(|i| Self::parse_function_arguments(context, i, ShapeType::Filled))
540 }
541}
542
543impl Polygon {
544 fn parse_function_arguments<'i, 't>(
546 context: &ParserContext,
547 input: &mut Parser<'i, 't>,
548 shape_type: ShapeType,
549 ) -> Result<Self, ParseError<'i>> {
550 let fill = parse_fill_rule(input, shape_type, true );
551 let coordinates = input
552 .parse_comma_separated(|i| {
553 Ok(PolygonCoord(
554 LengthPercentage::parse(context, i)?,
555 LengthPercentage::parse(context, i)?,
556 ))
557 })?
558 .into();
559
560 Ok(Polygon { fill, coordinates })
561 }
562}
563
564impl Path {
565 fn parse_function_arguments<'i, 't>(
567 input: &mut Parser<'i, 't>,
568 shape_type: ShapeType,
569 ) -> Result<Self, ParseError<'i>> {
570 use crate::values::specified::svg_path::AllowEmpty;
571
572 let fill = parse_fill_rule(input, shape_type, true );
573 let path = SVGPathData::parse(input, AllowEmpty::No)?;
574 Ok(Path { fill, path })
575 }
576}
577
578fn round_to_css<W>(round: &BorderRadius, dest: &mut CssWriter<W>) -> fmt::Result
579where
580 W: Write,
581{
582 if !round.is_zero() {
583 dest.write_str(" round ")?;
584 round.to_css(dest)?;
585 }
586 Ok(())
587}
588
589impl ToCss for Xywh {
590 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
591 where
592 W: Write,
593 {
594 self.x.to_css(dest)?;
595 dest.write_char(' ')?;
596 self.y.to_css(dest)?;
597 dest.write_char(' ')?;
598 self.width.to_css(dest)?;
599 dest.write_char(' ')?;
600 self.height.to_css(dest)?;
601 round_to_css(&self.round, dest)
602 }
603}
604
605impl Xywh {
606 fn parse_function_arguments<'i, 't>(
608 context: &ParserContext,
609 input: &mut Parser<'i, 't>,
610 ) -> Result<Self, ParseError<'i>> {
611 let x = LengthPercentage::parse(context, input)?;
612 let y = LengthPercentage::parse(context, input)?;
613 let width = NonNegativeLengthPercentage::parse(context, input)?;
614 let height = NonNegativeLengthPercentage::parse(context, input)?;
615 let round = parse_round(context, input)?;
616 Ok(Xywh {
617 x,
618 y,
619 width,
620 height,
621 round,
622 })
623 }
624}
625
626impl ToCss for ShapeRectFunction {
627 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
628 where
629 W: Write,
630 {
631 self.rect.0.to_css(dest)?;
632 dest.write_char(' ')?;
633 self.rect.1.to_css(dest)?;
634 dest.write_char(' ')?;
635 self.rect.2.to_css(dest)?;
636 dest.write_char(' ')?;
637 self.rect.3.to_css(dest)?;
638 round_to_css(&self.round, dest)
639 }
640}
641
642impl ShapeRectFunction {
643 fn parse_function_arguments<'i, 't>(
645 context: &ParserContext,
646 input: &mut Parser<'i, 't>,
647 ) -> Result<Self, ParseError<'i>> {
648 let rect = Rect::parse_all_components_with(context, input, LengthPercentageOrAuto::parse)?;
649 let round = parse_round(context, input)?;
650 Ok(ShapeRectFunction { rect, round })
651 }
652}
653
654impl ToComputedValue for BasicShapeRect {
655 type ComputedValue = ComputedInsetRect;
656
657 #[inline]
658 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
659 use crate::values::computed::LengthPercentage;
660 use crate::values::computed::LengthPercentageOrAuto;
661 use style_traits::values::specified::AllowedNumericType;
662
663 match self {
664 Self::Inset(ref inset) => inset.to_computed_value(context),
665 Self::Xywh(ref xywh) => {
666 let x = xywh.x.to_computed_value(context);
672 let y = xywh.y.to_computed_value(context);
673 let w = xywh.width.to_computed_value(context);
674 let h = xywh.height.to_computed_value(context);
675 let right = LengthPercentage::hundred_percent_minus_list(
677 &[&x, &w.0],
678 AllowedNumericType::All,
679 );
680 let bottom = LengthPercentage::hundred_percent_minus_list(
682 &[&y, &h.0],
683 AllowedNumericType::All,
684 );
685
686 ComputedInsetRect {
687 rect: Rect::new(y, right, bottom, x),
688 round: xywh.round.to_computed_value(context),
689 }
690 },
691 Self::Rect(ref rect) => {
692 fn compute_top_or_left(v: LengthPercentageOrAuto) -> LengthPercentage {
697 match v {
698 LengthPercentageOrAuto::Auto => LengthPercentage::zero_percent(),
701 LengthPercentageOrAuto::LengthPercentage(lp) => lp,
702 }
703 }
704 fn compute_bottom_or_right(v: LengthPercentageOrAuto) -> LengthPercentage {
705 match v {
706 LengthPercentageOrAuto::Auto => LengthPercentage::zero_percent(),
710 LengthPercentageOrAuto::LengthPercentage(lp) => {
711 LengthPercentage::hundred_percent_minus(lp, AllowedNumericType::All)
712 },
713 }
714 }
715
716 let round = rect.round.to_computed_value(context);
717 let rect = rect.rect.to_computed_value(context);
718 let rect = Rect::new(
719 compute_top_or_left(rect.0),
720 compute_bottom_or_right(rect.1),
721 compute_bottom_or_right(rect.2),
722 compute_top_or_left(rect.3),
723 );
724
725 ComputedInsetRect { rect, round }
726 },
727 }
728 }
729
730 #[inline]
731 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
732 Self::Inset(ToComputedValue::from_computed_value(computed))
733 }
734}
735
736impl generic::Shape<Angle, Position, LengthPercentage> {
737 fn parse_function_arguments<'i, 't>(
740 context: &ParserContext,
741 input: &mut Parser<'i, 't>,
742 shape_type: ShapeType,
743 ) -> Result<Self, ParseError<'i>> {
744 let fill = parse_fill_rule(input, shape_type, false );
745
746 let mut first = true;
747 let commands = input.parse_comma_separated(|i| {
748 if first {
749 first = false;
750
751 i.expect_ident_matching("from")?;
755 Ok(ShapeCommand::Move {
756 point: generic::CommandEndPoint::parse(context, i, generic::ByTo::To)?,
757 })
758 } else {
759 ShapeCommand::parse(context, i)
761 }
762 })?;
763
764 if commands.len() < 2 {
766 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
767 }
768
769 Ok(Self {
770 fill,
771 commands: commands.into(),
772 })
773 }
774}
775
776impl Parse for ShapeCommand {
777 fn parse<'i, 't>(
778 context: &ParserContext,
779 input: &mut Parser<'i, 't>,
780 ) -> Result<Self, ParseError<'i>> {
781 use crate::values::generics::basic_shape::{
782 ArcRadii, ArcSize, ArcSweep, ByTo, CommandEndPoint, ControlPoint,
783 };
784
785 Ok(try_match_ident_ignore_ascii_case! { input,
788 "close" => Self::Close,
789 "move" => {
790 let by_to = ByTo::parse(input)?;
791 let point = CommandEndPoint::parse(context, input, by_to)?;
792 Self::Move { point }
793 },
794 "line" => {
795 let by_to = ByTo::parse(input)?;
796 let point = CommandEndPoint::parse(context, input, by_to)?;
797 Self::Line { point }
798 },
799 "hline" => {
800 let by_to = ByTo::parse(input)?;
801 let x = if by_to.is_abs() {
806 convert_to_length_percentage(Position::parse(context, input)?.horizontal)
807 } else {
808 LengthPercentage::parse(context, input)?
809 };
810 Self::HLine { by_to, x }
811 },
812 "vline" => {
813 let by_to = ByTo::parse(input)?;
814 let y = if by_to.is_abs() {
816 convert_to_length_percentage(Position::parse(context, input)?.horizontal)
817 } else {
818 LengthPercentage::parse(context, input)?
819 };
820 Self::VLine { by_to, y }
821 },
822 "curve" => {
823 let by_to = ByTo::parse(input)?;
824 let point = CommandEndPoint::parse(context, input, by_to)?;
825 input.expect_ident_matching("with")?;
826 let control1 = ControlPoint::parse(context, input, by_to)?;
827 if input.expect_delim('/').is_ok() {
828 let control2 = ControlPoint::parse(context, input, by_to)?;
829 Self::CubicCurve {
830 point,
831 control1,
832 control2,
833 }
834 } else {
835 Self::QuadCurve {
836 point,
837 control1,
838 }
839 }
840 },
841 "smooth" => {
842 let by_to = ByTo::parse(input)?;
843 let point = CommandEndPoint::parse(context, input, by_to)?;
844 if input.try_parse(|i| i.expect_ident_matching("with")).is_ok() {
845 let control2 = ControlPoint::parse(context, input, by_to)?;
846 Self::SmoothCubic {
847 point,
848 control2,
849 }
850 } else {
851 Self::SmoothQuad { point }
852 }
853 },
854 "arc" => {
855 let by_to = ByTo::parse(input)?;
856 let point = CommandEndPoint::parse(context, input, by_to)?;
857 input.expect_ident_matching("of")?;
858 let rx = LengthPercentage::parse(context, input)?;
859 let ry = input.try_parse(|i| LengthPercentage::parse(context, i)).ok();
860 let radii = ArcRadii { rx, ry: ry.into() };
861
862 let mut arc_sweep = None;
864 let mut arc_size = None;
865 let mut rotate = None;
866 loop {
867 if arc_sweep.is_none() {
868 arc_sweep = input.try_parse(ArcSweep::parse).ok();
869 }
870
871 if arc_size.is_none() {
872 arc_size = input.try_parse(ArcSize::parse).ok();
873 if arc_size.is_some() {
874 continue;
875 }
876 }
877
878 if rotate.is_none()
879 && input
880 .try_parse(|i| i.expect_ident_matching("rotate"))
881 .is_ok()
882 {
883 rotate = Some(Angle::parse(context, input)?);
884 continue;
885 }
886 break;
887 }
888 Self::Arc {
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}
910
911impl generic::ControlPoint<Position, LengthPercentage> {
912 fn parse<'i, 't>(
914 context: &ParserContext,
915 input: &mut Parser<'i, 't>,
916 by_to: generic::ByTo,
917 ) -> Result<Self, ParseError<'i>> {
918 let coord = input.try_parse(|i| generic::CoordinatePair::parse(context, i));
919
920 if by_to.is_abs() && coord.is_err() {
922 let pos = Position::parse(context, input)?;
923 return Ok(Self::Absolute(pos));
924 }
925
926 let coord = coord?;
928 let mut reference = generic::ControlReference::None;
929 if input.try_parse(|i| i.expect_ident_matching("from")).is_ok() {
930 reference = generic::ControlReference::parse(input)?;
931 }
932 Ok(Self::Relative(generic::RelativeControlPoint {
933 coord,
934 reference,
935 }))
936 }
937}
938
939impl generic::CommandEndPoint<Position, LengthPercentage> {
940 pub fn parse<'i, 't>(
942 context: &ParserContext,
943 input: &mut Parser<'i, 't>,
944 by_to: generic::ByTo,
945 ) -> Result<Self, ParseError<'i>> {
946 if by_to.is_abs() {
947 let point = Position::parse(context, input)?;
948 Ok(Self::ToPosition(point))
949 } else {
950 let point = generic::CoordinatePair::parse(context, input)?;
951 Ok(Self::ByCoordinate(point))
952 }
953 }
954}