aboutsummaryrefslogtreecommitdiff
path: root/src/font.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/font.c')
-rw-r--r--src/font.c258
1 files changed, 258 insertions, 0 deletions
diff --git a/src/font.c b/src/font.c
new file mode 100644
index 0000000..62985e4
--- /dev/null
+++ b/src/font.c
@@ -0,0 +1,258 @@
+#include "font.h"
+#include "util.h"
+
+#define STB_TRUETYPE_IMPLEMENTATION
+#include "stb_truetype.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+const char *font_choose_path(const char *explicit_font_path) {
+ static char candidate[4096];
+
+ if (explicit_font_path && explicit_font_path[0]) {
+ return explicit_font_path;
+ }
+
+ const char *env_font = getenv("ECEX_FONT");
+ if (env_font && env_font[0]) {
+ return env_font;
+ }
+
+ const char *guix_env = getenv("GUIX_ENVIRONMENT");
+ if (guix_env && guix_env[0]) {
+ snprintf(candidate, sizeof(candidate),
+ "%s/share/fonts/truetype/DejaVuSansMono.ttf", guix_env);
+ if (ecex_file_exists(candidate)) return candidate;
+
+ snprintf(candidate, sizeof(candidate),
+ "%s/share/fonts/truetype/dejavu/DejaVuSansMono.ttf", guix_env);
+ if (ecex_file_exists(candidate)) return candidate;
+
+ snprintf(candidate, sizeof(candidate),
+ "%s/share/fonts/dejavu/DejaVuSansMono.ttf", guix_env);
+ if (ecex_file_exists(candidate)) return candidate;
+ }
+
+ const char *home = getenv("HOME");
+ if (home && home[0]) {
+ snprintf(candidate, sizeof(candidate),
+ "%s/.guix-profile/share/fonts/truetype/DejaVuSansMono.ttf", home);
+ if (ecex_file_exists(candidate)) return candidate;
+
+ snprintf(candidate, sizeof(candidate),
+ "%s/.guix-profile/share/fonts/truetype/dejavu/DejaVuSansMono.ttf", home);
+ if (ecex_file_exists(candidate)) return candidate;
+
+ snprintf(candidate, sizeof(candidate),
+ "%s/.guix-profile/share/fonts/dejavu/DejaVuSansMono.ttf", home);
+ if (ecex_file_exists(candidate)) return candidate;
+ }
+
+ if (ecex_file_exists("/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf")) {
+ return "/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf";
+ }
+
+ if (ecex_file_exists("/usr/share/fonts/TTF/DejaVuSansMono.ttf")) {
+ return "/usr/share/fonts/TTF/DejaVuSansMono.ttf";
+ }
+
+ return NULL;
+}
+
+int font_load(font_t *font, const char *path, float size_px) {
+ if (!font || !path) return -1;
+
+ memset(font, 0, sizeof(*font));
+
+ size_t ttf_size = 0;
+ char *ttf_data = ecex_read_entire_file(path, &ttf_size);
+ if (!ttf_data) {
+ fprintf(stderr, "failed to read font: %s\n", path);
+ return -1;
+ }
+
+ unsigned char *bitmap = calloc(1, ECEX_FONT_BITMAP_W * ECEX_FONT_BITMAP_H);
+ if (!bitmap) {
+ free(ttf_data);
+ return -1;
+ }
+
+ stbtt_fontinfo info;
+ int ascent = 0;
+ int descent = 0;
+ int line_gap = 0;
+
+ if (stbtt_InitFont(&info, (const unsigned char *)ttf_data, 0)) {
+ float scale = stbtt_ScaleForPixelHeight(&info, size_px);
+ stbtt_GetFontVMetrics(&info, &ascent, &descent, &line_gap);
+
+ font->ascent_px = (float)ascent * scale;
+ font->descent_px = (float)(-descent) * scale;
+ font->line_gap_px = (float)line_gap * scale;
+ }
+
+ if (font->ascent_px <= 0.0f) {
+ font->ascent_px = size_px * 0.80f;
+ }
+
+ if (font->descent_px <= 0.0f) {
+ font->descent_px = size_px * 0.20f;
+ }
+
+ if (font->line_gap_px < 0.0f) {
+ font->line_gap_px = 0.0f;
+ }
+
+ int bake_result = stbtt_BakeFontBitmap(
+ (const unsigned char *)ttf_data,
+ 0,
+ size_px,
+ bitmap,
+ ECEX_FONT_BITMAP_W,
+ ECEX_FONT_BITMAP_H,
+ ECEX_FONT_FIRST_CHAR,
+ ECEX_FONT_CHAR_COUNT,
+ font->chars
+ );
+
+ free(ttf_data);
+
+ if (bake_result <= 0) {
+ fprintf(stderr, "failed to bake font atlas from: %s\n", path);
+ free(bitmap);
+ return -1;
+ }
+
+ glGenTextures(1, &font->texture);
+ glBindTexture(GL_TEXTURE_2D, font->texture);
+
+ glTexImage2D(GL_TEXTURE_2D,
+ 0,
+ GL_ALPHA,
+ ECEX_FONT_BITMAP_W,
+ ECEX_FONT_BITMAP_H,
+ 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);
+
+ free(bitmap);
+
+ font->size_px = size_px;
+ font->line_height = font->ascent_px + font->descent_px + font->line_gap_px;
+
+ if (font->line_height < size_px * 1.15f) {
+ font->line_height = size_px * 1.15f;
+ }
+
+ fprintf(stderr, "ecex: loaded font: %s\n", path);
+ return 0;
+}
+
+void font_free(font_t *font) {
+ if (!font) return;
+
+ if (font->texture) {
+ glDeleteTextures(1, &font->texture);
+ font->texture = 0;
+ }
+}
+
+void draw_text(font_t *font, float x, float y, const char *text) {
+ if (!font || !font->texture || !text) return;
+
+ glEnable(GL_TEXTURE_2D);
+ glBindTexture(GL_TEXTURE_2D, font->texture);
+
+ glBegin(GL_QUADS);
+
+ float start_x = x;
+
+ for (const char *p = text; *p; p++) {
+ unsigned char c = (unsigned char)*p;
+
+ if (c == '\n') {
+ x = start_x;
+ y += font->line_height;
+ continue;
+ }
+
+ if (c == '\t') {
+ x += font->size_px * 4.0f;
+ continue;
+ }
+
+ if (c < ECEX_FONT_FIRST_CHAR ||
+ c >= ECEX_FONT_FIRST_CHAR + ECEX_FONT_CHAR_COUNT) {
+ c = '?';
+ }
+
+ stbtt_aligned_quad q;
+
+ stbtt_GetBakedQuad(font->chars,
+ ECEX_FONT_BITMAP_W,
+ ECEX_FONT_BITMAP_H,
+ c - ECEX_FONT_FIRST_CHAR,
+ &x,
+ &y,
+ &q,
+ 1);
+
+ glTexCoord2f(q.s0, q.t0);
+ glVertex2f(q.x0, q.y0);
+
+ glTexCoord2f(q.s1, q.t0);
+ glVertex2f(q.x1, q.y0);
+
+ glTexCoord2f(q.s1, q.t1);
+ glVertex2f(q.x1, q.y1);
+
+ glTexCoord2f(q.s0, q.t1);
+ glVertex2f(q.x0, q.y1);
+ }
+
+ glEnd();
+
+ glDisable(GL_TEXTURE_2D);
+}
+
+float text_width(font_t *font, const char *text) {
+ if (!font || !text) return 0.0f;
+
+ float x = 0.0f;
+ float y = 0.0f;
+
+ for (const char *p = text; *p; p++) {
+ unsigned char c = (unsigned char)*p;
+
+ if (c == '\n') break;
+
+ if (c == '\t') {
+ x += font->size_px * 4.0f;
+ continue;
+ }
+
+ if (c < ECEX_FONT_FIRST_CHAR ||
+ c >= ECEX_FONT_FIRST_CHAR + ECEX_FONT_CHAR_COUNT) {
+ c = '?';
+ }
+
+ stbtt_aligned_quad q;
+
+ stbtt_GetBakedQuad(font->chars,
+ ECEX_FONT_BITMAP_W,
+ ECEX_FONT_BITMAP_H,
+ c - ECEX_FONT_FIRST_CHAR,
+ &x,
+ &y,
+ &q,
+ 1);
+ }
+
+ return x;
+}