Skip to main content

style/typed_om/
mod.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//! Typed OM.
6//!
7//! https://drafts.css-houdini.org/css-typed-om-1/
8
9use crate::derives::*;
10use crate::values::computed::url::ComputedUrl;
11use crate::values::generics::transform::GenericMatrix3D;
12use crate::values::specified::url::SpecifiedUrl;
13use crate::values::CSSFloat;
14use crate::{One, Zero};
15use app_units::Au;
16use servo_arc::Arc;
17use style_traits::CssString;
18use thin_vec::ThinVec;
19
20pub mod numeric;
21pub mod numeric_declaration;
22pub mod numeric_type;
23pub mod sum_value;
24
25pub use numeric_type::NumericType;
26
27/// A single segment of an unparsed Typed OM value.
28///
29/// This corresponds to the `CSSUnparsedSegment` union in the Typed OM
30/// specification. Unparsed values are represented as a list of string
31/// fragments and variable references.
32#[derive(Clone, Debug)]
33#[repr(C)]
34pub enum UnparsedSegment {
35    /// A string fragment.
36    ///
37    /// This corresponds to the string branch of `CSSUnparsedSegment` and is
38    /// used for the non-variable parts of a `CSSUnparsedValue`.
39    String(CssString),
40
41    /// A `var()` reference segment.
42    ///
43    /// This corresponds to `CSSVariableReferenceValue` in the Typed OM
44    /// specification.
45    VariableReference(VariableReferenceValue),
46}
47
48/// An unparsed value used by the Typed OM.
49///
50/// This corresponds to `CSSUnparsedValue` in the Typed OM specification. It
51/// is used for values that cannot be reified into a more specific
52/// property-agnostic representation and therefore need to preserve their
53/// token-like structure as a sequence of string fragments and variable
54/// references.
55///
56/// The underlying list of segments corresponds to the `[[tokens]]` internal
57/// slot of `CSSUnparsedValue`.
58///
59/// This is represented as a type alias over `ThinVec<UnparsedSegment>` rather
60/// than a dedicated struct. This avoids the need for additional wrapper types
61/// when embedding unparsed values within other structures, while still
62/// allowing recursive representations via the segment list.
63pub type UnparsedValue = ThinVec<UnparsedSegment>;
64
65/// A variable reference inside an unparsed Typed OM value.
66///
67/// This corresponds to `CSSVariableReferenceValue` in the Typed OM
68/// specification.
69#[derive(Clone, Debug)]
70#[repr(C)]
71pub struct VariableReferenceValue {
72    /// The referenced custom property name.
73    ///
74    /// This corresponds to the `variable` attribute of
75    /// `CSSVariableReferenceValue`.
76    pub variable: CssString,
77
78    /// The fallback value, if present.
79    ///
80    /// This corresponds to the `fallback` attribute of
81    /// `CSSVariableReferenceValue`. When `has_fallback` is false, this value
82    /// must be ignored. When `has_fallback` is true, this contains the
83    /// fallback tokens (which may be empty).
84    pub fallback: UnparsedValue,
85
86    /// Whether a fallback was explicitly provided.
87    ///
88    /// This is needed to distinguish between the absence of a fallback
89    /// (`var(--a)`) and an explicitly empty fallback (`var(--a,)`), which are
90    /// observable via Typed OM.
91    pub has_fallback: bool,
92}
93
94/// A keyword value used by the Typed OM.
95///
96/// This corresponds to `CSSKeywordValue` in the Typed OM specification.
97/// The keyword is stored as a `CssString` so it can be represented and
98/// transferred independently of any specific property (e.g. `"none"`,
99/// `"block"`, `"thin"`).
100#[derive(Clone, Debug)]
101#[repr(C)]
102pub struct KeywordValue(pub CssString);
103
104/// A single numeric value with an associated unit.
105///
106/// This corresponds to `CSSUnitValue` in the Typed OM specification. The
107/// numeric component is stored separately from the textual unit identifier.
108#[derive(Clone, Debug)]
109#[repr(C)]
110pub struct UnitValue {
111    /// The numeric type associated with this value.
112    pub numeric_type: NumericType,
113
114    /// The numeric component of the value.
115    pub value: f32,
116
117    /// The textual unit string (e.g. `"px"`, `"em"`, `"%"`, `"deg"`).
118    pub unit: CssString,
119}
120
121impl UnitValue {
122    /// Returns the unit as a string slice.
123    #[inline]
124    pub fn unit_str(&self) -> &str {
125        #[cfg(feature = "gecko")]
126        unsafe {
127            self.unit.as_str_unchecked()
128        }
129
130        #[cfg(feature = "servo")]
131        {
132            &self.unit
133        }
134    }
135}
136
137/// A sum of numeric values.
138///
139/// This corresponds to `CSSMathSum` in the Typed OM specification. A sum
140/// value represents an expression such as `10px + 2em`. Each entry is itself
141/// a `NumericValue`, allowing nested sums if needed.
142#[derive(Clone, Debug)]
143#[repr(C)]
144pub struct MathSum {
145    /// The numeric type associated with this sum.
146    pub numeric_type: NumericType,
147
148    /// The list of numeric terms that make up the sum.
149    pub values: ThinVec<NumericValue>,
150}
151
152impl MathSum {
153    /// Creates a math sum from a sequence of numeric values.
154    ///
155    /// Returns an error if the values do not have addable numeric types.
156    pub fn try_from_numeric_values(values: ThinVec<NumericValue>) -> Result<Self, ()> {
157        // Temporarily ignore NumericValue variants that don't expose a
158        // numeric type. This filter can be removed once numeric type support
159        // is implemented for all NumericValue variants.
160        let numeric_type = NumericType::add_types(values.iter().filter_map(|v| v.numeric_type()))?;
161
162        Ok(Self {
163            numeric_type,
164            values,
165        })
166    }
167
168    /// Creates a math sum from a previously validated sequence of numeric
169    /// values.
170    pub fn from_numeric_values_unchecked(values: ThinVec<NumericValue>) -> Self {
171        let result = Self::try_from_numeric_values(values);
172        debug_assert!(result.is_ok(), "Expected addable values");
173
174        result.unwrap_or_else(|_| Self {
175            numeric_type: NumericType::number(),
176            values: ThinVec::from([NumericValue::zero()]),
177        })
178    }
179}
180
181/// A product of numeric values.
182///
183/// This corresponds to `CSSMathProduct` in the Typed OM specification. A
184/// product value represents an expression such as `10px * 2`. Each entry is
185/// itself a `NumericValue`, allowing nested math expressions if needed.
186pub type MathProduct = ThinVec<NumericValue>;
187
188/// A negated numeric value.
189///
190/// This corresponds to `CSSMathNegate` in the Typed OM specification. A negate
191/// expression represents constructs such as `-10px` or `-(10px + 2em)`.
192pub type MathNegate = Box<NumericValue>;
193
194/// An inverted numeric value.
195///
196/// This corresponds to `CSSMathInvert` in the Typed OM specification. An
197/// invert expression represents constructs such as `1 / 2`, `1 / 10px`, or
198/// more generally the reciprocal of another numeric value.
199pub type MathInvert = Box<NumericValue>;
200
201/// A minimum expression over numeric values.
202///
203/// This corresponds to `CSSMathMin` in the Typed OM specification. A minimum
204/// expression represents constructs such as `min(10px, 20%)`. Each entry is
205/// itself a `NumericValue`, allowing nested math expressions if needed.
206pub type MathMin = ThinVec<NumericValue>;
207
208/// A maximum expression over numeric values.
209///
210/// This corresponds to `CSSMathMax` in the Typed OM specification. A maximum
211/// expression represents constructs such as `max(10px, 20%)`. Each entry is
212/// itself a `NumericValue`, allowing nested math expressions if needed.
213pub type MathMax = ThinVec<NumericValue>;
214
215/// A clamp expression over numeric values.
216///
217/// This corresponds to `CSSMathClamp` in the Typed OM specification. A clamp
218/// expression represents constructs such as `clamp(10px, 20%, 30px)`.
219///
220/// The array entries correspond to the lower bound, value, and upper bound,
221/// respectively.
222pub type MathClamp = crate::OwnedArray<NumericValue, 3>;
223
224/// A math expression used by the Typed OM.
225///
226/// This corresponds to `CSSMathValue` and its subclasses in the Typed OM
227/// specification.
228#[derive(Clone, Debug)]
229#[repr(C)]
230pub enum MathValue {
231    /// A sum of numeric values.
232    ///
233    /// This corresponds to `CSSMathSum`.
234    Sum(MathSum),
235
236    /// A product of numeric values.
237    ///
238    /// This corresponds to `CSSMathProduct`.
239    Product(MathProduct),
240
241    /// A negated numeric value.
242    ///
243    /// This corresponds to `CSSMathNegate`.
244    Negate(MathNegate),
245
246    /// An inverted numeric value.
247    ///
248    /// This corresponds to `CSSMathInvert`.
249    Invert(MathInvert),
250
251    /// A minimum expression over numeric values.
252    ///
253    /// This corresponds to `CSSMathMin`.
254    Min(MathMin),
255
256    /// A maximum expression over numeric values.
257    ///
258    /// This corresponds to `CSSMathMax`.
259    Max(MathMax),
260
261    /// A clamp expression over numeric values.
262    ///
263    /// This corresponds to `CSSMathClamp`.
264    Clamp(MathClamp),
265}
266
267impl MathValue {
268    /// Returns the numeric type associated with this math value, if
269    /// available.
270    pub fn numeric_type(&self) -> Option<&NumericType> {
271        match self {
272            Self::Sum(math_sum) => Some(&math_sum.numeric_type),
273            _ => None,
274        }
275    }
276}
277
278/// A numeric value used by the Typed OM.
279///
280/// This corresponds to `CSSNumericValue` and its subclasses in the Typed OM
281/// specification. It represents numbers that can appear in CSS values,
282/// including both simple unit quantities and composite expressions.
283///
284/// Unlike the parser-level representation, `NumericValue` is property-agnostic
285/// and suitable for conversion to or from the `CSSNumericValue` family of DOM
286/// objects.
287#[derive(Clone, Debug)]
288#[repr(C)]
289pub enum NumericValue {
290    /// A single numeric value with a concrete unit.
291    ///
292    /// This corresponds to `CSSUnitValue`.
293    Unit(UnitValue),
294
295    /// A math expression.
296    ///
297    /// This corresponds to `CSSMathValue` and its subclasses.
298    Math(MathValue),
299}
300
301impl NumericValue {
302    /// Returns a zero pixel unit value.
303    #[inline]
304    pub fn zero_px() -> Self {
305        Self::Unit(UnitValue {
306            numeric_type: NumericType::length(),
307            value: 0.0,
308            unit: CssString::from("px"),
309        })
310    }
311
312    /// Returns the numeric type associated with this numeric value, if
313    /// available.
314    pub fn numeric_type(&self) -> Option<&NumericType> {
315        match self {
316            Self::Unit(unit_value) => Some(&unit_value.numeric_type),
317            Self::Math(math_value) => math_value.numeric_type(),
318        }
319    }
320}
321
322impl Zero for NumericValue {
323    #[inline]
324    fn zero() -> Self {
325        Self::Unit(UnitValue {
326            numeric_type: NumericType::number(),
327            value: 0.0,
328            unit: CssString::from("number"),
329        })
330    }
331
332    #[inline]
333    fn is_zero(&self) -> bool {
334        match *self {
335            Self::Unit(ref value) => value.value == 0.0,
336            _ => false,
337        }
338    }
339}
340
341impl One for NumericValue {
342    #[inline]
343    fn one() -> Self {
344        Self::Unit(UnitValue {
345            numeric_type: NumericType::number(),
346            value: 1.0,
347            unit: CssString::from("number"),
348        })
349    }
350
351    #[inline]
352    fn is_one(&self) -> bool {
353        match *self {
354            Self::Unit(ref value) => value.value == 1.0,
355            _ => false,
356        }
357    }
358}
359
360/// A translate transform component used by the Typed OM.
361///
362/// This corresponds to `CSSTranslate` in the Typed OM specification. The `x`,
363/// `y`, and `z` components are always present; omitted offsets are represented
364/// as `0px`.
365///
366/// The `is_2d` flag indicates whether the component was reified from a 2D
367/// translate function.
368#[derive(Clone, Debug)]
369#[repr(C)]
370pub struct TranslateComponent {
371    /// The x-axis translation component.
372    pub x: NumericValue,
373
374    /// The y-axis translation component.
375    pub y: NumericValue,
376
377    /// The z-axis translation component.
378    pub z: NumericValue,
379
380    /// Whether this translate component is two-dimensional.
381    pub is_2d: bool,
382}
383
384/// A rotate transform component used by the Typed OM.
385///
386/// This corresponds to `CSSRotate` in the Typed OM specification. The `angle`,
387/// `x`, `y`, and `z` components are always present; omitted axis coordinates
388/// are represented using the implicit axis for the corresponding rotate
389/// function.
390///
391/// The `is_2d` flag indicates whether the component was reified from a 2D
392/// rotate function.
393#[derive(Clone, Debug)]
394#[repr(C)]
395pub struct RotateComponent {
396    /// The rotation angle.
397    pub angle: NumericValue,
398
399    /// The x-axis rotation coordinate.
400    pub x: NumericValue,
401
402    /// The y-axis rotation coordinate.
403    pub y: NumericValue,
404
405    /// The z-axis rotation coordinate.
406    pub z: NumericValue,
407
408    /// Whether this rotate component is two-dimensional.
409    pub is_2d: bool,
410}
411
412/// A scale transform component used by the Typed OM.
413///
414/// This corresponds to `CSSScale` in the Typed OM specification. The `x`, `y`,
415/// and `z` components are always present; omitted scale factors are
416/// represented as `1`.
417///
418/// The `is_2d` flag indicates whether the component was reified from a 2D
419/// scale function.
420#[derive(Clone, Debug)]
421#[repr(C)]
422pub struct ScaleComponent {
423    /// The x-axis scale factor.
424    pub x: NumericValue,
425
426    /// The y-axis scale factor.
427    pub y: NumericValue,
428
429    /// The z-axis scale factor.
430    pub z: NumericValue,
431
432    /// Whether this scale component is two-dimensional.
433    pub is_2d: bool,
434}
435
436/// A skew transform component used by the Typed OM.
437///
438/// This corresponds to `CSSSkew` in the Typed OM specification. The `ax` and
439/// `ay` components are always present; omitted angles are represented as
440/// `0deg`.
441///
442/// Skew components are always two-dimensional.
443#[derive(Clone, Debug)]
444#[repr(C)]
445pub struct SkewComponent {
446    /// The x-axis skew angle.
447    pub ax: NumericValue,
448
449    /// The y-axis skew angle.
450    pub ay: NumericValue,
451}
452
453/// A skewX transform component used by the Typed OM.
454///
455/// This corresponds to `CSSSkewX` in the Typed OM specification. The value is
456/// always present; omitted angles are represented as `0deg`.
457///
458/// SkewX components are always two-dimensional.
459pub type SkewXComponent = NumericValue;
460
461/// A skewY transform component used by the Typed OM.
462///
463/// This corresponds to `CSSSkewY` in the Typed OM specification. The value is
464/// always present; omitted angles are represented as `0deg`.
465///
466/// SkewY components are always two-dimensional.
467pub type SkewYComponent = NumericValue;
468
469/// A perspective value used by a perspective component.
470///
471/// This corresponds to the `CSSPerspectiveValue` union in the Typed OM
472/// specification.
473#[derive(Clone, Debug)]
474#[repr(C)]
475pub enum PerspectiveValue {
476    /// A numeric perspective value.
477    ///
478    /// This corresponds to `CSSNumericValue`.
479    Numeric(NumericValue),
480
481    /// A keyword perspective value.
482    ///
483    /// This corresponds to `CSSKeywordValue`.
484    Keyword(KeywordValue),
485}
486
487/// A perspective transform component used by the Typed OM.
488///
489/// This corresponds to `CSSPerspective` in the Typed OM specification. The
490/// `length` component is always present.
491///
492/// Perspective components are always three-dimensional.
493#[derive(Clone, Debug)]
494#[repr(C)]
495pub struct PerspectiveComponent {
496    /// The perspective length.
497    pub length: PerspectiveValue,
498}
499
500/// A matrix transform component used by the Typed OM.
501///
502/// This corresponds to `CSSMatrixComponent` in the Typed OM specification.
503///
504/// The `matrix` field always stores a full 4×4 matrix. Two-dimensional
505/// matrices are expanded to their equivalent 3D representation during
506/// reification.
507///
508/// The `is_2d` flag indicates whether the component was reified from a 2D
509/// matrix function.
510#[derive(Clone, Debug)]
511#[repr(C)]
512pub struct MatrixComponent {
513    /// The 4×4 matrix.
514    pub matrix: GenericMatrix3D<CSSFloat>,
515
516    /// Whether this matrix component is two-dimensional.
517    pub is_2d: bool,
518}
519
520/// A single transform component used by the Typed OM.
521///
522/// This corresponds to `CSSTransformComponent` in the Typed OM specification.
523/// Each variant represents one concrete transform component subclass.
524#[derive(Clone, Debug)]
525#[repr(C)]
526pub enum TransformComponent {
527    /// A translate transform component.
528    ///
529    /// This corresponds to `CSSTranslate`.
530    Translate(TranslateComponent),
531
532    /// A rotate transform component.
533    ///
534    /// This corresponds to `CSSRotate`.
535    Rotate(RotateComponent),
536
537    /// A scale transform component.
538    ///
539    /// This corresponds to `CSSScale`.
540    Scale(ScaleComponent),
541
542    /// A skew transform component.
543    ///
544    /// This corresponds to `CSSSkew`.
545    Skew(SkewComponent),
546
547    /// A skewX transform component.
548    ///
549    /// This corresponds to `CSSSkewX`.
550    SkewX(SkewXComponent),
551
552    /// A skewY transform component.
553    ///
554    /// This corresponds to `CSSSkewY`.
555    SkewY(SkewYComponent),
556
557    /// A perspective transform component.
558    ///
559    /// This corresponds to `CSSPerspective`.
560    Perspective(PerspectiveComponent),
561
562    /// A matrix transform component.
563    ///
564    /// This corresponds to `CSSMatrixComponent`.
565    Matrix(MatrixComponent),
566}
567
568/// A transform value used by the Typed OM.
569///
570/// This corresponds to `CSSTransformValue` in the Typed OM specification. It
571/// represents a `<transform-list>` as an ordered list of transform components.
572pub type TransformValue = ThinVec<TransformComponent>;
573
574/// An image value used by the Typed OM.
575///
576/// This corresponds to `CSSImageValue` in the Typed OM specification.
577///
578/// `CSSImageValue` objects represent values for properties that take
579/// `<image>` values.
580#[derive(Clone, Debug, ToCss)]
581#[repr(C)]
582pub enum ImageValue {
583    /// A specified image URL value.
584    ///
585    /// Relative URLs are preserved and continue to resolve against the
586    /// originating stylesheet or document when later used.
587    Specified(SpecifiedUrl),
588
589    /// A computed image URL value.
590    ///
591    /// Computed URLs are already resolved according to normal CSS computed
592    /// value processing.
593    Computed(ComputedUrl),
594}
595
596/// A property-agnostic representation of a value, used by Typed OM.
597///
598/// `TypedValue` is the internal counterpart of the various `CSSStyleValue`
599/// subclasses defined by the Typed OM specification. It captures values that
600/// can be represented independently of any particular property.
601#[derive(Clone, Debug)]
602#[repr(C)]
603pub enum TypedValue {
604    /// An unparsed value consisting of string fragments and variable
605    /// references.
606    ///
607    /// This corresponds to `CSSUnparsedValue` in the Typed OM specification.
608    Unparsed(UnparsedValue),
609
610    /// A keyword value such as `"block"`, `"none"`, or `"thin"`.
611    ///
612    /// This corresponds to `CSSKeywordValue` in the Typed OM specification.
613    /// Keywords are represented as a standalone `KeywordValue` so they can
614    /// be carried and compared independently of any particular property.
615    Keyword(KeywordValue),
616
617    /// A numeric value such as a length, angle, time, or a sum thereof.
618    ///
619    /// This corresponds to the `CSSNumericValue` hierarchy in the Typed OM
620    /// specification, including `CSSUnitValue` and `CSSMathSum`.
621    Numeric(NumericValue),
622
623    /// A transform value such as `translate(10px, 20px)`.
624    ///
625    /// This corresponds to `CSSTransformValue` in the Typed OM specification.
626    Transform(TransformValue),
627
628    /// An image value.
629    ///
630    /// This corresponds to `CSSImageValue` in the Typed OM specification.
631    Image(ImageValue),
632}
633
634/// A list of property-agnostic values used by the Typed OM.
635///
636/// `TypedValueList` is the internal counterpart of CSS value lists exposed by
637/// Typed OM. It stores one or more [`TypedValue`] items in source order and
638/// is used when a value reifies to multiple property-agnostic components.
639#[derive(Clone, Debug)]
640#[repr(C)]
641pub struct TypedValueList {
642    /// The list of reified values.
643    pub values: ThinVec<TypedValue>,
644}
645
646/// Reifies a value into its Typed OM representation.
647///
648/// This trait is the Typed OM analogue of [`ToCss`]. Instead of serializing
649/// values into CSS syntax, it converts them into [`TypedValue`]s that can be
650/// exposed to the DOM as `CSSStyleValue` subclasses.
651///
652/// Most consumers should use [`ToTyped::to_typed_value`] or
653/// [`ToTyped::to_typed_value_list`], depending on whether they need a single
654/// reified value or the full list of reified values.
655///
656/// This trait is derivable with `#[derive(ToTyped)]`. The derived
657/// implementation currently supports:
658///
659/// * Keyword enums: Enums whose variants are all unit variants are
660///   automatically reified as [`TypedValue::Keyword`], using the same
661///   serialization logic as [`ToCss`].
662///
663/// * Bitflags structs: Structs annotated with
664///   `#[css(bitflags(single = "...", mixed = "...", overlapping_bits))]`
665///   are automatically reified as [`TypedValue::Keyword`] values when they
666///   can be represented as a single CSS keyword. Values that would serialize
667///   to multiple CSS keywords are treated as unsupported.
668///
669/// * Structs and data-carrying variants: Unless treated specially (such as
670///   bitflags structs), the derive attempts to call `.to_typed()` recursively
671///   on supported fields or variant payloads, producing [`TypedValue`]s when
672///   possible.
673///
674/// * Other cases: If no automatic mapping is defined, or recursion is
675///   explicitly disabled, the derived implementation falls back to the
676///   default method (which returns `Err(())`, and thus `to_typed_value()`
677///   returns `None`).
678///
679/// Over time, the derive may be extended to handle additional CSS value
680/// categories such as numeric, color, and transform types.
681///
682/// Summary of derive attributes recognized by `#[derive(ToTyped)]`:
683///
684/// * `#[css(bitflags(single = "...", mixed = "...", overlapping_bits))]` on a
685///   struct generates keyword reification for CSS bitflags types. Values that
686///   can be represented as a single CSS keyword are reified as
687///   [`TypedValue::Keyword`]; values that would serialize to multiple CSS
688///   keywords are treated as unsupported and return `Err(())`.
689///
690///   `overlapping_bits` is supported for bitflags where one keyword subsumes
691///   other internal bits, such as `contain: size`.
692///
693/// * `#[typed(skip_derive_fields)]` on the type disables recursion for
694///   structs and data-carrying enum variants.
695///
696/// * `#[css(skip)]`, `#[typed(skip)]`, or `#[typed(todo)]` on a variant cause
697///   that variant to be treated as unsupported (the derived implementation
698///   returns `Err(())`).
699///
700/// * `#[css(skip)]` on a field causes that field to be ignored during
701///   reification.
702///
703/// * `#[css(skip_if = "...")]` / `#[typed(skip_if = "...")]` on a field
704///   conditionally disables reification for that field. If the provided
705///   function returns `true` for the field value, the field is ignored.
706///
707/// * `#[css(contextual_skip_if = "...")]` /
708///   `#[typed(contextual_skip_if = "...")]` on a field conditionally disables
709///   reification for that field. The provided function is called with all
710///   fields in the current struct or variant. If it returns `true`, the field
711///   is ignored.
712///
713///   Typed skip annotations override CSS skip annotations when both are
714///   present.
715///
716/// * `#[css(keyword = "...")]` on a unit variant overrides the keyword that
717///   would otherwise be derived from the Rust identifier.
718///
719/// * `#[css(comma)]` on the variant indicates that supported fields may reify
720///   to multiple separate values. When this attribute is present, multiple
721///   [`TypedValue`] items may be produced, unless
722///   `#[typed(no_multiple_values)]` is also present. If multiple values are
723///   not allowed and the derived implementation would produce more than one
724///   item, it returns `Err(())`.
725///
726/// * `#[typed(no_multiple_values)]` on a variant prevents it from reifying to
727///   multiple [`TypedValue`] items, even if `#[css(comma)]` is present.
728///
729/// * `#[css(iterable)]` on a field indicates that the field represents a list
730///   of values. Each item in the iterable is reified individually by calling
731///   `ToTyped::to_typed` on the element type.
732///
733/// * `#[css(if_empty = "...")]` on an iterable field specifies a keyword
734///   value that should be produced when the iterable is empty.
735///
736/// * `#[css(represents_keyword)]` on a bool field causes the field name to be
737///   reified as a keyword when the field is true.
738pub trait ToTyped {
739    /// Attempt to convert `self` into one or more [`TypedValue`] items.
740    ///
741    /// Implementations append any resulting values to `dest`. This is the
742    /// low-level entry point used by the Typed OM reification infrastructure.
743    /// Most callers should prefer [`ToTyped::to_typed_value`] or
744    /// [`ToTyped::to_typed_value_list`].
745    ///
746    /// Returning `Err(())` indicates that the value cannot be represented as
747    /// a property-agnostic Typed OM value.
748    fn to_typed(&self, _dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
749        Err(())
750    }
751
752    /// Attempt to convert `self` into a [`TypedValue`].
753    ///
754    /// Returns the first reified value as `Some(TypedValue)` if the value can
755    /// be reified into a property-agnostic CSSStyleValue subclass. Returns
756    /// `None` if the value is unrepresentable, in which case consumers
757    /// produce a property-tied CSSStyleValue instead.
758    fn to_typed_value(&self) -> Option<TypedValue> {
759        let mut dest = ThinVec::new();
760        self.to_typed(&mut dest).ok()?;
761        dest.into_iter().next()
762    }
763
764    /// Attempt to convert `self` into a [`NumericValue`].
765    ///
766    /// Returns `Some(NumericValue)` if the value reifies to a single
767    /// `TypedValue::Numeric` item. Returns `None` otherwise.
768    fn to_numeric_value(&self) -> Option<NumericValue> {
769        match self.to_typed_value()? {
770            TypedValue::Numeric(value) => Some(value),
771            _ => None,
772        }
773    }
774
775    /// Attempt to convert `self` into a [`TypedValueList`].
776    ///
777    /// Returns `Some(TypedValueList)` if the value can be reified into one or
778    /// more property-agnostic Typed OM values. Returns `None` if the value is
779    /// unrepresentable, in which case consumers produce a property-tied
780    /// `CSSStyleValue` instead.
781    fn to_typed_value_list(&self) -> Option<TypedValueList> {
782        let mut dest = ThinVec::new();
783        self.to_typed(&mut dest).ok()?;
784        Some(TypedValueList { values: dest })
785    }
786}
787
788impl<'a, T> ToTyped for &'a T
789where
790    T: ToTyped + ?Sized,
791{
792    fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
793        (*self).to_typed(dest)
794    }
795}
796
797impl<T> ToTyped for Box<T>
798where
799    T: ?Sized + ToTyped,
800{
801    fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
802        (**self).to_typed(dest)
803    }
804}
805
806impl<T> ToTyped for Arc<T>
807where
808    T: ?Sized + ToTyped,
809{
810    fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
811        (**self).to_typed(dest)
812    }
813}
814
815impl ToTyped for Au {
816    fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
817        let numeric_type = NumericType::length();
818        let value = self.to_f32_px();
819        let unit = CssString::from("px");
820        dest.push(TypedValue::Numeric(NumericValue::Unit(UnitValue {
821            numeric_type,
822            value,
823            unit,
824        })));
825        Ok(())
826    }
827}
828
829macro_rules! impl_to_typed_for_predefined_type {
830    ($name: ty) => {
831        impl<'a> ToTyped for $name {
832            fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
833                dest.push(TypedValue::Numeric(NumericValue::Unit(UnitValue {
834                    numeric_type: NumericType::number(),
835                    value: *self as f32,
836                    unit: CssString::from("number"),
837                })));
838                Ok(())
839            }
840        }
841    };
842}
843
844impl_to_typed_for_predefined_type!(f32);
845impl_to_typed_for_predefined_type!(i8);
846impl_to_typed_for_predefined_type!(i32);