# Bazaar merge directive format 2 (Bazaar 0.90)
# revision_id: colin@gibibit.com-20080803040022-bxhp6libdkx40muv
# target_branch: ../../repo/trunk-clean
# testament_sha1: a9261a62ae1b371e0a9b27a0f1f55c4c880aaf1e
# timestamp: 2008-08-03 07:26:43 -0700
# source_branch: http://grub.gibibit.com/bzr/trunk-clean
# base_revision_id: colin@gibibit.com-20080803034658-kcm160bcz0e1rt9f
# 
# 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-02 22:24:34 +0000
+++ ChangeLog	2008-08-03 03:57:46 +0000
@@ -64,7 +64,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>
 
@@ -197,6 +196,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-07-24 13:56:30 +0000
+++ Makefile.in	2008-07-28 02:00:30 +0000
@@ -89,6 +89,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@
@@ -136,13 +169,16 @@
 	  || (rm -f $@; exit 1)
 
 command.lst: $(COMMANDFILES)
-	cat $^ /dev/null | sort > $@
+	$(INFO_GEN_FINAL_COMMAND_LIST)
+	$(V_PREFIX)cat $^ /dev/null | sort > $@
 
 fs.lst: $(FSFILES)
-	cat $^ /dev/null | sort > $@
+	$(INFO_GEN_FINAL_FS_LIST)
+	$(V_PREFIX)cat $^ /dev/null | sort > $@
 
 partmap.lst: $(PARTMAPFILES)
-	cat $^ /dev/null | sort > $@
+	$(INFO_GEN_FINAL_PARTMAP_LIST)
+	$(V_PREFIX)cat $^ /dev/null | sort > $@
 
 ifeq (, $(UNIFONT_HEX))
 else

=== modified file 'commands/i386/pc/vbeinfo.c'
--- commands/i386/pc/vbeinfo.c	2007-07-21 22:32:33 +0000
+++ commands/i386/pc/vbeinfo.c	2008-07-09 14:39:39 +0000
@@ -18,6 +18,7 @@
  */
 
 #include <grub/normal.h>
+#include <grub/term.h>
 #include <grub/dl.h>
 #include <grub/arg.h>
 #include <grub/env.h>
@@ -48,12 +49,22 @@
   grub_err_t err;
   char *modevar;
 
-  grub_printf ("List of compatible video modes:\n");
-
   err = grub_vbe_probe (&controller_info);
   if (err != GRUB_ERR_NONE)
     return err;
 
+  int row = 0;
+
+  grub_printf ("VBE info:   version: %d.%d  OEM software rev: %d.%d\n",
+               controller_info.version >> 8, 
+               controller_info.version & 0xFF,
+               controller_info.oem_software_rev >> 8,
+               controller_info.oem_software_rev & 0xFF);
+  row++;
+  grub_printf ("            total memory: %d KiB\n",
+               (controller_info.total_memory << 16) / 1024);
+  row++;
+
   /* Because the information on video modes is stored in a temporary place,
      it is better to copy it to somewhere safe.  */
   p = video_mode_list = real2pm (controller_info.video_mode_ptr);
@@ -67,6 +78,10 @@
 
   grub_memcpy (saved_video_mode_list, video_mode_list, video_mode_list_size);
   
+  grub_printf ("List of compatible video modes:\n");
+  grub_printf ("Legend: P=Packed pixel, D=Direct color, "
+               "mask/pos=R/G/B/reserved\n"); 
+
   /* Walk through all video modes listed.  */
   for (p = saved_video_mode_list; *p != 0xFFFF; p++)
     {
@@ -103,10 +118,10 @@
       switch (mode_info_tmp.memory_model)
 	{
 	case 0x04:
-	  memory_model = "Packed Pixel";
+	  memory_model = "Packed";
 	  break;
 	case 0x06:
-	  memory_model = "Direct Color";
+	  memory_model = "Direct";
 	  break;
 
 	default:
@@ -116,12 +131,31 @@
       if (! memory_model)
 	continue;
 
-      grub_printf ("0x%03x: %d x %d x %d bpp (%s)\n",
-		   mode,
+      grub_printf ("0x%03x: %4dx%4dx%2d %s", 
+                   mode,
                    mode_info_tmp.x_resolution,
                    mode_info_tmp.y_resolution,
                    mode_info_tmp.bits_per_pixel,
-		   memory_model);
+                   memory_model);
+      if (memory_model[0] == 'D')
+        grub_printf (" mask: %d/%d/%d/%d  pos: %d/%d/%d/%d",
+                     mode_info_tmp.red_mask_size,
+                     mode_info_tmp.green_mask_size,
+                     mode_info_tmp.blue_mask_size,
+                     mode_info_tmp.rsvd_mask_size,
+                     mode_info_tmp.red_field_position,
+                     mode_info_tmp.green_field_position,
+                     mode_info_tmp.blue_field_position,
+                     mode_info_tmp.rsvd_field_position);
+      grub_printf ("\n");
+
+      if (++row >= 25)
+        {
+          grub_printf ("-- More --");
+          grub_getkey ();
+          grub_printf ("\r          \r");
+          row = 0;
+        }
     }
 
   grub_free (saved_video_mode_list);

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

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

=== modified file 'conf/common.rmk'
--- conf/common.rmk	2008-08-01 03:06:55 +0000
+++ conf/common.rmk	2008-08-03 04:00:22 +0000
@@ -274,6 +274,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.
@@ -281,6 +282,16 @@
 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/widget-box.c \
+	gfxmenu/stringutil.c
+gfxmenu_mod_CFLAGS = $(COMMON_CFLAGS)
+gfxmenu_mod_LDFLAGS = $(COMMON_LDFLAGS)
+
 # For boot.mod.
 boot_mod_SOURCES = commands/boot.c
 boot_mod_CFLAGS = $(COMMON_CFLAGS)
@@ -317,7 +328,7 @@
 help_mod_LDFLAGS = $(COMMON_LDFLAGS)
 
 # For font.mod.
-font_mod_SOURCES = font/manager.c
+font_mod_SOURCES = font/font_cmd.c font/font.c font/loader.c
 font_mod_CFLAGS = $(COMMON_CFLAGS)
 font_mod_LDFLAGS = $(COMMON_LDFLAGS)
 

=== modified file 'conf/i386-coreboot.rmk'
--- conf/i386-coreboot.rmk	2008-08-02 11:17:44 +0000
+++ conf/i386-coreboot.rmk	2008-08-03 03:57:46 +0000
@@ -16,6 +16,7 @@
 	kern/main.c kern/device.c \
 	kern/disk.c kern/dl.c kern/file.c kern/fs.c kern/err.c \
 	kern/misc.c kern/mm.c kern/loader.c kern/rescue.c kern/term.c \
+	kern/time.c \
 	kern/i386/dl.c kern/parser.c kern/partition.c \
 	kern/env.c \
 	term/i386/pc/console.c \

=== modified file 'conf/i386-efi.rmk'
--- conf/i386-efi.rmk	2008-07-27 12:51:30 +0000
+++ conf/i386-efi.rmk	2008-07-28 16:26:46 +0000
@@ -84,7 +84,10 @@
 	kern/misc.c kern/mm.c kern/loader.c kern/rescue.c kern/term.c \
 	kern/i386/dl.c kern/i386/efi/init.c kern/parser.c kern/partition.c \
 	kern/env.c symlist.c kern/efi/efi.c kern/efi/init.c kern/efi/mm.c \
-	term/efi/console.c disk/efi/efidisk.c
+	term/efi/console.c disk/efi/efidisk.c \
+	kern/i386/tsc.c \
+	kern/generic/rtc_get_time_ms.c \
+	kern/generic/millisleep.c
 kernel_mod_HEADERS = arg.h boot.h cache.h device.h disk.h dl.h elf.h elfload.h \
 	env.h err.h file.h fs.h kernel.h loader.h misc.h mm.h net.h parser.h \
 	partition.h pc_partition.h rescue.h symbol.h term.h time.h types.h \

=== modified file 'conf/i386-ieee1275.rmk'
--- conf/i386-ieee1275.rmk	2008-08-02 11:17:44 +0000
+++ conf/i386-ieee1275.rmk	2008-08-03 03:57:46 +0000
@@ -19,6 +19,7 @@
 	kern/misc.c kern/mm.c kern/loader.c kern/rescue.c kern/term.c \
 	kern/i386/dl.c kern/parser.c kern/partition.c \
 	kern/env.c \
+	kern/generic/millisleep.c \
 	kern/ieee1275/ieee1275.c \
 	term/ieee1275/ofconsole.c term/i386/pc/at_keyboard.c \
 	disk/ieee1275/ofdisk.c \

=== modified file 'conf/i386-pc.rmk'
--- conf/i386-pc.rmk	2008-07-27 12:51:30 +0000
+++ conf/i386-pc.rmk	2008-07-28 17:02:56 +0000
@@ -42,13 +42,19 @@
 kernel_img_SOURCES = kern/i386/pc/startup.S kern/main.c kern/device.c \
 	kern/disk.c kern/dl.c kern/file.c kern/fs.c kern/err.c \
 	kern/misc.c kern/mm.c kern/loader.c kern/rescue.c kern/term.c \
+	kern/time.c \
 	kern/i386/dl.c kern/i386/pc/init.c kern/parser.c kern/partition.c \
+	kern/i386/tsc.c \
+	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
 kernel_img_CFLAGS = $(COMMON_CFLAGS)
@@ -246,7 +252,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)
 
@@ -261,7 +267,10 @@
 videotest_mod_LDFLAGS = $(COMMON_LDFLAGS)
 
 # For bitmap.mod
-bitmap_mod_SOURCES = video/bitmap.c
+bitmap_mod_SOURCES = video/bitmap.c \
+                     video/bitmap_scale_nn.c \
+                     video/bitmap_scale_bilinear.c \
+                     video/bitmap_scale.c
 bitmap_mod_CFLAGS = $(COMMON_CFLAGS)
 bitmap_mod_LDFLAGS = $(COMMON_LDFLAGS)
 

=== modified file 'conf/powerpc-ieee1275.rmk'
--- conf/powerpc-ieee1275.rmk	2008-08-02 11:17:44 +0000
+++ conf/powerpc-ieee1275.rmk	2008-08-03 03:57:46 +0000
@@ -85,6 +85,7 @@
 	kern/ieee1275/init.c term/ieee1275/ofconsole.c 		\
 	kern/ieee1275/openfw.c disk/ieee1275/ofdisk.c 		\
 	kern/parser.c kern/partition.c kern/env.c kern/powerpc/dl.c 	\
+	kern/generic/millisleep.c                                       \
 	symlist.c kern/powerpc/cache.S
 kernel_elf_HEADERS = grub/powerpc/ieee1275/ieee1275.h
 kernel_elf_CFLAGS = $(COMMON_CFLAGS)

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

=== modified file 'conf/x86_64-efi.rmk'
--- conf/x86_64-efi.rmk	2008-07-27 12:51:30 +0000
+++ conf/x86_64-efi.rmk	2008-07-28 16:23:46 +0000
@@ -87,6 +87,7 @@
 	kern/misc.c kern/mm.c kern/loader.c kern/rescue.c kern/term.c \
 	kern/x86_64/dl.c kern/i386/efi/init.c kern/parser.c kern/partition.c \
 	kern/env.c symlist.c kern/efi/efi.c kern/efi/init.c kern/efi/mm.c \
+	kern/generic/millisleep.c kern/generic/rtc_get_time_ms.c \
 	term/efi/console.c disk/efi/efidisk.c
 kernel_mod_HEADERS = arg.h boot.h cache.h device.h disk.h dl.h elf.h elfload.h \
 	env.h err.h file.h fs.h kernel.h loader.h misc.h mm.h net.h parser.h \

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

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

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

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

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

=== added directory 'gfxmenu'
=== added file 'gfxmenu/gfxmenu.c'
--- gfxmenu/gfxmenu.c	1970-01-01 00:00:00 +0000
+++ gfxmenu/gfxmenu.c	2008-07-27 00:06:20 +0000
@@ -0,0 +1,221 @@
+/* gfxmenu.c - Graphical menu interface controller. */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2008  Free Software Foundation, Inc.
+ *
+ *  GRUB is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/types.h>
+#include <grub/misc.h>
+#include <grub/mm.h>
+#include <grub/err.h>
+#include <grub/dl.h>
+#include <grub/normal.h>
+#include <grub/video.h>
+#include <grub/gfxterm.h>
+#include <grub/bitmap.h>
+#include <grub/bitmap_scale.h>
+#include <grub/term.h>
+#include <grub/gfxwidgets.h>
+#include <grub/menu.h>
+#include <grub/menu_viewer.h>
+#include <grub/gfxmenu_model.h>
+#include <grub/gfxmenu_view.h>
+
+static void switch_to_text_menu (void)
+{
+  grub_env_set ("menuviewer", "terminal");
+}
+
+static void
+process_key_press (int c,
+                   grub_gfxmenu_model_t model,
+                   grub_gfxmenu_view_t view,
+                   int nested,
+                   int *should_exit)
+{
+  /* When a key is pressed, stop the timeout.  */
+  grub_gfxmenu_model_clear_timeout (model);
+
+  if (c == 'j' || c == GRUB_TERM_DOWN)
+    {
+      int i = grub_gfxmenu_model_get_selected_index (model);
+      int num_items = grub_gfxmenu_model_get_num_entries (model);
+      if (i < num_items - 1)
+        {
+          i++;
+          grub_gfxmenu_model_set_selected_index (model, i);
+        }
+    }
+  else if (c == 'k' || c == GRUB_TERM_UP)
+    {
+      int i = grub_gfxmenu_model_get_selected_index (model);
+      if (i > 0)
+        {
+          i--;
+          grub_gfxmenu_model_set_selected_index (model, i);
+        }
+    }
+  else if (c == '\r' || c == '\n' || c == GRUB_TERM_RIGHT)
+    {
+      int selected = grub_gfxmenu_model_get_selected_index (model);
+      int num_entries = grub_gfxmenu_model_get_num_entries (model);
+      if (selected >= 0 && selected < num_entries)
+        {
+          grub_menu_entry_t entry =
+            grub_gfxmenu_model_get_entry (model, selected);
+          grub_gfxmenu_view_execute_entry (view, entry);
+        }
+    }
+  else if (c == 'c')
+    {
+      grub_gfxmenu_view_run_terminal (view);
+      if (grub_errno != GRUB_ERR_NONE)
+        *should_exit = 1;
+    }
+  else if (c == 't')
+    {
+      /* The write hook for 'menuviewer' will cause
+       * grub_menu_viewer_should_return to return nonzero. */
+      switch_to_text_menu ();
+      *should_exit = 1;
+    }
+  else if (c == '1')
+    {
+      grub_gfxmenu_view_load_theme (view,
+                                    "/boot/grub/themes/proto/theme.txt");
+    }
+  else if (c == '2')
+    {
+      grub_gfxmenu_view_load_theme (view,
+                                    "/boot/grub/themes/winter/theme.txt");
+    }
+  else if (nested && c == GRUB_TERM_ESC)
+    {
+      *should_exit = 1;
+    }
+}
+
+static void
+handle_key_events (grub_gfxmenu_model_t model,
+                   grub_gfxmenu_view_t view,
+                   int nested,
+                   int *should_exit)
+{
+  while (!*should_exit && grub_checkkey () != -1)
+    {
+      int key = grub_getkey ();
+      int c = GRUB_TERM_ASCII_CHAR (key);
+      process_key_press (c, model, view, nested, should_exit);
+    }
+}
+
+static grub_err_t
+show_menu (grub_menu_t menu, int nested)
+{
+  grub_gfxmenu_model_t model;
+
+  model = grub_gfxmenu_model_new (menu);
+  if (! model)
+    {
+      grub_print_error ();
+      grub_printf ("Initializing menu data for graphical menu failed;\n"
+                   "falling back to terminal based menu.\n");
+      grub_wait_after_message ();
+      switch_to_text_menu ();
+      return grub_errno;
+    }
+
+  grub_gfxmenu_view_t view;
+
+  /* Create the view.  */
+  const char *theme_path = grub_env_get ("theme");
+  if (! theme_path)
+    theme_path = "/boot/grub/themes/proto/theme.txt";
+
+  view = grub_gfxmenu_view_new (theme_path, model);
+  if (! view)
+    {
+      grub_print_error ();
+      grub_printf ("Starting graphical menu failed;\n"
+                   "falling back to terminal based menu.\n");
+      grub_wait_after_message ();
+      grub_gfxmenu_model_destroy (model);
+      switch_to_text_menu ();
+      return grub_errno;
+    }
+
+  /* Initially select the default menu entry.  */
+  int default_index = grub_menu_get_default_entry_index (menu);
+  grub_gfxmenu_model_set_selected_index (model, default_index);
+
+  /* Start the timer to execute the default entry.  */
+  grub_gfxmenu_model_set_timeout (model);
+
+  /* Main event loop.  */
+  int exit_requested = 0;
+  while (!exit_requested && !grub_menu_viewer_should_return ())
+    {
+      if (grub_gfxmenu_model_timeout_expired (model))
+        {
+          grub_gfxmenu_model_clear_timeout (model);
+          int i = grub_gfxmenu_model_get_selected_index (model);
+          grub_menu_entry_t e = grub_gfxmenu_model_get_entry (model, i);
+          grub_gfxmenu_view_execute_with_fallback (view, e);
+          continue;
+        }
+
+      grub_gfxmenu_view_draw (view);
+      grub_video_swap_buffers ();
+      handle_key_events (model, view, nested, &exit_requested);
+    }
+
+  grub_gfxmenu_view_destroy (view);
+  grub_gfxmenu_model_destroy (model);
+
+  return grub_errno;
+}
+
+static grub_err_t
+grub_cmd_gfxmenu (struct grub_arg_list *state __attribute__ ((unused)),
+                  int argc __attribute__ ((unused)),
+                  char **args __attribute__ ((unused)))
+{
+  grub_menu_t menu = grub_env_get_data_slot ("menu");
+  if (!menu)
+    return grub_error (GRUB_ERR_MENU, "No menu context");
+
+  return show_menu (menu, 1);
+}
+
+static struct grub_menu_viewer menu_viewer =
+{
+  .name = "gfxmenu",
+  .show_menu = show_menu
+};
+
+GRUB_MOD_INIT (gfxmenu)
+{
+  (void) mod;                   /* To stop warning. */
+  grub_menu_viewer_register (&menu_viewer);
+  grub_register_command ("gfxmenu",
+                         grub_cmd_gfxmenu, GRUB_COMMAND_FLAG_BOTH,
+                         "gfxmenu", "Show graphical menu interface", 0);
+}
+
+GRUB_MOD_FINI (gfxmenu)
+{
+  grub_unregister_command ("gfxmenu");
+}

=== added file 'gfxmenu/model.c'
--- gfxmenu/model.c	1970-01-01 00:00:00 +0000
+++ gfxmenu/model.c	2008-07-26 23:37:04 +0000
@@ -0,0 +1,192 @@
+/* model.c - Graphical menu interface MVC model. */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2008  Free Software Foundation, Inc.
+ *
+ *  GRUB is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/types.h>
+#include <grub/misc.h>
+#include <grub/mm.h>
+#include <grub/err.h>
+#include <grub/normal.h>
+#include <grub/menu.h>
+#include <grub/time.h>
+#include <grub/gfxmenu_model.h>
+
+/* Model type definition.  */
+struct grub_gfxmenu_model
+{
+  grub_menu_t menu;
+  int num_entries;
+  grub_menu_entry_t *entries;
+  int selected_entry_index;
+  int timeout_set;
+  grub_uint64_t timeout_start;
+  grub_uint64_t timeout_at;
+};
+
+
+grub_gfxmenu_model_t
+grub_gfxmenu_model_new (grub_menu_t menu)
+{
+  grub_gfxmenu_model_t model;
+
+  model = grub_malloc (sizeof (*model));
+  if (! model)
+    return 0;
+
+  model->menu = menu;
+  model->num_entries = menu->size;
+  model->entries = 0;
+  model->selected_entry_index = 0;
+  model->timeout_set = 0;
+  model->timeout_at = 0;
+  if (model->num_entries > 0)
+    {
+      model->entries = grub_malloc (model->num_entries 
+                                    * sizeof (*model->entries));
+      if (! model->entries)
+        goto fail_and_free;
+
+      int i;
+      grub_menu_entry_t cur;
+      for (i = 0, cur = menu->entry_list;
+           i < model->num_entries;
+           i++, cur = cur->next)
+        {
+          model->entries[i] = cur;
+        }
+    }
+
+  return model;
+
+fail_and_free:
+  grub_free (model->entries);
+  grub_free (model);
+  return 0;
+}
+
+void
+grub_gfxmenu_model_destroy (grub_gfxmenu_model_t model)
+{
+  if (! model)
+    return;
+
+  grub_free (model->entries);
+  model->entries = 0;
+
+  grub_free (model);
+}
+
+grub_menu_t
+grub_gfxmenu_model_get_menu (grub_gfxmenu_model_t model)
+{
+  return model->menu;
+}
+
+void
+grub_gfxmenu_model_set_timeout (grub_gfxmenu_model_t model)
+{
+  int timeout_sec = grub_menu_get_timeout ();
+  if (timeout_sec >= 0)
+    {
+      model->timeout_start = grub_get_time_ms ();
+      model->timeout_at = model->timeout_start + timeout_sec * 1000;
+      model->timeout_set = 1;
+    }
+  else
+    {
+      model->timeout_set = 0;
+    }
+}
+
+void
+grub_gfxmenu_model_clear_timeout (grub_gfxmenu_model_t model)
+{
+  model->timeout_set = 0;
+  grub_menu_set_timeout (-1);
+}
+
+int
+grub_gfxmenu_model_get_timeout_ms (grub_gfxmenu_model_t model)
+{
+  if (!model->timeout_set)
+    return -1;
+
+  return model->timeout_at - model->timeout_start;
+}
+
+int
+grub_gfxmenu_model_get_timeout_remaining_ms (grub_gfxmenu_model_t model)
+{
+  if (!model->timeout_set)
+    return -1;
+
+  return model->timeout_at - grub_get_time_ms ();
+}
+
+int
+grub_gfxmenu_model_timeout_expired (grub_gfxmenu_model_t model)
+{
+  if (model->timeout_set 
+      && grub_get_time_ms () >= model->timeout_at)
+    return 1;
+
+  return 0;
+}
+
+int 
+grub_gfxmenu_model_get_num_entries (grub_gfxmenu_model_t model)
+{
+  return model->num_entries;
+}
+
+int 
+grub_gfxmenu_model_get_selected_index (grub_gfxmenu_model_t model)
+{
+  return model->selected_entry_index;
+}
+
+void 
+grub_gfxmenu_model_set_selected_index (grub_gfxmenu_model_t model, int index)
+{
+  model->selected_entry_index = index;
+}
+
+const char *
+grub_gfxmenu_model_get_entry_title (grub_gfxmenu_model_t model, int index)
+{
+  if (index < 0 || index >= model->num_entries)
+    {
+      grub_error (GRUB_ERR_OUT_OF_RANGE, "invalid menu index");
+      return 0;
+    }
+
+  return model->entries[index]->title;
+}
+
+grub_menu_entry_t
+grub_gfxmenu_model_get_entry (grub_gfxmenu_model_t model, int index)
+{
+  if (index < 0 || index >= model->num_entries)
+    {
+      grub_error (GRUB_ERR_OUT_OF_RANGE, "invalid menu index");
+      return 0;
+    }
+
+  return model->entries[index];
+}
+

=== added file 'gfxmenu/stringutil.c'
--- gfxmenu/stringutil.c	1970-01-01 00:00:00 +0000
+++ gfxmenu/stringutil.c	2008-07-26 00:44:48 +0000
@@ -0,0 +1,196 @@
+/* stringutil.c - String utilities. */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2008  Free Software Foundation, Inc.
+ *
+ *  GRUB is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/stringutil.h>
+#include <grub/types.h>
+#include <grub/misc.h>
+#include <grub/mm.h>
+
+/* Create a new NUL-terminated string on the heap as a substring of BUF.
+   The range of buf included is the half-open interval [START,END).
+   The index START is inclusive, END is exclusive.  */
+char *
+grub_new_substring (const char *buf,
+                    grub_size_t start, grub_size_t end)
+{
+  char *s = grub_malloc (end - start + 1);
+  if (! s)
+    return 0;
+  grub_memcpy (s, buf + start, end - start);
+  s[end - start] = '\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);
+}

=== added file 'gfxmenu/view.c'
--- gfxmenu/view.c	1970-01-01 00:00:00 +0000
+++ gfxmenu/view.c	2008-08-03 02:14:40 +0000
@@ -0,0 +1,1096 @@
+/* view.c - Graphical menu interface MVC view. */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2008  Free Software Foundation, Inc.
+ *
+ *  GRUB is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/types.h>
+#include <grub/file.h>
+#include <grub/misc.h>
+#include <grub/mm.h>
+#include <grub/err.h>
+#include <grub/dl.h>
+#include <grub/normal.h>
+#include <grub/video.h>
+#include <grub/stringutil.h>
+#include <grub/gfxterm.h>
+#include <grub/bitmap.h>
+#include <grub/bitmap_scale.h>
+#include <grub/term.h>
+#include <grub/gfxwidgets.h>
+#include <grub/time.h>
+#include <grub/menu.h>
+#include <grub/menu_viewer.h>
+#include <grub/gfxmenu_view.h>
+
+/* Specifies a time (in ms) to delay so the user can read the message if a
+   default boot attempt fails and an attempt is made to fall back on
+   another entry.  */
+#define FALLBACK_MESSAGE_DELAY 2000
+
+typedef struct grub_gfxmenu_view_icon
+{
+  const char *class_name;
+  struct grub_video_bitmap *bitmap;
+  struct grub_gfxmenu_view_icon *next;
+} *grub_gfxmenu_view_icon_t;
+
+/* Definition of the private representation of the view.  */
+struct grub_gfxmenu_view
+{
+  grub_video_rect_t screen;
+
+  int icon_width;
+  int icon_height;
+  int item_height;
+  int item_padding;
+  int item_icon_space;
+  int item_spacing;
+  grub_font_t title_font;
+  grub_font_t item_font;
+  grub_font_t selected_item_font;
+  grub_font_t status_font;
+  char *terminal_font_name;
+  grub_video_color_t title_color;
+  grub_video_color_t item_color;
+  grub_video_color_t selected_item_color;
+  grub_video_color_t status_color;
+  grub_video_color_t status_bg_color;
+  grub_video_color_t progress_bar_border_color;
+  grub_video_color_t progress_bar_fg_color;
+  grub_video_color_t progress_bar_bg_color;
+  struct grub_video_bitmap *desktop_image;
+  grub_video_color_t desktop_color;
+  grub_gfxmenu_box_t menu_box;
+  grub_gfxmenu_box_t selected_item_box;
+  grub_gfxmenu_box_t terminal_box;
+  char *title_text;
+  char *progress_message_text;
+  char *theme_path;
+  /* Icon cache: linked list w/ dummy head node.  */
+  struct grub_gfxmenu_view_icon icon_cache;
+
+  grub_gfxmenu_model_t model;
+};
+
+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_video_color_t default_fg_color;
+  grub_video_color_t default_bg_color;
+
+  default_font = grub_font_get ("Helvetica 12");
+  default_fg_color = grub_video_map_rgb (0, 0, 0);
+  default_bg_color = grub_video_map_rgb (255, 255, 255);
+
+  view->icon_width = 32;
+  view->icon_height = 32;
+  view->item_height = 42;
+  view->item_padding = 14;
+  view->item_icon_space = 4;
+  view->item_spacing = 16;
+  view->title_font = default_font;
+  view->item_font = default_font;
+  view->selected_item_font = default_font;
+  view->status_font = default_font;
+  view->terminal_font_name = grub_strdup ("Fixed 10");
+  view->title_color = default_fg_color;
+  view->item_color = default_fg_color;
+  view->selected_item_color = default_fg_color;
+  view->status_color = default_bg_color;
+  view->status_bg_color = default_fg_color;
+  view->progress_bar_border_color = default_fg_color;
+  view->progress_bar_fg_color = grub_video_map_rgb (160, 160, 160);
+  view->progress_bar_bg_color = default_bg_color;
+  view->desktop_image = 0;
+  view->desktop_color = default_bg_color;
+  view->menu_box = 0;
+  view->selected_item_box = 0;
+  view->terminal_box = 0;
+  view->title_text = grub_strdup ("GRUB Boot Menu");
+  view->progress_message_text = 0;
+  view->theme_path = 0;
+  view->icon_cache.class_name = 0;
+  view->icon_cache.bitmap = 0;
+  view->icon_cache.next = 0;
+
+  view->model = model;
+
+  if (! grub_gfxmenu_view_load_theme (view, theme_path))
+    {
+      grub_gfxmenu_view_destroy (view);
+      return 0;
+    }
+
+  init_terminal (view);
+
+  return view;
+}
+
+static void
+free_icon_cache (grub_gfxmenu_view_t view)
+{
+  grub_gfxmenu_view_icon_t cur;
+  grub_gfxmenu_view_icon_t next;
+  for (cur = view->icon_cache.next; cur; cur = next)
+    {
+      next = cur->next;
+      grub_free (cur);
+    }
+  view->icon_cache.next = 0;
+}
+
+/* Destroy the view object.  All used memory is freed.  */
+void
+grub_gfxmenu_view_destroy (grub_gfxmenu_view_t view)
+{
+  grub_video_bitmap_destroy (view->desktop_image);
+  if (view->menu_box)
+    view->menu_box->destroy (view->menu_box);
+  if (view->selected_item_box)
+    view->selected_item_box->destroy (view->selected_item_box);
+  if (view->terminal_box)
+    view->terminal_box->destroy (view->terminal_box);
+  grub_free (view->terminal_font_name);
+  grub_free (view->title_text);
+  grub_free (view->progress_message_text);
+  grub_free (view->theme_path);
+  free_icon_cache (view);
+  grub_free (view);
+
+  set_text_mode ();
+  destroy_terminal ();
+}
+
+/* Sets MESSAGE as the progress message for the view.  The string MESSAGE
+   must be heap-allocated and will be owned by VIEW.  MESSAGE can be 0, in
+   which case no message is displayed.  */
+static void
+set_progress_message (grub_gfxmenu_view_t view, char *message)
+{
+  grub_free (view->progress_message_text);
+  view->progress_message_text = message;
+}
+
+/* Parse a color string of the form "r, g, b".
+   Whitespace is insignificant.  */
+static grub_video_color_t
+parse_color (const char *s)
+{
+  int red = grub_strtoul (s, 0, 0);
+  if ((s = grub_strchr (s, ',')) == 0)
+    goto fail;
+  s++;
+  int green = grub_strtoul (s, 0, 0);
+  if ((s = grub_strchr (s, ',')) == 0)
+    goto fail;
+  s++;
+  int blue = grub_strtoul (s, 0, 0);
+  int alpha;
+  if ((s = grub_strchr (s, ',')) == 0)
+    alpha = 255;
+  else
+    {
+      s++;
+      alpha = grub_strtoul (s, 0, 0);
+    }
+  grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
+  return grub_video_map_rgba (red, green, blue, alpha);
+
+fail:
+  return 0;
+}
+
+/* Construct a new box widget using PATTERN to find the pixmap files for it,
+   storing the new widget at *BOXPTR.  PATTERN should be of the form:
+   "somewhere/style*.png".  The '*' then gets substituted with the various
+   pixmap names that the widget uses.
+   Return zero on failure, nonzero on success.  */
+static int
+theme_set_box (grub_gfxmenu_box_t *boxptr,
+               char *pattern, const char *theme_dir)
+{
+  char *abspattern;
+  char *prefix;
+  char *suffix;
+  char *star;
+  grub_gfxmenu_box_t box;
+
+  abspattern = grub_resolve_relative_path (theme_dir, pattern);
+  if (! abspattern)
+    return 0;
+
+  star = grub_strchr (abspattern, '*');
+  if (! star)
+    {
+      grub_free (abspattern);
+      return 0;
+    }
+
+  /* Prefix:  Get the part before the '*'.  */
+  prefix = grub_malloc (star - abspattern + 1);
+  if (! prefix)
+    {
+      grub_free (abspattern);
+      return 0;
+    }
+
+  grub_memcpy (prefix, abspattern, star - abspattern);
+  prefix[star - abspattern] = '\0';
+
+  /* Suffix:  Everything after the '*' is the suffix.  */
+  suffix = star + 1;
+
+  box = grub_gfxmenu_create_box (prefix, suffix);
+  grub_free (abspattern);   /* Note: suffix, star point into abspattern.  */
+  grub_free (prefix);
+  if (! box)
+    return 0;
+
+  if (*boxptr)
+    (*boxptr)->destroy (*boxptr);
+  *boxptr = box;
+  return 1;
+
+}
+
+/* Set the specified property NAME on the view to the given string VALUE.
+   This function takes ownership of both NAME and VALUE, so the caller
+   should pass pointers to new heap-allocated strings.  */
+static void
+theme_set_string (grub_gfxmenu_view_t view, char *name, char *value,
+                  const char *theme_dir)
+{
+  if (! grub_strcmp ("title-font", name))
+    view->title_font = grub_font_get (value);
+  else if (! grub_strcmp ("item-font", name))
+    view->item_font = grub_font_get (value);
+  else if (! grub_strcmp ("selected-item-font", name))
+    view->selected_item_font = grub_font_get (value);
+  else if (! grub_strcmp ("status-font", name))
+    view->status_font = grub_font_get (value);
+  else if (! grub_strcmp ("terminal-font", name))
+    {
+      grub_free (view->terminal_font_name);
+      view->terminal_font_name = value;
+      value = 0;  /* Prevent value from being freed below.  */
+    }
+  else if (! grub_strcmp ("title-color", name))
+    view->title_color = parse_color (value);
+  else if (! grub_strcmp ("item-color", name))
+    view->item_color = parse_color (value);
+  else if (! grub_strcmp ("selected-item-color", name))
+    view->selected_item_color = parse_color (value);
+  else if (! grub_strcmp ("status-color", name))
+    view->status_color = parse_color (value);
+  else if (! grub_strcmp ("status-bg-color", name))
+    view->status_bg_color = parse_color (value);
+  else if (! grub_strcmp ("progress-bar-border-color", name))
+    view->progress_bar_border_color = parse_color (value);
+  else if (! grub_strcmp ("progress-bar-fg-color", name))
+    view->progress_bar_fg_color = parse_color (value);
+  else if (! grub_strcmp ("progress-bar-bg-color", name))
+    view->progress_bar_bg_color = 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)
+        goto fail;
+      if (grub_video_bitmap_load (&raw_bitmap, path) != GRUB_ERR_NONE)
+        {
+          grub_free (path);
+          goto fail;
+        }
+      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)
+        goto fail;
+
+      grub_video_bitmap_destroy (view->desktop_image);
+      view->desktop_image = scaled_bitmap;
+    }
+  else if (! grub_strcmp ("desktop-color", name))
+    view->desktop_color = parse_color (value);
+  else if (! grub_strcmp ("menu-box", name))
+    theme_set_box (&view->menu_box, value, theme_dir);
+  else if (! grub_strcmp ("selected-item-box", name))
+    theme_set_box (&view->selected_item_box, value, theme_dir);
+  else if (! grub_strcmp ("terminal-box", name))
+    theme_set_box (&view->terminal_box, value, theme_dir);
+  else if (! grub_strcmp ("title-text", name))
+    {
+      grub_free (view->title_text);
+      view->title_text = value;
+      value = 0;  /* Prevent value from being freed below.  */
+    }
+
+fail:
+  grub_free (value);
+  grub_free (name);
+}
+
+/* Set the specified property NAME on the view to the given numeric VALUE.
+   This function takes ownership NAME, so the caller should pass a pointer
+   to a new heap-allocated string.  */
+static void
+theme_set_number (grub_gfxmenu_view_t view, char *name, int value)
+{
+  if (! grub_strcmp ("icon-width", name))
+    view->icon_width = value;
+  else if (! grub_strcmp ("icon-height", name))
+    view->icon_height = value;
+  else if (! grub_strcmp ("item-height", name))
+    view->item_height = value;
+  else if (! grub_strcmp ("item-padding", name))
+    view->item_padding = value;
+  else if (! grub_strcmp ("item-icon-space", name))
+    view->item_icon_space = value;
+  else if (! grub_strcmp ("item-spacing", name))
+    view->item_spacing = value;
+
+  grub_free (name);
+}
+
+/* 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)
+{
+  char *theme_dir;
+  grub_file_t file;
+  char *buf;
+  int pos;
+  int len;
+
+  theme_dir = grub_get_dirname (theme_path);
+
+  file = grub_file_open (theme_path);
+  if (!file)
+    {
+      grub_free (theme_dir);
+      return 0;
+    }
+
+  len = grub_file_size (file);
+  buf = grub_malloc (len);
+  if (! buf)
+    {
+      grub_file_close (file);
+      grub_free (theme_dir);
+      return 0;
+    }
+  if (grub_file_read (file, buf, len) != len)
+    {
+      grub_free (buf);
+      grub_file_close (file);
+      grub_free (theme_dir);
+      return 0;
+    }
+
+  pos = 0;
+  while (pos < len)
+    {
+      /* Skip comments (lines beginning with #).  */
+      if (pos < len && buf[pos] == '#')
+        goto nextline;
+
+      /* Get name.  */
+      /* Find a word character.  */
+      while (pos < len && grub_isspace(buf[pos]))
+        pos++;
+      int name_start = pos;
+      /* Find the end of the name.  */
+      while (pos < len
+             && (grub_isalpha(buf[pos])
+                 || grub_isdigit(buf[pos])
+                 || buf[pos] == '_'
+                 || buf[pos] == '-'))
+        pos++;
+      int name_end = pos;
+
+      if (name_end - name_start < 1)
+        goto nextline;
+
+      /* Skip whitespace before separator.  */
+      while (pos < len
+             && (buf[pos] == ' '
+                 || buf[pos] == '\t'
+                 || buf[pos] == '\f'))
+        pos++;
+
+      /* Read separator.  */
+      if (buf[pos] != ':')
+        goto nextline;
+
+      pos++;  /* Skip separator.  */
+
+      /* Skip whitespace after separator.  */
+      while (pos < len
+             && (buf[pos] == ' '
+                 || buf[pos] == '\t'
+                 || buf[pos] == '\f'))
+        pos++;
+
+      /* Get the value based on its type.  */
+      if (pos < len && buf[pos] == '"')
+        {
+          /* String value.  (e.g., '"My string"')  */
+
+          int value_start;
+          int value_end;
+
+          /* Skip the opening quotation mark.  */
+          pos++;
+          /* Get string value.  */
+          value_start = pos;
+          /* Find the ending quotation mark.  */
+          while (pos < len
+                 && !(buf[pos] == '"'
+                      || buf[pos] == '\n'))
+            pos++;
+          value_end = pos;
+          theme_set_string (view,
+                            grub_new_substring (buf, name_start, name_end),
+                            grub_new_substring (buf, value_start, value_end),
+                            theme_dir);
+        }
+      else if (pos < len && grub_isdigit(buf[pos]))
+        {
+          /* Numeric value.  (e.g., '123')  */
+
+          int value_start;
+          int value_end;
+          char *value_str;
+          int value;
+
+          /* Get numeric value.  */
+          value_start = pos;
+          /* Find the end of the digit sequence.  */
+          while (pos < len && grub_isdigit(buf[pos]))
+            pos++;
+          value_end = pos;
+          value_str = grub_new_substring (buf, value_start, value_end);
+          if (!value_str)
+            continue;
+          value = grub_strtoul (value_str, 0, 0);
+          grub_free (value_str);
+          theme_set_number (view,
+                            grub_new_substring (buf, name_start, name_end),
+                            value);
+        }
+
+nextline:
+      /* Eat characters up to the newline.  */
+      while (pos < len && buf[pos] != '\n')
+        pos++;
+      pos++;   /* Eat the newline.  */
+    }
+
+  grub_free (view->theme_path);
+  view->theme_path = grub_strdup (theme_path);
+  free_icon_cache (view);   /* There may be new icons for this theme.  */
+  grub_free (buf);
+  grub_file_close (file);
+  grub_free (theme_dir);
+  return 1;
+}
+
+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 (view->desktop_color,
+                            view->screen.x, view->screen.y,
+                            view->screen.width, view->screen.height);
+    }
+}
+
+static const char icon_extension[] = ".png";
+
+static struct grub_video_bitmap *
+try_loading_icon (grub_gfxmenu_view_t view,
+                  const char *dir, const char *class_name)
+{
+  char *path = grub_malloc (grub_strlen (dir)
+                            + grub_strlen (class_name)
+                            + grub_strlen (icon_extension)
+                            + 100);
+  if (! path)
+    return 0;
+
+  grub_strcpy (path, dir);
+  grub_strcat (path, class_name);
+  grub_strcat (path, icon_extension);
+
+  struct grub_video_bitmap *raw_bitmap;
+  grub_video_bitmap_load (&raw_bitmap, path);
+  grub_free (path);
+  grub_errno = GRUB_ERR_NONE;  /* Critical to clear the error!!  */
+  if (! raw_bitmap)
+    return 0;
+
+  struct grub_video_bitmap *scaled_bitmap;
+  grub_video_bitmap_create_scaled (&scaled_bitmap,
+                                   view->icon_width, view->icon_height,
+                                   raw_bitmap,
+                                   GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST);
+  grub_video_bitmap_destroy (raw_bitmap);
+  if (! scaled_bitmap)
+    return 0;
+
+  return scaled_bitmap;
+}
+
+static struct grub_video_bitmap *
+get_icon_by_class (grub_gfxmenu_view_t view, const char *class_name)
+{
+
+  /* First check the view's icon cache.  */
+  grub_gfxmenu_view_icon_t vicon;
+  for (vicon = view->icon_cache.next; vicon; vicon = vicon->next)
+    {
+      if (grub_strcmp (vicon->class_name, class_name) == 0)
+        return vicon->bitmap;
+    }
+
+  /* Otherwise, we search for an icon to load.  */
+  if (! view->theme_path)
+    return 0;
+
+  char *theme_dir = grub_get_dirname (view->theme_path);
+  char *icons_dir;
+  struct grub_video_bitmap *icon;
+  icon = 0;
+  /* First try the theme's own icons, from "grub/themes/NAME/icons/"  */
+  icons_dir = grub_resolve_relative_path (theme_dir, "icons/");
+  if (icons_dir)
+    {
+      icon = try_loading_icon (view, icons_dir, class_name);
+      grub_free (icons_dir);
+    }
+  if (! icon)
+    {
+      /* If the theme doesn't have an appropriate icon, check in
+         "grub/themes/icons".  */
+      icons_dir = grub_resolve_relative_path (theme_dir, "../icons/");
+      if (icons_dir)
+        {
+          icon = try_loading_icon (view, icons_dir, class_name);
+          grub_free (icons_dir);
+        }
+    }
+  grub_free (theme_dir);
+
+  if (! icon)
+    return 0;
+
+  vicon = grub_malloc (sizeof (*vicon));
+  if (! vicon)
+    {
+      grub_video_bitmap_destroy (icon);
+      return 0;
+    }
+  vicon->class_name = class_name;
+  vicon->bitmap = icon;
+  vicon->next = view->icon_cache.next;
+  view->icon_cache.next = vicon;   /* Link it into the cache.  */
+  return vicon->bitmap;
+}
+
+static struct grub_video_bitmap *
+get_item_icon (grub_gfxmenu_view_t view, int item_index)
+{
+  grub_menu_entry_t entry;
+  entry = grub_gfxmenu_model_get_entry (view->model, item_index);
+  if (! entry)
+    return 0;
+
+  struct grub_menu_entry_class *c;
+  struct grub_video_bitmap *icon;
+  icon = 0;
+  for (c = entry->classes->next; c && ! icon; c = c->next)
+    icon = get_icon_by_class (view, c->name);
+  return icon;
+}
+
+static void
+draw_menu (grub_gfxmenu_view_t view)
+{
+  int boxpad = view->item_padding;
+  int icon_text_space = view->item_icon_space;
+  int item_vspace = view->item_spacing;
+
+  int ascent = grub_font_get_ascent (view->item_font);
+  int descent = grub_font_get_descent (view->item_font);
+  int item_height = view->item_height;
+
+  int num_items = grub_gfxmenu_model_get_num_entries (view->model);
+  grub_video_rect_t r;
+  r.width = view->screen.width * 4 / 5;
+  /* Set the menu box height to fit the items.  */
+  r.height = (item_height * num_items
+              + item_vspace * (num_items - 1)
+              + 2 * boxpad);
+  r.x = (view->screen.width - r.width) / 2;
+  r.y = (view->screen.height - r.height) / 2;
+  view->menu_box->set_content_size (view->menu_box, r.width, r.height);
+
+  int menu_box_left_pad = view->menu_box->get_left_pad (view->menu_box);
+  int menu_box_top_pad = view->menu_box->get_top_pad (view->menu_box);
+  view->menu_box->draw (view->menu_box,
+                        r.x - menu_box_left_pad, r.y - menu_box_top_pad);
+
+  int item_top = r.y + boxpad;
+  int item_left = r.x + boxpad;
+  int i;
+
+  for (i = 0; i < num_items; i++)
+    {
+      int is_selected =
+        (i == grub_gfxmenu_model_get_selected_index (view->model));
+
+      if (is_selected)
+        {
+          view->selected_item_box->set_content_size (view->selected_item_box,
+                                                     r.width - 2 * boxpad,
+                                                     item_height);
+          int leftpad = view->selected_item_box->get_left_pad (view->selected_item_box);
+          int toppad = view->selected_item_box->get_top_pad (view->selected_item_box);
+          view->selected_item_box->draw (view->selected_item_box,
+                                         item_left - leftpad,
+                                         item_top - toppad);
+        }
+
+      struct grub_video_bitmap *icon;
+      if ((icon = get_item_icon (view, i)) != 0)
+        grub_video_blit_bitmap (icon, GRUB_VIDEO_BLIT_BLEND,
+                                item_left,
+                                item_top + (item_height - view->icon_height) / 2,
+                                0, 0, view->icon_width, view->icon_height);
+
+      const char *item_title =
+        grub_gfxmenu_model_get_entry_title (view->model, i);
+      grub_video_draw_string (item_title,
+                              (is_selected
+                               ? view->selected_item_font
+                               : view->item_font),
+                              (is_selected
+                               ? view->selected_item_color
+                               : view->item_color),
+                              item_left + view->icon_width + icon_text_space,
+                              (item_top + (item_height - (ascent + descent))
+                               / 2 + ascent));
+
+      item_top += item_height + item_vspace;
+    }
+}
+
+static void
+draw_title (grub_gfxmenu_view_t view)
+{
+  if (! view->title_text)
+    return;
+
+  /* Center the title. */
+  int title_width = grub_font_get_string_width (view->title_font,
+                                                view->title_text);
+  int x = (view->screen.width - title_width) / 2;
+  int y = 40 + grub_font_get_ascent (view->title_font);
+  grub_video_draw_string (view->title_text,
+                          view->title_font, view->title_color,
+                          x, y);
+}
+
+static void
+draw_status (grub_gfxmenu_view_t view)
+{
+  int descent = grub_font_get_descent (view->status_font);
+  int ascent = grub_font_get_ascent (view->status_font);
+  int vpad = 5;
+  int textheight = descent + ascent + 1;
+  int h = 2 * vpad + 2 * textheight;
+
+  grub_video_fill_rect (view->status_bg_color,
+                        0, view->screen.height - h,
+                        view->screen.width, view->screen.height - 1);
+
+  int texty = view->screen.height - h + vpad + ascent;
+  grub_video_draw_string ("Select an item with the arrow keys and "
+                          "press Enter to boot.",
+                          view->status_font, view->status_color, 30, texty);
+  texty += textheight;
+  grub_video_draw_string ("Press:  'c' for command line; 't' to switch to "
+                          "non-graphical menu.",
+                          view->status_font, view->status_color, 30, texty);
+}
+
+static void
+draw_timeout (grub_gfxmenu_view_t view)
+{
+  int timeout = grub_gfxmenu_model_get_timeout_ms (view->model);
+  if (timeout == -1)
+    return;
+
+  int remaining = grub_gfxmenu_model_get_timeout_remaining_ms (view->model);
+  if (remaining < 0)
+    remaining = 0;
+
+  int t = timeout - remaining;
+
+  /* Set the timeout bar's frame.  */
+  grub_video_rect_t f;
+  f.width = view->screen.width * 3 / 5;
+  f.height = view->screen.height / 25;
+  f.x = view->screen.x + (view->screen.width - f.width) / 2;
+  f.y = view->screen.y + view->screen.height - 90;
+
+  /* First attempt just uses filled rectangles;
+     TODO we should enhance with a pixmap themed progress bar component.  */
+
+  /* Border.  */
+  grub_video_fill_rect (view->progress_bar_border_color,
+                        f.x - 1, f.y - 1,
+                        f.width + 2, f.height + 2);
+
+  /* Bar background.  */
+  int barwidth = f.width * t / timeout;
+  grub_video_fill_rect (view->progress_bar_bg_color,
+                        f.x + barwidth, f.y,
+                        f.width - barwidth, f.height);
+
+  /* Bar foreground.  */
+  grub_video_fill_rect (view->progress_bar_fg_color,
+                        f.x, f.y,
+                        barwidth, f.height);
+
+  char *text = grub_malloc (200);
+  if (!text)
+    return;
+  int seconds_remaining_rounded_up = (remaining + 999) / 1000;
+  grub_sprintf (text,
+                "The highlighted entry will be booted automatically in %d s.",
+                seconds_remaining_rounded_up);
+  grub_font_t font = view->status_font;
+  grub_video_color_t color = view->progress_bar_border_color;
+
+  /* Center the text. */
+  int text_width = grub_font_get_string_width (font, text);
+  int x = f.x + (f.width - text_width) / 2;
+  int y = (f.y + (f.height - grub_font_get_descent (font)) / 2
+           + grub_font_get_ascent (font) / 2);
+  grub_video_draw_string (text, font, color, x, y);
+  grub_free (text);
+}
+
+static void
+draw_message (grub_gfxmenu_view_t view)
+{
+  char *text = view->progress_message_text;
+  if (! text)
+    return;
+
+  grub_font_t font = view->status_font;
+  grub_video_color_t color = view->status_color;
+
+  /* Set the timeout bar's frame.  */
+  grub_video_rect_t f;
+  f.width = view->screen.width * 4 / 5;
+  f.height = 50;
+  f.x = view->screen.x + (view->screen.width - f.width) / 2;
+  f.y = view->screen.y + view->screen.height - 90 - 20 - f.height;
+
+  /* Border.  */
+  grub_video_fill_rect (color,
+                        f.x-1, f.y-1, f.width+2, f.height+2);
+  /* Fill.  */
+  grub_video_fill_rect (view->status_bg_color,
+                        f.x, f.y, f.width, f.height);
+
+  /* Center the text. */
+  int text_width = grub_font_get_string_width (font, text);
+  int x = f.x + (f.width - text_width) / 2;
+  int y = (f.y + (f.height - grub_font_get_descent (font)) / 2
+           + grub_font_get_ascent (font) / 2);
+  grub_video_draw_string (text, font, color, x, y);
+}
+
+
+void
+grub_gfxmenu_view_draw (grub_gfxmenu_view_t view)
+{
+  grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
+  draw_background (view);
+  draw_menu (view);
+  draw_title (view);
+  draw_status (view);
+  draw_timeout (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_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_gfxmenu_view_draw (view);
+  grub_video_swap_buffers ();
+  grub_millisleep (FALLBACK_MESSAGE_DELAY);
+}
+
+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, grub_strdup ("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-07-19 18:25:18 +0000
@@ -0,0 +1,244 @@
+/* widget_box.c - Pixmap-stylized box widget. */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2008  Free Software Foundation, Inc.
+ *
+ *  GRUB is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/types.h>
+#include <grub/misc.h>
+#include <grub/mm.h>
+#include <grub/err.h>
+#include <grub/video.h>
+#include <grub/bitmap.h>
+#include <grub/bitmap_scale.h>
+#include <grub/gfxwidgets.h>
+
+enum box_pixmaps
+{
+  BOX_PIXMAP_NW, BOX_PIXMAP_NE, BOX_PIXMAP_SE, BOX_PIXMAP_SW,
+  BOX_PIXMAP_N, BOX_PIXMAP_E, BOX_PIXMAP_S, BOX_PIXMAP_W,
+  BOX_PIXMAP_CENTER
+};
+
+static const char *box_pixmap_names[] = {
+  /* Corners: */
+  "nw", "ne", "se", "sw",
+  /* Sides: */
+  "n", "e", "s", "w",
+  /* Center: */
+  "c"
+};
+
+#define BOX_NUM_PIXMAPS (sizeof(box_pixmap_names)/sizeof(*box_pixmap_names))
+
+static void
+draw (grub_gfxmenu_box_t self, int x, int y)
+{
+  int height_n;
+  int height_s;
+  int height_e;
+  int height_w;
+  int width_n;
+  int width_s;
+  int width_e;
+  int width_w;
+  unsigned i;
+
+  /* Don't try to draw if any pixmaps are null, except the center. */
+  for (i = 0; i < BOX_NUM_PIXMAPS; i++)
+    {
+      if (i != BOX_PIXMAP_CENTER && self->scaled_pixmaps[i] == 0)
+        return;
+    }
+
+  height_n = grub_video_bitmap_get_height (self->scaled_pixmaps[BOX_PIXMAP_N]);
+  height_s = grub_video_bitmap_get_height (self->scaled_pixmaps[BOX_PIXMAP_S]);
+  height_e = grub_video_bitmap_get_height (self->scaled_pixmaps[BOX_PIXMAP_E]);
+  height_w = grub_video_bitmap_get_height (self->scaled_pixmaps[BOX_PIXMAP_W]);
+  width_n = grub_video_bitmap_get_width (self->scaled_pixmaps[BOX_PIXMAP_N]);
+  width_s = grub_video_bitmap_get_width (self->scaled_pixmaps[BOX_PIXMAP_S]);
+  width_e = grub_video_bitmap_get_width (self->scaled_pixmaps[BOX_PIXMAP_E]);
+  width_w = grub_video_bitmap_get_width (self->scaled_pixmaps[BOX_PIXMAP_W]);
+
+  /* Draw sides.  */
+  grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_N], 
+                          GRUB_VIDEO_BLIT_BLEND,
+                          x + width_w, y, 0, 0, width_n, height_n);
+  grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_S],
+                          GRUB_VIDEO_BLIT_BLEND,
+                          x + width_w, y + height_n + height_w,
+                          0, 0, width_s, height_s);
+  grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_E],
+                          GRUB_VIDEO_BLIT_BLEND,
+                          x + width_w + width_n, y + height_n,
+                          0, 0, width_e, height_e);
+  grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_W],
+                          GRUB_VIDEO_BLIT_BLEND,
+                          x, y + height_n, 0, 0, width_w, height_w);
+
+  /* Draw corners.  */
+  grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_NW],
+                          GRUB_VIDEO_BLIT_BLEND,
+                          x, y, 0, 0, width_w, height_n);
+  grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_NE],
+                          GRUB_VIDEO_BLIT_BLEND,
+                          x + width_w + width_n, y, 
+                          0, 0, width_e, height_n);
+  grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_SE],
+                          GRUB_VIDEO_BLIT_BLEND,
+                          x + width_w + width_n, y + height_n + height_e, 
+                          0, 0, width_e, height_s);
+  grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_SW],
+                          GRUB_VIDEO_BLIT_BLEND,
+                          x, y + height_n + height_w, 
+                          0, 0, width_w, height_s);
+
+  /* Draw center.  */
+  if (self->scaled_pixmaps[BOX_PIXMAP_CENTER])
+    grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_CENTER],
+                            GRUB_VIDEO_BLIT_BLEND,
+                            x + width_w, y + height_n,
+                            0, 0, self->width, self->height);
+}
+
+static void
+scale_pixmap (grub_gfxmenu_box_t self, int i, int w, int h)
+{
+  struct grub_video_bitmap **scaled = &self->scaled_pixmaps[i];
+  struct grub_video_bitmap *raw = self->raw_pixmaps[i];
+
+  if (raw == 0)
+    return;
+
+  if (w == -1)
+    w = grub_video_bitmap_get_width (raw);
+  if (h == -1)
+    h = grub_video_bitmap_get_height (raw);
+
+  if (*scaled == 0 
+      || ((int) grub_video_bitmap_get_width (*scaled) != w)
+      || ((int) grub_video_bitmap_get_height (*scaled) != h))
+    {
+      if (*scaled)
+        {
+          grub_video_bitmap_destroy (*scaled);
+          *scaled = 0;
+        }
+      grub_video_bitmap_create_scaled (scaled, w, h, raw, 
+                                       GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST);
+    }
+}
+
+static void
+set_content_size (grub_gfxmenu_box_t self,
+                  int width, int height)
+{
+  self->width = width;
+  self->height = height;
+  /* Resize sides to match the width and height.  */
+  /* It is assumed that the corners width/height match the adjacent sides.  */
+
+  /* Resize N and S sides to match width.  */
+  scale_pixmap(self, BOX_PIXMAP_N, width, -1);
+  scale_pixmap(self, BOX_PIXMAP_S, width, -1);
+
+  /* Resize E and W sides to match height.  */
+  scale_pixmap(self, BOX_PIXMAP_E, -1, height);
+  scale_pixmap(self, BOX_PIXMAP_W, -1, height);
+
+  /* Don't scale the corners--they are assumed to match the sides. */
+  scale_pixmap(self, BOX_PIXMAP_NW, -1, -1);
+  scale_pixmap(self, BOX_PIXMAP_SW, -1, -1);
+  scale_pixmap(self, BOX_PIXMAP_NE, -1, -1);
+  scale_pixmap(self, BOX_PIXMAP_SE, -1, -1);
+
+  /* Scale the center area. */
+  scale_pixmap(self, BOX_PIXMAP_CENTER, width, height);
+}
+
+static int
+get_left_pad (grub_gfxmenu_box_t self)
+{
+  return grub_video_bitmap_get_width (self->raw_pixmaps[BOX_PIXMAP_W]);
+}
+
+static int
+get_top_pad (grub_gfxmenu_box_t self)
+{
+  return grub_video_bitmap_get_height (self->raw_pixmaps[BOX_PIXMAP_N]);
+}
+
+static void
+destroy (grub_gfxmenu_box_t self)
+{
+  unsigned i;
+  for (i = 0; i < BOX_NUM_PIXMAPS; i++)
+    {
+      if (self->raw_pixmaps[i])
+        grub_video_bitmap_destroy(self->raw_pixmaps[i]);
+      self->raw_pixmaps[i] = 0;
+
+      if (self->scaled_pixmaps[i])
+        grub_video_bitmap_destroy(self->scaled_pixmaps[i]);
+      self->scaled_pixmaps[i] = 0;
+    }
+  grub_free (self->raw_pixmaps);
+  self->raw_pixmaps = 0;
+  grub_free (self->scaled_pixmaps);
+  self->scaled_pixmaps = 0;
+
+  grub_free (self);             /* Free self:  must be the last step! */
+}
+
+
+grub_gfxmenu_box_t
+grub_gfxmenu_create_box (const char *pixmaps_prefix,
+                         const char *pixmaps_suffix)
+{
+  char path[200];
+  unsigned i;
+  grub_gfxmenu_box_t box;
+
+  box = (grub_gfxmenu_box_t) grub_malloc (sizeof (*box));
+  if (!box)
+    return 0;
+  box->width = 0;
+  box->height = 0;
+  box->raw_pixmaps =
+    (struct grub_video_bitmap **) 
+    grub_malloc (BOX_NUM_PIXMAPS * sizeof (struct grub_video_bitmap *));
+  box->scaled_pixmaps =
+    (struct grub_video_bitmap **)
+    grub_malloc (BOX_NUM_PIXMAPS * sizeof (struct grub_video_bitmap *));
+
+  for (i = 0; i < BOX_NUM_PIXMAPS; i++)
+    {
+      /* TODO XXX dynamically allocate PATH, making sure it's large enough */
+      grub_sprintf (path, "%s%s%s",
+                    pixmaps_prefix, box_pixmap_names[i], pixmaps_suffix);
+      grub_video_bitmap_load (&box->raw_pixmaps[i], path);
+      box->scaled_pixmaps[i] = 0;
+    }
+
+  box->draw = draw;
+  box->set_content_size = set_content_size;
+  box->get_left_pad = get_left_pad;
+  box->get_top_pad = get_top_pad;
+  box->destroy = destroy;
+
+  return box;
+}
+

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

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

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

=== added file 'include/grub/gfxmenu_model.h'
--- include/grub/gfxmenu_model.h	1970-01-01 00:00:00 +0000
+++ include/grub/gfxmenu_model.h	2008-07-26 23:37:04 +0000
@@ -0,0 +1,59 @@
+/* gfxmenu_model.h - gfxmenu model interface. */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2008  Free Software Foundation, Inc.
+ *
+ *  GRUB is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GRUB_GFXMENU_MODEL_HEADER
+#define GRUB_GFXMENU_MODEL_HEADER 1
+
+#include <grub/menu.h>
+
+struct grub_gfxmenu_model;   /* Forward declaration of opaque type.  */
+typedef struct grub_gfxmenu_model *grub_gfxmenu_model_t;
+
+
+grub_gfxmenu_model_t grub_gfxmenu_model_new (grub_menu_t menu);
+
+void grub_gfxmenu_model_destroy (grub_gfxmenu_model_t model);
+
+grub_menu_t grub_gfxmenu_model_get_menu (grub_gfxmenu_model_t model);
+
+void grub_gfxmenu_model_set_timeout (grub_gfxmenu_model_t model);
+
+void grub_gfxmenu_model_clear_timeout (grub_gfxmenu_model_t model);
+
+int grub_gfxmenu_model_get_timeout_ms (grub_gfxmenu_model_t model);
+
+int grub_gfxmenu_model_get_timeout_remaining_ms (grub_gfxmenu_model_t model);
+
+int grub_gfxmenu_model_timeout_expired (grub_gfxmenu_model_t model);
+
+int grub_gfxmenu_model_get_num_entries (grub_gfxmenu_model_t model);
+
+int grub_gfxmenu_model_get_selected_index (grub_gfxmenu_model_t model);
+
+void grub_gfxmenu_model_set_selected_index (grub_gfxmenu_model_t model,
+                                            int index);
+
+const char *grub_gfxmenu_model_get_entry_title (grub_gfxmenu_model_t model,
+                                                int index);
+
+grub_menu_entry_t grub_gfxmenu_model_get_entry (grub_gfxmenu_model_t model,
+                                                int index);
+
+#endif /* GRUB_GFXMENU_MODEL_HEADER */
+

=== added file 'include/grub/gfxmenu_view.h'
--- include/grub/gfxmenu_view.h	1970-01-01 00:00:00 +0000
+++ include/grub/gfxmenu_view.h	2008-07-26 23:37:04 +0000
@@ -0,0 +1,53 @@
+/* gfxmenu_view.h - gfxmenu view interface. */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2008  Free Software Foundation, Inc.
+ *
+ *  GRUB is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GRUB_GFXMENU_VIEW_HEADER
+#define GRUB_GFXMENU_VIEW_HEADER 1
+
+#include <grub/types.h>
+#include <grub/err.h>
+#include <grub/menu.h>
+#include <grub/gfxmenu_model.h>
+
+struct grub_gfxmenu_view;   /* Forward declaration of opaque type.  */
+typedef struct grub_gfxmenu_view *grub_gfxmenu_view_t;
+
+
+grub_gfxmenu_view_t grub_gfxmenu_view_new (const char *theme_path,
+                                           grub_gfxmenu_model_t model);
+
+void grub_gfxmenu_view_destroy (grub_gfxmenu_view_t view);
+
+/* Set properties on the view based on settings from the specified
+   theme file. */
+int grub_gfxmenu_view_load_theme (grub_gfxmenu_view_t view,
+                                  const char *theme_path);
+
+void grub_gfxmenu_view_draw (grub_gfxmenu_view_t view);
+
+int grub_gfxmenu_view_execute_with_fallback (grub_gfxmenu_view_t view,
+                                             grub_menu_entry_t entry);
+
+int grub_gfxmenu_view_execute_entry (grub_gfxmenu_view_t view,
+                                     grub_menu_entry_t entry);
+
+void grub_gfxmenu_view_run_terminal (grub_gfxmenu_view_t view);
+
+#endif /* GRUB_GFXMENU_VIEW_HEADER */
+

=== added file 'include/grub/gfxterm.h'
--- include/grub/gfxterm.h	1970-01-01 00:00:00 +0000
+++ include/grub/gfxterm.h	2008-07-19 19:56:06 +0000
@@ -0,0 +1,41 @@
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2006,2007,2008  Free Software Foundation, Inc.
+ *
+ *  GRUB is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GRUB_GFXTERM_HEADER
+#define GRUB_GFXTERM_HEADER	1
+
+#include <grub/err.h>
+#include <grub/types.h>
+#include <grub/term.h>
+#include <grub/video.h>
+
+grub_err_t
+grub_gfxterm_init_window (struct grub_video_render_target *target,
+                          int x, int y, int width, int height, 
+                          const char *font_name, int border_width);
+
+void grub_gfxterm_destroy_window (void);
+
+grub_term_t grub_gfxterm_get_term (void);
+
+typedef void (*grub_gfxterm_repaint_callback_t)(int x, int y, 
+                                                int width, int height);
+
+void grub_gfxterm_set_repaint_callback (grub_gfxterm_repaint_callback_t func);
+
+#endif /* ! GRUB_GFXTERM_HEADER */

=== added file 'include/grub/gfxwidgets.h'
--- include/grub/gfxwidgets.h	1970-01-01 00:00:00 +0000
+++ include/grub/gfxwidgets.h	2008-07-19 18:25:18 +0000
@@ -0,0 +1,47 @@
+/* gfxwidgets.h - Widgets for the graphical menu (gfxmenu).  */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2008  Free Software Foundation, Inc.
+ *
+ *  GRUB is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GRUB_GFXWIDGETS_HEADER
+#define GRUB_GFXWIDGETS_HEADER 1
+
+#include <grub/video.h>
+
+typedef struct grub_gfxmenu_box *grub_gfxmenu_box_t;
+
+struct grub_gfxmenu_box
+{
+  /* The size of the content.  */
+  int width;
+  int height;
+
+  struct grub_video_bitmap **raw_pixmaps;
+  struct grub_video_bitmap **scaled_pixmaps;
+
+  void (*draw) (grub_gfxmenu_box_t self, int x, int y);
+  void (*set_content_size) (grub_gfxmenu_box_t self,
+                            int width, int height);
+  int (*get_left_pad) (grub_gfxmenu_box_t self);
+  int (*get_top_pad) (grub_gfxmenu_box_t self);
+  void (*destroy) (grub_gfxmenu_box_t self);
+};
+
+grub_gfxmenu_box_t grub_gfxmenu_create_box (const char *pixmaps_prefix,
+                                            const char *pixmaps_suffix);
+
+#endif /* ! GRUB_GFXWIDGETS_HEADER */

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

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

=== added file 'include/grub/i386/tsc.h'
--- include/grub/i386/tsc.h	1970-01-01 00:00:00 +0000
+++ include/grub/i386/tsc.h	2008-07-28 16:23:46 +0000
@@ -0,0 +1,80 @@
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2008  Free Software Foundation, Inc.
+ *
+ *  GRUB is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef KERNEL_CPU_TSC_HEADER
+#define KERNEL_CPU_TSC_HEADER   1
+
+#include <grub/types.h>
+
+/* Read the TSC value, which increments with each CPU clock cycle. */
+static __inline grub_uint64_t
+grub_get_tsc (void)
+{
+  grub_uint32_t lo, hi;
+
+  /* The CPUID instruction is a 'serializing' instruction, and
+     avoids out-of-order execution of the RDTSC instruction. */
+  __asm__ __volatile__ ("xorl %%eax, %%eax\n\t"
+                        "cpuid":::"%rax", "%rbx", "%rcx", "%rdx");
+  /* Read TSC value.  We cannot use "=A", since this would use
+     %rax on x86_64. */
+  __asm__ __volatile__ ("rdtsc":"=a" (lo), "=d" (hi));
+
+  return (((grub_uint64_t) hi) << 32) | lo;
+}
+
+static __inline int
+grub_cpu_is_cpuid_supported (void)
+{
+  grub_uint32_t id_supported;
+
+  __asm__ ("pushfl\n\t"
+           "popl %%eax             /* Get EFLAGS into EAX */\n\t"
+           "movl %%eax, %%ecx      /* Save original flags in ECX */\n\t"
+           "xorl $0x200000, %%eax  /* Flip ID bit in EFLAGS */\n\t"
+           "pushl %%eax            /* Store modified EFLAGS on stack */\n\t"
+           "popfl                  /* Replace current EFLAGS */\n\t"
+           "pushfl                 /* Read back the EFLAGS */\n\t"
+           "popl %%eax             /* Get EFLAGS into EAX */\n\t"
+           "xorl %%ecx, %%eax      /* Check if flag could be modified */\n\t"
+           : "=a" (id_supported)
+           : /* No inputs.  */
+           : /* Clobbered:  */ "%rcx");
+
+  return id_supported != 0;
+}
+
+static __inline int
+grub_cpu_is_tsc_supported (void)
+{
+  if (! grub_cpu_is_cpuid_supported ())
+    return 0;
+
+  grub_uint32_t features;
+  __asm__ ("movl $1, %%eax\n\t"
+           "cpuid"
+           : "=d" (features)
+           : /* No inputs.  */
+           : /* Clobbered:  */ "%rax", "%rbx", "%rcx");
+  return (features & (1 << 4)) != 0;
+}
+
+void grub_tsc_init (void);
+grub_uint64_t grub_tsc_get_time_ms (void);
+
+#endif /* ! KERNEL_CPU_TSC_HEADER */

=== added file 'include/grub/menu.h'
--- include/grub/menu.h	1970-01-01 00:00:00 +0000
+++ include/grub/menu.h	2008-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/normal.h'
--- include/grub/normal.h	2008-03-26 12:01:02 +0000
+++ include/grub/normal.h	2008-08-03 04:00:22 +0000
@@ -25,6 +25,7 @@
 #include <grub/err.h>
 #include <grub/arg.h>
 #include <grub/env.h>
+#include <grub/menu.h>
 
 /* The maximum size of a command-line.  */
 #define GRUB_MAX_CMDLINE	1600
@@ -39,7 +40,7 @@
 #define GRUB_COMMAND_FLAG_TITLE		0x4
 /* Don't print the command on booting.  */
 #define GRUB_COMMAND_FLAG_NO_ECHO	0x8
-/* Don't print the command on booting.  */
+/* Pass arguments to the command without parsing options.  */
 #define GRUB_COMMAND_FLAG_NO_ARG_PARSE	0x10
 /* Not loaded yet. Used for auto-loading.  */
 #define GRUB_COMMAND_FLAG_NOT_LOADED	0x20
@@ -84,34 +85,6 @@
 };
 typedef struct grub_command *grub_command_t;
 
-/* The menu entry.  */
-struct grub_menu_entry
-{
-  /* The title name.  */
-  const char *title;
-
-    /* The commands associated with this menu entry.  */
-  struct grub_script *commands;
-
-  /* The sourcecode of the menu entry, used by the editor.  */
-  const char *sourcecode;
-
-  /* The next element.  */
-  struct grub_menu_entry *next;
-};
-typedef struct grub_menu_entry *grub_menu_entry_t;
-
-/* The menu.  */
-struct grub_menu
-{
-  /* The size of a menu.  */
-  int size;
-
-  /* The list of menu entries.  */
-  grub_menu_entry_t entry_list;
-};
-typedef struct grub_menu *grub_menu_t;
-
 /* This is used to store the names of filesystem modules for auto-loading.  */
 struct grub_fs_module_list
 {
@@ -123,10 +96,27 @@
 /* To exit from the normal mode.  */
 extern grub_jmp_buf grub_exit_env;
 
+extern struct grub_menu_viewer grub_normal_terminal_menu_viewer;
+
+typedef struct grub_menu_execute_callback
+{
+  void (*notify_booting) (void *userdata, grub_menu_entry_t entry);
+  void (*notify_fallback) (void *userdata, grub_menu_entry_t entry);
+  void (*notify_failure) (void *userdata);
+} 
+*grub_menu_execute_callback_t;
+
 void grub_enter_normal_mode (const char *config);
 void grub_normal_execute (const char *config, int nested);
-void grub_menu_run (grub_menu_t menu, int nested);
+void grub_menu_execute_with_fallback (grub_menu_t menu,
+                                      grub_menu_entry_t entry,
+                                      grub_menu_execute_callback_t callback,
+                                      void *callback_data);
 void grub_menu_entry_run (grub_menu_entry_t entry);
+int grub_menu_get_default_entry_index (grub_menu_t menu);
+int grub_menu_get_timeout (void);
+void grub_menu_set_timeout (int timeout);
+void grub_menu_execute_entry(grub_menu_entry_t entry);
 void grub_cmdline_run (int nested);
 int grub_cmdline_get (const char *prompt, char cmdline[], unsigned max_len,
 		      int echo_char, int readline);

=== added file 'include/grub/stringutil.h'
--- include/grub/stringutil.h	1970-01-01 00:00:00 +0000
+++ include/grub/stringutil.h	2008-07-25 15:52:57 +0000
@@ -0,0 +1,32 @@
+/* gfxmenu_stringutil.h - String utilities for the graphical menu interface. */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2008  Free Software Foundation, Inc.
+ *
+ *  GRUB is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GRUB_GFXMENU_STRINGUTIL_HEADER
+#define GRUB_GFXMENU_STRINGUTIL_HEADER 1
+
+#include <grub/types.h>
+
+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);
+
+#endif /* GRUB_GFXMENU_STRINGUTIL_HEADER */

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

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

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

=== modified file 'kern/i386/linuxbios/init.c'
--- kern/i386/linuxbios/init.c	2008-07-31 18:33:23 +0000
+++ kern/i386/linuxbios/init.c	2008-08-03 03:57:46 +0000
@@ -143,6 +143,8 @@
 
   /* This variable indicates size, not offset.  */
   grub_upper_mem -= GRUB_MEMORY_MACHINE_UPPER_START;
+
+  grub_install_get_time_ms (grub_rtc_get_time_ms);
 }
 
 void

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

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

=== modified file 'kern/ieee1275/init.c'
--- kern/ieee1275/init.c	2008-07-30 09:42:11 +0000
+++ kern/ieee1275/init.c	2008-08-03 03:57:46 +0000
@@ -47,12 +47,6 @@
 extern char _end[];
 
 void
-grub_millisleep (grub_uint32_t ms)
-{
-  grub_millisleep_generic (ms);
-}
-
-void
 grub_exit (void)
 {
   grub_ieee1275_exit ();
@@ -209,6 +203,8 @@
 
 #endif
 
+static grub_uint64_t ieee1275_get_time_ms (void);
+
 void
 grub_machine_init (void)
 {
@@ -258,6 +254,8 @@
 	    }
 	}
     }
+
+  grub_install_get_time_ms (ieee1275_get_time_ms);
 }
 
 void
@@ -267,8 +265,8 @@
   grub_console_fini ();
 }
 
-grub_uint32_t
-grub_get_rtc (void)
+static grub_uint64_t
+ieee1275_get_time_ms (void)
 {
   grub_uint32_t msecs = 0;
 
@@ -277,6 +275,12 @@
   return msecs;
 }
 
+grub_uint32_t
+grub_get_rtc (void)
+{
+  return ieee1275_get_time_ms ();
+}
+
 grub_addr_t
 grub_arch_modules_addr (void)
 {

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

=== modified file 'kern/sparc64/ieee1275/init.c'
--- kern/sparc64/ieee1275/init.c	2007-10-22 18:59:33 +0000
+++ kern/sparc64/ieee1275/init.c	2008-07-28 16:23:46 +0000
@@ -66,12 +66,6 @@
   /* Never reached.  */
 }
 
-void
-grub_millisleep (grub_uint32_t ms)
-{
-  grub_millisleep_generic (ms);
-}
-
 int
 grub_ieee1275_test_flag (enum grub_ieee1275_flag flag)
 {
@@ -145,6 +139,8 @@
   grub_free (prefix);
 }
 
+grub_uint64_t ieee1275_get_time_ms (void);
+
 void
 grub_machine_init (void)
 {
@@ -201,6 +197,7 @@
 	}
     }
 
+  grub_install_get_time_ms (ieee1275_get_time_ms);
 }
 
 void
@@ -216,6 +213,12 @@
   grub_ieee1275_enter ();
 }
 
+grub_uint64_t
+ieee1275_get_time_ms (void)
+{
+  return grub_get_rtc ();
+}
+
 grub_uint32_t
 grub_get_rtc (void)
 {

=== added file 'kern/time.c'
--- kern/time.c	1970-01-01 00:00:00 +0000
+++ kern/time.c	2008-07-28 16:23:46 +0000
@@ -0,0 +1,37 @@
+/* time.c - kernel time 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/time.h>
+
+typedef grub_uint64_t (*get_time_ms_func_t) (void);
+
+/* Function pointer to the implementation in use.  */
+static get_time_ms_func_t get_time_ms_func;
+
+grub_uint64_t
+grub_get_time_ms (void)
+{
+  return get_time_ms_func ();
+}
+
+void
+grub_install_get_time_ms (get_time_ms_func_t func)
+{
+  get_time_ms_func = func;
+}

=== 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-02-09 11:00:19 +0000
+++ normal/menu.c	2008-07-26 23:37:04 +0000
@@ -21,9 +21,11 @@
 #include <grub/misc.h>
 #include <grub/loader.h>
 #include <grub/mm.h>
+#include <grub/time.h>
 #include <grub/machine/time.h>
 #include <grub/env.h>
 #include <grub/script.h>
+#include <grub/menu_viewer.h>
 
 static grub_uint8_t grub_color_menu_normal;
 static grub_uint8_t grub_color_menu_highlight;
@@ -241,8 +243,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 +271,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 +310,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 +385,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 +407,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 +425,7 @@
 	  if (current_time - saved_time >= GRUB_TICKS_PER_SECOND)
 	    {
 	      timeout--;
-	      set_timeout (timeout);
+	      grub_menu_set_timeout (timeout);
 	      saved_time = current_time;
 	      print_timeout (timeout, offset, 1);
 	    }
@@ -468,6 +516,10 @@
 		}
 	      goto refresh;
 	      
+	    case 't':
+	      grub_env_set ("menuviewer", "protomenu");
+	      goto refresh;
+
 	    default:
 	      break;
 	    }
@@ -476,13 +528,14 @@
 	}
     }
 
-  /* Never reach here.  */
+  /* Exit menu without activating an item.  This occurs if the user presses
+   * 't', switching to the graphical menu viewer.  */
   return -1;
 }
 
 /* Run a menu entry.  */
-static void
-run_menu_entry (grub_menu_entry_t entry)
+void
+grub_menu_execute_entry(grub_menu_entry_t entry)
 {
   grub_script_execute (entry->commands);
   
@@ -491,15 +544,80 @@
     grub_command_execute ("boot", 0);
 }
 
+/* Execute ENTRY from the menu MENU, falling back to entries specified
+   in the environment variable "fallback" if it fails.  CALLBACK is a 
+   pointer to a struct of function pointers which are used to allow the
+   caller provide feedback to the user.  */
 void
-grub_menu_run (grub_menu_t menu, int nested)
+grub_menu_execute_with_fallback (grub_menu_t menu,
+				 grub_menu_entry_t entry,
+				 grub_menu_execute_callback_t callback,
+				 void *callback_data)
+{
+  int fallback_entry;
+
+  callback->notify_booting (callback_data, entry);
+
+  grub_menu_execute_entry (entry);
+
+  /* Deal with fallback entries.  */
+  while ((fallback_entry = get_and_remove_first_entry_number ("fallback"))
+	 >= 0)
+    {
+      grub_print_error ();
+      grub_errno = GRUB_ERR_NONE;
+
+      entry = get_entry (menu, fallback_entry);
+      callback->notify_fallback (callback_data, entry);
+      grub_menu_execute_entry (entry);
+    }
+
+  if (grub_errno != GRUB_ERR_NONE)
+    callback->notify_failure (callback_data);
+}
+
+static void
+notify_booting (void *userdata __attribute__((unused)),
+		grub_menu_entry_t entry)
+{
+  grub_printf ("  Booting \'%s\'\n\n", entry->title);
+}
+
+static void
+notify_fallback (void *userdata __attribute__((unused)),
+		 grub_menu_entry_t entry)
+{
+  grub_printf ("\n  Falling back to \'%s\'\n\n", entry->title);
+  grub_millisleep (2000);
+}
+
+static void
+notify_execution_failure (void *userdata __attribute__((unused)))
+{
+  if (grub_errno != GRUB_ERR_NONE)
+    {
+      grub_print_error ();
+      grub_errno = GRUB_ERR_NONE;
+
+      grub_wait_after_message ();
+    }
+}
+
+static struct grub_menu_execute_callback execution_callback = 
+{
+  .notify_booting = notify_booting,
+  .notify_fallback = notify_fallback,
+  .notify_failure = notify_execution_failure
+};
+
+static grub_err_t
+show_menu (grub_menu_t menu, int nested)
 {
   while (1)
     {
       int boot_entry;
       grub_menu_entry_t e;
-      int fallback_entry;
-      
+
       boot_entry = run_menu (menu, nested);
       if (boot_entry < 0)
 	break;
@@ -507,34 +625,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 directory 'util/fonttool/src'
=== added directory 'util/fonttool/src/org'
=== added directory 'util/fonttool/src/org/gnu'
=== added directory 'util/fonttool/src/org/gnu/grub'
=== added directory 'util/fonttool/src/org/gnu/grub/fonttool'
=== added file 'util/fonttool/src/org/gnu/grub/fonttool/BDFFont.java'
--- util/fonttool/src/org/gnu/grub/fonttool/BDFFont.java	1970-01-01 00:00:00 +0000
+++ util/fonttool/src/org/gnu/grub/fonttool/BDFFont.java	2008-07-03 14:08:39 +0000
@@ -0,0 +1,24 @@
+package org.gnu.grub.fonttool;
+
+import java.awt.image.BufferedImage;
+import java.util.HashMap;
+import java.util.Map;
+
+public class BDFFont {
+    private Map<Character, BufferedImage> glyphs = new HashMap<Character, BufferedImage>();
+    private int height = 0;
+
+    void putGlyph(char ch, BufferedImage glyph) {
+        glyphs.put(ch, glyph);
+        if (glyph.getHeight() > height)
+            height = glyph.getHeight();
+    }
+
+    public BufferedImage getGlyph(char c) {
+        return glyphs.get(c);
+    }
+
+    public int getHeight() {
+        return height;
+    }
+}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

=== modified file 'util/i386/pc/grub-mkrescue.in'
--- util/i386/pc/grub-mkrescue.in	2008-07-12 14:40:50 +0000
+++ util/i386/pc/grub-mkrescue.in	2008-07-19 21:39:24 +0000
@@ -71,7 +71,7 @@
     --modules=*)
 	modules=`echo "$option" | sed 's/--modules=//'` ;;
     --overlay=*)
-	overlay=`echo "$option" | sed 's/--overlay=//'` ;;
+	overlay=${overlay}${overlay:+ }`echo "$option" | sed 's/--overlay=//'` ;;
     --pkglibdir=*)
 	input_dir=`echo "$option" | sed 's/--pkglibdir=//'` ;;
     --grub-mkimage=*)
@@ -124,9 +124,10 @@
   echo "insmod $i"
 done > ${aux_dir}/boot/grub/grub.cfg
 
-if test "x$overlay" = x ; then : ; else
-  cp -dpR ${overlay}/* ${aux_dir}/
-fi
+for d in ${overlay}; do
+  echo "Overlaying $d"
+  cp -vdpR ${d}/* ${aux_dir}/
+done
 
 if [ "x${image_type}" = xfloppy -o "x${emulation}" = xfloppy ] ; then
   # build memdisk

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

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

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

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

=== modified file 'video/i386/pc/vbe.c'
--- video/i386/pc/vbe.c	2008-01-21 15:48:27 +0000
+++ video/i386/pc/vbe.c	2008-07-19 19:42:37 +0000
@@ -63,6 +63,7 @@
 static struct
 {
   struct grub_video_render_target render_target;
+  int is_double_buffered;       /* Is the video mode double buffered? */
 
   unsigned int bytes_per_scan_line;
   unsigned int bytes_per_pixel;
@@ -77,6 +78,24 @@
 static grub_uint32_t mode_in_use = 0x55aa;
 static grub_uint16_t *mode_list;
 
+static struct 
+{
+  grub_size_t page_size;        /* The size of a page in bytes. */
+
+  /* For page flipping strategy. */
+  int displayed_page;           /* The page # that is the front buffer. */
+  int render_page;              /* The page # that is the back buffer. */
+
+  /* For blit strategy. */
+  grub_uint8_t *offscreen_buffer;
+
+  /* Virtual functions. */
+  int (*update_screen) (void);
+  int (*destroy) (void);
+} doublebuf_state;
+
+static void double_buffering_init (void);
+
 static void *
 real2pm (grub_vbe_farptr_t ptr)
 {
@@ -376,6 +395,7 @@
   /* Reset frame buffer and render target variables.  */
   grub_memset (&framebuffer, 0, sizeof(framebuffer));
   render_target = &framebuffer.render_target;
+  grub_memset (&doublebuf_state, 0, sizeof(doublebuf_state));
 
   return GRUB_ERR_NONE;
 }
@@ -391,6 +411,9 @@
     /* TODO: Decide, is this something we want to do.  */
     return grub_errno;
 
+  if (doublebuf_state.destroy)
+    doublebuf_state.destroy();
+
   /* TODO: Free any resources allocated by driver.  */
   grub_free (mode_list);
   mode_list = 0;
@@ -533,9 +556,13 @@
       render_target->viewport.width = active_mode_info.x_resolution;
       render_target->viewport.height = active_mode_info.y_resolution;
 
-      /* Set framebuffer pointer and mark it as non allocated.  */
+      /* Mark framebuffer memory as non allocated.  */
       render_target->is_allocated = 0;
-      render_target->data = framebuffer.ptr;
+
+      /* Set up double buffering information. */
+      framebuffer.is_double_buffered =
+        ((mode_type & GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED) != 0);
+      double_buffering_init ();
 
       /* Copy default palette to initialize emulated palette.  */
       for (i = 0; 
@@ -556,6 +583,166 @@
   return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "no matching mode found.");
 }
 
+/* 
+   Set framebuffer render target page and display the proper page, based on
+   `doublebuf_state.render_page' and `doublebuf_state.displayed_page',
+   respectively. 
+
+   Returns 0 upon success, nonzero upon failure.
+ */
+static int
+doublebuf_pageflipping_commit (void)
+{
+  /* Set the render target's data pointer to the start of the render_page. */
+  framebuffer.render_target.data =
+    ((char *) framebuffer.ptr) +
+    doublebuf_state.page_size * doublebuf_state.render_page;
+
+  /* Tell the video adapter to display the new front page. */
+  int display_start_line =
+    framebuffer.render_target.mode_info.height 
+    * doublebuf_state.displayed_page;
+ 
+  grub_vbe_status_t vbe_err = 
+    grub_vbe_bios_set_display_start (0, display_start_line);
+  if (vbe_err != GRUB_VBE_STATUS_OK)
+    return 1;
+
+  return 0;
+}
+
+static int
+doublebuf_pageflipping_update_screen (void)
+{
+  /* Swap the page numbers in the framebuffer struct. */
+  int new_displayed_page = doublebuf_state.render_page;
+  doublebuf_state.render_page = doublebuf_state.displayed_page;
+  doublebuf_state.displayed_page = new_displayed_page;
+
+  return doublebuf_pageflipping_commit ();
+}
+
+static int
+doublebuf_pageflipping_destroy (void)
+{
+  doublebuf_state.update_screen = 0;
+  doublebuf_state.destroy = 0;
+  return 0;
+}
+
+static int
+doublebuf_pageflipping_init (void)
+{
+  doublebuf_state.page_size =
+    framebuffer.bytes_per_scan_line * render_target->mode_info.height;
+  
+  /* Get video RAM size in bytes.  */
+  grub_size_t vram_size = controller_info.total_memory << 16;
+
+  if (2 * doublebuf_state.page_size > vram_size)
+    return 1;   /* Not enough video memory for 2 pages. */
+
+  doublebuf_state.displayed_page = 0;
+  doublebuf_state.render_page = 1;
+
+  doublebuf_state.update_screen = doublebuf_pageflipping_update_screen;
+  doublebuf_state.destroy = doublebuf_pageflipping_destroy;
+
+  /* Set the framebuffer memory data pointer and display the right page. */
+  if (doublebuf_pageflipping_commit () != GRUB_ERR_NONE)
+    return 1;   /* Unable to set the display start.  */
+
+  framebuffer.render_target.mode_info.mode_type
+    |= GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED;
+  return 0;
+}
+
+static int
+doublebuf_blit_update_screen (void)
+{
+  grub_memcpy (framebuffer.ptr,
+               doublebuf_state.offscreen_buffer, 
+               doublebuf_state.page_size);
+  return 0;
+}
+
+static int
+doublebuf_blit_destroy (void)
+{
+  grub_free (doublebuf_state.offscreen_buffer);
+  doublebuf_state.offscreen_buffer = 0;
+
+  doublebuf_state.update_screen = 0;
+  doublebuf_state.destroy = 0;
+  return 0;
+}
+
+static int
+doublebuf_blit_init (void)
+{
+  doublebuf_state.page_size =
+    framebuffer.bytes_per_scan_line * render_target->mode_info.height;
+
+  doublebuf_state.offscreen_buffer = (grub_uint8_t *)
+    grub_malloc (doublebuf_state.page_size);
+  if (doublebuf_state.offscreen_buffer == 0)
+    return 1;   /* Error.  */
+
+  framebuffer.render_target.data = doublebuf_state.offscreen_buffer;
+  doublebuf_state.update_screen = doublebuf_blit_update_screen;
+  doublebuf_state.destroy = doublebuf_blit_destroy;
+
+  framebuffer.render_target.mode_info.mode_type
+    |= GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED;
+  return 0;
+}
+
+static int
+doublebuf_null_update_screen (void)
+{
+  return 0;
+}
+
+static int
+doublebuf_null_destroy (void)
+{
+  doublebuf_state.update_screen = 0;
+  doublebuf_state.destroy = 0;
+  return 0;
+}
+
+static int
+doublebuf_null_init (void)
+{
+  framebuffer.render_target.data = framebuffer.ptr;
+  doublebuf_state.update_screen = doublebuf_null_update_screen;
+  doublebuf_state.destroy = doublebuf_null_destroy;
+
+  framebuffer.render_target.mode_info.mode_type
+    &= ~GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED;
+  return 0;
+}
+
+/* Select the best double buffering mode available.  */
+static void
+double_buffering_init (void)
+{
+  if (doublebuf_state.destroy)
+    doublebuf_state.destroy();
+
+  if (framebuffer.is_double_buffered)
+    {
+      if (doublebuf_pageflipping_init () == 0)
+        return;
+
+      if (doublebuf_blit_init () == 0)
+        return;
+    }
+
+  /* Fall back to no double buffering. */
+  doublebuf_null_init ();
+}
+
 static grub_err_t
 grub_video_vbe_get_info (struct grub_video_mode_info *mode_info)
 {
@@ -708,6 +895,16 @@
 
       return minindex;
     }
+  else if ((render_target->mode_info.mode_type 
+            & GRUB_VIDEO_MODE_TYPE_1BIT_BITMAP) != 0)
+    {
+       if (red == render_target->mode_info.fg_red
+           && green == render_target->mode_info.fg_green
+           && blue == render_target->mode_info.fg_blue)
+         return 1;
+       else
+         return 0;
+    }
   else
     {
       grub_uint32_t value;
@@ -737,6 +934,17 @@
     /* No alpha available in index color modes, just use
        same value as in only RGB modes.  */
     return grub_video_vbe_map_rgb (red, green, blue);
+  else if ((render_target->mode_info.mode_type 
+            & GRUB_VIDEO_MODE_TYPE_1BIT_BITMAP) != 0)
+    {
+      if (red == render_target->mode_info.fg_red
+          && green == render_target->mode_info.fg_green
+          && blue == render_target->mode_info.fg_blue
+          && alpha == render_target->mode_info.fg_alpha)
+        return 1;
+      else
+        return 0;
+    }
   else
     {
       grub_uint32_t value;
@@ -797,6 +1005,24 @@
       *alpha = framebuffer.palette[color].a;
       return;
     }
+  else if ((mode_info->mode_type
+            & GRUB_VIDEO_MODE_TYPE_1BIT_BITMAP) != 0)
+    {
+      if (color & 1)
+        {
+          *red = mode_info->fg_red;
+          *green = mode_info->fg_green;
+          *blue = mode_info->fg_blue;
+          *alpha = mode_info->fg_alpha;
+        }
+      else
+        {
+          *red = mode_info->bg_red;
+          *green = mode_info->bg_green;
+          *blue = mode_info->bg_blue;
+          *alpha = mode_info->bg_alpha;
+        }
+    }
   else
     {
       grub_uint32_t tmp;
@@ -876,17 +1102,24 @@
   target.data = render_target->data;
 
   /* Try to figure out more optimized version.  */
-  if (target.mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_R8G8B8A8)
-    {
-      grub_video_i386_vbefill_R8G8B8A8 (&target, color, x, y, 
-                                        width, height);
-      return GRUB_ERR_NONE;
-    }
-
-  if (target.mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_R8G8B8)
-    {
-      grub_video_i386_vbefill_R8G8B8 (&target, color, x, y,
-                                      width, height);
+  if (target.mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_BGRA_8888)
+    {
+      grub_video_i386_vbefill_direct32 (&target, color, x, y, 
+                                        width, height);
+      return GRUB_ERR_NONE;
+    }
+
+  if (target.mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_RGBA_8888)
+    {
+      grub_video_i386_vbefill_direct32 (&target, color, x, y, 
+                                        width, height);
+      return GRUB_ERR_NONE;
+    }
+
+  if (target.mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_RGB_888)
+    {
+      grub_video_i386_vbefill_direct24 (&target, color, x, y,
+                                        width, height);
       return GRUB_ERR_NONE;
     }
 
@@ -903,76 +1136,6 @@
   return GRUB_ERR_NONE;
 }
 
-// TODO: Remove this method and replace with bitmap based glyphs
-static grub_err_t
-grub_video_vbe_blit_glyph (struct grub_font_glyph * glyph,
-                           grub_video_color_t color, int x, int y)
-{
-  struct grub_video_i386_vbeblit_info target;
-  unsigned int width;
-  unsigned int charwidth;
-  unsigned int height;
-  unsigned int i;
-  unsigned int j;
-  unsigned int x_offset = 0;
-  unsigned int y_offset = 0;
-
-  /* Make sure there is something to do.  */
-  if (x >= (int)render_target->viewport.width)
-    return GRUB_ERR_NONE;
-
-  if (y >= (int)render_target->viewport.height)
-    return GRUB_ERR_NONE;
-
-  /* Calculate glyph dimensions.  */
-  width = ((glyph->width + 7) / 8) * 8;
-  charwidth = width;
-  height = glyph->height;
-
-  if (x + (int)width < 0)
-    return GRUB_ERR_NONE;
-
-  if (y + (int)height < 0)
-    return GRUB_ERR_NONE;
-
-  /* Do not allow drawing out of viewport.  */
-  if (x < 0)
-    {
-      width += x;
-      x_offset = (unsigned int)-x;
-      x = 0;
-    }
-  if (y < 0)
-    {
-      height += y;
-      y_offset = (unsigned int)-y;
-      y = 0;
-    }
-
-  if ((x + width) > render_target->viewport.width)
-    width = render_target->viewport.width - x;
-  if ((y + height) > render_target->viewport.height)
-    height = render_target->viewport.height - y;
-
-  /* Add viewport offset.  */
-  x += render_target->viewport.x;
-  y += render_target->viewport.y;
-
-  /* Use vbeblit_info to encapsulate rendering.  */
-  target.mode_info = &render_target->mode_info;
-  target.data = render_target->data;
-
-  /* Draw glyph.  */
-  for (j = 0; j < height; j++)
-    for (i = 0; i < width; i++)
-      if ((glyph->bitmap[((i + x_offset) / 8) 
-                         + (j + y_offset) * (charwidth / 8)] 
-           & (1 << ((charwidth - (i + x_offset) - 1) % 8))))
-        set_pixel (&target, x+i, y+j, color);
-
-  return GRUB_ERR_NONE;
-}
-
 /* NOTE: This function assumes that given coordinates are within bounds of 
    handled data.  */
 static void
@@ -985,19 +1148,35 @@
   if (oper == GRUB_VIDEO_BLIT_REPLACE)
     {
       /* Try to figure out more optimized version for replace operator.  */
-      if (source->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_R8G8B8A8)
+      if (source->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_RGBA_8888)
         {
-          if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_R8G8B8A8)
-            {
-              grub_video_i386_vbeblit_R8G8B8X8_R8G8B8X8 (target, source,
+          if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_RGBA_8888)
+            {
+              grub_video_i386_vbeblit_direct32_copy (target, source,
+                                                     x, y, width, height,
+                                                     offset_x, offset_y);
+              return;
+            }
+
+          if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_BGRA_8888)
+            {
+              grub_video_i386_vbeblit_BGRX8888_RGBX8888 (target, source,
                                                          x, y, width, height,
                                                          offset_x, offset_y);
               return;
             }
 
-          if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_R8G8B8)
-            {
-              grub_video_i386_vbeblit_R8G8B8_R8G8B8X8 (target, source,
+          if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_BGR_888)
+            {
+              grub_video_i386_vbeblit_BGR888_RGBX8888 (target, source,
+                                                       x, y, width, height,
+                                                       offset_x, offset_y);
+              return;
+            }
+
+          if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_RGB_888)
+            {
+              grub_video_i386_vbeblit_RGB888_RGBX8888 (target, source,
                                                        x, y, width, height,
                                                        offset_x, offset_y);
               return;
@@ -1005,26 +1184,42 @@
 
           if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_INDEXCOLOR)
             {
-              grub_video_i386_vbeblit_index_R8G8B8X8 (target, source,
+              grub_video_i386_vbeblit_index_RGBX8888 (target, source,
                                                       x, y, width, height,
                                                       offset_x, offset_y);
               return;
             }
         }
 
-      if (source->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_R8G8B8)
+      if (source->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_RGB_888)
         {
-          if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_R8G8B8A8)
-            {
-              grub_video_i386_vbeblit_R8G8B8A8_R8G8B8 (target, source,
-                                                       x, y, width, height,
-                                                       offset_x, offset_y);
-              return;
-            }
-
-          if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_R8G8B8)
-            {
-              grub_video_i386_vbeblit_R8G8B8_R8G8B8 (target, source,
+          if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_BGRA_8888)
+            {
+              grub_video_i386_vbeblit_BGRA8888_RGB888 (target, source,
+                                                       x, y, width, height,
+                                                       offset_x, offset_y);
+              return;
+            }
+
+          if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_RGBA_8888)
+            {
+              grub_video_i386_vbeblit_RGBA8888_RGB888 (target, source,
+                                                       x, y, width, height,
+                                                       offset_x, offset_y);
+              return;
+            }
+
+          if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_BGR_888)
+            {
+              grub_video_i386_vbeblit_BGR888_RGB888 (target, source,
+                                                     x, y, width, height,
+                                                     offset_x, offset_y);
+              return;
+            }
+
+          if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_RGB_888)
+            {
+              grub_video_i386_vbeblit_RGB888_RGB888 (target, source,
                                                      x, y, width, height,
                                                      offset_x, offset_y);
               return;
@@ -1032,13 +1227,24 @@
 
           if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_INDEXCOLOR)
             {
-              grub_video_i386_vbeblit_index_R8G8B8 (target, source,
+              grub_video_i386_vbeblit_index_RGB888 (target, source,
                                                     x, y, width, height,
                                                     offset_x, offset_y);
               return;
             }
         }
 
+      if (source->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_BGRA_8888)
+        {
+          if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_BGRA_8888)
+            {
+              grub_video_i386_vbeblit_direct32_copy (target, source,
+                                                     x, y, width, height,
+                                                     offset_x, offset_y);
+              return;
+            }
+        }
+
       if (source->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_INDEXCOLOR)
         {
           if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_INDEXCOLOR)
@@ -1057,19 +1263,35 @@
   else
     {
       /* Try to figure out more optimized blend operator.  */
-      if (source->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_R8G8B8A8)
+      if (source->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_RGBA_8888)
       {
-        if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_R8G8B8A8)
-        {
-          grub_video_i386_vbeblit_R8G8B8A8_R8G8B8A8 (target, source,
-              x, y, width, height,
-              offset_x, offset_y);
-          return;
-        }
-
-        if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_R8G8B8)
-        {
-          grub_video_i386_vbeblit_R8G8B8_R8G8B8A8 (target, source,
+        if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_BGRA_8888)
+        {
+          grub_video_i386_vbeblit_BGRA8888_RGBA8888 (target, source,
+              x, y, width, height,
+              offset_x, offset_y);
+          return;
+        }
+
+        if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_RGBA_8888)
+        {
+          grub_video_i386_vbeblit_RGBA8888_RGBA8888 (target, source,
+              x, y, width, height,
+              offset_x, offset_y);
+          return;
+        }
+
+        if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_BGR_888)
+        {
+          grub_video_i386_vbeblit_BGR888_RGBA8888 (target, source,
+              x, y, width, height,
+              offset_x, offset_y);
+          return;
+        }
+
+        if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_RGB_888)
+        {
+          grub_video_i386_vbeblit_RGB888_RGBA8888 (target, source,
               x, y, width, height,
               offset_x, offset_y);
           return;
@@ -1077,26 +1299,42 @@
 
         if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_INDEXCOLOR)
         {
-          grub_video_i386_vbeblit_index_R8G8B8A8 (target, source,
+          grub_video_i386_vbeblit_index_RGBA8888 (target, source,
               x, y, width, height,
               offset_x, offset_y);
           return;
         }
       }
 
-      if (source->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_R8G8B8)
+      if (source->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_RGB_888)
       {
-        if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_R8G8B8A8)
-        {
-          grub_video_i386_vbeblit_R8G8B8A8_R8G8B8 (target, source,
-              x, y, width, height,
-              offset_x, offset_y);
-          return;
-        }
-
-        if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_R8G8B8)
-        {
-          grub_video_i386_vbeblit_R8G8B8_R8G8B8 (target, source,
+        if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_BGRA_8888)
+        {
+          grub_video_i386_vbeblit_BGRA8888_RGB888 (target, source,
+              x, y, width, height,
+              offset_x, offset_y);
+          return;
+        }
+
+        if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_RGBA_8888)
+        {
+          grub_video_i386_vbeblit_RGBA8888_RGB888 (target, source,
+              x, y, width, height,
+              offset_x, offset_y);
+          return;
+        }
+
+        if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_BGR_888)
+        {
+          grub_video_i386_vbeblit_BGR888_RGB888 (target, source,
+              x, y, width, height,
+              offset_x, offset_y);
+          return;
+        }
+
+        if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_RGB_888)
+        {
+          grub_video_i386_vbeblit_RGB888_RGB888 (target, source,
               x, y, width, height,
               offset_x, offset_y);
           return;
@@ -1104,7 +1342,7 @@
 
         if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_INDEXCOLOR)
         {
-          grub_video_i386_vbeblit_index_R8G8B8 (target, source,
+          grub_video_i386_vbeblit_index_RGB888 (target, source,
               x, y, width, height,
               offset_x, offset_y);
           return;
@@ -1219,6 +1457,77 @@
   return GRUB_ERR_NONE;
 }
 
+/* 
+ * Draw the specified glyph at (x, y).  The y coordinate designates the
+ * baseline of the character, while the x coordinate designates the left
+ * side location of the character. 
+ */
+static grub_err_t
+grub_video_vbe_blit_glyph (struct grub_font_glyph *glyph,
+                           grub_video_color_t color, 
+                           int left_x, int baseline_y)
+{
+  struct grub_video_bitmap glyph_bitmap;
+
+  /* Don't try to draw empty glyphs (U+0020, etc.). */
+  if (glyph->width == 0 || glyph->height == 0)
+    return GRUB_ERR_NONE;
+
+  glyph_bitmap.mode_info.width = glyph->width;
+  glyph_bitmap.mode_info.height = glyph->height;
+  glyph_bitmap.mode_info.mode_type = 
+    (1 << GRUB_VIDEO_MODE_TYPE_DEPTH_POS)
+    | GRUB_VIDEO_MODE_TYPE_1BIT_BITMAP;
+  glyph_bitmap.mode_info.blit_format = GRUB_VIDEO_BLIT_FORMAT_1BIT_PACKED;
+  glyph_bitmap.mode_info.bpp = 1;
+  glyph_bitmap.mode_info.bytes_per_pixel = 0;  /* Really 1 bit per pixel. */
+  glyph_bitmap.mode_info.pitch = glyph->width; /* Packed densely as bits. */
+  glyph_bitmap.mode_info.number_of_colors = 2;
+  glyph_bitmap.mode_info.bg_red = 0;
+  glyph_bitmap.mode_info.bg_green = 0;
+  glyph_bitmap.mode_info.bg_blue = 0;
+  glyph_bitmap.mode_info.bg_alpha = 0;
+  grub_video_vbe_unmap_color(color, 
+                             &glyph_bitmap.mode_info.fg_red,
+                             &glyph_bitmap.mode_info.fg_green,
+                             &glyph_bitmap.mode_info.fg_blue,
+                             &glyph_bitmap.mode_info.fg_alpha);
+  glyph_bitmap.data = glyph->bitmap;
+
+  int bitmap_left = left_x + glyph->offset_x;
+  int bitmap_bottom = baseline_y - glyph->offset_y;
+  int bitmap_top = bitmap_bottom - glyph->height;
+
+  return grub_video_vbe_blit_bitmap (&glyph_bitmap, GRUB_VIDEO_BLIT_BLEND,
+                                     bitmap_left, bitmap_top,
+                                     0, 0,
+                                     glyph->width, glyph->height);
+}
+
+static grub_err_t
+grub_video_vbe_draw_string (const char *str, grub_font_t font, 
+                            grub_video_color_t color, 
+                            int left_x, int baseline_y)
+{
+  grub_size_t len;
+  grub_size_t i;
+  int x;
+  struct grub_font_glyph *glyph;
+
+  len = grub_strlen (str);
+  x = left_x;
+  for (i = 0; i < len; i++)
+    {
+      glyph = grub_font_get_glyph (font, str[i]);
+      if (grub_video_vbe_blit_glyph (glyph, color, x, baseline_y) 
+          != GRUB_ERR_NONE)
+        return grub_errno;
+      x += glyph->device_width;
+    }
+
+  return GRUB_ERR_NONE;
+}
+
 static grub_err_t
 grub_video_vbe_blit_render_target (struct grub_video_render_target *source,
                                    enum grub_video_blit_operators oper,
@@ -1403,10 +1712,13 @@
   return GRUB_ERR_NONE;
 }
 
-static grub_err_t 
+static grub_err_t
 grub_video_vbe_swap_buffers (void)
 {
-  /* TODO: Implement buffer swapping.  */
+  if (doublebuf_state.update_screen () != 0)
+    return grub_error (GRUB_ERR_INVALID_COMMAND,
+                       "Double buffer update failed");
+
   return GRUB_ERR_NONE;
 }
 
@@ -1505,17 +1817,13 @@
 static grub_err_t
 grub_video_vbe_set_active_render_target (struct grub_video_render_target *target)
 {
-  if (target == GRUB_VIDEO_RENDER_TARGET_FRONT_BUFFER)
+  if (target == GRUB_VIDEO_RENDER_TARGET_DISPLAY)
     {
       render_target = &framebuffer.render_target;
-
+      
       return GRUB_ERR_NONE;
     }
 
-  if (target == GRUB_VIDEO_RENDER_TARGET_BACK_BUFFER)
-    return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, 
-                       "double buffering not implemented yet.");
-
   if (! target->data)
     return grub_error (GRUB_ERR_BAD_ARGUMENT, 
                        "invalid render target given.");
@@ -1551,6 +1859,7 @@
     .unmap_color = grub_video_vbe_unmap_color,
     .fill_rect = grub_video_vbe_fill_rect,
     .blit_glyph = grub_video_vbe_blit_glyph,
+    .draw_string = grub_video_vbe_draw_string,
     .blit_bitmap = grub_video_vbe_blit_bitmap,
     .blit_render_target = grub_video_vbe_blit_render_target,
     .scroll = grub_video_vbe_scroll,

=== modified file 'video/i386/pc/vbeblit.c'
--- video/i386/pc/vbeblit.c	2008-01-01 12:02:07 +0000
+++ video/i386/pc/vbeblit.c	2008-07-09 17:27:53 +0000
@@ -35,7 +35,343 @@
 #include <grub/video.h>
 
 void
-grub_video_i386_vbeblit_R8G8B8A8_R8G8B8A8 (struct grub_video_i386_vbeblit_info *dst,
+grub_video_i386_vbeblit_BGRX8888_RGBX8888 (struct grub_video_i386_vbeblit_info
+                                           *dst,
+                                           struct grub_video_i386_vbeblit_info
+                                           *src, int x, int y, int width,
+                                           int height, int offset_x,
+                                           int offset_y)
+{
+  int i;
+  int j;
+  grub_uint8_t *srcptr;
+  grub_uint8_t *dstptr;
+  unsigned srcrowskip;
+  unsigned dstrowskip;
+
+  /* Calculate the number of bytes to advance from the end of one line
+   * to the beginning of the next line. */
+  srcrowskip =
+    src->mode_info->pitch - src->mode_info->bytes_per_pixel * width;
+  dstrowskip =
+    dst->mode_info->pitch - dst->mode_info->bytes_per_pixel * width;
+
+  srcptr = (grub_uint8_t *) get_data_ptr (src, offset_x, offset_y);
+  dstptr = (grub_uint8_t *) get_data_ptr (dst, x, y);
+  for (j = 0; j < height; j++)
+    {
+      for (i = 0; i < width; i++)
+        {
+          grub_uint8_t r = *srcptr++;
+          grub_uint8_t g = *srcptr++;
+          grub_uint8_t b = *srcptr++;
+          grub_uint8_t a = *srcptr++;
+
+          *dstptr++ = b;
+          *dstptr++ = g;
+          *dstptr++ = r;
+          *dstptr++ = a;
+        }
+      srcptr += srcrowskip;
+      dstptr += dstrowskip;
+    }
+}
+
+void
+grub_video_i386_vbeblit_BGRA8888_RGB888 (struct grub_video_i386_vbeblit_info
+                                         *dst,
+                                         struct grub_video_i386_vbeblit_info
+                                         *src, int x, int y, int width,
+                                         int height, int offset_x,
+                                         int offset_y)
+{
+  int i;
+  int j;
+  grub_uint8_t *srcptr;
+  grub_uint8_t *dstptr;
+  unsigned srcrowskip;
+  unsigned dstrowskip;
+
+  /* Calculate the number of bytes to advance from the end of one line
+   * to the beginning of the next line. */
+  srcrowskip =
+    src->mode_info->pitch - src->mode_info->bytes_per_pixel * width;
+  dstrowskip =
+    dst->mode_info->pitch - dst->mode_info->bytes_per_pixel * width;
+
+  srcptr = (grub_uint8_t *) get_data_ptr (src, offset_x, offset_y);
+  dstptr = (grub_uint8_t *) get_data_ptr (dst, x, y);
+  for (j = 0; j < height; j++)
+    {
+      for (i = 0; i < width; i++)
+        {
+          grub_uint8_t r = *srcptr++;
+          grub_uint8_t g = *srcptr++;
+          grub_uint8_t b = *srcptr++;
+
+          *dstptr++ = b;
+          *dstptr++ = g;
+          *dstptr++ = r;
+          *dstptr++ = 255;      /* Alpha component: Set opaque. */
+        }
+      srcptr += srcrowskip;
+      dstptr += dstrowskip;
+    }
+}
+
+void
+grub_video_i386_vbeblit_BGR888_RGB888 (struct grub_video_i386_vbeblit_info
+                                         *dst,
+                                         struct grub_video_i386_vbeblit_info
+                                         *src, int x, int y, int width,
+                                         int height, int offset_x,
+                                         int offset_y)
+{
+  int i;
+  int j;
+  grub_uint8_t *srcptr;
+  grub_uint8_t *dstptr;
+  unsigned srcrowskip;
+  unsigned dstrowskip;
+
+  /* Calculate the number of bytes to advance from the end of one line
+   * to the beginning of the next line. */
+  srcrowskip =
+    src->mode_info->pitch - src->mode_info->bytes_per_pixel * width;
+  dstrowskip =
+    dst->mode_info->pitch - dst->mode_info->bytes_per_pixel * width;
+
+  srcptr = (grub_uint8_t *) get_data_ptr (src, offset_x, offset_y);
+  dstptr = (grub_uint8_t *) get_data_ptr (dst, x, y);
+  for (j = 0; j < height; j++)
+    {
+      for (i = 0; i < width; i++)
+        {
+          grub_uint8_t r = *srcptr++;
+          grub_uint8_t g = *srcptr++;
+          grub_uint8_t b = *srcptr++;
+
+          *dstptr++ = b;
+          *dstptr++ = g;
+          *dstptr++ = r;
+        }
+      srcptr += srcrowskip;
+      dstptr += dstrowskip;
+    }
+}
+
+void
+grub_video_i386_vbeblit_BGRA8888_RGBA8888 (struct grub_video_i386_vbeblit_info *dst,
+                                           struct grub_video_i386_vbeblit_info *src,
+                                           int x, int y, int width, int height,
+                                           int offset_x, int offset_y)
+{
+  grub_uint32_t *srcptr;
+  grub_uint32_t *dstptr;
+  unsigned srcrowskip;
+  unsigned dstrowskip;
+  int i;
+  int j;
+
+  /* Calculate the number of bytes to advance from the end of one line
+   * to the beginning of the next line. */
+  srcrowskip =
+    src->mode_info->pitch - src->mode_info->bytes_per_pixel * width;
+  dstrowskip =
+    dst->mode_info->pitch - dst->mode_info->bytes_per_pixel * width;
+
+  srcptr = (grub_uint32_t *) get_data_ptr (src, offset_x, offset_y);
+  dstptr = (grub_uint32_t *) get_data_ptr (dst, x, y);
+
+  for (j = 0; j < height; j++)
+    {
+      for (i = 0; i < width; i++)
+        {
+          grub_uint32_t color;
+          unsigned int sr;
+          unsigned int sg;
+          unsigned int sb;
+          unsigned int a;
+          unsigned int dr;
+          unsigned int dg;
+          unsigned int db;
+
+          color = *srcptr++;
+
+          a = color >> 24;
+
+          if (a == 0)
+            {
+              /* Skip transparent source pixels.  */
+              dstptr++;
+              continue;
+            }
+
+          sr = (color >> 0) & 0xFF;
+          sg = (color >> 8) & 0xFF;
+          sb = (color >> 16) & 0xFF;
+
+          if (a == 255)
+            {
+              /* Opaque pixel shortcut.  */
+              dr = sr;
+              dg = sg;
+              db = sb;
+            }
+          else
+            {
+              /* General pixel color blending.  */
+              color = *dstptr;
+
+              dr = (color >> 16) & 0xFF;
+              dr = (dr * (255 - a) + sr * a) / 255;
+              dg = (color >> 8) & 0xFF;
+              dg = (dg * (255 - a) + sg * a) / 255;
+              db = (color >> 0) & 0xFF;
+              db = (db * (255 - a) + sb * a) / 255;
+            }
+
+          color = (a << 24) | (dr << 16) | (dg << 8) | db;
+
+          *dstptr++ = color;
+        }
+
+      srcptr = (grub_uint32_t *) (((grub_uint8_t *) srcptr) + srcrowskip);
+      dstptr = (grub_uint32_t *) (((grub_uint8_t *) dstptr) + dstrowskip);
+    }
+}
+
+void
+grub_video_i386_vbeblit_BGR888_RGBA8888 (struct grub_video_i386_vbeblit_info *dst,
+                                          struct grub_video_i386_vbeblit_info *src,
+                                          int x, int y, int width, int height,
+                                          int offset_x, int offset_y)
+{
+  grub_uint32_t *srcptr;
+  grub_uint8_t *dstptr;
+  unsigned srcrowskip;
+  unsigned dstrowskip;
+  int i;
+  int j;
+
+  /* Calculate the number of bytes to advance from the end of one line
+   * to the beginning of the next line. */
+  srcrowskip =
+    src->mode_info->pitch - src->mode_info->bytes_per_pixel * width;
+  dstrowskip =
+    dst->mode_info->pitch - dst->mode_info->bytes_per_pixel * width;
+
+  srcptr = (grub_uint32_t *) get_data_ptr (src, offset_x, offset_y);
+  dstptr = (grub_uint8_t *) get_data_ptr (dst, x, y);
+
+  for (j = 0; j < height; j++)
+    {
+      for (i = 0; i < width; i++)
+        {
+          grub_uint32_t color;
+          unsigned int sr;
+          unsigned int sg;
+          unsigned int sb;
+          unsigned int a;
+          unsigned int dr;
+          unsigned int dg;
+          unsigned int db;
+
+          color = *srcptr++;
+
+          a = color >> 24;
+
+          if (a == 0)
+            {
+              /* Skip transparent source pixels.  */
+              dstptr += 3;
+              continue;
+            }
+
+          sr = (color >> 0) & 0xFF;
+          sg = (color >> 8) & 0xFF;
+          sb = (color >> 16) & 0xFF;
+
+          if (a == 255)
+            {
+              /* Opaque pixel shortcut.  */
+              dr = sr;
+              dg = sg;
+              db = sb;
+            }
+          else
+            {
+              /* General pixel color blending.  */
+              color = *dstptr;
+
+              db = dstptr[0];
+              db = (db * (255 - a) + sb * a) / 255;
+              dg = dstptr[1];
+              dg = (dg * (255 - a) + sg * a) / 255;
+              dr = dstptr[2];
+              dr = (dr * (255 - a) + sr * a) / 255;
+            }
+
+          *dstptr++ = db;
+          *dstptr++ = dg;
+          *dstptr++ = dr;
+        }
+
+      srcptr = (grub_uint32_t *) (((grub_uint8_t *) srcptr) + srcrowskip);
+      dstptr += dstrowskip;
+    }
+}
+
+void
+grub_video_i386_vbeblit_BGR888_RGBX8888 (struct grub_video_i386_vbeblit_info *dst,
+                                          struct grub_video_i386_vbeblit_info *src,
+                                          int x, int y, int width, int height,
+                                          int offset_x, int offset_y)
+{
+  grub_uint32_t *srcptr;
+  grub_uint8_t *dstptr;
+  unsigned srcrowskip;
+  unsigned dstrowskip;
+  int i;
+  int j;
+
+  /* Calculate the number of bytes to advance from the end of one line
+   * to the beginning of the next line. */
+  srcrowskip =
+    src->mode_info->pitch - src->mode_info->bytes_per_pixel * width;
+  dstrowskip =
+    dst->mode_info->pitch - dst->mode_info->bytes_per_pixel * width;
+
+  srcptr = (grub_uint32_t *) get_data_ptr (src, offset_x, offset_y);
+  dstptr = (grub_uint8_t *) get_data_ptr (dst, x, y);
+
+  for (j = 0; j < height; j++)
+    {
+      for (i = 0; i < width; i++)
+        {
+          grub_uint32_t color;
+          grub_uint8_t sr;
+          grub_uint8_t sg;
+          grub_uint8_t sb;
+
+          color = *srcptr++;
+
+          sr = (color >> 0) & 0xFF;
+          sg = (color >> 8) & 0xFF;
+          sb = (color >> 16) & 0xFF;
+
+          *dstptr++ = sb;
+          *dstptr++ = sg;
+          *dstptr++ = sr;
+        }
+
+      srcptr = (grub_uint32_t *) (((grub_uint8_t *) srcptr) + srcrowskip);
+      dstptr += dstrowskip;
+    }
+}
+
+void
+grub_video_i386_vbeblit_RGBA8888_RGBA8888 (struct grub_video_i386_vbeblit_info *dst,
                                            struct grub_video_i386_vbeblit_info *src,
                                            int x, int y, int width, int height,
                                            int offset_x, int offset_y)
@@ -101,10 +437,10 @@
 }
 
 void
-grub_video_i386_vbeblit_R8G8B8X8_R8G8B8X8 (struct grub_video_i386_vbeblit_info *dst,
-                                           struct grub_video_i386_vbeblit_info *src,
-                                           int x, int y, int width, int height,
-                                           int offset_x, int offset_y)
+grub_video_i386_vbeblit_direct32_copy (struct grub_video_i386_vbeblit_info *dst,
+                                       struct grub_video_i386_vbeblit_info *src,
+                                       int x, int y, int width, int height,
+                                       int offset_x, int offset_y)
 {
   int j;
   grub_uint32_t *srcptr;
@@ -126,7 +462,7 @@
 }
 
 void
-grub_video_i386_vbeblit_R8G8B8_R8G8B8A8 (struct grub_video_i386_vbeblit_info *dst,
+grub_video_i386_vbeblit_RGB888_RGBA8888 (struct grub_video_i386_vbeblit_info *dst,
                                          struct grub_video_i386_vbeblit_info *src,
                                          int x, int y, int width, int height,
                                          int offset_x, int offset_y)
@@ -193,7 +529,7 @@
 }
 
 void
-grub_video_i386_vbeblit_R8G8B8_R8G8B8X8 (struct grub_video_i386_vbeblit_info *dst,
+grub_video_i386_vbeblit_RGB888_RGBX8888 (struct grub_video_i386_vbeblit_info *dst,
                                          struct grub_video_i386_vbeblit_info *src,
                                          int x, int y, int width, int height,
                                          int offset_x, int offset_y)
@@ -231,7 +567,7 @@
 }
 
 void
-grub_video_i386_vbeblit_index_R8G8B8A8 (struct grub_video_i386_vbeblit_info *dst,
+grub_video_i386_vbeblit_index_RGBA8888 (struct grub_video_i386_vbeblit_info *dst,
                                         struct grub_video_i386_vbeblit_info *src,
                                         int x, int y, int width, int height,
                                         int offset_x, int offset_y)
@@ -295,7 +631,7 @@
 }
 
 void
-grub_video_i386_vbeblit_index_R8G8B8X8 (struct grub_video_i386_vbeblit_info *dst,
+grub_video_i386_vbeblit_index_RGBX8888 (struct grub_video_i386_vbeblit_info *dst,
                                         struct grub_video_i386_vbeblit_info *src,
                                         int x, int y, int width, int height,
                                         int offset_x, int offset_y)
@@ -332,7 +668,7 @@
 }
 
 void
-grub_video_i386_vbeblit_R8G8B8A8_R8G8B8 (struct grub_video_i386_vbeblit_info *dst,
+grub_video_i386_vbeblit_RGBA8888_RGB888 (struct grub_video_i386_vbeblit_info *dst,
                                          struct grub_video_i386_vbeblit_info *src,
                                          int x, int y, int width, int height,
                                          int offset_x, int offset_y)
@@ -368,7 +704,7 @@
 }
 
 void
-grub_video_i386_vbeblit_R8G8B8_R8G8B8 (struct grub_video_i386_vbeblit_info *dst,
+grub_video_i386_vbeblit_RGB888_RGB888 (struct grub_video_i386_vbeblit_info *dst,
                                        struct grub_video_i386_vbeblit_info *src,
                                        int x, int y, int width, int height,
                                        int offset_x, int offset_y)
@@ -393,7 +729,7 @@
 }
 
 void
-grub_video_i386_vbeblit_index_R8G8B8 (struct grub_video_i386_vbeblit_info *dst,
+grub_video_i386_vbeblit_index_RGB888 (struct grub_video_i386_vbeblit_info *dst,
                                       struct grub_video_i386_vbeblit_info *src,
                                       int x, int y, int width, int height,
                                       int offset_x, int offset_y)

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

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

=== modified file 'video/readers/jpeg.c'
--- video/readers/jpeg.c	2008-08-01 03:06:55 +0000
+++ video/readers/jpeg.c	2008-08-03 03:57:46 +0000
@@ -1,20 +1,20 @@
 /*
- *  GRUB  --  GRand Unified Bootloader
- *  Copyright (C) 2008  Free Software Foundation, Inc.
- *
- *  GRUB is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  GRUB is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
- */
+*  GRUB  --  GRand Unified Bootloader
+*  Copyright (C) 2008  Free Software Foundation, Inc.
+*
+*  GRUB is free software: you can redistribute it and/or modify
+*  it under the terms of the GNU General Public License as published by
+*  the Free Software Foundation, either version 3 of the License, or
+*  (at your option) any later version.
+*
+*  GRUB is distributed in the hope that it will be useful,
+*  but WITHOUT ANY WARRANTY; without even the implied warranty of
+*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*  GNU General Public License for more details.
+*
+*  You should have received a copy of the GNU General Public License
+*  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+*/
 
 #include <grub/bitmap.h>
 #include <grub/types.h>
@@ -542,7 +542,7 @@
 
   if (grub_video_bitmap_create (data->bitmap, data->image_width,
 				data->image_height,
-				GRUB_VIDEO_BLIT_FORMAT_R8G8B8))
+				GRUB_VIDEO_BLIT_FORMAT_RGB_888))
     return grub_errno;
 
   data->bit_mask = 0x0;

=== modified file 'video/readers/png.c'
--- video/readers/png.c	2008-08-01 03:06:55 +0000
+++ video/readers/png.c	2008-08-03 04:00:22 +0000
@@ -231,7 +231,7 @@
     {
       if (grub_video_bitmap_create (data->bitmap, data->image_width,
 				    data->image_height,
-				    GRUB_VIDEO_BLIT_FORMAT_R8G8B8))
+				    GRUB_VIDEO_BLIT_FORMAT_RGB_888))
 	return grub_errno;
       data->bpp = 3;
     }
@@ -239,7 +239,7 @@
     {
       if (grub_video_bitmap_create (data->bitmap, data->image_width,
 				    data->image_height,
-				    GRUB_VIDEO_BLIT_FORMAT_R8G8B8A8))
+				    GRUB_VIDEO_BLIT_FORMAT_RGBA_8888))
 	return grub_errno;
       data->bpp = 4;
     }

=== modified file 'video/readers/tga.c'
--- video/readers/tga.c	2008-08-01 03:06:55 +0000
+++ video/readers/tga.c	2008-08-03 03:57:46 +0000
@@ -397,7 +397,7 @@
     {
       grub_video_bitmap_create (bitmap, header.image_width,
                                 header.image_height,
-                                GRUB_VIDEO_BLIT_FORMAT_R8G8B8A8);
+                                GRUB_VIDEO_BLIT_FORMAT_RGBA_8888);
       if (grub_errno != GRUB_ERR_NONE)
         {
           grub_file_close (file);
@@ -420,7 +420,7 @@
     {
       grub_video_bitmap_create (bitmap, header.image_width,
                                 header.image_height,
-                                GRUB_VIDEO_BLIT_FORMAT_R8G8B8);
+                                GRUB_VIDEO_BLIT_FORMAT_RGB_888);
       if (grub_errno != GRUB_ERR_NONE)
         {
           grub_file_close (file);

=== added file 'video/setmode.c'
--- video/setmode.c	1970-01-01 00:00:00 +0000
+++ video/setmode.c	2008-07-19 19:31:46 +0000
@@ -0,0 +1,249 @@
+/* video/setmode.c - Smart video mode selection based on preferences. */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2006,2007,2008  Free Software Foundation, Inc.
+ *
+ *  GRUB is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/video.h>
+#include <grub/types.h>
+#include <grub/mm.h>
+#include <grub/misc.h>
+
+/* Set the video mode based on the preferred modes specified in MODE_LIST in
+   the form: <width>x<height>[x<depth>][;...]
+
+   For example:  640x480;800x600x8;400x300x32
+
+   If MODE_LIST is null, or no modes in it are usable, then DEFAULT_WIDTH and
+   DEFAULT_HEIGHT are used to set the mode.  The MODE_FLAGS argument determines
+   the video mode flags such as double buffering that are used.  */
+
+grub_err_t
+grub_video_setup_preferred_mode (const char *mode_list, int mode_flags,
+                                 int default_width, int default_height)
+{
+  int mode_found = 0;
+
+  if (mode_list != NULL)
+    {
+      /* Take copy of mode_list as we don't want tat.  */
+      char *const modes_copy = grub_strdup (mode_list);
+      if (modes_copy == NULL)
+        return grub_errno;
+
+      /* Initialize next mode.  */
+      char *next_mode = modes_copy;
+
+      /* Loop until all modes has been tested out.  */
+      while ((next_mode != NULL) && !mode_found)
+        {
+          /* Use last next_mode as current mode.  */
+          char *tmp = next_mode;
+
+          int width = -1;
+          int height = -1;
+          int depth = -1;
+
+          /* Save position of next mode and separate modes.  */
+          next_mode = grub_strchr(next_mode, ';');
+          if (next_mode)
+            {
+              *next_mode = 0;
+              next_mode++;
+            }
+
+          /* Skip whitespace.  */
+          while (grub_isspace (*tmp))
+            tmp++;
+
+          /* Initialize token holders.  */
+          char *current_mode = tmp;
+          char *param = tmp;
+          char *value = NULL;
+
+          /* Parse <width>x<height>[x<depth>]*/
+
+          /* Find width value.  */
+          value = param;
+          param = grub_strchr(param, 'x');
+          if (param == NULL)
+            {
+              grub_err_t rc;
+
+              /* First setup error message.  */
+              rc = grub_error (GRUB_ERR_BAD_ARGUMENT, 
+                               "Invalid mode: %s\n",
+                               current_mode);
+
+              /* Free memory before returning.  */
+              grub_free (modes_copy);
+
+              return rc;
+            }
+
+          *param = 0;
+          param++;
+
+          width = grub_strtoul (value, 0, 0);
+          if (grub_errno != GRUB_ERR_NONE)
+            {
+              grub_err_t rc;
+
+              /* First setup error message.  */
+              rc = grub_error (GRUB_ERR_BAD_ARGUMENT, 
+                               "Invalid mode: %s\n",
+                               current_mode);
+
+              /* Free memory before returning.  */
+              grub_free (modes_copy);
+
+              return rc;
+            }
+
+          /* Find height value.  */
+          value = param;
+          param = grub_strchr(param, 'x');
+          if (param == NULL)
+            {
+              height = grub_strtoul (value, 0, 0);
+              if (grub_errno != GRUB_ERR_NONE)
+                {
+                  grub_err_t rc;
+
+                  /* First setup error message.  */
+                  rc = grub_error (GRUB_ERR_BAD_ARGUMENT, 
+                                   "Invalid mode: %s\n",
+                                   current_mode);
+
+                  /* Free memory before returning.  */
+                  grub_free (modes_copy);
+
+                  return rc;
+                }
+            }
+          else
+            {
+              /* We have optional color depth value.  */
+              *param = 0;
+              param++;
+
+              height = grub_strtoul (value, 0, 0);
+              if (grub_errno != GRUB_ERR_NONE)
+                {
+                  grub_err_t rc;
+
+                  /* First setup error message.  */
+                  rc = grub_error (GRUB_ERR_BAD_ARGUMENT, 
+                                   "Invalid mode: %s\n",
+                                   current_mode);
+
+                  /* Free memory before returning.  */
+                  grub_free (modes_copy);
+
+                  return rc;
+                }
+
+              /* Convert color depth value.  */
+              value = param;
+              depth = grub_strtoul (value, 0, 0);
+              if (grub_errno != GRUB_ERR_NONE)
+                {
+                  grub_err_t rc;
+
+                  /* First setup error message.  */
+                  rc = grub_error (GRUB_ERR_BAD_ARGUMENT, 
+                                   "Invalid mode: %s\n",
+                                   current_mode);
+
+                  /* Free memory before returning.  */
+                  grub_free (modes_copy);
+
+                  return rc;
+                }
+            }
+
+          /* Try out video mode.  */
+
+          int flags = mode_flags;
+          /* If we have <= 8 bits, assume it is an indexed color mode.  */
+          if ((depth <= 8) && (depth != -1))
+            flags |= GRUB_VIDEO_MODE_TYPE_INDEX_COLOR;
+
+          /* We have > 8 bits; assume that it is RGB color mode.  */
+          if (depth > 8)
+            flags |= GRUB_VIDEO_MODE_TYPE_RGB;
+
+          /* If user requested specific depth, pass the request to driver.  */
+          if (depth != -1)
+            flags |= (depth << GRUB_VIDEO_MODE_TYPE_DEPTH_POS)
+              & GRUB_VIDEO_MODE_TYPE_DEPTH_MASK;
+
+          /* Try to initialize requested mode.  Ignore any errors.  */
+          grub_error_push ();
+          if (grub_video_setup (width, height, flags) != GRUB_ERR_NONE)
+            {
+              grub_error_pop ();
+              continue;
+            }
+
+          /* Figure out what mode we ended up.  */
+          struct grub_video_mode_info mode_info;
+          if (grub_video_get_info (&mode_info) != GRUB_ERR_NONE)
+            {
+              /* Couldn't get video mode info, restore old mode
+                 and continue to next one.  */
+              grub_error_pop ();
+
+              grub_video_restore ();
+              continue;
+            }
+
+          /* Restore state of error stack.  */
+          grub_error_pop ();
+
+          /* Mode found!  Exit loop.  */
+          mode_found = 1;
+        }
+
+      /* Free memory.  */
+      grub_free (modes_copy);
+    }
+
+  if (!mode_found)
+    {
+      /* No gfxmode variable set, or no listed mode was supported.
+         Use the caller-specified defaults.  */
+      int flags = mode_flags | GRUB_VIDEO_MODE_TYPE_RGB;
+
+      /* Initialize user requested mode.  */
+      if (grub_video_setup (default_width, default_height, flags) 
+          != GRUB_ERR_NONE)
+        return grub_errno;
+
+      /* Figure out what mode we ended up.  */
+      struct grub_video_mode_info mode_info;
+      if (grub_video_get_info (&mode_info) != GRUB_ERR_NONE)
+        grub_video_restore ();
+      else
+        mode_found = 1;
+    }
+
+  if (!mode_found)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT,
+                       "No suitable mode found.");
+
+  return (grub_errno = GRUB_ERR_NONE);
+}

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

# Begin bundle
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
x7elnz41dYxKpPbJtdN2jjpuuya4ZsWCd9t3DG2jB9vhm5bPRsbNWTVuYPVUplNFV3zEzelmbQx0
H4qqvAemT19WLpWWa5ItilF6ngLbOKVDNq6M3jYPzjWMhal5MTIocQc28zKzmKSNTEumJ9dsjdos
2fWB0dJgqsyf78m3fDT7XK1zntN1WGE9ZPFTZz1SmS7xR07dMllnLMxQNCxYiVIlty7Kqrg2CuFV
Uya5BcFq6DOzVI721uqzUZUbtFFUvbtnOHTdo0Pls28MKqOacy3zeujtqzcuXDFmu6b9l2qqm9L+
mrhb2K15e2LF0YPTdk9uW7Rdy8bu0ozMTLaRgkBgYwGKjjAc9Zsz4ucuGxkyl2cnKcN5blgQWx3d
hABlscDcj7VczQf7jBQgJW/jNbc770hO3hs1TrBc9PHTZgyYr5qnLtV21PF23RknavdjRymyjRq9
J46UeOXb0wbPHbaijQ2YuRuwPwOkD7SCfD29r2PcNt83tdI+4aavrTCOU4DsU0otJFzSvx1visI+
gQE8gLHHF8urHgAR4Cw/x+UFJJSSdP4o+9Vvtym/rTtkyysmz7/dPbw7dtHGJuwSkzvlg+XyyfLB
RR03MXjBg6fdcUfK7JLtnzJP7GsJCOaCP1/WPefKlG7dm9Pu8Z6a+DNsuus1YNWDEs+jSJ6ZF1Gb
dsbtVGC7dismBw1bPn7fDlw5dM398nLdgzcuHTlLPGDxmeFhy88zZoXSjpNHhmsxUVartmCrdwqu
MFlmSjI338XdKOHJyyy+nSUXYtnbVyo3WZOV1HSpy0M2qrxqxbt2jdR2Xf2yJrw5Xd9+ZqZaFGTJ
ms4VYrmKjpZ0quydGjJo7bOnp6aNWyir00bOVjkxctW7JolW6q6zBxEyWVuzbLr5KN2ayyzlVdyx
LtFnCrdu4LNWbpddVwzRowatjVVZi5bLNXC5ddmzasmTNc1dNFWT+KP7WO++qdvFjCmGm6m9bJyi
7sUYqO1GVPM6yllLaKNnjvVTV4zGbRios1ZGLVFh799lmbB2Wbt27VksuycFlzBy0M04NDRg7anj
R1107iJPv69MmDksvKOHa7Z6ZjVy443axMlXTh20Z52cqF2KxwssyXUFHjl6ZM2SdNtuWp4ycN27
Rk+E6I/VE/mQ/BH9UYB6C6NZJ9on7pP8gxhd/Ik+RNQ/vR5E+yPzR9SfzDMjdF0fCPmHeCeJHhR0
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
