# Bazaar merge directive format 2 (Bazaar 0.90)
# revision_id: colin@gibibit.com-20080818142101-ko4kkni8u61e510j
# target_branch: file:///home/cdb/grub/repo/trunk-clean/
# testament_sha1: 9ef85bcbc96d0f5c165e2cdd1996f1c1be6a6e53
# timestamp: 2008-08-18 08:47:00 -0700
# source_branch: http://grub.gibibit.com/bzr/trunk-clean
# base_revision_id: colin@gibibit.com-20080818142050-zqm84a9aewr81xtj
# 
# 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-18 12:46:31 +0000
+++ ChangeLog	2008-08-18 14:21:01 +0000
@@ -667,7 +667,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>
 
@@ -800,6 +799,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 17:35:05 +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,27 @@
 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/gui_string_util.c \
+	gfxmenu/named_colors.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 +352,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 +417,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 +433,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-18 12:46:31 +0000
+++ conf/i386-pc.rmk	2008-08-18 14:21:01 +0000
@@ -49,11 +49,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
@@ -253,7 +255,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)
 
@@ -268,7 +270,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-16 17:35:05 +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/gui_string_util.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-16 17:35:05 +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/gui_string_util.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-16 17:35:05 +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/gui_string_util.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-16 17:35:05 +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/gui_string_util.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-16 17:35:05 +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/gui_string_util.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-16 17:35:05 +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/gui_string_util.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-16 17:35:05 +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/gui_string_util.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_string_util.c'
--- gfxmenu/gui_string_util.c	1970-01-01 00:00:00 +0000
+++ gfxmenu/gui_string_util.c	2008-08-16 17:35:05 +0000
@@ -0,0 +1,348 @@
+/* gui_string_util.c - String utilities used by the GUI system.  */
+/*
+ *  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/gui_string_util.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 if (grub_isdigit (*s))
+    {
+      /* 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);
+        }
+    }
+  else 
+    {
+      if (! grub_gui_get_named_color (s, &c))
+        goto fail;
+    }
+
+  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/gui_util.c'
--- gfxmenu/gui_util.c	1970-01-01 00:00:00 +0000
+++ gfxmenu/gui_util.c	2008-08-16 17:35:05 +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/gui_string_util.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-16 17:35:05 +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/gui_string_util.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/named_colors.c'
--- gfxmenu/named_colors.c	1970-01-01 00:00:00 +0000
+++ gfxmenu/named_colors.c	2008-08-16 17:35:05 +0000
@@ -0,0 +1,209 @@
+/* named_colors.c - Named color values.  */
+/*
+ *  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/gui.h>
+#include <grub/gui_string_util.h>
+#include <grub/misc.h>
+
+struct named_color
+{
+  const char *name;
+  grub_gui_color_t color;
+};
+
+/*
+   Named color list generated from the list of SVG color keywords from
+   <http://www.w3.org/TR/css3-color/#svg-color>,
+   processed through the following Perl command:
+   perl -ne 'chomp;split;print "{ \"$_[0]\", RGB_COLOR($_[2]) },\n"'
+ */
+
+#define RGB_COLOR(r,g,b) {.red = r, .green = g, .blue = b, .alpha = 255}
+
+static struct named_color named_colors[] =
+{
+    { "aliceblue", RGB_COLOR(240,248,255) },
+    { "antiquewhite", RGB_COLOR(250,235,215) },
+    { "aqua", RGB_COLOR(0,255,255) },
+    { "aquamarine", RGB_COLOR(127,255,212) },
+    { "azure", RGB_COLOR(240,255,255) },
+    { "beige", RGB_COLOR(245,245,220) },
+    { "bisque", RGB_COLOR(255,228,196) },
+    { "black", RGB_COLOR(0,0,0) },
+    { "blanchedalmond", RGB_COLOR(255,235,205) },
+    { "blue", RGB_COLOR(0,0,255) },
+    { "blueviolet", RGB_COLOR(138,43,226) },
+    { "brown", RGB_COLOR(165,42,42) },
+    { "burlywood", RGB_COLOR(222,184,135) },
+    { "cadetblue", RGB_COLOR(95,158,160) },
+    { "chartreuse", RGB_COLOR(127,255,0) },
+    { "chocolate", RGB_COLOR(210,105,30) },
+    { "coral", RGB_COLOR(255,127,80) },
+    { "cornflowerblue", RGB_COLOR(100,149,237) },
+    { "cornsilk", RGB_COLOR(255,248,220) },
+    { "crimson", RGB_COLOR(220,20,60) },
+    { "cyan", RGB_COLOR(0,255,255) },
+    { "darkblue", RGB_COLOR(0,0,139) },
+    { "darkcyan", RGB_COLOR(0,139,139) },
+    { "darkgoldenrod", RGB_COLOR(184,134,11) },
+    { "darkgray", RGB_COLOR(169,169,169) },
+    { "darkgreen", RGB_COLOR(0,100,0) },
+    { "darkgrey", RGB_COLOR(169,169,169) },
+    { "darkkhaki", RGB_COLOR(189,183,107) },
+    { "darkmagenta", RGB_COLOR(139,0,139) },
+    { "darkolivegreen", RGB_COLOR(85,107,47) },
+    { "darkorange", RGB_COLOR(255,140,0) },
+    { "darkorchid", RGB_COLOR(153,50,204) },
+    { "darkred", RGB_COLOR(139,0,0) },
+    { "darksalmon", RGB_COLOR(233,150,122) },
+    { "darkseagreen", RGB_COLOR(143,188,143) },
+    { "darkslateblue", RGB_COLOR(72,61,139) },
+    { "darkslategray", RGB_COLOR(47,79,79) },
+    { "darkslategrey", RGB_COLOR(47,79,79) },
+    { "darkturquoise", RGB_COLOR(0,206,209) },
+    { "darkviolet", RGB_COLOR(148,0,211) },
+    { "deeppink", RGB_COLOR(255,20,147) },
+    { "deepskyblue", RGB_COLOR(0,191,255) },
+    { "dimgray", RGB_COLOR(105,105,105) },
+    { "dimgrey", RGB_COLOR(105,105,105) },
+    { "dodgerblue", RGB_COLOR(30,144,255) },
+    { "firebrick", RGB_COLOR(178,34,34) },
+    { "floralwhite", RGB_COLOR(255,250,240) },
+    { "forestgreen", RGB_COLOR(34,139,34) },
+    { "fuchsia", RGB_COLOR(255,0,255) },
+    { "gainsboro", RGB_COLOR(220,220,220) },
+    { "ghostwhite", RGB_COLOR(248,248,255) },
+    { "gold", RGB_COLOR(255,215,0) },
+    { "goldenrod", RGB_COLOR(218,165,32) },
+    { "gray", RGB_COLOR(128,128,128) },
+    { "green", RGB_COLOR(0,128,0) },
+    { "greenyellow", RGB_COLOR(173,255,47) },
+    { "grey", RGB_COLOR(128,128,128) },
+    { "honeydew", RGB_COLOR(240,255,240) },
+    { "hotpink", RGB_COLOR(255,105,180) },
+    { "indianred", RGB_COLOR(205,92,92) },
+    { "indigo", RGB_COLOR(75,0,130) },
+    { "ivory", RGB_COLOR(255,255,240) },
+    { "khaki", RGB_COLOR(240,230,140) },
+    { "lavender", RGB_COLOR(230,230,250) },
+    { "lavenderblush", RGB_COLOR(255,240,245) },
+    { "lawngreen", RGB_COLOR(124,252,0) },
+    { "lemonchiffon", RGB_COLOR(255,250,205) },
+    { "lightblue", RGB_COLOR(173,216,230) },
+    { "lightcoral", RGB_COLOR(240,128,128) },
+    { "lightcyan", RGB_COLOR(224,255,255) },
+    { "lightgoldenrodyellow", RGB_COLOR(250,250,210) },
+    { "lightgray", RGB_COLOR(211,211,211) },
+    { "lightgreen", RGB_COLOR(144,238,144) },
+    { "lightgrey", RGB_COLOR(211,211,211) },
+    { "lightpink", RGB_COLOR(255,182,193) },
+    { "lightsalmon", RGB_COLOR(255,160,122) },
+    { "lightseagreen", RGB_COLOR(32,178,170) },
+    { "lightskyblue", RGB_COLOR(135,206,250) },
+    { "lightslategray", RGB_COLOR(119,136,153) },
+    { "lightslategrey", RGB_COLOR(119,136,153) },
+    { "lightsteelblue", RGB_COLOR(176,196,222) },
+    { "lightyellow", RGB_COLOR(255,255,224) },
+    { "lime", RGB_COLOR(0,255,0) },
+    { "limegreen", RGB_COLOR(50,205,50) },
+    { "linen", RGB_COLOR(250,240,230) },
+    { "magenta", RGB_COLOR(255,0,255) },
+    { "maroon", RGB_COLOR(128,0,0) },
+    { "mediumaquamarine", RGB_COLOR(102,205,170) },
+    { "mediumblue", RGB_COLOR(0,0,205) },
+    { "mediumorchid", RGB_COLOR(186,85,211) },
+    { "mediumpurple", RGB_COLOR(147,112,219) },
+    { "mediumseagreen", RGB_COLOR(60,179,113) },
+    { "mediumslateblue", RGB_COLOR(123,104,238) },
+    { "mediumspringgreen", RGB_COLOR(0,250,154) },
+    { "mediumturquoise", RGB_COLOR(72,209,204) },
+    { "mediumvioletred", RGB_COLOR(199,21,133) },
+    { "midnightblue", RGB_COLOR(25,25,112) },
+    { "mintcream", RGB_COLOR(245,255,250) },
+    { "mistyrose", RGB_COLOR(255,228,225) },
+    { "moccasin", RGB_COLOR(255,228,181) },
+    { "navajowhite", RGB_COLOR(255,222,173) },
+    { "navy", RGB_COLOR(0,0,128) },
+    { "oldlace", RGB_COLOR(253,245,230) },
+    { "olive", RGB_COLOR(128,128,0) },
+    { "olivedrab", RGB_COLOR(107,142,35) },
+    { "orange", RGB_COLOR(255,165,0) },
+    { "orangered", RGB_COLOR(255,69,0) },
+    { "orchid", RGB_COLOR(218,112,214) },
+    { "palegoldenrod", RGB_COLOR(238,232,170) },
+    { "palegreen", RGB_COLOR(152,251,152) },
+    { "paleturquoise", RGB_COLOR(175,238,238) },
+    { "palevioletred", RGB_COLOR(219,112,147) },
+    { "papayawhip", RGB_COLOR(255,239,213) },
+    { "peachpuff", RGB_COLOR(255,218,185) },
+    { "peru", RGB_COLOR(205,133,63) },
+    { "pink", RGB_COLOR(255,192,203) },
+    { "plum", RGB_COLOR(221,160,221) },
+    { "powderblue", RGB_COLOR(176,224,230) },
+    { "purple", RGB_COLOR(128,0,128) },
+    { "red", RGB_COLOR(255,0,0) },
+    { "rosybrown", RGB_COLOR(188,143,143) },
+    { "royalblue", RGB_COLOR(65,105,225) },
+    { "saddlebrown", RGB_COLOR(139,69,19) },
+    { "salmon", RGB_COLOR(250,128,114) },
+    { "sandybrown", RGB_COLOR(244,164,96) },
+    { "seagreen", RGB_COLOR(46,139,87) },
+    { "seashell", RGB_COLOR(255,245,238) },
+    { "sienna", RGB_COLOR(160,82,45) },
+    { "silver", RGB_COLOR(192,192,192) },
+    { "skyblue", RGB_COLOR(135,206,235) },
+    { "slateblue", RGB_COLOR(106,90,205) },
+    { "slategray", RGB_COLOR(112,128,144) },
+    { "slategrey", RGB_COLOR(112,128,144) },
+    { "snow", RGB_COLOR(255,250,250) },
+    { "springgreen", RGB_COLOR(0,255,127) },
+    { "steelblue", RGB_COLOR(70,130,180) },
+    { "tan", RGB_COLOR(210,180,140) },
+    { "teal", RGB_COLOR(0,128,128) },
+    { "thistle", RGB_COLOR(216,191,216) },
+    { "tomato", RGB_COLOR(255,99,71) },
+    { "turquoise", RGB_COLOR(64,224,208) },
+    { "violet", RGB_COLOR(238,130,238) },
+    { "wheat", RGB_COLOR(245,222,179) },
+    { "white", RGB_COLOR(255,255,255) },
+    { "whitesmoke", RGB_COLOR(245,245,245) },
+    { "yellow", RGB_COLOR(255,255,0) },
+    { "yellowgreen", RGB_COLOR(154,205,50) },
+    { 0, { 0, 0, 0, 0 } }  /* Terminator.  */
+};
+
+/* Get the color named NAME.  If the color was found, returns 1 and
+   stores the color into *COLOR.  If the color was not found, returns 0 and
+   does not modify *COLOR.  */
+int
+grub_gui_get_named_color (const char *name,
+                          grub_gui_color_t *color)
+{
+  int i;
+  for (i = 0; named_colors[i].name; i++)
+    {
+      if (grub_strcmp (named_colors[i].name, name) == 0)
+        {
+          *color = named_colors[i].color;
+          return 1;
+        }
+    }
+  return 0;
+}

=== added file 'gfxmenu/theme_loader.c'
--- gfxmenu/theme_loader.c	1970-01-01 00:00:00 +0000
+++ gfxmenu/theme_loader.c	2008-08-16 17:35:05 +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/gui_string_util.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-16 17:35:05 +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/gui_string_util.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-16 17:35:05 +0000
@@ -0,0 +1,165 @@
+/* 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 */

=== added file 'include/grub/gui_string_util.h'
--- include/grub/gui_string_util.h	1970-01-01 00:00:00 +0000
+++ include/grub/gui_string_util.h	2008-08-16 17:35:05 +0000
@@ -0,0 +1,39 @@
+/* gui_string_util.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_GUI_STRING_UTIL_HEADER
+#define GRUB_GUI_STRING_UTIL_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);
+
+int grub_gui_get_named_color (const char *name, grub_gui_color_t *color);
+
+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_GUI_STRING_UTIL_HEADER */

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

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

=== 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/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-17 09:30:25 +0000
+++ normal/menu.c	2008-08-18 14:21:01 +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);
 	    }
@@ -484,6 +531,10 @@
 		}
 	      goto refresh;
 	      
+	    case 't':
+	      grub_env_set ("menuviewer", "protomenu");
+	      goto refresh;
+
 	    default:
 	      break;
 	    }
@@ -492,13 +543,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);
   
@@ -507,15 +559,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;
@@ -523,34 +640,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
IyBCYXphYXIgcmV2aXNpb24gYnVuZGxlIHY0CiMKQlpoOTFBWSZTWRugVaMC6iR/gH//////////
//////////9i6H7wV7IMBQD2pANsSokJUtH3vun1j66izXvsRAfPsujQCnofRwfbQF93er33Onvm
72+8U+6O756uhVWY0AkAFGmjR8tje8dvC71wADsNdVWRu47QL73lAAAAAOee7x1bvPvaGd1NwoC1
gKAAEu18dPeLzzXfXz29vHntwV8npus9tPPvvOtvVy9eeTzvtPqAAt9fI6d2+m+b5r67sgAAABQA
vHuOp774A+gFNUHd73nhADIPS7DR0AAkANBJQNNd2DT73uL5r7tVpwa12bsRvQOkLvp7vdfei7Lo
bje4176zXTLuzefW9BXtjrzb0vaaXr6cx7VfY3vbnbro+7t3Xfe7vX3t3e69B27vr0727vnvZu1S
+3cvc7u+70N3esdx6ve9teVDi762Z9zTJ9V333b13vdK1CtNQvt3Fu9fTvnavqtO87PePqHSmmNb
sHpvZ7m3N33z2u8b1o8vs+Ej7hnz5Pe97256uA0OHWVXPe8+n07kgKoN6Z111RAQda330+u++9b2
7sttFsBpPgPR5UDp9PXCvS8d70xkOnvYKUFUevQchtgV8AB6qj0NW+z3dgDoW+2T1rVHjoVLbVe2
6vB4Im+nfN3zu3PTfexd66u5wO9dcj7593rpXppffee9nAmgyWdu9vo9tY77buzwE9tnXtx2++++
I9tFAPrvgR4HbiPZp8De+DofRo3Zte93tb3d9nPa7um22nOZzY3XuG7aZa3jnXu93l1nnvcAFA+D
75HwgCnfHXTvYHfd9nvXc1SKinSgDuaqXuwPeFmBb7c7Zexo+2B2MLr7voAAYfPeb7NAPje6154H
ez0NBztQusLW82PTResp2w0dd8wA69Bw772loA8vnnaHbUXsDvYL2ByBR0006N7BdgAD0+No+gAa
3m27oTZbds6ddCujoO7Docgo8ffd2wegAd1w9fQA+x32XD1Qtjuw2w7rXXXDd3NG2AAAU+MHoKHt
mfbvhoNG03rfYZEh6aCh6AB0Dy8fIAN77daAGvOZyHro0HRSQDoBoBvpwADu3fbx1kzY0aKLYbY0
PWXl92fEGgA30cAA9s8VWLuwobsB0bYkddKA6lQ8FdPtjj1my2fbvay7bPOr7s8HqxZSfI7vdttv
t94LzLsM+w972+roAAAaAAAEQ77PeaqskmItapalJtqLeurqdWUbtu1m7Qt06HbYgTW0bXc5ybbY
JUsuu6LCy9uVe1eXDXcDdjbal7b22td33B497UVdvsuXJtT2rubu7sNjZ21bm4bm3NbaUO1xrmpT
kzrLmRzu3docVjeT69voAApQC2BxPfXSXe7drAvcYWvdZiAar3Z2vOaQ23GDlcmcFPe7fQAoKAA9
K767lAo+zI7wTXru5y4BcuBgTdt1VrwB9FUACmT2PTgejj2HkNx9N5g3feeoAAA6p5F2WlYCdLUg
SCvoGdwAAPoAPeofc+8sPoN8z7q6+zpzyGj0poLfVW3xzJdMdn1uN27sN72XLbnb0CFdteJ63efP
gAAAAA+gU+31Xx3azDQ33267cafZ942+vvtLTs333eRbNbojvZWuU2BhKcR007o74vLZIve6qvef
X1fAAASX2bWFsV9Puy9z6UAoAA7svYADdjmiur7uld2+8cj3t0yna+7o7GWzA8unC7UddQOQKkkl
XQZ2EkQACQooB1uA2tjRQlbNQc2KbWGg4UTurFUlGNlSr56l7j1PXbuzZVAKoXW06cgklIhQXNbK
GnOiEzAl2Zceurtvd0cy7ijJCpoYu9u94bcwmxa2gN2oZxoq4sR2cNAdKUvezkMmnu3VGzu0BoCm
rsEuazHboNAKCgDRy2G6u19mnmwy9GRK6ZoGg2wLbNtHIwnSrMK7u25112LHWx1rduy5bgKGrLDV
tqdYDobp2MFJQNhw+9wB01e51O1NzY2tguqu7qohFRABI++99rPexS93dd533u7z3unvL7dvvr3v
vdx98qD5rVoUMJGMOy2rrnG0baw3Ddd3txJXob0Hd683bp0otN3G1pCbZX3t0282O6OTkSbNmPr2
549Xru3RkCj3TKoN3rk3gLd2wkBnt247u02UFnONttlsyO51OlsttmkRmKWYppQZN3dOws4KuKCe
m9jJCnga7kgZrT33fQqgAvZ8840+Xb733sxXQAVUGsEoQABCEBAIBCYEyNJpT9NTaEaFPRqemozU
0yGQHojI0bUASmgIRBEETCTAlT8Qp7IJNqGnqDeqNNAaA0HqAD1AAANASCRCEBEaBMgm0CaBDTCm
aGknptNU8ifompo9T2qek09NQ2U2UfqTTyIEKREEJoCaACGQE0aGhPJPU0waTE1T1NmUxBlP0aRi
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
