mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-03 06:51:50 -06:00
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 <noreply@anthropic.com> Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * 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 <noreply@anthropic.com> --------- Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
Brandon Thomas
parent
f035218cb4
commit
8faebd0d96
@@ -730,11 +730,14 @@ impl Matrix {
|
||||
pub fn invert(&self) -> Option<Matrix> {
|
||||
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.
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user