diff --git a/packages/engine/src/utils/alphaBlit.test.ts b/packages/engine/src/utils/alphaBlit.test.ts index 6ef20e99d4..e4e7c9364d 100644 --- a/packages/engine/src/utils/alphaBlit.test.ts +++ b/packages/engine/src/utils/alphaBlit.test.ts @@ -815,6 +815,29 @@ describe("blitRgb48leRegion", () => { // ── parseTransformMatrix tests ─────────────────────────────────────────────── describe("parseTransformMatrix", () => { + it("preserves whitespace and numeric conversion for 2D and 3D matrices", () => { + expect(parseTransformMatrix("matrix(\t1 ,\n-0 , .5 , 1e0 , 0x10 , )")).toEqual([ + 1, -0, 0.5, 1, 16, 0, + ]); + expect( + parseTransformMatrix("matrix3d(\t1 , , 0, 0, .5, 1e0, 0, 0, 0, 0, 1, 0, 0x10, , 0, 1 )"), + ).toEqual([1, 0, 0.5, 1, 16, 0]); + expect(parseTransformMatrix("matrix(1,,0,1,0,0)")).toBeNull(); + }); + + it.each([ + "matrix(", + "matrix(1,", + "matrix(1,0,", + "matrix(1,0,0,", + "matrix(1,0,0,1,", + "matrix(1,0,0,1,0,", + "matrix(1,0,0,1,0,(", + "matrix3d(", + ])("rejects long malformed whitespace after %s", (prefix) => { + expect(parseTransformMatrix(prefix + " ".repeat(100_000) + "!")).toBeNull(); + }); + it("returns null for 'none'", () => { expect(parseTransformMatrix("none")).toBeNull(); }); diff --git a/packages/engine/src/utils/alphaBlit.ts b/packages/engine/src/utils/alphaBlit.ts index b7a0dba5ae..e8bf2e0eed 100644 --- a/packages/engine/src/utils/alphaBlit.ts +++ b/packages/engine/src/utils/alphaBlit.ts @@ -920,16 +920,16 @@ export function normalizeObjectFit(value: string | undefined): ObjectFit { export function parseTransformMatrix(css: string): number[] | null { if (!css || css === "none") return null; - const match2d = css.match( - /^matrix\(\s*([^,]+),\s*([^,]+),\s*([^,]+),\s*([^,]+),\s*([^,]+),\s*([^,)]+)\s*\)$/, - ); + // The captures already include whitespace; overlapping whitespace quantifiers + // cause excessive backtracking on malformed input. Number handles the spaces. + const match2d = css.match(/^matrix\(([^,]+),([^,]+),([^,]+),([^,]+),([^,]+),([^,)]+)\)$/); if (match2d) { const values = match2d.slice(1, 7).map(Number); if (!values.every(Number.isFinite)) return null; return values; } - const match3d = css.match(/^matrix3d\(\s*([^)]+)\)$/); + const match3d = css.match(/^matrix3d\(([^)]+)\)$/); if (match3d) { const raw = match3d[1]; if (!raw) return null;