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