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};
12use thin_vec::ThinVec;
13
14/// Serialises a value according to its CSS representation.
15///
16/// This trait is implemented for `str` and its friends, serialising the string
17/// contents as a CSS quoted string.
18///
19/// This trait is derivable with `#[derive(ToCss)]`, with the following behaviour:
20/// * unit variants get serialised as the `snake-case` representation
21/// of their name;
22/// * unit variants whose name starts with "Moz" or "Webkit" are prepended
23/// with a "-";
24/// * if `#[css(comma)]` is found on a variant, its fields are separated by
25/// commas, otherwise, by spaces;
26/// * if `#[css(function)]` is found on a variant, the variant name gets
27/// serialised like unit variants and its fields are surrounded by parentheses;
28/// * if `#[css(iterable)]` is found on a function variant, that variant needs
29/// to have a single member, and that member needs to be iterable. The
30/// iterable will be serialized as the arguments for the function;
31/// * an iterable field can also be annotated with `#[css(if_empty = "foo")]`
32/// to print `"foo"` if the iterator is empty;
33/// * if `#[css(dimension)]` is found on a variant, that variant needs
34/// to have a single member. The variant would be serialized as a CSS
35/// dimension token, like: <member><identifier>;
36/// * if `#[css(skip)]` is found on a field, the `ToCss` call for that field
37/// is skipped;
38/// * if `#[css(skip_if = "function")]` is found on a field, the `ToCss` call
39/// for that field is skipped if `function` returns true. This function is
40/// provided the field as an argument;
41/// * if `#[css(contextual_skip_if = "function")]` is found on a field, the
42/// `ToCss` call for that field is skipped if `function` returns true. This
43/// function is given all the fields in the current struct or variant as an
44/// argument;
45/// * `#[css(represents_keyword)]` can be used on bool fields in order to
46/// serialize the field name if the field is true, or nothing otherwise. It
47/// also collects those keywords for `SpecifiedValueInfo`.
48/// * `#[css(bitflags(single="", mixed="", validate_mixed="", overlapping_bits)]` can
49/// be used to derive parse / serialize / etc on bitflags. The rules for parsing
50/// bitflags are the following:
51///
52/// * `single` flags can only appear on their own. It's common that bitflags
53/// properties at least have one such value like `none` or `auto`.
54/// * `mixed` properties can appear mixed together, but not along any other
55/// flag that shares a bit with itself. For example, if you have three
56/// bitflags like:
57///
58/// FOO = 1 << 0;
59/// BAR = 1 << 1;
60/// BAZ = 1 << 2;
61/// BAZZ = BAR | BAZ;
62///
63/// Then the following combinations won't be valid:
64///
65/// * foo foo: (every flag shares a bit with itself)
66/// * bar bazz: (bazz shares a bit with bar)
67///
68/// But `bar baz` will be valid, as they don't share bits, and so would
69/// `foo` with any other flag, or `bazz` on its own.
70/// * `validate_mixed` can be used to reject invalid mixed combinations, and also to simplify
71/// the type or add default ones if needed.
72/// * `overlapping_bits` enables some tracking during serialization of mixed flags to avoid
73/// serializing variants that can subsume other variants.
74/// In the example above, you could do:
75/// mixed="foo,bazz,bar,baz", overlapping_bits
76/// to ensure that if bazz is serialized, bar and baz aren't, even though
77/// their bits are set. Note that the serialization order is canonical,
78/// and thus depends on the order you specify the flags in.
79///
80/// * finally, one can put `#[css(derive_debug)]` on the whole type, to
81/// implement `Debug` by a single call to `ToCss::to_css`.
82pub trait ToCss {
83 /// Serialize `self` in CSS syntax, writing to `dest`.
84 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
85 where
86 W: Write;
87
88 /// Serialize `self` in CSS syntax and return a string.
89 ///
90 /// (This is a convenience wrapper for `to_css` and probably should not be overridden.)
91 #[inline]
92 fn to_css_string(&self) -> String {
93 let mut s = String::new();
94 self.to_css(&mut CssWriter::new(&mut s)).unwrap();
95 s
96 }
97
98 /// Serialize `self` in CSS syntax and return a CssString.
99 ///
100 /// (This is a convenience wrapper for `to_css` and probably should not be overridden.)
101 #[inline]
102 fn to_css_cssstring(&self) -> CssString {
103 let mut s = CssString::new();
104 self.to_css(&mut CssWriter::new(&mut s)).unwrap();
105 s
106 }
107}
108
109impl<'a, T> ToCss for &'a T
110where
111 T: ToCss + ?Sized,
112{
113 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
114 where
115 W: Write,
116 {
117 (*self).to_css(dest)
118 }
119}
120
121impl ToCss for crate::owned_str::OwnedStr {
122 #[inline]
123 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
124 where
125 W: Write,
126 {
127 serialize_string(self, dest)
128 }
129}
130
131impl ToCss for str {
132 #[inline]
133 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
134 where
135 W: Write,
136 {
137 serialize_string(self, dest)
138 }
139}
140
141impl ToCss for String {
142 #[inline]
143 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
144 where
145 W: Write,
146 {
147 serialize_string(self, dest)
148 }
149}
150
151impl<T> ToCss for Option<T>
152where
153 T: ToCss,
154{
155 #[inline]
156 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
157 where
158 W: Write,
159 {
160 self.as_ref().map_or(Ok(()), |value| value.to_css(dest))
161 }
162}
163
164impl ToCss for () {
165 #[inline]
166 fn to_css<W>(&self, _: &mut CssWriter<W>) -> fmt::Result
167 where
168 W: Write,
169 {
170 Ok(())
171 }
172}
173
174/// A writer tailored for serialising CSS.
175///
176/// Coupled with SequenceWriter, this allows callers to transparently handle
177/// things like comma-separated values etc.
178pub struct CssWriter<'w, W: 'w> {
179 inner: &'w mut W,
180 prefix: Option<&'static str>,
181}
182
183impl<'w, W> CssWriter<'w, W>
184where
185 W: Write,
186{
187 /// Creates a new `CssWriter`.
188 #[inline]
189 pub fn new(inner: &'w mut W) -> Self {
190 Self {
191 inner,
192 prefix: Some(""),
193 }
194 }
195}
196
197impl<'w, W> Write for CssWriter<'w, W>
198where
199 W: Write,
200{
201 #[inline]
202 fn write_str(&mut self, s: &str) -> fmt::Result {
203 if s.is_empty() {
204 return Ok(());
205 }
206 if let Some(prefix) = self.prefix.take() {
207 // We are going to write things, but first we need to write
208 // the prefix that was set by `SequenceWriter::item`.
209 if !prefix.is_empty() {
210 self.inner.write_str(prefix)?;
211 }
212 }
213 self.inner.write_str(s)
214 }
215
216 #[inline]
217 fn write_char(&mut self, c: char) -> fmt::Result {
218 if let Some(prefix) = self.prefix.take() {
219 // See comment in `write_str`.
220 if !prefix.is_empty() {
221 self.inner.write_str(prefix)?;
222 }
223 }
224 self.inner.write_char(c)
225 }
226}
227
228/// To avoid accidentally instantiating multiple monomorphizations of large
229/// serialization routines, we define explicit concrete types and require
230/// them in those routines. This avoids accidental mixing of String and
231/// nsACString arguments in Gecko, which would cause code size to blow up.
232#[cfg(feature = "gecko")]
233pub type CssStringWriter = ::nsstring::nsACString;
234
235/// String type that coerces to CssStringWriter, used when serialization code
236/// needs to allocate a temporary string. In Gecko, this is backed by
237/// nsCString, which allows the result to be passed directly to C++ without
238/// conversion or copying. This makes it suitable not only for temporary
239/// serialization but also for values that need to cross the Rust/C++ boundary.
240#[cfg(feature = "gecko")]
241pub type CssString = ::nsstring::nsCString;
242
243/// String. The comments for the Gecko types explain the need for this abstraction.
244#[cfg(feature = "servo")]
245pub type CssStringWriter = String;
246
247/// String. The comments for the Gecko types explain the need for this abstraction.
248#[cfg(feature = "servo")]
249pub type CssString = String;
250
251/// Convenience wrapper to serialise CSS values separated by a given string.
252pub struct SequenceWriter<'a, 'b: 'a, W: 'b> {
253 inner: &'a mut CssWriter<'b, W>,
254 separator: &'static str,
255}
256
257impl<'a, 'b, W> SequenceWriter<'a, 'b, W>
258where
259 W: Write + 'b,
260{
261 /// Returns whether this writer has written any item.
262 pub fn has_written(&self) -> bool {
263 // See comment in item()
264 self.inner.prefix.is_none()
265 }
266
267 /// Create a new sequence writer.
268 #[inline]
269 pub fn new(inner: &'a mut CssWriter<'b, W>, separator: &'static str) -> Self {
270 if inner.prefix.is_none() {
271 // See comment in `item`.
272 inner.prefix = Some("");
273 }
274 Self { inner, separator }
275 }
276
277 /// Serialize the CSS Value with the specific serialization function.
278 #[inline]
279 pub fn write_item<F>(&mut self, f: F) -> fmt::Result
280 where
281 F: FnOnce(&mut CssWriter<'b, W>) -> fmt::Result,
282 {
283 // Separate non-generic functions so that this code is not repeated
284 // in every monomorphization with a different type `F` or `W`.
285 // https://github.com/servo/servo/issues/26713
286 fn before(
287 prefix: &mut Option<&'static str>,
288 separator: &'static str,
289 ) -> Option<&'static str> {
290 let old_prefix = *prefix;
291 if old_prefix.is_none() {
292 // If there is no prefix in the inner writer, a previous
293 // call to this method produced output, which means we need
294 // to write the separator next time we produce output again.
295 *prefix = Some(separator);
296 }
297 old_prefix
298 }
299 fn after(
300 old_prefix: Option<&'static str>,
301 prefix: &mut Option<&'static str>,
302 separator: &'static str,
303 ) {
304 match (old_prefix, *prefix) {
305 (_, None) => {
306 // This call produced output and cleaned up after itself.
307 },
308 (None, Some(p)) => {
309 // Some previous call to `item` produced output,
310 // but this one did not, prefix should be the same as
311 // the one we set.
312 debug_assert_eq!(separator, p);
313 // We clean up here even though it's not necessary just
314 // to be able to do all these assertion checks.
315 *prefix = None;
316 },
317 (Some(old), Some(new)) => {
318 // No previous call to `item` produced output, and this one
319 // either.
320 debug_assert_eq!(old, new);
321 },
322 }
323 }
324
325 let old_prefix = before(&mut self.inner.prefix, self.separator);
326 f(self.inner)?;
327 after(old_prefix, &mut self.inner.prefix, self.separator);
328 Ok(())
329 }
330
331 /// Serialises a CSS value, writing any separator as necessary.
332 ///
333 /// The separator is never written before any `item` produces any output,
334 /// and is written in subsequent calls only if the `item` produces some
335 /// output on its own again. This lets us handle `Option<T>` fields by
336 /// just not printing anything on `None`.
337 #[inline]
338 pub fn item<T>(&mut self, item: &T) -> fmt::Result
339 where
340 T: ToCss,
341 {
342 self.write_item(|inner| item.to_css(inner))
343 }
344
345 /// Writes a string as-is (i.e. not escaped or wrapped in quotes)
346 /// with any separator as necessary.
347 ///
348 /// See SequenceWriter::item.
349 #[inline]
350 pub fn raw_item(&mut self, item: &str) -> fmt::Result {
351 self.write_item(|inner| inner.write_str(item))
352 }
353}
354
355/// Type used as the associated type in the `OneOrMoreSeparated` trait on a
356/// type to indicate that a serialized list of elements of this type is
357/// separated by commas.
358pub struct Comma;
359
360/// Type used as the associated type in the `OneOrMoreSeparated` trait on a
361/// type to indicate that a serialized list of elements of this type is
362/// separated by spaces.
363pub struct Space;
364
365/// Type used as the associated type in the `OneOrMoreSeparated` trait on a
366/// type to indicate that a serialized list of elements of this type is
367/// separated by commas, but spaces without commas are also allowed when
368/// parsing.
369pub struct CommaWithSpace;
370
371/// A trait satisfied by the types corresponding to separators.
372pub trait Separator {
373 /// The separator string that the satisfying separator type corresponds to.
374 fn separator() -> &'static str;
375
376 /// Parses a sequence of values separated by this separator.
377 ///
378 /// The given closure is called repeatedly for each item in the sequence.
379 ///
380 /// Successful results are accumulated in a vector.
381 ///
382 /// This method returns `Err(_)` the first time a closure does or if
383 /// the separators aren't correct.
384 fn parse<'i, 't, F, T, E>(
385 parser: &mut Parser<'i, 't>,
386 parse_one: F,
387 ) -> Result<Vec<T>, ParseError<'i, E>>
388 where
389 F: for<'tt> FnMut(&mut Parser<'i, 'tt>) -> Result<T, ParseError<'i, E>>;
390}
391
392impl Separator for Comma {
393 fn separator() -> &'static str {
394 ", "
395 }
396
397 fn parse<'i, 't, F, T, E>(
398 input: &mut Parser<'i, 't>,
399 parse_one: F,
400 ) -> Result<Vec<T>, ParseError<'i, E>>
401 where
402 F: for<'tt> FnMut(&mut Parser<'i, 'tt>) -> Result<T, ParseError<'i, E>>,
403 {
404 input.parse_comma_separated(parse_one)
405 }
406}
407
408impl Separator for Space {
409 fn separator() -> &'static str {
410 " "
411 }
412
413 fn parse<'i, 't, F, T, E>(
414 input: &mut Parser<'i, 't>,
415 mut parse_one: F,
416 ) -> Result<Vec<T>, ParseError<'i, E>>
417 where
418 F: for<'tt> FnMut(&mut Parser<'i, 'tt>) -> Result<T, ParseError<'i, E>>,
419 {
420 input.skip_whitespace(); // Unnecessary for correctness, but may help try_parse() rewind less.
421 let mut results = vec![parse_one(input)?];
422 loop {
423 input.skip_whitespace(); // Unnecessary for correctness, but may help try_parse() rewind less.
424 if let Ok(item) = input.try_parse(&mut parse_one) {
425 results.push(item);
426 } else {
427 return Ok(results);
428 }
429 }
430 }
431}
432
433impl Separator for CommaWithSpace {
434 fn separator() -> &'static str {
435 ", "
436 }
437
438 fn parse<'i, 't, F, T, E>(
439 input: &mut Parser<'i, 't>,
440 mut parse_one: F,
441 ) -> Result<Vec<T>, ParseError<'i, E>>
442 where
443 F: for<'tt> FnMut(&mut Parser<'i, 'tt>) -> Result<T, ParseError<'i, E>>,
444 {
445 input.skip_whitespace(); // Unnecessary for correctness, but may help try_parse() rewind less.
446 let mut results = vec![parse_one(input)?];
447 loop {
448 input.skip_whitespace(); // Unnecessary for correctness, but may help try_parse() rewind less.
449 let comma_location = input.current_source_location();
450 let comma = input.try_parse(|i| i.expect_comma()).is_ok();
451 input.skip_whitespace(); // Unnecessary for correctness, but may help try_parse() rewind less.
452 if let Ok(item) = input.try_parse(&mut parse_one) {
453 results.push(item);
454 } else if comma {
455 return Err(comma_location.new_unexpected_token_error(Token::Comma));
456 } else {
457 break;
458 }
459 }
460 Ok(results)
461 }
462}
463
464/// Marker trait on T to automatically implement ToCss for Vec<T> when T's are
465/// separated by some delimiter `delim`.
466pub trait OneOrMoreSeparated {
467 /// Associated type indicating which separator is used.
468 type S: Separator;
469}
470
471impl OneOrMoreSeparated for UnicodeRange {
472 type S = Comma;
473}
474
475impl<T> ToCss for Vec<T>
476where
477 T: ToCss + OneOrMoreSeparated,
478{
479 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
480 where
481 W: Write,
482 {
483 let mut iter = self.iter();
484 iter.next().unwrap().to_css(dest)?;
485 for item in iter {
486 dest.write_str(<T as OneOrMoreSeparated>::S::separator())?;
487 item.to_css(dest)?;
488 }
489 Ok(())
490 }
491}
492
493impl<T> ToCss for Box<T>
494where
495 T: ?Sized + ToCss,
496{
497 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
498 where
499 W: Write,
500 {
501 (**self).to_css(dest)
502 }
503}
504
505impl<T> ToCss for Arc<T>
506where
507 T: ?Sized + ToCss,
508{
509 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
510 where
511 W: Write,
512 {
513 (**self).to_css(dest)
514 }
515}
516
517impl ToCss for Au {
518 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
519 where
520 W: Write,
521 {
522 self.to_f64_px().to_css(dest)?;
523 dest.write_str("px")
524 }
525}
526
527macro_rules! impl_to_css_for_predefined_type {
528 ($name: ty) => {
529 impl<'a> ToCss for $name {
530 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
531 where
532 W: Write,
533 {
534 ::cssparser::ToCss::to_css(self, dest)
535 }
536 }
537 };
538}
539
540impl_to_css_for_predefined_type!(f32);
541impl_to_css_for_predefined_type!(i8);
542impl_to_css_for_predefined_type!(i32);
543impl_to_css_for_predefined_type!(u8);
544impl_to_css_for_predefined_type!(u16);
545impl_to_css_for_predefined_type!(u32);
546impl_to_css_for_predefined_type!(::cssparser::Token<'a>);
547impl_to_css_for_predefined_type!(::cssparser::UnicodeRange);
548
549/// Helper types for the handling of specified values.
550pub mod specified {
551 use crate::ParsingMode;
552
553 /// Whether to allow negative lengths or not.
554 #[repr(u8)]
555 #[derive(
556 Clone, Copy, Debug, Deserialize, Eq, MallocSizeOf, PartialEq, PartialOrd, Serialize, ToShmem,
557 )]
558 pub enum AllowedNumericType {
559 /// Allow all kind of numeric values.
560 All,
561 /// Allow only non-negative numeric values.
562 NonNegative,
563 /// Allow only numeric values greater or equal to 1.0.
564 AtLeastOne,
565 /// Allow only numeric values from 0 to 1.0.
566 ZeroToOne,
567 }
568
569 impl Default for AllowedNumericType {
570 #[inline]
571 fn default() -> Self {
572 AllowedNumericType::All
573 }
574 }
575
576 impl AllowedNumericType {
577 /// Whether the value fits the rules of this numeric type.
578 #[inline]
579 pub fn is_ok(&self, parsing_mode: ParsingMode, val: f32) -> bool {
580 if parsing_mode.allows_all_numeric_values() {
581 return true;
582 }
583 match *self {
584 AllowedNumericType::All => true,
585 AllowedNumericType::NonNegative => val >= 0.0,
586 AllowedNumericType::AtLeastOne => val >= 1.0,
587 AllowedNumericType::ZeroToOne => val >= 0.0 && val <= 1.0,
588 }
589 }
590
591 /// Clamp the value following the rules of this numeric type.
592 #[inline]
593 pub fn clamp(&self, val: f32) -> f32 {
594 match *self {
595 AllowedNumericType::All => val,
596 AllowedNumericType::NonNegative => val.max(0.),
597 AllowedNumericType::AtLeastOne => val.max(1.),
598 AllowedNumericType::ZeroToOne => val.max(0.).min(1.),
599 }
600 }
601 }
602}
603
604/// A keyword value used by the Typed OM.
605///
606/// This corresponds to `CSSKeywordValue` in the Typed OM specification.
607/// The keyword is stored as a `CssString` so it can be represented and
608/// transferred independently of any specific property (e.g. `"none"`,
609/// `"block"`, `"thin"`).
610#[derive(Clone, Debug)]
611#[repr(C)]
612pub struct KeywordValue(pub CssString);
613
614/// A single numeric value with an associated unit.
615///
616/// This corresponds to `CSSUnitValue` in the Typed OM specification. The
617/// numeric component is stored separately from the textual unit identifier.
618#[derive(Clone, Debug)]
619#[repr(C)]
620pub struct UnitValue {
621 /// The numeric component of the value.
622 pub value: f32,
623
624 /// The textual unit string (e.g. `"px"`, `"em"`, `"%"`, `"deg"`).
625 pub unit: CssString,
626}
627
628/// A sum of numeric values.
629///
630/// This corresponds to `CSSMathSum` in the Typed OM specification. A sum
631/// value represents an expression such as `10px + 2em`. Each entry is itself
632/// a `NumericValue`, allowing nested sums if needed.
633#[derive(Clone, Debug)]
634#[repr(C)]
635pub struct MathSum {
636 /// The list of numeric terms that make up the sum.
637 pub values: ThinVec<NumericValue>,
638}
639
640/// A numeric value used by the Typed OM.
641///
642/// This corresponds to `CSSNumericValue` and its subclasses in the Typed OM
643/// specification. It represents numbers that can appear in CSS values,
644/// including both simple unit quantities and composite expressions.
645///
646/// Unlike the parser-level representation, `NumericValue` is property-agnostic
647/// and suitable for conversion to or from the `CSSNumericValue` family of DOM
648/// objects.
649#[derive(Clone, Debug)]
650#[repr(C)]
651pub enum NumericValue {
652 /// A single numeric value with a concrete unit.
653 ///
654 /// This corresponds to `CSSUnitValue`.
655 Unit(UnitValue),
656
657 /// A sum of numeric values.
658 ///
659 /// This corresponds to `CSSMathSum`.
660 Sum(MathSum),
661}
662
663/// A property-agnostic representation of a value, used by Typed OM.
664///
665/// `TypedValue` is the internal counterpart of the various `CSSStyleValue`
666/// subclasses defined by the Typed OM specification. It captures values that
667/// can be represented independently of any particular property.
668#[derive(Clone, Debug)]
669#[repr(C)]
670pub enum TypedValue {
671 /// A keyword value such as `"block"`, `"none"`, or `"thin"`.
672 ///
673 /// This corresponds to `CSSKeywordValue` in the Typed OM specification.
674 /// Keywords are represented as a standalone `KeywordValue` so they can
675 /// be carried and compared independently of any particular property.
676 Keyword(KeywordValue),
677
678 /// A numeric value such as a length, angle, time, or a sum thereof.
679 ///
680 /// This corresponds to the `CSSNumericValue` hierarchy in the Typed OM
681 /// specification, including `CSSUnitValue` and `CSSMathSum`.
682 Numeric(NumericValue),
683}
684
685/// A list of property-agnostic values used by the Typed OM.
686///
687/// `TypedValueList` is the internal counterpart of CSS value lists exposed by
688/// Typed OM. It stores one or more [`TypedValue`] items in source order and
689/// is used when a value reifies to multiple property-agnostic components.
690#[derive(Clone, Debug)]
691#[repr(C)]
692pub struct TypedValueList {
693 /// The list of reified values.
694 pub values: ThinVec<TypedValue>,
695}
696
697/// Reifies a value into its Typed OM representation.
698///
699/// This trait is the Typed OM analogue of [`ToCss`]. Instead of serializing
700/// values into CSS syntax, it converts them into [`TypedValue`]s that can be
701/// exposed to the DOM as `CSSStyleValue` subclasses.
702///
703/// Most consumers should use [`ToTyped::to_typed_value`] or
704/// [`ToTyped::to_typed_value_list`], depending on whether they need a single
705/// reified value or the full list of reified values.
706///
707/// This trait is derivable with `#[derive(ToTyped)]`. The derived
708/// implementation currently supports:
709///
710/// * Keyword enums: Enums whose variants are all unit variants are
711/// automatically reified as [`TypedValue::Keyword`], using the same
712/// serialization logic as [`ToCss`].
713///
714/// * Structs and data-carrying variants: When the
715/// `#[typed_value(derive_fields)]` attribute is present, the derive
716/// attempts to call `.to_typed()` recursively on supported fields or
717/// variant payloads, producing [`TypedValue`]s when possible.
718///
719/// * Other cases: If no automatic mapping is defined or recursion is not
720/// enabled, the derived implementation falls back to the default method
721/// (which returns `Err(())`, and thus `to_typed_value()` returns `None`).
722///
723/// The `derive_fields` attribute is intentionally opt-in for now to avoid
724/// forcing types that do not participate in reification to implement
725/// [`ToTyped`]. Once Typed OM coverage stabilizes, this behavior is expected
726/// to become the default (see the corresponding follow-up bug).
727///
728/// Over time, the derive may be extended to handle additional CSS value
729/// categories such as numeric, color, and transform types.
730///
731/// Summary of derive attributes recognized by `#[derive(ToTyped)]`:
732///
733/// * `#[typed_value(derive_fields)]` on the type enables limited recursion
734/// for structs and data-carrying enum variants.
735///
736/// * `#[css(skip)]`, `#[typed_value(skip)]`, or `#[typed_value(todo)]` on a
737/// variant cause that variant to be treated as unsupported (the derived
738/// implementation returns `Err(())`).
739///
740/// * `#[css(skip)]` on a field causes that field to be ignored during
741/// reification.
742///
743/// * `#[typed_value(skip_if = "...")]` on a field conditionally disables
744/// reification for that field. If the provided function returns `true` for
745/// the field value, the field is ignored.
746///
747/// * `#[css(keyword = "...")]` on a unit variant overrides the keyword that
748/// would otherwise be derived from the Rust identifier.
749///
750/// * `#[css(comma)]` on the variant indicates that supported fields may reify
751/// to multiple separate values. When this attribute is present, multiple
752/// [`TypedValue`] items may be produced. If it is not present and the
753/// derived implementation would produce more than one item, it returns
754/// `Err(())`.
755///
756/// * `#[css(iterable)]` on a field indicates that the field represents a list
757/// of values. Each item in the iterable is reified individually by calling
758/// `ToTyped::to_typed` on the element type.
759///
760/// * `#[css(if_empty = "...")]` on an iterable field specifies a keyword
761/// value that should be produced when the iterable is empty.
762pub trait ToTyped {
763 /// Attempt to convert `self` into one or more [`TypedValue`] items.
764 ///
765 /// Implementations append any resulting values to `dest`. This is the
766 /// low-level entry point used by the Typed OM reification infrastructure.
767 /// Most callers should prefer [`ToTyped::to_typed_value`] or
768 /// [`ToTyped::to_typed_value_list`].
769 ///
770 /// Returning `Err(())` indicates that the value cannot be represented as
771 /// a property-agnostic Typed OM value.
772 fn to_typed(&self, _dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
773 Err(())
774 }
775
776 /// Attempt to convert `self` into a [`TypedValue`].
777 ///
778 /// Returns the first reified value as `Some(TypedValue)` if the value can
779 /// be reified into a property-agnostic CSSStyleValue subclass. Returns
780 /// `None` if the value is unrepresentable, in which case consumers
781 /// produce a property-tied CSSStyleValue instead.
782 fn to_typed_value(&self) -> Option<TypedValue> {
783 let mut dest = ThinVec::new();
784 self.to_typed(&mut dest).ok()?;
785 dest.into_iter().next()
786 }
787
788 /// Attempt to convert `self` into a [`TypedValueList`].
789 ///
790 /// Returns `Some(TypedValueList)` if the value can be reified into one or
791 /// more property-agnostic Typed OM values. Returns `None` if the value is
792 /// unrepresentable, in which case consumers produce a property-tied
793 /// `CSSStyleValue` instead.
794 fn to_typed_value_list(&self) -> Option<TypedValueList> {
795 let mut dest = ThinVec::new();
796 self.to_typed(&mut dest).ok()?;
797 Some(TypedValueList { values: dest })
798 }
799}
800
801impl<'a, T> ToTyped for &'a T
802where
803 T: ToTyped + ?Sized,
804{
805 fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
806 (*self).to_typed(dest)
807 }
808}
809
810impl<T> ToTyped for Box<T>
811where
812 T: ?Sized + ToTyped,
813{
814 fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
815 (**self).to_typed(dest)
816 }
817}
818
819impl ToTyped for Au {
820 fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
821 let value = self.to_f32_px();
822 let unit = CssString::from("px");
823 dest.push(TypedValue::Numeric(NumericValue::Unit(UnitValue {
824 value,
825 unit,
826 })));
827 Ok(())
828 }
829}
830
831macro_rules! impl_to_typed_for_predefined_type {
832 ($name: ty) => {
833 impl<'a> ToTyped for $name {
834 fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
835 dest.push(TypedValue::Numeric(NumericValue::Unit(UnitValue {
836 value: *self as f32,
837 unit: CssString::from("number"),
838 })));
839 Ok(())
840 }
841 }
842 };
843}
844
845impl_to_typed_for_predefined_type!(f32);
846impl_to_typed_for_predefined_type!(i8);
847impl_to_typed_for_predefined_type!(i32);