1use crate::parser::{Parse, ParserContext};
8use crate::values::generics::svg as generic;
9use crate::values::specified::color::Color;
10use crate::values::specified::url::SpecifiedUrl;
11use crate::values::specified::AllowQuirks;
12use crate::values::specified::LengthPercentage;
13use crate::values::specified::SVGPathData;
14use crate::values::specified::{NonNegativeLengthPercentage, Opacity};
15use crate::values::CustomIdent;
16use cssparser::{Parser, Token};
17use std::fmt::{self, Write};
18use style_traits::{CommaWithSpace, CssWriter, ParseError, Separator};
19use style_traits::{StyleParseErrorKind, ToCss};
20
21pub type SVGPaint = generic::GenericSVGPaint<Color, SpecifiedUrl>;
23
24pub type SVGLength = generic::GenericSVGLength<LengthPercentage>;
26
27pub type SVGWidth = generic::GenericSVGLength<NonNegativeLengthPercentage>;
29
30pub type SVGStrokeDashArray = generic::GenericSVGStrokeDashArray<NonNegativeLengthPercentage>;
32
33#[cfg(feature = "gecko")]
35pub fn is_context_value_enabled() -> bool {
36 static_prefs::pref!("gfx.font_rendering.opentype_svg.enabled")
37}
38
39#[cfg(not(feature = "gecko"))]
41pub fn is_context_value_enabled() -> bool {
42 false
43}
44
45macro_rules! parse_svg_length {
46 ($ty:ty, $lp:ty) => {
47 impl Parse for $ty {
48 fn parse<'i, 't>(
49 context: &ParserContext,
50 input: &mut Parser<'i, 't>,
51 ) -> Result<Self, ParseError<'i>> {
52 if let Ok(lp) =
53 input.try_parse(|i| <$lp>::parse_quirky(context, i, AllowQuirks::Always))
54 {
55 return Ok(generic::SVGLength::LengthPercentage(lp));
56 }
57
58 try_match_ident_ignore_ascii_case! { input,
59 "context-value" if is_context_value_enabled() => {
60 Ok(generic::SVGLength::ContextValue)
61 },
62 }
63 }
64 }
65 };
66}
67
68parse_svg_length!(SVGLength, LengthPercentage);
69parse_svg_length!(SVGWidth, NonNegativeLengthPercentage);
70
71impl Parse for SVGStrokeDashArray {
72 fn parse<'i, 't>(
73 context: &ParserContext,
74 input: &mut Parser<'i, 't>,
75 ) -> Result<Self, ParseError<'i>> {
76 if let Ok(values) = input.try_parse(|i| {
77 CommaWithSpace::parse(i, |i| {
78 NonNegativeLengthPercentage::parse_quirky(context, i, AllowQuirks::Always)
79 })
80 }) {
81 return Ok(generic::SVGStrokeDashArray::Values(values.into()));
82 }
83
84 try_match_ident_ignore_ascii_case! { input,
85 "context-value" if is_context_value_enabled() => {
86 Ok(generic::SVGStrokeDashArray::ContextValue)
87 },
88 "none" => Ok(generic::SVGStrokeDashArray::Values(Default::default())),
89 }
90 }
91}
92
93pub type SVGOpacity = generic::SVGOpacity<Opacity>;
95
96#[repr(u8)]
98#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, ToCss)]
99pub enum PaintOrder {
100 Normal = 0,
102 Fill = 1,
104 Stroke = 2,
106 Markers = 3,
108}
109
110pub const PAINT_ORDER_COUNT: u8 = 3;
112
113pub const PAINT_ORDER_SHIFT: u8 = 2;
115
116pub const PAINT_ORDER_MASK: u8 = 0b11;
118
119#[derive(
130 Clone,
131 Copy,
132 Debug,
133 MallocSizeOf,
134 PartialEq,
135 SpecifiedValueInfo,
136 ToComputedValue,
137 ToResolvedValue,
138 ToShmem,
139 ToTyped,
140)]
141#[repr(transparent)]
142pub struct SVGPaintOrder(pub u8);
143
144impl SVGPaintOrder {
145 pub fn normal() -> Self {
147 SVGPaintOrder(0)
148 }
149
150 pub fn order_at(&self, pos: u8) -> PaintOrder {
152 match (self.0 >> pos * PAINT_ORDER_SHIFT) & PAINT_ORDER_MASK {
153 0 => PaintOrder::Normal,
154 1 => PaintOrder::Fill,
155 2 => PaintOrder::Stroke,
156 3 => PaintOrder::Markers,
157 _ => unreachable!("this cannot happen"),
158 }
159 }
160}
161
162impl Parse for SVGPaintOrder {
163 fn parse<'i, 't>(
164 _context: &ParserContext,
165 input: &mut Parser<'i, 't>,
166 ) -> Result<SVGPaintOrder, ParseError<'i>> {
167 if let Ok(()) = input.try_parse(|i| i.expect_ident_matching("normal")) {
168 return Ok(SVGPaintOrder::normal());
169 }
170
171 let mut value = 0;
172 let mut seen = 0;
175 let mut pos = 0;
176
177 loop {
178 let result: Result<_, ParseError> = input.try_parse(|input| {
179 try_match_ident_ignore_ascii_case! { input,
180 "fill" => Ok(PaintOrder::Fill),
181 "stroke" => Ok(PaintOrder::Stroke),
182 "markers" => Ok(PaintOrder::Markers),
183 }
184 });
185
186 match result {
187 Ok(val) => {
188 if (seen & (1 << val as u8)) != 0 {
189 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
191 }
192
193 value |= (val as u8) << (pos * PAINT_ORDER_SHIFT);
194 seen |= 1 << (val as u8);
195 pos += 1;
196 },
197 Err(_) => break,
198 }
199 }
200
201 if value == 0 {
202 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
204 }
205
206 for i in pos..PAINT_ORDER_COUNT {
208 for paint in 1..(PAINT_ORDER_COUNT + 1) {
209 if (seen & (1 << paint)) == 0 {
211 seen |= 1 << paint;
212 value |= paint << (i * PAINT_ORDER_SHIFT);
213 break;
214 }
215 }
216 }
217
218 Ok(SVGPaintOrder(value))
219 }
220}
221
222impl ToCss for SVGPaintOrder {
223 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
224 where
225 W: Write,
226 {
227 if self.0 == 0 {
228 return dest.write_str("normal");
229 }
230
231 let mut last_pos_to_serialize = 0;
232 for i in (1..PAINT_ORDER_COUNT).rev() {
233 let component = self.order_at(i);
234 let earlier_component = self.order_at(i - 1);
235 if component < earlier_component {
236 last_pos_to_serialize = i - 1;
237 break;
238 }
239 }
240
241 for pos in 0..last_pos_to_serialize + 1 {
242 if pos != 0 {
243 dest.write_char(' ')?
244 }
245 self.order_at(pos).to_css(dest)?;
246 }
247 Ok(())
248 }
249}
250
251#[derive(
253 Clone,
254 Copy,
255 Eq,
256 Debug,
257 Default,
258 MallocSizeOf,
259 PartialEq,
260 SpecifiedValueInfo,
261 ToComputedValue,
262 ToResolvedValue,
263 ToShmem,
264)]
265#[repr(C)]
266pub struct ContextPropertyBits(u8);
267bitflags! {
268 impl ContextPropertyBits: u8 {
269 const FILL = 1 << 0;
271 const STROKE = 1 << 1;
273 const FILL_OPACITY = 1 << 2;
275 const STROKE_OPACITY = 1 << 3;
277 }
278}
279
280#[derive(
283 Clone,
284 Debug,
285 Default,
286 MallocSizeOf,
287 PartialEq,
288 SpecifiedValueInfo,
289 ToComputedValue,
290 ToCss,
291 ToResolvedValue,
292 ToShmem,
293 ToTyped,
294)]
295#[repr(C)]
296pub struct MozContextProperties {
297 #[css(iterable, if_empty = "none")]
298 #[ignore_malloc_size_of = "Arc"]
299 idents: crate::ArcSlice<CustomIdent>,
300 #[css(skip)]
301 bits: ContextPropertyBits,
302}
303
304impl Parse for MozContextProperties {
305 fn parse<'i, 't>(
306 _context: &ParserContext,
307 input: &mut Parser<'i, 't>,
308 ) -> Result<MozContextProperties, ParseError<'i>> {
309 let mut values = vec![];
310 let mut bits = ContextPropertyBits::empty();
311 loop {
312 {
313 let location = input.current_source_location();
314 let ident = input.expect_ident()?;
315
316 if ident.eq_ignore_ascii_case("none") && values.is_empty() {
317 return Ok(Self::default());
318 }
319
320 let ident = CustomIdent::from_ident(location, ident, &["all", "none", "auto"])?;
321
322 if ident.0 == atom!("fill") {
323 bits.insert(ContextPropertyBits::FILL);
324 } else if ident.0 == atom!("stroke") {
325 bits.insert(ContextPropertyBits::STROKE);
326 } else if ident.0 == atom!("fill-opacity") {
327 bits.insert(ContextPropertyBits::FILL_OPACITY);
328 } else if ident.0 == atom!("stroke-opacity") {
329 bits.insert(ContextPropertyBits::STROKE_OPACITY);
330 }
331
332 values.push(ident);
333 }
334
335 let location = input.current_source_location();
336 match input.next() {
337 Ok(&Token::Comma) => continue,
338 Err(..) => break,
339 Ok(other) => return Err(location.new_unexpected_token_error(other.clone())),
340 }
341 }
342
343 if values.is_empty() {
344 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
345 }
346
347 Ok(MozContextProperties {
348 idents: crate::ArcSlice::from_iter(values.into_iter()),
349 bits,
350 })
351 }
352}
353
354#[derive(
358 Animate,
359 Clone,
360 ComputeSquaredDistance,
361 Debug,
362 Deserialize,
363 MallocSizeOf,
364 PartialEq,
365 Serialize,
366 SpecifiedValueInfo,
367 ToAnimatedValue,
368 ToAnimatedZero,
369 ToComputedValue,
370 ToCss,
371 ToResolvedValue,
372 ToShmem,
373 ToTyped,
374)]
375#[repr(C, u8)]
376pub enum DProperty {
377 #[css(function)]
379 Path(SVGPathData),
380 #[animation(error)]
382 None,
383}
384
385impl DProperty {
386 #[inline]
388 pub fn none() -> Self {
389 DProperty::None
390 }
391}
392
393impl Parse for DProperty {
394 fn parse<'i, 't>(
395 context: &ParserContext,
396 input: &mut Parser<'i, 't>,
397 ) -> Result<Self, ParseError<'i>> {
398 if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
400 return Ok(DProperty::none());
401 }
402
403 input.expect_function_matching("path")?;
405 let path_data = input.parse_nested_block(|i| Parse::parse(context, i))?;
406 Ok(DProperty::Path(path_data))
407 }
408}
409
410#[derive(
411 Clone,
412 Copy,
413 Debug,
414 Default,
415 Eq,
416 MallocSizeOf,
417 Parse,
418 PartialEq,
419 SpecifiedValueInfo,
420 ToComputedValue,
421 ToCss,
422 ToResolvedValue,
423 ToShmem,
424 ToTyped,
425)]
426#[css(bitflags(single = "none", mixed = "non-scaling-stroke"))]
427#[repr(C)]
428pub struct VectorEffect(u8);
430bitflags! {
431 impl VectorEffect: u8 {
432 const NONE = 0;
434 const NON_SCALING_STROKE = 1 << 0;
436 }
437}
438
439impl VectorEffect {
440 #[inline]
442 pub fn none() -> Self {
443 Self::NONE
444 }
445}