# Bazaar merge directive format 2 (Bazaar 0.90)
# revision_id: colin@gibibit.com-20080711163832-cutvrahspirds0fw
# target_branch: ../../repo/trunk-clean/
# testament_sha1: 25c6cfc2b182753473e3e2647dd4a223f17a28ec
# timestamp: 2008-07-11 10:09:11 -0700
# source_branch: http://grub.gibibit.com/bzr/trunk-clean
# base_revision_id: colin@gibibit.com-20080711163623-vpxrq241l75k5jed
# 
# 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-07 14:11:06 +0000
+++ ChangeLog	2008-07-11 16:37:47 +0000
@@ -12,6 +12,73 @@
 
 	* disk/ata.c: Use named constants for status bits.
 
+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-09 14:39:39 +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>
@@ -48,12 +49,22 @@
   grub_err_t err;
   char *modevar;
 
-  grub_printf ("List of compatible video modes:\n");
-
   err = grub_vbe_probe (&controller_info);
   if (err != GRUB_ERR_NONE)
     return err;
 
+  int row = 0;
+
+  grub_printf ("VBE info:   version: %d.%d  OEM software rev: %d.%d\n",
+               controller_info.version >> 8, 
+               controller_info.version & 0xFF,
+               controller_info.oem_software_rev >> 8,
+               controller_info.oem_software_rev & 0xFF);
+  row++;
+  grub_printf ("            total memory: %d KiB\n",
+               (controller_info.total_memory << 16) / 1024);
+  row++;
+
   /* Because the information on video modes is stored in a temporary place,
      it is better to copy it to somewhere safe.  */
   p = video_mode_list = real2pm (controller_info.video_mode_ptr);
@@ -67,6 +78,10 @@
 
   grub_memcpy (saved_video_mode_list, video_mode_list, video_mode_list_size);
   
+  grub_printf ("List of compatible video modes:\n");
+  grub_printf ("Legend: P=Packed pixel, D=Direct color, "
+               "mask/pos=R/G/B/reserved\n"); 
+
   /* Walk through all video modes listed.  */
   for (p = saved_video_mode_list; *p != 0xFFFF; p++)
     {
@@ -103,10 +118,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 +131,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-09 16:52:26 +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,38 @@
 #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;
+};
+
+
+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 +68,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 +90,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 +109,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 +133,1076 @@
                                      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 (0), 0},
+  {640, 480, MODE_TYPE_RGB (0), 0},
+  {1024, 768, MODE_TYPE_RGB (0), 0},
+  {1024, 768, MODE_TYPE_RGB (0), 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 * 10 to achieve one decimal place. */
+  /* 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 10
+
+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_uint64_t start_time;
+  grub_uint64_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_time_ms ();
+  desired_stop_time = start_time + vt_opts->test_time * 1000;
+
+  while (grub_get_time_ms () < 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_time_ms () - start_time;
+  if (duration == 0)
+    {
+      result->fill_fps = 0;
+    }
+  else
+    {
+      result->fill_fps =
+        (BENCHMARK_RESULT_FPS_SCALE * 1000
+         * 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_time_ms ();
+  desired_stop_time = start_time + vt_opts->test_time * 1000;
+
+  int cur = 0;                  /* Which bitmap to draw this frame. */
+  while (grub_get_time_ms () < 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_time_ms () - start_time;
+  if (duration == 0)
+    {
+      result->blit_fps = 0;
+    }
+  else
+    {
+      result->blit_fps =
+        (BENCHMARK_RESULT_FPS_SCALE * 1000
+         * 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_time_ms ();
+  desired_stop_time = start_time + vt_opts->test_time * 1000;
+
+
+  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_time_ms () < 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_time_ms () - start_time;
+  if (duration == 0)
+    {
+      result->blend_fps = 0;
+    }
+  else
+    {
+      result->blend_fps =
+        (BENCHMARK_RESULT_FPS_SCALE * 1000
+         * 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++)
+    {
+      grub_error_push ();
+      do_benchmark (&benchmark_configs[i], &results[i], vt_opts);
+    }
+
+  grub_print_error ();
+
+  /* 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.%d  %4d.%d  %4d.%d\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-07 15:07:13 +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/widget-box.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-10 13:55:34 +0000
@@ -43,12 +43,16 @@
 	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 \
+	kern/menu_viewer.c \
 	term/i386/pc/console.c \
 	symlist.c
 kernel_img_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 \
+	menu_viewer.h \
 	machine/biosdisk.h machine/boot.h machine/console.h machine/init.h \
 	machine/memory.h machine/loader.h machine/vga.h machine/vbe.h machine/kernel.h
 kernel_img_CFLAGS = $(COMMON_CFLAGS)
@@ -256,7 +260,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-09 17:27:53 +0000
@@ -25,55 +25,93 @@
 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_BGRA8888_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_BGR888_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_BGR888_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_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 */

=== added file 'include/grub/menu.h'
--- include/grub/menu.h	1970-01-01 00:00:00 +0000
+++ include/grub/menu.h	2008-07-10 13:55:34 +0000
@@ -0,0 +1,51 @@
+/* menu.h - menu and menu entry model declarations. */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2002,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_MENU_HEADER
+#define GRUB_MENU_HEADER 1
+
+/* The menu entry.  */
+struct grub_menu_entry
+{
+  /* The title name.  */
+  const char *title;
+
+    /* The commands associated with this menu entry.  */
+  struct grub_script *commands;
+
+  /* The sourcecode of the menu entry, used by the editor.  */
+  const char *sourcecode;
+
+  /* The next element.  */
+  struct grub_menu_entry *next;
+};
+typedef struct grub_menu_entry *grub_menu_entry_t;
+
+/* The menu.  */
+struct grub_menu
+{
+  /* The size of a menu.  */
+  int size;
+
+  /* The list of menu entries.  */
+  grub_menu_entry_t entry_list;
+};
+typedef struct grub_menu *grub_menu_t;
+
+#endif /* GRUB_MENU_HEADER */

=== added file 'include/grub/menu_viewer.h'
--- include/grub/menu_viewer.h	1970-01-01 00:00:00 +0000
+++ include/grub/menu_viewer.h	2008-07-11 14:56:59 +0000
@@ -0,0 +1,48 @@
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2002,2003,2005,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_MENU_VIEWER_HEADER
+#define GRUB_MENU_VIEWER_HEADER 1
+
+#include <grub/err.h>
+#include <grub/symbol.h>
+#include <grub/types.h>
+#include <grub/menu.h>
+
+struct grub_menu_viewer
+{
+  /* The menu viewer name.  */
+  const char *name;
+
+  grub_err_t (*show_menu) (grub_menu_t menu, int nested);
+
+  struct grub_menu_viewer *next;
+};
+typedef struct grub_menu_viewer *grub_menu_viewer_t;
+
+void EXPORT_FUNC(grub_menu_viewer_init) (void);
+
+void EXPORT_FUNC(grub_menu_viewer_register) (grub_menu_viewer_t viewer);
+
+grub_err_t EXPORT_FUNC(grub_menu_viewer_show_menu) (grub_menu_t menu, int nested);
+
+/* Return nonzero iff the menu viewer should clean up and return ASAP. */
+int EXPORT_FUNC(grub_menu_viewer_should_return) (void);
+
+#endif /* GRUB_MENU_VIEWER_HEADER */
+

=== modified file 'include/grub/normal.h'
--- include/grub/normal.h	2008-03-26 12:01:02 +0000
+++ include/grub/normal.h	2008-07-11 16:03:24 +0000
@@ -25,6 +25,7 @@
 #include <grub/err.h>
 #include <grub/arg.h>
 #include <grub/env.h>
+#include <grub/menu.h>
 
 /* The maximum size of a command-line.  */
 #define GRUB_MAX_CMDLINE	1600
@@ -84,34 +85,6 @@
 };
 typedef struct grub_command *grub_command_t;
 
-/* The menu entry.  */
-struct grub_menu_entry
-{
-  /* The title name.  */
-  const char *title;
-
-    /* The commands associated with this menu entry.  */
-  struct grub_script *commands;
-
-  /* The sourcecode of the menu entry, used by the editor.  */
-  const char *sourcecode;
-
-  /* The next element.  */
-  struct grub_menu_entry *next;
-};
-typedef struct grub_menu_entry *grub_menu_entry_t;
-
-/* The menu.  */
-struct grub_menu
-{
-  /* The size of a menu.  */
-  int size;
-
-  /* The list of menu entries.  */
-  grub_menu_entry_t entry_list;
-};
-typedef struct grub_menu *grub_menu_t;
-
 /* This is used to store the names of filesystem modules for auto-loading.  */
 struct grub_fs_module_list
 {
@@ -123,10 +96,12 @@
 /* To exit from the normal mode.  */
 extern grub_jmp_buf grub_exit_env;
 
+extern struct grub_menu_viewer grub_normal_terminal_menu_viewer;
+
 void grub_enter_normal_mode (const char *config);
 void grub_normal_execute (const char *config, int nested);
-void grub_menu_run (grub_menu_t menu, int nested);
 void grub_menu_entry_run (grub_menu_entry_t entry);
+void grub_menu_execute_entry(grub_menu_entry_t entry);
 void grub_cmdline_run (int nested);
 int grub_cmdline_get (const char *prompt, char cmdline[], unsigned max_len,
 		      int echo_char, int readline);

=== added file 'include/grub/proto_widgets.h'
--- include/grub/proto_widgets.h	1970-01-01 00:00:00 +0000
+++ include/grub/proto_widgets.h	2008-07-08 13:59:48 +0000
@@ -0,0 +1,48 @@
+/* proto_widgets.h - Widgets for the graphical menu prototype.  */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 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_PROTO_WIDGETS_HEADER
+#define GRUB_PROTO_WIDGETS_HEADER 1
+
+#include <grub/video.h>
+
+typedef struct grub_protomenu_widgets_box
+{
+  /* The size of the content.  */
+  int width;
+  int height;
+
+  struct grub_video_bitmap **raw_pixmaps;
+  struct grub_video_bitmap **scaled_pixmaps;
+
+  void (*draw) (struct grub_protomenu_widgets_box *self, int x, int y);
+  void (*set_content_size) (struct grub_protomenu_widgets_box *self,
+                            int width, int height);
+  int (*get_left_pad) (struct grub_protomenu_widgets_box *self);
+  int (*get_top_pad) (struct grub_protomenu_widgets_box *self);
+  void (*destroy) (struct grub_protomenu_widgets_box *self);
+}
+*grub_protomenu_widgets_box_t;
+
+grub_protomenu_widgets_box_t
+grub_protomenu_widgets_create_box (const char *pixmaps_prefix,
+                                   const char *pixmaps_suffix);
+
+#endif /* ! GRUB_PROTO_WIDGETS_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-07 15:07:13 +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
@@ -137,6 +151,16 @@
   grub_uint8_t a; /* Reserved bits value (0-255).  */
 };
 
+/* A 2D rectangle type.  */
+struct grub_video_rect
+{
+  int x;
+  int y;
+  int width;
+  int height;
+};
+typedef struct grub_video_rect grub_video_rect_t;
+
 struct grub_video_adapter
 {
   /* The video adapter name.  */
@@ -183,6 +207,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 +283,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 +301,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)
 {

=== added file 'kern/menu_viewer.c'
--- kern/menu_viewer.c	1970-01-01 00:00:00 +0000
+++ kern/menu_viewer.c	2008-07-11 14:56:59 +0000
@@ -0,0 +1,99 @@
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2002,2003,2005,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/env.h>
+#include <grub/menu_viewer.h>
+#include <grub/menu.h>
+
+/* The list of menu viewers.  */
+static grub_menu_viewer_t menu_viewer_list;
+
+static int should_return;
+static int menu_viewer_changed;
+
+void
+grub_menu_viewer_register (grub_menu_viewer_t viewer)
+{
+  viewer->next = menu_viewer_list;
+  menu_viewer_list = viewer;
+}
+
+static grub_menu_viewer_t get_current_menu_viewer (void)
+{
+  const char *selected_name = grub_env_get ("menuviewer");
+
+  /* If none selected, pick the last registered one. */
+  if (selected_name == 0)
+    return menu_viewer_list;
+
+  grub_menu_viewer_t cur;
+  for (cur = menu_viewer_list; cur; cur = cur->next)
+    {
+      if (grub_strcmp (cur->name, selected_name) == 0)
+        return cur;
+    }
+
+  /* Fall back to the first entry (or null).  */
+  return menu_viewer_list;
+}
+
+grub_err_t
+grub_menu_viewer_show_menu (grub_menu_t menu, int nested)
+{
+  grub_err_t err;
+  int repeat = 0;
+  do
+    {
+      repeat = 0;
+      menu_viewer_changed = 0;
+      grub_menu_viewer_t cur = get_current_menu_viewer ();
+      if (!cur) 
+        return grub_error (GRUB_ERR_BAD_ARGUMENT, "No menu viewer available.");
+
+      should_return = 0;
+      err = cur->show_menu (menu, nested);
+      if (menu_viewer_changed)
+        repeat = 1;
+    }
+  while (repeat);
+  return err;
+}
+
+int
+grub_menu_viewer_should_return (void)
+{
+  return should_return;
+}
+
+static char *
+menuviewer_write_hook (struct grub_env_var *var __attribute__ ((unused)),
+                       const char *val)
+{
+  menu_viewer_changed = 1;
+  should_return = 1;
+  return grub_strdup (val);
+}
+
+void
+grub_menu_viewer_init (void)
+{
+  grub_register_variable_hook ("menuviewer", 0, menuviewer_write_hook);
+}
+

=== 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)
 {

=== modified file 'normal/main.c'
--- normal/main.c	2008-02-02 16:48:52 +0000
+++ normal/main.c	2008-07-11 14:56:59 +0000
@@ -28,6 +28,7 @@
 #include <grub/env.h>
 #include <grub/parser.h>
 #include <grub/script.h>
+#include <grub/menu_viewer.h>
 
 grub_jmp_buf grub_exit_env;
 
@@ -476,7 +477,7 @@
 
   if (menu && menu->size)
     {
-      grub_menu_run (menu, nested);
+      grub_menu_viewer_show_menu (menu, nested);
       if (nested)
 	free_menu (menu);
     }
@@ -519,6 +520,8 @@
   if (mod)
     grub_dl_ref (mod);
 
+  grub_menu_viewer_register (&grub_normal_terminal_menu_viewer);
+
   grub_set_history (GRUB_DEFAULT_HISTORY_SIZE);
 
   /* Register a command "normal" for the rescue mode.  */
@@ -535,6 +538,8 @@
 
   /* This registers some built-in commands.  */
   grub_command_init ();
+
+  grub_menu_viewer_init ();
 }
 
 GRUB_MOD_FINI(normal)

=== modified file 'normal/menu.c'
--- normal/menu.c	2008-02-09 11:00:19 +0000
+++ normal/menu.c	2008-07-11 16:03:24 +0000
@@ -24,6 +24,7 @@
 #include <grub/machine/time.h>
 #include <grub/env.h>
 #include <grub/script.h>
+#include <grub/menu_viewer.h>
 
 static grub_uint8_t grub_color_menu_normal;
 static grub_uint8_t grub_color_menu_highlight;
@@ -364,7 +365,7 @@
   if (timeout > 0)
     print_timeout (timeout, offset, 0);
 
-  while (1)
+  while (!grub_menu_viewer_should_return ())
     {
       int c;
       timeout = get_timeout ();
@@ -468,6 +469,10 @@
 		}
 	      goto refresh;
 	      
+	    case 't':
+	      grub_env_set ("menuviewer", "protomenu");
+	      goto refresh;
+
 	    default:
 	      break;
 	    }
@@ -476,13 +481,14 @@
 	}
     }
 
-  /* Never reach here.  */
+  /* Exit menu without activating an item.  This occurs if the user presses
+   * 't', switching to the graphical menu viewer.  */
   return -1;
 }
 
 /* Run a menu entry.  */
-static void
-run_menu_entry (grub_menu_entry_t entry)
+void
+grub_menu_execute_entry(grub_menu_entry_t entry)
 {
   grub_script_execute (entry->commands);
   
@@ -491,8 +497,8 @@
     grub_command_execute ("boot", 0);
 }
 
-void
-grub_menu_run (grub_menu_t menu, int nested)
+static grub_err_t
+show_menu (grub_menu_t menu, int nested)
 {
   while (1)
     {
@@ -513,7 +519,7 @@
 
       grub_printf ("  Booting \'%s\'\n\n", e->title);
   
-      run_menu_entry (e);
+      grub_menu_execute_entry (e);
 
       /* Deal with a fallback entry.  */
       /* FIXME: Multiple fallback entries like GRUB Legacy.  */
@@ -526,7 +532,7 @@
 	  e = get_entry (menu, fallback_entry);
 	  grub_env_unset ("fallback");
 	  grub_printf ("\n  Falling back to \'%s\'\n\n", e->title);
-	  run_menu_entry (e);
+	  grub_menu_execute_entry (e);
 	}
 
       if (grub_errno != GRUB_ERR_NONE)
@@ -537,4 +543,14 @@
 	  grub_wait_after_message ();
 	}
     }
+
+  return GRUB_ERR_NONE;
 }
+
+struct grub_menu_viewer grub_normal_terminal_menu_viewer =
+{
+  .name = "terminal",
+  .show_menu = show_menu
+};
+
+

=== added directory 'protomenu'
=== added file 'protomenu/protomenu.c'
--- protomenu/protomenu.c	1970-01-01 00:00:00 +0000
+++ protomenu/protomenu.c	2008-07-11 16:03:24 +0000
@@ -0,0 +1,417 @@
+/* protomenu.c - Prototype graphical menu: command 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/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>
+#include <grub/proto_widgets.h>
+#include <grub/menu.h>
+#include <grub/menu_viewer.h>
+
+typedef struct menu_item
+{
+  const char *name;
+  struct grub_video_bitmap *icon;
+  grub_menu_entry_t entry;
+}
+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 int num_items;
+static menu_item_t *items;
+static int selected_item_index;
+static grub_protomenu_widgets_box_t menu_box;
+static grub_protomenu_widgets_box_t selected_item_box;
+
+static grub_err_t
+menu_init (grub_menu_t menu)
+{
+  num_items = menu->size;
+  if (num_items < 1)
+    return grub_error (GRUB_ERR_MENU, "Empty menu");
+
+  items = grub_malloc (num_items * sizeof (*items));
+  if (!items)
+    return grub_errno;
+
+  int i;
+  grub_menu_entry_t cur;
+  for (i = 0, cur = menu->entry_list; i < num_items; i++, cur = cur->next)
+    {
+        items[i].name = cur->title;
+        items[i].icon = 0;  /* TODO match w/ patterns to choose an icon */
+        items[i].entry = cur;
+    }
+
+  /*
+  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);
+    }
+  */
+
+  return GRUB_ERR_NONE;
+}
+
+static void
+menu_destroy (void)
+{
+  if (items)
+    {
+      int i;
+      for (i = 0; i < num_items; i++)
+        {
+          if (items[i].icon)
+            {
+              grub_video_bitmap_destroy (items[i].icon);
+              items[i].icon = 0;
+            }
+        }
+      grub_free (items);
+      items = 0;
+    }
+}
+
+static grub_err_t
+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 grub_errno;
+
+  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_errno;
+
+  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_errno;
+  grub_video_bitmap_destroy (img);
+
+  menu_box = grub_protomenu_widgets_create_box ("/boot/images/menubox_", ".tga");
+  if (menu_box == 0)
+      return grub_error (GRUB_ERR_MENU, "Unable to create menu box");
+
+  selected_item_box = grub_protomenu_widgets_create_box ("/boot/images/select_blue_", ".tga");
+  if (selected_item_box == 0)
+      return grub_error (GRUB_ERR_MENU, "Unable to create selection box");
+
+  return GRUB_ERR_NONE;
+}
+
+static void
+style_destroy (void)
+{
+  if (menu_box) 
+    {
+      menu_box->destroy(menu_box);
+      menu_box = 0;
+    }
+  if (selected_item_box) 
+    {
+      selected_item_box->destroy(selected_item_box);
+      selected_item_box = 0;
+    }
+}
+
+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)
+{
+  int boxpad = 14;
+  int icon_text_space = 4;
+  int item_vspace = 16;
+
+  int ascent = grub_font_get_ascent (item_font);
+  int descent = grub_font_get_descent (item_font);
+  int item_height = 52;
+
+  grub_video_rect_t r;
+  r.width = screen.width * 2 / 3;
+  /* Set the menu box height to fit the items.  */
+  r.height = (item_height * num_items 
+              + item_vspace * (num_items - 1) 
+              + 2 * boxpad);
+  r.x = (screen.width - r.width) / 2;
+  r.y = (screen.height - r.height) / 2;
+  menu_box->set_content_size (menu_box, r.width, r.height);
+
+  int menu_box_left_pad = menu_box->get_left_pad (menu_box);
+  int menu_box_top_pad = menu_box->get_top_pad (menu_box);
+  menu_box->draw (menu_box, r.x - menu_box_left_pad, r.y - menu_box_top_pad);
+
+  int item_top = r.y + boxpad;
+  int item_left = r.x + boxpad;
+  int i;
+
+  for (i = 0; i < num_items; i++)
+    {
+      if (i == selected_item_index)
+        {
+          selected_item_box->set_content_size (selected_item_box,
+                                               r.width - 2 * boxpad,
+                                               item_height);
+          int leftpad = selected_item_box->get_left_pad (selected_item_box);
+          int toppad = selected_item_box->get_top_pad (selected_item_box);
+          selected_item_box->draw (selected_item_box, 
+                                   item_left - leftpad, 
+                                   item_top - toppad);
+        }
+
+      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 + 1;
+  int h = 2 * vpad + 2 * textheight;
+
+  grub_video_fill_rect (status_bg_color, 0, screen.height - h,
+                        screen.width, screen.height - 1);
+
+  int texty = screen.height - h + vpad + ascent;
+  grub_video_draw_string ("Select an item with the arrow keys and "
+                          "press Enter to boot.",
+                          status_font, status_color, 30, texty);
+  texty += textheight;
+  grub_video_draw_string ("Press:  'c' for command line; 't' to switch to "
+                          "non-graphical menu.",
+                          status_font, status_color, 30, texty);
+
+}
+
+static grub_err_t
+set_graphics_mode (void)
+{
+  const char *nodoublebuf_str = grub_env_get ("protomenu_nodoublebuf");
+  int doublebuf_flags =
+    (nodoublebuf_str && nodoublebuf_str[0] == 'y')
+    ? 0
+    : GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED;
+
+  if (grub_video_setup (1024, 768, GRUB_VIDEO_MODE_TYPE_RGB | doublebuf_flags) 
+      != GRUB_ERR_NONE)
+    return grub_errno;
+
+  return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+set_text_mode (void)
+{
+  return grub_video_restore ();
+}
+
+/* TODO set the 'terminal' type to console for now, here.
+   (until gfxterm use is integrated). */
+static grub_err_t
+show_menu (grub_menu_t menu, int nested __attribute__ ((unused)))
+{
+  set_graphics_mode ();
+
+  grub_error_push ();
+  grub_video_get_viewport ((unsigned *) &screen.x,
+                           (unsigned *) &screen.y,
+                           (unsigned *) &screen.width,
+                           (unsigned *) &screen.height);
+
+  /* Clear the screen; there may be garbage left over in video memory, and
+     loading the menu style (particularly the background) can take a while. */
+  grub_video_fill_rect (grub_video_map_rgb (100, 100, 100),
+                        screen.x, screen.y, screen.width, screen.height);
+  grub_video_swap_buffers ();
+
+  if (menu_init (menu) != GRUB_ERR_NONE)
+    goto menufail;
+
+  if (style_init () != GRUB_ERR_NONE)
+    goto stylefail;
+
+  selected_item_index = 0;
+  int done = 0;
+  while (!done && !grub_menu_viewer_should_return ())
+    {
+      draw_background ();
+      draw_menu ();
+      draw_title ();
+      draw_status ();
+      grub_video_swap_buffers ();
+
+      int c = GRUB_TERM_ASCII_CHAR (grub_getkey ());
+      if (c == 'j' || c == 14)
+        {
+          selected_item_index++;
+          if (selected_item_index >= num_items)
+            selected_item_index = 0;
+        }
+      else if (c == 'k' || c == 16)
+        {
+          selected_item_index--;
+          if (selected_item_index < 0)
+            selected_item_index = num_items - 1;
+        }
+      else if (c == '\r' || c == '\n' || c == 6)
+        {
+          if (selected_item_index >=0 && selected_item_index < num_items)
+            {
+              menu_item_t *item = &items[selected_item_index];
+              if (item->entry != 0)
+                {
+                  set_text_mode ();
+                  grub_menu_execute_entry (item->entry);
+                  if (grub_errno != GRUB_ERR_NONE)
+                    grub_wait_after_message ();
+                  if (set_graphics_mode () != GRUB_ERR_NONE)
+                    done = 1;
+                }
+            }
+        }
+      else if (c == 'c')
+        {
+          set_text_mode ();
+          grub_cmdline_run (1);
+          if (grub_errno != GRUB_ERR_NONE)
+            grub_wait_after_message ();
+          if (set_graphics_mode () != GRUB_ERR_NONE)
+            done = 1;
+        }
+      else if (c == 't')
+        {
+          /* The write hook for 'menuviewer' will cause 
+           * grub_menu_viewer_should_return to return nonzero. */
+          grub_env_set ("menuviewer", "terminal");
+        }
+      else if (nested && c == GRUB_TERM_ESC)
+        {
+          done = 1;
+        }
+    }
+
+  style_destroy ();
+stylefail:
+  menu_destroy ();
+menufail:
+  set_text_mode ();
+
+  grub_print_error ();
+
+  return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+grub_cmd_protomenu (struct grub_arg_list *state __attribute__ ((unused)),
+                    int argc __attribute__ ((unused)),
+                    char **args __attribute__ ((unused)))
+{
+  grub_menu_t menu = grub_env_get_data_slot ("menu");
+  if (!menu)
+    return grub_error (GRUB_ERR_MENU, "No menu context");
+
+  show_menu (menu, 1);
+
+  return 0;
+}
+
+static struct grub_menu_viewer menu_viewer =
+{
+  .name = "protomenu",
+  .show_menu = show_menu
+};
+
+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_menu_viewer_register (&menu_viewer);
+}
+
+GRUB_MOD_FINI (protomenu)
+{
+  grub_unregister_command ("protomenu");
+}

=== added file 'protomenu/widget-box.c'
--- protomenu/widget-box.c	1970-01-01 00:00:00 +0000
+++ protomenu/widget-box.c	2008-07-08 13:59:48 +0000
@@ -0,0 +1,245 @@
+/* widget_box.c - Pixmap-stylized box widget. */
+/*
+ *  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/misc.h>
+#include <grub/mm.h>
+#include <grub/err.h>
+#include <grub/video.h>
+#include <grub/bitmap.h>
+#include <grub/bitmap_scale.h>
+#include <grub/proto_widgets.h>
+
+enum box_pixmaps
+{
+  BOX_PIXMAP_NW, BOX_PIXMAP_NE, BOX_PIXMAP_SE, BOX_PIXMAP_SW,
+  BOX_PIXMAP_N, BOX_PIXMAP_E, BOX_PIXMAP_S, BOX_PIXMAP_W,
+  BOX_PIXMAP_CENTER
+};
+
+static const char *box_pixmap_names[] = {
+  /* Corners: */
+  "nw", "ne", "se", "sw",
+  /* Sides: */
+  "n", "e", "s", "w",
+  /* Center: */
+  "c"
+};
+
+#define BOX_NUM_PIXMAPS (sizeof(box_pixmap_names)/sizeof(*box_pixmap_names))
+
+static void
+draw (struct grub_protomenu_widgets_box *self, int x, int y)
+{
+  int height_n;
+  int height_s;
+  int height_e;
+  int height_w;
+  int width_n;
+  int width_s;
+  int width_e;
+  int width_w;
+  unsigned i;
+
+  /* Don't try to draw if any pixmaps are null, except the center. */
+  for (i = 0; i < BOX_NUM_PIXMAPS; i++)
+    {
+      if (i != BOX_PIXMAP_CENTER && self->scaled_pixmaps[i] == 0)
+        return;
+    }
+
+  height_n = grub_video_bitmap_get_height (self->scaled_pixmaps[BOX_PIXMAP_N]);
+  height_s = grub_video_bitmap_get_height (self->scaled_pixmaps[BOX_PIXMAP_S]);
+  height_e = grub_video_bitmap_get_height (self->scaled_pixmaps[BOX_PIXMAP_E]);
+  height_w = grub_video_bitmap_get_height (self->scaled_pixmaps[BOX_PIXMAP_W]);
+  width_n = grub_video_bitmap_get_width (self->scaled_pixmaps[BOX_PIXMAP_N]);
+  width_s = grub_video_bitmap_get_width (self->scaled_pixmaps[BOX_PIXMAP_S]);
+  width_e = grub_video_bitmap_get_width (self->scaled_pixmaps[BOX_PIXMAP_E]);
+  width_w = grub_video_bitmap_get_width (self->scaled_pixmaps[BOX_PIXMAP_W]);
+
+  /* Draw sides.  */
+  grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_N], 
+                          GRUB_VIDEO_BLIT_BLEND,
+                          x + width_w, y, 0, 0, width_n, height_n);
+  grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_S],
+                          GRUB_VIDEO_BLIT_BLEND,
+                          x + width_w, y + height_n + height_w,
+                          0, 0, width_s, height_s);
+  grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_E],
+                          GRUB_VIDEO_BLIT_BLEND,
+                          x + width_w + width_n, y + height_n,
+                          0, 0, width_e, height_e);
+  grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_W],
+                          GRUB_VIDEO_BLIT_BLEND,
+                          x, y + height_n, 0, 0, width_w, height_w);
+
+  /* Draw corners.  */
+  grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_NW],
+                          GRUB_VIDEO_BLIT_BLEND,
+                          x, y, 0, 0, width_w, height_n);
+  grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_NE],
+                          GRUB_VIDEO_BLIT_BLEND,
+                          x + width_w + width_n, y, 
+                          0, 0, width_e, height_n);
+  grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_SE],
+                          GRUB_VIDEO_BLIT_BLEND,
+                          x + width_w + width_n, y + height_n + height_e, 
+                          0, 0, width_e, height_s);
+  grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_SW],
+                          GRUB_VIDEO_BLIT_BLEND,
+                          x, y + height_n + height_w, 
+                          0, 0, width_w, height_s);
+
+  /* Draw center.  */
+  if (self->scaled_pixmaps[BOX_PIXMAP_CENTER])
+    grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_CENTER],
+                            GRUB_VIDEO_BLIT_BLEND,
+                            x + width_w, y + height_n,
+                            0, 0, self->width, self->height);
+}
+
+static void
+scale_pixmap (grub_protomenu_widgets_box_t self, int i, int w, int h)
+{
+  struct grub_video_bitmap **scaled = &self->scaled_pixmaps[i];
+  struct grub_video_bitmap *raw = self->raw_pixmaps[i];
+
+  if (raw == 0)
+    return;
+
+  if (w == -1)
+    w = grub_video_bitmap_get_width (raw);
+  if (h == -1)
+    h = grub_video_bitmap_get_height (raw);
+
+  if (*scaled == 0 
+      || ((int) grub_video_bitmap_get_width (*scaled) != w)
+      || ((int) grub_video_bitmap_get_height (*scaled) != h))
+    {
+      if (*scaled)
+        {
+          grub_video_bitmap_destroy (*scaled);
+          *scaled = 0;
+        }
+      grub_video_bitmap_create_scaled (scaled, w, h, raw, 
+                                       GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST);
+    }
+}
+
+static void
+set_content_size (grub_protomenu_widgets_box_t self,
+                  int width, int height)
+{
+  self->width = width;
+  self->height = height;
+  /* Resize sides to match the width and height.  */
+  /* It is assumed that the corners width/height match the adjacent sides.  */
+
+  /* Resize N and S sides to match width.  */
+  scale_pixmap(self, BOX_PIXMAP_N, width, -1);
+  scale_pixmap(self, BOX_PIXMAP_S, width, -1);
+
+  /* Resize E and W sides to match height.  */
+  scale_pixmap(self, BOX_PIXMAP_E, -1, height);
+  scale_pixmap(self, BOX_PIXMAP_W, -1, height);
+
+  /* Don't scale the corners--they are assumed to match the sides. */
+  scale_pixmap(self, BOX_PIXMAP_NW, -1, -1);
+  scale_pixmap(self, BOX_PIXMAP_SW, -1, -1);
+  scale_pixmap(self, BOX_PIXMAP_NE, -1, -1);
+  scale_pixmap(self, BOX_PIXMAP_SE, -1, -1);
+
+  /* Scale the center area. */
+  scale_pixmap(self, BOX_PIXMAP_CENTER, width, height);
+}
+
+static int
+get_left_pad (struct grub_protomenu_widgets_box *self)
+{
+  return grub_video_bitmap_get_width (self->raw_pixmaps[BOX_PIXMAP_W]);
+}
+
+static int
+get_top_pad (struct grub_protomenu_widgets_box *self)
+{
+  return grub_video_bitmap_get_height (self->raw_pixmaps[BOX_PIXMAP_N]);
+}
+
+static void
+destroy (struct grub_protomenu_widgets_box *self)
+{
+  unsigned i;
+  for (i = 0; i < BOX_NUM_PIXMAPS; i++)
+    {
+      if (self->raw_pixmaps[i])
+        grub_video_bitmap_destroy(self->raw_pixmaps[i]);
+      self->raw_pixmaps[i] = 0;
+
+      if (self->scaled_pixmaps[i])
+        grub_video_bitmap_destroy(self->scaled_pixmaps[i]);
+      self->scaled_pixmaps[i] = 0;
+    }
+  grub_free (self->raw_pixmaps);
+  self->raw_pixmaps = 0;
+  grub_free (self->scaled_pixmaps);
+  self->scaled_pixmaps = 0;
+
+  grub_free (self);             /* Free self:  must be the last step! */
+}
+
+
+grub_protomenu_widgets_box_t
+grub_protomenu_widgets_create_box (const char *pixmaps_prefix,
+                                   const char *pixmaps_suffix)
+{
+  char path[200];
+  unsigned i;
+  grub_protomenu_widgets_box_t box;
+
+  box = (grub_protomenu_widgets_box_t) grub_malloc (sizeof (*box));
+  if (!box)
+    return 0;
+  box->width = 0;
+  box->height = 0;
+  box->raw_pixmaps =
+    (struct grub_video_bitmap **) grub_malloc (BOX_NUM_PIXMAPS *
+                                               sizeof (struct grub_video_bitmap
+                                                       *));
+  box->scaled_pixmaps =
+    (struct grub_video_bitmap **) grub_malloc (BOX_NUM_PIXMAPS *
+                                               sizeof (struct grub_video_bitmap
+                                                       *));
+
+  for (i = 0; i < BOX_NUM_PIXMAPS; i++)
+    {
+      /* TODO dynamically allocate PATH, making sure it's large enough */
+      grub_sprintf (path, "%s%s%s",
+                    pixmaps_prefix, box_pixmap_names[i], pixmaps_suffix);
+      grub_video_bitmap_load (&box->raw_pixmaps[i], path);
+      box->scaled_pixmaps[i] = 0;
+    }
+
+  box->draw = draw;
+  box->set_content_size = set_content_size;
+  box->get_left_pad = get_left_pad;
+  box->get_top_pad = get_top_pad;
+  box->destroy = destroy;
+
+  return box;
+}

=== 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 'util/i386/pc/grub-mkrescue.in'
--- util/i386/pc/grub-mkrescue.in	2008-04-18 15:42:57 +0000
+++ util/i386/pc/grub-mkrescue.in	2008-07-10 15:05:44 +0000
@@ -71,7 +71,7 @@
     --modules=*)
 	modules=`echo "$option" | sed 's/--modules=//'` ;;
     --overlay=*)
-	overlay=`echo "$option" | sed 's/--overlay=//'` ;;
+	overlay=${overlay}${overlay:+ }`echo "$option" | sed 's/--overlay=//'` ;;
     --pkglibdir=*)
 	input_dir=`echo "$option" | sed 's/--pkglibdir=//'` ;;
     --grub-mkimage=*)
@@ -124,9 +124,10 @@
   echo "insmod $i"
 done > ${aux_dir}/boot/grub/grub.cfg
 
-if test "x$overlay" = x ; then : ; else
-  cp -dpR ${overlay}/* ${aux_dir}/
-fi
+for d in ${overlay}; do
+  echo "Overlaying $d"
+  cp -vdpR ${d}/* ${aux_dir}/
+done
 
 if [ "x${image_type}" = xfloppy -o "x${emulation}" = xfloppy ] ; then
   # build memdisk

=== 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-10 14:59:42 +0000
@@ -63,6 +63,7 @@
 static struct
 {
   struct grub_video_render_target render_target;
+  int is_double_buffered;       /* Is the video mode double buffered? */
 
   unsigned int bytes_per_scan_line;
   unsigned int bytes_per_pixel;
@@ -77,6 +78,24 @@
 static grub_uint32_t mode_in_use = 0x55aa;
 static grub_uint16_t *mode_list;
 
+static struct 
+{
+  grub_size_t page_size;        /* The size of a page in bytes. */
+
+  /* For page flipping strategy. */
+  int displayed_page;           /* The page # that is the front buffer. */
+  int render_page;              /* The page # that is the back buffer. */
+
+  /* For blit strategy. */
+  grub_uint8_t *offscreen_buffer;
+
+  /* Virtual functions. */
+  int (*update_screen) (void);
+  int (*destroy) (void);
+} doublebuf_state;
+
+static void double_buffering_init (void);
+
 static void *
 real2pm (grub_vbe_farptr_t ptr)
 {
@@ -376,6 +395,7 @@
   /* Reset frame buffer and render target variables.  */
   grub_memset (&framebuffer, 0, sizeof(framebuffer));
   render_target = &framebuffer.render_target;
+  grub_memset (&doublebuf_state, 0, sizeof(doublebuf_state));
 
   return GRUB_ERR_NONE;
 }
@@ -391,6 +411,9 @@
     /* TODO: Decide, is this something we want to do.  */
     return grub_errno;
 
+  if (doublebuf_state.destroy)
+    doublebuf_state.destroy();
+
   /* TODO: Free any resources allocated by driver.  */
   grub_free (mode_list);
   mode_list = 0;
@@ -533,9 +556,13 @@
       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.  */
+      /* Mark framebuffer memory as non allocated.  */
       render_target->is_allocated = 0;
-      render_target->data = framebuffer.ptr;
+
+      /* Set up double buffering information. */
+      framebuffer.is_double_buffered =
+        ((mode_type & GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED) != 0);
+      double_buffering_init ();
 
       /* Copy default palette to initialize emulated palette.  */
       for (i = 0; 
@@ -556,6 +583,159 @@
   return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "no matching mode found.");
 }
 
+/* 
+   Set framebuffer render target page and display the proper page, based on
+   `doublebuf_state.render_page' and `doublebuf_state.displayed_page',
+   respectively. 
+
+   Returns 0 upon success, nonzero upon failure.
+ */
+static int
+doublebuf_pageflipping_commit (void)
+{
+  /* Set the render target's data pointer to the start of the render_page. */
+  framebuffer.render_target.data =
+    ((char *) framebuffer.ptr) +
+    doublebuf_state.page_size * doublebuf_state.render_page;
+
+  /* Tell the video adapter to display the new front page. */
+  int display_start_line =
+    framebuffer.render_target.mode_info.height 
+    * doublebuf_state.displayed_page;
+ 
+  grub_vbe_status_t vbe_err = 
+    grub_vbe_bios_set_display_start (0, display_start_line);
+  if (vbe_err != GRUB_VBE_STATUS_OK)
+    return 1;
+
+  return 0;
+}
+
+static int
+doublebuf_pageflipping_update_screen (void)
+{
+  /* Swap the page numbers in the framebuffer struct. */
+  int new_displayed_page = doublebuf_state.render_page;
+  doublebuf_state.render_page = doublebuf_state.displayed_page;
+  doublebuf_state.displayed_page = new_displayed_page;
+
+  return doublebuf_pageflipping_commit ();
+}
+
+static int
+doublebuf_pageflipping_destroy (void)
+{
+  doublebuf_state.update_screen = 0;
+  doublebuf_state.destroy = 0;
+  return 0;
+}
+
+static int
+doublebuf_pageflipping_init (void)
+{
+  doublebuf_state.page_size =
+    framebuffer.bytes_per_scan_line * render_target->mode_info.height;
+  
+  /* Get video RAM size in bytes.  */
+  grub_size_t vram_size = controller_info.total_memory << 16;
+
+  if (2 * doublebuf_state.page_size > vram_size)
+    return 1;   /* Not enough video memory for 2 pages. */
+
+  doublebuf_state.displayed_page = 0;
+  doublebuf_state.render_page = 1;
+
+  doublebuf_state.update_screen = doublebuf_pageflipping_update_screen;
+  doublebuf_state.destroy = doublebuf_pageflipping_destroy;
+
+  /* Set the framebuffer memory data pointer and display the right page. */
+  if (doublebuf_pageflipping_commit () != GRUB_ERR_NONE)
+    return 1;   /* Unable to set the display start.  */
+
+  return 0;
+}
+
+static int
+doublebuf_blit_update_screen (void)
+{
+  grub_memcpy (framebuffer.ptr,
+               doublebuf_state.offscreen_buffer, 
+               doublebuf_state.page_size);
+  return 0;
+}
+
+static int
+doublebuf_blit_destroy (void)
+{
+  grub_free (doublebuf_state.offscreen_buffer);
+  doublebuf_state.offscreen_buffer = 0;
+
+  doublebuf_state.update_screen = 0;
+  doublebuf_state.destroy = 0;
+  return 0;
+}
+
+static int
+doublebuf_blit_init (void)
+{
+  doublebuf_state.page_size =
+    framebuffer.bytes_per_scan_line * render_target->mode_info.height;
+
+  doublebuf_state.offscreen_buffer = (grub_uint8_t *)
+    grub_malloc (doublebuf_state.page_size);
+  if (doublebuf_state.offscreen_buffer == 0)
+    return 1;   /* Error.  */
+
+  framebuffer.render_target.data = doublebuf_state.offscreen_buffer;
+  doublebuf_state.update_screen = doublebuf_blit_update_screen;
+  doublebuf_state.destroy = doublebuf_blit_destroy;
+
+  return 0;
+}
+
+static int
+doublebuf_null_update_screen (void)
+{
+  return 0;
+}
+
+static int
+doublebuf_null_destroy (void)
+{
+  doublebuf_state.update_screen = 0;
+  doublebuf_state.destroy = 0;
+  return 0;
+}
+
+static int
+doublebuf_null_init (void)
+{
+  framebuffer.render_target.data = framebuffer.ptr;
+  doublebuf_state.update_screen = doublebuf_null_update_screen;
+  doublebuf_state.destroy = doublebuf_null_destroy;
+  return 0;
+}
+
+/* Select the best double buffering mode available.  */
+static void
+double_buffering_init (void)
+{
+  if (doublebuf_state.destroy)
+    doublebuf_state.destroy();
+
+  if (framebuffer.is_double_buffered)
+    {
+      if (doublebuf_pageflipping_init () == 0)
+        return;
+
+      if (doublebuf_blit_init () == 0)
+        return;
+    }
+
+  /* Fall back to no double buffering. */
+  doublebuf_null_init ();
+}
+
 static grub_err_t
 grub_video_vbe_get_info (struct grub_video_mode_info *mode_info)
 {
@@ -708,6 +888,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 +927,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 +998,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 +1095,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 +1129,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 +1141,35 @@
   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)
-            {
-              grub_video_i386_vbeblit_R8G8B8_R8G8B8X8 (target, source,
+          if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_BGR_888)
+            {
+              grub_video_i386_vbeblit_BGR888_RGBX8888 (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_RGBX8888 (target, source,
                                                        x, y, width, height,
                                                        offset_x, offset_y);
               return;
@@ -1005,26 +1177,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 +1220,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 +1256,35 @@
   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)
-        {
-          grub_video_i386_vbeblit_R8G8B8A8_R8G8B8A8 (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_R8G8B8A8 (target, source,
+        if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_BGRA_8888)
+        {
+          grub_video_i386_vbeblit_BGRA8888_RGBA8888 (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_RGBA8888 (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_RGBA8888 (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_RGBA8888 (target, source,
               x, y, width, height,
               offset_x, offset_y);
           return;
@@ -1077,26 +1292,42 @@
 
         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)
-        {
-          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;
@@ -1104,7 +1335,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 +1450,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 +1705,13 @@
   return GRUB_ERR_NONE;
 }
 
-static grub_err_t 
+static grub_err_t
 grub_video_vbe_swap_buffers (void)
 {
-  /* TODO: Implement buffer swapping.  */
+  if (doublebuf_state.update_screen () != 0)
+    return grub_error (GRUB_ERR_INVALID_COMMAND,
+                       "Double buffer update failed");
+
   return GRUB_ERR_NONE;
 }
 
@@ -1505,17 +1810,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 +1852,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-09 17:27:53 +0000
@@ -35,7 +35,343 @@
 #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_BGRA8888_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)
+{
+  grub_uint32_t *srcptr;
+  grub_uint32_t *dstptr;
+  unsigned srcrowskip;
+  unsigned dstrowskip;
+  int i;
+  int j;
+
+  /* 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_uint32_t *) get_data_ptr (src, offset_x, offset_y);
+  dstptr = (grub_uint32_t *) get_data_ptr (dst, x, y);
+
+  for (j = 0; j < height; j++)
+    {
+      for (i = 0; i < width; i++)
+        {
+          grub_uint32_t color;
+          unsigned int sr;
+          unsigned int sg;
+          unsigned int sb;
+          unsigned int a;
+          unsigned int dr;
+          unsigned int dg;
+          unsigned int db;
+
+          color = *srcptr++;
+
+          a = color >> 24;
+
+          if (a == 0)
+            {
+              /* Skip transparent source pixels.  */
+              dstptr++;
+              continue;
+            }
+
+          sr = (color >> 0) & 0xFF;
+          sg = (color >> 8) & 0xFF;
+          sb = (color >> 16) & 0xFF;
+
+          if (a == 255)
+            {
+              /* Opaque pixel shortcut.  */
+              dr = sr;
+              dg = sg;
+              db = sb;
+            }
+          else
+            {
+              /* General pixel color blending.  */
+              color = *dstptr;
+
+              dr = (color >> 16) & 0xFF;
+              dr = (dr * (255 - a) + sr * a) / 255;
+              dg = (color >> 8) & 0xFF;
+              dg = (dg * (255 - a) + sg * a) / 255;
+              db = (color >> 0) & 0xFF;
+              db = (db * (255 - a) + sb * a) / 255;
+            }
+
+          color = (a << 24) | (dr << 16) | (dg << 8) | db;
+
+          *dstptr++ = color;
+        }
+
+      srcptr = (grub_uint32_t *) (((grub_uint8_t *) srcptr) + srcrowskip);
+      dstptr = (grub_uint32_t *) (((grub_uint8_t *) dstptr) + dstrowskip);
+    }
+}
+
+void
+grub_video_i386_vbeblit_BGR888_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)
+{
+  grub_uint32_t *srcptr;
+  grub_uint8_t *dstptr;
+  unsigned srcrowskip;
+  unsigned dstrowskip;
+  int i;
+  int j;
+
+  /* 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_uint32_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_uint32_t color;
+          unsigned int sr;
+          unsigned int sg;
+          unsigned int sb;
+          unsigned int a;
+          unsigned int dr;
+          unsigned int dg;
+          unsigned int db;
+
+          color = *srcptr++;
+
+          a = color >> 24;
+
+          if (a == 0)
+            {
+              /* Skip transparent source pixels.  */
+              dstptr += 3;
+              continue;
+            }
+
+          sr = (color >> 0) & 0xFF;
+          sg = (color >> 8) & 0xFF;
+          sb = (color >> 16) & 0xFF;
+
+          if (a == 255)
+            {
+              /* Opaque pixel shortcut.  */
+              dr = sr;
+              dg = sg;
+              db = sb;
+            }
+          else
+            {
+              /* General pixel color blending.  */
+              color = *dstptr;
+
+              db = dstptr[0];
+              db = (db * (255 - a) + sb * a) / 255;
+              dg = dstptr[1];
+              dg = (dg * (255 - a) + sg * a) / 255;
+              dr = dstptr[2];
+              dr = (dr * (255 - a) + sr * a) / 255;
+            }
+
+          *dstptr++ = db;
+          *dstptr++ = dg;
+          *dstptr++ = dr;
+        }
+
+      srcptr = (grub_uint32_t *) (((grub_uint8_t *) srcptr) + srcrowskip);
+      dstptr += dstrowskip;
+    }
+}
+
+void
+grub_video_i386_vbeblit_BGR888_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)
+{
+  grub_uint32_t *srcptr;
+  grub_uint8_t *dstptr;
+  unsigned srcrowskip;
+  unsigned dstrowskip;
+  int i;
+  int j;
+
+  /* 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_uint32_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_uint32_t color;
+          grub_uint8_t sr;
+          grub_uint8_t sg;
+          grub_uint8_t sb;
+
+          color = *srcptr++;
+
+          sr = (color >> 0) & 0xFF;
+          sg = (color >> 8) & 0xFF;
+          sb = (color >> 16) & 0xFF;
+
+          *dstptr++ = sb;
+          *dstptr++ = sg;
+          *dstptr++ = sr;
+        }
+
+      srcptr = (grub_uint32_t *) (((grub_uint8_t *) 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 +437,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 +462,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 +529,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 +567,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 +631,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 +668,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 +704,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 +729,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-09 16:50:11 +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;
         }
     }
 
@@ -168,13 +175,22 @@
   if (mode_info->bpp == 24)
     {
       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_BGR_888;
+        }
+      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))
         {
-          return GRUB_VIDEO_BLIT_FORMAT_R8G8B8;
+          return GRUB_VIDEO_BLIT_FORMAT_RGB_888;
         }
     }
 
@@ -305,6 +321,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
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
