paperjs / paper.js

The Swiss Army Knife of Vector Graphics Scripting – Scriptographer ported to JavaScript and the browser, using HTML5 Canvas. Created by @lehni & @puckey
http://paperjs.org
Other
14.49k stars 1.23k forks source link

SvgImport: Linked gradient defs fail if the linked gradient is defined after the current gradient. #1146

Open yincrash opened 8 years ago

yincrash commented 8 years ago

The following SVG document cannot be imported.

<svg width="120" height="120"  viewBox="0 0 120 120"
     xmlns="http://www.w3.org/2000/svg" version="1.1"
     xmlns:xlink="http://www.w3.org/1999/xlink" >

    <defs>
        <linearGradient id="derivedGradient"
          xlink:href="#MyGradient"/>
        <linearGradient id="MyGradient">
            <stop offset="5%"  stop-color="green"/>
            <stop offset="95%" stop-color="gold"/>
        </linearGradient>
    </defs>

    <rect fill="url(#derivedGradient)"
          x="10" y="10" width="100" height="100"/>
</svg>

Paperscript Sketch

var svg = '<svg width="120" height="120" viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><linearGradient id="derivedGradient" xlink:href="#MyGradient"/><linearGradient id="MyGradient"><stop offset="5%"  stop-color="green"/><stop offset="95%" stop-color="gold"/></linearGradient></defs><rect fill="url(#derivedGradient)" x="10" y="10" width="100" height="100"/></svg>'
project.clear();
project.importSVG(svg);

However, if you have the MyGradient element before the derivedGradient, it imports successfully. The line the code is dying on is in importGradient().

The spec allows for linking to anywhere in the document fragment (<svg>). The code already processes all <defs> first, so maybe we can defer processing of a node that links to an unknown id until after all the other nodes in <defs> blocks are processed? I'm open to contributing a PR if that would be helpful.

sasensi commented 5 years ago

I investigated a bit around this and I think the problem is wider than that because it seems that SVG spec allows <linearGradient> to be placed anywhere in the document, in any order (see this discussion). Even if it seems to be a best practice to put them inside of <defs> and in the right order (see example in Mozzila documentation). Also note that we are not the only ones facing this problem (see https://github.com/mapnik/mapnik/issues/2968). So this SVG would also be valid:

<svg width="120" height="120" viewBox="0 0 120 120"
     xmlns="http://www.w3.org/2000/svg" version="1.1"
     xmlns:xlink="http://www.w3.org/1999/xlink">

    <rect fill="url(#derivedGradient)" x="10" y="10" width="100" height="100"/>

    <linearGradient id="derivedGradient" xlink:href="#MyGradient"/>
    <linearGradient id="MyGradient">
        <stop offset="5%" stop-color="green"/>
        <stop offset="95%" stop-color="gold"/>
    </linearGradient>
</svg>

If we want to handle this, we have to improve the parsing algorithm. One possible solution is:

@lehni what do you think ?