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