1// Copyright 2016 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "textcon.h"
6
7#include <assert.h>
8#include <string.h>
9#include <zircon/compiler.h>
10
11static inline void invalidate(textcon_t* tc, int x, int y, int w, int h) {
12    tc->invalidate(tc->cookie, x, y, w, h);
13}
14static inline void movecursor(textcon_t* tc, int x, int y) {
15    tc->movecursor(tc->cookie, x, y);
16}
17static inline void push_scrollback_line(textcon_t* tc, int y) {
18    tc->push_scrollback_line(tc->cookie, y);
19}
20static inline void setparam(textcon_t* tc, int param, uint8_t* arg,
21                            size_t arglen) {
22    tc->setparam(tc->cookie, param, arg, arglen);
23}
24
25// Construct a vc_char_t from the given character using the current colors.
26static inline vc_char_t make_vc_char(textcon_t* tc, uint8_t ch) {
27    return (vc_char_t)(ch |
28                       (((vc_char_t)tc->fg & 15) << 8) |
29                       (((vc_char_t)tc->bg & 15) << 12));
30}
31
32static vc_char_t* dataxy(textcon_t* tc, int x, int y) {
33    assert(x >= 0);
34    assert(x < tc->w);
35    assert(y >= 0);
36    assert(y < tc->h);
37    return tc->data + y * tc->w + x;
38}
39
40static vc_char_t* get_start_of_line(textcon_t* tc, int y) {
41    assert(y >= 0);
42    assert(y <= tc->h);
43    return tc->data + y * tc->w;
44}
45
46static int clampx(textcon_t* tc, int x) {
47    return x < 0 ? 0 : x >= tc->w ? tc->w - 1 : x;
48}
49
50static int clampxatedge(textcon_t* tc, int x) {
51    return x < 0 ? 0 : x > tc->w ? tc->w : x;
52}
53
54static int clampy(textcon_t* tc, int y) {
55    return y < 0 ? 0 : y >= tc->h ? tc->h - 1 : y;
56}
57
58static void moveto(textcon_t* tc, int x, int y) {
59    tc->x = clampx(tc, x);
60    tc->y = clampy(tc, y);
61}
62
63static inline void moverel(textcon_t* tc, int dx, int dy) {
64    moveto(tc, tc->x + dx, tc->y + dy);
65}
66
67static void fill(vc_char_t* ptr, vc_char_t val, size_t count) {
68    while (count-- > 0) {
69        *ptr++ = val;
70    }
71}
72
73static void erase_region(textcon_t* tc, int x0, int y0, int x1, int y1) {
74    if (x0 >= tc->w) {
75        return;
76    }
77    x1 = clampx(tc, x1);
78    vc_char_t* ptr = dataxy(tc, x0, y0);
79    vc_char_t* end = dataxy(tc, x1, y1) + 1;
80    fill(ptr, make_vc_char(tc, ' '), end - ptr);
81    invalidate(tc, x0, y0, x1 - x0 + 1, y1 - y0 + 1);
82}
83
84static void erase_screen(textcon_t* tc, int arg) {
85    switch (arg) {
86    case 0: // erase downward
87        erase_region(tc, tc->x, tc->y, tc->w - 1, tc->h - 1);
88        break;
89    case 1: // erase upward
90        erase_region(tc, 0, 0, tc->x, tc->y);
91        break;
92    case 2: // erase all
93        erase_region(tc, 0, 0, tc->w - 1, tc->h - 1);
94        break;
95    }
96}
97
98static void erase_line(textcon_t* tc, int arg) {
99    switch (arg) {
100    case 0: // erase to eol
101        erase_region(tc, tc->x, tc->y, tc->w - 1, tc->y);
102        break;
103    case 1: // erase from bol
104        erase_region(tc, 0, tc->y, tc->x, tc->y);
105        break;
106    case 2: // erase line
107        erase_region(tc, 0, tc->y, tc->w - 1, tc->y);
108        break;
109    }
110}
111
112static void erase_chars(textcon_t* tc, int arg) {
113    if (tc->x >= tc->w) {
114        return;
115    }
116    if (arg < 0) {
117        arg = 0;
118    }
119    if (arg > tc->w) {
120        arg = tc->w;
121    }
122
123    vc_char_t* dst = dataxy(tc, tc->x, tc->y);
124    vc_char_t* src = dataxy(tc, tc->x + arg, tc->y);
125    vc_char_t* end = dataxy(tc, tc->x + tc->w, tc->y);
126
127    while (src < end) {
128        *dst++ = *src++;
129    }
130    while (dst < end) {
131        *dst++ = make_vc_char(tc, ' ');
132    }
133
134    invalidate(tc, tc->x, tc->y, tc->w - tc->x, 1);
135}
136
137void tc_copy_lines(textcon_t* tc, int y_dest, int y_src, int line_count) {
138    vc_char_t* dest = get_start_of_line(tc, y_dest);
139    vc_char_t* src = get_start_of_line(tc, y_src);
140    memmove(dest, src, line_count * tc->w * sizeof(vc_char_t));
141}
142
143static void clear_lines(textcon_t* tc, int y, int line_count) {
144    fill(get_start_of_line(tc, y), make_vc_char(tc, ' '), line_count * tc->w);
145    invalidate(tc, 0, y, tc->w, line_count);
146}
147
148// Scroll the region between line |y0| (inclusive) and |y1| (exclusive).
149// Scroll by |diff| lines, which may be positive (for moving lines up) or
150// negative (for moving lines down).
151static void scroll_lines(textcon_t* tc, int y0, int y1, int diff) {
152    int delta = diff > 0 ? diff : -diff;
153    if (delta > y1 - y0)
154        delta = y1 - y0;
155    int copy_count = y1 - y0 - delta;
156    if (diff > 0) {
157        // Scroll up.
158        for (int i = 0; i < delta; ++i) {
159            push_scrollback_line(tc, y0 + i);
160        }
161        tc->copy_lines(tc->cookie, y0, y0 + delta, copy_count);
162        clear_lines(tc, y0 + copy_count, delta);
163    } else {
164        // Scroll down.
165        tc->copy_lines(tc->cookie, y0 + delta, y0, copy_count);
166        clear_lines(tc, y0, delta);
167    }
168}
169
170static void scroll_up(textcon_t* tc) {
171    scroll_lines(tc, tc->scroll_y0, tc->scroll_y1, 1);
172}
173
174// positive = up, negative = down
175static void scroll_at_pos(textcon_t* tc, int dir) {
176    if (tc->y < tc->scroll_y0)
177        return;
178    if (tc->y >= tc->scroll_y1)
179        return;
180
181    scroll_lines(tc, tc->y, tc->scroll_y1, dir);
182}
183
184void set_scroll(textcon_t* tc, int y0, int y1) {
185    if (y0 > y1) {
186        return;
187    }
188    tc->scroll_y0 = (y0 < 0) ? 0 : y0;
189    tc->scroll_y1 = (y1 > tc->h) ? tc->h : y1;
190}
191
192static void savecursorpos(textcon_t* tc) {
193    tc->save_x = tc->x;
194    tc->save_y = tc->y;
195}
196
197static void restorecursorpos(textcon_t* tc) {
198    tc->x = clampxatedge(tc, tc->save_x);
199    tc->y = clampy(tc, tc->save_y);
200}
201
202static void putc_plain(textcon_t* tc, uint8_t c);
203static void putc_escape2(textcon_t* tc, uint8_t c);
204
205static void putc_ignore(textcon_t* tc, uint8_t c) {
206    tc->putc = putc_plain;
207}
208
209static void putc_param(textcon_t* tc, uint8_t c) {
210    switch (c) {
211    case '0':
212    case '1':
213    case '2':
214    case '3':
215    case '4':
216    case '5':
217    case '6':
218    case '7':
219    case '8':
220    case '9':
221        tc->num = tc->num * 10 + (c - '0');
222        return;
223    case ';':
224        if (tc->argn_count < TC_MAX_ARG) {
225            tc->argn[tc->argn_count++] = tc->num;
226        }
227        tc->putc = putc_escape2;
228        break;
229    default:
230        if (tc->argn_count < TC_MAX_ARG) {
231            tc->argn[tc->argn_count++] = tc->num;
232        }
233        tc->putc = putc_escape2;
234        putc_escape2(tc, c);
235        break;
236    }
237}
238
239#define ARG0(def) ((tc->argn_count > 0) ? tc->argn[0] : (def))
240#define ARG1(def) ((tc->argn_count > 1) ? tc->argn[1] : (def))
241
242static void putc_dec(textcon_t* tc, uint8_t c) {
243    switch (c) {
244    case '0':
245    case '1':
246    case '2':
247    case '3':
248    case '4':
249    case '5':
250    case '6':
251    case '7':
252    case '8':
253    case '9':
254        tc->num = tc->num * 10 + (c - '0');
255        return;
256    case 'h':
257        if (tc->num == 25)
258            setparam(tc, TC_SHOW_CURSOR, NULL, 0);
259        break;
260    case 'l':
261        if (tc->num == 25)
262            setparam(tc, TC_HIDE_CURSOR, NULL, 0);
263        break;
264    default:
265        putc_plain(tc, c);
266        break;
267    }
268    tc->putc = putc_plain;
269}
270
271static textcon_param_t osc_to_param(int osc) {
272    switch (osc) {
273    case 2:
274        return TC_SET_TITLE;
275    default:
276        return TC_INVALID;
277    }
278}
279
280static void putc_osc2(textcon_t* tc, uint8_t c) {
281    switch (c) {
282    case 7: { // end command
283        textcon_param_t param = osc_to_param(ARG0(-1));
284        if (param != TC_INVALID && tc->argstr_size)
285            setparam(tc, param, tc->argstr, tc->argstr_size);
286        tc->putc = putc_plain;
287        break;
288    }
289    default:
290        if (tc->argstr_size < TC_MAX_ARG_LENGTH)
291            tc->argstr[tc->argstr_size++] = c;
292        break;
293    }
294}
295
296static void putc_osc(textcon_t* tc, uint8_t c) {
297    switch (c) {
298    case '0':
299    case '1':
300    case '2':
301    case '3':
302    case '4':
303    case '5':
304    case '6':
305    case '7':
306    case '8':
307    case '9':
308        tc->num = tc->num * 10 + (c - '0');
309        return;
310    case ';':
311        if (tc->argn_count < TC_MAX_ARG) {
312            tc->argn[tc->argn_count++] = tc->num;
313        }
314        memset(tc->argstr, 0, TC_MAX_ARG_LENGTH);
315        tc->argstr_size = 0;
316        tc->putc = putc_osc2;
317        break;
318    default:
319        if (tc->argn_count < TC_MAX_ARG) {
320            tc->argn[tc->argn_count++] = tc->num;
321        }
322        tc->putc = putc_osc2;
323        putc_osc2(tc, c);
324        break;
325    }
326}
327
328static void putc_escape2(textcon_t* tc, uint8_t c) {
329    int x, y;
330    switch (c) {
331    case '0':
332    case '1':
333    case '2':
334    case '3':
335    case '4':
336    case '5':
337    case '6':
338    case '7':
339    case '8':
340    case '9':
341        tc->num = c - '0';
342        tc->putc = putc_param;
343        return;
344    case ';': // end parameter
345        if (tc->argn_count < TC_MAX_ARG) {
346            tc->argn[tc->argn_count++] = 0;
347        }
348        return;
349    case '?':
350        tc->num = 0;
351        tc->argn_count = 0;
352        tc->putc = putc_dec;
353        return;
354    case 'A': // (CUU) Cursor Up
355        moverel(tc, 0, -ARG0(1));
356        break;
357    case 'B': // (CUD) Cursor Down
358        moverel(tc, 0, ARG0(1));
359        break;
360    case 'C': // (CUF) Cursor Forward
361        moverel(tc, ARG0(1), 0);
362        break;
363    case 'D': // (CUB) Cursor Backward
364        moverel(tc, -ARG0(1), 0);
365        break;
366    case 'E':
367        moveto(tc, 0, tc->y + ARG0(1));
368        break;
369    case 'F':
370        moveto(tc, 0, tc->y - ARG0(1));
371        break;
372    case 'G': // move xpos absolute
373        x = ARG0(1);
374        moveto(tc, x ? (x - 1) : 0, tc->y);
375        break;
376    case 'H': // (CUP) Cursor Position
377    case 'f': // (HVP) Horizontal and Vertical Position
378        x = ARG1(1);
379        y = ARG0(1);
380        moveto(tc, x ? (x - 1) : 0, y ? (y - 1) : 0);
381        break;
382    case 'J': // (ED) erase in display
383        erase_screen(tc, ARG0(0));
384        break;
385    case 'K': // (EL) erase in line
386        erase_line(tc, ARG0(0));
387        break;
388    case 'L': // (IL) insert line(s) at cursor
389        scroll_at_pos(tc, -ARG0(1));
390        break;
391    case 'M': // (DL) delete line(s) at cursor
392        scroll_at_pos(tc, ARG0(1));
393        break;
394    case 'P': // (DCH) delete character(s)
395        erase_chars(tc, ARG0(1));
396        break;
397    case 'd': // move ypos absolute
398        y = ARG0(1);
399        moveto(tc, tc->x, y ? (y - 1) : 0);
400        break;
401    case 'm': // (SGR) Character Attributes
402        if (tc->argn_count == 0) { // no params == default param
403            tc->argn[0] = 0;
404            tc->argn_count = 1;
405        }
406        for (int i = 0; i < tc->argn_count; i++) {
407            int n = tc->argn[i];
408            if ((n >= 30) && (n <= 37)) { // set fg
409                tc->fg = (uint8_t)(n - 30);
410            } else if ((n >= 40) && (n <= 47)) { // set bg
411                tc->bg = (uint8_t)(n - 40);
412            } else if ((n == 1) && (tc->fg <= 7)) { // bold
413                tc->fg = (uint8_t)(tc->fg + 8);
414            } else if (n == 0) { // reset
415                tc->fg = 0;
416                tc->bg = 15;
417            } else if (n == 7) { // reverse
418                uint8_t temp = tc->fg;
419                tc->fg = tc->bg;
420                tc->bg = temp;
421            } else if (n == 39) { // default fg
422                tc->fg = 0;
423            } else if (n == 49) { // default bg
424                tc->bg = 15;
425            }
426        }
427        break;
428    case 'r': // set scroll region
429        set_scroll(tc, ARG0(1) - 1, ARG1(tc->h));
430        break;
431    case 's': // save cursor position ??
432        savecursorpos(tc);
433        break;
434    case 'u': // restore cursor position ??
435        restorecursorpos(tc);
436        break;
437    case '@': // (ICH) Insert Blank Character(s)
438    case 'T': // Initiate Hilight Mouse Tracking (xterm)
439    case 'c': // (DA) Send Device Attributes
440    case 'g': // (TBC) Tab Clear
441    case 'h': // (SM) Set Mode  (4=Insert,20=AutoNewline)
442    case 'l': // (RM) Reset Mode (4=Replace,20=NormalLinefeed)
443    case 'n': // (DSR) Device Status Report
444    case 'x': // Request Terminal Parameters
445    default:
446        break;
447    }
448    movecursor(tc, tc->x, tc->y);
449    tc->putc = putc_plain;
450}
451
452static void putc_escape(textcon_t* tc, uint8_t c) {
453    switch (c) {
454    case 27: // escape
455        return;
456    case '(':
457    case ')':
458    case '*':
459    case '+':
460        // select various character sets
461        tc->putc = putc_ignore;
462        return;
463    case '[':
464        tc->num = 0;
465        tc->argn_count = 0;
466        tc->putc = putc_escape2;
467        return;
468    case ']':
469        tc->num = 0;
470        tc->argn_count = 0;
471        tc->putc = putc_osc;
472        return;
473    case '7': // (DECSC) Save Cursor
474        savecursorpos(tc);
475        // save attribute
476        break;
477    case '8': // (DECRC) Restore Cursor
478        restorecursorpos(tc);
479        movecursor(tc, tc->x, tc->y);
480        break;
481    case 'E': // (NEL) Next Line
482        tc->x = 0;
483        __FALLTHROUGH;
484    case 'D': // (IND) Index
485        tc->y++;
486        if (tc->y >= tc->scroll_y1) {
487            tc->y--;
488            scroll_up(tc);
489        }
490        movecursor(tc, tc->x, tc->y);
491        break;
492    case 'M': // (RI) Reverse Index)
493        tc->y--;
494        if (tc->y < tc->scroll_y0) {
495            tc->y++;
496            scroll_at_pos(tc, -1);
497        }
498        movecursor(tc, tc->x, tc->y);
499        break;
500    }
501    tc->putc = putc_plain;
502}
503
504static void putc_cr(textcon_t* tc) {
505    tc->x = 0;
506}
507
508static void putc_lf(textcon_t* tc) {
509    tc->y++;
510    if (tc->y >= tc->scroll_y1) {
511        tc->y--;
512        scroll_up(tc);
513    }
514}
515
516static void putc_plain(textcon_t* tc, uint8_t c) {
517    switch (c) {
518    case 7: // bell
519        break;
520    case 8: // backspace / ^H
521        if (tc->x > 0)
522            tc->x--;
523        break;
524    case 9: // tab / ^I
525        moveto(tc, (tc->x + 8) & (~7), tc->y);
526        break;
527    case 10:         // newline
528        putc_cr(tc); // should we imply this?
529        putc_lf(tc);
530        break;
531    case 12:
532        erase_screen(tc, 2);
533        break;
534    case 13: // carriage return
535        putc_cr(tc);
536        break;
537    case 27: // escape
538        tc->putc = putc_escape;
539        return;
540    default:
541        if ((c < ' ') || (c > 127)) {
542            return;
543        }
544        if (tc->x >= tc->w) {
545            // apply deferred line wrap upon printing first character beyond
546            // end of current line
547            putc_cr(tc);
548            putc_lf(tc);
549        }
550        dataxy(tc, tc->x, tc->y)[0] = make_vc_char(tc, c);
551        invalidate(tc, tc->x, tc->y, 1, 1);
552        tc->x++;
553        break;
554    }
555    movecursor(tc, tc->x, tc->y);
556}
557
558void tc_init(textcon_t* tc, int w, int h, vc_char_t* data,
559             uint8_t fg, uint8_t bg, int cursor_x, int cursor_y) {
560    tc->w = w;
561    tc->h = h;
562    tc->x = cursor_x;
563    tc->y = cursor_y;
564    tc->data = data;
565    tc->scroll_y0 = 0;
566    tc->scroll_y1 = h;
567    tc->save_x = 0;
568    tc->save_y = 0;
569    tc->fg = fg;
570    tc->bg = bg;
571    tc->putc = putc_plain;
572}
573
574void tc_seth(textcon_t* tc, int h) {
575    // tc->data must be big enough for the new height
576    int old_h = h;
577    tc->h = h;
578
579    // move contents
580    int y = 0;
581    if (old_h > h) {
582        vc_char_t* dst = dataxy(tc, 0, tc->scroll_y0);
583        vc_char_t* src = dataxy(tc, 0, tc->scroll_y0 + old_h - h);
584        vc_char_t* end = dataxy(tc, 0, tc->scroll_y1);
585        do {
586            push_scrollback_line(tc, y);
587        } while (++y < old_h - h);
588        memmove(dst, src, (end - dst) * sizeof(vc_char_t));
589        tc->y -= old_h - h;
590    } else if (old_h < h) {
591        do {
592            fill(dataxy(tc, 0, tc->scroll_y1 + y), make_vc_char(tc, ' '), tc->w);
593        } while (++y < h - old_h);
594    }
595    tc->y = clampy(tc, tc->y);
596
597    // try to fixup the scroll region
598    if (tc->scroll_y0 >= h) {
599        tc->scroll_y0 = 0;
600    }
601    if (tc->scroll_y1 == old_h) {
602        tc->scroll_y1 = h;
603    } else {
604        tc->scroll_y1 = tc->scroll_y1 >= h ? h : tc->scroll_y1;
605    }
606
607    invalidate(tc, 0, 0, tc->w, tc->h);
608    movecursor(tc, tc->x, tc->y);
609}
610