In an earlier article, I mentioned that Boing-Boing had a few issues when they wanted to use @font-face embedding inside their website. In short, the problem was that some fonts look bad on computer monitors without font-smoothing enabled in the operating system. This brought on a lot of discussion as to whether there was a way to detect whether or not font-smoothing was being used using JavaScript. I initially thought there wasn’t a way, but after seeing a promising but incomplete method of detecting font-smoothing, I spent a few days devising a way to do it.
Paul Irish mentioned to me and a few other colleagues that he came across a page using an Active X control that detects font-smoothing in IE. I was so hopeful … until I realized that this only works on browsers that have come into contact with Microsoft’s online Cleartype Tuner, which I visited with my copy of IE months before. If a users had never visited this page, the script would fail.
I was more disappointed because Googling “javascript cleartype” did not point to anything useful. However, searching for “javascript font-smoothing” pointed me to an article that told me about Internet Explorer’s screen.fontSmoothingEnabled property. This gives us what we need … but only in Internet Explorer. How on Earth can we detect font-smoothing in other browsers, and in non-Windows operating systems?
I then thought about about the screenshots I made for the @font-face in Depth article. Can a browser render a black glyph and detect if there is some sort of non-black pixel colouring around the its edges of the glyphs? A human can tell the difference: if there are some non-black pixels around the edge of the glyph, it must be using font-smoothing.
But web browsers can do this too! Just have the browser draw a letter in black inside a canvas
tag, and then have it sift through the canvas’ pixels to see if there are any that are not pure black or pure white (more accurately, have the browser check the alpha channel to see if there are any semi-transparent pixels, which have a value that is not 0 or 255). If there are no semi-transparent pixels, then the algorithm assumes that no font-smoothing is being used. I wrote an JavaScript routine that does this – it starts from co-ordinate (8,1) and scans left to right, to the bottom of the canvas (any point on near the top on the left-hand side of the canvas would have done as well).
The result is a JavaScript object, TypeHelpers
, which implements this routine in one method, hasSmoothing()
:
var TypeHelpers = new function(){ // I use me instead of this. For reasons why, please read: // http://w3future.com/html/stories/callbacks.xml var me = this; me.hasSmoothing = function(){ // IE has screen.fontSmoothingEnabled - sweet! if (typeof(screen.fontSmoothingEnabled) != "undefined") { return screen.fontSmoothingEnabled; } else { try { // Create a 35x35 Canvas block. var canvasNode = document.createElement('canvas'); canvasNode.width = "35"; canvasNode.height = "35" // We must put this node into the body, otherwise // Safari Windows does not report correctly. canvasNode.style.display = 'none'; document.body.appendChild(canvasNode); var ctx = canvasNode.getContext('2d'); // draw a black letter 'O', 32px Arial. ctx.textBaseline = "top"; ctx.font = "32px Arial"; ctx.fillStyle = "black"; ctx.strokeStyle = "black"; ctx.fillText("O", 0, 0); // start at (8,1) and search the canvas from left to right, // top to bottom to see if we can find a non-black pixel. If // so we return true. for (var j = 8; j <= 32; j++) { for (var i = 1; i <= 32; i++) { var imageData = ctx.getImageData(i, j, 1, 1).data; var alpha = imageData[3]; if (alpha != 255 && alpha != 0) { return true; // font-smoothing must be on. } } } // didn't find any non-black pixels - return false. return false; } catch (ex) { // Something went wrong (for example, Opera cannot use the // canvas fillText() method. Return null (unknown). return null; } } } me.insertClasses = function(){ var result = me.hasSmoothing(); var htmlNode = document.getElementsByTagName('html')[0]; if (result == true) { htmlNode.className += " hasFontSmoothing-true"; } else if (result == false) { htmlNode.className += " hasFontSmoothing-false"; } else { // result == null htmlNode.className += " hasFontSmoothing-unknown"; } } } // if EventHelpers.js is included, insert the hasFontSmoothing CSS classes if (window.EventHelpers) { EventHelpers.addPageLoadEvent('TypeHelpers.insertClasses') }
Note the object also has an insertClasses()
method. This method, when run, adds a class to the html
tag:
hasFontSmoothing-true
if font-smoothing is being usedhasFontSmoothing-false
if it is nothasFontSmoothing-unknown
if the user agent is unable to tellThis makes it easy for developers who don’t want to mess with JavaScript code and just want to use CSS.
Also note the EventHelpers.addPageLoadEvent()
call at the end of the code. This method (which is part of EventHelpers.js
, included with the archive below) implements Dean Edwards’ window.onload alternative which doesn’t wait for all the objects in the page to be loaded. I use this implementation to execute TypeHelpers.insertClasses()
when the page loads so any font-detection CSS rules will work right away. Please feel free to change this code to use the equivalent function call in Dojo, Prototype, jQuery, or whatever JavaScript code framework you prefer.
Enough of theory … let’s look at it in practice! To show how to detect font-smoothing with JavaScript, I created a page that, when the page is loaded, checks to see if it can tell if font-smoothing has been implemented and tells the user. Here is the code that does this check:
function displayInfo() { var message; var isFontSmoothingOn = TypeHelpers.hasSmoothing(); if (isFontSmoothingOn == true) { message = "This browser is using a font-smoothing technology"; } else if (isFontSmoothingOn == false) { message = "This browser isn't using a font-smoothing technology" } else { message = "We could not detect if font-smoothing is being used." } document.getElementById('detectInfo').innerHTML = message; } window.onload = displayInfo;
As implied earlier, this library can help CSS use different fonts if the browser is using a font-smoothing technology. For example, using the following CSS will allow a browser to use the Droid Sans embedded font only if it using font-smoothing — otherwise, it will use Arial:
@font-face { font-family: "Droid Sans"; src: url("/shared/fonts/DroidSans/DroidSans.eot"); src: local("Droid Sans"), local("Droid Sans"), url("/shared/fonts/DroidSans/DroidSans.ttf") format("truetype"); } body { font-family: "Arial", "Helvetica", sans-serif; } html.hasFontSmoothing-true body { font-family: "Droid Sans", "Arial", "Helvetica", sans-serif; }
We can also serve special content to users depending on the way fonts are rendered on their browser. We first create content for all three scenerios (browser uses font-smoothing, browser doesn’t use font-smoothing, and the “we cannot detect” case) and wrap the content inside <code>div</code> tags using appropriate CSS classes:
<div class="fontSmoothingMessage initiallyHidden"> <p>You browser <strong>is</strong> rendering this page with font-smoothing. Because of that, we will attempt to serve up the Droid Sans font to render this page, because we think it looks cool. If you are using a browser (such as Google Chrome) that cannot render downloaded True Type fonts by default, then the page will be rendered using Arial instead.</p> </div> <div class="noFontSmoothingMessage initiallyHidden"> Your browser <strong>is not</strong> rendering this page with font-smoothing. It is for that reason we have decided to use the plain old Arial font to render this page, because it is hinted for use for displays that don't employ a font-smoothing technology. </div> <div class="unknownFontSmoothingMessage initiallyHidden"> <strong>We are not sure</strong> if your browser is rendering this page with a font-smoothing technology. It is for that reason we have decided to use the plain old Arial font to render this page, because it is hinted for use for displays that don't employ a font-smoothing technology. </div>
Note all the div
tags are members of the class initiallyHidden
. This class will be used to hide all font-smoothing related content until the script kicks in.
However, all this will not work unless we use the following CSS code:
.initiallyHidden { display: none; } html.hasFontSmoothing-true .fontSmoothingMessage, html.hasFontSmoothing-false .noFontSmoothingMessage, html.hasFontSmoothing-unknown .unknownFontSmoothingMessage { display: block; }
Of course, this whole soltution relies on whether JavaScript being turned on in the user’s browser. This should be kept in mind when implementing this solution.
All code used in this article can be downloaded below (Note: version 1.0 was missing the Droid Sans Fonts which have now been put into the archive. Thanks to John Faulds for pointing this out).
TypeHelpers.js v.1.0a and sample code.
With the help of Tim Brown, it was determined that the code detected font-smoothing correctly in the following browsers:
This script cannot detect font-smoothing in any version of Opera (at the time of this writing, this includes all versions up to 10.10), since it cannot write text inside the canvas element in a way we can poll the pixels afterwards. If anyone can find a way of making it work with Opera, please write a comment below — I’d love to be able to support this browser.
Testing font-smoothing in most Windows web browsers is easy since it can be turned off inside the Display control panel. However, when using Safari for Windows, it is necessary to navigate inside Safari’s Appearance preferences and set the Font-smoothing option to Windows Standard. This is because by default, Safari uses it’s own built-in font-rendering engine which doesn’t seem to render aliased fonts. In Mac OS X, it seems anti-aliasing only works for fonts below a certain size, so aliased fonts don’t seem to be an issue with that operating system. In Ubuntu Linux I have yet to find a way of shutting of font-smoothing. If anyone knows a way, please let me know.