| 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 | |