giant robots smashing into other giant robots

We are thoughtbot. We make web & mobile apps.

Tagged:

Comments (View)

DIY @font-face web service

Phil recently designed Developers Developers Developers Developers, an event we’re running for Boston-area high school and college students:

It looks awesome in part because of liberal use of @font-face.

These days, it’s simple to use @font-face. Just use a stylesheet referencing a web service like Google Font Directory:

<link href='http://fonts.googleapis.com/css?family=Lobster&subset=latin' rel='stylesheet' type='text/css'> 

… then use the font as part of a normal CSS font stack:

#schedule .event-time {
  font-family: 'Lobster', arial, sans-serif;
}

What if the font isn’t on an @font-face web service?

There are plenty of commercial @font-face web services, which Kyle has covered in detail.

However, there are cases where the font you want to use isn’t on an existing service. Or, the font you want to use requires explicit permission from the font’s creator before you can use it.

We ran into this problem on Developers Developers Developers Developers.

First attempt: serve fonts from the app

To solve it, we included the font files in our git repository. This worked, but wasn’t ideal:

  • Unnecessarily adds large files to the repo.
  • No HTTP caching by default, downloading font files on subsequent requests.
  • Repetition. Soon after, I wanted to use League Gothic again on another app.

Second attempt: serve fonts directly from Amazon S3

So our next attempt was to upload the @font-face stylesheet and font files to S3 and serve them directly from there like:

<link href='http://our-bucket.s3.amazonaws.com' type='text/css'>

At first, this seemed to work well. We could set some HTTP headers to handle caching and everything seemed like it was in the right place:

  • application code in version control
  • assets in an asset host (S3)

However, Firefox (and probably some versions of IE), balks at this technique because of the same origin policy.

On S3, you’re not allowed to tweak the Access-Control-Allow-Origin HTTP header to allow Firefox to serve your font from S3. There’s a long Amazon thread where S3 customers are asking for this ability.

However, even if you could set the Access-Control-Allow-Origin header on S3, you also want the Content-Type, Cache-Control, and Expires to all also be set in a standard way. It’s a pain to have to do that manually.

Solution: serve fonts from a DIY web service

Our final solution was a small Sinatra app called Fist Face. It is now open source under the MIT License and it solves all the problems we experienced.

Fist Face

Greatest boxer alive

It works exactly the same way as Google Font Directory, Typekit, or any other @font-face web service, except that you have full control over it:

<link href='http://your-font-face-service.com/league-gothic.css' rel='stylesheet' type='text/css'>

To use Fist Face, this is all you have to write:

# Gemfile
source "http://rubygems.org"
gem "sinatra",  "~> 1.1"
gem "fistface", "~> 1.0"

# config.ru
require 'rubygems'
require 'bundler'
Bundler.require
run FistFace

Then deploy it. Follow a few conventions in the README regarding your asset host (ex: S3) and a few minutes later, you’ll be serving fonts via your own @font-face web service.

Why would I want to do it myself?

If you’ve run into the same issues that we did, have you solved this problem differently?

Among the weaknesses of this approach are:

  • No CDN. (patches welcome!)
  • Up to you to write a decent cross-browser CSS file.
  • The font files you use might be old-school (not “hinted” for computer screens).
  • Requires deeper knowledge of typefaces.

With an open mind, the last weakness is actually a strength. Typography is pretty interesting. You’ve got all these independent type foundries doing beautiful work and some release their fonts for free under permissive licensing.

In that way, hunting great typefaces for your @font-face web service is like building your own art collection. It can differentiate your work.

So, when your friends ask you, “whoa, what font is that?”, you can tell them, “you’ve probably never heard of it” … which I’ve heard is hip.

Tagged:

Comments (View)

Video: Sinatra at Boston.rb, part 2

This is the second in a series of short videos. They feature Blake Mizerany discussing Sinatra and Heroku in great technical detail at September’s Boston.rb. Watch Part 1.

In HD this time… watch out, the kid is learning!

“use” is built-in

In this video, Blake extends his example code using Rack middleware from rack-contrib called Rack::AcceptFormat:

Adds a format extension at the end of the URI when there is none, corresponding to the mime-type given in the Accept HTTP header.

Example from the docs:

GET /some/resource HTTP/1.1
Accept: application/json

GET /some/resource.json HTTP/1.1
Accept: application/json

Blake says we can use Rack::AcceptFormat in our config.ru or in Sinatra, depending on the situation.

This middleware will re-write path_info

What’s cool about this is Sinatra doesn’t have to know about Rack::AcceptFormat’s deeds making our work with content types easier. By the time the request gets to Sinatra, “.json” has been added. Call it “just-in-time routing”.

Rack Hoptoad

On the day Blake spoke at Boston.rb, he and I sat down to get Rack Hoptoad working on Heroku. Staying true to the philosophy of backing into patterns, we started with a bare-bones Rack app, then moved to Sinatra.

In both cases, we need to specify the gem in our .gems file:

# http://docs.heroku.com/gems#overview
rack_hoptoad

Rack:

# config.ru
require 'rack_hoptoad'

use Rack::HoptoadNotifier, "123abc"
run lambda { |env| fail "Fail!" }

Sinatra:

# config.ru
require 'rack_hoptoad'
require 'example'

use Rack::HoptoadNotifier, "1234"
run Sinatra::Application

# example.rb
require 'sinatra'

configure :production do
  enable :raise_errors
end

get '/boom' do
  fail "Fail from Sinatra!"
end

when Rack, when Sinatra?

Blake’s rule of thumb is to start with Rack when he knows, when he’s starting, that he’s only going to have one route, maybe two, and there’s nothing special about them.

In many ways, Sinatra is just a clean DSL around HTTP with lots of delegation to Rack. Without it, you’d have messy conditional statements that are fine for a couple of routes, but get out of hand beyond that.

routing tricks

Access wildcard operators using block syntax instead of a Rails-style params[:first_name]:

get '/:first_name/:last_name' do |first, last|
  "Hello, #{first} #{last}"
end

Sinatra allows splats, such as this non-greedy match:

get '/:name.*?/:last' do
end

The returned object will be a normal Ruby MatchData object, so everything you can do with that applies in your Sinatra code.

next

Stay tuned next time when our protagonist discusses:

  • POSTing data to a legacy API
  • war stories
  • view templates

Tagged:

Comments (View)

Video: Sinatra at Boston.rb, part 1

This the first in a series of short videos. They feature Blake Mizerany discussing Sinatra and Heroku in great technical detail at September’s Boston.rb.

Blake Mizerany wrote Sinatra in 2006 because he was working on a high traffic site with a lot of POSTs, PUTs, and DELETEs. GETs can be cached but the others cannot.

In this video, Blake discusses the following concepts as they apply to Sinatra:

backing into patterns

Start with a string, start with a text file, use main on Ruby. Move up to Factories and Model-View-Controller and ActiveRecord when you need it.

Rails creates 72 files when you run the rails command. Blake decided he didn’t need to start with MVC on each new project.

clean routing system

Blake uses Sinatra often for web services. He compares Rails’ respond_to with Sinatra’s content_type declaration. He discusses his feeling that the routing systems in other frameworks are overly complex undesirable.

get '/users.json' do
  content_type :json
  @user = User.find(params[:id])
  @user.to_json
end

His first example, shown above, highlights Sinatra’s clean content type approach.

next

In the next video, Blake discusses:

  • handling the Accepts header using Rack middleware
  • deciding between Rack and Sinatra
  • rack-hoptoad
  • routing tricks such as passing wildcard params into block variables and non-greedy matching