rewrite: update default dumb and smart prefixes
[elinks/elinks-j605.git] / src / terminal / draw.c
blobbce6e6f3f972e7a1ee569e8466e035054814a5de
1 /** Public terminal drawing API. Frontend for the screen image in memory.
2 * @file */
4 #ifdef HAVE_CONFIG_H
5 #include "config.h"
6 #endif
8 #include "elinks.h"
10 #include "bfu/dialog.h"
11 #include "config/options.h"
12 #include "intl/charsets.h"
13 #include "terminal/color.h"
14 #include "terminal/draw.h"
15 #include "terminal/screen.h"
16 #include "terminal/terminal.h"
17 #include "util/color.h"
18 #include "util/box.h"
20 /** Makes sure that @a x and @a y are within the dimensions of the terminal. */
21 #define check_range(term, x, y) \
22 do { \
23 int_bounds(&(x), 0, (term)->width - 1); \
24 int_bounds(&(y), 0, (term)->height - 1); \
25 } while (0)
27 #if SCREEN_COLOR_SIZE > 1
28 #define clear_screen_char_color(schar) \
29 do { memset((schar)->c.color, 0, SCREEN_COLOR_SIZE); } while (0)
30 #else
31 #define clear_screen_char_color(schar) \
32 do { (schar)->c.color[0] = 0; } while (0)
33 #endif
37 NONSTATIC_INLINE struct screen_char *
38 get_char(struct terminal *term, int x, int y)
40 assert(term && term->screen && term->screen->image);
41 if_assert_failed return NULL;
42 check_range(term, x, y);
44 return &term->screen->image[x + term->width * y];
47 void
48 draw_border_cross(struct terminal *term, int x, int y,
49 enum border_cross_direction dir, struct color_pair *color)
51 static const unsigned char border_trans[2][4] = {
52 /* Used for BORDER_X_{RIGHT,LEFT}: */
53 { BORDER_SVLINE, BORDER_SRTEE, BORDER_SLTEE },
54 /* Used for BORDER_X_{DOWN,UP}: */
55 { BORDER_SHLINE, BORDER_SDTEE, BORDER_SUTEE },
57 struct screen_char *screen_char = get_char(term, x, y);
58 unsigned int d;
60 if (!screen_char) return;
61 if (!(screen_char->attr & SCREEN_ATTR_FRAME)) return;
63 /* First check if there is already a horizontal/vertical line, so that
64 * we will have to replace with a T char. Example: if there is a '|'
65 * and the direction is right, replace with a '|-' T char.
67 * If this is not the case check if there is a T char and we are adding
68 * the direction so that we end up with a cross. Example : if there is
69 * a '|-' and the direction is left, replace with a '+' (cross) char. */
70 d = dir>>1;
71 if (screen_char->data == border_trans[d][0]) {
72 screen_char->data = border_trans[d][1 + (dir & 1)];
74 } else if (screen_char->data == border_trans[d][2 - (dir & 1)]) {
75 screen_char->data = BORDER_SCROSS;
78 set_term_color(screen_char, color, 0,
79 get_opt_int_tree(term->spec, "colors", NULL));
82 void
83 draw_border_char(struct terminal *term, int x, int y,
84 enum border_char border, struct color_pair *color)
86 struct screen_char *screen_char = get_char(term, x, y);
88 if (!screen_char) return;
90 screen_char->data = (unsigned char) border;
91 screen_char->attr = SCREEN_ATTR_FRAME;
92 set_term_color(screen_char, color, 0,
93 get_opt_int_tree(term->spec, "colors", NULL));
94 set_screen_dirty(term->screen, y, y);
97 void
98 draw_char_color(struct terminal *term, int x, int y, struct color_pair *color)
100 struct screen_char *screen_char = get_char(term, x, y);
102 if (!screen_char) return;
104 set_term_color(screen_char, color, 0,
105 get_opt_int_tree(term->spec, "colors", NULL));
106 set_screen_dirty(term->screen, y, y);
109 /*! The @a data parameter here is like screen_char.data: UCS-4 if the
110 * charset of the terminal is UTF-8 (possible only if CONFIG_UTF8 is
111 * defined), and a byte otherwise. */
112 void
113 #ifdef CONFIG_UTF8
114 draw_char_data(struct terminal *term, int x, int y, unicode_val_T data)
115 #else
116 draw_char_data(struct terminal *term, int x, int y, unsigned char data)
117 #endif /* CONFIG_UTF8 */
119 struct screen_char *screen_char = get_char(term, x, y);
121 if (!screen_char) return;
123 screen_char->data = data;
125 #ifdef CONFIG_UTF8
126 #ifdef CONFIG_DEBUG
127 /* Detect attempt to draw double-width char on the last
128 * column of terminal. The unicode_to_cell(data) call is
129 * in principle wrong if CONFIG_UTF8 is defined but the
130 * charset of the terminal is not UTF-8, because @data
131 * is then a byte in that charset; but unicode_to_cell
132 * returns 1 for U+0000...U+00FF so it's not a problem. */
133 if (unicode_to_cell(data) == 2 && x + 1 > term->width)
134 INTERNAL("Attempt to draw double-width glyph on last column!");
135 #endif /* CONFIG_DEBUG */
137 if (data == UCS_NO_CHAR)
138 screen_char->attr = 0;
139 #endif /* CONFIG_UTF8 */
141 set_screen_dirty(term->screen, y, y);
144 void
145 draw_space(struct terminal *term, int x, int y, struct screen_char *color)
147 struct screen_char *screen_char = get_char(term, x, y);
149 if (!screen_char) return;
151 screen_char->data = ' ';
152 if (color) screen_char->c = color->c;
155 /*! Used by viewer to copy over a document.
156 * When doing frame drawing @a x can be different than 0. */
157 void
158 draw_line(struct terminal *term, int x, int y, int l, struct screen_char *line)
160 struct screen_char *screen_char = get_char(term, x, y);
161 int size;
163 assert(line);
164 if_assert_failed return;
165 if (!screen_char) return;
167 size = int_min(l, term->width - x);
168 if (size == 0) return;
170 #ifdef CONFIG_UTF8
171 if (term->utf8_cp) {
172 struct screen_char *sc;
174 if (line[0].data == UCS_NO_CHAR && x == 0) {
175 unicode_val_T data_save;
177 sc = line;
178 data_save = sc->data;
179 sc->data = UCS_ORPHAN_CELL;
180 copy_screen_chars(screen_char, line, 1);
181 sc->data = data_save;
182 size--;
183 line++;
184 screen_char++;
187 /* Instead of displaying double-width character at last column
188 * display only UCS_ORPHAN_CELL. */
189 if (size - 1 > 0 && unicode_to_cell(line[size - 1].data) == 2) {
190 unicode_val_T data_save;
192 sc = &line[size - 1];
193 data_save = sc->data;
194 sc->data = UCS_ORPHAN_CELL;
195 copy_screen_chars(screen_char, line, size);
196 sc->data = data_save;
197 } else {
198 copy_screen_chars(screen_char, line, size);
200 } else
201 #endif
202 copy_screen_chars(screen_char, line, size);
203 set_screen_dirty(term->screen, y, y);
206 void
207 draw_border(struct terminal *term, struct box *box,
208 struct color_pair *color, int width)
210 static const enum border_char p1[] = {
211 BORDER_SULCORNER,
212 BORDER_SURCORNER,
213 BORDER_SDLCORNER,
214 BORDER_SDRCORNER,
215 BORDER_SVLINE,
216 BORDER_SHLINE,
218 static const enum border_char p2[] = {
219 BORDER_DULCORNER,
220 BORDER_DURCORNER,
221 BORDER_DDLCORNER,
222 BORDER_DDRCORNER,
223 BORDER_DVLINE,
224 BORDER_DHLINE,
226 const enum border_char *p = (width > 1) ? p2 : p1;
227 struct box borderbox;
229 set_box(&borderbox, box->x - 1, box->y - 1,
230 box->width + 2, box->height + 2);
232 if (borderbox.width > 2) {
233 struct box bbox;
235 /* Horizontal top border */
236 set_box(&bbox, box->x, borderbox.y, box->width, 1);
237 draw_box(term, &bbox, p[5], SCREEN_ATTR_FRAME, color);
239 /* Horizontal bottom border */
240 bbox.y += borderbox.height - 1;
241 draw_box(term, &bbox, p[5], SCREEN_ATTR_FRAME, color);
244 if (borderbox.height > 2) {
245 struct box bbox;
247 /* Vertical left border */
248 set_box(&bbox, borderbox.x, box->y, 1, box->height);
249 draw_box(term, &bbox, p[4], SCREEN_ATTR_FRAME, color);
251 /* Vertical right border */
252 bbox.x += borderbox.width - 1;
253 draw_box(term, &bbox, p[4], SCREEN_ATTR_FRAME, color);
256 if (borderbox.width > 1 && borderbox.height > 1) {
257 int right = borderbox.x + borderbox.width - 1;
258 int bottom = borderbox.y + borderbox.height - 1;
260 /* Upper left corner */
261 draw_border_char(term, borderbox.x, borderbox.y, p[0], color);
262 /* Upper right corner */
263 draw_border_char(term, right, borderbox.y, p[1], color);
264 /* Lower left corner */
265 draw_border_char(term, borderbox.x, bottom, p[2], color);
266 /* Lower right corner */
267 draw_border_char(term, right, bottom, p[3], color);
270 set_screen_dirty(term->screen, borderbox.y, borderbox.y + borderbox.height);
273 #ifdef CONFIG_UTF8
274 /** Checks cells left and right to the box for broken double-width chars.
275 * Replace it with UCS_ORPHAN_CELL.
277 * @verbatim
278 * 1+---+3
279 * 1|box|##4
280 * 1| |##4
281 * 1| |##4
282 * 1+---+##4
283 * 2#####4
284 * 1,2,3,4 - needs to be checked, # - shadow , +,-,| - border
285 * @endverbatim
287 void
288 fix_dwchar_around_box(struct terminal *term, struct box *box, int border,
289 int shadow_width, int shadow_height)
291 struct screen_char *schar;
292 int height, x, y;
294 if (!term->utf8_cp)
295 return;
297 /* 1 */
298 x = box->x - border - 1;
299 if (x > 0) {
300 y = box->y - border;
301 height = box->height + 2 * border;
303 schar = get_char(term, x, y);
304 for (;height--; schar += term->width)
305 if (unicode_to_cell(schar->data) == 2)
306 schar->data = UCS_ORPHAN_CELL;
309 /* 2 */
310 x = box->x - border + shadow_width - 1;
311 if (x > 0 && x < term->width) {
312 y = box->y + border + box->height;
313 height = shadow_height;
315 schar = get_char(term, x, y);
316 for (;height--; schar += term->width)
317 if (unicode_to_cell(schar->data) == 2)
318 schar->data = UCS_ORPHAN_CELL;
321 /* 3 */
322 x = box->x + box->width + border;
323 if (x < term->width) {
324 y = box->y - border;
325 height = shadow_height;
327 schar = get_char(term, x, y);
328 for (;height--; schar += term->width)
329 if (schar->data == UCS_NO_CHAR)
330 schar->data = UCS_ORPHAN_CELL;
333 /* 4 */
334 x = box->x + box->width + border + shadow_width;
335 if (x < term->width) {
336 y = box->y - border + shadow_height;
337 height = box->height + 2 * border;
339 schar = get_char(term, x, y);
340 for (;height--; schar += term->width)
341 if (schar->data == UCS_NO_CHAR)
342 schar->data = UCS_ORPHAN_CELL;
345 #endif
347 #ifdef CONFIG_UTF8
348 void
349 draw_char(struct terminal *term, int x, int y,
350 unicode_val_T data, enum screen_char_attr attr,
351 struct color_pair *color)
352 #else
353 void
354 draw_char(struct terminal *term, int x, int y,
355 unsigned char data, enum screen_char_attr attr,
356 struct color_pair *color)
357 #endif /* CONFIG_UTF8 */
359 struct screen_char *screen_char = get_char(term, x, y);
361 if (!screen_char) return;
363 screen_char->data = data;
364 screen_char->attr = attr;
365 set_term_color(screen_char, color, 0,
366 get_opt_int_tree(term->spec, "colors", NULL));
368 set_screen_dirty(term->screen, y, y);
371 void
372 draw_box(struct terminal *term, struct box *box,
373 unsigned char data, enum screen_char_attr attr,
374 struct color_pair *color)
376 struct screen_char *line, *pos, *end;
377 int width, height;
379 line = get_char(term, box->x, box->y);
380 if (!line) return;
382 height = int_min(box->height, term->height - box->y);
383 width = int_min(box->width, term->width - box->x);
385 if (height <= 0 || width <= 0) return;
387 /* Compose off the ending screen position in the areas first line. */
388 end = &line[width - 1];
389 end->attr = attr;
390 end->data = data;
391 if (color) {
392 set_term_color(end, color, 0,
393 get_opt_int_tree(term->spec, "colors", NULL));
394 } else {
395 clear_screen_char_color(end);
398 /* Draw the first area line. */
399 for (pos = line; pos < end; pos++) {
400 copy_screen_chars(pos, end, 1);
403 /* Now make @end point to the last line */
404 /* For the rest of the area use the first area line. */
405 pos = line;
406 while (--height) {
407 pos += term->width;
408 copy_screen_chars(pos, line, width);
411 set_screen_dirty(term->screen, box->y, box->y + box->height);
414 void
415 draw_shadow(struct terminal *term, struct box *box,
416 struct color_pair *color, int width, int height)
418 struct box dbox;
420 /* (horizontal) */
421 set_box(&dbox, box->x + width, box->y + box->height,
422 box->width - width, height);
424 draw_box(term, &dbox, ' ', 0, color);
426 /* (vertical) */
427 set_box(&dbox, box->x + box->width, box->y + height,
428 width, box->height);
430 draw_box(term, &dbox, ' ', 0, color);
433 #ifdef CONFIG_UTF8
434 static void
435 draw_text_utf8(struct terminal *term, int x, int y,
436 unsigned char *text, int length,
437 enum screen_char_attr attr, struct color_pair *color)
439 struct screen_char *start, *pos;
440 unsigned char *end = text + length;
441 unicode_val_T data;
443 assert(text && length >= 0);
444 if_assert_failed return;
446 if (length <= 0) return;
447 if (x >= term->width) return;
449 data = utf8_to_unicode(&text, end);
450 if (data == UCS_NO_CHAR) return;
451 start = get_char(term, x, y);
452 if (color) {
453 start->attr = attr;
454 set_term_color(start, color, 0,
455 get_opt_int_tree(term->spec, "colors", NULL));
458 if (start->data == UCS_NO_CHAR && x - 1 > 0)
459 draw_char_data(term, x - 1, y, UCS_ORPHAN_CELL);
461 pos = start;
463 if (unicode_to_cell(data) == 2) {
464 /* Is there enough room for whole double-width char? */
465 if (x + 1 < term->width) {
466 pos->data = data;
467 pos++;
468 x++;
470 pos->data = UCS_NO_CHAR;
471 pos->attr = 0;
472 } else {
473 pos->data = UCS_ORPHAN_CELL;
475 } else {
476 pos->data = data;
478 pos++;
479 x++;
481 for (; x < term->width; x++, pos++) {
482 data = utf8_to_unicode(&text, end);
483 if (data == UCS_NO_CHAR) break;
484 if (color) copy_screen_chars(pos, start, 1);
486 if (unicode_to_cell(data) == 2) {
487 /* Is there enough room for whole double-width char? */
488 if (x + 1 < term->width) {
489 pos->data = data;
491 x++;
492 pos++;
493 pos->data = UCS_NO_CHAR;
494 pos->attr = 0;
495 } else {
496 pos->data = UCS_ORPHAN_CELL;
498 } else {
499 pos->data = data;
502 set_screen_dirty(term->screen, y, y);
505 #endif /* CONFIG_UTF8 */
507 void
508 draw_text(struct terminal *term, int x, int y,
509 unsigned char *text, int length,
510 enum screen_char_attr attr, struct color_pair *color)
512 int end_pos;
513 struct screen_char *pos, *end;
515 assert(text && length >= 0);
516 if_assert_failed return;
518 if (x >= term->width || y >= term->height) return;
520 #ifdef CONFIG_UTF8
521 if (term->utf8_cp) {
522 draw_text_utf8(term, x, y, text, length, attr, color);
523 return;
525 #endif /* CONFIG_UTF8 */
527 if (length <= 0) return;
528 pos = get_char(term, x, y);
529 if (!pos) return;
531 end_pos = int_min(length, term->width - x) - 1;
533 #ifdef CONFIG_DEBUG
534 /* Detect attempt to set @end to a point outside @text,
535 * it may occur in case of bad calculations. --Zas */
536 if (end_pos < 0) {
537 INTERNAL("end_pos < 0 !!");
538 end_pos = 0;
539 } else {
540 int textlen = strlen(text);
542 if (end_pos >= textlen) {
543 INTERNAL("end_pos (%d) >= text length (%d) !!", end_pos, textlen);
544 end_pos = textlen - 1;
547 #endif
549 end = &pos[int_max(0, end_pos)];
551 if (color) {
552 /* Use the last char as template. */
553 end->attr = attr;
554 set_term_color(end, color, 0,
555 get_opt_int_tree(term->spec, "colors", NULL));
557 for (; pos < end && *text; text++, pos++) {
558 end->data = *text;
559 copy_screen_chars(pos, end, 1);
562 end->data = *text;
564 } else {
565 for (; pos <= end && *text; text++, pos++) {
566 pos->data = *text;
570 set_screen_dirty(term->screen, y, y);
573 void
574 draw_dlg_text(struct dialog_data *dlg_data, int x, int y,
575 unsigned char *text, int length,
576 enum screen_char_attr attr, struct color_pair *color)
578 struct terminal *term = dlg_data->win->term;
579 struct box *box = &dlg_data->real_box;
581 if (box->height) {
582 int y_max = box->y + box->height;
584 y -= dlg_data->y;
585 if (y < box->y || y >= y_max) return;
587 draw_text(term, x, y, text, length, attr, color);
591 void
592 set_cursor(struct terminal *term, int x, int y, int blockable)
594 assert(term && term->screen);
595 if_assert_failed return;
597 if (blockable && get_opt_bool_tree(term->spec, "block_cursor", NULL)) {
598 x = term->width - 1;
599 y = term->height - 1;
602 if (term->screen->cx != x || term->screen->cy != y) {
603 check_range(term, x, y);
605 set_screen_dirty(term->screen, int_min(term->screen->cy, y),
606 int_max(term->screen->cy, y));
607 term->screen->cx = x;
608 term->screen->cy = y;
612 void
613 set_dlg_cursor(struct terminal *term, struct dialog_data *dlg_data, int x, int y, int blockable)
615 struct box *box = &dlg_data->real_box;
617 assert(term && term->screen);
618 if_assert_failed return;
620 if (box->height) {
621 int y_max = box->y + box->height;
623 y -= dlg_data->y;
624 if (y < box->y || y >= y_max) return;
626 set_cursor(term, x, y, blockable);
630 void
631 clear_terminal(struct terminal *term)
633 struct box box;
635 set_box(&box, 0, 0, term->width, term->height);
636 draw_box(term, &box, ' ', 0, NULL);
637 set_cursor(term, 0, 0, 1);