1use crate::corety::AzString;
8use alloc::string::{String, ToString};
9use core::fmt;
10
11use crate::{
12 codegen::format::FormatAsRustCode,
13 props::{
14 basic::{
15 error::{InvalidValueErr, InvalidValueErrOwned},
16 length::{PercentageParseError, PercentageParseErrorOwned, PercentageValue},
17 pixel::{CssPixelValueParseError, CssPixelValueParseErrorOwned, PixelValue},
18 ColorU, CssDuration,
19 },
20 formatter::PrintAsCssValue,
21 macros::PixelValueTaker,
22 },
23};
24
25#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
31#[repr(C)]
32pub struct StyleTextColor {
33 pub inner: ColorU,
34}
35
36impl fmt::Debug for StyleTextColor {
37 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38 write!(f, "{}", self.print_as_css_value())
39 }
40}
41
42impl StyleTextColor {
43 #[must_use]
44 pub fn interpolate(&self, other: &Self, t: f32) -> Self {
45 Self {
46 inner: self.inner.interpolate(&other.inner, t),
47 }
48 }
49}
50
51impl PrintAsCssValue for StyleTextColor {
52 fn print_as_css_value(&self) -> String {
53 self.inner.to_hash()
54 }
55}
56
57#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
61#[repr(C)]
62pub enum StyleTextAlign {
63 Left,
64 Center,
65 Right,
66 Justify,
67 #[default]
68 Start,
69 End,
70}
71
72impl_option!(
73 StyleTextAlign,
74 OptionStyleTextAlign,
75 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
76);
77
78impl PrintAsCssValue for StyleTextAlign {
79 fn print_as_css_value(&self) -> String {
80 String::from(match self {
81 Self::Left => "left",
82 Self::Center => "center",
83 Self::Right => "right",
84 Self::Justify => "justify",
85 Self::Start => "start",
86 Self::End => "end",
87 })
88 }
89}
90
91#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
95#[repr(C)]
96pub struct StyleLetterSpacing {
97 pub inner: PixelValue,
98}
99
100impl fmt::Debug for StyleLetterSpacing {
101 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102 write!(f, "{}", self.inner)
103 }
104}
105impl Default for StyleLetterSpacing {
106 fn default() -> Self {
107 Self {
108 inner: PixelValue::const_px(0),
109 }
110 }
111}
112impl_pixel_value!(StyleLetterSpacing);
113impl PixelValueTaker for StyleLetterSpacing {
114 fn from_pixel_value(inner: PixelValue) -> Self {
115 Self { inner }
116 }
117}
118impl PrintAsCssValue for StyleLetterSpacing {
119 fn print_as_css_value(&self) -> String {
120 format!("{}", self.inner)
121 }
122}
123
124#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
128#[repr(C)]
129pub struct StyleWordSpacing {
130 pub inner: PixelValue,
131}
132
133impl fmt::Debug for StyleWordSpacing {
134 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135 write!(f, "{}", self.inner)
136 }
137}
138impl Default for StyleWordSpacing {
139 fn default() -> Self {
140 Self {
141 inner: PixelValue::const_px(0),
142 }
143 }
144}
145impl_pixel_value!(StyleWordSpacing);
146impl PixelValueTaker for StyleWordSpacing {
147 fn from_pixel_value(inner: PixelValue) -> Self {
148 Self { inner }
149 }
150}
151impl PrintAsCssValue for StyleWordSpacing {
152 fn print_as_css_value(&self) -> String {
153 format!("{}", self.inner)
154 }
155}
156
157#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
161#[repr(C)]
162pub struct StyleLineHeight {
163 pub inner: PercentageValue,
164}
165impl Default for StyleLineHeight {
166 fn default() -> Self {
167 Self {
168 inner: PercentageValue::const_new(120),
169 }
170 }
171}
172impl_percentage_value!(StyleLineHeight);
173impl PrintAsCssValue for StyleLineHeight {
174 fn print_as_css_value(&self) -> String {
175 format!("{}", self.inner)
176 }
177}
178
179#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
183#[repr(C)]
184pub struct StyleTabSize {
185 pub inner: PixelValue, }
187
188impl fmt::Debug for StyleTabSize {
189 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
190 write!(f, "{}", self.inner)
191 }
192}
193impl Default for StyleTabSize {
194 fn default() -> Self {
195 Self {
196 inner: PixelValue::em(8.0),
197 }
198 }
199}
200impl_pixel_value!(StyleTabSize);
201impl PixelValueTaker for StyleTabSize {
202 fn from_pixel_value(inner: PixelValue) -> Self {
203 Self { inner }
204 }
205}
206impl PrintAsCssValue for StyleTabSize {
207 fn print_as_css_value(&self) -> String {
208 format!("{}", self.inner)
209 }
210}
211
212#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
218#[repr(C)]
219#[derive(Default)]
220pub enum StyleWhiteSpace {
221 #[default]
223 Normal,
224 Pre,
226 Nowrap,
228 PreWrap,
230 PreLine,
232 BreakSpaces,
234}
235impl_option!(
236 StyleWhiteSpace,
237 OptionStyleWhiteSpace,
238 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
239);
240impl PrintAsCssValue for StyleWhiteSpace {
241 fn print_as_css_value(&self) -> String {
242 String::from(match self {
243 Self::Normal => "normal",
244 Self::Pre => "pre",
245 Self::Nowrap => "nowrap",
246 Self::PreWrap => "pre-wrap",
247 Self::PreLine => "pre-line",
248 Self::BreakSpaces => "break-spaces",
249 })
250 }
251}
252
253#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
257#[repr(C)]
258#[derive(Default)]
259pub enum StyleHyphens {
260 None,
262 #[default]
265 Manual,
266 Auto,
270}
271impl_option!(
272 StyleHyphens,
273 OptionStyleHyphens,
274 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
275);
276impl PrintAsCssValue for StyleHyphens {
277 fn print_as_css_value(&self) -> String {
278 String::from(match self {
279 Self::None => "none",
280 Self::Manual => "manual",
281 Self::Auto => "auto",
282 })
283 }
284}
285
286#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
292#[repr(C)]
293#[derive(Default)]
294pub enum StyleLineBreak {
295 #[default]
297 Auto,
298 Loose,
300 Normal,
302 Strict,
304 Anywhere,
308}
309impl_option!(
310 StyleLineBreak,
311 OptionStyleLineBreak,
312 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
313);
314impl PrintAsCssValue for StyleLineBreak {
315 fn print_as_css_value(&self) -> String {
316 String::from(match self {
317 Self::Auto => "auto",
318 Self::Loose => "loose",
319 Self::Normal => "normal",
320 Self::Strict => "strict",
321 Self::Anywhere => "anywhere",
322 })
323 }
324}
325
326#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
332#[repr(C)]
333#[derive(Default)]
334pub enum StyleWordBreak {
335 #[default]
337 Normal,
338 BreakAll,
340 KeepAll,
342 BreakWord,
345}
346impl_option!(
347 StyleWordBreak,
348 OptionStyleWordBreak,
349 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
350);
351impl PrintAsCssValue for StyleWordBreak {
352 fn print_as_css_value(&self) -> String {
353 String::from(match self {
354 Self::Normal => "normal",
355 Self::BreakAll => "break-all",
356 Self::KeepAll => "keep-all",
357 Self::BreakWord => "break-word",
358 })
359 }
360}
361
362#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
369#[repr(C)]
370#[derive(Default)]
371pub enum StyleOverflowWrap {
372 #[default]
374 Normal,
375 Anywhere,
378 BreakWord,
381}
382impl_option!(
383 StyleOverflowWrap,
384 OptionStyleOverflowWrap,
385 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
386);
387impl PrintAsCssValue for StyleOverflowWrap {
388 fn print_as_css_value(&self) -> String {
389 String::from(match self {
390 Self::Normal => "normal",
391 Self::Anywhere => "anywhere",
392 Self::BreakWord => "break-word",
393 })
394 }
395}
396
397#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
404#[repr(C)]
405#[derive(Default)]
406pub enum StyleTextAlignLast {
407 #[default]
409 Auto,
410 Start,
412 End,
414 Left,
416 Right,
418 Center,
420 Justify,
422}
423impl_option!(
424 StyleTextAlignLast,
425 OptionStyleTextAlignLast,
426 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
427);
428impl PrintAsCssValue for StyleTextAlignLast {
429 fn print_as_css_value(&self) -> String {
430 String::from(match self {
431 Self::Auto => "auto",
432 Self::Start => "start",
433 Self::End => "end",
434 Self::Left => "left",
435 Self::Right => "right",
436 Self::Center => "center",
437 Self::Justify => "justify",
438 })
439 }
440}
441
442#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
448#[repr(C)]
449#[derive(Default)]
450pub enum StyleTextTransform {
451 #[default]
453 None,
454 Capitalize,
456 Uppercase,
458 Lowercase,
460 FullWidth,
462}
463impl_option!(
464 StyleTextTransform,
465 OptionStyleTextTransform,
466 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
467);
468impl PrintAsCssValue for StyleTextTransform {
469 fn print_as_css_value(&self) -> String {
470 String::from(match self {
471 Self::None => "none",
472 Self::Capitalize => "capitalize",
473 Self::Uppercase => "uppercase",
474 Self::Lowercase => "lowercase",
475 Self::FullWidth => "full-width",
476 })
477 }
478}
479
480#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
485#[repr(C)]
486#[derive(Default)]
487pub enum StyleDirection {
488 #[default]
490 Ltr,
491 Rtl,
493}
494impl_option!(
495 StyleDirection,
496 OptionStyleDirection,
497 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
498);
499impl PrintAsCssValue for StyleDirection {
500 fn print_as_css_value(&self) -> String {
501 String::from(match self {
502 Self::Ltr => "ltr",
503 Self::Rtl => "rtl",
504 })
505 }
506}
507
508#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
513#[repr(C)]
514#[derive(Default)]
515pub enum StyleUserSelect {
516 #[default]
518 Auto,
519 Text,
521 None,
523 All,
525}
526impl_option!(
527 StyleUserSelect,
528 OptionStyleUserSelect,
529 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
530);
531impl PrintAsCssValue for StyleUserSelect {
532 fn print_as_css_value(&self) -> String {
533 String::from(match self {
534 Self::Auto => "auto",
535 Self::Text => "text",
536 Self::None => "none",
537 Self::All => "all",
538 })
539 }
540}
541
542#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
546#[repr(C)]
547#[derive(Default)]
548pub enum StyleTextDecoration {
549 #[default]
551 None,
552 Underline,
554 Overline,
556 LineThrough,
558}
559impl_option!(
560 StyleTextDecoration,
561 OptionStyleTextDecoration,
562 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
563);
564impl PrintAsCssValue for StyleTextDecoration {
565 fn print_as_css_value(&self) -> String {
566 String::from(match self {
567 Self::None => "none",
568 Self::Underline => "underline",
569 Self::Overline => "overline",
570 Self::LineThrough => "line-through",
571 })
572 }
573}
574
575#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
579#[repr(C, u8)]
580#[derive(Default)]
581pub enum StyleVerticalAlign {
582 #[default]
584 Baseline,
585 Top,
587 Middle,
589 Bottom,
591 Sub,
593 Superscript,
595 TextTop,
597 TextBottom,
599 Percentage(PercentageValue),
601 Length(PixelValue),
603}
604
605impl_option!(
606 StyleVerticalAlign,
607 OptionStyleVerticalAlign,
608 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
609);
610
611impl PrintAsCssValue for StyleVerticalAlign {
612 fn print_as_css_value(&self) -> String {
613 match self {
614 Self::Baseline => String::from("baseline"),
615 Self::Top => String::from("top"),
616 Self::Middle => String::from("middle"),
617 Self::Bottom => String::from("bottom"),
618 Self::Sub => String::from("sub"),
619 Self::Superscript => String::from("super"),
620 Self::TextTop => String::from("text-top"),
621 Self::TextBottom => String::from("text-bottom"),
622 Self::Percentage(p) => format!("{}%", p.normalized() * 100.0),
623 Self::Length(l) => l.print_as_css_value(),
624 }
625 }
626}
627
628impl FormatAsRustCode for StyleVerticalAlign {
629 fn format_as_rust_code(&self, indent: usize) -> String {
630 match self {
631 Self::Baseline => "StyleVerticalAlign::Baseline".to_string(),
632 Self::Top => "StyleVerticalAlign::Top".to_string(),
633 Self::Middle => "StyleVerticalAlign::Middle".to_string(),
634 Self::Bottom => "StyleVerticalAlign::Bottom".to_string(),
635 Self::Sub => "StyleVerticalAlign::Sub".to_string(),
636 Self::Superscript => "StyleVerticalAlign::Superscript".to_string(),
637 Self::TextTop => "StyleVerticalAlign::TextTop".to_string(),
638 Self::TextBottom => "StyleVerticalAlign::TextBottom".to_string(),
639 Self::Percentage(p) => format!(
640 "StyleVerticalAlign::Percentage(PercentageValue::new({}))",
641 p.normalized() * 100.0
642 ),
643 Self::Length(l) => format!("StyleVerticalAlign::Length({l})"),
644 }
645 }
646}
647
648#[cfg(feature = "parser")]
651use crate::props::basic::{
652 color::{parse_css_color, CssColorParseError, CssColorParseErrorOwned},
653 DurationParseError,
654};
655
656#[cfg(feature = "parser")]
657#[derive(Clone, PartialEq)]
658pub enum StyleTextColorParseError<'a> {
659 ColorParseError(CssColorParseError<'a>),
660}
661#[cfg(feature = "parser")]
662impl_debug_as_display!(StyleTextColorParseError<'a>);
663#[cfg(feature = "parser")]
664impl_display! { StyleTextColorParseError<'a>, {
665 ColorParseError(e) => format!("Invalid color: {}", e),
666}}
667#[cfg(feature = "parser")]
668impl_from!(
669 CssColorParseError<'a>,
670 StyleTextColorParseError::ColorParseError
671);
672
673#[cfg(feature = "parser")]
674#[derive(Debug, Clone, PartialEq)]
675#[repr(C, u8)]
676pub enum StyleTextColorParseErrorOwned {
677 ColorParseError(CssColorParseErrorOwned),
678}
679
680#[cfg(feature = "parser")]
681impl StyleTextColorParseError<'_> {
682 #[must_use]
683 pub fn to_contained(&self) -> StyleTextColorParseErrorOwned {
684 match self {
685 Self::ColorParseError(e) => {
686 StyleTextColorParseErrorOwned::ColorParseError(e.to_contained())
687 }
688 }
689 }
690}
691
692#[cfg(feature = "parser")]
693impl StyleTextColorParseErrorOwned {
694 #[must_use]
695 pub fn to_shared(&self) -> StyleTextColorParseError<'_> {
696 match self {
697 Self::ColorParseError(e) => StyleTextColorParseError::ColorParseError(e.to_shared()),
698 }
699 }
700}
701
702#[cfg(feature = "parser")]
703pub fn parse_style_text_color(input: &str) -> Result<StyleTextColor, StyleTextColorParseError<'_>> {
707 parse_css_color(input)
708 .map(|inner| StyleTextColor { inner })
709 .map_err(StyleTextColorParseError::ColorParseError)
710}
711
712#[cfg(feature = "parser")]
713#[derive(Clone, PartialEq, Eq)]
714pub enum StyleTextAlignParseError<'a> {
715 InvalidValue(InvalidValueErr<'a>),
716}
717#[cfg(feature = "parser")]
718impl_debug_as_display!(StyleTextAlignParseError<'a>);
719#[cfg(feature = "parser")]
720impl_display! { StyleTextAlignParseError<'a>, {
721 InvalidValue(e) => format!("Invalid text-align value: \"{}\"", e.0),
722}}
723#[cfg(feature = "parser")]
724impl_from!(InvalidValueErr<'a>, StyleTextAlignParseError::InvalidValue);
725
726#[cfg(feature = "parser")]
727#[derive(Debug, Clone, PartialEq, Eq)]
728#[repr(C, u8)]
729pub enum StyleTextAlignParseErrorOwned {
730 InvalidValue(InvalidValueErrOwned),
731}
732
733#[cfg(feature = "parser")]
734impl StyleTextAlignParseError<'_> {
735 #[must_use]
736 pub fn to_contained(&self) -> StyleTextAlignParseErrorOwned {
737 match self {
738 Self::InvalidValue(e) => StyleTextAlignParseErrorOwned::InvalidValue(e.to_contained()),
739 }
740 }
741}
742
743#[cfg(feature = "parser")]
744impl StyleTextAlignParseErrorOwned {
745 #[must_use]
746 pub fn to_shared(&self) -> StyleTextAlignParseError<'_> {
747 match self {
748 Self::InvalidValue(e) => StyleTextAlignParseError::InvalidValue(e.to_shared()),
749 }
750 }
751}
752
753#[cfg(feature = "parser")]
754pub fn parse_style_text_align(input: &str) -> Result<StyleTextAlign, StyleTextAlignParseError<'_>> {
758 match input.trim() {
759 "left" => Ok(StyleTextAlign::Left),
760 "center" => Ok(StyleTextAlign::Center),
761 "right" => Ok(StyleTextAlign::Right),
762 "justify" => Ok(StyleTextAlign::Justify),
763 "start" => Ok(StyleTextAlign::Start),
764 "end" => Ok(StyleTextAlign::End),
765 other => Err(StyleTextAlignParseError::InvalidValue(InvalidValueErr(
766 other,
767 ))),
768 }
769}
770
771#[cfg(feature = "parser")]
772#[derive(Clone, PartialEq, Eq)]
773pub enum StyleLetterSpacingParseError<'a> {
774 PixelValue(CssPixelValueParseError<'a>),
775}
776#[cfg(feature = "parser")]
777impl_debug_as_display!(StyleLetterSpacingParseError<'a>);
778#[cfg(feature = "parser")]
779impl_display! { StyleLetterSpacingParseError<'a>, {
780 PixelValue(e) => format!("Invalid letter-spacing value: {}", e),
781}}
782#[cfg(feature = "parser")]
783impl_from!(
784 CssPixelValueParseError<'a>,
785 StyleLetterSpacingParseError::PixelValue
786);
787
788#[cfg(feature = "parser")]
789#[derive(Debug, Clone, PartialEq, Eq)]
790#[repr(C, u8)]
791pub enum StyleLetterSpacingParseErrorOwned {
792 PixelValue(CssPixelValueParseErrorOwned),
793}
794
795#[cfg(feature = "parser")]
796impl StyleLetterSpacingParseError<'_> {
797 #[must_use]
798 pub fn to_contained(&self) -> StyleLetterSpacingParseErrorOwned {
799 match self {
800 Self::PixelValue(e) => StyleLetterSpacingParseErrorOwned::PixelValue(e.to_contained()),
801 }
802 }
803}
804
805#[cfg(feature = "parser")]
806impl StyleLetterSpacingParseErrorOwned {
807 #[must_use]
808 pub fn to_shared(&self) -> StyleLetterSpacingParseError<'_> {
809 match self {
810 Self::PixelValue(e) => StyleLetterSpacingParseError::PixelValue(e.to_shared()),
811 }
812 }
813}
814
815#[cfg(feature = "parser")]
816pub fn parse_style_letter_spacing(
820 input: &str,
821) -> Result<StyleLetterSpacing, StyleLetterSpacingParseError<'_>> {
822 crate::props::basic::pixel::parse_pixel_value(input)
823 .map(|inner| StyleLetterSpacing { inner })
824 .map_err(StyleLetterSpacingParseError::PixelValue)
825}
826
827#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
831#[repr(C)]
832pub struct StyleTextIndent {
833 pub inner: PixelValue,
834 pub each_line: bool,
837 pub hanging: bool,
839}
840
841impl fmt::Debug for StyleTextIndent {
842 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
843 write!(f, "{}", self.print_as_css_value())
844 }
845}
846
847impl StyleTextIndent {
848 #[inline]
849 #[must_use]
850 pub const fn zero() -> Self {
851 Self {
852 inner: PixelValue::zero(),
853 each_line: false,
854 hanging: false,
855 }
856 }
857 #[inline]
858 #[must_use]
859 pub const fn const_px(value: isize) -> Self {
860 Self {
861 inner: PixelValue::const_px(value),
862 each_line: false,
863 hanging: false,
864 }
865 }
866 #[inline]
867 #[must_use]
868 pub const fn const_em(value: isize) -> Self {
869 Self {
870 inner: PixelValue::const_em(value),
871 each_line: false,
872 hanging: false,
873 }
874 }
875 #[inline]
876 #[must_use]
877 pub const fn const_pt(value: isize) -> Self {
878 Self {
879 inner: PixelValue::const_pt(value),
880 each_line: false,
881 hanging: false,
882 }
883 }
884 #[inline]
885 #[must_use]
886 pub const fn const_percent(value: isize) -> Self {
887 Self {
888 inner: PixelValue::const_percent(value),
889 each_line: false,
890 hanging: false,
891 }
892 }
893 #[inline]
894 #[must_use]
895 pub const fn const_in(value: isize) -> Self {
896 Self {
897 inner: PixelValue::const_in(value),
898 each_line: false,
899 hanging: false,
900 }
901 }
902 #[inline]
903 #[must_use]
904 pub const fn const_cm(value: isize) -> Self {
905 Self {
906 inner: PixelValue::const_cm(value),
907 each_line: false,
908 hanging: false,
909 }
910 }
911 #[inline]
912 #[must_use]
913 pub const fn const_mm(value: isize) -> Self {
914 Self {
915 inner: PixelValue::const_mm(value),
916 each_line: false,
917 hanging: false,
918 }
919 }
920 #[inline]
921 #[must_use]
922 pub const fn const_from_metric(
923 metric: crate::props::basic::length::SizeMetric,
924 value: isize,
925 ) -> Self {
926 Self {
927 inner: PixelValue::const_from_metric(metric, value),
928 each_line: false,
929 hanging: false,
930 }
931 }
932 #[inline]
933 #[must_use]
934 pub fn px(value: f32) -> Self {
935 Self {
936 inner: PixelValue::px(value),
937 each_line: false,
938 hanging: false,
939 }
940 }
941 #[inline]
942 #[must_use]
943 pub fn em(value: f32) -> Self {
944 Self {
945 inner: PixelValue::em(value),
946 each_line: false,
947 hanging: false,
948 }
949 }
950 #[inline]
951 #[must_use]
952 pub fn pt(value: f32) -> Self {
953 Self {
954 inner: PixelValue::pt(value),
955 each_line: false,
956 hanging: false,
957 }
958 }
959 #[inline]
960 #[must_use]
961 pub fn percent(value: f32) -> Self {
962 Self {
963 inner: PixelValue::percent(value),
964 each_line: false,
965 hanging: false,
966 }
967 }
968 #[inline]
969 #[must_use]
970 pub fn from_metric(metric: crate::props::basic::length::SizeMetric, value: f32) -> Self {
971 Self {
972 inner: PixelValue::from_metric(metric, value),
973 each_line: false,
974 hanging: false,
975 }
976 }
977 #[inline]
978 #[must_use]
979 pub fn interpolate(&self, other: &Self, t: f32) -> Self {
980 Self {
981 inner: self.inner.interpolate(&other.inner, t),
982 each_line: self.each_line,
983 hanging: self.hanging,
984 }
985 }
986}
987
988impl PrintAsCssValue for StyleTextIndent {
989 fn print_as_css_value(&self) -> String {
990 let mut s = self.inner.to_string();
991 if self.hanging {
992 s.push_str(" hanging");
993 }
994 if self.each_line {
995 s.push_str(" each-line");
996 }
997 s
998 }
999}
1000
1001impl FormatAsRustCode for StyleTextIndent {
1002 fn format_as_rust_code(&self, _tabs: usize) -> String {
1003 format!(
1004 "StyleTextIndent {{ inner: {}, each_line: {}, hanging: {} }}",
1005 self.inner.format_as_rust_code(0),
1006 self.each_line,
1007 self.hanging
1008 )
1009 }
1010}
1011
1012#[cfg(feature = "parser")]
1013#[derive(Clone, PartialEq, Eq)]
1014pub enum StyleTextIndentParseError<'a> {
1015 PixelValue(CssPixelValueParseError<'a>),
1016}
1017#[cfg(feature = "parser")]
1018impl_debug_as_display!(StyleTextIndentParseError<'a>);
1019#[cfg(feature = "parser")]
1020impl_display! { StyleTextIndentParseError<'a>, {
1021 PixelValue(e) => format!("Invalid text-indent value: {}", e),
1022}}
1023#[cfg(feature = "parser")]
1024impl_from!(
1025 CssPixelValueParseError<'a>,
1026 StyleTextIndentParseError::PixelValue
1027);
1028
1029#[cfg(feature = "parser")]
1030#[derive(Debug, Clone, PartialEq, Eq)]
1031#[repr(C, u8)]
1032pub enum StyleTextIndentParseErrorOwned {
1033 PixelValue(CssPixelValueParseErrorOwned),
1034}
1035
1036#[cfg(feature = "parser")]
1037impl StyleTextIndentParseError<'_> {
1038 #[must_use]
1039 pub fn to_contained(&self) -> StyleTextIndentParseErrorOwned {
1040 match self {
1041 Self::PixelValue(e) => StyleTextIndentParseErrorOwned::PixelValue(e.to_contained()),
1042 }
1043 }
1044}
1045
1046#[cfg(feature = "parser")]
1047impl StyleTextIndentParseErrorOwned {
1048 #[must_use]
1049 pub fn to_shared(&self) -> StyleTextIndentParseError<'_> {
1050 match self {
1051 Self::PixelValue(e) => StyleTextIndentParseError::PixelValue(e.to_shared()),
1052 }
1053 }
1054}
1055
1056#[cfg(feature = "parser")]
1057pub fn parse_style_text_indent(
1061 input: &str,
1062) -> Result<StyleTextIndent, StyleTextIndentParseError<'_>> {
1063 let mut each_line = false;
1064 let mut hanging = false;
1065 let mut pixel_part: Option<&str> = None;
1066
1067 for token in input.split_whitespace() {
1068 match token {
1069 "each-line" => each_line = true,
1070 "hanging" => hanging = true,
1071 _ => {
1072 pixel_part = Some(token);
1073 }
1074 }
1075 }
1076
1077 let pixel_str = pixel_part.unwrap_or("0px");
1078
1079 crate::props::basic::pixel::parse_pixel_value(pixel_str)
1080 .map(|inner| StyleTextIndent {
1081 inner,
1082 each_line,
1083 hanging,
1084 })
1085 .map_err(StyleTextIndentParseError::PixelValue)
1086}
1087
1088#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1090#[repr(C)]
1091pub struct StyleInitialLetter {
1092 pub size: u32,
1093 pub sink: crate::corety::OptionU32,
1094}
1095
1096impl FormatAsRustCode for StyleInitialLetter {
1097 fn format_as_rust_code(&self, _tabs: usize) -> String {
1098 format!("{self:?}")
1099 }
1100}
1101
1102impl PrintAsCssValue for StyleInitialLetter {
1103 fn print_as_css_value(&self) -> String {
1104 if let crate::corety::OptionU32::Some(sink) = self.sink {
1105 format!("{} {}", self.size, sink)
1106 } else {
1107 format!("{}", self.size)
1108 }
1109 }
1110}
1111
1112#[cfg(feature = "parser")]
1113#[derive(Clone, PartialEq, Eq)]
1114pub enum StyleInitialLetterParseError<'a> {
1115 InvalidFormat(&'a str),
1116 InvalidSize(&'a str),
1117 InvalidSink(&'a str),
1118}
1119#[cfg(feature = "parser")]
1120impl_debug_as_display!(StyleInitialLetterParseError<'a>);
1121#[cfg(feature = "parser")]
1122impl_display! { StyleInitialLetterParseError<'a>, {
1123 InvalidFormat(e) => format!("Invalid initial-letter format: {}", e),
1124 InvalidSize(e) => format!("Invalid initial-letter size: {}", e),
1125 InvalidSink(e) => format!("Invalid initial-letter sink: {}", e),
1126}}
1127
1128#[cfg(feature = "parser")]
1129#[derive(Debug, Clone, PartialEq, Eq)]
1130#[repr(C, u8)]
1131pub enum StyleInitialLetterParseErrorOwned {
1132 InvalidFormat(AzString),
1133 InvalidSize(AzString),
1134 InvalidSink(AzString),
1135}
1136
1137#[cfg(feature = "parser")]
1138impl StyleInitialLetterParseError<'_> {
1139 #[must_use]
1140 pub fn to_contained(&self) -> StyleInitialLetterParseErrorOwned {
1141 match self {
1142 Self::InvalidFormat(s) => {
1143 StyleInitialLetterParseErrorOwned::InvalidFormat((*s).to_string().into())
1144 }
1145 Self::InvalidSize(s) => {
1146 StyleInitialLetterParseErrorOwned::InvalidSize((*s).to_string().into())
1147 }
1148 Self::InvalidSink(s) => {
1149 StyleInitialLetterParseErrorOwned::InvalidSink((*s).to_string().into())
1150 }
1151 }
1152 }
1153}
1154
1155#[cfg(feature = "parser")]
1156impl StyleInitialLetterParseErrorOwned {
1157 #[must_use]
1158 pub fn to_shared(&self) -> StyleInitialLetterParseError<'_> {
1159 match self {
1160 Self::InvalidFormat(s) => StyleInitialLetterParseError::InvalidFormat(s.as_str()),
1161 Self::InvalidSize(s) => StyleInitialLetterParseError::InvalidSize(s.as_str()),
1162 Self::InvalidSink(s) => StyleInitialLetterParseError::InvalidSink(s.as_str()),
1163 }
1164 }
1165}
1166
1167#[cfg(feature = "parser")]
1168impl From<StyleInitialLetterParseError<'_>> for StyleInitialLetterParseErrorOwned {
1169 fn from(e: StyleInitialLetterParseError<'_>) -> Self {
1170 match e {
1171 StyleInitialLetterParseError::InvalidFormat(s) => {
1172 Self::InvalidFormat(s.to_string().into())
1173 }
1174 StyleInitialLetterParseError::InvalidSize(s) => Self::InvalidSize(s.to_string().into()),
1175 StyleInitialLetterParseError::InvalidSink(s) => Self::InvalidSink(s.to_string().into()),
1176 }
1177 }
1178}
1179
1180#[cfg(feature = "parser")]
1181impl_display! { StyleInitialLetterParseErrorOwned, {
1182 InvalidFormat(e) => format!("Invalid initial-letter format: {}", e),
1183 InvalidSize(e) => format!("Invalid initial-letter size: {}", e),
1184 InvalidSink(e) => format!("Invalid initial-letter sink: {}", e),
1185}}
1186
1187#[cfg(feature = "parser")]
1188pub fn parse_style_initial_letter(
1192 input: &str,
1193) -> Result<StyleInitialLetter, StyleInitialLetterParseError<'_>> {
1194 let input = input.trim();
1195 let parts: Vec<&str> = input.split_whitespace().collect();
1196
1197 if parts.is_empty() {
1198 return Err(StyleInitialLetterParseError::InvalidFormat(input));
1199 }
1200
1201 let size = parts[0]
1203 .parse::<u32>()
1204 .map_err(|_| StyleInitialLetterParseError::InvalidSize(parts[0]))?;
1205
1206 if size == 0 {
1207 return Err(StyleInitialLetterParseError::InvalidSize(parts[0]));
1208 }
1209
1210 let sink = if parts.len() > 1 {
1212 crate::corety::OptionU32::Some(
1213 parts[1]
1214 .parse::<u32>()
1215 .map_err(|_| StyleInitialLetterParseError::InvalidSink(parts[1]))?,
1216 )
1217 } else {
1218 crate::corety::OptionU32::None
1219 };
1220
1221 Ok(StyleInitialLetter { size, sink })
1222}
1223
1224#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1226#[repr(C)]
1227pub struct StyleLineClamp {
1228 pub max_lines: usize,
1229}
1230
1231impl FormatAsRustCode for StyleLineClamp {
1232 fn format_as_rust_code(&self, _tabs: usize) -> String {
1233 format!("{self:?}")
1234 }
1235}
1236
1237impl PrintAsCssValue for StyleLineClamp {
1238 fn print_as_css_value(&self) -> String {
1239 format!("{}", self.max_lines)
1240 }
1241}
1242
1243#[cfg(feature = "parser")]
1244#[derive(Clone, PartialEq, Eq)]
1245pub enum StyleLineClampParseError<'a> {
1246 InvalidValue(&'a str),
1247 ZeroValue,
1248}
1249#[cfg(feature = "parser")]
1250impl_debug_as_display!(StyleLineClampParseError<'a>);
1251#[cfg(feature = "parser")]
1252impl_display! { StyleLineClampParseError<'a>, {
1253 InvalidValue(e) => format!("Invalid line-clamp value: {}", e),
1254 ZeroValue => format!("line-clamp cannot be zero"),
1255}}
1256#[allow(variant_size_differences)]
1257#[cfg(feature = "parser")]
1259#[derive(Debug, Clone, PartialEq, Eq)]
1260#[repr(C, u8)]
1261pub enum StyleLineClampParseErrorOwned {
1262 InvalidValue(AzString),
1263 ZeroValue,
1264}
1265
1266#[cfg(feature = "parser")]
1267impl StyleLineClampParseError<'_> {
1268 #[must_use]
1269 pub fn to_contained(&self) -> StyleLineClampParseErrorOwned {
1270 match self {
1271 Self::InvalidValue(s) => {
1272 StyleLineClampParseErrorOwned::InvalidValue((*s).to_string().into())
1273 }
1274 Self::ZeroValue => StyleLineClampParseErrorOwned::ZeroValue,
1275 }
1276 }
1277}
1278
1279#[cfg(feature = "parser")]
1280impl StyleLineClampParseErrorOwned {
1281 #[must_use]
1282 pub fn to_shared(&self) -> StyleLineClampParseError<'_> {
1283 match self {
1284 Self::InvalidValue(s) => StyleLineClampParseError::InvalidValue(s.as_str()),
1285 Self::ZeroValue => StyleLineClampParseError::ZeroValue,
1286 }
1287 }
1288}
1289
1290#[cfg(feature = "parser")]
1291impl From<StyleLineClampParseError<'_>> for StyleLineClampParseErrorOwned {
1292 fn from(e: StyleLineClampParseError<'_>) -> Self {
1293 e.to_contained()
1294 }
1295}
1296
1297#[cfg(feature = "parser")]
1298impl_display! { StyleLineClampParseErrorOwned, {
1299 InvalidValue(e) => format!("Invalid line-clamp value: {}", e),
1300 ZeroValue => format!("line-clamp cannot be zero"),
1301}}
1302
1303#[cfg(feature = "parser")]
1304pub fn parse_style_line_clamp(input: &str) -> Result<StyleLineClamp, StyleLineClampParseError<'_>> {
1308 let input = input.trim();
1309
1310 let max_lines = input
1311 .parse::<usize>()
1312 .map_err(|_| StyleLineClampParseError::InvalidValue(input))?;
1313
1314 if max_lines == 0 {
1315 return Err(StyleLineClampParseError::ZeroValue);
1316 }
1317
1318 Ok(StyleLineClamp { max_lines })
1319}
1320
1321#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1325#[repr(C)]
1326#[derive(Default)]
1327pub struct StyleHangingPunctuation {
1328 pub first: bool,
1329 pub force_end: bool,
1330 pub allow_end: bool,
1331 pub last: bool,
1332}
1333
1334impl StyleHangingPunctuation {
1335 #[must_use]
1336 pub const fn is_enabled(&self) -> bool {
1337 self.first || self.force_end || self.allow_end || self.last
1338 }
1339}
1340
1341impl FormatAsRustCode for StyleHangingPunctuation {
1342 fn format_as_rust_code(&self, _tabs: usize) -> String {
1343 format!("{self:?}")
1344 }
1345}
1346
1347impl PrintAsCssValue for StyleHangingPunctuation {
1348 fn print_as_css_value(&self) -> String {
1349 if !self.is_enabled() {
1350 return "none".to_string();
1351 }
1352 let mut parts = Vec::new();
1353 if self.first {
1354 parts.push("first");
1355 }
1356 if self.force_end {
1357 parts.push("force-end");
1358 }
1359 if self.allow_end {
1360 parts.push("allow-end");
1361 }
1362 if self.last {
1363 parts.push("last");
1364 }
1365 parts.join(" ")
1366 }
1367}
1368
1369#[cfg(feature = "parser")]
1370#[derive(Clone, PartialEq, Eq)]
1371pub enum StyleHangingPunctuationParseError<'a> {
1372 InvalidValue(&'a str),
1373}
1374#[cfg(feature = "parser")]
1375impl_debug_as_display!(StyleHangingPunctuationParseError<'a>);
1376#[cfg(feature = "parser")]
1377impl_display! { StyleHangingPunctuationParseError<'a>, {
1378 InvalidValue(e) => format!("Invalid hanging-punctuation value: {}", e),
1379}}
1380
1381#[cfg(feature = "parser")]
1382#[derive(Debug, Clone, PartialEq, Eq)]
1383#[repr(C, u8)]
1384pub enum StyleHangingPunctuationParseErrorOwned {
1385 InvalidValue(AzString),
1386}
1387
1388#[cfg(feature = "parser")]
1389impl StyleHangingPunctuationParseError<'_> {
1390 #[must_use]
1391 pub fn to_contained(&self) -> StyleHangingPunctuationParseErrorOwned {
1392 match self {
1393 Self::InvalidValue(s) => {
1394 StyleHangingPunctuationParseErrorOwned::InvalidValue((*s).to_string().into())
1395 }
1396 }
1397 }
1398}
1399
1400#[cfg(feature = "parser")]
1401impl StyleHangingPunctuationParseErrorOwned {
1402 #[must_use]
1403 pub fn to_shared(&self) -> StyleHangingPunctuationParseError<'_> {
1404 match self {
1405 Self::InvalidValue(s) => StyleHangingPunctuationParseError::InvalidValue(s.as_str()),
1406 }
1407 }
1408}
1409
1410#[cfg(feature = "parser")]
1411impl From<StyleHangingPunctuationParseError<'_>> for StyleHangingPunctuationParseErrorOwned {
1412 fn from(e: StyleHangingPunctuationParseError<'_>) -> Self {
1413 e.to_contained()
1414 }
1415}
1416
1417#[cfg(feature = "parser")]
1418impl_display! { StyleHangingPunctuationParseErrorOwned, {
1419 InvalidValue(e) => format!("Invalid hanging-punctuation value: {}", e),
1420}}
1421
1422#[cfg(feature = "parser")]
1423pub fn parse_style_hanging_punctuation(
1427 input: &str,
1428) -> Result<StyleHangingPunctuation, StyleHangingPunctuationParseError<'_>> {
1429 let input = input.trim();
1430
1431 if input.eq_ignore_ascii_case("none") {
1432 return Ok(StyleHangingPunctuation::default());
1433 }
1434
1435 let mut first = false;
1436 let mut force_end = false;
1437 let mut allow_end = false;
1438 let mut last = false;
1439
1440 for token in input.split_whitespace() {
1441 match token.to_lowercase().as_str() {
1442 "first" => first = true,
1443 "force-end" => force_end = true,
1444 "allow-end" => allow_end = true,
1445 "last" => last = true,
1446 _ => return Err(StyleHangingPunctuationParseError::InvalidValue(input)),
1447 }
1448 }
1449
1450 if force_end && allow_end {
1451 return Err(StyleHangingPunctuationParseError::InvalidValue(input));
1452 }
1453
1454 Ok(StyleHangingPunctuation {
1455 first,
1456 force_end,
1457 allow_end,
1458 last,
1459 })
1460}
1461
1462#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1464#[repr(C, u8)]
1465#[derive(Default)]
1466pub enum StyleTextCombineUpright {
1467 #[default]
1468 None,
1469 All,
1470 Digits(u8),
1471}
1472
1473impl FormatAsRustCode for StyleTextCombineUpright {
1474 fn format_as_rust_code(&self, _tabs: usize) -> String {
1475 format!("{self:?}")
1476 }
1477}
1478
1479impl PrintAsCssValue for StyleTextCombineUpright {
1480 fn print_as_css_value(&self) -> String {
1481 match self {
1482 Self::None => "none".to_string(),
1483 Self::All => "all".to_string(),
1484 Self::Digits(n) => format!("digits {n}"),
1485 }
1486 }
1487}
1488
1489#[cfg(feature = "parser")]
1490#[derive(Clone, PartialEq, Eq)]
1491pub enum StyleTextCombineUprightParseError<'a> {
1492 InvalidValue(&'a str),
1493 InvalidDigits(&'a str),
1494}
1495#[cfg(feature = "parser")]
1496impl_debug_as_display!(StyleTextCombineUprightParseError<'a>);
1497#[cfg(feature = "parser")]
1498impl_display! { StyleTextCombineUprightParseError<'a>, {
1499 InvalidValue(e) => format!("Invalid text-combine-upright value: {}", e),
1500 InvalidDigits(e) => format!("Invalid text-combine-upright digits: {}", e),
1501}}
1502
1503#[cfg(feature = "parser")]
1504#[derive(Debug, Clone, PartialEq, Eq)]
1505#[repr(C, u8)]
1506pub enum StyleTextCombineUprightParseErrorOwned {
1507 InvalidValue(AzString),
1508 InvalidDigits(AzString),
1509}
1510
1511#[cfg(feature = "parser")]
1512impl StyleTextCombineUprightParseError<'_> {
1513 #[must_use]
1514 pub fn to_contained(&self) -> StyleTextCombineUprightParseErrorOwned {
1515 match self {
1516 Self::InvalidValue(s) => {
1517 StyleTextCombineUprightParseErrorOwned::InvalidValue((*s).to_string().into())
1518 }
1519 Self::InvalidDigits(s) => {
1520 StyleTextCombineUprightParseErrorOwned::InvalidDigits((*s).to_string().into())
1521 }
1522 }
1523 }
1524}
1525
1526#[cfg(feature = "parser")]
1527impl StyleTextCombineUprightParseErrorOwned {
1528 #[must_use]
1529 pub fn to_shared(&self) -> StyleTextCombineUprightParseError<'_> {
1530 match self {
1531 Self::InvalidValue(s) => StyleTextCombineUprightParseError::InvalidValue(s.as_str()),
1532 Self::InvalidDigits(s) => StyleTextCombineUprightParseError::InvalidDigits(s.as_str()),
1533 }
1534 }
1535}
1536
1537#[cfg(feature = "parser")]
1538impl From<StyleTextCombineUprightParseError<'_>> for StyleTextCombineUprightParseErrorOwned {
1539 fn from(e: StyleTextCombineUprightParseError<'_>) -> Self {
1540 e.to_contained()
1541 }
1542}
1543
1544#[cfg(feature = "parser")]
1545impl_display! { StyleTextCombineUprightParseErrorOwned, {
1546 InvalidValue(e) => format!("Invalid text-combine-upright value: {}", e),
1547 InvalidDigits(e) => format!("Invalid text-combine-upright digits: {}", e),
1548}}
1549
1550#[cfg(feature = "parser")]
1551pub fn parse_style_text_combine_upright(
1555 input: &str,
1556) -> Result<StyleTextCombineUpright, StyleTextCombineUprightParseError<'_>> {
1557 let trimmed = input.trim();
1558
1559 if trimmed.eq_ignore_ascii_case("none") {
1560 Ok(StyleTextCombineUpright::None)
1561 } else if trimmed.eq_ignore_ascii_case("all") {
1562 Ok(StyleTextCombineUpright::All)
1563 } else if trimmed.starts_with("digits") {
1564 let parts: Vec<&str> = trimmed.split_whitespace().collect();
1565 if parts.len() == 2 {
1566 let n = parts[1]
1567 .parse::<u8>()
1568 .map_err(|_| StyleTextCombineUprightParseError::InvalidDigits(input))?;
1569 if (2..=4).contains(&n) {
1570 Ok(StyleTextCombineUpright::Digits(n))
1571 } else {
1572 Err(StyleTextCombineUprightParseError::InvalidDigits(input))
1573 }
1574 } else {
1575 Ok(StyleTextCombineUpright::Digits(2))
1577 }
1578 } else {
1579 Err(StyleTextCombineUprightParseError::InvalidValue(input))
1580 }
1581}
1582
1583#[cfg(feature = "parser")]
1584#[derive(Clone, PartialEq, Eq)]
1585pub enum StyleWordSpacingParseError<'a> {
1586 PixelValue(CssPixelValueParseError<'a>),
1587}
1588#[cfg(feature = "parser")]
1589impl_debug_as_display!(StyleWordSpacingParseError<'a>);
1590#[cfg(feature = "parser")]
1591impl_display! { StyleWordSpacingParseError<'a>, {
1592 PixelValue(e) => format!("Invalid word-spacing value: {}", e),
1593}}
1594#[cfg(feature = "parser")]
1595impl_from!(
1596 CssPixelValueParseError<'a>,
1597 StyleWordSpacingParseError::PixelValue
1598);
1599
1600#[cfg(feature = "parser")]
1601#[derive(Debug, Clone, PartialEq, Eq)]
1602#[repr(C, u8)]
1603pub enum StyleWordSpacingParseErrorOwned {
1604 PixelValue(CssPixelValueParseErrorOwned),
1605}
1606
1607#[cfg(feature = "parser")]
1608impl StyleWordSpacingParseError<'_> {
1609 #[must_use]
1610 pub fn to_contained(&self) -> StyleWordSpacingParseErrorOwned {
1611 match self {
1612 Self::PixelValue(e) => StyleWordSpacingParseErrorOwned::PixelValue(e.to_contained()),
1613 }
1614 }
1615}
1616
1617#[cfg(feature = "parser")]
1618impl StyleWordSpacingParseErrorOwned {
1619 #[must_use]
1620 pub fn to_shared(&self) -> StyleWordSpacingParseError<'_> {
1621 match self {
1622 Self::PixelValue(e) => StyleWordSpacingParseError::PixelValue(e.to_shared()),
1623 }
1624 }
1625}
1626
1627#[cfg(feature = "parser")]
1628pub fn parse_style_word_spacing(
1632 input: &str,
1633) -> Result<StyleWordSpacing, StyleWordSpacingParseError<'_>> {
1634 crate::props::basic::pixel::parse_pixel_value(input)
1635 .map(|inner| StyleWordSpacing { inner })
1636 .map_err(StyleWordSpacingParseError::PixelValue)
1637}
1638
1639#[cfg(feature = "parser")]
1640#[derive(Clone, PartialEq, Eq)]
1641#[repr(C, u8)]
1642pub enum StyleLineHeightParseError {
1643 Percentage(PercentageParseError),
1644}
1645#[cfg(feature = "parser")]
1646impl_debug_as_display!(StyleLineHeightParseError);
1647#[cfg(feature = "parser")]
1648impl_display! { StyleLineHeightParseError, {
1649 Percentage(e) => format!("Invalid line-height value: {}", e),
1650}}
1651#[cfg(feature = "parser")]
1652impl_from!(PercentageParseError, StyleLineHeightParseError::Percentage);
1653
1654#[cfg(feature = "parser")]
1655#[derive(Debug, Clone, PartialEq, Eq)]
1656pub enum StyleLineHeightParseErrorOwned {
1657 Percentage(PercentageParseErrorOwned),
1658}
1659
1660#[cfg(feature = "parser")]
1661impl StyleLineHeightParseError {
1662 #[must_use]
1663 pub fn to_contained(&self) -> StyleLineHeightParseErrorOwned {
1664 match self {
1665 Self::Percentage(e) => StyleLineHeightParseErrorOwned::Percentage(e.to_contained()),
1666 }
1667 }
1668}
1669
1670#[cfg(feature = "parser")]
1671impl StyleLineHeightParseErrorOwned {
1672 #[must_use]
1673 pub fn to_shared(&self) -> StyleLineHeightParseError {
1674 match self {
1675 Self::Percentage(e) => StyleLineHeightParseError::Percentage(e.to_shared()),
1676 }
1677 }
1678}
1679
1680#[cfg(feature = "parser")]
1681pub fn parse_style_line_height(input: &str) -> Result<StyleLineHeight, StyleLineHeightParseError> {
1685 if let Ok(inner) = crate::props::basic::length::parse_percentage_value(input) {
1687 return Ok(StyleLineHeight { inner });
1688 }
1689 if let Ok(px) = crate::props::basic::pixel::parse_pixel_value(input) {
1693 if px.metric == crate::props::basic::length::SizeMetric::Px {
1694 let px_val = px.number.get();
1695 return Ok(StyleLineHeight {
1696 inner: PercentageValue::new(-px_val * 100.0),
1697 });
1698 }
1699 }
1700 Err(StyleLineHeightParseError::Percentage(
1701 PercentageParseError::InvalidUnit(String::new().into()),
1702 ))
1703}
1704
1705#[cfg(feature = "parser")]
1706#[derive(Clone, PartialEq, Eq)]
1707pub enum StyleTabSizeParseError<'a> {
1708 PixelValue(CssPixelValueParseError<'a>),
1709}
1710#[cfg(feature = "parser")]
1711impl_debug_as_display!(StyleTabSizeParseError<'a>);
1712#[cfg(feature = "parser")]
1713impl_display! { StyleTabSizeParseError<'a>, {
1714 PixelValue(e) => format!("Invalid tab-size value: {}", e),
1715}}
1716#[cfg(feature = "parser")]
1717impl_from!(
1718 CssPixelValueParseError<'a>,
1719 StyleTabSizeParseError::PixelValue
1720);
1721
1722#[cfg(feature = "parser")]
1723#[derive(Debug, Clone, PartialEq, Eq)]
1724#[repr(C, u8)]
1725pub enum StyleTabSizeParseErrorOwned {
1726 PixelValue(CssPixelValueParseErrorOwned),
1727}
1728
1729#[cfg(feature = "parser")]
1730impl StyleTabSizeParseError<'_> {
1731 #[must_use]
1732 pub fn to_contained(&self) -> StyleTabSizeParseErrorOwned {
1733 match self {
1734 Self::PixelValue(e) => StyleTabSizeParseErrorOwned::PixelValue(e.to_contained()),
1735 }
1736 }
1737}
1738
1739#[cfg(feature = "parser")]
1740impl StyleTabSizeParseErrorOwned {
1741 #[must_use]
1742 pub fn to_shared(&self) -> StyleTabSizeParseError<'_> {
1743 match self {
1744 Self::PixelValue(e) => StyleTabSizeParseError::PixelValue(e.to_shared()),
1745 }
1746 }
1747}
1748
1749#[cfg(feature = "parser")]
1750pub fn parse_style_tab_size(input: &str) -> Result<StyleTabSize, StyleTabSizeParseError<'_>> {
1754 input.trim().parse::<f32>().map_or_else(
1755 |_| {
1756 crate::props::basic::pixel::parse_pixel_value(input)
1757 .map(|v| StyleTabSize { inner: v })
1758 .map_err(StyleTabSizeParseError::PixelValue)
1759 },
1760 |number| {
1761 Ok(StyleTabSize {
1762 inner: PixelValue::em(number),
1763 })
1764 },
1765 )
1766}
1767
1768#[cfg(feature = "parser")]
1769#[derive(Clone, PartialEq, Eq)]
1770pub enum StyleWhiteSpaceParseError<'a> {
1771 InvalidValue(InvalidValueErr<'a>),
1772}
1773#[cfg(feature = "parser")]
1774impl_debug_as_display!(StyleWhiteSpaceParseError<'a>);
1775#[cfg(feature = "parser")]
1776impl_display! { StyleWhiteSpaceParseError<'a>, {
1777 InvalidValue(e) => format!("Invalid white-space value: \"{}\"", e.0),
1778}}
1779#[cfg(feature = "parser")]
1780impl_from!(InvalidValueErr<'a>, StyleWhiteSpaceParseError::InvalidValue);
1781
1782#[cfg(feature = "parser")]
1783#[derive(Debug, Clone, PartialEq, Eq)]
1784#[repr(C, u8)]
1785pub enum StyleWhiteSpaceParseErrorOwned {
1786 InvalidValue(InvalidValueErrOwned),
1787}
1788
1789#[cfg(feature = "parser")]
1790impl StyleWhiteSpaceParseError<'_> {
1791 #[must_use]
1792 pub fn to_contained(&self) -> StyleWhiteSpaceParseErrorOwned {
1793 match self {
1794 Self::InvalidValue(e) => StyleWhiteSpaceParseErrorOwned::InvalidValue(e.to_contained()),
1795 }
1796 }
1797}
1798
1799#[cfg(feature = "parser")]
1800impl StyleWhiteSpaceParseErrorOwned {
1801 #[must_use]
1802 pub fn to_shared(&self) -> StyleWhiteSpaceParseError<'_> {
1803 match self {
1804 Self::InvalidValue(e) => StyleWhiteSpaceParseError::InvalidValue(e.to_shared()),
1805 }
1806 }
1807}
1808
1809#[cfg(feature = "parser")]
1810pub fn parse_style_white_space(
1814 input: &str,
1815) -> Result<StyleWhiteSpace, StyleWhiteSpaceParseError<'_>> {
1816 match input.trim() {
1817 "normal" => Ok(StyleWhiteSpace::Normal),
1818 "pre" => Ok(StyleWhiteSpace::Pre),
1819 "nowrap" | "no-wrap" => Ok(StyleWhiteSpace::Nowrap),
1820 "pre-wrap" => Ok(StyleWhiteSpace::PreWrap),
1821 "pre-line" => Ok(StyleWhiteSpace::PreLine),
1822 "break-spaces" => Ok(StyleWhiteSpace::BreakSpaces),
1823 other => Err(StyleWhiteSpaceParseError::InvalidValue(InvalidValueErr(
1824 other,
1825 ))),
1826 }
1827}
1828
1829#[cfg(feature = "parser")]
1830#[derive(Clone, PartialEq, Eq)]
1831pub enum StyleHyphensParseError<'a> {
1832 InvalidValue(InvalidValueErr<'a>),
1833}
1834#[cfg(feature = "parser")]
1835impl_debug_as_display!(StyleHyphensParseError<'a>);
1836#[cfg(feature = "parser")]
1837impl_display! { StyleHyphensParseError<'a>, {
1838 InvalidValue(e) => format!("Invalid hyphens value: \"{}\"", e.0),
1839}}
1840#[cfg(feature = "parser")]
1841impl_from!(InvalidValueErr<'a>, StyleHyphensParseError::InvalidValue);
1842
1843#[cfg(feature = "parser")]
1844#[derive(Debug, Clone, PartialEq, Eq)]
1845#[repr(C, u8)]
1846pub enum StyleHyphensParseErrorOwned {
1847 InvalidValue(InvalidValueErrOwned),
1848}
1849
1850#[cfg(feature = "parser")]
1851impl StyleHyphensParseError<'_> {
1852 #[must_use]
1853 pub fn to_contained(&self) -> StyleHyphensParseErrorOwned {
1854 match self {
1855 Self::InvalidValue(e) => StyleHyphensParseErrorOwned::InvalidValue(e.to_contained()),
1856 }
1857 }
1858}
1859
1860#[cfg(feature = "parser")]
1861impl StyleHyphensParseErrorOwned {
1862 #[must_use]
1863 pub fn to_shared(&self) -> StyleHyphensParseError<'_> {
1864 match self {
1865 Self::InvalidValue(e) => StyleHyphensParseError::InvalidValue(e.to_shared()),
1866 }
1867 }
1868}
1869
1870#[cfg(feature = "parser")]
1871pub fn parse_style_hyphens(input: &str) -> Result<StyleHyphens, StyleHyphensParseError<'_>> {
1875 match input.trim() {
1876 "none" => Ok(StyleHyphens::None),
1877 "manual" => Ok(StyleHyphens::Manual),
1878 "auto" => Ok(StyleHyphens::Auto),
1879 other => Err(StyleHyphensParseError::InvalidValue(InvalidValueErr(other))),
1880 }
1881}
1882
1883#[cfg(feature = "parser")]
1886#[derive(Clone, PartialEq, Eq)]
1887pub enum StyleLineBreakParseError<'a> {
1888 InvalidValue(InvalidValueErr<'a>),
1889}
1890#[cfg(feature = "parser")]
1891impl_debug_as_display!(StyleLineBreakParseError<'a>);
1892#[cfg(feature = "parser")]
1893impl_display! { StyleLineBreakParseError<'a>, {
1894 InvalidValue(e) => format!("Invalid line-break value: \"{}\"", e.0),
1895}}
1896#[cfg(feature = "parser")]
1897impl_from!(InvalidValueErr<'a>, StyleLineBreakParseError::InvalidValue);
1898
1899#[cfg(feature = "parser")]
1900#[derive(Debug, Clone, PartialEq, Eq)]
1901#[repr(C, u8)]
1902pub enum StyleLineBreakParseErrorOwned {
1903 InvalidValue(InvalidValueErrOwned),
1904}
1905
1906#[cfg(feature = "parser")]
1907impl StyleLineBreakParseError<'_> {
1908 #[must_use]
1909 pub fn to_contained(&self) -> StyleLineBreakParseErrorOwned {
1910 match self {
1911 Self::InvalidValue(e) => StyleLineBreakParseErrorOwned::InvalidValue(e.to_contained()),
1912 }
1913 }
1914}
1915
1916#[cfg(feature = "parser")]
1917impl StyleLineBreakParseErrorOwned {
1918 #[must_use]
1919 pub fn to_shared(&self) -> StyleLineBreakParseError<'_> {
1920 match self {
1921 Self::InvalidValue(e) => StyleLineBreakParseError::InvalidValue(e.to_shared()),
1922 }
1923 }
1924}
1925
1926#[cfg(feature = "parser")]
1927pub fn parse_style_line_break(input: &str) -> Result<StyleLineBreak, StyleLineBreakParseError<'_>> {
1931 match input.trim() {
1932 "auto" => Ok(StyleLineBreak::Auto),
1933 "loose" => Ok(StyleLineBreak::Loose),
1934 "normal" => Ok(StyleLineBreak::Normal),
1935 "strict" => Ok(StyleLineBreak::Strict),
1936 "anywhere" => Ok(StyleLineBreak::Anywhere),
1937 other => Err(StyleLineBreakParseError::InvalidValue(InvalidValueErr(
1938 other,
1939 ))),
1940 }
1941}
1942
1943#[cfg(feature = "parser")]
1946#[derive(Clone, PartialEq, Eq)]
1947pub enum StyleWordBreakParseError<'a> {
1948 InvalidValue(InvalidValueErr<'a>),
1949}
1950#[cfg(feature = "parser")]
1951impl_debug_as_display!(StyleWordBreakParseError<'a>);
1952#[cfg(feature = "parser")]
1953impl_display! { StyleWordBreakParseError<'a>, {
1954 InvalidValue(e) => format!("Invalid word-break value: \"{}\"", e.0),
1955}}
1956#[cfg(feature = "parser")]
1957impl_from!(InvalidValueErr<'a>, StyleWordBreakParseError::InvalidValue);
1958
1959#[cfg(feature = "parser")]
1960#[derive(Debug, Clone, PartialEq, Eq)]
1961#[repr(C, u8)]
1962pub enum StyleWordBreakParseErrorOwned {
1963 InvalidValue(InvalidValueErrOwned),
1964}
1965
1966#[cfg(feature = "parser")]
1967impl StyleWordBreakParseError<'_> {
1968 #[must_use]
1969 pub fn to_contained(&self) -> StyleWordBreakParseErrorOwned {
1970 match self {
1971 Self::InvalidValue(e) => StyleWordBreakParseErrorOwned::InvalidValue(e.to_contained()),
1972 }
1973 }
1974}
1975
1976#[cfg(feature = "parser")]
1977impl StyleWordBreakParseErrorOwned {
1978 #[must_use]
1979 pub fn to_shared(&self) -> StyleWordBreakParseError<'_> {
1980 match self {
1981 Self::InvalidValue(e) => StyleWordBreakParseError::InvalidValue(e.to_shared()),
1982 }
1983 }
1984}
1985
1986#[cfg(feature = "parser")]
1987pub fn parse_style_word_break(input: &str) -> Result<StyleWordBreak, StyleWordBreakParseError<'_>> {
1991 match input.trim() {
1992 "normal" => Ok(StyleWordBreak::Normal),
1993 "break-all" => Ok(StyleWordBreak::BreakAll),
1994 "keep-all" => Ok(StyleWordBreak::KeepAll),
1995 "break-word" => Ok(StyleWordBreak::BreakWord),
1996 other => Err(StyleWordBreakParseError::InvalidValue(InvalidValueErr(
1997 other,
1998 ))),
1999 }
2000}
2001
2002#[cfg(feature = "parser")]
2005#[derive(Clone, PartialEq, Eq)]
2006pub enum StyleOverflowWrapParseError<'a> {
2007 InvalidValue(InvalidValueErr<'a>),
2008}
2009#[cfg(feature = "parser")]
2010impl_debug_as_display!(StyleOverflowWrapParseError<'a>);
2011#[cfg(feature = "parser")]
2012impl_display! { StyleOverflowWrapParseError<'a>, {
2013 InvalidValue(e) => format!("Invalid overflow-wrap value: \"{}\"", e.0),
2014}}
2015#[cfg(feature = "parser")]
2016impl_from!(
2017 InvalidValueErr<'a>,
2018 StyleOverflowWrapParseError::InvalidValue
2019);
2020
2021#[cfg(feature = "parser")]
2022#[derive(Debug, Clone, PartialEq, Eq)]
2023#[repr(C, u8)]
2024pub enum StyleOverflowWrapParseErrorOwned {
2025 InvalidValue(InvalidValueErrOwned),
2026}
2027
2028#[cfg(feature = "parser")]
2029impl StyleOverflowWrapParseError<'_> {
2030 #[must_use]
2031 pub fn to_contained(&self) -> StyleOverflowWrapParseErrorOwned {
2032 match self {
2033 Self::InvalidValue(e) => {
2034 StyleOverflowWrapParseErrorOwned::InvalidValue(e.to_contained())
2035 }
2036 }
2037 }
2038}
2039
2040#[cfg(feature = "parser")]
2041impl StyleOverflowWrapParseErrorOwned {
2042 #[must_use]
2043 pub fn to_shared(&self) -> StyleOverflowWrapParseError<'_> {
2044 match self {
2045 Self::InvalidValue(e) => StyleOverflowWrapParseError::InvalidValue(e.to_shared()),
2046 }
2047 }
2048}
2049
2050#[cfg(feature = "parser")]
2051pub fn parse_style_overflow_wrap(
2055 input: &str,
2056) -> Result<StyleOverflowWrap, StyleOverflowWrapParseError<'_>> {
2057 match input.trim() {
2058 "normal" => Ok(StyleOverflowWrap::Normal),
2059 "anywhere" => Ok(StyleOverflowWrap::Anywhere),
2060 "break-word" => Ok(StyleOverflowWrap::BreakWord),
2061 other => Err(StyleOverflowWrapParseError::InvalidValue(InvalidValueErr(
2062 other,
2063 ))),
2064 }
2065}
2066
2067#[cfg(feature = "parser")]
2070#[derive(Clone, PartialEq, Eq)]
2071pub enum StyleTextAlignLastParseError<'a> {
2072 InvalidValue(InvalidValueErr<'a>),
2073}
2074#[cfg(feature = "parser")]
2075impl_debug_as_display!(StyleTextAlignLastParseError<'a>);
2076#[cfg(feature = "parser")]
2077impl_display! { StyleTextAlignLastParseError<'a>, {
2078 InvalidValue(e) => format!("Invalid text-align-last value: \"{}\"", e.0),
2079}}
2080#[cfg(feature = "parser")]
2081impl_from!(
2082 InvalidValueErr<'a>,
2083 StyleTextAlignLastParseError::InvalidValue
2084);
2085
2086#[cfg(feature = "parser")]
2087#[derive(Debug, Clone, PartialEq, Eq)]
2088#[repr(C, u8)]
2089pub enum StyleTextAlignLastParseErrorOwned {
2090 InvalidValue(InvalidValueErrOwned),
2091}
2092
2093#[cfg(feature = "parser")]
2094impl StyleTextAlignLastParseError<'_> {
2095 #[must_use]
2096 pub fn to_contained(&self) -> StyleTextAlignLastParseErrorOwned {
2097 match self {
2098 Self::InvalidValue(e) => {
2099 StyleTextAlignLastParseErrorOwned::InvalidValue(e.to_contained())
2100 }
2101 }
2102 }
2103}
2104
2105#[cfg(feature = "parser")]
2106impl StyleTextAlignLastParseErrorOwned {
2107 #[must_use]
2108 pub fn to_shared(&self) -> StyleTextAlignLastParseError<'_> {
2109 match self {
2110 Self::InvalidValue(e) => StyleTextAlignLastParseError::InvalidValue(e.to_shared()),
2111 }
2112 }
2113}
2114
2115#[cfg(feature = "parser")]
2116pub fn parse_style_text_align_last(
2120 input: &str,
2121) -> Result<StyleTextAlignLast, StyleTextAlignLastParseError<'_>> {
2122 match input.trim() {
2123 "auto" => Ok(StyleTextAlignLast::Auto),
2124 "start" => Ok(StyleTextAlignLast::Start),
2125 "end" => Ok(StyleTextAlignLast::End),
2126 "left" => Ok(StyleTextAlignLast::Left),
2127 "right" => Ok(StyleTextAlignLast::Right),
2128 "center" => Ok(StyleTextAlignLast::Center),
2129 "justify" => Ok(StyleTextAlignLast::Justify),
2130 other => Err(StyleTextAlignLastParseError::InvalidValue(InvalidValueErr(
2131 other,
2132 ))),
2133 }
2134}
2135
2136#[cfg(feature = "parser")]
2139#[derive(Clone, PartialEq, Eq)]
2140pub enum StyleTextTransformParseError<'a> {
2141 InvalidValue(InvalidValueErr<'a>),
2142}
2143#[cfg(feature = "parser")]
2144impl_debug_as_display!(StyleTextTransformParseError<'a>);
2145#[cfg(feature = "parser")]
2146impl_display! { StyleTextTransformParseError<'a>, {
2147 InvalidValue(e) => format!("Invalid text-transform value: \"{}\"", e.0),
2148}}
2149#[cfg(feature = "parser")]
2150impl_from!(
2151 InvalidValueErr<'a>,
2152 StyleTextTransformParseError::InvalidValue
2153);
2154
2155#[cfg(feature = "parser")]
2156#[derive(Debug, Clone, PartialEq, Eq)]
2157#[repr(C, u8)]
2158pub enum StyleTextTransformParseErrorOwned {
2159 InvalidValue(InvalidValueErrOwned),
2160}
2161
2162#[cfg(feature = "parser")]
2163impl StyleTextTransformParseError<'_> {
2164 #[must_use]
2165 pub fn to_contained(&self) -> StyleTextTransformParseErrorOwned {
2166 match self {
2167 Self::InvalidValue(e) => {
2168 StyleTextTransformParseErrorOwned::InvalidValue(e.to_contained())
2169 }
2170 }
2171 }
2172}
2173
2174#[cfg(feature = "parser")]
2175impl StyleTextTransformParseErrorOwned {
2176 #[must_use]
2177 pub fn to_shared(&self) -> StyleTextTransformParseError<'_> {
2178 match self {
2179 Self::InvalidValue(e) => StyleTextTransformParseError::InvalidValue(e.to_shared()),
2180 }
2181 }
2182}
2183
2184#[cfg(feature = "parser")]
2185pub fn parse_style_text_transform(
2189 input: &str,
2190) -> Result<StyleTextTransform, StyleTextTransformParseError<'_>> {
2191 match input.trim() {
2192 "none" => Ok(StyleTextTransform::None),
2193 "capitalize" => Ok(StyleTextTransform::Capitalize),
2194 "uppercase" => Ok(StyleTextTransform::Uppercase),
2195 "lowercase" => Ok(StyleTextTransform::Lowercase),
2196 "full-width" => Ok(StyleTextTransform::FullWidth),
2197 other => Err(StyleTextTransformParseError::InvalidValue(InvalidValueErr(
2198 other,
2199 ))),
2200 }
2201}
2202
2203#[cfg(feature = "parser")]
2204#[derive(Clone, PartialEq, Eq)]
2205pub enum StyleDirectionParseError<'a> {
2206 InvalidValue(InvalidValueErr<'a>),
2207}
2208#[cfg(feature = "parser")]
2209impl_debug_as_display!(StyleDirectionParseError<'a>);
2210#[cfg(feature = "parser")]
2211impl_display! { StyleDirectionParseError<'a>, {
2212 InvalidValue(e) => format!("Invalid direction value: \"{}\"", e.0),
2213}}
2214#[cfg(feature = "parser")]
2215impl_from!(InvalidValueErr<'a>, StyleDirectionParseError::InvalidValue);
2216
2217#[cfg(feature = "parser")]
2218#[derive(Debug, Clone, PartialEq, Eq)]
2219#[repr(C, u8)]
2220pub enum StyleDirectionParseErrorOwned {
2221 InvalidValue(InvalidValueErrOwned),
2222}
2223
2224#[cfg(feature = "parser")]
2225impl StyleDirectionParseError<'_> {
2226 #[must_use]
2227 pub fn to_contained(&self) -> StyleDirectionParseErrorOwned {
2228 match self {
2229 Self::InvalidValue(e) => StyleDirectionParseErrorOwned::InvalidValue(e.to_contained()),
2230 }
2231 }
2232}
2233
2234#[cfg(feature = "parser")]
2235impl StyleDirectionParseErrorOwned {
2236 #[must_use]
2237 pub fn to_shared(&self) -> StyleDirectionParseError<'_> {
2238 match self {
2239 Self::InvalidValue(e) => StyleDirectionParseError::InvalidValue(e.to_shared()),
2240 }
2241 }
2242}
2243
2244#[cfg(feature = "parser")]
2245pub fn parse_style_direction(input: &str) -> Result<StyleDirection, StyleDirectionParseError<'_>> {
2249 match input.trim() {
2250 "ltr" => Ok(StyleDirection::Ltr),
2251 "rtl" => Ok(StyleDirection::Rtl),
2252 other => Err(StyleDirectionParseError::InvalidValue(InvalidValueErr(
2253 other,
2254 ))),
2255 }
2256}
2257
2258#[cfg(feature = "parser")]
2259#[derive(Clone, PartialEq, Eq)]
2260pub enum StyleUserSelectParseError<'a> {
2261 InvalidValue(InvalidValueErr<'a>),
2262}
2263#[cfg(feature = "parser")]
2264impl_debug_as_display!(StyleUserSelectParseError<'a>);
2265#[cfg(feature = "parser")]
2266impl_display! { StyleUserSelectParseError<'a>, {
2267 InvalidValue(e) => format!("Invalid user-select value: \"{}\"", e.0),
2268}}
2269#[cfg(feature = "parser")]
2270impl_from!(InvalidValueErr<'a>, StyleUserSelectParseError::InvalidValue);
2271
2272#[cfg(feature = "parser")]
2273#[derive(Debug, Clone, PartialEq, Eq)]
2274#[repr(C, u8)]
2275pub enum StyleUserSelectParseErrorOwned {
2276 InvalidValue(InvalidValueErrOwned),
2277}
2278
2279#[cfg(feature = "parser")]
2280impl StyleUserSelectParseError<'_> {
2281 #[must_use]
2282 pub fn to_contained(&self) -> StyleUserSelectParseErrorOwned {
2283 match self {
2284 Self::InvalidValue(e) => StyleUserSelectParseErrorOwned::InvalidValue(e.to_contained()),
2285 }
2286 }
2287}
2288
2289#[cfg(feature = "parser")]
2290impl StyleUserSelectParseErrorOwned {
2291 #[must_use]
2292 pub fn to_shared(&self) -> StyleUserSelectParseError<'_> {
2293 match self {
2294 Self::InvalidValue(e) => StyleUserSelectParseError::InvalidValue(e.to_shared()),
2295 }
2296 }
2297}
2298
2299#[cfg(feature = "parser")]
2300pub fn parse_style_user_select(
2304 input: &str,
2305) -> Result<StyleUserSelect, StyleUserSelectParseError<'_>> {
2306 match input.trim() {
2307 "auto" => Ok(StyleUserSelect::Auto),
2308 "text" => Ok(StyleUserSelect::Text),
2309 "none" => Ok(StyleUserSelect::None),
2310 "all" => Ok(StyleUserSelect::All),
2311 other => Err(StyleUserSelectParseError::InvalidValue(InvalidValueErr(
2312 other,
2313 ))),
2314 }
2315}
2316
2317#[cfg(feature = "parser")]
2318#[derive(Clone, PartialEq, Eq)]
2319pub enum StyleTextDecorationParseError<'a> {
2320 InvalidValue(InvalidValueErr<'a>),
2321}
2322#[cfg(feature = "parser")]
2323impl_debug_as_display!(StyleTextDecorationParseError<'a>);
2324#[cfg(feature = "parser")]
2325impl_display! { StyleTextDecorationParseError<'a>, {
2326 InvalidValue(e) => format!("Invalid text-decoration value: \"{}\"", e.0),
2327}}
2328#[cfg(feature = "parser")]
2329impl_from!(
2330 InvalidValueErr<'a>,
2331 StyleTextDecorationParseError::InvalidValue
2332);
2333
2334#[cfg(feature = "parser")]
2335#[derive(Debug, Clone, PartialEq, Eq)]
2336#[repr(C, u8)]
2337pub enum StyleTextDecorationParseErrorOwned {
2338 InvalidValue(InvalidValueErrOwned),
2339}
2340
2341#[cfg(feature = "parser")]
2342impl StyleTextDecorationParseError<'_> {
2343 #[must_use]
2344 pub fn to_contained(&self) -> StyleTextDecorationParseErrorOwned {
2345 match self {
2346 Self::InvalidValue(e) => {
2347 StyleTextDecorationParseErrorOwned::InvalidValue(e.to_contained())
2348 }
2349 }
2350 }
2351}
2352
2353#[cfg(feature = "parser")]
2354impl StyleTextDecorationParseErrorOwned {
2355 #[must_use]
2356 pub fn to_shared(&self) -> StyleTextDecorationParseError<'_> {
2357 match self {
2358 Self::InvalidValue(e) => StyleTextDecorationParseError::InvalidValue(e.to_shared()),
2359 }
2360 }
2361}
2362
2363#[cfg(feature = "parser")]
2364pub fn parse_style_text_decoration(
2368 input: &str,
2369) -> Result<StyleTextDecoration, StyleTextDecorationParseError<'_>> {
2370 match input.trim() {
2371 "none" => Ok(StyleTextDecoration::None),
2372 "underline" => Ok(StyleTextDecoration::Underline),
2373 "overline" => Ok(StyleTextDecoration::Overline),
2374 "line-through" => Ok(StyleTextDecoration::LineThrough),
2375 other => Err(StyleTextDecorationParseError::InvalidValue(
2376 InvalidValueErr(other),
2377 )),
2378 }
2379}
2380
2381#[cfg(feature = "parser")]
2382#[derive(Clone, PartialEq, Eq)]
2383pub enum StyleVerticalAlignParseError<'a> {
2384 InvalidValue(InvalidValueErr<'a>),
2385}
2386#[cfg(feature = "parser")]
2387impl_debug_as_display!(StyleVerticalAlignParseError<'a>);
2388#[cfg(feature = "parser")]
2389impl_display! { StyleVerticalAlignParseError<'a>, {
2390 InvalidValue(e) => format!("Invalid vertical-align value: \"{}\"", e.0),
2391}}
2392#[cfg(feature = "parser")]
2393impl_from!(
2394 InvalidValueErr<'a>,
2395 StyleVerticalAlignParseError::InvalidValue
2396);
2397
2398#[cfg(feature = "parser")]
2399#[derive(Debug, Clone, PartialEq, Eq)]
2400#[repr(C, u8)]
2401pub enum StyleVerticalAlignParseErrorOwned {
2402 InvalidValue(InvalidValueErrOwned),
2403}
2404
2405#[cfg(feature = "parser")]
2406impl StyleVerticalAlignParseError<'_> {
2407 #[must_use]
2408 pub fn to_contained(&self) -> StyleVerticalAlignParseErrorOwned {
2409 match self {
2410 Self::InvalidValue(e) => {
2411 StyleVerticalAlignParseErrorOwned::InvalidValue(e.to_contained())
2412 }
2413 }
2414 }
2415}
2416
2417#[cfg(feature = "parser")]
2418impl StyleVerticalAlignParseErrorOwned {
2419 #[must_use]
2420 pub fn to_shared(&self) -> StyleVerticalAlignParseError<'_> {
2421 match self {
2422 Self::InvalidValue(e) => StyleVerticalAlignParseError::InvalidValue(e.to_shared()),
2423 }
2424 }
2425}
2426
2427#[cfg(feature = "parser")]
2428pub fn parse_style_vertical_align(
2432 input: &str,
2433) -> Result<StyleVerticalAlign, StyleVerticalAlignParseError<'_>> {
2434 match input.trim() {
2435 "baseline" => Ok(StyleVerticalAlign::Baseline),
2436 "top" => Ok(StyleVerticalAlign::Top),
2437 "middle" => Ok(StyleVerticalAlign::Middle),
2438 "bottom" => Ok(StyleVerticalAlign::Bottom),
2439 "sub" => Ok(StyleVerticalAlign::Sub),
2440 "super" => Ok(StyleVerticalAlign::Superscript),
2441 "text-top" => Ok(StyleVerticalAlign::TextTop),
2442 "text-bottom" => Ok(StyleVerticalAlign::TextBottom),
2443 other if other.ends_with('%') => {
2444 let num_str = other.trim_end_matches('%').trim();
2445 num_str.parse::<f32>().map_or_else(
2446 |_| {
2447 Err(StyleVerticalAlignParseError::InvalidValue(InvalidValueErr(
2448 other,
2449 )))
2450 },
2451 |val| Ok(StyleVerticalAlign::Percentage(PercentageValue::new(val))),
2452 )
2453 }
2454 other => crate::props::basic::pixel::parse_pixel_value(other).map_or_else(
2455 |_| {
2456 Err(StyleVerticalAlignParseError::InvalidValue(InvalidValueErr(
2457 other,
2458 )))
2459 },
2460 |pv| Ok(StyleVerticalAlign::Length(pv)),
2461 ),
2462 }
2463}
2464
2465#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2468#[repr(C)]
2469pub struct CaretColor {
2470 pub inner: ColorU,
2471}
2472
2473impl Default for CaretColor {
2474 fn default() -> Self {
2475 Self {
2476 inner: ColorU::BLACK,
2477 }
2478 }
2479}
2480
2481impl PrintAsCssValue for CaretColor {
2482 fn print_as_css_value(&self) -> String {
2483 self.inner.to_hash()
2484 }
2485}
2486
2487impl FormatAsRustCode for CaretColor {
2488 fn format_as_rust_code(&self, _tabs: usize) -> String {
2489 format!(
2490 "CaretColor {{ inner: {} }}",
2491 crate::codegen::format::format_color_value(&self.inner)
2492 )
2493 }
2494}
2495
2496#[cfg(feature = "parser")]
2497pub fn parse_caret_color(input: &str) -> Result<CaretColor, CssColorParseError<'_>> {
2501 parse_css_color(input).map(|inner| CaretColor { inner })
2502}
2503
2504#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2507#[repr(C)]
2508pub struct CaretAnimationDuration {
2509 pub inner: CssDuration,
2510}
2511
2512impl Default for CaretAnimationDuration {
2513 fn default() -> Self {
2514 Self {
2515 inner: CssDuration::from_millis(500),
2516 } }
2518}
2519
2520impl PrintAsCssValue for CaretAnimationDuration {
2521 fn print_as_css_value(&self) -> String {
2522 self.inner.print_as_css_value()
2523 }
2524}
2525
2526impl FormatAsRustCode for CaretAnimationDuration {
2527 fn format_as_rust_code(&self, _tabs: usize) -> String {
2528 format!(
2529 "CaretAnimationDuration {{ inner: {} }}",
2530 self.inner.format_as_rust_code(0)
2531 )
2532 }
2533}
2534
2535#[cfg(feature = "parser")]
2536pub fn parse_caret_animation_duration(
2540 input: &str,
2541) -> Result<CaretAnimationDuration, DurationParseError<'_>> {
2542 use crate::props::basic::parse_duration;
2543
2544 parse_duration(input).map(|inner| CaretAnimationDuration { inner })
2545}
2546
2547#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2552#[repr(C)]
2553pub struct CaretWidth {
2554 pub inner: PixelValue,
2555}
2556
2557impl Default for CaretWidth {
2558 fn default() -> Self {
2559 Self {
2560 inner: PixelValue::px(2.0), }
2562 }
2563}
2564
2565impl PrintAsCssValue for CaretWidth {
2566 fn print_as_css_value(&self) -> String {
2567 self.inner.print_as_css_value()
2568 }
2569}
2570
2571impl FormatAsRustCode for CaretWidth {
2572 fn format_as_rust_code(&self, _tabs: usize) -> String {
2573 format!(
2574 "CaretWidth {{ inner: {} }}",
2575 self.inner.format_as_rust_code(0)
2576 )
2577 }
2578}
2579
2580#[cfg(feature = "parser")]
2581pub fn parse_caret_width(input: &str) -> Result<CaretWidth, CssPixelValueParseError<'_>> {
2585 use crate::props::basic::pixel::parse_pixel_value;
2586
2587 parse_pixel_value(input).map(|inner| CaretWidth { inner })
2588}
2589
2590impl From<StyleUserSelect> for crate::props::property::CssProperty {
2593 fn from(value: StyleUserSelect) -> Self {
2594 use crate::props::property::CssProperty;
2595 Self::user_select(value)
2596 }
2597}
2598
2599impl From<StyleTextDecoration> for crate::props::property::CssProperty {
2600 fn from(value: StyleTextDecoration) -> Self {
2601 use crate::props::property::CssProperty;
2602 Self::text_decoration(value)
2603 }
2604}
2605
2606#[cfg(all(test, feature = "parser"))]
2607mod tests {
2608 use super::*;
2609 use crate::props::basic::{color::ColorU, length::PercentageValue, pixel::PixelValue};
2610
2611 #[test]
2612 fn test_parse_style_text_color() {
2613 assert_eq!(
2614 parse_style_text_color("red").unwrap().inner,
2615 ColorU::new_rgb(255, 0, 0)
2616 );
2617 assert_eq!(
2618 parse_style_text_color("#aabbcc").unwrap().inner,
2619 ColorU::new_rgb(170, 187, 204)
2620 );
2621 assert!(parse_style_text_color("not-a-color").is_err());
2622 }
2623
2624 #[test]
2625 fn test_parse_style_text_align() {
2626 assert_eq!(
2627 parse_style_text_align("left").unwrap(),
2628 StyleTextAlign::Left
2629 );
2630 assert_eq!(
2631 parse_style_text_align("center").unwrap(),
2632 StyleTextAlign::Center
2633 );
2634 assert_eq!(
2635 parse_style_text_align("right").unwrap(),
2636 StyleTextAlign::Right
2637 );
2638 assert_eq!(
2639 parse_style_text_align("justify").unwrap(),
2640 StyleTextAlign::Justify
2641 );
2642 assert_eq!(
2643 parse_style_text_align("start").unwrap(),
2644 StyleTextAlign::Start
2645 );
2646 assert_eq!(parse_style_text_align("end").unwrap(), StyleTextAlign::End);
2647 assert!(parse_style_text_align("middle").is_err());
2648 }
2649
2650 #[test]
2651 fn test_parse_spacing() {
2652 assert_eq!(
2653 parse_style_letter_spacing("2px").unwrap().inner,
2654 PixelValue::px(2.0)
2655 );
2656 assert_eq!(
2657 parse_style_letter_spacing("-0.1em").unwrap().inner,
2658 PixelValue::em(-0.1)
2659 );
2660 assert_eq!(
2661 parse_style_word_spacing("0.5em").unwrap().inner,
2662 PixelValue::em(0.5)
2663 );
2664 }
2665
2666 #[test]
2667 fn test_parse_line_height() {
2668 assert_eq!(
2669 parse_style_line_height("1.5").unwrap().inner,
2670 PercentageValue::new(150.0)
2671 );
2672 assert_eq!(
2673 parse_style_line_height("120%").unwrap().inner,
2674 PercentageValue::new(120.0)
2675 );
2676 assert_eq!(
2678 parse_style_line_height("20px").unwrap().inner,
2679 PercentageValue::new(-20.0 * 100.0)
2680 );
2681 }
2682
2683 #[test]
2684 fn test_parse_tab_size() {
2685 assert_eq!(
2687 parse_style_tab_size("4").unwrap().inner,
2688 PixelValue::em(4.0)
2689 );
2690 assert_eq!(
2691 parse_style_tab_size("20px").unwrap().inner,
2692 PixelValue::px(20.0)
2693 );
2694 }
2695
2696 #[test]
2697 fn test_parse_white_space() {
2698 assert_eq!(
2699 parse_style_white_space("normal").unwrap(),
2700 StyleWhiteSpace::Normal
2701 );
2702 assert_eq!(
2703 parse_style_white_space("pre").unwrap(),
2704 StyleWhiteSpace::Pre
2705 );
2706 assert_eq!(
2707 parse_style_white_space("nowrap").unwrap(),
2708 StyleWhiteSpace::Nowrap
2709 );
2710 assert_eq!(
2711 parse_style_white_space("pre-wrap").unwrap(),
2712 StyleWhiteSpace::PreWrap
2713 );
2714 }
2715}
2716
2717#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2723#[repr(C)]
2724#[derive(Default)]
2725pub enum StyleUnicodeBidi {
2726 #[default]
2728 Normal,
2729 Embed,
2731 Isolate,
2733 BidiOverride,
2735 IsolateOverride,
2737 Plaintext,
2739}
2740impl_option!(
2741 StyleUnicodeBidi,
2742 OptionStyleUnicodeBidi,
2743 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
2744);
2745impl PrintAsCssValue for StyleUnicodeBidi {
2746 fn print_as_css_value(&self) -> String {
2747 String::from(match self {
2748 Self::Normal => "normal",
2749 Self::Embed => "embed",
2750 Self::Isolate => "isolate",
2751 Self::BidiOverride => "bidi-override",
2752 Self::IsolateOverride => "isolate-override",
2753 Self::Plaintext => "plaintext",
2754 })
2755 }
2756}
2757
2758#[cfg(feature = "parser")]
2759#[derive(Clone, PartialEq, Eq)]
2760pub enum StyleUnicodeBidiParseError<'a> {
2761 InvalidValue(InvalidValueErr<'a>),
2762}
2763#[cfg(feature = "parser")]
2764impl_debug_as_display!(StyleUnicodeBidiParseError<'a>);
2765#[cfg(feature = "parser")]
2766impl_display! { StyleUnicodeBidiParseError<'a>, {
2767 InvalidValue(e) => format!("Invalid unicode-bidi value: \"{}\"", e.0),
2768}}
2769#[cfg(feature = "parser")]
2770impl_from!(
2771 InvalidValueErr<'a>,
2772 StyleUnicodeBidiParseError::InvalidValue
2773);
2774
2775#[cfg(feature = "parser")]
2776#[derive(Debug, Clone, PartialEq, Eq)]
2777#[repr(C, u8)]
2778pub enum StyleUnicodeBidiParseErrorOwned {
2779 InvalidValue(InvalidValueErrOwned),
2780}
2781
2782#[cfg(feature = "parser")]
2783impl StyleUnicodeBidiParseError<'_> {
2784 #[must_use]
2785 pub fn to_contained(&self) -> StyleUnicodeBidiParseErrorOwned {
2786 match self {
2787 Self::InvalidValue(e) => {
2788 StyleUnicodeBidiParseErrorOwned::InvalidValue(e.to_contained())
2789 }
2790 }
2791 }
2792}
2793
2794#[cfg(feature = "parser")]
2795impl StyleUnicodeBidiParseErrorOwned {
2796 #[must_use]
2797 pub fn to_shared(&self) -> StyleUnicodeBidiParseError<'_> {
2798 match self {
2799 Self::InvalidValue(e) => StyleUnicodeBidiParseError::InvalidValue(e.to_shared()),
2800 }
2801 }
2802}
2803
2804#[cfg(feature = "parser")]
2805pub fn parse_style_unicode_bidi(
2809 input: &str,
2810) -> Result<StyleUnicodeBidi, StyleUnicodeBidiParseError<'_>> {
2811 match input.trim() {
2812 "normal" => Ok(StyleUnicodeBidi::Normal),
2813 "embed" => Ok(StyleUnicodeBidi::Embed),
2814 "isolate" => Ok(StyleUnicodeBidi::Isolate),
2815 "bidi-override" => Ok(StyleUnicodeBidi::BidiOverride),
2816 "isolate-override" => Ok(StyleUnicodeBidi::IsolateOverride),
2817 "plaintext" => Ok(StyleUnicodeBidi::Plaintext),
2818 other => Err(StyleUnicodeBidiParseError::InvalidValue(InvalidValueErr(
2819 other,
2820 ))),
2821 }
2822}
2823
2824#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2830#[repr(C)]
2831#[derive(Default)]
2832pub enum StyleTextBoxTrim {
2833 #[default]
2835 None,
2836 TrimStart,
2838 TrimEnd,
2840 TrimBoth,
2842}
2843impl_option!(
2844 StyleTextBoxTrim,
2845 OptionStyleTextBoxTrim,
2846 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
2847);
2848impl PrintAsCssValue for StyleTextBoxTrim {
2849 fn print_as_css_value(&self) -> String {
2850 String::from(match self {
2851 Self::None => "none",
2852 Self::TrimStart => "trim-start",
2853 Self::TrimEnd => "trim-end",
2854 Self::TrimBoth => "trim-both",
2855 })
2856 }
2857}
2858
2859#[cfg(feature = "parser")]
2860#[derive(Clone, PartialEq, Eq)]
2861pub enum StyleTextBoxTrimParseError<'a> {
2862 InvalidValue(InvalidValueErr<'a>),
2863}
2864#[cfg(feature = "parser")]
2865impl_debug_as_display!(StyleTextBoxTrimParseError<'a>);
2866#[cfg(feature = "parser")]
2867impl_display! { StyleTextBoxTrimParseError<'a>, {
2868 InvalidValue(e) => format!("Invalid text-box-trim value: \"{}\"", e.0),
2869}}
2870#[cfg(feature = "parser")]
2871impl_from!(
2872 InvalidValueErr<'a>,
2873 StyleTextBoxTrimParseError::InvalidValue
2874);
2875
2876#[cfg(feature = "parser")]
2877#[derive(Debug, Clone, PartialEq, Eq)]
2878#[repr(C, u8)]
2879pub enum StyleTextBoxTrimParseErrorOwned {
2880 InvalidValue(InvalidValueErrOwned),
2881}
2882
2883#[cfg(feature = "parser")]
2884impl StyleTextBoxTrimParseError<'_> {
2885 #[must_use]
2886 pub fn to_contained(&self) -> StyleTextBoxTrimParseErrorOwned {
2887 match self {
2888 Self::InvalidValue(e) => {
2889 StyleTextBoxTrimParseErrorOwned::InvalidValue(e.to_contained())
2890 }
2891 }
2892 }
2893}
2894
2895#[cfg(feature = "parser")]
2896impl StyleTextBoxTrimParseErrorOwned {
2897 #[must_use]
2898 pub fn to_shared(&self) -> StyleTextBoxTrimParseError<'_> {
2899 match self {
2900 Self::InvalidValue(e) => StyleTextBoxTrimParseError::InvalidValue(e.to_shared()),
2901 }
2902 }
2903}
2904
2905#[cfg(feature = "parser")]
2906pub fn parse_style_text_box_trim(
2910 input: &str,
2911) -> Result<StyleTextBoxTrim, StyleTextBoxTrimParseError<'_>> {
2912 match input.trim() {
2913 "none" => Ok(StyleTextBoxTrim::None),
2914 "trim-start" => Ok(StyleTextBoxTrim::TrimStart),
2915 "trim-end" => Ok(StyleTextBoxTrim::TrimEnd),
2916 "trim-both" => Ok(StyleTextBoxTrim::TrimBoth),
2917 other => Err(StyleTextBoxTrimParseError::InvalidValue(InvalidValueErr(
2918 other,
2919 ))),
2920 }
2921}
2922
2923#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
2927#[repr(C)]
2928pub enum TextBoxEdgeOver {
2929 #[default]
2933 Auto,
2934 Text,
2936 Cap,
2938 Ex,
2940 Ideographic,
2942 IdeographicInk,
2944}
2945
2946#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
2948#[repr(C)]
2949pub enum TextBoxEdgeUnder {
2950 #[default]
2952 Auto,
2953 Text,
2955 Alphabetic,
2957 Ideographic,
2959 IdeographicInk,
2961}
2962
2963#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
2975#[repr(C)]
2976pub struct StyleTextBoxEdge {
2977 pub over: TextBoxEdgeOver,
2978 pub under: TextBoxEdgeUnder,
2979}
2980
2981impl StyleTextBoxEdge {
2982 pub const AUTO: Self = Self {
2984 over: TextBoxEdgeOver::Auto,
2985 under: TextBoxEdgeUnder::Auto,
2986 };
2987}
2988impl_option!(
2989 StyleTextBoxEdge,
2990 OptionStyleTextBoxEdge,
2991 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
2992);
2993impl PrintAsCssValue for StyleTextBoxEdge {
2994 fn print_as_css_value(&self) -> String {
2995 let over = match self.over {
2996 TextBoxEdgeOver::Auto => return String::from("auto"),
2997 TextBoxEdgeOver::Text => "text",
2998 TextBoxEdgeOver::Cap => "cap",
2999 TextBoxEdgeOver::Ex => "ex",
3000 TextBoxEdgeOver::Ideographic => "ideographic",
3001 TextBoxEdgeOver::IdeographicInk => "ideographic-ink",
3002 };
3003 let under = match self.under {
3004 TextBoxEdgeUnder::Auto | TextBoxEdgeUnder::Text => "text",
3005 TextBoxEdgeUnder::Alphabetic => "alphabetic",
3006 TextBoxEdgeUnder::Ideographic => "ideographic",
3007 TextBoxEdgeUnder::IdeographicInk => "ideographic-ink",
3008 };
3009 let single_round_trips = match self.over {
3012 TextBoxEdgeOver::Text => {
3013 matches!(self.under, TextBoxEdgeUnder::Text | TextBoxEdgeUnder::Auto)
3014 }
3015 TextBoxEdgeOver::Cap | TextBoxEdgeOver::Ex => {
3016 matches!(self.under, TextBoxEdgeUnder::Text | TextBoxEdgeUnder::Auto)
3017 }
3018 TextBoxEdgeOver::Ideographic => matches!(self.under, TextBoxEdgeUnder::Ideographic),
3019 TextBoxEdgeOver::IdeographicInk => {
3020 matches!(self.under, TextBoxEdgeUnder::IdeographicInk)
3021 }
3022 TextBoxEdgeOver::Auto => unreachable!(),
3023 };
3024 if single_round_trips {
3025 String::from(over)
3026 } else {
3027 alloc::format!("{over} {under}")
3028 }
3029 }
3030}
3031
3032#[cfg(feature = "parser")]
3033#[derive(Clone, PartialEq, Eq)]
3034pub enum StyleTextBoxEdgeParseError<'a> {
3035 InvalidValue(InvalidValueErr<'a>),
3036}
3037#[cfg(feature = "parser")]
3038impl_debug_as_display!(StyleTextBoxEdgeParseError<'a>);
3039#[cfg(feature = "parser")]
3040impl_display! { StyleTextBoxEdgeParseError<'a>, {
3041 InvalidValue(e) => format!("Invalid text-box-edge value: \"{}\"", e.0),
3042}}
3043#[cfg(feature = "parser")]
3044impl_from!(
3045 InvalidValueErr<'a>,
3046 StyleTextBoxEdgeParseError::InvalidValue
3047);
3048
3049#[cfg(feature = "parser")]
3050#[derive(Debug, Clone, PartialEq, Eq)]
3051#[repr(C, u8)]
3052pub enum StyleTextBoxEdgeParseErrorOwned {
3053 InvalidValue(InvalidValueErrOwned),
3054}
3055
3056#[cfg(feature = "parser")]
3057impl StyleTextBoxEdgeParseError<'_> {
3058 #[must_use]
3059 pub fn to_contained(&self) -> StyleTextBoxEdgeParseErrorOwned {
3060 match self {
3061 Self::InvalidValue(e) => {
3062 StyleTextBoxEdgeParseErrorOwned::InvalidValue(e.to_contained())
3063 }
3064 }
3065 }
3066}
3067
3068#[cfg(feature = "parser")]
3069impl StyleTextBoxEdgeParseErrorOwned {
3070 #[must_use]
3071 pub fn to_shared(&self) -> StyleTextBoxEdgeParseError<'_> {
3072 match self {
3073 Self::InvalidValue(e) => StyleTextBoxEdgeParseError::InvalidValue(e.to_shared()),
3074 }
3075 }
3076}
3077
3078#[cfg(feature = "parser")]
3079pub fn parse_style_text_box_edge(
3083 input: &str,
3084) -> Result<StyleTextBoxEdge, StyleTextBoxEdgeParseError<'_>> {
3085 let trimmed = input.trim();
3086 let mut parts = trimmed.split_whitespace();
3087 let first = parts.next().unwrap_or("");
3088 let second = parts.next();
3089 if parts.next().is_some() {
3090 return Err(StyleTextBoxEdgeParseError::InvalidValue(InvalidValueErr(
3091 input,
3092 )));
3093 }
3094
3095 let over = match first {
3096 "auto" => {
3097 if second.is_some() {
3099 return Err(StyleTextBoxEdgeParseError::InvalidValue(InvalidValueErr(
3100 input,
3101 )));
3102 }
3103 return Ok(StyleTextBoxEdge::AUTO);
3104 }
3105 "text" => TextBoxEdgeOver::Text,
3106 "cap" => TextBoxEdgeOver::Cap,
3107 "ex" => TextBoxEdgeOver::Ex,
3108 "ideographic" => TextBoxEdgeOver::Ideographic,
3109 "ideographic-ink" => TextBoxEdgeOver::IdeographicInk,
3110 other => {
3111 return Err(StyleTextBoxEdgeParseError::InvalidValue(InvalidValueErr(
3112 other,
3113 )))
3114 }
3115 };
3116
3117 let under = match second {
3118 Some("text") => TextBoxEdgeUnder::Text,
3119 Some("alphabetic") => TextBoxEdgeUnder::Alphabetic,
3120 Some("ideographic") => TextBoxEdgeUnder::Ideographic,
3121 Some("ideographic-ink") => TextBoxEdgeUnder::IdeographicInk,
3122 Some(other) => {
3123 return Err(StyleTextBoxEdgeParseError::InvalidValue(InvalidValueErr(
3124 other,
3125 )))
3126 }
3127 None => match over {
3130 TextBoxEdgeOver::Ideographic => TextBoxEdgeUnder::Ideographic,
3131 TextBoxEdgeOver::IdeographicInk => TextBoxEdgeUnder::IdeographicInk,
3132 _ => TextBoxEdgeUnder::Text,
3133 },
3134 };
3135
3136 Ok(StyleTextBoxEdge { over, under })
3137}
3138
3139#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
3145#[repr(C)]
3146#[derive(Default)]
3147pub enum StyleDominantBaseline {
3148 #[default]
3150 Auto,
3151 TextBottom,
3153 Alphabetic,
3155 Ideographic,
3157 Middle,
3159 Central,
3161 Mathematical,
3163 Hanging,
3165 TextTop,
3167}
3168impl_option!(
3169 StyleDominantBaseline,
3170 OptionStyleDominantBaseline,
3171 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
3172);
3173impl PrintAsCssValue for StyleDominantBaseline {
3174 fn print_as_css_value(&self) -> String {
3175 String::from(match self {
3176 Self::Auto => "auto",
3177 Self::TextBottom => "text-bottom",
3178 Self::Alphabetic => "alphabetic",
3179 Self::Ideographic => "ideographic",
3180 Self::Middle => "middle",
3181 Self::Central => "central",
3182 Self::Mathematical => "mathematical",
3183 Self::Hanging => "hanging",
3184 Self::TextTop => "text-top",
3185 })
3186 }
3187}
3188
3189#[cfg(feature = "parser")]
3190#[derive(Clone, PartialEq, Eq)]
3191pub enum StyleDominantBaselineParseError<'a> {
3192 InvalidValue(InvalidValueErr<'a>),
3193}
3194#[cfg(feature = "parser")]
3195impl_debug_as_display!(StyleDominantBaselineParseError<'a>);
3196#[cfg(feature = "parser")]
3197impl_display! { StyleDominantBaselineParseError<'a>, {
3198 InvalidValue(e) => format!("Invalid dominant-baseline value: \"{}\"", e.0),
3199}}
3200#[cfg(feature = "parser")]
3201impl_from!(
3202 InvalidValueErr<'a>,
3203 StyleDominantBaselineParseError::InvalidValue
3204);
3205
3206#[cfg(feature = "parser")]
3207#[derive(Debug, Clone, PartialEq, Eq)]
3208#[repr(C, u8)]
3209pub enum StyleDominantBaselineParseErrorOwned {
3210 InvalidValue(InvalidValueErrOwned),
3211}
3212
3213#[cfg(feature = "parser")]
3214impl StyleDominantBaselineParseError<'_> {
3215 #[must_use]
3216 pub fn to_contained(&self) -> StyleDominantBaselineParseErrorOwned {
3217 match self {
3218 Self::InvalidValue(e) => {
3219 StyleDominantBaselineParseErrorOwned::InvalidValue(e.to_contained())
3220 }
3221 }
3222 }
3223}
3224
3225#[cfg(feature = "parser")]
3226impl StyleDominantBaselineParseErrorOwned {
3227 #[must_use]
3228 pub fn to_shared(&self) -> StyleDominantBaselineParseError<'_> {
3229 match self {
3230 Self::InvalidValue(e) => StyleDominantBaselineParseError::InvalidValue(e.to_shared()),
3231 }
3232 }
3233}
3234
3235#[cfg(feature = "parser")]
3236pub fn parse_style_dominant_baseline(
3240 input: &str,
3241) -> Result<StyleDominantBaseline, StyleDominantBaselineParseError<'_>> {
3242 match input.trim() {
3243 "auto" => Ok(StyleDominantBaseline::Auto),
3244 "text-bottom" => Ok(StyleDominantBaseline::TextBottom),
3245 "alphabetic" => Ok(StyleDominantBaseline::Alphabetic),
3246 "ideographic" => Ok(StyleDominantBaseline::Ideographic),
3247 "middle" => Ok(StyleDominantBaseline::Middle),
3248 "central" => Ok(StyleDominantBaseline::Central),
3249 "mathematical" => Ok(StyleDominantBaseline::Mathematical),
3250 "hanging" => Ok(StyleDominantBaseline::Hanging),
3251 "text-top" => Ok(StyleDominantBaseline::TextTop),
3252 other => Err(StyleDominantBaselineParseError::InvalidValue(
3253 InvalidValueErr(other),
3254 )),
3255 }
3256}
3257
3258#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
3268#[repr(C)]
3269#[derive(Default)]
3270pub enum StyleAlignmentBaseline {
3271 #[default]
3273 Baseline,
3274 TextBottom,
3276 Alphabetic,
3278 Ideographic,
3280 Middle,
3282 Central,
3284 Mathematical,
3286 TextTop,
3288}
3289impl_option!(
3290 StyleAlignmentBaseline,
3291 OptionStyleAlignmentBaseline,
3292 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
3293);
3294impl PrintAsCssValue for StyleAlignmentBaseline {
3295 fn print_as_css_value(&self) -> String {
3296 String::from(match self {
3297 Self::Baseline => "baseline",
3298 Self::TextBottom => "text-bottom",
3299 Self::Alphabetic => "alphabetic",
3300 Self::Ideographic => "ideographic",
3301 Self::Middle => "middle",
3302 Self::Central => "central",
3303 Self::Mathematical => "mathematical",
3304 Self::TextTop => "text-top",
3305 })
3306 }
3307}
3308
3309#[cfg(feature = "parser")]
3310#[derive(Clone, PartialEq, Eq)]
3311pub enum StyleAlignmentBaselineParseError<'a> {
3312 InvalidValue(InvalidValueErr<'a>),
3313}
3314#[cfg(feature = "parser")]
3315impl_debug_as_display!(StyleAlignmentBaselineParseError<'a>);
3316#[cfg(feature = "parser")]
3317impl_display! { StyleAlignmentBaselineParseError<'a>, {
3318 InvalidValue(e) => format!("Invalid alignment-baseline value: \"{}\"", e.0),
3319}}
3320#[cfg(feature = "parser")]
3321impl_from!(
3322 InvalidValueErr<'a>,
3323 StyleAlignmentBaselineParseError::InvalidValue
3324);
3325
3326#[cfg(feature = "parser")]
3327#[derive(Debug, Clone, PartialEq, Eq)]
3328#[repr(C, u8)]
3329pub enum StyleAlignmentBaselineParseErrorOwned {
3330 InvalidValue(InvalidValueErrOwned),
3331}
3332
3333#[cfg(feature = "parser")]
3334impl StyleAlignmentBaselineParseError<'_> {
3335 #[must_use]
3336 pub fn to_contained(&self) -> StyleAlignmentBaselineParseErrorOwned {
3337 match self {
3338 Self::InvalidValue(e) => {
3339 StyleAlignmentBaselineParseErrorOwned::InvalidValue(e.to_contained())
3340 }
3341 }
3342 }
3343}
3344
3345#[cfg(feature = "parser")]
3346impl StyleAlignmentBaselineParseErrorOwned {
3347 #[must_use]
3348 pub fn to_shared(&self) -> StyleAlignmentBaselineParseError<'_> {
3349 match self {
3350 Self::InvalidValue(e) => StyleAlignmentBaselineParseError::InvalidValue(e.to_shared()),
3351 }
3352 }
3353}
3354
3355#[cfg(feature = "parser")]
3356pub fn parse_style_alignment_baseline(
3360 input: &str,
3361) -> Result<StyleAlignmentBaseline, StyleAlignmentBaselineParseError<'_>> {
3362 match input.trim() {
3363 "baseline" => Ok(StyleAlignmentBaseline::Baseline),
3364 "text-bottom" => Ok(StyleAlignmentBaseline::TextBottom),
3365 "alphabetic" => Ok(StyleAlignmentBaseline::Alphabetic),
3366 "ideographic" => Ok(StyleAlignmentBaseline::Ideographic),
3367 "middle" => Ok(StyleAlignmentBaseline::Middle),
3368 "central" => Ok(StyleAlignmentBaseline::Central),
3369 "mathematical" => Ok(StyleAlignmentBaseline::Mathematical),
3370 "text-top" => Ok(StyleAlignmentBaseline::TextTop),
3371 other => Err(StyleAlignmentBaselineParseError::InvalidValue(
3372 InvalidValueErr(other),
3373 )),
3374 }
3375}
3376
3377#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
3385#[repr(C)]
3386#[derive(Default)]
3387pub enum StyleBaselineSource {
3388 #[default]
3390 Auto,
3391 First,
3393 Last,
3395}
3396impl_option!(
3397 StyleBaselineSource,
3398 OptionStyleBaselineSource,
3399 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
3400);
3401impl PrintAsCssValue for StyleBaselineSource {
3402 fn print_as_css_value(&self) -> String {
3403 String::from(match self {
3404 Self::Auto => "auto",
3405 Self::First => "first",
3406 Self::Last => "last",
3407 })
3408 }
3409}
3410
3411#[cfg(feature = "parser")]
3412#[derive(Clone, PartialEq, Eq)]
3413pub enum StyleBaselineSourceParseError<'a> {
3414 InvalidValue(InvalidValueErr<'a>),
3415}
3416#[cfg(feature = "parser")]
3417impl_debug_as_display!(StyleBaselineSourceParseError<'a>);
3418#[cfg(feature = "parser")]
3419impl_display! { StyleBaselineSourceParseError<'a>, {
3420 InvalidValue(e) => format!("Invalid baseline-source value: \"{}\"", e.0),
3421}}
3422#[cfg(feature = "parser")]
3423impl_from!(
3424 InvalidValueErr<'a>,
3425 StyleBaselineSourceParseError::InvalidValue
3426);
3427
3428#[cfg(feature = "parser")]
3429#[derive(Debug, Clone, PartialEq, Eq)]
3430#[repr(C, u8)]
3431pub enum StyleBaselineSourceParseErrorOwned {
3432 InvalidValue(InvalidValueErrOwned),
3433}
3434
3435#[cfg(feature = "parser")]
3436impl StyleBaselineSourceParseError<'_> {
3437 #[must_use]
3438 pub fn to_contained(&self) -> StyleBaselineSourceParseErrorOwned {
3439 match self {
3440 Self::InvalidValue(e) => {
3441 StyleBaselineSourceParseErrorOwned::InvalidValue(e.to_contained())
3442 }
3443 }
3444 }
3445}
3446
3447#[cfg(feature = "parser")]
3448impl StyleBaselineSourceParseErrorOwned {
3449 #[must_use]
3450 pub fn to_shared(&self) -> StyleBaselineSourceParseError<'_> {
3451 match self {
3452 Self::InvalidValue(e) => StyleBaselineSourceParseError::InvalidValue(e.to_shared()),
3453 }
3454 }
3455}
3456
3457#[cfg(feature = "parser")]
3458pub fn parse_style_baseline_source(
3462 input: &str,
3463) -> Result<StyleBaselineSource, StyleBaselineSourceParseError<'_>> {
3464 match input.trim() {
3465 "auto" => Ok(StyleBaselineSource::Auto),
3466 "first" => Ok(StyleBaselineSource::First),
3467 "last" => Ok(StyleBaselineSource::Last),
3468 other => Err(StyleBaselineSourceParseError::InvalidValue(
3469 InvalidValueErr(other),
3470 )),
3471 }
3472}
3473
3474#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
3484#[repr(C)]
3485#[derive(Default)]
3486pub enum StyleLineFitEdge {
3487 #[default]
3489 Leading,
3490 Text,
3492 Cap,
3494 Ex,
3496 Ideographic,
3498 IdeographicInk,
3500 Alphabetic,
3502}
3503impl_option!(
3504 StyleLineFitEdge,
3505 OptionStyleLineFitEdge,
3506 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
3507);
3508impl PrintAsCssValue for StyleLineFitEdge {
3509 fn print_as_css_value(&self) -> String {
3510 String::from(match self {
3511 Self::Leading => "leading",
3512 Self::Text => "text",
3513 Self::Cap => "cap",
3514 Self::Ex => "ex",
3515 Self::Ideographic => "ideographic",
3516 Self::IdeographicInk => "ideographic-ink",
3517 Self::Alphabetic => "alphabetic",
3518 })
3519 }
3520}
3521
3522#[cfg(feature = "parser")]
3523#[derive(Clone, PartialEq, Eq)]
3524pub enum StyleLineFitEdgeParseError<'a> {
3525 InvalidValue(InvalidValueErr<'a>),
3526}
3527#[cfg(feature = "parser")]
3528impl_debug_as_display!(StyleLineFitEdgeParseError<'a>);
3529#[cfg(feature = "parser")]
3530impl_display! { StyleLineFitEdgeParseError<'a>, {
3531 InvalidValue(e) => format!("Invalid line-fit-edge value: \"{}\"", e.0),
3532}}
3533#[cfg(feature = "parser")]
3534impl_from!(
3535 InvalidValueErr<'a>,
3536 StyleLineFitEdgeParseError::InvalidValue
3537);
3538
3539#[cfg(feature = "parser")]
3540#[derive(Debug, Clone, PartialEq, Eq)]
3541#[repr(C, u8)]
3542pub enum StyleLineFitEdgeParseErrorOwned {
3543 InvalidValue(InvalidValueErrOwned),
3544}
3545
3546#[cfg(feature = "parser")]
3547impl StyleLineFitEdgeParseError<'_> {
3548 #[must_use]
3549 pub fn to_contained(&self) -> StyleLineFitEdgeParseErrorOwned {
3550 match self {
3551 Self::InvalidValue(e) => {
3552 StyleLineFitEdgeParseErrorOwned::InvalidValue(e.to_contained())
3553 }
3554 }
3555 }
3556}
3557
3558#[cfg(feature = "parser")]
3559impl StyleLineFitEdgeParseErrorOwned {
3560 #[must_use]
3561 pub fn to_shared(&self) -> StyleLineFitEdgeParseError<'_> {
3562 match self {
3563 Self::InvalidValue(e) => StyleLineFitEdgeParseError::InvalidValue(e.to_shared()),
3564 }
3565 }
3566}
3567
3568#[cfg(feature = "parser")]
3569pub fn parse_style_line_fit_edge(
3573 input: &str,
3574) -> Result<StyleLineFitEdge, StyleLineFitEdgeParseError<'_>> {
3575 match input.trim() {
3576 "leading" => Ok(StyleLineFitEdge::Leading),
3577 "text" => Ok(StyleLineFitEdge::Text),
3578 "cap" => Ok(StyleLineFitEdge::Cap),
3579 "ex" => Ok(StyleLineFitEdge::Ex),
3580 "ideographic" => Ok(StyleLineFitEdge::Ideographic),
3581 "ideographic-ink" => Ok(StyleLineFitEdge::IdeographicInk),
3582 "alphabetic" => Ok(StyleLineFitEdge::Alphabetic),
3583 other => Err(StyleLineFitEdgeParseError::InvalidValue(InvalidValueErr(
3584 other,
3585 ))),
3586 }
3587}
3588
3589#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
3595#[repr(C)]
3596#[derive(Default)]
3597pub enum StyleInitialLetterAlign {
3598 #[default]
3600 Auto,
3601 Alphabetic,
3603 Hanging,
3605 Ideographic,
3607}
3608impl_option!(
3609 StyleInitialLetterAlign,
3610 OptionStyleInitialLetterAlign,
3611 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
3612);
3613impl PrintAsCssValue for StyleInitialLetterAlign {
3614 fn print_as_css_value(&self) -> String {
3615 String::from(match self {
3616 Self::Auto => "auto",
3617 Self::Alphabetic => "alphabetic",
3618 Self::Hanging => "hanging",
3619 Self::Ideographic => "ideographic",
3620 })
3621 }
3622}
3623
3624#[cfg(feature = "parser")]
3625#[derive(Clone, PartialEq, Eq)]
3626pub enum StyleInitialLetterAlignParseError<'a> {
3627 InvalidValue(InvalidValueErr<'a>),
3628}
3629#[cfg(feature = "parser")]
3630impl_debug_as_display!(StyleInitialLetterAlignParseError<'a>);
3631#[cfg(feature = "parser")]
3632impl_display! { StyleInitialLetterAlignParseError<'a>, {
3633 InvalidValue(e) => format!("Invalid initial-letter-align value: \"{}\"", e.0),
3634}}
3635#[cfg(feature = "parser")]
3636impl_from!(
3637 InvalidValueErr<'a>,
3638 StyleInitialLetterAlignParseError::InvalidValue
3639);
3640
3641#[cfg(feature = "parser")]
3642#[derive(Debug, Clone, PartialEq, Eq)]
3643#[repr(C, u8)]
3644pub enum StyleInitialLetterAlignParseErrorOwned {
3645 InvalidValue(InvalidValueErrOwned),
3646}
3647
3648#[cfg(feature = "parser")]
3649impl StyleInitialLetterAlignParseError<'_> {
3650 #[must_use]
3651 pub fn to_contained(&self) -> StyleInitialLetterAlignParseErrorOwned {
3652 match self {
3653 Self::InvalidValue(e) => {
3654 StyleInitialLetterAlignParseErrorOwned::InvalidValue(e.to_contained())
3655 }
3656 }
3657 }
3658}
3659
3660#[cfg(feature = "parser")]
3661impl StyleInitialLetterAlignParseErrorOwned {
3662 #[must_use]
3663 pub fn to_shared(&self) -> StyleInitialLetterAlignParseError<'_> {
3664 match self {
3665 Self::InvalidValue(e) => StyleInitialLetterAlignParseError::InvalidValue(e.to_shared()),
3666 }
3667 }
3668}
3669
3670#[cfg(feature = "parser")]
3671pub fn parse_style_initial_letter_align(
3675 input: &str,
3676) -> Result<StyleInitialLetterAlign, StyleInitialLetterAlignParseError<'_>> {
3677 match input.trim() {
3678 "auto" => Ok(StyleInitialLetterAlign::Auto),
3679 "alphabetic" => Ok(StyleInitialLetterAlign::Alphabetic),
3680 "hanging" => Ok(StyleInitialLetterAlign::Hanging),
3681 "ideographic" => Ok(StyleInitialLetterAlign::Ideographic),
3682 other => Err(StyleInitialLetterAlignParseError::InvalidValue(
3683 InvalidValueErr(other),
3684 )),
3685 }
3686}
3687
3688#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
3694#[repr(C)]
3695#[derive(Default)]
3696pub enum StyleInitialLetterWrap {
3697 #[default]
3699 None,
3700 First,
3702 All,
3704 Grid,
3706}
3707impl_option!(
3708 StyleInitialLetterWrap,
3709 OptionStyleInitialLetterWrap,
3710 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
3711);
3712impl PrintAsCssValue for StyleInitialLetterWrap {
3713 fn print_as_css_value(&self) -> String {
3714 String::from(match self {
3715 Self::None => "none",
3716 Self::First => "first",
3717 Self::All => "all",
3718 Self::Grid => "grid",
3719 })
3720 }
3721}
3722
3723#[cfg(feature = "parser")]
3724#[derive(Clone, PartialEq, Eq)]
3725pub enum StyleInitialLetterWrapParseError<'a> {
3726 InvalidValue(InvalidValueErr<'a>),
3727}
3728#[cfg(feature = "parser")]
3729impl_debug_as_display!(StyleInitialLetterWrapParseError<'a>);
3730#[cfg(feature = "parser")]
3731impl_display! { StyleInitialLetterWrapParseError<'a>, {
3732 InvalidValue(e) => format!("Invalid initial-letter-wrap value: \"{}\"", e.0),
3733}}
3734#[cfg(feature = "parser")]
3735impl_from!(
3736 InvalidValueErr<'a>,
3737 StyleInitialLetterWrapParseError::InvalidValue
3738);
3739
3740#[cfg(feature = "parser")]
3741#[derive(Debug, Clone, PartialEq, Eq)]
3742#[repr(C, u8)]
3743pub enum StyleInitialLetterWrapParseErrorOwned {
3744 InvalidValue(InvalidValueErrOwned),
3745}
3746
3747#[cfg(feature = "parser")]
3748impl StyleInitialLetterWrapParseError<'_> {
3749 #[must_use]
3750 pub fn to_contained(&self) -> StyleInitialLetterWrapParseErrorOwned {
3751 match self {
3752 Self::InvalidValue(e) => {
3753 StyleInitialLetterWrapParseErrorOwned::InvalidValue(e.to_contained())
3754 }
3755 }
3756 }
3757}
3758
3759#[cfg(feature = "parser")]
3760impl StyleInitialLetterWrapParseErrorOwned {
3761 #[must_use]
3762 pub fn to_shared(&self) -> StyleInitialLetterWrapParseError<'_> {
3763 match self {
3764 Self::InvalidValue(e) => StyleInitialLetterWrapParseError::InvalidValue(e.to_shared()),
3765 }
3766 }
3767}
3768
3769#[cfg(feature = "parser")]
3770pub fn parse_style_initial_letter_wrap(
3774 input: &str,
3775) -> Result<StyleInitialLetterWrap, StyleInitialLetterWrapParseError<'_>> {
3776 match input.trim() {
3777 "none" => Ok(StyleInitialLetterWrap::None),
3778 "first" => Ok(StyleInitialLetterWrap::First),
3779 "all" => Ok(StyleInitialLetterWrap::All),
3780 "grid" => Ok(StyleInitialLetterWrap::Grid),
3781 other => Err(StyleInitialLetterWrapParseError::InvalidValue(
3782 InvalidValueErr(other),
3783 )),
3784 }
3785}
3786
3787#[cfg(test)]
3788#[allow(clippy::float_cmp, clippy::too_many_lines, clippy::cast_precision_loss)]
3789mod autotest_generated {
3790 use super::*;
3791 use crate::props::basic::length::SizeMetric;
3792
3793 const OPAQUE_BLACK: ColorU = ColorU {
3794 r: 0,
3795 g: 0,
3796 b: 0,
3797 a: 255,
3798 };
3799 const OPAQUE_WHITE: ColorU = ColorU {
3800 r: 255,
3801 g: 255,
3802 b: 255,
3803 a: 255,
3804 };
3805 const TRANSPARENT_BLACK: ColorU = ColorU {
3806 r: 0,
3807 g: 0,
3808 b: 0,
3809 a: 0,
3810 };
3811
3812 const SCALE: isize = 1000;
3814
3815 #[test]
3820 fn text_indent_zero_is_the_neutral_element() {
3821 let z = StyleTextIndent::zero();
3822 assert_eq!(z, StyleTextIndent::default());
3823 assert_eq!(z.inner.metric, SizeMetric::Px);
3824 assert_eq!(z.inner.number.get(), 0.0);
3825 assert!(!z.each_line);
3826 assert!(!z.hanging);
3827 assert_eq!(z.print_as_css_value(), "0px");
3828 }
3829
3830 #[test]
3831 fn text_indent_const_ctors_pin_metric_and_value() {
3832 for v in [0_isize, 1, -1, 42, -42] {
3834 let cases = [
3835 (StyleTextIndent::const_px(v), SizeMetric::Px),
3836 (StyleTextIndent::const_em(v), SizeMetric::Em),
3837 (StyleTextIndent::const_pt(v), SizeMetric::Pt),
3838 (StyleTextIndent::const_percent(v), SizeMetric::Percent),
3839 (StyleTextIndent::const_in(v), SizeMetric::In),
3840 (StyleTextIndent::const_cm(v), SizeMetric::Cm),
3841 (StyleTextIndent::const_mm(v), SizeMetric::Mm),
3842 ];
3843 for (got, metric) in cases {
3844 assert_eq!(got.inner.metric, metric, "metric for {v}");
3845 assert_eq!(got.inner.number.get(), v as f32, "value for {v} {metric:?}");
3846 assert!(!got.each_line && !got.hanging);
3848 }
3849 }
3850 }
3851
3852 #[test]
3853 fn text_indent_const_ctors_hold_the_whole_encodable_isize_range() {
3854 assert_eq!(StyleTextIndent::const_px(1).inner.number.number(), SCALE);
3857
3858 let max = isize::MAX / SCALE;
3859 let min = isize::MIN / SCALE;
3860 assert_eq!(
3861 StyleTextIndent::const_px(max).inner.number.number(),
3862 max * SCALE
3863 );
3864 assert_eq!(
3865 StyleTextIndent::const_px(min).inner.number.number(),
3866 min * SCALE
3867 );
3868 assert_eq!(
3869 StyleTextIndent::const_from_metric(SizeMetric::Em, max)
3870 .inner
3871 .number
3872 .number(),
3873 max * SCALE
3874 );
3875 }
3879
3880 #[test]
3881 fn text_indent_float_ctors_saturate_on_nan_and_infinity() {
3882 assert_eq!(StyleTextIndent::px(f32::NAN).inner.number.get(), 0.0);
3884 assert_eq!(StyleTextIndent::em(f32::NAN).inner.number.get(), 0.0);
3885
3886 assert_eq!(
3887 StyleTextIndent::px(f32::INFINITY).inner.number.number(),
3888 isize::MAX
3889 );
3890 assert_eq!(
3891 StyleTextIndent::px(f32::NEG_INFINITY).inner.number.number(),
3892 isize::MIN
3893 );
3894 assert_eq!(
3895 StyleTextIndent::pt(f32::MAX).inner.number.number(),
3896 isize::MAX
3897 );
3898 assert_eq!(
3899 StyleTextIndent::pt(-f32::MAX).inner.number.number(),
3900 isize::MIN
3901 );
3902
3903 assert_eq!(
3905 StyleTextIndent::percent(f32::MIN_POSITIVE)
3906 .inner
3907 .number
3908 .number(),
3909 0
3910 );
3911 assert_eq!(StyleTextIndent::px(-0.0).inner.number.number(), 0);
3912
3913 for v in [
3915 f32::NAN,
3916 f32::INFINITY,
3917 f32::NEG_INFINITY,
3918 f32::MAX,
3919 -f32::MAX,
3920 ] {
3921 assert!(StyleTextIndent::px(v).inner.number.get().is_finite());
3922 }
3923 }
3924
3925 #[test]
3926 fn text_indent_from_metric_agrees_with_the_typed_ctors() {
3927 assert_eq!(
3928 StyleTextIndent::from_metric(SizeMetric::Px, 1.5),
3929 StyleTextIndent::px(1.5)
3930 );
3931 assert_eq!(
3932 StyleTextIndent::from_metric(SizeMetric::Em, -2.5),
3933 StyleTextIndent::em(-2.5)
3934 );
3935 assert_eq!(
3936 StyleTextIndent::from_metric(SizeMetric::Pt, 0.0),
3937 StyleTextIndent::pt(0.0)
3938 );
3939 assert_eq!(
3940 StyleTextIndent::from_metric(SizeMetric::Percent, 50.0),
3941 StyleTextIndent::percent(50.0)
3942 );
3943 assert_eq!(
3944 StyleTextIndent::const_from_metric(SizeMetric::Cm, 3),
3945 StyleTextIndent::const_cm(3)
3946 );
3947 assert_eq!(
3948 StyleTextIndent::const_from_metric(SizeMetric::Mm, -3),
3949 StyleTextIndent::const_mm(-3)
3950 );
3951
3952 let vw = StyleTextIndent::from_metric(SizeMetric::Vw, 10.0);
3954 assert_eq!(vw.inner.metric, SizeMetric::Vw);
3955 assert_eq!(vw.inner.number.get(), 10.0);
3956 }
3957
3958 #[test]
3959 fn text_indent_interpolate_endpoints_and_extrapolation() {
3960 let a = StyleTextIndent::px(0.0);
3961 let b = StyleTextIndent::px(100.0);
3962
3963 assert_eq!(a.interpolate(&b, 0.0).inner.number.get(), 0.0);
3964 assert_eq!(a.interpolate(&b, 1.0).inner.number.get(), 100.0);
3965 assert_eq!(a.interpolate(&b, 0.5).inner.number.get(), 50.0);
3966 assert_eq!(a.interpolate(&b, -1.0).inner.number.get(), -100.0);
3968 assert_eq!(a.interpolate(&b, 2.0).inner.number.get(), 200.0);
3969 assert_eq!(b.interpolate(&b, 0.25), b);
3971 }
3972
3973 #[test]
3974 fn text_indent_interpolate_with_nonfinite_t_is_defined() {
3975 let a = StyleTextIndent::px(0.0);
3976 let b = StyleTextIndent::px(100.0);
3977
3978 assert_eq!(a.interpolate(&b, f32::NAN).inner.number.get(), 0.0);
3980 assert_eq!(
3982 a.interpolate(&b, f32::INFINITY).inner.number.number(),
3983 isize::MAX
3984 );
3985 assert_eq!(
3986 a.interpolate(&b, f32::NEG_INFINITY).inner.number.number(),
3987 isize::MIN
3988 );
3989 assert_eq!(b.interpolate(&b, f32::INFINITY).inner.number.get(), 0.0);
3991
3992 for t in [f32::NAN, f32::INFINITY, f32::NEG_INFINITY, f32::MAX] {
3993 assert!(a.interpolate(&b, t).inner.number.get().is_finite());
3994 }
3995 }
3996
3997 #[test]
3998 fn text_indent_interpolate_keeps_self_flags_and_normalizes_mixed_metrics() {
3999 let a = StyleTextIndent {
4000 each_line: true,
4001 hanging: true,
4002 ..StyleTextIndent::px(0.0)
4003 };
4004 let b = StyleTextIndent {
4005 each_line: false,
4006 hanging: false,
4007 ..StyleTextIndent::px(10.0)
4008 };
4009
4010 let mid = a.interpolate(&b, 0.5);
4012 assert!(mid.each_line && mid.hanging);
4013 assert!(!b.interpolate(&a, 0.5).each_line);
4014
4015 let mixed = StyleTextIndent::px(10.0).interpolate(&StyleTextIndent::em(2.0), 0.5);
4017 assert_eq!(mixed.inner.metric, SizeMetric::Px);
4018 assert!(mixed.inner.number.get().is_finite());
4019 }
4020
4021 #[test]
4026 fn text_color_interpolate_endpoints_are_exact() {
4027 let a = StyleTextColor {
4028 inner: OPAQUE_BLACK,
4029 };
4030 let b = StyleTextColor {
4031 inner: OPAQUE_WHITE,
4032 };
4033
4034 assert_eq!(a.interpolate(&b, 0.0), a);
4035 assert_eq!(a.interpolate(&b, 1.0), b);
4036 assert_eq!(
4038 a.interpolate(&b, 0.5).inner,
4039 ColorU {
4040 r: 128,
4041 g: 128,
4042 b: 128,
4043 a: 255
4044 }
4045 );
4046 assert_eq!(a.print_as_css_value(), "#000000ff");
4047 }
4048
4049 #[test]
4050 fn text_color_interpolate_saturates_out_of_range_t() {
4051 let a = StyleTextColor {
4052 inner: OPAQUE_BLACK,
4053 };
4054 let b = StyleTextColor {
4055 inner: OPAQUE_WHITE,
4056 };
4057
4058 assert_eq!(a.interpolate(&b, 2.0).inner, OPAQUE_WHITE);
4060 assert_eq!(a.interpolate(&b, -1.0).inner, OPAQUE_BLACK);
4062 }
4063
4064 #[test]
4065 fn text_color_interpolate_with_nonfinite_t_is_defined() {
4066 let a = StyleTextColor {
4067 inner: OPAQUE_BLACK,
4068 };
4069 let b = StyleTextColor {
4070 inner: OPAQUE_WHITE,
4071 };
4072
4073 assert_eq!(a.interpolate(&b, f32::NAN).inner, TRANSPARENT_BLACK);
4076
4077 assert_eq!(
4081 a.interpolate(&b, f32::INFINITY).inner,
4082 ColorU {
4083 r: 255,
4084 g: 255,
4085 b: 255,
4086 a: 0
4087 }
4088 );
4089 assert_eq!(
4090 a.interpolate(&b, f32::NEG_INFINITY).inner,
4091 ColorU {
4092 r: 0,
4093 g: 0,
4094 b: 0,
4095 a: 0
4096 }
4097 );
4098 }
4099
4100 #[test]
4105 fn hanging_punctuation_is_enabled_matches_its_flags_exhaustively() {
4106 assert!(!StyleHangingPunctuation::default().is_enabled());
4107
4108 for bits in 0_u8..16 {
4109 let hp = StyleHangingPunctuation {
4110 first: bits & 1 != 0,
4111 force_end: bits & 2 != 0,
4112 allow_end: bits & 4 != 0,
4113 last: bits & 8 != 0,
4114 };
4115 assert_eq!(hp.is_enabled(), bits != 0, "bits={bits}");
4116 assert_eq!(
4118 hp.print_as_css_value() == "none",
4119 !hp.is_enabled(),
4120 "bits={bits}"
4121 );
4122 }
4123 }
4124
4125 #[test]
4126 fn hanging_punctuation_prints_flags_in_spec_order() {
4127 let all = StyleHangingPunctuation {
4128 first: true,
4129 force_end: true,
4130 allow_end: true,
4131 last: true,
4132 };
4133 assert_eq!(all.print_as_css_value(), "first force-end allow-end last");
4134 assert_eq!(
4135 StyleHangingPunctuation {
4136 last: true,
4137 ..Default::default()
4138 }
4139 .print_as_css_value(),
4140 "last"
4141 );
4142 }
4143
4144 #[cfg(feature = "parser")]
4149 mod parser {
4150 use super::super::*;
4151 use crate::props::basic::length::SizeMetric;
4152
4153 const GARBAGE: &[&str] = &[
4154 "",
4155 " ",
4156 "\t\n",
4157 "!!!",
4158 "\0\0",
4159 "0",
4160 "-0",
4161 "9223372036854775807",
4162 "1e400",
4163 "NaN",
4164 "inf",
4165 "\u{1F600}",
4166 "e\u{0301}",
4167 "\u{202E}left",
4168 "left;garbage",
4169 "left garbage",
4170 ];
4171
4172 macro_rules! assert_keyword_round_trip {
4174 ($parse:ident, $variants:expr) => {{
4175 for v in $variants {
4176 let printed = v.print_as_css_value();
4177 assert_eq!(
4178 $parse(&printed).as_ref(),
4179 Ok(&v),
4180 "round-trip of {printed:?}"
4181 );
4182 assert_eq!($parse(&format!(" {printed} ")).as_ref(), Ok(&v));
4184 }
4185 for g in GARBAGE.iter().copied() {
4186 assert!(
4187 $parse(g).is_err(),
4188 "{} accepted garbage {g:?}",
4189 stringify!($parse)
4190 );
4191 }
4192 }};
4193 }
4194
4195 #[test]
4196 fn keyword_parsers_round_trip_every_variant_and_reject_garbage() {
4197 type TA = StyleTextAlign;
4198 assert_keyword_round_trip!(
4199 parse_style_text_align,
4200 [
4201 TA::Left,
4202 TA::Center,
4203 TA::Right,
4204 TA::Justify,
4205 TA::Start,
4206 TA::End
4207 ]
4208 );
4209 type WS = StyleWhiteSpace;
4210 assert_keyword_round_trip!(
4211 parse_style_white_space,
4212 [
4213 WS::Normal,
4214 WS::Pre,
4215 WS::Nowrap,
4216 WS::PreWrap,
4217 WS::PreLine,
4218 WS::BreakSpaces
4219 ]
4220 );
4221 type H = StyleHyphens;
4222 assert_keyword_round_trip!(parse_style_hyphens, [H::None, H::Manual, H::Auto]);
4223 type LB = StyleLineBreak;
4224 assert_keyword_round_trip!(
4225 parse_style_line_break,
4226 [LB::Auto, LB::Loose, LB::Normal, LB::Strict, LB::Anywhere]
4227 );
4228 type WB = StyleWordBreak;
4229 assert_keyword_round_trip!(
4230 parse_style_word_break,
4231 [WB::Normal, WB::BreakAll, WB::KeepAll, WB::BreakWord]
4232 );
4233 type OW = StyleOverflowWrap;
4234 assert_keyword_round_trip!(
4235 parse_style_overflow_wrap,
4236 [OW::Normal, OW::Anywhere, OW::BreakWord]
4237 );
4238 type Tal = StyleTextAlignLast;
4239 assert_keyword_round_trip!(
4240 parse_style_text_align_last,
4241 [
4242 Tal::Auto,
4243 Tal::Start,
4244 Tal::End,
4245 Tal::Left,
4246 Tal::Right,
4247 Tal::Center,
4248 Tal::Justify
4249 ]
4250 );
4251 type TT = StyleTextTransform;
4252 assert_keyword_round_trip!(
4253 parse_style_text_transform,
4254 [
4255 TT::None,
4256 TT::Capitalize,
4257 TT::Uppercase,
4258 TT::Lowercase,
4259 TT::FullWidth
4260 ]
4261 );
4262 type D = StyleDirection;
4263 assert_keyword_round_trip!(parse_style_direction, [D::Ltr, D::Rtl]);
4264 type US = StyleUserSelect;
4265 assert_keyword_round_trip!(
4266 parse_style_user_select,
4267 [US::Auto, US::Text, US::None, US::All]
4268 );
4269 type TD = StyleTextDecoration;
4270 assert_keyword_round_trip!(
4271 parse_style_text_decoration,
4272 [TD::None, TD::Underline, TD::Overline, TD::LineThrough]
4273 );
4274 type UB = StyleUnicodeBidi;
4275 assert_keyword_round_trip!(
4276 parse_style_unicode_bidi,
4277 [
4278 UB::Normal,
4279 UB::Embed,
4280 UB::Isolate,
4281 UB::BidiOverride,
4282 UB::IsolateOverride,
4283 UB::Plaintext
4284 ]
4285 );
4286 type Tbt = StyleTextBoxTrim;
4287 assert_keyword_round_trip!(
4288 parse_style_text_box_trim,
4289 [Tbt::None, Tbt::TrimStart, Tbt::TrimEnd, Tbt::TrimBoth]
4290 );
4291 {
4292 use crate::props::style::text::{TextBoxEdgeOver as O, TextBoxEdgeUnder as U};
4295 let cases = [
4296 ("auto", StyleTextBoxEdge::AUTO),
4297 (
4298 "text",
4299 StyleTextBoxEdge {
4300 over: O::Text,
4301 under: U::Text,
4302 },
4303 ),
4304 (
4305 "cap",
4306 StyleTextBoxEdge {
4307 over: O::Cap,
4308 under: U::Text,
4309 },
4310 ),
4311 (
4312 "ex",
4313 StyleTextBoxEdge {
4314 over: O::Ex,
4315 under: U::Text,
4316 },
4317 ),
4318 (
4319 "ideographic",
4320 StyleTextBoxEdge {
4321 over: O::Ideographic,
4322 under: U::Ideographic,
4323 },
4324 ),
4325 (
4326 "ideographic-ink",
4327 StyleTextBoxEdge {
4328 over: O::IdeographicInk,
4329 under: U::IdeographicInk,
4330 },
4331 ),
4332 (
4333 "cap alphabetic",
4334 StyleTextBoxEdge {
4335 over: O::Cap,
4336 under: U::Alphabetic,
4337 },
4338 ),
4339 (
4340 "text ideographic",
4341 StyleTextBoxEdge {
4342 over: O::Text,
4343 under: U::Ideographic,
4344 },
4345 ),
4346 ];
4347 for (input, expected) in cases {
4348 let parsed = parse_style_text_box_edge(input)
4349 .unwrap_or_else(|e| panic!("`{input}` must parse: {e:?}"));
4350 assert_eq!(parsed, expected, "parse of `{input}`");
4351 let printed = parsed.print_as_css_value();
4352 let reparsed = parse_style_text_box_edge(&printed)
4353 .unwrap_or_else(|e| panic!("reprint `{printed}` must parse: {e:?}"));
4354 assert_eq!(reparsed, parsed, "print/parse round trip via `{printed}`");
4355 }
4356 assert!(parse_style_text_box_edge("auto text").is_err());
4358 assert!(
4359 parse_style_text_box_edge("cap cap").is_err(),
4360 "cap is over-only"
4361 );
4362 assert!(
4363 parse_style_text_box_edge("alphabetic").is_err(),
4364 "alphabetic is under-only"
4365 );
4366 assert!(parse_style_text_box_edge("bogus").is_err());
4367 }
4368 type DB = StyleDominantBaseline;
4369 assert_keyword_round_trip!(
4370 parse_style_dominant_baseline,
4371 [
4372 DB::Auto,
4373 DB::TextBottom,
4374 DB::Alphabetic,
4375 DB::Ideographic,
4376 DB::Middle,
4377 DB::Central,
4378 DB::Mathematical,
4379 DB::Hanging,
4380 DB::TextTop
4381 ]
4382 );
4383 type AB = StyleAlignmentBaseline;
4384 assert_keyword_round_trip!(
4385 parse_style_alignment_baseline,
4386 [
4387 AB::Baseline,
4388 AB::TextBottom,
4389 AB::Alphabetic,
4390 AB::Ideographic,
4391 AB::Middle,
4392 AB::Central,
4393 AB::Mathematical,
4394 AB::TextTop
4395 ]
4396 );
4397 type Bs = StyleBaselineSource;
4398 assert_keyword_round_trip!(
4399 parse_style_baseline_source,
4400 [Bs::Auto, Bs::First, Bs::Last]
4401 );
4402 type Lfe = StyleLineFitEdge;
4403 assert_keyword_round_trip!(
4404 parse_style_line_fit_edge,
4405 [
4406 Lfe::Leading,
4407 Lfe::Text,
4408 Lfe::Cap,
4409 Lfe::Ex,
4410 Lfe::Ideographic,
4411 Lfe::IdeographicInk,
4412 Lfe::Alphabetic
4413 ]
4414 );
4415 type Ila = StyleInitialLetterAlign;
4416 assert_keyword_round_trip!(
4417 parse_style_initial_letter_align,
4418 [Ila::Auto, Ila::Alphabetic, Ila::Hanging, Ila::Ideographic]
4419 );
4420 type Ilw = StyleInitialLetterWrap;
4421 assert_keyword_round_trip!(
4422 parse_style_initial_letter_wrap,
4423 [Ilw::None, Ilw::First, Ilw::All, Ilw::Grid]
4424 );
4425 }
4426
4427 #[test]
4428 fn keyword_parsers_are_case_sensitive() {
4429 assert!(parse_style_text_align("LEFT").is_err());
4434 assert!(parse_style_text_align("Left").is_err());
4435 assert!(parse_style_white_space("Normal").is_err());
4436 assert!(parse_style_direction("LTR").is_err());
4437 assert!(parse_style_hanging_punctuation("FIRST").is_ok());
4439 assert!(parse_style_text_combine_upright("NONE").is_ok());
4440 }
4441
4442 #[test]
4443 fn extremely_long_and_deeply_nested_input_terminates_with_err() {
4444 let long = "a".repeat(1_000_000);
4445 assert!(parse_style_text_align(&long).is_err());
4446 assert!(parse_style_white_space(&long).is_err());
4447 assert!(parse_style_text_color(&long).is_err());
4448 assert!(parse_style_letter_spacing(&long).is_err());
4449 assert!(parse_style_word_spacing(&long).is_err());
4450 assert!(parse_style_tab_size(&long).is_err());
4451 assert!(parse_style_line_height(&long).is_err());
4452 assert!(parse_style_text_indent(&long).is_err());
4453 assert!(parse_style_hanging_punctuation(&long).is_err());
4454 assert!(parse_style_initial_letter(&long).is_err());
4455 assert!(parse_style_line_clamp(&long).is_err());
4456 assert!(parse_style_vertical_align(&long).is_err());
4457
4458 let huge_number = "9".repeat(1000);
4460 assert!(parse_style_line_clamp(&huge_number).is_err());
4461 assert!(parse_style_initial_letter(&huge_number).is_err());
4462
4463 let nested = format!("{}{}", "(".repeat(10_000), ")".repeat(10_000));
4465 assert!(parse_style_text_align(&nested).is_err());
4466 assert!(parse_style_letter_spacing(&nested).is_err());
4467 assert!(parse_style_text_indent(&nested).is_err());
4468 assert!(parse_style_line_height(&nested).is_err());
4469 }
4470
4471 #[test]
4474 fn spacing_and_tab_size_round_trip_through_their_printed_form() {
4475 for pv in [
4476 PixelValue::px(2.0),
4477 PixelValue::px(-3.0),
4478 PixelValue::em(0.5),
4479 PixelValue::pt(12.0),
4480 PixelValue::percent(10.0),
4481 PixelValue::zero(),
4482 ] {
4483 let ls = StyleLetterSpacing { inner: pv };
4484 assert_eq!(
4485 parse_style_letter_spacing(&ls.print_as_css_value()).unwrap(),
4486 ls
4487 );
4488 let ws = StyleWordSpacing { inner: pv };
4489 assert_eq!(
4490 parse_style_word_spacing(&ws.print_as_css_value()).unwrap(),
4491 ws
4492 );
4493 }
4494 let ts = StyleTabSize::default();
4496 assert_eq!(ts.inner, PixelValue::em(8.0));
4497 assert_eq!(parse_style_tab_size(&ts.print_as_css_value()).unwrap(), ts);
4498 assert_eq!(
4499 parse_style_tab_size("4").unwrap().inner,
4500 PixelValue::em(4.0)
4501 );
4502 assert_eq!(
4503 parse_style_tab_size("20px").unwrap().inner,
4504 PixelValue::px(20.0)
4505 );
4506 }
4507
4508 #[test]
4509 fn pixel_parsers_reject_empty_and_unit_only_input() {
4510 for bad in [
4511 "",
4512 " ",
4513 "\t\n",
4514 "px",
4515 "em",
4516 "abc",
4517 "px px",
4518 "10pxx",
4519 "\u{1F600}",
4520 ] {
4521 assert!(
4522 parse_style_letter_spacing(bad).is_err(),
4523 "letter-spacing {bad:?}"
4524 );
4525 assert!(
4526 parse_style_word_spacing(bad).is_err(),
4527 "word-spacing {bad:?}"
4528 );
4529 }
4530 assert_eq!(
4533 parse_style_letter_spacing("10 px").unwrap().inner,
4534 PixelValue::px(10.0)
4535 );
4536 }
4537
4538 #[test]
4539 fn pixel_parsers_accept_float_keywords_and_saturate_instead_of_panicking() {
4540 assert_eq!(
4544 parse_style_letter_spacing("NaN")
4545 .unwrap()
4546 .inner
4547 .number
4548 .number(),
4549 0
4550 );
4551 assert_eq!(
4552 parse_style_letter_spacing("inf")
4553 .unwrap()
4554 .inner
4555 .number
4556 .number(),
4557 isize::MAX
4558 );
4559 assert_eq!(
4560 parse_style_letter_spacing("-inf")
4561 .unwrap()
4562 .inner
4563 .number
4564 .number(),
4565 isize::MIN
4566 );
4567 assert_eq!(
4568 parse_style_word_spacing("1e400px")
4569 .unwrap()
4570 .inner
4571 .number
4572 .number(),
4573 isize::MAX
4574 );
4575 assert_eq!(
4577 parse_style_tab_size("NaN").unwrap().inner,
4578 PixelValue::em(0.0)
4579 );
4580 assert!(parse_style_tab_size("inf")
4581 .unwrap()
4582 .inner
4583 .number
4584 .get()
4585 .is_finite());
4586
4587 assert_eq!(
4589 parse_style_letter_spacing("0").unwrap().inner,
4590 PixelValue::px(0.0)
4591 );
4592 assert_eq!(
4593 parse_style_letter_spacing("-0")
4594 .unwrap()
4595 .inner
4596 .number
4597 .number(),
4598 0
4599 );
4600 assert!(parse_style_letter_spacing("9223372036854775807px")
4601 .unwrap()
4602 .inner
4603 .number
4604 .get()
4605 .is_finite());
4606 }
4607
4608 #[test]
4611 fn text_indent_round_trips_length_and_keywords() {
4612 for pv in [
4613 PixelValue::px(10.0),
4614 PixelValue::em(-2.0),
4615 PixelValue::percent(50.0),
4616 ] {
4617 for (each_line, hanging) in
4618 [(false, false), (true, false), (false, true), (true, true)]
4619 {
4620 let ti = StyleTextIndent {
4621 inner: pv,
4622 each_line,
4623 hanging,
4624 };
4625 let printed = ti.print_as_css_value();
4626 assert_eq!(
4627 parse_style_text_indent(&printed).unwrap(),
4628 ti,
4629 "{printed:?}"
4630 );
4631 }
4632 }
4633 assert!(parse_style_text_indent("10px hanging").unwrap().hanging);
4634 assert!(parse_style_text_indent("each-line 10px").unwrap().each_line);
4635 }
4636
4637 #[test]
4638 fn text_indent_defaults_missing_length_to_zero_and_keeps_the_last_one() {
4639 assert_eq!(
4643 parse_style_text_indent("").unwrap(),
4644 StyleTextIndent::zero()
4645 );
4646 assert_eq!(
4647 parse_style_text_indent(" ").unwrap(),
4648 StyleTextIndent::zero()
4649 );
4650 assert_eq!(
4651 parse_style_text_indent("hanging").unwrap(),
4652 StyleTextIndent {
4653 hanging: true,
4654 ..StyleTextIndent::zero()
4655 }
4656 );
4657 assert_eq!(
4659 parse_style_text_indent("10px 20px").unwrap().inner,
4660 PixelValue::px(20.0)
4661 );
4662
4663 assert!(parse_style_text_indent("garbage").is_err());
4665 assert!(parse_style_text_indent("hanging garbage").is_err());
4666 }
4667
4668 #[test]
4671 fn initial_letter_parses_size_and_optional_sink() {
4672 assert_eq!(
4673 parse_style_initial_letter("3").unwrap(),
4674 StyleInitialLetter {
4675 size: 3,
4676 sink: crate::corety::OptionU32::None
4677 }
4678 );
4679 let with_sink = StyleInitialLetter {
4680 size: 3,
4681 sink: crate::corety::OptionU32::Some(2),
4682 };
4683 assert_eq!(parse_style_initial_letter("3 2").unwrap(), with_sink);
4684 assert_eq!(
4686 parse_style_initial_letter(&with_sink.print_as_css_value()).unwrap(),
4687 with_sink
4688 );
4689 }
4690
4691 #[test]
4692 fn initial_letter_rejects_zero_negative_and_overflowing_sizes() {
4693 assert!(
4694 parse_style_initial_letter("0").is_err(),
4695 "size 0 must be rejected"
4696 );
4697 assert!(parse_style_initial_letter("-1").is_err());
4698 assert!(parse_style_initial_letter("1.5").is_err());
4699 assert!(
4700 parse_style_initial_letter("4294967296").is_err(),
4701 "u32::MAX + 1"
4702 );
4703 assert!(parse_style_initial_letter("3 -1").is_err(), "negative sink");
4704 assert!(parse_style_initial_letter("3 x").is_err());
4705 assert!(parse_style_initial_letter("").is_err());
4706 assert!(parse_style_initial_letter(" ").is_err());
4707 assert_eq!(
4709 parse_style_initial_letter("4294967295").unwrap().size,
4710 u32::MAX
4711 );
4712 assert_eq!(
4714 parse_style_initial_letter("3 2 9").unwrap(),
4715 StyleInitialLetter {
4716 size: 3,
4717 sink: crate::corety::OptionU32::Some(2),
4718 }
4719 );
4720 }
4721
4722 #[test]
4725 fn line_clamp_rejects_zero_and_out_of_range_values() {
4726 assert_eq!(
4727 parse_style_line_clamp("3").unwrap(),
4728 StyleLineClamp { max_lines: 3 }
4729 );
4730 assert_eq!(parse_style_line_clamp(" 7 ").unwrap().max_lines, 7);
4731 assert_eq!(
4732 parse_style_line_clamp("0").unwrap_err(),
4733 StyleLineClampParseError::ZeroValue
4734 );
4735 assert!(parse_style_line_clamp("-1").is_err());
4736 assert!(parse_style_line_clamp("1.0").is_err());
4737 assert!(parse_style_line_clamp("").is_err());
4738 assert!(parse_style_line_clamp(" ").is_err());
4739 assert!(parse_style_line_clamp("\u{1F600}").is_err());
4740 assert!(parse_style_line_clamp("99999999999999999999999").is_err());
4742 let max = usize::MAX.to_string();
4743 assert_eq!(parse_style_line_clamp(&max).unwrap().max_lines, usize::MAX);
4744 let lc = StyleLineClamp { max_lines: 42 };
4746 assert_eq!(
4747 parse_style_line_clamp(&lc.print_as_css_value()).unwrap(),
4748 lc
4749 );
4750 }
4751
4752 #[test]
4755 fn hanging_punctuation_round_trips_and_enforces_mutual_exclusion() {
4756 for bits in 0_u8..16 {
4757 let hp = StyleHangingPunctuation {
4758 first: bits & 1 != 0,
4759 force_end: bits & 2 != 0,
4760 allow_end: bits & 4 != 0,
4761 last: bits & 8 != 0,
4762 };
4763 let printed = hp.print_as_css_value();
4764 if hp.force_end && hp.allow_end {
4765 assert!(
4767 parse_style_hanging_punctuation(&printed).is_err(),
4768 "{printed:?}"
4769 );
4770 } else {
4771 assert_eq!(
4772 parse_style_hanging_punctuation(&printed).unwrap(),
4773 hp,
4774 "{printed:?}"
4775 );
4776 }
4777 }
4778 assert!(parse_style_hanging_punctuation("first bogus").is_err());
4779 assert!(parse_style_hanging_punctuation("\u{1F600}").is_err());
4780 assert!(parse_style_hanging_punctuation("none first").is_err());
4781 }
4782
4783 #[test]
4784 fn hanging_punctuation_accepts_empty_input_as_none() {
4785 assert_eq!(
4788 parse_style_hanging_punctuation("").unwrap(),
4789 StyleHangingPunctuation::default()
4790 );
4791 assert_eq!(
4792 parse_style_hanging_punctuation(" ").unwrap(),
4793 StyleHangingPunctuation::default()
4794 );
4795 assert!(
4797 parse_style_hanging_punctuation("first first")
4798 .unwrap()
4799 .first
4800 );
4801 }
4802
4803 #[test]
4806 fn text_combine_upright_bounds_the_digits_operand() {
4807 assert_eq!(
4808 parse_style_text_combine_upright("none").unwrap(),
4809 StyleTextCombineUpright::None
4810 );
4811 assert_eq!(
4812 parse_style_text_combine_upright("all").unwrap(),
4813 StyleTextCombineUpright::All
4814 );
4815 for n in 2_u8..=4 {
4816 let v = StyleTextCombineUpright::Digits(n);
4817 assert_eq!(
4818 parse_style_text_combine_upright(&v.print_as_css_value()).unwrap(),
4819 v
4820 );
4821 }
4822 for bad in [
4824 "digits 0",
4825 "digits 1",
4826 "digits 5",
4827 "digits 255",
4828 "digits 256",
4829 "digits -1",
4830 ] {
4831 assert!(
4832 parse_style_text_combine_upright(bad).is_err(),
4833 "{bad:?} accepted"
4834 );
4835 }
4836 assert!(parse_style_text_combine_upright("").is_err());
4837 assert!(parse_style_text_combine_upright("bogus").is_err());
4838 }
4839
4840 #[test]
4841 fn text_combine_upright_accepts_garbage_after_the_digits_prefix() {
4842 assert_eq!(
4846 parse_style_text_combine_upright("digits").unwrap(),
4847 StyleTextCombineUpright::Digits(2)
4848 );
4849 assert_eq!(
4850 parse_style_text_combine_upright("digitsgarbage").unwrap(),
4851 StyleTextCombineUpright::Digits(2)
4852 );
4853 assert_eq!(
4854 parse_style_text_combine_upright("digits 2 3").unwrap(),
4855 StyleTextCombineUpright::Digits(2)
4856 );
4857 }
4858
4859 #[test]
4862 fn line_height_parses_numbers_percentages_and_px() {
4863 assert_eq!(
4864 parse_style_line_height("1.5").unwrap().inner,
4865 PercentageValue::new(150.0)
4866 );
4867 assert_eq!(
4868 parse_style_line_height("120%").unwrap().inner,
4869 PercentageValue::new(120.0)
4870 );
4871 assert_eq!(
4873 parse_style_line_height("20px").unwrap().inner,
4874 PercentageValue::new(-2000.0)
4875 );
4876 assert!(parse_style_line_height("").is_err());
4877 assert!(parse_style_line_height(" ").is_err());
4878 assert!(parse_style_line_height("abc").is_err());
4879 assert!(parse_style_line_height("\u{1F600}").is_err());
4880 let lh = StyleLineHeight::default();
4882 assert_eq!(
4883 parse_style_line_height(&lh.print_as_css_value()).unwrap(),
4884 lh
4885 );
4886 }
4887
4888 #[test]
4889 fn line_height_negative_numbers_alias_absolute_px_lengths() {
4890 assert_eq!(
4895 parse_style_line_height("-1").unwrap(),
4896 parse_style_line_height("1px").unwrap()
4897 );
4898 assert_eq!(
4899 parse_style_line_height("-100%").unwrap(),
4900 parse_style_line_height("1px").unwrap()
4901 );
4902 }
4903
4904 #[test]
4905 fn line_height_rejects_em_and_other_length_units() {
4906 assert!(parse_style_line_height("1.5em").is_err());
4909 assert!(parse_style_line_height("12pt").is_err());
4910 assert!(parse_style_line_height("2rem").is_err());
4911 }
4912
4913 #[test]
4914 fn line_height_rejects_non_ascii_numerals_without_panicking() {
4915 assert!(parse_style_line_height("\u{FF15}").is_err()); assert!(parse_style_line_height("\u{0665}").is_err()); assert!(parse_style_line_height("1\u{00B2}").is_err()); }
4923
4924 #[test]
4927 fn vertical_align_round_trips_keywords_percentages_and_lengths() {
4928 type VA = StyleVerticalAlign;
4929 for v in [
4930 VA::Baseline,
4931 VA::Top,
4932 VA::Middle,
4933 VA::Bottom,
4934 VA::Sub,
4935 VA::Superscript,
4936 VA::TextTop,
4937 VA::TextBottom,
4938 VA::Percentage(PercentageValue::new(50.0)),
4939 VA::Percentage(PercentageValue::new(-25.0)),
4940 VA::Length(PixelValue::px(12.0)),
4941 VA::Length(PixelValue::em(1.5)),
4942 ] {
4943 let printed = v.print_as_css_value();
4944 assert_eq!(
4945 parse_style_vertical_align(&printed).unwrap(),
4946 v,
4947 "{printed:?}"
4948 );
4949 }
4950 assert!(parse_style_vertical_align("").is_err());
4951 assert!(parse_style_vertical_align("%").is_err());
4952 assert!(parse_style_vertical_align("bogus%").is_err());
4953 assert!(parse_style_vertical_align("\u{1F600}").is_err());
4954 }
4955
4956 #[test]
4959 fn caret_parsers_reject_garbage_and_accept_minimal_input() {
4960 assert_eq!(
4961 parse_caret_color("red").unwrap().inner,
4962 parse_style_text_color("red").unwrap().inner
4963 );
4964 assert!(parse_caret_color("").is_err());
4965 assert!(parse_caret_color("not-a-color").is_err());
4966 assert_eq!(parse_caret_width("2px").unwrap().inner, PixelValue::px(2.0));
4967 assert!(parse_caret_width("").is_err());
4968 assert!(parse_caret_animation_duration("bogus").is_err());
4969 assert!(parse_caret_animation_duration("500ms").is_ok());
4970 }
4971
4972 macro_rules! assert_error_round_trip {
4976 ($parse:ident, $($bad:expr),+ $(,)?) => {{
4977 $(
4978 let e = $parse($bad).expect_err(concat!(stringify!($parse), " accepted ", $bad));
4979 assert_eq!(e.to_contained().to_shared(), e, "{:?} via {}", $bad, stringify!($parse));
4980 )+
4981 }};
4982 }
4983
4984 #[test]
4985 fn invalid_value_errors_round_trip_through_their_owned_form() {
4986 assert_error_round_trip!(parse_style_text_align, "", "middle", "\u{1F600}");
4987 assert_error_round_trip!(parse_style_white_space, "", "wrap");
4988 assert_error_round_trip!(parse_style_hyphens, "", "always");
4989 assert_error_round_trip!(parse_style_line_break, "", "tight");
4990 assert_error_round_trip!(parse_style_word_break, "", "break");
4991 assert_error_round_trip!(parse_style_overflow_wrap, "", "wrap");
4992 assert_error_round_trip!(parse_style_text_align_last, "", "middle");
4993 assert_error_round_trip!(parse_style_text_transform, "", "smallcaps");
4994 assert_error_round_trip!(parse_style_direction, "", "sideways");
4995 assert_error_round_trip!(parse_style_user_select, "", "some");
4996 assert_error_round_trip!(parse_style_text_decoration, "", "blink");
4997 assert_error_round_trip!(parse_style_vertical_align, "", "bogus");
4998 assert_error_round_trip!(parse_style_unicode_bidi, "", "override");
4999 assert_error_round_trip!(parse_style_text_box_trim, "", "trim");
5000 assert_error_round_trip!(parse_style_text_box_edge, "", "edge");
5001 assert_error_round_trip!(parse_style_dominant_baseline, "", "bogus");
5002 assert_error_round_trip!(parse_style_alignment_baseline, "", "bogus");
5003 assert_error_round_trip!(parse_style_baseline_source, "", "bogus");
5004 assert_error_round_trip!(parse_style_line_fit_edge, "", "bogus");
5005 assert_error_round_trip!(parse_style_initial_letter_align, "", "bogus");
5006 assert_error_round_trip!(parse_style_initial_letter_wrap, "", "bogus");
5007 }
5008
5009 #[test]
5010 fn pixel_and_numeric_errors_round_trip_through_their_owned_form() {
5011 assert_error_round_trip!(parse_style_letter_spacing, "", "abcpx", "px", "zz");
5013 assert_error_round_trip!(parse_style_word_spacing, "", "abcem", "em", "zz");
5014 assert_error_round_trip!(parse_style_text_indent, "abcpx", "zz");
5015 assert_error_round_trip!(parse_style_tab_size, "", "abcpx", "zz");
5016 assert_error_round_trip!(parse_style_line_height, "", "abc", "1.5em");
5017 assert_error_round_trip!(parse_style_initial_letter, "", "x", "0", "3 x");
5018 assert_error_round_trip!(parse_style_line_clamp, "", "x", "0");
5019 assert_error_round_trip!(
5020 parse_style_hanging_punctuation,
5021 "bogus",
5022 "force-end allow-end"
5023 );
5024 assert_error_round_trip!(parse_style_text_combine_upright, "bogus", "digits 9");
5025 }
5026
5027 #[test]
5028 fn text_color_error_round_trips_and_preserves_its_message() {
5029 let e = parse_style_text_color("not-a-color").unwrap_err();
5030 let owned = e.to_contained();
5031 let round_tripped = owned.to_shared();
5032 assert_eq!(format!("{e}"), format!("{round_tripped}"));
5033 assert!(parse_style_text_color("").is_err());
5034 assert!(parse_style_text_color("#gggggg").is_err());
5035 assert!(parse_style_text_color("\u{1F600}").is_err());
5036 assert_eq!(
5038 parse_style_text_color("#aabbcc").unwrap().inner.to_hash(),
5039 "#aabbccff"
5040 );
5041 }
5042
5043 #[test]
5044 fn error_types_survive_a_default_ish_extreme_instance() {
5045 let long = "z".repeat(100_000);
5047 let e = parse_style_text_align(&long).unwrap_err();
5048 assert_eq!(e.to_contained().to_shared(), e);
5049 let e = parse_style_line_clamp(&long).unwrap_err();
5050 assert_eq!(e.to_contained().to_shared(), e);
5051 let e = parse_style_hanging_punctuation(&long).unwrap_err();
5052 assert_eq!(e.to_contained().to_shared(), e);
5053 let e = parse_style_letter_spacing("").unwrap_err();
5055 assert_eq!(e.to_contained().to_shared(), e);
5056 }
5057
5058 #[test]
5061 fn letter_spacing_accepts_every_size_metric_it_prints() {
5062 for (unit, metric) in [
5063 ("px", SizeMetric::Px),
5064 ("pt", SizeMetric::Pt),
5065 ("em", SizeMetric::Em),
5066 ("rem", SizeMetric::Rem),
5067 ("in", SizeMetric::In),
5068 ("cm", SizeMetric::Cm),
5069 ("mm", SizeMetric::Mm),
5070 ("%", SizeMetric::Percent),
5071 ("vw", SizeMetric::Vw),
5072 ("vh", SizeMetric::Vh),
5073 ("vmax", SizeMetric::Vmax),
5074 ("vmin", SizeMetric::Vmin),
5080 ] {
5081 let parsed = parse_style_letter_spacing(&format!("1{unit}")).unwrap();
5082 assert_eq!(parsed.inner.metric, metric, "unit {unit}");
5083 assert_eq!(parsed.inner.number.get(), 1.0);
5084 }
5085 }
5086 }
5087}