1use core::fmt;
12
13use crate::data_type::{
14 Color, CssString, FitContent, Length, LengthPercentage, MaxContent, Number, Percentage,
15};
16use crate::to_css::{ToCss, write_number};
17
18#[derive(Clone, Copy, Debug, PartialEq)]
20pub enum VerticalAlignment {
21 Keyword(crate::VerticalAlign),
23 Offset(Length),
25}
26
27impl From<crate::VerticalAlign> for VerticalAlignment {
28 fn from(value: crate::VerticalAlign) -> Self {
29 Self::Keyword(value)
30 }
31}
32impl From<Length> for VerticalAlignment {
33 fn from(value: Length) -> Self {
34 Self::Offset(value)
35 }
36}
37impl ToCss for VerticalAlignment {
38 fn to_css(&self, dest: &mut dyn fmt::Write) -> fmt::Result {
39 match self {
40 Self::Keyword(value) => value.to_css(dest),
41 Self::Offset(value) => value.to_css(dest),
42 }
43 }
44}
45
46#[derive(Clone, Debug, PartialEq)]
53pub enum BackdropFilter {
54 None,
56 Blur(crate::ValueOrVariable<Length>),
58}
59
60impl BackdropFilter {
61 pub fn blur(radius: impl Into<crate::ValueOrVariable<Length>>) -> Self {
63 Self::Blur(radius.into())
64 }
65}
66
67impl ToCss for BackdropFilter {
68 fn to_css(&self, dest: &mut dyn fmt::Write) -> fmt::Result {
69 match self {
70 Self::None => dest.write_str("none"),
71 Self::Blur(radius) => {
72 dest.write_str("blur(")?;
73 radius.to_css(dest)?;
74 dest.write_char(')')
75 }
76 }
77 }
78}
79
80#[derive(Clone, Debug, PartialEq)]
84pub struct BoxShadow {
85 pub offset_x: crate::ValueOrVariable<Length>,
87 pub offset_y: crate::ValueOrVariable<Length>,
89 pub blur_radius: crate::ValueOrVariable<Length>,
91 pub spread_radius: crate::ValueOrVariable<Length>,
93 pub color: crate::ValueOrVariable<Color>,
95 pub inset: bool,
97}
98
99impl BoxShadow {
100 pub fn outer(
102 offset_x: impl Into<crate::ValueOrVariable<Length>>,
103 offset_y: impl Into<crate::ValueOrVariable<Length>>,
104 blur_radius: impl Into<crate::ValueOrVariable<Length>>,
105 spread_radius: impl Into<crate::ValueOrVariable<Length>>,
106 color: impl Into<crate::ValueOrVariable<Color>>,
107 ) -> Self {
108 Self {
109 offset_x: offset_x.into(),
110 offset_y: offset_y.into(),
111 blur_radius: blur_radius.into(),
112 spread_radius: spread_radius.into(),
113 color: color.into(),
114 inset: false,
115 }
116 }
117
118 pub fn inset(
120 offset_x: impl Into<crate::ValueOrVariable<Length>>,
121 offset_y: impl Into<crate::ValueOrVariable<Length>>,
122 blur_radius: impl Into<crate::ValueOrVariable<Length>>,
123 spread_radius: impl Into<crate::ValueOrVariable<Length>>,
124 color: impl Into<crate::ValueOrVariable<Color>>,
125 ) -> Self {
126 Self {
127 inset: true,
128 ..Self::outer(offset_x, offset_y, blur_radius, spread_radius, color)
129 }
130 }
131}
132
133impl ToCss for BoxShadow {
134 fn to_css(&self, dest: &mut dyn fmt::Write) -> fmt::Result {
135 if self.inset {
136 dest.write_str("inset ")?;
137 }
138 self.offset_x.to_css(dest)?;
139 dest.write_char(' ')?;
140 self.offset_y.to_css(dest)?;
141 dest.write_char(' ')?;
142 self.blur_radius.to_css(dest)?;
143 dest.write_char(' ')?;
144 self.spread_radius.to_css(dest)?;
145 dest.write_char(' ')?;
146 self.color.to_css(dest)
147 }
148}
149
150#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
154pub enum ClipBox {
155 #[default]
157 BorderBox,
158 PaddingBox,
160 ContentBox,
162 FillBox,
164 StrokeBox,
166 ViewBox,
168}
169
170impl ToCss for ClipBox {
171 fn to_css(&self, dest: &mut dyn fmt::Write) -> fmt::Result {
172 dest.write_str(match self {
173 Self::BorderBox => "border-box",
174 Self::PaddingBox => "padding-box",
175 Self::ContentBox => "content-box",
176 Self::FillBox => "fill-box",
177 Self::StrokeBox => "stroke-box",
178 Self::ViewBox => "view-box",
179 })
180 }
181}
182
183#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
185pub enum ClipFillRule {
186 #[default]
188 NonZero,
189 EvenOdd,
191}
192
193#[derive(Clone, Debug, PartialEq)]
195pub struct ClipPoint {
196 pub x: LengthPercentage,
198 pub y: LengthPercentage,
200}
201
202impl ClipPoint {
203 pub fn new(x: impl Into<LengthPercentage>, y: impl Into<LengthPercentage>) -> Self {
205 Self {
206 x: x.into(),
207 y: y.into(),
208 }
209 }
210}
211
212#[derive(Clone, Debug, PartialEq)]
214pub enum ClipPathCommand {
215 MoveTo(ClipPoint),
217 LineTo(ClipPoint),
219 QuadraticTo {
221 control: ClipPoint,
223 end: ClipPoint,
225 },
226 CubicTo {
228 control_1: ClipPoint,
230 control_2: ClipPoint,
232 end: ClipPoint,
234 },
235 Close,
237}
238
239#[derive(Clone, Debug, PartialEq)]
241pub enum ClipPath {
242 None,
244 Inset {
246 reference_box: ClipBox,
248 offsets: [LengthPercentage; 4],
250 radii: Option<BorderRadius>,
252 },
253 Circle {
255 reference_box: ClipBox,
257 radius: LengthPercentage,
259 center_x: LengthPercentage,
261 center_y: LengthPercentage,
263 },
264 Ellipse {
266 reference_box: ClipBox,
268 radius_x: LengthPercentage,
270 radius_y: LengthPercentage,
272 center_x: LengthPercentage,
274 center_y: LengthPercentage,
276 },
277 Path {
279 reference_box: ClipBox,
281 fill_rule: ClipFillRule,
283 commands: Vec<ClipPathCommand>,
285 },
286}
287
288impl ClipPath {
289 pub fn circle(radius: impl Into<LengthPercentage>) -> Self {
291 Self::Circle {
292 reference_box: ClipBox::BorderBox,
293 radius: radius.into(),
294 center_x: Percentage::new(50.0).into(),
295 center_y: Percentage::new(50.0).into(),
296 }
297 }
298
299 pub fn ellipse(
301 radius_x: impl Into<LengthPercentage>,
302 radius_y: impl Into<LengthPercentage>,
303 ) -> Self {
304 Self::Ellipse {
305 reference_box: ClipBox::BorderBox,
306 radius_x: radius_x.into(),
307 radius_y: radius_y.into(),
308 center_x: Percentage::new(50.0).into(),
309 center_y: Percentage::new(50.0).into(),
310 }
311 }
312
313 pub fn inset(offsets: [LengthPercentage; 4]) -> Self {
315 Self::Inset {
316 reference_box: ClipBox::BorderBox,
317 offsets,
318 radii: None,
319 }
320 }
321
322 pub fn with_reference_box(mut self, reference_box: ClipBox) -> Self {
324 match &mut self {
325 Self::None => {}
326 Self::Inset {
327 reference_box: box_value,
328 ..
329 }
330 | Self::Circle {
331 reference_box: box_value,
332 ..
333 }
334 | Self::Ellipse {
335 reference_box: box_value,
336 ..
337 }
338 | Self::Path {
339 reference_box: box_value,
340 ..
341 } => *box_value = reference_box,
342 }
343 self
344 }
345}
346
347impl ToCss for ClipPath {
348 fn to_css(&self, dest: &mut dyn fmt::Write) -> fmt::Result {
349 fn point(dest: &mut dyn fmt::Write, point: &ClipPoint) -> fmt::Result {
350 point.x.to_css(dest)?;
351 dest.write_char(' ')?;
352 point.y.to_css(dest)
353 }
354
355 let reference_box = match self {
356 Self::None => return dest.write_str("none"),
357 Self::Inset {
358 reference_box,
359 offsets,
360 radii,
361 } => {
362 dest.write_str("inset(")?;
363 write_four(dest, offsets)?;
364 if let Some(radii) = radii {
365 dest.write_str(" round ")?;
366 radii.to_css(dest)?;
367 }
368 dest.write_char(')')?;
369 reference_box
370 }
371 Self::Circle {
372 reference_box,
373 radius,
374 center_x,
375 center_y,
376 } => {
377 dest.write_str("circle(")?;
378 radius.to_css(dest)?;
379 dest.write_str(" at ")?;
380 center_x.to_css(dest)?;
381 dest.write_char(' ')?;
382 center_y.to_css(dest)?;
383 dest.write_char(')')?;
384 reference_box
385 }
386 Self::Ellipse {
387 reference_box,
388 radius_x,
389 radius_y,
390 center_x,
391 center_y,
392 } => {
393 dest.write_str("ellipse(")?;
394 radius_x.to_css(dest)?;
395 dest.write_char(' ')?;
396 radius_y.to_css(dest)?;
397 dest.write_str(" at ")?;
398 center_x.to_css(dest)?;
399 dest.write_char(' ')?;
400 center_y.to_css(dest)?;
401 dest.write_char(')')?;
402 reference_box
403 }
404 Self::Path {
405 reference_box,
406 fill_rule,
407 commands,
408 } => {
409 dest.write_str("path(")?;
410 if matches!(fill_rule, ClipFillRule::EvenOdd) {
411 dest.write_str("evenodd, ")?;
412 }
413 for (index, command) in commands.iter().enumerate() {
414 if index > 0 {
415 dest.write_char(' ')?;
416 }
417 match command {
418 ClipPathCommand::MoveTo(value) => {
419 dest.write_str("M ")?;
420 point(dest, value)?;
421 }
422 ClipPathCommand::LineTo(value) => {
423 dest.write_str("L ")?;
424 point(dest, value)?;
425 }
426 ClipPathCommand::QuadraticTo { control, end } => {
427 dest.write_str("Q ")?;
428 point(dest, control)?;
429 dest.write_char(' ')?;
430 point(dest, end)?;
431 }
432 ClipPathCommand::CubicTo {
433 control_1,
434 control_2,
435 end,
436 } => {
437 dest.write_str("C ")?;
438 point(dest, control_1)?;
439 dest.write_char(' ')?;
440 point(dest, control_2)?;
441 dest.write_char(' ')?;
442 point(dest, end)?;
443 }
444 ClipPathCommand::Close => dest.write_char('Z')?,
445 }
446 }
447 dest.write_char(')')?;
448 reference_box
449 }
450 };
451 dest.write_char(' ')?;
452 reference_box.to_css(dest)
453 }
454}
455
456#[derive(Clone, Copy, Debug, PartialEq)]
460pub struct MotionPathPoint {
461 pub x: f32,
463 pub y: f32,
465}
466
467impl MotionPathPoint {
468 pub const fn new(x: f32, y: f32) -> Self {
470 Self { x, y }
471 }
472}
473
474#[derive(Clone, Copy, Debug, PartialEq)]
476pub enum MotionPathCommand {
477 MoveTo(MotionPathPoint),
479 LineTo(MotionPathPoint),
481 QuadraticTo {
483 control: MotionPathPoint,
485 to: MotionPathPoint,
487 },
488 CubicTo {
490 control1: MotionPathPoint,
492 control2: MotionPathPoint,
494 to: MotionPathPoint,
496 },
497 ArcTo {
499 radius_x: f32,
501 radius_y: f32,
503 x_axis_rotation: f32,
505 large_arc: bool,
507 sweep: bool,
509 to: MotionPathPoint,
511 },
512 Close,
514}
515
516#[derive(Clone, Debug, PartialEq)]
518pub struct InsetPath {
519 pub offsets: [LengthPercentage; 4],
521 pub radii: Option<BorderRadius>,
523}
524
525#[derive(Clone, Debug, PartialEq)]
527pub enum OffsetPath {
528 None,
530 Path(Vec<MotionPathCommand>),
532 Circle {
534 radius: LengthPercentage,
536 center_x: LengthPercentage,
538 center_y: LengthPercentage,
540 },
541 Ellipse {
543 radius_x: LengthPercentage,
545 radius_y: LengthPercentage,
547 center_x: LengthPercentage,
549 center_y: LengthPercentage,
551 },
552 Inset(Box<InsetPath>),
554}
555
556impl OffsetPath {
557 pub fn path(commands: impl Into<Vec<MotionPathCommand>>) -> Self {
559 Self::Path(commands.into())
560 }
561
562 pub fn circle(radius: impl Into<LengthPercentage>) -> Self {
564 Self::circle_at(radius, Percentage::new(50.0), Percentage::new(50.0))
565 }
566
567 pub fn circle_at(
569 radius: impl Into<LengthPercentage>,
570 center_x: impl Into<LengthPercentage>,
571 center_y: impl Into<LengthPercentage>,
572 ) -> Self {
573 Self::Circle {
574 radius: radius.into(),
575 center_x: center_x.into(),
576 center_y: center_y.into(),
577 }
578 }
579
580 pub fn ellipse(
582 radius_x: impl Into<LengthPercentage>,
583 radius_y: impl Into<LengthPercentage>,
584 ) -> Self {
585 Self::ellipse_at(
586 radius_x,
587 radius_y,
588 Percentage::new(50.0),
589 Percentage::new(50.0),
590 )
591 }
592
593 pub fn ellipse_at(
595 radius_x: impl Into<LengthPercentage>,
596 radius_y: impl Into<LengthPercentage>,
597 center_x: impl Into<LengthPercentage>,
598 center_y: impl Into<LengthPercentage>,
599 ) -> Self {
600 Self::Ellipse {
601 radius_x: radius_x.into(),
602 radius_y: radius_y.into(),
603 center_x: center_x.into(),
604 center_y: center_y.into(),
605 }
606 }
607
608 pub fn inset(
610 top: impl Into<LengthPercentage>,
611 right: impl Into<LengthPercentage>,
612 bottom: impl Into<LengthPercentage>,
613 left: impl Into<LengthPercentage>,
614 ) -> Self {
615 Self::Inset(Box::new(InsetPath {
616 offsets: [top.into(), right.into(), bottom.into(), left.into()],
617 radii: None,
618 }))
619 }
620
621 pub fn inset_round(
623 top: impl Into<LengthPercentage>,
624 right: impl Into<LengthPercentage>,
625 bottom: impl Into<LengthPercentage>,
626 left: impl Into<LengthPercentage>,
627 radii: BorderRadius,
628 ) -> Self {
629 Self::Inset(Box::new(InsetPath {
630 offsets: [top.into(), right.into(), bottom.into(), left.into()],
631 radii: Some(radii),
632 }))
633 }
634}
635
636impl ToCss for OffsetPath {
637 fn to_css(&self, dest: &mut dyn fmt::Write) -> fmt::Result {
638 match self {
639 Self::None => dest.write_str("none"),
640 Self::Path(commands) => {
641 dest.write_str("path(\"")?;
642 for (index, command) in commands.iter().enumerate() {
643 if index > 0 {
644 dest.write_char(' ')?;
645 }
646 match command {
647 MotionPathCommand::MoveTo(point) => {
648 dest.write_str("M ")?;
649 write_number(dest, point.x)?;
650 dest.write_char(' ')?;
651 write_number(dest, point.y)?;
652 }
653 MotionPathCommand::LineTo(point) => {
654 dest.write_str("L ")?;
655 write_number(dest, point.x)?;
656 dest.write_char(' ')?;
657 write_number(dest, point.y)?;
658 }
659 MotionPathCommand::QuadraticTo { control, to } => {
660 dest.write_str("Q ")?;
661 write_number(dest, control.x)?;
662 dest.write_char(' ')?;
663 write_number(dest, control.y)?;
664 dest.write_char(' ')?;
665 write_number(dest, to.x)?;
666 dest.write_char(' ')?;
667 write_number(dest, to.y)?;
668 }
669 MotionPathCommand::CubicTo {
670 control1,
671 control2,
672 to,
673 } => {
674 dest.write_str("C ")?;
675 write_number(dest, control1.x)?;
676 dest.write_char(' ')?;
677 write_number(dest, control1.y)?;
678 dest.write_char(' ')?;
679 write_number(dest, control2.x)?;
680 dest.write_char(' ')?;
681 write_number(dest, control2.y)?;
682 dest.write_char(' ')?;
683 write_number(dest, to.x)?;
684 dest.write_char(' ')?;
685 write_number(dest, to.y)?;
686 }
687 MotionPathCommand::ArcTo {
688 radius_x,
689 radius_y,
690 x_axis_rotation,
691 large_arc,
692 sweep,
693 to,
694 } => {
695 dest.write_str("A ")?;
696 write_number(dest, *radius_x)?;
697 dest.write_char(' ')?;
698 write_number(dest, *radius_y)?;
699 dest.write_char(' ')?;
700 write_number(dest, *x_axis_rotation)?;
701 dest.write_char(' ')?;
702 dest.write_char(if *large_arc { '1' } else { '0' })?;
703 dest.write_char(' ')?;
704 dest.write_char(if *sweep { '1' } else { '0' })?;
705 dest.write_char(' ')?;
706 write_number(dest, to.x)?;
707 dest.write_char(' ')?;
708 write_number(dest, to.y)?;
709 }
710 MotionPathCommand::Close => dest.write_char('Z')?,
711 }
712 }
713 dest.write_str("\")")
714 }
715 Self::Circle {
716 radius,
717 center_x,
718 center_y,
719 } => {
720 dest.write_str("circle(")?;
721 radius.to_css(dest)?;
722 dest.write_str(" at ")?;
723 center_x.to_css(dest)?;
724 dest.write_char(' ')?;
725 center_y.to_css(dest)?;
726 dest.write_char(')')
727 }
728 Self::Ellipse {
729 radius_x,
730 radius_y,
731 center_x,
732 center_y,
733 } => {
734 dest.write_str("ellipse(")?;
735 radius_x.to_css(dest)?;
736 dest.write_char(' ')?;
737 radius_y.to_css(dest)?;
738 dest.write_str(" at ")?;
739 center_x.to_css(dest)?;
740 dest.write_char(' ')?;
741 center_y.to_css(dest)?;
742 dest.write_char(')')
743 }
744 Self::Inset(value) => {
745 dest.write_str("inset(")?;
746 write_four(dest, &value.offsets)?;
747 if let Some(radii) = &value.radii {
748 dest.write_str(" round ")?;
749 radii.to_css(dest)?;
750 }
751 dest.write_char(')')
752 }
753 }
754 }
755}
756
757#[derive(Clone, Copy, Debug, PartialEq)]
759pub enum OffsetDistance {
760 Number(Number),
762 Percentage(Percentage),
764}
765
766impl From<Number> for OffsetDistance {
767 fn from(value: Number) -> Self {
768 Self::Number(value)
769 }
770}
771
772impl From<Percentage> for OffsetDistance {
773 fn from(value: Percentage) -> Self {
774 Self::Percentage(value)
775 }
776}
777
778impl ToCss for OffsetDistance {
779 fn to_css(&self, dest: &mut dyn fmt::Write) -> fmt::Result {
780 match self {
781 Self::Number(value) => value.to_css(dest),
782 Self::Percentage(value) => value.to_css(dest),
783 }
784 }
785}
786
787#[derive(Clone, Copy, Debug, PartialEq)]
789pub enum OffsetRotate {
790 Auto,
792 Angle(crate::Angle),
794}
795
796impl ToCss for OffsetRotate {
797 fn to_css(&self, dest: &mut dyn fmt::Write) -> fmt::Result {
798 match self {
799 Self::Auto => dest.write_str("auto"),
800 Self::Angle(angle) => angle.to_css(dest),
801 }
802 }
803}
804
805#[derive(Clone, Debug, PartialEq)]
810pub enum Size {
811 Auto,
813 LengthPercentage(LengthPercentage),
815 MaxContent,
817 MinContent,
819 FitContent(FitContent),
821 None,
823}
824
825impl ToCss for Size {
826 fn to_css(&self, dest: &mut dyn fmt::Write) -> fmt::Result {
827 match self {
828 Size::Auto => dest.write_str("auto"),
829 Size::LengthPercentage(lp) => lp.to_css(dest),
830 Size::MaxContent => dest.write_str("max-content"),
831 Size::MinContent => dest.write_str("min-content"),
832 Size::FitContent(fc) => fc.to_css(dest),
833 Size::None => dest.write_str("none"),
834 }
835 }
836}
837
838impl From<Length> for Size {
839 fn from(l: Length) -> Self {
840 Self::LengthPercentage(l.into())
841 }
842}
843
844impl From<Percentage> for Size {
845 fn from(p: Percentage) -> Self {
846 Self::LengthPercentage(p.into())
847 }
848}
849
850impl From<LengthPercentage> for Size {
851 fn from(lp: LengthPercentage) -> Self {
852 Self::LengthPercentage(lp)
853 }
854}
855
856impl From<MaxContent> for Size {
857 fn from(_: MaxContent) -> Self {
858 Self::MaxContent
859 }
860}
861
862impl From<FitContent> for Size {
863 fn from(fc: FitContent) -> Self {
864 Self::FitContent(fc)
865 }
866}
867
868#[derive(Clone, Debug, PartialEq)]
872pub enum FlexBasis {
873 Auto,
875 Content,
877 LengthPercentage(LengthPercentage),
879}
880
881impl ToCss for FlexBasis {
882 fn to_css(&self, dest: &mut dyn fmt::Write) -> fmt::Result {
883 match self {
884 FlexBasis::Auto => dest.write_str("auto"),
885 FlexBasis::Content => dest.write_str("content"),
886 FlexBasis::LengthPercentage(lp) => lp.to_css(dest),
887 }
888 }
889}
890
891impl From<Length> for FlexBasis {
892 fn from(l: Length) -> Self {
893 Self::LengthPercentage(l.into())
894 }
895}
896
897impl From<Percentage> for FlexBasis {
898 fn from(p: Percentage) -> Self {
899 Self::LengthPercentage(p.into())
900 }
901}
902
903impl From<LengthPercentage> for FlexBasis {
904 fn from(lp: LengthPercentage) -> Self {
905 Self::LengthPercentage(lp)
906 }
907}
908
909#[derive(Clone, Debug, PartialEq)]
913pub enum LineHeight {
914 Normal,
916 Number(f32),
918 LengthPercentage(LengthPercentage),
920}
921
922impl ToCss for LineHeight {
923 fn to_css(&self, dest: &mut dyn fmt::Write) -> fmt::Result {
924 match self {
925 LineHeight::Normal => dest.write_str("normal"),
926 LineHeight::Number(n) => write_number(dest, *n),
927 LineHeight::LengthPercentage(lp) => lp.to_css(dest),
928 }
929 }
930}
931
932impl From<Length> for LineHeight {
933 fn from(l: Length) -> Self {
934 Self::LengthPercentage(l.into())
935 }
936}
937
938impl From<Percentage> for LineHeight {
939 fn from(p: Percentage) -> Self {
940 Self::LengthPercentage(p.into())
941 }
942}
943
944impl From<LengthPercentage> for LineHeight {
945 fn from(lp: LengthPercentage) -> Self {
946 Self::LengthPercentage(lp)
947 }
948}
949
950impl From<f32> for LineHeight {
951 fn from(v: f32) -> Self {
952 Self::Number(v)
953 }
954}
955
956#[derive(Clone, Debug, PartialEq)]
962pub enum ImageRef {
963 None,
965 Url(CssString),
967 Gradient(crate::Gradient),
969}
970
971impl ToCss for ImageRef {
972 fn to_css(&self, dest: &mut dyn fmt::Write) -> fmt::Result {
973 match self {
974 ImageRef::None => dest.write_str("none"),
975 ImageRef::Url(s) => {
976 dest.write_str("url(")?;
977 s.to_css(dest)?;
978 dest.write_char(')')
979 }
980 ImageRef::Gradient(g) => g.to_css(dest),
981 }
982 }
983}
984
985impl From<crate::Gradient> for ImageRef {
986 fn from(g: crate::Gradient) -> Self {
987 Self::Gradient(g)
988 }
989}
990
991#[derive(Clone, Debug, PartialEq)]
996pub struct BorderRadius {
997 pub horizontal: [LengthPercentage; 4],
999 pub vertical: Option<[LengthPercentage; 4]>,
1001}
1002
1003impl BorderRadius {
1004 pub fn all(v: impl Into<LengthPercentage>) -> Self {
1006 let v = v.into();
1007 Self {
1008 horizontal: [v.clone(), v.clone(), v.clone(), v],
1009 vertical: None,
1010 }
1011 }
1012
1013 pub fn corners(
1015 tl: impl Into<LengthPercentage>,
1016 tr: impl Into<LengthPercentage>,
1017 br: impl Into<LengthPercentage>,
1018 bl: impl Into<LengthPercentage>,
1019 ) -> Self {
1020 Self {
1021 horizontal: [tl.into(), tr.into(), br.into(), bl.into()],
1022 vertical: None,
1023 }
1024 }
1025
1026 pub fn elliptical(horizontal: [LengthPercentage; 4], vertical: [LengthPercentage; 4]) -> Self {
1028 Self {
1029 horizontal,
1030 vertical: Some(vertical),
1031 }
1032 }
1033}
1034
1035impl ToCss for BorderRadius {
1036 fn to_css(&self, dest: &mut dyn fmt::Write) -> fmt::Result {
1037 write_four(dest, &self.horizontal)?;
1038 if let Some(v) = &self.vertical {
1039 dest.write_str(" / ")?;
1040 write_four(dest, v)?;
1041 }
1042 Ok(())
1043 }
1044}
1045
1046fn write_four(dest: &mut dyn fmt::Write, v: &[LengthPercentage; 4]) -> fmt::Result {
1047 for (i, item) in v.iter().enumerate() {
1048 if i > 0 {
1049 dest.write_char(' ')?;
1050 }
1051 item.to_css(dest)?;
1052 }
1053 Ok(())
1054}
1055
1056#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1062pub enum GridLine {
1063 Auto,
1065 Number(i16),
1067 Span(u16),
1069 Named(String, i16),
1071 NamedSpan(String, u16),
1073}
1074
1075impl ToCss for GridLine {
1076 fn to_css(&self, dest: &mut dyn fmt::Write) -> fmt::Result {
1077 match self {
1078 GridLine::Auto => dest.write_str("auto"),
1079 GridLine::Number(n) => write!(dest, "{n}"),
1080 GridLine::Span(n) => write!(dest, "span {n}"),
1081 GridLine::Named(name, occurrence) if *occurrence == 0 => dest.write_str(name),
1082 GridLine::Named(name, occurrence) => write!(dest, "{occurrence} {name}"),
1083 GridLine::NamedSpan(name, occurrence) if *occurrence == 0 => {
1084 write!(dest, "span {name}")
1085 }
1086 GridLine::NamedSpan(name, occurrence) => write!(dest, "span {occurrence} {name}"),
1087 }
1088 }
1089}
1090
1091#[derive(Clone, Debug, PartialEq)]
1093pub enum GridTrackMin {
1094 Fixed(LengthPercentage),
1096 MinContent,
1098 MaxContent,
1100 Auto,
1102}
1103
1104#[derive(Clone, Debug, PartialEq)]
1106pub enum GridTrackMax {
1107 Fixed(LengthPercentage),
1109 MinContent,
1111 MaxContent,
1113 FitContent(LengthPercentage),
1115 Auto,
1117 Fraction(f32),
1119}
1120
1121#[derive(Clone, Debug, PartialEq)]
1123pub struct GridTrack {
1124 pub(crate) min: GridTrackMin,
1125 pub(crate) max: GridTrackMax,
1126}
1127
1128impl GridTrack {
1129 pub const fn auto() -> Self {
1131 Self {
1132 min: GridTrackMin::Auto,
1133 max: GridTrackMax::Auto,
1134 }
1135 }
1136
1137 pub const fn min_content() -> Self {
1139 Self {
1140 min: GridTrackMin::MinContent,
1141 max: GridTrackMax::MinContent,
1142 }
1143 }
1144
1145 pub const fn max_content() -> Self {
1147 Self {
1148 min: GridTrackMin::MaxContent,
1149 max: GridTrackMax::MaxContent,
1150 }
1151 }
1152
1153 pub fn fixed(value: impl Into<LengthPercentage>) -> Self {
1155 let value = value.into();
1156 Self {
1157 min: GridTrackMin::Fixed(value.clone()),
1158 max: GridTrackMax::Fixed(value),
1159 }
1160 }
1161
1162 pub const fn fraction(value: f32) -> Self {
1164 Self {
1165 min: GridTrackMin::Auto,
1166 max: GridTrackMax::Fraction(value),
1167 }
1168 }
1169
1170 pub fn fit_content(limit: impl Into<LengthPercentage>) -> Self {
1172 Self {
1173 min: GridTrackMin::Auto,
1174 max: GridTrackMax::FitContent(limit.into()),
1175 }
1176 }
1177
1178 pub const fn minmax(min: GridTrackMin, max: GridTrackMax) -> Self {
1180 Self { min, max }
1181 }
1182}
1183
1184impl From<Length> for GridTrack {
1185 fn from(value: Length) -> Self {
1186 Self::fixed(value)
1187 }
1188}
1189
1190impl From<Percentage> for GridTrack {
1191 fn from(value: Percentage) -> Self {
1192 Self::fixed(value)
1193 }
1194}
1195
1196impl From<LengthPercentage> for GridTrack {
1197 fn from(value: LengthPercentage) -> Self {
1198 Self::fixed(value)
1199 }
1200}
1201
1202impl From<Length> for GridTrackMin {
1203 fn from(value: Length) -> Self {
1204 Self::Fixed(value.into())
1205 }
1206}
1207
1208impl From<Percentage> for GridTrackMin {
1209 fn from(value: Percentage) -> Self {
1210 Self::Fixed(value.into())
1211 }
1212}
1213
1214impl From<LengthPercentage> for GridTrackMin {
1215 fn from(value: LengthPercentage) -> Self {
1216 Self::Fixed(value)
1217 }
1218}
1219
1220impl From<Length> for GridTrackMax {
1221 fn from(value: Length) -> Self {
1222 Self::Fixed(value.into())
1223 }
1224}
1225
1226impl From<Percentage> for GridTrackMax {
1227 fn from(value: Percentage) -> Self {
1228 Self::Fixed(value.into())
1229 }
1230}
1231
1232impl From<LengthPercentage> for GridTrackMax {
1233 fn from(value: LengthPercentage) -> Self {
1234 Self::Fixed(value)
1235 }
1236}
1237
1238impl ToCss for GridTrack {
1239 fn to_css(&self, dest: &mut dyn fmt::Write) -> fmt::Result {
1240 match (&self.min, &self.max) {
1241 (GridTrackMin::Auto, GridTrackMax::Auto) => dest.write_str("auto"),
1242 (GridTrackMin::MinContent, GridTrackMax::MinContent) => dest.write_str("min-content"),
1243 (GridTrackMin::MaxContent, GridTrackMax::MaxContent) => dest.write_str("max-content"),
1244 (GridTrackMin::Fixed(min), GridTrackMax::Fixed(max)) if min == max => min.to_css(dest),
1245 (GridTrackMin::Auto, GridTrackMax::Fraction(value)) => {
1246 write_number(dest, *value)?;
1247 dest.write_str("fr")
1248 }
1249 (GridTrackMin::Auto, GridTrackMax::FitContent(limit)) => {
1250 dest.write_str("fit-content(")?;
1251 limit.to_css(dest)?;
1252 dest.write_char(')')
1253 }
1254 (min, max) => {
1255 dest.write_str("minmax(")?;
1256 min.to_css(dest)?;
1257 dest.write_str(", ")?;
1258 max.to_css(dest)?;
1259 dest.write_char(')')
1260 }
1261 }
1262 }
1263}
1264
1265impl ToCss for GridTrackMin {
1266 fn to_css(&self, dest: &mut dyn fmt::Write) -> fmt::Result {
1267 match self {
1268 Self::Fixed(value) => value.to_css(dest),
1269 Self::MinContent => dest.write_str("min-content"),
1270 Self::MaxContent => dest.write_str("max-content"),
1271 Self::Auto => dest.write_str("auto"),
1272 }
1273 }
1274}
1275
1276impl ToCss for GridTrackMax {
1277 fn to_css(&self, dest: &mut dyn fmt::Write) -> fmt::Result {
1278 match self {
1279 Self::Fixed(value) => value.to_css(dest),
1280 Self::MinContent => dest.write_str("min-content"),
1281 Self::MaxContent => dest.write_str("max-content"),
1282 Self::FitContent(limit) => {
1283 dest.write_str("fit-content(")?;
1284 limit.to_css(dest)?;
1285 dest.write_char(')')
1286 }
1287 Self::Auto => dest.write_str("auto"),
1288 Self::Fraction(value) => {
1289 write_number(dest, *value)?;
1290 dest.write_str("fr")
1291 }
1292 }
1293 }
1294}
1295
1296#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
1298pub enum GridRepeatCount {
1299 Count(u16),
1301 AutoFill,
1303 AutoFit,
1305}
1306
1307#[derive(Clone, Debug, PartialEq)]
1309pub enum GridTemplateComponent {
1310 Track(GridTrack),
1312 Repeat {
1314 count: GridRepeatCount,
1316 tracks: Vec<GridTrack>,
1318 line_names: Vec<Vec<String>>,
1320 },
1321}
1322
1323impl From<GridTrack> for GridTemplateComponent {
1324 fn from(value: GridTrack) -> Self {
1325 Self::Track(value)
1326 }
1327}
1328
1329#[derive(Clone, Debug, PartialEq)]
1331pub struct GridTemplate {
1332 pub(crate) components: Vec<GridTemplateComponent>,
1333 pub(crate) line_names: Vec<Vec<String>>,
1334}
1335
1336impl GridTemplate {
1337 pub fn tracks(tracks: impl IntoIterator<Item = impl Into<GridTrack>>) -> Self {
1340 let components: Vec<_> = tracks
1341 .into_iter()
1342 .map(|track| track.into().into())
1343 .collect();
1344 let line_names = vec![Vec::new(); components.len() + 1];
1345 Self {
1346 components,
1347 line_names,
1348 }
1349 }
1350
1351 pub fn components(components: impl IntoIterator<Item = GridTemplateComponent>) -> Self {
1353 let components: Vec<_> = components.into_iter().collect();
1354 let line_names = vec![Vec::new(); components.len() + 1];
1355 Self {
1356 components,
1357 line_names,
1358 }
1359 }
1360
1361 pub fn repeat(
1363 count: GridRepeatCount,
1364 tracks: impl IntoIterator<Item = impl Into<GridTrack>>,
1365 ) -> Self {
1366 let tracks: Vec<_> = tracks.into_iter().map(Into::into).collect();
1367 let line_names = vec![Vec::new(); tracks.len() + 1];
1368 Self::components([GridTemplateComponent::Repeat {
1369 count,
1370 tracks,
1371 line_names,
1372 }])
1373 }
1374
1375 pub fn line_names(
1378 mut self,
1379 line_names: impl IntoIterator<Item = impl IntoIterator<Item = impl Into<String>>>,
1380 ) -> Self {
1381 self.line_names = line_names
1382 .into_iter()
1383 .map(|names| names.into_iter().map(Into::into).collect())
1384 .collect();
1385 self
1386 }
1387}
1388
1389impl ToCss for GridTemplate {
1390 fn to_css(&self, dest: &mut dyn fmt::Write) -> fmt::Result {
1391 for (index, component) in self.components.iter().enumerate() {
1392 if index > 0 {
1393 dest.write_char(' ')?;
1394 }
1395 write_grid_line_names(dest, self.line_names.get(index))?;
1396 if !self.line_names.get(index).is_none_or(Vec::is_empty) {
1397 dest.write_char(' ')?;
1398 }
1399 component.to_css(dest)?;
1400 }
1401 if !self
1402 .line_names
1403 .get(self.components.len())
1404 .is_none_or(Vec::is_empty)
1405 {
1406 if !self.components.is_empty() {
1407 dest.write_char(' ')?;
1408 }
1409 write_grid_line_names(dest, self.line_names.get(self.components.len()))?;
1410 }
1411 Ok(())
1412 }
1413}
1414
1415impl ToCss for GridTemplateComponent {
1416 fn to_css(&self, dest: &mut dyn fmt::Write) -> fmt::Result {
1417 match self {
1418 Self::Track(track) => track.to_css(dest),
1419 Self::Repeat {
1420 count,
1421 tracks,
1422 line_names,
1423 } => {
1424 dest.write_str("repeat(")?;
1425 count.to_css(dest)?;
1426 dest.write_str(", ")?;
1427 for (index, track) in tracks.iter().enumerate() {
1428 if index > 0 {
1429 dest.write_char(' ')?;
1430 }
1431 write_grid_line_names(dest, line_names.get(index))?;
1432 if !line_names.get(index).is_none_or(Vec::is_empty) {
1433 dest.write_char(' ')?;
1434 }
1435 track.to_css(dest)?;
1436 }
1437 if !line_names.get(tracks.len()).is_none_or(Vec::is_empty) {
1438 if !tracks.is_empty() {
1439 dest.write_char(' ')?;
1440 }
1441 write_grid_line_names(dest, line_names.get(tracks.len()))?;
1442 }
1443 dest.write_char(')')
1444 }
1445 }
1446 }
1447}
1448
1449impl ToCss for GridRepeatCount {
1450 fn to_css(&self, dest: &mut dyn fmt::Write) -> fmt::Result {
1451 match self {
1452 Self::Count(value) => write!(dest, "{value}"),
1453 Self::AutoFill => dest.write_str("auto-fill"),
1454 Self::AutoFit => dest.write_str("auto-fit"),
1455 }
1456 }
1457}
1458
1459fn write_grid_line_names(dest: &mut dyn fmt::Write, names: Option<&Vec<String>>) -> fmt::Result {
1460 let Some(names) = names.filter(|names| !names.is_empty()) else {
1461 return Ok(());
1462 };
1463 dest.write_char('[')?;
1464 for (index, name) in names.iter().enumerate() {
1465 if index > 0 {
1466 dest.write_char(' ')?;
1467 }
1468 dest.write_str(name)?;
1469 }
1470 dest.write_char(']')
1471}
1472
1473#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1475pub struct GridArea {
1476 pub(crate) name: String,
1477 pub(crate) row_start: u16,
1478 pub(crate) row_end: u16,
1479 pub(crate) column_start: u16,
1480 pub(crate) column_end: u16,
1481}
1482
1483impl GridArea {
1484 pub fn new(
1486 name: impl Into<String>,
1487 row_start: u16,
1488 row_end: u16,
1489 column_start: u16,
1490 column_end: u16,
1491 ) -> Self {
1492 Self {
1493 name: name.into(),
1494 row_start,
1495 row_end,
1496 column_start,
1497 column_end,
1498 }
1499 }
1500}
1501
1502#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1504pub struct GridTemplateAreas {
1505 pub(crate) row_count: u16,
1506 pub(crate) column_count: u16,
1507 pub(crate) areas: Vec<GridArea>,
1508}
1509
1510impl GridTemplateAreas {
1511 pub const fn new(row_count: u16, column_count: u16) -> Self {
1513 Self {
1514 row_count,
1515 column_count,
1516 areas: Vec::new(),
1517 }
1518 }
1519
1520 pub fn area(mut self, area: GridArea) -> Self {
1522 self.areas.push(area);
1523 self
1524 }
1525}
1526
1527impl ToCss for GridTemplateAreas {
1528 fn to_css(&self, dest: &mut dyn fmt::Write) -> fmt::Result {
1529 for row in 0..self.row_count {
1530 if row > 0 {
1531 dest.write_char(' ')?;
1532 }
1533 dest.write_char('"')?;
1534 for column in 0..self.column_count {
1535 if column > 0 {
1536 dest.write_char(' ')?;
1537 }
1538 let name = self
1539 .areas
1540 .iter()
1541 .rev()
1542 .find(|area| {
1543 area.row_start <= row
1544 && row < area.row_end
1545 && area.column_start <= column
1546 && column < area.column_end
1547 })
1548 .map_or(".", |area| area.name.as_str());
1549 dest.write_str(name)?;
1550 }
1551 dest.write_char('"')?;
1552 }
1553 Ok(())
1554 }
1555}
1556
1557#[derive(Clone, Debug, PartialEq)]
1562pub struct Repeated<T>(pub Vec<T>);
1563
1564impl<T> Repeated<T> {
1565 pub fn new(v: impl IntoIterator<Item = T>) -> Self {
1567 Self(v.into_iter().collect())
1568 }
1569}
1570
1571impl<T: ToCss> ToCss for Repeated<T> {
1572 fn to_css(&self, dest: &mut dyn fmt::Write) -> fmt::Result {
1573 for (i, item) in self.0.iter().enumerate() {
1574 if i > 0 {
1575 dest.write_str(", ")?;
1576 }
1577 item.to_css(dest)?;
1578 }
1579 Ok(())
1580 }
1581}
1582
1583#[cfg(test)]
1584mod tests {
1585 use super::*;
1586 use crate::data_type::{ColorStop, Gradient, Length, NamedColor};
1587 use crate::ext::*;
1588
1589 #[test]
1590 fn size_keywords() {
1591 assert_eq!(Size::Auto.to_css_string(), "auto");
1592 assert_eq!(Size::MaxContent.to_css_string(), "max-content");
1593 assert_eq!(Size::MinContent.to_css_string(), "min-content");
1594 assert_eq!(Size::None.to_css_string(), "none");
1595 }
1596
1597 #[test]
1598 fn size_from_lengths_and_percentages() {
1599 let from_len: Size = px(8).into();
1600 let from_pct: Size = 50.percent().into();
1601 let from_lp: Size = LengthPercentage::Length(Length::Px(4.0)).into();
1602 let from_mc: Size = MaxContent.into();
1603 let from_fc: Size = FitContent::keyword().into();
1604 assert_eq!(from_len.to_css_string(), "8px");
1605 assert_eq!(from_pct.to_css_string(), "50%");
1606 assert_eq!(from_lp.to_css_string(), "4px");
1607 assert_eq!(from_mc.to_css_string(), "max-content");
1608 assert_eq!(from_fc.to_css_string(), "fit-content");
1609 }
1610
1611 #[test]
1612 fn size_fit_content_with_limit() {
1613 let s = Size::FitContent(FitContent::with_limit(px(200)));
1614 assert_eq!(s.to_css_string(), "fit-content(200px)");
1615 }
1616
1617 #[test]
1618 fn flex_basis_variants() {
1619 assert_eq!(FlexBasis::Auto.to_css_string(), "auto");
1620 assert_eq!(FlexBasis::Content.to_css_string(), "content");
1621 let from_len: FlexBasis = px(120).into();
1622 let from_pct: FlexBasis = 25.percent().into();
1623 let from_lp: FlexBasis = LengthPercentage::Length(Length::Px(8.0)).into();
1624 assert_eq!(from_len.to_css_string(), "120px");
1625 assert_eq!(from_pct.to_css_string(), "25%");
1626 assert_eq!(from_lp.to_css_string(), "8px");
1627 }
1628
1629 #[test]
1630 fn line_height_variants() {
1631 assert_eq!(LineHeight::Normal.to_css_string(), "normal");
1632 let n: LineHeight = 1.5_f32.into();
1633 let from_len: LineHeight = px(20).into();
1634 let from_pct: LineHeight = 150.percent().into();
1635 let from_lp: LineHeight = LengthPercentage::Length(Length::Px(10.0)).into();
1636 assert_eq!(n.to_css_string(), "1.5");
1637 assert_eq!(from_len.to_css_string(), "20px");
1638 assert_eq!(from_pct.to_css_string(), "150%");
1639 assert_eq!(from_lp.to_css_string(), "10px");
1640 }
1641
1642 #[test]
1643 fn image_ref_variants() {
1644 assert_eq!(ImageRef::None.to_css_string(), "none");
1645 assert_eq!(
1646 ImageRef::Url(CssString::new("a.png")).to_css_string(),
1647 "url(\"a.png\")"
1648 );
1649 let g = Gradient::linear_to_bottom([ColorStop::new(crate::Color::Named(NamedColor::Red))]);
1650 let r: ImageRef = g.into();
1651 assert_eq!(r.to_css_string(), "linear-gradient(to bottom, red)");
1652 }
1653
1654 #[test]
1655 fn border_radius_uniform() {
1656 let r = BorderRadius::all(px(8));
1657 assert_eq!(r.to_css_string(), "8px 8px 8px 8px");
1658 }
1659
1660 #[test]
1661 fn border_radius_corners() {
1662 let r = BorderRadius::corners(px(2), px(4), px(6), px(8));
1663 assert_eq!(r.to_css_string(), "2px 4px 6px 8px");
1664 }
1665
1666 #[test]
1667 fn border_radius_elliptical() {
1668 let h = [px(2).into(), px(4).into(), px(6).into(), px(8).into()];
1669 let v = [px(20).into(), px(40).into(), px(60).into(), px(80).into()];
1670 let r = BorderRadius::elliptical(h, v);
1671 assert_eq!(r.to_css_string(), "2px 4px 6px 8px / 20px 40px 60px 80px");
1672 }
1673
1674 #[test]
1675 fn grid_line_variants() {
1676 assert_eq!(GridLine::Auto.to_css_string(), "auto");
1677 assert_eq!(GridLine::Number(1).to_css_string(), "1");
1678 assert_eq!(GridLine::Number(-1).to_css_string(), "-1");
1679 assert_eq!(GridLine::Span(2).to_css_string(), "span 2");
1680 assert_eq!(
1681 GridLine::Named("content".into(), 0).to_css_string(),
1682 "content"
1683 );
1684 assert_eq!(
1685 GridLine::NamedSpan("content".into(), 2).to_css_string(),
1686 "span 2 content"
1687 );
1688 }
1689
1690 #[test]
1691 fn grid_template_joins_tracks() {
1692 let t = GridTemplate::tracks([
1693 GridTrack::fraction(1.0),
1694 GridTrack::auto(),
1695 GridTrack::fraction(2.0),
1696 ]);
1697 assert_eq!(t.to_css_string(), "1fr auto 2fr");
1698 }
1699
1700 #[test]
1701 fn repeated_serializes_with_commas() {
1702 let r = Repeated::new([Length::Px(8.0), Length::Px(16.0)]);
1703 assert_eq!(r.to_css_string(), "8px, 16px");
1704 }
1705}