1#include <ctype.h>
2#include <stdint.h>
3#include <stdio.h>
4#include <stdlib.h>
5#include <string.h>
6
7#include "font.h"
8
9static int load_bdf(Fontface* f, const char* path);
10static int strcicmp(char const* a, char const* b);
11static bool is_bdf(const char* path);
12
13/* load a bdf font into `f`, -1 on fail, 0 on success */
14static int load_bdf(Fontface* f, const char* path)
15{
16 FILE* fp;
17 fp = fopen(path, "r");
18 if (!fp)
19 return -1;
20
21 char line[1024];
22 int asc = -1;
23 int dsc = -1;
24 int encoding = -1;
25 int advance = 0;
26 int bw = 0; /* bmp width */
27 int bh = 0; /* bmp height */
28 int xoff = 0;
29 int yoff = 0;
30 int seen_any_glyph = 0;
31
32 while (fgets(line, sizeof(line), fp)) {
33 if (sscanf(line, "FONT_ASCENT %d", &asc) == 1)
34 continue;
35 if (sscanf(line, "FONT_DESCENT %d", &dsc) == 1)
36 continue;
37
38 /* ignore everything until glyphs start */
39 if (strncmp(line, "STARTCHAR", 9) != 0)
40 continue;
41
42 /* reset vaules for new glyph */
43 encoding = -1;
44 advance = 0;
45 bw = 0;
46 bh = 0;
47 xoff = 0;
48 yoff = 0;
49
50 while (fgets(line, sizeof(line), fp)) {
51 if (sscanf(line, "ENCODING %d", &encoding) == 1)
52 continue;
53 if (sscanf(line, "DWIDTH %d", &advance) == 1)
54 continue;
55 if (sscanf(line, "BBX %d %d %d %d",
56 &bw, &bh, &xoff, &yoff) == 4)
57 continue;
58
59 if (strncmp(line, "BITMAP", 6) == 0) {
60 BdfGlyph* g;
61 int rowbits;
62
63 /* invalid character code
64 TODO: just skip it
65 */
66 if (encoding < 0 || encoding >= BDF_GLYPHS_MAX)
67 break;
68
69 /* empty/invalid size */
70 if (bw <= 0 || bh <= 0)
71 break;
72
73 g = &f->bdf[encoding];
74 free(g->bmp); /* prevent leak on reset */
75 memset(g, 0, sizeof(*g));
76
77 g->w = bw;
78 g->h = bh;
79 g->xoff = xoff;
80 g->yoff = yoff;
81 g->adv = advance;
82
83 g->bmp = calloc((size_t)bw * bh, 1);
84 if (!g->bmp) {
85 fclose(fp);
86 font_free(f);
87 return -1;
88 }
89
90 /* each bmp row in BDF is padded as a multiple
91 of 8 bits. this rounds width up to the next
92 multiple of 8 */
93 rowbits = (bw + 7) & ~7;
94
95 for (int y = 0; y < bh; y++) {
96 uint64_t bits;
97
98 /* missing row */
99 if (!fgets(line, sizeof(line), fp)) {
100 fclose(fp);
101 font_free(f);
102 return -1;
103 }
104
105 bits = strtoull(line, NULL, 16); /* hex to bits */
106 for (int x = 0; x < bw; x++) {
107 int bit = rowbits - 1 - x;
108
109 if ((bits >> bit) & 1)
110 g->bmp[y * bw + x] = 255; /* solid */
111 }
112 }
113
114 g->valid = 1;
115 if (advance > f->cellw)
116 f->cellw = advance;
117 seen_any_glyph = 1;
118
119 continue;
120 }
121
122 if (strncmp(line, "ENDCHAR", 7) == 0)
123 break;
124 }
125 }
126
127 fclose(fp);
128
129 if (!seen_any_glyph)
130 return -1;
131
132 /* estimate asc/dsc if they werent provided */
133 if (asc < 0 || dsc < 0) {
134 asc = 0;
135 dsc = 0;
136
137 for (int i = 0; i < BDF_GLYPHS_MAX; i++) {
138 BdfGlyph* g = &f->bdf[i];
139
140 /* unloaded glpyh */
141 if (!g->valid)
142 continue;
143
144 /* highest point above baseline */
145 if (g->h + g->yoff > asc)
146 asc = g->h + g->yoff;
147
148 /* lowest point below baseline */
149 if (-g->yoff > dsc)
150 dsc = -g->yoff;
151 }
152 }
153
154 f->asc = asc;
155 f->dsc = -dsc;
156 f->cellh = asc + dsc;
157
158 if (f->cellw <= 0 || f->cellh <= 0) {
159 font_free(f);
160 return -1;
161 }
162
163 return 0;
164}
165
166/* case insensitive string compare
167 -1=(a<b), 0=(a==b), 1=(a>b) */
168static int strcicmp(char const* a, char const* b)
169{
170 int la;
171 int lb;
172 while (*a && *b) {
173 la = tolower((unsigned char)*a);
174 lb = tolower((unsigned char)*b);
175 if (la != lb)
176 return la - lb;
177 a++;
178 b++;
179 }
180
181 return tolower((unsigned char)*a) - tolower((unsigned char)*b);
182}
183
184/* determine if font is BDF (by judging extension) */
185static bool is_bdf(const char* path)
186{
187 size_t pl = strlen(path);
188
189 if (pl < 4)
190 return false;
191
192 return strcicmp(path + pl - 4, ".bdf") == 0;
193}
194
195int font_load(Fontface* f, const char* path)
196{
197 int ret;
198
199 if (!f || !path)
200 return -1;
201
202 memset(f, 0, sizeof(*f));
203
204 if (!is_bdf(path))
205 return -1;
206
207 f->bdf = calloc(BDF_GLYPHS_MAX, sizeof(*f->bdf));
208 if (!f->bdf)
209 return -1;
210
211 ret = load_bdf(f, path);
212 if (ret < 0)
213 font_free(f);
214 return ret;
215}
216
217void font_free(Fontface* f)
218{
219 if (!f)
220 return;
221
222 if (f->bdf) {
223 for (int i = 0; i < BDF_GLYPHS_MAX; i++)
224 free(f->bdf[i].bmp);
225 free(f->bdf);
226 }
227
228 memset(f, 0, sizeof(*f));
229}
230