# Bazaar merge directive format 2 (Bazaar 0.90)
# revision_id: colin@gibibit.com-20080816001443-4jgm6402d77qak97
# target_branch: ../../repo/trunk-clean
# testament_sha1: 4d387d3526dd500d6021ca16746813856e173102
# timestamp: 2008-08-15 17:23:39 -0700
# source_branch: http://grub.gibibit.com/bzr/trunk-clean
# base_revision_id: colin@gibibit.com-20080816000932-p6ptwiy9ylhhtd1v
# 
# 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-15 14:39:02 +0000
+++ ChangeLog	2008-08-16 00:13:21 +0000
@@ -572,7 +572,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>
 
@@ -705,6 +704,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-14 17:59:33 +0000
+++ conf/common.rmk	2008-08-16 00:14:43 +0000
@@ -287,6 +287,7 @@
 	cmp.mod cat.mod help.mod font.mod search.mod		\
 	loopback.mod fs_uuid.mod configfile.mod echo.mod	\
 	terminfo.mod test.mod blocklist.mod hexdump.mod		\
+	gfxmenu.mod                                             \
 	read.mod sleep.mod loadenv.mod crc.mod
 
 # For hello.mod.
@@ -294,6 +295,26 @@
 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/icon_manager.c \
+	gfxmenu/theme_loader.c \
+	gfxmenu/widget-box.c \
+	gfxmenu/gui_canvas.c \
+	gfxmenu/gui_circular_progress.c \
+	gfxmenu/gui_box.c \
+	gfxmenu/gui_label.c \
+	gfxmenu/gui_list.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)
@@ -330,7 +351,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)
 
@@ -395,7 +416,7 @@
 crc_mod_LDFLAGS = $(COMMON_LDFLAGS)
 
 # Misc.
-pkglib_MODULES += gzio.mod bufio.mod elf.mod
+pkglib_MODULES += gzio.mod bufio.mod elf.mod trig.mod
 
 # For elf.mod.
 elf_mod_SOURCES = kern/elf.c
@@ -411,3 +432,8 @@
 bufio_mod_SOURCES = io/bufio.c
 bufio_mod_CFLAGS = $(COMMON_CFLAGS)
 bufio_mod_LDFLAGS = $(COMMON_LDFLAGS)
+
+# For trig.mod.
+trig_mod_SOURCES = lib/trig.c
+trig_mod_CFLAGS = $(COMMON_CFLAGS)
+trig_mod_LDFLAGS = $(COMMON_LDFLAGS)

=== modified file 'conf/i386-pc.rmk'
--- conf/i386-pc.rmk	2008-08-15 14:39:02 +0000
+++ conf/i386-pc.rmk	2008-08-16 00:14:43 +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
@@ -252,7 +254,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)
 
@@ -267,7 +269,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-14 21:15:21 +0000
@@ -0,0 +1,128 @@
+# 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"
+status-font: "Helvetica 8"
+terminal-font: "Fixed 9"
+title-color: "40, 40, 40"
+status-color: "#FFF"
+status-bg-color: "0, 166, 183, 128"
+desktop-image: "without-leaves.png"
+desktop-color: "0, 154, 183"
+terminal-box: "terminal_*.png"
+
++ boot_menu {
+   position = (120, 60)
+   preferred_size = (400, -1)
+   item_font = "Helvetica Bold 14"
+   selected_item_font = "Helvetica Bold 14"
+   item_color = "0, 0, 0"
+   selected_item_color = "203, 251, 255"
+   menu_pixmap_style = "menu_*.png"
+   selected_item_pixmap_style = "select_*.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)
+   preferred_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)
+   preferred_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 file 'gentrigtables.py'
--- gentrigtables.py	1970-01-01 00:00:00 +0000
+++ gentrigtables.py	2008-08-15 23:59:38 +0000
@@ -0,0 +1,59 @@
+#!/usr/bin/python
+# Script to generate trigonometric function tables.
+# By Colin D. Bennett <colin@gibibit.com>, 2008-08-15.
+#
+#  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/>.
+
+from math import *
+from sys import stdout
+
+def write(x):
+    stdout.write(x)
+
+def writeTable(arr, name):
+    indent = ' ' * 4
+    write("short ")
+    write(name)
+    write("[] =\n{\n")
+    write(indent)
+    for i in range(len(arr)):
+        if i != 0:
+            write(",")
+            if i % 10 == 0:
+                write("\n")
+                write(indent)
+        write("%d" % arr[i])
+    write("\n};\n")
+
+def main():
+    sintab = []
+    costab = []
+    for i in range(256):
+        # Convert to an angle in 1/256 of a circle.
+        x = i * 2 * pi / 256
+        sintab.append(int(round(sin(x) * 16384)))
+        costab.append(int(round(cos(x) * 16384)))
+
+    write("#define TRIG_ANGLE_MAX 256\n")
+    write("#define TRIG_FRACTION_SCALE 16384\n")
+    writeTable(sintab, "sintab")
+    writeTable(costab, "costab")
+
+if __name__ == "__main__":
+    main()
+
+# vim:ai et sw=4 ts=4

=== added directory 'gfxmenu'
=== added file 'gfxmenu/gfxmenu.c'
--- gfxmenu/gfxmenu.c	1970-01-01 00:00:00 +0000
+++ gfxmenu/gfxmenu.c	2008-08-15 23:59:38 +0000
@@ -0,0 +1,231 @@
+/* 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 (c == '3')
+    {
+      grub_gfxmenu_view_load_theme (view,
+                                    "/boot/grub/themes/ubuntu1/theme.txt");
+    }
+  else if (c == '4')
+    {
+      grub_gfxmenu_view_load_theme (view,
+                                    "/boot/grub/themes/ubuntu2/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-14 21:14:23 +0000
@@ -0,0 +1,264 @@
+/* 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)
+    {
+      int pw;
+      int ph;
+      grub_video_rect_t r;
+      grub_gui_component_t comp;
+      
+      comp = cur->component;
+
+      /* Give the child its preferred size.  */
+      comp->ops->get_preferred_size (comp, &pw, &ph);
+      comp->ops->get_bounds (comp, &r);
+      if (r.width != pw || r.height != ph)
+        {
+          r.width = pw;
+          r.height = ph;
+          comp->ops->set_bounds (comp, &r);
+        }
+
+      /* Paint the child.  */
+      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_circular_progress.c'
--- gfxmenu/gui_circular_progress.c	1970-01-01 00:00:00 +0000
+++ gfxmenu/gui_circular_progress.c	2008-08-15 23:59:38 +0000
@@ -0,0 +1,339 @@
+/* gui_circular_process.c - GUI circular progress indicator 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>
+#include <grub/gfxmenu_view.h>
+#include <grub/gfxwidgets.h>
+#include <grub/trig.h>
+
+struct grub_gui_circular_progress
+{
+  struct grub_gui_component_ops *circprog_ops;
+
+  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 num_ticks;
+  int start_angle;
+  int ticks_disappear;
+  char *theme_dir;
+  int need_to_load_pixmaps;
+  char *center_file;
+  char *tick_file;
+  struct grub_video_bitmap *center_bitmap;
+  struct grub_video_bitmap *tick_bitmap;
+};
+
+typedef struct grub_gui_circular_progress *circular_progress_t;
+
+static void
+circprog_destroy (void *vself)
+{
+  circular_progress_t self = vself;
+  grub_free (self);
+}
+
+static const char *
+circprog_get_id (void *vself)
+{
+  circular_progress_t self = vself;
+  return self->id;
+}
+
+static int
+circprog_is_instance (void *vself __attribute__((unused)), const char *type)
+{
+  return grub_strcmp (type, "component") == 0;
+}
+
+static struct grub_video_bitmap *
+load_bitmap (const char *dir, const char *file)
+{
+  struct grub_video_bitmap *bitmap;
+  char *abspath;
+
+  /* Check arguments.  */
+  if (! dir || ! file)
+    return 0;
+
+  /* Resolve to an absolute path.  */
+  abspath = grub_resolve_relative_path (dir, file);
+  if (! abspath)
+    return 0;
+
+  /* Load the image.  */
+  grub_errno = GRUB_ERR_NONE;
+  grub_video_bitmap_load (&bitmap, abspath);
+  grub_errno = GRUB_ERR_NONE;
+
+  grub_free (abspath);
+  return bitmap;
+}
+
+static int
+check_pixmaps (circular_progress_t self)
+{
+  if (self->need_to_load_pixmaps)
+    {
+      if (self->center_bitmap)
+        grub_video_bitmap_destroy (self->center_bitmap);
+      self->center_bitmap = load_bitmap (self->theme_dir, self->center_file);
+      self->tick_bitmap = load_bitmap (self->theme_dir, self->tick_file);
+      self->need_to_load_pixmaps = 0;
+    }
+
+  return (self->center_bitmap != 0 && self->tick_bitmap != 0);
+}
+
+static void
+circprog_paint (void *vself)
+{
+  circular_progress_t self = vself;
+
+  if (! self->visible)
+    return;
+  if (! check_pixmaps (self))
+    return;
+
+  grub_video_rect_t vpsave;
+  grub_gui_set_viewport (&self->bounds, &vpsave);
+
+  int width = self->bounds.width;
+  int height = self->bounds.height;
+  int center_width = grub_video_bitmap_get_width (self->center_bitmap);
+  int center_height = grub_video_bitmap_get_height (self->center_bitmap);
+  int tick_width = grub_video_bitmap_get_width (self->tick_bitmap);
+  int tick_height = grub_video_bitmap_get_height (self->tick_bitmap);
+  grub_video_blit_bitmap (self->center_bitmap, GRUB_VIDEO_BLIT_BLEND,
+                          (width - center_width) / 2,
+                          (height - center_height) / 2, 0, 0,
+                          center_width, center_height);
+
+  int radius = width / 2 - tick_width / 2 - 1;
+  int nticks = (self->num_ticks
+                * (self->value - self->start)
+                / (self->end - self->start));
+  int tick_begin;
+  int tick_end;
+  /* Do ticks appear or disappear as the value approached the end?  */
+  if (self->ticks_disappear)
+    {
+      tick_begin = nticks;
+      tick_end = self->num_ticks - 1;
+    }
+  else
+    {
+      tick_begin = 0;
+      tick_end = nticks - 1;
+    }
+
+  int i;
+  for (i = tick_begin; i < tick_end; i++)
+    {
+       int x;
+       int y;
+       int angle;
+
+       /* Calculate the location of the tick.  */
+       angle = self->start_angle + i * GRUB_TRIG_ANGLE_MAX / self->num_ticks;
+       x = width / 2 + (grub_cos (angle) * radius / GRUB_TRIG_FRACTION_SCALE);
+       y = height / 2 + (grub_sin (angle) * radius / GRUB_TRIG_FRACTION_SCALE);
+
+       /* Adjust (x,y) so the tick is centered.  */
+       x -= tick_width / 2;
+       y -= tick_height / 2;
+
+       /* Draw the tick.  */
+       grub_video_blit_bitmap (self->tick_bitmap, GRUB_VIDEO_BLIT_BLEND,
+                               x, y, 0, 0, tick_width, tick_height);
+    }
+
+  grub_gui_restore_viewport (&vpsave);
+}
+
+static void
+circprog_set_parent (void *vself, grub_gui_container_t parent)
+{
+  circular_progress_t self = vself;
+  self->parent = parent;
+}
+
+static grub_gui_container_t
+circprog_get_parent (void *vself)
+{
+  circular_progress_t self = vself;
+  return self->parent;
+}
+
+static void
+circprog_set_bounds (void *vself, const grub_video_rect_t *bounds)
+{
+  circular_progress_t self = vself;
+  self->bounds = *bounds;
+}
+
+static void
+circprog_get_bounds (void *vself, grub_video_rect_t *bounds)
+{
+  circular_progress_t self = vself;
+  *bounds = self->bounds;
+}
+
+static void
+circprog_get_preferred_size (void *vself, int *width, int *height)
+{
+  circular_progress_t self = vself;
+
+  *width = 0;
+  *height = 0;
+
+  /* Allow preferred dimensions to override the circprog dimensions.  */
+  if (self->preferred_width >= 0)
+    *width = self->preferred_width;
+  if (self->preferred_height >= 0)
+    *height = self->preferred_height;
+}
+
+static void
+circprog_set_property (void *vself, const char *name, const char *value)
+{
+  circular_progress_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, "num_ticks") == 0)
+    {
+      self->num_ticks = grub_strtol (value, 0, 10);
+    }
+  else if (grub_strcmp (name, "start_angle") == 0)
+    {
+      self->start_angle = grub_strtol (value, 0, 10);
+    }
+  else if (grub_strcmp (name, "ticks_disappear") == 0)
+    {
+      self->ticks_disappear = grub_strcmp (value, "false") != 0;
+    }
+  else if (grub_strcmp (name, "center_bitmap") == 0)
+    {
+      self->need_to_load_pixmaps = 1;
+      grub_free (self->center_file);
+      self->center_file = value ? grub_strdup (value) : 0;
+    }
+  else if (grub_strcmp (name, "tick_bitmap") == 0)
+    {
+      self->need_to_load_pixmaps = 1;
+      grub_free (self->tick_file);
+      self->tick_file = value ? grub_strdup (value) : 0;
+    }
+  else if (grub_strcmp (name, "theme_dir") == 0)
+    {
+      self->need_to_load_pixmaps = 1;
+      grub_free (self->theme_dir);
+      self->theme_dir = value ? grub_strdup (value) : 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, "visible") == 0)
+    {
+      self->visible = 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 circprog_ops =
+{
+  .destroy = circprog_destroy,
+  .get_id = circprog_get_id,
+  .is_instance = circprog_is_instance,
+  .paint = circprog_paint,
+  .set_parent = circprog_set_parent,
+  .get_parent = circprog_get_parent,
+  .set_bounds = circprog_set_bounds,
+  .get_bounds = circprog_get_bounds,
+  .get_preferred_size = circprog_get_preferred_size,
+  .set_property = circprog_set_property
+};
+
+grub_gui_component_t
+grub_gui_circular_progress_new (void)
+{
+  circular_progress_t self;
+  self = grub_malloc (sizeof (*self));
+  if (! self)
+    return 0;
+  self->circprog_ops = &circprog_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->start = 0;
+  self->end = 0;
+  self->value = 0;
+  self->num_ticks = 64;
+  self->start_angle = -64;
+  self->ticks_disappear = 0;
+
+  self->theme_dir = 0;
+  self->need_to_load_pixmaps = 0;
+  self->center_file = 0;
+  self->tick_file = 0;
+  self->center_bitmap = 0;
+  self->tick_bitmap = 0;
+
+  return (grub_gui_component_t) self;
+}

=== 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_list.c'
--- gfxmenu/gui_list.c	1970-01-01 00:00:00 +0000
+++ gfxmenu/gui_list.c	2008-08-15 23:59:38 +0000
@@ -0,0 +1,624 @@
+/* gui_list.c - GUI component to display a selectable list of items.  */
+/*
+ *  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/gfxmenu_view.h>
+#include <grub/gfxwidgets.h>
+
+struct grub_gui_list_impl
+{
+  struct grub_gui_list_ops *list_ops;
+
+  grub_gui_container_t parent;
+  grub_video_rect_t bounds;
+  char *id;
+  int preferred_width;
+  int preferred_height;
+  int visible;
+
+  int icon_width;
+  int icon_height;
+  int item_height;
+  int item_padding;
+  int item_icon_space;
+  int item_spacing;
+  grub_font_t item_font;
+  grub_font_t selected_item_font;
+  grub_gui_color_t item_color;
+  int selected_item_color_set;
+  grub_gui_color_t selected_item_color;
+
+  int draw_scrollbar;
+  int need_to_recreate_scrollbar;
+  char *scrollbar_frame_pattern;
+  char *scrollbar_thumb_pattern;
+  grub_gfxmenu_box_t scrollbar_frame;
+  grub_gfxmenu_box_t scrollbar_thumb;
+  int scrollbar_width;
+
+  int min_items_shown;
+  int max_items_shown;
+  int first_shown_index;
+
+  int need_to_recreate_boxes;
+  char *theme_dir;
+  char *menu_box_pattern;
+  char *selected_item_box_pattern;
+  grub_gfxmenu_box_t menu_box;
+  grub_gfxmenu_box_t selected_item_box;
+
+  grub_gfxmenu_icon_manager_t icon_manager;
+  grub_gfxmenu_model_t menu;
+};
+
+typedef struct grub_gui_list_impl *list_impl_t;
+
+static void
+list_destroy (void *vself)
+{
+  list_impl_t self = vself;
+
+  grub_free (self->theme_dir);
+  grub_free (self->menu_box_pattern);
+  grub_free (self->selected_item_box_pattern);
+  if (self->menu_box)
+    self->menu_box->destroy (self->menu_box);
+  if (self->selected_item_box)
+    self->selected_item_box->destroy (self->selected_item_box);
+  if (self->icon_manager)
+    grub_gfxmenu_icon_manager_destroy (self->icon_manager);
+
+  grub_free (self);
+}
+
+static int
+get_num_shown_items (list_impl_t self)
+{
+  int n = grub_gfxmenu_model_get_num_entries (self->menu);
+  if (self->min_items_shown != -1 && n < self->min_items_shown)
+    n = self->min_items_shown;
+  if (self->max_items_shown != -1 && n > self->max_items_shown)
+    n = self->max_items_shown;
+  return n;
+}
+
+static int
+check_boxes (list_impl_t self)
+{
+  if (self->need_to_recreate_boxes)
+    {
+      grub_gui_recreate_box (&self->menu_box,
+                             self->menu_box_pattern,
+                             self->theme_dir);
+
+      grub_gui_recreate_box (&self->selected_item_box,
+                             self->selected_item_box_pattern,
+                             self->theme_dir);
+
+      self->need_to_recreate_boxes = 0;
+    }
+
+  return (self->menu_box != 0 && self->selected_item_box != 0);
+}
+
+static int
+check_scrollbar (list_impl_t self)
+{
+  if (self->need_to_recreate_scrollbar)
+    {
+      grub_gui_recreate_box (&self->scrollbar_frame,
+                             self->scrollbar_frame_pattern,
+                             self->theme_dir);
+
+      grub_gui_recreate_box (&self->scrollbar_thumb,
+                             self->scrollbar_thumb_pattern,
+                             self->theme_dir);
+
+      self->need_to_recreate_scrollbar = 0;
+    }
+
+  return (self->scrollbar_frame != 0 && self->scrollbar_thumb != 0);
+}
+
+static const char *
+list_get_id (void *vself)
+{
+  list_impl_t self = vself;
+  return self->id;
+}
+
+static int
+list_is_instance (void *vself __attribute__((unused)), const char *type)
+{
+  return (grub_strcmp (type, "component") == 0
+          || grub_strcmp (type, "list") == 0);
+}
+
+static struct grub_video_bitmap *
+get_item_icon (list_impl_t self, int item_index)
+{
+  grub_menu_entry_t entry;
+  entry = grub_gfxmenu_model_get_entry (self->menu, item_index);
+  if (! entry)
+    return 0;
+
+  return grub_gfxmenu_icon_manager_get_icon (self->icon_manager, entry);
+}
+
+static void
+make_selected_item_visible (list_impl_t self)
+{
+  int selected_index = grub_gfxmenu_model_get_selected_index (self->menu);
+  if (selected_index < 0)
+    return;   /* No item is selected.  */
+  int num_shown_items = get_num_shown_items (self);
+  int last_shown_index = self->first_shown_index + (num_shown_items - 1);
+  if (selected_index < self->first_shown_index)
+    self->first_shown_index = selected_index;
+  else if (selected_index > last_shown_index)
+    self->first_shown_index = selected_index - (num_shown_items - 1);
+}
+
+/* Draw a scrollbar on the menu.  */
+static void
+draw_scrollbar (list_impl_t self,
+                int value, int extent, int min, int max,
+                int rightx, int topy, int height)
+{
+  grub_gfxmenu_box_t frame = self->scrollbar_frame;
+  grub_gfxmenu_box_t thumb = self->scrollbar_thumb;
+  int frame_vertical_pad = (frame->get_top_pad (frame)
+                            + frame->get_bottom_pad (frame));
+  int frame_horizontal_pad = (frame->get_left_pad (frame)
+                              + frame->get_right_pad (frame));
+  int tracktop = topy + frame->get_top_pad (frame);
+  int tracklen = height - frame_vertical_pad;
+  frame->set_content_size (frame, self->scrollbar_width, tracklen);
+  int thumby = tracktop + tracklen * (value - min) / (max - min);
+  int thumbheight = tracklen * extent / (max - min) + 1;
+  thumb->set_content_size (thumb,
+                           self->scrollbar_width - frame_horizontal_pad,
+                           thumbheight - (thumb->get_top_pad (thumb)
+                                          + thumb->get_bottom_pad (thumb)));
+  frame->draw (frame,
+               rightx - (self->scrollbar_width + frame_horizontal_pad),
+               topy);
+  thumb->draw (thumb,
+               rightx - (self->scrollbar_width - frame->get_right_pad (frame)),
+               thumby);
+}
+
+/* Draw the list of items.  */
+static void
+draw_menu (list_impl_t self)
+{
+  if (! self->menu_box || ! self->selected_item_box)
+    return;
+
+  int boxpad = self->item_padding;
+  int icon_text_space = self->item_icon_space;
+  int item_vspace = self->item_spacing;
+
+  int ascent = grub_font_get_ascent (self->item_font);
+  int descent = grub_font_get_descent (self->item_font);
+  int item_height = self->item_height;
+
+  int total_num_items = grub_gfxmenu_model_get_num_entries (self->menu);
+  int num_shown_items = get_num_shown_items (self);
+  grub_gfxmenu_box_t box = self->menu_box;
+  int width = self->bounds.width;
+  int height = self->bounds.height;
+
+  int box_left_pad = box->get_left_pad (box);
+  int box_top_pad = box->get_top_pad (box);
+  int box_right_pad = box->get_right_pad (box);
+  int box_bottom_pad = box->get_bottom_pad (box);
+
+  box->set_content_size (box,
+                         width - box_left_pad - box_right_pad,
+                         height - box_top_pad - box_bottom_pad);
+
+  box->draw (box, 0, 0);
+
+  make_selected_item_visible (self);
+
+  int drawing_scrollbar = (self->draw_scrollbar
+                           && (num_shown_items < total_num_items)
+                           && check_scrollbar (self));
+
+  int scrollbar_h_space = drawing_scrollbar ? self->scrollbar_width : 0;
+
+  int item_top = box_top_pad + boxpad;
+  int item_left = box_left_pad + boxpad;
+  int menu_index;
+  int visible_index;
+
+  for (visible_index = 0, menu_index = self->first_shown_index;
+       visible_index < num_shown_items && menu_index < total_num_items;
+       visible_index++, menu_index++)
+    {
+      int is_selected =
+        (menu_index == grub_gfxmenu_model_get_selected_index (self->menu));
+
+      if (is_selected)
+        {
+          grub_gfxmenu_box_t selbox = self->selected_item_box;
+          int sel_leftpad = selbox->get_left_pad (selbox);
+          int sel_toppad = selbox->get_top_pad (selbox);
+          selbox->set_content_size (selbox,
+                                    (width - 2 * boxpad
+                                     - box_left_pad - box_right_pad
+                                     - scrollbar_h_space),
+                                    item_height);
+          selbox->draw (selbox,
+                        item_left - sel_leftpad,
+                        item_top - sel_toppad);
+        }
+
+      struct grub_video_bitmap *icon;
+      if ((icon = get_item_icon (self, menu_index)) != 0)
+        grub_video_blit_bitmap (icon, GRUB_VIDEO_BLIT_BLEND,
+                                item_left,
+                                item_top + (item_height - self->icon_height) / 2,
+                                0, 0, self->icon_width, self->icon_height);
+
+      const char *item_title =
+        grub_gfxmenu_model_get_entry_title (self->menu, menu_index);
+      grub_font_t font =
+        (is_selected && self->selected_item_font
+         ? self->selected_item_font
+         : self->item_font);
+      grub_gui_color_t text_color =
+        ((is_selected && self->selected_item_color_set)
+         ? self->selected_item_color
+         : self->item_color);
+      grub_video_draw_string (item_title,
+                              font,
+                              grub_gui_map_color (text_color),
+                              item_left + self->icon_width + icon_text_space,
+                              (item_top + (item_height - (ascent + descent))
+                               / 2 + ascent));
+
+      item_top += item_height + item_vspace;
+    }
+
+  if (drawing_scrollbar)
+    draw_scrollbar (self,
+                    self->first_shown_index, num_shown_items,
+                    0, total_num_items,
+                    width - box_right_pad + self->scrollbar_width,
+                    box_top_pad + boxpad,
+                    height - box_top_pad - box_bottom_pad);
+}
+
+static void
+list_paint (void *vself)
+{
+  list_impl_t self = vself;
+
+  if (! self->visible)
+    return;
+
+  check_boxes (self);
+
+  grub_video_rect_t vpsave;
+  grub_gui_set_viewport (&self->bounds, &vpsave);
+  draw_menu (self);
+  grub_gui_restore_viewport (&vpsave);
+}
+
+static void
+list_set_parent (void *vself, grub_gui_container_t parent)
+{
+  list_impl_t self = vself;
+  self->parent = parent;
+}
+
+static grub_gui_container_t
+list_get_parent (void *vself)
+{
+  list_impl_t self = vself;
+  return self->parent;
+}
+
+static void
+list_set_bounds (void *vself, const grub_video_rect_t *bounds)
+{
+  list_impl_t self = vself;
+  self->bounds = *bounds;
+}
+
+static void
+list_get_bounds (void *vself, grub_video_rect_t *bounds)
+{
+  list_impl_t self = vself;
+  *bounds = self->bounds;
+}
+
+static void
+list_get_preferred_size (void *vself, int *width, int *height)
+{
+  list_impl_t self = vself;
+
+  if (check_boxes (self))
+    {
+      int boxpad = self->item_padding;
+      int item_vspace = self->item_spacing;
+      int item_height = self->item_height;
+      int num_items = get_num_shown_items (self);
+
+      grub_gfxmenu_box_t box = self->menu_box;
+      int box_left_pad = box->get_left_pad (box);
+      int box_top_pad = box->get_top_pad (box);
+      int box_right_pad = box->get_right_pad (box);
+      int box_bottom_pad = box->get_bottom_pad (box);
+
+      *width = 400 + 2 * boxpad + box_left_pad + box_right_pad;
+
+      /* Set the menu box height to fit the items.  */
+      *height = (item_height * num_items
+                 + item_vspace * (num_items - 1)
+                 + 2 * boxpad
+                 + box_top_pad + box_bottom_pad);
+    }
+  else
+    {
+      *width = 0;
+      *height = 0;
+    }
+
+  /* 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
+list_set_property (void *vself, const char *name, const char *value)
+{
+  list_impl_t self = vself;
+  if (grub_strcmp (name, "item_font") == 0)
+    {
+      self->item_font = grub_font_get (value);
+    }
+  else if (grub_strcmp (name, "selected_item_font") == 0)
+    {
+      if (! value || grub_strcmp (value, "inherit") == 0)
+        self->selected_item_font = 0;
+      else
+        self->selected_item_font = grub_font_get (value);
+    }
+  else if (grub_strcmp (name, "item_color") == 0)
+    {
+      self->item_color = grub_gui_parse_color (value);
+    }
+  else if (grub_strcmp (name, "selected_item_color") == 0)
+    {
+      if (! value || grub_strcmp (value, "inherit") == 0)
+        {
+          self->selected_item_color_set = 0;
+        }
+      else
+        {
+          self->selected_item_color_set = 1;
+          self->selected_item_color = grub_gui_parse_color (value);
+        }
+    }
+  else if (grub_strcmp (name, "icon_width") == 0)
+    {
+      self->icon_width = grub_strtol (value, 0, 10);
+      grub_gfxmenu_icon_manager_set_icon_size (self->icon_manager,
+                                               self->icon_width,
+                                               self->icon_height);
+    }
+  else if (grub_strcmp (name, "icon_height") == 0)
+    {
+      self->icon_height = grub_strtol (value, 0, 10);
+      grub_gfxmenu_icon_manager_set_icon_size (self->icon_manager,
+                                               self->icon_width,
+                                               self->icon_height);
+    }
+  else if (grub_strcmp (name, "item_height") == 0)
+    {
+      self->item_height = grub_strtol (value, 0, 10);
+    }
+  else if (grub_strcmp (name, "item_padding") == 0)
+    {
+      self->item_padding = grub_strtol (value, 0, 10);
+    }
+  else if (grub_strcmp (name, "item_icon_space") == 0)
+    {
+      self->item_icon_space = grub_strtol (value, 0, 10);
+    }
+  else if (grub_strcmp (name, "item_spacing") == 0)
+    {
+      self->item_spacing = grub_strtol (value, 0, 10);
+    }
+  else if (grub_strcmp (name, "visible") == 0)
+    {
+      self->visible = grub_strcmp (value, "false") != 0;
+    }
+  else if (grub_strcmp (name, "menu_pixmap_style") == 0)
+    {
+      self->need_to_recreate_boxes = 1;
+      grub_free (self->menu_box_pattern);
+      self->menu_box_pattern = value ? grub_strdup (value) : 0;
+    }
+  else if (grub_strcmp (name, "selected_item_pixmap_style") == 0)
+    {
+      self->need_to_recreate_boxes = 1;
+      grub_free (self->selected_item_box_pattern);
+      self->selected_item_box_pattern = value ? grub_strdup (value) : 0;
+    }
+  else if (grub_strcmp (name, "scrollbar_frame") == 0)
+    {
+      self->need_to_recreate_scrollbar = 1;
+      grub_free (self->scrollbar_frame_pattern);
+      self->scrollbar_frame_pattern = value ? grub_strdup (value) : 0;
+    }
+  else if (grub_strcmp (name, "scrollbar_thumb") == 0)
+    {
+      self->need_to_recreate_scrollbar = 1;
+      grub_free (self->scrollbar_thumb_pattern);
+      self->scrollbar_thumb_pattern = value ? grub_strdup (value) : 0;
+    }
+  else if (grub_strcmp (name, "scrollbar_width") == 0)
+    {
+      self->scrollbar_width = grub_strtol (value, 0, 10);
+    }
+  else if (grub_strcmp (name, "scrollbar") == 0)
+    {
+      self->draw_scrollbar = grub_strcmp (value, "false") != 0;
+    }
+  else if (grub_strcmp (name, "min_items_shown") == 0)
+    {
+      self->min_items_shown = grub_strtol (value, 0, 10);
+    }
+  else if (grub_strcmp (name, "max_items_shown") == 0)
+    {
+      self->max_items_shown = grub_strtol (value, 0, 10);
+    }
+  else if (grub_strcmp (name, "theme_dir") == 0)
+    {
+      self->need_to_recreate_boxes = 1;
+      grub_free (self->theme_dir);
+      self->theme_dir = value ? grub_strdup (value) : 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;
+    }
+}
+
+/* Set necessary information that the gfxmenu view provides.  */
+static void
+list_set_view_info (void *vself,
+                    const char *theme_path,
+                    grub_gfxmenu_model_t menu)
+{
+  list_impl_t self = vself;
+  grub_gfxmenu_icon_manager_set_theme_path (self->icon_manager, theme_path);
+  self->menu = menu;
+}
+
+static struct grub_gui_list_ops list_ops =
+{
+  .component_ops =
+    {
+      .destroy = list_destroy,
+      .get_id = list_get_id,
+      .is_instance = list_is_instance,
+      .paint = list_paint,
+      .set_parent = list_set_parent,
+      .get_parent = list_get_parent,
+      .set_bounds = list_set_bounds,
+      .get_bounds = list_get_bounds,
+      .get_preferred_size = list_get_preferred_size,
+      .set_property = list_set_property
+    },
+  .set_view_info = list_set_view_info
+};
+
+grub_gui_component_t
+grub_gui_list_new (void)
+{
+  list_impl_t self;
+  grub_font_t default_font;
+  grub_gui_color_t default_fg_color;
+  grub_gui_color_t default_bg_color;
+
+  self = grub_malloc (sizeof (*self));
+  if (! self)
+    return 0;
+
+  self->list_ops = &list_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;
+
+  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);
+
+  self->icon_width = 32;
+  self->icon_height = 32;
+  self->item_height = 42;
+  self->item_padding = 14;
+  self->item_icon_space = 4;
+  self->item_spacing = 16;
+  self->item_font = default_font;
+  self->selected_item_font = 0;    /* Default to using the item_font.  */
+  self->item_color = default_fg_color;
+  self->selected_item_color_set = 0;  /* Default to using the item_color.  */
+  self->selected_item_color = default_fg_color;
+
+  self->draw_scrollbar = 1;
+  self->need_to_recreate_scrollbar = 1;
+  self->scrollbar_frame = 0;
+  self->scrollbar_thumb = 0;
+  self->scrollbar_frame_pattern = 0;
+  self->scrollbar_thumb_pattern = 0;
+  self->scrollbar_width = 16;
+
+  self->min_items_shown = -1;
+  self->max_items_shown = -1;
+  self->first_shown_index = 0;
+
+  self->need_to_recreate_boxes = 0;
+  self->theme_dir = 0;
+  self->menu_box_pattern = 0;
+  self->selected_item_box_pattern = 0;
+  self->menu_box = grub_gfxmenu_create_box (0, 0);
+  self->selected_item_box = grub_gfxmenu_create_box (0, 0);
+
+  self->icon_manager = grub_gfxmenu_icon_manager_new ();
+  if (! self->icon_manager)
+    {
+      self->list_ops->component_ops.destroy (self);
+      return 0;
+    }
+  grub_gfxmenu_icon_manager_set_icon_size (self->icon_manager,
+                                           self->icon_width,
+                                           self->icon_height);
+  return (grub_gui_component_t) self;
+}

=== 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-15 23:59:38 +0000
@@ -0,0 +1,378 @@
+/* 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>
+#include <grub/gfxmenu_view.h>
+#include <grub/gfxwidgets.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;
+
+  char *theme_dir;
+  int need_to_recreate_pixmaps;
+  char *bar_pattern;
+  char *highlight_pattern;
+  grub_gfxmenu_box_t bar_box;
+  grub_gfxmenu_box_t highlight_box;
+};
+
+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 int
+check_pixmaps (grub_gui_progress_bar_t self)
+{
+  if (self->need_to_recreate_pixmaps)
+    {
+      grub_gui_recreate_box (&self->bar_box,
+                             self->bar_pattern,
+                             self->theme_dir);
+
+      grub_gui_recreate_box (&self->highlight_box,
+                             self->highlight_pattern,
+                             self->theme_dir);
+
+      self->need_to_recreate_pixmaps = 0;
+    }
+
+  return (self->bar_box != 0 && self->highlight_box != 0);
+}
+
+static void
+draw_filled_rect_bar (grub_gui_progress_bar_t self)
+{
+  /* 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);
+}
+
+static void
+draw_pixmap_bar (grub_gui_progress_bar_t self)
+{
+  grub_gfxmenu_box_t bar = self->bar_box;
+  grub_gfxmenu_box_t hl = self->highlight_box;
+  int w = self->bounds.width;
+  int h = self->bounds.height;
+  int bar_l_pad = bar->get_left_pad (bar);
+  int bar_r_pad = bar->get_right_pad (bar);
+  int bar_t_pad = bar->get_top_pad (bar);
+  int bar_b_pad = bar->get_bottom_pad (bar);
+  int bar_h_pad = bar_l_pad + bar_r_pad;
+  int bar_v_pad = bar_t_pad + bar_b_pad;
+  int tracklen = w - bar_h_pad;
+  int trackheight = h - bar_v_pad;
+  bar->set_content_size (bar, tracklen, trackheight);
+
+  int barwidth = (tracklen
+                  * (self->value - self->start)
+                  / (self->end - self->start));
+  hl->set_content_size (hl, barwidth, h - bar_v_pad);
+
+  bar->draw (bar, 0, 0);
+  hl->draw (hl, bar_l_pad, bar_t_pad);
+}
+
+static void
+draw_text (grub_gui_progress_bar_t self)
+{
+  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);
+      int width = self->bounds.width;
+      int height = self->bounds.height;
+
+      /* Center the text. */
+      int text_width = grub_font_get_string_width (font, text);
+      int x = (width - text_width) / 2;
+      int y = ((height - grub_font_get_descent (font)) / 2
+               + grub_font_get_ascent (font) / 2);
+      grub_video_draw_string (text, font, text_color, x, y);
+    }
+}
+
+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);
+
+  if (check_pixmaps (self))
+    draw_pixmap_bar (self);
+  else
+    draw_filled_rect_bar (self);
+
+  draw_text (self);
+
+  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, "bar_style") == 0)
+    {
+      self->need_to_recreate_pixmaps = 1;
+      grub_free (self->bar_pattern);
+      self->bar_pattern = value ? grub_strdup (value) : 0;
+    }
+  else if (grub_strcmp (name, "highlight_style") == 0)
+    {
+      self->need_to_recreate_pixmaps = 1;
+      grub_free (self->highlight_pattern);
+      self->highlight_pattern = value ? grub_strdup (value) : 0;
+    }
+  else if (grub_strcmp (name, "theme_dir") == 0)
+    {
+      self->need_to_recreate_pixmaps = 1;
+      grub_free (self->theme_dir);
+      self->theme_dir = value ? grub_strdup (value) : 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, "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->start = 0;
+  self->end = 0;
+  self->value = 0;
+  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;
+
+  self->theme_dir = 0;
+  self->need_to_recreate_pixmaps = 0;
+  self->bar_pattern = 0;
+  self->highlight_pattern = 0;
+  self->bar_box = 0;
+  self->highlight_box = 0;
+
+  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-14 20:55:18 +0000
@@ -0,0 +1,101 @@
+/* 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);
+}
+
+
+struct iterate_recursively_state
+{
+  grub_gui_component_callback callback;
+  void *userdata;
+};
+
+static
+void iterate_recursively_cb (grub_gui_component_t component, void *userdata)
+{
+  struct iterate_recursively_state *state;
+
+  state = (struct iterate_recursively_state *) userdata;
+  state->callback (component, state->userdata);
+
+  if (component->ops->is_instance (component, "container"))
+    {
+      grub_gui_container_t container;
+      container = (grub_gui_container_t) component;
+      container->ops->iterate_children (container,
+                                        iterate_recursively_cb,
+                                        state);
+    }
+}
+
+void
+grub_gui_iterate_recursively (grub_gui_component_t root,
+                              grub_gui_component_callback cb,
+                              void *userdata)
+{
+  struct iterate_recursively_state state;
+  state.callback = cb;
+  state.userdata = userdata;
+  iterate_recursively_cb (root, &state);
+}

=== added file 'gfxmenu/icon_manager.c'
--- gfxmenu/icon_manager.c	1970-01-01 00:00:00 +0000
+++ gfxmenu/icon_manager.c	2008-08-14 16:32:41 +0000
@@ -0,0 +1,254 @@
+/* icon_manager.c - gfxmenu icon manager.  */
+/*
+ *  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/stringutil.h>
+#include <grub/bitmap.h>
+#include <grub/bitmap_scale.h>
+#include <grub/menu.h>
+#include <grub/icon_manager.h>
+
+/* Currently hard coded to '.png' extension.  */
+static const char icon_extension[] = ".png";
+
+typedef struct icon_entry
+{
+  char *class_name;
+  struct grub_video_bitmap *bitmap;
+  struct icon_entry *next;
+} *icon_entry_t;
+
+struct grub_gfxmenu_icon_manager
+{
+  char *theme_path;
+  int icon_width;
+  int icon_height;
+
+  /* Icon cache: linked list w/ dummy head node.  */
+  struct icon_entry cache;
+};
+
+
+/* Create a new icon manager and return a point to it.  */
+grub_gfxmenu_icon_manager_t
+grub_gfxmenu_icon_manager_new (void)
+{
+  grub_gfxmenu_icon_manager_t mgr;
+  mgr = grub_malloc (sizeof (*mgr));
+  if (! mgr)
+    return 0;
+
+  mgr->theme_path = 0;
+  mgr->icon_width = 0;
+  mgr->icon_height = 0;
+
+  /* Initialize the dummy head node.  */
+  mgr->cache.class_name = 0;
+  mgr->cache.bitmap = 0;
+  mgr->cache.next = 0;
+
+  return mgr;
+}
+
+/* Destroy the icon manager MGR, freeing all resources used by it.
+
+Note: Any bitmaps returned by grub_gfxmenu_icon_manager_get_icon()
+are destroyed and must not be used by the caller after this function
+is called.  */
+void
+grub_gfxmenu_icon_manager_destroy (grub_gfxmenu_icon_manager_t mgr)
+{
+  grub_gfxmenu_icon_manager_clear_cache (mgr);
+  grub_free (mgr->theme_path);
+  grub_free (mgr);
+}
+
+/* Clear the icon cache.  */
+void
+grub_gfxmenu_icon_manager_clear_cache (grub_gfxmenu_icon_manager_t mgr)
+{
+  icon_entry_t cur;
+  icon_entry_t next;
+  for (cur = mgr->cache.next; cur; cur = next)
+    {
+      next = cur->next;
+      grub_free (cur->class_name);
+      grub_video_bitmap_destroy (cur->bitmap);
+      grub_free (cur);
+    }
+  mgr->cache.next = 0;
+}
+
+/* Set the theme path.  If the theme path is changed, the icon cache
+   is cleared.  */
+void
+grub_gfxmenu_icon_manager_set_theme_path (grub_gfxmenu_icon_manager_t mgr,
+                                          const char *path)
+{
+  /* Clear the cache if the theme path has changed.  */
+  if (((mgr->theme_path == 0) != (path == 0))
+      || (grub_strcmp (mgr->theme_path, path) != 0))
+    grub_gfxmenu_icon_manager_clear_cache (mgr);
+
+  grub_free (mgr->theme_path);
+  mgr->theme_path = path ? grub_strdup (path) : 0;
+}
+
+/* Set the icon size.  When icons are requested from the icon manager,
+   they are scaled to this size before being returned.  If the size is
+   changed, the icon cache is cleared.  */
+void
+grub_gfxmenu_icon_manager_set_icon_size (grub_gfxmenu_icon_manager_t mgr,
+                                         int width, int height)
+{
+  /* If the width or height is changed, we must clear the cache, since the
+     scaled bitmaps are stored in the cache.  */
+  if (width != mgr->icon_width || height != mgr->icon_height)
+    grub_gfxmenu_icon_manager_clear_cache (mgr);
+
+  mgr->icon_width = width;
+  mgr->icon_height = height;
+}
+
+/* Try to load an icon for the specified CLASS_NAME in the directory DIR.
+   Returns 0 if the icon could not be loaded, or returns a pointer to a new
+   bitmap if it was successful.  */
+static struct grub_video_bitmap *
+try_loading_icon (grub_gfxmenu_icon_manager_t mgr,
+                  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,
+                                   mgr->icon_width, mgr->icon_height,
+                                   raw_bitmap,
+                                   GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST);
+  grub_video_bitmap_destroy (raw_bitmap);
+  if (! scaled_bitmap)
+    return 0;
+
+  return scaled_bitmap;
+}
+
+/* Get the icon for the specified class CLASS_NAME.  If an icon for
+   CLASS_NAME already exists in the cache, then a reference to the cached
+   bitmap is returned.  If it is not cached, then it is loaded and cached.
+   If no icon could be could for CLASS_NAME, then 0 is returned.  */
+static struct grub_video_bitmap *
+get_icon_by_class (grub_gfxmenu_icon_manager_t mgr, const char *class_name)
+{
+  /* First check the icon cache.  */
+  icon_entry_t entry;
+  for (entry = mgr->cache.next; entry; entry = entry->next)
+    {
+      if (grub_strcmp (entry->class_name, class_name) == 0)
+        return entry->bitmap;
+    }
+
+  if (! mgr->theme_path)
+    return 0;
+
+  /* Otherwise, we search for an icon to load.  */
+  char *theme_dir = grub_get_dirname (mgr->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 (mgr, icons_dir, class_name);
+      grub_free (icons_dir);
+    }
+  if (! icon)
+    {
+      /* If the theme doesn't have an appropriate icon, check in
+         "grub/themes/icons".  */
+      /* TODO use GRUB prefix "/icons" */
+      icons_dir = grub_resolve_relative_path (theme_dir, "../icons/");
+      if (icons_dir)
+        {
+          icon = try_loading_icon (mgr, icons_dir, class_name);
+          grub_free (icons_dir);
+        }
+    }
+  grub_free (theme_dir);
+
+  /* No icon was found.  */
+  /* This should probably be noted in the cache, so that a search is not
+     performed each time an icon for CLASS_NAME is requested.  */
+  if (! icon)
+    return 0;
+
+  /* Insert a new cache entry for this icon.  */
+  entry = grub_malloc (sizeof (*entry));
+  if (! entry)
+    {
+      grub_video_bitmap_destroy (icon);
+      return 0;
+    }
+  entry->class_name = grub_strdup (class_name);
+  entry->bitmap = icon;
+  entry->next = mgr->cache.next;
+  mgr->cache.next = entry;   /* Link it into the cache.  */
+  return entry->bitmap;
+}
+
+/* Get the best available icon for ENTRY.  Beginning with the first class
+   listed in the menu entry and proceeding forward, an icon for each class
+   is searched for.  The first icon found is returned.  The returned icon
+   is scaled to the size specified by
+   grub_gfxmenu_icon_manager_set_icon_size().
+
+     Note:  Bitmaps returned by this function are destroyed when the
+            icon manager is destroyed.
+ */
+struct grub_video_bitmap *
+grub_gfxmenu_icon_manager_get_icon (grub_gfxmenu_icon_manager_t mgr,
+                                    grub_menu_entry_t entry)
+{
+  struct grub_menu_entry_class *c;
+  struct grub_video_bitmap *icon;
+
+  /* Try each class in succession.  */
+  icon = 0;
+  for (c = entry->classes->next; c && ! icon; c = c->next)
+    icon = get_icon_by_class (mgr, c->name);
+  return icon;
+}

=== 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-15 16:17: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 (hexits == 8)
+            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_strtol (s, 0, 10);
+  if ((s = grub_strchr (s, ',')) == 0)
+    return 0;
+  s++;  /* Skip the element separator (the comma).  */
+  y = grub_strtol (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-15 23:59:38 +0000
@@ -0,0 +1,609 @@
+/* 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 ABSPATTERN to find the pixmap files for
+   it, storing the new box instance at *BOXPTR.
+   PATTERN should be of the form: "(hd0,0)/somewhere/style*.png".
+   The '*' then gets substituted with the various pixmap names that the
+   box uses.
+
+   Returns zero on failure, nonzero on success.
+ */
+static int
+recreate_box_absolute (grub_gfxmenu_box_t *boxptr, const char *abspattern)
+{
+  char *prefix;
+  char *suffix;
+  char *star;
+  grub_gfxmenu_box_t box;
+
+  star = grub_strchr (abspattern, '*');
+  if (! star)
+    return 0;
+
+  /* Prefix:  Get the part before the '*'.  */
+  prefix = grub_malloc (star - abspattern + 1);
+  if (! prefix)
+    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 (prefix);
+  if (! box)
+    return 0;
+
+  if (*boxptr)
+    (*boxptr)->destroy (*boxptr);
+  *boxptr = box;
+  return 1;
+}
+
+
+/* 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.
+
+   Important!  The value of *BOXPTR must be initialized!  It must either
+   (1) Be 0 (a NULL pointer), or
+   (2) Be a pointer to a valid 'grub_gfxmenu_box_t' instance.
+   In this case, the previous instance is destroyed.
+
+   Returns zero on failure, nonzero on success.
+ */
+int
+grub_gui_recreate_box (grub_gfxmenu_box_t *boxptr,
+                       const char *pattern, const char *theme_dir)
+{
+  char *abspattern;
+  int success;
+
+  /* Check arguments.  */
+  if (! pattern || ! theme_dir)
+    return 0;
+
+  /* Resolve to an absolute path.  */
+  abspattern = grub_resolve_relative_path (theme_dir, pattern);
+  if (! abspattern)
+    return 0;
+
+  /* Create the box.  */
+  success = recreate_box_absolute (boxptr, abspattern);
+  grub_free (abspattern);
+  return success;
+}
+
+/* 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 ("message-font", name))
+    view->message_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 ("message-color", name))
+    view->message_color = grub_gui_parse_color (value);
+  else if (! grub_strcmp ("message-bg-color", name))
+    view->message_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;
+      grub_errno = GRUB_ERR_NONE;
+      if (grub_video_bitmap_load (&raw_bitmap, path) != GRUB_ERR_NONE)
+        {
+          grub_errno = 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 ("terminal-box", name))
+    grub_gui_recreate_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);
+    }
+}
+
+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_progress_bar_new ();
+    }
+  else if (grub_strcmp (name, "circular_progress") == 0)
+    {
+      component = grub_gui_circular_progress_new ();
+    }
+  else if (grub_strcmp (name, "boot_menu") == 0)
+    {
+      component = grub_gui_list_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);
+    }
+
+next_line:
+  grub_free (name);
+  advance_to_next_line (p);
+}
+
+struct theme_info
+{
+  const char *theme_dir;
+  const char *theme_path;
+};
+
+static void
+update_theme_info_visit (grub_gui_component_t component, void *userdata)
+{
+  struct theme_info *info;
+  info = userdata;
+  component->ops->set_property (component, "theme_dir", info->theme_dir);
+  component->ops->set_property (component, "theme_path", info->theme_path);
+}
+
+/* Update any boot menu components with the current menu model and
+   theme path.  */
+static void
+update_component_tree_theme_info (grub_gui_component_t root,
+                                  const char *theme_dir,
+                                  const char *theme_path)
+{
+  struct theme_info info;
+  info.theme_dir = theme_dir;
+  info.theme_path = theme_path;
+  grub_gui_iterate_recursively (root, update_theme_info_visit, &info);
+}
+
+/* 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 ();
+  ((grub_gui_component_t) view->canvas)
+    ->ops->set_bounds ((grub_gui_component_t) view->canvas,
+                       &view->screen);
+
+  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);
+  update_component_tree_theme_info ((grub_gui_component_t) view->canvas,
+                                    p.theme_dir,
+                                    theme_path);
+
+  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-15 16:49:18 +0000
@@ -0,0 +1,505 @@
+/* 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>
+#include <grub/icon_manager.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->model = model;
+  view->canvas = 0;
+
+  view->title_font = default_font;
+  view->message_font = default_font;
+  view->terminal_font_name = grub_strdup ("Fixed 10");
+  view->title_color = default_fg_color;
+  view->message_color = default_bg_color;
+  view->message_bg_color = default_fg_color;
+  view->desktop_image = 0;
+  view->desktop_color = default_bg_color;
+  view->terminal_box = grub_gfxmenu_create_box (0, 0);
+  view->title_text = grub_strdup ("GRUB Boot Menu");
+  view->progress_message_text = 0;
+  view->theme_path = 0;
+
+  if (! grub_gfxmenu_view_load_theme (view, theme_path))
+    {
+      grub_gfxmenu_view_destroy (view);
+      return 0;
+    }
+
+  init_terminal (view);
+
+  return view;
+}
+
+/* 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->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);
+  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 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);
+}
+
+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
+update_menu_visit (grub_gui_component_t component,
+                   void *userdata)
+{
+  grub_gfxmenu_view_t view;
+  view = userdata;
+  if (component->ops->is_instance (component, "list"))
+    {
+      grub_gui_list_t list = (grub_gui_list_t) component;
+      list->ops->set_view_info (list, view->theme_path, view->model);
+    }
+}
+
+/* Update any boot menu components with the current menu model and
+   theme path.  */
+static void
+update_menu_components (grub_gfxmenu_view_t view)
+{
+  grub_gui_iterate_recursively ((grub_gui_component_t) view->canvas,
+                                update_menu_visit, view);
+}
+
+static void
+draw_message (grub_gfxmenu_view_t view)
+{
+  char *text = view->progress_message_text;
+  if (! text)
+    return;
+
+  grub_font_t font = view->message_font;
+  grub_video_color_t color = grub_gui_map_color (view->message_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->message_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);
+  update_menu_components (view);
+
+  draw_background (view);
+  if (view->canvas)
+    view->canvas->ops->component.paint (view->canvas);
+  draw_title (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-15 16:49:18 +0000
@@ -0,0 +1,271 @@
+/* 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 int
+get_height (struct grub_video_bitmap *bitmap)
+{
+  if (bitmap)
+    return grub_video_bitmap_get_height (bitmap);
+  else
+    return 0;
+}
+
+static int
+get_width (struct grub_video_bitmap *bitmap)
+{
+  if (bitmap)
+    return grub_video_bitmap_get_width (bitmap);
+  else
+    return 0;
+}
+
+static void
+blit (grub_gfxmenu_box_t self, int pixmap_index, int x, int y)
+{
+  struct grub_video_bitmap *bitmap;
+  bitmap = self->scaled_pixmaps[pixmap_index];
+  if (! bitmap)
+    return;
+  grub_video_blit_bitmap (bitmap, GRUB_VIDEO_BLIT_BLEND,
+                          x, y, 0, 0,
+                          grub_video_bitmap_get_width (bitmap),
+                          grub_video_bitmap_get_height (bitmap));
+}
+
+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;
+
+  height_n = get_height (self->scaled_pixmaps[BOX_PIXMAP_N]);
+  height_s = get_height (self->scaled_pixmaps[BOX_PIXMAP_S]);
+  height_e = get_height (self->scaled_pixmaps[BOX_PIXMAP_E]);
+  height_w = get_height (self->scaled_pixmaps[BOX_PIXMAP_W]);
+  width_n = get_width (self->scaled_pixmaps[BOX_PIXMAP_N]);
+  width_s = get_width (self->scaled_pixmaps[BOX_PIXMAP_S]);
+  width_e = get_width (self->scaled_pixmaps[BOX_PIXMAP_E]);
+  width_w = get_width (self->scaled_pixmaps[BOX_PIXMAP_W]);
+
+  /* Draw sides.  */
+  blit (self, BOX_PIXMAP_N, x + width_w, y);
+  blit (self, BOX_PIXMAP_S, x + width_w, y + height_n + self->content_height);
+  blit (self, BOX_PIXMAP_E, x + width_w + self->content_width, y + height_n);
+  blit (self, BOX_PIXMAP_W, x, y + height_n);
+
+  /* Draw corners.  */
+  blit (self, BOX_PIXMAP_NW, x, y);
+  blit (self, BOX_PIXMAP_NE, x + width_w + self->content_width, y);
+  blit (self, BOX_PIXMAP_SE,
+        x + width_w + self->content_width,
+        y + height_n + self->content_height);
+  blit (self, BOX_PIXMAP_SW, x, y + height_n + self->content_height);
+
+  /* Draw center.  */
+  blit (self, BOX_PIXMAP_CENTER, x + width_w, y + height_n);
+}
+
+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->content_width = width;
+  self->content_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 get_width (self->raw_pixmaps[BOX_PIXMAP_W]);
+}
+
+static int
+get_top_pad (grub_gfxmenu_box_t self)
+{
+  return get_height (self->raw_pixmaps[BOX_PIXMAP_N]);
+}
+
+static int
+get_right_pad (grub_gfxmenu_box_t self)
+{
+  return get_width (self->raw_pixmaps[BOX_PIXMAP_E]);
+}
+
+static int
+get_bottom_pad (grub_gfxmenu_box_t self)
+{
+  return get_height (self->raw_pixmaps[BOX_PIXMAP_S]);
+}
+
+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! */
+}
+
+
+/* Create a new box.  If PIXMAPS_PREFIX and PIXMAPS_SUFFIX are both non-null,
+   then an attempt is made to load the north, south, east, west, northwest,
+   northeast, southeast, southwest, and center pixmaps.
+   If either PIXMAPS_PREFIX or PIXMAPS_SUFFIX is 0, then no pixmaps are
+   loaded, and the box has zero-width borders and is drawn transparent.  */
+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->content_width = 0;
+  box->content_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++)
+    {
+      if (pixmaps_prefix && pixmaps_suffix)
+        {
+          /* TODO XXX dynamically allocate PATH, ensure it's large enough */
+          grub_sprintf (path, "%s%s%s",
+                        pixmaps_prefix, box_pixmap_names[i], pixmaps_suffix);
+          grub_errno = GRUB_ERR_NONE;
+          grub_video_bitmap_load (&box->raw_pixmaps[i], path);
+          grub_errno = GRUB_ERR_NONE;   /* Clear any potential error.  */
+        }
+      else
+        {
+          box->raw_pixmaps[i] = 0;
+        }
+      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->get_right_pad = get_right_pad;
+  box->get_bottom_pad = get_bottom_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-15 14:46:37 +0000
@@ -0,0 +1,91 @@
+/* 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>
+#include <grub/gfxwidgets.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);
+
+int
+grub_gui_recreate_box (grub_gfxmenu_box_t *boxptr,
+                       const char *pattern, const char *theme_dir);
+
+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>
+#include <grub/icon_manager.h>
+
+/* Definition of the private representation of the view.  */
+struct grub_gfxmenu_view
+{
+  grub_video_rect_t screen;
+
+  grub_font_t title_font;
+  grub_font_t message_font;
+  char *terminal_font_name;
+  grub_gui_color_t title_color;
+  grub_gui_color_t message_color;
+  grub_gui_color_t message_bg_color;
+  struct grub_video_bitmap *desktop_image;
+  grub_gui_color_t desktop_color;
+  grub_gfxmenu_box_t terminal_box;
+  char *title_text;
+  char *progress_message_text;
+  char *theme_path;
+
+  grub_gui_container_t canvas;
+
+  grub_gfxmenu_model_t model;
+};
+
+#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-08-15 04:57:08 +0000
@@ -0,0 +1,49 @@
+/* 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 content_width;
+  int content_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);
+  int (*get_right_pad) (grub_gfxmenu_box_t self);
+  int (*get_bottom_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-15 23:59:38 +0000
@@ -0,0 +1,166 @@
+/* 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>
+#include <grub/gfxmenu_model.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 struct grub_gui_list *grub_gui_list_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_list_ops
+{
+  struct grub_gui_component_ops component_ops;
+  void (*set_view_info) (void *self,
+                         const char *theme_path,
+                         grub_gfxmenu_model_t menu);
+};
+
+struct grub_gui_component
+{
+  struct grub_gui_component_ops *ops;
+};
+
+struct grub_gui_container
+{
+  struct grub_gui_container_ops *ops;
+};
+
+struct grub_gui_list
+{
+  struct grub_gui_list_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);
+grub_gui_component_t grub_gui_list_new (void);
+grub_gui_component_t grub_gui_circular_progress_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);
+
+/* Visit all components.  */
+void grub_gui_iterate_recursively (grub_gui_component_t root,
+                                   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/icon_manager.h'
--- include/grub/icon_manager.h	1970-01-01 00:00:00 +0000
+++ include/grub/icon_manager.h	2008-08-14 16:32:41 +0000
@@ -0,0 +1,41 @@
+/* icon_manager.h - gfxmenu icon manager. */
+/*
+ *  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_ICON_MANAGER_HEADER
+#define GRUB_ICON_MANAGER_HEADER 1
+
+#include <grub/menu.h>
+#include <grub/bitmap.h>
+
+/* Forward declaration of opaque structure handle type.  */
+typedef struct grub_gfxmenu_icon_manager *grub_gfxmenu_icon_manager_t;
+
+grub_gfxmenu_icon_manager_t grub_gfxmenu_icon_manager_new (void);
+void grub_gfxmenu_icon_manager_destroy (grub_gfxmenu_icon_manager_t mgr);
+void grub_gfxmenu_icon_manager_clear_cache (grub_gfxmenu_icon_manager_t mgr);
+void grub_gfxmenu_icon_manager_set_theme_path (grub_gfxmenu_icon_manager_t mgr,
+                                               const char *path);
+void grub_gfxmenu_icon_manager_set_icon_size (grub_gfxmenu_icon_manager_t mgr,
+                                              int width, int height);
+struct grub_video_bitmap *
+grub_gfxmenu_icon_manager_get_icon (grub_gfxmenu_icon_manager_t mgr,
+                                    grub_menu_entry_t entry);
+
+#endif /* GRUB_ICON_MANAGER_HEADER */
+

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

=== added file 'include/grub/trig.h'
--- include/grub/trig.h	1970-01-01 00:00:00 +0000
+++ include/grub/trig.h	2008-08-15 23:59:38 +0000
@@ -0,0 +1,44 @@
+/* trig.h - Trigonometric function support.  */
+/*
+ *  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_TRIG_HEADER
+#define GRUB_TRIG_HEADER 1
+
+#define GRUB_TRIG_ANGLE_MAX 256
+#define GRUB_TRIG_ANGLE_MASK 255
+#define GRUB_TRIG_FRACTION_SCALE 16384
+
+extern short grub_trig_sintab[];
+extern short grub_trig_costab[];
+
+static __inline int
+grub_sin (int x)
+{
+  x &= GRUB_TRIG_ANGLE_MASK;
+  return grub_trig_sintab[x];
+}
+
+static __inline int
+grub_cos (int x)
+{
+  x &= GRUB_TRIG_ANGLE_MASK;
+  return grub_trig_costab[x];
+}
+
+#endif /* ! GRUB_TRIG_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-14 17:46:47 +0000
+++ kern/misc.c	2008-08-16 00:14:43 +0000
@@ -363,6 +363,12 @@
 }
 
 int
+grub_iscntrl (int c)
+{
+  return (c >= 0x00 && c <= 0x1F) || c == 0x7F;
+}
+
+int
 grub_isprint (int c)
 {
   return (c >= ' ' && c <= '~');
@@ -375,6 +381,12 @@
 }
 
 int
+grub_isalnum (int c)
+{
+  return grub_isalpha (c) || grub_isdigit (c);
+}
+
+int
 grub_isdigit (int c)
 {
   return (c >= '0' && c <= '9');
@@ -395,6 +407,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)

=== added file 'lib/trig.c'
--- lib/trig.c	1970-01-01 00:00:00 +0000
+++ lib/trig.c	2008-08-15 23:59:38 +0000
@@ -0,0 +1,83 @@
+/* trig.c - Trigonometric table definitions.  */
+/*
+ *  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/trig.h>
+
+/* These tables were generated with `gentrigtables.py'.  */
+
+short grub_trig_sintab[] =
+{
+  0,402,804,1205,1606,2006,2404,2801,3196,3590,
+  3981,4370,4756,5139,5520,5897,6270,6639,7005,7366,
+  7723,8076,8423,8765,9102,9434,9760,10080,10394,10702,
+  11003,11297,11585,11866,12140,12406,12665,12916,13160,13395,
+  13623,13842,14053,14256,14449,14635,14811,14978,15137,15286,
+  15426,15557,15679,15791,15893,15986,16069,16143,16207,16261,
+  16305,16340,16364,16379,16384,16379,16364,16340,16305,16261,
+  16207,16143,16069,15986,15893,15791,15679,15557,15426,15286,
+  15137,14978,14811,14635,14449,14256,14053,13842,13623,13395,
+  13160,12916,12665,12406,12140,11866,11585,11297,11003,10702,
+  10394,10080,9760,9434,9102,8765,8423,8076,7723,7366,
+  7005,6639,6270,5897,5520,5139,4756,4370,3981,3590,
+  3196,2801,2404,2006,1606,1205,804,402,0,-402,
+  -804,-1205,-1606,-2006,-2404,-2801,-3196,-3590,-3981,-4370,
+  -4756,-5139,-5520,-5897,-6270,-6639,-7005,-7366,-7723,-8076,
+  -8423,-8765,-9102,-9434,-9760,-10080,-10394,-10702,-11003,-11297,
+  -11585,-11866,-12140,-12406,-12665,-12916,-13160,-13395,-13623,-13842,
+  -14053,-14256,-14449,-14635,-14811,-14978,-15137,-15286,-15426,-15557,
+  -15679,-15791,-15893,-15986,-16069,-16143,-16207,-16261,-16305,-16340,
+  -16364,-16379,-16384,-16379,-16364,-16340,-16305,-16261,-16207,-16143,
+  -16069,-15986,-15893,-15791,-15679,-15557,-15426,-15286,-15137,-14978,
+  -14811,-14635,-14449,-14256,-14053,-13842,-13623,-13395,-13160,-12916,
+  -12665,-12406,-12140,-11866,-11585,-11297,-11003,-10702,-10394,-10080,
+  -9760,-9434,-9102,-8765,-8423,-8076,-7723,-7366,-7005,-6639,
+  -6270,-5897,-5520,-5139,-4756,-4370,-3981,-3590,-3196,-2801,
+  -2404,-2006,-1606,-1205,-804,-402
+};
+
+short grub_trig_costab[] =
+{
+  16384,16379,16364,16340,16305,16261,16207,16143,16069,15986,
+  15893,15791,15679,15557,15426,15286,15137,14978,14811,14635,
+  14449,14256,14053,13842,13623,13395,13160,12916,12665,12406,
+  12140,11866,11585,11297,11003,10702,10394,10080,9760,9434,
+  9102,8765,8423,8076,7723,7366,7005,6639,6270,5897,
+  5520,5139,4756,4370,3981,3590,3196,2801,2404,2006,
+  1606,1205,804,402,0,-402,-804,-1205,-1606,-2006,
+  -2404,-2801,-3196,-3590,-3981,-4370,-4756,-5139,-5520,-5897,
+  -6270,-6639,-7005,-7366,-7723,-8076,-8423,-8765,-9102,-9434,
+  -9760,-10080,-10394,-10702,-11003,-11297,-11585,-11866,-12140,-12406,
+  -12665,-12916,-13160,-13395,-13623,-13842,-14053,-14256,-14449,-14635,
+  -14811,-14978,-15137,-15286,-15426,-15557,-15679,-15791,-15893,-15986,
+  -16069,-16143,-16207,-16261,-16305,-16340,-16364,-16379,-16384,-16379,
+  -16364,-16340,-16305,-16261,-16207,-16143,-16069,-15986,-15893,-15791,
+  -15679,-15557,-15426,-15286,-15137,-14978,-14811,-14635,-14449,-14256,
+  -14053,-13842,-13623,-13395,-13160,-12916,-12665,-12406,-12140,-11866,
+  -11585,-11297,-11003,-10702,-10394,-10080,-9760,-9434,-9102,-8765,
+  -8423,-8076,-7723,-7366,-7005,-6639,-6270,-5897,-5520,-5139,
+  -4756,-4370,-3981,-3590,-3196,-2801,-2404,-2006,-1606,-1205,
+  -804,-402,0,402,804,1205,1606,2006,2404,2801,
+  3196,3590,3981,4370,4756,5139,5520,5897,6270,6639,
+  7005,7366,7723,8076,8423,8765,9102,9434,9760,10080,
+  10394,10702,11003,11297,11585,11866,12140,12406,12665,12916,
+  13160,13395,13623,13842,14053,14256,14449,14635,14811,14978,
+  15137,15286,15426,15557,15679,15791,15893,15986,16069,16143,
+  16207,16261,16305,16340,16364,16379
+};
+

=== 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
IyBCYXphYXIgcmV2aXNpb24gYnVuZGxlIHY0CiMKQlpoOTFBWSZTWWE/Vv8C6pR/gH//////////
//////////9i4p7wV7uwcBQD2pAfY0VESlKh74OuPd6Hx7vr7t7oDzeqgB1V767IcO2gL33H3vu3
Z777lfbs296Pfd7N0R23YFAyapoAiaPlsb3jrwhAAczV5otUbCwHNygAAAADc+7x0u93zeDb0bhI
KaA0AAJfd7vh8Oc677c+9PHO3fRsd3tu9MjweXl7z65esz76vbXwACfX28953vBevNXsMAAAAKAJ
wwwYAdAB9AN33G6wA8gDyaCgACgA9AvrAOh6Bo6G+dWD6eCqi+x9nfQ9U7M9999e++p6e2dmlxPo
Hr10Vkd8QthQ+u29FPNNA+LUvvu69t921R77t3r3Pu99y27m9w4jtt2zzuze1S+ul733ve7vbF3v
Ydx45ztLvN71Mz7HvW98c88zW2vddNthJvTVOwuu3q7xnu6u7u52urvu9pFCNPQels9seW+fe14X
W8b17HI9nfCzd99m++3O+jAAHjrKE+TT3IgvpjumddbtowEPrXd9Prvdvve92W2hbUaL46H0A6Ff
dmuJBzNesldUAAPJTQNA6Ouu+ABEHoan0XgD6F3bNfVKo8dA22mnrV3npI+c+Wrb7np314nvdRsD
e3dkPPboufcG++892cBetEtort9H2qe6e9s4C99t6q+4z33Dx9g0APvgTcC3EJXwOeqmh9GjVXPt
nvey++33nni22nN1y2tu3bi7U3Y3cuX3e7y6zx2ACgXw+3vk47gDT3XvW63fb1d9q997nK+m61VT
6UoNoMe9wPXjZmjaZJ0zs9xldmpdfcAAerC+993FIB8919t89qnyyNB5Oq+93BrEegbsyusJC++4
AH0GDvvJAC9vfevUNtR33cH32CzXbNlLY7sc7dGQb7D0oAMbR6ABvfTt3Q5sk9srlqvdg9B6dSFs
OjfPp19hQAGM+vWgBe+N93To09DbDth3Zy2wkUNAANAp4sAAe3vuL2rz0FE7Uuw2Z0daaKodGgAG
+b3n32AD3u3WjoLu89p0HoKNUUaFOgHQB8WAAL73cZaMijRQ0N2ND1py+7PvsOqoAZ3vAAe5mKsN
poDtjo2xQoWygGqorwrp9Dgk+tfd1L71vJzV92fa99a7zeXtmQ3tttvt7wXmSE+w72333sAAAHQA
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
