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 /// Create a new sequence writer.
262 #[inline]
263 pub fn new(inner: &'a mut CssWriter<'b, W>, separator: &'static str) -> Self {
264 if inner.prefix.is_none() {
265 // See comment in `item`.
266 inner.prefix = Some("");
267 }
268 Self { inner, separator }
269 }
270
271 /// Serialize the CSS Value with the specific serialization function.
272 #[inline]
273 pub fn write_item<F>(&mut self, f: F) -> fmt::Result
274 where
275 F: FnOnce(&mut CssWriter<'b, W>) -> fmt::Result,
276 {
277 // Separate non-generic functions so that this code is not repeated
278 // in every monomorphization with a different type `F` or `W`.
279 // https://github.com/servo/servo/issues/26713
280 fn before(
281 prefix: &mut Option<&'static str>,
282 separator: &'static str,
283 ) -> Option<&'static str> {
284 let old_prefix = *prefix;
285 if old_prefix.is_none() {
286 // If there is no prefix in the inner writer, a previous
287 // call to this method produced output, which means we need
288 // to write the separator next time we produce output again.
289 *prefix = Some(separator);
290 }
291 old_prefix
292 }
293 fn after(
294 old_prefix: Option<&'static str>,
295 prefix: &mut Option<&'static str>,
296 separator: &'static str,
297 ) {
298 match (old_prefix, *prefix) {
299 (_, None) => {
300 // This call produced output and cleaned up after itself.
301 },
302 (None, Some(p)) => {
303 // Some previous call to `item` produced output,
304 // but this one did not, prefix should be the same as
305 // the one we set.
306 debug_assert_eq!(separator, p);
307 // We clean up here even though it's not necessary just
308 // to be able to do all these assertion checks.
309 *prefix = None;
310 },
311 (Some(old), Some(new)) => {
312 // No previous call to `item` produced output, and this one
313 // either.
314 debug_assert_eq!(old, new);
315 },
316 }
317 }
318
319 let old_prefix = before(&mut self.inner.prefix, self.separator);
320 f(self.inner)?;
321 after(old_prefix, &mut self.inner.prefix, self.separator);
322 Ok(())
323 }
324
325 /// Serialises a CSS value, writing any separator as necessary.
326 ///
327 /// The separator is never written before any `item` produces any output,
328 /// and is written in subsequent calls only if the `item` produces some
329 /// output on its own again. This lets us handle `Option<T>` fields by
330 /// just not printing anything on `None`.
331 #[inline]
332 pub fn item<T>(&mut self, item: &T) -> fmt::Result
333 where
334 T: ToCss,
335 {
336 self.write_item(|inner| item.to_css(inner))
337 }
338
339 /// Writes a string as-is (i.e. not escaped or wrapped in quotes)
340 /// with any separator as necessary.
341 ///
342 /// See SequenceWriter::item.
343 #[inline]
344 pub fn raw_item(&mut self, item: &str) -> fmt::Result {
345 self.write_item(|inner| inner.write_str(item))
346 }
347}
348
349/// Type used as the associated type in the `OneOrMoreSeparated` trait on a
350/// type to indicate that a serialized list of elements of this type is
351/// separated by commas.
352pub struct Comma;
353
354/// Type used as the associated type in the `OneOrMoreSeparated` trait on a
355/// type to indicate that a serialized list of elements of this type is
356/// separated by spaces.
357pub struct Space;
358
359/// Type used as the associated type in the `OneOrMoreSeparated` trait on a
360/// type to indicate that a serialized list of elements of this type is
361/// separated by commas, but spaces without commas are also allowed when
362/// parsing.
363pub struct CommaWithSpace;
364
365/// A trait satisfied by the types corresponding to separators.
366pub trait Separator {
367 /// The separator string that the satisfying separator type corresponds to.
368 fn separator() -> &'static str;
369
370 /// Parses a sequence of values separated by this separator.
371 ///
372 /// The given closure is called repeatedly for each item in the sequence.
373 ///
374 /// Successful results are accumulated in a vector.
375 ///
376 /// This method returns `Err(_)` the first time a closure does or if
377 /// the separators aren't correct.
378 fn parse<'i, 't, F, T, E>(
379 parser: &mut Parser<'i, 't>,
380 parse_one: F,
381 ) -> Result<Vec<T>, ParseError<'i, E>>
382 where
383 F: for<'tt> FnMut(&mut Parser<'i, 'tt>) -> Result<T, ParseError<'i, E>>;
384}
385
386impl Separator for Comma {
387 fn separator() -> &'static str {
388 ", "
389 }
390
391 fn parse<'i, 't, F, T, E>(
392 input: &mut Parser<'i, 't>,
393 parse_one: F,
394 ) -> Result<Vec<T>, ParseError<'i, E>>
395 where
396 F: for<'tt> FnMut(&mut Parser<'i, 'tt>) -> Result<T, ParseError<'i, E>>,
397 {
398 input.parse_comma_separated(parse_one)
399 }
400}
401
402impl Separator for Space {
403 fn separator() -> &'static str {
404 " "
405 }
406
407 fn parse<'i, 't, F, T, E>(
408 input: &mut Parser<'i, 't>,
409 mut parse_one: F,
410 ) -> Result<Vec<T>, ParseError<'i, E>>
411 where
412 F: for<'tt> FnMut(&mut Parser<'i, 'tt>) -> Result<T, ParseError<'i, E>>,
413 {
414 input.skip_whitespace(); // Unnecessary for correctness, but may help try_parse() rewind less.
415 let mut results = vec![parse_one(input)?];
416 loop {
417 input.skip_whitespace(); // Unnecessary for correctness, but may help try_parse() rewind less.
418 if let Ok(item) = input.try_parse(&mut parse_one) {
419 results.push(item);
420 } else {
421 return Ok(results);
422 }
423 }
424 }
425}
426
427impl Separator for CommaWithSpace {
428 fn separator() -> &'static str {
429 ", "
430 }
431
432 fn parse<'i, 't, F, T, E>(
433 input: &mut Parser<'i, 't>,
434 mut parse_one: F,
435 ) -> Result<Vec<T>, ParseError<'i, E>>
436 where
437 F: for<'tt> FnMut(&mut Parser<'i, 'tt>) -> Result<T, ParseError<'i, E>>,
438 {
439 input.skip_whitespace(); // Unnecessary for correctness, but may help try_parse() rewind less.
440 let mut results = vec![parse_one(input)?];
441 loop {
442 input.skip_whitespace(); // Unnecessary for correctness, but may help try_parse() rewind less.
443 let comma_location = input.current_source_location();
444 let comma = input.try_parse(|i| i.expect_comma()).is_ok();
445 input.skip_whitespace(); // Unnecessary for correctness, but may help try_parse() rewind less.
446 if let Ok(item) = input.try_parse(&mut parse_one) {
447 results.push(item);
448 } else if comma {
449 return Err(comma_location.new_unexpected_token_error(Token::Comma));
450 } else {
451 break;
452 }
453 }
454 Ok(results)
455 }
456}
457
458/// Marker trait on T to automatically implement ToCss for Vec<T> when T's are
459/// separated by some delimiter `delim`.
460pub trait OneOrMoreSeparated {
461 /// Associated type indicating which separator is used.
462 type S: Separator;
463}
464
465impl OneOrMoreSeparated for UnicodeRange {
466 type S = Comma;
467}
468
469impl<T> ToCss for Vec<T>
470where
471 T: ToCss + OneOrMoreSeparated,
472{
473 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
474 where
475 W: Write,
476 {
477 let mut iter = self.iter();
478 iter.next().unwrap().to_css(dest)?;
479 for item in iter {
480 dest.write_str(<T as OneOrMoreSeparated>::S::separator())?;
481 item.to_css(dest)?;
482 }
483 Ok(())
484 }
485}
486
487impl<T> ToCss for Box<T>
488where
489 T: ?Sized + ToCss,
490{
491 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
492 where
493 W: Write,
494 {
495 (**self).to_css(dest)
496 }
497}
498
499impl<T> ToCss for Arc<T>
500where
501 T: ?Sized + ToCss,
502{
503 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
504 where
505 W: Write,
506 {
507 (**self).to_css(dest)
508 }
509}
510
511impl ToCss for Au {
512 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
513 where
514 W: Write,
515 {
516 self.to_f64_px().to_css(dest)?;
517 dest.write_str("px")
518 }
519}
520
521macro_rules! impl_to_css_for_predefined_type {
522 ($name: ty) => {
523 impl<'a> ToCss for $name {
524 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
525 where
526 W: Write,
527 {
528 ::cssparser::ToCss::to_css(self, dest)
529 }
530 }
531 };
532}
533
534impl_to_css_for_predefined_type!(f32);
535impl_to_css_for_predefined_type!(i8);
536impl_to_css_for_predefined_type!(i32);
537impl_to_css_for_predefined_type!(u8);
538impl_to_css_for_predefined_type!(u16);
539impl_to_css_for_predefined_type!(u32);
540impl_to_css_for_predefined_type!(::cssparser::Token<'a>);
541impl_to_css_for_predefined_type!(::cssparser::UnicodeRange);
542
543/// Helper types for the handling of specified values.
544pub mod specified {
545 use crate::ParsingMode;
546
547 /// Whether to allow negative lengths or not.
548 #[repr(u8)]
549 #[derive(
550 Clone, Copy, Debug, Deserialize, Eq, MallocSizeOf, PartialEq, PartialOrd, Serialize, ToShmem,
551 )]
552 pub enum AllowedNumericType {
553 /// Allow all kind of numeric values.
554 All,
555 /// Allow only non-negative numeric values.
556 NonNegative,
557 /// Allow only numeric values greater or equal to 1.0.
558 AtLeastOne,
559 /// Allow only numeric values from 0 to 1.0.
560 ZeroToOne,
561 }
562
563 impl Default for AllowedNumericType {
564 #[inline]
565 fn default() -> Self {
566 AllowedNumericType::All
567 }
568 }
569
570 impl AllowedNumericType {
571 /// Whether the value fits the rules of this numeric type.
572 #[inline]
573 pub fn is_ok(&self, parsing_mode: ParsingMode, val: f32) -> bool {
574 if parsing_mode.allows_all_numeric_values() {
575 return true;
576 }
577 match *self {
578 AllowedNumericType::All => true,
579 AllowedNumericType::NonNegative => val >= 0.0,
580 AllowedNumericType::AtLeastOne => val >= 1.0,
581 AllowedNumericType::ZeroToOne => val >= 0.0 && val <= 1.0,
582 }
583 }
584
585 /// Clamp the value following the rules of this numeric type.
586 #[inline]
587 pub fn clamp(&self, val: f32) -> f32 {
588 match *self {
589 AllowedNumericType::All => val,
590 AllowedNumericType::NonNegative => val.max(0.),
591 AllowedNumericType::AtLeastOne => val.max(1.),
592 AllowedNumericType::ZeroToOne => val.max(0.).min(1.),
593 }
594 }
595 }
596}
597
598/// A numeric value used by the Typed OM.
599///
600/// This corresponds to `CSSNumericValue` and its subclasses in the Typed OM
601/// specification. It represents numbers that can appear in CSS values,
602/// including both simple unit quantities and sums of numeric terms.
603///
604/// Unlike the parser-level representation, `NumericValue` is property-agnostic
605/// and suitable for conversion to or from the `CSSNumericValue` family of DOM
606/// objects.
607#[derive(Clone, Debug)]
608#[repr(C)]
609pub enum NumericValue {
610 /// A single numeric value with a concrete unit.
611 ///
612 /// This corresponds to `CSSUnitValue`. The `value` field stores the raw
613 /// numeric component, and the `unit` field stores the textual unit
614 /// identifier (e.g. `"px"`, `"em"`, `"%"`, `"deg"`).
615 Unit {
616 /// The numeric component of the value.
617 value: f32,
618 /// The textual unit string (e.g. `"px"`, `"em"`, `"deg"`).
619 unit: CssString,
620 },
621
622 /// A sum of multiple numeric values.
623 ///
624 /// This corresponds to `CSSMathSum`, representing an expression such as
625 /// `10px + 2em`. Each entry in `values` is another `NumericValue`, which
626 /// may itself be a unit value or a nested sum.
627 Sum {
628 /// The list of numeric terms that make up the sum.
629 values: ThinVec<NumericValue>,
630 },
631}
632
633/// A property-agnostic representation of a value, used by Typed OM.
634///
635/// `TypedValue` is the internal counterpart of the various `CSSStyleValue`
636/// subclasses defined by the Typed OM specification. It captures values that
637/// can be represented independently of any particular property.
638#[derive(Clone, Debug)]
639#[repr(C)]
640pub enum TypedValue {
641 /// A keyword value (e.g. `"block"`, `"none"`, `"thin"`).
642 ///
643 /// Keywords are stored as a `CssString` so they can be represented and
644 /// transferred independently of any specific property. This corresponds
645 /// to `CSSKeywordValue` in the Typed OM specification.
646 Keyword(CssString),
647
648 /// A numeric value such as a length, angle, time, or a sum thereof.
649 ///
650 /// This corresponds to the `CSSNumericValue` hierarchy in the Typed OM
651 /// specification, including `CSSUnitValue` and `CSSMathSum`.
652 Numeric(NumericValue),
653}
654
655/// Reifies a value into its Typed OM representation.
656///
657/// This trait is the Typed OM analogue of [`ToCss`]. Instead of serializing
658/// values into CSS syntax, it converts them into [`TypedValue`]s that can be
659/// exposed to the DOM as `CSSStyleValue` subclasses.
660///
661/// This trait is derivable with `#[derive(ToTyped)]`. The derived
662/// implementation currently supports:
663///
664/// * Keyword enums: Enums whose variants are all unit variants are
665/// automatically reified as [`TypedValue::Keyword`], using the same
666/// serialization logic as [`ToCss`].
667///
668/// * Structs and data-carrying variants: When the
669/// `#[typed_value(derive_fields)]` attribute is present, the derive attempts
670/// to call `.to_typed()` recursively on inner fields or variant payloads,
671/// producing a nested [`TypedValue`] representation when possible.
672///
673/// * Other cases: If no automatic mapping is defined or recursion is not
674/// enabled, the derived implementation falls back to the default method,
675/// returning `None`.
676///
677/// The `derive_fields` attribute is intentionally opt-in for now to avoid
678/// forcing types that do not participate in reification to implement
679/// [`ToTyped`]. Once Typed OM coverage stabilizes, this behavior is expected
680/// to become the default (see the corresponding follow-up bug).
681///
682/// Over time, the derive may be extended to handle additional CSS value
683/// categories such as numeric, color, and transform types.
684pub trait ToTyped {
685 /// Attempt to convert `self` into a [`TypedValue`].
686 ///
687 /// Returns `Some(TypedValue)` if the value can be reified into a
688 /// property-agnostic CSSStyleValue subclass. Returns `None` if the value
689 /// is unrepresentable, in which case reification produces a property-tied
690 /// CSSStyleValue instead.
691 fn to_typed(&self) -> Option<TypedValue> {
692 None
693 }
694}
695
696impl<T> ToTyped for Box<T>
697where
698 T: ?Sized + ToTyped,
699{
700 fn to_typed(&self) -> Option<TypedValue> {
701 (**self).to_typed()
702 }
703}
704
705impl ToTyped for Au {
706 fn to_typed(&self) -> Option<TypedValue> {
707 let value = self.to_f32_px();
708 let unit = CssString::from("px");
709 Some(TypedValue::Numeric(NumericValue::Unit { value, unit }))
710 }
711}
712
713macro_rules! impl_to_typed_for_predefined_type {
714 ($name: ty) => {
715 impl<'a> ToTyped for $name {
716 fn to_typed(&self) -> Option<TypedValue> {
717 // XXX Should return TypedValue::Numeric with unit "number"
718 // once that variant is available. Tracked in bug 1990419.
719 None
720 }
721 }
722 };
723}
724
725impl_to_typed_for_predefined_type!(f32);
726impl_to_typed_for_predefined_type!(i8);
727impl_to_typed_for_predefined_type!(i32);