summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile6
-rw-r--r--base.h2
-rw-r--r--lib/stb_truetype.h (renamed from stb_truetype.h)0
-rw-r--r--main.c30
-rw-r--r--text.c230
-rw-r--r--text.h24
-rw-r--r--valgrind_suppresions.txt70
7 files changed, 307 insertions, 55 deletions
diff --git a/Makefile b/Makefile
index e10bdc9..0b720c0 100644
--- a/Makefile
+++ b/Makefile
@@ -4,5 +4,7 @@ LIBS=-lSDL2 -lGL -ldl -lm
DEBUG_CFLAGS=$(ALL_CFLAGS) -DDEBUG -O0 -g
ted: *.[ch] text.o
$(CC) main.c text.o -o $@ $(DEBUG_CFLAGS) $(LIBS)
-%.o: %.c
- $(CC) $< -c -o $@ $(DEBUG_CFLAGS)
+text.o: text.c text.h base.h lib/stb_truetype.h
+ $(CC) text.c -c -o $@ $(DEBUG_CFLAGS)
+clean:
+ rm -f ted *.o
diff --git a/base.h b/base.h
index 8327fdb..62c7117 100644
--- a/base.h
+++ b/base.h
@@ -40,7 +40,7 @@ typedef unsigned long ulong;
#if DEBUG
#if __unix__
-#define debug_println printf
+#define debug_println(...) printf(__VA_ARGS__), printf("\n")
#else // __unix__
static void debug_println(char const *fmt, ...) {
char buf[256];
diff --git a/stb_truetype.h b/lib/stb_truetype.h
index 62595a1..62595a1 100644
--- a/stb_truetype.h
+++ b/lib/stb_truetype.h
diff --git a/main.c b/main.c
index 2353d08..f2aa450 100644
--- a/main.c
+++ b/main.c
@@ -3,6 +3,7 @@ no_warn_start
#include <SDL2/SDL.h>
no_warn_end
#include <GL/gl.h>
+#include <locale.h>
#include "text.h"
static void die(char const *fmt, ...) {
@@ -23,7 +24,8 @@ static void die(char const *fmt, ...) {
int main(void) {
- if (SDL_Init(SDL_INIT_VIDEO) < 0)
+ setlocale(LC_ALL, ""); // allow unicode
+ if (SDL_Init(SDL_INIT_VIDEO|SDL_INIT_TIMER) < 0)
die("%s", SDL_GetError());
SDL_Window *window = SDL_CreateWindow("ted", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 1280, 720, SDL_WINDOW_SHOWN|SDL_WINDOW_OPENGL);
@@ -39,7 +41,7 @@ int main(void) {
SDL_GL_SetSwapInterval(1); // vsync
- Font *font = text_font_load("assets/font.ttf", 12);
+ Font *font = text_font_load("assets/font.ttf", 24);
if (!font) {
die("Couldn't load font: %s", text_get_err());
}
@@ -55,11 +57,33 @@ int main(void) {
}
}
- glClearColor(0,0,0,1);
+ int window_width = 0, window_height = 0;
+ SDL_GetWindowSize(window, &window_width, &window_height);
+
+ // set up GL
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ glViewport(0, 0, window_width, window_height);
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+ glOrtho(0, window_width, 0, window_height, -1, +1);
+ glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
+ glColor3f(1,1,1);
+ text_render(font, u8"hőello☐øλㄔ☺☹", 50, 50);
+ if (text_has_err()) {
+ printf("Text error: %s\n", text_get_err());
+ break;
+ }
+
SDL_GL_SwapWindow(window);
}
+ SDL_GL_DeleteContext(glctx);
+ SDL_DestroyWindow(window);
+ SDL_Quit();
+ text_font_free(font);
+
return 0;
}
diff --git a/text.c b/text.c
index 7007ad6..5bc0bac 100644
--- a/text.c
+++ b/text.c
@@ -3,25 +3,34 @@
#define STB_TRUETYPE_IMPLEMENTATION
#define STBTT_STATIC
no_warn_start
-#include "stb_truetype.h"
+#include "lib/stb_truetype.h"
no_warn_end
#include <stdarg.h>
#include <stdlib.h>
#include <GL/gl.h>
+#define UNICODE_CODE_POINTS 0x110000 // number of Unicode code points
+// We split up code points into a bunch of pages, so we don't have to load all of the font at
+// once into one texture.
+#define CHAR_PAGE_SIZE 2048
+#define CHAR_PAGE_COUNT UNICODE_CODE_POINTS / CHAR_PAGE_SIZE
+
struct Font {
float char_height;
- GLuint texture;
- u32 nchars;
- stbtt_bakedchar chars[];
+ GLuint textures[CHAR_PAGE_COUNT];
+ int tex_widths[CHAR_PAGE_COUNT], tex_heights[CHAR_PAGE_COUNT];
+ stbtt_bakedchar *char_pages[CHAR_PAGE_COUNT]; // character pages. NULL if the page hasn't been loaded yet.
+ // TTF data (i.e. the contents of the TTF file)
+ u8 *ttf_data;
+ int curr_page;
};
static char text_err[200];
-static void text_clear_err(void) {
+void text_clear_err(void) {
text_err[0] = '\0';
}
-static bool text_has_err(void) {
+bool text_has_err(void) {
return text_err[0] != '\0';
}
@@ -38,9 +47,57 @@ static void text_set_err(char const *fmt, ...) {
}
}
+static void text_load_char_page(Font *font, int page) {
+ if (font->char_pages[page]) {
+ // already loaded
+ return;
+ }
+ font->char_pages[page] = calloc(CHAR_PAGE_SIZE, sizeof *font->char_pages[page]);
+ for (int bitmap_width = 128, bitmap_height = 128; bitmap_width <= 4096; bitmap_width *= 2, bitmap_height *= 2) {
+ u8 *bitmap = calloc((size_t)bitmap_width, (size_t)bitmap_height);
+ if (bitmap) {
+ int err = stbtt_BakeFontBitmap(font->ttf_data, 0, font->char_height, bitmap,
+ bitmap_width, bitmap_height, page * CHAR_PAGE_SIZE, CHAR_PAGE_SIZE, font->char_pages[page]);
+ if (err > 0) {
+ // font converted to bitmap successfully.
+ GLuint texture = 0;
+ glGenTextures(1, &texture);
+ glBindTexture(GL_TEXTURE_2D, texture);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, bitmap_width, bitmap_height, 0, GL_ALPHA, GL_UNSIGNED_BYTE, bitmap);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ #if DEBUG
+ debug_println("Loaded font page %d with %dx%d bitmap as texture %u.", page, bitmap_width, bitmap_height, texture);
+ #endif
+ font->textures[page] = texture;
+ font->tex_widths[page] = bitmap_width;
+ font->tex_heights[page] = bitmap_height;
+ GLenum glerr = glGetError();
+ if (glerr) {
+ text_set_err("Couldn't create texture for font (GL error %u).", glerr);
+ if (texture) glDeleteTextures(1, &texture);
+ break;
+ }
+ }
+ } else {
+ text_set_err("Not enough memory for font bitmap.");
+ }
+ free(bitmap);
+ if (font->textures[page]) { // if font loaded successfully
+ break;
+ }
+ }
+ if (!font->textures[page] && !text_has_err()) {
+ text_set_err("Couldn't convert font to bitmap.");
+ }
+ if (text_has_err()) {
+ free(font->char_pages[page]);
+ font->char_pages[page] = NULL;
+ }
+}
+
Font *text_font_load(char const *ttf_filename, float font_size) {
Font *font = NULL;
- u32 nchars = 128;
FILE *ttf_file = fopen(ttf_filename, "rb");
text_clear_err();
@@ -48,54 +105,25 @@ Font *text_font_load(char const *ttf_filename, float font_size) {
if (ttf_file) {
fseek(ttf_file, 0, SEEK_END);
u32 file_size = (u32)ftell(ttf_file);
+ fseek(ttf_file, 0, SEEK_SET);
if (file_size < (50UL<<20)) { // fonts aren't usually bigger than 50 MB
u8 *file_data = calloc(1, file_size);
- font = calloc(1, sizeof *font + nchars * sizeof *font->chars);
+ font = calloc(1, sizeof *font);
if (file_data && font) {
- if (fread(file_data, 1, file_size, ttf_file) == file_size) {
- font->nchars = nchars;
+ size_t bytes_read = fread(file_data, 1, file_size, ttf_file);
+ if (bytes_read == file_size) {
font->char_height = font_size;
-
- for (int bitmap_width = 256, bitmap_height = 256; bitmap_width <= 4096; bitmap_width *= 2, bitmap_height *= 2) {
- u8 *bitmap = calloc((size_t)bitmap_width, (size_t)bitmap_height);
- if (bitmap) {
- int err = stbtt_BakeFontBitmap(file_data, 0, font->char_height, bitmap,
- bitmap_width, bitmap_height, 0, (int)font->nchars, font->chars);
- if (err > 0) {
- // font converted to bitmap successfully.
- GLuint texture = 0;
- glGenTextures(1, &texture);
- glBindTexture(GL_TEXTURE_2D, texture);
- glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, bitmap_width, bitmap_height, 0, GL_ALPHA, GL_UNSIGNED_BYTE, bitmap);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
- #if DEBUG
- debug_println("Loaded font %s with %dx%d bitmap.", ttf_filename, bitmap_width, bitmap_height);
- #endif
- font->texture = texture;
- if (glGetError()) {
- text_set_err("Couldn't create texture for font.");
- }
- }
- } else {
- text_set_err("Not enough memory for font bitmap.");
- }
- free(bitmap);
- if (font->texture) { // if font loaded successfully
- break;
- }
- }
- if (!font->texture && !text_has_err()) {
- text_set_err("Couldn't convert font to bitmap.");
- }
+ font->ttf_data = file_data;
+ text_load_char_page(font, 0); // load page with Latin text, etc.
+ font->curr_page = -1;
} else {
- text_set_err("Couldn't read font file.", ttf_filename);
+ text_set_err("Couldn't read font file.");
}
} else {
text_set_err("Not enough memory for font.");
}
- free(file_data);
if (text_has_err()) {
+ free(file_data);
free(font);
font = NULL;
}
@@ -109,11 +137,117 @@ Font *text_font_load(char const *ttf_filename, float font_size) {
return font;
}
-#if 0
+typedef struct {
+ float x, y;
+} TextRenderState;
+
+static void text_render_with_page(Font *font, int page) {
+ if (font->curr_page != page) {
+ if (font->curr_page != -1) {
+ // we were rendering chars from another page.
+ glEnd(); // stop doing that
+ }
+ text_load_char_page(font, page); // load the page if necessary
+ glEnable(GL_TEXTURE_2D);
+ glBindTexture(GL_TEXTURE_2D, font->textures[page]);
+ glBegin(GL_QUADS);
+ font->curr_page = page;
+ }
+}
+
+void text_chars_begin(Font *font) {
+ text_render_with_page(font, 0); // start by loading Latin text
+}
+
+void text_chars_end(Font *font) {
+ glEnd();
+ glDisable(GL_TEXTURE_2D);
+ font->curr_page = -1;
+}
+
+static void text_render_char_internal(Font *font, char32_t c, TextRenderState *state) {
+ if (c >= 0x30000 && c < 0xE0000){
+ // these Unicode code points are currently unassigned. replace them with ☐.
+ // (specifically, we don't want to use extra memory for pages which
+ // won't even have any valid characters in them)
+ c = 0x2610;
+ }
+ if (c >= UNICODE_CODE_POINTS) c = 0x2610; // code points this big should never appear in valid Unicode
+ uint page = c / CHAR_PAGE_SIZE;
+ uint index = c % CHAR_PAGE_SIZE;
+ text_render_with_page(font, (int)page);
+ stbtt_bakedchar *char_data = font->char_pages[page];
+ if (char_data) { // if page was successfully loaded
+ stbtt_aligned_quad q = {0};
+ // because stb_truetype uses down is positive, we need to negate the y
+ // coordinate, pass it into the function, then negate it back.
+ state->y = -state->y;
+ stbtt_GetBakedQuad(char_data, font->tex_widths[page], font->tex_heights[page],
+ (int)index, &state->x, &state->y, &q, 1);
+ state->y = -state->y;
+ glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,-q.y1);
+ glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,-q.y1);
+ glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,-q.y0);
+ glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,-q.y0);
+ }
+}
+
+void text_render_char(Font *font, char32_t c, float *x, float *y) {
+ TextRenderState state = {*x, *y};
+ text_render_char_internal(font, c, &state);
+ *x = state.x;
+ *y = state.y;
+}
+
+static void text_render_internal(Font *font, char const *text, float *x, float *y) {
+ mbstate_t mbstate = {0};
+ TextRenderState render_state = {*x, *y};
+ text_chars_begin(font);
+ char32_t c = 0;
+ char const *end = text + strlen(text);
+ while (text != end) {
+ size_t ret = mbrtoc32(&c, text, (size_t)(end - text), &mbstate);
+ if (ret == 0) break;
+ if (ret == (size_t)(-2)) { // incomplete multi-byte character
+ text_render_char_internal(font, '?', &render_state);
+ text = end; // done reading text
+ } else if (ret == (size_t)(-1)) {
+ // invalid UTF-8; skip this byte
+ text_render_char_internal(font, '?', &render_state);
+ ++text;
+ } else {
+ if (ret != (size_t)(-3))
+ text += ret; // character consists of `ret` bytes
+ switch (c) {
+ default:
+ text_render_char_internal(font, (char32_t)c, &render_state);
+ break;
+ }
+ }
+ }
+ text_chars_end(font);
+ *x = render_state.x;
+ *y = render_state.y;
+}
+
void text_render(Font *font, char const *text, float x, float y) {
-
+ text_render_internal(font, text, &x, &y);
}
void text_get_size(Font *font, char const *text, float *width, float *height) {
+ float x = 0, y = 0;
+ text_render_internal(font, text, &x, &y);
+ if (width) *width = x;
+ if (height) *height = y + font->char_height * (2/3.0f);
+}
+
+void text_font_free(Font *font) {
+ free(font->ttf_data);
+ stbtt_bakedchar **char_pages = font->char_pages;
+ for (int i = 0; i < CHAR_PAGE_COUNT; ++i) {
+ if (char_pages[i]) {
+ free(char_pages[i]);
+ }
+ }
+ free(font);
}
-#endif
diff --git a/text.h b/text.h
index c855ec9..ad18ef3 100644
--- a/text.h
+++ b/text.h
@@ -1,11 +1,33 @@
#ifndef TEXT_H_
#define TEXT_H_
+#include <uchar.h>
+
+// A text-rendering interface.
+// You can either use the simple API (text_render)
+// or the character-by-character API (text_chars_begin, text_chars_end, text_render_char)
+
+
typedef struct Font Font;
-char const *text_get_err(void);
+extern bool text_has_err(void);
+// Get the current error. Errors will NOT be overwritten with newer errors.
+extern char const *text_get_err(void);
+// Clear the current error.
+extern void text_clear_err(void);
+// Load a TTF font found in ttf_filename with the given font size (character pixel height)
extern Font *text_font_load(char const *ttf_filename, float font_size);
+// Render some UTF-8 text to the screen (simple interface).
extern void text_render(Font *font, char const *text, float x, float y);
+// Get the dimensions of some text.
extern void text_get_size(Font *font, char const *text, float *width, float *height);
+// Begin writing characters.
+extern void text_chars_begin(Font *font);
+// Finish writing characters.
+extern void text_chars_end(Font *font);
+// Render a single character.
+extern void text_render_char(Font *font, char32_t c, float *x, float *y);
+// Free memory used by font.
+extern void text_font_free(Font *font);
#endif
diff --git a/valgrind_suppresions.txt b/valgrind_suppresions.txt
new file mode 100644
index 0000000..21b3680
--- /dev/null
+++ b/valgrind_suppresions.txt
@@ -0,0 +1,70 @@
+{
+ Ignore dlopen .
+ Memcheck:Leak
+ ...
+ fun:_dl_open
+ ...
+}
+{
+ Ignore dlopen .
+ Memcheck:Leak
+ ...
+ fun:_dlerror_run
+ ...
+}
+{
+ Ignore dlopen .
+ Memcheck:Leak
+ ...
+ fun:_dl_init
+ ...
+}
+{
+ Ignore.
+ Memcheck:Leak
+ ...
+ obj:*/radeonsi_dri.so
+ ...
+}
+{
+ Ignore.
+ Memcheck:Leak
+ ...
+ fun:_XlcCreateLC
+ ...
+}
+{
+ Ignore.
+ Memcheck:Leak
+ ...
+ obj:*LLVM*
+ ...
+}
+{
+ Ignore.
+ Memcheck:Leak
+ ...
+ fun:dbus_connection_send_with_reply_and_block
+ ...
+}
+{
+ Ignore.
+ Memcheck:Leak
+ ...
+ fun:__glDispatchMakeCurrent
+ ...
+}
+{
+ Ignore.
+ Memcheck:Leak
+ ...
+ obj:*libdbus-1.so*
+ ...
+}
+{
+ Ignore.
+ Memcheck:Leak
+ ...
+ fun:XSetLocaleModifiers
+ ...
+}