1use crate::derives::*;
8use crate::parser::{Parse, ParserContext};
9use crate::typed_om::{NumericBaseType, ToTyped, TypedValue};
10use crate::values::computed::transform::DirectionVector;
11use crate::values::computed::{Context, ToComputedValue};
12use crate::values::generics::transform::IsParallelTo;
13use crate::values::generics::Optional;
14use crate::values::generics::{GreaterThanOrEqualToOne, NonNegative};
15use crate::values::specified::calc::{
16 CalcNode, CalcNumeric, CalcPercentageLeaf, Leaf, PercentageContext,
17};
18use crate::values::specified::Percentage;
19use crate::values::tagged_numeric::{NumericUnion, Unpacked, UnpackedMut};
20use crate::values::{serialize_number, CSSFloat, CSSInteger};
21use crate::{One, Zero};
22use cssparser::{Parser, Token};
23use std::fmt::{self, Write};
24use style_traits::values::specified::AllowedNumericType;
25use style_traits::{CssWriter, ParseError, ParsingMode, SpecifiedValueInfo, ToCss};
26use thin_vec::ThinVec;
27
28pub fn parse_number_with_clamping_mode(
30 context: &ParserContext,
31 input: &mut Parser,
32 clamping_mode: AllowedNumericType,
33 percentage_context: PercentageContext,
34) -> Result<Number, ParseError> {
35 Ok(Number(match *input.next()? {
36 Token::Number { value, .. } if clamping_mode.is_ok(context.parsing_mode, value) => {
37 NumericUnion::inline((), value)
38 },
39 Token::Function(ref name) => {
40 let function = CalcNode::math_function(context, name)?;
41 let number = CalcNode::parse_number(
42 context,
43 input,
44 clamping_mode,
45 function,
46 percentage_context,
47 )?;
48 NumericUnion::boxed(Box::new(number))
49 },
50 _ => return Err(ParseError::unexpected_token()),
51 }))
52}
53
54pub fn parse_integer_with_clamping_mode(
56 context: &ParserContext,
57 input: &mut Parser,
58 clamping_mode: AllowedNumericType,
59 percentage_context: PercentageContext,
60) -> Result<Integer, ParseError> {
61 Ok(Integer(match *input.next()? {
62 Token::Number {
63 int_value: Some(v), ..
64 } if clamping_mode.is_ok(context.parsing_mode, v as f32) => NumericUnion::inline((), v),
65 Token::Function(ref name) => {
66 let function = CalcNode::math_function(context, name)?;
67 let calc = CalcNode::parse_number(
68 context,
69 input,
70 clamping_mode,
71 function,
72 percentage_context,
73 )?;
74 NumericUnion::boxed(Box::new(calc))
75 },
76 _ => return Err(ParseError::unexpected_token()),
77 }))
78}
79
80#[derive(Clone, Copy, Debug, MallocSizeOf, ToShmem, ToTyped)]
82#[repr(C)]
83pub struct NoCalcNumber(CSSFloat);
84
85impl NoCalcNumber {
86 #[inline]
88 pub fn new(val: CSSFloat) -> Self {
89 Self(val)
90 }
91
92 #[inline]
94 pub fn value(&self) -> f32 {
95 self.0
96 }
97
98 #[inline]
100 pub fn get(&self) -> f32 {
101 crate::values::normalize(self.0).min(f32::MAX).max(f32::MIN)
102 }
103
104 pub fn unit(&self) -> &'static str {
106 "number"
107 }
108
109 pub fn canonical_unit(&self) -> Option<&'static str> {
111 None
112 }
113
114 pub fn to(&self, unit: &str) -> Result<Self, ()> {
116 if !unit.eq_ignore_ascii_case("number") {
117 return Err(());
118 }
119 Ok(*self)
120 }
121}
122
123impl PartialEq<NoCalcNumber> for NoCalcNumber {
124 fn eq(&self, other: &NoCalcNumber) -> bool {
125 self.0 == other.0 || (self.0.is_nan() && other.0.is_nan())
126 }
127}
128
129impl PartialOrd<NoCalcNumber> for NoCalcNumber {
130 fn partial_cmp(&self, other: &NoCalcNumber) -> Option<std::cmp::Ordering> {
131 self.get().partial_cmp(&other.get())
132 }
133}
134
135impl ToCss for NoCalcNumber {
136 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
137 where
138 W: Write,
139 {
140 serialize_number(self.0, dest)
141 }
142}
143
144impl ToComputedValue for NoCalcNumber {
145 type ComputedValue = CSSFloat;
146
147 fn to_computed_value(&self, _: &Context) -> Self::ComputedValue {
148 self.get()
149 }
150
151 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
152 Self::new(*computed)
153 }
154}
155
156#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
160pub struct Number(NumericUnion<(), f32, CalcNumeric>);
161
162impl ToCss for Number {
163 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
164 where
165 W: Write,
166 {
167 match self.0.unpack() {
168 Unpacked::Inline(_, v) => NoCalcNumber(v).to_css(dest),
169 Unpacked::Boxed(calc) => calc.to_css(dest),
170 }
171 }
172}
173
174impl ToTyped for Number {
175 fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
176 match self.0.unpack() {
177 Unpacked::Inline((), v) => NoCalcNumber(v).to_typed(dest),
178 Unpacked::Boxed(ref calc) => calc.to_typed(dest),
179 }
180 }
181}
182
183impl Parse for Number {
184 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
185 parse_number_with_clamping_mode(
186 context,
187 input,
188 AllowedNumericType::All,
189 PercentageContext::not_allowed(),
190 )
191 }
192}
193
194impl PartialOrd<Number> for Number {
195 fn partial_cmp(&self, other: &Number) -> Option<std::cmp::Ordering> {
196 self.get().partial_cmp(&other.get())
197 }
198}
199
200impl Number {
201 #[inline]
203 pub fn new(val: CSSFloat) -> Self {
204 Self(NumericUnion::inline((), val))
205 }
206
207 #[inline]
209 pub fn new_calc(val: Box<CalcNumeric>) -> Self {
210 Self(NumericUnion::boxed(val))
211 }
212
213 pub fn to_percentage(&self) -> Option<Percentage> {
215 Some(match self.0.unpack() {
216 Unpacked::Inline((), n) => Percentage::new(n),
217 Unpacked::Boxed(calc) => {
218 let n = calc.as_number()?.get();
219 Percentage::new_calc(Box::new(calc.with_leaf_node(Leaf::Percentage(
220 CalcPercentageLeaf::new(n, Optional::Some(NumericBaseType::Percent)),
221 ))))
222 },
223 })
224 }
225
226 #[inline]
230 pub fn get(&self) -> Option<f32> {
231 match self.0.unpack() {
232 Unpacked::Inline((), f) => Some(NoCalcNumber(f).get()),
233 Unpacked::Boxed(..) => None,
234 }
235 }
236
237 pub fn resolve(&self) -> Option<f32> {
240 match self.0.unpack() {
241 Unpacked::Inline((), f) => Some(NoCalcNumber(f).get()),
242 Unpacked::Boxed(calc) => calc.as_number().map(|n| n.get()),
243 }
244 }
245
246 #[inline]
248 pub fn as_calc(&self) -> Option<&CalcNumeric> {
249 match self.0.unpack() {
250 Unpacked::Inline(..) => None,
251 Unpacked::Boxed(calc) => Some(calc),
252 }
253 }
254
255 #[allow(missing_docs)]
256 pub fn parse_non_negative(
257 context: &ParserContext,
258 input: &mut Parser,
259 ) -> Result<Number, ParseError> {
260 parse_number_with_clamping_mode(
261 context,
262 input,
263 AllowedNumericType::NonNegative,
264 PercentageContext::not_allowed(),
265 )
266 }
267
268 #[allow(missing_docs)]
269 pub fn parse_at_least_one(
270 context: &ParserContext,
271 input: &mut Parser,
272 ) -> Result<Number, ParseError> {
273 parse_number_with_clamping_mode(
274 context,
275 input,
276 AllowedNumericType::AtLeastOne,
277 PercentageContext::not_allowed(),
278 )
279 }
280
281 #[inline]
283 pub fn clamp_to_one(&mut self) {
284 match self.0.unpack_mut() {
285 UnpackedMut::Inline(_, ref mut v) => **v = v.min(1.),
286 UnpackedMut::Boxed(ref mut calc) => {
287 calc.clamping_mode = AllowedNumericType::ZeroToOne;
288 },
289 }
290 }
291}
292
293impl ToComputedValue for Number {
294 type ComputedValue = CSSFloat;
295
296 #[inline]
297 fn to_computed_value(&self, context: &Context) -> CSSFloat {
298 match self.0.unpack() {
299 Unpacked::Inline((), n) => NoCalcNumber(n).to_computed_value(context),
300 Unpacked::Boxed(calc) => {
301 let value = calc.resolve(context, |result| match result {
302 Ok(Leaf::Number(n)) => n.get(),
303 _ => {
304 debug_assert!(false, "Unexpected Number::Calc without resolved number");
305 f32::NAN
306 },
307 });
308 crate::values::normalize(value).min(f32::MAX).max(f32::MIN)
309 },
310 }
311 }
312
313 #[inline]
314 fn from_computed_value(computed: &CSSFloat) -> Self {
315 Number::new(*computed)
316 }
317}
318
319impl IsParallelTo for (Number, Number, Number) {
320 fn is_parallel_to(&self, vector: &DirectionVector) -> bool {
321 use euclid::approxeq::ApproxEq;
322 match (self.0.get(), self.1.get(), self.2.get()) {
325 (Some(x), Some(y), Some(z)) => DirectionVector::new(x, y, z)
326 .cross(*vector)
327 .square_length()
328 .approx_eq(&0.0f32),
329 _ => false,
330 }
331 }
332}
333
334impl SpecifiedValueInfo for Number {}
335
336impl Zero for Number {
337 #[inline]
338 fn zero() -> Self {
339 Self::new(0.)
340 }
341
342 #[inline]
344 fn is_zero(&self) -> bool {
345 self.get() == Some(0.)
346 }
347}
348
349pub type NonNegativeNumber = NonNegative<Number>;
351
352impl Parse for NonNegativeNumber {
353 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
354 parse_number_with_clamping_mode(
355 context,
356 input,
357 AllowedNumericType::NonNegative,
358 PercentageContext::not_allowed(),
359 )
360 .map(NonNegative::<Number>)
361 }
362}
363
364impl One for NonNegativeNumber {
365 #[inline]
366 fn one() -> Self {
367 NonNegativeNumber::new(1.0)
368 }
369
370 #[inline]
372 fn is_one(&self) -> bool {
373 self.get() == Some(1.)
374 }
375}
376
377impl NonNegativeNumber {
378 pub fn new(val: CSSFloat) -> Self {
380 NonNegative(Number::new(val.max(0.)))
381 }
382
383 #[inline]
385 pub fn get(&self) -> Option<f32> {
386 self.0.get()
387 }
388
389 #[inline]
391 pub fn as_calc(&self) -> Option<&CalcNumeric> {
392 self.0.as_calc()
393 }
394}
395
396pub type NonNegativeInteger = NonNegative<Integer>;
399
400impl Parse for NonNegativeInteger {
401 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
402 Ok(NonNegative(Integer::parse_non_negative(context, input)?))
403 }
404}
405
406pub type GreaterThanOrEqualToOneNumber = GreaterThanOrEqualToOne<Number>;
408
409impl Parse for GreaterThanOrEqualToOneNumber {
410 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
411 parse_number_with_clamping_mode(
412 context,
413 input,
414 AllowedNumericType::AtLeastOne,
415 PercentageContext::not_allowed(),
416 )
417 .map(GreaterThanOrEqualToOne::<Number>)
418 }
419}
420
421#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
428pub struct Integer(NumericUnion<(), i32, CalcNumeric>);
429
430impl Zero for Integer {
431 #[inline]
432 fn zero() -> Self {
433 Self::new(0)
434 }
435
436 #[inline]
438 fn is_zero(&self) -> bool {
439 self.get() == Some(0)
440 }
441}
442
443impl One for Integer {
444 #[inline]
445 fn one() -> Self {
446 Self::new(1)
447 }
448
449 #[inline]
451 fn is_one(&self) -> bool {
452 self.get() == Some(1)
453 }
454}
455
456impl PartialEq<i32> for Integer {
457 fn eq(&self, value: &i32) -> bool {
458 self.get().is_some_and(|v| v == *value)
459 }
460}
461
462impl Integer {
463 pub fn new(val: CSSInteger) -> Self {
465 Self(NumericUnion::inline((), val))
466 }
467
468 pub fn get(&self) -> Option<CSSInteger> {
472 match self.0.unpack() {
473 Unpacked::Inline((), v) => Some(v),
474 Unpacked::Boxed(..) => None,
475 }
476 }
477
478 pub fn resolve(&self) -> Option<CSSInteger> {
481 Some(match self.0.unpack() {
482 Unpacked::Inline((), v) => v,
483 Unpacked::Boxed(calc) => {
484 let value = calc.as_number()?.get();
485 (value + 0.5).floor() as CSSInteger
486 },
487 })
488 }
489
490 pub fn ensure_clamping_mode(&mut self, clamping_mode: AllowedNumericType) -> Result<(), ()> {
492 match self.0.unpack_mut() {
493 UnpackedMut::Inline(_, i) => {
494 if !clamping_mode.is_ok(ParsingMode::DEFAULT, *i as f32) {
495 return Err(());
496 }
497 },
498 UnpackedMut::Boxed(ref mut calc) => {
499 calc.clamping_mode = clamping_mode;
500 },
501 }
502 Ok(())
503 }
504}
505
506impl Parse for Integer {
507 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
508 parse_integer_with_clamping_mode(
509 context,
510 input,
511 AllowedNumericType::All,
512 PercentageContext::not_allowed(),
513 )
514 }
515}
516
517impl Integer {
518 pub fn parse_non_negative(
520 context: &ParserContext,
521 input: &mut Parser,
522 ) -> Result<Integer, ParseError> {
523 parse_integer_with_clamping_mode(
524 context,
525 input,
526 AllowedNumericType::NonNegative,
527 PercentageContext::not_allowed(),
528 )
529 }
530
531 pub fn parse_positive(
533 context: &ParserContext,
534 input: &mut Parser,
535 ) -> Result<Integer, ParseError> {
536 parse_integer_with_clamping_mode(
537 context,
538 input,
539 AllowedNumericType::AtLeastOne,
540 PercentageContext::not_allowed(),
541 )
542 }
543}
544
545impl ToCss for Integer {
546 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
547 where
548 W: Write,
549 {
550 match self.0.unpack() {
551 Unpacked::Inline(_, v) => v.to_css(dest),
552 Unpacked::Boxed(calc) => calc.to_css(dest),
553 }
554 }
555}
556
557impl ToTyped for Integer {
558 fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
559 match self.0.unpack() {
560 Unpacked::Inline((), n) => n.to_typed(dest),
561 Unpacked::Boxed(ref calc) => calc.to_typed(dest),
562 }
563 }
564}
565
566impl ToComputedValue for Integer {
567 type ComputedValue = i32;
568
569 #[inline]
570 fn to_computed_value(&self, context: &Context) -> i32 {
571 match self.0.unpack() {
572 Unpacked::Inline((), i) => i,
573 Unpacked::Boxed(calc) => {
574 let value = calc.resolve(context, |result| match result {
575 Ok(Leaf::Number(n)) => n.get(),
576 _ => {
577 debug_assert!(false, "Unexpected Integer::Calc without resolved number");
578 f32::NAN
579 },
580 });
581 let clamped = crate::values::normalize(value).min(f32::MAX).max(f32::MIN);
582 (clamped + 0.5).floor() as i32
583 },
584 }
585 }
586
587 #[inline]
588 fn from_computed_value(computed: &i32) -> Self {
589 Self::new(*computed)
590 }
591}
592
593impl SpecifiedValueInfo for Integer {}
594
595pub type PositiveInteger = GreaterThanOrEqualToOne<Integer>;
598
599impl Parse for PositiveInteger {
600 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
601 Integer::parse_positive(context, input).map(GreaterThanOrEqualToOne)
602 }
603}