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