From 8faebd0d96eb158ade0eea4c6825fac6fc682319 Mon Sep 17 00:00:00 2001 From: NetterSilver <133535268+Silvertree2010@users.noreply.github.com> Date: Mon, 2 Nov 2026 05:49:14 +0100 Subject: [PATCH] Fix Matrix::invert rejecting valid matrices with a small scale (#5703) * Fix Matrix::invert rejecting valid matrices with a small scale Found while testing crates/content/src/lib.rs:732-735 (Matrix::invert) Co-Authored-By: Claude Haiku 5.5 Co-Authored-By: Claude Sonnet 5.5 * Matrix::invert: a matrix whose inverse overflows has none A subnormal determinant passes the relative check, and dividing by it overflows (CodeRabbit on #5703). Co-Authored-By: Claude Opus 5.5 --------- Co-authored-by: Claude Haiku 5.5 Co-authored-by: Brandon Thomas --- crates/content/src/lib.rs | 7 +++++-- crates/content/src/tests.rs | 14 ++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/crates/content/src/lib.rs b/crates/content/src/lib.rs index c52916d332..4957f86861 100644 --- a/crates/content/src/lib.rs +++ b/crates/content/src/lib.rs @@ -730,11 +730,14 @@ impl Matrix { pub fn invert(&self) -> Option { let [a, b, c, d, e, f] = self.0; let det = a * d - b * c; - if det.abs() < 1e-12 || !det.is_finite() { + if det == 0.0 || !det.is_finite() || det.abs() <= 1e-12 * ((a * d).abs() + (b * c).abs()) { return None; } let (ia, ib, ic, id) = (d / det, -b / det, -c / det, a / det); - Some(Matrix([ia, ib, ic, id, -(e * ia + f * ic), -(e * ib + f * id)])) + let inverse = [ia, ib, ic, id, -(e * ia + f * ic), -(e * ib + f * id)]; + // A determinant that underflows toward zero can still pass the relative check above, + // and dividing by it overflows: such a matrix has no usable inverse. + inverse.iter().all(|v| v.is_finite()).then_some(Matrix(inverse)) } /// The bounding box of a rectangle `[x0 y0 x1 y1]` after this transform. diff --git a/crates/content/src/tests.rs b/crates/content/src/tests.rs index c5c7a3aaac..9ac9297cd2 100644 --- a/crates/content/src/tests.rs +++ b/crates/content/src/tests.rs @@ -552,3 +552,17 @@ fn inline_images_end_where_the_renderer_ends_them() { assert_eq!((parsed.skipped, parsed.ambiguous_images), (0, 0), "{}", String::from_utf8_lossy(data)); } } + +#[test] +fn invert_accepts_small_scale_and_rejects_singular() { + let m = Matrix([1e-7, 0., 0., 1e-7, 0., 0.]); + let inv = m.invert().expect("a uniform 1e-7 scale is invertible"); + let id = m.then(&inv).0; + for (got, want) in id.iter().zip(Matrix::IDENTITY.0) { + assert!((got - want).abs() < 1e-9, "{id:?}"); + } + assert!(Matrix([1., 2., 2., 4., 0., 0.]).invert().is_none()); + assert!(Matrix([f64::NAN, 0., 0., 1., 0., 0.]).invert().is_none()); + // A subnormal scale passes the relative check but its inverse overflows (CodeRabbit on #5703). + assert!(Matrix([1e-310, 0., 0., 1., 0., 0.]).invert().is_none()); +}