# Bazaar merge directive format 2 (Bazaar 0.90)
# revision_id: colin@gibibit.com-20080724055504-ril13j8f5vdkel89
# target_branch: ../../repo/trunk-clean
# testament_sha1: 9035bd89bd8d56da840e7e32479d8d691fc3b4d3
# timestamp: 2008-07-23 22:57:45 -0700
# source_branch: http://grub.gibibit.com/bzr/trunk-clean
# base_revision_id: colin@gibibit.com-20080722152415-23v9fixusetjqy23
# 
# 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-07-21 09:40:01 +0000
+++ ChangeLog	2008-07-24 05:54:33 +0000
@@ -1,3 +1,17 @@
+2008-07-22  Colin D Bennett <colin@gibibit.com>
+
+	* include/grub/file.h (grub_file_buffering_enabled): New global
+	variable.
+
+	* kern/file.c (BUFFER_SIZE): New constant.
+	(grub_file_buffering_enabled): New global variable.
+	(grub_file_open): Allocate file readahead buffer.
+	(grub_file_close): Free file readahead buffer.
+	(grub_file_read): Use file readahead buffer.
+
+	* kern/main.c (grub_main): Enable file readahead buffering when it is
+	safe to do so.
+
 2008-07-21  Bean  <bean123ch@gmail.com>
 
 	* kern/i386/pc/startup.S (gate_a20_try_bios): Change test order for
@@ -337,6 +351,73 @@
 
 	* disk/ata.c: Use named constants for status bits.
 
+2008-07-04  Colin D Bennett <colin@gibibit.com>
+
+	High resolution timer support.  Implemented for i386 CPU using TSC.
+	Extracted generic grub_millisleep() so it's linked in only as needed.
+	This requires a Pentium compatible CPU; currently the code does not
+	check for this (so it will fail on 386 and 486 machines).
+
+	* conf/i386-efi.rmk: Added TSC high resolution time module, link with
+	generic grub_millisleep() function.
+
+	* conf/i386-pc.rmk: Likewise.
+
+	* conf/sparc64-ieee1275.rmk: Add kern/generic/millisleep.c and
+	kern/generic/get_time_ms.c to kernel, to use generic time functions.
+
+	* conf/powerpc-ieee1275.rmk: Add kern/generic/millisleep.c to kernel,
+	to use generic grub_millisleep() function.
+
+	* conf/i386-linuxbios.rmk: Added kern/generic/get_time_ms.c to the
+	kernel.
+
+	* kern/generic/get_time_ms.c (grub_get_time_ms): New file.  Platform
+	independent implementation of grub_get_time_ms() using the RTC that
+	can be linked into a platform's kernel when it does not implement its
+	own specialized grub_get_time_ms() function.
+
+	* kern/generic/millisleep.c (grub_millisleep): New file.  Extracted
+	from grub_millisleep_generic() in kern/misc.c and renamed.  Changed it
+	to use grub_get_time_ms() instead of grub_get_rtc() for better
+	precision on when high resolution time is available.
+
+	* kern/misc.c (grub_millisleep_generic): Deleted.  Moved to
+	kern/generic/millisleep.c so that it is only included in the kernel
+	image when a platform does not define a specialized version.
+
+	* commands/sleep.c (grub_interruptible_millisleep): Uses
+	grub_get_time_ms() instead of grub_get_rtc() to stay in sync with
+	grub_millisleep() from kern/generic/millisleep.c.
+
+	* include/grub/i386/tsc.h (grub_get_tsc): New file.  Inline function
+	grub_get_tsc() uses x86 RDTSC instruction (available on Pentium+ CPUs)
+	to read the counter value for the TSC.
+	(grub_tsc_calibrate): Declare this function for grub_machine_init().
+
+	* kern/i386/tsc.c (grub_get_time_ms): x86 TSC support providing a high
+	resolution clock.
+	(grub_tsc_calibrate): New function to calibrate the TSC using RTC.
+
+	* include/grub/time.h (grub_get_time_ms): Added grub_get_time_ms()
+	function to return the current time in millseconds since the epoch.
+	This supports higher resolution time than grub_get_rtc() on some
+	platforms such as i386-pc, where the RTC has only about 1/18 s
+	precision but a higher precision timer such as the TSC is available.
+
+	* kern/i386/efi/init.c (grub_millisleep): Deleted.  Don't define
+	grub_millisleep() -- it just called grub_millisleep_generic() but now
+	it is linked to kern/generic/millisleep.c for the implementation.
+
+	* kern/sparc64/ieee1275/init.c (grub_millisleep): Deleted.
+
+	* kern/i386/pc/init.c (grub_machine_init): Call grub_tsc_calibrate().
+	(grub_millisleep): Deleted.
+
+	* kern/ieee1275/init.c (grub_millisleep): Deleted.
+	(grub_get_rtc): Now calls grub_get_time_ms(), which does the real
+	work.
+
 2008-07-04  Pavel Roskin  <proski@gnu.org>
 
 	* kern/i386/linuxbios/init.c (grub_machine_init): Cast addr to

=== modified file 'Makefile.in'
--- Makefile.in	2008-07-17 14:05:12 +0000
+++ Makefile.in	2008-07-19 21:39:24 +0000
@@ -85,6 +85,39 @@
 YACC = @YACC@
 UNIFONT_HEX = @UNIFONT_HEX@
 
+### Pretty output control ###
+# Set up compiler and linker commands that either is quiet (does not print
+# the command line being executed) or verbose (print the command line).
+_CC := $(CC)
+_TARGET_CC := $(TARGET_CC)
+_STRIP := $(STRIP)
+_GENMODSRC := sh $(srcdir)/genmodsrc.sh
+ifeq ($(V),1)
+ override V_PREFIX :=
+ override CC = $(_CC)
+ override TARGET_CC = $(_CC)
+ override STRIP = $(_STRIP)
+ override GENMODSRC = $(_GENMODSRC)
+ override INFO_GENCMDLIST =
+ override INFO_GENFSLIST =
+ override INFO_GENPARTMAPLIST =
+ override INFO_GEN_FINAL_COMMAND_LIST =
+ override INFO_GEN_FINAL_FS_LIST =
+ override INFO_GEN_FINAL_PARTMAP_LIST =
+else
+ override V_PREFIX := @
+ override CC = @echo "COMPILE         $<"; $(_CC)
+ override TARGET_CC = @echo "COMPILE(TARGET) $<"; $(_TARGET_CC)
+ override STRIP = @echo "STRIP           $@"; $(_STRIP)
+ override GENMODSRC = @echo "GENMODSRC       $@"; $(_GENMODSRC)
+ override INFO_GENCMDLIST = @echo "GENCMDLIST      $@"
+ override INFO_GENFSLIST = @echo "GENFSLIST       $@"
+ override INFO_GENPARTMAPLIST = @echo "GENPARTMAPLIST  $@"
+ override INFO_GEN_FINAL_COMMAND_LIST = @echo "GENCMDLIST[final]  $@"
+ override INFO_GEN_FINAL_FS_LIST = @echo "GENFSLIST[final]   $@"
+ override INFO_GEN_FINAL_PARTMAP_LIST = @echo "GENPARTMAPLIST[final]  $@"
+endif
+
 # Options.
 enable_grub_emu = @enable_grub_emu@
 enable_grub_fstest = @enable_grub_fstest@
@@ -132,13 +165,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-07-11 19:09:14 +0000
+++ conf/common.rmk	2008-07-24 05:36:41 +0000
@@ -269,6 +269,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
 
 # For hello.mod.
@@ -276,6 +277,15 @@
 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_mod_CFLAGS = $(COMMON_CFLAGS)
+gfxmenu_mod_LDFLAGS = $(COMMON_LDFLAGS)
+
 # For boot.mod.
 boot_mod_SOURCES = commands/boot.c
 boot_mod_CFLAGS = $(COMMON_CFLAGS)
@@ -312,7 +322,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-efi.rmk'
--- conf/i386-efi.rmk	2008-07-17 08:50:26 +0000
+++ conf/i386-efi.rmk	2008-07-19 21:39:45 +0000
@@ -84,7 +84,9 @@
 	kern/misc.c kern/mm.c kern/loader.c kern/rescue.c kern/term.c \
 	kern/i386/dl.c kern/i386/efi/init.c kern/parser.c kern/partition.c \
 	kern/env.c symlist.c kern/efi/efi.c kern/efi/init.c kern/efi/mm.c \
-	term/efi/console.c disk/efi/efidisk.c
+	term/efi/console.c disk/efi/efidisk.c \
+	kern/i386/tsc.c \
+	kern/generic/millisleep.c
 kernel_mod_HEADERS = arg.h boot.h cache.h device.h disk.h dl.h elf.h elfload.h \
 	env.h err.h file.h fs.h kernel.h loader.h misc.h mm.h net.h parser.h \
 	partition.h pc_partition.h rescue.h symbol.h term.h time.h types.h \

=== modified file 'conf/i386-pc.rmk'
--- conf/i386-pc.rmk	2008-07-13 00:55:15 +0000
+++ conf/i386-pc.rmk	2008-07-19 21:46:40 +0000
@@ -43,12 +43,16 @@
 	kern/disk.c kern/dl.c kern/file.c kern/fs.c kern/err.c \
 	kern/misc.c kern/mm.c kern/loader.c kern/rescue.c kern/term.c \
 	kern/i386/dl.c kern/i386/pc/init.c kern/parser.c kern/partition.c \
+	kern/i386/tsc.c \
+	kern/generic/millisleep.c \
 	kern/env.c \
+	kern/menu_viewer.c \
 	term/i386/pc/console.c \
 	symlist.c
 kernel_img_HEADERS = arg.h boot.h cache.h device.h disk.h dl.h elf.h elfload.h \
 	env.h err.h file.h fs.h kernel.h loader.h misc.h mm.h net.h parser.h \
 	partition.h pc_partition.h rescue.h symbol.h term.h time.h types.h \
+	menu_viewer.h \
 	machine/biosdisk.h machine/boot.h machine/console.h machine/init.h \
 	machine/memory.h machine/loader.h machine/vga.h machine/vbe.h machine/kernel.h
 kernel_img_CFLAGS = $(COMMON_CFLAGS)
@@ -246,7 +250,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 +265,10 @@
 videotest_mod_LDFLAGS = $(COMMON_LDFLAGS)
 
 # For bitmap.mod
-bitmap_mod_SOURCES = video/bitmap.c
+bitmap_mod_SOURCES = video/bitmap.c \
+                     video/bitmap_scale_nn.c \
+                     video/bitmap_scale_bilinear.c \
+                     video/bitmap_scale.c
 bitmap_mod_CFLAGS = $(COMMON_CFLAGS)
 bitmap_mod_LDFLAGS = $(COMMON_LDFLAGS)
 

=== modified file 'conf/powerpc-ieee1275.rmk'
--- conf/powerpc-ieee1275.rmk	2008-06-19 04:14:16 +0000
+++ conf/powerpc-ieee1275.rmk	2008-07-03 04:19:16 +0000
@@ -85,6 +85,7 @@
 	kern/ieee1275/init.c term/ieee1275/ofconsole.c 		\
 	kern/ieee1275/openfw.c disk/ieee1275/ofdisk.c 		\
 	kern/parser.c kern/partition.c kern/env.c kern/powerpc/dl.c 	\
+	kern/generic/millisleep.c                                       \
 	symlist.c kern/powerpc/cache.S
 kernel_elf_HEADERS = grub/powerpc/ieee1275/ieee1275.h
 kernel_elf_CFLAGS = $(COMMON_CFLAGS)

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

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

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

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

=== added directory 'gfxmenu'
=== added file 'gfxmenu/gfxmenu.c'
--- gfxmenu/gfxmenu.c	1970-01-01 00:00:00 +0000
+++ gfxmenu/gfxmenu.c	2008-07-24 05:36:41 +0000
@@ -0,0 +1,155 @@
+/* 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 grub_err_t
+show_menu (grub_menu_t menu, int nested __attribute__ ((unused)))
+{
+  grub_gfxmenu_model_t model;
+
+  model = grub_gfxmenu_model_new (menu);
+  if (! model)
+    {
+      grub_print_error ();
+      grub_wait_after_message ();
+      return grub_errno;
+    }
+
+  grub_gfxmenu_view_t view;
+
+  view = grub_gfxmenu_view_new ("/boot/theme.txt", model);
+  if (! view)
+    {
+      grub_print_error ();
+      grub_wait_after_message ();
+      grub_gfxmenu_model_destroy (model);
+      return grub_errno;
+    }
+
+  grub_gfxmenu_model_set_selected_index (model, 0);
+
+  int done = 0;
+  while (!done && !grub_menu_viewer_should_return ())
+    {
+      grub_gfxmenu_view_draw (view);
+      grub_video_swap_buffers ();
+
+      int c = GRUB_TERM_ASCII_CHAR (grub_getkey ());
+      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)
+            goto fail;
+        }
+      else if (c == 't')
+        {
+          /* The write hook for 'menuviewer' will cause 
+           * grub_menu_viewer_should_return to return nonzero. */
+          grub_env_set ("menuviewer", "terminal");
+        }
+      else if (nested && c == GRUB_TERM_ESC)
+        {
+          done = 1;
+        }
+    }
+
+fail:
+  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-24 05:36:41 +0000
@@ -0,0 +1,135 @@
+/* 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/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>
+
+/* Model type definition.  */
+struct grub_gfxmenu_model
+{
+  int num_entries;
+  grub_menu_entry_t *entries;
+  int selected_entry_index;
+};
+
+
+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->entries = 0;
+  model->selected_entry_index = 0;
+  model->num_entries = menu->size;
+  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);
+}
+
+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/view.c'
--- gfxmenu/view.c	1970-01-01 00:00:00 +0000
+++ gfxmenu/view.c	2008-07-24 05:36:41 +0000
@@ -0,0 +1,694 @@
+/* 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/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_view.h>
+
+struct grub_gfxmenu_view
+{
+  grub_video_rect_t screen;
+
+  int icon_width;
+  int icon_height;
+  int item_height;
+  grub_font_t title_font;
+  grub_font_t item_font;
+  grub_font_t status_font;
+  grub_video_color_t title_color;
+  grub_video_color_t item_color;
+  grub_video_color_t status_color;
+  grub_video_color_t status_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;
+  char *screen_title;
+
+  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);
+
+/* TODO Make image files referenced by a theme relative to theme path. */
+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 (100, 100, 100),
+                        view->screen.x, view->screen.y,
+                        view->screen.width, view->screen.height);
+  grub_video_swap_buffers ();
+
+  init_terminal (view);
+
+  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->title_font = default_font;
+  view->item_font = default_font;
+  view->status_font = default_font;
+  view->title_color = default_fg_color;
+  view->item_color = default_fg_color;
+  view->status_color = default_bg_color;
+  view->status_bg_color = default_fg_color;
+  view->desktop_image = 0;
+  view->desktop_color = default_bg_color;
+  view->menu_box = 0;
+  view->selected_item_box = 0;
+  view->screen_title = grub_strdup ("GRUB Boot Menu");
+
+  view->model = model;
+
+  grub_gfxmenu_view_load_theme (view, theme_path);
+
+  return view;
+}
+
+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);
+  grub_free (view->screen_title);
+  grub_free (view);
+
+  set_text_mode ();
+  destroy_terminal ();
+}
+
+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;
+}
+
+/* Return zero on failure, nonzero on success.  */
+static int
+theme_set_box (grub_gfxmenu_box_t *boxptr, char *pattern)
+{
+  char *prefix;
+  char *suffix;
+  char *star;
+  grub_gfxmenu_box_t box;
+
+  star = grub_strchr (pattern, '*');
+  if (! star)
+    return 0;
+
+  prefix = grub_malloc (star - pattern + 1);
+  if (! prefix)
+    return 0;
+  grub_memcpy (prefix, pattern, star - pattern);
+  prefix[star - pattern] = '\0';
+  suffix = star + 1;
+  box = grub_gfxmenu_create_box (prefix, suffix);
+  grub_free (prefix);
+  if (! box)
+    return 0;
+
+  if (*boxptr)
+    (*boxptr)->destroy (*boxptr);
+  *boxptr = box;
+  return 1;
+}
+
+/* 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)
+{
+  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 ("status-font", name))
+    view->status_font = grub_font_get (value);
+  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 ("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 ("desktop-image", name))
+    {
+      struct grub_video_bitmap *raw_bitmap;
+      struct grub_video_bitmap *scaled_bitmap;
+      if (grub_video_bitmap_load (&raw_bitmap, value) != GRUB_ERR_NONE)
+        goto fail;
+      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);
+  else if (! grub_strcmp ("selected-item-box", name))
+    theme_set_box (&view->selected_item_box, value);
+
+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;
+
+  grub_free (name);
+}
+
+static char *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;
+}
+
+/* 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)
+{
+#if 1
+  grub_file_t file;
+  char *buf;
+  int pos;
+  int len;
+
+  file = grub_file_open (theme_path);
+  if (!file)
+    return 0;
+
+  len = grub_file_size (file);
+  buf = grub_malloc (len);
+  if (! buf)
+    {
+      grub_file_close (file);
+      return 0;
+    }
+  if (grub_file_read (file, buf, len) != len)
+    {
+      grub_free (buf);
+      grub_file_close (file);
+      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,
+                            new_substring (buf, name_start, name_end),
+                            new_substring (buf, value_start, value_end));
+        }
+      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 = 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,
+                            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 (buf);
+  grub_file_close (file);
+  return 1;
+#else
+  theme_set_string (view, 
+                    grub_strdup ("title-font"),
+                    grub_strdup ("Helvetica Bold 24"));
+  theme_set_string (view, 
+                    grub_strdup ("item-font"),
+                    grub_strdup ("Helvetica Bold 12"));
+  theme_set_string (view, 
+                    grub_strdup ("status-font"),
+                    grub_strdup ("Helvetica 12"));
+  theme_set_string (view, 
+                    grub_strdup ("title-color"),
+                    grub_strdup ("255,255,0"));
+  theme_set_string (view, 
+                    grub_strdup ("item-color"),
+                    grub_strdup ("0,0,255"));
+  theme_set_string (view, 
+                    grub_strdup ("status-color"),
+                    grub_strdup ("255,255,0"));
+  theme_set_string (view, 
+                    grub_strdup ("status-bg-color"),
+                    grub_strdup ("64,64,64"));
+  theme_set_string (view, 
+                    grub_strdup ("menu-box"),
+                    grub_strdup ("/boot/images/menubox_*.tga"));
+  theme_set_string (view, 
+                    grub_strdup ("selected-item-box"),
+                    grub_strdup ("/boot/images/select_blue_*.tga"));
+  return 1;
+#endif
+}
+
+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 struct grub_video_bitmap *
+get_item_icon (grub_gfxmenu_view_t view __attribute__((unused)),
+               int item_index __attribute__((unused)))
+{
+  /* TODO Implement icons.  */
+  return 0;
+}
+
+static void
+draw_menu (grub_gfxmenu_view_t view)
+{
+  int boxpad = 14;
+  int icon_text_space = 4;
+  int item_vspace = 16;
+
+  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++)
+    {
+      if (i == grub_gfxmenu_model_get_selected_index (view->model))
+        {
+          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);
+
+      grub_video_draw_string (grub_gfxmenu_model_get_entry_title (view->model, i),
+                              view->item_font, 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->screen_title)
+    return;
+
+  /* Center the title. */
+  int title_width = grub_font_get_string_width (view->title_font, 
+                                                view->screen_title);
+  int x = (view->screen.width - title_width) / 2;
+  int y = 40 + grub_font_get_ascent (view->title_font);
+  grub_video_draw_string (view->screen_title,
+                          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);
+}
+
+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);
+}
+
+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 grub_gfxmenu_box_t term_box;
+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)))
+{
+  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;
+  int termy = term_view->screen.y + term_view->screen.height / 10;
+
+  if (term_box)
+    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 * 8 / 10;
+  term_target_height = view->screen.height * 8 / 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;
+
+  grub_gfxterm_init_window (term_target, 0, 0,
+                            term_target_width, term_target_height,
+                            "Fixed 10", 16);
+  if (grub_errno != GRUB_ERR_NONE)
+    return;
+
+  term_box = grub_gfxmenu_create_box ("/boot/images/select_blue_", ".tga");
+  term_box->set_content_size (term_box, term_target_width, term_target_height);
+
+  /* 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_box->destroy (term_box);
+  term_box = 0;
+  grub_gfxterm_destroy_window ();
+  grub_gfxterm_set_repaint_callback (0);
+  grub_video_delete_render_target (term_target);
+  grub_term_set_current (term_original);
+}
+
+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/file.h'
--- include/grub/file.h	2007-08-02 17:40:37 +0000
+++ include/grub/file.h	2008-07-22 15:23:31 +0000
@@ -1,6 +1,6 @@
 /*
  *  GRUB  --  GRand Unified Bootloader
- *  Copyright (C) 2002,2007  Free Software Foundation, Inc.
+ *  Copyright (C) 2002,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
@@ -39,6 +39,19 @@
   /* The file size.  */
   grub_off_t size;
 
+  /* The file offset when `buffer' starts, or -1 to indicate that
+     buffer has no data.  */
+  grub_off_t buffer_offset;
+
+  /* The number of bytes in `buffer'.  */
+  grub_size_t buffer_length;
+
+  /* The number of bytes allocated for `buffer'.  */
+  grub_size_t buffer_capacity;
+
+  /* Readahead buffer.  */
+  char *buffer;
+
   /* Filesystem-specific data.  */
   void *data;
 
@@ -48,6 +61,11 @@
 };
 typedef struct grub_file *grub_file_t;
 
+/* Enable file readahead buffering if nonzero.  Buffering is disabled by
+   default, since it seems to cause problems during the early GRUB boot
+   process.  The grub_main function enables it.  */
+extern int grub_file_buffering_enabled;
+
 /* Get a device name from NAME.  */
 char *EXPORT_FUNC(grub_file_get_device_name) (const char *name);
 

=== 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-24 05:36:41 +0000
@@ -0,0 +1,45 @@
+/* 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
+
+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);
+
+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-24 05:36:41 +0000
@@ -0,0 +1,50 @@
+/* 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_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-04 17:55:21 +0000
@@ -0,0 +1,43 @@
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2008  Free Software Foundation, Inc.
+ *
+ *  GRUB is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef KERNEL_CPU_TSC_HEADER
+#define KERNEL_CPU_TSC_HEADER	1
+
+#include <grub/types.h>
+
+/* Read the TSC value, which increments with each CPU clock cycle. */
+static __inline grub_uint64_t
+grub_get_tsc (void)
+{
+  grub_uint32_t lo, hi;
+
+  /* The CPUID instruction is a 'serializing' instruction, and
+     avoids out-of-order execution of the RDTSC instruction. */
+  __asm__ __volatile__ ("xorl %%eax, %%eax\n\t"
+                        "cpuid":::"%rax", "%rbx", "%rcx", "%rdx");
+  /* Read TSC value.  We cannot use "=A", since this would use
+     %rax on x86_64. */
+  __asm__ __volatile__ ("rdtsc":"=a" (lo), "=d" (hi));
+
+  return (((grub_uint64_t) hi) << 32) | lo;
+}
+
+void grub_tsc_calibrate (void);
+
+#endif /* ! KERNEL_CPU_TSC_HEADER */

=== added file 'include/grub/menu.h'
--- include/grub/menu.h	1970-01-01 00:00:00 +0000
+++ include/grub/menu.h	2008-07-19 18:40:28 +0000
@@ -0,0 +1,51 @@
+/* menu.h - Menu and menu entry model declarations. */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2002,2003,2005,2006,2007,2008  Free Software Foundation, Inc.
+ *
+ *  GRUB is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GRUB_MENU_HEADER
+#define GRUB_MENU_HEADER 1
+
+/* The menu entry.  */
+struct grub_menu_entry
+{
+  /* The title name.  */
+  const char *title;
+
+  /* The commands associated with this menu entry.  */
+  struct grub_script *commands;
+
+  /* The sourcecode of the menu entry, used by the editor.  */
+  const char *sourcecode;
+
+  /* The next element.  */
+  struct grub_menu_entry *next;
+};
+typedef struct grub_menu_entry *grub_menu_entry_t;
+
+/* The menu.  */
+struct grub_menu
+{
+  /* The size of a menu.  */
+  int size;
+
+  /* The list of menu entries.  */
+  grub_menu_entry_t entry_list;
+};
+typedef struct grub_menu *grub_menu_t;
+
+#endif /* GRUB_MENU_HEADER */

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

=== modified file 'include/grub/time.h'
--- include/grub/time.h	2007-10-22 19:02:16 +0000
+++ include/grub/time.h	2008-07-03 04:19:16 +0000
@@ -1,6 +1,6 @@
 /*
  *  GRUB  --  GRand Unified Bootloader
- *  Copyright (C) 2007  Free Software Foundation, Inc.
+ *  Copyright (C) 2007, 2008  Free Software Foundation, Inc.
  *
  *  GRUB is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License as published by
@@ -19,12 +19,13 @@
 #ifndef KERNEL_TIME_HEADER
 #define KERNEL_TIME_HEADER	1
 
+#include <grub/types.h>
 #include <grub/symbol.h>
 #include <grub/machine/time.h>
 #include <grub/cpu/time.h>
 
 void EXPORT_FUNC(grub_millisleep) (grub_uint32_t ms);
-void EXPORT_FUNC(grub_millisleep_generic) (grub_uint32_t ms);
+grub_uint64_t EXPORT_FUNC(grub_get_time_ms) (void);
 
 static __inline void
 grub_sleep (grub_uint32_t s)

=== modified file 'include/grub/video.h'
--- include/grub/video.h	2008-01-01 12:02:07 +0000
+++ include/grub/video.h	2008-07-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 */

=== modified file 'kern/file.c'
--- kern/file.c	2008-01-25 20:57:40 +0000
+++ kern/file.c	2008-07-22 15:23:31 +0000
@@ -1,7 +1,7 @@
 /* file.c - file I/O functions */
 /*
  *  GRUB  --  GRand Unified Bootloader
- *  Copyright (C) 2002,2006,2007  Free Software Foundation, Inc.
+ *  Copyright (C) 2002,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
@@ -24,6 +24,12 @@
 #include <grub/fs.h>
 #include <grub/device.h>
 
+/* File readahead buffer size in bytes.  */
+#define BUFFER_SIZE 1024
+
+/* Use readahead buffering for file reads?  */
+int grub_file_buffering_enabled = 0;
+
 /* Get the device part of the filename NAME. It is enclosed by parentheses.  */
 char *
 grub_file_get_device_name (const char *name)
@@ -81,6 +87,10 @@
   
   file->device = device;
   file->offset = 0;
+  file->buffer_offset = -1;
+  file->buffer_length = 0;
+  file->buffer_capacity = 0;
+  file->buffer = 0;
   file->data = 0;
   file->read_hook = 0;
     
@@ -97,6 +107,13 @@
   if ((file->fs->open) (file, file_name) != GRUB_ERR_NONE)
     goto fail;
 
+  if (grub_file_buffering_enabled)
+    {
+      file->buffer = grub_malloc (BUFFER_SIZE);
+      if (file->buffer)
+        file->buffer_capacity = BUFFER_SIZE;
+    }
+
   return file;
 
  fail:
@@ -110,6 +127,15 @@
   return 0;
 }
 
+static inline grub_size_t
+grub_min (grub_size_t a, grub_size_t b)
+{
+  if (a < b)
+    return a;
+  else
+    return b;
+}
+
 grub_ssize_t
 grub_file_read (grub_file_t file, char *buf, grub_size_t len)
 {
@@ -124,10 +150,52 @@
   
   if (len == 0)
     return 0;
-  
-  res = (file->fs->read) (file, buf, len);
-  if (res > 0)
-    file->offset += res;
+
+  if (grub_file_buffering_enabled && file->buffer)
+    {
+      res = 0;   /* The while loop uses res to accumulate the # bytes read. */
+      while (len)
+        {
+          /* If the start of the chunk requested is not in the buffer,
+             then fill the buffer starting at the requested offset.  */
+          if (file->buffer_offset == -1
+              || file->offset < file->buffer_offset
+              || file->offset >= file->buffer_offset + file->buffer_length)
+            {
+              grub_ssize_t res2;
+
+              /* Read into the buffer, and then use that data.  */
+              file->buffer_offset = -1;
+              file->buffer_length = grub_min (file->buffer_capacity,
+                                              file->size - file->offset);
+              res2 = (file->fs->read) (file, file->buffer, file->buffer_length);
+              if (res2 > 0)
+                {
+                  file->buffer_length = res2;
+                  file->buffer_offset = file->offset;
+                }
+              else
+                return res;
+            }
+
+          /* Use as much buffered data as possible.  */
+          grub_size_t bufstart = file->offset - file->buffer_offset;
+          grub_size_t buflen = len;
+          if (bufstart + len > file->buffer_length)
+            buflen = file->buffer_length - bufstart;
+          grub_memcpy (buf, file->buffer + bufstart, buflen);
+          buf += buflen;
+          res += buflen;
+          len -= buflen;
+          file->offset += buflen;
+        }
+    }
+  else
+    {
+      res = (file->fs->read) (file, buf, len);
+      if (res > 0)
+        file->offset += res;
+    }
 
   return res;
 }
@@ -135,6 +203,9 @@
 grub_err_t
 grub_file_close (grub_file_t file)
 {
+  grub_free (file->buffer);
+  file->buffer = 0;
+
   if (file->fs->close)
     (file->fs->close) (file);
 

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

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

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

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

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

=== modified file 'kern/ieee1275/init.c'
--- kern/ieee1275/init.c	2008-07-04 02:01:55 +0000
+++ kern/ieee1275/init.c	2008-07-04 19:43:25 +0000
@@ -47,12 +47,6 @@
 extern char _end[];
 
 void
-grub_millisleep (grub_uint32_t ms)
-{
-  grub_millisleep_generic (ms);
-}
-
-void
 grub_exit (void)
 {
   grub_ieee1275_exit ();
@@ -260,8 +254,8 @@
   grub_console_fini ();
 }
 
-grub_uint32_t
-grub_get_rtc (void)
+grub_uint64_t
+grub_get_time_ms (void)
 {
   grub_uint32_t msecs = 0;
 
@@ -270,6 +264,12 @@
   return msecs;
 }
 
+grub_uint32_t
+grub_get_rtc (void)
+{
+  return grub_get_time_ms ();
+}
+
 grub_addr_t
 grub_arch_modules_addr (void)
 {

=== modified file 'kern/main.c'
--- kern/main.c	2008-06-19 19:08:57 +0000
+++ kern/main.c	2008-07-24 05:54:33 +0000
@@ -128,6 +128,9 @@
   grub_env_export ("prefix");
   grub_set_root_dev ();
 
+  /* Enable file readahead buffering, now that it is safe to do so.  */
+  grub_file_buffering_enabled = 1;
+
   /* Load the normal mode module.  */
   grub_load_normal_mode ();
   

=== 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-04 17:55:21 +0000
@@ -66,12 +66,6 @@
   /* Never reached.  */
 }
 
-void
-grub_millisleep (grub_uint32_t ms)
-{
-  grub_millisleep_generic (ms);
-}
-
 int
 grub_ieee1275_test_flag (enum grub_ieee1275_flag flag)
 {

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

=== modified file 'normal/menu.c'
--- normal/menu.c	2008-02-09 11:00:19 +0000
+++ normal/menu.c	2008-07-19 19:24:29 +0000
@@ -24,6 +24,7 @@
 #include <grub/machine/time.h>
 #include <grub/env.h>
 #include <grub/script.h>
+#include <grub/menu_viewer.h>
 
 static grub_uint8_t grub_color_menu_normal;
 static grub_uint8_t grub_color_menu_highlight;
@@ -364,7 +365,7 @@
   if (timeout > 0)
     print_timeout (timeout, offset, 0);
 
-  while (1)
+  while (!grub_menu_viewer_should_return ())
     {
       int c;
       timeout = get_timeout ();
@@ -468,6 +469,10 @@
 		}
 	      goto refresh;
 	      
+	    case 't':
+	      grub_env_set ("menuviewer", "protomenu");
+	      goto refresh;
+
 	    default:
 	      break;
 	    }
@@ -476,13 +481,14 @@
 	}
     }
 
-  /* Never reach here.  */
+  /* Exit menu without activating an item.  This occurs if the user presses
+   * 't', switching to the graphical menu viewer.  */
   return -1;
 }
 
 /* Run a menu entry.  */
-static void
-run_menu_entry (grub_menu_entry_t entry)
+void
+grub_menu_execute_entry(grub_menu_entry_t entry)
 {
   grub_script_execute (entry->commands);
   
@@ -491,8 +497,8 @@
     grub_command_execute ("boot", 0);
 }
 
-void
-grub_menu_run (grub_menu_t menu, int nested)
+static grub_err_t
+show_menu (grub_menu_t menu, int nested)
 {
   while (1)
     {
@@ -513,7 +519,7 @@
 
       grub_printf ("  Booting \'%s\'\n\n", e->title);
   
-      run_menu_entry (e);
+      grub_menu_execute_entry (e);
 
       /* Deal with a fallback entry.  */
       /* FIXME: Multiple fallback entries like GRUB Legacy.  */
@@ -526,7 +532,7 @@
 	  e = get_entry (menu, fallback_entry);
 	  grub_env_unset ("fallback");
 	  grub_printf ("\n  Falling back to \'%s\'\n\n", e->title);
-	  run_menu_entry (e);
+	  grub_menu_execute_entry (e);
 	}
 
       if (grub_errno != GRUB_ERR_NONE)
@@ -537,4 +543,12 @@
 	  grub_wait_after_message ();
 	}
     }
+
+  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-07-03 14:27:43 +0000
@@ -0,0 +1,99 @@
+/* bitmap_scale.c - Bitmap scaling interface. */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2006,2007  Free Software Foundation, Inc.
+ *
+ *  GRUB is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/mm.h>
+#include <grub/misc.h>
+#include <grub/video.h>
+#include <grub/bitmap.h>
+#include <grub/bitmap_scale.h>
+#include <grub/types.h>
+
+/* 
+ * This function creates a new scaled version of the bitmap SRC.  The new
+ * bitmap has dimensions DST_WIDTH by DST_HEIGHT.  The scaling algorithm
+ * is given by SCALE_METHOD.  If an error is encountered, the return code is
+ * not equal to GRUB_ERR_NONE, and the bitmap DST is either not created, or 
+ * it is destroyed before this function returns.
+ *
+ * Supports only direct color modes which have components separated
+ * into bytes (e.g., RGBA 8:8:8:8 or BGR 8:8:8 true color). 
+ * But because of this simplifying assumption, the implementation is
+ * greatly simplified.
+ */
+grub_err_t
+grub_video_bitmap_create_scaled (struct grub_video_bitmap **dst,
+                                 int dst_width, int dst_height,
+                                 struct grub_video_bitmap *src,
+                                 enum grub_video_bitmap_scale_method
+                                 scale_method)
+{
+
+  /* Verify the simplifying assumptions. */
+  if (src == 0)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT,
+                       "null src bitmap in grub_video_bitmap_create_scaled");
+  if (src->mode_info.red_field_pos % 8 != 0
+      || src->mode_info.green_field_pos % 8 != 0
+      || src->mode_info.blue_field_pos % 8 != 0
+      || src->mode_info.reserved_field_pos % 8 != 0)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT,
+                       "src format not supported for scale");
+  if (src->mode_info.width == 0 || src->mode_info.height == 0)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "bitmap has a zero dimension");
+  if (dst_width <= 0 || dst_height <= 0)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT,
+                       "requested to scale to a size w/ a zero dimension");
+  if (src->mode_info.bytes_per_pixel * 8 != src->mode_info.bpp)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT,
+                       "bitmap to scale has inconsistent Bpp and bpp");
+
+  /* Create the new bitmap. */
+  grub_err_t ret;
+  ret = grub_video_bitmap_create (dst, dst_width, dst_height,
+                                  src->mode_info.blit_format);
+  if (ret != GRUB_ERR_NONE)
+    return ret;                 /* Error. */
+
+  switch (scale_method)
+    {
+    case GRUB_VIDEO_BITMAP_SCALE_METHOD_FASTEST:
+    case GRUB_VIDEO_BITMAP_SCALE_METHOD_NEAREST:
+      ret = grub_video_bitmap_scale_nn (*dst, src);
+      break;
+    case GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST:
+    case GRUB_VIDEO_BITMAP_SCALE_METHOD_BILINEAR:
+      ret = grub_video_bitmap_scale_bilinear (*dst, src);
+      break;
+    default:
+      ret = grub_error (GRUB_ERR_BAD_ARGUMENT, "Invalid scale_method value");
+      break;
+    }
+
+  if (ret == GRUB_ERR_NONE)
+    {
+      /* Success:  *dst is now a pointer to the scaled bitmap. */
+      return GRUB_ERR_NONE;
+    }
+  else
+    {
+      /* Destroy the bitmap and return the error code. */
+      grub_video_bitmap_destroy (*dst);
+      return ret;
+    }
+}

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

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

=== modified file 'video/i386/pc/vbe.c'
--- video/i386/pc/vbe.c	2008-01-21 15:48:27 +0000
+++ video/i386/pc/vbe.c	2008-07-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-01-22 13:50:38 +0000
+++ video/readers/jpeg.c	2008-07-03 13:49:18 +0000
@@ -542,7 +542,7 @@
 
   if (grub_video_bitmap_create (data->bitmap, data->image_width,
 				data->image_height,
-				GRUB_VIDEO_BLIT_FORMAT_R8G8B8))
+				GRUB_VIDEO_BLIT_FORMAT_RGB_888))
     return grub_errno;
 
   data->bit_mask = 0x0;

=== modified file 'video/readers/png.c'
--- video/readers/png.c	2008-03-31 11:00:48 +0000
+++ video/readers/png.c	2008-07-03 13:49:18 +0000
@@ -231,7 +231,7 @@
     {
       if (grub_video_bitmap_create (data->bitmap, data->image_width,
 				    data->image_height,
-				    GRUB_VIDEO_BLIT_FORMAT_R8G8B8))
+				    GRUB_VIDEO_BLIT_FORMAT_RGB_888))
 	return grub_errno;
       data->bpp = 3;
     }
@@ -239,7 +239,7 @@
     {
       if (grub_video_bitmap_create (data->bitmap, data->image_width,
 				    data->image_height,
-				    GRUB_VIDEO_BLIT_FORMAT_R8G8B8A8))
+				    GRUB_VIDEO_BLIT_FORMAT_RGBA_8888))
 	return grub_errno;
       data->bpp = 4;
     }

=== modified file 'video/readers/tga.c'
--- video/readers/tga.c	2007-12-30 08:52:06 +0000
+++ video/readers/tga.c	2008-07-03 13:49:18 +0000
@@ -397,7 +397,7 @@
     {
       grub_video_bitmap_create (bitmap, header.image_width,
                                 header.image_height,
-                                GRUB_VIDEO_BLIT_FORMAT_R8G8B8A8);
+                                GRUB_VIDEO_BLIT_FORMAT_RGBA_8888);
       if (grub_errno != GRUB_ERR_NONE)
         {
           grub_file_close (file);
@@ -420,7 +420,7 @@
     {
       grub_video_bitmap_create (bitmap, header.image_width,
                                 header.image_height,
-                                GRUB_VIDEO_BLIT_FORMAT_R8G8B8);
+                                GRUB_VIDEO_BLIT_FORMAT_RGB_888);
       if (grub_errno != GRUB_ERR_NONE)
         {
           grub_file_close (file);

=== 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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
Rl+dETS7viG2QhuYWhYYm/y9Bt5Ne7rlrY4UCp73s4VTxQQQ4naGnLQlodgB2py9Cr+pyNw7zo6N
HDntw2I438huqP3lIK7Ix0n/maR0DTNgS09eXjbcGawY4Nt2YuyAgeTvucp3PO62WDo2ULBJEIMF
q3A+0jcXhw3clhtaSvFKheqTaqaxEyBerBpADnt0TqEbqFhxYqW16jkQSzve389PhaHP15jOjRqU
JzFWK+9wyso53j40r4eGE48PFEqtlg3wxo/uiHNXQYcrKiSg7ZRBqIFG/mUpVzLumeLid17QK569
p+bufpV9WuXOLa2qGiZCh2smHbsdnhzE6XyNUXLHF108NU11cyrvqu8bco1UvTCtsTIo5/S/JeFZ
d15LNea9lcu681mtVouF6LdUqrK5ZrFey8KVtcTqF2mlcWKlxaW0lS/RdK12trS6XhQtLF4XlUnT
eUlK7VLi7UKF5XE64vK910u1VXLJdFcqaLdVVlxp8u/nor+3d8X/KcI18XU471Wk9j5fP0fs6c5+
muL7Ge+XlSHLxNLNBuXblezY20hj0bE+rjo6Oz3FFZ68lNMwdlujuJS8424s7jfut3TaCk1jrB2+
MOTdV3x460PPn2i/prlrtsz1v8Hyftrr4v7sFd8dz1XSEocr9856rjb2VbXulvWhTs9u9odLShrf
x3rvmU7Y3b1XjO+/lx4790+ueF1uN6eOkH1jCc+PXHdDh+hgz4V6+b6TabrTbze6M1bZF9Z+mfsc
mt81moiI74dkb1WP43vptp7oXnc+IJF84XtPDVnSK3tgtmy1xKl53pik1HNaelHXja1Mnq129d1w
xO/lEnRJPrU+rZLc+NeHeDm+t+oq8WMQc5cvm8zbBWiwvTL3VS7StWjPfcdid1mOxGF5R0zV6S4k
rd7ud8+mvPt2b0tfy8/PR3Xjy8ecPTz8sc+z/Hx29W8PX08fLPHTz3bfztruuzet2l1Nu/fbl5dt
eXrx3LKG1ufd2t4J14++K8VrP1kd1iyTd5t4W9ZSxXuolZuCL2J1tx565zrji8Zy6eVzo13116D3
fvff+lD63yrLPwfKOfp2u8aaOfw0m84M50/Pd3Pq/tIzVOn2Ov6rzwcsR35/l8fdtPoA+T43++1F
h+Ogr66WealLONB8fT66+H3fB245Ht9Q59O3kf1n+hnHs8vb7g46f9Hx/DzjqM327nqe0OieUf/b
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
ChIbAOiuYA==
