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