image.rs (49609B)
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 //! CSS handling for the specified value of 6 //! [`image`][image]s 7 //! 8 //! [image]: https://drafts.csswg.org/css-images/#image-values 9 10 use crate::color::mix::ColorInterpolationMethod; 11 use crate::derives::*; 12 use crate::parser::{Parse, ParserContext}; 13 use crate::stylesheets::CorsMode; 14 use crate::values::generics::color::{ColorMixFlags, GenericLightDark}; 15 use crate::values::generics::image::{ 16 self as generic, Circle, Ellipse, GradientCompatMode, ShapeExtent, 17 }; 18 use crate::values::generics::image::{GradientFlags, PaintWorklet}; 19 use crate::values::generics::position::Position as GenericPosition; 20 use crate::values::generics::NonNegative; 21 use crate::values::specified::position::{HorizontalPositionKeyword, VerticalPositionKeyword}; 22 use crate::values::specified::position::{Position, PositionComponent, Side}; 23 use crate::values::specified::url::SpecifiedUrl; 24 use crate::values::specified::{ 25 Angle, AngleOrPercentage, Color, Length, LengthPercentage, NonNegativeLength, 26 NonNegativeLengthPercentage, Resolution, 27 }; 28 use crate::values::specified::{Number, NumberOrPercentage, Percentage}; 29 use crate::Atom; 30 use cssparser::{match_ignore_ascii_case, Delimiter, Parser, Token}; 31 use selectors::parser::SelectorParseErrorKind; 32 use std::cmp::Ordering; 33 use std::fmt::{self, Write}; 34 use style_traits::{CssType, CssWriter, KeywordsCollectFn, ParseError}; 35 use style_traits::{SpecifiedValueInfo, StyleParseErrorKind, ToCss}; 36 37 #[inline] 38 fn gradient_color_interpolation_method_enabled() -> bool { 39 static_prefs::pref!("layout.css.gradient-color-interpolation-method.enabled") 40 } 41 42 /// Specified values for an image according to CSS-IMAGES. 43 /// <https://drafts.csswg.org/css-images/#image-values> 44 pub type Image = generic::Image<Gradient, SpecifiedUrl, Color, Percentage, Resolution>; 45 46 // Images should remain small, see https://github.com/servo/servo/pull/18430 47 size_of_test!(Image, 16); 48 49 /// Specified values for a CSS gradient. 50 /// <https://drafts.csswg.org/css-images/#gradients> 51 pub type Gradient = generic::Gradient< 52 LineDirection, 53 Length, 54 LengthPercentage, 55 Position, 56 Angle, 57 AngleOrPercentage, 58 Color, 59 >; 60 61 /// Specified values for CSS cross-fade 62 /// cross-fade( CrossFadeElement, ...) 63 /// <https://drafts.csswg.org/css-images-4/#cross-fade-function> 64 pub type CrossFade = generic::CrossFade<Image, Color, Percentage>; 65 /// CrossFadeElement = percent? CrossFadeImage 66 pub type CrossFadeElement = generic::CrossFadeElement<Image, Color, Percentage>; 67 /// CrossFadeImage = image | color 68 pub type CrossFadeImage = generic::CrossFadeImage<Image, Color>; 69 70 /// `image-set()` 71 pub type ImageSet = generic::ImageSet<Image, Resolution>; 72 73 /// Each of the arguments to `image-set()` 74 pub type ImageSetItem = generic::ImageSetItem<Image, Resolution>; 75 76 type LengthPercentageItemList = crate::OwnedSlice<generic::GradientItem<Color, LengthPercentage>>; 77 78 impl Color { 79 fn has_modern_syntax(&self) -> bool { 80 match self { 81 Self::Absolute(absolute) => !absolute.color.is_legacy_syntax(), 82 Self::ColorMix(mix) => { 83 if mix.flags.contains(ColorMixFlags::RESULT_IN_MODERN_SYNTAX) { 84 true 85 } else { 86 mix.left.has_modern_syntax() || mix.right.has_modern_syntax() 87 } 88 }, 89 Self::LightDark(ld) => ld.light.has_modern_syntax() || ld.dark.has_modern_syntax(), 90 91 // The default is that this color doesn't have any modern syntax. 92 _ => false, 93 } 94 } 95 } 96 97 fn default_color_interpolation_method<T>( 98 items: &[generic::GradientItem<Color, T>], 99 ) -> ColorInterpolationMethod { 100 let has_modern_syntax_item = items.iter().any(|item| match item { 101 generic::GenericGradientItem::SimpleColorStop(color) => color.has_modern_syntax(), 102 generic::GenericGradientItem::ComplexColorStop { color, .. } => color.has_modern_syntax(), 103 generic::GenericGradientItem::InterpolationHint(_) => false, 104 }); 105 106 if has_modern_syntax_item { 107 ColorInterpolationMethod::default() 108 } else { 109 ColorInterpolationMethod::srgb() 110 } 111 } 112 113 fn image_light_dark_enabled(context: &ParserContext) -> bool { 114 context.chrome_rules_enabled() || static_prefs::pref!("layout.css.light-dark.images.enabled") 115 } 116 117 #[cfg(feature = "gecko")] 118 fn cross_fade_enabled() -> bool { 119 static_prefs::pref!("layout.css.cross-fade.enabled") 120 } 121 122 #[cfg(feature = "servo")] 123 fn cross_fade_enabled() -> bool { 124 false 125 } 126 127 impl SpecifiedValueInfo for Gradient { 128 const SUPPORTED_TYPES: u8 = CssType::GRADIENT; 129 130 fn collect_completion_keywords(f: KeywordsCollectFn) { 131 // This list here should keep sync with that in Gradient::parse. 132 f(&[ 133 "linear-gradient", 134 "-webkit-linear-gradient", 135 "-moz-linear-gradient", 136 "repeating-linear-gradient", 137 "-webkit-repeating-linear-gradient", 138 "-moz-repeating-linear-gradient", 139 "radial-gradient", 140 "-webkit-radial-gradient", 141 "-moz-radial-gradient", 142 "repeating-radial-gradient", 143 "-webkit-repeating-radial-gradient", 144 "-moz-repeating-radial-gradient", 145 "-webkit-gradient", 146 "conic-gradient", 147 "repeating-conic-gradient", 148 ]); 149 } 150 } 151 152 // Need to manually implement as whether or not cross-fade shows up in 153 // completions & etc is dependent on it being enabled. 154 impl<Image, Color, Percentage> SpecifiedValueInfo for generic::CrossFade<Image, Color, Percentage> { 155 const SUPPORTED_TYPES: u8 = 0; 156 157 fn collect_completion_keywords(f: KeywordsCollectFn) { 158 if cross_fade_enabled() { 159 f(&["cross-fade"]); 160 } 161 } 162 } 163 164 impl<Image, Resolution> SpecifiedValueInfo for generic::ImageSet<Image, Resolution> { 165 const SUPPORTED_TYPES: u8 = 0; 166 167 fn collect_completion_keywords(f: KeywordsCollectFn) { 168 f(&["image-set"]); 169 } 170 } 171 172 /// A specified gradient line direction. 173 /// 174 /// FIXME(emilio): This should be generic over Angle. 175 #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] 176 pub enum LineDirection { 177 /// An angular direction. 178 Angle(Angle), 179 /// A horizontal direction. 180 Horizontal(HorizontalPositionKeyword), 181 /// A vertical direction. 182 Vertical(VerticalPositionKeyword), 183 /// A direction towards a corner of a box. 184 Corner(HorizontalPositionKeyword, VerticalPositionKeyword), 185 } 186 187 /// A specified ending shape. 188 pub type EndingShape = generic::EndingShape<NonNegativeLength, NonNegativeLengthPercentage>; 189 190 bitflags! { 191 #[derive(Clone, Copy)] 192 struct ParseImageFlags: u8 { 193 const FORBID_NONE = 1 << 0; 194 const FORBID_IMAGE_SET = 1 << 1; 195 const FORBID_NON_URL = 1 << 2; 196 } 197 } 198 199 impl Parse for Image { 200 fn parse<'i, 't>( 201 context: &ParserContext, 202 input: &mut Parser<'i, 't>, 203 ) -> Result<Image, ParseError<'i>> { 204 Image::parse_with_cors_mode(context, input, CorsMode::None, ParseImageFlags::empty()) 205 } 206 } 207 208 impl Image { 209 fn parse_with_cors_mode<'i, 't>( 210 context: &ParserContext, 211 input: &mut Parser<'i, 't>, 212 cors_mode: CorsMode, 213 flags: ParseImageFlags, 214 ) -> Result<Image, ParseError<'i>> { 215 if !flags.contains(ParseImageFlags::FORBID_NONE) 216 && input.try_parse(|i| i.expect_ident_matching("none")).is_ok() 217 { 218 return Ok(generic::Image::None); 219 } 220 221 if let Ok(url) = 222 input.try_parse(|input| SpecifiedUrl::parse_with_cors_mode(context, input, cors_mode)) 223 { 224 return Ok(generic::Image::Url(url)); 225 } 226 227 if !flags.contains(ParseImageFlags::FORBID_IMAGE_SET) { 228 if let Ok(is) = 229 input.try_parse(|input| ImageSet::parse(context, input, cors_mode, flags)) 230 { 231 return Ok(generic::Image::ImageSet(Box::new(is))); 232 } 233 } 234 235 if flags.contains(ParseImageFlags::FORBID_NON_URL) { 236 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); 237 } 238 239 if let Ok(gradient) = input.try_parse(|i| Gradient::parse(context, i)) { 240 return Ok(generic::Image::Gradient(Box::new(gradient))); 241 } 242 243 let function = input.expect_function()?.clone(); 244 input.parse_nested_block(|input| Ok(match_ignore_ascii_case! { &function, 245 #[cfg(feature = "servo")] 246 "paint" => Self::PaintWorklet(Box::new(<PaintWorklet>::parse_args(context, input)?)), 247 "cross-fade" if cross_fade_enabled() => Self::CrossFade(Box::new(CrossFade::parse_args(context, input, cors_mode, flags)?)), 248 "light-dark" if image_light_dark_enabled(context) => Self::LightDark(Box::new(GenericLightDark::parse_args_with(input, |input| { 249 Self::parse_with_cors_mode(context, input, cors_mode, flags) 250 })?)), 251 #[cfg(feature = "gecko")] 252 "-moz-element" => Self::Element(Self::parse_element(input)?), 253 #[cfg(feature = "gecko")] 254 "-moz-symbolic-icon" if context.chrome_rules_enabled() => Self::MozSymbolicIcon(input.expect_ident()?.as_ref().into()), 255 _ => return Err(input.new_custom_error(StyleParseErrorKind::UnexpectedFunction(function))), 256 })) 257 } 258 } 259 260 impl Image { 261 /// Creates an already specified image value from an already resolved URL 262 /// for insertion in the cascade. 263 #[cfg(feature = "servo")] 264 pub fn for_cascade(url: ::servo_arc::Arc<::url::Url>) -> Self { 265 use crate::values::CssUrl; 266 generic::Image::Url(CssUrl::for_cascade(url)) 267 } 268 269 /// Parses a `-moz-element(# <element-id>)`. 270 #[cfg(feature = "gecko")] 271 fn parse_element<'i>(input: &mut Parser<'i, '_>) -> Result<Atom, ParseError<'i>> { 272 let location = input.current_source_location(); 273 Ok(match *input.next()? { 274 Token::IDHash(ref id) => Atom::from(id.as_ref()), 275 ref t => return Err(location.new_unexpected_token_error(t.clone())), 276 }) 277 } 278 279 /// Provides an alternate method for parsing that associates the URL with 280 /// anonymous CORS headers. 281 pub fn parse_with_cors_anonymous<'i, 't>( 282 context: &ParserContext, 283 input: &mut Parser<'i, 't>, 284 ) -> Result<Image, ParseError<'i>> { 285 Self::parse_with_cors_mode( 286 context, 287 input, 288 CorsMode::Anonymous, 289 ParseImageFlags::empty(), 290 ) 291 } 292 293 /// Provides an alternate method for parsing, but forbidding `none` 294 pub fn parse_forbid_none<'i, 't>( 295 context: &ParserContext, 296 input: &mut Parser<'i, 't>, 297 ) -> Result<Image, ParseError<'i>> { 298 Self::parse_with_cors_mode(context, input, CorsMode::None, ParseImageFlags::FORBID_NONE) 299 } 300 301 /// Provides an alternate method for parsing, but only for urls. 302 pub fn parse_only_url<'i, 't>( 303 context: &ParserContext, 304 input: &mut Parser<'i, 't>, 305 ) -> Result<Image, ParseError<'i>> { 306 Self::parse_with_cors_mode( 307 context, 308 input, 309 CorsMode::None, 310 ParseImageFlags::FORBID_NONE | ParseImageFlags::FORBID_NON_URL, 311 ) 312 } 313 } 314 315 impl CrossFade { 316 /// cross-fade() = cross-fade( <cf-image># ) 317 fn parse_args<'i, 't>( 318 context: &ParserContext, 319 input: &mut Parser<'i, 't>, 320 cors_mode: CorsMode, 321 flags: ParseImageFlags, 322 ) -> Result<Self, ParseError<'i>> { 323 let elements = crate::OwnedSlice::from(input.parse_comma_separated(|input| { 324 CrossFadeElement::parse(context, input, cors_mode, flags) 325 })?); 326 Ok(Self { elements }) 327 } 328 } 329 330 impl CrossFadeElement { 331 fn parse_percentage<'i, 't>( 332 context: &ParserContext, 333 input: &mut Parser<'i, 't>, 334 ) -> Option<Percentage> { 335 // We clamp our values here as this is the way that Safari and Chrome's 336 // implementation handle out-of-bounds percentages but whether or not 337 // this behavior follows the specification is still being discussed. 338 // See: <https://github.com/w3c/csswg-drafts/issues/5333> 339 input 340 .try_parse(|input| Percentage::parse_non_negative(context, input)) 341 .ok() 342 .map(|p| p.clamp_to_hundred()) 343 } 344 345 /// <cf-image> = <percentage>? && [ <image> | <color> ] 346 fn parse<'i, 't>( 347 context: &ParserContext, 348 input: &mut Parser<'i, 't>, 349 cors_mode: CorsMode, 350 flags: ParseImageFlags, 351 ) -> Result<Self, ParseError<'i>> { 352 // Try and parse a leading percent sign. 353 let mut percent = Self::parse_percentage(context, input); 354 // Parse the image 355 let image = CrossFadeImage::parse(context, input, cors_mode, flags)?; 356 // Try and parse a trailing percent sign. 357 if percent.is_none() { 358 percent = Self::parse_percentage(context, input); 359 } 360 Ok(Self { 361 percent: percent.into(), 362 image, 363 }) 364 } 365 } 366 367 impl CrossFadeImage { 368 fn parse<'i, 't>( 369 context: &ParserContext, 370 input: &mut Parser<'i, 't>, 371 cors_mode: CorsMode, 372 flags: ParseImageFlags, 373 ) -> Result<Self, ParseError<'i>> { 374 if let Ok(image) = input.try_parse(|input| { 375 Image::parse_with_cors_mode( 376 context, 377 input, 378 cors_mode, 379 flags | ParseImageFlags::FORBID_NONE, 380 ) 381 }) { 382 return Ok(Self::Image(image)); 383 } 384 Ok(Self::Color(Color::parse(context, input)?)) 385 } 386 } 387 388 impl ImageSet { 389 fn parse<'i, 't>( 390 context: &ParserContext, 391 input: &mut Parser<'i, 't>, 392 cors_mode: CorsMode, 393 flags: ParseImageFlags, 394 ) -> Result<Self, ParseError<'i>> { 395 let function = input.expect_function()?; 396 match_ignore_ascii_case! { &function, 397 "-webkit-image-set" | "image-set" => {}, 398 _ => { 399 let func = function.clone(); 400 return Err(input.new_custom_error(StyleParseErrorKind::UnexpectedFunction(func))); 401 } 402 } 403 let items = input.parse_nested_block(|input| { 404 input.parse_comma_separated(|input| { 405 ImageSetItem::parse(context, input, cors_mode, flags) 406 }) 407 })?; 408 Ok(Self { 409 selected_index: std::usize::MAX, 410 items: items.into(), 411 }) 412 } 413 } 414 415 impl ImageSetItem { 416 fn parse_type<'i>(p: &mut Parser<'i, '_>) -> Result<crate::OwnedStr, ParseError<'i>> { 417 p.expect_function_matching("type")?; 418 p.parse_nested_block(|input| Ok(input.expect_string()?.as_ref().to_owned().into())) 419 } 420 421 fn parse<'i, 't>( 422 context: &ParserContext, 423 input: &mut Parser<'i, 't>, 424 cors_mode: CorsMode, 425 flags: ParseImageFlags, 426 ) -> Result<Self, ParseError<'i>> { 427 let image = match input.try_parse(|i| i.expect_url_or_string()) { 428 Ok(url) => Image::Url(SpecifiedUrl::parse_from_string( 429 url.as_ref().into(), 430 context, 431 cors_mode, 432 )), 433 Err(..) => Image::parse_with_cors_mode( 434 context, 435 input, 436 cors_mode, 437 flags | ParseImageFlags::FORBID_NONE | ParseImageFlags::FORBID_IMAGE_SET, 438 )?, 439 }; 440 441 let mut resolution = input 442 .try_parse(|input| Resolution::parse(context, input)) 443 .ok(); 444 let mime_type = input.try_parse(Self::parse_type).ok(); 445 446 // Try to parse resolution after type(). 447 if mime_type.is_some() && resolution.is_none() { 448 resolution = input 449 .try_parse(|input| Resolution::parse(context, input)) 450 .ok(); 451 } 452 453 let resolution = resolution.unwrap_or_else(|| Resolution::from_x(1.0)); 454 let has_mime_type = mime_type.is_some(); 455 let mime_type = mime_type.unwrap_or_default(); 456 457 Ok(Self { 458 image, 459 resolution, 460 has_mime_type, 461 mime_type, 462 }) 463 } 464 } 465 466 impl Parse for Gradient { 467 fn parse<'i, 't>( 468 context: &ParserContext, 469 input: &mut Parser<'i, 't>, 470 ) -> Result<Self, ParseError<'i>> { 471 enum Shape { 472 Linear, 473 Radial, 474 Conic, 475 } 476 477 let func = input.expect_function()?; 478 let (shape, repeating, compat_mode) = match_ignore_ascii_case! { &func, 479 "linear-gradient" => { 480 (Shape::Linear, false, GradientCompatMode::Modern) 481 }, 482 "-webkit-linear-gradient" => { 483 (Shape::Linear, false, GradientCompatMode::WebKit) 484 }, 485 #[cfg(feature = "gecko")] 486 "-moz-linear-gradient" => { 487 (Shape::Linear, false, GradientCompatMode::Moz) 488 }, 489 "repeating-linear-gradient" => { 490 (Shape::Linear, true, GradientCompatMode::Modern) 491 }, 492 "-webkit-repeating-linear-gradient" => { 493 (Shape::Linear, true, GradientCompatMode::WebKit) 494 }, 495 #[cfg(feature = "gecko")] 496 "-moz-repeating-linear-gradient" => { 497 (Shape::Linear, true, GradientCompatMode::Moz) 498 }, 499 "radial-gradient" => { 500 (Shape::Radial, false, GradientCompatMode::Modern) 501 }, 502 "-webkit-radial-gradient" => { 503 (Shape::Radial, false, GradientCompatMode::WebKit) 504 }, 505 #[cfg(feature = "gecko")] 506 "-moz-radial-gradient" => { 507 (Shape::Radial, false, GradientCompatMode::Moz) 508 }, 509 "repeating-radial-gradient" => { 510 (Shape::Radial, true, GradientCompatMode::Modern) 511 }, 512 "-webkit-repeating-radial-gradient" => { 513 (Shape::Radial, true, GradientCompatMode::WebKit) 514 }, 515 #[cfg(feature = "gecko")] 516 "-moz-repeating-radial-gradient" => { 517 (Shape::Radial, true, GradientCompatMode::Moz) 518 }, 519 "conic-gradient" => { 520 (Shape::Conic, false, GradientCompatMode::Modern) 521 }, 522 "repeating-conic-gradient" => { 523 (Shape::Conic, true, GradientCompatMode::Modern) 524 }, 525 "-webkit-gradient" => { 526 return input.parse_nested_block(|i| { 527 Self::parse_webkit_gradient_argument(context, i) 528 }); 529 }, 530 _ => { 531 let func = func.clone(); 532 return Err(input.new_custom_error(StyleParseErrorKind::UnexpectedFunction(func))); 533 } 534 }; 535 536 Ok(input.parse_nested_block(|i| { 537 Ok(match shape { 538 Shape::Linear => Self::parse_linear(context, i, repeating, compat_mode)?, 539 Shape::Radial => Self::parse_radial(context, i, repeating, compat_mode)?, 540 Shape::Conic => Self::parse_conic(context, i, repeating)?, 541 }) 542 })?) 543 } 544 } 545 546 impl Gradient { 547 fn parse_webkit_gradient_argument<'i, 't>( 548 context: &ParserContext, 549 input: &mut Parser<'i, 't>, 550 ) -> Result<Self, ParseError<'i>> { 551 use crate::values::specified::position::{ 552 HorizontalPositionKeyword as X, VerticalPositionKeyword as Y, 553 }; 554 type Point = GenericPosition<Component<X>, Component<Y>>; 555 556 #[derive(Clone, Copy, Parse)] 557 enum Component<S> { 558 Center, 559 Number(NumberOrPercentage), 560 Side(S), 561 } 562 563 fn line_direction_from_points(first: Point, second: Point) -> LineDirection { 564 let h_ord = first.horizontal.partial_cmp(&second.horizontal); 565 let v_ord = first.vertical.partial_cmp(&second.vertical); 566 let (h, v) = match (h_ord, v_ord) { 567 (Some(h), Some(v)) => (h, v), 568 _ => return LineDirection::Vertical(Y::Bottom), 569 }; 570 match (h, v) { 571 (Ordering::Less, Ordering::Less) => LineDirection::Corner(X::Right, Y::Bottom), 572 (Ordering::Less, Ordering::Equal) => LineDirection::Horizontal(X::Right), 573 (Ordering::Less, Ordering::Greater) => LineDirection::Corner(X::Right, Y::Top), 574 (Ordering::Equal, Ordering::Greater) => LineDirection::Vertical(Y::Top), 575 (Ordering::Equal, Ordering::Equal) | (Ordering::Equal, Ordering::Less) => { 576 LineDirection::Vertical(Y::Bottom) 577 }, 578 (Ordering::Greater, Ordering::Less) => LineDirection::Corner(X::Left, Y::Bottom), 579 (Ordering::Greater, Ordering::Equal) => LineDirection::Horizontal(X::Left), 580 (Ordering::Greater, Ordering::Greater) => LineDirection::Corner(X::Left, Y::Top), 581 } 582 } 583 584 impl Parse for Point { 585 fn parse<'i, 't>( 586 context: &ParserContext, 587 input: &mut Parser<'i, 't>, 588 ) -> Result<Self, ParseError<'i>> { 589 input.try_parse(|i| { 590 let x = Component::parse(context, i)?; 591 let y = Component::parse(context, i)?; 592 593 Ok(Self::new(x, y)) 594 }) 595 } 596 } 597 598 impl<S: Side> Into<NumberOrPercentage> for Component<S> { 599 fn into(self) -> NumberOrPercentage { 600 match self { 601 Component::Center => NumberOrPercentage::Percentage(Percentage::new(0.5)), 602 Component::Number(number) => number, 603 Component::Side(side) => { 604 let p = if side.is_start() { 605 Percentage::zero() 606 } else { 607 Percentage::hundred() 608 }; 609 NumberOrPercentage::Percentage(p) 610 }, 611 } 612 } 613 } 614 615 impl<S: Side> Into<PositionComponent<S>> for Component<S> { 616 fn into(self) -> PositionComponent<S> { 617 match self { 618 Component::Center => PositionComponent::Center, 619 Component::Number(NumberOrPercentage::Number(number)) => { 620 PositionComponent::Length(Length::from_px(number.value).into()) 621 }, 622 Component::Number(NumberOrPercentage::Percentage(p)) => { 623 PositionComponent::Length(p.into()) 624 }, 625 Component::Side(side) => PositionComponent::Side(side, None), 626 } 627 } 628 } 629 630 impl<S: Copy + Side> Component<S> { 631 fn partial_cmp(&self, other: &Self) -> Option<Ordering> { 632 match ((*self).into(), (*other).into()) { 633 (NumberOrPercentage::Percentage(a), NumberOrPercentage::Percentage(b)) => { 634 a.get().partial_cmp(&b.get()) 635 }, 636 (NumberOrPercentage::Number(a), NumberOrPercentage::Number(b)) => { 637 a.value.partial_cmp(&b.value) 638 }, 639 (_, _) => None, 640 } 641 } 642 } 643 644 let ident = input.expect_ident_cloned()?; 645 input.expect_comma()?; 646 647 Ok(match_ignore_ascii_case! { &ident, 648 "linear" => { 649 let first = Point::parse(context, input)?; 650 input.expect_comma()?; 651 let second = Point::parse(context, input)?; 652 653 let direction = line_direction_from_points(first, second); 654 let items = Gradient::parse_webkit_gradient_stops(context, input, false)?; 655 656 generic::Gradient::Linear { 657 direction, 658 color_interpolation_method: ColorInterpolationMethod::srgb(), 659 items, 660 // Legacy gradients always use srgb as a default. 661 flags: generic::GradientFlags::HAS_DEFAULT_COLOR_INTERPOLATION_METHOD, 662 compat_mode: GradientCompatMode::Modern, 663 } 664 }, 665 "radial" => { 666 let first_point = Point::parse(context, input)?; 667 input.expect_comma()?; 668 let first_radius = Number::parse_non_negative(context, input)?; 669 input.expect_comma()?; 670 let second_point = Point::parse(context, input)?; 671 input.expect_comma()?; 672 let second_radius = Number::parse_non_negative(context, input)?; 673 674 let (reverse_stops, point, radius) = if second_radius.value >= first_radius.value { 675 (false, second_point, second_radius) 676 } else { 677 (true, first_point, first_radius) 678 }; 679 680 let rad = Circle::Radius(NonNegative(Length::from_px(radius.value))); 681 let shape = generic::EndingShape::Circle(rad); 682 let position = Position::new(point.horizontal.into(), point.vertical.into()); 683 let items = Gradient::parse_webkit_gradient_stops(context, input, reverse_stops)?; 684 685 generic::Gradient::Radial { 686 shape, 687 position, 688 color_interpolation_method: ColorInterpolationMethod::srgb(), 689 items, 690 // Legacy gradients always use srgb as a default. 691 flags: generic::GradientFlags::HAS_DEFAULT_COLOR_INTERPOLATION_METHOD, 692 compat_mode: GradientCompatMode::Modern, 693 } 694 }, 695 _ => { 696 let e = SelectorParseErrorKind::UnexpectedIdent(ident.clone()); 697 return Err(input.new_custom_error(e)); 698 }, 699 }) 700 } 701 702 fn parse_webkit_gradient_stops<'i, 't>( 703 context: &ParserContext, 704 input: &mut Parser<'i, 't>, 705 reverse_stops: bool, 706 ) -> Result<LengthPercentageItemList, ParseError<'i>> { 707 let mut items = input 708 .try_parse(|i| { 709 i.expect_comma()?; 710 i.parse_comma_separated(|i| { 711 let function = i.expect_function()?.clone(); 712 let (color, mut p) = i.parse_nested_block(|i| { 713 let p = match_ignore_ascii_case! { &function, 714 "color-stop" => { 715 let p = NumberOrPercentage::parse(context, i)?.to_percentage(); 716 i.expect_comma()?; 717 p 718 }, 719 "from" => Percentage::zero(), 720 "to" => Percentage::hundred(), 721 _ => { 722 return Err(i.new_custom_error( 723 StyleParseErrorKind::UnexpectedFunction(function.clone()) 724 )) 725 }, 726 }; 727 let color = Color::parse(context, i)?; 728 if color == Color::CurrentColor { 729 return Err(i.new_custom_error(StyleParseErrorKind::UnspecifiedError)); 730 } 731 Ok((color.into(), p)) 732 })?; 733 if reverse_stops { 734 p.reverse(); 735 } 736 Ok(generic::GradientItem::ComplexColorStop { 737 color, 738 position: p.into(), 739 }) 740 }) 741 }) 742 .unwrap_or(vec![]); 743 744 if items.is_empty() { 745 items = vec![ 746 generic::GradientItem::ComplexColorStop { 747 color: Color::transparent(), 748 position: LengthPercentage::zero_percent(), 749 }, 750 generic::GradientItem::ComplexColorStop { 751 color: Color::transparent(), 752 position: LengthPercentage::hundred_percent(), 753 }, 754 ]; 755 } else if items.len() == 1 { 756 let first = items[0].clone(); 757 items.push(first); 758 } else { 759 items.sort_by(|a, b| { 760 match (a, b) { 761 ( 762 &generic::GradientItem::ComplexColorStop { 763 position: ref a_position, 764 .. 765 }, 766 &generic::GradientItem::ComplexColorStop { 767 position: ref b_position, 768 .. 769 }, 770 ) => match (a_position, b_position) { 771 (&LengthPercentage::Percentage(a), &LengthPercentage::Percentage(b)) => { 772 return a.0.partial_cmp(&b.0).unwrap_or(Ordering::Equal); 773 }, 774 _ => {}, 775 }, 776 _ => {}, 777 } 778 if reverse_stops { 779 Ordering::Greater 780 } else { 781 Ordering::Less 782 } 783 }) 784 } 785 Ok(items.into()) 786 } 787 788 /// Not used for -webkit-gradient syntax and conic-gradient 789 fn parse_stops<'i, 't>( 790 context: &ParserContext, 791 input: &mut Parser<'i, 't>, 792 ) -> Result<LengthPercentageItemList, ParseError<'i>> { 793 let items = 794 generic::GradientItem::parse_comma_separated(context, input, LengthPercentage::parse)?; 795 if items.is_empty() { 796 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); 797 } 798 Ok(items) 799 } 800 801 /// Try to parse a color interpolation method. 802 fn try_parse_color_interpolation_method<'i, 't>( 803 context: &ParserContext, 804 input: &mut Parser<'i, 't>, 805 ) -> Option<ColorInterpolationMethod> { 806 if gradient_color_interpolation_method_enabled() { 807 input 808 .try_parse(|i| ColorInterpolationMethod::parse(context, i)) 809 .ok() 810 } else { 811 None 812 } 813 } 814 815 /// Parses a linear gradient. 816 /// GradientCompatMode can change during `-moz-` prefixed gradient parsing if it come across a `to` keyword. 817 fn parse_linear<'i, 't>( 818 context: &ParserContext, 819 input: &mut Parser<'i, 't>, 820 repeating: bool, 821 mut compat_mode: GradientCompatMode, 822 ) -> Result<Self, ParseError<'i>> { 823 let mut flags = GradientFlags::empty(); 824 flags.set(GradientFlags::REPEATING, repeating); 825 826 let mut color_interpolation_method = 827 Self::try_parse_color_interpolation_method(context, input); 828 829 let direction = input 830 .try_parse(|p| LineDirection::parse(context, p, &mut compat_mode)) 831 .ok(); 832 833 if direction.is_some() && color_interpolation_method.is_none() { 834 color_interpolation_method = Self::try_parse_color_interpolation_method(context, input); 835 } 836 837 // If either of the 2 options were specified, we require a comma. 838 if color_interpolation_method.is_some() || direction.is_some() { 839 input.expect_comma()?; 840 } 841 842 let items = Gradient::parse_stops(context, input)?; 843 844 let default = default_color_interpolation_method(&items); 845 let color_interpolation_method = color_interpolation_method.unwrap_or(default); 846 flags.set( 847 GradientFlags::HAS_DEFAULT_COLOR_INTERPOLATION_METHOD, 848 default == color_interpolation_method, 849 ); 850 851 let direction = direction.unwrap_or(match compat_mode { 852 GradientCompatMode::Modern => LineDirection::Vertical(VerticalPositionKeyword::Bottom), 853 _ => LineDirection::Vertical(VerticalPositionKeyword::Top), 854 }); 855 856 Ok(Gradient::Linear { 857 direction, 858 color_interpolation_method, 859 items, 860 flags, 861 compat_mode, 862 }) 863 } 864 865 /// Parses a radial gradient. 866 fn parse_radial<'i, 't>( 867 context: &ParserContext, 868 input: &mut Parser<'i, 't>, 869 repeating: bool, 870 compat_mode: GradientCompatMode, 871 ) -> Result<Self, ParseError<'i>> { 872 let mut flags = GradientFlags::empty(); 873 flags.set(GradientFlags::REPEATING, repeating); 874 875 let mut color_interpolation_method = 876 Self::try_parse_color_interpolation_method(context, input); 877 878 let (shape, position) = match compat_mode { 879 GradientCompatMode::Modern => { 880 let shape = input.try_parse(|i| EndingShape::parse(context, i, compat_mode)); 881 let position = input.try_parse(|i| { 882 i.expect_ident_matching("at")?; 883 Position::parse(context, i) 884 }); 885 (shape, position.ok()) 886 }, 887 _ => { 888 let position = input.try_parse(|i| Position::parse(context, i)); 889 let shape = input.try_parse(|i| { 890 if position.is_ok() { 891 i.expect_comma()?; 892 } 893 EndingShape::parse(context, i, compat_mode) 894 }); 895 (shape, position.ok()) 896 }, 897 }; 898 899 let has_shape_or_position = shape.is_ok() || position.is_some(); 900 if has_shape_or_position && color_interpolation_method.is_none() { 901 color_interpolation_method = Self::try_parse_color_interpolation_method(context, input); 902 } 903 904 if has_shape_or_position || color_interpolation_method.is_some() { 905 input.expect_comma()?; 906 } 907 908 let shape = shape.unwrap_or({ 909 generic::EndingShape::Ellipse(Ellipse::Extent(ShapeExtent::FarthestCorner)) 910 }); 911 912 let position = position.unwrap_or(Position::center()); 913 914 let items = Gradient::parse_stops(context, input)?; 915 916 let default = default_color_interpolation_method(&items); 917 let color_interpolation_method = color_interpolation_method.unwrap_or(default); 918 flags.set( 919 GradientFlags::HAS_DEFAULT_COLOR_INTERPOLATION_METHOD, 920 default == color_interpolation_method, 921 ); 922 923 Ok(Gradient::Radial { 924 shape, 925 position, 926 color_interpolation_method, 927 items, 928 flags, 929 compat_mode, 930 }) 931 } 932 933 /// Parse a conic gradient. 934 fn parse_conic<'i, 't>( 935 context: &ParserContext, 936 input: &mut Parser<'i, 't>, 937 repeating: bool, 938 ) -> Result<Self, ParseError<'i>> { 939 let mut flags = GradientFlags::empty(); 940 flags.set(GradientFlags::REPEATING, repeating); 941 942 let mut color_interpolation_method = 943 Self::try_parse_color_interpolation_method(context, input); 944 945 let angle = input.try_parse(|i| { 946 i.expect_ident_matching("from")?; 947 // Spec allows unitless zero start angles 948 // https://drafts.csswg.org/css-images-4/#valdef-conic-gradient-angle 949 Angle::parse_with_unitless(context, i) 950 }); 951 let position = input.try_parse(|i| { 952 i.expect_ident_matching("at")?; 953 Position::parse(context, i) 954 }); 955 956 let has_angle_or_position = angle.is_ok() || position.is_ok(); 957 if has_angle_or_position && color_interpolation_method.is_none() { 958 color_interpolation_method = Self::try_parse_color_interpolation_method(context, input); 959 } 960 961 if has_angle_or_position || color_interpolation_method.is_some() { 962 input.expect_comma()?; 963 } 964 965 let angle = angle.unwrap_or(Angle::zero()); 966 967 let position = position.unwrap_or(Position::center()); 968 969 let items = generic::GradientItem::parse_comma_separated( 970 context, 971 input, 972 AngleOrPercentage::parse_with_unitless, 973 )?; 974 975 if items.is_empty() { 976 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); 977 } 978 979 let default = default_color_interpolation_method(&items); 980 let color_interpolation_method = color_interpolation_method.unwrap_or(default); 981 flags.set( 982 GradientFlags::HAS_DEFAULT_COLOR_INTERPOLATION_METHOD, 983 default == color_interpolation_method, 984 ); 985 986 Ok(Gradient::Conic { 987 angle, 988 position, 989 color_interpolation_method, 990 items, 991 flags, 992 }) 993 } 994 } 995 996 impl generic::LineDirection for LineDirection { 997 fn points_downwards(&self, compat_mode: GradientCompatMode) -> bool { 998 match *self { 999 LineDirection::Angle(ref angle) => angle.degrees() == 180.0, 1000 LineDirection::Vertical(VerticalPositionKeyword::Bottom) => { 1001 compat_mode == GradientCompatMode::Modern 1002 }, 1003 LineDirection::Vertical(VerticalPositionKeyword::Top) => { 1004 compat_mode != GradientCompatMode::Modern 1005 }, 1006 _ => false, 1007 } 1008 } 1009 1010 fn to_css<W>(&self, dest: &mut CssWriter<W>, compat_mode: GradientCompatMode) -> fmt::Result 1011 where 1012 W: Write, 1013 { 1014 match *self { 1015 LineDirection::Angle(angle) => angle.to_css(dest), 1016 LineDirection::Horizontal(x) => { 1017 if compat_mode == GradientCompatMode::Modern { 1018 dest.write_str("to ")?; 1019 } 1020 x.to_css(dest) 1021 }, 1022 LineDirection::Vertical(y) => { 1023 if compat_mode == GradientCompatMode::Modern { 1024 dest.write_str("to ")?; 1025 } 1026 y.to_css(dest) 1027 }, 1028 LineDirection::Corner(x, y) => { 1029 if compat_mode == GradientCompatMode::Modern { 1030 dest.write_str("to ")?; 1031 } 1032 x.to_css(dest)?; 1033 dest.write_char(' ')?; 1034 y.to_css(dest) 1035 }, 1036 } 1037 } 1038 } 1039 1040 impl LineDirection { 1041 fn parse<'i, 't>( 1042 context: &ParserContext, 1043 input: &mut Parser<'i, 't>, 1044 compat_mode: &mut GradientCompatMode, 1045 ) -> Result<Self, ParseError<'i>> { 1046 // Gradients allow unitless zero angles as an exception, see: 1047 // https://github.com/w3c/csswg-drafts/issues/1162 1048 if let Ok(angle) = input.try_parse(|i| Angle::parse_with_unitless(context, i)) { 1049 return Ok(LineDirection::Angle(angle)); 1050 } 1051 1052 input.try_parse(|i| { 1053 let to_ident = i.try_parse(|i| i.expect_ident_matching("to")); 1054 match *compat_mode { 1055 // `to` keyword is mandatory in modern syntax. 1056 GradientCompatMode::Modern => to_ident?, 1057 // Fall back to Modern compatibility mode in case there is a `to` keyword. 1058 // According to Gecko, `-moz-linear-gradient(to ...)` should serialize like 1059 // `linear-gradient(to ...)`. 1060 GradientCompatMode::Moz if to_ident.is_ok() => { 1061 *compat_mode = GradientCompatMode::Modern 1062 }, 1063 // There is no `to` keyword in webkit prefixed syntax. If it's consumed, 1064 // parsing should throw an error. 1065 GradientCompatMode::WebKit if to_ident.is_ok() => { 1066 return Err( 1067 i.new_custom_error(SelectorParseErrorKind::UnexpectedIdent("to".into())) 1068 ); 1069 }, 1070 _ => {}, 1071 } 1072 1073 if let Ok(x) = i.try_parse(HorizontalPositionKeyword::parse) { 1074 if let Ok(y) = i.try_parse(VerticalPositionKeyword::parse) { 1075 return Ok(LineDirection::Corner(x, y)); 1076 } 1077 return Ok(LineDirection::Horizontal(x)); 1078 } 1079 let y = VerticalPositionKeyword::parse(i)?; 1080 if let Ok(x) = i.try_parse(HorizontalPositionKeyword::parse) { 1081 return Ok(LineDirection::Corner(x, y)); 1082 } 1083 Ok(LineDirection::Vertical(y)) 1084 }) 1085 } 1086 } 1087 1088 impl EndingShape { 1089 fn parse<'i, 't>( 1090 context: &ParserContext, 1091 input: &mut Parser<'i, 't>, 1092 compat_mode: GradientCompatMode, 1093 ) -> Result<Self, ParseError<'i>> { 1094 if let Ok(extent) = input.try_parse(|i| ShapeExtent::parse_with_compat_mode(i, compat_mode)) 1095 { 1096 if input 1097 .try_parse(|i| i.expect_ident_matching("circle")) 1098 .is_ok() 1099 { 1100 return Ok(generic::EndingShape::Circle(Circle::Extent(extent))); 1101 } 1102 let _ = input.try_parse(|i| i.expect_ident_matching("ellipse")); 1103 return Ok(generic::EndingShape::Ellipse(Ellipse::Extent(extent))); 1104 } 1105 if input 1106 .try_parse(|i| i.expect_ident_matching("circle")) 1107 .is_ok() 1108 { 1109 if let Ok(extent) = 1110 input.try_parse(|i| ShapeExtent::parse_with_compat_mode(i, compat_mode)) 1111 { 1112 return Ok(generic::EndingShape::Circle(Circle::Extent(extent))); 1113 } 1114 if compat_mode == GradientCompatMode::Modern { 1115 if let Ok(length) = input.try_parse(|i| NonNegativeLength::parse(context, i)) { 1116 return Ok(generic::EndingShape::Circle(Circle::Radius(length))); 1117 } 1118 } 1119 return Ok(generic::EndingShape::Circle(Circle::Extent( 1120 ShapeExtent::FarthestCorner, 1121 ))); 1122 } 1123 if input 1124 .try_parse(|i| i.expect_ident_matching("ellipse")) 1125 .is_ok() 1126 { 1127 if let Ok(extent) = 1128 input.try_parse(|i| ShapeExtent::parse_with_compat_mode(i, compat_mode)) 1129 { 1130 return Ok(generic::EndingShape::Ellipse(Ellipse::Extent(extent))); 1131 } 1132 if compat_mode == GradientCompatMode::Modern { 1133 let pair: Result<_, ParseError> = input.try_parse(|i| { 1134 let x = NonNegativeLengthPercentage::parse(context, i)?; 1135 let y = NonNegativeLengthPercentage::parse(context, i)?; 1136 Ok((x, y)) 1137 }); 1138 if let Ok((x, y)) = pair { 1139 return Ok(generic::EndingShape::Ellipse(Ellipse::Radii(x, y))); 1140 } 1141 } 1142 return Ok(generic::EndingShape::Ellipse(Ellipse::Extent( 1143 ShapeExtent::FarthestCorner, 1144 ))); 1145 } 1146 if let Ok(length) = input.try_parse(|i| NonNegativeLength::parse(context, i)) { 1147 if let Ok(y) = input.try_parse(|i| NonNegativeLengthPercentage::parse(context, i)) { 1148 if compat_mode == GradientCompatMode::Modern { 1149 let _ = input.try_parse(|i| i.expect_ident_matching("ellipse")); 1150 } 1151 return Ok(generic::EndingShape::Ellipse(Ellipse::Radii( 1152 NonNegative(LengthPercentage::from(length.0)), 1153 y, 1154 ))); 1155 } 1156 if compat_mode == GradientCompatMode::Modern { 1157 let y = input.try_parse(|i| { 1158 i.expect_ident_matching("ellipse")?; 1159 NonNegativeLengthPercentage::parse(context, i) 1160 }); 1161 if let Ok(y) = y { 1162 return Ok(generic::EndingShape::Ellipse(Ellipse::Radii( 1163 NonNegative(LengthPercentage::from(length.0)), 1164 y, 1165 ))); 1166 } 1167 let _ = input.try_parse(|i| i.expect_ident_matching("circle")); 1168 } 1169 1170 return Ok(generic::EndingShape::Circle(Circle::Radius(length))); 1171 } 1172 input.try_parse(|i| { 1173 let x = Percentage::parse_non_negative(context, i)?; 1174 let y = if let Ok(y) = i.try_parse(|i| NonNegativeLengthPercentage::parse(context, i)) { 1175 if compat_mode == GradientCompatMode::Modern { 1176 let _ = i.try_parse(|i| i.expect_ident_matching("ellipse")); 1177 } 1178 y 1179 } else { 1180 if compat_mode == GradientCompatMode::Modern { 1181 i.expect_ident_matching("ellipse")?; 1182 } 1183 NonNegativeLengthPercentage::parse(context, i)? 1184 }; 1185 Ok(generic::EndingShape::Ellipse(Ellipse::Radii( 1186 NonNegative(LengthPercentage::from(x)), 1187 y, 1188 ))) 1189 }) 1190 } 1191 } 1192 1193 impl ShapeExtent { 1194 fn parse_with_compat_mode<'i, 't>( 1195 input: &mut Parser<'i, 't>, 1196 compat_mode: GradientCompatMode, 1197 ) -> Result<Self, ParseError<'i>> { 1198 match Self::parse(input)? { 1199 ShapeExtent::Contain | ShapeExtent::Cover 1200 if compat_mode == GradientCompatMode::Modern => 1201 { 1202 Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) 1203 }, 1204 ShapeExtent::Contain => Ok(ShapeExtent::ClosestSide), 1205 ShapeExtent::Cover => Ok(ShapeExtent::FarthestCorner), 1206 keyword => Ok(keyword), 1207 } 1208 } 1209 } 1210 1211 impl<T> generic::GradientItem<Color, T> { 1212 fn parse_comma_separated<'i, 't>( 1213 context: &ParserContext, 1214 input: &mut Parser<'i, 't>, 1215 parse_position: impl for<'i1, 't1> Fn(&ParserContext, &mut Parser<'i1, 't1>) -> Result<T, ParseError<'i1>> 1216 + Copy, 1217 ) -> Result<crate::OwnedSlice<Self>, ParseError<'i>> { 1218 let mut items = Vec::new(); 1219 let mut seen_stop = false; 1220 1221 loop { 1222 input.parse_until_before(Delimiter::Comma, |input| { 1223 if seen_stop { 1224 if let Ok(hint) = input.try_parse(|i| parse_position(context, i)) { 1225 seen_stop = false; 1226 items.push(generic::GradientItem::InterpolationHint(hint)); 1227 return Ok(()); 1228 } 1229 } 1230 1231 let stop = generic::ColorStop::parse(context, input, parse_position)?; 1232 1233 if let Ok(multi_position) = input.try_parse(|i| parse_position(context, i)) { 1234 let stop_color = stop.color.clone(); 1235 items.push(stop.into_item()); 1236 items.push( 1237 generic::ColorStop { 1238 color: stop_color, 1239 position: Some(multi_position), 1240 } 1241 .into_item(), 1242 ); 1243 } else { 1244 items.push(stop.into_item()); 1245 } 1246 1247 seen_stop = true; 1248 Ok(()) 1249 })?; 1250 1251 match input.next() { 1252 Err(_) => break, 1253 Ok(&Token::Comma) => continue, 1254 Ok(_) => unreachable!(), 1255 } 1256 } 1257 1258 if !seen_stop || items.is_empty() { 1259 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); 1260 } 1261 Ok(items.into()) 1262 } 1263 } 1264 1265 impl<T> generic::ColorStop<Color, T> { 1266 fn parse<'i, 't>( 1267 context: &ParserContext, 1268 input: &mut Parser<'i, 't>, 1269 parse_position: impl for<'i1, 't1> Fn( 1270 &ParserContext, 1271 &mut Parser<'i1, 't1>, 1272 ) -> Result<T, ParseError<'i1>>, 1273 ) -> Result<Self, ParseError<'i>> { 1274 Ok(generic::ColorStop { 1275 color: Color::parse(context, input)?, 1276 position: input.try_parse(|i| parse_position(context, i)).ok(), 1277 }) 1278 } 1279 } 1280 1281 impl PaintWorklet { 1282 #[cfg(feature = "servo")] 1283 fn parse_args<'i>( 1284 context: &ParserContext, 1285 input: &mut Parser<'i, '_>, 1286 ) -> Result<Self, ParseError<'i>> { 1287 use crate::custom_properties::SpecifiedValue; 1288 use servo_arc::Arc; 1289 let name = Atom::from(&**input.expect_ident()?); 1290 let arguments = input 1291 .try_parse(|input| { 1292 input.expect_comma()?; 1293 input.parse_comma_separated(|input| { 1294 SpecifiedValue::parse(input, &context.url_data).map(Arc::new) 1295 }) 1296 }) 1297 .unwrap_or_default(); 1298 Ok(Self { name, arguments }) 1299 } 1300 } 1301 1302 /// https://drafts.csswg.org/css-images/#propdef-image-rendering 1303 #[allow(missing_docs)] 1304 #[derive( 1305 Clone, 1306 Copy, 1307 Debug, 1308 Eq, 1309 Hash, 1310 MallocSizeOf, 1311 Parse, 1312 PartialEq, 1313 SpecifiedValueInfo, 1314 ToCss, 1315 ToComputedValue, 1316 ToResolvedValue, 1317 ToShmem, 1318 ToTyped, 1319 )] 1320 #[repr(u8)] 1321 pub enum ImageRendering { 1322 Auto, 1323 #[cfg(feature = "gecko")] 1324 Smooth, 1325 #[parse(aliases = "-moz-crisp-edges")] 1326 CrispEdges, 1327 Pixelated, 1328 // From the spec: 1329 // 1330 // This property previously accepted the values optimizeSpeed and 1331 // optimizeQuality. These are now deprecated; a user agent must accept 1332 // them as valid values but must treat them as having the same behavior 1333 // as crisp-edges and smooth respectively, and authors must not use 1334 // them. 1335 // 1336 #[cfg(feature = "gecko")] 1337 Optimizespeed, 1338 #[cfg(feature = "gecko")] 1339 Optimizequality, 1340 }