1

I'm trying to put together a tool that checks whether a given character is displayed in the specified style font or a system default font. My ultimate goal is to be able to check, at least in modern (read: IE8+) browsers, whether a ligature is supported in a given font.

I've got two canvases displaying the same ligature (in this case, st). I turn those canvases into data and compare them to see if the characters match.

Arial (like most fonts) does not have an st ligature, so it falls back to the default serif font. Here's where it gets weird: although they're displaying the same font, the two canvases don't have the same data.

Why? Because their positions on the canvas aren't exactly the same. I'm guessing it has something to do with the different relative heights of the fonts (one is slightly taller than the other, though which varies font to font). The difference appears to be one of a pixel or two, and it varies font by font.

How might one go about solving this? My only current idea is finding a way to measure the height of the font and adjusting its position accordingly, but I have no idea how to do that, unfortunately. Are there other approaches I might take to make the two images identical?

You can see the code below. Both canvases are successfully initialized and appended to the body of the element so I can see what's going on visually (though that's not necessary in the actual script I'm working on). I've dropped the initialization and context, as those are all working just fine.

function checkLig() {

   lig = 'fb06'   // this is the unicode for the st ligature

   canvas0.width = 250;
   canvas0.height = 50;
   context0.fillStyle = 'rgb(0, 0, 0)';
   context0.textBaseline = 'top';
   context0.font = 'normal normal normal 40px Arial';
   context0.fillText(String.fromCharCode(parseInt(lig, 16)), 0, 0);
   var print0 = context0.getImageData(0, 0, 720, 50).data;

   canvas1.width = 250;
   canvas1.height = 50;
   context1.fillStyle = 'rgb(0, 0, 0)';
   context1.textBaseline = 'top';
   context1.font = 'normal normal normal 40px serif';
   context1.fillText(String.fromCharCode(parseInt(lig, 16)), 0, 0);
   var print1 = context1.getImageData(0, 0, 720, 50).data;

   var i = 0, same = true, len = 720 * 50 * 4;
   while (i < len && same === true) {
      if (print0[i] !== print1[i]) {
         same = false;
      }
      else {
         i++;
      }
   }

   return same;
}
Chris Krycho
  • 3,125
  • 1
  • 23
  • 35
  • If anyone is curious, the still (very much _in-progress_) results of this can be found [here](http://www.chriskrycho.com/web/projects/ligatures-plus-js/). – Chris Krycho Apr 06 '12 at 21:28

1 Answers1

2

So i understand the question correctly, the problem is one canvas is specifying Arial (but falls back to Serif) and the other is Serif and when you do the pixel matching, and it's not a match because one of them has a slight offset?

One suggestion is to grab a reference pixel from each canvas and compare the positions of the two reference pixels to get an offset. And then factor that offset into your comparison loop.

For example, in this case you could get your reference pixel by starting to check pixels from the upper left corner of one canvas and scan downwards in that column and when you reach the bottom, go back to the top of the next column and scan down.

As soon as you hit a pixel that is not the color of your background, record the position and use that as your reference pixel. That should be the edge of your font. Do the same with your next canvas and then compare the positions of the two reference pixels to get an offset. Take that offset into consideration in your comparison loop.

Hope I have the right idea of the problem and i hope that helps!

Chris Ching
  • 1,404
  • 12
  • 12
  • You do understand the question correctly, and that's a good idea... I will play around with it. I'm concerned that it may run into problems with the general case (where fonts don't fall back) but I don't think that should be a problem on the whole. The real trick will be figuring out how to move through the canvas in that fashion. – Chris Krycho Oct 21 '11 at 14:31
  • If it's the single dimension array that getImageData returns, scanning horizontally will achieve the same thing. By using the same scanning method on both canvasses, you should hit "land" at the same spot. In the general case that the fonts are different, then i think the pre-scan won't make a difference in the result. The only consideration is the extra performance hit. – Chris Ching Oct 21 '11 at 15:26
  • Right. Then it should be a matter of simple math to do the offset from that point. And it's a small enough piece of code that performance issues are negligible. Thanks! – Chris Krycho Oct 21 '11 at 16:18