1use crate::Zero;
11use crate::derives::*;
12use crate::parser::{Parse, ParserContext};
13use crate::values::computed::basic_shape::InsetRect as ComputedInsetRect;
14use crate::values::computed::{
15 Context, LengthPercentage as ComputedLengthPercentage, ToComputedValue,
16};
17use crate::values::generics::basic_shape as generic;
18use crate::values::generics::basic_shape::{Path, PolygonCoord};
19use crate::values::generics::position::GenericPositionOrAuto;
20use crate::values::generics::rect::Rect;
21use crate::values::specified::angle::Angle;
22use crate::values::specified::border::BorderRadius;
23use crate::values::specified::image::Image;
24use crate::values::specified::length::LengthPercentageOrAuto;
25use crate::values::specified::position::Position;
26use crate::values::specified::url::SpecifiedUrl;
27use crate::values::specified::{
28 LengthPercentage, NoCalcPercentage, NonNegativeLengthPercentage, SVGPathData,
29};
30use cssparser::{Parser, match_ignore_ascii_case};
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 BasicShape =
45 generic::GenericBasicShape<Angle, AxisPosition, 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, AxisPosition, Position, LengthPercentage>;
65
66pub type ShapeCommand =
68 generic::GenericShapeCommand<Angle, AxisPosition, Position, LengthPercentage>;
69
70pub type AxisPosition = generic::AxisPosition<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<R, ReferenceBox>(
207 context: &ParserContext,
208 input: &mut Parser,
209 to_shape: impl FnOnce(Box<BasicShape>, ReferenceBox) -> R,
210 to_reference_box: impl FnOnce(ReferenceBox) -> R,
211 flags: AllowedBasicShapes,
212) -> Result<R, ParseError>
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(ParseError::custom(StyleParseErrorKind::UnspecifiedError)),
241 }
242}
243
244impl Parse for ClipPath {
245 #[inline]
246 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
247 if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
248 return Ok(ClipPath::None);
249 }
250
251 if let Ok(url) = input.try_parse(|i| SpecifiedUrl::parse(context, i)) {
252 return Ok(ClipPath::Url(url));
253 }
254
255 parse_shape_or_box(
256 context,
257 input,
258 ClipPath::Shape,
259 ClipPath::Box,
260 AllowedBasicShapes::ALL,
261 )
262 }
263}
264
265impl Parse for ShapeOutside {
266 #[inline]
267 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
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(
295 context: &ParserContext,
296 input: &mut Parser,
297 flags: AllowedBasicShapes,
298 shape_type: ShapeType,
299 ) -> Result<Self, ParseError> {
300 let function = input.expect_function()?.clone();
301 input.parse_nested_block(move |i| {
302 match_ignore_ascii_case! { &function,
303 "inset" if flags.contains(AllowedBasicShapes::INSET) => {
304 InsetRect::parse_function_arguments(context, i)
305 .map(BasicShapeRect::Inset)
306 .map(BasicShape::Rect)
307 },
308 "xywh" if flags.contains(AllowedBasicShapes::XYWH) => {
309 Xywh::parse_function_arguments(context, i)
310 .map(BasicShapeRect::Xywh)
311 .map(BasicShape::Rect)
312 },
313 "rect" if flags.contains(AllowedBasicShapes::RECT) => {
314 ShapeRectFunction::parse_function_arguments(context, i)
315 .map(BasicShapeRect::Rect)
316 .map(BasicShape::Rect)
317 },
318 "circle" if flags.contains(AllowedBasicShapes::CIRCLE) => {
319 Circle::parse_function_arguments(context, i)
320 .map(BasicShape::Circle)
321 },
322 "ellipse" if flags.contains(AllowedBasicShapes::ELLIPSE) => {
323 Ellipse::parse_function_arguments(context, i)
324 .map(BasicShape::Ellipse)
325 },
326 "polygon" if flags.contains(AllowedBasicShapes::POLYGON) => {
327 Polygon::parse_function_arguments(context, i, shape_type)
328 .map(BasicShape::Polygon)
329 },
330 "path" if flags.contains(AllowedBasicShapes::PATH) => {
331 Path::parse_function_arguments(i, shape_type)
332 .map(PathOrShapeFunction::Path)
333 .map(BasicShape::PathOrShape)
334 },
335 "shape"
336 if flags.contains(AllowedBasicShapes::SHAPE)
337 && crate::pref!("layout.css.basic-shape-shape.enabled") =>
338 {
339 generic::Shape::parse_function_arguments(context, i, shape_type)
340 .map(PathOrShapeFunction::Shape)
341 .map(BasicShape::PathOrShape)
342 },
343 _ => Err(ParseError::custom(StyleParseErrorKind::UnexpectedFunction)),
344 }
345 })
346 }
347}
348
349impl Parse for InsetRect {
350 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
351 input.expect_function_matching("inset")?;
352 input.parse_nested_block(|i| Self::parse_function_arguments(context, i))
353 }
354}
355
356fn parse_round(context: &ParserContext, input: &mut Parser) -> Result<BorderRadius, ParseError> {
357 if input
358 .try_parse(|i| i.expect_ident_matching("round"))
359 .is_ok()
360 {
361 return BorderRadius::parse(context, input);
362 }
363
364 Ok(BorderRadius::zero())
365}
366
367impl InsetRect {
368 fn parse_function_arguments(
370 context: &ParserContext,
371 input: &mut Parser,
372 ) -> Result<Self, ParseError> {
373 let rect = Rect::parse_with(context, input, LengthPercentage::parse)?;
374 let round = parse_round(context, input)?;
375 Ok(generic::InsetRect { rect, round })
376 }
377}
378
379fn parse_at_position(
380 context: &ParserContext,
381 input: &mut Parser,
382) -> Result<GenericPositionOrAuto<Position>, ParseError> {
383 if input.try_parse(|i| i.expect_ident_matching("at")).is_ok() {
384 Position::parse(context, input).map(GenericPositionOrAuto::Position)
385 } else {
386 Ok(GenericPositionOrAuto::Auto)
387 }
388}
389
390impl Parse for Circle {
391 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
392 input.expect_function_matching("circle")?;
393 input.parse_nested_block(|i| Self::parse_function_arguments(context, i))
394 }
395}
396
397impl Circle {
398 fn parse_function_arguments(
399 context: &ParserContext,
400 input: &mut Parser,
401 ) -> Result<Self, ParseError> {
402 let radius = input
403 .try_parse(|i| ShapeRadius::parse(context, i))
404 .unwrap_or_default();
405 let position = parse_at_position(context, input)?;
406
407 Ok(generic::Circle { radius, position })
408 }
409}
410
411impl Parse for Ellipse {
412 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
413 input.expect_function_matching("ellipse")?;
414 input.parse_nested_block(|i| Self::parse_function_arguments(context, i))
415 }
416}
417
418impl Ellipse {
419 fn parse_function_arguments(
420 context: &ParserContext,
421 input: &mut Parser,
422 ) -> Result<Self, ParseError> {
423 let (semiaxis_x, semiaxis_y) = input
424 .try_parse(|i| -> Result<_, ParseError> {
425 let s_x = ShapeRadius::parse(context, i)?;
426 let s_y = ShapeRadius::parse(context, i)?;
427 if !crate::pref!("layout.css.ellipse-corners.enabled")
428 && (matches!(
429 s_x,
430 ShapeRadius::ClosestCorner | ShapeRadius::FarthestCorner
431 ) || matches!(
432 s_y,
433 ShapeRadius::ClosestCorner | ShapeRadius::FarthestCorner
434 ))
435 {
436 Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError))
437 } else {
438 Ok((s_x, s_y))
439 }
440 })
441 .unwrap_or_default();
442 let position = parse_at_position(context, input)?;
443
444 Ok(generic::Ellipse {
445 semiaxis_x,
446 semiaxis_y,
447 position,
448 })
449 }
450}
451
452fn parse_fill_rule(input: &mut Parser, shape_type: ShapeType, expect_comma: bool) -> FillRule {
453 match shape_type {
454 ShapeType::Outline => Default::default(),
467 ShapeType::Filled => input
468 .try_parse(|i| -> Result<_, ParseError> {
469 let fill = FillRule::parse(i)?;
470 if expect_comma {
471 i.expect_comma()?;
472 }
473 Ok(fill)
474 })
475 .unwrap_or_default(),
476 }
477}
478
479impl Parse for Polygon {
480 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
481 input.expect_function_matching("polygon")?;
482 input.parse_nested_block(|i| Self::parse_function_arguments(context, i, ShapeType::Filled))
483 }
484}
485
486impl Polygon {
487 fn parse_function_arguments(
489 context: &ParserContext,
490 input: &mut Parser,
491 shape_type: ShapeType,
492 ) -> Result<Self, ParseError> {
493 let fill = parse_fill_rule(input, shape_type, true );
494 let coordinates = input
495 .parse_comma_separated(|i| {
496 Ok(PolygonCoord(
497 LengthPercentage::parse(context, i)?,
498 LengthPercentage::parse(context, i)?,
499 ))
500 })?
501 .into();
502
503 Ok(Polygon { fill, coordinates })
504 }
505}
506
507impl Path {
508 fn parse_function_arguments(
510 input: &mut Parser,
511 shape_type: ShapeType,
512 ) -> Result<Self, ParseError> {
513 use crate::values::specified::svg_path::AllowEmpty;
514
515 let fill = parse_fill_rule(input, shape_type, true );
516 let path = SVGPathData::parse(input, AllowEmpty::No)?;
517 Ok(Path { fill, path })
518 }
519}
520
521fn round_to_css<W>(round: &BorderRadius, dest: &mut CssWriter<W>) -> fmt::Result
522where
523 W: Write,
524{
525 if !round.is_zero() {
526 dest.write_str(" round ")?;
527 round.to_css(dest)?;
528 }
529 Ok(())
530}
531
532impl ToCss for Xywh {
533 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
534 where
535 W: Write,
536 {
537 self.x.to_css(dest)?;
538 dest.write_char(' ')?;
539 self.y.to_css(dest)?;
540 dest.write_char(' ')?;
541 self.width.to_css(dest)?;
542 dest.write_char(' ')?;
543 self.height.to_css(dest)?;
544 round_to_css(&self.round, dest)
545 }
546}
547
548impl Xywh {
549 fn parse_function_arguments(
551 context: &ParserContext,
552 input: &mut Parser,
553 ) -> Result<Self, ParseError> {
554 let x = LengthPercentage::parse(context, input)?;
555 let y = LengthPercentage::parse(context, input)?;
556 let width = NonNegativeLengthPercentage::parse(context, input)?;
557 let height = NonNegativeLengthPercentage::parse(context, input)?;
558 let round = parse_round(context, input)?;
559 Ok(Xywh {
560 x,
561 y,
562 width,
563 height,
564 round,
565 })
566 }
567}
568
569impl ToCss for ShapeRectFunction {
570 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
571 where
572 W: Write,
573 {
574 self.rect.0.to_css(dest)?;
575 dest.write_char(' ')?;
576 self.rect.1.to_css(dest)?;
577 dest.write_char(' ')?;
578 self.rect.2.to_css(dest)?;
579 dest.write_char(' ')?;
580 self.rect.3.to_css(dest)?;
581 round_to_css(&self.round, dest)
582 }
583}
584
585impl ShapeRectFunction {
586 fn parse_function_arguments(
588 context: &ParserContext,
589 input: &mut Parser,
590 ) -> Result<Self, ParseError> {
591 let rect = Rect::parse_all_components_with(context, input, LengthPercentageOrAuto::parse)?;
592 let round = parse_round(context, input)?;
593 Ok(ShapeRectFunction { rect, round })
594 }
595}
596
597impl ToComputedValue for BasicShapeRect {
598 type ComputedValue = ComputedInsetRect;
599
600 #[inline]
601 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
602 use crate::values::computed::LengthPercentage;
603 use crate::values::computed::LengthPercentageOrAuto;
604 use style_traits::values::specified::AllowedNumericType;
605
606 match self {
607 Self::Inset(inset) => inset.to_computed_value(context),
608 Self::Xywh(xywh) => {
609 let x = xywh.x.to_computed_value(context);
615 let y = xywh.y.to_computed_value(context);
616 let w = xywh.width.to_computed_value(context);
617 let h = xywh.height.to_computed_value(context);
618 let right = LengthPercentage::hundred_percent_minus_list(
620 &[&x, &w.0],
621 AllowedNumericType::All,
622 );
623 let bottom = LengthPercentage::hundred_percent_minus_list(
625 &[&y, &h.0],
626 AllowedNumericType::All,
627 );
628
629 ComputedInsetRect {
630 rect: Rect::new(y, right, bottom, x),
631 round: xywh.round.to_computed_value(context),
632 }
633 },
634 Self::Rect(rect) => {
635 fn compute_top_or_left(v: LengthPercentageOrAuto) -> LengthPercentage {
640 match v {
641 LengthPercentageOrAuto::Auto => LengthPercentage::zero_percent(),
644 LengthPercentageOrAuto::LengthPercentage(lp) => lp,
645 }
646 }
647 fn compute_bottom_or_right(v: LengthPercentageOrAuto) -> LengthPercentage {
648 match v {
649 LengthPercentageOrAuto::Auto => LengthPercentage::zero_percent(),
653 LengthPercentageOrAuto::LengthPercentage(lp) => {
654 LengthPercentage::hundred_percent_minus(lp, AllowedNumericType::All)
655 },
656 }
657 }
658
659 let round = rect.round.to_computed_value(context);
660 let rect = rect.rect.to_computed_value(context);
661 let rect = Rect::new(
662 compute_top_or_left(rect.0),
663 compute_bottom_or_right(rect.1),
664 compute_bottom_or_right(rect.2),
665 compute_top_or_left(rect.3),
666 );
667
668 ComputedInsetRect { rect, round }
669 },
670 }
671 }
672
673 #[inline]
674 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
675 Self::Inset(ToComputedValue::from_computed_value(computed))
676 }
677}
678
679impl generic::Shape<Angle, AxisPosition, Position, LengthPercentage> {
680 fn parse_function_arguments(
683 context: &ParserContext,
684 input: &mut Parser,
685 shape_type: ShapeType,
686 ) -> Result<Self, ParseError> {
687 let fill = parse_fill_rule(input, shape_type, false );
688
689 let mut first = true;
690 let commands = input.parse_comma_separated(|i| {
691 if first {
692 first = false;
693
694 i.expect_ident_matching("from")?;
698 Ok(ShapeCommand::Move {
699 point: generic::CommandEndPoint::parse_endpoint_as_abs(context, i)?,
700 })
701 } else {
702 ShapeCommand::parse(context, i)
704 }
705 })?;
706
707 if commands.len() < 2 {
709 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
710 }
711
712 Ok(Self {
713 fill,
714 commands: commands.into(),
715 })
716 }
717}
718
719impl Parse for ShapeCommand {
720 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
721 use crate::values::generics::basic_shape::{
722 ArcRadii, ArcSize, ArcSweep, AxisEndPoint, CommandEndPoint, ControlPoint,
723 };
724
725 Ok(try_match_ident_ignore_ascii_case! { input,
728 "close" => Self::Close,
729 "move" => {
730 let point = CommandEndPoint::parse(context, input)?;
731 Self::Move { point }
732 },
733 "line" => {
734 let point = CommandEndPoint::parse(context, input)?;
735 Self::Line { point }
736 },
737 "hline" => {
738 let x = AxisEndPoint::parse_hline(context, input)?;
739 Self::HLine { x }
740 },
741 "vline" => {
742 let y = AxisEndPoint::parse_vline(context, input)?;
743 Self::VLine { y }
744 },
745 "curve" => {
746 let point = CommandEndPoint::parse(context, input)?;
747 input.expect_ident_matching("with")?;
748 let control1 = ControlPoint::parse(context, input, point.is_abs())?;
749 if input.try_parse(|i| i.expect_delim('/')).is_ok() {
750 let control2 = ControlPoint::parse(context, input, point.is_abs())?;
751 Self::CubicCurve {
752 point,
753 control1,
754 control2,
755 }
756 } else {
757 Self::QuadCurve {
758 point,
759 control1,
760 }
761 }
762 },
763 "smooth" => {
764 let point = CommandEndPoint::parse(context, input)?;
765 if input.try_parse(|i| i.expect_ident_matching("with")).is_ok() {
766 let control2 = ControlPoint::parse(context, input, point.is_abs())?;
767 Self::SmoothCubic {
768 point,
769 control2,
770 }
771 } else {
772 Self::SmoothQuad { point }
773 }
774 },
775 "arc" => {
776 let point = CommandEndPoint::parse(context, input)?;
777 input.expect_ident_matching("of")?;
778 let rx = LengthPercentage::parse(context, input)?;
779 let ry = input.try_parse(|i| LengthPercentage::parse(context, i)).ok();
780 let radii = ArcRadii { rx, ry: ry.into() };
781
782 let mut arc_sweep = None;
784 let mut arc_size = None;
785 let mut rotate = None;
786 loop {
787 if arc_sweep.is_none() {
788 arc_sweep = input.try_parse(ArcSweep::parse).ok();
789 }
790
791 if arc_size.is_none() {
792 arc_size = input.try_parse(ArcSize::parse).ok();
793 if arc_size.is_some() {
794 continue;
795 }
796 }
797
798 if rotate.is_none()
799 && input
800 .try_parse(|i| i.expect_ident_matching("rotate"))
801 .is_ok()
802 {
803 rotate = Some(Angle::parse(context, input)?);
804 continue;
805 }
806 break;
807 }
808 Self::Arc {
809 point,
810 radii,
811 arc_sweep: arc_sweep.unwrap_or(ArcSweep::Ccw),
812 arc_size: arc_size.unwrap_or(ArcSize::Small),
813 rotate: rotate.unwrap_or(Angle::zero()),
814 }
815 },
816 })
817 }
818}
819
820impl Parse for generic::CoordinatePair<LengthPercentage> {
821 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
822 let x = LengthPercentage::parse(context, input)?;
823 let y = LengthPercentage::parse(context, input)?;
824 Ok(Self::new(x, y))
825 }
826}
827
828impl generic::ControlPoint<Position, LengthPercentage> {
829 fn parse(
831 context: &ParserContext,
832 input: &mut Parser,
833 is_end_point_abs: bool,
834 ) -> Result<Self, ParseError> {
835 use generic::ControlReference;
836 let coord = input.try_parse(|i| generic::CoordinatePair::parse(context, i));
837
838 if is_end_point_abs && coord.is_err() {
840 let pos = Position::parse(context, input)?;
841 return Ok(Self::Absolute(pos));
842 }
843
844 let coord = coord?;
846 let mut reference = if is_end_point_abs {
847 ControlReference::Origin
848 } else {
849 ControlReference::Start
850 };
851 if input.try_parse(|i| i.expect_ident_matching("from")).is_ok() {
852 reference = ControlReference::parse(input)?;
853 }
854
855 Ok(Self::Relative(generic::RelativeControlPoint {
856 coord,
857 reference,
858 }))
859 }
860}
861
862impl Parse for generic::CommandEndPoint<Position, LengthPercentage> {
863 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
865 if ByTo::parse(input)?.is_abs() {
866 Self::parse_endpoint_as_abs(context, input)
867 } else {
868 let point = generic::CoordinatePair::parse(context, input)?;
869 Ok(Self::ByCoordinate(point))
870 }
871 }
872}
873
874impl generic::CommandEndPoint<Position, LengthPercentage> {
875 fn parse_endpoint_as_abs(
877 context: &ParserContext,
878 input: &mut Parser,
879 ) -> Result<Self, ParseError> {
880 let point = Position::parse(context, input)?;
881 Ok(generic::CommandEndPoint::ToPosition(point))
882 }
883}
884
885impl generic::AxisEndPoint<AxisPosition, LengthPercentage> {
886 pub fn parse_hline(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
888 use cssparser::Token;
889 use generic::{AxisPosition, AxisPositionKeyword};
890
891 if !ByTo::parse(input)?.is_abs() {
893 return Ok(Self::ByCoordinate(LengthPercentage::parse(context, input)?));
894 }
895
896 let x = AxisPosition::parse(context, input)?;
897 if let AxisPosition::Keyword(
898 _word @ (AxisPositionKeyword::Top
899 | AxisPositionKeyword::Bottom
900 | AxisPositionKeyword::YStart
901 | AxisPositionKeyword::YEnd),
902 ) = &x
903 {
904 let _ = Token::Ident(x.to_css_string().into());
905 return Err(ParseError::unexpected_token());
906 }
907 Ok(Self::ToPosition(x))
908 }
909
910 pub fn parse_vline(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
912 use cssparser::Token;
913 use generic::{AxisPosition, AxisPositionKeyword};
914
915 if !ByTo::parse(input)?.is_abs() {
917 return Ok(Self::ByCoordinate(LengthPercentage::parse(context, input)?));
918 }
919
920 let y = AxisPosition::parse(context, input)?;
921 if let AxisPosition::Keyword(
922 _word @ (AxisPositionKeyword::Left
923 | AxisPositionKeyword::Right
924 | AxisPositionKeyword::XStart
925 | AxisPositionKeyword::XEnd),
926 ) = &y
927 {
928 let _ = Token::Ident(y.to_css_string().into());
930 return Err(ParseError::unexpected_token());
931 }
932 Ok(Self::ToPosition(y))
933 }
934}
935
936impl ToComputedValue for generic::AxisPosition<LengthPercentage> {
937 type ComputedValue = ComputedLengthPercentage;
938
939 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
940 match self {
941 Self::LengthPercent(lp) => lp.to_computed_value(context),
942 Self::Keyword(word) => {
943 LengthPercentage::Percentage(NoCalcPercentage::new(word.as_percentage().0))
944 .to_computed_value(context)
945 },
946 }
947 }
948
949 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
950 Self::LengthPercent(LengthPercentage::from_computed_value(computed))
951 }
952}
953
954#[derive(Clone, Copy, Debug, Parse, PartialEq)]
957enum ByTo {
958 By,
960 To,
962}
963
964impl ByTo {
965 #[inline]
967 pub fn is_abs(&self) -> bool {
968 matches!(self, ByTo::To)
969 }
970}