1 | use std::str::FromStr; |
2 | |
3 | use crate::{Color, Error, Stream}; |
4 | |
5 | /// Representation of the fallback part of the [`<paint>`] type. |
6 | /// |
7 | /// Used by the [`Paint`](enum.Paint.html) type. |
8 | /// |
9 | /// [`<paint>`]: https://www.w3.org/TR/SVG2/painting.html#SpecifyingPaint |
10 | #[derive (Clone, Copy, PartialEq, Eq, Debug)] |
11 | pub enum PaintFallback { |
12 | /// The `none` value. |
13 | None, |
14 | /// The `currentColor` value. |
15 | CurrentColor, |
16 | /// [`<color>`] value. |
17 | /// |
18 | /// [`<color>`]: https://www.w3.org/TR/css-color-3/ |
19 | Color(Color), |
20 | } |
21 | |
22 | /// Representation of the [`<paint>`] type. |
23 | /// |
24 | /// Doesn't own the data. Use only for parsing. |
25 | /// |
26 | /// `<icccolor>` isn't supported. |
27 | /// |
28 | /// [`<paint>`]: https://www.w3.org/TR/SVG2/painting.html#SpecifyingPaint |
29 | /// |
30 | /// # Examples |
31 | /// |
32 | /// ``` |
33 | /// use svgtypes::{Paint, PaintFallback, Color}; |
34 | /// |
35 | /// let paint = Paint::from_str("url(#gradient) red" ).unwrap(); |
36 | /// assert_eq!(paint, Paint::FuncIRI("gradient" , |
37 | /// Some(PaintFallback::Color(Color::red())))); |
38 | /// |
39 | /// let paint = Paint::from_str("inherit" ).unwrap(); |
40 | /// assert_eq!(paint, Paint::Inherit); |
41 | /// ``` |
42 | #[derive (Clone, Copy, PartialEq, Eq, Debug)] |
43 | pub enum Paint<'a> { |
44 | /// The `none` value. |
45 | None, |
46 | /// The `inherit` value. |
47 | Inherit, |
48 | /// The `currentColor` value. |
49 | CurrentColor, |
50 | /// [`<color>`] value. |
51 | /// |
52 | /// [`<color>`]: https://www.w3.org/TR/css-color-3/ |
53 | Color(Color), |
54 | /// [`<FuncIRI>`] value with an optional fallback. |
55 | /// |
56 | /// [`<FuncIRI>`]: https://www.w3.org/TR/SVG11/types.html#DataTypeFuncIRI |
57 | FuncIRI(&'a str, Option<PaintFallback>), |
58 | /// The `context-fill` value. |
59 | ContextFill, |
60 | /// The `context-stroke` value. |
61 | ContextStroke, |
62 | } |
63 | |
64 | impl<'a> Paint<'a> { |
65 | /// Parses a `Paint` from a string. |
66 | /// |
67 | /// We can't use the `FromStr` trait because it requires |
68 | /// an owned value as a return type. |
69 | #[allow (clippy::should_implement_trait)] |
70 | pub fn from_str(text: &'a str) -> Result<Self, Error> { |
71 | let text = text.trim(); |
72 | match text { |
73 | "none" => Ok(Paint::None), |
74 | "inherit" => Ok(Paint::Inherit), |
75 | "currentColor" => Ok(Paint::CurrentColor), |
76 | "context-fill" => Ok(Paint::ContextFill), |
77 | "context-stroke" => Ok(Paint::ContextStroke), |
78 | _ => { |
79 | let mut s = Stream::from(text); |
80 | if s.starts_with(b"url(" ) { |
81 | match s.parse_func_iri() { |
82 | Ok(link) => { |
83 | s.skip_spaces(); |
84 | |
85 | // get fallback |
86 | if !s.at_end() { |
87 | let fallback = s.slice_tail(); |
88 | match fallback { |
89 | "none" => Ok(Paint::FuncIRI(link, Some(PaintFallback::None))), |
90 | "currentColor" => { |
91 | Ok(Paint::FuncIRI(link, Some(PaintFallback::CurrentColor))) |
92 | } |
93 | _ => { |
94 | let color = Color::from_str(fallback)?; |
95 | Ok(Paint::FuncIRI(link, Some(PaintFallback::Color(color)))) |
96 | } |
97 | } |
98 | } else { |
99 | Ok(Paint::FuncIRI(link, None)) |
100 | } |
101 | } |
102 | Err(_) => Err(Error::InvalidValue), |
103 | } |
104 | } else { |
105 | match Color::from_str(text) { |
106 | Ok(c) => Ok(Paint::Color(c)), |
107 | Err(_) => Err(Error::InvalidValue), |
108 | } |
109 | } |
110 | } |
111 | } |
112 | } |
113 | } |
114 | |
115 | #[rustfmt::skip] |
116 | #[cfg (test)] |
117 | mod tests { |
118 | use super::*; |
119 | |
120 | macro_rules! test { |
121 | ($name:ident, $text:expr, $result:expr) => ( |
122 | #[test] |
123 | fn $name() { |
124 | assert_eq!(Paint::from_str($text).unwrap(), $result); |
125 | } |
126 | ) |
127 | } |
128 | |
129 | test !(parse_1, "none" , Paint::None); |
130 | test !(parse_2, " none " , Paint::None); |
131 | test !(parse_3, " inherit " , Paint::Inherit); |
132 | test !(parse_4, " currentColor " , Paint::CurrentColor); |
133 | test !(parse_5, " red " , Paint::Color(Color::red())); |
134 | test !(parse_6, " url(#qwe) " , Paint::FuncIRI("qwe" , None)); |
135 | test !(parse_7, " url(#qwe) none " , Paint::FuncIRI("qwe" , Some(PaintFallback::None))); |
136 | test !(parse_8, " url(#qwe) currentColor " , Paint::FuncIRI("qwe" , Some(PaintFallback::CurrentColor))); |
137 | test !(parse_9, " url(#qwe) red " , Paint::FuncIRI("qwe" , Some(PaintFallback::Color(Color::red())))); |
138 | |
139 | macro_rules! test_err { |
140 | ($name:ident, $text:expr, $result:expr) => ( |
141 | #[test] |
142 | fn $name() { |
143 | assert_eq!(Paint::from_str($text).unwrap_err().to_string(), $result); |
144 | } |
145 | ) |
146 | } |
147 | |
148 | test_err!(parse_err_1, "qwe" , "invalid value" ); |
149 | test_err!(parse_err_2, "red icc-color(acmecmyk, 0.11, 0.48, 0.83, 0.00)" , "invalid value" ); |
150 | // TODO: this |
151 | // test_err!(parse_err_3, "url(#qwe) red icc-color(acmecmyk, 0.11, 0.48, 0.83, 0.00)", "invalid color at 1:15"); |
152 | } |
153 | |