2

I'm trying to make some SVGs and then draw them to canvas. They need to be drawn to canvas because I then do some pixel-level manipulation afterwards. I was following the MDN article and trying to draw a rectangle. So far so good (fiddle). But then when I try to apply a filter, it breaks.

(The example that I'm using is a rectangle, but I'm going to be using custom paths later, so the canvas rect() methods aren't appropriate).

What's going on? My intuition is that the filter node doesn't "exist" before the svg node is loaded ... but then, neither does the rect node, so really I don't know...

var data = '<svg ...' //the svg code
var DOMURL = window.URL || window.webkitURL || window;

var img = new Image();
var svg = new Blob([data], {type: 'image/svg+xml;charset=utf-8'});
var url = DOMURL.createObjectURL(svg);

img.onload = function () {
  ctx.drawImage(img, 0, 0, 223, 223);
  DOMURL.revokeObjectURL(url);
}

img.src = url;

Edit: goodness, it didn't even occur to me that it was the browser! I'm using Firefox 43. It displays in chromium; I really do need it displaying correctly in Firefox/gecko though because FF has a very large share of the target audience's browser market. I've checked the Linux and Windows versions of FF 42 & 43 and they both have the same problem.

Michael Mullany
  • 30,283
  • 6
  • 81
  • 105
Escher
  • 5,418
  • 12
  • 54
  • 101

1 Answers1

4

There might be some character that doesn't pass well into the blob.
(as pointed out by Robert Longson, its actually an FF bug, but there were other issues reported with this method anyway)

The solution, is to replace the [string to blob to blobURI] into [encodedString to dataURI] :

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

var data = '<svg xmlns="http://www.w3.org/2000/svg" width="223" height="223">' +
  '<defs>' +
  '<filter id="f1" x="0" y="0">' +
  '<feGaussianBlur in="SourceGraphic" stdDeviation="5" />' +
  '</filter>' +
  '</defs>' +
  '<rect class="shape" x="0" y="0" width="100" height="150" transform="translate(40,15) rotate(15,50,75)" fill="#006791" filter="url(#f1)"></rect></svg>';

// first encode the special characters
var svg_data = encodeURIComponent(data);
// create a dataURI version
var url = 'data:image/svg+xml; charset=utf8, ' + svg_data;

var img = new Image();
img.onload = function() {
  ctx.drawImage(img, 0, 0, 223, 223);
}

img.src = url;
<canvas id="canvas" style="border:2px solid black;" width="223" height="223">
</canvas>

Ps : an even sanitizer solution, but a little bit heavier, is to use a DOMParser, to parse your string, then serialize its results to string with an XMLSerializer, encode this string and finally make this a dataURI :

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

var data = '<svg xmlns="http://www.w3.org/2000/svg" width="223" height="223">' +
  '<defs>' +
  '<filter id="f1" x="0" y="0">' +
  '<feGaussianBlur in="SourceGraphic" stdDeviation="5" />' +
  '</filter>' +
  '</defs>' +
  '<rect class="shape" x="0" y="0" width="100" height="150" transform="translate(40,15) rotate(15,50,75)" fill="#006791" filter="url(#f1)"></rect></svg>';

// create a new DOMParser
var parser = new DOMParser();
// Parse our string as an svg document
var doc = parser.parseFromString(data, 'image/svg+xml');
// Serialize the new svg document
var svg_data = new XMLSerializer().serializeToString(doc.documentElement);
// set it to a dataURI
var url = 'data:image/svg+xml; charset=utf8, '+encodeURIComponent(svg_data);

var img = new Image();
img.onload = function () {
  ctx.drawImage(img, 0, 0, 223, 223);
}
img.src = url;
<canvas id="canvas" style="border:2px solid black;" width="223" height="223">
</canvas>

Now that you've got a solution, we can try to answer the "What's going on?"

Chrome is buggy in its way to get the external elements referenced with the xlink:href attribute, or with a <funcIRI> (url(#xxx)) inside attributes.
e.g, it doesn't refer to the good URI path if you do such reference inside an external CSS stylesheet, and it isn't able to get the references specified inside an external reference.

See this codepen for more clarity about this :

The ball should be painted with two radialGradients, placed inside an external svg document. The radial gradients do themselves reference to a linearGradient in the same doc.

Firefox does fetch correctly every of the requested elements, when chrome only is able to fetch direct elements (the check-mark).
Firefox : FF screenshot Chrome : enter image description here

When you do use URL.createObjectURL(), you are creating a new file in the browser's memory, with a special location (its location.origin will be set to the one of the page where have created it but no pathName nor host will be set.).

I didn't gone into FF fetching code, but I guess that they do something in here that will make even inner references wrong when you are inside such a special url scheme and that's what does break in your case.

So yes, here FF is broken, but certainly because of something other browsers don't even try to do...

Community
  • 1
  • 1
Kaiido
  • 123,334
  • 13
  • 219
  • 285
  • 1
    Works perfectly, and I'm super impressed with your level of knowledge of what goes on inside the browser svg/xml mechanics. Thanks a lot! (Edit: bounty has time restriction on it. I'll come back later) – Escher Dec 16 '15 at 12:27