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