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)]
140#[repr(transparent)]
141pub struct SVGPaintOrder(pub u8);
142
143impl SVGPaintOrder {
144    /// Get default `paint-order` with `0`
145    pub fn normal() -> Self {
146        SVGPaintOrder(0)
147    }
148
149    /// Get variant of `paint-order`
150    pub fn order_at(&self, pos: u8) -> PaintOrder {
151        match (self.0 >> pos * PAINT_ORDER_SHIFT) & PAINT_ORDER_MASK {
152            0 => PaintOrder::Normal,
153            1 => PaintOrder::Fill,
154            2 => PaintOrder::Stroke,
155            3 => PaintOrder::Markers,
156            _ => unreachable!("this cannot happen"),
157        }
158    }
159}
160
161impl Parse for SVGPaintOrder {
162    fn parse<'i, 't>(
163        _context: &ParserContext,
164        input: &mut Parser<'i, 't>,
165    ) -> Result<SVGPaintOrder, ParseError<'i>> {
166        if let Ok(()) = input.try_parse(|i| i.expect_ident_matching("normal")) {
167            return Ok(SVGPaintOrder::normal());
168        }
169
170        let mut value = 0;
171        // bitfield representing what we've seen so far
172        // bit 1 is fill, bit 2 is stroke, bit 3 is markers
173        let mut seen = 0;
174        let mut pos = 0;
175
176        loop {
177            let result: Result<_, ParseError> = input.try_parse(|input| {
178                try_match_ident_ignore_ascii_case! { input,
179                    "fill" => Ok(PaintOrder::Fill),
180                    "stroke" => Ok(PaintOrder::Stroke),
181                    "markers" => Ok(PaintOrder::Markers),
182                }
183            });
184
185            match result {
186                Ok(val) => {
187                    if (seen & (1 << val as u8)) != 0 {
188                        // don't parse the same ident twice
189                        return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
190                    }
191
192                    value |= (val as u8) << (pos * PAINT_ORDER_SHIFT);
193                    seen |= 1 << (val as u8);
194                    pos += 1;
195                },
196                Err(_) => break,
197            }
198        }
199
200        if value == 0 {
201            // Couldn't find any keyword
202            return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
203        }
204
205        // fill in rest
206        for i in pos..PAINT_ORDER_COUNT {
207            for paint in 1..(PAINT_ORDER_COUNT + 1) {
208                // if not seen, set bit at position, mark as seen
209                if (seen & (1 << paint)) == 0 {
210                    seen |= 1 << paint;
211                    value |= paint << (i * PAINT_ORDER_SHIFT);
212                    break;
213                }
214            }
215        }
216
217        Ok(SVGPaintOrder(value))
218    }
219}
220
221impl ToCss for SVGPaintOrder {
222    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
223    where
224        W: Write,
225    {
226        if self.0 == 0 {
227            return dest.write_str("normal");
228        }
229
230        let mut last_pos_to_serialize = 0;
231        for i in (1..PAINT_ORDER_COUNT).rev() {
232            let component = self.order_at(i);
233            let earlier_component = self.order_at(i - 1);
234            if component < earlier_component {
235                last_pos_to_serialize = i - 1;
236                break;
237            }
238        }
239
240        for pos in 0..last_pos_to_serialize + 1 {
241            if pos != 0 {
242                dest.write_char(' ')?
243            }
244            self.order_at(pos).to_css(dest)?;
245        }
246        Ok(())
247    }
248}
249
250/// The context properties we understand.
251#[derive(
252    Clone,
253    Copy,
254    Eq,
255    Debug,
256    Default,
257    MallocSizeOf,
258    PartialEq,
259    SpecifiedValueInfo,
260    ToComputedValue,
261    ToResolvedValue,
262    ToShmem,
263)]
264#[repr(C)]
265pub struct ContextPropertyBits(u8);
266bitflags! {
267    impl ContextPropertyBits: u8 {
268        /// `fill`
269        const FILL = 1 << 0;
270        /// `stroke`
271        const STROKE = 1 << 1;
272        /// `fill-opacity`
273        const FILL_OPACITY = 1 << 2;
274        /// `stroke-opacity`
275        const STROKE_OPACITY = 1 << 3;
276    }
277}
278
279/// Specified MozContextProperties value.
280/// Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-context-properties)
281#[derive(
282    Clone,
283    Debug,
284    Default,
285    MallocSizeOf,
286    PartialEq,
287    SpecifiedValueInfo,
288    ToComputedValue,
289    ToCss,
290    ToResolvedValue,
291    ToShmem,
292)]
293#[repr(C)]
294pub struct MozContextProperties {
295    #[css(iterable, if_empty = "none")]
296    #[ignore_malloc_size_of = "Arc"]
297    idents: crate::ArcSlice<CustomIdent>,
298    #[css(skip)]
299    bits: ContextPropertyBits,
300}
301
302impl Parse for MozContextProperties {
303    fn parse<'i, 't>(
304        _context: &ParserContext,
305        input: &mut Parser<'i, 't>,
306    ) -> Result<MozContextProperties, ParseError<'i>> {
307        let mut values = vec![];
308        let mut bits = ContextPropertyBits::empty();
309        loop {
310            {
311                let location = input.current_source_location();
312                let ident = input.expect_ident()?;
313
314                if ident.eq_ignore_ascii_case("none") && values.is_empty() {
315                    return Ok(Self::default());
316                }
317
318                let ident = CustomIdent::from_ident(location, ident, &["all", "none", "auto"])?;
319
320                if ident.0 == atom!("fill") {
321                    bits.insert(ContextPropertyBits::FILL);
322                } else if ident.0 == atom!("stroke") {
323                    bits.insert(ContextPropertyBits::STROKE);
324                } else if ident.0 == atom!("fill-opacity") {
325                    bits.insert(ContextPropertyBits::FILL_OPACITY);
326                } else if ident.0 == atom!("stroke-opacity") {
327                    bits.insert(ContextPropertyBits::STROKE_OPACITY);
328                }
329
330                values.push(ident);
331            }
332
333            let location = input.current_source_location();
334            match input.next() {
335                Ok(&Token::Comma) => continue,
336                Err(..) => break,
337                Ok(other) => return Err(location.new_unexpected_token_error(other.clone())),
338            }
339        }
340
341        if values.is_empty() {
342            return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
343        }
344
345        Ok(MozContextProperties {
346            idents: crate::ArcSlice::from_iter(values.into_iter()),
347            bits,
348        })
349    }
350}
351
352/// The svg d property type.
353///
354/// https://svgwg.org/svg2-draft/paths.html#TheDProperty
355#[derive(
356    Animate,
357    Clone,
358    ComputeSquaredDistance,
359    Debug,
360    Deserialize,
361    MallocSizeOf,
362    PartialEq,
363    Serialize,
364    SpecifiedValueInfo,
365    ToAnimatedValue,
366    ToAnimatedZero,
367    ToComputedValue,
368    ToCss,
369    ToResolvedValue,
370    ToShmem,
371)]
372#[repr(C, u8)]
373pub enum DProperty {
374    /// Path value for path(<string>) or just a <string>.
375    #[css(function)]
376    Path(SVGPathData),
377    /// None value.
378    #[animation(error)]
379    None,
380}
381
382impl DProperty {
383    /// return none.
384    #[inline]
385    pub fn none() -> Self {
386        DProperty::None
387    }
388}
389
390impl Parse for DProperty {
391    fn parse<'i, 't>(
392        context: &ParserContext,
393        input: &mut Parser<'i, 't>,
394    ) -> Result<Self, ParseError<'i>> {
395        // Parse none.
396        if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
397            return Ok(DProperty::none());
398        }
399
400        // Parse possible functions.
401        input.expect_function_matching("path")?;
402        let path_data = input.parse_nested_block(|i| Parse::parse(context, i))?;
403        Ok(DProperty::Path(path_data))
404    }
405}
406
407#[derive(
408    Clone,
409    Copy,
410    Debug,
411    Default,
412    Eq,
413    MallocSizeOf,
414    Parse,
415    PartialEq,
416    SpecifiedValueInfo,
417    ToComputedValue,
418    ToCss,
419    ToResolvedValue,
420    ToShmem,
421)]
422#[css(bitflags(single = "none", mixed = "non-scaling-stroke"))]
423#[repr(C)]
424/// https://svgwg.org/svg2-draft/coords.html#VectorEffects
425pub struct VectorEffect(u8);
426bitflags! {
427    impl VectorEffect: u8 {
428        /// `none`
429        const NONE = 0;
430        /// `non-scaling-stroke`
431        const NON_SCALING_STROKE = 1 << 0;
432    }
433}
434
435impl VectorEffect {
436    /// Returns the initial value of vector-effect
437    #[inline]
438    pub fn none() -> Self {
439        Self::NONE
440    }
441}