Skip to main content

style/values/specified/
time.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//! Specified time values.
6
7use crate::derives::*;
8use crate::parser::{Parse, ParserContext};
9use crate::typed_om::{NumericValue, ToTyped, TypedValue, UnitValue};
10use crate::values::computed::time::Time as ComputedTime;
11use crate::values::computed::{Context, ToComputedValue};
12use crate::values::specified::calc::{CalcNode, CalcNumeric, Leaf};
13use crate::values::tagged_numeric::{NumericUnion, Unpacked};
14use crate::values::CSSFloat;
15use crate::Zero;
16use cssparser::{match_ignore_ascii_case, Parser, Token};
17use std::fmt::{self, Write};
18use style_traits::values::specified::AllowedNumericType;
19use style_traits::{
20    CssString, CssWriter, ParseError, SpecifiedValueInfo, StyleParseErrorKind, ToCss,
21};
22use thin_vec::ThinVec;
23
24/// A time unit.
25#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
26#[repr(u8)]
27pub enum TimeUnit {
28    /// `s`
29    Second,
30    /// `ms`
31    Millisecond,
32}
33
34/// A time value according to CSS-VALUES § 6.2.
35#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToShmem)]
36#[repr(C)]
37pub struct NoCalcTime {
38    unit: TimeUnit,
39    value: CSSFloat,
40}
41
42impl NoCalcTime {
43    /// Creates a time with the given unit and value (in that unit).
44    #[inline]
45    pub fn new(unit: TimeUnit, value: CSSFloat) -> Self {
46        Self { unit, value }
47    }
48
49    /// Returns a time value that represents `seconds` seconds.
50    #[inline]
51    pub fn from_seconds(seconds: CSSFloat) -> Self {
52        Self::new(TimeUnit::Second, seconds)
53    }
54
55    /// Returns the time in fractional seconds.
56    #[inline]
57    pub fn seconds(&self) -> CSSFloat {
58        match self.unit {
59            TimeUnit::Second => self.value,
60            TimeUnit::Millisecond => self.value / 1000.0,
61        }
62    }
63
64    /// Returns the unit of the time.
65    #[inline]
66    pub fn time_unit(&self) -> TimeUnit {
67        self.unit
68    }
69
70    /// Returns the unit of the time as a string.
71    #[inline]
72    pub fn unit(&self) -> &'static str {
73        match self.unit {
74            TimeUnit::Second => "s",
75            TimeUnit::Millisecond => "ms",
76        }
77    }
78
79    /// Return the unitless, raw value.
80    #[inline]
81    pub fn unitless_value(&self) -> CSSFloat {
82        self.value
83    }
84
85    /// Return the canonical unit for this value.
86    pub fn canonical_unit(&self) -> Option<&'static str> {
87        Some("s")
88    }
89
90    /// Convert this value to the specified unit, if possible.
91    pub fn to(&self, unit: &str) -> Result<Self, ()> {
92        let target = match_ignore_ascii_case! { unit,
93            "s" => TimeUnit::Second,
94            "ms" => TimeUnit::Millisecond,
95             _ => return Err(()),
96        };
97        let value = match target {
98            TimeUnit::Second => self.seconds(),
99            TimeUnit::Millisecond => self.seconds() * 1000.0,
100        };
101        Ok(Self::new(target, value))
102    }
103
104    /// Parses a time according to CSS-VALUES § 6.2.
105    pub fn parse_dimension(value: CSSFloat, unit: &str) -> Result<Self, ()> {
106        let unit = match_ignore_ascii_case! { unit,
107            "s" => TimeUnit::Second,
108            "ms" => TimeUnit::Millisecond,
109            _ => return Err(())
110        };
111        Ok(Self::new(unit, value))
112    }
113}
114
115impl ToCss for NoCalcTime {
116    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
117    where
118        W: Write,
119    {
120        crate::values::serialize_specified_dimension(
121            self.unitless_value(),
122            self.unit(),
123            /* was_calc = */ false,
124            dest,
125        )
126    }
127}
128
129impl ToComputedValue for NoCalcTime {
130    type ComputedValue = ComputedTime;
131
132    fn to_computed_value(&self, _: &Context) -> Self::ComputedValue {
133        ComputedTime::from_seconds(self.seconds())
134    }
135
136    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
137        Self::from_seconds(computed.seconds())
138    }
139}
140
141impl ToTyped for NoCalcTime {
142    fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
143        let numeric_value = NumericValue::Unit(UnitValue {
144            value: self.unitless_value(),
145            unit: CssString::from(self.unit()),
146        });
147
148        // https://drafts.css-houdini.org/css-typed-om-1/#reify-a-math-expression
149        dest.push(TypedValue::Numeric(numeric_value));
150
151        Ok(())
152    }
153}
154
155impl SpecifiedValueInfo for NoCalcTime {}
156
157/// A specified time value, either a plain value or a `calc()` expression.
158#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
159pub struct Time(NumericUnion<TimeUnit, f32, CalcNumeric>);
160
161impl ToCss for Time {
162    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
163    where
164        W: Write,
165    {
166        match self.0.unpack() {
167            Unpacked::Inline(unit, value) => NoCalcTime::new(unit, value).to_css(dest),
168            Unpacked::Boxed(calc) => calc.to_css(dest),
169        }
170    }
171}
172
173impl ToTyped for Time {
174    fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
175        match self.0.unpack() {
176            Unpacked::Inline(unit, value) => NoCalcTime::new(unit, value).to_typed(dest),
177            Unpacked::Boxed(calc) => calc.to_typed(dest),
178        }
179    }
180}
181
182impl SpecifiedValueInfo for Time {}
183
184impl Time {
185    /// Creates a time from a non-calc `NoCalcTime`.
186    #[inline]
187    pub fn new(time: NoCalcTime) -> Self {
188        Self(NumericUnion::inline(time.unit, time.value))
189    }
190
191    /// Creates a time from a `calc()` expression.
192    #[inline]
193    pub fn new_calc(calc: Box<CalcNumeric>) -> Self {
194        Self(NumericUnion::boxed(calc))
195    }
196
197    /// Returns a time value that represents `seconds` seconds.
198    #[inline]
199    pub fn from_seconds(seconds: CSSFloat) -> Self {
200        Self::new(NoCalcTime::from_seconds(seconds))
201    }
202
203    /// Returns true if this is a `calc()` expression.
204    #[inline]
205    pub fn is_calc(&self) -> bool {
206        self.0.is_boxed()
207    }
208
209    fn parse_with_clamping_mode<'i, 't>(
210        context: &ParserContext,
211        input: &mut Parser<'i, 't>,
212        clamping_mode: AllowedNumericType,
213    ) -> Result<Self, ParseError<'i>> {
214        use style_traits::ParsingMode;
215
216        let location = input.current_source_location();
217        match *input.next()? {
218            // Note that we generally pass ParserContext to is_ok() to check
219            // that the ParserMode of the ParserContext allows all numeric
220            // values for SMIL regardless of clamping_mode, but in this Time
221            // value case, the value does not animate for SMIL at all, so we use
222            // ParsingMode::DEFAULT directly.
223            Token::Dimension {
224                value, ref unit, ..
225            } if clamping_mode.is_ok(ParsingMode::DEFAULT, value) => {
226                NoCalcTime::parse_dimension(value, unit)
227                    .map_err(|()| location.new_custom_error(StyleParseErrorKind::UnspecifiedError))
228                    .map(Self::new)
229            },
230            Token::Function(ref name) => {
231                let function = CalcNode::math_function(context, name, location)?;
232                CalcNode::parse_time(context, input, clamping_mode, function)
233                    .map(Box::new)
234                    .map(Self::new_calc)
235            },
236            ref t => return Err(location.new_unexpected_token_error(t.clone())),
237        }
238    }
239
240    /// Parses a non-negative time value.
241    pub fn parse_non_negative<'i, 't>(
242        context: &ParserContext,
243        input: &mut Parser<'i, 't>,
244    ) -> Result<Self, ParseError<'i>> {
245        Self::parse_with_clamping_mode(context, input, AllowedNumericType::NonNegative)
246    }
247}
248
249impl Zero for Time {
250    #[inline]
251    fn zero() -> Self {
252        Self::from_seconds(0.0)
253    }
254
255    #[inline]
256    fn is_zero(&self) -> bool {
257        // The unit doesn't matter, i.e. `s` and `ms` are the same for zero.
258        match self.0.unpack() {
259            Unpacked::Inline(_, value) => value == 0.0,
260            Unpacked::Boxed(_) => false,
261        }
262    }
263}
264
265impl ToComputedValue for Time {
266    type ComputedValue = ComputedTime;
267
268    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
269        match self.0.unpack() {
270            Unpacked::Inline(unit, value) => {
271                NoCalcTime::new(unit, value).to_computed_value(context)
272            },
273            Unpacked::Boxed(calc) => {
274                let value = calc.resolve(context, |result| match result {
275                    Ok(Leaf::Time(t)) => t.seconds(),
276                    _ => {
277                        debug_assert!(false, "Unexpected Time::Calc without resolved time");
278                        f32::NAN
279                    },
280                });
281                ComputedTime::from_seconds(
282                    crate::values::normalize(value).min(f32::MAX).max(f32::MIN),
283                )
284            },
285        }
286    }
287
288    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
289        Self::from_seconds(computed.seconds())
290    }
291}
292
293impl Parse for Time {
294    fn parse<'i, 't>(
295        context: &ParserContext,
296        input: &mut Parser<'i, 't>,
297    ) -> Result<Self, ParseError<'i>> {
298        Self::parse_with_clamping_mode(context, input, AllowedNumericType::All)
299    }
300}