Skip to content

Commit 16aa687

Browse files
committed
Add calc support for angle values
1 parent 716dbc7 commit 16aa687

File tree

6 files changed

+255
-111
lines changed

6 files changed

+255
-111
lines changed

src/lib.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1570,5 +1570,41 @@ mod tests {
15701570
".foo{transform:translateX(2in) translateX(50px)}",
15711571
".foo{transform:translate(242px)}"
15721572
);
1573+
minify_test(
1574+
".foo{transform:translateX(calc(2in + 50px))}",
1575+
".foo{transform:translate(242px)}"
1576+
);
1577+
minify_test(
1578+
".foo{transform:translateX(50%)}",
1579+
".foo{transform:translate(50%)}"
1580+
);
1581+
minify_test(
1582+
".foo{transform:translateX(calc(50% - 100px + 20px))}",
1583+
".foo{transform:translate(calc(50% - 80px))}"
1584+
);
1585+
minify_test(
1586+
".foo{transform:rotate(calc(10deg + 20deg))}",
1587+
".foo{transform:rotate(30deg)}"
1588+
);
1589+
minify_test(
1590+
".foo{transform:rotate(calc(10deg + 0.349066rad))}",
1591+
".foo{transform:rotate(30deg)}"
1592+
);
1593+
minify_test(
1594+
".foo{transform:rotate(calc(10deg + 1.5turn))}",
1595+
".foo{transform:rotate(550deg)}"
1596+
);
1597+
minify_test(
1598+
".foo{transform:rotate(calc(10deg * 2))}",
1599+
".foo{transform:rotate(20deg)}"
1600+
);
1601+
minify_test(
1602+
".foo{transform:rotate(calc(-10deg * 2))}",
1603+
".foo{transform:rotate(-20deg)}"
1604+
);
1605+
minify_test(
1606+
".foo{transform:rotate(calc(10deg + var(--test)))}",
1607+
".foo{transform:rotate(calc(10deg + var(--test)))}"
1608+
);
15731609
}
15741610
}

src/properties/font.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
use cssparser::*;
22
use crate::macros::*;
3-
use crate::values::length::{LengthPercentage, Angle, Percentage};
3+
use crate::values::{
4+
angle::Angle,
5+
length::{LengthPercentage, Percentage}
6+
};
47
use crate::traits::{Parse, ToCss, PropertyHandler};
58
use super::Property;
69
use crate::printer::Printer;
710
use std::fmt::Write;
8-
use smallvec::SmallVec;
911

1012
/// https://www.w3.org/TR/2021/WD-css-fonts-4-20210729/#font-weight-prop
1113
#[derive(Debug, Clone, PartialEq)]

src/properties/transform.rs

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
use cssparser::*;
22
use crate::traits::{Parse, ToCss};
3-
use crate::values::length::{LengthPercentage, NumberOrPercentage, Length, Unit, Angle};
3+
use crate::values::{
4+
angle::Angle,
5+
length::{LengthPercentage, NumberOrPercentage, Length, Unit}
6+
};
47
use crate::printer::Printer;
58
use std::fmt::Write;
69

@@ -1093,25 +1096,39 @@ impl Transform {
10931096
return Some(Matrix3d::scale(x.into(), y.into(), z.into()))
10941097
}
10951098
Transform::Rotate(angle) | Transform::RotateZ(angle) => {
1096-
return Some(Matrix3d::rotate(0.0, 0.0, 1.0, angle.to_radians()))
1099+
if let Some(a) = angle.to_radians() {
1100+
return Some(Matrix3d::rotate(0.0, 0.0, 1.0, a))
1101+
}
10971102
}
10981103
Transform::RotateX(angle) => {
1099-
return Some(Matrix3d::rotate(1.0, 0.0, 0.0, angle.to_radians()))
1104+
if let Some(a) = angle.to_radians() {
1105+
return Some(Matrix3d::rotate(1.0, 0.0, 0.0, a))
1106+
}
11001107
}
11011108
Transform::RotateY(angle) => {
1102-
return Some(Matrix3d::rotate(0.0, 1.0, 0.0, angle.to_radians()))
1109+
if let Some(a) = angle.to_radians() {
1110+
return Some(Matrix3d::rotate(0.0, 1.0, 0.0, a))
1111+
}
11031112
}
11041113
Transform::Rotate3d(x, y, z, angle) => {
1105-
return Some(Matrix3d::rotate(*x, *y, *z, angle.to_radians()))
1114+
if let Some(a) = angle.to_radians() {
1115+
return Some(Matrix3d::rotate(*x, *y, *z, a))
1116+
}
11061117
}
11071118
Transform::Skew(x, y) => {
1108-
return Some(Matrix3d::skew(x.to_radians(), y.to_radians()))
1119+
if let (Some(x), Some(y)) = (x.to_radians(), y.to_radians()) {
1120+
return Some(Matrix3d::skew(x, y))
1121+
}
11091122
}
11101123
Transform::SkewX(x) => {
1111-
return Some(Matrix3d::skew(x.to_radians(), 0.0))
1124+
if let Some(x) = x.to_radians() {
1125+
return Some(Matrix3d::skew(x, 0.0))
1126+
}
11121127
}
11131128
Transform::SkewY(y) => {
1114-
return Some(Matrix3d::skew(0.0, y.to_radians()))
1129+
if let Some(y) = y.to_radians() {
1130+
return Some(Matrix3d::skew(0.0, y))
1131+
}
11151132
}
11161133
Transform::Perspective(len) => {
11171134
if let Some(len) = len.to_px() {

src/values/angle.rs

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
use cssparser::*;
2+
use crate::traits::{Parse, ToCss};
3+
use crate::printer::Printer;
4+
use std::fmt::Write;
5+
use super::calc::Calc;
6+
use std::f32::consts::PI;
7+
8+
#[derive(Debug, Clone, PartialEq)]
9+
pub enum Angle {
10+
Deg(f32),
11+
Grad(f32),
12+
Rad(f32),
13+
Turn(f32),
14+
Calc(Calc<Angle>)
15+
}
16+
17+
impl Parse for Angle {
18+
fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ()>> {
19+
let location = input.current_source_location();
20+
let token = input.next()?;
21+
match *token {
22+
Token::Dimension { value, ref unit, .. } => {
23+
match_ignore_ascii_case! { unit,
24+
"deg" => Ok(Angle::Deg(value)),
25+
"grad" => Ok(Angle::Grad(value)),
26+
"turn" => Ok(Angle::Turn(value)),
27+
"rad" => Ok(Angle::Rad(value)),
28+
_ => return Err(location.new_unexpected_token_error(token.clone())),
29+
}
30+
},
31+
Token::Function(ref name) => {
32+
match_ignore_ascii_case! { name,
33+
"calc" => {
34+
match Calc::parse(input)? {
35+
Calc::Value(v) => Ok(*v),
36+
v => Ok(Angle::Calc(v))
37+
}
38+
},
39+
_ => Err(input.new_error(BasicParseErrorKind::QualifiedRuleInvalid))
40+
}
41+
}
42+
ref token => return Err(location.new_unexpected_token_error(token.clone())),
43+
}
44+
}
45+
}
46+
47+
impl ToCss for Angle {
48+
fn to_css<W>(&self, dest: &mut Printer<W>) -> std::fmt::Result where W: std::fmt::Write {
49+
let (value, unit) = match self {
50+
Angle::Deg(val) => (*val, "deg"),
51+
Angle::Grad(val) => (*val, "grad"),
52+
Angle::Rad(val) => {
53+
if let Some(deg) = self.to_degrees() {
54+
// We print 5 digits of precision by default.
55+
// Switch to degrees if there are an even number of them.
56+
if (deg * 100000.0).round().fract() == 0.0 {
57+
(deg, "deg")
58+
} else {
59+
(*val, "rad")
60+
}
61+
} else {
62+
(*val, "rad")
63+
}
64+
},
65+
Angle::Turn(val) => (*val, "turn"),
66+
Angle::Calc(calc) => {
67+
if let Calc::Value(v) = calc {
68+
v.to_css(dest)?;
69+
} else {
70+
dest.write_str("calc(")?;
71+
calc.to_css(dest)?;
72+
dest.write_char(')')?;
73+
}
74+
return Ok(())
75+
}
76+
};
77+
78+
use cssparser::ToCss;
79+
let int_value = if value.fract() == 0.0 {
80+
Some(value as i32)
81+
} else {
82+
None
83+
};
84+
let token = Token::Dimension {
85+
has_sign: value < 0.0,
86+
value,
87+
int_value,
88+
unit: CowRcStr::from(unit)
89+
};
90+
if value != 0.0 && value.abs() < 1.0 {
91+
let mut s = String::new();
92+
token.to_css(&mut s)?;
93+
if value < 0.0 {
94+
dest.write_char('-')?;
95+
dest.write_str(s.trim_start_matches("-0"))
96+
} else {
97+
dest.write_str(s.trim_start_matches('0'))
98+
}
99+
} else {
100+
token.to_css(dest)
101+
}
102+
}
103+
}
104+
105+
impl Angle {
106+
pub fn is_zero(&self) -> bool {
107+
use Angle::*;
108+
match self {
109+
Deg(v) | Rad(v) | Grad(v) | Turn(v) => *v == 0.0,
110+
Calc(_) => false
111+
}
112+
}
113+
114+
pub fn to_radians(&self) -> Option<f32> {
115+
const RAD_PER_DEG: f32 = PI / 180.0;
116+
let r = match self {
117+
Angle::Deg(deg) => deg * RAD_PER_DEG,
118+
Angle::Rad(rad) => *rad,
119+
Angle::Grad(grad) => grad * 180.0 / 200.0 * RAD_PER_DEG,
120+
Angle::Turn(turn) => turn * 360.0 * RAD_PER_DEG,
121+
Angle::Calc(_) => return None
122+
};
123+
Some(r)
124+
}
125+
126+
pub fn to_degrees(&self) -> Option<f32> {
127+
const DEG_PER_RAD: f32 = 180.0 / PI;
128+
let d = match self {
129+
Angle::Deg(deg) => *deg,
130+
Angle::Rad(rad) => rad * DEG_PER_RAD,
131+
Angle::Grad(grad) => grad * 180.0 / 200.0,
132+
Angle::Turn(turn) => turn * 360.0,
133+
Angle::Calc(_) => return None
134+
};
135+
Some(d)
136+
}
137+
}
138+
139+
impl std::ops::Mul<f32> for Angle {
140+
type Output = Self;
141+
142+
fn mul(self, other: f32) -> Angle {
143+
match self {
144+
Angle::Deg(v) => Angle::Deg(v * other),
145+
Angle::Rad(v) => Angle::Deg(v * other),
146+
Angle::Grad(v) => Angle::Deg(v * other),
147+
Angle::Turn(v) => Angle::Deg(v * other),
148+
Angle::Calc(c) => Angle::Calc(c * other)
149+
}
150+
}
151+
}
152+
153+
impl std::ops::Add<Angle> for Angle {
154+
type Output = Self;
155+
156+
fn add(self, other: Angle) -> Angle {
157+
match (self, other) {
158+
(Angle::Calc(a), Angle::Calc(b)) => Angle::Calc(a + b),
159+
(Angle::Calc(a), b) => Angle::Calc(a + Calc::Value(Box::new(b))),
160+
(a, Angle::Calc(b)) => Angle::Calc(Calc::Value(Box::new(a)) + b),
161+
(a, b) => Angle::Deg(a.to_degrees().unwrap() + b.to_degrees().unwrap())
162+
}
163+
}
164+
}
165+
166+
impl std::cmp::PartialEq<f32> for Angle {
167+
fn eq(&self, other: &f32) -> bool {
168+
match self {
169+
Angle::Deg(a) | Angle::Rad(a) | Angle::Grad(a) | Angle::Turn(a) => a == other,
170+
Angle::Calc(_) => false
171+
}
172+
}
173+
}
174+
175+
impl std::cmp::PartialOrd<f32> for Angle {
176+
fn partial_cmp(&self, other: &f32) -> Option<std::cmp::Ordering> {
177+
match self {
178+
Angle::Deg(a) | Angle::Rad(a) | Angle::Grad(a) | Angle::Turn(a) => a.partial_cmp(other),
179+
Angle::Calc(_) => None
180+
}
181+
}
182+
}

0 commit comments

Comments
 (0)