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, PercentageContext};
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(
221        context: &ParserContext,
222        input: &mut Parser,
223        clamping_mode: AllowedNumericType,
224    ) -> Result<Self, ParseError> {
225        use style_traits::ParsingMode;
226
227        match *input.next()? {
228            // Note that we generally pass ParserContext to is_ok() to check
229            // that the ParserMode of the ParserContext allows all numeric
230            // values for SMIL regardless of clamping_mode, but in this Time
231            // value case, the value does not animate for SMIL at all, so we use
232            // ParsingMode::DEFAULT directly.
233            Token::Dimension {
234                value, ref unit, ..
235            } if clamping_mode.is_ok(ParsingMode::DEFAULT, value) => {
236                NoCalcTime::parse_dimension(value, unit)
237                    .map_err(|()| ParseError::custom(StyleParseErrorKind::UnspecifiedError))
238                    .map(Self::new)
239            },
240            Token::Function(ref name) => {
241                let function = CalcNode::math_function(context, name)?;
242                CalcNode::parse_time(
243                    context,
244                    input,
245                    clamping_mode,
246                    function,
247                    PercentageContext::not_allowed(),
248                )
249                .map(Box::new)
250                .map(Self::new_calc)
251            },
252            _ => Err(ParseError::unexpected_token()),
253        }
254    }
255
256    /// Parses a non-negative time value.
257    pub fn parse_non_negative(
258        context: &ParserContext,
259        input: &mut Parser,
260    ) -> Result<Self, ParseError> {
261        Self::parse_with_clamping_mode(context, input, AllowedNumericType::NonNegative)
262    }
263}
264
265impl Zero for Time {
266    #[inline]
267    fn zero() -> Self {
268        Self::from_seconds(0.0)
269    }
270
271    #[inline]
272    fn is_zero(&self) -> bool {
273        // The unit doesn't matter, i.e. `s` and `ms` are the same for zero.
274        match self.0.unpack() {
275            Unpacked::Inline(_, value) => value == 0.0,
276            Unpacked::Boxed(_) => false,
277        }
278    }
279}
280
281impl ToComputedValue for Time {
282    type ComputedValue = ComputedTime;
283
284    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
285        match self.0.unpack() {
286            Unpacked::Inline(unit, value) => {
287                NoCalcTime::new(unit, value).to_computed_value(context)
288            },
289            Unpacked::Boxed(calc) => {
290                let value = calc.resolve(context, |result| match result {
291                    Ok(Leaf::Time(t)) => t.seconds(),
292                    _ => {
293                        debug_assert!(false, "Unexpected Time::Calc without resolved time");
294                        f32::NAN
295                    },
296                });
297                ComputedTime::from_seconds(
298                    crate::values::normalize(value).min(f32::MAX).max(f32::MIN),
299                )
300            },
301        }
302    }
303
304    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
305        Self::from_seconds(computed.seconds())
306    }
307}
308
309impl Parse for Time {
310    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
311        Self::parse_with_clamping_mode(context, input, AllowedNumericType::All)
312    }
313}