svgtypes/paint.rs
1// Copyright 2018 the SVG Types Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4use std::str::FromStr;
5
6use 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)]
14pub 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)]
46pub 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
67impl<'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)]
120mod 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}