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