Skip to main content

azul_css/props/style/
text.rs

1//! CSS properties for styling text.
2
3use alloc::string::{String, ToString};
4use core::fmt;
5
6use crate::{
7    format_rust_code::FormatAsRustCode,
8    props::{
9        basic::{
10            error::{InvalidValueErr, InvalidValueErrOwned},
11            length::{PercentageParseError, PercentageParseErrorOwned, PercentageValue},
12            pixel::{CssPixelValueParseError, CssPixelValueParseErrorOwned, PixelValue},
13            ColorU, CssDuration,
14        },
15        formatter::PrintAsCssValue,
16        macros::PixelValueTaker,
17    },
18};
19
20// -- StyleTextColor (color property) --
21// NOTE: `color` is a text property, but the `ColorU` type itself is in `basic/color.rs`.
22// This is a newtype wrapper for type safety.
23
24/// Represents a `color` attribute.
25#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
26#[repr(C)]
27pub struct StyleTextColor {
28    pub inner: crate::props::basic::color::ColorU,
29}
30
31impl fmt::Debug for StyleTextColor {
32    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
33        write!(f, "{}", self.print_as_css_value())
34    }
35}
36
37impl StyleTextColor {
38    pub fn interpolate(&self, other: &Self, t: f32) -> Self {
39        Self {
40            inner: self.inner.interpolate(&other.inner, t),
41        }
42    }
43}
44
45impl PrintAsCssValue for StyleTextColor {
46    fn print_as_css_value(&self) -> String {
47        self.inner.to_hash()
48    }
49}
50
51// -- StyleTextAlign --
52
53/// Horizontal text alignment enum (left, center, right) - default: `Left`
54#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
55#[repr(C)]
56pub enum StyleTextAlign {
57    Left,
58    Center,
59    Right,
60    Justify,
61    #[default]
62    Start,
63    End,
64}
65
66impl_option!(
67    StyleTextAlign,
68    OptionStyleTextAlign,
69    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
70);
71
72impl PrintAsCssValue for StyleTextAlign {
73    fn print_as_css_value(&self) -> String {
74        String::from(match self {
75            StyleTextAlign::Left => "left",
76            StyleTextAlign::Center => "center",
77            StyleTextAlign::Right => "right",
78            StyleTextAlign::Justify => "justify",
79            StyleTextAlign::Start => "start",
80            StyleTextAlign::End => "end",
81        })
82    }
83}
84
85// -- StyleLetterSpacing --
86
87/// Represents a `letter-spacing` attribute
88#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
89#[repr(C)]
90pub struct StyleLetterSpacing {
91    pub inner: PixelValue,
92}
93
94impl fmt::Debug for StyleLetterSpacing {
95    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
96        write!(f, "{}", self.inner)
97    }
98}
99impl Default for StyleLetterSpacing {
100    fn default() -> Self {
101        Self {
102            inner: PixelValue::const_px(0),
103        }
104    }
105}
106impl_pixel_value!(StyleLetterSpacing);
107impl PixelValueTaker for StyleLetterSpacing {
108    fn from_pixel_value(inner: PixelValue) -> Self {
109        Self { inner }
110    }
111}
112impl PrintAsCssValue for StyleLetterSpacing {
113    fn print_as_css_value(&self) -> String {
114        format!("{}", self.inner)
115    }
116}
117
118// -- StyleWordSpacing --
119
120/// Represents a `word-spacing` attribute
121#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
122#[repr(C)]
123pub struct StyleWordSpacing {
124    pub inner: PixelValue,
125}
126
127impl fmt::Debug for StyleWordSpacing {
128    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
129        write!(f, "{}", self.inner)
130    }
131}
132impl Default for StyleWordSpacing {
133    fn default() -> Self {
134        Self {
135            inner: PixelValue::const_px(0),
136        }
137    }
138}
139impl_pixel_value!(StyleWordSpacing);
140impl PixelValueTaker for StyleWordSpacing {
141    fn from_pixel_value(inner: PixelValue) -> Self {
142        Self { inner }
143    }
144}
145impl PrintAsCssValue for StyleWordSpacing {
146    fn print_as_css_value(&self) -> String {
147        format!("{}", self.inner)
148    }
149}
150
151// -- StyleLineHeight --
152
153/// Represents a `line-height` attribute
154#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
155#[repr(C)]
156pub struct StyleLineHeight {
157    pub inner: PercentageValue,
158}
159impl Default for StyleLineHeight {
160    fn default() -> Self {
161        Self {
162            inner: PercentageValue::const_new(120),
163        }
164    }
165}
166impl_percentage_value!(StyleLineHeight);
167impl PrintAsCssValue for StyleLineHeight {
168    fn print_as_css_value(&self) -> String {
169        format!("{}", self.inner)
170    }
171}
172
173// -- StyleTabWidth --
174
175/// Represents a `tab-width` attribute
176#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
177#[repr(C)]
178pub struct StyleTabWidth {
179    pub inner: PixelValue, // Can be a number (space characters, em-based) or a length
180}
181
182impl fmt::Debug for StyleTabWidth {
183    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
184        write!(f, "{}", self.inner)
185    }
186}
187impl Default for StyleTabWidth {
188    fn default() -> Self {
189        Self {
190            inner: PixelValue::em(8.0),
191        }
192    }
193}
194impl_pixel_value!(StyleTabWidth);
195impl PixelValueTaker for StyleTabWidth {
196    fn from_pixel_value(inner: PixelValue) -> Self {
197        Self { inner }
198    }
199}
200impl PrintAsCssValue for StyleTabWidth {
201    fn print_as_css_value(&self) -> String {
202        format!("{}", self.inner)
203    }
204}
205
206// -- StyleWhiteSpace --
207
208/// How to handle white space inside an element.
209#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
210#[repr(C)]
211pub enum StyleWhiteSpace {
212    Normal,
213    Pre,
214    Nowrap,
215}
216impl Default for StyleWhiteSpace {
217    fn default() -> Self {
218        StyleWhiteSpace::Normal
219    }
220}
221impl_option!(
222    StyleWhiteSpace,
223    OptionStyleWhiteSpace,
224    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
225);
226impl PrintAsCssValue for StyleWhiteSpace {
227    fn print_as_css_value(&self) -> String {
228        String::from(match self {
229            StyleWhiteSpace::Normal => "normal",
230            StyleWhiteSpace::Pre => "pre",
231            StyleWhiteSpace::Nowrap => "nowrap",
232        })
233    }
234}
235
236// -- StyleHyphens --
237
238/// Hyphenation rules.
239#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
240#[repr(C)]
241pub enum StyleHyphens {
242    Auto,
243    None,
244}
245impl Default for StyleHyphens {
246    fn default() -> Self {
247        StyleHyphens::None
248    }
249}
250impl_option!(
251    StyleHyphens,
252    OptionStyleHyphens,
253    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
254);
255impl PrintAsCssValue for StyleHyphens {
256    fn print_as_css_value(&self) -> String {
257        String::from(match self {
258            StyleHyphens::Auto => "auto",
259            StyleHyphens::None => "none",
260        })
261    }
262}
263
264// -- StyleDirection --
265
266/// Text direction.
267#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
268#[repr(C)]
269pub enum StyleDirection {
270    Ltr,
271    Rtl,
272}
273impl Default for StyleDirection {
274    fn default() -> Self {
275        StyleDirection::Ltr
276    }
277}
278impl_option!(
279    StyleDirection,
280    OptionStyleDirection,
281    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
282);
283impl PrintAsCssValue for StyleDirection {
284    fn print_as_css_value(&self) -> String {
285        String::from(match self {
286            StyleDirection::Ltr => "ltr",
287            StyleDirection::Rtl => "rtl",
288        })
289    }
290}
291
292// -- StyleUserSelect --
293
294/// Controls whether the user can select text.
295/// Used to prevent accidental text selection on UI controls like buttons.
296#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
297#[repr(C)]
298pub enum StyleUserSelect {
299    /// Browser determines selectability (default)
300    Auto,
301    /// Text is selectable
302    Text,
303    /// Text is not selectable
304    None,
305    /// User can select all text with a single action
306    All,
307}
308impl Default for StyleUserSelect {
309    fn default() -> Self {
310        StyleUserSelect::Auto
311    }
312}
313impl_option!(
314    StyleUserSelect,
315    OptionStyleUserSelect,
316    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
317);
318impl PrintAsCssValue for StyleUserSelect {
319    fn print_as_css_value(&self) -> String {
320        String::from(match self {
321            StyleUserSelect::Auto => "auto",
322            StyleUserSelect::Text => "text",
323            StyleUserSelect::None => "none",
324            StyleUserSelect::All => "all",
325        })
326    }
327}
328
329// -- StyleTextDecoration --
330
331/// Text decoration (underline, overline, line-through).
332#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
333#[repr(C)]
334pub enum StyleTextDecoration {
335    /// No decoration
336    None,
337    /// Underline
338    Underline,
339    /// Line above text
340    Overline,
341    /// Strike-through line
342    LineThrough,
343}
344impl Default for StyleTextDecoration {
345    fn default() -> Self {
346        StyleTextDecoration::None
347    }
348}
349impl_option!(
350    StyleTextDecoration,
351    OptionStyleTextDecoration,
352    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
353);
354impl PrintAsCssValue for StyleTextDecoration {
355    fn print_as_css_value(&self) -> String {
356        String::from(match self {
357            StyleTextDecoration::None => "none",
358            StyleTextDecoration::Underline => "underline",
359            StyleTextDecoration::Overline => "overline",
360            StyleTextDecoration::LineThrough => "line-through",
361        })
362    }
363}
364
365// -- StyleVerticalAlign --
366
367/// Vertical text alignment enum (top, center, bottom) - default: `Top`
368#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
369#[repr(C)]
370pub enum StyleVerticalAlign {
371    /// CSS default - align baselines
372    Baseline,
373    /// Align top of element with top of line box
374    Top,
375    /// Align middle of element with baseline + half x-height
376    Middle,
377    /// Align bottom of element with bottom of line box
378    Bottom,
379    /// Align baseline with parent's subscript baseline
380    Sub,
381    /// Align baseline with parent's superscript baseline
382    Superscript,
383    /// Align top with top of parent's font
384    TextTop,
385    /// Align bottom with bottom of parent's font
386    TextBottom,
387}
388
389impl_option!(
390    StyleVerticalAlign,
391    OptionStyleVerticalAlign,
392    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
393);
394
395impl Default for StyleVerticalAlign {
396    fn default() -> Self {
397        StyleVerticalAlign::Baseline
398    }
399}
400impl PrintAsCssValue for StyleVerticalAlign {
401    fn print_as_css_value(&self) -> String {
402        String::from(match self {
403            StyleVerticalAlign::Baseline => "baseline",
404            StyleVerticalAlign::Top => "top",
405            StyleVerticalAlign::Middle => "middle",
406            StyleVerticalAlign::Bottom => "bottom",
407            StyleVerticalAlign::Sub => "sub",
408            StyleVerticalAlign::Superscript => "super",
409            StyleVerticalAlign::TextTop => "text-top",
410            StyleVerticalAlign::TextBottom => "text-bottom",
411        })
412    }
413}
414
415impl crate::format_rust_code::FormatAsRustCode for StyleVerticalAlign {
416    fn format_as_rust_code(&self, _: usize) -> String {
417        match self {
418            StyleVerticalAlign::Baseline => "StyleVerticalAlign::Baseline",
419            StyleVerticalAlign::Top => "StyleVerticalAlign::Top",
420            StyleVerticalAlign::Middle => "StyleVerticalAlign::Middle",
421            StyleVerticalAlign::Bottom => "StyleVerticalAlign::Bottom",
422            StyleVerticalAlign::Sub => "StyleVerticalAlign::Sub",
423            StyleVerticalAlign::Superscript => "StyleVerticalAlign::Superscript",
424            StyleVerticalAlign::TextTop => "StyleVerticalAlign::TextTop",
425            StyleVerticalAlign::TextBottom => "StyleVerticalAlign::TextBottom",
426        }
427        .to_string()
428    }
429}
430
431// --- PARSERS ---
432
433#[cfg(feature = "parser")]
434use crate::props::basic::{
435    color::{parse_css_color, CssColorParseError, CssColorParseErrorOwned},
436    DurationParseError,
437};
438
439#[cfg(feature = "parser")]
440#[derive(Clone, PartialEq)]
441pub enum StyleTextColorParseError<'a> {
442    ColorParseError(CssColorParseError<'a>),
443}
444#[cfg(feature = "parser")]
445impl_debug_as_display!(StyleTextColorParseError<'a>);
446#[cfg(feature = "parser")]
447impl_display! { StyleTextColorParseError<'a>, {
448    ColorParseError(e) => format!("Invalid color: {}", e),
449}}
450#[cfg(feature = "parser")]
451impl_from!(
452    CssColorParseError<'a>,
453    StyleTextColorParseError::ColorParseError
454);
455
456#[cfg(feature = "parser")]
457#[derive(Debug, Clone, PartialEq)]
458pub enum StyleTextColorParseErrorOwned {
459    ColorParseError(CssColorParseErrorOwned),
460}
461
462#[cfg(feature = "parser")]
463impl<'a> StyleTextColorParseError<'a> {
464    pub fn to_contained(&self) -> StyleTextColorParseErrorOwned {
465        match self {
466            Self::ColorParseError(e) => {
467                StyleTextColorParseErrorOwned::ColorParseError(e.to_contained())
468            }
469        }
470    }
471}
472
473#[cfg(feature = "parser")]
474impl StyleTextColorParseErrorOwned {
475    pub fn to_shared<'a>(&'a self) -> StyleTextColorParseError<'a> {
476        match self {
477            Self::ColorParseError(e) => StyleTextColorParseError::ColorParseError(e.to_shared()),
478        }
479    }
480}
481
482#[cfg(feature = "parser")]
483pub fn parse_style_text_color(input: &str) -> Result<StyleTextColor, StyleTextColorParseError> {
484    parse_css_color(input)
485        .map(|inner| StyleTextColor { inner })
486        .map_err(|e| StyleTextColorParseError::ColorParseError(e))
487}
488
489#[cfg(feature = "parser")]
490#[derive(Clone, PartialEq)]
491pub enum StyleTextAlignParseError<'a> {
492    InvalidValue(InvalidValueErr<'a>),
493}
494#[cfg(feature = "parser")]
495impl_debug_as_display!(StyleTextAlignParseError<'a>);
496#[cfg(feature = "parser")]
497impl_display! { StyleTextAlignParseError<'a>, {
498    InvalidValue(e) => format!("Invalid text-align value: \"{}\"", e.0),
499}}
500#[cfg(feature = "parser")]
501impl_from!(InvalidValueErr<'a>, StyleTextAlignParseError::InvalidValue);
502
503#[cfg(feature = "parser")]
504#[derive(Debug, Clone, PartialEq)]
505pub enum StyleTextAlignParseErrorOwned {
506    InvalidValue(InvalidValueErrOwned),
507}
508
509#[cfg(feature = "parser")]
510impl<'a> StyleTextAlignParseError<'a> {
511    pub fn to_contained(&self) -> StyleTextAlignParseErrorOwned {
512        match self {
513            Self::InvalidValue(e) => StyleTextAlignParseErrorOwned::InvalidValue(e.to_contained()),
514        }
515    }
516}
517
518#[cfg(feature = "parser")]
519impl StyleTextAlignParseErrorOwned {
520    pub fn to_shared<'a>(&'a self) -> StyleTextAlignParseError<'a> {
521        match self {
522            Self::InvalidValue(e) => StyleTextAlignParseError::InvalidValue(e.to_shared()),
523        }
524    }
525}
526
527#[cfg(feature = "parser")]
528pub fn parse_style_text_align(input: &str) -> Result<StyleTextAlign, StyleTextAlignParseError> {
529    match input.trim() {
530        "left" => Ok(StyleTextAlign::Left),
531        "center" => Ok(StyleTextAlign::Center),
532        "right" => Ok(StyleTextAlign::Right),
533        "justify" => Ok(StyleTextAlign::Justify),
534        "start" => Ok(StyleTextAlign::Start),
535        "end" => Ok(StyleTextAlign::End),
536        other => Err(StyleTextAlignParseError::InvalidValue(InvalidValueErr(
537            other,
538        ))),
539    }
540}
541
542#[cfg(feature = "parser")]
543#[derive(Clone, PartialEq)]
544pub enum StyleLetterSpacingParseError<'a> {
545    PixelValue(CssPixelValueParseError<'a>),
546}
547#[cfg(feature = "parser")]
548impl_debug_as_display!(StyleLetterSpacingParseError<'a>);
549#[cfg(feature = "parser")]
550impl_display! { StyleLetterSpacingParseError<'a>, {
551    PixelValue(e) => format!("Invalid letter-spacing value: {}", e),
552}}
553#[cfg(feature = "parser")]
554impl_from!(
555    CssPixelValueParseError<'a>,
556    StyleLetterSpacingParseError::PixelValue
557);
558
559#[cfg(feature = "parser")]
560#[derive(Debug, Clone, PartialEq)]
561pub enum StyleLetterSpacingParseErrorOwned {
562    PixelValue(CssPixelValueParseErrorOwned),
563}
564
565#[cfg(feature = "parser")]
566impl<'a> StyleLetterSpacingParseError<'a> {
567    pub fn to_contained(&self) -> StyleLetterSpacingParseErrorOwned {
568        match self {
569            Self::PixelValue(e) => StyleLetterSpacingParseErrorOwned::PixelValue(e.to_contained()),
570        }
571    }
572}
573
574#[cfg(feature = "parser")]
575impl StyleLetterSpacingParseErrorOwned {
576    pub fn to_shared<'a>(&'a self) -> StyleLetterSpacingParseError<'a> {
577        match self {
578            Self::PixelValue(e) => StyleLetterSpacingParseError::PixelValue(e.to_shared()),
579        }
580    }
581}
582
583#[cfg(feature = "parser")]
584pub fn parse_style_letter_spacing(
585    input: &str,
586) -> Result<StyleLetterSpacing, StyleLetterSpacingParseError> {
587    crate::props::basic::pixel::parse_pixel_value(input)
588        .map(|inner| StyleLetterSpacing { inner })
589        .map_err(|e| StyleLetterSpacingParseError::PixelValue(e))
590}
591
592// -- StyleTextIndent (text-indent property) --
593
594/// Represents a `text-indent` attribute (indentation of first line in a block).
595#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
596#[repr(C)]
597pub struct StyleTextIndent {
598    pub inner: PixelValue,
599}
600
601impl fmt::Debug for StyleTextIndent {
602    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
603        write!(f, "{}", self.print_as_css_value())
604    }
605}
606
607impl_pixel_value!(StyleTextIndent);
608
609impl PrintAsCssValue for StyleTextIndent {
610    fn print_as_css_value(&self) -> String {
611        self.inner.to_string()
612    }
613}
614
615impl crate::format_rust_code::FormatAsRustCode for StyleTextIndent {
616    fn format_as_rust_code(&self, _tabs: usize) -> String {
617        format!(
618            "StyleTextIndent {{ inner: PixelValue::const_px(0) /* {} */ }}",
619            self.inner
620        )
621    }
622}
623
624#[cfg(feature = "parser")]
625#[derive(Clone, PartialEq)]
626pub enum StyleTextIndentParseError<'a> {
627    PixelValue(CssPixelValueParseError<'a>),
628}
629#[cfg(feature = "parser")]
630impl_debug_as_display!(StyleTextIndentParseError<'a>);
631#[cfg(feature = "parser")]
632impl_display! { StyleTextIndentParseError<'a>, {
633    PixelValue(e) => format!("Invalid text-indent value: {}", e),
634}}
635#[cfg(feature = "parser")]
636impl_from!(
637    CssPixelValueParseError<'a>,
638    StyleTextIndentParseError::PixelValue
639);
640
641#[cfg(feature = "parser")]
642#[derive(Debug, Clone, PartialEq)]
643pub enum StyleTextIndentParseErrorOwned {
644    PixelValue(CssPixelValueParseErrorOwned),
645}
646
647#[cfg(feature = "parser")]
648impl<'a> StyleTextIndentParseError<'a> {
649    pub fn to_contained(&self) -> StyleTextIndentParseErrorOwned {
650        match self {
651            Self::PixelValue(e) => StyleTextIndentParseErrorOwned::PixelValue(e.to_contained()),
652        }
653    }
654}
655
656#[cfg(feature = "parser")]
657impl StyleTextIndentParseErrorOwned {
658    pub fn to_shared<'a>(&'a self) -> StyleTextIndentParseError<'a> {
659        match self {
660            Self::PixelValue(e) => StyleTextIndentParseError::PixelValue(e.to_shared()),
661        }
662    }
663}
664
665#[cfg(feature = "parser")]
666pub fn parse_style_text_indent(input: &str) -> Result<StyleTextIndent, StyleTextIndentParseError> {
667    crate::props::basic::pixel::parse_pixel_value(input)
668        .map(|inner| StyleTextIndent { inner })
669        .map_err(|e| StyleTextIndentParseError::PixelValue(e))
670}
671
672/// initial-letter property for drop caps
673#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
674#[repr(C)]
675pub struct StyleInitialLetter {
676    pub size: u32,
677    pub sink: crate::corety::OptionU32,
678}
679
680impl FormatAsRustCode for StyleInitialLetter {
681    fn format_as_rust_code(&self, _tabs: usize) -> String {
682        format!("{:?}", self)
683    }
684}
685
686impl PrintAsCssValue for StyleInitialLetter {
687    fn print_as_css_value(&self) -> String {
688        if let crate::corety::OptionU32::Some(sink) = self.sink {
689            format!("{} {}", self.size, sink)
690        } else {
691            format!("{}", self.size)
692        }
693    }
694}
695
696#[cfg(feature = "parser")]
697#[derive(Clone, PartialEq)]
698pub enum StyleInitialLetterParseError<'a> {
699    InvalidFormat(&'a str),
700    InvalidSize(&'a str),
701    InvalidSink(&'a str),
702}
703#[cfg(feature = "parser")]
704impl_debug_as_display!(StyleInitialLetterParseError<'a>);
705#[cfg(feature = "parser")]
706impl_display! { StyleInitialLetterParseError<'a>, {
707    InvalidFormat(e) => format!("Invalid initial-letter format: {}", e),
708    InvalidSize(e) => format!("Invalid initial-letter size: {}", e),
709    InvalidSink(e) => format!("Invalid initial-letter sink: {}", e),
710}}
711
712#[cfg(feature = "parser")]
713#[derive(Debug, Clone, PartialEq)]
714pub enum StyleInitialLetterParseErrorOwned {
715    InvalidFormat(String),
716    InvalidSize(String),
717    InvalidSink(String),
718}
719
720#[cfg(feature = "parser")]
721impl<'a> StyleInitialLetterParseError<'a> {
722    pub fn to_contained(&self) -> StyleInitialLetterParseErrorOwned {
723        match self {
724            Self::InvalidFormat(s) => {
725                StyleInitialLetterParseErrorOwned::InvalidFormat(s.to_string())
726            }
727            Self::InvalidSize(s) => StyleInitialLetterParseErrorOwned::InvalidSize(s.to_string()),
728            Self::InvalidSink(s) => StyleInitialLetterParseErrorOwned::InvalidSink(s.to_string()),
729        }
730    }
731}
732
733#[cfg(feature = "parser")]
734impl StyleInitialLetterParseErrorOwned {
735    pub fn to_shared<'a>(&'a self) -> StyleInitialLetterParseError<'a> {
736        match self {
737            Self::InvalidFormat(s) => StyleInitialLetterParseError::InvalidFormat(s.as_str()),
738            Self::InvalidSize(s) => StyleInitialLetterParseError::InvalidSize(s.as_str()),
739            Self::InvalidSink(s) => StyleInitialLetterParseError::InvalidSink(s.as_str()),
740        }
741    }
742}
743
744#[cfg(feature = "parser")]
745impl From<StyleInitialLetterParseError<'_>> for StyleInitialLetterParseErrorOwned {
746    fn from(e: StyleInitialLetterParseError) -> Self {
747        match e {
748            StyleInitialLetterParseError::InvalidFormat(s) => {
749                StyleInitialLetterParseErrorOwned::InvalidFormat(s.to_string())
750            }
751            StyleInitialLetterParseError::InvalidSize(s) => {
752                StyleInitialLetterParseErrorOwned::InvalidSize(s.to_string())
753            }
754            StyleInitialLetterParseError::InvalidSink(s) => {
755                StyleInitialLetterParseErrorOwned::InvalidSink(s.to_string())
756            }
757        }
758    }
759}
760
761#[cfg(feature = "parser")]
762impl_display! { StyleInitialLetterParseErrorOwned, {
763    InvalidFormat(e) => format!("Invalid initial-letter format: {}", e),
764    InvalidSize(e) => format!("Invalid initial-letter size: {}", e),
765    InvalidSink(e) => format!("Invalid initial-letter sink: {}", e),
766}}
767
768#[cfg(feature = "parser")]
769pub fn parse_style_initial_letter<'a>(
770    input: &'a str,
771) -> Result<StyleInitialLetter, StyleInitialLetterParseError<'a>> {
772    let input = input.trim();
773    let parts: Vec<&str> = input.split_whitespace().collect();
774
775    if parts.is_empty() {
776        return Err(StyleInitialLetterParseError::InvalidFormat(input));
777    }
778
779    // Parse size (required)
780    let size = parts[0]
781        .parse::<u32>()
782        .map_err(|_| StyleInitialLetterParseError::InvalidSize(parts[0]))?;
783
784    if size == 0 {
785        return Err(StyleInitialLetterParseError::InvalidSize(parts[0]));
786    }
787
788    // Parse sink (optional)
789    let sink = if parts.len() > 1 {
790        crate::corety::OptionU32::Some(
791            parts[1]
792                .parse::<u32>()
793                .map_err(|_| StyleInitialLetterParseError::InvalidSink(parts[1]))?,
794        )
795    } else {
796        crate::corety::OptionU32::None
797    };
798
799    Ok(StyleInitialLetter { size, sink })
800}
801
802/// line-clamp property for limiting visible lines
803#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
804#[repr(C)]
805pub struct StyleLineClamp {
806    pub max_lines: usize,
807}
808
809impl FormatAsRustCode for StyleLineClamp {
810    fn format_as_rust_code(&self, _tabs: usize) -> String {
811        format!("{:?}", self)
812    }
813}
814
815impl PrintAsCssValue for StyleLineClamp {
816    fn print_as_css_value(&self) -> String {
817        format!("{}", self.max_lines)
818    }
819}
820
821#[cfg(feature = "parser")]
822#[derive(Clone, PartialEq)]
823pub enum StyleLineClampParseError<'a> {
824    InvalidValue(&'a str),
825    ZeroValue,
826}
827#[cfg(feature = "parser")]
828impl_debug_as_display!(StyleLineClampParseError<'a>);
829#[cfg(feature = "parser")]
830impl_display! { StyleLineClampParseError<'a>, {
831    InvalidValue(e) => format!("Invalid line-clamp value: {}", e),
832    ZeroValue => format!("line-clamp cannot be zero"),
833}}
834
835#[cfg(feature = "parser")]
836#[derive(Debug, Clone, PartialEq)]
837pub enum StyleLineClampParseErrorOwned {
838    InvalidValue(String),
839    ZeroValue,
840}
841
842#[cfg(feature = "parser")]
843impl<'a> StyleLineClampParseError<'a> {
844    pub fn to_contained(&self) -> StyleLineClampParseErrorOwned {
845        match self {
846            Self::InvalidValue(s) => StyleLineClampParseErrorOwned::InvalidValue(s.to_string()),
847            Self::ZeroValue => StyleLineClampParseErrorOwned::ZeroValue,
848        }
849    }
850}
851
852#[cfg(feature = "parser")]
853impl StyleLineClampParseErrorOwned {
854    pub fn to_shared<'a>(&'a self) -> StyleLineClampParseError<'a> {
855        match self {
856            Self::InvalidValue(s) => StyleLineClampParseError::InvalidValue(s.as_str()),
857            Self::ZeroValue => StyleLineClampParseError::ZeroValue,
858        }
859    }
860}
861
862#[cfg(feature = "parser")]
863impl From<StyleLineClampParseError<'_>> for StyleLineClampParseErrorOwned {
864    fn from(e: StyleLineClampParseError) -> Self {
865        e.to_contained()
866    }
867}
868
869#[cfg(feature = "parser")]
870impl_display! { StyleLineClampParseErrorOwned, {
871    InvalidValue(e) => format!("Invalid line-clamp value: {}", e),
872    ZeroValue => format!("line-clamp cannot be zero"),
873}}
874
875#[cfg(feature = "parser")]
876pub fn parse_style_line_clamp<'a>(
877    input: &'a str,
878) -> Result<StyleLineClamp, StyleLineClampParseError<'a>> {
879    let input = input.trim();
880
881    let max_lines = input
882        .parse::<usize>()
883        .map_err(|_| StyleLineClampParseError::InvalidValue(input))?;
884
885    if max_lines == 0 {
886        return Err(StyleLineClampParseError::ZeroValue);
887    }
888
889    Ok(StyleLineClamp { max_lines })
890}
891
892/// hanging-punctuation property for hanging punctuation marks
893#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
894#[repr(C)]
895pub struct StyleHangingPunctuation {
896    pub enabled: bool,
897}
898
899impl Default for StyleHangingPunctuation {
900    fn default() -> Self {
901        Self { enabled: false }
902    }
903}
904
905impl FormatAsRustCode for StyleHangingPunctuation {
906    fn format_as_rust_code(&self, _tabs: usize) -> String {
907        format!("{:?}", self)
908    }
909}
910
911impl PrintAsCssValue for StyleHangingPunctuation {
912    fn print_as_css_value(&self) -> String {
913        if self.enabled {
914            "first allow-end last force-end".to_string()
915        } else {
916            "none".to_string()
917        }
918    }
919}
920
921#[cfg(feature = "parser")]
922#[derive(Clone, PartialEq)]
923pub enum StyleHangingPunctuationParseError<'a> {
924    InvalidValue(&'a str),
925}
926#[cfg(feature = "parser")]
927impl_debug_as_display!(StyleHangingPunctuationParseError<'a>);
928#[cfg(feature = "parser")]
929impl_display! { StyleHangingPunctuationParseError<'a>, {
930    InvalidValue(e) => format!("Invalid hanging-punctuation value: {}", e),
931}}
932
933#[cfg(feature = "parser")]
934#[derive(Debug, Clone, PartialEq)]
935pub enum StyleHangingPunctuationParseErrorOwned {
936    InvalidValue(String),
937}
938
939#[cfg(feature = "parser")]
940impl<'a> StyleHangingPunctuationParseError<'a> {
941    pub fn to_contained(&self) -> StyleHangingPunctuationParseErrorOwned {
942        match self {
943            Self::InvalidValue(s) => {
944                StyleHangingPunctuationParseErrorOwned::InvalidValue(s.to_string())
945            }
946        }
947    }
948}
949
950#[cfg(feature = "parser")]
951impl StyleHangingPunctuationParseErrorOwned {
952    pub fn to_shared<'a>(&'a self) -> StyleHangingPunctuationParseError<'a> {
953        match self {
954            Self::InvalidValue(s) => StyleHangingPunctuationParseError::InvalidValue(s.as_str()),
955        }
956    }
957}
958
959#[cfg(feature = "parser")]
960impl From<StyleHangingPunctuationParseError<'_>> for StyleHangingPunctuationParseErrorOwned {
961    fn from(e: StyleHangingPunctuationParseError) -> Self {
962        e.to_contained()
963    }
964}
965
966#[cfg(feature = "parser")]
967impl_display! { StyleHangingPunctuationParseErrorOwned, {
968    InvalidValue(e) => format!("Invalid hanging-punctuation value: {}", e),
969}}
970
971#[cfg(feature = "parser")]
972pub fn parse_style_hanging_punctuation<'a>(
973    input: &'a str,
974) -> Result<StyleHangingPunctuation, StyleHangingPunctuationParseError<'a>> {
975    let input = input.trim().to_lowercase();
976
977    // For simplicity: "none" = disabled, anything else = enabled
978    // Full spec supports: first, last, force-end, allow-end
979    let enabled = input != "none";
980
981    Ok(StyleHangingPunctuation { enabled })
982}
983
984/// text-combine-upright property for combining horizontal text in vertical layout
985#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
986#[repr(C, u8)]
987pub enum StyleTextCombineUpright {
988    None,
989    All,
990    Digits(u8),
991}
992
993impl Default for StyleTextCombineUpright {
994    fn default() -> Self {
995        Self::None
996    }
997}
998
999impl FormatAsRustCode for StyleTextCombineUpright {
1000    fn format_as_rust_code(&self, _tabs: usize) -> String {
1001        format!("{:?}", self)
1002    }
1003}
1004
1005impl PrintAsCssValue for StyleTextCombineUpright {
1006    fn print_as_css_value(&self) -> String {
1007        match self {
1008            Self::None => "none".to_string(),
1009            Self::All => "all".to_string(),
1010            Self::Digits(n) => format!("digits {}", n),
1011        }
1012    }
1013}
1014
1015#[cfg(feature = "parser")]
1016#[derive(Clone, PartialEq)]
1017pub enum StyleTextCombineUprightParseError<'a> {
1018    InvalidValue(&'a str),
1019    InvalidDigits(&'a str),
1020}
1021#[cfg(feature = "parser")]
1022impl_debug_as_display!(StyleTextCombineUprightParseError<'a>);
1023#[cfg(feature = "parser")]
1024impl_display! { StyleTextCombineUprightParseError<'a>, {
1025    InvalidValue(e) => format!("Invalid text-combine-upright value: {}", e),
1026    InvalidDigits(e) => format!("Invalid text-combine-upright digits: {}", e),
1027}}
1028
1029#[cfg(feature = "parser")]
1030#[derive(Debug, Clone, PartialEq)]
1031pub enum StyleTextCombineUprightParseErrorOwned {
1032    InvalidValue(String),
1033    InvalidDigits(String),
1034}
1035
1036#[cfg(feature = "parser")]
1037impl<'a> StyleTextCombineUprightParseError<'a> {
1038    pub fn to_contained(&self) -> StyleTextCombineUprightParseErrorOwned {
1039        match self {
1040            Self::InvalidValue(s) => {
1041                StyleTextCombineUprightParseErrorOwned::InvalidValue(s.to_string())
1042            }
1043            Self::InvalidDigits(s) => {
1044                StyleTextCombineUprightParseErrorOwned::InvalidDigits(s.to_string())
1045            }
1046        }
1047    }
1048}
1049
1050#[cfg(feature = "parser")]
1051impl StyleTextCombineUprightParseErrorOwned {
1052    pub fn to_shared<'a>(&'a self) -> StyleTextCombineUprightParseError<'a> {
1053        match self {
1054            Self::InvalidValue(s) => StyleTextCombineUprightParseError::InvalidValue(s.as_str()),
1055            Self::InvalidDigits(s) => StyleTextCombineUprightParseError::InvalidDigits(s.as_str()),
1056        }
1057    }
1058}
1059
1060#[cfg(feature = "parser")]
1061impl From<StyleTextCombineUprightParseError<'_>> for StyleTextCombineUprightParseErrorOwned {
1062    fn from(e: StyleTextCombineUprightParseError) -> Self {
1063        e.to_contained()
1064    }
1065}
1066
1067#[cfg(feature = "parser")]
1068impl_display! { StyleTextCombineUprightParseErrorOwned, {
1069    InvalidValue(e) => format!("Invalid text-combine-upright value: {}", e),
1070    InvalidDigits(e) => format!("Invalid text-combine-upright digits: {}", e),
1071}}
1072
1073#[cfg(feature = "parser")]
1074pub fn parse_style_text_combine_upright<'a>(
1075    input: &'a str,
1076) -> Result<StyleTextCombineUpright, StyleTextCombineUprightParseError<'a>> {
1077    let trimmed = input.trim();
1078
1079    if trimmed.eq_ignore_ascii_case("none") {
1080        Ok(StyleTextCombineUpright::None)
1081    } else if trimmed.eq_ignore_ascii_case("all") {
1082        Ok(StyleTextCombineUpright::All)
1083    } else if trimmed.starts_with("digits") {
1084        let parts: Vec<&str> = trimmed.split_whitespace().collect();
1085        if parts.len() == 2 {
1086            let n = parts[1]
1087                .parse::<u8>()
1088                .map_err(|_| StyleTextCombineUprightParseError::InvalidDigits(input))?;
1089            if n >= 2 && n <= 4 {
1090                Ok(StyleTextCombineUpright::Digits(n))
1091            } else {
1092                Err(StyleTextCombineUprightParseError::InvalidDigits(input))
1093            }
1094        } else {
1095            // Default to "digits 2"
1096            Ok(StyleTextCombineUpright::Digits(2))
1097        }
1098    } else {
1099        Err(StyleTextCombineUprightParseError::InvalidValue(input))
1100    }
1101}
1102
1103#[cfg(feature = "parser")]
1104#[derive(Clone, PartialEq)]
1105pub enum StyleWordSpacingParseError<'a> {
1106    PixelValue(CssPixelValueParseError<'a>),
1107}
1108#[cfg(feature = "parser")]
1109impl_debug_as_display!(StyleWordSpacingParseError<'a>);
1110#[cfg(feature = "parser")]
1111impl_display! { StyleWordSpacingParseError<'a>, {
1112    PixelValue(e) => format!("Invalid word-spacing value: {}", e),
1113}}
1114#[cfg(feature = "parser")]
1115impl_from!(
1116    CssPixelValueParseError<'a>,
1117    StyleWordSpacingParseError::PixelValue
1118);
1119
1120#[cfg(feature = "parser")]
1121#[derive(Debug, Clone, PartialEq)]
1122pub enum StyleWordSpacingParseErrorOwned {
1123    PixelValue(CssPixelValueParseErrorOwned),
1124}
1125
1126#[cfg(feature = "parser")]
1127impl<'a> StyleWordSpacingParseError<'a> {
1128    pub fn to_contained(&self) -> StyleWordSpacingParseErrorOwned {
1129        match self {
1130            Self::PixelValue(e) => StyleWordSpacingParseErrorOwned::PixelValue(e.to_contained()),
1131        }
1132    }
1133}
1134
1135#[cfg(feature = "parser")]
1136impl StyleWordSpacingParseErrorOwned {
1137    pub fn to_shared<'a>(&'a self) -> StyleWordSpacingParseError<'a> {
1138        match self {
1139            Self::PixelValue(e) => StyleWordSpacingParseError::PixelValue(e.to_shared()),
1140        }
1141    }
1142}
1143
1144#[cfg(feature = "parser")]
1145pub fn parse_style_word_spacing(
1146    input: &str,
1147) -> Result<StyleWordSpacing, StyleWordSpacingParseError> {
1148    crate::props::basic::pixel::parse_pixel_value(input)
1149        .map(|inner| StyleWordSpacing { inner })
1150        .map_err(|e| StyleWordSpacingParseError::PixelValue(e))
1151}
1152
1153#[cfg(feature = "parser")]
1154#[derive(Clone, PartialEq)]
1155pub enum StyleLineHeightParseError {
1156    Percentage(PercentageParseError),
1157}
1158#[cfg(feature = "parser")]
1159impl_debug_as_display!(StyleLineHeightParseError);
1160#[cfg(feature = "parser")]
1161impl_display! { StyleLineHeightParseError, {
1162    Percentage(e) => format!("Invalid line-height value: {}", e),
1163}}
1164#[cfg(feature = "parser")]
1165impl_from!(PercentageParseError, StyleLineHeightParseError::Percentage);
1166
1167#[cfg(feature = "parser")]
1168#[derive(Debug, Clone, PartialEq)]
1169pub enum StyleLineHeightParseErrorOwned {
1170    Percentage(PercentageParseErrorOwned),
1171}
1172
1173#[cfg(feature = "parser")]
1174impl StyleLineHeightParseError {
1175    pub fn to_contained(&self) -> StyleLineHeightParseErrorOwned {
1176        match self {
1177            Self::Percentage(e) => StyleLineHeightParseErrorOwned::Percentage(e.to_contained()),
1178        }
1179    }
1180}
1181
1182#[cfg(feature = "parser")]
1183impl StyleLineHeightParseErrorOwned {
1184    pub fn to_shared(&self) -> StyleLineHeightParseError {
1185        match self {
1186            Self::Percentage(e) => StyleLineHeightParseError::Percentage(e.to_shared()),
1187        }
1188    }
1189}
1190
1191#[cfg(feature = "parser")]
1192pub fn parse_style_line_height(input: &str) -> Result<StyleLineHeight, StyleLineHeightParseError> {
1193    crate::props::basic::length::parse_percentage_value(input)
1194        .map(|inner| StyleLineHeight { inner })
1195        .map_err(|e| StyleLineHeightParseError::Percentage(e))
1196}
1197
1198#[cfg(feature = "parser")]
1199#[derive(Clone, PartialEq)]
1200pub enum StyleTabWidthParseError<'a> {
1201    PixelValue(CssPixelValueParseError<'a>),
1202}
1203#[cfg(feature = "parser")]
1204impl_debug_as_display!(StyleTabWidthParseError<'a>);
1205#[cfg(feature = "parser")]
1206impl_display! { StyleTabWidthParseError<'a>, {
1207    PixelValue(e) => format!("Invalid tab-width value: {}", e),
1208}}
1209#[cfg(feature = "parser")]
1210impl_from!(
1211    CssPixelValueParseError<'a>,
1212    StyleTabWidthParseError::PixelValue
1213);
1214
1215#[cfg(feature = "parser")]
1216#[derive(Debug, Clone, PartialEq)]
1217pub enum StyleTabWidthParseErrorOwned {
1218    PixelValue(CssPixelValueParseErrorOwned),
1219}
1220
1221#[cfg(feature = "parser")]
1222impl<'a> StyleTabWidthParseError<'a> {
1223    pub fn to_contained(&self) -> StyleTabWidthParseErrorOwned {
1224        match self {
1225            Self::PixelValue(e) => StyleTabWidthParseErrorOwned::PixelValue(e.to_contained()),
1226        }
1227    }
1228}
1229
1230#[cfg(feature = "parser")]
1231impl StyleTabWidthParseErrorOwned {
1232    pub fn to_shared<'a>(&'a self) -> StyleTabWidthParseError<'a> {
1233        match self {
1234            Self::PixelValue(e) => StyleTabWidthParseError::PixelValue(e.to_shared()),
1235        }
1236    }
1237}
1238
1239#[cfg(feature = "parser")]
1240pub fn parse_style_tab_width(input: &str) -> Result<StyleTabWidth, StyleTabWidthParseError> {
1241    if let Ok(number) = input.trim().parse::<f32>() {
1242        Ok(StyleTabWidth {
1243            inner: PixelValue::em(number),
1244        })
1245    } else {
1246        crate::props::basic::pixel::parse_pixel_value(input)
1247            .map(|v| StyleTabWidth { inner: v })
1248            .map_err(|e| StyleTabWidthParseError::PixelValue(e))
1249    }
1250}
1251
1252#[cfg(feature = "parser")]
1253#[derive(Clone, PartialEq)]
1254pub enum StyleWhiteSpaceParseError<'a> {
1255    InvalidValue(InvalidValueErr<'a>),
1256}
1257#[cfg(feature = "parser")]
1258impl_debug_as_display!(StyleWhiteSpaceParseError<'a>);
1259#[cfg(feature = "parser")]
1260impl_display! { StyleWhiteSpaceParseError<'a>, {
1261    InvalidValue(e) => format!("Invalid white-space value: \"{}\"", e.0),
1262}}
1263#[cfg(feature = "parser")]
1264impl_from!(InvalidValueErr<'a>, StyleWhiteSpaceParseError::InvalidValue);
1265
1266#[cfg(feature = "parser")]
1267#[derive(Debug, Clone, PartialEq)]
1268pub enum StyleWhiteSpaceParseErrorOwned {
1269    InvalidValue(InvalidValueErrOwned),
1270}
1271
1272#[cfg(feature = "parser")]
1273impl<'a> StyleWhiteSpaceParseError<'a> {
1274    pub fn to_contained(&self) -> StyleWhiteSpaceParseErrorOwned {
1275        match self {
1276            Self::InvalidValue(e) => StyleWhiteSpaceParseErrorOwned::InvalidValue(e.to_contained()),
1277        }
1278    }
1279}
1280
1281#[cfg(feature = "parser")]
1282impl StyleWhiteSpaceParseErrorOwned {
1283    pub fn to_shared<'a>(&'a self) -> StyleWhiteSpaceParseError<'a> {
1284        match self {
1285            Self::InvalidValue(e) => StyleWhiteSpaceParseError::InvalidValue(e.to_shared()),
1286        }
1287    }
1288}
1289
1290#[cfg(feature = "parser")]
1291pub fn parse_style_white_space(input: &str) -> Result<StyleWhiteSpace, StyleWhiteSpaceParseError> {
1292    match input.trim() {
1293        "normal" => Ok(StyleWhiteSpace::Normal),
1294        "pre" => Ok(StyleWhiteSpace::Pre),
1295        "nowrap" => Ok(StyleWhiteSpace::Nowrap),
1296        other => Err(StyleWhiteSpaceParseError::InvalidValue(InvalidValueErr(
1297            other,
1298        ))),
1299    }
1300}
1301
1302#[cfg(feature = "parser")]
1303#[derive(Clone, PartialEq)]
1304pub enum StyleHyphensParseError<'a> {
1305    InvalidValue(InvalidValueErr<'a>),
1306}
1307#[cfg(feature = "parser")]
1308impl_debug_as_display!(StyleHyphensParseError<'a>);
1309#[cfg(feature = "parser")]
1310impl_display! { StyleHyphensParseError<'a>, {
1311    InvalidValue(e) => format!("Invalid hyphens value: \"{}\"", e.0),
1312}}
1313#[cfg(feature = "parser")]
1314impl_from!(InvalidValueErr<'a>, StyleHyphensParseError::InvalidValue);
1315
1316#[cfg(feature = "parser")]
1317#[derive(Debug, Clone, PartialEq)]
1318pub enum StyleHyphensParseErrorOwned {
1319    InvalidValue(InvalidValueErrOwned),
1320}
1321
1322#[cfg(feature = "parser")]
1323impl<'a> StyleHyphensParseError<'a> {
1324    pub fn to_contained(&self) -> StyleHyphensParseErrorOwned {
1325        match self {
1326            Self::InvalidValue(e) => StyleHyphensParseErrorOwned::InvalidValue(e.to_contained()),
1327        }
1328    }
1329}
1330
1331#[cfg(feature = "parser")]
1332impl StyleHyphensParseErrorOwned {
1333    pub fn to_shared<'a>(&'a self) -> StyleHyphensParseError<'a> {
1334        match self {
1335            Self::InvalidValue(e) => StyleHyphensParseError::InvalidValue(e.to_shared()),
1336        }
1337    }
1338}
1339
1340#[cfg(feature = "parser")]
1341pub fn parse_style_hyphens(input: &str) -> Result<StyleHyphens, StyleHyphensParseError> {
1342    match input.trim() {
1343        "auto" => Ok(StyleHyphens::Auto),
1344        "none" => Ok(StyleHyphens::None),
1345        other => Err(StyleHyphensParseError::InvalidValue(InvalidValueErr(other))),
1346    }
1347}
1348
1349#[cfg(feature = "parser")]
1350#[derive(Clone, PartialEq)]
1351pub enum StyleDirectionParseError<'a> {
1352    InvalidValue(InvalidValueErr<'a>),
1353}
1354#[cfg(feature = "parser")]
1355impl_debug_as_display!(StyleDirectionParseError<'a>);
1356#[cfg(feature = "parser")]
1357impl_display! { StyleDirectionParseError<'a>, {
1358    InvalidValue(e) => format!("Invalid direction value: \"{}\"", e.0),
1359}}
1360#[cfg(feature = "parser")]
1361impl_from!(InvalidValueErr<'a>, StyleDirectionParseError::InvalidValue);
1362
1363#[cfg(feature = "parser")]
1364#[derive(Debug, Clone, PartialEq)]
1365pub enum StyleDirectionParseErrorOwned {
1366    InvalidValue(InvalidValueErrOwned),
1367}
1368
1369#[cfg(feature = "parser")]
1370impl<'a> StyleDirectionParseError<'a> {
1371    pub fn to_contained(&self) -> StyleDirectionParseErrorOwned {
1372        match self {
1373            Self::InvalidValue(e) => StyleDirectionParseErrorOwned::InvalidValue(e.to_contained()),
1374        }
1375    }
1376}
1377
1378#[cfg(feature = "parser")]
1379impl StyleDirectionParseErrorOwned {
1380    pub fn to_shared<'a>(&'a self) -> StyleDirectionParseError<'a> {
1381        match self {
1382            Self::InvalidValue(e) => StyleDirectionParseError::InvalidValue(e.to_shared()),
1383        }
1384    }
1385}
1386
1387#[cfg(feature = "parser")]
1388pub fn parse_style_direction(input: &str) -> Result<StyleDirection, StyleDirectionParseError> {
1389    match input.trim() {
1390        "ltr" => Ok(StyleDirection::Ltr),
1391        "rtl" => Ok(StyleDirection::Rtl),
1392        other => Err(StyleDirectionParseError::InvalidValue(InvalidValueErr(
1393            other,
1394        ))),
1395    }
1396}
1397
1398#[cfg(feature = "parser")]
1399#[derive(Clone, PartialEq)]
1400pub enum StyleUserSelectParseError<'a> {
1401    InvalidValue(InvalidValueErr<'a>),
1402}
1403#[cfg(feature = "parser")]
1404impl_debug_as_display!(StyleUserSelectParseError<'a>);
1405#[cfg(feature = "parser")]
1406impl_display! { StyleUserSelectParseError<'a>, {
1407    InvalidValue(e) => format!("Invalid user-select value: \"{}\"", e.0),
1408}}
1409#[cfg(feature = "parser")]
1410impl_from!(InvalidValueErr<'a>, StyleUserSelectParseError::InvalidValue);
1411
1412#[cfg(feature = "parser")]
1413#[derive(Debug, Clone, PartialEq)]
1414pub enum StyleUserSelectParseErrorOwned {
1415    InvalidValue(InvalidValueErrOwned),
1416}
1417
1418#[cfg(feature = "parser")]
1419impl<'a> StyleUserSelectParseError<'a> {
1420    pub fn to_contained(&self) -> StyleUserSelectParseErrorOwned {
1421        match self {
1422            Self::InvalidValue(e) => StyleUserSelectParseErrorOwned::InvalidValue(e.to_contained()),
1423        }
1424    }
1425}
1426
1427#[cfg(feature = "parser")]
1428impl StyleUserSelectParseErrorOwned {
1429    pub fn to_shared<'a>(&'a self) -> StyleUserSelectParseError<'a> {
1430        match self {
1431            Self::InvalidValue(e) => StyleUserSelectParseError::InvalidValue(e.to_shared()),
1432        }
1433    }
1434}
1435
1436#[cfg(feature = "parser")]
1437pub fn parse_style_user_select(input: &str) -> Result<StyleUserSelect, StyleUserSelectParseError> {
1438    match input.trim() {
1439        "auto" => Ok(StyleUserSelect::Auto),
1440        "text" => Ok(StyleUserSelect::Text),
1441        "none" => Ok(StyleUserSelect::None),
1442        "all" => Ok(StyleUserSelect::All),
1443        other => Err(StyleUserSelectParseError::InvalidValue(InvalidValueErr(
1444            other,
1445        ))),
1446    }
1447}
1448
1449#[cfg(feature = "parser")]
1450#[derive(Clone, PartialEq)]
1451pub enum StyleTextDecorationParseError<'a> {
1452    InvalidValue(InvalidValueErr<'a>),
1453}
1454#[cfg(feature = "parser")]
1455impl_debug_as_display!(StyleTextDecorationParseError<'a>);
1456#[cfg(feature = "parser")]
1457impl_display! { StyleTextDecorationParseError<'a>, {
1458    InvalidValue(e) => format!("Invalid text-decoration value: \"{}\"", e.0),
1459}}
1460#[cfg(feature = "parser")]
1461impl_from!(
1462    InvalidValueErr<'a>,
1463    StyleTextDecorationParseError::InvalidValue
1464);
1465
1466#[cfg(feature = "parser")]
1467#[derive(Debug, Clone, PartialEq)]
1468pub enum StyleTextDecorationParseErrorOwned {
1469    InvalidValue(InvalidValueErrOwned),
1470}
1471
1472#[cfg(feature = "parser")]
1473impl<'a> StyleTextDecorationParseError<'a> {
1474    pub fn to_contained(&self) -> StyleTextDecorationParseErrorOwned {
1475        match self {
1476            Self::InvalidValue(e) => {
1477                StyleTextDecorationParseErrorOwned::InvalidValue(e.to_contained())
1478            }
1479        }
1480    }
1481}
1482
1483#[cfg(feature = "parser")]
1484impl StyleTextDecorationParseErrorOwned {
1485    pub fn to_shared<'a>(&'a self) -> StyleTextDecorationParseError<'a> {
1486        match self {
1487            Self::InvalidValue(e) => StyleTextDecorationParseError::InvalidValue(e.to_shared()),
1488        }
1489    }
1490}
1491
1492#[cfg(feature = "parser")]
1493pub fn parse_style_text_decoration(
1494    input: &str,
1495) -> Result<StyleTextDecoration, StyleTextDecorationParseError> {
1496    match input.trim() {
1497        "none" => Ok(StyleTextDecoration::None),
1498        "underline" => Ok(StyleTextDecoration::Underline),
1499        "overline" => Ok(StyleTextDecoration::Overline),
1500        "line-through" => Ok(StyleTextDecoration::LineThrough),
1501        other => Err(StyleTextDecorationParseError::InvalidValue(
1502            InvalidValueErr(other),
1503        )),
1504    }
1505}
1506
1507#[cfg(feature = "parser")]
1508#[derive(Clone, PartialEq)]
1509pub enum StyleVerticalAlignParseError<'a> {
1510    InvalidValue(InvalidValueErr<'a>),
1511}
1512#[cfg(feature = "parser")]
1513impl_debug_as_display!(StyleVerticalAlignParseError<'a>);
1514#[cfg(feature = "parser")]
1515impl_display! { StyleVerticalAlignParseError<'a>, {
1516    InvalidValue(e) => format!("Invalid vertical-align value: \"{}\"", e.0),
1517}}
1518#[cfg(feature = "parser")]
1519impl_from!(
1520    InvalidValueErr<'a>,
1521    StyleVerticalAlignParseError::InvalidValue
1522);
1523
1524#[cfg(feature = "parser")]
1525#[derive(Debug, Clone, PartialEq)]
1526pub enum StyleVerticalAlignParseErrorOwned {
1527    InvalidValue(InvalidValueErrOwned),
1528}
1529
1530#[cfg(feature = "parser")]
1531impl<'a> StyleVerticalAlignParseError<'a> {
1532    pub fn to_contained(&self) -> StyleVerticalAlignParseErrorOwned {
1533        match self {
1534            Self::InvalidValue(e) => {
1535                StyleVerticalAlignParseErrorOwned::InvalidValue(e.to_contained())
1536            }
1537        }
1538    }
1539}
1540
1541#[cfg(feature = "parser")]
1542impl StyleVerticalAlignParseErrorOwned {
1543    pub fn to_shared<'a>(&'a self) -> StyleVerticalAlignParseError<'a> {
1544        match self {
1545            Self::InvalidValue(e) => StyleVerticalAlignParseError::InvalidValue(e.to_shared()),
1546        }
1547    }
1548}
1549
1550#[cfg(feature = "parser")]
1551pub fn parse_style_vertical_align(
1552    input: &str,
1553) -> Result<StyleVerticalAlign, StyleVerticalAlignParseError> {
1554    match input.trim() {
1555        "baseline" => Ok(StyleVerticalAlign::Baseline),
1556        "top" => Ok(StyleVerticalAlign::Top),
1557        "middle" => Ok(StyleVerticalAlign::Middle),
1558        "bottom" => Ok(StyleVerticalAlign::Bottom),
1559        "sub" => Ok(StyleVerticalAlign::Sub),
1560        "super" => Ok(StyleVerticalAlign::Superscript),
1561        "text-top" => Ok(StyleVerticalAlign::TextTop),
1562        "text-bottom" => Ok(StyleVerticalAlign::TextBottom),
1563        other => Err(StyleVerticalAlignParseError::InvalidValue(InvalidValueErr(
1564            other,
1565        ))),
1566    }
1567}
1568
1569// --- CaretColor ---
1570
1571#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1572#[repr(C)]
1573pub struct CaretColor {
1574    pub inner: ColorU,
1575}
1576
1577impl Default for CaretColor {
1578    fn default() -> Self {
1579        Self {
1580            inner: ColorU::BLACK,
1581        }
1582    }
1583}
1584
1585impl PrintAsCssValue for CaretColor {
1586    fn print_as_css_value(&self) -> String {
1587        self.inner.to_hash()
1588    }
1589}
1590
1591impl crate::format_rust_code::FormatAsRustCode for CaretColor {
1592    fn format_as_rust_code(&self, _tabs: usize) -> String {
1593        format!(
1594            "CaretColor {{ inner: {} }}",
1595            crate::format_rust_code::format_color_value(&self.inner)
1596        )
1597    }
1598}
1599
1600#[cfg(feature = "parser")]
1601pub fn parse_caret_color(input: &str) -> Result<CaretColor, CssColorParseError> {
1602    parse_css_color(input).map(|inner| CaretColor { inner })
1603}
1604
1605// --- CaretAnimationDuration ---
1606
1607#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1608#[repr(C)]
1609pub struct CaretAnimationDuration {
1610    pub inner: CssDuration,
1611}
1612
1613impl Default for CaretAnimationDuration {
1614    fn default() -> Self {
1615        Self {
1616            inner: CssDuration { inner: 500 },
1617        } // Default 500ms blink time
1618    }
1619}
1620
1621impl PrintAsCssValue for CaretAnimationDuration {
1622    fn print_as_css_value(&self) -> String {
1623        self.inner.print_as_css_value()
1624    }
1625}
1626
1627impl crate::format_rust_code::FormatAsRustCode for CaretAnimationDuration {
1628    fn format_as_rust_code(&self, _tabs: usize) -> String {
1629        format!(
1630            "CaretAnimationDuration {{ inner: {} }}",
1631            self.inner.format_as_rust_code(0)
1632        )
1633    }
1634}
1635
1636#[cfg(feature = "parser")]
1637pub fn parse_caret_animation_duration(
1638    input: &str,
1639) -> Result<CaretAnimationDuration, DurationParseError> {
1640    use crate::props::basic::parse_duration;
1641
1642    parse_duration(input).map(|inner| CaretAnimationDuration { inner })
1643}
1644
1645// --- CaretWidth ---
1646
1647/// Width of the text cursor (caret) in pixels.
1648/// CSS doesn't have a standard property for this, so we use `-azul-caret-width`.
1649#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1650#[repr(C)]
1651pub struct CaretWidth {
1652    pub inner: PixelValue,
1653}
1654
1655impl Default for CaretWidth {
1656    fn default() -> Self {
1657        Self {
1658            inner: PixelValue::px(2.0), // Default 2px caret width
1659        }
1660    }
1661}
1662
1663impl PrintAsCssValue for CaretWidth {
1664    fn print_as_css_value(&self) -> String {
1665        self.inner.print_as_css_value()
1666    }
1667}
1668
1669impl crate::format_rust_code::FormatAsRustCode for CaretWidth {
1670    fn format_as_rust_code(&self, _tabs: usize) -> String {
1671        format!(
1672            "CaretWidth {{ inner: {} }}",
1673            self.inner.format_as_rust_code(0)
1674        )
1675    }
1676}
1677
1678#[cfg(feature = "parser")]
1679pub fn parse_caret_width(input: &str) -> Result<CaretWidth, CssPixelValueParseError> {
1680    use crate::props::basic::pixel::parse_pixel_value;
1681
1682    parse_pixel_value(input).map(|inner| CaretWidth { inner })
1683}
1684
1685// --- From implementations for CssProperty ---
1686
1687impl From<StyleUserSelect> for crate::props::property::CssProperty {
1688    fn from(value: StyleUserSelect) -> Self {
1689        use crate::props::property::CssProperty;
1690        CssProperty::user_select(value)
1691    }
1692}
1693
1694impl From<StyleTextDecoration> for crate::props::property::CssProperty {
1695    fn from(value: StyleTextDecoration) -> Self {
1696        use crate::props::property::CssProperty;
1697        CssProperty::text_decoration(value)
1698    }
1699}
1700
1701#[cfg(all(test, feature = "parser"))]
1702mod tests {
1703    use super::*;
1704    use crate::props::basic::{color::ColorU, length::PercentageValue, pixel::PixelValue};
1705
1706    #[test]
1707    fn test_parse_style_text_color() {
1708        assert_eq!(
1709            parse_style_text_color("red").unwrap().inner,
1710            ColorU::new_rgb(255, 0, 0)
1711        );
1712        assert_eq!(
1713            parse_style_text_color("#aabbcc").unwrap().inner,
1714            ColorU::new_rgb(170, 187, 204)
1715        );
1716        assert!(parse_style_text_color("not-a-color").is_err());
1717    }
1718
1719    #[test]
1720    fn test_parse_style_text_align() {
1721        assert_eq!(
1722            parse_style_text_align("left").unwrap(),
1723            StyleTextAlign::Left
1724        );
1725        assert_eq!(
1726            parse_style_text_align("center").unwrap(),
1727            StyleTextAlign::Center
1728        );
1729        assert_eq!(
1730            parse_style_text_align("right").unwrap(),
1731            StyleTextAlign::Right
1732        );
1733        assert_eq!(
1734            parse_style_text_align("justify").unwrap(),
1735            StyleTextAlign::Justify
1736        );
1737        assert_eq!(
1738            parse_style_text_align("start").unwrap(),
1739            StyleTextAlign::Start
1740        );
1741        assert_eq!(parse_style_text_align("end").unwrap(), StyleTextAlign::End);
1742        assert!(parse_style_text_align("middle").is_err());
1743    }
1744
1745    #[test]
1746    fn test_parse_spacing() {
1747        assert_eq!(
1748            parse_style_letter_spacing("2px").unwrap().inner,
1749            PixelValue::px(2.0)
1750        );
1751        assert_eq!(
1752            parse_style_letter_spacing("-0.1em").unwrap().inner,
1753            PixelValue::em(-0.1)
1754        );
1755        assert_eq!(
1756            parse_style_word_spacing("0.5em").unwrap().inner,
1757            PixelValue::em(0.5)
1758        );
1759    }
1760
1761    #[test]
1762    fn test_parse_line_height() {
1763        assert_eq!(
1764            parse_style_line_height("1.5").unwrap().inner,
1765            PercentageValue::new(150.0)
1766        );
1767        assert_eq!(
1768            parse_style_line_height("120%").unwrap().inner,
1769            PercentageValue::new(120.0)
1770        );
1771        assert!(parse_style_line_height("20px").is_err()); // lengths not supported by this parser
1772    }
1773
1774    #[test]
1775    fn test_parse_tab_width() {
1776        // Unitless number is treated as `em`
1777        assert_eq!(
1778            parse_style_tab_width("4").unwrap().inner,
1779            PixelValue::em(4.0)
1780        );
1781        assert_eq!(
1782            parse_style_tab_width("20px").unwrap().inner,
1783            PixelValue::px(20.0)
1784        );
1785    }
1786
1787    #[test]
1788    fn test_parse_white_space() {
1789        assert_eq!(
1790            parse_style_white_space("normal").unwrap(),
1791            StyleWhiteSpace::Normal
1792        );
1793        assert_eq!(
1794            parse_style_white_space("pre").unwrap(),
1795            StyleWhiteSpace::Pre
1796        );
1797        assert_eq!(
1798            parse_style_white_space("nowrap").unwrap(),
1799            StyleWhiteSpace::Nowrap
1800        );
1801        assert!(parse_style_white_space("pre-wrap").is_err());
1802    }
1803}