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 //! Generic types for the handling of [images].
6 //!
7 //! [images]: https://drafts.csswg.org/css-images/#image-values
8 
9 use crate::custom_properties;
10 use crate::values::generics::position::PositionComponent;
11 use crate::values::serialize_atom_identifier;
12 use crate::Atom;
13 use crate::Zero;
14 use servo_arc::Arc;
15 use std::fmt::{self, Write};
16 use style_traits::{CssWriter, ToCss};
17 
18 /// An `<image> | none` value.
19 ///
20 /// https://drafts.csswg.org/css-images/#image-values
21 #[derive(
22     Clone, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem,
23 )]
24 #[repr(C, u8)]
25 pub enum GenericImage<G, MozImageRect, ImageUrl, Color, Percentage, Resolution> {
26     /// `none` variant.
27     None,
28     /// A `<url()>` image.
29     Url(ImageUrl),
30 
31     /// A `<gradient>` image.  Gradients are rather large, and not nearly as
32     /// common as urls, so we box them here to keep the size of this enum sane.
33     Gradient(Box<G>),
34     /// A `-moz-image-rect` image.  Also fairly large and rare.
35     // not cfg’ed out on non-Gecko to avoid `error[E0392]: parameter `MozImageRect` is never used`
36     // Instead we make MozImageRect an empty enum
37     Rect(Box<MozImageRect>),
38 
39     /// A `-moz-element(# <element-id>)`
40     #[cfg(feature = "gecko")]
41     #[css(function = "-moz-element")]
42     Element(Atom),
43 
44     /// A paint worklet image.
45     /// <https://drafts.css-houdini.org/css-paint-api/>
46     #[cfg(feature = "servo-layout-2013")]
47     PaintWorklet(PaintWorklet),
48 
49     /// A `<cross-fade()>` image. Storing this directly inside of
50     /// GenericImage increases the size by 8 bytes so we box it here
51     /// and store images directly inside of cross-fade instead of
52     /// boxing them there.
53     CrossFade(Box<GenericCrossFade<Self, Color, Percentage>>),
54 
55     /// An `image-set()` function.
56     ImageSet(#[compute(field_bound)] Box<GenericImageSet<Self, Resolution>>),
57 }
58 
59 pub use self::GenericImage as Image;
60 
61 /// <https://drafts.csswg.org/css-images-4/#cross-fade-function>
62 #[derive(
63     Clone, Debug, MallocSizeOf, PartialEq, ToResolvedValue, ToShmem, ToCss, ToComputedValue,
64 )]
65 #[css(comma, function = "cross-fade")]
66 #[repr(C)]
67 pub struct GenericCrossFade<Image, Color, Percentage> {
68     /// All of the image percent pairings passed as arguments to
69     /// cross-fade.
70     #[css(iterable)]
71     pub elements: crate::OwnedSlice<GenericCrossFadeElement<Image, Color, Percentage>>,
72 }
73 
74 /// A `<percent> | none` value. Represents optional percentage values
75 /// assosicated with cross-fade images.
76 #[derive(
77     Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, ToCss,
78 )]
79 #[repr(C, u8)]
80 pub enum PercentOrNone<Percentage> {
81     /// `none` variant.
82     #[css(skip)]
83     None,
84     /// A percentage variant.
85     Percent(Percentage),
86 }
87 
88 /// An optional percent and a cross fade image.
89 #[derive(
90     Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, ToCss,
91 )]
92 #[repr(C)]
93 pub struct GenericCrossFadeElement<Image, Color, Percentage> {
94     /// The percent of the final image that `image` will be.
95     pub percent: PercentOrNone<Percentage>,
96     /// A color or image that will be blended when cross-fade is
97     /// evaluated.
98     pub image: GenericCrossFadeImage<Image, Color>,
99 }
100 
101 /// An image or a color. `cross-fade` takes either when blending
102 /// images together.
103 #[derive(
104     Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, ToCss,
105 )]
106 #[repr(C, u8)]
107 pub enum GenericCrossFadeImage<I, C> {
108     /// A boxed image value. Boxing provides indirection so images can
109     /// be cross-fades and cross-fades can be images.
110     Image(I),
111     /// A color value.
112     Color(C),
113 }
114 
115 pub use self::GenericCrossFade as CrossFade;
116 pub use self::GenericCrossFadeElement as CrossFadeElement;
117 pub use self::GenericCrossFadeImage as CrossFadeImage;
118 
119 /// https://drafts.csswg.org/css-images-4/#image-set-notation
120 #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss, ToResolvedValue, ToShmem)]
121 #[css(comma, function = "image-set")]
122 #[repr(C)]
123 pub struct GenericImageSet<Image, Resolution> {
124     /// The index of the selected candidate. Zero for specified values.
125     #[css(skip)]
126     pub selected_index: usize,
127 
128     /// All of the image and resolution pairs.
129     #[css(iterable)]
130     pub items: crate::OwnedSlice<GenericImageSetItem<Image, Resolution>>,
131 }
132 
133 /// An optional percent and a cross fade image.
134 #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem)]
135 #[repr(C)]
136 pub struct GenericImageSetItem<Image, Resolution> {
137     /// `<image>`. `<string>` is converted to `Image::Url` at parse time.
138     pub image: Image,
139     /// The `<resolution>`.
140     ///
141     /// TODO: Skip serialization if it is 1x.
142     pub resolution: Resolution,
143 
144     /// The `type(<string>)`
145     /// (Optional) Specify the image's MIME type
146     pub mime_type: crate::OwnedStr,
147 
148     /// True if mime_type has been specified
149     pub has_mime_type: bool,
150 }
151 
152 impl<I: style_traits::ToCss, R: style_traits::ToCss> ToCss for GenericImageSetItem<I, R> {
to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write,153     fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
154     where
155         W: fmt::Write,
156     {
157         self.image.to_css(dest)?;
158         dest.write_str(" ")?;
159         self.resolution.to_css(dest)?;
160 
161         if self.has_mime_type {
162             dest.write_str(" ")?;
163             dest.write_str("type(")?;
164             self.mime_type.to_css(dest)?;
165             dest.write_str(")")?;
166         }
167         Ok(())
168     }
169 }
170 
171 pub use self::GenericImageSet as ImageSet;
172 pub use self::GenericImageSetItem as ImageSetItem;
173 
174 /// A CSS gradient.
175 /// <https://drafts.csswg.org/css-images/#gradients>
176 #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem)]
177 #[repr(C)]
178 pub enum GenericGradient<
179     LineDirection,
180     LengthPercentage,
181     NonNegativeLength,
182     NonNegativeLengthPercentage,
183     Position,
184     Angle,
185     AngleOrPercentage,
186     Color,
187 > {
188     /// A linear gradient.
189     Linear {
190         /// Line direction
191         direction: LineDirection,
192         /// The color stops and interpolation hints.
193         items: crate::OwnedSlice<GenericGradientItem<Color, LengthPercentage>>,
194         /// True if this is a repeating gradient.
195         repeating: bool,
196         /// Compatibility mode.
197         compat_mode: GradientCompatMode,
198     },
199     /// A radial gradient.
200     Radial {
201         /// Shape of gradient
202         shape: GenericEndingShape<NonNegativeLength, NonNegativeLengthPercentage>,
203         /// Center of gradient
204         position: Position,
205         /// The color stops and interpolation hints.
206         items: crate::OwnedSlice<GenericGradientItem<Color, LengthPercentage>>,
207         /// True if this is a repeating gradient.
208         repeating: bool,
209         /// Compatibility mode.
210         compat_mode: GradientCompatMode,
211     },
212     /// A conic gradient.
213     Conic {
214         /// Start angle of gradient
215         angle: Angle,
216         /// Center of gradient
217         position: Position,
218         /// The color stops and interpolation hints.
219         items: crate::OwnedSlice<GenericGradientItem<Color, AngleOrPercentage>>,
220         /// True if this is a repeating gradient.
221         repeating: bool,
222     },
223 }
224 
225 pub use self::GenericGradient as Gradient;
226 
227 #[derive(
228     Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem,
229 )]
230 #[repr(u8)]
231 /// Whether we used the modern notation or the compatibility `-webkit`, `-moz` prefixes.
232 pub enum GradientCompatMode {
233     /// Modern syntax.
234     Modern,
235     /// `-webkit` prefix.
236     WebKit,
237     /// `-moz` prefix
238     Moz,
239 }
240 
241 /// A radial gradient's ending shape.
242 #[derive(
243     Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem,
244 )]
245 #[repr(C, u8)]
246 pub enum GenericEndingShape<NonNegativeLength, NonNegativeLengthPercentage> {
247     /// A circular gradient.
248     Circle(GenericCircle<NonNegativeLength>),
249     /// An elliptic gradient.
250     Ellipse(GenericEllipse<NonNegativeLengthPercentage>),
251 }
252 
253 pub use self::GenericEndingShape as EndingShape;
254 
255 /// A circle shape.
256 #[derive(
257     Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem,
258 )]
259 #[repr(C, u8)]
260 pub enum GenericCircle<NonNegativeLength> {
261     /// A circle radius.
262     Radius(NonNegativeLength),
263     /// A circle extent.
264     Extent(ShapeExtent),
265 }
266 
267 pub use self::GenericCircle as Circle;
268 
269 /// An ellipse shape.
270 #[derive(
271     Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem,
272 )]
273 #[repr(C, u8)]
274 pub enum GenericEllipse<NonNegativeLengthPercentage> {
275     /// An ellipse pair of radii.
276     Radii(NonNegativeLengthPercentage, NonNegativeLengthPercentage),
277     /// An ellipse extent.
278     Extent(ShapeExtent),
279 }
280 
281 pub use self::GenericEllipse as Ellipse;
282 
283 /// <https://drafts.csswg.org/css-images/#typedef-extent-keyword>
284 #[allow(missing_docs)]
285 #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
286 #[derive(
287     Clone,
288     Copy,
289     Debug,
290     Eq,
291     MallocSizeOf,
292     Parse,
293     PartialEq,
294     ToComputedValue,
295     ToCss,
296     ToResolvedValue,
297     ToShmem,
298 )]
299 #[repr(u8)]
300 pub enum ShapeExtent {
301     ClosestSide,
302     FarthestSide,
303     ClosestCorner,
304     FarthestCorner,
305     Contain,
306     Cover,
307 }
308 
309 /// A gradient item.
310 /// <https://drafts.csswg.org/css-images-4/#color-stop-syntax>
311 #[derive(
312     Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem,
313 )]
314 #[repr(C, u8)]
315 pub enum GenericGradientItem<Color, T> {
316     /// A simple color stop, without position.
317     SimpleColorStop(Color),
318     /// A complex color stop, with a position.
319     ComplexColorStop {
320         /// The color for the stop.
321         color: Color,
322         /// The position for the stop.
323         position: T,
324     },
325     /// An interpolation hint.
326     InterpolationHint(T),
327 }
328 
329 pub use self::GenericGradientItem as GradientItem;
330 
331 /// A color stop.
332 /// <https://drafts.csswg.org/css-images/#typedef-color-stop-list>
333 #[derive(
334     Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem,
335 )]
336 pub struct ColorStop<Color, T> {
337     /// The color of this stop.
338     pub color: Color,
339     /// The position of this stop.
340     pub position: Option<T>,
341 }
342 
343 impl<Color, T> ColorStop<Color, T> {
344     /// Convert the color stop into an appropriate `GradientItem`.
345     #[inline]
into_item(self) -> GradientItem<Color, T>346     pub fn into_item(self) -> GradientItem<Color, T> {
347         match self.position {
348             Some(position) => GradientItem::ComplexColorStop {
349                 color: self.color,
350                 position,
351             },
352             None => GradientItem::SimpleColorStop(self.color),
353         }
354     }
355 }
356 
357 /// Specified values for a paint worklet.
358 /// <https://drafts.css-houdini.org/css-paint-api/>
359 #[cfg_attr(feature = "servo", derive(MallocSizeOf))]
360 #[derive(Clone, Debug, PartialEq, ToComputedValue, ToResolvedValue, ToShmem)]
361 pub struct PaintWorklet {
362     /// The name the worklet was registered with.
363     pub name: Atom,
364     /// The arguments for the worklet.
365     /// TODO: store a parsed representation of the arguments.
366     #[cfg_attr(feature = "servo", ignore_malloc_size_of = "Arc")]
367     #[compute(no_field_bound)]
368     #[resolve(no_field_bound)]
369     pub arguments: Vec<Arc<custom_properties::SpecifiedValue>>,
370 }
371 
372 impl ::style_traits::SpecifiedValueInfo for PaintWorklet {}
373 
374 impl ToCss for PaintWorklet {
to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: Write,375     fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
376     where
377         W: Write,
378     {
379         dest.write_str("paint(")?;
380         serialize_atom_identifier(&self.name, dest)?;
381         for argument in &self.arguments {
382             dest.write_str(", ")?;
383             argument.to_css(dest)?;
384         }
385         dest.write_str(")")
386     }
387 }
388 
389 /// Values for `moz-image-rect`.
390 ///
391 /// `-moz-image-rect(<uri>, top, right, bottom, left);`
392 #[allow(missing_docs)]
393 #[derive(
394     Clone,
395     Debug,
396     MallocSizeOf,
397     PartialEq,
398     SpecifiedValueInfo,
399     ToComputedValue,
400     ToCss,
401     ToResolvedValue,
402     ToShmem,
403 )]
404 #[css(comma, function = "-moz-image-rect")]
405 #[repr(C)]
406 pub struct GenericMozImageRect<NumberOrPercentage, MozImageRectUrl> {
407     pub url: MozImageRectUrl,
408     pub top: NumberOrPercentage,
409     pub right: NumberOrPercentage,
410     pub bottom: NumberOrPercentage,
411     pub left: NumberOrPercentage,
412 }
413 
414 pub use self::GenericMozImageRect as MozImageRect;
415 
416 impl<G, R, U, C, P, Resolution> fmt::Debug for Image<G, R, U, C, P, Resolution>
417 where
418     Image<G, R, U, C, P, Resolution>: ToCss,
419 {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result420     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
421         self.to_css(&mut CssWriter::new(f))
422     }
423 }
424 
425 impl<G, R, U, C, P, Resolution> ToCss for Image<G, R, U, C, P, Resolution>
426 where
427     G: ToCss,
428     R: ToCss,
429     U: ToCss,
430     C: ToCss,
431     P: ToCss,
432     Resolution: ToCss,
433 {
to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: Write,434     fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
435     where
436         W: Write,
437     {
438         match *self {
439             Image::None => dest.write_str("none"),
440             Image::Url(ref url) => url.to_css(dest),
441             Image::Gradient(ref gradient) => gradient.to_css(dest),
442             Image::Rect(ref rect) => rect.to_css(dest),
443             #[cfg(feature = "servo-layout-2013")]
444             Image::PaintWorklet(ref paint_worklet) => paint_worklet.to_css(dest),
445             #[cfg(feature = "gecko")]
446             Image::Element(ref selector) => {
447                 dest.write_str("-moz-element(#")?;
448                 serialize_atom_identifier(selector, dest)?;
449                 dest.write_str(")")
450             },
451             Image::ImageSet(ref is) => is.to_css(dest),
452             Image::CrossFade(ref cf) => cf.to_css(dest),
453         }
454     }
455 }
456 
457 impl<D, LP, NL, NLP, P, A: Zero, AoP, C> ToCss for Gradient<D, LP, NL, NLP, P, A, AoP, C>
458 where
459     D: LineDirection,
460     LP: ToCss,
461     NL: ToCss,
462     NLP: ToCss,
463     P: PositionComponent + ToCss,
464     A: ToCss,
465     AoP: ToCss,
466     C: ToCss,
467 {
to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: Write,468     fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
469     where
470         W: Write,
471     {
472         let (compat_mode, repeating) = match *self {
473             Gradient::Linear {
474                 compat_mode,
475                 repeating,
476                 ..
477             } => (compat_mode, repeating),
478             Gradient::Radial {
479                 compat_mode,
480                 repeating,
481                 ..
482             } => (compat_mode, repeating),
483             Gradient::Conic { repeating, .. } => (GradientCompatMode::Modern, repeating),
484         };
485 
486         match compat_mode {
487             GradientCompatMode::WebKit => dest.write_str("-webkit-")?,
488             GradientCompatMode::Moz => dest.write_str("-moz-")?,
489             _ => {},
490         }
491 
492         if repeating {
493             dest.write_str("repeating-")?;
494         }
495 
496         match *self {
497             Gradient::Linear {
498                 ref direction,
499                 ref items,
500                 compat_mode,
501                 ..
502             } => {
503                 dest.write_str("linear-gradient(")?;
504                 let mut skip_comma = if !direction.points_downwards(compat_mode) {
505                     direction.to_css(dest, compat_mode)?;
506                     false
507                 } else {
508                     true
509                 };
510                 for item in &**items {
511                     if !skip_comma {
512                         dest.write_str(", ")?;
513                     }
514                     skip_comma = false;
515                     item.to_css(dest)?;
516                 }
517             },
518             Gradient::Radial {
519                 ref shape,
520                 ref position,
521                 ref items,
522                 compat_mode,
523                 ..
524             } => {
525                 dest.write_str("radial-gradient(")?;
526                 let omit_shape = match *shape {
527                     EndingShape::Ellipse(Ellipse::Extent(ShapeExtent::Cover)) |
528                     EndingShape::Ellipse(Ellipse::Extent(ShapeExtent::FarthestCorner)) => true,
529                     _ => false,
530                 };
531                 let omit_position = position.is_center();
532                 if compat_mode == GradientCompatMode::Modern {
533                     if !omit_shape {
534                         shape.to_css(dest)?;
535                         if !omit_position {
536                             dest.write_str(" ")?;
537                         }
538                     }
539                     if !omit_position {
540                         dest.write_str("at ")?;
541                         position.to_css(dest)?;
542                     }
543                 } else {
544                     if !omit_position {
545                         position.to_css(dest)?;
546                         if !omit_shape {
547                             dest.write_str(", ")?;
548                         }
549                     }
550                     if !omit_shape {
551                         shape.to_css(dest)?;
552                     }
553                 }
554                 let mut skip_comma = omit_shape && omit_position;
555                 for item in &**items {
556                     if !skip_comma {
557                         dest.write_str(", ")?;
558                     }
559                     skip_comma = false;
560                     item.to_css(dest)?;
561                 }
562             },
563             Gradient::Conic {
564                 ref angle,
565                 ref position,
566                 ref items,
567                 ..
568             } => {
569                 dest.write_str("conic-gradient(")?;
570                 let omit_angle = angle.is_zero();
571                 let omit_position = position.is_center();
572                 if !omit_angle {
573                     dest.write_str("from ")?;
574                     angle.to_css(dest)?;
575                     if !omit_position {
576                         dest.write_str(" ")?;
577                     }
578                 }
579                 if !omit_position {
580                     dest.write_str("at ")?;
581                     position.to_css(dest)?;
582                 }
583                 let mut skip_comma = omit_angle && omit_position;
584                 for item in &**items {
585                     if !skip_comma {
586                         dest.write_str(", ")?;
587                     }
588                     skip_comma = false;
589                     item.to_css(dest)?;
590                 }
591             },
592         }
593         dest.write_str(")")
594     }
595 }
596 
597 /// The direction of a linear gradient.
598 pub trait LineDirection {
599     /// Whether this direction points towards, and thus can be omitted.
points_downwards(&self, compat_mode: GradientCompatMode) -> bool600     fn points_downwards(&self, compat_mode: GradientCompatMode) -> bool;
601 
602     /// Serialises this direction according to the compatibility mode.
to_css<W>(&self, dest: &mut CssWriter<W>, compat_mode: GradientCompatMode) -> fmt::Result where W: Write603     fn to_css<W>(&self, dest: &mut CssWriter<W>, compat_mode: GradientCompatMode) -> fmt::Result
604     where
605         W: Write;
606 }
607 
608 impl<L> ToCss for Circle<L>
609 where
610     L: ToCss,
611 {
to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: Write,612     fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
613     where
614         W: Write,
615     {
616         match *self {
617             Circle::Extent(ShapeExtent::FarthestCorner) | Circle::Extent(ShapeExtent::Cover) => {
618                 dest.write_str("circle")
619             },
620             Circle::Extent(keyword) => {
621                 dest.write_str("circle ")?;
622                 keyword.to_css(dest)
623             },
624             Circle::Radius(ref length) => length.to_css(dest),
625         }
626     }
627 }
628