diff --git a/src/lib.rs b/src/lib.rs index dc40670a8..bef5265c2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7685,6 +7685,233 @@ mod tests { minify_test(".foo { width: calc(20px + 30px) }", ".foo{width:50px}"); minify_test(".foo { width: calc(20px + 30px + 40px) }", ".foo{width:90px}"); minify_test(".foo { width: calc(100% - 30px) }", ".foo{width:calc(100% - 30px)}"); + + // Test is out of the `i32` range + minify_test(".z-index { z-index: 99999988888 }", ".z-index{z-index:calc(1/0)}"); + minify_test(".z-index { z-index: -99999988888 }", ".z-index{z-index:calc(-1/0)}"); + + // Test in calc() + minify_test(".z-index1 { z-index: calc(10 / 5) }", ".z-index1{z-index:2}"); + minify_test(".z-index2 { z-index: calc(-10 / 5) }", ".z-index2{z-index:-2}"); + minify_test(".z-index3 { z-index: calc(10 / 3) }", ".z-index3{z-index:3}"); + minify_test(".z-index4 { z-index: calc(11 / 3) }", ".z-index4{z-index:4}"); + minify_test(".z-index5 { z-index: calc(-11 / 3) }", ".z-index5{z-index:-4}"); + minify_test(".order1 { order: calc(5 * 2 - 1) }", ".order1{order:9}"); + minify_test(".order2 { order: calc( calc(8 - 2) / -2 ) }", ".order2{order:-3}"); + minify_test(".order3 { order: calc(-6 + calc(2 * 3)) }", ".order3{order:0}"); + minify_test(".order4 { order: calc(0) }", ".order4{order:0}"); + minify_test(".order5 { order: calc(-0) }", ".order5{order:0}"); + minify_test(".order6 { order: calc(0.5) }", ".order6{order:1}"); + minify_test(".order7 { order: calc(-5 * 0) }", ".order7{order:0}"); + + // Test Infinity + // i32: calc(1/0) = 2147483647 = 2^31 - 1 + // i32: calc(-1/0) = -2147483648 = -2^31 + // Spec: https://drafts.csswg.org/css-values-4/#calc-ieee + minify_test(".infinity1 { z-index: calc(9/0) }", ".infinity1{z-index:calc(1/0)}"); + minify_test( + ".infinity2 { z-index: calc(0.0002/0) }", + ".infinity2{z-index:calc(1/0)}", + ); + minify_test(".infinity3 { z-index: calc(-1/0) }", ".infinity3{z-index:calc(-1/0)}"); + minify_test( + ".infinity3-1 { z-index: calc(1/-0) }", + ".infinity3-1{z-index:calc(-1/0)}", + ); + minify_test(".infinity3-2 { z-index: calc(0/-0) }", ".infinity3-2{z-index:0}"); + minify_test(".infinity3-3 { z-index: calc(-0/-0) }", ".infinity3-3{z-index:0}"); + minify_test( + ".infinity3-4 { z-index: calc(-0/0) }", // NaN + ".infinity3-4{z-index:0}", + ); + minify_test( + ".infinity3-5 { z-index: calc(-1/-0) }", + ".infinity3-5{z-index:calc(1/0)}", + ); + minify_test( + ".infinity4 { z-index: calc(1 * infinity) }", + ".infinity4{z-index:calc(1/0)}", + ); + minify_test( + ".infinity5 { z-index: calc(-1 * infinity) }", + ".infinity5{z-index:calc(-1/0)}", + ); + minify_test(".infinity6 { z-index: calc(1 / -infinity) }", ".infinity6{z-index:0}"); + minify_test( + ".infinity7 { z-index: calc(1 - infinity) }", + ".infinity7{z-index:calc(-1/0)}", + ); + minify_test( + ".infinity8 { z-index: calc(infinity - infinity) }", + ".infinity8{z-index:0}", + ); + minify_test( + ".infinity9 { z-index: calc(1 / calc(-5 * 0)) }", + ".infinity9{z-index:calc(-1/0)}", + ); + minify_test( + ".infinity10 { z-index: calc(infinity + infinity) }", + ".infinity10{z-index:calc(1/0)}", + ); + + minify_test( + ".infinity11-0 { z-index: calc(2147483 - 1) }", + ".infinity11-0{z-index:2147482}", + ); + // TODO: Using the double type (f64) in Chrome can prevent precision loss. + // We currently align with Firefox, 2147483647 - 65 = 2147483520 + minify_test( + ".infinity11-1 { z-index: calc(2147483647 - 65) }", + ".infinity11-1{z-index:2147483520}", // Chrome: 2147483582, Firefox: 2147483520 + ); + minify_test( + ".infinity11-2 { z-index: calc(-2147483647 + 1) }", + ".infinity11-2{z-index:calc(-1/0)}", // Chrome: -2147483646, Firefox: -2147483648 + ); + minify_test( + ".infinity11-3 { z-index: calc(2147483647 + 1) }", + ".infinity11-3{z-index:calc(1/0)}", + ); + minify_test( + ".infinity11-4 { z-index: calc(-2147483647 - 1) }", + ".infinity11-4{z-index:calc(-1/0)}", + ); + minify_test( + ".infinity11-5 { z-index: calc(2147483646 + infinity) }", + ".infinity11-5{z-index:calc(1/0)}", + ); + minify_test( + ".infinity11-6 { z-index: calc(2147483647 + 2 - 1) }", + ".infinity11-6{z-index:calc(1/0)}", + ); + minify_test( + ".infinity11-7 { z-index: calc(1 - 2147483649) }", + ".infinity11-7{z-index:calc(-1/0)}", // Negative overflow: 1 + (-2147483649) = -2147483648 + ); + minify_test( + ".infinity11-8 { z-index: 2147483647 }", + ".infinity11-8{z-index:calc(1/0)}", + ); + minify_test( + ".infinity12-1 { z-index: calc(calc(1/0) + infinity) }", + ".infinity12-1{z-index:calc(1/0)}", + ); + minify_test( + ".infinity12-2 { z-index: calc(calc(1/0) - infinity) }", + ".infinity12-2{z-index:0}", + ); + + minify_test( + ".infinity-order-1 { order: calc(infinity / infinity) }", + ".infinity-order-1{order:0}", + ); + minify_test( + ".infinity-order-2 { order: calc(infinity / 0) }", + ".infinity-order-2{order:calc(1/0)}", + ); + minify_test( + ".infinity-order-3 { order: calc(infinity + 888) }", + ".infinity-order-3{order:calc(1/0)}", + ); + minify_test( + ".infinity-order-4 { order: calc(infinity - 888) }", + ".infinity-order-4{order:calc(1/0)}", + ); + minify_test( + ".infinity-steps-1 { transition-timing-function: steps(calc(infinity), jump-start) }", + ".infinity-steps-1{transition-timing-function:steps(calc(1/0),start)}", + ); + minify_test( + ".infinity-steps-2 { transition: steps(calc(infinity), jump-start) }", + ".infinity-steps-2{transition:all steps(calc(1/0),start)}", + ); + + // Test Infinity - division by zero should preserve sign + minify_test( + ".infinity-dim1 { width: calc(100px / 0) }", + ".infinity-dim1{width:calc(100px/0)}", + ); + minify_test( + ".infinity-dim2 { width: calc(100px / -0) }", + ".infinity-dim2{width:calc(100px/-0)}", + ); + minify_test( + ".infinity-dim3 { width: calc(-100px / 0) }", + ".infinity-dim3{width:calc(-100px/0)}", + ); + minify_test( + ".infinity-dim4 { width: calc(-100px / -0) }", + ".infinity-dim4{width:calc(-100px/-0)}", + ); + minify_test( + ".infinity-dim5 { width: calc(-0px); height: calc(-0); }", + ".infinity-dim5{width:0;height:0}", + ); + + // Test Infinity + minify_test(".number1 { line-height: calc(9/0) }", ".number1{line-height:calc(1/0)}"); + minify_test( + ".number2 { line-height: calc(0.0002/0) }", + ".number2{line-height:calc(1/0)}", + ); + minify_test( + ".number3 { line-height: calc(-1/0) }", + ".number3{line-height:calc(-1/0)}", + ); + minify_test( + ".number3-1 { line-height: calc(1/-0) }", + ".number3-1{line-height:calc(-1/0)}", + ); + minify_test(".number3-1 { flex: calc(0/-0) }", ".number3-1{flex:0}"); + minify_test(".number4 { flex: calc(1 * infinity) }", ".number4{flex:calc(1/0)}"); + minify_test(".number5 { flex: calc(-1 * infinity) }", ".number5{flex:calc(-1/0)}"); + + // TODO Support orphans prop + // Test orphans = + // minify_test(".orphans1 { orphans: calc(5 + 0.3) }", ".orphans1{orphans:5}"); + // minify_test(".orphans2 { orphans: calc(10 / 3) }", ".orphans2{orphans:3}"); + // minify_test(".orphans3 { orphans: calc(0.3) }", ".orphans3{orphans:1}"); + // minify_test( + // ".orphans1 { orphans: calc(-0.5) }", + // ".orphans1{orphans:1}", + // ); + // minify_test( + // ".orphans2 { orphans: calc(-.5 * 2) }", + // ".orphans2{orphans:-1}", + // ); + // minify_test( + // ".orphans4 { orphans: calc(5 - 10) }", + // ".orphans4{orphans:-5}", + // ); + + // Test infinity repeat() = + minify_test( + ".grid1 { grid-template-rows: repeat( calc(0.5), 1fr ); }", + ".grid1{grid-template-rows:repeat(1,1fr)}", + ); + minify_test( + ".grid2 { grid-template-rows: repeat( calc(10 / 3), 1fr ); }", + ".grid2{grid-template-rows:repeat(3,1fr)}", + ); + minify_test( + ".grid3 { grid-template-rows: repeat( calc(0.5 * 3), 200px ); }", + ".grid3{grid-template-rows:repeat(2,200px)}", + ); + minify_test( + ".grid-mix1 { grid-template-rows: repeat( calc(0.5 * 5 - 1), minmax(calc(100px * 2), 1fr) ); }", + ".grid-mix1{grid-template-rows:repeat(2,minmax(200px,1fr))}", + ); + // end infinity + + minify_test(".mix1 { z-index: calc(10 + 5 * 2 - 3) }", ".mix1{z-index:17}"); + minify_test(".mix2 { z-index: calc(10 * 2 + 5) }", ".mix2{z-index:25}"); + minify_test(".mix3 { z-index: calc(10 * 2 - 5) }", ".mix3{z-index:15}"); + minify_test(".mix4 { z-index: calc((10 + 5) * 2) }", ".mix4{z-index:30}"); + minify_test(".mix5 { order: calc(100 - 50 + 25 - 10) }", ".mix5{order:65}"); + minify_test(".mix6 { z-index: calc(2 * 3 * 4) }", ".mix6{z-index:24}"); + minify_test(".mix7 { z-index: calc(2 * 3 + 4 * 5) }", ".mix7{z-index:26}"); + minify_test(".mix8 { z-index: calc(2 * (3 + 4)) }", ".mix8{z-index:14}"); + minify_test( ".foo { width: calc(100% - 30px + 20px) }", ".foo{width:calc(100% - 10px)}", @@ -7772,7 +7999,8 @@ mod tests { ".foo { width: calc((900px - (10% - 63.5px)) + (2 * 100px)) }", ".foo{width:calc(1163.5px - 10%)}", ); - minify_test(".foo { width: calc(500px/0) }", ".foo{width:calc(500px/0)}"); + minify_test(".foo { margin: calc(500px/0) }", ".foo{margin:calc(500px/0)}"); + minify_test(".foo { margin: calc(-500px/0) }", ".foo{margin:calc(-500px/0)}"); minify_test(".foo { width: calc(500px/2px) }", ".foo{width:calc(500px/2px)}"); minify_test(".foo { width: calc(100% / 3 * 3) }", ".foo{width:100%}"); minify_test(".foo { width: calc(+100px + +100px) }", ".foo{width:200px}"); diff --git a/src/properties/flex.rs b/src/properties/flex.rs index e885c5975..247fc8c4d 100644 --- a/src/properties/flex.rs +++ b/src/properties/flex.rs @@ -357,6 +357,9 @@ impl FromStandard for BoxOrdinalGroup { } } +/// A value for the legacy (prefixed) [box-flex-group](https://www.w3.org/TR/2009/WD-css3-flexbox-20090723/#box-flex-group) property. +pub type BoxFlexGroup = CSSInteger; + // Old flex (2012): https://www.w3.org/TR/2012/WD-css3-flexbox-20120322/ enum_property! { diff --git a/src/properties/grid.rs b/src/properties/grid.rs index 6e7063197..a0e4bfb10 100644 --- a/src/properties/grid.rs +++ b/src/properties/grid.rs @@ -1337,7 +1337,7 @@ impl<'i> Parse<'i> for GridLine<'i> { let ident = input.try_parse(CustomIdent::parse).ok(); (line_number, ident) } else if let Ok(ident) = input.try_parse(CustomIdent::parse) { - let line_number = input.try_parse(CSSInteger::parse).unwrap_or(1); + let line_number = input.try_parse(CSSInteger::parse).unwrap_or(CSSInteger(1)); (line_number, Some(ident)) } else { return Err(input.new_custom_error(ParserError::InvalidDeclaration)); diff --git a/src/properties/position.rs b/src/properties/position.rs index 34a0cf3bf..eafd3b931 100644 --- a/src/properties/position.rs +++ b/src/properties/position.rs @@ -73,7 +73,7 @@ impl ToCss for Position { } /// A value for the [z-index](https://drafts.csswg.org/css2/#z-index) property. -#[derive(Debug, Clone, PartialEq, Parse, ToCss)] +#[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -85,10 +85,34 @@ impl ToCss for Position { pub enum ZIndex { /// The `auto` keyword. Auto, - /// An integer value. + /// An integer value (supports infinity via calc()). Integer(CSSInteger), } +impl<'i> Parse<'i> for ZIndex { + fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { + if input.try_parse(|input| input.expect_ident_matching("auto")).is_ok() { + return Ok(ZIndex::Auto); + } + + // Use CSSInteger::parse which handles calc() and infinity + let integer = CSSInteger::parse(input)?; + Ok(ZIndex::Integer(integer)) + } +} + +impl ToCss for ZIndex { + fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> + where + W: std::fmt::Write, + { + match self { + ZIndex::Auto => dest.write_str("auto"), + ZIndex::Integer(v) => v.to_css(dest), + } + } +} + #[derive(Default)] pub(crate) struct PositionHandler { position: Option, diff --git a/src/rules/font_palette_values.rs b/src/rules/font_palette_values.rs index af06c487f..38d7416f3 100644 --- a/src/rules/font_palette_values.rs +++ b/src/rules/font_palette_values.rs @@ -187,7 +187,7 @@ impl<'i> Parse<'i> for BasePalette { if i.is_negative() { return Err(input.new_custom_error(ParserError::InvalidValue)); } - return Ok(BasePalette::Integer(i as u16)); + return Ok(BasePalette::Integer(*i as u16)); } let location = input.current_source_location(); @@ -208,7 +208,7 @@ impl ToCss for BasePalette { match self { BasePalette::Light => dest.write_str("light"), BasePalette::Dark => dest.write_str("dark"), - BasePalette::Integer(i) => (*i as CSSInteger).to_css(dest), + BasePalette::Integer(i) => CSSInteger(i32::from(*i)).to_css(dest), } } } @@ -226,7 +226,7 @@ impl<'i> Parse<'i> for OverrideColors { } Ok(OverrideColors { - index: index as u16, + index: (*index) as u16, color, }) } @@ -237,7 +237,7 @@ impl ToCss for OverrideColors { where W: std::fmt::Write, { - (self.index as CSSInteger).to_css(dest)?; + CSSInteger(self.index as i32).to_css(dest)?; dest.write_char(' ')?; self.color.to_css(dest) } diff --git a/src/values/calc.rs b/src/values/calc.rs index 6022cf2f4..eaaa3dd25 100644 --- a/src/values/calc.rs +++ b/src/values/calc.rs @@ -601,7 +601,55 @@ impl< node = node * (1.0 / val); continue; } + // Division by zero produces infinity (IEEE-754 semantics) + if val == 0.0 { + // IEEE-754: 0 / -0 = NaN, not infinity + if let Calc::Number(node_val) = node { + if node_val == 0.0 { + // 0 / 0 or 0 / -0 = NaN + node = Calc::Number(f32::NAN); + continue; + } + // Number / 0 = infinity + let infinity = if val.is_sign_positive() { + if node.is_sign_negative() { + Calc::Number(-f32::INFINITY) + } else { + Calc::Number(f32::INFINITY) + } + } else { + if node.is_sign_negative() { + Calc::Number(f32::INFINITY) + } else { + Calc::Number(-f32::INFINITY) + } + }; + node = infinity; + continue; + } + // Non-Number (like Length) / 0 = infinity + // For Length values, we keep them as-is and let ToCss handle serialization + // Use f32::INFINITY as marker for division by zero + // In ToCss, we'll output /0 or /-0 based on the marker + let marker = if f32::is_sign_negative(val) { + -f32::INFINITY + } else { + f32::INFINITY + }; + node = Calc::Product(marker, Box::new(node)); + continue; + } + // Division by infinity + if val.is_infinite() { + if node.is_sign_negative() { + node = Calc::Number(-0.0); + } else { + node = Calc::Number(0.0); + } + continue; + } } + // Non-Number / 0 case handled above return Err(input.new_custom_error(ParserError::InvalidValue)); } _ => { @@ -969,7 +1017,22 @@ impl + TrySign + Clone + std::fmt::Deb } } Calc::Product(num, calc) => { - if num.abs() < 1.0 { + // Special case: Product(INFINITY, value) represents value/0 (division by positive zero) + // Special case: Product(-INFINITY, value) represents value/-0 (division by negative zero) + // Serialize as calc(value/0) or calc(value/-0) instead of calc(INFINITY * value) + if num.is_infinite() { + calc.to_css(dest)?; + dest.delim('/', true)?; + if f32::is_sign_negative(*num) { + dest.write_str("-0") + } else { + dest.write_str("0") + } + } else if *num == 1.0 { + calc.to_css(dest)?; + dest.delim('/', true)?; + dest.write_str("0") + } else if num.abs() < 1.0 { let div = 1.0 / num; calc.to_css(dest)?; dest.delim('/', true)?; diff --git a/src/values/easing.rs b/src/values/easing.rs index 4db5c063d..ba69f2c5b 100644 --- a/src/values/easing.rs +++ b/src/values/easing.rs @@ -7,7 +7,6 @@ use crate::values::number::{CSSInteger, CSSNumber}; #[cfg(feature = "visitor")] use crate::visitor::Visit; use cssparser::*; -use std::fmt::Write; /// A CSS [easing function](https://www.w3.org/TR/css-easing-1/#easing-functions). #[derive(Debug, Clone, PartialEq)] @@ -75,8 +74,8 @@ impl<'i> Parse<'i> for EasingFunction { "ease-in" => EasingFunction::EaseIn, "ease-out" => EasingFunction::EaseOut, "ease-in-out" => EasingFunction::EaseInOut, - "step-start" => EasingFunction::Steps { count: 1, position: StepPosition::Start }, - "step-end" => EasingFunction::Steps { count: 1, position: StepPosition::End }, + "step-start" => EasingFunction::Steps { count: CSSInteger(1), position: StepPosition::Start }, + "step-end" => EasingFunction::Steps { count: CSSInteger(1), position: StepPosition::End }, _ => return Err(location.new_unexpected_token_error(Token::Ident(ident.clone()))) }; return Ok(keyword); @@ -163,16 +162,16 @@ impl ToCss for EasingFunction { dest.write_char(')') } EasingFunction::Steps { - count: 1, + count: CSSInteger(1), position: StepPosition::Start, } => dest.write_str("step-start"), EasingFunction::Steps { - count: 1, + count: CSSInteger(1), position: StepPosition::End, } => dest.write_str("step-end"), EasingFunction::Steps { count, position } => { dest.write_str("steps(")?; - write!(dest, "{}", count)?; + count.to_css(dest)?; dest.delim(',', false)?; position.to_css(dest)?; dest.write_char(')') diff --git a/src/values/number.rs b/src/values/number.rs index 60506eac8..361b50162 100644 --- a/src/values/number.rs +++ b/src/values/number.rs @@ -6,6 +6,8 @@ use crate::error::{ParserError, PrinterError}; use crate::printer::Printer; use crate::traits::private::AddInternal; use crate::traits::{Map, Op, Parse, Sign, ToCss, Zero}; +#[cfg(feature = "visitor")] +use crate::visitor::Visit; use cssparser::*; /// A CSS [``](https://www.w3.org/TR/css-values-4/#numbers) value. @@ -35,6 +37,18 @@ impl ToCss for CSSNumber { W: std::fmt::Write, { let number = *self; + // Handle NaN + if number.is_nan() { + return dest.write_str("0"); + } + // Handle infinity + if number.is_infinite() { + if number.is_sign_negative() { + return dest.write_str("calc(-1/0)"); + } else { + return dest.write_str("calc(1/0)"); + } + } if number != 0.0 && number.abs() < 1.0 { let mut s = String::new(); cssparser::ToCss::to_css(self, &mut s)?; @@ -111,13 +125,113 @@ impl Zero for CSSNumber { impl_try_from_angle!(CSSNumber); /// A CSS [``](https://www.w3.org/TR/css-values-4/#integers) value. -pub type CSSInteger = i32; +/// +/// Integers may be explicit or computed by `calc()`, but are always stored and serialized +/// as their computed value. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(transparent))] +#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] +#[cfg_attr(feature = "visitor", derive(crate::visitor::Visit))] +#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] +pub struct CSSInteger(pub i32); + +impl std::ops::Deref for CSSInteger { + type Target = i32; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl std::ops::DerefMut for CSSInteger { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl From for CSSInteger { + fn from(v: i32) -> Self { + CSSInteger(v) + } +} + +impl From for i32 { + fn from(v: CSSInteger) -> Self { + v.0 + } +} + +impl From for CSSInteger { + fn from(v: CSSNumber) -> Self { + CSSInteger(v as i32) + } +} + +impl PartialEq for CSSInteger { + fn eq(&self, other: &i32) -> bool { + self.0 == *other + } +} + +impl PartialOrd for CSSInteger { + fn partial_cmp(&self, other: &i32) -> Option { + self.0.partial_cmp(other) + } +} + +impl std::fmt::Display for CSSInteger { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + +impl std::ops::Add for CSSInteger { + type Output = i32; + + fn add(self, other: i32) -> Self::Output { + self.0 + other + } +} + +impl std::ops::Sub for CSSInteger { + type Output = i32; + + fn sub(self, other: i32) -> Self::Output { + self.0 - other + } +} + +impl std::ops::Neg for CSSInteger { + type Output = Self; + + fn neg(self) -> Self::Output { + CSSInteger(-self.0) + } +} impl<'i> Parse<'i> for CSSInteger { fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { - // TODO: calc?? + match input.try_parse(Calc::parse) { + Ok(Calc::Value(v)) => return Ok(*v), + Ok(Calc::Number(n)) => { + // Handle infinity and NaN from calc + if n.is_infinite() { + if n.is_sign_negative() { + return Ok(CSSInteger(i32::MIN)); + } else { + return Ok(CSSInteger(i32::MAX)); + } + } + // NaN rounds to 0 per CSS spec + return Ok(CSSInteger(n.round() as i32)); + } + // Numbers are always compatible, so they will always compute to a value. + Ok(_) => return Err(input.new_custom_error(ParserError::InvalidValue)), + _ => {} + } + let integer = input.expect_integer()?; - Ok(integer) + Ok(CSSInteger(integer)) } } @@ -126,17 +240,124 @@ impl ToCss for CSSInteger { where W: std::fmt::Write, { - cssparser::ToCss::to_css(self, dest)?; + // Handle infinity values + if self.0 == i32::MAX { + return dest.write_str("calc(1/0)"); + } + if self.0 == i32::MIN { + return dest.write_str("calc(-1/0)"); + } + cssparser::ToCss::to_css(&self.0, dest)?; Ok(()) } } +impl std::ops::Mul for CSSInteger { + type Output = Self; + fn mul(self, other: f32) -> Self { + let result = (self.0 as f32 * other).round(); + // Check for overflow and produce infinity + if result > i32::MAX as f32 { + CSSInteger(i32::MAX) + } else if result < i32::MIN as f32 { + CSSInteger(i32::MIN) + } else { + CSSInteger(result as i32) + } + } +} + +impl AddInternal for CSSInteger { + fn add(self, other: Self) -> Self { + let result = self.0.saturating_add(other.0); + // Check for overflow and produce infinity + if result == i32::MAX { + CSSInteger(i32::MAX) + } else if result == i32::MIN { + CSSInteger(i32::MIN) + } else { + CSSInteger(result) + } + } +} + +impl Op for CSSInteger { + fn op f32>(&self, to: &Self, op: F) -> Self { + let result = op(self.0 as f32, to.0 as f32); + // Check for overflow and produce infinity + if result > i32::MAX as f32 { + CSSInteger(i32::MAX) + } else if result < i32::MIN as f32 { + CSSInteger(i32::MIN) + } else { + CSSInteger(result.round() as i32) + } + } + + fn op_to T>(&self, rhs: &Self, op: F) -> T { + op(self.0 as f32, rhs.0 as f32) + } +} + +impl Map for CSSInteger { + fn map f32>(&self, op: F) -> Self { + let result = op(self.0 as f32); + // Check for overflow and produce infinity + if result > i32::MAX as f32 { + CSSInteger(i32::MAX) + } else if result < i32::MIN as f32 { + CSSInteger(i32::MIN) + } else { + CSSInteger(result as i32) + } + } +} + +impl Sign for CSSInteger { + fn sign(&self) -> f32 { + if self.0 == 0 { + return if self.0.is_positive() { 0.0 } else { -0.0 }; + } + self.0.signum() as f32 + } +} + +impl std::convert::Into> for CSSInteger { + fn into(self) -> Calc { + Calc::Value(Box::new(self)) + } +} + +impl std::convert::From> for CSSInteger { + fn from(calc: Calc) -> Self { + match calc { + Calc::Value(v) => *v, + Calc::Number(n) => { + if n.is_infinite() { + if n.is_sign_negative() { + CSSInteger(i32::MIN) + } else { + CSSInteger(i32::MAX) + } + } else if n.is_nan() { + CSSInteger(0) + } else { + CSSInteger(n.round() as i32) + } + } + _ => unreachable!(), + } + } +} + impl Zero for CSSInteger { fn zero() -> Self { - 0 + CSSInteger(0) } fn is_zero(&self) -> bool { - *self == 0 + self.0 == 0 } } + +impl_try_from_angle!(CSSInteger); diff --git a/src/values/syntax.rs b/src/values/syntax.rs index 42dfe485c..9102c8b29 100644 --- a/src/values/syntax.rs +++ b/src/values/syntax.rs @@ -544,7 +544,11 @@ mod tests { test("foo|+|", "foo", ParsedComponent::Literal("foo".into())); - test("foo | + | ", "2", ParsedComponent::Integer(2)); + test( + "foo | + | ", + "2", + ParsedComponent::Integer(CSSInteger(2)), + ); test( "foo | + | ",