# Bazaar merge directive format 2 (Bazaar 0.90)
# revision_id: colin@gibibit.com-20080719214404-ye3x3zjigausaru3
# target_branch: file:///home/cdb/grub/repo/trunk-clean/
# testament_sha1: f3f7e613f4952d31fe35294a437f872442769d50
# timestamp: 2008-07-19 14:49:37 -0700
# source_branch: http://grub.gibibit.com/bzr/trunk-clean
# base_revision_id: colin@gibibit.com-20080719214342-aw2lim5mylg2crpc
# 
# Begin patch
=== modified file '.bzrignore'
--- .bzrignore	2008-07-19 21:43:42 +0000
+++ .bzrignore	2008-07-19 21:44:04 +0000
@@ -7,3 +7,5 @@
 ./conf/sparc64-ieee1275.mk
 ./conf/i386-coreboot.mk
 ./build*
+./util/fonttool/build
+./util/fonttool/fonttool.iws

=== modified file 'ChangeLog'
--- ChangeLog	2008-07-18 15:11:35 +0000
+++ ChangeLog	2008-07-19 21:39:45 +0000
@@ -332,6 +332,73 @@
 
 	* disk/ata.c: Use named constants for status bits.
 
+2008-07-04  Colin D Bennett <colin@gibibit.com>
+
+	High resolution timer support.  Implemented for i386 CPU using TSC.
+	Extracted generic grub_millisleep() so it's linked in only as needed.
+	This requires a Pentium compatible CPU; currently the code does not
+	check for this (so it will fail on 386 and 486 machines).
+
+	* conf/i386-efi.rmk: Added TSC high resolution time module, link with
+	generic grub_millisleep() function.
+
+	* conf/i386-pc.rmk: Likewise.
+
+	* conf/sparc64-ieee1275.rmk: Add kern/generic/millisleep.c and
+	kern/generic/get_time_ms.c to kernel, to use generic time functions.
+
+	* conf/powerpc-ieee1275.rmk: Add kern/generic/millisleep.c to kernel,
+	to use generic grub_millisleep() function.
+
+	* conf/i386-linuxbios.rmk: Added kern/generic/get_time_ms.c to the
+	kernel.
+
+	* kern/generic/get_time_ms.c (grub_get_time_ms): New file.  Platform
+	independent implementation of grub_get_time_ms() using the RTC that
+	can be linked into a platform's kernel when it does not implement its
+	own specialized grub_get_time_ms() function.
+
+	* kern/generic/millisleep.c (grub_millisleep): New file.  Extracted
+	from grub_millisleep_generic() in kern/misc.c and renamed.  Changed it
+	to use grub_get_time_ms() instead of grub_get_rtc() for better
+	precision on when high resolution time is available.
+
+	* kern/misc.c (grub_millisleep_generic): Deleted.  Moved to
+	kern/generic/millisleep.c so that it is only included in the kernel
+	image when a platform does not define a specialized version.
+
+	* commands/sleep.c (grub_interruptible_millisleep): Uses
+	grub_get_time_ms() instead of grub_get_rtc() to stay in sync with
+	grub_millisleep() from kern/generic/millisleep.c.
+
+	* include/grub/i386/tsc.h (grub_get_tsc): New file.  Inline function
+	grub_get_tsc() uses x86 RDTSC instruction (available on Pentium+ CPUs)
+	to read the counter value for the TSC.
+	(grub_tsc_calibrate): Declare this function for grub_machine_init().
+
+	* kern/i386/tsc.c (grub_get_time_ms): x86 TSC support providing a high
+	resolution clock.
+	(grub_tsc_calibrate): New function to calibrate the TSC using RTC.
+
+	* include/grub/time.h (grub_get_time_ms): Added grub_get_time_ms()
+	function to return the current time in millseconds since the epoch.
+	This supports higher resolution time than grub_get_rtc() on some
+	platforms such as i386-pc, where the RTC has only about 1/18 s
+	precision but a higher precision timer such as the TSC is available.
+
+	* kern/i386/efi/init.c (grub_millisleep): Deleted.  Don't define
+	grub_millisleep() -- it just called grub_millisleep_generic() but now
+	it is linked to kern/generic/millisleep.c for the implementation.
+
+	* kern/sparc64/ieee1275/init.c (grub_millisleep): Deleted.
+
+	* kern/i386/pc/init.c (grub_machine_init): Call grub_tsc_calibrate().
+	(grub_millisleep): Deleted.
+
+	* kern/ieee1275/init.c (grub_millisleep): Deleted.
+	(grub_get_rtc): Now calls grub_get_time_ms(), which does the real
+	work.
+
 2008-07-04  Pavel Roskin  <proski@gnu.org>
 
 	* kern/i386/linuxbios/init.c (grub_machine_init): Cast addr to

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

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

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

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

=== modified file 'conf/common.rmk'
--- conf/common.rmk	2008-07-11 19:09:14 +0000
+++ conf/common.rmk	2008-07-19 21:39:24 +0000
@@ -312,7 +312,7 @@
 help_mod_LDFLAGS = $(COMMON_LDFLAGS)
 
 # For font.mod.
-font_mod_SOURCES = font/manager.c
+font_mod_SOURCES = font/font_cmd.c font/font.c font/loader.c
 font_mod_CFLAGS = $(COMMON_CFLAGS)
 font_mod_LDFLAGS = $(COMMON_LDFLAGS)
 

=== modified file 'conf/i386-efi.rmk'
--- conf/i386-efi.rmk	2008-07-17 08:50:26 +0000
+++ conf/i386-efi.rmk	2008-07-19 21:39:45 +0000
@@ -84,7 +84,9 @@
 	kern/misc.c kern/mm.c kern/loader.c kern/rescue.c kern/term.c \
 	kern/i386/dl.c kern/i386/efi/init.c kern/parser.c kern/partition.c \
 	kern/env.c symlist.c kern/efi/efi.c kern/efi/init.c kern/efi/mm.c \
-	term/efi/console.c disk/efi/efidisk.c
+	term/efi/console.c disk/efi/efidisk.c \
+	kern/i386/tsc.c \
+	kern/generic/millisleep.c
 kernel_mod_HEADERS = arg.h boot.h cache.h device.h disk.h dl.h elf.h elfload.h \
 	env.h err.h file.h fs.h kernel.h loader.h misc.h mm.h net.h parser.h \
 	partition.h pc_partition.h rescue.h symbol.h term.h time.h types.h \

=== modified file 'conf/i386-pc.rmk'
--- conf/i386-pc.rmk	2008-07-13 00:55:15 +0000
+++ conf/i386-pc.rmk	2008-07-19 21:39:45 +0000
@@ -43,6 +43,8 @@
 	kern/disk.c kern/dl.c kern/file.c kern/fs.c kern/err.c \
 	kern/misc.c kern/mm.c kern/loader.c kern/rescue.c kern/term.c \
 	kern/i386/dl.c kern/i386/pc/init.c kern/parser.c kern/partition.c \
+	kern/i386/tsc.c \
+	kern/generic/millisleep.c \
 	kern/env.c \
 	term/i386/pc/console.c \
 	symlist.c
@@ -261,7 +263,10 @@
 videotest_mod_LDFLAGS = $(COMMON_LDFLAGS)
 
 # For bitmap.mod
-bitmap_mod_SOURCES = video/bitmap.c
+bitmap_mod_SOURCES = video/bitmap.c \
+                     video/bitmap_scale_nn.c \
+                     video/bitmap_scale_bilinear.c \
+                     video/bitmap_scale.c
 bitmap_mod_CFLAGS = $(COMMON_CFLAGS)
 bitmap_mod_LDFLAGS = $(COMMON_LDFLAGS)
 

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

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

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

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

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

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

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

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

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

=== modified file 'include/grub/video.h'
--- include/grub/video.h	2008-01-01 12:02:07 +0000
+++ include/grub/video.h	2008-07-03 14:12:08 +0000
@@ -21,6 +21,7 @@
 
 #include <grub/err.h>
 #include <grub/types.h>
+#include <grub/font.h>
 
 /* Video color in hardware dependent format.  Users should not assume any
    specific coding format.  */
@@ -31,40 +32,41 @@
 struct grub_video_render_target;
 
 /* Forward declarations for used data structures.  */
-struct grub_font_glyph;
 struct grub_video_bitmap;
 
 /* Defines used to describe video mode or rendering target.  */
-#define GRUB_VIDEO_MODE_TYPE_ALPHA		0x00000008
-#define GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED	0x00000004
+#define GRUB_VIDEO_MODE_TYPE_ALPHA		0x00000020
+#define GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED	0x00000010
+#define GRUB_VIDEO_MODE_TYPE_1BIT_BITMAP	0x00000004
 #define GRUB_VIDEO_MODE_TYPE_INDEX_COLOR	0x00000002
 #define GRUB_VIDEO_MODE_TYPE_RGB		0x00000001
 
 /* Defines used to mask flags.  */
-#define GRUB_VIDEO_MODE_TYPE_COLOR_MASK		0x00000003
+#define GRUB_VIDEO_MODE_TYPE_COLOR_MASK		0x0000000F
 
 /* Defines used to specify requested bit depth.  */
 #define GRUB_VIDEO_MODE_TYPE_DEPTH_MASK		0x0000ff00
 #define GRUB_VIDEO_MODE_TYPE_DEPTH_POS		8
 
-/* Defined predefined render targets.  */
-#define GRUB_VIDEO_RENDER_TARGET_DISPLAY	((struct grub_video_render_target *) 0)
-#define GRUB_VIDEO_RENDER_TARGET_FRONT_BUFFER	((struct grub_video_render_target *) 0)
-#define GRUB_VIDEO_RENDER_TARGET_BACK_BUFFER	((struct grub_video_render_target *) 1)
+/* Predefined render target: */
+/* The render target that client code should render to. */
+#define GRUB_VIDEO_RENDER_TARGET_DISPLAY \
+  ((struct grub_video_render_target *) 0)
+
 
 /* Defined blitting formats.  */
 enum grub_video_blit_format
   {
-    /* Follow exactly field & mask information.  */
-    GRUB_VIDEO_BLIT_FORMAT_RGBA,
-    /* Make optimization assumption.  */
-    GRUB_VIDEO_BLIT_FORMAT_R8G8B8A8,
-    /* Follow exactly field & mask information.  */
-    GRUB_VIDEO_BLIT_FORMAT_RGB,
-    /* Make optimization assumption.  */
-    GRUB_VIDEO_BLIT_FORMAT_R8G8B8,
+    GRUB_VIDEO_BLIT_FORMAT_RGBA,       /* General: Use fields & masks. */
+    GRUB_VIDEO_BLIT_FORMAT_RGBA_8888,  /* Optimized format. */
+    GRUB_VIDEO_BLIT_FORMAT_BGRA_8888,  /* Optimized format. */
+    GRUB_VIDEO_BLIT_FORMAT_RGB,        /* General: Use fields & masks. */
+    GRUB_VIDEO_BLIT_FORMAT_RGB_888,    /* Optimized format. */
+    GRUB_VIDEO_BLIT_FORMAT_BGR_888,    /* Optimized format. */
     /* When needed, decode color or just use value as is.  */
-    GRUB_VIDEO_BLIT_FORMAT_INDEXCOLOR
+    GRUB_VIDEO_BLIT_FORMAT_INDEXCOLOR,
+    /* Two color bitmap; bits packed: rows are not padded to byte boundary. */
+    GRUB_VIDEO_BLIT_FORMAT_1BIT_PACKED
   };
 
 /* Define blitting operators.  */
@@ -127,6 +129,18 @@
   /* What is location of reserved color bits.  In Index Color mode, 
      this is 0.  */
   unsigned int reserved_field_pos;
+
+  /* For 1-bit bitmaps, the background color.  Used for bits = 0. */
+  grub_uint8_t bg_red;
+  grub_uint8_t bg_green;
+  grub_uint8_t bg_blue;
+  grub_uint8_t bg_alpha;
+
+  /* For 1-bit bitmaps, the foreground color.  Used for bits = 1. */
+  grub_uint8_t fg_red;
+  grub_uint8_t fg_green;
+  grub_uint8_t fg_blue;
+  grub_uint8_t fg_alpha;
 };
 
 struct grub_video_palette_data
@@ -183,6 +197,10 @@
   grub_err_t (*blit_glyph) (struct grub_font_glyph *glyph,
                             grub_video_color_t color, int x, int y);
 
+  grub_err_t (*draw_string) (const char *str, grub_font_t font, 
+                             grub_video_color_t color, 
+                             int left_x, int baseline_y);
+
   grub_err_t (*blit_bitmap) (struct grub_video_bitmap *bitmap,
                              enum grub_video_blit_operators oper,
                              int x, int y, int offset_x, int offset_y,
@@ -255,6 +273,10 @@
 grub_err_t grub_video_blit_glyph (struct grub_font_glyph *glyph,
                                   grub_video_color_t color, int x, int y);
 
+grub_err_t grub_video_draw_string (const char *str, grub_font_t font, 
+                                   grub_video_color_t color, 
+                                   int left_x, int baseline_y);
+
 grub_err_t grub_video_blit_bitmap (struct grub_video_bitmap *bitmap,
                                    enum grub_video_blit_operators oper,
                                    int x, int y, int offset_x, int offset_y,
@@ -269,6 +291,9 @@
 
 grub_err_t grub_video_scroll (grub_video_color_t color, int dx, int dy);
 
+/* Swap the pages referred to by the front buffer and back buffer render
+ * targets, and the page previously referred to by the back buffer is made
+ * visible on the display. */
 grub_err_t grub_video_swap_buffers (void);
 
 grub_err_t grub_video_create_render_target (struct grub_video_render_target **result,

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

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

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

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

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

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

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

=== modified file 'kern/sparc64/ieee1275/init.c'
--- kern/sparc64/ieee1275/init.c	2007-10-22 18:59:33 +0000
+++ kern/sparc64/ieee1275/init.c	2008-07-04 17:55:21 +0000
@@ -66,12 +66,6 @@
   /* Never reached.  */
 }
 
-void
-grub_millisleep (grub_uint32_t ms)
-{
-  grub_millisleep_generic (ms);
-}
-
 int
 grub_ieee1275_test_flag (enum grub_ieee1275_flag flag)
 {

=== modified file 'term/gfxterm.c'
--- term/gfxterm.c	2008-01-01 12:02:07 +0000
+++ term/gfxterm.c	2008-07-03 14:28:01 +0000
@@ -29,14 +29,12 @@
 #include <grub/env.h>
 #include <grub/video.h>
 #include <grub/bitmap.h>
+#include <grub/bitmap_scale.h>
 
 #define DEFAULT_VIDEO_WIDTH	640
 #define DEFAULT_VIDEO_HEIGHT	480
 #define DEFAULT_VIDEO_FLAGS	0
 
-#define DEFAULT_CHAR_WIDTH  	8
-#define DEFAULT_CHAR_HEIGHT 	16
-
 #define DEFAULT_BORDER_WIDTH	10
 
 #define DEFAULT_STANDARD_COLOR  0x07
@@ -91,6 +89,9 @@
   unsigned int cursor_y;
   int cursor_state;
 
+  /* Font settings. */
+  grub_font_t font;
+
   /* Terminal color settings.  */
   grub_uint8_t standard_color_setting;
   grub_uint8_t normal_color_setting;
@@ -170,18 +171,25 @@
 
 static grub_err_t
 grub_virtual_screen_setup (unsigned int x, unsigned int y,
-                           unsigned int width, unsigned int height)
+                           unsigned int width, unsigned int height,
+                           const char *font_name)
 {
   /* Free old virtual screen.  */
   grub_virtual_screen_free ();
 
   /* Initialize with default data.  */
+  virtual_screen.font = grub_font_get (font_name);
+  if (!virtual_screen.font)
+    return grub_error (GRUB_ERR_BAD_FONT,
+                       "No font loaded.");
   virtual_screen.width = width;
   virtual_screen.height = height;
   virtual_screen.offset_x = x;
   virtual_screen.offset_y = y;
-  virtual_screen.char_width = DEFAULT_CHAR_WIDTH;
-  virtual_screen.char_height = DEFAULT_CHAR_HEIGHT;
+  virtual_screen.char_width = 
+    grub_font_get_max_char_width (virtual_screen.font);
+  virtual_screen.char_height = 
+    grub_font_get_max_char_height (virtual_screen.font);
   virtual_screen.cursor_x = 0;
   virtual_screen.cursor_y = 0;
   virtual_screen.cursor_state = 1;
@@ -229,6 +237,7 @@
 static grub_err_t
 grub_gfxterm_init (void)
 {
+  char *font_name;
   char *modevar;
   int width = DEFAULT_VIDEO_WIDTH;
   int height = DEFAULT_VIDEO_HEIGHT;
@@ -236,6 +245,11 @@
   int flags = DEFAULT_VIDEO_FLAGS;
   grub_video_color_t color;
 
+  /* Select the font to use. */
+  font_name = grub_env_get ("gfxterm_font");
+  if (!font_name)
+    font_name = "";   /* Allow fallback to any font. */
+
   /* Parse gfxmode environment variable if set.  */
   modevar = grub_env_get ("gfxmode");
   if (modevar)
@@ -475,7 +489,7 @@
 
   /* Create virtual screen.  */
   if (grub_virtual_screen_setup (DEFAULT_BORDER_WIDTH, DEFAULT_BORDER_WIDTH,
-                                 width, height) != GRUB_ERR_NONE)
+                                 width, height, font_name) != GRUB_ERR_NONE)
     {
       grub_video_restore ();
       return grub_errno;
@@ -661,11 +675,12 @@
 write_char (void)
 {
   struct grub_colored_char *p;
-  struct grub_font_glyph glyph;
+  struct grub_font_glyph *glyph;
   grub_video_color_t color;
   grub_video_color_t bgcolor;
   unsigned int x;
   unsigned int y;
+  int ascent;
 
   /* Find out active character.  */
   p = (virtual_screen.text_buffer
@@ -675,7 +690,8 @@
   p -= p->index;
 
   /* Get glyph for character.  */
-  grub_font_get_glyph (p->code, &glyph);
+  glyph = grub_font_get_glyph (virtual_screen.font, p->code);
+  ascent = grub_font_get_ascent (virtual_screen.font);
 
   color = p->fg_color;
   bgcolor = p->bg_color;
@@ -685,13 +701,13 @@
 
   /* Render glyph to text layer.  */
   grub_video_set_active_render_target (text_layer);
-  grub_video_fill_rect (bgcolor, x, y, glyph.width, glyph.height);
-  grub_video_blit_glyph (&glyph, color, x, y);
+  grub_video_fill_rect (bgcolor, x, y, glyph->width, glyph->height);
+  grub_video_blit_glyph (glyph, color, x, y + ascent);
   grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
 
   /* Mark character to be drawn.  */
   dirty_region_add (virtual_screen.offset_x + x, virtual_screen.offset_y + y,
-                    glyph.width, glyph.height);
+                    glyph->width, glyph->height);
 }
 
 static void
@@ -705,7 +721,8 @@
 
   /* Determine cursor properties and position on text layer. */
   x = virtual_screen.cursor_x * virtual_screen.char_width;
-  y = ((virtual_screen.cursor_y + 1) * virtual_screen.char_height) - 3;  
+  y = (virtual_screen.cursor_y * virtual_screen.char_height   
+       + grub_font_get_ascent (virtual_screen.font));
   width = virtual_screen.char_width;
   height = 2;
 
@@ -822,14 +839,18 @@
     }
   else
     {
-      struct grub_font_glyph glyph;
+      struct grub_font_glyph *glyph;
       struct grub_colored_char *p;
+      unsigned char_width;
 
       /* Get properties of the character.  */    
-      grub_font_get_glyph (c, &glyph);
+      glyph = grub_font_get_glyph (virtual_screen.font, c);
+
+      /* TODO [CDB] Fix wide characters.  Bi-width font? */
+      char_width = 1;
 
       /* If we are about to exceed line length, wrap to next line.  */
-      if (virtual_screen.cursor_x + glyph.char_width > virtual_screen.columns)
+      if (virtual_screen.cursor_x + char_width > virtual_screen.columns)
         grub_putchar ('\n');
 
       /* Find position on virtual screen, and fill information.  */
@@ -839,18 +860,18 @@
       p->code = c;
       p->fg_color = virtual_screen.fg_color;
       p->bg_color = virtual_screen.bg_color;
-      p->width = glyph.char_width - 1;
+      p->width = char_width - 1;
       p->index = 0;
 
       /* If we have large glyph, add fixup info.  */
-      if (glyph.char_width > 1)
+      if (char_width > 1)
         {
           unsigned i;
 
-          for (i = 1; i < glyph.char_width; i++)
+          for (i = 1; i < char_width; i++)
             {
               p[i].code = ' ';
-              p[i].width = glyph.char_width - 1;
+              p[i].width = char_width - 1;
               p[i].index = i;
             }
         }
@@ -859,7 +880,7 @@
       write_char ();
 
       /* Make sure we scroll screen when needed and wrap line correctly.  */
-      virtual_screen.cursor_x += glyph.char_width;
+      virtual_screen.cursor_x += char_width;
       if (virtual_screen.cursor_x >= virtual_screen.columns)
         {
           virtual_screen.cursor_x = 0;
@@ -879,11 +900,15 @@
 static grub_ssize_t
 grub_gfxterm_getcharwidth (grub_uint32_t c)
 {
-  struct grub_font_glyph glyph;
-
-  grub_font_get_glyph (c, &glyph);
-
-  return glyph.char_width;
+#if 0
+  struct grub_font_glyph *glyph;
+
+  glyph = grub_font_get_glyph (c);
+
+  return glyph->char_width;
+#else
+  return 1;   /* TODO [CDB] Fix wide characters. */
+#endif
 }
 
 static grub_uint16_t
@@ -1014,8 +1039,18 @@
   dirty_region_redraw ();
 }
 
+
+/* Option array indices. */
+#define BACKGROUND_CMD_ARGINDEX_MODE 0
+
+static const struct grub_arg_option background_image_cmd_options[] = {
+  {"mode", 'm', 0, "Background image mode (`stretch', `normal').", 0, 
+    ARG_TYPE_STRING},
+  {0, 0, 0, 0, 0, 0}
+};
+
 static grub_err_t
-grub_gfxterm_background_image_cmd (struct grub_arg_list *state __attribute__ ((unused)),
+grub_gfxterm_background_image_cmd (struct grub_arg_list *state,
                                    int argc,
                                    char **args)
 {
@@ -1042,12 +1077,36 @@
     if (grub_errno != GRUB_ERR_NONE)
       return grub_errno;
 
+    /* Determine if the bitmap should be scaled to fit the screen. */
+    if (!state[BACKGROUND_CMD_ARGINDEX_MODE].set
+        || grub_strcmp (state[BACKGROUND_CMD_ARGINDEX_MODE].arg,
+                        "stretch") == 0)
+        {
+          if (mode_info.width != grub_video_bitmap_get_width (bitmap)
+              || mode_info.height != grub_video_bitmap_get_height (bitmap)) 
+            {
+              struct grub_video_bitmap *scaled_bitmap;
+              grub_video_bitmap_create_scaled (&scaled_bitmap,
+                                              mode_info.width, 
+                                              mode_info.height,
+                                              bitmap,
+                                              GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST);
+              if (grub_errno == GRUB_ERR_NONE)
+                {
+                  /* Replace the original bitmap with the scaled one. */
+                  grub_video_bitmap_destroy (bitmap);
+                  bitmap = scaled_bitmap;
+                }
+            }
+        }
+
+
     /* If bitmap was loaded correctly, display it.  */
     if (bitmap)
       {
         /* Determine bitmap dimensions.  */
         bitmap_width = grub_video_bitmap_get_width (bitmap);
-        bitmap_height = grub_video_bitmap_get_width (bitmap);
+        bitmap_height = grub_video_bitmap_get_height (bitmap);
         
         /* Mark whole screen as dirty.  */
         dirty_region_reset ();
@@ -1092,7 +1151,7 @@
                          GRUB_COMMAND_FLAG_BOTH,
                          "background_image",
                          "Load background image for active terminal",
-                         0);
+                         background_image_cmd_options);
 }
 
 GRUB_MOD_FINI(term_gfxterm)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

=== modified file 'video/readers/jpeg.c'
--- video/readers/jpeg.c	2008-01-22 13:50:38 +0000
+++ video/readers/jpeg.c	2008-07-03 13:49:18 +0000
@@ -542,7 +542,7 @@
 
   if (grub_video_bitmap_create (data->bitmap, data->image_width,
 				data->image_height,
-				GRUB_VIDEO_BLIT_FORMAT_R8G8B8))
+				GRUB_VIDEO_BLIT_FORMAT_RGB_888))
     return grub_errno;
 
   data->bit_mask = 0x0;

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

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

=== 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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
8XckU4UJDYq38SA=
