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;
25use crate::values::specified::url::SpecifiedUrl;
26use crate::values::specified::{LengthPercentage, NonNegativeLengthPercentage, SVGPathData};
27use crate::values::CSSFloat;
28use crate::Zero;
29use cssparser::{match_ignore_ascii_case, Parser};
30use std::fmt::{self, Write};
31use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
32
33pub use crate::values::generics::basic_shape::FillRule;
35
36pub type ClipPath = generic::GenericClipPath<BasicShape, SpecifiedUrl>;
38
39pub type ShapeOutside = generic::GenericShapeOutside<BasicShape, Image>;
41
42pub type BasicShape = generic::GenericBasicShape<Angle, Position, LengthPercentage, BasicShapeRect>;
44
45pub type InsetRect = generic::GenericInsetRect<LengthPercentage>;
47
48pub type Circle = generic::Circle<Position, LengthPercentage>;
50
51pub type Ellipse = generic::Ellipse<Position, LengthPercentage>;
53
54pub type ShapeRadius = generic::ShapeRadius<LengthPercentage>;
56
57pub type Polygon = generic::GenericPolygon<LengthPercentage>;
59
60pub type PathOrShapeFunction =
62 generic::GenericPathOrShapeFunction<Angle, Position, LengthPercentage>;
63
64pub type ShapeCommand = generic::GenericShapeCommand<Angle, Position, LengthPercentage>;
66
67#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToShmem)]
77pub struct Xywh {
78 pub x: LengthPercentage,
80 pub y: LengthPercentage,
82 pub width: NonNegativeLengthPercentage,
84 pub height: NonNegativeLengthPercentage,
86 pub round: BorderRadius,
89}
90
91#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToShmem)]
95#[repr(C)]
96pub struct ShapeRectFunction {
97 pub rect: Rect<LengthPercentageOrAuto>,
106 pub round: BorderRadius,
109}
110
111#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
116pub enum BasicShapeRect {
117 Inset(InsetRect),
119 #[css(function)]
121 Xywh(Xywh),
122 #[css(function)]
124 Rect(ShapeRectFunction),
125}
126
127pub enum ShapeType {
134 Filled,
136 Outline,
138}
139
140bitflags! {
141 #[derive(Clone, Copy)]
158 #[repr(C)]
159 pub struct AllowedBasicShapes: u8 {
160 const INSET = 1 << 0;
162 const XYWH = 1 << 1;
164 const RECT = 1 << 2;
166 const CIRCLE = 1 << 3;
168 const ELLIPSE = 1 << 4;
170 const POLYGON = 1 << 5;
172 const PATH = 1 << 6;
174 const SHAPE = 1 << 7;
176
177 const ALL =
179 Self::INSET.bits() |
180 Self::XYWH.bits() |
181 Self::RECT.bits() |
182 Self::CIRCLE.bits() |
183 Self::ELLIPSE.bits() |
184 Self::POLYGON.bits() |
185 Self::PATH.bits() |
186 Self::SHAPE.bits();
187
188 const SHAPE_OUTSIDE =
190 Self::INSET.bits() |
191 Self::XYWH.bits() |
192 Self::RECT.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
387fn parse_at_position<'i, 't>(
388 context: &ParserContext,
389 input: &mut Parser<'i, 't>,
390) -> Result<GenericPositionOrAuto<Position>, ParseError<'i>> {
391 if input.try_parse(|i| i.expect_ident_matching("at")).is_ok() {
392 Position::parse(context, input).map(GenericPositionOrAuto::Position)
393 } else {
394 Ok(GenericPositionOrAuto::Auto)
395 }
396}
397
398impl Parse for Circle {
399 fn parse<'i, 't>(
400 context: &ParserContext,
401 input: &mut Parser<'i, 't>,
402 ) -> Result<Self, ParseError<'i>> {
403 input.expect_function_matching("circle")?;
404 input.parse_nested_block(|i| Self::parse_function_arguments(context, i))
405 }
406}
407
408impl Circle {
409 fn parse_function_arguments<'i, 't>(
410 context: &ParserContext,
411 input: &mut Parser<'i, 't>,
412 ) -> Result<Self, ParseError<'i>> {
413 let radius = input
414 .try_parse(|i| ShapeRadius::parse(context, i))
415 .unwrap_or_default();
416 let position = parse_at_position(context, input)?;
417
418 Ok(generic::Circle { radius, position })
419 }
420}
421
422impl Parse for Ellipse {
423 fn parse<'i, 't>(
424 context: &ParserContext,
425 input: &mut Parser<'i, 't>,
426 ) -> Result<Self, ParseError<'i>> {
427 input.expect_function_matching("ellipse")?;
428 input.parse_nested_block(|i| Self::parse_function_arguments(context, i))
429 }
430}
431
432impl Ellipse {
433 fn parse_function_arguments<'i, 't>(
434 context: &ParserContext,
435 input: &mut Parser<'i, 't>,
436 ) -> Result<Self, ParseError<'i>> {
437 let (semiaxis_x, semiaxis_y) = input
438 .try_parse(|i| -> Result<_, ParseError> {
439 Ok((
440 ShapeRadius::parse(context, i)?,
441 ShapeRadius::parse(context, i)?,
442 ))
443 })
444 .unwrap_or_default();
445 let position = parse_at_position(context, input)?;
446
447 Ok(generic::Ellipse {
448 semiaxis_x,
449 semiaxis_y,
450 position,
451 })
452 }
453}
454
455fn parse_fill_rule<'i, 't>(
456 input: &mut Parser<'i, 't>,
457 shape_type: ShapeType,
458 expect_comma: bool,
459) -> FillRule {
460 match shape_type {
461 ShapeType::Outline => Default::default(),
474 ShapeType::Filled => input
475 .try_parse(|i| -> Result<_, ParseError> {
476 let fill = FillRule::parse(i)?;
477 if expect_comma {
478 i.expect_comma()?;
479 }
480 Ok(fill)
481 })
482 .unwrap_or_default(),
483 }
484}
485
486impl Parse for Polygon {
487 fn parse<'i, 't>(
488 context: &ParserContext,
489 input: &mut Parser<'i, 't>,
490 ) -> Result<Self, ParseError<'i>> {
491 input.expect_function_matching("polygon")?;
492 input.parse_nested_block(|i| Self::parse_function_arguments(context, i, ShapeType::Filled))
493 }
494}
495
496impl Polygon {
497 fn parse_function_arguments<'i, 't>(
499 context: &ParserContext,
500 input: &mut Parser<'i, 't>,
501 shape_type: ShapeType,
502 ) -> Result<Self, ParseError<'i>> {
503 let fill = parse_fill_rule(input, shape_type, true );
504 let coordinates = input
505 .parse_comma_separated(|i| {
506 Ok(PolygonCoord(
507 LengthPercentage::parse(context, i)?,
508 LengthPercentage::parse(context, i)?,
509 ))
510 })?
511 .into();
512
513 Ok(Polygon { fill, coordinates })
514 }
515}
516
517impl Path {
518 fn parse_function_arguments<'i, 't>(
520 input: &mut Parser<'i, 't>,
521 shape_type: ShapeType,
522 ) -> Result<Self, ParseError<'i>> {
523 use crate::values::specified::svg_path::AllowEmpty;
524
525 let fill = parse_fill_rule(input, shape_type, true );
526 let path = SVGPathData::parse(input, AllowEmpty::No)?;
527 Ok(Path { fill, path })
528 }
529}
530
531fn round_to_css<W>(round: &BorderRadius, dest: &mut CssWriter<W>) -> fmt::Result
532where
533 W: Write,
534{
535 if !round.is_zero() {
536 dest.write_str(" round ")?;
537 round.to_css(dest)?;
538 }
539 Ok(())
540}
541
542impl ToCss for Xywh {
543 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
544 where
545 W: Write,
546 {
547 self.x.to_css(dest)?;
548 dest.write_char(' ')?;
549 self.y.to_css(dest)?;
550 dest.write_char(' ')?;
551 self.width.to_css(dest)?;
552 dest.write_char(' ')?;
553 self.height.to_css(dest)?;
554 round_to_css(&self.round, dest)
555 }
556}
557
558impl Xywh {
559 fn parse_function_arguments<'i, 't>(
561 context: &ParserContext,
562 input: &mut Parser<'i, 't>,
563 ) -> Result<Self, ParseError<'i>> {
564 let x = LengthPercentage::parse(context, input)?;
565 let y = LengthPercentage::parse(context, input)?;
566 let width = NonNegativeLengthPercentage::parse(context, input)?;
567 let height = NonNegativeLengthPercentage::parse(context, input)?;
568 let round = parse_round(context, input)?;
569 Ok(Xywh {
570 x,
571 y,
572 width,
573 height,
574 round,
575 })
576 }
577}
578
579impl ToCss for ShapeRectFunction {
580 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
581 where
582 W: Write,
583 {
584 self.rect.0.to_css(dest)?;
585 dest.write_char(' ')?;
586 self.rect.1.to_css(dest)?;
587 dest.write_char(' ')?;
588 self.rect.2.to_css(dest)?;
589 dest.write_char(' ')?;
590 self.rect.3.to_css(dest)?;
591 round_to_css(&self.round, dest)
592 }
593}
594
595impl ShapeRectFunction {
596 fn parse_function_arguments<'i, 't>(
598 context: &ParserContext,
599 input: &mut Parser<'i, 't>,
600 ) -> Result<Self, ParseError<'i>> {
601 let rect = Rect::parse_all_components_with(context, input, LengthPercentageOrAuto::parse)?;
602 let round = parse_round(context, input)?;
603 Ok(ShapeRectFunction { rect, round })
604 }
605}
606
607impl ToComputedValue for BasicShapeRect {
608 type ComputedValue = ComputedInsetRect;
609
610 #[inline]
611 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
612 use crate::values::computed::LengthPercentage;
613 use crate::values::computed::LengthPercentageOrAuto;
614 use style_traits::values::specified::AllowedNumericType;
615
616 match self {
617 Self::Inset(ref inset) => inset.to_computed_value(context),
618 Self::Xywh(ref xywh) => {
619 let x = xywh.x.to_computed_value(context);
625 let y = xywh.y.to_computed_value(context);
626 let w = xywh.width.to_computed_value(context);
627 let h = xywh.height.to_computed_value(context);
628 let right = LengthPercentage::hundred_percent_minus_list(
630 &[&x, &w.0],
631 AllowedNumericType::All,
632 );
633 let bottom = LengthPercentage::hundred_percent_minus_list(
635 &[&y, &h.0],
636 AllowedNumericType::All,
637 );
638
639 ComputedInsetRect {
640 rect: Rect::new(y, right, bottom, x),
641 round: xywh.round.to_computed_value(context),
642 }
643 },
644 Self::Rect(ref rect) => {
645 fn compute_top_or_left(v: LengthPercentageOrAuto) -> LengthPercentage {
650 match v {
651 LengthPercentageOrAuto::Auto => LengthPercentage::zero_percent(),
654 LengthPercentageOrAuto::LengthPercentage(lp) => lp,
655 }
656 }
657 fn compute_bottom_or_right(v: LengthPercentageOrAuto) -> LengthPercentage {
658 match v {
659 LengthPercentageOrAuto::Auto => LengthPercentage::zero_percent(),
663 LengthPercentageOrAuto::LengthPercentage(lp) => {
664 LengthPercentage::hundred_percent_minus(lp, AllowedNumericType::All)
665 },
666 }
667 }
668
669 let round = rect.round.to_computed_value(context);
670 let rect = rect.rect.to_computed_value(context);
671 let rect = Rect::new(
672 compute_top_or_left(rect.0),
673 compute_bottom_or_right(rect.1),
674 compute_bottom_or_right(rect.2),
675 compute_top_or_left(rect.3),
676 );
677
678 ComputedInsetRect { rect, round }
679 },
680 }
681 }
682
683 #[inline]
684 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
685 Self::Inset(ToComputedValue::from_computed_value(computed))
686 }
687}
688
689impl generic::Shape<Angle, Position, LengthPercentage> {
690 fn parse_function_arguments<'i, 't>(
693 context: &ParserContext,
694 input: &mut Parser<'i, 't>,
695 shape_type: ShapeType,
696 ) -> Result<Self, ParseError<'i>> {
697 let fill = parse_fill_rule(input, shape_type, false );
698
699 let mut first = true;
700 let commands = input.parse_comma_separated(|i| {
701 if first {
702 first = false;
703
704 i.expect_ident_matching("from")?;
708 Ok(ShapeCommand::Move {
709 point: generic::CommandEndPoint::parse_endpoint_as_abs(context, i)?,
710 })
711 } else {
712 ShapeCommand::parse(context, i)
714 }
715 })?;
716
717 if commands.len() < 2 {
719 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
720 }
721
722 Ok(Self {
723 fill,
724 commands: commands.into(),
725 })
726 }
727}
728
729impl Parse for ShapeCommand {
730 fn parse<'i, 't>(
731 context: &ParserContext,
732 input: &mut Parser<'i, 't>,
733 ) -> Result<Self, ParseError<'i>> {
734 use crate::values::generics::basic_shape::{
735 ArcRadii, ArcSize, ArcSweep, AxisEndPoint, CommandEndPoint, ControlPoint,
736 };
737
738 Ok(try_match_ident_ignore_ascii_case! { input,
741 "close" => Self::Close,
742 "move" => {
743 let point = CommandEndPoint::parse(context, input)?;
744 Self::Move { point }
745 },
746 "line" => {
747 let point = CommandEndPoint::parse(context, input)?;
748 Self::Line { point }
749 },
750 "hline" => {
751 let x = AxisEndPoint::parse_hline(context, input)?;
752 Self::HLine { x }
753 },
754 "vline" => {
755 let y = AxisEndPoint::parse_vline(context, input)?;
756 Self::VLine { y }
757 },
758 "curve" => {
759 let point = CommandEndPoint::parse(context, input)?;
760 input.expect_ident_matching("with")?;
761 let control1 = ControlPoint::parse(context, input, point.is_abs())?;
762 if input.try_parse(|i| i.expect_delim('/')).is_ok() {
763 let control2 = ControlPoint::parse(context, input, point.is_abs())?;
764 Self::CubicCurve {
765 point,
766 control1,
767 control2,
768 }
769 } else {
770 Self::QuadCurve {
771 point,
772 control1,
773 }
774 }
775 },
776 "smooth" => {
777 let point = CommandEndPoint::parse(context, input)?;
778 if input.try_parse(|i| i.expect_ident_matching("with")).is_ok() {
779 let control2 = ControlPoint::parse(context, input, point.is_abs())?;
780 Self::SmoothCubic {
781 point,
782 control2,
783 }
784 } else {
785 Self::SmoothQuad { point }
786 }
787 },
788 "arc" => {
789 let point = CommandEndPoint::parse(context, input)?;
790 input.expect_ident_matching("of")?;
791 let rx = LengthPercentage::parse(context, input)?;
792 let ry = input.try_parse(|i| LengthPercentage::parse(context, i)).ok();
793 let radii = ArcRadii { rx, ry: ry.into() };
794
795 let mut arc_sweep = None;
797 let mut arc_size = None;
798 let mut rotate = None;
799 loop {
800 if arc_sweep.is_none() {
801 arc_sweep = input.try_parse(ArcSweep::parse).ok();
802 }
803
804 if arc_size.is_none() {
805 arc_size = input.try_parse(ArcSize::parse).ok();
806 if arc_size.is_some() {
807 continue;
808 }
809 }
810
811 if rotate.is_none()
812 && input
813 .try_parse(|i| i.expect_ident_matching("rotate"))
814 .is_ok()
815 {
816 rotate = Some(Angle::parse(context, input)?);
817 continue;
818 }
819 break;
820 }
821 Self::Arc {
822 point,
823 radii,
824 arc_sweep: arc_sweep.unwrap_or(ArcSweep::Ccw),
825 arc_size: arc_size.unwrap_or(ArcSize::Small),
826 rotate: rotate.unwrap_or(Angle::zero()),
827 }
828 },
829 })
830 }
831}
832
833impl Parse for generic::CoordinatePair<LengthPercentage> {
834 fn parse<'i, 't>(
835 context: &ParserContext,
836 input: &mut Parser<'i, 't>,
837 ) -> Result<Self, ParseError<'i>> {
838 let x = LengthPercentage::parse(context, input)?;
839 let y = LengthPercentage::parse(context, input)?;
840 Ok(Self::new(x, y))
841 }
842}
843
844impl generic::ControlPoint<Position, LengthPercentage> {
845 fn parse<'i, 't>(
847 context: &ParserContext,
848 input: &mut Parser<'i, 't>,
849 is_end_point_abs: bool,
850 ) -> Result<Self, ParseError<'i>> {
851 use generic::ControlReference;
852 let coord = input.try_parse(|i| generic::CoordinatePair::parse(context, i));
853
854 if is_end_point_abs && coord.is_err() {
856 let pos = Position::parse(context, input)?;
857 return Ok(Self::Absolute(pos));
858 }
859
860 let coord = coord?;
862 let mut reference = if is_end_point_abs {
863 ControlReference::Origin
864 } else {
865 ControlReference::Start
866 };
867 if input.try_parse(|i| i.expect_ident_matching("from")).is_ok() {
868 reference = ControlReference::parse(input)?;
869 }
870
871 Ok(Self::Relative(generic::RelativeControlPoint {
872 coord,
873 reference,
874 }))
875 }
876}
877
878impl Parse for generic::CommandEndPoint<Position, LengthPercentage> {
879 fn parse<'i, 't>(
881 context: &ParserContext,
882 input: &mut Parser<'i, 't>,
883 ) -> Result<Self, ParseError<'i>> {
884 if ByTo::parse(input)?.is_abs() {
885 Self::parse_endpoint_as_abs(context, input)
886 } else {
887 let point = generic::CoordinatePair::parse(context, input)?;
888 Ok(Self::ByCoordinate(point))
889 }
890 }
891}
892
893impl generic::CommandEndPoint<Position, LengthPercentage> {
894 fn parse_endpoint_as_abs<'i, 't>(
896 context: &ParserContext,
897 input: &mut Parser<'i, 't>,
898 ) -> Result<Self, ParseError<'i>> {
899 let point = Position::parse(context, input)?;
900 Ok(generic::CommandEndPoint::ToPosition(point))
901 }
902}
903
904impl generic::AxisEndPoint<LengthPercentage> {
905 pub fn parse_hline<'i, 't>(
907 context: &ParserContext,
908 input: &mut Parser<'i, 't>,
909 ) -> Result<Self, ParseError<'i>> {
910 use cssparser::Token;
911 use generic::{AxisPosition, AxisPositionKeyword};
912
913 if !ByTo::parse(input)?.is_abs() {
915 return Ok(Self::ByCoordinate(LengthPercentage::parse(context, input)?));
916 }
917
918 let x = AxisPosition::parse(context, input)?;
919 if let AxisPosition::Keyword(
920 _word @ (AxisPositionKeyword::Top
921 | AxisPositionKeyword::Bottom
922 | AxisPositionKeyword::YStart
923 | AxisPositionKeyword::YEnd),
924 ) = &x
925 {
926 let location = input.current_source_location();
927 let token = Token::Ident(x.to_css_string().into());
928 return Err(location.new_unexpected_token_error(token));
929 }
930 Ok(Self::ToPosition(x))
931 }
932
933 pub fn parse_vline<'i, 't>(
935 context: &ParserContext,
936 input: &mut Parser<'i, 't>,
937 ) -> Result<Self, ParseError<'i>> {
938 use cssparser::Token;
939 use generic::{AxisPosition, AxisPositionKeyword};
940
941 if !ByTo::parse(input)?.is_abs() {
943 return Ok(Self::ByCoordinate(LengthPercentage::parse(context, input)?));
944 }
945
946 let y = AxisPosition::parse(context, input)?;
947 if let AxisPosition::Keyword(
948 _word @ (AxisPositionKeyword::Left
949 | AxisPositionKeyword::Right
950 | AxisPositionKeyword::XStart
951 | AxisPositionKeyword::XEnd),
952 ) = &y
953 {
954 let location = input.current_source_location();
956 let token = Token::Ident(y.to_css_string().into());
957 return Err(location.new_unexpected_token_error(token));
958 }
959 Ok(Self::ToPosition(y))
960 }
961}
962
963impl ToComputedValue for generic::AxisPosition<LengthPercentage> {
964 type ComputedValue = generic::AxisPosition<ComputedLengthPercentage>;
965
966 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
967 match self {
968 Self::LengthPercent(lp) => {
969 Self::ComputedValue::LengthPercent(lp.to_computed_value(context))
970 },
971 Self::Keyword(word) => {
972 let lp = LengthPercentage::Percentage(word.as_percentage());
973 Self::ComputedValue::LengthPercent(lp.to_computed_value(context))
974 },
975 }
976 }
977
978 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
979 match computed {
980 Self::ComputedValue::LengthPercent(lp) => {
981 Self::LengthPercent(LengthPercentage::from_computed_value(lp))
982 },
983 _ => unreachable!("Invalid state: computed value cannot be a keyword."),
984 }
985 }
986}
987
988impl ToComputedValue for generic::AxisPosition<CSSFloat> {
989 type ComputedValue = Self;
990
991 fn to_computed_value(&self, _context: &Context) -> Self {
992 *self
993 }
994
995 fn from_computed_value(computed: &Self) -> Self {
996 *computed
997 }
998}
999
1000#[derive(Clone, Copy, Debug, Parse, PartialEq)]
1003enum ByTo {
1004 By,
1006 To,
1008}
1009
1010impl ByTo {
1011 #[inline]
1013 pub fn is_abs(&self) -> bool {
1014 matches!(self, ByTo::To)
1015 }
1016}