# Bazaar merge directive format 2 (Bazaar 0.90)
# revision_id: colin@gibibit.com-20080812025506-uxychfeydaod9wap
# target_branch: ../trunk-clean
# testament_sha1: 9db9fd86bc3b44cbe9cf194d4ed98d1af80f929a
# timestamp: 2008-08-11 19:59:56 -0700
# source_branch: http://grub.gibibit.com/bzr/trunk-clean
# base_revision_id: colin@gibibit.com-20080811144250-qvttic16dey3cnvb
# 
# Begin patch
=== modified file '.bzrignore'
--- .bzrignore	2008-07-19 21:43:42 +0000
+++ .bzrignore	2008-07-19 21:44:04 +0000
@@ -7,3 +7,5 @@
 ./conf/sparc64-ieee1275.mk
 ./conf/i386-coreboot.mk
 ./build*
+./util/fonttool/build
+./util/fonttool/fonttool.iws

=== modified file 'ChangeLog'
--- ChangeLog	2008-08-11 09:33:14 +0000
+++ ChangeLog	2008-08-11 14:43:03 +0000
@@ -405,7 +405,6 @@
 	(memdisk_mod_SOURCES, memdisk_mod_CFLAGS)
 	(memdisk_mod_LDFLAGS): New variables.
 	* conf/i386-coreboot.rmk: Likewise.
-	* conf/i386-ieee1275.rmk: Likewise.
 
 2008-08-02  Robert Millan  <rmh@aybabtu.com>
 
@@ -538,6 +537,72 @@
 
 	* normal/main.c (get_line): Fix buffer overflow bug.
 
+2008-07-28  Colin D Bennett <colin@gibibit.com>
+
+	High resolution timer support.  Implemented for x86 CPUs using TSC.
+	Extracted generic grub_millisleep() so it's linked in only as needed.
+	This requires a Pentium compatible CPU; if the RDTSC instruction is
+	not supported, then it falls back on the generic grub_get_time_ms()
+	implementation that uses the machine's RTC.
+
+	* conf/i386-efi.rmk: Added new source files to kernel_elf_SOURCES.
+
+	* conf/i386-pc.rmk: Likewise.
+
+	* conf/sparc64-ieee1275.rmk: Likewise.
+
+	* conf/powerpc-ieee1275.rmk: Likewise.
+
+
+	* kern/generic/rtc_get_time_ms.c: New file.
+
+	* kern/generic/millisleep.c: New file.
+
+	* kern/misc.c (grub_millisleep_generic): Removed.
+
+	* 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.  New inline
+	function.
+	(grub_cpu_is_cpuid_supported): New inline function.
+	(grub_cpu_is_tsc_supported): New inline function.
+	(grub_tsc_init): New function prototype.
+	(grub_tsc_get_time_ms): New function prototype.
+
+	* kern/i386/tsc.c (grub_get_time_ms): New file.  New function.
+	(calibrate_tsc): New static function.
+	(grub_tsc_init): New function.
+
+	* include/grub/time.h (grub_millisleep_generic): Removed.
+	(grub_get_time_ms): New function.
+	(grub_install_get_time_ms): New function.
+
+	* kern/time.c (grub_get_time_ms): New function.
+	(grub_install_get_time_ms): New function.
+
+	* kern/i386/efi/init.c (grub_millisleep): Removed.
+	(grub_machine_init): Call grub_tsc_init.
+
+	* kern/i386/linuxbios/init.c (grub_machine_init): Install the RTC
+	get_time_ms() implementation.
+
+	* kern/sparc64/ieee1275/init.c (grub_millisleep): Removed.
+	(ieee1275_get_time_ms): New function.
+	(grub_machine_init): Install get_time_ms() implementation.
+
+	* kern/i386/pc/init.c (grub_machine_init): Call grub_tsc_init().
+	(grub_millisleep): Removed.
+
+	* kern/ieee1275/init.c (grub_millisleep): Removed.
+	(grub_machine_init): Install ieee1275_get_time_ms() implementation.
+	(ieee1275_get_time_ms): New static function.
+	(grub_get_rtc): Now calls ieee1275_get_time_ms(), which does the real
+	work.
+
+	* conf/i386-ieee1275.rmk: Likewise.
+
 2008-07-28  Robert Millan  <rmh@aybabtu.com>
 
 	* partmap/apple.c (GRUB_APPLE_HEADER_MAGIC): New macro.

=== modified file 'Makefile.in'
--- Makefile.in	2008-08-09 11:30:26 +0000
+++ Makefile.in	2008-08-11 14:43:03 +0000
@@ -90,6 +90,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@
@@ -138,13 +171,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/videotest.c'
--- commands/videotest.c	2007-07-21 22:32:33 +0000
+++ commands/videotest.c	2008-08-11 15:02:14 +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/grub/themes/winter/without-leaves.png";
+  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-08-09 11:30:26 +0000
+++ conf/common.rmk	2008-08-11 14:46:59 +0000
@@ -281,6 +281,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		\
+	gfxmenu.mod                                             \
 	read.mod sleep.mod loadenv.mod crc.mod
 
 # For hello.mod.
@@ -288,6 +289,23 @@
 hello_mod_CFLAGS = $(COMMON_CFLAGS)
 hello_mod_LDFLAGS = $(COMMON_LDFLAGS)
 
+# For gfxmenu.mod.
+gfxmenu_mod_SOURCES = \
+	gfxmenu/gfxmenu.c \
+	gfxmenu/model.c \
+	gfxmenu/view.c \
+	gfxmenu/theme_loader.c \
+	gfxmenu/widget-box.c \
+	gfxmenu/gui_canvas.c \
+	gfxmenu/gui_box.c \
+	gfxmenu/gui_label.c \
+	gfxmenu/gui_image.c \
+	gfxmenu/gui_progress_bar.c \
+	gfxmenu/gui_util.c \
+	gfxmenu/stringutil.c
+gfxmenu_mod_CFLAGS = $(COMMON_CFLAGS)
+gfxmenu_mod_LDFLAGS = $(COMMON_LDFLAGS)
+
 # For boot.mod.
 boot_mod_SOURCES = commands/boot.c
 boot_mod_CFLAGS = $(COMMON_CFLAGS)
@@ -324,7 +342,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)
 

=== modified file 'conf/i386-pc.rmk'
--- conf/i386-pc.rmk	2008-08-05 19:24:00 +0000
+++ conf/i386-pc.rmk	2008-08-11 14:46:59 +0000
@@ -48,11 +48,13 @@
 	kern/generic/rtc_get_time_ms.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 machine/pxe.h
@@ -251,7 +253,7 @@
 play_mod_LDFLAGS = $(COMMON_LDFLAGS)
 
 # For video.mod.
-video_mod_SOURCES = video/video.c
+video_mod_SOURCES = video/video.c video/setmode.c
 video_mod_CFLAGS = $(COMMON_CFLAGS)
 video_mod_LDFLAGS = $(COMMON_LDFLAGS)
 
@@ -266,7 +268,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/sparc64-ieee1275.rmk'
--- conf/sparc64-ieee1275.rmk	2008-08-05 10:54:37 +0000
+++ conf/sparc64-ieee1275.rmk	2008-08-07 23:48:19 +0000
@@ -196,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)
 

=== modified file 'config.h.in'
--- config.h.in	2008-07-13 00:55:15 +0000
+++ config.h.in	2008-07-19 21:46:40 +0000
@@ -113,10 +113,37 @@
 /* Number of bits in a file offset, on hosts where this is settable. */
 #undef _FILE_OFFSET_BITS
 
+/* Define for large files, on AIX-style hosts. */
+#undef _LARGE_FILES
+
+/* Define to 1 if on MINIX. */
+#undef _MINIX
+
+/* Define to 2 if the system does not provide POSIX.1 features except with
+   this defined. */
+#undef _POSIX_1_SOURCE
+
+/* Define to 1 if you need to in order for `stat' and other things to work. */
+#undef _POSIX_SOURCE
+
+/* Enable extensions on AIX 3, Interix.  */
+#ifndef _ALL_SOURCE
+# undef _ALL_SOURCE
+#endif
 /* Enable GNU extensions on systems that have them.  */
 #ifndef _GNU_SOURCE
 # undef _GNU_SOURCE
 #endif
+/* Enable threading extensions on Solaris.  */
+#ifndef _POSIX_PTHREAD_SEMANTICS
+# undef _POSIX_PTHREAD_SEMANTICS
+#endif
+/* Enable extensions on HP NonStop.  */
+#ifndef _TANDEM_SOURCE
+# undef _TANDEM_SOURCE
+#endif
+/* Enable general extensions on Solaris.  */
+#ifndef __EXTENSIONS__
+# undef __EXTENSIONS__
+#endif
 
-/* Define for large files, on AIX-style hosts. */
-#undef _LARGE_FILES

=== added file 'docs/gfxmenu-theme-example.txt'
--- docs/gfxmenu-theme-example.txt	1970-01-01 00:00:00 +0000
+++ docs/gfxmenu-theme-example.txt	2008-08-11 14:38:31 +0000
@@ -0,0 +1,125 @@
+# GRUB gfxmenu theme "winter".
+# Uses background image from:
+# http://www.cyberpunkcafe.com/e107_plugins/autogallery/autogallery.php?show=1.Open%20Source%20Wallpaper
+# "without-leaves.png" was called "Without Leafs in Winter.png"
+
+lua-script: "winter.lua"
+title-text: ""
+title-font: "Helvetica Bold 18"
+item-font: "Helvetica Bold 14"
+selected-item-font: "Helvetica Bold 14"
+status-font: "Helvetica 8"
+terminal-font: "Fixed 9"
+title-color: "40, 40, 40"
+item-color: "0, 0, 0"
+selected-item-color: "203, 251, 255"
+status-color: "#FFF"
+status-bg-color: "0, 166, 183, 128"
+desktop-image: "without-leaves.png"
+desktop-color: "0, 154, 183"
+
+menu-box: "menu_*.png"
+selected-item-box: "select_*.png"
+terminal-box: "terminal_*.png"
+
+icon-width: 44
+icon-height: 44
+item-height: 32
+item-padding: 0
+item-icon-space: 3
+item-spacing: 11
+
+# You can add text at arbitrary locations on the screen.
+# The specification within the "+label {...}" block is free-form,
+# so you can use as much or as little white space as you like.
+
++ label {
+   position = (170, 50)
+   font = "smoothansi 13"
+   color = "0,0,128" 
+   text = "This is the Winter theme ... brought to you by GRUB!"
+}
+
+# Show the text alignment supported by labels.
++ vbox {
+   position = (220, 347)
+   preferred_size = (200, -1)     # A preferred size of -1 means automatic.
+   + label { text="Text alignment demo" align="center" font="aqui 11" }
+   + label { text="Left" align="left" font="cure 11" }
+   + label { text="Center" align="center" font="cure 11" }
+   + label { text="Right" align="right" font="cure 11" }
+}
+
++ vbox {
+   position = (580, 10)
+   + label { text="GNU" font="gelly 11" color="0, 0, 0" }
+   + label { text="GRUB" font="aqui 11" color="0, 0, 0" }
+   + label { text="boot loader" font="cure 11" color="0, 0, 0" }
+}
+
++ hbox {
+   position = (80, 10)
+   + label { text="GNU" font="gelly 11" color="0, 0, 0" }
+   + label { text="GRUB" font="aqui 11" color="0, 0, 0" }
+   + label { text="boot loader" font="cure 11" color="0, 0, 0" }
+}
+
+# Demonstration of a compound layout: boxes within boxes.
++ hbox 
+{
+   position = (480, 3)
+
+   + vbox 
+   {
+      # Note: We can't just use 'size' to set the image's size,
+      #       since the vbox will resize the component according to its
+      #       preferred size, which for images is the native image size.
+
+      + image { file="/boot/grub/themes/icons/ubuntu.png"
+                preferred_size = (20, 20) }
+      + image { file="/boot/grub/themes/icons/gentoo.png"
+                preferred_size = (20, 20) }
+   }
+
+   + vbox 
+   {
+      + label { text="GRand" font="cure 11" color=#99F }
+      + label { text="Unified" font="cure 11" color=#BBF }
+      + label { text="Bootloader" font="cure 11" color=#DDF }
+   }
+}
+
+# By defining a 'progress_bar' type component with an ID of '__timeout__',
+# the progress bar will be used to display the time remaining before an 
+# the default entry is automatically booted.
++ progress_bar
+{
+   id = "__timeout__"
+   position = (80, 393)
+   size = (500, 24)
+   font = "cure 11"
+   text_color = #000
+   fg_color = #CCF
+   bg_color = #66B
+   border_color = #006
+   show_text = false
+}
+
+# Although the progress_bar component is normally used to indicate the
+# time remaining, it's also possible to create other components with an ID
+# of '__timeout__'.  All components with and ID of 'timeout_bar' will have
+# the following properties set based on the timeout value:
+#   text, value, start, end, visible.
+# In this case, we have set 'show_text=false' on the progress bar, and use
+# the following label's 'text' property to display the message.
++ label
+{
+   id = "__timeout__"
+   position = (80, 420)
+   size = (500, 24)
+   font = "lime 11"
+   color = #117
+   align = "center"
+}
+
+

=== 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-29 14:51:29 +0000
@@ -0,0 +1,689 @@
+/* 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/bufio.h>
+#include <grub/misc.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_buffile_open (filename, 1024);
+  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
+          grub_off_t section_end = grub_file_tell (file) + section.length;
+          if ((int) grub_file_seek (file, section_end) == -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-08-01 03:06:55 +0000
+++ font/manager.c	1970-01-01 00:00:00 +0000
@@ -1,283 +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>
-#include <grub/bufio.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_buffile_open (filename, 0);
-  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-28 21:35:40 +0000
+++ genmk.rb	2008-08-03 03:57:46 +0000
@@ -126,7 +126,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}
@@ -158,18 +158,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 directory 'gfxmenu'
=== added file 'gfxmenu/gfxmenu.c'
--- gfxmenu/gfxmenu.c	1970-01-01 00:00:00 +0000
+++ gfxmenu/gfxmenu.c	2008-07-27 00:06:20 +0000
@@ -0,0 +1,221 @@
+/* gfxmenu.c - Graphical menu interface controller. */
+/*
+ *  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/gfxterm.h>
+#include <grub/bitmap.h>
+#include <grub/bitmap_scale.h>
+#include <grub/term.h>
+#include <grub/gfxwidgets.h>
+#include <grub/menu.h>
+#include <grub/menu_viewer.h>
+#include <grub/gfxmenu_model.h>
+#include <grub/gfxmenu_view.h>
+
+static void switch_to_text_menu (void)
+{
+  grub_env_set ("menuviewer", "terminal");
+}
+
+static void
+process_key_press (int c,
+                   grub_gfxmenu_model_t model,
+                   grub_gfxmenu_view_t view,
+                   int nested,
+                   int *should_exit)
+{
+  /* When a key is pressed, stop the timeout.  */
+  grub_gfxmenu_model_clear_timeout (model);
+
+  if (c == 'j' || c == GRUB_TERM_DOWN)
+    {
+      int i = grub_gfxmenu_model_get_selected_index (model);
+      int num_items = grub_gfxmenu_model_get_num_entries (model);
+      if (i < num_items - 1)
+        {
+          i++;
+          grub_gfxmenu_model_set_selected_index (model, i);
+        }
+    }
+  else if (c == 'k' || c == GRUB_TERM_UP)
+    {
+      int i = grub_gfxmenu_model_get_selected_index (model);
+      if (i > 0)
+        {
+          i--;
+          grub_gfxmenu_model_set_selected_index (model, i);
+        }
+    }
+  else if (c == '\r' || c == '\n' || c == GRUB_TERM_RIGHT)
+    {
+      int selected = grub_gfxmenu_model_get_selected_index (model);
+      int num_entries = grub_gfxmenu_model_get_num_entries (model);
+      if (selected >= 0 && selected < num_entries)
+        {
+          grub_menu_entry_t entry =
+            grub_gfxmenu_model_get_entry (model, selected);
+          grub_gfxmenu_view_execute_entry (view, entry);
+        }
+    }
+  else if (c == 'c')
+    {
+      grub_gfxmenu_view_run_terminal (view);
+      if (grub_errno != GRUB_ERR_NONE)
+        *should_exit = 1;
+    }
+  else if (c == 't')
+    {
+      /* The write hook for 'menuviewer' will cause
+       * grub_menu_viewer_should_return to return nonzero. */
+      switch_to_text_menu ();
+      *should_exit = 1;
+    }
+  else if (c == '1')
+    {
+      grub_gfxmenu_view_load_theme (view,
+                                    "/boot/grub/themes/proto/theme.txt");
+    }
+  else if (c == '2')
+    {
+      grub_gfxmenu_view_load_theme (view,
+                                    "/boot/grub/themes/winter/theme.txt");
+    }
+  else if (nested && c == GRUB_TERM_ESC)
+    {
+      *should_exit = 1;
+    }
+}
+
+static void
+handle_key_events (grub_gfxmenu_model_t model,
+                   grub_gfxmenu_view_t view,
+                   int nested,
+                   int *should_exit)
+{
+  while (!*should_exit && grub_checkkey () != -1)
+    {
+      int key = grub_getkey ();
+      int c = GRUB_TERM_ASCII_CHAR (key);
+      process_key_press (c, model, view, nested, should_exit);
+    }
+}
+
+static grub_err_t
+show_menu (grub_menu_t menu, int nested)
+{
+  grub_gfxmenu_model_t model;
+
+  model = grub_gfxmenu_model_new (menu);
+  if (! model)
+    {
+      grub_print_error ();
+      grub_printf ("Initializing menu data for graphical menu failed;\n"
+                   "falling back to terminal based menu.\n");
+      grub_wait_after_message ();
+      switch_to_text_menu ();
+      return grub_errno;
+    }
+
+  grub_gfxmenu_view_t view;
+
+  /* Create the view.  */
+  const char *theme_path = grub_env_get ("theme");
+  if (! theme_path)
+    theme_path = "/boot/grub/themes/proto/theme.txt";
+
+  view = grub_gfxmenu_view_new (theme_path, model);
+  if (! view)
+    {
+      grub_print_error ();
+      grub_printf ("Starting graphical menu failed;\n"
+                   "falling back to terminal based menu.\n");
+      grub_wait_after_message ();
+      grub_gfxmenu_model_destroy (model);
+      switch_to_text_menu ();
+      return grub_errno;
+    }
+
+  /* Initially select the default menu entry.  */
+  int default_index = grub_menu_get_default_entry_index (menu);
+  grub_gfxmenu_model_set_selected_index (model, default_index);
+
+  /* Start the timer to execute the default entry.  */
+  grub_gfxmenu_model_set_timeout (model);
+
+  /* Main event loop.  */
+  int exit_requested = 0;
+  while (!exit_requested && !grub_menu_viewer_should_return ())
+    {
+      if (grub_gfxmenu_model_timeout_expired (model))
+        {
+          grub_gfxmenu_model_clear_timeout (model);
+          int i = grub_gfxmenu_model_get_selected_index (model);
+          grub_menu_entry_t e = grub_gfxmenu_model_get_entry (model, i);
+          grub_gfxmenu_view_execute_with_fallback (view, e);
+          continue;
+        }
+
+      grub_gfxmenu_view_draw (view);
+      grub_video_swap_buffers ();
+      handle_key_events (model, view, nested, &exit_requested);
+    }
+
+  grub_gfxmenu_view_destroy (view);
+  grub_gfxmenu_model_destroy (model);
+
+  return grub_errno;
+}
+
+static grub_err_t
+grub_cmd_gfxmenu (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");
+
+  return show_menu (menu, 1);
+}
+
+static struct grub_menu_viewer menu_viewer =
+{
+  .name = "gfxmenu",
+  .show_menu = show_menu
+};
+
+GRUB_MOD_INIT (gfxmenu)
+{
+  (void) mod;                   /* To stop warning. */
+  grub_menu_viewer_register (&menu_viewer);
+  grub_register_command ("gfxmenu",
+                         grub_cmd_gfxmenu, GRUB_COMMAND_FLAG_BOTH,
+                         "gfxmenu", "Show graphical menu interface", 0);
+}
+
+GRUB_MOD_FINI (gfxmenu)
+{
+  grub_unregister_command ("gfxmenu");
+}

=== added file 'gfxmenu/gui_box.c'
--- gfxmenu/gui_box.c	1970-01-01 00:00:00 +0000
+++ gfxmenu/gui_box.c	2008-08-12 02:48:48 +0000
@@ -0,0 +1,367 @@
+/* gui_box.c - GUI container that stack components. */
+/*
+ *  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/mm.h>
+#include <grub/misc.h>
+#include <grub/gui.h>
+#include <grub/stringutil.h>
+
+struct component_node
+{
+  grub_gui_component_t component;
+  struct component_node *next;
+  struct component_node *prev;
+};
+
+typedef struct grub_gui_box *grub_gui_box_t;
+
+typedef void (*layout_func_t) (grub_gui_box_t self, int modify_layout,
+                               int *width, int *height);
+
+struct grub_gui_box
+{
+  struct grub_gui_container_ops *container;
+
+  grub_gui_container_t parent;
+  grub_video_rect_t bounds;
+  char *id;
+  int preferred_width;
+  int preferred_height;
+
+  /* Doubly linked list of components with dummy head & tail nodes.  */
+  struct component_node chead;
+  struct component_node ctail;
+
+  /* The layout function: differs for vertical and horizontal boxes.  */
+  layout_func_t layout_func;
+};
+
+static void
+box_destroy (void *vself)
+{
+  grub_gui_box_t self = vself;
+  struct component_node *cur;
+  struct component_node *next;
+  for (cur = self->chead.next; cur != &self->ctail; cur = next)
+    {
+      /* Copy the 'next' pointer, since we need it for the next iteration,
+         and we're going to free the memory it is stored in.  */
+      next = cur->next;
+      /* Destroy the child component.  */
+      cur->component->ops->destroy (cur->component);
+      /* Free the linked list node.  */
+      grub_free (cur);
+    }
+  grub_free (self);
+}
+
+static const char *
+box_get_id (void *vself)
+{
+  grub_gui_box_t self = vself;
+  return self->id;
+}
+
+static int
+box_is_instance (void *vself __attribute__((unused)), const char *type)
+{
+  return (grub_strcmp (type, "component") == 0
+          || grub_strcmp (type, "container") == 0);
+}
+
+static void
+layout_horizontally (grub_gui_box_t self, int modify_layout,
+                     int *width, int *height)
+{
+  /* Start at the left (chead) and set the x coordinates as we go right.  */
+  /* All components have their width set to the box's width.  */
+
+  struct component_node *cur;
+  int x = 0;
+  if (height)
+    *height = 0;
+  for (cur = self->chead.next; cur != &self->ctail; cur = cur->next)
+    {
+      grub_gui_component_t c = cur->component;
+      grub_video_rect_t r;
+
+      c->ops->get_preferred_size (c, &r.width, &r.height);
+
+      /* Check and possibly update the maximum width, if non-null.  */
+      if (height && r.height > *height)
+        *height = r.height;
+
+      /* Set the component's bounds, if the flag is set.  */
+      if (modify_layout)
+        {
+          r.x = x;
+          r.y = 0;
+          /* Width comes from the component's preferred size.  */
+          r.height = self->bounds.height;
+          c->ops->set_bounds (c, &r);
+        }
+
+      x += r.width;
+    }
+
+  /* Return the sum of the children's preferred widths.  */
+  if (width)
+    *width = x;
+}
+
+static void
+layout_vertically (grub_gui_box_t self, int modify_layout,
+                   int *width, int *height)
+{
+  /* Start at the top (chead) and set the y coordinates as we go down.  */
+  /* All components have their width set to the vbox's width.  */
+
+  struct component_node *cur;
+  int y = 0;
+  if (width)
+    *width = 0;
+  for (cur = self->chead.next; cur != &self->ctail; cur = cur->next)
+    {
+      grub_gui_component_t c = cur->component;
+      grub_video_rect_t r;
+
+      c->ops->get_preferred_size (c, &r.width, &r.height);
+
+      /* Check and possibly update the maximum width, if non-null.  */
+      if (width && r.width > *width)
+        *width = r.width;
+
+      /* Set the component's bounds, if the flag is set.  */
+      if (modify_layout)
+        {
+          r.x = 0;
+          r.y = y;
+          r.width = self->bounds.width;
+          /* Height comes from the component's preferred size.  */
+          c->ops->set_bounds (c, &r);
+        }
+
+      y += r.height;
+    }
+
+  /* Return the sum of the children's preferred heights.  */
+  if (height)
+    *height = y;
+}
+
+static void
+box_paint (void *vself)
+{
+  grub_gui_box_t self = vself;
+  struct component_node *cur;
+  grub_video_rect_t vpsave;
+
+  grub_gui_set_viewport (&self->bounds, &vpsave);
+  for (cur = self->chead.next; cur != &self->ctail; cur = cur->next)
+    {
+      grub_gui_component_t comp = cur->component;
+      comp->ops->paint (comp);
+    }
+  grub_gui_restore_viewport (&vpsave);
+}
+
+static void
+box_set_parent (void *vself, grub_gui_container_t parent)
+{
+  grub_gui_box_t self = vself;
+  self->parent = parent;
+}
+
+static grub_gui_container_t
+box_get_parent (void *vself)
+{
+  grub_gui_box_t self = vself;
+  return self->parent;
+}
+
+static void
+box_set_bounds (void *vself, const grub_video_rect_t *bounds)
+{
+  grub_gui_box_t self = vself;
+  self->bounds = *bounds;
+  self->layout_func (self, 1, 0, 0);   /* Relayout the children.  */
+}
+
+static void
+box_get_bounds (void *vself, grub_video_rect_t *bounds)
+{
+  grub_gui_box_t self = vself;
+  *bounds = self->bounds;
+}
+
+/* The box's preferred size is based on the preferred sizes
+   of its children.  */
+static void
+box_get_preferred_size (void *vself, int *width, int *height)
+{
+  grub_gui_box_t self = vself;
+  self->layout_func (self, 0, width, height);   /* Just calculate the size.  */
+
+  /* Allow preferred dimensions to override the computed dimensions.  */
+  if (self->preferred_width >= 0)
+    *width = self->preferred_width;
+  if (self->preferred_height >= 0)
+    *height = self->preferred_height;
+}
+
+static void
+box_set_property (void *vself, const char *name, const char *value)
+{
+  grub_gui_box_t self = vself;
+  if (grub_strcmp (name, "id") == 0)
+    {
+      grub_free (self->id);
+      if (value)
+        self->id = grub_strdup (value);
+      else
+        self->id = 0;
+    }
+  else if (grub_strcmp (name, "preferred_size") == 0)
+    {
+      int w;
+      int h;
+      if (grub_gui_parse_2_tuple (value, &w, &h))
+        {
+          self->preferred_width = w;
+          self->preferred_height = h;
+        }
+    }
+}
+
+static void
+box_add (void *vself, grub_gui_component_t comp)
+{
+  grub_gui_box_t self = vself;
+  struct component_node *node;
+  node = grub_malloc (sizeof (*node));
+  if (! node)
+    return;   /* Note: probably should handle the error.  */
+  node->component = comp;
+  /* Insert the node before the tail.  */
+  node->prev = self->ctail.prev;
+  node->prev->next = node;
+  node->next = &self->ctail;
+  node->next->prev = node;
+
+  comp->ops->set_parent (comp, (grub_gui_container_t) self);
+  self->layout_func (self, 1, 0, 0);   /* Relayout the children.  */
+}
+
+static void
+box_remove (void *vself, grub_gui_component_t comp)
+{
+  grub_gui_box_t self = vself;
+  struct component_node *cur;
+  for (cur = self->chead.next; cur != &self->ctail; cur = cur->next)
+    {
+      if (cur->component == comp)
+        {
+          /* Unlink 'cur' from the list.  */
+          cur->prev->next = cur->next;
+          cur->next->prev = cur->prev;
+          /* Free the node's memory (but don't destroy the component).  */
+          grub_free (cur);
+          /* Must not loop again, since 'cur' would be dereferenced!  */
+          return;
+        }
+    }
+}
+
+static void
+box_iterate_children (void *vself,
+                      grub_gui_component_callback cb, void *userdata)
+{
+  grub_gui_box_t self = vself;
+  struct component_node *cur;
+  for (cur = self->chead.next; cur != &self->ctail; cur = cur->next)
+    cb (cur->component, userdata);
+}
+
+static struct grub_gui_container_ops box_ops =
+{
+  .component =
+    {
+      .destroy = box_destroy,
+      .get_id = box_get_id,
+      .is_instance = box_is_instance,
+      .paint = box_paint,
+      .set_parent = box_set_parent,
+      .get_parent = box_get_parent,
+      .set_bounds = box_set_bounds,
+      .get_bounds = box_get_bounds,
+      .get_preferred_size = box_get_preferred_size,
+      .set_property = box_set_property
+    },
+  .add = box_add,
+  .remove = box_remove,
+  .iterate_children = box_iterate_children
+};
+
+/* Box constructor.  Specify the appropriate layout function to create
+   a horizontal or vertical stacking box.  */
+static grub_gui_box_t
+box_new (layout_func_t layout_func)
+{
+  grub_gui_box_t box;
+  box = grub_malloc (sizeof (*box));
+  if (! box)
+    return 0;
+  box->container = &box_ops;
+  box->parent = 0;
+  box->bounds.x = 0;
+  box->bounds.y = 0;
+  box->bounds.width = 0;
+  box->bounds.height = 0;
+  box->id = 0;
+  box->preferred_width = -1;
+  box->preferred_height = -1;
+  box->chead.component = 0;
+  box->chead.prev = 0;
+  box->chead.next = &box->ctail;
+  box->ctail.component = 0;
+  box->ctail.prev = &box->chead;
+  box->ctail.next = 0;
+  box->layout_func = layout_func;
+  return box;
+}
+
+/* Create a new container that stacks its child components horizontally,
+   from left to right.  Each child get a width corresponding to its
+   preferred width.  The height of each child is set the maximum of the
+   preferred heights of all children.  */
+grub_gui_container_t
+grub_gui_hbox_new (void)
+{
+  return (grub_gui_container_t) box_new (layout_horizontally);
+}
+
+/* Create a new container that stacks its child components verticallyj,
+   from top to bottom.  Each child get a height corresponding to its
+   preferred height.  The width of each child is set the maximum of the
+   preferred widths of all children.  */
+grub_gui_container_t
+grub_gui_vbox_new (void)
+{
+  return (grub_gui_container_t) box_new (layout_vertically);
+}

=== added file 'gfxmenu/gui_canvas.c'
--- gfxmenu/gui_canvas.c	1970-01-01 00:00:00 +0000
+++ gfxmenu/gui_canvas.c	2008-08-12 02:55:06 +0000
@@ -0,0 +1,247 @@
+/* gui_canvas.c - GUI container allowing manually placed components. */
+/*
+ *  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/mm.h>
+#include <grub/misc.h>
+#include <grub/gui.h>
+#include <grub/stringutil.h>
+
+// TODO Add layering so that components can be properly overlaid.
+
+struct component_node
+{
+  grub_gui_component_t component;
+  struct component_node *next;
+};
+
+struct grub_gui_canvas
+{
+  struct grub_gui_container_ops *container;
+
+  grub_gui_container_t parent;
+  grub_video_rect_t bounds;
+  char *id;
+  int preferred_width;
+  int preferred_height;
+  /* Component list (dummy head node).  */
+  struct component_node components;
+};
+
+typedef struct grub_gui_canvas *grub_gui_canvas_t;
+
+static void
+canvas_destroy (void *vself)
+{
+  grub_gui_canvas_t self = vself;
+  struct component_node *cur;
+  struct component_node *next;
+  for (cur = self->components.next; cur; cur = next)
+    {
+      /* Copy the 'next' pointer, since we need it for the next iteration,
+         and we're going to free the memory it is stored in.  */
+      next = cur->next;
+      /* Destroy the child component.  */
+      cur->component->ops->destroy (cur->component);
+      /* Free the linked list node.  */
+      grub_free (cur);
+    }
+  grub_free (self);
+}
+
+static const char *
+canvas_get_id (void *vself)
+{
+  grub_gui_canvas_t self = vself;
+  return self->id;
+}
+
+static int
+canvas_is_instance (void *vself __attribute__((unused)), const char *type)
+{
+  return (grub_strcmp (type, "component") == 0
+          || grub_strcmp (type, "container") == 0);
+}
+
+static void
+canvas_paint (void *vself)
+{
+  grub_gui_canvas_t self = vself;
+  struct component_node *cur;
+  grub_video_rect_t vpsave;
+
+  grub_gui_set_viewport (&self->bounds, &vpsave);
+  for (cur = self->components.next; cur; cur = cur->next)
+    {
+      grub_gui_component_t comp = cur->component;
+      comp->ops->paint (comp);
+    }
+  grub_gui_restore_viewport (&vpsave);
+}
+
+static void
+canvas_set_parent (void *vself, grub_gui_container_t parent)
+{
+  grub_gui_canvas_t self = vself;
+  self->parent = parent;
+}
+
+static grub_gui_container_t
+canvas_get_parent (void *vself)
+{
+  grub_gui_canvas_t self = vself;
+  return self->parent;
+}
+
+static void
+canvas_set_bounds (void *vself, const grub_video_rect_t *bounds)
+{
+  grub_gui_canvas_t self = vself;
+  self->bounds = *bounds;
+}
+
+static void
+canvas_get_bounds (void *vself, grub_video_rect_t *bounds)
+{
+  grub_gui_canvas_t self = vself;
+  *bounds = self->bounds;
+}
+
+static void
+canvas_get_preferred_size (void *vself, int *width, int *height)
+{
+  grub_gui_canvas_t self = vself;
+  *width = 0;
+  *height = 0;
+
+  /* Allow preferred dimensions to override the empty dimensions.  */
+  if (self->preferred_width >= 0)
+    *width = self->preferred_width;
+  if (self->preferred_height >= 0)
+    *height = self->preferred_height;
+}
+
+static void
+canvas_set_property (void *vself, const char *name, const char *value)
+{
+  grub_gui_canvas_t self = vself;
+  if (grub_strcmp (name, "id") == 0)
+    {
+      grub_free (self->id);
+      if (value)
+        self->id = grub_strdup (value);
+      else
+        self->id = 0;
+    }
+  else if (grub_strcmp (name, "preferred_size") == 0)
+    {
+      int w;
+      int h;
+      if (grub_gui_parse_2_tuple (value, &w, &h))
+        {
+          self->preferred_width = w;
+          self->preferred_height = h;
+        }
+    }
+}
+
+static void
+canvas_add (void *vself, grub_gui_component_t comp)
+{
+  grub_gui_canvas_t self = vself;
+  struct component_node *node;
+  node = grub_malloc (sizeof (*node));
+  if (! node)
+    return;   /* Note: probably should handle the error.  */
+  node->component = comp;
+  node->next = self->components.next;
+  self->components.next = node;
+  comp->ops->set_parent (comp, (grub_gui_container_t) self);
+}
+
+static void
+canvas_remove (void *vself, grub_gui_component_t comp)
+{
+  grub_gui_canvas_t self = vself;
+  struct component_node *cur;
+  struct component_node *prev;
+  prev = &self->components;
+  for (cur = self->components.next; cur; prev = cur, cur = cur->next)
+    {
+      if (cur->component == comp)
+        {
+          /* Unlink 'cur' from the list.  */
+          prev->next = cur->next;
+          /* Free the node's memory (but don't destroy the component).  */
+          grub_free (cur);
+          /* Must not loop again, since 'cur' would be dereferenced!  */
+          return;
+        }
+    }
+}
+
+static void
+canvas_iterate_children (void *vself,
+                         grub_gui_component_callback cb, void *userdata)
+{
+  grub_gui_canvas_t self = vself;
+  struct component_node *cur;
+  for (cur = self->components.next; cur; cur = cur->next)
+    cb (cur->component, userdata);
+}
+
+static struct grub_gui_container_ops canvas_ops =
+{
+  .component =
+    {
+      .destroy = canvas_destroy,
+      .get_id = canvas_get_id,
+      .is_instance = canvas_is_instance,
+      .paint = canvas_paint,
+      .set_parent = canvas_set_parent,
+      .get_parent = canvas_get_parent,
+      .set_bounds = canvas_set_bounds,
+      .get_bounds = canvas_get_bounds,
+      .get_preferred_size = canvas_get_preferred_size,
+      .set_property = canvas_set_property
+    },
+  .add = canvas_add,
+  .remove = canvas_remove,
+  .iterate_children = canvas_iterate_children
+};
+
+grub_gui_container_t
+grub_gui_canvas_new (void)
+{
+  grub_gui_canvas_t canvas;
+  canvas = grub_malloc (sizeof (*canvas));
+  if (! canvas)
+    return 0;
+  canvas->container = &canvas_ops;
+  canvas->parent = 0;
+  canvas->bounds.x = 0;
+  canvas->bounds.y = 0;
+  canvas->bounds.width = 0;
+  canvas->bounds.height = 0;
+  canvas->id = 0;
+  canvas->preferred_width = -1;
+  canvas->preferred_height = -1;
+  canvas->components.component = 0;
+  canvas->components.next = 0;
+  return (grub_gui_container_t) canvas;
+}

=== added file 'gfxmenu/gui_image.c'
--- gfxmenu/gui_image.c	1970-01-01 00:00:00 +0000
+++ gfxmenu/gui_image.c	2008-08-10 15:14:06 +0000
@@ -0,0 +1,262 @@
+/* gui_image.c - GUI component to display an image.  */
+/*
+ *  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/mm.h>
+#include <grub/misc.h>
+#include <grub/gui.h>
+#include <grub/stringutil.h>
+#include <grub/bitmap.h>
+#include <grub/bitmap_scale.h>
+
+struct grub_gui_image
+{
+  struct grub_gui_component_ops *image;
+
+  grub_gui_container_t parent;
+  grub_video_rect_t bounds;
+  char *id;
+  int preferred_width;
+  int preferred_height;
+  struct grub_video_bitmap *raw_bitmap;
+  struct grub_video_bitmap *bitmap;
+};
+
+typedef struct grub_gui_image *grub_gui_image_t;
+
+static void
+image_destroy (void *vself)
+{
+  grub_gui_image_t self = vself;
+
+  /* Free the scaled bitmap, unless it's a reference to the raw bitmap.  */
+  if (self->bitmap && (self->bitmap != self->raw_bitmap))
+    grub_video_bitmap_destroy (self->bitmap);
+  if (self->raw_bitmap)
+    grub_video_bitmap_destroy (self->raw_bitmap);
+
+  grub_free (self);
+}
+
+static const char *
+image_get_id (void *vself)
+{
+  grub_gui_image_t self = vself;
+  return self->id;
+}
+
+static int
+image_is_instance (void *vself __attribute__((unused)), const char *type)
+{
+  return grub_strcmp (type, "component") == 0;
+}
+
+static void
+image_paint (void *vself)
+{
+  grub_gui_image_t self = vself;
+  if (! self->bitmap)
+    return;
+  grub_video_rect_t vpsave;
+  grub_gui_set_viewport (&self->bounds, &vpsave);
+  grub_video_blit_bitmap (self->bitmap, GRUB_VIDEO_BLIT_BLEND,
+                          0, 0, 0, 0,
+                          grub_video_bitmap_get_width (self->bitmap),
+                          grub_video_bitmap_get_height (self->bitmap));
+  grub_gui_restore_viewport (&vpsave);
+}
+
+static void
+image_set_parent (void *vself, grub_gui_container_t parent)
+{
+  grub_gui_image_t self = vself;
+  self->parent = parent;
+}
+
+static grub_gui_container_t
+image_get_parent (void *vself)
+{
+  grub_gui_image_t self = vself;
+  return self->parent;
+}
+
+static void
+rescale_image (grub_gui_image_t self)
+{
+  if (! self->raw_bitmap)
+    {
+      if (self->bitmap)
+        {
+          grub_video_bitmap_destroy (self->bitmap);
+          self->bitmap = 0;
+        }
+      return;
+    }
+
+  unsigned width = self->bounds.width;
+  unsigned height = self->bounds.height;
+
+  if (self->bitmap
+      && (grub_video_bitmap_get_width (self->bitmap) == width)
+      && (grub_video_bitmap_get_height (self->bitmap) == height))
+    return;   /* Nothing to do; already the right size.  */
+
+  /* Free any old scaled bitmap,
+     unless it's a reference to the raw bitmap.  */
+  if (self->bitmap && (self->bitmap != self->raw_bitmap))
+    grub_video_bitmap_destroy (self->bitmap);
+
+  /* Create a scaled bitmap, unless the requested size is the same
+     as the raw size -- in that case a reference is made.  */
+  if (grub_video_bitmap_get_width (self->raw_bitmap) == width
+      && grub_video_bitmap_get_height (self->raw_bitmap) == height)
+    {
+      self->bitmap = self->raw_bitmap;
+      return;
+    }
+
+  /* Create the scaled bitmap.  */
+  grub_video_bitmap_create_scaled (&self->bitmap,
+                                   width,
+                                   height,
+                                   self->raw_bitmap,
+                                   GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST);
+  grub_errno = GRUB_ERR_NONE;  /* Catch any errors.  */
+}
+
+static void
+image_set_bounds (void *vself, const grub_video_rect_t *bounds)
+{
+  grub_gui_image_t self = vself;
+  self->bounds = *bounds;
+  rescale_image (self);
+}
+
+static void
+image_get_bounds (void *vself, grub_video_rect_t *bounds)
+{
+  grub_gui_image_t self = vself;
+  *bounds = self->bounds;
+}
+
+static void
+image_get_preferred_size (void *vself, int *width, int *height)
+{
+  grub_gui_image_t self = vself;
+
+  if (self->raw_bitmap)
+    {
+      *width = grub_video_bitmap_get_width (self->raw_bitmap);
+      *height = grub_video_bitmap_get_height (self->raw_bitmap);
+    }
+  else
+    {
+      *width = 0;
+      *height = 0;
+    }
+
+  /* Allow preferred dimensions to override the image dimensions.  */
+  if (self->preferred_width >= 0)
+    *width = self->preferred_width;
+  if (self->preferred_height >= 0)
+    *height = self->preferred_height;
+}
+
+static void
+load_image (grub_gui_image_t self, const char *path)
+{
+  struct grub_video_bitmap *bitmap;
+  grub_errno = GRUB_ERR_NONE;
+  if (grub_video_bitmap_load (&bitmap, path) != GRUB_ERR_NONE)
+    {
+      /* We should log the error somehow.  */
+      grub_errno = GRUB_ERR_NONE;
+      return;
+    }
+  if (self->bitmap && (self->bitmap != self->raw_bitmap))
+    grub_video_bitmap_destroy (self->bitmap);
+  if (self->raw_bitmap)
+    grub_video_bitmap_destroy (self->raw_bitmap);
+
+  self->raw_bitmap = bitmap;
+  rescale_image (self);
+}
+
+static void
+image_set_property (void *vself, const char *name, const char *value)
+{
+  grub_gui_image_t self = vself;
+  if (grub_strcmp (name, "file") == 0)
+    {
+      load_image (self, value);
+    }
+  else if (grub_strcmp (name, "preferred_size") == 0)
+    {
+      int w;
+      int h;
+      if (grub_gui_parse_2_tuple (value, &w, &h))
+        {
+          self->preferred_width = w;
+          self->preferred_height = h;
+        }
+    }
+  else if (grub_strcmp (name, "id") == 0)
+    {
+      grub_free (self->id);
+      if (value)
+        self->id = grub_strdup (value);
+      else
+        self->id = 0;
+    }
+}
+
+static struct grub_gui_component_ops image_ops =
+{
+  .destroy = image_destroy,
+  .get_id = image_get_id,
+  .is_instance = image_is_instance,
+  .paint = image_paint,
+  .set_parent = image_set_parent,
+  .get_parent = image_get_parent,
+  .set_bounds = image_set_bounds,
+  .get_bounds = image_get_bounds,
+  .get_preferred_size = image_get_preferred_size,
+  .set_property = image_set_property
+};
+
+grub_gui_component_t
+grub_gui_image_new (void)
+{
+  grub_gui_image_t image;
+  image = grub_malloc (sizeof (*image));
+  if (! image)
+    return 0;
+  image->image = &image_ops;
+  image->parent = 0;
+  image->bounds.x = 0;
+  image->bounds.y = 0;
+  image->bounds.width = 0;
+  image->bounds.height = 0;
+  image->id = 0;
+  image->preferred_width = -1;
+  image->preferred_height = -1;
+  image->raw_bitmap = 0;
+  image->bitmap = 0;
+  return (grub_gui_component_t) image;
+}
+

=== added file 'gfxmenu/gui_label.c'
--- gfxmenu/gui_label.c	1970-01-01 00:00:00 +0000
+++ gfxmenu/gui_label.c	2008-08-12 02:55:06 +0000
@@ -0,0 +1,247 @@
+/* gui_label.c - GUI component to display a line of text.  */
+/*
+ *  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/mm.h>
+#include <grub/misc.h>
+#include <grub/gui.h>
+#include <grub/stringutil.h>
+
+static const char *align_options[] =
+{
+  "left",
+  "center",
+  "right",
+  0
+};
+
+enum align_mode {
+  align_left,
+  align_center,
+  align_right
+};
+
+struct grub_gui_label
+{
+  struct grub_gui_component_ops *label;
+
+  grub_gui_container_t parent;
+  grub_video_rect_t bounds;
+  char *id;
+  int preferred_width;
+  int preferred_height;
+  int visible;
+  char *text;
+  grub_font_t font;
+  grub_gui_color_t color;
+  enum align_mode align;
+};
+
+typedef struct grub_gui_label *grub_gui_label_t;
+
+static void
+label_destroy (void *vself)
+{
+  grub_gui_label_t self = vself;
+  grub_free (self->text);
+  grub_free (self);
+}
+
+static const char *
+label_get_id (void *vself)
+{
+  grub_gui_label_t self = vself;
+  return self->id;
+}
+
+static int
+label_is_instance (void *vself __attribute__((unused)), const char *type)
+{
+  return grub_strcmp (type, "component") == 0;
+}
+
+static void
+label_paint (void *vself)
+{
+  grub_gui_label_t self = vself;
+
+  if (! self->visible)
+    return;
+
+  /* Calculate the starting x coordinate.  */
+  int left_x;
+  if (self->align == align_left)
+    left_x = 0;
+  else if (self->align == align_center)
+    left_x = ((self->bounds.width
+               - grub_font_get_string_width (self->font, self->text))
+             ) / 2;
+  else if (self->align == align_right)
+    left_x = (self->bounds.width
+              - grub_font_get_string_width (self->font, self->text));
+  else
+    return;   /* Invalid alignment.  */
+
+  grub_video_rect_t vpsave;
+  grub_gui_set_viewport (&self->bounds, &vpsave);
+  grub_video_draw_string (self->text,
+                          self->font,
+                          grub_gui_map_color (self->color),
+                          left_x,
+                          grub_font_get_ascent (self->font));
+  grub_gui_restore_viewport (&vpsave);
+}
+
+static void
+label_set_parent (void *vself, grub_gui_container_t parent)
+{
+  grub_gui_label_t self = vself;
+  self->parent = parent;
+}
+
+static grub_gui_container_t
+label_get_parent (void *vself)
+{
+  grub_gui_label_t self = vself;
+  return self->parent;
+}
+
+static void
+label_set_bounds (void *vself, const grub_video_rect_t *bounds)
+{
+  grub_gui_label_t self = vself;
+  self->bounds = *bounds;
+}
+
+static void
+label_get_bounds (void *vself, grub_video_rect_t *bounds)
+{
+  grub_gui_label_t self = vself;
+  *bounds = self->bounds;
+}
+
+static void
+label_get_preferred_size (void *vself, int *width, int *height)
+{
+  grub_gui_label_t self = vself;
+  *width = grub_font_get_string_width (self->font, self->text);
+  *height = (grub_font_get_ascent (self->font)
+             + grub_font_get_descent (self->font));
+
+  /* Allow preferred dimensions to override the computed dimensions.  */
+  if (self->preferred_width >= 0)
+    *width = self->preferred_width;
+  if (self->preferred_height >= 0)
+    *height = self->preferred_height;
+}
+
+static void
+label_set_property (void *vself, const char *name, const char *value)
+{
+  grub_gui_label_t self = vself;
+  if (grub_strcmp (name, "text") == 0)
+    {
+      grub_free (self->text);
+      if (! value)
+        value = "";
+      self->text = grub_strdup (value);
+    }
+  else if (grub_strcmp (name, "font") == 0)
+    {
+      self->font = grub_font_get (value);
+    }
+  else if (grub_strcmp (name, "color") == 0)
+    {
+      self->color = grub_gui_parse_color (value);
+    }
+  else if (grub_strcmp (name, "align") == 0)
+    {
+      int i;
+      for (i = 0; align_options[i]; i++)
+        {
+          if (grub_strcmp (align_options[i], value) == 0)
+            {
+              self->align = i;   /* Set the alignment mode.  */
+              break;
+            }
+        }
+    }
+  else if (grub_strcmp (name, "visible") == 0)
+    {
+      self->visible = grub_strcmp (value, "false") != 0;
+    }
+  else if (grub_strcmp (name, "preferred_size") == 0)
+    {
+      int w;
+      int h;
+      if (grub_gui_parse_2_tuple (value, &w, &h))
+        {
+          self->preferred_width = w;
+          self->preferred_height = h;
+        }
+    }
+  else if (grub_strcmp (name, "id") == 0)
+    {
+      grub_free (self->id);
+      if (value)
+        self->id = grub_strdup (value);
+      else
+        self->id = 0;
+    }
+}
+
+static struct grub_gui_component_ops label_ops =
+{
+  .destroy = label_destroy,
+  .get_id = label_get_id,
+  .is_instance = label_is_instance,
+  .paint = label_paint,
+  .set_parent = label_set_parent,
+  .get_parent = label_get_parent,
+  .set_bounds = label_set_bounds,
+  .get_bounds = label_get_bounds,
+  .get_preferred_size = label_get_preferred_size,
+  .set_property = label_set_property
+};
+
+grub_gui_component_t
+grub_gui_label_new (void)
+{
+  grub_gui_label_t label;
+  label = grub_malloc (sizeof (*label));
+  if (! label)
+    return 0;
+  label->label = &label_ops;
+  label->parent = 0;
+  label->bounds.x = 0;
+  label->bounds.y = 0;
+  label->bounds.width = 0;
+  label->bounds.height = 0;
+  label->id = 0;
+  label->preferred_width = -1;
+  label->preferred_height = -1;
+  label->visible = 1;
+  label->text = grub_strdup ("");
+  label->font = grub_font_get ("Helvetica 10");
+  label->color.red = 0;
+  label->color.green = 0;
+  label->color.blue = 0;
+  label->color.alpha = 255;
+  label->align = align_left;
+  return (grub_gui_component_t) label;
+}

=== added file 'gfxmenu/gui_progress_bar.c'
--- gfxmenu/gui_progress_bar.c	1970-01-01 00:00:00 +0000
+++ gfxmenu/gui_progress_bar.c	2008-08-11 14:36:33 +0000
@@ -0,0 +1,278 @@
+/* gui_progress_bar.c - GUI progress bar component.  */
+/*
+ *  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/mm.h>
+#include <grub/misc.h>
+#include <grub/gui.h>
+#include <grub/font.h>
+#include <grub/stringutil.h>
+
+struct grub_gui_progress_bar
+{
+  struct grub_gui_component_ops *progress_bar;
+
+  grub_gui_container_t parent;
+  grub_video_rect_t bounds;
+  char *id;
+  int preferred_width;
+  int preferred_height;
+  int visible;
+  int start;
+  int end;
+  int value;
+  int show_text;
+  char *text;
+  grub_font_t font;
+  grub_gui_color_t text_color;
+  grub_gui_color_t border_color;
+  grub_gui_color_t bg_color;
+  grub_gui_color_t fg_color;
+};
+
+typedef struct grub_gui_progress_bar *grub_gui_progress_bar_t;
+
+static void
+progress_bar_destroy (void *vself)
+{
+  grub_gui_progress_bar_t self = vself;
+  grub_free (self);
+}
+
+static const char *
+progress_bar_get_id (void *vself)
+{
+  grub_gui_progress_bar_t self = vself;
+  return self->id;
+}
+
+static int
+progress_bar_is_instance (void *vself __attribute__((unused)), const char *type)
+{
+  return grub_strcmp (type, "component") == 0;
+}
+
+static void
+progress_bar_paint (void *vself)
+{
+  grub_gui_progress_bar_t self = vself;
+  if (! self->visible)
+    return;
+
+  grub_video_rect_t vpsave;
+  grub_gui_set_viewport (&self->bounds, &vpsave);
+
+  /* Set the progress bar's frame.  */
+  grub_video_rect_t f;
+  f.x = 1;
+  f.y = 1;
+  f.width = self->bounds.width - 2;
+  f.height = self->bounds.height - 2;
+
+  /* Border.  */
+  grub_video_fill_rect (grub_gui_map_color (self->border_color),
+                        f.x - 1, f.y - 1,
+                        f.width + 2, f.height + 2);
+
+  /* Bar background.  */
+  int barwidth = (f.width
+                  * (self->value - self->start)
+                  / (self->end - self->start));
+  grub_video_fill_rect (grub_gui_map_color (self->bg_color),
+                        f.x + barwidth, f.y,
+                        f.width - barwidth, f.height);
+
+  /* Bar foreground.  */
+  grub_video_fill_rect (grub_gui_map_color (self->fg_color),
+                        f.x, f.y,
+                        barwidth, f.height);
+
+  const char *text = self->text;
+  if (text && self->show_text)
+    {
+      grub_font_t font = self->font;
+      grub_video_color_t text_color = grub_gui_map_color (self->text_color);
+
+      /* Center the text. */
+      int text_width = grub_font_get_string_width (font, text);
+      int x = f.x + (f.width - text_width) / 2;
+      int y = (f.y + (f.height - grub_font_get_descent (font)) / 2
+               + grub_font_get_ascent (font) / 2);
+      grub_video_draw_string (text, font, text_color, x, y);
+    }
+
+  grub_gui_restore_viewport (&vpsave);
+}
+
+static void
+progress_bar_set_parent (void *vself, grub_gui_container_t parent)
+{
+  grub_gui_progress_bar_t self = vself;
+  self->parent = parent;
+}
+
+static grub_gui_container_t
+progress_bar_get_parent (void *vself)
+{
+  grub_gui_progress_bar_t self = vself;
+  return self->parent;
+}
+
+static void
+progress_bar_set_bounds (void *vself, const grub_video_rect_t *bounds)
+{
+  grub_gui_progress_bar_t self = vself;
+  self->bounds = *bounds;
+}
+
+static void
+progress_bar_get_bounds (void *vself, grub_video_rect_t *bounds)
+{
+  grub_gui_progress_bar_t self = vself;
+  *bounds = self->bounds;
+}
+
+static void
+progress_bar_get_preferred_size (void *vself, int *width, int *height)
+{
+  grub_gui_progress_bar_t self = vself;
+
+  *width = 200;
+  *height = 28;
+
+  /* Allow preferred dimensions to override the progress_bar dimensions.  */
+  if (self->preferred_width >= 0)
+    *width = self->preferred_width;
+  if (self->preferred_height >= 0)
+    *height = self->preferred_height;
+}
+
+static void
+progress_bar_set_property (void *vself, const char *name, const char *value)
+{
+  grub_gui_progress_bar_t self = vself;
+  if (grub_strcmp (name, "value") == 0)
+    {
+      self->value = grub_strtol (value, 0, 10);
+    }
+  else if (grub_strcmp (name, "start") == 0)
+    {
+      self->start = grub_strtol (value, 0, 10);
+    }
+  else if (grub_strcmp (name, "end") == 0)
+    {
+      self->end = grub_strtol (value, 0, 10);
+    }
+  else if (grub_strcmp (name, "text") == 0)
+    {
+      grub_free (self->text);
+      if (! value)
+        value = "";
+      self->text = grub_strdup (value);
+    }
+  else if (grub_strcmp (name, "font") == 0)
+    {
+      self->font = grub_font_get (value);
+    }
+  else if (grub_strcmp (name, "text_color") == 0)
+    {
+      self->text_color = grub_gui_parse_color (value);
+    }
+  else if (grub_strcmp (name, "border_color") == 0)
+    {
+      self->border_color = grub_gui_parse_color (value);
+    }
+  else if (grub_strcmp (name, "bg_color") == 0)
+    {
+      self->bg_color = grub_gui_parse_color (value);
+    }
+  else if (grub_strcmp (name, "fg_color") == 0)
+    {
+      self->fg_color = grub_gui_parse_color (value);
+    }
+  else if (grub_strcmp (name, "preferred_size") == 0)
+    {
+      int w;
+      int h;
+      if (grub_gui_parse_2_tuple (value, &w, &h))
+        {
+          self->preferred_width = w;
+          self->preferred_height = h;
+        }
+    }
+  else if (grub_strcmp (name, "visible") == 0)
+    {
+      self->visible = grub_strcmp (value, "false") != 0;
+    }
+  else if (grub_strcmp (name, "show_text") == 0)
+    {
+      self->show_text = grub_strcmp (value, "false") != 0;
+    }
+  else if (grub_strcmp (name, "id") == 0)
+    {
+      grub_free (self->id);
+      if (value)
+        self->id = grub_strdup (value);
+      else
+        self->id = 0;
+    }
+}
+
+static struct grub_gui_component_ops progress_bar_ops =
+{
+  .destroy = progress_bar_destroy,
+  .get_id = progress_bar_get_id,
+  .is_instance = progress_bar_is_instance,
+  .paint = progress_bar_paint,
+  .set_parent = progress_bar_set_parent,
+  .get_parent = progress_bar_get_parent,
+  .set_bounds = progress_bar_set_bounds,
+  .get_bounds = progress_bar_get_bounds,
+  .get_preferred_size = progress_bar_get_preferred_size,
+  .set_property = progress_bar_set_property
+};
+
+grub_gui_component_t
+grub_gui_progress_bar_new (void)
+{
+  grub_gui_progress_bar_t self;
+  self = grub_malloc (sizeof (*self));
+  if (! self)
+    return 0;
+  self->progress_bar = &progress_bar_ops;
+  self->parent = 0;
+  self->bounds.x = 0;
+  self->bounds.y = 0;
+  self->bounds.width = 0;
+  self->bounds.height = 0;
+  self->id = 0;
+  self->preferred_width = -1;
+  self->preferred_height = -1;
+  self->visible = 1;
+  self->show_text = 1;
+  self->text = grub_strdup ("");
+  self->font = grub_font_get ("Helvetica 10");
+  grub_gui_color_t black = { .red = 0, .green = 0, .blue = 0, .alpha = 255 };
+  grub_gui_color_t gray = { .red = 128, .green = 128, .blue = 128, .alpha = 255 };
+  grub_gui_color_t lightgray = { .red = 200, .green = 200, .blue = 200, .alpha = 255 };
+  self->text_color = black;
+  self->border_color = black;
+  self->bg_color = gray;
+  self->fg_color = lightgray;
+  return (grub_gui_component_t) self;
+}

=== added file 'gfxmenu/gui_util.c'
--- gfxmenu/gui_util.c	1970-01-01 00:00:00 +0000
+++ gfxmenu/gui_util.c	2008-08-11 14:36:33 +0000
@@ -0,0 +1,64 @@
+/* gui_util.c - GUI utility 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/mm.h>
+#include <grub/misc.h>
+#include <grub/gui.h>
+#include <grub/stringutil.h>
+
+struct find_by_id_state
+{
+  const char *match_id;
+  grub_gui_component_callback match_callback;
+  void *match_userdata;
+};
+
+static
+void find_by_id_recursively (grub_gui_component_t component, void *userdata)
+{
+  struct find_by_id_state *state;
+  const char *id;
+
+  state = (struct find_by_id_state *) userdata;
+  id = component->ops->get_id (component);
+  if (id && grub_strcmp (id, state->match_id) == 0)
+    state->match_callback (component, state->match_userdata);
+
+  if (component->ops->is_instance (component, "container"))
+    {
+      grub_gui_container_t container;
+      container = (grub_gui_container_t) component;
+      container->ops->iterate_children (container,
+                                        find_by_id_recursively,
+                                        state);
+    }
+}
+
+void
+grub_gui_find_by_id (grub_gui_component_t root,
+                     const char *id,
+                     grub_gui_component_callback cb,
+                     void *userdata)
+{
+  struct find_by_id_state state;
+  state.match_id = id;
+  state.match_callback = cb;
+  state.match_userdata = userdata;
+  find_by_id_recursively (root, &state);
+}

=== added file 'gfxmenu/model.c'
--- gfxmenu/model.c	1970-01-01 00:00:00 +0000
+++ gfxmenu/model.c	2008-08-12 02:55:06 +0000
@@ -0,0 +1,191 @@
+/* model.c - Graphical menu interface MVC model. */
+/*
+ *  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/normal.h>
+#include <grub/menu.h>
+#include <grub/time.h>
+#include <grub/gfxmenu_model.h>
+
+/* Model type definition.  */
+struct grub_gfxmenu_model
+{
+  grub_menu_t menu;
+  int num_entries;
+  grub_menu_entry_t *entries;
+  int selected_entry_index;
+  int timeout_set;
+  grub_uint64_t timeout_start;
+  grub_uint64_t timeout_at;
+};
+
+
+grub_gfxmenu_model_t
+grub_gfxmenu_model_new (grub_menu_t menu)
+{
+  grub_gfxmenu_model_t model;
+
+  model = grub_malloc (sizeof (*model));
+  if (! model)
+    return 0;
+
+  model->menu = menu;
+  model->num_entries = menu->size;
+  model->entries = 0;
+  model->selected_entry_index = 0;
+  model->timeout_set = 0;
+  model->timeout_at = 0;
+  if (model->num_entries > 0)
+    {
+      model->entries = grub_malloc (model->num_entries
+                                    * sizeof (*model->entries));
+      if (! model->entries)
+        goto fail_and_free;
+
+      int i;
+      grub_menu_entry_t cur;
+      for (i = 0, cur = menu->entry_list;
+           i < model->num_entries;
+           i++, cur = cur->next)
+        {
+          model->entries[i] = cur;
+        }
+    }
+
+  return model;
+
+fail_and_free:
+  grub_free (model->entries);
+  grub_free (model);
+  return 0;
+}
+
+void
+grub_gfxmenu_model_destroy (grub_gfxmenu_model_t model)
+{
+  if (! model)
+    return;
+
+  grub_free (model->entries);
+  model->entries = 0;
+
+  grub_free (model);
+}
+
+grub_menu_t
+grub_gfxmenu_model_get_menu (grub_gfxmenu_model_t model)
+{
+  return model->menu;
+}
+
+void
+grub_gfxmenu_model_set_timeout (grub_gfxmenu_model_t model)
+{
+  int timeout_sec = grub_menu_get_timeout ();
+  if (timeout_sec >= 0)
+    {
+      model->timeout_start = grub_get_time_ms ();
+      model->timeout_at = model->timeout_start + timeout_sec * 1000;
+      model->timeout_set = 1;
+    }
+  else
+    {
+      model->timeout_set = 0;
+    }
+}
+
+void
+grub_gfxmenu_model_clear_timeout (grub_gfxmenu_model_t model)
+{
+  model->timeout_set = 0;
+  grub_menu_set_timeout (-1);
+}
+
+int
+grub_gfxmenu_model_get_timeout_ms (grub_gfxmenu_model_t model)
+{
+  if (!model->timeout_set)
+    return -1;
+
+  return model->timeout_at - model->timeout_start;
+}
+
+int
+grub_gfxmenu_model_get_timeout_remaining_ms (grub_gfxmenu_model_t model)
+{
+  if (!model->timeout_set)
+    return -1;
+
+  return model->timeout_at - grub_get_time_ms ();
+}
+
+int
+grub_gfxmenu_model_timeout_expired (grub_gfxmenu_model_t model)
+{
+  if (model->timeout_set
+      && grub_get_time_ms () >= model->timeout_at)
+    return 1;
+
+  return 0;
+}
+
+int
+grub_gfxmenu_model_get_num_entries (grub_gfxmenu_model_t model)
+{
+  return model->num_entries;
+}
+
+int
+grub_gfxmenu_model_get_selected_index (grub_gfxmenu_model_t model)
+{
+  return model->selected_entry_index;
+}
+
+void
+grub_gfxmenu_model_set_selected_index (grub_gfxmenu_model_t model, int index)
+{
+  model->selected_entry_index = index;
+}
+
+const char *
+grub_gfxmenu_model_get_entry_title (grub_gfxmenu_model_t model, int index)
+{
+  if (index < 0 || index >= model->num_entries)
+    {
+      grub_error (GRUB_ERR_OUT_OF_RANGE, "invalid menu index");
+      return 0;
+    }
+
+  return model->entries[index]->title;
+}
+
+grub_menu_entry_t
+grub_gfxmenu_model_get_entry (grub_gfxmenu_model_t model, int index)
+{
+  if (index < 0 || index >= model->num_entries)
+    {
+      grub_error (GRUB_ERR_OUT_OF_RANGE, "invalid menu index");
+      return 0;
+    }
+
+  return model->entries[index];
+}

=== added file 'gfxmenu/stringutil.c'
--- gfxmenu/stringutil.c	1970-01-01 00:00:00 +0000
+++ gfxmenu/stringutil.c	2008-08-12 02:55:06 +0000
@@ -0,0 +1,343 @@
+/* stringutil.c - String utilities. */
+/*
+ *  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/stringutil.h>
+#include <grub/types.h>
+#include <grub/misc.h>
+#include <grub/mm.h>
+
+/* Create a new NUL-terminated string on the heap as a substring of BUF.
+   The range of buf included is the half-open interval [START,END).
+   The index START is inclusive, END is exclusive.  */
+char *
+grub_new_substring (const char *buf,
+                    grub_size_t start, grub_size_t end)
+{
+  if (end < start)
+    return 0;
+  grub_size_t len = end - start;
+  char *s = grub_malloc (len + 1);
+  if (! s)
+    return 0;
+  grub_memcpy (s, buf + start, len);
+  s[len] = '\0';
+  return s;
+}
+
+/* Eliminate "." and ".." path elements from PATH.  A new heap-allocated
+   string is returned.  */
+static char *
+canonicalize_path (const char *path)
+{
+  int i;
+  const char *p;
+  char *newpath = 0;
+
+  /* Count the path components in path.  */
+  int components = 1;
+  for (p = path; *p; p++)
+    if (*p == '/')
+      components++;
+
+  char **path_array = grub_malloc (components * sizeof (*path_array));
+  if (! path_array)
+    return 0;
+
+  /* Initialize array elements to NULL pointers; in case once of the
+     allocations fails, the cleanup code can just call grub_free() for all
+     pointers in the array.  */
+  for (i = 0; i < components; i++)
+    path_array[i] = 0;
+
+  /* Parse the path into path_array.  */
+  p = path;
+  for (i = 0; i < components && p; i++)
+    {
+      /* Find the end of the path element.  */
+      const char *end = grub_strchr (p, '/');
+      if (!end)
+        end = p + grub_strlen (p);
+
+      /* Copy the element.  */
+      path_array[i] = grub_new_substring (p, 0, end - p);
+      if (!path_array[i])
+        goto cleanup;
+
+      /* Advance p to point to the start of the next element, or NULL.  */
+      if (*end)
+        p = end + 1;
+      else
+        p = 0;
+    }
+
+  /* Eliminate '.' and '..' elements from the path array.  */
+  int newpath_length = 0;
+  for (i = components - 1; i >= 0; --i)
+    {
+      if (! grub_strcmp (path_array[i], "."))
+        {
+          grub_free (path_array[i]);
+          path_array[i] = 0;
+        }
+      else if (! grub_strcmp (path_array[i], "..")
+               && i > 0)
+        {
+          /* Delete the '..' and the prior path element.  */
+          grub_free (path_array[i]);
+          path_array[i] = 0;
+          --i;
+          grub_free (path_array[i]);
+          path_array[i] = 0;
+        }
+      else
+        {
+          newpath_length += grub_strlen (path_array[i]) + 1;
+        }
+    }
+
+  /* Construct a new path string.  */
+  newpath = grub_malloc (newpath_length + 1);
+  if (! newpath)
+    goto cleanup;
+
+  newpath[0] = '\0';
+  char *newpath_end = newpath;
+  int first = 1;
+  for (i = 0; i < components; i++)
+    {
+      char *element = path_array[i];
+      if (element)
+        {
+          /* For all components but the first, prefix with a slash.  */
+          if (! first)
+            newpath_end = grub_stpcpy (newpath_end, "/");
+          newpath_end = grub_stpcpy (newpath_end, element);
+          first = 0;
+        }
+    }
+
+cleanup:
+  for (i = 0; i < components; i++)
+    grub_free (path_array[i]);
+  grub_free (path_array);
+
+  return newpath;
+}
+
+/* Return a new heap-allocated string representing to absolute path
+   to the file referred to by PATH.  If PATH is an absolute path, then
+   the returned path is a copy of PATH.  If PATH is a relative path, then
+   BASE is with PATH used to construct the absolute path.  */
+char *
+grub_resolve_relative_path (const char *base, const char *path)
+{
+  char *abspath;
+  char *canonpath;
+  char *p;
+
+  /* If PATH is an absolute path, then just use it as is.  */
+  if (path[0] == '/' || path[0] == '(')
+    return canonicalize_path (path);
+
+  abspath = grub_malloc (grub_strlen (base) + grub_strlen (path) + 1);
+  if (!abspath)
+    return 0;
+
+  /* Concatenate BASE and PATH.
+     Note that BASE is expected to have a trailing slash.  */
+  p = grub_stpcpy (abspath, base);
+  grub_stpcpy (p, path);
+
+  canonpath = canonicalize_path (abspath);
+  if (!canonpath)
+    return abspath;
+
+  grub_free (abspath);
+  return canonpath;
+}
+
+/* Get the path of the directory where the file at FILE_PATH is located.
+   FILE_PATH should refer to a file, not a directory.  The returned path
+   includes a trailing slash.
+   This does not handle GRUB "(hd0,0)" paths properly yet since it only
+   looks at slashes.  */
+char *
+grub_get_dirname (const char *file_path)
+{
+  int i;
+  int last_slash;
+
+  last_slash = -1;
+  for (i = grub_strlen (file_path) - 1; i >= 0; --i)
+    {
+      if (file_path[i] == '/')
+        {
+          last_slash = i;
+          break;
+        }
+    }
+  if (last_slash == -1)
+    return grub_strdup ("/");
+
+  return grub_new_substring (file_path, 0, last_slash + 1);
+}
+
+static __inline int
+isxdigit (char c)
+{
+  return ((c >= '0' && c <= '9')
+          || (c >= 'a' && c <= 'f')
+          || (c >= 'A' && c <= 'F'));
+}
+
+static int
+parse_hex_color_component (const char *s, unsigned start, unsigned end)
+{
+  unsigned len;
+  char buf[3];
+
+  len = end - start;
+  /* Check the limits so we don't overrun the buffer.  */
+  if (len < 1 || len > 2)
+    return 0;
+
+  if (len == 1)
+    {
+      buf[0] = s[start];   /* Get the first and only hex digit.  */
+      buf[1] = buf[0];     /* Duplicate the hex digit.  */
+    }
+  else if (len == 2)
+    {
+      buf[0] = s[start];
+      buf[1] = s[start + 1];
+    }
+
+  buf[2] = '\0';
+
+  return grub_strtoul (buf, 0, 16);
+}
+
+/* Parse a color string of the form "r, g, b", "#RGB", "#RGBA",
+   "#RRGGBB", or "#RRGGBBAA".  */
+grub_gui_color_t
+grub_gui_parse_color (const char *s)
+{
+  grub_gui_color_t c;
+
+  /* Skip whitespace.  */
+  while (*s && grub_isspace (*s))
+    s++;
+
+  if (*s == '#')
+    {
+      /* HTML-style.  Number if hex digits:
+         [6] #RRGGBB     [3] #RGB
+         [8] #RRGGBBAA   [4] #RGBA  */
+
+      s++;  /* Skip the '#'.  */
+      /* Count the hexits to determine the format.  */
+      int hexits = 0;
+      const char *end = s;
+      while (isxdigit (*end))
+        {
+          end++;
+          hexits++;
+        }
+
+      /* Parse the color components based on the format.  */
+      if (hexits == 3 || hexits == 4)
+        {
+          c.red = parse_hex_color_component (s, 0, 1);
+          c.green = parse_hex_color_component (s, 1, 2);
+          c.blue = parse_hex_color_component (s, 2, 3);
+          if (hexits == 4)
+            c.alpha = parse_hex_color_component (s, 3, 4);
+          else
+            c.alpha = 255;
+        }
+      else if (hexits == 6 || hexits == 8)
+        {
+          c.red = parse_hex_color_component (s, 0, 2);
+          c.green = parse_hex_color_component (s, 2, 4);
+          c.blue = parse_hex_color_component (s, 4, 6);
+          if (*s)
+            c.alpha = parse_hex_color_component (s, 6, 8);
+          else
+            c.alpha = 255;
+        }
+      else
+        goto fail;
+    }
+  else
+    {
+      /* Comma separated decimal values.  */
+      c.red = grub_strtoul (s, 0, 0);
+      if ((s = grub_strchr (s, ',')) == 0)
+        goto fail;
+      s++;
+      c.green = grub_strtoul (s, 0, 0);
+      if ((s = grub_strchr (s, ',')) == 0)
+        goto fail;
+      s++;
+      c.blue = grub_strtoul (s, 0, 0);
+      if ((s = grub_strchr (s, ',')) == 0)
+        c.alpha = 255;
+      else
+        {
+          s++;
+          c.alpha = grub_strtoul (s, 0, 0);
+        }
+    }
+
+  return c;
+
+fail:
+  c.red = 0;
+  c.green = 0;
+  c.blue = 0;
+  c.alpha = 255;
+  return c;
+}
+
+/* Parse a value in the form "(x, y)", storing the first element (x) into
+ *PX and the second element (y) into *PY.
+ Returns 1 if successful, 0 if failed to parse.  */
+int
+grub_gui_parse_2_tuple (const char *s, int *px, int *py)
+{
+  int x;
+  int y;
+
+  while (*s && grub_isspace (*s))
+    s++;
+  if (*s != '(')
+    return 0;
+  s++;  /* Skip the opening parentheses.  */
+  if (! *s)
+    return 0;
+  x = grub_strtoul (s, 0, 10);
+  if ((s = grub_strchr (s, ',')) == 0)
+    return 0;
+  s++;  /* Skip the element separator (the comma).  */
+  y = grub_strtoul (s, 0, 10);
+
+  *px = x;
+  *py = y;
+  return 1;
+}

=== added file 'gfxmenu/theme_loader.c'
--- gfxmenu/theme_loader.c	1970-01-01 00:00:00 +0000
+++ gfxmenu/theme_loader.c	2008-08-12 02:55:06 +0000
@@ -0,0 +1,587 @@
+/* theme_loader.c - Theme file loader for gfxmenu.  */
+/*
+ *  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/file.h>
+#include <grub/misc.h>
+#include <grub/mm.h>
+#include <grub/err.h>
+#include <grub/dl.h>
+#include <grub/video.h>
+#include <grub/stringutil.h>
+#include <grub/bitmap.h>
+#include <grub/bitmap_scale.h>
+#include <grub/gfxwidgets.h>
+#include <grub/gfxmenu_view.h>
+#include <grub/gui.h>
+
+
+/* Construct a new box widget using PATTERN to find the pixmap files for it,
+   storing the new widget at *BOXPTR.  PATTERN should be of the form:
+   "somewhere/style*.png".  The '*' then gets substituted with the various
+   pixmap names that the widget uses.
+   Return zero on failure, nonzero on success.  */
+static int
+theme_set_box (grub_gfxmenu_box_t *boxptr,
+               const char *pattern, const char *theme_dir)
+{
+  char *abspattern;
+  char *prefix;
+  char *suffix;
+  char *star;
+  grub_gfxmenu_box_t box;
+
+  abspattern = grub_resolve_relative_path (theme_dir, pattern);
+  if (! abspattern)
+    return 0;
+
+  star = grub_strchr (abspattern, '*');
+  if (! star)
+    {
+      grub_free (abspattern);
+      return 0;
+    }
+
+  /* Prefix:  Get the part before the '*'.  */
+  prefix = grub_malloc (star - abspattern + 1);
+  if (! prefix)
+    {
+      grub_free (abspattern);
+      return 0;
+    }
+
+  grub_memcpy (prefix, abspattern, star - abspattern);
+  prefix[star - abspattern] = '\0';
+
+  /* Suffix:  Everything after the '*' is the suffix.  */
+  suffix = star + 1;
+
+  box = grub_gfxmenu_create_box (prefix, suffix);
+  grub_free (abspattern);   /* Note: suffix, star point into abspattern.  */
+  grub_free (prefix);
+  if (! box)
+    return 0;
+
+  if (*boxptr)
+    (*boxptr)->destroy (*boxptr);
+  *boxptr = box;
+  return 1;
+}
+
+/* Set the specified property NAME on the view to the given string VALUE.
+   This function takes ownership of both NAME and VALUE, so the caller
+   should pass pointers to new heap-allocated strings.  */
+static void
+theme_set_string (grub_gfxmenu_view_t view,
+                  const char *name,
+                  const char *value,
+                  const char *theme_dir)
+{
+  if (! grub_strcmp ("title-font", name))
+    view->title_font = grub_font_get (value);
+  else if (! grub_strcmp ("item-font", name))
+    view->item_font = grub_font_get (value);
+  else if (! grub_strcmp ("selected-item-font", name))
+    view->selected_item_font = grub_font_get (value);
+  else if (! grub_strcmp ("status-font", name))
+    view->status_font = grub_font_get (value);
+  else if (! grub_strcmp ("terminal-font", name))
+    {
+      grub_free (view->terminal_font_name);
+      view->terminal_font_name = grub_strdup (value);
+    }
+  else if (! grub_strcmp ("title-color", name))
+    view->title_color = grub_gui_parse_color (value);
+  else if (! grub_strcmp ("item-color", name))
+    view->item_color = grub_gui_parse_color (value);
+  else if (! grub_strcmp ("selected-item-color", name))
+    view->selected_item_color = grub_gui_parse_color (value);
+  else if (! grub_strcmp ("status-color", name))
+    view->status_color = grub_gui_parse_color (value);
+  else if (! grub_strcmp ("status-bg-color", name))
+    view->status_bg_color = grub_gui_parse_color (value);
+  else if (! grub_strcmp ("desktop-image", name))
+    {
+      struct grub_video_bitmap *raw_bitmap;
+      struct grub_video_bitmap *scaled_bitmap;
+      char *path;
+      path = grub_resolve_relative_path (theme_dir, value);
+      if (! path)
+        return;
+      if (grub_video_bitmap_load (&raw_bitmap, path) != GRUB_ERR_NONE)
+        {
+          grub_free (path);
+          return;
+        }
+      grub_free(path);
+      grub_video_bitmap_create_scaled (&scaled_bitmap,
+                                       view->screen.width,
+                                       view->screen.height,
+                                       raw_bitmap,
+                                       GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST);
+      grub_video_bitmap_destroy (raw_bitmap);
+      if (!scaled_bitmap)
+        return;
+
+      grub_video_bitmap_destroy (view->desktop_image);
+      view->desktop_image = scaled_bitmap;
+    }
+  else if (! grub_strcmp ("desktop-color", name))
+    view->desktop_color = grub_gui_parse_color (value);
+  else if (! grub_strcmp ("menu-box", name))
+    theme_set_box (&view->menu_box, value, theme_dir);
+  else if (! grub_strcmp ("selected-item-box", name))
+    theme_set_box (&view->selected_item_box, value, theme_dir);
+  else if (! grub_strcmp ("terminal-box", name))
+    theme_set_box (&view->terminal_box, value, theme_dir);
+  else if (! grub_strcmp ("title-text", name))
+    {
+      grub_free (view->title_text);
+      view->title_text = grub_strdup (value);
+    }
+}
+
+/* Set the specified property NAME on the view to the given numeric VALUE.
+   This function takes ownership NAME, so the caller should pass a pointer
+   to a new heap-allocated string.  */
+static void
+theme_set_number (grub_gfxmenu_view_t view, const char *name, int value)
+{
+  if (! grub_strcmp ("icon-width", name))
+    view->icon_width = value;
+  else if (! grub_strcmp ("icon-height", name))
+    view->icon_height = value;
+  else if (! grub_strcmp ("item-height", name))
+    view->item_height = value;
+  else if (! grub_strcmp ("item-padding", name))
+    view->item_padding = value;
+  else if (! grub_strcmp ("item-icon-space", name))
+    view->item_icon_space = value;
+  else if (! grub_strcmp ("item-spacing", name))
+    view->item_spacing = value;
+}
+
+struct parsebuf
+{
+  char *buf;
+  int pos;
+  int len;
+  char *theme_dir;
+  grub_gfxmenu_view_t view;
+};
+
+static void
+advance_to_next_line (struct parsebuf *p)
+{
+  /* Eat characters up to the newline.  */
+  while (p->pos < p->len && p->buf[p->pos] != '\n')
+    p->pos++;
+  p->pos++;   /* Eat the newline.  */
+}
+
+static void
+skip_whitespace (struct parsebuf *p)
+{
+  while (p->pos < p->len
+         && (p->buf[p->pos] == ' '
+             || p->buf[p->pos] == '\t'
+             || p->buf[p->pos] == '\r'
+             || p->buf[p->pos] == '\n'
+             || p->buf[p->pos] == '\f'))
+    p->pos++;
+}
+
+static int
+read_char (struct parsebuf *p)
+{
+  if (p->pos < p->len)
+    return p->buf[p->pos++];
+  else
+    return -1;
+}
+
+static char *
+read_identifier (struct parsebuf *p)
+{
+  int start;
+  int end;
+
+  skip_whitespace (p);
+  start = p->pos;
+  while (p->pos < p->len
+         && (grub_isalpha(p->buf[p->pos])
+             || grub_isdigit(p->buf[p->pos])
+             || p->buf[p->pos] == '_'
+             || p->buf[p->pos] == '-'))
+    p->pos++;
+  end = p->pos;
+  if (end - start < 1)
+    return 0;
+
+  return grub_new_substring (p->buf, start, end);
+}
+
+static char *
+read_expression (struct parsebuf *p)
+{
+  int start;
+  int end;
+
+  skip_whitespace (p);
+  if (p->pos < p->len && p->buf[p->pos] == '"')
+    {
+      /* Read as a quoted string.  */
+      /* The quotation marks are not included in the expression value.  */
+      p->pos++;
+      start = p->pos;
+      while (p->pos < p->len
+             && p->buf[p->pos] != '"')
+        p->pos++;
+      end = p->pos;
+      if (p->pos < p->len)
+        p->pos++;   /* Skip the terminating quotation mark.  */
+    }
+  else if (p->pos < p->len && p->buf[p->pos] == '(')
+    {
+      /* Read as a parenthesized string -- for tuples/coordinates.  */
+      /* The parentheses are included in the expression value.  */
+      start = p->pos;
+      while (p->pos < p->len)
+        {
+          char c = p->buf[p->pos];
+          p->pos++;
+          if (c == ')')
+            break;
+        }
+      end = p->pos;
+    }
+  else if (p->pos < p->len)
+    {
+      /* Read as a single word -- for numeric values.  */
+      start = p->pos;
+      while (p->pos < p->len
+             && ! grub_isspace (p->buf[p->pos]))
+        p->pos++;
+      end = p->pos;
+    }
+  else
+    return 0;
+
+  return grub_new_substring (p->buf, start, end);
+}
+
+/* Read a GUI object specification from the theme file.
+   Any components created will be added to the GUI container PARENT.
+   Returns 0 on success, nonzero on failure.  */
+static int
+read_object (struct parsebuf *p, grub_gui_container_t parent)
+{
+  char *name;
+  name = read_identifier (p);
+  if (! name)
+    goto fail;
+
+  grub_gui_component_t component = 0;
+  if (grub_strcmp (name, "label") == 0)
+    {
+      component = grub_gui_label_new ();
+    }
+  else if (grub_strcmp (name, "image") == 0)
+    {
+      component = grub_gui_image_new ();
+    }
+  else if (grub_strcmp (name, "vbox") == 0)
+    {
+      component = (grub_gui_component_t) grub_gui_vbox_new ();
+    }
+  else if (grub_strcmp (name, "hbox") == 0)
+    {
+      component = (grub_gui_component_t) grub_gui_hbox_new ();
+    }
+  else if (grub_strcmp (name, "canvas") == 0)
+    {
+      component = (grub_gui_component_t) grub_gui_canvas_new ();
+    }
+  else if (grub_strcmp (name, "progress_bar") == 0)
+    {
+      component = (grub_gui_component_t) grub_gui_progress_bar_new ();
+    }
+  else
+    {
+      /* Unknown type; ignore.  */
+      goto fail;   /* We should actually parse the structure.  */
+    }
+
+  if (component)
+    {
+      grub_video_rect_t r = { .x=0, .y=0, .width=-1, .height=-1 };
+      component->ops->set_bounds (component, &r);
+      parent->ops->add (parent, component);
+    }
+
+  skip_whitespace (p);
+  if (read_char (p) != '{')
+    goto fail;
+
+  while (p->pos < p->len)
+    {
+      skip_whitespace (p);
+
+      /* Check whether the end has been encountered.  */
+      if (p->pos < p->len
+          && p->buf[p->pos] == '}')
+        {
+          p->pos++;   /* Skip the closing brace.  */
+          break;
+        }
+
+      if (p->pos < p->len
+          && p->buf[p->pos] == '#')
+        {
+          /* Skip comments.  */
+          advance_to_next_line (p);
+          continue;
+        }
+
+      if (p->pos < p->len
+          && p->buf[p->pos] == '+')
+        {
+          p->pos++;  /* Skip the '+'.  */
+          /* Check whether this component is a container.  */
+          if (component->ops->is_instance (component, "container"))
+            {
+              /* Read the sub-object recursively and add it as a child.  */
+              if (read_object (p, (grub_gui_container_t) component) != 0)
+                goto fail;
+              /* After reading the sub-object, resume parsing, expecting
+                 another property assignment or sub-object definition.  */
+              continue;
+            }
+          else
+            {
+              /* error ("Attempted to add object to non-container.") */
+              goto fail;
+            }
+        }
+
+      char *property;
+      property = read_identifier (p);
+      if (! property)
+        goto fail;
+
+      skip_whitespace (p);
+      if (read_char (p) != '=')
+        {
+          grub_free (property);
+          goto fail;
+        }
+      skip_whitespace (p);
+
+      char *value;
+      value = read_expression (p);
+      if (! value)
+        {
+          grub_free (property);
+          goto fail;
+        }
+
+      /* Handle the property value.  */
+      if (grub_strcmp (property, "position") == 0)
+        {
+          /* Special case for position value.  */
+          int x;
+          int y;
+
+          if (grub_gui_parse_2_tuple (value, &x, &y))
+            {
+              grub_video_rect_t r;
+              component->ops->get_bounds (component, &r);
+              r.x = x;
+              r.y = y;
+              component->ops->set_bounds (component, &r);
+            }
+        }
+      else if (grub_strcmp (property, "size") == 0)
+        {
+          /* Special case for size value.  */
+          int w;
+          int h;
+
+          if (grub_gui_parse_2_tuple (value, &w, &h))
+            {
+              grub_video_rect_t r;
+              component->ops->get_bounds (component, &r);
+              r.width = w;
+              r.height = h;
+              component->ops->set_bounds (component, &r);
+            }
+        }
+      else
+        {
+          /* General property handling.  */
+          component->ops->set_property (component, property, value);
+        }
+
+      grub_free (value);
+      grub_free (property);
+    }
+
+  /* Set the object's size to its preferred size unless the user has
+     explicitly specified the size.  */
+  grub_video_rect_t r;
+  component->ops->get_bounds (component, &r);
+  if (r.width == -1 || r.height == -1)
+    {
+      component->ops->get_preferred_size (component, &r.width, &r.height);
+      component->ops->set_bounds (component, &r);
+    }
+
+  return 0;
+
+fail:
+  grub_free (name);
+
+  /* Once the opening brace has been found, we always jump past the closing
+     brace if an error is encountered.  */
+  while (p->pos < p->len
+         && p->buf[p->pos] != '}')
+    p->pos++;
+  return 1;
+}
+
+static void
+read_property (struct parsebuf *p)
+{
+  char *name;
+
+  name = read_identifier (p);  /* Read the property name.  */
+  if (! name)
+    goto next_line;
+
+  skip_whitespace (p);         /* Skip whitespace before separator.  */
+
+  if (read_char (p) != ':')    /* Read separator.  */
+    goto next_line;
+
+  skip_whitespace (p);         /* Skip whitespace after separator.  */
+
+  /* Get the value based on its type.  */
+  if (p->pos < p->len && p->buf[p->pos] == '"')
+    {
+      /* String value.  (e.g., '"My string"')  */
+      char *value = read_expression (p);
+      if (! value)
+        goto next_line;
+      theme_set_string (p->view, name, value, p->theme_dir);
+      grub_free (value);
+    }
+  else if (p->pos < p->len && grub_isdigit(p->buf[p->pos]))
+    {
+      /* Numeric value.  (e.g., '123')  */
+      int value;
+      char *value_str = read_expression (p);
+      if (! value_str)
+        goto next_line;
+
+      value = grub_strtoul (value_str, 0, 0);
+      grub_free (value_str);
+      theme_set_number (p->view, name, value);
+    }
+
+next_line:
+  grub_free (name);
+  advance_to_next_line (p);
+}
+
+/* Set properties on the view based on settings from the specified
+   theme file.  Returns nonzero on success, zero on failure.  */
+int
+grub_gfxmenu_view_load_theme (grub_gfxmenu_view_t view, const char *theme_path)
+{
+  grub_file_t file;
+  struct parsebuf p;
+
+  p.view = view;
+  p.theme_dir = grub_get_dirname (theme_path);
+
+  file = grub_file_open (theme_path);
+  if (!file)
+    {
+      grub_free (p.theme_dir);
+      return 0;
+    }
+
+  p.len = grub_file_size (file);
+  p.buf = grub_malloc (p.len);
+  if (! p.buf)
+    {
+      grub_file_close (file);
+      grub_free (p.theme_dir);
+      return 0;
+    }
+  if (grub_file_read (file, p.buf, p.len) != p.len)
+    {
+      grub_free (p.buf);
+      grub_file_close (file);
+      grub_free (p.theme_dir);
+      return 0;
+    }
+
+  if (view->canvas)
+    view->canvas->ops->component.destroy (view->canvas);
+  view->canvas = grub_gui_canvas_new ();
+
+  p.pos = 0;
+  while (p.pos < p.len)
+    {
+      /* Skip comments (lines beginning with #).  */
+      if (p.pos < p.len && p.buf[p.pos] == '#')
+        {
+          advance_to_next_line (&p);
+          continue;
+        }
+
+      /* Get name.  */
+      /* Find the first non-whitespace character.  */
+      skip_whitespace (&p);
+
+      /* Handle the content.  */
+      if (p.buf[p.pos] == '+')
+        {
+          p.pos++;   /* Skip the '+'.  */
+          read_object (&p, view->canvas);
+        }
+      else
+        {
+          read_property (&p);
+        }
+    }
+
+  /* Set the new theme path.  */
+  grub_free (view->theme_path);
+  view->theme_path = grub_strdup (theme_path);
+
+  /* There may be new icons for this theme.  */
+  grub_gfxmenu_view_free_icon_cache (view);
+
+  grub_free (p.buf);
+  grub_file_close (file);
+  grub_free (p.theme_dir);
+  return 1;
+}

=== added file 'gfxmenu/view.c'
--- gfxmenu/view.c	1970-01-01 00:00:00 +0000
+++ gfxmenu/view.c	2008-08-12 02:55:06 +0000
@@ -0,0 +1,725 @@
+/* view.c - Graphical menu interface MVC view. */
+/*
+ *  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/file.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/stringutil.h>
+#include <grub/gfxterm.h>
+#include <grub/bitmap.h>
+#include <grub/bitmap_scale.h>
+#include <grub/term.h>
+#include <grub/gfxwidgets.h>
+#include <grub/time.h>
+#include <grub/menu.h>
+#include <grub/menu_viewer.h>
+#include <grub/gfxmenu_view.h>
+#include <grub/gui.h>
+
+/* The component ID identifying GUI components to be updated as the timeout
+   status changes.  */
+#define TIMEOUT_COMPONENT_ID "__timeout__"
+
+static void init_terminal (grub_gfxmenu_view_t view);
+static void destroy_terminal (void);
+static grub_err_t set_graphics_mode (void);
+static grub_err_t set_text_mode (void);
+
+/* Create a new view object, loading the theme specified by THEME_PATH and
+   associating MODEL with the view.  */
+grub_gfxmenu_view_t
+grub_gfxmenu_view_new (const char *theme_path, grub_gfxmenu_model_t model)
+{
+  grub_gfxmenu_view_t view;
+
+  view = grub_malloc (sizeof (*view));
+  if (! view)
+    return 0;
+
+  set_graphics_mode ();
+  grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
+  grub_video_get_viewport ((unsigned *) &view->screen.x,
+                           (unsigned *) &view->screen.y,
+                           (unsigned *) &view->screen.width,
+                           (unsigned *) &view->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 (0, 0, 0),
+                        view->screen.x, view->screen.y,
+                        view->screen.width, view->screen.height);
+  grub_video_swap_buffers ();
+
+  grub_font_t default_font;
+  grub_gui_color_t default_fg_color;
+  grub_gui_color_t default_bg_color;
+
+  default_font = grub_font_get ("Helvetica 12");
+  default_fg_color = grub_gui_color_rgb (0, 0, 0);
+  default_bg_color = grub_gui_color_rgb (255, 255, 255);
+
+  view->icon_width = 32;
+  view->icon_height = 32;
+  view->item_height = 42;
+  view->item_padding = 14;
+  view->item_icon_space = 4;
+  view->item_spacing = 16;
+  view->title_font = default_font;
+  view->item_font = default_font;
+  view->selected_item_font = default_font;
+  view->status_font = default_font;
+  view->terminal_font_name = grub_strdup ("Fixed 10");
+  view->title_color = default_fg_color;
+  view->item_color = default_fg_color;
+  view->selected_item_color = default_fg_color;
+  view->status_color = default_bg_color;
+  view->status_bg_color = default_fg_color;
+  view->desktop_image = 0;
+  view->desktop_color = default_bg_color;
+  view->menu_box = 0;
+  view->selected_item_box = 0;
+  view->terminal_box = 0;
+  view->title_text = grub_strdup ("GRUB Boot Menu");
+  view->progress_message_text = 0;
+  view->theme_path = 0;
+  view->icon_cache.class_name = 0;
+  view->icon_cache.bitmap = 0;
+  view->icon_cache.next = 0;
+
+  view->model = model;
+
+  view->canvas = 0;
+
+  if (! grub_gfxmenu_view_load_theme (view, theme_path))
+    {
+      grub_gfxmenu_view_destroy (view);
+      return 0;
+    }
+
+  init_terminal (view);
+
+  return view;
+}
+
+void
+grub_gfxmenu_view_free_icon_cache (grub_gfxmenu_view_t view)
+{
+  grub_gfxmenu_view_icon_t cur;
+  grub_gfxmenu_view_icon_t next;
+  for (cur = view->icon_cache.next; cur; cur = next)
+    {
+      next = cur->next;
+      grub_free (cur);
+    }
+  view->icon_cache.next = 0;
+}
+
+/* Destroy the view object.  All used memory is freed.  */
+void
+grub_gfxmenu_view_destroy (grub_gfxmenu_view_t view)
+{
+  grub_video_bitmap_destroy (view->desktop_image);
+  if (view->menu_box)
+    view->menu_box->destroy (view->menu_box);
+  if (view->selected_item_box)
+    view->selected_item_box->destroy (view->selected_item_box);
+  if (view->terminal_box)
+    view->terminal_box->destroy (view->terminal_box);
+  grub_free (view->terminal_font_name);
+  grub_free (view->title_text);
+  grub_free (view->progress_message_text);
+  grub_free (view->theme_path);
+  grub_gfxmenu_view_free_icon_cache (view);
+  if (view->canvas)
+    view->canvas->ops->component.destroy (view->canvas);
+  grub_free (view);
+
+  set_text_mode ();
+  destroy_terminal ();
+}
+
+/* Sets MESSAGE as the progress message for the view.
+   MESSAGE can be 0, in which case no message is displayed.  */
+static void
+set_progress_message (grub_gfxmenu_view_t view, const char *message)
+{
+  grub_free (view->progress_message_text);
+  if (message)
+    view->progress_message_text = grub_strdup (message);
+  else
+    view->progress_message_text = 0;
+}
+
+static void
+draw_background (grub_gfxmenu_view_t view)
+{
+  if (view->desktop_image)
+    {
+      struct grub_video_bitmap *img = view->desktop_image;
+      grub_video_blit_bitmap (img, GRUB_VIDEO_BLIT_REPLACE,
+                              view->screen.x, view->screen.y, 0, 0,
+                              grub_video_bitmap_get_width (img),
+                              grub_video_bitmap_get_height (img));
+    }
+  else
+    {
+      grub_video_fill_rect (grub_gui_map_color (view->desktop_color),
+                            view->screen.x, view->screen.y,
+                            view->screen.width, view->screen.height);
+    }
+}
+
+static const char icon_extension[] = ".png";
+
+static struct grub_video_bitmap *
+try_loading_icon (grub_gfxmenu_view_t view,
+                  const char *dir, const char *class_name)
+{
+  char *path = grub_malloc (grub_strlen (dir)
+                            + grub_strlen (class_name)
+                            + grub_strlen (icon_extension)
+                            + 1);
+  if (! path)
+    return 0;
+
+  grub_strcpy (path, dir);
+  grub_strcat (path, class_name);
+  grub_strcat (path, icon_extension);
+
+  struct grub_video_bitmap *raw_bitmap;
+  grub_video_bitmap_load (&raw_bitmap, path);
+  grub_free (path);
+  grub_errno = GRUB_ERR_NONE;  /* Critical to clear the error!!  */
+  if (! raw_bitmap)
+    return 0;
+
+  struct grub_video_bitmap *scaled_bitmap;
+  grub_video_bitmap_create_scaled (&scaled_bitmap,
+                                   view->icon_width, view->icon_height,
+                                   raw_bitmap,
+                                   GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST);
+  grub_video_bitmap_destroy (raw_bitmap);
+  if (! scaled_bitmap)
+    return 0;
+
+  return scaled_bitmap;
+}
+
+static struct grub_video_bitmap *
+get_icon_by_class (grub_gfxmenu_view_t view, const char *class_name)
+{
+
+  /* First check the view's icon cache.  */
+  grub_gfxmenu_view_icon_t vicon;
+  for (vicon = view->icon_cache.next; vicon; vicon = vicon->next)
+    {
+      if (grub_strcmp (vicon->class_name, class_name) == 0)
+        return vicon->bitmap;
+    }
+
+  /* Otherwise, we search for an icon to load.  */
+  if (! view->theme_path)
+    return 0;
+
+  char *theme_dir = grub_get_dirname (view->theme_path);
+  char *icons_dir;
+  struct grub_video_bitmap *icon;
+  icon = 0;
+  /* First try the theme's own icons, from "grub/themes/NAME/icons/"  */
+  icons_dir = grub_resolve_relative_path (theme_dir, "icons/");
+  if (icons_dir)
+    {
+      icon = try_loading_icon (view, icons_dir, class_name);
+      grub_free (icons_dir);
+    }
+  if (! icon)
+    {
+      /* If the theme doesn't have an appropriate icon, check in
+         "grub/themes/icons".  */
+      icons_dir = grub_resolve_relative_path (theme_dir, "../icons/");
+      if (icons_dir)
+        {
+          icon = try_loading_icon (view, icons_dir, class_name);
+          grub_free (icons_dir);
+        }
+    }
+  grub_free (theme_dir);
+
+  if (! icon)
+    return 0;
+
+  vicon = grub_malloc (sizeof (*vicon));
+  if (! vicon)
+    {
+      grub_video_bitmap_destroy (icon);
+      return 0;
+    }
+  vicon->class_name = class_name;
+  vicon->bitmap = icon;
+  vicon->next = view->icon_cache.next;
+  view->icon_cache.next = vicon;   /* Link it into the cache.  */
+  return vicon->bitmap;
+}
+
+static struct grub_video_bitmap *
+get_item_icon (grub_gfxmenu_view_t view, int item_index)
+{
+  grub_menu_entry_t entry;
+  entry = grub_gfxmenu_model_get_entry (view->model, item_index);
+  if (! entry)
+    return 0;
+
+  struct grub_menu_entry_class *c;
+  struct grub_video_bitmap *icon;
+  icon = 0;
+  for (c = entry->classes->next; c && ! icon; c = c->next)
+    icon = get_icon_by_class (view, c->name);
+  return icon;
+}
+
+static void
+draw_menu (grub_gfxmenu_view_t view)
+{
+  int boxpad = view->item_padding;
+  int icon_text_space = view->item_icon_space;
+  int item_vspace = view->item_spacing;
+
+  int ascent = grub_font_get_ascent (view->item_font);
+  int descent = grub_font_get_descent (view->item_font);
+  int item_height = view->item_height;
+
+  int num_items = grub_gfxmenu_model_get_num_entries (view->model);
+  grub_video_rect_t r;
+  r.width = view->screen.width * 4 / 5;
+  /* Set the menu box height to fit the items.  */
+  r.height = (item_height * num_items
+              + item_vspace * (num_items - 1)
+              + 2 * boxpad);
+  r.x = (view->screen.width - r.width) / 2;
+  r.y = (view->screen.height - r.height) / 2;
+  view->menu_box->set_content_size (view->menu_box, r.width, r.height);
+
+  int menu_box_left_pad = view->menu_box->get_left_pad (view->menu_box);
+  int menu_box_top_pad = view->menu_box->get_top_pad (view->menu_box);
+  view->menu_box->draw (view->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++)
+    {
+      int is_selected =
+        (i == grub_gfxmenu_model_get_selected_index (view->model));
+
+      if (is_selected)
+        {
+          view->selected_item_box->set_content_size (view->selected_item_box,
+                                                     r.width - 2 * boxpad,
+                                                     item_height);
+          int leftpad = view->selected_item_box->get_left_pad (view->selected_item_box);
+          int toppad = view->selected_item_box->get_top_pad (view->selected_item_box);
+          view->selected_item_box->draw (view->selected_item_box,
+                                         item_left - leftpad,
+                                         item_top - toppad);
+        }
+
+      struct grub_video_bitmap *icon;
+      if ((icon = get_item_icon (view, i)) != 0)
+        grub_video_blit_bitmap (icon, GRUB_VIDEO_BLIT_BLEND,
+                                item_left,
+                                item_top + (item_height - view->icon_height) / 2,
+                                0, 0, view->icon_width, view->icon_height);
+
+      const char *item_title =
+        grub_gfxmenu_model_get_entry_title (view->model, i);
+      grub_video_draw_string (item_title,
+                              (is_selected
+                               ? view->selected_item_font
+                               : view->item_font),
+                              grub_gui_map_color (is_selected
+                                                  ? view->selected_item_color
+                                                  : view->item_color),
+                              item_left + view->icon_width + icon_text_space,
+                              (item_top + (item_height - (ascent + descent))
+                               / 2 + ascent));
+
+      item_top += item_height + item_vspace;
+    }
+}
+
+static void
+draw_title (grub_gfxmenu_view_t view)
+{
+  if (! view->title_text)
+    return;
+
+  /* Center the title. */
+  int title_width = grub_font_get_string_width (view->title_font,
+                                                view->title_text);
+  int x = (view->screen.width - title_width) / 2;
+  int y = 40 + grub_font_get_ascent (view->title_font);
+  grub_video_draw_string (view->title_text,
+                          view->title_font,
+                          grub_gui_map_color (view->title_color),
+                          x, y);
+}
+
+static void
+draw_status (grub_gfxmenu_view_t view)
+{
+  int descent = grub_font_get_descent (view->status_font);
+  int ascent = grub_font_get_ascent (view->status_font);
+  int vpad = 5;
+  int textheight = descent + ascent + 1;
+  int h = 2 * vpad + 2 * textheight;
+
+  grub_video_fill_rect (grub_gui_map_color (view->status_bg_color),
+                        0, view->screen.height - h,
+                        view->screen.width, view->screen.height - 1);
+
+  int texty = view->screen.height - h + vpad + ascent;
+  grub_video_draw_string ("Select an item with the arrow keys and "
+                          "press Enter to boot.",
+                          view->status_font,
+                          grub_gui_map_color (view->status_color),
+                          30, texty);
+  texty += textheight;
+  grub_video_draw_string ("Press:  'c' for command line; 't' to switch to "
+                          "non-graphical menu.",
+                          view->status_font,
+                          grub_gui_map_color (view->status_color),
+                          30, texty);
+}
+
+struct progress_value_data
+{
+  const char *visible;
+  const char *start;
+  const char *end;
+  const char *value;
+  const char *text;
+};
+
+static void
+update_timeout_visit (grub_gui_component_t component,
+                      void *userdata)
+{
+  struct progress_value_data *pv;
+  pv = (struct progress_value_data *) userdata;
+  component->ops->set_property (component, "visible", pv->visible);
+  component->ops->set_property (component, "start", pv->start);
+  component->ops->set_property (component, "end", pv->end);
+  component->ops->set_property (component, "value", pv->value);
+  component->ops->set_property (component, "text", pv->text);
+}
+
+static void
+update_timeout (grub_gfxmenu_view_t view)
+{
+  char startbuf[20];
+  char valuebuf[20];
+  char msgbuf[120];
+
+  int timeout = grub_gfxmenu_model_get_timeout_ms (view->model);
+  int remaining = grub_gfxmenu_model_get_timeout_remaining_ms (view->model);
+  struct progress_value_data pv;
+
+  pv.visible = timeout > 0 ? "true" : "false";
+  grub_sprintf (startbuf, "%d", -timeout);
+  pv.start = startbuf;
+  pv.end = "0";
+  grub_sprintf (valuebuf, "%d", remaining > 0 ? -remaining : 0);
+  pv.value = valuebuf;
+
+  int seconds_remaining_rounded_up = (remaining + 999) / 1000;
+  grub_sprintf (msgbuf,
+                "The highlighted entry will be booted automatically in %d s.",
+                seconds_remaining_rounded_up);
+  pv.text = msgbuf;
+
+  grub_gui_find_by_id ((grub_gui_component_t) view->canvas,
+                       TIMEOUT_COMPONENT_ID, update_timeout_visit, &pv);
+}
+
+static void
+draw_message (grub_gfxmenu_view_t view)
+{
+  char *text = view->progress_message_text;
+  if (! text)
+    return;
+
+  grub_font_t font = view->status_font;
+  grub_video_color_t color = grub_gui_map_color (view->status_color);
+
+  /* Set the timeout bar's frame.  */
+  grub_video_rect_t f;
+  f.width = view->screen.width * 4 / 5;
+  f.height = 50;
+  f.x = view->screen.x + (view->screen.width - f.width) / 2;
+  f.y = view->screen.y + view->screen.height - 90 - 20 - f.height;
+
+  /* Border.  */
+  grub_video_fill_rect (color,
+                        f.x-1, f.y-1, f.width+2, f.height+2);
+  /* Fill.  */
+  grub_video_fill_rect (grub_gui_map_color (view->status_bg_color),
+                        f.x, f.y, f.width, f.height);
+
+  /* Center the text. */
+  int text_width = grub_font_get_string_width (font, text);
+  int x = f.x + (f.width - text_width) / 2;
+  int y = (f.y + (f.height - grub_font_get_descent (font)) / 2
+           + grub_font_get_ascent (font) / 2);
+  grub_video_draw_string (text, font, color, x, y);
+}
+
+
+void
+grub_gfxmenu_view_draw (grub_gfxmenu_view_t view)
+{
+  grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
+  update_timeout (view);
+
+  draw_background (view);
+  if (view->canvas)
+    view->canvas->ops->component.paint (view->canvas);
+  draw_menu (view);
+  draw_title (view);
+  draw_status (view);
+  draw_message (view);
+}
+
+static grub_err_t
+set_graphics_mode (void)
+{
+  const char *doublebuf_str = grub_env_get ("doublebuffering");
+  int doublebuf_flags =
+    (doublebuf_str && doublebuf_str[0] == 'n')
+    ? 0
+    : GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED;
+
+  const char *modestr = grub_env_get ("gfxmode");
+  if (grub_video_setup_preferred_mode (modestr, doublebuf_flags, 640, 480)
+      != GRUB_ERR_NONE)
+    return grub_errno;
+
+  return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+set_text_mode (void)
+{
+  return grub_video_restore ();
+}
+
+static int term_target_width;
+static int term_target_height;
+static struct grub_video_render_target *term_target;
+static int term_initialized;
+static grub_term_t term_original;
+static grub_gfxmenu_view_t term_view;
+
+static void
+repaint_terminal (int x __attribute ((unused)),
+                  int y __attribute ((unused)),
+                  int width __attribute ((unused)),
+                  int height __attribute ((unused)))
+{
+  if (! term_view)
+    return;
+
+  grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
+  grub_gfxmenu_view_draw (term_view);
+  grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
+
+  int termx = term_view->screen.x
+    + term_view->screen.width * (10 - 7) / 10 / 2;
+  int termy = term_view->screen.y
+    + term_view->screen.height * (10 - 7) / 10 / 2;
+
+  grub_gfxmenu_box_t term_box = term_view->terminal_box;
+  if (term_box)
+    {
+      term_box->set_content_size (term_box,
+                                  term_target_width, term_target_height);
+
+      term_box->draw (term_box,
+                      termx - term_box->get_left_pad (term_box),
+                      termy - term_box->get_top_pad (term_box));
+    }
+
+  grub_video_blit_render_target (term_target, GRUB_VIDEO_BLIT_REPLACE,
+                                 termx, termy,
+                                 0, 0, term_target_width, term_target_height);
+  grub_video_swap_buffers ();
+}
+
+static void
+init_terminal (grub_gfxmenu_view_t view)
+{
+  term_original = grub_term_get_current ();
+
+  term_target_width = view->screen.width * 7 / 10;
+  term_target_height = view->screen.height * 7 / 10;
+
+  grub_video_create_render_target (&term_target,
+                                   term_target_width,
+                                   term_target_height,
+                                   GRUB_VIDEO_MODE_TYPE_RGB
+                                   | GRUB_VIDEO_MODE_TYPE_ALPHA);
+  if (grub_errno != GRUB_ERR_NONE)
+    return;
+
+  /* Note: currently there is no API for changing the gfxterm font
+     on the fly, so whatever font the initially loaded theme specifies
+     will be permanent.  */
+  grub_gfxterm_init_window (term_target, 0, 0,
+                            term_target_width, term_target_height,
+                            view->terminal_font_name, 3);
+  if (grub_errno != GRUB_ERR_NONE)
+    return;
+  term_initialized = 1;
+
+  /* XXX: store static pointer to the 'view' object so the repaint callback can access it.  */
+  term_view = view;
+  grub_gfxterm_set_repaint_callback (repaint_terminal);
+  grub_term_set_current (grub_gfxterm_get_term ());
+}
+
+static void destroy_terminal (void)
+{
+  term_view = 0;
+  if (term_initialized)
+    grub_gfxterm_destroy_window ();
+  grub_gfxterm_set_repaint_callback (0);
+  if (term_target)
+    grub_video_delete_render_target (term_target);
+  if (term_original)
+    grub_term_set_current (term_original);
+}
+
+
+static void
+notify_booting (void *userdata, grub_menu_entry_t entry)
+{
+  grub_gfxmenu_view_t view = (grub_gfxmenu_view_t) userdata;
+
+  char *s = grub_malloc (100 + grub_strlen (entry->title));
+  if (!s)
+    return;
+
+  grub_sprintf (s, "Booting '%s'", entry->title);
+  set_progress_message (view, s);
+  grub_free (s);
+  grub_gfxmenu_view_draw (view);
+  grub_video_swap_buffers ();
+}
+
+static void
+notify_fallback (void *userdata, grub_menu_entry_t entry)
+{
+  grub_gfxmenu_view_t view = (grub_gfxmenu_view_t) userdata;
+
+  char *s = grub_malloc (100 + grub_strlen (entry->title));
+  if (!s)
+    return;
+
+  grub_sprintf (s, "Falling back to '%s'", entry->title);
+  set_progress_message (view, s);
+  grub_free (s);
+  grub_gfxmenu_view_draw (view);
+  grub_video_swap_buffers ();
+}
+
+static void
+notify_execution_failure (void *userdata __attribute__ ((unused)))
+{
+}
+
+
+static struct grub_menu_execute_callback execute_callback =
+{
+  .notify_booting = notify_booting,
+  .notify_fallback = notify_fallback,
+  .notify_failure = notify_execution_failure
+};
+
+int
+grub_gfxmenu_view_execute_with_fallback (grub_gfxmenu_view_t view,
+                                         grub_menu_entry_t entry)
+{
+  grub_menu_execute_with_fallback (grub_gfxmenu_model_get_menu (view->model),
+                                   entry, &execute_callback, (void *) view);
+
+  if (set_graphics_mode () != GRUB_ERR_NONE)
+    return 0;  /* Failure.  */
+
+  /* If we returned, there was a failure.  */
+  set_progress_message (view,
+                        "Unable to automatically boot.  "
+                        "Press SPACE to continue.");
+  grub_gfxmenu_view_draw (view);
+  grub_video_swap_buffers ();
+  while (GRUB_TERM_ASCII_CHAR(grub_getkey ()) != ' ')
+    {
+      /* Wait for SPACE to be pressed.  */
+    }
+
+  set_progress_message (view, 0);   /* Clear the message.  */
+
+  return 1;   /* Ok.  */
+}
+
+int
+grub_gfxmenu_view_execute_entry (grub_gfxmenu_view_t view,
+                                 grub_menu_entry_t entry)
+{
+  /* Currently we switch back to text mode by restoring
+     the original terminal before executing the menu entry.
+     It is hard to make it work when executing a menu entry
+     that switches video modes -- it using gfxterm in a
+     window, the repaint callback seems to crash GRUB. */
+  /* TODO: Determine if this works when 'gfxterm' was set as
+     the current terminal before invoking the gfxmenu. */
+  destroy_terminal ();
+
+  grub_menu_execute_entry (entry);
+  if (grub_errno != GRUB_ERR_NONE)
+    grub_wait_after_message ();
+
+  if (set_graphics_mode () != GRUB_ERR_NONE)
+    return 0;  /* Failure.  */
+
+  init_terminal (view);
+  return 1;   /* Ok.  */
+}
+
+void
+grub_gfxmenu_view_run_terminal (grub_gfxmenu_view_t view __attribute__((unused)))
+{
+  grub_cmdline_run (1);
+}
+

=== added file 'gfxmenu/widget-box.c'
--- gfxmenu/widget-box.c	1970-01-01 00:00:00 +0000
+++ gfxmenu/widget-box.c	2008-08-12 02:55:06 +0000
@@ -0,0 +1,243 @@
+/* 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/gfxwidgets.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 (grub_gfxmenu_box_t 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_gfxmenu_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_gfxmenu_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 (grub_gfxmenu_box_t self)
+{
+  return grub_video_bitmap_get_width (self->raw_pixmaps[BOX_PIXMAP_W]);
+}
+
+static int
+get_top_pad (grub_gfxmenu_box_t self)
+{
+  return grub_video_bitmap_get_height (self->raw_pixmaps[BOX_PIXMAP_N]);
+}
+
+static void
+destroy (grub_gfxmenu_box_t 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_gfxmenu_box_t
+grub_gfxmenu_create_box (const char *pixmaps_prefix,
+                         const char *pixmaps_suffix)
+{
+  char path[200];
+  unsigned i;
+  grub_gfxmenu_box_t box;
+
+  box = (grub_gfxmenu_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 XXX 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;
+}

=== 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 */
+

=== added file 'include/grub/gfxmenu_model.h'
--- include/grub/gfxmenu_model.h	1970-01-01 00:00:00 +0000
+++ include/grub/gfxmenu_model.h	2008-07-26 23:37:04 +0000
@@ -0,0 +1,59 @@
+/* gfxmenu_model.h - gfxmenu model interface. */
+/*
+ *  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 GRUB_GFXMENU_MODEL_HEADER
+#define GRUB_GFXMENU_MODEL_HEADER 1
+
+#include <grub/menu.h>
+
+struct grub_gfxmenu_model;   /* Forward declaration of opaque type.  */
+typedef struct grub_gfxmenu_model *grub_gfxmenu_model_t;
+
+
+grub_gfxmenu_model_t grub_gfxmenu_model_new (grub_menu_t menu);
+
+void grub_gfxmenu_model_destroy (grub_gfxmenu_model_t model);
+
+grub_menu_t grub_gfxmenu_model_get_menu (grub_gfxmenu_model_t model);
+
+void grub_gfxmenu_model_set_timeout (grub_gfxmenu_model_t model);
+
+void grub_gfxmenu_model_clear_timeout (grub_gfxmenu_model_t model);
+
+int grub_gfxmenu_model_get_timeout_ms (grub_gfxmenu_model_t model);
+
+int grub_gfxmenu_model_get_timeout_remaining_ms (grub_gfxmenu_model_t model);
+
+int grub_gfxmenu_model_timeout_expired (grub_gfxmenu_model_t model);
+
+int grub_gfxmenu_model_get_num_entries (grub_gfxmenu_model_t model);
+
+int grub_gfxmenu_model_get_selected_index (grub_gfxmenu_model_t model);
+
+void grub_gfxmenu_model_set_selected_index (grub_gfxmenu_model_t model,
+                                            int index);
+
+const char *grub_gfxmenu_model_get_entry_title (grub_gfxmenu_model_t model,
+                                                int index);
+
+grub_menu_entry_t grub_gfxmenu_model_get_entry (grub_gfxmenu_model_t model,
+                                                int index);
+
+#endif /* GRUB_GFXMENU_MODEL_HEADER */
+

=== added file 'include/grub/gfxmenu_view.h'
--- include/grub/gfxmenu_view.h	1970-01-01 00:00:00 +0000
+++ include/grub/gfxmenu_view.h	2008-08-12 02:55:06 +0000
@@ -0,0 +1,108 @@
+/* gfxmenu_view.h - gfxmenu view interface. */
+/*
+ *  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 GRUB_GFXMENU_VIEW_HEADER
+#define GRUB_GFXMENU_VIEW_HEADER 1
+
+#include <grub/types.h>
+#include <grub/err.h>
+#include <grub/menu.h>
+#include <grub/gfxmenu_model.h>
+
+struct grub_gfxmenu_view;   /* Forward declaration of opaque type.  */
+typedef struct grub_gfxmenu_view *grub_gfxmenu_view_t;
+
+
+grub_gfxmenu_view_t grub_gfxmenu_view_new (const char *theme_path,
+                                           grub_gfxmenu_model_t model);
+
+void grub_gfxmenu_view_destroy (grub_gfxmenu_view_t view);
+
+/* Set properties on the view based on settings from the specified
+   theme file. */
+int grub_gfxmenu_view_load_theme (grub_gfxmenu_view_t view,
+                                  const char *theme_path);
+
+void grub_gfxmenu_view_draw (grub_gfxmenu_view_t view);
+
+int grub_gfxmenu_view_execute_with_fallback (grub_gfxmenu_view_t view,
+                                             grub_menu_entry_t entry);
+
+int grub_gfxmenu_view_execute_entry (grub_gfxmenu_view_t view,
+                                     grub_menu_entry_t entry);
+
+void grub_gfxmenu_view_run_terminal (grub_gfxmenu_view_t view);
+
+
+
+/* Implementation details -- this should not be used outside of the
+   view itself.  */
+
+#include <grub/video.h>
+#include <grub/bitmap.h>
+#include <grub/gui.h>
+#include <grub/gfxwidgets.h>
+
+typedef struct grub_gfxmenu_view_icon
+{
+  const char *class_name;
+  struct grub_video_bitmap *bitmap;
+  struct grub_gfxmenu_view_icon *next;
+} *grub_gfxmenu_view_icon_t;
+
+/* Definition of the private representation of the view.  */
+struct grub_gfxmenu_view
+{
+  grub_video_rect_t screen;
+
+  int icon_width;
+  int icon_height;
+  int item_height;
+  int item_padding;
+  int item_icon_space;
+  int item_spacing;
+  grub_font_t title_font;
+  grub_font_t item_font;
+  grub_font_t selected_item_font;
+  grub_font_t status_font;
+  char *terminal_font_name;
+  grub_gui_color_t title_color;
+  grub_gui_color_t item_color;
+  grub_gui_color_t selected_item_color;
+  grub_gui_color_t status_color;
+  grub_gui_color_t status_bg_color;
+  struct grub_video_bitmap *desktop_image;
+  grub_gui_color_t desktop_color;
+  grub_gfxmenu_box_t menu_box;
+  grub_gfxmenu_box_t selected_item_box;
+  grub_gfxmenu_box_t terminal_box;
+  char *title_text;
+  char *progress_message_text;
+  char *theme_path;
+  /* Icon cache: linked list w/ dummy head node.  */
+  struct grub_gfxmenu_view_icon icon_cache;
+
+  grub_gui_container_t canvas;
+
+  grub_gfxmenu_model_t model;
+};
+
+void grub_gfxmenu_view_free_icon_cache (grub_gfxmenu_view_t view);
+
+#endif /* ! GRUB_GFXMENU_VIEW_HEADER */

=== added file 'include/grub/gfxterm.h'
--- include/grub/gfxterm.h	1970-01-01 00:00:00 +0000
+++ include/grub/gfxterm.h	2008-07-19 19:56:06 +0000
@@ -0,0 +1,41 @@
+/*
+ *  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_GFXTERM_HEADER
+#define GRUB_GFXTERM_HEADER	1
+
+#include <grub/err.h>
+#include <grub/types.h>
+#include <grub/term.h>
+#include <grub/video.h>
+
+grub_err_t
+grub_gfxterm_init_window (struct grub_video_render_target *target,
+                          int x, int y, int width, int height, 
+                          const char *font_name, int border_width);
+
+void grub_gfxterm_destroy_window (void);
+
+grub_term_t grub_gfxterm_get_term (void);
+
+typedef void (*grub_gfxterm_repaint_callback_t)(int x, int y, 
+                                                int width, int height);
+
+void grub_gfxterm_set_repaint_callback (grub_gfxterm_repaint_callback_t func);
+
+#endif /* ! GRUB_GFXTERM_HEADER */

=== added file 'include/grub/gfxwidgets.h'
--- include/grub/gfxwidgets.h	1970-01-01 00:00:00 +0000
+++ include/grub/gfxwidgets.h	2008-07-19 18:25:18 +0000
@@ -0,0 +1,47 @@
+/* gfxwidgets.h - Widgets for the graphical menu (gfxmenu).  */
+/*
+ *  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 GRUB_GFXWIDGETS_HEADER
+#define GRUB_GFXWIDGETS_HEADER 1
+
+#include <grub/video.h>
+
+typedef struct grub_gfxmenu_box *grub_gfxmenu_box_t;
+
+struct grub_gfxmenu_box
+{
+  /* The size of the content.  */
+  int width;
+  int height;
+
+  struct grub_video_bitmap **raw_pixmaps;
+  struct grub_video_bitmap **scaled_pixmaps;
+
+  void (*draw) (grub_gfxmenu_box_t self, int x, int y);
+  void (*set_content_size) (grub_gfxmenu_box_t self,
+                            int width, int height);
+  int (*get_left_pad) (grub_gfxmenu_box_t self);
+  int (*get_top_pad) (grub_gfxmenu_box_t self);
+  void (*destroy) (grub_gfxmenu_box_t self);
+};
+
+grub_gfxmenu_box_t grub_gfxmenu_create_box (const char *pixmaps_prefix,
+                                            const char *pixmaps_suffix);
+
+#endif /* ! GRUB_GFXWIDGETS_HEADER */

=== added file 'include/grub/gui.h'
--- include/grub/gui.h	1970-01-01 00:00:00 +0000
+++ include/grub/gui.h	2008-08-11 14:36:33 +0000
@@ -0,0 +1,144 @@
+/* gui.h - GUI components header file. */
+/*
+ *  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/err.h>
+#include <grub/video.h>
+#include <grub/bitmap.h>
+
+#ifndef GRUB_GUI_H
+#define GRUB_GUI_H 1
+
+/* A representation of a color.  Unlike grub_video_color_t, this
+   representation is independent of any video mode specifics.  */
+typedef struct grub_gui_color
+{
+  grub_uint8_t red;
+  grub_uint8_t green;
+  grub_uint8_t blue;
+  grub_uint8_t alpha;
+} grub_gui_color_t;
+
+typedef struct grub_gui_component *grub_gui_component_t;
+typedef struct grub_gui_container *grub_gui_container_t;
+
+typedef void (*grub_gui_component_callback) (grub_gui_component_t component,
+                                             void *userdata);
+
+/* Component interface.  */
+
+struct grub_gui_component_ops
+{
+  void (*destroy) (void *self);
+  const char * (*get_id) (void *self);
+  int (*is_instance) (void *self, const char *type);
+  void (*paint) (void *self);
+  void (*set_parent) (void *self, grub_gui_container_t parent);
+  grub_gui_container_t (*get_parent) (void *self);
+  void (*set_bounds) (void *self, const grub_video_rect_t *bounds);
+  void (*get_bounds) (void *self, grub_video_rect_t *bounds);
+  void (*get_preferred_size) (void *self, int *width, int *height);
+  void (*set_property) (void *self, const char *name, const char *value);
+};
+
+struct grub_gui_container_ops
+{
+  struct grub_gui_component_ops component;
+  void (*add) (void *self, grub_gui_component_t comp);
+  void (*remove) (void *self, grub_gui_component_t comp);
+  void (*iterate_children) (void *self,
+                            grub_gui_component_callback cb, void *userdata);
+};
+
+struct grub_gui_component
+{
+  struct grub_gui_component_ops *ops;
+};
+
+struct grub_gui_container
+{
+  struct grub_gui_container_ops *ops;
+};
+
+
+/* Interfaces to concrete component classes.  */
+
+grub_gui_container_t grub_gui_canvas_new (void);
+grub_gui_container_t grub_gui_vbox_new (void);
+grub_gui_container_t grub_gui_hbox_new (void);
+grub_gui_component_t grub_gui_label_new (void);
+grub_gui_component_t grub_gui_image_new (void);
+grub_gui_component_t grub_gui_progress_bar_new (void);
+
+/* Manipulation functions.  */
+
+/* Visit all components with the specified ID.  */
+void grub_gui_find_by_id (grub_gui_component_t root,
+                          const char *id,
+                          grub_gui_component_callback cb,
+                          void *userdata);
+
+/* Helper functions.  */
+
+static __inline void
+grub_gui_save_viewport (grub_video_rect_t *r)
+{
+  grub_video_get_viewport ((unsigned *) &r->x,
+                           (unsigned *) &r->y,
+                           (unsigned *) &r->width,
+                           (unsigned *) &r->height);
+}
+
+static __inline void
+grub_gui_restore_viewport (const grub_video_rect_t *r)
+{
+  grub_video_set_viewport (r->x, r->y, r->width, r->height);
+}
+
+/* Set a new viewport relative the the current one, saving the current
+   viewport in OLD so it can be later restored.  */
+static __inline void
+grub_gui_set_viewport (const grub_video_rect_t *r, grub_video_rect_t *old)
+{
+  grub_gui_save_viewport (old);
+  grub_video_set_viewport (old->x + r->x,
+                           old->y + r->y,
+                           r->width,
+                           r->height);
+}
+
+static __inline grub_gui_color_t
+grub_gui_color_rgb (int r, int g, int b)
+{
+  grub_gui_color_t c;
+  c.red = r;
+  c.green = g;
+  c.blue = b;
+  c.alpha = 255;
+  return c;
+}
+
+static __inline grub_video_color_t
+grub_gui_map_color (grub_gui_color_t c)
+{
+  return grub_video_map_rgba (c.red, c.green, c.blue, c.alpha);
+}
+
+
+#endif /* ! GRUB_GUI_H */

=== 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,

=== modified file 'include/grub/i386/types.h'
--- include/grub/i386/types.h	2007-07-21 22:32:33 +0000
+++ include/grub/i386/types.h	2008-08-11 14:36:33 +0000
@@ -25,6 +25,51 @@
 /* The size of long.  */
 #define GRUB_TARGET_SIZEOF_LONG		4
 
+/* The native word size in bits.  */
+#define __WORDSIZE 32
+
+/* These assume 8-bit `char's, 16-bit `short int's,
+   and 32-bit `int's and `long int's.  */
+
+/* Number of bits in a `char'.	*/
+#define CHAR_BIT 8
+
+/* Minimum and maximum values a `signed char' can hold.  */
+#define SCHAR_MIN (-128)
+#define SCHAR_MAX 127
+
+/* Maximum value an `unsigned char' can hold.  (Minimum is 0.)  */
+#define UCHAR_MAX 255
+
+/* Minimum and maximum values a `char' can hold.  */
+#ifdef __CHAR_UNSIGNED__
+# define CHAR_MIN 0
+# define CHAR_MAX UCHAR_MAX
+#else
+# define CHAR_MIN SCHAR_MIN
+# define CHAR_MAX SCHAR_MAX
+#endif
+
+/* Minimum and maximum values for signed short int.  */
+#define SHRT_MIN (-32768)
+#define SHRT_MAX 32767
+
+/* Maximum value for unsigned short int.  (Minimum is 0.)  */
+#define USHRT_MAX 65535
+
+/* Mininum and maximum values for signed int.  */
+#define INT_MIN   (-INT_MAX - 1)
+#define INT_MAX   2147483647
+#define UINT_MAX  4294967295U
+
+/* Mininum and maximum values for signed long.  */
+#if __WORDSIZE == 64
+# define LONG_MAX 9223372036854775807L
+#else
+# define LONG_MAX 2147483647L
+#endif
+#define LONG_MIN  (-LONG_MAX - 1L)
+
 /* i386 is little-endian.  */
 #undef GRUB_TARGET_WORDS_BIGENDIAN
 

=== added file 'include/grub/menu.h'
--- include/grub/menu.h	1970-01-01 00:00:00 +0000
+++ include/grub/menu.h	2008-08-03 02:14:40 +0000
@@ -0,0 +1,63 @@
+/* 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
+
+struct grub_menu_entry_class
+{
+  char *name;
+  struct grub_menu_entry_class *next;
+};
+
+/* The menu entry.  */
+struct grub_menu_entry
+{
+  /* The title name.  */
+  const char *title;
+
+  /* The classes associated with the menu entry:
+     used to choose an icon or other style attributes.
+     This is a dummy head node for the linked list, so for an entry E,
+     E.classes->next is the first class if it is not NULL.  */
+  struct grub_menu_entry_class *classes;
+
+  /* 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-19 19:07:47 +0000
@@ -0,0 +1,48 @@
+/* menu_viewer.h - Interface to menu viewer implementations. */
+/*
+ *  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/misc.h'
--- include/grub/misc.h	2008-07-04 01:12:54 +0000
+++ include/grub/misc.h	2008-08-11 14:36:33 +0000
@@ -52,11 +52,14 @@
 char *EXPORT_FUNC(grub_strstr) (const char *haystack, const char *needle);
 int EXPORT_FUNC(grub_iswordseparator) (int c);
 int EXPORT_FUNC(grub_isspace) (int c);
+int EXPORT_FUNC(grub_iscntrl) (int c);
 int EXPORT_FUNC(grub_isprint) (int c);
 int EXPORT_FUNC(grub_isalpha) (int c);
+int EXPORT_FUNC(grub_isalnum) (int c);
 int EXPORT_FUNC(grub_isgraph) (int c);
 int EXPORT_FUNC(grub_isdigit) (int c);
 int EXPORT_FUNC(grub_tolower) (int c);
+long EXPORT_FUNC(grub_strtol) (const char *str, char **end, int base);
 unsigned long EXPORT_FUNC(grub_strtoul) (const char *str, char **end, int base);
 unsigned long long EXPORT_FUNC(grub_strtoull) (const char *str, char **end, int base);
 char *EXPORT_FUNC(grub_strdup) (const char *s);

=== modified file 'include/grub/normal.h'
--- include/grub/normal.h	2008-03-26 12:01:02 +0000
+++ include/grub/normal.h	2008-08-03 04:00:22 +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
@@ -39,7 +40,7 @@
 #define GRUB_COMMAND_FLAG_TITLE		0x4
 /* Don't print the command on booting.  */
 #define GRUB_COMMAND_FLAG_NO_ECHO	0x8
-/* Don't print the command on booting.  */
+/* Pass arguments to the command without parsing options.  */
 #define GRUB_COMMAND_FLAG_NO_ARG_PARSE	0x10
 /* Not loaded yet. Used for auto-loading.  */
 #define GRUB_COMMAND_FLAG_NOT_LOADED	0x20
@@ -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,27 @@
 /* To exit from the normal mode.  */
 extern grub_jmp_buf grub_exit_env;
 
+extern struct grub_menu_viewer grub_normal_terminal_menu_viewer;
+
+typedef struct grub_menu_execute_callback
+{
+  void (*notify_booting) (void *userdata, grub_menu_entry_t entry);
+  void (*notify_fallback) (void *userdata, grub_menu_entry_t entry);
+  void (*notify_failure) (void *userdata);
+} 
+*grub_menu_execute_callback_t;
+
 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_execute_with_fallback (grub_menu_t menu,
+                                      grub_menu_entry_t entry,
+                                      grub_menu_execute_callback_t callback,
+                                      void *callback_data);
 void grub_menu_entry_run (grub_menu_entry_t entry);
+int grub_menu_get_default_entry_index (grub_menu_t menu);
+int grub_menu_get_timeout (void);
+void grub_menu_set_timeout (int timeout);
+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/stringutil.h'
--- include/grub/stringutil.h	1970-01-01 00:00:00 +0000
+++ include/grub/stringutil.h	2008-08-10 04:38:24 +0000
@@ -0,0 +1,37 @@
+/* gfxmenu_stringutil.h - String utilities for the graphical menu interface. */
+/*
+ *  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 GRUB_GFXMENU_STRINGUTIL_HEADER
+#define GRUB_GFXMENU_STRINGUTIL_HEADER 1
+
+#include <grub/types.h>
+#include <grub/gui.h>
+
+char *grub_new_substring (const char *buf,
+                          grub_size_t start, grub_size_t end);
+
+char *grub_resolve_relative_path (const char *base, const char *path);
+
+char *grub_get_dirname (const char *file_path);
+
+grub_gui_color_t grub_gui_parse_color (const char *s);
+
+int grub_gui_parse_2_tuple (const char *s, int *px, int *py);
+
+#endif /* GRUB_GFXMENU_STRINGUTIL_HEADER */

=== modified file 'include/grub/video.h'
--- include/grub/video.h	2008-01-01 12:02:07 +0000
+++ include/grub/video.h	2008-07-19 19:31:46 +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,
@@ -282,4 +317,21 @@
 
 grub_err_t grub_video_get_active_render_target (struct grub_video_render_target **target);
 
+
+/* Defined in video/setmode.c */
+
+/* Set the video mode based on the preferred modes specified in MODE_LIST in
+   the form: <width>x<height>[x<depth>][;...]
+
+   For example:  640x480;800x600x8;400x300x32
+
+   If MODE_LIST is null, or no modes in it are usable, then DEFAULT_WIDTH and
+   DEFAULT_HEIGHT are used to set the mode.  The MODE_FLAGS argument determines
+   the video mode flags such as double buffering that are used.  */
+
+grub_err_t
+grub_video_setup_preferred_mode (const char *mode_list, int mode_flags,
+                                 int default_width, int default_height);
+
+
 #endif /* ! GRUB_VIDEO_HEADER */

=== added file 'kern/menu_viewer.c'
--- kern/menu_viewer.c	1970-01-01 00:00:00 +0000
+++ kern/menu_viewer.c	2008-07-19 19:07:47 +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-08-05 10:54:37 +0000
+++ kern/misc.c	2008-08-11 14:36:33 +0000
@@ -358,6 +358,12 @@
 }
 
 int
+grub_iscntrl (int c)
+{
+  return (c >= 0x00 && c <= 0x1F) || c == 0x7F;
+}
+
+int
 grub_isprint (int c)
 {
   return (c >= ' ' && c <= '~');
@@ -370,6 +376,12 @@
 }
 
 int
+grub_isalnum (int c)
+{
+  return grub_isalpha (c) || grub_isdigit (c);
+}
+
+int
 grub_isdigit (int c)
 {
   return (c >= '0' && c <= '9');
@@ -390,6 +402,41 @@
   return c;
 }
 
+long
+grub_strtol (const char *str, char **end, int base)
+{
+  int negative = 0;
+
+  while (*str && grub_isspace (*str))
+    str++;
+
+  if (*str == '-')
+    {
+      negative = 1;
+      str++;
+    }
+
+  unsigned long long magnitude;
+  magnitude = grub_strtoull (str, end, base);
+  if (negative)
+    {
+      if (magnitude > -((long long) LONG_MIN))
+        {
+          grub_error (GRUB_ERR_OUT_OF_RANGE, "negative overflow");
+          return LONG_MIN;
+        }
+      return -((long long) magnitude);
+    }
+  else
+    {
+      if (magnitude > LONG_MAX)
+        {
+          grub_error (GRUB_ERR_OUT_OF_RANGE, "positive overflow");
+          return LONG_MAX;
+        }
+      return (long) magnitude;
+    }
+}
 
 unsigned long
 grub_strtoul (const char *str, char **end, int base)

=== modified file 'normal/main.c'
--- normal/main.c	2008-07-29 14:07:47 +0000
+++ normal/main.c	2008-08-03 04:00:22 +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;
 
@@ -147,14 +148,132 @@
   grub_env_unset_data_slot ("menu");
 }
 
+static void
+free_menu_entry_classes (struct grub_menu_entry_class *head)
+{
+  /* Free all the classes.  */
+  while (head)
+    {
+      struct grub_menu_entry_class *next;
+
+      grub_free (head->name);
+      next = head->next;
+      grub_free (head);
+      head = next;
+    }
+}
+
+/* The tag that can be added to a menu entry's title to specify a class
+   for the UI to use in selecting an icon or other visual attributes.  */
+static const char entry_class_attr_tag[] = "|class=";
+
+#define ENTRY_ATTR_SEPARATOR_CHAR ','
+
+/* Parse and strip a possible "class" attribute in the title.
+   This code is not designed to support other attributes than "class"
+   in the title since, we are planning to use a better method of
+   specifying this information in the future.  The parameter TITLE is
+   modified by storing a '\0' at the appropriate location to strip the
+   class information, if it exists.  The class list is stored into *HEAD.  */
+static struct grub_menu_entry_class *
+get_classes_from_entry_title (char *title)
+{
+  struct grub_menu_entry_class *head;
+  char *attr_start;
+
+  head = grub_malloc (sizeof (struct grub_menu_entry_class));
+  if (! head)
+    return 0;
+  head->name = 0;
+  head->next = 0;
+
+  attr_start = grub_strstr (title, entry_class_attr_tag);
+  if (attr_start)
+    {
+      struct grub_menu_entry_class *tail;
+      const char *p;
+
+      /* Trim the properties off of the title.  */
+      *attr_start = '\0';
+
+      /* Move the pointer to the beginning of the first class name.  */
+      attr_start += grub_strlen (entry_class_attr_tag);
+
+      tail = head;
+      p = attr_start;
+      while (p && *p)
+        {
+          const char *q;
+          const char *end;
+          const char *next_start;
+
+          /* Skip any leading whitespace.  */
+          while (*p && grub_isspace (*p))
+            p++;
+
+          /* Find the comma terminating this one ...  */
+          q = grub_strchr (p, ENTRY_ATTR_SEPARATOR_CHAR);
+          /* ... or if it's the last one, find the '\0' terminator.  */
+          if (q)
+            {
+              end = q - 1;
+              next_start = q + 1;
+            }
+          else
+            {
+              /* For the last class, extend it to the end.  */
+              end = p + grub_strlen (p);
+              next_start = 0;
+            }
+
+          /* Trim any trailing whitespace.  */
+          while (end > p && grub_isspace (*end))
+            end--;
+
+          grub_size_t len = end - p + 1;
+          /* Copy the class name into a new string.  */
+          char *class_name = grub_malloc (len + 1);
+          if (! class_name)
+            {
+              free_menu_entry_classes (head);
+              return 0;
+            }
+          grub_memcpy (class_name, p, len);
+          class_name[len] = '\0';
+
+          /* Create a new class and add it at the tail of the list.  */
+          struct grub_menu_entry_class *new_class;
+          new_class = grub_malloc (sizeof (struct grub_menu_entry_class));
+          if (! new_class)
+            {
+              grub_free (class_name);
+              free_menu_entry_classes (head);
+              return 0;
+            }
+          /* Fill in the new class node.  */
+          new_class->name = class_name;
+          new_class->next = 0;
+          /* Link the tail to it, and make it the new tail.  */
+          tail->next = new_class;
+          tail = new_class;
+
+          /* Advance the character pointer.  */
+          p = next_start;
+        }
+    }
+
+  return head;
+}
+
 grub_err_t
 grub_normal_menu_addentry (const char *title, struct grub_script *script,
 			   const char *sourcecode)
 {
-  const char *menutitle;
+  char *menutitle;
   const char *menusourcecode;
   grub_menu_t menu;
   grub_menu_entry_t *last;
+  struct grub_menu_entry_class *classes;
 
   menu = grub_env_get_data_slot("menu");
   if (! menu)
@@ -173,6 +292,14 @@
       return grub_errno;
     }
 
+  classes = get_classes_from_entry_title (menutitle);
+  if (! classes)
+    {
+      grub_free ((void *) menutitle);
+      grub_free ((void *) menusourcecode);
+      return grub_errno;
+    }
+
   /* Add the menu entry at the end of the list.  */
   while (*last)
     last = &(*last)->next;
@@ -180,6 +307,7 @@
   *last = grub_malloc (sizeof (**last));
   if (! *last)
     {
+      free_menu_entry_classes (classes);
       grub_free ((void *) menutitle);
       grub_free ((void *) menusourcecode);
       return grub_errno;
@@ -187,6 +315,7 @@
 
   (*last)->commands = script;
   (*last)->title = menutitle;
+  (*last)->classes = classes;
   (*last)->next = 0;
   (*last)->sourcecode = menusourcecode;
 
@@ -476,7 +605,7 @@
 
   if (menu && menu->size)
     {
-      grub_menu_run (menu, nested);
+      grub_menu_viewer_show_menu (menu, nested);
       if (nested)
 	free_menu (menu);
     }
@@ -519,6 +648,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 +666,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-08-07 22:48:13 +0000
+++ normal/menu.c	2008-08-11 14:46:59 +0000
@@ -24,6 +24,7 @@
 #include <grub/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;
@@ -241,8 +242,8 @@
 
 /* Return the current timeout. If the variable "timeout" is not set or
    invalid, return -1.  */
-static int
-get_timeout (void)
+int
+grub_menu_get_timeout (void)
 {
   char *val;
   int timeout;
@@ -269,8 +270,8 @@
 }
 
 /* Set current timeout in the variable "timeout".  */
-static void
-set_timeout (int timeout)
+void
+grub_menu_set_timeout (int timeout)
 {
   /* Ignore TIMEOUT if it is zero, because it will be unset really soon.  */
   if (timeout > 0)
@@ -308,6 +309,57 @@
   return entry;
 }
 
+/* Get the default menu entry index.  */
+int
+grub_menu_get_default_entry_index (grub_menu_t menu)
+{
+  int i = get_entry_number ("default");
+
+  /* If DEFAULT_ENTRY is not within the menu entries, fall back to
+     the first entry.  */
+  if (i < 0 || i >= menu->size)
+    i = 0;
+
+  return i;
+}
+
+/* Get the first entry number from the variable NAME, which is a 
+   space-separated list of nonnegative integers.  The entry number which
+   is returned is stripped from the value of NAME.  */
+static int
+get_and_remove_first_entry_number (const char *name)
+{
+  char *val;
+  char *tail;
+  int entry;
+  
+  val = grub_env_get (name);
+  if (! val)
+    return -1;
+  
+  grub_error_push ();
+  
+  entry = (int) grub_strtoul (val, &tail, 0);
+
+  if (grub_errno == GRUB_ERR_NONE)
+    {
+      /* Skip whitespace to find the next digit.  */
+      while (*tail && grub_isspace (*tail))
+	tail++;
+      grub_env_set (name, tail);
+    }
+  else
+    {
+      grub_env_unset (name);
+      grub_errno = GRUB_ERR_NONE;
+      entry = -1;
+    }
+
+  grub_error_pop ();
+  
+  return entry;
+}
+
 static void
 print_timeout (int timeout, int offset, int second_stage)
 {
@@ -332,15 +384,10 @@
   
   first = 0;
   
-  default_entry = get_entry_number ("default");
-
-  /* If DEFAULT_ENTRY is not within the menu entries, fall back to
-     the first entry.  */
-  if (default_entry < 0 || default_entry >= menu->size)
-    default_entry = 0;
+  default_entry = grub_menu_get_default_entry_index (menu);
 
   /* If timeout is 0, drawing is pointless (and ugly).  */
-  if (get_timeout () == 0)
+  if (grub_menu_get_timeout () == 0)
     return default_entry;
 
   offset = default_entry;
@@ -359,15 +406,15 @@
   print_entries (menu, first, offset);
   grub_refresh ();
 
-  timeout = get_timeout ();
+  timeout = grub_menu_get_timeout ();
 
   if (timeout > 0)
     print_timeout (timeout, offset, 0);
 
-  while (1)
+  while (!grub_menu_viewer_should_return ())
     {
       int c;
-      timeout = get_timeout ();
+      timeout = grub_menu_get_timeout ();
       
       if (timeout > 0)
 	{
@@ -377,7 +424,7 @@
 	  if (current_time - saved_time >= 1000)
 	    {
 	      timeout--;
-	      set_timeout (timeout);
+	      grub_menu_set_timeout (timeout);
 	      saved_time = current_time;
 	      print_timeout (timeout, offset, 1);
 	    }
@@ -468,6 +515,10 @@
 		}
 	      goto refresh;
 	      
+	    case 't':
+	      grub_env_set ("menuviewer", "protomenu");
+	      goto refresh;
+
 	    default:
 	      break;
 	    }
@@ -476,13 +527,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,15 +543,80 @@
     grub_command_execute ("boot", 0);
 }
 
+/* Execute ENTRY from the menu MENU, falling back to entries specified
+   in the environment variable "fallback" if it fails.  CALLBACK is a 
+   pointer to a struct of function pointers which are used to allow the
+   caller provide feedback to the user.  */
 void
-grub_menu_run (grub_menu_t menu, int nested)
+grub_menu_execute_with_fallback (grub_menu_t menu,
+				 grub_menu_entry_t entry,
+				 grub_menu_execute_callback_t callback,
+				 void *callback_data)
+{
+  int fallback_entry;
+
+  callback->notify_booting (callback_data, entry);
+
+  grub_menu_execute_entry (entry);
+
+  /* Deal with fallback entries.  */
+  while ((fallback_entry = get_and_remove_first_entry_number ("fallback"))
+	 >= 0)
+    {
+      grub_print_error ();
+      grub_errno = GRUB_ERR_NONE;
+
+      entry = get_entry (menu, fallback_entry);
+      callback->notify_fallback (callback_data, entry);
+      grub_menu_execute_entry (entry);
+    }
+
+  if (grub_errno != GRUB_ERR_NONE)
+    callback->notify_failure (callback_data);
+}
+
+static void
+notify_booting (void *userdata __attribute__((unused)),
+		grub_menu_entry_t entry)
+{
+  grub_printf ("  Booting \'%s\'\n\n", entry->title);
+}
+
+static void
+notify_fallback (void *userdata __attribute__((unused)),
+		 grub_menu_entry_t entry)
+{
+  grub_printf ("\n  Falling back to \'%s\'\n\n", entry->title);
+  grub_millisleep (2000);
+}
+
+static void
+notify_execution_failure (void *userdata __attribute__((unused)))
+{
+  if (grub_errno != GRUB_ERR_NONE)
+    {
+      grub_print_error ();
+      grub_errno = GRUB_ERR_NONE;
+
+      grub_wait_after_message ();
+    }
+}
+
+static struct grub_menu_execute_callback execution_callback = 
+{
+  .notify_booting = notify_booting,
+  .notify_fallback = notify_fallback,
+  .notify_failure = notify_execution_failure
+};
+
+static grub_err_t
+show_menu (grub_menu_t menu, int nested)
 {
   while (1)
     {
       int boot_entry;
       grub_menu_entry_t e;
-      int fallback_entry;
-      
+
       boot_entry = run_menu (menu, nested);
       if (boot_entry < 0)
 	break;
@@ -507,34 +624,18 @@
       e = get_entry (menu, boot_entry);
       if (! e)
 	continue; /* Menu is empty.  */
-	
+
       grub_cls ();
       grub_setcursor (1);
 
-      grub_printf ("  Booting \'%s\'\n\n", e->title);
-  
-      run_menu_entry (e);
-
-      /* Deal with a fallback entry.  */
-      /* FIXME: Multiple fallback entries like GRUB Legacy.  */
-      fallback_entry = get_entry_number ("fallback");
-      if (fallback_entry >= 0)
-	{
-	  grub_print_error ();
-	  grub_errno = GRUB_ERR_NONE;
-	  
-	  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);
-	}
-
-      if (grub_errno != GRUB_ERR_NONE)
-	{
-	  grub_print_error ();
-	  grub_errno = GRUB_ERR_NONE;
-
-	  grub_wait_after_message ();
-	}
+      grub_menu_execute_with_fallback (menu, e, &execution_callback, 0);
     }
+
+  return GRUB_ERR_NONE;
 }
+
+struct grub_menu_viewer grub_normal_terminal_menu_viewer =
+{
+  .name = "terminal",
+  .show_menu = show_menu
+};

=== modified file 'term/gfxterm.c'
--- term/gfxterm.c	2008-01-01 12:02:07 +0000
+++ term/gfxterm.c	2008-07-19 19:56:06 +0000
@@ -28,14 +28,12 @@
 #include <grub/mm.h>
 #include <grub/env.h>
 #include <grub/video.h>
+#include <grub/gfxterm.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
 
@@ -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;
@@ -107,10 +108,20 @@
   struct grub_colored_char *text_buffer;
 };
 
+static int refcount;
+static struct grub_video_render_target *render_target;
+static grub_video_rect_t window;
 static struct grub_virtual_screen virtual_screen;
+static grub_gfxterm_repaint_callback_t repaint_callback;
+
+static grub_err_t init_window (struct grub_video_render_target *target,
+                               int x, int y, int width, int height, 
+                               const char *font_name, int border_width);
+
+static void destroy_window (void);
+
 
 static grub_dl_t my_mod;
-static struct grub_video_mode_info mode_info;
 
 static struct grub_video_render_target *text_layer;
 
@@ -170,18 +181,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;
@@ -227,281 +245,129 @@
 }
 
 static grub_err_t
+init_window (struct grub_video_render_target *target,
+             int x, int y, int width, int height, 
+             const char *font_name, int border_width)
+{
+  /* Clean up any prior instance.  */
+  destroy_window ();
+
+  /* Create virtual screen.  */
+  if (grub_virtual_screen_setup (border_width, border_width, 
+                                 width - 2 * border_width, 
+                                 height - 2 * border_width, 
+                                 font_name) 
+      != GRUB_ERR_NONE)
+    {
+      return grub_errno;
+    }
+
+  /* Set the render target.  */
+  render_target = target;
+
+  /* Set window bounds.  */
+  window.x = x;
+  window.y = y;
+  window.width = width;
+  window.height = height;
+
+  /* Mark whole window as dirty.  */
+  dirty_region_reset ();
+  dirty_region_add (0, 0, width, height);
+
+  return (grub_errno = GRUB_ERR_NONE);
+}
+
+grub_err_t
+grub_gfxterm_init_window (struct grub_video_render_target *target,
+                          int x, int y, int width, int height, 
+                          const char *font_name, int border_width)
+{
+  grub_errno = GRUB_ERR_NONE;
+  if (refcount++ == 0)
+    init_window (target, x, y, width, height, font_name, border_width);
+  return grub_errno;
+}
+
+static grub_err_t
 grub_gfxterm_init (void)
 {
-  char *modevar;
-  int width = DEFAULT_VIDEO_WIDTH;
-  int height = DEFAULT_VIDEO_HEIGHT;
-  int depth = -1;
-  int flags = DEFAULT_VIDEO_FLAGS;
-  grub_video_color_t color;
+  /* If gfxterm has already been initialized by calling the init_window
+     function, then leave it alone when it is set as the current terminal. */
+  if (refcount++ != 0)
+    return GRUB_ERR_NONE;
 
   /* Parse gfxmode environment variable if set.  */
-  modevar = grub_env_get ("gfxmode");
-  if (modevar)
-    {
-      char *tmp;
-      char *next_mode;
-      char *current_mode;
-      char *param;
-      char *value;
-      int mode_found = 0;
-
-      /* Take copy of env.var. as we don't want to modify that.  */
-      tmp = grub_strdup (modevar);
-      modevar = tmp;
-
-      if (grub_errno != GRUB_ERR_NONE)
-        return grub_errno;
-        
-      /* Initialize next mode.  */
-      next_mode = modevar;
-      
-      /* Loop until all modes has been tested out.  */
-      while (next_mode != NULL)
-        {
-          /* Use last next_mode as current mode.  */
-          tmp = next_mode;
-          
-          /* Reset video mode settings.  */
-          width = DEFAULT_VIDEO_WIDTH;
-          height = DEFAULT_VIDEO_HEIGHT;
-          depth = -1;
-          flags = DEFAULT_VIDEO_FLAGS;
-        
-          /* Save position of next mode and separate modes.  */
-          next_mode = grub_strchr(next_mode, ';');
-          if (next_mode)
-            {
-              *next_mode = 0;
-              next_mode++;
-            }
-
-          /* Skip whitespace.  */
-          while (grub_isspace (*tmp))
-            tmp++;
-
-          /* Initialize token holders.  */
-          current_mode = tmp;
-          param = tmp;
-          value = NULL;
-
-          /* Parse <width>x<height>[x<depth>]*/
-
-          /* Find width value.  */
-          value = param;
-          param = grub_strchr(param, 'x');
-          if (param == NULL)
-            {
-              grub_err_t rc;
-              
-              /* First setup error message.  */
-              rc = grub_error (GRUB_ERR_BAD_ARGUMENT, 
-                               "Invalid mode: %s\n",
-                               current_mode);
-              
-              /* Free memory before returning.  */
-              grub_free (modevar);
-              
-              return rc;
-            }
-
-          *param = 0;
-          param++;
-
-          width = grub_strtoul (value, 0, 0);
-          if (grub_errno != GRUB_ERR_NONE)
-            {
-              grub_err_t rc;
-              
-              /* First setup error message.  */
-              rc = grub_error (GRUB_ERR_BAD_ARGUMENT, 
-                               "Invalid mode: %s\n",
-                               current_mode);
-              
-              /* Free memory before returning.  */
-              grub_free (modevar);
-              
-              return rc;
-            }
-
-          /* Find height value.  */
-          value = param;
-          param = grub_strchr(param, 'x');
-          if (param == NULL)
-            {
-              height = grub_strtoul (value, 0, 0);
-              if (grub_errno != GRUB_ERR_NONE)
-                {
-                  grub_err_t rc;
-                  
-                  /* First setup error message.  */
-                  rc = grub_error (GRUB_ERR_BAD_ARGUMENT, 
-                                   "Invalid mode: %s\n",
-                                   current_mode);
-                                   
-                  /* Free memory before returning.  */
-                  grub_free (modevar);
-                  
-                  return rc;
-                }
-            }
-          else
-            {
-              /* We have optional color depth value.  */
-              *param = 0;
-              param++;
-
-              height = grub_strtoul (value, 0, 0);
-              if (grub_errno != GRUB_ERR_NONE)
-                {
-                  grub_err_t rc;
-                  
-                  /* First setup error message.  */
-                  rc = grub_error (GRUB_ERR_BAD_ARGUMENT, 
-                                   "Invalid mode: %s\n",
-                                   current_mode);
-                                   
-                  /* Free memory before returning.  */
-                  grub_free (modevar);
-                  
-                  return rc;
-                }
-
-              /* Convert color depth value.  */
-              value = param;
-              depth = grub_strtoul (value, 0, 0);
-              if (grub_errno != GRUB_ERR_NONE)
-                {
-                  grub_err_t rc;
-                  
-                  /* First setup error message.  */
-                  rc = grub_error (GRUB_ERR_BAD_ARGUMENT, 
-                                   "Invalid mode: %s\n",
-                                   current_mode);
-                                   
-                  /* Free memory before returning.  */
-                  grub_free (modevar);
-                  
-                  return rc;
-                }
-            }
-          
-          /* Try out video mode.  */
-          
-          /* If we have 8 or less bits, then assume that it is indexed color mode.  */
-          if ((depth <= 8) && (depth != -1))
-            flags |= GRUB_VIDEO_MODE_TYPE_INDEX_COLOR;
-
-          /* We have more than 8 bits, then assume that it is RGB color mode.  */
-          if (depth > 8)
-            flags |= GRUB_VIDEO_MODE_TYPE_RGB;
-
-          /* If user requested specific depth, forward that information to driver.  */
-          if (depth != -1)
-            flags |= (depth << GRUB_VIDEO_MODE_TYPE_DEPTH_POS)
-                     & GRUB_VIDEO_MODE_TYPE_DEPTH_MASK;
-
-          /* Try to initialize requested mode.  Ignore any errors.  */
-          grub_error_push ();
-          if (grub_video_setup (width, height, flags) != GRUB_ERR_NONE)
-            {
-              grub_error_pop ();
-              continue;
-            }
-            
-          /* Figure out what mode we ended up.  */
-          if (grub_video_get_info (&mode_info) != GRUB_ERR_NONE)
-            {
-              /* Couldn't get video mode info, restore old mode and continue to next one.  */
-              grub_error_pop ();
-              
-              grub_video_restore ();
-              continue;
-            }
-          
-          /* Restore state of error stack.  */
-          grub_error_pop ();
-          
-          /* Mode found!  Exit loop.  */
-          mode_found = 1;
-          break;
-        }
-
-      /* Free memory.  */
-      grub_free (modevar);
-      
-      if (!mode_found)
-        return grub_error (GRUB_ERR_BAD_ARGUMENT,
-                           "No suitable mode found.");
-    }
-  else
-    {
-      /* No gfxmode variable set, use defaults.  */
-      
-      /* If we have 8 or less bits, then assume that it is indexed color mode.  */
-      if ((depth <= 8) && (depth != -1))
-        flags |= GRUB_VIDEO_MODE_TYPE_INDEX_COLOR;
-
-      /* We have more than 8 bits, then assume that it is RGB color mode.  */
-      if (depth > 8)
-        flags |= GRUB_VIDEO_MODE_TYPE_RGB;
-
-      /* If user requested specific depth, forward that information to driver.  */
-      if (depth != -1)
-        flags |= (depth << GRUB_VIDEO_MODE_TYPE_DEPTH_POS)
-                 & GRUB_VIDEO_MODE_TYPE_DEPTH_MASK;
-
-      /* Initialize user requested mode.  */
-      if (grub_video_setup (width, height, flags) != GRUB_ERR_NONE)
-        return grub_errno;
-
-      /* Figure out what mode we ended up.  */
-      if (grub_video_get_info (&mode_info) != GRUB_ERR_NONE)
-        {
-          grub_video_restore ();
-          return grub_errno;
-        }
+  const char *modevar = grub_env_get ("gfxmode");
+  if (grub_video_setup_preferred_mode (modevar, 0, 
+                                       DEFAULT_VIDEO_WIDTH, 
+                                       DEFAULT_VIDEO_HEIGHT) 
+      != GRUB_ERR_NONE)
+    return grub_errno;
+
+  /* Figure out what mode we ended up.  */
+  struct grub_video_mode_info mode_info;
+  if (grub_video_get_info (&mode_info) != GRUB_ERR_NONE)
+    {
+      grub_video_restore ();
+      return grub_errno;
     }
 
   /* Make sure screen is black.  */
-  color = grub_video_map_rgb (0, 0, 0);
-  grub_video_fill_rect (color, 0, 0, mode_info.width, mode_info.height);
+  grub_video_fill_rect (grub_video_map_rgb (0, 0, 0), 
+                        0, 0, mode_info.width, mode_info.height);
   bitmap = 0;
 
+  /* Select the font to use.  */
+  char *font_name = grub_env_get ("gfxterm_font");
+  if (!font_name)
+    font_name = "";   /* Allow fallback to any font.  */
+
   /* Leave borders for virtual screen.  */
-  width = mode_info.width - (2 * DEFAULT_BORDER_WIDTH);
-  height = mode_info.height - (2 * DEFAULT_BORDER_WIDTH);
-
-  /* Create virtual screen.  */
-  if (grub_virtual_screen_setup (DEFAULT_BORDER_WIDTH, DEFAULT_BORDER_WIDTH,
-                                 width, height) != GRUB_ERR_NONE)
+  if (init_window (GRUB_VIDEO_RENDER_TARGET_DISPLAY,
+                   0, 0, mode_info.width, mode_info.height,
+                   font_name, 
+                   DEFAULT_BORDER_WIDTH) != GRUB_ERR_NONE)
     {
       grub_video_restore ();
       return grub_errno;
     }
 
-  /* Mark whole screen as dirty.  */
-  dirty_region_reset ();
-  dirty_region_add (0, 0, mode_info.width, mode_info.height);
-
   return (grub_errno = GRUB_ERR_NONE);
 }
 
+static void
+destroy_window (void)
+{
+  if (bitmap)
+    {
+      grub_video_bitmap_destroy (bitmap);
+      bitmap = 0;
+    }
+
+  repaint_callback = 0;
+  grub_virtual_screen_free ();
+}
+
+void
+grub_gfxterm_destroy_window (void)
+{
+  if (--refcount == 0)
+    destroy_window ();
+}
+
 static grub_err_t
 grub_gfxterm_fini (void)
 {
-  if (bitmap)
+  /* Don't destroy an explicitly initialized terminal instance when it is
+     unset as the current terminal.  */
+  if (--refcount == 0)
     {
-      grub_video_bitmap_destroy (bitmap);
-      bitmap = 0;
+      destroy_window ();
+      grub_video_restore ();
     }
 
-  grub_virtual_screen_free ();
-
-  grub_video_restore ();
-
-  return GRUB_ERR_NONE;
+  return (grub_errno = GRUB_ERR_NONE);
 }
 
 static void
@@ -509,9 +375,15 @@
                     unsigned int width, unsigned int height)
 {
   grub_video_color_t color;
-
-  grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
-
+  grub_video_rect_t saved_view;
+
+  grub_video_set_active_render_target (render_target);
+  /* Save viewport and set it to our window. */
+  grub_video_get_viewport ((unsigned *) &saved_view.x, 
+                           (unsigned *) &saved_view.y, 
+                           (unsigned *) &saved_view.width, 
+                           (unsigned *) &saved_view.height);
+  grub_video_set_viewport (window.x, window.y, window.width, window.height);
 
   if (bitmap)
     {
@@ -578,6 +450,14 @@
                                      y - virtual_screen.offset_y,
                                      width, height);      
     }
+
+  /* Restore saved viewport. */
+  grub_video_set_viewport (saved_view.x, saved_view.y,
+                           saved_view.width, saved_view.height);
+  grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
+  
+  if (repaint_callback)
+    repaint_callback (x, y, width, height);
 }
 
 static void
@@ -661,11 +541,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 +556,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 +567,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 +587,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;
 
@@ -769,7 +652,16 @@
       dirty_region_add_virtualscreen ();
     }
   else
-    {      
+    {
+      grub_video_rect_t saved_view;
+      grub_video_set_active_render_target (render_target);
+      /* Save viewport and set it to our window.  */
+      grub_video_get_viewport ((unsigned *) &saved_view.x, 
+                               (unsigned *) &saved_view.y, 
+                               (unsigned *) &saved_view.width, 
+                               (unsigned *) &saved_view.height);
+      grub_video_set_viewport (window.x, window.y, window.width, window.height);
+
       /* Clear new border area.  */
       grub_video_fill_rect (color, 
                             virtual_screen.offset_x, virtual_screen.offset_y, 
@@ -778,10 +670,18 @@
       /* Scroll physical screen.  */
       grub_video_scroll (color, 0, -virtual_screen.char_height);      
 
+      /* Restore saved viewport. */
+      grub_video_set_viewport (saved_view.x, saved_view.y,
+                               saved_view.width, saved_view.height);
+      grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
+
       /* Draw cursor if visible.  */
       if (virtual_screen.cursor_state)
         write_cursor ();
     }
+
+  if (repaint_callback)
+    repaint_callback (window.x, window.y, window.width, window.height);
 }
 
 static void
@@ -822,14 +722,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 +743,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 +763,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;
@@ -877,13 +781,17 @@
 }
 
 static grub_ssize_t
-grub_gfxterm_getcharwidth (grub_uint32_t c)
+grub_gfxterm_getcharwidth (grub_uint32_t c __attribute__((unused)))
 {
-  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
@@ -945,7 +853,8 @@
   /* Clear text layer.  */
   grub_video_set_active_render_target (text_layer);
   color = virtual_screen.bg_color;
-  grub_video_fill_rect (color, 0, 0, mode_info.width, mode_info.height);
+  grub_video_fill_rect (color, 0, 0,
+                        virtual_screen.width, virtual_screen.height);
   grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
 
   /* Mark virtual screen to be redrawn.  */
@@ -1014,8 +923,23 @@
   dirty_region_redraw ();
 }
 
+void 
+grub_gfxterm_set_repaint_callback (grub_gfxterm_repaint_callback_t func)
+{
+  repaint_callback = func;
+}
+
+/* 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)
 {
@@ -1031,7 +955,7 @@
       
       /* Mark whole screen as dirty.  */
       dirty_region_reset ();
-      dirty_region_add (0, 0, mode_info.width, mode_info.height);
+      dirty_region_add (0, 0, window.width, window.height);
     }
 
   /* If filename was provided, try to load that.  */
@@ -1042,16 +966,40 @@
     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 (window.width != (int) grub_video_bitmap_get_width (bitmap)
+              || window.height != (int) grub_video_bitmap_get_height (bitmap)) 
+            {
+              struct grub_video_bitmap *scaled_bitmap;
+              grub_video_bitmap_create_scaled (&scaled_bitmap,
+                                              window.width, 
+                                              window.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 ();
-        dirty_region_add (0, 0, mode_info.width, mode_info.height);
+        dirty_region_add (0, 0, window.width, window.height);
       }
     }
   
@@ -1082,9 +1030,16 @@
     .next = 0
   };
 
+grub_term_t
+grub_gfxterm_get_term (void)
+{
+  return &grub_video_term;
+}
+
 GRUB_MOD_INIT(term_gfxterm)
 {
   my_mod = mod;
+  refcount = 0;
   grub_term_register (&grub_video_term);
 
   grub_register_command ("background_image",
@@ -1092,7 +1047,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 file 'util/fonttool/makepf2.sh'
--- util/fonttool/makepf2.sh	1970-01-01 00:00:00 +0000
+++ util/fonttool/makepf2.sh	2008-08-07 23:50:20 +0000
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+# This script will generate GRUB 2 PFF2 font files out of BDF.
+# You need the GRUB font utilities "Convert" program for this.
+
+DIR=$( dirname $0 )
+
+for font in "$@"
+do
+  out=`basename "${font}" | cut -d'.' -f1`
+  out="${out}.pf2"
+  echo "Converting ${font} -> ${out} ..."
+  java -cp "${DIR}/build/fonttool.jar" \
+    org.gnu.grub.fonttool.Converter \
+    --in="${font}" \
+    --out="${out}"
+done

=== 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-07-12 14:40:50 +0000
+++ util/i386/pc/grub-mkrescue.in	2008-07-19 21:39:24 +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-08-03 01:30:02 +0000
@@ -0,0 +1,101 @@
+/* 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)
+{
+  *dst = 0;
+
+  /* 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);
+      *dst = 0;
+      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-19 19:42:37 +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,166 @@
   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.  */
+
+  framebuffer.render_target.mode_info.mode_type
+    |= GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED;
+  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;
+
+  framebuffer.render_target.mode_info.mode_type
+    |= GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED;
+  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;
+
+  framebuffer.render_target.mode_info.mode_type
+    &= ~GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED;
+  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 +895,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 +934,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 +1005,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 +1102,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 +1136,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 +1148,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 +1184,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 +1227,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 +1263,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 +1299,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 +1342,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 +1457,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 +1712,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 +1817,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 +1859,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-08-01 03:06:55 +0000
+++ video/readers/jpeg.c	2008-08-03 03:57:46 +0000
@@ -1,20 +1,20 @@
 /*
- *  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/>.
- */
+*  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/bitmap.h>
 #include <grub/types.h>
@@ -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-08-01 03:06:55 +0000
+++ video/readers/png.c	2008-08-03 04:00:22 +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	2008-08-01 03:06:55 +0000
+++ video/readers/tga.c	2008-08-03 03:57:46 +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);

=== added file 'video/setmode.c'
--- video/setmode.c	1970-01-01 00:00:00 +0000
+++ video/setmode.c	2008-07-19 19:31:46 +0000
@@ -0,0 +1,249 @@
+/* video/setmode.c - Smart video mode selection based on preferences. */
+/*
+ *  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/>.
+ */
+
+#include <grub/video.h>
+#include <grub/types.h>
+#include <grub/mm.h>
+#include <grub/misc.h>
+
+/* Set the video mode based on the preferred modes specified in MODE_LIST in
+   the form: <width>x<height>[x<depth>][;...]
+
+   For example:  640x480;800x600x8;400x300x32
+
+   If MODE_LIST is null, or no modes in it are usable, then DEFAULT_WIDTH and
+   DEFAULT_HEIGHT are used to set the mode.  The MODE_FLAGS argument determines
+   the video mode flags such as double buffering that are used.  */
+
+grub_err_t
+grub_video_setup_preferred_mode (const char *mode_list, int mode_flags,
+                                 int default_width, int default_height)
+{
+  int mode_found = 0;
+
+  if (mode_list != NULL)
+    {
+      /* Take copy of mode_list as we don't want tat.  */
+      char *const modes_copy = grub_strdup (mode_list);
+      if (modes_copy == NULL)
+        return grub_errno;
+
+      /* Initialize next mode.  */
+      char *next_mode = modes_copy;
+
+      /* Loop until all modes has been tested out.  */
+      while ((next_mode != NULL) && !mode_found)
+        {
+          /* Use last next_mode as current mode.  */
+          char *tmp = next_mode;
+
+          int width = -1;
+          int height = -1;
+          int depth = -1;
+
+          /* Save position of next mode and separate modes.  */
+          next_mode = grub_strchr(next_mode, ';');
+          if (next_mode)
+            {
+              *next_mode = 0;
+              next_mode++;
+            }
+
+          /* Skip whitespace.  */
+          while (grub_isspace (*tmp))
+            tmp++;
+
+          /* Initialize token holders.  */
+          char *current_mode = tmp;
+          char *param = tmp;
+          char *value = NULL;
+
+          /* Parse <width>x<height>[x<depth>]*/
+
+          /* Find width value.  */
+          value = param;
+          param = grub_strchr(param, 'x');
+          if (param == NULL)
+            {
+              grub_err_t rc;
+
+              /* First setup error message.  */
+              rc = grub_error (GRUB_ERR_BAD_ARGUMENT, 
+                               "Invalid mode: %s\n",
+                               current_mode);
+
+              /* Free memory before returning.  */
+              grub_free (modes_copy);
+
+              return rc;
+            }
+
+          *param = 0;
+          param++;
+
+          width = grub_strtoul (value, 0, 0);
+          if (grub_errno != GRUB_ERR_NONE)
+            {
+              grub_err_t rc;
+
+              /* First setup error message.  */
+              rc = grub_error (GRUB_ERR_BAD_ARGUMENT, 
+                               "Invalid mode: %s\n",
+                               current_mode);
+
+              /* Free memory before returning.  */
+              grub_free (modes_copy);
+
+              return rc;
+            }
+
+          /* Find height value.  */
+          value = param;
+          param = grub_strchr(param, 'x');
+          if (param == NULL)
+            {
+              height = grub_strtoul (value, 0, 0);
+              if (grub_errno != GRUB_ERR_NONE)
+                {
+                  grub_err_t rc;
+
+                  /* First setup error message.  */
+                  rc = grub_error (GRUB_ERR_BAD_ARGUMENT, 
+                                   "Invalid mode: %s\n",
+                                   current_mode);
+
+                  /* Free memory before returning.  */
+                  grub_free (modes_copy);
+
+                  return rc;
+                }
+            }
+          else
+            {
+              /* We have optional color depth value.  */
+              *param = 0;
+              param++;
+
+              height = grub_strtoul (value, 0, 0);
+              if (grub_errno != GRUB_ERR_NONE)
+                {
+                  grub_err_t rc;
+
+                  /* First setup error message.  */
+                  rc = grub_error (GRUB_ERR_BAD_ARGUMENT, 
+                                   "Invalid mode: %s\n",
+                                   current_mode);
+
+                  /* Free memory before returning.  */
+                  grub_free (modes_copy);
+
+                  return rc;
+                }
+
+              /* Convert color depth value.  */
+              value = param;
+              depth = grub_strtoul (value, 0, 0);
+              if (grub_errno != GRUB_ERR_NONE)
+                {
+                  grub_err_t rc;
+
+                  /* First setup error message.  */
+                  rc = grub_error (GRUB_ERR_BAD_ARGUMENT, 
+                                   "Invalid mode: %s\n",
+                                   current_mode);
+
+                  /* Free memory before returning.  */
+                  grub_free (modes_copy);
+
+                  return rc;
+                }
+            }
+
+          /* Try out video mode.  */
+
+          int flags = mode_flags;
+          /* If we have <= 8 bits, assume it is an indexed color mode.  */
+          if ((depth <= 8) && (depth != -1))
+            flags |= GRUB_VIDEO_MODE_TYPE_INDEX_COLOR;
+
+          /* We have > 8 bits; assume that it is RGB color mode.  */
+          if (depth > 8)
+            flags |= GRUB_VIDEO_MODE_TYPE_RGB;
+
+          /* If user requested specific depth, pass the request to driver.  */
+          if (depth != -1)
+            flags |= (depth << GRUB_VIDEO_MODE_TYPE_DEPTH_POS)
+              & GRUB_VIDEO_MODE_TYPE_DEPTH_MASK;
+
+          /* Try to initialize requested mode.  Ignore any errors.  */
+          grub_error_push ();
+          if (grub_video_setup (width, height, flags) != GRUB_ERR_NONE)
+            {
+              grub_error_pop ();
+              continue;
+            }
+
+          /* Figure out what mode we ended up.  */
+          struct grub_video_mode_info mode_info;
+          if (grub_video_get_info (&mode_info) != GRUB_ERR_NONE)
+            {
+              /* Couldn't get video mode info, restore old mode
+                 and continue to next one.  */
+              grub_error_pop ();
+
+              grub_video_restore ();
+              continue;
+            }
+
+          /* Restore state of error stack.  */
+          grub_error_pop ();
+
+          /* Mode found!  Exit loop.  */
+          mode_found = 1;
+        }
+
+      /* Free memory.  */
+      grub_free (modes_copy);
+    }
+
+  if (!mode_found)
+    {
+      /* No gfxmode variable set, or no listed mode was supported.
+         Use the caller-specified defaults.  */
+      int flags = mode_flags | GRUB_VIDEO_MODE_TYPE_RGB;
+
+      /* Initialize user requested mode.  */
+      if (grub_video_setup (default_width, default_height, flags) 
+          != GRUB_ERR_NONE)
+        return grub_errno;
+
+      /* Figure out what mode we ended up.  */
+      struct grub_video_mode_info mode_info;
+      if (grub_video_get_info (&mode_info) != GRUB_ERR_NONE)
+        grub_video_restore ();
+      else
+        mode_found = 1;
+    }
+
+  if (!mode_found)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT,
+                       "No suitable mode found.");
+
+  return (grub_errno = GRUB_ERR_NONE);
+}

=== 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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
8VWK1NDiZEeBIvQSVBe6QQKjnqIJjOp3FngIHJUZe9ZQIKo73d4Iclg8Iqt+cpQ1iNgV6ugVERCw
J3807thKEevIuaaJSvMnQTROeyrfVfFLJywLFivO+0jlX65iU4d+tY2qSq0ihknTOR1H1OdoziZp
+59PpRrEdYeHKrvb4wQZyb5lt2lHvcgvIkmXey0ixphqObKfSXk0s4ZN3DQb6BzSmalc988ZmrPN
kwxmyvDxvZuXYbDJro6eLMtyjOtdHTl45aM2HPjJVuuseKtmWtGyk8btzM5O59nfj5+6ce8mlEPS
0kS0Cs9O8uHDjGEGzkkVcBhRRpOcQ5E4ZqQXDU8IlwQuIiHCfewrVyI6dKO2Dqkp+3tsnTL87IlM
jH0/OvL9GeaaIbKkfmzV1xsjlW5NdEm+IqJYvGxN15OlPXZs1T5y9fH6N2bjnJ/UnnUc/FGzNezK
8W5XnPjntjM65acJ791est1OGraQlKBKNL8xKcvnifY4f6siJcxRZ2ykSMCJwctzUpkRMzg3JD1E
kUiakSJoRFJmEug+4m0WEVURmF5KOu03dhGlKsCqmVVVLYrq1U+J5HV9xLySOoG5lq2T5JWqRjCM
JVMNm0pSkptlly5YdCGycVm5BPqkJJh5snMp3ciM5NSabpVK1SnG3b8bnDSRNXF/jU5tpFWZHhlo
S3FCeUumaZ8t7k+/TZpkz0MvVnT51ZmrVmjrpTCi9PLxNrdsN2eWs+MLumNYmc+PS3TNz8Vfbhyy
8Ve1dsPFEoxfJoZuX1Vo+nbfdpsvyzbjufij8UlEUlE/lWViilAsFYhBEYIfJ/G4KyIiqffpRpSi
lL0hWgP/HwkgPAejuSKklA5RCh/zQt5dH/6qgetGpjj5zpNB3Fgt0Dy73DoHdQkdlJFBRTQnOovh
3xwMCQOWO8UqKYHI8C6ixIESZmc6iVMwm6mAyxJaECAOrDlVx0s3LruWj1s3c20FE6flM27/BE33
avr6q8csPR0rPHDLLp6UW8YYVcvpqyVZtZ4WfGtVOt1xmUaqszm7zzjJlozf8ztSqq7jNRmxu17Z
njpw9+0ynE6Txm9eZMVs88ZD9ZJBrBZxPpytalG95dlWzhr0UcL1dvcnTVwmrh3m1MjKyzZPN27B
w0WXLuukvrMfiSzZwvty6ZNzOM2Szl0sobtGT+0narNfizfpo/cRMvr1yw25Y8zLaqR2cr/GblM7
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
h81lveBr+CVt0s2PeHwDXMU2/XhYmIstU7k+ozHNn8g+QUu239bbUtbLdkvD5QZE4Val9piHWW0N
OQheMJ4pwXxPG6Nd7qYK1W0nwjxaYq981uiwvT2BAaCDkqlMRmIXUnXaxcLSrtifMDsDOzHJXxmR
dAMyxBtnlZfk1yBasl25Pvkp17ciHcDGLcpQfHNbrKKnQGSUNwzmz6XaOI2T5hwzmy/gP0D0P4QP
nPPnPjPn4/rCvZ+Ds1SzzXovZms7rD+oA2YYVOkCCyJAB1QBAUmMptftKas77aLiiGyNyHc2eYDA
KwPYBsQIAgC6gt2+yIINeieriHc7A05O8SFDQTJ74oQzhuTioM3Bldo7cvGdvGQkrtYFwAhk9Y84
+hedD3OhhEe0wKlXOp/Z/VTuiaV2Vc590cjdHUn6nMqJjef4mwq1nE0Q8EaC79Oen2/2HtWnuMIj
1uc5T4dvK1Xqntk6UpcqCqsqR9ah5+cT9WTY653q/z2a96pO3AXOC1KEIqnCoRf00FNIK9QKgLCq
EXxq1SGpdXvEV9syaSXX9wXrEo/vAO1tjrNKv34C7tdcj1WucRfGX8gXu2cxspT+YO+qxq/FqWMT
7xPub/06pYUtBN1/QHmGnq137jueIXZn0Sb/DMtS1IHGuShnF5rZI6Svhq0o/Osmp/kOgz7egIHx
D4DhI8lS2oZqoiwd9yciqIvMyLuCUYIebvD89QLtb7LNbg9rssJrXjp3ApXXYk0o99cYMW3po9lY
GgClh8Lrlh623OxIh6AAhECPFdEn0BmRmY6A0MX5P7jx2tvrrwkuB4LG1obs7QW5ZvcIXANCiwrk
PQDYCzJc6V1oGKcYKCPIqqV2219O9uRuy1fc749SfPi9oekipxiiQgmWk8WBZSzydjnXng/zaDqT
zizR3IyqK7KR6xWqCCuvqepZVaUMv/0sDOG8yiVEU7GkewOBmGwh3vxySAeg/ifNS2ylspbKWltL
bKWltLaW0tpbS2ltLbKWltLaW0tpbSsbSolpbZS0tpVtlLS2ltLaW0tpbZS0tpbZS0tpbS2ltLaW
0q2ylpbS2ltLaW0tpbS2ltLaW0tpbS2ltKiWylpbSolspaW0qJaW0qolspaW0tpbS2ylpbS2ltLa
W0tpbS2ltLaW0tpbS2ltLaW0tpbZS0qJbKWltLbKWltLbKWlVbS2ylpbS2lW0tpbZS0q2ltlLS2l
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
