style/values/specified/
svg.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! Specified types for SVG properties.
6
7use crate::parser::{Parse, ParserContext};
8use crate::values::generics::svg as generic;
9use crate::values::specified::color::Color;
10use crate::values::specified::url::SpecifiedUrl;
11use crate::values::specified::AllowQuirks;
12use crate::values::specified::LengthPercentage;
13use crate::values::specified::SVGPathData;
14use crate::values::specified::{NonNegativeLengthPercentage, Opacity};
15use crate::values::CustomIdent;
16use cssparser::{Parser, Token};
17use std::fmt::{self, Write};
18use style_traits::{CommaWithSpace, CssWriter, ParseError, Separator};
19use style_traits::{StyleParseErrorKind, ToCss};
20
21/// Specified SVG Paint value
22pub type SVGPaint = generic::GenericSVGPaint<Color, SpecifiedUrl>;
23
24/// <length> | <percentage> | <number> | context-value
25pub type SVGLength = generic::GenericSVGLength<LengthPercentage>;
26
27/// A non-negative version of SVGLength.
28pub type SVGWidth = generic::GenericSVGLength<NonNegativeLengthPercentage>;
29
30/// [ <length> | <percentage> | <number> ]# | context-value
31pub type SVGStrokeDashArray = generic::GenericSVGStrokeDashArray<NonNegativeLengthPercentage>;
32
33/// Whether the `context-value` value is enabled.
34#[cfg(feature = "gecko")]
35pub fn is_context_value_enabled() -> bool {
36    static_prefs::pref!("gfx.font_rendering.opentype_svg.enabled")
37}
38
39/// Whether the `context-value` value is enabled.
40#[cfg(not(feature = "gecko"))]
41pub fn is_context_value_enabled() -> bool {
42    false
43}
44
45macro_rules! parse_svg_length {
46    ($ty:ty, $lp:ty) => {
47        impl Parse for $ty {
48            fn parse<'i, 't>(
49                context: &ParserContext,
50                input: &mut Parser<'i, 't>,
51            ) -> Result<Self, ParseError<'i>> {
52                if let Ok(lp) =
53                    input.try_parse(|i| <$lp>::parse_quirky(context, i, AllowQuirks::Always))
54                {
55                    return Ok(generic::SVGLength::LengthPercentage(lp));
56                }
57
58                try_match_ident_ignore_ascii_case! { input,
59                    "context-value" if is_context_value_enabled() => {
60                        Ok(generic::SVGLength::ContextValue)
61                    },
62                }
63            }
64        }
65    };
66}
67
68parse_svg_length!(SVGLength, LengthPercentage);
69parse_svg_length!(SVGWidth, NonNegativeLengthPercentage);
70
71impl Parse for SVGStrokeDashArray {
72    fn parse<'i, 't>(
73        context: &ParserContext,
74        input: &mut Parser<'i, 't>,
75    ) -> Result<Self, ParseError<'i>> {
76        if let Ok(values) = input.try_parse(|i| {
77            CommaWithSpace::parse(i, |i| {
78                NonNegativeLengthPercentage::parse_quirky(context, i, AllowQuirks::Always)
79            })
80        }) {
81            return Ok(generic::SVGStrokeDashArray::Values(values.into()));
82        }
83
84        try_match_ident_ignore_ascii_case! { input,
85            "context-value" if is_context_value_enabled() => {
86                Ok(generic::SVGStrokeDashArray::ContextValue)
87            },
88            "none" => Ok(generic::SVGStrokeDashArray::Values(Default::default())),
89        }
90    }
91}
92
93/// <opacity-value> | context-fill-opacity | context-stroke-opacity
94pub type SVGOpacity = generic::SVGOpacity<Opacity>;
95
96/// The specified value for a single CSS paint-order property.
97#[repr(u8)]
98#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, ToCss)]
99pub enum PaintOrder {
100    /// `normal` variant
101    Normal = 0,
102    /// `fill` variant
103    Fill = 1,
104    /// `stroke` variant
105    Stroke = 2,
106    /// `markers` variant
107    Markers = 3,
108}
109
110/// Number of non-normal components
111pub const PAINT_ORDER_COUNT: u8 = 3;
112
113/// Number of bits for each component
114pub const PAINT_ORDER_SHIFT: u8 = 2;
115
116/// Mask with above bits set
117pub const PAINT_ORDER_MASK: u8 = 0b11;
118
119/// The specified value is tree `PaintOrder` values packed into the
120/// bitfields below, as a six-bit field, of 3 two-bit pairs
121///
122/// Each pair can be set to FILL, STROKE, or MARKERS
123/// Lowest significant bit pairs are highest priority.
124///  `normal` is the empty bitfield. The three pairs are
125/// never zero in any case other than `normal`.
126///
127/// Higher priority values, i.e. the values specified first,
128/// will be painted first (and may be covered by paintings of lower priority)
129#[derive(
130    Clone,
131    Copy,
132    Debug,
133    MallocSizeOf,
134    PartialEq,
135    SpecifiedValueInfo,
136    ToComputedValue,
137    ToResolvedValue,
138    ToShmem,
139    ToTyped,
140)]
141#[repr(transparent)]
142pub struct SVGPaintOrder(pub u8);
143
144impl SVGPaintOrder {
145    /// Get default `paint-order` with `0`
146    pub fn normal() -> Self {
147        SVGPaintOrder(0)
148    }
149
150    /// Get variant of `paint-order`
151    pub fn order_at(&self, pos: u8) -> PaintOrder {
152        match (self.0 >> pos * PAINT_ORDER_SHIFT) & PAINT_ORDER_MASK {
153            0 => PaintOrder::Normal,
154            1 => PaintOrder::Fill,
155            2 => PaintOrder::Stroke,
156            3 => PaintOrder::Markers,
157            _ => unreachable!("this cannot happen"),
158        }
159    }
160}
161
162impl Parse for SVGPaintOrder {
163    fn parse<'i, 't>(
164        _context: &ParserContext,
165        input: &mut Parser<'i, 't>,
166    ) -> Result<SVGPaintOrder, ParseError<'i>> {
167        if let Ok(()) = input.try_parse(|i| i.expect_ident_matching("normal")) {
168            return Ok(SVGPaintOrder::normal());
169        }
170
171        let mut value = 0;
172        // bitfield representing what we've seen so far
173        // bit 1 is fill, bit 2 is stroke, bit 3 is markers
174        let mut seen = 0;
175        let mut pos = 0;
176
177        loop {
178            let result: Result<_, ParseError> = input.try_parse(|input| {
179                try_match_ident_ignore_ascii_case! { input,
180                    "fill" => Ok(PaintOrder::Fill),
181                    "stroke" => Ok(PaintOrder::Stroke),
182                    "markers" => Ok(PaintOrder::Markers),
183                }
184            });
185
186            match result {
187                Ok(val) => {
188                    if (seen & (1 << val as u8)) != 0 {
189                        // don't parse the same ident twice
190                        return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
191                    }
192
193                    value |= (val as u8) << (pos * PAINT_ORDER_SHIFT);
194                    seen |= 1 << (val as u8);
195                    pos += 1;
196                },
197                Err(_) => break,
198            }
199        }
200
201        if value == 0 {
202            // Couldn't find any keyword
203            return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
204        }
205
206        // fill in rest
207        for i in pos..PAINT_ORDER_COUNT {
208            for paint in 1..(PAINT_ORDER_COUNT + 1) {
209                // if not seen, set bit at position, mark as seen
210                if (seen & (1 << paint)) == 0 {
211                    seen |= 1 << paint;
212                    value |= paint << (i * PAINT_ORDER_SHIFT);
213                    break;
214                }
215            }
216        }
217
218        Ok(SVGPaintOrder(value))
219    }
220}
221
222impl ToCss for SVGPaintOrder {
223    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
224    where
225        W: Write,
226    {
227        if self.0 == 0 {
228            return dest.write_str("normal");
229        }
230
231        let mut last_pos_to_serialize = 0;
232        for i in (1..PAINT_ORDER_COUNT).rev() {
233            let component = self.order_at(i);
234            let earlier_component = self.order_at(i - 1);
235            if component < earlier_component {
236                last_pos_to_serialize = i - 1;
237                break;
238            }
239        }
240
241        for pos in 0..last_pos_to_serialize + 1 {
242            if pos != 0 {
243                dest.write_char(' ')?
244            }
245            self.order_at(pos).to_css(dest)?;
246        }
247        Ok(())
248    }
249}
250
251/// The context properties we understand.
252#[derive(
253    Clone,
254    Copy,
255    Eq,
256    Debug,
257    Default,
258    MallocSizeOf,
259    PartialEq,
260    SpecifiedValueInfo,
261    ToComputedValue,
262    ToResolvedValue,
263    ToShmem,
264)]
265#[repr(C)]
266pub struct ContextPropertyBits(u8);
267bitflags! {
268    impl ContextPropertyBits: u8 {
269        /// `fill`
270        const FILL = 1 << 0;
271        /// `stroke`
272        const STROKE = 1 << 1;
273        /// `fill-opacity`
274        const FILL_OPACITY = 1 << 2;
275        /// `stroke-opacity`
276        const STROKE_OPACITY = 1 << 3;
277    }
278}
279
280/// Specified MozContextProperties value.
281/// Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-context-properties)
282#[derive(
283    Clone,
284    Debug,
285    Default,
286    MallocSizeOf,
287    PartialEq,
288    SpecifiedValueInfo,
289    ToComputedValue,
290    ToCss,
291    ToResolvedValue,
292    ToShmem,
293    ToTyped,
294)]
295#[repr(C)]
296pub struct MozContextProperties {
297    #[css(iterable, if_empty = "none")]
298    #[ignore_malloc_size_of = "Arc"]
299    idents: crate::ArcSlice<CustomIdent>,
300    #[css(skip)]
301    bits: ContextPropertyBits,
302}
303
304impl Parse for MozContextProperties {
305    fn parse<'i, 't>(
306        _context: &ParserContext,
307        input: &mut Parser<'i, 't>,
308    ) -> Result<MozContextProperties, ParseError<'i>> {
309        let mut values = vec![];
310        let mut bits = ContextPropertyBits::empty();
311        loop {
312            {
313                let location = input.current_source_location();
314                let ident = input.expect_ident()?;
315
316                if ident.eq_ignore_ascii_case("none") && values.is_empty() {
317                    return Ok(Self::default());
318                }
319
320                let ident = CustomIdent::from_ident(location, ident, &["all", "none", "auto"])?;
321
322                if ident.0 == atom!("fill") {
323                    bits.insert(ContextPropertyBits::FILL);
324                } else if ident.0 == atom!("stroke") {
325                    bits.insert(ContextPropertyBits::STROKE);
326                } else if ident.0 == atom!("fill-opacity") {
327                    bits.insert(ContextPropertyBits::FILL_OPACITY);
328                } else if ident.0 == atom!("stroke-opacity") {
329                    bits.insert(ContextPropertyBits::STROKE_OPACITY);
330                }
331
332                values.push(ident);
333            }
334
335            let location = input.current_source_location();
336            match input.next() {
337                Ok(&Token::Comma) => continue,
338                Err(..) => break,
339                Ok(other) => return Err(location.new_unexpected_token_error(other.clone())),
340            }
341        }
342
343        if values.is_empty() {
344            return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
345        }
346
347        Ok(MozContextProperties {
348            idents: crate::ArcSlice::from_iter(values.into_iter()),
349            bits,
350        })
351    }
352}
353
354/// The svg d property type.
355///
356/// https://svgwg.org/svg2-draft/paths.html#TheDProperty
357#[derive(
358    Animate,
359    Clone,
360    ComputeSquaredDistance,
361    Debug,
362    Deserialize,
363    MallocSizeOf,
364    PartialEq,
365    Serialize,
366    SpecifiedValueInfo,
367    ToAnimatedValue,
368    ToAnimatedZero,
369    ToComputedValue,
370    ToCss,
371    ToResolvedValue,
372    ToShmem,
373    ToTyped,
374)]
375#[repr(C, u8)]
376pub enum DProperty {
377    /// Path value for path(<string>) or just a <string>.
378    #[css(function)]
379    Path(SVGPathData),
380    /// None value.
381    #[animation(error)]
382    None,
383}
384
385impl DProperty {
386    /// return none.
387    #[inline]
388    pub fn none() -> Self {
389        DProperty::None
390    }
391}
392
393impl Parse for DProperty {
394    fn parse<'i, 't>(
395        context: &ParserContext,
396        input: &mut Parser<'i, 't>,
397    ) -> Result<Self, ParseError<'i>> {
398        // Parse none.
399        if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
400            return Ok(DProperty::none());
401        }
402
403        // Parse possible functions.
404        input.expect_function_matching("path")?;
405        let path_data = input.parse_nested_block(|i| Parse::parse(context, i))?;
406        Ok(DProperty::Path(path_data))
407    }
408}
409
410#[derive(
411    Clone,
412    Copy,
413    Debug,
414    Default,
415    Eq,
416    MallocSizeOf,
417    Parse,
418    PartialEq,
419    SpecifiedValueInfo,
420    ToComputedValue,
421    ToCss,
422    ToResolvedValue,
423    ToShmem,
424    ToTyped,
425)]
426#[css(bitflags(single = "none", mixed = "non-scaling-stroke"))]
427#[repr(C)]
428/// https://svgwg.org/svg2-draft/coords.html#VectorEffects
429pub struct VectorEffect(u8);
430bitflags! {
431    impl VectorEffect: u8 {
432        /// `none`
433        const NONE = 0;
434        /// `non-scaling-stroke`
435        const NON_SCALING_STROKE = 1 << 0;
436    }
437}
438
439impl VectorEffect {
440    /// Returns the initial value of vector-effect
441    #[inline]
442    pub fn none() -> Self {
443        Self::NONE
444    }
445}