Pebble Fonts and Metar Watchface   4 comments

Pebble METAR WatchfaceFor Christmas, my brother gave me a Pebble smart watch. In addition to showing the time in a variety of ways, this little watch can connect to a smartphone over Bluetooth to show text messages and caller ID information. It also interfaces with a variety of sports tracking apps, including my favorite, iSmoothRun. The most exciting feature of the Pebble, though, is that it’s an open platform with a simple development kit for the C language and streamlined developer’s tools.

The holidays came in the midst of the Pebble SDK 2.0 release, which includes a JavaScript environment allowing watch apps to make use of the phone’s internet connection and GPS signal. One of the most popular applications of the Pebble-phone connection is to display the local weather in addition to the time. Partly as an exercise to learn the Pebble system and partly to create a handy way to check aviation weather, I wrote a watchface that displays the nearest METAR — a standardized aviation forecast — alongside the time.

The project involved a PHP backend, C watch app, JavaScript phone component, and — perhaps most surprisingly — becoming intimately acquainted with the Pebble font resource file format. The lion’s share of this post describes generating a custom font for Pebble; the rest gives an overview of the Metar watchface itself.

First Steps Toward a Custom Pebble Font

It’s an idea older than Wingdings and score lines on 8-bit games: Sometimes the easiest way to display a simple graphic, especially one that will be intermingled with text, is to map the graphic to a character in a custom font. This strategy is ideal for the Metar watchface: mapping symbols (eg for cloud cover) to characters not only simplifies alignment with text, but also provides an easy way for a PHP backend to output the semi-graphical forecast.

The Pebble firmware supplies a number of attractive fonts and API functions to access both built-in and custom font resources. The SDK also includes Pebble/tools/font/, a Python script that rasterizes a TrueType font at a given size, outputting a ready-for-Pebble .pfo file. This isn’t a script you have any reason to run yourself: As the included feature-custom-font example demonstrates, the Pebble build tools will run it automatically if your .ttf and appinfo.json files are appropriately configured.

PentacomThe question, then, is how to make your .ttf file. If you’re a professional designer, you probably have one of the very expensive font packages lying around. If you’re like the rest of us, you’re probably looking for a free font creation tool. I went with BitFontMaker (pictured) because at the small character sizes I’m working with, the annoyance of drawing splines or even defining corner behavior seems to be more trouble than it’s worth. BitFontMaker also includes the “map” view to remind you which of your symbols is mapped to which character.

Although the next section describes in detail one method to edit the font pixel-by-pixel, I should point out that in my experience, just converting the .ttf from BitFontMaker using the standard Pebble workflow was very close to what I was looking for. The photo at the beginning of this post was taken before any refinement.

The Pebble Font Format

Although the Pebble font automatically generated from the .ttf file was pretty good, any small binary image — including these characters — will look better when refined by hand. And to do that, we need direct access to the Pebble font data. As mentioned above, the Pebble SDK’s Pebble/tools/font/ describes the .pfo font resource file, and the PebbleDev Wiki has a page on the format as well.

Pebble CharacterA bit of trial and error revealed the meaning of each of these parameters. The font file begins with three values defining the font as a whole: a version number, the nominal height above baseline, and the number of glyphs in the font. This is followed by a lookup table character, encoding each character’s unicode value (two bytes) and the memory offset to its bitmap information. The main data block follows. For each character, it stores, in order:

  • offset_top, the distance from the font’s top-line to the top of the bitmap
  • offset_left, the distance from the right edge of the preceding character to the left edge of the bitmap
  • bitmap_height and bitmap_width, which are needed to decode the bitmap
  • horizontal_advance, the total horizontal space the character takes up, including left and right padding, and
  • the bitmap information itself, stored in a multiple of 8 bytes

The metrics are shown graphically to the right. In general, the horizontal advance is the sum of the left offset, the bitmap width, and the (unspecified) right offset. The descent of the character below the baseline is equal to the top offset plus the bitmap height minus the font’s nominal height.

Making it Pixel-Perfect

There is an interesting git repository for working with Pebble fonts, and if you look hard enough you can find a few other resources online, but most activity on this front involves adding non-Latin characters to custom Pebble firmware. After playing with the tools online, I decided to write a fresh Python script that could read a .pfo file and manipulate the font more easily in code. Additionally, the script allows exporting the font into a standard .png image. After the image is modified (using any editor), the script can read it back in and produce a corrected .pfo file for use in a Pebble app.

This script functions at two levels: As a Python module, it can be used directly through an interactive Python session or imported into your own program to handle the font backend. As a standalone command line utility, it can perform the most common functions in a self-documenting way. The basic class is PebbleFontFile, which has methods for reading and writing .pfo and .png files as well as accessing individual characters. Characters are stored as instances of the PebbleFontCharacter class, which exposes the bitmap as a 2-d boolean NumPy array. The program requires Python 2.5 or later, NumPy, and bitstring, and the Python Imaging Library, all of which can be installed with pip. You can peruse below, or download it.

One important quirk of my PebbleFontCharacter is that it abstracts away the horizontal_advance characteristic of the characters, instead using lmargin and rmargin variables as the left and right margins, respectively. The advantage is that you can change the width of a glyph without recalculating the spacing. (The bitmap dimensions are read directly from the glyph array’s shape.) The disadvantage is that it’s less intuitive, for instance, to generate an acute accent character with an advance less than the character width. (I’m not sure whether this would cause errors in the Pebble text display system.) Although it’s less intuitive, a negative value for rmargin would do the trick.

The little program did the trick for me, and I hope it’s as useful for some other Pebble programmers out there. The completed .pfo file, for those interested, is here.

The Metar Watchface

In comparison to the above, the programming behind the Metar Watchface is fairly simple. The system has three components. The first is a PHP script which takes a given latitude and longitude and returns a text representation of the nearest METAR. It takes advantage of NOAA’s Aviation Digital Data Service for timely weather information, and its spatial calculations borrow heavily from Chris Veness’s JavaScript algorithms. In short, the script will:

  1. Retrieve a list of METAR reporting sites known to NOAA.
  2. Find the nearest site to the given lat/lon with a valid METAR.
  3. Decode the METAR and convert it to a pretty format using characters from the Pebble font above.

The second component is the actual Pebble watchface app. Written in C, it relies on the builtin AppSync layer to coordinate update messages from the phone with UI updates. The bulk of the code just lays out the display, and most everything is derived from the SDK examples. Every minute, the watchface updates both the time and the “xx m ago” line. Every fifteen minutes, the watchface queries the phone app to request an update of the METAR itself.

The third and final component is the bit of JavaScript glue that runs on the phone to use its GPS chip and internet connectivity to update the Pebble watch app. It’s very similar to the pebblekit-js/weather example.

The biggest problem I’ve run into is that on iOS, the operating system liberally kills background processes to free up memory. As the phone’s JavaScript component runs through the Pebble iOS app, I find I have to re-start the Pebble app every once in a while to keep the METARs updating.

You can download the completed watchface here. It should go without saying, but this is not for navigation or mandated weather briefings. It also points to my server’s copy of the PHP code, which I might take down at any point. My real hope is that others decide to do a bit of a project like this!

MetarScreenshot1 MetarScreenshot2 MetarScreenshot3

Posted 18 Feb 2014 by John McManigle in Technical

4 responses to Pebble Fonts and Metar Watchface

Subscribe to comments with RSS.

  1. Glad to know you’re enjoying it! I figured anything that allowed you to program would be the gift that keeps on giving.

    Bill McManigle
  2. Hi John,

    thanks for your script. But I can’t get it working. I tried to convert 2 pfo files into a png.

    The first resulted in a

    ValueError: total size of new array must be unchanged

    during the processing of line

    self.bitarray = np.reshape(array[:width*height], (height,width))

    The second resulted in a fully black png without any depicted char.

    Please help.

    Best regards

    Holger Dahm

    • Hi Holger,

      If you e-mail me ( the PFO files you are trying to convert, I will take a look!


      John McManigle
  3. The script doesn’t work when using a limited set of characters. This can be set in appinfo.json as “characterRegex”: “[0-9:. ]”.Should be helpful

    Sergey Vlasov

Leave a Reply

Your email address will not be published. Required fields are marked *