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