# Bazaar merge directive format 2 (Bazaar 0.90)
# revision_id: colin@gibibit.com-20080803035922-trvxiqbrfge9pilr
# target_branch: file:///home/cdb/grub/repo/trunk-clean/
# testament_sha1: 5f400b09b5eea044622e881ec30316bc7e124ed8
# timestamp: 2008-08-03 07:25:46 -0700
# source_branch: http://grub.gibibit.com/bzr/trunk-clean
# base_revision_id: colin@gibibit.com-20080803034658-kcm160bcz0e1rt9f
# 
# Begin patch
=== modified file '.bzrignore'
--- .bzrignore	2008-07-19 21:43:42 +0000
+++ .bzrignore	2008-07-19 21:44:04 +0000
@@ -7,3 +7,5 @@
 ./conf/sparc64-ieee1275.mk
 ./conf/i386-coreboot.mk
 ./build*
+./util/fonttool/build
+./util/fonttool/fonttool.iws

=== modified file 'ChangeLog'
--- ChangeLog	2008-08-02 22:24:34 +0000
+++ ChangeLog	2008-08-03 03:57:46 +0000
@@ -64,7 +64,6 @@
 	(memdisk_mod_SOURCES, memdisk_mod_CFLAGS)
 	(memdisk_mod_LDFLAGS): New variables.
 	* conf/i386-coreboot.rmk: Likewise.
-	* conf/i386-ieee1275.rmk: Likewise.
 
 2008-08-02  Robert Millan  <rmh@aybabtu.com>
 
@@ -197,6 +196,72 @@
 
 	* normal/main.c (get_line): Fix buffer overflow bug.
 
+2008-07-28  Colin D Bennett <colin@gibibit.com>
+
+	High resolution timer support.  Implemented for x86 CPUs using TSC.
+	Extracted generic grub_millisleep() so it's linked in only as needed.
+	This requires a Pentium compatible CPU; if the RDTSC instruction is
+	not supported, then it falls back on the generic grub_get_time_ms()
+	implementation that uses the machine's RTC.
+
+	* conf/i386-efi.rmk: Added new source files to kernel_elf_SOURCES.
+
+	* conf/i386-pc.rmk: Likewise.
+
+	* conf/sparc64-ieee1275.rmk: Likewise.
+
+	* conf/powerpc-ieee1275.rmk: Likewise.
+
+
+	* kern/generic/rtc_get_time_ms.c: New file.
+
+	* kern/generic/millisleep.c: New file.
+
+	* kern/misc.c (grub_millisleep_generic): Removed.
+
+	* commands/sleep.c (grub_interruptible_millisleep): Uses
+	grub_get_time_ms() instead of grub_get_rtc() to stay in sync with
+	grub_millisleep() from kern/generic/millisleep.c.
+
+	* include/grub/i386/tsc.h (grub_get_tsc): New file.  New inline
+	function.
+	(grub_cpu_is_cpuid_supported): New inline function.
+	(grub_cpu_is_tsc_supported): New inline function.
+	(grub_tsc_init): New function prototype.
+	(grub_tsc_get_time_ms): New function prototype.
+
+	* kern/i386/tsc.c (grub_get_time_ms): New file.  New function.
+	(calibrate_tsc): New static function.
+	(grub_tsc_init): New function.
+
+	* include/grub/time.h (grub_millisleep_generic): Removed.
+	(grub_get_time_ms): New function.
+	(grub_install_get_time_ms): New function.
+
+	* kern/time.c (grub_get_time_ms): New function.
+	(grub_install_get_time_ms): New function.
+
+	* kern/i386/efi/init.c (grub_millisleep): Removed.
+	(grub_machine_init): Call grub_tsc_init.
+
+	* kern/i386/linuxbios/init.c (grub_machine_init): Install the RTC
+	get_time_ms() implementation.
+
+	* kern/sparc64/ieee1275/init.c (grub_millisleep): Removed.
+	(ieee1275_get_time_ms): New function.
+	(grub_machine_init): Install get_time_ms() implementation.
+
+	* kern/i386/pc/init.c (grub_machine_init): Call grub_tsc_init().
+	(grub_millisleep): Removed.
+
+	* kern/ieee1275/init.c (grub_millisleep): Removed.
+	(grub_machine_init): Install ieee1275_get_time_ms() implementation.
+	(ieee1275_get_time_ms): New static function.
+	(grub_get_rtc): Now calls ieee1275_get_time_ms(), which does the real
+	work.
+
+	* conf/i386-ieee1275.rmk: Likewise.
+
 2008-07-28  Robert Millan  <rmh@aybabtu.com>
 
 	* partmap/apple.c (GRUB_APPLE_HEADER_MAGIC): New macro.

=== modified file 'Makefile.in'
--- Makefile.in	2008-07-24 13:56:30 +0000
+++ Makefile.in	2008-07-28 02:00:30 +0000
@@ -89,6 +89,39 @@
 YACC = @YACC@
 UNIFONT_HEX = @UNIFONT_HEX@
 
+### Pretty output control ###
+# Set up compiler and linker commands that either is quiet (does not print
+# the command line being executed) or verbose (print the command line).
+_CC := $(CC)
+_TARGET_CC := $(TARGET_CC)
+_STRIP := $(STRIP)
+_GENMODSRC := sh $(srcdir)/genmodsrc.sh
+ifeq ($(V),1)
+ override V_PREFIX :=
+ override CC = $(_CC)
+ override TARGET_CC = $(_CC)
+ override STRIP = $(_STRIP)
+ override GENMODSRC = $(_GENMODSRC)
+ override INFO_GENCMDLIST =
+ override INFO_GENFSLIST =
+ override INFO_GENPARTMAPLIST =
+ override INFO_GEN_FINAL_COMMAND_LIST =
+ override INFO_GEN_FINAL_FS_LIST =
+ override INFO_GEN_FINAL_PARTMAP_LIST =
+else
+ override V_PREFIX := @
+ override CC = @echo "COMPILE         $<"; $(_CC)
+ override TARGET_CC = @echo "COMPILE(TARGET) $<"; $(_TARGET_CC)
+ override STRIP = @echo "STRIP           $@"; $(_STRIP)
+ override GENMODSRC = @echo "GENMODSRC       $@"; $(_GENMODSRC)
+ override INFO_GENCMDLIST = @echo "GENCMDLIST      $@"
+ override INFO_GENFSLIST = @echo "GENFSLIST       $@"
+ override INFO_GENPARTMAPLIST = @echo "GENPARTMAPLIST  $@"
+ override INFO_GEN_FINAL_COMMAND_LIST = @echo "GENCMDLIST[final]  $@"
+ override INFO_GEN_FINAL_FS_LIST = @echo "GENFSLIST[final]   $@"
+ override INFO_GEN_FINAL_PARTMAP_LIST = @echo "GENPARTMAPLIST[final]  $@"
+endif
+
 # Options.
 enable_grub_emu = @enable_grub_emu@
 enable_grub_fstest = @enable_grub_fstest@
@@ -136,13 +169,16 @@
 	  || (rm -f $@; exit 1)
 
 command.lst: $(COMMANDFILES)
-	cat $^ /dev/null | sort > $@
+	$(INFO_GEN_FINAL_COMMAND_LIST)
+	$(V_PREFIX)cat $^ /dev/null | sort > $@
 
 fs.lst: $(FSFILES)
-	cat $^ /dev/null | sort > $@
+	$(INFO_GEN_FINAL_FS_LIST)
+	$(V_PREFIX)cat $^ /dev/null | sort > $@
 
 partmap.lst: $(PARTMAPFILES)
-	cat $^ /dev/null | sort > $@
+	$(INFO_GEN_FINAL_PARTMAP_LIST)
+	$(V_PREFIX)cat $^ /dev/null | sort > $@
 
 ifeq (, $(UNIFONT_HEX))
 else

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

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

=== modified file 'commands/videotest.c'
--- commands/videotest.c	2007-07-21 22:32:33 +0000
+++ commands/videotest.c	2008-07-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-08-01 03:06:55 +0000
+++ conf/common.rmk	2008-08-03 03:57:46 +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)
 

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

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

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

=== modified file 'conf/i386-pc.rmk'
--- conf/i386-pc.rmk	2008-07-27 12:51:30 +0000
+++ conf/i386-pc.rmk	2008-07-28 16:26:46 +0000
@@ -42,7 +42,11 @@
 kernel_img_SOURCES = kern/i386/pc/startup.S kern/main.c kern/device.c \
 	kern/disk.c kern/dl.c kern/file.c kern/fs.c kern/err.c \
 	kern/misc.c kern/mm.c kern/loader.c kern/rescue.c kern/term.c \
+	kern/time.c \
 	kern/i386/dl.c kern/i386/pc/init.c kern/parser.c kern/partition.c \
+	kern/i386/tsc.c \
+	kern/generic/rtc_get_time_ms.c \
+	kern/generic/millisleep.c \
 	kern/env.c \
 	term/i386/pc/console.c \
 	symlist.c
@@ -261,7 +265,10 @@
 videotest_mod_LDFLAGS = $(COMMON_LDFLAGS)
 
 # For bitmap.mod
-bitmap_mod_SOURCES = video/bitmap.c
+bitmap_mod_SOURCES = video/bitmap.c \
+                     video/bitmap_scale_nn.c \
+                     video/bitmap_scale_bilinear.c \
+                     video/bitmap_scale.c
 bitmap_mod_CFLAGS = $(COMMON_CFLAGS)
 bitmap_mod_LDFLAGS = $(COMMON_LDFLAGS)
 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

=== modified file 'include/grub/normal.h'
--- include/grub/normal.h	2008-03-26 12:01:02 +0000
+++ include/grub/normal.h	2008-08-03 03:47:20 +0000
@@ -39,7 +39,7 @@
 #define GRUB_COMMAND_FLAG_TITLE		0x4
 /* Don't print the command on booting.  */
 #define GRUB_COMMAND_FLAG_NO_ECHO	0x8
-/* Don't print the command on booting.  */
+/* Pass arguments to the command without parsing options.  */
 #define GRUB_COMMAND_FLAG_NO_ARG_PARSE	0x10
 /* Not loaded yet. Used for auto-loading.  */
 #define GRUB_COMMAND_FLAG_NOT_LOADED	0x20

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

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

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

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

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

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

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

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

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

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

=== added file 'kern/time.c'
--- kern/time.c	1970-01-01 00:00:00 +0000
+++ kern/time.c	2008-07-28 16:23:46 +0000
@@ -0,0 +1,37 @@
+/* time.c - kernel time functions */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2008  Free Software Foundation, Inc.
+ *
+ *  GRUB is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/time.h>
+
+typedef grub_uint64_t (*get_time_ms_func_t) (void);
+
+/* Function pointer to the implementation in use.  */
+static get_time_ms_func_t get_time_ms_func;
+
+grub_uint64_t
+grub_get_time_ms (void)
+{
+  return get_time_ms_func ();
+}
+
+void
+grub_install_get_time_ms (get_time_ms_func_t func)
+{
+  get_time_ms_func = func;
+}

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

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

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

=== 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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
IsAIm32DZP/+LuSKcKEgAbTM2g==
