1use std::str::FromStr;
2
3use 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)]
11pub 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)]
43pub 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
64impl<'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)]
117mod 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