1use 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
31macro_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
103pub 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
206pub 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#[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#[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#[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 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
659pub 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 let uniform_radius = parse_pixel_value(components.next().unwrap())?;
672 Ok(StyleBorderRadius::uniform(uniform_radius))
673 },
674 2 => {
675 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 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 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
770fn 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 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
820pub 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
843pub 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
877pub 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
896pub 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
1057pub 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
1075pub 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
1099pub 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
1116pub 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 #[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
1201pub 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#[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
1302pub 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
1374pub 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#[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 };
1414const 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
1430fn 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
1442pub 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 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 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 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 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
1494pub 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 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 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 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 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 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
1634pub 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)); },
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
1719fn 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 None
1747 } else {
1748 Some((last_character, character_was_found))
1749 }
1750}
1751
1752pub fn parse_gradient<'a>(input: &'a str, background_type: GradientType)
1754-> Result<StyleBackgroundContent, CssBackgroundParseError<'a>>
1755{
1756 let input = input.trim();
1757
1758 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(¤t_input, ',') {
1765 Some(s) => s,
1766 None => break 'outer,
1767 };
1768 let new_push_item = if character_was_found {
1769 ¤t_input[..skip_next_braces_result]
1770 } else {
1771 ¤t_input[..]
1772 };
1773 let new_current_input = ¤t_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 let first_brace_item = match brace_iterator.next() {
1786 Some(s) => s,
1787 None => return Err(CssBackgroundParseError::NoDirection(input)),
1788 };
1789
1790 let mut shape = Shape::Ellipse;
1792 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_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
1871pub 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 {
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 }
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#[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
1943pub 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
1999pub 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 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 (&input[..=closing_brace], Some(&input[(closing_brace + 1)..]))
2012 },
2013 (None, Some(last_ws)) => {
2014 (&input[..=last_ws], Some(&input[(last_ws + 1)..]))
2016 }
2017 (Some(closing_brace), Some(last_ws)) if closing_brace < last_ws => {
2018 (&input[..=last_ws], Some(&input[(last_ws + 1)..]))
2020 },
2021 _ => {
2022 (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
2036pub 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
2072pub 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 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 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 let start = end.opposite();
2122 Ok(Direction::FromTo(start, end))
2123 },
2124 3 => {
2125 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 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 let mut deg = deg % 360.0;
2141 if deg < 0.0 {
2142 deg = 360.0 + deg;
2143 }
2144
2145 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
2280pub 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
2332pub 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 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)), 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 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 #[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}