1use 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
22pub type SVGPaint = generic::GenericSVGPaint<Color, SpecifiedUrl>;
24
25pub type SVGLength = generic::GenericSVGLength<LengthPercentage>;
27
28pub type SVGWidth = generic::GenericSVGLength<NonNegativeLengthPercentage>;
30
31pub type SVGStrokeDashArray = generic::GenericSVGStrokeDashArray<NonNegativeLengthPercentage>;
33
34pub 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
81pub type SVGOpacity = generic::SVGOpacity<Opacity>;
83
84#[repr(u8)]
86#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, ToCss)]
87pub enum PaintOrder {
88 Normal = 0,
90 Fill = 1,
92 Stroke = 2,
94 Markers = 3,
96}
97
98pub const PAINT_ORDER_COUNT: u8 = 3;
100
101pub const PAINT_ORDER_SHIFT: u8 = 2;
103
104pub const PAINT_ORDER_MASK: u8 = 0b11;
106
107#[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 pub fn normal() -> Self {
136 SVGPaintOrder(0)
137 }
138
139 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 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 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 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
190 }
191
192 for i in pos..PAINT_ORDER_COUNT {
194 for paint in 1..(PAINT_ORDER_COUNT + 1) {
195 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#[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 const FILL = 1 << 0;
257 const STROKE = 1 << 1;
259 const FILL_OPACITY = 1 << 2;
261 const STROKE_OPACITY = 1 << 3;
263 }
264}
265
266#[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#[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 #[css(function)]
364 Path(SVGPathData),
365 #[animation(error)]
367 None,
368}
369
370impl DProperty {
371 #[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 if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
382 return Ok(DProperty::none());
383 }
384
385 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)]
410pub struct VectorEffect(u8);
412bitflags! {
413 impl VectorEffect: u8 {
414 const NONE = 0;
416 const NON_SCALING_STROKE = 1 << 0;
418 }
419}
420
421impl VectorEffect {
422 #[inline]
424 pub fn none() -> Self {
425 Self::NONE
426 }
427}