390 lines · 11.1 KB · C
RawDownload
/* This is a slightly modified   https://github.com/ccxvii/snippets/blob/master/glfont.c by Tor Andersson*//* * A very simple font cache and rasterizer that uses freetype * to draw fonts from a single OpenGL texture. The code uses * a linear-probe hashtable, and writes new glyphs into * the texture using glTexSubImage2D. When the texture fills * up, or the hash table gets too crowded, everything is wiped. * * This is designed to be used for horizontal text only, * and draws unhinted text with subpixel accurate metrics * and kerning. As such, you should always call the drawing * function with an identity transform that maps units * to pixels accurately. * * If you wish to use it to draw arbitrarily transformed * text, change the min and mag filters to GL_LINEAR and * add a pixel of padding between glyphs and rows, and * make sure to clear the texture when wiping the cache. */ #include FT_ADVANCES_Htypedef int Rune;               /* 32 bits */ #define PADDING 1               /* set to 0 to save some space but disallow arbitrary transforms */ #define MAXGLYPHS 4093  /* prime number for hash table goodness */#define CACHESIZE 256#define XPRECISION 4#define YPRECISION 1 struct key{        FT_Face face;        short size;        short gid;        short subx;        short suby;}; struct glyph{        signed char lsb, top, w, h;        short s, t;        float advance;}; struct table{        struct key key;        struct glyph glyph;}; static FT_Library g_freetype_lib = NULL;static struct table g_table[MAXGLYPHS];static int g_table_load = 0;static unsigned int g_cache_tex = 0;static int g_cache_w = CACHESIZE;static int g_cache_h = CACHESIZE;static int g_cache_row_y = 0;static int g_cache_row_x = 0;static int g_cache_row_h = 0;static int g_use_kern = 0; static void init_font_cache(void){        int code;         code = FT_Init_FreeType(&g_freetype_lib);        if (code)                errx(1, "cannot initialize freetype");         glGenTextures(1, &g_cache_tex);        glBindTexture(GL_TEXTURE_2D, g_cache_tex);        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);        glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, g_cache_w, g_cache_h, 0, GL_ALPHA, GL_UNSIGNED_BYTE, NULL);} static void clear_font_cache(void){#if PADDING > 0        unsigned char *zero = calloc(g_cache_w, g_cache_h);        if (!zero)                err(1, errno, "malloc zero (%u bytes failed)", g_cache_w * g_cache_h);        glBindTexture(GL_TEXTURE_2D, g_cache_tex);        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, g_cache_w, g_cache_h, GL_ALPHA, GL_UNSIGNED_BYTE, zero);        free(zero);#endif         memset(g_table, 0, sizeof(g_table));        g_table_load = 0;         g_cache_row_y = PADDING;        g_cache_row_x = PADDING;        g_cache_row_h = 0;} static void *filecontents (const char *path, int *len){        int ret, fd;        void *res;        struct stat st;        ssize_t nread;         ret = stat(path, &st);        if (ret) err(1, errno, "failed to stat `%s'", path);        if (st.st_size > INT_MAX) errx(1, "font `%s' is too big", path);        res = malloc(st.st_size);        if (!res)                err(1, errno, "failed to allocate %llu bytes for `%s'",                    st.st_size+0ull, path);         fd = open(path, O_RDONLY);        if (fd < 0) err(1, errno, "failed to open `%s'", path);         nread = read(fd, res, st.st_size);        if (nread - st.st_size)                err(1, errno, "read %llu failed, ret=%zd",                    st.st_size+0llu, nread);         *len = (int) st.st_size;        return res;} static FT_Face load_font(const char *fontname){        FT_Face face;        int code, len;        void *base;         if (g_freetype_lib == NULL)        {                init_font_cache();                clear_font_cache();        }         base = filecontents(fontname, &len);        code = FT_New_Memory_Face(g_freetype_lib, base, len, 0, &face);        if (code)        {                errx(1, "FT_New_Memory_Face for `%s' failed: %d",                     fontname, code);        }         FT_Select_Charmap(face, ft_encoding_unicode);        return face;} static FT_Face load_builtin_font(const void *base, int len){        FT_Face face;        int code;         if (g_freetype_lib == NULL)        {                init_font_cache();                clear_font_cache();        }         code = FT_New_Memory_Face(g_freetype_lib, base, len, 0, &face);        if (code)        {                errx (1, "failed to load builtin font: %d\n", code);                return NULL;        }         FT_Select_Charmap(face, ft_encoding_unicode);        return face;} static void UNUSED_ATTR free_font(FT_Face face){        clear_font_cache();        FT_Done_Face(face);} static unsigned int hashfunc(struct key *key){        unsigned char *buf = (unsigned char *)key;        unsigned int len = sizeof(struct key);        unsigned int h = 0;        while (len--)                h = *buf++ + (h << 6) + (h << 16) - h;        return h;} static unsigned int lookup_table(struct key *key){        unsigned int pos = hashfunc(key) % MAXGLYPHS;        while (1)        {                if (!g_table[pos].key.face) /* empty slot */                        return pos;                if (!memcmp(key, &g_table[pos].key, sizeof(struct key))) /* matching slot */                        return pos;                pos = (pos + 1) % MAXGLYPHS;        }} static struct glyph * lookup_glyph(FT_Face face, int size, int gid, int subx, int suby){        FT_Vector subv;        struct key key;        unsigned int pos;        int code;        int w, h;         /*         * Look it up in the table         */         key.face = face;        key.size = size;        key.gid = gid;        key.subx = subx;        key.suby = suby;         pos = lookup_table(&key);        if (g_table[pos].key.face)                return &g_table[pos].glyph;         /*         * Render the bitmap         */        subv.x = subx;        subv.y = suby;         FT_Set_Transform(face, NULL, &subv);         code = FT_Load_Glyph(face, gid, FT_LOAD_NO_BITMAP | FT_LOAD_NO_HINTING);        if (code < 0)                return NULL;         code = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_LIGHT);        if (code < 0)                return NULL;         w = face->glyph->bitmap.width;        h = face->glyph->bitmap.rows;         /*         * Find an empty slot in the texture         */         if (g_table_load == (MAXGLYPHS * 3) / 4)        {                clear_font_cache();                pos = lookup_table(&key);        }         if (h + PADDING > g_cache_h || w + PADDING > g_cache_w)                errx(1, "rendered glyph exceeds cache dimensions");         if (g_cache_row_x + w + PADDING > g_cache_w)        {                g_cache_row_y += g_cache_row_h + PADDING;                g_cache_row_x = PADDING;                g_cache_row_h = 0;        }        if (g_cache_row_y + h + PADDING > g_cache_h)        {                clear_font_cache();                pos = lookup_table(&key);        }         /*         * Copy bitmap into texture         */         memcpy(&g_table[pos].key, &key, sizeof(struct key));        g_table[pos].glyph.w = face->glyph->bitmap.width;        g_table[pos].glyph.h = face->glyph->bitmap.rows;        g_table[pos].glyph.lsb = face->glyph->bitmap_left;        g_table[pos].glyph.top = face->glyph->bitmap_top;        g_table[pos].glyph.s = g_cache_row_x;        g_table[pos].glyph.t = g_cache_row_y;        g_table[pos].glyph.advance = face->glyph->advance.x / 64.0;        g_table_load ++;         glPixelStorei(GL_UNPACK_ALIGNMENT, 1);        glPixelStorei(GL_UNPACK_ROW_LENGTH, face->glyph->bitmap.pitch);        glTexSubImage2D(GL_TEXTURE_2D, 0, g_cache_row_x, g_cache_row_y, w, h,                        GL_ALPHA, GL_UNSIGNED_BYTE, face->glyph->bitmap.buffer);        glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);         g_cache_row_x += w + PADDING;        if (g_cache_row_h < h + PADDING)                g_cache_row_h = h + PADDING;         return &g_table[pos].glyph;} static float draw_glyph(FT_Face face, int size, int gid, float x, float y){        struct glyph *glyph;        int subx = (x - floor(x)) * XPRECISION;        int suby = (y - floor(y)) * YPRECISION;        GLfloat *t = state.texcoords;        GLfloat *v = state.vertices;        float s0, t0, s1, t1, xc, yc;         subx = (subx * 64) / XPRECISION;        suby = (suby * 64) / YPRECISION;         glyph = lookup_glyph(face, size, gid, subx, suby);        if (!glyph)                return 0.0;         s0 = (float) glyph->s / g_cache_w;        t0 = (float) glyph->t / g_cache_h;        s1 = (float) (glyph->s + glyph->w) / g_cache_w;        t1 = (float) (glyph->t + glyph->h) / g_cache_h;        xc = floor(x) + glyph->lsb;        yc = floor(y) - glyph->top + glyph->h;         t[0] = s0; t[1] = t0; v[0] = xc;            v[1] = yc - glyph->h;        t[2] = s1; t[3] = t0; v[2] = xc + glyph->w; v[3] = yc - glyph->h;        t[4] = s0; t[5] = t1; v[4] = xc;            v[5] = yc;        t[6] = s1; t[7] = t1; v[6] = xc + glyph->w; v[7] = yc;         glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);        return glyph->advance;} static float measure_string(FT_Face face, float fsize, const char *str){        int size = fsize * 64;        FT_Fixed advance;        Rune ucs, gid;        float w = 0.0;        int left = 0;         FT_Set_Char_Size(face, size, size, 72, 72);         while (*str)        {                str += fz_chartorune(&ucs, str);                gid = FT_Get_Char_Index(face, ucs);                FT_Get_Advance(face, gid, FT_LOAD_NO_BITMAP | FT_LOAD_NO_HINTING, &advance);                w += advance / 65536.0;                if (g_use_kern) {                    FT_Vector kern;                     FT_Get_Kerning(face, left, gid, FT_KERNING_UNFITTED, &kern);                    w += kern.x / 64.0;                }                left = gid;        }         return w;} static float draw_string(FT_Face face, float fsize, float x, float y,                         const char *str){        int size = fsize * 64;        Rune ucs, gid;        int left = 0;         FT_Set_Char_Size(face, size, size, 72, 72);         glBindTexture(GL_TEXTURE_2D, g_cache_tex);        glVertexPointer(2, GL_FLOAT, 0, state.vertices);        glTexCoordPointer(2, GL_FLOAT, 0, state.texcoords);         while (*str)        {                str += fz_chartorune(&ucs, str);                gid = FT_Get_Char_Index(face, ucs);                x += draw_glyph(face, size, gid, x, y);                if (g_use_kern) {                    FT_Vector kern;                     FT_Get_Kerning(face, left, gid, FT_KERNING_UNFITTED, &kern);                    x += kern.x / 64.0;                }                left = gid;        }         return x;}/*  Local Variables:  c-file-style: "linux"  End:*/

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t