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);