svg.rs (12693B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ 4 5 //! Specified types for SVG properties. 6 7 use crate::derives::*; 8 use crate::parser::{Parse, ParserContext}; 9 use crate::values::generics::svg as generic; 10 use crate::values::specified::color::Color; 11 use crate::values::specified::url::SpecifiedUrl; 12 use crate::values::specified::AllowQuirks; 13 use crate::values::specified::LengthPercentage; 14 use crate::values::specified::SVGPathData; 15 use crate::values::specified::{NonNegativeLengthPercentage, Opacity}; 16 use crate::values::CustomIdent; 17 use cssparser::{Parser, Token}; 18 use std::fmt::{self, Write}; 19 use style_traits::{CommaWithSpace, CssWriter, ParseError, Separator}; 20 use style_traits::{StyleParseErrorKind, ToCss}; 21 22 /// Specified SVG Paint value 23 pub type SVGPaint = generic::GenericSVGPaint<Color, SpecifiedUrl>; 24 25 /// <length> | <percentage> | <number> | context-value 26 pub type SVGLength = generic::GenericSVGLength<LengthPercentage>; 27 28 /// A non-negative version of SVGLength. 29 pub type SVGWidth = generic::GenericSVGLength<NonNegativeLengthPercentage>; 30 31 /// [ <length> | <percentage> | <number> ]# | context-value 32 pub type SVGStrokeDashArray = generic::GenericSVGStrokeDashArray<NonNegativeLengthPercentage>; 33 34 /// Whether the `context-value` value is enabled. 35 #[cfg(feature = "gecko")] 36 pub fn is_context_value_enabled() -> bool { 37 static_prefs::pref!("gfx.font_rendering.opentype_svg.enabled") 38 } 39 40 /// Whether the `context-value` value is enabled. 41 #[cfg(not(feature = "gecko"))] 42 pub fn is_context_value_enabled() -> bool { 43 false 44 } 45 46 macro_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 69 parse_svg_length!(SVGLength, LengthPercentage); 70 parse_svg_length!(SVGWidth, NonNegativeLengthPercentage); 71 72 impl 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 94 /// <opacity-value> | context-fill-opacity | context-stroke-opacity 95 pub type SVGOpacity = generic::SVGOpacity<Opacity>; 96 97 /// The specified value for a single CSS paint-order property. 98 #[repr(u8)] 99 #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, ToCss)] 100 pub enum PaintOrder { 101 /// `normal` variant 102 Normal = 0, 103 /// `fill` variant 104 Fill = 1, 105 /// `stroke` variant 106 Stroke = 2, 107 /// `markers` variant 108 Markers = 3, 109 } 110 111 /// Number of non-normal components 112 pub const PAINT_ORDER_COUNT: u8 = 3; 113 114 /// Number of bits for each component 115 pub const PAINT_ORDER_SHIFT: u8 = 2; 116 117 /// Mask with above bits set 118 pub const PAINT_ORDER_MASK: u8 = 0b11; 119 120 /// The specified value is tree `PaintOrder` values packed into the 121 /// bitfields below, as a six-bit field, of 3 two-bit pairs 122 /// 123 /// Each pair can be set to FILL, STROKE, or MARKERS 124 /// Lowest significant bit pairs are highest priority. 125 /// `normal` is the empty bitfield. The three pairs are 126 /// never zero in any case other than `normal`. 127 /// 128 /// Higher priority values, i.e. the values specified first, 129 /// will be painted first (and may be covered by paintings of lower priority) 130 #[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 pub struct SVGPaintOrder(pub u8); 144 145 impl SVGPaintOrder { 146 /// Get default `paint-order` with `0` 147 pub fn normal() -> Self { 148 SVGPaintOrder(0) 149 } 150 151 /// Get variant of `paint-order` 152 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 163 impl 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 // bitfield representing what we've seen so far 174 // bit 1 is fill, bit 2 is stroke, bit 3 is markers 175 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 // don't parse the same ident twice 191 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 // Couldn't find any keyword 204 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); 205 } 206 207 // fill in rest 208 for i in pos..PAINT_ORDER_COUNT { 209 for paint in 1..(PAINT_ORDER_COUNT + 1) { 210 // if not seen, set bit at position, mark as seen 211 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 223 impl 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 /// The context properties we understand. 253 #[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)] 267 pub struct ContextPropertyBits(u8); 268 bitflags! { 269 impl ContextPropertyBits: u8 { 270 /// `fill` 271 const FILL = 1 << 0; 272 /// `stroke` 273 const STROKE = 1 << 1; 274 /// `fill-opacity` 275 const FILL_OPACITY = 1 << 2; 276 /// `stroke-opacity` 277 const STROKE_OPACITY = 1 << 3; 278 } 279 } 280 281 /// Specified MozContextProperties value. 282 /// Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-context-properties) 283 #[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)] 297 pub 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 305 impl 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 /// The svg d property type. 356 /// 357 /// https://svgwg.org/svg2-draft/paths.html#TheDProperty 358 #[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)] 377 pub enum DProperty { 378 /// Path value for path(<string>) or just a <string>. 379 #[css(function)] 380 Path(SVGPathData), 381 /// None value. 382 #[animation(error)] 383 None, 384 } 385 386 impl DProperty { 387 /// return none. 388 #[inline] 389 pub fn none() -> Self { 390 DProperty::None 391 } 392 } 393 394 impl Parse for DProperty { 395 fn parse<'i, 't>( 396 context: &ParserContext, 397 input: &mut Parser<'i, 't>, 398 ) -> Result<Self, ParseError<'i>> { 399 // Parse none. 400 if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() { 401 return Ok(DProperty::none()); 402 } 403 404 // Parse possible functions. 405 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)] 429 /// https://svgwg.org/svg2-draft/coords.html#VectorEffects 430 pub struct VectorEffect(u8); 431 bitflags! { 432 impl VectorEffect: u8 { 433 /// `none` 434 const NONE = 0; 435 /// `non-scaling-stroke` 436 const NON_SCALING_STROKE = 1 << 0; 437 } 438 } 439 440 impl VectorEffect { 441 /// Returns the initial value of vector-effect 442 #[inline] 443 pub fn none() -> Self { 444 Self::NONE 445 } 446 }