Skip to content

Commit 4120099

Browse files
committed
Implement trig functions
parcel-bundler#117
1 parent ae87b83 commit 4120099

File tree

7 files changed

+161
-9
lines changed

7 files changed

+161
-9
lines changed

src/lib.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6012,6 +6012,54 @@ mod tests {
60126012
);
60136013
}
60146014

6015+
#[test]
6016+
fn test_trig() {
6017+
minify_test(".foo { width: calc(2px * pi); }", ".foo{width:6.28319px}");
6018+
minify_test(".foo { width: calc(2px / pi); }", ".foo{width:.63662px}");
6019+
// minify_test(
6020+
// ".foo { width: calc(2px * infinity); }",
6021+
// ".foo{width:calc(2px*infinity)}",
6022+
// );
6023+
// minify_test(
6024+
// ".foo { width: calc(2px * -infinity); }",
6025+
// ".foo{width:calc(2px*-infinity)}",
6026+
// );
6027+
minify_test(".foo { width: calc(100px * sin(45deg))", ".foo{width:70.7107px}");
6028+
minify_test(".foo { width: calc(100px * sin(.125turn))", ".foo{width:70.7107px}");
6029+
minify_test(
6030+
".foo { width: calc(100px * sin(3.14159265 / 4))",
6031+
".foo{width:70.7107px}",
6032+
);
6033+
minify_test(".foo { width: calc(100px * sin(pi / 4))", ".foo{width:70.7107px}");
6034+
minify_test(
6035+
".foo { width: calc(100px * sin(22deg + 23deg))",
6036+
".foo{width:70.7107px}",
6037+
);
6038+
6039+
minify_test(".foo { width: calc(2px * cos(45deg))", ".foo{width:1.41421px}");
6040+
minify_test(".foo { width: calc(2px * tan(45deg))", ".foo{width:2px}");
6041+
6042+
minify_test(".foo { rotate: asin(sin(45deg))", ".foo{rotate:45deg}");
6043+
minify_test(".foo { rotate: asin(1)", ".foo{rotate:90deg}");
6044+
minify_test(".foo { rotate: asin(-1)", ".foo{rotate:-90deg}");
6045+
minify_test(".foo { rotate: asin(0.5)", ".foo{rotate:30deg}");
6046+
minify_test(".foo { rotate: asin(45deg)", ".foo{rotate:asin(45deg)}"); // invalid
6047+
minify_test(".foo { rotate: asin(-20)", ".foo{rotate:asin(-20)}"); // evaluates to NaN
6048+
minify_test(".foo { width: asin(sin(45deg))", ".foo{width:asin(sin(45deg))}"); // invalid
6049+
6050+
minify_test(".foo { rotate: acos(cos(45deg))", ".foo{rotate:45deg}");
6051+
minify_test(".foo { rotate: acos(-1)", ".foo{rotate:180deg}");
6052+
minify_test(".foo { rotate: acos(0)", ".foo{rotate:90deg}");
6053+
minify_test(".foo { rotate: acos(1)", ".foo{rotate:none}");
6054+
minify_test(".foo { rotate: acos(45deg)", ".foo{rotate:acos(45deg)}"); // invalid
6055+
minify_test(".foo { rotate: acos(-20)", ".foo{rotate:acos(-20)}"); // evaluates to NaN
6056+
6057+
minify_test(".foo { rotate: atan(tan(45deg))", ".foo{rotate:45deg}");
6058+
minify_test(".foo { rotate: atan(1)", ".foo{rotate:45deg}");
6059+
minify_test(".foo { rotate: atan(0)", ".foo{rotate:none}");
6060+
minify_test(".foo { rotate: atan(45deg)", ".foo{rotate:atan(45deg)}"); // invalid
6061+
}
6062+
60156063
#[test]
60166064
fn test_box_shadow() {
60176065
minify_test(

src/values/angle.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ impl<'i> Parse<'i> for Angle {
3939
match input.try_parse(Calc::parse) {
4040
Ok(Calc::Value(v)) => return Ok(*v),
4141
// Angles are always compatible, so they will always compute to a value.
42-
Ok(_) => unreachable!(),
42+
Ok(_) => return Err(input.new_custom_error(ParserError::InvalidValue)),
4343
_ => {}
4444
}
4545

@@ -222,3 +222,16 @@ impl std::ops::Rem for Angle {
222222
/// A CSS [`<angle-percentage>`](https://www.w3.org/TR/css-values-4/#typedef-angle-percentage) value.
223223
/// May be specified as either an angle or a percentage that resolves to an angle.
224224
pub type AnglePercentage = DimensionPercentage<Angle>;
225+
226+
macro_rules! impl_try_from_angle {
227+
($t: ty) => {
228+
impl TryFrom<crate::values::angle::Angle> for $t {
229+
type Error = ();
230+
fn try_from(_: crate::values::angle::Angle) -> Result<Self, Self::Error> {
231+
Err(())
232+
}
233+
}
234+
};
235+
}
236+
237+
pub(crate) use impl_try_from_angle;

src/values/calc.rs

Lines changed: 73 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use crate::traits::private::AddInternal;
88
use crate::traits::{Parse, ToCss};
99
use cssparser::*;
1010

11+
use super::angle::Angle;
1112
use super::number::CSSNumber;
1213

1314
/// A CSS [math function](https://www.w3.org/TR/css-values-4/#math-function).
@@ -203,6 +204,36 @@ pub enum Calc<V> {
203204
Function(Box<MathFunction<V>>),
204205
}
205206

207+
enum_property! {
208+
/// A mathematical constant.
209+
pub enum Constant {
210+
/// The base of the natural logarithm
211+
"e": E,
212+
/// The ratio of a circle’s circumference to its diameter
213+
"pi": Pi,
214+
/// infinity
215+
"infinity": Infinity,
216+
/// -infinity
217+
"-infinity": NegativeInfinity,
218+
/// Not a number.
219+
"nan": Nan,
220+
}
221+
}
222+
223+
impl Into<f32> for Constant {
224+
fn into(self) -> f32 {
225+
use std::f32::consts;
226+
use Constant::*;
227+
match self {
228+
E => consts::E,
229+
Pi => consts::PI,
230+
Infinity => f32::INFINITY,
231+
NegativeInfinity => -f32::INFINITY,
232+
Nan => f32::NAN,
233+
}
234+
}
235+
}
236+
206237
impl<
207238
'i,
208239
V: Parse<'i>
@@ -211,8 +242,9 @@ impl<
211242
+ TryRound
212243
+ TryRem
213244
+ std::cmp::PartialOrd<V>
214-
+ std::convert::Into<Calc<V>>
215-
+ std::convert::From<Calc<V>>
245+
+ Into<Calc<V>>
246+
+ From<Calc<V>>
247+
+ TryFrom<Angle>
216248
+ Clone
217249
+ std::fmt::Debug,
218250
> Parse<'i> for Calc<V>
@@ -353,7 +385,13 @@ impl<
353385
Ok(Calc::Function(Box::new(MathFunction::Rem(a, b))))
354386
})
355387
},
356-
_ => Err(location.new_unexpected_token_error(Token::Ident(f.clone()))),
388+
"sin" => Self::parse_trig(input, f32::sin, false),
389+
"cos" => Self::parse_trig(input, f32::cos, false),
390+
"tan" => Self::parse_trig(input, f32::tan, false),
391+
"asin" => Self::parse_trig(input, f32::asin, true),
392+
"acos" => Self::parse_trig(input, f32::acos, true),
393+
"atan" => Self::parse_trig(input, f32::atan, true),
394+
_ => Err(location.new_unexpected_token_error(Token::Ident(f.clone()))),
357395
}
358396
}
359397
}
@@ -366,8 +404,9 @@ impl<
366404
+ TryRound
367405
+ TryRem
368406
+ std::cmp::PartialOrd<V>
369-
+ std::convert::Into<Calc<V>>
370-
+ std::convert::From<Calc<V>>
407+
+ Into<Calc<V>>
408+
+ From<Calc<V>>
409+
+ TryFrom<Angle>
371410
+ Clone
372411
+ std::fmt::Debug,
373412
> Calc<V>
@@ -464,6 +503,10 @@ impl<
464503
return Ok(Calc::Number(num));
465504
}
466505

506+
if let Ok(constant) = input.try_parse(Constant::parse) {
507+
return Ok(Calc::Number(constant.into()));
508+
}
509+
467510
if let Ok(value) = input.try_parse(V::parse) {
468511
return Ok(Calc::Value(Box::new(value)));
469512
}
@@ -507,6 +550,31 @@ impl<
507550
}
508551
reduced
509552
}
553+
554+
fn parse_trig<'t, F: FnOnce(f32) -> f32>(
555+
input: &mut Parser<'i, 't>,
556+
f: F,
557+
to_angle: bool,
558+
) -> Result<Self, ParseError<'i, ParserError<'i>>> {
559+
input.parse_nested_block(|input| {
560+
let v: Calc<Angle> = Calc::parse_sum(input)?;
561+
let rad = match v {
562+
Calc::Value(angle) if !to_angle => f(angle.to_radians()),
563+
Calc::Number(v) => f(v),
564+
_ => return Err(input.new_custom_error(ParserError::InvalidValue)),
565+
};
566+
567+
if to_angle && !rad.is_nan() {
568+
if let Ok(v) = V::try_from(Angle::Rad(rad)) {
569+
return Ok(Calc::Value(Box::new(v)));
570+
} else {
571+
return Err(input.new_custom_error(ParserError::InvalidValue));
572+
}
573+
} else {
574+
Ok(Calc::Number(rad))
575+
}
576+
})
577+
}
510578
}
511579

512580
impl<V: std::ops::Mul<f32, Output = V>> std::ops::Mul<f32> for Calc<V> {

src/values/length.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! CSS length values.
22
3+
use super::angle::impl_try_from_angle;
34
use super::calc::{Calc, MathFunction, Round, RoundingStrategy, TryRem, TryRound};
45
use super::number::CSSNumber;
56
use super::percentage::DimensionPercentage;
@@ -243,6 +244,8 @@ macro_rules! define_length_units {
243244
}
244245
}
245246
}
247+
248+
impl_try_from_angle!(LengthValue);
246249
};
247250
}
248251

@@ -682,6 +685,8 @@ impl TryRem for Length {
682685
}
683686
}
684687

688+
impl_try_from_angle!(Length);
689+
685690
/// Either a [`<length>`](https://www.w3.org/TR/css-values-4/#lengths) or a [`<number>`](https://www.w3.org/TR/css-values-4/#numbers).
686691
#[derive(Debug, Clone, PartialEq)]
687692
#[cfg_attr(

src/values/number.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! CSS number values.
22
3+
use super::angle::impl_try_from_angle;
34
use super::calc::{Calc, Round, RoundingStrategy};
45
use crate::error::{ParserError, PrinterError};
56
use crate::printer::Printer;
@@ -19,7 +20,7 @@ impl<'i> Parse<'i> for CSSNumber {
1920
Ok(Calc::Value(v)) => return Ok(*v),
2021
Ok(Calc::Number(n)) => return Ok(n),
2122
// Numbers are always compatible, so they will always compute to a value.
22-
Ok(_) => unreachable!(),
23+
Ok(_) => return Err(input.new_custom_error(ParserError::InvalidValue)),
2324
_ => {}
2425
}
2526

@@ -83,6 +84,8 @@ impl Round for CSSNumber {
8384
}
8485
}
8586

87+
impl_try_from_angle!(CSSNumber);
88+
8689
/// A CSS [`<integer>`](https://www.w3.org/TR/css-values-4/#integers) value.
8790
pub type CSSInteger = i32;
8891

src/values/percentage.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! CSS percentage values.
22
3+
use super::angle::{impl_try_from_angle, Angle};
34
use super::calc::{Calc, MathFunction, Round, RoundingStrategy, TryRem, TryRound};
45
use super::number::CSSNumber;
56
use crate::error::{ParserError, PrinterError};
@@ -131,6 +132,8 @@ impl std::ops::Rem for Percentage {
131132
}
132133
}
133134

135+
impl_try_from_angle!(Percentage);
136+
134137
/// Either a `<number>` or `<percentage>`.
135138
#[derive(Debug, Clone, PartialEq)]
136139
#[cfg_attr(
@@ -219,7 +222,8 @@ impl<
219222
+ std::cmp::PartialEq<CSSNumber>
220223
+ std::cmp::PartialOrd<CSSNumber>
221224
+ std::cmp::PartialOrd<D>
222-
+ std::fmt::Debug,
225+
+ std::fmt::Debug
226+
+ TryFrom<Angle>,
223227
> Parse<'i> for DimensionPercentage<D>
224228
{
225229
fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
@@ -462,6 +466,14 @@ impl<D: TryRem> TryRem for DimensionPercentage<D> {
462466
}
463467
}
464468

469+
impl<E, D: TryFrom<Angle, Error = E>> TryFrom<Angle> for DimensionPercentage<D> {
470+
type Error = E;
471+
472+
fn try_from(value: Angle) -> Result<Self, Self::Error> {
473+
Ok(DimensionPercentage::Dimension(D::try_from(value)?))
474+
}
475+
}
476+
465477
impl<
466478
D: ToCss + std::cmp::PartialOrd<CSSNumber> + std::ops::Mul<CSSNumber, Output = D> + Clone + std::fmt::Debug,
467479
> ToCss for DimensionPercentage<D>

src/values/time.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! CSS time values.
22
3+
use super::angle::impl_try_from_angle;
34
use super::calc::{Calc, Round, RoundingStrategy};
45
use super::number::CSSNumber;
56
use crate::error::{ParserError, PrinterError};
@@ -41,7 +42,7 @@ impl<'i> Parse<'i> for Time {
4142
match input.try_parse(Calc::parse) {
4243
Ok(Calc::Value(v)) => return Ok(*v),
4344
// Time is always compatible, so they will always compute to a value.
44-
Ok(_) => unreachable!(),
45+
Ok(_) => return Err(input.new_custom_error(ParserError::InvalidValue)),
4546
_ => {}
4647
}
4748

@@ -179,3 +180,5 @@ impl std::ops::Rem for Time {
179180
}
180181
}
181182
}
183+
184+
impl_try_from_angle!(Time);

0 commit comments

Comments
 (0)