1use crate::derives::*;
11use crate::parser::{Parse, ParserContext};
12use crate::values::computed::basic_shape::InsetRect as ComputedInsetRect;
13use crate::values::computed::{
14 Context, LengthPercentage as ComputedLengthPercentage, ToComputedValue,
15};
16use crate::values::generics::basic_shape as generic;
17use crate::values::generics::basic_shape::{Path, PolygonCoord};
18use crate::values::generics::position::GenericPositionOrAuto;
19use crate::values::generics::rect::Rect;
20use crate::values::specified::angle::Angle;
21use crate::values::specified::border::BorderRadius;
22use crate::values::specified::image::Image;
23use crate::values::specified::length::LengthPercentageOrAuto;
24use crate::values::specified::position::{Position, Side};
25use crate::values::specified::url::SpecifiedUrl;
26use crate::values::specified::PositionComponent;
27use crate::values::specified::{LengthPercentage, NonNegativeLengthPercentage, SVGPathData};
28use crate::values::CSSFloat;
29use crate::Zero;
30use cssparser::{match_ignore_ascii_case, Parser};
31use std::fmt::{self, Write};
32use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
33
34pub use crate::values::generics::basic_shape::FillRule;
36
37pub type ClipPath = generic::GenericClipPath<BasicShape, SpecifiedUrl>;
39
40pub type ShapeOutside = generic::GenericShapeOutside<BasicShape, Image>;
42
43pub type RadialPosition = generic::ShapePosition<LengthPercentage>;
47
48pub type BasicShape = generic::GenericBasicShape<Angle, Position, LengthPercentage, BasicShapeRect>;
50
51pub type InsetRect = generic::GenericInsetRect<LengthPercentage>;
53
54pub type Circle = generic::Circle<LengthPercentage>;
56
57pub type Ellipse = generic::Ellipse<LengthPercentage>;
59
60pub type ShapeRadius = generic::ShapeRadius<LengthPercentage>;
62
63pub type Polygon = generic::GenericPolygon<LengthPercentage>;
65
66pub type PathOrShapeFunction =
68 generic::GenericPathOrShapeFunction<Angle, Position, LengthPercentage>;
69
70pub type ShapeCommand = generic::GenericShapeCommand<Angle, Position, LengthPercentage>;
72
73#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToShmem)]
83pub struct Xywh {
84 pub x: LengthPercentage,
86 pub y: LengthPercentage,
88 pub width: NonNegativeLengthPercentage,
90 pub height: NonNegativeLengthPercentage,
92 pub round: BorderRadius,
95}
96
97#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToShmem)]
101#[repr(C)]
102pub struct ShapeRectFunction {
103 pub rect: Rect<LengthPercentageOrAuto>,
112 pub round: BorderRadius,
115}
116
117#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
122pub enum BasicShapeRect {
123 Inset(InsetRect),
125 #[css(function)]
127 Xywh(Xywh),
128 #[css(function)]
130 Rect(ShapeRectFunction),
131}
132
133pub enum ShapeType {
140 Filled,
142 Outline,
144}
145
146bitflags! {
147 #[derive(Clone, Copy)]
164 #[repr(C)]
165 pub struct AllowedBasicShapes: u8 {
166 const INSET = 1 << 0;
168 const XYWH = 1 << 1;
170 const RECT = 1 << 2;
172 const CIRCLE = 1 << 3;
174 const ELLIPSE = 1 << 4;
176 const POLYGON = 1 << 5;
178 const PATH = 1 << 6;
180 const SHAPE = 1 << 7;
182
183 const ALL =
185 Self::INSET.bits() |
186 Self::XYWH.bits() |
187 Self::RECT.bits() |
188 Self::CIRCLE.bits() |
189 Self::ELLIPSE.bits() |
190 Self::POLYGON.bits() |
191 Self::PATH.bits() |
192 Self::SHAPE.bits();
193
194 const SHAPE_OUTSIDE =
196 Self::INSET.bits() |
197 Self::XYWH.bits() |
198 Self::RECT.bits() |
199 Self::CIRCLE.bits() |
200 Self::ELLIPSE.bits() |
201 Self::POLYGON.bits();
202 }
203}
204
205fn parse_shape_or_box<'i, 't, R, ReferenceBox>(
207 context: &ParserContext,
208 input: &mut Parser<'i, 't>,
209 to_shape: impl FnOnce(Box<BasicShape>, ReferenceBox) -> R,
210 to_reference_box: impl FnOnce(ReferenceBox) -> R,
211 flags: AllowedBasicShapes,
212) -> Result<R, ParseError<'i>>
213where
214 ReferenceBox: Default + Parse,
215{
216 let mut shape = None;
217 let mut ref_box = None;
218 loop {
219 if shape.is_none() {
220 shape = input
221 .try_parse(|i| BasicShape::parse(context, i, flags, ShapeType::Filled))
222 .ok();
223 }
224
225 if ref_box.is_none() {
226 ref_box = input.try_parse(|i| ReferenceBox::parse(context, i)).ok();
227 if ref_box.is_some() {
228 continue;
229 }
230 }
231 break;
232 }
233
234 if let Some(shp) = shape {
235 return Ok(to_shape(Box::new(shp), ref_box.unwrap_or_default()));
236 }
237
238 match ref_box {
239 Some(r) => Ok(to_reference_box(r)),
240 None => Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
241 }
242}
243
244impl Parse for ClipPath {
245 #[inline]
246 fn parse<'i, 't>(
247 context: &ParserContext,
248 input: &mut Parser<'i, 't>,
249 ) -> Result<Self, ParseError<'i>> {
250 if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
251 return Ok(ClipPath::None);
252 }
253
254 if let Ok(url) = input.try_parse(|i| SpecifiedUrl::parse(context, i)) {
255 return Ok(ClipPath::Url(url));
256 }
257
258 parse_shape_or_box(
259 context,
260 input,
261 ClipPath::Shape,
262 ClipPath::Box,
263 AllowedBasicShapes::ALL,
264 )
265 }
266}
267
268impl Parse for ShapeOutside {
269 #[inline]
270 fn parse<'i, 't>(
271 context: &ParserContext,
272 input: &mut Parser<'i, 't>,
273 ) -> Result<Self, ParseError<'i>> {
274 if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
277 return Ok(ShapeOutside::None);
278 }
279
280 if let Ok(image) = input.try_parse(|i| Image::parse_with_cors_anonymous(context, i)) {
281 debug_assert_ne!(image, Image::None);
282 return Ok(ShapeOutside::Image(image));
283 }
284
285 parse_shape_or_box(
286 context,
287 input,
288 ShapeOutside::Shape,
289 ShapeOutside::Box,
290 AllowedBasicShapes::SHAPE_OUTSIDE,
291 )
292 }
293}
294
295impl BasicShape {
296 pub fn parse<'i, 't>(
301 context: &ParserContext,
302 input: &mut Parser<'i, 't>,
303 flags: AllowedBasicShapes,
304 shape_type: ShapeType,
305 ) -> Result<Self, ParseError<'i>> {
306 let location = input.current_source_location();
307 let function = input.expect_function()?.clone();
308 input.parse_nested_block(move |i| {
309 match_ignore_ascii_case! { &function,
310 "inset" if flags.contains(AllowedBasicShapes::INSET) => {
311 InsetRect::parse_function_arguments(context, i)
312 .map(BasicShapeRect::Inset)
313 .map(BasicShape::Rect)
314 },
315 "xywh" if flags.contains(AllowedBasicShapes::XYWH) => {
316 Xywh::parse_function_arguments(context, i)
317 .map(BasicShapeRect::Xywh)
318 .map(BasicShape::Rect)
319 },
320 "rect" if flags.contains(AllowedBasicShapes::RECT) => {
321 ShapeRectFunction::parse_function_arguments(context, i)
322 .map(BasicShapeRect::Rect)
323 .map(BasicShape::Rect)
324 },
325 "circle" if flags.contains(AllowedBasicShapes::CIRCLE) => {
326 Circle::parse_function_arguments(context, i)
327 .map(BasicShape::Circle)
328 },
329 "ellipse" if flags.contains(AllowedBasicShapes::ELLIPSE) => {
330 Ellipse::parse_function_arguments(context, i)
331 .map(BasicShape::Ellipse)
332 },
333 "polygon" if flags.contains(AllowedBasicShapes::POLYGON) => {
334 Polygon::parse_function_arguments(context, i, shape_type)
335 .map(BasicShape::Polygon)
336 },
337 "path" if flags.contains(AllowedBasicShapes::PATH) => {
338 Path::parse_function_arguments(i, shape_type)
339 .map(PathOrShapeFunction::Path)
340 .map(BasicShape::PathOrShape)
341 },
342 "shape"
343 if flags.contains(AllowedBasicShapes::SHAPE)
344 && static_prefs::pref!("layout.css.basic-shape-shape.enabled") =>
345 {
346 generic::Shape::parse_function_arguments(context, i, shape_type)
347 .map(PathOrShapeFunction::Shape)
348 .map(BasicShape::PathOrShape)
349 },
350 _ => Err(location
351 .new_custom_error(StyleParseErrorKind::UnexpectedFunction(function.clone()))),
352 }
353 })
354 }
355}
356
357impl Parse for InsetRect {
358 fn parse<'i, 't>(
359 context: &ParserContext,
360 input: &mut Parser<'i, 't>,
361 ) -> Result<Self, ParseError<'i>> {
362 input.expect_function_matching("inset")?;
363 input.parse_nested_block(|i| Self::parse_function_arguments(context, i))
364 }
365}
366
367fn parse_round<'i, 't>(
368 context: &ParserContext,
369 input: &mut Parser<'i, 't>,
370) -> Result<BorderRadius, ParseError<'i>> {
371 if input
372 .try_parse(|i| i.expect_ident_matching("round"))
373 .is_ok()
374 {
375 return BorderRadius::parse(context, input);
376 }
377
378 Ok(BorderRadius::zero())
379}
380
381impl InsetRect {
382 fn parse_function_arguments<'i, 't>(
384 context: &ParserContext,
385 input: &mut Parser<'i, 't>,
386 ) -> Result<Self, ParseError<'i>> {
387 let rect = Rect::parse_with(context, input, LengthPercentage::parse)?;
388 let round = parse_round(context, input)?;
389 Ok(generic::InsetRect { rect, round })
390 }
391}
392
393impl ToCss for RadialPosition {
394 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
395 where
396 W: Write,
397 {
398 self.horizontal.to_css(dest)?;
399 dest.write_char(' ')?;
400 self.vertical.to_css(dest)
401 }
402}
403
404fn convert_to_length_percentage<S: Side>(c: PositionComponent<S>) -> LengthPercentage {
405 use crate::values::specified::{AllowedNumericType, Percentage};
406 match c {
410 PositionComponent::Center => LengthPercentage::from(Percentage::new(0.5)),
413 PositionComponent::Side(keyword, None) => {
414 Percentage::new(if keyword.is_start() { 0. } else { 1. }).into()
415 },
416 PositionComponent::Side(keyword, Some(length)) => {
423 if keyword.is_start() {
424 length
425 } else {
426 length.hundred_percent_minus(AllowedNumericType::All)
427 }
428 },
429 PositionComponent::Length(length) => length,
430 }
431}
432
433fn parse_at_position<'i, 't>(
434 context: &ParserContext,
435 input: &mut Parser<'i, 't>,
436) -> Result<GenericPositionOrAuto<RadialPosition>, ParseError<'i>> {
437 use crate::values::specified::position::Position;
438 if input.try_parse(|i| i.expect_ident_matching("at")).is_ok() {
439 Position::parse(context, input).map(|pos| {
440 GenericPositionOrAuto::Position(RadialPosition::new(
441 convert_to_length_percentage(pos.horizontal),
442 convert_to_length_percentage(pos.vertical),
443 ))
444 })
445 } else {
446 Ok(GenericPositionOrAuto::Auto)
448 }
449}
450
451impl Parse for Circle {
452 fn parse<'i, 't>(
453 context: &ParserContext,
454 input: &mut Parser<'i, 't>,
455 ) -> Result<Self, ParseError<'i>> {
456 input.expect_function_matching("circle")?;
457 input.parse_nested_block(|i| Self::parse_function_arguments(context, i))
458 }
459}
460
461impl Circle {
462 fn parse_function_arguments<'i, 't>(
463 context: &ParserContext,
464 input: &mut Parser<'i, 't>,
465 ) -> Result<Self, ParseError<'i>> {
466 let radius = input
467 .try_parse(|i| ShapeRadius::parse(context, i))
468 .unwrap_or_default();
469 let position = parse_at_position(context, input)?;
470
471 Ok(generic::Circle { radius, position })
472 }
473}
474
475impl Parse for Ellipse {
476 fn parse<'i, 't>(
477 context: &ParserContext,
478 input: &mut Parser<'i, 't>,
479 ) -> Result<Self, ParseError<'i>> {
480 input.expect_function_matching("ellipse")?;
481 input.parse_nested_block(|i| Self::parse_function_arguments(context, i))
482 }
483}
484
485impl Ellipse {
486 fn parse_function_arguments<'i, 't>(
487 context: &ParserContext,
488 input: &mut Parser<'i, 't>,
489 ) -> Result<Self, ParseError<'i>> {
490 let (semiaxis_x, semiaxis_y) = input
491 .try_parse(|i| -> Result<_, ParseError> {
492 Ok((
493 ShapeRadius::parse(context, i)?,
494 ShapeRadius::parse(context, i)?,
495 ))
496 })
497 .unwrap_or_default();
498 let position = parse_at_position(context, input)?;
499
500 Ok(generic::Ellipse {
501 semiaxis_x,
502 semiaxis_y,
503 position,
504 })
505 }
506}
507
508fn parse_fill_rule<'i, 't>(
509 input: &mut Parser<'i, 't>,
510 shape_type: ShapeType,
511 expect_comma: bool,
512) -> FillRule {
513 match shape_type {
514 ShapeType::Outline => Default::default(),
527 ShapeType::Filled => input
528 .try_parse(|i| -> Result<_, ParseError> {
529 let fill = FillRule::parse(i)?;
530 if expect_comma {
531 i.expect_comma()?;
532 }
533 Ok(fill)
534 })
535 .unwrap_or_default(),
536 }
537}
538
539impl Parse for Polygon {
540 fn parse<'i, 't>(
541 context: &ParserContext,
542 input: &mut Parser<'i, 't>,
543 ) -> Result<Self, ParseError<'i>> {
544 input.expect_function_matching("polygon")?;
545 input.parse_nested_block(|i| Self::parse_function_arguments(context, i, ShapeType::Filled))
546 }
547}
548
549impl Polygon {
550 fn parse_function_arguments<'i, 't>(
552 context: &ParserContext,
553 input: &mut Parser<'i, 't>,
554 shape_type: ShapeType,
555 ) -> Result<Self, ParseError<'i>> {
556 let fill = parse_fill_rule(input, shape_type, true );
557 let coordinates = input
558 .parse_comma_separated(|i| {
559 Ok(PolygonCoord(
560 LengthPercentage::parse(context, i)?,
561 LengthPercentage::parse(context, i)?,
562 ))
563 })?
564 .into();
565
566 Ok(Polygon { fill, coordinates })
567 }
568}
569
570impl Path {
571 fn parse_function_arguments<'i, 't>(
573 input: &mut Parser<'i, 't>,
574 shape_type: ShapeType,
575 ) -> Result<Self, ParseError<'i>> {
576 use crate::values::specified::svg_path::AllowEmpty;
577
578 let fill = parse_fill_rule(input, shape_type, true );
579 let path = SVGPathData::parse(input, AllowEmpty::No)?;
580 Ok(Path { fill, path })
581 }
582}
583
584fn round_to_css<W>(round: &BorderRadius, dest: &mut CssWriter<W>) -> fmt::Result
585where
586 W: Write,
587{
588 if !round.is_zero() {
589 dest.write_str(" round ")?;
590 round.to_css(dest)?;
591 }
592 Ok(())
593}
594
595impl ToCss for Xywh {
596 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
597 where
598 W: Write,
599 {
600 self.x.to_css(dest)?;
601 dest.write_char(' ')?;
602 self.y.to_css(dest)?;
603 dest.write_char(' ')?;
604 self.width.to_css(dest)?;
605 dest.write_char(' ')?;
606 self.height.to_css(dest)?;
607 round_to_css(&self.round, dest)
608 }
609}
610
611impl Xywh {
612 fn parse_function_arguments<'i, 't>(
614 context: &ParserContext,
615 input: &mut Parser<'i, 't>,
616 ) -> Result<Self, ParseError<'i>> {
617 let x = LengthPercentage::parse(context, input)?;
618 let y = LengthPercentage::parse(context, input)?;
619 let width = NonNegativeLengthPercentage::parse(context, input)?;
620 let height = NonNegativeLengthPercentage::parse(context, input)?;
621 let round = parse_round(context, input)?;
622 Ok(Xywh {
623 x,
624 y,
625 width,
626 height,
627 round,
628 })
629 }
630}
631
632impl ToCss for ShapeRectFunction {
633 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
634 where
635 W: Write,
636 {
637 self.rect.0.to_css(dest)?;
638 dest.write_char(' ')?;
639 self.rect.1.to_css(dest)?;
640 dest.write_char(' ')?;
641 self.rect.2.to_css(dest)?;
642 dest.write_char(' ')?;
643 self.rect.3.to_css(dest)?;
644 round_to_css(&self.round, dest)
645 }
646}
647
648impl ShapeRectFunction {
649 fn parse_function_arguments<'i, 't>(
651 context: &ParserContext,
652 input: &mut Parser<'i, 't>,
653 ) -> Result<Self, ParseError<'i>> {
654 let rect = Rect::parse_all_components_with(context, input, LengthPercentageOrAuto::parse)?;
655 let round = parse_round(context, input)?;
656 Ok(ShapeRectFunction { rect, round })
657 }
658}
659
660impl ToComputedValue for BasicShapeRect {
661 type ComputedValue = ComputedInsetRect;
662
663 #[inline]
664 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
665 use crate::values::computed::LengthPercentage;
666 use crate::values::computed::LengthPercentageOrAuto;
667 use style_traits::values::specified::AllowedNumericType;
668
669 match self {
670 Self::Inset(ref inset) => inset.to_computed_value(context),
671 Self::Xywh(ref xywh) => {
672 let x = xywh.x.to_computed_value(context);
678 let y = xywh.y.to_computed_value(context);
679 let w = xywh.width.to_computed_value(context);
680 let h = xywh.height.to_computed_value(context);
681 let right = LengthPercentage::hundred_percent_minus_list(
683 &[&x, &w.0],
684 AllowedNumericType::All,
685 );
686 let bottom = LengthPercentage::hundred_percent_minus_list(
688 &[&y, &h.0],
689 AllowedNumericType::All,
690 );
691
692 ComputedInsetRect {
693 rect: Rect::new(y, right, bottom, x),
694 round: xywh.round.to_computed_value(context),
695 }
696 },
697 Self::Rect(ref rect) => {
698 fn compute_top_or_left(v: LengthPercentageOrAuto) -> LengthPercentage {
703 match v {
704 LengthPercentageOrAuto::Auto => LengthPercentage::zero_percent(),
707 LengthPercentageOrAuto::LengthPercentage(lp) => lp,
708 }
709 }
710 fn compute_bottom_or_right(v: LengthPercentageOrAuto) -> LengthPercentage {
711 match v {
712 LengthPercentageOrAuto::Auto => LengthPercentage::zero_percent(),
716 LengthPercentageOrAuto::LengthPercentage(lp) => {
717 LengthPercentage::hundred_percent_minus(lp, AllowedNumericType::All)
718 },
719 }
720 }
721
722 let round = rect.round.to_computed_value(context);
723 let rect = rect.rect.to_computed_value(context);
724 let rect = Rect::new(
725 compute_top_or_left(rect.0),
726 compute_bottom_or_right(rect.1),
727 compute_bottom_or_right(rect.2),
728 compute_top_or_left(rect.3),
729 );
730
731 ComputedInsetRect { rect, round }
732 },
733 }
734 }
735
736 #[inline]
737 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
738 Self::Inset(ToComputedValue::from_computed_value(computed))
739 }
740}
741
742impl generic::Shape<Angle, Position, LengthPercentage> {
743 fn parse_function_arguments<'i, 't>(
746 context: &ParserContext,
747 input: &mut Parser<'i, 't>,
748 shape_type: ShapeType,
749 ) -> Result<Self, ParseError<'i>> {
750 let fill = parse_fill_rule(input, shape_type, false );
751
752 let mut first = true;
753 let commands = input.parse_comma_separated(|i| {
754 if first {
755 first = false;
756
757 i.expect_ident_matching("from")?;
761 Ok(ShapeCommand::Move {
762 point: generic::CommandEndPoint::parse_endpoint_as_abs(context, i)?,
763 })
764 } else {
765 ShapeCommand::parse(context, i)
767 }
768 })?;
769
770 if commands.len() < 2 {
772 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
773 }
774
775 Ok(Self {
776 fill,
777 commands: commands.into(),
778 })
779 }
780}
781
782impl Parse for ShapeCommand {
783 fn parse<'i, 't>(
784 context: &ParserContext,
785 input: &mut Parser<'i, 't>,
786 ) -> Result<Self, ParseError<'i>> {
787 use crate::values::generics::basic_shape::{
788 ArcRadii, ArcSize, ArcSweep, AxisEndPoint, CommandEndPoint, ControlPoint,
789 };
790
791 Ok(try_match_ident_ignore_ascii_case! { input,
794 "close" => Self::Close,
795 "move" => {
796 let point = CommandEndPoint::parse(context, input)?;
797 Self::Move { point }
798 },
799 "line" => {
800 let point = CommandEndPoint::parse(context, input)?;
801 Self::Line { point }
802 },
803 "hline" => {
804 let x = AxisEndPoint::parse_hline(context, input)?;
805 Self::HLine { x }
806 },
807 "vline" => {
808 let y = AxisEndPoint::parse_vline(context, input)?;
809 Self::VLine { y }
810 },
811 "curve" => {
812 let point = CommandEndPoint::parse(context, input)?;
813 input.expect_ident_matching("with")?;
814 let control1 = ControlPoint::parse(context, input, point.is_abs())?;
815 if input.try_parse(|i| i.expect_delim('/')).is_ok() {
816 let control2 = ControlPoint::parse(context, input, point.is_abs())?;
817 Self::CubicCurve {
818 point,
819 control1,
820 control2,
821 }
822 } else {
823 Self::QuadCurve {
824 point,
825 control1,
826 }
827 }
828 },
829 "smooth" => {
830 let point = CommandEndPoint::parse(context, input)?;
831 if input.try_parse(|i| i.expect_ident_matching("with")).is_ok() {
832 let control2 = ControlPoint::parse(context, input, point.is_abs())?;
833 Self::SmoothCubic {
834 point,
835 control2,
836 }
837 } else {
838 Self::SmoothQuad { point }
839 }
840 },
841 "arc" => {
842 let point = CommandEndPoint::parse(context, input)?;
843 input.expect_ident_matching("of")?;
844 let rx = LengthPercentage::parse(context, input)?;
845 let ry = input.try_parse(|i| LengthPercentage::parse(context, i)).ok();
846 let radii = ArcRadii { rx, ry: ry.into() };
847
848 let mut arc_sweep = None;
850 let mut arc_size = None;
851 let mut rotate = None;
852 loop {
853 if arc_sweep.is_none() {
854 arc_sweep = input.try_parse(ArcSweep::parse).ok();
855 }
856
857 if arc_size.is_none() {
858 arc_size = input.try_parse(ArcSize::parse).ok();
859 if arc_size.is_some() {
860 continue;
861 }
862 }
863
864 if rotate.is_none()
865 && input
866 .try_parse(|i| i.expect_ident_matching("rotate"))
867 .is_ok()
868 {
869 rotate = Some(Angle::parse(context, input)?);
870 continue;
871 }
872 break;
873 }
874 Self::Arc {
875 point,
876 radii,
877 arc_sweep: arc_sweep.unwrap_or(ArcSweep::Ccw),
878 arc_size: arc_size.unwrap_or(ArcSize::Small),
879 rotate: rotate.unwrap_or(Angle::zero()),
880 }
881 },
882 })
883 }
884}
885
886impl Parse for generic::CoordinatePair<LengthPercentage> {
887 fn parse<'i, 't>(
888 context: &ParserContext,
889 input: &mut Parser<'i, 't>,
890 ) -> Result<Self, ParseError<'i>> {
891 let x = LengthPercentage::parse(context, input)?;
892 let y = LengthPercentage::parse(context, input)?;
893 Ok(Self::new(x, y))
894 }
895}
896
897impl generic::ControlPoint<Position, LengthPercentage> {
898 fn parse<'i, 't>(
900 context: &ParserContext,
901 input: &mut Parser<'i, 't>,
902 is_end_point_abs: bool,
903 ) -> Result<Self, ParseError<'i>> {
904 use generic::ControlReference;
905 let coord = input.try_parse(|i| generic::CoordinatePair::parse(context, i));
906
907 if is_end_point_abs && coord.is_err() {
909 let pos = Position::parse(context, input)?;
910 return Ok(Self::Absolute(pos));
911 }
912
913 let coord = coord?;
915 let mut reference = if is_end_point_abs {
916 ControlReference::Origin
917 } else {
918 ControlReference::Start
919 };
920 if input.try_parse(|i| i.expect_ident_matching("from")).is_ok() {
921 reference = ControlReference::parse(input)?;
922 }
923
924 Ok(Self::Relative(generic::RelativeControlPoint {
925 coord,
926 reference,
927 }))
928 }
929}
930
931impl Parse for generic::CommandEndPoint<Position, LengthPercentage> {
932 fn parse<'i, 't>(
934 context: &ParserContext,
935 input: &mut Parser<'i, 't>,
936 ) -> Result<Self, ParseError<'i>> {
937 if ByTo::parse(input)?.is_abs() {
938 Self::parse_endpoint_as_abs(context, input)
939 } else {
940 let point = generic::CoordinatePair::parse(context, input)?;
941 Ok(Self::ByCoordinate(point))
942 }
943 }
944}
945
946impl generic::CommandEndPoint<Position, LengthPercentage> {
947 fn parse_endpoint_as_abs<'i, 't>(
949 context: &ParserContext,
950 input: &mut Parser<'i, 't>,
951 ) -> Result<Self, ParseError<'i>> {
952 let point = Position::parse(context, input)?;
953 Ok(generic::CommandEndPoint::ToPosition(point))
954 }
955}
956
957impl generic::AxisEndPoint<LengthPercentage> {
958 pub fn parse_hline<'i, 't>(
960 context: &ParserContext,
961 input: &mut Parser<'i, 't>,
962 ) -> Result<Self, ParseError<'i>> {
963 use cssparser::Token;
964 use generic::{AxisPosition, AxisPositionKeyword};
965
966 if !ByTo::parse(input)?.is_abs() {
968 return Ok(Self::ByCoordinate(LengthPercentage::parse(context, input)?));
969 }
970
971 let x = AxisPosition::parse(context, input)?;
972 if let AxisPosition::Keyword(
973 _word @ (AxisPositionKeyword::Top
974 | AxisPositionKeyword::Bottom
975 | AxisPositionKeyword::YStart
976 | AxisPositionKeyword::YEnd),
977 ) = &x
978 {
979 let location = input.current_source_location();
980 let token = Token::Ident(x.to_css_string().into());
981 return Err(location.new_unexpected_token_error(token));
982 }
983 Ok(Self::ToPosition(x))
984 }
985
986 pub fn parse_vline<'i, 't>(
988 context: &ParserContext,
989 input: &mut Parser<'i, 't>,
990 ) -> Result<Self, ParseError<'i>> {
991 use cssparser::Token;
992 use generic::{AxisPosition, AxisPositionKeyword};
993
994 if !ByTo::parse(input)?.is_abs() {
996 return Ok(Self::ByCoordinate(LengthPercentage::parse(context, input)?));
997 }
998
999 let y = AxisPosition::parse(context, input)?;
1000 if let AxisPosition::Keyword(
1001 _word @ (AxisPositionKeyword::Left
1002 | AxisPositionKeyword::Right
1003 | AxisPositionKeyword::XStart
1004 | AxisPositionKeyword::XEnd),
1005 ) = &y
1006 {
1007 let location = input.current_source_location();
1009 let token = Token::Ident(y.to_css_string().into());
1010 return Err(location.new_unexpected_token_error(token));
1011 }
1012 Ok(Self::ToPosition(y))
1013 }
1014}
1015
1016impl ToComputedValue for generic::AxisPosition<LengthPercentage> {
1017 type ComputedValue = generic::AxisPosition<ComputedLengthPercentage>;
1018
1019 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
1020 match self {
1021 Self::LengthPercent(lp) => {
1022 Self::ComputedValue::LengthPercent(lp.to_computed_value(context))
1023 },
1024 Self::Keyword(word) => {
1025 let lp = LengthPercentage::Percentage(word.as_percentage());
1026 Self::ComputedValue::LengthPercent(lp.to_computed_value(context))
1027 },
1028 }
1029 }
1030
1031 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
1032 match computed {
1033 Self::ComputedValue::LengthPercent(lp) => {
1034 Self::LengthPercent(LengthPercentage::from_computed_value(lp))
1035 },
1036 _ => unreachable!("Invalid state: computed value cannot be a keyword."),
1037 }
1038 }
1039}
1040
1041impl ToComputedValue for generic::AxisPosition<CSSFloat> {
1042 type ComputedValue = Self;
1043
1044 fn to_computed_value(&self, _context: &Context) -> Self {
1045 *self
1046 }
1047
1048 fn from_computed_value(computed: &Self) -> Self {
1049 *computed
1050 }
1051}
1052
1053#[derive(Clone, Copy, Debug, Parse, PartialEq)]
1056enum ByTo {
1057 By,
1059 To,
1061}
1062
1063impl ByTo {
1064 #[inline]
1066 pub fn is_abs(&self) -> bool {
1067 matches!(self, ByTo::To)
1068 }
1069}