Skip to main content

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