grub-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH v3 1/2] font: Add font scaling feature to grub_font_draw_glyph()


From: Zhang Boyang
Subject: [PATCH v3 1/2] font: Add font scaling feature to grub_font_draw_glyph()
Date: Wed, 1 Jun 2022 16:23:08 +0800

This patch adds an argument 'scale' to grub_font_draw_glyph(). If
scale > 1, then the function will create a new scaled bitmap of the
drawing glyph, and draws the scaled glyph. The scaled bitmap is cached
in the glyph itself, so it can be reused if same glyph is used many
times.

To avoid interger overflow during scaling, this patch intruduces
dimension limits to font files, described by GRUB_FONT_MAX_DIMENSION.
The scaled glyph should also obey this limit. In addition, scale factor
is also limited by GRUB_FONT_MAX_SCALE.

Signed-off-by: Zhang Boyang <zhangboyang.id@gmail.com>
---
 grub-core/commands/videotest.c |   4 +-
 grub-core/font/font.c          | 120 ++++++++++++++++++++++++++++++---
 grub-core/gfxmenu/font.c       |   2 +-
 grub-core/term/gfxterm.c       |   2 +-
 include/grub/font.h            |  16 ++++-
 5 files changed, 131 insertions(+), 13 deletions(-)

diff --git a/grub-core/commands/videotest.c b/grub-core/commands/videotest.c
index ac145afc2..d95ee411d 100644
--- a/grub-core/commands/videotest.c
+++ b/grub-core/commands/videotest.c
@@ -87,7 +87,7 @@ grub_cmd_videotest (grub_command_t cmd __attribute__ 
((unused)),
       return grub_error (GRUB_ERR_BAD_FONT, "no font loaded");
 
     glyph = grub_font_get_glyph (fixed, '*');
-    grub_font_draw_glyph (glyph, color, 200 ,0);
+    grub_font_draw_glyph (glyph, color, 200, 0, 1);
 
     color = grub_video_map_rgb (255, 255, 255);
 
@@ -148,7 +148,7 @@ grub_cmd_videotest (grub_command_t cmd __attribute__ 
((unused)),
       {
        color = grub_video_map_color (i);
        palette[i] = color;
-       grub_font_draw_glyph (glyph, color, 16 + i * 16, 220);
+       grub_font_draw_glyph (glyph, color, 16 + i * 16, 220, 1);
       }
   }
 
diff --git a/grub-core/font/font.c b/grub-core/font/font.c
index 42189c325..46ffec84c 100644
--- a/grub-core/font/font.c
+++ b/grub-core/font/font.c
@@ -137,6 +137,8 @@ ascii_glyph_lookup (grub_uint32_t code)
          ascii_font_glyph[current]->offset_x = 0;
          ascii_font_glyph[current]->offset_y = -2;
          ascii_font_glyph[current]->device_width = 8;
+         ascii_font_glyph[current]->scaled_bitmap = NULL;
+         ascii_font_glyph[current]->scale = 0;
          ascii_font_glyph[current]->font = NULL;
 
          grub_memcpy (ascii_font_glyph[current]->bitmap,
@@ -172,6 +174,8 @@ grub_font_loader_init (void)
   unknown_glyph->offset_x = 0;
   unknown_glyph->offset_y = -3;
   unknown_glyph->device_width = 8;
+  unknown_glyph->scaled_bitmap = NULL;
+  unknown_glyph->scale = 0;
   grub_memcpy (unknown_glyph->bitmap,
               unknown_glyph_bitmap, sizeof (unknown_glyph_bitmap));
 
@@ -646,6 +650,20 @@ grub_font_load (const char *filename)
       goto fail;
     }
 
+  if (font->max_char_width <= 0
+      || font->max_char_width > GRUB_FONT_MAX_DIMENSION
+      || font->max_char_height <= 0
+      || font->max_char_height > GRUB_FONT_MAX_DIMENSION
+      || font->ascent <= 0
+      || font->ascent > GRUB_FONT_MAX_DIMENSION
+      || font->descent <= 0
+      || font->descent > GRUB_FONT_MAX_DIMENSION)
+    {
+      grub_error (GRUB_ERR_BAD_FONT,
+                 "invalid font file: bad font metrics");
+      goto fail;
+    }
+
   /* Add the font to the global font registry.  */
   if (register_font (font) != 0)
     goto fail;
@@ -738,7 +756,7 @@ grub_font_get_glyph_internal (grub_font_t font, 
grub_uint32_t code)
       grub_uint16_t height;
       grub_int16_t xoff;
       grub_int16_t yoff;
-      grub_int16_t dwidth;
+      grub_uint16_t dwidth;
       int len;
 
       if (index_entry->glyph)
@@ -760,7 +778,18 @@ grub_font_get_glyph_internal (grub_font_t font, 
grub_uint32_t code)
          || 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)
+         || read_be_uint16 (font->file, &dwidth) != 0)
+       {
+         remove_font (font);
+         return 0;
+       }
+
+      /* Reject illegal glyphs.  */
+      if (width > font->max_char_width
+         || height > font->max_char_height
+         || xoff < -GRUB_FONT_MAX_DIMENSION || xoff > GRUB_FONT_MAX_DIMENSION
+         || yoff < -GRUB_FONT_MAX_DIMENSION || yoff > GRUB_FONT_MAX_DIMENSION
+         || dwidth > GRUB_FONT_MAX_DIMENSION)
        {
          remove_font (font);
          return 0;
@@ -780,6 +809,8 @@ grub_font_get_glyph_internal (grub_font_t font, 
grub_uint32_t code)
       glyph->offset_x = xoff;
       glyph->offset_y = yoff;
       glyph->device_width = dwidth;
+      glyph->scaled_bitmap = NULL;
+      glyph->scale = 0;
 
       /* Don't try to read empty bitmaps (e.g., space characters).  */
       if (len != 0)
@@ -787,6 +818,7 @@ grub_font_get_glyph_internal (grub_font_t font, 
grub_uint32_t code)
          if (grub_file_read (font->file, glyph->bitmap, len) != len)
            {
              remove_font (font);
+             grub_free (glyph->scaled_bitmap);
              grub_free (glyph);
              return 0;
            }
@@ -1054,6 +1086,8 @@ grub_font_dup_glyph (struct grub_font_glyph *glyph)
     return NULL;
   grub_memcpy (ret, glyph, sizeof (*ret)
               + (glyph->width * glyph->height + 7) / 8);
+  ret->scaled_bitmap = NULL;
+  ret->scale = 0;
   return ret;
 }
 #endif
@@ -1524,11 +1558,22 @@ grub_font_construct_glyph (grub_font_t hinted_font,
 
   if (max_glyph_size < sizeof (*glyph) + (bounds.width * bounds.height + 
GRUB_CHAR_BIT - 1) / GRUB_CHAR_BIT)
     {
-      grub_free (glyph);
+      if (glyph)
+       {
+         grub_free (glyph->scaled_bitmap);
+         grub_free (glyph);
+       }
       max_glyph_size = (sizeof (*glyph) + (bounds.width * bounds.height + 
GRUB_CHAR_BIT - 1) / GRUB_CHAR_BIT) * 2;
       if (max_glyph_size < 8)
        max_glyph_size = 8;
       glyph = grub_malloc (max_glyph_size);
+      if (glyph)
+       {
+         glyph->scaled_bitmap = NULL;
+         glyph->scale = 0;
+       }
+      else
+       max_glyph_size = 0;
     }
   if (!glyph)
     {
@@ -1536,6 +1581,7 @@ grub_font_construct_glyph (grub_font_t hinted_font,
       return main_glyph;
     }
 
+  grub_free (glyph->scaled_bitmap);
   grub_memset (glyph, 0, sizeof (*glyph)
               + (bounds.width * bounds.height
                  + GRUB_CHAR_BIT - 1) / GRUB_CHAR_BIT);
@@ -1545,6 +1591,8 @@ grub_font_construct_glyph (grub_font_t hinted_font,
   glyph->height = bounds.height;
   glyph->offset_x = bounds.x;
   glyph->offset_y = bounds.y;
+  glyph->scaled_bitmap = NULL;
+  glyph->scale = 0;
 
   if (glyph_id->attributes & GRUB_UNICODE_GLYPH_ATTRIBUTE_MIRROR)
     grub_font_blit_glyph_mirror (glyph, main_glyph,
@@ -1563,12 +1611,50 @@ grub_font_construct_glyph (grub_font_t hinted_font,
   return glyph;
 }
 
+/* Try to scale the glyph bitmap.  If failed, glyph will not be touched.
+   If succeeded, scaled bitmap will be stored at glyph->scaled_bitmap , and
+   the effective scale factor will be stored at glyph->scale .  */
+static void
+try_scale_glyph (struct grub_font_glyph * glyph, int scale)
+{
+  int i, j;
+  grub_uint8_t *bitmap;
+
+  if (glyph->scale == scale)
+    return;
+
+  if (scale > GRUB_FONT_MAX_SCALE
+      || glyph->height * scale > GRUB_FONT_MAX_DIMENSION
+      || glyph->width * scale > GRUB_FONT_MAX_DIMENSION)
+    return;
+
+  bitmap = grub_zalloc (((glyph->width * scale) *
+                        (glyph->height * scale) + 7) / 8);
+  if (!bitmap)
+    return;
+
+  /* FIXME: suboptimal.  */
+  for (i = 0; i < glyph->height * scale; i++)
+    for (j = 0; j < glyph->width * scale; j++)
+      {
+       int n = (i / scale) * glyph->width + (j / scale);
+       int m = i * (glyph->width * scale) + j;
+       int pixel = (glyph->bitmap[n / 8] >> (7 - n % 8)) & 1;
+       bitmap[m / 8] |= pixel << (7 - m % 8);
+      }
+  
+  grub_free (glyph->scaled_bitmap);
+  glyph->scaled_bitmap = bitmap;
+  glyph->scale = scale;
+}
+
 /* 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.  */
 grub_err_t
 grub_font_draw_glyph (struct grub_font_glyph * glyph,
-                     grub_video_color_t color, int left_x, int baseline_y)
+                     grub_video_color_t color, int left_x, int baseline_y,
+                     int scale)
 {
   struct grub_video_bitmap glyph_bitmap;
 
@@ -1601,11 +1687,29 @@ grub_font_draw_glyph (struct grub_font_glyph * glyph,
                          &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;
+  if (scale > 1)
+    {
+      try_scale_glyph (glyph, scale);
+      if (glyph->scale == scale)
+       {
+         glyph_bitmap.mode_info.width = glyph->width * scale;
+         glyph_bitmap.mode_info.height = glyph->height * scale;
+         glyph_bitmap.mode_info.pitch = glyph->width * scale;
+         glyph_bitmap.data = glyph->scaled_bitmap;
+       }
+      else
+       {
+         /* Scaled bitmap not suitable, fallback to no-scale.  */
+         scale = 1;
+       }
+    }
+
+  int bitmap_left = left_x + glyph->offset_x * scale;
+  int bitmap_bottom = baseline_y - glyph->offset_y * scale;
+  int bitmap_top = bitmap_bottom - glyph->height * scale;
 
   return grub_video_blit_bitmap (&glyph_bitmap, GRUB_VIDEO_BLIT_BLEND,
                                 bitmap_left, bitmap_top,
-                                0, 0, glyph->width, glyph->height);
+                                0, 0,
+                                glyph->width * scale, glyph->height * scale);
 }
diff --git a/grub-core/gfxmenu/font.c b/grub-core/gfxmenu/font.c
index 756c24f20..ed59ca954 100644
--- a/grub-core/gfxmenu/font.c
+++ b/grub-core/gfxmenu/font.c
@@ -67,7 +67,7 @@ grub_font_draw_string (const char *str, grub_font_t font,
          err = grub_errno;
          goto out;
        }
-      err = grub_font_draw_glyph (glyph, color, x, baseline_y);
+      err = grub_font_draw_glyph (glyph, color, x, baseline_y, 1);
       if (err)
        goto out;
       x += glyph->device_width;
diff --git a/grub-core/term/gfxterm.c b/grub-core/term/gfxterm.c
index 3c468f459..4512dee6f 100644
--- a/grub-core/term/gfxterm.c
+++ b/grub-core/term/gfxterm.c
@@ -656,7 +656,7 @@ paint_char (unsigned cx, unsigned cy)
   /* Render glyph to text layer.  */
   grub_video_set_active_render_target (text_layer);
   grub_video_fill_rect (bgcolor, x, y, width, height);
-  grub_font_draw_glyph (glyph, color, x, y + ascent);
+  grub_font_draw_glyph (glyph, color, x, y + ascent, 1);
   grub_video_set_active_render_target (render_target);
 
   /* Mark character to be drawn.  */
diff --git a/include/grub/font.h b/include/grub/font.h
index 708fa42ac..8fc646713 100644
--- a/include/grub/font.h
+++ b/include/grub/font.h
@@ -24,6 +24,13 @@
 #include <grub/file.h>
 #include <grub/unicode.h>
 
+/* Limit font dimensions to avoid interger overflow. Because some font metrics
+   may be derived from this limit, we must reserve some space for arithmetics,
+   so this value can't be too high.  When scaling fonts, this limit should also
+   applies to the font after scaling.  */
+#define GRUB_FONT_MAX_DIMENSION 500
+#define GRUB_FONT_MAX_SCALE 10
+
 /* 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.  */
@@ -79,6 +86,12 @@ struct grub_font_glyph
   /* Number of pixels to advance to start the next character.  */
   grub_uint16_t device_width;
 
+  /* Pointer to cached scaled bitmap.  Allocated during a scaling drawing.  */
+  grub_uint8_t *scaled_bitmap;
+
+  /* The scale factor for cached scaled bitmap.  */
+  grub_uint16_t scale;
+
   /* 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.
@@ -141,7 +154,8 @@ struct grub_font_glyph *EXPORT_FUNC 
(grub_font_get_glyph_with_fallback) (grub_fo
 
 grub_err_t EXPORT_FUNC (grub_font_draw_glyph) (struct grub_font_glyph *glyph,
                                               grub_video_color_t color,
-                                              int left_x, int baseline_y);
+                                              int left_x, int baseline_y,
+                                              int scale);
 
 int
 EXPORT_FUNC (grub_font_get_constructed_device_width) (grub_font_t hinted_font,
-- 
2.30.2




reply via email to

[Prev in Thread] Current Thread [Next in Thread]