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