480 lines
9.3 KiB
C
480 lines
9.3 KiB
C
/*
|
|
* gl_font.c
|
|
*
|
|
* Basic OpenGL font rendering
|
|
*
|
|
* Copyright (C) 2013-2021 Sylvain Munaut
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
*/
|
|
|
|
/*! \addtogroup gl/font
|
|
* @{
|
|
*/
|
|
|
|
/*! \file gl_font.c
|
|
* \brief Basic OpenGL font rendering
|
|
*/
|
|
|
|
#include <errno.h>
|
|
#include <math.h>
|
|
#include <stdarg.h>
|
|
#include <stdint.h>
|
|
|
|
#include <ft2build.h>
|
|
#include FT_FREETYPE_H
|
|
#include FT_GLYPH_H
|
|
#include FT_BITMAP_H
|
|
#include FT_LCD_FILTER_H
|
|
|
|
#include "gl_platform.h"
|
|
|
|
#include "gl_font.h"
|
|
|
|
|
|
#define GLF_MIN_CHR 32 /*!< Minimum ascii char we display */
|
|
#define GLF_MAX_CHR 127 /*!< Maximum ascii char we display */
|
|
#define GLF_N_CHR (GLF_MAX_CHR - GLF_MIN_CHR + 1)
|
|
|
|
struct gl_glyph
|
|
{
|
|
FT_Glyph glyph;
|
|
int advance_x;
|
|
};
|
|
|
|
struct gl_font
|
|
{
|
|
/* Book keeping */
|
|
int loaded;
|
|
|
|
int height;
|
|
int flags;
|
|
|
|
/* Free type stuff */
|
|
FT_Library library; /*!< */
|
|
FT_Face face; /*!< */
|
|
|
|
struct gl_glyph glyphs[GLF_N_CHR];
|
|
|
|
/* GL stuff */
|
|
GLuint vbo;
|
|
|
|
/* Texture */
|
|
struct {
|
|
int width;
|
|
int height;
|
|
GLuint id;
|
|
} tex;
|
|
|
|
/* Glyphs */
|
|
struct {
|
|
int width;
|
|
int height;
|
|
int ofs_x;
|
|
int ofs_y;
|
|
} glyph_bb;
|
|
};
|
|
|
|
|
|
struct gl_font *
|
|
glf_alloc(int height, int flags)
|
|
{
|
|
struct gl_font *glf;
|
|
FT_Error ftr;
|
|
|
|
glf = calloc(1, sizeof(struct gl_font));
|
|
if (!glf)
|
|
return NULL;
|
|
|
|
ftr = FT_Init_FreeType(&glf->library);
|
|
if (ftr)
|
|
goto err;
|
|
|
|
if (flags & GLF_FLG_LCD)
|
|
FT_Library_SetLcdFilter(glf->library, FT_LCD_FILTER_DEFAULT);
|
|
|
|
glf->height = height;
|
|
glf->flags = flags;
|
|
|
|
glGenBuffers(1, &glf->vbo);
|
|
|
|
return glf;
|
|
|
|
err:
|
|
glf_free(glf);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void
|
|
glf_free(struct gl_font *glf)
|
|
{
|
|
if (!glf)
|
|
return;
|
|
|
|
if (glf->loaded) {
|
|
int i;
|
|
|
|
for (i=0; i<GLF_N_CHR; i++)
|
|
if (glf->glyphs[i].glyph)
|
|
FT_Done_Glyph(glf->glyphs[i].glyph);
|
|
|
|
glDeleteTextures(1, &glf->tex.id);
|
|
}
|
|
|
|
glDeleteBuffers(1, &glf->vbo);
|
|
|
|
if (glf->face)
|
|
FT_Done_Face(glf->face);
|
|
|
|
if (glf->library)
|
|
FT_Done_FreeType(glf->library);
|
|
|
|
free(glf);
|
|
}
|
|
|
|
static int
|
|
np2(int x)
|
|
{
|
|
int r = 1;
|
|
while (r && r < x)
|
|
r <<= 1;
|
|
return r;
|
|
}
|
|
|
|
static int
|
|
_glf_init_glyph(struct gl_font *glf, int ch, uint8_t *data)
|
|
{
|
|
FT_Glyph glyph = NULL;
|
|
FT_BitmapGlyph bitmap_glyph;
|
|
FT_Bitmap bitmap;
|
|
FT_Error ftr;
|
|
int tx, ty, px, py;
|
|
int x, y, m, rv;
|
|
|
|
ftr = FT_Load_Glyph(glf->face, FT_Get_Char_Index(glf->face, ch), FT_LOAD_DEFAULT);
|
|
if (ftr) {
|
|
rv = -ENOMEM;
|
|
goto err;
|
|
}
|
|
|
|
ftr = FT_Get_Glyph(glf->face->glyph, &glyph);
|
|
if (ftr) {
|
|
rv = -ENOMEM;
|
|
goto err;
|
|
}
|
|
|
|
FT_Glyph_To_Bitmap(
|
|
&glyph,
|
|
glf->flags & GLF_FLG_LCD ?
|
|
FT_RENDER_MODE_LCD : FT_RENDER_MODE_NORMAL,
|
|
0, 1
|
|
);
|
|
|
|
bitmap_glyph = (FT_BitmapGlyph)glyph;
|
|
bitmap = bitmap_glyph->bitmap;
|
|
|
|
m = glf->flags & GLF_FLG_LCD ? 3 : 1;
|
|
|
|
px = ((ch - GLF_MIN_CHR) & 15) * glf->glyph_bb.width;
|
|
py = ((ch - GLF_MIN_CHR) >> 4) * glf->glyph_bb.height;
|
|
|
|
for (y=0; y<bitmap.rows; y++) {
|
|
for (x=0; x<bitmap.width; x++) {
|
|
tx = m * (px + (glf->glyph_bb.ofs_x + bitmap_glyph->left)) + x;
|
|
ty = py + glf->glyph_bb.height - (glf->glyph_bb.ofs_y + bitmap_glyph->top) + y;
|
|
|
|
data[m * glf->tex.width * ty + tx] =
|
|
bitmap.buffer[bitmap.pitch*y + x];
|
|
}
|
|
}
|
|
|
|
glf->glyphs[ch - GLF_MIN_CHR].glyph = glyph;
|
|
glf->glyphs[ch - GLF_MIN_CHR].advance_x = glf->face->glyph->advance.x >> 6;
|
|
|
|
return 0;
|
|
|
|
err:
|
|
if (glyph)
|
|
FT_Done_Glyph(glyph);
|
|
|
|
return rv;
|
|
}
|
|
|
|
static int
|
|
_glf_init_face(struct gl_font *glf)
|
|
{
|
|
int min_x, max_x, min_y, max_y;
|
|
uint8_t *data;
|
|
int i;
|
|
|
|
/* Set request size */
|
|
FT_Set_Char_Size(glf->face, glf->height << 6, glf->height << 6, 96, 96);
|
|
|
|
/* Find char BB and select texture size */
|
|
min_x = FT_MulFix(glf->face->bbox.xMin, glf->face->size->metrics.x_scale) >> 6;
|
|
max_x = FT_MulFix(glf->face->bbox.xMax, glf->face->size->metrics.x_scale) >> 6;
|
|
min_y = FT_MulFix(glf->face->bbox.yMin, glf->face->size->metrics.x_scale) >> 6;
|
|
max_y = FT_MulFix(glf->face->bbox.yMax, glf->face->size->metrics.x_scale) >> 6;
|
|
|
|
glf->glyph_bb.width = max_x - min_x + 1;
|
|
glf->glyph_bb.height = max_y - min_y + 1;
|
|
glf->glyph_bb.ofs_x = - min_x;
|
|
glf->glyph_bb.ofs_y = - min_y;
|
|
|
|
glf->tex.width = np2(glf->glyph_bb.width * 16);
|
|
glf->tex.height = np2(glf->glyph_bb.height * (GLF_N_CHR + 15) >> 4);
|
|
|
|
/* Raw data array */
|
|
data = calloc(glf->tex.width * glf->tex.height, glf->flags & GLF_FLG_LCD ? 3 : 1);
|
|
if (!data)
|
|
return -ENOMEM;
|
|
|
|
/* Init all glyphs */
|
|
for (i=GLF_MIN_CHR; i<=GLF_MAX_CHR; i++)
|
|
_glf_init_glyph(glf, i, data);
|
|
|
|
/* Create GL texture */
|
|
glGenTextures(1, &glf->tex.id);
|
|
|
|
glBindTexture(GL_TEXTURE_2D, glf->tex.id);
|
|
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
|
|
|
|
if (glf->flags & GLF_FLG_LCD) {
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, glf->tex.width, glf->tex.height, 0,
|
|
GL_RGB, GL_UNSIGNED_BYTE, data);
|
|
} else {
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, glf->tex.width, glf->tex.height, 0,
|
|
GL_ALPHA, GL_UNSIGNED_BYTE, data);
|
|
}
|
|
|
|
/* Done */
|
|
free(data);
|
|
|
|
glf->loaded = 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
glf_load_face_file(struct gl_font *glf, const char *filename)
|
|
{
|
|
FT_Error ftr;
|
|
|
|
if (glf->loaded)
|
|
return -EBUSY;
|
|
|
|
ftr = FT_New_Face(glf->library, filename, 0, &glf->face);
|
|
if (ftr)
|
|
return -EINVAL;
|
|
|
|
return _glf_init_face(glf);
|
|
}
|
|
|
|
int
|
|
glf_load_face_mem(struct gl_font *glf, const void *data, size_t len)
|
|
{
|
|
FT_Error ftr;
|
|
|
|
if (glf->loaded)
|
|
return -EBUSY;
|
|
|
|
ftr = FT_New_Memory_Face(glf->library, data, len, 0, &glf->face);
|
|
if (ftr)
|
|
return -EINVAL;
|
|
|
|
return _glf_init_face(glf);
|
|
}
|
|
|
|
static void
|
|
_glf_add_char(const struct gl_font *glf, float *data, char c, float x)
|
|
{
|
|
float u0, v0, u1, v1;
|
|
float cw, ch, crw, crh;
|
|
|
|
c -= GLF_MIN_CHR;
|
|
|
|
cw = (float)glf->glyph_bb.width;
|
|
ch = (float)glf->glyph_bb.height;
|
|
crw = cw / (float)glf->tex.width;
|
|
crh = ch / (float)glf->tex.height;
|
|
|
|
u0 = (c & 15) * crw;
|
|
v0 = (c >> 4) * crh;
|
|
u1 = u0 + crw;
|
|
v1 = v0 + crh;
|
|
|
|
#define VTX(x,y,u,v) do { \
|
|
data[0] = data[1] = data[2] = 1.0f; data[3] = 0.0f; \
|
|
data[4] = x; data[5] = y; data[6] = u, data[7] = v; \
|
|
data += 8; \
|
|
} while (0)
|
|
|
|
VTX(x, 0.0f, u0, v1);
|
|
VTX(x + cw, 0.0f, u1, v1);
|
|
VTX(x + cw, ch, u1, v0);
|
|
VTX(x, ch, u0, v0);
|
|
|
|
#undef VTX
|
|
}
|
|
|
|
float
|
|
glf_width_str(const struct gl_font *glf, const char *str)
|
|
{
|
|
float xb = 0.0f;
|
|
int i;
|
|
|
|
for (i=0; str[i]; i++) {
|
|
xb += (float)glf->glyphs[str[i] - GLF_MIN_CHR].advance_x;
|
|
}
|
|
|
|
return xb;
|
|
}
|
|
|
|
void
|
|
glf_draw_str(const struct gl_font *glf,
|
|
float x, enum glf_align x_align,
|
|
float y, enum glf_align y_align,
|
|
const char *str)
|
|
{
|
|
float *data;
|
|
float xb, xofs, yofs;
|
|
int i;
|
|
|
|
/* Temporary buffer for vertex data */
|
|
data = malloc(8 * sizeof(float) * 4 * strlen(str));
|
|
|
|
/* Add chars to the buffer */
|
|
xb = 0.0f;
|
|
|
|
for (i=0; str[i]; i++) {
|
|
_glf_add_char(glf, &data[32*i], str[i], xb);
|
|
xb += (float)glf->glyphs[str[i] - GLF_MIN_CHR].advance_x;
|
|
}
|
|
|
|
/* Align */
|
|
if (x_align == GLF_CENTER) {
|
|
xofs = x - roundf(xb / 2.0f);
|
|
} else if (x_align == GLF_RIGHT) {
|
|
xofs = x - xb;
|
|
} else {
|
|
xofs = x;
|
|
}
|
|
|
|
xofs -= (float) glf->glyph_bb.ofs_x;
|
|
|
|
if (y_align == GLF_TOP) {
|
|
yofs = y - (float)glf->glyph_bb.height;
|
|
} else if (y_align == GLF_CENTER) {
|
|
yofs = y - roundf((float)glf->glyph_bb.height / 2.0f);
|
|
} else {
|
|
yofs = y;
|
|
}
|
|
|
|
yofs += (float) glf->glyph_bb.ofs_y;
|
|
|
|
for (i=0; i<4*strlen(str); i++) {
|
|
data[8*i + 4] += xofs;
|
|
data[8*i + 5] += yofs;
|
|
}
|
|
|
|
/* Draw */
|
|
#if 1
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
|
|
glColorPointer (4, GL_FLOAT, 8 * sizeof(float), data + 0);
|
|
glVertexPointer (2, GL_FLOAT, 8 * sizeof(float), data + 4);
|
|
glTexCoordPointer(2, GL_FLOAT, 8 * sizeof(float), data + 6);
|
|
|
|
glEnableClientState(GL_COLOR_ARRAY);
|
|
glEnableClientState(GL_VERTEX_ARRAY);
|
|
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
|
|
|
glDrawArrays(GL_QUADS, 0, 4*strlen(str));
|
|
|
|
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
|
|
glDisableClientState(GL_VERTEX_ARRAY);
|
|
glDisableClientState(GL_COLOR_ARRAY);
|
|
#else
|
|
glBegin( GL_QUADS );
|
|
for (i=0; i<4*strlen(str); i++) {
|
|
glColor4f(data[8*i + 0], data[8*i + 1], data[8*i + 2], data[8*i + 3]);
|
|
glTexCoord2f(data[8*i + 6], data[8*i + 7]);
|
|
glVertex2f(data[8*i + 4], data[8*i + 5]);
|
|
}
|
|
glEnd();
|
|
#endif
|
|
|
|
/* Done */
|
|
free(data);
|
|
}
|
|
|
|
void
|
|
glf_printf(const struct gl_font *glf,
|
|
float x, enum glf_align x_align,
|
|
float y, enum glf_align y_align,
|
|
const char *fmt, ...)
|
|
{
|
|
va_list va1, va2;
|
|
char *buf;
|
|
char static_buf[128];
|
|
int l;
|
|
|
|
/* Grab 2 copies of the arguments */
|
|
va_start(va1, fmt);
|
|
va_copy(va2, va1);
|
|
|
|
/* Print to buffer (try a stack, fallback to heap if needed) */
|
|
l = vsnprintf(static_buf, sizeof(static_buf), fmt, va1);
|
|
|
|
if (l >= sizeof(static_buf)) {
|
|
buf = malloc(l + 1);
|
|
if (!buf)
|
|
goto error;
|
|
|
|
vsnprintf(buf, l, fmt, va2);
|
|
} else {
|
|
buf = static_buf;
|
|
}
|
|
|
|
/* Draw it */
|
|
glf_draw_str(glf, x, x_align, y, y_align, buf);
|
|
|
|
/* Release everything */
|
|
error:
|
|
va_end(va2);
|
|
va_end(va1);
|
|
|
|
if (buf && buf != static_buf)
|
|
free(buf);
|
|
}
|
|
|
|
void
|
|
glf_begin(const struct gl_font *glf, float fg_color[3])
|
|
{
|
|
glEnable(GL_TEXTURE_2D);
|
|
glEnable(GL_BLEND);
|
|
|
|
glBindTexture(GL_TEXTURE_2D, glf->tex.id);
|
|
|
|
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
|
|
|
|
if (glf->flags & GLF_FLG_LCD) {
|
|
glColor3f(1.0f, 1.0f, 1.0f);
|
|
glBlendColor(fg_color[0], fg_color[1], fg_color[2], 0.0f);
|
|
glBlendFunc(GL_CONSTANT_COLOR, GL_ONE_MINUS_SRC_COLOR);
|
|
} else {
|
|
glColor3f(fg_color[0], fg_color[1], fg_color[2]);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
}
|
|
}
|
|
|
|
void
|
|
glf_end(void)
|
|
{
|
|
glDisable(GL_BLEND);
|
|
glDisable(GL_TEXTURE_2D);
|
|
}
|
|
|
|
/*! @} */
|