1 /*
2  * Copyright (C) 2014 haru <uobikiemukot at gmail dot com>
3  * Copyright (C) 2014 Hayaki Saito <user@zuse.jp>
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include "config.h"
20 #include "yaft.h"
21 #include "util.h"
22 #include "terminal.h"
23 #include "wcwidth.h"
24 
25 #include <stdio.h>
26 #if HAVE_STDLIB_H
27 # include <stdlib.h>
28 #endif
29 #if HAVE_STRING_H
30 # include <string.h>
31 #endif
32 
33 #if !defined(HAVE_MEMMOVE)
34 # define memmove(d, s, n) (bcopy ((s), (d), (n)))
35 #endif
36 
37 /* See LICENSE for licence details. */
erase_cell(struct terminal * term,int y,int x)38 void erase_cell(struct terminal *term, int y, int x)
39 {
40     struct cell_t *cellp;
41 
42     cellp             = &term->cells[x + y * term->cols];
43     cellp->glyphp     = term->glyph_map[DEFAULT_CHAR];
44     cellp->color_pair = term->color_pair; /* bce */
45     cellp->attribute  = ATTR_RESET;
46     cellp->width      = HALF;
47     cellp->has_bitmap = false;
48 
49     term->line_dirty[y] = true;
50 }
51 
copy_cell(struct terminal * term,int dst_y,int dst_x,int src_y,int src_x)52 void copy_cell(struct terminal *term, int dst_y, int dst_x, int src_y, int src_x)
53 {
54     struct cell_t *dst, *src;
55 
56     dst = &term->cells[dst_x + dst_y * term->cols];
57     src = &term->cells[src_x + src_y * term->cols];
58 
59     if (src->width == NEXT_TO_WIDE)
60         return;
61     else if (src->width == WIDE && dst_x == (term->cols - 1))
62         erase_cell(term, dst_y, dst_x);
63     else {
64         *dst = *src;
65         if (src->width == WIDE) {
66             *(dst + 1) = *src;
67             (dst + 1)->width = NEXT_TO_WIDE;
68         }
69         term->line_dirty[dst_y] = true;
70     }
71 }
72 
set_cell(struct terminal * term,int y,int x,const struct glyph_t * glyphp)73 int set_cell(struct terminal *term, int y, int x, const struct glyph_t *glyphp)
74 {
75     struct cell_t cell, *cellp;
76     uint8_t color_tmp;
77 
78     cell.glyphp = glyphp;
79 
80     cell.color_pair.fg = (term->attribute & attr_mask[ATTR_BOLD] && term->color_pair.fg <= 7) ?
81         term->color_pair.fg + BRIGHT_INC: term->color_pair.fg;
82     cell.color_pair.bg = (term->attribute & attr_mask[ATTR_BLINK] && term->color_pair.bg <= 7) ?
83         term->color_pair.bg + BRIGHT_INC: term->color_pair.bg;
84 
85     if (term->attribute & attr_mask[ATTR_REVERSE]) {
86         color_tmp          = cell.color_pair.fg;
87         cell.color_pair.fg = cell.color_pair.bg;
88         cell.color_pair.bg = color_tmp;
89     }
90 
91     cell.attribute  = term->attribute;
92     cell.width      = glyphp->width;
93     cell.has_bitmap = false;
94 
95     cellp    = &term->cells[x + y * term->cols];
96     *cellp   = cell;
97     term->line_dirty[y] = true;
98 
99     if (cell.width == WIDE && x + 1 < term->cols) {
100         cellp        = &term->cells[x + 1 + y * term->cols];
101         *cellp       = cell;
102         cellp->width = NEXT_TO_WIDE;
103         return WIDE;
104     }
105     return HALF;
106 }
107 
scroll(struct terminal * term,int from,int to,int offset)108 void scroll(struct terminal *term, int from, int to, int offset)
109 {
110     int i, j, size, abs_offset;
111     struct cell_t *dst, *src;
112 
113     if (offset == 0 || from >= to)
114         return;
115 
116     if (DEBUG)
117         fprintf(stderr, "scroll from:%d to:%d offset:%d\n", from, to, offset);
118 
119     for (i = from; i <= to; i++)
120         term->line_dirty[i] = true;
121 
122     abs_offset = abs(offset);
123     size = sizeof(struct cell_t) * ((to - from + 1) - abs_offset) * term->cols;
124 
125     dst = term->cells + from * term->cols;
126     src = term->cells + (from + abs_offset) * term->cols;
127 
128     if (offset > 0) {
129         memmove(dst, src, size);
130         for (i = (to - offset + 1); i <= to; i++)
131             for (j = 0; j < term->cols; j++)
132                 erase_cell(term, i, j);
133     }
134     else {
135         memmove(src, dst, size);
136         for (i = from; i < from + abs_offset; i++)
137             for (j = 0; j < term->cols; j++)
138                 erase_cell(term, i, j);
139     }
140 }
141 
142 /* relative movement: cause scrolling */
move_cursor(struct terminal * term,int y_offset,int x_offset)143 void move_cursor(struct terminal *term, int y_offset, int x_offset)
144 {
145     int x, y, top, bottom;
146 
147     x = term->cursor.x + x_offset;
148     y = term->cursor.y + y_offset;
149 
150     top = term->scroll.top;
151     bottom = term->scroll.bottom;
152 
153     if (x < 0)
154         x = 0;
155     else if (x >= term->cols) {
156         if (term->mode & MODE_AMRIGHT)
157             term->wrap_occured = true;
158         x = term->cols - 1;
159     }
160     term->cursor.x = x;
161 
162     y = (y < 0) ? 0:
163         (y >= term->lines) ? term->lines - 1: y;
164 
165     if (term->cursor.y == top && y_offset < 0) {
166         y = top;
167         scroll(term, top, bottom, y_offset);
168     }
169     else if (term->cursor.y == bottom && y_offset > 0) {
170         y = bottom;
171         scroll(term, top, bottom, y_offset);
172     }
173     term->cursor.y = y;
174 }
175 
176 /* absolute movement: never scroll */
set_cursor(struct terminal * term,int y,int x)177 void set_cursor(struct terminal *term, int y, int x)
178 {
179     int top, bottom;
180 
181     if (term->mode & MODE_ORIGIN) {
182         top = term->scroll.top;
183         bottom = term->scroll.bottom;
184         y += term->scroll.top;
185     }
186     else {
187         top = 0;
188         bottom = term->lines - 1;
189     }
190 
191     x = (x < 0) ? 0: (x >= term->cols) ? term->cols - 1: x;
192     y = (y < top) ? top: (y > bottom) ? bottom: y;
193 
194     term->cursor.x = x;
195     term->cursor.y = y;
196     term->wrap_occured = false;
197 }
198 
drcsch(struct terminal * term,uint32_t code)199 const struct glyph_t *drcsch(struct terminal *term, uint32_t code)
200 {
201     /* DRCSMMv1
202         ESC ( SP <\xXX> <\xYY> ESC ( B
203         <===> U+10XXYY ( 0x40 <= 0xXX <=0x7E, 0x20 <= 0xYY <= 0x7F )
204     */
205     int ku, ten;
206 
207     ku  = (0xFF00 & code) >> 8;
208     ten = 0xFF & code;
209 
210     if (DEBUG)
211         fprintf(stderr, "drcs ku:0x%.2X ten:0x%.2X\n", ku, ten);
212 
213     if ((0x40 <= ku && ku <= 0x7E)
214         && (0x20 <= ten && ten <= 0x7F)
215         && (term->drcs[ku - 0x40] != NULL))
216         return &term->drcs[ku - 0x40][ten - 0x20]; /* sub each offset */
217     else {
218         if (DEBUG)
219             fprintf(stderr, "drcs char not found\n");
220         return term->glyph_map[SUBSTITUTE_HALF];
221     }
222 }
223 
addch(struct terminal * term,uint32_t code)224 void addch(struct terminal *term, uint32_t code)
225 {
226     int width;
227     const struct glyph_t *glyphp;
228 
229     if (DEBUG)
230         fprintf(stderr, "addch: U+%.4X\n", code);
231 
232     width = term->fn_wcwidth(code);
233 
234     if (width <= 0) /* zero width */
235         return;
236     else if (0x100000 <= code && code <= 0x10FFFD) /* Unicode private area: plane 16 */
237         glyphp = drcsch(term, code);
238     else if (code >= UCS2_CHARS /* yaft support only UCS2 */
239         || term->glyph_map[code] == NULL /* missing glyph */
240         || term->glyph_map[code]->width != width) /* width unmatch */
241         glyphp = (width == 1) ? term->glyph_map[SUBSTITUTE_HALF]: term->glyph_map[SUBSTITUTE_WIDE];
242     else
243         glyphp = term->glyph_map[code];
244 
245     if ((term->wrap_occured && term->cursor.x == term->cols - 1) /* folding */
246         || (glyphp->width == WIDE && term->cursor.x == term->cols - 1)) {
247         set_cursor(term, term->cursor.y, 0);
248         move_cursor(term, 1, 0);
249     }
250     term->wrap_occured = false;
251 
252     move_cursor(term, 0, set_cell(term, term->cursor.y, term->cursor.x, glyphp));
253 }
254 
reset_esc(struct terminal * term)255 void reset_esc(struct terminal *term)
256 {
257     if (DEBUG)
258         fprintf(stderr, "*esc reset*\n");
259 
260     term->esc.bp = term->esc.buf;
261     term->esc.state = STATE_RESET;
262 }
263 
push_esc(struct terminal * term,uint8_t ch)264 bool push_esc(struct terminal *term, uint8_t ch)
265 {
266     long offset;
267 
268     if ((term->esc.bp - term->esc.buf) >= term->esc.size) { /* buffer limit */
269         if (DEBUG)
270             fprintf(stderr, "escape sequence length >= %d, term.esc.buf reallocated\n", term->esc.size);
271         offset = term->esc.bp - term->esc.buf;
272         term->esc.buf  = erealloc(term->esc.buf, term->esc.size * 2);
273         term->esc.size *= 2;
274         term->esc.bp   = term->esc.buf + offset;
275     }
276 
277     /* ref: http://www.vt100.net/docs/vt102-ug/appendixd.html */
278     *term->esc.bp++ = ch;
279     if (term->esc.state == STATE_ESC) {
280         /* format:
281             ESC  I.......I F
282                  ' '  '/'  '0'  '~'
283             0x1B 0x20-0x2F 0x30-0x7E
284         */
285         if ('0' <= ch && ch <= '~')        /* final char */
286             return true;
287         else if (SPACE <= ch && ch <= '/') /* intermediate char */
288             return false;
289     }
290     else if (term->esc.state == STATE_CSI) {
291         /* format:
292             CSI       P.......P I.......I F
293             ESC  '['  '0'  '?'  ' '  '/'  '@'  '~'
294             0x1B 0x5B 0x30-0x3F 0x20-0x2F 0x40-0x7E
295         */
296         if ('@' <= ch && ch <= '~')
297             return true;
298         else if (SPACE <= ch && ch <= '?')
299             return false;
300     }
301     else {
302         /* format:
303             OSC       I.....I F
304             ESC  ']'          BEL  or ESC  '\'
305             0x1B 0x5D unknown 0x07 or 0x1B 0x5C
306             DCS       I....I  F
307             ESC  'P'          BEL  or ESC  '\'
308             0x1B 0x50 unknown 0x07 or 0x1B 0x5C
309         */
310         if (ch == BEL || (ch == BACKSLASH
311             && (term->esc.bp - term->esc.buf) >= 2 && *(term->esc.bp - 2) == ESC))
312             return true;
313         else if ((ch == ESC || ch == CR || ch == LF || ch == BS || ch == HT)
314             || (SPACE <= ch && ch <= '~'))
315             return false;
316     }
317 
318      /* invalid sequence */
319     reset_esc(term);
320     return false;
321 }
322 
reset_charset(struct terminal * term)323 void reset_charset(struct terminal *term)
324 {
325     term->charset.code = term->charset.count = term->charset.following_byte = 0;
326     term->charset.is_valid = true;
327 }
328 
reset(struct terminal * term)329 void reset(struct terminal *term)
330 {
331     int i, j;
332 
333     term->mode = MODE_RESET;
334     term->mode |= (MODE_CURSOR | MODE_AMRIGHT);
335     term->wrap_occured = false;
336 
337     term->scroll.top = 0;
338     term->scroll.bottom = term->lines - 1;
339 
340     term->cursor.x = term->cursor.y = 0;
341 
342     term->state.mode = term->mode;
343     term->state.cursor = term->cursor;
344     term->state.attribute = ATTR_RESET;
345 
346     term->color_pair.fg = term->default_fg;
347     term->color_pair.bg = term->default_bg;
348 
349     term->attribute = ATTR_RESET;
350 
351     for (i = 0; i < term->lines; i++) {
352         for (j = 0; j < term->cols; j++) {
353             erase_cell(term, i, j);
354             if ((j % term->tabwidth) == 0)
355                 term->tabstop[j] = true;
356             else
357                 term->tabstop[j] = false;
358         }
359         term->line_dirty[i] = true;
360     }
361 
362     reset_esc(term);
363     reset_charset(term);
364 }
365 
term_init(struct terminal * term,int width,int height,int foreground_color,int background_color,int cursor_color,int tabwidth,int cjkwidth)366 void term_init(struct terminal *term, int width, int height,
367                int foreground_color, int background_color,
368                int cursor_color, int tabwidth, int cjkwidth)
369 {
370     int i;
371     uint32_t code, gi;
372 
373     term->width  = width;
374     term->height = height;
375 
376     term->cols  = term->width / CELL_WIDTH;
377     term->lines = term->height / CELL_HEIGHT;
378 
379     term->default_fg   = foreground_color;
380     term->default_bg   = background_color;
381     term->cursor_color = cursor_color;
382 
383     term->tabwidth = tabwidth;
384     if (cjkwidth) {
385         term->fn_wcwidth = mk_wcwidth_cjk;
386     } else {
387         term->fn_wcwidth = mk_wcwidth;
388     }
389 
390     if (DEBUG)
391         fprintf(stderr, "width:%d height:%d cols:%d lines:%d\n",
392             width, height, term->cols, term->lines);
393 
394     term->line_dirty = (bool *) ecalloc(term->lines, sizeof(bool));
395     term->tabstop    = (bool *) ecalloc(term->cols, sizeof(bool));
396     term->cells      = (struct cell_t *) ecalloc(term->cols * term->lines, sizeof(struct cell_t));
397 
398     term->esc.buf  = (char *) ecalloc(1, ESCSEQ_SIZE);
399     term->esc.size = ESCSEQ_SIZE;
400 
401     /* initialize glyph map */
402     for (code = 0; code < UCS2_CHARS; code++)
403         term->glyph_map[code] = NULL;
404 
405     for (gi = 0; gi < sizeof(glyphs) / sizeof(struct glyph_t); gi++)
406         term->glyph_map[glyphs[gi].code] = &glyphs[gi];
407 
408     if (term->glyph_map[DEFAULT_CHAR] == NULL
409         || term->glyph_map[SUBSTITUTE_HALF] == NULL
410         || term->glyph_map[SUBSTITUTE_WIDE] == NULL)
411         fatal("cannot find DEFAULT_CHAR or SUBSTITUTE_HALF or SUBSTITUTE_HALF\n");
412 
413     /* initialize drcs */
414     for (i = 0; i < DRCS_CHARSETS; i++)
415         term->drcs[i] = NULL;
416 
417     /* allocate sixel buffer */
418     term->sixel.bitmap = (uint8_t *) ecalloc(width * height, BYTES_PER_PIXEL);
419 
420     reset(term);
421 }
422 
term_die(struct terminal * term)423 void term_die(struct terminal *term)
424 {
425     int i;
426 
427     free(term->line_dirty);
428     free(term->tabstop);
429     free(term->cells);
430     free(term->esc.buf);
431 
432     for (i = 0; i < DRCS_CHARSETS; i++)
433         if (term->drcs[i] != NULL)
434             free(term->drcs[i]);
435 
436     free(term->sixel.bitmap);
437 }
438 
439 /* emacs, -*- Mode: C; tab-width: 4; indent-tabs-mode: nil -*- */
440 /* vim: set expandtab ts=4 : */
441 /* EOF */
442