# Bazaar merge directive format 2 (Bazaar 0.90)
# revision_id: colin@gibibit.com-20080705175913-xmp2j688zzymz798
# target_branch: ../../repo/trunk-clean/
# testament_sha1: f20a1f4db0c7558275043ca686fb96b6b43d1947
# timestamp: 2008-07-05 11:47:02 -0700
# source_branch: http://grub.gibibit.com/bzr/trunk-clean
# base_revision_id: colin@gibibit.com-20080704180626-bbrj490ldahovosg
# 
# Begin patch
=== modified file '.bzrignore'
--- .bzrignore	2008-07-03 04:15:16 +0000
+++ .bzrignore	2008-07-03 14:08:39 +0000
@@ -6,3 +6,5 @@
 ./conf/powerpc-ieee1275.mk
 ./conf/sparc64-ieee1275.mk
 ./build*
+./util/fonttool/build
+./util/fonttool/fonttool.iws

=== modified file 'ChangeLog'
--- ChangeLog	2008-07-04 02:26:10 +0000
+++ ChangeLog	2008-07-04 19:43:25 +0000
@@ -1,3 +1,70 @@
+2008-07-04  Colin D Bennett <colin@gibibit.com>
+
+	High resolution timer support.  Implemented for i386 CPU using TSC.
+	Extracted generic grub_millisleep() so it's linked in only as needed.
+	This requires a Pentium compatible CPU; currently the code does not
+	check for this (so it will fail on 386 and 486 machines).
+
+	* conf/i386-efi.rmk: Added TSC high resolution time module, link with
+	generic grub_millisleep() function.
+
+	* conf/i386-pc.rmk: Likewise.
+
+	* conf/sparc64-ieee1275.rmk: Add kern/generic/millisleep.c and
+	kern/generic/get_time_ms.c to kernel, to use generic time functions.
+
+	* conf/powerpc-ieee1275.rmk: Add kern/generic/millisleep.c to kernel,
+	to use generic grub_millisleep() function.
+
+	* conf/i386-linuxbios.rmk: Added kern/generic/get_time_ms.c to the
+	kernel.
+
+	* kern/generic/get_time_ms.c (grub_get_time_ms): New file.  Platform
+	independent implementation of grub_get_time_ms() using the RTC that
+	can be linked into a platform's kernel when it does not implement its
+	own specialized grub_get_time_ms() function.
+
+	* kern/generic/millisleep.c (grub_millisleep): New file.  Extracted
+	from grub_millisleep_generic() in kern/misc.c and renamed.  Changed it
+	to use grub_get_time_ms() instead of grub_get_rtc() for better
+	precision on when high resolution time is available.
+
+	* kern/misc.c (grub_millisleep_generic): Deleted.  Moved to
+	kern/generic/millisleep.c so that it is only included in the kernel
+	image when a platform does not define a specialized version.
+
+	* commands/sleep.c (grub_interruptible_millisleep): Uses
+	grub_get_time_ms() instead of grub_get_rtc() to stay in sync with
+	grub_millisleep() from kern/generic/millisleep.c.
+
+	* include/grub/i386/tsc.h (grub_get_tsc): New file.  Inline function
+	grub_get_tsc() uses x86 RDTSC instruction (available on Pentium+ CPUs)
+	to read the counter value for the TSC.
+	(grub_tsc_calibrate): Declare this function for grub_machine_init().
+
+	* kern/i386/tsc.c (grub_get_time_ms): x86 TSC support providing a high
+	resolution clock.
+	(grub_tsc_calibrate): New function to calibrate the TSC using RTC.
+
+	* include/grub/time.h (grub_get_time_ms): Added grub_get_time_ms()
+	function to return the current time in millseconds since the epoch.
+	This supports higher resolution time than grub_get_rtc() on some
+	platforms such as i386-pc, where the RTC has only about 1/18 s
+	precision but a higher precision timer such as the TSC is available.
+
+	* kern/i386/efi/init.c (grub_millisleep): Deleted.  Don't define
+	grub_millisleep() -- it just called grub_millisleep_generic() but now
+	it is linked to kern/generic/millisleep.c for the implementation.
+
+	* kern/sparc64/ieee1275/init.c (grub_millisleep): Deleted.
+
+	* kern/i386/pc/init.c (grub_machine_init): Call grub_tsc_calibrate().
+	(grub_millisleep): Deleted.
+
+	* kern/ieee1275/init.c (grub_millisleep): Deleted.
+	(grub_get_rtc): Now calls grub_get_time_ms(), which does the real
+	work.
+
 2008-07-04  Pavel Roskin  <proski@gnu.org>
 
 	* kern/i386/linuxbios/init.c (grub_machine_init): Cast addr to

=== modified file 'Makefile.in'
--- Makefile.in	2008-07-02 18:03:23 +0000
+++ Makefile.in	2008-07-04 21:40:36 +0000
@@ -85,6 +85,39 @@
 YACC = @YACC@
 UNIFONT_HEX = @UNIFONT_HEX@
 
+### Pretty output control ###
+# Set up compiler and linker commands that either is quiet (does not print
+# the command line being executed) or verbose (print the command line).
+_CC := $(CC)
+_TARGET_CC := $(TARGET_CC)
+_STRIP := $(STRIP)
+_GENMODSRC := sh $(srcdir)/genmodsrc.sh
+ifeq ($(V),1)
+ override V_PREFIX :=
+ override CC = $(_CC)
+ override TARGET_CC = $(_CC)
+ override STRIP = $(_STRIP)
+ override GENMODSRC = $(_GENMODSRC)
+ override INFO_GENCMDLIST =
+ override INFO_GENFSLIST =
+ override INFO_GENPARTMAPLIST =
+ override INFO_GEN_FINAL_COMMAND_LIST =
+ override INFO_GEN_FINAL_FS_LIST =
+ override INFO_GEN_FINAL_PARTMAP_LIST =
+else
+ override V_PREFIX := @
+ override CC = @echo "COMPILE         $<"; $(_CC)
+ override TARGET_CC = @echo "COMPILE(TARGET) $<"; $(_TARGET_CC)
+ override STRIP = @echo "STRIP           $@"; $(_STRIP)
+ override GENMODSRC = @echo "GENMODSRC       $@"; $(_GENMODSRC)
+ override INFO_GENCMDLIST = @echo "GENCMDLIST      $@"
+ override INFO_GENFSLIST = @echo "GENFSLIST       $@"
+ override INFO_GENPARTMAPLIST = @echo "GENPARTMAPLIST  $@"
+ override INFO_GEN_FINAL_COMMAND_LIST = @echo "GENCMDLIST[final]  $@"
+ override INFO_GEN_FINAL_FS_LIST = @echo "GENFSLIST[final]   $@"
+ override INFO_GEN_FINAL_PARTMAP_LIST = @echo "GENPARTMAPLIST[final]  $@"
+endif
+
 # Options.
 enable_grub_emu = @enable_grub_emu@
 enable_grub_fstest = @enable_grub_fstest@
@@ -131,13 +164,16 @@
 	  || (rm -f $@; exit 1)
 
 command.lst: $(COMMANDFILES)
-	cat $^ /dev/null | sort > $@
+	$(INFO_GEN_FINAL_COMMAND_LIST)
+	$(V_PREFIX)cat $^ /dev/null | sort > $@
 
 fs.lst: $(FSFILES)
-	cat $^ /dev/null | sort > $@
+	$(INFO_GEN_FINAL_FS_LIST)
+	$(V_PREFIX)cat $^ /dev/null | sort > $@
 
 partmap.lst: $(PARTMAPFILES)
-	cat $^ /dev/null | sort > $@
+	$(INFO_GEN_FINAL_PARTMAP_LIST)
+	$(V_PREFIX)cat $^ /dev/null | sort > $@
 
 ifeq (, $(UNIFONT_HEX))
 else

=== modified file 'commands/i386/pc/vbeinfo.c'
--- commands/i386/pc/vbeinfo.c	2007-07-21 22:32:33 +0000
+++ commands/i386/pc/vbeinfo.c	2008-07-03 13:40:06 +0000
@@ -18,6 +18,7 @@
  */
 
 #include <grub/normal.h>
+#include <grub/term.h>
 #include <grub/dl.h>
 #include <grub/arg.h>
 #include <grub/env.h>
@@ -67,6 +68,11 @@
 
   grub_memcpy (saved_video_mode_list, video_mode_list, video_mode_list_size);
   
+  grub_printf ("Legend: P=Packed pixel, D=Direct color, "
+               "mask/pos=R/G/B/reserved\n"); 
+
+  int row = 3;
+
   /* Walk through all video modes listed.  */
   for (p = saved_video_mode_list; *p != 0xFFFF; p++)
     {
@@ -103,10 +109,10 @@
       switch (mode_info_tmp.memory_model)
 	{
 	case 0x04:
-	  memory_model = "Packed Pixel";
+	  memory_model = "Packed";
 	  break;
 	case 0x06:
-	  memory_model = "Direct Color";
+	  memory_model = "Direct";
 	  break;
 
 	default:
@@ -116,12 +122,31 @@
       if (! memory_model)
 	continue;
 
-      grub_printf ("0x%03x: %d x %d x %d bpp (%s)\n",
-		   mode,
+      grub_printf ("0x%03x: %4dx%4dx%2d %s", 
+                   mode,
                    mode_info_tmp.x_resolution,
                    mode_info_tmp.y_resolution,
                    mode_info_tmp.bits_per_pixel,
-		   memory_model);
+                   memory_model);
+      if (memory_model[0] == 'D')
+        grub_printf (" mask: %d/%d/%d/%d  pos: %d/%d/%d/%d",
+                     mode_info_tmp.red_mask_size,
+                     mode_info_tmp.green_mask_size,
+                     mode_info_tmp.blue_mask_size,
+                     mode_info_tmp.rsvd_mask_size,
+                     mode_info_tmp.red_field_position,
+                     mode_info_tmp.green_field_position,
+                     mode_info_tmp.blue_field_position,
+                     mode_info_tmp.rsvd_field_position);
+      grub_printf ("\n");
+
+      if (++row >= 25)
+        {
+          grub_printf ("-- More --");
+          grub_getkey ();
+          grub_printf ("\r          \r");
+          row = 0;
+        }
     }
 
   grub_free (saved_video_mode_list);

=== modified file 'commands/sleep.c'
--- commands/sleep.c	2008-05-16 20:55:29 +0000
+++ commands/sleep.c	2008-07-04 16:55:48 +0000
@@ -43,15 +43,15 @@
   grub_printf ("%d    ", n);
 }
 
-/* Based on grub_millisleep() from kern/misc.c.  */
+/* Based on grub_millisleep() from kern/generic/millisleep.c.  */
 static int
 grub_interruptible_millisleep (grub_uint32_t ms)
 {
-  grub_uint32_t end_at;
-  
-  end_at = grub_get_rtc () + grub_div_roundup (ms * GRUB_TICKS_PER_SECOND, 1000);
-  
-  while (grub_get_rtc () < end_at)
+  grub_uint64_t start;
+
+  start = grub_get_time_ms ();
+
+  while (grub_get_time_ms () - start < ms)
     if (grub_checkkey () >= 0 &&
 	GRUB_TERM_ASCII_CHAR (grub_getkey ()) == GRUB_TERM_ESC)
       return 1;

=== modified file 'commands/videotest.c'
--- commands/videotest.c	2007-07-21 22:32:33 +0000
+++ commands/videotest.c	2008-07-03 14:38:57 +0000
@@ -17,6 +17,7 @@
  */
 
 #include <grub/machine/memory.h>
+#include <grub/time.h>
 #include <grub/video.h>
 #include <grub/types.h>
 #include <grub/dl.h>
@@ -26,15 +27,51 @@
 #include <grub/mm.h>
 #include <grub/font.h>
 #include <grub/term.h>
-
-static grub_err_t
-grub_cmd_videotest (struct grub_arg_list *state __attribute__ ((unused)),
-                    int argc __attribute__ ((unused)),
-                    char **args __attribute__ ((unused)))
-{
+#include <grub/bitmap.h>
+#include <grub/bitmap_scale.h>
+#include <grub/machine/vbe.h>   /* for grub_vbe_bios_set_display_start () test */
+/* Option array indices. */
+#define ARGINDEX_TEST_TIME 0
+#define ARGINDEX_DOUBLE_BUF 1
+
+static const struct grub_arg_option arg_options[] = {
+  {"time", 't', 0, "Time to run each test, in seconds.", 0, ARG_TYPE_INT},
+  {"dbuf", 'd', 0, "Use double buffered graphics.", 0, ARG_TYPE_NONE},
+  {0, 0, 0, 0, 0, 0}
+};
+
+#define DEFAULT_TEST_TIME 5
+
+/* Command options -- populated base on command line arguments. */
+struct videotest_options
+{
+  int test_time;
+  int double_buffering;
+};
+
+/* A 2D rectangle type.
+ * This could be worth integrating into the video API if it proves useful.*/
+struct grub_video_rect
+{
+  /* These are signed because if there are unsigned it causes Bad Things 
+   * to happen when arithmetic and comparisions involving signed types is 
+   * done.  Important signed types include offsets from absolute locations. */
+  int x;
+  int y;
+  int width;
+  int height;
+};
+typedef struct grub_video_rect grub_video_rect_t;
+
+static void
+basic_video_test (struct videotest_options *vt_opts)
+{
+  int mode_type = GRUB_VIDEO_MODE_TYPE_INDEX_COLOR;
+  if (vt_opts->double_buffering)
+    mode_type |= GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED;
   if (grub_video_setup (1024, 768,
                         GRUB_VIDEO_MODE_TYPE_INDEX_COLOR) != GRUB_ERR_NONE)
-    return grub_errno;
+    return;
 
   grub_getkey ();
 
@@ -44,7 +81,8 @@
   unsigned int width;
   unsigned int height;
   int i;
-  struct grub_font_glyph glyph;
+  grub_font_t font;
+  struct grub_font_glyph *glyph;
   struct grub_video_render_target *text_layer;
   grub_video_color_t palette[16];
 
@@ -65,8 +103,15 @@
   color = grub_video_map_rgb (0, 255, 255);
   grub_video_fill_rect (color, 100, 100, 100, 100);
 
-  grub_font_get_glyph ('*', &glyph);  
-  grub_video_blit_glyph (&glyph, color, 200 ,0);
+  font = grub_font_get ("Helvetica Bold 14");
+  if (! font)
+    {
+      grub_error (GRUB_ERR_BAD_FONT, "No font loaded.");
+      return;
+    }
+
+  glyph = grub_font_get_glyph (font, '*');  
+  grub_video_blit_glyph (glyph, color, 200 ,0);
 
   grub_video_set_viewport (x + 150, y + 150,
                            width - 150 * 2, height - 150 * 2);
@@ -77,18 +122,18 @@
 
   color = grub_video_map_rgb (255, 255, 255);
 
-  grub_font_get_glyph ('A', &glyph);
-  grub_video_blit_glyph (&glyph, color, 16, 16);
-  grub_font_get_glyph ('B', &glyph);
-  grub_video_blit_glyph (&glyph, color, 16 * 2, 16);
+  glyph = grub_font_get_glyph (font, 'A');
+  grub_video_blit_glyph (glyph, color, 16, 16);
+  glyph = grub_font_get_glyph (font, 'B');
+  grub_video_blit_glyph (glyph, color, 16 * 2, 16);
 
-  grub_font_get_glyph ('*', &glyph);
+  glyph = grub_font_get_glyph (font, '*');
 
   for (i = 0; i < 16; i++)
     {
       color = grub_video_map_color (i);
       palette[i] = color;
-      grub_video_blit_glyph (&glyph, color, 16 + i * 16, 32);
+      grub_video_blit_glyph (glyph, color, 16 + i * 16, 32);
     }
 
   grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
@@ -101,30 +146,1075 @@
                                      0, 0, width, height);
     }
 
+  grub_video_swap_buffers ();
+  grub_getkey ();
+
+  /* Test VBE set display start address. */
+  /* This should scroll the screen, first vertically,
+   * then horizontally.  The horizontal scrolling seems to
+   * only have a resolution of about 16 pixels on my VIA Mini-ITX. */
+  int vbestatus;
+  int vbeok = 0;
+  int vbeerr = 0;
+  for (i = 0; i < 50; i++)
+    {
+      vbestatus = grub_vbe_bios_set_display_start (0, i);
+      if (vbestatus == GRUB_VBE_STATUS_OK)
+        vbeok++;
+      else
+        vbeerr++;
+    }
+
+
+  grub_getkey ();
+
+  for (i = 0; i < 50; i++)
+    {
+      vbestatus = grub_vbe_bios_set_display_start (i, 50);
+      if (vbestatus == GRUB_VBE_STATUS_OK)
+        vbeok++;
+      else
+        vbeerr++;
+    }
+
   grub_getkey ();
 
   grub_video_delete_render_target (text_layer);
-
-  grub_video_restore ();
-
-  for (i = 0; i < 16; i++)
-    grub_printf("color %d: %08x\n", i, palette[i]);
-
-  grub_errno = GRUB_ERR_NONE;
+  grub_video_restore ();
+
+  grub_printf ("VBE set_display_start status: %d\n", vbestatus);
+  grub_printf ("ok: %d\n", vbeok);
+  grub_printf ("errors: %d\n", vbeerr);
+
+  grub_errno = GRUB_ERR_NONE;
+}
+
+
+
+/** 
+ * Simple opaque image blit test. 
+ * Returns the error status in grub_errno.
+ */
+static void
+bitmap_demo (struct videotest_options *vt_opts)
+{
+  int mode_type = GRUB_VIDEO_MODE_TYPE_RGB;
+  if (vt_opts->double_buffering)
+    mode_type |= GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED;
+  if (grub_video_setup (1024, 768, mode_type) != GRUB_ERR_NONE)
+    return;
+
+  grub_video_rect_t view;
+  grub_video_get_viewport ((unsigned *) &view.x, (unsigned *) &view.y,
+                           (unsigned *) &view.width,
+                           (unsigned *) &view.height);
+
+  grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
+  const int bm1width = 500, bm1height = 400;
+  struct grub_video_bitmap *bitmap1;
+
+  if (grub_video_bitmap_create (&bitmap1, bm1width, bm1height,
+                                GRUB_VIDEO_BLIT_FORMAT_RGB_888)
+      != GRUB_ERR_NONE)
+    return;
+
+  int offset = 0;
+  int x;
+  int y;
+  grub_uint8_t *data = grub_video_bitmap_get_data (bitmap1);
+  for (y = 0; y < bm1height; y++)
+    {
+      for (x = 0; x < bm1width; x++)
+        {
+          data[offset++] = x ^ y;       /* red */
+          data[offset++] = (x * 3) ^ (y * 3);   /* green */
+          data[offset++] = (x * 2) ^ (y * 2);   /* blue */
+        }
+    }
+
+  /* Blit the entire bitmap in the center of the screen. */
+  grub_video_blit_bitmap (bitmap1,
+                          GRUB_VIDEO_BLIT_REPLACE,
+                          view.x + (view.width - bm1width) / 2,
+                          view.y + (view.height - bm1height) / 2,
+                          0, 0, bm1width, bm1height);
+  grub_video_swap_buffers ();
+  grub_getkey ();
+
+  /* Blit more copies of the bitmap. */
+  /* Upper left. */
+  grub_video_blit_bitmap (bitmap1, GRUB_VIDEO_BLIT_REPLACE,
+                          view.x, view.y, 0, 0, bm1width, bm1height);
+  /* Upper right. */
+  grub_video_blit_bitmap (bitmap1,
+                          GRUB_VIDEO_BLIT_REPLACE,
+                          view.x + view.width - bm1width,
+                          view.y, 0, 0, bm1width, bm1height);
+  /* Lower left. */
+  grub_video_blit_bitmap (bitmap1,
+                          GRUB_VIDEO_BLIT_REPLACE,
+                          view.x,
+                          view.y + view.height - bm1height,
+                          0, 0, bm1width, bm1height);
+  /* Lower right. */
+  grub_video_blit_bitmap (bitmap1,
+                          GRUB_VIDEO_BLIT_REPLACE,
+                          view.x + view.width - bm1width,
+                          view.y + view.height - bm1height,
+                          0, 0, bm1width, bm1height);
+  grub_video_swap_buffers ();
+  grub_getkey ();
+
+  int clearbg = 0;              /* Boolean flag: whether to fill background. */
+  grub_video_color_t bgcolor = grub_video_map_rgb (16, 16, 96);
+
+  /* Animate the image sliding in. End when a key is pressed. */
+  int vscale = 1000;
+  int velocityx = -5000;
+  int velocityy = -8000;
+  int positionx = 100;
+  int positiony = 300;
+
+  grub_uint32_t frame_count = 0;
+  grub_uint32_t start_time = grub_get_time_ms ();
+
+  while (grub_checkkey () == -1)
+    {
+      /* If the time limit option is set, then check if it's exceeded. */
+      if (vt_opts->test_time != 0
+          && grub_get_time_ms () >= start_time + vt_opts->test_time * 1000)
+        break;
+
+      int newx = positionx + velocityx / vscale;
+      int newy = positiony + velocityy / vscale;
+
+      /* Check collision w/ left */
+      if (newx < view.x && velocityx < 0)
+        {
+          velocityx = -velocityx;
+          newx = positionx + velocityx / vscale;
+        }
+      /* Check collision w/ right */
+      if (newx + bm1width > view.x + view.width && velocityx > 0)
+        {
+          velocityx = -velocityx;
+          newx = positionx + velocityx / vscale;
+        }
+      /* Check collision w/ top */
+      if (newy < 0 && velocityy < 0)
+        {
+          velocityy = -velocityy;
+          newy = positiony + velocityy / vscale;
+        }
+      /* Check collision w/ bottom */
+      if (newy + bm1height > view.y + view.height && velocityy > 0)
+        {
+          velocityy = -velocityy;
+          newy = positiony + velocityy / vscale;
+        }
+
+      positionx = newx;
+      positiony = newy;
+
+      if (clearbg)
+        grub_video_fill_rect (bgcolor, view.x, view.y, view.width,
+                              view.height);
+
+      grub_video_blit_bitmap (bitmap1,
+                              GRUB_VIDEO_BLIT_REPLACE,
+                              view.x + positionx,
+                              view.y + positiony, 0, 0, bm1width, bm1height);
+      grub_video_swap_buffers ();
+      frame_count++;
+
+      /* Acceleration due to  gravity... 
+       * note that the y coordinate is increasing downward. */
+      velocityy += vscale / 7;
+    }
+
+  /* Calculate average frame rate. */
+  grub_uint32_t duration = grub_get_time_ms () - start_time;
+  grub_uint32_t fps_x10 = 10 * 1000 * frame_count / duration;
+
+  /* Eat the keystroke. */
+  if (grub_checkkey () != -1)
+    grub_getkey ();
+
+  grub_video_bitmap_destroy (bitmap1);
+  grub_video_restore ();
+
+  grub_printf ("Average frame rate: %d.%d fps\n", fps_x10 / 10, fps_x10 % 10);
+
+  grub_errno = GRUB_ERR_NONE;
+}
+
+
+
+/* Configuration settings for a benchmark run. */
+struct benchmark_config
+{
+  int width;
+  int height;
+  unsigned int mode_type;
+  int use_rgba_bitmaps;
+};
+
+/* Macro to make the benchmark_configs[] declaration more concise. */
+#define MODE_TYPE_RGB(bpp) (GRUB_VIDEO_MODE_TYPE_RGB \
+                            | ((bpp) << GRUB_VIDEO_MODE_TYPE_DEPTH_POS))
+
+/* The video configurations to use for the benchmark. */
+static struct benchmark_config benchmark_configs[] = {
+  {320, 200, MODE_TYPE_RGB (32), 0},
+  {640, 480, MODE_TYPE_RGB (32), 0},
+  {1024, 768, MODE_TYPE_RGB (32), 0},
+  {1024, 768, MODE_TYPE_RGB (32), 1},
+  {1024, 768, MODE_TYPE_RGB (24), 0},
+  {1024, 768, MODE_TYPE_RGB (24), 1},
+};
+
+#define NUM_BENCHMARK_CONFIGS (sizeof(benchmark_configs) \
+                               / sizeof(benchmark_configs[0]))
+
+struct benchmark_result
+{
+  /* If set to 1, the test was able to run successfully; 
+   * 0 means there was an error. */
+  int test_passed;
+
+  /* Bits per pixel for the video mode used. */
+  int bpp;
+
+  /* All fps are in fps * 100 to achieve two decimal places. */
+  /* Set to 0 to indicate the test could not be run. */
+  grub_int32_t fill_fps;
+  grub_int32_t blit_fps;
+  grub_int32_t blend_fps;
+};
+
+#define BENCHMARK_RESULT_FPS_SCALE 100
+
+static void
+move_rectangle_one_step (int *x, int *y,
+                         int width, int height,
+                         int *vx, int *vy, const grub_video_rect_t * bounds)
+{
+  int newx = *x + *vx;
+  int newy = *y + *vy;
+
+  /* Check collision w/ left */
+  if (newx < bounds->x && *vx < 0)
+    {
+      *vx = -*vx;
+      newx = *x + *vx;
+    }
+  /* Check collision w/ right */
+  if (newx + width > bounds->x + bounds->width && *vx > 0)
+    {
+      *vx = -*vx;
+      newx = *x + *vx;
+    }
+  /* Check collision w/ top */
+  if (newy < 0 && *vy < 0)
+    {
+      *vy = -*vy;
+      newy = *y + *vy;
+    }
+  /* Check collision w/ bottom */
+  if (newy + height > bounds->y + bounds->height && *vy > 0)
+    {
+      *vy = -*vy;
+      newy = *y + *vy;
+    }
+
+  *x = newx;
+  *y = newy;
+}
+
+/**
+ * Run the benchmark test for a particular video mode, which is specified 
+ * by ``*config``.  The results of the test are stored in ``*result``.
+ */
+static void
+do_benchmark (const struct benchmark_config *config,
+              struct benchmark_result *result,
+              struct videotest_options *vt_opts)
+{
+  struct grub_video_mode_info modeinfo;
+
+  result->test_passed = 0;
+  result->fill_fps = 0;
+  result->blit_fps = 0;
+  result->blend_fps = 0;
+  result->bpp = 0;
+
+  int mode_type = config->mode_type;
+  if (vt_opts->double_buffering)
+    mode_type |= GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED;
+  if (grub_video_setup (config->width, config->height,
+                        mode_type) != GRUB_ERR_NONE)
+    return;
+
+  if (grub_video_get_info (&modeinfo) == GRUB_ERR_NONE)
+    result->bpp = modeinfo.bpp;
+
+  /* Full screen bitmap blit test. */
+
+  grub_video_rect_t view;
+  grub_video_get_viewport ((unsigned *) &view.x, (unsigned *) &view.y,
+                           (unsigned *) &view.width,
+                           (unsigned *) &view.height);
+
+  grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
+
+
+  /* For measuring timing. */
+  grub_uint32_t frame_count;
+  grub_uint32_t start_time;
+  grub_uint32_t desired_stop_time;
+  grub_uint32_t duration;
+
+
+  /*** FILL TEST ***/
+
+  /* Alternates between 0 and 1 to change the color. */
+  int color_flag = 0;
+  grub_video_color_t fillcolors[2];
+  fillcolors[0] = grub_video_map_rgb (0, 0, 255);
+  fillcolors[1] = grub_video_map_rgb (255, 255, 0);
+
+  frame_count = 0;
+  start_time = grub_get_rtc ();
+  desired_stop_time = start_time + vt_opts->test_time * GRUB_TICKS_PER_SECOND;
+
+  while (grub_get_rtc () < desired_stop_time && grub_checkkey () == -1)
+    {
+      grub_video_fill_rect (fillcolors[color_flag],
+                            view.x, view.y, view.width, view.height);
+      grub_video_swap_buffers ();
+      color_flag ^= 1;
+      frame_count++;
+    }
+  if (grub_checkkey () != -1)
+    grub_getkey ();             /* Eat the keypress, if there was one. */
+
+  /* Calculate average frame rate. */
+  duration = grub_get_rtc () - start_time;
+  if (duration == 0)
+    {
+      result->fill_fps = 0;
+    }
+  else
+    {
+      result->fill_fps =
+        (BENCHMARK_RESULT_FPS_SCALE * GRUB_TICKS_PER_SECOND
+         * frame_count / duration);
+    }
+
+
+  /*** BLIT TEST ***/
+
+  /* Generate two bitmaps, the same size as the screen. */
+  const int bitmapwidth = view.width, bitmapheight = view.height;
+  struct grub_video_bitmap *bitmap1;
+  struct grub_video_bitmap *bitmap2;
+  enum grub_video_blit_format bitmap_format = config->use_rgba_bitmaps
+    ? GRUB_VIDEO_BLIT_FORMAT_RGBA_8888 : GRUB_VIDEO_BLIT_FORMAT_RGB_888;
+
+  if (grub_video_bitmap_create (&bitmap1, bitmapwidth, bitmapheight,
+                                bitmap_format) != GRUB_ERR_NONE)
+    return;
+
+  if (grub_video_bitmap_create (&bitmap2, bitmapwidth, bitmapheight,
+                                bitmap_format) != GRUB_ERR_NONE)
+    {
+      grub_video_bitmap_destroy (bitmap1);
+      return;
+    }
+
+  int offset;
+  int x;
+  int y;
+  grub_uint8_t *data;
+
+  offset = 0;
+  data = grub_video_bitmap_get_data (bitmap1);
+  for (y = 0; y < bitmapheight; y++)
+    {
+      for (x = 0; x < bitmapwidth; x++)
+        {
+          data[offset++] = x ^ y;       /* red */
+          data[offset++] = (x * 3) ^ (y * 3);   /* green */
+          data[offset++] = (x * 2) ^ (y * 2);   /* blue */
+          if (config->use_rgba_bitmaps)
+            data[offset++] = 255;
+        }
+    }
+
+  offset = 0;
+  data = grub_video_bitmap_get_data (bitmap2);
+  for (y = 0; y < bitmapheight; y++)
+    {
+      for (x = 0; x < bitmapwidth; x++)
+        {
+          data[offset++] = x + y;       /* red */
+          data[offset++] = x * x + y * y;       /* green */
+          data[offset++] = x * x / 4 + y * y / 4;       /* blue */
+          if (config->use_rgba_bitmaps)
+            data[offset++] = 255;
+        }
+    }
+
+
+  /* Now do the blit test, alternating between the two bitmaps. */
+  frame_count = 0;
+  start_time = grub_get_rtc ();
+  desired_stop_time = start_time + vt_opts->test_time * GRUB_TICKS_PER_SECOND;
+
+  int cur = 0;                  /* Which bitmap to draw this frame. */
+  while (grub_get_rtc () < desired_stop_time && grub_checkkey () == -1)
+    {
+      struct grub_video_bitmap *current_bitmap = cur == 0 ? bitmap1 : bitmap2;
+      grub_video_blit_bitmap (current_bitmap,
+                              GRUB_VIDEO_BLIT_REPLACE,
+                              view.x, view.y, 0, 0, bitmapwidth,
+                              bitmapheight);
+      grub_video_swap_buffers ();
+      frame_count++;
+      cur ^= 1;
+    }
+  if (grub_checkkey () != -1)
+    grub_getkey ();             /* Eat the keypress, if there was once. */
+
+  /* Calculate average frame rate. */
+  duration = grub_get_rtc () - start_time;
+  if (duration == 0)
+    {
+      result->blit_fps = 0;
+    }
+  else
+    {
+      result->blit_fps =
+        (BENCHMARK_RESULT_FPS_SCALE * GRUB_TICKS_PER_SECOND
+         * frame_count / duration);
+    }
+
+  grub_video_bitmap_destroy (bitmap2);
+  grub_video_bitmap_destroy (bitmap1);
+
+
+
+  /*** BLEND TEST ***/
+
+  /* Generate two bitmaps, with alpha translucency. */
+  const int bbw = view.width * 2 / 3;
+  const int bbh = view.height * 2 / 3;
+
+  if (grub_video_bitmap_create (&bitmap1, bbw, bbh,
+                                GRUB_VIDEO_BLIT_FORMAT_RGBA_8888) !=
+      GRUB_ERR_NONE)
+    return;
+
+  if (grub_video_bitmap_create (&bitmap2, bbw, bbh,
+                                GRUB_VIDEO_BLIT_FORMAT_RGBA_8888) !=
+      GRUB_ERR_NONE)
+    {
+      grub_video_bitmap_destroy (bitmap1);
+      return;
+    }
+
+  offset = 0;
+  data = grub_video_bitmap_get_data (bitmap1);
+  for (y = 0; y < bbh; y++)
+    {
+      for (x = 0; x < bbw; x++)
+        {
+          /* Calculate a to be increasing away from the center. */
+          int dx = 256 * (x - bbw / 2) / (bbw / 2);
+          int dy = 256 * (y - bbh / 2) / (bbh / 2);
+          int a = dx * dx + dy * dy;
+          /* range for a = 0 .. 2*(256^2) = 2*2^16 = 2^17 */
+          a >>= 17 - 8;         /* Make range 0..256. */
+          if (a > 255)
+            a = 255;
+
+          data[offset++] = x ^ y;       /* red */
+          data[offset++] = (x * 3) ^ (y * 3);   /* green */
+          data[offset++] = (x * 2) ^ (y * 2);   /* blue */
+          data[offset++] = 255 - a;
+        }
+    }
+
+  offset = 0;
+  data = grub_video_bitmap_get_data (bitmap2);
+  for (y = 0; y < bbh; y++)
+    {
+      for (x = 0; x < bbw; x++)
+        {
+          data[offset++] = x + y;       /* red */
+          data[offset++] = x * x + y * y;       /* green */
+          data[offset++] = x * x / 4 + y * y / 4;       /* blue */
+          data[offset++] = 255;
+        }
+    }
+
+  frame_count = 0;
+  start_time = grub_get_rtc ();
+  desired_stop_time = start_time + vt_opts->test_time * GRUB_TICKS_PER_SECOND;
+
+
+  grub_video_color_t bgcolor = grub_video_map_rgb (80, 80, 80);
+
+  /* Bitmap locations. */
+  int b1x = 0;
+  int b1y = 0;
+  int b2x = view.width - bbw;
+  int b2y = 0;
+  /* Bitmap velocities. */
+  int b1vx = 8;
+  int b1vy = 12;
+  int b2vx = -10;
+  int b2vy = 9;
+
+  while (grub_get_rtc () < desired_stop_time && grub_checkkey () == -1)
+    {
+      move_rectangle_one_step (&b1x, &b1y, bbw, bbh, &b1vx, &b1vy, &view);
+      move_rectangle_one_step (&b2x, &b2y, bbw, bbh, &b2vx, &b2vy, &view);
+      grub_video_fill_rect (bgcolor, view.x, view.y, view.width, view.height);
+      grub_video_blit_bitmap (bitmap2,
+                              GRUB_VIDEO_BLIT_BLEND,
+                              b2x, b2y, 0, 0, bbw, bbh);
+      grub_video_blit_bitmap (bitmap1,
+                              GRUB_VIDEO_BLIT_BLEND,
+                              b1x, b1y, 0, 0, bbw, bbh);
+      grub_video_swap_buffers ();
+      frame_count++;
+      cur ^= 1;
+    }
+  if (grub_checkkey () != -1)
+    grub_getkey ();             /* Eat the keypress, if there was once. */
+
+  /* Calculate average frame rate. */
+  duration = grub_get_rtc () - start_time;
+  if (duration == 0)
+    {
+      result->blend_fps = 0;
+    }
+  else
+    {
+      result->blend_fps =
+        (BENCHMARK_RESULT_FPS_SCALE * GRUB_TICKS_PER_SECOND
+         * frame_count / duration);
+    }
+
+  grub_video_bitmap_destroy (bitmap2);
+  grub_video_bitmap_destroy (bitmap1);
+
+  grub_video_restore ();
+  result->test_passed = 1;
+}
+
+/**
+ * Run a benchmark test in a series of video modes.
+ * The results are reported in tabular form.  This will be helpful to
+ * determine how effective various optimizations are.
+ */
+static void
+benchmark_test (struct videotest_options *vt_opts)
+{
+  unsigned int i;
+  struct benchmark_result results[NUM_BENCHMARK_CONFIGS];
+
+  /* Set option default values. */
+  if (vt_opts->test_time == 0)
+    vt_opts->test_time = DEFAULT_TEST_TIME;
+
+  /* Run benchmarks. */
+  for (i = 0; i < NUM_BENCHMARK_CONFIGS; i++)
+    {
+      do_benchmark (&benchmark_configs[i], &results[i], vt_opts);
+    }
+
+  /* Display results. */
+  grub_printf ("Benchmark results (in frames/s):\n");
+  grub_printf ("(W=Width, H=Height, B=Bits per pixel,\n"
+               " T=Mode Type, A=Bitmap Alpha)\n");
+  grub_printf ("   W    H  B T A     FILL     BLIT    BLEND\n");
+  for (i = 0; i < NUM_BENCHMARK_CONFIGS; i++)
+    {
+      struct benchmark_config *c = &benchmark_configs[i];
+      struct benchmark_result *r = &results[i];
+
+      if (r->test_passed)
+        {
+          grub_printf ("%4dx%4d %2d %d %d: %4d.%02d  %4d.%02d  %4d.%02d\n",
+                       c->width, c->height, r->bpp,
+                       c->mode_type & 0x0F,
+                       c->use_rgba_bitmaps,
+                       r->fill_fps / BENCHMARK_RESULT_FPS_SCALE,
+                       r->fill_fps % BENCHMARK_RESULT_FPS_SCALE,
+                       r->blit_fps / BENCHMARK_RESULT_FPS_SCALE,
+                       r->blit_fps % BENCHMARK_RESULT_FPS_SCALE,
+                       r->blend_fps / BENCHMARK_RESULT_FPS_SCALE,
+                       r->blend_fps % BENCHMARK_RESULT_FPS_SCALE);
+        }
+      else
+        {
+          grub_printf ("%4dx%4d %2d %d  Not supported.\n",
+                       c->width, c->height,
+                       ((c->mode_type & GRUB_VIDEO_MODE_TYPE_DEPTH_MASK)
+                        >> GRUB_VIDEO_MODE_TYPE_DEPTH_POS),
+                       c->mode_type & 0x0F);
+        }
+    }
+
+  grub_errno = GRUB_ERR_NONE;
+}
+
+
+/**
+ * Test time functions.
+ */
+static void
+clock_test (struct videotest_options *vt_opts)
+{
+  int mode_type = GRUB_VIDEO_MODE_TYPE_RGB;
+  if (vt_opts->double_buffering)
+    mode_type |= GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED;
+  if (grub_video_setup (640, 480, mode_type) != GRUB_ERR_NONE)
+    return;
+
+  grub_video_rect_t view;
+  grub_video_get_viewport ((unsigned *) &view.x, (unsigned *) &view.y,
+                           (unsigned *) &view.width,
+                           (unsigned *) &view.height);
+
+  grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
+
+  /* Draw a progress bar that animates in sync with time. */
+
+  grub_video_rect_t bar_frame;
+  bar_frame.width = view.width - 2 * view.width / 10;
+  bar_frame.height = view.height / 20;
+  bar_frame.x = view.x + view.width / 10;
+  bar_frame.y = view.y + view.height - bar_frame.height - view.height / 10;
+
+  grub_video_color_t bgcolor = grub_video_map_rgb (50, 50, 50);
+  grub_video_color_t framecolor = grub_video_map_rgb (255, 255, 255);
+  grub_video_color_t barbgcolor = grub_video_map_rgb (0, 0, 128);
+  grub_video_color_t barcolor = grub_video_map_rgb (100, 100, 255);
+
+  grub_uint32_t frame_count;
+  grub_uint64_t start_time;
+  grub_uint64_t barstart;
+  grub_uint32_t barlength = 1000;
+
+  grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
+
+  frame_count = 0;
+  start_time = grub_get_time_ms ();
+  barstart = grub_get_time_ms ();
+
+  while (grub_checkkey () == -1)
+    {
+      grub_uint64_t now;
+      grub_uint32_t bartime;
+      now = grub_get_time_ms ();
+      /* If the time limit option is set, then check if it's exceeded. */
+      if (vt_opts->test_time != 0
+          && now >= start_time + vt_opts->test_time * 1000)
+        break;
+      bartime = now - barstart;
+      if (bartime > barlength)
+        {
+          barstart = grub_get_time_ms ();       /* Start over. */
+          bartime = barlength;
+        }
+
+      /* Clear screen. */
+      grub_video_fill_rect (bgcolor, view.x, view.y, view.width, view.height);
+
+      /* Border. */
+      grub_video_fill_rect (framecolor,
+                            bar_frame.x - 1, bar_frame.y - 1,
+                            bar_frame.width + 2, bar_frame.height + 2);
+
+      /* Bar background. */
+      int barwidth = bar_frame.width * bartime / barlength;
+      grub_video_fill_rect (barbgcolor, bar_frame.x + barwidth,
+                            bar_frame.y, bar_frame.width - barwidth,
+                            bar_frame.height);
+      /* Bar foreground. */
+      grub_video_fill_rect (barcolor, bar_frame.x, bar_frame.y,
+                            barwidth, bar_frame.height);
+      grub_video_swap_buffers ();
+      frame_count++;
+    }
+
+  grub_uint32_t duration = grub_get_time_ms () - start_time;
+  grub_uint32_t fps_x10 = 10 * 1000 * frame_count / duration;
+
+  if (grub_checkkey () != -1)
+    grub_getkey ();             /* Eat the keypress, if there was one. */
+  grub_video_restore ();
+  grub_printf ("Average frame rate: %d.%d fps\n", fps_x10 / 10, fps_x10 % 10);
+}
+
+
+/**
+ * Test double buffering.
+ */
+static void
+doublebuf_test (struct videotest_options *vt_opts)
+{
+  int mode_type = GRUB_VIDEO_MODE_TYPE_RGB;
+  if (vt_opts->double_buffering)
+    mode_type |= GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED;
+  if (grub_video_setup (640, 480, mode_type) != GRUB_ERR_NONE)
+    return;
+
+  grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
+
+  grub_video_color_t red = grub_video_map_rgb (255, 50, 50);
+  grub_video_color_t yellow = grub_video_map_rgb (255, 255, 0);
+  grub_video_color_t green = grub_video_map_rgb (20, 255, 20);
+  grub_video_color_t blue = grub_video_map_rgb (50, 50, 255);
+  grub_video_color_t black = grub_video_map_rgb (0, 0, 0);
+  grub_video_color_t bgcolor = grub_video_map_rgb (255, 255, 255);
+
+  grub_video_fill_rect (bgcolor, 0, 0, 640, 480);
+  grub_video_fill_rect (red, 100, 100, 200, 200);
+  grub_video_swap_buffers ();
+  grub_getkey ();
+
+  grub_video_fill_rect (bgcolor, 0, 0, 640, 480);
+  grub_video_fill_rect (yellow, 120, 120, 200, 200);
+  grub_video_swap_buffers ();
+  grub_getkey ();
+
+  grub_video_fill_rect (bgcolor, 0, 0, 640, 480);
+  grub_video_fill_rect (green, 140, 140, 200, 200);
+  grub_video_swap_buffers ();
+  grub_getkey ();
+
+  grub_video_fill_rect (bgcolor, 0, 0, 640, 480);
+  grub_video_fill_rect (blue, 160, 160, 200, 200);
+  grub_video_swap_buffers ();
+  grub_getkey ();
+
+  grub_video_fill_rect (bgcolor, 0, 0, 640, 480);
+  grub_video_fill_rect (black, 180, 180, 200, 200);
+  grub_video_swap_buffers ();
+  grub_getkey ();
+
+  grub_video_restore ();
+}
+
+
+/**
+ * Test text rendering.
+ */
+static void
+text_test (struct videotest_options *vt_opts)
+{
+  grub_video_color_t color;
+  const char *s;
+  int view_x;
+  int view_y;
+  int view_width;
+  int view_height;
+  int xpos;
+  int ypos;
+  int i;
+  grub_font_t font1;
+  grub_font_t font2;
+  grub_font_t font3;
+
+  int mode_type = GRUB_VIDEO_MODE_TYPE_RGB;
+  if (vt_opts->double_buffering)
+    mode_type |= GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED;
+  if (grub_video_setup (1024, 768, mode_type) != GRUB_ERR_NONE)
+    return;
+
+  grub_video_get_viewport ((unsigned int *) &view_x,
+                           (unsigned int *) &view_y,
+                           (unsigned int *) &view_width,
+                           (unsigned int *) &view_height);
+  grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
+
+  if (!(font1 = grub_font_get ("New Century Schoolbook 24"))
+      || !(font2 = grub_font_get ("Helvetica Bold 14"))
+      || !(font3 = grub_font_get ("Helvetica 10")))
+    {
+      grub_video_restore ();
+      grub_error (GRUB_ERR_BAD_FONT, "No font loaded.");
+      return;
+    }
+
+  color = grub_video_map_rgb (0, 0, 0);
+  grub_video_fill_rect (color, 0, 0, view_width, view_height);
+
+  color = grub_video_map_rgb (255, 0, 0);
+  grub_video_fill_rect (color, 0, 0, 100, 100);
+
+  color = grub_video_map_rgb (0, 255, 255);
+  grub_video_fill_rect (color, 100, 100, 100, 100);
+
+  color = grub_video_map_rgb (255, 255, 255);
+
+  xpos = 10;
+  ypos = 30;
+  s = "Hello, World!";
+  for (i = 0; i < 40; i++)
+    {
+      if (xpos + grub_font_get_string_width (font1, s) >= view_width)
+        {
+          /* The string will wrap; go to the beginning of the next line. */
+          xpos = 10;
+          ypos += (grub_font_get_descent (font1) 
+                   + grub_font_get_ascent (font1));
+        }
+      grub_video_draw_string ("Hello, World!",
+                              font1, grub_video_map_rgb (255, 255, 0), 
+                              view_x + xpos, view_y + ypos);
+
+      xpos += grub_font_get_string_width (font1, s);
+    }
+
+  xpos = 300;
+  ypos = 450;
+  grub_video_draw_string (grub_font_get_name (font1),
+                          font1, grub_video_map_rgb (255, 255, 255),
+                          view_x + xpos, view_y + ypos);
+  ypos += grub_font_get_descent (font1) + grub_font_get_ascent (font2) + 2;
+  grub_video_draw_string (grub_font_get_name (font2),
+                          font2, grub_video_map_rgb (255, 255, 255),
+                          view_x + xpos, view_y + ypos);
+  ypos += grub_font_get_descent (font2) + grub_font_get_ascent (font3) + 2;
+  grub_video_draw_string (grub_font_get_name (font3),
+                          font3, grub_video_map_rgb (255, 255, 255),
+                          view_x + xpos, view_y + ypos);
+
+  grub_video_swap_buffers ();
+  grub_getkey ();
+  grub_video_restore ();
+  grub_errno = GRUB_ERR_NONE;
+}
+
+
+/**
+ * Test bitmap scaling.
+ */
+static void
+scale_test (struct videotest_options *vt_opts)
+{
+  int mode_type = GRUB_VIDEO_MODE_TYPE_RGB;
+  if (vt_opts->double_buffering)
+    mode_type |= GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED;
+  if (grub_video_setup (1024, 768, mode_type) != GRUB_ERR_NONE)
+    return;
+
+  grub_errno = GRUB_ERR_NONE;
+  grub_video_rect_t view;
+  grub_video_get_viewport ((unsigned *) &view.x, (unsigned *) &view.y,
+                           (unsigned *) &view.width,
+                           (unsigned *) &view.height);
+
+  grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
+
+  grub_font_t font;
+  if (!(font = grub_font_get ("Helvetica 10")))
+    {
+      grub_video_restore ();
+      grub_error (GRUB_ERR_BAD_FONT, "No font loaded.");
+      return;
+    }
+
+  grub_video_color_t color;
+
+  int text_y = 0;
+  int text_height = 25;
+
+  color = grub_video_map_rgb (44, 44, 200);
+  grub_video_fill_rect (color, view.x, view.y, view.width, view.height);
+  color = grub_video_map_rgb (255, 255, 255);
+
+  grub_video_draw_string ("Loading image", 
+                          font, color, 10, text_y += text_height);
+
+  enum grub_video_bitmap_scale_method scale_method = 
+    GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST;
+  const char *bitmap_name = "/boot/images/wallpaper.tga";
+  struct grub_video_bitmap *bitmap;
+  grub_video_bitmap_load (&bitmap, bitmap_name);
+  if (grub_errno != GRUB_ERR_NONE)
+    {
+      grub_video_draw_string ("Error loading bitmap", 
+                              font, color, 10, text_y += text_height);
+    }
+  else 
+    {
+      grub_video_draw_string ("Original image", 
+                              font, color, 10, text_y += text_height);
+      grub_video_blit_bitmap (bitmap, GRUB_VIDEO_BLIT_BLEND,
+                              400, text_y - 10,
+                              0, 0, grub_video_bitmap_get_width (bitmap),
+                              grub_video_bitmap_get_height (bitmap));
+
+      struct grub_video_bitmap *bitmap2;
+      if (grub_video_bitmap_create_scaled (&bitmap2, 40, 40,
+                                              bitmap,
+                                              scale_method)
+          != GRUB_ERR_NONE)
+        {
+          grub_video_draw_string ("Error scaling down bitmap", 
+                                  font, color, 10, text_y += text_height);
+        }
+      else
+        {
+          grub_video_draw_string ("Scaled down version", 
+                                  font, color, 10, text_y += text_height);
+          grub_video_blit_bitmap (bitmap2, GRUB_VIDEO_BLIT_BLEND,
+                                  400, text_y + 100,
+                                  0, 0, grub_video_bitmap_get_width (bitmap2),
+                                  grub_video_bitmap_get_height (bitmap2));
+          grub_video_bitmap_destroy (bitmap2);
+        }
+
+      struct grub_video_bitmap *bitmap3;
+      if (grub_video_bitmap_create_scaled (&bitmap3, 500, 300, bitmap,
+                                           scale_method)
+          != GRUB_ERR_NONE)
+        {
+          grub_video_draw_string ("Error scaling up bitmap", 
+                                  font, color, 10, text_y += text_height);
+        }
+      else
+        {
+          grub_video_draw_string ("Scaled up version", 
+                                  font, color, 10, text_y += text_height);
+          grub_video_blit_bitmap (bitmap3, GRUB_VIDEO_BLIT_BLEND,
+                                  400, text_y + 50,
+                                  0, 0, grub_video_bitmap_get_width (bitmap3),
+                                  grub_video_bitmap_get_height (bitmap3));
+          grub_video_bitmap_destroy (bitmap3);
+        }
+    }
+
+  grub_video_swap_buffers ();
+  grub_getkey ();
+  grub_video_bitmap_destroy (bitmap);
+  grub_video_restore ();
+}
+
+
+/** Print a list of the available tests. */
+static void list_tests (void);
+
+/**
+ * Video test command.  Takes an argument specifying the test to run.
+ */
+static grub_err_t
+grub_cmd_videotest (struct grub_arg_list *state, int argc, char **args)
+{
+  int i;
+  struct videotest_options vt_opts;
+  /* Pointer to the test function. */
+  void (*test_func) (struct videotest_options *) = NULL;
+
+  vt_opts.test_time =
+    state[ARGINDEX_TEST_TIME].set
+    ? grub_strtoul (state[ARGINDEX_TEST_TIME].arg, 0, 0) : 0;
+  vt_opts.double_buffering = state[ARGINDEX_DOUBLE_BUF].set;
+
+  /* Parse command line arguments to determine the test to run. */
+  for (i = 0; i < argc; i++)
+    {
+      char *arg = args[i];
+      if (grub_strcmp (arg, "list") == 0)
+        {
+          list_tests ();
+          return GRUB_ERR_NONE;
+        }
+      else if (grub_strcmp (arg, "basic") == 0)
+        {
+          test_func = basic_video_test;
+        }
+      else if (grub_strcmp (arg, "bench") == 0)
+        {
+          test_func = benchmark_test;
+        }
+      else if (grub_strcmp (arg, "bitmaps") == 0)
+        {
+          test_func = bitmap_demo;
+        }
+      else if (grub_strcmp (arg, "clock") == 0)
+        {
+          test_func = clock_test;
+        }
+      else if (grub_strcmp (arg, "doublebuf") == 0)
+        {
+          test_func = doublebuf_test;
+        }
+      else if (grub_strcmp (arg, "text") == 0)
+        {
+          test_func = text_test;
+        }
+      else if (grub_strcmp (arg, "scale") == 0)
+        {
+          test_func = scale_test;
+        }
+      else
+        {
+          grub_printf ("Error: Unknown test `%s'\n", arg);
+          grub_errno = GRUB_ERR_BAD_ARGUMENT;
+          return grub_errno;
+        }
+    }
+
+  if (test_func == NULL)
+    {
+      grub_printf ("Usage: videotest TESTNAME    Run a test.\n");
+      grub_printf ("       videotest list        List available tests.\n");
+      grub_printf ("\n");
+      list_tests ();
+    }
+  else
+    {
+      test_func (&vt_opts);
+    }
+
   return grub_errno;
 }
 
-GRUB_MOD_INIT(videotest)
+static void
+list_tests (void)
+{
+  grub_printf ("Available tests\n");
+  grub_printf ("===============\n");
+  grub_printf ("basic       Basic video test with filled rectangles,\n");
+  grub_printf ("            offscreen rendering targets, some text.\n");
+  grub_printf ("bench       Run a performance benchmark.\n");
+  grub_printf ("bitmaps     Test generating and blitting bitmaps.\n");
+  grub_printf ("clock       Test time functions w/ animated progress bar.\n");
+  grub_printf ("doublebuf   Test double buffering.\n");
+  grub_printf ("text        Test text rendering.\n");
+  grub_printf ("scale       Test image scaling.\n");
+  grub_printf ("\n");
+}
+
+
+GRUB_MOD_INIT (videotest)
 {
   grub_register_command ("videotest",
                          grub_cmd_videotest,
                          GRUB_COMMAND_FLAG_BOTH,
-                         "videotest",
-                         "Test video subsystem",
-                         0);
+                         "videotest TEST",
+                         "Run the specified video subsystem test.",
+                         arg_options);
 }
 
-GRUB_MOD_FINI(videotest)
+GRUB_MOD_FINI (videotest)
 {
   grub_unregister_command ("videotest");
 }

=== modified file 'conf/common.rmk'
--- conf/common.rmk	2008-07-02 06:20:11 +0000
+++ conf/common.rmk	2008-07-05 17:04:34 +0000
@@ -287,6 +287,7 @@
 	cmp.mod cat.mod help.mod font.mod search.mod		\
 	loopback.mod fs_uuid.mod configfile.mod echo.mod	\
 	terminfo.mod test.mod blocklist.mod hexdump.mod		\
+	protomenu.mod                                           \
 	read.mod sleep.mod loadenv.mod
 
 # For hello.mod.
@@ -330,7 +331,7 @@
 help_mod_LDFLAGS = $(COMMON_LDFLAGS)
 
 # For font.mod.
-font_mod_SOURCES = font/manager.c
+font_mod_SOURCES = font/font_cmd.c font/font.c font/loader.c
 font_mod_CFLAGS = $(COMMON_CFLAGS)
 font_mod_LDFLAGS = $(COMMON_LDFLAGS)
 
@@ -374,6 +375,11 @@
 hexdump_mod_CFLAGS = $(COMMON_CFLAGS)
 hexdump_mod_LDFLAGS = $(COMMON_LDFLAGS)
 
+# For protomenu.mod.
+protomenu_mod_SOURCES = protomenu/protomenu.c
+protomenu_mod_CFLAGS = $(COMMON_CFLAGS)
+protomenu_mod_LDFLAGS = $(COMMON_LDFLAGS)
+
 # For read.mod.
 read_mod_SOURCES = commands/read.c
 read_mod_CFLAGS = $(COMMON_CFLAGS)

=== modified file 'conf/i386-efi.rmk'
--- conf/i386-efi.rmk	2008-06-19 04:14:16 +0000
+++ conf/i386-efi.rmk	2008-07-03 04:19:16 +0000
@@ -84,7 +84,9 @@
 	kern/misc.c kern/mm.c kern/loader.c kern/rescue.c kern/term.c \
 	kern/i386/dl.c kern/i386/efi/init.c kern/parser.c kern/partition.c \
 	kern/env.c symlist.c kern/efi/efi.c kern/efi/init.c kern/efi/mm.c \
-	term/efi/console.c disk/efi/efidisk.c
+	term/efi/console.c disk/efi/efidisk.c \
+	kern/i386/tsc.c \
+	kern/generic/millisleep.c
 kernel_mod_HEADERS = arg.h boot.h cache.h device.h disk.h dl.h elf.h elfload.h \
 	env.h err.h file.h fs.h kernel.h loader.h misc.h mm.h net.h parser.h \
 	partition.h pc_partition.h rescue.h symbol.h term.h time.h types.h \

=== modified file 'conf/i386-linuxbios.rmk'
--- conf/i386-linuxbios.rmk	2008-06-19 04:14:16 +0000
+++ conf/i386-linuxbios.rmk	2008-07-04 16:55:48 +0000
@@ -18,6 +18,7 @@
 	kern/misc.c kern/mm.c kern/loader.c kern/rescue.c kern/term.c \
 	kern/i386/dl.c kern/parser.c kern/partition.c \
 	kern/env.c \
+	kern/generic/get_time_ms.c \
 	term/i386/pc/console.c \
 	term/i386/pc/at_keyboard.c term/i386/pc/vga_text.c \
 	symlist.c

=== modified file 'conf/i386-pc.rmk'
--- conf/i386-pc.rmk	2008-06-19 04:14:16 +0000
+++ conf/i386-pc.rmk	2008-07-03 14:27:43 +0000
@@ -43,6 +43,8 @@
 	kern/disk.c kern/dl.c kern/file.c kern/fs.c kern/err.c \
 	kern/misc.c kern/mm.c kern/loader.c kern/rescue.c kern/term.c \
 	kern/i386/dl.c kern/i386/pc/init.c kern/parser.c kern/partition.c \
+	kern/i386/tsc.c \
+	kern/generic/millisleep.c \
 	kern/env.c \
 	term/i386/pc/console.c \
 	symlist.c
@@ -256,7 +258,10 @@
 videotest_mod_LDFLAGS = $(COMMON_LDFLAGS)
 
 # For bitmap.mod
-bitmap_mod_SOURCES = video/bitmap.c
+bitmap_mod_SOURCES = video/bitmap.c \
+                     video/bitmap_scale_nn.c \
+                     video/bitmap_scale_bilinear.c \
+                     video/bitmap_scale.c
 bitmap_mod_CFLAGS = $(COMMON_CFLAGS)
 bitmap_mod_LDFLAGS = $(COMMON_LDFLAGS)
 

=== modified file 'conf/powerpc-ieee1275.rmk'
--- conf/powerpc-ieee1275.rmk	2008-06-19 04:14:16 +0000
+++ conf/powerpc-ieee1275.rmk	2008-07-03 04:19:16 +0000
@@ -85,6 +85,7 @@
 	kern/ieee1275/init.c term/ieee1275/ofconsole.c 		\
 	kern/ieee1275/openfw.c disk/ieee1275/ofdisk.c 		\
 	kern/parser.c kern/partition.c kern/env.c kern/powerpc/dl.c 	\
+	kern/generic/millisleep.c                                       \
 	symlist.c kern/powerpc/cache.S
 kernel_elf_HEADERS = grub/powerpc/ieee1275/ieee1275.h
 kernel_elf_CFLAGS = $(COMMON_CFLAGS)

=== modified file 'conf/sparc64-ieee1275.rmk'
--- conf/sparc64-ieee1275.rmk	2008-06-19 00:04:59 +0000
+++ conf/sparc64-ieee1275.rmk	2008-07-03 14:16:34 +0000
@@ -73,6 +73,7 @@
 	kern/rescue.c kern/term.c term/ieee1275/ofconsole.c \
 	kern/sparc64/ieee1275/openfw.c disk/ieee1275/ofdisk.c \
 	kern/partition.c kern/env.c kern/sparc64/dl.c symlist.c \
+	kern/generic/millisleep.c kern/generic/get_time_ms.c \
 	kern/sparc64/cache.S kern/parser.c
 kernel_elf_HEADERS = grub/sparc64/ieee1275/ieee1275.h
 kernel_elf_CFLAGS = $(COMMON_CFLAGS)
@@ -195,7 +196,7 @@
 cat_mod_LDFLAGS = $(COMMON_LDFLAGS)
 
 # For font.mod.
-font_mod_SOURCES = font/manager.c
+font_mod_SOURCES = font/font_cmd.c font/font.c font/loader.c
 font_mod_CFLAGS = $(COMMON_CFLAGS)
 font_mod_LDFLAGS = $(COMMON_LDFLAGS)
 

=== added file 'font/font.c'
--- font/font.c	1970-01-01 00:00:00 +0000
+++ font/font.c	2008-07-03 14:12:08 +0000
@@ -0,0 +1,92 @@
+/* font.c - Font functions. */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2008  Free Software Foundation, Inc.
+ *
+ *  GRUB is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/file.h>
+#include <grub/misc.h>
+#include <grub/dl.h>
+#include <grub/normal.h>
+#include <grub/types.h>
+#include <grub/mm.h>
+#include <grub/font_internal.h>
+
+grub_font_t
+grub_font_get (const char *font_name)
+{
+  struct font_node *node;
+
+  for (node = grub_font_list; node; node = node->next)
+    {
+      grub_font_t font = node->value;
+      if (grub_strcmp (font->name, font_name) == 0)
+        return font;
+    }
+
+  /* If no font by that name is found, return the first font in the list
+   * as a fallback. */
+  return grub_font_list->value;
+}
+
+const char *
+grub_font_get_name (grub_font_t font)
+{
+  return font->name;
+}
+
+int
+grub_font_get_max_char_width (grub_font_t font)
+{
+  return font->max_char_width;
+}
+
+int
+grub_font_get_max_char_height (grub_font_t font)
+{
+  return font->max_char_height;
+}
+
+int
+grub_font_get_ascent (grub_font_t font)
+{
+  return font->ascent;
+}
+
+int
+grub_font_get_descent (grub_font_t font)
+{
+  return font->descent;
+}
+
+int
+grub_font_get_string_width (grub_font_t font, const char *str)
+{
+  int i;
+  int width;
+  struct grub_font_glyph *glyph;
+  grub_size_t len;
+
+  len = grub_strlen (str);
+
+  for (i = 0, width = 0; i < len; i++)
+    {
+      glyph = grub_font_get_glyph (font, str[i]);
+      width += glyph->device_width;
+    }
+
+  return width;
+}

=== added file 'font/font_cmd.c'
--- font/font_cmd.c	1970-01-01 00:00:00 +0000
+++ font/font_cmd.c	2008-07-03 14:12:08 +0000
@@ -0,0 +1,75 @@
+/* font_cmd.c - Font command definition. */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2003,2005,2006,2007  Free Software Foundation, Inc.
+ *
+ *  GRUB is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/dl.h>
+#include <grub/font_internal.h>
+
+static grub_err_t
+loadfont_command (struct grub_arg_list *state __attribute__ ((unused)),
+	      int argc,
+	      char **args)
+{
+  if (argc == 0)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "no font specified");
+
+  while (argc--)
+    if (grub_font_load (*args++) != 0)
+      return GRUB_ERR_BAD_FONT;
+
+  return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+lsfonts_command (struct grub_arg_list *state __attribute__ ((unused)),
+                 int argc __attribute__ ((unused)),
+                 char **args __attribute__ ((unused)))
+{
+  struct font_node *node;
+
+  grub_printf ("Loaded fonts:\n");
+  for (node = grub_font_list; node; node = node->next)
+    {
+      grub_font_t font = node->value;
+      grub_printf ("%s\n", font->name);
+    }
+
+  return GRUB_ERR_NONE;
+}
+
+GRUB_MOD_INIT(font_manager)
+{
+  grub_font_loader_init ();
+
+  grub_register_command ("loadfont", loadfont_command, GRUB_COMMAND_FLAG_BOTH,
+			 "loadfont FILE...",
+			 "Specify one or more font files to load.", 0);
+
+  grub_register_command ("lsfonts", lsfonts_command, GRUB_COMMAND_FLAG_BOTH,
+			 "lsfonts",
+			 "List the loaded fonts.", 0);
+}
+
+GRUB_MOD_FINI(font_manager)
+{
+  /* Should this free fonts, unknown_glyph, etc.?  Freeing fonts could 
+   * be a Bad Thing if there are still references to any of them. */
+
+  grub_unregister_command ("loadfont");
+}
+

=== added file 'font/loader.c'
--- font/loader.c	1970-01-01 00:00:00 +0000
+++ font/loader.c	2008-07-03 14:12:08 +0000
@@ -0,0 +1,696 @@
+/* loader.c - Functions to handle loading fonts and glyphs from files. */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2003,2005,2006,2007,2008  Free Software Foundation, Inc.
+ *
+ *  GRUB is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/file.h>
+#include <grub/misc.h>
+//#include <grub/dl.h>
+//#include <grub/normal.h>
+#include <grub/types.h>
+#include <grub/mm.h>
+#include <grub/font_internal.h>
+
+/* Definition of font registry. */
+struct font_node *grub_font_list;
+
+static int
+register_font (grub_font_t font);
+
+static void
+free_font (grub_font_t font);
+
+static void
+remove_font (grub_font_t font);
+
+struct font_file_section
+{
+  grub_file_t file;             /* The file this section is in. */
+  char name[4];                 /* FOURCC name of the section. */
+  grub_uint32_t length;         /* Length of the section contents. */
+  int eof;                      /* Set by open_section() on EOF. */
+};
+
+/* Font file format constants. */
+static const char pff2_magic[4] = { 'P', 'F', 'F', '2' };
+static const char section_names_file[4] = { 'F', 'I', 'L', 'E' };
+static const char section_names_font_name[4] = { 'N', 'A', 'M', 'E' };
+static const char section_names_max_char_width[4] = { 'M', 'A', 'X', 'W' };
+static const char section_names_max_char_height[4] = { 'M', 'A', 'X', 'H' };
+static const char section_names_ascent[4] = { 'A', 'S', 'C', 'E' };
+static const char section_names_descent[4] = { 'D', 'E', 'S', 'C' };
+static const char section_names_char_index[4] = { 'C', 'H', 'I', 'X' };
+static const char section_names_data[4] = { 'D', 'A', 'T', 'A' };
+
+/* Replace unknown glyphs with a rounded question mark.  */
+static grub_uint8_t unknown_glyph_bitmap[] =
+{
+        /* 76543210 */
+  0x7C, /*  ooooo   */
+  0x82, /* o     o  */
+  0xBA, /* o ooo o  */
+  0xAA, /* o o o o  */
+  0xAA, /* o o o o  */
+  0x8A, /* o   o o  */
+  0x9A, /* o  oo o  */
+  0x92, /* o  o  o  */
+  0x92, /* o  o  o  */
+  0x92, /* o  o  o  */
+  0x92, /* o  o  o  */
+  0x82, /* o     o  */
+  0x92, /* o  o  o  */
+  0x82, /* o     o  */
+  0x7C, /*  ooooo   */
+  0x00  /*          */
+};
+
+static struct grub_font_glyph *unknown_glyph;
+
+void
+grub_font_loader_init (void)
+{
+  unknown_glyph = grub_malloc(sizeof(struct grub_font_glyph)
+                              + sizeof(unknown_glyph_bitmap));
+  if (! unknown_glyph)
+    return;
+
+  unknown_glyph->width = 8;
+  unknown_glyph->height = 16;
+  unknown_glyph->offset_x = 0;
+  unknown_glyph->offset_y = 0;
+  unknown_glyph->device_width = 8;
+  grub_memcpy(unknown_glyph->bitmap, 
+              unknown_glyph_bitmap, sizeof(unknown_glyph_bitmap));
+}
+
+/* 
+ * Open the next section in the file.
+ *
+ * On success, the section name is stored in section->name and the length in
+ * section->length, and 0 is returned.  On failure, 1 is returned and
+ * grub_errno is set approriately with an error message.
+ *
+ * If 1 is returned due to being at the end of the file, then section->eof is
+ * set to 1; otherwise, section->eof is set to 0.
+ */
+static int
+open_section (grub_file_t file, struct font_file_section *section)
+{
+  grub_ssize_t retval;
+  grub_uint32_t raw_length;
+
+  section->file = file;
+  section->eof = 0;
+
+  /* Read the FOURCC section name. */
+  retval = grub_file_read (file, section->name, 4);
+  if (retval >= 0 && retval < 4)
+    {
+      section->eof = 1;
+      return 1;                 /* EOF encountered. */
+    }
+  else if (retval < 0)
+    {
+      grub_error (GRUB_ERR_BAD_FONT,
+                  "Font format error: can't read section name");
+      return 1;                 /* Read error. */
+    }
+
+  /* Read the big-endian 32-bit section length. */
+  retval = grub_file_read (file, (char *) &raw_length, 4);
+  if (retval >= 0 && retval < 4)
+    {
+      section->eof = 1;
+      return 1;                 /* EOF encountered. */
+    }
+  else if (retval < 0)
+    {
+      grub_error (GRUB_ERR_BAD_FONT,
+                  "Font format error: can't read section length");
+      return 1;                 /* Read error. */
+    }
+
+  /* Convert byte-order and store in *length. */
+  section->length = grub_be_to_cpu32 (raw_length);
+
+  return 0;
+}
+
+/* Size in bytes of each character index (CHIX section)
+ * entry in the font file. */
+#define FONT_CHAR_INDEX_ENTRY_SIZE (4 + 1 + 4)
+
+/*
+ * Load the character index (CHIX) section contents from the font file.  This
+ * presumes that the position of FILE is positioned immediately after the
+ * section length for the CHIX section (i.e., at the start of the section
+ * contents).  Returns 0 upon success, nonzero for failure (in which case
+ * grub_errno is set appropriately).
+ */
+static int
+load_font_index (grub_file_t file, grub_uint32_t sect_length, struct
+                 grub_font *font)
+{
+  unsigned i;
+
+#if FONT_DEBUG >= 2
+  grub_printf("load_font_index(sect_length=%d)\n", sect_length);
+#endif
+
+  /* Sanity check: ensure section length is divisible by the entry size.  */
+  if (sect_length % FONT_CHAR_INDEX_ENTRY_SIZE != 0)
+    {
+      grub_error (GRUB_ERR_BAD_FONT,
+                  "Font file format error: character index length %d "
+                  "is not a multiple of the entry size %d",
+                  sect_length, FONT_CHAR_INDEX_ENTRY_SIZE);
+      return 1;                 /* Invalid index section length. */
+    }
+
+  /* Calculate the number of characters. */
+  font->num_chars = sect_length / FONT_CHAR_INDEX_ENTRY_SIZE;
+
+  /* Allocate the character index array. */
+  font->char_index = grub_malloc (font->num_chars
+                                  * sizeof (struct char_index_entry));
+  if (!font->char_index)
+    return 1;                   /* Error allocating memory. */
+
+#if FONT_DEBUG >= 2
+  grub_printf("num_chars=%d)\n", font->num_chars);
+#endif
+
+  /* Load the character index data from the file. */
+  for (i = 0; i < font->num_chars; i++)
+    {
+      struct char_index_entry *entry = &font->char_index[i];
+
+      /* Read code point value; convert to native byte order. */
+      if (grub_file_read (file, (char *) &entry->code, 4) != 4)
+        return 1;
+      entry->code = grub_be_to_cpu32 (entry->code);
+
+      /* Read storage flags byte. */
+      if (grub_file_read (file, (char *) &entry->storage_flags, 1) != 1)
+        return 1;
+
+      /* Read glyph data offset; convert to native byte order. */
+      if (grub_file_read (file, (char *) &entry->offset, 4) != 4)
+        return 1;
+      entry->offset = grub_be_to_cpu32 (entry->offset);
+
+      /* No glyph loaded.  Will be loaded on demand and cached thereafter. */
+      entry->glyph = 0;
+
+#if FONT_DEBUG >= 5
+      if (i < 10)  /* Print the 1st 10 characters. */
+        grub_printf("c=%d o=%d\n", entry->code, entry->offset);
+#endif
+    }
+
+  return 0;                     /* Index loaded OK. */
+}
+
+/* 
+ * Read the contents of the specified section as a string, which is
+ * allocated on the heap.  Returns 0 if there is an error. 
+ */
+static char *
+read_section_as_string (struct font_file_section *section)
+{
+  char *str;
+  grub_ssize_t ret;
+
+  str = grub_malloc (section->length + 1);
+  if (!str)
+    return 0;
+
+  ret = grub_file_read (section->file, str, section->length);
+  if (ret < 0 || ret != (grub_ssize_t) section->length)
+    {
+      grub_free (str);
+      return 0;
+    }
+
+  str[section->length] = '\0';
+  return str;
+}
+
+/* 
+ * Read the contents of the current section as a 16-bit integer value,
+ * which is stored into *VALUE.  Returns 0 upon success, nonzero upon failure.
+ */
+static int
+read_section_as_short (struct font_file_section *section, grub_int16_t *value)
+{
+  grub_uint16_t raw_value;
+
+  if (section->length != 2)
+    {
+      grub_error (GRUB_ERR_BAD_FONT,
+                  "Font file format error: section %c%c%c%c length "
+                  "is %d but should be 2", 
+                  section->name[0], section->name[1], 
+                  section->name[2], section->name[3],
+                  section->length);
+      return 1;   /* An error occurred. */
+    }
+  if (grub_file_read (section->file, (char *) &raw_value, 2) != 2)
+    return 1;   /* An error occurred. */
+
+  *value = grub_be_to_cpu16 (raw_value);
+  return 0;   /* Successfully read the value. */
+}
+
+/*
+ * Load a font and add it to the beginning of the global font list.
+ * Returns: 0 upon success; nonzero upon failure.
+ */
+int
+grub_font_load (const char *filename)
+{
+  grub_file_t file = 0;
+  struct font_file_section section;
+  char magic[4];
+  grub_font_t font = 0;
+
+#if FONT_DEBUG >= 1
+  grub_printf("add_font(%s)\n", filename);
+#endif
+
+  file = grub_file_open (filename);
+  if (!file)
+    goto fail;
+
+#if FONT_DEBUG >= 3
+  grub_printf("file opened\n");
+#endif
+
+  /* Read the FILE section.  It indicates the file format. */
+  if (open_section (file, &section) != 0)
+    goto fail;
+
+#if FONT_DEBUG >= 3
+  grub_printf("opened FILE section\n");
+#endif
+  if (grub_memcmp (section.name, section_names_file, 4) != 0)
+    {
+      grub_error (GRUB_ERR_BAD_FONT,
+                  "Font file format error: 1st section must be FILE");
+      goto fail;
+    }
+
+#if FONT_DEBUG >= 3
+  grub_printf("section name ok\n");
+#endif
+  if (section.length != 4)
+    {
+      grub_error (GRUB_ERR_BAD_FONT,
+                  "Font file format error (file type ID length is %d "
+                  "but should be 4)", section.length);
+      goto fail;
+    }
+
+#if FONT_DEBUG >= 3
+  grub_printf("section length ok\n");
+#endif
+  /* Check the file format type code. */
+  if (grub_file_read (file, magic, 4) != 4)
+    goto fail;
+
+#if FONT_DEBUG >= 3
+  grub_printf("read magic ok\n");
+#endif
+
+  if (grub_memcmp (magic, pff2_magic, 4) != 0)
+    {
+      grub_error (GRUB_ERR_BAD_FONT, "Invalid font magic %x %x %x %x",
+                  magic[0], magic[1], magic[2], magic[3]);
+      goto fail;
+    }
+
+#if FONT_DEBUG >= 3
+  grub_printf("compare magic ok\n");
+#endif
+
+  /* Allocate the font object. */
+  font = (grub_font_t) grub_malloc (sizeof (struct grub_font));
+  if (!font)
+    goto fail;
+
+  font->file = file;
+  font->name = 0;
+  font->ascent = 0;
+  font->descent = 0;
+  font->max_char_width = 0;
+  font->max_char_height = 0;
+  font->num_chars = 0;
+  font->char_index = 0;
+
+#if FONT_DEBUG >= 3
+  grub_printf("allocate font ok; loading font info\n");
+#endif
+
+  /* Load the font information. */
+  while (1)
+    {
+      if (open_section (file, &section) != 0)
+        {
+          if (section.eof)
+            break;              /* Done reading the font file. */
+          else
+            goto fail;
+        }
+
+#if FONT_DEBUG >= 2
+      grub_printf("opened section %c%c%c%c ok\n",
+                  section.name[0], section.name[1],
+                  section.name[2], section.name[3]);
+#endif
+
+      if (grub_memcmp (section.name, section_names_font_name, 4) == 0)
+        {
+          font->name = read_section_as_string (&section);
+          if (!font->name)
+            goto fail;
+        }
+      else if (grub_memcmp (section.name, section_names_max_char_width, 4) == 0)
+        {
+          if (read_section_as_short (&section, &font->max_char_width) != 0)
+            goto fail;
+        }
+      else if (grub_memcmp (section.name, section_names_max_char_height, 4) == 0)
+        {
+          if (read_section_as_short (&section, &font->max_char_height) != 0)
+            goto fail;
+        }
+      else if (grub_memcmp (section.name, section_names_ascent, 4) == 0)
+        {
+          if (read_section_as_short (&section, &font->ascent) != 0)
+            goto fail;
+        }
+      else if (grub_memcmp (section.name, section_names_descent, 4) == 0)
+        {
+          if (read_section_as_short (&section, &font->descent) != 0)
+            goto fail;
+        }
+      else if (grub_memcmp (section.name, section_names_char_index, 4) == 0)
+        {
+          /* Load the font index. */
+          if (load_font_index (file, section.length, font) != 0)
+            goto fail;
+        }
+      else if (grub_memcmp (section.name, section_names_data, 4) == 0)
+        {
+          /* When the DATA section marker is reached, we stop reading. */
+          break;
+        }
+      else
+        {
+          /* Unhandled section type, simply skip past it. */
+#if FONT_DEBUG >= 3
+          grub_printf("Unhandled section type, skipping.\n");
+#endif
+          if ((int) grub_file_seek (file,
+                                    grub_file_tell (file) + section.length) 
+              == -1)
+            goto fail;
+        }
+    }
+
+  if (!font->name)
+    {
+      grub_printf ("Note: Font has no name.\n");
+      font->name = grub_strdup ("Unknown");
+    }
+
+#if FONT_DEBUG >= 1
+  grub_printf ("Loaded font `%s'.\n"
+              "Ascent=%d Descent=%d MaxW=%d MaxH=%d Number of characters=%d.\n",
+              font->name, 
+              font->ascent, font->descent, 
+              font->max_char_width, font->max_char_height,
+              font->num_chars);
+#endif
+
+  if (font->max_char_width == 0
+      || font->max_char_height == 0
+      || font->num_chars == 0
+      || font->char_index == 0
+      || font->ascent == 0
+      || font->descent == 0)
+    {
+      grub_error (GRUB_ERR_BAD_FONT,
+                  "Invalid font file: missing some required data.");
+      goto fail;
+    }
+
+  /* Add the font to the global font registry. */
+  if (register_font (font) != 0)
+    goto fail;
+
+  return 0;             /* Font loaded ok. */
+
+fail:
+  free_font (font);
+  return 1;                     /* Failed to load font. */
+}
+
+/*
+ * Read a 16-bit big-endian integer from FILE, convert it to native byte
+ * order, and store it in *VALUE.
+ * Returns 0 on success, 1 on failure.
+ */
+static int
+read_be_uint16 (grub_file_t file, grub_uint16_t * value)
+{
+  if (grub_file_read (file, (char *) value, 2) != 2)
+    return 1;
+  *value = grub_be_to_cpu16 (*value);
+  return 0;
+}
+
+static int
+read_be_int16 (grub_file_t file, grub_int16_t * value)
+{
+  /* For the signed integer version, use the same code as for unsigned. */
+  return read_be_uint16 (file, (grub_uint16_t *) value);
+}
+
+/*
+ * Return a pointer to the character index entry for the glyph corresponding to
+ * the codepoint CODE in the font FONT.  If not found, return zero.
+ */
+static struct char_index_entry *
+find_glyph (const grub_font_t font, grub_uint32_t code)
+{
+  grub_uint32_t i;
+  grub_uint32_t len = font->num_chars;
+  struct char_index_entry *table = font->char_index;
+
+  /* Do a linear search.  */
+  for (i = 0; i < len; i++)
+    {
+      if (table[i].code == code)
+        return &table[i];
+    }
+
+  return 0;                     /* No entry found for code point CODE. */
+}
+
+static struct grub_font_glyph *
+grub_font_get_glyph_internal (grub_font_t font, grub_uint32_t code)
+{
+  struct char_index_entry *index_entry;
+
+  index_entry = find_glyph (font, code);
+  if (index_entry)
+    {
+      struct grub_font_glyph *glyph = 0;
+      grub_uint16_t width;
+      grub_uint16_t height;
+      grub_int16_t xoff;
+      grub_int16_t yoff;
+      grub_int16_t dwidth;
+      int len;
+
+      if (index_entry->glyph)
+        return index_entry->glyph;   /* Return cached glyph. */
+
+      /* Make sure we can find glyphs for error messages.  Push active
+         error message to error stack and reset error message.  */
+      grub_error_push ();
+
+      grub_file_seek (font->file, index_entry->offset);
+
+      /* Read the glyph width, height, and baseline. */
+      if (read_be_uint16(font->file, &width) != 0
+          || read_be_uint16(font->file, &height) != 0
+          || read_be_int16(font->file, &xoff) != 0
+          || read_be_int16(font->file, &yoff) != 0
+          || read_be_int16(font->file, &dwidth) != 0)
+        {
+          //remove_font (font);
+          return 0;
+        }
+
+      len = (width * height + 7) / 8;
+      glyph = grub_malloc (sizeof (struct grub_font_glyph) + len);
+      if (! glyph)
+        {
+          //remove_font (font);
+          return 0;
+        }
+
+      glyph->font = font;
+      glyph->width = width;
+      glyph->height = height;
+      glyph->offset_x = xoff;
+      glyph->offset_y = yoff;
+      glyph->device_width = dwidth;
+
+      /* Don't try to read empty bitmaps (e.g., space characters). */
+      if (len != 0)
+        {
+          if (grub_file_read (font->file, (char *) glyph->bitmap, len) != len)
+            {
+              //remove_font (font);
+              return 0;
+            }
+        }
+
+      /* Restore old error message.  */
+      grub_error_pop ();
+
+      /* Cache the glyph. */
+      index_entry->glyph = glyph;
+
+      return glyph;  /* Glyph loaded ok. */
+    }
+
+  return 0;
+}
+
+/* Get the glyph for FONT corresponding to the Unicode code point CODE.
+ * Returns a pointer to an glyph indicating there is no glyph available
+ * if CODE does not exist in the font.  The glyphs are cached once loaded. */
+struct grub_font_glyph *
+grub_font_get_glyph (grub_font_t font, grub_uint32_t code)
+{
+  struct grub_font_glyph *glyph;
+  glyph = grub_font_get_glyph_internal (font, code);
+  if (glyph == 0)
+    glyph = unknown_glyph;
+  return glyph;
+}
+
+/* Get a glyph corresponding to the codepoint CODE.  If no glyph is available
+ * for CODE in the available fonts, then a glyph representing an unknown
+ * character is returned.  This function never returns NULL.
+ * The returned glyph is owned by the font manager and should not be freed
+ * by the caller.  The glyphs are cached. */
+struct grub_font_glyph *
+grub_font_get_glyph_any (grub_uint32_t code)
+{
+  struct font_node *node;
+  /* Keep track of next node, in case there's an I/O error in 
+   * grub_font_get_glyph() and the font is removed from the list. */
+  struct font_node *next;  
+  
+  for (node = grub_font_list; node; node = next)
+    {
+      grub_font_t font;
+      struct grub_font_glyph *glyph;
+
+      font = node->value;
+      next = node->next;
+
+      glyph = grub_font_get_glyph_internal (font, code);
+      if (glyph)
+        return glyph;
+    }
+
+  /* Uggh...  Glyph was not found in any font. */
+  return unknown_glyph;  /* Failed to load glyph. */
+}
+
+/*
+ * Free the memory used by a font.
+ * This should not be called if the font has been made available to
+ * users (once it is added to the global font list), since there would
+ * be the possibility of a dangling pointer.
+ */
+static void
+free_font (grub_font_t font)
+{
+  if (font)
+    {
+      if (font->file)
+        grub_file_close (font->file);
+      if (font->name)
+        grub_free (font->name);
+      if (font->char_index)
+        grub_free (font->char_index);
+      grub_free (font);
+    }
+}
+
+/*
+ * Add FONT to the global font registry.
+ * Returns 0 upon success, nonzero on failure (the font was not registered).
+ */
+static int
+register_font (grub_font_t font)
+{
+  struct font_node *node = 0;
+
+  node = grub_malloc (sizeof (struct font_node));
+  if (!node)
+    return 1;  /* Error. */
+
+  node->value = font;
+  node->next = grub_font_list;
+  grub_font_list = node;
+
+  return 0;  /* Success. */
+}
+
+/*
+ * Remove the font from the global font list.  We don't actually free the 
+ * font's memory since users could be holding references to the font.
+ */
+static void
+remove_font (grub_font_t font)
+{
+  struct font_node **nextp, *cur;
+
+  for (nextp = &grub_font_list, cur = *nextp; 
+       cur; 
+       nextp = &cur->next, cur = cur->next)
+    {
+      if (cur->value == font)
+        {
+          *nextp = cur->next;
+
+          /* Free the node, but not the font itself. */
+          grub_free (cur);
+
+          return;
+        }
+    }
+}
+

=== removed file 'font/manager.c'
--- font/manager.c	2008-02-10 11:04:38 +0000
+++ font/manager.c	1970-01-01 00:00:00 +0000
@@ -1,282 +0,0 @@
-/*
- *  GRUB  --  GRand Unified Bootloader
- *  Copyright (C) 2003,2005,2006,2007  Free Software Foundation, Inc.
- *
- *  GRUB is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  GRUB is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <grub/file.h>
-#include <grub/misc.h>
-#include <grub/dl.h>
-#include <grub/normal.h>
-#include <grub/types.h>
-#include <grub/mm.h>
-#include <grub/font.h>
-
-struct entry
-{
-  grub_uint32_t code;
-  grub_uint32_t offset;
-};
-
-struct font
-{
-  struct font *next;
-  grub_file_t file;
-  grub_uint32_t num;
-  struct entry table[0];
-};
-
-static struct font *font_list;
-
-/* Fill unknown glyph's with rounded question mark.  */
-static grub_uint8_t unknown_glyph[16] =
-{       /* 76543210 */
-  0x7C, /*  ooooo   */
-  0x82, /* o     o  */
-  0xBA, /* o ooo o  */
-  0xAA, /* o o o o  */
-  0xAA, /* o o o o  */
-  0x8A, /* o   o o  */
-  0x9A, /* o  oo o  */
-  0x92, /* o  o  o  */
-  0x92, /* o  o  o  */
-  0x92, /* o  o  o  */
-  0x92, /* o  o  o  */
-  0x82, /* o     o  */
-  0x92, /* o  o  o  */
-  0x82, /* o     o  */
-  0x7C, /*  ooooo   */
-  0x00  /*          */
-};
-
-static int
-add_font (const char *filename)
-{
-  grub_file_t file = 0;
-  char magic[4];
-  grub_uint32_t num, i;
-  struct font *font = 0;
-
-  file = grub_file_open (filename);
-  if (! file)
-    goto fail;
-
-  if (grub_file_read (file, magic, 4) != 4)
-    goto fail;
-
-  if (grub_memcmp (magic, GRUB_FONT_MAGIC, 4) != 0)
-    {
-      grub_error (GRUB_ERR_BAD_FONT, "invalid font magic");
-      goto fail;
-    }
-
-  if (grub_file_read (file, (char *) &num, 4) != 4)
-    goto fail;
-
-  num = grub_le_to_cpu32 (num);
-  font = (struct font *) grub_malloc (sizeof (struct font)
-				      + sizeof (struct entry) * num);
-  if (! font)
-    goto fail;
-
-  font->file = file;
-  font->num = num;
-
-  for (i = 0; i < num; i++)
-    {
-      grub_uint32_t code, offset;
-      
-      if (grub_file_read (file, (char *) &code, 4) != 4)
-	goto fail;
-
-      if (grub_file_read (file, (char *) &offset, 4) != 4)
-	goto fail;
-
-      font->table[i].code = grub_le_to_cpu32 (code);
-      font->table[i].offset = grub_le_to_cpu32 (offset);
-    }
-
-  font->next = font_list;
-  font_list = font;
-
-  return 1;
-
- fail:
-  if (font)
-    grub_free (font);
-
-  if (file)
-    grub_file_close (file);
-
-  return 0;
-}
-
-static void
-remove_font (struct font *font)
-{
-  struct font **p, *q;
-
-  for (p = &font_list, q = *p; q; p = &(q->next), q = q->next)
-    if (q == font)
-      {
-        *p = q->next;
-	
-	grub_file_close (font->file);
-	grub_free (font);
-	
-        break;
-      }
-}
-
-/* Return the offset of the glyph corresponding to the codepoint CODE
-   in the font FONT.  If no found, return zero.  */
-static grub_uint32_t
-find_glyph (const struct font *font, grub_uint32_t code)
-{
-  grub_uint32_t start = 0;
-  grub_uint32_t end = font->num - 1;
-  const struct entry *table = font->table;
-  
-  /* This shouldn't happen.  */
-  if (font->num == 0)
-    return 0;
-
-  /* Do a binary search.  */
-  while (start <= end)
-    {
-      grub_uint32_t i = (start + end) / 2;
-
-      if (table[i].code < code)
-	start = i + 1;
-      else if (table[i].code > code)
-	end = i - 1;
-      else
-	return table[i].offset;
-    }
-
-  return 0;
-}
-
-/* Set the glyph to something stupid.  */
-static void
-fill_with_default_glyph (grub_font_glyph_t glyph)
-{
-  unsigned i;
-
-  /* Use pre-defined pattern to fill unknown glyphs.  */
-  for (i = 0; i < 16; i++)
-    glyph->bitmap[i] = unknown_glyph[i];
-
-  glyph->char_width = 1;
-  glyph->width = glyph->char_width * 8;
-  glyph->height = 16;
-  glyph->baseline = (16 * 3) / 4;
-}
-
-/* Get a glyph corresponding to the codepoint CODE.  Always fill glyph
-   information with something, even if no glyph is found.  */
-int
-grub_font_get_glyph (grub_uint32_t code,
-		     grub_font_glyph_t glyph)
-{
-  struct font *font;
-  grub_uint8_t bitmap[32];
-
-  /* FIXME: It is necessary to cache glyphs!  */
-  
- restart:
-  for (font = font_list; font; font = font->next)
-    {
-      grub_uint32_t offset;
-
-      offset = find_glyph (font, code);
-      if (offset)
-	{
-	  grub_uint32_t w;
-	  int len;
-
-          /* Make sure we can find glyphs for error messages.  Push active
-             error message to error stack and reset error message.  */
-          grub_error_push ();
-	  
-	  grub_file_seek (font->file, offset);
-	  if ((len = grub_file_read (font->file, (char *) &w, sizeof (w)))
-	      != sizeof (w))
-	    {
-              remove_font (font);
-              goto restart;
-	    }
-
-	  w = grub_le_to_cpu32 (w);
-	  if (w != 1 && w != 2)
-	    {
-	      /* grub_error (GRUB_ERR_BAD_FONT, "invalid width"); */
-	      remove_font (font);
-	      goto restart;
-	    }
-
-	  if (grub_file_read (font->file, (char *) bitmap, w * 16)
-	      != (grub_ssize_t) w * 16)
-	    {
-	      remove_font (font);
-	      goto restart;
-	    }
-
-          /* Fill glyph with information.  */	    
-          grub_memcpy (glyph->bitmap, bitmap, w * 16);
-          
-	  glyph->char_width = w;
-	  glyph->width = glyph->char_width * 8;
-	  glyph->height = 16;
-	  glyph->baseline = (16 * 3) / 4;
-	  
-	  /* Restore old error message.  */
-          grub_error_pop ();
-          
-	  return 1;
-	}
-    }
-
-  /* Uggh...  No font was found.  */
-  fill_with_default_glyph (glyph);
-  return 0;
-}
-
-static grub_err_t
-font_command (struct grub_arg_list *state __attribute__ ((unused)),
-	      int argc  __attribute__ ((unused)),
-	      char **args __attribute__ ((unused)))
-{
-  if (argc == 0)
-    return grub_error (GRUB_ERR_BAD_ARGUMENT, "no font specified");
-
-  while (argc--)
-    if (! add_font (*args++))
-      return 1;
-
-  return 0;
-}
-
-GRUB_MOD_INIT(font_manager)
-{
-  grub_register_command ("font", font_command, GRUB_COMMAND_FLAG_BOTH,
-			 "font FILE...",
-			 "Specify one or more font files to display.", 0);
-}
-
-GRUB_MOD_FINI(font_manager)
-{
-  grub_unregister_command ("font");
-}

=== modified file 'genmk.rb'
--- genmk.rb	2008-07-02 18:03:23 +0000
+++ genmk.rb	2008-07-04 21:40:36 +0000
@@ -125,7 +125,7 @@
 	$(TARGET_CC) $(TARGET_CPPFLAGS) $(TARGET_CFLAGS) $(#{prefix}_CFLAGS) -c -o $@ $<
 
 #{mod_src}: moddep.lst genmodsrc.sh
-	sh $(srcdir)/genmodsrc.sh '#{mod_name}' $< > $@ || (rm -f $@; exit 1)
+	$(GENMODSRC) '#{mod_name}' $< > $@ || (rm -f $@; exit 1)
 
 ifneq ($(#{prefix}_EXPORTS),no)
 #{defsym}: #{pre_obj}
@@ -157,18 +157,21 @@
 PARTMAPFILES += #{partmap}
 
 #{command}: #{src} $(#{src}_DEPENDENCIES) gencmdlist.sh
-	set -e; \
-	  $(TARGET_CC) -I#{dir} -I$(srcdir)/#{dir} $(TARGET_CPPFLAGS) $(TARGET_#{flag}) $(#{prefix}_#{flag}) -E $< \
+	$(INFO_GENCMDLIST)
+	$(V_PREFIX)set -e; \
+	  $(_TARGET_CC) -I#{dir} -I$(srcdir)/#{dir} $(TARGET_CPPFLAGS) $(TARGET_#{flag}) $(#{prefix}_#{flag}) -E $< \
 	  | sh $(srcdir)/gencmdlist.sh #{symbolic_name} > $@ || (rm -f $@; exit 1)
 
 #{fs}: #{src} $(#{src}_DEPENDENCIES) genfslist.sh
-	set -e; \
-	  $(TARGET_CC) -I#{dir} -I$(srcdir)/#{dir} $(TARGET_CPPFLAGS) $(TARGET_#{flag}) $(#{prefix}_#{flag}) -E $< \
+	$(INFO_GENFSLIST)
+	$(V_PREFIX)set -e; \
+	  $(_TARGET_CC) -I#{dir} -I$(srcdir)/#{dir} $(TARGET_CPPFLAGS) $(TARGET_#{flag}) $(#{prefix}_#{flag}) -E $< \
 	  | sh $(srcdir)/genfslist.sh #{symbolic_name} > $@ || (rm -f $@; exit 1)
 
 #{partmap}: #{src} $(#{src}_DEPENDENCIES) genpartmaplist.sh
-	set -e; \
-	  $(TARGET_CC) -I#{dir} -I$(srcdir)/#{dir} $(TARGET_CPPFLAGS) $(TARGET_#{flag}) $(#{prefix}_#{flag}) -E $< \
+	$(INFO_GENPARTMAPLIST)
+	$(V_PREFIX)set -e; \
+	  $(_TARGET_CC) -I#{dir} -I$(srcdir)/#{dir} $(TARGET_CPPFLAGS) $(TARGET_#{flag}) $(#{prefix}_#{flag}) -E $< \
 	  | sh $(srcdir)/genpartmaplist.sh #{symbolic_name} > $@ || (rm -f $@; exit 1)
 
 

=== added file 'include/grub/bitmap_scale.h'
--- include/grub/bitmap_scale.h	1970-01-01 00:00:00 +0000
+++ include/grub/bitmap_scale.h	2008-07-03 14:27:43 +0000
@@ -0,0 +1,54 @@
+/* bitmap_scale.h - Bitmap scaling functions. */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2006,2007  Free Software Foundation, Inc.
+ *
+ *  GRUB is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GRUB_BITMAP_SCALE_HEADER
+#define GRUB_BITMAP_SCALE_HEADER 1
+
+#include <grub/err.h>
+#include <grub/types.h>
+#include <grub/bitmap_scale.h>
+
+enum grub_video_bitmap_scale_method
+{
+  /* Choose the fastest interpolation algorithm. */
+  GRUB_VIDEO_BITMAP_SCALE_METHOD_FASTEST,
+  /* Choose the highest quality interpolation algorithm. */
+  GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST,
+  /* Nearest neighbor interpolation algorithm. */
+  GRUB_VIDEO_BITMAP_SCALE_METHOD_NEAREST,
+  /* Bilinear interpolation algorithm. */
+  GRUB_VIDEO_BITMAP_SCALE_METHOD_BILINEAR
+};
+
+grub_err_t
+grub_video_bitmap_scale_nn (struct grub_video_bitmap *dst,
+                            struct grub_video_bitmap *src);
+
+grub_err_t
+grub_video_bitmap_scale_bilinear (struct grub_video_bitmap *dst,
+                                  struct grub_video_bitmap *src);
+
+grub_err_t
+grub_video_bitmap_create_scaled (struct grub_video_bitmap **dst,
+                                 int dst_width, int dst_height,
+                                 struct grub_video_bitmap *src,
+                                 enum
+                                 grub_video_bitmap_scale_method scale_method);
+
+#endif /* ! GRUB_BITMAP_SCALE_HEADER */

=== modified file 'include/grub/font.h'
--- include/grub/font.h	2007-07-21 22:32:33 +0000
+++ include/grub/font.h	2008-07-03 14:17:31 +0000
@@ -1,6 +1,6 @@
 /*
  *  GRUB  --  GRand Unified Bootloader
- *  Copyright (C) 2003,2007  Free Software Foundation, Inc.
+ *  Copyright (C) 2003,2007,2008  Free Software Foundation, Inc.
  *
  *  GRUB is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License as published by
@@ -21,33 +21,85 @@
 
 #include <grub/types.h>
 
-#define GRUB_FONT_MAGIC	"PPF\x7f"
+/* Forward declaration of opaque structure grub_font.
+ * Users only pass struct grub_font pointers to the font module functions,
+ * and do not have knowledge of the structure contents. */
+struct grub_font;
+
+/* Font type used to access font functions. */
+typedef struct grub_font *grub_font_t;
+
 
 struct grub_font_glyph
 {
-  /* Glyph width in pixels.  */
-  grub_uint8_t width;
-  
-  /* Glyph height in pixels.  */
-  grub_uint8_t height;
-  
-  /* Glyph width in characters.  */
-  grub_uint8_t char_width;
-  
-  /* Glyph baseline position in pixels (from up).  */
-  grub_uint8_t baseline;
-  
-  /* Glyph bitmap data array of bytes in ((width + 7) / 8) * height.
-     Bitmap is formulated by height scanlines, each scanline having
-     width number of pixels. Pixels are coded as bits, value 1 meaning
-     of opaque pixel and 0 is transparent. If width does not fit byte
-     boundary, it will be padded with 0 to make it fit.  */
-  grub_uint8_t bitmap[32];
+  /* Reference to the font this glyph belongs to. */
+  grub_font_t font;
+
+  /* Glyph bitmap width in pixels. */
+  grub_uint16_t width;
+
+  /* Glyph bitmap height in pixels. */
+  grub_uint16_t height;
+
+  /* Glyph bitmap x offset in pixels.  Add to screen coordinate. */
+  grub_int16_t offset_x;
+
+  /* Glyph bitmap y offset in pixels.  Subtract from screen coordinate.  */
+  grub_int16_t offset_y;
+
+  /* Number of pixels to advance to start the next character. */
+  grub_uint16_t device_width;
+
+  /* Row-major order, packed bits (no padding; rows can break within a byte).
+   * The length of the array is (width * height + 7) / 8.  Within a
+   * byte, the most significant bit is the first (leftmost/uppermost) pixel.
+   * Pixels are coded as bits, value 1 meaning of opaque pixel and 0 is
+   * transparent. If the length of the array does not fit byte boundary, it
+   * will be padded with 0 bits to make it fit.  */
+  grub_uint8_t bitmap[0];
 };
 
-typedef struct grub_font_glyph *grub_font_glyph_t;
-
-int grub_font_get_glyph (grub_uint32_t code,
-			 grub_font_glyph_t glyph);
+
+/****** font/font.c ******/
+
+/* Get the font that has the specified name.  Font names are in the form
+ * "Family Name Bold Italic 14", where Bold and Italic are optional.
+ * If no font matches the name specified, the most recently loaded font
+ * is returned as a fallback. */
+grub_font_t grub_font_get (const char *font_name);
+
+const char *grub_font_get_name (grub_font_t font);
+
+int grub_font_get_max_char_width (grub_font_t font);
+
+int grub_font_get_max_char_height (grub_font_t font);
+
+int grub_font_get_ascent (grub_font_t font);
+
+int grub_font_get_descent (grub_font_t font);
+
+int grub_font_get_string_width (grub_font_t font, const char *str);
+
+
+/****** font/loader.c ******/
+
+/*
+ * Load a font and add it to the beginning of the global font list.
+ * Returns: 0 upon success; nonzero upon failure.
+ */
+int grub_font_load (const char *filename);
+
+/* Get the glyph for FONT corresponding to the Unicode code point CODE.
+ * Returns a pointer to an glyph indicating there is no glyph available
+ * if CODE does not exist in the font.  The glyphs are cached once loaded. */
+struct grub_font_glyph *grub_font_get_glyph (grub_font_t font,
+                                             grub_uint32_t code);
+
+/* Get a glyph corresponding to the codepoint CODE.  If no glyph is available
+ * for CODE in the available fonts, then a glyph representing an unknown
+ * character is returned.  This function never returns NULL.
+ * The returned glyph is owned by the font manager and should not be freed
+ * by the caller.  The glyphs are cached. */
+struct grub_font_glyph *grub_font_get_glyph_any (grub_uint32_t code);
 
 #endif /* ! GRUB_FONT_HEADER */

=== added file 'include/grub/font_internal.h'
--- include/grub/font_internal.h	1970-01-01 00:00:00 +0000
+++ include/grub/font_internal.h	2008-07-03 14:16:11 +0000
@@ -0,0 +1,71 @@
+/* font_internal.h - Font declarations for use internally by the font module.
+ * Users of the font module should not include this header. */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2003,2005,2006,2007,2008  Free Software Foundation, Inc.
+ *
+ *  GRUB is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GRUB_FONT_INTERNAL_HEADER
+#define GRUB_FONT_INTERNAL_HEADER 1
+
+#include <grub/file.h>
+#include <grub/misc.h>
+#include <grub/dl.h>
+#include <grub/normal.h>
+#include <grub/types.h>
+#include <grub/mm.h>
+#include <grub/font.h>
+
+#define FONT_DEBUG 0
+
+struct char_index_entry
+{
+  grub_uint32_t code;
+  grub_uint8_t storage_flags;
+  grub_uint32_t offset;
+  struct grub_font_glyph *glyph;   /* Glyph if loaded, or null. */
+};
+
+struct grub_font
+{
+  char *name;
+  grub_file_t file;
+  short max_char_width;
+  short max_char_height;
+  short ascent;
+  short descent;
+  grub_uint32_t num_chars;
+  struct char_index_entry *char_index;
+};
+
+struct font_node
+{
+  struct font_node *next;
+  struct grub_font *value;
+};
+
+extern struct font_node *grub_font_list;
+
+
+/****** loader.c ******/
+
+/* Initialize the font loader module. */
+void
+grub_font_loader_init (void);
+
+
+#endif /* ! GRUB_FONT_INTERNAL_HEADER */
+

=== modified file 'include/grub/i386/pc/vbeblit.h'
--- include/grub/i386/pc/vbeblit.h	2007-07-21 22:32:33 +0000
+++ include/grub/i386/pc/vbeblit.h	2008-07-03 13:49:18 +0000
@@ -25,55 +25,75 @@
 struct grub_video_i386_vbeblit_info;
 
 void
-grub_video_i386_vbeblit_R8G8B8A8_R8G8B8A8 (struct grub_video_i386_vbeblit_info *dst,
-                                           struct grub_video_i386_vbeblit_info *src,
-                                           int x, int y, int width, int height,
-                                           int offset_x, int offset_y);
-
-void
-grub_video_i386_vbeblit_R8G8B8X8_R8G8B8X8 (struct grub_video_i386_vbeblit_info *dst,
-                                           struct grub_video_i386_vbeblit_info *src,
-                                           int x, int y, int width, int height,
-                                           int offset_x, int offset_y);
-
-void
-grub_video_i386_vbeblit_R8G8B8_R8G8B8A8 (struct grub_video_i386_vbeblit_info *dst,
-                                         struct grub_video_i386_vbeblit_info *src,
-                                         int x, int y, int width, int height,
-                                         int offset_x, int offset_y);
-
-void
-grub_video_i386_vbeblit_R8G8B8_R8G8B8X8 (struct grub_video_i386_vbeblit_info *dst,
-                                         struct grub_video_i386_vbeblit_info *src,
-                                         int x, int y, int width, int height,
-                                         int offset_x, int offset_y);
-
-void
-grub_video_i386_vbeblit_index_R8G8B8A8 (struct grub_video_i386_vbeblit_info *dst,
-                                        struct grub_video_i386_vbeblit_info *src,
-                                        int x, int y, int width, int height,
-                                        int offset_x, int offset_y);
-
-void
-grub_video_i386_vbeblit_index_R8G8B8X8 (struct grub_video_i386_vbeblit_info *dst,
-                                        struct grub_video_i386_vbeblit_info *src,
-                                        int x, int y, int width, int height,
-                                        int offset_x, int offset_y);
-
-void
-grub_video_i386_vbeblit_R8G8B8A8_R8G8B8 (struct grub_video_i386_vbeblit_info *dst,
-                                         struct grub_video_i386_vbeblit_info *src,
-                                         int x, int y, int width, int height,
-                                         int offset_x, int offset_y);
-
-void
-grub_video_i386_vbeblit_R8G8B8_R8G8B8 (struct grub_video_i386_vbeblit_info *dst,
-                                       struct grub_video_i386_vbeblit_info *src,
-                                       int x, int y, int width, int height,
-                                       int offset_x, int offset_y);
-
-void
-grub_video_i386_vbeblit_index_R8G8B8 (struct grub_video_i386_vbeblit_info *dst,
+grub_video_i386_vbeblit_BGRX8888_RGBX8888 (struct grub_video_i386_vbeblit_info *dst,
+                                           struct grub_video_i386_vbeblit_info *src,
+                                           int x, int y, int width, int height,
+                                           int offset_x, int offset_y);
+
+void
+grub_video_i386_vbeblit_BGRA8888_RGB888 (struct grub_video_i386_vbeblit_info *dst,
+                                         struct grub_video_i386_vbeblit_info *src,
+                                         int x, int y, int width, int height,
+                                         int offset_x, int offset_y);
+
+void
+grub_video_i386_vbeblit_BGR888_RGB888 (struct grub_video_i386_vbeblit_info *dst,
+                                       struct grub_video_i386_vbeblit_info *src,
+                                       int x, int y, int width, int height,
+                                       int offset_x, int offset_y);
+
+void
+grub_video_i386_vbeblit_RGBA8888_RGBA8888 (struct grub_video_i386_vbeblit_info *dst,
+                                           struct grub_video_i386_vbeblit_info *src,
+                                           int x, int y, int width, int height,
+                                           int offset_x, int offset_y);
+
+/* Direct copy for compatible 32 bpp blit formats.
+ * (RGBA8888->RGBA8888, BGRA8888->BGRA8888, etc.) */
+void
+grub_video_i386_vbeblit_direct32_copy (struct grub_video_i386_vbeblit_info *dst,
+                                       struct grub_video_i386_vbeblit_info *src,
+                                       int x, int y, int width, int height,
+                                       int offset_x, int offset_y);
+
+void
+grub_video_i386_vbeblit_RGB888_RGBA8888 (struct grub_video_i386_vbeblit_info *dst,
+                                         struct grub_video_i386_vbeblit_info *src,
+                                         int x, int y, int width, int height,
+                                         int offset_x, int offset_y);
+
+void
+grub_video_i386_vbeblit_RGB888_RGBX8888 (struct grub_video_i386_vbeblit_info *dst,
+                                         struct grub_video_i386_vbeblit_info *src,
+                                         int x, int y, int width, int height,
+                                         int offset_x, int offset_y);
+
+void
+grub_video_i386_vbeblit_index_RGBA8888 (struct grub_video_i386_vbeblit_info *dst,
+                                        struct grub_video_i386_vbeblit_info *src,
+                                        int x, int y, int width, int height,
+                                        int offset_x, int offset_y);
+
+void
+grub_video_i386_vbeblit_index_RGBX8888 (struct grub_video_i386_vbeblit_info *dst,
+                                        struct grub_video_i386_vbeblit_info *src,
+                                        int x, int y, int width, int height,
+                                        int offset_x, int offset_y);
+
+void
+grub_video_i386_vbeblit_RGBA8888_RGB888 (struct grub_video_i386_vbeblit_info *dst,
+                                         struct grub_video_i386_vbeblit_info *src,
+                                         int x, int y, int width, int height,
+                                         int offset_x, int offset_y);
+
+void
+grub_video_i386_vbeblit_RGB888_RGB888 (struct grub_video_i386_vbeblit_info *dst,
+                                       struct grub_video_i386_vbeblit_info *src,
+                                       int x, int y, int width, int height,
+                                       int offset_x, int offset_y);
+
+void
+grub_video_i386_vbeblit_index_RGB888 (struct grub_video_i386_vbeblit_info *dst,
                                       struct grub_video_i386_vbeblit_info *src,
                                       int x, int y, int width, int height,
                                       int offset_x, int offset_y);

=== modified file 'include/grub/i386/pc/vbefill.h'
--- include/grub/i386/pc/vbefill.h	2007-07-21 22:32:33 +0000
+++ include/grub/i386/pc/vbefill.h	2008-07-03 13:49:18 +0000
@@ -25,14 +25,14 @@
 struct grub_video_i386_vbeblit_info;
 
 void
-grub_video_i386_vbefill_R8G8B8A8 (struct grub_video_i386_vbeblit_info *dst,
+grub_video_i386_vbefill_direct32 (struct grub_video_i386_vbeblit_info *dst,
                                   grub_video_color_t color,  int x, int y,
                                   int width, int height);
 
 void
-grub_video_i386_vbefill_R8G8B8 (struct grub_video_i386_vbeblit_info *dst,
-                                grub_video_color_t color, int x, int y,
-                                int width, int height);
+grub_video_i386_vbefill_direct24 (struct grub_video_i386_vbeblit_info *dst,
+                                  grub_video_color_t color, int x, int y,
+                                  int width, int height);
 
 void
 grub_video_i386_vbefill_index (struct grub_video_i386_vbeblit_info *dst,

=== added file 'include/grub/i386/tsc.h'
--- include/grub/i386/tsc.h	1970-01-01 00:00:00 +0000
+++ include/grub/i386/tsc.h	2008-07-04 17:55:21 +0000
@@ -0,0 +1,43 @@
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2008  Free Software Foundation, Inc.
+ *
+ *  GRUB is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef KERNEL_CPU_TSC_HEADER
+#define KERNEL_CPU_TSC_HEADER	1
+
+#include <grub/types.h>
+
+/* Read the TSC value, which increments with each CPU clock cycle. */
+static __inline grub_uint64_t
+grub_get_tsc (void)
+{
+  grub_uint32_t lo, hi;
+
+  /* The CPUID instruction is a 'serializing' instruction, and
+     avoids out-of-order execution of the RDTSC instruction. */
+  __asm__ __volatile__ ("xorl %%eax, %%eax\n\t"
+                        "cpuid":::"%rax", "%rbx", "%rcx", "%rdx");
+  /* Read TSC value.  We cannot use "=A", since this would use
+     %rax on x86_64. */
+  __asm__ __volatile__ ("rdtsc":"=a" (lo), "=d" (hi));
+
+  return (((grub_uint64_t) hi) << 32) | lo;
+}
+
+void grub_tsc_calibrate (void);
+
+#endif /* ! KERNEL_CPU_TSC_HEADER */

=== modified file 'include/grub/time.h'
--- include/grub/time.h	2007-10-22 19:02:16 +0000
+++ include/grub/time.h	2008-07-03 04:19:16 +0000
@@ -1,6 +1,6 @@
 /*
  *  GRUB  --  GRand Unified Bootloader
- *  Copyright (C) 2007  Free Software Foundation, Inc.
+ *  Copyright (C) 2007, 2008  Free Software Foundation, Inc.
  *
  *  GRUB is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License as published by
@@ -19,12 +19,13 @@
 #ifndef KERNEL_TIME_HEADER
 #define KERNEL_TIME_HEADER	1
 
+#include <grub/types.h>
 #include <grub/symbol.h>
 #include <grub/machine/time.h>
 #include <grub/cpu/time.h>
 
 void EXPORT_FUNC(grub_millisleep) (grub_uint32_t ms);
-void EXPORT_FUNC(grub_millisleep_generic) (grub_uint32_t ms);
+grub_uint64_t EXPORT_FUNC(grub_get_time_ms) (void);
 
 static __inline void
 grub_sleep (grub_uint32_t s)

=== modified file 'include/grub/video.h'
--- include/grub/video.h	2008-01-01 12:02:07 +0000
+++ include/grub/video.h	2008-07-03 14:12:08 +0000
@@ -21,6 +21,7 @@
 
 #include <grub/err.h>
 #include <grub/types.h>
+#include <grub/font.h>
 
 /* Video color in hardware dependent format.  Users should not assume any
    specific coding format.  */
@@ -31,40 +32,41 @@
 struct grub_video_render_target;
 
 /* Forward declarations for used data structures.  */
-struct grub_font_glyph;
 struct grub_video_bitmap;
 
 /* Defines used to describe video mode or rendering target.  */
-#define GRUB_VIDEO_MODE_TYPE_ALPHA		0x00000008
-#define GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED	0x00000004
+#define GRUB_VIDEO_MODE_TYPE_ALPHA		0x00000020
+#define GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED	0x00000010
+#define GRUB_VIDEO_MODE_TYPE_1BIT_BITMAP	0x00000004
 #define GRUB_VIDEO_MODE_TYPE_INDEX_COLOR	0x00000002
 #define GRUB_VIDEO_MODE_TYPE_RGB		0x00000001
 
 /* Defines used to mask flags.  */
-#define GRUB_VIDEO_MODE_TYPE_COLOR_MASK		0x00000003
+#define GRUB_VIDEO_MODE_TYPE_COLOR_MASK		0x0000000F
 
 /* Defines used to specify requested bit depth.  */
 #define GRUB_VIDEO_MODE_TYPE_DEPTH_MASK		0x0000ff00
 #define GRUB_VIDEO_MODE_TYPE_DEPTH_POS		8
 
-/* Defined predefined render targets.  */
-#define GRUB_VIDEO_RENDER_TARGET_DISPLAY	((struct grub_video_render_target *) 0)
-#define GRUB_VIDEO_RENDER_TARGET_FRONT_BUFFER	((struct grub_video_render_target *) 0)
-#define GRUB_VIDEO_RENDER_TARGET_BACK_BUFFER	((struct grub_video_render_target *) 1)
+/* Predefined render target: */
+/* The render target that client code should render to. */
+#define GRUB_VIDEO_RENDER_TARGET_DISPLAY \
+  ((struct grub_video_render_target *) 0)
+
 
 /* Defined blitting formats.  */
 enum grub_video_blit_format
   {
-    /* Follow exactly field & mask information.  */
-    GRUB_VIDEO_BLIT_FORMAT_RGBA,
-    /* Make optimization assumption.  */
-    GRUB_VIDEO_BLIT_FORMAT_R8G8B8A8,
-    /* Follow exactly field & mask information.  */
-    GRUB_VIDEO_BLIT_FORMAT_RGB,
-    /* Make optimization assumption.  */
-    GRUB_VIDEO_BLIT_FORMAT_R8G8B8,
+    GRUB_VIDEO_BLIT_FORMAT_RGBA,       /* General: Use fields & masks. */
+    GRUB_VIDEO_BLIT_FORMAT_RGBA_8888,  /* Optimized format. */
+    GRUB_VIDEO_BLIT_FORMAT_BGRA_8888,  /* Optimized format. */
+    GRUB_VIDEO_BLIT_FORMAT_RGB,        /* General: Use fields & masks. */
+    GRUB_VIDEO_BLIT_FORMAT_RGB_888,    /* Optimized format. */
+    GRUB_VIDEO_BLIT_FORMAT_BGR_888,    /* Optimized format. */
     /* When needed, decode color or just use value as is.  */
-    GRUB_VIDEO_BLIT_FORMAT_INDEXCOLOR
+    GRUB_VIDEO_BLIT_FORMAT_INDEXCOLOR,
+    /* Two color bitmap; bits packed: rows are not padded to byte boundary. */
+    GRUB_VIDEO_BLIT_FORMAT_1BIT_PACKED
   };
 
 /* Define blitting operators.  */
@@ -127,6 +129,18 @@
   /* What is location of reserved color bits.  In Index Color mode, 
      this is 0.  */
   unsigned int reserved_field_pos;
+
+  /* For 1-bit bitmaps, the background color.  Used for bits = 0. */
+  grub_uint8_t bg_red;
+  grub_uint8_t bg_green;
+  grub_uint8_t bg_blue;
+  grub_uint8_t bg_alpha;
+
+  /* For 1-bit bitmaps, the foreground color.  Used for bits = 1. */
+  grub_uint8_t fg_red;
+  grub_uint8_t fg_green;
+  grub_uint8_t fg_blue;
+  grub_uint8_t fg_alpha;
 };
 
 struct grub_video_palette_data
@@ -183,6 +197,10 @@
   grub_err_t (*blit_glyph) (struct grub_font_glyph *glyph,
                             grub_video_color_t color, int x, int y);
 
+  grub_err_t (*draw_string) (const char *str, grub_font_t font, 
+                             grub_video_color_t color, 
+                             int left_x, int baseline_y);
+
   grub_err_t (*blit_bitmap) (struct grub_video_bitmap *bitmap,
                              enum grub_video_blit_operators oper,
                              int x, int y, int offset_x, int offset_y,
@@ -255,6 +273,10 @@
 grub_err_t grub_video_blit_glyph (struct grub_font_glyph *glyph,
                                   grub_video_color_t color, int x, int y);
 
+grub_err_t grub_video_draw_string (const char *str, grub_font_t font, 
+                                   grub_video_color_t color, 
+                                   int left_x, int baseline_y);
+
 grub_err_t grub_video_blit_bitmap (struct grub_video_bitmap *bitmap,
                                    enum grub_video_blit_operators oper,
                                    int x, int y, int offset_x, int offset_y,
@@ -269,6 +291,9 @@
 
 grub_err_t grub_video_scroll (grub_video_color_t color, int dx, int dy);
 
+/* Swap the pages referred to by the front buffer and back buffer render
+ * targets, and the page previously referred to by the back buffer is made
+ * visible on the display. */
 grub_err_t grub_video_swap_buffers (void);
 
 grub_err_t grub_video_create_render_target (struct grub_video_render_target **result,

=== added directory 'kern/generic'
=== added file 'kern/generic/get_time_ms.c'
--- kern/generic/get_time_ms.c	1970-01-01 00:00:00 +0000
+++ kern/generic/get_time_ms.c	2008-07-04 16:55:48 +0000
@@ -0,0 +1,37 @@
+/* get_time_ms.c - generic time implementation -- using platform RTC.
+ * The generic implementation of these functions can be used for architectures
+ * or platforms that do not have a more specialized implementation. */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2008  Free Software Foundation, Inc.
+ *
+ *  GRUB is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/time.h>
+#include <grub/misc.h>
+
+/* Calculate the time in milliseconds since the epoch based on the RTC. */
+grub_uint64_t
+grub_get_time_ms (void)
+{
+  /* By dimensional analysis: 
+    
+      1000 ms   N rtc ticks       1 s
+      ------- * ----------- * ----------- = 1000*N/T ms
+        1 s          1        T rtc ticks
+   */
+  grub_uint64_t ticks_ms_per_sec = ((grub_uint64_t) 1000) * grub_get_rtc ();
+  return grub_divmod64 (ticks_ms_per_sec, GRUB_TICKS_PER_SECOND, 0);
+}

=== added file 'kern/generic/millisleep.c'
--- kern/generic/millisleep.c	1970-01-01 00:00:00 +0000
+++ kern/generic/millisleep.c	2008-07-04 16:55:48 +0000
@@ -0,0 +1,39 @@
+/* millisleep.c - generic millisleep function.
+ * The generic implementation of these functions can be used for architectures
+ * or platforms that do not have a more specialized implementation. */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 1999,2000,2001,2002,2003,2004,2005,2006,2007,2008  Free Software Foundation, Inc.
+ *
+ *  GRUB is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/misc.h>
+#include <grub/time.h>
+
+void
+grub_millisleep (grub_uint32_t ms)
+{
+  grub_uint64_t start;
+
+  start = grub_get_time_ms ();
+
+  /* Instead of setting an end time and looping while the current time is 
+     less than that, comparing the elapsed sleep time with the desired sleep
+     time handles the (unlikely!) case that the timer would wrap around 
+     during the sleep. */
+
+  while (grub_get_time_ms () - start < ms)
+    grub_cpu_idle ();
+}

=== modified file 'kern/i386/efi/init.c'
--- kern/i386/efi/init.c	2007-10-22 18:59:33 +0000
+++ kern/i386/efi/init.c	2008-07-04 17:55:21 +0000
@@ -25,18 +25,13 @@
 #include <grub/cache.h>
 #include <grub/kernel.h>
 #include <grub/efi/efi.h>
-#include <grub/time.h>
-
-void
-grub_millisleep (grub_uint32_t ms)
-{
-  grub_millisleep_generic (ms);
-}
+#include <grub/cpu/tsc.h>
 
 void
 grub_machine_init (void)
 {
   grub_efi_init ();
+  grub_tsc_calibrate ();
 }
 
 void

=== modified file 'kern/i386/pc/init.c'
--- kern/i386/pc/init.c	2008-06-15 17:21:16 +0000
+++ kern/i386/pc/init.c	2008-07-04 18:03:26 +0000
@@ -30,6 +30,7 @@
 #include <grub/env.h>
 #include <grub/cache.h>
 #include <grub/time.h>
+#include <grub/cpu/tsc.h>
 
 struct mem_region
 {
@@ -46,12 +47,6 @@
 grub_size_t grub_os_area_size;
 grub_size_t grub_lower_mem, grub_upper_mem;
 
-void
-grub_millisleep (grub_uint32_t ms)
-{
-  grub_millisleep_generic (ms);
-}
-
 void 
 grub_arch_sync_caches (void *address __attribute__ ((unused)),
 		       grub_size_t len __attribute__ ((unused)))
@@ -231,6 +226,8 @@
   
   if (! grub_os_area_addr)
     grub_fatal ("no upper memory");
+
+  grub_tsc_calibrate ();
 }
 
 void

=== added file 'kern/i386/tsc.c'
--- kern/i386/tsc.c	1970-01-01 00:00:00 +0000
+++ kern/i386/tsc.c	2008-07-04 17:55:21 +0000
@@ -0,0 +1,89 @@
+/* kern/i386/tsc.c - x86 TSC time source implementation
+ * Requires Pentium or better x86 CPU that supports the RDTSC instruction.
+ * This module uses the RTC (via grub_get_rtc()) to calibrate the TSC to 
+ * real time.
+ *
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2008  Free Software Foundation, Inc.
+ *
+ *  GRUB is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/types.h>
+#include <grub/time.h>
+#include <grub/misc.h>
+#include <grub/i386/tsc.h>
+
+/* Calibrated reference for TSC=0.  This defines the time since the epoch in 
+   milliseconds that TSC=0 refers to. */
+static grub_uint64_t tsc_boot_time;
+
+/* Calibrated TSC rate.  (In TSC ticks per millisecond.) */
+static grub_uint64_t tsc_ticks_per_ms;
+
+
+/* Declared in <grub/time.h>. */
+grub_uint64_t
+grub_get_time_ms (void)
+{
+  return tsc_boot_time + grub_divmod64 (grub_get_tsc (), tsc_ticks_per_ms, 0);
+}
+
+
+/* How many RTC ticks to use for calibration loop. (>= 1) */
+#define CALIBRATION_TICKS 2
+
+/* Calibrate the TSC based on the RTC.  */
+void
+grub_tsc_calibrate (void)
+{
+  /* First calbrate the TSC rate (relative, not absolute time). */
+  grub_uint64_t start_tsc;
+  grub_uint64_t end_tsc;
+  grub_uint32_t initial_tick;
+  grub_uint32_t start_tick;
+  grub_uint32_t end_tick;
+
+  /* Wait for the start of the next tick;
+     we'll base out timing off this edge. */
+  initial_tick = grub_get_rtc ();
+  do
+    {
+      start_tick = grub_get_rtc ();
+    }
+  while (start_tick == initial_tick);
+  start_tsc = grub_get_tsc ();
+
+  /* Wait for the start of the next tick.  This will
+     be the end of the 1-tick period. */
+  do
+    {
+      end_tick = grub_get_rtc ();
+    }
+  while (end_tick - start_tick < CALIBRATION_TICKS);
+  end_tsc = grub_get_tsc ();
+
+  tsc_ticks_per_ms =
+    grub_divmod64 (grub_divmod64
+                   (end_tsc - start_tsc, end_tick - start_tick, 0)
+                   * GRUB_TICKS_PER_SECOND, 1000, 0);
+
+  /* Reference the TSC zero (boot time) to the epoch to 
+     get an absolute real time reference. */
+  grub_uint64_t ms_since_boot = grub_divmod64 (end_tsc, tsc_ticks_per_ms, 0);
+  grub_uint64_t mstime_now = grub_divmod64 ((grub_uint64_t) 1000 * end_tick,
+                                            GRUB_TICKS_PER_SECOND,
+                                            0);
+  tsc_boot_time = mstime_now - ms_since_boot;
+}

=== modified file 'kern/ieee1275/init.c'
--- kern/ieee1275/init.c	2008-07-04 02:01:55 +0000
+++ kern/ieee1275/init.c	2008-07-04 19:43:25 +0000
@@ -47,12 +47,6 @@
 extern char _end[];
 
 void
-grub_millisleep (grub_uint32_t ms)
-{
-  grub_millisleep_generic (ms);
-}
-
-void
 grub_exit (void)
 {
   grub_ieee1275_exit ();
@@ -260,8 +254,8 @@
   grub_console_fini ();
 }
 
-grub_uint32_t
-grub_get_rtc (void)
+grub_uint64_t
+grub_get_time_ms (void)
 {
   grub_uint32_t msecs = 0;
 
@@ -270,6 +264,12 @@
   return msecs;
 }
 
+grub_uint32_t
+grub_get_rtc (void)
+{
+  return grub_get_time_ms ();
+}
+
 grub_addr_t
 grub_arch_modules_addr (void)
 {

=== modified file 'kern/misc.c'
--- kern/misc.c	2008-06-15 23:42:48 +0000
+++ kern/misc.c	2008-07-04 18:03:26 +0000
@@ -23,7 +23,6 @@
 #include <stdarg.h>
 #include <grub/term.h>
 #include <grub/env.h>
-#include <grub/time.h>
 
 void *
 grub_memmove (void *dest, const void *src, grub_size_t n)
@@ -1018,17 +1017,6 @@
   return p - dest;
 }
 
-void
-grub_millisleep_generic (grub_uint32_t ms)
-{
-  grub_uint32_t end_at;
-
-  end_at = grub_get_rtc () + grub_div_roundup (ms * GRUB_TICKS_PER_SECOND, 1000);
-
-  while (grub_get_rtc () < end_at)
-    grub_cpu_idle ();
-}
-
 /* Abort GRUB. This function does not return.  */
 void
 grub_abort (void)

=== modified file 'kern/sparc64/ieee1275/init.c'
--- kern/sparc64/ieee1275/init.c	2007-10-22 18:59:33 +0000
+++ kern/sparc64/ieee1275/init.c	2008-07-04 17:55:21 +0000
@@ -66,12 +66,6 @@
   /* Never reached.  */
 }
 
-void
-grub_millisleep (grub_uint32_t ms)
-{
-  grub_millisleep_generic (ms);
-}
-
 int
 grub_ieee1275_test_flag (enum grub_ieee1275_flag flag)
 {

=== added directory 'protomenu'
=== added file 'protomenu/protomenu.c'
--- protomenu/protomenu.c	1970-01-01 00:00:00 +0000
+++ protomenu/protomenu.c	2008-07-05 17:59:13 +0000
@@ -0,0 +1,277 @@
+/* protomenu.c - Prototype graphical menu: command implementation. */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2003,2007  Free Software Foundation, Inc.
+ *  Copyright (C) 2003  NIIBE Yutaka <gniibe@m17n.org>
+ *
+ *  GRUB is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/types.h>
+#include <grub/misc.h>
+#include <grub/mm.h>
+#include <grub/err.h>
+#include <grub/dl.h>
+#include <grub/normal.h>
+#include <grub/video.h>
+#include <grub/bitmap.h>
+#include <grub/bitmap_scale.h>
+#include <grub/term.h>
+
+/* A 2D rectangle type.
+ * This could be worth integrating into the video API if it proves useful.*/
+struct grub_video_rect
+{
+  /* These are signed because if there are unsigned it causes Bad Things 
+   * to happen when arithmetic and comparisions involving signed types is 
+   * done.  Important signed types include offsets from absolute locations. */
+  int x;
+  int y;
+  int width;
+  int height;
+};
+typedef struct grub_video_rect grub_video_rect_t;
+
+typedef struct menu_item
+{
+  const char *name;
+  struct grub_video_bitmap *icon;
+  /* TODO add a command to execute. */
+}
+menu_item_t;
+
+static const int icon_width = 48;
+static const int icon_height = 48;
+static grub_video_rect_t screen;
+static grub_font_t title_font;
+static grub_font_t item_font;
+static grub_font_t status_font;
+static grub_video_color_t title_color;
+static grub_video_color_t item_color;
+static grub_video_color_t status_color;
+static grub_video_color_t status_bg_color;
+static struct grub_video_bitmap *background_image;
+static const char title_text[] = "GRUB 2 Boot Menu";
+static menu_item_t items[3];
+static int selected_item_index;
+
+static void
+style_init (void)
+{
+  if (!(title_font = grub_font_get ("Helvetica Bold 24"))
+      || !(item_font = grub_font_get ("Helvetica Bold 14"))
+      || !(status_font = grub_font_get ("Helvetica 10")))
+    return;
+
+  title_color = grub_video_map_rgb (0, 0, 0);
+  item_color = grub_video_map_rgb (0, 0, 0);
+  status_color = grub_video_map_rgb (255, 255, 255);
+  status_bg_color = grub_video_map_rgba (0, 0, 0, 112);
+
+  struct grub_video_bitmap *img;
+  grub_video_bitmap_load (&img, "/boot/images/bg-protomenu1.tga");
+  if (grub_errno != GRUB_ERR_NONE)
+    return;
+
+  grub_video_bitmap_create_scaled (&background_image, screen.width,
+                                   screen.height, img,
+                                   GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST);
+  if (grub_errno != GRUB_ERR_NONE)
+    return;
+  grub_video_bitmap_destroy (img);
+
+
+  items[0].name = "Ubuntu 8.10";
+  grub_video_bitmap_load (&img, "/boot/images/icon-ubuntu.tga");
+  if (grub_errno == GRUB_ERR_NONE)
+    {
+      grub_video_bitmap_create_scaled (&items[0].icon, icon_width,
+                                       icon_height, img,
+                                       GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST);
+      if (grub_errno != GRUB_ERR_NONE)
+        return;
+      grub_video_bitmap_destroy (img);
+    }
+
+  items[1].name = "Gentoo Linux";
+  grub_video_bitmap_load (&img, "/boot/images/icon-gentoo.tga");
+  if (grub_errno == GRUB_ERR_NONE)
+    {
+      grub_video_bitmap_create_scaled (&items[1].icon, icon_width,
+                                       icon_height, img,
+                                       GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST);
+      if (grub_errno != GRUB_ERR_NONE)
+        return;
+      grub_video_bitmap_destroy (img);
+    }
+
+  items[2].name = "Windows Vista";
+  grub_video_bitmap_load (&img, "/boot/images/icon-vista.tga");
+  if (grub_errno == GRUB_ERR_NONE)
+    {
+      grub_video_bitmap_create_scaled (&items[2].icon, icon_width,
+                                       icon_height, img,
+                                       GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST);
+      if (grub_errno != GRUB_ERR_NONE)
+        return;
+      grub_video_bitmap_destroy (img);
+    }
+}
+
+static void
+draw_background (void)
+{
+  grub_video_blit_bitmap (background_image, GRUB_VIDEO_BLIT_REPLACE,
+                          0, 0, 0, 0,
+                          grub_video_bitmap_get_width (background_image),
+                          grub_video_bitmap_get_height (background_image));
+}
+
+static void
+draw_menu (void)
+{
+  /* TODO make this use pixmaps and draw a cool menu */
+
+  grub_video_rect_t r;
+  r.width = screen.width * 2 / 3;
+  r.height = screen.height * 1 / 2;
+  r.x = (screen.width - r.width) / 2;
+  r.y = (screen.height - r.height) / 2;
+
+  grub_video_color_t bg = grub_video_map_rgb (255, 255, 255);
+  grub_video_fill_rect (bg, r.x, r.y, r.width, r.height);
+
+  int boxpad = 6;
+  int icon_text_space = 4;
+  int item_pad = 3;
+  int item_vspace = 2;
+
+  int ascent = grub_font_get_ascent (item_font);
+  int descent = grub_font_get_descent (item_font);
+  int item_top = r.y + boxpad;
+  int item_left = r.x + boxpad;
+  int item_height = 54;
+  unsigned i;
+  for (i = 0; i < sizeof (items) / sizeof (*items); i++)
+    {
+      if ((int) i == selected_item_index)
+        {
+          /* TODO use pixmap-stylized box to highlight selected item */
+          grub_video_fill_rect (grub_video_map_rgb (128, 128, 255),
+                                item_left, item_top,
+                                r.width - 2 * boxpad, item_height);
+        }
+
+      if (items[i].icon)
+        grub_video_blit_bitmap (items[i].icon, GRUB_VIDEO_BLIT_BLEND,
+                                item_left,
+                                item_top + (item_height - icon_height) / 2,
+                                0, 0, icon_width, icon_height);
+
+      grub_video_draw_string (items[i].name, item_font, item_color,
+                              item_left + icon_width + icon_text_space,
+                              (item_top + (item_height - (ascent + descent)) 
+                               / 2 + ascent));
+
+      item_top += item_height + item_vspace;
+    }
+}
+
+static void
+draw_title (void)
+{
+  /* Center the title. */
+  int title_width = grub_font_get_string_width (title_font, title_text);
+  int x = (screen.width - title_width) / 2;
+  int y = 40 + grub_font_get_ascent (title_font);
+  grub_video_draw_string (title_text, title_font, title_color, x, y);
+}
+
+static void
+draw_status (void)
+{
+  int descent = grub_font_get_descent (status_font);
+  int ascent = grub_font_get_ascent (status_font);
+  int vpad = 5;
+  int textheight = descent + ascent;
+  int h = 2 * vpad + textheight;
+
+  grub_video_fill_rect (status_bg_color, 0, screen.height - h,
+                        screen.width, screen.height - 1);
+
+  int texty = screen.height - vpad - descent;
+  grub_video_draw_string ("Select an item with the arrow keys and "
+                          "press Enter to boot.",
+                          status_font, status_color, 30, texty);
+}
+
+static grub_err_t
+grub_cmd_protomenu (struct grub_arg_list *state __attribute__ ((unused)),
+                    int argc __attribute__ ((unused)),
+                    char **args __attribute__ ((unused)))
+{
+  if (grub_video_setup (1024, 768, GRUB_VIDEO_MODE_TYPE_RGB) != GRUB_ERR_NONE)
+    return grub_errno;
+
+  grub_video_get_viewport ((unsigned *) &screen.x,
+                           (unsigned *) &screen.y,
+                           (unsigned *) &screen.width,
+                           (unsigned *) &screen.height);
+
+  style_init ();
+  selected_item_index = 0;
+
+  int done = 0;
+  while (!done)
+    {
+      draw_background ();
+      draw_menu ();
+      draw_title ();
+      draw_status ();
+
+      int c = GRUB_TERM_ASCII_CHAR (grub_getkey ());
+      if (c == 'j')
+        {
+          selected_item_index++;
+          if (selected_item_index >= (int) (sizeof (items) / sizeof (*items)))
+            selected_item_index = 0;
+        }
+      else if (c == 'k')
+        {
+          selected_item_index--;
+          if (selected_item_index < 0)
+            selected_item_index = (sizeof (items) / sizeof (*items)) - 1;
+        }
+      else if (c == GRUB_TERM_ESC)
+        {
+          done = 1;
+        }
+    }
+
+  grub_video_restore ();
+  return 0;
+}
+
+GRUB_MOD_INIT (protomenu)
+{
+  (void) mod;                   /* To stop warning. */
+  grub_register_command ("protomenu",
+                         grub_cmd_protomenu, GRUB_COMMAND_FLAG_BOTH,
+                         "protomenu", "Show graphical menu prototype", 0);
+}
+
+GRUB_MOD_FINI (protomenu)
+{
+  grub_unregister_command ("protomenu");
+}

=== modified file 'term/gfxterm.c'
--- term/gfxterm.c	2008-01-01 12:02:07 +0000
+++ term/gfxterm.c	2008-07-03 14:28:01 +0000
@@ -29,14 +29,12 @@
 #include <grub/env.h>
 #include <grub/video.h>
 #include <grub/bitmap.h>
+#include <grub/bitmap_scale.h>
 
 #define DEFAULT_VIDEO_WIDTH	640
 #define DEFAULT_VIDEO_HEIGHT	480
 #define DEFAULT_VIDEO_FLAGS	0
 
-#define DEFAULT_CHAR_WIDTH  	8
-#define DEFAULT_CHAR_HEIGHT 	16
-
 #define DEFAULT_BORDER_WIDTH	10
 
 #define DEFAULT_STANDARD_COLOR  0x07
@@ -91,6 +89,9 @@
   unsigned int cursor_y;
   int cursor_state;
 
+  /* Font settings. */
+  grub_font_t font;
+
   /* Terminal color settings.  */
   grub_uint8_t standard_color_setting;
   grub_uint8_t normal_color_setting;
@@ -170,18 +171,25 @@
 
 static grub_err_t
 grub_virtual_screen_setup (unsigned int x, unsigned int y,
-                           unsigned int width, unsigned int height)
+                           unsigned int width, unsigned int height,
+                           const char *font_name)
 {
   /* Free old virtual screen.  */
   grub_virtual_screen_free ();
 
   /* Initialize with default data.  */
+  virtual_screen.font = grub_font_get (font_name);
+  if (!virtual_screen.font)
+    return grub_error (GRUB_ERR_BAD_FONT,
+                       "No font loaded.");
   virtual_screen.width = width;
   virtual_screen.height = height;
   virtual_screen.offset_x = x;
   virtual_screen.offset_y = y;
-  virtual_screen.char_width = DEFAULT_CHAR_WIDTH;
-  virtual_screen.char_height = DEFAULT_CHAR_HEIGHT;
+  virtual_screen.char_width = 
+    grub_font_get_max_char_width (virtual_screen.font);
+  virtual_screen.char_height = 
+    grub_font_get_max_char_height (virtual_screen.font);
   virtual_screen.cursor_x = 0;
   virtual_screen.cursor_y = 0;
   virtual_screen.cursor_state = 1;
@@ -229,6 +237,7 @@
 static grub_err_t
 grub_gfxterm_init (void)
 {
+  char *font_name;
   char *modevar;
   int width = DEFAULT_VIDEO_WIDTH;
   int height = DEFAULT_VIDEO_HEIGHT;
@@ -236,6 +245,11 @@
   int flags = DEFAULT_VIDEO_FLAGS;
   grub_video_color_t color;
 
+  /* Select the font to use. */
+  font_name = grub_env_get ("gfxterm_font");
+  if (!font_name)
+    font_name = "";   /* Allow fallback to any font. */
+
   /* Parse gfxmode environment variable if set.  */
   modevar = grub_env_get ("gfxmode");
   if (modevar)
@@ -475,7 +489,7 @@
 
   /* Create virtual screen.  */
   if (grub_virtual_screen_setup (DEFAULT_BORDER_WIDTH, DEFAULT_BORDER_WIDTH,
-                                 width, height) != GRUB_ERR_NONE)
+                                 width, height, font_name) != GRUB_ERR_NONE)
     {
       grub_video_restore ();
       return grub_errno;
@@ -661,11 +675,12 @@
 write_char (void)
 {
   struct grub_colored_char *p;
-  struct grub_font_glyph glyph;
+  struct grub_font_glyph *glyph;
   grub_video_color_t color;
   grub_video_color_t bgcolor;
   unsigned int x;
   unsigned int y;
+  int ascent;
 
   /* Find out active character.  */
   p = (virtual_screen.text_buffer
@@ -675,7 +690,8 @@
   p -= p->index;
 
   /* Get glyph for character.  */
-  grub_font_get_glyph (p->code, &glyph);
+  glyph = grub_font_get_glyph (virtual_screen.font, p->code);
+  ascent = grub_font_get_ascent (virtual_screen.font);
 
   color = p->fg_color;
   bgcolor = p->bg_color;
@@ -685,13 +701,13 @@
 
   /* Render glyph to text layer.  */
   grub_video_set_active_render_target (text_layer);
-  grub_video_fill_rect (bgcolor, x, y, glyph.width, glyph.height);
-  grub_video_blit_glyph (&glyph, color, x, y);
+  grub_video_fill_rect (bgcolor, x, y, glyph->width, glyph->height);
+  grub_video_blit_glyph (glyph, color, x, y + ascent);
   grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
 
   /* Mark character to be drawn.  */
   dirty_region_add (virtual_screen.offset_x + x, virtual_screen.offset_y + y,
-                    glyph.width, glyph.height);
+                    glyph->width, glyph->height);
 }
 
 static void
@@ -705,7 +721,8 @@
 
   /* Determine cursor properties and position on text layer. */
   x = virtual_screen.cursor_x * virtual_screen.char_width;
-  y = ((virtual_screen.cursor_y + 1) * virtual_screen.char_height) - 3;  
+  y = (virtual_screen.cursor_y * virtual_screen.char_height   
+       + grub_font_get_ascent (virtual_screen.font));
   width = virtual_screen.char_width;
   height = 2;
 
@@ -822,14 +839,18 @@
     }
   else
     {
-      struct grub_font_glyph glyph;
+      struct grub_font_glyph *glyph;
       struct grub_colored_char *p;
+      unsigned char_width;
 
       /* Get properties of the character.  */    
-      grub_font_get_glyph (c, &glyph);
+      glyph = grub_font_get_glyph (virtual_screen.font, c);
+
+      /* TODO [CDB] Fix wide characters.  Bi-width font? */
+      char_width = 1;
 
       /* If we are about to exceed line length, wrap to next line.  */
-      if (virtual_screen.cursor_x + glyph.char_width > virtual_screen.columns)
+      if (virtual_screen.cursor_x + char_width > virtual_screen.columns)
         grub_putchar ('\n');
 
       /* Find position on virtual screen, and fill information.  */
@@ -839,18 +860,18 @@
       p->code = c;
       p->fg_color = virtual_screen.fg_color;
       p->bg_color = virtual_screen.bg_color;
-      p->width = glyph.char_width - 1;
+      p->width = char_width - 1;
       p->index = 0;
 
       /* If we have large glyph, add fixup info.  */
-      if (glyph.char_width > 1)
+      if (char_width > 1)
         {
           unsigned i;
 
-          for (i = 1; i < glyph.char_width; i++)
+          for (i = 1; i < char_width; i++)
             {
               p[i].code = ' ';
-              p[i].width = glyph.char_width - 1;
+              p[i].width = char_width - 1;
               p[i].index = i;
             }
         }
@@ -859,7 +880,7 @@
       write_char ();
 
       /* Make sure we scroll screen when needed and wrap line correctly.  */
-      virtual_screen.cursor_x += glyph.char_width;
+      virtual_screen.cursor_x += char_width;
       if (virtual_screen.cursor_x >= virtual_screen.columns)
         {
           virtual_screen.cursor_x = 0;
@@ -879,11 +900,15 @@
 static grub_ssize_t
 grub_gfxterm_getcharwidth (grub_uint32_t c)
 {
-  struct grub_font_glyph glyph;
-
-  grub_font_get_glyph (c, &glyph);
-
-  return glyph.char_width;
+#if 0
+  struct grub_font_glyph *glyph;
+
+  glyph = grub_font_get_glyph (c);
+
+  return glyph->char_width;
+#else
+  return 1;   /* TODO [CDB] Fix wide characters. */
+#endif
 }
 
 static grub_uint16_t
@@ -1014,8 +1039,18 @@
   dirty_region_redraw ();
 }
 
+
+/* Option array indices. */
+#define BACKGROUND_CMD_ARGINDEX_MODE 0
+
+static const struct grub_arg_option background_image_cmd_options[] = {
+  {"mode", 'm', 0, "Background image mode (`stretch', `normal').", 0, 
+    ARG_TYPE_STRING},
+  {0, 0, 0, 0, 0, 0}
+};
+
 static grub_err_t
-grub_gfxterm_background_image_cmd (struct grub_arg_list *state __attribute__ ((unused)),
+grub_gfxterm_background_image_cmd (struct grub_arg_list *state,
                                    int argc,
                                    char **args)
 {
@@ -1042,12 +1077,36 @@
     if (grub_errno != GRUB_ERR_NONE)
       return grub_errno;
 
+    /* Determine if the bitmap should be scaled to fit the screen. */
+    if (!state[BACKGROUND_CMD_ARGINDEX_MODE].set
+        || grub_strcmp (state[BACKGROUND_CMD_ARGINDEX_MODE].arg,
+                        "stretch") == 0)
+        {
+          if (mode_info.width != grub_video_bitmap_get_width (bitmap)
+              || mode_info.height != grub_video_bitmap_get_height (bitmap)) 
+            {
+              struct grub_video_bitmap *scaled_bitmap;
+              grub_video_bitmap_create_scaled (&scaled_bitmap,
+                                              mode_info.width, 
+                                              mode_info.height,
+                                              bitmap,
+                                              GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST);
+              if (grub_errno == GRUB_ERR_NONE)
+                {
+                  /* Replace the original bitmap with the scaled one. */
+                  grub_video_bitmap_destroy (bitmap);
+                  bitmap = scaled_bitmap;
+                }
+            }
+        }
+
+
     /* If bitmap was loaded correctly, display it.  */
     if (bitmap)
       {
         /* Determine bitmap dimensions.  */
         bitmap_width = grub_video_bitmap_get_width (bitmap);
-        bitmap_height = grub_video_bitmap_get_width (bitmap);
+        bitmap_height = grub_video_bitmap_get_height (bitmap);
         
         /* Mark whole screen as dirty.  */
         dirty_region_reset ();
@@ -1092,7 +1151,7 @@
                          GRUB_COMMAND_FLAG_BOTH,
                          "background_image",
                          "Load background image for active terminal",
-                         0);
+                         background_image_cmd_options);
 }
 
 GRUB_MOD_FINI(term_gfxterm)

=== modified file 'term/i386/pc/vesafb.c'
--- term/i386/pc/vesafb.c	2007-12-30 08:52:06 +0000
+++ term/i386/pc/vesafb.c	2008-07-03 14:12:08 +0000
@@ -250,10 +250,11 @@
 	  break;
 
 	default:
-	  return grub_font_get_glyph (code, bitmap, width);
+	  return grub_font_get_glyph_any (code, bitmap, width);
 	}
     }
 
+  /* TODO [CDB] This is wrong for the new font module.  Should it be fixed? */
   if (bitmap)
     grub_memcpy (bitmap,
 		 vga_font + code * virtual_screen.char_height,

=== modified file 'term/i386/pc/vga.c'
--- term/i386/pc/vga.c	2008-01-21 15:48:27 +0000
+++ term/i386/pc/vga.c	2008-07-03 14:12:08 +0000
@@ -65,6 +65,7 @@
 static struct colored_char text_buf[TEXT_WIDTH * TEXT_HEIGHT];
 static unsigned char saved_map_mask;
 static int page = 0;
+static grub_font_t font = 0;
 
 #define SEQUENCER_ADDR_PORT	0x3C4
 #define SEQUENCER_DATA_PORT	0x3C5
@@ -161,6 +162,9 @@
   saved_map_mask = get_map_mask ();
   set_map_mask (0x0f);
   set_start_address (PAGE_OFFSET (page));
+  font = grub_font_get ("");  /* Choose any font, for now. */
+  if (!font)
+    return grub_error (GRUB_ERR_BAD_FONT, "No font loaded.");
   
   return GRUB_ERR_NONE;
 }
@@ -185,7 +189,7 @@
 write_char (void)
 {
   struct colored_char *p = text_buf + xpos + ypos * TEXT_WIDTH;
-  struct grub_font_glyph glyph;
+  struct grub_font_glyph *glyph;
   unsigned char *mem_base;
   unsigned plane;
 
@@ -194,7 +198,7 @@
   p -= p->index;
 
   /* Get glyph for character.  */
-  grub_font_get_glyph (p->code, &glyph);
+  glyph = grub_font_get_glyph (font, p->code);
   
   for (plane = 0x01; plane <= 0x08; plane <<= 1)
     {
@@ -210,17 +214,21 @@
 	{
 	  unsigned i;
 
-	  for (i = 0; i < glyph.char_width && offset < 32; i++)
+          unsigned char_width = 1; /* TODO [CDB] Figure out wide characters. */
+          /* TODO [CDB] Re-implement glyph drawing for vga module. */
+#if 0
+	  for (i = 0; i < char_width && offset < 32; i++)
 	    {
 	      unsigned char fg_mask, bg_mask;
 	      
-	      fg_mask = (p->fg_color & plane) ? glyph.bitmap[offset] : 0;
-	      bg_mask = (p->bg_color & plane) ? ~(glyph.bitmap[offset]) : 0;
+	      fg_mask = (p->fg_color & plane) ? glyph->bitmap[offset] : 0;
+	      bg_mask = (p->bg_color & plane) ? ~(glyph->bitmap[offset]) : 0;
 	      offset++;
 
 	      if (check_vga_mem (mem + i))
 		mem[i] = (fg_mask | bg_mask);
 	    }
+#endif /* 0 */ 
 	}
     }
 
@@ -320,36 +328,37 @@
     }
   else
     {
-      struct grub_font_glyph glyph;
+      struct grub_font_glyph *glyph;
       struct colored_char *p;
+      unsigned char_width = 1;
       
-      grub_font_get_glyph(c, &glyph);
+      glyph = grub_font_get_glyph(font, c);
 
-      if (xpos + glyph.char_width > TEXT_WIDTH)
+      if (xpos + char_width > TEXT_WIDTH)
 	grub_putchar ('\n');
 
       p = text_buf + xpos + ypos * TEXT_WIDTH;
       p->code = c;
       p->fg_color = fg_color;
       p->bg_color = bg_color;
-      p->width = glyph.char_width - 1;
+      p->width = char_width - 1;
       p->index = 0;
 
-      if (glyph.char_width > 1)
+      if (char_width > 1)
 	{
 	  unsigned i;
 
-	  for (i = 1; i < glyph.char_width; i++)
+	  for (i = 1; i < char_width; i++)
 	    {
 	      p[i].code = ' ';
-	      p[i].width = glyph.char_width - 1;
+	      p[i].width = char_width - 1;
 	      p[i].index = i;
 	    }
 	}
 	  
       write_char ();
   
-      xpos += glyph.char_width;
+      xpos += char_width;
       if (xpos >= TEXT_WIDTH)
 	{
 	  xpos = 0;
@@ -381,11 +390,16 @@
 static grub_ssize_t
 grub_vga_getcharwidth (grub_uint32_t c)
 {
+#if 0
   struct grub_font_glyph glyph;
   
-  grub_font_get_glyph (c, &glyph);
+  glyph = grub_font_get_glyph (c);
   
   return glyph.char_width;
+#else
+  /* TODO [CDB] Glyph wide characters? vga? */
+  return 1;
+#endif
 }
 
 static grub_uint16_t

=== added directory 'util/fonttool'
=== added file 'util/fonttool/build.xml'
--- util/fonttool/build.xml	1970-01-01 00:00:00 +0000
+++ util/fonttool/build.xml	2008-07-03 14:08:39 +0000
@@ -0,0 +1,23 @@
+<project name="fonttool" default="jar" basedir=".">
+    <description>GRUB 2 font tool.</description>
+
+    <target name="clean">
+        <delete dir="build"/>
+    </target>
+
+    <target name="compile">
+        <mkdir dir="build/src"/>
+        <javac srcdir="src" destdir="build/src" source="1.5" target="1.5"
+               optimize="yes" debug="yes" encoding="UTF-8"/>
+    </target>
+
+    <target name="jar" depends="compile">
+        <jar jarfile="build/fonttool.jar" compress="yes">
+            <fileset dir="build/src"/>
+            <fileset dir="src">
+                <include name="*.txt"/>
+                <include name="*.png"/>
+            </fileset>
+        </jar>
+    </target>
+</project>

=== added file 'util/fonttool/fonttool.iml'
--- util/fonttool/fonttool.iml	1970-01-01 00:00:00 +0000
+++ util/fonttool/fonttool.iml	2008-07-03 14:08:39 +0000
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module version="4" relativePaths="true" type="JAVA_MODULE">
+  <component name="ModuleRootManager" />
+  <component name="NewModuleRootManager" inherit-compiler-output="true">
+    <exclude-output />
+    <content url="file://$MODULE_DIR$">
+      <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
+      <sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="module-library">
+      <library>
+        <CLASSES>
+          <root url="jar://$APPLICATION_HOME_DIR$/lib/junit-4.4.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES />
+      </library>
+    </orderEntry>
+    <orderEntryProperties />
+  </component>
+</module>
+

=== added file 'util/fonttool/fonttool.ipr'
--- util/fonttool/fonttool.ipr	1970-01-01 00:00:00 +0000
+++ util/fonttool/fonttool.ipr	2008-07-03 14:08:39 +0000
@@ -0,0 +1,306 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4" relativePaths="false">
+  <component name="AntConfiguration">
+    <defaultAnt bundledAnt="true" />
+  </component>
+  <component name="BuildJarProjectSettings">
+    <option name="BUILD_JARS_ON_MAKE" value="false" />
+  </component>
+  <component name="CodeStyleProjectProfileManger">
+    <option name="PROJECT_PROFILE" />
+    <option name="USE_PROJECT_LEVEL_SETTINGS" value="false" />
+  </component>
+  <component name="CodeStyleSettingsManager">
+    <option name="PER_PROJECT_SETTINGS">
+      <value>
+        <option name="ALIGN_MULTILINE_PARAMETERS_IN_CALLS" value="true" />
+        <option name="ALIGN_MULTILINE_BINARY_OPERATION" value="true" />
+        <option name="ALIGN_MULTILINE_ASSIGNMENT" value="true" />
+        <option name="ALIGN_MULTILINE_TERNARY_OPERATION" value="true" />
+        <option name="ALIGN_MULTILINE_THROWS_LIST" value="true" />
+        <option name="ALIGN_MULTILINE_EXTENDS_LIST" value="true" />
+        <option name="ALIGN_MULTILINE_PARENTHESIZED_EXPRESSION" value="true" />
+        <option name="ALIGN_MULTILINE_ARRAY_INITIALIZER_EXPRESSION" value="true" />
+        <option name="IMPORT_LAYOUT_TABLE">
+          <value>
+            <package name="java" withSubpackages="true" />
+            <package name="javax" withSubpackages="true" />
+            <package name="" withSubpackages="true" />
+          </value>
+        </option>
+        <option name="RIGHT_MARGIN" value="80" />
+      </value>
+    </option>
+    <option name="USE_PER_PROJECT_SETTINGS" value="true" />
+  </component>
+  <component name="CompilerConfiguration">
+    <option name="DEFAULT_COMPILER" value="Javac" />
+    <option name="DEPLOY_AFTER_MAKE" value="0" />
+    <resourceExtensions>
+      <entry name=".+\.(properties|xml|html|dtd|tld)" />
+      <entry name=".+\.(gif|png|jpeg|jpg)" />
+    </resourceExtensions>
+    <wildcardResourcePatterns>
+      <entry name="?*.properties" />
+      <entry name="?*.xml" />
+      <entry name="?*.gif" />
+      <entry name="?*.png" />
+      <entry name="?*.jpeg" />
+      <entry name="?*.jpg" />
+      <entry name="?*.html" />
+      <entry name="?*.dtd" />
+      <entry name="?*.tld" />
+      <entry name=".bzr" />
+    </wildcardResourcePatterns>
+  </component>
+  <component name="DataSourceManagerImpl" />
+  <component name="DependenciesAnalyzeManager">
+    <option name="myForwardDirection" value="false" />
+  </component>
+  <component name="DependencyValidationManager" />
+  <component name="EclipseCompilerSettings">
+    <option name="DEBUGGING_INFO" value="true" />
+    <option name="GENERATE_NO_WARNINGS" value="true" />
+    <option name="DEPRECATION" value="false" />
+    <option name="ADDITIONAL_OPTIONS_STRING" value="" />
+    <option name="MAXIMUM_HEAP_SIZE" value="128" />
+  </component>
+  <component name="EclipseEmbeddedCompilerSettings">
+    <option name="DEBUGGING_INFO" value="true" />
+    <option name="GENERATE_NO_WARNINGS" value="true" />
+    <option name="DEPRECATION" value="false" />
+    <option name="ADDITIONAL_OPTIONS_STRING" value="" />
+    <option name="MAXIMUM_HEAP_SIZE" value="128" />
+  </component>
+  <component name="EntryPointsManager">
+    <entry_points />
+  </component>
+  <component name="ExportToHTMLSettings">
+    <option name="PRINT_LINE_NUMBERS" value="false" />
+    <option name="OPEN_IN_BROWSER" value="false" />
+    <option name="OUTPUT_DIRECTORY" />
+  </component>
+  <component name="GUI Designer component loader factory" />
+  <component name="IdProvider" IDEtalkID="FEF368E009900571CCFB90CB4A0CEBC9" />
+  <component name="InspectionProjectProfileManager">
+    <option name="PROJECT_PROFILE" value="Project Default" />
+    <option name="USE_PROJECT_LEVEL_SETTINGS" value="false" />
+    <scopes />
+    <profiles>
+      <profile version="1.0" is_locked="false">
+        <option name="myName" value="Project Default" />
+        <option name="myLocal" value="false" />
+        <used_levels>
+          <error>
+            <option name="myName" value="ERROR" />
+            <option name="myVal" value="400" />
+          </error>
+          <warning>
+            <option name="myName" value="WARNING" />
+            <option name="myVal" value="300" />
+          </warning>
+          <information>
+            <option name="myName" value="INFO" />
+            <option name="myVal" value="200" />
+          </information>
+          <server>
+            <option name="myName" value="SERVER PROBLEM" />
+            <option name="myVal" value="100" />
+          </server>
+        </used_levels>
+      </profile>
+    </profiles>
+  </component>
+  <component name="JavacSettings">
+    <option name="DEBUGGING_INFO" value="true" />
+    <option name="GENERATE_NO_WARNINGS" value="false" />
+    <option name="DEPRECATION" value="true" />
+    <option name="ADDITIONAL_OPTIONS_STRING" value="" />
+    <option name="MAXIMUM_HEAP_SIZE" value="128" />
+  </component>
+  <component name="JavadocGenerationManager">
+    <option name="OUTPUT_DIRECTORY" />
+    <option name="OPTION_SCOPE" value="protected" />
+    <option name="OPTION_HIERARCHY" value="true" />
+    <option name="OPTION_NAVIGATOR" value="true" />
+    <option name="OPTION_INDEX" value="true" />
+    <option name="OPTION_SEPARATE_INDEX" value="true" />
+    <option name="OPTION_DOCUMENT_TAG_USE" value="false" />
+    <option name="OPTION_DOCUMENT_TAG_AUTHOR" value="false" />
+    <option name="OPTION_DOCUMENT_TAG_VERSION" value="false" />
+    <option name="OPTION_DOCUMENT_TAG_DEPRECATED" value="true" />
+    <option name="OPTION_DEPRECATED_LIST" value="true" />
+    <option name="OTHER_OPTIONS" value="" />
+    <option name="HEAP_SIZE" />
+    <option name="LOCALE" />
+    <option name="OPEN_IN_BROWSER" value="true" />
+  </component>
+  <component name="JikesSettings">
+    <option name="JIKES_PATH" value="" />
+    <option name="DEBUGGING_INFO" value="true" />
+    <option name="DEPRECATION" value="true" />
+    <option name="GENERATE_NO_WARNINGS" value="false" />
+    <option name="IS_EMACS_ERRORS_MODE" value="true" />
+    <option name="ADDITIONAL_OPTIONS_STRING" value="" />
+  </component>
+  <component name="LogConsolePreferences">
+    <option name="FILTER_ERRORS" value="false" />
+    <option name="FILTER_WARNINGS" value="false" />
+    <option name="FILTER_INFO" value="true" />
+    <option name="CUSTOM_FILTER" />
+  </component>
+  <component name="Palette2">
+    <group name="Swing">
+      <item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
+      </item>
+      <item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
+      </item>
+      <item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.png" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
+      </item>
+      <item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.png" removable="false" auto-create-binding="false" can-attach-label="true">
+        <default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
+      </item>
+      <item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.png" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
+        <initial-values>
+          <property name="text" value="Button" />
+        </initial-values>
+      </item>
+      <item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.png" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
+        <initial-values>
+          <property name="text" value="RadioButton" />
+        </initial-values>
+      </item>
+      <item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.png" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
+        <initial-values>
+          <property name="text" value="CheckBox" />
+        </initial-values>
+      </item>
+      <item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.png" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
+        <initial-values>
+          <property name="text" value="Label" />
+        </initial-values>
+      </item>
+      <item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.png" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
+          <preferred-size width="150" height="-1" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.png" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
+          <preferred-size width="150" height="-1" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.png" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
+          <preferred-size width="150" height="-1" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.png" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.png" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
+      </item>
+      <item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.png" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.png" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.png" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.png" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
+          <preferred-size width="200" height="200" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.png" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
+          <preferred-size width="200" height="200" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.png" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
+      </item>
+      <item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.png" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
+      </item>
+      <item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.png" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
+      </item>
+      <item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
+      </item>
+      <item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.png" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
+          <preferred-size width="-1" height="20" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.png" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
+      </item>
+      <item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
+      </item>
+    </group>
+  </component>
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/fonttool.iml" filepath="$PROJECT_DIR$/fonttool.iml" />
+    </modules>
+  </component>
+  <component name="ProjectRootManager" version="2" assert-keyword="true" jdk-15="true" project-jdk-name="1.6" project-jdk-type="JavaSDK">
+    <output url="file://$PROJECT_DIR$/build/classes" />
+  </component>
+  <component name="ProjectRunConfigurationManager" />
+  <component name="RmicSettings">
+    <option name="IS_EANABLED" value="false" />
+    <option name="DEBUGGING_INFO" value="true" />
+    <option name="GENERATE_NO_WARNINGS" value="false" />
+    <option name="GENERATE_IIOP_STUBS" value="false" />
+    <option name="ADDITIONAL_OPTIONS_STRING" value="" />
+  </component>
+  <component name="StarteamVcsAdapter" />
+  <component name="VssVcs" />
+  <component name="com.intellij.jsf.UserDefinedFacesConfigs">
+    <option name="USER_DEFINED_CONFIGS">
+      <value>
+        <list size="0" />
+      </value>
+    </option>
+  </component>
+  <component name="libraryTable" />
+  <component name="uidesigner-configuration">
+    <option name="INSTRUMENT_CLASSES" value="true" />
+    <option name="COPY_FORMS_RUNTIME_TO_OUTPUT" value="true" />
+    <option name="DEFAULT_LAYOUT_MANAGER" value="GridLayoutManager" />
+  </component>
+  <UsedPathMacros />
+</project>
+

=== added directory 'util/fonttool/src'
=== added directory 'util/fonttool/src/org'
=== added directory 'util/fonttool/src/org/gnu'
=== added directory 'util/fonttool/src/org/gnu/grub'
=== added directory 'util/fonttool/src/org/gnu/grub/fonttool'
=== added file 'util/fonttool/src/org/gnu/grub/fonttool/BDFFont.java'
--- util/fonttool/src/org/gnu/grub/fonttool/BDFFont.java	1970-01-01 00:00:00 +0000
+++ util/fonttool/src/org/gnu/grub/fonttool/BDFFont.java	2008-07-03 14:08:39 +0000
@@ -0,0 +1,24 @@
+package org.gnu.grub.fonttool;
+
+import java.awt.image.BufferedImage;
+import java.util.HashMap;
+import java.util.Map;
+
+public class BDFFont {
+    private Map<Character, BufferedImage> glyphs = new HashMap<Character, BufferedImage>();
+    private int height = 0;
+
+    void putGlyph(char ch, BufferedImage glyph) {
+        glyphs.put(ch, glyph);
+        if (glyph.getHeight() > height)
+            height = glyph.getHeight();
+    }
+
+    public BufferedImage getGlyph(char c) {
+        return glyphs.get(c);
+    }
+
+    public int getHeight() {
+        return height;
+    }
+}

=== added file 'util/fonttool/src/org/gnu/grub/fonttool/BDFLoader.java'
--- util/fonttool/src/org/gnu/grub/fonttool/BDFLoader.java	1970-01-01 00:00:00 +0000
+++ util/fonttool/src/org/gnu/grub/fonttool/BDFLoader.java	2008-07-03 14:10:25 +0000
@@ -0,0 +1,254 @@
+package org.gnu.grub.fonttool;
+
+import java.io.*;
+import java.util.StringTokenizer;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class BDFLoader {
+    private final BufferedReader in;
+    private final Font font;
+    private int maxCharWidth;
+    private int maxCharHeight;
+
+    BDFLoader(BufferedReader in) {
+        this.in = in;
+        this.font = new Font();
+        this.maxCharWidth = 0;
+        this.maxCharHeight = 0;
+    }
+
+    public static boolean isBDFFile(String filename) {
+        DataInputStream in = null;
+        try {
+            in = new DataInputStream(new FileInputStream(filename));
+            final String signature = "STARTFONT ";
+            byte[] b = new byte[signature.length()];
+            in.readFully(b);
+            in.close();
+
+            String s = new String(b, "US-ASCII");
+            return signature.equals(s);
+        } catch (IOException e) {
+            if (in != null) {
+                try {
+                    in.close();
+                } catch (IOException e1) {
+                    // Ignore.
+                }
+            }
+            return false;
+        }
+    }
+
+    public static Font loadFontResource(String resourceName) throws IOException {
+        InputStream in = BDFLoader.class.getClassLoader().getResourceAsStream(resourceName);
+        if (in == null)
+            throw new FileNotFoundException("Font resource " + resourceName + " not found");
+        return loadFontFromStream(in);
+    }
+
+    public static Font loadFontFile(String filename) throws IOException {
+        InputStream in = new FileInputStream(filename);
+        return loadFontFromStream(in);
+    }
+
+    private static Font loadFontFromStream(InputStream inStream) throws IOException {
+        BufferedReader in;
+        try {
+            in = new BufferedReader(new InputStreamReader(inStream, "ISO-8859-1"));
+        } catch (UnsupportedEncodingException e) {
+            throw new RuntimeException("Encoding not supported: " + e.getMessage(), e);
+        }
+        return new BDFLoader(in).loadFont();
+    }
+
+    private Font loadFont() throws IOException {
+        loadFontInfo();
+        while (loadChar()) {
+            /* Loop. */
+        }
+
+        font.setMaxCharWidth(maxCharWidth);
+        font.setMaxCharHeight(maxCharHeight);
+        return font;
+    }
+
+    private void loadFontInfo() throws IOException {
+        final Pattern stringSettingPattern = Pattern.compile("^(\\w+)\\s+\"([^\"]+)\"$");
+        String line;
+        // Load the global font information that appears before CHARS.
+        final int UNSET = Integer.MIN_VALUE;
+        font.setAscent(UNSET);
+        font.setDescent(UNSET);
+        font.setFamily("Unknown");
+        font.setBold(false);
+        font.setItalic(false);
+        font.setPointSize(Font.UNKNOWN_POINT_SIZE);
+        do {
+            line = in.readLine();
+            if (line == null)
+                throw new IOException("BDF format error: end of file while " +
+                                      "reading global font information");
+
+            StringTokenizer st = new StringTokenizer(line);
+            if (st.hasMoreTokens()) {
+                String name = st.nextToken();
+                if (name.equals("FONT_ASCENT")) {
+                    if (!st.hasMoreTokens())
+                        throw new IOException("BDF format error: " +
+                                              "no tokens after " + name);
+                    font.setAscent(Integer.parseInt(st.nextToken()));
+                } else if (name.equals("FONT_DESCENT")) {
+                    if (!st.hasMoreTokens())
+                        throw new IOException("BDF format error: " +
+                                              "no tokens after " + name);
+                    font.setDescent(Integer.parseInt(st.nextToken()));
+                } else if (name.equals("POINT_SIZE")) {
+                    if (!st.hasMoreTokens())
+                        throw new IOException("BDF format error: " +
+                                              "no tokens after " + name);
+                    // Divide by 10, since it is stored X10.
+                    font.setPointSize(Integer.parseInt(st.nextToken()) / 10);
+                } else if (name.equals("FAMILY_NAME")) {
+                    Matcher matcher = stringSettingPattern.matcher(line);
+                    if (!matcher.matches())
+                        throw new IOException("BDF format error: " +
+                                              "line doesn't match string " +
+                                              "setting pattern: " + line);
+                    font.setFamily(matcher.group(2));
+                } else if (name.equals("WEIGHT_NAME")) {
+                    Matcher matcher = stringSettingPattern.matcher(line);
+                    if (!matcher.matches())
+                        throw new IOException("BDF format error: " +
+                                              "line doesn't match string " +
+                                              "setting pattern: " + line);
+                    String weightName = matcher.group(2);
+                    font.setBold("bold".equalsIgnoreCase(weightName));
+                } else if (name.equals("SLANT")) {
+                    Matcher matcher = stringSettingPattern.matcher(line);
+                    if (!matcher.matches())
+                        throw new IOException("BDF format error: " +
+                                              "line doesn't match string " +
+                                              "setting pattern: " + line);
+                    String slantType = matcher.group(2);
+                    font.setItalic(!"R".equalsIgnoreCase(slantType));
+                } else if (name.equals("CHARS")) {
+                    // This is the end of the global font information and
+                    // the beginning of the character definitions.
+                    break;
+                } else {
+                    // Skip other fields.
+                }
+            }
+        } while (true);
+
+        if (font.getAscent() == UNSET)
+            throw new IOException("BDF format error: no FONT_ASCENT property");
+        if (font.getDescent() == UNSET)
+            throw new IOException("BDF format error: no FONT_DESCENT property");
+    }
+
+    private boolean loadChar() throws IOException {
+        String line;
+        // Find start of character
+        do {
+            line = in.readLine();
+            if (line == null)
+                return false;
+            StringTokenizer st = new StringTokenizer(line);
+            if (st.hasMoreTokens() && st.nextToken().equals("STARTCHAR")) {
+                if (!st.hasMoreTokens())
+                    throw new IOException("BDF format error: no character name after STARTCHAR");
+                break;
+            }
+        } while (true);
+
+        // Find properties
+        final int UNSET = Integer.MIN_VALUE;
+        int codePoint = UNSET;
+        int bbx = UNSET;
+        int bby = UNSET;
+        int bbox = UNSET;
+        int bboy = UNSET;
+        int dwidth = UNSET;
+        do {
+            line = in.readLine();
+            if (line == null)
+                return false;
+            StringTokenizer st = new StringTokenizer(line);
+            if (st.hasMoreTokens()) {
+                String field = st.nextToken();
+                if (field.equals("ENCODING")) {
+                    if (!st.hasMoreTokens())
+                        throw new IOException("BDF format error: no encoding # after ENCODING");
+                    String codePointStr = st.nextToken();
+                    codePoint = Integer.parseInt(codePointStr);
+                } else if (field.equals("BBX")) {
+                    if (!st.hasMoreTokens())
+                        throw new IOException("BDF format error: no tokens after BBX");
+                    bbx = Integer.parseInt(st.nextToken());
+                    bby = Integer.parseInt(st.nextToken());
+                    bbox = Integer.parseInt(st.nextToken());
+                    bboy = Integer.parseInt(st.nextToken());
+                } else if (field.equals("DWIDTH")) {
+                    if (!st.hasMoreTokens())
+                        throw new IOException("BDF format error: no tokens after DWIDTH");
+                    dwidth = Integer.parseInt(st.nextToken());
+                    int dwidthY = Integer.parseInt(st.nextToken());
+                    // The DWIDTH Y value should be zero for any normal font.
+                    if (dwidthY != 0) {
+                        throw new IOException("BDF format error: dwidth Y value" +
+                                              "is nonzero (" + dwidthY + ") " +
+                                              "for char " + codePoint + ".");
+                    }
+                } else if (field.equals("BITMAP")) {
+                    break; // now read the bitmap
+                }
+            }
+        } while (true);
+
+        if (codePoint == UNSET)
+            throw new IOException("BDF format error: " +
+                                  "no code point set");
+        if (bbx == UNSET || bby == UNSET)
+            throw new IOException("BDF format error: " +
+                                  "bbx/bby missing: " + bbx + ", " + bby +
+                                  " for char " + codePoint);
+
+        if (bbox == UNSET || bboy == UNSET)
+            throw new IOException("BDF format error: " +
+                                  "bbox/bboy missing: " + bbox + ", " + bboy +
+                                  " for char " + codePoint);
+
+        if (dwidth == UNSET)
+            throw new IOException("BDF format error: " +
+                                  "dwidth missing for char " + codePoint);
+
+        final int glyphWidth = bbx;
+        final int glyphHeight = bby;
+        if (glyphWidth > maxCharWidth)
+            maxCharWidth = glyphWidth;
+        if (glyphHeight > maxCharHeight)
+            maxCharHeight = glyphHeight;
+
+        // Read the bitmap
+        Glyph glyph = new Glyph(codePoint, glyphWidth, glyphHeight, bbox, bboy, dwidth);
+        for (int y = 0; y < glyphHeight; y++) {
+            line = in.readLine();
+            if (line == null)
+                return false;
+            for (int b = 0; b < line.length(); b++) {
+                int v = Integer.parseInt(Character.toString(line.charAt(b)), 16);
+                for (int x = b * 4, i = 0; i < 4 && x < glyphWidth; x++, i++) {
+                    boolean set = (v & 0x8) != 0;
+                    v <<= 1;
+                    glyph.setPixel(x, y, set);
+                }
+            }
+        }
+
+        font.putGlyph(codePoint, glyph);
+        return true;
+    }
+}

=== added file 'util/fonttool/src/org/gnu/grub/fonttool/CharDefs.java'
--- util/fonttool/src/org/gnu/grub/fonttool/CharDefs.java	1970-01-01 00:00:00 +0000
+++ util/fonttool/src/org/gnu/grub/fonttool/CharDefs.java	2008-07-03 14:10:25 +0000
@@ -0,0 +1,135 @@
+package org.gnu.grub.fonttool;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.TreeMap;
+
+class CharDefs {
+    private boolean debug = "1".equals(System.getProperty("fonttool.debug"));
+    private TreeMap<Integer, Glyph> glyphs;
+    private ByteArrayOutputStream charDefsData;
+    private int maxCharWidth;
+    private int maxCharHeight;
+    private HashMap<Integer, CharStorageInfo> charIndex;
+
+    public CharDefs(Font font) {
+        this.glyphs = font.getGlyphs();
+        this.charIndex = null;
+        this.charDefsData = null;
+
+        calculateMaxSizes();
+    }
+
+    private void calculateMaxSizes() {
+        maxCharWidth = 0;
+        maxCharHeight = 0;
+        for (Glyph glyph : glyphs.values()) {
+            final int w = glyph.getWidth();
+            final int h = glyph.getHeight();
+            if (w > maxCharWidth)
+                maxCharWidth = w;
+            if (h > maxCharHeight)
+                maxCharHeight = h;
+        }
+    }
+
+    void buildDefinitions() {
+        charIndex = new HashMap<Integer, CharStorageInfo>();
+        HashMap<CharDef, Long> charDefIndex = new HashMap<CharDef, Long>();
+        charDefsData = new ByteArrayOutputStream();
+        DataOutputStream charDefs = new DataOutputStream(charDefsData);
+        try {
+            // Loop through all the glyphs, writing the glyph data to the
+            // in-memory byte stream, collapsing duplicate glyphs, and
+            // constructing index information.
+            for (Glyph glyph : glyphs.values()) {
+                CharDef charDef = new CharDef(glyph.getWidth(),
+                                              glyph.getHeight(),
+                                              glyph.getBitmap());
+
+                if (charDefIndex.containsKey(charDef)) {
+                    // Use already-written glyph.
+                    if (debug)
+                        System.out.printf("Duplicate glyph for character U+%04X%n",
+                                          glyph.getCodePoint());
+                    final int charOffset = charDefIndex.get(charDef).intValue();
+                    final CharStorageInfo info =
+                            new CharStorageInfo(glyph.getCodePoint(), charOffset);
+                    charIndex.put(glyph.getCodePoint(), info);
+                } else {
+                    // Write glyph data.
+                    final int charOffset = charDefs.size();
+                    final CharStorageInfo info =
+                            new CharStorageInfo(glyph.getCodePoint(), charOffset);
+                    charIndex.put(glyph.getCodePoint(), info);
+
+                    charDefIndex.put(charDef, (long) charOffset);
+
+                    charDefs.writeShort(glyph.getWidth());
+                    charDefs.writeShort(glyph.getHeight());
+                    charDefs.writeShort(glyph.getBbox());
+                    charDefs.writeShort(glyph.getBboy());
+                    charDefs.writeShort(glyph.getDeviceWidth());
+                    charDefs.write(glyph.getBitmap());
+                }
+            }
+        } catch (IOException e) {
+            throw new RuntimeException("Error writing to in-memory byte stream", e);
+        }
+    }
+
+    public int getMaxCharWidth() {
+        return maxCharWidth;
+    }
+
+    public int getMaxCharHeight() {
+        return maxCharHeight;
+    }
+
+    public HashMap<Integer, CharStorageInfo> getCharIndex() {
+        if (charIndex == null) throw new IllegalStateException();
+        return charIndex;
+    }
+
+    public byte[] getDefinitionData() {
+        if (charDefsData == null) throw new IllegalStateException();
+        return charDefsData.toByteArray();
+    }
+
+
+    private static class CharDef {
+        private final int width;
+        private final int height;
+        private final byte[] data;
+
+        public CharDef(int width, int height, byte[] data) {
+            this.width = width;
+            this.height = height;
+            this.data = data;
+        }
+
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            CharDef charDef = (CharDef) o;
+
+            if (height != charDef.height) return false;
+            if (width != charDef.width) return false;
+            if (!Arrays.equals(data, charDef.data)) return false;
+
+            return true;
+        }
+
+        public int hashCode() {
+            int result;
+            result = width;
+            result = 31 * result + height;
+            result = 31 * result + Arrays.hashCode(data);
+            return result;
+        }
+    }
+}

=== added file 'util/fonttool/src/org/gnu/grub/fonttool/CharMatrix.java'
--- util/fonttool/src/org/gnu/grub/fonttool/CharMatrix.java	1970-01-01 00:00:00 +0000
+++ util/fonttool/src/org/gnu/grub/fonttool/CharMatrix.java	2008-07-03 14:10:25 +0000
@@ -0,0 +1,139 @@
+package org.gnu.grub.fonttool;
+
+import java.awt.*;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseMotionAdapter;
+import java.util.HashMap;
+import java.util.Map;
+import javax.swing.*;
+
+public class CharMatrix extends JComponent {
+    private Font charFont;
+    private int zoom = 1;
+    private int startingChar;
+    private Map<Rectangle, Integer> glyphLocations = new HashMap<Rectangle, Integer>();
+    private GlyphSelectionListener glyphSelectionListener;
+    private Point mouseLocation;
+
+    public interface GlyphSelectionListener {
+        void glyphSelected(int codePoint);
+    }
+
+    public CharMatrix() {
+        addMouseListener(new MouseAdapter() {
+            public void mousePressed(MouseEvent e) {
+                int codePoint = getCodePointForGlyphAt(e.getPoint());
+                if (codePoint != -1) {
+                    if (glyphSelectionListener != null)
+                        glyphSelectionListener.glyphSelected(codePoint);
+                }
+            }
+
+            public void mouseExited(MouseEvent e) {
+                mouseLocation = null;
+                repaint();
+            }
+        });
+
+        addMouseMotionListener(new MouseMotionAdapter() {
+            public void mouseMoved(MouseEvent e) {
+                mouseLocation = e.getPoint();
+                repaint();
+            }
+        });
+    }
+
+    public void setGlyphSelectionListener(GlyphSelectionListener listener) {
+        this.glyphSelectionListener = listener;
+    }
+
+    public Font getCharFont() {
+        return charFont;
+    }
+
+    public void setCharFont(Font font) {
+        this.charFont = font;
+        repaint();
+    }
+
+    public int getZoom() {
+        return zoom;
+    }
+
+    public void setZoom(int zoom) {
+        this.zoom = zoom;
+        repaint();
+    }
+
+    public int getStartingChar() {
+        return startingChar;
+    }
+
+    public void setStartingChar(int startingChar) {
+        this.startingChar = startingChar;
+        repaint();
+    }
+
+    public int getCodePointForGlyphAt(Point p) {
+        for (Rectangle rectangle : glyphLocations.keySet()) {
+            if (rectangle.contains(p))
+                return glyphLocations.get(rectangle);
+        }
+        return -1;
+    }
+
+    protected void paintComponent(Graphics g) {
+        glyphLocations.clear();
+
+        if (charFont == null)
+            return;
+
+        int w = getWidth();
+        int h = getHeight();
+        int maxCharWidth = charFont.getMaxCharWidth();
+        int maxCharHeight = charFont.getMaxCharHeight();
+        int space = 2;
+        int charsPerRow = w / (maxCharWidth + space);
+        int rows = h / (maxCharHeight + space);
+
+        Rectangle highlightBounds = null;
+        int codePoint = startingChar;
+        int y = 3 + charFont.getAscent();   // Point y to the baseline.
+        for (int row = 0; row < rows; row++) {
+            int x = 3;
+            for (int col = 0; col < charsPerRow; col++) {
+                Glyph glyph = charFont.getGlyph(codePoint);
+
+                if (glyph != null) {
+                    Rectangle charBounds =
+                            CharView.drawGlyph(g, charFont, glyph, x, y, zoom, getForeground());
+                    // Expand the bounding box for a bigger clickable area.
+                    final int boundsPad = 1;
+                    charBounds.x -= boundsPad;
+                    charBounds.y -= boundsPad;
+                    charBounds.width += 2 * boundsPad;
+                    charBounds.height += 2 * boundsPad;
+                    glyphLocations.put(charBounds, codePoint);
+                    if (mouseLocation != null && charBounds.contains(mouseLocation)) {
+                        highlightBounds = charBounds;
+                    }
+                }
+                x += maxCharWidth + space;
+                codePoint++;
+            }
+
+            y += maxCharHeight + space;
+        }
+
+        if (highlightBounds != null) {
+            g.setColor(Color.blue);
+            Graphics2D g2 = (Graphics2D) g;
+            g2.setStroke(new BasicStroke(3f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 2f));
+            g2.draw(new Rectangle(highlightBounds.x - 2, highlightBounds.y - 2,
+                                  highlightBounds.width + 3, highlightBounds.height + 3));
+            g2.setStroke(new BasicStroke(1f));
+        }
+
+    }
+}

=== added file 'util/fonttool/src/org/gnu/grub/fonttool/CharStorageInfo.java'
--- util/fonttool/src/org/gnu/grub/fonttool/CharStorageInfo.java	1970-01-01 00:00:00 +0000
+++ util/fonttool/src/org/gnu/grub/fonttool/CharStorageInfo.java	2008-07-03 14:08:39 +0000
@@ -0,0 +1,19 @@
+package org.gnu.grub.fonttool;
+
+class CharStorageInfo {
+    private final int codePoint;
+    private final int fileOffset;
+
+    public CharStorageInfo(int codePoint, int fileOffset) {
+        this.codePoint = codePoint;
+        this.fileOffset = fileOffset;
+    }
+
+    public int getCodePoint() {
+        return codePoint;
+    }
+
+    public int getFileOffset() {
+        return fileOffset;
+    }
+}

=== added file 'util/fonttool/src/org/gnu/grub/fonttool/CharView.java'
--- util/fonttool/src/org/gnu/grub/fonttool/CharView.java	1970-01-01 00:00:00 +0000
+++ util/fonttool/src/org/gnu/grub/fonttool/CharView.java	2008-07-03 14:10:25 +0000
@@ -0,0 +1,101 @@
+package org.gnu.grub.fonttool;
+
+import java.awt.*;
+import java.util.Formatter;
+import javax.swing.*;
+
+public class CharView extends JComponent {
+    private Font charFont = null;
+    private Glyph glyph = null;
+    private int zoom = 1;
+
+    public Font getCharFont() {
+        return charFont;
+    }
+
+    public void setCharFont(Font charFont) {
+        this.charFont = charFont;
+    }
+
+    public Glyph getGlyph() {
+        return glyph;
+    }
+
+    public void setGlyph(Glyph glyph) {
+        this.glyph = glyph;
+        repaint();
+    }
+
+    public void setZoom(int zoom) {
+        this.zoom = zoom;
+    }
+
+    public Dimension getPreferredSize() {
+        if (glyph == null)
+            return new Dimension(1, 1);
+        return new Dimension(glyph.getWidth() * zoom, glyph.getHeight() * zoom);
+    }
+
+    protected void paintComponent(Graphics g) {
+        if (glyph == null) {
+            // Draw a gray 'X' indicating no character selected.
+            g.setColor(Color.gray);
+            g.drawLine(0, 0, getWidth() - 1, getHeight() - 1);
+            g.drawLine(getWidth(), 0, 0, getHeight() - 1);
+            return;
+        }
+
+        final int x0 = (getWidth() - glyph.getWidth() * zoom) / 2;
+        final int y0 = getHeight() / 2;
+        drawGlyph(g, charFont, glyph, x0, y0, zoom, getForeground());
+
+        g.setFont(new java.awt.Font("SansSerif", java.awt.Font.PLAIN, 12));
+        FontMetrics fm = g.getFontMetrics();
+        Formatter f = new Formatter();
+        f.format("U+%04X", glyph.getCodePoint());
+        String str = f.toString();
+        g.drawString(str,
+                     (getWidth() - fm.stringWidth(str)) / 2,
+                     getHeight() - fm.getHeight());
+    }
+
+    /**
+     * Draw the specified glyph with its baseline at y0 and the horizontal
+     * start at x0.
+     */
+    static Rectangle drawGlyph(Graphics g, Font font, Glyph glyph, int startX, int baselineY, int zoom, Color fg) {
+        // Adjust (x0, y0) so that it puts the bitmap at the right location.
+        final int offsetX = zoom * glyph.getBbox();
+        final int offsetY = zoom * glyph.getBboy();
+        final int bitmapLeft = startX + offsetX;
+        final int bitmapBottom = baselineY - offsetY;
+        final int bitmapTop = bitmapBottom - zoom * glyph.getHeight();
+
+        // Draw a gray box around the glyph bounds.
+        g.setColor(Color.lightGray);
+        g.drawRect(bitmapLeft - 1, bitmapTop - 1,
+                   glyph.getWidth() * zoom + 1, zoom * glyph.getHeight() + 1);
+
+        // Draw the baseline
+        g.setColor(new Color(110, 110, 255));
+        g.drawLine(startX, baselineY, startX + zoom * glyph.getDeviceWidth(), baselineY);
+
+        // Draw the glyph.
+        g.setColor(fg);
+        byte[] bitmap = glyph.getBitmap();
+        int bitIndex = 0;
+        for (int y = 0; y < glyph.getHeight(); y++) {
+            for (int x = 0; x < glyph.getWidth(); x++, bitIndex++) {
+                int byteIndex = bitIndex / 8;
+                int bitShift = bitIndex % 8;
+                byte b = bitmap[byteIndex];
+                boolean bitValue = (b & (0x80 >>> bitShift)) != 0;
+                if (bitValue) {
+                    g.fillRect(bitmapLeft + x * zoom, bitmapTop + y * zoom, zoom, zoom);
+                }
+            }
+        }
+
+        return new Rectangle(bitmapLeft, bitmapTop, zoom * glyph.getWidth(), zoom * glyph.getHeight());
+    }
+}

=== added file 'util/fonttool/src/org/gnu/grub/fonttool/Converter.java'
--- util/fonttool/src/org/gnu/grub/fonttool/Converter.java	1970-01-01 00:00:00 +0000
+++ util/fonttool/src/org/gnu/grub/fonttool/Converter.java	2008-07-03 14:08:39 +0000
@@ -0,0 +1,78 @@
+package org.gnu.grub.fonttool;
+
+import java.io.IOException;
+
+/**
+ * Program to convert BDF fonts into PFF2 fonts for use with GRUB.
+ */
+public class Converter {
+    public static void main(String[] args) {
+        if (args.length < 1) {
+            printUsageAndExit();
+        }
+
+        String in = null;
+        String out = null;
+
+        try {
+            for (String arg : args) {
+                if (arg.startsWith("--")) {
+                    String option;
+                    String value;
+                    int equalsPos = arg.indexOf('=');
+                    if (equalsPos < 0) {
+                        option = arg.substring(2);
+                        value = null;
+                    } else {
+                        option = arg.substring(2, equalsPos);
+                        value = arg.substring(equalsPos + 1);
+                    }
+
+                    if ("in".equals(option)) {
+                        if (value == null)
+                            throw new CommandLineException(option + " option requires a value.");
+                        in = value;
+                    } else if ("out".equals(option)) {
+                        if (value == null)
+                            throw new CommandLineException(option + " option requires a value.");
+                        out = value;
+                    }
+                } else {
+                    throw new CommandLineException("Non-option argument `" + arg + "'.");
+                }
+            }
+            if (in == null || out == null) {
+                throw new CommandLineException("Both --in=X and --out=Y must be specified.");
+            }
+        } catch (CommandLineException e) {
+            System.err.println("Error: " + e.getMessage());
+            System.exit(1);
+        }
+
+        try {
+            // Read BDF.
+            Font font = BDFLoader.loadFontFile(in);
+
+            // Write PFF2.
+            new PFF2Writer(out).writeFont(font);
+        } catch (IOException e) {
+            System.err.println("I/O error converting font: " + e);
+            e.printStackTrace();
+            System.exit(1);
+        }
+    }
+
+    private static class CommandLineException extends Exception {
+        public CommandLineException(String message) {
+            super(message);
+        }
+    }
+
+    private static void printUsageAndExit() {
+        System.err.println("GNU GRUB Font Conversion Tool");
+        System.err.println();
+        System.err.println("Usage: Converter --in=IN.bdf --out=OUT.pf2");
+        System.err.println();
+        System.exit(1);
+    }
+}

=== added file 'util/fonttool/src/org/gnu/grub/fonttool/FileFormatException.java'
--- util/fonttool/src/org/gnu/grub/fonttool/FileFormatException.java	1970-01-01 00:00:00 +0000
+++ util/fonttool/src/org/gnu/grub/fonttool/FileFormatException.java	2008-07-03 14:08:39 +0000
@@ -0,0 +1,9 @@
+package org.gnu.grub.fonttool;
+
+import java.io.IOException;
+
+public class FileFormatException extends IOException {
+    public FileFormatException(String msg) {
+        super(msg);
+    }
+}

=== added file 'util/fonttool/src/org/gnu/grub/fonttool/Font.java'
--- util/fonttool/src/org/gnu/grub/fonttool/Font.java	1970-01-01 00:00:00 +0000
+++ util/fonttool/src/org/gnu/grub/fonttool/Font.java	2008-07-03 14:10:25 +0000
@@ -0,0 +1,106 @@
+package org.gnu.grub.fonttool;
+
+import java.util.TreeMap;
+
+public class Font {
+    public static final int UNKNOWN_POINT_SIZE = -1;
+
+    private TreeMap<Integer, Glyph> glyphs;
+    private String family;
+    private boolean bold;
+    private boolean italic;
+    private int pointSize;
+    private int maxCharWidth;
+    private int maxCharHeight;
+    private int ascent;
+    private int descent;
+
+    public Font() {
+        glyphs = new TreeMap<Integer, Glyph>();
+    }
+
+    public String getFamily() {
+        return family;
+    }
+
+    public void setFamily(String family) {
+        this.family = family;
+    }
+
+    public boolean isBold() {
+        return bold;
+    }
+
+    public void setBold(boolean bold) {
+        this.bold = bold;
+    }
+
+    public boolean isItalic() {
+        return italic;
+    }
+
+    public void setItalic(boolean italic) {
+        this.italic = italic;
+    }
+
+    public int getPointSize() {
+        return pointSize;
+    }
+
+    public void setPointSize(int pointSize) {
+        this.pointSize = pointSize;
+    }
+
+    public int getMaxCharWidth() {
+        return maxCharWidth;
+    }
+
+    public void setMaxCharWidth(int maxCharWidth) {
+        this.maxCharWidth = maxCharWidth;
+    }
+
+    public int getMaxCharHeight() {
+        return maxCharHeight;
+    }
+
+    public void setMaxCharHeight(int maxCharHeight) {
+        this.maxCharHeight = maxCharHeight;
+    }
+
+    public int getAscent() {
+        return ascent;
+    }
+
+    public void setAscent(int ascent) {
+        this.ascent = ascent;
+    }
+
+    public int getDescent() {
+        return descent;
+    }
+
+    public void setDescent(int descent) {
+        this.descent = descent;
+    }
+
+    public void putGlyph(int codePoint, Glyph glyph) {
+        glyphs.put(codePoint, glyph);
+    }
+
+    public TreeMap<Integer, Glyph> getGlyphs() {
+        return glyphs;
+    }
+
+    public Glyph getGlyph(int codePoint) {
+        return glyphs.get(codePoint);
+    }
+
+    public String getStandardName() {
+        StringBuilder name = new StringBuilder(getFamily());
+        if (isBold()) name.append(" Bold");
+        if (isItalic()) name.append(" Italic");
+        name.append(' ');
+        name.append(getPointSize());
+        return name.toString();
+    }
+}

=== added file 'util/fonttool/src/org/gnu/grub/fonttool/Glyph.java'
--- util/fonttool/src/org/gnu/grub/fonttool/Glyph.java	1970-01-01 00:00:00 +0000
+++ util/fonttool/src/org/gnu/grub/fonttool/Glyph.java	2008-07-03 14:10:25 +0000
@@ -0,0 +1,83 @@
+package org.gnu.grub.fonttool;
+
+public class Glyph {
+    private final int codePoint;
+    private final int width;
+    private final int height;
+
+    // These define the amounts to shift the character bitmap by
+    // before drawing it.  See
+    //    http://www.adobe.com/devnet/font/pdfs/5005.BDF_Spec.pdf
+    // and
+    //    http://www.linuxbabble.com/documentation/x/bdf/
+    // for explanatory figures.
+    private final int bbox;
+    private final int bboy;
+
+    // Number of pixels to advance horizontally from this character's origin
+    // to the origin of the next character.
+    private final int deviceWidth;
+
+    // Row-major order, no padding.  Rows can break within a byte.
+    // MSb is first (leftmost/uppermost) pixel.
+    private final byte[] bitmap;
+
+    public Glyph(int codePoint, int width, int height, int bbox, int bboy, int deviceWidth) {
+        this(codePoint, width, height, bbox, bboy, deviceWidth,
+             new byte[(width * height + 7) / 8]);
+    }
+
+    public Glyph(int codePoint, int width, int height,
+                 int bbox, int bboy, int deviceWidth,
+                 byte[] bitmap) {
+        this.codePoint = codePoint;
+        this.width = width;
+        this.height = height;
+        this.bboy = bboy;
+        this.bbox = bbox;
+        this.deviceWidth = deviceWidth;
+        this.bitmap = bitmap;
+    }
+
+    public void setPixel(int x, int y, boolean value) {
+        if (x < 0 || y < 0 || x >= width || y >= height)
+            throw new IllegalArgumentException(
+                    "Invalid pixel location (" + x + ", " + y + ") for "
+                    + width + "x" + height + " glyph");
+
+        int bitIndex = y * width + x;
+        int byteIndex = bitIndex / 8;
+        int bitPos = bitIndex % 8;
+        int v = value ? 0x80 >>> bitPos : 0;
+        int mask = ~(0x80 >>> bitPos);
+        bitmap[byteIndex] = (byte) ((bitmap[byteIndex] & mask) | v);
+    }
+
+    public int getCodePoint() {
+        return codePoint;
+    }
+
+    public int getWidth() {
+        return width;
+    }
+
+    public int getHeight() {
+        return height;
+    }
+
+    public int getBbox() {
+        return bbox;
+    }
+
+    public int getBboy() {
+        return bboy;
+    }
+
+    public int getDeviceWidth() {
+        return deviceWidth;
+    }
+
+    public byte[] getBitmap() {
+        return bitmap;
+    }
+}

=== added file 'util/fonttool/src/org/gnu/grub/fonttool/PFF2Loader.java'
--- util/fonttool/src/org/gnu/grub/fonttool/PFF2Loader.java	1970-01-01 00:00:00 +0000
+++ util/fonttool/src/org/gnu/grub/fonttool/PFF2Loader.java	2008-07-03 14:10:25 +0000
@@ -0,0 +1,203 @@
+package org.gnu.grub.fonttool;
+
+import java.io.EOFException;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.charset.Charset;
+
+/**
+ * Load a PFF2 font completely into memory as an instance of Font.
+ */
+public class PFF2Loader {
+    private static final int debug
+            = Integer.parseInt(System.getProperty("fonttool.debug", "0"));
+
+    private RandomAccessFile f;
+    private Font font;
+
+    private PFF2Loader(String filename) throws FileNotFoundException {
+        f = new RandomAccessFile(filename, "r");
+        font = new Font();
+    }
+
+    public static boolean isPFF2File(String filename) {
+        RandomAccessFile f = null;
+        boolean isFont = false;
+        try {
+            f = new RandomAccessFile(filename, "r");
+            byte[] sectionNameBytes = new byte[4];
+            f.readFully(sectionNameBytes);
+            String sectionName = new String(sectionNameBytes, "US-ASCII");
+            if (sectionName.equals(PFF2Sections.FILE)) {
+                final String expectedType = "PFF2";
+                int len = f.readInt();
+                if (len == expectedType.length()) {
+                    byte[] typeBytes = new byte[len];
+                    f.readFully(typeBytes);
+                    String type = new String(typeBytes, "US-ASCII");
+                    if (type.equals(expectedType)) {
+                        isFont = true;
+                    }
+                }
+            }
+            f.close();
+        } catch (IOException e) {
+            if (f != null) {
+                try {
+                    f.close();
+                } catch (IOException e1) {
+                    // Ignore.
+                }
+            }
+        }
+
+        return isFont;
+    }
+
+    public static Font loadFontFile(String filename) throws IOException {
+        PFF2Loader loader = new PFF2Loader(filename);
+        loader.loadFont();
+        return loader.getFont();
+    }
+
+    private void loadFont() throws IOException {
+        String s;
+        s = openSection();
+        if (!PFF2Sections.FILE.equals(s))
+            throw new FileFormatException(
+                    "First PFF2 section must be FILE but is `" + s + "'.");
+
+        s = readCompleteSectionAsString();
+        if (!"PFF2".equals(s))
+            throw new FileFormatException("Unsupported file type `" + s + "'.");
+
+        font.setFamily("Unnamed");
+        font.setPointSize(Font.UNKNOWN_POINT_SIZE);
+        try {
+            if (debug >= 1) System.out.println("Loading font information...");
+            boolean doneLoadingInfo = false;
+            while (!doneLoadingInfo) {
+                s = openSection();
+                int sectionLength = f.readInt();
+                if (debug >= 2)
+                    System.out.println("Section: " + s + " length: " + sectionLength);
+                if (s.equals(PFF2Sections.MAX_CHAR_WIDTH)) {
+                    int maxCharWidth = readSectionContentsAsUShort(sectionLength);
+                    font.setMaxCharWidth(maxCharWidth);
+                } else if (s.equals(PFF2Sections.MAX_CHAR_HEIGHT)) {
+                    int maxCharHeight = readSectionContentsAsUShort(sectionLength);
+                    font.setMaxCharHeight(maxCharHeight);
+                } else if (s.equals(PFF2Sections.FONT_POINT_SIZE)) {
+                    int pointSize = readSectionContentsAsUShort(sectionLength);
+                    font.setPointSize(pointSize);
+                } else if (s.equals(PFF2Sections.FONT_ASCENT)) {
+                    int ascent = readSectionContentsAsUShort(sectionLength);
+                    font.setAscent(ascent);
+                } else if (s.equals(PFF2Sections.FONT_DESCENT)) {
+                    int descent = readSectionContentsAsUShort(sectionLength);
+                    font.setDescent(descent);
+                } else if (s.equals(PFF2Sections.FONT_NAME)) {
+                    String fontName = readSectionContentsAsString(sectionLength);
+                    if (debug >= 1)
+                        System.out.println("Loaded font: " + fontName);
+                } else if (s.equals(PFF2Sections.FONT_FAMILY)) {
+                    font.setFamily(readSectionContentsAsString(sectionLength));
+                } else if (s.equals(PFF2Sections.FONT_WEIGHT)) {
+                    String weightString = readSectionContentsAsString(sectionLength);
+                    font.setBold(weightString.equalsIgnoreCase("bold"));
+                } else if (s.equals(PFF2Sections.FONT_SLANT)) {
+                    String slantString = readSectionContentsAsString(sectionLength);
+                    font.setItalic(slantString.equalsIgnoreCase("italic"));
+                } else if (s.equals(PFF2Sections.CHAR_INDEX)) {
+                    loadCharsFromIndex(sectionLength);
+                } else if (s.equals(PFF2Sections.REMAINDER_IS_DATA)) {
+                    doneLoadingInfo = true;
+                } else {
+                    // If the section is not handled, then we just skip it.
+                    if (f.skipBytes(sectionLength) != sectionLength) {
+                        System.err.println("Warning: Could not skip "
+                                           + sectionLength
+                                           + " bytes for section `" + s + "'.");
+                    }
+                }
+            }
+        } catch (EOFException eofException) {
+            // End of file; done reading font.
+        }
+        f.close();
+    }
+
+    private void loadCharsFromIndex(int sectionLength) throws IOException {
+        long indexSectionEnd = f.getFilePointer() + sectionLength;
+        try {
+            while (f.getFilePointer() < indexSectionEnd) {
+                int codePoint = f.readInt();
+                int storageFlags = f.readUnsignedByte();
+                int offset = f.readInt();
+
+                if ((storageFlags & 0x07) == 0) {
+                    Glyph g = loadUncompressedGlyph(codePoint, offset);
+                    font.putGlyph(codePoint, g);
+                }
+            }
+        } catch (EOFException eofException) {
+            // End of file; simple return.
+        }
+    }
+
+    private Glyph loadUncompressedGlyph(int codePoint, int fileOffset) throws IOException {
+        if (debug >= 3)
+            System.out.printf("Loading uncompressed glyph U+%04X at %d%n", codePoint, fileOffset);
+
+        long savedPos = f.getFilePointer();
+
+        f.seek(fileOffset);
+        int width = f.readUnsignedShort();
+        int height = f.readUnsignedShort();
+        int offsetX = f.readShort();
+        int offsetY = f.readShort();
+        int deviceWidth = f.readShort();
+        if (debug >= 3) System.out.println("  Size=" + width + "x" + height
+                                           + " offset=(" + offsetX + ", " + offsetY
+                                           + ") dWidth=" + deviceWidth);
+        byte[] bitmap = new byte[(width * height + 7) / 8];
+        f.readFully(bitmap);
+
+        f.seek(savedPos);
+        return new Glyph(codePoint, width, height,
+                         offsetX, offsetY, deviceWidth, bitmap);
+    }
+
+    private int readSectionContentsAsUShort(int sectionLength) throws IOException {
+        if (sectionLength != 2)
+            throw new FileFormatException(
+                    "For uint16 section, expected 2 bytes but length is "
+                    + sectionLength + ".");
+        return f.readUnsignedShort();
+    }
+
+    private String openSection() throws IOException {
+        byte[] b = new byte[4];
+        f.readFully(b);
+        return new String(b, Charset.forName("US-ASCII"));
+    }
+
+    private String readCompleteSectionAsString() throws IOException {
+        int n = f.readInt();
+        return readSectionContentsAsString(n);
+    }
+
+    private String readSectionContentsAsString(int n) throws IOException {
+        StringBuilder buf = new StringBuilder();
+        while (n-- != 0) {
+            char c = (char) (f.readByte() & 0xFF);
+            buf.append(c);
+        }
+        return buf.toString();
+    }
+
+    public Font getFont() {
+        return font;
+    }
+}

=== added file 'util/fonttool/src/org/gnu/grub/fonttool/PFF2Sections.java'
--- util/fonttool/src/org/gnu/grub/fonttool/PFF2Sections.java	1970-01-01 00:00:00 +0000
+++ util/fonttool/src/org/gnu/grub/fonttool/PFF2Sections.java	2008-07-03 14:10:25 +0000
@@ -0,0 +1,19 @@
+package org.gnu.grub.fonttool;
+
+/**
+ * Section name constants for the PFF2 file format.
+ */
+public class PFF2Sections {
+    static final String FILE = "FILE";
+    static final String FONT_NAME = "NAME";
+    static final String FONT_FAMILY = "FAMI";
+    static final String FONT_WEIGHT = "WEIG";
+    static final String FONT_SLANT = "SLAN";
+    static final String FONT_POINT_SIZE = "PTSZ";
+    static final String MAX_CHAR_WIDTH = "MAXW";
+    static final String MAX_CHAR_HEIGHT = "MAXH";
+    static final String FONT_ASCENT = "ASCE";
+    static final String FONT_DESCENT = "DESC";
+    static final String CHAR_INDEX = "CHIX";
+    static final String REMAINDER_IS_DATA = "DATA";
+}

=== added file 'util/fonttool/src/org/gnu/grub/fonttool/PFF2Writer.java'
--- util/fonttool/src/org/gnu/grub/fonttool/PFF2Writer.java	1970-01-01 00:00:00 +0000
+++ util/fonttool/src/org/gnu/grub/fonttool/PFF2Writer.java	2008-07-03 14:10:25 +0000
@@ -0,0 +1,133 @@
+package org.gnu.grub.fonttool;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+// TODO Add DEFLATE compressed blocks of characters.
+public class PFF2Writer {
+    private RandomAccessFile f;
+    private String currentSection;
+    private long currentSectionStart;
+
+    public PFF2Writer(String filename) throws FileNotFoundException {
+        this.f = new RandomAccessFile(filename, "rw");
+        this.currentSection = null;
+        this.currentSectionStart = -1;
+    }
+
+    public void writeFont(Font font) throws IOException {
+        // Write file type ID header.
+        writeSection(PFF2Sections.FILE, "PFF2");
+
+        writeSection(PFF2Sections.FONT_NAME, font.getStandardName());
+        writeSection(PFF2Sections.FONT_FAMILY, font.getFamily());
+        writeSection(PFF2Sections.FONT_WEIGHT, font.isBold() ? "bold" : "normal");
+        writeSection(PFF2Sections.FONT_SLANT, font.isItalic() ? "italic" : "normal");
+        if (font.getPointSize() != Font.UNKNOWN_POINT_SIZE)
+            writeShortSection(PFF2Sections.FONT_POINT_SIZE, font.getPointSize());
+
+        // Construct character definitions.
+        CharDefs charDefs = new CharDefs(font);
+        charDefs.buildDefinitions();
+
+        // Write max character width and height metrics.
+        writeShortSection(PFF2Sections.MAX_CHAR_WIDTH, charDefs.getMaxCharWidth());
+        writeShortSection(PFF2Sections.MAX_CHAR_HEIGHT, charDefs.getMaxCharHeight());
+        writeShortSection(PFF2Sections.FONT_ASCENT, font.getAscent());
+        writeShortSection(PFF2Sections.FONT_DESCENT, font.getDescent());
+
+        // Write character index with pointers to the character definitions.
+        beginSection(PFF2Sections.CHAR_INDEX);
+
+        // Determine the size of the index, so we can properly refer to the
+        // character definition offset in the index.  The actual number of
+        // bytes written is compared to the calculated value to ensure we
+        // are correct.
+        final int indexStart = (int) f.getFilePointer();
+        final int calculatedIndexLength =
+                font.getGlyphs().size() * (4 + 1 + 4);
+        final int charDefStart = indexStart + calculatedIndexLength + 8;
+
+        for (CharStorageInfo storageInfo : charDefs.getCharIndex().values()) {
+            f.writeInt(storageInfo.getCodePoint());
+            f.writeByte(0);   // Storage flags: bits 1..0 = 00b : uncompressed.
+            f.writeInt(charDefStart + storageInfo.getFileOffset());
+        }
+
+        final int indexEnd = (int) f.getFilePointer();
+        if (indexEnd - indexStart != calculatedIndexLength) {
+            throw new RuntimeException("Incorrect index length calculated, calc="
+                                       + calculatedIndexLength
+                                       + " actual=" + (indexEnd - indexStart));
+        }
+        endSection(PFF2Sections.CHAR_INDEX);
+
+        f.writeBytes(PFF2Sections.REMAINDER_IS_DATA);
+        f.writeInt(-1);     // Data takes up the rest of the file.
+        f.write(charDefs.getDefinitionData());
+
+        f.close();
+    }
+
+    private void beginSection(String sectionName) throws IOException {
+        verifyOkToBeginSection(sectionName);
+
+        f.writeBytes(sectionName);
+        f.writeInt(-1);     // Placeholder for the section length.
+        currentSection = sectionName;
+        currentSectionStart = f.getFilePointer();
+    }
+
+    private void endSection(String sectionName) throws IOException {
+        verifyOkToEndSection(sectionName);
+
+        long sectionEnd = f.getFilePointer();
+        long sectionLength = sectionEnd - currentSectionStart;
+        f.seek(currentSectionStart - 4);
+        f.writeInt((int) sectionLength);
+        f.seek(sectionEnd);
+        currentSection = null;
+        currentSectionStart = -1;
+    }
+
+    private void verifyOkToBeginSection(String sectionName) {
+        if (sectionName.length() != 4)
+            throw new IllegalArgumentException(
+                    "Section names must be 4 characters: `" + sectionName + "'.");
+        if (currentSection != null)
+            throw new IllegalStateException(
+                    "Attempt to start `" + sectionName
+                    + "' section before ending the previous section `"
+                    + currentSection + "'.");
+    }
+
+    private void verifyOkToEndSection(String sectionName) {
+        if (sectionName.length() != 4)
+            throw new IllegalStateException("Invalid section name '" + sectionName
+                                            + "'; must be 4 characters.");
+        if (currentSection == null)
+            throw new IllegalStateException(
+                    "Attempt to end section `" + sectionName
+                    + "' when no section active.");
+        if (!sectionName.equals(currentSection))
+            throw new IllegalStateException(
+                    "Attempt to end `" + sectionName
+                    + "' section during active section `"
+                    + currentSection + "'.");
+    }
+
+    private void writeSection(String sectionName, String contents) throws IOException {
+        verifyOkToBeginSection(sectionName);
+        f.writeBytes(sectionName);
+        f.writeInt(contents.length());
+        f.writeBytes(contents);
+    }
+
+    private void writeShortSection(String sectionName, int value) throws IOException {
+        verifyOkToBeginSection(sectionName);
+        f.writeBytes(sectionName);
+        f.writeInt(2);
+        f.writeShort(value);
+    }
+}

=== added file 'util/fonttool/src/org/gnu/grub/fonttool/Viewer.java'
--- util/fonttool/src/org/gnu/grub/fonttool/Viewer.java	1970-01-01 00:00:00 +0000
+++ util/fonttool/src/org/gnu/grub/fonttool/Viewer.java	2008-07-03 14:10:25 +0000
@@ -0,0 +1,135 @@
+package org.gnu.grub.fonttool;
+
+import java.io.File;
+import java.io.IOException;
+import javax.swing.*;
+
+/**
+ * Program to view fonts.
+ * <p/>
+ * Supports PFF2 and BDF format font files.
+ */
+public class Viewer {
+    public static void main(String[] args) {
+        if (args.length < 1) {
+            printUsageAndExit();
+        }
+
+        boolean verbose = false;
+        String format = null;
+        String filename = null;
+
+        try {
+            for (String arg : args) {
+                if (arg.startsWith("--")) {
+                    String option;
+                    String value;
+                    int equalsPos = arg.indexOf('=');
+                    if (equalsPos < 0) {
+                        option = arg.substring(2);
+                        value = null;
+                    } else {
+                        option = arg.substring(2, equalsPos);
+                        value = arg.substring(equalsPos + 1);
+                    }
+
+                    if ("format".equals(option)) {
+                        if (value == null)
+                            throw new CommandLineException(option + " option requires a value.");
+                        format = value;
+                    } else if ("verbose".equals(option)) {
+                        verbose = true;
+                    }
+                } else {
+                    if (filename == null) {
+                        filename = arg;
+                    } else {
+                        throw new CommandLineException(
+                                "Extra argument after filename: `" + arg + "'.");
+                    }
+                }
+            }
+            if (filename == null) {
+                throw new CommandLineException("A filename must be specified.");
+            }
+            if (!(format == null || "bdf".equals(format) || "pff2".equals(format))) {
+                throw new CommandLineException("Invalid format: must be `bdf' or `pff2'.");
+            }
+        } catch (CommandLineException e) {
+            System.err.println("Error: " + e.getMessage());
+            System.exit(1);
+        }
+
+        if (!new File(filename).exists()) {
+            System.err.println("Error: File `" + filename + "' not found.");
+            System.exit(1);
+        }
+
+        if (format == null) {
+            // Autodetect the format.
+            if (PFF2Loader.isPFF2File(filename))
+                format = "pff2";
+            else if (BDFLoader.isBDFFile(filename))
+                format = "bdf";
+            else {
+                System.err.println("Error: Unable to detect font file format " +
+                                   "for `" + filename + "'.");
+                System.exit(1);
+            }
+            if (verbose)
+                System.out.println("Detected file format `" + format + "'.");
+        }
+
+        Font font = null;
+        try {
+            if ("bdf".equals(format)) {
+                if (verbose)
+                    System.out.println("Loading BDF font.");
+                font = BDFLoader.loadFontFile(filename);
+            } else if ("pff2".equals(format)) {
+                if (verbose)
+                    System.out.println("Loading PFF2 font.");
+                font = PFF2Loader.loadFontFile(filename);
+            } else {
+                System.err.println("Error: Bad format `" + format + "'");
+                System.exit(1);
+            }
+        } catch (IOException e) {
+            System.err.println("I/O error loading font: " + e);
+            e.printStackTrace();
+            System.exit(1);
+        }
+
+        if (font == null) {
+            System.err.println("Error: Font not loaded.");
+            System.exit(1);
+        }
+
+        if (verbose) {
+            System.out.println("Loaded font: '" + font.getStandardName() + "'");
+        }
+
+        ViewerFrame f = new ViewerFrame(font);
+        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+        f.setLocationRelativeTo(null);   // Center the frame on screen.
+        f.setVisible(true);
+    }
+
+    private static class CommandLineException extends Exception {
+        public CommandLineException(String message) {
+            super(message);
+        }
+    }
+
+    private static void printUsageAndExit() {
+        System.err.println("GNU GRUB Font Viewer");
+        System.err.println();
+        System.err.println("Usage: Viewer [--format=FORMAT] FILE");
+        System.err.println("    --format=bdf   The font is in BDF format.");
+        System.err.println("    --format=pff2  The font is in PFF2 format.");
+        System.err.println("  If no --format option is given, the type will be");
+        System.err.println("  automatically detected.");
+        System.err.println();
+        System.exit(1);
+    }
+}

=== added file 'util/fonttool/src/org/gnu/grub/fonttool/ViewerFrame.java'
--- util/fonttool/src/org/gnu/grub/fonttool/ViewerFrame.java	1970-01-01 00:00:00 +0000
+++ util/fonttool/src/org/gnu/grub/fonttool/ViewerFrame.java	2008-07-03 14:08:39 +0000
@@ -0,0 +1,61 @@
+package org.gnu.grub.fonttool;
+
+import java.awt.*;
+import javax.swing.*;
+import javax.swing.border.BevelBorder;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+public class ViewerFrame extends JFrame {
+    private final Font font;
+
+    private JLabel startingCodePointLabel;
+    private JSlider startingCodePointSlider;
+    private CharMatrix charMatrix;
+    private CharView charView;
+    private JLabel statusBar;
+
+    public ViewerFrame(Font font) {
+        this.font = font;
+
+        createComponents();
+        buildUI();
+        setSize(800, 600);
+    }
+
+    private void createComponents() {
+        startingCodePointLabel = new JLabel("Starting Code Point");
+        startingCodePointSlider = new JSlider(JSlider.HORIZONTAL, 0, 65535, 0);
+        startingCodePointSlider.addChangeListener(new ChangeListener() {
+            public void stateChanged(ChangeEvent e) {
+                charMatrix.setStartingChar(startingCodePointSlider.getValue());
+            }
+        });
+        charMatrix = new CharMatrix();
+        charMatrix.setCharFont(font);
+        charMatrix.setGlyphSelectionListener(new CharMatrix.GlyphSelectionListener() {
+            public void glyphSelected(int codePoint) {
+                charView.setGlyph(font.getGlyph(codePoint));
+            }
+        });
+        charView = new CharView();
+        charView.setZoom(3);
+        charView.setBorder(new BevelBorder(BevelBorder.RAISED));
+        statusBar = new JLabel();
+    }
+
+    private void buildUI() {
+        JComponent topPanel = Box.createVerticalBox();
+        topPanel.add(startingCodePointLabel);
+        topPanel.add(startingCodePointSlider);
+
+        JComponent charPanel = new JPanel(new GridLayout(2, 1));
+        charPanel.add(charMatrix);
+        charPanel.add(charView);
+
+        setLayout(new BorderLayout());
+        add(topPanel, BorderLayout.NORTH);
+        add(charPanel, BorderLayout.CENTER);
+        add(statusBar, BorderLayout.SOUTH);
+    }
+}

=== added directory 'util/fonttool/test'
=== added directory 'util/fonttool/test/org'
=== added directory 'util/fonttool/test/org/gnu'
=== added directory 'util/fonttool/test/org/gnu/grub'
=== added directory 'util/fonttool/test/org/gnu/grub/fonttool'
=== added file 'util/fonttool/test/org/gnu/grub/fonttool/TestGlyph.java'
--- util/fonttool/test/org/gnu/grub/fonttool/TestGlyph.java	1970-01-01 00:00:00 +0000
+++ util/fonttool/test/org/gnu/grub/fonttool/TestGlyph.java	2008-07-03 14:10:25 +0000
@@ -0,0 +1,110 @@
+package org.gnu.grub.fonttool;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TestGlyph {
+    @Test
+    public void testConstructor3x5() {
+        Glyph g = new Glyph(65, 3, 5, 0, 0, 0);
+
+        // Should be empty upon construction.
+        assertBitmapEquals("..." +
+                           "..." +
+                           "..." +
+                           "..." +
+                           "...",
+                           g.getBitmap());
+
+        // Should have ceil(3 * 5 / 8) = ceil(15 / 8) = ceil(1 + 7/8) = 2 bytes.
+        Assert.assertEquals("Wrong bitmap array size.",
+                            2, g.getBitmap().length);
+    }
+
+    @Test
+    public void testConstructor5x5() {
+        Glyph g = new Glyph(65, 5, 5, 0, 0, 0);
+
+        // Should be empty upon construction.
+        assertBitmapEquals("....." +
+                           "....." +
+                           "....." +
+                           "....." +
+                           ".....",
+                           g.getBitmap());
+
+        // Should have ceil(5 * 5 / 8) = ceil(25 / 8) = ceil(3 + 1/8) = 4 bytes.
+        Assert.assertEquals("Wrong bitmap array size.",
+                            4, g.getBitmap().length);
+    }
+
+    @Test
+    public void testSetPixel() {
+        Glyph g = new Glyph(65, 3, 5, 0, 0, 0);
+
+        g.setPixel(0, 0, true);
+        Assert.assertEquals(0x80, g.getBitmap()[0] & 0xFF);
+        Assert.assertEquals(0x00, g.getBitmap()[1] & 0xFF);
+        assertBitmapEquals("X.." +
+                           "..." +
+                           "..." +
+                           "..." +
+                           "...",
+                           g.getBitmap());
+
+        g.setPixel(2, 1, true);
+        Assert.assertEquals(0x84, g.getBitmap()[0] & 0xFF);
+        Assert.assertEquals(0x00, g.getBitmap()[1] & 0xFF);
+        assertBitmapEquals("X.." +
+                           "..X" +
+                           "..." +
+                           "..." +
+                           "...",
+                           g.getBitmap());
+    }
+
+    static void assertBitmapEquals(String expectedBitmap, byte[] actualBitmap) {
+        Assert.assertNotNull("expected bitmap null", expectedBitmap);
+        Assert.assertNotNull("actual bitmap null", actualBitmap);
+        StringBuilder errors = new StringBuilder();
+
+        int bit = 7;
+        int byteIndex = -1;
+        for (int bitIndex = 0; bitIndex < expectedBitmap.length(); bitIndex++) {
+            char c = expectedBitmap.charAt(bitIndex);
+            boolean expectedBit;
+            if (c == '.') {
+                expectedBit = false;
+            } else if (c == 'X') {
+                expectedBit = true;
+            } else {
+                throw new IllegalArgumentException("Bad character '" + c + "' in expected bitmap string");
+            }
+
+            byteIndex = bitIndex / 8;
+            byte b = actualBitmap[byteIndex];
+            boolean actualBit = (b & (1 << bit)) != 0;
+            if (actualBit != expectedBit) {
+                errors.append("[").append(bitIndex)
+                        .append("] wrong (it is ")
+                        .append(actualBit ? "1" : "0").append("). ");
+            }
+
+            if (bit == 0) {
+                bit = 7;
+            } else {
+                bit--;
+            }
+        }
+
+        if (byteIndex != expectedBitmap.length() / 8) {
+            errors.append("Last byte index wrong, was ")
+                    .append(byteIndex).append(" but expected ")
+                    .append(expectedBitmap.length() - 1);
+        }
+
+        if (errors.length() != 0) {
+            Assert.fail("Bitmap wrong: " + errors);
+        }
+    }
+}

=== modified file 'video/bitmap.c'
--- video/bitmap.c	2007-07-21 22:32:33 +0000
+++ video/bitmap.c	2008-07-03 13:49:18 +0000
@@ -78,7 +78,7 @@
 
   switch (blit_format)
     {
-      case GRUB_VIDEO_BLIT_FORMAT_R8G8B8A8:
+      case GRUB_VIDEO_BLIT_FORMAT_RGBA_8888:
         mode_info->mode_type = GRUB_VIDEO_MODE_TYPE_RGB 
                                | GRUB_VIDEO_MODE_TYPE_ALPHA;
         mode_info->bpp = 32;
@@ -94,7 +94,7 @@
         mode_info->reserved_field_pos = 24;
         break;
 
-      case GRUB_VIDEO_BLIT_FORMAT_R8G8B8:
+      case GRUB_VIDEO_BLIT_FORMAT_RGB_888:
         mode_info->mode_type = GRUB_VIDEO_MODE_TYPE_RGB;
         mode_info->bpp = 24;
         mode_info->bytes_per_pixel = 3;

=== added file 'video/bitmap_scale.c'
--- video/bitmap_scale.c	1970-01-01 00:00:00 +0000
+++ video/bitmap_scale.c	2008-07-03 14:27:43 +0000
@@ -0,0 +1,99 @@
+/* bitmap_scale.c - Bitmap scaling interface. */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2006,2007  Free Software Foundation, Inc.
+ *
+ *  GRUB is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/mm.h>
+#include <grub/misc.h>
+#include <grub/video.h>
+#include <grub/bitmap.h>
+#include <grub/bitmap_scale.h>
+#include <grub/types.h>
+
+/* 
+ * This function creates a new scaled version of the bitmap SRC.  The new
+ * bitmap has dimensions DST_WIDTH by DST_HEIGHT.  The scaling algorithm
+ * is given by SCALE_METHOD.  If an error is encountered, the return code is
+ * not equal to GRUB_ERR_NONE, and the bitmap DST is either not created, or 
+ * it is destroyed before this function returns.
+ *
+ * Supports only direct color modes which have components separated
+ * into bytes (e.g., RGBA 8:8:8:8 or BGR 8:8:8 true color). 
+ * But because of this simplifying assumption, the implementation is
+ * greatly simplified.
+ */
+grub_err_t
+grub_video_bitmap_create_scaled (struct grub_video_bitmap **dst,
+                                 int dst_width, int dst_height,
+                                 struct grub_video_bitmap *src,
+                                 enum grub_video_bitmap_scale_method
+                                 scale_method)
+{
+
+  /* Verify the simplifying assumptions. */
+  if (src == 0)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT,
+                       "null src bitmap in grub_video_bitmap_create_scaled");
+  if (src->mode_info.red_field_pos % 8 != 0
+      || src->mode_info.green_field_pos % 8 != 0
+      || src->mode_info.blue_field_pos % 8 != 0
+      || src->mode_info.reserved_field_pos % 8 != 0)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT,
+                       "src format not supported for scale");
+  if (src->mode_info.width == 0 || src->mode_info.height == 0)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "bitmap has a zero dimension");
+  if (dst_width <= 0 || dst_height <= 0)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT,
+                       "requested to scale to a size w/ a zero dimension");
+  if (src->mode_info.bytes_per_pixel * 8 != src->mode_info.bpp)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT,
+                       "bitmap to scale has inconsistent Bpp and bpp");
+
+  /* Create the new bitmap. */
+  grub_err_t ret;
+  ret = grub_video_bitmap_create (dst, dst_width, dst_height,
+                                  src->mode_info.blit_format);
+  if (ret != GRUB_ERR_NONE)
+    return ret;                 /* Error. */
+
+  switch (scale_method)
+    {
+    case GRUB_VIDEO_BITMAP_SCALE_METHOD_FASTEST:
+    case GRUB_VIDEO_BITMAP_SCALE_METHOD_NEAREST:
+      ret = grub_video_bitmap_scale_nn (*dst, src);
+      break;
+    case GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST:
+    case GRUB_VIDEO_BITMAP_SCALE_METHOD_BILINEAR:
+      ret = grub_video_bitmap_scale_bilinear (*dst, src);
+      break;
+    default:
+      ret = grub_error (GRUB_ERR_BAD_ARGUMENT, "Invalid scale_method value");
+      break;
+    }
+
+  if (ret == GRUB_ERR_NONE)
+    {
+      /* Success:  *dst is now a pointer to the scaled bitmap. */
+      return GRUB_ERR_NONE;
+    }
+  else
+    {
+      /* Destroy the bitmap and return the error code. */
+      grub_video_bitmap_destroy (*dst);
+      return ret;
+    }
+}

=== added file 'video/bitmap_scale_bilinear.c'
--- video/bitmap_scale_bilinear.c	1970-01-01 00:00:00 +0000
+++ video/bitmap_scale_bilinear.c	2008-07-03 14:28:15 +0000
@@ -0,0 +1,145 @@
+/* bitmap_scale_bilinear.c - Bilinear image scaling algorithm. */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2006,2007  Free Software Foundation, Inc.
+ *
+ *  GRUB is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/mm.h>
+#include <grub/misc.h>
+#include <grub/video.h>
+#include <grub/bitmap.h>
+#include <grub/bitmap_scale.h>
+#include <grub/types.h>
+
+/* 
+ * Copy the bitmap SRC to the bitmap DST, scaling the bitmap to fit the 
+ * dimensions of DST.  This function uses the bilinear interpolation algorithm
+ * to interpolate the pixels.
+ *
+ * Supports only direct color modes which have components separated
+ * into bytes (e.g., RGBA 8:8:8:8 or BGR 8:8:8 true color). 
+ * But because of this simplifying assumption, the implementation is
+ * greatly simplified.
+ */
+grub_err_t
+grub_video_bitmap_scale_bilinear (struct grub_video_bitmap *dst,
+                                  struct grub_video_bitmap *src)
+{
+  /* Verify the simplifying assumptions. */
+  if (dst == 0 || src == 0)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "null bitmap in scale func");
+  if (dst->mode_info.red_field_pos % 8 != 0
+      || dst->mode_info.green_field_pos % 8 != 0
+      || dst->mode_info.blue_field_pos % 8 != 0
+      || dst->mode_info.reserved_field_pos % 8 != 0)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "dst format not supported");
+  if (src->mode_info.red_field_pos % 8 != 0
+      || src->mode_info.green_field_pos % 8 != 0
+      || src->mode_info.blue_field_pos % 8 != 0
+      || src->mode_info.reserved_field_pos % 8 != 0)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "src format not supported");
+  if (dst->mode_info.red_field_pos != src->mode_info.red_field_pos
+      || dst->mode_info.red_mask_size != src->mode_info.red_mask_size
+      || dst->mode_info.green_field_pos != src->mode_info.green_field_pos
+      || dst->mode_info.green_mask_size != src->mode_info.green_mask_size
+      || dst->mode_info.blue_field_pos != src->mode_info.blue_field_pos
+      || dst->mode_info.blue_mask_size != src->mode_info.blue_mask_size
+      || dst->mode_info.reserved_field_pos !=
+      src->mode_info.reserved_field_pos
+      || dst->mode_info.reserved_mask_size !=
+      src->mode_info.reserved_mask_size)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "dst and src not compatible");
+  if (dst->mode_info.bytes_per_pixel != src->mode_info.bytes_per_pixel)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "dst and src not compatible");
+  if (dst->mode_info.width == 0 || dst->mode_info.height == 0
+      || src->mode_info.width == 0 || src->mode_info.height == 0)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "bitmap has a zero dimension");
+
+  grub_uint8_t *ddata = dst->data;
+  grub_uint8_t *sdata = src->data;
+  int dw = dst->mode_info.width;
+  int dh = dst->mode_info.height;
+  int sw = src->mode_info.width;
+  int sh = src->mode_info.height;
+  int dstride = dst->mode_info.pitch;
+  int sstride = src->mode_info.pitch;
+  /* bytes_per_pixel is the same for both src and dst. */
+  int bytes_per_pixel = dst->mode_info.bytes_per_pixel;
+
+  int dy;
+  for (dy = 0; dy < dh; dy++)
+    {
+      int dx;
+      for (dx = 0; dx < dw; dx++)
+        {
+          grub_uint8_t *dptr;
+          grub_uint8_t *sptr;
+          int sx;
+          int sy;
+          int comp;
+
+          /* Compute the source coordinate that the destination coordinate
+           * maps to.  Note: sx/sw = dx/dw  =>  sx = sw*dx/dw. */
+          sx = sw * dx / dw;
+          sy = sh * dy / dh;
+
+          /* Get the address of the pixels in src and dst. */
+          dptr = ddata + dy * dstride + dx * bytes_per_pixel;
+          sptr = sdata + sy * sstride + sx * bytes_per_pixel;
+
+          /* If we have enough space to do so, use bilinear interpolation.
+           * Otherwise, fall back to nearest neighbor for this pixel. */
+          if (sx < sw - 1 && sy < sh - 1)
+            {
+              /* Do bilinear interpolation. */
+
+              /* Fixed-point .8 numbers representing the fraction of the
+               * distance in the x (u) and y (v) direction within the 
+               * box of 4 pixels in the source. */
+              int u = (256 * sw * dx / dw) - (sx * 256);
+              int v = (256 * sh * dy / dh) - (sy * 256);
+
+              for (comp = 0; comp < bytes_per_pixel; comp++)
+                {
+                  /* Get the component's values for the 
+                   * 4 source corner pixels. */
+                  grub_uint8_t f00 = sptr[comp];
+                  grub_uint8_t f10 = sptr[comp + bytes_per_pixel];
+                  grub_uint8_t f01 = sptr[comp + sstride];
+                  grub_uint8_t f11 = sptr[comp + sstride + bytes_per_pixel];
+
+                  /* Do linear interpolations along the top and bottom
+                   * rows of the box. */
+                  grub_uint8_t f0y = (256 - v) * f00 / 256 + v * f01 / 256;
+                  grub_uint8_t f1y = (256 - v) * f10 / 256 + v * f11 / 256;
+
+                  /* Interpolate vertically. */
+                  grub_uint8_t fxy = (256 - u) * f0y / 256 + u * f1y / 256;
+
+                  dptr[comp] = fxy;
+                }
+            }
+          else
+            {
+              /* Fall back to nearest neighbor interpolation. */
+              /* Copy the pixel color value. */
+              for (comp = 0; comp < bytes_per_pixel; comp++)
+                dptr[comp] = sptr[comp];
+            }
+        }
+    }
+  return GRUB_ERR_NONE;
+}

=== added file 'video/bitmap_scale_nn.c'
--- video/bitmap_scale_nn.c	1970-01-01 00:00:00 +0000
+++ video/bitmap_scale_nn.c	2008-07-03 14:27:43 +0000
@@ -0,0 +1,109 @@
+/* bitmap_scale_nn.c - Nearest neighbor image scaling algorithm. */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2006,2007  Free Software Foundation, Inc.
+ *
+ *  GRUB is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/mm.h>
+#include <grub/misc.h>
+#include <grub/video.h>
+#include <grub/bitmap.h>
+#include <grub/bitmap_scale.h>
+#include <grub/types.h>
+
+/* 
+ * Copy the bitmap SRC to the bitmap DST, scaling the bitmap to fit the 
+ * dimensions of DST.  This function uses the nearest neighbor algorithm to
+ * interpolate the pixels.
+ *
+ * Supports only direct color modes which have components separated
+ * into bytes (e.g., RGBA 8:8:8:8 or BGR 8:8:8 true color). 
+ * But because of this simplifying assumption, the implementation is
+ * greatly simplified.
+ */
+grub_err_t
+grub_video_bitmap_scale_nn (struct grub_video_bitmap *dst,
+                            struct grub_video_bitmap *src)
+{
+  /* Verify the simplifying assumptions. */
+  if (dst == 0 || src == 0)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "null bitmap in scale_nn");
+  if (dst->mode_info.red_field_pos % 8 != 0
+      || dst->mode_info.green_field_pos % 8 != 0
+      || dst->mode_info.blue_field_pos % 8 != 0
+      || dst->mode_info.reserved_field_pos % 8 != 0)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "dst format not supported");
+  if (src->mode_info.red_field_pos % 8 != 0
+      || src->mode_info.green_field_pos % 8 != 0
+      || src->mode_info.blue_field_pos % 8 != 0
+      || src->mode_info.reserved_field_pos % 8 != 0)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "src format not supported");
+  if (dst->mode_info.red_field_pos != src->mode_info.red_field_pos
+      || dst->mode_info.red_mask_size != src->mode_info.red_mask_size
+      || dst->mode_info.green_field_pos != src->mode_info.green_field_pos
+      || dst->mode_info.green_mask_size != src->mode_info.green_mask_size
+      || dst->mode_info.blue_field_pos != src->mode_info.blue_field_pos
+      || dst->mode_info.blue_mask_size != src->mode_info.blue_mask_size
+      || dst->mode_info.reserved_field_pos !=
+      src->mode_info.reserved_field_pos
+      || dst->mode_info.reserved_mask_size !=
+      src->mode_info.reserved_mask_size)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "dst and src not compatible");
+  if (dst->mode_info.bytes_per_pixel != src->mode_info.bytes_per_pixel)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "dst and src not compatible");
+  if (dst->mode_info.width == 0 || dst->mode_info.height == 0
+      || src->mode_info.width == 0 || src->mode_info.height == 0)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "bitmap has a zero dimension");
+
+  grub_uint8_t *ddata = dst->data;
+  grub_uint8_t *sdata = src->data;
+  int dw = dst->mode_info.width;
+  int dh = dst->mode_info.height;
+  int sw = src->mode_info.width;
+  int sh = src->mode_info.height;
+  int dstride = dst->mode_info.pitch;
+  int sstride = src->mode_info.pitch;
+  /* bytes_per_pixel is the same for both src and dst. */
+  int bytes_per_pixel = dst->mode_info.bytes_per_pixel;
+
+  int dy;
+  for (dy = 0; dy < dh; dy++)
+    {
+      int dx;
+      for (dx = 0; dx < dw; dx++)
+        {
+          grub_uint8_t *dptr;
+          grub_uint8_t *sptr;
+          int sx;
+          int sy;
+          int comp;
+
+          /* Compute the source coordinate that the destination coordinate
+           * maps to.  Note: sx/sw = dx/dw  =>  sx = sw*dx/dw. */
+          sx = sw * dx / dw;
+          sy = sh * dy / dh;
+
+          /* Get the address of the pixels in src and dst. */
+          dptr = ddata + dy * dstride + dx * bytes_per_pixel;
+          sptr = sdata + sy * sstride + sx * bytes_per_pixel;
+
+          /* Copy the pixel color value. */
+          for (comp = 0; comp < bytes_per_pixel; comp++)
+            dptr[comp] = sptr[comp];
+        }
+    }
+  return GRUB_ERR_NONE;
+}

=== modified file 'video/i386/pc/vbe.c'
--- video/i386/pc/vbe.c	2008-01-21 15:48:27 +0000
+++ video/i386/pc/vbe.c	2008-07-03 14:19:24 +0000
@@ -63,6 +63,10 @@
 static struct
 {
   struct grub_video_render_target render_target;
+  int is_double_buffered;       /* Is the video mode double buffered? */
+  int displayed_page;           /* The page # that is the front buffer. */
+  int render_page;              /* The page # that is the back buffer. */
+  grub_size_t page_size;        /* The size of a page in bytes. */
 
   unsigned int bytes_per_scan_line;
   unsigned int bytes_per_pixel;
@@ -77,6 +81,8 @@
 static grub_uint32_t mode_in_use = 0x55aa;
 static grub_uint16_t *mode_list;
 
+static void commit_video_page_update (void);
+
 static void *
 real2pm (grub_vbe_farptr_t ptr)
 {
@@ -239,6 +245,10 @@
       /* Just ignore the status.  */
     }
 
+  /* Set up page information. */
+  framebuffer.displayed_page = 0;
+  framebuffer.render_page = 1;
+
   /* Copy mode info for caller.  */
   if (mode_info)
     grub_memcpy (mode_info, &active_mode_info, sizeof (*mode_info));
@@ -533,9 +543,18 @@
       render_target->viewport.width = active_mode_info.x_resolution;
       render_target->viewport.height = active_mode_info.y_resolution;
 
-      /* Set framebuffer pointer and mark it as non allocated.  */
+      /* Set up double buffering information. */
+      framebuffer.is_double_buffered =
+        ((mode_type & GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED) != 0);
+      framebuffer.displayed_page = 0;
+      framebuffer.render_page = framebuffer.is_double_buffered ? 1 : 0;
+      framebuffer.page_size =
+        framebuffer.bytes_per_scan_line * render_target->mode_info.height;
+
+      /* Mark framebuffer memory as non allocated.  */
       render_target->is_allocated = 0;
-      render_target->data = framebuffer.ptr;
+      /* Set the framebuffer memory data pointer and display the right page. */
+      commit_video_page_update ();
 
       /* Copy default palette to initialize emulated palette.  */
       for (i = 0; 
@@ -708,6 +727,16 @@
 
       return minindex;
     }
+  else if ((render_target->mode_info.mode_type 
+            & GRUB_VIDEO_MODE_TYPE_1BIT_BITMAP) != 0)
+    {
+       if (red == render_target->mode_info.fg_red
+           && green == render_target->mode_info.fg_green
+           && blue == render_target->mode_info.fg_blue)
+         return 1;
+       else
+         return 0;
+    }
   else
     {
       grub_uint32_t value;
@@ -737,6 +766,17 @@
     /* No alpha available in index color modes, just use
        same value as in only RGB modes.  */
     return grub_video_vbe_map_rgb (red, green, blue);
+  else if ((render_target->mode_info.mode_type 
+            & GRUB_VIDEO_MODE_TYPE_1BIT_BITMAP) != 0)
+    {
+      if (red == render_target->mode_info.fg_red
+          && green == render_target->mode_info.fg_green
+          && blue == render_target->mode_info.fg_blue
+          && alpha == render_target->mode_info.fg_alpha)
+        return 1;
+      else
+        return 0;
+    }
   else
     {
       grub_uint32_t value;
@@ -797,6 +837,24 @@
       *alpha = framebuffer.palette[color].a;
       return;
     }
+  else if ((mode_info->mode_type
+            & GRUB_VIDEO_MODE_TYPE_1BIT_BITMAP) != 0)
+    {
+      if (color & 1)
+        {
+          *red = mode_info->fg_red;
+          *green = mode_info->fg_green;
+          *blue = mode_info->fg_blue;
+          *alpha = mode_info->fg_alpha;
+        }
+      else
+        {
+          *red = mode_info->bg_red;
+          *green = mode_info->bg_green;
+          *blue = mode_info->bg_blue;
+          *alpha = mode_info->bg_alpha;
+        }
+    }
   else
     {
       grub_uint32_t tmp;
@@ -876,17 +934,24 @@
   target.data = render_target->data;
 
   /* Try to figure out more optimized version.  */
-  if (target.mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_R8G8B8A8)
-    {
-      grub_video_i386_vbefill_R8G8B8A8 (&target, color, x, y, 
-                                        width, height);
-      return GRUB_ERR_NONE;
-    }
-
-  if (target.mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_R8G8B8)
-    {
-      grub_video_i386_vbefill_R8G8B8 (&target, color, x, y,
-                                      width, height);
+  if (target.mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_BGRA_8888)
+    {
+      grub_video_i386_vbefill_direct32 (&target, color, x, y, 
+                                        width, height);
+      return GRUB_ERR_NONE;
+    }
+
+  if (target.mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_RGBA_8888)
+    {
+      grub_video_i386_vbefill_direct32 (&target, color, x, y, 
+                                        width, height);
+      return GRUB_ERR_NONE;
+    }
+
+  if (target.mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_RGB_888)
+    {
+      grub_video_i386_vbefill_direct24 (&target, color, x, y,
+                                        width, height);
       return GRUB_ERR_NONE;
     }
 
@@ -903,76 +968,6 @@
   return GRUB_ERR_NONE;
 }
 
-// TODO: Remove this method and replace with bitmap based glyphs
-static grub_err_t
-grub_video_vbe_blit_glyph (struct grub_font_glyph * glyph,
-                           grub_video_color_t color, int x, int y)
-{
-  struct grub_video_i386_vbeblit_info target;
-  unsigned int width;
-  unsigned int charwidth;
-  unsigned int height;
-  unsigned int i;
-  unsigned int j;
-  unsigned int x_offset = 0;
-  unsigned int y_offset = 0;
-
-  /* Make sure there is something to do.  */
-  if (x >= (int)render_target->viewport.width)
-    return GRUB_ERR_NONE;
-
-  if (y >= (int)render_target->viewport.height)
-    return GRUB_ERR_NONE;
-
-  /* Calculate glyph dimensions.  */
-  width = ((glyph->width + 7) / 8) * 8;
-  charwidth = width;
-  height = glyph->height;
-
-  if (x + (int)width < 0)
-    return GRUB_ERR_NONE;
-
-  if (y + (int)height < 0)
-    return GRUB_ERR_NONE;
-
-  /* Do not allow drawing out of viewport.  */
-  if (x < 0)
-    {
-      width += x;
-      x_offset = (unsigned int)-x;
-      x = 0;
-    }
-  if (y < 0)
-    {
-      height += y;
-      y_offset = (unsigned int)-y;
-      y = 0;
-    }
-
-  if ((x + width) > render_target->viewport.width)
-    width = render_target->viewport.width - x;
-  if ((y + height) > render_target->viewport.height)
-    height = render_target->viewport.height - y;
-
-  /* Add viewport offset.  */
-  x += render_target->viewport.x;
-  y += render_target->viewport.y;
-
-  /* Use vbeblit_info to encapsulate rendering.  */
-  target.mode_info = &render_target->mode_info;
-  target.data = render_target->data;
-
-  /* Draw glyph.  */
-  for (j = 0; j < height; j++)
-    for (i = 0; i < width; i++)
-      if ((glyph->bitmap[((i + x_offset) / 8) 
-                         + (j + y_offset) * (charwidth / 8)] 
-           & (1 << ((charwidth - (i + x_offset) - 1) % 8))))
-        set_pixel (&target, x+i, y+j, color);
-
-  return GRUB_ERR_NONE;
-}
-
 /* NOTE: This function assumes that given coordinates are within bounds of 
    handled data.  */
 static void
@@ -985,19 +980,27 @@
   if (oper == GRUB_VIDEO_BLIT_REPLACE)
     {
       /* Try to figure out more optimized version for replace operator.  */
-      if (source->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_R8G8B8A8)
+      if (source->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_RGBA_8888)
         {
-          if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_R8G8B8A8)
-            {
-              grub_video_i386_vbeblit_R8G8B8X8_R8G8B8X8 (target, source,
+          if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_RGBA_8888)
+            {
+              grub_video_i386_vbeblit_direct32_copy (target, source,
+                                                     x, y, width, height,
+                                                     offset_x, offset_y);
+              return;
+            }
+
+          if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_BGRA_8888)
+            {
+              grub_video_i386_vbeblit_BGRX8888_RGBX8888 (target, source,
                                                          x, y, width, height,
                                                          offset_x, offset_y);
               return;
             }
 
-          if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_R8G8B8)
+          if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_RGB_888)
             {
-              grub_video_i386_vbeblit_R8G8B8_R8G8B8X8 (target, source,
+              grub_video_i386_vbeblit_RGB888_RGBX8888 (target, source,
                                                        x, y, width, height,
                                                        offset_x, offset_y);
               return;
@@ -1005,26 +1008,42 @@
 
           if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_INDEXCOLOR)
             {
-              grub_video_i386_vbeblit_index_R8G8B8X8 (target, source,
+              grub_video_i386_vbeblit_index_RGBX8888 (target, source,
                                                       x, y, width, height,
                                                       offset_x, offset_y);
               return;
             }
         }
 
-      if (source->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_R8G8B8)
+      if (source->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_RGB_888)
         {
-          if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_R8G8B8A8)
-            {
-              grub_video_i386_vbeblit_R8G8B8A8_R8G8B8 (target, source,
-                                                       x, y, width, height,
-                                                       offset_x, offset_y);
-              return;
-            }
-
-          if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_R8G8B8)
-            {
-              grub_video_i386_vbeblit_R8G8B8_R8G8B8 (target, source,
+          if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_BGRA_8888)
+            {
+              grub_video_i386_vbeblit_BGRA8888_RGB888 (target, source,
+                                                       x, y, width, height,
+                                                       offset_x, offset_y);
+              return;
+            }
+
+          if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_RGBA_8888)
+            {
+              grub_video_i386_vbeblit_RGBA8888_RGB888 (target, source,
+                                                       x, y, width, height,
+                                                       offset_x, offset_y);
+              return;
+            }
+
+          if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_BGR_888)
+            {
+              grub_video_i386_vbeblit_BGR888_RGB888 (target, source,
+                                                     x, y, width, height,
+                                                     offset_x, offset_y);
+              return;
+            }
+
+          if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_RGB_888)
+            {
+              grub_video_i386_vbeblit_RGB888_RGB888 (target, source,
                                                      x, y, width, height,
                                                      offset_x, offset_y);
               return;
@@ -1032,13 +1051,24 @@
 
           if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_INDEXCOLOR)
             {
-              grub_video_i386_vbeblit_index_R8G8B8 (target, source,
+              grub_video_i386_vbeblit_index_RGB888 (target, source,
                                                     x, y, width, height,
                                                     offset_x, offset_y);
               return;
             }
         }
 
+      if (source->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_BGRA_8888)
+        {
+          if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_BGRA_8888)
+            {
+              grub_video_i386_vbeblit_direct32_copy (target, source,
+                                                     x, y, width, height,
+                                                     offset_x, offset_y);
+              return;
+            }
+        }
+
       if (source->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_INDEXCOLOR)
         {
           if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_INDEXCOLOR)
@@ -1057,19 +1087,19 @@
   else
     {
       /* Try to figure out more optimized blend operator.  */
-      if (source->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_R8G8B8A8)
+      if (source->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_RGBA_8888)
       {
-        if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_R8G8B8A8)
+        if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_RGBA_8888)
         {
-          grub_video_i386_vbeblit_R8G8B8A8_R8G8B8A8 (target, source,
+          grub_video_i386_vbeblit_RGBA8888_RGBA8888 (target, source,
               x, y, width, height,
               offset_x, offset_y);
           return;
         }
 
-        if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_R8G8B8)
+        if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_RGB_888)
         {
-          grub_video_i386_vbeblit_R8G8B8_R8G8B8A8 (target, source,
+          grub_video_i386_vbeblit_RGB888_RGBA8888 (target, source,
               x, y, width, height,
               offset_x, offset_y);
           return;
@@ -1077,26 +1107,26 @@
 
         if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_INDEXCOLOR)
         {
-          grub_video_i386_vbeblit_index_R8G8B8A8 (target, source,
+          grub_video_i386_vbeblit_index_RGBA8888 (target, source,
               x, y, width, height,
               offset_x, offset_y);
           return;
         }
       }
 
-      if (source->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_R8G8B8)
+      if (source->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_RGB_888)
       {
-        if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_R8G8B8A8)
+        if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_RGBA_8888)
         {
-          grub_video_i386_vbeblit_R8G8B8A8_R8G8B8 (target, source,
+          grub_video_i386_vbeblit_RGBA8888_RGB888 (target, source,
               x, y, width, height,
               offset_x, offset_y);
           return;
         }
 
-        if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_R8G8B8)
+        if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_RGB_888)
         {
-          grub_video_i386_vbeblit_R8G8B8_R8G8B8 (target, source,
+          grub_video_i386_vbeblit_RGB888_RGB888 (target, source,
               x, y, width, height,
               offset_x, offset_y);
           return;
@@ -1104,7 +1134,7 @@
 
         if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_INDEXCOLOR)
         {
-          grub_video_i386_vbeblit_index_R8G8B8 (target, source,
+          grub_video_i386_vbeblit_index_RGB888 (target, source,
               x, y, width, height,
               offset_x, offset_y);
           return;
@@ -1219,6 +1249,77 @@
   return GRUB_ERR_NONE;
 }
 
+/* 
+ * Draw the specified glyph at (x, y).  The y coordinate designates the
+ * baseline of the character, while the x coordinate designates the left
+ * side location of the character. 
+ */
+static grub_err_t
+grub_video_vbe_blit_glyph (struct grub_font_glyph *glyph,
+                           grub_video_color_t color, 
+                           int left_x, int baseline_y)
+{
+  struct grub_video_bitmap glyph_bitmap;
+
+  /* Don't try to draw empty glyphs (U+0020, etc.). */
+  if (glyph->width == 0 || glyph->height == 0)
+    return GRUB_ERR_NONE;
+
+  glyph_bitmap.mode_info.width = glyph->width;
+  glyph_bitmap.mode_info.height = glyph->height;
+  glyph_bitmap.mode_info.mode_type = 
+    (1 << GRUB_VIDEO_MODE_TYPE_DEPTH_POS)
+    | GRUB_VIDEO_MODE_TYPE_1BIT_BITMAP;
+  glyph_bitmap.mode_info.blit_format = GRUB_VIDEO_BLIT_FORMAT_1BIT_PACKED;
+  glyph_bitmap.mode_info.bpp = 1;
+  glyph_bitmap.mode_info.bytes_per_pixel = 0;  /* Really 1 bit per pixel. */
+  glyph_bitmap.mode_info.pitch = glyph->width; /* Packed densely as bits. */
+  glyph_bitmap.mode_info.number_of_colors = 2;
+  glyph_bitmap.mode_info.bg_red = 0;
+  glyph_bitmap.mode_info.bg_green = 0;
+  glyph_bitmap.mode_info.bg_blue = 0;
+  glyph_bitmap.mode_info.bg_alpha = 0;
+  grub_video_vbe_unmap_color(color, 
+                             &glyph_bitmap.mode_info.fg_red,
+                             &glyph_bitmap.mode_info.fg_green,
+                             &glyph_bitmap.mode_info.fg_blue,
+                             &glyph_bitmap.mode_info.fg_alpha);
+  glyph_bitmap.data = glyph->bitmap;
+
+  int bitmap_left = left_x + glyph->offset_x;
+  int bitmap_bottom = baseline_y - glyph->offset_y;
+  int bitmap_top = bitmap_bottom - glyph->height;
+
+  return grub_video_vbe_blit_bitmap (&glyph_bitmap, GRUB_VIDEO_BLIT_BLEND,
+                                     bitmap_left, bitmap_top,
+                                     0, 0,
+                                     glyph->width, glyph->height);
+}
+
+static grub_err_t
+grub_video_vbe_draw_string (const char *str, grub_font_t font, 
+                            grub_video_color_t color, 
+                            int left_x, int baseline_y)
+{
+  grub_size_t len;
+  grub_size_t i;
+  int x;
+  struct grub_font_glyph *glyph;
+
+  len = grub_strlen (str);
+  x = left_x;
+  for (i = 0; i < len; i++)
+    {
+      glyph = grub_font_get_glyph (font, str[i]);
+      if (grub_video_vbe_blit_glyph (glyph, color, x, baseline_y) 
+          != GRUB_ERR_NONE)
+        return grub_errno;
+      x += glyph->device_width;
+    }
+
+  return GRUB_ERR_NONE;
+}
+
 static grub_err_t
 grub_video_vbe_blit_render_target (struct grub_video_render_target *source,
                                    enum grub_video_blit_operators oper,
@@ -1403,10 +1504,36 @@
   return GRUB_ERR_NONE;
 }
 
-static grub_err_t 
+/* Set framebuffer render target page and display the proper page, based on
+ * `framebuffer.render_page' and `framebuffer.displayed_page', respectively. */
+static void
+commit_video_page_update (void)
+{
+  /* Set the render target's data pointer to the start of the render_page. */
+  framebuffer.render_target.data =
+    ((char *) framebuffer.ptr) +
+    framebuffer.page_size * framebuffer.render_page;
+
+  /* Tell the video adapter to display the new front page. */
+  int display_start_line =
+    framebuffer.render_target.mode_info.height 
+    * framebuffer.displayed_page;
+  grub_vbe_bios_set_display_start (0, display_start_line);
+}
+
+static grub_err_t
 grub_video_vbe_swap_buffers (void)
 {
-  /* TODO: Implement buffer swapping.  */
+  if (framebuffer.is_double_buffered)
+    {
+      /* Swap the page numbers in the framebuffer struct. */
+      int new_displayed_page = framebuffer.render_page;
+      framebuffer.render_page = framebuffer.displayed_page;
+      framebuffer.displayed_page = new_displayed_page;
+
+      commit_video_page_update ();
+    }
+
   return GRUB_ERR_NONE;
 }
 
@@ -1505,17 +1632,13 @@
 static grub_err_t
 grub_video_vbe_set_active_render_target (struct grub_video_render_target *target)
 {
-  if (target == GRUB_VIDEO_RENDER_TARGET_FRONT_BUFFER)
+  if (target == GRUB_VIDEO_RENDER_TARGET_DISPLAY)
     {
       render_target = &framebuffer.render_target;
-
+      
       return GRUB_ERR_NONE;
     }
 
-  if (target == GRUB_VIDEO_RENDER_TARGET_BACK_BUFFER)
-    return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, 
-                       "double buffering not implemented yet.");
-
   if (! target->data)
     return grub_error (GRUB_ERR_BAD_ARGUMENT, 
                        "invalid render target given.");
@@ -1551,6 +1674,7 @@
     .unmap_color = grub_video_vbe_unmap_color,
     .fill_rect = grub_video_vbe_fill_rect,
     .blit_glyph = grub_video_vbe_blit_glyph,
+    .draw_string = grub_video_vbe_draw_string,
     .blit_bitmap = grub_video_vbe_blit_bitmap,
     .blit_render_target = grub_video_vbe_blit_render_target,
     .scroll = grub_video_vbe_scroll,

=== modified file 'video/i386/pc/vbeblit.c'
--- video/i386/pc/vbeblit.c	2008-01-01 12:02:07 +0000
+++ video/i386/pc/vbeblit.c	2008-07-03 13:49:18 +0000
@@ -35,7 +35,133 @@
 #include <grub/video.h>
 
 void
-grub_video_i386_vbeblit_R8G8B8A8_R8G8B8A8 (struct grub_video_i386_vbeblit_info *dst,
+grub_video_i386_vbeblit_BGRX8888_RGBX8888 (struct grub_video_i386_vbeblit_info
+                                           *dst,
+                                           struct grub_video_i386_vbeblit_info
+                                           *src, int x, int y, int width,
+                                           int height, int offset_x,
+                                           int offset_y)
+{
+  int i;
+  int j;
+  grub_uint8_t *srcptr;
+  grub_uint8_t *dstptr;
+  unsigned srcrowskip;
+  unsigned dstrowskip;
+
+  /* Calculate the number of bytes to advance from the end of one line
+   * to the beginning of the next line. */
+  srcrowskip =
+    src->mode_info->pitch - src->mode_info->bytes_per_pixel * width;
+  dstrowskip =
+    dst->mode_info->pitch - dst->mode_info->bytes_per_pixel * width;
+
+  srcptr = (grub_uint8_t *) get_data_ptr (src, offset_x, offset_y);
+  dstptr = (grub_uint8_t *) get_data_ptr (dst, x, y);
+  for (j = 0; j < height; j++)
+    {
+      for (i = 0; i < width; i++)
+        {
+          grub_uint8_t r = *srcptr++;
+          grub_uint8_t g = *srcptr++;
+          grub_uint8_t b = *srcptr++;
+          grub_uint8_t a = *srcptr++;
+
+          *dstptr++ = b;
+          *dstptr++ = g;
+          *dstptr++ = r;
+          *dstptr++ = a;
+        }
+      srcptr += srcrowskip;
+      dstptr += dstrowskip;
+    }
+}
+
+void
+grub_video_i386_vbeblit_BGRA8888_RGB888 (struct grub_video_i386_vbeblit_info
+                                         *dst,
+                                         struct grub_video_i386_vbeblit_info
+                                         *src, int x, int y, int width,
+                                         int height, int offset_x,
+                                         int offset_y)
+{
+  int i;
+  int j;
+  grub_uint8_t *srcptr;
+  grub_uint8_t *dstptr;
+  unsigned srcrowskip;
+  unsigned dstrowskip;
+
+  /* Calculate the number of bytes to advance from the end of one line
+   * to the beginning of the next line. */
+  srcrowskip =
+    src->mode_info->pitch - src->mode_info->bytes_per_pixel * width;
+  dstrowskip =
+    dst->mode_info->pitch - dst->mode_info->bytes_per_pixel * width;
+
+  srcptr = (grub_uint8_t *) get_data_ptr (src, offset_x, offset_y);
+  dstptr = (grub_uint8_t *) get_data_ptr (dst, x, y);
+  for (j = 0; j < height; j++)
+    {
+      for (i = 0; i < width; i++)
+        {
+          grub_uint8_t r = *srcptr++;
+          grub_uint8_t g = *srcptr++;
+          grub_uint8_t b = *srcptr++;
+
+          *dstptr++ = b;
+          *dstptr++ = g;
+          *dstptr++ = r;
+          *dstptr++ = 255;      /* Alpha component: Set opaque. */
+        }
+      srcptr += srcrowskip;
+      dstptr += dstrowskip;
+    }
+}
+
+void
+grub_video_i386_vbeblit_BGR888_RGB888 (struct grub_video_i386_vbeblit_info
+                                         *dst,
+                                         struct grub_video_i386_vbeblit_info
+                                         *src, int x, int y, int width,
+                                         int height, int offset_x,
+                                         int offset_y)
+{
+  int i;
+  int j;
+  grub_uint8_t *srcptr;
+  grub_uint8_t *dstptr;
+  unsigned srcrowskip;
+  unsigned dstrowskip;
+
+  /* Calculate the number of bytes to advance from the end of one line
+   * to the beginning of the next line. */
+  srcrowskip =
+    src->mode_info->pitch - src->mode_info->bytes_per_pixel * width;
+  dstrowskip =
+    dst->mode_info->pitch - dst->mode_info->bytes_per_pixel * width;
+
+  srcptr = (grub_uint8_t *) get_data_ptr (src, offset_x, offset_y);
+  dstptr = (grub_uint8_t *) get_data_ptr (dst, x, y);
+  for (j = 0; j < height; j++)
+    {
+      for (i = 0; i < width; i++)
+        {
+          grub_uint8_t r = *srcptr++;
+          grub_uint8_t g = *srcptr++;
+          grub_uint8_t b = *srcptr++;
+
+          *dstptr++ = b;
+          *dstptr++ = g;
+          *dstptr++ = r;
+        }
+      srcptr += srcrowskip;
+      dstptr += dstrowskip;
+    }
+}
+
+void
+grub_video_i386_vbeblit_RGBA8888_RGBA8888 (struct grub_video_i386_vbeblit_info *dst,
                                            struct grub_video_i386_vbeblit_info *src,
                                            int x, int y, int width, int height,
                                            int offset_x, int offset_y)
@@ -101,10 +227,10 @@
 }
 
 void
-grub_video_i386_vbeblit_R8G8B8X8_R8G8B8X8 (struct grub_video_i386_vbeblit_info *dst,
-                                           struct grub_video_i386_vbeblit_info *src,
-                                           int x, int y, int width, int height,
-                                           int offset_x, int offset_y)
+grub_video_i386_vbeblit_direct32_copy (struct grub_video_i386_vbeblit_info *dst,
+                                       struct grub_video_i386_vbeblit_info *src,
+                                       int x, int y, int width, int height,
+                                       int offset_x, int offset_y)
 {
   int j;
   grub_uint32_t *srcptr;
@@ -126,7 +252,7 @@
 }
 
 void
-grub_video_i386_vbeblit_R8G8B8_R8G8B8A8 (struct grub_video_i386_vbeblit_info *dst,
+grub_video_i386_vbeblit_RGB888_RGBA8888 (struct grub_video_i386_vbeblit_info *dst,
                                          struct grub_video_i386_vbeblit_info *src,
                                          int x, int y, int width, int height,
                                          int offset_x, int offset_y)
@@ -193,7 +319,7 @@
 }
 
 void
-grub_video_i386_vbeblit_R8G8B8_R8G8B8X8 (struct grub_video_i386_vbeblit_info *dst,
+grub_video_i386_vbeblit_RGB888_RGBX8888 (struct grub_video_i386_vbeblit_info *dst,
                                          struct grub_video_i386_vbeblit_info *src,
                                          int x, int y, int width, int height,
                                          int offset_x, int offset_y)
@@ -231,7 +357,7 @@
 }
 
 void
-grub_video_i386_vbeblit_index_R8G8B8A8 (struct grub_video_i386_vbeblit_info *dst,
+grub_video_i386_vbeblit_index_RGBA8888 (struct grub_video_i386_vbeblit_info *dst,
                                         struct grub_video_i386_vbeblit_info *src,
                                         int x, int y, int width, int height,
                                         int offset_x, int offset_y)
@@ -295,7 +421,7 @@
 }
 
 void
-grub_video_i386_vbeblit_index_R8G8B8X8 (struct grub_video_i386_vbeblit_info *dst,
+grub_video_i386_vbeblit_index_RGBX8888 (struct grub_video_i386_vbeblit_info *dst,
                                         struct grub_video_i386_vbeblit_info *src,
                                         int x, int y, int width, int height,
                                         int offset_x, int offset_y)
@@ -332,7 +458,7 @@
 }
 
 void
-grub_video_i386_vbeblit_R8G8B8A8_R8G8B8 (struct grub_video_i386_vbeblit_info *dst,
+grub_video_i386_vbeblit_RGBA8888_RGB888 (struct grub_video_i386_vbeblit_info *dst,
                                          struct grub_video_i386_vbeblit_info *src,
                                          int x, int y, int width, int height,
                                          int offset_x, int offset_y)
@@ -368,7 +494,7 @@
 }
 
 void
-grub_video_i386_vbeblit_R8G8B8_R8G8B8 (struct grub_video_i386_vbeblit_info *dst,
+grub_video_i386_vbeblit_RGB888_RGB888 (struct grub_video_i386_vbeblit_info *dst,
                                        struct grub_video_i386_vbeblit_info *src,
                                        int x, int y, int width, int height,
                                        int offset_x, int offset_y)
@@ -393,7 +519,7 @@
 }
 
 void
-grub_video_i386_vbeblit_index_R8G8B8 (struct grub_video_i386_vbeblit_info *dst,
+grub_video_i386_vbeblit_index_RGB888 (struct grub_video_i386_vbeblit_info *dst,
                                       struct grub_video_i386_vbeblit_info *src,
                                       int x, int y, int width, int height,
                                       int offset_x, int offset_y)

=== modified file 'video/i386/pc/vbefill.c'
--- video/i386/pc/vbefill.c	2007-07-21 22:32:33 +0000
+++ video/i386/pc/vbefill.c	2008-07-03 13:49:18 +0000
@@ -34,51 +34,63 @@
 #include <grub/video.h>
 
 void
-grub_video_i386_vbefill_R8G8B8A8 (struct grub_video_i386_vbeblit_info *dst,
+grub_video_i386_vbefill_direct32 (struct grub_video_i386_vbeblit_info *dst,
                                   grub_video_color_t color, int x, int y,
                                   int width, int height)
 {
   int i;
   int j;
   grub_uint32_t *dstptr;
-
-  /* We do not need to worry about data being out of bounds
-     as we assume that everything has been checked before.  */
+  grub_size_t rowskip;
+
+  /* Calculate the number of bytes to advance from the end of one line
+   * to the beginning of the next line. */
+  rowskip = dst->mode_info->pitch - dst->mode_info->bytes_per_pixel * width;
+
+  /* Get the start address. */
+  dstptr = (grub_uint32_t *) grub_video_vbe_get_video_ptr (dst, x, y);
 
   for (j = 0; j < height; j++)
     {
-      dstptr = (grub_uint32_t *)grub_video_vbe_get_video_ptr (dst, x, y + j);
-
       for (i = 0; i < width; i++)
         *dstptr++ = color;
+
+      /* Advance the dest pointer to the right location on the next line. */
+      dstptr = (grub_uint32_t *) (((char *) dstptr) + rowskip);
     }
 }
 
 void
-grub_video_i386_vbefill_R8G8B8 (struct grub_video_i386_vbeblit_info *dst,
-                                grub_video_color_t color, int x, int y,
-                                int width, int height)
+grub_video_i386_vbefill_direct24 (struct grub_video_i386_vbeblit_info *dst,
+                                  grub_video_color_t color, int x, int y,
+                                  int width, int height)
 {
   int i;
   int j;
+  grub_size_t rowskip;
   grub_uint8_t *dstptr;
-  grub_uint8_t fillr = (grub_uint8_t)((color >> 0) & 0xFF);
-  grub_uint8_t fillg = (grub_uint8_t)((color >> 8) & 0xFF);
-  grub_uint8_t fillb = (grub_uint8_t)((color >> 16) & 0xFF);
+  grub_uint8_t fill0 = (grub_uint8_t)((color >> 0) & 0xFF);
+  grub_uint8_t fill1 = (grub_uint8_t)((color >> 8) & 0xFF);
+  grub_uint8_t fill2 = (grub_uint8_t)((color >> 16) & 0xFF);
 
-  /* We do not need to worry about data being out of bounds
-     as we assume that everything has been checked before.  */
+  /* Calculate the number of bytes to advance from the end of one line
+   * to the beginning of the next line. */
+  rowskip = dst->mode_info->pitch - dst->mode_info->bytes_per_pixel * width;
+  
+  /* Get the start address. */
+  dstptr = (grub_uint8_t *) grub_video_vbe_get_video_ptr (dst, x, y);
 
   for (j = 0; j < height; j++)
     {
-      dstptr = (grub_uint8_t *)grub_video_vbe_get_video_ptr (dst, x, y + j);
-
       for (i = 0; i < width; i++)
         {
-          *dstptr++ = fillr;
-          *dstptr++ = fillg;
-          *dstptr++ = fillb;
+          *dstptr++ = fill0;
+          *dstptr++ = fill1;
+          *dstptr++ = fill2;
         }
+
+      /* Advance the dest pointer to the right location on the next line. */
+      dstptr += rowskip;
     }
 }
 
@@ -89,18 +101,24 @@
 {
   int i;
   int j;
+  grub_size_t rowskip;
   grub_uint8_t *dstptr;
   grub_uint8_t fill = (grub_uint8_t)color & 0xFF;
 
-  /* We do not need to worry about data being out of bounds
-     as we assume that everything has been checked before.  */
+  /* Calculate the number of bytes to advance from the end of one line
+   * to the beginning of the next line. */
+  rowskip = dst->mode_info->pitch - dst->mode_info->bytes_per_pixel * width;
+
+  /* Get the start address. */
+  dstptr = (grub_uint8_t *) grub_video_vbe_get_video_ptr (dst, x, y);
 
   for (j = 0; j < height; j++)
     {
-      dstptr = (grub_uint8_t *)grub_video_vbe_get_video_ptr (dst, x, y + j);
-
       for (i = 0; i < width; i++)
         *dstptr++ = fill;
+      
+      /* Advance the dest pointer to the right location on the next line. */
+      dstptr += rowskip;
     }
 }
 
@@ -112,9 +130,6 @@
   int i;
   int j;
 
-  /* We do not need to worry about data being out of bounds
-     as we assume that everything has been checked before.  */
-
   for (j = 0; j < height; j++)
     for (i = 0; i < width; i++)
       set_pixel (dst, x+i, y+j, color);

=== modified file 'video/i386/pc/vbeutil.c'
--- video/i386/pc/vbeutil.c	2007-07-21 22:32:33 +0000
+++ video/i386/pc/vbeutil.c	2008-07-03 14:10:25 +0000
@@ -52,6 +52,11 @@
             + y * source->mode_info->pitch
             + x;
       break;
+
+    /* case 1: */
+      /* For 1-bit bitmaps, addressing needs to be done at the bit level
+       * and it doesn't make sense, in general, to ask for a pointer
+       * to a particular pixel's data. */
     }
 
   return ptr;
@@ -86,6 +91,17 @@
       color = *(grub_uint8_t *)get_data_ptr (source, x, y);
       break;
 
+    case 1:
+      if (source->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_1BIT_PACKED)
+        {
+          int bit_index = y * source->mode_info->width + x;
+          grub_uint8_t *ptr = (grub_uint8_t *)source->data
+                              + bit_index / 8;
+          int bit_pos = 7 - bit_index % 8;
+          color = (*ptr >> bit_pos) & 0x01;
+        }
+      break;
+
     default:
       break;
     }
@@ -143,6 +159,17 @@
       }
       break;
 
+    case 1:
+      if (source->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_1BIT_PACKED)
+        {
+          int bit_index = y * source->mode_info->width + x;
+          grub_uint8_t *ptr = (grub_uint8_t *)source->data
+                              + bit_index / 8;
+          int bit_pos = 7 - bit_index % 8;
+          *ptr = (*ptr & ~(1 << bit_pos)) | ((color & 0x01) << bit_pos);
+        }
+      break;
+
     default:
       break;
     }

=== modified file 'video/readers/jpeg.c'
--- video/readers/jpeg.c	2008-01-22 13:50:38 +0000
+++ video/readers/jpeg.c	2008-07-03 13:49:18 +0000
@@ -542,7 +542,7 @@
 
   if (grub_video_bitmap_create (data->bitmap, data->image_width,
 				data->image_height,
-				GRUB_VIDEO_BLIT_FORMAT_R8G8B8))
+				GRUB_VIDEO_BLIT_FORMAT_RGB_888))
     return grub_errno;
 
   data->bit_mask = 0x0;

=== modified file 'video/readers/png.c'
--- video/readers/png.c	2008-03-31 11:00:48 +0000
+++ video/readers/png.c	2008-07-03 13:49:18 +0000
@@ -231,7 +231,7 @@
     {
       if (grub_video_bitmap_create (data->bitmap, data->image_width,
 				    data->image_height,
-				    GRUB_VIDEO_BLIT_FORMAT_R8G8B8))
+				    GRUB_VIDEO_BLIT_FORMAT_RGB_888))
 	return grub_errno;
       data->bpp = 3;
     }
@@ -239,7 +239,7 @@
     {
       if (grub_video_bitmap_create (data->bitmap, data->image_width,
 				    data->image_height,
-				    GRUB_VIDEO_BLIT_FORMAT_R8G8B8A8))
+				    GRUB_VIDEO_BLIT_FORMAT_RGBA_8888))
 	return grub_errno;
       data->bpp = 4;
     }

=== modified file 'video/readers/tga.c'
--- video/readers/tga.c	2007-12-30 08:52:06 +0000
+++ video/readers/tga.c	2008-07-03 13:49:18 +0000
@@ -397,7 +397,7 @@
     {
       grub_video_bitmap_create (bitmap, header.image_width,
                                 header.image_height,
-                                GRUB_VIDEO_BLIT_FORMAT_R8G8B8A8);
+                                GRUB_VIDEO_BLIT_FORMAT_RGBA_8888);
       if (grub_errno != GRUB_ERR_NONE)
         {
           grub_file_close (file);
@@ -420,7 +420,7 @@
     {
       grub_video_bitmap_create (bitmap, header.image_width,
                                 header.image_height,
-                                GRUB_VIDEO_BLIT_FORMAT_R8G8B8);
+                                GRUB_VIDEO_BLIT_FORMAT_RGB_888);
       if (grub_errno != GRUB_ERR_NONE)
         {
           grub_file_close (file);

=== modified file 'video/video.c'
--- video/video.c	2008-01-01 12:02:07 +0000
+++ video/video.c	2008-07-03 14:12:08 +0000
@@ -152,15 +152,22 @@
   if (mode_info->bpp == 32)
     {
       if ((mode_info->red_mask_size == 8)
+          && (mode_info->red_field_pos == 16)
+          && (mode_info->green_mask_size == 8)
+          && (mode_info->green_field_pos == 8)
+          && (mode_info->blue_mask_size == 8)
+          && (mode_info->blue_field_pos == 0))
+        {
+          return GRUB_VIDEO_BLIT_FORMAT_BGRA_8888;
+        }
+      if ((mode_info->red_mask_size == 8)
           && (mode_info->red_field_pos == 0)
           && (mode_info->green_mask_size == 8)
           && (mode_info->green_field_pos == 8)
           && (mode_info->blue_mask_size == 8)
-          && (mode_info->blue_field_pos == 16)
-          && (mode_info->reserved_mask_size == 8)
-          && (mode_info->reserved_field_pos == 24))
+          && (mode_info->blue_field_pos == 16))
         {
-          return GRUB_VIDEO_BLIT_FORMAT_R8G8B8A8;
+          return GRUB_VIDEO_BLIT_FORMAT_RGBA_8888;
         }
     }
 
@@ -174,7 +181,7 @@
           && (mode_info->blue_mask_size == 8)
           && (mode_info->blue_field_pos == 16))
         {
-          return GRUB_VIDEO_BLIT_FORMAT_R8G8B8;
+          return GRUB_VIDEO_BLIT_FORMAT_RGB_888;
         }
     }
 
@@ -305,6 +312,19 @@
   return grub_video_adapter_active->blit_glyph (glyph, color, x, y);
 }
 
+/* Draw string to screen using specified color and font.  */
+grub_err_t
+grub_video_draw_string (const char *str, grub_font_t font, 
+                        grub_video_color_t color, 
+                        int left_x, int baseline_y)
+{
+  if (! grub_video_adapter_active)
+    return grub_error (GRUB_ERR_BAD_DEVICE, "No video mode activated");
+
+  return grub_video_adapter_active->draw_string (str, font, color, 
+                                                 left_x, baseline_y);
+}
+
 /* Blit bitmap to screen.  */
 grub_err_t
 grub_video_blit_bitmap (struct grub_video_bitmap *bitmap,

# Begin bundle
IyBCYXphYXIgcmV2aXNpb24gYnVuZGxlIHY0CiMKQlpoOTFBWSZTWQq9+dcBJP7/gH//////////
//////////9hSbz6Z5ii9RRFx6z56752guz14dj0d7O7Pm+fVvaBpu777rvvW+H2Gl0AG9d3uT7a
o0ZKjrvm+l7dT3vbnqvY+STj5U3AD6b08+4GntqBdgaACutvNew9buOe3cXt9Hc2h2z60+gD5ByD
vsBe92971e72N6wH3vfO9q2+j4h5OtVe++3nrvsBux4vT6Huw7aCoCn3vuejrc6+3J7WW2fc0pNz
J6PvJ1i6rcx2tztvgXbveNrvfea64AL7urlZqUCSXwB9V6nHp3VTABFHpXN7sT3W2Kl92acF9309
e5W7s9Do3fOLRHnh6e73jDve6vs21jXuzvLRXrvvB73tr763Pj2YL17dwaFDZqabnA6vppVA6B99
5fXiu23o329cBaw22iltSBOjdsSqJGQBvRvoMQVQInvu+j3u452wJUaUe4Ndh9dV9ADT1gg0+7LY
fXQqXrBCkikmzHmvR1Evqqt5zX3Z1qVPneb6Gg6AAeh6PT2QejdgrOwGnkzll3c9dfT68tWzEA9O
h9ar2wB7YOhPe5trvd7tew7remvTrdj6z7ezTbfdaoLoWLcO3d2Vzz7azUOp7e5bd2BYVdO+sb3I
b32e+SeHg1b6+9vr5b58ebhrFb33nauFDesmzT3z3rd3OsjOe73YXeDyD274bFu994vtNhd7PQND
vvbfbuXvcD7sZ2AG8AaBL5x6IvsU83F8O6+++5j3JUL2a06N77G8efcc12DWq1q76Lzap1117r2L
7tjO1udsPvOe8x2rjbttypQXdxxO7AyOLqqOzG20a4GdOhSp3r775w+65A02sqkkGyxuOt1YznXY
DtbtpTdndN10doYrFwBA22tyttnLVbYjVjoKoyOHd27vXgOqbYbYIbe7Ts1UK5m6BRGs1ozoO2KH
bu1udYmN3K2kKJe+IvvsDu2632nwetVbJbYjZfOnatruW+hPt91tr3zeXe3A3Uzb33m9vbWq5tpn
dtGnDVbZ0K0u22ud10tpKGmR0DbCzGNh75rb72Dn1fdyfVPmloSRAJoAgQJoAE0BNCYE0ajJqPE1
GjI00Bk9QPSNNqephGgSmgQQQghEym0npU9oamKepoabSPSPU9QAAADR6gAAADQEgkRBE0aTJpqZ
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
F3JFOFCQCr351w==
