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