Skip to content

fix(joint-core): guard against non-invertible screen CTM#3341

Open
kumilingus wants to merge 2 commits into
clientIO:devfrom
kumilingus:fix/vectorizer-non-invertible-ctm-dev
Open

fix(joint-core): guard against non-invertible screen CTM#3341
kumilingus wants to merge 2 commits into
clientIO:devfrom
kumilingus:fix/vectorizer-non-invertible-ctm-dev

Conversation

@kumilingus
Copy link
Copy Markdown
Contributor

@kumilingus kumilingus commented Jun 2, 2026

Summary

If an SVG element sits inside a zero-sized container (e.g. an <svg> with width=0 / height=0, a hidden <foreignObject>, or anything else the browser renders with no extent), its getScreenCTM() returns a zero matrix (a=b=c=d=0, det=0). We then call .inverse() on it inside getRelativeTransformation(), which either throws or yields a NaN/zero matrix — and getTransformToElement() breaks for everything in that subtree.

This PR adds a determinant check in getRelativeTransformation(). If the target's screen CTM isn't invertible, it returns null and getTransformToElement() falls back to the identity matrix, same as it does for any other failure case.

Regression test in vectorizer.js:

  • baseline with an invertible ancestor proves the identity result isn't just trivial CTMs lining up,
  • then forces the target CTM to be singular, asserts a=b=c=d=0 and det=0,
  • finally checks getTransformToElement() doesn't throw and returns identity.

Test plan

  • yarn grunt karma:vectorizer — 453/453 pass
  • Manual check in DevTools with a zero-sized ancestor

🤖 Generated with Claude Code

…veTransformation

When a target SVG element is nested under an ancestor with a singular
transform (e.g. scale(0)), its getScreenCTM() returns a matrix with
det = 0. Calling .inverse() on such a matrix throws or produces a
zero/NaN matrix, breaking getTransformToElement for any caller in that
subtree.

Detect this case and return null so getTransformToElement falls back to
the identity matrix.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
QUnit fails the test automatically when an exception bubbles up,
so the manual try/catch was redundant — and the unused `e` binding
tripped the `no-unused-vars` rule.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant