Removed font renderer.

This commit is contained in:
Adam Harrison
2021-09-24 11:22:39 -04:00
parent 8c32950f4b
commit e25f2e9c5c
24 changed files with 420 additions and 3127 deletions
+240 -280
View File
@@ -2,37 +2,21 @@
#include <stdbool.h>
#include <assert.h>
#include <math.h>
#include "font_renderer.h"
#include <ft2build.h>
#include <freetype/ftlcdfil.h>
#include <freetype/ftoutln.h>
#include FT_FREETYPE_H
#include "renderer.h"
#include "renwindow.h"
#define DIVIDE_BY_255_SIGNED(x, sign_val) (((x) + (sign_val) + ((x)>>8)) >> 8)
#define DIVIDE_BY_255(x) DIVIDE_BY_255_SIGNED(x, 1)
#define MAX_GLYPHSET 256
#define REPLACEMENT_CHUNK_SIZE 8
struct RenImage {
RenColor *pixels;
int width, height;
};
struct GlyphSet {
FR_Bitmap *image;
FR_Bitmap_Glyph_Metrics glyphs[256];
};
typedef struct GlyphSet GlyphSet;
/* The field "padding" below must be there just before GlyphSet *sets[MAX_GLYPHSET]
because the field "sets" can be indexed and writted with an index -1. For this
reason the "padding" field must be there but is never explicitly used. */
struct RenFont {
GlyphSet *padding;
GlyphSet *sets[MAX_GLYPHSET];
float size;
int height;
int space_advance;
FR_Renderer *renderer;
};
#define SUBPIXEL_BITMAPS_CACHED 3
static RenWindow window_renderer = {0};
static FT_Library library;
static void* check_alloc(void *ptr) {
if (!ptr) {
@@ -42,6 +26,28 @@ static void* check_alloc(void *ptr) {
return ptr;
}
/************************* Fonts *************************/
typedef struct {
unsigned short x0, x1, y0, y1;
short bitmap_left, bitmap_top;
float xadvance;
} GlyphMetric;
typedef struct {
SDL_Surface* surface[SUBPIXEL_BITMAPS_CACHED];
GlyphMetric metrics[256];
} GlyphSet;
typedef struct RenFont {
FT_Face face;
GlyphSet* sets[MAX_GLYPHSET];
float size;
short max_height, space_advance, tab_advance;
bool subpixel;
ERenFontHinting hinting;
char path[0];
} RenFont;
static const char* utf8_to_codepoint(const char *p, unsigned *dst) {
unsigned res, n;
@@ -59,213 +65,206 @@ static const char* utf8_to_codepoint(const char *p, unsigned *dst) {
return p + 1;
}
void ren_cp_replace_init(CPReplaceTable *rep_table) {
rep_table->size = 0;
rep_table->replacements = NULL;
static int font_set_load_options(RenFont* font) {
switch (font->hinting) {
case FONT_HINTING_SLIGHT: return FT_LOAD_TARGET_LIGHT | FT_LOAD_FORCE_AUTOHINT;
case FONT_HINTING_FULL: return FT_LOAD_TARGET_NORMAL | FT_LOAD_FORCE_AUTOHINT;
case FONT_HINTING_NONE: return FT_LOAD_TARGET_NORMAL | FT_LOAD_NO_HINTING;
}
return FT_LOAD_TARGET_NORMAL | FT_LOAD_NO_HINTING;
}
void ren_cp_replace_free(CPReplaceTable *rep_table) {
free(rep_table->replacements);
}
void ren_cp_replace_add(CPReplaceTable *rep_table, const char *src, const char *dst) {
int table_size = rep_table->size;
if (table_size % REPLACEMENT_CHUNK_SIZE == 0) {
CPReplace *old_replacements = rep_table->replacements;
const int new_size = (table_size / REPLACEMENT_CHUNK_SIZE + 1) * REPLACEMENT_CHUNK_SIZE;
rep_table->replacements = malloc(new_size * sizeof(CPReplace));
if (!rep_table->replacements) {
rep_table->replacements = old_replacements;
return;
static int font_set_render_options(RenFont* font) {
if (font->subpixel) {
switch (font->hinting) {
case FONT_HINTING_NONE: FT_Library_SetLcdFilter(library, FT_LCD_FILTER_NONE); break;
case FONT_HINTING_SLIGHT: FT_Library_SetLcdFilter(library, FT_LCD_FILTER_LIGHT); break;
case FONT_HINTING_FULL: FT_Library_SetLcdFilter(library, FT_LCD_FILTER_DEFAULT); break;
}
return FT_RENDER_MODE_LCD;
} else {
switch (font->hinting) {
case FONT_HINTING_NONE: return FT_RENDER_MODE_NORMAL; break;
case FONT_HINTING_SLIGHT: return FT_RENDER_MODE_LIGHT; break;
case FONT_HINTING_FULL: return FT_RENDER_MODE_LIGHT; break;
}
memcpy(rep_table->replacements, old_replacements, table_size * sizeof(CPReplace));
free(old_replacements);
}
CPReplace *rep = &rep_table->replacements[table_size];
utf8_to_codepoint(src, &rep->codepoint_src);
utf8_to_codepoint(dst, &rep->codepoint_dst);
rep_table->size = table_size + 1;
}
void ren_free_window_resources() {
renwin_free(&window_renderer);
}
void ren_init(SDL_Window *win) {
assert(win);
window_renderer.window = win;
renwin_init_surface(&window_renderer);
renwin_clip_to_surface(&window_renderer);
}
void ren_resize_window() {
renwin_resize_surface(&window_renderer);
}
void ren_update_rects(RenRect *rects, int count) {
static bool initial_frame = true;
if (initial_frame) {
renwin_show_window(&window_renderer);
initial_frame = false;
}
renwin_update_rects(&window_renderer, rects, count);
}
void ren_set_clip_rect(RenRect rect) {
renwin_set_clip_rect(&window_renderer, rect);
}
void ren_get_size(int *x, int *y) {
RenWindow *ren = &window_renderer;
const int scale = renwin_surface_scale(ren);
SDL_Surface *surface = renwin_get_surface(ren);
*x = surface->w / scale;
*y = surface->h / scale;
}
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);
}
static GlyphSet* load_glyphset(RenFont *font, int idx) {
GlyphSet *set = check_alloc(calloc(1, sizeof(GlyphSet)));
set->image = FR_Bake_Font_Bitmap(font->renderer, font->height, idx << 8, 256, set->glyphs);
check_alloc(set->image);
return set;
}
static GlyphSet* get_glyphset(RenFont *font, int codepoint) {
int idx = (codepoint >> 8) % MAX_GLYPHSET;
if (!font->sets[idx]) {
font->sets[idx] = load_glyphset(font, idx);
}
return font->sets[idx];
}
int ren_verify_font(const char *filename) {
RenFont font[1];
font->renderer = FR_Renderer_New(0);
if (FR_Load_Font(font->renderer, filename)) {
return 1;
}
FR_Renderer_Free(font->renderer);
return 0;
}
RenFont* ren_load_font(const char *filename, float size, unsigned int renderer_flags) {
RenFont *font = NULL;
/* init font */
font = check_alloc(calloc(1, sizeof(RenFont)));
font->size = size;
unsigned int fr_renderer_flags = 0;
if ((renderer_flags & RenFontAntialiasingMask) == RenFontSubpixel) {
fr_renderer_flags |= FR_SUBPIXEL;
static GlyphSet* font_load_glyphset(RenFont* font, int idx) {
GlyphSet* set = check_alloc(calloc(1, sizeof(GlyphSet)));
unsigned int render_option = font_set_render_options(font), load_option = font_set_load_options(font);
int pen_x = 0, bitmaps_cached = font->subpixel ? SUBPIXEL_BITMAPS_CACHED : 1;
unsigned int byte_width = font->subpixel ? 3 : 1;
for (int i = 0; i < 256; ++i) {
int glyph_index = FT_Get_Char_Index( font->face, i + idx * MAX_GLYPHSET);
if (!glyph_index || FT_Load_Glyph(font->face, glyph_index, load_option | FT_LOAD_BITMAP_METRICS_ONLY) || FT_Render_Glyph(font->face->glyph, render_option))
continue;
FT_GlyphSlot slot = font->face->glyph;
int glyph_width = slot->bitmap.width / byte_width;
set->metrics[i] = (GlyphMetric){ pen_x, pen_x + glyph_width, 0, slot->bitmap.rows, slot->bitmap_left, slot->bitmap_top, (slot->advance.x + slot->lsb_delta - slot->rsb_delta) / 64.0f};
pen_x += glyph_width;
font->max_height = slot->bitmap.rows > font->max_height ? slot->bitmap.rows : font->max_height;
// xadvance for some reason should be computed like this.
if (FT_Load_Glyph(font->face, glyph_index, FT_LOAD_BITMAP_METRICS_ONLY) || FT_Render_Glyph(font->face->glyph, render_option))
continue;
set->metrics[i].xadvance = (slot->advance.x + slot->lsb_delta - slot->rsb_delta) / 64.0f;
}
if ((renderer_flags & RenFontHintingMask) == RenFontHintingSlight) {
fr_renderer_flags |= (FR_HINTING | FR_PRESCALE_X);
} else if ((renderer_flags & RenFontHintingMask) == RenFontHintingFull) {
fr_renderer_flags |= FR_HINTING;
}
font->renderer = FR_Renderer_New(fr_renderer_flags);
if (FR_Load_Font(font->renderer, filename)) {
free(font);
return NULL;
}
font->height = FR_Get_Font_Height(font->renderer, size);
FR_Bitmap_Glyph_Metrics *gs = get_glyphset(font, ' ')->glyphs;
font->space_advance = gs[' '].xadvance;
/* make tab and newline glyphs invisible */
FR_Bitmap_Glyph_Metrics *g = get_glyphset(font, '\n')->glyphs;
g['\t'].x1 = g['\t'].x0;
g['\n'].x1 = g['\n'].x0;
return font;
}
void ren_free_font(RenFont *font) {
for (int i = 0; i < MAX_GLYPHSET; i++) {
GlyphSet *set = font->sets[i];
if (set) {
FR_Bitmap_Free(set->image);
free(set);
for (int j = 0; j < bitmaps_cached; ++j) {
set->surface[j] = check_alloc(SDL_CreateRGBSurface(0, pen_x, font->max_height, font->subpixel ? 24 : 8, 0, 0, 0, 0));
unsigned char* pixels = set->surface[j]->pixels;
for (int i = 0; i < 256; ++i) {
int glyph_index = FT_Get_Char_Index(font->face, i + idx * MAX_GLYPHSET);
if (!glyph_index || FT_Load_Glyph(font->face, glyph_index, load_option))
continue;
FT_GlyphSlot slot = font->face->glyph;
FT_Outline_Translate(&slot->outline, (64 / bitmaps_cached) * j, 0 );
if (FT_Render_Glyph(slot, render_option))
continue;
for (int line = 0; line < slot->bitmap.rows; ++line) {
int target_offset = set->surface[j]->pitch * line + set->metrics[i].x0 * byte_width;
int source_offset = line * slot->bitmap.pitch;
memcpy(&pixels[target_offset], &slot->bitmap.buffer[source_offset], slot->bitmap.width);
}
}
}
FR_Renderer_Free(font->renderer);
return set;
}
static void font_free_glyphset(GlyphSet* set) {
for (int i = 0; i < SUBPIXEL_BITMAPS_CACHED; ++i) {
if (set->surface[i])
SDL_FreeSurface(set->surface[i]);
}
free(set);
}
static GlyphSet* font_get_glyphset(RenFont* font, int codepoint) {
int idx = (codepoint >> 8) % MAX_GLYPHSET;
if (!font->sets[idx])
font->sets[idx] = font_load_glyphset(font, idx);
return font->sets[idx];
}
RenFont* ren_font_load(const char* path, float size, bool subpixel, unsigned char hinting) {
FT_Face face;
if (FT_New_Face( library, path, 0, &face))
return NULL;
if (FT_Set_Pixel_Sizes(face, 0, (int)size))
goto failure;
int len = strlen(path);
RenFont* font = check_alloc(calloc(1, sizeof(RenFont) + len + 1));
strcpy(font->path, path);
font->face = face;
font->size = size;
font->subpixel = subpixel;
font->hinting = hinting;
GlyphSet* set = font_get_glyphset(font, ' ');
font->space_advance = (int)set->metrics[' '].xadvance;
font->tab_advance = font->space_advance * 2;
return font;
failure:
FT_Done_Face(face);
return NULL;
}
RenFont* ren_font_copy(RenFont* font, float size) {
return ren_font_load(font->path, size, font->subpixel, font->hinting);
}
void ren_font_free(RenFont* font) {
for (int i = 0; i < MAX_GLYPHSET; ++i) {
if (font->sets[i])
font_free_glyphset(font->sets[i]);
}
FT_Done_Face(font->face);
free(font);
}
void ren_set_font_tab_size(RenFont *font, int n) {
GlyphSet *set = get_glyphset(font, '\t');
set->glyphs['\t'].xadvance = font->space_advance * n;
void ren_font_set_tab_size(RenFont *font, int n) {
font_get_glyphset(font, '\t')->metrics['\t'].xadvance = font->space_advance * n;
}
int ren_get_font_tab_size(RenFont *font) {
GlyphSet *set = get_glyphset(font, '\t');
return set->glyphs['\t'].xadvance / font->space_advance;
int ren_font_get_tab_size(RenFont *font) {
return font_get_glyphset(font, '\t')->metrics['\t'].xadvance / font->space_advance;
}
/* Important: if subpixel_scale is NULL we will return width in points. Otherwise we will
return width in subpixels. */
int ren_get_font_width(FontDesc *font_desc, const char *text, int *subpixel_scale) {
int x = 0;
const char *p = text;
unsigned codepoint;
const int surface_scale = renwin_surface_scale(&window_renderer);
RenFont *font = font_desc_get_font_at_scale(font_desc, surface_scale);
while (*p) {
p = utf8_to_codepoint(p, &codepoint);
GlyphSet *set = get_glyphset(font, codepoint);
FR_Bitmap_Glyph_Metrics *g = &set->glyphs[codepoint & 0xff];
x += g->xadvance;
int ren_font_get_width(RenFont *font, const char *text) {
int width = 0;
const char* end = text + strlen(text);
while (text < end) {
unsigned int codepoint;
text = utf8_to_codepoint(text, &codepoint);
width += font_get_glyphset(font, codepoint)->metrics[codepoint % 256].xadvance;
}
/* At this point here x is in subpixel units */
const int x_scale_to_points = FR_Subpixel_Scale(font->renderer) * surface_scale;
if (subpixel_scale) {
*subpixel_scale = x_scale_to_points;
return x;
return width;
}
float ren_font_get_size(RenFont *font) {
return font->size;
}
int ren_font_get_height(RenFont *font) {
return font->size + 3;
}
int ren_draw_text(RenFont *font, const char *text, float x, int y, RenColor color) {
SDL_Surface *surface = renwin_get_surface(&window_renderer);
const RenRect clip = window_renderer.clip;
float pen_x = x;
int bytes_per_pixel = surface->format->BytesPerPixel;
const char* end = text + strlen(text);
unsigned char* destination_pixels = surface->pixels;
int clip_end_x = clip.x + clip.width, clip_end_y = clip.y + clip.height;
while (text < end) {
unsigned int codepoint, r, g, b;
text = utf8_to_codepoint(text, &codepoint);
GlyphSet* set = font_get_glyphset(font, codepoint);
GlyphMetric* metric = &set->metrics[codepoint % 256];
int bitmap_index = font->subpixel ? (int)(fmod(pen_x, 1.0) * SUBPIXEL_BITMAPS_CACHED) : 0;
unsigned char* source_pixels = set->surface[bitmap_index]->pixels;
int start_x = pen_x + metric->bitmap_left, endX = metric->x1 - metric->x0 + pen_x;
int glyph_end = metric->x1, glyphStart = metric->x0;
if (color.a > 0 && endX >= clip.x && start_x <= clip_end_x) {
for (int line = metric->y0; line < metric->y1; ++line) {
int targetY = line + y - metric->y0 - metric->bitmap_top + font->size;
if (targetY < clip.y)
continue;
if (targetY >= clip_end_y)
break;
unsigned int* destination_pixel = (unsigned int*)&destination_pixels[surface->pitch * targetY + start_x * bytes_per_pixel];
unsigned char* source_pixel = &source_pixels[line * set->surface[bitmap_index]->pitch + metric->x0 * (font->subpixel ? 3 : 1)];
for (int x = glyphStart; x < glyph_end; ++x) {
unsigned int destination_color = *destination_pixel;
SDL_Color dst = { (destination_color >> 16) & 0xFF, (destination_color >> 8) & 0xFF, (destination_color >> 0) & 0xFF, (destination_color >> 24) & 0xFF };
if (font->subpixel) {
SDL_Color src = { *source_pixel++, *source_pixel++, *source_pixel++ };
r = color.r * src.r + dst.r * (255 - src.r) + 127;
r = DIVIDE_BY_255(r);
g = color.g * src.g + dst.g * (255 - src.g) + 127;
g = DIVIDE_BY_255(g);
b = color.b * src.b + dst.b * (255 - src.b) + 127;
b = DIVIDE_BY_255(b);
} else {
unsigned char intensity = *source_pixel++;
r = color.r * intensity + dst.r * (255 - intensity) + 127;
r = DIVIDE_BY_255(r);
g = color.g * intensity + dst.g * (255 - intensity) + 127;
g = DIVIDE_BY_255(g);
b = color.b * intensity + dst.b * (255 - intensity) + 127;
b = DIVIDE_BY_255(b);
}
*destination_pixel++ = dst.a << 24 | r << 16 | g << 8 | b;
}
}
}
pen_x += metric->xadvance ? metric->xadvance : font->space_advance;
}
return (x + x_scale_to_points / 2) / x_scale_to_points;
return pen_x;
}
int ren_get_font_height(FontDesc *font_desc) {
const int surface_scale = renwin_surface_scale(&window_renderer);
RenFont *font = font_desc_get_font_at_scale(font_desc, surface_scale);
return (font->height + surface_scale / 2) / surface_scale;
}
/******************* Rectangles **********************/
static inline RenColor blend_pixel(RenColor dst, RenColor src) {
int ia = 0xff - src.a;
dst.r = ((src.r * src.a) + (dst.r * ia)) >> 8;
@@ -316,88 +315,49 @@ void ren_draw_rect(RenRect rect, RenColor color) {
}
}
/*************** Window Management ****************/
void ren_free_window_resources() {
renwin_free(&window_renderer);
}
static int codepoint_replace(CPReplaceTable *rep_table, unsigned *codepoint) {
for (int i = 0; i < rep_table->size; i++) {
const CPReplace *rep = &rep_table->replacements[i];
if (*codepoint == rep->codepoint_src) {
*codepoint = rep->codepoint_dst;
return 1;
}
void ren_init(SDL_Window *win) {
assert(win);
int error = FT_Init_FreeType( &library );
if ( error ) {
fprintf(stderr, "internal font error when starting the application\n");
return;
}
return 0;
window_renderer.window = win;
renwin_init_surface(&window_renderer);
renwin_clip_to_surface(&window_renderer);
}
static FR_Clip_Area clip_area_from_rect(const RenRect r) {
return (FR_Clip_Area) {r.x, r.y, r.x + r.width, r.y + r.height};
void ren_resize_window() {
renwin_resize_surface(&window_renderer);
}
static void draw_text_impl(RenFont *font, const char *text, int x_subpixel, int y_pixel, RenColor color,
CPReplaceTable *replacements, RenColor replace_color)
{
SDL_Surface *surf = renwin_get_surface(&window_renderer);
FR_Clip_Area clip = clip_area_from_rect(window_renderer.clip);
const char *p = text;
unsigned codepoint;
const FR_Color color_fr = { .r = color.r, .g = color.g, .b = color.b };
while (*p) {
FR_Color color_rep;
p = utf8_to_codepoint(p, &codepoint);
GlyphSet *set = get_glyphset(font, codepoint);
FR_Bitmap_Glyph_Metrics *g = &set->glyphs[codepoint & 0xff];
const int xadvance_original_cp = g->xadvance;
const int replaced = replacements ? codepoint_replace(replacements, &codepoint) : 0;
if (replaced) {
set = get_glyphset(font, codepoint);
g = &set->glyphs[codepoint & 0xff];
color_rep = (FR_Color) { .r = replace_color.r, .g = replace_color.g, .b = replace_color.b};
} else {
color_rep = color_fr;
}
if (color.a != 0) {
FR_Blend_Glyph(font->renderer, &clip,
x_subpixel, y_pixel, (uint8_t *) surf->pixels, surf->w, set->image, g, color_rep);
}
x_subpixel += xadvance_original_cp;
void ren_update_rects(RenRect *rects, int count) {
static bool initial_frame = true;
if (initial_frame) {
renwin_show_window(&window_renderer);
initial_frame = false;
}
renwin_update_rects(&window_renderer, rects, count);
}
void ren_draw_text_subpixel(FontDesc *font_desc, const char *text, int x_subpixel, int y, RenColor color,
CPReplaceTable *replacements, RenColor replace_color)
{
const int surface_scale = renwin_surface_scale(&window_renderer);
RenFont *font = font_desc_get_font_at_scale(font_desc, surface_scale);
draw_text_impl(font, text, x_subpixel, surface_scale * y, color, replacements, replace_color);
}
void ren_draw_text(FontDesc *font_desc, const char *text, int x, int y, RenColor color,
CPReplaceTable *replacements, RenColor replace_color)
{
const int surface_scale = renwin_surface_scale(&window_renderer);
RenFont *font = font_desc_get_font_at_scale(font_desc, surface_scale);
const int subpixel_scale = surface_scale * FR_Subpixel_Scale(font->renderer);
draw_text_impl(font, text, subpixel_scale * x, surface_scale * y, color, replacements, replace_color);
}
// Could be declared as static inline
int ren_font_subpixel_round(int width, int subpixel_scale, int orientation) {
int w_mult;
if (orientation < 0) {
w_mult = width;
} else if (orientation == 0) {
w_mult = width + subpixel_scale / 2;
} else {
w_mult = width + subpixel_scale - 1;
}
return w_mult / subpixel_scale;
void ren_set_clip_rect(RenRect rect) {
renwin_set_clip_rect(&window_renderer, rect);
}
int ren_get_font_subpixel_scale(FontDesc *font_desc) {
const int surface_scale = renwin_surface_scale(&window_renderer);
RenFont *font = font_desc_get_font_at_scale(font_desc, surface_scale);
return FR_Subpixel_Scale(font->renderer) * surface_scale;
void ren_get_size(int *x, int *y) {
RenWindow *ren = &window_renderer;
const int scale = renwin_surface_scale(ren);
SDL_Surface *surface = renwin_get_surface(ren);
*x = surface->w / scale;
*y = surface->h / scale;
}