# Bazaar merge directive format 2 (Bazaar 0.90)
# revision_id: colin@gibibit.com-20080728020030-u9gr2usazh2r2pcw
# target_branch: file:///home/cdb/grub/repo/trunk-clean/
# testament_sha1: ba4b36fe0c9333f75767c2889d0f85d158a4e1ae
# timestamp: 2008-07-27 19:16:17 -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-09 16:14:04 +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,51 @@
 #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;
+};
+
+/* A 2D rectangle type.
+ * This could be worth integrating into the video API if it proves useful.*/
+struct grub_video_rect
+{
+  /* These are signed because if there are unsigned it causes Bad Things 
+   * to happen when arithmetic and comparisions involving signed types is 
+   * done.  Important signed types include offsets from absolute locations. */
+  int x;
+  int y;
+  int width;
+  int height;
+};
+typedef struct grub_video_rect grub_video_rect_t;
+
+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 +81,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 +103,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 +122,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 +146,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:00:30 +0000
@@ -317,7 +317,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 +382,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 +393,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:00:30 +0000
@@ -43,6 +43,8 @@
 	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 \
 	term/i386/pc/console.c \
 	symlist.c
@@ -261,7 +263,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)
 

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

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

=== 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-03 14:12:08 +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
@@ -183,6 +197,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 +273,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 +291,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,

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

=== 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 'term/gfxterm.c'
--- term/gfxterm.c	2008-01-01 12:02:07 +0000
+++ term/gfxterm.c	2008-07-03 14:28:01 +0000
@@ -29,14 +29,12 @@
 #include <grub/env.h>
 #include <grub/video.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
 
 #define DEFAULT_STANDARD_COLOR  0x07
@@ -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;
@@ -170,18 +171,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;
@@ -229,6 +237,7 @@
 static grub_err_t
 grub_gfxterm_init (void)
 {
+  char *font_name;
   char *modevar;
   int width = DEFAULT_VIDEO_WIDTH;
   int height = DEFAULT_VIDEO_HEIGHT;
@@ -236,6 +245,11 @@
   int flags = DEFAULT_VIDEO_FLAGS;
   grub_video_color_t color;
 
+  /* Select the font to use. */
+  font_name = grub_env_get ("gfxterm_font");
+  if (!font_name)
+    font_name = "";   /* Allow fallback to any font. */
+
   /* Parse gfxmode environment variable if set.  */
   modevar = grub_env_get ("gfxmode");
   if (modevar)
@@ -475,7 +489,7 @@
 
   /* Create virtual screen.  */
   if (grub_virtual_screen_setup (DEFAULT_BORDER_WIDTH, DEFAULT_BORDER_WIDTH,
-                                 width, height) != GRUB_ERR_NONE)
+                                 width, height, font_name) != GRUB_ERR_NONE)
     {
       grub_video_restore ();
       return grub_errno;
@@ -661,11 +675,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 +690,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 +701,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 +721,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;
 
@@ -822,14 +839,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 +860,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 +880,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;
@@ -879,11 +900,15 @@
 static grub_ssize_t
 grub_gfxterm_getcharwidth (grub_uint32_t c)
 {
-  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
@@ -1014,8 +1039,18 @@
   dirty_region_redraw ();
 }
 
+
+/* 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)
 {
@@ -1042,12 +1077,36 @@
     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 (mode_info.width != grub_video_bitmap_get_width (bitmap)
+              || mode_info.height != grub_video_bitmap_get_height (bitmap)) 
+            {
+              struct grub_video_bitmap *scaled_bitmap;
+              grub_video_bitmap_create_scaled (&scaled_bitmap,
+                                              mode_info.width, 
+                                              mode_info.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 ();
@@ -1092,7 +1151,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-09 20:59:34 +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,159 @@
   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.  */
+
+  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;
+
+  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;
+  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 +888,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 +927,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 +998,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 +1095,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 +1129,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 +1141,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 +1177,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 +1220,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 +1256,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 +1292,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 +1335,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 +1450,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 +1705,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 +1810,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 +1852,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:00: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);

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

# Begin bundle
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
mHEzDH21sLAP0IFv5yKD/SXvkQmIo6DYyFImTADAioeOApn+MbR3h8hQP3n7zYLRhjsbECRQ/qCw
cKFxIi/12j/JwrV8LuSZtHwzYqZN36SJhoXW3apO1c+ZX1tWG+PCum7V48WYvGqx/bf7Cz21WZOn
Dpi5dLOWTto7atXbrrtuwUzWdMjj/7MzN/yuP5oU6X6GJmbkCMp3Dm5gZDEA3Nn02YPZdu6ZPn5y
feAhi2Xdtnp4+ylChgVNQxBiwuS+KRx4szNyce7bYtOBGNw2iBCuX2ZPazVipZd9fWb7NmjR8tFP
g+xmydLO8pmpk+njHDVipwzbMn2cs2jt23avFmLVm5Jyu5dKZrJi9PT9yVn11qvho6buWTlw9LMl
PSmqyzRmu0e1PbVSzZi1bHjNs9OWzpaMmjpL4t2SaqZtHDp7drsnTVi1WbrsmLdmdNWrJo4Ys2Cz
dy1dOXL95VToybMLNnDp799F27ly9OF3Dp7enbpkp7WYsqXeNGL23asntyzUpZZpOVLqmLlkwSyx
qp47XctXabGThdqyZrsTls2e/fTFyww6XcG7JuWaN3Kmjt7arMWS5owdMXTtdscMzBu3YKaNp+yv
tLRX6fkiQkenb0p20bqMVnp48aPHpZNTJiwfLdqyM37YySUBfukceMR31R62TDB9v7nuOhqWmhzI
CxNDof3gD2Pj+SMH+yppKP1lSf4Zv9nOB1H9OFXBm/rH7gyY2H2GHO87zqefiRHcqQNyw5BEX7Gi
7SYKjJk1Ys2Tdu49VC9f8tf0YWPVGFRb+9kprZA9GSJs9HIMTa8sLjIvNTEzIDmZi7aLrn6MGD9H
6MWTVuu/U0YnDkp6/bki/3bLvGHTPPFs8dtmqarO2LdgaPu8KNpDzoJRvGQQohpP0c++/q5SoQ+Y
TmQT4mQj9CQj0a41OAupucdIm5QTGxM2OBWqcz6asH5mrHHJ+LJ07D+0KqIr8m79FP+hR8MUB4+H
0d97OXt2fzemr08dNXDozKhoPrIAUsTsFCBuInCRTtIesgeCGQLJJDuJhMhZ4Y8BoQWJsROZzKHI
iTNjqdQ5FpcVLCBUuOZgROZeTHLjE5EgXLpeWHvE953uT7KlSpXlta14HUlQkOBT2CAqFx96J2LQ
QLHmUfWi2S/+ueAFmEtiICoZDogKR7AT3sf7oHfW5Wke4uNpqBniZ/LDkin9EkFh+59p7qQYBWkl
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
KcfypeP/+LuSKcKEhLofwLA=
