Skip to main content

style_traits/
values.rs

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//! Helper types and traits for the handling of CSS values.
6
7use app_units::Au;
8use cssparser::ToCss as CssparserToCss;
9use cssparser::{ParseError, Parser, UnicodeRange, serialize_string};
10use servo_arc::Arc;
11use std::fmt::{self, Write};
12
13/// Serialises a value according to its CSS representation.
14///
15/// This trait is implemented for `str` and its friends, serialising the string
16/// contents as a CSS quoted string.
17///
18/// This trait is derivable with `#[derive(ToCss)]`, with the following behaviour:
19/// * unit variants get serialised as the `snake-case` representation
20///   of their name;
21/// * unit variants whose name starts with "Moz" or "Webkit" are prepended
22///   with a "-";
23/// * if `#[css(comma)]` is found on a variant, its fields are separated by
24///   commas, otherwise, by spaces;
25/// * if `#[css(function)]` is found on a variant, the variant name gets
26///   serialised like unit variants and its fields are surrounded by parentheses;
27/// * if `#[css(iterable)]` is found on a function variant, that variant needs
28///   to have a single member, and that member needs to be iterable. The
29///   iterable will be serialized as the arguments for the function;
30/// * an iterable field can also be annotated with `#[css(if_empty = "foo")]`
31///   to print `"foo"` if the iterator is empty;
32/// * if `#[css(skip)]` is found on a field, the `ToCss` call for that field
33///   is skipped;
34/// * if `#[css(skip_if = "function")]` is found on a field, the `ToCss` call
35///   for that field is skipped if `function` returns true. This function is
36///   provided the field as an argument;
37/// * if `#[css(contextual_skip_if = "function")]` is found on a field, the
38///   `ToCss` call for that field is skipped if `function` returns true. This
39///   function is given all the fields in the current struct or variant as an
40///   argument;
41/// * `#[css(represents_keyword)]` can be used on bool fields in order to
42///   serialize the field name if the field is true, or nothing otherwise.  It
43///   also collects those keywords for `SpecifiedValueInfo`.
44/// * `#[css(bitflags(single="", mixed="", validate_mixed="", overlapping_bits)]` can
45///   be used to derive parse / serialize / etc on bitflags. The rules for parsing
46///   bitflags are the following:
47///
48///     * `single` flags can only appear on their own. It's common that bitflags
49///       properties at least have one such value like `none` or `auto`.
50///     * `mixed` properties can appear mixed together, but not along any other
51///       flag that shares a bit with itself. For example, if you have three
52///       bitflags like:
53///
54///       ```
55///       let FOO = 1 << 0;
56///       let BAR = 1 << 1;
57///       let BAZ = 1 << 2;
58///       let BAZZ = BAR | BAZ;
59///       ```
60///
61///       Then the following combinations won't be valid:
62///
63///         * foo foo: (every flag shares a bit with itself)
64///         * bar bazz: (bazz shares a bit with bar)
65///
66///       But `bar baz` will be valid, as they don't share bits, and so would
67///       `foo` with any other flag, or `bazz` on its own.
68///    * `validate_mixed` can be used to reject invalid mixed combinations, and also to simplify
69///      the type or add default ones if needed.
70///    * `overlapping_bits` enables some tracking during serialization of mixed flags to avoid
71///      serializing variants that can subsume other variants.
72///      In the example above, you could do: `mixed="foo,bazz,bar,baz", overlapping_bits`
73///      to ensure that if bazz is serialized, bar and baz aren't, even though
74///      their bits are set. Note that the serialization order is canonical,
75///      and thus depends on the order you specify the flags in.
76///
77/// * finally, one can put `#[css(derive_debug)]` on the whole type, to
78///   implement `Debug` by a single call to `ToCss::to_css`.
79pub trait ToCss {
80    /// Serialize `self` in CSS syntax, writing to `dest`.
81    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
82    where
83        W: Write;
84
85    /// Serialize `self` in CSS syntax and return a string.
86    ///
87    /// (This is a convenience wrapper for `to_css` and probably should not be overridden.)
88    #[inline]
89    fn to_css_string(&self) -> String {
90        let mut s = String::new();
91        self.to_css(&mut CssWriter::new(&mut s)).unwrap();
92        s
93    }
94
95    /// Serialize `self` in CSS syntax and return a CssString.
96    ///
97    /// (This is a convenience wrapper for `to_css` and probably should not be overridden.)
98    #[inline]
99    fn to_css_cssstring(&self) -> CssString {
100        let mut s = CssString::new();
101        self.to_css(&mut CssWriter::new(&mut s)).unwrap();
102        s
103    }
104}
105
106impl<T> ToCss for &T
107where
108    T: ToCss + ?Sized,
109{
110    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
111    where
112        W: Write,
113    {
114        (*self).to_css(dest)
115    }
116}
117
118impl ToCss for crate::owned_str::OwnedStr {
119    #[inline]
120    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
121    where
122        W: Write,
123    {
124        serialize_string(self, dest)
125    }
126}
127
128impl ToCss for str {
129    #[inline]
130    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
131    where
132        W: Write,
133    {
134        serialize_string(self, dest)
135    }
136}
137
138impl ToCss for String {
139    #[inline]
140    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
141    where
142        W: Write,
143    {
144        serialize_string(self, dest)
145    }
146}
147
148impl<T> ToCss for Option<T>
149where
150    T: ToCss,
151{
152    #[inline]
153    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
154    where
155        W: Write,
156    {
157        self.as_ref().map_or(Ok(()), |value| value.to_css(dest))
158    }
159}
160
161impl ToCss for () {
162    #[inline]
163    fn to_css<W>(&self, _: &mut CssWriter<W>) -> fmt::Result
164    where
165        W: Write,
166    {
167        Ok(())
168    }
169}
170
171/// A writer tailored for serialising CSS.
172///
173/// Coupled with SequenceWriter, this allows callers to transparently handle
174/// things like comma-separated values etc.
175pub struct CssWriter<'w, W: 'w> {
176    inner: &'w mut W,
177    prefix: Option<&'static str>,
178}
179
180impl<'w, W> CssWriter<'w, W>
181where
182    W: Write,
183{
184    /// Creates a new `CssWriter`.
185    #[inline]
186    pub fn new(inner: &'w mut W) -> Self {
187        Self {
188            inner,
189            prefix: Some(""),
190        }
191    }
192}
193
194impl<'w, W> Write for CssWriter<'w, W>
195where
196    W: Write,
197{
198    #[inline]
199    fn write_str(&mut self, s: &str) -> fmt::Result {
200        if s.is_empty() {
201            return Ok(());
202        }
203        if let Some(prefix) = self.prefix.take() {
204            // We are going to write things, but first we need to write
205            // the prefix that was set by `SequenceWriter::item`.
206            if !prefix.is_empty() {
207                self.inner.write_str(prefix)?;
208            }
209        }
210        self.inner.write_str(s)
211    }
212
213    #[inline]
214    fn write_char(&mut self, c: char) -> fmt::Result {
215        if let Some(prefix) = self.prefix.take() {
216            // See comment in `write_str`.
217            if !prefix.is_empty() {
218                self.inner.write_str(prefix)?;
219            }
220        }
221        self.inner.write_char(c)
222    }
223}
224
225/// To avoid accidentally instantiating multiple monomorphizations of large
226/// serialization routines, we define explicit concrete types and require
227/// them in those routines. This avoids accidental mixing of String and
228/// nsACString arguments in Gecko, which would cause code size to blow up.
229#[cfg(feature = "gecko")]
230pub type CssStringWriter = ::nsstring::nsACString;
231
232/// String type that coerces to CssStringWriter, used when serialization code
233/// needs to allocate a temporary string. In Gecko, this is backed by
234/// nsCString, which allows the result to be passed directly to C++ without
235/// conversion or copying. This makes it suitable not only for temporary
236/// serialization but also for values that need to cross the Rust/C++ boundary.
237#[cfg(feature = "gecko")]
238pub type CssString = ::nsstring::nsCString;
239
240/// String. The comments for the Gecko types explain the need for this abstraction.
241#[cfg(feature = "servo")]
242pub type CssStringWriter = String;
243
244/// String. The comments for the Gecko types explain the need for this abstraction.
245#[cfg(feature = "servo")]
246pub type CssString = String;
247
248/// Convenience wrapper to serialise CSS values separated by a given string.
249pub struct SequenceWriter<'a, 'b: 'a, W: 'b> {
250    inner: &'a mut CssWriter<'b, W>,
251    separator: &'static str,
252}
253
254impl<'a, 'b, W> SequenceWriter<'a, 'b, W>
255where
256    W: Write + 'b,
257{
258    /// Returns whether this writer has written any item.
259    pub fn has_written(&self) -> bool {
260        // See comment in item()
261        self.inner.prefix.is_none()
262    }
263
264    /// Create a new sequence writer.
265    #[inline]
266    pub fn new(inner: &'a mut CssWriter<'b, W>, separator: &'static str) -> Self {
267        if inner.prefix.is_none() {
268            // See comment in `item`.
269            inner.prefix = Some("");
270        }
271        Self { inner, separator }
272    }
273
274    /// Serialize the CSS Value with the specific serialization function.
275    #[inline]
276    pub fn write_item<F>(&mut self, f: F) -> fmt::Result
277    where
278        F: FnOnce(&mut CssWriter<'b, W>) -> fmt::Result,
279    {
280        // Separate non-generic functions so that this code is not repeated
281        // in every monomorphization with a different type `F` or `W`.
282        // https://github.com/servo/servo/issues/26713
283        fn before(
284            prefix: &mut Option<&'static str>,
285            separator: &'static str,
286        ) -> Option<&'static str> {
287            let old_prefix = *prefix;
288            if old_prefix.is_none() {
289                // If there is no prefix in the inner writer, a previous
290                // call to this method produced output, which means we need
291                // to write the separator next time we produce output again.
292                *prefix = Some(separator);
293            }
294            old_prefix
295        }
296        fn after(
297            old_prefix: Option<&'static str>,
298            prefix: &mut Option<&'static str>,
299            separator: &'static str,
300        ) {
301            match (old_prefix, *prefix) {
302                (_, None) => {
303                    // This call produced output and cleaned up after itself.
304                },
305                (None, Some(p)) => {
306                    // Some previous call to `item` produced output,
307                    // but this one did not, prefix should be the same as
308                    // the one we set.
309                    debug_assert_eq!(separator, p);
310                    // We clean up here even though it's not necessary just
311                    // to be able to do all these assertion checks.
312                    *prefix = None;
313                },
314                (Some(old), Some(new)) => {
315                    // No previous call to `item` produced output, and this one
316                    // either.
317                    debug_assert_eq!(old, new);
318                },
319            }
320        }
321
322        let old_prefix = before(&mut self.inner.prefix, self.separator);
323        f(self.inner)?;
324        after(old_prefix, &mut self.inner.prefix, self.separator);
325        Ok(())
326    }
327
328    /// Serialises a CSS value, writing any separator as necessary.
329    ///
330    /// The separator is never written before any `item` produces any output,
331    /// and is written in subsequent calls only if the `item` produces some
332    /// output on its own again. This lets us handle `Option<T>` fields by
333    /// just not printing anything on `None`.
334    #[inline]
335    pub fn item<T>(&mut self, item: &T) -> fmt::Result
336    where
337        T: ToCss,
338    {
339        self.write_item(|inner| item.to_css(inner))
340    }
341
342    /// Writes a string as-is (i.e. not escaped or wrapped in quotes)
343    /// with any separator as necessary.
344    ///
345    /// See SequenceWriter::item.
346    #[inline]
347    pub fn raw_item(&mut self, item: &str) -> fmt::Result {
348        self.write_item(|inner| inner.write_str(item))
349    }
350}
351
352/// Type used as the associated type in the `OneOrMoreSeparated` trait on a
353/// type to indicate that a serialized list of elements of this type is
354/// separated by commas.
355pub struct Comma;
356
357/// Type used as the associated type in the `OneOrMoreSeparated` trait on a
358/// type to indicate that a serialized list of elements of this type is
359/// separated by spaces.
360pub struct Space;
361
362/// Type used as the associated type in the `OneOrMoreSeparated` trait on a
363/// type to indicate that a serialized list of elements of this type is
364/// separated by commas, but spaces without commas are also allowed when
365/// parsing.
366pub struct CommaWithSpace;
367
368/// A trait satisfied by the types corresponding to separators.
369pub trait Separator {
370    /// The separator string that the satisfying separator type corresponds to.
371    fn separator() -> &'static str;
372
373    /// Parses a sequence of values separated by this separator.
374    ///
375    /// The given closure is called repeatedly for each item in the sequence.
376    ///
377    /// Successful results are accumulated in a vector.
378    ///
379    /// This method returns `Err(_)` the first time a closure does or if
380    /// the separators aren't correct.
381    fn parse<'i, F, T, E>(parser: &mut Parser<'i>, parse_one: F) -> Result<Vec<T>, ParseError<E>>
382    where
383        F: FnMut(&mut Parser<'i>) -> Result<T, ParseError<E>>;
384}
385
386impl Separator for Comma {
387    fn separator() -> &'static str {
388        ", "
389    }
390
391    fn parse<'i, F, T, E>(input: &mut Parser<'i>, parse_one: F) -> Result<Vec<T>, ParseError<E>>
392    where
393        F: FnMut(&mut Parser<'i>) -> Result<T, ParseError<E>>,
394    {
395        input.parse_comma_separated(parse_one)
396    }
397}
398
399impl Separator for Space {
400    fn separator() -> &'static str {
401        " "
402    }
403
404    fn parse<'i, F, T, E>(input: &mut Parser<'i>, mut parse_one: F) -> Result<Vec<T>, ParseError<E>>
405    where
406        F: FnMut(&mut Parser<'i>) -> Result<T, ParseError<E>>,
407    {
408        input.skip_whitespace(); // Unnecessary for correctness, but may help try_parse() rewind less.
409        let mut results = vec![parse_one(input)?];
410        loop {
411            input.skip_whitespace(); // Unnecessary for correctness, but may help try_parse() rewind less.
412            if let Ok(item) = input.try_parse(&mut parse_one) {
413                results.push(item);
414            } else {
415                return Ok(results);
416            }
417        }
418    }
419}
420
421impl Separator for CommaWithSpace {
422    fn separator() -> &'static str {
423        ", "
424    }
425
426    fn parse<'i, F, T, E>(input: &mut Parser<'i>, mut parse_one: F) -> Result<Vec<T>, ParseError<E>>
427    where
428        F: FnMut(&mut Parser<'i>) -> Result<T, ParseError<E>>,
429    {
430        input.skip_whitespace(); // Unnecessary for correctness, but may help try_parse() rewind less.
431        let mut results = vec![parse_one(input)?];
432        loop {
433            input.skip_whitespace(); // Unnecessary for correctness, but may help try_parse() rewind less.
434            let comma = input.try_parse(|i| i.expect_comma()).is_ok();
435            input.skip_whitespace(); // Unnecessary for correctness, but may help try_parse() rewind less.
436            if let Ok(item) = input.try_parse(&mut parse_one) {
437                results.push(item);
438            } else if comma {
439                return Err(ParseError::unexpected_token());
440            } else {
441                break;
442            }
443        }
444        Ok(results)
445    }
446}
447
448/// Marker trait on T to automatically implement ToCss for Vec<T> when T's are
449/// separated by some delimiter `delim`.
450pub trait OneOrMoreSeparated {
451    /// Associated type indicating which separator is used.
452    type S: Separator;
453}
454
455impl OneOrMoreSeparated for UnicodeRange {
456    type S = Comma;
457}
458
459impl<T> ToCss for Vec<T>
460where
461    T: ToCss + OneOrMoreSeparated,
462{
463    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
464    where
465        W: Write,
466    {
467        let mut iter = self.iter();
468        iter.next().unwrap().to_css(dest)?;
469        for item in iter {
470            dest.write_str(<T as OneOrMoreSeparated>::S::separator())?;
471            item.to_css(dest)?;
472        }
473        Ok(())
474    }
475}
476
477impl<T> ToCss for Box<T>
478where
479    T: ?Sized + ToCss,
480{
481    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
482    where
483        W: Write,
484    {
485        (**self).to_css(dest)
486    }
487}
488
489impl<T> ToCss for Arc<T>
490where
491    T: ?Sized + ToCss,
492{
493    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
494    where
495        W: Write,
496    {
497        (**self).to_css(dest)
498    }
499}
500
501impl ToCss for Au {
502    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
503    where
504        W: Write,
505    {
506        self.to_f64_px().to_css(dest)?;
507        dest.write_str("px")
508    }
509}
510
511macro_rules! impl_to_css_for_predefined_type {
512    ($name: ty) => {
513        impl<'a> ToCss for $name {
514            fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
515            where
516                W: Write,
517            {
518                ::cssparser::ToCss::to_css(self, dest)
519            }
520        }
521    };
522}
523
524impl_to_css_for_predefined_type!(f32);
525impl_to_css_for_predefined_type!(i8);
526impl_to_css_for_predefined_type!(i32);
527impl_to_css_for_predefined_type!(u8);
528impl_to_css_for_predefined_type!(u16);
529impl_to_css_for_predefined_type!(u32);
530impl_to_css_for_predefined_type!(::cssparser::Token<'a>);
531impl_to_css_for_predefined_type!(::cssparser::UnicodeRange);
532
533/// Helper types for the handling of specified values.
534pub mod specified {
535    use crate::ParsingMode;
536
537    /// Whether to allow negative lengths or not.
538    #[repr(u8)]
539    #[derive(
540        Clone,
541        Copy,
542        Debug,
543        Default,
544        Deserialize,
545        Eq,
546        MallocSizeOf,
547        PartialEq,
548        PartialOrd,
549        Serialize,
550        ToShmem,
551    )]
552    pub enum AllowedNumericType {
553        /// Allow all kind of numeric values.
554        #[default]
555        All,
556        /// Allow only non-negative numeric values.
557        NonNegative,
558        /// Allow only numeric values greater or equal to 1.0.
559        AtLeastOne,
560        /// Allow only numeric values from 0 to 1.0.
561        ZeroToOne,
562    }
563
564    impl AllowedNumericType {
565        /// Whether the value fits the rules of this numeric type.
566        #[inline]
567        pub fn is_ok(&self, parsing_mode: ParsingMode, val: f32) -> bool {
568            if parsing_mode.allows_all_numeric_values() {
569                return true;
570            }
571            match *self {
572                Self::All => true,
573                Self::NonNegative => val >= 0.0,
574                Self::AtLeastOne => val >= 1.0,
575                Self::ZeroToOne => (0.0..=1.0).contains(&val),
576            }
577        }
578
579        /// Clamp the value following the rules of this numeric type.
580        #[inline]
581        pub fn clamp(&self, val: f32) -> f32 {
582            match *self {
583                Self::All => val,
584                Self::NonNegative => val.max(0.),
585                Self::AtLeastOne => val.max(1.),
586                Self::ZeroToOne => val.clamp(0., 1.),
587            }
588        }
589    }
590}