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}