This blog uses external1 fonts, which are served from http://www.yjl.im/font/. Chromium (5.0) is my main browser and it renders with those fonts correctly. Some time ago, I noticed that Firefox (3.6) didn’t render with those fonts but it did download them. I couldn’t figure out why Chromium and Opera (10.10) works fine but Firefox doesn’t, and it’s not as if Firefox can’t render with fonts because it uses fonts at http://www.yjl.im/. (Internet Explorer (7) doesn’t, either, it’s same reason. But I don’t really care about IE)

I suddenly had a guess, a cross-domain thing. So I googled and found the answer2, it refers to Web Font linking and Cross-Origin Resource Sharing, my guess was confirmed.

Short story: you can’t use a font (in Firefox) which is not at same domain (origin). I think it’s right for a browser to have such behavior. Some people like stealing stuff.

Anyway, the solution is to add a response header Access-Control-Allow-Origin:


<FilesMatch "\.(ttf|otf|eot)$">
<IfModule mod_headers.c>
Header set Access-Control-Allow-Origin "*"
</IfModule>
</FilesMatch>

If you uses Apache and you do know how to configure your server, the code above is right for you.

However, many people do not have a webserver, if they do, they probably would host a WordPress blog already and wouldn’t encounter this issue. Moreover, Google Sites is probably some people’s file hosting options if they user Blogger, you have no way to add a header. So a normal blogger without skill or knowledge or money, probably wouldn’t have a chance to use @font-face. Say what? Data URI? For Green’s sake, don’t use that to serve a font.

As for me, I use Google App Engine at http://www.yjl.im (source code, BSD License). You only need to write a code3. font.py does the job, the following code is the main part:


class FontFile(webapp.RequestHandler):

# Chromium don't use fonts with text/html as we want.
EXT_TYPES = {
'.otf': 'application/x-font-otf',
'.ttf': 'application/x-font-ttf',
'.eot': 'application/vnd.ms-fontobject',
'.svg': 'image/svg+xml',
'.js': 'application/x-javascript',
'.woff': 'application/octet-stream',
}

def get(self, fontpath):

fontpath = os.path.normpath(fontpath)
# Does someone try to get other files?
if fontpath.startswith('.') or fontpath.startswith('/'):
self.error(403)
return

# Is requested file a font file?
ext = os.path.splitext(fontpath)[1]
if ext not in FontFile.EXT_TYPES.keys():
self.error(403)
return

fontpath = 'font/%s' % fontpath
# Does this font exist?
if not os.path.exists(fontpath):
self.error(404)
return

if 'Origin' not in self.request.headers:
self.response.headers.add_header('Access-Control-Allow-Origin', 'http://www.yjl.im')
elif os.environ['SERVER_NAME'] == 'localhost' or \
self.request.headers['Origin'] in ['http://www.yjl.im', 'https://yjlv.blogspot.com']:
self.response.headers.add_header('Access-Control-Allow-Origin', self.request.headers['Origin'])
else:
self.response.headers.add_header('Access-Control-Allow-Origin', 'http://www.yjl.im')
self.error(403)
return

self.response.headers['Content-Type'] = FontFile.EXT_TYPES[ext]

expires = datetime.datetime.utcnow() + datetime.timedelta(365)
self.response.headers.add_header('Expires', expires.strftime("%d %b %Y %H:%M:%S GMT"))
self.response.headers['Cache-Control'] = 'public, max-age=%d' % (86400 * 365)

f = open(fontpath)
self.response.out.write(f.read())
f.close()

The code is fairly simple. First, it checks to make sure no one is trying to do something bad. Then it check the Origin of request header (if client browsers sends it) to see if it is an allowed source. If the request is from an allowed source, then it sets the Access-Control-Allow-Origin to be Origin of request header, same value. Why? Because Access-Control-Allow-Origin can only have one origin assigned, this is the only way to have multiple-origin. Of course you can use *, but that means You are all welcome to hotlink my fonts!.

If it’s not an allowed source, it sets to http://www.yjl.im and returns 403 status code because it’s obviously not from allowed source. You can be more creative, serving a special font. ;)

If client browser doesn’t send Origin (Chromium doesn’t send it), then it sets the header to http://www.yjl.im and sends the font file.

As you can see, I also assign a proper Content-Type. Chromium wouldn’t use the font if you don’t tell it the right type. (But it doesn’t care about cross-origin)

The fonts I use are all downloaded from Font Squirrel‘s @font-face Kit4. I don’t know much about EOT (Embedded OpenType), but I think the EOT from kit are only allowed to use from same origin. Access-Control-Allow-Origin also works for those EOTs from kit, IE didn’t use external fonts but it did after I started to use that header.

[1]Blogger is not a file hosting provider, so if your Blogger blogs need some file for your blog, you have to find other options to host the files.
[2]http://www.thebrightlines.com/2010/01/12/implementing-font-face-cross-domain/ is gone. (2015-12-14T22:15:10Z)
[3]app.yaml doesn’t allow you to add header for static file.
[4]It packages all types of a font you need plus stylesheet in one ZIP archive.