Skip to main content

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#[css(case = lower)]
215#[cfg_attr(feature = "visitor", derive(Visit))]
216#[cfg_attr(
217  feature = "serde",
218  derive(serde::Serialize, serde::Deserialize),
219  serde(rename_all = "lowercase")
220)]
221#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
222#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
223pub enum ColorInterpolation {
224  /// The UA can choose between sRGB or linearRGB.
225  Auto,
226  /// Color interpolation occurs in the sRGB color space.
227  SRGB,
228  /// Color interpolation occurs in the linearized RGB color space
229  LinearRGB,
230}
231
232/// A value for the [color-rendering](https://www.w3.org/TR/SVG2/painting.html#ColorRendering) property.
233#[derive(Debug, Clone, Copy, PartialEq, Parse, ToCss)]
234#[cfg_attr(feature = "visitor", derive(Visit))]
235#[cfg_attr(
236  feature = "serde",
237  derive(serde::Serialize, serde::Deserialize),
238  serde(rename_all = "lowercase")
239)]
240#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
241#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
242pub enum ColorRendering {
243  /// The UA can choose a tradeoff between speed and quality.
244  Auto,
245  /// The UA shall optimize speed over quality.
246  OptimizeSpeed,
247  /// The UA shall optimize quality over speed.
248  OptimizeQuality,
249}
250
251/// A value for the [shape-rendering](https://www.w3.org/TR/SVG2/painting.html#ShapeRendering) property.
252#[derive(Debug, Clone, Copy, PartialEq, Parse, ToCss)]
253#[css(case = lower)]
254#[cfg_attr(feature = "visitor", derive(Visit))]
255#[cfg_attr(
256  feature = "serde",
257  derive(serde::Serialize, serde::Deserialize),
258  serde(rename_all = "lowercase")
259)]
260#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
261#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
262pub enum ShapeRendering {
263  /// The UA can choose an appropriate tradeoff.
264  Auto,
265  /// The UA shall optimize speed.
266  OptimizeSpeed,
267  /// The UA shall optimize crisp edges.
268  CrispEdges,
269  /// The UA shall optimize geometric precision.
270  GeometricPrecision,
271}
272
273/// A value for the [text-rendering](https://www.w3.org/TR/SVG2/painting.html#TextRendering) property.
274#[derive(Debug, Clone, Copy, PartialEq, Parse, ToCss)]
275#[css(case = lower)]
276#[cfg_attr(feature = "visitor", derive(Visit))]
277#[cfg_attr(
278  feature = "serde",
279  derive(serde::Serialize, serde::Deserialize),
280  serde(rename_all = "lowercase")
281)]
282#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
283#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
284pub enum TextRendering {
285  /// The UA can choose an appropriate tradeoff.
286  Auto,
287  /// The UA shall optimize speed.
288  OptimizeSpeed,
289  /// The UA shall optimize legibility.
290  OptimizeLegibility,
291  /// The UA shall optimize geometric precision.
292  GeometricPrecision,
293}
294
295/// A value for the [image-rendering](https://www.w3.org/TR/SVG2/painting.html#ImageRendering) property.
296#[derive(Debug, Clone, Copy, PartialEq, Parse, ToCss)]
297#[cfg_attr(feature = "visitor", derive(Visit))]
298#[cfg_attr(
299  feature = "serde",
300  derive(serde::Serialize, serde::Deserialize),
301  serde(rename_all = "lowercase")
302)]
303#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
304#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
305pub enum ImageRendering {
306  /// The UA can choose a tradeoff between speed and quality.
307  Auto,
308  /// The UA shall optimize speed over quality.
309  OptimizeSpeed,
310  /// The UA shall optimize quality over speed.
311  OptimizeQuality,
312}