1 #ifdef __GLIBC__
2 #  define _XOPEN_SOURCE 600 /* strdup */
3 #endif
4 
5 #include "tickit.h"
6 
7 #include <stdio.h>  // vsnprintf
8 #include <stdlib.h>
9 #include <string.h>
10 
11 #include "linechars.inc"
12 
13 #define RECT_PRINTF_FMT     "[(%d,%d)..(%d,%d)]"
14 #define RECT_PRINTF_ARGS(r) (r).left, (r).top, tickit_rect_right(&(r)), tickit_rect_bottom(&(r))
15 
16 /* must match .pm file */
17 enum TickitRenderBufferCellState {
18   SKIP  = 0,
19   TEXT  = 1,
20   ERASE = 2,
21   CONT  = 3,
22   LINE  = 4,
23   CHAR  = 5,
24 };
25 
26 enum {
27   NORTH_SHIFT = 0,
28   EAST_SHIFT  = 2,
29   SOUTH_SHIFT = 4,
30   WEST_SHIFT  = 6,
31 };
32 
33 // Internal cell structure definition
34 typedef struct {
35   enum TickitRenderBufferCellState state;
36   union {
37     int startcol; // for state == CONT
38     int cols;     // otherwise
39   };
40   int maskdepth; // -1 if not masked
41   TickitPen *pen; // state -> {TEXT, ERASE, LINE, CHAR}
42   union {
43     struct { TickitString *s; int offs; } text; // state == TEXT
44     struct { int mask;                  } line; // state == LINE
45     struct { int codepoint;             } chr;  // state == CHAR
46   } v;
47 } RBCell;
48 
49 typedef struct RBStack RBStack;
50 struct RBStack {
51   RBStack *prev;
52 
53   int vc_line, vc_col;
54   int xlate_line, xlate_col;
55   TickitRect clip;
56   TickitPen *pen;
57   unsigned int pen_only : 1;
58 };
59 
60 struct TickitRenderBuffer {
61   int lines, cols; // Size
62   RBCell **cells;
63 
64   unsigned int vc_pos_set : 1;
65   int vc_line, vc_col;
66   int xlate_line, xlate_col;
67   TickitRect clip;
68   TickitPen *pen;
69 
70   int depth;
71   RBStack *stack;
72 
73   char *tmp;
74   size_t tmplen;  // actually valid
75   size_t tmpsize; // allocated size
76 
77   int refcount;
78 };
79 
debug_logf(TickitRenderBuffer * rb,const char * flag,const char * fmt,...)80 static void debug_logf(TickitRenderBuffer *rb, const char *flag, const char *fmt, ...)
81 {
82   va_list args;
83   va_start(args, fmt);
84 
85   char fmt_with_indent[strlen(fmt) + 3 * rb->depth + 1];
86   {
87     char *s = fmt_with_indent;
88     for(int i = 0; i < rb->depth; i++)
89       s += sprintf(s, "|  ");
90     strcpy(s, fmt);
91   }
92 
93   tickit_debug_vlogf(flag, fmt_with_indent, args);
94 
95   va_end(args);
96 }
97 
98 #define DEBUG_LOGF  if(tickit_debug_enabled) debug_logf
99 
free_stack(RBStack * stack)100 static void free_stack(RBStack *stack)
101 {
102   while(stack) {
103     RBStack *prev = stack->prev;
104     if(stack->pen)
105       tickit_pen_unref(stack->pen);
106     free(stack);
107 
108     stack = prev;
109   }
110 }
111 
tmp_cat_utf8(TickitRenderBuffer * rb,long codepoint)112 static void tmp_cat_utf8(TickitRenderBuffer *rb, long codepoint)
113 {
114   int seqlen = tickit_utf8_seqlen(codepoint);
115   if(rb->tmpsize < rb->tmplen + seqlen) {
116     rb->tmpsize *= 2;
117     rb->tmp = realloc(rb->tmp, rb->tmpsize);
118   }
119 
120   tickit_utf8_put(rb->tmp + rb->tmplen, rb->tmpsize - rb->tmplen, codepoint);
121   rb->tmplen += seqlen;
122 
123   /* rb->tmp remains NOT nul-terminated */
124 }
125 
tmp_alloc(TickitRenderBuffer * rb,size_t len)126 static void tmp_alloc(TickitRenderBuffer *rb, size_t len)
127 {
128   if(rb->tmpsize < len) {
129     free(rb->tmp);
130 
131     while(rb->tmpsize < len)
132       rb->tmpsize *= 2;
133     rb->tmp = malloc(rb->tmpsize);
134   }
135 }
136 
xlate_and_clip(TickitRenderBuffer * rb,int * line,int * col,int * cols,int * startcol)137 static int xlate_and_clip(TickitRenderBuffer *rb, int *line, int *col, int *cols, int *startcol)
138 {
139   *line += rb->xlate_line;
140   *col  += rb->xlate_col;
141 
142   const TickitRect *clip = &rb->clip;
143 
144   if(!clip->lines)
145     return 0;
146 
147   if(*line < clip->top ||
148       *line >= tickit_rect_bottom(clip) ||
149       *col  >= tickit_rect_right(clip))
150     return 0;
151 
152   if(startcol)
153     *startcol = 0;
154 
155   if(*col < clip->left) {
156     *cols      -= clip->left - *col;
157     if(startcol)
158       *startcol += clip->left - *col;
159     *col = clip->left;
160   }
161   if(*cols <= 0)
162     return 0;
163 
164   if(*cols > tickit_rect_right(clip) - *col)
165     *cols = tickit_rect_right(clip) - *col;
166 
167   return 1;
168 }
169 
cont_cell(RBCell * cell,int startcol)170 static void cont_cell(RBCell *cell, int startcol)
171 {
172   switch(cell->state) {
173     case TEXT:
174       tickit_string_unref(cell->v.text.s);
175       /* fallthrough */
176     case ERASE:
177     case LINE:
178     case CHAR:
179       tickit_pen_unref(cell->pen);
180       break;
181     case SKIP:
182     case CONT:
183       /* ignore */
184       break;
185   }
186 
187   cell->state     = CONT;
188   cell->maskdepth = -1;
189   cell->startcol  = startcol;
190   cell->pen       = NULL;
191 }
192 
make_span(TickitRenderBuffer * rb,int line,int col,int cols)193 static RBCell *make_span(TickitRenderBuffer *rb, int line, int col, int cols)
194 {
195   int end = col + cols;
196   RBCell **cells = rb->cells;
197 
198   // If the following cell is a CONT, it needs to become a new start
199   if(end < rb->cols && cells[line][end].state == CONT) {
200     int spanstart = cells[line][end].cols;
201     RBCell *spancell = &cells[line][spanstart];
202     int spanend = spanstart + spancell->startcol;
203     int afterlen = spanend - end;
204     RBCell *endcell = &cells[line][end];
205 
206     switch(spancell->state) {
207       case SKIP:
208         endcell->state = SKIP;
209         endcell->cols  = afterlen;
210         break;
211       case TEXT:
212         endcell->state       = TEXT;
213         endcell->cols        = afterlen;
214         endcell->pen         = tickit_pen_ref(spancell->pen);
215         endcell->v.text.s    = tickit_string_ref(spancell->v.text.s);
216         endcell->v.text.offs = spancell->v.text.offs + end - spanstart;
217         break;
218       case ERASE:
219         endcell->state = ERASE;
220         endcell->cols  = afterlen;
221         endcell->pen   = tickit_pen_ref(spancell->pen);
222         break;
223       case LINE:
224       case CHAR:
225       case CONT:
226         abort();
227     }
228 
229     // We know these are already CONT cells
230     for(int c = end + 1; c < spanend; c++)
231       cells[line][c].cols = end;
232   }
233 
234   // If the initial cell is a CONT, shorten its start
235   if(cells[line][col].state == CONT) {
236     int beforestart = cells[line][col].cols;
237     RBCell *spancell = &cells[line][beforestart];
238     int beforelen = col - beforestart;
239 
240     switch(spancell->state) {
241       case SKIP:
242       case TEXT:
243       case ERASE:
244         spancell->cols = beforelen;
245         break;
246       case LINE:
247       case CHAR:
248       case CONT:
249         abort();
250     }
251   }
252 
253   // cont_cell() also frees any pens in the range
254   for(int c = col; c < end; c++)
255     cont_cell(&cells[line][c], col);
256 
257   cells[line][col].cols = cols;
258 
259   return &cells[line][col];
260 }
261 
262 // cell creation functions
263 
put_string(TickitRenderBuffer * rb,int line,int col,TickitString * s)264 static int put_string(TickitRenderBuffer *rb, int line, int col, TickitString *s)
265 {
266   TickitStringPos endpos;
267   size_t len = tickit_utf8_ncount(tickit_string_get(s), tickit_string_len(s), &endpos, NULL);
268   if(1 + len == 0)
269     return -1;
270 
271   int cols = endpos.columns;
272   int ret = cols;
273 
274   int startcol;
275   if(!xlate_and_clip(rb, &line, &col, &cols, &startcol))
276     return ret;
277 
278   RBCell *linecells = rb->cells[line];
279 
280   while(cols) {
281     while(cols && linecells[col].maskdepth > -1) {
282       col++;
283       cols--;
284       startcol++;
285     }
286     if(!cols)
287       break;
288 
289     int spanlen = 0;
290     while(cols && linecells[col + spanlen].maskdepth == -1) {
291       spanlen++;
292       cols--;
293     }
294     if(!spanlen)
295       break;
296 
297     RBCell *cell = make_span(rb, line, col, spanlen);
298     cell->state       = TEXT;
299     cell->pen         = tickit_pen_ref(rb->pen);
300     cell->v.text.s    = tickit_string_ref(s);
301     cell->v.text.offs = startcol;
302 
303     col      += spanlen;
304     startcol += spanlen;
305   }
306 
307   return ret;
308 }
309 
put_text(TickitRenderBuffer * rb,int line,int col,const char * text,size_t len)310 static int put_text(TickitRenderBuffer *rb, int line, int col, const char *text, size_t len)
311 {
312   TickitString *s = tickit_string_new(text, len == -1 ? strlen(text) : len);
313 
314   int ret = put_string(rb, line, col, s);
315 
316   tickit_string_unref(s);
317 
318   return ret;
319 }
320 
put_vtextf(TickitRenderBuffer * rb,int line,int col,const char * fmt,va_list args)321 static int put_vtextf(TickitRenderBuffer *rb, int line, int col, const char *fmt, va_list args)
322 {
323   /* It's likely the string will fit in, say, 64 bytes */
324   char buffer[64];
325   size_t len;
326   {
327     va_list args_for_size;
328     va_copy(args_for_size, args);
329 
330     len = vsnprintf(buffer, sizeof buffer, fmt, args_for_size);
331 
332     va_end(args_for_size);
333   }
334 
335   if(len < sizeof buffer)
336     return put_text(rb, line, col, buffer, len);
337 
338   tmp_alloc(rb, len + 1);
339   vsnprintf(rb->tmp, rb->tmpsize, fmt, args);
340   return put_text(rb, line, col, rb->tmp, len);
341 }
342 
put_char(TickitRenderBuffer * rb,int line,int col,long codepoint)343 static void put_char(TickitRenderBuffer *rb, int line, int col, long codepoint)
344 {
345   int cols = 1;
346 
347   if(!xlate_and_clip(rb, &line, &col, &cols, NULL))
348     return;
349 
350   if(rb->cells[line][col].maskdepth > -1)
351     return;
352 
353   RBCell *cell = make_span(rb, line, col, cols);
354   cell->state           = CHAR;
355   cell->pen             = tickit_pen_ref(rb->pen);
356   cell->v.chr.codepoint = codepoint;
357 }
358 
skip(TickitRenderBuffer * rb,int line,int col,int cols)359 static void skip(TickitRenderBuffer *rb, int line, int col, int cols)
360 {
361   if(!xlate_and_clip(rb, &line, &col, &cols, NULL))
362     return;
363 
364   RBCell *linecells = rb->cells[line];
365 
366   while(cols) {
367     while(cols && linecells[col].maskdepth > -1) {
368       col++;
369       cols--;
370     }
371     if(!cols)
372       break;
373 
374     int spanlen = 0;
375     while(cols && linecells[col + spanlen].maskdepth == -1) {
376       spanlen++;
377       cols--;
378     }
379     if(!spanlen)
380       break;
381 
382     RBCell *cell = make_span(rb, line, col, spanlen);
383     cell->state = SKIP;
384 
385     col += spanlen;
386   }
387 }
388 
erase(TickitRenderBuffer * rb,int line,int col,int cols)389 static void erase(TickitRenderBuffer *rb, int line, int col, int cols)
390 {
391   if(!xlate_and_clip(rb, &line, &col, &cols, NULL))
392     return;
393 
394   RBCell *linecells = rb->cells[line];
395 
396   while(cols) {
397     while(cols && linecells[col].maskdepth > -1) {
398       col++;
399       cols--;
400     }
401     if(!cols)
402       break;
403 
404     int spanlen = 0;
405     while(cols && linecells[col + spanlen].maskdepth == -1) {
406       spanlen++;
407       cols--;
408     }
409     if(!spanlen)
410       break;
411 
412     RBCell *cell = make_span(rb, line, col, spanlen);
413     cell->state = ERASE;
414     cell->pen   = tickit_pen_ref(rb->pen);
415 
416     col += spanlen;
417   }
418 }
419 
tickit_renderbuffer_new(int lines,int cols)420 TickitRenderBuffer *tickit_renderbuffer_new(int lines, int cols)
421 {
422   TickitRenderBuffer *rb = malloc(sizeof(TickitRenderBuffer));
423 
424   rb->lines = lines;
425   rb->cols  = cols;
426 
427   rb->cells = malloc(rb->lines * sizeof(RBCell *));
428   for(int line = 0; line < rb->lines; line++) {
429     rb->cells[line] = malloc(rb->cols * sizeof(RBCell));
430 
431     rb->cells[line][0].state     = SKIP;
432     rb->cells[line][0].maskdepth = -1;
433     rb->cells[line][0].cols      = rb->cols;
434     rb->cells[line][0].pen       = NULL;
435 
436     for(int col = 1; col < rb->cols; col++) {
437       rb->cells[line][col].state     = CONT;
438       rb->cells[line][col].maskdepth = -1;
439       rb->cells[line][col].cols      = 0;
440     }
441   }
442 
443   rb->vc_pos_set = 0;
444 
445   rb->xlate_line = 0;
446   rb->xlate_col  = 0;
447 
448   tickit_rect_init_sized(&rb->clip, 0, 0, rb->lines, rb->cols);
449 
450   rb->pen = tickit_pen_new();
451 
452   rb->stack = NULL;
453   rb->depth = 0;
454 
455   rb->tmpsize = 256; // hopefully enough but will grow if required
456   rb->tmp = malloc(rb->tmpsize);
457   rb->tmplen = 0;
458 
459   rb->refcount = 1;
460 
461   return rb;
462 }
463 
tickit_renderbuffer_destroy(TickitRenderBuffer * rb)464 void tickit_renderbuffer_destroy(TickitRenderBuffer *rb)
465 {
466   for(int line = 0; line < rb->lines; line++) {
467     for(int col = 0; col < rb->cols; col++) {
468       RBCell *cell = &rb->cells[line][col];
469       switch(cell->state) {
470         case TEXT:
471           tickit_string_unref(cell->v.text.s);
472           /* fallthrough */
473         case ERASE:
474         case LINE:
475         case CHAR:
476           tickit_pen_unref(cell->pen);
477           break;
478         case SKIP:
479         case CONT:
480           break;
481       }
482     }
483     free(rb->cells[line]);
484   }
485 
486   free(rb->cells);
487   rb->cells = NULL;
488 
489   tickit_pen_unref(rb->pen);
490 
491   if(rb->stack)
492     free_stack(rb->stack);
493 
494   free(rb->tmp);
495 
496   free(rb);
497 }
498 
tickit_renderbuffer_ref(TickitRenderBuffer * rb)499 TickitRenderBuffer *tickit_renderbuffer_ref(TickitRenderBuffer *rb)
500 {
501   rb->refcount++;
502   return rb;
503 }
504 
tickit_renderbuffer_unref(TickitRenderBuffer * rb)505 void tickit_renderbuffer_unref(TickitRenderBuffer *rb)
506 {
507   if(rb->refcount < 1) {
508     fprintf(stderr, "tickit_renderbuffer_unref: invalid refcount %d\n", rb->refcount);
509     abort();
510   }
511   rb->refcount--;
512   if(!rb->refcount)
513     tickit_renderbuffer_destroy(rb);
514 }
515 
tickit_renderbuffer_get_size(const TickitRenderBuffer * rb,int * lines,int * cols)516 void tickit_renderbuffer_get_size(const TickitRenderBuffer *rb, int *lines, int *cols)
517 {
518   if(lines)
519     *lines = rb->lines;
520 
521   if(cols)
522     *cols = rb->cols;
523 }
524 
tickit_renderbuffer_translate(TickitRenderBuffer * rb,int downward,int rightward)525 void tickit_renderbuffer_translate(TickitRenderBuffer *rb, int downward, int rightward)
526 {
527   DEBUG_LOGF(rb, "Bt", "Translate (%+d,%+d)", rightward, downward);
528 
529   rb->xlate_line += downward;
530   rb->xlate_col  += rightward;
531 }
532 
tickit_renderbuffer_clip(TickitRenderBuffer * rb,TickitRect * rect)533 void tickit_renderbuffer_clip(TickitRenderBuffer *rb, TickitRect *rect)
534 {
535   DEBUG_LOGF(rb, "Bt", "Clip " RECT_PRINTF_FMT, RECT_PRINTF_ARGS(*rect));
536 
537   TickitRect other;
538 
539   other = *rect;
540   other.top  += rb->xlate_line;
541   other.left += rb->xlate_col;
542 
543   if(!tickit_rect_intersect(&rb->clip, &rb->clip, &other))
544     rb->clip.lines = 0;
545 }
546 
tickit_renderbuffer_mask(TickitRenderBuffer * rb,TickitRect * mask)547 void tickit_renderbuffer_mask(TickitRenderBuffer *rb, TickitRect *mask)
548 {
549   DEBUG_LOGF(rb, "Bt", "Mask " RECT_PRINTF_FMT, RECT_PRINTF_ARGS(*mask));
550 
551   TickitRect hole;
552 
553   hole = *mask;
554   hole.top  += rb->xlate_line;
555   hole.left += rb->xlate_col;
556 
557   if(hole.top < 0) {
558     hole.lines += hole.top;
559     hole.top = 0;
560   }
561   if(hole.left < 0) {
562     hole.cols += hole.left;
563     hole.left = 0;
564   }
565 
566   for(int line = hole.top; line < tickit_rect_bottom(&hole) && line < rb->lines; line++) {
567     for(int col = hole.left; col < tickit_rect_right(&hole) && col < rb->cols; col++) {
568       RBCell *cell = &rb->cells[line][col];
569       if(cell->maskdepth == -1)
570         cell->maskdepth = rb->depth;
571     }
572   }
573 }
574 
tickit_renderbuffer_has_cursorpos(const TickitRenderBuffer * rb)575 bool tickit_renderbuffer_has_cursorpos(const TickitRenderBuffer *rb)
576 {
577   return rb->vc_pos_set;
578 }
579 
tickit_renderbuffer_get_cursorpos(const TickitRenderBuffer * rb,int * line,int * col)580 void tickit_renderbuffer_get_cursorpos(const TickitRenderBuffer *rb, int *line, int *col)
581 {
582   if(rb->vc_pos_set && line)
583     *line = rb->vc_line;
584   if(rb->vc_pos_set && col)
585     *col = rb->vc_col;
586 }
587 
tickit_renderbuffer_goto(TickitRenderBuffer * rb,int line,int col)588 void tickit_renderbuffer_goto(TickitRenderBuffer *rb, int line, int col)
589 {
590   rb->vc_pos_set = 1;
591   rb->vc_line = line;
592   rb->vc_col  = col;
593 }
594 
tickit_renderbuffer_ungoto(TickitRenderBuffer * rb)595 void tickit_renderbuffer_ungoto(TickitRenderBuffer *rb)
596 {
597   rb->vc_pos_set = 0;
598 }
599 
tickit_renderbuffer_setpen(TickitRenderBuffer * rb,const TickitPen * pen)600 void tickit_renderbuffer_setpen(TickitRenderBuffer *rb, const TickitPen *pen)
601 {
602   TickitPen *prevpen = rb->stack ? rb->stack->pen : NULL;
603 
604   /* never mutate the pen inplace; make a new one */
605   TickitPen *newpen = tickit_pen_new();
606 
607   if(pen)
608     tickit_pen_copy(newpen, pen, 1);
609   if(prevpen)
610     tickit_pen_copy(newpen, prevpen, 0);
611 
612   tickit_pen_unref(rb->pen);
613   rb->pen = newpen;
614 }
615 
tickit_renderbuffer_reset(TickitRenderBuffer * rb)616 void tickit_renderbuffer_reset(TickitRenderBuffer *rb)
617 {
618   for(int line = 0; line < rb->lines; line++) {
619     // cont_cell also frees pen
620     for(int col = 0; col < rb->cols; col++)
621       cont_cell(&rb->cells[line][col], 0);
622 
623     rb->cells[line][0].state     = SKIP;
624     rb->cells[line][0].maskdepth = -1;
625     rb->cells[line][0].cols      = rb->cols;
626   }
627 
628   rb->vc_pos_set = 0;
629 
630   rb->xlate_line = 0;
631   rb->xlate_col  = 0;
632 
633   tickit_rect_init_sized(&rb->clip, 0, 0, rb->lines, rb->cols);
634 
635   tickit_pen_unref(rb->pen);
636   rb->pen = tickit_pen_new();
637 
638   if(rb->stack) {
639     free_stack(rb->stack);
640     rb->stack = NULL;
641     rb->depth = 0;
642   }
643 }
644 
tickit_renderbuffer_clear(TickitRenderBuffer * rb)645 void tickit_renderbuffer_clear(TickitRenderBuffer *rb)
646 {
647   DEBUG_LOGF(rb, "Bd", "Clear");
648 
649   for(int line = 0; line < rb->lines; line++)
650     erase(rb, line, 0, rb->cols);
651 }
652 
tickit_renderbuffer_save(TickitRenderBuffer * rb)653 void tickit_renderbuffer_save(TickitRenderBuffer *rb)
654 {
655   DEBUG_LOGF(rb, "Bs", "+-Save");
656 
657   RBStack *stack = malloc(sizeof(struct RBStack));
658 
659   stack->vc_line    = rb->vc_line;
660   stack->vc_col     = rb->vc_col;
661   stack->xlate_line = rb->xlate_line;
662   stack->xlate_col  = rb->xlate_col;
663   stack->clip       = rb->clip;
664   stack->pen        = tickit_pen_ref(rb->pen);
665   stack->pen_only   = 0;
666 
667   stack->prev = rb->stack;
668   rb->stack = stack;
669   rb->depth++;
670 }
671 
tickit_renderbuffer_savepen(TickitRenderBuffer * rb)672 void tickit_renderbuffer_savepen(TickitRenderBuffer *rb)
673 {
674   DEBUG_LOGF(rb, "Bs", "+-Savepen");
675 
676   RBStack *stack = malloc(sizeof(struct RBStack));
677 
678   stack->pen      = tickit_pen_ref(rb->pen);
679   stack->pen_only = 1;
680 
681   stack->prev = rb->stack;
682   rb->stack = stack;
683   rb->depth++;
684 }
685 
tickit_renderbuffer_restore(TickitRenderBuffer * rb)686 void tickit_renderbuffer_restore(TickitRenderBuffer *rb)
687 {
688   RBStack *stack;
689 
690   if(!rb->stack)
691     return;
692 
693   stack = rb->stack;
694   rb->stack = stack->prev;
695 
696   if(!stack->pen_only) {
697     rb->vc_line    = stack->vc_line;
698     rb->vc_col     = stack->vc_col;
699     rb->xlate_line = stack->xlate_line;
700     rb->xlate_col  = stack->xlate_col;
701     rb->clip       = stack->clip;
702   }
703 
704   tickit_pen_unref(rb->pen);
705   rb->pen = stack->pen;
706   // We've now definitely taken ownership of the old stack frame's pen, so
707   //   it doesn't need destroying now
708 
709   rb->depth--;
710 
711   // TODO: this could be done more efficiently by remembering the edges of masking
712   for(int line = 0; line < rb->lines; line++)
713     for(int col = 0; col < rb->cols; col++)
714       if(rb->cells[line][col].maskdepth > rb->depth)
715         rb->cells[line][col].maskdepth = -1;
716 
717   free(stack);
718 
719   DEBUG_LOGF(rb, "Bs", "+-Restore");
720 }
721 
tickit_renderbuffer_skip_at(TickitRenderBuffer * rb,int line,int col,int cols)722 void tickit_renderbuffer_skip_at(TickitRenderBuffer *rb, int line, int col, int cols)
723 {
724   DEBUG_LOGF(rb, "Bd", "Skip (%d..%d,%d)", col, col + cols, line);
725 
726   skip(rb, line, col, cols);
727 }
728 
tickit_renderbuffer_skip(TickitRenderBuffer * rb,int cols)729 void tickit_renderbuffer_skip(TickitRenderBuffer *rb, int cols)
730 {
731   if(!rb->vc_pos_set)
732     return;
733 
734   DEBUG_LOGF(rb, "Bd", "Skip (%d..%d,%d) +%d", rb->vc_col, rb->vc_col + cols, rb->vc_line, cols);
735 
736   skip(rb, rb->vc_line, rb->vc_col, cols);
737   rb->vc_col += cols;
738 }
739 
tickit_renderbuffer_skip_to(TickitRenderBuffer * rb,int col)740 void tickit_renderbuffer_skip_to(TickitRenderBuffer *rb, int col)
741 {
742   if(!rb->vc_pos_set)
743     return;
744 
745   DEBUG_LOGF(rb, "Bd", "Skip (%d..%d,%d) +%d", rb->vc_col, col, rb->vc_line, col - rb->vc_col);
746 
747   if(rb->vc_col < col)
748     skip(rb, rb->vc_line, rb->vc_col, col - rb->vc_col);
749 
750   rb->vc_col = col;
751 }
752 
tickit_renderbuffer_skiprect(TickitRenderBuffer * rb,TickitRect * rect)753 void tickit_renderbuffer_skiprect(TickitRenderBuffer *rb, TickitRect *rect)
754 {
755   DEBUG_LOGF(rb, "Bd", "Skip [(%d,%d)..(%d,%d)]", rect->left, rect->top, tickit_rect_right(rect), tickit_rect_bottom(rect));
756 
757   for(int line = rect->top; line < tickit_rect_bottom(rect); line++)
758     skip(rb, line, rect->left, rect->cols);
759 }
760 
tickit_renderbuffer_text_at(TickitRenderBuffer * rb,int line,int col,const char * text)761 int tickit_renderbuffer_text_at(TickitRenderBuffer *rb, int line, int col, const char *text)
762 {
763   return tickit_renderbuffer_textn_at(rb, line, col, text, -1);
764 }
765 
tickit_renderbuffer_textn_at(TickitRenderBuffer * rb,int line,int col,const char * text,size_t len)766 int tickit_renderbuffer_textn_at(TickitRenderBuffer *rb, int line, int col, const char *text, size_t len)
767 {
768   int cols = put_text(rb, line, col, text, len);
769 
770   DEBUG_LOGF(rb, "Bd", "Text (%d..%d,%d)", col, col + cols, line);
771 
772   return cols;
773 }
774 
tickit_renderbuffer_text(TickitRenderBuffer * rb,const char * text)775 int tickit_renderbuffer_text(TickitRenderBuffer *rb, const char *text)
776 {
777   return tickit_renderbuffer_textn(rb, text, -1);
778 }
779 
tickit_renderbuffer_textn(TickitRenderBuffer * rb,const char * text,size_t len)780 int tickit_renderbuffer_textn(TickitRenderBuffer *rb, const char *text, size_t len)
781 {
782   if(!rb->vc_pos_set)
783     return -1;
784 
785   int cols = put_text(rb, rb->vc_line, rb->vc_col, text, len);
786 
787   DEBUG_LOGF(rb, "Bd", "Text (%d..%d,%d) +%d", rb->vc_col, rb->vc_col + cols, rb->vc_line, cols);
788 
789   rb->vc_col += cols;
790   return cols;
791 }
792 
tickit_renderbuffer_textf_at(TickitRenderBuffer * rb,int line,int col,const char * fmt,...)793 int tickit_renderbuffer_textf_at(TickitRenderBuffer *rb, int line, int col, const char *fmt, ...)
794 {
795   va_list args;
796   va_start(args, fmt);
797 
798   int ret = tickit_renderbuffer_vtextf_at(rb, line, col, fmt, args);
799 
800   va_end(args);
801 
802   return ret;
803 }
804 
tickit_renderbuffer_vtextf_at(TickitRenderBuffer * rb,int line,int col,const char * fmt,va_list args)805 int tickit_renderbuffer_vtextf_at(TickitRenderBuffer *rb, int line, int col, const char *fmt, va_list args)
806 {
807   int cols = put_vtextf(rb, line, col, fmt, args);
808 
809   DEBUG_LOGF(rb, "Bd", "Text (%d..%d,%d)", col, col + cols, line);
810 
811   return cols;
812 }
813 
tickit_renderbuffer_textf(TickitRenderBuffer * rb,const char * fmt,...)814 int tickit_renderbuffer_textf(TickitRenderBuffer *rb, const char *fmt, ...)
815 {
816   va_list args;
817   va_start(args, fmt);
818 
819   int ret = tickit_renderbuffer_vtextf(rb, fmt, args);
820 
821   va_end(args);
822 
823   return ret;
824 }
825 
tickit_renderbuffer_vtextf(TickitRenderBuffer * rb,const char * fmt,va_list args)826 int tickit_renderbuffer_vtextf(TickitRenderBuffer *rb, const char *fmt, va_list args)
827 {
828   if(!rb->vc_pos_set)
829     return -1;
830 
831   int cols = put_vtextf(rb, rb->vc_line, rb->vc_col, fmt, args);
832 
833   DEBUG_LOGF(rb, "Bd", "Text (%d..%d,%d) +%d", rb->vc_col, rb->vc_col + cols, rb->vc_line, cols);
834 
835   rb->vc_col += cols;
836   return cols;
837 }
838 
tickit_renderbuffer_erase_at(TickitRenderBuffer * rb,int line,int col,int cols)839 void tickit_renderbuffer_erase_at(TickitRenderBuffer *rb, int line, int col, int cols)
840 {
841   DEBUG_LOGF(rb, "Bd", "Erase (%d..%d,%d)", col, col + cols, line);
842 
843   erase(rb, line, col, cols);
844 }
845 
tickit_renderbuffer_erase(TickitRenderBuffer * rb,int cols)846 void tickit_renderbuffer_erase(TickitRenderBuffer *rb, int cols)
847 {
848   if(!rb->vc_pos_set)
849     return;
850 
851   DEBUG_LOGF(rb, "Bd", "Erase (%d..%d,%d) +%d", rb->vc_col, rb->vc_col + cols, rb->vc_line, cols);
852 
853   erase(rb, rb->vc_line, rb->vc_col, cols);
854   rb->vc_col += cols;
855 }
856 
tickit_renderbuffer_erase_to(TickitRenderBuffer * rb,int col)857 void tickit_renderbuffer_erase_to(TickitRenderBuffer *rb, int col)
858 {
859   if(!rb->vc_pos_set)
860     return;
861 
862   DEBUG_LOGF(rb, "Bd", "Erase (%d..%d,%d) +%d", rb->vc_col, col, rb->vc_line, col - rb->vc_col);
863 
864   if(rb->vc_col < col)
865     erase(rb, rb->vc_line, rb->vc_col, col - rb->vc_col);
866 
867   rb->vc_col = col;
868 }
869 
tickit_renderbuffer_eraserect(TickitRenderBuffer * rb,TickitRect * rect)870 void tickit_renderbuffer_eraserect(TickitRenderBuffer *rb, TickitRect *rect)
871 {
872   DEBUG_LOGF(rb, "Bd", "Erase [(%d,%d)..(%d,%d)]", rect->left, rect->top, tickit_rect_right(rect), tickit_rect_bottom(rect));
873 
874   for(int line = rect->top; line < tickit_rect_bottom(rect); line++)
875     erase(rb, line, rect->left, rect->cols);
876 }
877 
tickit_renderbuffer_char_at(TickitRenderBuffer * rb,int line,int col,long codepoint)878 void tickit_renderbuffer_char_at(TickitRenderBuffer *rb, int line, int col, long codepoint)
879 {
880   DEBUG_LOGF(rb, "Bd", "Char (%d.,%d,%d)", col, col + 1, line);
881 
882   put_char(rb, line, col, codepoint);
883 }
884 
tickit_renderbuffer_char(TickitRenderBuffer * rb,long codepoint)885 void tickit_renderbuffer_char(TickitRenderBuffer *rb, long codepoint)
886 {
887   if(!rb->vc_pos_set)
888     return;
889 
890   DEBUG_LOGF(rb, "Bd", "Char (%d..%d,%d) +%d", rb->vc_col, rb->vc_col + 1, rb->vc_line, 1);
891 
892   put_char(rb, rb->vc_line, rb->vc_col, codepoint);
893   // TODO: might not be 1; would have to look it up
894   rb->vc_col += 1;
895 }
896 
linecell(TickitRenderBuffer * rb,int line,int col,int bits)897 static void linecell(TickitRenderBuffer *rb, int line, int col, int bits)
898 {
899   int cols = 1;
900 
901   if(!xlate_and_clip(rb, &line, &col, &cols, NULL))
902     return;
903 
904   if(rb->cells[line][col].maskdepth > -1)
905     return;
906 
907   RBCell *cell = &rb->cells[line][col];
908   if(cell->state != LINE) {
909     make_span(rb, line, col, cols);
910     cell->state       = LINE;
911     cell->cols        = 1;
912     cell->pen         = tickit_pen_ref(rb->pen);
913     cell->v.line.mask = 0;
914   }
915   else if(!tickit_pen_equiv(cell->pen, rb->pen)) {
916     tickit_pen_unref(cell->pen);
917     cell->pen   = tickit_pen_ref(rb->pen);
918   }
919 
920   cell->v.line.mask |= bits;
921 }
922 
tickit_renderbuffer_hline_at(TickitRenderBuffer * rb,int line,int startcol,int endcol,TickitLineStyle style,TickitLineCaps caps)923 void tickit_renderbuffer_hline_at(TickitRenderBuffer *rb, int line, int startcol, int endcol,
924     TickitLineStyle style, TickitLineCaps caps)
925 {
926   DEBUG_LOGF(rb, "Bd", "HLine (%d..%d,%d)", startcol, endcol, line);
927 
928   int east = style << EAST_SHIFT;
929   int west = style << WEST_SHIFT;
930 
931   linecell(rb, line, startcol, east | (caps & TICKIT_LINECAP_START ? west : 0));
932   for(int col = startcol + 1; col <= endcol - 1; col++)
933     linecell(rb, line, col, east | west);
934   linecell(rb, line, endcol, (caps & TICKIT_LINECAP_END ? east : 0) | west);
935 }
936 
tickit_renderbuffer_vline_at(TickitRenderBuffer * rb,int startline,int endline,int col,TickitLineStyle style,TickitLineCaps caps)937 void tickit_renderbuffer_vline_at(TickitRenderBuffer *rb, int startline, int endline, int col,
938     TickitLineStyle style, TickitLineCaps caps)
939 {
940   DEBUG_LOGF(rb, "Bd", "VLine (%d,%d..%d)", col, startline, endline);
941 
942   int north = style << NORTH_SHIFT;
943   int south = style << SOUTH_SHIFT;
944 
945   linecell(rb, startline, col, south | (caps & TICKIT_LINECAP_START ? north : 0));
946   for(int line = startline + 1; line <= endline - 1; line++)
947     linecell(rb, line, col, south | north);
948   linecell(rb, endline, col, (caps & TICKIT_LINECAP_END ? south : 0) | north);
949 }
950 
tickit_renderbuffer_flush_to_term(TickitRenderBuffer * rb,TickitTerm * tt)951 void tickit_renderbuffer_flush_to_term(TickitRenderBuffer *rb, TickitTerm *tt)
952 {
953   DEBUG_LOGF(rb, "Bf", "Flush to term");
954 
955   for(int line = 0; line < rb->lines; line++) {
956     int phycol = -1; /* column where the terminal cursor physically is */
957 
958     for(int col = 0; col < rb->cols; /**/) {
959       RBCell *cell = &rb->cells[line][col];
960 
961       if(cell->state == SKIP) {
962         col += cell->cols;
963         continue;
964       }
965 
966       if(phycol < col)
967         tickit_term_goto(tt, line, col);
968       phycol = col;
969 
970       switch(cell->state) {
971         case TEXT:
972           {
973             TickitStringPos start, end, limit;
974             const char *text = tickit_string_get(cell->v.text.s);
975 
976             tickit_stringpos_limit_columns(&limit, cell->v.text.offs);
977             tickit_utf8_count(text, &start, &limit);
978 
979             limit.columns += cell->cols;
980             end = start;
981             tickit_utf8_countmore(text, &end, &limit);
982 
983             tickit_term_setpen(tt, cell->pen);
984             tickit_term_printn(tt, text + start.bytes, end.bytes - start.bytes);
985 
986             phycol += cell->cols;
987           }
988           break;
989         case ERASE:
990           {
991             /* No need to set moveend=true to erasech unless we actually
992              * have more content */
993             int moveend = col + cell->cols < rb->cols &&
994                           rb->cells[line][col + cell->cols].state != SKIP;
995 
996             tickit_term_setpen(tt, cell->pen);
997             tickit_term_erasech(tt, cell->cols, moveend ? TICKIT_YES : TICKIT_MAYBE);
998 
999             if(moveend)
1000               phycol += cell->cols;
1001             else
1002               phycol = -1;
1003           }
1004           break;
1005         case LINE:
1006           {
1007             TickitPen *pen = cell->pen;
1008 
1009             do {
1010               tmp_cat_utf8(rb, linemask_to_char[cell->v.line.mask]);
1011 
1012               col++;
1013               phycol += cell->cols;
1014             } while(col < rb->cols &&
1015                     (cell = &rb->cells[line][col]) &&
1016                     cell->state == LINE &&
1017                     tickit_pen_equiv(cell->pen, pen));
1018 
1019             tickit_term_setpen(tt, pen);
1020             tickit_term_printn(tt, rb->tmp, rb->tmplen);
1021             rb->tmplen = 0;
1022           }
1023           continue; /* col already updated */
1024         case CHAR:
1025           {
1026             tmp_cat_utf8(rb, cell->v.chr.codepoint);
1027 
1028             tickit_term_setpen(tt, cell->pen);
1029             tickit_term_printn(tt, rb->tmp, rb->tmplen);
1030             rb->tmplen = 0;
1031 
1032             phycol += cell->cols;
1033           }
1034           break;
1035         case SKIP:
1036         case CONT:
1037           /* unreachable */
1038           abort();
1039       }
1040 
1041       col += cell->cols;
1042     }
1043   }
1044 
1045   tickit_renderbuffer_reset(rb);
1046 }
1047 
copyrect(TickitRenderBuffer * dst,const TickitRenderBuffer * src,const TickitRect * dstrect,const TickitRect * srcrect,bool copy_skip)1048 static void copyrect(TickitRenderBuffer *dst, const TickitRenderBuffer *src,
1049     const TickitRect *dstrect, const TickitRect *srcrect, bool copy_skip)
1050 {
1051   if(srcrect->lines == 0 || srcrect->cols == 0)
1052     return;
1053 
1054   /* TODO:
1055    *   * consider how this works in the presence of a translation offset
1056    *     defined on src
1057    */
1058   int lineoffs = dstrect->top  - srcrect->top,
1059       coloffs  = dstrect->left - srcrect->left;
1060 
1061   int bottom = tickit_rect_bottom(srcrect),
1062       right  = tickit_rect_right(srcrect);
1063 
1064   /* Several steps have to be done somewhat specially for copies into the same
1065    * RB
1066    */
1067   bool samerb = dst == src;
1068 
1069   if(samerb && lineoffs == 0 && coloffs == 0)
1070     return;
1071 
1072   /* iterate lines from the bottom upward if we're coping down in the same RB */
1073   bool upwards = samerb && (lineoffs > 0);
1074   /* iterate columns leftward if we're copying rightward in the same RB */
1075   bool leftwards = samerb && (lineoffs == 0) && (coloffs > 0);
1076 
1077   for(int line = upwards ? bottom - 1           : srcrect->top;
1078                  upwards ? line >= srcrect->top : line < bottom;
1079                  upwards ? line--               : line++) {
1080     for(int col = leftwards ? right - 1            : srcrect->left;
1081                   leftwards ? col >= srcrect->left : col < right;
1082                               /**/) {
1083       RBCell *cell = &src->cells[line][col];
1084 
1085       int offset = 0;
1086 
1087       if(cell->state == CONT) {
1088         int startcol = cell->startcol;
1089         cell = &src->cells[line][startcol];
1090 
1091         if(leftwards) {
1092           col = startcol;
1093           if(col < srcrect->left)
1094             col = srcrect->left;
1095         }
1096 
1097         offset = col - startcol;
1098       }
1099 
1100       int cols = cell->cols;
1101 
1102       if(col + cols > tickit_rect_right(srcrect))
1103         cols = tickit_rect_right(srcrect) - col;
1104 
1105       if(cell->state != SKIP) {
1106         tickit_renderbuffer_savepen(dst);
1107         tickit_renderbuffer_setpen(dst, cell->pen);
1108       }
1109 
1110       switch(cell->state) {
1111         case SKIP:
1112           if(copy_skip)
1113             skip(dst, line + lineoffs, col + coloffs,
1114                 cols);
1115           break;
1116         case TEXT:
1117           {
1118             TickitStringPos start, end, limit;
1119             const char *text = tickit_string_get(cell->v.text.s);
1120 
1121             tickit_stringpos_limit_columns(&limit, cell->v.text.offs + offset);
1122             tickit_utf8_count(text, &start, &limit);
1123 
1124             limit.columns += cols;
1125             end = start;
1126             tickit_utf8_countmore(text, &end, &limit);
1127 
1128             if(start.bytes > 0 || end.bytes < tickit_string_len(cell->v.text.s))
1129               put_text(dst, line + lineoffs, col + coloffs,
1130                   text + start.bytes, end.bytes - start.bytes);
1131             else
1132               // We can just cheaply copy the entire string
1133               put_string(dst, line + lineoffs, col + coloffs,
1134                   cell->v.text.s);
1135           }
1136           break;
1137         case ERASE:
1138           erase(dst, line + lineoffs, col + coloffs,
1139               cols);
1140           break;
1141         case LINE:
1142           linecell(dst, line + lineoffs, col + coloffs,
1143               cell->v.line.mask);
1144           break;
1145         case CHAR:
1146           put_char(dst, line + lineoffs, col + coloffs,
1147               cell->v.chr.codepoint);
1148           break;
1149         case CONT:
1150           /* unreachable */
1151           abort();
1152       }
1153 
1154       if(cell->state != SKIP)
1155         tickit_renderbuffer_restore(dst);
1156 
1157       if(leftwards)
1158         col--; /* we'll jump back to the beginning of a CONT region on the
1159                   next iteration
1160                 */
1161       else
1162         col += cell->cols;
1163     }
1164   }
1165 }
1166 
tickit_renderbuffer_blit(TickitRenderBuffer * dst,const TickitRenderBuffer * src)1167 void tickit_renderbuffer_blit(TickitRenderBuffer *dst, const TickitRenderBuffer *src)
1168 {
1169   copyrect(dst, src,
1170       &(TickitRect){ .top = 0, .left = 0, .lines = src->lines, .cols = src->cols },
1171       &(TickitRect){ .top = 0, .left = 0, .lines = src->lines, .cols = src->cols },
1172       false);
1173 }
1174 
tickit_renderbuffer_copyrect(TickitRenderBuffer * rb,const TickitRect * dest,const TickitRect * src)1175 void tickit_renderbuffer_copyrect(TickitRenderBuffer *rb, const TickitRect *dest, const TickitRect *src)
1176 {
1177   copyrect(rb, rb, dest, src, true);
1178 }
1179 
tickit_renderbuffer_moverect(TickitRenderBuffer * rb,const TickitRect * dest,const TickitRect * src)1180 void tickit_renderbuffer_moverect(TickitRenderBuffer *rb, const TickitRect *dest, const TickitRect *src)
1181 {
1182   copyrect(rb, rb, dest, src, true);
1183 
1184   /* Calculate what area of the RB needs skipping due to move */
1185   TickitRectSet *cleararea = tickit_rectset_new();
1186   tickit_rectset_add(cleararea, src);
1187   tickit_rectset_subtract(cleararea, &(TickitRect){
1188       .top = dest->top, .left = dest->left, .lines = src->lines, .cols = src->cols});
1189 
1190   size_t n = tickit_rectset_rects(cleararea);
1191   for(size_t i = 0; i < n; i++) {
1192     TickitRect rect;
1193     tickit_rectset_get_rect(cleararea, i, &rect);
1194 
1195     tickit_renderbuffer_skiprect(rb, &rect);
1196   }
1197 
1198   tickit_rectset_destroy(cleararea);
1199 }
1200 
get_span(TickitRenderBuffer * rb,int line,int col,int * offset)1201 static RBCell *get_span(TickitRenderBuffer *rb, int line, int col, int *offset)
1202 {
1203   int cols = 1;
1204   if(!xlate_and_clip(rb, &line, &col, &cols, NULL))
1205     return NULL;
1206 
1207   *offset = 0;
1208   RBCell *cell = &rb->cells[line][col];
1209   if(cell->state == CONT) {
1210     *offset = col - cell->startcol;
1211     cell = &rb->cells[line][cell->startcol];
1212   }
1213 
1214   return cell;
1215 }
1216 
get_span_text(TickitRenderBuffer * rb,RBCell * span,int offset,int one_grapheme,char * buffer,size_t len)1217 static size_t get_span_text(TickitRenderBuffer *rb, RBCell *span, int offset, int one_grapheme, char *buffer, size_t len)
1218 {
1219   size_t bytes;
1220 
1221   switch(span->state) {
1222     case CONT: // should be unreachable
1223       return -1;
1224 
1225     case SKIP:
1226     case ERASE:
1227       bytes = 0;
1228       break;
1229 
1230     case TEXT:
1231       {
1232         const char *text = tickit_string_get(span->v.text.s);
1233         TickitStringPos start, end, limit;
1234 
1235         tickit_stringpos_limit_columns(&limit, span->v.text.offs + offset);
1236         tickit_utf8_count(text, &start, &limit);
1237 
1238         if(one_grapheme)
1239           tickit_stringpos_limit_graphemes(&limit, start.graphemes + 1);
1240         else
1241           tickit_stringpos_limit_columns(&limit, span->cols);
1242         end = start;
1243         tickit_utf8_countmore(text, &end, &limit);
1244 
1245         bytes = end.bytes - start.bytes;
1246 
1247         if(buffer) {
1248           if(len < bytes)
1249             return -1;
1250           strncpy(buffer, text + start.bytes, bytes);
1251           buffer[bytes] = 0;
1252         }
1253         break;
1254       }
1255     case LINE:
1256       bytes = tickit_utf8_put(buffer, len, linemask_to_char[span->v.line.mask]);
1257       break;
1258 
1259     case CHAR:
1260       bytes = tickit_utf8_put(buffer, len, span->v.chr.codepoint);
1261       break;
1262   }
1263 
1264   if(buffer && len > bytes)
1265     buffer[bytes] = 0;
1266 
1267   return bytes;
1268 }
1269 
tickit_renderbuffer_get_cell_active(TickitRenderBuffer * rb,int line,int col)1270 int tickit_renderbuffer_get_cell_active(TickitRenderBuffer *rb, int line, int col)
1271 {
1272   int offset;
1273   RBCell *span = get_span(rb, line, col, &offset);
1274   if(!span)
1275     return -1;
1276 
1277   return span->state != SKIP;
1278 }
1279 
tickit_renderbuffer_get_cell_text(TickitRenderBuffer * rb,int line,int col,char * buffer,size_t len)1280 size_t tickit_renderbuffer_get_cell_text(TickitRenderBuffer *rb, int line, int col, char *buffer, size_t len)
1281 {
1282   int offset;
1283   RBCell *span = get_span(rb, line, col, &offset);
1284   if(!span || span->state == CONT)
1285     return -1;
1286 
1287   return get_span_text(rb, span, offset, 1, buffer, len);
1288 }
1289 
tickit_renderbuffer_get_cell_linemask(TickitRenderBuffer * rb,int line,int col)1290 TickitRenderBufferLineMask tickit_renderbuffer_get_cell_linemask(TickitRenderBuffer *rb, int line, int col)
1291 {
1292   int offset;
1293   RBCell *span = get_span(rb, line, col, &offset);
1294   if(!span || span->state != LINE)
1295     return (TickitRenderBufferLineMask){ 0 };
1296 
1297   return (TickitRenderBufferLineMask){
1298     .north = (span->v.line.mask >> NORTH_SHIFT) & 0x03,
1299     .south = (span->v.line.mask >> SOUTH_SHIFT) & 0x03,
1300     .east  = (span->v.line.mask >> EAST_SHIFT ) & 0x03,
1301     .west  = (span->v.line.mask >> WEST_SHIFT ) & 0x03,
1302   };
1303 }
1304 
tickit_renderbuffer_get_cell_pen(TickitRenderBuffer * rb,int line,int col)1305 TickitPen *tickit_renderbuffer_get_cell_pen(TickitRenderBuffer *rb, int line, int col)
1306 {
1307   int offset;
1308   RBCell *span = get_span(rb, line, col, &offset);
1309   if(!span || span->state == SKIP)
1310     return NULL;
1311 
1312   return span->pen;
1313 }
1314 
tickit_renderbuffer_get_span(TickitRenderBuffer * rb,int line,int startcol,struct TickitRenderBufferSpanInfo * info,char * text,size_t len)1315 size_t tickit_renderbuffer_get_span(TickitRenderBuffer *rb, int line, int startcol, struct TickitRenderBufferSpanInfo *info, char *text, size_t len)
1316 {
1317   int offset;
1318   RBCell *span = get_span(rb, line, startcol, &offset);
1319   if(!span || span->state == CONT)
1320     return -1;
1321 
1322   if(info)
1323     info->n_columns = span->cols - offset;
1324 
1325   if(span->state == SKIP) {
1326     if(info)
1327       info->is_active = 0;
1328     return 0;
1329   }
1330 
1331   if(info)
1332     info->is_active = 1;
1333 
1334   if(info && info->pen) {
1335     tickit_pen_clear(info->pen);
1336     tickit_pen_copy(info->pen, span->pen, 1);
1337   }
1338 
1339   size_t retlen = get_span_text(rb, span, offset, 0, text, len);
1340   if(info) {
1341     info->len = retlen;
1342     info->text = text;
1343   }
1344   return len;
1345 }
1346