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 http://mozilla.org/MPL/2.0/. */
4 
5 //! Specified types for SVG properties.
6 
7 use cssparser::Parser;
8 use parser::{Parse, ParserContext};
9 use std::fmt::{self, Write};
10 use style_traits::{CommaWithSpace, CssWriter, ParseError, Separator};
11 use style_traits::{StyleParseErrorKind, ToCss};
12 use values::CustomIdent;
13 use values::generics::svg as generic;
14 use values::specified::{LengthOrPercentage, NonNegativeLengthOrPercentage, NonNegativeNumber};
15 use values::specified::{Number, Opacity, SpecifiedUrl};
16 use values::specified::color::RGBAColor;
17 
18 /// Specified SVG Paint value
19 pub type SVGPaint = generic::SVGPaint<RGBAColor, SpecifiedUrl>;
20 
21 
22 /// Specified SVG Paint Kind value
23 pub type SVGPaintKind = generic::SVGPaintKind<RGBAColor, SpecifiedUrl>;
24 
25 #[cfg(feature = "gecko")]
is_context_value_enabled() -> bool26 fn is_context_value_enabled() -> bool {
27     // The prefs can only be mutated on the main thread, so it is safe
28     // to read whenever we are on the main thread or the main thread is
29     // blocked.
30     use gecko_bindings::structs::mozilla;
31     unsafe { mozilla::StylePrefs_sOpentypeSVGEnabled }
32 }
33 #[cfg(not(feature = "gecko"))]
is_context_value_enabled() -> bool34 fn is_context_value_enabled() -> bool {
35     false
36 }
37 
parse_context_value<'i, 't, T>( input: &mut Parser<'i, 't>, value: T, ) -> Result<T, ParseError<'i>>38 fn parse_context_value<'i, 't, T>(
39     input: &mut Parser<'i, 't>,
40     value: T,
41 ) -> Result<T, ParseError<'i>> {
42     if !is_context_value_enabled() {
43         return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
44     }
45 
46     input.expect_ident_matching("context-value")?;
47     Ok(value)
48 }
49 
50 /// A value of <length> | <percentage> | <number> for stroke-dashoffset.
51 /// <https://www.w3.org/TR/SVG11/painting.html#StrokeProperties>
52 pub type SvgLengthOrPercentageOrNumber =
53     generic::SvgLengthOrPercentageOrNumber<LengthOrPercentage, Number>;
54 
55 /// <length> | <percentage> | <number> | context-value
56 pub type SVGLength = generic::SVGLength<SvgLengthOrPercentageOrNumber>;
57 
58 impl Parse for SVGLength {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>59     fn parse<'i, 't>(
60         context: &ParserContext,
61         input: &mut Parser<'i, 't>,
62     ) -> Result<Self, ParseError<'i>> {
63         input.try(|i| SvgLengthOrPercentageOrNumber::parse(context, i))
64              .map(Into::into)
65              .or_else(|_| parse_context_value(input, generic::SVGLength::ContextValue))
66     }
67 }
68 
69 impl From<SvgLengthOrPercentageOrNumber> for SVGLength {
from(length: SvgLengthOrPercentageOrNumber) -> Self70     fn from(length: SvgLengthOrPercentageOrNumber) -> Self {
71         generic::SVGLength::Length(length)
72     }
73 }
74 
75 /// A value of <length> | <percentage> | <number> for stroke-width/stroke-dasharray.
76 /// <https://www.w3.org/TR/SVG11/painting.html#StrokeProperties>
77 pub type NonNegativeSvgLengthOrPercentageOrNumber =
78     generic::SvgLengthOrPercentageOrNumber<NonNegativeLengthOrPercentage, NonNegativeNumber>;
79 
80 /// A non-negative version of SVGLength.
81 pub type SVGWidth = generic::SVGLength<NonNegativeSvgLengthOrPercentageOrNumber>;
82 
83 impl Parse for SVGWidth {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>84     fn parse<'i, 't>(
85         context: &ParserContext,
86         input: &mut Parser<'i, 't>,
87     ) -> Result<Self, ParseError<'i>> {
88         input.try(|i| NonNegativeSvgLengthOrPercentageOrNumber::parse(context, i))
89              .map(Into::into)
90              .or_else(|_| parse_context_value(input, generic::SVGLength::ContextValue))
91     }
92 }
93 
94 impl From<NonNegativeSvgLengthOrPercentageOrNumber> for SVGWidth {
from(length: NonNegativeSvgLengthOrPercentageOrNumber) -> Self95     fn from(length: NonNegativeSvgLengthOrPercentageOrNumber) -> Self {
96         generic::SVGLength::Length(length)
97     }
98 }
99 
100 /// [ <length> | <percentage> | <number> ]# | context-value
101 pub type SVGStrokeDashArray = generic::SVGStrokeDashArray<NonNegativeSvgLengthOrPercentageOrNumber>;
102 
103 impl Parse for SVGStrokeDashArray {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>104     fn parse<'i, 't>(
105         context: &ParserContext,
106         input: &mut Parser<'i, 't>,
107     ) -> Result<Self, ParseError<'i>> {
108         if let Ok(values) = input.try(|i| CommaWithSpace::parse(i, |i| {
109             NonNegativeSvgLengthOrPercentageOrNumber::parse(context, i)
110         })) {
111             Ok(generic::SVGStrokeDashArray::Values(values))
112         } else if let Ok(_) = input.try(|i| i.expect_ident_matching("none")) {
113             Ok(generic::SVGStrokeDashArray::Values(vec![]))
114         } else {
115             parse_context_value(input, generic::SVGStrokeDashArray::ContextValue)
116         }
117     }
118 }
119 
120 /// <opacity-value> | context-fill-opacity | context-stroke-opacity
121 pub type SVGOpacity = generic::SVGOpacity<Opacity>;
122 
123 impl Parse for SVGOpacity {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>124     fn parse<'i, 't>(
125         context: &ParserContext,
126         input: &mut Parser<'i, 't>,
127     ) -> Result<Self, ParseError<'i>> {
128         if let Ok(opacity) = input.try(|i| Opacity::parse(context, i)) {
129             return Ok(generic::SVGOpacity::Opacity(opacity));
130         }
131 
132         try_match_ident_ignore_ascii_case! { input,
133             "context-fill-opacity" => Ok(generic::SVGOpacity::ContextFillOpacity),
134             "context-stroke-opacity" => Ok(generic::SVGOpacity::ContextStrokeOpacity),
135         }
136     }
137 }
138 
139 /// The specified value for a single CSS paint-order property.
140 #[repr(u8)]
141 #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, ToCss)]
142 pub enum PaintOrder {
143     /// `normal` variant
144     Normal = 0,
145     /// `fill` variant
146     Fill = 1,
147     /// `stroke` variant
148     Stroke = 2,
149     /// `markers` variant
150     Markers = 3,
151 }
152 
153 /// Number of non-normal components
154 const PAINT_ORDER_COUNT: u8 = 3;
155 
156 /// Number of bits for each component
157 const PAINT_ORDER_SHIFT: u8 = 2;
158 
159 /// Mask with above bits set
160 const PAINT_ORDER_MASK: u8 = 0b11;
161 
162 /// The specified value is tree `PaintOrder` values packed into the
163 /// bitfields below, as a six-bit field, of 3 two-bit pairs
164 ///
165 /// Each pair can be set to FILL, STROKE, or MARKERS
166 /// Lowest significant bit pairs are highest priority.
167 ///  `normal` is the empty bitfield. The three pairs are
168 /// never zero in any case other than `normal`.
169 ///
170 /// Higher priority values, i.e. the values specified first,
171 /// will be painted first (and may be covered by paintings of lower priority)
172 #[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue)]
173 pub struct SVGPaintOrder(pub u8);
174 
175 impl SVGPaintOrder {
176     /// Get default `paint-order` with `0`
normal() -> Self177     pub fn normal() -> Self {
178         SVGPaintOrder(0)
179     }
180 
181     /// Get variant of `paint-order`
order_at(&self, pos: u8) -> PaintOrder182     pub fn order_at(&self, pos: u8) -> PaintOrder {
183         // Safe because PaintOrder covers all possible patterns.
184         unsafe { ::std::mem::transmute((self.0 >> pos * PAINT_ORDER_SHIFT) & PAINT_ORDER_MASK) }
185     }
186 }
187 
188 impl Parse for SVGPaintOrder {
parse<'i, 't>( _context: &ParserContext, input: &mut Parser<'i, 't> ) -> Result<SVGPaintOrder, ParseError<'i>>189     fn parse<'i, 't>(
190         _context: &ParserContext,
191         input: &mut Parser<'i, 't>
192     ) -> Result<SVGPaintOrder, ParseError<'i>> {
193         if let Ok(()) = input.try(|i| i.expect_ident_matching("normal")) {
194             return Ok(SVGPaintOrder::normal())
195         }
196 
197         let mut value = 0;
198         // bitfield representing what we've seen so far
199         // bit 1 is fill, bit 2 is stroke, bit 3 is markers
200         let mut seen = 0;
201         let mut pos = 0;
202 
203         loop {
204             let result: Result<_, ParseError> = input.try(|input| {
205                 try_match_ident_ignore_ascii_case! { input,
206                     "fill" => Ok(PaintOrder::Fill),
207                     "stroke" => Ok(PaintOrder::Stroke),
208                     "markers" => Ok(PaintOrder::Markers),
209                 }
210             });
211 
212             match result {
213                 Ok(val) => {
214                     if (seen & (1 << val as u8)) != 0 {
215                         // don't parse the same ident twice
216                         return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
217                     }
218 
219                     value |= (val as u8) << (pos * PAINT_ORDER_SHIFT);
220                     seen |= 1 << (val as u8);
221                     pos += 1;
222                 }
223                 Err(_) => break,
224             }
225         }
226 
227         if value == 0 {
228             // Couldn't find any keyword
229             return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
230         }
231 
232         // fill in rest
233         for i in pos..PAINT_ORDER_COUNT {
234             for paint in 0..PAINT_ORDER_COUNT {
235                 // if not seen, set bit at position, mark as seen
236                 if (seen & (1 << paint)) == 0 {
237                     seen |= 1 << paint;
238                     value |= paint << (i * PAINT_ORDER_SHIFT);
239                     break;
240                 }
241             }
242         }
243 
244         Ok(SVGPaintOrder(value))
245     }
246 }
247 
248 impl ToCss for SVGPaintOrder {
to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: Write,249     fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
250     where
251         W: Write,
252     {
253         if self.0 == 0 {
254             return dest.write_str("normal")
255         }
256 
257         let mut last_pos_to_serialize = 0;
258         for i in (1..PAINT_ORDER_COUNT).rev() {
259             let component = self.order_at(i);
260             let earlier_component = self.order_at(i - 1);
261             if component < earlier_component {
262                 last_pos_to_serialize = i - 1;
263                 break;
264             }
265         }
266 
267         for pos in 0..last_pos_to_serialize + 1 {
268             if pos != 0 {
269                 dest.write_str(" ")?
270             }
271             self.order_at(pos).to_css(dest)?;
272         }
273         Ok(())
274     }
275 }
276 
277 /// Specified MozContextProperties value.
278 /// Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-context-properties)
279 #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToCss)]
280 pub struct MozContextProperties(pub CustomIdent);
281 
282 impl Parse for MozContextProperties {
parse<'i, 't>( _context: &ParserContext, input: &mut Parser<'i, 't> ) -> Result<MozContextProperties, ParseError<'i>>283     fn parse<'i, 't>(
284         _context: &ParserContext,
285         input: &mut Parser<'i, 't>
286     ) -> Result<MozContextProperties, ParseError<'i>> {
287         let location = input.current_source_location();
288         let i = input.expect_ident()?;
289         Ok(MozContextProperties(CustomIdent::from_ident(location, i, &["all", "none", "auto"])?))
290     }
291 }
292