Efficiently Serving Custom Web Fonts

Note 2009-10-07: Paul Irish published a Bulletproof @font-face syntax, which is a much better way to specify cross-browser downloadable fonts. His technique deprecates what I outlined below. Good work, Paul!

Update 2008-09: Removed superfluous “+” from last RewriteRule.

Update: Acknowledged that Ben was pointing to a solution by John Allsopp.

Ben Galbraith at Ajaxian recently posted again about using custom fonts. He points to a cross-browser solution by John Allsopp which serves EOT fonts and TTF fonts to both IE and non-IE browsers respectively. The idea is to include two @font-face rules with different font-family names (such as "Foo" as the name for the EOT, and "Bar" as the name for the TTF), and then in order to apply these fonts to text in the page you provide both names for the font-family property, such as "Foo, Bar, sans-serif". While this does work, it is also problematic: the client potentially downloads both font files and then throws away the one it doesn’t understand; this problem of wasted HTTP request is intensified by the fact that font files are often quite big (note that Safari appears to skip downloading font files that end in “.eot”, but MSIE doesn’t skip files that end in “.ttf”). There is a more efficient way to serve these font.

In projects I’ve worked on at Shepherd Interactive what I have done is use conditional comments to serve one @font-face rule for IE and another for everyone else. It is also very helpful to include the official name of the file in the CSS3 Web Fonts local() value in the case that the font is already locally installed (also include an additional src:url(...) declaration first in case local() isn’t understood, but if it is then the following src:local(...) declaration will override the previous one).

For example, given a font named “FooBarBaz”, the following is very best way I can conceive to serve web fonts:

<!--[if IE]>
<style type="text/css">
/* Font rule for MSIE referencing EOT font */
@font-face {
    font-family: "FooBarBaz";
    src: url("/fonts/foobarbaz.eot");
    /* above included in case local() isn't *
     * understood, which it isn't by IE7)   */
    src: local("FooBarBaz"), url("/fonts/foobarbaz.eot");
}
</style>
<![endif]-->

<!--[if !IE]>-->
<style type="text/css">
/* Font rule for other browsers referencing TTF font */
@font-face {
    font-family: "FooBarBaz";
    src: url("/fonts/foobarbaz.ttf");
    /* above included in case local() isn't *
     * understood, but Safari 3.1 does      */
    src: local("FooBarBaz"), url("/fonts/foobarbaz.ttf");
}
</style>
<!--<![endif]-->

Of course this assumes that only IE will ever support EOT fonts.

Another way which would remove the need for conditional comments is to use some form of content negotiation (perhaps with mod_rewrite) to serve the appropriate font file based on the user agent (IE or not IE). For example in a CSS file:

@font-face {
    font-family: "FooBarBaz";
    src: url("/fonts/foobarbaz.font");
    src: local("FooBarBaz"), url("/fonts/foobarbaz.font");
}

And then in an .htaccess file:

#Make sure that font files are cached
ExpiresActive On
<FilesMatch "\.(eot|ttf|otf)$">
    ExpiresDefault "access plus 10 years"
</filesMatch>

#Serve "proper" MIME types (they aren't standardized yet)
AddType application/vnd.ms-fontobject .eot
AddType font/ttf .ttf
AddType font/otf .otf

RewriteEngine On

#Serve EOT font if MSIE
RewriteCond %{HTTP_USER_AGENT}   MSIE
RewriteCond %{HTTP_USER_AGENT}   !Opera
RewriteRule (.+)\.font$          $1.eot       [L]

#Serve OTF file if it exists
RewriteCond %{REQUEST_FILENAME}  (.+?)([^/]+)\.font$
RewriteCond %1%2.otf             -f
RewriteRule .+                   %2.otf       [L]

#Serve TTF file otherwise
RewriteRule (.+)\.font$          $1.ttf       [L]

This, however, depends on the client sending a truthful User Agent string.

Any suggestions for further improvements?

Real XHTML 2.0

This is real XHTML 2.0:

<?xml version="1.0"?>
<!DOCTYPE HTML PUBLIC "-//IETF//DTD XHTML 2.0//EN"
"http://www.w3.org/MarkUp/xhtml-spec/xhtml.dtd">
<HTML>
<!-- Here's a good place to put a comment. -->
<HEAD>
<TITLE>Structural Example</TITLE>
</HEAD><BODY>
<H1>First Header</H1>
<P>This is a paragraph in the example HTML file. Keep in mind
that the title does not appear in the document text, but that
the header (defined by H1) does.</P>
<OL>
  <LI>First item in an ordered list.</LI>
  <LI>Second item in an ordered list.</LI>
  <UL COMPACT="COMPACT">
    <LI> Note that lists can be nested;</LI>
    <LI> Whitespace may be used to assist in reading the
    HTML source.</LI>
  </UL>
  <LI>Third item in an ordered list.</LI>
</OL>
<P>This is an additional paragraph. Technically, end tags are
not required for paragraphs, although they are allowed. You can
include character highlighting in a paragraph. <EM>This sentence
of the paragraph is emphasized.</EM> <!--Note that the &lt;/P&gt;
end tag has been omitted-->.</P>
<P>
<IMG SRC="triangle.xbm" alt="Warning: "/>
Be sure to read these <B>bold instructions</B>.</P>
</BODY></HTML>

MapQuest Google Bomb

MapQuestI think it is unfortunate that “MapQuest” has in large part become the generic term for online mapping. If someone who is Internet-illiterate is told to “MapQuest an address” they will likely end up at mapquest.com instead of using a more advanced mapping service such as, especially, Google Maps. So let’s Google Bomb the term “MapQuest“!