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(
135     Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem,
136 )]
137 #[repr(C)]
138 pub struct GenericImageSetItem<Image, Resolution> {
139     /// `<image>`. `<string>` is converted to `Image::Url` at parse time.
140     pub image: Image,
141     /// The `<resolution>`.
142     ///
143     /// TODO: Skip serialization if it is 1x.
144     pub resolution: Resolution,
145 
146     /// The `type(<string>)`
147     /// (Optional) Specify the image's MIME type
148     pub mime_type: crate::OwnedStr,
149 
150     /// True if mime_type has been specified
151     pub has_mime_type: bool,
152 }
153 
154 impl<I: style_traits::ToCss, R: style_traits::ToCss> ToCss for GenericImageSetItem<I, R>
155 {
to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write,156     fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
157     where
158         W: fmt::Write,
159     {
160         self.image.to_css(dest)?;
161         dest.write_str(" ")?;
162         self.resolution.to_css(dest)?;
163 
164         if self.has_mime_type {
165             dest.write_str(" ")?;
166             dest.write_str("type(")?;
167             self.mime_type.to_css(dest)?;
168             dest.write_str(")")?;
169         }
170         Ok(())
171     }
172 }
173 
174 pub use self::GenericImageSet as ImageSet;
175 pub use self::GenericImageSetItem as ImageSetItem;
176 
177 /// A CSS gradient.
178 /// <https://drafts.csswg.org/css-images/#gradients>
179 #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem)]
180 #[repr(C)]
181 pub enum GenericGradient<
182     LineDirection,
183     LengthPercentage,
184     NonNegativeLength,
185     NonNegativeLengthPercentage,
186     Position,
187     Angle,
188     AngleOrPercentage,
189     Color,
190 > {
191     /// A linear gradient.
192     Linear {
193         /// Line direction
194         direction: LineDirection,
195         /// The color stops and interpolation hints.
196         items: crate::OwnedSlice<GenericGradientItem<Color, LengthPercentage>>,
197         /// True if this is a repeating gradient.
198         repeating: bool,
199         /// Compatibility mode.
200         compat_mode: GradientCompatMode,
201     },
202     /// A radial gradient.
203     Radial {
204         /// Shape of gradient
205         shape: GenericEndingShape<NonNegativeLength, NonNegativeLengthPercentage>,
206         /// Center of gradient
207         position: Position,
208         /// The color stops and interpolation hints.
209         items: crate::OwnedSlice<GenericGradientItem<Color, LengthPercentage>>,
210         /// True if this is a repeating gradient.
211         repeating: bool,
212         /// Compatibility mode.
213         compat_mode: GradientCompatMode,
214     },
215     /// A conic gradient.
216     Conic {
217         /// Start angle of gradient
218         angle: Angle,
219         /// Center of gradient
220         position: Position,
221         /// The color stops and interpolation hints.
222         items: crate::OwnedSlice<GenericGradientItem<Color, AngleOrPercentage>>,
223         /// True if this is a repeating gradient.
224         repeating: bool,
225     },
226 }
227 
228 pub use self::GenericGradient as Gradient;
229 
230 #[derive(
231     Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem,
232 )]
233 #[repr(u8)]
234 /// Whether we used the modern notation or the compatibility `-webkit`, `-moz` prefixes.
235 pub enum GradientCompatMode {
236     /// Modern syntax.
237     Modern,
238     /// `-webkit` prefix.
239     WebKit,
240     /// `-moz` prefix
241     Moz,
242 }
243 
244 /// A radial gradient's ending shape.
245 #[derive(
246     Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem,
247 )]
248 #[repr(C, u8)]
249 pub enum GenericEndingShape<NonNegativeLength, NonNegativeLengthPercentage> {
250     /// A circular gradient.
251     Circle(GenericCircle<NonNegativeLength>),
252     /// An elliptic gradient.
253     Ellipse(GenericEllipse<NonNegativeLengthPercentage>),
254 }
255 
256 pub use self::GenericEndingShape as EndingShape;
257 
258 /// A circle shape.
259 #[derive(
260     Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem,
261 )]
262 #[repr(C, u8)]
263 pub enum GenericCircle<NonNegativeLength> {
264     /// A circle radius.
265     Radius(NonNegativeLength),
266     /// A circle extent.
267     Extent(ShapeExtent),
268 }
269 
270 pub use self::GenericCircle as Circle;
271 
272 /// An ellipse shape.
273 #[derive(
274     Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem,
275 )]
276 #[repr(C, u8)]
277 pub enum GenericEllipse<NonNegativeLengthPercentage> {
278     /// An ellipse pair of radii.
279     Radii(NonNegativeLengthPercentage, NonNegativeLengthPercentage),
280     /// An ellipse extent.
281     Extent(ShapeExtent),
282 }
283 
284 pub use self::GenericEllipse as Ellipse;
285 
286 /// <https://drafts.csswg.org/css-images/#typedef-extent-keyword>
287 #[allow(missing_docs)]
288 #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
289 #[derive(
290     Clone,
291     Copy,
292     Debug,
293     Eq,
294     MallocSizeOf,
295     Parse,
296     PartialEq,
297     ToComputedValue,
298     ToCss,
299     ToResolvedValue,
300     ToShmem,
301 )]
302 #[repr(u8)]
303 pub enum ShapeExtent {
304     ClosestSide,
305     FarthestSide,
306     ClosestCorner,
307     FarthestCorner,
308     Contain,
309     Cover,
310 }
311 
312 /// A gradient item.
313 /// <https://drafts.csswg.org/css-images-4/#color-stop-syntax>
314 #[derive(
315     Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem,
316 )]
317 #[repr(C, u8)]
318 pub enum GenericGradientItem<Color, T> {
319     /// A simple color stop, without position.
320     SimpleColorStop(Color),
321     /// A complex color stop, with a position.
322     ComplexColorStop {
323         /// The color for the stop.
324         color: Color,
325         /// The position for the stop.
326         position: T,
327     },
328     /// An interpolation hint.
329     InterpolationHint(T),
330 }
331 
332 pub use self::GenericGradientItem as GradientItem;
333 
334 /// A color stop.
335 /// <https://drafts.csswg.org/css-images/#typedef-color-stop-list>
336 #[derive(
337     Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem,
338 )]
339 pub struct ColorStop<Color, T> {
340     /// The color of this stop.
341     pub color: Color,
342     /// The position of this stop.
343     pub position: Option<T>,
344 }
345 
346 impl<Color, T> ColorStop<Color, T> {
347     /// Convert the color stop into an appropriate `GradientItem`.
348     #[inline]
into_item(self) -> GradientItem<Color, T>349     pub fn into_item(self) -> GradientItem<Color, T> {
350         match self.position {
351             Some(position) => GradientItem::ComplexColorStop {
352                 color: self.color,
353                 position,
354             },
355             None => GradientItem::SimpleColorStop(self.color),
356         }
357     }
358 }
359 
360 /// Specified values for a paint worklet.
361 /// <https://drafts.css-houdini.org/css-paint-api/>
362 #[cfg_attr(feature = "servo", derive(MallocSizeOf))]
363 #[derive(Clone, Debug, PartialEq, ToComputedValue, ToResolvedValue, ToShmem)]
364 pub struct PaintWorklet {
365     /// The name the worklet was registered with.
366     pub name: Atom,
367     /// The arguments for the worklet.
368     /// TODO: store a parsed representation of the arguments.
369     #[cfg_attr(feature = "servo", ignore_malloc_size_of = "Arc")]
370     #[compute(no_field_bound)]
371     #[resolve(no_field_bound)]
372     pub arguments: Vec<Arc<custom_properties::SpecifiedValue>>,
373 }
374 
375 impl ::style_traits::SpecifiedValueInfo for PaintWorklet {}
376 
377 impl ToCss for PaintWorklet {
to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: Write,378     fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
379     where
380         W: Write,
381     {
382         dest.write_str("paint(")?;
383         serialize_atom_identifier(&self.name, dest)?;
384         for argument in &self.arguments {
385             dest.write_str(", ")?;
386             argument.to_css(dest)?;
387         }
388         dest.write_str(")")
389     }
390 }
391 
392 /// Values for `moz-image-rect`.
393 ///
394 /// `-moz-image-rect(<uri>, top, right, bottom, left);`
395 #[allow(missing_docs)]
396 #[derive(
397     Clone,
398     Debug,
399     MallocSizeOf,
400     PartialEq,
401     SpecifiedValueInfo,
402     ToComputedValue,
403     ToCss,
404     ToResolvedValue,
405     ToShmem,
406 )]
407 #[css(comma, function = "-moz-image-rect")]
408 #[repr(C)]
409 pub struct GenericMozImageRect<NumberOrPercentage, MozImageRectUrl> {
410     pub url: MozImageRectUrl,
411     pub top: NumberOrPercentage,
412     pub right: NumberOrPercentage,
413     pub bottom: NumberOrPercentage,
414     pub left: NumberOrPercentage,
415 }
416 
417 pub use self::GenericMozImageRect as MozImageRect;
418 
419 impl<G, R, U, C, P, Resolution> fmt::Debug for Image<G, R, U, C, P, Resolution>
420 where
421     Image<G, R, U, C, P, Resolution>: ToCss,
422 {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result423     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
424         self.to_css(&mut CssWriter::new(f))
425     }
426 }
427 
428 impl<G, R, U, C, P, Resolution> ToCss for Image<G, R, U, C, P, Resolution>
429 where
430     G: ToCss,
431     R: ToCss,
432     U: ToCss,
433     C: ToCss,
434     P: ToCss,
435     Resolution: ToCss,
436 {
to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: Write,437     fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
438     where
439         W: Write,
440     {
441         match *self {
442             Image::None => dest.write_str("none"),
443             Image::Url(ref url) => url.to_css(dest),
444             Image::Gradient(ref gradient) => gradient.to_css(dest),
445             Image::Rect(ref rect) => rect.to_css(dest),
446             #[cfg(feature = "servo-layout-2013")]
447             Image::PaintWorklet(ref paint_worklet) => paint_worklet.to_css(dest),
448             #[cfg(feature = "gecko")]
449             Image::Element(ref selector) => {
450                 dest.write_str("-moz-element(#")?;
451                 serialize_atom_identifier(selector, dest)?;
452                 dest.write_str(")")
453             },
454             Image::ImageSet(ref is) => is.to_css(dest),
455             Image::CrossFade(ref cf) => cf.to_css(dest),
456         }
457     }
458 }
459 
460 impl<D, LP, NL, NLP, P, A: Zero, AoP, C> ToCss for Gradient<D, LP, NL, NLP, P, A, AoP, C>
461 where
462     D: LineDirection,
463     LP: ToCss,
464     NL: ToCss,
465     NLP: ToCss,
466     P: PositionComponent + ToCss,
467     A: ToCss,
468     AoP: ToCss,
469     C: ToCss,
470 {
to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: Write,471     fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
472     where
473         W: Write,
474     {
475         let (compat_mode, repeating) = match *self {
476             Gradient::Linear {
477                 compat_mode,
478                 repeating,
479                 ..
480             } => (compat_mode, repeating),
481             Gradient::Radial {
482                 compat_mode,
483                 repeating,
484                 ..
485             } => (compat_mode, repeating),
486             Gradient::Conic { repeating, .. } => (GradientCompatMode::Modern, repeating),
487         };
488 
489         match compat_mode {
490             GradientCompatMode::WebKit => dest.write_str("-webkit-")?,
491             GradientCompatMode::Moz => dest.write_str("-moz-")?,
492             _ => {},
493         }
494 
495         if repeating {
496             dest.write_str("repeating-")?;
497         }
498 
499         match *self {
500             Gradient::Linear {
501                 ref direction,
502                 ref items,
503                 compat_mode,
504                 ..
505             } => {
506                 dest.write_str("linear-gradient(")?;
507                 let mut skip_comma = if !direction.points_downwards(compat_mode) {
508                     direction.to_css(dest, compat_mode)?;
509                     false
510                 } else {
511                     true
512                 };
513                 for item in &**items {
514                     if !skip_comma {
515                         dest.write_str(", ")?;
516                     }
517                     skip_comma = false;
518                     item.to_css(dest)?;
519                 }
520             },
521             Gradient::Radial {
522                 ref shape,
523                 ref position,
524                 ref items,
525                 compat_mode,
526                 ..
527             } => {
528                 dest.write_str("radial-gradient(")?;
529                 let omit_shape = match *shape {
530                     EndingShape::Ellipse(Ellipse::Extent(ShapeExtent::Cover)) |
531                     EndingShape::Ellipse(Ellipse::Extent(ShapeExtent::FarthestCorner)) => true,
532                     _ => false,
533                 };
534                 let omit_position = position.is_center();
535                 if compat_mode == GradientCompatMode::Modern {
536                     if !omit_shape {
537                         shape.to_css(dest)?;
538                         if !omit_position {
539                             dest.write_str(" ")?;
540                         }
541                     }
542                     if !omit_position {
543                         dest.write_str("at ")?;
544                         position.to_css(dest)?;
545                     }
546                 } else {
547                     if !omit_position {
548                         position.to_css(dest)?;
549                         if !omit_shape {
550                             dest.write_str(", ")?;
551                         }
552                     }
553                     if !omit_shape {
554                         shape.to_css(dest)?;
555                     }
556                 }
557                 let mut skip_comma = omit_shape && omit_position;
558                 for item in &**items {
559                     if !skip_comma {
560                         dest.write_str(", ")?;
561                     }
562                     skip_comma = false;
563                     item.to_css(dest)?;
564                 }
565             },
566             Gradient::Conic {
567                 ref angle,
568                 ref position,
569                 ref items,
570                 ..
571             } => {
572                 dest.write_str("conic-gradient(")?;
573                 let omit_angle = angle.is_zero();
574                 let omit_position = position.is_center();
575                 if !omit_angle {
576                     dest.write_str("from ")?;
577                     angle.to_css(dest)?;
578                     if !omit_position {
579                         dest.write_str(" ")?;
580                     }
581                 }
582                 if !omit_position {
583                     dest.write_str("at ")?;
584                     position.to_css(dest)?;
585                 }
586                 let mut skip_comma = omit_angle && omit_position;
587                 for item in &**items {
588                     if !skip_comma {
589                         dest.write_str(", ")?;
590                     }
591                     skip_comma = false;
592                     item.to_css(dest)?;
593                 }
594             },
595         }
596         dest.write_str(")")
597     }
598 }
599 
600 /// The direction of a linear gradient.
601 pub trait LineDirection {
602     /// Whether this direction points towards, and thus can be omitted.
points_downwards(&self, compat_mode: GradientCompatMode) -> bool603     fn points_downwards(&self, compat_mode: GradientCompatMode) -> bool;
604 
605     /// Serialises this direction according to the compatibility mode.
to_css<W>(&self, dest: &mut CssWriter<W>, compat_mode: GradientCompatMode) -> fmt::Result where W: Write606     fn to_css<W>(&self, dest: &mut CssWriter<W>, compat_mode: GradientCompatMode) -> fmt::Result
607     where
608         W: Write;
609 }
610 
611 impl<L> ToCss for Circle<L>
612 where
613     L: ToCss,
614 {
to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: Write,615     fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
616     where
617         W: Write,
618     {
619         match *self {
620             Circle::Extent(ShapeExtent::FarthestCorner) | Circle::Extent(ShapeExtent::Cover) => {
621                 dest.write_str("circle")
622             },
623             Circle::Extent(keyword) => {
624                 dest.write_str("circle ")?;
625                 keyword.to_css(dest)
626             },
627             Circle::Radius(ref length) => length.to_css(dest),
628         }
629     }
630 }
631