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