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}