1 /* Public terminal drawing API. Frontend for the screen image in memory. */
2 
3 #ifdef HAVE_CONFIG_H
4 #include "config.h"
5 #endif
6 
7 #include "elinks.h"
8 
9 #include "config/options.h"
10 #include "terminal/color.h"
11 #include "terminal/draw.h"
12 #include "terminal/screen.h"
13 #include "terminal/terminal.h"
14 #include "util/color.h"
15 #include "util/box.h"
16 
17 /* Makes sure that @x and @y are within the dimensions of the terminal. */
18 #define check_range(term, x, y) \
19 	do { \
20 		int_bounds(&(x), 0, (term)->width - 1); \
21 		int_bounds(&(y), 0, (term)->height - 1); \
22 	} while (0)
23 
24 #if defined(CONFIG_88_COLORS) || defined(CONFIG_256_COLORS)
25 #define clear_screen_char_color(schar) do { memset((schar)->color, 0, 2); } while (0)
26 #else
27 #define clear_screen_char_color(schar) do { (schar)->color[0] = 0; } while (0)
28 #endif
29 
30 
31 
32 inline struct screen_char *
get_char(struct terminal * term,int x,int y)33 get_char(struct terminal *term, int x, int y)
34 {
35 	assert(term && term->screen && term->screen->image);
36 	if_assert_failed return NULL;
37 	check_range(term, x, y);
38 
39 	return &term->screen->image[x + term->width * y];
40 }
41 
42 void
draw_border_cross(struct terminal * term,int x,int y,enum border_cross_direction dir,struct color_pair * color)43 draw_border_cross(struct terminal *term, int x, int y,
44 		  enum border_cross_direction dir, struct color_pair *color)
45 {
46 	static unsigned char border_trans[2][4] = {
47 		/* Used for BORDER_X_{RIGHT,LEFT}: */
48 		{ BORDER_SVLINE, BORDER_SRTEE, BORDER_SLTEE },
49 		/* Used for BORDER_X_{DOWN,UP}: */
50 		{ BORDER_SHLINE, BORDER_SDTEE, BORDER_SUTEE },
51 	};
52 	struct screen_char *screen_char = get_char(term, x, y);
53 	unsigned int d;
54 
55 	if (!screen_char) return;
56 	if (!(screen_char->attr & SCREEN_ATTR_FRAME)) return;
57 
58 	/* First check if there is already a horizontal/vertical line, so that
59 	 * we will have to replace with a T char. Example: if there is a '|'
60 	 * and the direction is right, replace with a '|-' T char.
61 	 *
62 	 * If this is not the case check if there is a T char and we are adding
63 	 * the direction so that we end up with a cross. Example : if there is
64 	 * a '|-' and the direction is left, replace with a '+' (cross) char. */
65 	d = dir>>1;
66 	if (screen_char->data == border_trans[d][0]) {
67 		screen_char->data = border_trans[d][1 + (dir & 1)];
68 
69 	} else if (screen_char->data == border_trans[d][2 - (dir & 1)]) {
70 		screen_char->data = BORDER_SCROSS;
71 	}
72 
73 	set_term_color(screen_char, color, 0,
74 		       get_opt_int_tree(term->spec, "colors"));
75 }
76 
77 void
draw_border_char(struct terminal * term,int x,int y,enum border_char border,struct color_pair * color)78 draw_border_char(struct terminal *term, int x, int y,
79 		 enum border_char border, struct color_pair *color)
80 {
81 	struct screen_char *screen_char = get_char(term, x, y);
82 
83 	if (!screen_char) return;
84 
85 	screen_char->data = (unsigned char) border;
86 	screen_char->attr = SCREEN_ATTR_FRAME;
87 	set_term_color(screen_char, color, 0,
88 		       get_opt_int_tree(term->spec, "colors"));
89 	set_screen_dirty(term->screen, y, y);
90 }
91 
92 void
draw_char_color(struct terminal * term,int x,int y,struct color_pair * color)93 draw_char_color(struct terminal *term, int x, int y, struct color_pair *color)
94 {
95 	struct screen_char *screen_char = get_char(term, x, y);
96 
97 	if (!screen_char) return;
98 
99 	set_term_color(screen_char, color, 0,
100 		       get_opt_int_tree(term->spec, "colors"));
101 	set_screen_dirty(term->screen, y, y);
102 }
103 
104 void
draw_char_data(struct terminal * term,int x,int y,unsigned char data)105 draw_char_data(struct terminal *term, int x, int y, unsigned char data)
106 {
107 	struct screen_char *screen_char = get_char(term, x, y);
108 
109 	if (!screen_char) return;
110 
111 	screen_char->data = data;
112 	set_screen_dirty(term->screen, y, y);
113 }
114 
115 /* Updates a line in the terms screen. */
116 /* When doing frame drawing @x can be different than 0. */
117 void
draw_line(struct terminal * term,int x,int y,int l,struct screen_char * line)118 draw_line(struct terminal *term, int x, int y, int l, struct screen_char *line)
119 {
120 	struct screen_char *screen_char = get_char(term, x, y);
121 	int size;
122 
123 	assert(line);
124 	if_assert_failed return;
125 	if (!screen_char) return;
126 
127 	size = int_min(l, term->width - x);
128 	if (size == 0) return;
129 
130 	copy_screen_chars(screen_char, line, size);
131 	set_screen_dirty(term->screen, y, y);
132 }
133 
134 void
draw_border(struct terminal * term,struct box * box,struct color_pair * color,int width)135 draw_border(struct terminal *term, struct box *box,
136 	    struct color_pair *color, int width)
137 {
138 	static enum border_char p1[] = {
139 		BORDER_SULCORNER,
140 		BORDER_SURCORNER,
141 		BORDER_SDLCORNER,
142 		BORDER_SDRCORNER,
143 		BORDER_SVLINE,
144 		BORDER_SHLINE,
145 	};
146 	static enum border_char p2[] = {
147 		BORDER_DULCORNER,
148 		BORDER_DURCORNER,
149 		BORDER_DDLCORNER,
150 		BORDER_DDRCORNER,
151 		BORDER_DVLINE,
152 		BORDER_DHLINE,
153 	};
154 	enum border_char *p = (width > 1) ? p2 : p1;
155 	struct box borderbox;
156 
157 	set_box(&borderbox, box->x - 1, box->y - 1,
158 		box->width + 2, box->height + 2);
159 
160 	if (borderbox.width > 2) {
161 		struct box bbox;
162 
163 		/* Horizontal top border */
164 		set_box(&bbox, box->x, borderbox.y, box->width, 1);
165 		draw_box(term, &bbox, p[5], SCREEN_ATTR_FRAME, color);
166 
167 		/* Horizontal bottom border */
168 		bbox.y += borderbox.height - 1;
169 		draw_box(term, &bbox, p[5], SCREEN_ATTR_FRAME, color);
170 	}
171 
172 	if (borderbox.height > 2) {
173 		struct box bbox;
174 
175 		/* Vertical left border */
176 		set_box(&bbox, borderbox.x, box->y, 1, box->height);
177 		draw_box(term, &bbox, p[4], SCREEN_ATTR_FRAME, color);
178 
179 		/* Vertical right border */
180 		bbox.x += borderbox.width - 1;
181 		draw_box(term, &bbox, p[4], SCREEN_ATTR_FRAME, color);
182 	}
183 
184 	if (borderbox.width > 1 && borderbox.height > 1) {
185 		int right = borderbox.x + borderbox.width - 1;
186 		int bottom = borderbox.y + borderbox.height - 1;
187 
188 		/* Upper left corner */
189 		draw_border_char(term, borderbox.x, borderbox.y, p[0], color);
190 		/* Upper right corner */
191 		draw_border_char(term, right, borderbox.y, p[1], color);
192 		/* Lower left corner */
193 		draw_border_char(term, borderbox.x, bottom, p[2], color);
194 		/* Lower right corner */
195 		draw_border_char(term, right, bottom, p[3], color);
196 	}
197 
198 	set_screen_dirty(term->screen, borderbox.y, borderbox.y + borderbox.height);
199 }
200 
201 void
draw_char(struct terminal * term,int x,int y,unsigned char data,enum screen_char_attr attr,struct color_pair * color)202 draw_char(struct terminal *term, int x, int y,
203 	  unsigned char data, enum screen_char_attr attr,
204 	  struct color_pair *color)
205 {
206 	struct screen_char *screen_char = get_char(term, x, y);
207 
208 	if (!screen_char) return;
209 
210 	screen_char->data = data;
211 	screen_char->attr = attr;
212 	set_term_color(screen_char, color, 0,
213 		       get_opt_int_tree(term->spec, "colors"));
214 
215 	set_screen_dirty(term->screen, y, y);
216 }
217 
218 void
draw_box(struct terminal * term,struct box * box,unsigned char data,enum screen_char_attr attr,struct color_pair * color)219 draw_box(struct terminal *term, struct box *box,
220 	 unsigned char data, enum screen_char_attr attr,
221 	 struct color_pair *color)
222 {
223 	struct screen_char *line, *pos, *end;
224 	int width, height;
225 
226 	line = get_char(term, box->x, box->y);
227 	if (!line) return;
228 
229 	height = int_min(box->height, term->height - box->y);
230 	width = int_min(box->width, term->width - box->x);
231 
232 	if (height <= 0 || width <= 0) return;
233 
234 	/* Compose off the ending screen position in the areas first line. */
235 	end = &line[width - 1];
236 	end->attr = attr;
237 	end->data = data;
238 	if (color) {
239 		set_term_color(end, color, 0,
240 			       get_opt_int_tree(term->spec, "colors"));
241 	} else {
242 		clear_screen_char_color(end);
243 	}
244 
245 	/* Draw the first area line. */
246 	for (pos = line; pos < end; pos++) {
247 		copy_screen_chars(pos, end, 1);
248 	}
249 
250 	/* Now make @end point to the last line */
251 	/* For the rest of the area use the first area line. */
252 	pos = line;
253 	while (--height) {
254 		pos += term->width;
255 		copy_screen_chars(pos, line, width);
256 	}
257 
258 	set_screen_dirty(term->screen, box->y, box->y + box->height);
259 }
260 
261 void
draw_shadow(struct terminal * term,struct box * box,struct color_pair * color,int width,int height)262 draw_shadow(struct terminal *term, struct box *box,
263 	    struct color_pair *color, int width, int height)
264 {
265 	struct box dbox;
266 
267 	/* (horizontal) */
268 	set_box(&dbox, box->x + width, box->y + box->height,
269 		box->width - width, height);
270 
271 	draw_box(term, &dbox, ' ', 0, color);
272 
273 	/* (vertical) */
274 	set_box(&dbox, box->x + box->width, box->y + height,
275 		width, box->height);
276 
277 	draw_box(term, &dbox, ' ', 0, color);
278 }
279 
280 void
draw_text(struct terminal * term,int x,int y,unsigned char * text,int length,enum screen_char_attr attr,struct color_pair * color)281 draw_text(struct terminal *term, int x, int y,
282 	  unsigned char *text, int length,
283 	  enum screen_char_attr attr, struct color_pair *color)
284 {
285 	int end_pos;
286 	struct screen_char *pos, *end;
287 
288 	assert(text && length >= 0);
289 	if_assert_failed return;
290 
291 	if (length <= 0) return;
292 	pos = get_char(term, x, y);
293 	if (!pos) return;
294 
295 	end_pos = int_min(length, term->width - x) - 1;
296 
297 #ifdef CONFIG_DEBUG
298 	/* Detect attempt to set @end to a point outside @text,
299 	 * it may occur in case of bad calculations. --Zas */
300 	if (end_pos < 0) {
301 		INTERNAL("end_pos < 0 !!");
302 		end_pos = 0;
303 	} else {
304 		int textlen = strlen(text);
305 
306 		if (end_pos >= textlen) {
307 			INTERNAL("end_pos (%d) >= text length (%d) !!", end_pos, textlen);
308 			end_pos = textlen - 1;
309 		}
310 	}
311 #endif
312 
313 	end = &pos[int_max(0, end_pos)];
314 
315 	if (color) {
316 		/* Use the last char as template. */
317 		end->attr = attr;
318 		set_term_color(end, color, 0,
319 			       get_opt_int_tree(term->spec, "colors"));
320 
321 		for (; pos < end && *text; text++, pos++) {
322 			end->data = *text;
323 			copy_screen_chars(pos, end, 1);
324 		}
325 
326 		end->data = *text;
327 
328 	} else {
329 		for (; pos <= end && *text; text++, pos++) {
330 			pos->data = *text;
331 		}
332 	}
333 
334 	set_screen_dirty(term->screen, y, y);
335 }
336 
337 void
set_cursor(struct terminal * term,int x,int y,int blockable)338 set_cursor(struct terminal *term, int x, int y, int blockable)
339 {
340 	assert(term && term->screen);
341 	if_assert_failed return;
342 
343 	if (blockable && get_opt_bool_tree(term->spec, "block_cursor")) {
344 		x = term->width - 1;
345 		y = term->height - 1;
346 	}
347 
348 	if (term->screen->cx != x || term->screen->cy != y) {
349 		check_range(term, x, y);
350 
351 		set_screen_dirty(term->screen, int_min(term->screen->cy, y),
352 					       int_max(term->screen->cy, y));
353 		term->screen->cx = x;
354 		term->screen->cy = y;
355 	}
356 }
357 
358 void
clear_terminal(struct terminal * term)359 clear_terminal(struct terminal *term)
360 {
361 	struct box box;
362 
363 	set_box(&box, 0, 0, term->width, term->height);
364 	draw_box(term, &box, ' ', 0, NULL);
365 	set_cursor(term, 0, 0, 1);
366 }
367