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