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
34#[cfg(feature = "gecko")]
36pub fn is_context_value_enabled() -> bool {
37 static_prefs::pref!("gfx.font_rendering.opentype_svg.enabled")
38}
39
40#[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
94pub type SVGOpacity = generic::SVGOpacity<Opacity>;
96
97#[repr(u8)]
99#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, ToCss)]
100pub enum PaintOrder {
101 Normal = 0,
103 Fill = 1,
105 Stroke = 2,
107 Markers = 3,
109}
110
111pub const PAINT_ORDER_COUNT: u8 = 3;
113
114pub const PAINT_ORDER_SHIFT: u8 = 2;
116
117pub const PAINT_ORDER_MASK: u8 = 0b11;
119
120#[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 pub fn normal() -> Self {
148 SVGPaintOrder(0)
149 }
150
151 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 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 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 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
205 }
206
207 for i in pos..PAINT_ORDER_COUNT {
209 for paint in 1..(PAINT_ORDER_COUNT + 1) {
210 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#[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 const FILL = 1 << 0;
272 const STROKE = 1 << 1;
274 const FILL_OPACITY = 1 << 2;
276 const STROKE_OPACITY = 1 << 3;
278 }
279}
280
281#[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#[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 #[css(function)]
380 Path(SVGPathData),
381 #[animation(error)]
383 None,
384}
385
386impl DProperty {
387 #[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 if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
401 return Ok(DProperty::none());
402 }
403
404 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)]
429pub struct VectorEffect(u8);
431bitflags! {
432 impl VectorEffect: u8 {
433 const NONE = 0;
435 const NON_SCALING_STROKE = 1 << 0;
437 }
438}
439
440impl VectorEffect {
441 #[inline]
443 pub fn none() -> Self {
444 Self::NONE
445 }
446}