Weston Ruter

Web application developer in Portland, Oregon

Efficiently Serving Custom Web Fonts

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 HTML 2.0//EN"
"http://www.w3.org/MarkUp/html-spec/html.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>

XHTML document.write() Support

Update 6/5: Made modifications to HTMLParser to support more usages, and reverted the changes to it that turned the local variables into member properties since it wasn't necessary.

Update 6/4: Added discussion about supported usages.

Google's AJAX APIs provide some incredible tools which equip web developers to do some amazing things. I've lately been dazzled by the power and accuracy of their AJAX Language API and also of the performance and convenience afforded by their AJAX Libraries API. The only issue I have with their APIs is that the fundamental google.load() function uses document.write() to output the necessary script elements to the DOM, which doesn't even appear to be necessary since google.setOnLoadCallback() executes after the scripts are loaded without using document.write() (but instead appended document with DOM methods). And the reason why using document.write() is bad, of course, is that it is not available when in XHTML.

Likewise, Google's AdSense program provides a great way for web authors to make get some compensation for their hard work. But it too relies on document.write() to output the necessary iframe element to display the advertisement. This has been well noted, and a workaround has been developed which utilizes the an object element. However, there is another solution which enables AdSense to work in XHTML without any HTML workaround, and which allows web authors to use the Google Ajax APIs in XHTML pages: simply define and implement document.write() yourself, as this script does.

When I set out to do this, somehow I completely missed a solution developed by John Resig (one of my biggest JavaScript heroes). My solution, however, has a couple advantages as I see it. First, his solution uses some regular expression hacks to attempt to make the HTML markup well-formed enough for the browser's XML parser, but as he notes, it is not very robust. Secondly, John's solution relies on innerHTML which causes it to completely fail in Safari 2 (although this implementation also fails for an unknown reason). I'm trying a different approach. Instead of using innerHTML, this implementation of document.write() parses the string argument of HTML markup into DOM nodes; if the DOM has not been completely loaded yet, it appends these DOM nodes to the document immediately after the requesting script element; otherwise, it appends the parsed nodes to the end of the body.

I've incorporated John Resig's own HTML Parser (via Erik Arvidsson), but I've made a couple key modifications to make it play nice with document.write(). I turned HTMLParser into a class with member properties in order to save the end state of the parser after all of the buffer has been processed. To this class I added a parse(moreHTML) method which allows additional markup to be passed into the parser for handling so that it can continue parsing from where it had finished from the previous buffer. And by removing the last parseEndTag() cleanup call (for document.write() is anything but clean), it then became possible for multiple document.write() calls to be made with arguments consisting of chopped up HTML fragments like just a start tag or end tag, which is exactly what AdSense does and is a common usage of the method.

Now for some examples, which of course will work when the document is served as either application/xhtml+xml and text/html, as in the case of MSIE. First is the canonical AdSense script elements which output an advertisement:

And to demonstrate handling arbitrary HTML elements with text and comment nodes:

And to illustrate the use of the Google AJAX APIs, the phrase “Hello world!” (loaded from an external script, whose calling script element is itself output by document.write()) is translated into Spanish via Google's AJAX Language API: Loading...

The Language API is loaded via google.load('language', 1) and its completed load-state is detected by google.setOnLoadCallback(). The completed load-state of the sourceText variable in the external script is then detected with this anonymous polling function:

(function(){
    if(window.sourceText){
        //ready to work!
    }
    else
        setTimeout(arguments.callee, 10);
})();

This document.write() implementation is known to work at least in Firefox 2/3, Opera 9.26, and Safari 3. It will work in Internet Explorer, of course, since the document must be served as text/html to be viewed and so it will already have document.write(). For some unknown reason, this currently does not work in Safari 2: the error “TypeError - Undefined value” is raised on the line of HTML where a script element loads xhtml-document-write.js. Any help would be much appreciated. As a workaround in the mean time, simply serve documents as text/html for Safari 2 browsers.

Supported usages: There are three common usages of document.write() in the wild of HTML, and the first two are currently supported:

  1. Outputting a well-formed HTML code fragment:

    document.write('<p>Hello <i>World</i>!</p>');

    This usage is fully supported by this implementation.

  2. Outputting a well-formed HTML code fragment spread out over multiple sequential function calls:

    document.write('<p>');
    document.write('Hello <i>World</i>!');
    document.write('</p>');

    This usage is also supported

  3. script elements with function calls outputting HTML fragments interspersed by arbitrary HTML elements:

    <script type="text/javascript">document.write('<b>');</script>
    Hello <i>World</i>!
    <script type="text/javascript">document.write('</b>');</script>

    This is not supported. Instead of outputting “Hello World!”, this implementation would output one empty b element, followed by just “Hello World!” This usage is more difficult to support although I have an idea of how to do it, but I may not end up implementing it unless there is demand for it.

One restriction, of course, to the use of this implementation is that the entire document must be well-formed XHTML without regard for the markup output by the calls to document.write(); thus you cannot do something like this (via Ian Hixie):

<foo>
 <script type="text/javascript" xmlns="http://www.w3.org/xhtml/1999"/><[!CDATA[
  document.write('<bar>');
 ]]></script>
 </bar>
</foo>

Download (or link to) the script source or the minified version (via Dojo's ShrinkSafe) (except ShrinkSafe currently breaks it).

Please comment with any suggestions or feedback!

This emerged while working for my employer Shepherd Interactive.

Three Indispensable Bookmarklets

Three bookmarklets I find indispensable:

  1. Share on Facebook
  2. Google Bookmark
  3. Note in Reader

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“!

ESV Bible Online: Short Dynamic Title

I’m a big fan of the ESV Bible Online website. It’s very readable, accessible, open, and you can listen to Scripture audio down to the verse-level. We read from it together daily in our read-through, and we just started to be more intentional about noting and sharing certain things that stand out to us.

One thing I don’t like about the website is that the document titles get so long and verbose. So if I look up John 3:16, the document title says, “ESV Bible Online: Passage: John 3:16″. If you’re doing any kind of research, you are bound to have multiple windows and tabs open at once. And if so, then all you’ll see is “ESV Bible O…” or something. And when you make bookmarks it will have that whole long title instead of just “John 3:16 (ESV)”…

Well, not any longer, if you have the Greasemonkey add-on for Firefox or the GreaseKit plugin for Safari/WebKit. I wrote a user script (click to install) that shortens the document title to be as short as possible, so window titles and bookmarks are short and more manageable. The coolest part of the script, however, is that the document title changes as you select verses of text from the page! So if I’m reading Romans 5, and I select a portion of verse 5, the document title will become “Romans 5:5 (ESV)”. If I select a few verses, then it will become “Romans 5:5-8 (ESV)”, and it even works if the verses span chapters!

This script also greatly facilitates note-taking in Google Notebook. When we are reading from the ESV Online, and we want to note a passage, then we just select it with the cursor, and click “Note This” (made available by the Google Notebook extension). The selected text is then added to the selected Notebook and the title of the note is the same as the document title, which is the scripture reference of the selected text. It works great!

Updated Site

I’ve finally put together my website. I’ll be posting thoughts on this blog from time to time, mostly about web development, but also about culture, cycling, and life in Portland. This code on this site is XHTML5; it’s my first draft of the theme, so I’ll be making improvements over time. I also want to integrate my shared items from Google Reader, and my Tweets.

I’ve been working on some exciting things at Shepherd Interactive, and I’m looking forward to sharing about them here. I need to figure out a better way of listing the projects I’m working on, and order/filter them in meaningful ways.

Looking forward to sharing the Web with you.