Skip to content

Commit d1a02ae

Browse files
committed
Implement rem() and mod() functions
1 parent 2efd326 commit d1a02ae

File tree

6 files changed

+158
-2
lines changed

6 files changed

+158
-2
lines changed

src/lib.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5996,6 +5996,19 @@ mod tests {
59965996
);
59975997
minify_test(".foo { margin: round(to-zero, -23px, 5px) }", ".foo{margin:-20px}");
59985998
minify_test(".foo { margin: round(nearest, -23px, 5px) }", ".foo{margin:-25px}");
5999+
minify_test(".foo { width: rem(18px, 5px) }", ".foo{width:3px}");
6000+
minify_test(".foo { width: rem(-18px, 5px) }", ".foo{width:-3px}");
6001+
minify_test(".foo { width: rem(18px, 5vw) }", ".foo{width:rem(18px,5vw)}");
6002+
minify_test(".foo { rotate: rem(-140deg, -90deg) }", ".foo{rotate:-50deg}");
6003+
minify_test(".foo { rotate: rem(140deg, -90deg) }", ".foo{rotate:50deg}");
6004+
minify_test(".foo { width: mod(18px, 5px) }", ".foo{width:3px}");
6005+
minify_test(".foo { width: mod(-18px, 5px) }", ".foo{width:2px}");
6006+
minify_test(".foo { rotate: mod(-140deg, -90deg) }", ".foo{rotate:-50deg}");
6007+
minify_test(".foo { rotate: mod(140deg, -90deg) }", ".foo{rotate:-40deg}");
6008+
minify_test(
6009+
".foo { transform: rotateX(mod(140deg, -90deg)) rotateY(rem(140deg, -90deg)) }",
6010+
".foo{transform:rotateX(-40deg)rotateY(50deg)}",
6011+
);
59996012
}
60006013

60016014
#[test]

src/values/angle.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,20 @@ impl Round for Angle {
205205
}
206206
}
207207

208+
impl std::ops::Rem for Angle {
209+
type Output = Angle;
210+
211+
fn rem(self, rhs: Self) -> Self::Output {
212+
match (self, rhs) {
213+
(Angle::Deg(a), Angle::Deg(b)) => Angle::Deg(a % b),
214+
(Angle::Rad(a), Angle::Rad(b)) => Angle::Rad(a % b),
215+
(Angle::Grad(a), Angle::Grad(b)) => Angle::Grad(a % b),
216+
(Angle::Turn(a), Angle::Turn(b)) => Angle::Turn(a % b),
217+
(a, b) => Angle::Deg(a.to_degrees() % b.to_degrees()),
218+
}
219+
}
220+
}
221+
208222
/// A CSS [`<angle-percentage>`](https://www.w3.org/TR/css-values-4/#typedef-angle-percentage) value.
209223
/// May be specified as either an angle or a percentage that resolves to an angle.
210224
pub type AnglePercentage = DimensionPercentage<Angle>;

src/values/calc.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ pub enum MathFunction<V> {
3131
Clamp(Calc<V>, Calc<V>, Calc<V>),
3232
/// The [`round()`](https://www.w3.org/TR/css-values-4/#funcdef-round) function.
3333
Round(RoundingStrategy, Calc<V>, Calc<V>),
34+
/// The [`rem()`](https://www.w3.org/TR/css-values-4/#funcdef-rem) function.
35+
Rem(Calc<V>, Calc<V>),
36+
/// The [`mod()`](https://www.w3.org/TR/css-values-4/#funcdef-mod) function.
37+
Mod(Calc<V>, Calc<V>),
3438
}
3539

3640
enum_property! {
@@ -72,6 +76,18 @@ impl<T: Round> TryRound for T {
7276
}
7377
}
7478

79+
/// A trait for values that potentially support a remainder (e.g. if they have the same unit).
80+
pub trait TryRem: Sized {
81+
/// Returns the remainder between two values, if possible.
82+
fn try_rem(&self, rhs: &Self) -> Option<Self>;
83+
}
84+
85+
impl<T: std::ops::Rem<T, Output = T> + Clone> TryRem for T {
86+
fn try_rem(&self, rhs: &Self) -> Option<Self> {
87+
Some(self.clone() % rhs.clone())
88+
}
89+
}
90+
7591
impl<V: ToCss + std::cmp::PartialOrd<f32> + std::ops::Mul<f32, Output = V> + Clone + std::fmt::Debug> ToCss
7692
for MathFunction<V>
7793
{
@@ -146,6 +162,20 @@ impl<V: ToCss + std::cmp::PartialOrd<f32> + std::ops::Mul<f32, Output = V> + Clo
146162
b.to_css(dest)?;
147163
dest.write_char(')')
148164
}
165+
MathFunction::Rem(a, b) => {
166+
dest.write_str("rem(")?;
167+
a.to_css(dest)?;
168+
dest.delim(',', false)?;
169+
b.to_css(dest)?;
170+
dest.write_char(')')
171+
}
172+
MathFunction::Mod(a, b) => {
173+
dest.write_str("mod(")?;
174+
a.to_css(dest)?;
175+
dest.delim(',', false)?;
176+
b.to_css(dest)?;
177+
dest.write_char(')')
178+
}
149179
}
150180
}
151181
}
@@ -179,9 +209,11 @@ impl<
179209
+ std::ops::Mul<f32, Output = V>
180210
+ AddInternal
181211
+ TryRound
212+
+ TryRem
182213
+ std::cmp::PartialOrd<V>
183214
+ std::convert::Into<Calc<V>>
184215
+ std::convert::From<Calc<V>>
216+
+ Clone
185217
+ std::fmt::Debug,
186218
> Parse<'i> for Calc<V>
187219
{
@@ -289,6 +321,38 @@ impl<
289321
Ok(Calc::Function(Box::new(MathFunction::Round(strategy, a, b))))
290322
})
291323
},
324+
"rem" => {
325+
input.parse_nested_block(|input| {
326+
let a: Calc<V> = Calc::parse_sum(input)?;
327+
input.expect_comma()?;
328+
let b: Calc<V> = Calc::parse_sum(input)?;
329+
330+
if let (Calc::Value(a), Calc::Value(b)) = (&a, &b) {
331+
if let Some(rem) = a.try_rem(&**b) {
332+
return Ok(Calc::Value(Box::new(rem)))
333+
}
334+
}
335+
336+
Ok(Calc::Function(Box::new(MathFunction::Rem(a, b))))
337+
})
338+
},
339+
"mod" => {
340+
input.parse_nested_block(|input| {
341+
let a: Calc<V> = Calc::parse_sum(input)?;
342+
input.expect_comma()?;
343+
let b: Calc<V> = Calc::parse_sum(input)?;
344+
345+
if let (Calc::Value(a), Calc::Value(b)) = (&a, &b) {
346+
// ((a % b) + b) % b
347+
if let Some(rem) = a.try_rem(&**b) {
348+
let rem = rem.add((**b).clone()).try_rem(b).unwrap();
349+
return Ok(Calc::Value(Box::new(rem)))
350+
}
351+
}
352+
353+
Ok(Calc::Function(Box::new(MathFunction::Rem(a, b))))
354+
})
355+
},
292356
_ => Err(location.new_unexpected_token_error(Token::Ident(f.clone()))),
293357
}
294358
}
@@ -300,9 +364,11 @@ impl<
300364
+ std::ops::Mul<f32, Output = V>
301365
+ AddInternal
302366
+ TryRound
367+
+ TryRem
303368
+ std::cmp::PartialOrd<V>
304369
+ std::convert::Into<Calc<V>>
305370
+ std::convert::From<Calc<V>>
371+
+ Clone
306372
+ std::fmt::Debug,
307373
> Calc<V>
308374
{

src/values/length.rs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! CSS length values.
22
3-
use super::calc::{Calc, MathFunction, Round, RoundingStrategy, TryRound};
3+
use super::calc::{Calc, MathFunction, Round, RoundingStrategy, TryRem, TryRound};
44
use super::number::CSSNumber;
55
use super::percentage::DimensionPercentage;
66
use crate::error::{ParserError, PrinterError};
@@ -225,6 +225,24 @@ macro_rules! define_length_units {
225225
}
226226
}
227227
}
228+
229+
impl TryRem for LengthValue {
230+
fn try_rem(&self, rhs: &Self) -> Option<Self> {
231+
use LengthValue::*;
232+
match (self, rhs) {
233+
$(
234+
($name(a), $name(b)) => Some($name(a % b)),
235+
)+
236+
(a, b) => {
237+
if let (Some(a), Some(b)) = (a.to_px(), b.to_px()) {
238+
Some(Px(a % b))
239+
} else {
240+
None
241+
}
242+
}
243+
}
244+
}
245+
}
228246
};
229247
}
230248

@@ -655,6 +673,15 @@ impl TryRound for Length {
655673
}
656674
}
657675

676+
impl TryRem for Length {
677+
fn try_rem(&self, rhs: &Self) -> Option<Self> {
678+
match (self, rhs) {
679+
(Length::Value(a), Length::Value(b)) => a.try_rem(b).map(Length::Value),
680+
_ => None,
681+
}
682+
}
683+
}
684+
658685
/// Either a [`<length>`](https://www.w3.org/TR/css-values-4/#lengths) or a [`<number>`](https://www.w3.org/TR/css-values-4/#numbers).
659686
#[derive(Debug, Clone, PartialEq)]
660687
#[cfg_attr(

src/values/percentage.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! CSS percentage values.
22
3-
use super::calc::{Calc, MathFunction, Round, RoundingStrategy, TryRound};
3+
use super::calc::{Calc, MathFunction, Round, RoundingStrategy, TryRem, TryRound};
44
use super::number::CSSNumber;
55
use crate::error::{ParserError, PrinterError};
66
use crate::printer::Printer;
@@ -123,6 +123,14 @@ impl Round for Percentage {
123123
}
124124
}
125125

126+
impl std::ops::Rem for Percentage {
127+
type Output = Percentage;
128+
129+
fn rem(self, rhs: Self) -> Self::Output {
130+
Percentage(self.0 % rhs.0)
131+
}
132+
}
133+
126134
/// Either a `<number>` or `<percentage>`.
127135
#[derive(Debug, Clone, PartialEq)]
128136
#[cfg_attr(
@@ -207,6 +215,7 @@ impl<
207215
+ TryAdd<D>
208216
+ Clone
209217
+ TryRound
218+
+ TryRem
210219
+ std::cmp::PartialEq<CSSNumber>
211220
+ std::cmp::PartialOrd<CSSNumber>
212221
+ std::cmp::PartialOrd<D>
@@ -439,6 +448,20 @@ impl<D: TryRound> TryRound for DimensionPercentage<D> {
439448
}
440449
}
441450

451+
impl<D: TryRem> TryRem for DimensionPercentage<D> {
452+
fn try_rem(&self, rhs: &Self) -> Option<Self> {
453+
match (self, rhs) {
454+
(DimensionPercentage::Dimension(a), DimensionPercentage::Dimension(b)) => {
455+
a.try_rem(b).map(DimensionPercentage::Dimension)
456+
}
457+
(DimensionPercentage::Percentage(a), DimensionPercentage::Percentage(b)) => {
458+
Some(DimensionPercentage::Percentage(a.clone() % b.clone()))
459+
}
460+
_ => None,
461+
}
462+
}
463+
}
464+
442465
impl<
443466
D: ToCss + std::cmp::PartialOrd<CSSNumber> + std::ops::Mul<CSSNumber, Output = D> + Clone + std::fmt::Debug,
444467
> ToCss for DimensionPercentage<D>

src/values/time.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,3 +166,16 @@ impl Round for Time {
166166
}
167167
}
168168
}
169+
170+
impl std::ops::Rem for Time {
171+
type Output = Time;
172+
173+
fn rem(self, rhs: Self) -> Self::Output {
174+
match (self, rhs) {
175+
(Time::Seconds(a), Time::Seconds(b)) => Time::Seconds(a % b),
176+
(Time::Milliseconds(a), Time::Milliseconds(b)) => Time::Milliseconds(a % b),
177+
(Time::Seconds(a), Time::Milliseconds(b)) => Time::Seconds(a % (b / 1000.0)),
178+
(Time::Milliseconds(a), Time::Seconds(b)) => Time::Seconds(a % (b * 1000.0)),
179+
}
180+
}
181+
}

0 commit comments

Comments
 (0)