Jun
17
2008
Cross-Browser Web Fonts
There are two general techniques in modern web browsers to display selectable text on a web page in an arbitrary font (and of course the standard technique for displaying non-selectable text is to simply use a raster image). The first technique is to use the CSS @font-face rule, and second is to use Scalable Inman Flash Replacement (sIFR). Used in conjunction, a progressive-enhanced graceful-degrading cross-browser solution becomes possible, as is demonstrated below.
The @font-face Rule
Using the @font-face rule to display a custom font is the preferred solution since it has been standardized since CSS 2; it furthermore works without JavaScript, it is simple to implement, it is lightweight (if the font file is minimized to include only the necessary glyphs), and it is also forward-looking as more and more browsers add support. To be employed, there are two font file formats which must be referenced with the @font-face rule: Microsoft’s Embedded OpenType (EOT) font format must be served to Internet Explorer 4+, but then either TrueType (TTF) or OpenType (OTF) font formats may be served to other browsers (currently only Safari 3.1 and the upcoming Firefox 3.1 and Opera 10).
The easiest way to reference these two separate formats is to employ Internet Explorer’s conditional comments, which requires no server-side configuration. The @font-face rule referencing the EOT font file should be included in a conditional comment in such a way that only IE browsers can see it. For non-IE browsers, a second @font-face rule is then be included which references the TrueType or OpenType font files; this rule should be wrapped in two conditional comments which cause it to be hidden from IE but visible to all other browsers. For example:
<!--[if IE]>
<style type="text/css">
@font-face {
font-family: "DemoFont1";
font-style: normal;
font-weight: normal;
src: url("fonts/I-Did-This.eot");
}
</style>
<![endif]-->
<!--[if !IE]>-->
<style type="text/css">
@font-face {
font-family: "DemoFont1";
font-style: normal;
font-weight: normal;
src: url("fonts/I-Did-This.ttf");
}
</style>
<!--<![endif]-->
Another way to accomplish this is to recerence a script in the @font-face rule’s src property; this script would then sniff the requesting user agent and then pass-through the EOT font if the browser is IE, and the TTF font if otherwise (remember to include a far-future HTTP Expires header). This @font-face rule can then be placed in the global stylesheet without having to add it to the head element of each page. For example:
@font-face {
font-family: "DemoFont2";
font-style: normal;
font-weight: normal;
src: url("fonts/choose-font.php?I-Did-This");
}
And the choose-font.php script could be something like:
<?php
$basename = basename($_SERVER['QUERY_STRING']); //make sure that no one is trying to be malicious
$filename = null;
if(strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE') !== false){
if(!file_exists("$basename.eot")){
header("HTTP/1.0 404 Not Found");
echo "$basename.eot does not exist";
exit;
}
header("Content-Type: application/vnd.bw-fontobject");
$filename = "$basename.eot";
$f = fopen($filename, 'rb');
}
else {
if(file_exists("$basename.ttf")){
header("Content-Type: application/x-font-ttf"); //MIME type not standardized?
$filename = "$basename.ttf";
$f = fopen($filename, 'rb');
}
else if(file_exists("$basename.otf")){
header("Content-Type: application/font-opentype"); //MIME type not standardized
$filename = "$basename.otf";
$f = fopen($filename, 'rb');
}
else {
header("HTTP/1.0 404 Not Found");
echo "Neither $basename.ttf nor $basename.otf exist";
exit;
}
}
header("Expires: " . str_replace('+0000', 'GMT', gmdate('r', time()+315569260))); //time plus 10 years
fpassthru($f);
fclose($f);
?>
An alternate server-side solution is to employ content negotiation using mod_rewrite.
Beware of a bug in WebKit we found: if there is a Flash piece on one page but no text in the font defined by the @font-face rule–upon navigating from such a page to one which has text in that font, a crash occurs (the workaround is to make sure that some text, even whitespace, on every page uses the font specified by the @font-face rule).
Scalable Inman Flash Replacement (sIFR)
For other browsers which do not support the @font-face rule, then Scalable Inman Flash Replacement (sIFR) is a fallback method for displaying the desired font (see the sIFR project page to learn how to generate the SWF font objects). To ensure that the sIFR script doesn’t clobber text in a @font-face-supporting browser, a font detection algorithm can be employed in sifr-config.js. If the algorithm detects at page-load that no font has been applied to the text in question, then it applies the corresponding sIFR font to the text. On subsequent page views, the algorithm doesn’t need to wait until page-load because it keeps a history of the detected fonts in a cookie so it can take any necessary action as soon as the DOM loads. It is therefore important to note that the text may appear momentarily in the next available font (e.g. sans-serif) until the page completes loading; however, upon subsequent page views it should appear in the proper font immediately. If JavaScript is turned off or Flash is not installed then the next available font will always be displayed.
The sifr-config.js requires a couple properties to be set on the sIFR object in order to be able to find the elements which have special fonts, and in order to tell sIFR where to find the SWF files that correspond to the special fonts defined. The elements are found by adding to each a specific class name to each element, and this desired class name can be stored in sIFR.webFontsClassName; alternatively, the elements may be specified with CSS selectors by setting sIFR.webFontSelector. Secondly, the locations of the sIFR SWF font files is stored in sIFR.webFonts, which is an Object containing keys which correspond to the font-family properties defined in the @font-face rules, and values which consist of yet another Object which consists of one src property whose value is the location SWF file. For example:
sIFR.webFontsClassName = 'demo';
sIFR.webFontSelector = "h1, h2, h3";
sIFR.webFonts = {
"DemoFont1" : {
src:'./fonts/I-Did-This.swf'
},
"DemoFont2" : {
src:'./fonts/A-Charming-Font-Expanded.swf'
},
"DemoFont3" : {
src:'./fonts/Y2K-PopMuzik-Outline-AOE.swf'
}
};
Examples
Here are a couple sites we’ve done employing custom web fonts:
Leave a Reply
You must be logged in to post a comment.