1use super::color::ColorFallbackKind;
4use super::gradient::*;
5use super::resolution::Resolution;
6use crate::compat;
7use crate::dependencies::{Dependency, UrlDependency};
8use crate::error::{ParserError, PrinterError};
9use crate::prefixes::{is_webkit_gradient, Feature};
10use crate::printer::Printer;
11use crate::targets::Browsers;
12use crate::traits::{FallbackValues, Parse, ToCss};
13use crate::values::string::CowArcStr;
14use crate::values::url::Url;
15use crate::vendor_prefix::VendorPrefix;
16use cssparser::*;
17use smallvec::SmallVec;
18
19#[derive(Debug, Clone, PartialEq)]
21#[cfg_attr(
22 feature = "serde",
23 derive(serde::Serialize, serde::Deserialize),
24 serde(tag = "type", content = "value", rename_all = "kebab-case")
25)]
26pub enum Image<'i> {
27 None,
29 #[cfg_attr(feature = "serde", serde(borrow))]
31 Url(Url<'i>),
32 Gradient(Box<Gradient>),
34 ImageSet(ImageSet<'i>),
36}
37
38impl<'i> Default for Image<'i> {
39 fn default() -> Image<'i> {
40 Image::None
41 }
42}
43
44impl<'i> Image<'i> {
45 pub fn has_vendor_prefix(&self) -> bool {
47 let prefix = self.get_vendor_prefix();
48 !prefix.is_empty() && prefix != VendorPrefix::None
49 }
50
51 pub fn get_vendor_prefix(&self) -> VendorPrefix {
53 match self {
54 Image::Gradient(a) => a.get_vendor_prefix(),
55 Image::ImageSet(a) => a.get_vendor_prefix(),
56 _ => VendorPrefix::empty(),
57 }
58 }
59
60 pub fn get_necessary_prefixes(&self, targets: Browsers) -> VendorPrefix {
62 match self {
63 Image::Gradient(grad) => grad.get_necessary_prefixes(targets),
64 Image::ImageSet(image_set) => image_set.get_necessary_prefixes(targets),
65 _ => VendorPrefix::None,
66 }
67 }
68
69 pub fn get_prefixed(&self, prefix: VendorPrefix) -> Image<'i> {
71 match self {
72 Image::Gradient(grad) => Image::Gradient(Box::new(grad.get_prefixed(prefix))),
73 Image::ImageSet(image_set) => Image::ImageSet(image_set.get_prefixed(prefix)),
74 _ => self.clone(),
75 }
76 }
77
78 pub fn get_legacy_webkit(&self) -> Result<Image<'i>, ()> {
82 match self {
83 Image::Gradient(grad) => Ok(Image::Gradient(Box::new(grad.get_legacy_webkit()?))),
84 _ => Ok(self.clone()),
85 }
86 }
87
88 pub fn get_necessary_fallbacks(&self, targets: Browsers) -> ColorFallbackKind {
90 match self {
91 Image::Gradient(grad) => grad.get_necessary_fallbacks(targets),
92 _ => ColorFallbackKind::empty(),
93 }
94 }
95
96 pub fn get_fallback(&self, kind: ColorFallbackKind) -> Image<'i> {
98 match self {
99 Image::Gradient(grad) => Image::Gradient(Box::new(grad.get_fallback(kind))),
100 _ => self.clone(),
101 }
102 }
103
104 pub(crate) fn should_preserve_fallback(&self, fallback: &Option<Image>, targets: Option<Browsers>) -> bool {
105 if let (Some(fallback), Some(targets)) = (&fallback, targets) {
106 return !compat::Feature::ImageSet.is_compatible(targets)
107 && matches!(self, Image::ImageSet(..))
108 && !matches!(fallback, Image::ImageSet(..));
109 }
110
111 false
112 }
113
114 pub(crate) fn should_preserve_fallbacks(
115 images: &SmallVec<[Image; 1]>,
116 fallback: Option<&SmallVec<[Image; 1]>>,
117 targets: Option<Browsers>,
118 ) -> bool {
119 if let (Some(fallback), Some(targets)) = (&fallback, targets) {
120 return !compat::Feature::ImageSet.is_compatible(targets)
121 && images.iter().any(|x| matches!(x, Image::ImageSet(..)))
122 && !fallback.iter().any(|x| matches!(x, Image::ImageSet(..)));
123 }
124
125 false
126 }
127}
128
129pub(crate) trait ImageFallback<'i>: Sized {
130 fn get_image(&self) -> &Image<'i>;
131 fn with_image(&self, image: Image<'i>) -> Self;
132
133 #[inline]
134 fn get_necessary_fallbacks(&self, targets: Browsers) -> ColorFallbackKind {
135 self.get_image().get_necessary_fallbacks(targets)
136 }
137
138 #[inline]
139 fn get_fallback(&self, kind: ColorFallbackKind) -> Self {
140 self.with_image(self.get_image().get_fallback(kind))
141 }
142}
143
144impl<'i> ImageFallback<'i> for Image<'i> {
145 #[inline]
146 fn get_image(&self) -> &Image<'i> {
147 self
148 }
149
150 #[inline]
151 fn with_image(&self, image: Image<'i>) -> Self {
152 image
153 }
154}
155
156impl<'i> FallbackValues for Image<'i> {
157 fn get_fallbacks(&mut self, targets: Browsers) -> Vec<Self> {
158 let prefixes = self.get_necessary_prefixes(targets);
160 let fallbacks = self.get_necessary_fallbacks(targets);
161 let mut res = Vec::new();
162
163 let rgb = if fallbacks.contains(ColorFallbackKind::RGB) {
165 Some(self.get_fallback(ColorFallbackKind::RGB))
166 } else {
167 None
168 };
169
170 let prefix_image = rgb.as_ref().unwrap_or(self);
172
173 if prefixes.contains(VendorPrefix::WebKit)
175 && is_webkit_gradient(targets)
176 && matches!(prefix_image, Image::Gradient(_))
177 {
178 if let Ok(legacy) = prefix_image.get_legacy_webkit() {
179 res.push(legacy);
180 }
181 }
182
183 if prefixes.contains(VendorPrefix::WebKit) {
185 res.push(prefix_image.get_prefixed(VendorPrefix::WebKit))
186 }
187
188 if prefixes.contains(VendorPrefix::Moz) {
189 res.push(prefix_image.get_prefixed(VendorPrefix::Moz))
190 }
191
192 if prefixes.contains(VendorPrefix::O) {
193 res.push(prefix_image.get_prefixed(VendorPrefix::O))
194 }
195
196 if prefixes.contains(VendorPrefix::None) {
197 if let Some(rgb) = rgb {
199 res.push(rgb);
200 }
201
202 if fallbacks.contains(ColorFallbackKind::P3) {
204 res.push(self.get_fallback(ColorFallbackKind::P3));
205 }
206
207 if fallbacks.contains(ColorFallbackKind::LAB) {
209 *self = self.get_fallback(ColorFallbackKind::LAB);
210 }
211 } else if let Some(last) = res.pop() {
212 *self = last;
216 }
217
218 res
219 }
220}
221
222impl<'i, T: ImageFallback<'i>> FallbackValues for SmallVec<[T; 1]> {
223 fn get_fallbacks(&mut self, targets: Browsers) -> Vec<Self> {
224 let mut prefixes = VendorPrefix::empty();
226 let mut fallbacks = ColorFallbackKind::empty();
227 let mut res = Vec::new();
228 for item in self.iter() {
229 prefixes |= item.get_image().get_necessary_prefixes(targets);
230 fallbacks |= item.get_necessary_fallbacks(targets);
231 }
232
233 let rgb: Option<SmallVec<[T; 1]>> = if fallbacks.contains(ColorFallbackKind::RGB) {
235 Some(self.iter().map(|item| item.get_fallback(ColorFallbackKind::RGB)).collect())
236 } else {
237 None
238 };
239
240 let prefix_images = rgb.as_ref().unwrap_or(&self);
242
243 if prefixes.contains(VendorPrefix::WebKit) && is_webkit_gradient(targets) {
245 let images: SmallVec<[T; 1]> = prefix_images
246 .iter()
247 .map(|item| item.get_image().get_legacy_webkit().map(|image| item.with_image(image)))
248 .flatten()
249 .collect();
250 if !images.is_empty() {
251 res.push(images)
252 }
253 }
254
255 macro_rules! prefix {
257 ($prefix: ident) => {
258 if prefixes.contains(VendorPrefix::$prefix) {
259 let images = prefix_images
260 .iter()
261 .map(|item| {
262 let image = item.get_image().get_prefixed(VendorPrefix::$prefix);
263 item.with_image(image)
264 })
265 .collect();
266 res.push(images)
267 }
268 };
269 }
270
271 prefix!(WebKit);
272 prefix!(Moz);
273 prefix!(O);
274 if prefixes.contains(VendorPrefix::None) {
275 if let Some(rgb) = rgb {
276 res.push(rgb);
277 }
278
279 if fallbacks.contains(ColorFallbackKind::P3) {
280 let p3_images = self.iter().map(|item| item.get_fallback(ColorFallbackKind::P3)).collect();
281
282 res.push(p3_images)
283 }
284
285 if fallbacks.contains(ColorFallbackKind::LAB) {
287 for item in self.iter_mut() {
288 *item = item.get_fallback(ColorFallbackKind::LAB);
289 }
290 }
291 } else if let Some(last) = res.pop() {
292 *self = last;
296 }
297
298 res
299 }
300}
301
302impl<'i> Parse<'i> for Image<'i> {
303 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
304 if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
305 return Ok(Image::None);
306 }
307
308 if let Ok(url) = input.try_parse(Url::parse) {
309 return Ok(Image::Url(url));
310 }
311
312 if let Ok(grad) = input.try_parse(Gradient::parse) {
313 return Ok(Image::Gradient(Box::new(grad)));
314 }
315
316 if let Ok(image_set) = input.try_parse(ImageSet::parse) {
317 return Ok(Image::ImageSet(image_set));
318 }
319
320 Err(input.new_error_for_next_token())
321 }
322}
323
324impl<'i> ToCss for Image<'i> {
325 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
326 where
327 W: std::fmt::Write,
328 {
329 match self {
330 Image::None => dest.write_str("none"),
331 Image::Url(url) => url.to_css(dest),
332 Image::Gradient(grad) => grad.to_css(dest),
333 Image::ImageSet(image_set) => image_set.to_css(dest),
334 }
335 }
336}
337
338#[derive(Debug, Clone, PartialEq)]
343#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
344pub struct ImageSet<'i> {
345 #[cfg_attr(feature = "serde", serde(borrow))]
347 pub options: Vec<ImageSetOption<'i>>,
348 pub vendor_prefix: VendorPrefix,
350}
351
352impl<'i> ImageSet<'i> {
353 pub fn get_vendor_prefix(&self) -> VendorPrefix {
355 self.vendor_prefix
356 }
357
358 pub fn get_necessary_prefixes(&self, targets: Browsers) -> VendorPrefix {
360 if self.vendor_prefix.contains(VendorPrefix::None) {
361 Feature::ImageSet.prefixes_for(targets)
362 } else {
363 self.vendor_prefix
364 }
365 }
366
367 pub fn get_prefixed(&self, prefix: VendorPrefix) -> ImageSet<'i> {
369 ImageSet {
370 options: self.options.clone(),
371 vendor_prefix: prefix,
372 }
373 }
374}
375
376impl<'i> Parse<'i> for ImageSet<'i> {
377 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
378 let location = input.current_source_location();
379 let f = input.expect_function()?;
380 let vendor_prefix = match_ignore_ascii_case! { &*f,
381 "image-set" => VendorPrefix::None,
382 "-webkit-image-set" => VendorPrefix::WebKit,
383 _ => return Err(location.new_unexpected_token_error(
384 cssparser::Token::Ident(f.clone())
385 ))
386 };
387
388 let options = input.parse_nested_block(|input| input.parse_comma_separated(ImageSetOption::parse))?;
389 Ok(ImageSet { options, vendor_prefix })
390 }
391}
392
393impl<'i> ToCss for ImageSet<'i> {
394 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
395 where
396 W: std::fmt::Write,
397 {
398 self.vendor_prefix.to_css(dest)?;
399 dest.write_str("image-set(")?;
400 let mut first = true;
401 for option in &self.options {
402 if first {
403 first = false;
404 } else {
405 dest.delim(',', false)?;
406 }
407 option.to_css(dest, self.vendor_prefix != VendorPrefix::None)?;
408 }
409 dest.write_char(')')
410 }
411}
412
413#[derive(Debug, Clone, PartialEq)]
415#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
416pub struct ImageSetOption<'i> {
417 pub image: Image<'i>,
419 pub resolution: Resolution,
421 #[cfg_attr(feature = "serde", serde(borrow))]
423 pub file_type: Option<CowArcStr<'i>>,
424}
425
426impl<'i> Parse<'i> for ImageSetOption<'i> {
427 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
428 let loc = input.current_source_location();
429 let image = if let Ok(url) = input.try_parse(|input| input.expect_url_or_string()) {
430 Image::Url(Url {
431 url: url.into(),
432 loc: loc.into(),
433 })
434 } else {
435 Image::parse(input)?
436 };
437
438 let (resolution, file_type) = if let Ok(res) = input.try_parse(Resolution::parse) {
439 let file_type = input.try_parse(parse_file_type).ok();
440 (res, file_type)
441 } else {
442 let file_type = input.try_parse(parse_file_type).ok();
443 let resolution = input.try_parse(Resolution::parse).unwrap_or(Resolution::Dppx(1.0));
444 (resolution, file_type)
445 };
446
447 Ok(ImageSetOption {
448 image,
449 resolution,
450 file_type: file_type.map(|x| x.into()),
451 })
452 }
453}
454
455impl<'i> ImageSetOption<'i> {
456 fn to_css<W>(&self, dest: &mut Printer<W>, is_prefixed: bool) -> Result<(), PrinterError>
457 where
458 W: std::fmt::Write,
459 {
460 match &self.image {
461 Image::Url(url) if !is_prefixed => {
463 let dep = if dest.dependencies.is_some() {
465 Some(UrlDependency::new(url, dest.filename()))
466 } else {
467 None
468 };
469 if let Some(dep) = dep {
470 serialize_string(&dep.placeholder, dest)?;
471 if let Some(dependencies) = &mut dest.dependencies {
472 dependencies.push(Dependency::Url(dep))
473 }
474 } else {
475 serialize_string(&url.url, dest)?;
476 }
477 }
478 _ => self.image.to_css(dest)?,
479 }
480
481 dest.write_char(' ')?;
485
486 let targets = std::mem::take(&mut dest.targets);
490 self.resolution.to_css(dest)?;
491 dest.targets = targets;
492
493 if let Some(file_type) = &self.file_type {
494 dest.write_str(" type(")?;
495 serialize_string(&file_type, dest)?;
496 dest.write_char(')')?;
497 }
498
499 Ok(())
500 }
501}
502
503fn parse_file_type<'i, 't>(input: &mut Parser<'i, 't>) -> Result<CowRcStr<'i>, ParseError<'i, ParserError<'i>>> {
504 input.expect_function_matching("type")?;
505 input.parse_nested_block(|input| Ok(input.expect_string_cloned()?))
506}