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

=== modified file 'conf/common.rmk'
--- conf/common.rmk	2008-07-11 19:09:14 +0000
+++ conf/common.rmk	2008-07-19 21:46:40 +0000
@@ -269,6 +269,7 @@
 	cmp.mod cat.mod help.mod font.mod search.mod		\
 	loopback.mod fs_uuid.mod configfile.mod echo.mod	\
 	terminfo.mod test.mod blocklist.mod hexdump.mod		\
+	gfxmenu.mod                                             \
 	read.mod sleep.mod loadenv.mod
 
 # For hello.mod.
@@ -276,6 +277,11 @@
 hello_mod_CFLAGS = $(COMMON_CFLAGS)
 hello_mod_LDFLAGS = $(COMMON_LDFLAGS)
 
+# For gfxmenu.mod.
+gfxmenu_mod_SOURCES = gfxmenu/gfxmenu.c gfxmenu/widget-box.c
+gfxmenu_mod_CFLAGS = $(COMMON_CFLAGS)
+gfxmenu_mod_LDFLAGS = $(COMMON_LDFLAGS)
+
 # For boot.mod.
 boot_mod_SOURCES = commands/boot.c
 boot_mod_CFLAGS = $(COMMON_CFLAGS)
@@ -312,7 +318,7 @@
 help_mod_LDFLAGS = $(COMMON_LDFLAGS)
 
 # For font.mod.
-font_mod_SOURCES = font/manager.c
+font_mod_SOURCES = font/font_cmd.c font/font.c font/loader.c
 font_mod_CFLAGS = $(COMMON_CFLAGS)
 font_mod_LDFLAGS = $(COMMON_LDFLAGS)
 

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

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

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

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

=== modified file 'config.h.in'
--- config.h.in	2008-07-13 00:55:15 +0000
+++ config.h.in	2008-07-19 21:46:40 +0000
@@ -113,10 +113,37 @@
 /* Number of bits in a file offset, on hosts where this is settable. */
 #undef _FILE_OFFSET_BITS
 
+/* Define for large files, on AIX-style hosts. */
+#undef _LARGE_FILES
+
+/* Define to 1 if on MINIX. */
+#undef _MINIX
+
+/* Define to 2 if the system does not provide POSIX.1 features except with
+   this defined. */
+#undef _POSIX_1_SOURCE
+
+/* Define to 1 if you need to in order for `stat' and other things to work. */
+#undef _POSIX_SOURCE
+
+/* Enable extensions on AIX 3, Interix.  */
+#ifndef _ALL_SOURCE
+# undef _ALL_SOURCE
+#endif
 /* Enable GNU extensions on systems that have them.  */
 #ifndef _GNU_SOURCE
 # undef _GNU_SOURCE
 #endif
+/* Enable threading extensions on Solaris.  */
+#ifndef _POSIX_PTHREAD_SEMANTICS
+# undef _POSIX_PTHREAD_SEMANTICS
+#endif
+/* Enable extensions on HP NonStop.  */
+#ifndef _TANDEM_SOURCE
+# undef _TANDEM_SOURCE
+#endif
+/* Enable general extensions on Solaris.  */
+#ifndef __EXTENSIONS__
+# undef __EXTENSIONS__
+#endif
 
-/* Define for large files, on AIX-style hosts. */
-#undef _LARGE_FILES

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

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

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

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

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

=== added directory 'gfxmenu'
=== added file 'gfxmenu/gfxmenu.c'
--- gfxmenu/gfxmenu.c	1970-01-01 00:00:00 +0000
+++ gfxmenu/gfxmenu.c	2008-07-19 19:56:06 +0000
@@ -0,0 +1,506 @@
+/* gfxmenu.c - Graphical menu interface. */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2008  Free Software Foundation, Inc.
+ *
+ *  GRUB is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/types.h>
+#include <grub/misc.h>
+#include <grub/mm.h>
+#include <grub/err.h>
+#include <grub/dl.h>
+#include <grub/normal.h>
+#include <grub/video.h>
+#include <grub/gfxterm.h>
+#include <grub/bitmap.h>
+#include <grub/bitmap_scale.h>
+#include <grub/term.h>
+#include <grub/gfxwidgets.h>
+#include <grub/menu.h>
+#include <grub/menu_viewer.h>
+
+typedef struct menu_item
+{
+  const char *name;
+  struct grub_video_bitmap *icon;
+  grub_menu_entry_t entry;
+}
+menu_item_t;
+
+static const int icon_width = 32;
+static const int icon_height = 32;
+static grub_video_rect_t screen;
+static grub_font_t title_font;
+static grub_font_t item_font;
+static grub_font_t status_font;
+static grub_video_color_t title_color;
+static grub_video_color_t item_color;
+static grub_video_color_t status_color;
+static grub_video_color_t status_bg_color;
+static struct grub_video_bitmap *background_image;
+static const char title_text[] = "GRUB 2 Boot Menu";
+static int num_items;
+static menu_item_t *items;
+static int selected_item_index;
+static grub_gfxmenu_box_t menu_box;
+static grub_gfxmenu_box_t selected_item_box;
+
+static grub_err_t
+menu_init (grub_menu_t menu)
+{
+  num_items = menu->size;
+  if (num_items < 1)
+    return grub_error (GRUB_ERR_MENU, "Empty menu");
+
+  items = grub_malloc (num_items * sizeof (*items));
+  if (!items)
+    return grub_errno;
+
+  int i;
+  grub_menu_entry_t cur;
+  for (i = 0, cur = menu->entry_list; i < num_items; i++, cur = cur->next)
+    {
+      items[i].name = cur->title;
+      items[i].icon = 0;  /* TODO match w/ patterns to choose an icon */
+      items[i].entry = cur;
+    }
+
+  /*
+     items[0].name = "Ubuntu 8.10";
+     grub_video_bitmap_load (&img, "/boot/images/icon-ubuntu.tga");
+     if (grub_errno == GRUB_ERR_NONE)
+     {
+     grub_video_bitmap_create_scaled (&items[0].icon, icon_width,
+     icon_height, img,
+     GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST);
+     if (grub_errno != GRUB_ERR_NONE)
+     return;
+     grub_video_bitmap_destroy (img);
+     }
+   */
+
+  return GRUB_ERR_NONE;
+}
+
+static void
+menu_destroy (void)
+{
+  if (items)
+    {
+      int i;
+      for (i = 0; i < num_items; i++)
+        {
+          if (items[i].icon)
+            {
+              grub_video_bitmap_destroy (items[i].icon);
+              items[i].icon = 0;
+            }
+        }
+      grub_free (items);
+      items = 0;
+    }
+}
+
+static grub_err_t
+style_init (void)
+{
+  if (!(title_font = grub_font_get ("Helvetica Bold 24"))
+      || !(item_font = grub_font_get ("Helvetica Bold 12"))
+      || !(status_font = grub_font_get ("Helvetica 10")))
+    return grub_errno;
+
+  title_color = grub_video_map_rgb (0, 0, 0);
+  item_color = grub_video_map_rgb (0, 0, 0);
+  status_color = grub_video_map_rgb (255, 255, 255);
+  status_bg_color = grub_video_map_rgba (0, 0, 0, 112);
+
+  struct grub_video_bitmap *img;
+  grub_video_bitmap_load (&img, "/boot/images/bg-protomenu1.tga");
+  if (grub_errno != GRUB_ERR_NONE)
+    return grub_errno;
+
+  grub_video_bitmap_create_scaled (&background_image, screen.width,
+                                   screen.height, img,
+                                   GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST);
+  if (grub_errno != GRUB_ERR_NONE)
+    return grub_errno;
+  grub_video_bitmap_destroy (img);
+
+  menu_box = grub_gfxmenu_create_box ("/boot/images/menubox_", ".tga");
+  if (menu_box == 0)
+    return grub_error (GRUB_ERR_MENU, "Unable to create menu box");
+
+  selected_item_box = grub_gfxmenu_create_box ("/boot/images/select_blue_", ".tga");
+  if (selected_item_box == 0)
+    return grub_error (GRUB_ERR_MENU, "Unable to create selection box");
+
+  return GRUB_ERR_NONE;
+}
+
+static void
+style_destroy (void)
+{
+  if (menu_box)
+    {
+      menu_box->destroy(menu_box);
+      menu_box = 0;
+    }
+  if (selected_item_box)
+    {
+      selected_item_box->destroy(selected_item_box);
+      selected_item_box = 0;
+    }
+}
+
+static void
+draw_background (void)
+{
+  grub_video_blit_bitmap (background_image, GRUB_VIDEO_BLIT_REPLACE,
+                          0, 0, 0, 0,
+                          grub_video_bitmap_get_width (background_image),
+                          grub_video_bitmap_get_height (background_image));
+}
+
+static void
+draw_menu (void)
+{
+  int boxpad = 14;
+  int icon_text_space = 4;
+  int item_vspace = 16;
+
+  int ascent = grub_font_get_ascent (item_font);
+  int descent = grub_font_get_descent (item_font);
+  int item_height = 34;
+
+  grub_video_rect_t r;
+  r.width = screen.width * 4 / 5;
+  /* Set the menu box height to fit the items.  */
+  r.height = (item_height * num_items 
+              + item_vspace * (num_items - 1) 
+              + 2 * boxpad);
+  r.x = (screen.width - r.width) / 2;
+  r.y = (screen.height - r.height) / 2;
+  menu_box->set_content_size (menu_box, r.width, r.height);
+
+  int menu_box_left_pad = menu_box->get_left_pad (menu_box);
+  int menu_box_top_pad = menu_box->get_top_pad (menu_box);
+  menu_box->draw (menu_box, r.x - menu_box_left_pad, r.y - menu_box_top_pad);
+
+  int item_top = r.y + boxpad;
+  int item_left = r.x + boxpad;
+  int i;
+
+  for (i = 0; i < num_items; i++)
+    {
+      if (i == selected_item_index)
+        {
+          selected_item_box->set_content_size (selected_item_box,
+                                               r.width - 2 * boxpad,
+                                               item_height);
+          int leftpad = selected_item_box->get_left_pad (selected_item_box);
+          int toppad = selected_item_box->get_top_pad (selected_item_box);
+          selected_item_box->draw (selected_item_box,
+                                   item_left - leftpad,
+                                   item_top - toppad);
+        }
+
+      if (items[i].icon)
+        grub_video_blit_bitmap (items[i].icon, GRUB_VIDEO_BLIT_BLEND,
+                                item_left,
+                                item_top + (item_height - icon_height) / 2,
+                                0, 0, icon_width, icon_height);
+
+      grub_video_draw_string (items[i].name, item_font, item_color,
+                              item_left + icon_width + icon_text_space,
+                              (item_top + (item_height - (ascent + descent))
+                               / 2 + ascent));
+
+      item_top += item_height + item_vspace;
+    }
+}
+
+static void
+draw_title (void)
+{
+  /* Center the title. */
+  int title_width = grub_font_get_string_width (title_font, title_text);
+  int x = (screen.width - title_width) / 2;
+  int y = 40 + grub_font_get_ascent (title_font);
+  grub_video_draw_string (title_text, title_font, title_color, x, y);
+}
+
+static void
+draw_status (void)
+{
+  int descent = grub_font_get_descent (status_font);
+  int ascent = grub_font_get_ascent (status_font);
+  int vpad = 5;
+  int textheight = descent + ascent + 1;
+  int h = 2 * vpad + 2 * textheight;
+
+  grub_video_fill_rect (status_bg_color, 0, screen.height - h,
+                        screen.width, screen.height - 1);
+
+  int texty = screen.height - h + vpad + ascent;
+  grub_video_draw_string ("Select an item with the arrow keys and "
+                          "press Enter to boot.",
+                          status_font, status_color, 30, texty);
+  texty += textheight;
+  grub_video_draw_string ("Press:  'c' for command line; 't' to switch to "
+                          "non-graphical menu.",
+                          status_font, status_color, 30, texty);
+}
+
+static void
+draw_menu_screen (void)
+{
+  grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
+  draw_background ();
+  draw_menu ();
+  draw_title ();
+  draw_status ();
+}
+
+static grub_err_t
+set_graphics_mode (void)
+{
+  const char *doublebuf_str = grub_env_get ("doublebuffering");
+  int doublebuf_flags =
+    (doublebuf_str && doublebuf_str[0] == 'n')
+    ? 0
+    : GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED;
+
+  const char *modestr = grub_env_get ("gfxmode");
+  if (grub_video_setup_preferred_mode (modestr, doublebuf_flags, 640, 480)
+      != GRUB_ERR_NONE)
+    return grub_errno;
+
+  return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+set_text_mode (void)
+{
+  return grub_video_restore ();
+}
+
+static int term_target_width;
+static int term_target_height;
+static struct grub_video_render_target *term_target;
+static grub_gfxmenu_box_t term_box;
+static grub_term_t term_original;
+
+static void
+repaint_terminal (int x __attribute ((unused)),
+                  int y __attribute ((unused)),
+                  int width __attribute ((unused)),
+                  int height __attribute ((unused)))
+{
+  draw_menu_screen ();
+  grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
+
+  int termx = screen.x + screen.width / 10;
+  int termy = screen.y + screen.height / 10;
+
+  if (term_box)
+    term_box->draw (term_box, 
+                    termx - term_box->get_left_pad (term_box),
+                    termy - term_box->get_top_pad (term_box));
+
+  grub_video_blit_render_target (term_target, GRUB_VIDEO_BLIT_REPLACE,
+                                 termx, termy,
+                                 0, 0, term_target_width, term_target_height);
+  grub_video_swap_buffers ();
+}
+
+static void init_terminal (void)
+{
+  term_original = grub_term_get_current ();
+
+  term_target_width = screen.width * 8 / 10;
+  term_target_height = screen.height * 8 / 10;
+
+  grub_video_create_render_target (&term_target,
+                                   term_target_width, 
+                                   term_target_height,
+                                   GRUB_VIDEO_MODE_TYPE_RGB
+                                   | GRUB_VIDEO_MODE_TYPE_ALPHA);
+  if (grub_errno != GRUB_ERR_NONE)
+    return;
+
+  grub_gfxterm_init_window (term_target, 0, 0,
+                            term_target_width, term_target_height,
+                            "Fixed 10", 16);
+  if (grub_errno != GRUB_ERR_NONE)
+    return;
+
+  term_box = grub_gfxmenu_create_box ("/boot/images/select_blue_", ".tga");
+  term_box->set_content_size (term_box, term_target_width, term_target_height);
+
+  grub_gfxterm_set_repaint_callback (repaint_terminal);
+  grub_term_set_current (grub_gfxterm_get_term ());
+}
+
+static void destroy_terminal (void)
+{
+  term_box->destroy (term_box);
+  term_box = 0;
+  grub_gfxterm_destroy_window ();
+  grub_gfxterm_set_repaint_callback (0);
+  grub_video_delete_render_target (term_target);
+  grub_term_set_current (term_original);
+}
+
+static void
+run_terminal (void)
+{
+  grub_cmdline_run (1);
+}
+
+static grub_err_t
+show_menu (grub_menu_t menu, int nested __attribute__ ((unused)))
+{
+  set_graphics_mode ();
+  grub_error_push ();
+  grub_video_get_viewport ((unsigned *) &screen.x,
+                           (unsigned *) &screen.y,
+                           (unsigned *) &screen.width,
+                           (unsigned *) &screen.height);
+
+  /* Clear the screen; there may be garbage left over in video memory, and
+     loading the menu style (particularly the background) can take a while. */
+  grub_video_fill_rect (grub_video_map_rgb (100, 100, 100),
+                        screen.x, screen.y, screen.width, screen.height);
+  grub_video_swap_buffers ();
+
+  init_terminal ();
+
+  if (menu_init (menu) != GRUB_ERR_NONE)
+    goto menufail;
+
+  if (style_init () != GRUB_ERR_NONE)
+    goto stylefail;
+
+  selected_item_index = 0;
+
+  int done = 0;
+  while (!done && !grub_menu_viewer_should_return ())
+    {
+      draw_menu_screen ();
+      grub_video_swap_buffers ();
+
+      int c = GRUB_TERM_ASCII_CHAR (grub_getkey ());
+      if (c == 'j' || c == 14)
+        {
+          selected_item_index++;
+          if (selected_item_index >= num_items)
+            selected_item_index = 0;
+        }
+      else if (c == 'k' || c == 16)
+        {
+          selected_item_index--;
+          if (selected_item_index < 0)
+            selected_item_index = num_items - 1;
+        }
+      else if (c == '\r' || c == '\n' || c == 6)
+        {
+          if (selected_item_index >=0 && selected_item_index < num_items)
+            {
+              menu_item_t *item = &items[selected_item_index];
+              if (item->entry != 0)
+                {
+                  /* Currently we switch back to text mode by restoring
+                     the original terminal before executing the menu entry.
+                     It is hard to make it work when executing a menu entry
+                     that switches video modes -- it using gfxterm in a
+                     window, the repaint callback seems to crash GRUB. */
+                  /* TODO: Determine if this works when 'gfxterm' was set as
+                     the current terminal before invoking the gfxmenu. */
+                  destroy_terminal ();
+
+                  grub_menu_execute_entry (item->entry);
+                  if (grub_errno != GRUB_ERR_NONE)
+                    grub_wait_after_message ();
+
+                  if (set_graphics_mode () != GRUB_ERR_NONE)
+                    done = 1;
+                  else
+                    init_terminal ();
+                }
+            }
+        }
+      else if (c == 'c')
+        {
+          run_terminal ();
+          if (grub_errno != GRUB_ERR_NONE)
+            goto fail;
+        }
+      else if (c == 't')
+        {
+          /* The write hook for 'menuviewer' will cause 
+           * grub_menu_viewer_should_return to return nonzero. */
+          grub_env_set ("menuviewer", "terminal");
+        }
+      else if (nested && c == GRUB_TERM_ESC)
+        {
+          done = 1;
+        }
+    }
+
+fail:
+  style_destroy ();
+stylefail:
+  menu_destroy ();
+menufail:
+  set_text_mode ();
+  destroy_terminal ();
+
+  grub_print_error ();
+
+  return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+grub_cmd_gfxmenu (struct grub_arg_list *state __attribute__ ((unused)),
+                  int argc __attribute__ ((unused)),
+                  char **args __attribute__ ((unused)))
+{
+  grub_menu_t menu = grub_env_get_data_slot ("menu");
+  if (!menu)
+    return grub_error (GRUB_ERR_MENU, "No menu context");
+
+  return show_menu (menu, 1);
+}
+
+static struct grub_menu_viewer menu_viewer =
+{
+  .name = "gfxmenu",
+  .show_menu = show_menu
+};
+
+GRUB_MOD_INIT (gfxmenu)
+{
+  (void) mod;                   /* To stop warning. */
+  grub_menu_viewer_register (&menu_viewer);
+  grub_register_command ("gfxmenu",
+                         grub_cmd_gfxmenu, GRUB_COMMAND_FLAG_BOTH,
+                         "gfxmenu", "Show graphical menu interface", 0);
+}
+
+GRUB_MOD_FINI (gfxmenu)
+{
+  grub_unregister_command ("gfxmenu");
+}

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

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

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

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

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

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

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

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

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

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

=== added file 'include/grub/menu_viewer.h'
--- include/grub/menu_viewer.h	1970-01-01 00:00:00 +0000
+++ include/grub/menu_viewer.h	2008-07-19 19:07:47 +0000
@@ -0,0 +1,48 @@
+/* menu_viewer.h - Interface to menu viewer implementations. */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2002,2003,2005,2007,2008  Free Software Foundation, Inc.
+ *
+ *  GRUB is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GRUB_MENU_VIEWER_HEADER
+#define GRUB_MENU_VIEWER_HEADER 1
+
+#include <grub/err.h>
+#include <grub/symbol.h>
+#include <grub/types.h>
+#include <grub/menu.h>
+
+struct grub_menu_viewer
+{
+  /* The menu viewer name.  */
+  const char *name;
+
+  grub_err_t (*show_menu) (grub_menu_t menu, int nested);
+
+  struct grub_menu_viewer *next;
+};
+typedef struct grub_menu_viewer *grub_menu_viewer_t;
+
+void EXPORT_FUNC(grub_menu_viewer_init) (void);
+
+void EXPORT_FUNC(grub_menu_viewer_register) (grub_menu_viewer_t viewer);
+
+grub_err_t EXPORT_FUNC(grub_menu_viewer_show_menu) (grub_menu_t menu, int nested);
+
+/* Return nonzero iff the menu viewer should clean up and return ASAP. */
+int EXPORT_FUNC(grub_menu_viewer_should_return) (void);
+
+#endif /* GRUB_MENU_VIEWER_HEADER */

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

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

=== modified file 'include/grub/video.h'
--- include/grub/video.h	2008-01-01 12:02:07 +0000
+++ include/grub/video.h	2008-07-19 19:31:46 +0000
@@ -21,6 +21,7 @@
 
 #include <grub/err.h>
 #include <grub/types.h>
+#include <grub/font.h>
 
 /* Video color in hardware dependent format.  Users should not assume any
    specific coding format.  */
@@ -31,40 +32,41 @@
 struct grub_video_render_target;
 
 /* Forward declarations for used data structures.  */
-struct grub_font_glyph;
 struct grub_video_bitmap;
 
 /* Defines used to describe video mode or rendering target.  */
-#define GRUB_VIDEO_MODE_TYPE_ALPHA		0x00000008
-#define GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED	0x00000004
+#define GRUB_VIDEO_MODE_TYPE_ALPHA		0x00000020
+#define GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED	0x00000010
+#define GRUB_VIDEO_MODE_TYPE_1BIT_BITMAP	0x00000004
 #define GRUB_VIDEO_MODE_TYPE_INDEX_COLOR	0x00000002
 #define GRUB_VIDEO_MODE_TYPE_RGB		0x00000001
 
 /* Defines used to mask flags.  */
-#define GRUB_VIDEO_MODE_TYPE_COLOR_MASK		0x00000003
+#define GRUB_VIDEO_MODE_TYPE_COLOR_MASK		0x0000000F
 
 /* Defines used to specify requested bit depth.  */
 #define GRUB_VIDEO_MODE_TYPE_DEPTH_MASK		0x0000ff00
 #define GRUB_VIDEO_MODE_TYPE_DEPTH_POS		8
 
-/* Defined predefined render targets.  */
-#define GRUB_VIDEO_RENDER_TARGET_DISPLAY	((struct grub_video_render_target *) 0)
-#define GRUB_VIDEO_RENDER_TARGET_FRONT_BUFFER	((struct grub_video_render_target *) 0)
-#define GRUB_VIDEO_RENDER_TARGET_BACK_BUFFER	((struct grub_video_render_target *) 1)
+/* Predefined render target: */
+/* The render target that client code should render to. */
+#define GRUB_VIDEO_RENDER_TARGET_DISPLAY \
+  ((struct grub_video_render_target *) 0)
+
 
 /* Defined blitting formats.  */
 enum grub_video_blit_format
   {
-    /* Follow exactly field & mask information.  */
-    GRUB_VIDEO_BLIT_FORMAT_RGBA,
-    /* Make optimization assumption.  */
-    GRUB_VIDEO_BLIT_FORMAT_R8G8B8A8,
-    /* Follow exactly field & mask information.  */
-    GRUB_VIDEO_BLIT_FORMAT_RGB,
-    /* Make optimization assumption.  */
-    GRUB_VIDEO_BLIT_FORMAT_R8G8B8,
+    GRUB_VIDEO_BLIT_FORMAT_RGBA,       /* General: Use fields & masks. */
+    GRUB_VIDEO_BLIT_FORMAT_RGBA_8888,  /* Optimized format. */
+    GRUB_VIDEO_BLIT_FORMAT_BGRA_8888,  /* Optimized format. */
+    GRUB_VIDEO_BLIT_FORMAT_RGB,        /* General: Use fields & masks. */
+    GRUB_VIDEO_BLIT_FORMAT_RGB_888,    /* Optimized format. */
+    GRUB_VIDEO_BLIT_FORMAT_BGR_888,    /* Optimized format. */
     /* When needed, decode color or just use value as is.  */
-    GRUB_VIDEO_BLIT_FORMAT_INDEXCOLOR
+    GRUB_VIDEO_BLIT_FORMAT_INDEXCOLOR,
+    /* Two color bitmap; bits packed: rows are not padded to byte boundary. */
+    GRUB_VIDEO_BLIT_FORMAT_1BIT_PACKED
   };
 
 /* Define blitting operators.  */
@@ -127,6 +129,18 @@
   /* What is location of reserved color bits.  In Index Color mode, 
      this is 0.  */
   unsigned int reserved_field_pos;
+
+  /* For 1-bit bitmaps, the background color.  Used for bits = 0. */
+  grub_uint8_t bg_red;
+  grub_uint8_t bg_green;
+  grub_uint8_t bg_blue;
+  grub_uint8_t bg_alpha;
+
+  /* For 1-bit bitmaps, the foreground color.  Used for bits = 1. */
+  grub_uint8_t fg_red;
+  grub_uint8_t fg_green;
+  grub_uint8_t fg_blue;
+  grub_uint8_t fg_alpha;
 };
 
 struct grub_video_palette_data
@@ -137,6 +151,16 @@
   grub_uint8_t a; /* Reserved bits value (0-255).  */
 };
 
+/* A 2D rectangle type.  */
+struct grub_video_rect
+{
+  int x;
+  int y;
+  int width;
+  int height;
+};
+typedef struct grub_video_rect grub_video_rect_t;
+
 struct grub_video_adapter
 {
   /* The video adapter name.  */
@@ -183,6 +207,10 @@
   grub_err_t (*blit_glyph) (struct grub_font_glyph *glyph,
                             grub_video_color_t color, int x, int y);
 
+  grub_err_t (*draw_string) (const char *str, grub_font_t font, 
+                             grub_video_color_t color, 
+                             int left_x, int baseline_y);
+
   grub_err_t (*blit_bitmap) (struct grub_video_bitmap *bitmap,
                              enum grub_video_blit_operators oper,
                              int x, int y, int offset_x, int offset_y,
@@ -255,6 +283,10 @@
 grub_err_t grub_video_blit_glyph (struct grub_font_glyph *glyph,
                                   grub_video_color_t color, int x, int y);
 
+grub_err_t grub_video_draw_string (const char *str, grub_font_t font, 
+                                   grub_video_color_t color, 
+                                   int left_x, int baseline_y);
+
 grub_err_t grub_video_blit_bitmap (struct grub_video_bitmap *bitmap,
                                    enum grub_video_blit_operators oper,
                                    int x, int y, int offset_x, int offset_y,
@@ -269,6 +301,9 @@
 
 grub_err_t grub_video_scroll (grub_video_color_t color, int dx, int dy);
 
+/* Swap the pages referred to by the front buffer and back buffer render
+ * targets, and the page previously referred to by the back buffer is made
+ * visible on the display. */
 grub_err_t grub_video_swap_buffers (void);
 
 grub_err_t grub_video_create_render_target (struct grub_video_render_target **result,
@@ -282,4 +317,21 @@
 
 grub_err_t grub_video_get_active_render_target (struct grub_video_render_target **target);
 
+
+/* Defined in video/setmode.c */
+
+/* Set the video mode based on the preferred modes specified in MODE_LIST in
+   the form: <width>x<height>[x<depth>][;...]
+
+   For example:  640x480;800x600x8;400x300x32
+
+   If MODE_LIST is null, or no modes in it are usable, then DEFAULT_WIDTH and
+   DEFAULT_HEIGHT are used to set the mode.  The MODE_FLAGS argument determines
+   the video mode flags such as double buffering that are used.  */
+
+grub_err_t
+grub_video_setup_preferred_mode (const char *mode_list, int mode_flags,
+                                 int default_width, int default_height);
+
+
 #endif /* ! GRUB_VIDEO_HEADER */

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

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

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

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

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

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

=== added file 'kern/menu_viewer.c'
--- kern/menu_viewer.c	1970-01-01 00:00:00 +0000
+++ kern/menu_viewer.c	2008-07-19 19:07:47 +0000
@@ -0,0 +1,99 @@
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2002,2003,2005,2007  Free Software Foundation, Inc.
+ *
+ *  GRUB is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/mm.h>
+#include <grub/misc.h>
+#include <grub/env.h>
+#include <grub/menu_viewer.h>
+#include <grub/menu.h>
+
+/* The list of menu viewers.  */
+static grub_menu_viewer_t menu_viewer_list;
+
+static int should_return;
+static int menu_viewer_changed;
+
+void
+grub_menu_viewer_register (grub_menu_viewer_t viewer)
+{
+  viewer->next = menu_viewer_list;
+  menu_viewer_list = viewer;
+}
+
+static grub_menu_viewer_t get_current_menu_viewer (void)
+{
+  const char *selected_name = grub_env_get ("menuviewer");
+
+  /* If none selected, pick the last registered one. */
+  if (selected_name == 0)
+    return menu_viewer_list;
+
+  grub_menu_viewer_t cur;
+  for (cur = menu_viewer_list; cur; cur = cur->next)
+    {
+      if (grub_strcmp (cur->name, selected_name) == 0)
+        return cur;
+    }
+
+  /* Fall back to the first entry (or null).  */
+  return menu_viewer_list;
+}
+
+grub_err_t
+grub_menu_viewer_show_menu (grub_menu_t menu, int nested)
+{
+  grub_err_t err;
+  int repeat = 0;
+  do
+    {
+      repeat = 0;
+      menu_viewer_changed = 0;
+      grub_menu_viewer_t cur = get_current_menu_viewer ();
+      if (!cur) 
+        return grub_error (GRUB_ERR_BAD_ARGUMENT, "No menu viewer available.");
+
+      should_return = 0;
+      err = cur->show_menu (menu, nested);
+      if (menu_viewer_changed)
+        repeat = 1;
+    }
+  while (repeat);
+  return err;
+}
+
+int
+grub_menu_viewer_should_return (void)
+{
+  return should_return;
+}
+
+static char *
+menuviewer_write_hook (struct grub_env_var *var __attribute__ ((unused)),
+                       const char *val)
+{
+  menu_viewer_changed = 1;
+  should_return = 1;
+  return grub_strdup (val);
+}
+
+void
+grub_menu_viewer_init (void)
+{
+  grub_register_variable_hook ("menuviewer", 0, menuviewer_write_hook);
+}
+

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

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

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

=== modified file 'normal/menu.c'
--- normal/menu.c	2008-02-09 11:00:19 +0000
+++ normal/menu.c	2008-07-19 19:24:29 +0000
@@ -24,6 +24,7 @@
 #include <grub/machine/time.h>
 #include <grub/env.h>
 #include <grub/script.h>
+#include <grub/menu_viewer.h>
 
 static grub_uint8_t grub_color_menu_normal;
 static grub_uint8_t grub_color_menu_highlight;
@@ -364,7 +365,7 @@
   if (timeout > 0)
     print_timeout (timeout, offset, 0);
 
-  while (1)
+  while (!grub_menu_viewer_should_return ())
     {
       int c;
       timeout = get_timeout ();
@@ -468,6 +469,10 @@
 		}
 	      goto refresh;
 	      
+	    case 't':
+	      grub_env_set ("menuviewer", "protomenu");
+	      goto refresh;
+
 	    default:
 	      break;
 	    }
@@ -476,13 +481,14 @@
 	}
     }
 
-  /* Never reach here.  */
+  /* Exit menu without activating an item.  This occurs if the user presses
+   * 't', switching to the graphical menu viewer.  */
   return -1;
 }
 
 /* Run a menu entry.  */
-static void
-run_menu_entry (grub_menu_entry_t entry)
+void
+grub_menu_execute_entry(grub_menu_entry_t entry)
 {
   grub_script_execute (entry->commands);
   
@@ -491,8 +497,8 @@
     grub_command_execute ("boot", 0);
 }
 
-void
-grub_menu_run (grub_menu_t menu, int nested)
+static grub_err_t
+show_menu (grub_menu_t menu, int nested)
 {
   while (1)
     {
@@ -513,7 +519,7 @@
 
       grub_printf ("  Booting \'%s\'\n\n", e->title);
   
-      run_menu_entry (e);
+      grub_menu_execute_entry (e);
 
       /* Deal with a fallback entry.  */
       /* FIXME: Multiple fallback entries like GRUB Legacy.  */
@@ -526,7 +532,7 @@
 	  e = get_entry (menu, fallback_entry);
 	  grub_env_unset ("fallback");
 	  grub_printf ("\n  Falling back to \'%s\'\n\n", e->title);
-	  run_menu_entry (e);
+	  grub_menu_execute_entry (e);
 	}
 
       if (grub_errno != GRUB_ERR_NONE)
@@ -537,4 +543,12 @@
 	  grub_wait_after_message ();
 	}
     }
+
+  return GRUB_ERR_NONE;
 }
+
+struct grub_menu_viewer grub_normal_terminal_menu_viewer =
+{
+  .name = "terminal",
+  .show_menu = show_menu
+};

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

=== added file 'video/setmode.c'
--- video/setmode.c	1970-01-01 00:00:00 +0000
+++ video/setmode.c	2008-07-19 19:31:46 +0000
@@ -0,0 +1,249 @@
+/* video/setmode.c - Smart video mode selection based on preferences. */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2006,2007,2008  Free Software Foundation, Inc.
+ *
+ *  GRUB is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/video.h>
+#include <grub/types.h>
+#include <grub/mm.h>
+#include <grub/misc.h>
+
+/* Set the video mode based on the preferred modes specified in MODE_LIST in
+   the form: <width>x<height>[x<depth>][;...]
+
+   For example:  640x480;800x600x8;400x300x32
+
+   If MODE_LIST is null, or no modes in it are usable, then DEFAULT_WIDTH and
+   DEFAULT_HEIGHT are used to set the mode.  The MODE_FLAGS argument determines
+   the video mode flags such as double buffering that are used.  */
+
+grub_err_t
+grub_video_setup_preferred_mode (const char *mode_list, int mode_flags,
+                                 int default_width, int default_height)
+{
+  int mode_found = 0;
+
+  if (mode_list != NULL)
+    {
+      /* Take copy of mode_list as we don't want tat.  */
+      char *const modes_copy = grub_strdup (mode_list);
+      if (modes_copy == NULL)
+        return grub_errno;
+
+      /* Initialize next mode.  */
+      char *next_mode = modes_copy;
+
+      /* Loop until all modes has been tested out.  */
+      while ((next_mode != NULL) && !mode_found)
+        {
+          /* Use last next_mode as current mode.  */
+          char *tmp = next_mode;
+
+          int width = -1;
+          int height = -1;
+          int depth = -1;
+
+          /* Save position of next mode and separate modes.  */
+          next_mode = grub_strchr(next_mode, ';');
+          if (next_mode)
+            {
+              *next_mode = 0;
+              next_mode++;
+            }
+
+          /* Skip whitespace.  */
+          while (grub_isspace (*tmp))
+            tmp++;
+
+          /* Initialize token holders.  */
+          char *current_mode = tmp;
+          char *param = tmp;
+          char *value = NULL;
+
+          /* Parse <width>x<height>[x<depth>]*/
+
+          /* Find width value.  */
+          value = param;
+          param = grub_strchr(param, 'x');
+          if (param == NULL)
+            {
+              grub_err_t rc;
+
+              /* First setup error message.  */
+              rc = grub_error (GRUB_ERR_BAD_ARGUMENT, 
+                               "Invalid mode: %s\n",
+                               current_mode);
+
+              /* Free memory before returning.  */
+              grub_free (modes_copy);
+
+              return rc;
+            }
+
+          *param = 0;
+          param++;
+
+          width = grub_strtoul (value, 0, 0);
+          if (grub_errno != GRUB_ERR_NONE)
+            {
+              grub_err_t rc;
+
+              /* First setup error message.  */
+              rc = grub_error (GRUB_ERR_BAD_ARGUMENT, 
+                               "Invalid mode: %s\n",
+                               current_mode);
+
+              /* Free memory before returning.  */
+              grub_free (modes_copy);
+
+              return rc;
+            }
+
+          /* Find height value.  */
+          value = param;
+          param = grub_strchr(param, 'x');
+          if (param == NULL)
+            {
+              height = grub_strtoul (value, 0, 0);
+              if (grub_errno != GRUB_ERR_NONE)
+                {
+                  grub_err_t rc;
+
+                  /* First setup error message.  */
+                  rc = grub_error (GRUB_ERR_BAD_ARGUMENT, 
+                                   "Invalid mode: %s\n",
+                                   current_mode);
+
+                  /* Free memory before returning.  */
+                  grub_free (modes_copy);
+
+                  return rc;
+                }
+            }
+          else
+            {
+              /* We have optional color depth value.  */
+              *param = 0;
+              param++;
+
+              height = grub_strtoul (value, 0, 0);
+              if (grub_errno != GRUB_ERR_NONE)
+                {
+                  grub_err_t rc;
+
+                  /* First setup error message.  */
+                  rc = grub_error (GRUB_ERR_BAD_ARGUMENT, 
+                                   "Invalid mode: %s\n",
+                                   current_mode);
+
+                  /* Free memory before returning.  */
+                  grub_free (modes_copy);
+
+                  return rc;
+                }
+
+              /* Convert color depth value.  */
+              value = param;
+              depth = grub_strtoul (value, 0, 0);
+              if (grub_errno != GRUB_ERR_NONE)
+                {
+                  grub_err_t rc;
+
+                  /* First setup error message.  */
+                  rc = grub_error (GRUB_ERR_BAD_ARGUMENT, 
+                                   "Invalid mode: %s\n",
+                                   current_mode);
+
+                  /* Free memory before returning.  */
+                  grub_free (modes_copy);
+
+                  return rc;
+                }
+            }
+
+          /* Try out video mode.  */
+
+          int flags = mode_flags;
+          /* If we have <= 8 bits, assume it is an indexed color mode.  */
+          if ((depth <= 8) && (depth != -1))
+            flags |= GRUB_VIDEO_MODE_TYPE_INDEX_COLOR;
+
+          /* We have > 8 bits; assume that it is RGB color mode.  */
+          if (depth > 8)
+            flags |= GRUB_VIDEO_MODE_TYPE_RGB;
+
+          /* If user requested specific depth, pass the request to driver.  */
+          if (depth != -1)
+            flags |= (depth << GRUB_VIDEO_MODE_TYPE_DEPTH_POS)
+              & GRUB_VIDEO_MODE_TYPE_DEPTH_MASK;
+
+          /* Try to initialize requested mode.  Ignore any errors.  */
+          grub_error_push ();
+          if (grub_video_setup (width, height, flags) != GRUB_ERR_NONE)
+            {
+              grub_error_pop ();
+              continue;
+            }
+
+          /* Figure out what mode we ended up.  */
+          struct grub_video_mode_info mode_info;
+          if (grub_video_get_info (&mode_info) != GRUB_ERR_NONE)
+            {
+              /* Couldn't get video mode info, restore old mode
+                 and continue to next one.  */
+              grub_error_pop ();
+
+              grub_video_restore ();
+              continue;
+            }
+
+          /* Restore state of error stack.  */
+          grub_error_pop ();
+
+          /* Mode found!  Exit loop.  */
+          mode_found = 1;
+        }
+
+      /* Free memory.  */
+      grub_free (modes_copy);
+    }
+
+  if (!mode_found)
+    {
+      /* No gfxmode variable set, or no listed mode was supported.
+         Use the caller-specified defaults.  */
+      int flags = mode_flags | GRUB_VIDEO_MODE_TYPE_RGB;
+
+      /* Initialize user requested mode.  */
+      if (grub_video_setup (default_width, default_height, flags) 
+          != GRUB_ERR_NONE)
+        return grub_errno;
+
+      /* Figure out what mode we ended up.  */
+      struct grub_video_mode_info mode_info;
+      if (grub_video_get_info (&mode_info) != GRUB_ERR_NONE)
+        grub_video_restore ();
+      else
+        mode_found = 1;
+    }
+
+  if (!mode_found)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT,
+                       "No suitable mode found.");
+
+  return (grub_errno = GRUB_ERR_NONE);
+}

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

# Begin bundle
IyBCYXphYXIgcmV2aXNpb24gYnVuZGxlIHY0CiMKQlpoOTFBWSZTWeSnXxoBp5H/gH//////////
//////////9hvD4Pe2ABvNG0y2ra8DuheUjvdOpoAfTqIUuzQemVRCQo727vbl99ywPuD1t3dXN2
goB3fT17zK7PoBdqk1u1rfevdrbfPslzL7svtvZZrTgWbsCcOABRb7c488dC7G9l2HqnWIKdmFPb
Apy9L05XoU3d6eezNTbb7t30b3vTybeuaBoZABotgBpa2dy3B7Z7bbXp69163Y7Q4Wk4so6TtvN1
5KUFLeDn00Dnt0fXfLWVgH0U+3uds+9eGp74z5szmznowQlu4IkwKEJ9tS4m+tbvsuz1ttlPXXSv
eAPQOZq0Pe0lB61l073KKgn2Dxvt976XWYCj6Y606fT3QslUvHr7Y0227dc77z6871pdOvZvPmzq
sQufeeBdfRvPnxVVKdjCvvr19eSmjRVFF63z7AUeO8p9SvveW2jz7y9a6N8DuC82ed3XbbZpuw2m
zsApm4PDNPvfHofSj6lXp7s0UszmwAbcPDO55dl7O77m6zbbapND7sCht30ge9rHPZRlkqssa1q+
871AVm+8gz22fbdZ1tsApFtgAPr3oL3m8XbbfdjqWtrbbbAFHt96Anjp19PR6ZNVSQNA80Hd2yQt
sqbYtedp9sCRvfAXr1saaMbO2XW2wAPW5w7jX1521VVdm+z1LpruxQ48j7aAEQFACI9dd0OqKAF2
VLQ6a07O5Rp07YdBpQchoKU0BoGQppo6FV0BJ1dhnYPXXQ71h63ce8GturKckldWTVMwzbhdxiPX
NAADQsjt3NlboenTjVSm7ZHMquprA9XutOmgAzd1a7Ztse9NQFAbd7e6ugKAVYsHd3NtuoklQAAm
gYQgA6HNAADnrFzBvWJicgaOh7p0U+g0XbSujvrFHr1968+IEBegEd52xjK9iVbi8H0fPfap9Ecl
BJS9tOnDqm6HX3333vogQUoR7KuAAFKAGC7JeztnFAztzWbG1s2VAjtgAVIASRJAEoJzGWhqquRY
6AA6cfTHbWaiIJESgKAXtbaY6NLodzWx3ZoMgaNNjuauxpEl3YU5CkABJUoVtooqUpQaNQABKtgY
6x1bRABVAkalmsKaZFECFaANaNKtgc4AM1qm2JddAODtpSgEptWvr29vp92d7uaD5vu9Xhxy0Vez
AAFONolRBtCQClV6Gh1gAX1vefc87NmihSPbkkdOwbQO4BuhS2XYdNDl0oA0A0UZBdV24c206p3t
7sdAOs4dLvVKAGIEoQAgCaAJoRomCNBGSmybUMKepiepNGmMkZpqaZqYgZMahoGmgBIiTEmQp4pN
6DVMTQ0Gg0AaAAAABoAAADQEiCCTQIjQhMRkaamIm1PKam2pPKP1T1PSeSaBpoyZGmg9QbSD1Bo0
0BJpIiCEABTBNNNCMp+poj0T1T2JT2mmpT09JjKQz1D1GJ5TTTaNTU9pPSQBEkQQBAACAAIMgAE0
TMkyZNNMKPUwjEYp4SYKbJ6kCpIgAIQRBGmRMEAm1JgpvTTVPaFD0Qepo0aGRoeobSBoADvaTV8i
j7JdYCQgQhFD/iWtaEFBvATCLZAslKoif6HsShQ91ikYgPwSEVAQ9pFSpICIoUEEETNDICJ+H89g
WyRQS4H5Lfqn6zAXCIBICSKBtfkJD6Pk+1t9FH22x/+f0jw20yQkfsHQOkJ7JNIKu4bQDEJ4PDO/
BRxP6v7Q/Vej/b3X9JUn9R/8f0qrMyf1L/Of+xn4z/9fZxs+EN/Yo9Z5H3YyCGoraf++e3CdRyG4
28RzLt6znRD+Yutv9iMJCRn7bbSmn/K14d7pPWlgxF0q8GYKqMVDytFTbC6prLFimCH/DesnOilw
/z4dZhURJ2SBVYio/zWsIKQivqGoLOyQrBSdRrBEYqdmQqIrIggqiKrBBFkVYmqBVGCwFAQtqIwU
VUSofIwhh/uPMa3qbnOUMsbIiIqAjtzs1GGk2DpKJgmvppoQ0wH6izg9F0MDbCVOfC4wU4Tq6SXW
8BSibtCZGVIrFBOBEYFZxaIqHZgW2cYU+pnOVEikYzpmGQUFCIhUkUrPMnCYiKJoSsEEoHkCAsXv
A1IfoMMziQmGa87P6P8f+b8mdU1t8ONOZvsyCoqBSy+F7KOD9BtfdR6O3oe89WX4M/0ferKKpViY
qIh6X+y8VNlXg/Bmq8h8DzNUcDrZdXOSzRnp5+B8vDL5Osq6qqRxK1H6XM1ZVagt5fCk63b9x4eX
78uK8kXaxdBgtwcc28RbRTWbV6umaJYY1BpoNRjJo0Uouy1kwsAuRDyYiFliFN2MqpczFjBkWzq9
tc5htqbAt1a1nLhaiBmp6d0B1aQjvP0t+vpFDfwsmV6PCne/ho1taJw5nDc4RLSiPwtXXUw1rNb2
Uzi40ypEODCuJf1YjI6f1PXR1/iDiINTmv33k1pYdSRMP9biba/IVR+KI5ognUk+ZqRsbBKlKLTB
ZOfhV3ZYyQwKLbsxR1dh6l/SILgPtCILsGUnjO+n4PLMQrNbka8vxqLGmoV+O5B1UGpp10m5itVX
54LPz0RinE56clFMOeaGc5mJ6h2IYmt3oj77vl4bbiJx9F9+dYc8OnZUkCZXDK6WkOoKFqpqPzP3
MIarcN1608tZlHysKzLKnFqf0UvW+h9fld20F6VRaiXhlIeZUohKJkTLi1otRWShtRNKkrZMxhCf
+KnB8KW5vEEFJj1ETU6pDZE/GCTN4Kccayj+NA2JpdZgedrVZeYGt6Mu2nPiZe3DXj+9Bw994l6i
Flm4IFplsDNs5WVV1e3Gcym0HSZGFmtFwRmLQYhRfPevr8vUanm3rhLqeuhPIALzJT6YiyXX/gv/
JP+mLoq9XP+b3pissSh+b7rj7CQ88yLiyqrKfYdR7tx/zP+Y7W+PB68Xve7bV3PXVzvyDXLRt7Cu
BpKpobx9j4nBzs6PwaSvi7qN/aZjSorXzm5XCsL/aNzPwevIZ5mNwF4fTJV/tJCWXkq//g3gdGW9
hcRiXE1HGhiYweu4dyIeBjyWraN0v0dE/J58SK9wasL+mT7hWJv0k/TBDEpv2j5CHf3PmOvqfP6D
mURRRYsi+xmoK+Rhw9T5OnzsPqY/Dw7kL9n2KGcu3zEq5jGGNU3VXmB4NX94/O/PRfxMGBRvyq/i
a6PZ0ufDos+DDQ8O7y7P4UWGJ8UBiwXUYcDNbkObOM6i0MAmTmXEUIFLFsuRFvZnDNPDXZa+IE4g
BCICRgVB2QBgqv1oV1SxUc7GE34t2gtpebYSLFj6Nmvs90vGGm/n4d+Xu+9c0+Xt5ufk4kFMmcnm
3+E93E4pNLsnPDI4/XWfNrf+XONEKhSM1Eoakh1YG5zzfCtila0e2aTNK2l9Y0tnuVL4UFBtiHZF
MuuhvJDKLvqlSYFIHAzWrpgxibYCY50oXjuG03ZbDTnRsiaN9UZPgkP2GEh4IfKgSuaqqgwMywxB
OflYRSKjCAsikgvg1oIIhVQRiJFBL9owAxBYTAQWiozKwjKqhRUHCoFfyfDbur3X/JzaTJKbqQ8c
vl2zL41kp8Bjv+trTPLZrEnlxcIqYEfRR8fha64r8HeRQ6jMNZHIx1tpnRa8kqot2JgvZpwJloDl
TnBka1cIpIctFNfRRhXQ2kShdXDeCy6VGrklWNYlQQVCkLmAkWGYzbrWxyiFZvjWsgzXA8ZpS5Fw
RJsUkS12zFutGZzTuUwkmII+stGawTgczJxhXLlXTrKVUDDLrUnEwoyCeqmsDeVIgqXyDvLNGket
vU0Zy4Ja6LroVHi6zvnIUUUFFFERPT14emOnXRu1DGxeaxZRkr3+d8PcBA+c5hyVvKU6i/acw0xo
2ixQRgVluzBHTnnLtMSoFdpvxoeo44uT0uDOT3XEDjX14TRha+xqFSos2h6H4JwejjxTkPKTwEnJ
p0WtX45fjdAjhSiFM+4HQPNrYcKh62QzxoaeFdGUvhQuIlDvPRuybhmqKYlRtFjPbS8XUQdvoem7
vu1OSXey6Y/Yy+TVERFRViiKMUZpRB7nJomvQ/FJ2zZwyIwiihBEkn1eJwApI+q1IjIjOEmITGAC
hUMZgKisn8JfNn4Kd0nuw/Z+XTdPEU80LUeVrWV5tdDEondnnyUYppPtSEw1jWaUoUJkqUPQTAwz
jNwXFFQ0aBL0GgsGkNNPBBBR7zEhIqU/QaCESshhQaihyWk2GHOXXx3p+DwPv454GFdMiZmApILI
RTlq3iVCKXTKkpLrAlJIO3PYPHj3+/1TNpbuvUS++7zxDTkUC9ocJjJZI5BhB7ot+KmTDJZEYVQU
NjiFIenBALVYxGX163hrMyFQbZREnBxZtmpvZTVqdNmDgAxtLpnSILIIwnjuTlIKorGCKogooCor
C/dLo/vj09PB1uSwQMMOJNMBZQwOQutE2TFKVGBw0ypcwimQ7C4ooixpRbeC4DPoD2s33pUc850J
1OaWI2hT5XDO/hTWPtazBYg7BKUKCzypMh5kxL89MHLQDTWmpO3Y+gaS7I+fql1b9N4Iri1KtsmP
WUcyUZAU5NPCQM+e3B2VhI8lf8l4HeGRrNISPRIVBg5KfU57EoeTBZvKs0qBUK+fGhpmE2XYjhR6
pY8Knu8vFzrxJM6MD834fo/X0f9fCCWkeXwcvlkxzNSExGGE2RYm5gXJ7nbA5Iw5FQWRBhJ+IAso
QX4MPp9FJoaMneJ4s1Ot8TZYGhkZNtD2tZxBIX3tD3MuvAnpNBoEgzLCiiv9VLDEKCft+fAwhE+w
LJImvn/t42AfFDxfF6vaQYVH10ohPjfI+zrc0vh1z1offf9h104XwLkC63ra47oR+ji1HETh606d
/EqKxMcVFD6Ex2zbD8AgXXliew/Brpv4pyrX6X60maLHdhmeWB6Xwdp91OHjyoI99WYMnVKnmZt6
GdMj25J249tbqhNsbWY/JFVSIGSAfeJC0XKbpnt7/b/TsV9My2l89UbVSRalr3+uMccNKjz9ywm9
VEUnseryJaUqVmmCMTVi18RHE+ZDWcOGZPbVUFDIYZakbQxEtVBbUGJ07ZjB0jUvZ6n2Tm+/N0No
qUjJUXHFRplvMyzMQwsq6fNqGgplTDsgcwU3M1ipmR2qT5cO6jHtao4sNMKDQpBgZwavEFcURRRc
FQ5obqIrdVY1zjU0ZQlfHtepopyMKgIx5p0em742xiTOIURk1u7R1cyju1VVJpsOaLBy420GbSve
msKwTReLrRYw0Iip+J2zLT3ocJoQRg9Mu5HEXEkApKGHMTR2UygsqsWfMNFPo8GVMQS+YuSai7Vh
lkOnHWVDCSNE43alF15qGAap4XI4XeVSY7uRXDsmBm8hTCwNJ2TTMgnfirBZNurq7tzi72Jg9Msx
Ct1c1ec3kWR07yQaWQ0R3595oRKh4caOEa7wNqNuMxGOWq6tTbhuX8pDkYeWqyiCFiYmFqKUxNE0
UwkYsVJSlhhqk8ybJsiUPAjKDOEXh8+VOLQ4vPE1hXfK6wlbptwrlW9/VfX47zz84BgyUEWRTnu3
T2b7++OjXmQsbDYuN4XMqDDMyFzK22DwIh0wV5QGqM6SlJ0R+kkn0IoixGZH7xH19DfukZUCiDH5
UyGJZe9kwDELr5DW/r/TqFhHyk+weFpMwY4kzS5+zRrTMjJgkkvvzMpjr0ougkiJ6T6DYBR4CEIQ
hCEGEJEQ8f6y8/T/Ev/35nr8fM7HmMqjQd+IkXPaD1h5+oQOOILYjpWEbZcfpk00qGujDuxKQr60
ZEiGrBixrGIq+VKQxIIMLjcYtZRBVg7D0enJQScfNs7MDoizZDh/uM+9geuOJ4NwpTzcClVH2mKC
V3gMyggEaQKOkBwy0UxlDjir5m6VVzSfxtrE1gTDmrQ8N8dfvEYQJL7a3bcWhsQqlqliIChOLYQ0
whpxlKWFSlzJKowwzHDFiMFK8JjNLYSy0qgLUlFIhURDCpENkQqSSNmvG+SuN02mZ49K6zE+DTSe
8yrJIW5ucOM7LHZeF3KcMKYWJ2INTMqCrwtELJNVIYCnLXAYq71BiapbC7NHBSZFwk5ItxgeqLkS
wsglXkYe0CDiTW46NsMOFSBJncezyQKt0VkV8hRTVCvnKLU2ii8w0GASLeYbVkuHwNFU8/Tx7Vyq
qOFSxHCMKASbOSppQJZSUz1qmqCvi3bjHCBSG3wUUNd3o6+XOG/QgibRgAayiw8mSBjDEPW7Q0w9
D6mQ2hDEmkhfXVgcMFIbcQOWG2Q2woyGkJ6kxA5icsOeaHV6IBiEizSs3zQR6uqUNPDM1QgpKnCG
b6YbZNatEiJyhOGGJxi2sK20jlIoIu4C2W1DR7twkaGCbEa3CCGLOalG1yhtA1xSHDAhygHLONWp
Dlgc40uiAJeIGOdLaJaAtY0i1AxiaYogaSy0hWMFhidVKEKcG8znWzVuimz2McS+I4qxSEMNDWKF
BMkODbqdo3Yd6nV0di8J1O3Uw7+kbKH10CASyYJPffdN4kuMGHeO8ZO3gp3qm6KRIsijCQIpFQSM
QUFUhPPKJSCIFtNECEgxWRkEKWufQBiQ3anx7D5ujpzzFa560zyN+zhEYAwjqDBs5Lx1wWKh6xEM
8O6UDj2q6Cca6O86GwYnkjPwcyx3OfTrW7attqeLTLbVvgYXrN2c7zwKnV0clyM0L6/IwBRbArM9
F7NtLswdlxQ1CDGCJi8Zi7KKnM6IkujInDNUDHe5e8mXsyBRIWHtoDORRckKbXIhWexFB2eofBbk
EOxDGhFGbh3C2rU1HKBp1msqmelW2OO8PirApwoYQKUHA9wtGWqhUuXZReQW8ZerJTXCkINtNKPq
lHdYGmQakpKi6JlMoCoQMSScRlEiUmaDqkQ7JLW1rih2bMmoFm6wZMNOUGWXh5uZ0yMcCEF4vEvU
vgejKgtLy65DSzFrdy0W5EzgKi5D5gxizlkoEbWW+zGk41491mQ7igNzY1WhSMG3DBqaYlHgzhpi
GuDMAxkVAp2kUIDGFZmt4nFoS1uDDyHwK+BbcVTRAo7pJRWZmRwHdlZaLCzqqxzLtxFUol51XmnW
3DSYNtc2GyIdRMtW5a7Qt4hTI1lfHxWe1dsK6SwV1WY13ogKH0CTTVVMV1lLMbl6CzkzYijkYVDX
WJTLoV6hoNqlPBuduHacO27MJNruGnKOpOcp7gjISMEJlQFTZttpi74a4tGHj2N+KqqsBQUFRUBV
FVUAVBss6A9X+66EX+zswSOnydnbfux023ResVZ8p+PrD2MT32ujzQ9QlPVGB16qqqwFBQVFQFUV
VAGGBnZ3U6dEU9cgZkCTvB4c17aypZF3LE0TKUtDCogHWDw8I7DZymo4DxZZ3So+i1OWsd0Cg8IR
SQkPruCIUA9Dnsvov1ma7CHmJrfIyWaygrZSlRyvTje2nl0B0nYSEXig+b6fk7+Bhnx2s9kQ1EQ1
RcooGXZlZE5CCGI8nJJCKA+ixbRZYjAsRRIiRgfhSq4wrCoRSoosUYyW1fpv3M0PoH/vPPLwkHQP
JF51gICK7Z3phNJlNpbPIvZpfMns9JZ83fr3DUOY9XlKTEG2SRSAfvXjeT9Hwhfii14ZjkLAbI5X
z53hZEBloQsogBviCiK6iYaW0sAKqtXoFEWwnKQ2kTCAZwBHj/1D7wsv0ST/0nr+6wifYpCC1Qgq
kiKoQbNNL/FQHCwHURDGIq5QSR1wUCoIMYCqSKshIogXPiKTbipme56+f8BkPFAc8ylFeSAvlKpF
FDiI3YA7oAhrIo9MURD7yDgQRE0wVJBUaAiMgQgMIigQgABFYiEgixgIJFiBCCIptiinTFQ6AU+8
iiFRioRSfmJQuIB7YNA8gXCiGsh7hqSrkOeD3rAVBhBHoHM1liEU5RUQyoMqn4PRFpL1ES5VEStY
pCyFQAZthqIjPzhDi5D7iBRSKu6ClVTCANFFK1HnIeEi3g3JCFLRQrAKgMRD6aWIBzQnUwUflNX+
iYBdsHt97MBwLn/SBR8T8EGRgQH/dwAQ6w2f5QhyUT4MgGwgmH7t9zyysJpgRThKx8KQCwZOQhxM
ShBNxAoEgCJ0wCRFeeA1AwIKlRQSoIKFQBALgkWiDbhpcBLUrYtbowoDKlUhD8yhiRgtUpIJEUKT
/ZLcwdp5Txvmukqcity4P22SnyiT7GB+R936bA6oMFEn/UhO764kRVWNttg3+aerMP+FxhdDHf75
uyjR3/3CSRjK9D/OeU6TRDLXYN1/u6w369dh+hmqTSFCRrCxXOh8h35KChB26uF9bFQGXrR9lQfu
9MU2dAahn04SWYH27KBjYUuCGDEfnth162AhOoKrBMHAyM5lIbOh42HsZQo90IXqLQmMPnGB6iEI
QLjzH+Z4ByPEBgB2nUO2yG8/gO/1HUEPIFTA3b6B4CchgVRRQWiOkSGTC8fR1dMIqZhze8XH9ThP
9b8ffdvP/5f+BSNiwJGBCI5HPJ46ZY6TmgRihrT76whX69hOqKTI0YvrvowwM4IQNyayhKF0bMNB
BHZybbnV+XEzHRQH2EUhUCgYQSBCBAPko5QUK9IufvbfHCIg8Iva+KQsqDqd4z6wr0bTIcuJ2AF2
CyCvkyrkimLB1rJYCkq0sn0lJMTqULZQ/Lk/jY/5Tg4294oJz8kDc9AW8GsRDp9o3qoh1zVD6DOe
9hwoYqHGoHEXikZ1JpuXNfUJGDAwUuWmBb8Jh8viOQfR66otTR4OKEc00OioRBqWbAo5ocNQoMwN
g/Fv4xYjOKObjSeiJKMianDx+GABxeDrGNb6Y0Xp205gdBx4dtzviSrD0GTxm5e4/lOHJ+kDpt2p
34b9LWh9hqNxc41h8Rsrz7Q8I2F4gP1XA9wwwdAT4NHl6TeA+QNfSdIRBebwYqcxSxhLBx2tfRft
nAvADUs95nYI9jJzvcLX8jbgQwohN/gC2HzpwlJRNDKWrLKoqj8k6MwcnzIFrLnGgck1tjK3ZOKq
qbzjN/lMJs7rSK2BbhhGXu5oMel48SXJoEGHxQGERQyOAP9ntDOQPzZCZ6GRwDYRPWKm8kP0GRSo
qB7w22wooYzJskJPf7iVpVkghuQhr0j3H6/6vuwjSTRho0e+EB3TQjyHkk6fJXoN7g3IKOcwM5TV
WlAEyeKFzHt3Gk0kuvk1bi1+wzSpdDD2M3zWIaMkdfeuizlk5wDKDng6mIGiD88DUiy/IaUs9W4w
t8MTXm6o8uoxgJ9cjit9ghZHhcqQLpUEmQExkxrIieY+WyDJwJ4uPFEFKK9AVmZAKGqht63j1DLT
iLYzkIbb947UuheOFFHVage+RS5l08OlwKXyEDzgDJh6ezkKdeuY8a1ZbZbW3Jz0+RhP3GZSIGoi
gN1KVTs8XpEugzhmMTiK7V+ShdzNZOH5T8Ac0qpUO+dBRYIQDmDm2vjgbPK21gyNNNM03EGqQjsX
Pu0Q7kfSfHrYX9reIZ77cJJxBUT9Xt9voPnimKX9nKysyrz7dXmIq4Qk878Hu4ChacvqjO5n1JyX
dVqdqKOZ83j1DIRHZG02fVQvsr0RduT/UK7RXVdn/GCA9ZYXbUXYJCIAsPcOwwN2HB5D9h/uNGri
ffH5TMD7MNx4XgdcoI24Pt/j688JOAD0kqbXdtbI74gEgMnHr2ZmcjybMpQqqDA7EGAYdTEh1SaO
1mb/4VF8FguaNXct1rKVJoGLRA2SiBI4vsKHmnr+6dgqvLNJsQ9hNzA49klfoEh1FDcEpMuRHxAt
Ca5ZgMIhDwFEPE+VulngI+34jTGRYR5oVA3gk0YDTf7aNUDnuVJFhpCjJ99APz4UBnwoSvJ8fHIa
VVZ5J/EzDrPVNiU0TQ827KUjEYqkWLEYgztPVcieLCUnNoxEQgiQVYsBRSIyKRO5076DQrCeLCoK
I1qERIIye1AoIAs7eiZkooKIjWapkDEi1KxgWIpifqSTVOPDWfBbVcg0d7nN1W94wq4SGOSlyEYI
esoWjCIR6wkey0MSiSvnx818ElnU2WBpXEPggWJQ1lkYmTKKIJS6CB5QpBUg0WFO4nSZL7YH5DPs
lLZs6Ryy31n7wB2GAOnhgnZpQgQHuXV6/rpqfoXOal2iRgCp+RL+1xMY/Pg4+p3kiPuVkiiY5H+O
8zzYVDY/Par6dc0FD5eT9hcgnVM+nNnGvdAmfd3o0YS9X01FMGQSQS2hl4bhHYnk6nfoLa4SYbwk
cT+4mLfesKg9wzm7k2Hbxvf0TiEOcFLcVaIQvkcPWmeTrPFUsca4u7rGQq4CsT+P5rY2mMH+hKuk
5zGS1C9RoNmz5akwsDNVv5q2lk/UNI41OGO9Jkbv3UX+EXuG4EHhqfx9ehD8gevnn9/G5EQ9oIoV
VA234gn6xOG8TYydMO2cYBMx6N/AQfIe56vamElwvB2YnjqWSTxu4ueHm74YuEgR6XWYZOpMXFtU
IbTkOZMpYRD7ILRFAqCpIgl0YNeJBU0RVNUR9sFfoiAWiyKCSKCfOOj/tPlPg6UQtav+0CdVeRBE
+JPixSKKRtgUYREkFWALAS0AlGslYRGC0kkUixVgLIKSLIoooIxiCrBEgsBEgKjAWQUkRgjCLBSC
yIkFUiKgsgsUBYsRiMCDEGJFAUFBSLFikYkWCwWCAwFIsFWCgsUAWKREEjJIjBQRIKiRZBYLFisQ
iDIxkRYkVQWSKKLJFgAxkWSKAxkBEBjJBYCqMQgiEGMhFIsFUGCCgMSAiBFYgIwFARgsEYoCMAZF
jEkCQkVLd7n2nfc6PDE+EyDURIdVJJf1jYIJCDRpFD4ZBLDBlIKSBFICyHJ3z429GKQokqBRRkqR
ZRIoCwhK0ARIqsCT4/tyJOOkaFMwD6EX5E/bzllFDMhCKih5fP1/vdy+p/0EHSOqO9sGm9mWcHfc
0kAWZGs1rRXivncVjBm6pdAysKYLKwbymi7FNcDFWQVpjRWi1sLZ6UPw+T43t74JtA2FIEVKSCSE
VYJBYFsBIIqDGREkWSDFVBgosIsFkiRFQiCe78n7PynvPQ8GPVeeR5czw0ZZDDxtB7CowKItnOd8
0tL12IbewVDpyODL4HQXpGxBOaoWOhyThsf8P2KobKdOCIa41cm4UBhL/awyejDn5iqOEWBv8aOG
e3kvmpwZFx7T4+ujHDi2cDJ+aDV1KMcRZw40zLpu4RTw6aVMl6zFsw8sLZphn5nR0XLB/MBHKpc5
MdO/A3OlVZE6tkeSA+DjIq82+SJdSJA4zYsssazJJvHYJXLaSIKMYV21aDEsUSuFnmAIFSadNCTA
qyVx22rtFafmOhsAOPOjm9e+5y449kWGoHalYM2mYQ8GmigJUSbIE7HSK4koLC8DI3FGEBWzX53+
S0gdZVCD004200AkOUgrSgU1tMKI6Ki3jKWEkEdvi1kB32IDvsjwn3+IuB0fIdtV/BQT2o7Ikh+l
MyJvaFU8dswbTkV4ugSCnCsgyxNO3ScukVxat3nOq6yd+2L2vGPgc3d7t1vTzQ6jTE2Xsk6Bm8a1
7CMZYxVw94pirlMylC4VQRUt6OFYQHDPd+PAHv6jvtbbty4nZIB1QpFMJai57BAbtQi4nbXLd64N
SLmmvwFgA8OKoJou6oLD1EtBZw1kmisKxqFylrpTXNhzvMN9vd888jzCny1FGFgwWVlHqhUiJHvn
W4nlKeIeEX1RciCsCKhrHn4+nt8HVfcOpUe49xiZeBbuY+IlVulNRNIayDFWFRTVVNarRTGqsKyq
tQppqiDoCe46Huoko7Ohzcn4e3Gubxc9xeVdJpbhrsWfnxsGy+FbTfuTZtyuNd+BveMAdABVRIxe
QXgu+vAgVaMSCvKKDBQw9zAewoU+neRBnsXyVc4nCLKLItWNkTXKVmeWIbI0q9VTWUkiMWCBVjQ1
vTI4ZUwxqjIUY0MoI3CoeHayCqh9DzWupv4j9T5/ZzCnRFnwcPfSoix2k/cH8zFDYXIH0UoFYucv
w1OoT1qeHfFnqhJh6CdshaxzFrl+xhHjYSyL7XzFMiUWKDwfAtUgUbYJ0zG3HkssJPN8UhtW0Yav
cEzCorjMZd23GaoOONVQjcl1D6Q9IukC6gsASVUEoFN7Y1ZMDClmSXthAK4FsiHVNKyVdZZFJeGs
gZE5FzXrjDlKfjbb9wLRBILFTTN2q0NRrquOwB8kqaSgGHiGFXCxasqN5gU6LAy0XzMQQOsbp1F0
8bu+DpwaDEH1AijyvLXcP7M7N7fdh4ZMQlk5jvJ0JWcDVyvDXaoGxjLrjHd2sGeJF440oqjhwWZJ
S4bZb4ogXJrElG4moSGh2D64VgFlZpdKNlXULxUn6/j49R+YO+C7PTBdISD0FVHE9VMIJ8O7eg78
hx6WWnXA0jG4eg6MHV7VOiDSgjVq1TMZrIe5XTIqr3dJFYojVBkvkS9DqxTlTqibUIq1AZZjWCjV
Etw0nF0MVSAXIxlDkFlsVETEENW5G7Zsd4g1PBLfEuCeBwSyOxCzIQXGmuOMFI4QVlpKYCm9fEoS
fuCcQIBUZRbG66Zh4cWJYworqQaZG7QgK1aqbSsrO76PPY+NRMdT1w2HpMFE2REInaDgjnmVnZsM
mNKAyRlobpa9IXqzCZH5fPu11dmKzCbSYzq+wXXFYcvljZyyR3xMjiDE8SWYjLLcRDu5xBujLyQP
Exr1nGYXOOVaWdxNMilB4tKEOTJSyIBEHrNZC1PCBZlujKOHYkMvAUYkwyi51rpQp6CIUji/WKVO
xxcNcWT06wUBQmFTpmDKjkKBIBToYPiPlz8hDgoB6Zp863XS9MfD3fjHLo0WMYDM5xjc73MS9cL1
L2/IFcZ1dr3BTGRR4bC5TgUMfHsOmHjte7xILoPKqP1pHkFjGM5BdVtRdhediM76oIm5gDSqU6sc
MyIZlQWHRnCb60zLZOEe1kxE0KMh2ztum+L4nJmHm5yyGyyAhuIKN7lbzI5loW1aDLWLE5IN3FYc
zPbnQRLfcu3GvA6tAzk86O61l2RmiNmOLRoaipzFA8nTwxb9FEdAShBboLsqSp65duLB4LIUlHBA
zVkiNxgOBPn9I7eDRbFQb0oyArVQ3gg9ItKok9vTJw8gqCUh1S1CijB0guqNMY6ORNLpAbFplWFA
sh3WMWYXxCCmGrGs+CxzTFxYWKY9HPB0LroNgPYwI9EuhPDXMQbjJWbOWN0k8ZNwyGkgEiSlm7C0
R1sW+NgYCAQLJKaC5solFSLaowrA9h4RAJraWUqF0dcaJYiqrniTESpOLX1s1GhZCUIYPuHe4EMU
Gmpf4KAoi4obOG/OZxqpoLiPat7JooOUbFjLRkHOuqowrlvYsyPU1ijTNBU3IYKZMgpBFEaIaImM
klVUZMTR7r3nk3nIwJONOzqs8KGvh4QfKjy6t6qPM+vqTb3i04u1INGhShwQIMv6tGBU1SGY3Kw8
GyHtbv2VamEILg0cl+Ph8+x/L4zV3qy7fpMPIeH2v4fKfEfyEKIfzHtPYT5ifiCqnA6T4HDwCT7G
cnIiKfmmTfrgbNldGVf+0mrhu0GFn6sqLEH3BS3snx6Obm32on47a+atNiKudxRPNFE9+fIXOV5B
uFZ2G3w3a09dofjB7xxEZUm96aS/N4fW7r+r8L6Yx/fB6ye5FXBVWx5ieo/PuOwp/JrtkRcdfwmJ
/B9sMMaUenoC8gYUY+RH0ciIOp64YqfQbc2xXhNg6Q1nfprY05uTCE6e9pLVTKvCGfsPDPGd+4u3
S9vKd8KMYDtXhaRGiOZtZUpMiyVZ7uWcx5rQrVrZJQw60XrG8evpPB3vCzSkNuDA6dfC20XZoxM3
IiYqiH0CaQEgiY7H90aeMXEc3mYu5ZDPfvBoIuGbEsRT3HDgqfmxwvuMfreMPSq6vlfc6EpNQZki
PGHElI+QeoBRQ8zORxt9Ah5PcJI9TivsW8vJZRufGL1bXtmSCid5zklIdDGFoSfVoL5jl37+wZya
bbC4Pkdesvf1Axx1xijCrVR4aqspLSHF5VN5QkQMdL4E4FrZY4sryTLr9EXvre7gujkbRHDipM9h
FHiqmK+fbjVI+WGuJoXF0onBmJEamwpqmii1Zt3yciXtzhQe2Hlj5YBpFFHieyessaBmJ4WS9j5Y
e3yNnUH2Ce7sa6r76I9EOs4vkmZmzr0D4NYcXAfO/Z7anewKBa+h9KppB6e8Dsw7nZZmaa+MCJ/F
fO4Y4I6CaZwx+OrZhT0HEaUzz+CR2aPQgtmQH2cHR9eHxuKZgmiAhbV7+ZYJ32NRKhXyedWbMriM
Cw+7MzvvwKHE58BVNho3m4vKli5oZMgK16pkDtIcOjx1e7tV6j2L2qq7nvegypogQEgnbwvICnXs
3WiEncOnXSajoNhgQOPis2IyctraIx6cbIpei6By0kew6fU2NURkJGVfvRZZIJR6+ToQd70bOWea
6ViYLS53Vum+bgweT7T85IT85XBVsVqwqxwVk0FZXy8Q/iDoKIsmAGFKwpBWN6AdM2v6phIv0e4+
0RD0TwX6k/OCH4F48E6TU3L4/+p7EnTS/PZHykHw2wTN0cfTx8eydx0cbNP0XKqr+uyKTFLjXLEF
p4o60UZQVQOeBummJAr9wKFsvruQgZpqPnt7u8ErPS7OTzWfFh05BqtOGJ0EPdOZ1IeT2uD2GkU5
gnVDXzemvLyXOjqw5alczMnvUwp9kXVT0EfW0vfwUXWCEaGpXb1heMMEwEVy5KfxL5AiB/HcYjyY
3TxhI3Q6p0OGL+mmpwm7iIUECSI6j81i2PJkXLQOPixZZbkSDOnSA8HTlEtKBkIMG+sorUm64zJ7
Y8DOFPn+Zvt0RSmV2nx+pq+XK04JMsqj9hG7EVEL8dc1TcowsLCokUXkTOo4w6vV9UsPYIm1Qc82
zxx8+sgz+/AYfCQikkMEQghnTvQeyAJgAn/4dngsAnBQpt/Nlud3bFV56AoQEOOOC1bfNbbqWcsL
O2lVHUmEOUJx/cWXExwstVhiFvuf0VJR5X77WxzUyHKhwmgE4QV1ruiBzmObYqGJ1dPQJkGSZHRe
zwUXMKdF7vogDmKYCGBpZCEZBSeLqpJgpBPmhs55SRFCZeZPCFZhyGB/iOei4bSqr6fXP92lnkkm
RHc59sSee1WPxeCPbUpgSBB0pJucUcl4W9X8b3aF+K0VNhNyKO6HeZSXSBT3s/M6imi3yPRlxKUR
QRk15BDvevyjv036xkuurjg5/1oIYJ5yvJ1MY7tMizK89YqFgREhiZHPoTDXZ+KuOiNBMkUOVTfJ
PdIiHBOuugJIOZdoOSAHCQhIRMIURu+/wbfU+nWGS3mYpEk0yRDmIGoL0yEqnnyUTiOcfOLkE2iy
5Cg2QhWaIfYe/61SnroH+LaPM93fIuQCdTiOcNwbmrn+B6+DQQh4MKKLelCe5UFU7re6WIjIgLBR
YsBS9JkwRSZ82YQhNXRqOe7R6dX0bHwi4nZHI2gltZ24FO65axCDAY17/rpGAx6Yfjxc8I4/YCMg
iyhja6wqms2Dl7Pw8WIJ+HjwE+Md76hgZR8Pi4IPzOg6+4EvN1eUs96KvLBVFLMf9h2K9IxcSdB0
2i5KujTjh7aOydLKGt7OXn8NZFcxiWF1BsorZ70fJVR3ZQQxjEco8zpZWTxwUV2JmdTwcyWbhqjP
OhQamyjghLAe6D0UczJFWVOvYTMRfC8ikcjD4FiN0lRWvxg89zee186HfelMTViITaSIKOgh73Oz
OOOFWGBTjCObbESRSNuIIfZR0DHFnTJjX75jkvmTg8lLeKmmW9EE5nLE0X1Ei37/xrE/P9B+eb/s
/6e/9pY3JHmpu+0qbjnyJEvDdEHt1HP4ECXynhB5ILfx3j8UqlGBhk+HCA5Y1hIEOiwO5AgCFHd5
3TQga6iW8mCCm+bG0Ie0tRLZ/WcxweVDhzT3fOeoww9n0layHH71rCuHDVH9t+Tg35Pbwn9TDCjg
YEGSgy6qnBRkUyq5xz91uHD7Gt1yE92L3HlvoXwpciCZslB4QVwLuFDa2uMggcxzguUFgkM1PcFy
sWa7ZSfzxaqsqqKtg+7yhCMM1G443XWNzY/rzY2Xldp1x7xMDTCa+hMqWO59KoX1nrMjXnFT/ash
XeZoUZM1cRmeESMYcs7yfS5CPfJXJKumsUUCMWoZazeqb4xHoRMREoKbqesVDzevRlVwdmYMbndU
fTEk5AgKUa5bnjfw7rkL4Iu/s5VXHgcB4PmVPAVExFnpw19OBFdS5zQYV6k+9ajnZFwoUp6v4fCO
1KydgVFVWFVRsgZgzOjk4pzzdFImWFxx9v7qi0gshsXMfp2+ToDoRL1jq53sVptqhL7iHbwYRRdY
Pej0R9TguB2QUhwog1hby/zJJC8u7nh4FomJ6+3b5QwG1igYUdn9ziaZH2SKt/NGiblaIx8xlHDI
IqvFCSVR9wISO/w7Nj7k1qh3UELvySrrBC83Si7rFIwFSr2+h5F6sED3yeMTGTmhiUVe44qnHXE+
pA8X83pYwE8U6p0evxD1OdmhM2N4qQe53lQh5aIU+kbuvFYsihQT7OnEgh1gKCGFu9PgHREElmcI
7v8AJ4UMjjZI7oa+E1UGPa81IckQkGEVhD3qCkQGnyt3JMe1ZcPTfEhq+I86wRywIzGugm00bVRE
8KByLDMrjB2uflNk2FAyF5JwOGmm0fdVlUwU+IrC+Ywz86E+5dcgHJEE1nqvwobVuCivUkVoaDMi
HOExxlc0WMvJAMzE9GI+vCY+kDuHI6c/WtT/BCk4UI22AMYrJnLRDxHLDt9/ywD3nQuj4Z3VEEDM
Spuxglt5yiHwFfjeqenxbMQtuNDfyUB4rIgMwMJGxu1HKKm6o4d7U1kXLcXGwZ51Op4rcK4aiwBB
U9S+o+4VOYRebHbFVM4WwMhymSsLmBBRXD7WRO5sfTA8bezdmG81N5eN81bUdpl8tByLr4NA3zmr
+FnFxSkTgpBQK+WEyjnC6yUHVHVpA8MRyHIVpMcb09EDZAejkqo0n2VboAhrl4NML9GVa4Myh3TA
kFLVLZzIJyWOJplGdRwepzzIUfdQRCidYEKd1ob08jCQ7DiReoXKrNa82qPIHnd6nvdQY8nMZcRv
BNzty3qMBgzCqMorMJ8Pg6dip5s95hC9HuKIas953QFTEYQCBAIZBWZukjoQ6TF3Fy49ubgkc1ND
GKBI8EVSLDJ4De++o4sqjXByHqOGHo9HjlGY+2oDBIihJ5u4mDOMPpyXf3uH3Ag90ROljBIEROPH
LE5/A3/QFuTBGtmenl17OSrzMGNPZufP3l/cWaKwGME7BqDZ+LxmjsQKIZkSoNvmLSG6/fTyXNvP
nYyCZKONzp8epzxBIm8es65jZ2kTOwtI9VabjgzSgG/lVI+1oqSTxFbQaa+XLY2NjY2WfZnR4Qgq
6NIJ7cmijgYmcjjHr+vZwGbJuvnfwaFZ+lkNT8JAmHiShRDwjNCeTnjskJDtEUWAIIEIsIDzmz5I
OZt69JkTsritrkkwUa4CeDF86MJAkHdkdlnXxZ6hlz6s7FgxIhl5jPAfAYxmzD3zt7j2+7nOSciY
9mp6z4SlQsOsIwNQxvTC+MHGLRw/m/b6HIHgaBNnzMPB7jVgYiMMRHuRw4c4VRmYHDh53Mscr7aM
MjHpllZLC9lmpL+nkRFG0TAgMEFNEZlVRucK2igLrEJDWSCIEi/DPEOGB4y+qyAhrimZQ7r088qV
TpeXikiaQ7loH04S+NSfAymW3riqCDOpjJrXjSRw3MtpYjtUfSYSzxJxVVTkTjg9A4CClwpYVXMu
uf2/qyLsvttflEPKIaKUwrtkP0XosnqFFgY9yJ+QM8lkDJ+e38hzMQlC00ELnpNP17yBsTBoE7I/
CsED6O5M+UpUm6adxkZTFiFaEyTJZUKEuUB6QRI++bUfZnFy1Aw2mJ2X7LG+N12HNCDfyx2ZmzOn
igbCHlhOfY4p++z+D7oXNQeWdw89HqTNjaO2Yp02PuCjRzEsHxm9HXy5TOL6e6m50l295WBYkqJm
HZERUmWIwTlblG+O8xbsMG5bu9BbQcbCBxnH8+qjnweix5KmTeOSBnqVcPDgblfmqqopmRteSya+
a/3tnKZMnPegnj52MEKlwwpcjxvpf6r2JV4mQEPsI0zQHdCtBr1keISv6xK02lsLSCxMBTZPcWTi
HZ0QU6S2UZWGkA5Tlml3YbEldSEya9QmUpTCkGfrMjOrmgOWCt9Q5DB+A4s4SOg5UqZWYuS3T5TQ
/Ze4+rWPKlUZG0UaVZKndNnVJiaFbMSRZaFpfJrTPmGilt6fHpP1z6bwrzjDPBaBg0GEcojgERYQ
2TSTs07QQverUtk3jMQq9YXENQfPHjCTGEOqxJiACqN1MYRgNKWbwJQlDjFdUBowzRydXREVpscz
vDDKw82w26o03um49QowpGh3R75YE6PDoudZzjkFTV4ujT65h3+OjkJCQkGEigkSIIxB15sNPwLR
HvSh9JPuBPj1421MqFMyoiJJ8eSvnf0nQ+B7EmDPyeTMIE2cqOrwiuGqUCya+M28Jq06zC+MkIQk
gOvWo0dmqjYQpMNQrpGBIwCKEYIEAhFIAEWIECBBIgHOeHyHlOY2+7nj4e7T6IH4GQNPZqZ2TR56
qqfGNUJkiSE2PYfd0gRSpxXcGCP1nu9xjEGuDJB9x4fEjdaKxRSOsfvuRyy4xYb2sSoEUY3SS6Mi
kRxxFbRIajECZWRIEHKCs4yfkzXfXEh6BFV0eU6oWRnWXKQhRSFw0Ucj/Txue3fM1Fzgb7zEekUu
1kh7cHuTzuze92LOT2Klsb6ud4anLYUKHu3MHgAmIZBuKowzgxQKTzu0azJTIZkGCibLhk3TNGoC
ZKAmcSAmCZm9YBtkJvFArIY8CJosA2AnHBQOKSzE2ZCG8E1YbKlKlE1qmhCYyGy1kk3hkhkRlYBU
AGFYSAxeSiXbFq4dfcy6Xac4blvVEv6stXM6PYQg3Ro/F3PCv8o8FYZxB/ZzGBnXCdvFne8lL7wQ
cYeqv7qxBDOlMHDDiAxJh3J5QVLJ4FHTBbvYWU1IJsEdwt5vudiMxqqZC3jpC9inn3HzQhFk8sgc
r4zmTzCiW/rDPcsWl9AIzigRg4rI5K+3tBwfW+jOnFt2whp/fCdcHFTd4p6AYPJHh0UInFA0WjxH
duKIcJDU+NuvHHhiwIGacrmHp0+DANxHHectnrBOnbsv5SZHRxpJ1Eecscp0l3yhw083ldhrL+Qs
WIh1UpSPnS4UoXHuK+XyLJ7Don4LYyiJawQiJhrnD5vEwiPBhmhmuZxq+ArHUYYcJZT8na2UPAWU
+rUDxZMh1MtCFlD/r+JDaUyLxZIl7ipBfwxi9Pw+TgJ/X9DNyDNvoviV1IhfpT5YcU3CpOmFPVkI
Y+320qpBBRoK5V7HDzFKboEWf04ugUX3qcJDCrGaMO9uVbeComhGVFbjiQFieiqzehrPScYre1GX
HfdXcKIRaTh1KXM47/aZn1Y5Y3Xsgyo5VZhV1ZY18S6GHn0KgfU+iLOZ69yZB1runpSMyqOqs5vB
BjbjEcm+GjmEQcjyj1Picz2Dzlre6VajrLzvcm/f/Jl0tGgtr1Zyn1CklPckHSebFhnz3z7gwMhO
2S1LALGhLIyxJaWFFisoUoBSMoKfewwGYwNEIDSJIhIohMClqKF1IDcIBaKiUGfMSKj2d8oFcyOD
xCXTc4OqVSC7xPYAow0qyFGFkIgUstSGasKHBwYIipcPzVXVi7CsPpN9c74OVTnVS7dnk3fL6mnN
3JSkwNW9bNqwUt7YF/MfidYT8aj42d3aIpPHJwWUer0NV8pk6IIWlFM/Y744kIYXsnMVBwvkUWQM
TRXJO9ePTmj9eQZG6MCJQ1SoyTmva8cWON7P4oIfGEBS/2bcCZCM2ki3tVAtiAiCcrDBpwUZdfpt
Bua0VLwCNAj+4CKPkZijXEuWaLmLVFwIssZtmTBQ3vI5HQnOBx1CBlSHN54HXzcKKP9GQQYRREFA
FwLwY7hkPGFHnQzlxG7xGN7SOnuGgvJYX0PuKXtcHuTGYPY+c+FnQyd0FGattmhp8wsta4KNQny7
FiATh9bmDknUEJjzDZBD9J4p7gQ1efZOY5DU4GzFyQBDAyZD14+w4Jd8ZS2UGJOFXw/Auhg/vAkZ
U+Ts5m+Uwg8X1z9/2mWeYVGhbd9+J1SHREBES+pphcfJBr8U+GYlnUXwPVMmLnsKOzYZlTmWZ53+
vkODUgo011tZHe4x9uaqGKp/NcXYVqiapxhNEcipZrL7vTyonNd5ZJe9kZn4l7Hgu+cN9j6YlSRD
NvAgWitUh8xYfOKbRe4YEFgKKMMHPU3MMMxqrlZWVkfLLf4jgVSMATBCpucGrHrvo3bIzClo+0Uj
IOF8Bh1YIw/1rz7Wi9ghm0Llma3vy+JlNDgMqIJSChxSoN+ArEi7RExTF6QgNFVd0SrVVWYVCDMS
SN2cwmKJPpnbhW7hT6dj9zIs8fZsnNfEwoKb9pR6MEvRpGmvrBjlADbFhhtwuFGOSRiROF098MQ0
nVjzqEk+kERFERRERERRREUUUUUUUURFFFFFFFFFEERFFFERRRRRRREUURFFFFFFFGIooooooooo
oooooooxFFEQ+6eGidjie6iLUb6HcPsgnOPVVX1n6jgF/Tp9J8wqFu/Q/tagCuC8iLgop0vczbdk
drcgSlZOg5kJnojXDhzu/1dhuiXWSDYsk+AqooUKDyL8XIsYosWLH02fxnk+j7SY9XgRGeSHaikR
fsIHsPmxCAp7xQvPlxNTQYMRi8eMHzKm8cCPNubzBlRzqKI9Ez9HU6HtERERERERET9goQ4+37+/
jz5rD/e5w6kRkQe5XrBXQNeKe3RhMYwT8L0IHw2GwlZxMAwreVrdWiobibIoXKfQ4nQDuORL6+Vt
ePzBCOaQGlWjLeMPw/ol+yXo7nNgeOPNvj70skUkciakIQFHDkdp9DIdXoVPkZLyYGo5eYsCIxcb
FR7uv8PIly/BHaP7aWBDcOvuI+YkT8qpkPQQ3DvrWcIfZuDheSRkl9OWEBhQqqL3QKCsimJUsedg
vMT4RhkxYwjAIkOTwDysHW3cxriNhXY+TFo1GZzdfC6dVDffhrNO+tJLKaWothqEYw4XpRnnjpXg
lppsYys43D4IOR4UbJg7wODvuIqm1ChgKMWKQkbJ5N6+RXRLGlVuUVBVMELukNH3b5bsBMlXeUFI
ZKN9R3Pmw3EkQopQJI+Rm4+A9jQmhJRRWlxxPOAxdC6sPpXPiSPqHjjqxNKLgtV2rkiX+OWby3Xx
zRhXKq0RmYbZezZSJtBVUEFI98X5L71VmRY+ClNLkcTlM+ssWFepHokJK5C8+PbD1gxZvf7HF7vb
r4TeDm/aEZAoIL5fslrAYUmVkPU8IAg55rFrFzPtWMwUWQ9h4BFgkrzmR6OxoiRi06J4HbRi4FtF
7kei/ssf8FQJXN50KVUkYOGF7BeFWpQ588ETQkEMGFCviHSfYWlWiuobgsPXiFhVxzyguzT2YLtd
GazHyIMqjBOJUmyCUbIVdVJwZw98GGiTzbQhXJh5W4sSwnffeyXsTyXySenB8+M457GGCNQd1koE
cvpohbcI8Xui57r0NAxsBVLOR0MHJjrBd6ihAQRACUbP5w6NiQ4sGxsCxCSONVtqTQhJER6eaEZ1
aHkgxJtb6M58G9E8LRyQc2TSERqdxy6GC6mny6fwRoxK+A3xvUnrnSJ7EAcDIHH39MBIwGGuRdLY
WIwymyFFkuNXOIMySRVU07ICPPUHNGrNniiyYZNm3DxTUYnEugWlGV8BHKvYEEBobnmmj1Vat4dj
JM/bhvuLsdGayMKi5iyXnym678qXohwzJjtNzeFCNEnENsX/bSeqsqLQZuRpNHk4vHj3qxNJGZ8I
mmlqRtE5golEXwZlI3Cbhle5KLmkBjGBfP0i80V8n6msmuwMd1xjVMs0DiY40Jpdc/FY/ltLzoIy
3/RShyPhoVHm5QopLRCFsPEwIlFiePHepvu/ImZ5dEVKiVRXN0VVgK8NM2Bx0VpBHg5n3+MPNlxV
q1ejzBiCa3Wbj7TfQnBgYlkbwiKJU2wyWZeUDc8eqSwaRyDyRJxFIXCtC9RPcPYjz8HfZg5xShJw
6F0LCIjhU9y9VguUl9R60+34ivht1dIx6kUgfU4y6RIkv4OCrmD7QIqY/GCjMPM/E7zW4HgRQ5a/
PlWVa7G+QndwvvWlMqbDHhaBPmzpKv2ugKiggvq+ZdlZtzNL9W4ueQFSPZ/d4EC5qeL99PGMZfHk
ve7s/2OwLwReUFSTc0FcqKTVzlQ+3+DMhbQOgLbRB9ME990zy8889L34Dq9c84AWOCFYskZGMTIi
pRIYbJt2GnZXuDxpC66SOScYTuOK9A9zDB7GJ+fwCN1vREB+h91OFzJuJ2U5XkSqLd4dqhQ+tRw4
wdS1FJdYDk1gjCcVo9EQ/anSsNdrvtNF3rdrxGhRRy2wWqy4yiigHYQoQL5+xjI+jiiKYSwq58xO
4T07WIjqpmYlTKhTs3AIx7vplcD+PuxJ8PK+7t2Q2goqQ2O4x8Nq2BCOZO8wdTaKcPZdbPjhj6ko
l/nt8QQx8s7wXhdg9k3Ph811zl2DqnUYUwE9uIdOsUdOkpOsIFSFKV1y/F7/B3Mk3VVcrlvWEvJp
Xebti55paZ7ndYzUkyWigkJIoxAqUqlRtq/4puwwqq6I/VYjkxLff+Rz4asvA9D4zqIlrS0ERN8P
7FjPn4nsH2Xv8d86N8mKgiqIKiEUY+elAgsIhpsPoGQWIigicioSiHQVhBRRRSSoFakrAYnYkPRA
lnV/hj852DV8lha3+8+I8YexwkiHBYQ5TZ3equ7u4E/4DYQosfV7cAJP41QgsFP9IxGH6hCQxJAS
EAkV6Ao/dE+kh2Qdk18oPp0I28H6A+2D/wCZr2xDTrKDngW00Ny9UekobxstqndV/Iv41+30iP2/
V/gub/nJrEYeZz/hJZBOMYwzEXFL/Dx5gRLZUtlZQlS7RFigbaFUM1h6nKdsaaNtCxZerajElTQL
TkHBcNRdi1RdK4FvajJbKNkUVdsllBcrcnJmh6fi/J9zIn4EfUQGp9VNVE6ofZHtvXya64N/HUqQ
LmzTtPmfdt69P1ZmiSMj4u6ab3cQN8pk/i/D+z+XA54nac0rnw31sy9XZThl/Hy4fjNtKtf+U/ff
dzLd+knu+MsIcf7/Tc35fT2a+n+S2q4Ny4NP+Q09OVefSd8Nv6Zu935YYOc66Zz3bwHc+qj28Fvs
OWKvl6QczLR+yInDLv19TVepT4Ter8n4Ko+2/R8vfK6/Jnx+c/X7dmWfN3erCf2evj4p0ytGy14n
RThLFozuUdOnRZRt4eutttJhh0tiMYxH/yZfiEQ0Xd0Q2SENrCyjhFQx9NS/W2HBXOjBgVPW9nCq
chBA1naGO7ElidEQdkbu5V/A3GgdSxYmVK5VKClU+lCzvYFck4huPwqiGkaAhR07RO1wJYBHAuIQ
YwSElHPM5xEHbtObW1KQSDbQMTEp8ogXfu2cNhtaSu9KhetYpoFDIB57tIAclt68RBHgFhxYsWx3
jkQSzvU38lPXaG7vuGdGjUoTmKsV8nDKyjndOlK8+d6a8+iJVbLBvO/F/KIbK6DDFSiVDfAswtmB
bPZyXMu6qUVpZ8j0YGX56P7TL+8TyTStJJUlVAcIyAgaURhujVlWZR0fOaS3GXLddPDSa63Mbd9b
d5VxK20fb17n2Kn1PueH4n5lj6n4mzpw9H6D0U0qWNmT5PZc4dKlj0Uc6aMHTZwkwftPDJ6OGzw9
ljZo9nwYKlPBJc9GDp6LFj4OlTp8H0PDvf5Pq99V/Hl63yfCnR0+OCznwPT2+D8nTnPwxtkX74+E
4a8TCrQbdv1us1+W6F/g18/a43ujm9xRWeqYXvde292kesK6Vdpn4V5TaCk1jjB2fG9wZ3eGMzrt
xi/bG9rNkz1u9b5Pyx6u5XFM+LlXB8cNbc5zx55+VWfnORwdzV+04wwp1yfCdcW8Fytvx7ZdMeM9
+GS5XDePTGD6xhOfh438IavxLzDpXp4PpNputPwdZspgauV91fWoeM26tmZutHQbLilrUoq/YPi3
7hJfqOuX3t4Oudjl+N7rrq+s7yyDy/ON1fOZ4HmT0sdROqvghxwOH8ulX1PRYZW+fLRPfB1DMfD2
48c6G5HWeIbU6rm4sv0G7vo2G7mOqnz410r2pW5u2uw75bceLeFru3fwxdw17ddod/Dtftxf165+
LdPHv17Ya7/DRtPC2Wi5t43NLgZ89M93bjkvNMrbnb37R5C91ksnnOVJ0m6hOO1kgvYKmlZYpseZ
MtSutKrYqxjcHv/Y/o/ih+39pjP5fic/3lu8qaOf5km84M51fru3Px/w0s0mfz6PbWWJlTRWG729
vh2Hxge/77/Vaiw+/QVBCN6D6t/tb5fZ9HOMjx8A13b+h+0/oZx36ePkGv1f4/T8nKP/X5+K9/07
+zDvu9bHV81u6q0GYZE+bDHrkw44jJYePT+2DDvhdg9p2dct0n73tD74fjBvCrkXicvN6SXyVPDt
4TeJx0QTzyOm81IbnDcwZRno7N7wCMmPd7j23vAvLDD2Y9UKKn8Sh/igJzh0PI8LaOVSQop0DBP6
U3VhXtRFr7nG4kC+zx4btB80Ki9d+fVc+0f309nneoVDlI1/9qSJT6dRvNeicVvF26mfF4OmZ4Jt
ZsEAgQWotQkERGEFAEvl+mY8H+3k/4eo+H0ej+F+7dx808vkh8D2eXwP8Z/KCnO78F/X+P6vfc3q
6/j+rT9X5tW4favs+y9o+/4fg/4ae/vf+vfD6vtlv8ud+mf2S/XxX7r+2Gbfry7D5xi0+nTtw21O
X6+v35eM23hhJCiQk/uz0fV+A0KOce+HcB6tXXhbvCIfaIh8iHiPPSPmn5z/JuKWZL0FEoaEBkCN
sb/ZTJlrJH+7Myspbv+0/tP7BGBxs3wFIkMGqFqBUZWV0YZ/cf/U41riAypC1iwbb2SLkySNr+60
mMo1hjP/n+a72MScpZlZaMsiFIKUlopD/rchGMiIA5/MZLjZaHXIBmCWAQ8BMQMGAJaSQklEP/lg
f7nNKTlxRsQVSEGggI0QNn9NrCwvssoFYUkRkCiSVCHXzhxTe9kgamYYWi0nlbCIJOyE0dnidKaE
8OSsMIlKBpsyfm3hiIaOIhrRYeNlhtCvVkITEJ4MA0iZZ/rPYiagkoUokGBAjBEkFP3wEZEAoWBQ
/aP8gP8pmO38+Yn+gsALlA0KZDC4wD/7IJ/Qf6lAQ/0KNaaZ7tlLEQ3/3yhQ9iH2f2P1HyifGP0g
fgAPcFE90fWqiB7cJCIvv6C0kygI6HSaQUX8yrJolJAwBE0FNk7BJv9AwWKjD9HCv8XfMalxuWYZ
JkyLNScxGW6pT+spLGBhJImLZxCzEbBRgaBVwGgbAvCPBBgxCcg1igz1AvupD2CkYYUpjQQShZMm
Fn+8gB+b9f7raA1VK/4viLpaAH9pCAEVf+zPPLEBiIwiH9Mggn02W2BRJP/hOjFRZBFAFT+4LX/z
J9BCA0yCCDIIRIgSAQYkCCkg+Irl7PCd35K+D+Tuy4+iVKSaH+E5zPJzyVyyzf7j+b3en+Y/pP/A
0P7p+8U/yinNLhSn/llwww291+uf9nQv/1/I+JCMSPdRRRObv85/Wf6Dcf7A/7g6n/yOHkj/WakR
x3P0mR4ChEcMHn29PXrzf1PIT1KHP7CGH6y9uojIpIquEPiIvsiBqif9a3aTZDZYoPPVZIeZUiBA
IuEQGiARngfTEIvpPIsNstIC3tcH9i3UjI2WWA0YgIG06cNU2bICXiecFH+EwoWYYakHjpyGgzWG
CM1TkwDeFARQYihH94w0EFTKNyCfx8XtLf82NkiZR/7wA+9AO/kUnnmUMq8aFW3Xr1gquQn/iGZg
imhSn2910QjChhogj12ywtEySWrNfXV8fue6kyajH8W9fG6R9UW/0ue97uVc3oZvYaLFyji/Rfrl
3A9QwQeu4xMEDCmSO86GEGkU6RXUhyS7dDbjRlhddczNW9E0mtRC1XBJXOCVTnMPnRfTa6ZhGE9u
WJU0JYZ6P0Z/6lxgz7XjOLNkCmIRF+FQ3YDK3X+Tei06LuPC+hJRnL6ub368YeXkRM6lDRpGHWrl
pTDKUkFNJ8BD2Yo85qMnzsSbEFjEO4Ab8tLB//RBPzzTMANj20HLw9NdMM8EXBQoYJ9lHHZDKBaD
uGWh0oR0kAe0gQ56dV2AhY8MwUkINYwQTQJgcYs7Z2sbdEMaqqqqqiibw6CZ7eToHMUDXP3zoLnY
lC+IP9aFLAaogYQBmo4CynIRS7hBbEIg5+qS/hBn9VyW0L0epqJQoTChzqlet9KcxJwby1h51vTa
goKHpSSXv+ywFvkrTagcoYwDZB6YqFp/6g8BHZnQVwbLKBrhrN0vVFiM/hFz1Xlkd8V+8SK14SLF
J9H+G49AQ2Ha+P917s/fQn23xNH3ZQtO2ctkV+FkQjCSIB+ogFRqCD1EWwQbEQYpFDpZIJGKpjHR
FUwbYZyA9TLIUvUHPVW4ze89J4PT4etguFC9FWQg8pAP/ZAygpmOul4Y6ybSCWg4EQ2xjTaR8HOY
QdydNcx1lffOANuml5aDkdQVeqGiGmHNFNkHRBNBA1wXNinXCHrEklc83JtqsfANTc66srIffgIV
BJEe12UKeECOhS1c5vpqc0N5iclg1wE5SB3yKJjEUMo5wNM2Z1RmFf0/5k0FVCsBDApW2QEnggQj
064H++MgfF2TCK8ffBzeQ+v4v5Tvf2chu4rFrVtwn9x1nuYf6/z5W4iWxtnHItvrI9Og48s7ef0P
w/hR8OLduT1xXhey3hXiu5jL9c6H9E7i4aQzKKZXD7Ifcf/Tfzy8kT2k/WcW/s/iHCywFIG2/3PE
geYyI2Y2P87UBFU39hnF6xswMz15/uWC2VBOA4MlRLwsRJVqmIqNz6GI+9rWMFR5MeyGj3vRgliE
U5xYj4xLnWD6mMjUU7evsL1rpEwPz5hY4S4KzkZOhsT3QMpZXvHKsA8nD/0MDwHWNjkQgqJ6nf3R
3EuBkQ3TEkf2XfI7F9+l1O4OLhLiW41PIhQmrvcJHk94zTotcSp15wuBXKYG8VUhnLKPkeKl+uJo
SNEcr5hQ/QTMnkusO3fa0UBUuOOx+4O4JI4fc8eG32+XsUsqbuTHtUjebnIZrOcnJTX3ZIuU44fD
pw18wwszRJYanDXrrnJaHqKjUqMKyl1qxCJJ1ntP3vwH+Vnur3J77xW6jzgfJDHJZqyt5u2IOTin
eMCKDEfZcVvPFEshFNPiKnJxt+h4ot6r+Ugu9ueY2bvP94ncS4n7p89zkVGBW9rP7oIh5ioZ+3DT
08mNIsxA8n/LiSfGXrXguqj3Qx/5UXc5E8PaXDjv8HOg+w/5GZFgcmcHh5qgGyh2E4iVIdF3TWwi
z/AeN/tE4J0iWATd81kiH9OW7wzWqhipyEPIJ4YEA3c/iwDghkX1X1NnDr7QSk73y+DZ3uA5m2RY
gPmoW7IkYWFznWemgsXKufPPrX+6SoXzbUfvHsanzn6bVBbdSezo5ahhWw4+fHX1J1oG0RMcHBBb
qCAD8nSEKsSeGtP8jf6NlRn1hkYZkzKI97I7494I0IDtM4HYYIHqUvlFnFdj+uUe8Zj3YhrqUzkg
21LRbC2K2Li2Qiinp55eX3Y8sIkAeHx70dT522VXNr7WTOgZGj4nHs5CahMEo2ON+qPRFPbrxM67
8bw9wmP5vjsN32Y+mQZLj0SaMyGFwyIf0w0fBmE9n5Pp8PlfFyzFoig5Tiv3qfBUOCvUiqTfR1KO
cEOIr5dYCJguJjU+BrUgsfj9K3iFPW3jplnXclyveq3Ej4N7IArwVhhas5XPEM1E//hUzUFUu0Oa
+T3nqT375kuTR+6dfltxmQk/In0J6PMB4So6ZwIdYH2DN5cRMNSjgZMj6u1/J+i3H+mm9fBeW7Kg
mFWgaaS7/JmHgGyykmky3reS6jiEu10ft9LaPlSqtBXjVZfuV1Tczu8RvYHMdHSG/S+6rf5e3f6M
TXuFC2T5bx2kQX4yhvTA1S7IThoDqOnswXm3j8YMn6CPn85gOR2e5H1nz7jNJ0t971zr1NlMh+MJ
7ZYWL2ZXReqijLhVsFIqy7RY9y6jm3Myu0n9MN7/ps9075t8WxjozAsq/Ur3fvaglfYNzpWavh+n
VSN98tZK/H4+/wrCN8eC5EijIolXqBVZIIzWsp4cWtEYhDVaPS9UkTydVcbfB2KpZfJcCrzFfUYv
wJJeopzhRuL5nz1Wjn0WBliRfBIIrhzLq40kMQO42StKC+UWdF2OSGCVb6OFPqw3Y448joPDOhkr
k3k8Uh1Mjc7VSbM5wMKCns4/6IzM48lwXorRwf9MOGrazdPM+n5MnpMeup1IVc3vhavi5Ioqmczd
QlLxET90EVUTfze79/d4+nMy19f6YqmGIjp5m7LBkHLcnLfgbVPeFEl61faUOLukL3MetMNXmyLm
R6kN8gMfx4bxKqqx7LdCff77YjfDBgLpFHz+TyQ5GYiVFHvkbRLBIZRc05w1heJGqELOAgezZ+LP
tldGGerbg6n5/t11ecHEezkDZdsma38HDcQFGKU/ExV+pAtmiyXreZ/LKR3iFyL8f59yndYU3cLq
XKIqkFkgqie/Hyjt1JXEeQq67bFNPhZlDLQUPN3926moP4J6PTN+FFMFToYZAfr2cvZ7g7s1cj2A
4R/nD5Kj/ex61M2yJj1VGrXlkEIcTpvta9pSw7bw8bGTijhQyHuHJQUCi10Yf7IT7SOjn0rBwkPD
V1BqHWLInfHPzA/sJQR9qflTHxzo3kH6PgPAXNgYCegcSDXtAtP01FCIsRLYUpYMsYMhYFaf0WkA
kRCDBRgCMCH+JlEiChDQyoDEQYRYQFn/CgqK2gqVEgXjQ/qhIURSSsCkYSIiJP52VNpRRgDEixZE
YgIqyKSCkEEIaYUEGMIgyDBhEYoDIcslgRPzQCiBI/eDEtD5IKXYz+potCzBGDJ1ZDiDAYTGQcEx
eWwiQVhE01SH9xB1wcIIXgyKp0jz7bSIiQboxlFDQ0NMR/8Dywkpe6N4kIgJogq/zxBVPpIqf8I6
bAUdRG5Mk5aEsUUP9UQojCSI3Ip/WQDdAtcxRLAe6Q8P2Qn4LCmJEDEi1F4pSRQ65c6lgXLDWUqa
wp4JmahmIFGEngeFgQxFFIjCf6RD+QSjCjAJ9z/VxkWH/RKJnV6IcURkkV/Yon+xUNKGioPbHsiB
nAXRhSGRBBqCB/qf8HUCvZz+57USfcB/WPsSgIRPiSMIH1vu2vnPft+PKpMNGl0R5iFg+Ypcfjvb
KlDp3vBFXiVDf5Cyho2YFqPSfSfk83GZg7P4/u+OwfeSQkNJ9kKMvy5fZeYFDaYBb8eVc2Wwy/Ej
Q/ZMQ78Iffj9pfDR8X3bZDCm1uEpPh0mO/Ug7WOcPwlzXyWPsW4LNkuXnsibgvC+JYohS7AkmgDY
PYGBVacwe91L4F0btzT3zN61Fndvu6NB92S8HF+OeTtDSBjpZoipSjVj2OI8LYE2jBtotPrXE55v
4dccThw2mk0OipKU4usnQoUAcsgZTCpDpKN/Zd0rfToSemLXZNNLvheclUX/Sfl8RA8gQ30HRK4q
8UwPHjHpFstJxzj9twl/KcuCCYGeoqqrXSgJR7n3nwKA+4L3wZIrRtPv/l+03Ya1zj1pODZ5vRxL
UO4xw5/VMS2gyYwca2PhHHBK+3wRTZ9wPmp8EPYJPaaKlQJBEzd5dQ8egOo1eIkOLvSE/SeN6okD
xcj98m6pGoHPKgV5Ln03CuzjeguHGE0RwnLZpcWHuJ8tp8dGgYiT/P3OMgFZXqgGwVRQVWMAzw2a
SISRF9bGYp/Uetj14tInf/PiPbGPteIrk0Hq4/ybhWB/K6nhbFUigL9iGyxl69BCHOd8/OWLPfjG
jI4DbQv8muSEK+KNWK6oOChDZnexpd3p8h0Q8c8cIT9EK945eKL6/QXL4Hs8PM9fX3nn15qvH04x
8cFFVxfohilkRChXcUKCiiKqqqqv5Uhp8Bv8Avb4b2y+DYYijyWb8znKi4Hob5XTN8LHt/wOPeJi
MbyVqn3tyvnK7wo/osUi+efLaEURRI32fg1rA8+/v4e/6/uwOCUDPnucCXAKCqZdBg4boQ+9x0fG
FLdgQzcbHI+i1ec5Ssw5iBcww9NxhkMQmqrUiNd8HqpoHRiFlNhWTWkZaziSMQuMuLDwUihTquPZ
qoKoIRctG4uY8pz0ryrUHVDWmokNz2ld1JeFkzOMjc3UoHiseMY5CD5fKaQFARSRseCSmgBh1WeV
LcG5Z+ZAcL9onl4LbBFT2qou5LaHBCv9T+OwmwBGIIaeayYYYp9eGdSLReb7HnFn/R5PTzHkDeNu
XKsOA4yFMBwjDFB0RWel9MF+fs0v9jFlv76xH7affTJThZcrJKqqIEzqmke7PTWEByVRzQePZ4yc
UnLhpS7fQzfqkEHCbIZAOtEG2x4fgxXAdgaRGGddbh8fpdv92BywYSWA3pZo4XhNJiEiRJiF+Lmq
qoWoVEDzc2gJwbrlzWUfLgf6YZecPQnoh7U9pI0RqPwAd7t5dn4lGKh+CJJJUCoKlIkUCVKxSpUY
k/ctBIhD2/bsBJ37j/FZOfqr9vwFB+U+w28HCr76AoHOJzfHKo5ecbxf/K/4Qj009/XkFxOxQtOU
7JYtJLokkFJAnfcgvEQcGQUPegtKcShlo2zQJbHXqvtxycbZGCmOWTiekPCvYCeQaEEcV9r8w0K5
gKU/pUkln72k2nCaTUEzVBMU2LIUoCgoUsEVFG4txSlYXKFKChoEGiIhQxFAxDqgvSIhBfgSgPHp
1fpM4odSUYCSmMsSSQHzCIXvsomAnODyiYPQjqF6gDJC7kuKNPidH/jFVEDSAD5hMWwnjMTwFwOu
d49MOZes1MkJSywS2SUAz9kNghwaPiIFfrMk/VJaMPvfm/ajdvTW6klUU4Wm2o4HAd80H7qD4/bc
nTsSIHkwFRFkUUVQmiMBJRlBgijGAiDBBjEQREQYIiKIiIxE6ocGE2Mgj3aHBiJFHADuQ6EOkk7d
pFpaxFVFG2jnNovxJK4mW67EsVjs13ckNc0Gmm0Mn3Q4BD5OhVEDtQeFR9YfP3IcKjDoZc4jvLoQ
sGgO4Aw5YqHKo4JMZDWSSdfoBvEsmqC0k/Mj4OExah+Y2R50klkW2RGcTsmKSyMX0UqghODMoXWJ
pEoHtQ4DcKTmUd3Rp5dq+Z9Nkn7ZHEj2kSkXIZB6ksj93KIdGInMadh1nkrkJ00ahNiAcYvkxFHp
5x4UeqJlESWR9yNlkaxB19cZJJuTSG5z3P8Gc9Im6Is405hzJ8VdHTm60kk7iegOlLcDkJ0xEoXv
9YlKm8U0o7xWl96Hzwsw2K9/EcKO7EeUnuTzHk3SI6uSBJGfBw+SvgZVaa+v7WJy6jrn1g5blujz
gPWNp04/kclBTJpenm6EJs597e9hldypFV+RdWJtHDBrAhiiBUEHAqhl/sMs0Z9cfxLZVw3/lBHh
QftoziveeCFH5TEtHj7eEWHYQf0YgXrd8ltCErvm+o775eEtmrPOJ2fhfwo71V++c09BxDT9zOkW
lC2W3P0rX6XdMDScNv+bDP8YjbuXAdjBRMjTTUstmvwEYLZ410s12zl4rRa/VCqxg15Wk22gYoOS
/Q/GHgn5zsCErxSailCT+w5GePgLkZFdiU3AhKc3uNIm5M+uk+2eF1joXpA41fwaBVLyeDnnp6X7
ucpUdjSqPg6z5QJ6D4Le3nX7P1/fzuYTGOQjCPA7AnwgvsntQIFLCt1qggYrEqsMWuGg0uBEenH6
fbyH43UwH4qdPDCm6DjLUN9EiuuskmmpEcOwaThmYbK2rnwakhh7njbqcfdfIdYz6uojg3BR24mx
tas5ouyqVfu4s18ZMn0CkH00wYI/aK6EfAqXC7eU4PFo+6lQ9z9n4/7VyMbzKa6Qs5sEFJvFyXxw
h/lIOfD4G3rKJHm0XogvvOY5gk+JC0O6/sOTMQOx0y1dfU4UmDXuZqVDP/Mzt5jadzc5pWZHUTcb
EPenbAJBOblL5oEd7Af6jx8nUP0CvQ/L62+zD3j/sZvuawr6NB7fB06whEKu2tU4WMZXX92VhRN0
JYr8FeTRmtd3IcrSk6/1IiInLj7ODnd/gvNRzROi9PfY6y8/x8a1qeucHLhTTHX4XXhDUkygIUrH
6MEBQuUssQZFZZjInNGSrbTX71/T+Q2uDGx9YzZ+NaOAr82z1NH+aXb822X5HHaB6BKPt4QqGuIe
qBLcFmoBd87zV3PbencPfxQ6HJYv4/Hpmh1duGIFEFVlfUHA+ImdIz9V5fI87vJfV6lZdfIga4cS
UQKHTccqT82bKHmb3E+lbcrTJAE6NDjSEoAjxXAxkOdjdpN2GfXEk0PG6vGK8wQzvVpOYTwFGToA
TZnokUk+CAOyE4CgKnugZDL1eHDShtja7Zb34YjscHbAYChaOTk4DQbEjgoKNDY9xuJmEZRziZ94
P3EkciRAIdBLmdimTRU68+Rrkg9aiKrfk+B6h+n9X0m7acRqhE6pqkE6KqUB70rUUcyJRxOWEOcf
ZIOx7LJ8ADfr7Bv2pqfSHSKiKE7cw0oogcmQPfikWLowMy3tDKOYM4kPTzidAD1hDO8D3INnwAPf
22KrYlsp45hT4Frxx2vMNAboXkjlmTK3CmbuMEV2ULSeggUBoNGjQZF0WrJ1ripHreGrW2qVqUhz
QJwTTlA+IJFG0F0zzf6sUQXRCjBIZWaqoUlBvEzgHZwIzk5b9Wy6KmgapkN6qLJukR5tUxThwW5Z
TRDlVFrdzA6NKZLwaqSosjwHDuepXJ26CJot552ru1jnvGvf2xdVN4gpwSQFBowElBYQGChmuXeH
+vwvulTJ8qtVqu8qGu97F5TiPLu2VHQpCRgbR4g90hoAcFeLB2DmCYcYltcJGC8vy5JxX2wmNEz5
JYtNdF7dbY2a8yZMJtsQxNKmFGW1R9aD5HDAYMDwgepB8Tr3EVRYGoTzfMfXeoxExw8czRKGZA0G
W6aawppW2l0GGjRnU7BuC8ThjxQg9EYe9yPUHnNgf5EI1ORzvgtTkolAAdz+S68vFN/N1uKviQ7E
ESIiYXXJIzVTkEh5XBdMLTH7F2XgxSK5zSGymNUQSNDYYGjaTqwLBmjnm8iGb0tw0kuUxkPD5JdO
hDfGluaYVI5mQNHAQn8SjA/NFXRFVUqIqfiOVQ/piI0RgMGAHRWtE05IJgss1hhhP6MPIiTyGF/j
QSAaCH9hZ12jEJA/9/+7F7jKqfuKZMgmKqsilwaNap/voGvxXUdUNYQDyH+QJkP8BBTTP6CFTMbh
jk0mr1U4hKFamsWFn+HmSVRmFIJsMlKdExMVezUn+UZgZj+mDV0pt7JUB23k7U3A8shrBYMKGJdX
E70s0OkxhcKYn/7LcYUawoa8MNK6trRLqhbxdah5DgKp/qG+YTly2eenhkoikdJ1oYdhm8lYzCws
JvIZNMJqM5+Jovo3f9D+9qxLTdlzDW81TC6yRTLtQ5bZMO6mz/nF/lOhMiR/Y+z+Vn6/1fj+X7v2
fc4f6fXvn9P62h8Jjvz3O/I6p9tYE5ZN+Zw5znDnfW2qv+tc3flX/j+1ZZW4xoRlKMYzKcBmXfOl
KfmxtG2K/sXZY38HZD060r9dNlK3wVSp/U0AmPclynfwZjnvL5f1fVXt8+OHN83J4gFT5yKAyKwg
yCOcFUOgE/9frP/M+9/iO88XJM+XrGbe2SwH5bXJfeh48/8NFd2ooXPzZ0+iQLVpatLVoS1aWrba
EC2wltlthLbbbbbbVsLaS2221bbYFtIW2W2W22y2y2ltLbbbbbZbbbbbbbS2hatlttW222222y2k
tslttsLbbbbLbbQttttpbbbLbLaFtktoW22zrITfpJDlfILB4x7LJOAMa/F2ueI5xqulqxsqzPxS
7HgIcPBC7dQAPxOwHrexH3Hvu08W4Z+z38KzHOrMqpjomylycnF4xxAMmweRRt52E4ENOHEa01kk
q4b2JRJsy+t/1kfbhtNs+JrYtYsY6IhtBz89JkuKpgQPxjT1Ak0mihEJrdeeEknXxGRlBRDkr8D/
NdRCwzcG3tLubqCjj3OzpM8DS044ZTBu9JAp4AOPwbbNrWtiYq9ajxmv8oqbpxAvvL0jXm2wiEfp
Q88vl4fJnfQ/ogn0vW7fCr4St/x1a6adVvBWDmksEkZ9ALhuXnF0PWjSAiYQgbGkhnCqZWsGXwPN
CHoQ4cQ7ZTFSzvUSEXIOsUZKqIuRsxTuSC+M7nN5QhPjMEI/S4JTIv5/vtxVlOECEi+ekQulCEu+
WcRWTljabYS8UUCjIKQ472Hgk4TfjQhXo9mSdBnRBWMAqdmHRgdGGZZUDlDqhwwOgwxN2khnWrMY
MYKWkss4GHF1KBsTYlxwYqip3IUyG0J2V7MmdLCcJOBkA5ZWThAm1IJTQNiIZpksl00U1hiSlZvN
7wFJMYExJwwyUsIVFDhJ1YKSTEBtLhtoJYkNpYGgzNvW7xVcULbAOBBQ53ZOoh1Ssm0J0EDl7MnL
WHRA4aQcMMDSWGkJttAEtIpmLuQ2MJYLDN1DYjbEoYkttZxAjOHBtoDSQm0xqB1SApF0Mgsh2cZI
dBNMAyajTQc8/d83YpmeFkp0MVDO5JMoRfWoH1uOzlu4+/DpYGd3KkPN5ilyMQYWBRGQR0pivy6N
Z9q18eiTT3OmKj9nY+j5U3lcUzFR+EIH+U9XmQhMEAmxhU9iQqfzgbuDtv/Oh63GsNJ63OeTDixh
TGHFMMMBbvLfDMtkqLCfr4sQf+P/EoYgiQxRv9H/j5b/0f8X8mP9H+qNvhQ23AxdQbDxHo/H0esm
ETVzcBicetaoyAUEWLI0wgbbgbbSsxkuyz3tGH9kwFjMMwEJD+gkSwGCuBDEMAtCrGDP4iAz+Hrv
5600cX2kVPo12+uFm6NyJyXKePPJzzwLeafYyEDc1HthKYbuYA0M2bkLng2jOhD4aNiRq8Es4IS4
e/xLZ5xNs7tV2S5/t/Jg/Jg2bIspUOpNFoF9pRvopHYjPCaTPAuujKkLFqAIeKlUiRuu3aLYbo1s
j/i3d4VJlTm+XL3xdMHZmVR5QKGhgfeIYjOrhJZwZ7KkwGFugM/c++SRBqi0TBGds9MO1KTeIzXw
eWL9bgRTZeec9pPUBYJzjjuhyV6vB3wFB2CAnwDDw0TVkzid9OnLFdT9H+XFa2j0wcO08qeG9eg4
bZn0dOE1DZJ34nXbsEoWCMoTJMGxHBOdWZMlGh2SWzyZwJGizBIkGVbN3LVdjw9PUkiDdu5iTLV/
eDZG7QzbLM1kwevX+5PDJuvKcPWK7wssu9dY+o1hPO5UsxSoqpsx2XuwATvWjwCEuDwZK2rCIenL
Zw+vu3SaMUauZo90XwRg6klnw7XejJxnOVKembN0eGDJ02eHbVQeAo9uvCAd9K3S9frreKIlqUMr
swVQW9XZfZgNSMKRtcxYYIyXYYVIuRJSJQVEFJKWsSVZGKzFTK5m8Pn59OXDc292qzVl7LOWLB9z
JHb1MzFqU4atVk66wLPRJd+aYju3tDSl+Jy+1v0xcP0SpCUQmauWjbpXZgxXpo44UjfFrW6266/9
K00YM+WGt+WqM2G7Bd7NXLJiulmbV8NXLCSRBTpuzYc8xV9cC5eXwXwvnI8bxLRM9zex3ZZok1e8
Q+p0QbKINFHBs2Scmcgq6nztvWzZj03wXbM3yzYqLMmzFw5t1wFPCUELbxJEqFTjmeNZvWvjlB47
JJCA3E2RtJro8ISRMVQEs2WRZQat8c88YiSO3loatz2eFmDUxkNGDDw0W1YLsWCXFLSTVk6cqZM2
DZR5bt2BgWBkHUvp34aAuOOuVJRfOe+ahjANIRLy8RIkcMb4qA3evC8xbxElJKBLUssRrmwlJY4Y
GFsF7rrPDwsdDlicNmrli4cN2Ttm1Yu9FZunDJiusF463uNtYBuzOM68RSzaQyFyWWIoY1YlI4Xq
iKjjBUNIkQKxoEuWbAHUgJ7EMaOo11ST2PLFBjdjvoP3++zx0Leu7KVqrpU3ZtHDtixXMi8T/FEw
iSQL9oOzI0ooSkZAPxJiA+psAPSldlpRA9aS5SNsADnZQh4nkcD/s98/qgFDGNTUe9/EGLixmMaG
RAUqZDBIgcCpGjRT3WeV1KZMT6sWjdq4aO20TRq71cNHbl2zaPr9c2TNypswdGi6nl4YPLlTp01e
V1l127h5YJg2Ml3LhZZydPykmWa7JducNV8jz58vDBys7YLM3hypTZweGCylOWy7yuZMnpy8OmLF
wu6XM3jx22dPD1lldiwsYt2yomM8WbrX4bnh03UwWdNWq77ZJEGrvvtjBIOHCpkdkEpgRBnPIla4
EjAuIQsRjQcw5iJqONG72N0upiZMGDRw0eWbNg0bNWzZ2stuyLbvLgxaPTpF2Dy2bsGSXbLLNizN
kyXdNl2qyyU5dJ585O2azZd5fMjmj1qfOJyCcCFC/CJkhzh9IieATmEe1eHt8h/IFLyh1MKC7GUU
AEE1C0AcBEWTGEhc3XlclWCjt6Ldc7deixyDuImiCZe0L2629+gYIQUIKSB2SSpDlAUNJsBEtCSf
EB82sboBCWsYrdi1khvm4JZmJjii6m6WswqUcS2aKIRgaUMNsJcvVCs7MDQqCnSbodN2ptiXqZDM
QU0oYYnd0E6IlNIe7ladbzOtZkD+UZI14xmQF0szJxdShJD5ysdJ5eudxNcY4EkFauriAZt6dHBK
rpFOroCGXAug3MyKnDuNYGymGN5nN2p4jNkMqhi4GFbuSGqU240W1XWrTvmpBUCgUwiIdgjqlRBe
pQZShrsBVRWQXI0ZmymaopVd1CTijLWYCjGqxveJdgZYtJ4rUiWbMyjjiCmqnF1SISIVAplCFDKN
N2qVumZ3DQIELFxav+t0mPjdJVietUD+Y/l/c74pEu1TNc3OLGmuvETeisPlPSBjI4RTOKSIFdHf
8WV+q1tOzZo1YQOBsMgyB7hLGJ+6PvY+qz6zZdHxphaclLRYIiWVphEoKUMuKiaNCUOPMQ9YO5VE
DcrZROJDBBUDF1KrSOCDthBwhBBBxgkqbQePzbSdF8tbFHsTZjJ7B+9KgLF+6J4FPbgdxmx8/EZU
0GcHBsNG/VlW3WYh2EE1kF/hpp4IBzRkV3wQ+kuCJSeAw/f5L5g7U8iFN4VIvYPyhGe4w6kApTZ7
yhVEXu6HpIJph7EXfcs9J5Q1DjQSsZbeexmvZdqihoQ0onpZyWZmxzD+IaP3C+uYI6CWRJiYmAwG
muR/mviwoG2DBvRS+o6EJY6wdxhoNEpTFe1JWS3h/ZE2un1qYt38SdPtct+/D3dolRLxRTEibjyN
ihYse5BLjO/Ilam9NcoTRBObIxSs1KQIQrI5lRyBqghWF00OS5WGYyZCFR5VhXu3eFzCjB0Fr0sz
tMajwp8sPlZtyat28trMVtNyBNk4COyDJxLmU7MZW04ypM60qaq5aeUYNmjRu31Fw+utUVV6wblB
6TMSDkEwUyJwHVVJtLQWNR5UgMaGhoRcvTy4bMcVYvLNk7ZHp+8PPk7qfN4vQxiNtQNsyTLIjiDX
jJkMeRFCJSORHDR2i6I9U7ZC126pJC9bPZGLCL4Wzd2L5tlyNPtr7Gej3ZRtVNHlfZ7rImTtZJhZ
8Ot+ER8y80XeGTnZGU82+te/bC8fUBXGZMcKqIpNlFMSREy2QRy8L9S99o53urn1xOiW43v1x2cn
ruwfVQ2VjRLhYjjP1D7nbduznULc+PnlMPEnxGyOt8O3OXePtSsjm7kjMrjibheh5ba8AnrrmZyz
k+IUX3fS9ca/HfV6zx348+dZeS3Rfz15Hh/AXVq/M9jV7evE9DqHjz5keOdYWWPGWy3rF8fu/G9W
3V2safPfjLzvMfR48Ced14vmdv4WV2PO5twrTXWOPLddDR133YqtDhAgHoOCho7HQyfUIv27nx4W
76sklibQagmlUxjqFV6ytWO+sJZHp7WxqRtqjBWgZL7sJNwyS3wvE+nzeumzy1bPGyY1si+2oqSp
LMJjiJh8q9lXRGNIsoj3eWjFlS0ElVESl3TuQo04KtieT4Wc0za0iMXbFy8vTFTwu2e7Vl8qj3eG
TB8yT13lXz598YXl+WiG4ghtZdUupMIu6mriG+x6bWfTW0cABcMhKEgua3HnhgOKOlGK5I8msuqI
70VQ4DSwsIG+KAZ7NN4F1GlSN88IirXR6fGNycUQXdsjSJsi0vhnuaZvARgR70HPAYwgoRGk3lkM
tLJJHg4vRSwgIEY4k8E2umSzsdi/gsuOxniRSCLdCBOEht47MyKFSwxsbxxwNi4U0EgDBA2LEDbb
4nHHJJoqqiYqhyycJxQpB0YYuKlVOdDK8ZzIc0zE0mwyWgHWZobzYtxsxtU+i+3h8PDZinuV8Bna
TxswI8rOWbK54H7HxR6og7GLJISXHqc3tgy6OpHc5PGRvL2dat99WeiXR0tywt74sXyp8LqdPZg4
QIGgbtl4DzEiaFBRhTY80Eej8TJNl3iiPUh/iMiaIVXEbcYfRt/1PXtng93csRgFyDR9ti0aRB1J
3a8OXsvE9np5ZEr1DJkQ5YGLOREOJxKIQKXDGpiOIAkethudVwQLAHWnUoNBHUaOAC45LtCLQ0o0
JKTw+jtvkb1yvZvwpaXt9hRfyqG6+mrZm0wZvF84cPcxT5kTdo9OHalmDBZ5fa098nEueiHJaaLH
e43s59nOrUjs8SiJojxplTfDMgkdOmMe7FNuW7FSTNjpDp6bsIb9mjiJGC0fQ1gWkJeacKmj2aYn
XfeTTN4ZSzDNGpKLLMHc6ncs4GQdxh/WrHprlKU20Q1UOiWakN96CxKh0W6yWz4ejaKaER0vDxqJ
u5Yb0+jhojf31SXk2fKxcmKyRprUwDPBtSNMVpEwYXjhkxT7Xu3U/OjbQ+XblS84Y2N9HunX1s9N
tWmpDPqJ4aP5dGpy8Kens9nT7H1el1l2bFzn9GfqL0c2oR3HMNw47YO043knZIhtCUhJb9yEBQkd
ig1AEXAyoRS6lxeGCTix2l1QaDV2EHFgl4DYlG6gAZQsLsUhYGZonoZryM4ZgXE+IlfCOhsxMkli
xSjodjsZPlm3YPD2U3XXZsXbj5u2avDNo9MXc+K44yeSCURWJHmnesUPZHCAugAUOgF1IEBYHCUP
/ZC3+yq++pbgLnWUZnKdAQNxuIkSBy1F48cSopEkKUHFxyE5ClgcTdKZOWT7HspP6UmxdsZNmjdZ
q+5RS7FoxZvTU4Zv9Q5YNW6mD168tXs9k9mrhOXKzBZST5kkmKPNezVe8u9LtXg1Up7MWDlwyWey
zp21YmazdkWfxyYNWymhd01f30m6zF0xX88tmLJ0yyszeHTw6TBdi2XOmThqpN3wxdLOHkxe/vuy
bOnlg8PDp5dsWDRTVGnanTVbpm8tm7ZOHfeznzVfmqduHbtm5eXSnHHowcumz0xatJwJhoKaF5GL
DN+q4oZFjMtf7YEC8vFzFjKJZ28smzhomL3U5cPLBus9lOFHBwyavZd20UWdLME3dsmLeHwkfxRP
KI/eTqE86h5xNj0i8gmwA7z4hOETQCHlEuHQjZEXFeITxqHKHAjHJ+5XobM7AC4QcgnQA3IiSQDY
cwQ4cWa93RuanCes4ukquHWoXk10IP5RiJYqZnW876THGucRCSG1w0kQ0A3zuEgrM4xxw4IMZlmX
RxVVb4mFpMsggRDyxEO8Qssw4YRczkmDZrc5d7gzECOLsutDOKhtYYt6cEOhrLAtvEVikIkUsbVe
NiPpKSbAx0w846c4ibJ4iuV1QasUqqjHt1QVEvOTi1iypDCBBjU1oENrgzq5kULMQHL3Dsts0rCq
LhnL6VXGnUWQwldbGWseYaHiXknUty13SsoWXpnnYnFxne1mZd5EH7/q/L3/jkKFAJQg2nHvrtn8
LAiRyuH+ofrk2UIyrSiQNRAsoLtS3a+sBZ3NthSDSjfV6rgu6/K2bmHKtY+X+chJBDcKfJUioe+o
vKLEALuLqiAgIAbOV4RRNA2oP49V7g8hsUGQRgCoIZVdnqxzTV58eWd59d1b3gYXQR7xyOUZgXpT
lc+BENZvCaH0yEEBwB9p6hWT3mdxFRHwZE+JmMVjzZKgKooiLP0m5EUU1convd4yTj4hMcSa9Wzj
zC/CjuKzyxT31ZKfOEm78WL2YSdtmiMmskiDbKK6crwss3dOGNyJevc7fobZTweGDZZo/Fu7XeXh
dku9PE9/PWqsai1S1FpVV4wWmsc3ic9+vYRSAPAjPEHQIoo9K53uM7JNnozjJy0M86c+O3mr6dVx
vHWkJwpEdMJvoSVSMImKkZY5JDZ6WIubM2bCI9Mz2/lzY6YcpV9/DRlOU2arx7OHTyum6y7ZTBox
Ro7c94MGrty8u1360X4e3w9Mqbln8TAPS+ptB1kh9kRo9e+ZfQKjr0IqviPcfEYO2vfCsV2/ax4R
rsfDVfHzvu3XZs1mjRDRv7/D4eucpSzddus8uHTV29lhmwWY+QM4Irz7iEDEIdWJXsFHAyYPqxL9
YM4Ck6WblubdOFLjd6evWzGePZjc9tCuUZ5GdNmcX9K4asHatYdM2jB00a2XTd5S7NwweVmj8UdI
E/1IwdLvl7tGz5cPZmxMBIjjMkaGOm81kpWKMCoCnklXO2dF4mj6rD1S2P5tHzl4zcs8Fb5zQ0Iv
Ibk1kbIpBPqaVB+RuHpxgXfUjzveUETY4kDQvKFsjINTfCYlsS838CETAqcS/pQiSSsio4zMhggU
uIHWMfj0s+GT5U9lz2cOUnus92DN4aPUz6mihyNMAOUY7nqlzFIDPLX1KwkIBIRKPW4wK5j2Zs90
jA2RD39mDd43zNeFGK5u9ixbgGKMikR4jebjebDFTSjHInshCpaRU+unnpXbNnKzt43eHw+F3TKL
RVnhks+qi7wu9PC+tTwwfLVZjztT5a34j3r13ItG0PqmA3byXOYQWJYRB7zuM8loXAYWRKdMzdIw
mjEgwcKqBjGQsQR5tqRIYjDcZM4vspmvtWP0LOtnN1+npy93pZ6bvTBo5aOWbHFZi9m7yxdHu570
p2hVpQ3mfbA4ZeGlGOYMVE4gw72b8vQFliQ1wcFIwjis/Xt2+HSaT47aMHTrKOMWRSzp5xmOciey
l0X0RHpJywTvu7wyTFTh4XTx1JIg9lzJi6fDJ8GBZ6YtnDR7Kf/0yx9/nxjsMa9vaUeyqClFTG3R
ESsu2rEdkhRHODFGr4sycYza+ELL38Plp4d5TlXs9z3Y+DNvnHDgveKe5QsqimzI7dPZi7Uo7GDw
WdA2QWI0Jep02w4qER2GvLqtTAWYc9o6xnQ0zyWwiKCBpUmFi41ciX7Upa2BIRnlqmhByZFAwe9Q
1IjAT0I6XGZEfcWIFFJjGzz2uOGZZObbarLrPYpy6cOmTlmszbqU0U7DmERMRBKSBfYTh8/cXCTt
hlHCpYDQBoCDB9/Fn1Cio+YiiiQVPEh/RhoDjCIAcGj+QKriMDhLx4p2N4SN5EqdDgHAoMRC4ibF
x0JmyIi77vw6dMGrV27eWRk6aPDB21fcwXZrOecW705dPTl2s9MGT0zcMWrywbsXbBm668tGFMWy
yyYKMJudPBrr2mKmrZsvllbNs2btk6eGTtwmrB4YqctmztS7VdRibvCzJk5RH7zZPskpdg7cNXbp
u5V0rBm0bKaO12rNadLO3Kz2Uu3ZtnJdq2Z5Kfpydu2ambjXO2NRpVmbU93R0XbtGimTJm4PDRTF
mdKU5ZN0fSzZu5WeVzyu3MHl05Wcs2LJm0YOWi7B27bLPj4zdFKZuWOO7+lGp1DKT8kfk/vI5R9x
9D+ETwjSRSD7IbP6mR7I+iLo74nhE0BiHpKgQgd5Gnegd5E86Hcp2L18e+boGu88YR8YuYVicLnK
4XgKOUcs6rMVGalsNIE5IXs5a4iBbjmHGms51uLzMFaZnJDzFU1vV6HZaUuGQPbLqA5IiWQzbLJj
EQqiB3mZpwN0wmJsFwWlsxMk3OXizTShhRjRImwwwpyNZcMquNlFflI1qsZ4N4OSkQoqnp3ahwkq
DAxmVndRiqSWW9pby7gROjKe0qxhU6xhXg7EMC7G3ktL6BlxkEhQQ4tw7imnXzCHwTodSA1GmetX
WYPtNVG5AUoMUlXM0FiWNO2tsQDHx4AgaFaRq57UcOZfFMWGQHmI5RFASnGt/l45yCygOhejbFce
+udFWquyWw9+YGmuDB7FVymyuRFCS1XQ9I8aNLBNZBPAqtO0Wype02cEdQOzONfl99cSO0TUCcxm
PV2XY2ENRPpx05eBoXGpXozSUcSfmGg52bkMEn0ko95s4Jn1/SfV6X4REupFzHKhx9q0nbyzelme
Kq0vUtMnjkxdt33GabtmbV5asW5ZyydhlbSNs90Rrfd3QNRmZHLfF1lJSgsVFCpj6TjQzMgcHXBH
bnYTzvfk0JYWyzzizsAeAYJI80IezCJZEiTqQu8sIycumrRi2WWnS7Vv6wYyMZs+bm2mlTds9NnL
Ny4HkSJAmYn3iDoqZGhkIIcDwH52XFFVWXqcxCFDp2kAsOFyxcgklR8GPC/2s3rfXXsueztuipuH
mw/wnxtJIg+d0cM2GidvlsmZKmFd6MHbR9jKPLN8ucJqkza2Psid/ZyyZDqJSkmOAu4FQjPvNFLL
XshAcngwWGIxa2hAgKWJDhhxsZmRgMWMTQlG1GXIqcm7FUbrvo2mxS3L8fVoJ+G56TZsDfvRojp8
vfdvjobr9O2x3z3omT2cZN5IzVkmWZuu6VRstGmDlS7nHZvy3dJf32XcG8oTHlxkUpAmVqRLFjQ3
DiS6KZUYZBVcMfmXKbfeSsL0q/IjSDRlJEiKJk6HkpTnOJAkXRBaVIEKCEypAsV1jucTcG6hkVM2
OXapuurX03at812a+amEe/lqiPLljnJG7dZ6eWbb31aPDtGvbiHNeMbOMqyWvSkXDqJrMTXVCEro
OuxGxFHuLrZA8HBI6oZtYlcTNxlk4ruwwLh77F4+5BMWiORNxkFprng92Z1rd5e7tu8uWLw6el2D
Nv69mG1dB6GZmGQ1R6ryV6JLQl0SVZCdFEsoyAMZDkexgNCYwzF7iWmQl4o9JmhBySPjQdPQ9nwx
dPZuYrvvR7bPdSkJKioiVSt/diimlnSggZ6yYoago5Jznu3QuKTRA3bWHBF85V4bvm7iJxnbw1nS
zwzuuzwK2NMrN13epUoS3Fw8UoRLVKGhEqgK4cWoSZCDmW+bkYWaxfGBCGjtqYsXmLWHCVoYFiWr
zI0JGJfwpl5RyyXP76lSJE3qCTRorA0cNny8vhy9zRGz0u6bLqZu2q7dwygkDwUegSdyCTJ+sSPc
d+zeTGJmo5cl0eLrFr48E7BLQ2x1SsXDd5eV2cZvfXFs4RrlZ6XRNOmLpZ3DNs7ezpsGurhGjVhp
mtq9nLnB2tY67bvDaJy2cN1mThZm+xGLF5ZM10cPt/TZLkT2UgnoqRKkkUyIEU4yKDF//VV90ec4
S5wHGUaDaMCg4gRIFTI4ETUYmbGxYccOcChAqIXGh8vDdi8OmTyxduuumry6kzauW7wZsDtw0Ymb
VdTyxXPCmrdkzds2TNs4aPDx4xPDw7avDfhTF0s6aMF2CJ27aKYNGLFZNVlMnh5atHhdszZnDRZq
xPHjlk8PKeWWVVy+yJi9P5kzXcN3LRms8vLBd6eWhkZsXh5YtXlwyWaLs2jkwZMHpgkXGo8oYhQg
UNdYh5Qe/MxJmJUkaFxcJWsJ4ePPXKzRsyZsGbhmpZ4ZulPTtiu1UwWeE8NHlrE2Ysmjhi1/Hlku
2MltHSYNG7RZwxdJThq2ezNi2eE8KXbuGLViZsnDl4e3tu1fkjh08M8/Dy7cru2bh6knkTvIelDr
V4kfWHEdjgG0TAT1omsTnR+CJJNED78rAAnFAgktdKuOa7LjCLcrSdCMDZZH8V2s/fk8EF1wjTMp
mTpzJXTnrtoWWlDRw0mxVTB0atq4Ua4ZmVo2S+wppiTgilkI+OU80SyWTNyEOm2Z3M6ai4EVp6QZ
HvM4IN6kiLiSmi2FMvgmcudRPAMf4FKnVXXFh34zsq43Jh5qhVAVxbJRZLQHD00h7xIiXZHUyRQg
stZmCjVES9KdXdqWG5stGxheXS3dswLdVDLTOz1LFaCtABVlbKmZUyEs0yq0SuTV2qwXuMxoloEo
HrRG3jEREahBF4yiYmKkqrqFeBZanO6w7gdjh+sKGINfZOEN9fT9LS/KvI+wPz4+npy74z/AHpfE
sNJS6rVPTGiGVKKSsFSH+9phrHMhmRlIQEzQgtIBmJBHAUstXL6Ko4pstdVvwbtIA8C/yjxfg9KJ
KkYe2Hg4rp2ai2sY7uKIahdIkEsN7s5Pvkn5csUdaOWg/5FaSLhE5YyJy4Yy2Mk6fR4vEyhuikaq
bMmWueTpZX6fLE3+j7GTjkmDI1iIn1ZJo8MeJh6Wma+Hh8MIkkL2dLnTRo4ZNnC7E5Ox8j0GdjR0
OoSsdktOt5qnfiSBj7sqsCJsU2CD1LGWetYRghJbIPAeYNlghZD9NXGcqZ6aUrppWjx18MIRfLWT
FiBTIqwQEaY0Xibsowk2lmaF0ktpqrCGACntaIPMvIsk5YYebLWu8nk3bNGeeLpm9jEzTXIxFVBB
cbnOWGA5BrEdKxhowRCVYlmcmurPck3RdKY3z3atmWTO7NGcMmOvt1yq13h44x6rdr6PLtmTZ17M
5hJGKpE9l30ujFUi/skz8t3luzefTQp5eWr2duDBu9Bk593ddOw2dTnNqHecJIsRaSMmApLIcncw
dTG8LKMlYsowJQD2ZN3mJDBQclrv33cMWuTZnbJp5ZLI92Noenchk3bOnDAIhcTeRL8KH1IJfULY
/YY6PxWQPFkqavIjQIY75SUVXMssY1co7c0usRC0ZVnaZwu02IIgVHXXyKa7vgrNowdtffVqqV1W
jp7vLB9HPDlsaed3umuKu9GnCmHKZWyLTvTFo0cvcozNDeSOBrIvGUgavTHFBVUc2ebI9XsrYkXk
R6DzfDy2aYPZi2YFMHTFi4b7HC7dwwtCBUcQePMyL8xUmSMDEmULBKA8wMTQiaFMHl23XeHazZ22
ZKfkjhv42vVpxrzh4uvMkDwPi9ZQGOvmhEe6D0hgEhJYXB6kksWPQmjQjgc43M0cFnTg9DuLNoK9
n4OCHtJIgs8vK5us93kwdKdtXT4JbfzvEbFr8HFlIDmcsKvYZ8dAAtIcayLaQtiVqYkApcRDWyqO
MAsSC0B31jxXqiA6+EjK3DItA4CmBfvoZjiViRiQHDF12hIwHvVdTA1Mihooog4OTtPYKcZcz1Nw
2EdojGdaiBoXZIiRAPHGxcSHl5VNHtW1hBqxHEhoC2xGnno+T0zmHpY9ODtyp5s6ynpkydeWOtoa
KI4uHlRjEmPLHAoZmAxpu1KlWN5QefFEW648niZomUx+tEjqXJHajxm+aPTR1PtQZyI4ylMQQpTU
YnMlnYhvIx2MSxUUkJADflsI8MHbjJ2MmFdA4OT2IVdY8MwbPOEHQs0Xi4ec5P3JDlJFgdhDEWFQ
WB7UUgOg9NInIQB9vpKqPKRjANBiUJsOekepgGuSIdJFPaQADu0k0DYcYbvN780nAJ4QSpwOZ1GI
GRAeQIFxvGQREQInhryIqjiSKiLosin27z+eSNpE3XZM3DB9zVw/E1KmB2OYilwSPAgSHEjQsRKG
BAvFLDzv+PDYyauIlmrFi6duWTld5eFmbc6eGDh4bNVOnDNg5M0uu6XRm2bsFMWDlh+2SRBdwvLO
mLhiatHDpmu5enr1i3enlo9FJu2cs2TpyzeF2bR0/nTbXFJeHsvhk3ePHLV4cvClnhZs3cOsnbyv
uwU3M1zVmwemTRq7cN1mjVPaSehpl6ezwna6m7ldMmbdE8s13NOWzO91m7JmyHkixIgYAlRxrrqT
IECpGMCI51RmoTKhy9mj2auHhy0dGi7Js3YLv7GL0nDy0cNZsMCxpNg+weJX3FDSj30d4mtD3kfG
I5i2R94AHzrvEsPchS+BaUI4IdgmX0CJyAPI3cd3PpyZfCEFaslRyxhF3RUie8b2Eh7sOJbfNcnq
+wGKAkC2DP1mZOUGMpChRGZZVbbwUvj1MzVJwSdoMgMJFRAYIMYJZnXnIc5eLTaFjeenGiTQ7XXO
Zlqyq6pJqmkh2hk/QlEZhXJ4yyBoaCDRh45C6XiaS6dZR+ZEBirGFahbCst4DMuJBjMtbbALEpUm
5qpoBZCy9BnpoIyqh4NMNZQKyncAzCK1wLmXcQGjKp2gCygRoOtD6sVFBV1hZVnlbtnaIBwpcrH6
EiOCrYiStQrNNUXHF0VvIQh5iCA6plIVUOHbF01Q1078nTgrawtlfjaks873O0jGc2ET2FpgbIoJ
soyKiIWBhVt5P3uUui8lgzQWSKSZxEm8Lcl/Cr0iJ4ICV+XB22qhThYS6qx5yo+QSUA59+AORcyJ
yzPvqIQHIITnwHzIiFXEjiKmpxFJXwbPqw0iVJ7+LvLf3vJWJ9r4XaTp4TVnom1E2pMM3l8NHp2z
WYuW3682jA5XasGCzrzyjl049ahFyKhzA4juxdns9fHTJ0JwWls1HVJEA1IY4xZrlw1pSOpfcGWP
ShoRilCw55RNWK7CeZN1vXB6dJGjtl01Tys2LPKSRoIceo6Cj0Fg3ngeT4b4J2y6VaZmEgqnmMWs
O2GaIXtgswpnjjA8rOxkxOWnvwxbRiZFC4jCBgQHaE42wMRS4nHVdDHdoM1ry7EsUrcCA1iOpEyF
KDGRM1J2FqVMxipobih9gjxfuYd9tDYLbrcmqiR0bcUjvN58JLjAmESRESNBjU1xHO2MRSw6OZMU
c6+ySJlHPbx09LzAN23w8mGLM2WbMWnhi5cMnZ5PcWdyw0ZLGM5PnETxz17R7pl3UqVDi6lKqDPY
izG7CVZYUpoUKlJGJmXkSzoi7sy7AvdGZ27N2F0cN2OM2PK6/z5RgYvhnvPLW9V49JJzsjN2mrPB
977nvd42R7Lvd7LPLhy5XenpdsmrXeKkoqvFXrjCzCkXpIvOPem+fOKSOUY+dyvbbXF9GHObzA1c
LEN2DTi5k03mGKkkpHKaHDRz9GmZsxbYNt9WefPz4zZNJdtIyel3huxiXcvB5Sr640qJlQ36thjv
vrr1m1CjzXO/DKJ6VEu7db8evnJao4bM7zVU6aOImEwbL3Td7NPczGXJaYZjzEnqQiQTIbQciXFx
Q1KDGZoTMjM+2BkwXcLq6es66nqqNrWvXd87orFFWVeYyU8d1g9YtM+GXv06fyIMjG8k+FpT2PyM
DDEcb9I6FBzlI1kQMjeWNA2CYakTQ3D8mwcoKtKsyo/RrJKNt+D4M2ViuZcUpgZE5EC9tKjYF4Go
Q12kVXN7JiWpAwNxfvEKSMTcSKs2/u06xYMnpq+Hpd2s2Untpx1ZUlbZrXjNSNRNOZgm8+/IifRn
rfJ2CTVHBgRo15Cdn4Fww4iSMDAKEjPNcZYGY4uyvbMaeoYkzUuocPXeloVW2Pl5TBv4x3PKzlm1
fDh2YOk1enSzFkwNXlTYpk4YtDJm2bN3lkpg0aM3LwXezpguyaMFl2TVy4ZM2SWdLOHnzk8PCzhk
8OXDZoxeXby3UwdM2TN4eW7mJ06cLs27hk6aqbtk4eW7Jq0U0auHJm8+c2z3Ry3OHhw2eHT09M2R
gpq3bM3qY4tVrzy0YtT09MGbZo2XeFnD1PUzZNNVbtmqzhy3dtGTFTBixsXYs2H1x+uXhs1U8vLd
Z0xZSSIOHpt3vlxa1YW54x6bLOGzhUT2cLqeXbZsp0yYPDpg4Yvb23YOjF7GDdu3dNF3Cz1E6iZ9
qeytmzEzUyemrp0pdiyeWDJmdGyU/eYVKwctGrFswXYO+lcLuWCnDdgd8QMEfCvK3diBrVB9Qc4P
Yt1dQmtH6UnUT0jNGyTPyk/UjKedWHvnv8ZPV7ZYX+MNkSF+8Fe9WuLz7BQIQaq7xRXShkgDXdGM
VnVXxkky6GZMEIy9sJsgisycFw3TwBvHDBTBcQqypqoaTRliUCYCQSgE62Vk7T5FszB6hNrdsIGp
cDtLsXR8wVruHMsKDTKqmoxfA72+ZFyjJMQWKWKaZm5ErKWROA2FZygMqtAg1FB1eSHguMEF6pXf
cWoKgaWY1sA42lDcPIXZRigCkkYRTuoXc7xl/Hr0qm44v6EkaOTt9WCGFiC2JW0C6qgFimkob3yv
51HNIWcPCs6vaWjoOEEAqiGqiZRMiA8KnErNCBQuxC7dHNW6txaXEAiAYAoU4IySphbivWkxUO7K
acHRsDjTgxRHSlqq6JjZckxLEayfSaPJ8nGjau9uW0S27w4OmDN06em+DXVicMePiuWbw2aphE49
NmDNuyqJ7LZ70GzVY5YvDB1zzEqcsGbRy8MWTo+XlsdMmh6wcjb6kEIaa2u9deMYkZAimS/ItEWh
HI5FC827tnmQk9cTF8Vvyk0s6TcpIuEByIheRIKeJ7tXswedtGC7TFkty2uu6ODC7dm6ex2NnUyY
OhBk0YGUdjg/Sl1Juo7NxMbqieVZsouWEchlLycIrcbnzKFCJCG2iYkzQsyn5RJy14be/EM1qU7U
1KdvGuTZj7l3pg6avSesw6EPI79SrOKlUtZmXCqtPS3URUNNuPHpZm71whOVkws8GBnBU+DwTkNd
C/FQz3ct3LJ1M5nSTh8/O7TRlLsnBNmEKZPZs225ZOWz0pw7WPLxpp4Kjl208WxrKzTG+sk/rbeL
mafrf4Cnsjt27eWc4u+vpmwNjSSbze8eJiPH79rh5AnR5eQHDGxeX4G4arF2zdzBXulSThs3fdIm
Czczezpy1eHrRxA1MBwpMtq17HFYWaLMnFzUdvEUmghgqFVRNjUllk40LsrDy+q4Kb5kAebD4SkI
iYzH0BgwX0yevdwnu93jx8tHD2WaKXZZVWr39O2DfQbPcfgQq768nV+WFpypiR+m4VvpPrEZ9CXD
lm2q7Rk+GKnutl7uXOrVZxktZNXlf0iQKlbyZgMbbaCvJZlCZePB5UvFMypkDFncs+dC8eTTDq1E
R09OZ0TNUnFyGTHrzrO6pmb2kVmzJGY4gCig8liWMyJClC4yMhxBPLFivm39YsOa6WWmTg8PLrVg
uuxcvdy8rOpR5Nmd9eqb7MiZ3VDZTICNRddOOARcUHmOYjSiXl5kbszMcTJE8nDipAe8fa68dOrw
8NcDdZZw4ZPLy/aisPnPanCsIsray+EN0mGrqvT19J03qjudyjsX6a3pjMBZ5waFuNSBcGYzDzAo
pNwUIhcVJamkCeJQjoULOtXDtMlPTFd5dLsld+Vmj88TByukkh6kkQcmSlKp5tNWqYeGEiVBJ7Zr
NmTDtywXZPXzixXvu6emjy9OGpuswWblmCnssyauk2dNmTRgxddeHB4dOnCi56Yum6mi0k6N9+Hh
2s6eX74mj0pywcnpy6e6T24crZslLqbnSmjFqbUr00bPrE8NHTty0Ynr1ddwp4aQ6Rb32XeWGHh2
0YvLt5cLOXup02YLOWjVZwwcujRdsxbNWTZZmcOG86VMn3JJ7h+pJKhgNOF+uraGZM0HF44LzE0L
Fzma66hkXj3+7hm4Xe7to00s2TQ6csGb2We7Nm5eHo/VE8PfJTJq3cwLoupu5UxWeGTB5WYPd7Ls
1MFM2DV25ZPCmjN+ySLCB5gAfSvjRhvFuh0IeVXMHzCdQcInYIckE8xD2oBBEE7iHjtx8LqPdyfv
6SGc7i5YyjCTllT9o+sfaGf48UoL9sba8EhqWW8JPh5mZu5V7B3m9vR2et8Zw5gSSabTSBtJJgpc
sZm3BZAhyQ7uLoFmlcGvl5mYtPg2JUxTTQwbWJYhXk07IECPNhZRFCjNrDUYwaxpDCWNPFQJRi+H
F21OUsrAgalGdMPOQ6u0WKoSde3m1lVAlH2HZGobEBXFipAhiLOpl0z7F7AI1EUPOzk6Q+LkYiC4
iLbYU0ynLrcpRUkw7HG3VkxD6O+98N+aLHQ8jv7H8ePEVo64v09v9MXRHWX1tZ1nWh5n1COiCkQD
1CAoEVEkIDog4FAktubm3pa1vN/hMl8Pv05o1By5TC7msI9Y1yRk9TKou+CyeCCmdTJ0BBI2ccTV
gkiNl+dpN0jl2zeXZ5cdaNHOKr+NUYqiWpERq4XeH4Nl3h2u9/x3YuGjpyw4qlnly3PL2KbPZVRW
CrWqzTXRXqrZYdwRmjeIiQHE0QnS9h+skhdjRa1ji1lPDnvdK9er6L30Yezz5O1tmDmIkjtZ4XdM
HtEZeGZ2yXYLM2J6aN2yzVs6ZKYnHAmoEnxzpSfh0D6m5V4V1BLeXkE+YnsR2IGLpoxrd2xdLuOn
Ldm9JH75FSKKRKgMBWMCCRkBjERGAYeY7FPA+rmQ8M/fRm5eXsZvdk3cJO2L70mE+Hw7e7Js4YN1
mycnD8ER0ZzErg2N1nwyfIihAcvXddm6h1D5iS+lzS7Z47GYPYz4Oxo24hrpos3nG7VizYuHDJq5
ZssWOrJThxJPSzw3yybmb9sN12LZupw5ZNJy7brNGDp20YOExdt2zRTJgui3swcTWqws2vC55AHQ
FqwmEoTmuS4rWVYPolhNtnakVMwcVHbl4YMJI7d/RxJSlSp5PPk2fZPrp9GL4cNZDGJ6NXWK6132
x9Gj2eymLhu+matnwsss+B7/XxB3MeK8oSKlYctErE9SZaiqVYcpMVMgEmhpO12MEZMSbPXZWxJ+
xZg4ZeeMIg2MslOFlPTtlJOvC5s3cNHl2wXYuG7Ni5ZOiztkxfH0/onyk0eU9d2qxmVSqslsIxi7
HaBqZEkk4yNjcTqOo4cQLy82Y3WWZ7e092chfw/DBqO2CnTo1XYPDlvMyZcMPMKLTS7TIHrRkV6w
eYQbAoG9DkKKKKUzX6e7W6pwystpy2s9Hhy83bLvKvcvIGBWAxoYEh5qamgo8vJBUsRyhSJdmqNx
TOsN53pemWISC9+Grg7arsd+0RZk5w3bHhm9fSzy4bvZ96Ou/WPpk4dKdMY9O8aF5Eobi8vIkQiI
biBhDDQe9lFHvq7AdnBRxiMa2bcYGJXHHEuCRYzzniRH5ky8fiWeZFG6cPLBTdg1XepIE9SSTqYy
TM+Xs2bN1PT4aOGSzpsxdPXtwue6z3Zvd7NlOFm21lrYMXsyffInWzpuv26aPDJ6eXKzNk8NF3lg
6bs2Dz57aPSzRywaPDts223aaU4bt18PzSeXTB07dN2LFgop27WYuGbRZZduzZKbOGxo3aOVl11m
THK5mmbJKIPJQWFio9SSiD6kISskk9Tqep7PTo5WWWcqeGCmby8GBTlk7csXLVbTwswPS7lswbMW
WbXLUzYMWqembNw2eWzl20amTNu3cuWx0p2U5c6u2q+zF0s7csF3DZo8+d2Dw6auG6zy11qrPDp2
7PDVdyuscumjwxaSSzz+SapP1o+Ubn4pH0icQtDg/GQ/Wk9Ih931956pn73y9/X2NXdt2354lfOY
wnSljbK9BAxcqeKQvU7rSgqSTgCs9SyTEAhovH0NUxCKCq0CkjYW08DLxuJ1BGLqnqzcRMwTk2AS
hHAIIEVNldWnraZqWlSg5UGH18psnde9ErhFiQsTEmdsiMlXaisFg9qYWVeKE5r3ujVFFmOBnbWG
xtopymq2zDcWZQTZkxeWzqMlhMkZbJcG2rJu1qme32I0PYC7dKI/jsPxfNKBTUssast10nnxB7EY
rfSgF36D5fDc1uul7+nn0Mt0OqY0pWLpyyW5Zj5C40UwDVVU7qMiKmOx3eJ+KkxniyyzxIcuZJEF
BdGoFSC7IUiJEMQiBJZVRmK7YnU9n3Z4zngjQyjVKPL0m621e85qhYb3OEqqOdr9hHAQxKeQbUwZ
JNvu+x0uz8vsfa9jWFlB8EkkTqJUJINXb3km3V+GiTwuuyzWfa6fK7dqyJjDy8eP4v1HKkVGVmUZ
JUfbpoXF5zhITUuwLw1HjCEbbi+drsoUurg0jGmqmlpMZZGlpCCkmrl3C8mzV2z0zcMGLHVJJs5U
9mil2DJTRNmLN0zcrtF350fzo04+a37NlWerK6+6eng6avTfXhmwUYsnbBliT0U9OGF0jp21bqWe
nPOnTpwu90xe7XCnpy0HZmb995kRJijEzIuIDyJQvua/RVqrtH4vYlJM1WOHDH3smwDwYOBCM1g8
mBepyY0ZJREmCklvbV5aOmzME4aI1drs27l4YCPHlDQy10MiBqUDJ+Ol2olHxc15J2ks9J23XTiW
RBJEjAhB+Y6hmmaU1MCLYD6lCc5JqLxmZm7Bw92zPjw2e7Uiyx2wcPh5XYPT40ezQx3ZYMCCUzHJ
GzKRlXF7pWHGZgZEd2RO41Jjt4wXe1zPHFNGbXPZiy8I5bPHbpg2YnT0s2NGpm3YuumazVkyTdjI
9Neo84b+lrVHRxg8QZmZXb30NDLHBo1IrBghCx7jwPRzFBsyRu2yIVvMDE0GNRjcYGJTcSMhTU4g
k0bJqzYPSl1nlZ7PfHuuW3FVVplS95hLsG65g3bLvd2mTyeLKcLt806dqRqsxYOnDimLP49MG2r4
sr17nT04kfrJH82S/Fmt8PZHygkSvprcbx5YYwNDmOGOYggcY2BwI988WOGU1Y5j1+Tzk5euvMIC
M5LOpRiRLyYnAccjhXj1qVNDHy0GtqdFGSJmaGoggYhDpx/egH8H3VVRUQYxRskKDEksofzcLEVV
VV9pz9B2PQdwJ7invDk8x7w9lfCs08vq2avcaOHKmq8+zUMr2iLdu2goMzrWpNyk1L1CMyRBBLyR
KO+QGJYuSJUemLk0drtFPlq8unizpo0eWSl3h5ZNWrJ9n2XT90RJq0ZvZtEvnDwmyIcW9YlMiOoh
4DQkwf1pu50xcwT0qCTsVRAxDiWOZDgaGY4uHDyYpA213mpuJEhhw8+F30bszZm3aunKYPqdru+7
vsdu3Dlw2ec/GWTV0s/BGGeWe7t5cLmskiDhqpZm8M1NGhZg2YNnlqp581WT09KdNmbZ6TkWcPDF
ZmTrFT0rRdThm2cMk2YLLtVNWy7tuzXYKUus2YM2rVm3ZmLl9pMsuGLV5YY2bvPnswKZOnk2U9O2
yw8xJEi1qkDQvNCw8gVGNCBcMKVrEljneOMiZgOjHxcWHFxmSFCZffmRrmVXNRVRihmZNdcWrNi4
eVnDJ0prrZuwcLOHwCcuDtZ5U9PC7V4W9PZovyo2bOF2DJssu6bKdOGSnl6Usu5TJ/eR+pHhFk/W
j5TMTyghBeQT0qYghBPWAbV5RNPEHIksCPAkdhH05z59Z9MX0lxREu6g0T30R3mcPcG+IxQcnYuJ
m6LUF4pm3RVPHUOQaYZNUwd6YG3EIiWXFiwoGVeJMHBtq1vQk2GZnUSMhdeRoUXSBARMOsgw6bOw
kupkXlPRLQquNLPj0caS95pDvgdqxbjaDB8BwkVY2JcPgt8o4zMMYmsnIfBlLlTVwr48zcA4XsUx
1o3MTITMrNU3TWtmlWjd3O5+4q6OlYG8K84jR+uQ4ZY6pNHY+a7Q6fd6+e9afnHFfK8Wr7RvOIdx
z1QhKxAoEYEY0Rzozl7k4zmqr6kEEjRzI0Bh0nEnTIWkkTmWHHbtOZUsZ25Wgwc9qYLIjkwWR+O+
Df0ni9lmEgtj21wRi5cMs3l1EwezBmxbvPnZ22LM2yCn5lHb0I4mI76zPpBUBDwplDcyDjUGfUwK
mGFS65EQXc2doQZbspPtO/K9yYW8jNUQTImMO2chS7peIcvOsQVeSLIqJ4USdN+npgkxZS6pCY5o
jFF6gpE2VEKTw8S8ExqRIxqJNXD3cPHtdu6NtBncZDdc9nR2JntF+tksZ34HCzEwb7Z/GjXCKeGK
6OHC+6JHS7nl5dumzpMU52kaomNkldrYuXDbBFe7SmNmTnLXpqsk3UHlSxA00vNTMqUJGZ2QPEnw
Ms00qrtYvaGyuytv8PdqxqQ1bZfXBG7t7sfl5Rki+K603ZvifL3fDPJn6tpCsnhZTkDds8FNXlmp
s2dM2a7FuydfOa7o1XTZqcOUZ5eq9XL1DiFMqJmfDPd34k5xwaMFi8Hmq16EpB0M4lB0iAoYlHui
8gHcibBoB93yRGWJV+8kvJM54NnuMHoQe88GRmhamyzBs8F6J0KY6viz4TQbODk0Jl7nzLzEdnH4
iJcOeXhkGbO+7p2zyZvDFF3lZ7b3iaRPD02wTNvy2em2GC6fOney/z7snDV7sGzt5ZKfWJw8sXIs
aPzQieY9uodPjBHRiHj35JkVHU14b0zakHR2JHEKM4eZlxPYQ2gRMDQcFwilidqjG20pmLl2+Wix
u2YLPTpiPLy096ntW1WVVdZnqr94sfVlzVkpzbply5bs2rWbVVcMHTZmUvkXktVfkVG1QZrGhAuD
cWIGhAYuMiZgJkml3GYJ2+YirmePB1jrgzWc4HMKQdQFtjCj4GhcTHDEct2A4TAwK5GRTEi8S4ta
RcVaPZOXLy8rOmXCmR6aLn3o3eN+LeeN6qckU5IdGQdQ53Xr21saIgzmtyvQ8FH6PVACoiKiJ5CF
w26mUnv2E1N+hcXEitEI4bzcbjUepTw4Yvdg7fKzFNXtJP1tJJIjqkfn+MuuOlU3eH0eGWemSyn0
eV1MonlgxaLNWjBobNHy1bt3DFmu0aKenLR7/TF5JgmLg3N3K7twswWWOnSzZs2fRS7d5ZmizVds
yYruWbk8LttW67x46ZaFMlN2jFonDypozctWTV05eXlo1bLM2rh4OWzBkWeVm67iJus2UxYN3S6z
pgwdOW7g1drKbsUZNS7Zk2dF127l0yZrmrtT9S+LtbJumqM2zh3krNTh0yYOTJoxdPXr0xbuizd6
buHJkwTho8MTjjh1ESd9LrSzyweW0Tpm8NtrNVGixys3XbsHKeWuvDM2bO2zRk4D+pH9SMpHgR7F
9gntQueFR4FToAPMJuR4xOkTwpMw3R6icBzExiDdHKf2/7P6n/P/H3b735fU6IZfBTseXMQ4FBjw
c75Bx9Egh03wqbcbxVchXTrRLOgpP1HtoTGOWGu8C7fzkJH+Zq8Mez40bGhaxvmK0e8gk68EOLCB
hsgPUs1YscVcxyrToKBO+KeLx8howTvCncoZIEiSISDJCCEixRTkiIdgxSIQF0aNgOIruNeKC+BV
RLqi+hVbKAUhoEQg+OIAkRVCK+dUAb+VFUuIJjhJJLQMZJEFRCfDLV8qOU7f2MfsV/9bJv8G5ws4
3u6x6wEL4sYKf31Q98GS1eyh6lKdmx1uuqeF3ofXfWJ/qLxgnNoKEbErPxWSoklENWZDmohNfgpA
XFPp/ZqQpUzdKf+3T/X9MCFyfZMLeZtO+HNnoJUN057e+WOkmMTou05kTj7SjtlvmYnbs/wX7lDt
l2nom05DVqjiF7ZQjPUheyfRuf43snJaYfLb8dTuM6nenn8c9Wp0t6utU1bKn05VJv17mQVQ4K3n
54uT0pEXfdm6O+2EykZF99I5q2kZLxm4ih36vHYo/TcSz0fuAgIbOW2z4/ZK8/fC/IDWX28jOyGW
Uy4IhzBx/NjHyz+eZflks/ZEPf4ZX1e6Edc70OydMa4ae9ave7s7Pej5IdfglG/ba3RO2GHelIeP
i3NY8WCkWBVFpJ0YN2Wy9V7vYIqTXRR2nzca15ky5cdYJa+TVNSRBf01PLd9/Xt8awkYefwQh1fN
x+Iongo+2525TF7LiNvvc5yqzBBfFTFXUY5MGQhQhT2/Ces3zeYGPJXrHXmMLBCmIENj3Oy2pP8k
4nS0SEOnnjSlykWBrP8/btfGnNAqfQ9tHRPMJQxGmm1+rzHZ2/HifA6HT3LH3csfwiCSpGL4vdvw
tG6rFM/m7SVul1IGkhnC6OOHgPcL5NO96JUoiBnb3GpXVZwT0OYmX6NYbPRxLC3THBfvcYqHJmRK
ihFFSrhQo4vPYz1S0FXy6Y4w8JEQ7nDLnr3VB9VwLTQe7V/f7WljOJbOH3PYalw8wMgd+heurwMD
221JBSRuZ4DIkwJ+lf9ylVivFUC+mLOQTFQ8wXd0rWuUZWGKqhReijlMbn9XOeCd1DZ9qvGwfvnY
iPVNpRWL0CQcazLxypFhhP1uNG8zqT8khjiZt5Pju/pj6eq5xF2aDQR1C8BIJJT3gdTH4e++joB0
Oww/S/CmDkcir6ytN+/zHnjw0aQKys67iYHmiXsRA3KH8EGLF5m5VkUGUedCz06qh9SKqcz1G8yL
HIYYNGZ0EHsjv8dBC1LfwUGdsufWYBeDJlfmlmM/r4+xyrxdGAPJEHV5Pq5k8lDUUNwKlF9TMmdr
+XVwnPe965SrWzXti3huIJU5tdN4luandOQmcOuHVekTjjqT1FAcxA75nKQwgBaA7TBgZfJgvNz0
SJ3lF5Jelm6ixio1zlcO7NF3DmzwYhtqlmZiyRGhP7fEYEYZ0z7h+1fdnxEIHLZWS39nx1jdIvbC
saVkIPZjU5j3eCoJbIYW1xyU5zHleRG58klR24qh5nzSXDmzNMcKju3Xmj0RL46yuQsp1h5qRvBw
ijpYJ0pAwgtvf9qODnOqlfDdE5i3m46s56HPdi525PH5y4DMvl4COxzPr4OiPAcfneFJD+rXjJr+
3w5Occ++oLmjCePFDALvmCUxdWev8JBTRwdYwzQQVnAWu/UM1hxLBoPafSXnSpJVOJvGOAm7fO7c
JQJChlrm5Hj2QDJUQ+ZKfs3IujCq5nruyJKPizOFiT+YBPhugUxZZoz354375SLCXBVU+Kondc3M
SrrRUfruucVy4DEfZ67WIjjzjTbJ0KWMbCHTU0QMZ4kqsuJMR2xQvc9VVOvZuJBEoYG9iAYKw3rG
OPSmwX8hiqhRS9WgofI4D0wFUexgIK4HMo8C+ToQwehATdGdRm1bqqexJEzZf/c7PiSFMD/9KuRD
EUCigdMeEz/17LAzZVaTSGIiMMJMTUzv4NwYBY1mVebDbr6z2AmQ8Jtd8ssNbaKIbPvOoxPnMFEI
Kf0JEJ/SUFMKGJTEAKD9lCnx0D9X7f+V6uH2KMn63GSMjAISREVgxYgoKJIx2aSkIGrQkD4+7wPf
8r7w9+iz9QcczRrmR5jpNMvK3FRyR0aBENlOvWWLbleCGJj0iyEsIGFKMnpawRzBrOWFBeEytBUc
WAn95jmGQiGajRJQiEqshsBEGRRkSZqpLRnaYIhkTLG5If9KIYF7wiLXRZzTDma/0FAiRIfxiAf9
6ECiIQQphBIAH+0H6YYEEdUaIimA0jIolgCSkGAiQcDLPOk0jGLbYjGJEEYxGApFgiAkRFEUchCL
ESQk0FGUJighWT/vFYG2TCcFnEJlXgQlAFGQ0moDJgw0w3QGk6DJDaKkNMAQYKALCkKVCsCdCFWF
Z5MDWA0KAhIfpMsESHUdCBJYJMXKSREIGCshIoIyEUCBiRSAKdUJp0mhhVZCQuSKCgKMaQNXMkks
miyBjMGHQSBkgggmohsgJSkkFKlS0LQEf/nP7ni7EJDpaoRD3InUowwetuIRHzi2WiBg5YvujCJA
Yq4wQophINDB7AAjGEVBCxIrJEmJUBS3rAywQwjIsgwOTodOk3yJOEURigLEYycTiAgZDFRjEUVi
kFBkOCypBYxVVFIgiqMnABClUVRIoKCwWCiIybkhhCBmCkYsBgYM5khARCLCZYpDrAuSRypWCKMk
olipYIgERElkCYQQgp9cQMYiB3mqTBUMWmEIECCQgcNbKYQEmIdJAZEZEQEIyIyLEIDGEYxjCMWL
FiyMGJGLFIsYDEgxiRiCxjGMRBGLFixjFigsWIggkYxSLFiwWMYkYyMWMGLFjGLEGLFiMWMYxggx
kYggoxL+yMFLogSkmMAOYHYgb1sMhm1BFREEgCqsGCLCMgyDIMWIgiDCB0cTcCMDSQUREREgJBBQ
6LSoJAMm6ckDX5N75gYuDGMVGMkxw1GJ/2If9jeYG4h8x6jHj0HJ+oP/T1YCPjIgqV8dE/Yfhek1
cD0/lLfk4fk5XHDVJzV+A6KwhzgzFBf1AtiLIsIQIjAgCgLFWQFJALAGSUB4eD3NnzJwRgsViRYC
KwBWHSl6U4yzwYkVUGLFFYsil6EI4OIwDfTpzzeASYN3EBDDbli10kFAYudYhxLxKOU2w5YKH0Wn
L22wqoIhbQVF5eEgvaRCt0QzRC6STJEP/tsiB0EgaYoosUgsFgqrFBYrIyBIIRhDQIhnynKD0pCL
GAQYM5+hX3fRlpEzNW9gieBCDIj/A9sSF/dTSaA80V2RAKgBwOhsIJvVIi3iAUwUYBAKpAlCtBAB
AkVhBQIsQKoCEpUsz4cnwZT7/duQhDbWhT3D4xsQEwfvBH86sIHzHqBbCn8on+Qf6V0MVyzGFEKF
DBFB/qXGhLjdEpD6koSgLDFS4BEgQMUoDBFRdZZZNyPkSCPn9l4/dKY+b/CwJ+fjn74YHinviy00
aODi9wtwXaAMiSSEkkkk1n2GhE/w/nE94Mn/xwJ+hE+G4o0fwzzR0RjDN16yi/xiJp1ActtIXTSI
6GLP1pvxu3VC57kWZEckWj+FWUj8qkSaDZQNZZgkK4DR+irOpFMctP372qKBaoh4rinyKOGs/t0O
oEOEuqaOTJPWE0uxrjPmG3EcS6ygokO9tdAK5oGjcbro6UDPh6EqRISLITn4PmA9Pi8RLqe+QJED
544YBXve90WLH0uAfZf221BoWQITIDM+4D8WXxfQTB0x6/GCQUhaECqJBE/XT7pEsKfPuRXiFBIk
RIqwJAEiEBCKEEIggd+AoVEH1wShA80QKTIVhcLIjR4IhddTF9cAa7TwijZA3X9EKEIdc5yWmyHz
2KOX3O8jqDFDBDxWEu2S8bCQ91b1eQqh/QRTahP6oXf+PHBCpzX1D+vAzRJIPcSeoRiCjBBRBEPM
aMoAcIIa+LykB6/WJSuSAKYaWTlhPGwr+yBp8Q19YZUihv6adB5iQ+MsGOauGEiWF1uJDO8ZSQ1b
RDj9Jr60RVVT+U8MsrWp1Q1zzw0/aNONvfotuPFaxosG2L7D3id3oCwgiCfvTR+4yyGhARKIpoQP
xE2EKYGwiJPQBP0HIbBUWbb86AdWQdh3tvUPf+u44BDSEE1kHy+IS46fKJo0WEzb1MpPFliK7fXl
kwLWliXu5Wp6SM9Ya11g5+f7KyEjpFo/3WR0moK4RImq1DksYaTVipQJxBjk4qPMEHRurpr8qliK
ZKeos5duUkX4P0YOniO/Z0+NPfQCFksRxIlQD+wowQDIw1GO6AbY5eKnSy8anxFI7gMQSCkSjazM
DBV44Vhgks+9z9/8HFUSCgdEBDnOtpFCV+LpCNHlSNS6p1SoRTDE7IZ0eATtTMAsq3T/H/HItCR/
8scJHUaREY3UUmsRgh+owwS8JtCGX7kh+CD9ZBLNzEB+FPJ8wl1DXgOyI2FpdWkB1FQV1do0tAlY
KVn0p0ilKBQusheFm50OfKU+K0nf109QpDpn+EEtLHcWQbHgimJgtPyIstGzH4ZbKlCrUpXYPHxH
95+EiWBnxbFcPtdE19nBkYODEEN0/02X5c/kxsxhdJhfaAz5xArYKKmWG6kaMJDmfqHH1RRjP7Dh
DyCILP22ZMkQjI0l+JCNnEF432If25Jwin74V4rr1dfGktUa9Y/27IMACB/FskCQkJk/hPGa9+bs
P/40nMKfaJ1dOhQzV30mACUcgc0CKMJBw1gWVwE+1on9D9XIiO9UWSN1E8215yXn7/ZOHITlFwE5
3h4ERDi2YaoSc/M1vZz7AbK2gb+Vc22oiZbwTicw2LVVJUpChDdmJxEA4VCmgiiazTxMXj5R6ZIH
9UXemzEQg4feeQfolK5AISdMhCnrZS0WpCRBFkwc2wIb2WM8kANKcp1e/Vgng9HwMLsPsPMW84ZT
7DzkIEuXAjFhr1AeIPgj4SxnYJ5MOS2GpoEpAtmIVBZ/5F0cbKHBQY0aKJR6zZxfi8+gNqp6R1p9
cCSW6mYF0V9sB2dzIWQPLa4uD0CdaKwHWLoDanAMlTT9K0rj+4/0YXxlIdVLkWCnW2Crcpgb1A7y
ebmbAiKdO5YELSpktFFJCAnv2BWgxBChDNoSh+GInjHfq6M08sdfUB8ooqMwkhdPiaObCS9i7N9F
kyiWLqUGAoXrqiRjRWiWuL2MVSPf0Qku+/HGGan1fHgFwuEVkNkNm0rQbbS1OeWOiFNs0SGtODlm
NiONxsFmRcC+7F5n8uKUom5QXSZQkpCyI6jBsAWNSQoFHhVeUD3Pt+NOu3sEEOE6RgkGBDSEDkLF
DCCHVgdLAUsOtMxc7odSWzLgrQHuCJ7GwJfroJEQNajIgyJFT98EkBA7Rg2iHz+KPwf/1zywD/ID
7a/9xhpILBX/polKQpVpKylaGwbkVP/kXLVSsR4BKBOHnDrQqUhTxcoLZLpQ0YCAftmEEP+vlGjS
/ksWLQTwyQj/oElPqS4CZgdDCw+seH8GiXA/C5nHm9qeID8vwhQB8Bk+w9qA+ZE0wHEll3qrBSwh
aboCZRryg/PDG43DjxA4j7owizhBojIkjAKh8LVMgTYJ5z5SstfIeo4TuD6vxg8wYGP9yq6dM/Hx
6fwtrCBhnD9FwESvxgmP47v1SA+afjwA+tOwN/dX07eUxFhBk9RwAoiyPoXh7/qn2fyPwzQ0vOYY
qFJ2xqGmCfJ+xRSilFKKUU8+8f4ljpSjSl9LmG4003+wCklzSuoBPGOk5YSEikIj16BdqG/XSu9U
djYpN5Js6gSsU4RHgBO7IEskRQ5KChMdWIdawGrb+ZFLlIOO7fu4k+QgifUEQ854o+SwHRAHLf8H
3yKM/TzMeBP5TNs3NSdQ0T9Qk2IVgZD6j92u5VzXDbfQkRPziXTJiO3JKIBA/850697H9yq9Kq9/
Vo8LvNB4kLFbcKLQjIMktQm+4SdHfyahO084aHpp7ggPWKX4odg+4Qc4neE7wBDUnnBOTU4YK2DY
5gwCQfBlTYYlhGFygC7dUwiGA4QN51NlKU0TBMEwZJhhglKck2EGKJjQ5KU8HAGrK5r8mYZqJdKC
D6WAkMDq9JqjRXh5rU1+UL+3+NhDmIFQApQTAP3YklPm4O3BPuqqLuE1D/EfH5GjFRFxMX74m74k
pQL5rw8Uk7Rqg7XnEo94A9wAgwWAkVv5BSPgBXSmnxMj2dyOvtYmy36sYGyNCMrIYn41eL4arc5E
sYGCsBIEgmYs/Ywd5gmjblvMQLt9YQxjSGBjxelp/lA4Q5eqSHVz2qh4poCoZwbJVQPpb+FHMW5i
QNSFkr4SVq8FsNQmTbR6GeG1VjZwr4rPJyIdXcWOcn8RcoHocUoezF4IWwrpROkQMmGcQKWjFsrE
GDdshdADAipgUzFxHMEvdDAYolxIo6sVocw+PAXJBTg+8KSwmjEQoysBRqootRSkjBBgigkC2WhC
0Mi2L0BQJdGoIl4MAjQRrMVbBpUyj+qhsOFOQJApyW4hGEKyAr5QgWOwOOdEmrxp187jiGMWqoTE
OODzxhXRYpIEGOKYlFiLhYoPdF/MwPMvv10T3YHo8noWRYghDtUKELCwY6TjRHnQ0HvAeukOpxNI
ZxA06BaE9KCkSgNyr+ipA51wFrWjH3XFItfEjfbbxP7xfGJJi47SM2diqhM5t5DOaHqJsR+63Wb/
0tgZulFNfGgjqBCCptiuKQ6PYdj1LJGPaPOpXXRCDunyYEkt4ecDlrzlhpMuRNsoE3CUteBK0QTl
BAu/3xLs9R8FPHcopf3QUsWYdo9BtO8mtFNwnqO1XLES+BnI/hEaWPwI3sfkmmiXDNpld/CUupkn
6BCjIeQAw0LmCd5pL5iLIDfwY8iTvfu83ohVWt8declQhoPFfHxn0YEemxZNZNKRLKTrShjIsCDA
ZKgfXs0g9FpvevK99Qjco1YPqxo26cjTmqvZynFAOAnuWoGnkBKsCWtZkksavGtrzCR5YJ0JlgWF
5NTfsBM1qk/evlDmD+iDEjLU0S37n5J0ySWBhrNiuUEuU0qFySIpBgBz9NANoo3QU4O0coLruKRF
dKNLcHe73gN28ExRMhRDOUQqtYpGoCEFMlFIXHOJVFlgTQxcCQopNDAHDC4BKUi2hIQ19OIJ49Kc
BAXlfqld3wyfFYy5KfmOanvRDsNJSpu7xfvXZH+b+KjHCnk8Zx9rb4/LcsOPgBLFe74LHZCQxh6p
aHvBO8RYaTV4DwhljtkJwajweFQKYpmib4gXLkJJJEq77+nrQwQxH5Gv7BxfuXuEFolT3ZRpFgjn
lamQ4BNVD2wfHZWvgA4CDNAJoCOAxgipohgmIgGG7eFgrtIqGWDD8J1jYt5gSCFnhjxYJ0XeHeHF
oottZp5RPe5hDau89AhpoP3z5JLn0z6Vfp2IohcHMuJdOtSAHwFh+hAMhTGPzo6PZ5PfMBT39HsC
fdiTHy7sDctLzIQTSO2OBCYQWt5ArayRoVPro8tj2fT4dJmZJoPwc1RrRaxiYPtHC84gC6HwRhOL
ApMEfKe2ipJ93wWG9XI2b2uXpv91pQ2uXiZv3MuSolfHNBi05sfd0HWYkOk7zinxhA+SPHqDUb4S
iiH8INDY1l7Y3tbgLz4ffgQIeailaO2SgfSYh4QSY4GJin2FGwmZ03MB/dOlCbJ0E3UchgmSQYhJ
iK0pacMx0S9T/AShD6YKecBNAlEiMVxH3QHF+hyPPNcybtNaaMD35qDSTECaF1uq3sS8W96+RjeV
p1CGOwE9hpg1QOBoEcC4v/fN0yjMlqKWTMKI89WIbVCsPiNOtuiagMR3DDriri69y5aXUiakZEGC
YYpRhFSTGSOzAMIWskAg5XJKSlWkJWIOyEM1q1NaJ12hs0kbmSWEggUWUwgZGYESmFcPp9+bg/uY
H6vprpqp5K755j1aPbJMFH2xEkFIRP4yMIZS3n/KPqAPiWwmGZ9gYlbG+6J3ps0yUVzKr83fCX3T
yYBgfs74inkAPJSAKdqfYCP+Xblrh+RH7TiR2P1R+lCiX2JOMy5lKT90R3OfmUj0pNMIOiPY/dRv
7T9EMiHDuPmZgnjLGaOwieoSjJBCwfDD3LiXeYLBUb/+pR+DgoMyOvV9zwFrmZ39VNqqsH1HHVrU
oiZW1QNQPmTcHF8uDr8NeRGNid+FTKVB9HuG3uOSp85uc7jmCVTOF0Nu/GEEwBOtadM03M4htPij
CbyDW5bWKlUyNRj542kjD4mYELBI0d2RnyFeuCfYlz3/NsaTjov/zbQDrEkQevIBh6KCTRfRai3T
BO6Rl3BYoqQR8rLU7M8LgYK9KKWacuM5fRVjoBM/bwRu6f1Y61JtSa3utmRgog5/bfLRH/JIrpRO
3/FVeBU1BybHH/odf2d1fSYtB8F8S13Ao17sNTRpwyXsfWfpKaE5KcCKf7Bh6U6AmwSazOgv4yIV
A82AW2A9A0NQkDAilzaUWIzaoXMC9/U6kDfA/fEr+3/nDxltv/XzE569Z+qFDarXxK24/+4H0fkP
eKbEPuIarpc9yrlqzTbRqLQhCm3Pn/hb/vqmg8A/8TymjvfsfGW5x/5MDxnNhyBIPuECBHY9SgQQ
DT0gXy/w/1/5VWAfL7vyRiZsmfu6V6GFnK5/Ne+OST/WJJhjI/TwoSKLtsM4en4MhyuZliqbZ8OL
vF0brolCHxPCFLU3ON58BP+0Y8+Z/zs9X0Am/ado/5DfnSwfRoO8vaECTrEY1/u2GZ7qrNCfkr7s
hRsGAniQMUIwaMaTQ/SgUIRUSBm+EwAJ+IuPwEIEA+pPdw+jiDACBLa2xorW/UOP7bxobPnaVeEd
7aphGlrSpUFza8SuWiw0tvIQ9Nb5cxm6XAvjpELNjhoF0OKi8kENoxEwBNTUGASIlf3Vm7H9+UYN
CL4xg+L3xSFigyNgTlxCmSEHojuu9q7wcQsfDB7YhUYPQggg0F7qUu0yyW/tUeU0Q2y+NadEj7M0
xG7d1S93MrgSOJNkvJvM60Zeo4xNF0ytSPKayQx3VSuzs/8IXaN2a8bySkfVA3CS/i/7R+gSjbba
J8pDcm9b22UG27W6Ba1yNSqqyu7hIIQE3Q1icRbhMTDiCiggUQhH178OOTmHLe60nWdYXX3dScxg
YAb1ehz6tuZVEtwGAmAFutiLgQEIYGDUARwYosMHWdGiBdqBEohYNp4AD7ibhH8B1I/Qe60/P8C/
GH+YGCGCuAeU6WSH/HObjOJQ2ZTkNJEvIKrYtjZIQhYUH8SYFIP7yDUJRUNgGRc/IHyH+eQfFVFU
WLEYIirFFFNtWSPEmEGJGQZSTyftkjsqSNSPANLAoUDXQisTuYkocz1SF14RvDdr9oiUv6jCGsGk
Tfcdxe0TMmRUS91pFzF0JH7OMVdlVfVDSgOwxe4OiXCOiA4Ynv1IxefB1ElRQoklJyQwZCQ2CQAy
GHpPWSlKIlLLLGCI0q3qFoFhAxBtCXlemGmDarKqQTGpWexM02XNaBUpg5mEADREAoMITp50JEeC
bvmto0attvQgdzmENkOQOjKE+8YoabJayKUAawoExUYOAUKBqbgYK3uIaWCjihUCEBTQRgkSAggI
HoA7lIIkTiFsdYsw51RZqvoobQUK6EiOguJjO07QCMLEBGECMGLFYKRWCkRgoQYjFisIkSJAIrIs
gARENyBJGBYKBFGIMQiQFVVgKoCqE83aBgHQORRpSyjQUkJCQZQlCxRcEESiKsCAum2Y5fLkpvWt
6MtWs4YTEKwZPbQPEk9M0Raa7QKMyZCCVOqVWUeqqo7URBE5PKUOfElPHKCiJKiif5FodpKk9cp7
lJF3oGrZu3UpZYsssqisFzCLAyROEhsFkCCxgl2FowipscUaQCEU3mBDZr030sJCkqlqBc6NJBqq
JKVBVZMTzSUVVSaU0VJVFqiUcguyDCQ3lnumtjtiJxVoQPTOtFRrVUV1OIoxERVSqqpTk0hgcBME
hqj1GfmqUqoFAVYqo+bYUIHIyMQ7SdJ4wCH0nEqGcbpF56iSoLTseJJI4MjQxknGclHgzvJPk6hk
6BUdevnaRMFXpkmCI2R1s4yEgG9QjHbcsbBiw1IUGk2I7XYkClcYJs3QZCSRZBkkFkFBVigKMZFB
ViiixYLBSKCyCILGMiqAiLEZERYCowUFgpGCsVRQFBFVgiAooIiJFiIbh1ATzElzWNLXS97L3WLW
UkJN4ecV5eTRHuiPaZnUReYoYBsoUozS4vOEGJrHDk+ZOXZgqYfCHuZJGRjGRKIpUMBkrEzXPGwy
m9N0CC0eaEoDEPNsnLTGRIVhJdwTVzpCRCI8lIgrfErha0S3i5KGveBnE9E1owbW6KRgXGCGizDw
aBSaJTDBdF8XdwFnaQo1wpZLMGqizK4o6c9J0+EhSDSQ0LRIdu6UZb9YcQe+bL359VtzNNDUYcz0
lXlbVvxhT3QEkKhSqqkqobwlIR8TYaxOYypWMN0kTTJEhMwDFQMwYBQKpIQOi+qSsHn4Mk1oSciA
tAAjmsUB2sE1KHECBcGz0m9OlvIwZLukshsKIURp6UMSU84UcsnmQ26ZvTLNsRLQM1qI1mMH2EZB
Cy4FoVkIe8jLlChsoRSLFiCSRtH/6P4hJ8pASH2PDCFiisqymvBH0zKHykKUgSPqrsNBfQR4KQrU
xy7Llji/f+MuF4tyBiEqr3ySBwxf58aFN0AzIh+GK8EEsWKFoiQiMV7mdBhD+/lkdxBP9/68PbuW
Uv93tWR7vW6JG2gGSue4y3g0FMfNshGIZiDklyQoTNCAFrLJNL6+H+FZdwt5Xf7l3ho8MH+lipk9
QSD9rViupgmjhM2CmLhSnC7+7/g+WDpk1LFipUYkNMvG+kyKGREiUhtS9xF+V2dnFhj5iSQu1Tcx
ZG7Vwpy5asHLNwu4CQ7aN3aWcMlMTtqxasWbozatXbBgnhspZs0dOlMlN3hu4aNW7Vys1YNTVs4c
ruHC5+xHGTJqvm0eWDZ4WYu2bpZ0pgu2aN2bV2zibN2bF/JJ5XYtDNjOmTR7++zpg2TN28RPT2ZL
t2THts8r+G66xg4cPDZTJ5U6Xbt3jxyeHb06eXb/4ydKmLt+AecfbVqvL4WZO1NGzw1eymalmTtm
wYNF2yZrDI9FMD2eXLNu4XaNWazVswavLFuwZMWTpy3amjYumzdSl3LNi7bt1MnDN02ZsHt7cOmx
wtLtFmryuealmLlhm3YLNVOHh0yXYPDg771ZL3wdvSI2csHl03M2Lh08zpgwaMHR7ST1y8eL3aLP
Y8NFK5Vw9m22bZO3lw0dMDBi4duXl2p7FN1MDFqWLl2bZi2YPTZ4bujJqwUyS7Fw0PLFwps4HgcV
TQJz80kOqiNB0xOmCnGkR5eE2leE9PZgfDJ6YPRmwYvdTw+Hv78sVSJgs+SzVoxdKarrug/+XmJ1
07aOW6zHRs5eGTB8sHLFg0XcNX0ctVl27dswYNW7lc7Us3TR69atn9/3z+kxYmz+sf01UidvTpZN
J5fcLMtR4Y2kdc2nuFwwjj1qMC2QFWBKXnAUGRjiZHEyOIRHnM2HETmOKlTeRKF3MkTsRHkhh5vM
DIgEyRH1AKq6jMMKvkWbSxRqDeXb/3UNfVoOEhiPIo/kO+NiO59guIGjKg07aUvemEC6p8bKD54D
l9hmqugPiJEeErxQ96SBgh6RN6hrCMQ2RCnjE0FslLSiQiDBgz5boDEp2PeQY7ngdDmHtOpsXEhS
Ix2HkChEiExw86Gz87R+hy9Hk48lKklIKVEUKke76GIbjMhZuH8n7Iw1GJax73SchlshKKpi9Z3d
nGcCA8DEIEBZGIcpyHEWLgSMhJBRRU/OdrZ+uDkEieSIE+XYZrodjA6kjsPN/wnMpT8Hs0TRi/Os
4buF1maaPzLlDicTl0wavo7T7WJ+yIMXb/GjPff09MMmbl/65JCAKWNStajNmUNSZieJmFTHNJjR
HmJeQNXDdxEuwezZk1ZOGSy7wHyTF+M+kfgcDBe47GgINFO2vU2PIUAVZcYtBSUIyjYDSNlIUQpC
BRJIHAEsgKA2AYeoBovLoYMEcwEuJ9AnUh0IXE4wB7yJ6Ao2AkUq+gAbupR2Khj+g+lMXhEMvJag
921iSSRgNVYnuiYFuIHaDIEHxTWCt/FYQvAvyiaLXiCMYS3WTCi8LamKR/MGoJGIY23wx6jYEG5I
QhFiWKuZe1g5UNx0kbimTm4nvIEAwQgKh5/cwB6FOxTBMoAQjtBxJOrxHG+siZYT6/NTW9TYsjBS
lC0HbBiBfznbS82BxzXNmupCCYimX7iih9ItGKq7FFKODggLCLIgIVpOVdZEZBFFiDP4gwirICIp
Fn0RwpQLJbchRClCFRERioyJEFCCyLJCjCUoCWhELaEgSWlBaJJUKpBVTT2hRn0iHw+F47PoCxiV
SW22n5BDRklFNEhWayYqpdB8T7yUKw/C88I2WWLTR0iGQQ3IhPsCDAogKf87SAgMiwk+2xaiooig
zmaMheEn3xsqOaRpk2UkTitSPyrdp/a7tZoXxffweF5iR9072JJX6D1igiCqRVRIrohA6snCJaUJ
HCoSOyoqKwqwUiYEVJJI8UmBGnh/SksYIvQk6eYf4w3ADcd6IGkcgq7SglHw1VUEzq6/HT6GqoIA
PyREjLDAQqYZJxQNXNhDSCH/lwskkpCgRrIaRR60Yy8hZ09XOHOG95SxxXUTKQHQRKgaiCUKQqwg
gOldlw8U4/kAhRRF5gP/6rRJNAdCoZxcrBYfBvWS0u+j2UVUJ7HWXk8P46aKIpFxjQfe2SWlaFnS
JtSKpAxxmMXI/VD+QM4wPhgr6Wq03x4qwMq2jBZFESyUoQokr+5ISkPYSGeo/mPc5BMdGTKYZBY0
+ATwE/CFDsiAazSfoRsXkCPIcjqirpPgBwQOdQ5SKsDH5wnABegefD6IXfPr3GzhHQU7YYMQDYIg
qIMAFQgJGaYz/HClkLEo5AS3VMouWNBeCXiKh0AP5yEgCoigKLBIkA7BPHSkURnzRgSB6z1al6DM
lQqTvoGiA6wwOHOBcKMaSWAaAl3nMSlRSVhu9IUu7IrAKRZaRso1hGQzuqFELkHEqlihcW7lEXBJ
tburrdTnJASlYoBFb08DgoSOi9zYwOiBJFFHHGWb2mzLG5SG1hGcEn6JriYuFC8UY3OKKGmeEhDZ
M43wVHDaomIxbJMQSYwpw3SiIDIrDSsJ6oNAQJ31GHFHaO1khXAoZaITWC8gi+RDahpBf+5ZDyoi
+aRuBzkEsgHOpph6oCR8gpgFuP0ofwvYF5A50KAUtDuE5Ao6proALA9CwRiwWDagFPtRA6sfPdq0
ljnLNoRCMhBjBXvrerMPH60fGXh1HEfYZGB0Hw9ar4Tk6T6KtZhQyEOvXruuvLxLgA2jCSqpLH8f
pW7zVik7ha0qzM59gEThd0KQNTADExkpEM86XyxEMIIGr6vsnjiZDPfCPyEefiSP4BTEUkKcBSBI
xCgwSOGLBw4f2NnHKt3wuxiaNXwzYsVmTh+ZCuwkjk2I8ER2d9vEnBfN9R9zk6GzwWeDZBg/33+Z
dq9Gy7Rq7dMWbosxYuzl4auhTIsZGWUy4UeMTGMTI4/8FVV/3sN6kEjK68yPD0wxy9OF2D3eztgn
u0U+GzBT01dOGzp8fH0kkQZOGrlm4brPDhTN07asz5emxsX0qcvLJJzUVwcnIajlNZhhrNZXEqiB
NpkZqZvZZkuyXYNmrBZ6fPzku4Plkp7tHw3cs3jWbKas2THDJip4ZtVMny7ZtHbtYmXjEDlUmYII
xiRK1sPIlxoZnmItcxWkdPD0sydt3pZwpqssyZs2DBg9nThq6XYt2jdwZM3Dy6bN1j9aM7aukvfJ
wzTZ69XZt3by93pdk5eXDJxdWbpg0YunlkzYuWjFdTh6cN3Lys/iiqnRi6evXDPRkpuwcvDTTJ6c
uVjw3asmCzyzeGlN2Dh0yZqdO2zhZo8OOVbLKU3jBUzcs2TpTJLMl3Lhqu8rtXDFozTRkpuuwZtW
bRo7emjdmu9eu2qlMWGHbkweXDdTVu11u7YNnTpwxZLGzFwxdLGpysydsm7d0zMG8/KvrLR90BIn
6fbw1Ut02alPLw8NHh5Usmh7vD5Zs2DRk5buoykmCfPuOM5eXA3FrdIHKkYkQ95pPC9nzX+fDQaj
hOA5zmLDwnWeT8Amk39sKD/WSMiSHCSRPujP+trA9U+tUQDgq/tG+KAdzzDNTYZxsMDjseR4Hn9h
REGxknyPiMKF9hyaKNKxr5hmSTqZOrhk0bN2jJdyJ371DCv9tf3sLHvRhWFf7akyXsgjLSMOMwSz
D93nwHQ5JqDwdiijwehRCzAzZNFLMWD87wwe73Zsmrhd+DRicuAUz9cRB795UmQMiMUxMStZEiY9
+RYwIiVLrO2Lli5U3fRq8KXYqXfzIi7y2arsC5pNZ+Xo3v5+bacBV4Q+cTnVD5Ii+4KnjnQcCgnM
0Nr/Xt48yZzOInIeWHnEqVtWz1ZWepUm4YrG7VT8zB08f1UH7hVRFfe3eyyzhggN3wnLd5cnL+fw
+F3TptOIKkf2Ki0sDvohA6iJwkU8JD1kDyQyB2glMIoCpjdsaDO2NB48wNx0OJvJHAkMbHA6Eyzd
muuzdMXTVw3XfV0xbLKcxNWr7SeninN3vSCHhw/DUqdbarem1rZwM0qEhwinQoxTvAKAmCfAidYa
lsEC/pUfeRbJf+/TgNmEtiAoCUhcgEiOAq7TbVj+5CX6pZIshwIiJixlELf26+gun7skFh/N73qp
EwCspLK0QIgY25klEjVzpuiZQ0Kwwv3s2JqEPvWgR/4FCBvP9uSb6a7PcolSiJBNzMwyQzMESz/D
AvapTQEg9BBPcAP5/cLbvCnLCE+VEooixZBRgkPlQAyAOFAIb/+/rhQLKCkXuRXYiwOjBRtVJeyF
0AIlIyJJwkKwmqDSGLEhO5D0gYiMfMgwQDWgHuuPG4h5lfH/N4NuBaSHZC0Pjlt97daFk/Cuu6GH
WhSYEQOdpei1NMSUB9jY70DngOAQMIMjIx8E8dmnyVPkMb3phXbRnslyyJFwHUxcX4UA7d8xCtFS
WlugRDqE6IiXdxyxnzvT0dJNwP0o86cC8qzv+D9DOuWBA9spKCg0QpAKFRVRapaCVFSA9nCSSBfi
m+Bqn7DUUCGJxKLn7RffEzE904WIQYsYn1rwCJ31uEh8AjrRKkYhICeUWqOPnpDsgEhaV/QFDx3h
+mw26qAeZ7lZQQhCJ/VCU+gSPKc5zlkB/nJ8MkV8JAW8h73mA+Ji+8mALBLWsGyA2ntVAD+EFZFF
GQVEkEESRAAM/V48jSk7E0GNzoMIBf5WSE1YJI6CMK5JGYD+7ITLASjUJKkyWsIEc/5ZNAhTU+rI
Q8wIGAKxBtPfH329vfPdqQlqkiLUGMQz+f8f5ZbbV8vz/w0dT8nu/T+Pxh7+tcL8Nyb8zlGEX7CC
ByQPzHEVUqh8RBDsUoin2x6HgfJgcHBr5pteJUfmsEyeC2d0LlbC9h1C4MBSGZEF/2dFqclibSRW
6+1qRJveJNXfnzyjGUkZfL7H1iogW6i51EOg/AfgIXDM7TqKO0zLl+8TWZGrIyaNmyx+d+DFkdMF
jFo6dMFM8MpIggF5IwIQeVMCRFCUKUvkt1MK+MMDC0WEUoYNGrdS7sss5Zs3S7Fqswcsy66zps0c
qZP+sHUmrc8OnDVZwzYt1PDFi7asXK7FmdtnbRg7UlPDIxUsztzardM2a7dk5XYuGzhXCs2DQyYu
VjtZk3WUyU4bcK7YO2j+kE3myslNTN22eHZwxZuWLVoyasGLJm2SynTtqu6Yp9iOmzy8uGbhqpqy
bPCmrB+n+7Fm9M3bRnnUzHkiEImJUmaGJePMi8rVjIyEsONCMbyrl0pkZZeHTR4eHblTlmwYPtkM
XbJ5auU9LPCZsG7ymbpdy0ePF3LdZm/3oenDFi9OTpqyYse8mDlhhd6WZM112ry8MGjRw1ezJyux
ctmDTRgt6YuDB4ZN+FaGzRups2du2rVSmax4OOs1S01aM2btZ22ZsXDJw2ZPXrtm9M12705dLO27
wmjNi5Yt2bI3U03V4WbmjRmswU0WbFMVkYPTF5bOm7ds5cOGTZw4Zrrl2CopeckEcRMAwLyIXoJu
Q4oqHU8PPu5Ev4Hq30DGEqZFDnt7Le709MC74fD4ZrPZZ3xYkKnEe+xkKVOBxJkDEPNAbI5mgGyw
T6lHlT74qDCEIgnxpZIpGkSJQEixAwT95Sq3ZbjKPUyB8xR/vGA6nEgSJCEYCVxjD6EzgcBuLQ5D
iIcRgc3N1HWRInU6FSB0IkSR5ki86kygmZMgKTL74lxq/M6XcvzPDZ5WYPRqu0eWrV23euONGK98
M3fezNoww3XcOGzNis7WeV3Rwyenl0wenhTlTN04U1eWz/mz/yJLCpbF5ZNnyXaopkdNnBgWXOWL
pg8vTw6MHLc6bLPl/CSPZq4cu3T5+cmz+KH8WlqhafMpa8sIqICWkFEERo0KjJFJVSimEqUbcJNA
2NZR6vT6vLxnMGkeg1Gg84QiTnWo5riBcVJmRxGMDcPMChQxOYxzGKEmj7WrNRu3bz3kn4RPhUmL
N9jpo/O6elmz6vTy8NTZ5e6zl5+9OiNPTj0Qn6UJnSq5JlpFBuOTMSt1tCpiwPnR4BbTjOU0HvyD
ecBTgbHAU4EiJIYwIkjt2cZFDkRLRLtXKz7ImC7FZs5eHHGjhy8u2DNEbzPOjYaDQbjcajcbT1I8
nqRbdS1350MuUtWKBcGLMSbB+0/ZuQmiEDZ9o3ghzz+AYRDME/GfL57khPqoRqqQJJ8xiWAbyveL
X8oMsQW2z8bWYTNaNFGshmGcbNhvSfv5AzIuOcd4LFkn4QiJ+8UpqSWCGqXs3E6GFkYzbREIT8v0
ImBDDG94Q4OQ9pimGQBdTAU9wUpArlOVR7yp8Giyjd5xMEOir2UfXVDe8VNA0wirDAMZUTGlslzC
EDBjmFxPwkP2gDZrTLKTZlkJWAYAYGJCgdZ+WSI8MUd/trivXJ5EKP1mBUhnOQyHegg+/FPYjxK0
ImCtBxifIIT2CTR1Gko4iJ6mSaERUFE9CUMcT6HgZiLFR70KSDEiyDsH0A0/6CIcR7ic5SpFCwIl
ChSwSglChSwSglChSgCEoRKFClglBLKUsGwSyQEIFkBKAMJYlIyglAGEsglBolBolBolBoJQoUoD
CyJYCSkSg2JQaJZSlBsShGBZEsIkKAlIyyxJSkZYJYNIlIywSkZYlglBKRLBKDYlBolBsSkZYJQb
EoNiWDYJQbEsEoJQELIllKUjLEoNiWUpQbEoNiUGypSwbBKRlBLASWCUgyUBKDYlAYUEoNiUGxKR
liUGxLIhQSg2JQbEsEoJQaJSMsEpGUohSyIUiUgyWRLIhZWUoNEsGf7DcDoeo+s9wzWH5b6rRfRq
4XkhgwYSs1bMQTUHMkKwpm8KGrNMyIUE8X12BfEHEqICbWG1RhLrkpCAblBh5IFnQbrKIfeJoTU2
djGmEG60Sm9n6aepJKcpFR8H2AW9h4QFCdG8OC3+kNzibcMR2Y6VHVWvgKdKf7R8//g/2mNzEIfq
lB9koJ9sUKiG6qTaqEeV5a28C0BjGQNQQ/wLVREMOKkfHMhgpFgcRAM4lk+mJAiyi6Q7hAzDAXPy
OBiXAp2qM9/ddQsGsQHGPuEPeI1rEPoDqT+uJIm8RPjO+yABFSJBkRcqoAIMZFZEIwFFCAUYMVIS
MkJexgiSiBKKIsisSoAWY0ZBBiQJiVhASVEVolWlBJCQZFIJJFJgKAMwthFZClNCmL40H2bBOaeQ
/MWbB+YiUdZQf9SfUcgUBhDm5pYtKtLGV8GvCf+HAzM35ThF4hOjmQIJ8YnPzocwo1q/MoaAsS40
UQ5iPPZojV0Fve1y9+of41McPsG7eMSWvBiTTCDIUvMP0ywJpho+3kwywAsqyXssWl7NVDVGjNYU
xETKGZZIcdS9BM443I6EQ4ChCrGFm8CFmBTdlnFqYAWYkMYo0vEEkmGNm9pTDSVtOPLgzbssjSwQ
OM1QwmBrDIFGVDOSwDBImGjCWMFdyQwMgLDxYUYAaiEnVNprrLEKE2DnJhTHbMgjGUDOhuWMGAoa
kgiG4lpa0ZVg7zA5rmjSbNpQwMAGUd2gkc0UMyOQMGBULzaiDhw4gLIdWbYYyCaLzyWBpAiqayZj
h/7//jTtWYP26fu/b/c3Okn6IydHh+3ppalom6+oLmyGyONXOf/OqTaxDQJU7zLjTAz2HJp3jkUu
ECshE6TWYbxsIZAd2u45lB21egbxHQ0ay2UDsSIAVnFskUkUfblwgsimz/GHAqhzEdpv4E7EMImg
gzflVOnbyu9LsD3YJup8MGLFipy+GLBo8N3hd/1JH+GSSUiRJw1cMmr72j+jwwcEj+UKkBYZqkH4
suN5mSzvZmutzyLEypMcXmRu6Mmbdu1U7ctGC7Jo2YMmD+j2zcN27Js6csGbywU5Wb7+Ed5LO1+2
ryxeGDN5eWjlZypg3YO3l334ZOHK7dq6WeXTw8NGql3Lo4eHho8suHK3MTF0xZMVM05ZMWTNgxU3
aNnbz51aOGjJqdO3a+7Vqv01Yu12JdiszLPDAyddbs0asWblu1cM1OlFOH0RO3XWTR2/lh/RE/zp
7vinhKctVsKs9nb2T4UzYPC7J6YNjQ1atXsO2Tc9evS7tq2XYNXb3XXUtxj4XWaqi2HRowYum7ts
+5Pli2cMnLR0xbNGLbRWbVOmr7UkkMHT2asXs8OHC74kKiJOoesmS3lq3eV2LyxctmbQ5Mmb6yQJ
w0aO09Mmb0zicLLLuW7JTp5WXbFN2rdgwZLu3h6RDlizYrlMXlHSnazB0yZrHCzdkyYM1mzlo8OF
lOFOlNWym7BKXbuVNVOG7RgU7dOjVRmZBmYdSTfHLqaKSEJyXOwVRC0QF7IJSOE00FQyMLYoSi4J
RzKrEKS4ySGBLJlWj+lhBSLQEpjbFkhL7JXTfJJE1gZOIBgqfmlapCDce5gNxUwpGTBaJGwkyhaI
syhhdD4U+18Po3YPdgpk+j5ffEpq+19jQ3ezJszbuHlGDBwpi8sn2NGDto2U9Q+NX/DFPeP8r+WO
XgPQFEMz1wuQS3uYksmEyC5+Ff45IhSP8iEFECyIIedD2CbufoKMvM6nQ5EDwN5AcSInU9gx7fKo
8mVKFhNAidg8DuXmBYiVInkYYeDJ6MlniEy3RHObttJIgyYbrPD00YFmKm705brs2zB4UxasVPKy
lnlu/Q4eTw2bs2jV0ZFOTNdd2dPDpu5n6FVUpL2kxaLzV5WauWbdsp2eXbNi4WarnTdy3bsm66YN
GD3fndGrZw3cN2Ls3jaMDIrXYhsDYQsp3ye7QgdKUijUokiMYAKAiDDElWDIW2OFQgFoFReDbTYU
SIhhBSoKRONZEGbh7M9N50fCyj4WeVdrvK7Bk4aukSO1REHhu9mjd4ez4fbH4qs7eVhu6c0+8vJa
fJSyVdUqVZnKeLLDd7MTtNpchmoZCbw4DYPzqPzA/otaPDAP9UWJ+/RQvpOMDK3ihDYVHjAQBhWC
aQrw1U5nsCHHN6n2xK2wc0SSmGVtNXExMcsKwWrSCkBWkLNAAUghYGvd24ZGIW1iQ7wOp94+LQTw
dGTMMMljKOHZ/OhgaIC6DFA3Rc4mlqOEKLFHhKLz9VNcn0053qCmiJsHehZ03Q1BIYcKCIfR1A9Y
iqdTees5HrHnc9BPE8zzJED0LFBw4qMRFLEj0PQgPPQYJExS4oOMD2/RUMyxYoaTh8L1jzvAj4pI
+AXaj/IuC8InQLiQEdKKodgJgB5FOW201EyiFWt3XaAkMCi1XbqMuOrqA1afhhaB31JweVGnw/PX
9QOJvsDQwPG0QIQKDUCQEn6ZexB6BN3r/q+h8yauQT2k59Qm99xHh49hpxE7iIaAI4fgo9vt/lsX
jVUocac3urbZorzF21tdFDIQlSjaW/w0n4zCn99Cn6rpmZTSYyP+C8vV9EiMGfJ8/CZHx8f8vyK6
3WOoJwJQcWimumSDx2xqqqqqq5FTKYZVjjjD0LODrIh1TtIvhIIvpmE9NNI1AIbznHaClQiGlHqG
LRBlFClSKo56haRjEXoWhMyfVHB/+blRV+UV3YpkwEhTtlA62L/DeJwieVj9YfKeMyYQPHNE5fEU
locQpEKJs7AKDkOEAo3L8AlAZE3QkCKHve4cYed9BPesaj2d76UKP6x/Znlgp6pv2GeNwJ49jaTN
CFrgkAh+igZkkqiIwV2ks0YRnk0YgU5zj/jDng6Q7YQ2cP5Ood5FDue4tVLiGd2tWZDwO5g6wYbL
VyQGxnCkYwXV6KiYFlSYMxdrmYJzVBzqMkM44SNGVZ2Q0KgqVSpHO2t8uc9+ZT+EZ0V43iROyMuW
ze3iAfWEc6I6w4HPTT7lEqAvQsKCNxg0cbRkmlCgtVChIaZ95XATuS5JeSQgpG4bHJAh7zJQqLKB
qhnWLmgJ58zkJ8HUrFTGmleKUvFmBlIixGZQjBN0pFruzssAKFWBIcG4iWU3AYGlJIeEhECgJEoI
INLcSFB046E+eO5L5UVSnWzSkDxzruF9BG81hzlIU1hiSkAIPMwIAgWEVEJ4YBQzbRLSMMNxHGhQ
aOEIYzqLCK7DD0P6NmCgy9sQ2RzwjoUoPJzKPRPjtAuivUhGx+kSSxHgz1JFQIiDlWu3fxq/HDON
mBoWVPbp2sSyX2g4iFjeOgxS0qEgSvcUkqWCjI0EgaGkFjEYXpyY2ceO9iM7retTCCqOFmiUczNt
C8y1Z6zbBhqMIoYzbPMiXVjZTAho7WuOHxvx5b0cuo4kSOtangQlDH2kviEaYI5XddiFxhRms9NH
aoysN2gOdZuBqqkkz5sEeLNYiCbavBSO9NrzajVjDhfiizdzhzNcjhV7eYYuJFJ35szuUXNdBMAU
A8yHQVWkScUvG6OtbHvgvlSSdDBUx2KuV0MXezgahxGWJo6wMKOaUo2XW3kpvvaNqYY8dRYVnU0C
ObKS6vtJCKaoTF2YoGQ+RMDLVDRvnjvzxXeI662NHVMXOwRBKZSpLcrQvvFqW8Ksu3y55zxcVInM
ymleiLHl9UI5I8tDdc8xujvBhd2i/PnJJ2HGczy6UogtlD86cK6E1yo66qhaHyyYJtekElSD8JV8
VR3zKuuSJc+JJK8cKEskdsBDWDsHc6QjvKEYOHjMXqTdAWMJpUhY0jLGuA121vILpPbr1OnpoU1Y
LKXcgY2NptpoY2NQQQFBFenryEmArEVSD5eAbdIXC/jw/N50/uXBT8fzzi9xCuSGEXOM3g9d56s9
IS4OrVhSIICInUpwSEOpxafa9fXnk+HwXVGmmRo4dmA4OQmTOJK+crTXAaeOIAvNEmMj3dQ8b1Cc
mIZ8+bLGWYgjKmXLHAx/IbRxG8kGhiyxOvpDbOoxUWb3EPgwVs0U0xOPHVHCLRPnExRyqeBhhkO4
4Zs6ZeGcKzlkDGG/plVMOpwUOaNhyxQ0YC42MUcaW31awJaaQYDfggJKLDGqJ44pYprrHORcq7k5
wlC70CNMkKQHQLRlRxpyW4VtS0MCZSJ/tzs5w6SJYw4Mbi44XPUk6jxaW6qOmiDIw5KeqIa44Lzw
ZpEboZAoI2w8bxwtLIaUfQr2CoMq6csySXN151MquCUDg08jL3sk7Sup+gvrnr0cHZp5VaKkpWFU
rQU1tljNXBDdo4g4ldThEiiKqLDmkp5RFBiDLDnRnoBpx56PgLplFgPMYqCs2Juy+ru5Dq1l7XrD
R+Y1kkQYvwf2C7R7NmyZv0KZtV27F/mfkycPybt2T8v82zhi1WdtBip9r3kknZAyLWyC4QQvJYY0
o7CWmMDN8obvr37fXPV2PvdP29u3LHp/kph37ZQ38dbOXHn6ZW+i+6QsuNeuPLnTxxuWNrruj5Qt
+n+rvPaLoZTqZB4BEwGDiSHF5Y5jyhQ7mBM3ExZKfj+OBk1YpTp+LobMVlh4zdNGDffw4YKZqY45
vdw1bsDhg1XPLF4bbau27wXZvqwdsmCmBg2ME7aMV3SmLJTQ49K8sSzNd2pm2Uxbs1MVMGCzy3RN
mbNk4bHlO35ofVOajJ5U8tGOjRY1eFLNHTy3bMl2rWSRBd2wXWZuHhr2vJEzIsTDfyl5OuY5ybpv
nF93bbn18png/Ax4X4dJd5Ue3CsvJ9N8dF1rDJudcuO/ffjO/PCMceS54Vx03w1yvwu67uWEC/HL
IrKOsLq3wZaPmsV6rdMkbSW521LOI9WwdXlp2ik3Rco2LZ81bCXjXDKBlhPnfkumWURl4z3vzWV/
JxfbN1lg+9xkl1Mid+0MSeC7t+2t0359J30Z3lg6OSy6ddMuUtpdFbkx4T0fz27c7s0E49Gvk7Pk
/Z2+RLrouF/Wme8uyz6z761xh1pJfDlhHGcfCR0GMAY3GhwOY41OYxMvPZu8P5omV1XYuXy0bmpi
UzYuTdT8vy2fAo8kHk2QSdTWskR2NCme6FqZ8lCydCDTPp28Pq7YPClnlgs66stbxyrli1bNmTFi
8PLJ2s5VE2dsl3yjwWZp5XLP1oycJN+vZ2tfVd0wYLrvC72ctFjVT29sHTg4cvZ6ezNy5MXpy8tG
TJ22bu++GLhq4dOynhgu8sV2jc5aMNVccWXbtWOPTVyeSyjlq2NyzNk8OnLpmswXVLs3hfhmwwVT
tuwYPLlYwcsXlTpi2efPhixM2yzAu1drOW6llmJkxdsXJ8vd4j4wHpHx+vBZUgiRRj6yCfy0Zf2M
uUFMrVUT2kgyMx9ZuHWLmVBANjBuWlu9aMyBZdSvp/M3nKxcGG8yMskoZx9IrKyD+kzSWlkIhEq2
q1rKhGIpIoyIsgAkSFCSsgLICyAosO62goQEQVQihCHLAsk2hkhMgAaYBKqC9LUjC8SWSSY43VRs
kQ75WOhnxIjUUTrgqBUZCQkkYsIMiiMiigKiCsYKIiLFikUEGLFFFkg9u9havoDOiPRB8QyM4jVG
GupuM0eBEhkIUoaFWQTC1Yy0WkGDnK24zZGEwC3713hIMIYVMRVQXY7ovlE2LTcmallKiLFM3a68
kqR3DNeLyHMqZwUxYRTKDk4smnXEgSSZJccWsiKCtlAWZyMgGmMYkImAGYCA4JoXFXAQSgpRDWaV
ErdxGy8iYxNUbYS5OYsWyOpQIhgGwLkcRK6XTGwU10nScxxE7nU7h0OwkDEYeQDI4EAmTKjHfvEO
5NqzYGqnsbMW7F4eHbBm/YnoT+NKpKfWFFkVUpSrYM3DCQOmUftl4YqhvOnqepye7KE1pI5+gn3u
HsCfVGClCgp0GP9gLu71KMDCIB4k/2HHHY5jnaBsZAx0ORyKExxyFHjFRw85coEiZcVBhiQjjdgw
dsH3OTRg/M6av3SJmgPSSHwiClATlZd8TUfULjwNgxHHgFjM3Cm/fsFSZo5cJ8NVm7Bgdp8OU+U3
bOzRZw+sR9rRkcJR9h9wh1w5kA4HQPaKFJ/c6zrOoDEAPxHy2H27Qwsy59INFGQQNGwmSTUGMUSd
w42xg2EoQNh/nQBIgVWElQFJsSwkSCyEgqsgKyCNhIo3sNKJEAgb7GCuJ8gsfJEnsqWf3YMScfcM
F8QS0jZmKi4nEuFLWTQI/KbPUNl4gsEWaeKg/JMt7/c2UGYOfYETmsk7UNc5jkniygNFcKGGghbB
87FUnDJnOk+hWNmzXAX02lLLXvvgerySaQigqmAqg12KMYOYNtaMdeNaDQxiyM6nG6DkrqiGtMDA
VkQE6+LcD9JNDZxfTsEMBqxQfOdhbolTC6Jxz0IIBFXK9/zL7JxrrJDO3jtzSOxVYpjISCoRLgZy
PcxR4J9GC8WsmoEx6U3JQ+UweCGmWW2hllKDSaLeRvWdx5wPUaaKRIDyBAgbyJ7SYMOPURPb7fUe
wmUKHUeUMCJIUxdz5bb0HcW5SEOM2aRBByFxnxNBsHcKZzNFBkYM1ADZTBBEXn5oMSS8Rgh7tX8S
z7Hh7vS79js0RGJC7HhM6DhzzocUPEQyvMDEuQTP0TxN8wQv/h3fpwkjEipEWokoijxrbt5fDtqh
CMliRGGOx4nAifQMQImxMeWQTiJQYuPmSXXcPhTp8u/Ek+x0avy/L2cOnblmxZ58unE/A3VNFP0o
i73eXln7pPoiz+iiVUH0KqqoBHrD/OACXDtXznUoQxVUoSvmpHluvwdOn0YPoWYPh/OgvEGbdZos
p9zaJscJ9zJ+/l972YJkpEVUmrFq0U+j0nS7w7enh0zncoH6PX/IlJPxT9pPrB4kQ4yQCEkkiyEC
Bmh5xLqcpbAHAiNg/tIEhII9ip1BSFvOgGR/XoYEFoUswdglL2IBnx2QDvr6kU/6EkgoxhGMFgiR
UYRYCSRFRCLIsgCJBZCAxiwgxUBJCIhIr4AijgPQJq4CDwlUHT5xXLnVMwTJTAUgmp9PrENJLQ01
SWlRvC5gI2qys1mBkRRBkhWhWDITTewZ8L1eRFfT0RjGAJBMNmfSQgOCInGf4EFeQ9w2im4OJ3gF
jWh+FT9n5QPy/pPyGK/TMljFCDIrUE2AbTfHxQ1kADbEkUTzIxBT6SAHIdqJ5AQ5zrLl1GlHriEm
IbUgHcRRMKM9VB4LFCQisGKZJGeNKPCeSZlJRBR1YhDgzFpuScBpGb2WEkwZFQdy0JKBKEDQL0Ie
OAOzWhtqsYATMXv/lLhcfrrmhUksEcQ1KoeUOnpIgmOMOIcliB/tCSeCKCpIskJs/AcLJFFBVFBh
FCLCMSQIhEGReRbqcIKGAWV4AV6YCcMBOZTUOLvupCDIrM1Pr+vnD4A0Tg3yySNTGPVsddL0ycSI
xJTTvSLsAgYrkSLVpKgYkklSUpkREykgYH+JGXDT/il8w4N2sVRxPtkPzsqE/Wfqpkn8wRhYsdvy
HyH/2MMNkTRTC4RohhjYeJON4khQVGTcCIQGKgDxWAf8YgMgCgf/NfbU1l1nwahKOgQJH+dDtFRP
TA/STFIP1eFVUqgjwg9QexnesHsMnzWhNI2SvQjA7iyMBB+Q+wTpRy3gyKvOFBmrpib/TA1InxaL
DA2mzoTRamw/G8TB4PsWSbHVVZ6T+Q4yjCb4iCytmn9trCFJRk0Uq+/WcLIXZVPFbK+IBWEtUdNp
kHRbxmH8l00fgFCCgYIeswiLIQVDIXNAhkAjXEaeG3XTATR7OopHcGR3NI7qwVKlQ7zrO8zYpvk5
sxTOhRYikRV9zpRb/U8downtmEVBLZMM6dS11EwZzREsoOETXeaCYaDiuVxAmMGEBAUqMCXUrNJB
zqUhV1neJu2TZt1iBCDg4OnbCMIqA/KWZLBgQKrEItnWm+6tChcIkAKQG8Eo4QNk5QP21NdLYQ5z
qFUTqbUpVFRFIYpc8KHFt3mkUCdh/B1jqQ3wkRkZE1bDZgpZKUQYRU+QSLzKIQIIBASIBHkEN6UL
4Dv+ePiTvIncYIJtAdgKUqjZT/vEBx890LJ7f5vF/FtN2sQ5zvCOph8ffAzDOM1mYQrpnnJIHMCM
PgJzgKEgKhYA6GHemZ4jV4LGltnlYyMnG0VJ+v7o0vMt/OqE4/AxGfukRHHQmQsDqPvUKCe1svHo
Ho0SiOuxKLCCSoWX2FF8qJkg0hMLxWCxHmTmmuZjwQc8XcmGeUl2BGhcNYGtBkYppEQ08ZozhQm0
I8TUwSP3yVIQhohsUiG8aFLEksXkRYKeZ4kugUYJ1pJ4JzSggIKxEDuH3okASmyECPzuKkiyJ0UU
QgWA0uu104KUWohGQiiZiIYuQ5cY5GMKWUFijBXXIwsgm2AuA6JdEOHiaeAApiwmCEpMEkSVfwRM
k++MEspGUQtyVojRlloqRC5TKNSwQESLCCREFhBGQBQUDwa7LUqLbSqLBBhBgjEgJGCDEYxhESLF
RkIkQYgxmqFIwQUEFYAkFICAMixABIkAiREYERCJAUYERIKJoBKO84FcS3NcpGUx38BEDkAxENAH
GXtYimsFDRxBcNgSLJrSgWIo8JFhEVZAEYhGMhBC4b8wDAEMruDsVkcW57GG9E8ia3JyOBitIggz
4R9xvsyfyJAkpZLUoIOQyEhEZLgUoWCII9LBCwDiopuIihqOsSBznko8iRcIboQSHhnjInjjI56h
SMYIJEBRBIJ8yijZIwiwQEIiBYT3iksJak9BvEDuhT+OiiAdoiHGL+FEWjkAek854DjueOEgwEhJ
47cXWXBHeIkgwkIQHFIAAEYKlRQShIIuEU/cWd0GcPzerp13MzIICzJehZKEZK4kMGTGLPwtSTQV
KSwkpSTUkkMtgIQDEIpMbqkhjDGlrATGXLqmV8/m877iQ+r9eTe7bPZ09v8XT+G/q/b+Gf7J3X37
jM4o+ENARB/IET/0N4moohJLwcZ/3GuMiHDRQCYm1r3K6zr/lyt190G1WMMMFLSLfG2N8SISQJN+
jieIKriUv0x0H2aT5zpOEhiRSLSVPA6h0OA49396Izf3ixgdzxIxmdSpYyGPIozKMDcQuQhgdpRi
cqHE8X3iZWbUU1W4NipsKYGOsi/HDKRTWF2dCQSHlSBQcPLhix7vhounTp2xbt3TNqwXbOHDVw8z
zp3/ismLNu4eFGe7pgsxenLY2XU8nl+1FnsyLMmazNZo6NmaTl9Ppg5b+FY4/DNw0U6ZvDIxasWz
Ji22+GbNSdM3qSRB4cHDRSzhmpMXLR1EyRn02WxeFmDZg8KdMXlTZo5aHay7huyajJd+hH2ya4sG
a/l02ZF3bh0uwWdMXhku7drMXlk3csGLZ00dLs2ryIlMzBfZq5bsSymD29ulzpkp4eXTI4cHJm6e
XhizLsWLRdg7d9+GjZk5cST2Rk00yemCZOVN26nby8Ls2bffw8MmzJmu7aadGjy8vDZZ6eFlOGb9
qTjpsxX8qWdtHhTy6cuGLp0ydrMHLNdw0avTJqunamESmCzFy6bLOGDlsxevWvat2KnDBZw8RLpy
6XeHTpsnlk3YMHDhNDt5Zt2CWYvt+x2PyM0/QlqkDgeGD4cMPCnDhddd6Ys3sxDQiaESA8nZVgVJ
G230AFSRvJmrj7UDF2ZZu+Vnl5au3hk1fu9/5DZhF6kJ7RIfxLEyqMUSfanpy4Xus+jX57fCmCmb
NZ9GAUgVKAxyNhLi67UuNxMhDgdhEHnAuJFDMxdN2bN5WdKaNGbQu8u2DV/jXZOmD6t3pycsWRsy
ZuTBkxdrsHTJwzRi7ZvZy2TlEfhBjq1YLuGAtkwLMGixoyfuhoyibuXLld6aMGbB2brMTpqwbLum
K7pZu6MlLDJg9v4vwtk0U1apqu3ZaUrRarLVm2e7y6XeDw9zNy0XeHLJq+n08RJIfVk5cMGyzVyw
5WMHDl5SYNomD3Xfvaoe6iSUigUJSFKn6L1xRutTli/TH1YN1wp7mbNwp5eXluzWfCcjyzeXazJu
4ZMniJJDF5/PIDhwUGobGtT0RISRIMZIEAYxBHkO7uK+ymVKZX12bW8IPnE2CO5eSqFDDQHsVSnF
FnUATQz2igsRO4jkTbP2mJchCBAgwGMDQXGxZIUifW6wfMoK2NCQDdpd9nRHSEUFYIOjKx/bMCet
g2LKSypQ0dM7uEqBstJzF94kqaUfZ+Mp+NhPyaswRmkymQwwpQpSz+ol9MNKCEF5mmiD7ouosQgz
NjREkf1g0BgJ1hT7qj1KdSKGkgCAQL5iiD/rtGHu9FfX68360rq+q0+Ud+VkgHDIfihTChmHyzlz
gZ3ZEtJp3mGQlCZIzLDQSVAr+3/E/+B3BdcscqKIWP0w/N1FLrMQgoZbIYSIGSyWiJJspdyzBgZU
FjUQKMnKE+ZTsu8UfVSs/lBQ0GzD2lMM+f2KiioxVVEoGuYej000hbfIvL3RxSe9RaVGbaq+V4YC
CrsFDUzkI9ZCDLhtiKPB/jLt+g/7skGP9P/lsuxUsgMIRD84gJ4h4pFgRYIqKh8ADU9YfSp8POa+
IhBRI5ravxpoJuER0REkAygIGce+pDvkgMfMkDQbSvNSjxFiogn0w1abmAS4gJg6TwqW4JrAxaGM
T5QaaYDGMA+sSIUm0iNIeeCQ9HxMx/7qV8jWYYlNUmhDTxJBFOSPGx3RX/AIA2i2RO2D4UUT8QUB
54EIqe6gRPmX3hAPwCfVcE70dT6xELG9CxIobosY/0RQPyI/cIC07j6Deg1UZGEJAurTYbLANptO
oozIH7yPzd9yiIcpvLHaFsLtO15duN6zwWFiZJEHGKucXRBJBVS0FOScmrXu3WN2gU3GaQpLxGYm
Z5sbmruv+ttgB4zB8Tyc9v7ahldbxXmoQMRRR9AcQrBBYsJFUIX4wYgeqqqr9z8ix/U/6DiTYNpR
mzksPf4nx8YCfWJozfIHGiZn2iBxKlcQIegThTqnXqdBRKnj12ED4sxgT5EkOnb4iqKKohkKVVbD
ZMyglVXAPZ+v9iAqos+2HUTrGHENoh/niUNw4k3qSXVSpCpDo1hiGIEy0uSWQDEFFvv93A85LVVr
Wq2CKJkQBgoRFQYpAeVTvCbtwnhIh6/GD66SAB5Rpo6dLv8mk2KmCLUWJSUFLVIahiSpUT9m5YKp
9QasAD1ioQzSKKKSMVEYDEVR+7akFEiIyJ/zy4jIsREQFtoKsGjSpWPEk4bGCEgq4jA9HyB7MLgq
QiB7sQ4C0HhIQi+eZnlgg0TVaQpYpP7g8sGOdG0fym1w13RwSpKUikU5bBkgYCOwLYOfkIYBeKf9
AHVA4ArGMToSiSGrin0g/+D9P8ZdaUin2rAD8kkP+USCPuSLIfZ7YvcUPvaXIdP3o/YjEiLofxqn
3q6OIFbvIdhVGyg+KLCEkZPhxLPxdJQgZC84uESfm67Cs1oiIVpz0wcHMkwzMwzMwzNf4N/mSGK9
KU6WSwUcsPoJZidR1nvhovlIOsppqqNhYs3mcT4oW301fy4adIcAeIlAoJLBOHlhESfkg+0U6gAD
+Lzo/msMHgOGEhpBDR+YT2qfMHShOlYD6QtYor2K7FgnfF+Y0pacaeFp2ca03IknUQrngYIdREhN
AfIGiQJIpzXeTRyHqFEtRIWsyNNwDBBMCSAJCAt1UQMETUkGEeE/yngtxzCFRDkvS4IwSSMh/Q1i
gXjQxIKmCMwClYwdYXEz5TNkRkRiGkoCT+NAOExCsjZSoNLJPXhZMG5mGK9bhlcuQdXRiVkk+d0I
Zq1oJyYSFBptMUOWprZNmweDhRSZuFoGlbQQEAgxWgi+wg/CXQkovcs2cIOiFAm1TUgvazFYuFq0
aDRxxhvpRq2bictEHmUkpASCJzxevSm5N2TMpb9Zcy8VwKb10JqCx7bkmAMfBDfNk8PAmZLbCsLE
RqQRDBIYIQUOBiQYBsRBh0f3WdTclm0mZCiJx0hS3eiGoapRRP627jIFSGwVJtkMCCGhIiIxBh4y
DIlgUMjPJIQsiJ14nKmi8Jzko4i2b78Jon6k/jf1+9bJpJOidGdB7p+tphTaCL3xb38dyGJJ+x+n
+z//f1+c4hO50e63FzFqhiqk70oCM0zwBxJs0xYLFIosFiyKLLJCZRE1kirIBiQHbNYSVAkhLiyx
QU2GIcsLXKWvNb3PafGP1sKiIMn7VwQthYZShcDH8MDtAIfjiOAgB+JGYiYqe8J+kSkT0iaEflCg
+4WAhBc61g/EGB61/skgBZPyH3UjoiYMlJYUwIgZCIyIo6hbHGFRFIwXUScZIFkPak0gcQYcQFIm
kPQM4iQYxQMEJUn7owx3TUwEkEQT7aWJOhecIe9AxRhRnO7CDq9UM7WG5HcoqoWwsC2W1LEzeBDQ
JootKUZZSllLCwtUiNmpCmAigiIxqZBZnfZJImjWjMCkmKwgkAYIiKiIidkhRTvZKKzGErOwyj2S
SoGkCoaEARJwyLJMAYoVkgFEmmSFDoCwVLqg2ooqiEIENBalEuxHRh8Yiakdgm8T8CH1r+BHFU/K
KYIfIJ9Zo6EO3kPj4Up6j0YGICioqiQVYCyobEmKyKUayKCA6EJ6RAOBFG6ShoyIjVI0jTAIBAAi
Rg4wWhLIooQKeUzYET+rGKJCMkaOqlRLQViSJAYMYRSBEjAGDIMAEEQEFjJEAWKCDABAgCMFAMSE
oxGSyToQkxAH6mI2EDlXYhyo3FT9kZERIQVkRUkUWEAYQBIREiRFRAIqIIwUFEYCkWQBBILILARY
kikggwUAUknQSVIsigKKYcH+NO0DgGMRgHkEGQzufofi/ochBB1RJaI/YkrIjhrBGfBDwNb+vR9Y
5yq7uPspTTNdEPzpNv1lmnZq2DUqwrFU4SrlNc5hopDUxN5g6MXMMhcKaHV2bwNEBtCUSLGssNsx
HtmXLpIa1oNR1RmkmzihPzzZYE5YntRYwEYjBFU/qGoeeLGHMEFKEtSyQi4Q6CNx5mnHErvhgKdY
mfJ5RWvzg712aUCgc3+HRMhdHwUe9asyQPKHQk89tKxIiiAoxEYwUVhCMYSERTyAFi/ABvEwU8Ym
CjR6RP1CeQW6pdX06zTzlTA8DYEpRgcSPEgEKi0QYQEkWQHNB8aF73AOcT9quT1CJmoBzisM1Hv8
YKK97/p6+8CTmRF9Q/tHYJzid8TSeE7tvkVUFookiKEjve2QolUVioqCICqsVofympJNvq9P/ho4
JOeioqqoyVW1SkWlrIx+Q0n+kIVQ/rPldde65dC8LxH5oJM/vRmg+5G34o8JJ6R+9XZaKn+48aL+
8UP0h7XiFMgYj0SJI2qeYKwb2Lhu+PhAUBOD4QFATQ0ESQ86AYquSK3xLC4BEQL0UEqFEgozVKiJ
DKQnwZAKkSJIxAmBBkUuwwDITJqG8hA0gxGSGsoUsIQI4JBQp7rhdSOCGxZeBliak5I/5wi5F2aG
38aM8dB+z9CIWF4LRyeUHsD77Kv4f7GlLqOvYEIBodIo+UQSJFhFiRJA10Ug9MUA76CimReIJxQG
e4k5bNIB/xioKmBIBLj7mjIfbFwMeTTqMEBxIoyRgsjCBEUUFRgjICwiEiMSRFYxIRVjJESEVUkU
kGJIwZAURhIfV9oREVIiKC8SQ8UDoEGPlROgXiW1mBGCDfvYld4A4zjuVIiIbfQZ7J7J6kRGSCIM
QICgApBkiLCDEii9YIcx9f4KfwQ+aSqLwS5J/0xmkf58J+3qc4WcTmonA0Q3qDozk2lVRMwFDCqU
qIixkGMXJNl4bEFUniJWJmqLBjIUA2W4JiCnCR9dLK/fX81q5smw0C9mEwYCiMUZhbbCW2VO06YF
KXHVw4ujT24DDNA23tTbDERHV2mGDWwy/1aITaGyJ04SzAeFvexSK06kiyUxjGMZCK1+xTgrRmaS
YynK9zYB4nEgLEHs4jaWwRVDlHyyAkB6WGanK4AljNDAa0dQPpCB+BD4lbaB/uCHuLEYppAYAwgA
MjAhDTSCJZQhYVJJZH7wFJrE/IrSI3UAfUAZitwn+HutxPCfEQGaxpQoQD9qh41fSQTeIGtNtRpF
VogHlbI0qhaKoBTBgEIkjFgEMyKghmkRDAYD5gDcvzSQDTeesgrnCwCntUE+ITSgFC8QdvvwD9LG
iSSfDQe8h9od96naAD+CKiH/HxBCBSKh1i/UmtPxJ83cJi8BEDemJ9Q/P4/lujySRALI39SqZAen
BDbALe8U+y5WwFCAeMIh1xLxQyzpQwiqXLXAPrxXDCo0NIGj4JZbKm40gKAmhb5dqFu8KUVRCqBC
NEAkGUNPOpxnCL7wqP2Gh/GKJELTTHR6HTP5wEBs854jEIqQWIyRiZFchBso/BVyx8V7Fw7SIehR
86o+jUnKIwAH2oFRfPGZhgyftSahK/KCsAWYLAoYAFhuQKjY7tyi6mB4IygoUGoBBiKQgK1VNEAi
Ho5/Zp66Z9vWSgCWfgYiHBKXbFk1/jxhEigeRcmJy3anAz+K8FmGIa+hlTFT72ZuXJogiseXQ6iy
CD9JmZGMWYwz9CS7WsvFrDjX7tq2Il9TiiqRbM9qjhgtiYLloMeOoDOCpVBi9lrCpcNV3gQGahW4
NRGSUMJjtoPhohAtYNEwM5Nbp3gIElNDFlASDCBKxNsZBxVXUXkQ4vL/Zyoj4FDmFihdQw80MO6P
TC31VmHUFx8UQ7FOMDtiGXEcwvmclDkIjsUin2wD9s3ywJdSnyY0ft+w81g5NFCgZ9+yfoeWoIbT
+DsGoBhlOXyQ0poeBEjeBi/uEQuKi2kFkwE1eh0EjUzCheSbBhUC0qa4CH/Q/ooXKAh/N8H6/1/r
1b8+Tsd+gTJMiD31MpW22zDpth+rt/CM8JuX/XS3YU1STt375A/wteyd8l4ZNMMdJ4ghPgDfZXXG
cjIVm7fYz+W2/8vdnyMCeDJHxSghHhDhOEeI4g/b4s6n+mhCjeVv4aBS5CknykcCJJP1n3BiIJEF
RiICDAIREkFAKG5BHnIQPosrQQyAs+dRwAbAZIwWFGKKNgc7ZAJPcTzeYV9AiHaIhmihrV9nsdeg
fZAJFSBBAiMIpIiJCAgAsCALILIiSCMgICQBAZJBSEUgQRIREkBSCMBCCKEgJCBCQQiCrBgiEQR9
LEKUFYAEVUqUoSMiQJAgiEILY+gQJ8p8oLc4xKEaXaBRBNTDF/av0CuuRXKP4WAyK6ENYpB+UHk2
HwHxkIMP02VM1D5uRCEQ+VKKhA8EQKJEgDIrEQgwiKxP8zJZllE1ElSLFkZEgiQYsYIkFQU9IwJP
LPWTsnwy0tKh+Sgo+kR4R+zoQd7wWKEQgWApBRTYcBYFCq5YKGaeDBHvz8ZVJCCJp0ZWuDIzg7lb
gJHLaH39/XAJAkB0Z9sRdgSw6/HJPkMppPah8RJjB6G8exrImB0FDJCTsBwMiIkHtxSGrfVnDMgc
siIoQEGQENgSAQQnFRoixENTBUKGKARJdhoCgezm6jQHP9QBAgR2OMAHrN1c+X1eNRrjJ5VHAgWY
RFYw/4ZJP5/3zX9I6Dgz829ybhlp/RxUdTCFyWCvaeWqkhl65kMl9yHBxKD+Qj6lSqC6NFCk6AXz
dpzYnicJIkkIQYD6gTHvKgd8gpIwiyEiEViyKwiMgMixgAkSIyKKnjYcJAksESszviIeAHSR8uhE
0p94urNESyrwAkPKWr0YAeT14SECHmZGKe6JKsM8yAf/SRUkV/tEilREdglx3pwwhI8pCfyCERxg
rGEqgpEESADEJFQW0VtQxgQSlXjGpco1J/GcxzD3DViMhPlLj+luxR4xOlD+dE0IZn6Bf2icIn8y
IsB1D/4VXT7Or4JD242EbQQaAS9qCIMe+p5UTQhQv7VC9jbxBJ9DIwgH/F/YvQjD9aHMiaUcRS6H
ajoVT0vB4jg/haeZa/4I8tHYjz8iFCnhF8Igcwp3g3inpOLv1OoKbQJMyVEqK1VBchnGiQjUWpKH
ideYKYKIXVRA8BoCx06lRDmdaIo4DVOSO+kBgH7JDZH+ZGZNUfmn7Ylw/Mk3RmGuGyLq6lkZBhGx
T2/3jRYTaLkI2SpMaVOsTtiajAM5P4kWZN0qlSxPfFIYNX/HIge8/Qon+opRQQQKyFKWCWyQTvj2
Qe+Op1B7oB3xDN70Xu3CcyB7ongU4oP8QgnDL7IP5qVKgfCMg+khx+h9omgS3QqG0U6VMwNGklLu
V4xMkfWcAmtDkUPGh6wAbiZo6R8LvPSeErgu8pja8EQ/gIzYAgbsWWCMCQ1PPcwAKIijSpRtEJbL
lkzAIIqatJgmiywYJhREsYYWbfUbldkMl6bZqqIgZrKLMOEUwyTVoKAMw7GoGgwsJjCUsvGHQIMH
FNZPaTcIHUKGoGgQ20bwcHITYaqrhgbdNVIltERlsn3g1QtIXRs7aKU4OsnpLE8onoVotPeFo6RO
gUENzEU0jsJg+JSIgeLlkmtZqRgISiFmGRshKQPQRgZM2UmRwElSIWAWDCRBYeDtUckTevvz3Cyh
tTxEkBDcJgG0cb/B+cfgeF7yH/MokqsJcMFVULEbTQCXRzCuEikGKOFSqPULpBMZKdQlcXYCSHqF
MQsg6+GYjrhSB6iqAZBdR5bYMcYBay2AsxKVTBTvIWC8jGZoSr4NPYJ3l50yuOg7qFPVBtfDvXtG
w2MYCn3DMklIEJdIXMoRmqjG5y97IT0E4c6M6G1l04zSaDcyuXrTaDkm0BctAWEBEDYwHCloWWlJ
WTKFc0uYmUGsqUxYEBtFBWyq59/kQKwFUlZApqTjsqFCcyE62ApCb5kclAy0iEBBhUNEykCSlUhS
MA0t6EiAwwSZCGrooqgjz5gCcCK8mQBGEGTeFaQtQ1EtJFXfvBE2mBofKkjCITtg+wjpQ4k1wVvz
Hy/qyS8R/p3QeZJ/o/lTGOlLrLtrsAuiy9mCi6CUiUpqH6BpspTZzPJQYk8vXgfBOmE5afrxDiIj
L31ces9K5KLDoQqg7hcwecIKdonfFbiagDwiB5tc+P39CL0oiVSIUk8sSK7/JImAf0j8arEjH7rQ
+kDt6xX2ekOk1ggonRSE6Xih+gLeEMGocbobNi7GOJYswgloFB3iCc6NmBNfGhhO9amQC6Bu2e4i
Qeb3BDlM06ASAiZm5SJ08NgtBImdAHl8v2YBoPpiSAPUREB4DjgvhIJI7UZSB6BS/l5zAC3ysCaP
lsP903CmqxspUdPBrUJBCMAZCaiUYRGEiXQQcCwMt/pgMNDgK1UulAQFD4kblwTIItUSguZCIUMG
BPpAPfRbCd7acJyiQJG0/bDuIRgfnjzTAo6iwppBI9TC1Su0PGGepwc8R92eETaq0hxCnfAz6+HD
UFIm+xlHnAFMwPb6LmpxtVFq3oy2gOiMIHOCFjoEsAVgQaUGyJSaoNgaUKQRpUjB7PbbXH7A/tib
oh/nRCkfjAyy2QQvrIAbuBER+jkHIkAjBIRGQSRkSQRhEhAFIKRXHgBshSJ84vOqcQFjGyvyKe7s
0HrMy0oreSmEHuvKM482pwYWV4nJOIiNLzacW/SH44QDiZs+BT3xLAGwVH/IILBSARiRjCEEkUkB
iIBAgIRYIQYosFE7e8TOJ4U9n7mzFh7fK3C/i5YWpLLdjA+qyiJpDhmG5CYaJdQMDDUXJctU1YQ4
3py6O3bt2zYFuUYs1cuZy5rW4E5IQH8CF5UiIiw7FhYpIQkDZwy3EDExChJLoyELkQ7UiK44OW5r
xMrwpRvaSZI/9MhHoOQTmQ9IWEucgf3K6RE2qO8TDabXgSqNqjBLRnSXvJEVGKKjFVVfUELLBESJ
EgxCgaGmRS5RUJJIgQHvJ5/MNJtPwjBYAqIBSYP3z/MUmCh+ml8j++HWj9xZeSP4T6ZxSnXEqB52
gwlNjZjHYu8gimPiUPNERYhAUjBFeuKA0GgAKFWoQblNEQhIiEHmWxFCE1fHiXNUoRhqEQCA+88l
H3ss73A+PwpIb5FeD9q/vfzmsk2Mi7wu7TYhtWOpPpGEISiUsGBGiUcQiWfxI/694E2nTygHQieB
chGAYHCE4mRChikCEjAUVQZBiiwYCoooFSwBnShQiYJWLUxMAWSRQFCALFkBCMkNfe+/861tmIjq
bWAxEhICgsYgERR2NApkROwoV3qlwO4iP9/HfNTEyhuZCuIqzFCRS1VLdw6MD5xqoyiQWB50tbAV
t+TCfImd+DKOEspJQYiv0RjOJYAZftqFkzih8+GQFUtlCESfDoFmFqhLhcQlpjP1a2MZX9uCRjC9
3tymJgitzgP3b39Cvh4UvUg0mE1VDVEP99ELZIgY4o2PKS4iFyIv5AQ+RUpToF+xD7UekT0odI/5
6dSbJBA3BUR4yEIllLFxfSAHZF28cOszWGgpHNNTgQVCIGtHMQDYhFIvHwKdRj4qqSEMVyEwUxx2
R+aBM1FJQJ1O37j5Tv2H/StZEikJBDTAjwywHfl1NgESonq+3wtw8LgHpNkFkWTvJFbBSUrAhZIL
ICwUUUWETBsLP1WTIjEkmtZkNZKDP6wN10bm0Qy6k/9JH/8obHonUUYuSzOpCgTrOugkeGB0WnYr
ZR5lH50aO05DvW1e+SnJEPOeaj1IveQ0EZGAQhoEQpE/OiqEQ+PwlyNEVIwLMECKARVKKGZB4Rj7
YXNZUOZQNtgqKeeiBKNIF0MbZEBiZRpAsgxsVL+NRo99VaEpdorBKBD3lT1BoRsAQucpXQp9YbDg
9ZANpamHsYFGUqM1SiGTC2ony+o0UQzUoUyIZlg7BIemOtOkkAwIewSmaLlBuNDI+NlkE4HaIYKL
LS0ZvISBhhkIPDqJTVPYw0TqzUKw4Mzg2pD/4dCZwWThiYF4uu5vei1sdGDxDBQaYZGQIUaaspWh
ON6wdM/SIcbpubf5BJSGDCWaBrKoYZtJ0GaVCQ0O3FZNhp1UwuMwKksxNGoUxMhmoTNS4hQqXUVJ
KyQggYJSBiaElF1aDpzWAIQuTWqjdNHQlMZoENmjCZJwJ0eNgnHOlzVyJZtvItFgUQhoIJLKJdZc
kDSIIblGU0XTxLNQNRlNlKwsSE/5AnmTQBpbC2bxgGJEkR3WKUD+FBw2tBc8sHToBAw+K4Hs/NAx
CyP8SREkIMgRiSLOss8pCuBkouWQiIFRZBOrQh7NUBj8tKGhntjEtC2gEWoYzFIWb0iGajDAnBD4
FViJlNEAeKWkiyCqcFzFKJKhiDJ6ogIiGoUTCNyCkQKYA/5QukVuicI0IhMAAkFYwIV94mI+UUzX
sX6BdguKmEP+gh2nUHu1AWFQqJ4NkKJMEbBSVwFVZapiXVLAiBCICQBQEiG2KiWbLSwhzWoGImkV
pRV4Gy0AARDEpTxB/oGADmmhijhEhBRkShIJEKluzl1ppI+wfeLVZsXvS5hcEKyZUwlKRCJZQyJg
mFKYUSRLoFXQ+I6FrH3so43A4oAAtZlKKxXfOZZdIf19yOvqqzFO8J/IjIPXAYh92SDz6jlTuMhc
EsuWWRtswwsWKQgiERYoy1hBHCfKIl+WMyPab4dwZqQAp0kKyIjxLI2JSExILAwYAmqKWIBmhhLN
JCoSsmkMZjq3RqAgkQ0ZLHo2FoQ2AgOFF05QoWziRDBwQpC7SKUEGfpAoKKtOibEhgQYQDDBQFEV
FioIgxRYREGIpERiAgMRgghGKyQOxDAoCEhqGhYWUcgLwSgFoLDa6REbCVELHEmg0yCqRBhILEQj
AUUIDLp47LaxSJQQYxSkncxsIhBYwGMFd47wXM4GjpYJSRcTEoSZdmEqHR1BMVDRyAamxgiIghFC
YqAfoRwMYYTkgFmVSjSqBYMwenQD38gUKSwQkZ/J/xLWgPzxqEiMQAUEf0oVgH6WBWRD8hT84mA4
NiHc2KQaJEPBInfVxrGYW+OV/kUXHkRQ9IkReEFewjEgRkGBICARggLIAMGSEqQARIpICwBZFYxR
JBHnIFAn5QWiRQKjIKtMGoIkIqtgFohzEALKrEgWiFhAC1+8eAMDvCJPIIhY64CTw5vUr2CcmaI+
BiCO6RiuJtX0CBYDBEUDvCUJSawgrrRmZfgQ9+D+UTD4hFd+1HEPaSn7w5k53YEhSTXvzB91SSB5
okiUEQ6pZgWF9YmpA6zEMB8RcoW5CEHKKciM+ZLEE9v3fxBc+oPFpc1A80RO6APGEC55qHrATtA5
T4ROQXsB5oCMO0LIF97YYsB+B+WwNIQgiJbAsYArIKaICaBUOpKQL4RjhSiHAMZLLAhYIxNm5chA
WiFJKRREYgKRoMBhYPEMKlwGFmLlpWg0oDIwEtpgmYWwKyDGNShbLAhXhITJ9xsBjNIjIFREEZCj
IAhSgyQYxGAMRIpCoBYwkCllGQZClpJSwBSVZArIWDLJJaEVGARFjEYMYslSVJdFo5aSYliQxClQ
rQbWsiwUhWEkClkrAqHNLFMLWrAzCxbSljlDJQrGNESwiSFRmIyFbRS2WsQSsJUUg0bIcSW4MBC2
EGlsREEioCCASVRAUYIQJQkGQGAApJCIVIxpbFaWoqMKUwcUYrLS8WQl0ZkyGDBjCySwJwEShitg
IEUQKUFYqkYQYKkWIjRYoCygWTEFiAWUCNgT7rA5GBiywobQUAWSbiAQwzI4BdIN0WJAYICzCwFh
BxYEgxgaw5cHKBwlkI4JEjNCVcJYEVZaSBKkgwLYEVIH2qKwYqBACCQEgqJ2rMUh/IJwc7DnGKvd
BTlHFfSDYgYlYEkUPJHYiYrpVYcQME4FbCqO9QsU/cLp9/AeFiQAJ9IBwgFrA/U/DiXOM/jKPocB
xK7KXhD9MZFlUp30Cu5U48wndeyA4UpSUJCJz8hfG5Xy8FGBP2kQoipXC0Abcipmb0TE6Ahv+xoL
KJJC2DXJTEHIJYYzvGaDmoJOiYkRA6TmUoBwCaETCHnj5u6KHZybnek5ZJiBz3of7eCMdIUU4sTE
jDQDaFx9e0QEiWmsPeV29WevL7LrwkLMExFFFGpTbUVuW21i4AmC7IG2T4aYzV+opATWo7jcSioH
WrdV0khCMI1SmiAFhGAAcMBNhs2kokZIyEDdLYhTAhPfR6hM1fwSP7Imeoxie8TVPlH90Dn07qWV
FU+6q5blk5gxOJ47MC5xGsioDZGIgJYKR/BGqP+UoqFSCiqoRVVJQVIwGICMUVB+RIc9ywC6pIqQ
JIxgWD5R6rgBh3O+SEizbEDeJBGAMAXsMKINP+z/aZMGRgqVKqyIoTC6ExClowYRjKjUJaQYkjYL
BkMkEICQRMhCAVAiRB3xUocij7ge1KVzYdvTxnP0wLRqqvKlyBeB5jeL4IvIqc4h7KKGCjAiqBFZ
9BQeqx4fmkkkkko+vSnzSy+6RgSH7VCFAf3QbFgpE1h6OKELLdIKEhzD+tRO+qeEELD74nEJdEX+
JUB/3HkiEQhGALCIRIsRiESYi/KKe688I6w8IPOAfBYHJfH6RSJBPcE2XMzSMiCeuCvEbnEiln96
YQ1FFSLA8/0sOKQxBMhR9IKGoBQE1pIBFFoiwJCIrGIpeyhCsETUbsMFojCEIUimAiFAMBRUIw8I
Fk1zrkPJmgyAaQEkYECMFQAYpBGEGHrN0kAbQAKKKQaEinrIgVgpCB4CBRgDYjQKhAUIrGJCLApa
aBigZUUECCOQRXSeQSUqhmxBJGRZABgRRJn7abCK4nqVpUS0AijABRvBaAigqRSJm/BAs6lg6ALI
zSafeteED4hEKfS6TQoBoRU3QYyTgdEgjDvlOlWxkIjgowMqcoiLIw9xBOYhJJFRWLDiNBiqwiES
RViiEGRgCIUSUZFRJERIDAiwUIRRWQGJEJAkYREjFBISMQkSAwkVBC7rSKmAMALKiVRGLGOQOwTk
OLVVc6vOSGVg6oiClL4D7akXnCT/9FSRo1np6VW8jY12rzFP2sCrAnEGxNskkXWRqeG1QqhA2BsR
8Wn1P2rP0FOCSQ7bW1U/IZ5R/1VrP++b81gf7EIqf7VEZFP65TGRVQj12SdYRYA9uCJ29AQ4AhEF
gyBIEIpBEA1HRqBBliEGCyPv2YaZpG0E3mENIx0OhlN5klPm3lyZgmmQxjcjAjOKcWYv+GZ3Y4FV
KguoVRyaLr2GoAFlpSKyhSAUh75CU4N4EwboRR8TdBEbnwvGGjrIkgbaOR3SgsJ0qaFfk0zweQUs
wuVEiQphVgbiWW28J2mqEhrEPaicJuLHTAho+dTk/1jxp8n9ZtEOAQIggG8YLJGIdgdkU6d6CHzd
wSCn2iIUGARBqBYESIEYRTWRDQcwu8WQRCMgQRCJFELAh75F0U0sCL3CkCiQg0sKjZ9x9xVkor+a
GsTKEpOPLHNYP5aULRWoWjIqS0EskYQNbV4Ot8goHdrOkMc+5gJ0qJmmIZE9COr3kT+sE9w+xWAS
eHVnBPf8fUQURISoV+0zFZC8B8kRFFEGLGJ9qFgeRZCFL5gWAQSJWYrzUm4E43rBT3iDIB1nMOUi
RhWhx9ENwaIiUPjs+ezT5BPIVPVcfPG2sBNkN9BcP4H3GJRYFMENiH2CPU9YomoJ9X48w1Puzngc
ubP2UhDjVjd8xxMtgNWyV9hh5kmAYkNpKMaagrra0AMhoIqnOo9jEkBWMRSlWRjAkkhCQCJGEV2r
6j0Ho+o7ftbFj1BZToAsLS226GmCAWA0pDQwIzIh9ws4CffD2cGpbwOjcune6Qc5zLQZlKB0pDhx
LU3MgZdbNBdms2XMhrt2FRXnUlzUkwBZHmWBtIxPQ9fooGuIBZ1VGIbk1ewk7iQvJ0V/xGdtzqZi
jg9rUexmIChFFkEowIh4HhH5tEge0hBfWr0I/GJ+EAH0A98TxAH2i3XgE5BTTuOW4SzQQJItimkI
lyKFhEOKi8Q4g7ClOTx0ZDEEOVSIjihmtIiUiQ86L6Ah08zoAO1se0DpDqqqoODhqOYrkTcChEAQ
YjEk/ABSh++wKd6wghh8PXwH5yH2n6SeArgo0hpA4lDmY2kJJuiHuiH/BJAmJ5SYzSngzsLaKxAg
d3k9iqNFJBXJp6lD8sk1XzRVDwAvECRV5emBChOnaHXxisJigRwE8a/OmjpNBSkF0JQ1xFESq3Pd
ygDliOCYkRFDSocH7fgfz5BxliEIQ7txzi/1C6QUFuLuQaFc618yG0KgBy8nfV4E4kTQMIEIBEYr
BIRCEUDvn64W5Ueg1xDnS4VFfYRNQCeYMYSYBQCZFUi7MPIgFk40NqL3jn/r2jzw2kYmMPBATi8A
uIo70dq7FAJgGPL86sTBFJSUYUT04U8ED3oHIQNnxUQSyCMKDOq7LJcIvniSHEcWByPsgOo7EDxn
CdIikFz8CF55ifecx+wnhH+FL/urIrx7hyhaETwEN5DrOwo/jNao9dyhGIu7D+CIftiySIh6R4em
TAlFFEQyoRCwiGqCoXLKIdoZHBcnerggHO+xkGEZFYsfgMA/cJHBUAESJPiUffzH2gVdCyPUXX1M
gxCELKwBS1CNERSAkIGDSURcaZECzDzcyvvAfC2osWJGJT9s+0X3mjcYQYRqbNmaJqBYbscgQzHA
Q4+mMIISB3CxQqIfKDAFD2BDfN8Yd4HhIhlGn7wIxHuv7B/2ikhPoj1J9U/ciPwn9rvsFossNM+I
SANzI5yIm4A+ASC+MU9b8aH8i94TWK6x8IIbkN6OwTtV4kQKR3LxC8YmKPyq7UP3i+1D9iHtExVV
09P8xQMAiMijsGDYIrOFQ8iL0C0HyLgHMeco7ESCk/f1WtqCmg7CqtKIySqVAshBtFBobFrCUHOe
YpAbwCRUtIixXBj77sysKRGGCo7DXdVqkOzDke2soDFFQUWOZVO0BUQJjEhjGSVkkyLIxUtljIMs
tthUEVBgnxaiKIIOFKKZS4ytSrAESlRVtQLSglsrBkEYEUkEKJYsjANBCzHCxWFGSMKCgFJRUFJL
aICkqSwgrGpAoMRBBIQbVEyXGTPNYeXldRE3S2nyb2oq5D9nztmasnLtlEFFA5RiCIVCVnxUzwMF
7mXJG3c8AL5bODKrFCTBIbMQOEBQg0kphEspYVA8D/iHnTptIfYywOBUUERIhFFgdGsCqgIgFSTq
ocwsAMVSXoqqkuiRE/u/txR3hvcFHsE5BKKE04HgQwQO1W+9HrEoHOCm2pwlby8ppjhmvjfZZKmC
iSRjzWRgecwKkWEVMYPHgo3EQodFWI0JIKSIMSAkSlgBWEQYSsiMKKIBUkhsEspZjYASACUhEDEV
oQpqipQbYVZRZAmARjEiKSJZqgHIIn6CwGCo+LUh7+bmMFBHFUKEAxXtIvlN3ymlYyRJEUL/IIhw
GgNwJ+U4P+xgwbGi+cGQ/MNUzbSsaRIEqDHEm6esku9plGYyOVhrxJ9z57/1QRIynagCj+osWH5X
GLQgYBgGAVVWP6XM4F31QHsQ1j/vNyGC5fIf8nqUVyUNYlIcKHSQEbhyCxuCOf7hLOMhFggz04hh
dZzF8LxIQasv0MmaawJUsHQI5hRmRwGwUpSgmFosCwwcGlJaNoHl8kasCYCw3wmw+MUmsgHDCCxf
pULD0sKUulrKfhD9N8qP4O350Lx7CncJ+mMIiPQ3RPKO4AOVI9VQilCVB4IUEoCCEdIj6yyvfgBx
HvkNFVBUrPGi4OMoJcKUrjQ7xEhKXNE/VqBPEqcqiSLESHl4oBYS3AcKxIyMSAQgqwjCIQGEWbFV
MvMbygNPE0QGotEQm0xA+WSGPSVgnEN6NMRfw+gyJCycgkU0rCBDQqTMyoSi6HNYOQeYgyIEUFYj
FFAK+kP6zQH0lzQSwa7W1TK1f0RDuivgOsYfvU5V4uFHZl1fhKVw/DRshxrNB1nrKCB11YhUGW5l
wDWrNUph/DG/D2mInvpYYk+w7lPKbsS2H0nibmHwHsMbex0P+Ov+s+TfAE4NmDDimtEKVqVGnJbh
SzmgIcQrBRYinl8spgwY7BKJcyYnE0xvGtQxkhSFOEhmUxJXqftS4a54yZqk63MCsctzRAu4p/RZ
qY1qmTRWiR2UKKXU/dOn1RDNCF1BXoEQigrZVTvodwPPSxPCIRJBEQEIKkO0ICBgiAyCh6mFRhc0
nMothQMafYRDtBSBiYUZUp+IB1qYB+OJ9YzJQMYj44IVAgIIyQQ6YXFURBYEZEEiIgkYOs7GIK2h
h5mJb9yfJIClUlBoUuUGKIni0DlFMU5kMgKkIyInpEt5JfAnMhTk3DD2WzpCbKofAoFiyQIERSEA
QgqoXCLSoeZFDetFoCRYAaFRJFBSMAIohCIIQEkBYjEIwWIBSYWSBxiDSt4w+YGHBEJF4XYbSiEI
SQgwaQuMGcih5xU9xOPkI8RdOrp1IaoKCwiKG5BQhENVAe3g9IyIHUZ0mA5xud+H1aTQIlgipA4R
1DQVTVMCJTGDAQJxJGC/hYChsEe9CDztgDnEBIQslgBD9IiIpIgxRjJERgyAbEylCCUYaxQwYA4F
GoDHNZTYCMCggJSgHaLg8gtyfIcVCMGCSJOLCkbKFaokCWRIsSglgRrSWB+eTIFJMgWEIvgOipFl
8LSKiml7lShIUlVBbSMWiaAHATAQSoIhAuhyIqLDIiIzaJ7fqwiJBf2mYahR/TwtCz8onUJmiYET
bQOJFbEEQiB8NrWFKmMMQjlgW2EilRLSkIH+AhKbSQkKCQRCDMUPxicCukmBsMPh4jrGFAP20tmO
g+NGIuEYUPvYlTi6FFG4EKFSJY88HPXKBMmlzMD6skDjmyMXgqqpaAtVP6uy4wXwYU5iJtSd5aDs
HVBRusKMu6QT2ckcJDKmFJOB9wwOdIeoEVleQvOZq6B6iOF9r9uP6P4UxOpOnr2aKG9aq7UqWpCp
eecJjiFiu6peuDUENs0RAwYUSKPfVEt/l9+/KP37JXSxo5qAc7EN0PTQqWrHwPjKQOk+LCzmKG4o
XlyMSOjgYqJj7evXk84OhsqNJIZbIQtBiF5OIlqzRzIvQ64Wh2RZJiPA9VyMldj1MobtNclEGiJK
WgtaocT34dBFetN/K0wzONkwm0z549/djMulFozVoRQ6OWB4YjhmEww0LSmMmYDr6ng1R3vZYzQt
vJ10RNQTqiEw5ZuSySP8mNd+mEHgfMv05ZQpR18BvSHHnQZHVEEcMogSh2wzipdm4mw9VNqWBxtz
Y35WVRRGVUFEeZW/hPFn1DWis00SallKJNI9kPD46sitPPF7ykuJYSWrie90Wsh4aFhnkiFLDTBt
SH853yt4g9Bk0xs7MgPFB9E0w3+8XhklnpEoPth3W4klYvJrSgx1oXjiISNi/9j4uL6kjePTE49/
deeukG6wpIXqzsFaxxaW2kmHJmLCVHs42CGzBZMJvmdDxUk4GQ79iac5m3kLlc/IlXVBJXXju8m1
ptyMjbNVRLbhD3U3zus5SSw6o5R8OqAhFs20Z9yR+CUeTGurVLIcoYxGdlBnDQx9znVpcW/KM1dL
RYuNBR1nTfWiaBgaKKgSEJkQHnt9x/wU7wmhQ5D1AhvXeoflE5hdB31HhQOU4J+GLZi79X3tc/kc
E2dCMR3W6t36XveEedzFGeKmJwU8N+WQJ5pbyHbz74iigs+TDCoVCQMBCBiEF/CJBep52yFXzCoC
Vk2ELda9fejF1GjQaNv5pGSobbM9vJaiq9EYaasGLAh1eEhzQ5n6xScYDumDCyEBKGXSjK9h4j8J
LQjBnM/UeYwUYTWkp5kQMncCeFgIhAn4hOocULNPgh9gf3/6UXLlED3mDj4wQ4F9hEOQhGMAipJI
RSCB6PuMkqeFUsMFqE8Xn2f6y+AySL7aQkKBxaTlLWDDfWH42oA0gcwYvRRFFUYwVEEUYMVEX6VR
iqqURGMYqoKoqCDRKM+5IH724Tj6xFqQYA/0oeDPDV/9k27E2/0PFxVTMukzKUBwSfpHAIZ3ORJf
Sz7wR3IUkfaDX3wQL7Ag2ZBGDE2poEOU0GREiGkOa2LeaLWZxdwgHx4oQ2UiIUdeKJExJ77cGx4y
JqFgmUHYbjNyIZ6xD9YlE2MSIwZIeIyREKIInnD1ELU/WCTgiHKH+oQqsMASCAUoiSqxQVFgMgyI
/0pRE6IQAPaHFR5DdIHIXtahOKmxESRgXo1sIch+YKakg+8Z5PeBfvmC4/4lLKYO/gvZCQiEGOMK
IoVVHDR5ZsQemupYF16GX9Htg8eagJOYiT84EIoCxBDVNx9NP+InkXMgw5dCOSgDCKQjPoK9LJqz
SsRIKKCqKfvtRQUFtoaINKdUfP5g58LgTmJgWCum3dLhmKJxh3pMrEphdWDF94EP1wT3RPGHK4mt
8jzF0S42HhKOdIPYVdHlsr2IqhtAFDyAmVJFBj36ohzx5G1Esajntf9Im3QSGpS66yHh7AWiKhCA
RItlWlHMyN69mdTx3tIQgQ9aq4IOoFDTRQNBQQgLQiEWKoCUVRQGkU+auMB1C6yCGBQEmYO8uJb5
X30ZIBGCxCSktxQqKIuwIEO/YIRgw7xVSEIG4XhTeT4VYo3+ZWMFdlFjzqCJWcmIlgRtkSCBJ+Oa
SAFeGhS19k4+XUWOJDwo/kE5U9wTDwnn4SynLETYfSiLtfvTSAYLwc7Q6L0BIiaSy0Fn1nHSW8E4
gdvWFtxAmNrHZXOJoOdeBxE5pGEiNZl+ABJQGnym0yUp8Im5AsP0ocNLsYE0yMzSxAG460aA/kJ/
ZQNpAYn3xiJU/gKBcIwM1CCG3UJIhEkjFCQIowSf5UiiURkJCfOJEakfxonmp4pA4hwrNPvblC/a
YVr6SAPxuRfpNKF5MWEA5MCIFq5D5ISVASPBiCaIJIAingwRUEJTaIRzMbNIq8YyslMWGcEwQhQ4
IDIzaRDKW0ptgZEhJgFWhaEh6fyyMJ4BfMHM9ijZyhBikUM0gBQRApKcGEIF7SeaGp3VhKzYmVp2
dA+vKQh+yw/JltW4f+HuDOaqBCm/gr6MDD9yjGJCWEpSEUhIVJQ3PAKr25wWEf8ysIYcQO4Sh3xH
2CpQhi40ryYBbjKD4gCziiQ1EZJ7Sj4gEB5TJWwQkiRPcSE5xynqPX1VQ00NCJtDzSQEPG2EKO/z
cACefWQhEwcRWPqGih6aA9nBA8ggGE3Ph9qlrUQoqec81q5boSilEFIUBG2WwqZ97V0KpNCYFqqK
LLIkxVsLM/P90f9/8Jxwn8Y/nYrgH/MuP2GAY6tda2OiQIVQElOxGHI7wnjoLdEyyqiWeQaFOJmK
gcwQi/gPHcHXAJHulM8PhVXvmCq3D1kO9D6w2Am0AO0G4G04aJyFD8YBamxDTG6M7/J8ocnCcxzm
4k2H3HzGRZnoPjtl0ckV0BBIpIDCMQa8ZJfx0Q9dOCcRWK4fUBn9oQgwkuzDAKBwC9F4ppmeCat3
MAm5B7wiAyaJBw0BEEKWNpYlXzQdC8YyZJROFOAIsZbwIZEIs6YzjAbbRANGmhQxF11b/joFYmjp
lwIyKURQs7IIAxBwyN0qiENw0oGsiuAaP62eSDgeFoz2MwzOrxeXMy0sTaaC4GDjCmPYZODRpxj2
4Lk1S86uPGWeZ0CYeZlkma4CmlEC9Lm3BypICDmOHjBAyI1xGMYM9LJCSIBrGcJCcw288CwsRSR3
O0CG3mF143TIHBSlyCNxRqC8xwskaGOPw5Ddz0ydKWFwFgjoYDdUUYRjCuUpUdFKBiYIgXR5gIWl
UI2BOEGC0i2pe2UnJDISbFb2OsbJFiYGFJoSUAaSXYg28CG2IM2pQ2lJgGcA8HBGLucA0HREqEtF
KpFDDomGzoowabILYjCaY0jB0lSbxISYl2ThkJ3CGx1IYw3JEBAYkYVuToKiGOL1QFBUJBDSGNsg
Ep4wWEkddCyjgfCxvW8LBdEIjkkSJxCKmUk5CkkcpggEYwAKkMJ0Z3bGEhbbbSG0xpK5IOhO9nWS
LwbgP5xgbMAdVDwTa7Kd90MezoUcsO53lCHE6liG0FoSMBCUjYzO5K7IWtLKLYLltDOzmipe0Gxx
nlQwc1bLc3lYE3qM3hbmTDBLq3twsnVpQCe4wig2pyzeQ6shKVbQJtCGyFrfD4go7vG8HJJseWuE
wwpHkzN1gA7BkqYioRnoXPVneVhkgxM1qVOTZRIdLhpDJREBtJolvThFIIsidrMynOTYHjsRhsZN
xNiTwcIga7Y897LpNmNzlMKoaNYps4ohOyFCafS5fU50pXGKJwKr5J60cHQGs5MypF2Ww5JKBEnA
T04UWNdx8wQN5aO3O5fXiwWAsGNtHOYsGkIpttiOARy1srrxq8gJlBxIolQaaBKU/LrMeKb44JAb
S0MIZ0pGea7ZzOAx2S0cooKa7oGOQIGMmnAtse1uCVUmpLTyawqhVphSyNKZSF2gfkQTwOdC6ts0
nBkQpzRMw3qSwicHc787nZmQRNBTUEWCgioaOcydmrPMWCILwZhhvpdMxleOeAwz06mdkOvfghnT
qxiFC2EtqhVYMIqxqTaOS4hRiWlES2ljSVqHLSd2Zl0WUCyBuQypPMgUTC0O2FEUcQsERIgIiCR2
wGMRSZEqIWmZDQmEDU4ps5G7ERCMK0pZYcW8JfSEOuoQ4NhS84sLAzX+YgBkQgVkmoHYBANCElm6
QGAYEjIgMREgWQDCESXKSDsdepGerxohTEWOSYxNLnF7mDBz1LjCcAnWBFjsPAgRIWhrJAIfa/ip
1LPb0ayoUsdQtc6ApKWwGAwbYIoGkikiSWklgUQJAIchzN3UA0xFYooopijGxMBcM0XRLEqXp60I
UFz7RPjRj3hLofyIwQdzEPtEgUBBANq7jI9iF1GwrQdYalHUiLBEBD8TewZLuscZq84CkWS5XMMO
WUWJDxpS3dHO9zHe1i6ONAU3QqCijRrnGFyqcTozD2uO3S3YYiKcusMwpyZhitksn1xEkYJTd1/E
KCyhMGEpCDQhBP4AKVtciEDExCDRjXwYYuRnrI6hikyaxyLoutImCwS8EMHSUjgObdZgHIZCgCIe
ELOMnZERpSdWSHjCbKDE3xLAMO/FXsbOeq8gFjyJw0hLADA3NkitpKhkCZsS3DcmdJKRyvVUoXIQ
ilgwMLGCIRGMVijczLUqRRLDBwCyNgQzEoEGrqsjBSkIxERCTOL3LUgmRcupAVwIECEAKCKDQlLk
DmFlWwsTJCQiRzUslhJAQzB3Ah0IUGgcFXFMhQXJYgi2GtEkVSBCQRlDZi6OA1DrCwnhZC1sha2S
XEVKgQxGlc0it4aqhGguDppYMUCTNAiiYbFQxiMiA+H4jOvIe6eTmUcz+g4DWCR48Ro4ZDxa5CAQ
P47nkE0qoaXlROUs9IDv/C7Es8QR4XAUqIEBvAsYUR9UoSh26QYiCCwixYKqowkYAVBBoYqsgABr
QYK++CBxGtVjzCcoPCoRCSQsQoKoAqsdH08uj5xA9wMJOECYIJSnNqwBRykDRIONTApQKe6ikJgm
5aQNIK7jVAWQiCIwxgQ5PufC0tUlfijBqlZnCIapeFLooWUmxECrJFgBpVpIlirxR0Ca7ObMC3wM
J4cKBwd3UyRKoqdAirsLG0NxGZpOojAZlYMlwhkxky0Zr6sFBaqxiHQZ7S6oYAhdetAOMsK2M4GQ
rpIyRRIgjmEUGG0bIEGYqBA7LltNl0ukhNJiuYQEtQQjkaCGbBFXvOawQMthZq0S2aGRecDGE1kt
IQMyBljdXRqCL7iARUOYhP2c5hejoGfXAst4LV5QWYEbdUOvAPzQkPmmKEX2HrC1n4IU860Cf0z5
6o5TaQDiF6imgDnhFM4HbD5BFEEgB9r5vGTvREiiHxaIfD04YWsUFhEwGU/J8K8/PgD9bjEfjhRp
NJpCztLUkTgvJLeQxb2BiQFBY/FQhyCIpGQR8Q/nkTZI4h/ocex7mSp+7+JcUYDAgoiKBPShTSax
trArJBYIyslYQrDIQRIzLLQfrLDAwpLsQM2OjBlBYoIhEVgisEYLIxGDGInZCiCyCiSSoUGIkBEJ
A0yUQBUisiMgyBCCsILaA0WabXaahciDsBN1QP91EQVQkfNEfxVEQtSS9BeIgHYlUCa9eo7JVUCX
QP57lPPdLWDTBA74B9EUkUkMEc1Yg3P0fqfD5HMC63ovZotAwIxU4zr1++Jv1AuAHMRdhBBSAXKh
O1eCOmSEA4KTAc4iIdoetFzOrPIQMYyAyCSh/bBHsgdvfJalxQximliiOZkACbkCjDvmua82VJa9
dRj+6xSJ4rEkUsIBiVowWxNg2Itw6RMoISisSJEoMFDkQwctskSqCMaxGEvQpRhMIEGsdbLbYuYW
YhoNSoI6CasJg6IRSgwgUEagFiRiSJINoMzBiFkgoQTIkgVixBgxjERFEVjMa2mGUqBCqFqopCie
oyQNCSBU1EJFgE11bsoTRiiCNphSyg45GwZAESJTAnrgdfCeB9X1U8RJCRwTS6RjIRgB9S8oh4/6
tgJkjPBFraSixCSeg/wNB/S7ff5Hb9iSULpgshCIBGKutEIqlSoL4p5S1RgheJUESopCNiEIhzqM
HgTNG1h6FrIKHIiCXIK9JSYrHe0V8E5vGXQwRLnIckOcouZxIJGDEERtBwtE3RCqIhXrt196fsHh
Ip8zZInqIfOAfmF7rxQYkN0piwalAhEIsIDGKwMuDsobKhYOEpBxGRSR5acFEVJKI+6UTL2WgZAV
UUqSgREgggIMiiT1wBMMCpQKKJuWAIBg95ADnSkPgVRAg4oDGB0CeQpUMoQiu0o8FUWp5eK2F6N0
gmxhqzUMC+271SAcvIDFdayoXCQ8SUaLcmGNp4sUx/CkERi5QkI2ZUyT1r3QdvIPCxIxjGMbHhW4
UARNOaoA7AUP4BuUah9jMCxUklCIRHzIUWixhaUL2rxDmqB4xTFQwSweA4JIsFJYFvRHGirACphQ
L9YBYQxF9yBY6jaJxJ+X4T6u1UrVJDgjXD8GLAMSXgqHOdxBMXkUNgkFg7J3WK2wLHjEoOhXUifM
cp4jhEMYayqQ1gkUI2CGgLvEqZJIiQHEygvjpMtJAGi8crk4ArkElLtB/MJ6keJbjDQd8R9iPQJQ
mhVDGKKbhNqfZEWSKroBUBusQAUeEUXEE/aCsUBvzvOs0FIUxk8KeKl0CAB+ISwoj7vYJSAXn26H
ksXpL3dyQikEgrEVEhNJNkPf8E9lrrgQUeQaIkADkD9ggQ0RED+pCKDJtCKfzoTjTpR8AB4TwKT6
fu4aE3pSUfgfjQE6RjJOq5T88Jn8HdATXBwRA5gTQ/3ZvTYWxFRyuohdJwghr8kQiwWAJCITrP+c
SgtAzf1qKlEjYouYpJ0OqXnL7LJ9r/1FKIEhtF5CCjvgPCKpb8zJBSuOCv87kiKBcTzkE+EjQeko
rpwh8dgX6vqnnyRuFwKYP76E1isvwNtoXuoT2Ck5D1ixlEiRMaCMCUuwLeOmDwU2OV+NH+rRdHym
HuMJFCQjDkeZGAHYLtVIo5i9gQYWRXsd7ncIFkLLbAvqDxTAFBMUIB/CAugRP6iJ3e8JcDKayAlH
kQkgsIxiyJYQ+MKHxxihJHi/LQriRfQntpXPk8wkQwsJ3CknraswEtTCQJSoDIgj5y2EUQUk/16A
nrNpolWViQEh9AneB5mG8u4hCA7lH5pqR/COrYGuevrE/kX4l5lyAJpPlEoV+MTqAPPx9B+GNY0X
9wp8QmGEHlE4RfgFwE7AQ+lEX8YmlVfcR0g6UPkUPWJgj9AnaLyIfUhqHP1KrpXdHEUXuUeoSgDv
QDnAPf56Mlee/4j0FNHwnyXM3EHGBJVfBYsEKlwiNUFJLlKoeV9bXnSc4HkAPqRAto5dELFocion
CgHCJ5hX6VcNu7NTfekxDIoUWREu/zMGFKwk/1XLsWLMf5Gi5iY7w7VTtAB76PacnDwpBgHEQUrt
LVZi2UiMICRKJD4xYIXTYPUTI30liWLmbESfnCkh2iqOmzetJpDuND3cPGX+ZVaD+ikkX/PRFcrI
igpV3H0a/IPIL7q+0UpdGIYleQTbTxKmrVCyAexRTN70dDAYNAX+5id8FQfJAVcLeB8BT/9fz7jU
C5C44g7An5yOMDTP9JcLUSSdoQNoaB0KJP+7i0XDQJrNT/IlylNhsMxkowxFNamZBdAIixPfBoxk
DhNZVogFy97hcGD3uk5jqLrs8Ur91DXf0PUWzUBB8WoT2onmAN4B7VTMfOqufaOlQgkisBkSEP5Y
h5bJQtoI7lANZw7iHUhFMEj8o8D6y6IYpz+Q91Dqw1oRTZVPsUNgj8AnOJxD8Qml9GVSq/KsAzK2
8QjDZDpOSlvnLA9aMYLD3T0xp1yQi8FlqjHdxrODVd6ZBYgSlQ3DGky1o2iSNSqwkF9NMkcdgoq/
V9vXwu3nl9Z5mDmNH9/lYKjYt0phwv7QQLga/V0GELQEQFYwWSsrAWRQGDI1BUpKqCplIzvVUfnN
Tc1BP9kPxEqSwzek+hwVQXyYwSWgtiaTFQxoJAiq0AYOJwz+INYJxcM+NRPzRUxMvmoAO6a/P84/
vX+JH6RMRP5SB+bk4/GLVMdwYdslwDmhJIAJYhSR6VDyiXDTADzxF/lnvQKgv2esotBefx0VVfcP
1bp8fgqAHdEKJonBTLszwdB/NSSm90rD8SYfdOt4XRxbPNmGP3t1VwSs5yb6XMD1JUwP2jKs24Ju
QopEcG5JVqCAZBBU0WlW0wawfuYAbdlGAxQM0MbNrJUGjSRtBqmliVCa0WCaqIi4lJ16tCfVOZsy
A24aECwpA+qCe7rqoNMTq8Z1+j0ApxNmgMVE64CHKid5dG8UzEpAuLBA9b8Z/rpaQP37tvyUOUOE
R/dC8+2FVD9KVSFHhgW5j2hRviegTyIfFxCvxCQVOhHoNEdQzBPZI/ZEH50j2J7SUPeE3nMa9mCf
uHw83Dg4BCBx9z0xh2NhwNtZ95nDqrozlPvsDD5w+mYBoCen9eRunxTE4E1vY49tXwTrgmR+teu7
ulpRpNYGP7+xPB0RvzMb17m5jKVFG2ZJbMXplkHLMxIoWS5r82MFUIMYcRVe4LXKGWuWL3jKPcK1
dNEaJjY4ektr+76Ms/+mYH4jx8fAbSgiooWwtRopWeUNmFHXoenwzCqCh9/X5PrYbKmeN6N6+NIo
YgtADAKMQ0VzWUw6zpoWpR6Q2P1N3RoUdlNVRSSShooKdwAVu48ObwcYXOs2mohHmF+VXjHlXzXD
yCcKFO51Fvh4hPIuJ2oH8dKbRFQMwCexjsyrVmFyIinq31gpseohUksESKUP6HzO16RALtClFAQg
QgSJhBKFTwlAtgWhOhSxtEbCtC/xgKQgk5B6QoXiCRAT3xIJ4BOUAHpX8AWAOkT7xc01DBIdiOhA
D6RVf/08gfusnIInKJwiJ1r9B1B6+8p1LupN6B5VJAlvIe6fToXcJsfgBDyGhQIQeqqVh2itAAgf
uEgImszVQpApkYnOJiJceRECHwh3xK6xA7BMh8PgwNJDeeG9gSgh3yO5Ce9igyT9rRzmDDEcC+th
h7p8F4HAseWlwE1ZW73vOIW7vLhVhp2MDAfjinV6buXFm9q+vzqaJNOGeBD4QZCIpu3B+W1mZro1
SpVPdHHeil5mhQm5tyjo5CmKhmxwlwxTY22NTIna6/XHdHftHerksG9fL5ZpMlekI8S6kzJoJCl/
wNYOjoShD7TAkgqHya2SqLIUDpxHTQIqec5QpEkFsSNoRHTPzmTOSsU4UcOFaUkJkkaNypGgooYa
WEbYY2RFvhkglvE5FYVvBTM1s2dbGctDjZcRjwefRQ4Rtono8WM/NjBlEKL4kJQq4gGxWxUDuBdx
nv9UW14pdHI16nx+NeGWcxV1jwQxkJxwX1ZhMCMShJ4s9Pf0uedKnob82G9NysK4uM6eCrEcRENY
lK9QcQB9olCYCamL2idQ/tS/sk7SZRBn4e/X37/XDKmtmi8wCuTrOs32OI0kcZq0UsNrwCjuDY8o
iHSz0mEwfWsDK0mRSIYopN4nwj6BvBF8aBHWgER0a9flQpqOKjo1IG3ur0Os/3i9glCdQmGld4mx
HmEDpE41OQI9yP7i0+/7v2BafCKifyvg/EKhHgeg0A/VFkAYEQkVChEV9eCHSHoFOUB9KhY72lM9
j9aiYeJHxo+diECCBCCwSCp4xOZE1nDl4gDhRPiR2bT/HcHYJ5RdaHlUNKuXuC8aEAQHigqG7mGA
QlHvCbUfjFyEufCh8YuIp4xXUjziepfEuIBziIaE2g+6RU2o7ROccRdgnjUO8Gxdy4Ii94X7vmNY
FRX8dAq2LCI/gQEiI/CKEAD7Bgo3SREAjIon4z94MILAg/Qv4b0Qho/32sMCAhIDAIASBGxF5Dwn
wcfk+pMSEAQiICkE8/lf71VVoaIxhbHT2BJ6MMRTLFSKCxRkCFkkyEqT0f9hkCTabGFgEuOCKIOK
EkYQDARMFDAeLEFxIgHxniBzQyJhlnJdQjEpu+TnOAGweU+uf2+3YFpIhNhxm1OCBIpACDr9Y2H/
/i7kinChIclOvjQ=
