From 0e713f7692d49e2cd5d685104a50419c01a2f074 Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Fri, 29 May 2020 15:57:22 +0200 Subject: [PATCH] WIP: to use AGG font renderer to create a coverage bitmap --- notes.md | 78 ++++++++++++++++++++ notes.txt | 34 --------- src/font_render_capi.cpp | 5 +- src/font_render_lcd.h | 34 ++++----- tests/agg_font_render_test.cpp | 127 +++++++++++++++++++++++++++++++++ 5 files changed, 225 insertions(+), 53 deletions(-) create mode 100644 notes.md delete mode 100644 notes.txt create mode 100644 tests/agg_font_render_test.cpp diff --git a/notes.md b/notes.md new file mode 100644 index 00000000..536ecfe6 --- /dev/null +++ b/notes.md @@ -0,0 +1,78 @@ +```c +stbtt_InitFont + +stbtt_ScaleForMappingEmToPixels x 3 +stbtt_ScaleForPixelHeight +stbtt_BakeFontBitmap +stbtt_GetFontVMetrics x 2 + +typedef struct { + unsigned short x0, y0, x1, y1; // coordinates of bbox in bitmap + float xoff, yoff, xadvance; +} stbtt_bakedchar; + +struct RenImage { + RenColor *pixels; + int width, height; +}; + +typedef struct { + RenImage *image; + stbtt_bakedchar glyphs[256]; +} GlyphSet; + +struct RenFont { + void *data; + stbtt_fontinfo stbfont; + GlyphSet *sets[MAX_GLYPHSET]; + float size; + int height; +}; + +``` + +The function stbtt_BakeFontBitmap is used to write bitmap data into set->image->pixels (where set is a GlyphSet). +Note that set->image->pixels need data in RGB format. After stbtt_BakeFontBitmap call the bitmap data are converted into RGB. +With a single call many glyphs corresponding to a range of codepoints, all in a +single image. + +## STB truetype font metrics + +stbtt_ScaleForPixelHeight takes a float 'height' and returns height / (ascent - descent). + +stbtt_ScaleForMappingEmToPixels take a float 'pixels' and returns pixels / unitsPerEm. + +### Computing RenFont + +When loading a font, in renderer.c, the font->height is determined as: + +```c +int ascent, descent, linegap; +stbtt_GetFontVMetrics(&font->stbfont, &ascent, &descent, &linegap); +float scale = stbtt_ScaleForMappingEmToPixels(&font->stbfont, font->size); +font->height = (ascent - descent + linegap) * scale + 0.5; +``` + +so, mathematically + +```c +font->height = (ascent - descent + linegap) * font->size / unitsPerEm + 0.5; +``` + +**TO DO**: find out for what font->height is actually used. + +### Call to BakeFontBitmap + +In the same file, renderer.c, to create the glyphset image it computes: + +```c +// Using stbtt functions +float s = ScaleForMappingEmToPixels(1) / ScaleForPixelHeight(1); +``` + +so 's' is actually equal to (ascent - descent) / unitsPerEm. + +Then BakeFontBitmap is called and `font->size * s` is used for the pixel_height argument. +So BakeFontBitmap gets, for pixel_height, (ascent - descent) * font->size / unitsPerEm. +This is equal almost equal to font->height except the 0.5, the missing linegap calculation +and the fact that this latter is an integer instead of a float. diff --git a/notes.txt b/notes.txt deleted file mode 100644 index e5bf6bea..00000000 --- a/notes.txt +++ /dev/null @@ -1,34 +0,0 @@ -stbtt_InitFont - -stbtt_ScaleForMappingEmToPixels x 3 -stbtt_ScaleForPixelHeight -stbtt_BakeFontBitmap -stbtt_GetFontVMetrics x 2 - -struct RenImage { - RenColor *pixels; - int width, height; -}; - -typedef struct { - RenImage *image; - stbtt_bakedchar glyphs[256]; -} GlyphSet; - -struct RenFont { - void *data; - stbtt_fontinfo stbfont; - GlyphSet *sets[MAX_GLYPHSET]; - float size; - int height; -}; - -typedef struct { - unsigned short x0, y0, x1, y1; // coordinates of bbox in bitmap - float xoff, yoff, xadvance; -} stbtt_bakedchar; - -The function stbtt_BakeFontBitmap is used to write bitmap data into set->image->pixels (where set is a GlyphSet). -Note that set->image->pixels need data in RGB format. After stbtt_BakeFontBitmap call the bitmap data are converted into RGB. -With a single call many glyphs corresponding to a range of codepoints, all in a -single image. diff --git a/src/font_render_capi.cpp b/src/font_render_capi.cpp index 9423f5a9..37e55441 100644 --- a/src/font_render_capi.cpp +++ b/src/font_render_capi.cpp @@ -21,6 +21,7 @@ int FontRendererDrawText(FontRenderer *fr_, RenColor *pixels, int width, int hei font_renderer_lcd *font_renderer = (font_renderer_lcd *) fr_; agg::rendering_buffer ren_buf((agg::int8u *) pixels, width, height, -pitch); const agg::rgba8 agg_color(color.r, color.g, color.b); - double new_x = font_renderer->render_text(ren_buf, (double) text_size, agg_color, (double) x, (double) (height - y), text); - return int(new_x); + double xd = x, yd = (height - y - text_size); + font_renderer->render_text(ren_buf, (double) text_size, agg_color, xd, yd, text); + return int(xd); } diff --git a/src/font_render_lcd.h b/src/font_render_lcd.h index f831a921..fa63dba5 100644 --- a/src/font_render_lcd.h +++ b/src/font_render_lcd.h @@ -63,11 +63,11 @@ public: } template - double draw_text(Rasterizer& ras, Scanline& sl, - RenSolid& ren_solid, const agg::rgba8 color, - const char* text, - double x, double y, double height, - unsigned subpixel_scale) + void draw_text(Rasterizer& ras, Scanline& sl, + RenSolid& ren_solid, const agg::rgba8 color, + const char* text, + double& x, double& y, double height, + unsigned subpixel_scale) { const double scale_x = 100; @@ -77,14 +77,14 @@ public: const char* p = text; - x *= subpixel_scale; - double start_x = x; + double x_lcd = x * subpixel_scale; + double start_x_lcd = x_lcd; while(*p) { if(*p == '\n') { - x = start_x; + x_lcd = start_x_lcd; y -= height * 1.25; ++p; continue; @@ -95,7 +95,7 @@ public: { if(m_kerning) { - m_fman.add_kerning(&x, &y); + m_fman.add_kerning(&x_lcd, &y); } m_fman.init_embedded_adaptors(glyph, 0, 0); @@ -105,19 +105,20 @@ public: ras.reset(); m_mtx.reset(); m_mtx *= agg::trans_affine_scaling(1.0 / scale_x, 1); - m_mtx *= agg::trans_affine_translation(start_x + x/scale_x, ty); + m_mtx *= agg::trans_affine_translation(start_x_lcd + x_lcd / scale_x, ty); ras.add_path(m_trans); ren_solid.color(color); agg::render_scanlines(ras, sl, ren_solid); } // increment pen position - x += glyph->advance_x; + x_lcd += glyph->advance_x; y += glyph->advance_y; } ++p; } - return x / (subpixel_scale * scale_x); + // Update x value befor returning. + x = x_lcd / subpixel_scale; } void clear(agg::rendering_buffer& ren_buf, const agg::rgba8 color) { @@ -126,14 +127,14 @@ public: ren_base.clear(color); } - double render_text(agg::rendering_buffer& ren_buf, + void render_text(agg::rendering_buffer& ren_buf, const double text_size, const agg::rgba8 text_color, double x, double y, const char *text) { if (!m_font_loaded) { - return y; + return; } agg::scanline_u8 sl; @@ -146,7 +147,7 @@ public: agg::pixfmt_bgra32 pf(ren_buf); base_ren_type ren_base(pf); renderer_solid ren_solid(ren_base); - y = draw_text(ras, sl, ren_solid, text_color, text, x, y, text_size, 1); + draw_text(ras, sl, ren_solid, text_color, text, x, y, text_size, 1); } else { @@ -158,8 +159,7 @@ public: pixfmt_lcd_type pf_lcd(ren_buf, lut, m_gamma_lut); agg::renderer_base ren_base_lcd(pf_lcd); agg::renderer_scanline_aa_solid > ren_solid_lcd(ren_base_lcd); - y = draw_text(ras, sl, ren_solid_lcd, text_color, text, x, y, text_size, 3); + draw_text(ras, sl, ren_solid_lcd, text_color, text, x, y, text_size, 3); } - return y; } }; diff --git a/tests/agg_font_render_test.cpp b/tests/agg_font_render_test.cpp new file mode 100644 index 00000000..46258b0d --- /dev/null +++ b/tests/agg_font_render_test.cpp @@ -0,0 +1,127 @@ +#include +#include +#include + +#include "SDL_surface.h" +#include "font_render_lcd.h" + +#define MAX_GLYPHSET 256 + +typedef struct { uint8_t b, g, r, a; } RenColor; +typedef struct { int x, y, width, height; } RenRect; + +struct RenImage { + RenColor *pixels; + int width, height; +}; + +struct GlyphSetA { + RenImage *image; + // FIXME: add glyphs information for AGG implementation + // stbtt_bakedchar glyphs[256]; +}; + +struct RenFontA { + font_renderer_lcd *renderer; + float size; + int height; +}; + +static void* check_alloc(void *ptr) { + if (!ptr) { + fprintf(stderr, "Fatal error: memory allocation failed\n"); + exit(EXIT_FAILURE); + } + return ptr; +} + +RenImage* ren_new_image(int width, int height) { + assert(width > 0 && height > 0); + RenImage *image = malloc(sizeof(RenImage) + width * height * sizeof(RenColor)); + check_alloc(image); + image->pixels = (void*) (image + 1); + image->width = width; + image->height = height; + return image; +} + +void ren_free_image(RenImage *image) { + free(image); +} + +RenFontA* ren_load_font_agg(const char *filename, float size) { + RenFont *font = NULL; + + /* init font */ + font = check_alloc(calloc(1, sizeof(RenFontA))); + font->size = size; + + font->renderer = new font_renderer_lcd(true, false, true, 1.8); + font->renderer->load_font(filename); + + // FIXME: figure out correct calculation for font->height with + // ascent, descent and linegap. + fit->height = size; + return font; +} + +static GlyphSet* load_glyphset_agg(RenFont *font, int idx) { + GlyphSet *set = check_alloc(calloc(1, sizeof(GlyphSet))); + + /* init image */ + int width = 128; + int height = 128; +retry: + set->image = ren_new_image(width, height); + + /* load glyphs */ + float s = + stbtt_ScaleForMappingEmToPixels(&font->stbfont, 1) / + stbtt_ScaleForPixelHeight(&font->stbfont, 1); + int res = stbtt_BakeFontBitmap( + font->data, 0, font->size * s, (void*) set->image->pixels, + width, height, idx * 256, 256, set->glyphs); + + /* retry with a larger image buffer if the buffer wasn't large enough */ + if (res < 0) { + width *= 2; + height *= 2; + ren_free_image(set->image); + goto retry; + } + + /* adjust glyph yoffsets and xadvance */ + int ascent, descent, linegap; + stbtt_GetFontVMetrics(&font->stbfont, &ascent, &descent, &linegap); + float scale = stbtt_ScaleForMappingEmToPixels(&font->stbfont, font->size); + int scaled_ascent = ascent * scale + 0.5; + for (int i = 0; i < 256; i++) { + set->glyphs[i].yoff += scaled_ascent; + set->glyphs[i].xadvance = floor(set->glyphs[i].xadvance); + } + + /* convert 8bit data to 32bit */ + for (int i = width * height - 1; i >= 0; i--) { + uint8_t n = *((uint8_t*) set->image->pixels + i); + set->image->pixels[i] = (RenColor) { .r = 255, .g = 255, .b = 255, .a = n }; + } + + return set; +} + +int main(int argc, char *argv[]) { + if (argc < 3) { + fprintf(stderr, "usage: %s \n", argv[0]); + return 1; + } + const char *filename = argv[1]; + const int size = atoi(argv[2]); + RenFont *font = ren_load_font(filename, size); + GlyphSet *set = load_glyphset(font, 0); + SDL_Surface *surface = SDL_CreateRGBSurfaceWithFormatFrom( + set->image->pixels, + set->image->width, set->image->height, 32, set->image->width * 4, + SDL_PIXELFORMAT_RGBA32); + SDL_SaveBMP(surface, "stb-glyphset.bmp"); + return 0; +}