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