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