Draw Svg on HTML5 Canvas with Support for Font Element

Draw SVG on HTML5 Canvas with support for font element

Browsers that support HTML5 Canvas also support SVG pretty well themselves. As such, you could do this:

var img = new Image;
img.onload = function(){ myCanvasContext.drawImage(img,0,0); };
img.src = "foo.svg";

The only downside to this technique is that if the SVG is outside of your domain the canvas will become tainted; you will not be able to use getImageData() to read the resulting SVG, if that is your goal.

I've put an example of this technique on my server: http://phrogz.net/tmp/canvas_from_svg.html

I've tested this and verified that it works (and looks the same) on IE9, Chrome v11b, Safari v5, and Firefox v4.

[Edit] Note that:

  1. Chrome and Firefox currently 'punt' on security and disallow you from reading the canvas (e.g. getImageData() or toDataURL()) after you draw any SVG to the canvas (regardless of the domain) these have been fixed

  2. Firefox currently has a bug where it refuses to draw SVG to the canvas unless the SVG has height and width attributes specified.

Drawing an SVG file on a HTML5 canvas

EDIT: Dec 2019

The Path2D() constructor is supported by all major browsers now, "allowing path objects to be declared on 2D canvas surfaces".



EDIT: Nov 2014

You can now use ctx.drawImage to draw HTMLImageElements that have a .svg source in some but not all browsers (75% coverage: Chrome, IE11, and Safari work, Firefox works with some bugs, but nightly has fixed them).

var img = new Image();
img.onload = function() {
ctx.drawImage(img, 0, 0);
}
img.src = "http://upload.wikimedia.org/wikipedia/commons/d/d2/Svg_example_square.svg";

Live example here. You should see a green square in the canvas. The second green square on the page is the same <svg> element inserted into the DOM for reference.

You can also use the new Path2D objects to draw SVG (string) paths. In other words, you can write:

var path = new Path2D('M 100,100 h 50 v 50 h 50');
ctx.stroke(path);

Live example of that here.



Original 2010 answer:

There's nothing native that allows you to natively use SVG paths in canvas. You must convert yourself or use a library to do it for you.

I'd suggest looking in to canvg: (check homepage & demos)

canvg takes the URL to an SVG file, or the text of the SVG file, parses it in JavaScript and renders the result on Canvas.

How to use Google fonts in Canvas when Drawing DOM objects in SVG?

This has been already asked a few times, but never really as precise as it about Google Fonts.

So the general ideas are that :

  • To draw an svg on a canvas, we need to load it in an <img> element first.
  • For security reasons, <img> inner documents can not make any external requests.
    This means that you'll have to embed all your external resources as dataURIs inside your svg markup itself, before loading it to the <img> element.

So for a font, you'll need to append a <style> element, and replace font's src in between url(...), with the dataURI version.

Google fonts embed documents like the one you use are actually just css files which will then point to the actual font files. So we need to fetch not only the first level CSS doc, but also the actual font files.

Here is a annotated and working (?) proof of concept, written with ES6 syntax, so which will require a modern browser, but it could be transpiled quite easily since all the methods in it can be polyfiled.

/*  Only tested on a really limited set of fonts, can very well not work  This should be taken as an proof of concept rather than a solid script.   @Params : an url pointing to an embed Google Font stylesheet  @Returns : a Promise, fulfiled with all the cssRules converted to dataURI as an Array*/function GFontToDataURI(url) {  return fetch(url) // first fecth the embed stylesheet page    .then(resp => resp.text()) // we only need the text of it    .then(text => {      // now we need to parse the CSSruleSets contained      // but chrome doesn't support styleSheets in DOMParsed docs...      let s = document.createElement('style');      s.innerHTML = text;      document.head.appendChild(s);      let styleSheet = s.sheet
// this will help us to keep track of the rules and the original urls let FontRule = rule => { let src = rule.style.getPropertyValue('src') || rule.style.cssText.match(/url\(.*?\)/g)[0]; if (!src) return null; let url = src.split('url(')[1].split(')')[0]; return { rule: rule, src: src, url: url.replace(/\"/g, '') }; }; let fontRules = [], fontProms = [];
// iterate through all the cssRules of the embedded doc // Edge doesn't make CSSRuleList enumerable... for (let i = 0; i < styleSheet.cssRules.length; i++) { let r = styleSheet.cssRules[i]; let fR = FontRule(r); if (!fR) { continue; } fontRules.push(fR); fontProms.push( fetch(fR.url) // fetch the actual font-file (.woff) .then(resp => resp.blob()) .then(blob => { return new Promise(resolve => { // we have to return it as a dataURI // because for whatever reason, // browser are afraid of blobURI in <img> too... let f = new FileReader(); f.onload = e => resolve(f.result); f.readAsDataURL(blob); }) }) .then(dataURL => { // now that we have our dataURI version, // we can replace the original URI with it // and we return the full rule's cssText return fR.rule.cssText.replace(fR.url, dataURL); }) ) } document.head.removeChild(s); // clean up return Promise.all(fontProms); // wait for all this has been done });}
/* Demo Code */
const ctx = canvas.getContext('2d');let svgData = '<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">' + '<foreignObject width="100%" height="100%">' + '<div xmlns="http://www.w3.org/1999/xhtml" style="font-size:40px;font-family:Pangolin">' + 'test' + '</div>' + '</foreignObject>' + '</svg>';// I'll use a DOMParser because it's easier to do DOM manipulation for melet svgDoc = new DOMParser().parseFromString(svgData, 'image/svg+xml');// request our dataURI versionGFontToDataURI('https://fonts.googleapis.com/css?family=Pangolin') .then(cssRules => { // we've got our array with all the cssRules let svgNS = "http://www.w3.org/2000/svg"; // so let's append it in our svg node let defs = svgDoc.createElementNS(svgNS, 'defs'); let style = svgDoc.createElementNS(svgNS, 'style'); style.innerHTML = cssRules.join('\n'); defs.appendChild(style); svgDoc.documentElement.appendChild(defs); // now we're good to create our string representation of the svg node let str = new XMLSerializer().serializeToString(svgDoc.documentElement); // Edge throws when blobURIs load dataURIs from https doc... // So we'll use only dataURIs all the way... let uri = 'data:image/svg+xml;charset=utf8,' + encodeURIComponent(str);
let img = new Image(); img.onload = function(e) { URL.revokeObjectURL(this.src); canvas.width = this.width; canvas.height = this.height; ctx.drawImage(this, 0, 0); } img.src = uri; }) .catch(reason => console.log(reason)) // if something went wrong, it'll go here
<canvas id="canvas"></canvas>

html5 canvas element and svg

SVG and canvas aren't really interchangeable technologies. SVG is a type of retained mode graphics where everything is drawn from a rather abstract model (the SVG document). Canvas on the other hand is a kind of immediate mode graphics, where there is no model and the client (JavaScript) must take care of redrawing, animations etc.



Related Topics



Leave a reply



Submit