bmpblock v1.2 - render HWID inside vboot_reference

The vboot_api.h doesn't require the BIOS display the ASCII HWID in
a graphical form (ARM U-Boot doesn't know how), so we have to do it
ourselves. This change makes that possible.

Summary of changes:
* bmpblk_font.h defines a structure to map ASCII chars to BMPs
* bmpblk_font utility generates that font structure
* bmpblock format is bumped to version 1.2
  - YAML file specifies font to use for $HWID
  - make_default_yaml updated to emit the new format
  - README updated to describe the difference

BUG=chromium-os:18631
TEST=manual

I've tested this on ARM, like so:

Inside the chroot, build a U-Boot that uses it:

  emerge-tegra2_kaen vboot_reference vboot_reference-firmware
  emerge-tegra2_kaen tegra-bct tegra2-public-firmware-fdts \
                     chromeos-u-boot chromeos-bootimage

Outside chroot, but in src/platform/vboot_reference:

  make
  <copy ./build/utility/bmpblk_font and ./build/utility/bmpblk_utility to
    somewhere in your $PATH>
  make clean

  cd scripts/newbitmaps/fonts
  bmpblk_font --outfile ../images/hwid_fonts.bin outdir/*

  cd scripts/newbitmaps/images
  make arm
  cd out_arm
  <edit DEFAULT.yaml>
  bmpblk_utility -z 2 -c DEFAULT.yaml arm_bmpblock.bin

  <use gbb_utility to replace the bitmaps in the U-Boot image, boot it>

The HWID string is displayed.

Change-Id: I782004a0f30c57fa1f3bb246e8c59a02c5e9f561
Reviewed-on: http://gerrit.chromium.org/gerrit/6544
Reviewed-by: Bill Richardson <wfrichar@chromium.org>
Tested-by: Bill Richardson <wfrichar@chromium.org>
diff --git a/firmware/include/bmpblk_header.h b/firmware/include/bmpblk_header.h
index 9d42123..c99f0fe 100644
--- a/firmware/include/bmpblk_header.h
+++ b/firmware/include/bmpblk_header.h
@@ -54,7 +54,7 @@
 #define BMPBLOCK_SIGNATURE_SIZE (4)
 
 #define BMPBLOCK_MAJOR_VERSION  (0x0001)
-#define BMPBLOCK_MINOR_VERSION  (0x0001)
+#define BMPBLOCK_MINOR_VERSION  (0x0002)
 
 #define MAX_IMAGE_IN_LAYOUT     (8)
 
@@ -118,6 +118,7 @@
 typedef enum ImageFormat {
   FORMAT_INVALID = 0,
   FORMAT_BMP,
+  FORMAT_FONT,
 } ImageFormat;
 
 /* Constants for ImageInfo.compression */
diff --git a/firmware/lib/include/bmpblk_font.h b/firmware/lib/include/bmpblk_font.h
new file mode 100644
index 0000000..1ffaefd
--- /dev/null
+++ b/firmware/lib/include/bmpblk_font.h
@@ -0,0 +1,65 @@
+/* Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ * This describes the internal format used to pack a set of character glpyhs so
+ * we can render strings by drawing one character at a time.
+ *
+ * The format is this:
+ *
+ *   +-------------------------+
+ *   | FontArrayHeader         |
+ *   +-------------------------+
+ *   | FontArrayEntryHeader[0] |
+ *   +-------------------------+
+ *   | raw image data[0]       |
+ *   +-------------------------+
+ *   | FontArrayEntryHeader[1] |
+ *   +-------------------------+
+ *   | raw image data[1]       |
+ *   +-------------------------+
+ *   | FontArrayEntryHeader[2] |
+ *   +-------------------------+
+ *   | raw image data[2]       |
+ *   +-------------------------+
+ *      ...
+ *   +-------------------------+
+ *   | FontArrayEntryHeader[n] |
+ *   +-------------------------+
+ *   | raw image data[n]       |
+ *   +-------------------------+
+ *
+ * The FontArrayHeader describes how many characters will be encoded.
+ * Each character encoding consists of a FontArrayEntryHeader followed
+ * immediately by the raw image data for that character.
+ */
+
+#ifndef VBOOT_REFERENCE_BMPBLK_FONT_H_
+#define VBOOT_REFERENCE_BMPBLK_FONT_H_
+
+#include "bmpblk_header.h"
+
+__pragma(pack(push, 1))  /* Support packing for MSVC. */
+
+#define FONT_SIGNATURE      "FONT"
+#define FONT_SIGNATURE_SIZE 4
+
+typedef struct FontArrayHeader {
+  uint8_t  signature[FONT_SIGNATURE_SIZE];
+  uint32_t num_entries;                 /* Number of chars encoded here. */
+} __attribute__((packed)) FontArrayHeader;
+
+typedef struct FontArrayEntryHeader {
+  uint32_t ascii;                       /* What to show. Could even be UTF? */
+  ImageInfo info;                       /* Describes the bitmap. */
+  /* The image to use follows immediately, NOT compressed. It's uncompressed
+   * because each glyph is only a few hundred bytes, but they have much in
+   * common (colormaps, for example). When we add the whole font blob to the
+   * bmpblk, it will be compressed as a single item there.
+  */
+} __attribute__((packed)) FontArrayEntryHeader;
+
+
+__pragma(pack(pop)) /* Support packing for MSVC. */
+
+#endif  /* VBOOT_REFERENCE_BMPBLK_FONT_H_ */
diff --git a/firmware/lib/vboot_display.c b/firmware/lib/vboot_display.c
index 179bf7b..9ee9419 100644
--- a/firmware/lib/vboot_display.c
+++ b/firmware/lib/vboot_display.c
@@ -5,6 +5,7 @@
  * Display functions used in kernel selection.
  */
 
+#include "bmpblk_font.h"
 #include "gbb_header.h"
 #include "utility.h"
 #include "vboot_api.h"
@@ -48,21 +49,131 @@
 
 
 
+/* Return a fixed string representing the HWID */
+static char *VbHWID(VbCommonParams* cparams) {
+  GoogleBinaryBlockHeader* gbb = (GoogleBinaryBlockHeader*)cparams->gbb_data;
+  if (0 == gbb->hwid_size ||
+      gbb->hwid_offset > cparams->gbb_size ||
+      gbb->hwid_offset + gbb->hwid_size > cparams->gbb_size) {
+    VBDEBUG(("VbHWID(): invalid hwid offset/size\n"));
+    return "{INVALID}";
+  }
+  return (char*)((uint8_t*)gbb + gbb->hwid_offset);
+}
+
+
+/* TODO: We could cache the font info to speed things up, by making the
+ * in-memory font structure distinct from the in-flash version.  We'll do that
+ * Real Soon Now. Until then, we just repeat the same linear search every time.
+ */
+typedef FontArrayHeader VbFont_t;
+
+static VbFont_t *VbInternalizeFontData(FontArrayHeader *fonthdr) {
+  /* Just return the raw data pointer for now. */
+  return (VbFont_t *)fonthdr;
+}
+
+static void VbDoneWithFontForNow(VbFont_t *ptr) {
+  /* Nothing. */
+}
+
+static ImageInfo *VbFindFontGlyph(VbFont_t *font, uint32_t ascii,
+                                  void **bufferptr, uint32_t *buffersize) {
+  uint8_t *ptr, *firstptr;
+  uint32_t max;
+  uint32_t i;
+  FontArrayEntryHeader *entry;
+
+  ptr = (uint8_t *)font;
+  max = ((FontArrayHeader *)ptr)->num_entries;
+  ptr += sizeof(FontArrayHeader);
+  firstptr = ptr;
+
+  /* Simple linear search. */
+  for(i=0; i<max; i++)
+  {
+    entry = (FontArrayEntryHeader *)ptr;
+    if (entry->ascii == ascii) {
+      /* Note: We're assuming the glpyh is uncompressed. That's true
+       * because the bmpblk_font tool doesn't compress anything. The
+       * bmpblk_utility does, but it compresses the entire font blob at once,
+       * and we've already uncompressed that before we got here.
+       */
+      *bufferptr = ptr + sizeof(FontArrayEntryHeader);
+      *buffersize = entry->info.original_size;
+      return &(entry->info);
+    }
+    ptr += sizeof(FontArrayEntryHeader)+entry->info.compressed_size;
+  }
+
+  /* NOTE: We must return something valid. We'll just use the first glyph in the
+   * font structure (so it should be something distinct).
+   */
+  entry = (FontArrayEntryHeader *)firstptr;
+  *bufferptr = firstptr + sizeof(FontArrayEntryHeader);
+  *buffersize = entry->info.original_size;
+  return &(entry->info);
+}
+
+/* Try to display the specified text at a particular position. */
+static void VbRenderTextAtPos(char *text, int right_to_left,
+                              uint32_t x, uint32_t y, VbFont_t *font) {
+  int i;
+  ImageInfo *image_info = 0;
+  void *buffer;
+  uint32_t buffersize;
+  uint32_t cur_x = x, cur_y = y;
+
+  if (!text || !font) {
+    VBDEBUG(("  VbRenderTextAtPos: invalid args\n"));
+    return;
+  }
+
+  for (i=0; text[i]; i++) {
+
+    if (text[i] == '\n') {
+      if (!image_info)
+        image_info = VbFindFontGlyph(font, text[i], &buffer, &buffersize);
+      cur_x = x;
+      cur_y += image_info->height;
+      continue;
+    }
+
+    image_info = VbFindFontGlyph(font, text[i], &buffer, &buffersize);
+
+    if (right_to_left) {
+      cur_x -= image_info->width;
+    }
+
+    if (VBERROR_SUCCESS != VbExDisplayImage(cur_x, cur_y, buffer, buffersize)) {
+      VBDEBUG(("  VbRenderTextAtPos: can't display ascii 0x%x\n", text[i]));
+    }
+
+    if (!right_to_left) {
+      cur_x += image_info->width;
+    }
+  }
+}
+
+
 /* Display a screen from the GBB. */
 VbError_t VbDisplayScreenFromGBB(VbCommonParams* cparams, uint32_t screen,
                                  VbNvContext *vncptr) {
   GoogleBinaryBlockHeader* gbb = (GoogleBinaryBlockHeader*)cparams->gbb_data;
   uint8_t* bmpfv = NULL;
-  uint8_t* fullimage = NULL;
+  void* fullimage = NULL;
   BmpBlockHeader* hdr;
   ScreenLayout* layout;
   ImageInfo* image_info;
   uint32_t screen_index;
   uint32_t localization = 0;
-  VbError_t retval = VBERROR_UNKNOWN;  /* Assume error until proven ok */
+  VbError_t retval = VBERROR_UNKNOWN;   /* Assume error until proven ok */
   uint32_t inoutsize;
   uint32_t offset;
   uint32_t i;
+  VbFont_t *font;
+  char *text_to_show;
+  int rtol = 0;
 
   /* Make sure the bitmap data is inside the GBB and is non-zero in size */
   if (0 == gbb->bmpfv_size ||
@@ -93,26 +204,26 @@
   /* TODO: ensure screen IDs match indices?  Having this translation
    * here is awful. */
   switch (screen) {
-    case VB_SCREEN_DEVELOPER_WARNING:
-      screen_index = 0;
-      break;
-    case VB_SCREEN_RECOVERY_REMOVE:
-      screen_index = 1;
-      break;
-    case VB_SCREEN_RECOVERY_NO_GOOD:
-      screen_index = 2;
-      break;
-    case VB_SCREEN_RECOVERY_INSERT:
-      screen_index = 3;
-      break;
-    case VB_SCREEN_BLANK:
-    case VB_SCREEN_DEVELOPER_EGG:
-    default:
-      /* Screens which aren't in the GBB */
-      VBDEBUG(("VbDisplayScreenFromGBB(): screen %d not in the GBB\n",
-               (int)screen));
-      retval = VBERROR_INVALID_SCREEN_INDEX;
-      goto VbDisplayScreenFromGBB_exit;
+  case VB_SCREEN_DEVELOPER_WARNING:
+    screen_index = 0;
+    break;
+  case VB_SCREEN_RECOVERY_REMOVE:
+    screen_index = 1;
+    break;
+  case VB_SCREEN_RECOVERY_NO_GOOD:
+    screen_index = 2;
+    break;
+  case VB_SCREEN_RECOVERY_INSERT:
+    screen_index = 3;
+    break;
+  case VB_SCREEN_BLANK:
+  case VB_SCREEN_DEVELOPER_EGG:
+  default:
+    /* Screens which aren't in the GBB */
+    VBDEBUG(("VbDisplayScreenFromGBB(): screen %d not in the GBB\n",
+             (int)screen));
+    retval = VBERROR_INVALID_SCREEN_INDEX;
+    goto VbDisplayScreenFromGBB_exit;
   }
   if (screen_index >= hdr->number_of_screenlayouts) {
     VBDEBUG(("VbDisplayScreenFromGBB(): screen %d index %d not in the GBB\n",
@@ -131,10 +242,8 @@
   /* Calculate offset of screen layout = start of screen stuff +
    * correct locale + correct screen. */
   offset = sizeof(BmpBlockHeader) +
-      localization * hdr->number_of_screenlayouts * sizeof(ScreenLayout) +
-      screen_index * sizeof(ScreenLayout);
-  VBDEBUG(("VbDisplayScreenFromGBB(): scr_%d_%d at offset 0x%x\n",
-           localization, screen_index, offset));
+    localization * hdr->number_of_screenlayouts * sizeof(ScreenLayout) +
+    screen_index * sizeof(ScreenLayout);
   layout = (ScreenLayout*)(bmpfv + offset);
 
   /* Display all bitmaps for the image */
@@ -142,15 +251,10 @@
     if (layout->images[i].image_info_offset) {
       offset = layout->images[i].image_info_offset;
       image_info = (ImageInfo*)(bmpfv + offset);
-      VBDEBUG(("VbDisplayScreenFromGBB: image %d: %dx%d+%d+%d %d/%d"
-               "tag %d at 0x%x\n",
-               i, image_info->width, image_info->height,
-               layout->images[i].x, layout->images[i].y,
-               image_info->compressed_size, image_info->original_size,
-               image_info->tag, offset));
-      if (COMPRESS_NONE != image_info->compression) {
-        inoutsize = image_info->original_size;
-        fullimage = (uint8_t*)VbExMalloc(inoutsize);
+      fullimage = bmpfv + offset + sizeof(ImageInfo);
+      inoutsize = image_info->original_size;
+      if (inoutsize && image_info->compression != COMPRESS_NONE) {
+        fullimage = VbExMalloc(inoutsize);
         retval = VbExDecompress(bmpfv + offset + sizeof(ImageInfo),
                                 image_info->compressed_size,
                                 image_info->compression,
@@ -159,16 +263,45 @@
           VbExFree(fullimage);
           goto VbDisplayScreenFromGBB_exit;
         }
+      }
+
+      switch(image_info->format) {
+      case FORMAT_BMP:
         retval = VbExDisplayImage(layout->images[i].x, layout->images[i].y,
                                   fullimage, inoutsize);
-        VbExFree(fullimage);
-      } else {
-        retval = VbExDisplayImage(layout->images[i].x, layout->images[i].y,
-                                  bmpfv + offset + sizeof(ImageInfo),
-                                  image_info->original_size);
+        break;
+
+      case FORMAT_FONT:
+        /* The uncompressed blob is our font structure. Cache it as needed. */
+        font = VbInternalizeFontData(fullimage);
+
+        /* TODO: handle text in general here */
+        if (TAG_HWID == image_info->tag || TAG_HWID_RTOL == image_info->tag) {
+          text_to_show = VbHWID(cparams);
+          rtol = (TAG_HWID_RTOL == image_info->tag);
+        } else {
+          text_to_show = "";
+          rtol = 0;
+        }
+
+        VbRenderTextAtPos(text_to_show, rtol,
+                          layout->images[i].x, layout->images[i].y, font);
+
+        VbDoneWithFontForNow(font);
+        break;
+
+      default:
+        VBDEBUG(("VbDisplayScreenFromGBB(): unsupported ImageFormat %d\n",
+                 image_info->format));
+        retval = VBERROR_INVALID_GBB;
       }
+
+      if (COMPRESS_NONE != image_info->compression)
+        VbExFree(fullimage);
+
       if (VBERROR_SUCCESS != retval)
         goto VbDisplayScreenFromGBB_exit;
+
     }
   }
 
@@ -189,8 +322,6 @@
                           VbNvContext *vncptr) {
   VbError_t retval;
 
-  VBDEBUG(("VbDisplayScreen(%d, %d)\n", (int)screen, force));
-
   /* Initialize display if necessary */
   if (!disp_width) {
     retval = VbExDisplayInit(&disp_width, &disp_height);
diff --git a/scripts/newbitmaps/README b/scripts/newbitmaps/README
index 5a23eaf..728d42f 100644
--- a/scripts/newbitmaps/README
+++ b/scripts/newbitmaps/README
@@ -49,6 +49,31 @@
 factory process sets the default locale to the appropriate region.
 
 
+Version 1.2. Used by any BIOS that uses "vboot_api.h"
+
+The "vboot wrapper" is a refactoring of the vboot_reference library to
+isolate our verified boot stuff from the underlying BIOS. Among other
+things, it places the burden of displaying the ASCII HWID value on the
+vboot_reference library, which means the bmpblock must contain a "font" to
+translate the ASCII characters in the HWID into graphical images. The yaml
+file must now specify a font file for the $HWID (and $HWID.rtol, if used)
+image name. For example:
+
+  bmpblock: 1.2
+  images:
+    $HWID: font.bin            # THIS IS NOW REQUIRED WHEN USING $HWID
+  screens:
+    scr_0_0:
+     - [ 0, 0, $HWID]
+  localizations:
+    - [ scr_0_0, scr_0_0, scr_0_0, scr_0_0 ]
+  locale_index:
+    en
+
+The old v1.1 bmpblock will be accepted by the vboot wrapper, but a $HWID
+screen without a corresponding font will be silently ignored.
+
+
 
 Instructions:
 
diff --git a/scripts/newbitmaps/fonts/make_ascii_bmps.py b/scripts/newbitmaps/fonts/make_ascii_bmps.py
new file mode 100755
index 0000000..df6083e
--- /dev/null
+++ b/scripts/newbitmaps/fonts/make_ascii_bmps.py
@@ -0,0 +1,87 @@
+#!/usr/bin/python -tt
+# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import optparse
+import os
+import subprocess
+import sys
+import tempfile
+
+chars = '* 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ{}-_'
+
+def main():
+  """Convert a set of text chars into individual BMPs.
+
+  This uses ImageMagick, so don't run it inside the build chroot.
+  Not all characters in the world are supported.
+  """
+
+  parser = optparse.OptionParser()
+  parser.description = ' '.join(main.__doc__.split())
+  parser.add_option("--foreground", default='#9ccaec',
+                    dest="fg", action="store", metavar="COLOR",
+                    help="foreground color (%default)")
+  parser.add_option("--background", default='#607c91',
+                    dest="bg", action="store", metavar="COLOR",
+                    help="background color (%default)")
+  parser.add_option("--font", default='Helvetica',
+                    dest="font", action="store",
+                    help="font to use (%default)")
+  parser.add_option("--size", default='22', metavar="POINTSIZE",
+                    dest="size", action="store",
+                    help="font size (%default)")
+  parser.add_option('--dir', default='./outdir',
+                    dest="outdir", action="store",
+                    help="output directory (%default)")
+  (options, args) = parser.parse_args()
+
+
+  if not os.path.isdir(options.outdir):
+    os.mkdir(options.outdir)
+
+  # ARM U-Boot is very picky about its BMPs. They have to have exactly 256
+  # colors in their colormap. Imagemagick generally tries to reduce the
+  # colormap when it can, so we have to play some games to force it not to.
+  # We'll create a gradient file with 256 colors, and then make sure that all
+  # our rendered characters use the same colormap. This makes the resulting
+  # images larger, but it also means they'll work on x86 too. Sigh.
+  (handle, gradient_file) = tempfile.mkstemp(".png")
+  os.close(handle)
+
+  cmd = ('convert', '-size', '256x1',
+         'gradient:%s-%s' % (options.fg, options.bg),
+         gradient_file)
+  print ' '.join(cmd)
+  subprocess.call(cmd)
+
+
+  count=0
+  for ascii in chars:
+    outfile = os.path.join(options.outdir,
+                           "idx%03d_%x.bmp" % (count,ord(ascii)))
+    print outfile
+    cmd = ('convert',
+           '-font', options.font,
+           '-background', options.bg,
+           '-fill', options.fg,
+           '-bordercolor', options.bg,
+           '-border', '0x3',
+           '-gravity', 'Center',
+           '-pointsize', options.size,
+           'label:%s' % ascii,
+           '-remap', gradient_file,
+           '-compress', 'none',
+           '-alpha', 'off',
+           outfile)
+    print ' '.join(cmd)
+    count += 1
+    subprocess.call(cmd)
+
+  os.unlink(gradient_file)
+
+
+# Start it all off
+if __name__ == '__main__':
+  main()
diff --git a/scripts/newbitmaps/images/DEFAULT.yaml b/scripts/newbitmaps/images/DEFAULT.yaml
index 3aa9677..fa5269e 100644
--- a/scripts/newbitmaps/images/DEFAULT.yaml
+++ b/scripts/newbitmaps/images/DEFAULT.yaml
@@ -1,11 +1,11 @@
-bmpblock: 1.1
+bmpblock: 1.2
 
 compression: 1
 
 images:
 
-  # The HWID must change for every BOM
-  # $HWID:              hwid_unknown.bmp
+  # We must specify a font blob to use to render the HWID
+  $HWID:              hwid_fonts.bin
 
   # This URL never changes
   url:               Url.bmp
diff --git a/scripts/newbitmaps/images/Makefile b/scripts/newbitmaps/images/Makefile
index b026ef5..fe2be48 100644
--- a/scripts/newbitmaps/images/Makefile
+++ b/scripts/newbitmaps/images/Makefile
@@ -14,6 +14,7 @@
 
 BASE_IMAGES=Devmode.bmp Insert.bmp Remove.bmp Yuck.bmp
 OTHER_IMAGES=Url.bmp hwid_unknown.bmp
+FONTS=hwid_fonts.bin
 
 default: outside_chroot
 	@echo "Specify a target to build for:"
@@ -36,6 +37,8 @@
 # those, so let's just assume 16:9 for future platforms to make things simpler.
 _x86_max="800x600!"
 _x86_scale="59%x78%"
+_arm_max="800x600!"
+_arm_scale="59%x78%"
 
 x86::
 	# create output directories
@@ -43,6 +46,8 @@
 	for i in localized_images/*; do \
 		mkdir -p "out_$@/$$i"; \
 	done
+	# copy stuff we need
+	cp "${FONTS}" "out_$@"
 	# scale the background pictures exactly
 	for i in ${BASE_IMAGES}; do \
 		convert $$i -scale "${_x86_max}" "out_$@/$$i"; \
@@ -58,7 +63,32 @@
 
 
 arm::
-	echo "Not sure what to do here. Please fix me."
+	# create output directories
+	mkdir -p "out_$@"
+	for i in localized_images/*; do \
+		mkdir -p "out_$@/$$i"; \
+	done
+	# copy stuff we need
+	cp "${FONTS}" "out_$@"
+	convert ${BASE_IMAGES} -append \
+		-colors 256 -unique-colors "out_$@/base_cmap.png"
+	convert localized_images/*/*.bmp -append \
+		-colors 256 -unique-colors "out_$@/loc_cmap.png"
+	# scale the background pictures exactly
+	for i in ${BASE_IMAGES}; do \
+		convert $$i -scale "${_arm_max}" \
+			-remap "out_$@/base_cmap.png" "out_$@/$$i"; \
+	done
+	# scale the localized string images using percentages
+	for i in ${OTHER_IMAGES} localized_images/*/*.bmp; do \
+		convert $$i -scale "${_arm_scale}" \
+			-remap "out_$@/loc_cmap.png" "out_$@/$$i"; \
+	done
+	# produce the new yaml
+	cd "out_$@" && ../make_default_yaml
+	# Note: hand-edit the new DEFAULT.yaml to select the shipping locales,
+	# then use bmpblk_utility to create the binary.
+
 
 
 clean:
diff --git a/scripts/newbitmaps/images/hwid_fonts.bin b/scripts/newbitmaps/images/hwid_fonts.bin
new file mode 100644
index 0000000..ecdfaef
--- /dev/null
+++ b/scripts/newbitmaps/images/hwid_fonts.bin
Binary files differ
diff --git a/scripts/newbitmaps/images/make_default_yaml b/scripts/newbitmaps/images/make_default_yaml
index 93178f5..88c540e 100755
--- a/scripts/newbitmaps/images/make_default_yaml
+++ b/scripts/newbitmaps/images/make_default_yaml
@@ -117,15 +117,15 @@
   echo "$yaml_file"
 
   # List the images. The major difference is the HWID.
-  cat >"$yaml_file" <<EOF1
-bmpblock: 1.1
+  cat >"$yaml_file" <<'EOF1'
+bmpblock: 1.2
 
 compression: 1
 
 images:
 
-  # The HWID must change for every BOM
-  # hwid:              $hwid_bmp
+  # We must specify a font blob to use to render the HWID
+  $HWID:              hwid_fonts.bin
 
   # This URL never changes
   url:               Url.bmp
diff --git a/scripts/newbitmaps/lib/bmpblock.py b/scripts/newbitmaps/lib/bmpblock.py
index 4d588d7..539159b 100755
--- a/scripts/newbitmaps/lib/bmpblock.py
+++ b/scripts/newbitmaps/lib/bmpblock.py
@@ -57,10 +57,13 @@
     # image values should all be filenames (ie, strings)
     for val in images.values():
       assert val and isinstance(val, types.StringTypes)
-    if not "$HWID" in images:
-      images["$HWID"] = os.path.join(self.libdir,'current_hwid.bmp')
-    if not "$HWID.rtol" in images:
-      images["$HWID.rtol"] = os.path.join(self.libdir, 'current_hwid.bmp')
+    # don't worry about fonts. eventually we'll have graphical mocks on host.
+    if "$HWID" in images:
+      print "WARNING: ignoring $HWID font blob"
+    if "$HWID.rtol" in images:
+      print "WARNING: ignoring $HWID.rtol font blob"
+    images["$HWID"] = os.path.join(self.libdir,'current_hwid.bmp')
+    images["$HWID.rtol"] = os.path.join(self.libdir, 'current_hwid.bmp')
 
     screens = thing["screens"]
     assert isinstance(screens, dict)
diff --git a/utility/Makefile b/utility/Makefile
index 88c8a5b..b53ee57 100644
--- a/utility/Makefile
+++ b/utility/Makefile
@@ -39,7 +39,7 @@
 		vbutil_what_keys
 
 ifeq ($(MINIMAL),)
-TARGET_NAMES += bmpblk_utility eficompress efidecompress
+TARGET_NAMES += bmpblk_font bmpblk_utility eficompress efidecompress
 endif
 
 TARGET_BINS = $(addprefix ${BUILD_ROOT}/,$(TARGET_NAMES))
@@ -65,6 +65,12 @@
 ${BUILD_ROOT}/bmpblk_util.o: bmpblk_util.c
 	$(CC) $(CFLAGS) -c $< -o $@
 
+${BUILD_ROOT}/bmpblk_font.o: bmpblk_font.c
+	$(CC) $(CFLAGS) -c $< -o $@
+
+${BUILD_ROOT}/image_types.o: image_types.c
+	$(CC) $(CFLAGS) -c $< -o $@
+
 ${BUILD_ROOT}/eficompress.o: eficompress.c
 	$(CC) $(CFLAGS) -c $< -o $@
 
@@ -79,10 +85,15 @@
 
 ${BUILD_ROOT}/bmpblk_utility: ${BUILD_ROOT}/bmpblk_utility.o \
 				${BUILD_ROOT}/bmpblk_util.o \
+				${BUILD_ROOT}/image_types.o \
 				${BUILD_ROOT}/eficompress.o \
 				${BUILD_ROOT}/efidecompress.o
 	$(CXX) -llzma -lyaml $(CFLAGS) $^ -o $@
 
+${BUILD_ROOT}/bmpblk_font: ${BUILD_ROOT}/bmpblk_font.o \
+				${BUILD_ROOT}/image_types.o
+	$(CC) $(CFLAGS) $^ -o $@
+
 # TODO: rewrite load_firmware_test to support new wrapper API
 #${BUILD_ROOT}/load_firmware_test: load_firmware_test.c $(LIBS)
 #	$(CC) $(CFLAGS) $< -o $@ $(LIBS) -lcrypto
diff --git a/utility/bmpblk_font.c b/utility/bmpblk_font.c
new file mode 100644
index 0000000..493e87f
--- /dev/null
+++ b/utility/bmpblk_font.c
@@ -0,0 +1,227 @@
+// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "bmpblk_font.h"
+#include "image_types.h"
+
+static char *progname;
+
+static void error(const char *fmt, ...)
+{
+    va_list args;
+    va_start( args, fmt );
+    fprintf(stderr, "%s: ", progname);
+    vfprintf( stderr, fmt, args );
+    va_end( args );
+}
+#define fatal(args...) do { error(args); exit(1); } while(0)
+
+
+/* Command line options */
+enum {
+  OPT_OUTFILE = 1000,
+};
+
+#define DEFAULT_OUTFILE "font.bin"
+
+
+static struct option long_opts[] = {
+  {"outfile", 1, 0,                   OPT_OUTFILE             },
+  {NULL, 0, 0, 0}
+};
+
+
+/* Print help and return error */
+static void HelpAndDie(void) {
+  fprintf(stderr,
+          "\n"
+          "%s - Create a vboot fontfile from a set of BMP files.\n"
+          "\n"
+          "Usage:  %s [OPTIONS] BMPFILE [BMPFILE...]\n"
+          "\n"
+          "Each BMP file must match *_HEX.bmp, where HEX is the hexadecimal\n"
+          "representation of the character that the file displays. The images\n"
+          "will be encoded in the given order. Typically the first image is\n"
+          "reused to represent any missing characters.\n"
+          "\n"
+          "OPTIONS are:\n"
+          "  --outfile <filename>      Output file (default is %s)\n"
+          "\n", progname, progname, DEFAULT_OUTFILE);
+  exit(1);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+// Returns pointer to buffer containing entire file, sets length.
+static void *read_entire_file(const char *filename, size_t *length) {
+  int fd;
+  struct stat sbuf;
+  void *ptr;
+
+  *length = 0;                          // just in case
+
+  if (0 != stat(filename, &sbuf)) {
+    error("Unable to stat %s: %s\n", filename, strerror(errno));
+    return 0;
+  }
+
+  if (!sbuf.st_size) {
+    error("File %s is empty\n", filename);
+    return 0;
+  }
+
+  fd = open(filename, O_RDONLY);
+  if (fd < 0) {
+    error("Unable to open %s: %s\n", filename, strerror(errno));
+    return 0;
+  }
+
+  ptr = mmap(0, sbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+  if (MAP_FAILED == ptr) {
+    error("Unable to mmap %s: %s\n", filename, strerror(errno));
+    close(fd);
+    return 0;
+  }
+
+  *length = sbuf.st_size;
+
+  close(fd);
+
+  return ptr;
+}
+
+
+// Reclaims buffer from read_entire_file().
+static void discard_file(void *ptr, size_t length) {
+  munmap(ptr, length);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+
+
+int main(int argc, char* argv[]) {
+  char* outfile = DEFAULT_OUTFILE;
+  int numimages = 0;
+  int parse_error = 0;
+  int i;
+  FILE *ofp;
+  FontArrayHeader header;
+  FontArrayEntryHeader entry;
+
+  progname = strrchr(argv[0], '/');
+  if (progname)
+    progname++;
+  else
+    progname = argv[0];
+
+  while ((i = getopt_long(argc, argv, "", long_opts, NULL)) != -1) {
+    switch (i) {
+      case OPT_OUTFILE:
+        outfile = optarg;
+        break;
+
+    default:
+        /* Unhandled option */
+        printf("Unknown option\n");
+        parse_error = 1;
+        break;
+    }
+  }
+
+  numimages = argc - optind;
+
+  if (parse_error || numimages < 1)
+    HelpAndDie();
+
+  printf("outfile is %s\n", outfile);
+  printf("numimages is %d\n", numimages);
+
+  ofp = fopen(outfile, "wb");
+  if (!ofp)
+    fatal("Unable to open %s: %s\n", outfile, strerror(errno));
+
+  memcpy(&header.signature, FONT_SIGNATURE, FONT_SIGNATURE_SIZE);
+  header.num_entries = numimages;
+  if (1 != fwrite(&header, sizeof(header), 1, ofp)) {
+    error("Can't write header to %s: %s\n", outfile, strerror(errno));
+    goto bad1;
+  }
+
+  for(i=0; i<numimages; i++) {
+    char *imgfile = argv[optind+i];
+    char *s;
+    uint32_t ascii;
+    void *imgdata = 0;
+    size_t imgsize, filesize, diff;
+
+    s = strrchr(imgfile, '_');
+    if (!s || 1 != sscanf(s, "_%x.bmp", &ascii)) { // This is not foolproof.
+      error("Unable to parse the character from filename %s\n", imgfile);
+      goto bad1;
+    }
+
+    imgdata = read_entire_file(imgfile, &imgsize);
+    if (!imgdata)
+      goto bad1;
+
+    if (FORMAT_BMP != identify_image_type(imgdata, imgsize, &entry.info)) {
+      error("%s does not contain a valid BMP image\n", imgfile);
+      goto bad1;
+    }
+
+    // Pad the image to align it on a 4-byte boundary.
+    filesize = imgsize;
+    if (imgsize % 4)
+      filesize = ((imgsize + 4) / 4) * 4;
+    diff = filesize - imgsize;
+
+    entry.ascii = ascii;
+    entry.info.tag = TAG_NONE;
+    entry.info.compression = COMPRESS_NONE; // we'll compress it all later
+    entry.info.original_size = filesize;
+    entry.info.compressed_size = filesize;
+
+    printf("%s => 0x%x %dx%d\n", imgfile, entry.ascii,
+           entry.info.width, entry.info.height);
+
+    if (1 != fwrite(&entry, sizeof(entry), 1, ofp)) {
+      error("Can't write entry to %s: %s\n", outfile, strerror(errno));
+      goto bad1;
+    }
+    if (1 != fwrite(imgdata, imgsize, 1, ofp)) {
+      error("Can't write image to %s: %s\n", outfile, strerror(errno));
+      goto bad1;
+    }
+    if (diff && 1 != fwrite("\0\0\0\0\0\0\0\0", diff, 1, ofp)) {
+      error("Can't write padding to %s: %s\n", outfile, strerror(errno));
+      goto bad1;
+    }
+
+
+    discard_file(imgdata, imgsize);
+  }
+
+  fclose(ofp);
+  return 0;
+
+bad1:
+  fclose(ofp);
+  error("Aborting\n");
+  (void) unlink(outfile);
+  exit(1);
+}
diff --git a/utility/bmpblk_util.c b/utility/bmpblk_util.c
index 84a48fb..49573af 100644
--- a/utility/bmpblk_util.c
+++ b/utility/bmpblk_util.c
@@ -285,20 +285,23 @@
     if (img->compressed_size) {
       sprintf(image_name, "img_%08x.bmp", offset);
       if (img->tag == TAG_HWID) {
-        fprintf(yfp, "  %s: %s  # %dx%d  %d/%d  tag=%d\n",
+        fprintf(yfp, "  %s: %s  # %dx%d  %d/%d  tag=%d fmt=%d\n",
                 RENDER_HWID, image_name,
                 img->width, img->height,
-                img->compressed_size, img->original_size, img->tag);
+                img->compressed_size, img->original_size,
+                img->tag, img->format);
       } else if (img->tag == TAG_HWID_RTOL) {
-        fprintf(yfp, "  %s: %s  # %dx%d  %d/%d  tag=%d\n",
+        fprintf(yfp, "  %s: %s  # %dx%d  %d/%d  tag=%d fmt=%d\n",
                 RENDER_HWID_RTOL, image_name,
                 img->width, img->height,
-                img->compressed_size, img->original_size, img->tag);
+                img->compressed_size, img->original_size,
+                img->tag, img->format);
       } else {
-        fprintf(yfp, "  img_%08x: %s  # %dx%d  %d/%d  tag=%d\n",
+        fprintf(yfp, "  img_%08x: %s  # %dx%d  %d/%d  tag=%d fmt=%d\n",
                 offset, image_name,
                 img->width, img->height,
-                img->compressed_size, img->original_size, img->tag);
+                img->compressed_size, img->original_size,
+                img->tag, img->format);
       }
       if (todir) {
         sprintf(full_path_name, "%s/%s", todir, image_name);
@@ -388,17 +391,23 @@
           ImageInfo *iptr =
             (ImageInfo *)(ptr + scr->images[i].image_info_offset);
           if (iptr->tag == TAG_HWID) {
-            fprintf(yfp, "    - [%d, %d, %s]\n",
+            fprintf(yfp, "    - [%d, %d, %s] # tag=%d fmt=%d c=%d %d/%d\n",
                     scr->images[i].x, scr->images[i].y,
-                    RENDER_HWID);
+                    RENDER_HWID, iptr->tag, iptr->format, iptr->compression,
+                    iptr->compressed_size, iptr->original_size);
           } else if (iptr->tag == TAG_HWID_RTOL) {
-            fprintf(yfp, "    - [%d, %d, %s]\n",
+            fprintf(yfp, "    - [%d, %d, %s] # tag=%d fmt=%d c=%d %d/%d\n",
                     scr->images[i].x, scr->images[i].y,
-                    RENDER_HWID_RTOL);
+                    RENDER_HWID_RTOL, iptr->tag,
+                    iptr->format, iptr->compression,
+                    iptr->compressed_size, iptr->original_size);
           } else {
-            fprintf(yfp, "    - [%d, %d, img_%08x]\n",
+            fprintf(yfp, "    - [%d, %d, img_%08x]"
+                    " # tag=%d fmt=%d c=%d %d/%d\n",
                     scr->images[i].x, scr->images[i].y,
-                    scr->images[i].image_info_offset);
+                    scr->images[i].image_info_offset,
+                    iptr->tag, iptr->format, iptr->compression,
+                    iptr->compressed_size, iptr->original_size);
           }
         }
       }
diff --git a/utility/bmpblk_utility.cc b/utility/bmpblk_utility.cc
index f4bde7c..2846746 100644
--- a/utility/bmpblk_utility.cc
+++ b/utility/bmpblk_utility.cc
@@ -6,6 +6,7 @@
 //
 
 #include "bmpblk_utility.h"
+#include "image_types.h"
 
 #include <assert.h>
 #include <errno.h>
@@ -22,29 +23,6 @@
 }
 
 
-/* BMP header, used to validate image requirements
- * See http://en.wikipedia.org/wiki/BMP_file_format
- */
-typedef struct {
-  uint8_t         CharB;                // must be 'B'
-  uint8_t         CharM;                // must be 'M'
-  uint32_t        Size;
-  uint16_t        Reserved[2];
-  uint32_t        ImageOffset;
-  uint32_t        HeaderSize;
-  uint32_t        PixelWidth;
-  uint32_t        PixelHeight;
-  uint16_t        Planes;               // Must be 1 for x86
-  uint16_t        BitPerPixel;          // 1, 4, 8, or 24 for x86
-  uint32_t        CompressionType;      // 0 (none) for x86, 1 (RLE) for arm
-  uint32_t        ImageSize;
-  uint32_t        XPixelsPerMeter;
-  uint32_t        YPixelsPerMeter;
-  uint32_t        NumberOfColors;
-  uint32_t        ImportantColors;
-} __attribute__((packed)) BMP_IMAGE_HEADER;
-
-
 static void error(const char *format, ...) {
   va_list ap;
   va_start(ap, format);
@@ -71,6 +49,10 @@
     set_compression_ = false;
     compression_ = COMPRESS_NONE;
     debug_ = debug;
+    render_hwid_ = true;
+    support_font_ = true;
+    got_font_ = false;
+    got_rtol_font_ = false;
   }
 
   BmpBlockUtil::~BmpBlockUtil() {
@@ -125,11 +107,12 @@
       for (StrImageConfigMap::iterator it = config_.images_map.begin();
            it != config_.images_map.end();
            ++it) {
-        printf("  \"%s\": filename=\"%s\" offset=0x%x tag=%d\n",
+        printf("  \"%s\": filename=\"%s\" offset=0x%x tag=%d fmt=%d\n",
                it->first.c_str(),
                it->second.filename.c_str(),
                it->second.offset,
-               it->second.data.tag);
+               it->second.data.tag,
+               it->second.data.format);
       }
       printf("%ld screens_map\n", config_.screens_map.size());
       for (StrScreenConfigMap::iterator it = config_.screens_map.begin();
@@ -206,11 +189,19 @@
       error("Syntax error in parsing bmpblock.\n");
     }
     string gotversion = (char*)event.data.scalar.value;
-    if (gotversion == "1.1") {
+    if (gotversion == "1.2") {
       render_hwid_ = true;
+      support_font_ = true;
+    } else if (gotversion == "1.1") {
+      minor_version_ = 1;
+      render_hwid_ = true;
+      support_font_ = false;
+      fprintf(stderr, "WARNING: using old format: %s\n", gotversion.c_str());
     } else if (gotversion == "1.0") {
       minor_version_ = 0;
       render_hwid_ = false;
+      support_font_ = false;
+      fprintf(stderr, "WARNING: using old format: %s\n", gotversion.c_str());
     } else {
       error("Unsupported version specified in config file (%s)\n",
             gotversion.c_str());
@@ -254,6 +245,12 @@
         config_.image_names.push_back(image_name);
         config_.images_map[image_name] = ImageConfig();
         config_.images_map[image_name].filename = image_filename;
+        if (image_name == RENDER_HWID) {
+          got_font_ = true;
+        }
+        if (image_name == RENDER_HWID_RTOL) {
+          got_rtol_font_ = true;
+        }
         break;
       case YAML_MAPPING_END_EVENT:
         yaml_event_delete(&event);
@@ -286,17 +283,22 @@
         case 2:
           screen.image_names[index1] = (char*)event.data.scalar.value;
           // Detect the special case where we're rendering the HWID string
-          // instead of displaying a bitmap.  The image name shouldn't
-          // exist in the list of images, but we will still need an
+          // instead of displaying a bitmap.  The image name may not
+          // exist in the list of images (v1.1), but we will still need an
           // ImageInfo struct to remember where to draw the text.
-          // Note that if the image name DOES exist, we still will won't
-          // display it (yet). Future versions may use that image to hold the
-          // font glpyhs, which is why we pass it around now.
+          // Note that v1.2 requires that the image name DOES exist, because
+          // the corresponding file is used to hold the font glpyhs.
           if (render_hwid_) {
             if (screen.image_names[index1] == RENDER_HWID) {
               config_.images_map[RENDER_HWID].data.tag = TAG_HWID;
+              if (support_font_ && !got_font_)
+                error("Font required in 'image:' section for %s\n",
+                      RENDER_HWID);
             } else if (screen.image_names[index1] == RENDER_HWID_RTOL) {
               config_.images_map[RENDER_HWID_RTOL].data.tag = TAG_HWID_RTOL;
+              if (support_font_ && !got_rtol_font_)
+                error("Font required in 'image:' section for %s\n",
+                      RENDER_HWID_RTOL);
             }
           }
           break;
@@ -406,13 +408,10 @@
       const string &content = read_image_file(it->second.filename.c_str());
       it->second.raw_content = content;
       it->second.data.original_size = content.size();
-      it->second.data.format = get_image_format(content);
-      switch (it->second.data.format) {
-      case FORMAT_BMP:
-        it->second.data.width = get_bmp_image_width(it->second.raw_content);
-        it->second.data.height = get_bmp_image_height(it->second.raw_content);
-        break;
-      default:
+      it->second.data.format =
+        identify_image_type(content.c_str(),
+                            (uint32_t)content.size(), &it->second.data);
+      if (FORMAT_INVALID == it->second.data.format) {
         error("Unsupported image format in %s\n", it->second.filename.c_str());
       }
       switch(compression_) {
@@ -503,31 +502,6 @@
     return content;
   }
 
-  ImageFormat BmpBlockUtil::get_image_format(const string content) {
-    if (content.size() < sizeof(BMP_IMAGE_HEADER))
-      return FORMAT_INVALID;
-    const BMP_IMAGE_HEADER *hdr = (const BMP_IMAGE_HEADER *)content.c_str();
-
-    if (hdr->CharB != 'B' || hdr->CharM != 'M' ||
-        hdr->Planes != 1 ||
-        (hdr->CompressionType != 0 && hdr->CompressionType != 1) ||
-        (hdr->BitPerPixel != 1 && hdr->BitPerPixel != 4 &&
-         hdr->BitPerPixel != 8 && hdr->BitPerPixel != 24))
-      return FORMAT_INVALID;
-
-    return FORMAT_BMP;
-  }
-
-  uint32_t BmpBlockUtil::get_bmp_image_width(const string content) {
-    const BMP_IMAGE_HEADER *hdr = (const BMP_IMAGE_HEADER *)content.c_str();
-    return hdr->PixelWidth;
-  }
-
-  uint32_t BmpBlockUtil::get_bmp_image_height(const string content) {
-    const BMP_IMAGE_HEADER *hdr = (const BMP_IMAGE_HEADER *)content.c_str();
-    return hdr->PixelHeight;
-  }
-
   void BmpBlockUtil::fill_bmpblock_header() {
     memset(&config_.header, '\0', sizeof(config_.header));
     memcpy(&config_.header.signature, BMPBLOCK_SIGNATURE,
@@ -557,11 +531,12 @@
          ++it) {
       it->second.offset = current_offset;
       if (debug_)
-        printf("  \"%s\": filename=\"%s\" offset=0x%x tag=%d\n",
+        printf("  \"%s\": filename=\"%s\" offset=0x%x tag=%d fmt=%d\n",
                it->first.c_str(),
                it->second.filename.c_str(),
                it->second.offset,
-               it->second.data.tag);
+               it->second.data.tag,
+               it->second.data.format);
       current_offset += sizeof(ImageInfo) +
         it->second.data.compressed_size;
       /* Make it 4-byte aligned. */
diff --git a/utility/image_types.c b/utility/image_types.c
new file mode 100644
index 0000000..2cc27b3
--- /dev/null
+++ b/utility/image_types.c
@@ -0,0 +1,71 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stdint.h>
+#include <string.h>
+
+#include "bmpblk_header.h"
+#include "bmpblk_font.h"
+#include "image_types.h"
+
+/* BMP header, used to validate image requirements
+ * See http://en.wikipedia.org/wiki/BMP_file_format
+ */
+typedef struct {
+  uint8_t         CharB;                // must be 'B'
+  uint8_t         CharM;                // must be 'M'
+  uint32_t        Size;
+  uint16_t        Reserved[2];
+  uint32_t        ImageOffset;
+  uint32_t        HeaderSize;
+  uint32_t        PixelWidth;
+  uint32_t        PixelHeight;
+  uint16_t        Planes;               // Must be 1 for x86
+  uint16_t        BitPerPixel;          // 1, 4, 8, or 24 for x86
+  uint32_t        CompressionType;      // 0 (none) for x86, 1 (RLE) for arm
+  uint32_t        ImageSize;
+  uint32_t        XPixelsPerMeter;
+  uint32_t        YPixelsPerMeter;
+  uint32_t        NumberOfColors;
+  uint32_t        ImportantColors;
+} __attribute__((packed)) BMP_IMAGE_HEADER;
+
+
+ImageFormat identify_image_type(const void *buf, uint32_t bufsize,
+                                ImageInfo *info) {
+
+  if (info)
+    info->format = FORMAT_INVALID;
+
+  if (bufsize < sizeof(BMP_IMAGE_HEADER) &&
+      bufsize < sizeof(FontArrayHeader)) {
+    return FORMAT_INVALID;
+  }
+
+  const BMP_IMAGE_HEADER *bhdr = buf;
+  if (bhdr->CharB == 'B' && bhdr->CharM == 'M' &&
+      bhdr->Planes == 1 &&
+      (bhdr->CompressionType == 0 || bhdr->CompressionType == 1) &&
+      (bhdr->BitPerPixel == 1 || bhdr->BitPerPixel == 4 ||
+       bhdr->BitPerPixel == 8 || bhdr->BitPerPixel == 24)) {
+    if (info) {
+      info->format = FORMAT_BMP;
+      info->width = bhdr->PixelWidth;
+      info->height = bhdr->PixelHeight;
+    }
+    return FORMAT_BMP;
+  }
+
+  const FontArrayHeader *fhdr = buf;
+  if (0 == memcmp(&fhdr->signature, FONT_SIGNATURE, FONT_SIGNATURE_SIZE) &&
+      fhdr->num_entries > 0) {
+    if (info)
+      info->format = FORMAT_FONT;
+    return FORMAT_FONT;
+  }
+
+  return FORMAT_BMP;
+}
+
+
diff --git a/utility/include/bmpblk_utility.h b/utility/include/bmpblk_utility.h
index 5b70ce2..fec2b02 100644
--- a/utility/include/bmpblk_utility.h
+++ b/utility/include/bmpblk_utility.h
@@ -7,6 +7,8 @@
 #define VBOOT_REFERENCE_BMPBLK_UTILITY_H_
 
 #include "bmpblk_header.h"
+#include "bmpblk_font.h"
+#include "image_types.h"
 
 #include <yaml.h>
 
@@ -93,9 +95,6 @@
 
   /* Useful functions */
   const string read_image_file(const char *filename);
-  ImageFormat get_image_format(const string content);
-  uint32_t get_bmp_image_width(const string content);
-  uint32_t get_bmp_image_height(const string content);
 
   /* Verbosity flags */
   bool debug_;
@@ -106,6 +105,9 @@
 
   /* Flags for version-specific features */
   bool render_hwid_;
+  bool support_font_;
+  bool got_font_;
+  bool got_rtol_font_;
 
   /* Internal variable for storing the config of BmpBlock. */
   BmpBlockConfig config_;
diff --git a/utility/include/image_types.h b/utility/include/image_types.h
new file mode 100644
index 0000000..f85a9a9
--- /dev/null
+++ b/utility/include/image_types.h
@@ -0,0 +1,25 @@
+/* Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef VBOOT_REFERENCE_IMAGE_TYPES_H_
+#define VBOOT_REFERENCE_IMAGE_TYPES_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif  /* __cplusplus */
+
+#include <stdint.h>
+#include "bmpblk_header.h"
+
+/* Identify the data. Fill in known values if info is not NULL */
+ImageFormat identify_image_type(const void *buf, uint32_t bufsize,
+                                ImageInfo *info);
+
+#ifdef __cplusplus
+}
+#endif  /* __cplusplus */
+
+#endif /* VBOOT_REFERENCE_IMAGE_TYPES_H_ */
+