Cross-Browser Canvas Fonts

Cross-Browser Font Rendering, the 'Glyph' method.

Info

  • Works on PC in FireFox, Chrome, Opera & Safari. Have not tested OSX or Linux.
  • This example is Poly-Spaced. Unlike the old hacky version.
  • I created two Processing.js functions. loadGlyph() and glyph().
  • The font data is now stored in the Processing.js script which is much more sensible!

How to Use the Glyph Method

1) The first thing you will have to do is update your Processing.js script to include the loadGlyph() and glyph() functions, which I will paste at the end of this document.

2) Export the character paths from your SVG software, such as InkScape. You can round your point-data to 2 decimal places if you want a smaller foot-print. But make sure you dont end up with any whole numbers as Processing.js will try to parse a whole number followed by a comma as a variable. (See the Angel bug).

3) Then in your Processing script, build a Glyph-Table, which stores a a multi-dimensional array of SVG path data; Like this:

var myFont=
[
  ["letterSpacing",5],
  ["lineHeight",144],
  ["a","M -0.2,11.67 L -0.2,39.8 L 67.3,39.8 L 67.3,45.42 L 27.92,45.42 C 18.17,45.42 11.05,47.77 6.55,52.45 C 2.05,57.14 -0.2,63.42 -0.2,71.3 L -0.2,79.17 C -0.2,88.55 2.05,95.58 6.55,100.27 C 11.05,104.95 18.17,107.3 27.92,107.3 L 61.67,107.3 L 61.67,79.17 L 27.92,79.17 L 27.92,73.55 L 67.3,73.55 L 67.3,107.3 L 95.42,107.3 L 95.42,39.8 C 95.42,31.17 93.17,24.33 88.67,19.27 C 84.17,14.2 77.05,11.67 67.3,11.67 L -0.2,11.67 z"],
  ["b","M 0...
];

4) Pass the Glyph Table to the loadGlyph() function in Processing.js like so:

loadGlyph(myFont);

5) Now you are able to output your text to the Canvas by passing parameters to the glyph function like this:

glyph("Hello world!", scale, x, y);
Download The Source Code
canvas font glyphs.zip
The Processing.js Functions

To use the functions, they must be placed in your Processing.js script. I put mine starting at line 630, just after the standard font section; but you can really put them anywhere that you feel appropriate.

p.loadGlyph = function loadGlyph(glyphs){
  p.glyphTable=glyphs;
}

p.glyph = function glyph(output,scaleVal,xPos,yPos){   
  var regex=function(needle, hay){
    var regexp=new RegExp(needle,"g");
    var i=0;
    var results=[];
    while(results[i]=regexp.exec(hay)){i++;}
    return results;
  }     
  var glyphWidth;
  // Gets width of glyph to use poly-space fonts.
  var gWid=function(gWidth){
    if(gWidth>=glyphWidth){glyphWidth=parseFloat(gWidth);}
  }
  
  curContext.save();
  curContext.translate(xPos,yPos);
  curContext.scale(scaleVal,scaleVal);
  
  // Regular expressions for SVG Paths                        
  var pathcom = "(([a-zA-Z][ ][0-9\-\.\, ]+)|([z][ ]|[z]))"; // Finds a path command in font data
  var findXY = "([0-9\-\.]+)";        

  var com; // Draw command
  var pv; // Position value
  var cx, cy; // Cursor X & Y
      
  var lines=output.split("\n");
  for(var hh=0; hh < lines.length; hh++){
    output=lines[hh];
      var lineLength=0;
      for(var ii=0;ii< output.length;ii++){
        for(var jj=0;jj< p.glyphTable.length;jj++){
          if(output[ii]==p.glyphTable[jj][0]){
            glyphWidth=0;              
              com=regex(pathcom,p.glyphTable[jj][1]);
              curContext.beginPath();
              for(var kk=0;kk< com.length-1;kk++){
                switch(com[kk][0][0]){
                case "M":
                    pv=regex(findXY,com[kk][0]);
                    cx=pv[0][0];
                    cy=pv[1][0];
                    gWid(cx);
                    curContext.moveTo(cx,cy);                  
                    break;
                case "C":
                    pv=regex(findXY,com[kk][0]);                                        
                    cx=pv[4][0];
                    cy=pv[5][0];
                    curContext.bezierCurveTo(pv[0][0],pv[1][0],pv[2][0],pv[3][0],cx,cy);
                    gWid(pv[0][0]);gWid(pv[2][0]);gWid(cx);
                    break;
                case "L":                      
                    pv=regex(findXY,com[kk][0]);
                    cx=pv[0][0];
                    cy=pv[1][0];
                    gWid(pv[0][0]);
                    curContext.lineTo(cx,cy);                                                            
                    break;
                case "z":break;
                }
              }
              curContext.closePath();            
              doFill==true?curContext.fill():0;
              doStroke==true?curContext.stroke():0;
              curContext.translate(glyphWidth+p.glyphTable[0][1],0);
              lineLength=lineLength+glyphWidth+p.glyphTable[0][1];              
          }
        }
      }
      curContext.translate(-lineLength,p.glyphTable[1][1]);      
}
curContext.restore();  
}