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::{NumericBaseType, 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        let numeric_type = NumericType::add_types(values.iter().map(|v| v.numeric_type()))?;
158
159        Ok(Self {
160            numeric_type,
161            values,
162        })
163    }
164
165    /// Creates a math sum from a previously validated sequence of numeric
166    /// values.
167    pub fn from_numeric_values_unchecked(values: ThinVec<NumericValue>) -> Self {
168        let result = Self::try_from_numeric_values(values);
169        debug_assert!(result.is_ok(), "Expected addable values");
170
171        result.unwrap_or_else(|_| Self {
172            numeric_type: NumericType::number(),
173            values: ThinVec::from([NumericValue::zero()]),
174        })
175    }
176}
177
178/// A product of numeric values.
179///
180/// This corresponds to `CSSMathProduct` in the Typed OM specification. A
181/// product value represents an expression such as `10px * 2`. Each entry is
182/// itself a `NumericValue`, allowing nested math expressions if needed.
183#[derive(Clone, Debug)]
184#[repr(C)]
185pub struct MathProduct {
186    /// The numeric type associated with this product.
187    pub numeric_type: NumericType,
188
189    /// The list of numeric terms that make up the product.
190    pub values: ThinVec<NumericValue>,
191}
192
193impl MathProduct {
194    /// Creates a math product from a sequence of numeric values.
195    ///
196    /// Returns an error if the values do not have multipliable numeric types.
197    pub fn try_from_numeric_values(values: ThinVec<NumericValue>) -> Result<Self, ()> {
198        let numeric_type = NumericType::multiply_types(values.iter().map(|v| v.numeric_type()))?;
199
200        Ok(Self {
201            numeric_type,
202            values,
203        })
204    }
205}
206
207/// A negated numeric value.
208///
209/// This corresponds to `CSSMathNegate` in the Typed OM specification. A negate
210/// expression represents constructs such as `-10px` or `-(10px + 2em)`.
211#[derive(Clone, Debug)]
212#[repr(C)]
213pub struct MathNegate {
214    /// The numeric type associated with this negate.
215    pub numeric_type: NumericType,
216
217    /// The numeric value being negated.
218    pub value: Box<NumericValue>,
219}
220
221impl MathNegate {
222    /// Creates a math negate from a numeric value.
223    ///
224    /// The numeric type is the same as the type of the negated value.
225    pub fn from_numeric_value(value: NumericValue) -> Self {
226        let numeric_type = value.numeric_type().clone();
227
228        Self {
229            numeric_type,
230            value: Box::new(value),
231        }
232    }
233}
234
235/// An inverted numeric value.
236///
237/// This corresponds to `CSSMathInvert` in the Typed OM specification. An
238/// invert expression represents constructs such as `1 / 2`, `1 / 10px`, or
239/// more generally the reciprocal of another numeric value.
240#[derive(Clone, Debug)]
241#[repr(C)]
242pub struct MathInvert {
243    /// The numeric type associated with this invert.
244    pub numeric_type: NumericType,
245
246    /// The numeric value being inverted.
247    pub value: Box<NumericValue>,
248}
249
250impl MathInvert {
251    /// Creates a math invert from a numeric value.
252    ///
253    /// The numeric type is the same as the input type, but with all exponent
254    /// values negated.
255    pub fn from_numeric_value(value: NumericValue) -> Self {
256        let mut numeric_type = value.numeric_type().clone();
257        numeric_type.invert();
258
259        Self {
260            numeric_type,
261            value: Box::new(value),
262        }
263    }
264}
265
266/// A minimum expression over numeric values.
267///
268/// This corresponds to `CSSMathMin` in the Typed OM specification. A minimum
269/// expression represents constructs such as `min(10px, 20%)`. Each entry is
270/// itself a `NumericValue`, allowing nested math expressions if needed.
271#[derive(Clone, Debug)]
272#[repr(C)]
273pub struct MathMin {
274    /// The numeric type associated with this min.
275    pub numeric_type: NumericType,
276
277    /// The list of numeric terms that make up the min.
278    pub values: ThinVec<NumericValue>,
279}
280
281impl MathMin {
282    /// Creates a math min from a sequence of numeric values.
283    ///
284    /// Returns an error if the values do not have addable numeric types.
285    pub fn try_from_numeric_values(values: ThinVec<NumericValue>) -> Result<Self, ()> {
286        let numeric_type = NumericType::add_types(values.iter().map(|v| v.numeric_type()))?;
287
288        Ok(Self {
289            numeric_type,
290            values,
291        })
292    }
293}
294
295/// A maximum expression over numeric values.
296///
297/// This corresponds to `CSSMathMax` in the Typed OM specification. A maximum
298/// expression represents constructs such as `max(10px, 20%)`. Each entry is
299/// itself a `NumericValue`, allowing nested math expressions if needed.
300#[derive(Clone, Debug)]
301#[repr(C)]
302pub struct MathMax {
303    /// The numeric type associated with this max.
304    pub numeric_type: NumericType,
305
306    /// The list of numeric terms that make up the max.
307    pub values: ThinVec<NumericValue>,
308}
309
310impl MathMax {
311    /// Creates a math max from a sequence of numeric values.
312    ///
313    /// Returns an error if the values do not have addable numeric types.
314    pub fn try_from_numeric_values(values: ThinVec<NumericValue>) -> Result<Self, ()> {
315        let numeric_type = NumericType::add_types(values.iter().map(|v| v.numeric_type()))?;
316
317        Ok(Self {
318            numeric_type,
319            values,
320        })
321    }
322}
323
324/// A clamp expression over numeric values.
325///
326/// This corresponds to `CSSMathClamp` in the Typed OM specification. A clamp
327/// expression represents constructs such as `clamp(10px, 20%, 30px)`.
328///
329/// The array entries correspond to the lower bound, value, and upper bound,
330/// respectively.
331#[derive(Clone, Debug)]
332#[repr(C)]
333pub struct MathClamp {
334    /// The numeric type associated with this clamp.
335    pub numeric_type: NumericType,
336
337    /// The lower bound, value, and upper bound of the clamp expression, in
338    /// that order.
339    pub values: crate::OwnedArray<NumericValue, 3>,
340}
341
342impl MathClamp {
343    /// Creates a math clamp from a sequence of numeric values.
344    ///
345    /// Returns an error if the values do not have addable numeric types.
346    pub fn try_from_numeric_values(values: crate::OwnedArray<NumericValue, 3>) -> Result<Self, ()> {
347        let numeric_type = NumericType::add_types(values.iter().map(|v| v.numeric_type()))?;
348
349        Ok(Self {
350            numeric_type,
351            values,
352        })
353    }
354}
355
356/// A math expression used by the Typed OM.
357///
358/// This corresponds to `CSSMathValue` and its subclasses in the Typed OM
359/// specification.
360#[derive(Clone, Debug)]
361#[repr(C)]
362pub enum MathValue {
363    /// A sum of numeric values.
364    ///
365    /// This corresponds to `CSSMathSum`.
366    Sum(MathSum),
367
368    /// A product of numeric values.
369    ///
370    /// This corresponds to `CSSMathProduct`.
371    Product(MathProduct),
372
373    /// A negated numeric value.
374    ///
375    /// This corresponds to `CSSMathNegate`.
376    Negate(MathNegate),
377
378    /// An inverted numeric value.
379    ///
380    /// This corresponds to `CSSMathInvert`.
381    Invert(MathInvert),
382
383    /// A minimum expression over numeric values.
384    ///
385    /// This corresponds to `CSSMathMin`.
386    Min(MathMin),
387
388    /// A maximum expression over numeric values.
389    ///
390    /// This corresponds to `CSSMathMax`.
391    Max(MathMax),
392
393    /// A clamp expression over numeric values.
394    ///
395    /// This corresponds to `CSSMathClamp`.
396    Clamp(MathClamp),
397}
398
399impl MathValue {
400    /// Returns the numeric type associated with this math value.
401    pub fn numeric_type(&self) -> &NumericType {
402        match self {
403            Self::Sum(math_sum) => &math_sum.numeric_type,
404            Self::Product(math_product) => &math_product.numeric_type,
405            Self::Negate(math_negate) => &math_negate.numeric_type,
406            Self::Invert(math_invert) => &math_invert.numeric_type,
407            Self::Min(math_min) => &math_min.numeric_type,
408            Self::Max(math_max) => &math_max.numeric_type,
409            Self::Clamp(math_clamp) => &math_clamp.numeric_type,
410        }
411    }
412}
413
414/// A numeric value used by the Typed OM.
415///
416/// This corresponds to `CSSNumericValue` and its subclasses in the Typed OM
417/// specification. It represents numbers that can appear in CSS values,
418/// including both simple unit quantities and composite expressions.
419///
420/// Unlike the parser-level representation, `NumericValue` is property-agnostic
421/// and suitable for conversion to or from the `CSSNumericValue` family of DOM
422/// objects.
423#[derive(Clone, Debug)]
424#[repr(C)]
425pub enum NumericValue {
426    /// A single numeric value with a concrete unit.
427    ///
428    /// This corresponds to `CSSUnitValue`.
429    Unit(UnitValue),
430
431    /// A math expression.
432    ///
433    /// This corresponds to `CSSMathValue` and its subclasses.
434    Math(MathValue),
435}
436
437impl NumericValue {
438    /// Returns a zero pixel unit value.
439    #[inline]
440    pub fn zero_px() -> Self {
441        Self::Unit(UnitValue {
442            numeric_type: NumericType::length(),
443            value: 0.0,
444            unit: CssString::from("px"),
445        })
446    }
447
448    /// Returns the numeric type associated with this numeric value.
449    pub fn numeric_type(&self) -> &NumericType {
450        match self {
451            Self::Unit(unit_value) => &unit_value.numeric_type,
452            Self::Math(math_value) => math_value.numeric_type(),
453        }
454    }
455}
456
457impl Zero for NumericValue {
458    #[inline]
459    fn zero() -> Self {
460        Self::Unit(UnitValue {
461            numeric_type: NumericType::number(),
462            value: 0.0,
463            unit: CssString::from("number"),
464        })
465    }
466
467    #[inline]
468    fn is_zero(&self) -> bool {
469        match *self {
470            Self::Unit(ref value) => value.value == 0.0,
471            _ => false,
472        }
473    }
474}
475
476impl One for NumericValue {
477    #[inline]
478    fn one() -> Self {
479        Self::Unit(UnitValue {
480            numeric_type: NumericType::number(),
481            value: 1.0,
482            unit: CssString::from("number"),
483        })
484    }
485
486    #[inline]
487    fn is_one(&self) -> bool {
488        match *self {
489            Self::Unit(ref value) => value.value == 1.0,
490            _ => false,
491        }
492    }
493}
494
495/// A translate transform component used by the Typed OM.
496///
497/// This corresponds to `CSSTranslate` in the Typed OM specification. The `x`,
498/// `y`, and `z` components are always present; omitted offsets are represented
499/// as `0px`.
500///
501/// The `is_2d` flag indicates whether the component was reified from a 2D
502/// translate function.
503#[derive(Clone, Debug)]
504#[repr(C)]
505pub struct TranslateComponent {
506    /// The x-axis translation component.
507    pub x: NumericValue,
508
509    /// The y-axis translation component.
510    pub y: NumericValue,
511
512    /// The z-axis translation component.
513    pub z: NumericValue,
514
515    /// Whether this translate component is two-dimensional.
516    pub is_2d: bool,
517}
518
519/// A rotate transform component used by the Typed OM.
520///
521/// This corresponds to `CSSRotate` in the Typed OM specification. The `angle`,
522/// `x`, `y`, and `z` components are always present; omitted axis coordinates
523/// are represented using the implicit axis for the corresponding rotate
524/// function.
525///
526/// The `is_2d` flag indicates whether the component was reified from a 2D
527/// rotate function.
528#[derive(Clone, Debug)]
529#[repr(C)]
530pub struct RotateComponent {
531    /// The rotation angle.
532    pub angle: NumericValue,
533
534    /// The x-axis rotation coordinate.
535    pub x: NumericValue,
536
537    /// The y-axis rotation coordinate.
538    pub y: NumericValue,
539
540    /// The z-axis rotation coordinate.
541    pub z: NumericValue,
542
543    /// Whether this rotate component is two-dimensional.
544    pub is_2d: bool,
545}
546
547/// A scale transform component used by the Typed OM.
548///
549/// This corresponds to `CSSScale` in the Typed OM specification. The `x`, `y`,
550/// and `z` components are always present; omitted scale factors are
551/// represented as `1`.
552///
553/// The `is_2d` flag indicates whether the component was reified from a 2D
554/// scale function.
555#[derive(Clone, Debug)]
556#[repr(C)]
557pub struct ScaleComponent {
558    /// The x-axis scale factor.
559    pub x: NumericValue,
560
561    /// The y-axis scale factor.
562    pub y: NumericValue,
563
564    /// The z-axis scale factor.
565    pub z: NumericValue,
566
567    /// Whether this scale component is two-dimensional.
568    pub is_2d: bool,
569}
570
571/// A skew transform component used by the Typed OM.
572///
573/// This corresponds to `CSSSkew` in the Typed OM specification. The `ax` and
574/// `ay` components are always present; omitted angles are represented as
575/// `0deg`.
576///
577/// Skew components are always two-dimensional.
578#[derive(Clone, Debug)]
579#[repr(C)]
580pub struct SkewComponent {
581    /// The x-axis skew angle.
582    pub ax: NumericValue,
583
584    /// The y-axis skew angle.
585    pub ay: NumericValue,
586}
587
588/// A skewX transform component used by the Typed OM.
589///
590/// This corresponds to `CSSSkewX` in the Typed OM specification. The value is
591/// always present; omitted angles are represented as `0deg`.
592///
593/// SkewX components are always two-dimensional.
594pub type SkewXComponent = NumericValue;
595
596/// A skewY transform component used by the Typed OM.
597///
598/// This corresponds to `CSSSkewY` in the Typed OM specification. The value is
599/// always present; omitted angles are represented as `0deg`.
600///
601/// SkewY components are always two-dimensional.
602pub type SkewYComponent = NumericValue;
603
604/// A perspective value used by a perspective component.
605///
606/// This corresponds to the `CSSPerspectiveValue` union in the Typed OM
607/// specification.
608#[derive(Clone, Debug)]
609#[repr(C)]
610pub enum PerspectiveValue {
611    /// A numeric perspective value.
612    ///
613    /// This corresponds to `CSSNumericValue`.
614    Numeric(NumericValue),
615
616    /// A keyword perspective value.
617    ///
618    /// This corresponds to `CSSKeywordValue`.
619    Keyword(KeywordValue),
620}
621
622/// A perspective transform component used by the Typed OM.
623///
624/// This corresponds to `CSSPerspective` in the Typed OM specification. The
625/// `length` component is always present.
626///
627/// Perspective components are always three-dimensional.
628#[derive(Clone, Debug)]
629#[repr(C)]
630pub struct PerspectiveComponent {
631    /// The perspective length.
632    pub length: PerspectiveValue,
633}
634
635/// A matrix transform component used by the Typed OM.
636///
637/// This corresponds to `CSSMatrixComponent` in the Typed OM specification.
638///
639/// The `matrix` field always stores a full 4×4 matrix. Two-dimensional
640/// matrices are expanded to their equivalent 3D representation during
641/// reification.
642///
643/// The `is_2d` flag indicates whether the component was reified from a 2D
644/// matrix function.
645#[derive(Clone, Debug)]
646#[repr(C)]
647pub struct MatrixComponent {
648    /// The 4×4 matrix.
649    pub matrix: GenericMatrix3D<CSSFloat>,
650
651    /// Whether this matrix component is two-dimensional.
652    pub is_2d: bool,
653}
654
655/// A single transform component used by the Typed OM.
656///
657/// This corresponds to `CSSTransformComponent` in the Typed OM specification.
658/// Each variant represents one concrete transform component subclass.
659#[derive(Clone, Debug)]
660#[repr(C)]
661pub enum TransformComponent {
662    /// A translate transform component.
663    ///
664    /// This corresponds to `CSSTranslate`.
665    Translate(TranslateComponent),
666
667    /// A rotate transform component.
668    ///
669    /// This corresponds to `CSSRotate`.
670    Rotate(RotateComponent),
671
672    /// A scale transform component.
673    ///
674    /// This corresponds to `CSSScale`.
675    Scale(ScaleComponent),
676
677    /// A skew transform component.
678    ///
679    /// This corresponds to `CSSSkew`.
680    Skew(SkewComponent),
681
682    /// A skewX transform component.
683    ///
684    /// This corresponds to `CSSSkewX`.
685    SkewX(SkewXComponent),
686
687    /// A skewY transform component.
688    ///
689    /// This corresponds to `CSSSkewY`.
690    SkewY(SkewYComponent),
691
692    /// A perspective transform component.
693    ///
694    /// This corresponds to `CSSPerspective`.
695    Perspective(PerspectiveComponent),
696
697    /// A matrix transform component.
698    ///
699    /// This corresponds to `CSSMatrixComponent`.
700    Matrix(MatrixComponent),
701}
702
703/// A transform value used by the Typed OM.
704///
705/// This corresponds to `CSSTransformValue` in the Typed OM specification. It
706/// represents a `<transform-list>` as an ordered list of transform components.
707pub type TransformValue = ThinVec<TransformComponent>;
708
709/// An image value used by the Typed OM.
710///
711/// This corresponds to `CSSImageValue` in the Typed OM specification.
712///
713/// `CSSImageValue` objects represent values for properties that take
714/// `<image>` values.
715#[derive(Clone, Debug, ToCss)]
716#[repr(C)]
717pub enum ImageValue {
718    /// A specified image URL value.
719    ///
720    /// Relative URLs are preserved and continue to resolve against the
721    /// originating stylesheet or document when later used.
722    Specified(SpecifiedUrl),
723
724    /// A computed image URL value.
725    ///
726    /// Computed URLs are already resolved according to normal CSS computed
727    /// value processing.
728    Computed(ComputedUrl),
729}
730
731/// A property-agnostic representation of a value, used by Typed OM.
732///
733/// `TypedValue` is the internal counterpart of the various `CSSStyleValue`
734/// subclasses defined by the Typed OM specification. It captures values that
735/// can be represented independently of any particular property.
736#[derive(Clone, Debug)]
737#[repr(C)]
738pub enum TypedValue {
739    /// An unparsed value consisting of string fragments and variable
740    /// references.
741    ///
742    /// This corresponds to `CSSUnparsedValue` in the Typed OM specification.
743    Unparsed(UnparsedValue),
744
745    /// A keyword value such as `"block"`, `"none"`, or `"thin"`.
746    ///
747    /// This corresponds to `CSSKeywordValue` in the Typed OM specification.
748    /// Keywords are represented as a standalone `KeywordValue` so they can
749    /// be carried and compared independently of any particular property.
750    Keyword(KeywordValue),
751
752    /// A numeric value such as a length, angle, time, or a sum thereof.
753    ///
754    /// This corresponds to the `CSSNumericValue` hierarchy in the Typed OM
755    /// specification, including `CSSUnitValue` and `CSSMathSum`.
756    Numeric(NumericValue),
757
758    /// A transform value such as `translate(10px, 20px)`.
759    ///
760    /// This corresponds to `CSSTransformValue` in the Typed OM specification.
761    Transform(TransformValue),
762
763    /// An image value.
764    ///
765    /// This corresponds to `CSSImageValue` in the Typed OM specification.
766    Image(ImageValue),
767}
768
769/// A list of property-agnostic values used by the Typed OM.
770///
771/// `TypedValueList` is the internal counterpart of CSS value lists exposed by
772/// Typed OM. It stores one or more [`TypedValue`] items in source order and
773/// is used when a value reifies to multiple property-agnostic components.
774#[derive(Clone, Debug)]
775#[repr(C)]
776pub struct TypedValueList {
777    /// The list of reified values.
778    pub values: ThinVec<TypedValue>,
779}
780
781/// Reifies a value into its Typed OM representation.
782///
783/// This trait is the Typed OM analogue of [`ToCss`]. Instead of serializing
784/// values into CSS syntax, it converts them into [`TypedValue`]s that can be
785/// exposed to the DOM as `CSSStyleValue` subclasses.
786///
787/// Most consumers should use [`ToTyped::to_typed_value`] or
788/// [`ToTyped::to_typed_value_list`], depending on whether they need a single
789/// reified value or the full list of reified values.
790///
791/// This trait is derivable with `#[derive(ToTyped)]`. The derived
792/// implementation currently supports:
793///
794/// * Keyword enums: Enums whose variants are all unit variants are
795///   automatically reified as [`TypedValue::Keyword`], using the same
796///   serialization logic as [`ToCss`].
797///
798/// * Bitflags structs: Structs annotated with
799///   `#[css(bitflags(single = "...", mixed = "...", overlapping_bits))]`
800///   are automatically reified as [`TypedValue::Keyword`] values when they
801///   can be represented as a single CSS keyword. Values that would serialize
802///   to multiple CSS keywords are treated as unsupported.
803///
804/// * Structs and data-carrying variants: Unless treated specially (such as
805///   bitflags structs), the derive attempts to call `.to_typed()` recursively
806///   on supported fields or variant payloads, producing [`TypedValue`]s when
807///   possible.
808///
809/// * Other cases: If no automatic mapping is defined, or recursion is
810///   explicitly disabled, the derived implementation falls back to the
811///   default method (which returns `Err(())`, and thus `to_typed_value()`
812///   returns `None`).
813///
814/// Over time, the derive may be extended to handle additional CSS value
815/// categories such as numeric, color, and transform types.
816///
817/// Summary of derive attributes recognized by `#[derive(ToTyped)]`:
818///
819/// * `#[css(bitflags(single = "...", mixed = "...", overlapping_bits))]` on a
820///   struct generates keyword reification for CSS bitflags types. Values that
821///   can be represented as a single CSS keyword are reified as
822///   [`TypedValue::Keyword`]; values that would serialize to multiple CSS
823///   keywords are treated as unsupported and return `Err(())`.
824///
825///   `overlapping_bits` is supported for bitflags where one keyword subsumes
826///   other internal bits, such as `contain: size`.
827///
828/// * `#[typed(skip_derive_fields)]` on the type disables recursion for
829///   structs and data-carrying enum variants.
830///
831/// * `#[css(skip)]`, `#[typed(skip)]`, or `#[typed(todo)]` on a variant cause
832///   that variant to be treated as unsupported (the derived implementation
833///   returns `Err(())`).
834///
835/// * `#[css(skip)]` on a field causes that field to be ignored during
836///   reification.
837///
838/// * `#[css(skip_if = "...")]` / `#[typed(skip_if = "...")]` on a field
839///   conditionally disables reification for that field. If the provided
840///   function returns `true` for the field value, the field is ignored.
841///
842/// * `#[css(contextual_skip_if = "...")]` /
843///   `#[typed(contextual_skip_if = "...")]` on a field conditionally disables
844///   reification for that field. The provided function is called with all
845///   fields in the current struct or variant. If it returns `true`, the field
846///   is ignored.
847///
848///   Typed skip annotations override CSS skip annotations when both are
849///   present.
850///
851/// * `#[css(keyword = "...")]` on a unit variant overrides the keyword that
852///   would otherwise be derived from the Rust identifier.
853///
854/// * `#[css(comma)]` on the variant indicates that supported fields may reify
855///   to multiple separate values. When this attribute is present, multiple
856///   [`TypedValue`] items may be produced, unless
857///   `#[typed(no_multiple_values)]` is also present. If multiple values are
858///   not allowed and the derived implementation would produce more than one
859///   item, it returns `Err(())`.
860///
861/// * `#[typed(no_multiple_values)]` on a variant prevents it from reifying to
862///   multiple [`TypedValue`] items, even if `#[css(comma)]` is present.
863///
864/// * `#[css(iterable)]` on a field indicates that the field represents a list
865///   of values. Each item in the iterable is reified individually by calling
866///   `ToTyped::to_typed` on the element type.
867///
868/// * `#[css(if_empty = "...")]` on an iterable field specifies a keyword
869///   value that should be produced when the iterable is empty.
870///
871/// * `#[css(represents_keyword)]` on a bool field causes the field name to be
872///   reified as a keyword when the field is true.
873pub trait ToTyped {
874    /// Attempt to convert `self` into one or more [`TypedValue`] items.
875    ///
876    /// Implementations append any resulting values to `dest`. This is the
877    /// low-level entry point used by the Typed OM reification infrastructure.
878    /// Most callers should prefer [`ToTyped::to_typed_value`] or
879    /// [`ToTyped::to_typed_value_list`].
880    ///
881    /// Returning `Err(())` indicates that the value cannot be represented as
882    /// a property-agnostic Typed OM value.
883    fn to_typed(&self, _dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
884        Err(())
885    }
886
887    /// Attempt to convert `self` into a [`TypedValue`].
888    ///
889    /// Returns the first reified value as `Some(TypedValue)` if the value can
890    /// be reified into a property-agnostic CSSStyleValue subclass. Returns
891    /// `None` if the value is unrepresentable, in which case consumers
892    /// produce a property-tied CSSStyleValue instead.
893    fn to_typed_value(&self) -> Option<TypedValue> {
894        let mut dest = ThinVec::new();
895        self.to_typed(&mut dest).ok()?;
896        dest.into_iter().next()
897    }
898
899    /// Attempt to convert `self` into a [`NumericValue`].
900    ///
901    /// Returns `Some(NumericValue)` if the value reifies to a single
902    /// `TypedValue::Numeric` item. Returns `None` otherwise.
903    fn to_numeric_value(&self) -> Option<NumericValue> {
904        match self.to_typed_value()? {
905            TypedValue::Numeric(value) => Some(value),
906            _ => None,
907        }
908    }
909
910    /// Attempt to convert `self` into a [`TypedValueList`].
911    ///
912    /// Returns `Some(TypedValueList)` if the value can be reified into one or
913    /// more property-agnostic Typed OM values. Returns `None` if the value is
914    /// unrepresentable, in which case consumers produce a property-tied
915    /// `CSSStyleValue` instead.
916    fn to_typed_value_list(&self) -> Option<TypedValueList> {
917        let mut dest = ThinVec::new();
918        self.to_typed(&mut dest).ok()?;
919        Some(TypedValueList { values: dest })
920    }
921}
922
923impl<T> ToTyped for &T
924where
925    T: ToTyped + ?Sized,
926{
927    fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
928        (*self).to_typed(dest)
929    }
930}
931
932impl<T> ToTyped for Box<T>
933where
934    T: ?Sized + ToTyped,
935{
936    fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
937        (**self).to_typed(dest)
938    }
939}
940
941impl<T> ToTyped for Arc<T>
942where
943    T: ?Sized + ToTyped,
944{
945    fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
946        (**self).to_typed(dest)
947    }
948}
949
950impl ToTyped for Au {
951    fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
952        let numeric_type = NumericType::length();
953        let value = self.to_f32_px();
954        let unit = CssString::from("px");
955        dest.push(TypedValue::Numeric(NumericValue::Unit(UnitValue {
956            numeric_type,
957            value,
958            unit,
959        })));
960        Ok(())
961    }
962}
963
964macro_rules! impl_to_typed_for_predefined_type {
965    ($name: ty) => {
966        impl<'a> ToTyped for $name {
967            fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
968                dest.push(TypedValue::Numeric(NumericValue::Unit(UnitValue {
969                    numeric_type: NumericType::number(),
970                    value: *self as f32,
971                    unit: CssString::from("number"),
972                })));
973                Ok(())
974            }
975        }
976    };
977}
978
979impl_to_typed_for_predefined_type!(f32);
980impl_to_typed_for_predefined_type!(i8);
981impl_to_typed_for_predefined_type!(i32);