Skip to content

Commit

Permalink
fix(SVGParser): Don't crash on nested CSS at-rules (#9707)
Browse files Browse the repository at this point in the history
  • Loading branch information
silverwind authored Mar 4, 2024
1 parent e16980c commit ab20586
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 6 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## [next]

- fix(SVGParser): Don't crash on nested CSS at-rules [#9707](https://github.com/fabricjs/fabric.js/pull/9707)
- perf(): measuring canvas size [#9697](https://github.com/fabricjs/fabric.js/pull/9697)
- chore(TS): Add type for options in toCanvasElement and toDataUrl [#9673](https://github.com/fabricjs/fabric.js/pull/9673)
- ci(): add source map support to node sandbox [#9686](https://github.com/fabricjs/fabric.js/pull/9686)
Expand Down
30 changes: 24 additions & 6 deletions src/parser/getCSSRules.spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,31 @@
import { loadSVGFromString } from './loadSVGFromString';

const testSvg = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 4439.1 3733" xml:space="preserve"><style>
@import url("https://fonts.googleapis.com/css2?family=Black+Ops+One%7Cfamily=Catamaran:wght@400,700%7Cfamily=Caveat+Brush%7Cfamily=Comfortaa:wght@400,500%7Cfamily=Henny+Penny%7Cfamily=Montserrat:wght@400,500,700%7Cfamily=Mulish:wght@400,500%7Cfamily=Oswald:wght@400,500%7Cfamily=PT+Sans%7Cfamily=Poppins:wght@300,500%7Cfamily=Prompt%7Cfamily=Roboto+Slab:wght@400,500%7Cfamily=Roboto:wght@300,400,700%7Cfamily=Rubik:wght@400,500%7Cfamily=Varela%7Cfamily=Viga%7Cfamily=Work+Sans:wght@300,400%7Cfamily=Yesteryear%7Cdisplay");
</style><defs/><rect x="0" y="0" width="100%" height="100%" fill="transparent"/><g transform="matrix(1 0 0 1 1643 1651.95)" id="COLORZONE-5" fill="#FFC900" style="fill: rgb(255, 201, 0);"><rect style="stroke: rgb(0, 0, 0); stroke-width: 2; stroke-dasharray: none; stroke-linecap: round; stroke-dashoffset: 0; stroke-linejoin: round; stroke-miterlimit: 10; fill: rgb(255, 201, 0); fill-rule: nonzero; opacity: 1;" x="-481.9" y="-141.75" rx="0" ry="0" width="963.8" height="283.5" fill="#FFC900"/></g></svg>`;

describe('getCSSRules', () => {
test('can load svgs with style tags with import statement', async () => {
const loaded = await loadSVGFromString(testSvg);
const loaded =
await loadSVGFromString(`<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 4439.1 3733" xml:space="preserve"><style>
@import url("https://fonts.googleapis.com/css2?family=Black+Ops+One%7Cfamily=Catamaran:wght@400,700%7Cfamily=Caveat+Brush%7Cfamily=Comfortaa:wght@400,500%7Cfamily=Henny+Penny%7Cfamily=Montserrat:wght@400,500,700%7Cfamily=Mulish:wght@400,500%7Cfamily=Oswald:wght@400,500%7Cfamily=PT+Sans%7Cfamily=Poppins:wght@300,500%7Cfamily=Prompt%7Cfamily=Roboto+Slab:wght@400,500%7Cfamily=Roboto:wght@300,400,700%7Cfamily=Rubik:wght@400,500%7Cfamily=Varela%7Cfamily=Viga%7Cfamily=Work+Sans:wght@300,400%7Cfamily=Yesteryear%7Cdisplay");
</style><defs/><rect x="0" y="0" width="100%" height="100%" fill="transparent"/><g transform="matrix(1 0 0 1 1643 1651.95)" id="COLORZONE-5" fill="#FFC900" style="fill: rgb(255, 201, 0);"><rect style="stroke: rgb(0, 0, 0); stroke-width: 2; stroke-dasharray: none; stroke-linecap: round; stroke-dashoffset: 0; stroke-linejoin: round; stroke-miterlimit: 10; fill: rgb(255, 201, 0); fill-rule: nonzero; opacity: 1;" x="-481.9" y="-141.75" rx="0" ry="0" width="963.8" height="283.5" fill="#FFC900"/></g></svg>`);
expect(loaded.objects).toHaveLength(2);
});

test('can load svgs with style tags with nested at-rules', async () => {
const loaded = await loadSVGFromString(`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
<style>
circle { fill: black; }
@media (prefers-color-scheme: dark) { circle { fill: white; } }
@supports (display: flex) {
circle { color: blue; }
}
@scope (scope root) to (scope limit) {
circle { color: green; }
}
</style>
<circle r="10" cx="10" cy="10"/>
</svg>
`);
expect(loaded.objects).toHaveLength(1);
});
});
10 changes: 10 additions & 0 deletions src/parser/getCSSRules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,16 @@ export function getCSSRules(doc: Document) {
.filter((rule, index, array) => array.length > 1 && rule.trim())
// at this point we have hopefully an array of rules `body { style code... `
.forEach((rule) => {
// if there is more than one opening bracket and the rule starts with '@', it is likely
// a nested at-rule like @media, @supports, @scope, etc. Ignore these as the code below
// can not handle it.
if (
(rule.match(/{/g) || []).length > 1 &&
rule.trim().startsWith('@')
) {
return;
}

const match = rule.split('{'),
ruleObj: Record<string, string> = {},
declaration = match[1].trim(),
Expand Down

0 comments on commit ab20586

Please sign in to comment.