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