2019-12-28 12:16:32 +01:00
|
|
|
#include <stdio.h>
|
2020-05-14 23:28:22 +02:00
|
|
|
#include <stdbool.h>
|
2019-12-28 12:16:32 +01:00
|
|
|
#include <assert.h>
|
|
|
|
#include <math.h>
|
|
|
|
#include "renderer.h"
|
2020-06-01 12:56:23 +02:00
|
|
|
#include "font_renderer.h"
|
2019-12-28 12:16:32 +01:00
|
|
|
|
|
|
|
#define MAX_GLYPHSET 256
|
|
|
|
|
|
|
|
struct RenImage {
|
|
|
|
RenColor *pixels;
|
|
|
|
int width, height;
|
|
|
|
};
|
|
|
|
|
2020-06-01 14:08:50 +02:00
|
|
|
struct GlyphSet {
|
2020-06-11 16:39:11 +02:00
|
|
|
FontRendererBitmap *image;
|
2020-06-01 12:56:23 +02:00
|
|
|
GlyphBitmapInfo glyphs[256];
|
|
|
|
};
|
2020-06-01 14:08:50 +02:00
|
|
|
typedef struct GlyphSet GlyphSet;
|
2019-12-28 12:16:32 +01:00
|
|
|
|
|
|
|
struct RenFont {
|
|
|
|
void *data;
|
2020-06-01 14:08:50 +02:00
|
|
|
GlyphSet *sets[MAX_GLYPHSET];
|
2019-12-28 12:16:32 +01:00
|
|
|
float size;
|
|
|
|
int height;
|
2020-05-28 12:15:24 +02:00
|
|
|
FontRenderer *renderer;
|
2019-12-28 12:16:32 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static SDL_Window *window;
|
2020-06-11 16:39:11 +02:00
|
|
|
//static struct { int left, top, right, bottom; } clip;
|
|
|
|
static FontRendererClipArea clip;
|
2019-12-28 12:16:32 +01:00
|
|
|
|
2020-05-08 14:41:39 +02:00
|
|
|
static void* check_alloc(void *ptr) {
|
|
|
|
if (!ptr) {
|
|
|
|
fprintf(stderr, "Fatal error: memory allocation failed\n");
|
|
|
|
exit(EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
return ptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-12-28 12:16:32 +01:00
|
|
|
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_init(SDL_Window *win) {
|
|
|
|
assert(win);
|
|
|
|
window = win;
|
|
|
|
SDL_Surface *surf = SDL_GetWindowSurface(window);
|
|
|
|
ren_set_clip_rect( (RenRect) { 0, 0, surf->w, surf->h } );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ren_update_rects(RenRect *rects, int count) {
|
|
|
|
SDL_UpdateWindowSurfaceRects(window, (SDL_Rect*) rects, count);
|
2020-05-14 23:28:22 +02:00
|
|
|
static bool initial_frame = true;
|
|
|
|
if (initial_frame) {
|
|
|
|
SDL_ShowWindow(window);
|
|
|
|
initial_frame = false;
|
|
|
|
}
|
2019-12-28 12:16:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ren_set_clip_rect(RenRect rect) {
|
|
|
|
clip.left = rect.x;
|
|
|
|
clip.top = rect.y;
|
|
|
|
clip.right = rect.x + rect.width;
|
|
|
|
clip.bottom = rect.y + rect.height;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ren_get_size(int *x, int *y) {
|
|
|
|
SDL_Surface *surf = SDL_GetWindowSurface(window);
|
|
|
|
*x = surf->w;
|
|
|
|
*y = surf->h;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
RenImage* ren_new_image(int width, int height) {
|
|
|
|
assert(width > 0 && height > 0);
|
2020-05-08 14:41:39 +02:00
|
|
|
RenImage *image = malloc(sizeof(RenImage) + width * height * sizeof(RenColor));
|
|
|
|
check_alloc(image);
|
2019-12-28 12:16:32 +01:00
|
|
|
image->pixels = (void*) (image + 1);
|
|
|
|
image->width = width;
|
|
|
|
image->height = height;
|
|
|
|
return image;
|
|
|
|
}
|
|
|
|
|
2020-06-11 16:39:11 +02:00
|
|
|
void ren_free_image(RenImage *image) {
|
|
|
|
free(image);
|
|
|
|
}
|
|
|
|
|
2020-06-01 14:08:50 +02:00
|
|
|
static GlyphSet* load_glyphset(RenFont *font, int idx) {
|
|
|
|
GlyphSet *set = check_alloc(calloc(1, sizeof(GlyphSet)));
|
2019-12-28 12:16:32 +01:00
|
|
|
|
2020-06-04 16:29:28 +02:00
|
|
|
const int subpixel_scale = 3;
|
|
|
|
|
2019-12-28 12:16:32 +01:00
|
|
|
/* init image */
|
|
|
|
int width = 128;
|
|
|
|
int height = 128;
|
|
|
|
retry:
|
2020-06-11 17:52:00 +02:00
|
|
|
set->image = FontRendererBitmapNew(width, height, subpixel_scale);
|
|
|
|
check_alloc(set->image);
|
2019-12-28 12:16:32 +01:00
|
|
|
|
2020-06-01 12:56:23 +02:00
|
|
|
int res = FontRendererBakeFontBitmap(font->renderer, font->height,
|
2020-06-11 17:52:00 +02:00
|
|
|
set->image, idx << 8, 256, set->glyphs, subpixel_scale);
|
2019-12-28 12:16:32 +01:00
|
|
|
|
|
|
|
/* retry with a larger image buffer if the buffer wasn't large enough */
|
|
|
|
if (res < 0) {
|
|
|
|
width *= 2;
|
|
|
|
height *= 2;
|
2020-06-11 17:52:00 +02:00
|
|
|
FontRendererBitmapFree(set->image);
|
2019-12-28 12:16:32 +01:00
|
|
|
goto retry;
|
|
|
|
}
|
|
|
|
|
2020-06-01 14:33:46 +02:00
|
|
|
/* adjust glyph's xadvance */
|
2019-12-28 12:16:32 +01:00
|
|
|
for (int i = 0; i < 256; i++) {
|
2020-06-02 17:52:02 +02:00
|
|
|
set->glyphs[i].xadvance = floor(set->glyphs[i].xadvance + 0.5);
|
2019-12-28 12:16:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return set;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-06-01 14:08:50 +02:00
|
|
|
static GlyphSet* get_glyphset(RenFont *font, int codepoint) {
|
2019-12-28 12:16:32 +01:00
|
|
|
int idx = (codepoint >> 8) % MAX_GLYPHSET;
|
|
|
|
if (!font->sets[idx]) {
|
|
|
|
font->sets[idx] = load_glyphset(font, idx);
|
|
|
|
}
|
|
|
|
return font->sets[idx];
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
RenFont* ren_load_font(const char *filename, float size) {
|
|
|
|
RenFont *font = NULL;
|
|
|
|
|
|
|
|
/* init font */
|
2020-05-08 14:41:39 +02:00
|
|
|
font = check_alloc(calloc(1, sizeof(RenFont)));
|
2019-12-28 12:16:32 +01:00
|
|
|
font->size = size;
|
|
|
|
|
2020-06-04 16:29:28 +02:00
|
|
|
const float gamma = 1.5;
|
2020-06-06 19:05:45 +02:00
|
|
|
font->renderer = FontRendererNew(FONT_RENDERER_HINTING|FONT_RENDERER_SUBPIXEL, gamma);
|
2020-06-01 17:01:42 +02:00
|
|
|
if (FontRendererLoadFont(font->renderer, filename)) {
|
|
|
|
free(font);
|
|
|
|
return NULL;
|
|
|
|
}
|
2020-06-01 12:56:23 +02:00
|
|
|
font->height = FontRendererGetFontHeight(font->renderer, size);
|
|
|
|
|
2020-02-05 21:16:09 +01:00
|
|
|
/* make tab and newline glyphs invisible */
|
2020-06-01 12:56:23 +02:00
|
|
|
GlyphBitmapInfo *g = get_glyphset(font, '\n')->glyphs;
|
2020-02-05 21:16:09 +01:00
|
|
|
g['\t'].x1 = g['\t'].x0;
|
|
|
|
g['\n'].x1 = g['\n'].x0;
|
|
|
|
|
2019-12-28 12:16:32 +01:00
|
|
|
return font;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ren_free_font(RenFont *font) {
|
|
|
|
for (int i = 0; i < MAX_GLYPHSET; i++) {
|
2020-06-01 14:08:50 +02:00
|
|
|
GlyphSet *set = font->sets[i];
|
2019-12-28 12:16:32 +01:00
|
|
|
if (set) {
|
2020-06-11 17:52:00 +02:00
|
|
|
FontRendererBitmapFree(set->image);
|
2020-05-08 14:41:39 +02:00
|
|
|
free(set);
|
2019-12-28 12:16:32 +01:00
|
|
|
}
|
|
|
|
}
|
2020-06-01 14:42:57 +02:00
|
|
|
FontRendererFree(font->renderer);
|
2020-05-08 14:41:39 +02:00
|
|
|
free(font);
|
2019-12-28 12:16:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ren_set_font_tab_width(RenFont *font, int n) {
|
2020-06-01 14:08:50 +02:00
|
|
|
GlyphSet *set = get_glyphset(font, '\t');
|
2019-12-28 12:16:32 +01:00
|
|
|
set->glyphs['\t'].xadvance = n;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int ren_get_font_width(RenFont *font, const char *text) {
|
|
|
|
int x = 0;
|
|
|
|
const char *p = text;
|
|
|
|
unsigned codepoint;
|
|
|
|
while (*p) {
|
|
|
|
p = utf8_to_codepoint(p, &codepoint);
|
2020-06-01 14:08:50 +02:00
|
|
|
GlyphSet *set = get_glyphset(font, codepoint);
|
2020-06-01 12:56:23 +02:00
|
|
|
GlyphBitmapInfo *g = &set->glyphs[codepoint & 0xff];
|
2019-12-28 12:16:32 +01:00
|
|
|
x += g->xadvance;
|
|
|
|
}
|
|
|
|
return x;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int ren_get_font_height(RenFont *font) {
|
|
|
|
return font->height;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static inline RenColor blend_pixel2(RenColor dst, RenColor src, RenColor color) {
|
|
|
|
src.a = (src.a * color.a) >> 8;
|
|
|
|
int ia = 0xff - src.a;
|
|
|
|
dst.r = ((src.r * color.r * src.a) >> 16) + ((dst.r * ia) >> 8);
|
|
|
|
dst.g = ((src.g * color.g * src.a) >> 16) + ((dst.g * ia) >> 8);
|
|
|
|
dst.b = ((src.b * color.b * src.a) >> 16) + ((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; }
|
|
|
|
|
|
|
|
int x1 = rect.x < clip.left ? clip.left : rect.x;
|
|
|
|
int y1 = rect.y < clip.top ? clip.top : rect.y;
|
|
|
|
int x2 = rect.x + rect.width;
|
|
|
|
int y2 = rect.y + rect.height;
|
|
|
|
x2 = x2 > clip.right ? clip.right : x2;
|
|
|
|
y2 = y2 > clip.bottom ? clip.bottom : y2;
|
|
|
|
|
|
|
|
SDL_Surface *surf = SDL_GetWindowSurface(window);
|
|
|
|
RenColor *d = (RenColor*) surf->pixels;
|
|
|
|
d += x1 + y1 * surf->w;
|
|
|
|
int dr = surf->w - (x2 - x1);
|
|
|
|
|
|
|
|
if (color.a == 0xff) {
|
|
|
|
rect_draw_loop(color);
|
|
|
|
} else {
|
|
|
|
rect_draw_loop(blend_pixel(*d, color));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ren_draw_image(RenImage *image, RenRect *sub, int x, int y, RenColor color) {
|
|
|
|
if (color.a == 0) { return; }
|
|
|
|
|
2020-06-11 16:39:11 +02:00
|
|
|
int n;
|
|
|
|
if ((n = clip.left - x) > 0) { sub->width -= n; sub->x += n; x += n; }
|
|
|
|
if ((n = clip.top - y) > 0) { sub->height -= n; sub->y += n; x += n; }
|
|
|
|
if ((n = x + sub->width - clip.right ) > 0) { sub->width -= n; }
|
|
|
|
if ((n = y + sub->height - clip.bottom) > 0) { sub->height -= n; }
|
2019-12-28 12:16:32 +01:00
|
|
|
|
|
|
|
if (sub->width <= 0 || sub->height <= 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* draw */
|
|
|
|
SDL_Surface *surf = SDL_GetWindowSurface(window);
|
|
|
|
RenColor *s = image->pixels;
|
|
|
|
RenColor *d = (RenColor*) surf->pixels;
|
|
|
|
s += sub->x + sub->y * image->width;
|
|
|
|
d += x + y * surf->w;
|
|
|
|
int sr = image->width - sub->width;
|
|
|
|
int dr = surf->w - sub->width;
|
|
|
|
|
|
|
|
for (int j = 0; j < sub->height; j++) {
|
|
|
|
for (int i = 0; i < sub->width; i++) {
|
|
|
|
*d = blend_pixel2(*d, *s, color);
|
|
|
|
d++;
|
|
|
|
s++;
|
|
|
|
}
|
|
|
|
d += dr;
|
|
|
|
s += sr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int ren_draw_text(RenFont *font, const char *text, int x, int y, RenColor color) {
|
|
|
|
const char *p = text;
|
|
|
|
unsigned codepoint;
|
2020-06-11 16:39:11 +02:00
|
|
|
SDL_Surface *surf = SDL_GetWindowSurface(window);
|
|
|
|
FontRendererColor color_fr = { .r = color.r, .g = color.g, .b = color.b };
|
2019-12-28 12:16:32 +01:00
|
|
|
while (*p) {
|
|
|
|
p = utf8_to_codepoint(p, &codepoint);
|
2020-06-01 14:08:50 +02:00
|
|
|
GlyphSet *set = get_glyphset(font, codepoint);
|
2020-06-01 12:56:23 +02:00
|
|
|
GlyphBitmapInfo *g = &set->glyphs[codepoint & 0xff];
|
2020-06-11 16:39:11 +02:00
|
|
|
if (color.a != 0) {
|
|
|
|
FontRendererBlendGammaSubpixel(
|
|
|
|
font->renderer, &clip,
|
|
|
|
x, y,
|
|
|
|
(uint8_t *) surf->pixels, surf->w,
|
|
|
|
set->image, g, color_fr);
|
|
|
|
}
|
2019-12-28 12:16:32 +01:00
|
|
|
x += g->xadvance;
|
|
|
|
}
|
|
|
|
return x;
|
|
|
|
}
|