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