diff --git a/crates/typst-library/src/layout/page.rs b/crates/typst-library/src/layout/page.rs index 19184d22b..9ec69f586 100644 --- a/crates/typst-library/src/layout/page.rs +++ b/crates/typst-library/src/layout/page.rs @@ -738,12 +738,12 @@ pub struct Paper { impl Paper { /// The width of the paper. pub fn width(self) -> Abs { - Abs::mm(self.width.0) + Abs::mm(self.width.get()) } /// The height of the paper. pub fn height(self) -> Abs { - Abs::mm(self.height.0) + Abs::mm(self.height.get()) } } @@ -756,8 +756,8 @@ macro_rules! papers { impl Paper { $(pub const $var: Self = Self { name: $name, - width: Scalar($width), - height: Scalar($height), + width: Scalar::new($width), + height: Scalar::new($height), };)* } diff --git a/crates/typst/src/eval/fields.rs b/crates/typst/src/eval/fields.rs index 094cfa384..7d1644975 100644 --- a/crates/typst/src/eval/fields.rs +++ b/crates/typst/src/eval/fields.rs @@ -34,7 +34,9 @@ pub(crate) fn field(value: &Value, field: &str) -> StrResult { "cap" => stroke.line_cap.into_value(), "join" => stroke.line_join.into_value(), "dash" => stroke.dash_pattern.clone().into_value(), - "miter-limit" => stroke.miter_limit.map(|limit| limit.0).into_value(), + "miter-limit" => { + stroke.miter_limit.map(|limit| limit.get()).into_value() + } _ => return missing(), } } else if let Some(align) = dynamic.downcast::() { diff --git a/crates/typst/src/export/pdf/page.rs b/crates/typst/src/export/pdf/page.rs index d9798f54c..caa8e394f 100644 --- a/crates/typst/src/export/pdf/page.rs +++ b/crates/typst/src/export/pdf/page.rs @@ -336,7 +336,7 @@ impl PageContext<'_, '_> { } } if self.state.stroke.as_ref().map(|s| &s.miter_limit) != Some(miter_limit) { - self.content.set_miter_limit(miter_limit.0 as f32); + self.content.set_miter_limit(miter_limit.get() as f32); } self.state.stroke = Some(stroke.clone()); } diff --git a/crates/typst/src/export/render.rs b/crates/typst/src/export/render.rs index 81442ad06..df51f0bd6 100644 --- a/crates/typst/src/export/render.rs +++ b/crates/typst/src/export/render.rs @@ -496,7 +496,7 @@ fn render_shape( line_cap: line_cap.into(), line_join: line_join.into(), dash, - miter_limit: miter_limit.0 as f32, + miter_limit: miter_limit.get() as f32, }; canvas.stroke_path(&path, &paint, &stroke, ts, mask); } diff --git a/crates/typst/src/export/svg.rs b/crates/typst/src/export/svg.rs index 518c40c3a..6aef30fd1 100644 --- a/crates/typst/src/export/svg.rs +++ b/crates/typst/src/export/svg.rs @@ -323,7 +323,8 @@ impl SVGRenderer { LineJoin::Bevel => "bevel", }, ); - self.xml.write_attribute("stroke-miterlimit", &stroke.miter_limit.0); + self.xml + .write_attribute("stroke-miterlimit", &stroke.miter_limit.get()); if let Some(pattern) = &stroke.dash_pattern { self.xml.write_attribute("stroke-dashoffset", &pattern.phase.to_pt()); self.xml.write_attribute( diff --git a/crates/typst/src/geom/abs.rs b/crates/typst/src/geom/abs.rs index 4ca3a9a1d..d177a3048 100644 --- a/crates/typst/src/geom/abs.rs +++ b/crates/typst/src/geom/abs.rs @@ -7,22 +7,22 @@ pub struct Abs(Scalar); impl Abs { /// The zero length. pub const fn zero() -> Self { - Self(Scalar(0.0)) + Self(Scalar::ZERO) } /// The infinite length. pub const fn inf() -> Self { - Self(Scalar(f64::INFINITY)) + Self(Scalar::INFINITY) } /// Create an absolute length from a number of raw units. pub const fn raw(raw: f64) -> Self { - Self(Scalar(raw)) + Self(Scalar::new(raw)) } /// Create an absolute length from a value in a unit. pub fn with_unit(val: f64, unit: AbsUnit) -> Self { - Self(Scalar(val * unit.raw_scale())) + Self(Scalar::new(val * unit.raw_scale())) } /// Create an absolute length from a number of points. @@ -47,7 +47,7 @@ impl Abs { /// Get the value of this absolute length in raw units. pub const fn to_raw(self) -> f64 { - (self.0).0 + (self.0).get() } /// Get the value of this absolute length in a unit. diff --git a/crates/typst/src/geom/angle.rs b/crates/typst/src/geom/angle.rs index 242c80c91..9786f9030 100644 --- a/crates/typst/src/geom/angle.rs +++ b/crates/typst/src/geom/angle.rs @@ -18,17 +18,17 @@ pub struct Angle(Scalar); impl Angle { /// The zero angle. pub const fn zero() -> Self { - Self(Scalar(0.0)) + Self(Scalar::ZERO) } /// Create an angle from a number of raw units. pub const fn raw(raw: f64) -> Self { - Self(Scalar(raw)) + Self(Scalar::new(raw)) } /// Create an angle from a value in a unit. pub fn with_unit(val: f64, unit: AngleUnit) -> Self { - Self(Scalar(val * unit.raw_scale())) + Self(Scalar::new(val * unit.raw_scale())) } /// Create an angle from a number of radians. @@ -43,7 +43,7 @@ impl Angle { /// Get the value of this angle in raw units. pub const fn to_raw(self) -> f64 { - (self.0).0 + (self.0).get() } /// Get the value of this angle in a unit. diff --git a/crates/typst/src/geom/em.rs b/crates/typst/src/geom/em.rs index 8dda9ff65..e65970988 100644 --- a/crates/typst/src/geom/em.rs +++ b/crates/typst/src/geom/em.rs @@ -9,29 +9,29 @@ pub struct Em(Scalar); impl Em { /// The zero em length. pub const fn zero() -> Self { - Self(Scalar(0.0)) + Self(Scalar::ZERO) } /// The font size. pub const fn one() -> Self { - Self(Scalar(1.0)) + Self(Scalar::ONE) } /// Create a font-relative length. pub const fn new(em: f64) -> Self { - Self(Scalar(em)) + Self(Scalar::new(em)) } /// Create an em length from font units at the given units per em. pub fn from_units(units: impl Into, units_per_em: f64) -> Self { - Self(Scalar(units.into() / units_per_em)) + Self(Scalar::new(units.into() / units_per_em)) } /// Create an em length from a length at the given font size. pub fn from_length(length: Abs, font_size: Abs) -> Self { let result = length / font_size; if result.is_finite() { - Self(Scalar(result)) + Self(Scalar::new(result)) } else { Self::zero() } @@ -39,7 +39,7 @@ impl Em { /// The number of em units. pub const fn get(self) -> f64 { - (self.0).0 + (self.0).get() } /// The absolute value of this em length. diff --git a/crates/typst/src/geom/fr.rs b/crates/typst/src/geom/fr.rs index f7cec5d78..ad301558e 100644 --- a/crates/typst/src/geom/fr.rs +++ b/crates/typst/src/geom/fr.rs @@ -19,22 +19,22 @@ pub struct Fr(Scalar); impl Fr { /// Takes up zero space: `0fr`. pub const fn zero() -> Self { - Self(Scalar(0.0)) + Self(Scalar::ZERO) } /// Takes up as much space as all other items with this fraction: `1fr`. pub const fn one() -> Self { - Self(Scalar(1.0)) + Self(Scalar::ONE) } /// Create a new fraction. pub const fn new(ratio: f64) -> Self { - Self(Scalar(ratio)) + Self(Scalar::new(ratio)) } /// Get the underlying number. pub const fn get(self) -> f64 { - (self.0).0 + (self.0).get() } /// The absolute value of this fraction. diff --git a/crates/typst/src/geom/ratio.rs b/crates/typst/src/geom/ratio.rs index 76a09006d..b1488276f 100644 --- a/crates/typst/src/geom/ratio.rs +++ b/crates/typst/src/geom/ratio.rs @@ -18,22 +18,22 @@ pub struct Ratio(Scalar); impl Ratio { /// A ratio of `0%` represented as `0.0`. pub const fn zero() -> Self { - Self(Scalar(0.0)) + Self(Scalar::ZERO) } /// A ratio of `100%` represented as `1.0`. pub const fn one() -> Self { - Self(Scalar(1.0)) + Self(Scalar::ONE) } /// Create a new ratio from a value, where `1.0` means `100%`. pub const fn new(ratio: f64) -> Self { - Self(Scalar(ratio)) + Self(Scalar::new(ratio)) } /// Get the underlying ratio. pub const fn get(self) -> f64 { - (self.0).0 + (self.0).get() } /// Whether the ratio is zero. diff --git a/crates/typst/src/geom/scalar.rs b/crates/typst/src/geom/scalar.rs index 71d300407..6801bbc0a 100644 --- a/crates/typst/src/geom/scalar.rs +++ b/crates/typst/src/geom/scalar.rs @@ -4,7 +4,40 @@ use super::*; /// /// Panics if it's `NaN` during any of those operations. #[derive(Default, Copy, Clone)] -pub struct Scalar(pub f64); +pub struct Scalar(f64); + +// We have to detect NaNs this way since `f64::is_nan` isn’t const +// on stable yet: +// ([tracking issue](https://github.com/rust-lang/rust/issues/57241)) +#[allow(clippy::unusual_byte_groupings)] +const fn is_nan_const(x: f64) -> bool { + // Safety: all bit patterns are valid for u64, and f64 has no padding bits. + // We cannot use `f64::to_bits` because it is not const. + let x_bits = unsafe { std::mem::transmute::(x) }; + (x_bits << 1 >> (64 - 12 + 1)) == 0b0_111_1111_1111 && (x_bits << 12) != 0 +} + +impl Scalar { + /// Creates a [`Scalar`] with the given value. + /// + /// If the value is NaN, then it is set to `0.0` in the result. + pub const fn new(x: f64) -> Self { + Self(if is_nan_const(x) { 0.0 } else { x }) + } + + /// Gets the value of this [`Scalar`]. + #[inline] + pub const fn get(self) -> f64 { + self.0 + } + + /// The scalar containing `0.0`. + pub const ZERO: Self = Self(0.0); + /// The scalar containing `1.0`. + pub const ONE: Self = Self(1.0); + /// The scalar containing `f64::INFINITY`. + pub const INFINITY: Self = Self(f64::INFINITY); +} impl Numeric for Scalar { fn zero() -> Self { @@ -18,7 +51,7 @@ impl Numeric for Scalar { impl From for Scalar { fn from(float: f64) -> Self { - Self(float) + Self::new(float) } } @@ -88,7 +121,7 @@ impl Neg for Scalar { type Output = Self; fn neg(self) -> Self::Output { - Self(-self.0) + Self::new(-self.0) } } @@ -96,13 +129,13 @@ impl> Add for Scalar { type Output = Self; fn add(self, rhs: T) -> Self::Output { - Self(self.0 + rhs.into().0) + Self::new(self.0 + rhs.into().0) } } impl> AddAssign for Scalar { fn add_assign(&mut self, rhs: T) { - self.0 += rhs.into().0; + *self = *self + rhs.into(); } } @@ -110,13 +143,13 @@ impl> Sub for Scalar { type Output = Self; fn sub(self, rhs: T) -> Self::Output { - Self(self.0 - rhs.into().0) + Self::new(self.0 - rhs.into().0) } } impl> SubAssign for Scalar { fn sub_assign(&mut self, rhs: T) { - self.0 -= rhs.into().0; + *self = *self - rhs.into(); } } @@ -124,13 +157,13 @@ impl> Mul for Scalar { type Output = Self; fn mul(self, rhs: T) -> Self::Output { - Self(self.0 * rhs.into().0) + Self::new(self.0 * rhs.into().0) } } impl> MulAssign for Scalar { fn mul_assign(&mut self, rhs: T) { - self.0 *= rhs.into().0; + *self = *self * rhs.into(); } } @@ -138,13 +171,13 @@ impl> Div for Scalar { type Output = Self; fn div(self, rhs: T) -> Self::Output { - Self(self.0 / rhs.into().0) + Self::new(self.0 / rhs.into().0) } } impl> DivAssign for Scalar { fn div_assign(&mut self, rhs: T) { - self.0 /= rhs.into().0; + *self = *self / rhs.into(); } } @@ -152,24 +185,24 @@ impl> Rem for Scalar { type Output = Self; fn rem(self, rhs: T) -> Self::Output { - Self(self.0 % rhs.into().0) + Self::new(self.0 % rhs.into().0) } } impl> RemAssign for Scalar { fn rem_assign(&mut self, rhs: T) { - self.0 %= rhs.into().0; + *self = *self % rhs.into(); } } impl Sum for Scalar { fn sum>(iter: I) -> Self { - Self(iter.map(|s| s.0).sum()) + Self::new(iter.map(|s| s.0).sum()) } } impl<'a> Sum<&'a Self> for Scalar { fn sum>(iter: I) -> Self { - Self(iter.map(|s| s.0).sum()) + Self::new(iter.map(|s| s.0).sum()) } } diff --git a/crates/typst/src/geom/stroke.rs b/crates/typst/src/geom/stroke.rs index 893ac2ebb..91e7ee2e1 100644 --- a/crates/typst/src/geom/stroke.rs +++ b/crates/typst/src/geom/stroke.rs @@ -266,7 +266,7 @@ cast! { line_cap, line_join, dash_pattern, - miter_limit: miter_limit.map(Scalar), + miter_limit: miter_limit.map(Scalar::new), } }, } @@ -460,7 +460,7 @@ impl Default for FixedStroke { line_cap: LineCap::Butt, line_join: LineJoin::Miter, dash_pattern: None, - miter_limit: Scalar(4.0), + miter_limit: Scalar::new(4.0), } } } diff --git a/tests/typ/compiler/ops.typ b/tests/typ/compiler/ops.typ index 56fdb7217..89ed3e5f7 100644 --- a/tests/typ/compiler/ops.typ +++ b/tests/typ/compiler/ops.typ @@ -128,6 +128,20 @@ // Error: 2-8 invalid hexadecimal number: 0x123z #0x123z +--- +// Test that multiplying infinite numbers by certain units does not crash. +#(float("inf") * 1pt) +#(float("inf") * 1em) +#(float("inf") * (1pt + 1em)) + +--- +// Test that trying to produce a NaN scalar (such as in lengths) does not crash. +#let infpt = float("inf") * 1pt +#test(infpt - infpt, 0pt) +#test(infpt + (-infpt), 0pt) +// TODO: this result is surprising +#test(infpt / float("inf"), 0pt) + --- // Test boolean operators.