Skip to main content

azul_css/props/style/
text.rs

1//! CSS properties for styling text.
2//!
3//! Each property type implements `PrintAsCssValue` for CSS serialization and
4//! (behind the `parser` feature) has a corresponding `parse_style_*` function
5//! with borrowed/owned error type pairs.
6
7use alloc::string::{String, ToString};
8use core::fmt;
9use crate::corety::AzString;
10
11use crate::{
12    format_rust_code::FormatAsRustCode,
13    props::{
14        basic::{
15            error::{InvalidValueErr, InvalidValueErrOwned},
16            length::{PercentageParseError, PercentageParseErrorOwned, PercentageValue},
17            pixel::{CssPixelValueParseError, CssPixelValueParseErrorOwned, PixelValue},
18            ColorU, CssDuration,
19        },
20        formatter::PrintAsCssValue,
21        macros::PixelValueTaker,
22    },
23};
24
25// -- StyleTextColor (color property) --
26// NOTE: `color` is a text property, but the `ColorU` type itself is in `basic/color.rs`.
27// This is a newtype wrapper for type safety.
28
29/// Represents a `color` attribute.
30#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
31#[repr(C)]
32pub struct StyleTextColor {
33    pub inner: crate::props::basic::color::ColorU,
34}
35
36impl fmt::Debug for StyleTextColor {
37    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
38        write!(f, "{}", self.print_as_css_value())
39    }
40}
41
42impl StyleTextColor {
43    pub fn interpolate(&self, other: &Self, t: f32) -> Self {
44        Self {
45            inner: self.inner.interpolate(&other.inner, t),
46        }
47    }
48}
49
50impl PrintAsCssValue for StyleTextColor {
51    fn print_as_css_value(&self) -> String {
52        self.inner.to_hash()
53    }
54}
55
56// -- StyleTextAlign --
57
58/// Horizontal text alignment enum (left, center, right) - default: `Left`
59#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
60#[repr(C)]
61pub enum StyleTextAlign {
62    Left,
63    Center,
64    Right,
65    Justify,
66    #[default]
67    Start,
68    End,
69}
70
71impl_option!(
72    StyleTextAlign,
73    OptionStyleTextAlign,
74    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
75);
76
77impl PrintAsCssValue for StyleTextAlign {
78    fn print_as_css_value(&self) -> String {
79        String::from(match self {
80            StyleTextAlign::Left => "left",
81            StyleTextAlign::Center => "center",
82            StyleTextAlign::Right => "right",
83            StyleTextAlign::Justify => "justify",
84            StyleTextAlign::Start => "start",
85            StyleTextAlign::End => "end",
86        })
87    }
88}
89
90// -- StyleLetterSpacing --
91
92/// Represents a `letter-spacing` attribute
93#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
94#[repr(C)]
95pub struct StyleLetterSpacing {
96    pub inner: PixelValue,
97}
98
99impl fmt::Debug for StyleLetterSpacing {
100    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
101        write!(f, "{}", self.inner)
102    }
103}
104impl Default for StyleLetterSpacing {
105    fn default() -> Self {
106        Self {
107            inner: PixelValue::const_px(0),
108        }
109    }
110}
111impl_pixel_value!(StyleLetterSpacing);
112impl PixelValueTaker for StyleLetterSpacing {
113    fn from_pixel_value(inner: PixelValue) -> Self {
114        Self { inner }
115    }
116}
117impl PrintAsCssValue for StyleLetterSpacing {
118    fn print_as_css_value(&self) -> String {
119        format!("{}", self.inner)
120    }
121}
122
123// -- StyleWordSpacing --
124
125/// Represents a `word-spacing` attribute
126#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
127#[repr(C)]
128pub struct StyleWordSpacing {
129    pub inner: PixelValue,
130}
131
132impl fmt::Debug for StyleWordSpacing {
133    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
134        write!(f, "{}", self.inner)
135    }
136}
137impl Default for StyleWordSpacing {
138    fn default() -> Self {
139        Self {
140            inner: PixelValue::const_px(0),
141        }
142    }
143}
144impl_pixel_value!(StyleWordSpacing);
145impl PixelValueTaker for StyleWordSpacing {
146    fn from_pixel_value(inner: PixelValue) -> Self {
147        Self { inner }
148    }
149}
150impl PrintAsCssValue for StyleWordSpacing {
151    fn print_as_css_value(&self) -> String {
152        format!("{}", self.inner)
153    }
154}
155
156// -- StyleLineHeight --
157
158/// Represents a `line-height` attribute
159#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
160#[repr(C)]
161pub struct StyleLineHeight {
162    pub inner: PercentageValue,
163}
164impl Default for StyleLineHeight {
165    fn default() -> Self {
166        Self {
167            inner: PercentageValue::const_new(120),
168        }
169    }
170}
171impl_percentage_value!(StyleLineHeight);
172impl PrintAsCssValue for StyleLineHeight {
173    fn print_as_css_value(&self) -> String {
174        format!("{}", self.inner)
175    }
176}
177
178// -- StyleTabSize --
179
180/// Represents a `tab-size` attribute
181#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
182#[repr(C)]
183pub struct StyleTabSize {
184    pub inner: PixelValue, // Can be a number (space characters, em-based) or a length
185}
186
187impl fmt::Debug for StyleTabSize {
188    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
189        write!(f, "{}", self.inner)
190    }
191}
192impl Default for StyleTabSize {
193    fn default() -> Self {
194        Self {
195            inner: PixelValue::em(8.0),
196        }
197    }
198}
199impl_pixel_value!(StyleTabSize);
200impl PixelValueTaker for StyleTabSize {
201    fn from_pixel_value(inner: PixelValue) -> Self {
202        Self { inner }
203    }
204}
205impl PrintAsCssValue for StyleTabSize {
206    fn print_as_css_value(&self) -> String {
207        format!("{}", self.inner)
208    }
209}
210
211// -- StyleWhiteSpace --
212
213/// How to handle white space inside an element.
214/// 
215/// CSS Text Level 3: https://www.w3.org/TR/css-text-3/#white-space-property
216#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
217#[repr(C)]
218#[derive(Default)]
219pub enum StyleWhiteSpace {
220    /// Collapse whitespace, wrap lines
221    #[default]
222    Normal,
223    /// Preserve whitespace, no wrap (except for explicit breaks)
224    Pre,
225    /// Collapse whitespace, no wrap
226    Nowrap,
227    /// Preserve whitespace, wrap lines
228    PreWrap,
229    /// Collapse whitespace (except newlines), wrap lines
230    PreLine,
231    /// Preserve whitespace, allow breaking at spaces
232    BreakSpaces,
233}
234impl_option!(
235    StyleWhiteSpace,
236    OptionStyleWhiteSpace,
237    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
238);
239impl PrintAsCssValue for StyleWhiteSpace {
240    fn print_as_css_value(&self) -> String {
241        String::from(match self {
242            StyleWhiteSpace::Normal => "normal",
243            StyleWhiteSpace::Pre => "pre",
244            StyleWhiteSpace::Nowrap => "nowrap",
245            StyleWhiteSpace::PreWrap => "pre-wrap",
246            StyleWhiteSpace::PreLine => "pre-line",
247            StyleWhiteSpace::BreakSpaces => "break-spaces",
248        })
249    }
250}
251
252// -- StyleHyphens --
253
254/// Hyphenation rules.
255#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
256#[repr(C)]
257#[derive(Default)]
258pub enum StyleHyphens {
259    /// No hyphenation: words are not broken at hyphenation opportunities.
260    None,
261    /// Manual hyphenation: words are only broken at explicit soft hyphens (U+00AD)
262    /// or unconditional hyphens (U+2010).
263    #[default]
264    Manual,
265    /// Automatic hyphenation: words may be broken at automatic hyphenation
266    /// opportunities determined by a language-appropriate hyphenation resource,
267    /// in addition to explicit opportunities.
268    Auto,
269}
270impl_option!(
271    StyleHyphens,
272    OptionStyleHyphens,
273    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
274);
275impl PrintAsCssValue for StyleHyphens {
276    fn print_as_css_value(&self) -> String {
277        String::from(match self {
278            StyleHyphens::None => "none",
279            StyleHyphens::Manual => "manual",
280            StyleHyphens::Auto => "auto",
281        })
282    }
283}
284
285// -- StyleLineBreak --
286
287/// Controls the strictness of line breaking rules.
288///
289/// CSS Text Level 3: https://www.w3.org/TR/css-text-3/#line-break-property
290#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
291#[repr(C)]
292#[derive(Default)]
293pub enum StyleLineBreak {
294    /// The browser determines the set of line-breaking restrictions to use.
295    #[default]
296    Auto,
297    /// Breaks text using the least restrictive set of line-breaking rules.
298    Loose,
299    /// Breaks text using the most common set of line-breaking rules.
300    Normal,
301    /// Breaks text using the most stringent set of line-breaking rules.
302    Strict,
303    /// There is a soft wrap opportunity around every typographic character unit,
304    /// including around any punctuation character or preserved white spaces,
305    /// or in the middle of words, disregarding any prohibition against line breaks.
306    Anywhere,
307}
308impl_option!(
309    StyleLineBreak,
310    OptionStyleLineBreak,
311    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
312);
313impl PrintAsCssValue for StyleLineBreak {
314    fn print_as_css_value(&self) -> String {
315        String::from(match self {
316            StyleLineBreak::Auto => "auto",
317            StyleLineBreak::Loose => "loose",
318            StyleLineBreak::Normal => "normal",
319            StyleLineBreak::Strict => "strict",
320            StyleLineBreak::Anywhere => "anywhere",
321        })
322    }
323}
324
325// -- StyleWordBreak --
326
327/// Controls line breaking rules within words.
328///
329/// CSS Text Level 3 §5.2: https://www.w3.org/TR/css-text-3/#word-break-property
330#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
331#[repr(C)]
332#[derive(Default)]
333pub enum StyleWordBreak {
334    /// Use default line break rules.
335    #[default]
336    Normal,
337    /// Allow break opportunities between any two characters (CJK and non-CJK).
338    BreakAll,
339    /// Forbid break opportunities within CJK character sequences.
340    KeepAll,
341    // +spec:line-breaking:815882 - deprecated break-word keyword: same as normal + overflow-wrap: anywhere
342    /// Deprecated: equivalent to word-break: normal and overflow-wrap: anywhere.
343    BreakWord,
344}
345impl_option!(
346    StyleWordBreak,
347    OptionStyleWordBreak,
348    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
349);
350impl PrintAsCssValue for StyleWordBreak {
351    fn print_as_css_value(&self) -> String {
352        String::from(match self {
353            StyleWordBreak::Normal => "normal",
354            StyleWordBreak::BreakAll => "break-all",
355            StyleWordBreak::KeepAll => "keep-all",
356            StyleWordBreak::BreakWord => "break-word",
357        })
358    }
359}
360
361// -- StyleOverflowWrap --
362
363/// Controls whether the browser may break at otherwise disallowed points
364/// to prevent overflow.
365///
366/// CSS Text Level 3 §3.3: https://www.w3.org/TR/css-text-3/#overflow-wrap-property
367#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
368#[repr(C)]
369#[derive(Default)]
370pub enum StyleOverflowWrap {
371    /// Lines may only break at allowed break points.
372    #[default]
373    Normal,
374    /// An otherwise unbreakable sequence may be broken at an arbitrary point
375    /// if there are no otherwise acceptable break points.
376    Anywhere,
377    /// Same as `anywhere` but soft wrap opportunities introduced are not
378    /// considered when calculating min-content intrinsic sizes.
379    BreakWord,
380}
381impl_option!(
382    StyleOverflowWrap,
383    OptionStyleOverflowWrap,
384    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
385);
386impl PrintAsCssValue for StyleOverflowWrap {
387    fn print_as_css_value(&self) -> String {
388        String::from(match self {
389            StyleOverflowWrap::Normal => "normal",
390            StyleOverflowWrap::Anywhere => "anywhere",
391            StyleOverflowWrap::BreakWord => "break-word",
392        })
393    }
394}
395
396// -- StyleTextAlignLast --
397
398/// Controls alignment of the last line of a block or a line right before
399/// a forced line break.
400///
401/// CSS Text Level 3 §7.2: https://www.w3.org/TR/css-text-3/#text-align-last-property
402#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
403#[repr(C)]
404#[derive(Default)]
405pub enum StyleTextAlignLast {
406    /// Alignment of the last line is determined by text-align (or start if justify).
407    #[default]
408    Auto,
409    /// Align to the start edge of the line box.
410    Start,
411    /// Align to the end edge of the line box.
412    End,
413    /// Align to the line left.
414    Left,
415    /// Align to the line right.
416    Right,
417    /// Center the content.
418    Center,
419    /// Justify the content.
420    Justify,
421}
422impl_option!(
423    StyleTextAlignLast,
424    OptionStyleTextAlignLast,
425    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
426);
427impl PrintAsCssValue for StyleTextAlignLast {
428    fn print_as_css_value(&self) -> String {
429        String::from(match self {
430            StyleTextAlignLast::Auto => "auto",
431            StyleTextAlignLast::Start => "start",
432            StyleTextAlignLast::End => "end",
433            StyleTextAlignLast::Left => "left",
434            StyleTextAlignLast::Right => "right",
435            StyleTextAlignLast::Center => "center",
436            StyleTextAlignLast::Justify => "justify",
437        })
438    }
439}
440
441// -- StyleDirection --
442
443/// Text direction.
444// +spec:writing-modes:46fed3 - direction property provides explicit bidi controls in CSS
445#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
446#[repr(C)]
447#[derive(Default)]
448pub enum StyleDirection {
449    /// Left-to-right text direction
450    #[default]
451    Ltr,
452    /// Right-to-left text direction
453    Rtl,
454}
455impl_option!(
456    StyleDirection,
457    OptionStyleDirection,
458    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
459);
460impl PrintAsCssValue for StyleDirection {
461    fn print_as_css_value(&self) -> String {
462        String::from(match self {
463            StyleDirection::Ltr => "ltr",
464            StyleDirection::Rtl => "rtl",
465        })
466    }
467}
468
469// -- StyleUserSelect --
470
471/// Controls whether the user can select text.
472/// Used to prevent accidental text selection on UI controls like buttons.
473#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
474#[repr(C)]
475#[derive(Default)]
476pub enum StyleUserSelect {
477    /// Browser determines selectability (default)
478    #[default]
479    Auto,
480    /// Text is selectable
481    Text,
482    /// Text is not selectable
483    None,
484    /// User can select all text with a single action
485    All,
486}
487impl_option!(
488    StyleUserSelect,
489    OptionStyleUserSelect,
490    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
491);
492impl PrintAsCssValue for StyleUserSelect {
493    fn print_as_css_value(&self) -> String {
494        String::from(match self {
495            StyleUserSelect::Auto => "auto",
496            StyleUserSelect::Text => "text",
497            StyleUserSelect::None => "none",
498            StyleUserSelect::All => "all",
499        })
500    }
501}
502
503// -- StyleTextDecoration --
504
505/// Text decoration (underline, overline, line-through).
506#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
507#[repr(C)]
508#[derive(Default)]
509pub enum StyleTextDecoration {
510    /// No decoration
511    #[default]
512    None,
513    /// Underline
514    Underline,
515    /// Line above text
516    Overline,
517    /// Strike-through line
518    LineThrough,
519}
520impl_option!(
521    StyleTextDecoration,
522    OptionStyleTextDecoration,
523    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
524);
525impl PrintAsCssValue for StyleTextDecoration {
526    fn print_as_css_value(&self) -> String {
527        String::from(match self {
528            StyleTextDecoration::None => "none",
529            StyleTextDecoration::Underline => "underline",
530            StyleTextDecoration::Overline => "overline",
531            StyleTextDecoration::LineThrough => "line-through",
532        })
533    }
534}
535
536// -- StyleVerticalAlign --
537
538/// CSS 2.2 §10.8.1 vertical-align property values
539#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
540#[repr(C, u8)]
541#[derive(Default)]
542pub enum StyleVerticalAlign {
543    /// CSS default - align baselines
544    #[default]
545    Baseline,
546    /// Align top of element with top of line box
547    Top,
548    /// Align middle of element with baseline + half x-height
549    Middle,
550    /// Align bottom of element with bottom of line box
551    Bottom,
552    /// Align baseline with parent's subscript baseline
553    Sub,
554    /// Align baseline with parent's superscript baseline
555    Superscript,
556    /// Align top with top of parent's font
557    TextTop,
558    /// Align bottom with bottom of parent's font
559    TextBottom,
560    /// <percentage> refers to line-height of the element itself
561    Percentage(PercentageValue),
562    /// <length> offset from baseline
563    Length(PixelValue),
564}
565
566impl_option!(
567    StyleVerticalAlign,
568    OptionStyleVerticalAlign,
569    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
570);
571
572impl PrintAsCssValue for StyleVerticalAlign {
573    fn print_as_css_value(&self) -> String {
574        match self {
575            StyleVerticalAlign::Baseline => String::from("baseline"),
576            StyleVerticalAlign::Top => String::from("top"),
577            StyleVerticalAlign::Middle => String::from("middle"),
578            StyleVerticalAlign::Bottom => String::from("bottom"),
579            StyleVerticalAlign::Sub => String::from("sub"),
580            StyleVerticalAlign::Superscript => String::from("super"),
581            StyleVerticalAlign::TextTop => String::from("text-top"),
582            StyleVerticalAlign::TextBottom => String::from("text-bottom"),
583            StyleVerticalAlign::Percentage(p) => format!("{}%", p.normalized() * 100.0),
584            StyleVerticalAlign::Length(l) => l.print_as_css_value(),
585        }
586    }
587}
588
589impl crate::format_rust_code::FormatAsRustCode for StyleVerticalAlign {
590    fn format_as_rust_code(&self, indent: usize) -> String {
591        match self {
592            StyleVerticalAlign::Baseline => "StyleVerticalAlign::Baseline".to_string(),
593            StyleVerticalAlign::Top => "StyleVerticalAlign::Top".to_string(),
594            StyleVerticalAlign::Middle => "StyleVerticalAlign::Middle".to_string(),
595            StyleVerticalAlign::Bottom => "StyleVerticalAlign::Bottom".to_string(),
596            StyleVerticalAlign::Sub => "StyleVerticalAlign::Sub".to_string(),
597            StyleVerticalAlign::Superscript => "StyleVerticalAlign::Superscript".to_string(),
598            StyleVerticalAlign::TextTop => "StyleVerticalAlign::TextTop".to_string(),
599            StyleVerticalAlign::TextBottom => "StyleVerticalAlign::TextBottom".to_string(),
600            StyleVerticalAlign::Percentage(p) => format!("StyleVerticalAlign::Percentage(PercentageValue::new({}))", p.normalized() * 100.0),
601            StyleVerticalAlign::Length(l) => format!("StyleVerticalAlign::Length({})", l),
602        }
603    }
604}
605
606// --- PARSERS ---
607
608#[cfg(feature = "parser")]
609use crate::props::basic::{
610    color::{parse_css_color, CssColorParseError, CssColorParseErrorOwned},
611    DurationParseError,
612};
613
614#[cfg(feature = "parser")]
615#[derive(Clone, PartialEq)]
616pub enum StyleTextColorParseError<'a> {
617    ColorParseError(CssColorParseError<'a>),
618}
619#[cfg(feature = "parser")]
620impl_debug_as_display!(StyleTextColorParseError<'a>);
621#[cfg(feature = "parser")]
622impl_display! { StyleTextColorParseError<'a>, {
623    ColorParseError(e) => format!("Invalid color: {}", e),
624}}
625#[cfg(feature = "parser")]
626impl_from!(
627    CssColorParseError<'a>,
628    StyleTextColorParseError::ColorParseError
629);
630
631#[cfg(feature = "parser")]
632#[derive(Debug, Clone, PartialEq)]
633#[repr(C, u8)]
634pub enum StyleTextColorParseErrorOwned {
635    ColorParseError(CssColorParseErrorOwned),
636}
637
638#[cfg(feature = "parser")]
639impl<'a> StyleTextColorParseError<'a> {
640    pub fn to_contained(&self) -> StyleTextColorParseErrorOwned {
641        match self {
642            Self::ColorParseError(e) => {
643                StyleTextColorParseErrorOwned::ColorParseError(e.to_contained())
644            }
645        }
646    }
647}
648
649#[cfg(feature = "parser")]
650impl StyleTextColorParseErrorOwned {
651    pub fn to_shared<'a>(&'a self) -> StyleTextColorParseError<'a> {
652        match self {
653            Self::ColorParseError(e) => StyleTextColorParseError::ColorParseError(e.to_shared()),
654        }
655    }
656}
657
658#[cfg(feature = "parser")]
659pub fn parse_style_text_color(input: &str) -> Result<StyleTextColor, StyleTextColorParseError<'_>> {
660    parse_css_color(input)
661        .map(|inner| StyleTextColor { inner })
662        .map_err(StyleTextColorParseError::ColorParseError)
663}
664
665#[cfg(feature = "parser")]
666#[derive(Clone, PartialEq)]
667pub enum StyleTextAlignParseError<'a> {
668    InvalidValue(InvalidValueErr<'a>),
669}
670#[cfg(feature = "parser")]
671impl_debug_as_display!(StyleTextAlignParseError<'a>);
672#[cfg(feature = "parser")]
673impl_display! { StyleTextAlignParseError<'a>, {
674    InvalidValue(e) => format!("Invalid text-align value: \"{}\"", e.0),
675}}
676#[cfg(feature = "parser")]
677impl_from!(InvalidValueErr<'a>, StyleTextAlignParseError::InvalidValue);
678
679#[cfg(feature = "parser")]
680#[derive(Debug, Clone, PartialEq)]
681#[repr(C, u8)]
682pub enum StyleTextAlignParseErrorOwned {
683    InvalidValue(InvalidValueErrOwned),
684}
685
686#[cfg(feature = "parser")]
687impl<'a> StyleTextAlignParseError<'a> {
688    pub fn to_contained(&self) -> StyleTextAlignParseErrorOwned {
689        match self {
690            Self::InvalidValue(e) => StyleTextAlignParseErrorOwned::InvalidValue(e.to_contained()),
691        }
692    }
693}
694
695#[cfg(feature = "parser")]
696impl StyleTextAlignParseErrorOwned {
697    pub fn to_shared<'a>(&'a self) -> StyleTextAlignParseError<'a> {
698        match self {
699            Self::InvalidValue(e) => StyleTextAlignParseError::InvalidValue(e.to_shared()),
700        }
701    }
702}
703
704#[cfg(feature = "parser")]
705pub fn parse_style_text_align(input: &str) -> Result<StyleTextAlign, StyleTextAlignParseError<'_>> {
706    match input.trim() {
707        "left" => Ok(StyleTextAlign::Left),
708        "center" => Ok(StyleTextAlign::Center),
709        "right" => Ok(StyleTextAlign::Right),
710        "justify" => Ok(StyleTextAlign::Justify),
711        "start" => Ok(StyleTextAlign::Start),
712        "end" => Ok(StyleTextAlign::End),
713        other => Err(StyleTextAlignParseError::InvalidValue(InvalidValueErr(
714            other,
715        ))),
716    }
717}
718
719#[cfg(feature = "parser")]
720#[derive(Clone, PartialEq)]
721pub enum StyleLetterSpacingParseError<'a> {
722    PixelValue(CssPixelValueParseError<'a>),
723}
724#[cfg(feature = "parser")]
725impl_debug_as_display!(StyleLetterSpacingParseError<'a>);
726#[cfg(feature = "parser")]
727impl_display! { StyleLetterSpacingParseError<'a>, {
728    PixelValue(e) => format!("Invalid letter-spacing value: {}", e),
729}}
730#[cfg(feature = "parser")]
731impl_from!(
732    CssPixelValueParseError<'a>,
733    StyleLetterSpacingParseError::PixelValue
734);
735
736#[cfg(feature = "parser")]
737#[derive(Debug, Clone, PartialEq)]
738#[repr(C, u8)]
739pub enum StyleLetterSpacingParseErrorOwned {
740    PixelValue(CssPixelValueParseErrorOwned),
741}
742
743#[cfg(feature = "parser")]
744impl<'a> StyleLetterSpacingParseError<'a> {
745    pub fn to_contained(&self) -> StyleLetterSpacingParseErrorOwned {
746        match self {
747            Self::PixelValue(e) => StyleLetterSpacingParseErrorOwned::PixelValue(e.to_contained()),
748        }
749    }
750}
751
752#[cfg(feature = "parser")]
753impl StyleLetterSpacingParseErrorOwned {
754    pub fn to_shared<'a>(&'a self) -> StyleLetterSpacingParseError<'a> {
755        match self {
756            Self::PixelValue(e) => StyleLetterSpacingParseError::PixelValue(e.to_shared()),
757        }
758    }
759}
760
761#[cfg(feature = "parser")]
762pub fn parse_style_letter_spacing(
763    input: &str,
764) -> Result<StyleLetterSpacing, StyleLetterSpacingParseError<'_>> {
765    crate::props::basic::pixel::parse_pixel_value(input)
766        .map(|inner| StyleLetterSpacing { inner })
767        .map_err(StyleLetterSpacingParseError::PixelValue)
768}
769
770// -- StyleTextIndent (text-indent property) --
771
772/// Represents a `text-indent` attribute (indentation of first line in a block).
773#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
774#[repr(C)]
775pub struct StyleTextIndent {
776    pub inner: PixelValue,
777    /// `each-line` keyword: indent first line of each block container
778    /// AND each line after a forced line break (but not after soft wrap).
779    pub each_line: bool,
780    /// `hanging` keyword: inverts which lines are affected by the indent.
781    pub hanging: bool,
782}
783
784impl fmt::Debug for StyleTextIndent {
785    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
786        write!(f, "{}", self.print_as_css_value())
787    }
788}
789
790impl StyleTextIndent {
791    #[inline]
792    pub const fn zero() -> Self {
793        Self { inner: crate::props::basic::pixel::PixelValue::zero(), each_line: false, hanging: false }
794    }
795    #[inline]
796    pub const fn const_px(value: isize) -> Self {
797        Self { inner: crate::props::basic::pixel::PixelValue::const_px(value), each_line: false, hanging: false }
798    }
799    #[inline]
800    pub const fn const_em(value: isize) -> Self {
801        Self { inner: crate::props::basic::pixel::PixelValue::const_em(value), each_line: false, hanging: false }
802    }
803    #[inline]
804    pub const fn const_pt(value: isize) -> Self {
805        Self { inner: crate::props::basic::pixel::PixelValue::const_pt(value), each_line: false, hanging: false }
806    }
807    #[inline]
808    pub const fn const_percent(value: isize) -> Self {
809        Self { inner: crate::props::basic::pixel::PixelValue::const_percent(value), each_line: false, hanging: false }
810    }
811    #[inline]
812    pub const fn const_in(value: isize) -> Self {
813        Self { inner: crate::props::basic::pixel::PixelValue::const_in(value), each_line: false, hanging: false }
814    }
815    #[inline]
816    pub const fn const_cm(value: isize) -> Self {
817        Self { inner: crate::props::basic::pixel::PixelValue::const_cm(value), each_line: false, hanging: false }
818    }
819    #[inline]
820    pub const fn const_mm(value: isize) -> Self {
821        Self { inner: crate::props::basic::pixel::PixelValue::const_mm(value), each_line: false, hanging: false }
822    }
823    #[inline]
824    pub const fn const_from_metric(metric: crate::props::basic::length::SizeMetric, value: isize) -> Self {
825        Self { inner: crate::props::basic::pixel::PixelValue::const_from_metric(metric, value), each_line: false, hanging: false }
826    }
827    #[inline]
828    pub fn px(value: f32) -> Self {
829        Self { inner: crate::props::basic::pixel::PixelValue::px(value), each_line: false, hanging: false }
830    }
831    #[inline]
832    pub fn em(value: f32) -> Self {
833        Self { inner: crate::props::basic::pixel::PixelValue::em(value), each_line: false, hanging: false }
834    }
835    #[inline]
836    pub fn pt(value: f32) -> Self {
837        Self { inner: crate::props::basic::pixel::PixelValue::pt(value), each_line: false, hanging: false }
838    }
839    #[inline]
840    pub fn percent(value: f32) -> Self {
841        Self { inner: crate::props::basic::pixel::PixelValue::percent(value), each_line: false, hanging: false }
842    }
843    #[inline]
844    pub fn from_metric(metric: crate::props::basic::length::SizeMetric, value: f32) -> Self {
845        Self { inner: crate::props::basic::pixel::PixelValue::from_metric(metric, value), each_line: false, hanging: false }
846    }
847    #[inline]
848    pub fn interpolate(&self, other: &Self, t: f32) -> Self {
849        StyleTextIndent { inner: self.inner.interpolate(&other.inner, t), each_line: self.each_line, hanging: self.hanging }
850    }
851}
852
853impl PrintAsCssValue for StyleTextIndent {
854    fn print_as_css_value(&self) -> String {
855        let mut s = self.inner.to_string();
856        if self.hanging {
857            s.push_str(" hanging");
858        }
859        if self.each_line {
860            s.push_str(" each-line");
861        }
862        s
863    }
864}
865
866impl crate::format_rust_code::FormatAsRustCode for StyleTextIndent {
867    fn format_as_rust_code(&self, _tabs: usize) -> String {
868        format!(
869            "StyleTextIndent {{ inner: {}, each_line: {}, hanging: {} }}",
870            self.inner.format_as_rust_code(0), self.each_line, self.hanging
871        )
872    }
873}
874
875#[cfg(feature = "parser")]
876#[derive(Clone, PartialEq)]
877pub enum StyleTextIndentParseError<'a> {
878    PixelValue(CssPixelValueParseError<'a>),
879}
880#[cfg(feature = "parser")]
881impl_debug_as_display!(StyleTextIndentParseError<'a>);
882#[cfg(feature = "parser")]
883impl_display! { StyleTextIndentParseError<'a>, {
884    PixelValue(e) => format!("Invalid text-indent value: {}", e),
885}}
886#[cfg(feature = "parser")]
887impl_from!(
888    CssPixelValueParseError<'a>,
889    StyleTextIndentParseError::PixelValue
890);
891
892#[cfg(feature = "parser")]
893#[derive(Debug, Clone, PartialEq)]
894#[repr(C, u8)]
895pub enum StyleTextIndentParseErrorOwned {
896    PixelValue(CssPixelValueParseErrorOwned),
897}
898
899#[cfg(feature = "parser")]
900impl<'a> StyleTextIndentParseError<'a> {
901    pub fn to_contained(&self) -> StyleTextIndentParseErrorOwned {
902        match self {
903            Self::PixelValue(e) => StyleTextIndentParseErrorOwned::PixelValue(e.to_contained()),
904        }
905    }
906}
907
908#[cfg(feature = "parser")]
909impl StyleTextIndentParseErrorOwned {
910    pub fn to_shared<'a>(&'a self) -> StyleTextIndentParseError<'a> {
911        match self {
912            Self::PixelValue(e) => StyleTextIndentParseError::PixelValue(e.to_shared()),
913        }
914    }
915}
916
917#[cfg(feature = "parser")]
918pub fn parse_style_text_indent(input: &str) -> Result<StyleTextIndent, StyleTextIndentParseError<'_>> {
919    let mut each_line = false;
920    let mut hanging = false;
921    let mut pixel_part: Option<&str> = None;
922
923    for token in input.split_whitespace() {
924        match token {
925            "each-line" => each_line = true,
926            "hanging" => hanging = true,
927            _ => {
928                pixel_part = Some(token);
929            }
930        }
931    }
932
933    let pixel_str = pixel_part.unwrap_or("0px");
934
935    crate::props::basic::pixel::parse_pixel_value(pixel_str)
936        .map(|inner| StyleTextIndent { inner, each_line, hanging })
937        .map_err(StyleTextIndentParseError::PixelValue)
938}
939
940/// initial-letter property for drop caps
941#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
942#[repr(C)]
943pub struct StyleInitialLetter {
944    pub size: u32,
945    pub sink: crate::corety::OptionU32,
946}
947
948impl FormatAsRustCode for StyleInitialLetter {
949    fn format_as_rust_code(&self, _tabs: usize) -> String {
950        format!("{:?}", self)
951    }
952}
953
954impl PrintAsCssValue for StyleInitialLetter {
955    fn print_as_css_value(&self) -> String {
956        if let crate::corety::OptionU32::Some(sink) = self.sink {
957            format!("{} {}", self.size, sink)
958        } else {
959            format!("{}", self.size)
960        }
961    }
962}
963
964#[cfg(feature = "parser")]
965#[derive(Clone, PartialEq)]
966pub enum StyleInitialLetterParseError<'a> {
967    InvalidFormat(&'a str),
968    InvalidSize(&'a str),
969    InvalidSink(&'a str),
970}
971#[cfg(feature = "parser")]
972impl_debug_as_display!(StyleInitialLetterParseError<'a>);
973#[cfg(feature = "parser")]
974impl_display! { StyleInitialLetterParseError<'a>, {
975    InvalidFormat(e) => format!("Invalid initial-letter format: {}", e),
976    InvalidSize(e) => format!("Invalid initial-letter size: {}", e),
977    InvalidSink(e) => format!("Invalid initial-letter sink: {}", e),
978}}
979
980#[cfg(feature = "parser")]
981#[derive(Debug, Clone, PartialEq)]
982#[repr(C, u8)]
983pub enum StyleInitialLetterParseErrorOwned {
984    InvalidFormat(AzString),
985    InvalidSize(AzString),
986    InvalidSink(AzString),
987}
988
989#[cfg(feature = "parser")]
990impl<'a> StyleInitialLetterParseError<'a> {
991    pub fn to_contained(&self) -> StyleInitialLetterParseErrorOwned {
992        match self {
993            Self::InvalidFormat(s) => {
994                StyleInitialLetterParseErrorOwned::InvalidFormat(s.to_string().into())
995            }
996            Self::InvalidSize(s) => StyleInitialLetterParseErrorOwned::InvalidSize(s.to_string().into()),
997            Self::InvalidSink(s) => StyleInitialLetterParseErrorOwned::InvalidSink(s.to_string().into()),
998        }
999    }
1000}
1001
1002#[cfg(feature = "parser")]
1003impl StyleInitialLetterParseErrorOwned {
1004    pub fn to_shared<'a>(&'a self) -> StyleInitialLetterParseError<'a> {
1005        match self {
1006            Self::InvalidFormat(s) => StyleInitialLetterParseError::InvalidFormat(s.as_str()),
1007            Self::InvalidSize(s) => StyleInitialLetterParseError::InvalidSize(s.as_str()),
1008            Self::InvalidSink(s) => StyleInitialLetterParseError::InvalidSink(s.as_str()),
1009        }
1010    }
1011}
1012
1013#[cfg(feature = "parser")]
1014impl From<StyleInitialLetterParseError<'_>> for StyleInitialLetterParseErrorOwned {
1015    fn from(e: StyleInitialLetterParseError) -> Self {
1016        match e {
1017            StyleInitialLetterParseError::InvalidFormat(s) => {
1018                StyleInitialLetterParseErrorOwned::InvalidFormat(s.to_string().into())
1019            }
1020            StyleInitialLetterParseError::InvalidSize(s) => {
1021                StyleInitialLetterParseErrorOwned::InvalidSize(s.to_string().into())
1022            }
1023            StyleInitialLetterParseError::InvalidSink(s) => {
1024                StyleInitialLetterParseErrorOwned::InvalidSink(s.to_string().into())
1025            }
1026        }
1027    }
1028}
1029
1030#[cfg(feature = "parser")]
1031impl_display! { StyleInitialLetterParseErrorOwned, {
1032    InvalidFormat(e) => format!("Invalid initial-letter format: {}", e),
1033    InvalidSize(e) => format!("Invalid initial-letter size: {}", e),
1034    InvalidSink(e) => format!("Invalid initial-letter sink: {}", e),
1035}}
1036
1037#[cfg(feature = "parser")]
1038pub fn parse_style_initial_letter<'a>(
1039    input: &'a str,
1040) -> Result<StyleInitialLetter, StyleInitialLetterParseError<'a>> {
1041    let input = input.trim();
1042    let parts: Vec<&str> = input.split_whitespace().collect();
1043
1044    if parts.is_empty() {
1045        return Err(StyleInitialLetterParseError::InvalidFormat(input));
1046    }
1047
1048    // Parse size (required)
1049    let size = parts[0]
1050        .parse::<u32>()
1051        .map_err(|_| StyleInitialLetterParseError::InvalidSize(parts[0]))?;
1052
1053    if size == 0 {
1054        return Err(StyleInitialLetterParseError::InvalidSize(parts[0]));
1055    }
1056
1057    // Parse sink (optional)
1058    let sink = if parts.len() > 1 {
1059        crate::corety::OptionU32::Some(
1060            parts[1]
1061                .parse::<u32>()
1062                .map_err(|_| StyleInitialLetterParseError::InvalidSink(parts[1]))?,
1063        )
1064    } else {
1065        crate::corety::OptionU32::None
1066    };
1067
1068    Ok(StyleInitialLetter { size, sink })
1069}
1070
1071/// line-clamp property for limiting visible lines
1072#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1073#[repr(C)]
1074pub struct StyleLineClamp {
1075    pub max_lines: usize,
1076}
1077
1078impl FormatAsRustCode for StyleLineClamp {
1079    fn format_as_rust_code(&self, _tabs: usize) -> String {
1080        format!("{:?}", self)
1081    }
1082}
1083
1084impl PrintAsCssValue for StyleLineClamp {
1085    fn print_as_css_value(&self) -> String {
1086        format!("{}", self.max_lines)
1087    }
1088}
1089
1090#[cfg(feature = "parser")]
1091#[derive(Clone, PartialEq)]
1092pub enum StyleLineClampParseError<'a> {
1093    InvalidValue(&'a str),
1094    ZeroValue,
1095}
1096#[cfg(feature = "parser")]
1097impl_debug_as_display!(StyleLineClampParseError<'a>);
1098#[cfg(feature = "parser")]
1099impl_display! { StyleLineClampParseError<'a>, {
1100    InvalidValue(e) => format!("Invalid line-clamp value: {}", e),
1101    ZeroValue => format!("line-clamp cannot be zero"),
1102}}
1103
1104#[cfg(feature = "parser")]
1105#[derive(Debug, Clone, PartialEq)]
1106#[repr(C, u8)]
1107pub enum StyleLineClampParseErrorOwned {
1108    InvalidValue(AzString),
1109    ZeroValue,
1110}
1111
1112#[cfg(feature = "parser")]
1113impl<'a> StyleLineClampParseError<'a> {
1114    pub fn to_contained(&self) -> StyleLineClampParseErrorOwned {
1115        match self {
1116            Self::InvalidValue(s) => StyleLineClampParseErrorOwned::InvalidValue(s.to_string().into()),
1117            Self::ZeroValue => StyleLineClampParseErrorOwned::ZeroValue,
1118        }
1119    }
1120}
1121
1122#[cfg(feature = "parser")]
1123impl StyleLineClampParseErrorOwned {
1124    pub fn to_shared<'a>(&'a self) -> StyleLineClampParseError<'a> {
1125        match self {
1126            Self::InvalidValue(s) => StyleLineClampParseError::InvalidValue(s.as_str()),
1127            Self::ZeroValue => StyleLineClampParseError::ZeroValue,
1128        }
1129    }
1130}
1131
1132#[cfg(feature = "parser")]
1133impl From<StyleLineClampParseError<'_>> for StyleLineClampParseErrorOwned {
1134    fn from(e: StyleLineClampParseError) -> Self {
1135        e.to_contained()
1136    }
1137}
1138
1139#[cfg(feature = "parser")]
1140impl_display! { StyleLineClampParseErrorOwned, {
1141    InvalidValue(e) => format!("Invalid line-clamp value: {}", e),
1142    ZeroValue => format!("line-clamp cannot be zero"),
1143}}
1144
1145#[cfg(feature = "parser")]
1146pub fn parse_style_line_clamp<'a>(
1147    input: &'a str,
1148) -> Result<StyleLineClamp, StyleLineClampParseError<'a>> {
1149    let input = input.trim();
1150
1151    let max_lines = input
1152        .parse::<usize>()
1153        .map_err(|_| StyleLineClampParseError::InvalidValue(input))?;
1154
1155    if max_lines == 0 {
1156        return Err(StyleLineClampParseError::ZeroValue);
1157    }
1158
1159    Ok(StyleLineClamp { max_lines })
1160}
1161
1162/// hanging-punctuation property for hanging punctuation marks
1163///
1164/// CSS Text 3 §8: `none | [ first || [ force-end | allow-end ] || last ]`
1165#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1166#[repr(C)]
1167#[derive(Default)]
1168pub struct StyleHangingPunctuation {
1169    pub first: bool,
1170    pub force_end: bool,
1171    pub allow_end: bool,
1172    pub last: bool,
1173}
1174
1175impl StyleHangingPunctuation {
1176    pub fn is_enabled(&self) -> bool {
1177        self.first || self.force_end || self.allow_end || self.last
1178    }
1179}
1180
1181impl FormatAsRustCode for StyleHangingPunctuation {
1182    fn format_as_rust_code(&self, _tabs: usize) -> String {
1183        format!("{:?}", self)
1184    }
1185}
1186
1187impl PrintAsCssValue for StyleHangingPunctuation {
1188    fn print_as_css_value(&self) -> String {
1189        if !self.is_enabled() {
1190            return "none".to_string();
1191        }
1192        let mut parts = alloc::vec::Vec::new();
1193        if self.first { parts.push("first"); }
1194        if self.force_end { parts.push("force-end"); }
1195        if self.allow_end { parts.push("allow-end"); }
1196        if self.last { parts.push("last"); }
1197        parts.join(" ")
1198    }
1199}
1200
1201#[cfg(feature = "parser")]
1202#[derive(Clone, PartialEq)]
1203pub enum StyleHangingPunctuationParseError<'a> {
1204    InvalidValue(&'a str),
1205}
1206#[cfg(feature = "parser")]
1207impl_debug_as_display!(StyleHangingPunctuationParseError<'a>);
1208#[cfg(feature = "parser")]
1209impl_display! { StyleHangingPunctuationParseError<'a>, {
1210    InvalidValue(e) => format!("Invalid hanging-punctuation value: {}", e),
1211}}
1212
1213#[cfg(feature = "parser")]
1214#[derive(Debug, Clone, PartialEq)]
1215#[repr(C, u8)]
1216pub enum StyleHangingPunctuationParseErrorOwned {
1217    InvalidValue(AzString),
1218}
1219
1220#[cfg(feature = "parser")]
1221impl<'a> StyleHangingPunctuationParseError<'a> {
1222    pub fn to_contained(&self) -> StyleHangingPunctuationParseErrorOwned {
1223        match self {
1224            Self::InvalidValue(s) => {
1225                StyleHangingPunctuationParseErrorOwned::InvalidValue(s.to_string().into())
1226            }
1227        }
1228    }
1229}
1230
1231#[cfg(feature = "parser")]
1232impl StyleHangingPunctuationParseErrorOwned {
1233    pub fn to_shared<'a>(&'a self) -> StyleHangingPunctuationParseError<'a> {
1234        match self {
1235            Self::InvalidValue(s) => StyleHangingPunctuationParseError::InvalidValue(s.as_str()),
1236        }
1237    }
1238}
1239
1240#[cfg(feature = "parser")]
1241impl From<StyleHangingPunctuationParseError<'_>> for StyleHangingPunctuationParseErrorOwned {
1242    fn from(e: StyleHangingPunctuationParseError) -> Self {
1243        e.to_contained()
1244    }
1245}
1246
1247#[cfg(feature = "parser")]
1248impl_display! { StyleHangingPunctuationParseErrorOwned, {
1249    InvalidValue(e) => format!("Invalid hanging-punctuation value: {}", e),
1250}}
1251
1252#[cfg(feature = "parser")]
1253pub fn parse_style_hanging_punctuation<'a>(
1254    input: &'a str,
1255) -> Result<StyleHangingPunctuation, StyleHangingPunctuationParseError<'a>> {
1256    let input = input.trim();
1257
1258    if input.eq_ignore_ascii_case("none") {
1259        return Ok(StyleHangingPunctuation::default());
1260    }
1261
1262    let mut first = false;
1263    let mut force_end = false;
1264    let mut allow_end = false;
1265    let mut last = false;
1266
1267    for token in input.split_whitespace() {
1268        match token.to_lowercase().as_str() {
1269            "first" => first = true,
1270            "force-end" => force_end = true,
1271            "allow-end" => allow_end = true,
1272            "last" => last = true,
1273            _ => return Err(StyleHangingPunctuationParseError::InvalidValue(input)),
1274        }
1275    }
1276
1277    if force_end && allow_end {
1278        return Err(StyleHangingPunctuationParseError::InvalidValue(input));
1279    }
1280
1281    Ok(StyleHangingPunctuation { first, force_end, allow_end, last })
1282}
1283
1284/// text-combine-upright property for combining horizontal text in vertical layout
1285#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1286#[repr(C, u8)]
1287#[derive(Default)]
1288pub enum StyleTextCombineUpright {
1289    #[default]
1290    None,
1291    All,
1292    Digits(u8),
1293}
1294
1295
1296impl FormatAsRustCode for StyleTextCombineUpright {
1297    fn format_as_rust_code(&self, _tabs: usize) -> String {
1298        format!("{:?}", self)
1299    }
1300}
1301
1302impl PrintAsCssValue for StyleTextCombineUpright {
1303    fn print_as_css_value(&self) -> String {
1304        match self {
1305            Self::None => "none".to_string(),
1306            Self::All => "all".to_string(),
1307            Self::Digits(n) => format!("digits {}", n),
1308        }
1309    }
1310}
1311
1312#[cfg(feature = "parser")]
1313#[derive(Clone, PartialEq)]
1314pub enum StyleTextCombineUprightParseError<'a> {
1315    InvalidValue(&'a str),
1316    InvalidDigits(&'a str),
1317}
1318#[cfg(feature = "parser")]
1319impl_debug_as_display!(StyleTextCombineUprightParseError<'a>);
1320#[cfg(feature = "parser")]
1321impl_display! { StyleTextCombineUprightParseError<'a>, {
1322    InvalidValue(e) => format!("Invalid text-combine-upright value: {}", e),
1323    InvalidDigits(e) => format!("Invalid text-combine-upright digits: {}", e),
1324}}
1325
1326#[cfg(feature = "parser")]
1327#[derive(Debug, Clone, PartialEq)]
1328#[repr(C, u8)]
1329pub enum StyleTextCombineUprightParseErrorOwned {
1330    InvalidValue(AzString),
1331    InvalidDigits(AzString),
1332}
1333
1334#[cfg(feature = "parser")]
1335impl<'a> StyleTextCombineUprightParseError<'a> {
1336    pub fn to_contained(&self) -> StyleTextCombineUprightParseErrorOwned {
1337        match self {
1338            Self::InvalidValue(s) => {
1339                StyleTextCombineUprightParseErrorOwned::InvalidValue(s.to_string().into())
1340            }
1341            Self::InvalidDigits(s) => {
1342                StyleTextCombineUprightParseErrorOwned::InvalidDigits(s.to_string().into())
1343            }
1344        }
1345    }
1346}
1347
1348#[cfg(feature = "parser")]
1349impl StyleTextCombineUprightParseErrorOwned {
1350    pub fn to_shared<'a>(&'a self) -> StyleTextCombineUprightParseError<'a> {
1351        match self {
1352            Self::InvalidValue(s) => StyleTextCombineUprightParseError::InvalidValue(s.as_str()),
1353            Self::InvalidDigits(s) => StyleTextCombineUprightParseError::InvalidDigits(s.as_str()),
1354        }
1355    }
1356}
1357
1358#[cfg(feature = "parser")]
1359impl From<StyleTextCombineUprightParseError<'_>> for StyleTextCombineUprightParseErrorOwned {
1360    fn from(e: StyleTextCombineUprightParseError) -> Self {
1361        e.to_contained()
1362    }
1363}
1364
1365#[cfg(feature = "parser")]
1366impl_display! { StyleTextCombineUprightParseErrorOwned, {
1367    InvalidValue(e) => format!("Invalid text-combine-upright value: {}", e),
1368    InvalidDigits(e) => format!("Invalid text-combine-upright digits: {}", e),
1369}}
1370
1371#[cfg(feature = "parser")]
1372pub fn parse_style_text_combine_upright<'a>(
1373    input: &'a str,
1374) -> Result<StyleTextCombineUpright, StyleTextCombineUprightParseError<'a>> {
1375    let trimmed = input.trim();
1376
1377    if trimmed.eq_ignore_ascii_case("none") {
1378        Ok(StyleTextCombineUpright::None)
1379    } else if trimmed.eq_ignore_ascii_case("all") {
1380        Ok(StyleTextCombineUpright::All)
1381    } else if trimmed.starts_with("digits") {
1382        let parts: Vec<&str> = trimmed.split_whitespace().collect();
1383        if parts.len() == 2 {
1384            let n = parts[1]
1385                .parse::<u8>()
1386                .map_err(|_| StyleTextCombineUprightParseError::InvalidDigits(input))?;
1387            if (2..=4).contains(&n) {
1388                Ok(StyleTextCombineUpright::Digits(n))
1389            } else {
1390                Err(StyleTextCombineUprightParseError::InvalidDigits(input))
1391            }
1392        } else {
1393            // Default to "digits 2"
1394            Ok(StyleTextCombineUpright::Digits(2))
1395        }
1396    } else {
1397        Err(StyleTextCombineUprightParseError::InvalidValue(input))
1398    }
1399}
1400
1401#[cfg(feature = "parser")]
1402#[derive(Clone, PartialEq)]
1403pub enum StyleWordSpacingParseError<'a> {
1404    PixelValue(CssPixelValueParseError<'a>),
1405}
1406#[cfg(feature = "parser")]
1407impl_debug_as_display!(StyleWordSpacingParseError<'a>);
1408#[cfg(feature = "parser")]
1409impl_display! { StyleWordSpacingParseError<'a>, {
1410    PixelValue(e) => format!("Invalid word-spacing value: {}", e),
1411}}
1412#[cfg(feature = "parser")]
1413impl_from!(
1414    CssPixelValueParseError<'a>,
1415    StyleWordSpacingParseError::PixelValue
1416);
1417
1418#[cfg(feature = "parser")]
1419#[derive(Debug, Clone, PartialEq)]
1420#[repr(C, u8)]
1421pub enum StyleWordSpacingParseErrorOwned {
1422    PixelValue(CssPixelValueParseErrorOwned),
1423}
1424
1425#[cfg(feature = "parser")]
1426impl<'a> StyleWordSpacingParseError<'a> {
1427    pub fn to_contained(&self) -> StyleWordSpacingParseErrorOwned {
1428        match self {
1429            Self::PixelValue(e) => StyleWordSpacingParseErrorOwned::PixelValue(e.to_contained()),
1430        }
1431    }
1432}
1433
1434#[cfg(feature = "parser")]
1435impl StyleWordSpacingParseErrorOwned {
1436    pub fn to_shared<'a>(&'a self) -> StyleWordSpacingParseError<'a> {
1437        match self {
1438            Self::PixelValue(e) => StyleWordSpacingParseError::PixelValue(e.to_shared()),
1439        }
1440    }
1441}
1442
1443#[cfg(feature = "parser")]
1444pub fn parse_style_word_spacing(
1445    input: &str,
1446) -> Result<StyleWordSpacing, StyleWordSpacingParseError<'_>> {
1447    crate::props::basic::pixel::parse_pixel_value(input)
1448        .map(|inner| StyleWordSpacing { inner })
1449        .map_err(StyleWordSpacingParseError::PixelValue)
1450}
1451
1452#[cfg(feature = "parser")]
1453#[derive(Clone, PartialEq)]
1454#[repr(C, u8)]
1455pub enum StyleLineHeightParseError {
1456    Percentage(PercentageParseError),
1457}
1458#[cfg(feature = "parser")]
1459impl_debug_as_display!(StyleLineHeightParseError);
1460#[cfg(feature = "parser")]
1461impl_display! { StyleLineHeightParseError, {
1462    Percentage(e) => format!("Invalid line-height value: {}", e),
1463}}
1464#[cfg(feature = "parser")]
1465impl_from!(PercentageParseError, StyleLineHeightParseError::Percentage);
1466
1467#[cfg(feature = "parser")]
1468#[derive(Debug, Clone, PartialEq)]
1469pub enum StyleLineHeightParseErrorOwned {
1470    Percentage(PercentageParseErrorOwned),
1471}
1472
1473#[cfg(feature = "parser")]
1474impl StyleLineHeightParseError {
1475    pub fn to_contained(&self) -> StyleLineHeightParseErrorOwned {
1476        match self {
1477            Self::Percentage(e) => StyleLineHeightParseErrorOwned::Percentage(e.to_contained()),
1478        }
1479    }
1480}
1481
1482#[cfg(feature = "parser")]
1483impl StyleLineHeightParseErrorOwned {
1484    pub fn to_shared(&self) -> StyleLineHeightParseError {
1485        match self {
1486            Self::Percentage(e) => StyleLineHeightParseError::Percentage(e.to_shared()),
1487        }
1488    }
1489}
1490
1491#[cfg(feature = "parser")]
1492pub fn parse_style_line_height(input: &str) -> Result<StyleLineHeight, StyleLineHeightParseError> {
1493    // Try <number> or <percentage> first (multiplier of font-size)
1494    if let Ok(inner) = crate::props::basic::length::parse_percentage_value(input) {
1495        return Ok(StyleLineHeight { inner });
1496    }
1497    // Try <length> (e.g., "50px") — store as NEGATIVE PercentageValue to signal absolute px.
1498    // Convention: negative normalized() = absolute pixel value (CSS line-height can't be negative).
1499    // Resolved at layout time in fc.rs where font_size is known.
1500    if let Ok(px) = crate::props::basic::pixel::parse_pixel_value(input) {
1501        if px.metric == crate::props::basic::length::SizeMetric::Px {
1502            let px_val = px.number.get();
1503            return Ok(StyleLineHeight {
1504                inner: crate::props::basic::length::PercentageValue::new(-px_val * 100.0),
1505            });
1506        }
1507    }
1508    Err(StyleLineHeightParseError::Percentage(
1509        crate::props::basic::length::PercentageParseError::InvalidUnit("".to_string().into()),
1510    ))
1511}
1512
1513#[cfg(feature = "parser")]
1514#[derive(Clone, PartialEq)]
1515pub enum StyleTabSizeParseError<'a> {
1516    PixelValue(CssPixelValueParseError<'a>),
1517}
1518#[cfg(feature = "parser")]
1519impl_debug_as_display!(StyleTabSizeParseError<'a>);
1520#[cfg(feature = "parser")]
1521impl_display! { StyleTabSizeParseError<'a>, {
1522    PixelValue(e) => format!("Invalid tab-size value: {}", e),
1523}}
1524#[cfg(feature = "parser")]
1525impl_from!(
1526    CssPixelValueParseError<'a>,
1527    StyleTabSizeParseError::PixelValue
1528);
1529
1530#[cfg(feature = "parser")]
1531#[derive(Debug, Clone, PartialEq)]
1532#[repr(C, u8)]
1533pub enum StyleTabSizeParseErrorOwned {
1534    PixelValue(CssPixelValueParseErrorOwned),
1535}
1536
1537#[cfg(feature = "parser")]
1538impl<'a> StyleTabSizeParseError<'a> {
1539    pub fn to_contained(&self) -> StyleTabSizeParseErrorOwned {
1540        match self {
1541            Self::PixelValue(e) => StyleTabSizeParseErrorOwned::PixelValue(e.to_contained()),
1542        }
1543    }
1544}
1545
1546#[cfg(feature = "parser")]
1547impl StyleTabSizeParseErrorOwned {
1548    pub fn to_shared<'a>(&'a self) -> StyleTabSizeParseError<'a> {
1549        match self {
1550            Self::PixelValue(e) => StyleTabSizeParseError::PixelValue(e.to_shared()),
1551        }
1552    }
1553}
1554
1555#[cfg(feature = "parser")]
1556pub fn parse_style_tab_size(input: &str) -> Result<StyleTabSize, StyleTabSizeParseError<'_>> {
1557    if let Ok(number) = input.trim().parse::<f32>() {
1558        Ok(StyleTabSize {
1559            inner: PixelValue::em(number),
1560        })
1561    } else {
1562        crate::props::basic::pixel::parse_pixel_value(input)
1563            .map(|v| StyleTabSize { inner: v })
1564            .map_err(StyleTabSizeParseError::PixelValue)
1565    }
1566}
1567
1568#[cfg(feature = "parser")]
1569#[derive(Clone, PartialEq)]
1570pub enum StyleWhiteSpaceParseError<'a> {
1571    InvalidValue(InvalidValueErr<'a>),
1572}
1573#[cfg(feature = "parser")]
1574impl_debug_as_display!(StyleWhiteSpaceParseError<'a>);
1575#[cfg(feature = "parser")]
1576impl_display! { StyleWhiteSpaceParseError<'a>, {
1577    InvalidValue(e) => format!("Invalid white-space value: \"{}\"", e.0),
1578}}
1579#[cfg(feature = "parser")]
1580impl_from!(InvalidValueErr<'a>, StyleWhiteSpaceParseError::InvalidValue);
1581
1582#[cfg(feature = "parser")]
1583#[derive(Debug, Clone, PartialEq)]
1584#[repr(C, u8)]
1585pub enum StyleWhiteSpaceParseErrorOwned {
1586    InvalidValue(InvalidValueErrOwned),
1587}
1588
1589#[cfg(feature = "parser")]
1590impl<'a> StyleWhiteSpaceParseError<'a> {
1591    pub fn to_contained(&self) -> StyleWhiteSpaceParseErrorOwned {
1592        match self {
1593            Self::InvalidValue(e) => StyleWhiteSpaceParseErrorOwned::InvalidValue(e.to_contained()),
1594        }
1595    }
1596}
1597
1598#[cfg(feature = "parser")]
1599impl StyleWhiteSpaceParseErrorOwned {
1600    pub fn to_shared<'a>(&'a self) -> StyleWhiteSpaceParseError<'a> {
1601        match self {
1602            Self::InvalidValue(e) => StyleWhiteSpaceParseError::InvalidValue(e.to_shared()),
1603        }
1604    }
1605}
1606
1607#[cfg(feature = "parser")]
1608pub fn parse_style_white_space(input: &str) -> Result<StyleWhiteSpace, StyleWhiteSpaceParseError<'_>> {
1609    match input.trim() {
1610        "normal" => Ok(StyleWhiteSpace::Normal),
1611        "pre" => Ok(StyleWhiteSpace::Pre),
1612        "nowrap" | "no-wrap" => Ok(StyleWhiteSpace::Nowrap),
1613        "pre-wrap" => Ok(StyleWhiteSpace::PreWrap),
1614        "pre-line" => Ok(StyleWhiteSpace::PreLine),
1615        "break-spaces" => Ok(StyleWhiteSpace::BreakSpaces),
1616        other => Err(StyleWhiteSpaceParseError::InvalidValue(InvalidValueErr(
1617            other,
1618        ))),
1619    }
1620}
1621
1622#[cfg(feature = "parser")]
1623#[derive(Clone, PartialEq)]
1624pub enum StyleHyphensParseError<'a> {
1625    InvalidValue(InvalidValueErr<'a>),
1626}
1627#[cfg(feature = "parser")]
1628impl_debug_as_display!(StyleHyphensParseError<'a>);
1629#[cfg(feature = "parser")]
1630impl_display! { StyleHyphensParseError<'a>, {
1631    InvalidValue(e) => format!("Invalid hyphens value: \"{}\"", e.0),
1632}}
1633#[cfg(feature = "parser")]
1634impl_from!(InvalidValueErr<'a>, StyleHyphensParseError::InvalidValue);
1635
1636#[cfg(feature = "parser")]
1637#[derive(Debug, Clone, PartialEq)]
1638#[repr(C, u8)]
1639pub enum StyleHyphensParseErrorOwned {
1640    InvalidValue(InvalidValueErrOwned),
1641}
1642
1643#[cfg(feature = "parser")]
1644impl<'a> StyleHyphensParseError<'a> {
1645    pub fn to_contained(&self) -> StyleHyphensParseErrorOwned {
1646        match self {
1647            Self::InvalidValue(e) => StyleHyphensParseErrorOwned::InvalidValue(e.to_contained()),
1648        }
1649    }
1650}
1651
1652#[cfg(feature = "parser")]
1653impl StyleHyphensParseErrorOwned {
1654    pub fn to_shared<'a>(&'a self) -> StyleHyphensParseError<'a> {
1655        match self {
1656            Self::InvalidValue(e) => StyleHyphensParseError::InvalidValue(e.to_shared()),
1657        }
1658    }
1659}
1660
1661#[cfg(feature = "parser")]
1662pub fn parse_style_hyphens(input: &str) -> Result<StyleHyphens, StyleHyphensParseError<'_>> {
1663    match input.trim() {
1664        "none" => Ok(StyleHyphens::None),
1665        "manual" => Ok(StyleHyphens::Manual),
1666        "auto" => Ok(StyleHyphens::Auto),
1667        other => Err(StyleHyphensParseError::InvalidValue(InvalidValueErr(other))),
1668    }
1669}
1670
1671// -- StyleLineBreak parse --
1672
1673#[cfg(feature = "parser")]
1674#[derive(Clone, PartialEq)]
1675pub enum StyleLineBreakParseError<'a> {
1676    InvalidValue(InvalidValueErr<'a>),
1677}
1678#[cfg(feature = "parser")]
1679impl_debug_as_display!(StyleLineBreakParseError<'a>);
1680#[cfg(feature = "parser")]
1681impl_display! { StyleLineBreakParseError<'a>, {
1682    InvalidValue(e) => format!("Invalid line-break value: \"{}\"", e.0),
1683}}
1684#[cfg(feature = "parser")]
1685impl_from!(InvalidValueErr<'a>, StyleLineBreakParseError::InvalidValue);
1686
1687#[cfg(feature = "parser")]
1688#[derive(Debug, Clone, PartialEq)]
1689#[repr(C, u8)]
1690pub enum StyleLineBreakParseErrorOwned {
1691    InvalidValue(InvalidValueErrOwned),
1692}
1693
1694#[cfg(feature = "parser")]
1695impl<'a> StyleLineBreakParseError<'a> {
1696    pub fn to_contained(&self) -> StyleLineBreakParseErrorOwned {
1697        match self {
1698            Self::InvalidValue(e) => StyleLineBreakParseErrorOwned::InvalidValue(e.to_contained()),
1699        }
1700    }
1701}
1702
1703#[cfg(feature = "parser")]
1704impl StyleLineBreakParseErrorOwned {
1705    pub fn to_shared<'a>(&'a self) -> StyleLineBreakParseError<'a> {
1706        match self {
1707            Self::InvalidValue(e) => StyleLineBreakParseError::InvalidValue(e.to_shared()),
1708        }
1709    }
1710}
1711
1712#[cfg(feature = "parser")]
1713pub fn parse_style_line_break(input: &str) -> Result<StyleLineBreak, StyleLineBreakParseError<'_>> {
1714    match input.trim() {
1715        "auto" => Ok(StyleLineBreak::Auto),
1716        "loose" => Ok(StyleLineBreak::Loose),
1717        "normal" => Ok(StyleLineBreak::Normal),
1718        "strict" => Ok(StyleLineBreak::Strict),
1719        "anywhere" => Ok(StyleLineBreak::Anywhere),
1720        other => Err(StyleLineBreakParseError::InvalidValue(InvalidValueErr(other))),
1721    }
1722}
1723
1724// -- StyleWordBreak parse --
1725
1726#[cfg(feature = "parser")]
1727#[derive(Clone, PartialEq)]
1728pub enum StyleWordBreakParseError<'a> {
1729    InvalidValue(InvalidValueErr<'a>),
1730}
1731#[cfg(feature = "parser")]
1732impl_debug_as_display!(StyleWordBreakParseError<'a>);
1733#[cfg(feature = "parser")]
1734impl_display! { StyleWordBreakParseError<'a>, {
1735    InvalidValue(e) => format!("Invalid word-break value: \"{}\"", e.0),
1736}}
1737#[cfg(feature = "parser")]
1738impl_from!(InvalidValueErr<'a>, StyleWordBreakParseError::InvalidValue);
1739
1740#[cfg(feature = "parser")]
1741#[derive(Debug, Clone, PartialEq)]
1742#[repr(C, u8)]
1743pub enum StyleWordBreakParseErrorOwned {
1744    InvalidValue(InvalidValueErrOwned),
1745}
1746
1747#[cfg(feature = "parser")]
1748impl<'a> StyleWordBreakParseError<'a> {
1749    pub fn to_contained(&self) -> StyleWordBreakParseErrorOwned {
1750        match self {
1751            Self::InvalidValue(e) => StyleWordBreakParseErrorOwned::InvalidValue(e.to_contained()),
1752        }
1753    }
1754}
1755
1756#[cfg(feature = "parser")]
1757impl StyleWordBreakParseErrorOwned {
1758    pub fn to_shared<'a>(&'a self) -> StyleWordBreakParseError<'a> {
1759        match self {
1760            Self::InvalidValue(e) => StyleWordBreakParseError::InvalidValue(e.to_shared()),
1761        }
1762    }
1763}
1764
1765#[cfg(feature = "parser")]
1766pub fn parse_style_word_break(input: &str) -> Result<StyleWordBreak, StyleWordBreakParseError<'_>> {
1767    match input.trim() {
1768        "normal" => Ok(StyleWordBreak::Normal),
1769        "break-all" => Ok(StyleWordBreak::BreakAll),
1770        "keep-all" => Ok(StyleWordBreak::KeepAll),
1771        "break-word" => Ok(StyleWordBreak::BreakWord),
1772        other => Err(StyleWordBreakParseError::InvalidValue(InvalidValueErr(other))),
1773    }
1774}
1775
1776// -- StyleOverflowWrap parse --
1777
1778#[cfg(feature = "parser")]
1779#[derive(Clone, PartialEq)]
1780pub enum StyleOverflowWrapParseError<'a> {
1781    InvalidValue(InvalidValueErr<'a>),
1782}
1783#[cfg(feature = "parser")]
1784impl_debug_as_display!(StyleOverflowWrapParseError<'a>);
1785#[cfg(feature = "parser")]
1786impl_display! { StyleOverflowWrapParseError<'a>, {
1787    InvalidValue(e) => format!("Invalid overflow-wrap value: \"{}\"", e.0),
1788}}
1789#[cfg(feature = "parser")]
1790impl_from!(InvalidValueErr<'a>, StyleOverflowWrapParseError::InvalidValue);
1791
1792#[cfg(feature = "parser")]
1793#[derive(Debug, Clone, PartialEq)]
1794#[repr(C, u8)]
1795pub enum StyleOverflowWrapParseErrorOwned {
1796    InvalidValue(InvalidValueErrOwned),
1797}
1798
1799#[cfg(feature = "parser")]
1800impl<'a> StyleOverflowWrapParseError<'a> {
1801    pub fn to_contained(&self) -> StyleOverflowWrapParseErrorOwned {
1802        match self {
1803            Self::InvalidValue(e) => StyleOverflowWrapParseErrorOwned::InvalidValue(e.to_contained()),
1804        }
1805    }
1806}
1807
1808#[cfg(feature = "parser")]
1809impl StyleOverflowWrapParseErrorOwned {
1810    pub fn to_shared<'a>(&'a self) -> StyleOverflowWrapParseError<'a> {
1811        match self {
1812            Self::InvalidValue(e) => StyleOverflowWrapParseError::InvalidValue(e.to_shared()),
1813        }
1814    }
1815}
1816
1817#[cfg(feature = "parser")]
1818pub fn parse_style_overflow_wrap(input: &str) -> Result<StyleOverflowWrap, StyleOverflowWrapParseError<'_>> {
1819    match input.trim() {
1820        "normal" => Ok(StyleOverflowWrap::Normal),
1821        "anywhere" => Ok(StyleOverflowWrap::Anywhere),
1822        "break-word" => Ok(StyleOverflowWrap::BreakWord),
1823        other => Err(StyleOverflowWrapParseError::InvalidValue(InvalidValueErr(other))),
1824    }
1825}
1826
1827// -- StyleTextAlignLast parse --
1828
1829#[cfg(feature = "parser")]
1830#[derive(Clone, PartialEq)]
1831pub enum StyleTextAlignLastParseError<'a> {
1832    InvalidValue(InvalidValueErr<'a>),
1833}
1834#[cfg(feature = "parser")]
1835impl_debug_as_display!(StyleTextAlignLastParseError<'a>);
1836#[cfg(feature = "parser")]
1837impl_display! { StyleTextAlignLastParseError<'a>, {
1838    InvalidValue(e) => format!("Invalid text-align-last value: \"{}\"", e.0),
1839}}
1840#[cfg(feature = "parser")]
1841impl_from!(InvalidValueErr<'a>, StyleTextAlignLastParseError::InvalidValue);
1842
1843#[cfg(feature = "parser")]
1844#[derive(Debug, Clone, PartialEq)]
1845#[repr(C, u8)]
1846pub enum StyleTextAlignLastParseErrorOwned {
1847    InvalidValue(InvalidValueErrOwned),
1848}
1849
1850#[cfg(feature = "parser")]
1851impl<'a> StyleTextAlignLastParseError<'a> {
1852    pub fn to_contained(&self) -> StyleTextAlignLastParseErrorOwned {
1853        match self {
1854            Self::InvalidValue(e) => StyleTextAlignLastParseErrorOwned::InvalidValue(e.to_contained()),
1855        }
1856    }
1857}
1858
1859#[cfg(feature = "parser")]
1860impl StyleTextAlignLastParseErrorOwned {
1861    pub fn to_shared<'a>(&'a self) -> StyleTextAlignLastParseError<'a> {
1862        match self {
1863            Self::InvalidValue(e) => StyleTextAlignLastParseError::InvalidValue(e.to_shared()),
1864        }
1865    }
1866}
1867
1868#[cfg(feature = "parser")]
1869pub fn parse_style_text_align_last(input: &str) -> Result<StyleTextAlignLast, StyleTextAlignLastParseError<'_>> {
1870    match input.trim() {
1871        "auto" => Ok(StyleTextAlignLast::Auto),
1872        "start" => Ok(StyleTextAlignLast::Start),
1873        "end" => Ok(StyleTextAlignLast::End),
1874        "left" => Ok(StyleTextAlignLast::Left),
1875        "right" => Ok(StyleTextAlignLast::Right),
1876        "center" => Ok(StyleTextAlignLast::Center),
1877        "justify" => Ok(StyleTextAlignLast::Justify),
1878        other => Err(StyleTextAlignLastParseError::InvalidValue(InvalidValueErr(other))),
1879    }
1880}
1881
1882#[cfg(feature = "parser")]
1883#[derive(Clone, PartialEq)]
1884pub enum StyleDirectionParseError<'a> {
1885    InvalidValue(InvalidValueErr<'a>),
1886}
1887#[cfg(feature = "parser")]
1888impl_debug_as_display!(StyleDirectionParseError<'a>);
1889#[cfg(feature = "parser")]
1890impl_display! { StyleDirectionParseError<'a>, {
1891    InvalidValue(e) => format!("Invalid direction value: \"{}\"", e.0),
1892}}
1893#[cfg(feature = "parser")]
1894impl_from!(InvalidValueErr<'a>, StyleDirectionParseError::InvalidValue);
1895
1896#[cfg(feature = "parser")]
1897#[derive(Debug, Clone, PartialEq)]
1898#[repr(C, u8)]
1899pub enum StyleDirectionParseErrorOwned {
1900    InvalidValue(InvalidValueErrOwned),
1901}
1902
1903#[cfg(feature = "parser")]
1904impl<'a> StyleDirectionParseError<'a> {
1905    pub fn to_contained(&self) -> StyleDirectionParseErrorOwned {
1906        match self {
1907            Self::InvalidValue(e) => StyleDirectionParseErrorOwned::InvalidValue(e.to_contained()),
1908        }
1909    }
1910}
1911
1912#[cfg(feature = "parser")]
1913impl StyleDirectionParseErrorOwned {
1914    pub fn to_shared<'a>(&'a self) -> StyleDirectionParseError<'a> {
1915        match self {
1916            Self::InvalidValue(e) => StyleDirectionParseError::InvalidValue(e.to_shared()),
1917        }
1918    }
1919}
1920
1921#[cfg(feature = "parser")]
1922pub fn parse_style_direction(input: &str) -> Result<StyleDirection, StyleDirectionParseError<'_>> {
1923    match input.trim() {
1924        "ltr" => Ok(StyleDirection::Ltr),
1925        "rtl" => Ok(StyleDirection::Rtl),
1926        other => Err(StyleDirectionParseError::InvalidValue(InvalidValueErr(
1927            other,
1928        ))),
1929    }
1930}
1931
1932#[cfg(feature = "parser")]
1933#[derive(Clone, PartialEq)]
1934pub enum StyleUserSelectParseError<'a> {
1935    InvalidValue(InvalidValueErr<'a>),
1936}
1937#[cfg(feature = "parser")]
1938impl_debug_as_display!(StyleUserSelectParseError<'a>);
1939#[cfg(feature = "parser")]
1940impl_display! { StyleUserSelectParseError<'a>, {
1941    InvalidValue(e) => format!("Invalid user-select value: \"{}\"", e.0),
1942}}
1943#[cfg(feature = "parser")]
1944impl_from!(InvalidValueErr<'a>, StyleUserSelectParseError::InvalidValue);
1945
1946#[cfg(feature = "parser")]
1947#[derive(Debug, Clone, PartialEq)]
1948#[repr(C, u8)]
1949pub enum StyleUserSelectParseErrorOwned {
1950    InvalidValue(InvalidValueErrOwned),
1951}
1952
1953#[cfg(feature = "parser")]
1954impl<'a> StyleUserSelectParseError<'a> {
1955    pub fn to_contained(&self) -> StyleUserSelectParseErrorOwned {
1956        match self {
1957            Self::InvalidValue(e) => StyleUserSelectParseErrorOwned::InvalidValue(e.to_contained()),
1958        }
1959    }
1960}
1961
1962#[cfg(feature = "parser")]
1963impl StyleUserSelectParseErrorOwned {
1964    pub fn to_shared<'a>(&'a self) -> StyleUserSelectParseError<'a> {
1965        match self {
1966            Self::InvalidValue(e) => StyleUserSelectParseError::InvalidValue(e.to_shared()),
1967        }
1968    }
1969}
1970
1971#[cfg(feature = "parser")]
1972pub fn parse_style_user_select(input: &str) -> Result<StyleUserSelect, StyleUserSelectParseError<'_>> {
1973    match input.trim() {
1974        "auto" => Ok(StyleUserSelect::Auto),
1975        "text" => Ok(StyleUserSelect::Text),
1976        "none" => Ok(StyleUserSelect::None),
1977        "all" => Ok(StyleUserSelect::All),
1978        other => Err(StyleUserSelectParseError::InvalidValue(InvalidValueErr(
1979            other,
1980        ))),
1981    }
1982}
1983
1984#[cfg(feature = "parser")]
1985#[derive(Clone, PartialEq)]
1986pub enum StyleTextDecorationParseError<'a> {
1987    InvalidValue(InvalidValueErr<'a>),
1988}
1989#[cfg(feature = "parser")]
1990impl_debug_as_display!(StyleTextDecorationParseError<'a>);
1991#[cfg(feature = "parser")]
1992impl_display! { StyleTextDecorationParseError<'a>, {
1993    InvalidValue(e) => format!("Invalid text-decoration value: \"{}\"", e.0),
1994}}
1995#[cfg(feature = "parser")]
1996impl_from!(
1997    InvalidValueErr<'a>,
1998    StyleTextDecorationParseError::InvalidValue
1999);
2000
2001#[cfg(feature = "parser")]
2002#[derive(Debug, Clone, PartialEq)]
2003#[repr(C, u8)]
2004pub enum StyleTextDecorationParseErrorOwned {
2005    InvalidValue(InvalidValueErrOwned),
2006}
2007
2008#[cfg(feature = "parser")]
2009impl<'a> StyleTextDecorationParseError<'a> {
2010    pub fn to_contained(&self) -> StyleTextDecorationParseErrorOwned {
2011        match self {
2012            Self::InvalidValue(e) => {
2013                StyleTextDecorationParseErrorOwned::InvalidValue(e.to_contained())
2014            }
2015        }
2016    }
2017}
2018
2019#[cfg(feature = "parser")]
2020impl StyleTextDecorationParseErrorOwned {
2021    pub fn to_shared<'a>(&'a self) -> StyleTextDecorationParseError<'a> {
2022        match self {
2023            Self::InvalidValue(e) => StyleTextDecorationParseError::InvalidValue(e.to_shared()),
2024        }
2025    }
2026}
2027
2028#[cfg(feature = "parser")]
2029pub fn parse_style_text_decoration(
2030    input: &str,
2031) -> Result<StyleTextDecoration, StyleTextDecorationParseError<'_>> {
2032    match input.trim() {
2033        "none" => Ok(StyleTextDecoration::None),
2034        "underline" => Ok(StyleTextDecoration::Underline),
2035        "overline" => Ok(StyleTextDecoration::Overline),
2036        "line-through" => Ok(StyleTextDecoration::LineThrough),
2037        other => Err(StyleTextDecorationParseError::InvalidValue(
2038            InvalidValueErr(other),
2039        )),
2040    }
2041}
2042
2043#[cfg(feature = "parser")]
2044#[derive(Clone, PartialEq)]
2045pub enum StyleVerticalAlignParseError<'a> {
2046    InvalidValue(InvalidValueErr<'a>),
2047}
2048#[cfg(feature = "parser")]
2049impl_debug_as_display!(StyleVerticalAlignParseError<'a>);
2050#[cfg(feature = "parser")]
2051impl_display! { StyleVerticalAlignParseError<'a>, {
2052    InvalidValue(e) => format!("Invalid vertical-align value: \"{}\"", e.0),
2053}}
2054#[cfg(feature = "parser")]
2055impl_from!(
2056    InvalidValueErr<'a>,
2057    StyleVerticalAlignParseError::InvalidValue
2058);
2059
2060#[cfg(feature = "parser")]
2061#[derive(Debug, Clone, PartialEq)]
2062#[repr(C, u8)]
2063pub enum StyleVerticalAlignParseErrorOwned {
2064    InvalidValue(InvalidValueErrOwned),
2065}
2066
2067#[cfg(feature = "parser")]
2068impl<'a> StyleVerticalAlignParseError<'a> {
2069    pub fn to_contained(&self) -> StyleVerticalAlignParseErrorOwned {
2070        match self {
2071            Self::InvalidValue(e) => {
2072                StyleVerticalAlignParseErrorOwned::InvalidValue(e.to_contained())
2073            }
2074        }
2075    }
2076}
2077
2078#[cfg(feature = "parser")]
2079impl StyleVerticalAlignParseErrorOwned {
2080    pub fn to_shared<'a>(&'a self) -> StyleVerticalAlignParseError<'a> {
2081        match self {
2082            Self::InvalidValue(e) => StyleVerticalAlignParseError::InvalidValue(e.to_shared()),
2083        }
2084    }
2085}
2086
2087#[cfg(feature = "parser")]
2088pub fn parse_style_vertical_align(
2089    input: &str,
2090) -> Result<StyleVerticalAlign, StyleVerticalAlignParseError<'_>> {
2091    match input.trim() {
2092        "baseline" => Ok(StyleVerticalAlign::Baseline),
2093        "top" => Ok(StyleVerticalAlign::Top),
2094        "middle" => Ok(StyleVerticalAlign::Middle),
2095        "bottom" => Ok(StyleVerticalAlign::Bottom),
2096        "sub" => Ok(StyleVerticalAlign::Sub),
2097        "super" => Ok(StyleVerticalAlign::Superscript),
2098        "text-top" => Ok(StyleVerticalAlign::TextTop),
2099        "text-bottom" => Ok(StyleVerticalAlign::TextBottom),
2100        other if other.ends_with('%') => {
2101            let num_str = other.trim_end_matches('%').trim();
2102            match num_str.parse::<f32>() {
2103                Ok(val) => Ok(StyleVerticalAlign::Percentage(PercentageValue::new(val))),
2104                Err(_) => Err(StyleVerticalAlignParseError::InvalidValue(InvalidValueErr(other))),
2105            }
2106        }
2107        other => match crate::props::basic::pixel::parse_pixel_value(other) {
2108            Ok(pv) => Ok(StyleVerticalAlign::Length(pv)),
2109            Err(_) => Err(StyleVerticalAlignParseError::InvalidValue(InvalidValueErr(
2110                other,
2111            ))),
2112        },
2113    }
2114}
2115
2116// --- CaretColor ---
2117
2118#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2119#[repr(C)]
2120pub struct CaretColor {
2121    pub inner: ColorU,
2122}
2123
2124impl Default for CaretColor {
2125    fn default() -> Self {
2126        Self {
2127            inner: ColorU::BLACK,
2128        }
2129    }
2130}
2131
2132impl PrintAsCssValue for CaretColor {
2133    fn print_as_css_value(&self) -> String {
2134        self.inner.to_hash()
2135    }
2136}
2137
2138impl crate::format_rust_code::FormatAsRustCode for CaretColor {
2139    fn format_as_rust_code(&self, _tabs: usize) -> String {
2140        format!(
2141            "CaretColor {{ inner: {} }}",
2142            crate::format_rust_code::format_color_value(&self.inner)
2143        )
2144    }
2145}
2146
2147#[cfg(feature = "parser")]
2148pub fn parse_caret_color(input: &str) -> Result<CaretColor, CssColorParseError<'_>> {
2149    parse_css_color(input).map(|inner| CaretColor { inner })
2150}
2151
2152// --- CaretAnimationDuration ---
2153
2154#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2155#[repr(C)]
2156pub struct CaretAnimationDuration {
2157    pub inner: CssDuration,
2158}
2159
2160impl Default for CaretAnimationDuration {
2161    fn default() -> Self {
2162        Self {
2163            inner: CssDuration { inner: 500 },
2164        } // Default 500ms blink time
2165    }
2166}
2167
2168impl PrintAsCssValue for CaretAnimationDuration {
2169    fn print_as_css_value(&self) -> String {
2170        self.inner.print_as_css_value()
2171    }
2172}
2173
2174impl crate::format_rust_code::FormatAsRustCode for CaretAnimationDuration {
2175    fn format_as_rust_code(&self, _tabs: usize) -> String {
2176        format!(
2177            "CaretAnimationDuration {{ inner: {} }}",
2178            self.inner.format_as_rust_code(0)
2179        )
2180    }
2181}
2182
2183#[cfg(feature = "parser")]
2184pub fn parse_caret_animation_duration(
2185    input: &str,
2186) -> Result<CaretAnimationDuration, DurationParseError<'_>> {
2187    use crate::props::basic::parse_duration;
2188
2189    parse_duration(input).map(|inner| CaretAnimationDuration { inner })
2190}
2191
2192// --- CaretWidth ---
2193
2194/// Width of the text cursor (caret) in pixels.
2195/// CSS doesn't have a standard property for this, so we use `-azul-caret-width`.
2196#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2197#[repr(C)]
2198pub struct CaretWidth {
2199    pub inner: PixelValue,
2200}
2201
2202impl Default for CaretWidth {
2203    fn default() -> Self {
2204        Self {
2205            inner: PixelValue::px(2.0), // Default 2px caret width
2206        }
2207    }
2208}
2209
2210impl PrintAsCssValue for CaretWidth {
2211    fn print_as_css_value(&self) -> String {
2212        self.inner.print_as_css_value()
2213    }
2214}
2215
2216impl crate::format_rust_code::FormatAsRustCode for CaretWidth {
2217    fn format_as_rust_code(&self, _tabs: usize) -> String {
2218        format!(
2219            "CaretWidth {{ inner: {} }}",
2220            self.inner.format_as_rust_code(0)
2221        )
2222    }
2223}
2224
2225#[cfg(feature = "parser")]
2226pub fn parse_caret_width(input: &str) -> Result<CaretWidth, CssPixelValueParseError<'_>> {
2227    use crate::props::basic::pixel::parse_pixel_value;
2228
2229    parse_pixel_value(input).map(|inner| CaretWidth { inner })
2230}
2231
2232// --- From implementations for CssProperty ---
2233
2234impl From<StyleUserSelect> for crate::props::property::CssProperty {
2235    fn from(value: StyleUserSelect) -> Self {
2236        use crate::props::property::CssProperty;
2237        CssProperty::user_select(value)
2238    }
2239}
2240
2241impl From<StyleTextDecoration> for crate::props::property::CssProperty {
2242    fn from(value: StyleTextDecoration) -> Self {
2243        use crate::props::property::CssProperty;
2244        CssProperty::text_decoration(value)
2245    }
2246}
2247
2248#[cfg(all(test, feature = "parser"))]
2249mod tests {
2250    use super::*;
2251    use crate::props::basic::{color::ColorU, length::PercentageValue, pixel::PixelValue};
2252
2253    #[test]
2254    fn test_parse_style_text_color() {
2255        assert_eq!(
2256            parse_style_text_color("red").unwrap().inner,
2257            ColorU::new_rgb(255, 0, 0)
2258        );
2259        assert_eq!(
2260            parse_style_text_color("#aabbcc").unwrap().inner,
2261            ColorU::new_rgb(170, 187, 204)
2262        );
2263        assert!(parse_style_text_color("not-a-color").is_err());
2264    }
2265
2266    #[test]
2267    fn test_parse_style_text_align() {
2268        assert_eq!(
2269            parse_style_text_align("left").unwrap(),
2270            StyleTextAlign::Left
2271        );
2272        assert_eq!(
2273            parse_style_text_align("center").unwrap(),
2274            StyleTextAlign::Center
2275        );
2276        assert_eq!(
2277            parse_style_text_align("right").unwrap(),
2278            StyleTextAlign::Right
2279        );
2280        assert_eq!(
2281            parse_style_text_align("justify").unwrap(),
2282            StyleTextAlign::Justify
2283        );
2284        assert_eq!(
2285            parse_style_text_align("start").unwrap(),
2286            StyleTextAlign::Start
2287        );
2288        assert_eq!(parse_style_text_align("end").unwrap(), StyleTextAlign::End);
2289        assert!(parse_style_text_align("middle").is_err());
2290    }
2291
2292    #[test]
2293    fn test_parse_spacing() {
2294        assert_eq!(
2295            parse_style_letter_spacing("2px").unwrap().inner,
2296            PixelValue::px(2.0)
2297        );
2298        assert_eq!(
2299            parse_style_letter_spacing("-0.1em").unwrap().inner,
2300            PixelValue::em(-0.1)
2301        );
2302        assert_eq!(
2303            parse_style_word_spacing("0.5em").unwrap().inner,
2304            PixelValue::em(0.5)
2305        );
2306    }
2307
2308    #[test]
2309    fn test_parse_line_height() {
2310        assert_eq!(
2311            parse_style_line_height("1.5").unwrap().inner,
2312            PercentageValue::new(150.0)
2313        );
2314        assert_eq!(
2315            parse_style_line_height("120%").unwrap().inner,
2316            PercentageValue::new(120.0)
2317        );
2318        // px values stored as negative PercentageValue (convention: negative = absolute px)
2319        assert_eq!(
2320            parse_style_line_height("20px").unwrap().inner,
2321            PercentageValue::new(-20.0 * 100.0)
2322        );
2323    }
2324
2325    #[test]
2326    fn test_parse_tab_size() {
2327        // Unitless number is treated as `em`
2328        assert_eq!(
2329            parse_style_tab_size("4").unwrap().inner,
2330            PixelValue::em(4.0)
2331        );
2332        assert_eq!(
2333            parse_style_tab_size("20px").unwrap().inner,
2334            PixelValue::px(20.0)
2335        );
2336    }
2337
2338    #[test]
2339    fn test_parse_white_space() {
2340        assert_eq!(
2341            parse_style_white_space("normal").unwrap(),
2342            StyleWhiteSpace::Normal
2343        );
2344        assert_eq!(
2345            parse_style_white_space("pre").unwrap(),
2346            StyleWhiteSpace::Pre
2347        );
2348        assert_eq!(
2349            parse_style_white_space("nowrap").unwrap(),
2350            StyleWhiteSpace::Nowrap
2351        );
2352        assert_eq!(
2353            parse_style_white_space("pre-wrap").unwrap(),
2354            StyleWhiteSpace::PreWrap
2355        );
2356    }
2357}
2358
2359// -- StyleUnicodeBidi --
2360
2361/// Represents the `unicode-bidi` CSS property.
2362///
2363/// Controls how bidirectional text is handled within an element.
2364#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2365#[repr(C)]
2366#[derive(Default)]
2367pub enum StyleUnicodeBidi {
2368    /// No additional level of embedding
2369    #[default]
2370    Normal,
2371    /// Open an additional level of embedding
2372    Embed,
2373    /// Isolate the element from surrounding bidirectional text
2374    Isolate,
2375    /// Override the bidirectional algorithm for inline content
2376    BidiOverride,
2377    /// Combine isolation and override
2378    IsolateOverride,
2379    /// Determine paragraph direction from content without bidi algorithm
2380    Plaintext,
2381}
2382impl_option!(
2383    StyleUnicodeBidi,
2384    OptionStyleUnicodeBidi,
2385    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
2386);
2387impl PrintAsCssValue for StyleUnicodeBidi {
2388    fn print_as_css_value(&self) -> String {
2389        String::from(match self {
2390            StyleUnicodeBidi::Normal => "normal",
2391            StyleUnicodeBidi::Embed => "embed",
2392            StyleUnicodeBidi::Isolate => "isolate",
2393            StyleUnicodeBidi::BidiOverride => "bidi-override",
2394            StyleUnicodeBidi::IsolateOverride => "isolate-override",
2395            StyleUnicodeBidi::Plaintext => "plaintext",
2396        })
2397    }
2398}
2399
2400#[cfg(feature = "parser")]
2401#[derive(Clone, PartialEq)]
2402pub enum StyleUnicodeBidiParseError<'a> {
2403    InvalidValue(InvalidValueErr<'a>),
2404}
2405#[cfg(feature = "parser")]
2406impl_debug_as_display!(StyleUnicodeBidiParseError<'a>);
2407#[cfg(feature = "parser")]
2408impl_display! { StyleUnicodeBidiParseError<'a>, {
2409    InvalidValue(e) => format!("Invalid unicode-bidi value: \"{}\"", e.0),
2410}}
2411#[cfg(feature = "parser")]
2412impl_from!(InvalidValueErr<'a>, StyleUnicodeBidiParseError::InvalidValue);
2413
2414#[cfg(feature = "parser")]
2415#[derive(Debug, Clone, PartialEq)]
2416#[repr(C, u8)]
2417pub enum StyleUnicodeBidiParseErrorOwned {
2418    InvalidValue(InvalidValueErrOwned),
2419}
2420
2421#[cfg(feature = "parser")]
2422impl<'a> StyleUnicodeBidiParseError<'a> {
2423    pub fn to_contained(&self) -> StyleUnicodeBidiParseErrorOwned {
2424        match self {
2425            Self::InvalidValue(e) => StyleUnicodeBidiParseErrorOwned::InvalidValue(e.to_contained()),
2426        }
2427    }
2428}
2429
2430#[cfg(feature = "parser")]
2431impl StyleUnicodeBidiParseErrorOwned {
2432    pub fn to_shared<'a>(&'a self) -> StyleUnicodeBidiParseError<'a> {
2433        match self {
2434            Self::InvalidValue(e) => StyleUnicodeBidiParseError::InvalidValue(e.to_shared()),
2435        }
2436    }
2437}
2438
2439#[cfg(feature = "parser")]
2440pub fn parse_style_unicode_bidi(input: &str) -> Result<StyleUnicodeBidi, StyleUnicodeBidiParseError<'_>> {
2441    match input.trim() {
2442        "normal" => Ok(StyleUnicodeBidi::Normal),
2443        "embed" => Ok(StyleUnicodeBidi::Embed),
2444        "isolate" => Ok(StyleUnicodeBidi::Isolate),
2445        "bidi-override" => Ok(StyleUnicodeBidi::BidiOverride),
2446        "isolate-override" => Ok(StyleUnicodeBidi::IsolateOverride),
2447        "plaintext" => Ok(StyleUnicodeBidi::Plaintext),
2448        other => Err(StyleUnicodeBidiParseError::InvalidValue(InvalidValueErr(other))),
2449    }
2450}
2451
2452// -- StyleTextBoxTrim --
2453
2454/// Represents the `text-box-trim` CSS property.
2455///
2456/// Controls whether the leading is trimmed at the start/end of a block container.
2457#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2458#[repr(C)]
2459#[derive(Default)]
2460pub enum StyleTextBoxTrim {
2461    /// No trimming
2462    #[default]
2463    None,
2464    /// Trim leading over the first formatted line
2465    TrimStart,
2466    /// Trim leading under the last formatted line
2467    TrimEnd,
2468    /// Trim both start and end
2469    TrimBoth,
2470}
2471impl_option!(
2472    StyleTextBoxTrim,
2473    OptionStyleTextBoxTrim,
2474    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
2475);
2476impl PrintAsCssValue for StyleTextBoxTrim {
2477    fn print_as_css_value(&self) -> String {
2478        String::from(match self {
2479            StyleTextBoxTrim::None => "none",
2480            StyleTextBoxTrim::TrimStart => "trim-start",
2481            StyleTextBoxTrim::TrimEnd => "trim-end",
2482            StyleTextBoxTrim::TrimBoth => "trim-both",
2483        })
2484    }
2485}
2486
2487#[cfg(feature = "parser")]
2488#[derive(Clone, PartialEq)]
2489pub enum StyleTextBoxTrimParseError<'a> {
2490    InvalidValue(InvalidValueErr<'a>),
2491}
2492#[cfg(feature = "parser")]
2493impl_debug_as_display!(StyleTextBoxTrimParseError<'a>);
2494#[cfg(feature = "parser")]
2495impl_display! { StyleTextBoxTrimParseError<'a>, {
2496    InvalidValue(e) => format!("Invalid text-box-trim value: \"{}\"", e.0),
2497}}
2498#[cfg(feature = "parser")]
2499impl_from!(InvalidValueErr<'a>, StyleTextBoxTrimParseError::InvalidValue);
2500
2501#[cfg(feature = "parser")]
2502#[derive(Debug, Clone, PartialEq)]
2503#[repr(C, u8)]
2504pub enum StyleTextBoxTrimParseErrorOwned {
2505    InvalidValue(InvalidValueErrOwned),
2506}
2507
2508#[cfg(feature = "parser")]
2509impl<'a> StyleTextBoxTrimParseError<'a> {
2510    pub fn to_contained(&self) -> StyleTextBoxTrimParseErrorOwned {
2511        match self {
2512            Self::InvalidValue(e) => StyleTextBoxTrimParseErrorOwned::InvalidValue(e.to_contained()),
2513        }
2514    }
2515}
2516
2517#[cfg(feature = "parser")]
2518impl StyleTextBoxTrimParseErrorOwned {
2519    pub fn to_shared<'a>(&'a self) -> StyleTextBoxTrimParseError<'a> {
2520        match self {
2521            Self::InvalidValue(e) => StyleTextBoxTrimParseError::InvalidValue(e.to_shared()),
2522        }
2523    }
2524}
2525
2526#[cfg(feature = "parser")]
2527pub fn parse_style_text_box_trim(input: &str) -> Result<StyleTextBoxTrim, StyleTextBoxTrimParseError<'_>> {
2528    match input.trim() {
2529        "none" => Ok(StyleTextBoxTrim::None),
2530        "trim-start" => Ok(StyleTextBoxTrim::TrimStart),
2531        "trim-end" => Ok(StyleTextBoxTrim::TrimEnd),
2532        "trim-both" => Ok(StyleTextBoxTrim::TrimBoth),
2533        other => Err(StyleTextBoxTrimParseError::InvalidValue(InvalidValueErr(other))),
2534    }
2535}
2536
2537// -- StyleTextBoxEdge --
2538
2539/// Represents the `text-box-edge` CSS property.
2540///
2541/// Specifies the metrics used for determining the over/under edges of text
2542/// for the purposes of `text-box-trim`.
2543// +spec:writing-modes:daad86 - first value = over edge, second = under edge; single value applies to both (else "text" assumed for missing)
2544#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2545#[repr(C)]
2546#[derive(Default)]
2547pub enum StyleTextBoxEdge {
2548    // +spec:line-height:cc03df - Auto uses line-fit-edge value, interpreting leading (initial) as text
2549    /// Use the line-fit-edge value (initial: text)
2550    #[default]
2551    Auto,
2552    /// Use the text-over / text-under baselines
2553    TextEdge,
2554    /// Use the cap-height baseline
2555    CapHeight,
2556    /// Use the x-height baseline
2557    ExHeight,
2558}
2559impl_option!(
2560    StyleTextBoxEdge,
2561    OptionStyleTextBoxEdge,
2562    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
2563);
2564impl PrintAsCssValue for StyleTextBoxEdge {
2565    fn print_as_css_value(&self) -> String {
2566        String::from(match self {
2567            StyleTextBoxEdge::Auto => "auto",
2568            StyleTextBoxEdge::TextEdge => "text",
2569            StyleTextBoxEdge::CapHeight => "cap",
2570            StyleTextBoxEdge::ExHeight => "ex",
2571        })
2572    }
2573}
2574
2575#[cfg(feature = "parser")]
2576#[derive(Clone, PartialEq)]
2577pub enum StyleTextBoxEdgeParseError<'a> {
2578    InvalidValue(InvalidValueErr<'a>),
2579}
2580#[cfg(feature = "parser")]
2581impl_debug_as_display!(StyleTextBoxEdgeParseError<'a>);
2582#[cfg(feature = "parser")]
2583impl_display! { StyleTextBoxEdgeParseError<'a>, {
2584    InvalidValue(e) => format!("Invalid text-box-edge value: \"{}\"", e.0),
2585}}
2586#[cfg(feature = "parser")]
2587impl_from!(InvalidValueErr<'a>, StyleTextBoxEdgeParseError::InvalidValue);
2588
2589#[cfg(feature = "parser")]
2590#[derive(Debug, Clone, PartialEq)]
2591#[repr(C, u8)]
2592pub enum StyleTextBoxEdgeParseErrorOwned {
2593    InvalidValue(InvalidValueErrOwned),
2594}
2595
2596#[cfg(feature = "parser")]
2597impl<'a> StyleTextBoxEdgeParseError<'a> {
2598    pub fn to_contained(&self) -> StyleTextBoxEdgeParseErrorOwned {
2599        match self {
2600            Self::InvalidValue(e) => StyleTextBoxEdgeParseErrorOwned::InvalidValue(e.to_contained()),
2601        }
2602    }
2603}
2604
2605#[cfg(feature = "parser")]
2606impl StyleTextBoxEdgeParseErrorOwned {
2607    pub fn to_shared<'a>(&'a self) -> StyleTextBoxEdgeParseError<'a> {
2608        match self {
2609            Self::InvalidValue(e) => StyleTextBoxEdgeParseError::InvalidValue(e.to_shared()),
2610        }
2611    }
2612}
2613
2614#[cfg(feature = "parser")]
2615pub fn parse_style_text_box_edge(input: &str) -> Result<StyleTextBoxEdge, StyleTextBoxEdgeParseError<'_>> {
2616    match input.trim() {
2617        "auto" => Ok(StyleTextBoxEdge::Auto),
2618        "text" => Ok(StyleTextBoxEdge::TextEdge),
2619        "cap" => Ok(StyleTextBoxEdge::CapHeight),
2620        "ex" => Ok(StyleTextBoxEdge::ExHeight),
2621        other => Err(StyleTextBoxEdgeParseError::InvalidValue(InvalidValueErr(other))),
2622    }
2623}
2624
2625// -- StyleDominantBaseline --
2626
2627/// Represents the `dominant-baseline` CSS property.
2628///
2629/// Specifies the dominant baseline used to align inline-level contents.
2630#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2631#[repr(C)]
2632#[derive(Default)]
2633pub enum StyleDominantBaseline {
2634    /// Use the dominant baseline of the parent
2635    #[default]
2636    Auto,
2637    /// Use the text-under baseline
2638    TextBottom,
2639    /// Use the alphabetic baseline
2640    Alphabetic,
2641    /// Use the ideographic baseline
2642    Ideographic,
2643    /// Use the middle baseline
2644    Middle,
2645    /// Use the central baseline
2646    Central,
2647    /// Use the mathematical baseline
2648    Mathematical,
2649    /// Use the hanging baseline
2650    Hanging,
2651    /// Use the text-over baseline
2652    TextTop,
2653}
2654impl_option!(
2655    StyleDominantBaseline,
2656    OptionStyleDominantBaseline,
2657    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
2658);
2659impl PrintAsCssValue for StyleDominantBaseline {
2660    fn print_as_css_value(&self) -> String {
2661        String::from(match self {
2662            StyleDominantBaseline::Auto => "auto",
2663            StyleDominantBaseline::TextBottom => "text-bottom",
2664            StyleDominantBaseline::Alphabetic => "alphabetic",
2665            StyleDominantBaseline::Ideographic => "ideographic",
2666            StyleDominantBaseline::Middle => "middle",
2667            StyleDominantBaseline::Central => "central",
2668            StyleDominantBaseline::Mathematical => "mathematical",
2669            StyleDominantBaseline::Hanging => "hanging",
2670            StyleDominantBaseline::TextTop => "text-top",
2671        })
2672    }
2673}
2674
2675#[cfg(feature = "parser")]
2676#[derive(Clone, PartialEq)]
2677pub enum StyleDominantBaselineParseError<'a> {
2678    InvalidValue(InvalidValueErr<'a>),
2679}
2680#[cfg(feature = "parser")]
2681impl_debug_as_display!(StyleDominantBaselineParseError<'a>);
2682#[cfg(feature = "parser")]
2683impl_display! { StyleDominantBaselineParseError<'a>, {
2684    InvalidValue(e) => format!("Invalid dominant-baseline value: \"{}\"", e.0),
2685}}
2686#[cfg(feature = "parser")]
2687impl_from!(InvalidValueErr<'a>, StyleDominantBaselineParseError::InvalidValue);
2688
2689#[cfg(feature = "parser")]
2690#[derive(Debug, Clone, PartialEq)]
2691#[repr(C, u8)]
2692pub enum StyleDominantBaselineParseErrorOwned {
2693    InvalidValue(InvalidValueErrOwned),
2694}
2695
2696#[cfg(feature = "parser")]
2697impl<'a> StyleDominantBaselineParseError<'a> {
2698    pub fn to_contained(&self) -> StyleDominantBaselineParseErrorOwned {
2699        match self {
2700            Self::InvalidValue(e) => StyleDominantBaselineParseErrorOwned::InvalidValue(e.to_contained()),
2701        }
2702    }
2703}
2704
2705#[cfg(feature = "parser")]
2706impl StyleDominantBaselineParseErrorOwned {
2707    pub fn to_shared<'a>(&'a self) -> StyleDominantBaselineParseError<'a> {
2708        match self {
2709            Self::InvalidValue(e) => StyleDominantBaselineParseError::InvalidValue(e.to_shared()),
2710        }
2711    }
2712}
2713
2714#[cfg(feature = "parser")]
2715pub fn parse_style_dominant_baseline(input: &str) -> Result<StyleDominantBaseline, StyleDominantBaselineParseError<'_>> {
2716    match input.trim() {
2717        "auto" => Ok(StyleDominantBaseline::Auto),
2718        "text-bottom" => Ok(StyleDominantBaseline::TextBottom),
2719        "alphabetic" => Ok(StyleDominantBaseline::Alphabetic),
2720        "ideographic" => Ok(StyleDominantBaseline::Ideographic),
2721        "middle" => Ok(StyleDominantBaseline::Middle),
2722        "central" => Ok(StyleDominantBaseline::Central),
2723        "mathematical" => Ok(StyleDominantBaseline::Mathematical),
2724        "hanging" => Ok(StyleDominantBaseline::Hanging),
2725        "text-top" => Ok(StyleDominantBaseline::TextTop),
2726        other => Err(StyleDominantBaselineParseError::InvalidValue(InvalidValueErr(other))),
2727    }
2728}
2729
2730// -- StyleAlignmentBaseline --
2731
2732// +spec:display-property:c90924 - alignment-baseline property: values, initial value, and applies-to per CSS Inline 3 §4.2.2
2733// +spec:font-metrics:fa4489 - alignment-baseline property: specifies box's alignment baseline used before post-alignment shift
2734// +spec:inline-block:939f05 - alignment-baseline property definition with all spec values (baseline, text-bottom, alphabetic, ideographic, middle, central, mathematical, text-top)
2735/// Represents the `alignment-baseline` CSS property.
2736///
2737/// Specifies which baseline of the element is aligned with the dominant baseline.
2738// +spec:writing-modes:cc8e70 - alignment-baseline values for inline baseline alignment
2739#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2740#[repr(C)]
2741#[derive(Default)]
2742pub enum StyleAlignmentBaseline {
2743    /// Use the dominant baseline of the parent
2744    #[default]
2745    Baseline,
2746    /// Align to the text-under baseline
2747    TextBottom,
2748    /// Align to the alphabetic baseline
2749    Alphabetic,
2750    /// Align to the ideographic baseline
2751    Ideographic,
2752    /// Align to the middle baseline
2753    Middle,
2754    /// Align to the central baseline
2755    Central,
2756    /// Align to the mathematical baseline
2757    Mathematical,
2758    /// Align to the text-over baseline
2759    TextTop,
2760}
2761impl_option!(
2762    StyleAlignmentBaseline,
2763    OptionStyleAlignmentBaseline,
2764    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
2765);
2766impl PrintAsCssValue for StyleAlignmentBaseline {
2767    fn print_as_css_value(&self) -> String {
2768        String::from(match self {
2769            StyleAlignmentBaseline::Baseline => "baseline",
2770            StyleAlignmentBaseline::TextBottom => "text-bottom",
2771            StyleAlignmentBaseline::Alphabetic => "alphabetic",
2772            StyleAlignmentBaseline::Ideographic => "ideographic",
2773            StyleAlignmentBaseline::Middle => "middle",
2774            StyleAlignmentBaseline::Central => "central",
2775            StyleAlignmentBaseline::Mathematical => "mathematical",
2776            StyleAlignmentBaseline::TextTop => "text-top",
2777        })
2778    }
2779}
2780
2781#[cfg(feature = "parser")]
2782#[derive(Clone, PartialEq)]
2783pub enum StyleAlignmentBaselineParseError<'a> {
2784    InvalidValue(InvalidValueErr<'a>),
2785}
2786#[cfg(feature = "parser")]
2787impl_debug_as_display!(StyleAlignmentBaselineParseError<'a>);
2788#[cfg(feature = "parser")]
2789impl_display! { StyleAlignmentBaselineParseError<'a>, {
2790    InvalidValue(e) => format!("Invalid alignment-baseline value: \"{}\"", e.0),
2791}}
2792#[cfg(feature = "parser")]
2793impl_from!(InvalidValueErr<'a>, StyleAlignmentBaselineParseError::InvalidValue);
2794
2795#[cfg(feature = "parser")]
2796#[derive(Debug, Clone, PartialEq)]
2797#[repr(C, u8)]
2798pub enum StyleAlignmentBaselineParseErrorOwned {
2799    InvalidValue(InvalidValueErrOwned),
2800}
2801
2802#[cfg(feature = "parser")]
2803impl<'a> StyleAlignmentBaselineParseError<'a> {
2804    pub fn to_contained(&self) -> StyleAlignmentBaselineParseErrorOwned {
2805        match self {
2806            Self::InvalidValue(e) => StyleAlignmentBaselineParseErrorOwned::InvalidValue(e.to_contained()),
2807        }
2808    }
2809}
2810
2811#[cfg(feature = "parser")]
2812impl StyleAlignmentBaselineParseErrorOwned {
2813    pub fn to_shared<'a>(&'a self) -> StyleAlignmentBaselineParseError<'a> {
2814        match self {
2815            Self::InvalidValue(e) => StyleAlignmentBaselineParseError::InvalidValue(e.to_shared()),
2816        }
2817    }
2818}
2819
2820#[cfg(feature = "parser")]
2821pub fn parse_style_alignment_baseline(input: &str) -> Result<StyleAlignmentBaseline, StyleAlignmentBaselineParseError<'_>> {
2822    match input.trim() {
2823        "baseline" => Ok(StyleAlignmentBaseline::Baseline),
2824        "text-bottom" => Ok(StyleAlignmentBaseline::TextBottom),
2825        "alphabetic" => Ok(StyleAlignmentBaseline::Alphabetic),
2826        "ideographic" => Ok(StyleAlignmentBaseline::Ideographic),
2827        "middle" => Ok(StyleAlignmentBaseline::Middle),
2828        "central" => Ok(StyleAlignmentBaseline::Central),
2829        "mathematical" => Ok(StyleAlignmentBaseline::Mathematical),
2830        "text-top" => Ok(StyleAlignmentBaseline::TextTop),
2831        other => Err(StyleAlignmentBaselineParseError::InvalidValue(InvalidValueErr(other))),
2832    }
2833}
2834
2835// -- StyleInitialLetterAlign --
2836
2837/// Represents the `initial-letter-align` CSS property.
2838///
2839/// Specifies the alignment points used to align an initial letter.
2840#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2841#[repr(C)]
2842#[derive(Default)]
2843pub enum StyleInitialLetterAlign {
2844    /// Automatically determine alignment based on script
2845    #[default]
2846    Auto,
2847    /// Align to the alphabetic baseline
2848    Alphabetic,
2849    /// Align to the hanging baseline
2850    Hanging,
2851    /// Align to the ideographic baseline
2852    Ideographic,
2853}
2854impl_option!(
2855    StyleInitialLetterAlign,
2856    OptionStyleInitialLetterAlign,
2857    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
2858);
2859impl PrintAsCssValue for StyleInitialLetterAlign {
2860    fn print_as_css_value(&self) -> String {
2861        String::from(match self {
2862            StyleInitialLetterAlign::Auto => "auto",
2863            StyleInitialLetterAlign::Alphabetic => "alphabetic",
2864            StyleInitialLetterAlign::Hanging => "hanging",
2865            StyleInitialLetterAlign::Ideographic => "ideographic",
2866        })
2867    }
2868}
2869
2870#[cfg(feature = "parser")]
2871#[derive(Clone, PartialEq)]
2872pub enum StyleInitialLetterAlignParseError<'a> {
2873    InvalidValue(InvalidValueErr<'a>),
2874}
2875#[cfg(feature = "parser")]
2876impl_debug_as_display!(StyleInitialLetterAlignParseError<'a>);
2877#[cfg(feature = "parser")]
2878impl_display! { StyleInitialLetterAlignParseError<'a>, {
2879    InvalidValue(e) => format!("Invalid initial-letter-align value: \"{}\"", e.0),
2880}}
2881#[cfg(feature = "parser")]
2882impl_from!(InvalidValueErr<'a>, StyleInitialLetterAlignParseError::InvalidValue);
2883
2884#[cfg(feature = "parser")]
2885#[derive(Debug, Clone, PartialEq)]
2886#[repr(C, u8)]
2887pub enum StyleInitialLetterAlignParseErrorOwned {
2888    InvalidValue(InvalidValueErrOwned),
2889}
2890
2891#[cfg(feature = "parser")]
2892impl<'a> StyleInitialLetterAlignParseError<'a> {
2893    pub fn to_contained(&self) -> StyleInitialLetterAlignParseErrorOwned {
2894        match self {
2895            Self::InvalidValue(e) => StyleInitialLetterAlignParseErrorOwned::InvalidValue(e.to_contained()),
2896        }
2897    }
2898}
2899
2900#[cfg(feature = "parser")]
2901impl StyleInitialLetterAlignParseErrorOwned {
2902    pub fn to_shared<'a>(&'a self) -> StyleInitialLetterAlignParseError<'a> {
2903        match self {
2904            Self::InvalidValue(e) => StyleInitialLetterAlignParseError::InvalidValue(e.to_shared()),
2905        }
2906    }
2907}
2908
2909#[cfg(feature = "parser")]
2910pub fn parse_style_initial_letter_align(input: &str) -> Result<StyleInitialLetterAlign, StyleInitialLetterAlignParseError<'_>> {
2911    match input.trim() {
2912        "auto" => Ok(StyleInitialLetterAlign::Auto),
2913        "alphabetic" => Ok(StyleInitialLetterAlign::Alphabetic),
2914        "hanging" => Ok(StyleInitialLetterAlign::Hanging),
2915        "ideographic" => Ok(StyleInitialLetterAlign::Ideographic),
2916        other => Err(StyleInitialLetterAlignParseError::InvalidValue(InvalidValueErr(other))),
2917    }
2918}
2919
2920// -- StyleInitialLetterWrap --
2921
2922/// Represents the `initial-letter-wrap` CSS property.
2923///
2924/// Specifies how text adjacent to an initial letter wraps.
2925#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2926#[repr(C)]
2927#[derive(Default)]
2928pub enum StyleInitialLetterWrap {
2929    /// No special wrapping around the initial letter
2930    #[default]
2931    None,
2932    /// Wrap only the first line adjacent to the initial letter
2933    First,
2934    /// Wrap all lines adjacent to the initial letter
2935    All,
2936    /// Wrap using a grid-based layout
2937    Grid,
2938}
2939impl_option!(
2940    StyleInitialLetterWrap,
2941    OptionStyleInitialLetterWrap,
2942    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
2943);
2944impl PrintAsCssValue for StyleInitialLetterWrap {
2945    fn print_as_css_value(&self) -> String {
2946        String::from(match self {
2947            StyleInitialLetterWrap::None => "none",
2948            StyleInitialLetterWrap::First => "first",
2949            StyleInitialLetterWrap::All => "all",
2950            StyleInitialLetterWrap::Grid => "grid",
2951        })
2952    }
2953}
2954
2955#[cfg(feature = "parser")]
2956#[derive(Clone, PartialEq)]
2957pub enum StyleInitialLetterWrapParseError<'a> {
2958    InvalidValue(InvalidValueErr<'a>),
2959}
2960#[cfg(feature = "parser")]
2961impl_debug_as_display!(StyleInitialLetterWrapParseError<'a>);
2962#[cfg(feature = "parser")]
2963impl_display! { StyleInitialLetterWrapParseError<'a>, {
2964    InvalidValue(e) => format!("Invalid initial-letter-wrap value: \"{}\"", e.0),
2965}}
2966#[cfg(feature = "parser")]
2967impl_from!(InvalidValueErr<'a>, StyleInitialLetterWrapParseError::InvalidValue);
2968
2969#[cfg(feature = "parser")]
2970#[derive(Debug, Clone, PartialEq)]
2971#[repr(C, u8)]
2972pub enum StyleInitialLetterWrapParseErrorOwned {
2973    InvalidValue(InvalidValueErrOwned),
2974}
2975
2976#[cfg(feature = "parser")]
2977impl<'a> StyleInitialLetterWrapParseError<'a> {
2978    pub fn to_contained(&self) -> StyleInitialLetterWrapParseErrorOwned {
2979        match self {
2980            Self::InvalidValue(e) => StyleInitialLetterWrapParseErrorOwned::InvalidValue(e.to_contained()),
2981        }
2982    }
2983}
2984
2985#[cfg(feature = "parser")]
2986impl StyleInitialLetterWrapParseErrorOwned {
2987    pub fn to_shared<'a>(&'a self) -> StyleInitialLetterWrapParseError<'a> {
2988        match self {
2989            Self::InvalidValue(e) => StyleInitialLetterWrapParseError::InvalidValue(e.to_shared()),
2990        }
2991    }
2992}
2993
2994#[cfg(feature = "parser")]
2995pub fn parse_style_initial_letter_wrap(input: &str) -> Result<StyleInitialLetterWrap, StyleInitialLetterWrapParseError<'_>> {
2996    match input.trim() {
2997        "none" => Ok(StyleInitialLetterWrap::None),
2998        "first" => Ok(StyleInitialLetterWrap::First),
2999        "all" => Ok(StyleInitialLetterWrap::All),
3000        "grid" => Ok(StyleInitialLetterWrap::Grid),
3001        other => Err(StyleInitialLetterWrapParseError::InvalidValue(InvalidValueErr(other))),
3002    }
3003}