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::CIRCLE.bits() |
198 Self::ELLIPSE.bits() |
199 Self::POLYGON.bits();
200 }
201}
202
203fn parse_shape_or_box<'i, 't, R, ReferenceBox>(
205 context: &ParserContext,
206 input: &mut Parser<'i, 't>,
207 to_shape: impl FnOnce(Box<BasicShape>, ReferenceBox) -> R,
208 to_reference_box: impl FnOnce(ReferenceBox) -> R,
209 flags: AllowedBasicShapes,
210) -> Result<R, ParseError<'i>>
211where
212 ReferenceBox: Default + Parse,
213{
214 let mut shape = None;
215 let mut ref_box = None;
216 loop {
217 if shape.is_none() {
218 shape = input
219 .try_parse(|i| BasicShape::parse(context, i, flags, ShapeType::Filled))
220 .ok();
221 }
222
223 if ref_box.is_none() {
224 ref_box = input.try_parse(|i| ReferenceBox::parse(context, i)).ok();
225 if ref_box.is_some() {
226 continue;
227 }
228 }
229 break;
230 }
231
232 if let Some(shp) = shape {
233 return Ok(to_shape(Box::new(shp), ref_box.unwrap_or_default()));
234 }
235
236 match ref_box {
237 Some(r) => Ok(to_reference_box(r)),
238 None => Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
239 }
240}
241
242impl Parse for ClipPath {
243 #[inline]
244 fn parse<'i, 't>(
245 context: &ParserContext,
246 input: &mut Parser<'i, 't>,
247 ) -> Result<Self, ParseError<'i>> {
248 if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
249 return Ok(ClipPath::None);
250 }
251
252 if let Ok(url) = input.try_parse(|i| SpecifiedUrl::parse(context, i)) {
253 return Ok(ClipPath::Url(url));
254 }
255
256 parse_shape_or_box(
257 context,
258 input,
259 ClipPath::Shape,
260 ClipPath::Box,
261 AllowedBasicShapes::ALL,
262 )
263 }
264}
265
266impl Parse for ShapeOutside {
267 #[inline]
268 fn parse<'i, 't>(
269 context: &ParserContext,
270 input: &mut Parser<'i, 't>,
271 ) -> Result<Self, ParseError<'i>> {
272 if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
275 return Ok(ShapeOutside::None);
276 }
277
278 if let Ok(image) = input.try_parse(|i| Image::parse_with_cors_anonymous(context, i)) {
279 debug_assert_ne!(image, Image::None);
280 return Ok(ShapeOutside::Image(image));
281 }
282
283 parse_shape_or_box(
284 context,
285 input,
286 ShapeOutside::Shape,
287 ShapeOutside::Box,
288 AllowedBasicShapes::SHAPE_OUTSIDE,
289 )
290 }
291}
292
293impl BasicShape {
294 pub fn parse<'i, 't>(
299 context: &ParserContext,
300 input: &mut Parser<'i, 't>,
301 flags: AllowedBasicShapes,
302 shape_type: ShapeType,
303 ) -> Result<Self, ParseError<'i>> {
304 let location = input.current_source_location();
305 let function = input.expect_function()?.clone();
306 input.parse_nested_block(move |i| {
307 match_ignore_ascii_case! { &function,
308 "inset" if flags.contains(AllowedBasicShapes::INSET) => {
309 InsetRect::parse_function_arguments(context, i)
310 .map(BasicShapeRect::Inset)
311 .map(BasicShape::Rect)
312 },
313 "xywh" if flags.contains(AllowedBasicShapes::XYWH) => {
314 Xywh::parse_function_arguments(context, i)
315 .map(BasicShapeRect::Xywh)
316 .map(BasicShape::Rect)
317 },
318 "rect" if flags.contains(AllowedBasicShapes::RECT) => {
319 ShapeRectFunction::parse_function_arguments(context, i)
320 .map(BasicShapeRect::Rect)
321 .map(BasicShape::Rect)
322 },
323 "circle" if flags.contains(AllowedBasicShapes::CIRCLE) => {
324 Circle::parse_function_arguments(context, i)
325 .map(BasicShape::Circle)
326 },
327 "ellipse" if flags.contains(AllowedBasicShapes::ELLIPSE) => {
328 Ellipse::parse_function_arguments(context, i)
329 .map(BasicShape::Ellipse)
330 },
331 "polygon" if flags.contains(AllowedBasicShapes::POLYGON) => {
332 Polygon::parse_function_arguments(context, i, shape_type)
333 .map(BasicShape::Polygon)
334 },
335 "path" if flags.contains(AllowedBasicShapes::PATH) => {
336 Path::parse_function_arguments(i, shape_type)
337 .map(PathOrShapeFunction::Path)
338 .map(BasicShape::PathOrShape)
339 },
340 "shape"
341 if flags.contains(AllowedBasicShapes::SHAPE)
342 && static_prefs::pref!("layout.css.basic-shape-shape.enabled") =>
343 {
344 generic::Shape::parse_function_arguments(context, i, shape_type)
345 .map(PathOrShapeFunction::Shape)
346 .map(BasicShape::PathOrShape)
347 },
348 _ => Err(location
349 .new_custom_error(StyleParseErrorKind::UnexpectedFunction(function.clone()))),
350 }
351 })
352 }
353}
354
355impl Parse for InsetRect {
356 fn parse<'i, 't>(
357 context: &ParserContext,
358 input: &mut Parser<'i, 't>,
359 ) -> Result<Self, ParseError<'i>> {
360 input.expect_function_matching("inset")?;
361 input.parse_nested_block(|i| Self::parse_function_arguments(context, i))
362 }
363}
364
365fn parse_round<'i, 't>(
366 context: &ParserContext,
367 input: &mut Parser<'i, 't>,
368) -> Result<BorderRadius, ParseError<'i>> {
369 if input
370 .try_parse(|i| i.expect_ident_matching("round"))
371 .is_ok()
372 {
373 return BorderRadius::parse(context, input);
374 }
375
376 Ok(BorderRadius::zero())
377}
378
379impl InsetRect {
380 fn parse_function_arguments<'i, 't>(
382 context: &ParserContext,
383 input: &mut Parser<'i, 't>,
384 ) -> Result<Self, ParseError<'i>> {
385 let rect = Rect::parse_with(context, input, LengthPercentage::parse)?;
386 let round = parse_round(context, input)?;
387 Ok(generic::InsetRect { rect, round })
388 }
389}
390
391impl ToCss for RadialPosition {
392 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
393 where
394 W: Write,
395 {
396 self.horizontal.to_css(dest)?;
397 dest.write_char(' ')?;
398 self.vertical.to_css(dest)
399 }
400}
401
402fn convert_to_length_percentage<S: Side>(c: PositionComponent<S>) -> LengthPercentage {
403 use crate::values::specified::{AllowedNumericType, Percentage};
404 match c {
408 PositionComponent::Center => LengthPercentage::from(Percentage::new(0.5)),
411 PositionComponent::Side(keyword, None) => {
412 Percentage::new(if keyword.is_start() { 0. } else { 1. }).into()
413 },
414 PositionComponent::Side(keyword, Some(length)) => {
421 if keyword.is_start() {
422 length
423 } else {
424 length.hundred_percent_minus(AllowedNumericType::All)
425 }
426 },
427 PositionComponent::Length(length) => length,
428 }
429}
430
431fn parse_at_position<'i, 't>(
432 context: &ParserContext,
433 input: &mut Parser<'i, 't>,
434) -> Result<GenericPositionOrAuto<RadialPosition>, ParseError<'i>> {
435 use crate::values::specified::position::Position;
436 if input.try_parse(|i| i.expect_ident_matching("at")).is_ok() {
437 Position::parse(context, input).map(|pos| {
438 GenericPositionOrAuto::Position(RadialPosition::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, Position, 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 point: generic::CommandEndPoint::parse_endpoint_as_abs(context, i)?,
761 })
762 } else {
763 ShapeCommand::parse(context, i)
765 }
766 })?;
767
768 if commands.len() < 2 {
770 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
771 }
772
773 Ok(Self {
774 fill,
775 commands: commands.into(),
776 })
777 }
778}
779
780impl Parse for ShapeCommand {
781 fn parse<'i, 't>(
782 context: &ParserContext,
783 input: &mut Parser<'i, 't>,
784 ) -> Result<Self, ParseError<'i>> {
785 use crate::values::generics::basic_shape::{
786 ArcRadii, ArcSize, ArcSweep, AxisEndPoint, CommandEndPoint, ControlPoint,
787 };
788
789 Ok(try_match_ident_ignore_ascii_case! { input,
792 "close" => Self::Close,
793 "move" => {
794 let point = CommandEndPoint::parse(context, input)?;
795 Self::Move { point }
796 },
797 "line" => {
798 let point = CommandEndPoint::parse(context, input)?;
799 Self::Line { point }
800 },
801 "hline" => {
802 let x = AxisEndPoint::parse_hline(context, input)?;
803 Self::HLine { x }
804 },
805 "vline" => {
806 let y = AxisEndPoint::parse_vline(context, input)?;
807 Self::VLine { y }
808 },
809 "curve" => {
810 let point = CommandEndPoint::parse(context, input)?;
811 input.expect_ident_matching("with")?;
812 let control1 = ControlPoint::parse(context, input, point.is_abs())?;
813 if input.try_parse(|i| i.expect_delim('/')).is_ok() {
814 let control2 = ControlPoint::parse(context, input, point.is_abs())?;
815 Self::CubicCurve {
816 point,
817 control1,
818 control2,
819 }
820 } else {
821 Self::QuadCurve {
822 point,
823 control1,
824 }
825 }
826 },
827 "smooth" => {
828 let point = CommandEndPoint::parse(context, input)?;
829 if input.try_parse(|i| i.expect_ident_matching("with")).is_ok() {
830 let control2 = ControlPoint::parse(context, input, point.is_abs())?;
831 Self::SmoothCubic {
832 point,
833 control2,
834 }
835 } else {
836 Self::SmoothQuad { point }
837 }
838 },
839 "arc" => {
840 let point = CommandEndPoint::parse(context, input)?;
841 input.expect_ident_matching("of")?;
842 let rx = LengthPercentage::parse(context, input)?;
843 let ry = input.try_parse(|i| LengthPercentage::parse(context, i)).ok();
844 let radii = ArcRadii { rx, ry: ry.into() };
845
846 let mut arc_sweep = None;
848 let mut arc_size = None;
849 let mut rotate = None;
850 loop {
851 if arc_sweep.is_none() {
852 arc_sweep = input.try_parse(ArcSweep::parse).ok();
853 }
854
855 if arc_size.is_none() {
856 arc_size = input.try_parse(ArcSize::parse).ok();
857 if arc_size.is_some() {
858 continue;
859 }
860 }
861
862 if rotate.is_none()
863 && input
864 .try_parse(|i| i.expect_ident_matching("rotate"))
865 .is_ok()
866 {
867 rotate = Some(Angle::parse(context, input)?);
868 continue;
869 }
870 break;
871 }
872 Self::Arc {
873 point,
874 radii,
875 arc_sweep: arc_sweep.unwrap_or(ArcSweep::Ccw),
876 arc_size: arc_size.unwrap_or(ArcSize::Small),
877 rotate: rotate.unwrap_or(Angle::zero()),
878 }
879 },
880 })
881 }
882}
883
884impl Parse for generic::CoordinatePair<LengthPercentage> {
885 fn parse<'i, 't>(
886 context: &ParserContext,
887 input: &mut Parser<'i, 't>,
888 ) -> Result<Self, ParseError<'i>> {
889 let x = LengthPercentage::parse(context, input)?;
890 let y = LengthPercentage::parse(context, input)?;
891 Ok(Self::new(x, y))
892 }
893}
894
895impl generic::ControlPoint<Position, LengthPercentage> {
896 fn parse<'i, 't>(
898 context: &ParserContext,
899 input: &mut Parser<'i, 't>,
900 is_end_point_abs: bool,
901 ) -> Result<Self, ParseError<'i>> {
902 use generic::ControlReference;
903 let coord = input.try_parse(|i| generic::CoordinatePair::parse(context, i));
904
905 if is_end_point_abs && coord.is_err() {
907 let pos = Position::parse(context, input)?;
908 return Ok(Self::Absolute(pos));
909 }
910
911 let coord = coord?;
913 let mut reference = if is_end_point_abs {
914 ControlReference::Origin
915 } else {
916 ControlReference::Start
917 };
918 if input.try_parse(|i| i.expect_ident_matching("from")).is_ok() {
919 reference = ControlReference::parse(input)?;
920 }
921
922 Ok(Self::Relative(generic::RelativeControlPoint {
923 coord,
924 reference,
925 }))
926 }
927}
928
929impl Parse for generic::CommandEndPoint<Position, LengthPercentage> {
930 fn parse<'i, 't>(
932 context: &ParserContext,
933 input: &mut Parser<'i, 't>,
934 ) -> Result<Self, ParseError<'i>> {
935 if ByTo::parse(input)?.is_abs() {
936 Self::parse_endpoint_as_abs(context, input)
937 } else {
938 let point = generic::CoordinatePair::parse(context, input)?;
939 Ok(Self::ByCoordinate(point))
940 }
941 }
942}
943
944impl generic::CommandEndPoint<Position, LengthPercentage> {
945 fn parse_endpoint_as_abs<'i, 't>(
947 context: &ParserContext,
948 input: &mut Parser<'i, 't>,
949 ) -> Result<Self, ParseError<'i>> {
950 let point = Position::parse(context, input)?;
951 Ok(generic::CommandEndPoint::ToPosition(point))
952 }
953}
954
955impl generic::AxisEndPoint<LengthPercentage> {
956 pub fn parse_hline<'i, 't>(
958 context: &ParserContext,
959 input: &mut Parser<'i, 't>,
960 ) -> Result<Self, ParseError<'i>> {
961 use cssparser::Token;
962 use generic::{AxisPosition, AxisPositionKeyword};
963
964 if !ByTo::parse(input)?.is_abs() {
966 return Ok(Self::ByCoordinate(LengthPercentage::parse(context, input)?));
967 }
968
969 let x = AxisPosition::parse(context, input)?;
970 if let AxisPosition::Keyword(
971 _word @ (AxisPositionKeyword::Top
972 | AxisPositionKeyword::Bottom
973 | AxisPositionKeyword::YStart
974 | AxisPositionKeyword::YEnd),
975 ) = &x
976 {
977 let location = input.current_source_location();
978 let token = Token::Ident(x.to_css_string().into());
979 return Err(location.new_unexpected_token_error(token));
980 }
981 Ok(Self::ToPosition(x))
982 }
983
984 pub fn parse_vline<'i, 't>(
986 context: &ParserContext,
987 input: &mut Parser<'i, 't>,
988 ) -> Result<Self, ParseError<'i>> {
989 use cssparser::Token;
990 use generic::{AxisPosition, AxisPositionKeyword};
991
992 if !ByTo::parse(input)?.is_abs() {
994 return Ok(Self::ByCoordinate(LengthPercentage::parse(context, input)?));
995 }
996
997 let y = AxisPosition::parse(context, input)?;
998 if let AxisPosition::Keyword(
999 _word @ (AxisPositionKeyword::Left
1000 | AxisPositionKeyword::Right
1001 | AxisPositionKeyword::XStart
1002 | AxisPositionKeyword::XEnd),
1003 ) = &y
1004 {
1005 let location = input.current_source_location();
1007 let token = Token::Ident(y.to_css_string().into());
1008 return Err(location.new_unexpected_token_error(token));
1009 }
1010 Ok(Self::ToPosition(y))
1011 }
1012}
1013
1014impl ToComputedValue for generic::AxisPosition<LengthPercentage> {
1015 type ComputedValue = generic::AxisPosition<ComputedLengthPercentage>;
1016
1017 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
1018 match self {
1019 Self::LengthPercent(lp) => {
1020 Self::ComputedValue::LengthPercent(lp.to_computed_value(context))
1021 },
1022 Self::Keyword(word) => {
1023 let lp = LengthPercentage::Percentage(word.as_percentage());
1024 Self::ComputedValue::LengthPercent(lp.to_computed_value(context))
1025 },
1026 }
1027 }
1028
1029 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
1030 match computed {
1031 Self::ComputedValue::LengthPercent(lp) => {
1032 Self::LengthPercent(LengthPercentage::from_computed_value(lp))
1033 },
1034 _ => unreachable!("Invalid state: computed value cannot be a keyword."),
1035 }
1036 }
1037}
1038
1039impl ToComputedValue for generic::AxisPosition<CSSFloat> {
1040 type ComputedValue = Self;
1041
1042 fn to_computed_value(&self, _context: &Context) -> Self {
1043 *self
1044 }
1045
1046 fn from_computed_value(computed: &Self) -> Self {
1047 *computed
1048 }
1049}
1050
1051#[derive(Clone, Copy, Debug, Parse, PartialEq)]
1054enum ByTo {
1055 By,
1057 To,
1059}
1060
1061impl ByTo {
1062 #[inline]
1064 pub fn is_abs(&self) -> bool {
1065 matches!(self, ByTo::To)
1066 }
1067}