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