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)

Recipe: Google Calendar

A recipe for accessing Google Calendar data from Ruby.

Why?

Google Calendar is a great interface for a few people to share management of events. You might have a client that has this need. Neither you nor the client want to spend time or money duplicating an ‘admin interface’ for managing events. You want to focus development time on displaying those events in a custom way.

Ingredients

Google Calendar

For a Google Calendar you own, navigate to the “Calendar Details” page:

Google Calendar details

Your calendar has an address and can be accessed as XML/RSS, iCalendar, or HTML.

A good example of the Google Calendar embed option is Betahouse’s calendar.

iCalendar

For this recipe, we’ll use the iCalendar format. I tried GET’ing the XML version and parsing it with Nokogiri but found the iCalendar version more manageable.

Copy the “ICAL” button’s value. Save it in a comment in your code for now. Also download it. We’ll use it for our spec:

describe Tour do
  before do
    ics   = File.join(File.dirname(__FILE__), 'local_copy.ics')
    @tour = Tour.new(ics)
  end

  it "should find upcoming gigs" do
    @tour.upcoming_gigs.all? { |gig| gig.dtstart.should > DateTime.now }
  end
end

Make it go:

require 'open-uri'
class Tour
  GOOGLE_ICS = "http://google.com/calendar/ical/you@gmail.com/public/basic.ics"

  attr_accessor :cal

  def initialize(ics = GOOGLE_ICS)
    self.cal = Icalendar.parse(open(ics).read).first
  rescue *HTTP_ERRORS => error
    HoptoadNotifier.notify(error)
  end

  def upcoming_gigs
    cal.events.select  { |event| event.dtstart > DateTime.now }.
               sort_by { |event| event.dtstart }
  end
end

The default behavior uses the remote Google ics file, but we set up a constructor to allow us to easily replace the file in our specs.

The rescue is optional. HTTP_ERRORS is an Array from Suspenders I use in these circumstances in combination with Hoptoad to be notified if the HTTP calls to a remote service stop working for some reason.

If you wanted to get really fancy, you could have a nightly cron job that downloads a new copy of the .ics file and runs the spec on CI.

Example web app

I happened to be doing this in order to style the calendar nicely in a web app. He’s my Sinatra route…

get '/tour' do
  erb :tour, :locals => { :gigs => Tour.new.upcoming_gigs }
end

.. and the relevant portion of the view:

<ul class="gigs">
  <% gigs.each do |gig| %>
    <li>
      <%= gig.dtstart.strftime("%B %d, %Y %I:%M %p") %>
      <a href="http://maps.google.com/maps?hl=en&q=<%= gig.location %>"><%= gig.summary %></a>
    </li>
  <% end %>
</ul>

Check out the live example.

Bon appétit!