1 #include <inttypes.h>
2 #include "../../arcan_shmif.h"
3 #include "../../arcan_tui.h"
4 
5 #include "arcan_ttf.h"
6 #include "../tui_int.h"
7 
8 #ifdef NO_ARCAN_AGP
9 struct agp_vstore;
10 #else
11 #include "platform.h"
12 #endif
13 
14 #include "draw.h"
15 #include "raster.h"
16 #include "pixelfont.h"
17 
18 struct cell {
19 	shmif_pixel fc;
20 	shmif_pixel bc;
21 	uint32_t ucs4;
22 	uint8_t attr;
23 };
24 
25 struct tui_raster_context {
26 	struct tui_font* fonts[4];
27 	int last_style;
28 	int cursor_state;
29 
30 	shmif_pixel cc;
31 
32 	size_t cell_w;
33 	size_t cell_h;
34 
35 	size_t min_x, min_y;
36 	size_t max_x, max_y;
37 };
38 
tui_raster_setfont(struct tui_raster_context * ctx,struct tui_font ** src,size_t n_fonts)39 void tui_raster_setfont(
40 	struct tui_raster_context* ctx, struct tui_font** src, size_t n_fonts)
41 {
42 	for (size_t i = 0; i < 4; i++)
43 		ctx->fonts[i] = i < n_fonts ? src[i] : NULL;
44 	ctx->last_style = -1;
45 }
46 
tui_raster_setup(size_t cell_w,size_t cell_h)47 struct tui_raster_context* tui_raster_setup(size_t cell_w, size_t cell_h)
48 {
49 	struct tui_raster_context* res = malloc(sizeof(struct tui_raster_context));
50 	if (!res)
51 		return NULL;
52 
53 	*res = (struct tui_raster_context){
54 		.cell_w = cell_w,
55 		.cell_h = cell_h,
56 		.cc = SHMIF_RGBA(0x00, 0xaa, 0x00, 0xff),
57 		.last_style = -1
58 	};
59 
60 	return res;
61 }
62 
tui_raster_get_cell_size(struct tui_raster_context * ctx,size_t * w,size_t * h)63 void tui_raster_get_cell_size(
64 	struct tui_raster_context* ctx, size_t* w, size_t* h)
65 {
66 	*w = ctx->cell_w;
67 	*h = ctx->cell_h;
68 }
69 
tui_raster_cell_size(struct tui_raster_context * ctx,size_t w,size_t h)70 void tui_raster_cell_size(struct tui_raster_context* ctx, size_t w, size_t h)
71 {
72 	ctx->cell_w = w;
73 	ctx->cell_h = h;
74 }
75 
unpack_u32(uint32_t * dst,uint8_t * inbuf)76 void unpack_u32(uint32_t* dst, uint8_t* inbuf)
77 {
78 	*dst =
79 		((uint64_t)inbuf[0] <<  0) |
80 		((uint64_t)inbuf[1] <<  8) |
81 		((uint64_t)inbuf[2] << 16) |
82 		((uint64_t)inbuf[3] << 24);
83 }
84 
unpack_cell(uint8_t unpack[static12],struct cell * dst,uint8_t alpha)85 static void unpack_cell(uint8_t unpack[static 12], struct cell* dst, uint8_t alpha)
86 {
87 	dst->fc = SHMIF_RGBA(unpack[0], unpack[1], unpack[2], 0xff);
88 	dst->bc = SHMIF_RGBA(unpack[3], unpack[4], unpack[5], alpha);
89 	dst->attr = unpack[6];
90 	unpack_u32(&dst->ucs4, &unpack[8]);
91 }
92 
linehint(struct tui_raster_context * ctx,struct cell * cell,shmif_pixel * vidp,size_t pitch,int x,int y,size_t maxx,size_t maxy,bool strikethrough,bool underline)93 static void linehint(struct tui_raster_context* ctx, struct cell* cell,
94 	shmif_pixel* vidp, size_t pitch, int x, int y, size_t maxx, size_t maxy,
95 	bool strikethrough, bool underline)
96 {
97 	if (underline){
98 		int n_lines = (int)(ctx->cell_h * 0.05) | 1;
99 		draw_box_px(vidp, pitch, maxx, maxy,
100 			x, y + ctx->cell_h - n_lines, ctx->cell_w, n_lines, cell->fc);
101 	}
102 
103 /* the y value should be retrievable from the font rather than using the cell */
104 	if (strikethrough){
105 		int n_lines = (int)(ctx->cell_h * 0.05) | 1;
106 		draw_box_px(vidp, pitch, maxx, maxy,
107 			x, y + (ctx->cell_h >> 1) - (n_lines >> 1),
108 			ctx->cell_w, n_lines, cell->fc);
109 	}
110 }
111 
drawglyph(struct tui_raster_context * ctx,struct cell * cell,shmif_pixel * vidp,size_t pitch,int x,int y,size_t maxx,size_t maxy)112 static size_t drawglyph(struct tui_raster_context* ctx, struct cell* cell,
113 	shmif_pixel* vidp, size_t pitch, int x, int y, size_t maxx, size_t maxy)
114 {
115 /* draw glyph based on font state */
116 	if (!ctx->fonts[0]->vector){
117 
118 /* mouse-cursor drawing in this mode is a bit primitive */
119 		if (cell->attr & (1 << CATTR_CURSOR)){
120 			if (ctx->cursor_state == CURSOR_ACTIVE){
121 				cell->bc = ctx->cc;
122 			}
123 		}
124 
125 /* linear search for cp, on fail, fill with background */
126 		tui_pixelfont_draw(ctx->fonts[0]->bitmap,
127 			vidp, pitch, cell->ucs4, x, y, cell->fc, cell->bc, maxx, maxy, false);
128 
129 /* add line-marks */
130 		if (cell->ucs4 &&
131 			cell->attr & ((1 << CATTR_STRIKETHROUGH) | (1 << CATTR_UNDERLINE)))
132 			linehint(ctx, cell, vidp, pitch, x, y, maxx, maxy,
133 				cell->attr & (1 << CATTR_STRIKETHROUGH),
134 				cell->attr & (1 << CATTR_UNDERLINE)
135 			);
136 
137 		return ctx->cell_w;
138 	}
139 
140 /* vector font drawing */
141 	size_t nfonts = 1;
142 	TTF_Font* fonts[2] = {ctx->fonts[0]->truetype, NULL};
143 	if (ctx->fonts[1]->vector && ctx->fonts[1]->truetype){
144 		nfonts = 2;
145 		fonts[1] = ctx->fonts[1]->truetype;
146 	}
147 
148 /* Clear to bg-color as the glyph drawing with background won't pad,
149  * except if it is the cursor color, then use that. We can't do the
150  * fg/bg swap as even in unshaped the glyph might be conditionally
151  * smaller than the cell size */
152 	shmif_pixel bc = cell->bc;
153 	if ((cell->attr & (1 << CATTR_CURSOR)) && ctx->cursor_state == CURSOR_ACTIVE)
154 		bc = ctx->cc;
155 
156 	draw_box_px(vidp,
157 		pitch, maxx, maxy, x, y, ctx->cell_w, ctx->cell_h, bc);
158 
159 /* fast-path, just clear to background */
160 	if (!cell->ucs4){
161 		return ctx->cell_w;
162 	}
163 
164 	int prem = TTF_STYLE_NORMAL;
165 	prem |= TTF_STYLE_ITALIC * !!(cell->attr & (1 << CATTR_ITALIC));
166 	prem |= TTF_STYLE_BOLD * !!(cell->attr & (1 << CATTR_BOLD));
167 
168 /* seriously expensive so only perform if we actually need to as it can cause a
169  * glyph cache flush (bold / italic / ...), other option would be to run
170  * separate glyph caches on the different style options.. */
171 	if (prem != ctx->last_style){
172 		ctx->last_style = prem;
173 		TTF_SetFontStyle(fonts[0], prem);
174 		if (fonts[1])
175 			TTF_SetFontStyle(fonts[1], prem);
176 	}
177 
178 	uint8_t fg[4], bg[4];
179 	SHMIF_RGBA_DECOMP(cell->fc, &fg[0], &fg[1], &fg[2], &fg[3]);
180 	SHMIF_RGBA_DECOMP(bc, &bg[0], &bg[1], &bg[2], &bg[3]);
181 
182 	/* these are mainly used as state machine for kernel / shaping,
183 	 * we need the 'x-start' position from the previous glyph and commit
184 	 * that to the line-offset table for coordinate translation */
185 	int adv = 0;
186 	unsigned xs = 0;
187 	unsigned ind = 0;
188 	TTF_RenderUNICODEglyph(&vidp[y * pitch + x],
189 		ctx->cell_w, ctx->cell_h, pitch, fonts, nfonts, cell->ucs4, &xs,
190 		fg, bg, true, true, ctx->last_style, &adv, &ind
191 	);
192 
193 /* add line-marks, this actually does not belong here, it should be part
194  * of the style marker to the TTF_RenderUNICODEglyph - the code should be
195  * added as part of arcan_ttf.c */
196 	if (cell->ucs4 &&
197 		cell->attr & ((1 << CATTR_STRIKETHROUGH) | (1 << CATTR_UNDERLINE)))
198 		linehint(ctx, cell, vidp, pitch, x, y, maxx, maxy,
199 			cell->attr & (1 << CATTR_STRIKETHROUGH),
200 			cell->attr & (1 << CATTR_UNDERLINE)
201 		);
202 
203 	return ctx->cell_w;
204 }
205 
raster_tobuf(struct tui_raster_context * ctx,shmif_pixel * vidp,size_t pitch,size_t max_w,size_t max_h,uint16_t * x1,uint16_t * y1,uint16_t * x2,uint16_t * y2,uint8_t * buf,size_t buf_sz)206 static int raster_tobuf(
207 	struct tui_raster_context* ctx, shmif_pixel* vidp, size_t pitch,
208 	size_t max_w, size_t max_h,
209 	uint16_t* x1, uint16_t* y1, uint16_t* x2, uint16_t* y2,
210 	uint8_t* buf, size_t buf_sz)
211 {
212 	struct tui_raster_header hdr;
213 	if (!buf_sz || buf_sz < sizeof(struct tui_raster_header))
214 		return -1;
215 
216 	bool update = false;
217 	memcpy(&hdr, buf, sizeof(struct tui_raster_header));
218 
219 /* the caller might provide a larger input buffer than what the header sets,
220  * and that will still clamp/drop-out etc. but mismatch between the header
221  * fields is, of course, not permitted. */
222 	size_t hdr_ver_sz = hdr.lines * raster_line_sz +
223 		hdr.cells * raster_cell_sz + raster_hdr_sz;
224 
225 	if (hdr.data_sz > buf_sz || hdr.data_sz != hdr_ver_sz){
226 		return -1;
227 	}
228 
229 	buf_sz -= sizeof(struct tui_raster_header);
230 	buf += sizeof(struct tui_raster_header);
231 	shmif_pixel bgc = SHMIF_RGBA(hdr.bgc[0], hdr.bgc[1], hdr.bgc[2], hdr.bgc[3]);
232 
233 /* dframe, set 'always replaced' region */
234 	if (hdr.flags & RPACK_DFRAME){
235 		update = true;
236 		*y1 = max_h;
237 		*y2 = 0;
238 		*x1 = max_w;
239 		*x2 = 0;
240 	}
241 /* full-frame: pre-clear the pad region */
242 	else {
243 		*x1 = 0;
244 		*y1 = 0;
245 		*x2 = max_w;
246 		*y2 = max_h;
247 
248 		size_t pad_w = max_w % ctx->cell_w;
249 		size_t pad_h = max_h % ctx->cell_h;
250 
251 		if (pad_w){
252 			size_t start = max_w - pad_w;
253 			draw_box_px(vidp, pitch, max_w, max_h, start, 0, pad_w, max_h, bgc);
254 		}
255 		if (pad_h){
256 			size_t start = max_h - pad_h;
257 			draw_box_px(vidp, pitch, max_w, max_h, 0, start, max_w, pad_h, bgc);
258 		}
259 	}
260 
261 	ctx->cursor_state = hdr.cursor_state;
262 
263 	ssize_t cur_y = -1;
264 	size_t last_line = 0;
265 	size_t draw_y = 0;
266 
267 	for (size_t i = 0; i < hdr.lines && buf_sz; i++){
268 		if (buf_sz < sizeof(struct tui_raster_line))
269 			return -1;
270 
271 /* read / unpack line metadata */
272 		struct tui_raster_line line;
273 
274 		memcpy(&line, buf, sizeof(struct tui_raster_line));
275 		buf += sizeof(line);
276 
277 /* remember the lower line we were at, these are not always ordered */
278 		if (line.start_line > last_line)
279 			last_line = line.start_line;
280 
281 /* respecting scrolling will need another drawing routine, as we need clipping
282  * etc. and multiple lines can be scrolled, and that's better fixed when we
283  * have an atlas to work from */
284 		if (update && cur_y == -1){
285 			*y1 = line.start_line * ctx->cell_h;
286 		}
287 
288 /* skip omitted lines */
289 		if (cur_y != line.start_line){
290 /* for full draw we fill in the skipped space with the background color */
291 			cur_y = line.start_line;
292 		}
293 		draw_y = cur_y * ctx->cell_h;
294 
295 /* the line- raster routine isn't right, we actually need to unpack each line
296  * into a local buffer, make note of actual offsets and width, and then two-pass
297  * with bg first and then blend the glyphs on top of that - otherwise kerning,
298  * shapes etc. looks bad. */
299 		if (draw_y < *y1){
300 			*y1 = draw_y;
301 		}
302 
303 /* Shaping, BiDi, ... missing here now while we get the rest in place */
304 		size_t draw_x = line.offset * ctx->cell_w;
305 
306 		if (draw_x < *x1){
307 			*x1 = draw_x;
308 		}
309 
310 		for (size_t i = line.offset; line.ncells && buf_sz >= raster_cell_sz; i++){
311 			line.ncells--;
312 
313 /* extract each cell */
314 			struct cell cell;
315 			unpack_cell(buf, &cell, hdr.bgc[3]);
316 			buf += raster_cell_sz;
317 			buf_sz -= raster_cell_sz;
318 
319 /* skip bit is set, note that for a shaped line, this means that
320  * we need to have an offset- map to advance correctly */
321 			if (cell.attr & (1 << CATTR_SKIP)){
322 				draw_x += ctx->cell_w;
323 				continue;
324 			}
325 
326 /* blit or discard if OOB */
327 			if (draw_x + ctx->cell_w <= max_w && draw_y + ctx->cell_h <= max_h){
328 				draw_x += drawglyph(ctx, &cell, vidp, pitch, draw_x, draw_y, max_w, max_h);
329 			}
330 			else
331 				continue;
332 
333 			uint16_t next_x = draw_x + ctx->cell_w;
334 			if (*x2 < next_x && next_x <= max_w){
335 				*x2 = next_x;
336 			}
337 		}
338 
339 		cur_y++;
340 	}
341 
342 	*y2 = (last_line + 1) * ctx->cell_h;
343 
344 /* sweep through the context struct and blit the glyphs */
345 	return 1;
346 }
347 
tui_raster_render(struct tui_raster_context * ctx,struct arcan_shmif_cont * dst,uint8_t * buf,size_t buf_sz)348 int tui_raster_render(struct tui_raster_context* ctx,
349 	struct arcan_shmif_cont* dst, uint8_t* buf, size_t buf_sz)
350 {
351 	if (!ctx || !dst || !ctx->fonts[0] || buf_sz < sizeof(struct tui_raster_header))
352 		return -1;
353 
354 /* pixel- rasterization over shmif should work with one big BB until we have
355  * chain-mode. server-side, the vertex buffer slicing will just stream so not
356  * much to care about there */
357 	uint16_t x1, y1, x2, y2;
358 	if (-1 == raster_tobuf(ctx, dst->vidp, dst->pitch,
359 		dst->w, dst->h, &x1, &y1, &x2, &y2, buf, buf_sz))
360 	return -1;
361 
362 	if (x2 > dst->w)
363 		x2 = dst->w;
364 
365 	arcan_shmif_dirty(dst, x1, y1, x2, y2, 0);
366 	return 1;
367 }
368 
tui_raster_offset(struct tui_raster_context * ctx,size_t px_x,size_t row,size_t * offset)369 void tui_raster_offset(
370 	struct tui_raster_context* ctx, size_t px_x, size_t row, size_t* offset)
371 {
372 	if (ctx->cell_w)
373 		*offset = px_x;
374 	else
375 		*offset = px_x;
376 }
377 
378 /*
379  * Synch the raster state into the agp_store
380  *
381  * This is an intermediate step in doing this properly, i.e. just offloading
382  * the raster to the server side and go from there. The context still need to
383  * be built to handle / register fonts within though.
384  *
385  * A 'special' option here would be to return the offsets and widths into the
386  * buf during processing, as it will guaranteed fit - and the client side
387  * becomes easier as those won't need to be 'predicted'.
388  */
389 #ifndef NO_ARCAN_AGP
tui_raster_renderagp(struct tui_raster_context * ctx,struct agp_vstore * dst,uint8_t * buf,size_t buf_sz,struct stream_meta * out)390 void tui_raster_renderagp(struct tui_raster_context* ctx,
391 	struct agp_vstore* dst, uint8_t* buf, size_t buf_sz,
392 	struct stream_meta* out)
393 {
394 	if (!ctx || !dst || buf_sz < sizeof(struct tui_raster_header))
395 		return;
396 
397 	uint16_t x1, y1, x2, y2;
398 
399 	if (-1 == raster_tobuf(ctx, dst->vinf.text.raw, dst->w,
400 		dst->w, dst->h, &x1, &y1, &x2, &y2, buf, buf_sz)){
401 		*out = (struct stream_meta){0};
402 	}
403 	else {
404 		*out = (struct stream_meta){
405 			.buf = dst->vinf.text.raw,
406 			.x1 = x1, .y1 = y1, .w = x2 - x1, .h = y2 - y1,
407 			.dirty = true
408 		};
409 	}
410 }
411 #endif
412 
413 /*
414  * Free any buffers and resources bound to the raster
415  */
tui_raster_free(struct tui_raster_context * ctx)416 void tui_raster_free(struct tui_raster_context* ctx)
417 {
418 	if (!ctx)
419 		return;
420 
421 	free(ctx);
422 }
423