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