1use 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#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
26#[repr(u8)]
27pub enum TimeUnit {
28 Second,
30 Millisecond,
32}
33
34impl TimeUnit {
35 #[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 #[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#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToShmem)]
57#[repr(C)]
58pub struct NoCalcTime {
59 unit: TimeUnit,
60 value: CSSFloat,
61}
62
63impl NoCalcTime {
64 #[inline]
66 pub fn new(unit: TimeUnit, value: CSSFloat) -> Self {
67 Self { unit, value }
68 }
69
70 #[inline]
72 pub fn from_seconds(seconds: CSSFloat) -> Self {
73 Self::new(TimeUnit::Second, seconds)
74 }
75
76 #[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 #[inline]
87 pub fn time_unit(&self) -> TimeUnit {
88 self.unit
89 }
90
91 #[inline]
93 pub fn unit(&self) -> &'static str {
94 self.unit.as_str()
95 }
96
97 #[inline]
99 pub fn unitless_value(&self) -> CSSFloat {
100 self.value
101 }
102
103 pub fn canonical_unit(&self) -> Option<&'static str> {
105 Some("s")
106 }
107
108 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 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 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 dest.push(TypedValue::Numeric(numeric_value));
161
162 Ok(())
163 }
164}
165
166impl SpecifiedValueInfo for NoCalcTime {}
167
168#[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 #[inline]
198 pub fn new(time: NoCalcTime) -> Self {
199 Self(NumericUnion::inline(time.unit, time.value))
200 }
201
202 #[inline]
204 pub fn new_calc(calc: Box<CalcNumeric>) -> Self {
205 Self(NumericUnion::boxed(calc))
206 }
207
208 #[inline]
210 pub fn from_seconds(seconds: CSSFloat) -> Self {
211 Self::new(NoCalcTime::from_seconds(seconds))
212 }
213
214 #[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 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 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 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}