403 lines
11 KiB
C
403 lines
11 KiB
C
#include <stdio.h>
|
|
#include <stdbool.h>
|
|
#include <assert.h>
|
|
#include <math.h>
|
|
#include "font_renderer.h"
|
|
#include "renderer.h"
|
|
#include "renwindow.h"
|
|
|
|
#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;
|
|
};
|
|
|
|
static RenWindow window_renderer = {0};
|
|
|
|
static void* check_alloc(void *ptr) {
|
|
if (!ptr) {
|
|
fprintf(stderr, "Fatal error: memory allocation failed\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
return ptr;
|
|
}
|
|
|
|
|
|
static const char* utf8_to_codepoint(const char *p, unsigned *dst) {
|
|
unsigned res, n;
|
|
switch (*p & 0xf0) {
|
|
case 0xf0 : res = *p & 0x07; n = 3; break;
|
|
case 0xe0 : res = *p & 0x0f; n = 2; break;
|
|
case 0xd0 :
|
|
case 0xc0 : res = *p & 0x1f; n = 1; break;
|
|
default : res = *p; n = 0; break;
|
|
}
|
|
while (n--) {
|
|
res = (res << 6) | (*(++p) & 0x3f);
|
|
}
|
|
*dst = res;
|
|
return p + 1;
|
|
}
|
|
|
|
|
|
void ren_cp_replace_init(CPReplaceTable *rep_table) {
|
|
rep_table->size = 0;
|
|
rep_table->replacements = NULL;
|
|
}
|
|
|
|
|
|
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;
|
|
}
|
|
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;
|
|
}
|
|
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);
|
|
}
|
|
}
|
|
FR_Renderer_Free(font->renderer);
|
|
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;
|
|
}
|
|
|
|
|
|
int ren_get_font_tab_size(RenFont *font) {
|
|
GlyphSet *set = get_glyphset(font, '\t');
|
|
return set->glyphs['\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;
|
|
}
|
|
/* 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 (x + x_scale_to_points / 2) / x_scale_to_points;
|
|
}
|
|
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
static inline RenColor blend_pixel(RenColor dst, RenColor src) {
|
|
int ia = 0xff - src.a;
|
|
dst.r = ((src.r * src.a) + (dst.r * ia)) >> 8;
|
|
dst.g = ((src.g * src.a) + (dst.g * ia)) >> 8;
|
|
dst.b = ((src.b * src.a) + (dst.b * ia)) >> 8;
|
|
return dst;
|
|
}
|
|
|
|
|
|
#define rect_draw_loop(expr) \
|
|
for (int j = y1; j < y2; j++) { \
|
|
for (int i = x1; i < x2; i++) { \
|
|
*d = expr; \
|
|
d++; \
|
|
} \
|
|
d += dr; \
|
|
}
|
|
|
|
void ren_draw_rect(RenRect rect, RenColor color) {
|
|
if (color.a == 0) { return; }
|
|
|
|
const int surface_scale = renwin_surface_scale(&window_renderer);
|
|
|
|
/* transforms coordinates in pixels. */
|
|
rect.x *= surface_scale;
|
|
rect.y *= surface_scale;
|
|
rect.width *= surface_scale;
|
|
rect.height *= surface_scale;
|
|
|
|
const RenRect clip = window_renderer.clip;
|
|
int x1 = rect.x < clip.x ? clip.x : rect.x;
|
|
int y1 = rect.y < clip.y ? clip.y : rect.y;
|
|
int x2 = rect.x + rect.width;
|
|
int y2 = rect.y + rect.height;
|
|
x2 = x2 > clip.x + clip.width ? clip.x + clip.width : x2;
|
|
y2 = y2 > clip.y + clip.height ? clip.y + clip.height : y2;
|
|
|
|
SDL_Surface *surface = renwin_get_surface(&window_renderer);
|
|
RenColor *d = (RenColor*) surface->pixels;
|
|
d += x1 + y1 * surface->w;
|
|
int dr = surface->w - (x2 - x1);
|
|
|
|
if (color.a == 0xff) {
|
|
rect_draw_loop(color);
|
|
} else {
|
|
rect_draw_loop(blend_pixel(*d, color));
|
|
}
|
|
}
|
|
|
|
|
|
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;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
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};
|
|
}
|
|
|
|
|
|
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_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;
|
|
}
|
|
|
|
|
|
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;
|
|
}
|