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