# Bazaar merge directive format 2 (Bazaar 0.90)
# revision_id: colin@gibibit.com-20080728020230-a79jyq7pkv9jnco1
# target_branch: ../trunk-clean
# testament_sha1: 990f6d3418364109d162680f19eb5aa7adddfadf
# timestamp: 2008-07-27 19:17:00 -0700
# source_branch: http://grub.gibibit.com/bzr/trunk-clean
# base_revision_id: colin@gibibit.com-20080728020000-lhbskg67rnn3gfp7
# 
# 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-27 19:57:43 +0000
+++ ChangeLog	2008-07-28 02:00:30 +0000
@@ -521,6 +521,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-24 13:56:30 +0000
+++ Makefile.in	2008-07-28 02:00:30 +0000
@@ -89,6 +89,39 @@
 YACC = @YACC@
 UNIFONT_HEX = @UNIFONT_HEX@
 
+### Pretty output control ###
+# Set up compiler and linker commands that either is quiet (does not print
+# the command line being executed) or verbose (print the command line).
+_CC := $(CC)
+_TARGET_CC := $(TARGET_CC)
+_STRIP := $(STRIP)
+_GENMODSRC := sh $(srcdir)/genmodsrc.sh
+ifeq ($(V),1)
+ override V_PREFIX :=
+ override CC = $(_CC)
+ override TARGET_CC = $(_CC)
+ override STRIP = $(_STRIP)
+ override GENMODSRC = $(_GENMODSRC)
+ override INFO_GENCMDLIST =
+ override INFO_GENFSLIST =
+ override INFO_GENPARTMAPLIST =
+ override INFO_GEN_FINAL_COMMAND_LIST =
+ override INFO_GEN_FINAL_FS_LIST =
+ override INFO_GEN_FINAL_PARTMAP_LIST =
+else
+ override V_PREFIX := @
+ override CC = @echo "COMPILE         $<"; $(_CC)
+ override TARGET_CC = @echo "COMPILE(TARGET) $<"; $(_TARGET_CC)
+ override STRIP = @echo "STRIP           $@"; $(_STRIP)
+ override GENMODSRC = @echo "GENMODSRC       $@"; $(_GENMODSRC)
+ override INFO_GENCMDLIST = @echo "GENCMDLIST      $@"
+ override INFO_GENFSLIST = @echo "GENFSLIST       $@"
+ override INFO_GENPARTMAPLIST = @echo "GENPARTMAPLIST  $@"
+ override INFO_GEN_FINAL_COMMAND_LIST = @echo "GENCMDLIST[final]  $@"
+ override INFO_GEN_FINAL_FS_LIST = @echo "GENFSLIST[final]   $@"
+ override INFO_GEN_FINAL_PARTMAP_LIST = @echo "GENPARTMAPLIST[final]  $@"
+endif
+
 # Options.
 enable_grub_emu = @enable_grub_emu@
 enable_grub_fstest = @enable_grub_fstest@
@@ -136,13 +169,16 @@
 	  || (rm -f $@; exit 1)
 
 command.lst: $(COMMANDFILES)
-	cat $^ /dev/null | sort > $@
+	$(INFO_GEN_FINAL_COMMAND_LIST)
+	$(V_PREFIX)cat $^ /dev/null | sort > $@
 
 fs.lst: $(FSFILES)
-	cat $^ /dev/null | sort > $@
+	$(INFO_GEN_FINAL_FS_LIST)
+	$(V_PREFIX)cat $^ /dev/null | sort > $@
 
 partmap.lst: $(PARTMAPFILES)
-	cat $^ /dev/null | sort > $@
+	$(INFO_GEN_FINAL_PARTMAP_LIST)
+	$(V_PREFIX)cat $^ /dev/null | sort > $@
 
 ifeq (, $(UNIFONT_HEX))
 else

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

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

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

=== modified file 'conf/common.rmk'
--- conf/common.rmk	2008-07-27 12:51:30 +0000
+++ conf/common.rmk	2008-07-28 02:02:30 +0000
@@ -274,6 +274,7 @@
 	cmp.mod cat.mod help.mod font.mod search.mod		\
 	loopback.mod fs_uuid.mod configfile.mod echo.mod	\
 	terminfo.mod test.mod blocklist.mod hexdump.mod		\
+	gfxmenu.mod                                             \
 	read.mod sleep.mod loadenv.mod crc.mod
 
 # For hello.mod.
@@ -281,6 +282,16 @@
 hello_mod_CFLAGS = $(COMMON_CFLAGS)
 hello_mod_LDFLAGS = $(COMMON_LDFLAGS)
 
+# For gfxmenu.mod.
+gfxmenu_mod_SOURCES = \
+	gfxmenu/gfxmenu.c \
+	gfxmenu/model.c \
+	gfxmenu/view.c \
+	gfxmenu/widget-box.c \
+	gfxmenu/stringutil.c
+gfxmenu_mod_CFLAGS = $(COMMON_CFLAGS)
+gfxmenu_mod_LDFLAGS = $(COMMON_LDFLAGS)
+
 # For boot.mod.
 boot_mod_SOURCES = commands/boot.c
 boot_mod_CFLAGS = $(COMMON_CFLAGS)
@@ -317,7 +328,7 @@
 help_mod_LDFLAGS = $(COMMON_LDFLAGS)
 
 # For font.mod.
-font_mod_SOURCES = font/manager.c
+font_mod_SOURCES = font/font_cmd.c font/font.c font/loader.c
 font_mod_CFLAGS = $(COMMON_CFLAGS)
 font_mod_LDFLAGS = $(COMMON_LDFLAGS)
 
@@ -382,7 +393,7 @@
 crc_mod_LDFLAGS = $(COMMON_LDFLAGS)
 
 # Misc.
-pkglib_MODULES += gzio.mod elf.mod
+pkglib_MODULES += gzio.mod bufio.mod elf.mod
 
 # For elf.mod.
 elf_mod_SOURCES = kern/elf.c
@@ -393,3 +404,8 @@
 gzio_mod_SOURCES = io/gzio.c
 gzio_mod_CFLAGS = $(COMMON_CFLAGS)
 gzio_mod_LDFLAGS = $(COMMON_LDFLAGS)
+
+# For bufio.mod.
+bufio_mod_SOURCES = io/bufio.c
+bufio_mod_CFLAGS = $(COMMON_CFLAGS)
+bufio_mod_LDFLAGS = $(COMMON_LDFLAGS)

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

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

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

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

=== added file 'gfxmenu/stringutil.c'
--- gfxmenu/stringutil.c	1970-01-01 00:00:00 +0000
+++ gfxmenu/stringutil.c	2008-07-26 00:44:48 +0000
@@ -0,0 +1,196 @@
+/* stringutil.c - String utilities. */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2008  Free Software Foundation, Inc.
+ *
+ *  GRUB is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/stringutil.h>
+#include <grub/types.h>
+#include <grub/misc.h>
+#include <grub/mm.h>
+
+/* Create a new NUL-terminated string on the heap as a substring of BUF.
+   The range of buf included is the half-open interval [START,END).
+   The index START is inclusive, END is exclusive.  */
+char *
+grub_new_substring (const char *buf,
+                    grub_size_t start, grub_size_t end)
+{
+  char *s = grub_malloc (end - start + 1);
+  if (! s)
+    return 0;
+  grub_memcpy (s, buf + start, end - start);
+  s[end - start] = '\0';
+  return s;
+}
+
+/* Eliminate "." and ".." path elements from PATH.  A new heap-allocated
+   string is returned.  */
+static char *
+canonicalize_path (const char *path)
+{
+  int i;
+  const char *p;
+  char *newpath = 0;
+
+  /* Count the path components in path.  */
+  int components = 1;
+  for (p = path; *p; p++)
+    if (*p == '/')
+      components++;
+
+  char **path_array = grub_malloc (components * sizeof (*path_array));
+  if (! path_array)
+    return 0;
+
+  /* Initialize array elements to NULL pointers; in case once of the
+     allocations fails, the cleanup code can just call grub_free() for all
+     pointers in the array.  */
+  for (i = 0; i < components; i++)
+    path_array[i] = 0;
+
+  /* Parse the path into path_array.  */
+  p = path;
+  for (i = 0; i < components && p; i++)
+    {
+      /* Find the end of the path element.  */
+      const char *end = grub_strchr (p, '/');
+      if (!end)
+        end = p + grub_strlen (p);
+
+      /* Copy the element.  */
+      path_array[i] = grub_new_substring (p, 0, end - p);
+      if (!path_array[i])
+        goto cleanup;
+
+      /* Advance p to point to the start of the next element, or NULL.  */
+      if (*end)
+        p = end + 1;
+      else
+        p = 0;
+    }
+
+  /* Eliminate '.' and '..' elements from the path array.  */
+  int newpath_length = 0;
+  for (i = components - 1; i >= 0; --i)
+    {
+      if (! grub_strcmp (path_array[i], "."))
+        {
+          grub_free (path_array[i]);
+          path_array[i] = 0;
+        }
+      else if (! grub_strcmp (path_array[i], "..")
+               && i > 0)
+        {
+          /* Delete the '..' and the prior path element.  */
+          grub_free (path_array[i]);
+          path_array[i] = 0;
+          --i;
+          grub_free (path_array[i]);
+          path_array[i] = 0;
+        }
+      else
+        {
+          newpath_length += grub_strlen (path_array[i]) + 1;
+        }
+    }
+
+  /* Construct a new path string.  */
+  newpath = grub_malloc (newpath_length + 1);
+  if (! newpath)
+    goto cleanup;
+
+  newpath[0] = '\0';
+  char *newpath_end = newpath;
+  int first = 1;
+  for (i = 0; i < components; i++)
+    {
+      char *element = path_array[i];
+      if (element)
+        {
+          /* For all components but the first, prefix with a slash.  */
+          if (! first)
+            newpath_end = grub_stpcpy (newpath_end, "/");
+          newpath_end = grub_stpcpy (newpath_end, element);
+          first = 0;
+        }
+    }
+
+cleanup:
+  for (i = 0; i < components; i++)
+    grub_free (path_array[i]);
+  grub_free (path_array);
+
+  return newpath;
+}
+
+/* Return a new heap-allocated string representing to absolute path
+   to the file referred to by PATH.  If PATH is an absolute path, then
+   the returned path is a copy of PATH.  If PATH is a relative path, then
+   BASE is with PATH used to construct the absolute path.  */
+char *
+grub_resolve_relative_path (const char *base, const char *path)
+{
+  char *abspath;
+  char *canonpath;
+  char *p;
+
+  /* If PATH is an absolute path, then just use it as is.  */
+  if (path[0] == '/' || path[0] == '(')
+    return canonicalize_path (path);
+
+  abspath = grub_malloc (grub_strlen (base) + grub_strlen (path) + 1);
+  if (!abspath)
+    return 0;
+
+  /* Concatenate BASE and PATH.
+     Note that BASE is expected to have a trailing slash.  */
+  p = grub_stpcpy (abspath, base);
+  grub_stpcpy (p, path);
+
+  canonpath = canonicalize_path (abspath);
+  if (!canonpath)
+    return abspath;
+
+  grub_free (abspath);
+  return canonpath;
+}
+
+/* Get the path of the directory where the file at FILE_PATH is located.
+   FILE_PATH should refer to a file, not a directory.  The returned path
+   includes a trailing slash.
+   This does not handle GRUB "(hd0,0)" paths properly yet since it only
+   looks at slashes.  */
+char *
+grub_get_dirname (const char *file_path)
+{
+  int i;
+  int last_slash;
+
+  last_slash = -1;
+  for (i = grub_strlen (file_path) - 1; i >= 0; --i)
+    {
+      if (file_path[i] == '/')
+        {
+          last_slash = i;
+          break;
+        }
+    }
+  if (last_slash == -1)
+    return grub_strdup ("/");
+
+  return grub_new_substring (file_path, 0, last_slash + 1);
+}

=== added file 'gfxmenu/view.c'
--- gfxmenu/view.c	1970-01-01 00:00:00 +0000
+++ gfxmenu/view.c	2008-07-27 00:32:41 +0000
@@ -0,0 +1,959 @@
+/* view.c - Graphical menu interface MVC view. */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2008  Free Software Foundation, Inc.
+ *
+ *  GRUB is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/types.h>
+#include <grub/file.h>
+#include <grub/misc.h>
+#include <grub/mm.h>
+#include <grub/err.h>
+#include <grub/dl.h>
+#include <grub/normal.h>
+#include <grub/video.h>
+#include <grub/stringutil.h>
+#include <grub/gfxterm.h>
+#include <grub/bitmap.h>
+#include <grub/bitmap_scale.h>
+#include <grub/term.h>
+#include <grub/gfxwidgets.h>
+#include <grub/time.h>
+#include <grub/menu.h>
+#include <grub/menu_viewer.h>
+#include <grub/gfxmenu_view.h>
+
+/* Specifies a time (in ms) to delay so the user can read the message if a
+   default boot attempt fails and an attempt is made to fall back on
+   another entry.  */
+#define FALLBACK_MESSAGE_DELAY 2000
+
+/* Definition of the private representation of the view.  */
+struct grub_gfxmenu_view
+{
+  grub_video_rect_t screen;
+
+  int icon_width;
+  int icon_height;
+  int item_height;
+  int item_padding;
+  int item_icon_space;
+  int item_spacing;
+  grub_font_t title_font;
+  grub_font_t item_font;
+  grub_font_t selected_item_font;
+  grub_font_t status_font;
+  char *terminal_font_name;
+  grub_video_color_t title_color;
+  grub_video_color_t item_color;
+  grub_video_color_t selected_item_color;
+  grub_video_color_t status_color;
+  grub_video_color_t status_bg_color;
+  grub_video_color_t progress_bar_border_color;
+  grub_video_color_t progress_bar_fg_color;
+  grub_video_color_t progress_bar_bg_color;
+  struct grub_video_bitmap *desktop_image;
+  grub_video_color_t desktop_color;
+  grub_gfxmenu_box_t menu_box;
+  grub_gfxmenu_box_t selected_item_box;
+  grub_gfxmenu_box_t terminal_box;
+  char *title_text;
+  char *progress_message_text;
+
+  grub_gfxmenu_model_t model;
+};
+
+static void init_terminal (grub_gfxmenu_view_t view);
+static void destroy_terminal (void);
+static grub_err_t set_graphics_mode (void);
+static grub_err_t set_text_mode (void);
+
+/* Create a new view object, loading the theme specified by THEME_PATH and
+   associating MODEL with the view.  */
+grub_gfxmenu_view_t
+grub_gfxmenu_view_new (const char *theme_path, grub_gfxmenu_model_t model)
+{
+  grub_gfxmenu_view_t view;
+
+  view = grub_malloc (sizeof (*view));
+  if (! view)
+    return 0;
+
+  set_graphics_mode ();
+  grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
+  grub_video_get_viewport ((unsigned *) &view->screen.x,
+                           (unsigned *) &view->screen.y,
+                           (unsigned *) &view->screen.width,
+                           (unsigned *) &view->screen.height);
+
+  /* Clear the screen; there may be garbage left over in video memory, and
+     loading the menu style (particularly the background) can take a while. */
+  grub_video_fill_rect (grub_video_map_rgb (0, 0, 0),
+                        view->screen.x, view->screen.y,
+                        view->screen.width, view->screen.height);
+  grub_video_swap_buffers ();
+
+  grub_font_t default_font;
+  grub_video_color_t default_fg_color;
+  grub_video_color_t default_bg_color;
+
+  default_font = grub_font_get ("Helvetica 12");
+  default_fg_color = grub_video_map_rgb (0, 0, 0);
+  default_bg_color = grub_video_map_rgb (255, 255, 255);
+
+  view->icon_width = 32;
+  view->icon_height = 32;
+  view->item_height = 42;
+  view->item_padding = 14;
+  view->item_icon_space = 4;
+  view->item_spacing = 16;
+  view->title_font = default_font;
+  view->item_font = default_font;
+  view->selected_item_font = default_font;
+  view->status_font = default_font;
+  view->terminal_font_name = grub_strdup ("Fixed 10");
+  view->title_color = default_fg_color;
+  view->item_color = default_fg_color;
+  view->selected_item_color = default_fg_color;
+  view->status_color = default_bg_color;
+  view->status_bg_color = default_fg_color;
+  view->progress_bar_border_color = default_fg_color;
+  view->progress_bar_fg_color = grub_video_map_rgb (160, 160, 160);
+  view->progress_bar_bg_color = default_bg_color;
+  view->desktop_image = 0;
+  view->desktop_color = default_bg_color;
+  view->menu_box = 0;
+  view->selected_item_box = 0;
+  view->terminal_box = 0;
+  view->title_text = grub_strdup ("GRUB Boot Menu");
+  view->progress_message_text = 0;
+
+  view->model = model;
+
+  if (! grub_gfxmenu_view_load_theme (view, theme_path))
+    {
+      grub_gfxmenu_view_destroy (view);
+      return 0;
+    }
+
+  init_terminal (view);
+
+  return view;
+}
+
+/* Destroy the view object.  All used memory is freed.  */
+void
+grub_gfxmenu_view_destroy (grub_gfxmenu_view_t view)
+{
+  grub_video_bitmap_destroy (view->desktop_image);
+  if (view->menu_box)
+    view->menu_box->destroy (view->menu_box);
+  if (view->selected_item_box)
+    view->selected_item_box->destroy (view->selected_item_box);
+  if (view->terminal_box)
+    view->terminal_box->destroy (view->terminal_box);
+  grub_free (view->terminal_font_name);
+  grub_free (view->title_text);
+  grub_free (view->progress_message_text);
+  grub_free (view);
+
+  set_text_mode ();
+  destroy_terminal ();
+}
+
+/* Sets MESSAGE as the progress message for the view.  The string MESSAGE
+   must be heap-allocated and will be owned by VIEW.  MESSAGE can be 0, in
+   which case no message is displayed.  */
+static void
+set_progress_message (grub_gfxmenu_view_t view, char *message)
+{
+  grub_free (view->progress_message_text);
+  view->progress_message_text = message;
+}
+
+/* Parse a color string of the form "r, g, b".
+   Whitespace is insignificant.  */
+static grub_video_color_t
+parse_color (const char *s)
+{
+  int red = grub_strtoul (s, 0, 0);
+  if ((s = grub_strchr (s, ',')) == 0)
+    goto fail;
+  s++;
+  int green = grub_strtoul (s, 0, 0);
+  if ((s = grub_strchr (s, ',')) == 0)
+    goto fail;
+  s++;
+  int blue = grub_strtoul (s, 0, 0);
+  int alpha;
+  if ((s = grub_strchr (s, ',')) == 0)
+    alpha = 255;
+  else
+    {
+      s++;
+      alpha = grub_strtoul (s, 0, 0);
+    }
+  grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
+  return grub_video_map_rgba (red, green, blue, alpha);
+
+fail:
+  return 0;
+}
+
+/* Construct a new box widget using PATTERN to find the pixmap files for it,
+   storing the new widget at *BOXPTR.  PATTERN should be of the form:
+   "somewhere/style*.png".  The '*' then gets substituted with the various
+   pixmap names that the widget uses.
+   Return zero on failure, nonzero on success.  */
+static int
+theme_set_box (grub_gfxmenu_box_t *boxptr,
+               char *pattern, const char *theme_dir)
+{
+  char *abspattern;
+  char *prefix;
+  char *suffix;
+  char *star;
+  grub_gfxmenu_box_t box;
+
+  abspattern = grub_resolve_relative_path (theme_dir, pattern);
+  if (! abspattern)
+    return 0;
+
+  star = grub_strchr (abspattern, '*');
+  if (! star)
+    {
+      grub_free (abspattern);
+      return 0;
+    }
+
+  /* Prefix:  Get the part before the '*'.  */
+  prefix = grub_malloc (star - abspattern + 1);
+  if (! prefix)
+    {
+      grub_free (abspattern);
+      return 0;
+    }
+
+  grub_memcpy (prefix, abspattern, star - abspattern);
+  prefix[star - abspattern] = '\0';
+
+  /* Suffix:  Everything after the '*' is the suffix.  */
+  suffix = star + 1;
+
+  box = grub_gfxmenu_create_box (prefix, suffix);
+  grub_free (abspattern);   /* Note: suffix, star point into abspattern.  */
+  grub_free (prefix);
+  if (! box)
+    return 0;
+
+  if (*boxptr)
+    (*boxptr)->destroy (*boxptr);
+  *boxptr = box;
+  return 1;
+
+}
+
+/* Set the specified property NAME on the view to the given string VALUE.
+   This function takes ownership of both NAME and VALUE, so the caller
+   should pass pointers to new heap-allocated strings.  */
+static void
+theme_set_string (grub_gfxmenu_view_t view, char *name, char *value,
+                  const char *theme_dir)
+{
+  if (! grub_strcmp ("title-font", name))
+    view->title_font = grub_font_get (value);
+  else if (! grub_strcmp ("item-font", name))
+    view->item_font = grub_font_get (value);
+  else if (! grub_strcmp ("selected-item-font", name))
+    view->selected_item_font = grub_font_get (value);
+  else if (! grub_strcmp ("status-font", name))
+    view->status_font = grub_font_get (value);
+  else if (! grub_strcmp ("terminal-font", name))
+    {
+      grub_free (view->terminal_font_name);
+      view->terminal_font_name = value;
+      value = 0;  /* Prevent value from being freed below.  */
+    }
+  else if (! grub_strcmp ("title-color", name))
+    view->title_color = parse_color (value);
+  else if (! grub_strcmp ("item-color", name))
+    view->item_color = parse_color (value);
+  else if (! grub_strcmp ("selected-item-color", name))
+    view->selected_item_color = parse_color (value);
+  else if (! grub_strcmp ("status-color", name))
+    view->status_color = parse_color (value);
+  else if (! grub_strcmp ("status-bg-color", name))
+    view->status_bg_color = parse_color (value);
+  else if (! grub_strcmp ("progress-bar-border-color", name))
+    view->progress_bar_border_color = parse_color (value);
+  else if (! grub_strcmp ("progress-bar-fg-color", name))
+    view->progress_bar_fg_color = parse_color (value);
+  else if (! grub_strcmp ("progress-bar-bg-color", name))
+    view->progress_bar_bg_color = parse_color (value);
+  else if (! grub_strcmp ("desktop-image", name))
+    {
+      struct grub_video_bitmap *raw_bitmap;
+      struct grub_video_bitmap *scaled_bitmap;
+      char *path;
+      path = grub_resolve_relative_path (theme_dir, value);
+      if (! path)
+        goto fail;
+      if (grub_video_bitmap_load (&raw_bitmap, path) != GRUB_ERR_NONE)
+        {
+          grub_free (path);
+          goto fail;
+        }
+      grub_free(path);
+      grub_video_bitmap_create_scaled (&scaled_bitmap,
+                                       view->screen.width,
+                                       view->screen.height,
+                                       raw_bitmap,
+                                       GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST);
+      grub_video_bitmap_destroy (raw_bitmap);
+      if (!scaled_bitmap)
+        goto fail;
+
+      grub_video_bitmap_destroy (view->desktop_image);
+      view->desktop_image = scaled_bitmap;
+    }
+  else if (! grub_strcmp ("desktop-color", name))
+    view->desktop_color = parse_color (value);
+  else if (! grub_strcmp ("menu-box", name))
+    theme_set_box (&view->menu_box, value, theme_dir);
+  else if (! grub_strcmp ("selected-item-box", name))
+    theme_set_box (&view->selected_item_box, value, theme_dir);
+  else if (! grub_strcmp ("terminal-box", name))
+    theme_set_box (&view->terminal_box, value, theme_dir);
+  else if (! grub_strcmp ("title-text", name))
+    {
+      grub_free (view->title_text);
+      view->title_text = value;
+      value = 0;  /* Prevent value from being freed below.  */
+    }
+
+fail:
+  grub_free (value);
+  grub_free (name);
+}
+
+/* Set the specified property NAME on the view to the given numeric VALUE.
+   This function takes ownership NAME, so the caller should pass a pointer
+   to a new heap-allocated string.  */
+static void
+theme_set_number (grub_gfxmenu_view_t view, char *name, int value)
+{
+  if (! grub_strcmp ("icon-width", name))
+    view->icon_width = value;
+  else if (! grub_strcmp ("icon-height", name))
+    view->icon_height = value;
+  else if (! grub_strcmp ("item-height", name))
+    view->item_height = value;
+  else if (! grub_strcmp ("item-padding", name))
+    view->item_padding = value;
+  else if (! grub_strcmp ("item-icon-space", name))
+    view->item_icon_space = value;
+  else if (! grub_strcmp ("item-spacing", name))
+    view->item_spacing = value;
+
+  grub_free (name);
+}
+
+/* Set properties on the view based on settings from the specified
+   theme file.  Returns nonzero on success, zero on failure.  */
+int
+grub_gfxmenu_view_load_theme (grub_gfxmenu_view_t view, const char *theme_path)
+{
+  char *theme_dir;
+  grub_file_t file;
+  char *buf;
+  int pos;
+  int len;
+
+  theme_dir = grub_get_dirname (theme_path);
+
+  file = grub_file_open (theme_path);
+  if (!file)
+    {
+      grub_free (theme_dir);
+      return 0;
+    }
+
+  len = grub_file_size (file);
+  buf = grub_malloc (len);
+  if (! buf)
+    {
+      grub_file_close (file);
+      grub_free (theme_dir);
+      return 0;
+    }
+  if (grub_file_read (file, buf, len) != len)
+    {
+      grub_free (buf);
+      grub_file_close (file);
+      grub_free (theme_dir);
+      return 0;
+    }
+
+  pos = 0;
+  while (pos < len)
+    {
+      /* Skip comments (lines beginning with #).  */
+      if (pos < len && buf[pos] == '#')
+        goto nextline;
+
+      /* Get name.  */
+      /* Find a word character.  */
+      while (pos < len && grub_isspace(buf[pos]))
+        pos++;
+      int name_start = pos;
+      /* Find the end of the name.  */
+      while (pos < len
+             && (grub_isalpha(buf[pos])
+                 || grub_isdigit(buf[pos])
+                 || buf[pos] == '_'
+                 || buf[pos] == '-'))
+        pos++;
+      int name_end = pos;
+
+      if (name_end - name_start < 1)
+        goto nextline;
+
+      /* Skip whitespace before separator.  */
+      while (pos < len
+             && (buf[pos] == ' '
+                 || buf[pos] == '\t'
+                 || buf[pos] == '\f'))
+        pos++;
+
+      /* Read separator.  */
+      if (buf[pos] != ':')
+        goto nextline;
+
+      pos++;  /* Skip separator.  */
+
+      /* Skip whitespace after separator.  */
+      while (pos < len
+             && (buf[pos] == ' '
+                 || buf[pos] == '\t'
+                 || buf[pos] == '\f'))
+        pos++;
+
+      /* Get the value based on its type.  */
+      if (pos < len && buf[pos] == '"')
+        {
+          /* String value.  (e.g., '"My string"')  */
+
+          int value_start;
+          int value_end;
+
+          /* Skip the opening quotation mark.  */
+          pos++;
+          /* Get string value.  */
+          value_start = pos;
+          /* Find the ending quotation mark.  */
+          while (pos < len
+                 && !(buf[pos] == '"'
+                      || buf[pos] == '\n'))
+            pos++;
+          value_end = pos;
+          theme_set_string (view,
+                            grub_new_substring (buf, name_start, name_end),
+                            grub_new_substring (buf, value_start, value_end),
+                            theme_dir);
+        }
+      else if (pos < len && grub_isdigit(buf[pos]))
+        {
+          /* Numeric value.  (e.g., '123')  */
+
+          int value_start;
+          int value_end;
+          char *value_str;
+          int value;
+
+          /* Get numeric value.  */
+          value_start = pos;
+          /* Find the end of the digit sequence.  */
+          while (pos < len && grub_isdigit(buf[pos]))
+            pos++;
+          value_end = pos;
+          value_str = grub_new_substring (buf, value_start, value_end);
+          if (!value_str)
+            continue;
+          value = grub_strtoul (value_str, 0, 0);
+          grub_free (value_str);
+          theme_set_number (view,
+                            grub_new_substring (buf, name_start, name_end),
+                            value);
+        }
+
+nextline:
+      /* Eat characters up to the newline.  */
+      while (pos < len && buf[pos] != '\n')
+        pos++;
+      pos++;   /* Eat the newline.  */
+    }
+
+  grub_free (buf);
+  grub_file_close (file);
+  grub_free (theme_dir);
+  return 1;
+}
+
+static void
+draw_background (grub_gfxmenu_view_t view)
+{
+  if (view->desktop_image)
+    {
+      struct grub_video_bitmap *img = view->desktop_image;
+      grub_video_blit_bitmap (img, GRUB_VIDEO_BLIT_REPLACE,
+                              view->screen.x, view->screen.y, 0, 0,
+                              grub_video_bitmap_get_width (img),
+                              grub_video_bitmap_get_height (img));
+    }
+  else
+    {
+      grub_video_fill_rect (view->desktop_color,
+                            view->screen.x, view->screen.y,
+                            view->screen.width, view->screen.height);
+    }
+}
+
+static 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 = view->item_padding;
+  int icon_text_space = view->item_icon_space;
+  int item_vspace = view->item_spacing;
+
+  int ascent = grub_font_get_ascent (view->item_font);
+  int descent = grub_font_get_descent (view->item_font);
+  int item_height = view->item_height;
+
+  int num_items = grub_gfxmenu_model_get_num_entries (view->model);
+  grub_video_rect_t r;
+  r.width = view->screen.width * 4 / 5;
+  /* Set the menu box height to fit the items.  */
+  r.height = (item_height * num_items
+              + item_vspace * (num_items - 1)
+              + 2 * boxpad);
+  r.x = (view->screen.width - r.width) / 2;
+  r.y = (view->screen.height - r.height) / 2;
+  view->menu_box->set_content_size (view->menu_box, r.width, r.height);
+
+  int menu_box_left_pad = view->menu_box->get_left_pad (view->menu_box);
+  int menu_box_top_pad = view->menu_box->get_top_pad (view->menu_box);
+  view->menu_box->draw (view->menu_box,
+                        r.x - menu_box_left_pad, r.y - menu_box_top_pad);
+
+  int item_top = r.y + boxpad;
+  int item_left = r.x + boxpad;
+  int i;
+
+  for (i = 0; i < num_items; i++)
+    {
+      int is_selected =
+        (i == grub_gfxmenu_model_get_selected_index (view->model));
+
+      if (is_selected)
+        {
+          view->selected_item_box->set_content_size (view->selected_item_box,
+                                                     r.width - 2 * boxpad,
+                                                     item_height);
+          int leftpad = view->selected_item_box->get_left_pad (view->selected_item_box);
+          int toppad = view->selected_item_box->get_top_pad (view->selected_item_box);
+          view->selected_item_box->draw (view->selected_item_box,
+                                         item_left - leftpad,
+                                         item_top - toppad);
+        }
+
+      struct grub_video_bitmap *icon;
+      if ((icon = get_item_icon (view, i)) != 0)
+        grub_video_blit_bitmap (icon, GRUB_VIDEO_BLIT_BLEND,
+                                item_left,
+                                item_top + (item_height - view->icon_height) / 2,
+                                0, 0, view->icon_width, view->icon_height);
+
+      const char *item_title =
+        grub_gfxmenu_model_get_entry_title (view->model, i);
+      grub_video_draw_string (item_title,
+                              (is_selected
+                               ? view->selected_item_font
+                               : view->item_font),
+                              (is_selected
+                               ? view->selected_item_color
+                               : view->item_color),
+                              item_left + view->icon_width + icon_text_space,
+                              (item_top + (item_height - (ascent + descent))
+                               / 2 + ascent));
+
+      item_top += item_height + item_vspace;
+    }
+}
+
+static void
+draw_title (grub_gfxmenu_view_t view)
+{
+  if (! view->title_text)
+    return;
+
+  /* Center the title. */
+  int title_width = grub_font_get_string_width (view->title_font,
+                                                view->title_text);
+  int x = (view->screen.width - title_width) / 2;
+  int y = 40 + grub_font_get_ascent (view->title_font);
+  grub_video_draw_string (view->title_text,
+                          view->title_font, view->title_color,
+                          x, y);
+}
+
+static void
+draw_status (grub_gfxmenu_view_t view)
+{
+  int descent = grub_font_get_descent (view->status_font);
+  int ascent = grub_font_get_ascent (view->status_font);
+  int vpad = 5;
+  int textheight = descent + ascent + 1;
+  int h = 2 * vpad + 2 * textheight;
+
+  grub_video_fill_rect (view->status_bg_color,
+                        0, view->screen.height - h,
+                        view->screen.width, view->screen.height - 1);
+
+  int texty = view->screen.height - h + vpad + ascent;
+  grub_video_draw_string ("Select an item with the arrow keys and "
+                          "press Enter to boot.",
+                          view->status_font, view->status_color, 30, texty);
+  texty += textheight;
+  grub_video_draw_string ("Press:  'c' for command line; 't' to switch to "
+                          "non-graphical menu.",
+                          view->status_font, view->status_color, 30, texty);
+}
+
+static void
+draw_timeout (grub_gfxmenu_view_t view)
+{
+  int timeout = grub_gfxmenu_model_get_timeout_ms (view->model);
+  if (timeout == -1)
+    return;
+
+  int remaining = grub_gfxmenu_model_get_timeout_remaining_ms (view->model);
+  if (remaining < 0)
+    remaining = 0;
+
+  int t = timeout - remaining;
+
+  /* Set the timeout bar's frame.  */
+  grub_video_rect_t f;
+  f.width = view->screen.width * 3 / 5;
+  f.height = view->screen.height / 25;
+  f.x = view->screen.x + (view->screen.width - f.width) / 2;
+  f.y = view->screen.y + view->screen.height - 90;
+
+  /* First attempt just uses filled rectangles;
+     TODO we should enhance with a pixmap themed progress bar component.  */
+
+  /* Border.  */
+  grub_video_fill_rect (view->progress_bar_border_color,
+                        f.x - 1, f.y - 1,
+                        f.width + 2, f.height + 2);
+
+  /* Bar background.  */
+  int barwidth = f.width * t / timeout;
+  grub_video_fill_rect (view->progress_bar_bg_color,
+                        f.x + barwidth, f.y,
+                        f.width - barwidth, f.height);
+
+  /* Bar foreground.  */
+  grub_video_fill_rect (view->progress_bar_fg_color,
+                        f.x, f.y,
+                        barwidth, f.height);
+
+  char *text = grub_malloc (200);
+  if (!text)
+    return;
+  int seconds_remaining_rounded_up = (remaining + 999) / 1000;
+  grub_sprintf (text,
+                "The highlighted entry will be booted automatically in %d s.",
+                seconds_remaining_rounded_up);
+  grub_font_t font = view->status_font;
+  grub_video_color_t color = view->progress_bar_border_color;
+
+  /* Center the text. */
+  int text_width = grub_font_get_string_width (font, text);
+  int x = f.x + (f.width - text_width) / 2;
+  int y = (f.y + (f.height - grub_font_get_descent (font)) / 2
+           + grub_font_get_ascent (font) / 2);
+  grub_video_draw_string (text, font, color, x, y);
+  grub_free (text);
+}
+
+static void
+draw_message (grub_gfxmenu_view_t view)
+{
+  char *text = view->progress_message_text;
+  if (! text)
+    return;
+
+  grub_font_t font = view->status_font;
+  grub_video_color_t color = view->status_color;
+
+  /* Set the timeout bar's frame.  */
+  grub_video_rect_t f;
+  f.width = view->screen.width * 4 / 5;
+  f.height = 50;
+  f.x = view->screen.x + (view->screen.width - f.width) / 2;
+  f.y = view->screen.y + view->screen.height - 90 - 20 - f.height;
+
+  /* Border.  */
+  grub_video_fill_rect (color,
+                        f.x-1, f.y-1, f.width+2, f.height+2);
+  /* Fill.  */
+  grub_video_fill_rect (view->status_bg_color,
+                        f.x, f.y, f.width, f.height);
+
+  /* Center the text. */
+  int text_width = grub_font_get_string_width (font, text);
+  int x = f.x + (f.width - text_width) / 2;
+  int y = (f.y + (f.height - grub_font_get_descent (font)) / 2
+           + grub_font_get_ascent (font) / 2);
+  grub_video_draw_string (text, font, color, x, y);
+}
+
+
+void
+grub_gfxmenu_view_draw (grub_gfxmenu_view_t view)
+{
+  grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
+  draw_background (view);
+  draw_menu (view);
+  draw_title (view);
+  draw_status (view);
+  draw_timeout (view);
+  draw_message (view);
+}
+
+static grub_err_t
+set_graphics_mode (void)
+{
+  const char *doublebuf_str = grub_env_get ("doublebuffering");
+  int doublebuf_flags =
+    (doublebuf_str && doublebuf_str[0] == 'n')
+    ? 0
+    : GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED;
+
+  const char *modestr = grub_env_get ("gfxmode");
+  if (grub_video_setup_preferred_mode (modestr, doublebuf_flags, 640, 480)
+      != GRUB_ERR_NONE)
+    return grub_errno;
+
+  return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+set_text_mode (void)
+{
+  return grub_video_restore ();
+}
+
+static int term_target_width;
+static int term_target_height;
+static struct grub_video_render_target *term_target;
+static int term_initialized;
+static grub_term_t term_original;
+static grub_gfxmenu_view_t term_view;
+
+static void
+repaint_terminal (int x __attribute ((unused)),
+                  int y __attribute ((unused)),
+                  int width __attribute ((unused)),
+                  int height __attribute ((unused)))
+{
+  if (! term_view)
+    return;
+
+  grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
+  grub_gfxmenu_view_draw (term_view);
+  grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
+
+  int termx = term_view->screen.x
+    + term_view->screen.width * (10 - 7) / 10 / 2;
+  int termy = term_view->screen.y
+    + term_view->screen.height * (10 - 7) / 10 / 2;
+
+  grub_gfxmenu_box_t term_box = term_view->terminal_box;
+  if (term_box)
+    {
+      term_box->set_content_size (term_box,
+                                  term_target_width, term_target_height);
+
+      term_box->draw (term_box,
+                      termx - term_box->get_left_pad (term_box),
+                      termy - term_box->get_top_pad (term_box));
+    }
+
+  grub_video_blit_render_target (term_target, GRUB_VIDEO_BLIT_REPLACE,
+                                 termx, termy,
+                                 0, 0, term_target_width, term_target_height);
+  grub_video_swap_buffers ();
+}
+
+static void
+init_terminal (grub_gfxmenu_view_t view)
+{
+  term_original = grub_term_get_current ();
+
+  term_target_width = view->screen.width * 7 / 10;
+  term_target_height = view->screen.height * 7 / 10;
+
+  grub_video_create_render_target (&term_target,
+                                   term_target_width,
+                                   term_target_height,
+                                   GRUB_VIDEO_MODE_TYPE_RGB
+                                   | GRUB_VIDEO_MODE_TYPE_ALPHA);
+  if (grub_errno != GRUB_ERR_NONE)
+    return;
+
+  /* Note: currently there is no API for changing the gfxterm font
+     on the fly, so whatever font the initially loaded theme specifies
+     will be permanent.  */
+  grub_gfxterm_init_window (term_target, 0, 0,
+                            term_target_width, term_target_height,
+                            view->terminal_font_name, 3);
+  if (grub_errno != GRUB_ERR_NONE)
+    return;
+  term_initialized = 1;
+
+  /* XXX: store static pointer to the 'view' object so the repaint callback can access it.  */
+  term_view = view;
+  grub_gfxterm_set_repaint_callback (repaint_terminal);
+  grub_term_set_current (grub_gfxterm_get_term ());
+}
+
+static void destroy_terminal (void)
+{
+  term_view = 0;
+  if (term_initialized)
+    grub_gfxterm_destroy_window ();
+  grub_gfxterm_set_repaint_callback (0);
+  if (term_target)
+    grub_video_delete_render_target (term_target);
+  if (term_original)
+    grub_term_set_current (term_original);
+}
+
+
+static void
+notify_booting (void *userdata, grub_menu_entry_t entry)
+{
+  grub_gfxmenu_view_t view = (grub_gfxmenu_view_t) userdata;
+
+  char *s = grub_malloc (100 + grub_strlen (entry->title));
+  if (!s)
+    return;
+
+  grub_sprintf (s, "Booting '%s'", entry->title);
+  set_progress_message (view, s);
+  grub_gfxmenu_view_draw (view);
+  grub_video_swap_buffers ();
+}
+
+static void
+notify_fallback (void *userdata, grub_menu_entry_t entry)
+{
+  grub_gfxmenu_view_t view = (grub_gfxmenu_view_t) userdata;
+
+  char *s = grub_malloc (100 + grub_strlen (entry->title));
+  if (!s)
+    return;
+
+  grub_sprintf (s, "Falling back to '%s'", entry->title);
+  set_progress_message (view, s);
+  grub_gfxmenu_view_draw (view);
+  grub_video_swap_buffers ();
+  grub_millisleep (FALLBACK_MESSAGE_DELAY);
+}
+
+static void
+notify_execution_failure (void *userdata __attribute__ ((unused)))
+{
+}
+
+
+static struct grub_menu_execute_callback execute_callback =
+{
+  .notify_booting = notify_booting,
+  .notify_fallback = notify_fallback,
+  .notify_failure = notify_execution_failure
+};
+
+int
+grub_gfxmenu_view_execute_with_fallback (grub_gfxmenu_view_t view,
+                                         grub_menu_entry_t entry)
+{
+  grub_menu_execute_with_fallback (grub_gfxmenu_model_get_menu (view->model),
+                                   entry, &execute_callback, (void *) view);
+
+  if (set_graphics_mode () != GRUB_ERR_NONE)
+    return 0;  /* Failure.  */
+
+  /* If we returned, there was a failure.  */
+  set_progress_message (view, grub_strdup ("Unable to automatically boot."));
+  grub_gfxmenu_view_draw (view);
+  grub_video_swap_buffers ();
+  grub_millisleep (5000);
+  set_progress_message (view, 0);   /* Clear the message.  */
+
+  return 1;   /* Ok.  */
+}
+
+int
+grub_gfxmenu_view_execute_entry (grub_gfxmenu_view_t view,
+                                 grub_menu_entry_t entry)
+{
+  /* Currently we switch back to text mode by restoring
+     the original terminal before executing the menu entry.
+     It is hard to make it work when executing a menu entry
+     that switches video modes -- it using gfxterm in a
+     window, the repaint callback seems to crash GRUB. */
+  /* TODO: Determine if this works when 'gfxterm' was set as
+     the current terminal before invoking the gfxmenu. */
+  destroy_terminal ();
+
+  grub_menu_execute_entry (entry);
+  if (grub_errno != GRUB_ERR_NONE)
+    grub_wait_after_message ();
+
+  if (set_graphics_mode () != GRUB_ERR_NONE)
+    return 0;  /* Failure.  */
+
+  init_terminal (view);
+  return 1;   /* Ok.  */
+}
+
+void
+grub_gfxmenu_view_run_terminal (grub_gfxmenu_view_t view __attribute__((unused)))
+{
+  grub_cmdline_run (1);
+}
+

=== added file 'gfxmenu/widget-box.c'
--- gfxmenu/widget-box.c	1970-01-01 00:00:00 +0000
+++ gfxmenu/widget-box.c	2008-07-19 18:25:18 +0000
@@ -0,0 +1,244 @@
+/* widget_box.c - Pixmap-stylized box widget. */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2008  Free Software Foundation, Inc.
+ *
+ *  GRUB is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/types.h>
+#include <grub/misc.h>
+#include <grub/mm.h>
+#include <grub/err.h>
+#include <grub/video.h>
+#include <grub/bitmap.h>
+#include <grub/bitmap_scale.h>
+#include <grub/gfxwidgets.h>
+
+enum box_pixmaps
+{
+  BOX_PIXMAP_NW, BOX_PIXMAP_NE, BOX_PIXMAP_SE, BOX_PIXMAP_SW,
+  BOX_PIXMAP_N, BOX_PIXMAP_E, BOX_PIXMAP_S, BOX_PIXMAP_W,
+  BOX_PIXMAP_CENTER
+};
+
+static const char *box_pixmap_names[] = {
+  /* Corners: */
+  "nw", "ne", "se", "sw",
+  /* Sides: */
+  "n", "e", "s", "w",
+  /* Center: */
+  "c"
+};
+
+#define BOX_NUM_PIXMAPS (sizeof(box_pixmap_names)/sizeof(*box_pixmap_names))
+
+static void
+draw (grub_gfxmenu_box_t self, int x, int y)
+{
+  int height_n;
+  int height_s;
+  int height_e;
+  int height_w;
+  int width_n;
+  int width_s;
+  int width_e;
+  int width_w;
+  unsigned i;
+
+  /* Don't try to draw if any pixmaps are null, except the center. */
+  for (i = 0; i < BOX_NUM_PIXMAPS; i++)
+    {
+      if (i != BOX_PIXMAP_CENTER && self->scaled_pixmaps[i] == 0)
+        return;
+    }
+
+  height_n = grub_video_bitmap_get_height (self->scaled_pixmaps[BOX_PIXMAP_N]);
+  height_s = grub_video_bitmap_get_height (self->scaled_pixmaps[BOX_PIXMAP_S]);
+  height_e = grub_video_bitmap_get_height (self->scaled_pixmaps[BOX_PIXMAP_E]);
+  height_w = grub_video_bitmap_get_height (self->scaled_pixmaps[BOX_PIXMAP_W]);
+  width_n = grub_video_bitmap_get_width (self->scaled_pixmaps[BOX_PIXMAP_N]);
+  width_s = grub_video_bitmap_get_width (self->scaled_pixmaps[BOX_PIXMAP_S]);
+  width_e = grub_video_bitmap_get_width (self->scaled_pixmaps[BOX_PIXMAP_E]);
+  width_w = grub_video_bitmap_get_width (self->scaled_pixmaps[BOX_PIXMAP_W]);
+
+  /* Draw sides.  */
+  grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_N], 
+                          GRUB_VIDEO_BLIT_BLEND,
+                          x + width_w, y, 0, 0, width_n, height_n);
+  grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_S],
+                          GRUB_VIDEO_BLIT_BLEND,
+                          x + width_w, y + height_n + height_w,
+                          0, 0, width_s, height_s);
+  grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_E],
+                          GRUB_VIDEO_BLIT_BLEND,
+                          x + width_w + width_n, y + height_n,
+                          0, 0, width_e, height_e);
+  grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_W],
+                          GRUB_VIDEO_BLIT_BLEND,
+                          x, y + height_n, 0, 0, width_w, height_w);
+
+  /* Draw corners.  */
+  grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_NW],
+                          GRUB_VIDEO_BLIT_BLEND,
+                          x, y, 0, 0, width_w, height_n);
+  grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_NE],
+                          GRUB_VIDEO_BLIT_BLEND,
+                          x + width_w + width_n, y, 
+                          0, 0, width_e, height_n);
+  grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_SE],
+                          GRUB_VIDEO_BLIT_BLEND,
+                          x + width_w + width_n, y + height_n + height_e, 
+                          0, 0, width_e, height_s);
+  grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_SW],
+                          GRUB_VIDEO_BLIT_BLEND,
+                          x, y + height_n + height_w, 
+                          0, 0, width_w, height_s);
+
+  /* Draw center.  */
+  if (self->scaled_pixmaps[BOX_PIXMAP_CENTER])
+    grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_CENTER],
+                            GRUB_VIDEO_BLIT_BLEND,
+                            x + width_w, y + height_n,
+                            0, 0, self->width, self->height);
+}
+
+static void
+scale_pixmap (grub_gfxmenu_box_t self, int i, int w, int h)
+{
+  struct grub_video_bitmap **scaled = &self->scaled_pixmaps[i];
+  struct grub_video_bitmap *raw = self->raw_pixmaps[i];
+
+  if (raw == 0)
+    return;
+
+  if (w == -1)
+    w = grub_video_bitmap_get_width (raw);
+  if (h == -1)
+    h = grub_video_bitmap_get_height (raw);
+
+  if (*scaled == 0 
+      || ((int) grub_video_bitmap_get_width (*scaled) != w)
+      || ((int) grub_video_bitmap_get_height (*scaled) != h))
+    {
+      if (*scaled)
+        {
+          grub_video_bitmap_destroy (*scaled);
+          *scaled = 0;
+        }
+      grub_video_bitmap_create_scaled (scaled, w, h, raw, 
+                                       GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST);
+    }
+}
+
+static void
+set_content_size (grub_gfxmenu_box_t self,
+                  int width, int height)
+{
+  self->width = width;
+  self->height = height;
+  /* Resize sides to match the width and height.  */
+  /* It is assumed that the corners width/height match the adjacent sides.  */
+
+  /* Resize N and S sides to match width.  */
+  scale_pixmap(self, BOX_PIXMAP_N, width, -1);
+  scale_pixmap(self, BOX_PIXMAP_S, width, -1);
+
+  /* Resize E and W sides to match height.  */
+  scale_pixmap(self, BOX_PIXMAP_E, -1, height);
+  scale_pixmap(self, BOX_PIXMAP_W, -1, height);
+
+  /* Don't scale the corners--they are assumed to match the sides. */
+  scale_pixmap(self, BOX_PIXMAP_NW, -1, -1);
+  scale_pixmap(self, BOX_PIXMAP_SW, -1, -1);
+  scale_pixmap(self, BOX_PIXMAP_NE, -1, -1);
+  scale_pixmap(self, BOX_PIXMAP_SE, -1, -1);
+
+  /* Scale the center area. */
+  scale_pixmap(self, BOX_PIXMAP_CENTER, width, height);
+}
+
+static int
+get_left_pad (grub_gfxmenu_box_t self)
+{
+  return grub_video_bitmap_get_width (self->raw_pixmaps[BOX_PIXMAP_W]);
+}
+
+static int
+get_top_pad (grub_gfxmenu_box_t self)
+{
+  return grub_video_bitmap_get_height (self->raw_pixmaps[BOX_PIXMAP_N]);
+}
+
+static void
+destroy (grub_gfxmenu_box_t self)
+{
+  unsigned i;
+  for (i = 0; i < BOX_NUM_PIXMAPS; i++)
+    {
+      if (self->raw_pixmaps[i])
+        grub_video_bitmap_destroy(self->raw_pixmaps[i]);
+      self->raw_pixmaps[i] = 0;
+
+      if (self->scaled_pixmaps[i])
+        grub_video_bitmap_destroy(self->scaled_pixmaps[i]);
+      self->scaled_pixmaps[i] = 0;
+    }
+  grub_free (self->raw_pixmaps);
+  self->raw_pixmaps = 0;
+  grub_free (self->scaled_pixmaps);
+  self->scaled_pixmaps = 0;
+
+  grub_free (self);             /* Free self:  must be the last step! */
+}
+
+
+grub_gfxmenu_box_t
+grub_gfxmenu_create_box (const char *pixmaps_prefix,
+                         const char *pixmaps_suffix)
+{
+  char path[200];
+  unsigned i;
+  grub_gfxmenu_box_t box;
+
+  box = (grub_gfxmenu_box_t) grub_malloc (sizeof (*box));
+  if (!box)
+    return 0;
+  box->width = 0;
+  box->height = 0;
+  box->raw_pixmaps =
+    (struct grub_video_bitmap **) 
+    grub_malloc (BOX_NUM_PIXMAPS * sizeof (struct grub_video_bitmap *));
+  box->scaled_pixmaps =
+    (struct grub_video_bitmap **)
+    grub_malloc (BOX_NUM_PIXMAPS * sizeof (struct grub_video_bitmap *));
+
+  for (i = 0; i < BOX_NUM_PIXMAPS; i++)
+    {
+      /* TODO XXX dynamically allocate PATH, making sure it's large enough */
+      grub_sprintf (path, "%s%s%s",
+                    pixmaps_prefix, box_pixmap_names[i], pixmaps_suffix);
+      grub_video_bitmap_load (&box->raw_pixmaps[i], path);
+      box->scaled_pixmaps[i] = 0;
+    }
+
+  box->draw = draw;
+  box->set_content_size = set_content_size;
+  box->get_left_pad = get_left_pad;
+  box->get_top_pad = get_top_pad;
+  box->destroy = destroy;
+
+  return box;
+}
+

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

=== added file 'include/grub/bufio.h'
--- include/grub/bufio.h	1970-01-01 00:00:00 +0000
+++ include/grub/bufio.h	2008-07-27 23:56:02 +0000
@@ -0,0 +1,28 @@
+/* bufio.h - prototypes for bufio */
+/*
+ *  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_BUFIO_H
+#define GRUB_BUFIO_H	1
+
+#include <grub/file.h>
+
+grub_file_t grub_bufio_open (grub_file_t io);
+grub_file_t grub_buffile_open (const char *name);
+
+#endif /* ! GRUB_BUFIO_H */

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

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

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

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

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

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

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

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

=== added file 'include/grub/i386/tsc.h'
--- include/grub/i386/tsc.h	1970-01-01 00:00:00 +0000
+++ include/grub/i386/tsc.h	2008-07-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-26 23:37:04 +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,27 @@
 /* To exit from the normal mode.  */
 extern grub_jmp_buf grub_exit_env;
 
+extern struct grub_menu_viewer grub_normal_terminal_menu_viewer;
+
+typedef struct grub_menu_execute_callback
+{
+  void (*notify_booting) (void *userdata, grub_menu_entry_t entry);
+  void (*notify_fallback) (void *userdata, grub_menu_entry_t entry);
+  void (*notify_failure) (void *userdata);
+} 
+*grub_menu_execute_callback_t;
+
 void grub_enter_normal_mode (const char *config);
 void grub_normal_execute (const char *config, int nested);
-void grub_menu_run (grub_menu_t menu, int nested);
+void grub_menu_execute_with_fallback (grub_menu_t menu,
+                                      grub_menu_entry_t entry,
+                                      grub_menu_execute_callback_t callback,
+                                      void *callback_data);
 void grub_menu_entry_run (grub_menu_entry_t entry);
+int grub_menu_get_default_entry_index (grub_menu_t menu);
+int grub_menu_get_timeout (void);
+void grub_menu_set_timeout (int timeout);
+void grub_menu_execute_entry(grub_menu_entry_t entry);
 void grub_cmdline_run (int nested);
 int grub_cmdline_get (const char *prompt, char cmdline[], unsigned max_len,
 		      int echo_char, int readline);

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

=== modified file 'include/grub/time.h'
--- include/grub/time.h	2007-10-22 19:02:16 +0000
+++ include/grub/time.h	2008-07-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 */

=== added file 'io/bufio.c'
--- io/bufio.c	1970-01-01 00:00:00 +0000
+++ io/bufio.c	2008-07-27 23:56:02 +0000
@@ -0,0 +1,174 @@
+/* bufio.c - buffered io access */
+/*
+ *  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/err.h>
+#include <grub/types.h>
+#include <grub/mm.h>
+#include <grub/misc.h>
+#include <grub/fs.h>
+#include <grub/bufio.h>
+
+#define GRUB_BUFIO_BUFSIZE	8192
+
+struct grub_bufio
+{
+  grub_file_t file;
+  grub_size_t length;
+  char buffer[GRUB_BUFIO_BUFSIZE];
+};
+typedef struct grub_bufio *grub_bufio_t;
+
+static struct grub_fs grub_bufio_fs;
+
+grub_file_t
+grub_bufio_open (grub_file_t io)
+{
+  grub_file_t file;
+  grub_bufio_t bufio = 0;
+
+  file = (grub_file_t) grub_malloc (sizeof (*file));
+  if (! file)
+    return 0;
+
+  bufio = grub_malloc (sizeof (*bufio));
+  if (! bufio)
+    {
+      grub_free (file);
+      return 0;
+    }
+
+  grub_memset (bufio, 0, sizeof (*bufio));
+  bufio->file = io;
+  bufio->file->offset = -1;
+
+  file->device = io->device;
+  file->offset = 0;
+  file->size = io->size;
+  file->data = bufio;
+  file->read_hook = 0;
+  file->fs = &grub_bufio_fs;
+
+  return file;
+}
+
+grub_file_t
+grub_buffile_open (const char *name)
+{
+  grub_file_t io, file;
+
+  io = grub_file_open (name);
+  if (! io)
+    return 0;
+
+  file = grub_bufio_open (io);
+  if (! file)
+    {
+      grub_file_close (io);
+      return 0;
+    }
+
+  return file;
+}
+
+static grub_ssize_t
+grub_bufio_read (grub_file_t file, char *buf, grub_size_t len)
+{
+  grub_size_t res = len;
+  grub_bufio_t bufio = file->data;
+  grub_size_t pos = file->offset & (GRUB_BUFIO_BUFSIZE - 1);
+
+  if (bufio->file->offset == file->offset - pos)
+    {
+      grub_size_t n;
+
+      n = bufio->length - pos;
+      if (n > len)
+        n = len;
+
+      grub_memcpy (buf, &bufio->buffer[pos], n);
+      len -= n;
+
+      if (! len)
+        return res;
+
+      buf += n;
+      pos = 0;
+      bufio->file->offset += bufio->length;
+    }
+  else
+    bufio->file->offset = file->offset - pos;
+
+  while (pos + len >= GRUB_BUFIO_BUFSIZE)
+    {
+      grub_size_t n;
+
+      n = GRUB_BUFIO_BUFSIZE - pos;
+      bufio->file->offset += pos;
+      bufio->file->fs->read (bufio->file, buf, n);
+      if (grub_errno)
+        return -1;
+
+      buf += n;
+      len -= n;
+      bufio->file->offset += n;
+      pos = 0;
+    }
+
+  if (! len)
+    {
+      bufio->file->offset = -1;
+      return res;
+    }
+
+  bufio->length = bufio->file->size - bufio->file->offset;
+  if (bufio->length > GRUB_BUFIO_BUFSIZE)
+    bufio->length = GRUB_BUFIO_BUFSIZE;
+
+  bufio->file->fs->read (bufio->file, bufio->buffer, bufio->length);
+  if (grub_errno)
+    return -1;
+
+  grub_memcpy (buf, &bufio->buffer[pos], len);
+
+  return res;
+}
+
+static grub_err_t
+grub_bufio_close (grub_file_t file)
+{
+  grub_bufio_t bufio = file->data;
+
+  grub_file_close (bufio->file);
+  grub_free (bufio);
+
+  file->device = 0;
+
+  return grub_errno;
+}
+
+static struct grub_fs grub_bufio_fs =
+  {
+    .name = "bufio",
+    .dir = 0,
+    .open = 0,
+    .read = grub_bufio_read,
+    .close = grub_bufio_close,
+    .label = 0,
+    .next = 0
+  };

=== 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)
 {

=== 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-26 23:37:04 +0000
@@ -21,9 +21,11 @@
 #include <grub/misc.h>
 #include <grub/loader.h>
 #include <grub/mm.h>
+#include <grub/time.h>
 #include <grub/machine/time.h>
 #include <grub/env.h>
 #include <grub/script.h>
+#include <grub/menu_viewer.h>
 
 static grub_uint8_t grub_color_menu_normal;
 static grub_uint8_t grub_color_menu_highlight;
@@ -241,8 +243,8 @@
 
 /* Return the current timeout. If the variable "timeout" is not set or
    invalid, return -1.  */
-static int
-get_timeout (void)
+int
+grub_menu_get_timeout (void)
 {
   char *val;
   int timeout;
@@ -269,8 +271,8 @@
 }
 
 /* Set current timeout in the variable "timeout".  */
-static void
-set_timeout (int timeout)
+void
+grub_menu_set_timeout (int timeout)
 {
   /* Ignore TIMEOUT if it is zero, because it will be unset really soon.  */
   if (timeout > 0)
@@ -308,6 +310,57 @@
   return entry;
 }
 
+/* Get the default menu entry index.  */
+int
+grub_menu_get_default_entry_index (grub_menu_t menu)
+{
+  int i = get_entry_number ("default");
+
+  /* If DEFAULT_ENTRY is not within the menu entries, fall back to
+     the first entry.  */
+  if (i < 0 || i >= menu->size)
+    i = 0;
+
+  return i;
+}
+
+/* Get the first entry number from the variable NAME, which is a 
+   space-separated list of nonnegative integers.  The entry number which
+   is returned is stripped from the value of NAME.  */
+static int
+get_and_remove_first_entry_number (const char *name)
+{
+  char *val;
+  char *tail;
+  int entry;
+  
+  val = grub_env_get (name);
+  if (! val)
+    return -1;
+  
+  grub_error_push ();
+  
+  entry = (int) grub_strtoul (val, &tail, 0);
+
+  if (grub_errno == GRUB_ERR_NONE)
+    {
+      /* Skip whitespace to find the next digit.  */
+      while (*tail && grub_isspace (*tail))
+	tail++;
+      grub_env_set (name, tail);
+    }
+  else
+    {
+      grub_env_unset (name);
+      grub_errno = GRUB_ERR_NONE;
+      entry = -1;
+    }
+
+  grub_error_pop ();
+  
+  return entry;
+}
+
 static void
 print_timeout (int timeout, int offset, int second_stage)
 {
@@ -332,15 +385,10 @@
   
   first = 0;
   
-  default_entry = get_entry_number ("default");
-
-  /* If DEFAULT_ENTRY is not within the menu entries, fall back to
-     the first entry.  */
-  if (default_entry < 0 || default_entry >= menu->size)
-    default_entry = 0;
+  default_entry = grub_menu_get_default_entry_index (menu);
 
   /* If timeout is 0, drawing is pointless (and ugly).  */
-  if (get_timeout () == 0)
+  if (grub_menu_get_timeout () == 0)
     return default_entry;
 
   offset = default_entry;
@@ -359,15 +407,15 @@
   print_entries (menu, first, offset);
   grub_refresh ();
 
-  timeout = get_timeout ();
+  timeout = grub_menu_get_timeout ();
 
   if (timeout > 0)
     print_timeout (timeout, offset, 0);
 
-  while (1)
+  while (!grub_menu_viewer_should_return ())
     {
       int c;
-      timeout = get_timeout ();
+      timeout = grub_menu_get_timeout ();
       
       if (timeout > 0)
 	{
@@ -377,7 +425,7 @@
 	  if (current_time - saved_time >= GRUB_TICKS_PER_SECOND)
 	    {
 	      timeout--;
-	      set_timeout (timeout);
+	      grub_menu_set_timeout (timeout);
 	      saved_time = current_time;
 	      print_timeout (timeout, offset, 1);
 	    }
@@ -468,6 +516,10 @@
 		}
 	      goto refresh;
 	      
+	    case 't':
+	      grub_env_set ("menuviewer", "protomenu");
+	      goto refresh;
+
 	    default:
 	      break;
 	    }
@@ -476,13 +528,14 @@
 	}
     }
 
-  /* Never reach here.  */
+  /* Exit menu without activating an item.  This occurs if the user presses
+   * 't', switching to the graphical menu viewer.  */
   return -1;
 }
 
 /* Run a menu entry.  */
-static void
-run_menu_entry (grub_menu_entry_t entry)
+void
+grub_menu_execute_entry(grub_menu_entry_t entry)
 {
   grub_script_execute (entry->commands);
   
@@ -491,15 +544,80 @@
     grub_command_execute ("boot", 0);
 }
 
+/* Execute ENTRY from the menu MENU, falling back to entries specified
+   in the environment variable "fallback" if it fails.  CALLBACK is a 
+   pointer to a struct of function pointers which are used to allow the
+   caller provide feedback to the user.  */
 void
-grub_menu_run (grub_menu_t menu, int nested)
+grub_menu_execute_with_fallback (grub_menu_t menu,
+				 grub_menu_entry_t entry,
+				 grub_menu_execute_callback_t callback,
+				 void *callback_data)
+{
+  int fallback_entry;
+
+  callback->notify_booting (callback_data, entry);
+
+  grub_menu_execute_entry (entry);
+
+  /* Deal with fallback entries.  */
+  while ((fallback_entry = get_and_remove_first_entry_number ("fallback"))
+	 >= 0)
+    {
+      grub_print_error ();
+      grub_errno = GRUB_ERR_NONE;
+
+      entry = get_entry (menu, fallback_entry);
+      callback->notify_fallback (callback_data, entry);
+      grub_menu_execute_entry (entry);
+    }
+
+  if (grub_errno != GRUB_ERR_NONE)
+    callback->notify_failure (callback_data);
+}
+
+static void
+notify_booting (void *userdata __attribute__((unused)),
+		grub_menu_entry_t entry)
+{
+  grub_printf ("  Booting \'%s\'\n\n", entry->title);
+}
+
+static void
+notify_fallback (void *userdata __attribute__((unused)),
+		 grub_menu_entry_t entry)
+{
+  grub_printf ("\n  Falling back to \'%s\'\n\n", entry->title);
+  grub_millisleep (2000);
+}
+
+static void
+notify_execution_failure (void *userdata __attribute__((unused)))
+{
+  if (grub_errno != GRUB_ERR_NONE)
+    {
+      grub_print_error ();
+      grub_errno = GRUB_ERR_NONE;
+
+      grub_wait_after_message ();
+    }
+}
+
+static struct grub_menu_execute_callback execution_callback = 
+{
+  .notify_booting = notify_booting,
+  .notify_fallback = notify_fallback,
+  .notify_failure = notify_execution_failure
+};
+
+static grub_err_t
+show_menu (grub_menu_t menu, int nested)
 {
   while (1)
     {
       int boot_entry;
       grub_menu_entry_t e;
-      int fallback_entry;
-      
+
       boot_entry = run_menu (menu, nested);
       if (boot_entry < 0)
 	break;
@@ -507,34 +625,18 @@
       e = get_entry (menu, boot_entry);
       if (! e)
 	continue; /* Menu is empty.  */
-	
+
       grub_cls ();
       grub_setcursor (1);
 
-      grub_printf ("  Booting \'%s\'\n\n", e->title);
-  
-      run_menu_entry (e);
-
-      /* Deal with a fallback entry.  */
-      /* FIXME: Multiple fallback entries like GRUB Legacy.  */
-      fallback_entry = get_entry_number ("fallback");
-      if (fallback_entry >= 0)
-	{
-	  grub_print_error ();
-	  grub_errno = GRUB_ERR_NONE;
-	  
-	  e = get_entry (menu, fallback_entry);
-	  grub_env_unset ("fallback");
-	  grub_printf ("\n  Falling back to \'%s\'\n\n", e->title);
-	  run_menu_entry (e);
-	}
-
-      if (grub_errno != GRUB_ERR_NONE)
-	{
-	  grub_print_error ();
-	  grub_errno = GRUB_ERR_NONE;
-
-	  grub_wait_after_message ();
-	}
+      grub_menu_execute_with_fallback (menu, e, &execution_callback, 0);
     }
+
+  return GRUB_ERR_NONE;
 }
+
+struct grub_menu_viewer grub_normal_terminal_menu_viewer =
+{
+  .name = "terminal",
+  .show_menu = show_menu
+};

=== modified file 'term/gfxterm.c'
--- term/gfxterm.c	2008-01-01 12:02:07 +0000
+++ term/gfxterm.c	2008-07-19 19:56:06 +0000
@@ -28,14 +28,12 @@
 #include <grub/mm.h>
 #include <grub/env.h>
 #include <grub/video.h>
+#include <grub/gfxterm.h>
 #include <grub/bitmap.h>
+#include <grub/bitmap_scale.h>
 
 #define DEFAULT_VIDEO_WIDTH	640
 #define DEFAULT_VIDEO_HEIGHT	480
-#define DEFAULT_VIDEO_FLAGS	0
-
-#define DEFAULT_CHAR_WIDTH  	8
-#define DEFAULT_CHAR_HEIGHT 	16
 
 #define DEFAULT_BORDER_WIDTH	10
 
@@ -91,6 +89,9 @@
   unsigned int cursor_y;
   int cursor_state;
 
+  /* Font settings. */
+  grub_font_t font;
+
   /* Terminal color settings.  */
   grub_uint8_t standard_color_setting;
   grub_uint8_t normal_color_setting;
@@ -107,10 +108,20 @@
   struct grub_colored_char *text_buffer;
 };
 
+static int refcount;
+static struct grub_video_render_target *render_target;
+static grub_video_rect_t window;
 static struct grub_virtual_screen virtual_screen;
+static grub_gfxterm_repaint_callback_t repaint_callback;
+
+static grub_err_t init_window (struct grub_video_render_target *target,
+                               int x, int y, int width, int height, 
+                               const char *font_name, int border_width);
+
+static void destroy_window (void);
+
 
 static grub_dl_t my_mod;
-static struct grub_video_mode_info mode_info;
 
 static struct grub_video_render_target *text_layer;
 
@@ -170,18 +181,25 @@
 
 static grub_err_t
 grub_virtual_screen_setup (unsigned int x, unsigned int y,
-                           unsigned int width, unsigned int height)
+                           unsigned int width, unsigned int height,
+                           const char *font_name)
 {
   /* Free old virtual screen.  */
   grub_virtual_screen_free ();
 
   /* Initialize with default data.  */
+  virtual_screen.font = grub_font_get (font_name);
+  if (!virtual_screen.font)
+    return grub_error (GRUB_ERR_BAD_FONT,
+                       "No font loaded.");
   virtual_screen.width = width;
   virtual_screen.height = height;
   virtual_screen.offset_x = x;
   virtual_screen.offset_y = y;
-  virtual_screen.char_width = DEFAULT_CHAR_WIDTH;
-  virtual_screen.char_height = DEFAULT_CHAR_HEIGHT;
+  virtual_screen.char_width = 
+    grub_font_get_max_char_width (virtual_screen.font);
+  virtual_screen.char_height = 
+    grub_font_get_max_char_height (virtual_screen.font);
   virtual_screen.cursor_x = 0;
   virtual_screen.cursor_y = 0;
   virtual_screen.cursor_state = 1;
@@ -227,281 +245,129 @@
 }
 
 static grub_err_t
+init_window (struct grub_video_render_target *target,
+             int x, int y, int width, int height, 
+             const char *font_name, int border_width)
+{
+  /* Clean up any prior instance.  */
+  destroy_window ();
+
+  /* Create virtual screen.  */
+  if (grub_virtual_screen_setup (border_width, border_width, 
+                                 width - 2 * border_width, 
+                                 height - 2 * border_width, 
+                                 font_name) 
+      != GRUB_ERR_NONE)
+    {
+      return grub_errno;
+    }
+
+  /* Set the render target.  */
+  render_target = target;
+
+  /* Set window bounds.  */
+  window.x = x;
+  window.y = y;
+  window.width = width;
+  window.height = height;
+
+  /* Mark whole window as dirty.  */
+  dirty_region_reset ();
+  dirty_region_add (0, 0, width, height);
+
+  return (grub_errno = GRUB_ERR_NONE);
+}
+
+grub_err_t
+grub_gfxterm_init_window (struct grub_video_render_target *target,
+                          int x, int y, int width, int height, 
+                          const char *font_name, int border_width)
+{
+  grub_errno = GRUB_ERR_NONE;
+  if (refcount++ == 0)
+    init_window (target, x, y, width, height, font_name, border_width);
+  return grub_errno;
+}
+
+static grub_err_t
 grub_gfxterm_init (void)
 {
-  char *modevar;
-  int width = DEFAULT_VIDEO_WIDTH;
-  int height = DEFAULT_VIDEO_HEIGHT;
-  int depth = -1;
-  int flags = DEFAULT_VIDEO_FLAGS;
-  grub_video_color_t color;
+  /* If gfxterm has already been initialized by calling the init_window
+     function, then leave it alone when it is set as the current terminal. */
+  if (refcount++ != 0)
+    return GRUB_ERR_NONE;
 
   /* Parse gfxmode environment variable if set.  */
-  modevar = grub_env_get ("gfxmode");
-  if (modevar)
-    {
-      char *tmp;
-      char *next_mode;
-      char *current_mode;
-      char *param;
-      char *value;
-      int mode_found = 0;
-
-      /* Take copy of env.var. as we don't want to modify that.  */
-      tmp = grub_strdup (modevar);
-      modevar = tmp;
-
-      if (grub_errno != GRUB_ERR_NONE)
-        return grub_errno;
-        
-      /* Initialize next mode.  */
-      next_mode = modevar;
-      
-      /* Loop until all modes has been tested out.  */
-      while (next_mode != NULL)
-        {
-          /* Use last next_mode as current mode.  */
-          tmp = next_mode;
-          
-          /* Reset video mode settings.  */
-          width = DEFAULT_VIDEO_WIDTH;
-          height = DEFAULT_VIDEO_HEIGHT;
-          depth = -1;
-          flags = DEFAULT_VIDEO_FLAGS;
-        
-          /* Save position of next mode and separate modes.  */
-          next_mode = grub_strchr(next_mode, ';');
-          if (next_mode)
-            {
-              *next_mode = 0;
-              next_mode++;
-            }
-
-          /* Skip whitespace.  */
-          while (grub_isspace (*tmp))
-            tmp++;
-
-          /* Initialize token holders.  */
-          current_mode = tmp;
-          param = tmp;
-          value = NULL;
-
-          /* Parse <width>x<height>[x<depth>]*/
-
-          /* Find width value.  */
-          value = param;
-          param = grub_strchr(param, 'x');
-          if (param == NULL)
-            {
-              grub_err_t rc;
-              
-              /* First setup error message.  */
-              rc = grub_error (GRUB_ERR_BAD_ARGUMENT, 
-                               "Invalid mode: %s\n",
-                               current_mode);
-              
-              /* Free memory before returning.  */
-              grub_free (modevar);
-              
-              return rc;
-            }
-
-          *param = 0;
-          param++;
-
-          width = grub_strtoul (value, 0, 0);
-          if (grub_errno != GRUB_ERR_NONE)
-            {
-              grub_err_t rc;
-              
-              /* First setup error message.  */
-              rc = grub_error (GRUB_ERR_BAD_ARGUMENT, 
-                               "Invalid mode: %s\n",
-                               current_mode);
-              
-              /* Free memory before returning.  */
-              grub_free (modevar);
-              
-              return rc;
-            }
-
-          /* Find height value.  */
-          value = param;
-          param = grub_strchr(param, 'x');
-          if (param == NULL)
-            {
-              height = grub_strtoul (value, 0, 0);
-              if (grub_errno != GRUB_ERR_NONE)
-                {
-                  grub_err_t rc;
-                  
-                  /* First setup error message.  */
-                  rc = grub_error (GRUB_ERR_BAD_ARGUMENT, 
-                                   "Invalid mode: %s\n",
-                                   current_mode);
-                                   
-                  /* Free memory before returning.  */
-                  grub_free (modevar);
-                  
-                  return rc;
-                }
-            }
-          else
-            {
-              /* We have optional color depth value.  */
-              *param = 0;
-              param++;
-
-              height = grub_strtoul (value, 0, 0);
-              if (grub_errno != GRUB_ERR_NONE)
-                {
-                  grub_err_t rc;
-                  
-                  /* First setup error message.  */
-                  rc = grub_error (GRUB_ERR_BAD_ARGUMENT, 
-                                   "Invalid mode: %s\n",
-                                   current_mode);
-                                   
-                  /* Free memory before returning.  */
-                  grub_free (modevar);
-                  
-                  return rc;
-                }
-
-              /* Convert color depth value.  */
-              value = param;
-              depth = grub_strtoul (value, 0, 0);
-              if (grub_errno != GRUB_ERR_NONE)
-                {
-                  grub_err_t rc;
-                  
-                  /* First setup error message.  */
-                  rc = grub_error (GRUB_ERR_BAD_ARGUMENT, 
-                                   "Invalid mode: %s\n",
-                                   current_mode);
-                                   
-                  /* Free memory before returning.  */
-                  grub_free (modevar);
-                  
-                  return rc;
-                }
-            }
-          
-          /* Try out video mode.  */
-          
-          /* If we have 8 or less bits, then assume that it is indexed color mode.  */
-          if ((depth <= 8) && (depth != -1))
-            flags |= GRUB_VIDEO_MODE_TYPE_INDEX_COLOR;
-
-          /* We have more than 8 bits, then assume that it is RGB color mode.  */
-          if (depth > 8)
-            flags |= GRUB_VIDEO_MODE_TYPE_RGB;
-
-          /* If user requested specific depth, forward that information to driver.  */
-          if (depth != -1)
-            flags |= (depth << GRUB_VIDEO_MODE_TYPE_DEPTH_POS)
-                     & GRUB_VIDEO_MODE_TYPE_DEPTH_MASK;
-
-          /* Try to initialize requested mode.  Ignore any errors.  */
-          grub_error_push ();
-          if (grub_video_setup (width, height, flags) != GRUB_ERR_NONE)
-            {
-              grub_error_pop ();
-              continue;
-            }
-            
-          /* Figure out what mode we ended up.  */
-          if (grub_video_get_info (&mode_info) != GRUB_ERR_NONE)
-            {
-              /* Couldn't get video mode info, restore old mode and continue to next one.  */
-              grub_error_pop ();
-              
-              grub_video_restore ();
-              continue;
-            }
-          
-          /* Restore state of error stack.  */
-          grub_error_pop ();
-          
-          /* Mode found!  Exit loop.  */
-          mode_found = 1;
-          break;
-        }
-
-      /* Free memory.  */
-      grub_free (modevar);
-      
-      if (!mode_found)
-        return grub_error (GRUB_ERR_BAD_ARGUMENT,
-                           "No suitable mode found.");
-    }
-  else
-    {
-      /* No gfxmode variable set, use defaults.  */
-      
-      /* If we have 8 or less bits, then assume that it is indexed color mode.  */
-      if ((depth <= 8) && (depth != -1))
-        flags |= GRUB_VIDEO_MODE_TYPE_INDEX_COLOR;
-
-      /* We have more than 8 bits, then assume that it is RGB color mode.  */
-      if (depth > 8)
-        flags |= GRUB_VIDEO_MODE_TYPE_RGB;
-
-      /* If user requested specific depth, forward that information to driver.  */
-      if (depth != -1)
-        flags |= (depth << GRUB_VIDEO_MODE_TYPE_DEPTH_POS)
-                 & GRUB_VIDEO_MODE_TYPE_DEPTH_MASK;
-
-      /* Initialize user requested mode.  */
-      if (grub_video_setup (width, height, flags) != GRUB_ERR_NONE)
-        return grub_errno;
-
-      /* Figure out what mode we ended up.  */
-      if (grub_video_get_info (&mode_info) != GRUB_ERR_NONE)
-        {
-          grub_video_restore ();
-          return grub_errno;
-        }
+  const char *modevar = grub_env_get ("gfxmode");
+  if (grub_video_setup_preferred_mode (modevar, 0, 
+                                       DEFAULT_VIDEO_WIDTH, 
+                                       DEFAULT_VIDEO_HEIGHT) 
+      != GRUB_ERR_NONE)
+    return grub_errno;
+
+  /* Figure out what mode we ended up.  */
+  struct grub_video_mode_info mode_info;
+  if (grub_video_get_info (&mode_info) != GRUB_ERR_NONE)
+    {
+      grub_video_restore ();
+      return grub_errno;
     }
 
   /* Make sure screen is black.  */
-  color = grub_video_map_rgb (0, 0, 0);
-  grub_video_fill_rect (color, 0, 0, mode_info.width, mode_info.height);
+  grub_video_fill_rect (grub_video_map_rgb (0, 0, 0), 
+                        0, 0, mode_info.width, mode_info.height);
   bitmap = 0;
 
+  /* Select the font to use.  */
+  char *font_name = grub_env_get ("gfxterm_font");
+  if (!font_name)
+    font_name = "";   /* Allow fallback to any font.  */
+
   /* Leave borders for virtual screen.  */
-  width = mode_info.width - (2 * DEFAULT_BORDER_WIDTH);
-  height = mode_info.height - (2 * DEFAULT_BORDER_WIDTH);
-
-  /* Create virtual screen.  */
-  if (grub_virtual_screen_setup (DEFAULT_BORDER_WIDTH, DEFAULT_BORDER_WIDTH,
-                                 width, height) != GRUB_ERR_NONE)
+  if (init_window (GRUB_VIDEO_RENDER_TARGET_DISPLAY,
+                   0, 0, mode_info.width, mode_info.height,
+                   font_name, 
+                   DEFAULT_BORDER_WIDTH) != GRUB_ERR_NONE)
     {
       grub_video_restore ();
       return grub_errno;
     }
 
-  /* Mark whole screen as dirty.  */
-  dirty_region_reset ();
-  dirty_region_add (0, 0, mode_info.width, mode_info.height);
-
   return (grub_errno = GRUB_ERR_NONE);
 }
 
+static void
+destroy_window (void)
+{
+  if (bitmap)
+    {
+      grub_video_bitmap_destroy (bitmap);
+      bitmap = 0;
+    }
+
+  repaint_callback = 0;
+  grub_virtual_screen_free ();
+}
+
+void
+grub_gfxterm_destroy_window (void)
+{
+  if (--refcount == 0)
+    destroy_window ();
+}
+
 static grub_err_t
 grub_gfxterm_fini (void)
 {
-  if (bitmap)
+  /* Don't destroy an explicitly initialized terminal instance when it is
+     unset as the current terminal.  */
+  if (--refcount == 0)
     {
-      grub_video_bitmap_destroy (bitmap);
-      bitmap = 0;
+      destroy_window ();
+      grub_video_restore ();
     }
 
-  grub_virtual_screen_free ();
-
-  grub_video_restore ();
-
-  return GRUB_ERR_NONE;
+  return (grub_errno = GRUB_ERR_NONE);
 }
 
 static void
@@ -509,9 +375,15 @@
                     unsigned int width, unsigned int height)
 {
   grub_video_color_t color;
-
-  grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
-
+  grub_video_rect_t saved_view;
+
+  grub_video_set_active_render_target (render_target);
+  /* Save viewport and set it to our window. */
+  grub_video_get_viewport ((unsigned *) &saved_view.x, 
+                           (unsigned *) &saved_view.y, 
+                           (unsigned *) &saved_view.width, 
+                           (unsigned *) &saved_view.height);
+  grub_video_set_viewport (window.x, window.y, window.width, window.height);
 
   if (bitmap)
     {
@@ -578,6 +450,14 @@
                                      y - virtual_screen.offset_y,
                                      width, height);      
     }
+
+  /* Restore saved viewport. */
+  grub_video_set_viewport (saved_view.x, saved_view.y,
+                           saved_view.width, saved_view.height);
+  grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
+  
+  if (repaint_callback)
+    repaint_callback (x, y, width, height);
 }
 
 static void
@@ -661,11 +541,12 @@
 write_char (void)
 {
   struct grub_colored_char *p;
-  struct grub_font_glyph glyph;
+  struct grub_font_glyph *glyph;
   grub_video_color_t color;
   grub_video_color_t bgcolor;
   unsigned int x;
   unsigned int y;
+  int ascent;
 
   /* Find out active character.  */
   p = (virtual_screen.text_buffer
@@ -675,7 +556,8 @@
   p -= p->index;
 
   /* Get glyph for character.  */
-  grub_font_get_glyph (p->code, &glyph);
+  glyph = grub_font_get_glyph (virtual_screen.font, p->code);
+  ascent = grub_font_get_ascent (virtual_screen.font);
 
   color = p->fg_color;
   bgcolor = p->bg_color;
@@ -685,13 +567,13 @@
 
   /* Render glyph to text layer.  */
   grub_video_set_active_render_target (text_layer);
-  grub_video_fill_rect (bgcolor, x, y, glyph.width, glyph.height);
-  grub_video_blit_glyph (&glyph, color, x, y);
+  grub_video_fill_rect (bgcolor, x, y, glyph->width, glyph->height);
+  grub_video_blit_glyph (glyph, color, x, y + ascent);
   grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
 
   /* Mark character to be drawn.  */
   dirty_region_add (virtual_screen.offset_x + x, virtual_screen.offset_y + y,
-                    glyph.width, glyph.height);
+                    glyph->width, glyph->height);
 }
 
 static void
@@ -705,7 +587,8 @@
 
   /* Determine cursor properties and position on text layer. */
   x = virtual_screen.cursor_x * virtual_screen.char_width;
-  y = ((virtual_screen.cursor_y + 1) * virtual_screen.char_height) - 3;  
+  y = (virtual_screen.cursor_y * virtual_screen.char_height   
+       + grub_font_get_ascent (virtual_screen.font));
   width = virtual_screen.char_width;
   height = 2;
 
@@ -769,7 +652,16 @@
       dirty_region_add_virtualscreen ();
     }
   else
-    {      
+    {
+      grub_video_rect_t saved_view;
+      grub_video_set_active_render_target (render_target);
+      /* Save viewport and set it to our window.  */
+      grub_video_get_viewport ((unsigned *) &saved_view.x, 
+                               (unsigned *) &saved_view.y, 
+                               (unsigned *) &saved_view.width, 
+                               (unsigned *) &saved_view.height);
+      grub_video_set_viewport (window.x, window.y, window.width, window.height);
+
       /* Clear new border area.  */
       grub_video_fill_rect (color, 
                             virtual_screen.offset_x, virtual_screen.offset_y, 
@@ -778,10 +670,18 @@
       /* Scroll physical screen.  */
       grub_video_scroll (color, 0, -virtual_screen.char_height);      
 
+      /* Restore saved viewport. */
+      grub_video_set_viewport (saved_view.x, saved_view.y,
+                               saved_view.width, saved_view.height);
+      grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
+
       /* Draw cursor if visible.  */
       if (virtual_screen.cursor_state)
         write_cursor ();
     }
+
+  if (repaint_callback)
+    repaint_callback (window.x, window.y, window.width, window.height);
 }
 
 static void
@@ -822,14 +722,18 @@
     }
   else
     {
-      struct grub_font_glyph glyph;
+      struct grub_font_glyph *glyph;
       struct grub_colored_char *p;
+      unsigned char_width;
 
       /* Get properties of the character.  */    
-      grub_font_get_glyph (c, &glyph);
+      glyph = grub_font_get_glyph (virtual_screen.font, c);
+
+      /* TODO [CDB] Fix wide characters.  Bi-width font? */
+      char_width = 1;
 
       /* If we are about to exceed line length, wrap to next line.  */
-      if (virtual_screen.cursor_x + glyph.char_width > virtual_screen.columns)
+      if (virtual_screen.cursor_x + char_width > virtual_screen.columns)
         grub_putchar ('\n');
 
       /* Find position on virtual screen, and fill information.  */
@@ -839,18 +743,18 @@
       p->code = c;
       p->fg_color = virtual_screen.fg_color;
       p->bg_color = virtual_screen.bg_color;
-      p->width = glyph.char_width - 1;
+      p->width = char_width - 1;
       p->index = 0;
 
       /* If we have large glyph, add fixup info.  */
-      if (glyph.char_width > 1)
+      if (char_width > 1)
         {
           unsigned i;
 
-          for (i = 1; i < glyph.char_width; i++)
+          for (i = 1; i < char_width; i++)
             {
               p[i].code = ' ';
-              p[i].width = glyph.char_width - 1;
+              p[i].width = char_width - 1;
               p[i].index = i;
             }
         }
@@ -859,7 +763,7 @@
       write_char ();
 
       /* Make sure we scroll screen when needed and wrap line correctly.  */
-      virtual_screen.cursor_x += glyph.char_width;
+      virtual_screen.cursor_x += char_width;
       if (virtual_screen.cursor_x >= virtual_screen.columns)
         {
           virtual_screen.cursor_x = 0;
@@ -877,13 +781,17 @@
 }
 
 static grub_ssize_t
-grub_gfxterm_getcharwidth (grub_uint32_t c)
+grub_gfxterm_getcharwidth (grub_uint32_t c __attribute__((unused)))
 {
-  struct grub_font_glyph glyph;
-
-  grub_font_get_glyph (c, &glyph);
-
-  return glyph.char_width;
+#if 0
+  struct grub_font_glyph *glyph;
+
+  glyph = grub_font_get_glyph (c);
+
+  return glyph->char_width;
+#else
+  return 1;   /* TODO [CDB] Fix wide characters. */
+#endif
 }
 
 static grub_uint16_t
@@ -945,7 +853,8 @@
   /* Clear text layer.  */
   grub_video_set_active_render_target (text_layer);
   color = virtual_screen.bg_color;
-  grub_video_fill_rect (color, 0, 0, mode_info.width, mode_info.height);
+  grub_video_fill_rect (color, 0, 0,
+                        virtual_screen.width, virtual_screen.height);
   grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
 
   /* Mark virtual screen to be redrawn.  */
@@ -1014,8 +923,23 @@
   dirty_region_redraw ();
 }
 
+void 
+grub_gfxterm_set_repaint_callback (grub_gfxterm_repaint_callback_t func)
+{
+  repaint_callback = func;
+}
+
+/* Option array indices. */
+#define BACKGROUND_CMD_ARGINDEX_MODE 0
+
+static const struct grub_arg_option background_image_cmd_options[] = {
+  {"mode", 'm', 0, "Background image mode (`stretch', `normal').", 0, 
+    ARG_TYPE_STRING},
+  {0, 0, 0, 0, 0, 0}
+};
+
 static grub_err_t
-grub_gfxterm_background_image_cmd (struct grub_arg_list *state __attribute__ ((unused)),
+grub_gfxterm_background_image_cmd (struct grub_arg_list *state,
                                    int argc,
                                    char **args)
 {
@@ -1031,7 +955,7 @@
       
       /* Mark whole screen as dirty.  */
       dirty_region_reset ();
-      dirty_region_add (0, 0, mode_info.width, mode_info.height);
+      dirty_region_add (0, 0, window.width, window.height);
     }
 
   /* If filename was provided, try to load that.  */
@@ -1042,16 +966,40 @@
     if (grub_errno != GRUB_ERR_NONE)
       return grub_errno;
 
+    /* Determine if the bitmap should be scaled to fit the screen. */
+    if (!state[BACKGROUND_CMD_ARGINDEX_MODE].set
+        || grub_strcmp (state[BACKGROUND_CMD_ARGINDEX_MODE].arg,
+                        "stretch") == 0)
+        {
+          if (window.width != (int) grub_video_bitmap_get_width (bitmap)
+              || window.height != (int) grub_video_bitmap_get_height (bitmap)) 
+            {
+              struct grub_video_bitmap *scaled_bitmap;
+              grub_video_bitmap_create_scaled (&scaled_bitmap,
+                                              window.width, 
+                                              window.height,
+                                              bitmap,
+                                              GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST);
+              if (grub_errno == GRUB_ERR_NONE)
+                {
+                  /* Replace the original bitmap with the scaled one. */
+                  grub_video_bitmap_destroy (bitmap);
+                  bitmap = scaled_bitmap;
+                }
+            }
+        }
+
+
     /* If bitmap was loaded correctly, display it.  */
     if (bitmap)
       {
         /* Determine bitmap dimensions.  */
         bitmap_width = grub_video_bitmap_get_width (bitmap);
-        bitmap_height = grub_video_bitmap_get_width (bitmap);
+        bitmap_height = grub_video_bitmap_get_height (bitmap);
         
         /* Mark whole screen as dirty.  */
         dirty_region_reset ();
-        dirty_region_add (0, 0, mode_info.width, mode_info.height);
+        dirty_region_add (0, 0, window.width, window.height);
       }
     }
   
@@ -1082,9 +1030,16 @@
     .next = 0
   };
 
+grub_term_t
+grub_gfxterm_get_term (void)
+{
+  return &grub_video_term;
+}
+
 GRUB_MOD_INIT(term_gfxterm)
 {
   my_mod = mod;
+  refcount = 0;
   grub_term_register (&grub_video_term);
 
   grub_register_command ("background_image",
@@ -1092,7 +1047,7 @@
                          GRUB_COMMAND_FLAG_BOTH,
                          "background_image",
                          "Load background image for active terminal",
-                         0);
+                         background_image_cmd_options);
 }
 
 GRUB_MOD_FINI(term_gfxterm)

=== modified file 'term/i386/pc/vesafb.c'
--- term/i386/pc/vesafb.c	2007-12-30 08:52:06 +0000
+++ term/i386/pc/vesafb.c	2008-07-03 14:12:08 +0000
@@ -250,10 +250,11 @@
 	  break;
 
 	default:
-	  return grub_font_get_glyph (code, bitmap, width);
+	  return grub_font_get_glyph_any (code, bitmap, width);
 	}
     }
 
+  /* TODO [CDB] This is wrong for the new font module.  Should it be fixed? */
   if (bitmap)
     grub_memcpy (bitmap,
 		 vga_font + code * virtual_screen.char_height,

=== modified file 'term/i386/pc/vga.c'
--- term/i386/pc/vga.c	2008-01-21 15:48:27 +0000
+++ term/i386/pc/vga.c	2008-07-03 14:12:08 +0000
@@ -65,6 +65,7 @@
 static struct colored_char text_buf[TEXT_WIDTH * TEXT_HEIGHT];
 static unsigned char saved_map_mask;
 static int page = 0;
+static grub_font_t font = 0;
 
 #define SEQUENCER_ADDR_PORT	0x3C4
 #define SEQUENCER_DATA_PORT	0x3C5
@@ -161,6 +162,9 @@
   saved_map_mask = get_map_mask ();
   set_map_mask (0x0f);
   set_start_address (PAGE_OFFSET (page));
+  font = grub_font_get ("");  /* Choose any font, for now. */
+  if (!font)
+    return grub_error (GRUB_ERR_BAD_FONT, "No font loaded.");
   
   return GRUB_ERR_NONE;
 }
@@ -185,7 +189,7 @@
 write_char (void)
 {
   struct colored_char *p = text_buf + xpos + ypos * TEXT_WIDTH;
-  struct grub_font_glyph glyph;
+  struct grub_font_glyph *glyph;
   unsigned char *mem_base;
   unsigned plane;
 
@@ -194,7 +198,7 @@
   p -= p->index;
 
   /* Get glyph for character.  */
-  grub_font_get_glyph (p->code, &glyph);
+  glyph = grub_font_get_glyph (font, p->code);
   
   for (plane = 0x01; plane <= 0x08; plane <<= 1)
     {
@@ -210,17 +214,21 @@
 	{
 	  unsigned i;
 
-	  for (i = 0; i < glyph.char_width && offset < 32; i++)
+          unsigned char_width = 1; /* TODO [CDB] Figure out wide characters. */
+          /* TODO [CDB] Re-implement glyph drawing for vga module. */
+#if 0
+	  for (i = 0; i < char_width && offset < 32; i++)
 	    {
 	      unsigned char fg_mask, bg_mask;
 	      
-	      fg_mask = (p->fg_color & plane) ? glyph.bitmap[offset] : 0;
-	      bg_mask = (p->bg_color & plane) ? ~(glyph.bitmap[offset]) : 0;
+	      fg_mask = (p->fg_color & plane) ? glyph->bitmap[offset] : 0;
+	      bg_mask = (p->bg_color & plane) ? ~(glyph->bitmap[offset]) : 0;
 	      offset++;
 
 	      if (check_vga_mem (mem + i))
 		mem[i] = (fg_mask | bg_mask);
 	    }
+#endif /* 0 */ 
 	}
     }
 
@@ -320,36 +328,37 @@
     }
   else
     {
-      struct grub_font_glyph glyph;
+      struct grub_font_glyph *glyph;
       struct colored_char *p;
+      unsigned char_width = 1;
       
-      grub_font_get_glyph(c, &glyph);
+      glyph = grub_font_get_glyph(font, c);
 
-      if (xpos + glyph.char_width > TEXT_WIDTH)
+      if (xpos + char_width > TEXT_WIDTH)
 	grub_putchar ('\n');
 
       p = text_buf + xpos + ypos * TEXT_WIDTH;
       p->code = c;
       p->fg_color = fg_color;
       p->bg_color = bg_color;
-      p->width = glyph.char_width - 1;
+      p->width = char_width - 1;
       p->index = 0;
 
-      if (glyph.char_width > 1)
+      if (char_width > 1)
 	{
 	  unsigned i;
 
-	  for (i = 1; i < glyph.char_width; i++)
+	  for (i = 1; i < char_width; i++)
 	    {
 	      p[i].code = ' ';
-	      p[i].width = glyph.char_width - 1;
+	      p[i].width = char_width - 1;
 	      p[i].index = i;
 	    }
 	}
 	  
       write_char ();
   
-      xpos += glyph.char_width;
+      xpos += char_width;
       if (xpos >= TEXT_WIDTH)
 	{
 	  xpos = 0;
@@ -381,11 +390,16 @@
 static grub_ssize_t
 grub_vga_getcharwidth (grub_uint32_t c)
 {
+#if 0
   struct grub_font_glyph glyph;
   
-  grub_font_get_glyph (c, &glyph);
+  glyph = grub_font_get_glyph (c);
   
   return glyph.char_width;
+#else
+  /* TODO [CDB] Glyph wide characters? vga? */
+  return 1;
+#endif
 }
 
 static grub_uint16_t

=== added directory 'util/fonttool'
=== added file 'util/fonttool/build.xml'
--- util/fonttool/build.xml	1970-01-01 00:00:00 +0000
+++ util/fonttool/build.xml	2008-07-03 14:08:39 +0000
@@ -0,0 +1,23 @@
+<project name="fonttool" default="jar" basedir=".">
+    <description>GRUB 2 font tool.</description>
+
+    <target name="clean">
+        <delete dir="build"/>
+    </target>
+
+    <target name="compile">
+        <mkdir dir="build/src"/>
+        <javac srcdir="src" destdir="build/src" source="1.5" target="1.5"
+               optimize="yes" debug="yes" encoding="UTF-8"/>
+    </target>
+
+    <target name="jar" depends="compile">
+        <jar jarfile="build/fonttool.jar" compress="yes">
+            <fileset dir="build/src"/>
+            <fileset dir="src">
+                <include name="*.txt"/>
+                <include name="*.png"/>
+            </fileset>
+        </jar>
+    </target>
+</project>

=== added file 'util/fonttool/fonttool.iml'
--- util/fonttool/fonttool.iml	1970-01-01 00:00:00 +0000
+++ util/fonttool/fonttool.iml	2008-07-03 14:08:39 +0000
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module version="4" relativePaths="true" type="JAVA_MODULE">
+  <component name="ModuleRootManager" />
+  <component name="NewModuleRootManager" inherit-compiler-output="true">
+    <exclude-output />
+    <content url="file://$MODULE_DIR$">
+      <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
+      <sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="module-library">
+      <library>
+        <CLASSES>
+          <root url="jar://$APPLICATION_HOME_DIR$/lib/junit-4.4.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES />
+      </library>
+    </orderEntry>
+    <orderEntryProperties />
+  </component>
+</module>
+

=== added file 'util/fonttool/fonttool.ipr'
--- util/fonttool/fonttool.ipr	1970-01-01 00:00:00 +0000
+++ util/fonttool/fonttool.ipr	2008-07-03 14:08:39 +0000
@@ -0,0 +1,306 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4" relativePaths="false">
+  <component name="AntConfiguration">
+    <defaultAnt bundledAnt="true" />
+  </component>
+  <component name="BuildJarProjectSettings">
+    <option name="BUILD_JARS_ON_MAKE" value="false" />
+  </component>
+  <component name="CodeStyleProjectProfileManger">
+    <option name="PROJECT_PROFILE" />
+    <option name="USE_PROJECT_LEVEL_SETTINGS" value="false" />
+  </component>
+  <component name="CodeStyleSettingsManager">
+    <option name="PER_PROJECT_SETTINGS">
+      <value>
+        <option name="ALIGN_MULTILINE_PARAMETERS_IN_CALLS" value="true" />
+        <option name="ALIGN_MULTILINE_BINARY_OPERATION" value="true" />
+        <option name="ALIGN_MULTILINE_ASSIGNMENT" value="true" />
+        <option name="ALIGN_MULTILINE_TERNARY_OPERATION" value="true" />
+        <option name="ALIGN_MULTILINE_THROWS_LIST" value="true" />
+        <option name="ALIGN_MULTILINE_EXTENDS_LIST" value="true" />
+        <option name="ALIGN_MULTILINE_PARENTHESIZED_EXPRESSION" value="true" />
+        <option name="ALIGN_MULTILINE_ARRAY_INITIALIZER_EXPRESSION" value="true" />
+        <option name="IMPORT_LAYOUT_TABLE">
+          <value>
+            <package name="java" withSubpackages="true" />
+            <package name="javax" withSubpackages="true" />
+            <package name="" withSubpackages="true" />
+          </value>
+        </option>
+        <option name="RIGHT_MARGIN" value="80" />
+      </value>
+    </option>
+    <option name="USE_PER_PROJECT_SETTINGS" value="true" />
+  </component>
+  <component name="CompilerConfiguration">
+    <option name="DEFAULT_COMPILER" value="Javac" />
+    <option name="DEPLOY_AFTER_MAKE" value="0" />
+    <resourceExtensions>
+      <entry name=".+\.(properties|xml|html|dtd|tld)" />
+      <entry name=".+\.(gif|png|jpeg|jpg)" />
+    </resourceExtensions>
+    <wildcardResourcePatterns>
+      <entry name="?*.properties" />
+      <entry name="?*.xml" />
+      <entry name="?*.gif" />
+      <entry name="?*.png" />
+      <entry name="?*.jpeg" />
+      <entry name="?*.jpg" />
+      <entry name="?*.html" />
+      <entry name="?*.dtd" />
+      <entry name="?*.tld" />
+      <entry name=".bzr" />
+    </wildcardResourcePatterns>
+  </component>
+  <component name="DataSourceManagerImpl" />
+  <component name="DependenciesAnalyzeManager">
+    <option name="myForwardDirection" value="false" />
+  </component>
+  <component name="DependencyValidationManager" />
+  <component name="EclipseCompilerSettings">
+    <option name="DEBUGGING_INFO" value="true" />
+    <option name="GENERATE_NO_WARNINGS" value="true" />
+    <option name="DEPRECATION" value="false" />
+    <option name="ADDITIONAL_OPTIONS_STRING" value="" />
+    <option name="MAXIMUM_HEAP_SIZE" value="128" />
+  </component>
+  <component name="EclipseEmbeddedCompilerSettings">
+    <option name="DEBUGGING_INFO" value="true" />
+    <option name="GENERATE_NO_WARNINGS" value="true" />
+    <option name="DEPRECATION" value="false" />
+    <option name="ADDITIONAL_OPTIONS_STRING" value="" />
+    <option name="MAXIMUM_HEAP_SIZE" value="128" />
+  </component>
+  <component name="EntryPointsManager">
+    <entry_points />
+  </component>
+  <component name="ExportToHTMLSettings">
+    <option name="PRINT_LINE_NUMBERS" value="false" />
+    <option name="OPEN_IN_BROWSER" value="false" />
+    <option name="OUTPUT_DIRECTORY" />
+  </component>
+  <component name="GUI Designer component loader factory" />
+  <component name="IdProvider" IDEtalkID="FEF368E009900571CCFB90CB4A0CEBC9" />
+  <component name="InspectionProjectProfileManager">
+    <option name="PROJECT_PROFILE" value="Project Default" />
+    <option name="USE_PROJECT_LEVEL_SETTINGS" value="false" />
+    <scopes />
+    <profiles>
+      <profile version="1.0" is_locked="false">
+        <option name="myName" value="Project Default" />
+        <option name="myLocal" value="false" />
+        <used_levels>
+          <error>
+            <option name="myName" value="ERROR" />
+            <option name="myVal" value="400" />
+          </error>
+          <warning>
+            <option name="myName" value="WARNING" />
+            <option name="myVal" value="300" />
+          </warning>
+          <information>
+            <option name="myName" value="INFO" />
+            <option name="myVal" value="200" />
+          </information>
+          <server>
+            <option name="myName" value="SERVER PROBLEM" />
+            <option name="myVal" value="100" />
+          </server>
+        </used_levels>
+      </profile>
+    </profiles>
+  </component>
+  <component name="JavacSettings">
+    <option name="DEBUGGING_INFO" value="true" />
+    <option name="GENERATE_NO_WARNINGS" value="false" />
+    <option name="DEPRECATION" value="true" />
+    <option name="ADDITIONAL_OPTIONS_STRING" value="" />
+    <option name="MAXIMUM_HEAP_SIZE" value="128" />
+  </component>
+  <component name="JavadocGenerationManager">
+    <option name="OUTPUT_DIRECTORY" />
+    <option name="OPTION_SCOPE" value="protected" />
+    <option name="OPTION_HIERARCHY" value="true" />
+    <option name="OPTION_NAVIGATOR" value="true" />
+    <option name="OPTION_INDEX" value="true" />
+    <option name="OPTION_SEPARATE_INDEX" value="true" />
+    <option name="OPTION_DOCUMENT_TAG_USE" value="false" />
+    <option name="OPTION_DOCUMENT_TAG_AUTHOR" value="false" />
+    <option name="OPTION_DOCUMENT_TAG_VERSION" value="false" />
+    <option name="OPTION_DOCUMENT_TAG_DEPRECATED" value="true" />
+    <option name="OPTION_DEPRECATED_LIST" value="true" />
+    <option name="OTHER_OPTIONS" value="" />
+    <option name="HEAP_SIZE" />
+    <option name="LOCALE" />
+    <option name="OPEN_IN_BROWSER" value="true" />
+  </component>
+  <component name="JikesSettings">
+    <option name="JIKES_PATH" value="" />
+    <option name="DEBUGGING_INFO" value="true" />
+    <option name="DEPRECATION" value="true" />
+    <option name="GENERATE_NO_WARNINGS" value="false" />
+    <option name="IS_EMACS_ERRORS_MODE" value="true" />
+    <option name="ADDITIONAL_OPTIONS_STRING" value="" />
+  </component>
+  <component name="LogConsolePreferences">
+    <option name="FILTER_ERRORS" value="false" />
+    <option name="FILTER_WARNINGS" value="false" />
+    <option name="FILTER_INFO" value="true" />
+    <option name="CUSTOM_FILTER" />
+  </component>
+  <component name="Palette2">
+    <group name="Swing">
+      <item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
+      </item>
+      <item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
+      </item>
+      <item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.png" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
+      </item>
+      <item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.png" removable="false" auto-create-binding="false" can-attach-label="true">
+        <default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
+      </item>
+      <item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.png" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
+        <initial-values>
+          <property name="text" value="Button" />
+        </initial-values>
+      </item>
+      <item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.png" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
+        <initial-values>
+          <property name="text" value="RadioButton" />
+        </initial-values>
+      </item>
+      <item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.png" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
+        <initial-values>
+          <property name="text" value="CheckBox" />
+        </initial-values>
+      </item>
+      <item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.png" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
+        <initial-values>
+          <property name="text" value="Label" />
+        </initial-values>
+      </item>
+      <item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.png" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
+          <preferred-size width="150" height="-1" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.png" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
+          <preferred-size width="150" height="-1" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.png" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
+          <preferred-size width="150" height="-1" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.png" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.png" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
+      </item>
+      <item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.png" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.png" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.png" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.png" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
+          <preferred-size width="200" height="200" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.png" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
+          <preferred-size width="200" height="200" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.png" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
+      </item>
+      <item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.png" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
+      </item>
+      <item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.png" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
+      </item>
+      <item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
+      </item>
+      <item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.png" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
+          <preferred-size width="-1" height="20" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.png" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
+      </item>
+      <item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
+      </item>
+    </group>
+  </component>
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/fonttool.iml" filepath="$PROJECT_DIR$/fonttool.iml" />
+    </modules>
+  </component>
+  <component name="ProjectRootManager" version="2" assert-keyword="true" jdk-15="true" project-jdk-name="1.6" project-jdk-type="JavaSDK">
+    <output url="file://$PROJECT_DIR$/build/classes" />
+  </component>
+  <component name="ProjectRunConfigurationManager" />
+  <component name="RmicSettings">
+    <option name="IS_EANABLED" value="false" />
+    <option name="DEBUGGING_INFO" value="true" />
+    <option name="GENERATE_NO_WARNINGS" value="false" />
+    <option name="GENERATE_IIOP_STUBS" value="false" />
+    <option name="ADDITIONAL_OPTIONS_STRING" value="" />
+  </component>
+  <component name="StarteamVcsAdapter" />
+  <component name="VssVcs" />
+  <component name="com.intellij.jsf.UserDefinedFacesConfigs">
+    <option name="USER_DEFINED_CONFIGS">
+      <value>
+        <list size="0" />
+      </value>
+    </option>
+  </component>
+  <component name="libraryTable" />
+  <component name="uidesigner-configuration">
+    <option name="INSTRUMENT_CLASSES" value="true" />
+    <option name="COPY_FORMS_RUNTIME_TO_OUTPUT" value="true" />
+    <option name="DEFAULT_LAYOUT_MANAGER" value="GridLayoutManager" />
+  </component>
+  <UsedPathMacros />
+</project>
+

=== added directory 'util/fonttool/src'
=== added directory 'util/fonttool/src/org'
=== added directory 'util/fonttool/src/org/gnu'
=== added directory 'util/fonttool/src/org/gnu/grub'
=== added directory 'util/fonttool/src/org/gnu/grub/fonttool'
=== added file 'util/fonttool/src/org/gnu/grub/fonttool/BDFFont.java'
--- util/fonttool/src/org/gnu/grub/fonttool/BDFFont.java	1970-01-01 00:00:00 +0000
+++ util/fonttool/src/org/gnu/grub/fonttool/BDFFont.java	2008-07-03 14:08:39 +0000
@@ -0,0 +1,24 @@
+package org.gnu.grub.fonttool;
+
+import java.awt.image.BufferedImage;
+import java.util.HashMap;
+import java.util.Map;
+
+public class BDFFont {
+    private Map<Character, BufferedImage> glyphs = new HashMap<Character, BufferedImage>();
+    private int height = 0;
+
+    void putGlyph(char ch, BufferedImage glyph) {
+        glyphs.put(ch, glyph);
+        if (glyph.getHeight() > height)
+            height = glyph.getHeight();
+    }
+
+    public BufferedImage getGlyph(char c) {
+        return glyphs.get(c);
+    }
+
+    public int getHeight() {
+        return height;
+    }
+}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

=== added file 'video/bitmap_scale.c'
--- video/bitmap_scale.c	1970-01-01 00:00:00 +0000
+++ video/bitmap_scale.c	2008-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-27 23:56:02 +0000
@@ -23,7 +23,7 @@
 #include <grub/mm.h>
 #include <grub/misc.h>
 #include <grub/arg.h>
-#include <grub/file.h>
+#include <grub/bufio.h>
 
 /* Uncomment following define to enable JPEG debug.  */
 //#define JPEG_DEBUG
@@ -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;
@@ -664,7 +664,7 @@
   grub_file_t file;
   struct grub_jpeg_data *data;
 
-  file = grub_file_open (filename);
+  file = grub_buffile_open (filename);
   if (!file)
     return grub_errno;
 

=== modified file 'video/readers/png.c'
--- video/readers/png.c	2008-07-24 14:02:36 +0000
+++ video/readers/png.c	2008-07-28 02:02:30 +0000
@@ -23,7 +23,7 @@
 #include <grub/mm.h>
 #include <grub/misc.h>
 #include <grub/arg.h>
-#include <grub/file.h>
+#include <grub/bufio.h>
 
 /* Uncomment following define to enable PNG debug.  */
 //#define PNG_DEBUG
@@ -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;
     }
@@ -840,7 +840,7 @@
   grub_file_t file;
   struct grub_png_data *data;
 
-  file = grub_file_open (filename);
+  file = grub_buffile_open (filename);
   if (!file)
     return grub_errno;
 

=== modified file 'video/readers/tga.c'
--- video/readers/tga.c	2007-12-30 08:52:06 +0000
+++ video/readers/tga.c	2008-07-27 23:56:02 +0000
@@ -23,7 +23,7 @@
 #include <grub/mm.h>
 #include <grub/misc.h>
 #include <grub/arg.h>
-#include <grub/file.h>
+#include <grub/bufio.h>
 
 /* Uncomment following define to enable TGA debug.  */
 //#define TGA_DEBUG
@@ -319,7 +319,7 @@
   struct grub_tga_header header;
   int has_alpha;
 
-  file = grub_file_open (filename);
+  file = grub_buffile_open (filename);
   if (! file)
     return grub_errno;
 
@@ -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
IyBCYXphYXIgcmV2aXNpb24gYnVuZGxlIHY0CiMKQlpoOTFBWSZTWXvLAmQCJDf/gH//////////
//////////9iMj7wq2egAe0oegB7sFugFwec977w73n3vYte9vI8Ppi+xk175t3vKZcV721JDWRI
Nvvd3c7149j3ATDXrJHMsdAAADn2zjnuLT64kLY92oNMvZ0Pp7bd3uw8Ljb7jtnO+7Id7gfAPr7r
s3xSYeAAAoTliPcCgHqj3u6IFAKAeg0DQUDQCnRDVWSlMmEvboiui+7n32Vxpta1pPkABEBTU6xS
gD7e7W2peWvfcHJX1tn3r6993dH3tIwHt9Tj19gY0b2b3fe33fer7zuKAiL3wHByArT6devilAjo
BRK++zj3Z332RSfBPNbs5u53xHuA1InjAnTTxyn1icjrqnvUe976L6Hfae+TIlS3t8ABb7nIlqmg
0ClUASAb4AAL697bAtlT0pqAqjpq6a697694PcExJKj4HqQErJlPbe3W8+92eC+18+e96HPT3EHe
de7wC5CPDd4dClJ73nvO+xGtU9mfQ733n3zWa9zVsz1s73ABTDc9X0AOe73zvu9Zd76k9t6DdCtp
6PprrxGzrqVJOw2xl97uAKGq96+gHxezyxzYW2tsJFUU1RQehuZ8tLsNK5b7z6Ad99zsbYvrD6M9
MyU3sOhIHQClHzcAE727grQ0FNprMqto+uSr4pABkze99gHPfd2Pu7mmm2ZafQ0r3uCc0ASD7cAN
u+83TbLR9u4KkDarKwoKSH259AZ7TTbMuhuxk29x3bVbaQk00ObgC3W1dmiZMWm9breuvPcj3tQJ
2BAG7aqcWxqts201pNMQAA6t1ve1cKdN9j621RXtkK8qLsa6SW9eqpAABQAAAEjkGkkr6BkKSrKq
ZaO7KdVL0bs69HvanruxiUFu13S7PrrsYYgN2Nds7dLtPLdWUvt0dsKNdvdJ8jqe7ueXfb2Uz5fe
911VaAabbQlXZqB0689G9O9nTJ9eGgCgAC5LR13Y2D3nh9skur13XMntrtr24i9u2bg197fNOlKo
AAvezz2uzNazz4TVVEIqUvfb67UAqlC7MveruoXLBTnfFrTJYAAA2XbVII7VKVSvWl46ACgB73jj
j2A3Me7rMzvZ69NNUH33sa0733e9877YWx7r3Pr33e8PoffYR7nwgEAVABePHQSA5tWwecXnoLtn
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
1kbrWbRkY9zmbLFhO7t0VFE5m8l5ndXi1FPVqdajFVu5govdb28QbuDUwn093O4lXVBu9ObyDIfe
rT0lKxREb1WVCkVp5mn3mRW4k9v4fr+2Gb+SPwQM6/3XZ3THTD8key9fPrrg3b1SpAubNO0/E+23
r0/bmaJIyPj7ZpvdxA3SmT8/3fx/04HNE7Dllc2G6tmXq66cMuL4/x9HN92mufXWczllj/rwX85O
b/B5Zdvx93v3y+2FKXPL/0+nZv14x1/1fG2mOemTd9YHLv5cv9feuW2zf2zZoaaZ+dfOnHe6Nocu
zfTwdlFnvtxDx27wI+v5ol/0Xnpvyl8VRr73h4fSyqvauZ8/s1E6jjfk+qqqytaj5xeUmu8ufhyp
K/1U46x79/OXKPna5lk/tn3O+y6/9328nHN6Vo4bXidFOEsWj8Ndyjq1abKNvJ2VvW1GGHU6JmZH
/wdP31Ee230yHnRHoY1Ghk9P1djHFsvFXOjBgVPe9nCr96+iIIJxS8dOWhLQ8BAdqcvYVf1ORuh5
FixMqV0qUFKiqbIhaCfFIKFdkY6T/0BI6YCSSwJaevLxtuMBbDHBts4zXZAQOSHfcs2FO55g15o1
LS0MwQFSEMLgmvUftk7VPV6vP2aHWqz61kbzFl9gAakByFefBpEDktyznEbghmWLFTy/DG3cQRBM
O+pv8NvjiHT2dBnRo1KE5irFfqcMrKOd5eVK+PjhOXj5IlVssG+OM398Q5K6DDlZUSUHaq5xqIFG
/mUpVzLumeLid17QK569p+bufpV9WseLtraoaJkKHeyYd+53eHMTq+pqi5Y4uuvLVNdnMq77LvG3
KNVL1hW2JnEzXW9brSdrHVZbOOMuweO3mOrOFSj8fP333V18L6LwvqvwUL5X1WlxbXa+xdpuJ1C0
rX0XhStridQu00rixUuLS2kqX7l0rXa2tLpeFC0sXheVSdN5SUrtUuLtQoXlcTri8r5XS7SULF6U
T6Lxqyy87m8bLzsu942rzsut0uLxuJrvcrda5/Dw5bctnR7nP+MPlSMreDq8vBVpPM+Xz9X7OnOb
vDSuLtqmXdOkNdvLFOXebZtBp9a5XbPLSGPNsT0cc3R1e6iqrPXhfp5trpK79HdXP5Vn5yy5ZO5c
d9L99GipRZbwr7c415rm4svOOrnca+e8svNfR3htOHXk/pLchmw7V0lz7ofGNo2439H+Ohfln4PZ
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
