How to Use Custom Fonts in an HTML5 Canvas Element

How can I use custom fonts in an HTML5 Canvas element?

I've thrown together a simple demo on jsfiddle here showing how to do this with @font-face: http://jsfiddle.net/zMKge/

Opera also has a simple tutorial on using <canvas>, including the text API.

CSS:

@font-face {
font-family: 'KulminoituvaRegular';
src: url('http://www.miketaylr.com/f/kulminoituva.ttf');
}

Javascript:

var ctx = document.getElementById('c').getContext('2d');
var kitty = new Image();
kitty.src = 'http://i954.photobucket.com/albums/ae30/rte148/891blog_keyboard_cat.gif';
kitty.onload = function(){
ctx.drawImage(this, 0,0,this.width, this.height);
ctx.font = '68px KulminoituvaRegular';
ctx.fillStyle = 'orangered';
ctx.textBaseline = 'top';
ctx.fillText ('Keyboard Cat', 0, 270);
};

How do I put custom fonts into HTML5 Canvas easily?

I'll discuss my code underneath.
First off, My Javascript:

window.onload = function start() {
canvas = document.getElementById('canvas1');
C_Width = canvas.width;
C_Height = canvas.height;
cxt = canvas.getContext('2d');
setTimeout(text_handler,200);
}

function text_handler() {
console.log('made it!');
cxt.font="20px myFont";//font size and then font family.
cxt.fillStyle="white"; //color to be seen on a black canvas, default is black text
cxt.fillText("TEST",(C_Width/2-200),C_Height/2); //cxt.fillText("message",x_coord,y_coord);
}

Allow me to explain whats going on here. I have a simple window.onload function so that the canvas exists before I try to get its Id and Context. Inside my text_handler() function, I have the font size and the font family defined in one line, as well as my font color (White because I have a black canvas). Then in the canvas I draw my message, and provide an x coordinate and a y coordinate. The setTimeout(); delay may be necessary in order to give the canvas time to load the font. In the test program I wrote, I actually got the delay time down really low, though 100ms is almost unnoticeable.
Another critical part of this is the CSS stylesheet:

canvas {
background-color: black; //for white text
font-family:myFont; //necessary
}
#load {
font-family:myFont; //necessary to load font with div
visibility: hidden; //makes div invisible but the div element forces the text to load before the canvas.
height: 0px;
}
@font-face {
font-family:myFont;
src: url('./Font_Path.ttf');
} //self-explanatory

I have of course defined the font in the canvas, the body of the webpage, and a necessary hidden div. The div helps load the font. I have styles in order that the div remains invisible but it still does its job. For some reason, through my testing, I determined that the font-family style must be above the other styles in my div. Of course, you could use whatever you want. Also of course, I use @font-face for my custom font.

Here's what the HTML looks like:

<body>
<div id="load">words</div> <!-- load font -->
<canvas id="canvas1" width="500px" height="500px"></canvas>
</body>

As explained above, the div is necessary to load the font before the canvas can implement it. That's why I have "words" inside it.

I really hope this helps someone out because I simply couldn't find anything on it. Please ask me questions, tell me what I could have done differently in the comment section below, or just simply ways I can simplify my code. I'd appreciate the help and constructive criticism. Thanks, and happy Canvas-ing...

Custom font in canvas with IE 10

Check out Thomas Bachem's answer (which was not the accepted answer) on this previous SO question.

He uses a timer to test if the font widths have been set. If so, your font is loaded.

Using jQuery to know when @font-face fonts are loaded?

You can also check out this web font loader co-created by Google & Typekit. This plugin gives you finer control over your font loading process if you need it.

https://github.com/typekit/webfontloader#events

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>

How come my custom font wont show when I write stuff with fillText()?

I've experienced this problem and it was down to the fact that at the time of writing the canvas the font wasn't loaded.

It wouldn't have been loaded to display the paragraph in your example either, but the system will go back and redisplay that once the font is fully loaded, and on modern systems that tends to be so quick that you don't even notice a flash of repainting. However by then it doesn't know that what you had drawn on the canvas was text, the canvas is just a bunch of 'pixels' as far as it is concerned.

MDN discusses the canvas font issue and has a recommendation for not drawing the canvas text until the font is loaded.

With the help of the FontFace API, you can explicitly load fonts before using them in a canvas.

let f = new FontFace('myCusomFont', 'url(myCustomFont.ttf)');

f.load().then(function() {
// Ready to use the font in a canvas context
let ctx = document.getElementById('canvas').getContext('2d');
ctx.font = "20px myCustomFont";
ctx.fillText('Hello World', 150, 250);
});

I guess you'd want to combine that with some check that the fontface does actually load within a reasonable time (for example, if the font server is outside your control could it be down?) and if not fall back to some font you know will be available.

Note: check caniuse that the API is supported by all the browsers you have to support. It is supported by Chrome/Edge, FF, Safari but not by IE so if you still need to support IE you'll need to sense that and add a workaround e.g. not drawing on the canvas until a timeout has expired.



Related Topics



Leave a reply



Submit