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 Ok((
442 ShapeRadius::parse(context, i)?,
443 ShapeRadius::parse(context, i)?,
444 ))
445 })
446 .unwrap_or_default();
447 let position = parse_at_position(context, input)?;
448
449 Ok(generic::Ellipse {
450 semiaxis_x,
451 semiaxis_y,
452 position,
453 })
454 }
455}
456
457fn parse_fill_rule<'i, 't>(
458 input: &mut Parser<'i, 't>,
459 shape_type: ShapeType,
460 expect_comma: bool,
461) -> FillRule {
462 match shape_type {
463 ShapeType::Outline => Default::default(),
476 ShapeType::Filled => input
477 .try_parse(|i| -> Result<_, ParseError> {
478 let fill = FillRule::parse(i)?;
479 if expect_comma {
480 i.expect_comma()?;
481 }
482 Ok(fill)
483 })
484 .unwrap_or_default(),
485 }
486}
487
488impl Parse for Polygon {
489 fn parse<'i, 't>(
490 context: &ParserContext,
491 input: &mut Parser<'i, 't>,
492 ) -> Result<Self, ParseError<'i>> {
493 input.expect_function_matching("polygon")?;
494 input.parse_nested_block(|i| Self::parse_function_arguments(context, i, ShapeType::Filled))
495 }
496}
497
498impl Polygon {
499 fn parse_function_arguments<'i, 't>(
501 context: &ParserContext,
502 input: &mut Parser<'i, 't>,
503 shape_type: ShapeType,
504 ) -> Result<Self, ParseError<'i>> {
505 let fill = parse_fill_rule(input, shape_type, true );
506 let coordinates = input
507 .parse_comma_separated(|i| {
508 Ok(PolygonCoord(
509 LengthPercentage::parse(context, i)?,
510 LengthPercentage::parse(context, i)?,
511 ))
512 })?
513 .into();
514
515 Ok(Polygon { fill, coordinates })
516 }
517}
518
519impl Path {
520 fn parse_function_arguments<'i, 't>(
522 input: &mut Parser<'i, 't>,
523 shape_type: ShapeType,
524 ) -> Result<Self, ParseError<'i>> {
525 use crate::values::specified::svg_path::AllowEmpty;
526
527 let fill = parse_fill_rule(input, shape_type, true );
528 let path = SVGPathData::parse(input, AllowEmpty::No)?;
529 Ok(Path { fill, path })
530 }
531}
532
533fn round_to_css<W>(round: &BorderRadius, dest: &mut CssWriter<W>) -> fmt::Result
534where
535 W: Write,
536{
537 if !round.is_zero() {
538 dest.write_str(" round ")?;
539 round.to_css(dest)?;
540 }
541 Ok(())
542}
543
544impl ToCss for Xywh {
545 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
546 where
547 W: Write,
548 {
549 self.x.to_css(dest)?;
550 dest.write_char(' ')?;
551 self.y.to_css(dest)?;
552 dest.write_char(' ')?;
553 self.width.to_css(dest)?;
554 dest.write_char(' ')?;
555 self.height.to_css(dest)?;
556 round_to_css(&self.round, dest)
557 }
558}
559
560impl Xywh {
561 fn parse_function_arguments<'i, 't>(
563 context: &ParserContext,
564 input: &mut Parser<'i, 't>,
565 ) -> Result<Self, ParseError<'i>> {
566 let x = LengthPercentage::parse(context, input)?;
567 let y = LengthPercentage::parse(context, input)?;
568 let width = NonNegativeLengthPercentage::parse(context, input)?;
569 let height = NonNegativeLengthPercentage::parse(context, input)?;
570 let round = parse_round(context, input)?;
571 Ok(Xywh {
572 x,
573 y,
574 width,
575 height,
576 round,
577 })
578 }
579}
580
581impl ToCss for ShapeRectFunction {
582 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
583 where
584 W: Write,
585 {
586 self.rect.0.to_css(dest)?;
587 dest.write_char(' ')?;
588 self.rect.1.to_css(dest)?;
589 dest.write_char(' ')?;
590 self.rect.2.to_css(dest)?;
591 dest.write_char(' ')?;
592 self.rect.3.to_css(dest)?;
593 round_to_css(&self.round, dest)
594 }
595}
596
597impl ShapeRectFunction {
598 fn parse_function_arguments<'i, 't>(
600 context: &ParserContext,
601 input: &mut Parser<'i, 't>,
602 ) -> Result<Self, ParseError<'i>> {
603 let rect = Rect::parse_all_components_with(context, input, LengthPercentageOrAuto::parse)?;
604 let round = parse_round(context, input)?;
605 Ok(ShapeRectFunction { rect, round })
606 }
607}
608
609impl ToComputedValue for BasicShapeRect {
610 type ComputedValue = ComputedInsetRect;
611
612 #[inline]
613 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
614 use crate::values::computed::LengthPercentage;
615 use crate::values::computed::LengthPercentageOrAuto;
616 use style_traits::values::specified::AllowedNumericType;
617
618 match self {
619 Self::Inset(ref inset) => inset.to_computed_value(context),
620 Self::Xywh(ref xywh) => {
621 let x = xywh.x.to_computed_value(context);
627 let y = xywh.y.to_computed_value(context);
628 let w = xywh.width.to_computed_value(context);
629 let h = xywh.height.to_computed_value(context);
630 let right = LengthPercentage::hundred_percent_minus_list(
632 &[&x, &w.0],
633 AllowedNumericType::All,
634 );
635 let bottom = LengthPercentage::hundred_percent_minus_list(
637 &[&y, &h.0],
638 AllowedNumericType::All,
639 );
640
641 ComputedInsetRect {
642 rect: Rect::new(y, right, bottom, x),
643 round: xywh.round.to_computed_value(context),
644 }
645 },
646 Self::Rect(ref rect) => {
647 fn compute_top_or_left(v: LengthPercentageOrAuto) -> LengthPercentage {
652 match v {
653 LengthPercentageOrAuto::Auto => LengthPercentage::zero_percent(),
656 LengthPercentageOrAuto::LengthPercentage(lp) => lp,
657 }
658 }
659 fn compute_bottom_or_right(v: LengthPercentageOrAuto) -> LengthPercentage {
660 match v {
661 LengthPercentageOrAuto::Auto => LengthPercentage::zero_percent(),
665 LengthPercentageOrAuto::LengthPercentage(lp) => {
666 LengthPercentage::hundred_percent_minus(lp, AllowedNumericType::All)
667 },
668 }
669 }
670
671 let round = rect.round.to_computed_value(context);
672 let rect = rect.rect.to_computed_value(context);
673 let rect = Rect::new(
674 compute_top_or_left(rect.0),
675 compute_bottom_or_right(rect.1),
676 compute_bottom_or_right(rect.2),
677 compute_top_or_left(rect.3),
678 );
679
680 ComputedInsetRect { rect, round }
681 },
682 }
683 }
684
685 #[inline]
686 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
687 Self::Inset(ToComputedValue::from_computed_value(computed))
688 }
689}
690
691impl generic::Shape<Angle, Position, LengthPercentage> {
692 fn parse_function_arguments<'i, 't>(
695 context: &ParserContext,
696 input: &mut Parser<'i, 't>,
697 shape_type: ShapeType,
698 ) -> Result<Self, ParseError<'i>> {
699 let fill = parse_fill_rule(input, shape_type, false );
700
701 let mut first = true;
702 let commands = input.parse_comma_separated(|i| {
703 if first {
704 first = false;
705
706 i.expect_ident_matching("from")?;
710 Ok(ShapeCommand::Move {
711 point: generic::CommandEndPoint::parse_endpoint_as_abs(context, i)?,
712 })
713 } else {
714 ShapeCommand::parse(context, i)
716 }
717 })?;
718
719 if commands.len() < 2 {
721 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
722 }
723
724 Ok(Self {
725 fill,
726 commands: commands.into(),
727 })
728 }
729}
730
731impl Parse for ShapeCommand {
732 fn parse<'i, 't>(
733 context: &ParserContext,
734 input: &mut Parser<'i, 't>,
735 ) -> Result<Self, ParseError<'i>> {
736 use crate::values::generics::basic_shape::{
737 ArcRadii, ArcSize, ArcSweep, AxisEndPoint, CommandEndPoint, ControlPoint,
738 };
739
740 Ok(try_match_ident_ignore_ascii_case! { input,
743 "close" => Self::Close,
744 "move" => {
745 let point = CommandEndPoint::parse(context, input)?;
746 Self::Move { point }
747 },
748 "line" => {
749 let point = CommandEndPoint::parse(context, input)?;
750 Self::Line { point }
751 },
752 "hline" => {
753 let x = AxisEndPoint::parse_hline(context, input)?;
754 Self::HLine { x }
755 },
756 "vline" => {
757 let y = AxisEndPoint::parse_vline(context, input)?;
758 Self::VLine { y }
759 },
760 "curve" => {
761 let point = CommandEndPoint::parse(context, input)?;
762 input.expect_ident_matching("with")?;
763 let control1 = ControlPoint::parse(context, input, point.is_abs())?;
764 if input.try_parse(|i| i.expect_delim('/')).is_ok() {
765 let control2 = ControlPoint::parse(context, input, point.is_abs())?;
766 Self::CubicCurve {
767 point,
768 control1,
769 control2,
770 }
771 } else {
772 Self::QuadCurve {
773 point,
774 control1,
775 }
776 }
777 },
778 "smooth" => {
779 let point = CommandEndPoint::parse(context, input)?;
780 if input.try_parse(|i| i.expect_ident_matching("with")).is_ok() {
781 let control2 = ControlPoint::parse(context, input, point.is_abs())?;
782 Self::SmoothCubic {
783 point,
784 control2,
785 }
786 } else {
787 Self::SmoothQuad { point }
788 }
789 },
790 "arc" => {
791 let point = CommandEndPoint::parse(context, input)?;
792 input.expect_ident_matching("of")?;
793 let rx = LengthPercentage::parse(context, input)?;
794 let ry = input.try_parse(|i| LengthPercentage::parse(context, i)).ok();
795 let radii = ArcRadii { rx, ry: ry.into() };
796
797 let mut arc_sweep = None;
799 let mut arc_size = None;
800 let mut rotate = None;
801 loop {
802 if arc_sweep.is_none() {
803 arc_sweep = input.try_parse(ArcSweep::parse).ok();
804 }
805
806 if arc_size.is_none() {
807 arc_size = input.try_parse(ArcSize::parse).ok();
808 if arc_size.is_some() {
809 continue;
810 }
811 }
812
813 if rotate.is_none()
814 && input
815 .try_parse(|i| i.expect_ident_matching("rotate"))
816 .is_ok()
817 {
818 rotate = Some(Angle::parse(context, input)?);
819 continue;
820 }
821 break;
822 }
823 Self::Arc {
824 point,
825 radii,
826 arc_sweep: arc_sweep.unwrap_or(ArcSweep::Ccw),
827 arc_size: arc_size.unwrap_or(ArcSize::Small),
828 rotate: rotate.unwrap_or(Angle::zero()),
829 }
830 },
831 })
832 }
833}
834
835impl Parse for generic::CoordinatePair<LengthPercentage> {
836 fn parse<'i, 't>(
837 context: &ParserContext,
838 input: &mut Parser<'i, 't>,
839 ) -> Result<Self, ParseError<'i>> {
840 let x = LengthPercentage::parse(context, input)?;
841 let y = LengthPercentage::parse(context, input)?;
842 Ok(Self::new(x, y))
843 }
844}
845
846impl generic::ControlPoint<Position, LengthPercentage> {
847 fn parse<'i, 't>(
849 context: &ParserContext,
850 input: &mut Parser<'i, 't>,
851 is_end_point_abs: bool,
852 ) -> Result<Self, ParseError<'i>> {
853 use generic::ControlReference;
854 let coord = input.try_parse(|i| generic::CoordinatePair::parse(context, i));
855
856 if is_end_point_abs && coord.is_err() {
858 let pos = Position::parse(context, input)?;
859 return Ok(Self::Absolute(pos));
860 }
861
862 let coord = coord?;
864 let mut reference = if is_end_point_abs {
865 ControlReference::Origin
866 } else {
867 ControlReference::Start
868 };
869 if input.try_parse(|i| i.expect_ident_matching("from")).is_ok() {
870 reference = ControlReference::parse(input)?;
871 }
872
873 Ok(Self::Relative(generic::RelativeControlPoint {
874 coord,
875 reference,
876 }))
877 }
878}
879
880impl Parse for generic::CommandEndPoint<Position, LengthPercentage> {
881 fn parse<'i, 't>(
883 context: &ParserContext,
884 input: &mut Parser<'i, 't>,
885 ) -> Result<Self, ParseError<'i>> {
886 if ByTo::parse(input)?.is_abs() {
887 Self::parse_endpoint_as_abs(context, input)
888 } else {
889 let point = generic::CoordinatePair::parse(context, input)?;
890 Ok(Self::ByCoordinate(point))
891 }
892 }
893}
894
895impl generic::CommandEndPoint<Position, LengthPercentage> {
896 fn parse_endpoint_as_abs<'i, 't>(
898 context: &ParserContext,
899 input: &mut Parser<'i, 't>,
900 ) -> Result<Self, ParseError<'i>> {
901 let point = Position::parse(context, input)?;
902 Ok(generic::CommandEndPoint::ToPosition(point))
903 }
904}
905
906impl generic::AxisEndPoint<LengthPercentage> {
907 pub fn parse_hline<'i, 't>(
909 context: &ParserContext,
910 input: &mut Parser<'i, 't>,
911 ) -> Result<Self, ParseError<'i>> {
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 x = AxisPosition::parse(context, input)?;
921 if let AxisPosition::Keyword(
922 _word @ (AxisPositionKeyword::Top
923 | AxisPositionKeyword::Bottom
924 | AxisPositionKeyword::YStart
925 | AxisPositionKeyword::YEnd),
926 ) = &x
927 {
928 let location = input.current_source_location();
929 let token = Token::Ident(x.to_css_string().into());
930 return Err(location.new_unexpected_token_error(token));
931 }
932 Ok(Self::ToPosition(x))
933 }
934
935 pub fn parse_vline<'i, 't>(
937 context: &ParserContext,
938 input: &mut Parser<'i, 't>,
939 ) -> Result<Self, ParseError<'i>> {
940 use cssparser::Token;
941 use generic::{AxisPosition, AxisPositionKeyword};
942
943 if !ByTo::parse(input)?.is_abs() {
945 return Ok(Self::ByCoordinate(LengthPercentage::parse(context, input)?));
946 }
947
948 let y = AxisPosition::parse(context, input)?;
949 if let AxisPosition::Keyword(
950 _word @ (AxisPositionKeyword::Left
951 | AxisPositionKeyword::Right
952 | AxisPositionKeyword::XStart
953 | AxisPositionKeyword::XEnd),
954 ) = &y
955 {
956 let location = input.current_source_location();
958 let token = Token::Ident(y.to_css_string().into());
959 return Err(location.new_unexpected_token_error(token));
960 }
961 Ok(Self::ToPosition(y))
962 }
963}
964
965impl ToComputedValue for generic::AxisPosition<LengthPercentage> {
966 type ComputedValue = generic::AxisPosition<ComputedLengthPercentage>;
967
968 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
969 match self {
970 Self::LengthPercent(lp) => {
971 Self::ComputedValue::LengthPercent(lp.to_computed_value(context))
972 },
973 Self::Keyword(word) => {
974 let lp =
975 LengthPercentage::Percentage(NoCalcPercentage::new(word.as_percentage().0));
976 Self::ComputedValue::LengthPercent(lp.to_computed_value(context))
977 },
978 }
979 }
980
981 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
982 match computed {
983 Self::ComputedValue::LengthPercent(lp) => {
984 Self::LengthPercent(LengthPercentage::from_computed_value(lp))
985 },
986 _ => unreachable!("Invalid state: computed value cannot be a keyword."),
987 }
988 }
989}
990
991impl ToComputedValue for generic::AxisPosition<CSSFloat> {
992 type ComputedValue = Self;
993
994 fn to_computed_value(&self, _context: &Context) -> Self {
995 *self
996 }
997
998 fn from_computed_value(computed: &Self) -> Self {
999 *computed
1000 }
1001}
1002
1003#[derive(Clone, Copy, Debug, Parse, PartialEq)]
1006enum ByTo {
1007 By,
1009 To,
1011}
1012
1013impl ByTo {
1014 #[inline]
1016 pub fn is_abs(&self) -> bool {
1017 matches!(self, ByTo::To)
1018 }
1019}