1 /*
2 * Single-header simple raster drawing functions
3 */
4 #include <stdbool.h>
5 #include <stdint.h>
6 #include <inttypes.h>
7 #include <stdlib.h>
8 #include <stdio.h>
9 #include <arcan_shmif.h>
10
11 /*
12 * builtin- fonts to load on init, see tui_draw_init()
13 */
14 #include "terminus_small.h"
15 #include "terminus_medium.h"
16 #include "terminus_large.h"
17 #include "pixelfont.h"
18 #include "draw.h"
19
20 #include "utf8.c"
21 #include "uthash.h"
22
23 struct glyph_ent {
24 uint32_t codepoint;
25 uint8_t* data;
26 UT_hash_handle hh;
27 };
28
29 struct bitmap_font {
30 uint8_t* fontdata;
31 size_t chsz, w, h;
32 size_t n_glyphs;
33 struct glyph_ent glyphs[0];
34 };
35
psf2_decode_header(const uint8_t * const buf,size_t buf_sz,size_t * glyph_count,size_t * glyph_bytes,size_t * w,size_t * h,size_t * ofs)36 static bool psf2_decode_header(
37 const uint8_t* const buf, size_t buf_sz,
38 size_t* glyph_count, size_t* glyph_bytes, size_t* w, size_t* h, size_t* ofs)
39 {
40 if (buf_sz < 32)
41 return false;
42
43 uint32_t u32[8];
44 memcpy(u32, buf, 32);
45
46 if (buf[0] != 0x72 || buf[1] != 0xb5 || buf[2] != 0x4a || buf[3] != 0x86)
47 return false;
48
49 if (glyph_count)
50 *glyph_count = u32[4];
51
52 if (glyph_bytes)
53 *glyph_bytes = u32[5];
54
55 if (ofs)
56 *ofs = u32[2];
57
58 if (w)
59 *w = u32[7];
60
61 if (h)
62 *h = u32[6];
63
64 return true;
65 }
66
67 /*
68 * support a subset of PSF(v2), no ranges in the unicode- table
69 */
open_psf2(const uint8_t * const buf,size_t buf_sz,struct glyph_ent ** ht)70 static struct bitmap_font* open_psf2(
71 const uint8_t* const buf, size_t buf_sz, struct glyph_ent** ht)
72 {
73 size_t glyph_count, glyph_bytes, w, h, ofs;
74
75 if (!psf2_decode_header(buf, buf_sz, &glyph_count, &glyph_bytes, &w,&h,&ofs))
76 return NULL;
77
78 size_t pos = ofs;
79 size_t glyphbuf_sz = glyph_count * glyph_bytes;
80 size_t unicodecount = buf_sz - pos - glyphbuf_sz;
81
82 /* we overallocate as we need to support aliases and it's not many bytes */
83 struct bitmap_font* res = malloc(
84 sizeof(struct bitmap_font) +
85 sizeof(struct glyph_ent) * unicodecount +
86 glyphbuf_sz
87 );
88
89 /* read in the raw font-data */
90 res->chsz = glyph_bytes;
91 res->w = w;
92 res->h = h;
93 res->n_glyphs = 0;
94
95 res->fontdata = (uint8_t*) &res->glyphs[unicodecount];
96 memcpy(res->fontdata, &buf[pos], glyphbuf_sz);
97 pos = ofs + glyphbuf_sz;
98
99 /* the rest is UTF-8 sequences, build glyph_ent for these */
100 uint32_t state = 0;
101 uint32_t codepoint = 0;
102 size_t ind = 0;
103
104 /* this format is:
105 * [implicit, glyph index], UTF-8,
106 * [0xfe or 0xff] fe = start sequence, ff = step index
107 */
108 while (pos < buf_sz && ind < glyph_count){
109 if (buf[pos] == 0xff){
110 ind++;
111 }
112 else if(buf[pos] == 0xfe){
113 fprintf(stderr, "open_psf2() unicode- ranges not supported\n");
114 }
115 else if (utf8_decode(&state, &codepoint, buf[pos]) == UTF8_REJECT){
116 fprintf(stderr, "open_psf2(), invalid UTF-8 sequence found\n");
117 return res;
118 }
119 else if (state == UTF8_ACCEPT){
120 res->glyphs[res->n_glyphs].hh = (struct UT_hash_handle){};
121 res->glyphs[res->n_glyphs].codepoint = codepoint;
122 res->glyphs[res->n_glyphs].data = &res->fontdata[glyph_bytes*ind];
123
124 struct glyph_ent* repl;
125 HASH_REPLACE_INT(*ht, codepoint, &res->glyphs[res->n_glyphs], repl);
126 res->n_glyphs++;
127 state = 0;
128 codepoint = 0;
129 }
130 pos++;
131 }
132
133 return res;
134 }
135
136 /*
137 * Fixed size font/glyph container
138 */
139 #define MAX_BITMAP_FONTS 64
140 struct font_entry {
141 size_t sz;
142 struct bitmap_font* font;
143 bool shared_ht;
144 struct glyph_ent* ht;
145 };
146
147 struct tui_pixelfont {
148 size_t n_fonts;
149 struct font_entry* active_font;
150 size_t active_font_px;
151 struct font_entry fonts[];
152 };
153
tui_pixelfont_valid(uint8_t * buf,size_t buf_sz)154 bool tui_pixelfont_valid(uint8_t* buf, size_t buf_sz)
155 {
156 return psf2_decode_header(buf, buf_sz, NULL, NULL, NULL, NULL, NULL);
157 }
158
159 /*
160 * if there's a match for this size slot, then we share the hash lookup and the
161 * new font act as an override for missing glyphs. This means that it is not
162 * safe to just free a font (not that we do that anyhow).
163 */
tui_pixelfont_load(struct tui_pixelfont * ctx,uint8_t * buf,size_t buf_sz,size_t px_sz,bool merge)164 bool tui_pixelfont_load(struct tui_pixelfont* ctx,
165 uint8_t* buf, size_t buf_sz, size_t px_sz, bool merge)
166 {
167 struct glyph_ent** ht = NULL;
168
169 /* don't waste time with a font we can't decode */
170 if (!psf2_decode_header(buf, buf_sz, NULL, NULL, NULL, NULL, NULL))
171 return false;
172
173 /* if not merge, delete all for this size slot */
174 if (!merge){
175 for (size_t i = 0; i < ctx->n_fonts; i++){
176 if (ctx->fonts[i].font && ctx->fonts[i].sz == px_sz){
177 if (!ctx->fonts[i].shared_ht)
178 HASH_CLEAR(hh, ctx->fonts[i].ht);
179 free(ctx->fonts[i].font);
180 ctx->fonts[i].font = NULL;
181 ctx->fonts[i].sz = 0;
182 ctx->fonts[i].shared_ht = false;
183 }
184 }
185 }
186
187 /* find slot for font */
188 struct font_entry* dst = NULL;
189 for (size_t i = 0; i < ctx->n_fonts; i++){
190 if (!ctx->fonts[i].font){
191 dst = &ctx->fonts[i];
192 break;
193 }
194 }
195 if (!dst)
196 return false;
197
198 /* find out if there's a hash-table to re-use */
199 if (merge){
200 for (size_t i = 0; i < ctx->n_fonts; i++){
201 if (ctx->fonts[i].font && ctx->fonts[i].sz == px_sz){
202 dst->shared_ht = true;
203 dst->ht = ctx->fonts[i].ht;
204 break;
205 }
206 }
207 }
208
209 /* load it */
210 dst->font = open_psf2(buf, buf_sz, &dst->ht);
211 if (!dst->font){
212 dst->shared_ht = false;
213 dst->ht = NULL;
214 return false;
215 }
216 dst->sz = px_sz;
217
218 return true;
219 }
220
drop_font_context(struct tui_pixelfont * ctx)221 static void drop_font_context (struct tui_pixelfont* ctx)
222 {
223 if (!ctx)
224 return;
225
226 for (size_t i = 0; i < ctx->n_fonts; i++){
227 if (!ctx->fonts[i].font)
228 continue;
229
230 /* some font slots share hash table with others, don't free those,
231 * the real table slot won't be marked as shared */
232 if (!ctx->fonts[i].shared_ht)
233 HASH_CLEAR(hh, ctx->fonts[i].ht);
234
235 free(ctx->fonts[i].font);
236 ctx->fonts[i].font = NULL;
237 ctx->fonts[i].sz = 0;
238 ctx->fonts[i].shared_ht = false;
239 }
240
241 free(ctx);
242 }
243
244 /*
245 * pick the nearest font for the requested size slot, set the sizes
246 * used in *w and *h.
247 */
tui_pixelfont_setsz(struct tui_pixelfont * ctx,size_t px,size_t * w,size_t * h)248 void tui_pixelfont_setsz(struct tui_pixelfont* ctx, size_t px, size_t* w, size_t* h)
249 {
250 int dist = abs((int)px - (int)ctx->active_font->sz);
251
252 /* only search if we aren't at that size already */
253 if (ctx->active_font_px != px){
254 for (size_t i = 0; i < ctx->n_fonts; i++){
255 int nd = abs((int)px-(int)ctx->fonts[i].sz);
256 if (ctx->fonts[i].font && nd < dist){
257 dist = nd;
258 ctx->active_font = &ctx->fonts[i];
259 }
260 }
261 }
262
263 *w = ctx->active_font->font->w;
264 *h = ctx->active_font->font->h;
265 ctx->active_font_px = px;
266 }
267
tui_pixelfont_close(struct tui_pixelfont * ctx)268 void tui_pixelfont_close(struct tui_pixelfont* ctx)
269 {
270 for (size_t i = 0; i < ctx->n_fonts; i++){
271 if (!ctx->fonts[i].font)
272 continue;
273
274 if (!ctx->fonts[i].shared_ht)
275 HASH_CLEAR(hh, ctx->fonts[i].ht);
276
277 free(ctx->fonts[i].font);ctx->fonts[i].font = NULL;
278 ctx->fonts[i].sz = 0;
279 ctx->fonts[i].shared_ht = false;
280 }
281 free(ctx);
282 }
283
tui_pixelfont_open(size_t lim)284 struct tui_pixelfont* tui_pixelfont_open(size_t lim)
285 {
286 if (lim < 3)
287 return NULL;
288
289 size_t ctx_sz = sizeof(struct font_entry)*lim + sizeof(struct tui_pixelfont);
290 struct tui_pixelfont* res = malloc(ctx_sz);
291 if (!res)
292 return NULL;
293 memset(res, '\0', ctx_sz);
294 res->n_fonts = lim;
295
296 bool fontstatus = false;
297 fontstatus |= tui_pixelfont_load(res,
298 Lat15_Terminus32x16_psf, Lat15_Terminus32x16_psf_len, 32, false);
299
300 fontstatus |= tui_pixelfont_load(res,
301 Lat15_Terminus22x11_psf, Lat15_Terminus22x11_psf_len, 22, false);
302
303 fontstatus |= tui_pixelfont_load(res,
304 Lat15_Terminus12x6_psf, Lat15_Terminus12x6_psf_len, 12, false);
305
306 if (!fontstatus){
307 fprintf(stderr, "tui_draw_init(), builtin- fonts broken");
308 free(res);
309 return NULL;
310 }
311
312 res->active_font = &res->fonts[0];
313 res->active_font->sz = res->active_font->font->h;
314
315 return res;
316 }
317
tui_pixelfont_hascp(struct tui_pixelfont * ctx,uint32_t cp)318 bool tui_pixelfont_hascp(struct tui_pixelfont* ctx, uint32_t cp)
319 {
320 if (!ctx->active_font)
321 return false;
322
323 struct glyph_ent* gent;
324 HASH_FIND_INT(ctx->active_font->ht, &cp, gent);
325 return gent != NULL;
326 }
327
tui_pixelfont_draw(struct tui_pixelfont * ctx,shmif_pixel * c,size_t pitch,uint32_t cp,int x,int y,shmif_pixel fg,shmif_pixel bg,int maxx,int maxy,bool bgign)328 void tui_pixelfont_draw(
329 struct tui_pixelfont* ctx, shmif_pixel* c, size_t pitch,
330 uint32_t cp, int x, int y, shmif_pixel fg, shmif_pixel bg,
331 int maxx, int maxy, bool bgign)
332 {
333 struct font_entry* font = ctx->active_font;
334 struct glyph_ent* gent;
335 if (font)
336 HASH_FIND_INT(font->ht, &cp, gent);
337
338 if (x >= maxx || y >= maxy)
339 return;
340
341 if (!font || !gent){
342 size_t w = font->font->w;
343 size_t h = font->font->h;
344 if (w + x >= maxx)
345 w = maxx - x;
346 if (h + y >= maxy)
347 h = maxy - y;
348 if (!bgign)
349 draw_box_px(c, pitch, maxx, maxy, x, y, w, h, bg);
350 return;
351 }
352
353 /*
354 * handle partial- clipping against screen regions
355 */
356 uint8_t bind = 0;
357 int row = 0;
358 if (y < 0){
359 row -= y;
360 uint8_t bpr = font->font->w / 8;
361 if (font->font->w % 8 != 0 || bpr == 0)
362 bpr++;
363 bind += -y * bpr;
364 y = 0;
365 }
366
367 int colst = 0;
368 if (x < 0){
369 colst =-1*x;
370 x = 0;
371 }
372
373 if (font->font->w + x > maxx || font->font->h + y > maxy)
374 return;
375
376 for (; row < font->font->h && y < maxy; row++, y++){
377 shmif_pixel* pos = &c[y * pitch + x];
378 for (int col = colst; col < font->font->w; bind++){
379 /* padding bits will just be 0 */
380 int lx = x;
381 for (
382 int bit = 7;
383 bit >= 0 && col < font->font->w && lx < maxx;
384 bit--, col++, lx++)
385 {
386 if ((1 << bit) & gent->data[bind]){
387 pos[col] = fg;
388 }
389 else if (!bgign){
390 pos[col] = bg;
391 }
392 }
393 }
394 }
395 }
396