azul_css_parser/
css_parser.rs

1//! Contains utilities to convert strings (CSS strings) to servo types
2
3use std::num::{ParseIntError, ParseFloatError};
4use azul_css::{
5    CssPropertyType, CssProperty, CombinedCssPropertyType, CssPropertyValue,
6    Overflow, Shape, PixelValue, PixelValueNoPercent, PercentageValue, FloatValue, ColorU,
7    GradientStopPre, RadialGradient, DirectionCorner, Direction, CssImageId,
8    LinearGradient, BoxShadowPreDisplayItem, StyleBorderSide, BorderStyle,
9    SizeMetric, BoxShadowClipMode, ExtendMode, FontId, GradientType,
10    BackgroundPositionHorizontal, BackgroundPositionVertical,
11
12    StyleTextColor, StyleFontSize, StyleFontFamily, StyleTextAlignmentHorz,
13    StyleLetterSpacing, StyleLineHeight, StyleWordSpacing, StyleTabWidth,
14    StyleCursor, StyleBackgroundContent, StyleBackgroundPosition, StyleBackgroundSize,
15    StyleBackgroundRepeat, StyleBorderTopLeftRadius, StyleBorderTopRightRadius,
16    StyleBorderBottomLeftRadius, StyleBorderBottomRightRadius, StyleBorderTopColor,
17    StyleBorderRightColor, StyleBorderLeftColor, StyleBorderBottomColor,
18    StyleBorderTopStyle, StyleBorderRightStyle, StyleBorderLeftStyle,
19    StyleBorderBottomStyle, StyleBorderTopWidth, StyleBorderRightWidth,
20    StyleBorderLeftWidth, StyleBorderBottomWidth,
21
22    LayoutDisplay, LayoutFloat, LayoutWidth, LayoutHeight, LayoutBoxSizing,
23    LayoutMinWidth, LayoutMinHeight, LayoutMaxWidth, LayoutMaxHeight,
24    LayoutPosition, LayoutTop, LayoutRight, LayoutLeft, LayoutBottom, LayoutWrap,
25    LayoutDirection, LayoutFlexGrow, LayoutFlexShrink, LayoutJustifyContent,
26    LayoutAlignItems, LayoutAlignContent, LayoutPaddingRight, LayoutPaddingBottom,
27    LayoutMarginTop, LayoutMarginLeft, LayoutMarginRight, LayoutMarginBottom,
28    LayoutPaddingTop, LayoutPaddingLeft,
29};
30
31/// A parser that can accept a list of items and mappings
32macro_rules! multi_type_parser {
33    ($fn:ident, $return_str:expr, $return:ident, $import_str:expr, $([$identifier_string:expr, $enum_type:ident, $parse_str:expr]),+) => {
34        #[doc = "Parses a `"]
35        #[doc = $return_str]
36        #[doc = "` attribute from a `&str`"]
37        #[doc = ""]
38        #[doc = "# Example"]
39        #[doc = ""]
40        #[doc = "```rust"]
41        #[doc = $import_str]
42        $(
43            #[doc = $parse_str]
44        )+
45        #[doc = "```"]
46        pub fn $fn<'a>(input: &'a str)
47        -> Result<$return, InvalidValueErr<'a>>
48        {
49            let input = input.trim();
50            match input {
51                $(
52                    $identifier_string => Ok($return::$enum_type),
53                )+
54                _ => Err(InvalidValueErr(input)),
55            }
56        }
57    };
58    ($fn:ident, $return:ident, $([$identifier_string:expr, $enum_type:ident]),+) => {
59        multi_type_parser!($fn, stringify!($return), $return,
60            concat!(
61                "# extern crate azul_css;", "\r\n",
62                "# extern crate azul_css_parser;", "\r\n",
63                "# use azul_css_parser::", stringify!($fn), ";", "\r\n",
64                "# use azul_css::", stringify!($return), ";"
65            ),
66            $([
67                $identifier_string, $enum_type,
68                concat!("assert_eq!(", stringify!($fn), "(\"", $identifier_string, "\"), Ok(", stringify!($return), "::", stringify!($enum_type), "));")
69            ]),+
70        );
71    };
72}
73
74macro_rules! typed_pixel_value_parser {
75    ($fn:ident, $fn_str:expr, $return:ident, $return_str:expr, $import_str:expr, $test_str:expr) => {
76        #[doc = "Parses a `"]
77        #[doc = $return_str]
78        #[doc = "` attribute from a `&str`"]
79        #[doc = ""]
80        #[doc = "# Example"]
81        #[doc = ""]
82        #[doc = "```rust"]
83        #[doc = $import_str]
84        #[doc = $test_str]
85        #[doc = "```"]
86        pub fn $fn<'a>(input: &'a str) -> Result<$return, PixelParseError<'a>> {
87            parse_pixel_value(input).and_then(|e| Ok($return(e)))
88        }
89    };
90    ($fn:ident, $return:ident) => {
91        typed_pixel_value_parser!($fn, stringify!($fn), $return, stringify!($return),
92            concat!(
93                "# extern crate azul_css;", "\r\n",
94                "# extern crate azul_css_parser;", "\r\n",
95                "# use azul_css_parser::", stringify!($fn), ";", "\r\n",
96                "# use azul_css::{PixelValue, ", stringify!($return), "};"
97            ),
98            concat!("assert_eq!(", stringify!($fn), "(\"5px\"), Ok(", stringify!($return), "(PixelValue::px(5.0))));")
99        );
100    };
101}
102
103/// Main parsing function, takes a stringified key / value pair and either
104/// returns the parsed value or an error
105///
106/// ```rust
107/// # extern crate azul_css_parser;
108/// # extern crate azul_css;
109///
110/// # use azul_css_parser;
111/// # use azul_css::{LayoutWidth, PixelValue, CssPropertyType, CssPropertyValue, CssProperty};
112/// assert_eq!(
113///     azul_css_parser::parse_css_property(CssPropertyType::Width, "500px"),
114///     Ok(CssProperty::Width(CssPropertyValue::Exact(LayoutWidth(PixelValue::px(500.0)))))
115/// )
116/// ```
117pub fn parse_css_property<'a>(key: CssPropertyType, value: &'a str) -> Result<CssProperty, CssParsingError<'a>> {
118    use self::CssPropertyType::*;
119    let value = value.trim();
120    Ok(match value {
121        "auto" => CssProperty::auto(key),
122        "none" => CssProperty::none(key),
123        "initial" => CssProperty::initial(key).into(),
124        "inherit" => CssProperty::inherit(key).into(),
125        value => match key {
126            TextColor                   => parse_style_text_color(value)?.into(),
127            FontSize                    => parse_style_font_size(value)?.into(),
128            FontFamily                  => parse_style_font_family(value)?.into(),
129            TextAlign                   => parse_layout_text_align(value)?.into(),
130            LetterSpacing               => parse_style_letter_spacing(value)?.into(),
131            LineHeight                  => parse_style_line_height(value)?.into(),
132            WordSpacing                 => parse_style_word_spacing(value)?.into(),
133            TabWidth                    => parse_style_tab_width(value)?.into(),
134            Cursor                      => parse_style_cursor(value)?.into(),
135
136            Display                     => parse_layout_display(value)?.into(),
137            Float                       => parse_layout_float(value)?.into(),
138            BoxSizing                   => parse_layout_box_sizing(value)?.into(),
139            Width                       => parse_layout_width(value)?.into(),
140            Height                      => parse_layout_height(value)?.into(),
141            MinWidth                    => parse_layout_min_width(value)?.into(),
142            MinHeight                   => parse_layout_min_height(value)?.into(),
143            MaxWidth                    => parse_layout_max_width(value)?.into(),
144            MaxHeight                   => parse_layout_max_height(value)?.into(),
145            Position                    => parse_layout_position(value)?.into(),
146            Top                         => parse_layout_top(value)?.into(),
147            Right                       => parse_layout_right(value)?.into(),
148            Left                        => parse_layout_left(value)?.into(),
149            Bottom                      => parse_layout_bottom(value)?.into(),
150            FlexWrap                    => parse_layout_wrap(value)?.into(),
151            FlexDirection               => parse_layout_direction(value)?.into(),
152            FlexGrow                    => parse_layout_flex_grow(value)?.into(),
153            FlexShrink                  => parse_layout_flex_shrink(value)?.into(),
154            JustifyContent              => parse_layout_justify_content(value)?.into(),
155            AlignItems                  => parse_layout_align_items(value)?.into(),
156            AlignContent                => parse_layout_align_content(value)?.into(),
157
158            Background                  => parse_style_background_content(value)?.into(),
159            BackgroundImage             => StyleBackgroundContent::Image(parse_image(value)?).into(),
160            BackgroundColor             => StyleBackgroundContent::Color(parse_css_color(value)?).into(),
161            BackgroundPosition          => parse_style_background_position(value)?.into(),
162            BackgroundSize              => parse_style_background_size(value)?.into(),
163            BackgroundRepeat            => parse_style_background_repeat(value)?.into(),
164
165            OverflowX                   => CssProperty::OverflowX(CssPropertyValue::Exact(parse_layout_overflow(value)?)).into(),
166            OverflowY                   => CssProperty::OverflowY(CssPropertyValue::Exact(parse_layout_overflow(value)?)).into(),
167
168            PaddingTop                  => parse_layout_padding_top(value)?.into(),
169            PaddingLeft                 => parse_layout_padding_left(value)?.into(),
170            PaddingRight                => parse_layout_padding_right(value)?.into(),
171            PaddingBottom               => parse_layout_padding_bottom(value)?.into(),
172
173            MarginTop                   => parse_layout_margin_top(value)?.into(),
174            MarginLeft                  => parse_layout_margin_left(value)?.into(),
175            MarginRight                 => parse_layout_margin_right(value)?.into(),
176            MarginBottom                => parse_layout_margin_bottom(value)?.into(),
177
178            BorderTopLeftRadius         => parse_style_border_top_left_radius(value)?.into(),
179            BorderTopRightRadius        => parse_style_border_top_right_radius(value)?.into(),
180            BorderBottomLeftRadius      => parse_style_border_bottom_left_radius(value)?.into(),
181            BorderBottomRightRadius     => parse_style_border_bottom_right_radius(value)?.into(),
182
183            BorderTopColor              => StyleBorderTopColor(parse_css_color(value)?).into(),
184            BorderRightColor            => StyleBorderRightColor(parse_css_color(value)?).into(),
185            BorderLeftColor             => StyleBorderLeftColor(parse_css_color(value)?).into(),
186            BorderBottomColor           => StyleBorderBottomColor(parse_css_color(value)?).into(),
187
188            BorderTopStyle              => StyleBorderTopStyle(parse_style_border_style(value)?).into(),
189            BorderRightStyle            => StyleBorderRightStyle(parse_style_border_style(value)?).into(),
190            BorderLeftStyle             => StyleBorderLeftStyle(parse_style_border_style(value)?).into(),
191            BorderBottomStyle           => StyleBorderBottomStyle(parse_style_border_style(value)?).into(),
192
193            BorderTopWidth              => parse_style_border_top_width(value)?.into(),
194            BorderRightWidth            => parse_style_border_right_width(value)?.into(),
195            BorderLeftWidth             => parse_style_border_left_width(value)?.into(),
196            BorderBottomWidth           => parse_style_border_bottom_width(value)?.into(),
197
198            BoxShadowLeft               => CssProperty::BoxShadowLeft(CssPropertyValue::Exact(parse_style_box_shadow(value)?)).into(),
199            BoxShadowRight              => CssProperty::BoxShadowRight(CssPropertyValue::Exact(parse_style_box_shadow(value)?)).into(),
200            BoxShadowTop                => CssProperty::BoxShadowTop(CssPropertyValue::Exact(parse_style_box_shadow(value)?)).into(),
201            BoxShadowBottom             => CssProperty::BoxShadowBottom(CssPropertyValue::Exact(parse_style_box_shadow(value)?)).into(),
202        }
203    })
204}
205
206/// Parses a combined CSS property or a CSS property shorthand, for example "margin"
207/// (as a shorthand for setting all four properties of "margin-top", "margin-bottom",
208/// "margin-left" and "margin-right")
209///
210/// ```rust
211/// # extern crate azul_css_parser;
212/// # extern crate azul_css;
213/// # use azul_css_parser;
214/// # use azul_css::*;
215/// assert_eq!(
216///     azul_css_parser::parse_combined_css_property(CombinedCssPropertyType::BorderRadius, "10px"),
217///     Ok(vec![
218///         CssProperty::BorderTopLeftRadius(CssPropertyValue::Exact(StyleBorderTopLeftRadius::px(10.0))),
219///         CssProperty::BorderTopRightRadius(CssPropertyValue::Exact(StyleBorderTopRightRadius::px(10.0))),
220///         CssProperty::BorderBottomLeftRadius(CssPropertyValue::Exact(StyleBorderBottomLeftRadius::px(10.0))),
221///         CssProperty::BorderBottomRightRadius(CssPropertyValue::Exact(StyleBorderBottomRightRadius::px(10.0))),
222///     ])
223/// )
224/// ```
225pub fn parse_combined_css_property<'a>(key: CombinedCssPropertyType, value: &'a str)
226-> Result<Vec<CssProperty>, CssParsingError<'a>>
227{
228    use self::CombinedCssPropertyType::*;
229
230    macro_rules! convert_value {($thing:expr, $prop_type:ident, $wrapper:ident) => (
231        match $thing {
232            PixelValueWithAuto::None => CssProperty::none(CssPropertyType::$prop_type),
233            PixelValueWithAuto::Initial => CssProperty::initial(CssPropertyType::$prop_type),
234            PixelValueWithAuto::Inherit => CssProperty::inherit(CssPropertyType::$prop_type),
235            PixelValueWithAuto::Auto => CssProperty::auto(CssPropertyType::$prop_type),
236            PixelValueWithAuto::Exact(x) => CssProperty::$prop_type($wrapper(x).into()),
237        }
238    )}
239
240    let keys = match key {
241        BorderRadius => {
242            vec![
243                CssPropertyType::BorderTopLeftRadius,
244                CssPropertyType::BorderTopRightRadius,
245                CssPropertyType::BorderBottomLeftRadius,
246                CssPropertyType::BorderBottomRightRadius,
247            ]
248        },
249        Overflow => {
250            vec![
251                CssPropertyType::OverflowX,
252                CssPropertyType::OverflowY,
253            ]
254        },
255        Padding => {
256            vec![
257                CssPropertyType::PaddingTop,
258                CssPropertyType::PaddingBottom,
259                CssPropertyType::PaddingLeft,
260                CssPropertyType::PaddingRight,
261            ]
262        },
263        Margin => {
264            vec![
265                CssPropertyType::MarginTop,
266                CssPropertyType::MarginBottom,
267                CssPropertyType::MarginLeft,
268                CssPropertyType::MarginRight,
269            ]
270        },
271        Border => {
272            vec![
273                CssPropertyType::BorderTopColor,
274                CssPropertyType::BorderRightColor,
275                CssPropertyType::BorderLeftColor,
276                CssPropertyType::BorderBottomColor,
277                CssPropertyType::BorderTopStyle,
278                CssPropertyType::BorderRightStyle,
279                CssPropertyType::BorderLeftStyle,
280                CssPropertyType::BorderBottomStyle,
281                CssPropertyType::BorderTopWidth,
282                CssPropertyType::BorderRightWidth,
283                CssPropertyType::BorderLeftWidth,
284                CssPropertyType::BorderBottomWidth,
285            ]
286        },
287        BorderLeft => {
288            vec![
289                CssPropertyType::BorderLeftColor,
290                CssPropertyType::BorderLeftStyle,
291                CssPropertyType::BorderLeftWidth,
292            ]
293        },
294        BorderRight => {
295            vec![
296                CssPropertyType::BorderRightColor,
297                CssPropertyType::BorderRightStyle,
298                CssPropertyType::BorderRightWidth,
299            ]
300        },
301        BorderTop => {
302            vec![
303                CssPropertyType::BorderTopColor,
304                CssPropertyType::BorderTopStyle,
305                CssPropertyType::BorderTopWidth,
306            ]
307        },
308        BorderBottom => {
309            vec![
310                CssPropertyType::BorderBottomColor,
311                CssPropertyType::BorderBottomStyle,
312                CssPropertyType::BorderBottomWidth,
313            ]
314        },
315        BoxShadow => {
316            vec![
317                CssPropertyType::BoxShadowLeft,
318                CssPropertyType::BoxShadowRight,
319                CssPropertyType::BoxShadowTop,
320                CssPropertyType::BoxShadowBottom,
321            ]
322        },
323    };
324
325    match value {
326        "auto" => return Ok(keys.into_iter().map(|ty| CssProperty::auto(ty)).collect()),
327        "none" => return Ok(keys.into_iter().map(|ty| CssProperty::none(ty)).collect()),
328        "initial" => return Ok(keys.into_iter().map(|ty| CssProperty::initial(ty)).collect()),
329        "inherit" => return Ok(keys.into_iter().map(|ty| CssProperty::inherit(ty)).collect()),
330        _ => { },
331    };
332
333    match key {
334        BorderRadius => {
335            let border_radius = parse_style_border_radius(value)?;
336            Ok(vec![
337                CssProperty::BorderTopLeftRadius(StyleBorderTopLeftRadius(border_radius.top_left).into()),
338                CssProperty::BorderTopRightRadius(StyleBorderTopRightRadius(border_radius.top_right).into()),
339                CssProperty::BorderBottomLeftRadius(StyleBorderBottomLeftRadius(border_radius.bottom_left).into()),
340                CssProperty::BorderBottomRightRadius(StyleBorderBottomRightRadius(border_radius.bottom_right).into()),
341            ])
342        },
343        Overflow => {
344            let overflow = parse_layout_overflow(value)?;
345            Ok(vec![
346                CssProperty::OverflowX(overflow.into()),
347                CssProperty::OverflowY(overflow.into()),
348            ])
349        },
350        Padding => {
351            let padding = parse_layout_padding(value)?;
352            Ok(vec![
353                convert_value!(padding.top, PaddingTop, LayoutPaddingTop),
354                convert_value!(padding.bottom, PaddingBottom, LayoutPaddingBottom),
355                convert_value!(padding.left, PaddingLeft, LayoutPaddingLeft),
356                convert_value!(padding.right, PaddingRight, LayoutPaddingRight),
357            ])
358        },
359        Margin => {
360            let margin = parse_layout_margin(value)?;
361            Ok(vec![
362                convert_value!(margin.top, MarginTop, LayoutMarginTop),
363                convert_value!(margin.bottom, MarginBottom, LayoutMarginBottom),
364                convert_value!(margin.left, MarginLeft, LayoutMarginLeft),
365                convert_value!(margin.right, MarginRight, LayoutMarginRight),
366            ])
367        },
368        Border => {
369            let border = parse_style_border(value)?;
370            Ok(vec![
371               CssProperty::BorderTopColor(StyleBorderTopColor(border.border_color).into()),
372               CssProperty::BorderRightColor(StyleBorderRightColor(border.border_color).into()),
373               CssProperty::BorderLeftColor(StyleBorderLeftColor(border.border_color).into()),
374               CssProperty::BorderBottomColor(StyleBorderBottomColor(border.border_color).into()),
375
376               CssProperty::BorderTopStyle(StyleBorderTopStyle(border.border_style).into()),
377               CssProperty::BorderRightStyle(StyleBorderRightStyle(border.border_style).into()),
378               CssProperty::BorderLeftStyle(StyleBorderLeftStyle(border.border_style).into()),
379               CssProperty::BorderBottomStyle(StyleBorderBottomStyle(border.border_style).into()),
380
381               CssProperty::BorderTopWidth(StyleBorderTopWidth(border.border_width).into()),
382               CssProperty::BorderRightWidth(StyleBorderRightWidth(border.border_width).into()),
383               CssProperty::BorderLeftWidth(StyleBorderLeftWidth(border.border_width).into()),
384               CssProperty::BorderBottomWidth(StyleBorderBottomWidth(border.border_width).into()),
385            ])
386        },
387        BorderLeft => {
388            let border = parse_style_border(value)?;
389            Ok(vec![
390               CssProperty::BorderLeftColor(StyleBorderLeftColor(border.border_color).into()),
391               CssProperty::BorderLeftStyle(StyleBorderLeftStyle(border.border_style).into()),
392               CssProperty::BorderLeftWidth(StyleBorderLeftWidth(border.border_width).into()),
393            ])
394        },
395        BorderRight => {
396            let border = parse_style_border(value)?;
397            Ok(vec![
398               CssProperty::BorderRightColor(StyleBorderRightColor(border.border_color).into()),
399               CssProperty::BorderRightStyle(StyleBorderRightStyle(border.border_style).into()),
400               CssProperty::BorderRightWidth(StyleBorderRightWidth(border.border_width).into()),
401            ])
402        },
403        BorderTop => {
404            let border = parse_style_border(value)?;
405            Ok(vec![
406               CssProperty::BorderTopColor(StyleBorderTopColor(border.border_color).into()),
407               CssProperty::BorderTopStyle(StyleBorderTopStyle(border.border_style).into()),
408               CssProperty::BorderTopWidth(StyleBorderTopWidth(border.border_width).into()),
409            ])
410        },
411        BorderBottom => {
412            let border = parse_style_border(value)?;
413            Ok(vec![
414               CssProperty::BorderBottomColor(StyleBorderBottomColor(border.border_color).into()),
415               CssProperty::BorderBottomStyle(StyleBorderBottomStyle(border.border_style).into()),
416               CssProperty::BorderBottomWidth(StyleBorderBottomWidth(border.border_width).into()),
417            ])
418        },
419        BoxShadow => {
420            let box_shadow = parse_style_box_shadow(value)?;
421            Ok(vec![
422               CssProperty::BoxShadowLeft(CssPropertyValue::Exact(box_shadow)),
423               CssProperty::BoxShadowRight(CssPropertyValue::Exact(box_shadow)),
424               CssProperty::BoxShadowTop(CssPropertyValue::Exact(box_shadow)),
425               CssProperty::BoxShadowBottom(CssPropertyValue::Exact(box_shadow)),
426            ])
427        },
428    }
429}
430
431/// Error containing all sub-errors that could happen during CSS parsing
432///
433/// Usually we want to crash on the first error, to notify the user of the problem.
434#[derive(Clone, PartialEq)]
435pub enum CssParsingError<'a> {
436    CssBorderParseError(CssBorderParseError<'a>),
437    CssShadowParseError(CssShadowParseError<'a>),
438    InvalidValueErr(InvalidValueErr<'a>),
439    PixelParseError(PixelParseError<'a>),
440    PercentageParseError(PercentageParseError),
441    CssImageParseError(CssImageParseError<'a>),
442    CssStyleFontFamilyParseError(CssStyleFontFamilyParseError<'a>),
443    CssBackgroundParseError(CssBackgroundParseError<'a>),
444    CssColorParseError(CssColorParseError<'a>),
445    CssStyleBorderRadiusParseError(CssStyleBorderRadiusParseError<'a>),
446    PaddingParseError(LayoutPaddingParseError<'a>),
447    MarginParseError(LayoutMarginParseError<'a>),
448    FlexShrinkParseError(FlexShrinkParseError<'a>),
449    FlexGrowParseError(FlexGrowParseError<'a>),
450    BackgroundPositionParseError(CssBackgroundPositionParseError<'a>),
451}
452
453impl_debug_as_display!(CssParsingError<'a>);
454impl_display!{ CssParsingError<'a>, {
455    CssStyleBorderRadiusParseError(e) => format!("Invalid border-radius: {}", e),
456    CssBorderParseError(e) => format!("Invalid border property: {}", e),
457    CssShadowParseError(e) => format!("Invalid shadow: \"{}\"", e),
458    InvalidValueErr(e) => format!("\"{}\"", e.0),
459    PixelParseError(e) => format!("{}", e),
460    PercentageParseError(e) => format!("{}", e),
461    CssImageParseError(e) => format!("{}", e),
462    CssStyleFontFamilyParseError(e) => format!("{}", e),
463    CssBackgroundParseError(e) => format!("{}", e),
464    CssColorParseError(e) => format!("{}", e),
465    PaddingParseError(e) => format!("{}", e),
466    MarginParseError(e) => format!("{}", e),
467    FlexShrinkParseError(e) => format!("{}", e),
468    FlexGrowParseError(e) => format!("{}", e),
469    BackgroundPositionParseError(e) => format!("{}", e),
470}}
471
472impl_from!(CssBorderParseError<'a>, CssParsingError::CssBorderParseError);
473impl_from!(CssShadowParseError<'a>, CssParsingError::CssShadowParseError);
474impl_from!(CssColorParseError<'a>, CssParsingError::CssColorParseError);
475impl_from!(InvalidValueErr<'a>, CssParsingError::InvalidValueErr);
476impl_from!(PixelParseError<'a>, CssParsingError::PixelParseError);
477impl_from!(CssImageParseError<'a>, CssParsingError::CssImageParseError);
478impl_from!(CssStyleFontFamilyParseError<'a>, CssParsingError::CssStyleFontFamilyParseError);
479impl_from!(CssBackgroundParseError<'a>, CssParsingError::CssBackgroundParseError);
480impl_from!(CssStyleBorderRadiusParseError<'a>, CssParsingError::CssStyleBorderRadiusParseError);
481impl_from!(LayoutPaddingParseError<'a>, CssParsingError::PaddingParseError);
482impl_from!(LayoutMarginParseError<'a>, CssParsingError::MarginParseError);
483impl_from!(FlexShrinkParseError<'a>, CssParsingError::FlexShrinkParseError);
484impl_from!(FlexGrowParseError<'a>, CssParsingError::FlexGrowParseError);
485impl_from!(CssBackgroundPositionParseError<'a>, CssParsingError::BackgroundPositionParseError);
486
487impl<'a> From<PercentageParseError> for CssParsingError<'a> {
488    fn from(e: PercentageParseError) -> Self {
489        CssParsingError::PercentageParseError(e)
490    }
491}
492
493/// Simple "invalid value" error, used for
494#[derive(Debug, Copy, Clone, Eq, PartialEq)]
495pub struct InvalidValueErr<'a>(pub &'a str);
496
497#[derive(Clone, PartialEq)]
498pub enum CssStyleBorderRadiusParseError<'a> {
499    TooManyValues(&'a str),
500    PixelParseError(PixelParseError<'a>),
501}
502
503impl_debug_as_display!(CssStyleBorderRadiusParseError<'a>);
504impl_display!{ CssStyleBorderRadiusParseError<'a>, {
505    TooManyValues(val) => format!("Too many values: \"{}\"", val),
506    PixelParseError(e) => format!("{}", e),
507}}
508
509impl_from!(PixelParseError<'a>, CssStyleBorderRadiusParseError::PixelParseError);
510
511#[derive(Debug, Copy, Clone, PartialEq)]
512pub enum CssColorComponent {
513    Red,
514    Green,
515    Blue,
516    Hue,
517    Saturation,
518    Lightness,
519    Alpha,
520}
521
522#[derive(Clone, PartialEq)]
523pub enum CssColorParseError<'a> {
524    InvalidColor(&'a str),
525    InvalidFunctionName(&'a str),
526    InvalidColorComponent(u8),
527    IntValueParseErr(ParseIntError),
528    FloatValueParseErr(ParseFloatError),
529    FloatValueOutOfRange(f32),
530    MissingColorComponent(CssColorComponent),
531    ExtraArguments(&'a str),
532    UnclosedColor(&'a str),
533    EmptyInput,
534    DirectionParseError(CssDirectionParseError<'a>),
535    UnsupportedDirection(&'a str),
536    InvalidPercentage(PercentageParseError),
537}
538
539impl_debug_as_display!(CssColorParseError<'a>);
540impl_display!{CssColorParseError<'a>, {
541    InvalidColor(i) => format!("Invalid CSS color: \"{}\"", i),
542    InvalidFunctionName(i) => format!("Invalid function name, expected one of: \"rgb\", \"rgba\", \"hsl\", \"hsla\" got: \"{}\"", i),
543    InvalidColorComponent(i) => format!("Invalid color component when parsing CSS color: \"{}\"", i),
544    IntValueParseErr(e) => format!("CSS color component: Value not in range between 00 - FF: \"{}\"", e),
545    FloatValueParseErr(e) => format!("CSS color component: Value cannot be parsed as floating point number: \"{}\"", e),
546    FloatValueOutOfRange(v) => format!("CSS color component: Value not in range between 0.0 - 1.0: \"{}\"", v),
547    MissingColorComponent(c) => format!("CSS color is missing {:?} component", c),
548    ExtraArguments(a) => format!("Extra argument to CSS color: \"{}\"", a),
549    EmptyInput => format!("Empty color string."),
550    UnclosedColor(i) => format!("Unclosed color: \"{}\"", i),
551    DirectionParseError(e) => format!("Could not parse direction argument for CSS color: \"{}\"", e),
552    UnsupportedDirection(d) => format!("Unsupported direction type for CSS color: \"{}\"", d),
553    InvalidPercentage(p) => format!("Invalid percentage when parsing CSS color: \"{}\"", p),
554}}
555
556impl<'a> From<ParseIntError> for CssColorParseError<'a> {
557    fn from(e: ParseIntError) -> Self {
558        CssColorParseError::IntValueParseErr(e)
559    }
560}
561
562impl<'a> From<ParseFloatError> for CssColorParseError<'a> {
563    fn from(e: ParseFloatError) -> Self {
564        CssColorParseError::FloatValueParseErr(e)
565    }
566}
567
568impl_from!(CssDirectionParseError<'a>, CssColorParseError::DirectionParseError);
569
570#[derive(Copy, Clone, PartialEq)]
571pub enum CssImageParseError<'a> {
572    UnclosedQuotes(&'a str),
573}
574
575impl_debug_as_display!(CssImageParseError<'a>);
576impl_display!{CssImageParseError<'a>, {
577    UnclosedQuotes(e) => format!("Unclosed quotes: \"{}\"", e),
578}}
579
580/// String has unbalanced `'` or `"` quotation marks
581#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
582pub struct UnclosedQuotesError<'a>(pub &'a str);
583
584impl<'a> From<UnclosedQuotesError<'a>> for CssImageParseError<'a> {
585    fn from(err: UnclosedQuotesError<'a>) -> Self {
586        CssImageParseError::UnclosedQuotes(err.0)
587    }
588}
589
590#[derive(Clone, PartialEq)]
591pub enum CssBorderParseError<'a> {
592    MissingThickness(&'a str),
593    InvalidBorderStyle(InvalidValueErr<'a>),
594    InvalidBorderDeclaration(&'a str),
595    ThicknessParseError(PixelParseError<'a>),
596    ColorParseError(CssColorParseError<'a>),
597}
598impl_debug_as_display!(CssBorderParseError<'a>);
599impl_display!{ CssBorderParseError<'a>, {
600    MissingThickness(e) => format!("Missing border thickness: \"{}\"", e),
601    InvalidBorderStyle(e) => format!("Invalid style: {}", e.0),
602    InvalidBorderDeclaration(e) => format!("Invalid declaration: \"{}\"", e),
603    ThicknessParseError(e) => format!("Invalid thickness: {}", e),
604    ColorParseError(e) => format!("Invalid color: {}", e),
605}}
606
607#[derive(Clone, PartialEq)]
608pub enum CssShadowParseError<'a> {
609    InvalidSingleStatement(&'a str),
610    TooManyComponents(&'a str),
611    ValueParseErr(PixelParseError<'a>),
612    ColorParseError(CssColorParseError<'a>),
613}
614impl_debug_as_display!(CssShadowParseError<'a>);
615impl_display!{ CssShadowParseError<'a>, {
616    InvalidSingleStatement(e) => format!("Invalid single statement: \"{}\"", e),
617    TooManyComponents(e) => format!("Too many components: \"{}\"", e),
618    ValueParseErr(e) => format!("Invalid value: {}", e),
619    ColorParseError(e) => format!("Invalid color-value: {}", e),
620}}
621
622impl_from!(PixelParseError<'a>, CssShadowParseError::ValueParseErr);
623impl_from!(CssColorParseError<'a>, CssShadowParseError::ColorParseError);
624
625#[derive(Debug, Copy, Clone, PartialEq, Ord, PartialOrd, Eq, Hash)]
626pub struct StyleBorderRadius {
627
628    // TODO: Should technically be PixelSize because the border radius doesn't have to be uniform
629    // but the parsing for that is complicated...
630
631    pub top_left: PixelValue,
632    pub top_right: PixelValue,
633    pub bottom_left: PixelValue,
634    pub bottom_right: PixelValue,
635}
636
637impl Default for StyleBorderRadius {
638    fn default() -> Self {
639        Self::zero()
640    }
641}
642
643impl StyleBorderRadius {
644
645    pub const fn zero() -> Self {
646        Self::uniform(PixelValue::zero())
647    }
648
649    pub const fn uniform(value: PixelValue) -> Self {
650        Self {
651            top_left: value,
652            top_right: value,
653            bottom_left: value,
654            bottom_right: value,
655        }
656    }
657}
658
659/// parse the border-radius like "5px 10px" or "5px 10px 6px 10px"
660pub fn parse_style_border_radius<'a>(input: &'a str)
661-> Result<StyleBorderRadius, CssStyleBorderRadiusParseError<'a>>
662{
663    let mut components = input.split_whitespace();
664    let len = components.clone().count();
665
666    match len {
667        1 => {
668            // One value - border-radius: 15px;
669            // (the value applies to all four corners, which are rounded equally:
670
671            let uniform_radius = parse_pixel_value(components.next().unwrap())?;
672            Ok(StyleBorderRadius::uniform(uniform_radius))
673        },
674        2 => {
675            // Two values - border-radius: 15px 50px;
676            // (first value applies to top-left and bottom-right corners,
677            // and the second value applies to top-right and bottom-left corners):
678
679            let top_left_bottom_right = parse_pixel_value(components.next().unwrap())?;
680            let top_right_bottom_left = parse_pixel_value(components.next().unwrap())?;
681
682            Ok(StyleBorderRadius {
683                top_left:       top_left_bottom_right,
684                bottom_right:   top_left_bottom_right,
685                top_right:      top_right_bottom_left,
686                bottom_left:    top_right_bottom_left,
687            })
688        },
689        3 => {
690            // Three values - border-radius: 15px 50px 30px;
691            // (first value applies to top-left corner,
692            // second value applies to top-right and bottom-left corners,
693            // and third value applies to bottom-right corner):
694            let top_left = parse_pixel_value(components.next().unwrap())?;
695            let top_right_bottom_left = parse_pixel_value(components.next().unwrap())?;
696            let bottom_right = parse_pixel_value(components.next().unwrap())?;
697
698            Ok(StyleBorderRadius {
699                top_left,
700                bottom_right,
701                top_right:  top_right_bottom_left,
702                bottom_left: top_right_bottom_left,
703            })
704        }
705        4 => {
706
707            // Four values - border-radius: 15px 50px 30px 5px;
708            //
709            // first value applies to top-left corner,
710            // second value applies to top-right corner,
711            // third value applies to bottom-right corner,
712            // fourth value applies to bottom-left corner
713
714            let top_left = parse_pixel_value(components.next().unwrap())?;
715            let top_right = parse_pixel_value(components.next().unwrap())?;
716            let bottom_right = parse_pixel_value(components.next().unwrap())?;
717            let bottom_left = parse_pixel_value(components.next().unwrap())?;
718
719            Ok(StyleBorderRadius {
720                top_left,
721                bottom_right,
722                top_right,
723                bottom_left,
724            })
725        },
726        _ => {
727            Err(CssStyleBorderRadiusParseError::TooManyValues(input))
728        }
729    }
730}
731
732#[derive(Clone, PartialEq)]
733pub enum PixelParseError<'a> {
734    EmptyString,
735    NoValueGiven(&'a str),
736    UnsupportedMetric(f32, String, &'a str),
737    ValueParseErr(ParseFloatError, String),
738}
739
740impl_debug_as_display!(PixelParseError<'a>);
741
742impl_display!{ PixelParseError<'a>, {
743    EmptyString => format!("Missing [px / pt / em / %] value"),
744    NoValueGiven(input) => format!("Expected floating-point pixel value, got: \"{}\"", input),
745    UnsupportedMetric(_, metric, input) => format!("Could not parse \"{}\": Metric \"{}\" is not (yet) implemented.", input, metric),
746    ValueParseErr(err, number_str) => format!("Could not parse \"{}\" as floating-point value: \"{}\"", number_str, err),
747}}
748
749pub fn parse_pixel_value<'a>(input: &'a str)
750-> Result<PixelValue, PixelParseError<'a>> {
751    parse_pixel_value_inner(input, &[
752        ("px", SizeMetric::Px),
753        ("em", SizeMetric::Em),
754        ("pt", SizeMetric::Pt),
755        ("%", SizeMetric::Percent),
756    ])
757}
758
759pub fn parse_pixel_value_no_percent<'a>(input: &'a str)
760-> Result<PixelValueNoPercent, PixelParseError<'a>> {
761    Ok(PixelValueNoPercent(
762        parse_pixel_value_inner(input, &[
763            ("px", SizeMetric::Px),
764            ("em", SizeMetric::Em),
765            ("pt", SizeMetric::Pt),
766        ])?
767    ))
768}
769
770/// parse a single value such as "15px"
771fn parse_pixel_value_inner<'a>(input: &'a str, match_values: &[(&'static str, SizeMetric)])
772-> Result<PixelValue, PixelParseError<'a>>
773{
774    let input = input.trim();
775
776    if input.is_empty() {
777        return Err(PixelParseError::EmptyString);
778    }
779
780    let is_part_of_number = |ch: &char| ch.is_numeric() || *ch == '.' || *ch == '-';
781
782    // You can't sub-string pixel values, have to call collect() here!
783    let number_str = input.chars().take_while(is_part_of_number).collect::<String>();
784    let unit_str = input.chars().filter(|ch| !is_part_of_number(ch)).collect::<String>();
785    let unit_str = unit_str.trim();
786    let unit_str = unit_str.to_string();
787
788    if number_str.is_empty() {
789        return Err(PixelParseError::NoValueGiven(input));
790    }
791
792    let number = number_str.parse::<f32>().map_err(|e| PixelParseError::ValueParseErr(e, number_str))?;
793
794    let unit =
795        if unit_str.is_empty() {
796            SizeMetric::Px
797        } else {
798            match_values.iter().find_map(|(target_str, target_size_metric)|
799                if unit_str.as_str() == *target_str { Some(*target_size_metric) } else { None }
800            ).ok_or(PixelParseError::UnsupportedMetric(number, unit_str, input))?
801        };
802
803    Ok(PixelValue::from_metric(unit, number))
804}
805
806#[derive(Clone, PartialEq, Eq)]
807pub enum PercentageParseError {
808    ValueParseErr(ParseFloatError),
809    NoPercentSign
810}
811
812impl_debug_as_display!(PercentageParseError);
813impl_from!(ParseFloatError, PercentageParseError::ValueParseErr);
814
815impl_display! { PercentageParseError, {
816    ValueParseErr(e) => format!("\"{}\"", e),
817    NoPercentSign => format!("No percent sign after number"),
818}}
819
820// Parse "1.2" or "120%" (similar to parse_pixel_value)
821pub fn parse_percentage_value(input: &str)
822-> Result<PercentageValue, PercentageParseError>
823{
824    let mut split_pos = 0;
825    for (idx, ch) in input.char_indices() {
826        if ch.is_numeric() || ch == '.' {
827            split_pos = idx;
828        }
829    }
830
831    split_pos += 1;
832
833    let unit = &input[split_pos..];
834    let mut number = input[..split_pos].parse::<f32>().map_err(|e| PercentageParseError::ValueParseErr(e))?;
835
836    if unit == "%" {
837        number /= 100.0;
838    }
839
840    Ok(PercentageValue::new(number))
841}
842
843/// Parse any valid CSS color, INCLUDING THE HASH
844///
845/// "blue" -> "00FF00" -> ColorF { r: 0, g: 255, b: 0 })
846/// "#00FF00" -> ColorF { r: 0, g: 255, b: 0 })
847pub fn parse_css_color<'a>(input: &'a str)
848-> Result<ColorU, CssColorParseError<'a>>
849{
850    let input = input.trim();
851    if input.starts_with('#') {
852        parse_color_no_hash(&input[1..])
853    } else {
854        use self::ParenthesisParseError::*;
855
856        match parse_parentheses(input, &["rgba", "rgb", "hsla", "hsl"]) {
857            Ok((stopword, inner_value)) => {
858                match stopword {
859                    "rgba" => parse_color_rgb(inner_value, true),
860                    "rgb" => parse_color_rgb(inner_value, false),
861                    "hsla" => parse_color_hsl(inner_value, true),
862                    "hsl" => parse_color_hsl(inner_value, false),
863                    _ => unreachable!(),
864                }
865            },
866            Err(e) => match e {
867                UnclosedBraces => Err(CssColorParseError::UnclosedColor(input)),
868                EmptyInput => Err(CssColorParseError::EmptyInput),
869                StopWordNotFound(stopword) => Err(CssColorParseError::InvalidFunctionName(stopword)),
870                NoClosingBraceFound => Err(CssColorParseError::UnclosedColor(input)),
871                NoOpeningBraceFound => parse_color_builtin(input),
872            },
873        }
874    }
875}
876
877/// Formats a ColorU in hex format
878pub fn css_color_to_string(color: ColorU, prefix_hash: bool) -> String {
879    let prefix = if prefix_hash { "#" } else { "" };
880    let alpha = if color.a == 255 { String::new() } else { format!("{:02x}", color.a) };
881    format!("{}{:02x}{:02x}{:02x}{}", prefix, color.r, color.g, color.b, alpha)
882}
883
884pub fn parse_float_value(input: &str)
885-> Result<FloatValue, ParseFloatError>
886{
887    Ok(FloatValue::new(input.trim().parse::<f32>()?))
888}
889
890pub fn parse_style_text_color<'a>(input: &'a str)
891-> Result<StyleTextColor, CssColorParseError<'a>>
892{
893    parse_css_color(input).and_then(|ok| Ok(StyleTextColor(ok)))
894}
895
896/// Parse a built-in background color
897///
898/// "blue" -> "00FF00" -> ColorF { r: 0, g: 255, b: 0 })
899pub fn parse_color_builtin<'a>(input: &'a str)
900-> Result<ColorU, CssColorParseError<'a>>
901{
902    let (r, g, b, a) = match input {
903        "AliceBlue"             | "alice-blue"                =>  (240, 248, 255, 255),
904        "AntiqueWhite"          | "antique-white"             =>  (250, 235, 215, 255),
905        "Aqua"                  | "aqua"                      =>  (  0, 255, 255, 255),
906        "Aquamarine"            | "aquamarine"                =>  (127, 255, 212, 255),
907        "Azure"                 | "azure"                     =>  (240, 255, 255, 255),
908        "Beige"                 | "beige"                     =>  (245, 245, 220, 255),
909        "Bisque"                | "bisque"                    =>  (255, 228, 196, 255),
910        "Black"                 | "black"                     =>  (  0,   0,   0, 255),
911        "BlanchedAlmond"        | "blanched-almond"           =>  (255, 235, 205, 255),
912        "Blue"                  | "blue"                      =>  (  0,   0, 255, 255),
913        "BlueViolet"            | "blue-violet"               =>  (138,  43, 226, 255),
914        "Brown"                 | "brown"                     =>  (165,  42,  42, 255),
915        "BurlyWood"             | "burly-wood"                =>  (222, 184, 135, 255),
916        "CadetBlue"             | "cadet-blue"                =>  ( 95, 158, 160, 255),
917        "Chartreuse"            | "chartreuse"                =>  (127, 255,   0, 255),
918        "Chocolate"             | "chocolate"                 =>  (210, 105,  30, 255),
919        "Coral"                 | "coral"                     =>  (255, 127,  80, 255),
920        "CornflowerBlue"        | "cornflower-blue"           =>  (100, 149, 237, 255),
921        "Cornsilk"              | "cornsilk"                  =>  (255, 248, 220, 255),
922        "Crimson"               | "crimson"                   =>  (220,  20,  60, 255),
923        "Cyan"                  | "cyan"                      =>  (  0, 255, 255, 255),
924        "DarkBlue"              | "dark-blue"                 =>  (  0,   0, 139, 255),
925        "DarkCyan"              | "dark-cyan"                 =>  (  0, 139, 139, 255),
926        "DarkGoldenRod"         | "dark-golden-rod"           =>  (184, 134,  11, 255),
927        "DarkGray"              | "dark-gray"                 =>  (169, 169, 169, 255),
928        "DarkGrey"              | "dark-grey"                 =>  (169, 169, 169, 255),
929        "DarkGreen"             | "dark-green"                =>  (  0, 100,   0, 255),
930        "DarkKhaki"             | "dark-khaki"                =>  (189, 183, 107, 255),
931        "DarkMagenta"           | "dark-magenta"              =>  (139,   0, 139, 255),
932        "DarkOliveGreen"        | "dark-olive-green"          =>  ( 85, 107,  47, 255),
933        "DarkOrange"            | "dark-orange"               =>  (255, 140,   0, 255),
934        "DarkOrchid"            | "dark-orchid"               =>  (153,  50, 204, 255),
935        "DarkRed"               | "dark-red"                  =>  (139,   0,   0, 255),
936        "DarkSalmon"            | "dark-salmon"               =>  (233, 150, 122, 255),
937        "DarkSeaGreen"          | "dark-sea-green"            =>  (143, 188, 143, 255),
938        "DarkSlateBlue"         | "dark-slate-blue"           =>  ( 72,  61, 139, 255),
939        "DarkSlateGray"         | "dark-slate-gray"           =>  ( 47,  79,  79, 255),
940        "DarkSlateGrey"         | "dark-slate-grey"           =>  ( 47,  79,  79, 255),
941        "DarkTurquoise"         | "dark-turquoise"            =>  (  0, 206, 209, 255),
942        "DarkViolet"            | "dark-violet"               =>  (148,   0, 211, 255),
943        "DeepPink"              | "deep-pink"                 =>  (255,  20, 147, 255),
944        "DeepSkyBlue"           | "deep-sky-blue"             =>  (  0, 191, 255, 255),
945        "DimGray"               | "dim-gray"                  =>  (105, 105, 105, 255),
946        "DimGrey"               | "dim-grey"                  =>  (105, 105, 105, 255),
947        "DodgerBlue"            | "dodger-blue"               =>  ( 30, 144, 255, 255),
948        "FireBrick"             | "fire-brick"                =>  (178,  34,  34, 255),
949        "FloralWhite"           | "floral-white"              =>  (255, 250, 240, 255),
950        "ForestGreen"           | "forest-green"              =>  ( 34, 139,  34, 255),
951        "Fuchsia"               | "fuchsia"                   =>  (255,   0, 255, 255),
952        "Gainsboro"             | "gainsboro"                 =>  (220, 220, 220, 255),
953        "GhostWhite"            | "ghost-white"               =>  (248, 248, 255, 255),
954        "Gold"                  | "gold"                      =>  (255, 215,   0, 255),
955        "GoldenRod"             | "golden-rod"                =>  (218, 165,  32, 255),
956        "Gray"                  | "gray"                      =>  (128, 128, 128, 255),
957        "Grey"                  | "grey"                      =>  (128, 128, 128, 255),
958        "Green"                 | "green"                     =>  (  0, 128,   0, 255),
959        "GreenYellow"           | "green-yellow"              =>  (173, 255,  47, 255),
960        "HoneyDew"              | "honey-dew"                 =>  (240, 255, 240, 255),
961        "HotPink"               | "hot-pink"                  =>  (255, 105, 180, 255),
962        "IndianRed"             | "indian-red"                =>  (205,  92,  92, 255),
963        "Indigo"                | "indigo"                    =>  ( 75,   0, 130, 255),
964        "Ivory"                 | "ivory"                     =>  (255, 255, 240, 255),
965        "Khaki"                 | "khaki"                     =>  (240, 230, 140, 255),
966        "Lavender"              | "lavender"                  =>  (230, 230, 250, 255),
967        "LavenderBlush"         | "lavender-blush"            =>  (255, 240, 245, 255),
968        "LawnGreen"             | "lawn-green"                =>  (124, 252,   0, 255),
969        "LemonChiffon"          | "lemon-chiffon"             =>  (255, 250, 205, 255),
970        "LightBlue"             | "light-blue"                =>  (173, 216, 230, 255),
971        "LightCoral"            | "light-coral"               =>  (240, 128, 128, 255),
972        "LightCyan"             | "light-cyan"                =>  (224, 255, 255, 255),
973        "LightGoldenRodYellow"  | "light-golden-rod-yellow"   =>  (250, 250, 210, 255),
974        "LightGray"             | "light-gray"                =>  (211, 211, 211, 255),
975        "LightGrey"             | "light-grey"                =>  (144, 238, 144, 255),
976        "LightGreen"            | "light-green"               =>  (211, 211, 211, 255),
977        "LightPink"             | "light-pink"                =>  (255, 182, 193, 255),
978        "LightSalmon"           | "light-salmon"              =>  (255, 160, 122, 255),
979        "LightSeaGreen"         | "light-sea-green"           =>  ( 32, 178, 170, 255),
980        "LightSkyBlue"          | "light-sky-blue"            =>  (135, 206, 250, 255),
981        "LightSlateGray"        | "light-slate-gray"          =>  (119, 136, 153, 255),
982        "LightSlateGrey"        | "light-slate-grey"          =>  (119, 136, 153, 255),
983        "LightSteelBlue"        | "light-steel-blue"          =>  (176, 196, 222, 255),
984        "LightYellow"           | "light-yellow"              =>  (255, 255, 224, 255),
985        "Lime"                  | "lime"                      =>  (  0, 255,   0, 255),
986        "LimeGreen"             | "lime-green"                =>  ( 50, 205,  50, 255),
987        "Linen"                 | "linen"                     =>  (250, 240, 230, 255),
988        "Magenta"               | "magenta"                   =>  (255,   0, 255, 255),
989        "Maroon"                | "maroon"                    =>  (128,   0,   0, 255),
990        "MediumAquaMarine"      | "medium-aqua-marine"        =>  (102, 205, 170, 255),
991        "MediumBlue"            | "medium-blue"               =>  (  0,   0, 205, 255),
992        "MediumOrchid"          | "medium-orchid"             =>  (186,  85, 211, 255),
993        "MediumPurple"          | "medium-purple"             =>  (147, 112, 219, 255),
994        "MediumSeaGreen"        | "medium-sea-green"          =>  ( 60, 179, 113, 255),
995        "MediumSlateBlue"       | "medium-slate-blue"         =>  (123, 104, 238, 255),
996        "MediumSpringGreen"     | "medium-spring-green"       =>  (  0, 250, 154, 255),
997        "MediumTurquoise"       | "medium-turquoise"          =>  ( 72, 209, 204, 255),
998        "MediumVioletRed"       | "medium-violet-red"         =>  (199,  21, 133, 255),
999        "MidnightBlue"          | "midnight-blue"             =>  ( 25,  25, 112, 255),
1000        "MintCream"             | "mint-cream"                =>  (245, 255, 250, 255),
1001        "MistyRose"             | "misty-rose"                =>  (255, 228, 225, 255),
1002        "Moccasin"              | "moccasin"                  =>  (255, 228, 181, 255),
1003        "NavajoWhite"           | "navajo-white"              =>  (255, 222, 173, 255),
1004        "Navy"                  | "navy"                      =>  (  0,   0, 128, 255),
1005        "OldLace"               | "old-lace"                  =>  (253, 245, 230, 255),
1006        "Olive"                 | "olive"                     =>  (128, 128,   0, 255),
1007        "OliveDrab"             | "olive-drab"                =>  (107, 142,  35, 255),
1008        "Orange"                | "orange"                    =>  (255, 165,   0, 255),
1009        "OrangeRed"             | "orange-red"                =>  (255,  69,   0, 255),
1010        "Orchid"                | "orchid"                    =>  (218, 112, 214, 255),
1011        "PaleGoldenRod"         | "pale-golden-rod"           =>  (238, 232, 170, 255),
1012        "PaleGreen"             | "pale-green"                =>  (152, 251, 152, 255),
1013        "PaleTurquoise"         | "pale-turquoise"            =>  (175, 238, 238, 255),
1014        "PaleVioletRed"         | "pale-violet-red"           =>  (219, 112, 147, 255),
1015        "PapayaWhip"            | "papaya-whip"               =>  (255, 239, 213, 255),
1016        "PeachPuff"             | "peach-puff"                =>  (255, 218, 185, 255),
1017        "Peru"                  | "peru"                      =>  (205, 133,  63, 255),
1018        "Pink"                  | "pink"                      =>  (255, 192, 203, 255),
1019        "Plum"                  | "plum"                      =>  (221, 160, 221, 255),
1020        "PowderBlue"            | "powder-blue"               =>  (176, 224, 230, 255),
1021        "Purple"                | "purple"                    =>  (128,   0, 128, 255),
1022        "RebeccaPurple"         | "rebecca-purple"            =>  (102,  51, 153, 255),
1023        "Red"                   | "red"                       =>  (255,   0,   0, 255),
1024        "RosyBrown"             | "rosy-brown"                =>  (188, 143, 143, 255),
1025        "RoyalBlue"             | "royal-blue"                =>  ( 65, 105, 225, 255),
1026        "SaddleBrown"           | "saddle-brown"              =>  (139,  69,  19, 255),
1027        "Salmon"                | "salmon"                    =>  (250, 128, 114, 255),
1028        "SandyBrown"            | "sandy-brown"               =>  (244, 164,  96, 255),
1029        "SeaGreen"              | "sea-green"                 =>  ( 46, 139,  87, 255),
1030        "SeaShell"              | "sea-shell"                 =>  (255, 245, 238, 255),
1031        "Sienna"                | "sienna"                    =>  (160,  82,  45, 255),
1032        "Silver"                | "silver"                    =>  (192, 192, 192, 255),
1033        "SkyBlue"               | "sky-blue"                  =>  (135, 206, 235, 255),
1034        "SlateBlue"             | "slate-blue"                =>  (106,  90, 205, 255),
1035        "SlateGray"             | "slate-gray"                =>  (112, 128, 144, 255),
1036        "SlateGrey"             | "slate-grey"                =>  (112, 128, 144, 255),
1037        "Snow"                  | "snow"                      =>  (255, 250, 250, 255),
1038        "SpringGreen"           | "spring-green"              =>  (  0, 255, 127, 255),
1039        "SteelBlue"             | "steel-blue"                =>  ( 70, 130, 180, 255),
1040        "Tan"                   | "tan"                       =>  (210, 180, 140, 255),
1041        "Teal"                  | "teal"                      =>  (  0, 128, 128, 255),
1042        "Thistle"               | "thistle"                   =>  (216, 191, 216, 255),
1043        "Tomato"                | "tomato"                    =>  (255,  99,  71, 255),
1044        "Turquoise"             | "turquoise"                 =>  ( 64, 224, 208, 255),
1045        "Violet"                | "violet"                    =>  (238, 130, 238, 255),
1046        "Wheat"                 | "wheat"                     =>  (245, 222, 179, 255),
1047        "White"                 | "white"                     =>  (255, 255, 255, 255),
1048        "WhiteSmoke"            | "white-smoke"               =>  (245, 245, 245, 255),
1049        "Yellow"                | "yellow"                    =>  (255, 255,   0, 255),
1050        "YellowGreen"           | "yellow-green"              =>  (154, 205,  50, 255),
1051        "Transparent"           | "transparent"               =>  (255, 255, 255,   0),
1052        _ => { return Err(CssColorParseError::InvalidColor(input)); }
1053    };
1054    Ok(ColorU { r, g, b, a })
1055}
1056
1057/// Parse a color of the form `rgb([0-255], [0-255], [0-255])`, or `rgba([0-255], [0-255], [0-255],
1058/// [0.0-1.0])` without the leading `rgb[a](` or trailing `)`. Alpha defaults to 255.
1059pub fn parse_color_rgb<'a>(input: &'a str, parse_alpha: bool)
1060-> Result<ColorU, CssColorParseError<'a>>
1061{
1062    let mut components = input.split(',').map(|c| c.trim());
1063    let rgb_color = parse_color_rgb_components(&mut components)?;
1064    let a = if parse_alpha {
1065        parse_alpha_component(&mut components)?
1066    } else {
1067        255
1068    };
1069    if let Some(arg) = components.next() {
1070        return Err(CssColorParseError::ExtraArguments(arg));
1071    }
1072    Ok(ColorU { a, ..rgb_color })
1073}
1074
1075/// Parse the color components passed as arguments to an rgb(...) CSS color.
1076pub fn parse_color_rgb_components<'a>(components: &mut dyn Iterator<Item = &'a str>)
1077-> Result<ColorU, CssColorParseError<'a>>
1078{
1079    #[inline]
1080    fn component_from_str<'a>(components: &mut dyn Iterator<Item = &'a str>, which: CssColorComponent)
1081    -> Result<u8, CssColorParseError<'a>>
1082    {
1083        let c = components.next().ok_or(CssColorParseError::MissingColorComponent(which))?;
1084        if c.is_empty() {
1085            return Err(CssColorParseError::MissingColorComponent(which));
1086        }
1087        let c = c.parse::<u8>()?;
1088        Ok(c)
1089    }
1090
1091    Ok(ColorU {
1092        r: component_from_str(components, CssColorComponent::Red)?,
1093        g: component_from_str(components, CssColorComponent::Green)?,
1094        b: component_from_str(components, CssColorComponent::Blue)?,
1095        a: 255
1096    })
1097}
1098
1099/// Parse a color of the form 'hsl([0.0-360.0]deg, [0-100]%, [0-100]%)', or 'hsla([0.0-360.0]deg, [0-100]%, [0-100]%, [0.0-1.0])' without the leading 'hsl[a](' or trailing ')'. Alpha defaults to 255.
1100pub fn parse_color_hsl<'a>(input: &'a str, parse_alpha: bool)
1101-> Result<ColorU, CssColorParseError<'a>>
1102{
1103    let mut components = input.split(',').map(|c| c.trim());
1104    let rgb_color = parse_color_hsl_components(&mut components)?;
1105    let a = if parse_alpha {
1106        parse_alpha_component(&mut components)?
1107    } else {
1108        255
1109    };
1110    if let Some(arg) = components.next() {
1111        return Err(CssColorParseError::ExtraArguments(arg));
1112    }
1113    Ok(ColorU { a, ..rgb_color })
1114}
1115
1116/// Parse the color components passed as arguments to an hsl(...) CSS color.
1117pub fn parse_color_hsl_components<'a>(components: &mut dyn Iterator<Item = &'a str>)
1118-> Result<ColorU, CssColorParseError<'a>>
1119{
1120    #[inline]
1121    fn angle_from_str<'a>(components: &mut dyn Iterator<Item = &'a str>, which: CssColorComponent)
1122    -> Result<f32, CssColorParseError<'a>>
1123    {
1124        let c = components.next().ok_or(CssColorParseError::MissingColorComponent(which))?;
1125        if c.is_empty() {
1126            return Err(CssColorParseError::MissingColorComponent(which));
1127        }
1128        let dir = parse_direction(c)?;
1129        match dir {
1130            Direction::Angle(deg) => Ok(deg.get()),
1131            Direction::FromTo(_, _) => return Err(CssColorParseError::UnsupportedDirection(c)),
1132        }
1133    }
1134
1135    #[inline]
1136    fn percent_from_str<'a>(components: &mut dyn Iterator<Item = &'a str>, which: CssColorComponent)
1137    -> Result<f32, CssColorParseError<'a>>
1138    {
1139        let c = components.next().ok_or(CssColorParseError::MissingColorComponent(which))?;
1140        if c.is_empty() {
1141            return Err(CssColorParseError::MissingColorComponent(which));
1142        }
1143
1144        let parsed_percent = parse_percentage(c).map_err(|e| CssColorParseError::InvalidPercentage(e))?;
1145
1146        Ok(parsed_percent.get())
1147    }
1148
1149    /// Adapted from [https://en.wikipedia.org/wiki/HSL_and_HSV#Converting_to_RGB]
1150    #[inline]
1151    fn hsl_to_rgb<'a>(h: f32, s: f32, l: f32) -> (u8, u8, u8) {
1152        let s = s / 100.0;
1153        let l = l / 100.0;
1154        let c = (1.0 - (2.0 * l - 1.0).abs()) * s;
1155        let h = h / 60.0;
1156        let x = c * (1.0 - ((h % 2.0) - 1.0).abs());
1157        let (r1, g1, b1) = match h as u8 {
1158            0 => (c, x, 0.0),
1159            1 => (x, c, 0.0),
1160            2 => (0.0, c, x),
1161            3 => (0.0, x, c),
1162            4 => (x, 0.0, c),
1163            5 => (c, 0.0, x),
1164            _ => {
1165                unreachable!();
1166            }
1167        };
1168        let m = l - c / 2.0;
1169        (
1170            ((r1 + m) * 256.0).min(255.0) as u8,
1171            ((g1 + m) * 256.0).min(255.0) as u8,
1172            ((b1 + m) * 256.0).min(255.0) as u8,
1173        )
1174    }
1175
1176    let (h, s, l) = (
1177        angle_from_str(components, CssColorComponent::Hue)?,
1178        percent_from_str(components, CssColorComponent::Saturation)?,
1179        percent_from_str(components, CssColorComponent::Lightness)?,
1180    );
1181
1182    let (r, g, b) = hsl_to_rgb(h, s, l);
1183
1184    Ok(ColorU { r, g, b, a: 255 })
1185}
1186
1187fn parse_alpha_component<'a>(components: &mut dyn Iterator<Item=&'a str>) -> Result<u8, CssColorParseError<'a>> {
1188    let a = components.next().ok_or(CssColorParseError::MissingColorComponent(CssColorComponent::Alpha))?;
1189    if a.is_empty() {
1190        return Err(CssColorParseError::MissingColorComponent(CssColorComponent::Alpha));
1191    }
1192    let a = a.parse::<f32>()?;
1193    if a < 0.0 || a > 1.0 {
1194        return Err(CssColorParseError::FloatValueOutOfRange(a));
1195    }
1196    let a = (a * 256.0).min(255.0) as u8;
1197    Ok(a)
1198}
1199
1200
1201/// Parse a background color, WITHOUT THE HASH
1202///
1203/// "00FFFF" -> ColorF { r: 0, g: 255, b: 255})
1204pub fn parse_color_no_hash<'a>(input: &'a str)
1205-> Result<ColorU, CssColorParseError<'a>>
1206{
1207    #[inline]
1208    fn from_hex<'a>(c: u8) -> Result<u8, CssColorParseError<'a>> {
1209        match c {
1210            b'0' ..= b'9' => Ok(c - b'0'),
1211            b'a' ..= b'f' => Ok(c - b'a' + 10),
1212            b'A' ..= b'F' => Ok(c - b'A' + 10),
1213            _ => Err(CssColorParseError::InvalidColorComponent(c))
1214        }
1215    }
1216
1217    match input.len() {
1218        3 => {
1219            let mut input_iter = input.chars();
1220
1221            let r = input_iter.next().unwrap() as u8;
1222            let g = input_iter.next().unwrap() as u8;
1223            let b = input_iter.next().unwrap() as u8;
1224
1225            let r = from_hex(r)? * 16 + from_hex(r)?;
1226            let g = from_hex(g)? * 16 + from_hex(g)?;
1227            let b = from_hex(b)? * 16 + from_hex(b)?;
1228
1229            Ok(ColorU {
1230                r: r,
1231                g: g,
1232                b: b,
1233                a: 255,
1234            })
1235        },
1236        4 => {
1237            let mut input_iter = input.chars();
1238
1239            let r = input_iter.next().unwrap() as u8;
1240            let g = input_iter.next().unwrap() as u8;
1241            let b = input_iter.next().unwrap() as u8;
1242            let a = input_iter.next().unwrap() as u8;
1243
1244            let r = from_hex(r)? * 16 + from_hex(r)?;
1245            let g = from_hex(g)? * 16 + from_hex(g)?;
1246            let b = from_hex(b)? * 16 + from_hex(b)?;
1247            let a = from_hex(a)? * 16 + from_hex(a)?;
1248
1249            Ok(ColorU {
1250                r: r,
1251                g: g,
1252                b: b,
1253                a: a,
1254            })
1255        },
1256        6 => {
1257            let input = u32::from_str_radix(input, 16).map_err(|e| CssColorParseError::IntValueParseErr(e))?;
1258            Ok(ColorU {
1259                r: ((input >> 16) & 255) as u8,
1260                g: ((input >> 8) & 255) as u8,
1261                b: (input & 255) as u8,
1262                a: 255,
1263            })
1264        },
1265        8 => {
1266            let input = u32::from_str_radix(input, 16).map_err(|e| CssColorParseError::IntValueParseErr(e))?;
1267            Ok(ColorU {
1268                r: ((input >> 24) & 255) as u8,
1269                g: ((input >> 16) & 255) as u8,
1270                b: ((input >> 8) & 255) as u8,
1271                a: (input & 255) as u8,
1272            })
1273        },
1274        _ => { Err(CssColorParseError::InvalidColor(input)) }
1275    }
1276}
1277
1278#[derive(Debug, Clone, PartialEq)]
1279pub enum LayoutPaddingParseError<'a> {
1280    PixelParseError(PixelParseError<'a>),
1281    TooManyValues,
1282    TooFewValues,
1283}
1284
1285impl_display!{ LayoutPaddingParseError<'a>, {
1286    PixelParseError(e) => format!("Could not parse pixel value: {}", e),
1287    TooManyValues => format!("Too many values - padding property has a maximum of 4 values."),
1288    TooFewValues => format!("Too few values - padding property has a minimum of 1 value."),
1289}}
1290
1291impl_from!(PixelParseError<'a>, LayoutPaddingParseError::PixelParseError);
1292
1293/// Represents a parsed `padding` attribute
1294#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1295pub struct LayoutPadding {
1296    pub top: PixelValueWithAuto,
1297    pub bottom: PixelValueWithAuto,
1298    pub left: PixelValueWithAuto,
1299    pub right: PixelValueWithAuto,
1300}
1301
1302/// Parse a padding value such as
1303///
1304/// "10px 10px"
1305pub fn parse_layout_padding<'a>(input: &'a str)
1306-> Result<LayoutPadding, LayoutPaddingParseError>
1307{
1308    let mut input_iter = input.split_whitespace();
1309    let first = parse_pixel_value_with_auto(input_iter.next().ok_or(LayoutPaddingParseError::TooFewValues)?)?;
1310    let second = parse_pixel_value_with_auto(match input_iter.next() {
1311        Some(s) => s,
1312        None => return Ok(LayoutPadding {
1313            top: first,
1314            bottom: first,
1315            left: first,
1316            right: first,
1317        }),
1318    })?;
1319    let third = parse_pixel_value_with_auto(match input_iter.next() {
1320        Some(s) => s,
1321        None => return Ok(LayoutPadding {
1322            top: first,
1323            bottom: first,
1324            left: second,
1325            right: second,
1326        }),
1327    })?;
1328    let fourth = parse_pixel_value_with_auto(match input_iter.next() {
1329        Some(s) => s,
1330        None => return Ok(LayoutPadding {
1331            top: first,
1332            left: second,
1333            right: second,
1334            bottom: third,
1335        }),
1336    })?;
1337
1338    if input_iter.next().is_some() {
1339        return Err(LayoutPaddingParseError::TooManyValues);
1340    }
1341
1342    Ok(LayoutPadding {
1343        top: first,
1344        right: second,
1345        bottom: third,
1346        left: fourth,
1347    })
1348}
1349
1350#[derive(Debug, Clone, PartialEq)]
1351pub enum LayoutMarginParseError<'a> {
1352    PixelParseError(PixelParseError<'a>),
1353    TooManyValues,
1354    TooFewValues,
1355}
1356
1357impl_display!{ LayoutMarginParseError<'a>, {
1358    PixelParseError(e) => format!("Could not parse pixel value: {}", e),
1359    TooManyValues => format!("Too many values - margin property has a maximum of 4 values."),
1360    TooFewValues => format!("Too few values - margin property has a minimum of 1 value."),
1361}}
1362
1363impl_from!(PixelParseError<'a>, LayoutMarginParseError::PixelParseError);
1364
1365#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1366pub enum PixelValueWithAuto {
1367    None,
1368    Initial,
1369    Inherit,
1370    Auto,
1371    Exact(PixelValue),
1372}
1373
1374/// Parses a pixel value, but also tries values like "auto", "initial", "inherit" and "none"
1375pub fn parse_pixel_value_with_auto<'a>(input: &'a str) -> Result<PixelValueWithAuto, PixelParseError<'a>> {
1376    let input = input.trim();
1377    match input {
1378        "none" => Ok(PixelValueWithAuto::None),
1379        "initial" => Ok(PixelValueWithAuto::Initial),
1380        "inherit" => Ok(PixelValueWithAuto::Inherit),
1381        "auto" => Ok(PixelValueWithAuto::Auto),
1382        e => Ok(PixelValueWithAuto::Exact(parse_pixel_value(e)?)),
1383    }
1384}
1385
1386/// Represents a parsed `padding` attribute
1387#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1388pub struct LayoutMargin {
1389    pub top: PixelValueWithAuto,
1390    pub bottom: PixelValueWithAuto,
1391    pub left: PixelValueWithAuto,
1392    pub right: PixelValueWithAuto,
1393}
1394
1395pub fn parse_layout_margin<'a>(input: &'a str)
1396-> Result<LayoutMargin, LayoutMarginParseError>
1397{
1398    match parse_layout_padding(input) {
1399        Ok(padding) => {
1400            Ok(LayoutMargin {
1401                top: padding.top,
1402                left: padding.left,
1403                right: padding.right,
1404                bottom: padding.bottom,
1405            })
1406        },
1407        Err(LayoutPaddingParseError::PixelParseError(e)) => Err(e.into()),
1408        Err(LayoutPaddingParseError::TooManyValues) => Err(LayoutMarginParseError::TooManyValues),
1409        Err(LayoutPaddingParseError::TooFewValues) => Err(LayoutMarginParseError::TooFewValues),
1410    }
1411}
1412
1413const DEFAULT_BORDER_COLOR: ColorU = ColorU { r: 0, g: 0, b: 0, a: 255 };
1414// Default border thickness on the web seems to be 3px
1415const DEFAULT_BORDER_THICKNESS: PixelValue = PixelValue::const_px(3);
1416
1417use std::str::CharIndices;
1418
1419fn advance_until_next_char(iter: &mut CharIndices) -> Option<usize> {
1420    let mut next_char = iter.next()?;
1421    while next_char.1.is_whitespace() {
1422        match iter.next() {
1423            Some(s) => next_char = s,
1424            None => return Some(next_char.0 + 1),
1425        }
1426    }
1427    Some(next_char.0)
1428}
1429
1430/// Advances a CharIndices iterator until the next space is encountered
1431fn take_until_next_whitespace(iter: &mut CharIndices) -> Option<usize> {
1432    let mut next_char = iter.next()?;
1433    while !next_char.1.is_whitespace() {
1434        match iter.next() {
1435            Some(s) => next_char = s,
1436            None => return Some(next_char.0 + 1),
1437        }
1438    }
1439    Some(next_char.0)
1440}
1441
1442/// Parse a CSS border such as
1443///
1444/// "5px solid red"
1445pub fn parse_style_border<'a>(input: &'a str)
1446-> Result<StyleBorderSide, CssBorderParseError<'a>>
1447{
1448    use self::CssBorderParseError::*;
1449
1450    let input = input.trim();
1451
1452    // The first argument can either be a style or a pixel value
1453
1454    let mut char_iter = input.char_indices();
1455    let first_arg_end = take_until_next_whitespace(&mut char_iter).ok_or(MissingThickness(input))?;
1456    let first_arg_str = &input[0..first_arg_end];
1457
1458    advance_until_next_char(&mut char_iter);
1459
1460    let second_argument_end = take_until_next_whitespace(&mut char_iter);
1461    let (border_width, border_width_str_end, border_style);
1462
1463    match second_argument_end {
1464        None => {
1465            // First argument is the one and only argument, therefore has to be a style such as "double"
1466            border_style = parse_style_border_style(first_arg_str).map_err(|e| InvalidBorderStyle(e))?;
1467            return Ok(StyleBorderSide {
1468                border_style,
1469                border_width: DEFAULT_BORDER_THICKNESS,
1470                border_color: DEFAULT_BORDER_COLOR,
1471            });
1472        },
1473        Some(end) => {
1474            // First argument is a pixel value, second argument is the border style
1475            border_width = parse_pixel_value(first_arg_str).map_err(|e| ThicknessParseError(e))?;
1476            let border_style_str = &input[first_arg_end..end];
1477            border_style = parse_style_border_style(border_style_str).map_err(|e| InvalidBorderStyle(e))?;
1478            border_width_str_end = end;
1479        }
1480    }
1481
1482    let border_color_str = &input[border_width_str_end..];
1483
1484    // Last argument can be either a hex color or a rgb str
1485    let border_color = parse_css_color(border_color_str).map_err(|e| ColorParseError(e))?;
1486
1487    Ok(StyleBorderSide {
1488        border_width,
1489        border_style,
1490        border_color,
1491    })
1492}
1493
1494/// Parses a CSS box-shadow, such as "5px 10px inset"
1495pub fn parse_style_box_shadow<'a>(input: &'a str)
1496-> Result<BoxShadowPreDisplayItem, CssShadowParseError<'a>>
1497{
1498    let mut input_iter = input.split_whitespace();
1499    let count = input_iter.clone().count();
1500
1501    let mut box_shadow = BoxShadowPreDisplayItem {
1502        offset: [PixelValueNoPercent(PixelValue::const_px(0)), PixelValueNoPercent(PixelValue::const_px(0))],
1503        color: ColorU { r: 0, g: 0, b: 0, a: 255 },
1504        blur_radius: PixelValueNoPercent(PixelValue::const_px(0)),
1505        spread_radius: PixelValueNoPercent(PixelValue::const_px(0)),
1506        clip_mode: BoxShadowClipMode::Outset,
1507    };
1508
1509    let last_val = input_iter.clone().rev().next();
1510    let is_inset = last_val == Some("inset") || last_val == Some("outset");
1511
1512    if count > 2 && is_inset {
1513        let l_val = last_val.unwrap();
1514        if l_val == "outset" {
1515            box_shadow.clip_mode = BoxShadowClipMode::Outset;
1516        } else if l_val == "inset" {
1517            box_shadow.clip_mode = BoxShadowClipMode::Inset;
1518        }
1519    }
1520
1521    match count {
1522        2 => {
1523            // box-shadow: 5px 10px; (h_offset, v_offset)
1524            let h_offset = parse_pixel_value_no_percent(input_iter.next().unwrap())?;
1525            let v_offset = parse_pixel_value_no_percent(input_iter.next().unwrap())?;
1526            box_shadow.offset[0] = h_offset;
1527            box_shadow.offset[1] = v_offset;
1528        },
1529        3 => {
1530            // box-shadow: 5px 10px inset; (h_offset, v_offset, inset)
1531            let h_offset = parse_pixel_value_no_percent(input_iter.next().unwrap())?;
1532            let v_offset = parse_pixel_value_no_percent(input_iter.next().unwrap())?;
1533            box_shadow.offset[0] = h_offset;
1534            box_shadow.offset[1] = v_offset;
1535
1536            if !is_inset {
1537                // box-shadow: 5px 10px #888888; (h_offset, v_offset, color)
1538                let color = parse_css_color(input_iter.next().unwrap())?;
1539                box_shadow.color = color;
1540            }
1541        },
1542        4 => {
1543            let h_offset = parse_pixel_value_no_percent(input_iter.next().unwrap())?;
1544            let v_offset = parse_pixel_value_no_percent(input_iter.next().unwrap())?;
1545            box_shadow.offset[0] = h_offset;
1546            box_shadow.offset[1] = v_offset;
1547
1548            if !is_inset {
1549                let blur = parse_pixel_value_no_percent(input_iter.next().unwrap())?;
1550                box_shadow.blur_radius = blur.into();
1551            }
1552
1553            let color = parse_css_color(input_iter.next().unwrap())?;
1554            box_shadow.color = color;
1555        },
1556        5 => {
1557            // box-shadow: 5px 10px 5px 10px #888888; (h_offset, v_offset, blur, spread, color)
1558            // box-shadow: 5px 10px 5px #888888 inset; (h_offset, v_offset, blur, color, inset)
1559            let h_offset = parse_pixel_value_no_percent(input_iter.next().unwrap())?;
1560            let v_offset = parse_pixel_value_no_percent(input_iter.next().unwrap())?;
1561            box_shadow.offset[0] = h_offset;
1562            box_shadow.offset[1] = v_offset;
1563
1564            let blur = parse_pixel_value_no_percent(input_iter.next().unwrap())?;
1565            box_shadow.blur_radius = blur.into();
1566
1567            if !is_inset {
1568                let spread = parse_pixel_value_no_percent(input_iter.next().unwrap())?;
1569                box_shadow.spread_radius = spread.into();
1570            }
1571
1572            let color = parse_css_color(input_iter.next().unwrap())?;
1573            box_shadow.color = color;
1574        },
1575        6 => {
1576            // box-shadow: 5px 10px 5px 10px #888888 inset; (h_offset, v_offset, blur, spread, color, inset)
1577            let h_offset = parse_pixel_value_no_percent(input_iter.next().unwrap())?;
1578            let v_offset = parse_pixel_value_no_percent(input_iter.next().unwrap())?;
1579            box_shadow.offset[0] = h_offset;
1580            box_shadow.offset[1] = v_offset;
1581
1582            let blur = parse_pixel_value_no_percent(input_iter.next().unwrap())?;
1583            box_shadow.blur_radius = blur.into();
1584
1585            let spread = parse_pixel_value_no_percent(input_iter.next().unwrap())?;
1586            box_shadow.spread_radius = spread.into();
1587
1588            let color = parse_css_color(input_iter.next().unwrap())?;
1589            box_shadow.color = color;
1590        }
1591        _ => {
1592            return Err(CssShadowParseError::TooManyComponents(input));
1593        }
1594    }
1595
1596    Ok(box_shadow)
1597}
1598
1599#[derive(Clone, PartialEq)]
1600pub enum CssBackgroundParseError<'a> {
1601    Error(&'a str),
1602    InvalidBackground(ParenthesisParseError<'a>),
1603    UnclosedGradient(&'a str),
1604    NoDirection(&'a str),
1605    TooFewGradientStops(&'a str),
1606    DirectionParseError(CssDirectionParseError<'a>),
1607    GradientParseError(CssGradientStopParseError<'a>),
1608    ShapeParseError(CssShapeParseError<'a>),
1609    ImageParseError(CssImageParseError<'a>),
1610    ColorParseError(CssColorParseError<'a>),
1611}
1612
1613impl_debug_as_display!(CssBackgroundParseError<'a>);
1614impl_display!{ CssBackgroundParseError<'a>, {
1615    Error(e) => e,
1616    InvalidBackground(val) => format!("Invalid background value: \"{}\"", val),
1617    UnclosedGradient(val) => format!("Unclosed gradient: \"{}\"", val),
1618    NoDirection(val) => format!("Gradient has no direction: \"{}\"", val),
1619    TooFewGradientStops(val) => format!("Failed to parse gradient due to too few gradient steps: \"{}\"", val),
1620    DirectionParseError(e) => format!("Failed to parse gradient direction: \"{}\"", e),
1621    GradientParseError(e) => format!("Failed to parse gradient: {}", e),
1622    ShapeParseError(e) => format!("Failed to parse shape of radial gradient: {}", e),
1623    ImageParseError(e) => format!("Failed to parse image() value: {}", e),
1624    ColorParseError(e) => format!("Failed to parse color value: {}", e),
1625}}
1626
1627impl_from!(ParenthesisParseError<'a>, CssBackgroundParseError::InvalidBackground);
1628impl_from!(CssDirectionParseError<'a>, CssBackgroundParseError::DirectionParseError);
1629impl_from!(CssGradientStopParseError<'a>, CssBackgroundParseError::GradientParseError);
1630impl_from!(CssShapeParseError<'a>, CssBackgroundParseError::ShapeParseError);
1631impl_from!(CssImageParseError<'a>, CssBackgroundParseError::ImageParseError);
1632impl_from!(CssColorParseError<'a>, CssBackgroundParseError::ColorParseError);
1633
1634// parses a background, such as "linear-gradient(red, green)"
1635pub fn parse_style_background_content<'a>(input: &'a str)
1636-> Result<StyleBackgroundContent, CssBackgroundParseError<'a>>
1637{
1638    match parse_parentheses(input, &[
1639        "linear-gradient", "repeating-linear-gradient",
1640        "radial-gradient", "repeating-radial-gradient",
1641        "image",
1642    ]) {
1643        Ok((background_type, brace_contents)) => {
1644            let gradient_type = match background_type {
1645                "linear-gradient" => GradientType::LinearGradient,
1646                "repeating-linear-gradient" => GradientType::RepeatingLinearGradient,
1647                "radial-gradient" => GradientType::RadialGradient,
1648                "repeating-radial-gradient" => GradientType::RepeatingRadialGradient,
1649                "image" => { return Ok(StyleBackgroundContent::Image(parse_image(brace_contents)?)); },
1650                other => { return Err(CssBackgroundParseError::Error(other)); /* unreachable */ },
1651            };
1652
1653            parse_gradient(brace_contents, gradient_type)
1654        },
1655        Err(_) => {
1656            Ok(StyleBackgroundContent::Color(parse_css_color(input)?))
1657        }
1658    }
1659}
1660
1661#[derive(Debug, Clone, PartialEq)]
1662pub enum CssBackgroundPositionParseError<'a> {
1663    NoPosition(&'a str),
1664    TooManyComponents(&'a str),
1665    FirstComponentWrong(PixelParseError<'a>),
1666    SecondComponentWrong(PixelParseError<'a>),
1667}
1668
1669impl_display!{CssBackgroundPositionParseError<'a>, {
1670    NoPosition(e) => format!("First background position missing: \"{}\"", e),
1671    TooManyComponents(e) => format!("background-position can only have one or two components, not more: \"{}\"", e),
1672    FirstComponentWrong(e) => format!("Failed to parse first component: \"{}\"", e),
1673    SecondComponentWrong(e) => format!("Failed to parse second component: \"{}\"", e),
1674}}
1675
1676pub fn parse_background_position_horizontal<'a>(input: &'a str) -> Result<BackgroundPositionHorizontal, PixelParseError<'a>> {
1677    Ok(match input {
1678        "left" => BackgroundPositionHorizontal::Left,
1679        "center" => BackgroundPositionHorizontal::Center,
1680        "right" => BackgroundPositionHorizontal::Right,
1681        other => BackgroundPositionHorizontal::Exact(parse_pixel_value(other)?),
1682    })
1683}
1684
1685pub fn parse_background_position_vertical<'a>(input: &'a str) -> Result<BackgroundPositionVertical, PixelParseError<'a>> {
1686    Ok(match input {
1687        "top" => BackgroundPositionVertical::Top,
1688        "center" => BackgroundPositionVertical::Center,
1689        "bottom" => BackgroundPositionVertical::Bottom,
1690        other => BackgroundPositionVertical::Exact(parse_pixel_value(other)?),
1691    })
1692}
1693
1694pub fn parse_style_background_position<'a>(input: &'a str)
1695-> Result<StyleBackgroundPosition, CssBackgroundPositionParseError<'a>>
1696{
1697    use self::CssBackgroundPositionParseError::*;
1698
1699    let input = input.trim();
1700    let mut whitespace_iter = input.split_whitespace();
1701
1702    let first = whitespace_iter.next().ok_or(NoPosition(input))?;
1703    let second = whitespace_iter.next();
1704
1705    if whitespace_iter.next().is_some() {
1706        return Err(TooManyComponents(input));
1707    }
1708
1709    let horizontal = parse_background_position_horizontal(first).map_err(|e| FirstComponentWrong(e))?;
1710
1711    let vertical = match second {
1712        Some(second) => parse_background_position_vertical(second).map_err(|e| SecondComponentWrong(e))?,
1713        None => BackgroundPositionVertical::Center,
1714    };
1715
1716    Ok(StyleBackgroundPosition { horizontal, vertical })
1717}
1718
1719/// Given a string, returns how many characters need to be skipped
1720fn skip_next_braces(input: &str, target_char: char) -> Option<(usize, bool)> {
1721
1722    let mut depth = 0;
1723    let mut last_character = 0;
1724    let mut character_was_found = false;
1725
1726    if input.is_empty() {
1727        return None;
1728    }
1729
1730    for (idx, ch) in input.char_indices() {
1731        last_character = idx;
1732        match ch {
1733            '(' => { depth += 1; },
1734            ')' => { depth -= 1; },
1735            c => {
1736                if c == target_char && depth == 0 {
1737                    character_was_found = true;
1738                    break;
1739                }
1740            },
1741        }
1742    }
1743
1744    if last_character == 0 {
1745        // No more split by `,`
1746        None
1747    } else {
1748        Some((last_character, character_was_found))
1749    }
1750}
1751
1752// parses a single gradient such as "to right, 50px"
1753pub fn parse_gradient<'a>(input: &'a str, background_type: GradientType)
1754-> Result<StyleBackgroundContent, CssBackgroundParseError<'a>>
1755{
1756    let input = input.trim();
1757
1758    // Splitting the input by "," doesn't work since rgba() might contain commas
1759    let mut comma_separated_items = Vec::<&str>::new();
1760    let mut current_input = &input[..];
1761
1762    'outer: loop {
1763        let (skip_next_braces_result, character_was_found) =
1764        match skip_next_braces(&current_input, ',') {
1765            Some(s) => s,
1766            None => break 'outer,
1767        };
1768        let new_push_item = if character_was_found {
1769            &current_input[..skip_next_braces_result]
1770        } else {
1771            &current_input[..]
1772        };
1773        let new_current_input = &current_input[(skip_next_braces_result + 1)..];
1774        comma_separated_items.push(new_push_item);
1775        current_input = new_current_input;
1776        if !character_was_found {
1777            break 'outer;
1778        }
1779    }
1780
1781    let mut brace_iterator = comma_separated_items.iter();
1782    let mut gradient_stop_count = brace_iterator.clone().count();
1783
1784    // "50deg", "to right bottom", etc.
1785    let first_brace_item = match brace_iterator.next() {
1786        Some(s) => s,
1787        None => return Err(CssBackgroundParseError::NoDirection(input)),
1788    };
1789
1790    // default shape: ellipse
1791    let mut shape = Shape::Ellipse;
1792    // default gradient: from top to bottom
1793    let mut direction = Direction::FromTo(DirectionCorner::Top, DirectionCorner::Bottom);
1794
1795    let mut first_is_direction = false;
1796    let mut first_is_shape = false;
1797
1798    let is_linear_gradient = background_type == GradientType::LinearGradient ||
1799                             background_type == GradientType::RepeatingLinearGradient;
1800
1801    let is_radial_gradient = background_type == GradientType::RadialGradient ||
1802                             background_type == GradientType::RepeatingRadialGradient;
1803
1804    if is_linear_gradient {
1805        if let Ok(dir) = parse_direction(first_brace_item) {
1806            direction = dir;
1807            first_is_direction = true;
1808        }
1809    }
1810
1811    if is_radial_gradient {
1812        if let Ok(sh) = parse_shape(first_brace_item) {
1813            shape = sh;
1814            first_is_shape = true;
1815        }
1816    }
1817
1818    let mut first_item_doesnt_count = false;
1819    if (is_linear_gradient && first_is_direction) || (is_radial_gradient && first_is_shape) {
1820        gradient_stop_count -= 1; // first item is not a gradient stop
1821        first_item_doesnt_count = true;
1822    }
1823
1824    if gradient_stop_count < 2 {
1825        return Err(CssBackgroundParseError::TooFewGradientStops(input));
1826    }
1827
1828    let mut color_stops = Vec::<GradientStopPre>::with_capacity(gradient_stop_count);
1829    if !first_item_doesnt_count {
1830        color_stops.push(parse_gradient_stop(first_brace_item)?);
1831    }
1832
1833    for stop in brace_iterator {
1834        color_stops.push(parse_gradient_stop(stop)?);
1835    }
1836
1837    normalize_color_stops(&mut color_stops);
1838
1839    match background_type {
1840        GradientType::LinearGradient => {
1841            Ok(StyleBackgroundContent::LinearGradient(LinearGradient {
1842                direction: direction,
1843                extend_mode: ExtendMode::Clamp,
1844                stops: color_stops,
1845            }))
1846        },
1847        GradientType::RepeatingLinearGradient => {
1848            Ok(StyleBackgroundContent::LinearGradient(LinearGradient {
1849                direction: direction,
1850                extend_mode: ExtendMode::Repeat,
1851                stops: color_stops,
1852            }))
1853        },
1854        GradientType::RadialGradient => {
1855            Ok(StyleBackgroundContent::RadialGradient(RadialGradient {
1856                shape: shape,
1857                extend_mode: ExtendMode::Clamp,
1858                stops: color_stops,
1859            }))
1860        },
1861        GradientType::RepeatingRadialGradient => {
1862            Ok(StyleBackgroundContent::RadialGradient(RadialGradient {
1863                shape: shape,
1864                extend_mode: ExtendMode::Repeat,
1865                stops: color_stops,
1866            }))
1867        },
1868    }
1869}
1870
1871// Normalize the percentages of the parsed color stops
1872pub fn normalize_color_stops(color_stops: &mut Vec<GradientStopPre>) {
1873
1874    let mut last_stop = PercentageValue::new(0.0);
1875    let mut increase_stop_cnt: Option<f32> = None;
1876
1877    let color_stop_len = color_stops.len();
1878    'outer: for i in 0..color_stop_len {
1879        let offset = color_stops[i].offset;
1880        match offset {
1881            Some(s) => {
1882                last_stop = s;
1883                increase_stop_cnt = None;
1884            },
1885            None => {
1886                let (_, next) = color_stops.split_at_mut(i);
1887
1888                if let Some(increase_stop_cnt) = increase_stop_cnt {
1889                    last_stop = PercentageValue::new(last_stop.get() + increase_stop_cnt);
1890                    next[0].offset = Some(last_stop);
1891                    continue 'outer;
1892                }
1893
1894                let mut next_count: u32 = 0;
1895                let mut next_value = None;
1896
1897                // iterate until we find a value where the offset isn't none
1898                {
1899                    let mut next_iter = next.iter();
1900                    next_iter.next();
1901                    'inner: for next_stop in next_iter {
1902                        if let Some(off) = next_stop.offset {
1903                            next_value = Some(off);
1904                            break 'inner;
1905                        } else {
1906                            next_count += 1;
1907                        }
1908                    }
1909                }
1910
1911                let next_value = next_value.unwrap_or(PercentageValue::new(100.0));
1912                let increase = (next_value.get() / (next_count as f32)) - (last_stop.get() / (next_count as f32)) ;
1913                increase_stop_cnt = Some(increase);
1914                if next_count == 1 && (color_stop_len - i) == 1 {
1915                    next[0].offset = Some(last_stop);
1916                } else {
1917                    if i == 0 {
1918                        next[0].offset = Some(PercentageValue::new(0.0));
1919                    } else {
1920                        next[0].offset = Some(last_stop);
1921                        // last_stop += increase;
1922                    }
1923                }
1924            }
1925        }
1926    }
1927}
1928
1929impl<'a> From<QuoteStripped<'a>> for CssImageId {
1930    fn from(input: QuoteStripped<'a>) -> Self {
1931        CssImageId(input.0.to_string())
1932    }
1933}
1934
1935/// A string that has been stripped of the beginning and ending quote
1936#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
1937pub struct QuoteStripped<'a>(pub &'a str);
1938
1939pub fn parse_image<'a>(input: &'a str) -> Result<CssImageId, CssImageParseError<'a>> {
1940    Ok(strip_quotes(input)?.into())
1941}
1942
1943/// Strip quotes from an input, given that both quotes use either `"` or `'`, but not both.
1944///
1945/// # Example
1946///
1947/// ```rust
1948/// # extern crate azul_css_parser;
1949/// # use azul_css_parser::{strip_quotes, QuoteStripped, UnclosedQuotesError};
1950/// assert_eq!(strip_quotes("\"Helvetica\""), Ok(QuoteStripped("Helvetica")));
1951/// assert_eq!(strip_quotes("'Arial'"), Ok(QuoteStripped("Arial")));
1952/// assert_eq!(strip_quotes("\"Arial'"), Err(UnclosedQuotesError("\"Arial'")));
1953/// ```
1954pub fn strip_quotes<'a>(input: &'a str) -> Result<QuoteStripped<'a>, UnclosedQuotesError<'a>> {
1955    let mut double_quote_iter = input.splitn(2, '"');
1956    double_quote_iter.next();
1957    let mut single_quote_iter = input.splitn(2, '\'');
1958    single_quote_iter.next();
1959
1960    let first_double_quote = double_quote_iter.next();
1961    let first_single_quote = single_quote_iter.next();
1962    if first_double_quote.is_some() && first_single_quote.is_some() {
1963        return Err(UnclosedQuotesError(input));
1964    }
1965    if first_double_quote.is_some() {
1966        let quote_contents = first_double_quote.unwrap();
1967        if !quote_contents.ends_with('"') {
1968            return Err(UnclosedQuotesError(quote_contents));
1969        }
1970        Ok(QuoteStripped(quote_contents.trim_end_matches("\"")))
1971    } else if first_single_quote.is_some() {
1972        let quote_contents = first_single_quote.unwrap();
1973        if!quote_contents.ends_with('\'') {
1974            return Err(UnclosedQuotesError(input));
1975        }
1976        Ok(QuoteStripped(quote_contents.trim_end_matches("'")))
1977    } else {
1978        Err(UnclosedQuotesError(input))
1979    }
1980}
1981
1982#[derive(Clone, PartialEq)]
1983pub enum CssGradientStopParseError<'a> {
1984    Error(&'a str),
1985    Percentage(PercentageParseError),
1986    ColorParseError(CssColorParseError<'a>),
1987}
1988
1989impl_debug_as_display!(CssGradientStopParseError<'a>);
1990impl_display!{ CssGradientStopParseError<'a>, {
1991    Error(e) => e,
1992    Percentage(e) => format!("Failed to parse offset percentage: {}", e),
1993    ColorParseError(e) => format!("{}", e),
1994}}
1995
1996impl_from!(CssColorParseError<'a>, CssGradientStopParseError::ColorParseError);
1997
1998
1999// parses "red" , "red 5%"
2000pub fn parse_gradient_stop<'a>(input: &'a str)
2001-> Result<GradientStopPre, CssGradientStopParseError<'a>>
2002{
2003    use self::CssGradientStopParseError::*;
2004
2005    let input = input.trim();
2006
2007    // Color functions such as "rgba(...)" can contain spaces, so we parse right-to-left.
2008    let (color_str, percentage_str) = match (input.rfind(')'), input.rfind(char::is_whitespace)) {
2009        (Some(closing_brace), None) if closing_brace < input.len() - 1 => {
2010            // percentage after closing brace, eg. "rgb(...)50%"
2011            (&input[..=closing_brace], Some(&input[(closing_brace + 1)..]))
2012        },
2013        (None, Some(last_ws)) => {
2014            // percentage after last whitespace, eg. "... 50%"
2015            (&input[..=last_ws], Some(&input[(last_ws + 1)..]))
2016        }
2017        (Some(closing_brace), Some(last_ws)) if closing_brace < last_ws => {
2018            // percentage after last whitespace, eg. "... 50%"
2019            (&input[..=last_ws], Some(&input[(last_ws + 1)..]))
2020        },
2021        _ => {
2022            // no percentage
2023            (input, None)
2024        },
2025    };
2026
2027    let color = parse_css_color(color_str)?;
2028    let offset = match percentage_str {
2029        None => None,
2030        Some(s) => Some(parse_percentage(s).map_err(|e| Percentage(e))?)
2031    };
2032
2033    Ok(GradientStopPre { offset, color: color })
2034}
2035
2036// parses "5%" -> 5
2037pub fn parse_percentage(input: &str)
2038-> Result<PercentageValue, PercentageParseError>
2039{
2040    let percent_location = input.rfind('%').ok_or(PercentageParseError::NoPercentSign)?;
2041    let input = &input[..percent_location];
2042    Ok(PercentageValue::new(input.parse::<f32>()?))
2043}
2044
2045#[derive(Debug, Clone, PartialEq)]
2046pub enum CssDirectionParseError<'a> {
2047    Error(&'a str),
2048    InvalidArguments(&'a str),
2049    ParseFloat(ParseFloatError),
2050    CornerError(CssDirectionCornerParseError<'a>),
2051}
2052
2053impl_display!{CssDirectionParseError<'a>, {
2054    Error(e) => e,
2055    InvalidArguments(val) => format!("Invalid arguments: \"{}\"", val),
2056    ParseFloat(e) => format!("Invalid value: {}", e),
2057    CornerError(e) => format!("Invalid corner value: {}", e),
2058}}
2059
2060impl<'a> From<ParseFloatError> for CssDirectionParseError<'a> {
2061    fn from(e: ParseFloatError) -> Self {
2062        CssDirectionParseError::ParseFloat(e)
2063    }
2064}
2065
2066impl<'a> From<CssDirectionCornerParseError<'a>> for CssDirectionParseError<'a> {
2067    fn from(e: CssDirectionCornerParseError<'a>) -> Self {
2068        CssDirectionParseError::CornerError(e)
2069    }
2070}
2071
2072/// Parses an `direction` such as `"50deg"` or `"to right bottom"` (in the context of gradients)
2073///
2074/// # Example
2075///
2076/// ```rust
2077/// # extern crate azul_css;
2078/// # extern crate azul_css_parser;
2079/// # use azul_css_parser::parse_direction;
2080/// # use azul_css::{Direction, FloatValue};
2081/// use azul_css::DirectionCorner::*;
2082///
2083/// assert_eq!(parse_direction("to right bottom"), Ok(Direction::FromTo(TopLeft, BottomRight)));
2084/// assert_eq!(parse_direction("to right"), Ok(Direction::FromTo(Left, Right)));
2085/// assert_eq!(parse_direction("50deg"), Ok(Direction::Angle(FloatValue::new(50.0))));
2086/// ```
2087pub fn parse_direction<'a>(input: &'a str)
2088-> Result<Direction, CssDirectionParseError<'a>>
2089{
2090    use std::f32::consts::PI;
2091
2092    let input_iter = input.split_whitespace();
2093    let count = input_iter.clone().count();
2094    let mut first_input_iter = input_iter.clone();
2095    // "50deg" | "to" | "right"
2096    let first_input = first_input_iter.next().ok_or(CssDirectionParseError::Error(input))?;
2097
2098    let deg = {
2099        if first_input.ends_with("grad") {
2100            first_input.split("grad").next().unwrap().parse::<f32>()? / 400.0 * 360.0
2101        } else if first_input.ends_with("rad") {
2102            first_input.split("rad").next().unwrap().parse::<f32>()? * 180.0 / PI
2103        } else if first_input.ends_with("deg") || first_input.parse::<f32>().is_ok() {
2104            first_input.split("deg").next().unwrap().parse::<f32>()?
2105        } else if let Ok(angle) = first_input.parse::<f32>() {
2106            angle
2107        }
2108        else {
2109            // if we get here, the input is definitely not an angle
2110
2111            if first_input != "to" {
2112                return Err(CssDirectionParseError::InvalidArguments(input));
2113            }
2114
2115            let second_input = first_input_iter.next().ok_or(CssDirectionParseError::Error(input))?;
2116            let end = parse_direction_corner(second_input)?;
2117
2118            return match count {
2119                2 => {
2120                    // "to right"
2121                    let start = end.opposite();
2122                    Ok(Direction::FromTo(start, end))
2123                },
2124                3 => {
2125                    // "to bottom right"
2126                    let beginning = end;
2127                    let third_input = first_input_iter.next().ok_or(CssDirectionParseError::Error(input))?;
2128                    let new_end = parse_direction_corner(third_input)?;
2129                    // "Bottom, Right" -> "BottomRight"
2130                    let new_end = beginning.combine(&new_end).ok_or(CssDirectionParseError::Error(input))?;
2131                    let start = new_end.opposite();
2132                    Ok(Direction::FromTo(start, new_end))
2133                },
2134                _ => { Err(CssDirectionParseError::InvalidArguments(input)) }
2135            };
2136        }
2137    };
2138
2139    // clamp the degree to 360 (so 410deg = 50deg)
2140    let mut deg = deg % 360.0;
2141    if deg < 0.0 {
2142        deg = 360.0 + deg;
2143    }
2144
2145    // now deg is in the range of +0..+360
2146    debug_assert!(deg >= 0.0 && deg <= 360.0);
2147
2148    return Ok(Direction::Angle(FloatValue::new(deg)));
2149}
2150
2151#[derive(Debug, Copy, Clone, PartialEq)]
2152pub enum CssDirectionCornerParseError<'a> {
2153    InvalidDirection(&'a str),
2154}
2155
2156impl_display!{ CssDirectionCornerParseError<'a>, {
2157    InvalidDirection(val) => format!("Invalid direction: \"{}\"", val),
2158}}
2159
2160pub fn parse_direction_corner<'a>(input: &'a str)
2161-> Result<DirectionCorner, CssDirectionCornerParseError<'a>>
2162{
2163    match input {
2164        "right" => Ok(DirectionCorner::Right),
2165        "left" => Ok(DirectionCorner::Left),
2166        "top" => Ok(DirectionCorner::Top),
2167        "bottom" => Ok(DirectionCorner::Bottom),
2168        _ => { Err(CssDirectionCornerParseError::InvalidDirection(input))}
2169    }
2170}
2171
2172#[derive(Debug, PartialEq, Copy, Clone)]
2173pub enum CssShapeParseError<'a> {
2174    ShapeErr(InvalidValueErr<'a>),
2175}
2176
2177impl_display!{CssShapeParseError<'a>, {
2178    ShapeErr(e) => format!("\"{}\"", e.0),
2179}}
2180
2181typed_pixel_value_parser!(parse_style_letter_spacing, StyleLetterSpacing);
2182typed_pixel_value_parser!(parse_style_word_spacing, StyleWordSpacing);
2183
2184typed_pixel_value_parser!(parse_layout_width, LayoutWidth);
2185typed_pixel_value_parser!(parse_layout_height, LayoutHeight);
2186
2187typed_pixel_value_parser!(parse_layout_min_height, LayoutMinHeight);
2188typed_pixel_value_parser!(parse_layout_min_width, LayoutMinWidth);
2189typed_pixel_value_parser!(parse_layout_max_width, LayoutMaxWidth);
2190typed_pixel_value_parser!(parse_layout_max_height, LayoutMaxHeight);
2191
2192typed_pixel_value_parser!(parse_layout_top, LayoutTop);
2193typed_pixel_value_parser!(parse_layout_bottom, LayoutBottom);
2194typed_pixel_value_parser!(parse_layout_right, LayoutRight);
2195typed_pixel_value_parser!(parse_layout_left, LayoutLeft);
2196
2197typed_pixel_value_parser!(parse_layout_margin_top, LayoutMarginTop);
2198typed_pixel_value_parser!(parse_layout_margin_bottom, LayoutMarginBottom);
2199typed_pixel_value_parser!(parse_layout_margin_right, LayoutMarginRight);
2200typed_pixel_value_parser!(parse_layout_margin_left, LayoutMarginLeft);
2201
2202typed_pixel_value_parser!(parse_layout_padding_top, LayoutPaddingTop);
2203typed_pixel_value_parser!(parse_layout_padding_bottom, LayoutPaddingBottom);
2204typed_pixel_value_parser!(parse_layout_padding_right, LayoutPaddingRight);
2205typed_pixel_value_parser!(parse_layout_padding_left, LayoutPaddingLeft);
2206
2207typed_pixel_value_parser!(parse_style_border_top_left_radius, StyleBorderTopLeftRadius);
2208typed_pixel_value_parser!(parse_style_border_bottom_left_radius, StyleBorderBottomLeftRadius);
2209typed_pixel_value_parser!(parse_style_border_top_right_radius, StyleBorderTopRightRadius);
2210typed_pixel_value_parser!(parse_style_border_bottom_right_radius, StyleBorderBottomRightRadius);
2211
2212typed_pixel_value_parser!(parse_style_border_top_width, StyleBorderTopWidth);
2213typed_pixel_value_parser!(parse_style_border_bottom_width, StyleBorderBottomWidth);
2214typed_pixel_value_parser!(parse_style_border_right_width, StyleBorderRightWidth);
2215typed_pixel_value_parser!(parse_style_border_left_width, StyleBorderLeftWidth);
2216
2217#[derive(Debug, Clone, PartialEq)]
2218pub enum FlexGrowParseError<'a> {
2219    ParseFloat(ParseFloatError, &'a str),
2220}
2221
2222impl_display!{FlexGrowParseError<'a>, {
2223    ParseFloat(e, orig_str) => format!("flex-grow: Could not parse floating-point value: \"{}\" - Error: \"{}\"", orig_str, e),
2224}}
2225
2226pub fn parse_layout_flex_grow<'a>(input: &'a str) -> Result<LayoutFlexGrow, FlexGrowParseError<'a>> {
2227    match parse_float_value(input) {
2228        Ok(o) => Ok(LayoutFlexGrow(o)),
2229        Err(e) => Err(FlexGrowParseError::ParseFloat(e, input)),
2230    }
2231}
2232
2233#[derive(Debug, Clone, PartialEq)]
2234pub enum FlexShrinkParseError<'a> {
2235    ParseFloat(ParseFloatError, &'a str),
2236}
2237
2238impl_display!{FlexShrinkParseError<'a>, {
2239    ParseFloat(e, orig_str) => format!("flex-shrink: Could not parse floating-point value: \"{}\" - Error: \"{}\"", orig_str, e),
2240}}
2241
2242pub fn parse_layout_flex_shrink<'a>(input: &'a str) -> Result<LayoutFlexShrink, FlexShrinkParseError<'a>> {
2243    match parse_float_value(input) {
2244        Ok(o) => Ok(LayoutFlexShrink(o)),
2245        Err(e) => Err(FlexShrinkParseError::ParseFloat(e, input)),
2246    }
2247}
2248
2249pub fn parse_style_tab_width(input: &str)
2250-> Result<StyleTabWidth, PercentageParseError>
2251{
2252    parse_percentage_value(input).and_then(|e| Ok(StyleTabWidth(e)))
2253}
2254
2255pub fn parse_style_line_height(input: &str)
2256-> Result<StyleLineHeight, PercentageParseError>
2257{
2258    parse_percentage_value(input).and_then(|e| Ok(StyleLineHeight(e)))
2259}
2260
2261typed_pixel_value_parser!(parse_style_font_size, StyleFontSize);
2262
2263#[derive(Debug, PartialEq, Copy, Clone)]
2264pub enum CssStyleFontFamilyParseError<'a> {
2265    InvalidStyleFontFamily(&'a str),
2266    UnclosedQuotes(&'a str),
2267}
2268
2269impl_display!{CssStyleFontFamilyParseError<'a>, {
2270    InvalidStyleFontFamily(val) => format!("Invalid font-family: \"{}\"", val),
2271    UnclosedQuotes(val) => format!("Unclosed quotes: \"{}\"", val),
2272}}
2273
2274impl<'a> From<UnclosedQuotesError<'a>> for CssStyleFontFamilyParseError<'a> {
2275    fn from(err: UnclosedQuotesError<'a>) -> Self {
2276        CssStyleFontFamilyParseError::UnclosedQuotes(err.0)
2277    }
2278}
2279
2280/// Parses a `StyleFontFamily` declaration from a `&str`
2281///
2282/// # Example
2283///
2284/// ```rust
2285/// # extern crate azul_css;
2286/// # extern crate azul_css_parser;
2287/// # use azul_css_parser::parse_style_font_family;
2288/// # use azul_css::{StyleFontFamily, FontId};
2289/// let input = "\"Helvetica\", 'Arial', Times New Roman";
2290/// let fonts = vec![
2291///     FontId("Helvetica".into()),
2292///     FontId("Arial".into()),
2293///     FontId("Times New Roman".into())
2294/// ];
2295///
2296/// assert_eq!(parse_style_font_family(input), Ok(StyleFontFamily { fonts }));
2297/// ```
2298pub fn parse_style_font_family<'a>(input: &'a str) -> Result<StyleFontFamily, CssStyleFontFamilyParseError<'a>> {
2299    let multiple_fonts = input.split(',');
2300    let mut fonts = Vec::with_capacity(1);
2301
2302    for font in multiple_fonts {
2303        let font = font.trim();
2304        let font = font.trim_matches('\'');
2305        let font = font.trim_matches('\"');
2306        let font = font.trim();
2307        fonts.push(FontId(font.into()));
2308    }
2309
2310    Ok(StyleFontFamily {
2311        fonts: fonts,
2312    })
2313}
2314
2315#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd)]
2316pub enum ParenthesisParseError<'a> {
2317    UnclosedBraces,
2318    NoOpeningBraceFound,
2319    NoClosingBraceFound,
2320    StopWordNotFound(&'a str),
2321    EmptyInput,
2322}
2323
2324impl_display!{ ParenthesisParseError<'a>, {
2325    UnclosedBraces => format!("Unclosed parenthesis"),
2326    NoOpeningBraceFound => format!("Expected value in parenthesis (missing \"(\")"),
2327    NoClosingBraceFound => format!("Missing closing parenthesis (missing \")\")"),
2328    StopWordNotFound(e) => format!("Stopword not found, found: \"{}\"", e),
2329    EmptyInput => format!("Empty parenthesis"),
2330}}
2331
2332/// Checks wheter a given input is enclosed in parentheses, prefixed
2333/// by a certain number of stopwords.
2334///
2335/// On success, returns what the stopword was + the string inside the braces
2336/// on failure returns None.
2337///
2338/// ```rust
2339/// # use azul_css_parser::parse_parentheses;
2340/// # use azul_css_parser::ParenthesisParseError::*;
2341/// // Search for the nearest "abc()" brace
2342/// assert_eq!(parse_parentheses("abc(def(g))", &["abc"]), Ok(("abc", "def(g)")));
2343/// assert_eq!(parse_parentheses("abc(def(g))", &["def"]), Err(StopWordNotFound("abc")));
2344/// assert_eq!(parse_parentheses("def(ghi(j))", &["def"]), Ok(("def", "ghi(j)")));
2345/// assert_eq!(parse_parentheses("abc(def(g))", &["abc", "def"]), Ok(("abc", "def(g)")));
2346/// ```
2347pub fn parse_parentheses<'a>(
2348    input: &'a str,
2349    stopwords: &[&'static str])
2350-> Result<(&'static str, &'a str), ParenthesisParseError<'a>>
2351{
2352    use self::ParenthesisParseError::*;
2353
2354    let input = input.trim();
2355    if input.is_empty() {
2356        return Err(EmptyInput);
2357    }
2358
2359    let first_open_brace = input.find('(').ok_or(NoOpeningBraceFound)?;
2360    let found_stopword = &input[..first_open_brace];
2361
2362    // CSS does not allow for space between the ( and the stopword, so no .trim() here
2363    let mut validated_stopword = None;
2364    for stopword in stopwords {
2365        if found_stopword == *stopword {
2366            validated_stopword = Some(stopword);
2367            break;
2368        }
2369    }
2370
2371    let validated_stopword = validated_stopword.ok_or(StopWordNotFound(found_stopword))?;
2372    let last_closing_brace = input.rfind(')').ok_or(NoClosingBraceFound)?;
2373
2374    Ok((validated_stopword, &input[(first_open_brace + 1)..last_closing_brace]))
2375}
2376
2377multi_type_parser!(parse_style_border_style, BorderStyle,
2378    ["none", None],
2379    ["solid", Solid],
2380    ["double", Double],
2381    ["dotted", Dotted],
2382    ["dashed", Dashed],
2383    ["hidden", Hidden],
2384    ["groove", Groove],
2385    ["ridge", Ridge],
2386    ["inset", Inset],
2387    ["outset", Outset]);
2388
2389multi_type_parser!(parse_style_cursor, StyleCursor,
2390                    ["alias", Alias],
2391                    ["all-scroll", AllScroll],
2392                    ["cell", Cell],
2393                    ["col-resize", ColResize],
2394                    ["context-menu", ContextMenu],
2395                    ["copy", Copy],
2396                    ["crosshair", Crosshair],
2397                    ["default", Default],
2398                    ["e-resize", EResize],
2399                    ["ew-resize", EwResize],
2400                    ["grab", Grab],
2401                    ["grabbing", Grabbing],
2402                    ["help", Help],
2403                    ["move", Move],
2404                    ["n-resize", NResize],
2405                    ["ns-resize", NsResize],
2406                    ["nesw-resize", NeswResize],
2407                    ["nwse-resize", NwseResize],
2408                    ["pointer", Pointer],
2409                    ["progress", Progress],
2410                    ["row-resize", RowResize],
2411                    ["s-resize", SResize],
2412                    ["se-resize", SeResize],
2413                    ["text", Text],
2414                    ["unset", Unset],
2415                    ["vertical-text", VerticalText],
2416                    ["w-resize", WResize],
2417                    ["wait", Wait],
2418                    ["zoom-in", ZoomIn],
2419                    ["zoom-out", ZoomOut]);
2420
2421multi_type_parser!(parse_style_background_size, StyleBackgroundSize,
2422                    ["contain", Contain],
2423                    ["cover", Cover]);
2424
2425multi_type_parser!(parse_style_background_repeat, StyleBackgroundRepeat,
2426                    ["no-repeat", NoRepeat],
2427                    ["repeat", Repeat],
2428                    ["repeat-x", RepeatX],
2429                    ["repeat-y", RepeatY]);
2430
2431multi_type_parser!(parse_layout_display, LayoutDisplay,
2432                    ["flex", Flex],
2433                    ["block", Block],
2434                    ["inline-block", InlineBlock]);
2435
2436multi_type_parser!(parse_layout_float, LayoutFloat,
2437                    ["left", Left],
2438                    ["right", Right]);
2439
2440multi_type_parser!(parse_layout_box_sizing, LayoutBoxSizing,
2441    ["content-box", ContentBox],
2442    ["border-box", BorderBox]);
2443
2444multi_type_parser!(parse_layout_direction, LayoutDirection,
2445                    ["row", Row],
2446                    ["row-reverse", RowReverse],
2447                    ["column", Column],
2448                    ["column-reverse", ColumnReverse]);
2449
2450multi_type_parser!(parse_layout_wrap, LayoutWrap,
2451                    ["wrap", Wrap],
2452                    ["nowrap", NoWrap]);
2453
2454multi_type_parser!(parse_layout_justify_content, LayoutJustifyContent,
2455                    ["flex-start", Start],
2456                    ["flex-end", End],
2457                    ["center", Center],
2458                    ["space-between", SpaceBetween],
2459                    ["space-around", SpaceAround],
2460                    ["space-evenly", SpaceEvenly]);
2461
2462multi_type_parser!(parse_layout_align_items, LayoutAlignItems,
2463                    ["flex-start", Start],
2464                    ["flex-end", End],
2465                    ["stretch", Stretch],
2466                    ["center", Center]);
2467
2468multi_type_parser!(parse_layout_align_content, LayoutAlignContent,
2469                    ["flex-start", Start],
2470                    ["flex-end", End],
2471                    ["stretch", Stretch],
2472                    ["center", Center],
2473                    ["space-between", SpaceBetween],
2474                    ["space-around", SpaceAround]);
2475
2476multi_type_parser!(parse_shape, Shape,
2477                    ["circle", Circle],
2478                    ["ellipse", Ellipse]);
2479
2480multi_type_parser!(parse_layout_position, LayoutPosition,
2481                    ["static", Static],
2482                    ["fixed", Fixed],
2483                    ["absolute", Absolute],
2484                    ["relative", Relative]);
2485
2486multi_type_parser!(parse_layout_overflow, Overflow,
2487                    ["auto", Auto],
2488                    ["scroll", Scroll],
2489                    ["visible", Visible],
2490                    ["hidden", Hidden]);
2491
2492multi_type_parser!(parse_layout_text_align, StyleTextAlignmentHorz,
2493                    ["center", Center],
2494                    ["left", Left],
2495                    ["right", Right]);
2496
2497#[cfg(test)]
2498mod css_tests {
2499    use super::*;
2500
2501
2502    #[test]
2503    fn test_parse_box_shadow_1() {
2504        assert_eq!(
2505            parse_style_box_shadow("none"),
2506            Err(CssShadowParseError::TooManyComponents("none"))
2507        );
2508    }
2509
2510    #[test]
2511    fn test_parse_box_shadow_2() {
2512        assert_eq!(
2513            parse_style_box_shadow("5px 10px"),
2514            Ok(BoxShadowPreDisplayItem {
2515                offset: [
2516                    PixelValueNoPercent(PixelValue::px(5.0)),
2517                    PixelValueNoPercent(PixelValue::px(10.0))
2518                ],
2519                color: ColorU {
2520                    r: 0,
2521                    g: 0,
2522                    b: 0,
2523                    a: 255
2524                },
2525                blur_radius: PixelValueNoPercent(PixelValue::px(0.0)),
2526                spread_radius: PixelValueNoPercent(PixelValue::px(0.0)),
2527                clip_mode: BoxShadowClipMode::Outset,
2528            })
2529        );
2530    }
2531
2532    #[test]
2533    fn test_parse_box_shadow_3() {
2534        assert_eq!(
2535            parse_style_box_shadow("5px 10px #888888"),
2536            Ok(BoxShadowPreDisplayItem {
2537                offset: [
2538                    PixelValueNoPercent(PixelValue::px(5.0)),
2539                    PixelValueNoPercent(PixelValue::px(10.0))
2540                ],
2541                color: ColorU {
2542                    r: 136,
2543                    g: 136,
2544                    b: 136,
2545                    a: 255
2546                },
2547                blur_radius: PixelValueNoPercent(PixelValue::px(0.0)),
2548                spread_radius: PixelValueNoPercent(PixelValue::px(0.0)),
2549                clip_mode: BoxShadowClipMode::Outset,
2550            })
2551        );
2552    }
2553
2554    #[test]
2555    fn test_parse_box_shadow_4() {
2556        assert_eq!(
2557            parse_style_box_shadow("5px 10px inset"),
2558            Ok(BoxShadowPreDisplayItem {
2559                offset: [
2560                    PixelValueNoPercent(PixelValue::px(5.0)),
2561                    PixelValueNoPercent(PixelValue::px(10.0))
2562                ],
2563                color: ColorU {
2564                    r: 0,
2565                    g: 0,
2566                    b: 0,
2567                    a: 255
2568                },
2569                blur_radius: PixelValueNoPercent(PixelValue::px(0.0)),
2570                spread_radius: PixelValueNoPercent(PixelValue::px(0.0)),
2571                clip_mode: BoxShadowClipMode::Inset,
2572            })
2573        );
2574    }
2575
2576    #[test]
2577    fn test_parse_box_shadow_5() {
2578        assert_eq!(
2579            parse_style_box_shadow("5px 10px outset"),
2580            Ok(BoxShadowPreDisplayItem {
2581                offset: [
2582                    PixelValueNoPercent(PixelValue::px(5.0)),
2583                    PixelValueNoPercent(PixelValue::px(10.0))
2584                ],
2585                color: ColorU {
2586                    r: 0,
2587                    g: 0,
2588                    b: 0,
2589                    a: 255
2590                },
2591                blur_radius: PixelValueNoPercent(PixelValue::px(0.0)),
2592                spread_radius: PixelValueNoPercent(PixelValue::px(0.0)),
2593                clip_mode: BoxShadowClipMode::Outset,
2594            })
2595        );
2596    }
2597
2598    #[test]
2599    fn test_parse_box_shadow_6() {
2600        assert_eq!(
2601            parse_style_box_shadow("5px 10px 5px #888888"),
2602            Ok(BoxShadowPreDisplayItem {
2603                offset: [
2604                    PixelValueNoPercent(PixelValue::px(5.0)),
2605                    PixelValueNoPercent(PixelValue::px(10.0))
2606                ],
2607                color: ColorU {
2608                    r: 136,
2609                    g: 136,
2610                    b: 136,
2611                    a: 255
2612                },
2613                blur_radius: PixelValueNoPercent(PixelValue::px(5.0)),
2614                spread_radius: PixelValueNoPercent(PixelValue::px(0.0)),
2615                clip_mode: BoxShadowClipMode::Outset,
2616            })
2617        );
2618    }
2619
2620    #[test]
2621    fn test_parse_box_shadow_7() {
2622        assert_eq!(
2623            parse_style_box_shadow("5px 10px #888888 inset"),
2624            Ok(BoxShadowPreDisplayItem {
2625                offset: [
2626                    PixelValueNoPercent(PixelValue::px(5.0)),
2627                    PixelValueNoPercent(PixelValue::px(10.0))
2628                ],
2629                color: ColorU {
2630                    r: 136,
2631                    g: 136,
2632                    b: 136,
2633                    a: 255
2634                },
2635                blur_radius: PixelValueNoPercent(PixelValue::px(0.0)),
2636                spread_radius: PixelValueNoPercent(PixelValue::px(0.0)),
2637                clip_mode: BoxShadowClipMode::Inset,
2638            })
2639        );
2640    }
2641
2642    #[test]
2643    fn test_parse_box_shadow_8() {
2644        assert_eq!(
2645            parse_style_box_shadow("5px 10px 5px #888888 inset"),
2646            Ok(BoxShadowPreDisplayItem {
2647                offset: [
2648                    PixelValueNoPercent(PixelValue::px(5.0)),
2649                    PixelValueNoPercent(PixelValue::px(10.0))
2650                ],
2651                color: ColorU {
2652                    r: 136,
2653                    g: 136,
2654                    b: 136,
2655                    a: 255
2656                },
2657                blur_radius: PixelValueNoPercent(PixelValue::px(5.0)),
2658                spread_radius: PixelValueNoPercent(PixelValue::px(0.0)),
2659                clip_mode: BoxShadowClipMode::Inset,
2660            })
2661        );
2662    }
2663
2664    #[test]
2665    fn test_parse_box_shadow_9() {
2666        assert_eq!(
2667            parse_style_box_shadow("5px 10px 5px 10px #888888"),
2668            Ok(BoxShadowPreDisplayItem {
2669                offset: [
2670                    PixelValueNoPercent(PixelValue::px(5.0)),
2671                    PixelValueNoPercent(PixelValue::px(10.0)),
2672                ],
2673                color: ColorU {
2674                    r: 136,
2675                    g: 136,
2676                    b: 136,
2677                    a: 255
2678                },
2679                blur_radius: PixelValueNoPercent(PixelValue::px(5.0)),
2680                spread_radius: PixelValueNoPercent(PixelValue::px(10.0)),
2681                clip_mode: BoxShadowClipMode::Outset,
2682            })
2683        );
2684    }
2685
2686    #[test]
2687    fn test_parse_box_shadow_10() {
2688        assert_eq!(
2689            parse_style_box_shadow("5px 10px 5px 10px #888888 inset"),
2690            Ok(BoxShadowPreDisplayItem {
2691                offset: [
2692                    PixelValueNoPercent(PixelValue::px(5.0)),
2693                    PixelValueNoPercent(PixelValue::px(10.0))
2694                ],
2695                color: ColorU {
2696                    r: 136,
2697                    g: 136,
2698                    b: 136,
2699                    a: 255
2700                },
2701                blur_radius: PixelValueNoPercent(PixelValue::px(5.0)),
2702                spread_radius: PixelValueNoPercent(PixelValue::px(10.0)),
2703                clip_mode: BoxShadowClipMode::Inset,
2704            })
2705        );
2706    }
2707
2708
2709    #[test]
2710    fn test_parse_css_border_1() {
2711        assert_eq!(
2712            parse_style_border("5px solid red"),
2713            Ok(StyleBorderSide {
2714                border_width: PixelValue::px(5.0),
2715                border_style: BorderStyle::Solid,
2716                border_color: ColorU { r: 255, g: 0, b: 0, a: 255 },
2717            })
2718        );
2719    }
2720
2721    #[test]
2722    fn test_parse_css_border_2() {
2723        assert_eq!(
2724            parse_style_border("double"),
2725            Ok(StyleBorderSide {
2726                border_width: PixelValue::px(3.0),
2727                border_style: BorderStyle::Double,
2728                border_color: ColorU { r: 0, g: 0, b: 0, a: 255 },
2729            })
2730        );
2731    }
2732
2733    #[test]
2734    fn test_parse_css_border_3() {
2735        assert_eq!(
2736            parse_style_border("1px solid rgb(51, 153, 255)"),
2737            Ok(StyleBorderSide {
2738                border_width: PixelValue::px(1.0),
2739                border_style: BorderStyle::Solid,
2740                border_color: ColorU { r: 51, g: 153, b: 255, a: 255 },
2741            })
2742        );
2743    }
2744
2745    #[test]
2746    fn test_parse_linear_gradient_1() {
2747        assert_eq!(parse_style_background_content("linear-gradient(red, yellow)"),
2748            Ok(StyleBackgroundContent::LinearGradient(LinearGradient {
2749                direction: Direction::FromTo(DirectionCorner::Top, DirectionCorner::Bottom),
2750                extend_mode: ExtendMode::Clamp,
2751                stops: vec![GradientStopPre {
2752                    offset: Some(PercentageValue::new(0.0)),
2753                    color: ColorU { r: 255, g: 0, b: 0, a: 255 },
2754                },
2755                GradientStopPre {
2756                    offset: Some(PercentageValue::new(100.0)),
2757                    color: ColorU { r: 255, g: 255, b: 0, a: 255 },
2758                }],
2759            })));
2760    }
2761
2762    #[test]
2763    fn test_parse_linear_gradient_2() {
2764        assert_eq!(parse_style_background_content("linear-gradient(red, lime, blue, yellow)"),
2765            Ok(StyleBackgroundContent::LinearGradient(LinearGradient {
2766                direction: Direction::FromTo(DirectionCorner::Top, DirectionCorner::Bottom),
2767                extend_mode: ExtendMode::Clamp,
2768                stops: vec![GradientStopPre {
2769                    offset: Some(PercentageValue::new(0.0)),
2770                    color: ColorU { r: 255, g: 0, b: 0, a: 255 },
2771                },
2772                GradientStopPre {
2773                    offset: Some(PercentageValue::new(33.333332)),
2774                    color: ColorU { r: 0, g: 255, b: 0, a: 255 },
2775                },
2776                GradientStopPre {
2777                    offset: Some(PercentageValue::new(66.666664)),
2778                    color: ColorU { r: 0, g: 0, b: 255, a: 255 },
2779                },
2780                GradientStopPre {
2781                    offset: Some(PercentageValue::new(99.9999)), // note: not 100%, but close enough
2782                    color: ColorU { r: 255, g: 255, b: 0, a: 255 },
2783                }],
2784        })));
2785    }
2786
2787    #[test]
2788    fn test_parse_linear_gradient_3() {
2789        assert_eq!(parse_style_background_content("repeating-linear-gradient(50deg, blue, yellow, #00FF00)"),
2790            Ok(StyleBackgroundContent::LinearGradient(LinearGradient {
2791                direction: Direction::Angle(50.0.into()),
2792                extend_mode: ExtendMode::Repeat,
2793                stops: vec![
2794                GradientStopPre {
2795                    offset: Some(PercentageValue::new(0.0)),
2796                    color: ColorU { r: 0, g: 0, b: 255, a: 255 },
2797                },
2798                GradientStopPre {
2799                    offset: Some(PercentageValue::new(50.0)),
2800                    color: ColorU { r: 255, g: 255, b: 0, a: 255 },
2801                },
2802                GradientStopPre {
2803                    offset: Some(PercentageValue::new(100.0)),
2804                    color: ColorU { r: 0, g: 255, b: 0, a: 255 },
2805                }],
2806        })));
2807    }
2808
2809    #[test]
2810    fn test_parse_linear_gradient_4() {
2811        assert_eq!(parse_style_background_content("linear-gradient(to bottom right, red, yellow)"),
2812            Ok(StyleBackgroundContent::LinearGradient(LinearGradient {
2813                direction: Direction::FromTo(DirectionCorner::TopLeft, DirectionCorner::BottomRight),
2814                extend_mode: ExtendMode::Clamp,
2815                stops: vec![GradientStopPre {
2816                    offset: Some(PercentageValue::new(0.0)),
2817                    color: ColorU { r: 255, g: 0, b: 0, a: 255 },
2818                },
2819                GradientStopPre {
2820                    offset: Some(PercentageValue::new(100.0)),
2821                    color: ColorU { r: 255, g: 255, b: 0, a: 255 },
2822                }],
2823            })
2824        ));
2825    }
2826
2827    #[test]
2828    fn test_parse_linear_gradient_5() {
2829        assert_eq!(parse_style_background_content("linear-gradient(0.42rad, red, yellow)"),
2830            Ok(StyleBackgroundContent::LinearGradient(LinearGradient {
2831                direction: Direction::Angle(FloatValue::new(24.0642)),
2832                extend_mode: ExtendMode::Clamp,
2833                stops: vec![GradientStopPre {
2834                    offset: Some(PercentageValue::new(0.0)),
2835                    color: ColorU { r: 255, g: 0, b: 0, a: 255 },
2836                },
2837                GradientStopPre {
2838                    offset: Some(PercentageValue::new(100.0)),
2839                    color: ColorU { r: 255, g: 255, b: 0, a: 255 },
2840                }],
2841        })));
2842    }
2843
2844    #[test]
2845    fn test_parse_linear_gradient_6() {
2846        assert_eq!(parse_style_background_content("linear-gradient(12.93grad, red, yellow)"),
2847            Ok(StyleBackgroundContent::LinearGradient(LinearGradient {
2848                direction: Direction::Angle(FloatValue::new(11.637)),
2849                extend_mode: ExtendMode::Clamp,
2850                stops: vec![GradientStopPre {
2851                    offset: Some(PercentageValue::new(0.0)),
2852                    color: ColorU { r: 255, g: 0, b: 0, a: 255 },
2853                },
2854                GradientStopPre {
2855                    offset: Some(PercentageValue::new(100.0)),
2856                    color: ColorU { r: 255, g: 255, b: 0, a: 255 },
2857                }],
2858        })));
2859    }
2860
2861    #[test]
2862    fn test_parse_linear_gradient_7() {
2863        assert_eq!(parse_style_background_content("linear-gradient(to right, rgba(255,0, 0,1) 0%,rgba(0,0,0, 0) 100%)"),
2864            Ok(StyleBackgroundContent::LinearGradient(LinearGradient {
2865                direction: Direction::FromTo(DirectionCorner::Left, DirectionCorner::Right),
2866                extend_mode: ExtendMode::Clamp,
2867                stops: vec![GradientStopPre {
2868                    offset: Some(PercentageValue::new(0.0)),
2869                    color: ColorU { r: 255, g: 0, b: 0, a: 255 },
2870                },
2871                GradientStopPre {
2872                    offset: Some(PercentageValue::new(100.0)),
2873                    color: ColorU { r: 0, g: 0, b: 0, a: 0 },
2874                }],
2875            })
2876        ));
2877    }
2878
2879    #[test]
2880    fn test_parse_linear_gradient_8() {
2881        assert_eq!(parse_style_background_content("linear-gradient(to bottom, rgb(255,0, 0),rgb(0,0,0))"),
2882            Ok(StyleBackgroundContent::LinearGradient(LinearGradient {
2883                direction: Direction::FromTo(DirectionCorner::Top, DirectionCorner::Bottom),
2884                extend_mode: ExtendMode::Clamp,
2885                stops: vec![GradientStopPre {
2886                    offset: Some(PercentageValue::new(0.0)),
2887                    color: ColorU { r: 255, g: 0, b: 0, a: 255 },
2888                },
2889                GradientStopPre {
2890                    offset: Some(PercentageValue::new(100.0)),
2891                    color: ColorU { r: 0, g: 0, b: 0, a: 255 },
2892                }],
2893            })
2894        ));
2895    }
2896
2897    #[test]
2898    fn test_parse_linear_gradient_9() {
2899        assert_eq!(parse_style_background_content("linear-gradient(10deg, rgb(10, 30, 20), yellow)"),
2900            Ok(StyleBackgroundContent::LinearGradient(LinearGradient {
2901                direction: Direction::Angle(FloatValue::new(10.0)),
2902                extend_mode: ExtendMode::Clamp,
2903                stops: vec![GradientStopPre {
2904                    offset: Some(PercentageValue::new(0.0)),
2905                    color: ColorU { r: 10, g: 30, b: 20, a: 255 },
2906                },
2907                GradientStopPre {
2908                    offset: Some(PercentageValue::new(100.0)),
2909                    color: ColorU { r: 255, g: 255, b: 0, a: 255 },
2910                }],
2911        })));
2912    }
2913
2914    #[test]
2915    fn test_parse_linear_gradient_10() {
2916        assert_eq!(parse_style_background_content("linear-gradient(50deg, rgba(10, 30, 20, 0.93), hsla(40deg, 80%, 30%, 0.1))"),
2917            Ok(StyleBackgroundContent::LinearGradient(LinearGradient {
2918                direction: Direction::Angle(FloatValue::new(50.0)),
2919                extend_mode: ExtendMode::Clamp,
2920                stops: vec![GradientStopPre {
2921                    offset: Some(PercentageValue::new(0.0)),
2922                    color: ColorU { r: 10, g: 30, b: 20, a: 238 },
2923                },
2924                GradientStopPre {
2925                    offset: Some(PercentageValue::new(100.0)),
2926                    color: ColorU { r: 138, g: 97, b: 15, a: 25 },
2927                }],
2928        })));
2929    }
2930
2931    #[test]
2932    fn test_parse_linear_gradient_11() {
2933        // wacky whitespace on purpose
2934        assert_eq!(parse_style_background_content("linear-gradient(to bottom,rgb(255,0, 0)0%, rgb( 0 , 255 , 0 ) 10% ,blue   100%  )"),
2935            Ok(StyleBackgroundContent::LinearGradient(LinearGradient {
2936                direction: Direction::FromTo(DirectionCorner::Top, DirectionCorner::Bottom),
2937                extend_mode: ExtendMode::Clamp,
2938                stops: vec![GradientStopPre {
2939                    offset: Some(PercentageValue::new(0.0)),
2940                    color: ColorU { r: 255, g: 0, b: 0, a: 255 },
2941                },
2942                GradientStopPre {
2943                    offset: Some(PercentageValue::new(10.0)),
2944                    color: ColorU { r: 0, g: 255, b: 0, a: 255 },
2945                },
2946                GradientStopPre {
2947                    offset: Some(PercentageValue::new(100.0)),
2948                    color: ColorU { r: 0, g: 0, b: 255, a: 255 },
2949                }],
2950            })
2951        ));
2952    }
2953
2954    #[test]
2955    fn test_parse_radial_gradient_1() {
2956        assert_eq!(parse_style_background_content("radial-gradient(circle, lime, blue, yellow)"),
2957            Ok(StyleBackgroundContent::RadialGradient(RadialGradient {
2958                shape: Shape::Circle,
2959                extend_mode: ExtendMode::Clamp,
2960                stops: vec![
2961                GradientStopPre {
2962                    offset: Some(PercentageValue::new(0.0)),
2963                    color: ColorU { r: 0, g: 255, b: 0, a: 255 },
2964                },
2965                GradientStopPre {
2966                    offset: Some(PercentageValue::new(50.0)),
2967                    color: ColorU { r: 0, g: 0, b: 255, a: 255 },
2968                },
2969                GradientStopPre {
2970                    offset: Some(PercentageValue::new(100.0)),
2971                    color: ColorU { r: 255, g: 255, b: 0, a: 255 },
2972                }],
2973        })));
2974    }
2975
2976    // This test currently fails, but it's not that important to fix right now
2977    /*
2978    #[test]
2979    fn test_parse_radial_gradient_2() {
2980        assert_eq!(parse_style_background_content("repeating-radial-gradient(circle, red 10%, blue 50%, lime, yellow)"),
2981            Ok(ParsedGradient::RadialGradient(RadialGradient {
2982                shape: Shape::Circle,
2983                extend_mode: ExtendMode::Repeat,
2984                stops: vec![
2985                GradientStopPre {
2986                    offset: Some(0.1),
2987                    color: ColorF { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
2988                },
2989                GradientStopPre {
2990                    offset: Some(0.5),
2991                    color: ColorF { r: 0.0, g: 0.0, b: 1.0, a: 1.0 },
2992                },
2993                GradientStopPre {
2994                    offset: Some(0.75),
2995                    color: ColorF { r: 0.0, g: 1.0, b: 0.0, a: 1.0 },
2996                },
2997                GradientStopPre {
2998                    offset: Some(1.0),
2999                    color: ColorF { r: 1.0, g: 1.0, b: 0.0, a: 1.0 },
3000                }],
3001        })));
3002    }
3003    */
3004
3005    #[test]
3006    fn test_parse_css_color_1() {
3007        assert_eq!(parse_css_color("#F0F8FF"), Ok(ColorU { r: 240, g: 248, b: 255, a: 255 }));
3008    }
3009
3010    #[test]
3011    fn test_parse_css_color_2() {
3012        assert_eq!(parse_css_color("#F0F8FF00"), Ok(ColorU { r: 240, g: 248, b: 255, a: 0 }));
3013    }
3014
3015    #[test]
3016    fn test_parse_css_color_3() {
3017        assert_eq!(parse_css_color("#EEE"), Ok(ColorU { r: 238, g: 238, b: 238, a: 255 }));
3018    }
3019
3020    #[test]
3021    fn test_parse_css_color_4() {
3022        assert_eq!(parse_css_color("rgb(192, 14, 12)"), Ok(ColorU { r: 192, g: 14, b: 12, a: 255 }));
3023    }
3024
3025    #[test]
3026    fn test_parse_css_color_5() {
3027        assert_eq!(parse_css_color("rgb(283, 8, 105)"), Err(CssColorParseError::IntValueParseErr("283".parse::<u8>().err().unwrap())));
3028    }
3029
3030    #[test]
3031    fn test_parse_css_color_6() {
3032        assert_eq!(parse_css_color("rgba(192, 14, 12, 80)"), Err(CssColorParseError::FloatValueOutOfRange(80.0)));
3033    }
3034
3035    #[test]
3036    fn test_parse_css_color_7() {
3037        assert_eq!(parse_css_color("rgba( 0,127,     255   , 0.25  )"), Ok(ColorU { r: 0, g: 127, b: 255, a: 64 }));
3038    }
3039
3040    #[test]
3041    fn test_parse_css_color_8() {
3042        assert_eq!(parse_css_color("rgba( 1 ,2,3, 1.0)"), Ok(ColorU { r: 1, g: 2, b: 3, a: 255 }));
3043    }
3044
3045    #[test]
3046    fn test_parse_css_color_9() {
3047        assert_eq!(parse_css_color("rgb("), Err(CssColorParseError::UnclosedColor("rgb(")));
3048    }
3049
3050    #[test]
3051    fn test_parse_css_color_10() {
3052        assert_eq!(parse_css_color("rgba("), Err(CssColorParseError::UnclosedColor("rgba(")));
3053    }
3054
3055    #[test]
3056    fn test_parse_css_color_11() {
3057        assert_eq!(parse_css_color("rgba(123, 36, 92, 0.375"), Err(CssColorParseError::UnclosedColor("rgba(123, 36, 92, 0.375")));
3058    }
3059
3060    #[test]
3061    fn test_parse_css_color_12() {
3062        assert_eq!(parse_css_color("rgb()"), Err(CssColorParseError::MissingColorComponent(CssColorComponent::Red)));
3063    }
3064
3065    #[test]
3066    fn test_parse_css_color_13() {
3067        assert_eq!(parse_css_color("rgb(10)"), Err(CssColorParseError::MissingColorComponent(CssColorComponent::Green)));
3068    }
3069
3070    #[test]
3071    fn test_parse_css_color_14() {
3072        assert_eq!(parse_css_color("rgb(20, 30)"), Err(CssColorParseError::MissingColorComponent(CssColorComponent::Blue)));
3073    }
3074
3075    #[test]
3076    fn test_parse_css_color_15() {
3077        assert_eq!(parse_css_color("rgb(30, 40,)"), Err(CssColorParseError::MissingColorComponent(CssColorComponent::Blue)));
3078    }
3079
3080    #[test]
3081    fn test_parse_css_color_16() {
3082        assert_eq!(parse_css_color("rgba(40, 50, 60)"), Err(CssColorParseError::MissingColorComponent(CssColorComponent::Alpha)));
3083    }
3084
3085    #[test]
3086    fn test_parse_css_color_17() {
3087        assert_eq!(parse_css_color("rgba(50, 60, 70, )"), Err(CssColorParseError::MissingColorComponent(CssColorComponent::Alpha)));
3088    }
3089
3090    #[test]
3091    fn test_parse_css_color_18() {
3092        assert_eq!(parse_css_color("hsl(0deg, 100%, 100%)"), Ok(ColorU { r: 255, g: 255, b: 255, a: 255 }));
3093    }
3094
3095    #[test]
3096    fn test_parse_css_color_19() {
3097        assert_eq!(parse_css_color("hsl(0deg, 100%, 50%)"), Ok(ColorU { r: 255, g: 0, b: 0, a: 255 }));
3098    }
3099
3100    #[test]
3101    fn test_parse_css_color_20() {
3102        assert_eq!(parse_css_color("hsl(170deg, 50%, 75%)"), Ok(ColorU { r: 160, g: 224, b: 213, a: 255 }));
3103    }
3104
3105    #[test]
3106    fn test_parse_css_color_21() {
3107        assert_eq!(parse_css_color("hsla(190deg, 50%, 75%, 1.0)"), Ok(ColorU { r: 160, g: 213, b: 224, a: 255 }));
3108    }
3109
3110    #[test]
3111    fn test_parse_css_color_22() {
3112        assert_eq!(parse_css_color("hsla(120deg, 0%, 25%, 0.25)"), Ok(ColorU { r: 64, g: 64, b: 64, a: 64 }));
3113    }
3114
3115    #[test]
3116    fn test_parse_css_color_23() {
3117        assert_eq!(parse_css_color("hsla(120deg, 0%, 0%, 0.5)"), Ok(ColorU { r: 0, g: 0, b: 0, a: 128 }));
3118    }
3119
3120    #[test]
3121    fn test_parse_css_color_24() {
3122        assert_eq!(parse_css_color("hsla(60.9deg, 80.3%, 40%, 0.5)"), Ok(ColorU { r: 182, g: 184, b: 20, a: 128 }));
3123    }
3124
3125    #[test]
3126    fn test_parse_css_color_25() {
3127        assert_eq!(parse_css_color("hsla(60.9rad, 80.3%, 40%, 0.5)"), Ok(ColorU { r: 45, g: 20, b: 184, a: 128 }));
3128    }
3129
3130    #[test]
3131    fn test_parse_css_color_26() {
3132        assert_eq!(parse_css_color("hsla(60.9grad, 80.3%, 40%, 0.5)"), Ok(ColorU { r: 184, g: 170, b: 20, a: 128 }));
3133    }
3134
3135    #[test]
3136    fn test_parse_direction() {
3137        let first_input = "60.9grad";
3138        let e = FloatValue::new(first_input.split("grad").next().unwrap().parse::<f32>().expect("Parseable float") / 400.0 * 360.0);
3139        assert_eq!(e, FloatValue::new(60.9 / 400.0 * 360.0));
3140        assert_eq!(parse_direction("60.9grad"), Ok(Direction::Angle(FloatValue::new(60.9 / 400.0 * 360.0))));
3141    }
3142
3143    #[test]
3144    fn test_parse_float_value() {
3145        assert_eq!(parse_float_value("60.9"), Ok(FloatValue::new(60.9)));
3146    }
3147
3148    #[test]
3149    fn test_parse_css_color_27() {
3150        assert_eq!(parse_css_color("hsla(240, 0%, 0%, 0.5)"), Ok(ColorU { r: 0, g: 0, b: 0, a: 128 }));
3151    }
3152
3153    #[test]
3154    fn test_parse_css_color_28() {
3155        assert_eq!(parse_css_color("hsla(240deg, 0, 0%, 0.5)"), Err(CssColorParseError::InvalidPercentage(PercentageParseError::NoPercentSign)));
3156    }
3157
3158    #[test]
3159    fn test_parse_css_color_29() {
3160        assert_eq!(parse_css_color("hsla(240deg, 0%, 0, 0.5)"), Err(CssColorParseError::InvalidPercentage(PercentageParseError::NoPercentSign)));
3161    }
3162
3163    #[test]
3164    fn test_parse_css_color_30() {
3165        assert_eq!(parse_css_color("hsla(240deg, 0%, 0%, )"), Err(CssColorParseError::MissingColorComponent(CssColorComponent::Alpha)));
3166    }
3167
3168    #[test]
3169    fn test_parse_css_color_31() {
3170        assert_eq!(parse_css_color("hsl(, 0%, 0%, )"), Err(CssColorParseError::MissingColorComponent(CssColorComponent::Hue)));
3171    }
3172
3173    #[test]
3174    fn test_parse_css_color_32() {
3175        assert_eq!(parse_css_color("hsl(240deg ,  )"), Err(CssColorParseError::MissingColorComponent(CssColorComponent::Saturation)));
3176    }
3177
3178    #[test]
3179    fn test_parse_css_color_33() {
3180        assert_eq!(parse_css_color("hsl(240deg, 0%,  )"), Err(CssColorParseError::MissingColorComponent(CssColorComponent::Lightness)));
3181    }
3182
3183    #[test]
3184    fn test_parse_css_color_34() {
3185        assert_eq!(parse_css_color("hsl(240deg, 0%, 0%,  )"), Err(CssColorParseError::ExtraArguments("")));
3186    }
3187
3188    #[test]
3189    fn test_parse_css_color_35() {
3190        assert_eq!(parse_css_color("hsla(240deg, 0%, 0%  )"), Err(CssColorParseError::MissingColorComponent(CssColorComponent::Alpha)));
3191    }
3192
3193    #[test]
3194    fn test_parse_css_color_36() {
3195        assert_eq!(parse_css_color("rgb(255,0, 0)"), Ok(ColorU { r: 255, g: 0, b: 0, a: 255 }));
3196    }
3197
3198    #[test]
3199    fn test_parse_pixel_value_1() {
3200        assert_eq!(parse_pixel_value("15px"), Ok(PixelValue::px(15.0)));
3201    }
3202
3203    #[test]
3204    fn test_parse_pixel_value_2() {
3205        assert_eq!(parse_pixel_value("1.2em"), Ok(PixelValue::em(1.2)));
3206    }
3207
3208    #[test]
3209    fn test_parse_pixel_value_3() {
3210        assert_eq!(parse_pixel_value("11pt"), Ok(PixelValue::pt(11.0)));
3211    }
3212
3213    #[test]
3214    fn test_parse_pixel_value_4() {
3215        assert_eq!(parse_pixel_value("aslkfdjasdflk"), Err(PixelParseError::NoValueGiven("aslkfdjasdflk")));
3216    }
3217
3218    #[test]
3219    fn test_parse_style_border_radius_1() {
3220        assert_eq!(
3221            parse_style_border_radius("15px"),
3222            Ok(StyleBorderRadius {
3223                top_left: PixelValue::px(15.0),
3224                top_right: PixelValue::px(15.0),
3225                bottom_left: PixelValue::px(15.0),
3226                bottom_right: PixelValue::px(15.0),
3227            })
3228        );
3229    }
3230
3231    #[test]
3232    fn test_parse_style_border_radius_2() {
3233        assert_eq!(
3234            parse_style_border_radius("15px 50px"),
3235            Ok(StyleBorderRadius {
3236                top_left: PixelValue::px(15.0),
3237                bottom_right: PixelValue::px(15.0),
3238                top_right: PixelValue::px(50.0),
3239                bottom_left: PixelValue::px(50.0),
3240            })
3241        );
3242    }
3243
3244    #[test]
3245    fn test_parse_style_border_radius_3() {
3246        assert_eq!(
3247            parse_style_border_radius("15px 50px 30px"),
3248            Ok(StyleBorderRadius {
3249                top_left: PixelValue::px(15.0),
3250                bottom_right: PixelValue::px(30.0),
3251                top_right: PixelValue::px(50.0),
3252                bottom_left: PixelValue::px(50.0),
3253            })
3254        );
3255    }
3256
3257    #[test]
3258    fn test_parse_style_border_radius_4() {
3259        assert_eq!(
3260            parse_style_border_radius("15px 50px 30px 5px"),
3261            Ok(StyleBorderRadius {
3262                top_left: PixelValue::px(15.0),
3263                bottom_right: PixelValue::px(30.0),
3264                top_right: PixelValue::px(50.0),
3265                bottom_left: PixelValue::px(5.0),
3266            })
3267        );
3268    }
3269
3270    #[test]
3271    fn test_parse_style_font_family_1() {
3272        assert_eq!(parse_style_font_family("\"Webly Sleeky UI\", monospace"), Ok(StyleFontFamily {
3273            fonts: vec![
3274                FontId("Webly Sleeky UI".into()),
3275                FontId("monospace".into()),
3276            ]
3277        }));
3278    }
3279
3280    #[test]
3281    fn test_parse_style_font_family_2() {
3282        assert_eq!(parse_style_font_family("'Webly Sleeky UI'"), Ok(StyleFontFamily {
3283            fonts: vec![
3284                FontId("Webly Sleeky UI".into()),
3285            ]
3286        }));
3287    }
3288
3289    #[test]
3290    fn test_parse_background_image() {
3291        assert_eq!(parse_style_background_content("image(\"Cat 01\")"), Ok(StyleBackgroundContent::Image(
3292            CssImageId(String::from("Cat 01"))
3293        )));
3294    }
3295
3296    #[test]
3297    fn test_parse_padding_1() {
3298        assert_eq!(
3299            parse_layout_padding("10px"),
3300            Ok(LayoutPadding {
3301                top: PixelValueWithAuto::Exact(PixelValue::px(10.0)),
3302                right: PixelValueWithAuto::Exact(PixelValue::px(10.0)),
3303                bottom: PixelValueWithAuto::Exact(PixelValue::px(10.0)),
3304                left: PixelValueWithAuto::Exact(PixelValue::px(10.0)),
3305            })
3306        );
3307    }
3308
3309    #[test]
3310    fn test_parse_padding_2() {
3311        assert_eq!(
3312            parse_layout_padding("25px 50px"),
3313            Ok(LayoutPadding {
3314                top: PixelValueWithAuto::Exact(PixelValue::px(25.0)),
3315                right: PixelValueWithAuto::Exact(PixelValue::px(50.0)),
3316                bottom: PixelValueWithAuto::Exact(PixelValue::px(25.0)),
3317                left: PixelValueWithAuto::Exact(PixelValue::px(50.0)),
3318            })
3319        );
3320    }
3321
3322    #[test]
3323    fn test_parse_padding_3() {
3324        assert_eq!(
3325            parse_layout_padding("25px 50px 75px"),
3326            Ok(LayoutPadding {
3327                top: PixelValueWithAuto::Exact(PixelValue::px(25.0)),
3328                right: PixelValueWithAuto::Exact(PixelValue::px(50.0)),
3329                left: PixelValueWithAuto::Exact(PixelValue::px(50.0)),
3330                bottom: PixelValueWithAuto::Exact(PixelValue::px(75.0)),
3331            })
3332        );
3333    }
3334
3335    #[test]
3336    fn test_parse_padding_4() {
3337        assert_eq!(
3338            parse_layout_padding("25px 50px 75px 100px"),
3339            Ok(LayoutPadding {
3340                top: PixelValueWithAuto::Exact(PixelValue::px(25.0)),
3341                right: PixelValueWithAuto::Exact(PixelValue::px(50.0)),
3342                bottom: PixelValueWithAuto::Exact(PixelValue::px(75.0)),
3343                left: PixelValueWithAuto::Exact(PixelValue::px(100.0)),
3344            })
3345        );
3346    }
3347}