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::{
27 LengthPercentage, NoCalcPercentage, NonNegativeLengthPercentage, SVGPathData,
28};
29use crate::values::CSSFloat;
30use crate::Zero;
31use cssparser::{match_ignore_ascii_case, Parser};
32use std::fmt::{self, Write};
33use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
34
35pub use crate::values::generics::basic_shape::FillRule;
37
38pub type ClipPath = generic::GenericClipPath<BasicShape, SpecifiedUrl>;
40
41pub type ShapeOutside = generic::GenericShapeOutside<BasicShape, Image>;
43
44pub type BasicShape = generic::GenericBasicShape<Angle, Position, LengthPercentage, BasicShapeRect>;
46
47pub type InsetRect = generic::GenericInsetRect<LengthPercentage>;
49
50pub type Circle = generic::Circle<Position, LengthPercentage>;
52
53pub type Ellipse = generic::Ellipse<Position, 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::XYWH.bits() |
194 Self::RECT.bits() |
195 Self::CIRCLE.bits() |
196 Self::ELLIPSE.bits() |
197 Self::POLYGON.bits();
198 }
199}
200
201fn parse_shape_or_box<'i, 't, R, ReferenceBox>(
203 context: &ParserContext,
204 input: &mut Parser<'i, 't>,
205 to_shape: impl FnOnce(Box<BasicShape>, ReferenceBox) -> R,
206 to_reference_box: impl FnOnce(ReferenceBox) -> R,
207 flags: AllowedBasicShapes,
208) -> Result<R, ParseError<'i>>
209where
210 ReferenceBox: Default + Parse,
211{
212 let mut shape = None;
213 let mut ref_box = None;
214 loop {
215 if shape.is_none() {
216 shape = input
217 .try_parse(|i| BasicShape::parse(context, i, flags, ShapeType::Filled))
218 .ok();
219 }
220
221 if ref_box.is_none() {
222 ref_box = input.try_parse(|i| ReferenceBox::parse(context, i)).ok();
223 if ref_box.is_some() {
224 continue;
225 }
226 }
227 break;
228 }
229
230 if let Some(shp) = shape {
231 return Ok(to_shape(Box::new(shp), ref_box.unwrap_or_default()));
232 }
233
234 match ref_box {
235 Some(r) => Ok(to_reference_box(r)),
236 None => Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
237 }
238}
239
240impl Parse for ClipPath {
241 #[inline]
242 fn parse<'i, 't>(
243 context: &ParserContext,
244 input: &mut Parser<'i, 't>,
245 ) -> Result<Self, ParseError<'i>> {
246 if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
247 return Ok(ClipPath::None);
248 }
249
250 if let Ok(url) = input.try_parse(|i| SpecifiedUrl::parse(context, i)) {
251 return Ok(ClipPath::Url(url));
252 }
253
254 parse_shape_or_box(
255 context,
256 input,
257 ClipPath::Shape,
258 ClipPath::Box,
259 AllowedBasicShapes::ALL,
260 )
261 }
262}
263
264impl Parse for ShapeOutside {
265 #[inline]
266 fn parse<'i, 't>(
267 context: &ParserContext,
268 input: &mut Parser<'i, 't>,
269 ) -> Result<Self, ParseError<'i>> {
270 if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
273 return Ok(ShapeOutside::None);
274 }
275
276 if let Ok(image) = input.try_parse(|i| Image::parse_with_cors_anonymous(context, i)) {
277 debug_assert_ne!(image, Image::None);
278 return Ok(ShapeOutside::Image(image));
279 }
280
281 parse_shape_or_box(
282 context,
283 input,
284 ShapeOutside::Shape,
285 ShapeOutside::Box,
286 AllowedBasicShapes::SHAPE_OUTSIDE,
287 )
288 }
289}
290
291impl BasicShape {
292 pub fn parse<'i, 't>(
297 context: &ParserContext,
298 input: &mut Parser<'i, 't>,
299 flags: AllowedBasicShapes,
300 shape_type: ShapeType,
301 ) -> Result<Self, ParseError<'i>> {
302 let location = input.current_source_location();
303 let function = input.expect_function()?.clone();
304 input.parse_nested_block(move |i| {
305 match_ignore_ascii_case! { &function,
306 "inset" if flags.contains(AllowedBasicShapes::INSET) => {
307 InsetRect::parse_function_arguments(context, i)
308 .map(BasicShapeRect::Inset)
309 .map(BasicShape::Rect)
310 },
311 "xywh" if flags.contains(AllowedBasicShapes::XYWH) => {
312 Xywh::parse_function_arguments(context, i)
313 .map(BasicShapeRect::Xywh)
314 .map(BasicShape::Rect)
315 },
316 "rect" if flags.contains(AllowedBasicShapes::RECT) => {
317 ShapeRectFunction::parse_function_arguments(context, i)
318 .map(BasicShapeRect::Rect)
319 .map(BasicShape::Rect)
320 },
321 "circle" if flags.contains(AllowedBasicShapes::CIRCLE) => {
322 Circle::parse_function_arguments(context, i)
323 .map(BasicShape::Circle)
324 },
325 "ellipse" if flags.contains(AllowedBasicShapes::ELLIPSE) => {
326 Ellipse::parse_function_arguments(context, i)
327 .map(BasicShape::Ellipse)
328 },
329 "polygon" if flags.contains(AllowedBasicShapes::POLYGON) => {
330 Polygon::parse_function_arguments(context, i, shape_type)
331 .map(BasicShape::Polygon)
332 },
333 "path" if flags.contains(AllowedBasicShapes::PATH) => {
334 Path::parse_function_arguments(i, shape_type)
335 .map(PathOrShapeFunction::Path)
336 .map(BasicShape::PathOrShape)
337 },
338 "shape"
339 if flags.contains(AllowedBasicShapes::SHAPE)
340 && static_prefs::pref!("layout.css.basic-shape-shape.enabled") =>
341 {
342 generic::Shape::parse_function_arguments(context, i, shape_type)
343 .map(PathOrShapeFunction::Shape)
344 .map(BasicShape::PathOrShape)
345 },
346 _ => Err(location
347 .new_custom_error(StyleParseErrorKind::UnexpectedFunction(function.clone()))),
348 }
349 })
350 }
351}
352
353impl Parse for InsetRect {
354 fn parse<'i, 't>(
355 context: &ParserContext,
356 input: &mut Parser<'i, 't>,
357 ) -> Result<Self, ParseError<'i>> {
358 input.expect_function_matching("inset")?;
359 input.parse_nested_block(|i| Self::parse_function_arguments(context, i))
360 }
361}
362
363fn parse_round<'i, 't>(
364 context: &ParserContext,
365 input: &mut Parser<'i, 't>,
366) -> Result<BorderRadius, ParseError<'i>> {
367 if input
368 .try_parse(|i| i.expect_ident_matching("round"))
369 .is_ok()
370 {
371 return BorderRadius::parse(context, input);
372 }
373
374 Ok(BorderRadius::zero())
375}
376
377impl InsetRect {
378 fn parse_function_arguments<'i, 't>(
380 context: &ParserContext,
381 input: &mut Parser<'i, 't>,
382 ) -> Result<Self, ParseError<'i>> {
383 let rect = Rect::parse_with(context, input, LengthPercentage::parse)?;
384 let round = parse_round(context, input)?;
385 Ok(generic::InsetRect { rect, round })
386 }
387}
388
389fn parse_at_position<'i, 't>(
390 context: &ParserContext,
391 input: &mut Parser<'i, 't>,
392) -> Result<GenericPositionOrAuto<Position>, ParseError<'i>> {
393 if input.try_parse(|i| i.expect_ident_matching("at")).is_ok() {
394 Position::parse(context, input).map(GenericPositionOrAuto::Position)
395 } else {
396 Ok(GenericPositionOrAuto::Auto)
397 }
398}
399
400impl Parse for Circle {
401 fn parse<'i, 't>(
402 context: &ParserContext,
403 input: &mut Parser<'i, 't>,
404 ) -> Result<Self, ParseError<'i>> {
405 input.expect_function_matching("circle")?;
406 input.parse_nested_block(|i| Self::parse_function_arguments(context, i))
407 }
408}
409
410impl Circle {
411 fn parse_function_arguments<'i, 't>(
412 context: &ParserContext,
413 input: &mut Parser<'i, 't>,
414 ) -> Result<Self, ParseError<'i>> {
415 let radius = input
416 .try_parse(|i| ShapeRadius::parse(context, i))
417 .unwrap_or_default();
418 let position = parse_at_position(context, input)?;
419
420 Ok(generic::Circle { radius, position })
421 }
422}
423
424impl Parse for Ellipse {
425 fn parse<'i, 't>(
426 context: &ParserContext,
427 input: &mut Parser<'i, 't>,
428 ) -> Result<Self, ParseError<'i>> {
429 input.expect_function_matching("ellipse")?;
430 input.parse_nested_block(|i| Self::parse_function_arguments(context, i))
431 }
432}
433
434impl Ellipse {
435 fn parse_function_arguments<'i, 't>(
436 context: &ParserContext,
437 input: &mut Parser<'i, 't>,
438 ) -> Result<Self, ParseError<'i>> {
439 let (semiaxis_x, semiaxis_y) = input
440 .try_parse(|i| -> Result<_, ParseError> {
441 let s_x = ShapeRadius::parse(context, i)?;
442 let s_y = ShapeRadius::parse(context, i)?;
443 if !static_prefs::pref!("layout.css.ellipse-corners.enabled")
444 && (matches!(
445 s_x,
446 ShapeRadius::ClosestCorner | ShapeRadius::FarthestCorner
447 ) || matches!(
448 s_y,
449 ShapeRadius::ClosestCorner | ShapeRadius::FarthestCorner
450 ))
451 {
452 Err(i.new_custom_error(StyleParseErrorKind::UnspecifiedError))
453 } else {
454 Ok((s_x, s_y))
455 }
456 })
457 .unwrap_or_default();
458 let position = parse_at_position(context, input)?;
459
460 Ok(generic::Ellipse {
461 semiaxis_x,
462 semiaxis_y,
463 position,
464 })
465 }
466}
467
468fn parse_fill_rule<'i, 't>(
469 input: &mut Parser<'i, 't>,
470 shape_type: ShapeType,
471 expect_comma: bool,
472) -> FillRule {
473 match shape_type {
474 ShapeType::Outline => Default::default(),
487 ShapeType::Filled => input
488 .try_parse(|i| -> Result<_, ParseError> {
489 let fill = FillRule::parse(i)?;
490 if expect_comma {
491 i.expect_comma()?;
492 }
493 Ok(fill)
494 })
495 .unwrap_or_default(),
496 }
497}
498
499impl Parse for Polygon {
500 fn parse<'i, 't>(
501 context: &ParserContext,
502 input: &mut Parser<'i, 't>,
503 ) -> Result<Self, ParseError<'i>> {
504 input.expect_function_matching("polygon")?;
505 input.parse_nested_block(|i| Self::parse_function_arguments(context, i, ShapeType::Filled))
506 }
507}
508
509impl Polygon {
510 fn parse_function_arguments<'i, 't>(
512 context: &ParserContext,
513 input: &mut Parser<'i, 't>,
514 shape_type: ShapeType,
515 ) -> Result<Self, ParseError<'i>> {
516 let fill = parse_fill_rule(input, shape_type, true );
517 let coordinates = input
518 .parse_comma_separated(|i| {
519 Ok(PolygonCoord(
520 LengthPercentage::parse(context, i)?,
521 LengthPercentage::parse(context, i)?,
522 ))
523 })?
524 .into();
525
526 Ok(Polygon { fill, coordinates })
527 }
528}
529
530impl Path {
531 fn parse_function_arguments<'i, 't>(
533 input: &mut Parser<'i, 't>,
534 shape_type: ShapeType,
535 ) -> Result<Self, ParseError<'i>> {
536 use crate::values::specified::svg_path::AllowEmpty;
537
538 let fill = parse_fill_rule(input, shape_type, true );
539 let path = SVGPathData::parse(input, AllowEmpty::No)?;
540 Ok(Path { fill, path })
541 }
542}
543
544fn round_to_css<W>(round: &BorderRadius, dest: &mut CssWriter<W>) -> fmt::Result
545where
546 W: Write,
547{
548 if !round.is_zero() {
549 dest.write_str(" round ")?;
550 round.to_css(dest)?;
551 }
552 Ok(())
553}
554
555impl ToCss for Xywh {
556 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
557 where
558 W: Write,
559 {
560 self.x.to_css(dest)?;
561 dest.write_char(' ')?;
562 self.y.to_css(dest)?;
563 dest.write_char(' ')?;
564 self.width.to_css(dest)?;
565 dest.write_char(' ')?;
566 self.height.to_css(dest)?;
567 round_to_css(&self.round, dest)
568 }
569}
570
571impl Xywh {
572 fn parse_function_arguments<'i, 't>(
574 context: &ParserContext,
575 input: &mut Parser<'i, 't>,
576 ) -> Result<Self, ParseError<'i>> {
577 let x = LengthPercentage::parse(context, input)?;
578 let y = LengthPercentage::parse(context, input)?;
579 let width = NonNegativeLengthPercentage::parse(context, input)?;
580 let height = NonNegativeLengthPercentage::parse(context, input)?;
581 let round = parse_round(context, input)?;
582 Ok(Xywh {
583 x,
584 y,
585 width,
586 height,
587 round,
588 })
589 }
590}
591
592impl ToCss for ShapeRectFunction {
593 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
594 where
595 W: Write,
596 {
597 self.rect.0.to_css(dest)?;
598 dest.write_char(' ')?;
599 self.rect.1.to_css(dest)?;
600 dest.write_char(' ')?;
601 self.rect.2.to_css(dest)?;
602 dest.write_char(' ')?;
603 self.rect.3.to_css(dest)?;
604 round_to_css(&self.round, dest)
605 }
606}
607
608impl ShapeRectFunction {
609 fn parse_function_arguments<'i, 't>(
611 context: &ParserContext,
612 input: &mut Parser<'i, 't>,
613 ) -> Result<Self, ParseError<'i>> {
614 let rect = Rect::parse_all_components_with(context, input, LengthPercentageOrAuto::parse)?;
615 let round = parse_round(context, input)?;
616 Ok(ShapeRectFunction { rect, round })
617 }
618}
619
620impl ToComputedValue for BasicShapeRect {
621 type ComputedValue = ComputedInsetRect;
622
623 #[inline]
624 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
625 use crate::values::computed::LengthPercentage;
626 use crate::values::computed::LengthPercentageOrAuto;
627 use style_traits::values::specified::AllowedNumericType;
628
629 match self {
630 Self::Inset(ref inset) => inset.to_computed_value(context),
631 Self::Xywh(ref xywh) => {
632 let x = xywh.x.to_computed_value(context);
638 let y = xywh.y.to_computed_value(context);
639 let w = xywh.width.to_computed_value(context);
640 let h = xywh.height.to_computed_value(context);
641 let right = LengthPercentage::hundred_percent_minus_list(
643 &[&x, &w.0],
644 AllowedNumericType::All,
645 );
646 let bottom = LengthPercentage::hundred_percent_minus_list(
648 &[&y, &h.0],
649 AllowedNumericType::All,
650 );
651
652 ComputedInsetRect {
653 rect: Rect::new(y, right, bottom, x),
654 round: xywh.round.to_computed_value(context),
655 }
656 },
657 Self::Rect(ref rect) => {
658 fn compute_top_or_left(v: LengthPercentageOrAuto) -> LengthPercentage {
663 match v {
664 LengthPercentageOrAuto::Auto => LengthPercentage::zero_percent(),
667 LengthPercentageOrAuto::LengthPercentage(lp) => lp,
668 }
669 }
670 fn compute_bottom_or_right(v: LengthPercentageOrAuto) -> LengthPercentage {
671 match v {
672 LengthPercentageOrAuto::Auto => LengthPercentage::zero_percent(),
676 LengthPercentageOrAuto::LengthPercentage(lp) => {
677 LengthPercentage::hundred_percent_minus(lp, AllowedNumericType::All)
678 },
679 }
680 }
681
682 let round = rect.round.to_computed_value(context);
683 let rect = rect.rect.to_computed_value(context);
684 let rect = Rect::new(
685 compute_top_or_left(rect.0),
686 compute_bottom_or_right(rect.1),
687 compute_bottom_or_right(rect.2),
688 compute_top_or_left(rect.3),
689 );
690
691 ComputedInsetRect { rect, round }
692 },
693 }
694 }
695
696 #[inline]
697 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
698 Self::Inset(ToComputedValue::from_computed_value(computed))
699 }
700}
701
702impl generic::Shape<Angle, Position, LengthPercentage> {
703 fn parse_function_arguments<'i, 't>(
706 context: &ParserContext,
707 input: &mut Parser<'i, 't>,
708 shape_type: ShapeType,
709 ) -> Result<Self, ParseError<'i>> {
710 let fill = parse_fill_rule(input, shape_type, false );
711
712 let mut first = true;
713 let commands = input.parse_comma_separated(|i| {
714 if first {
715 first = false;
716
717 i.expect_ident_matching("from")?;
721 Ok(ShapeCommand::Move {
722 point: generic::CommandEndPoint::parse_endpoint_as_abs(context, i)?,
723 })
724 } else {
725 ShapeCommand::parse(context, i)
727 }
728 })?;
729
730 if commands.len() < 2 {
732 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
733 }
734
735 Ok(Self {
736 fill,
737 commands: commands.into(),
738 })
739 }
740}
741
742impl Parse for ShapeCommand {
743 fn parse<'i, 't>(
744 context: &ParserContext,
745 input: &mut Parser<'i, 't>,
746 ) -> Result<Self, ParseError<'i>> {
747 use crate::values::generics::basic_shape::{
748 ArcRadii, ArcSize, ArcSweep, AxisEndPoint, CommandEndPoint, ControlPoint,
749 };
750
751 Ok(try_match_ident_ignore_ascii_case! { input,
754 "close" => Self::Close,
755 "move" => {
756 let point = CommandEndPoint::parse(context, input)?;
757 Self::Move { point }
758 },
759 "line" => {
760 let point = CommandEndPoint::parse(context, input)?;
761 Self::Line { point }
762 },
763 "hline" => {
764 let x = AxisEndPoint::parse_hline(context, input)?;
765 Self::HLine { x }
766 },
767 "vline" => {
768 let y = AxisEndPoint::parse_vline(context, input)?;
769 Self::VLine { y }
770 },
771 "curve" => {
772 let point = CommandEndPoint::parse(context, input)?;
773 input.expect_ident_matching("with")?;
774 let control1 = ControlPoint::parse(context, input, point.is_abs())?;
775 if input.try_parse(|i| i.expect_delim('/')).is_ok() {
776 let control2 = ControlPoint::parse(context, input, point.is_abs())?;
777 Self::CubicCurve {
778 point,
779 control1,
780 control2,
781 }
782 } else {
783 Self::QuadCurve {
784 point,
785 control1,
786 }
787 }
788 },
789 "smooth" => {
790 let point = CommandEndPoint::parse(context, input)?;
791 if input.try_parse(|i| i.expect_ident_matching("with")).is_ok() {
792 let control2 = ControlPoint::parse(context, input, point.is_abs())?;
793 Self::SmoothCubic {
794 point,
795 control2,
796 }
797 } else {
798 Self::SmoothQuad { point }
799 }
800 },
801 "arc" => {
802 let point = CommandEndPoint::parse(context, input)?;
803 input.expect_ident_matching("of")?;
804 let rx = LengthPercentage::parse(context, input)?;
805 let ry = input.try_parse(|i| LengthPercentage::parse(context, i)).ok();
806 let radii = ArcRadii { rx, ry: ry.into() };
807
808 let mut arc_sweep = None;
810 let mut arc_size = None;
811 let mut rotate = None;
812 loop {
813 if arc_sweep.is_none() {
814 arc_sweep = input.try_parse(ArcSweep::parse).ok();
815 }
816
817 if arc_size.is_none() {
818 arc_size = input.try_parse(ArcSize::parse).ok();
819 if arc_size.is_some() {
820 continue;
821 }
822 }
823
824 if rotate.is_none()
825 && input
826 .try_parse(|i| i.expect_ident_matching("rotate"))
827 .is_ok()
828 {
829 rotate = Some(Angle::parse(context, input)?);
830 continue;
831 }
832 break;
833 }
834 Self::Arc {
835 point,
836 radii,
837 arc_sweep: arc_sweep.unwrap_or(ArcSweep::Ccw),
838 arc_size: arc_size.unwrap_or(ArcSize::Small),
839 rotate: rotate.unwrap_or(Angle::zero()),
840 }
841 },
842 })
843 }
844}
845
846impl Parse for generic::CoordinatePair<LengthPercentage> {
847 fn parse<'i, 't>(
848 context: &ParserContext,
849 input: &mut Parser<'i, 't>,
850 ) -> Result<Self, ParseError<'i>> {
851 let x = LengthPercentage::parse(context, input)?;
852 let y = LengthPercentage::parse(context, input)?;
853 Ok(Self::new(x, y))
854 }
855}
856
857impl generic::ControlPoint<Position, LengthPercentage> {
858 fn parse<'i, 't>(
860 context: &ParserContext,
861 input: &mut Parser<'i, 't>,
862 is_end_point_abs: bool,
863 ) -> Result<Self, ParseError<'i>> {
864 use generic::ControlReference;
865 let coord = input.try_parse(|i| generic::CoordinatePair::parse(context, i));
866
867 if is_end_point_abs && coord.is_err() {
869 let pos = Position::parse(context, input)?;
870 return Ok(Self::Absolute(pos));
871 }
872
873 let coord = coord?;
875 let mut reference = if is_end_point_abs {
876 ControlReference::Origin
877 } else {
878 ControlReference::Start
879 };
880 if input.try_parse(|i| i.expect_ident_matching("from")).is_ok() {
881 reference = ControlReference::parse(input)?;
882 }
883
884 Ok(Self::Relative(generic::RelativeControlPoint {
885 coord,
886 reference,
887 }))
888 }
889}
890
891impl Parse for generic::CommandEndPoint<Position, LengthPercentage> {
892 fn parse<'i, 't>(
894 context: &ParserContext,
895 input: &mut Parser<'i, 't>,
896 ) -> Result<Self, ParseError<'i>> {
897 if ByTo::parse(input)?.is_abs() {
898 Self::parse_endpoint_as_abs(context, input)
899 } else {
900 let point = generic::CoordinatePair::parse(context, input)?;
901 Ok(Self::ByCoordinate(point))
902 }
903 }
904}
905
906impl generic::CommandEndPoint<Position, LengthPercentage> {
907 fn parse_endpoint_as_abs<'i, 't>(
909 context: &ParserContext,
910 input: &mut Parser<'i, 't>,
911 ) -> Result<Self, ParseError<'i>> {
912 let point = Position::parse(context, input)?;
913 Ok(generic::CommandEndPoint::ToPosition(point))
914 }
915}
916
917impl generic::AxisEndPoint<LengthPercentage> {
918 pub fn parse_hline<'i, 't>(
920 context: &ParserContext,
921 input: &mut Parser<'i, 't>,
922 ) -> Result<Self, ParseError<'i>> {
923 use cssparser::Token;
924 use generic::{AxisPosition, AxisPositionKeyword};
925
926 if !ByTo::parse(input)?.is_abs() {
928 return Ok(Self::ByCoordinate(LengthPercentage::parse(context, input)?));
929 }
930
931 let x = AxisPosition::parse(context, input)?;
932 if let AxisPosition::Keyword(
933 _word @ (AxisPositionKeyword::Top
934 | AxisPositionKeyword::Bottom
935 | AxisPositionKeyword::YStart
936 | AxisPositionKeyword::YEnd),
937 ) = &x
938 {
939 let location = input.current_source_location();
940 let token = Token::Ident(x.to_css_string().into());
941 return Err(location.new_unexpected_token_error(token));
942 }
943 Ok(Self::ToPosition(x))
944 }
945
946 pub fn parse_vline<'i, 't>(
948 context: &ParserContext,
949 input: &mut Parser<'i, 't>,
950 ) -> Result<Self, ParseError<'i>> {
951 use cssparser::Token;
952 use generic::{AxisPosition, AxisPositionKeyword};
953
954 if !ByTo::parse(input)?.is_abs() {
956 return Ok(Self::ByCoordinate(LengthPercentage::parse(context, input)?));
957 }
958
959 let y = AxisPosition::parse(context, input)?;
960 if let AxisPosition::Keyword(
961 _word @ (AxisPositionKeyword::Left
962 | AxisPositionKeyword::Right
963 | AxisPositionKeyword::XStart
964 | AxisPositionKeyword::XEnd),
965 ) = &y
966 {
967 let location = input.current_source_location();
969 let token = Token::Ident(y.to_css_string().into());
970 return Err(location.new_unexpected_token_error(token));
971 }
972 Ok(Self::ToPosition(y))
973 }
974}
975
976impl ToComputedValue for generic::AxisPosition<LengthPercentage> {
977 type ComputedValue = generic::AxisPosition<ComputedLengthPercentage>;
978
979 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
980 match self {
981 Self::LengthPercent(lp) => {
982 Self::ComputedValue::LengthPercent(lp.to_computed_value(context))
983 },
984 Self::Keyword(word) => {
985 let lp =
986 LengthPercentage::Percentage(NoCalcPercentage::new(word.as_percentage().0));
987 Self::ComputedValue::LengthPercent(lp.to_computed_value(context))
988 },
989 }
990 }
991
992 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
993 match computed {
994 Self::ComputedValue::LengthPercent(lp) => {
995 Self::LengthPercent(LengthPercentage::from_computed_value(lp))
996 },
997 _ => unreachable!("Invalid state: computed value cannot be a keyword."),
998 }
999 }
1000}
1001
1002impl ToComputedValue for generic::AxisPosition<CSSFloat> {
1003 type ComputedValue = Self;
1004
1005 fn to_computed_value(&self, _context: &Context) -> Self {
1006 *self
1007 }
1008
1009 fn from_computed_value(computed: &Self) -> Self {
1010 *computed
1011 }
1012}
1013
1014#[derive(Clone, Copy, Debug, Parse, PartialEq)]
1017enum ByTo {
1018 By,
1020 To,
1022}
1023
1024impl ByTo {
1025 #[inline]
1027 pub fn is_abs(&self) -> bool {
1028 matches!(self, ByTo::To)
1029 }
1030}