1use alloc::string::{String, ToString};
8use core::fmt;
9use crate::corety::AzString;
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] pub fn interpolate(&self, other: &Self, t: f32) -> Self {
44 Self {
45 inner: self.inner.interpolate(&other.inner, t),
46 }
47 }
48}
49
50impl PrintAsCssValue for StyleTextColor {
51 fn print_as_css_value(&self) -> String {
52 self.inner.to_hash()
53 }
54}
55
56#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
60#[repr(C)]
61pub enum StyleTextAlign {
62 Left,
63 Center,
64 Right,
65 Justify,
66 #[default]
67 Start,
68 End,
69}
70
71impl_option!(
72 StyleTextAlign,
73 OptionStyleTextAlign,
74 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
75);
76
77impl PrintAsCssValue for StyleTextAlign {
78 fn print_as_css_value(&self) -> String {
79 String::from(match self {
80 Self::Left => "left",
81 Self::Center => "center",
82 Self::Right => "right",
83 Self::Justify => "justify",
84 Self::Start => "start",
85 Self::End => "end",
86 })
87 }
88}
89
90#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
94#[repr(C)]
95pub struct StyleLetterSpacing {
96 pub inner: PixelValue,
97}
98
99impl fmt::Debug for StyleLetterSpacing {
100 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101 write!(f, "{}", self.inner)
102 }
103}
104impl Default for StyleLetterSpacing {
105 fn default() -> Self {
106 Self {
107 inner: PixelValue::const_px(0),
108 }
109 }
110}
111impl_pixel_value!(StyleLetterSpacing);
112impl PixelValueTaker for StyleLetterSpacing {
113 fn from_pixel_value(inner: PixelValue) -> Self {
114 Self { inner }
115 }
116}
117impl PrintAsCssValue for StyleLetterSpacing {
118 fn print_as_css_value(&self) -> String {
119 format!("{}", self.inner)
120 }
121}
122
123#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
127#[repr(C)]
128pub struct StyleWordSpacing {
129 pub inner: PixelValue,
130}
131
132impl fmt::Debug for StyleWordSpacing {
133 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
134 write!(f, "{}", self.inner)
135 }
136}
137impl Default for StyleWordSpacing {
138 fn default() -> Self {
139 Self {
140 inner: PixelValue::const_px(0),
141 }
142 }
143}
144impl_pixel_value!(StyleWordSpacing);
145impl PixelValueTaker for StyleWordSpacing {
146 fn from_pixel_value(inner: PixelValue) -> Self {
147 Self { inner }
148 }
149}
150impl PrintAsCssValue for StyleWordSpacing {
151 fn print_as_css_value(&self) -> String {
152 format!("{}", self.inner)
153 }
154}
155
156#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
160#[repr(C)]
161pub struct StyleLineHeight {
162 pub inner: PercentageValue,
163}
164impl Default for StyleLineHeight {
165 fn default() -> Self {
166 Self {
167 inner: PercentageValue::const_new(120),
168 }
169 }
170}
171impl_percentage_value!(StyleLineHeight);
172impl PrintAsCssValue for StyleLineHeight {
173 fn print_as_css_value(&self) -> String {
174 format!("{}", self.inner)
175 }
176}
177
178#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
182#[repr(C)]
183pub struct StyleTabSize {
184 pub inner: PixelValue, }
186
187impl fmt::Debug for StyleTabSize {
188 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
189 write!(f, "{}", self.inner)
190 }
191}
192impl Default for StyleTabSize {
193 fn default() -> Self {
194 Self {
195 inner: PixelValue::em(8.0),
196 }
197 }
198}
199impl_pixel_value!(StyleTabSize);
200impl PixelValueTaker for StyleTabSize {
201 fn from_pixel_value(inner: PixelValue) -> Self {
202 Self { inner }
203 }
204}
205impl PrintAsCssValue for StyleTabSize {
206 fn print_as_css_value(&self) -> String {
207 format!("{}", self.inner)
208 }
209}
210
211#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
217#[repr(C)]
218#[derive(Default)]
219pub enum StyleWhiteSpace {
220 #[default]
222 Normal,
223 Pre,
225 Nowrap,
227 PreWrap,
229 PreLine,
231 BreakSpaces,
233}
234impl_option!(
235 StyleWhiteSpace,
236 OptionStyleWhiteSpace,
237 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
238);
239impl PrintAsCssValue for StyleWhiteSpace {
240 fn print_as_css_value(&self) -> String {
241 String::from(match self {
242 Self::Normal => "normal",
243 Self::Pre => "pre",
244 Self::Nowrap => "nowrap",
245 Self::PreWrap => "pre-wrap",
246 Self::PreLine => "pre-line",
247 Self::BreakSpaces => "break-spaces",
248 })
249 }
250}
251
252#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
256#[repr(C)]
257#[derive(Default)]
258pub enum StyleHyphens {
259 None,
261 #[default]
264 Manual,
265 Auto,
269}
270impl_option!(
271 StyleHyphens,
272 OptionStyleHyphens,
273 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
274);
275impl PrintAsCssValue for StyleHyphens {
276 fn print_as_css_value(&self) -> String {
277 String::from(match self {
278 Self::None => "none",
279 Self::Manual => "manual",
280 Self::Auto => "auto",
281 })
282 }
283}
284
285#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
291#[repr(C)]
292#[derive(Default)]
293pub enum StyleLineBreak {
294 #[default]
296 Auto,
297 Loose,
299 Normal,
301 Strict,
303 Anywhere,
307}
308impl_option!(
309 StyleLineBreak,
310 OptionStyleLineBreak,
311 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
312);
313impl PrintAsCssValue for StyleLineBreak {
314 fn print_as_css_value(&self) -> String {
315 String::from(match self {
316 Self::Auto => "auto",
317 Self::Loose => "loose",
318 Self::Normal => "normal",
319 Self::Strict => "strict",
320 Self::Anywhere => "anywhere",
321 })
322 }
323}
324
325#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
331#[repr(C)]
332#[derive(Default)]
333pub enum StyleWordBreak {
334 #[default]
336 Normal,
337 BreakAll,
339 KeepAll,
341 BreakWord,
344}
345impl_option!(
346 StyleWordBreak,
347 OptionStyleWordBreak,
348 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
349);
350impl PrintAsCssValue for StyleWordBreak {
351 fn print_as_css_value(&self) -> String {
352 String::from(match self {
353 Self::Normal => "normal",
354 Self::BreakAll => "break-all",
355 Self::KeepAll => "keep-all",
356 Self::BreakWord => "break-word",
357 })
358 }
359}
360
361#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
368#[repr(C)]
369#[derive(Default)]
370pub enum StyleOverflowWrap {
371 #[default]
373 Normal,
374 Anywhere,
377 BreakWord,
380}
381impl_option!(
382 StyleOverflowWrap,
383 OptionStyleOverflowWrap,
384 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
385);
386impl PrintAsCssValue for StyleOverflowWrap {
387 fn print_as_css_value(&self) -> String {
388 String::from(match self {
389 Self::Normal => "normal",
390 Self::Anywhere => "anywhere",
391 Self::BreakWord => "break-word",
392 })
393 }
394}
395
396#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
403#[repr(C)]
404#[derive(Default)]
405pub enum StyleTextAlignLast {
406 #[default]
408 Auto,
409 Start,
411 End,
413 Left,
415 Right,
417 Center,
419 Justify,
421}
422impl_option!(
423 StyleTextAlignLast,
424 OptionStyleTextAlignLast,
425 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
426);
427impl PrintAsCssValue for StyleTextAlignLast {
428 fn print_as_css_value(&self) -> String {
429 String::from(match self {
430 Self::Auto => "auto",
431 Self::Start => "start",
432 Self::End => "end",
433 Self::Left => "left",
434 Self::Right => "right",
435 Self::Center => "center",
436 Self::Justify => "justify",
437 })
438 }
439}
440
441#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
447#[repr(C)]
448#[derive(Default)]
449pub enum StyleTextTransform {
450 #[default]
452 None,
453 Capitalize,
455 Uppercase,
457 Lowercase,
459 FullWidth,
461}
462impl_option!(
463 StyleTextTransform,
464 OptionStyleTextTransform,
465 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
466);
467impl PrintAsCssValue for StyleTextTransform {
468 fn print_as_css_value(&self) -> String {
469 String::from(match self {
470 Self::None => "none",
471 Self::Capitalize => "capitalize",
472 Self::Uppercase => "uppercase",
473 Self::Lowercase => "lowercase",
474 Self::FullWidth => "full-width",
475 })
476 }
477}
478
479#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
484#[repr(C)]
485#[derive(Default)]
486pub enum StyleDirection {
487 #[default]
489 Ltr,
490 Rtl,
492}
493impl_option!(
494 StyleDirection,
495 OptionStyleDirection,
496 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
497);
498impl PrintAsCssValue for StyleDirection {
499 fn print_as_css_value(&self) -> String {
500 String::from(match self {
501 Self::Ltr => "ltr",
502 Self::Rtl => "rtl",
503 })
504 }
505}
506
507#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
512#[repr(C)]
513#[derive(Default)]
514pub enum StyleUserSelect {
515 #[default]
517 Auto,
518 Text,
520 None,
522 All,
524}
525impl_option!(
526 StyleUserSelect,
527 OptionStyleUserSelect,
528 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
529);
530impl PrintAsCssValue for StyleUserSelect {
531 fn print_as_css_value(&self) -> String {
532 String::from(match self {
533 Self::Auto => "auto",
534 Self::Text => "text",
535 Self::None => "none",
536 Self::All => "all",
537 })
538 }
539}
540
541#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
545#[repr(C)]
546#[derive(Default)]
547pub enum StyleTextDecoration {
548 #[default]
550 None,
551 Underline,
553 Overline,
555 LineThrough,
557}
558impl_option!(
559 StyleTextDecoration,
560 OptionStyleTextDecoration,
561 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
562);
563impl PrintAsCssValue for StyleTextDecoration {
564 fn print_as_css_value(&self) -> String {
565 String::from(match self {
566 Self::None => "none",
567 Self::Underline => "underline",
568 Self::Overline => "overline",
569 Self::LineThrough => "line-through",
570 })
571 }
572}
573
574#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
578#[repr(C, u8)]
579#[derive(Default)]
580pub enum StyleVerticalAlign {
581 #[default]
583 Baseline,
584 Top,
586 Middle,
588 Bottom,
590 Sub,
592 Superscript,
594 TextTop,
596 TextBottom,
598 Percentage(PercentageValue),
600 Length(PixelValue),
602}
603
604impl_option!(
605 StyleVerticalAlign,
606 OptionStyleVerticalAlign,
607 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
608);
609
610impl PrintAsCssValue for StyleVerticalAlign {
611 fn print_as_css_value(&self) -> String {
612 match self {
613 Self::Baseline => String::from("baseline"),
614 Self::Top => String::from("top"),
615 Self::Middle => String::from("middle"),
616 Self::Bottom => String::from("bottom"),
617 Self::Sub => String::from("sub"),
618 Self::Superscript => String::from("super"),
619 Self::TextTop => String::from("text-top"),
620 Self::TextBottom => String::from("text-bottom"),
621 Self::Percentage(p) => format!("{}%", p.normalized() * 100.0),
622 Self::Length(l) => l.print_as_css_value(),
623 }
624 }
625}
626
627impl FormatAsRustCode for StyleVerticalAlign {
628 fn format_as_rust_code(&self, indent: usize) -> String {
629 match self {
630 Self::Baseline => "StyleVerticalAlign::Baseline".to_string(),
631 Self::Top => "StyleVerticalAlign::Top".to_string(),
632 Self::Middle => "StyleVerticalAlign::Middle".to_string(),
633 Self::Bottom => "StyleVerticalAlign::Bottom".to_string(),
634 Self::Sub => "StyleVerticalAlign::Sub".to_string(),
635 Self::Superscript => "StyleVerticalAlign::Superscript".to_string(),
636 Self::TextTop => "StyleVerticalAlign::TextTop".to_string(),
637 Self::TextBottom => "StyleVerticalAlign::TextBottom".to_string(),
638 Self::Percentage(p) => format!("StyleVerticalAlign::Percentage(PercentageValue::new({}))", p.normalized() * 100.0),
639 Self::Length(l) => format!("StyleVerticalAlign::Length({l})"),
640 }
641 }
642}
643
644#[cfg(feature = "parser")]
647use crate::props::basic::{
648 color::{parse_css_color, CssColorParseError, CssColorParseErrorOwned},
649 DurationParseError,
650};
651
652#[cfg(feature = "parser")]
653#[derive(Clone, PartialEq)]
654pub enum StyleTextColorParseError<'a> {
655 ColorParseError(CssColorParseError<'a>),
656}
657#[cfg(feature = "parser")]
658impl_debug_as_display!(StyleTextColorParseError<'a>);
659#[cfg(feature = "parser")]
660impl_display! { StyleTextColorParseError<'a>, {
661 ColorParseError(e) => format!("Invalid color: {}", e),
662}}
663#[cfg(feature = "parser")]
664impl_from!(
665 CssColorParseError<'a>,
666 StyleTextColorParseError::ColorParseError
667);
668
669#[cfg(feature = "parser")]
670#[derive(Debug, Clone, PartialEq)]
671#[repr(C, u8)]
672pub enum StyleTextColorParseErrorOwned {
673 ColorParseError(CssColorParseErrorOwned),
674}
675
676#[cfg(feature = "parser")]
677impl StyleTextColorParseError<'_> {
678 #[must_use] pub fn to_contained(&self) -> StyleTextColorParseErrorOwned {
679 match self {
680 Self::ColorParseError(e) => {
681 StyleTextColorParseErrorOwned::ColorParseError(e.to_contained())
682 }
683 }
684 }
685}
686
687#[cfg(feature = "parser")]
688impl StyleTextColorParseErrorOwned {
689 #[must_use] pub fn to_shared(&self) -> StyleTextColorParseError<'_> {
690 match self {
691 Self::ColorParseError(e) => StyleTextColorParseError::ColorParseError(e.to_shared()),
692 }
693 }
694}
695
696#[cfg(feature = "parser")]
697pub fn parse_style_text_color(input: &str) -> Result<StyleTextColor, StyleTextColorParseError<'_>> {
701 parse_css_color(input)
702 .map(|inner| StyleTextColor { inner })
703 .map_err(StyleTextColorParseError::ColorParseError)
704}
705
706#[cfg(feature = "parser")]
707#[derive(Clone, PartialEq, Eq)]
708pub enum StyleTextAlignParseError<'a> {
709 InvalidValue(InvalidValueErr<'a>),
710}
711#[cfg(feature = "parser")]
712impl_debug_as_display!(StyleTextAlignParseError<'a>);
713#[cfg(feature = "parser")]
714impl_display! { StyleTextAlignParseError<'a>, {
715 InvalidValue(e) => format!("Invalid text-align value: \"{}\"", e.0),
716}}
717#[cfg(feature = "parser")]
718impl_from!(InvalidValueErr<'a>, StyleTextAlignParseError::InvalidValue);
719
720#[cfg(feature = "parser")]
721#[derive(Debug, Clone, PartialEq, Eq)]
722#[repr(C, u8)]
723pub enum StyleTextAlignParseErrorOwned {
724 InvalidValue(InvalidValueErrOwned),
725}
726
727#[cfg(feature = "parser")]
728impl StyleTextAlignParseError<'_> {
729 #[must_use] pub fn to_contained(&self) -> StyleTextAlignParseErrorOwned {
730 match self {
731 Self::InvalidValue(e) => StyleTextAlignParseErrorOwned::InvalidValue(e.to_contained()),
732 }
733 }
734}
735
736#[cfg(feature = "parser")]
737impl StyleTextAlignParseErrorOwned {
738 #[must_use] pub fn to_shared(&self) -> StyleTextAlignParseError<'_> {
739 match self {
740 Self::InvalidValue(e) => StyleTextAlignParseError::InvalidValue(e.to_shared()),
741 }
742 }
743}
744
745#[cfg(feature = "parser")]
746pub fn parse_style_text_align(input: &str) -> Result<StyleTextAlign, StyleTextAlignParseError<'_>> {
750 match input.trim() {
751 "left" => Ok(StyleTextAlign::Left),
752 "center" => Ok(StyleTextAlign::Center),
753 "right" => Ok(StyleTextAlign::Right),
754 "justify" => Ok(StyleTextAlign::Justify),
755 "start" => Ok(StyleTextAlign::Start),
756 "end" => Ok(StyleTextAlign::End),
757 other => Err(StyleTextAlignParseError::InvalidValue(InvalidValueErr(
758 other,
759 ))),
760 }
761}
762
763#[cfg(feature = "parser")]
764#[derive(Clone, PartialEq, Eq)]
765pub enum StyleLetterSpacingParseError<'a> {
766 PixelValue(CssPixelValueParseError<'a>),
767}
768#[cfg(feature = "parser")]
769impl_debug_as_display!(StyleLetterSpacingParseError<'a>);
770#[cfg(feature = "parser")]
771impl_display! { StyleLetterSpacingParseError<'a>, {
772 PixelValue(e) => format!("Invalid letter-spacing value: {}", e),
773}}
774#[cfg(feature = "parser")]
775impl_from!(
776 CssPixelValueParseError<'a>,
777 StyleLetterSpacingParseError::PixelValue
778);
779
780#[cfg(feature = "parser")]
781#[derive(Debug, Clone, PartialEq, Eq)]
782#[repr(C, u8)]
783pub enum StyleLetterSpacingParseErrorOwned {
784 PixelValue(CssPixelValueParseErrorOwned),
785}
786
787#[cfg(feature = "parser")]
788impl StyleLetterSpacingParseError<'_> {
789 #[must_use] pub fn to_contained(&self) -> StyleLetterSpacingParseErrorOwned {
790 match self {
791 Self::PixelValue(e) => StyleLetterSpacingParseErrorOwned::PixelValue(e.to_contained()),
792 }
793 }
794}
795
796#[cfg(feature = "parser")]
797impl StyleLetterSpacingParseErrorOwned {
798 #[must_use] pub fn to_shared(&self) -> StyleLetterSpacingParseError<'_> {
799 match self {
800 Self::PixelValue(e) => StyleLetterSpacingParseError::PixelValue(e.to_shared()),
801 }
802 }
803}
804
805#[cfg(feature = "parser")]
806pub fn parse_style_letter_spacing(
810 input: &str,
811) -> Result<StyleLetterSpacing, StyleLetterSpacingParseError<'_>> {
812 crate::props::basic::pixel::parse_pixel_value(input)
813 .map(|inner| StyleLetterSpacing { inner })
814 .map_err(StyleLetterSpacingParseError::PixelValue)
815}
816
817#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
821#[repr(C)]
822pub struct StyleTextIndent {
823 pub inner: PixelValue,
824 pub each_line: bool,
827 pub hanging: bool,
829}
830
831impl fmt::Debug for StyleTextIndent {
832 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
833 write!(f, "{}", self.print_as_css_value())
834 }
835}
836
837impl StyleTextIndent {
838 #[inline]
839 #[must_use] pub const fn zero() -> Self {
840 Self { inner: PixelValue::zero(), each_line: false, hanging: false }
841 }
842 #[inline]
843 #[must_use] pub const fn const_px(value: isize) -> Self {
844 Self { inner: PixelValue::const_px(value), each_line: false, hanging: false }
845 }
846 #[inline]
847 #[must_use] pub const fn const_em(value: isize) -> Self {
848 Self { inner: PixelValue::const_em(value), each_line: false, hanging: false }
849 }
850 #[inline]
851 #[must_use] pub const fn const_pt(value: isize) -> Self {
852 Self { inner: PixelValue::const_pt(value), each_line: false, hanging: false }
853 }
854 #[inline]
855 #[must_use] pub const fn const_percent(value: isize) -> Self {
856 Self { inner: PixelValue::const_percent(value), each_line: false, hanging: false }
857 }
858 #[inline]
859 #[must_use] pub const fn const_in(value: isize) -> Self {
860 Self { inner: PixelValue::const_in(value), each_line: false, hanging: false }
861 }
862 #[inline]
863 #[must_use] pub const fn const_cm(value: isize) -> Self {
864 Self { inner: PixelValue::const_cm(value), each_line: false, hanging: false }
865 }
866 #[inline]
867 #[must_use] pub const fn const_mm(value: isize) -> Self {
868 Self { inner: PixelValue::const_mm(value), each_line: false, hanging: false }
869 }
870 #[inline]
871 #[must_use] pub const fn const_from_metric(metric: crate::props::basic::length::SizeMetric, value: isize) -> Self {
872 Self { inner: PixelValue::const_from_metric(metric, value), each_line: false, hanging: false }
873 }
874 #[inline]
875 #[must_use] pub fn px(value: f32) -> Self {
876 Self { inner: PixelValue::px(value), each_line: false, hanging: false }
877 }
878 #[inline]
879 #[must_use] pub fn em(value: f32) -> Self {
880 Self { inner: PixelValue::em(value), each_line: false, hanging: false }
881 }
882 #[inline]
883 #[must_use] pub fn pt(value: f32) -> Self {
884 Self { inner: PixelValue::pt(value), each_line: false, hanging: false }
885 }
886 #[inline]
887 #[must_use] pub fn percent(value: f32) -> Self {
888 Self { inner: PixelValue::percent(value), each_line: false, hanging: false }
889 }
890 #[inline]
891 #[must_use] pub fn from_metric(metric: crate::props::basic::length::SizeMetric, value: f32) -> Self {
892 Self { inner: PixelValue::from_metric(metric, value), each_line: false, hanging: false }
893 }
894 #[inline]
895 #[must_use] pub fn interpolate(&self, other: &Self, t: f32) -> Self {
896 Self { inner: self.inner.interpolate(&other.inner, t), each_line: self.each_line, hanging: self.hanging }
897 }
898}
899
900impl PrintAsCssValue for StyleTextIndent {
901 fn print_as_css_value(&self) -> String {
902 let mut s = self.inner.to_string();
903 if self.hanging {
904 s.push_str(" hanging");
905 }
906 if self.each_line {
907 s.push_str(" each-line");
908 }
909 s
910 }
911}
912
913impl FormatAsRustCode for StyleTextIndent {
914 fn format_as_rust_code(&self, _tabs: usize) -> String {
915 format!(
916 "StyleTextIndent {{ inner: {}, each_line: {}, hanging: {} }}",
917 self.inner.format_as_rust_code(0), self.each_line, self.hanging
918 )
919 }
920}
921
922#[cfg(feature = "parser")]
923#[derive(Clone, PartialEq, Eq)]
924pub enum StyleTextIndentParseError<'a> {
925 PixelValue(CssPixelValueParseError<'a>),
926}
927#[cfg(feature = "parser")]
928impl_debug_as_display!(StyleTextIndentParseError<'a>);
929#[cfg(feature = "parser")]
930impl_display! { StyleTextIndentParseError<'a>, {
931 PixelValue(e) => format!("Invalid text-indent value: {}", e),
932}}
933#[cfg(feature = "parser")]
934impl_from!(
935 CssPixelValueParseError<'a>,
936 StyleTextIndentParseError::PixelValue
937);
938
939#[cfg(feature = "parser")]
940#[derive(Debug, Clone, PartialEq, Eq)]
941#[repr(C, u8)]
942pub enum StyleTextIndentParseErrorOwned {
943 PixelValue(CssPixelValueParseErrorOwned),
944}
945
946#[cfg(feature = "parser")]
947impl StyleTextIndentParseError<'_> {
948 #[must_use] pub fn to_contained(&self) -> StyleTextIndentParseErrorOwned {
949 match self {
950 Self::PixelValue(e) => StyleTextIndentParseErrorOwned::PixelValue(e.to_contained()),
951 }
952 }
953}
954
955#[cfg(feature = "parser")]
956impl StyleTextIndentParseErrorOwned {
957 #[must_use] pub fn to_shared(&self) -> StyleTextIndentParseError<'_> {
958 match self {
959 Self::PixelValue(e) => StyleTextIndentParseError::PixelValue(e.to_shared()),
960 }
961 }
962}
963
964#[cfg(feature = "parser")]
965pub fn parse_style_text_indent(input: &str) -> Result<StyleTextIndent, StyleTextIndentParseError<'_>> {
969 let mut each_line = false;
970 let mut hanging = false;
971 let mut pixel_part: Option<&str> = None;
972
973 for token in input.split_whitespace() {
974 match token {
975 "each-line" => each_line = true,
976 "hanging" => hanging = true,
977 _ => {
978 pixel_part = Some(token);
979 }
980 }
981 }
982
983 let pixel_str = pixel_part.unwrap_or("0px");
984
985 crate::props::basic::pixel::parse_pixel_value(pixel_str)
986 .map(|inner| StyleTextIndent { inner, each_line, hanging })
987 .map_err(StyleTextIndentParseError::PixelValue)
988}
989
990#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
992#[repr(C)]
993pub struct StyleInitialLetter {
994 pub size: u32,
995 pub sink: crate::corety::OptionU32,
996}
997
998impl FormatAsRustCode for StyleInitialLetter {
999 fn format_as_rust_code(&self, _tabs: usize) -> String {
1000 format!("{self:?}")
1001 }
1002}
1003
1004impl PrintAsCssValue for StyleInitialLetter {
1005 fn print_as_css_value(&self) -> String {
1006 if let crate::corety::OptionU32::Some(sink) = self.sink {
1007 format!("{} {}", self.size, sink)
1008 } else {
1009 format!("{}", self.size)
1010 }
1011 }
1012}
1013
1014#[cfg(feature = "parser")]
1015#[derive(Clone, PartialEq, Eq)]
1016pub enum StyleInitialLetterParseError<'a> {
1017 InvalidFormat(&'a str),
1018 InvalidSize(&'a str),
1019 InvalidSink(&'a str),
1020}
1021#[cfg(feature = "parser")]
1022impl_debug_as_display!(StyleInitialLetterParseError<'a>);
1023#[cfg(feature = "parser")]
1024impl_display! { StyleInitialLetterParseError<'a>, {
1025 InvalidFormat(e) => format!("Invalid initial-letter format: {}", e),
1026 InvalidSize(e) => format!("Invalid initial-letter size: {}", e),
1027 InvalidSink(e) => format!("Invalid initial-letter sink: {}", e),
1028}}
1029
1030#[cfg(feature = "parser")]
1031#[derive(Debug, Clone, PartialEq, Eq)]
1032#[repr(C, u8)]
1033pub enum StyleInitialLetterParseErrorOwned {
1034 InvalidFormat(AzString),
1035 InvalidSize(AzString),
1036 InvalidSink(AzString),
1037}
1038
1039#[cfg(feature = "parser")]
1040impl StyleInitialLetterParseError<'_> {
1041 #[must_use] pub fn to_contained(&self) -> StyleInitialLetterParseErrorOwned {
1042 match self {
1043 Self::InvalidFormat(s) => {
1044 StyleInitialLetterParseErrorOwned::InvalidFormat((*s).to_string().into())
1045 }
1046 Self::InvalidSize(s) => StyleInitialLetterParseErrorOwned::InvalidSize((*s).to_string().into()),
1047 Self::InvalidSink(s) => StyleInitialLetterParseErrorOwned::InvalidSink((*s).to_string().into()),
1048 }
1049 }
1050}
1051
1052#[cfg(feature = "parser")]
1053impl StyleInitialLetterParseErrorOwned {
1054 #[must_use] pub fn to_shared(&self) -> StyleInitialLetterParseError<'_> {
1055 match self {
1056 Self::InvalidFormat(s) => StyleInitialLetterParseError::InvalidFormat(s.as_str()),
1057 Self::InvalidSize(s) => StyleInitialLetterParseError::InvalidSize(s.as_str()),
1058 Self::InvalidSink(s) => StyleInitialLetterParseError::InvalidSink(s.as_str()),
1059 }
1060 }
1061}
1062
1063#[cfg(feature = "parser")]
1064impl From<StyleInitialLetterParseError<'_>> for StyleInitialLetterParseErrorOwned {
1065 fn from(e: StyleInitialLetterParseError<'_>) -> Self {
1066 match e {
1067 StyleInitialLetterParseError::InvalidFormat(s) => {
1068 Self::InvalidFormat(s.to_string().into())
1069 }
1070 StyleInitialLetterParseError::InvalidSize(s) => {
1071 Self::InvalidSize(s.to_string().into())
1072 }
1073 StyleInitialLetterParseError::InvalidSink(s) => {
1074 Self::InvalidSink(s.to_string().into())
1075 }
1076 }
1077 }
1078}
1079
1080#[cfg(feature = "parser")]
1081impl_display! { StyleInitialLetterParseErrorOwned, {
1082 InvalidFormat(e) => format!("Invalid initial-letter format: {}", e),
1083 InvalidSize(e) => format!("Invalid initial-letter size: {}", e),
1084 InvalidSink(e) => format!("Invalid initial-letter sink: {}", e),
1085}}
1086
1087#[cfg(feature = "parser")]
1088pub fn parse_style_initial_letter(
1092 input: &str,
1093) -> Result<StyleInitialLetter, StyleInitialLetterParseError<'_>> {
1094 let input = input.trim();
1095 let parts: Vec<&str> = input.split_whitespace().collect();
1096
1097 if parts.is_empty() {
1098 return Err(StyleInitialLetterParseError::InvalidFormat(input));
1099 }
1100
1101 let size = parts[0]
1103 .parse::<u32>()
1104 .map_err(|_| StyleInitialLetterParseError::InvalidSize(parts[0]))?;
1105
1106 if size == 0 {
1107 return Err(StyleInitialLetterParseError::InvalidSize(parts[0]));
1108 }
1109
1110 let sink = if parts.len() > 1 {
1112 crate::corety::OptionU32::Some(
1113 parts[1]
1114 .parse::<u32>()
1115 .map_err(|_| StyleInitialLetterParseError::InvalidSink(parts[1]))?,
1116 )
1117 } else {
1118 crate::corety::OptionU32::None
1119 };
1120
1121 Ok(StyleInitialLetter { size, sink })
1122}
1123
1124#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1126#[repr(C)]
1127pub struct StyleLineClamp {
1128 pub max_lines: usize,
1129}
1130
1131impl FormatAsRustCode for StyleLineClamp {
1132 fn format_as_rust_code(&self, _tabs: usize) -> String {
1133 format!("{self:?}")
1134 }
1135}
1136
1137impl PrintAsCssValue for StyleLineClamp {
1138 fn print_as_css_value(&self) -> String {
1139 format!("{}", self.max_lines)
1140 }
1141}
1142
1143#[cfg(feature = "parser")]
1144#[derive(Clone, PartialEq, Eq)]
1145pub enum StyleLineClampParseError<'a> {
1146 InvalidValue(&'a str),
1147 ZeroValue,
1148}
1149#[cfg(feature = "parser")]
1150impl_debug_as_display!(StyleLineClampParseError<'a>);
1151#[cfg(feature = "parser")]
1152impl_display! { StyleLineClampParseError<'a>, {
1153 InvalidValue(e) => format!("Invalid line-clamp value: {}", e),
1154 ZeroValue => format!("line-clamp cannot be zero"),
1155}}
1156#[allow(variant_size_differences)] #[cfg(feature = "parser")]
1158#[derive(Debug, Clone, PartialEq, Eq)]
1159#[repr(C, u8)]
1160pub enum StyleLineClampParseErrorOwned {
1161 InvalidValue(AzString),
1162 ZeroValue,
1163}
1164
1165#[cfg(feature = "parser")]
1166impl StyleLineClampParseError<'_> {
1167 #[must_use] pub fn to_contained(&self) -> StyleLineClampParseErrorOwned {
1168 match self {
1169 Self::InvalidValue(s) => StyleLineClampParseErrorOwned::InvalidValue((*s).to_string().into()),
1170 Self::ZeroValue => StyleLineClampParseErrorOwned::ZeroValue,
1171 }
1172 }
1173}
1174
1175#[cfg(feature = "parser")]
1176impl StyleLineClampParseErrorOwned {
1177 #[must_use] pub fn to_shared(&self) -> StyleLineClampParseError<'_> {
1178 match self {
1179 Self::InvalidValue(s) => StyleLineClampParseError::InvalidValue(s.as_str()),
1180 Self::ZeroValue => StyleLineClampParseError::ZeroValue,
1181 }
1182 }
1183}
1184
1185#[cfg(feature = "parser")]
1186impl From<StyleLineClampParseError<'_>> for StyleLineClampParseErrorOwned {
1187 fn from(e: StyleLineClampParseError<'_>) -> Self {
1188 e.to_contained()
1189 }
1190}
1191
1192#[cfg(feature = "parser")]
1193impl_display! { StyleLineClampParseErrorOwned, {
1194 InvalidValue(e) => format!("Invalid line-clamp value: {}", e),
1195 ZeroValue => format!("line-clamp cannot be zero"),
1196}}
1197
1198#[cfg(feature = "parser")]
1199pub fn parse_style_line_clamp(
1203 input: &str,
1204) -> Result<StyleLineClamp, StyleLineClampParseError<'_>> {
1205 let input = input.trim();
1206
1207 let max_lines = input
1208 .parse::<usize>()
1209 .map_err(|_| StyleLineClampParseError::InvalidValue(input))?;
1210
1211 if max_lines == 0 {
1212 return Err(StyleLineClampParseError::ZeroValue);
1213 }
1214
1215 Ok(StyleLineClamp { max_lines })
1216}
1217
1218#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1222#[repr(C)]
1223#[derive(Default)]
1224pub struct StyleHangingPunctuation {
1225 pub first: bool,
1226 pub force_end: bool,
1227 pub allow_end: bool,
1228 pub last: bool,
1229}
1230
1231impl StyleHangingPunctuation {
1232 #[must_use] pub const fn is_enabled(&self) -> bool {
1233 self.first || self.force_end || self.allow_end || self.last
1234 }
1235}
1236
1237impl FormatAsRustCode for StyleHangingPunctuation {
1238 fn format_as_rust_code(&self, _tabs: usize) -> String {
1239 format!("{self:?}")
1240 }
1241}
1242
1243impl PrintAsCssValue for StyleHangingPunctuation {
1244 fn print_as_css_value(&self) -> String {
1245 if !self.is_enabled() {
1246 return "none".to_string();
1247 }
1248 let mut parts = Vec::new();
1249 if self.first { parts.push("first"); }
1250 if self.force_end { parts.push("force-end"); }
1251 if self.allow_end { parts.push("allow-end"); }
1252 if self.last { parts.push("last"); }
1253 parts.join(" ")
1254 }
1255}
1256
1257#[cfg(feature = "parser")]
1258#[derive(Clone, PartialEq, Eq)]
1259pub enum StyleHangingPunctuationParseError<'a> {
1260 InvalidValue(&'a str),
1261}
1262#[cfg(feature = "parser")]
1263impl_debug_as_display!(StyleHangingPunctuationParseError<'a>);
1264#[cfg(feature = "parser")]
1265impl_display! { StyleHangingPunctuationParseError<'a>, {
1266 InvalidValue(e) => format!("Invalid hanging-punctuation value: {}", e),
1267}}
1268
1269#[cfg(feature = "parser")]
1270#[derive(Debug, Clone, PartialEq, Eq)]
1271#[repr(C, u8)]
1272pub enum StyleHangingPunctuationParseErrorOwned {
1273 InvalidValue(AzString),
1274}
1275
1276#[cfg(feature = "parser")]
1277impl StyleHangingPunctuationParseError<'_> {
1278 #[must_use] pub fn to_contained(&self) -> StyleHangingPunctuationParseErrorOwned {
1279 match self {
1280 Self::InvalidValue(s) => {
1281 StyleHangingPunctuationParseErrorOwned::InvalidValue((*s).to_string().into())
1282 }
1283 }
1284 }
1285}
1286
1287#[cfg(feature = "parser")]
1288impl StyleHangingPunctuationParseErrorOwned {
1289 #[must_use] pub fn to_shared(&self) -> StyleHangingPunctuationParseError<'_> {
1290 match self {
1291 Self::InvalidValue(s) => StyleHangingPunctuationParseError::InvalidValue(s.as_str()),
1292 }
1293 }
1294}
1295
1296#[cfg(feature = "parser")]
1297impl From<StyleHangingPunctuationParseError<'_>> for StyleHangingPunctuationParseErrorOwned {
1298 fn from(e: StyleHangingPunctuationParseError<'_>) -> Self {
1299 e.to_contained()
1300 }
1301}
1302
1303#[cfg(feature = "parser")]
1304impl_display! { StyleHangingPunctuationParseErrorOwned, {
1305 InvalidValue(e) => format!("Invalid hanging-punctuation value: {}", e),
1306}}
1307
1308#[cfg(feature = "parser")]
1309pub fn parse_style_hanging_punctuation(
1313 input: &str,
1314) -> Result<StyleHangingPunctuation, StyleHangingPunctuationParseError<'_>> {
1315 let input = input.trim();
1316
1317 if input.eq_ignore_ascii_case("none") {
1318 return Ok(StyleHangingPunctuation::default());
1319 }
1320
1321 let mut first = false;
1322 let mut force_end = false;
1323 let mut allow_end = false;
1324 let mut last = false;
1325
1326 for token in input.split_whitespace() {
1327 match token.to_lowercase().as_str() {
1328 "first" => first = true,
1329 "force-end" => force_end = true,
1330 "allow-end" => allow_end = true,
1331 "last" => last = true,
1332 _ => return Err(StyleHangingPunctuationParseError::InvalidValue(input)),
1333 }
1334 }
1335
1336 if force_end && allow_end {
1337 return Err(StyleHangingPunctuationParseError::InvalidValue(input));
1338 }
1339
1340 Ok(StyleHangingPunctuation { first, force_end, allow_end, last })
1341}
1342
1343#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1345#[repr(C, u8)]
1346#[derive(Default)]
1347pub enum StyleTextCombineUpright {
1348 #[default]
1349 None,
1350 All,
1351 Digits(u8),
1352}
1353
1354
1355impl FormatAsRustCode for StyleTextCombineUpright {
1356 fn format_as_rust_code(&self, _tabs: usize) -> String {
1357 format!("{self:?}")
1358 }
1359}
1360
1361impl PrintAsCssValue for StyleTextCombineUpright {
1362 fn print_as_css_value(&self) -> String {
1363 match self {
1364 Self::None => "none".to_string(),
1365 Self::All => "all".to_string(),
1366 Self::Digits(n) => format!("digits {n}"),
1367 }
1368 }
1369}
1370
1371#[cfg(feature = "parser")]
1372#[derive(Clone, PartialEq, Eq)]
1373pub enum StyleTextCombineUprightParseError<'a> {
1374 InvalidValue(&'a str),
1375 InvalidDigits(&'a str),
1376}
1377#[cfg(feature = "parser")]
1378impl_debug_as_display!(StyleTextCombineUprightParseError<'a>);
1379#[cfg(feature = "parser")]
1380impl_display! { StyleTextCombineUprightParseError<'a>, {
1381 InvalidValue(e) => format!("Invalid text-combine-upright value: {}", e),
1382 InvalidDigits(e) => format!("Invalid text-combine-upright digits: {}", e),
1383}}
1384
1385#[cfg(feature = "parser")]
1386#[derive(Debug, Clone, PartialEq, Eq)]
1387#[repr(C, u8)]
1388pub enum StyleTextCombineUprightParseErrorOwned {
1389 InvalidValue(AzString),
1390 InvalidDigits(AzString),
1391}
1392
1393#[cfg(feature = "parser")]
1394impl StyleTextCombineUprightParseError<'_> {
1395 #[must_use] pub fn to_contained(&self) -> StyleTextCombineUprightParseErrorOwned {
1396 match self {
1397 Self::InvalidValue(s) => {
1398 StyleTextCombineUprightParseErrorOwned::InvalidValue((*s).to_string().into())
1399 }
1400 Self::InvalidDigits(s) => {
1401 StyleTextCombineUprightParseErrorOwned::InvalidDigits((*s).to_string().into())
1402 }
1403 }
1404 }
1405}
1406
1407#[cfg(feature = "parser")]
1408impl StyleTextCombineUprightParseErrorOwned {
1409 #[must_use] pub fn to_shared(&self) -> StyleTextCombineUprightParseError<'_> {
1410 match self {
1411 Self::InvalidValue(s) => StyleTextCombineUprightParseError::InvalidValue(s.as_str()),
1412 Self::InvalidDigits(s) => StyleTextCombineUprightParseError::InvalidDigits(s.as_str()),
1413 }
1414 }
1415}
1416
1417#[cfg(feature = "parser")]
1418impl From<StyleTextCombineUprightParseError<'_>> for StyleTextCombineUprightParseErrorOwned {
1419 fn from(e: StyleTextCombineUprightParseError<'_>) -> Self {
1420 e.to_contained()
1421 }
1422}
1423
1424#[cfg(feature = "parser")]
1425impl_display! { StyleTextCombineUprightParseErrorOwned, {
1426 InvalidValue(e) => format!("Invalid text-combine-upright value: {}", e),
1427 InvalidDigits(e) => format!("Invalid text-combine-upright digits: {}", e),
1428}}
1429
1430#[cfg(feature = "parser")]
1431pub fn parse_style_text_combine_upright(
1435 input: &str,
1436) -> Result<StyleTextCombineUpright, StyleTextCombineUprightParseError<'_>> {
1437 let trimmed = input.trim();
1438
1439 if trimmed.eq_ignore_ascii_case("none") {
1440 Ok(StyleTextCombineUpright::None)
1441 } else if trimmed.eq_ignore_ascii_case("all") {
1442 Ok(StyleTextCombineUpright::All)
1443 } else if trimmed.starts_with("digits") {
1444 let parts: Vec<&str> = trimmed.split_whitespace().collect();
1445 if parts.len() == 2 {
1446 let n = parts[1]
1447 .parse::<u8>()
1448 .map_err(|_| StyleTextCombineUprightParseError::InvalidDigits(input))?;
1449 if (2..=4).contains(&n) {
1450 Ok(StyleTextCombineUpright::Digits(n))
1451 } else {
1452 Err(StyleTextCombineUprightParseError::InvalidDigits(input))
1453 }
1454 } else {
1455 Ok(StyleTextCombineUpright::Digits(2))
1457 }
1458 } else {
1459 Err(StyleTextCombineUprightParseError::InvalidValue(input))
1460 }
1461}
1462
1463#[cfg(feature = "parser")]
1464#[derive(Clone, PartialEq, Eq)]
1465pub enum StyleWordSpacingParseError<'a> {
1466 PixelValue(CssPixelValueParseError<'a>),
1467}
1468#[cfg(feature = "parser")]
1469impl_debug_as_display!(StyleWordSpacingParseError<'a>);
1470#[cfg(feature = "parser")]
1471impl_display! { StyleWordSpacingParseError<'a>, {
1472 PixelValue(e) => format!("Invalid word-spacing value: {}", e),
1473}}
1474#[cfg(feature = "parser")]
1475impl_from!(
1476 CssPixelValueParseError<'a>,
1477 StyleWordSpacingParseError::PixelValue
1478);
1479
1480#[cfg(feature = "parser")]
1481#[derive(Debug, Clone, PartialEq, Eq)]
1482#[repr(C, u8)]
1483pub enum StyleWordSpacingParseErrorOwned {
1484 PixelValue(CssPixelValueParseErrorOwned),
1485}
1486
1487#[cfg(feature = "parser")]
1488impl StyleWordSpacingParseError<'_> {
1489 #[must_use] pub fn to_contained(&self) -> StyleWordSpacingParseErrorOwned {
1490 match self {
1491 Self::PixelValue(e) => StyleWordSpacingParseErrorOwned::PixelValue(e.to_contained()),
1492 }
1493 }
1494}
1495
1496#[cfg(feature = "parser")]
1497impl StyleWordSpacingParseErrorOwned {
1498 #[must_use] pub fn to_shared(&self) -> StyleWordSpacingParseError<'_> {
1499 match self {
1500 Self::PixelValue(e) => StyleWordSpacingParseError::PixelValue(e.to_shared()),
1501 }
1502 }
1503}
1504
1505#[cfg(feature = "parser")]
1506pub fn parse_style_word_spacing(
1510 input: &str,
1511) -> Result<StyleWordSpacing, StyleWordSpacingParseError<'_>> {
1512 crate::props::basic::pixel::parse_pixel_value(input)
1513 .map(|inner| StyleWordSpacing { inner })
1514 .map_err(StyleWordSpacingParseError::PixelValue)
1515}
1516
1517#[cfg(feature = "parser")]
1518#[derive(Clone, PartialEq, Eq)]
1519#[repr(C, u8)]
1520pub enum StyleLineHeightParseError {
1521 Percentage(PercentageParseError),
1522}
1523#[cfg(feature = "parser")]
1524impl_debug_as_display!(StyleLineHeightParseError);
1525#[cfg(feature = "parser")]
1526impl_display! { StyleLineHeightParseError, {
1527 Percentage(e) => format!("Invalid line-height value: {}", e),
1528}}
1529#[cfg(feature = "parser")]
1530impl_from!(PercentageParseError, StyleLineHeightParseError::Percentage);
1531
1532#[cfg(feature = "parser")]
1533#[derive(Debug, Clone, PartialEq, Eq)]
1534pub enum StyleLineHeightParseErrorOwned {
1535 Percentage(PercentageParseErrorOwned),
1536}
1537
1538#[cfg(feature = "parser")]
1539impl StyleLineHeightParseError {
1540 #[must_use] pub fn to_contained(&self) -> StyleLineHeightParseErrorOwned {
1541 match self {
1542 Self::Percentage(e) => StyleLineHeightParseErrorOwned::Percentage(e.to_contained()),
1543 }
1544 }
1545}
1546
1547#[cfg(feature = "parser")]
1548impl StyleLineHeightParseErrorOwned {
1549 #[must_use] pub fn to_shared(&self) -> StyleLineHeightParseError {
1550 match self {
1551 Self::Percentage(e) => StyleLineHeightParseError::Percentage(e.to_shared()),
1552 }
1553 }
1554}
1555
1556#[cfg(feature = "parser")]
1557pub fn parse_style_line_height(input: &str) -> Result<StyleLineHeight, StyleLineHeightParseError> {
1561 if let Ok(inner) = crate::props::basic::length::parse_percentage_value(input) {
1563 return Ok(StyleLineHeight { inner });
1564 }
1565 if let Ok(px) = crate::props::basic::pixel::parse_pixel_value(input) {
1569 if px.metric == crate::props::basic::length::SizeMetric::Px {
1570 let px_val = px.number.get();
1571 return Ok(StyleLineHeight {
1572 inner: PercentageValue::new(-px_val * 100.0),
1573 });
1574 }
1575 }
1576 Err(StyleLineHeightParseError::Percentage(
1577 PercentageParseError::InvalidUnit(String::new().into()),
1578 ))
1579}
1580
1581#[cfg(feature = "parser")]
1582#[derive(Clone, PartialEq, Eq)]
1583pub enum StyleTabSizeParseError<'a> {
1584 PixelValue(CssPixelValueParseError<'a>),
1585}
1586#[cfg(feature = "parser")]
1587impl_debug_as_display!(StyleTabSizeParseError<'a>);
1588#[cfg(feature = "parser")]
1589impl_display! { StyleTabSizeParseError<'a>, {
1590 PixelValue(e) => format!("Invalid tab-size value: {}", e),
1591}}
1592#[cfg(feature = "parser")]
1593impl_from!(
1594 CssPixelValueParseError<'a>,
1595 StyleTabSizeParseError::PixelValue
1596);
1597
1598#[cfg(feature = "parser")]
1599#[derive(Debug, Clone, PartialEq, Eq)]
1600#[repr(C, u8)]
1601pub enum StyleTabSizeParseErrorOwned {
1602 PixelValue(CssPixelValueParseErrorOwned),
1603}
1604
1605#[cfg(feature = "parser")]
1606impl StyleTabSizeParseError<'_> {
1607 #[must_use] pub fn to_contained(&self) -> StyleTabSizeParseErrorOwned {
1608 match self {
1609 Self::PixelValue(e) => StyleTabSizeParseErrorOwned::PixelValue(e.to_contained()),
1610 }
1611 }
1612}
1613
1614#[cfg(feature = "parser")]
1615impl StyleTabSizeParseErrorOwned {
1616 #[must_use] pub fn to_shared(&self) -> StyleTabSizeParseError<'_> {
1617 match self {
1618 Self::PixelValue(e) => StyleTabSizeParseError::PixelValue(e.to_shared()),
1619 }
1620 }
1621}
1622
1623#[cfg(feature = "parser")]
1624pub fn parse_style_tab_size(input: &str) -> Result<StyleTabSize, StyleTabSizeParseError<'_>> {
1628 input.trim().parse::<f32>().map_or_else(
1629 |_| {
1630 crate::props::basic::pixel::parse_pixel_value(input)
1631 .map(|v| StyleTabSize { inner: v })
1632 .map_err(StyleTabSizeParseError::PixelValue)
1633 },
1634 |number| {
1635 Ok(StyleTabSize {
1636 inner: PixelValue::em(number),
1637 })
1638 },
1639 )
1640}
1641
1642#[cfg(feature = "parser")]
1643#[derive(Clone, PartialEq, Eq)]
1644pub enum StyleWhiteSpaceParseError<'a> {
1645 InvalidValue(InvalidValueErr<'a>),
1646}
1647#[cfg(feature = "parser")]
1648impl_debug_as_display!(StyleWhiteSpaceParseError<'a>);
1649#[cfg(feature = "parser")]
1650impl_display! { StyleWhiteSpaceParseError<'a>, {
1651 InvalidValue(e) => format!("Invalid white-space value: \"{}\"", e.0),
1652}}
1653#[cfg(feature = "parser")]
1654impl_from!(InvalidValueErr<'a>, StyleWhiteSpaceParseError::InvalidValue);
1655
1656#[cfg(feature = "parser")]
1657#[derive(Debug, Clone, PartialEq, Eq)]
1658#[repr(C, u8)]
1659pub enum StyleWhiteSpaceParseErrorOwned {
1660 InvalidValue(InvalidValueErrOwned),
1661}
1662
1663#[cfg(feature = "parser")]
1664impl StyleWhiteSpaceParseError<'_> {
1665 #[must_use] pub fn to_contained(&self) -> StyleWhiteSpaceParseErrorOwned {
1666 match self {
1667 Self::InvalidValue(e) => StyleWhiteSpaceParseErrorOwned::InvalidValue(e.to_contained()),
1668 }
1669 }
1670}
1671
1672#[cfg(feature = "parser")]
1673impl StyleWhiteSpaceParseErrorOwned {
1674 #[must_use] pub fn to_shared(&self) -> StyleWhiteSpaceParseError<'_> {
1675 match self {
1676 Self::InvalidValue(e) => StyleWhiteSpaceParseError::InvalidValue(e.to_shared()),
1677 }
1678 }
1679}
1680
1681#[cfg(feature = "parser")]
1682pub fn parse_style_white_space(input: &str) -> Result<StyleWhiteSpace, StyleWhiteSpaceParseError<'_>> {
1686 match input.trim() {
1687 "normal" => Ok(StyleWhiteSpace::Normal),
1688 "pre" => Ok(StyleWhiteSpace::Pre),
1689 "nowrap" | "no-wrap" => Ok(StyleWhiteSpace::Nowrap),
1690 "pre-wrap" => Ok(StyleWhiteSpace::PreWrap),
1691 "pre-line" => Ok(StyleWhiteSpace::PreLine),
1692 "break-spaces" => Ok(StyleWhiteSpace::BreakSpaces),
1693 other => Err(StyleWhiteSpaceParseError::InvalidValue(InvalidValueErr(
1694 other,
1695 ))),
1696 }
1697}
1698
1699#[cfg(feature = "parser")]
1700#[derive(Clone, PartialEq, Eq)]
1701pub enum StyleHyphensParseError<'a> {
1702 InvalidValue(InvalidValueErr<'a>),
1703}
1704#[cfg(feature = "parser")]
1705impl_debug_as_display!(StyleHyphensParseError<'a>);
1706#[cfg(feature = "parser")]
1707impl_display! { StyleHyphensParseError<'a>, {
1708 InvalidValue(e) => format!("Invalid hyphens value: \"{}\"", e.0),
1709}}
1710#[cfg(feature = "parser")]
1711impl_from!(InvalidValueErr<'a>, StyleHyphensParseError::InvalidValue);
1712
1713#[cfg(feature = "parser")]
1714#[derive(Debug, Clone, PartialEq, Eq)]
1715#[repr(C, u8)]
1716pub enum StyleHyphensParseErrorOwned {
1717 InvalidValue(InvalidValueErrOwned),
1718}
1719
1720#[cfg(feature = "parser")]
1721impl StyleHyphensParseError<'_> {
1722 #[must_use] pub fn to_contained(&self) -> StyleHyphensParseErrorOwned {
1723 match self {
1724 Self::InvalidValue(e) => StyleHyphensParseErrorOwned::InvalidValue(e.to_contained()),
1725 }
1726 }
1727}
1728
1729#[cfg(feature = "parser")]
1730impl StyleHyphensParseErrorOwned {
1731 #[must_use] pub fn to_shared(&self) -> StyleHyphensParseError<'_> {
1732 match self {
1733 Self::InvalidValue(e) => StyleHyphensParseError::InvalidValue(e.to_shared()),
1734 }
1735 }
1736}
1737
1738#[cfg(feature = "parser")]
1739pub fn parse_style_hyphens(input: &str) -> Result<StyleHyphens, StyleHyphensParseError<'_>> {
1743 match input.trim() {
1744 "none" => Ok(StyleHyphens::None),
1745 "manual" => Ok(StyleHyphens::Manual),
1746 "auto" => Ok(StyleHyphens::Auto),
1747 other => Err(StyleHyphensParseError::InvalidValue(InvalidValueErr(other))),
1748 }
1749}
1750
1751#[cfg(feature = "parser")]
1754#[derive(Clone, PartialEq, Eq)]
1755pub enum StyleLineBreakParseError<'a> {
1756 InvalidValue(InvalidValueErr<'a>),
1757}
1758#[cfg(feature = "parser")]
1759impl_debug_as_display!(StyleLineBreakParseError<'a>);
1760#[cfg(feature = "parser")]
1761impl_display! { StyleLineBreakParseError<'a>, {
1762 InvalidValue(e) => format!("Invalid line-break value: \"{}\"", e.0),
1763}}
1764#[cfg(feature = "parser")]
1765impl_from!(InvalidValueErr<'a>, StyleLineBreakParseError::InvalidValue);
1766
1767#[cfg(feature = "parser")]
1768#[derive(Debug, Clone, PartialEq, Eq)]
1769#[repr(C, u8)]
1770pub enum StyleLineBreakParseErrorOwned {
1771 InvalidValue(InvalidValueErrOwned),
1772}
1773
1774#[cfg(feature = "parser")]
1775impl StyleLineBreakParseError<'_> {
1776 #[must_use] pub fn to_contained(&self) -> StyleLineBreakParseErrorOwned {
1777 match self {
1778 Self::InvalidValue(e) => StyleLineBreakParseErrorOwned::InvalidValue(e.to_contained()),
1779 }
1780 }
1781}
1782
1783#[cfg(feature = "parser")]
1784impl StyleLineBreakParseErrorOwned {
1785 #[must_use] pub fn to_shared(&self) -> StyleLineBreakParseError<'_> {
1786 match self {
1787 Self::InvalidValue(e) => StyleLineBreakParseError::InvalidValue(e.to_shared()),
1788 }
1789 }
1790}
1791
1792#[cfg(feature = "parser")]
1793pub fn parse_style_line_break(input: &str) -> Result<StyleLineBreak, StyleLineBreakParseError<'_>> {
1797 match input.trim() {
1798 "auto" => Ok(StyleLineBreak::Auto),
1799 "loose" => Ok(StyleLineBreak::Loose),
1800 "normal" => Ok(StyleLineBreak::Normal),
1801 "strict" => Ok(StyleLineBreak::Strict),
1802 "anywhere" => Ok(StyleLineBreak::Anywhere),
1803 other => Err(StyleLineBreakParseError::InvalidValue(InvalidValueErr(other))),
1804 }
1805}
1806
1807#[cfg(feature = "parser")]
1810#[derive(Clone, PartialEq, Eq)]
1811pub enum StyleWordBreakParseError<'a> {
1812 InvalidValue(InvalidValueErr<'a>),
1813}
1814#[cfg(feature = "parser")]
1815impl_debug_as_display!(StyleWordBreakParseError<'a>);
1816#[cfg(feature = "parser")]
1817impl_display! { StyleWordBreakParseError<'a>, {
1818 InvalidValue(e) => format!("Invalid word-break value: \"{}\"", e.0),
1819}}
1820#[cfg(feature = "parser")]
1821impl_from!(InvalidValueErr<'a>, StyleWordBreakParseError::InvalidValue);
1822
1823#[cfg(feature = "parser")]
1824#[derive(Debug, Clone, PartialEq, Eq)]
1825#[repr(C, u8)]
1826pub enum StyleWordBreakParseErrorOwned {
1827 InvalidValue(InvalidValueErrOwned),
1828}
1829
1830#[cfg(feature = "parser")]
1831impl StyleWordBreakParseError<'_> {
1832 #[must_use] pub fn to_contained(&self) -> StyleWordBreakParseErrorOwned {
1833 match self {
1834 Self::InvalidValue(e) => StyleWordBreakParseErrorOwned::InvalidValue(e.to_contained()),
1835 }
1836 }
1837}
1838
1839#[cfg(feature = "parser")]
1840impl StyleWordBreakParseErrorOwned {
1841 #[must_use] pub fn to_shared(&self) -> StyleWordBreakParseError<'_> {
1842 match self {
1843 Self::InvalidValue(e) => StyleWordBreakParseError::InvalidValue(e.to_shared()),
1844 }
1845 }
1846}
1847
1848#[cfg(feature = "parser")]
1849pub fn parse_style_word_break(input: &str) -> Result<StyleWordBreak, StyleWordBreakParseError<'_>> {
1853 match input.trim() {
1854 "normal" => Ok(StyleWordBreak::Normal),
1855 "break-all" => Ok(StyleWordBreak::BreakAll),
1856 "keep-all" => Ok(StyleWordBreak::KeepAll),
1857 "break-word" => Ok(StyleWordBreak::BreakWord),
1858 other => Err(StyleWordBreakParseError::InvalidValue(InvalidValueErr(other))),
1859 }
1860}
1861
1862#[cfg(feature = "parser")]
1865#[derive(Clone, PartialEq, Eq)]
1866pub enum StyleOverflowWrapParseError<'a> {
1867 InvalidValue(InvalidValueErr<'a>),
1868}
1869#[cfg(feature = "parser")]
1870impl_debug_as_display!(StyleOverflowWrapParseError<'a>);
1871#[cfg(feature = "parser")]
1872impl_display! { StyleOverflowWrapParseError<'a>, {
1873 InvalidValue(e) => format!("Invalid overflow-wrap value: \"{}\"", e.0),
1874}}
1875#[cfg(feature = "parser")]
1876impl_from!(InvalidValueErr<'a>, StyleOverflowWrapParseError::InvalidValue);
1877
1878#[cfg(feature = "parser")]
1879#[derive(Debug, Clone, PartialEq, Eq)]
1880#[repr(C, u8)]
1881pub enum StyleOverflowWrapParseErrorOwned {
1882 InvalidValue(InvalidValueErrOwned),
1883}
1884
1885#[cfg(feature = "parser")]
1886impl StyleOverflowWrapParseError<'_> {
1887 #[must_use] pub fn to_contained(&self) -> StyleOverflowWrapParseErrorOwned {
1888 match self {
1889 Self::InvalidValue(e) => StyleOverflowWrapParseErrorOwned::InvalidValue(e.to_contained()),
1890 }
1891 }
1892}
1893
1894#[cfg(feature = "parser")]
1895impl StyleOverflowWrapParseErrorOwned {
1896 #[must_use] pub fn to_shared(&self) -> StyleOverflowWrapParseError<'_> {
1897 match self {
1898 Self::InvalidValue(e) => StyleOverflowWrapParseError::InvalidValue(e.to_shared()),
1899 }
1900 }
1901}
1902
1903#[cfg(feature = "parser")]
1904pub fn parse_style_overflow_wrap(input: &str) -> Result<StyleOverflowWrap, StyleOverflowWrapParseError<'_>> {
1908 match input.trim() {
1909 "normal" => Ok(StyleOverflowWrap::Normal),
1910 "anywhere" => Ok(StyleOverflowWrap::Anywhere),
1911 "break-word" => Ok(StyleOverflowWrap::BreakWord),
1912 other => Err(StyleOverflowWrapParseError::InvalidValue(InvalidValueErr(other))),
1913 }
1914}
1915
1916#[cfg(feature = "parser")]
1919#[derive(Clone, PartialEq, Eq)]
1920pub enum StyleTextAlignLastParseError<'a> {
1921 InvalidValue(InvalidValueErr<'a>),
1922}
1923#[cfg(feature = "parser")]
1924impl_debug_as_display!(StyleTextAlignLastParseError<'a>);
1925#[cfg(feature = "parser")]
1926impl_display! { StyleTextAlignLastParseError<'a>, {
1927 InvalidValue(e) => format!("Invalid text-align-last value: \"{}\"", e.0),
1928}}
1929#[cfg(feature = "parser")]
1930impl_from!(InvalidValueErr<'a>, StyleTextAlignLastParseError::InvalidValue);
1931
1932#[cfg(feature = "parser")]
1933#[derive(Debug, Clone, PartialEq, Eq)]
1934#[repr(C, u8)]
1935pub enum StyleTextAlignLastParseErrorOwned {
1936 InvalidValue(InvalidValueErrOwned),
1937}
1938
1939#[cfg(feature = "parser")]
1940impl StyleTextAlignLastParseError<'_> {
1941 #[must_use] pub fn to_contained(&self) -> StyleTextAlignLastParseErrorOwned {
1942 match self {
1943 Self::InvalidValue(e) => StyleTextAlignLastParseErrorOwned::InvalidValue(e.to_contained()),
1944 }
1945 }
1946}
1947
1948#[cfg(feature = "parser")]
1949impl StyleTextAlignLastParseErrorOwned {
1950 #[must_use] pub fn to_shared(&self) -> StyleTextAlignLastParseError<'_> {
1951 match self {
1952 Self::InvalidValue(e) => StyleTextAlignLastParseError::InvalidValue(e.to_shared()),
1953 }
1954 }
1955}
1956
1957#[cfg(feature = "parser")]
1958pub fn parse_style_text_align_last(input: &str) -> Result<StyleTextAlignLast, StyleTextAlignLastParseError<'_>> {
1962 match input.trim() {
1963 "auto" => Ok(StyleTextAlignLast::Auto),
1964 "start" => Ok(StyleTextAlignLast::Start),
1965 "end" => Ok(StyleTextAlignLast::End),
1966 "left" => Ok(StyleTextAlignLast::Left),
1967 "right" => Ok(StyleTextAlignLast::Right),
1968 "center" => Ok(StyleTextAlignLast::Center),
1969 "justify" => Ok(StyleTextAlignLast::Justify),
1970 other => Err(StyleTextAlignLastParseError::InvalidValue(InvalidValueErr(other))),
1971 }
1972}
1973
1974#[cfg(feature = "parser")]
1977#[derive(Clone, PartialEq, Eq)]
1978pub enum StyleTextTransformParseError<'a> {
1979 InvalidValue(InvalidValueErr<'a>),
1980}
1981#[cfg(feature = "parser")]
1982impl_debug_as_display!(StyleTextTransformParseError<'a>);
1983#[cfg(feature = "parser")]
1984impl_display! { StyleTextTransformParseError<'a>, {
1985 InvalidValue(e) => format!("Invalid text-transform value: \"{}\"", e.0),
1986}}
1987#[cfg(feature = "parser")]
1988impl_from!(InvalidValueErr<'a>, StyleTextTransformParseError::InvalidValue);
1989
1990#[cfg(feature = "parser")]
1991#[derive(Debug, Clone, PartialEq, Eq)]
1992#[repr(C, u8)]
1993pub enum StyleTextTransformParseErrorOwned {
1994 InvalidValue(InvalidValueErrOwned),
1995}
1996
1997#[cfg(feature = "parser")]
1998impl StyleTextTransformParseError<'_> {
1999 #[must_use] pub fn to_contained(&self) -> StyleTextTransformParseErrorOwned {
2000 match self {
2001 Self::InvalidValue(e) => StyleTextTransformParseErrorOwned::InvalidValue(e.to_contained()),
2002 }
2003 }
2004}
2005
2006#[cfg(feature = "parser")]
2007impl StyleTextTransformParseErrorOwned {
2008 #[must_use] pub fn to_shared(&self) -> StyleTextTransformParseError<'_> {
2009 match self {
2010 Self::InvalidValue(e) => StyleTextTransformParseError::InvalidValue(e.to_shared()),
2011 }
2012 }
2013}
2014
2015#[cfg(feature = "parser")]
2016pub fn parse_style_text_transform(input: &str) -> Result<StyleTextTransform, StyleTextTransformParseError<'_>> {
2020 match input.trim() {
2021 "none" => Ok(StyleTextTransform::None),
2022 "capitalize" => Ok(StyleTextTransform::Capitalize),
2023 "uppercase" => Ok(StyleTextTransform::Uppercase),
2024 "lowercase" => Ok(StyleTextTransform::Lowercase),
2025 "full-width" => Ok(StyleTextTransform::FullWidth),
2026 other => Err(StyleTextTransformParseError::InvalidValue(InvalidValueErr(other))),
2027 }
2028}
2029
2030#[cfg(feature = "parser")]
2031#[derive(Clone, PartialEq, Eq)]
2032pub enum StyleDirectionParseError<'a> {
2033 InvalidValue(InvalidValueErr<'a>),
2034}
2035#[cfg(feature = "parser")]
2036impl_debug_as_display!(StyleDirectionParseError<'a>);
2037#[cfg(feature = "parser")]
2038impl_display! { StyleDirectionParseError<'a>, {
2039 InvalidValue(e) => format!("Invalid direction value: \"{}\"", e.0),
2040}}
2041#[cfg(feature = "parser")]
2042impl_from!(InvalidValueErr<'a>, StyleDirectionParseError::InvalidValue);
2043
2044#[cfg(feature = "parser")]
2045#[derive(Debug, Clone, PartialEq, Eq)]
2046#[repr(C, u8)]
2047pub enum StyleDirectionParseErrorOwned {
2048 InvalidValue(InvalidValueErrOwned),
2049}
2050
2051#[cfg(feature = "parser")]
2052impl StyleDirectionParseError<'_> {
2053 #[must_use] pub fn to_contained(&self) -> StyleDirectionParseErrorOwned {
2054 match self {
2055 Self::InvalidValue(e) => StyleDirectionParseErrorOwned::InvalidValue(e.to_contained()),
2056 }
2057 }
2058}
2059
2060#[cfg(feature = "parser")]
2061impl StyleDirectionParseErrorOwned {
2062 #[must_use] pub fn to_shared(&self) -> StyleDirectionParseError<'_> {
2063 match self {
2064 Self::InvalidValue(e) => StyleDirectionParseError::InvalidValue(e.to_shared()),
2065 }
2066 }
2067}
2068
2069#[cfg(feature = "parser")]
2070pub fn parse_style_direction(input: &str) -> Result<StyleDirection, StyleDirectionParseError<'_>> {
2074 match input.trim() {
2075 "ltr" => Ok(StyleDirection::Ltr),
2076 "rtl" => Ok(StyleDirection::Rtl),
2077 other => Err(StyleDirectionParseError::InvalidValue(InvalidValueErr(
2078 other,
2079 ))),
2080 }
2081}
2082
2083#[cfg(feature = "parser")]
2084#[derive(Clone, PartialEq, Eq)]
2085pub enum StyleUserSelectParseError<'a> {
2086 InvalidValue(InvalidValueErr<'a>),
2087}
2088#[cfg(feature = "parser")]
2089impl_debug_as_display!(StyleUserSelectParseError<'a>);
2090#[cfg(feature = "parser")]
2091impl_display! { StyleUserSelectParseError<'a>, {
2092 InvalidValue(e) => format!("Invalid user-select value: \"{}\"", e.0),
2093}}
2094#[cfg(feature = "parser")]
2095impl_from!(InvalidValueErr<'a>, StyleUserSelectParseError::InvalidValue);
2096
2097#[cfg(feature = "parser")]
2098#[derive(Debug, Clone, PartialEq, Eq)]
2099#[repr(C, u8)]
2100pub enum StyleUserSelectParseErrorOwned {
2101 InvalidValue(InvalidValueErrOwned),
2102}
2103
2104#[cfg(feature = "parser")]
2105impl StyleUserSelectParseError<'_> {
2106 #[must_use] pub fn to_contained(&self) -> StyleUserSelectParseErrorOwned {
2107 match self {
2108 Self::InvalidValue(e) => StyleUserSelectParseErrorOwned::InvalidValue(e.to_contained()),
2109 }
2110 }
2111}
2112
2113#[cfg(feature = "parser")]
2114impl StyleUserSelectParseErrorOwned {
2115 #[must_use] pub fn to_shared(&self) -> StyleUserSelectParseError<'_> {
2116 match self {
2117 Self::InvalidValue(e) => StyleUserSelectParseError::InvalidValue(e.to_shared()),
2118 }
2119 }
2120}
2121
2122#[cfg(feature = "parser")]
2123pub fn parse_style_user_select(input: &str) -> Result<StyleUserSelect, StyleUserSelectParseError<'_>> {
2127 match input.trim() {
2128 "auto" => Ok(StyleUserSelect::Auto),
2129 "text" => Ok(StyleUserSelect::Text),
2130 "none" => Ok(StyleUserSelect::None),
2131 "all" => Ok(StyleUserSelect::All),
2132 other => Err(StyleUserSelectParseError::InvalidValue(InvalidValueErr(
2133 other,
2134 ))),
2135 }
2136}
2137
2138#[cfg(feature = "parser")]
2139#[derive(Clone, PartialEq, Eq)]
2140pub enum StyleTextDecorationParseError<'a> {
2141 InvalidValue(InvalidValueErr<'a>),
2142}
2143#[cfg(feature = "parser")]
2144impl_debug_as_display!(StyleTextDecorationParseError<'a>);
2145#[cfg(feature = "parser")]
2146impl_display! { StyleTextDecorationParseError<'a>, {
2147 InvalidValue(e) => format!("Invalid text-decoration value: \"{}\"", e.0),
2148}}
2149#[cfg(feature = "parser")]
2150impl_from!(
2151 InvalidValueErr<'a>,
2152 StyleTextDecorationParseError::InvalidValue
2153);
2154
2155#[cfg(feature = "parser")]
2156#[derive(Debug, Clone, PartialEq, Eq)]
2157#[repr(C, u8)]
2158pub enum StyleTextDecorationParseErrorOwned {
2159 InvalidValue(InvalidValueErrOwned),
2160}
2161
2162#[cfg(feature = "parser")]
2163impl StyleTextDecorationParseError<'_> {
2164 #[must_use] pub fn to_contained(&self) -> StyleTextDecorationParseErrorOwned {
2165 match self {
2166 Self::InvalidValue(e) => {
2167 StyleTextDecorationParseErrorOwned::InvalidValue(e.to_contained())
2168 }
2169 }
2170 }
2171}
2172
2173#[cfg(feature = "parser")]
2174impl StyleTextDecorationParseErrorOwned {
2175 #[must_use] pub fn to_shared(&self) -> StyleTextDecorationParseError<'_> {
2176 match self {
2177 Self::InvalidValue(e) => StyleTextDecorationParseError::InvalidValue(e.to_shared()),
2178 }
2179 }
2180}
2181
2182#[cfg(feature = "parser")]
2183pub fn parse_style_text_decoration(
2187 input: &str,
2188) -> Result<StyleTextDecoration, StyleTextDecorationParseError<'_>> {
2189 match input.trim() {
2190 "none" => Ok(StyleTextDecoration::None),
2191 "underline" => Ok(StyleTextDecoration::Underline),
2192 "overline" => Ok(StyleTextDecoration::Overline),
2193 "line-through" => Ok(StyleTextDecoration::LineThrough),
2194 other => Err(StyleTextDecorationParseError::InvalidValue(
2195 InvalidValueErr(other),
2196 )),
2197 }
2198}
2199
2200#[cfg(feature = "parser")]
2201#[derive(Clone, PartialEq, Eq)]
2202pub enum StyleVerticalAlignParseError<'a> {
2203 InvalidValue(InvalidValueErr<'a>),
2204}
2205#[cfg(feature = "parser")]
2206impl_debug_as_display!(StyleVerticalAlignParseError<'a>);
2207#[cfg(feature = "parser")]
2208impl_display! { StyleVerticalAlignParseError<'a>, {
2209 InvalidValue(e) => format!("Invalid vertical-align value: \"{}\"", e.0),
2210}}
2211#[cfg(feature = "parser")]
2212impl_from!(
2213 InvalidValueErr<'a>,
2214 StyleVerticalAlignParseError::InvalidValue
2215);
2216
2217#[cfg(feature = "parser")]
2218#[derive(Debug, Clone, PartialEq, Eq)]
2219#[repr(C, u8)]
2220pub enum StyleVerticalAlignParseErrorOwned {
2221 InvalidValue(InvalidValueErrOwned),
2222}
2223
2224#[cfg(feature = "parser")]
2225impl StyleVerticalAlignParseError<'_> {
2226 #[must_use] pub fn to_contained(&self) -> StyleVerticalAlignParseErrorOwned {
2227 match self {
2228 Self::InvalidValue(e) => {
2229 StyleVerticalAlignParseErrorOwned::InvalidValue(e.to_contained())
2230 }
2231 }
2232 }
2233}
2234
2235#[cfg(feature = "parser")]
2236impl StyleVerticalAlignParseErrorOwned {
2237 #[must_use] pub fn to_shared(&self) -> StyleVerticalAlignParseError<'_> {
2238 match self {
2239 Self::InvalidValue(e) => StyleVerticalAlignParseError::InvalidValue(e.to_shared()),
2240 }
2241 }
2242}
2243
2244#[cfg(feature = "parser")]
2245pub fn parse_style_vertical_align(
2249 input: &str,
2250) -> Result<StyleVerticalAlign, StyleVerticalAlignParseError<'_>> {
2251 match input.trim() {
2252 "baseline" => Ok(StyleVerticalAlign::Baseline),
2253 "top" => Ok(StyleVerticalAlign::Top),
2254 "middle" => Ok(StyleVerticalAlign::Middle),
2255 "bottom" => Ok(StyleVerticalAlign::Bottom),
2256 "sub" => Ok(StyleVerticalAlign::Sub),
2257 "super" => Ok(StyleVerticalAlign::Superscript),
2258 "text-top" => Ok(StyleVerticalAlign::TextTop),
2259 "text-bottom" => Ok(StyleVerticalAlign::TextBottom),
2260 other if other.ends_with('%') => {
2261 let num_str = other.trim_end_matches('%').trim();
2262 num_str.parse::<f32>().map_or_else(
2263 |_| Err(StyleVerticalAlignParseError::InvalidValue(InvalidValueErr(other))),
2264 |val| Ok(StyleVerticalAlign::Percentage(PercentageValue::new(val))),
2265 )
2266 }
2267 other => crate::props::basic::pixel::parse_pixel_value(other).map_or_else(
2268 |_| Err(StyleVerticalAlignParseError::InvalidValue(InvalidValueErr(other))),
2269 |pv| Ok(StyleVerticalAlign::Length(pv)),
2270 ),
2271 }
2272}
2273
2274#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2277#[repr(C)]
2278pub struct CaretColor {
2279 pub inner: ColorU,
2280}
2281
2282impl Default for CaretColor {
2283 fn default() -> Self {
2284 Self {
2285 inner: ColorU::BLACK,
2286 }
2287 }
2288}
2289
2290impl PrintAsCssValue for CaretColor {
2291 fn print_as_css_value(&self) -> String {
2292 self.inner.to_hash()
2293 }
2294}
2295
2296impl FormatAsRustCode for CaretColor {
2297 fn format_as_rust_code(&self, _tabs: usize) -> String {
2298 format!(
2299 "CaretColor {{ inner: {} }}",
2300 crate::codegen::format::format_color_value(&self.inner)
2301 )
2302 }
2303}
2304
2305#[cfg(feature = "parser")]
2306pub fn parse_caret_color(input: &str) -> Result<CaretColor, CssColorParseError<'_>> {
2310 parse_css_color(input).map(|inner| CaretColor { inner })
2311}
2312
2313#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2316#[repr(C)]
2317pub struct CaretAnimationDuration {
2318 pub inner: CssDuration,
2319}
2320
2321impl Default for CaretAnimationDuration {
2322 fn default() -> Self {
2323 Self {
2324 inner: CssDuration::from_millis(500),
2325 } }
2327}
2328
2329impl PrintAsCssValue for CaretAnimationDuration {
2330 fn print_as_css_value(&self) -> String {
2331 self.inner.print_as_css_value()
2332 }
2333}
2334
2335impl FormatAsRustCode for CaretAnimationDuration {
2336 fn format_as_rust_code(&self, _tabs: usize) -> String {
2337 format!(
2338 "CaretAnimationDuration {{ inner: {} }}",
2339 self.inner.format_as_rust_code(0)
2340 )
2341 }
2342}
2343
2344#[cfg(feature = "parser")]
2345pub fn parse_caret_animation_duration(
2349 input: &str,
2350) -> Result<CaretAnimationDuration, DurationParseError<'_>> {
2351 use crate::props::basic::parse_duration;
2352
2353 parse_duration(input).map(|inner| CaretAnimationDuration { inner })
2354}
2355
2356#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2361#[repr(C)]
2362pub struct CaretWidth {
2363 pub inner: PixelValue,
2364}
2365
2366impl Default for CaretWidth {
2367 fn default() -> Self {
2368 Self {
2369 inner: PixelValue::px(2.0), }
2371 }
2372}
2373
2374impl PrintAsCssValue for CaretWidth {
2375 fn print_as_css_value(&self) -> String {
2376 self.inner.print_as_css_value()
2377 }
2378}
2379
2380impl FormatAsRustCode for CaretWidth {
2381 fn format_as_rust_code(&self, _tabs: usize) -> String {
2382 format!(
2383 "CaretWidth {{ inner: {} }}",
2384 self.inner.format_as_rust_code(0)
2385 )
2386 }
2387}
2388
2389#[cfg(feature = "parser")]
2390pub fn parse_caret_width(input: &str) -> Result<CaretWidth, CssPixelValueParseError<'_>> {
2394 use crate::props::basic::pixel::parse_pixel_value;
2395
2396 parse_pixel_value(input).map(|inner| CaretWidth { inner })
2397}
2398
2399impl From<StyleUserSelect> for crate::props::property::CssProperty {
2402 fn from(value: StyleUserSelect) -> Self {
2403 use crate::props::property::CssProperty;
2404 Self::user_select(value)
2405 }
2406}
2407
2408impl From<StyleTextDecoration> for crate::props::property::CssProperty {
2409 fn from(value: StyleTextDecoration) -> Self {
2410 use crate::props::property::CssProperty;
2411 Self::text_decoration(value)
2412 }
2413}
2414
2415#[cfg(all(test, feature = "parser"))]
2416mod tests {
2417 use super::*;
2418 use crate::props::basic::{color::ColorU, length::PercentageValue, pixel::PixelValue};
2419
2420 #[test]
2421 fn test_parse_style_text_color() {
2422 assert_eq!(
2423 parse_style_text_color("red").unwrap().inner,
2424 ColorU::new_rgb(255, 0, 0)
2425 );
2426 assert_eq!(
2427 parse_style_text_color("#aabbcc").unwrap().inner,
2428 ColorU::new_rgb(170, 187, 204)
2429 );
2430 assert!(parse_style_text_color("not-a-color").is_err());
2431 }
2432
2433 #[test]
2434 fn test_parse_style_text_align() {
2435 assert_eq!(
2436 parse_style_text_align("left").unwrap(),
2437 StyleTextAlign::Left
2438 );
2439 assert_eq!(
2440 parse_style_text_align("center").unwrap(),
2441 StyleTextAlign::Center
2442 );
2443 assert_eq!(
2444 parse_style_text_align("right").unwrap(),
2445 StyleTextAlign::Right
2446 );
2447 assert_eq!(
2448 parse_style_text_align("justify").unwrap(),
2449 StyleTextAlign::Justify
2450 );
2451 assert_eq!(
2452 parse_style_text_align("start").unwrap(),
2453 StyleTextAlign::Start
2454 );
2455 assert_eq!(parse_style_text_align("end").unwrap(), StyleTextAlign::End);
2456 assert!(parse_style_text_align("middle").is_err());
2457 }
2458
2459 #[test]
2460 fn test_parse_spacing() {
2461 assert_eq!(
2462 parse_style_letter_spacing("2px").unwrap().inner,
2463 PixelValue::px(2.0)
2464 );
2465 assert_eq!(
2466 parse_style_letter_spacing("-0.1em").unwrap().inner,
2467 PixelValue::em(-0.1)
2468 );
2469 assert_eq!(
2470 parse_style_word_spacing("0.5em").unwrap().inner,
2471 PixelValue::em(0.5)
2472 );
2473 }
2474
2475 #[test]
2476 fn test_parse_line_height() {
2477 assert_eq!(
2478 parse_style_line_height("1.5").unwrap().inner,
2479 PercentageValue::new(150.0)
2480 );
2481 assert_eq!(
2482 parse_style_line_height("120%").unwrap().inner,
2483 PercentageValue::new(120.0)
2484 );
2485 assert_eq!(
2487 parse_style_line_height("20px").unwrap().inner,
2488 PercentageValue::new(-20.0 * 100.0)
2489 );
2490 }
2491
2492 #[test]
2493 fn test_parse_tab_size() {
2494 assert_eq!(
2496 parse_style_tab_size("4").unwrap().inner,
2497 PixelValue::em(4.0)
2498 );
2499 assert_eq!(
2500 parse_style_tab_size("20px").unwrap().inner,
2501 PixelValue::px(20.0)
2502 );
2503 }
2504
2505 #[test]
2506 fn test_parse_white_space() {
2507 assert_eq!(
2508 parse_style_white_space("normal").unwrap(),
2509 StyleWhiteSpace::Normal
2510 );
2511 assert_eq!(
2512 parse_style_white_space("pre").unwrap(),
2513 StyleWhiteSpace::Pre
2514 );
2515 assert_eq!(
2516 parse_style_white_space("nowrap").unwrap(),
2517 StyleWhiteSpace::Nowrap
2518 );
2519 assert_eq!(
2520 parse_style_white_space("pre-wrap").unwrap(),
2521 StyleWhiteSpace::PreWrap
2522 );
2523 }
2524}
2525
2526#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2532#[repr(C)]
2533#[derive(Default)]
2534pub enum StyleUnicodeBidi {
2535 #[default]
2537 Normal,
2538 Embed,
2540 Isolate,
2542 BidiOverride,
2544 IsolateOverride,
2546 Plaintext,
2548}
2549impl_option!(
2550 StyleUnicodeBidi,
2551 OptionStyleUnicodeBidi,
2552 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
2553);
2554impl PrintAsCssValue for StyleUnicodeBidi {
2555 fn print_as_css_value(&self) -> String {
2556 String::from(match self {
2557 Self::Normal => "normal",
2558 Self::Embed => "embed",
2559 Self::Isolate => "isolate",
2560 Self::BidiOverride => "bidi-override",
2561 Self::IsolateOverride => "isolate-override",
2562 Self::Plaintext => "plaintext",
2563 })
2564 }
2565}
2566
2567#[cfg(feature = "parser")]
2568#[derive(Clone, PartialEq, Eq)]
2569pub enum StyleUnicodeBidiParseError<'a> {
2570 InvalidValue(InvalidValueErr<'a>),
2571}
2572#[cfg(feature = "parser")]
2573impl_debug_as_display!(StyleUnicodeBidiParseError<'a>);
2574#[cfg(feature = "parser")]
2575impl_display! { StyleUnicodeBidiParseError<'a>, {
2576 InvalidValue(e) => format!("Invalid unicode-bidi value: \"{}\"", e.0),
2577}}
2578#[cfg(feature = "parser")]
2579impl_from!(InvalidValueErr<'a>, StyleUnicodeBidiParseError::InvalidValue);
2580
2581#[cfg(feature = "parser")]
2582#[derive(Debug, Clone, PartialEq, Eq)]
2583#[repr(C, u8)]
2584pub enum StyleUnicodeBidiParseErrorOwned {
2585 InvalidValue(InvalidValueErrOwned),
2586}
2587
2588#[cfg(feature = "parser")]
2589impl StyleUnicodeBidiParseError<'_> {
2590 #[must_use] pub fn to_contained(&self) -> StyleUnicodeBidiParseErrorOwned {
2591 match self {
2592 Self::InvalidValue(e) => StyleUnicodeBidiParseErrorOwned::InvalidValue(e.to_contained()),
2593 }
2594 }
2595}
2596
2597#[cfg(feature = "parser")]
2598impl StyleUnicodeBidiParseErrorOwned {
2599 #[must_use] pub fn to_shared(&self) -> StyleUnicodeBidiParseError<'_> {
2600 match self {
2601 Self::InvalidValue(e) => StyleUnicodeBidiParseError::InvalidValue(e.to_shared()),
2602 }
2603 }
2604}
2605
2606#[cfg(feature = "parser")]
2607pub fn parse_style_unicode_bidi(input: &str) -> Result<StyleUnicodeBidi, StyleUnicodeBidiParseError<'_>> {
2611 match input.trim() {
2612 "normal" => Ok(StyleUnicodeBidi::Normal),
2613 "embed" => Ok(StyleUnicodeBidi::Embed),
2614 "isolate" => Ok(StyleUnicodeBidi::Isolate),
2615 "bidi-override" => Ok(StyleUnicodeBidi::BidiOverride),
2616 "isolate-override" => Ok(StyleUnicodeBidi::IsolateOverride),
2617 "plaintext" => Ok(StyleUnicodeBidi::Plaintext),
2618 other => Err(StyleUnicodeBidiParseError::InvalidValue(InvalidValueErr(other))),
2619 }
2620}
2621
2622#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2628#[repr(C)]
2629#[derive(Default)]
2630pub enum StyleTextBoxTrim {
2631 #[default]
2633 None,
2634 TrimStart,
2636 TrimEnd,
2638 TrimBoth,
2640}
2641impl_option!(
2642 StyleTextBoxTrim,
2643 OptionStyleTextBoxTrim,
2644 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
2645);
2646impl PrintAsCssValue for StyleTextBoxTrim {
2647 fn print_as_css_value(&self) -> String {
2648 String::from(match self {
2649 Self::None => "none",
2650 Self::TrimStart => "trim-start",
2651 Self::TrimEnd => "trim-end",
2652 Self::TrimBoth => "trim-both",
2653 })
2654 }
2655}
2656
2657#[cfg(feature = "parser")]
2658#[derive(Clone, PartialEq, Eq)]
2659pub enum StyleTextBoxTrimParseError<'a> {
2660 InvalidValue(InvalidValueErr<'a>),
2661}
2662#[cfg(feature = "parser")]
2663impl_debug_as_display!(StyleTextBoxTrimParseError<'a>);
2664#[cfg(feature = "parser")]
2665impl_display! { StyleTextBoxTrimParseError<'a>, {
2666 InvalidValue(e) => format!("Invalid text-box-trim value: \"{}\"", e.0),
2667}}
2668#[cfg(feature = "parser")]
2669impl_from!(InvalidValueErr<'a>, StyleTextBoxTrimParseError::InvalidValue);
2670
2671#[cfg(feature = "parser")]
2672#[derive(Debug, Clone, PartialEq, Eq)]
2673#[repr(C, u8)]
2674pub enum StyleTextBoxTrimParseErrorOwned {
2675 InvalidValue(InvalidValueErrOwned),
2676}
2677
2678#[cfg(feature = "parser")]
2679impl StyleTextBoxTrimParseError<'_> {
2680 #[must_use] pub fn to_contained(&self) -> StyleTextBoxTrimParseErrorOwned {
2681 match self {
2682 Self::InvalidValue(e) => StyleTextBoxTrimParseErrorOwned::InvalidValue(e.to_contained()),
2683 }
2684 }
2685}
2686
2687#[cfg(feature = "parser")]
2688impl StyleTextBoxTrimParseErrorOwned {
2689 #[must_use] pub fn to_shared(&self) -> StyleTextBoxTrimParseError<'_> {
2690 match self {
2691 Self::InvalidValue(e) => StyleTextBoxTrimParseError::InvalidValue(e.to_shared()),
2692 }
2693 }
2694}
2695
2696#[cfg(feature = "parser")]
2697pub fn parse_style_text_box_trim(input: &str) -> Result<StyleTextBoxTrim, StyleTextBoxTrimParseError<'_>> {
2701 match input.trim() {
2702 "none" => Ok(StyleTextBoxTrim::None),
2703 "trim-start" => Ok(StyleTextBoxTrim::TrimStart),
2704 "trim-end" => Ok(StyleTextBoxTrim::TrimEnd),
2705 "trim-both" => Ok(StyleTextBoxTrim::TrimBoth),
2706 other => Err(StyleTextBoxTrimParseError::InvalidValue(InvalidValueErr(other))),
2707 }
2708}
2709
2710#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
2714#[repr(C)]
2715pub enum TextBoxEdgeOver {
2716 #[default]
2720 Auto,
2721 Text,
2723 Cap,
2725 Ex,
2727 Ideographic,
2729 IdeographicInk,
2731}
2732
2733#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
2735#[repr(C)]
2736pub enum TextBoxEdgeUnder {
2737 #[default]
2739 Auto,
2740 Text,
2742 Alphabetic,
2744 Ideographic,
2746 IdeographicInk,
2748}
2749
2750#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
2762#[repr(C)]
2763pub struct StyleTextBoxEdge {
2764 pub over: TextBoxEdgeOver,
2765 pub under: TextBoxEdgeUnder,
2766}
2767
2768impl StyleTextBoxEdge {
2769 pub const AUTO: Self = Self {
2771 over: TextBoxEdgeOver::Auto,
2772 under: TextBoxEdgeUnder::Auto,
2773 };
2774}
2775impl_option!(
2776 StyleTextBoxEdge,
2777 OptionStyleTextBoxEdge,
2778 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
2779);
2780impl PrintAsCssValue for StyleTextBoxEdge {
2781 fn print_as_css_value(&self) -> String {
2782 let over = match self.over {
2783 TextBoxEdgeOver::Auto => return String::from("auto"),
2784 TextBoxEdgeOver::Text => "text",
2785 TextBoxEdgeOver::Cap => "cap",
2786 TextBoxEdgeOver::Ex => "ex",
2787 TextBoxEdgeOver::Ideographic => "ideographic",
2788 TextBoxEdgeOver::IdeographicInk => "ideographic-ink",
2789 };
2790 let under = match self.under {
2791 TextBoxEdgeUnder::Auto | TextBoxEdgeUnder::Text => "text",
2792 TextBoxEdgeUnder::Alphabetic => "alphabetic",
2793 TextBoxEdgeUnder::Ideographic => "ideographic",
2794 TextBoxEdgeUnder::IdeographicInk => "ideographic-ink",
2795 };
2796 let single_round_trips = match self.over {
2799 TextBoxEdgeOver::Text => matches!(self.under, TextBoxEdgeUnder::Text | TextBoxEdgeUnder::Auto),
2800 TextBoxEdgeOver::Cap | TextBoxEdgeOver::Ex => {
2801 matches!(self.under, TextBoxEdgeUnder::Text | TextBoxEdgeUnder::Auto)
2802 }
2803 TextBoxEdgeOver::Ideographic => matches!(self.under, TextBoxEdgeUnder::Ideographic),
2804 TextBoxEdgeOver::IdeographicInk => {
2805 matches!(self.under, TextBoxEdgeUnder::IdeographicInk)
2806 }
2807 TextBoxEdgeOver::Auto => unreachable!(),
2808 };
2809 if single_round_trips {
2810 String::from(over)
2811 } else {
2812 alloc::format!("{over} {under}")
2813 }
2814 }
2815}
2816
2817#[cfg(feature = "parser")]
2818#[derive(Clone, PartialEq, Eq)]
2819pub enum StyleTextBoxEdgeParseError<'a> {
2820 InvalidValue(InvalidValueErr<'a>),
2821}
2822#[cfg(feature = "parser")]
2823impl_debug_as_display!(StyleTextBoxEdgeParseError<'a>);
2824#[cfg(feature = "parser")]
2825impl_display! { StyleTextBoxEdgeParseError<'a>, {
2826 InvalidValue(e) => format!("Invalid text-box-edge value: \"{}\"", e.0),
2827}}
2828#[cfg(feature = "parser")]
2829impl_from!(InvalidValueErr<'a>, StyleTextBoxEdgeParseError::InvalidValue);
2830
2831#[cfg(feature = "parser")]
2832#[derive(Debug, Clone, PartialEq, Eq)]
2833#[repr(C, u8)]
2834pub enum StyleTextBoxEdgeParseErrorOwned {
2835 InvalidValue(InvalidValueErrOwned),
2836}
2837
2838#[cfg(feature = "parser")]
2839impl StyleTextBoxEdgeParseError<'_> {
2840 #[must_use] pub fn to_contained(&self) -> StyleTextBoxEdgeParseErrorOwned {
2841 match self {
2842 Self::InvalidValue(e) => StyleTextBoxEdgeParseErrorOwned::InvalidValue(e.to_contained()),
2843 }
2844 }
2845}
2846
2847#[cfg(feature = "parser")]
2848impl StyleTextBoxEdgeParseErrorOwned {
2849 #[must_use] pub fn to_shared(&self) -> StyleTextBoxEdgeParseError<'_> {
2850 match self {
2851 Self::InvalidValue(e) => StyleTextBoxEdgeParseError::InvalidValue(e.to_shared()),
2852 }
2853 }
2854}
2855
2856#[cfg(feature = "parser")]
2857pub fn parse_style_text_box_edge(input: &str) -> Result<StyleTextBoxEdge, StyleTextBoxEdgeParseError<'_>> {
2861 let trimmed = input.trim();
2862 let mut parts = trimmed.split_whitespace();
2863 let first = parts.next().unwrap_or("");
2864 let second = parts.next();
2865 if parts.next().is_some() {
2866 return Err(StyleTextBoxEdgeParseError::InvalidValue(InvalidValueErr(input)));
2867 }
2868
2869 let over = match first {
2870 "auto" => {
2871 if second.is_some() {
2873 return Err(StyleTextBoxEdgeParseError::InvalidValue(InvalidValueErr(input)));
2874 }
2875 return Ok(StyleTextBoxEdge::AUTO);
2876 }
2877 "text" => TextBoxEdgeOver::Text,
2878 "cap" => TextBoxEdgeOver::Cap,
2879 "ex" => TextBoxEdgeOver::Ex,
2880 "ideographic" => TextBoxEdgeOver::Ideographic,
2881 "ideographic-ink" => TextBoxEdgeOver::IdeographicInk,
2882 other => return Err(StyleTextBoxEdgeParseError::InvalidValue(InvalidValueErr(other))),
2883 };
2884
2885 let under = match second {
2886 Some("text") => TextBoxEdgeUnder::Text,
2887 Some("alphabetic") => TextBoxEdgeUnder::Alphabetic,
2888 Some("ideographic") => TextBoxEdgeUnder::Ideographic,
2889 Some("ideographic-ink") => TextBoxEdgeUnder::IdeographicInk,
2890 Some(other) => {
2891 return Err(StyleTextBoxEdgeParseError::InvalidValue(InvalidValueErr(other)))
2892 }
2893 None => match over {
2896 TextBoxEdgeOver::Ideographic => TextBoxEdgeUnder::Ideographic,
2897 TextBoxEdgeOver::IdeographicInk => TextBoxEdgeUnder::IdeographicInk,
2898 _ => TextBoxEdgeUnder::Text,
2899 },
2900 };
2901
2902 Ok(StyleTextBoxEdge { over, under })
2903}
2904
2905#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2911#[repr(C)]
2912#[derive(Default)]
2913pub enum StyleDominantBaseline {
2914 #[default]
2916 Auto,
2917 TextBottom,
2919 Alphabetic,
2921 Ideographic,
2923 Middle,
2925 Central,
2927 Mathematical,
2929 Hanging,
2931 TextTop,
2933}
2934impl_option!(
2935 StyleDominantBaseline,
2936 OptionStyleDominantBaseline,
2937 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
2938);
2939impl PrintAsCssValue for StyleDominantBaseline {
2940 fn print_as_css_value(&self) -> String {
2941 String::from(match self {
2942 Self::Auto => "auto",
2943 Self::TextBottom => "text-bottom",
2944 Self::Alphabetic => "alphabetic",
2945 Self::Ideographic => "ideographic",
2946 Self::Middle => "middle",
2947 Self::Central => "central",
2948 Self::Mathematical => "mathematical",
2949 Self::Hanging => "hanging",
2950 Self::TextTop => "text-top",
2951 })
2952 }
2953}
2954
2955#[cfg(feature = "parser")]
2956#[derive(Clone, PartialEq, Eq)]
2957pub enum StyleDominantBaselineParseError<'a> {
2958 InvalidValue(InvalidValueErr<'a>),
2959}
2960#[cfg(feature = "parser")]
2961impl_debug_as_display!(StyleDominantBaselineParseError<'a>);
2962#[cfg(feature = "parser")]
2963impl_display! { StyleDominantBaselineParseError<'a>, {
2964 InvalidValue(e) => format!("Invalid dominant-baseline value: \"{}\"", e.0),
2965}}
2966#[cfg(feature = "parser")]
2967impl_from!(InvalidValueErr<'a>, StyleDominantBaselineParseError::InvalidValue);
2968
2969#[cfg(feature = "parser")]
2970#[derive(Debug, Clone, PartialEq, Eq)]
2971#[repr(C, u8)]
2972pub enum StyleDominantBaselineParseErrorOwned {
2973 InvalidValue(InvalidValueErrOwned),
2974}
2975
2976#[cfg(feature = "parser")]
2977impl StyleDominantBaselineParseError<'_> {
2978 #[must_use] pub fn to_contained(&self) -> StyleDominantBaselineParseErrorOwned {
2979 match self {
2980 Self::InvalidValue(e) => StyleDominantBaselineParseErrorOwned::InvalidValue(e.to_contained()),
2981 }
2982 }
2983}
2984
2985#[cfg(feature = "parser")]
2986impl StyleDominantBaselineParseErrorOwned {
2987 #[must_use] pub fn to_shared(&self) -> StyleDominantBaselineParseError<'_> {
2988 match self {
2989 Self::InvalidValue(e) => StyleDominantBaselineParseError::InvalidValue(e.to_shared()),
2990 }
2991 }
2992}
2993
2994#[cfg(feature = "parser")]
2995pub fn parse_style_dominant_baseline(input: &str) -> Result<StyleDominantBaseline, StyleDominantBaselineParseError<'_>> {
2999 match input.trim() {
3000 "auto" => Ok(StyleDominantBaseline::Auto),
3001 "text-bottom" => Ok(StyleDominantBaseline::TextBottom),
3002 "alphabetic" => Ok(StyleDominantBaseline::Alphabetic),
3003 "ideographic" => Ok(StyleDominantBaseline::Ideographic),
3004 "middle" => Ok(StyleDominantBaseline::Middle),
3005 "central" => Ok(StyleDominantBaseline::Central),
3006 "mathematical" => Ok(StyleDominantBaseline::Mathematical),
3007 "hanging" => Ok(StyleDominantBaseline::Hanging),
3008 "text-top" => Ok(StyleDominantBaseline::TextTop),
3009 other => Err(StyleDominantBaselineParseError::InvalidValue(InvalidValueErr(other))),
3010 }
3011}
3012
3013#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
3023#[repr(C)]
3024#[derive(Default)]
3025pub enum StyleAlignmentBaseline {
3026 #[default]
3028 Baseline,
3029 TextBottom,
3031 Alphabetic,
3033 Ideographic,
3035 Middle,
3037 Central,
3039 Mathematical,
3041 TextTop,
3043}
3044impl_option!(
3045 StyleAlignmentBaseline,
3046 OptionStyleAlignmentBaseline,
3047 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
3048);
3049impl PrintAsCssValue for StyleAlignmentBaseline {
3050 fn print_as_css_value(&self) -> String {
3051 String::from(match self {
3052 Self::Baseline => "baseline",
3053 Self::TextBottom => "text-bottom",
3054 Self::Alphabetic => "alphabetic",
3055 Self::Ideographic => "ideographic",
3056 Self::Middle => "middle",
3057 Self::Central => "central",
3058 Self::Mathematical => "mathematical",
3059 Self::TextTop => "text-top",
3060 })
3061 }
3062}
3063
3064#[cfg(feature = "parser")]
3065#[derive(Clone, PartialEq, Eq)]
3066pub enum StyleAlignmentBaselineParseError<'a> {
3067 InvalidValue(InvalidValueErr<'a>),
3068}
3069#[cfg(feature = "parser")]
3070impl_debug_as_display!(StyleAlignmentBaselineParseError<'a>);
3071#[cfg(feature = "parser")]
3072impl_display! { StyleAlignmentBaselineParseError<'a>, {
3073 InvalidValue(e) => format!("Invalid alignment-baseline value: \"{}\"", e.0),
3074}}
3075#[cfg(feature = "parser")]
3076impl_from!(InvalidValueErr<'a>, StyleAlignmentBaselineParseError::InvalidValue);
3077
3078#[cfg(feature = "parser")]
3079#[derive(Debug, Clone, PartialEq, Eq)]
3080#[repr(C, u8)]
3081pub enum StyleAlignmentBaselineParseErrorOwned {
3082 InvalidValue(InvalidValueErrOwned),
3083}
3084
3085#[cfg(feature = "parser")]
3086impl StyleAlignmentBaselineParseError<'_> {
3087 #[must_use] pub fn to_contained(&self) -> StyleAlignmentBaselineParseErrorOwned {
3088 match self {
3089 Self::InvalidValue(e) => StyleAlignmentBaselineParseErrorOwned::InvalidValue(e.to_contained()),
3090 }
3091 }
3092}
3093
3094#[cfg(feature = "parser")]
3095impl StyleAlignmentBaselineParseErrorOwned {
3096 #[must_use] pub fn to_shared(&self) -> StyleAlignmentBaselineParseError<'_> {
3097 match self {
3098 Self::InvalidValue(e) => StyleAlignmentBaselineParseError::InvalidValue(e.to_shared()),
3099 }
3100 }
3101}
3102
3103#[cfg(feature = "parser")]
3104pub fn parse_style_alignment_baseline(input: &str) -> Result<StyleAlignmentBaseline, StyleAlignmentBaselineParseError<'_>> {
3108 match input.trim() {
3109 "baseline" => Ok(StyleAlignmentBaseline::Baseline),
3110 "text-bottom" => Ok(StyleAlignmentBaseline::TextBottom),
3111 "alphabetic" => Ok(StyleAlignmentBaseline::Alphabetic),
3112 "ideographic" => Ok(StyleAlignmentBaseline::Ideographic),
3113 "middle" => Ok(StyleAlignmentBaseline::Middle),
3114 "central" => Ok(StyleAlignmentBaseline::Central),
3115 "mathematical" => Ok(StyleAlignmentBaseline::Mathematical),
3116 "text-top" => Ok(StyleAlignmentBaseline::TextTop),
3117 other => Err(StyleAlignmentBaselineParseError::InvalidValue(InvalidValueErr(other))),
3118 }
3119}
3120
3121#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
3129#[repr(C)]
3130#[derive(Default)]
3131pub enum StyleBaselineSource {
3132 #[default]
3134 Auto,
3135 First,
3137 Last,
3139}
3140impl_option!(
3141 StyleBaselineSource,
3142 OptionStyleBaselineSource,
3143 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
3144);
3145impl PrintAsCssValue for StyleBaselineSource {
3146 fn print_as_css_value(&self) -> String {
3147 String::from(match self {
3148 Self::Auto => "auto",
3149 Self::First => "first",
3150 Self::Last => "last",
3151 })
3152 }
3153}
3154
3155#[cfg(feature = "parser")]
3156#[derive(Clone, PartialEq, Eq)]
3157pub enum StyleBaselineSourceParseError<'a> {
3158 InvalidValue(InvalidValueErr<'a>),
3159}
3160#[cfg(feature = "parser")]
3161impl_debug_as_display!(StyleBaselineSourceParseError<'a>);
3162#[cfg(feature = "parser")]
3163impl_display! { StyleBaselineSourceParseError<'a>, {
3164 InvalidValue(e) => format!("Invalid baseline-source value: \"{}\"", e.0),
3165}}
3166#[cfg(feature = "parser")]
3167impl_from!(InvalidValueErr<'a>, StyleBaselineSourceParseError::InvalidValue);
3168
3169#[cfg(feature = "parser")]
3170#[derive(Debug, Clone, PartialEq, Eq)]
3171#[repr(C, u8)]
3172pub enum StyleBaselineSourceParseErrorOwned {
3173 InvalidValue(InvalidValueErrOwned),
3174}
3175
3176#[cfg(feature = "parser")]
3177impl StyleBaselineSourceParseError<'_> {
3178 #[must_use] pub fn to_contained(&self) -> StyleBaselineSourceParseErrorOwned {
3179 match self {
3180 Self::InvalidValue(e) => StyleBaselineSourceParseErrorOwned::InvalidValue(e.to_contained()),
3181 }
3182 }
3183}
3184
3185#[cfg(feature = "parser")]
3186impl StyleBaselineSourceParseErrorOwned {
3187 #[must_use] pub fn to_shared(&self) -> StyleBaselineSourceParseError<'_> {
3188 match self {
3189 Self::InvalidValue(e) => StyleBaselineSourceParseError::InvalidValue(e.to_shared()),
3190 }
3191 }
3192}
3193
3194#[cfg(feature = "parser")]
3195pub fn parse_style_baseline_source(input: &str) -> Result<StyleBaselineSource, StyleBaselineSourceParseError<'_>> {
3199 match input.trim() {
3200 "auto" => Ok(StyleBaselineSource::Auto),
3201 "first" => Ok(StyleBaselineSource::First),
3202 "last" => Ok(StyleBaselineSource::Last),
3203 other => Err(StyleBaselineSourceParseError::InvalidValue(InvalidValueErr(other))),
3204 }
3205}
3206
3207#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
3217#[repr(C)]
3218#[derive(Default)]
3219pub enum StyleLineFitEdge {
3220 #[default]
3222 Leading,
3223 Text,
3225 Cap,
3227 Ex,
3229 Ideographic,
3231 IdeographicInk,
3233 Alphabetic,
3235}
3236impl_option!(
3237 StyleLineFitEdge,
3238 OptionStyleLineFitEdge,
3239 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
3240);
3241impl PrintAsCssValue for StyleLineFitEdge {
3242 fn print_as_css_value(&self) -> String {
3243 String::from(match self {
3244 Self::Leading => "leading",
3245 Self::Text => "text",
3246 Self::Cap => "cap",
3247 Self::Ex => "ex",
3248 Self::Ideographic => "ideographic",
3249 Self::IdeographicInk => "ideographic-ink",
3250 Self::Alphabetic => "alphabetic",
3251 })
3252 }
3253}
3254
3255#[cfg(feature = "parser")]
3256#[derive(Clone, PartialEq, Eq)]
3257pub enum StyleLineFitEdgeParseError<'a> {
3258 InvalidValue(InvalidValueErr<'a>),
3259}
3260#[cfg(feature = "parser")]
3261impl_debug_as_display!(StyleLineFitEdgeParseError<'a>);
3262#[cfg(feature = "parser")]
3263impl_display! { StyleLineFitEdgeParseError<'a>, {
3264 InvalidValue(e) => format!("Invalid line-fit-edge value: \"{}\"", e.0),
3265}}
3266#[cfg(feature = "parser")]
3267impl_from!(InvalidValueErr<'a>, StyleLineFitEdgeParseError::InvalidValue);
3268
3269#[cfg(feature = "parser")]
3270#[derive(Debug, Clone, PartialEq, Eq)]
3271#[repr(C, u8)]
3272pub enum StyleLineFitEdgeParseErrorOwned {
3273 InvalidValue(InvalidValueErrOwned),
3274}
3275
3276#[cfg(feature = "parser")]
3277impl StyleLineFitEdgeParseError<'_> {
3278 #[must_use] pub fn to_contained(&self) -> StyleLineFitEdgeParseErrorOwned {
3279 match self {
3280 Self::InvalidValue(e) => StyleLineFitEdgeParseErrorOwned::InvalidValue(e.to_contained()),
3281 }
3282 }
3283}
3284
3285#[cfg(feature = "parser")]
3286impl StyleLineFitEdgeParseErrorOwned {
3287 #[must_use] pub fn to_shared(&self) -> StyleLineFitEdgeParseError<'_> {
3288 match self {
3289 Self::InvalidValue(e) => StyleLineFitEdgeParseError::InvalidValue(e.to_shared()),
3290 }
3291 }
3292}
3293
3294#[cfg(feature = "parser")]
3295pub fn parse_style_line_fit_edge(input: &str) -> Result<StyleLineFitEdge, StyleLineFitEdgeParseError<'_>> {
3299 match input.trim() {
3300 "leading" => Ok(StyleLineFitEdge::Leading),
3301 "text" => Ok(StyleLineFitEdge::Text),
3302 "cap" => Ok(StyleLineFitEdge::Cap),
3303 "ex" => Ok(StyleLineFitEdge::Ex),
3304 "ideographic" => Ok(StyleLineFitEdge::Ideographic),
3305 "ideographic-ink" => Ok(StyleLineFitEdge::IdeographicInk),
3306 "alphabetic" => Ok(StyleLineFitEdge::Alphabetic),
3307 other => Err(StyleLineFitEdgeParseError::InvalidValue(InvalidValueErr(other))),
3308 }
3309}
3310
3311#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
3317#[repr(C)]
3318#[derive(Default)]
3319pub enum StyleInitialLetterAlign {
3320 #[default]
3322 Auto,
3323 Alphabetic,
3325 Hanging,
3327 Ideographic,
3329}
3330impl_option!(
3331 StyleInitialLetterAlign,
3332 OptionStyleInitialLetterAlign,
3333 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
3334);
3335impl PrintAsCssValue for StyleInitialLetterAlign {
3336 fn print_as_css_value(&self) -> String {
3337 String::from(match self {
3338 Self::Auto => "auto",
3339 Self::Alphabetic => "alphabetic",
3340 Self::Hanging => "hanging",
3341 Self::Ideographic => "ideographic",
3342 })
3343 }
3344}
3345
3346#[cfg(feature = "parser")]
3347#[derive(Clone, PartialEq, Eq)]
3348pub enum StyleInitialLetterAlignParseError<'a> {
3349 InvalidValue(InvalidValueErr<'a>),
3350}
3351#[cfg(feature = "parser")]
3352impl_debug_as_display!(StyleInitialLetterAlignParseError<'a>);
3353#[cfg(feature = "parser")]
3354impl_display! { StyleInitialLetterAlignParseError<'a>, {
3355 InvalidValue(e) => format!("Invalid initial-letter-align value: \"{}\"", e.0),
3356}}
3357#[cfg(feature = "parser")]
3358impl_from!(InvalidValueErr<'a>, StyleInitialLetterAlignParseError::InvalidValue);
3359
3360#[cfg(feature = "parser")]
3361#[derive(Debug, Clone, PartialEq, Eq)]
3362#[repr(C, u8)]
3363pub enum StyleInitialLetterAlignParseErrorOwned {
3364 InvalidValue(InvalidValueErrOwned),
3365}
3366
3367#[cfg(feature = "parser")]
3368impl StyleInitialLetterAlignParseError<'_> {
3369 #[must_use] pub fn to_contained(&self) -> StyleInitialLetterAlignParseErrorOwned {
3370 match self {
3371 Self::InvalidValue(e) => StyleInitialLetterAlignParseErrorOwned::InvalidValue(e.to_contained()),
3372 }
3373 }
3374}
3375
3376#[cfg(feature = "parser")]
3377impl StyleInitialLetterAlignParseErrorOwned {
3378 #[must_use] pub fn to_shared(&self) -> StyleInitialLetterAlignParseError<'_> {
3379 match self {
3380 Self::InvalidValue(e) => StyleInitialLetterAlignParseError::InvalidValue(e.to_shared()),
3381 }
3382 }
3383}
3384
3385#[cfg(feature = "parser")]
3386pub fn parse_style_initial_letter_align(input: &str) -> Result<StyleInitialLetterAlign, StyleInitialLetterAlignParseError<'_>> {
3390 match input.trim() {
3391 "auto" => Ok(StyleInitialLetterAlign::Auto),
3392 "alphabetic" => Ok(StyleInitialLetterAlign::Alphabetic),
3393 "hanging" => Ok(StyleInitialLetterAlign::Hanging),
3394 "ideographic" => Ok(StyleInitialLetterAlign::Ideographic),
3395 other => Err(StyleInitialLetterAlignParseError::InvalidValue(InvalidValueErr(other))),
3396 }
3397}
3398
3399#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
3405#[repr(C)]
3406#[derive(Default)]
3407pub enum StyleInitialLetterWrap {
3408 #[default]
3410 None,
3411 First,
3413 All,
3415 Grid,
3417}
3418impl_option!(
3419 StyleInitialLetterWrap,
3420 OptionStyleInitialLetterWrap,
3421 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
3422);
3423impl PrintAsCssValue for StyleInitialLetterWrap {
3424 fn print_as_css_value(&self) -> String {
3425 String::from(match self {
3426 Self::None => "none",
3427 Self::First => "first",
3428 Self::All => "all",
3429 Self::Grid => "grid",
3430 })
3431 }
3432}
3433
3434#[cfg(feature = "parser")]
3435#[derive(Clone, PartialEq, Eq)]
3436pub enum StyleInitialLetterWrapParseError<'a> {
3437 InvalidValue(InvalidValueErr<'a>),
3438}
3439#[cfg(feature = "parser")]
3440impl_debug_as_display!(StyleInitialLetterWrapParseError<'a>);
3441#[cfg(feature = "parser")]
3442impl_display! { StyleInitialLetterWrapParseError<'a>, {
3443 InvalidValue(e) => format!("Invalid initial-letter-wrap value: \"{}\"", e.0),
3444}}
3445#[cfg(feature = "parser")]
3446impl_from!(InvalidValueErr<'a>, StyleInitialLetterWrapParseError::InvalidValue);
3447
3448#[cfg(feature = "parser")]
3449#[derive(Debug, Clone, PartialEq, Eq)]
3450#[repr(C, u8)]
3451pub enum StyleInitialLetterWrapParseErrorOwned {
3452 InvalidValue(InvalidValueErrOwned),
3453}
3454
3455#[cfg(feature = "parser")]
3456impl StyleInitialLetterWrapParseError<'_> {
3457 #[must_use] pub fn to_contained(&self) -> StyleInitialLetterWrapParseErrorOwned {
3458 match self {
3459 Self::InvalidValue(e) => StyleInitialLetterWrapParseErrorOwned::InvalidValue(e.to_contained()),
3460 }
3461 }
3462}
3463
3464#[cfg(feature = "parser")]
3465impl StyleInitialLetterWrapParseErrorOwned {
3466 #[must_use] pub fn to_shared(&self) -> StyleInitialLetterWrapParseError<'_> {
3467 match self {
3468 Self::InvalidValue(e) => StyleInitialLetterWrapParseError::InvalidValue(e.to_shared()),
3469 }
3470 }
3471}
3472
3473#[cfg(feature = "parser")]
3474pub fn parse_style_initial_letter_wrap(input: &str) -> Result<StyleInitialLetterWrap, StyleInitialLetterWrapParseError<'_>> {
3478 match input.trim() {
3479 "none" => Ok(StyleInitialLetterWrap::None),
3480 "first" => Ok(StyleInitialLetterWrap::First),
3481 "all" => Ok(StyleInitialLetterWrap::All),
3482 "grid" => Ok(StyleInitialLetterWrap::Grid),
3483 other => Err(StyleInitialLetterWrapParseError::InvalidValue(InvalidValueErr(other))),
3484 }
3485}
3486
3487#[cfg(test)]
3488#[allow(clippy::float_cmp, clippy::too_many_lines, clippy::cast_precision_loss)]
3489mod autotest_generated {
3490 use super::*;
3491 use crate::props::basic::length::SizeMetric;
3492
3493 const OPAQUE_BLACK: ColorU = ColorU { r: 0, g: 0, b: 0, a: 255 };
3494 const OPAQUE_WHITE: ColorU = ColorU { r: 255, g: 255, b: 255, a: 255 };
3495 const TRANSPARENT_BLACK: ColorU = ColorU { r: 0, g: 0, b: 0, a: 0 };
3496
3497 const SCALE: isize = 1000;
3499
3500 #[test]
3505 fn text_indent_zero_is_the_neutral_element() {
3506 let z = StyleTextIndent::zero();
3507 assert_eq!(z, StyleTextIndent::default());
3508 assert_eq!(z.inner.metric, SizeMetric::Px);
3509 assert_eq!(z.inner.number.get(), 0.0);
3510 assert!(!z.each_line);
3511 assert!(!z.hanging);
3512 assert_eq!(z.print_as_css_value(), "0px");
3513 }
3514
3515 #[test]
3516 fn text_indent_const_ctors_pin_metric_and_value() {
3517 for v in [0_isize, 1, -1, 42, -42] {
3519 let cases = [
3520 (StyleTextIndent::const_px(v), SizeMetric::Px),
3521 (StyleTextIndent::const_em(v), SizeMetric::Em),
3522 (StyleTextIndent::const_pt(v), SizeMetric::Pt),
3523 (StyleTextIndent::const_percent(v), SizeMetric::Percent),
3524 (StyleTextIndent::const_in(v), SizeMetric::In),
3525 (StyleTextIndent::const_cm(v), SizeMetric::Cm),
3526 (StyleTextIndent::const_mm(v), SizeMetric::Mm),
3527 ];
3528 for (got, metric) in cases {
3529 assert_eq!(got.inner.metric, metric, "metric for {v}");
3530 assert_eq!(got.inner.number.get(), v as f32, "value for {v} {metric:?}");
3531 assert!(!got.each_line && !got.hanging);
3533 }
3534 }
3535 }
3536
3537 #[test]
3538 fn text_indent_const_ctors_hold_the_whole_encodable_isize_range() {
3539 assert_eq!(StyleTextIndent::const_px(1).inner.number.number(), SCALE);
3542
3543 let max = isize::MAX / SCALE;
3544 let min = isize::MIN / SCALE;
3545 assert_eq!(StyleTextIndent::const_px(max).inner.number.number(), max * SCALE);
3546 assert_eq!(StyleTextIndent::const_px(min).inner.number.number(), min * SCALE);
3547 assert_eq!(
3548 StyleTextIndent::const_from_metric(SizeMetric::Em, max).inner.number.number(),
3549 max * SCALE
3550 );
3551 }
3555
3556 #[test]
3557 fn text_indent_float_ctors_saturate_on_nan_and_infinity() {
3558 assert_eq!(StyleTextIndent::px(f32::NAN).inner.number.get(), 0.0);
3560 assert_eq!(StyleTextIndent::em(f32::NAN).inner.number.get(), 0.0);
3561
3562 assert_eq!(StyleTextIndent::px(f32::INFINITY).inner.number.number(), isize::MAX);
3563 assert_eq!(StyleTextIndent::px(f32::NEG_INFINITY).inner.number.number(), isize::MIN);
3564 assert_eq!(StyleTextIndent::pt(f32::MAX).inner.number.number(), isize::MAX);
3565 assert_eq!(StyleTextIndent::pt(-f32::MAX).inner.number.number(), isize::MIN);
3566
3567 assert_eq!(StyleTextIndent::percent(f32::MIN_POSITIVE).inner.number.number(), 0);
3569 assert_eq!(StyleTextIndent::px(-0.0).inner.number.number(), 0);
3570
3571 for v in [f32::NAN, f32::INFINITY, f32::NEG_INFINITY, f32::MAX, -f32::MAX] {
3573 assert!(StyleTextIndent::px(v).inner.number.get().is_finite());
3574 }
3575 }
3576
3577 #[test]
3578 fn text_indent_from_metric_agrees_with_the_typed_ctors() {
3579 assert_eq!(StyleTextIndent::from_metric(SizeMetric::Px, 1.5), StyleTextIndent::px(1.5));
3580 assert_eq!(StyleTextIndent::from_metric(SizeMetric::Em, -2.5), StyleTextIndent::em(-2.5));
3581 assert_eq!(StyleTextIndent::from_metric(SizeMetric::Pt, 0.0), StyleTextIndent::pt(0.0));
3582 assert_eq!(
3583 StyleTextIndent::from_metric(SizeMetric::Percent, 50.0),
3584 StyleTextIndent::percent(50.0)
3585 );
3586 assert_eq!(StyleTextIndent::const_from_metric(SizeMetric::Cm, 3), StyleTextIndent::const_cm(3));
3587 assert_eq!(StyleTextIndent::const_from_metric(SizeMetric::Mm, -3), StyleTextIndent::const_mm(-3));
3588
3589 let vw = StyleTextIndent::from_metric(SizeMetric::Vw, 10.0);
3591 assert_eq!(vw.inner.metric, SizeMetric::Vw);
3592 assert_eq!(vw.inner.number.get(), 10.0);
3593 }
3594
3595 #[test]
3596 fn text_indent_interpolate_endpoints_and_extrapolation() {
3597 let a = StyleTextIndent::px(0.0);
3598 let b = StyleTextIndent::px(100.0);
3599
3600 assert_eq!(a.interpolate(&b, 0.0).inner.number.get(), 0.0);
3601 assert_eq!(a.interpolate(&b, 1.0).inner.number.get(), 100.0);
3602 assert_eq!(a.interpolate(&b, 0.5).inner.number.get(), 50.0);
3603 assert_eq!(a.interpolate(&b, -1.0).inner.number.get(), -100.0);
3605 assert_eq!(a.interpolate(&b, 2.0).inner.number.get(), 200.0);
3606 assert_eq!(b.interpolate(&b, 0.25), b);
3608 }
3609
3610 #[test]
3611 fn text_indent_interpolate_with_nonfinite_t_is_defined() {
3612 let a = StyleTextIndent::px(0.0);
3613 let b = StyleTextIndent::px(100.0);
3614
3615 assert_eq!(a.interpolate(&b, f32::NAN).inner.number.get(), 0.0);
3617 assert_eq!(a.interpolate(&b, f32::INFINITY).inner.number.number(), isize::MAX);
3619 assert_eq!(a.interpolate(&b, f32::NEG_INFINITY).inner.number.number(), isize::MIN);
3620 assert_eq!(b.interpolate(&b, f32::INFINITY).inner.number.get(), 0.0);
3622
3623 for t in [f32::NAN, f32::INFINITY, f32::NEG_INFINITY, f32::MAX] {
3624 assert!(a.interpolate(&b, t).inner.number.get().is_finite());
3625 }
3626 }
3627
3628 #[test]
3629 fn text_indent_interpolate_keeps_self_flags_and_normalizes_mixed_metrics() {
3630 let a = StyleTextIndent { each_line: true, hanging: true, ..StyleTextIndent::px(0.0) };
3631 let b = StyleTextIndent { each_line: false, hanging: false, ..StyleTextIndent::px(10.0) };
3632
3633 let mid = a.interpolate(&b, 0.5);
3635 assert!(mid.each_line && mid.hanging);
3636 assert!(!b.interpolate(&a, 0.5).each_line);
3637
3638 let mixed = StyleTextIndent::px(10.0).interpolate(&StyleTextIndent::em(2.0), 0.5);
3640 assert_eq!(mixed.inner.metric, SizeMetric::Px);
3641 assert!(mixed.inner.number.get().is_finite());
3642 }
3643
3644 #[test]
3649 fn text_color_interpolate_endpoints_are_exact() {
3650 let a = StyleTextColor { inner: OPAQUE_BLACK };
3651 let b = StyleTextColor { inner: OPAQUE_WHITE };
3652
3653 assert_eq!(a.interpolate(&b, 0.0), a);
3654 assert_eq!(a.interpolate(&b, 1.0), b);
3655 assert_eq!(a.interpolate(&b, 0.5).inner, ColorU { r: 128, g: 128, b: 128, a: 255 });
3657 assert_eq!(a.print_as_css_value(), "#000000ff");
3658 }
3659
3660 #[test]
3661 fn text_color_interpolate_saturates_out_of_range_t() {
3662 let a = StyleTextColor { inner: OPAQUE_BLACK };
3663 let b = StyleTextColor { inner: OPAQUE_WHITE };
3664
3665 assert_eq!(a.interpolate(&b, 2.0).inner, OPAQUE_WHITE);
3667 assert_eq!(a.interpolate(&b, -1.0).inner, OPAQUE_BLACK);
3669 }
3670
3671 #[test]
3672 fn text_color_interpolate_with_nonfinite_t_is_defined() {
3673 let a = StyleTextColor { inner: OPAQUE_BLACK };
3674 let b = StyleTextColor { inner: OPAQUE_WHITE };
3675
3676 assert_eq!(a.interpolate(&b, f32::NAN).inner, TRANSPARENT_BLACK);
3679
3680 assert_eq!(
3684 a.interpolate(&b, f32::INFINITY).inner,
3685 ColorU { r: 255, g: 255, b: 255, a: 0 }
3686 );
3687 assert_eq!(
3688 a.interpolate(&b, f32::NEG_INFINITY).inner,
3689 ColorU { r: 0, g: 0, b: 0, a: 0 }
3690 );
3691 }
3692
3693 #[test]
3698 fn hanging_punctuation_is_enabled_matches_its_flags_exhaustively() {
3699 assert!(!StyleHangingPunctuation::default().is_enabled());
3700
3701 for bits in 0_u8..16 {
3702 let hp = StyleHangingPunctuation {
3703 first: bits & 1 != 0,
3704 force_end: bits & 2 != 0,
3705 allow_end: bits & 4 != 0,
3706 last: bits & 8 != 0,
3707 };
3708 assert_eq!(hp.is_enabled(), bits != 0, "bits={bits}");
3709 assert_eq!(hp.print_as_css_value() == "none", !hp.is_enabled(), "bits={bits}");
3711 }
3712 }
3713
3714 #[test]
3715 fn hanging_punctuation_prints_flags_in_spec_order() {
3716 let all = StyleHangingPunctuation { first: true, force_end: true, allow_end: true, last: true };
3717 assert_eq!(all.print_as_css_value(), "first force-end allow-end last");
3718 assert_eq!(
3719 StyleHangingPunctuation { last: true, ..Default::default() }.print_as_css_value(),
3720 "last"
3721 );
3722 }
3723
3724 #[cfg(feature = "parser")]
3729 mod parser {
3730 use super::super::*;
3731 use crate::props::basic::length::SizeMetric;
3732
3733 const GARBAGE: &[&str] = &[
3734 "",
3735 " ",
3736 "\t\n",
3737 "!!!",
3738 "\0\0",
3739 "0",
3740 "-0",
3741 "9223372036854775807",
3742 "1e400",
3743 "NaN",
3744 "inf",
3745 "\u{1F600}",
3746 "e\u{0301}",
3747 "\u{202E}left",
3748 "left;garbage",
3749 "left garbage",
3750 ];
3751
3752 macro_rules! assert_keyword_round_trip {
3754 ($parse:ident, $variants:expr) => {{
3755 for v in $variants {
3756 let printed = v.print_as_css_value();
3757 assert_eq!($parse(&printed).as_ref(), Ok(&v), "round-trip of {printed:?}");
3758 assert_eq!($parse(&format!(" {printed} ")).as_ref(), Ok(&v));
3760 }
3761 for g in GARBAGE.iter().copied() {
3762 assert!($parse(g).is_err(), "{} accepted garbage {g:?}", stringify!($parse));
3763 }
3764 }};
3765 }
3766
3767 #[test]
3768 fn keyword_parsers_round_trip_every_variant_and_reject_garbage() {
3769 type TA = StyleTextAlign;
3770 assert_keyword_round_trip!(
3771 parse_style_text_align,
3772 [TA::Left, TA::Center, TA::Right, TA::Justify, TA::Start, TA::End]
3773 );
3774 type WS = StyleWhiteSpace;
3775 assert_keyword_round_trip!(
3776 parse_style_white_space,
3777 [WS::Normal, WS::Pre, WS::Nowrap, WS::PreWrap, WS::PreLine, WS::BreakSpaces]
3778 );
3779 type H = StyleHyphens;
3780 assert_keyword_round_trip!(parse_style_hyphens, [H::None, H::Manual, H::Auto]);
3781 type LB = StyleLineBreak;
3782 assert_keyword_round_trip!(
3783 parse_style_line_break,
3784 [LB::Auto, LB::Loose, LB::Normal, LB::Strict, LB::Anywhere]
3785 );
3786 type WB = StyleWordBreak;
3787 assert_keyword_round_trip!(
3788 parse_style_word_break,
3789 [WB::Normal, WB::BreakAll, WB::KeepAll, WB::BreakWord]
3790 );
3791 type OW = StyleOverflowWrap;
3792 assert_keyword_round_trip!(
3793 parse_style_overflow_wrap,
3794 [OW::Normal, OW::Anywhere, OW::BreakWord]
3795 );
3796 type Tal = StyleTextAlignLast;
3797 assert_keyword_round_trip!(
3798 parse_style_text_align_last,
3799 [Tal::Auto, Tal::Start, Tal::End, Tal::Left, Tal::Right, Tal::Center, Tal::Justify]
3800 );
3801 type TT = StyleTextTransform;
3802 assert_keyword_round_trip!(
3803 parse_style_text_transform,
3804 [TT::None, TT::Capitalize, TT::Uppercase, TT::Lowercase, TT::FullWidth]
3805 );
3806 type D = StyleDirection;
3807 assert_keyword_round_trip!(parse_style_direction, [D::Ltr, D::Rtl]);
3808 type US = StyleUserSelect;
3809 assert_keyword_round_trip!(
3810 parse_style_user_select,
3811 [US::Auto, US::Text, US::None, US::All]
3812 );
3813 type TD = StyleTextDecoration;
3814 assert_keyword_round_trip!(
3815 parse_style_text_decoration,
3816 [TD::None, TD::Underline, TD::Overline, TD::LineThrough]
3817 );
3818 type UB = StyleUnicodeBidi;
3819 assert_keyword_round_trip!(
3820 parse_style_unicode_bidi,
3821 [UB::Normal, UB::Embed, UB::Isolate, UB::BidiOverride, UB::IsolateOverride, UB::Plaintext]
3822 );
3823 type Tbt = StyleTextBoxTrim;
3824 assert_keyword_round_trip!(
3825 parse_style_text_box_trim,
3826 [Tbt::None, Tbt::TrimStart, Tbt::TrimEnd, Tbt::TrimBoth]
3827 );
3828 {
3829 use crate::props::style::text::{TextBoxEdgeOver as O, TextBoxEdgeUnder as U};
3832 let cases = [
3833 ("auto", StyleTextBoxEdge::AUTO),
3834 ("text", StyleTextBoxEdge { over: O::Text, under: U::Text }),
3835 ("cap", StyleTextBoxEdge { over: O::Cap, under: U::Text }),
3836 ("ex", StyleTextBoxEdge { over: O::Ex, under: U::Text }),
3837 ("ideographic", StyleTextBoxEdge { over: O::Ideographic, under: U::Ideographic }),
3838 ("ideographic-ink", StyleTextBoxEdge { over: O::IdeographicInk, under: U::IdeographicInk }),
3839 ("cap alphabetic", StyleTextBoxEdge { over: O::Cap, under: U::Alphabetic }),
3840 ("text ideographic", StyleTextBoxEdge { over: O::Text, under: U::Ideographic }),
3841 ];
3842 for (input, expected) in cases {
3843 let parsed = parse_style_text_box_edge(input).unwrap_or_else(|e| {
3844 panic!("`{input}` must parse: {e:?}")
3845 });
3846 assert_eq!(parsed, expected, "parse of `{input}`");
3847 let printed = parsed.print_as_css_value();
3848 let reparsed = parse_style_text_box_edge(&printed)
3849 .unwrap_or_else(|e| panic!("reprint `{printed}` must parse: {e:?}"));
3850 assert_eq!(reparsed, parsed, "print/parse round trip via `{printed}`");
3851 }
3852 assert!(parse_style_text_box_edge("auto text").is_err());
3854 assert!(parse_style_text_box_edge("cap cap").is_err(), "cap is over-only");
3855 assert!(parse_style_text_box_edge("alphabetic").is_err(), "alphabetic is under-only");
3856 assert!(parse_style_text_box_edge("bogus").is_err());
3857 }
3858 type DB = StyleDominantBaseline;
3859 assert_keyword_round_trip!(
3860 parse_style_dominant_baseline,
3861 [
3862 DB::Auto, DB::TextBottom, DB::Alphabetic, DB::Ideographic, DB::Middle,
3863 DB::Central, DB::Mathematical, DB::Hanging, DB::TextTop
3864 ]
3865 );
3866 type AB = StyleAlignmentBaseline;
3867 assert_keyword_round_trip!(
3868 parse_style_alignment_baseline,
3869 [
3870 AB::Baseline, AB::TextBottom, AB::Alphabetic, AB::Ideographic, AB::Middle,
3871 AB::Central, AB::Mathematical, AB::TextTop
3872 ]
3873 );
3874 type Bs = StyleBaselineSource;
3875 assert_keyword_round_trip!(
3876 parse_style_baseline_source,
3877 [Bs::Auto, Bs::First, Bs::Last]
3878 );
3879 type Lfe = StyleLineFitEdge;
3880 assert_keyword_round_trip!(
3881 parse_style_line_fit_edge,
3882 [
3883 Lfe::Leading, Lfe::Text, Lfe::Cap, Lfe::Ex, Lfe::Ideographic,
3884 Lfe::IdeographicInk, Lfe::Alphabetic
3885 ]
3886 );
3887 type Ila = StyleInitialLetterAlign;
3888 assert_keyword_round_trip!(
3889 parse_style_initial_letter_align,
3890 [Ila::Auto, Ila::Alphabetic, Ila::Hanging, Ila::Ideographic]
3891 );
3892 type Ilw = StyleInitialLetterWrap;
3893 assert_keyword_round_trip!(
3894 parse_style_initial_letter_wrap,
3895 [Ilw::None, Ilw::First, Ilw::All, Ilw::Grid]
3896 );
3897 }
3898
3899 #[test]
3900 fn keyword_parsers_are_case_sensitive() {
3901 assert!(parse_style_text_align("LEFT").is_err());
3906 assert!(parse_style_text_align("Left").is_err());
3907 assert!(parse_style_white_space("Normal").is_err());
3908 assert!(parse_style_direction("LTR").is_err());
3909 assert!(parse_style_hanging_punctuation("FIRST").is_ok());
3911 assert!(parse_style_text_combine_upright("NONE").is_ok());
3912 }
3913
3914 #[test]
3915 fn extremely_long_and_deeply_nested_input_terminates_with_err() {
3916 let long = "a".repeat(1_000_000);
3917 assert!(parse_style_text_align(&long).is_err());
3918 assert!(parse_style_white_space(&long).is_err());
3919 assert!(parse_style_text_color(&long).is_err());
3920 assert!(parse_style_letter_spacing(&long).is_err());
3921 assert!(parse_style_word_spacing(&long).is_err());
3922 assert!(parse_style_tab_size(&long).is_err());
3923 assert!(parse_style_line_height(&long).is_err());
3924 assert!(parse_style_text_indent(&long).is_err());
3925 assert!(parse_style_hanging_punctuation(&long).is_err());
3926 assert!(parse_style_initial_letter(&long).is_err());
3927 assert!(parse_style_line_clamp(&long).is_err());
3928 assert!(parse_style_vertical_align(&long).is_err());
3929
3930 let huge_number = "9".repeat(1000);
3932 assert!(parse_style_line_clamp(&huge_number).is_err());
3933 assert!(parse_style_initial_letter(&huge_number).is_err());
3934
3935 let nested = format!("{}{}", "(".repeat(10_000), ")".repeat(10_000));
3937 assert!(parse_style_text_align(&nested).is_err());
3938 assert!(parse_style_letter_spacing(&nested).is_err());
3939 assert!(parse_style_text_indent(&nested).is_err());
3940 assert!(parse_style_line_height(&nested).is_err());
3941 }
3942
3943 #[test]
3946 fn spacing_and_tab_size_round_trip_through_their_printed_form() {
3947 for pv in [
3948 PixelValue::px(2.0),
3949 PixelValue::px(-3.0),
3950 PixelValue::em(0.5),
3951 PixelValue::pt(12.0),
3952 PixelValue::percent(10.0),
3953 PixelValue::zero(),
3954 ] {
3955 let ls = StyleLetterSpacing { inner: pv };
3956 assert_eq!(parse_style_letter_spacing(&ls.print_as_css_value()).unwrap(), ls);
3957 let ws = StyleWordSpacing { inner: pv };
3958 assert_eq!(parse_style_word_spacing(&ws.print_as_css_value()).unwrap(), ws);
3959 }
3960 let ts = StyleTabSize::default();
3962 assert_eq!(ts.inner, PixelValue::em(8.0));
3963 assert_eq!(parse_style_tab_size(&ts.print_as_css_value()).unwrap(), ts);
3964 assert_eq!(parse_style_tab_size("4").unwrap().inner, PixelValue::em(4.0));
3965 assert_eq!(parse_style_tab_size("20px").unwrap().inner, PixelValue::px(20.0));
3966 }
3967
3968 #[test]
3969 fn pixel_parsers_reject_empty_and_unit_only_input() {
3970 for bad in ["", " ", "\t\n", "px", "em", "abc", "px px", "10pxx", "\u{1F600}"] {
3971 assert!(parse_style_letter_spacing(bad).is_err(), "letter-spacing {bad:?}");
3972 assert!(parse_style_word_spacing(bad).is_err(), "word-spacing {bad:?}");
3973 }
3974 assert_eq!(parse_style_letter_spacing("10 px").unwrap().inner, PixelValue::px(10.0));
3977 }
3978
3979 #[test]
3980 fn pixel_parsers_accept_float_keywords_and_saturate_instead_of_panicking() {
3981 assert_eq!(parse_style_letter_spacing("NaN").unwrap().inner.number.number(), 0);
3985 assert_eq!(
3986 parse_style_letter_spacing("inf").unwrap().inner.number.number(),
3987 isize::MAX
3988 );
3989 assert_eq!(
3990 parse_style_letter_spacing("-inf").unwrap().inner.number.number(),
3991 isize::MIN
3992 );
3993 assert_eq!(
3994 parse_style_word_spacing("1e400px").unwrap().inner.number.number(),
3995 isize::MAX
3996 );
3997 assert_eq!(parse_style_tab_size("NaN").unwrap().inner, PixelValue::em(0.0));
3999 assert!(parse_style_tab_size("inf").unwrap().inner.number.get().is_finite());
4000
4001 assert_eq!(parse_style_letter_spacing("0").unwrap().inner, PixelValue::px(0.0));
4003 assert_eq!(parse_style_letter_spacing("-0").unwrap().inner.number.number(), 0);
4004 assert!(parse_style_letter_spacing("9223372036854775807px").unwrap().inner.number.get().is_finite());
4005 }
4006
4007 #[test]
4010 fn text_indent_round_trips_length_and_keywords() {
4011 for pv in [PixelValue::px(10.0), PixelValue::em(-2.0), PixelValue::percent(50.0)] {
4012 for (each_line, hanging) in [(false, false), (true, false), (false, true), (true, true)] {
4013 let ti = StyleTextIndent { inner: pv, each_line, hanging };
4014 let printed = ti.print_as_css_value();
4015 assert_eq!(parse_style_text_indent(&printed).unwrap(), ti, "{printed:?}");
4016 }
4017 }
4018 assert!(parse_style_text_indent("10px hanging").unwrap().hanging);
4019 assert!(parse_style_text_indent("each-line 10px").unwrap().each_line);
4020 }
4021
4022 #[test]
4023 fn text_indent_defaults_missing_length_to_zero_and_keeps_the_last_one() {
4024 assert_eq!(parse_style_text_indent("").unwrap(), StyleTextIndent::zero());
4028 assert_eq!(parse_style_text_indent(" ").unwrap(), StyleTextIndent::zero());
4029 assert_eq!(
4030 parse_style_text_indent("hanging").unwrap(),
4031 StyleTextIndent { hanging: true, ..StyleTextIndent::zero() }
4032 );
4033 assert_eq!(parse_style_text_indent("10px 20px").unwrap().inner, PixelValue::px(20.0));
4035
4036 assert!(parse_style_text_indent("garbage").is_err());
4038 assert!(parse_style_text_indent("hanging garbage").is_err());
4039 }
4040
4041 #[test]
4044 fn initial_letter_parses_size_and_optional_sink() {
4045 assert_eq!(
4046 parse_style_initial_letter("3").unwrap(),
4047 StyleInitialLetter { size: 3, sink: crate::corety::OptionU32::None }
4048 );
4049 let with_sink = StyleInitialLetter { size: 3, sink: crate::corety::OptionU32::Some(2) };
4050 assert_eq!(parse_style_initial_letter("3 2").unwrap(), with_sink);
4051 assert_eq!(parse_style_initial_letter(&with_sink.print_as_css_value()).unwrap(), with_sink);
4053 }
4054
4055 #[test]
4056 fn initial_letter_rejects_zero_negative_and_overflowing_sizes() {
4057 assert!(parse_style_initial_letter("0").is_err(), "size 0 must be rejected");
4058 assert!(parse_style_initial_letter("-1").is_err());
4059 assert!(parse_style_initial_letter("1.5").is_err());
4060 assert!(parse_style_initial_letter("4294967296").is_err(), "u32::MAX + 1");
4061 assert!(parse_style_initial_letter("3 -1").is_err(), "negative sink");
4062 assert!(parse_style_initial_letter("3 x").is_err());
4063 assert!(parse_style_initial_letter("").is_err());
4064 assert!(parse_style_initial_letter(" ").is_err());
4065 assert_eq!(parse_style_initial_letter("4294967295").unwrap().size, u32::MAX);
4067 assert_eq!(parse_style_initial_letter("3 2 9").unwrap(), StyleInitialLetter {
4069 size: 3,
4070 sink: crate::corety::OptionU32::Some(2),
4071 });
4072 }
4073
4074 #[test]
4077 fn line_clamp_rejects_zero_and_out_of_range_values() {
4078 assert_eq!(parse_style_line_clamp("3").unwrap(), StyleLineClamp { max_lines: 3 });
4079 assert_eq!(parse_style_line_clamp(" 7 ").unwrap().max_lines, 7);
4080 assert_eq!(
4081 parse_style_line_clamp("0").unwrap_err(),
4082 StyleLineClampParseError::ZeroValue
4083 );
4084 assert!(parse_style_line_clamp("-1").is_err());
4085 assert!(parse_style_line_clamp("1.0").is_err());
4086 assert!(parse_style_line_clamp("").is_err());
4087 assert!(parse_style_line_clamp(" ").is_err());
4088 assert!(parse_style_line_clamp("\u{1F600}").is_err());
4089 assert!(parse_style_line_clamp("99999999999999999999999").is_err());
4091 let max = usize::MAX.to_string();
4092 assert_eq!(parse_style_line_clamp(&max).unwrap().max_lines, usize::MAX);
4093 let lc = StyleLineClamp { max_lines: 42 };
4095 assert_eq!(parse_style_line_clamp(&lc.print_as_css_value()).unwrap(), lc);
4096 }
4097
4098 #[test]
4101 fn hanging_punctuation_round_trips_and_enforces_mutual_exclusion() {
4102 for bits in 0_u8..16 {
4103 let hp = StyleHangingPunctuation {
4104 first: bits & 1 != 0,
4105 force_end: bits & 2 != 0,
4106 allow_end: bits & 4 != 0,
4107 last: bits & 8 != 0,
4108 };
4109 let printed = hp.print_as_css_value();
4110 if hp.force_end && hp.allow_end {
4111 assert!(parse_style_hanging_punctuation(&printed).is_err(), "{printed:?}");
4113 } else {
4114 assert_eq!(parse_style_hanging_punctuation(&printed).unwrap(), hp, "{printed:?}");
4115 }
4116 }
4117 assert!(parse_style_hanging_punctuation("first bogus").is_err());
4118 assert!(parse_style_hanging_punctuation("\u{1F600}").is_err());
4119 assert!(parse_style_hanging_punctuation("none first").is_err());
4120 }
4121
4122 #[test]
4123 fn hanging_punctuation_accepts_empty_input_as_none() {
4124 assert_eq!(
4127 parse_style_hanging_punctuation("").unwrap(),
4128 StyleHangingPunctuation::default()
4129 );
4130 assert_eq!(
4131 parse_style_hanging_punctuation(" ").unwrap(),
4132 StyleHangingPunctuation::default()
4133 );
4134 assert!(parse_style_hanging_punctuation("first first").unwrap().first);
4136 }
4137
4138 #[test]
4141 fn text_combine_upright_bounds_the_digits_operand() {
4142 assert_eq!(parse_style_text_combine_upright("none").unwrap(), StyleTextCombineUpright::None);
4143 assert_eq!(parse_style_text_combine_upright("all").unwrap(), StyleTextCombineUpright::All);
4144 for n in 2_u8..=4 {
4145 let v = StyleTextCombineUpright::Digits(n);
4146 assert_eq!(parse_style_text_combine_upright(&v.print_as_css_value()).unwrap(), v);
4147 }
4148 for bad in ["digits 0", "digits 1", "digits 5", "digits 255", "digits 256", "digits -1"] {
4150 assert!(parse_style_text_combine_upright(bad).is_err(), "{bad:?} accepted");
4151 }
4152 assert!(parse_style_text_combine_upright("").is_err());
4153 assert!(parse_style_text_combine_upright("bogus").is_err());
4154 }
4155
4156 #[test]
4157 fn text_combine_upright_accepts_garbage_after_the_digits_prefix() {
4158 assert_eq!(
4162 parse_style_text_combine_upright("digits").unwrap(),
4163 StyleTextCombineUpright::Digits(2)
4164 );
4165 assert_eq!(
4166 parse_style_text_combine_upright("digitsgarbage").unwrap(),
4167 StyleTextCombineUpright::Digits(2)
4168 );
4169 assert_eq!(
4170 parse_style_text_combine_upright("digits 2 3").unwrap(),
4171 StyleTextCombineUpright::Digits(2)
4172 );
4173 }
4174
4175 #[test]
4178 fn line_height_parses_numbers_percentages_and_px() {
4179 assert_eq!(parse_style_line_height("1.5").unwrap().inner, PercentageValue::new(150.0));
4180 assert_eq!(parse_style_line_height("120%").unwrap().inner, PercentageValue::new(120.0));
4181 assert_eq!(parse_style_line_height("20px").unwrap().inner, PercentageValue::new(-2000.0));
4183 assert!(parse_style_line_height("").is_err());
4184 assert!(parse_style_line_height(" ").is_err());
4185 assert!(parse_style_line_height("abc").is_err());
4186 assert!(parse_style_line_height("\u{1F600}").is_err());
4187 let lh = StyleLineHeight::default();
4189 assert_eq!(parse_style_line_height(&lh.print_as_css_value()).unwrap(), lh);
4190 }
4191
4192 #[test]
4193 fn line_height_negative_numbers_alias_absolute_px_lengths() {
4194 assert_eq!(
4199 parse_style_line_height("-1").unwrap(),
4200 parse_style_line_height("1px").unwrap()
4201 );
4202 assert_eq!(
4203 parse_style_line_height("-100%").unwrap(),
4204 parse_style_line_height("1px").unwrap()
4205 );
4206 }
4207
4208 #[test]
4209 fn line_height_rejects_em_and_other_length_units() {
4210 assert!(parse_style_line_height("1.5em").is_err());
4213 assert!(parse_style_line_height("12pt").is_err());
4214 assert!(parse_style_line_height("2rem").is_err());
4215 }
4216
4217 #[test]
4218 fn line_height_rejects_non_ascii_numerals_without_panicking() {
4219 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()); }
4227
4228 #[test]
4231 fn vertical_align_round_trips_keywords_percentages_and_lengths() {
4232 type VA = StyleVerticalAlign;
4233 for v in [
4234 VA::Baseline, VA::Top, VA::Middle, VA::Bottom, VA::Sub, VA::Superscript,
4235 VA::TextTop, VA::TextBottom,
4236 VA::Percentage(PercentageValue::new(50.0)),
4237 VA::Percentage(PercentageValue::new(-25.0)),
4238 VA::Length(PixelValue::px(12.0)),
4239 VA::Length(PixelValue::em(1.5)),
4240 ] {
4241 let printed = v.print_as_css_value();
4242 assert_eq!(parse_style_vertical_align(&printed).unwrap(), v, "{printed:?}");
4243 }
4244 assert!(parse_style_vertical_align("").is_err());
4245 assert!(parse_style_vertical_align("%").is_err());
4246 assert!(parse_style_vertical_align("bogus%").is_err());
4247 assert!(parse_style_vertical_align("\u{1F600}").is_err());
4248 }
4249
4250 #[test]
4253 fn caret_parsers_reject_garbage_and_accept_minimal_input() {
4254 assert_eq!(parse_caret_color("red").unwrap().inner, parse_style_text_color("red").unwrap().inner);
4255 assert!(parse_caret_color("").is_err());
4256 assert!(parse_caret_color("not-a-color").is_err());
4257 assert_eq!(parse_caret_width("2px").unwrap().inner, PixelValue::px(2.0));
4258 assert!(parse_caret_width("").is_err());
4259 assert!(parse_caret_animation_duration("bogus").is_err());
4260 assert!(parse_caret_animation_duration("500ms").is_ok());
4261 }
4262
4263 macro_rules! assert_error_round_trip {
4267 ($parse:ident, $($bad:expr),+ $(,)?) => {{
4268 $(
4269 let e = $parse($bad).expect_err(concat!(stringify!($parse), " accepted ", $bad));
4270 assert_eq!(e.to_contained().to_shared(), e, "{:?} via {}", $bad, stringify!($parse));
4271 )+
4272 }};
4273 }
4274
4275 #[test]
4276 fn invalid_value_errors_round_trip_through_their_owned_form() {
4277 assert_error_round_trip!(parse_style_text_align, "", "middle", "\u{1F600}");
4278 assert_error_round_trip!(parse_style_white_space, "", "wrap");
4279 assert_error_round_trip!(parse_style_hyphens, "", "always");
4280 assert_error_round_trip!(parse_style_line_break, "", "tight");
4281 assert_error_round_trip!(parse_style_word_break, "", "break");
4282 assert_error_round_trip!(parse_style_overflow_wrap, "", "wrap");
4283 assert_error_round_trip!(parse_style_text_align_last, "", "middle");
4284 assert_error_round_trip!(parse_style_text_transform, "", "smallcaps");
4285 assert_error_round_trip!(parse_style_direction, "", "sideways");
4286 assert_error_round_trip!(parse_style_user_select, "", "some");
4287 assert_error_round_trip!(parse_style_text_decoration, "", "blink");
4288 assert_error_round_trip!(parse_style_vertical_align, "", "bogus");
4289 assert_error_round_trip!(parse_style_unicode_bidi, "", "override");
4290 assert_error_round_trip!(parse_style_text_box_trim, "", "trim");
4291 assert_error_round_trip!(parse_style_text_box_edge, "", "edge");
4292 assert_error_round_trip!(parse_style_dominant_baseline, "", "bogus");
4293 assert_error_round_trip!(parse_style_alignment_baseline, "", "bogus");
4294 assert_error_round_trip!(parse_style_baseline_source, "", "bogus");
4295 assert_error_round_trip!(parse_style_line_fit_edge, "", "bogus");
4296 assert_error_round_trip!(parse_style_initial_letter_align, "", "bogus");
4297 assert_error_round_trip!(parse_style_initial_letter_wrap, "", "bogus");
4298 }
4299
4300 #[test]
4301 fn pixel_and_numeric_errors_round_trip_through_their_owned_form() {
4302 assert_error_round_trip!(parse_style_letter_spacing, "", "abcpx", "px", "zz");
4304 assert_error_round_trip!(parse_style_word_spacing, "", "abcem", "em", "zz");
4305 assert_error_round_trip!(parse_style_text_indent, "abcpx", "zz");
4306 assert_error_round_trip!(parse_style_tab_size, "", "abcpx", "zz");
4307 assert_error_round_trip!(parse_style_line_height, "", "abc", "1.5em");
4308 assert_error_round_trip!(parse_style_initial_letter, "", "x", "0", "3 x");
4309 assert_error_round_trip!(parse_style_line_clamp, "", "x", "0");
4310 assert_error_round_trip!(parse_style_hanging_punctuation, "bogus", "force-end allow-end");
4311 assert_error_round_trip!(parse_style_text_combine_upright, "bogus", "digits 9");
4312 }
4313
4314 #[test]
4315 fn text_color_error_round_trips_and_preserves_its_message() {
4316 let e = parse_style_text_color("not-a-color").unwrap_err();
4317 let owned = e.to_contained();
4318 let round_tripped = owned.to_shared();
4319 assert_eq!(format!("{e}"), format!("{round_tripped}"));
4320 assert!(parse_style_text_color("").is_err());
4321 assert!(parse_style_text_color("#gggggg").is_err());
4322 assert!(parse_style_text_color("\u{1F600}").is_err());
4323 assert_eq!(parse_style_text_color("#aabbcc").unwrap().inner.to_hash(), "#aabbccff");
4325 }
4326
4327 #[test]
4328 fn error_types_survive_a_default_ish_extreme_instance() {
4329 let long = "z".repeat(100_000);
4331 let e = parse_style_text_align(&long).unwrap_err();
4332 assert_eq!(e.to_contained().to_shared(), e);
4333 let e = parse_style_line_clamp(&long).unwrap_err();
4334 assert_eq!(e.to_contained().to_shared(), e);
4335 let e = parse_style_hanging_punctuation(&long).unwrap_err();
4336 assert_eq!(e.to_contained().to_shared(), e);
4337 let e = parse_style_letter_spacing("").unwrap_err();
4339 assert_eq!(e.to_contained().to_shared(), e);
4340 }
4341
4342 #[test]
4345 fn letter_spacing_accepts_every_size_metric_it_prints() {
4346 for (unit, metric) in [
4347 ("px", SizeMetric::Px),
4348 ("pt", SizeMetric::Pt),
4349 ("em", SizeMetric::Em),
4350 ("rem", SizeMetric::Rem),
4351 ("in", SizeMetric::In),
4352 ("cm", SizeMetric::Cm),
4353 ("mm", SizeMetric::Mm),
4354 ("%", SizeMetric::Percent),
4355 ("vw", SizeMetric::Vw),
4356 ("vh", SizeMetric::Vh),
4357 ("vmax", SizeMetric::Vmax),
4358 ("vmin", SizeMetric::Vmin),
4364 ] {
4365 let parsed = parse_style_letter_spacing(&format!("1{unit}")).unwrap();
4366 assert_eq!(parsed.inner.metric, metric, "unit {unit}");
4367 assert_eq!(parsed.inner.number.get(), 1.0);
4368 }
4369 }
4370 }
4371}