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)]
140#[repr(transparent)]
141pub struct SVGPaintOrder(pub u8);
142
143impl SVGPaintOrder {
144 pub fn normal() -> Self {
146 SVGPaintOrder(0)
147 }
148
149 pub fn order_at(&self, pos: u8) -> PaintOrder {
151 match (self.0 >> pos * PAINT_ORDER_SHIFT) & PAINT_ORDER_MASK {
152 0 => PaintOrder::Normal,
153 1 => PaintOrder::Fill,
154 2 => PaintOrder::Stroke,
155 3 => PaintOrder::Markers,
156 _ => unreachable!("this cannot happen"),
157 }
158 }
159}
160
161impl Parse for SVGPaintOrder {
162 fn parse<'i, 't>(
163 _context: &ParserContext,
164 input: &mut Parser<'i, 't>,
165 ) -> Result<SVGPaintOrder, ParseError<'i>> {
166 if let Ok(()) = input.try_parse(|i| i.expect_ident_matching("normal")) {
167 return Ok(SVGPaintOrder::normal());
168 }
169
170 let mut value = 0;
171 let mut seen = 0;
174 let mut pos = 0;
175
176 loop {
177 let result: Result<_, ParseError> = input.try_parse(|input| {
178 try_match_ident_ignore_ascii_case! { input,
179 "fill" => Ok(PaintOrder::Fill),
180 "stroke" => Ok(PaintOrder::Stroke),
181 "markers" => Ok(PaintOrder::Markers),
182 }
183 });
184
185 match result {
186 Ok(val) => {
187 if (seen & (1 << val as u8)) != 0 {
188 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
190 }
191
192 value |= (val as u8) << (pos * PAINT_ORDER_SHIFT);
193 seen |= 1 << (val as u8);
194 pos += 1;
195 },
196 Err(_) => break,
197 }
198 }
199
200 if value == 0 {
201 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
203 }
204
205 for i in pos..PAINT_ORDER_COUNT {
207 for paint in 1..(PAINT_ORDER_COUNT + 1) {
208 if (seen & (1 << paint)) == 0 {
210 seen |= 1 << paint;
211 value |= paint << (i * PAINT_ORDER_SHIFT);
212 break;
213 }
214 }
215 }
216
217 Ok(SVGPaintOrder(value))
218 }
219}
220
221impl ToCss for SVGPaintOrder {
222 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
223 where
224 W: Write,
225 {
226 if self.0 == 0 {
227 return dest.write_str("normal");
228 }
229
230 let mut last_pos_to_serialize = 0;
231 for i in (1..PAINT_ORDER_COUNT).rev() {
232 let component = self.order_at(i);
233 let earlier_component = self.order_at(i - 1);
234 if component < earlier_component {
235 last_pos_to_serialize = i - 1;
236 break;
237 }
238 }
239
240 for pos in 0..last_pos_to_serialize + 1 {
241 if pos != 0 {
242 dest.write_char(' ')?
243 }
244 self.order_at(pos).to_css(dest)?;
245 }
246 Ok(())
247 }
248}
249
250#[derive(
252 Clone,
253 Copy,
254 Eq,
255 Debug,
256 Default,
257 MallocSizeOf,
258 PartialEq,
259 SpecifiedValueInfo,
260 ToComputedValue,
261 ToResolvedValue,
262 ToShmem,
263)]
264#[repr(C)]
265pub struct ContextPropertyBits(u8);
266bitflags! {
267 impl ContextPropertyBits: u8 {
268 const FILL = 1 << 0;
270 const STROKE = 1 << 1;
272 const FILL_OPACITY = 1 << 2;
274 const STROKE_OPACITY = 1 << 3;
276 }
277}
278
279#[derive(
282 Clone,
283 Debug,
284 Default,
285 MallocSizeOf,
286 PartialEq,
287 SpecifiedValueInfo,
288 ToComputedValue,
289 ToCss,
290 ToResolvedValue,
291 ToShmem,
292)]
293#[repr(C)]
294pub struct MozContextProperties {
295 #[css(iterable, if_empty = "none")]
296 #[ignore_malloc_size_of = "Arc"]
297 idents: crate::ArcSlice<CustomIdent>,
298 #[css(skip)]
299 bits: ContextPropertyBits,
300}
301
302impl Parse for MozContextProperties {
303 fn parse<'i, 't>(
304 _context: &ParserContext,
305 input: &mut Parser<'i, 't>,
306 ) -> Result<MozContextProperties, ParseError<'i>> {
307 let mut values = vec![];
308 let mut bits = ContextPropertyBits::empty();
309 loop {
310 {
311 let location = input.current_source_location();
312 let ident = input.expect_ident()?;
313
314 if ident.eq_ignore_ascii_case("none") && values.is_empty() {
315 return Ok(Self::default());
316 }
317
318 let ident = CustomIdent::from_ident(location, ident, &["all", "none", "auto"])?;
319
320 if ident.0 == atom!("fill") {
321 bits.insert(ContextPropertyBits::FILL);
322 } else if ident.0 == atom!("stroke") {
323 bits.insert(ContextPropertyBits::STROKE);
324 } else if ident.0 == atom!("fill-opacity") {
325 bits.insert(ContextPropertyBits::FILL_OPACITY);
326 } else if ident.0 == atom!("stroke-opacity") {
327 bits.insert(ContextPropertyBits::STROKE_OPACITY);
328 }
329
330 values.push(ident);
331 }
332
333 let location = input.current_source_location();
334 match input.next() {
335 Ok(&Token::Comma) => continue,
336 Err(..) => break,
337 Ok(other) => return Err(location.new_unexpected_token_error(other.clone())),
338 }
339 }
340
341 if values.is_empty() {
342 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
343 }
344
345 Ok(MozContextProperties {
346 idents: crate::ArcSlice::from_iter(values.into_iter()),
347 bits,
348 })
349 }
350}
351
352#[derive(
356 Animate,
357 Clone,
358 ComputeSquaredDistance,
359 Debug,
360 Deserialize,
361 MallocSizeOf,
362 PartialEq,
363 Serialize,
364 SpecifiedValueInfo,
365 ToAnimatedValue,
366 ToAnimatedZero,
367 ToComputedValue,
368 ToCss,
369 ToResolvedValue,
370 ToShmem,
371)]
372#[repr(C, u8)]
373pub enum DProperty {
374 #[css(function)]
376 Path(SVGPathData),
377 #[animation(error)]
379 None,
380}
381
382impl DProperty {
383 #[inline]
385 pub fn none() -> Self {
386 DProperty::None
387 }
388}
389
390impl Parse for DProperty {
391 fn parse<'i, 't>(
392 context: &ParserContext,
393 input: &mut Parser<'i, 't>,
394 ) -> Result<Self, ParseError<'i>> {
395 if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
397 return Ok(DProperty::none());
398 }
399
400 input.expect_function_matching("path")?;
402 let path_data = input.parse_nested_block(|i| Parse::parse(context, i))?;
403 Ok(DProperty::Path(path_data))
404 }
405}
406
407#[derive(
408 Clone,
409 Copy,
410 Debug,
411 Default,
412 Eq,
413 MallocSizeOf,
414 Parse,
415 PartialEq,
416 SpecifiedValueInfo,
417 ToComputedValue,
418 ToCss,
419 ToResolvedValue,
420 ToShmem,
421)]
422#[css(bitflags(single = "none", mixed = "non-scaling-stroke"))]
423#[repr(C)]
424pub struct VectorEffect(u8);
426bitflags! {
427 impl VectorEffect: u8 {
428 const NONE = 0;
430 const NON_SCALING_STROKE = 1 << 0;
432 }
433}
434
435impl VectorEffect {
436 #[inline]
438 pub fn none() -> Self {
439 Self::NONE
440 }
441}