From f637752566e08f7e8bb8e01c4f07eb3a9d91527a Mon Sep 17 00:00:00 2001 From: Ivan Mahonin Date: Jul 18 2020 08:37:24 +0000 Subject: opengl textures and fonts --- diff --git a/src/SConstruct b/src/SConstruct index 5cd207b..b70d8da 100644 --- a/src/SConstruct +++ b/src/SConstruct @@ -18,12 +18,13 @@ opts.Save(name + '.conf', env) # config -libs = ['gtk+-3.0', 'glib-2.0', 'cairo', 'cairo-ft', 'freetype2', 'SDL2_mixer'] +libs = ['sdl2', 'SDL2_mixer', 'freetype2'] +ldflags = ' -lGL ' # compute build options -flags = ' -lm -Wall -fmessage-length=0 ' +flags = ' -lm -Wall -fmessage-length=0 ' + ldflags if int(DEBUG): flags += ' -O0 -g -fdebug-prefix-map=src=../src' else: @@ -88,7 +89,7 @@ pcdict = { '@DESC@' : description, '@VERSION@' : version, '@DEPS@' : ' '.join(libs), - '@CONFIG_LIBS@' : '-l' + name, + '@CONFIG_LIBS@' : '-l' + name + ldflags, '@CONFIG_CFLAGS@' : '', } pcfile = env.Substfile(name + '.pc.in', SUBST_DICT = pcdict) diff --git a/src/animation.c b/src/animation.c index 05cf557..2e57434 100644 --- a/src/animation.c +++ b/src/animation.c @@ -1,5 +1,5 @@ -#include +#include #include "private.h" #include "world.h" @@ -8,26 +8,179 @@ static HeliArray cache; -static cairo_status_t read(void *closure, unsigned char *data, unsigned int length) { - return fread(data, length, 1, (FILE*)closure) - ? CAIRO_STATUS_SUCCESS : CAIRO_STATUS_READ_ERROR; + + +static int fixsize(int x) { + int p = (int)ceil(log2(x > 1 ? x : 1) - 0.25); + if (p > 30) p = 30; + return 1 << p; +} + + +static float colorclamp(float x) + { return x > 0.f ? (x < 1.f ? x : 1.f) : 0.f; } + + +static void mipx(float *buffer, int *w, int *h) { + *w /= 2; + if (*w <= 0) return; + for(float *d = buffer, *s = buffer, *end = d + (*w)*(*h); d < end; d += 4, s += 8) + for(int i = 0; i < 4; ++i) + d[i] = (s[i] + s[i + 4])*0.5f; +} + + +static void mipy(float *buffer, int *w, int *h) { + *h /= 2; + if (*h <= 0) return; + float *d = buffer, *s0 = buffer, *s1 = s0 + (*w)*4; + for(int r = 0; r < *h; ++r, s0 = s1, s1 += (*w)*4) + for(float *end = d + (*w); d < end; ++d, ++s0, ++s1) + *d = (*s0 + *s1)*0.5f; } -static cairo_surface_t* loadFrame(const char *path) { + +static void unloadFrame(void *x) { + unsigned int texid = (unsigned int)(size_t)x; + glDeleteTextures(1, &texid); +} + + +static unsigned int loadFrame(const char *path) { if (!heliStringEndsWithLowcase(path, ".png")) - return NULL; - FILE *f = fopen(path, "rb"); - if (f) { - cairo_surface_t *frame = cairo_image_surface_create_from_png_stream(&read, f); - fclose(f); - if (!frame) - fprintf(stderr, "helianthus: cannot load PNG content form file: %s\n", path); - return frame; + return 0; + + // load image + SDL_Surface *surface = IMG_Load(path); + if (!surface) { + fprintf(stderr, "helianthus: cannot load image: %s\n", path); + return 0; + } + + // convert to RGBA + if (surface->format->format != SDL_PIXELFORMAT_RGBA8888) { + SDL_PixelFormat *format = SDL_AllocFormat(SDL_PIXELFORMAT_RGBA8888); + SDL_Surface *converted = SDL_ConvertSurface(surface, format, 0); + SDL_FreeFormat(format); + SDL_FreeSurface(surface); + surface = converted; + } + + // convert to float and premult alpha + SDL_LockSurface(surface); + int w = surface->w; + int h = surface->h; + size_t count = (size_t)w*h*4; + float *buffer = (float*)malloc(count*sizeof(float)); + const unsigned char *pp = (const unsigned char *)surface->pixels; + for(float *p = buffer, *end = p + count; p < end; p += 4, pp += 4) { + if (pp[3]) { + float a = pp[3]/255.f; + p[0] = pp[0]/255.f*a; + p[1] = pp[1]/255.f*a; + p[2] = pp[2]/255.f*a; + p[3] = a; + } else { + p[0] = pp[0]/255.f; + p[1] = pp[1]/255.f; + p[2] = pp[2]/255.f; + p[3] = pp[3]/255.f; + } + } + SDL_UnlockSurface(surface); + SDL_FreeSurface(surface); + + // resample to power of two + int fw = fixsize(w); + int fh = fixsize(h); + if (fw != w || fh != h) { + float *fbuffer = (float*)malloc((size_t)fw*fh*4*sizeof(float)); + double kr = (double)w/fw; + double kc = (double)h/fh; + for(int r = 0; r < fh; ++r) { + for(int c = 0; c < fw; ++c) { + double pr1 = r*kr; + int r0 = (int)floor(pr1 + HELI_PRECISION); + pr1 -= r0; + int r1 = r0 + 1; + if (r1 >= w) r1 = w - 1; + if (r0 > r1) r0 = r1; + double pr0 = 1 - pr1; + + double pc1 = c*kc; + int c0 = (int)floor(pc1 + HELI_PRECISION); + pc1 -= c0; + int c1 = c0 + 1; + if (c1 >= h) c1 = h - 1; + if (c0 > c1) c0 = c1; + double pc0 = 1 - pc1; + + float *p = fbuffer + (r*fw + c)*4; + const float *p00 = buffer + (r0*w + c0)*4; + const float *p01 = buffer + (r0*w + c1)*4; + const float *p10 = buffer + (r1*w + c0)*4; + const float *p11 = buffer + (r1*w + c1)*4; + for(int i = 0; i < 4; ++i) + p[i] = p00[i]*pr0*pc0 + + p01[i]*pr0*pc1 + + p10[i]*pr1*pc0 + + p11[i]*pr1*pc1; + } + } + free(buffer); + buffer = fbuffer; + w = fw; + h = fh; + } + + // fix max texture size + int maxSize = 0; + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxSize); + if (maxSize < 64) maxSize = 64; + while(w > maxSize) mipx(buffer, &w, &h); + while(h > maxSize) mipy(buffer, &w, &h); + + // create OpenGL texture + unsigned int texid = 0; + glGenTextures(1, &texid); + glBindTexture(GL_TEXTURE_2D, texid); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + + // build mip levels + unsigned char *ibuffer = malloc((size_t)w*h*4); + while(1) { + float *pp = buffer; + for(unsigned char *p = ibuffer, *end = p + (size_t)w*h*4; p < end; p += 4, pp += 4) { + if (pp[3] > 1e-5) { + float k = 1/pp[3]; + p[0] = (unsigned char)floor(colorclamp(pp[0]*k)*255.99); + p[1] = (unsigned char)floor(colorclamp(pp[1]*k)*255.99); + p[2] = (unsigned char)floor(colorclamp(pp[2]*k)*255.99); + p[3] = (unsigned char)floor(colorclamp(pp[3])*255.99); + } else { + p[0] = (unsigned char)floor(colorclamp(pp[0])*255.99); + p[1] = (unsigned char)floor(colorclamp(pp[1])*255.99); + p[2] = (unsigned char)floor(colorclamp(pp[2])*255.99); + p[3] = 0; + } + } + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, ibuffer); + + if (w <= 1 && h <= 1) break; + if (w > 1) mipx(buffer, &w, &h); + if (h > 1) mipy(buffer, &w, &h); } - fprintf(stderr, "helianthus: cannot open image file: %s\n", path); - return NULL; + + // done + free(ibuffer); + free(buffer); + glBindTexture(GL_TEXTURE_2D, 0); + + return texid; } + static HeliAnimation* load(const char *path) { HeliAnimation *a = calloc(1, sizeof(*a)); a->path = heliStringCopy(path); @@ -37,15 +190,15 @@ static HeliAnimation* load(const char *path) { int count = directoryGetCount(d); for(int i = 0; i < count; ++i) { char *p = heliStringConcat3(path, "/", directoryGet(d, i)); - cairo_surface_t *frame = loadFrame(p); + size_t texid = loadFrame(p); free(p); - if (frame) heliArrayInsert(&a->frames, -1, frame, (HeliFreeCallback)&cairo_surface_destroy); + if (texid) heliArrayInsert(&a->frames, -1, (void*)texid, &unloadFrame); } closeDirectory(d); } else { - cairo_surface_t *frame = loadFrame(path); - if (frame) { - heliArrayInsert(&a->frames, -1, frame, (HeliFreeCallback)&cairo_surface_destroy); + size_t texid = loadFrame(path); + if (texid) { + heliArrayInsert(&a->frames, -1, (void*)texid, &unloadFrame); } else { fprintf(stderr, "helianthus: cannot load animation by path: %s\n", path); } @@ -54,6 +207,7 @@ static HeliAnimation* load(const char *path) { return a; } + static void unload(HeliAnimation *a) { assert(!a->refcount); free(a->path); @@ -61,6 +215,7 @@ static void unload(HeliAnimation *a) { free(a); } + HeliAnimation* heliAnimationLoad(const char *path) { HeliPair *item = heliStringmapGet(&cache, path); if (!item) item = heliStringmapAdd(&cache, path, load(path), (HeliFreeCallback)&unload); @@ -69,11 +224,13 @@ HeliAnimation* heliAnimationLoad(const char *path) { return a; } + void heliAnimationUnref(HeliAnimation *a) { if (--a->refcount <= 0) heliStringmapRemove(&cache, a->path); } + void heliAnimationFinish() { heliArrayDestroy(&cache); } diff --git a/src/blob.S b/src/blob.S new file mode 100644 index 0000000..639d80f --- /dev/null +++ b/src/blob.S @@ -0,0 +1,8 @@ + .global heliBlobDefaultFont + .global heliBlobDefaultFontSize + .section .rodata +heliBlobDefaultFont: + .incbin "data/unifont.ttf" +1: +heliBlobDefaultFontSize: + .int 1b - heliBlobDefaultFont diff --git a/src/common.c b/src/common.c index ec5e7fa..e58e2eb 100644 --- a/src/common.c +++ b/src/common.c @@ -1,10 +1,7 @@ -#include - #include "private.h" -cairo_t *heliCairo; int heliInitialized; HeliArray heliObjectsSet; @@ -47,6 +44,8 @@ double randomFloat() Directory openDirectory(const char *path) { if (!heliInitialized) return NULL; + return NULL; + /* TODO:bw GDir *dir = g_dir_open(path, 0, NULL); if (!dir) return NULL; @@ -61,6 +60,7 @@ Directory openDirectory(const char *path) { heliObjectRegister(d, (HeliFreeCallback)&closeDirectory); return d; + */ } void closeDirectory(Directory directory) { @@ -78,14 +78,14 @@ const char* directoryGet(Directory directory, int i) int fileExists(const char *path) - { return g_file_test(path, G_FILE_TEST_IS_REGULAR) ? TRUE : FALSE; } + { return FALSE; }//TODO:bw g_file_test(path, G_FILE_TEST_IS_REGULAR) ? TRUE : FALSE; } int directoryExists(const char *path) - { return g_file_test(path, G_FILE_TEST_IS_DIR) ? TRUE : FALSE; } + { return FALSE; }//TODO:bw g_file_test(path, G_FILE_TEST_IS_DIR) ? TRUE : FALSE; } char* heliStringCopy(const char *x) { int len = strlen(x) + 1; - char *cp = malloc(len + 1); + char *cp = malloc(len); memcpy(cp, x, len); return cp; } diff --git a/src/data/unifont.ttf b/src/data/unifont.ttf new file mode 100644 index 0000000..b47f1b9 Binary files /dev/null and b/src/data/unifont.ttf differ diff --git a/src/drawing.c b/src/drawing.c index f65f87b..fa2d6ce 100644 --- a/src/drawing.c +++ b/src/drawing.c @@ -1,43 +1,17 @@ -#include -#include FT_FREETYPE_H - -#include -#include - #include "private.h" #include "drawing.h" -typedef struct _HeliFontInstance { - char *path; - cairo_font_face_t *font_face; - int refcount; -} HeliFontInstance; - -struct _Font { - HeliFontInstance *instance; -}; - - -static int ftInitialized; -static FT_Library ftLibrary; -static HeliArray fontCache; +double [4] = {0, 0, 0, 1}; static double colorBack[4] = {1, 1, 1, 1}; static double colorFill[4] = {0.5, 0.5, 0.5, 1}; -static double colorStroke[4] = {0, 0, 0, 1}; static double lineWidth = 1; static double *path; static size_t pathSize; static size_t pathAllocated; -static HAlign horAlign = HALIGN_LEFT; -static VAlign vertAlign = VALIGN_TOP; -static char *fontFamily; -static Font font; -static double fontSize = 24; - void background(const char *color) { heliParseColor(color, colorBack); } @@ -46,7 +20,7 @@ void fill(const char *color) void noFill() { fill("transparent"); } void stroke(const char *color) - { heliParseColor(color, colorStroke); } + { heliParseColor(color, heliColorStroke); } void noStroke() { stroke("transparent"); } @@ -163,116 +137,14 @@ void moveTo(double x, double y) { resetPath(); lineTo(x, y); } -static HeliFontInstance* loadFont(const char *path) { - static cairo_user_data_key_t key = {}; - - HeliFontInstance *fi = calloc(1, sizeof(*fi)); - fi->path = heliStringCopy(path); - - if (!ftInitialized) { - if (FT_Init_FreeType(&ftLibrary)) - printf("Cannot initialize FreeType library"); - ftInitialized = TRUE; - } - if (ftLibrary) { - FT_Face face; - if (FT_New_Face(ftLibrary, path, 0, &face)) { - printf("Cannot load font from file: %s", path); - } else { - fi->font_face = cairo_ft_font_face_create_for_ft_face(face, 0); - cairo_font_face_set_user_data(fi->font_face, &key, face, (cairo_destroy_func_t)FT_Done_Face); - } - } - - return fi; -} - -static void unloadFont(HeliFontInstance *fi) { - assert(!fi->refcount); - free(fi->path); - if (fi->font_face) cairo_font_face_destroy(fi->font_face); - free(fi); -} - -Font createFont(const char *path) { - if (!heliInitialized) return NULL; - Font f = calloc(1, sizeof(*f)); - f->instance = (HeliFontInstance*)heliStringmapGet(&fontCache, path); - if (!f->instance) { - f->instance = loadFont(path); - heliStringmapAdd(&fontCache, path, f->instance, (HeliFreeCallback)&unloadFont); - } - ++f->instance->refcount; - heliObjectRegister(f, (HeliFreeCallback)&fontDestroy); - return f; -} - -void fontDestroy(Font f) { - heliObjectUnregister(f); - if (font == f) font = NULL; - --f->instance->refcount; - if (f->instance->refcount <= 0) - heliStringmapRemove(&fontCache, f->instance->path); - free(f); -} - -void textAlign(HAlign hor, VAlign vert) - { horAlign = hor; vertAlign = vert; } -void textFontFamily(const char *family) - { font = NULL; free(fontFamily); fontFamily = heliStringCopy(family); } -void textFontDefault() - { font = NULL; free(fontFamily); fontFamily = NULL; } -void textFont(Font f) - { font = f; } -void textSize(double size) - { fontSize = size; } - -void text(const char *text, double x, double y) { - resetPath(); - - cairo_t *cr = heliCairo; - if (!cr) return; - cairo_save(cr); - - if (font && font->instance->font_face) { - cairo_set_font_face(cr, font->instance->font_face); - } else - if (fontFamily) { - cairo_select_font_face(cr, fontFamily, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); - } else { - cairo_set_font_face(cr, NULL); - } - cairo_set_font_size(cr, fontSize); - - cairo_text_extents_t extents; - cairo_text_extents(cr, text, &extents); - double w = extents.width; - double h = extents.height; - if (horAlign == HALIGN_CENTER) x -= w*0.5; - if (horAlign == HALIGN_RIGHT ) x -= w; - if (vertAlign == VALIGN_CENTER) y -= h*0.5; - if (vertAlign == VALIGN_BOTTOM) y -= h; - - cairo_set_source_rgba(cr, colorStroke[0], colorStroke[1], colorStroke[2], colorStroke[3]); - cairo_move_to(cr, x - extents.x_bearing, y - extents.y_bearing); - cairo_show_text(cr, text); - - if (font && font->instance->font_face) - cairo_set_font_face(cr, NULL); - - cairo_restore(cr); -} - -void heliDrawingClearFrame(cairo_t *cr) { - cairo_save(cr); - cairo_set_source_rgba(cr, colorBack[0], colorBack[1], colorBack[2], 1); - cairo_paint(cr); - cairo_restore(cr); +void heliDrawingClearFrame() { + glClearColor(0, 0, 0, 0); + glClear(GL_COLOR_BUFFER_BIT); } void heliDrawingPrepareFrame() { resetPath(); - if (heliCairo) heliDrawingClearFrame(heliCairo); + heliDrawingClearFrame(); } void heliDrawingFinish() { diff --git a/src/drawing.h b/src/drawing.h index 7d1acdb..a8fbe64 100644 --- a/src/drawing.h +++ b/src/drawing.h @@ -5,22 +5,6 @@ #include "common.h" -typedef struct _Font *Font; - - -typedef enum _HAlign { - HALIGN_LEFT, - HALIGN_CENTER, - HALIGN_RIGHT -} HAlign; - -typedef enum _VAlign { - VALIGN_TOP, - VALIGN_CENTER, - VALIGN_BOTTOM -} VAlign; - - void background(const char *color); void fill(const char *color); void noFill(); @@ -43,16 +27,6 @@ void resetPath(); void closePath(); void strokePath(); -void text(const char *text, double x, double y); -void textAlign(HAlign hor, VAlign vert); -void textFontFamily(const char *family); -void textFontDefault(); -void textSize(double size); - -Font createFont(const char *path); -void fontDestroy(Font font); -void textFont(Font font); - #endif diff --git a/src/font.c b/src/font.c new file mode 100644 index 0000000..28374a1 --- /dev/null +++ b/src/font.c @@ -0,0 +1,495 @@ + +#include +#include FT_FREETYPE_H + +#include "private.h" +#include "font.h" + + + +#define FONT_MAP_GLYPHSIZE 64 +#define FONT_MAP_BORDERSIZE 16 +#define FONT_MAP_CNT 16 +#define FONT_MAP_GLYPHWORKSIZE (FONT_MAP_GLYPHSIZE - FONT_MAP_BORDERSIZE) +#define FONT_MAP_FULLCNT (FONT_MAP_CNT*FONT_MAP_CNT) +#define FONT_MAP_TEXSIZE (FONT_MAP_CNT*FONT_MAP_GLYPHSIZE) +#define FONT_MAX_GLYPHS (256*256) +#define FONT_BASE_SIZE 72 +#define FONT_BADCHAR_CODE 0xFFFD + + + +typedef struct _HeliFontMap HeliFontMap; +typedef struct _HeliFontInstance HeliFontInstance; +typedef struct _HeliCharDesc HeliCharDesc; + +typedef struct _HeliGlyph { + HeliFontMap *map; + HeliFontInstance *fi; + int code; + int index; + double l, t, r, b; + double advance; +} HeliGlyph; + +struct _HeliFontMap { + HeliFontMap *prev, *next; + unsigned int texid; + int count; + HeliGlyph glyphs[FONT_MAP_FULLCNT]; +}; + +struct _HeliFontInstance { + char *path; + FT_Face face; + double height; + HeliGlyph *glyphs[FONT_MAX_GLYPHS]; + int refcount; +}; + +struct _HeliCharDesc { + HeliGlyph *glyph; + double pos; + double x, y; + HeliCharDesc *next; +}; + +typedef struct _HeliLineDesc { + int begin, end; + double x, y; + double l, t, r, b; + double height; +} HeliLineDesc; + +struct _Font { + HeliFontInstance *instance; +}; + + + +static int ftInitialized = 0; +static FT_Library ftLibrary = NULL; +static HeliArray fontCache; +static HeliFontMap *fontMapFirst, *fontMapLast; +static HeliGlyph blankGlyph; +static const char blankPath[] = ""; + +static HAlign horAlign = HALIGN_LEFT; +static VAlign vertAlign = VALIGN_TOP; +static Font font; +static Font defaultFont; +static double fontSize = 24; + + + +static HeliFontMap* createFontMap() { + HeliFontMap *map = calloc(1, sizeof(*map)); + map->prev = fontMapLast; + *(fontMapLast ? &fontMapLast->next : &fontMapFirst) = map; + fontMapLast = map; + + for(int i = 0; i < FONT_MAP_FULLCNT; ++i) + map->glyphs[i].map = map; + + glGenTextures(1, &map->texid); + glBindTexture(GL_TEXTURE_2D, map->texid); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + char *buffer = calloc(1, FONT_MAP_TEXSIZE*FONT_MAP_TEXSIZE); + for(int size = FONT_MAP_TEXSIZE, border = FONT_MAP_BORDERSIZE; border > 0; border <<= 1, size <<= 1) + glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, size, size, 0, GL_ALPHA, GL_UNSIGNED_BYTE, buffer); + free(buffer); + glBindTexture(GL_TEXTURE_2D, 0); + + return map; +} + +static void destroyFontMap(HeliFontMap *map) { + assert(!map->count); + glDeleteTextures(1, &map->texid); + *(map->prev ? &map->prev->next : &fontMapFirst) = map->next; + *(map->next ? &map->next->prev : &fontMapLast ) = map->prev; + free(map); +} + + +static HeliGlyph* loadGlyph(HeliFontInstance *fi, int code) { + int index = FT_Get_Char_Index(fi->face, (unsigned int)code); + if (!index) return &blankGlyph; + + if (FT_Load_Glyph(fi->face, index, FT_LOAD_NO_HINTING)) + return &blankGlyph; + + HeliFontMap *map; + for(map = fontMapFirst; map; map = map->next) + if (map->count < FONT_MAP_FULLCNT) break; + if (!map) map = createFontMap(); + + HeliGlyph *g = NULL; + for(int i = 0; i < FONT_MAP_FULLCNT; ++i) + if (!map->glyphs[i].fi) { g = &map->glyphs[i]; break; } + assert(g); + + const double ftkf = 1/64.0; + const FT_Glyph_Metrics *metrics = &fi->face->glyph->metrics; + g->l = metrics->horiBearingX*ftkf; + g->r = (metrics->horiBearingX + metrics->width)*ftkf; + g->t = -metrics->horiBearingY*ftkf; + g->b = (-metrics->horiBearingY + metrics->height)*ftkf; + g->advance = metrics->horiAdvance*ftkf; + + FT_Matrix matrix = {}; + matrix.xx = (FONT_MAP_GLYPHWORKSIZE-2)*64*65536/metrics->width; + matrix.yy = (FONT_MAP_GLYPHWORKSIZE-2)*64*65536/metrics->height; + FT_Vector offset = {}; + offset.x = 64 - metrics->horiBearingX; + offset.y = 64 + metrics->horiBearingY - metrics->height; + FT_Set_Transform(fi->face, &matrix, &offset); + int success = !FT_Load_Glyph(fi->face, index, FT_LOAD_NO_HINTING)) + && !FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL); + matrix.xx = matrix.yy = 65536; + FT_Set_Transform(fi->face, NULL, NULL); + if (!success) return &blankGlyph; + + const FT_Bitmap *bitmap = &fi->face->glyph->bitmap; + unsigned char buffer[FONT_MAP_GLYPHWORKSIZE * FONT_MAP_GLYPHWORKSIZE] = {}; + int dr = fi->face->glyph->bitmap_top + 1 - FONT_MAP_GLYPHWORKSIZE; + int dc = -fi->face->glyph->bitmap_left; + int r0 = -dr; + int r1 = r0 + bitmap->rows; + int c0 = -dc; + int c1 = c0 + bitmap->width; + if (r0 < 0) r0 = 0; + if (c0 < 0) c0 = 0; + if (r1 > FONT_MAP_GLYPHWORKSIZE) r1 = FONT_MAP_GLYPHWORKSIZE; + if (c1 > FONT_MAP_GLYPHWORKSIZE) c1 = FONT_MAP_GLYPHWORKSIZE; + for(int r = r0; r < r1; ++r) + for(int c = c0; c < c1; ++c) + buffer[r*FONT_MAP_GLYPHWORKSIZE + c] = *(bitmap->buffer + bitmap->pitch*(r + dr) + dc); + + glBindTexture(GL_TEXTURE_2D, g->map->texid); + int gi = g - g->map->glyphs; + int r = gi/FONT_MAP_CNT; + int c = gi%FONT_MAP_CNT; + int size = FONT_MAP_GLYPHWORKSIZE; + for(int level = 0, border = FONT_MAP_BORDERSIZE; border > 0; border <<= 1, ++level) { + glTexSubImage2D( + GL_TEXTURE_2D, + level, + c*(size + border), + r*(size + border), + size, + size, + GL_ALPHA, + GL_UNSIGNED_BYTE, + buffer ); + size <<= 1; + if (border > 1) { + for(int r = 0; r < size; ++r) + for(int c = 0; c < size; ++c) + buffer[r*size + c] = (unsigned char)(( + (int)buffer[(r+0)*2*size + (c+0)*2] + + (int)buffer[(r+0)*2*size + (c+1)*2] + + (int)buffer[(r+1)*2*size + (c+0)*2] + + (int)buffer[(r+1)*2*size + (c+1)*2] + 2) << 2); + } + } + free(buffer); + glBindTexture(GL_TEXTURE_2D, 0); + + g->fi = fi; + g->code = code; + g->index = index; + return g; +} + + +static void unloadGlyph(HeliGlyph *g) { + g->fi = NULL; + g->code = 0; + g->index = 0; + if (g->map && --g->map->count <= 0) + destroyFontMap(g->map); +} + + +static HeliFontInstance* loadFont(const char *path) { + HeliFontInstance *fi = calloc(1, sizeof(*fi)); + fi->path = heliStringCopy(path); + + if (!ftInitialized) { + if (FT_Init_FreeType(&ftLibrary)) + printf("Cannot initialize FreeType library"); + ftInitialized = TRUE; + } + if (ftLibrary) { + if (path[0]) { + if (FT_New_Face(ftLibrary, path, 0, &fi->face)) { + printf("Cannot load font from file: %s", path); + fi->face = NULL; + } + } else { + FT_Open_Args args = {}; + args.flags = FT_OPEN_MEMORY; + args.memory_base = (FT_Byte*)heliBlobDefaultFont; + args.memory_size = heliBlobDefaultFontSize; + if (!FT_Open_Face(ftLibrary, &args, 0, &fi->face)) { + printf("Cannot initialize default font"); + fi->face = NULL; + } + } + if (fi->face) { + FT_Set_Char_Size(fi->face, 0, FONT_BASE_SIZE*64, 72, 72); + fi->height = fi->face->size->metrics.height/64.0; + } + } + + return fi; +} + + +static void unloadFont(HeliFontInstance *fi) { + assert(!fi->refcount); + for(int i = 0; i < FONT_MAX_GLYPHS; ++i) + if (fi->glyphs[i]) unloadGlyph(fi->glyphs[i]); + free(fi->path); + if (fi->face) FT_Done_Face(fi->face); + free(fi); +} + + +static HeliGlyph* getGlyph(HeliFontInstance *fi, int code) { + if (code < 0 || code >= FONT_MAX_GLYPHS) return &blankGlyph; + if (!fi->glyphs[code]) fi->glyphs[code] = loadGlyph(fi->font); + return fi->glyphs[code]; +} + + +Font createFont(const char *path) { + if (!heliInitialized) return NULL; + if (!path) path = blankPath; + Font f = calloc(1, sizeof(*f)); + f->instance = (HeliFontInstance*)heliStringmapGet(&fontCache, path); + if (!f->instance) { + f->instance = loadFont(path); + heliStringmapAdd(&fontCache, path, f->instance, (HeliFreeCallback)&unloadFont); + } + ++f->instance->refcount; + heliObjectRegister(f, (HeliFreeCallback)&fontDestroy); + return f; +} + +void fontDestroy(Font f) { + heliObjectUnregister(f); + if (font == f) font = NULL; + --f->instance->refcount; + if (f->instance->refcount <= 0) + heliStringmapRemove(&fontCache, f->instance->path); + free(f); +} + +void textAlign(HAlign hor, VAlign vert) + { horAlign = hor; vertAlign = vert; } +void textFontDefault() + { font = NULL; } +void textFont(Font f) + { font = f; } +void textSize(double size) + { fontSize = size; } + + +void text(const char *text, double x, double y) { + resetPath(); + + double fontScale = fontSize/FONT_BASE_SIZE; + if (fontScale < HELI_PRECISION) return; + + if (!defaultFont) defaultFont = createFont(NULL); + HeliFontInstance *f = font ? font->instance : NULL; + HeliFontInstance *ff = defaultFont ? defaultFont->instance : NULL; + if (!f) f = ff; + if (!ff) ff = f; + if (!f) return; + + if (!f->face) f = ff; + if (!ff->face) ff = f; + if (!f->face) return; + + int linesCount = 1; + int linesAllocated = 256; + HeliLineDesc *lines = calloc(linesAllocated, sizeof(HeliLineDesc)); + HeliLineDesc *line = lines; + line->height = f->height; + HeliCharDesc *chars = calloc(strlen(text), sizeof(HeliCharDesc)); + + // gather glyphs + const char *c = text; + while(1) { + int code = FONT_BADCHAR_CODE; + while((*c & 0xC0) == 0x80) ++c; + if ((*c & 0x80) == 0x00) { + code = *c; + } else + if ((*c & 0xE0) == 0xC0) { + if ((c[1] & 0xC0) == 0x80) { + code = ((c[0] & 0x1F) << 6) | (c[1] & 0x3F); + c += 1; + } + } else + if ((*c & 0xF0) == 0xE0) { + if ((c[1] & 0xC0) == 0x80 && (c[2] & 0xC0) == 0x80) { + code = ((c[0] & 0x0F) << 12) | ((c[1] & 0x3F) << 6) | (c[2] & 0x3F); + c += 2; + } + } + ++c; + + if (code == 0) break; + if (code == 10 || code == 13) { + if ((code == 10 && *c == 13) || (code == 13 && *c == 10)) ++c; + if (linesCount >= linesAllocated) { + linesAllocated += linesAllocated/4; + lines = realloc(lines, linesAllocated*sizeof(HeliLineDesc)); + memset(lines + linesCount, 0, (linesAllocated - linesCount)*sizeof(HeliLineDesc)); + line = &lines[linesCount - 1]; + } + ++line; + ++linesCount; + line->begin = line->end = (line - 1)->end; + line->height = f->height; + continue; + } + + HeliGlyph *glyph = &blankGlyph; + if (!glyph->fi) glyph = loadGlyph(f, code); + if (!glyph->fi) glyph = loadGlyph(ff, code); + if (!glyph->fi) glyph = loadGlyph(f, FONT_BADCHAR_CODE); + if (!glyph->fi) glyph = loadGlyph(ff, FONT_BADCHAR_CODE); + if (!glyph->fi) continue; + + HeliCharDesc *cc = &chars[line->end++]; + cc->glyph = glyph; + if (line->height < glyph->fi->height) line->height = glyph->fi->height; + if (line->end - 1 <= line->begin) { + line->l = cc->glyph->l; + line->t = cc->glyph->t; + line->r = cc->glyph->r; + line->b = cc->glyph->b; + } else { + HeliCharDesc *cp = cc - 1; + cc->pos = cp->pos + cp->glyph->advance; + FT_Vector kerning = {}; + if ( cp->glyph->fi == cc->glyph->fi + && !FT_Get_Kerning( + cc->glyph->fi->face, + cp->glyph->index, + cc->glyph->index, + FT_KERNING_UNFITTED, + &kerning )) + cc->pos += kerning.x/64.0; + if (line->l > cc->glyph->l) line->l = cc->glyph->l; + if (line->t > cc->glyph->t) line->t = cc->glyph->t; + if (line->r < cc->glyph->r) line->r = cc->glyph->r; + if (line->b < cc->glyph->b) line->b = cc->glyph->b; + } + } + + // bounds + double l = lines->l; + double t = lines->t; + double r = lines->r; + double b = lines->b; + for(int i = 1; i < linesCount; ++i) { + HeliLineDesc *line = &lines[i]; + line->y = (line-1)->y + line->height; + if (l > line->l) l = line->l; + if (r < line->r) r = line->r; + if (t > line->t + line->y) t = line->t + line->y; + if (b < line->b + line->y) b = line->b + line->y; + } + + // alignment + double dy = vertAlign == VALIGN_BOTTOM ? -b + : vertAlign == VALIGN_CENTER ? -0.5*(t + b) + : -t; + for(int i = 0; i < linesCount; ++i) { + HeliLineDesc *line = &lines[i]; + line->x = horAlign == HALIGN_RIGHT ? -line->r + : horAlign == HALIGN_CENTER ? -0.5*(line->l + line->r) + : -line->l; + line->y += dy; + for(int j = line->begin; j < line->end; ++j) { + HeliCharDesc *cc = &chars[j]; + cc->x = cc->pos + line->x; + cc->y = line->y; + } + } + + // sort by textures + int charsCount = lines[linesCount-1].end; + for(int i = 0; i < charsCount; ++i) + chars[i].next = &chars[i+1]; + HeliCharDesc *first = NULL, *last = NULL, *current = chars; + while(current) { + if (!current->next) { + current = current->next = first; + } else + if (current->glyph->map != current->next->glyph->map) { + *(last ? &last->next : &first) = current->next; + last = current->next; + current->next = current->next->next; + last->next = NULL; + } else { + current = current->next; + } + } + + // draw + const double borderK = 0.5*FONT_MAP_BORDERSIZE/FONT_MAP_GLYPHWORKSIZE; + const double texStep = 1.0/FONT_MAP_CNT; + const double texOffset = -0.5*FONT_MAP_BORDERSIZE/FONT_MAP_TEXSIZE; + + glPushMatrix(); + glTranslated(x, y, 0); + glScaled(fontScale, fontScale, 1); + glColor4dv(heliColorStroke); + HeliFontMap *currentMap = NULL; + glEnable(GL_TEXTURE_2D); + glBegin(GL_QUADS); + for(HeliCharDesc *cc = chars; cc; cc = cc->next) { + if ( !cc->glyph->map + || cc->glyph->r - cc->glyph->l <= HELI_PRECISION + || cc->glyph->b - cc->glyph->t <= HELI_PRECISION ) + continue; + if (currentMap != cc->glyph->map) { + glEnd(); + currentMap = cc->glyph->map; + glBindTexture(GL_TEXTURE_2D, currentMap->texid); + glBegin(GL_QUADS); + } + + double dx = (cc->glyph->r - cc->glyph->l)*borderK; + double dy = (cc->glyph->t - cc->glyph->b)*borderK; + double l = cc->x + cc->glyph->l - dx; + double t = cc->y + cc->glyph->t - dy; + double r = cc->x + cc->glyph->r + dx; + double b = cc->y + cc->glyph->b + dy; + + double tl = (cc->glyph->index % FONT_MAP_CNT)*texStep + texOffset; + double tt = (cc->glyph->index / FONT_MAP_CNT)*texStep + texOffset; + double tr = tl + texStep; + double tb = tt + texStep; + + glTexCoord2d(tl, tt); glVertex2d(l, t); + glTexCoord2d(tr, tt); glVertex2d(r, t); + glTexCoord2d(tr, tb); glVertex2d(r, b); + glTexCoord2d(tl, tb); glVertex2d(l, b); + } + glEnd(); + glBindTexture(GL_TEXTURE_2D, 0); + glDisable(GL_TEXTURE_2D); + glPopMatrix(); + glColor4d(1, 1, 1, 1); +} diff --git a/src/font.h b/src/font.h new file mode 100644 index 0000000..27e72e2 --- /dev/null +++ b/src/font.h @@ -0,0 +1,34 @@ +#ifndef HELI_FONT_H +#define HELI_FONT_H + + +#include "common.h" + + +typedef struct _Font *Font; + + +typedef enum _HAlign { + HALIGN_LEFT, + HALIGN_CENTER, + HALIGN_RIGHT +} HAlign; + +typedef enum _VAlign { + VALIGN_TOP, + VALIGN_CENTER, + VALIGN_BOTTOM +} VAlign; + + +Font createFont(const char *path); +void fontDestroy(Font font); +void textFont(Font font); +void textFontDefault(); + +void text(const char *text, double x, double y); +void textAlign(HAlign hor, VAlign vert); +void textSize(double size); + + +#endif diff --git a/src/private.h b/src/private.h index b8acfe4..04453f7 100644 --- a/src/private.h +++ b/src/private.h @@ -6,7 +6,7 @@ #include #include -#include +#include #include "common.h" #include "sprite.h" @@ -17,7 +17,11 @@ #define HELI_PRECISION 1e-6 #define HELI_PRECISION_SQR 1e-12 -extern cairo_t *heliCairo; + +// blobs + +extern char heliBlobDefaultFont[]; +extern int heliBlobDefaultFontSize; // string @@ -129,7 +133,8 @@ void heliSpriteFinish(); // drawing -void heliDrawingClearFrame(cairo_t *cr); +extern double heliColorStroke[4]; +void heliDrawingClearFrame(); void heliDrawingPrepareFrame(); void heliDrawingFinish(); diff --git a/src/world.c b/src/world.c index eed4a2f..322be68 100644 --- a/src/world.c +++ b/src/world.c @@ -1,7 +1,5 @@ -#include -#include -#include +#include #include "private.h"