commit 0c80f70

uint  ·  2026-06-11 22:34:02 +0000 UTC
parent a3d4e82
Rip BDF loading from cterm, add font loading, font drawing

[cterm](https://github.com/uint23/cterm)
2 files changed,  +266, -0
+226, -0
  1@@ -0,0 +1,226 @@
  2+#include <stdio.h>
  3+#include <stdlib.h>
  4+#include <string.h>
  5+
  6+#include "maus.h"
  7+#include "maus_font.h"
  8+
  9+void maus_draw_text(Maus* mw, MausFont* font, int32_t x, int32_t y,
 10+                    const char* text, MausColor col)
 11+{
 12+	if (!mw || !mw->fb || !font || !text)
 13+		return;
 14+
 15+	uint32_t col_up = MAUS_UNPACK_COL(col);
 16+	int32_t cx = x; /* current x */
 17+	int32_t cy = y; /* current y */
 18+
 19+	for (size_t i = 0; text[i] != '\0'; i++) {
 20+		char c = text[i];
 21+
 22+		if (c == '\n') {
 23+			cx = x;
 24+			cy += font->cellh;
 25+			continue;
 26+		}
 27+
 28+		MausGlyph* g = &font->glyphs[(uint8_t)c];
 29+		if (!g->valid) {
 30+			/* still advance cx if character invalid */
 31+			cx += (font->cellw > 0 ? font->cellw : 8);
 32+			continue;
 33+		}
 34+
 35+		/* origins to start drawing glpyh from */
 36+		int32_t gox = cx + g->xoff;
 37+		int32_t goy = cy + font->asc - g->h - g->yoff;
 38+
 39+		/* "blit" font to framebuffer */
 40+		for (uint16_t gy = 0; gy < g->h; gy++) {
 41+			for (uint16_t gx = 0; gx < g->w; gx++) {
 42+				bool solid = g->bmp[gy * g->w + gx] > 0;
 43+				if (solid) {
 44+					int32_t dstx = gox + gx;
 45+					int32_t dsty = goy + gy;
 46+					if (dstx >= 0 && dstx < (int)mw->width &&
 47+					    dsty >= 0 && dsty < (int)mw->height) {
 48+						MAUS_PIXEL_AT(mw, dstx, dsty) = col_up;
 49+					}
 50+				}
 51+			}
 52+		}
 53+
 54+		cx += g->adv;
 55+	}
 56+}
 57+
 58+MausFont* maus_font_load(const char* path)
 59+{
 60+	FILE* fp = fopen(path, "r");
 61+	if (!fp) {
 62+		maus_log(stderr, "failed to load font \"%s\"", path);
 63+		return NULL;
 64+	}
 65+
 66+	MausFont* font = calloc(1, sizeof(MausFont));
 67+	if (!font) {
 68+		maus_log(stderr, "failed to calloc font");
 69+		fclose(fp);
 70+		return NULL;
 71+	}
 72+
 73+	char line[1024];
 74+	int asc = -1;
 75+	int dsc = -1;
 76+	int encoding = -1;
 77+	int advance = 0;
 78+	int bw = 0; /* bmp width */
 79+	int bh = 0; /* bmp height */
 80+	int xoff = 0;
 81+	int yoff = 0;
 82+	bool seen_any_glyph = 0;
 83+
 84+	while (fgets(line, sizeof(line), fp)) {
 85+		if (sscanf(line, "FONT_ASCENT %d", &asc) == 1)
 86+			continue;
 87+		if (sscanf(line, "FONT_DESCENT %d", &dsc) == 1)
 88+			continue;
 89+
 90+		/* ignore everything until glyphs start */
 91+		if (strncmp(line, "STARTCHAR", 9) != 0)
 92+			continue;
 93+
 94+		/* reset vaules for new glyph */
 95+		encoding = -1;
 96+		advance = 0;
 97+		bw = 0;
 98+		bh = 0;
 99+		xoff = 0;
100+		yoff = 0;
101+
102+		while (fgets(line, sizeof(line), fp)) {
103+			if (sscanf(line, "ENCODING %d", &encoding) == 1)
104+				continue;
105+			if (sscanf(line, "DWIDTH %d", &advance) == 1)
106+				continue;
107+			if (sscanf(line, "BBX %d %d %d %d", &bw, &bh, &xoff, &yoff) == 4)
108+				continue;
109+
110+			if (strncmp(line, "BITMAP", 6) == 0) {
111+				int rowbits;
112+
113+				/* invalid character code
114+				   TODO: just skip it */
115+				if (encoding < 0 || encoding >= MAUS_BDF_GLYPHS_MAX)
116+					break;
117+
118+				/* empty/invalid size */
119+				if (bw <= 0 || bh <= 0)
120+					break;
121+
122+				MausGlyph* g = &font->glyphs[encoding];
123+				free(g->bmp); /* prevent leak on hot reload */
124+				memset(g, 0, sizeof(*g));
125+
126+				g->w = bw;
127+				g->h = bh;
128+				g->xoff = xoff;
129+				g->yoff = yoff;
130+				g->adv = advance;
131+
132+				g->bmp = calloc((size_t)bw * bh, 1);
133+				if (!g->bmp) {
134+					fclose(fp);
135+					maus_font_free(font);
136+					return NULL;
137+				}
138+
139+				/* each bmp row in BDF is padded as a multiple
140+				   of 8 bits. this rounds width up to the next
141+				   multiple of 8 */
142+				rowbits = (bw + 7) & ~7;
143+
144+				for (int y = 0; y < bh; y++) {
145+					uint64_t bits;
146+
147+					/* missing row */
148+					if (!fgets(line, sizeof(line), fp)) {
149+						fclose(fp);
150+						maus_font_free(font);
151+						return NULL;
152+					}
153+
154+					bits = strtoull(line, NULL, 16); /* hex to bits */
155+					for (int x = 0; x < bw; x++) {
156+						int bit = rowbits - 1 - x;
157+
158+						if ((bits >> bit) & 1)
159+							g->bmp[y * bw + x] = 255; /* solid */
160+					}
161+				}
162+
163+				g->valid = true;
164+				if (advance > font->cellw)
165+					font->cellw = advance;
166+				seen_any_glyph = true;
167+
168+				continue;
169+			}
170+
171+			if (strncmp(line, "ENDCHAR", 7) == 0)
172+				break;
173+		}
174+	}
175+
176+	fclose(fp);
177+
178+	if (!seen_any_glyph) {
179+		free(font);
180+		return NULL;
181+	}
182+
183+	/* estimate asc/dsc if they werent provided */
184+	if (asc < 0 || dsc < 0) {
185+		asc = 0;
186+		dsc = 0;
187+
188+		for (int i = 0; i < MAUS_BDF_GLYPHS_MAX; i++) {
189+			MausGlyph* g = &font->glyphs[i];
190+
191+			/* unloaded glpyh */
192+			if (!g->valid)
193+				continue;
194+
195+			/* highest point above baseline */
196+			if (g->h + g->yoff > asc)
197+				asc = g->h + g->yoff;
198+
199+			/* lowest point below baseline */
200+			if (-g->yoff > dsc)
201+				dsc = -g->yoff;
202+		}
203+	}
204+
205+	font->asc = asc;
206+	font->dsc = -dsc;
207+	font->cellh = asc + dsc;
208+
209+	if (font->cellw <= 0 || font->cellh <= 0) {
210+		maus_font_free(font);
211+		return NULL;
212+	}
213+
214+	return font;
215+}
216+
217+void maus_font_free(MausFont* font)
218+{
219+	if (!font)
220+		return;
221+
222+	for (int i = 0; i < MAUS_BDF_GLYPHS_MAX; i++)
223+		free(font->glyphs[i].bmp);
224+
225+	free(font);
226+}
227+
+40, -0
 1@@ -0,0 +1,40 @@
 2+#ifndef MAUS_FONT_H
 3+#define MAUS_FONT_H
 4+
 5+#include <stdint.h>
 6+#include <stdbool.h>
 7+
 8+#include "maus.h"
 9+
10+#define MAUS_BDF_GLYPHS_MAX 256
11+
12+typedef struct {
13+    uint8_t*    bmp;
14+    uint16_t    w;
15+    uint16_t    h;
16+    int16_t     xoff;
17+    int16_t     yoff;
18+    uint16_t    adv;
19+    bool        valid;
20+} MausGlyph;
21+
22+typedef struct {
23+    MausGlyph glyphs[MAUS_BDF_GLYPHS_MAX];
24+    int32_t     asc;
25+    int32_t     dsc;
26+    int32_t     cellh;
27+    int32_t     cellw;
28+} MausFont;
29+
30+/* Loops through text passing parameters to glyph drawer */
31+void maus_draw_text(Maus* mw, MausFont* font, int32_t x, int32_t y,
32+                    const char* text, MausColor col);
33+
34+/* load a bdf font into MausFont. returns NULL on fail */
35+MausFont* maus_font_load(const char* path);
36+
37+/* release all allocations made with a MausFont */
38+void maus_font_free(MausFont* font);
39+
40+#endif /* MAUS_FONT_H */
41+