lightningcss/properties/
svg.rs

1//! CSS properties used in SVG.
2
3use crate::error::{ParserError, PrinterError};
4use crate::macros::enum_property;
5use crate::printer::Printer;
6use crate::targets::{Browsers, Targets};
7use crate::traits::{FallbackValues, IsCompatible, Parse, ToCss};
8use crate::values::length::LengthPercentage;
9use crate::values::{color::CssColor, url::Url};
10#[cfg(feature = "visitor")]
11use crate::visitor::Visit;
12use cssparser::*;
13
14/// An SVG [`<paint>`](https://www.w3.org/TR/SVG2/painting.html#SpecifyingPaint) value
15/// used in the `fill` and `stroke` properties.
16#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
17#[cfg_attr(feature = "visitor", derive(Visit))]
18#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
19#[cfg_attr(
20  feature = "serde",
21  derive(serde::Serialize, serde::Deserialize),
22  serde(tag = "type", rename_all = "kebab-case")
23)]
24#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
25pub enum SVGPaint<'i> {
26  /// A URL reference to a paint server element, e.g. `linearGradient`, `radialGradient`, and `pattern`.
27  Url {
28    #[cfg_attr(feature = "serde", serde(borrow))]
29    /// The url of the paint server.
30    url: Url<'i>,
31    /// A fallback to be used used in case the paint server cannot be resolved.
32    fallback: Option<SVGPaintFallback>,
33  },
34  /// A solid color paint.
35  #[cfg_attr(feature = "serde", serde(with = "crate::serialization::ValueWrapper::<CssColor>"))]
36  Color(CssColor),
37  /// Use the paint value of fill from a context element.
38  ContextFill,
39  /// Use the paint value of stroke from a context element.
40  ContextStroke,
41  /// No paint.
42  None,
43}
44
45/// A fallback for an SVG paint in case a paint server `url()` cannot be resolved.
46///
47/// See [SVGPaint](SVGPaint).
48#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
49#[cfg_attr(feature = "visitor", derive(Visit))]
50#[cfg_attr(
51  feature = "serde",
52  derive(serde::Serialize, serde::Deserialize),
53  serde(tag = "type", content = "value", rename_all = "kebab-case")
54)]
55#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
56#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
57pub enum SVGPaintFallback {
58  /// No fallback.
59  None,
60  /// A solid color.
61  Color(CssColor),
62}
63
64impl<'i> FallbackValues for SVGPaint<'i> {
65  fn get_fallbacks(&mut self, targets: Targets) -> Vec<Self> {
66    match self {
67      SVGPaint::Color(color) => color
68        .get_fallbacks(targets)
69        .into_iter()
70        .map(|color| SVGPaint::Color(color))
71        .collect(),
72      SVGPaint::Url {
73        url,
74        fallback: Some(SVGPaintFallback::Color(color)),
75      } => color
76        .get_fallbacks(targets)
77        .into_iter()
78        .map(|color| SVGPaint::Url {
79          url: url.clone(),
80          fallback: Some(SVGPaintFallback::Color(color)),
81        })
82        .collect(),
83      _ => Vec::new(),
84    }
85  }
86}
87
88impl IsCompatible for SVGPaint<'_> {
89  fn is_compatible(&self, browsers: Browsers) -> bool {
90    match self {
91      SVGPaint::Color(c)
92      | SVGPaint::Url {
93        fallback: Some(SVGPaintFallback::Color(c)),
94        ..
95      } => c.is_compatible(browsers),
96      SVGPaint::Url { .. } | SVGPaint::None | SVGPaint::ContextFill | SVGPaint::ContextStroke => true,
97    }
98  }
99}
100
101enum_property! {
102  /// A value for the [stroke-linecap](https://www.w3.org/TR/SVG2/painting.html#LineCaps) property.
103  pub enum StrokeLinecap {
104    /// The stroke does not extend beyond its endpoints.
105    Butt,
106    /// The ends of the stroke are rounded.
107    Round,
108    /// The ends of the stroke are squared.
109    Square,
110  }
111}
112
113enum_property! {
114  /// A value for the [stroke-linejoin](https://www.w3.org/TR/SVG2/painting.html#LineJoin) property.
115  pub enum StrokeLinejoin {
116    /// A sharp corner is to be used to join path segments.
117    Miter,
118    /// Same as `miter` but clipped beyond `stroke-miterlimit`.
119    MiterClip,
120    /// A round corner is to be used to join path segments.
121    Round,
122    /// A bevelled corner is to be used to join path segments.
123    Bevel,
124    /// An arcs corner is to be used to join path segments.
125    Arcs,
126  }
127}
128
129/// A value for the [stroke-dasharray](https://www.w3.org/TR/SVG2/painting.html#StrokeDashing) property.
130#[derive(Debug, Clone, PartialEq)]
131#[cfg_attr(feature = "visitor", derive(Visit))]
132#[cfg_attr(
133  feature = "serde",
134  derive(serde::Serialize, serde::Deserialize),
135  serde(tag = "type", content = "value", rename_all = "kebab-case")
136)]
137#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
138#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
139pub enum StrokeDasharray {
140  /// No dashing is used.
141  None,
142  /// Specifies a dashing pattern to use.
143  Values(Vec<LengthPercentage>),
144}
145
146impl<'i> Parse<'i> for StrokeDasharray {
147  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
148    if input.try_parse(|input| input.expect_ident_matching("none")).is_ok() {
149      return Ok(StrokeDasharray::None);
150    }
151
152    input.skip_whitespace();
153    let mut results = vec![LengthPercentage::parse(input)?];
154    loop {
155      input.skip_whitespace();
156      let comma_location = input.current_source_location();
157      let comma = input.try_parse(|i| i.expect_comma()).is_ok();
158      if let Ok(item) = input.try_parse(LengthPercentage::parse) {
159        results.push(item);
160      } else if comma {
161        return Err(comma_location.new_unexpected_token_error(Token::Comma));
162      } else {
163        break;
164      }
165    }
166
167    Ok(StrokeDasharray::Values(results))
168  }
169}
170
171impl ToCss for StrokeDasharray {
172  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
173  where
174    W: std::fmt::Write,
175  {
176    match self {
177      StrokeDasharray::None => dest.write_str("none"),
178      StrokeDasharray::Values(values) => {
179        let mut first = true;
180        for value in values {
181          if first {
182            first = false;
183          } else {
184            dest.write_char(' ')?;
185          }
186          value.to_css_unitless(dest)?;
187        }
188        Ok(())
189      }
190    }
191  }
192}
193
194/// A value for the [marker](https://www.w3.org/TR/SVG2/painting.html#VertexMarkerProperties) properties.
195#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
196#[cfg_attr(feature = "visitor", derive(Visit))]
197#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
198#[cfg_attr(
199  feature = "serde",
200  derive(serde::Serialize, serde::Deserialize),
201  serde(tag = "type", content = "value", rename_all = "kebab-case")
202)]
203#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
204pub enum Marker<'i> {
205  /// No marker.
206  None,
207  /// A url reference to a `<marker>` element.
208  #[cfg_attr(feature = "serde", serde(borrow))]
209  Url(Url<'i>),
210}
211
212/// A value for the [color-interpolation](https://www.w3.org/TR/SVG2/painting.html#ColorInterpolation) property.
213#[derive(Debug, Clone, Copy, PartialEq, Parse, ToCss)]
214#[cfg_attr(feature = "visitor", derive(Visit))]
215#[cfg_attr(
216  feature = "serde",
217  derive(serde::Serialize, serde::Deserialize),
218  serde(rename_all = "lowercase")
219)]
220#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
221#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
222pub enum ColorInterpolation {
223  /// The UA can choose between sRGB or linearRGB.
224  Auto,
225  /// Color interpolation occurs in the sRGB color space.
226  SRGB,
227  /// Color interpolation occurs in the linearized RGB color space
228  LinearRGB,
229}
230
231/// A value for the [color-rendering](https://www.w3.org/TR/SVG2/painting.html#ColorRendering) property.
232#[derive(Debug, Clone, Copy, PartialEq, Parse, ToCss)]
233#[cfg_attr(feature = "visitor", derive(Visit))]
234#[cfg_attr(
235  feature = "serde",
236  derive(serde::Serialize, serde::Deserialize),
237  serde(rename_all = "lowercase")
238)]
239#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
240#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
241pub enum ColorRendering {
242  /// The UA can choose a tradeoff between speed and quality.
243  Auto,
244  /// The UA shall optimize speed over quality.
245  OptimizeSpeed,
246  /// The UA shall optimize quality over speed.
247  OptimizeQuality,
248}
249
250/// A value for the [shape-rendering](https://www.w3.org/TR/SVG2/painting.html#ShapeRendering) property.
251#[derive(Debug, Clone, Copy, PartialEq, Parse, ToCss)]
252#[cfg_attr(feature = "visitor", derive(Visit))]
253#[cfg_attr(
254  feature = "serde",
255  derive(serde::Serialize, serde::Deserialize),
256  serde(rename_all = "lowercase")
257)]
258#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
259#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
260pub enum ShapeRendering {
261  /// The UA can choose an appropriate tradeoff.
262  Auto,
263  /// The UA shall optimize speed.
264  OptimizeSpeed,
265  /// The UA shall optimize crisp edges.
266  CrispEdges,
267  /// The UA shall optimize geometric precision.
268  GeometricPrecision,
269}
270
271/// A value for the [text-rendering](https://www.w3.org/TR/SVG2/painting.html#TextRendering) property.
272#[derive(Debug, Clone, Copy, PartialEq, Parse, ToCss)]
273#[cfg_attr(feature = "visitor", derive(Visit))]
274#[cfg_attr(
275  feature = "serde",
276  derive(serde::Serialize, serde::Deserialize),
277  serde(rename_all = "lowercase")
278)]
279#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
280#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
281pub enum TextRendering {
282  /// The UA can choose an appropriate tradeoff.
283  Auto,
284  /// The UA shall optimize speed.
285  OptimizeSpeed,
286  /// The UA shall optimize legibility.
287  OptimizeLegibility,
288  /// The UA shall optimize geometric precision.
289  GeometricPrecision,
290}
291
292/// A value for the [image-rendering](https://www.w3.org/TR/SVG2/painting.html#ImageRendering) property.
293#[derive(Debug, Clone, Copy, PartialEq, Parse, ToCss)]
294#[cfg_attr(feature = "visitor", derive(Visit))]
295#[cfg_attr(
296  feature = "serde",
297  derive(serde::Serialize, serde::Deserialize),
298  serde(rename_all = "lowercase")
299)]
300#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
301#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
302pub enum ImageRendering {
303  /// The UA can choose a tradeoff between speed and quality.
304  Auto,
305  /// The UA shall optimize speed over quality.
306  OptimizeSpeed,
307  /// The UA shall optimize quality over speed.
308  OptimizeQuality,
309}