Compilation fix on Mingw32.
[elinks.git] / src / terminal / draw.c
blob1f4ceba96b38db531e63c697f05156241e145945
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)->color, 0, SCREEN_COLOR_SIZE); } while (0)
30 #else
31 #define clear_screen_char_color(schar) \
32 do { (schar)->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 /*! Used by viewer to copy over a document.
145 * When doing frame drawing @a x can be different than 0. */
146 void
147 draw_line(struct terminal *term, int x, int y, int l, struct screen_char *line)
149 struct screen_char *screen_char = get_char(term, x, y);
150 int size;
152 assert(line);
153 if_assert_failed return;
154 if (!screen_char) return;
156 size = int_min(l, term->width - x);
157 if (size == 0) return;
159 #ifdef CONFIG_UTF8
160 if (term->utf8_cp) {
161 struct screen_char *sc;
163 if (line[0].data == UCS_NO_CHAR && x == 0) {
164 unicode_val_T data_save;
166 sc = line;
167 data_save = sc->data;
168 sc->data = UCS_ORPHAN_CELL;
169 copy_screen_chars(screen_char, line, 1);
170 sc->data = data_save;
171 size--;
172 line++;
173 screen_char++;
176 /* Instead of displaying double-width character at last column
177 * display only UCS_ORPHAN_CELL. */
178 if (size - 1 > 0 && unicode_to_cell(line[size - 1].data) == 2) {
179 unicode_val_T data_save;
181 sc = &line[size - 1];
182 data_save = sc->data;
183 sc->data = UCS_ORPHAN_CELL;
184 copy_screen_chars(screen_char, line, size);
185 sc->data = data_save;
186 } else {
187 copy_screen_chars(screen_char, line, size);
189 } else
190 #endif
191 copy_screen_chars(screen_char, line, size);
192 set_screen_dirty(term->screen, y, y);
195 void
196 draw_border(struct terminal *term, struct box *box,
197 struct color_pair *color, int width)
199 static const enum border_char p1[] = {
200 BORDER_SULCORNER,
201 BORDER_SURCORNER,
202 BORDER_SDLCORNER,
203 BORDER_SDRCORNER,
204 BORDER_SVLINE,
205 BORDER_SHLINE,
207 static const enum border_char p2[] = {
208 BORDER_DULCORNER,
209 BORDER_DURCORNER,
210 BORDER_DDLCORNER,
211 BORDER_DDRCORNER,
212 BORDER_DVLINE,
213 BORDER_DHLINE,
215 const enum border_char *p = (width > 1) ? p2 : p1;
216 struct box borderbox;
218 set_box(&borderbox, box->x - 1, box->y - 1,
219 box->width + 2, box->height + 2);
221 if (borderbox.width > 2) {
222 struct box bbox;
224 /* Horizontal top border */
225 set_box(&bbox, box->x, borderbox.y, box->width, 1);
226 draw_box(term, &bbox, p[5], SCREEN_ATTR_FRAME, color);
228 /* Horizontal bottom border */
229 bbox.y += borderbox.height - 1;
230 draw_box(term, &bbox, p[5], SCREEN_ATTR_FRAME, color);
233 if (borderbox.height > 2) {
234 struct box bbox;
236 /* Vertical left border */
237 set_box(&bbox, borderbox.x, box->y, 1, box->height);
238 draw_box(term, &bbox, p[4], SCREEN_ATTR_FRAME, color);
240 /* Vertical right border */
241 bbox.x += borderbox.width - 1;
242 draw_box(term, &bbox, p[4], SCREEN_ATTR_FRAME, color);
245 if (borderbox.width > 1 && borderbox.height > 1) {
246 int right = borderbox.x + borderbox.width - 1;
247 int bottom = borderbox.y + borderbox.height - 1;
249 /* Upper left corner */
250 draw_border_char(term, borderbox.x, borderbox.y, p[0], color);
251 /* Upper right corner */
252 draw_border_char(term, right, borderbox.y, p[1], color);
253 /* Lower left corner */
254 draw_border_char(term, borderbox.x, bottom, p[2], color);
255 /* Lower right corner */
256 draw_border_char(term, right, bottom, p[3], color);
259 set_screen_dirty(term->screen, borderbox.y, borderbox.y + borderbox.height);
262 #ifdef CONFIG_UTF8
263 /** Checks cells left and right to the box for broken double-width chars.
264 * Replace it with UCS_ORPHAN_CELL.
266 * @verbatim
267 * 1+---+3
268 * 1|box|##4
269 * 1| |##4
270 * 1| |##4
271 * 1+---+##4
272 * 2#####4
273 * 1,2,3,4 - needs to be checked, # - shadow , +,-,| - border
274 * @endverbatim
276 void
277 fix_dwchar_around_box(struct terminal *term, struct box *box, int border,
278 int shadow_width, int shadow_height)
280 struct screen_char *schar;
281 int height, x, y;
283 if (!term->utf8_cp)
284 return;
286 /* 1 */
287 x = box->x - border - 1;
288 if (x > 0) {
289 y = box->y - border;
290 height = box->height + 2 * border;
292 schar = get_char(term, x, y);
293 for (;height--; schar += term->width)
294 if (unicode_to_cell(schar->data) == 2)
295 schar->data = UCS_ORPHAN_CELL;
298 /* 2 */
299 x = box->x - border + shadow_width - 1;
300 if (x > 0 && x < term->width) {
301 y = box->y + border + box->height;
302 height = shadow_height;
304 schar = get_char(term, x, y);
305 for (;height--; schar += term->width)
306 if (unicode_to_cell(schar->data) == 2)
307 schar->data = UCS_ORPHAN_CELL;
310 /* 3 */
311 x = box->x + box->width + border;
312 if (x < term->width) {
313 y = box->y - border;
314 height = shadow_height;
316 schar = get_char(term, x, y);
317 for (;height--; schar += term->width)
318 if (schar->data == UCS_NO_CHAR)
319 schar->data = UCS_ORPHAN_CELL;
322 /* 4 */
323 x = box->x + box->width + border + shadow_width;
324 if (x < term->width) {
325 y = box->y - border + shadow_height;
326 height = box->height + 2 * border;
328 schar = get_char(term, x, y);
329 for (;height--; schar += term->width)
330 if (schar->data == UCS_NO_CHAR)
331 schar->data = UCS_ORPHAN_CELL;
334 #endif
336 #ifdef CONFIG_UTF8
337 void
338 draw_char(struct terminal *term, int x, int y,
339 unicode_val_T data, enum screen_char_attr attr,
340 struct color_pair *color)
341 #else
342 void
343 draw_char(struct terminal *term, int x, int y,
344 unsigned char data, enum screen_char_attr attr,
345 struct color_pair *color)
346 #endif /* CONFIG_UTF8 */
348 struct screen_char *screen_char = get_char(term, x, y);
350 if (!screen_char) return;
352 screen_char->data = data;
353 screen_char->attr = attr;
354 set_term_color(screen_char, color, 0,
355 get_opt_int_tree(term->spec, "colors", NULL));
357 set_screen_dirty(term->screen, y, y);
360 void
361 draw_box(struct terminal *term, struct box *box,
362 unsigned char data, enum screen_char_attr attr,
363 struct color_pair *color)
365 struct screen_char *line, *pos, *end;
366 int width, height;
368 line = get_char(term, box->x, box->y);
369 if (!line) return;
371 height = int_min(box->height, term->height - box->y);
372 width = int_min(box->width, term->width - box->x);
374 if (height <= 0 || width <= 0) return;
376 /* Compose off the ending screen position in the areas first line. */
377 end = &line[width - 1];
378 end->attr = attr;
379 end->data = data;
380 if (color) {
381 set_term_color(end, color, 0,
382 get_opt_int_tree(term->spec, "colors", NULL));
383 } else {
384 clear_screen_char_color(end);
387 /* Draw the first area line. */
388 for (pos = line; pos < end; pos++) {
389 copy_screen_chars(pos, end, 1);
392 /* Now make @end point to the last line */
393 /* For the rest of the area use the first area line. */
394 pos = line;
395 while (--height) {
396 pos += term->width;
397 copy_screen_chars(pos, line, width);
400 set_screen_dirty(term->screen, box->y, box->y + box->height);
403 void
404 draw_shadow(struct terminal *term, struct box *box,
405 struct color_pair *color, int width, int height)
407 struct box dbox;
409 /* (horizontal) */
410 set_box(&dbox, box->x + width, box->y + box->height,
411 box->width - width, height);
413 draw_box(term, &dbox, ' ', 0, color);
415 /* (vertical) */
416 set_box(&dbox, box->x + box->width, box->y + height,
417 width, box->height);
419 draw_box(term, &dbox, ' ', 0, color);
422 #ifdef CONFIG_UTF8
423 static void
424 draw_text_utf8(struct terminal *term, int x, int y,
425 unsigned char *text, int length,
426 enum screen_char_attr attr, struct color_pair *color)
428 struct screen_char *start, *pos;
429 unsigned char *end = text + length;
430 unicode_val_T data;
432 assert(text && length >= 0);
433 if_assert_failed return;
435 if (length <= 0) return;
436 if (x >= term->width) return;
438 data = utf8_to_unicode(&text, end);
439 if (data == UCS_NO_CHAR) return;
440 start = get_char(term, x, y);
441 if (color) {
442 start->attr = attr;
443 set_term_color(start, color, 0,
444 get_opt_int_tree(term->spec, "colors", NULL));
447 if (start->data == UCS_NO_CHAR && x - 1 > 0)
448 draw_char_data(term, x - 1, y, UCS_ORPHAN_CELL);
450 pos = start;
452 if (unicode_to_cell(data) == 2) {
453 /* Is there enough room for whole double-width char? */
454 if (x + 1 < term->width) {
455 pos->data = data;
456 pos++;
457 x++;
459 pos->data = UCS_NO_CHAR;
460 pos->attr = 0;
461 } else {
462 pos->data = UCS_ORPHAN_CELL;
464 } else {
465 pos->data = data;
467 pos++;
468 x++;
470 for (; x < term->width; x++, pos++) {
471 data = utf8_to_unicode(&text, end);
472 if (data == UCS_NO_CHAR) break;
473 if (color) copy_screen_chars(pos, start, 1);
475 if (unicode_to_cell(data) == 2) {
476 /* Is there enough room for whole double-width char? */
477 if (x + 1 < term->width) {
478 pos->data = data;
480 x++;
481 pos++;
482 pos->data = UCS_NO_CHAR;
483 pos->attr = 0;
484 } else {
485 pos->data = UCS_ORPHAN_CELL;
487 } else {
488 pos->data = data;
491 set_screen_dirty(term->screen, y, y);
494 #endif /* CONFIG_UTF8 */
496 void
497 draw_text(struct terminal *term, int x, int y,
498 unsigned char *text, int length,
499 enum screen_char_attr attr, struct color_pair *color)
501 int end_pos;
502 struct screen_char *pos, *end;
504 assert(text && length >= 0);
505 if_assert_failed return;
507 if (x >= term->width || y >= term->height) return;
509 #ifdef CONFIG_UTF8
510 if (term->utf8_cp) {
511 draw_text_utf8(term, x, y, text, length, attr, color);
512 return;
514 #endif /* CONFIG_UTF8 */
516 if (length <= 0) return;
517 pos = get_char(term, x, y);
518 if (!pos) return;
520 end_pos = int_min(length, term->width - x) - 1;
522 #ifdef CONFIG_DEBUG
523 /* Detect attempt to set @end to a point outside @text,
524 * it may occur in case of bad calculations. --Zas */
525 if (end_pos < 0) {
526 INTERNAL("end_pos < 0 !!");
527 end_pos = 0;
528 } else {
529 int textlen = strlen(text);
531 if (end_pos >= textlen) {
532 INTERNAL("end_pos (%d) >= text length (%d) !!", end_pos, textlen);
533 end_pos = textlen - 1;
536 #endif
538 end = &pos[int_max(0, end_pos)];
540 if (color) {
541 /* Use the last char as template. */
542 end->attr = attr;
543 set_term_color(end, color, 0,
544 get_opt_int_tree(term->spec, "colors", NULL));
546 for (; pos < end && *text; text++, pos++) {
547 end->data = *text;
548 copy_screen_chars(pos, end, 1);
551 end->data = *text;
553 } else {
554 for (; pos <= end && *text; text++, pos++) {
555 pos->data = *text;
559 set_screen_dirty(term->screen, y, y);
562 void
563 draw_dlg_text(struct dialog_data *dlg_data, int x, int y,
564 unsigned char *text, int length,
565 enum screen_char_attr attr, struct color_pair *color)
567 struct terminal *term = dlg_data->win->term;
568 struct box *box = &dlg_data->real_box;
570 if (box->height) {
571 int y_max = box->y + box->height;
573 y -= dlg_data->y;
574 if (y < box->y || y >= y_max) return;
576 draw_text(term, x, y, text, length, attr, color);
580 void
581 set_cursor(struct terminal *term, int x, int y, int blockable)
583 assert(term && term->screen);
584 if_assert_failed return;
586 if (blockable && get_opt_bool_tree(term->spec, "block_cursor", NULL)) {
587 x = term->width - 1;
588 y = term->height - 1;
591 if (term->screen->cx != x || term->screen->cy != y) {
592 check_range(term, x, y);
594 set_screen_dirty(term->screen, int_min(term->screen->cy, y),
595 int_max(term->screen->cy, y));
596 term->screen->cx = x;
597 term->screen->cy = y;
601 void
602 set_dlg_cursor(struct terminal *term, struct dialog_data *dlg_data, int x, int y, int blockable)
604 struct box *box = &dlg_data->real_box;
606 assert(term && term->screen);
607 if_assert_failed return;
609 if (box->height) {
610 int y_max = box->y + box->height;
612 y -= dlg_data->y;
613 if (y < box->y || y >= y_max) return;
615 set_cursor(term, x, y, blockable);
619 void
620 clear_terminal(struct terminal *term)
622 struct box box;
624 set_box(&box, 0, 0, term->width, term->height);
625 draw_box(term, &box, ' ', 0, NULL);
626 set_cursor(term, 0, 0, 1);