From de4dd91580c37d39d3c3dfb290b9e6e425aaf1cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=80=E4=B8=9D?= Date: Wed, 7 Jan 2026 04:53:56 +0800 Subject: [PATCH 1/6] wip: calc() --- src/lib.rs | 146 ++++++++++++- src/properties/flex.rs | 15 +- src/properties/grid.rs | 2 +- src/properties/mod.rs | 10 +- src/properties/position.rs | 32 ++- src/rules/font_palette_values.rs | 8 +- src/values/calc.rs | 49 ++++- src/values/easing.rs | 8 +- src/values/number.rs | 354 ++++++++++++++++++++++++++++++- src/values/syntax.rs | 6 +- 10 files changed, 597 insertions(+), 33 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index dc40670a8..d80d4a1d6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7685,6 +7685,149 @@ 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 + 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 + // 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-1 { z-index: calc(0/-0) }", + ".infinity3-1{z-index:0}", + ); + minify_test( + ".infinity3-1 { z-index: calc(-0/-0) }", + ".infinity3-1{z-index:0}", + ); + minify_test( + ".infinity3-1 { z-index: calc(-0/0) }", + ".infinity3-1{z-index:0}", + ); + minify_test( + ".infinity3-2 { z-index: calc(-1/-0) }", + ".infinity3-2{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( + ".infinity6 { order: calc(infinity / infinity) }", + ".infinity6{order:0}", + ); + minify_test( + ".infinity6 { order: calc(infinity / 0) }", + ".infinity6{order:calc(1/0)}", + ); + minify_test( + ".infinity6 { order: calc(infinity + 888) }", + ".infinity6{order:calc(1/0)}", + ); + minify_test( + ".infinity6 { order: calc(infinity - 888) }", + ".infinity6{order:calc(1/0)}", + ); + minify_test( + ".infinity-flex { flex: calc(6 / 0); }", + ".infinity-flex{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 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))}", + ); + + 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 +7915,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..ae7448eec 100644 --- a/src/properties/flex.rs +++ b/src/properties/flex.rs @@ -11,7 +11,7 @@ use crate::macros::*; use crate::prefixes::{is_flex_2009, Feature}; use crate::printer::Printer; use crate::traits::{FromStandard, Parse, PropertyHandler, Shorthand, ToCss, Zero}; -use crate::values::number::{CSSInteger, CSSNumber}; +use crate::values::number::{CSSIntegerWithInfinity, CSSNumber}; use crate::values::{ length::{LengthPercentage, LengthPercentageOrAuto}, percentage::Percentage, @@ -350,13 +350,16 @@ impl FromStandard for BoxLines { } } -type BoxOrdinalGroup = CSSInteger; -impl FromStandard for BoxOrdinalGroup { - fn from_standard(order: &CSSInteger) -> Option { +type BoxOrdinalGroup = CSSIntegerWithInfinity; +impl FromStandard for BoxOrdinalGroup { + fn from_standard(order: &CSSIntegerWithInfinity) -> Option { Some(*order) } } +/// 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 = CSSIntegerWithInfinity; + // Old flex (2012): https://www.w3.org/TR/2012/WD-css3-flexbox-20120322/ enum_property! { @@ -484,9 +487,9 @@ pub(crate) struct FlexHandler { flex_negative: Option<(CSSNumber, VendorPrefix)>, basis: Option<(LengthPercentageOrAuto, VendorPrefix)>, preferred_size: Option<(LengthPercentageOrAuto, VendorPrefix)>, - order: Option<(CSSInteger, VendorPrefix)>, + order: Option<(CSSIntegerWithInfinity, VendorPrefix)>, box_ordinal_group: Option<(BoxOrdinalGroup, VendorPrefix)>, - flex_order: Option<(CSSInteger, VendorPrefix)>, + flex_order: Option<(CSSIntegerWithInfinity, VendorPrefix)>, has_any: bool, } 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/mod.rs b/src/properties/mod.rs index 54c47548a..950608461 100644 --- a/src/properties/mod.rs +++ b/src/properties/mod.rs @@ -129,7 +129,7 @@ use crate::prefixes::Feature; use crate::printer::{Printer, PrinterOptions}; use crate::targets::Targets; use crate::traits::{Parse, ParseWithOptions, Shorthand, ToCss}; -use crate::values::number::{CSSInteger, CSSNumber}; +use crate::values::number::{CSSIntegerWithInfinity, CSSNumber}; use crate::values::string::CowArcStr; use crate::values::{ alpha::*, color::*, easing::EasingFunction, ident::DashedIdentReference, ident::NoneOrCustomIdentList, image::*, @@ -1335,7 +1335,7 @@ define_properties! { "flex-shrink": FlexShrink(CSSNumber, VendorPrefix) / WebKit, "flex-basis": FlexBasis(LengthPercentageOrAuto, VendorPrefix) / WebKit, "flex": Flex(Flex, VendorPrefix) / WebKit / Ms shorthand: true, - "order": Order(CSSInteger, VendorPrefix) / WebKit, + "order": Order(CSSIntegerWithInfinity, VendorPrefix) / WebKit, // Align properties: https://www.w3.org/TR/2020/WD-css-align-3-20200421 "align-content": AlignContent(AlignContent, VendorPrefix) / WebKit, @@ -1354,16 +1354,16 @@ define_properties! { // Old flex (2009): https://www.w3.org/TR/2009/WD-css3-flexbox-20090723/ "box-orient": BoxOrient(BoxOrient, VendorPrefix) / WebKit / Moz unprefixed: false, "box-direction": BoxDirection(BoxDirection, VendorPrefix) / WebKit / Moz unprefixed: false, - "box-ordinal-group": BoxOrdinalGroup(CSSInteger, VendorPrefix) / WebKit / Moz unprefixed: false, + "box-ordinal-group": BoxOrdinalGroup(CSSIntegerWithInfinity, VendorPrefix) / WebKit / Moz unprefixed: false, "box-align": BoxAlign(BoxAlign, VendorPrefix) / WebKit / Moz unprefixed: false, "box-flex": BoxFlex(CSSNumber, VendorPrefix) / WebKit / Moz unprefixed: false, - "box-flex-group": BoxFlexGroup(CSSInteger, VendorPrefix) / WebKit unprefixed: false, + "box-flex-group": BoxFlexGroup(CSSIntegerWithInfinity, VendorPrefix) / WebKit unprefixed: false, "box-pack": BoxPack(BoxPack, VendorPrefix) / WebKit / Moz unprefixed: false, "box-lines": BoxLines(BoxLines, VendorPrefix) / WebKit / Moz unprefixed: false, // Old flex (2012): https://www.w3.org/TR/2012/WD-css3-flexbox-20120322/ "flex-pack": FlexPack(FlexPack, VendorPrefix) / Ms unprefixed: false, - "flex-order": FlexOrder(CSSInteger, VendorPrefix) / Ms unprefixed: false, + "flex-order": FlexOrder(CSSIntegerWithInfinity, VendorPrefix) / Ms unprefixed: false, "flex-align": FlexAlign(BoxAlign, VendorPrefix) / Ms unprefixed: false, "flex-item-align": FlexItemAlign(FlexItemAlign, VendorPrefix) / Ms unprefixed: false, "flex-line-pack": FlexLinePack(FlexLinePack, VendorPrefix) / Ms unprefixed: false, diff --git a/src/properties/position.rs b/src/properties/position.rs index 34a0cf3bf..947e346bd 100644 --- a/src/properties/position.rs +++ b/src/properties/position.rs @@ -7,7 +7,7 @@ use crate::error::{ParserError, PrinterError}; use crate::prefixes::Feature; use crate::printer::Printer; use crate::traits::{Parse, PropertyHandler, ToCss}; -use crate::values::number::CSSInteger; +use crate::values::number::CSSIntegerWithInfinity; use crate::vendor_prefix::VendorPrefix; #[cfg(feature = "visitor")] use crate::visitor::Visit; @@ -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,8 +85,32 @@ impl ToCss for Position { pub enum ZIndex { /// The `auto` keyword. Auto, - /// An integer value. - Integer(CSSInteger), + /// An integer value (supports infinity via calc()). + Integer(CSSIntegerWithInfinity), +} + +impl<'i> Parse<'i> for ZIndex { + fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { + if input.try_parse(|input| input.expect_ident_cloned()).is_ok() { + return Ok(ZIndex::Auto); + } + + // Use CSSIntegerWithInfinity::parse which handles calc() and infinity + let integer = CSSIntegerWithInfinity::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)] diff --git a/src/rules/font_palette_values.rs b/src/rules/font_palette_values.rs index af06c487f..d4bc8ae37 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(i32::from(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: i32::from(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(i32::from(self.index)).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..e0fb9c9bc 100644 --- a/src/values/calc.rs +++ b/src/values/calc.rs @@ -601,7 +601,48 @@ 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 + node = Calc::Product(1.0, 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 +1010,13 @@ impl + TrySign + Clone + std::fmt::Deb } } Calc::Product(num, calc) => { - if num.abs() < 1.0 { + // Special case: Product(1, value) represents value/0 (infinity) + // Serialize as calc(value/0) instead of calc(1 * value) + 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..a17503c39 100644 --- a/src/values/easing.rs +++ b/src/values/easing.rs @@ -75,8 +75,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,11 +163,11 @@ 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 } => { diff --git a/src/values/number.rs b/src/values/number.rs index 60506eac8..ff574abf4 100644 --- a/src/values/number.rs +++ b/src/values/number.rs @@ -111,13 +111,98 @@ 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)] +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)) => 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 +211,274 @@ impl ToCss for CSSInteger { where W: std::fmt::Write, { - cssparser::ToCss::to_css(self, dest)?; + cssparser::ToCss::to_css(&self.0, dest)?; Ok(()) } } +impl std::ops::Mul for CSSInteger { + type Output = Self; + fn mul(self, other: f32) -> Self { + CSSInteger((self.0 as f32 * other).round() as i32) + } +} + +impl AddInternal for CSSInteger { + fn add(self, other: Self) -> Self { + CSSInteger(self.0 + other.0) + } +} + +impl Op for CSSInteger { + fn op f32>(&self, to: &Self, op: F) -> Self { + CSSInteger(op(self.0 as f32, to.0 as f32).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 { + CSSInteger((op(self.0 as f32)) 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) => { + // IEEE-754 special values are censored to 0 in top-level calculations + // https://drafts.csswg.org/css-values-4/#calc-ieee + if n.is_infinite() || 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); + +/// A CSS [``](https://www.w3.org/TR/css-values-4/#integers) value that supports infinity. +/// +/// This type is similar to CSSInteger but also supports infinity values from calc(). +/// Infinity is stored using i32::MAX/i32::MIN as markers and serialized as `calc(1/0)`/`calc(-1/0)`. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct CSSIntegerWithInfinity(pub i32); + +impl std::ops::Deref for CSSIntegerWithInfinity { + type Target = i32; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl std::ops::DerefMut for CSSIntegerWithInfinity { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl From for CSSIntegerWithInfinity { + fn from(v: i32) -> Self { + CSSIntegerWithInfinity(v) + } +} + +impl From for i32 { + fn from(v: CSSIntegerWithInfinity) -> Self { + v.0 + } +} + +impl PartialEq for CSSIntegerWithInfinity { + fn eq(&self, other: &i32) -> bool { + self.0 == *other + } +} + +impl std::fmt::Display for CSSIntegerWithInfinity { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + +impl<'i> Parse<'i> for CSSIntegerWithInfinity { + fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { + // Try to parse using Calc::parse first for general calc expressions + 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(CSSIntegerWithInfinity(i32::MIN)); + } else { + return Ok(CSSIntegerWithInfinity(i32::MAX)); + } + } else if n.is_nan() { + return Ok(CSSIntegerWithInfinity(0)); + } + return Ok(CSSIntegerWithInfinity(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(CSSIntegerWithInfinity(integer)) + } +} + +impl ToCss for CSSIntegerWithInfinity { + fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> + where + W: std::fmt::Write, + { + // Check for infinity marker values (i32::MAX for +infinity, i32::MIN for -infinity) + if self.0 == i32::MAX { + // Positive infinity: output calc(1/0) + return dest.write_str("calc(1/0)"); + } + if self.0 == i32::MIN { + // Negative infinity: output calc(-1/0) + return dest.write_str("calc(-1/0)"); + } + cssparser::ToCss::to_css(&self.0, dest)?; + Ok(()) + } +} + +impl std::ops::Neg for CSSIntegerWithInfinity { + type Output = Self; + + fn neg(self) -> Self::Output { + CSSIntegerWithInfinity(-self.0) + } +} + +impl Zero for CSSIntegerWithInfinity { + fn zero() -> Self { + CSSIntegerWithInfinity(0) + } + + fn is_zero(&self) -> bool { + self.0 == 0 + } +} + +impl crate::traits::private::AddInternal for CSSIntegerWithInfinity { + fn add(self, other: Self) -> Self { + CSSIntegerWithInfinity(self.0 + other.0) + } +} + +impl crate::traits::Op for CSSIntegerWithInfinity { + fn op f32>(&self, to: &Self, op: F) -> Self { + CSSIntegerWithInfinity(op(self.0 as f32, to.0 as f32).round() as i32) + } + + fn op_to T>(&self, rhs: &Self, op: F) -> T { + op(self.0 as f32, rhs.0 as f32) + } +} + +impl std::ops::Mul for CSSIntegerWithInfinity { + type Output = Self; + + fn mul(self, other: f32) -> Self { + CSSIntegerWithInfinity((self.0 as f32 * other).round() as i32) + } +} + +impl crate::traits::Sign for CSSIntegerWithInfinity { + 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 crate::traits::Map for CSSIntegerWithInfinity { + fn map f32>(&self, op: F) -> Self { + CSSIntegerWithInfinity((op(self.0 as f32)) as i32) + } +} + +// TryFrom implementation - integers cannot be converted from angles +impl TryFrom for CSSIntegerWithInfinity { + type Error = (); + fn try_from(_: crate::values::angle::Angle) -> Result { + Err(()) + } +} + +impl TryInto for CSSIntegerWithInfinity { + type Error = (); + fn try_into(self) -> Result { + Err(()) + } +} + +impl std::convert::Into> for CSSIntegerWithInfinity { + fn into(self) -> Calc { + Calc::Value(Box::new(self)) + } +} + +impl std::convert::From> for CSSIntegerWithInfinity { + fn from(calc: Calc) -> Self { + match calc { + Calc::Value(v) => *v, + Calc::Number(n) => { + // Handle infinity and NaN from calc + if n.is_infinite() { + if n.is_sign_negative() { + CSSIntegerWithInfinity(i32::MIN) + } else { + CSSIntegerWithInfinity(i32::MAX) + } + } else if n.is_nan() { + CSSIntegerWithInfinity(0) + } else { + CSSIntegerWithInfinity(n.round() as i32) + } + } + _ => unreachable!(), + } } } 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 | + | ", From 26e101ec1f260aa4ca7a92f9b0307e6b8dc0c780 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=80=E4=B8=9D?= Date: Wed, 7 Jan 2026 16:33:49 +0800 Subject: [PATCH 2/6] test: add more infinity test --- src/lib.rs | 51 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d80d4a1d6..fc811bc81 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7700,8 +7700,10 @@ mod tests { minify_test(".order6 { order: calc(0.5) }", ".order6{order:1}"); minify_test(".order7 { order: calc(-5 * 0) }", ".order7{order:0}"); - // Test Infinity - // https://drafts.csswg.org/css-values-4/#calc-ieee + // 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)}", @@ -7727,7 +7729,7 @@ mod tests { ".infinity3-1{z-index:0}", ); minify_test( - ".infinity3-1 { z-index: calc(-0/0) }", + ".infinity3-1 { z-index: calc(-0/0) }", // NaN ".infinity3-1{z-index:0}", ); minify_test( @@ -7762,6 +7764,46 @@ mod tests { ".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)}", // 2147483647 + ); + minify_test( + ".infinity11-4 { z-index: calc(-2147483647 - 1) }", + ".infinity11-4{z-index:calc(-1/0)}", // -2147483648 + ); + minify_test( + ".infinity11-5 { z-index: calc(2147483646 + infinity) }", + ".infinity11-5{z-index:calc(1/0)}", // 2147483647 + ); + minify_test( + ".infinity11-6 { z-index: calc(2147483647 + 2 - 1) }", + ".infinity11-6{z-index:calc(1/0)}", // 2147483647 + ); + 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( ".infinity6 { order: calc(infinity / infinity) }", ".infinity6{order:0}", @@ -7801,7 +7843,7 @@ mod tests { // ".orphans4{orphans:-5}", // ); - // Test repeat() = + // Test infinity repeat() = minify_test( ".grid1 { grid-template-rows: repeat( calc(0.5), 1fr ); }", ".grid1{grid-template-rows:repeat(1,1fr)}", @@ -7818,6 +7860,7 @@ mod tests { ".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}"); From 9196e9ed5f8b248b1bfac79db06bf24c3919751e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=80=E4=B8=9D?= Date: Wed, 7 Jan 2026 17:39:03 +0800 Subject: [PATCH 3/6] CSSIntegerWithInfinity to CSSInteger --- src/properties/align.rs | 1 + src/properties/flex.rs | 14 +- src/properties/mod.rs | 10 +- src/properties/position.rs | 8 +- src/values/easing.rs | 3 +- src/values/number.rs | 273 ++++++++++--------------------------- 6 files changed, 90 insertions(+), 219 deletions(-) diff --git a/src/properties/align.rs b/src/properties/align.rs index 819e9ac4c..0a58518f5 100644 --- a/src/properties/align.rs +++ b/src/properties/align.rs @@ -11,6 +11,7 @@ use crate::prefixes::{is_flex_2009, Feature}; use crate::printer::Printer; use crate::traits::{FromStandard, Parse, PropertyHandler, Shorthand, ToCss}; use crate::values::length::LengthPercentage; +use crate::values::number::CSSInteger; use crate::vendor_prefix::VendorPrefix; #[cfg(feature = "visitor")] use crate::visitor::Visit; diff --git a/src/properties/flex.rs b/src/properties/flex.rs index ae7448eec..247fc8c4d 100644 --- a/src/properties/flex.rs +++ b/src/properties/flex.rs @@ -11,7 +11,7 @@ use crate::macros::*; use crate::prefixes::{is_flex_2009, Feature}; use crate::printer::Printer; use crate::traits::{FromStandard, Parse, PropertyHandler, Shorthand, ToCss, Zero}; -use crate::values::number::{CSSIntegerWithInfinity, CSSNumber}; +use crate::values::number::{CSSInteger, CSSNumber}; use crate::values::{ length::{LengthPercentage, LengthPercentageOrAuto}, percentage::Percentage, @@ -350,15 +350,15 @@ impl FromStandard for BoxLines { } } -type BoxOrdinalGroup = CSSIntegerWithInfinity; -impl FromStandard for BoxOrdinalGroup { - fn from_standard(order: &CSSIntegerWithInfinity) -> Option { +type BoxOrdinalGroup = CSSInteger; +impl FromStandard for BoxOrdinalGroup { + fn from_standard(order: &CSSInteger) -> Option { Some(*order) } } /// 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 = CSSIntegerWithInfinity; +pub type BoxFlexGroup = CSSInteger; // Old flex (2012): https://www.w3.org/TR/2012/WD-css3-flexbox-20120322/ @@ -487,9 +487,9 @@ pub(crate) struct FlexHandler { flex_negative: Option<(CSSNumber, VendorPrefix)>, basis: Option<(LengthPercentageOrAuto, VendorPrefix)>, preferred_size: Option<(LengthPercentageOrAuto, VendorPrefix)>, - order: Option<(CSSIntegerWithInfinity, VendorPrefix)>, + order: Option<(CSSInteger, VendorPrefix)>, box_ordinal_group: Option<(BoxOrdinalGroup, VendorPrefix)>, - flex_order: Option<(CSSIntegerWithInfinity, VendorPrefix)>, + flex_order: Option<(CSSInteger, VendorPrefix)>, has_any: bool, } diff --git a/src/properties/mod.rs b/src/properties/mod.rs index 950608461..54c47548a 100644 --- a/src/properties/mod.rs +++ b/src/properties/mod.rs @@ -129,7 +129,7 @@ use crate::prefixes::Feature; use crate::printer::{Printer, PrinterOptions}; use crate::targets::Targets; use crate::traits::{Parse, ParseWithOptions, Shorthand, ToCss}; -use crate::values::number::{CSSIntegerWithInfinity, CSSNumber}; +use crate::values::number::{CSSInteger, CSSNumber}; use crate::values::string::CowArcStr; use crate::values::{ alpha::*, color::*, easing::EasingFunction, ident::DashedIdentReference, ident::NoneOrCustomIdentList, image::*, @@ -1335,7 +1335,7 @@ define_properties! { "flex-shrink": FlexShrink(CSSNumber, VendorPrefix) / WebKit, "flex-basis": FlexBasis(LengthPercentageOrAuto, VendorPrefix) / WebKit, "flex": Flex(Flex, VendorPrefix) / WebKit / Ms shorthand: true, - "order": Order(CSSIntegerWithInfinity, VendorPrefix) / WebKit, + "order": Order(CSSInteger, VendorPrefix) / WebKit, // Align properties: https://www.w3.org/TR/2020/WD-css-align-3-20200421 "align-content": AlignContent(AlignContent, VendorPrefix) / WebKit, @@ -1354,16 +1354,16 @@ define_properties! { // Old flex (2009): https://www.w3.org/TR/2009/WD-css3-flexbox-20090723/ "box-orient": BoxOrient(BoxOrient, VendorPrefix) / WebKit / Moz unprefixed: false, "box-direction": BoxDirection(BoxDirection, VendorPrefix) / WebKit / Moz unprefixed: false, - "box-ordinal-group": BoxOrdinalGroup(CSSIntegerWithInfinity, VendorPrefix) / WebKit / Moz unprefixed: false, + "box-ordinal-group": BoxOrdinalGroup(CSSInteger, VendorPrefix) / WebKit / Moz unprefixed: false, "box-align": BoxAlign(BoxAlign, VendorPrefix) / WebKit / Moz unprefixed: false, "box-flex": BoxFlex(CSSNumber, VendorPrefix) / WebKit / Moz unprefixed: false, - "box-flex-group": BoxFlexGroup(CSSIntegerWithInfinity, VendorPrefix) / WebKit unprefixed: false, + "box-flex-group": BoxFlexGroup(CSSInteger, VendorPrefix) / WebKit unprefixed: false, "box-pack": BoxPack(BoxPack, VendorPrefix) / WebKit / Moz unprefixed: false, "box-lines": BoxLines(BoxLines, VendorPrefix) / WebKit / Moz unprefixed: false, // Old flex (2012): https://www.w3.org/TR/2012/WD-css3-flexbox-20120322/ "flex-pack": FlexPack(FlexPack, VendorPrefix) / Ms unprefixed: false, - "flex-order": FlexOrder(CSSIntegerWithInfinity, VendorPrefix) / Ms unprefixed: false, + "flex-order": FlexOrder(CSSInteger, VendorPrefix) / Ms unprefixed: false, "flex-align": FlexAlign(BoxAlign, VendorPrefix) / Ms unprefixed: false, "flex-item-align": FlexItemAlign(FlexItemAlign, VendorPrefix) / Ms unprefixed: false, "flex-line-pack": FlexLinePack(FlexLinePack, VendorPrefix) / Ms unprefixed: false, diff --git a/src/properties/position.rs b/src/properties/position.rs index 947e346bd..f21a97160 100644 --- a/src/properties/position.rs +++ b/src/properties/position.rs @@ -7,7 +7,7 @@ use crate::error::{ParserError, PrinterError}; use crate::prefixes::Feature; use crate::printer::Printer; use crate::traits::{Parse, PropertyHandler, ToCss}; -use crate::values::number::CSSIntegerWithInfinity; +use crate::values::number::CSSInteger; use crate::vendor_prefix::VendorPrefix; #[cfg(feature = "visitor")] use crate::visitor::Visit; @@ -86,7 +86,7 @@ pub enum ZIndex { /// The `auto` keyword. Auto, /// An integer value (supports infinity via calc()). - Integer(CSSIntegerWithInfinity), + Integer(CSSInteger), } impl<'i> Parse<'i> for ZIndex { @@ -95,8 +95,8 @@ impl<'i> Parse<'i> for ZIndex { return Ok(ZIndex::Auto); } - // Use CSSIntegerWithInfinity::parse which handles calc() and infinity - let integer = CSSIntegerWithInfinity::parse(input)?; + // Use CSSInteger::parse which handles calc() and infinity + let integer = CSSInteger::parse(input)?; Ok(ZIndex::Integer(integer)) } } diff --git a/src/values/easing.rs b/src/values/easing.rs index a17503c39..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)] @@ -172,7 +171,7 @@ impl ToCss for EasingFunction { } => 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 ff574abf4..8f89da79c 100644 --- a/src/values/number.rs +++ b/src/values/number.rs @@ -35,6 +35,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)?; @@ -195,7 +207,18 @@ impl<'i> Parse<'i> for CSSInteger { fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { match input.try_parse(Calc::parse) { Ok(Calc::Value(v)) => return Ok(*v), - Ok(Calc::Number(n)) => return Ok(CSSInteger(n.round() as i32)), + 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)), _ => {} @@ -211,6 +234,13 @@ impl ToCss for CSSInteger { where W: std::fmt::Write, { + // 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(()) } @@ -219,19 +249,41 @@ impl ToCss for CSSInteger { impl std::ops::Mul for CSSInteger { type Output = Self; fn mul(self, other: f32) -> Self { - CSSInteger((self.0 as f32 * other).round() as i32) + 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 { - CSSInteger(self.0 + other.0) + let result = self.0.saturating_add(other.0); + // Check for overflow and produce infinity + if result == i32::MAX || result == i32::MIN { + CSSInteger(i32::MAX) + } else { + CSSInteger(result) + } } } impl Op for CSSInteger { fn op f32>(&self, to: &Self, op: F) -> Self { - CSSInteger(op(self.0 as f32, to.0 as f32).round() as i32) + 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 { @@ -241,7 +293,15 @@ impl Op for CSSInteger { impl Map for CSSInteger { fn map f32>(&self, op: F) -> Self { - CSSInteger((op(self.0 as f32)) as i32) + 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) + } } } @@ -265,9 +325,13 @@ impl std::convert::From> for CSSInteger { match calc { Calc::Value(v) => *v, Calc::Number(n) => { - // IEEE-754 special values are censored to 0 in top-level calculations - // https://drafts.csswg.org/css-values-4/#calc-ieee - if n.is_infinite() || n.is_nan() { + 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) @@ -289,196 +353,3 @@ impl Zero for CSSInteger { } impl_try_from_angle!(CSSInteger); - -/// A CSS [``](https://www.w3.org/TR/css-values-4/#integers) value that supports infinity. -/// -/// This type is similar to CSSInteger but also supports infinity values from calc(). -/// Infinity is stored using i32::MAX/i32::MIN as markers and serialized as `calc(1/0)`/`calc(-1/0)`. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct CSSIntegerWithInfinity(pub i32); - -impl std::ops::Deref for CSSIntegerWithInfinity { - type Target = i32; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl std::ops::DerefMut for CSSIntegerWithInfinity { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl From for CSSIntegerWithInfinity { - fn from(v: i32) -> Self { - CSSIntegerWithInfinity(v) - } -} - -impl From for i32 { - fn from(v: CSSIntegerWithInfinity) -> Self { - v.0 - } -} - -impl PartialEq for CSSIntegerWithInfinity { - fn eq(&self, other: &i32) -> bool { - self.0 == *other - } -} - -impl std::fmt::Display for CSSIntegerWithInfinity { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.0.fmt(f) - } -} - -impl<'i> Parse<'i> for CSSIntegerWithInfinity { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { - // Try to parse using Calc::parse first for general calc expressions - 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(CSSIntegerWithInfinity(i32::MIN)); - } else { - return Ok(CSSIntegerWithInfinity(i32::MAX)); - } - } else if n.is_nan() { - return Ok(CSSIntegerWithInfinity(0)); - } - return Ok(CSSIntegerWithInfinity(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(CSSIntegerWithInfinity(integer)) - } -} - -impl ToCss for CSSIntegerWithInfinity { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - // Check for infinity marker values (i32::MAX for +infinity, i32::MIN for -infinity) - if self.0 == i32::MAX { - // Positive infinity: output calc(1/0) - return dest.write_str("calc(1/0)"); - } - if self.0 == i32::MIN { - // Negative infinity: output calc(-1/0) - return dest.write_str("calc(-1/0)"); - } - cssparser::ToCss::to_css(&self.0, dest)?; - Ok(()) - } -} - -impl std::ops::Neg for CSSIntegerWithInfinity { - type Output = Self; - - fn neg(self) -> Self::Output { - CSSIntegerWithInfinity(-self.0) - } -} - -impl Zero for CSSIntegerWithInfinity { - fn zero() -> Self { - CSSIntegerWithInfinity(0) - } - - fn is_zero(&self) -> bool { - self.0 == 0 - } -} - -impl crate::traits::private::AddInternal for CSSIntegerWithInfinity { - fn add(self, other: Self) -> Self { - CSSIntegerWithInfinity(self.0 + other.0) - } -} - -impl crate::traits::Op for CSSIntegerWithInfinity { - fn op f32>(&self, to: &Self, op: F) -> Self { - CSSIntegerWithInfinity(op(self.0 as f32, to.0 as f32).round() as i32) - } - - fn op_to T>(&self, rhs: &Self, op: F) -> T { - op(self.0 as f32, rhs.0 as f32) - } -} - -impl std::ops::Mul for CSSIntegerWithInfinity { - type Output = Self; - - fn mul(self, other: f32) -> Self { - CSSIntegerWithInfinity((self.0 as f32 * other).round() as i32) - } -} - -impl crate::traits::Sign for CSSIntegerWithInfinity { - 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 crate::traits::Map for CSSIntegerWithInfinity { - fn map f32>(&self, op: F) -> Self { - CSSIntegerWithInfinity((op(self.0 as f32)) as i32) - } -} - -// TryFrom implementation - integers cannot be converted from angles -impl TryFrom for CSSIntegerWithInfinity { - type Error = (); - fn try_from(_: crate::values::angle::Angle) -> Result { - Err(()) - } -} - -impl TryInto for CSSIntegerWithInfinity { - type Error = (); - fn try_into(self) -> Result { - Err(()) - } -} - -impl std::convert::Into> for CSSIntegerWithInfinity { - fn into(self) -> Calc { - Calc::Value(Box::new(self)) - } -} - -impl std::convert::From> for CSSIntegerWithInfinity { - fn from(calc: Calc) -> Self { - match calc { - Calc::Value(v) => *v, - Calc::Number(n) => { - // Handle infinity and NaN from calc - if n.is_infinite() { - if n.is_sign_negative() { - CSSIntegerWithInfinity(i32::MIN) - } else { - CSSIntegerWithInfinity(i32::MAX) - } - } else if n.is_nan() { - CSSIntegerWithInfinity(0) - } else { - CSSIntegerWithInfinity(n.round() as i32) - } - } - _ => unreachable!(), - } - } -} From 31ea74e4d7946cbecbf2a81a24ee4c44dab3a8ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=80=E4=B8=9D?= Date: Wed, 7 Jan 2026 18:04:25 +0800 Subject: [PATCH 4/6] fix: issue where -0 lost its sign --- src/lib.rs | 64 +++++++++++++++++++++++++++++++- src/properties/position.rs | 5 ++- src/rules/font_palette_values.rs | 6 +-- src/values/calc.rs | 20 ++++++++-- src/values/number.rs | 4 +- 5 files changed, 88 insertions(+), 11 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index fc811bc81..14e94a323 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7795,6 +7795,10 @@ mod tests { ".infinity11-6 { z-index: calc(2147483647 + 2 - 1) }", ".infinity11-6{z-index:calc(1/0)}", // 2147483647 ); + minify_test( + ".infinity11-7 { z-index: calc(1 - 2147483649) }", + ".infinity11-7{z-index:calc(-1/0)}", // Negative overflow: 1 + (-2147483649) = -2147483648 + ); minify_test( ".infinity12-1 { z-index: calc(calc(1/0) + infinity) }", ".infinity12-1{z-index:calc(1/0)}", @@ -7821,8 +7825,64 @@ mod tests { ".infinity6{order:calc(1/0)}", ); minify_test( - ".infinity-flex { flex: calc(6 / 0); }", - ".infinity-flex{flex:calc(1/0)}", + ".infinity-new2 { transition-timing-function: steps(calc(infinity), jump-start) }", + ".infinity-new2{transition-timing-function:steps(calc(1/0),start)}", + ); + minify_test( + ".infinity-new3 { transition: steps(calc(infinity), jump-start) }", + ".infinity-new3{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 diff --git a/src/properties/position.rs b/src/properties/position.rs index f21a97160..733abb9a4 100644 --- a/src/properties/position.rs +++ b/src/properties/position.rs @@ -91,7 +91,10 @@ pub enum ZIndex { impl<'i> Parse<'i> for ZIndex { fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { - if input.try_parse(|input| input.expect_ident_cloned()).is_ok() { + if input + .try_parse(|input| input.expect_ident_matching("auto")) + .is_ok() + { return Ok(ZIndex::Auto); } diff --git a/src/rules/font_palette_values.rs b/src/rules/font_palette_values.rs index d4bc8ae37..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(i32::from(i) as u16)); + return Ok(BasePalette::Integer(*i as u16)); } let location = input.current_source_location(); @@ -226,7 +226,7 @@ impl<'i> Parse<'i> for OverrideColors { } Ok(OverrideColors { - index: i32::from(index) as u16, + index: (*index) as u16, color, }) } @@ -237,7 +237,7 @@ impl ToCss for OverrideColors { where W: std::fmt::Write, { - CSSInteger(i32::from(self.index)).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 e0fb9c9bc..339afdd2a 100644 --- a/src/values/calc.rs +++ b/src/values/calc.rs @@ -629,7 +629,10 @@ impl< } // Non-Number (like Length) / 0 = infinity // For Length values, we keep them as-is and let ToCss handle serialization - node = Calc::Product(1.0, Box::new(node)); + // 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 @@ -1010,9 +1013,18 @@ impl + TrySign + Clone + std::fmt::Deb } } Calc::Product(num, calc) => { - // Special case: Product(1, value) represents value/0 (infinity) - // Serialize as calc(value/0) instead of calc(1 * value) - if *num == 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") diff --git a/src/values/number.rs b/src/values/number.rs index 8f89da79c..227ae6e1d 100644 --- a/src/values/number.rs +++ b/src/values/number.rs @@ -265,8 +265,10 @@ 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 || result == i32::MIN { + if result == i32::MAX { CSSInteger(i32::MAX) + } else if result == i32::MIN { + CSSInteger(i32::MIN) } else { CSSInteger(result) } From cf2d81f01e0e4fe892de101418c91ca47434feef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=80=E4=B8=9D?= Date: Wed, 7 Jan 2026 23:50:54 +0800 Subject: [PATCH 5/6] fix: add missing derive macros for `CSSInteger` CSSInteger is used as a field type in MediaCondition and ParsedComponent enums, which require serde, jsonschema, visitor, and into_owned derives when their respective features are enabled. This commit adds the necessary conditional derive attributes to fix compilation errors when running `cargo test --all-features`. --- src/properties/align.rs | 1 - src/values/number.rs | 6 ++++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/properties/align.rs b/src/properties/align.rs index 0a58518f5..819e9ac4c 100644 --- a/src/properties/align.rs +++ b/src/properties/align.rs @@ -11,7 +11,6 @@ use crate::prefixes::{is_flex_2009, Feature}; use crate::printer::Printer; use crate::traits::{FromStandard, Parse, PropertyHandler, Shorthand, ToCss}; use crate::values::length::LengthPercentage; -use crate::values::number::CSSInteger; use crate::vendor_prefix::VendorPrefix; #[cfg(feature = "visitor")] use crate::visitor::Visit; diff --git a/src/values/number.rs b/src/values/number.rs index 227ae6e1d..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. @@ -127,6 +129,10 @@ impl_try_from_angle!(CSSNumber); /// 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 { From 521cd09c7b8e277aa8fad2e548d731e7ba74cf7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=80=E4=B8=9D?= Date: Tue, 20 Jan 2026 23:13:13 +0800 Subject: [PATCH 6/6] add more test case --- src/lib.rs | 95 +++++++++++++++----------------------- src/properties/position.rs | 5 +- src/values/calc.rs | 6 ++- 3 files changed, 44 insertions(+), 62 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 14e94a323..bef5265c2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7686,7 +7686,11 @@ mod tests { minify_test(".foo { width: calc(20px + 30px + 40px) }", ".foo{width:90px}"); minify_test(".foo { width: calc(100% - 30px) }", ".foo{width:calc(100% - 30px)}"); - // Test + // 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}"); @@ -7704,37 +7708,25 @@ mod tests { // 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(".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 { 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-1 { z-index: calc(0/-0) }", - ".infinity3-1{z-index:0}", + ".infinity3-4 { z-index: calc(-0/0) }", // NaN + ".infinity3-4{z-index:0}", ); minify_test( - ".infinity3-1 { z-index: calc(-0/-0) }", - ".infinity3-1{z-index:0}", - ); - minify_test( - ".infinity3-1 { z-index: calc(-0/0) }", // NaN - ".infinity3-1{z-index:0}", - ); - minify_test( - ".infinity3-2 { z-index: calc(-1/-0) }", - ".infinity3-2{z-index:calc(1/0)}", + ".infinity3-5 { z-index: calc(-1/-0) }", + ".infinity3-5{z-index:calc(1/0)}", ); minify_test( ".infinity4 { z-index: calc(1 * infinity) }", @@ -7744,10 +7736,7 @@ mod tests { ".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(".infinity6 { z-index: calc(1 / -infinity) }", ".infinity6{z-index:0}"); minify_test( ".infinity7 { z-index: calc(1 - infinity) }", ".infinity7{z-index:calc(-1/0)}", @@ -7781,24 +7770,28 @@ mod tests { ); minify_test( ".infinity11-3 { z-index: calc(2147483647 + 1) }", - ".infinity11-3{z-index:calc(1/0)}", // 2147483647 + ".infinity11-3{z-index:calc(1/0)}", ); minify_test( ".infinity11-4 { z-index: calc(-2147483647 - 1) }", - ".infinity11-4{z-index:calc(-1/0)}", // -2147483648 + ".infinity11-4{z-index:calc(-1/0)}", ); minify_test( ".infinity11-5 { z-index: calc(2147483646 + infinity) }", - ".infinity11-5{z-index:calc(1/0)}", // 2147483647 + ".infinity11-5{z-index:calc(1/0)}", ); minify_test( ".infinity11-6 { z-index: calc(2147483647 + 2 - 1) }", - ".infinity11-6{z-index:calc(1/0)}", // 2147483647 + ".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)}", @@ -7809,28 +7802,28 @@ mod tests { ); minify_test( - ".infinity6 { order: calc(infinity / infinity) }", - ".infinity6{order:0}", + ".infinity-order-1 { order: calc(infinity / infinity) }", + ".infinity-order-1{order:0}", ); minify_test( - ".infinity6 { order: calc(infinity / 0) }", - ".infinity6{order:calc(1/0)}", + ".infinity-order-2 { order: calc(infinity / 0) }", + ".infinity-order-2{order:calc(1/0)}", ); minify_test( - ".infinity6 { order: calc(infinity + 888) }", - ".infinity6{order:calc(1/0)}", + ".infinity-order-3 { order: calc(infinity + 888) }", + ".infinity-order-3{order:calc(1/0)}", ); minify_test( - ".infinity6 { order: calc(infinity - 888) }", - ".infinity6{order:calc(1/0)}", + ".infinity-order-4 { order: calc(infinity - 888) }", + ".infinity-order-4{order:calc(1/0)}", ); minify_test( - ".infinity-new2 { transition-timing-function: steps(calc(infinity), jump-start) }", - ".infinity-new2{transition-timing-function:steps(calc(1/0),start)}", + ".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-new3 { transition: steps(calc(infinity), jump-start) }", - ".infinity-new3{transition:all steps(calc(1/0),start)}", + ".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 @@ -7856,10 +7849,7 @@ mod tests { ); // Test Infinity - minify_test( - ".number1 { line-height: calc(9/0) }", - ".number1{line-height:calc(1/0)}", - ); + 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)}", @@ -7872,18 +7862,9 @@ mod tests { ".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)}", - ); + 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 = diff --git a/src/properties/position.rs b/src/properties/position.rs index 733abb9a4..eafd3b931 100644 --- a/src/properties/position.rs +++ b/src/properties/position.rs @@ -91,10 +91,7 @@ pub enum ZIndex { 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() - { + if input.try_parse(|input| input.expect_ident_matching("auto")).is_ok() { return Ok(ZIndex::Auto); } diff --git a/src/values/calc.rs b/src/values/calc.rs index 339afdd2a..eaaa3dd25 100644 --- a/src/values/calc.rs +++ b/src/values/calc.rs @@ -631,7 +631,11 @@ impl< // 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 }; + let marker = if f32::is_sign_negative(val) { + -f32::INFINITY + } else { + f32::INFINITY + }; node = Calc::Product(marker, Box::new(node)); continue; }