xref: /netbsd-src/lib/libcurses/addbytes.c (revision 627f7eb200a4419d89b531d55fccd2ee3ffdcde0)
1 /*	$NetBSD: addbytes.c,v 1.54 2021/02/13 14:30:37 rillig Exp $	*/
2 
3 /*
4  * Copyright (c) 1987, 1993, 1994
5  *	The Regents of the University of California.  All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. Neither the name of the University nor the names of its contributors
16  *    may be used to endorse or promote products derived from this software
17  *    without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31 
32 #include <sys/cdefs.h>
33 #ifndef lint
34 #if 0
35 static char sccsid[] = "@(#)addbytes.c	8.4 (Berkeley) 5/4/94";
36 #else
37 __RCSID("$NetBSD: addbytes.c,v 1.54 2021/02/13 14:30:37 rillig Exp $");
38 #endif
39 #endif				/* not lint */
40 
41 #include <stdlib.h>
42 #include <string.h>
43 #include "curses.h"
44 #include "curses_private.h"
45 #ifdef DEBUG
46 #include <assert.h>
47 #endif
48 
49 #ifndef _CURSES_USE_MACROS
50 
51 /*
52  * addbytes --
53  *      Add the characters to the current position in stdscr.
54  */
55 int
56 addbytes(const char *bytes, int count)
57 {
58 
59 	return _cursesi_waddbytes(stdscr, bytes, count, 0, 1);
60 }
61 
62 /*
63  * waddbytes --
64  *      Add the characters to the current position in the given window.
65  */
66 int
67 waddbytes(WINDOW *win, const char *bytes, int count)
68 {
69 
70 	return _cursesi_waddbytes(win, bytes, count, 0, 1);
71 }
72 
73 /*
74  * mvaddbytes --
75  *      Add the characters to stdscr at the location given.
76  */
77 int
78 mvaddbytes(int y, int x, const char *bytes, int count)
79 {
80 
81 	return mvwaddbytes(stdscr, y, x, bytes, count);
82 }
83 
84 /*
85  * mvwaddbytes --
86  *      Add the characters to the given window at the location given.
87  */
88 int
89 mvwaddbytes(WINDOW *win, int y, int x, const char *bytes, int count)
90 {
91 
92 	if (wmove(win, y, x) == ERR)
93 		return ERR;
94 
95 	return _cursesi_waddbytes(win, bytes, count, 0, 1);
96 }
97 
98 #endif
99 
100 int
101 __waddbytes(WINDOW *win, const char *bytes, int count, attr_t attr)
102 {
103 
104 	return _cursesi_waddbytes(win, bytes, count, attr, 1);
105 }
106 
107 /*
108  * _cursesi_waddbytes --
109  *	Add the characters to the current position in the given window.
110  * if char_interp is non-zero then character interpretation is done on
111  * the byte (i.e. \n to newline, \r to carriage return, \b to backspace
112  * and so on).
113  */
114 int
115 _cursesi_waddbytes(WINDOW *win, const char *bytes, int count, attr_t attr,
116 	    int char_interp)
117 {
118 	int		*py = &win->cury, *px = &win->curx, err;
119 	__LINE		*lp;
120 #ifdef HAVE_WCHAR
121 	int		n;
122 	cchar_t		cc;
123 	wchar_t		wc;
124 	mbstate_t	st;
125 #else
126 	int		c;
127 #endif
128 #ifdef DEBUG
129 	int             i;
130 
131 	for (i = 0; i < win->maxy; i++) {
132 		assert(win->alines[i]->sentinel == SENTINEL_VALUE);
133 	}
134 
135 	__CTRACE(__CTRACE_INPUT, "ADDBYTES: add %d bytes\n", count);
136 #endif
137 
138 	err = OK;
139 	lp = win->alines[*py];
140 
141 #ifdef HAVE_WCHAR
142 	(void)memset(&st, 0, sizeof(st));
143 #endif
144 	while (count > 0) {
145 #ifndef HAVE_WCHAR
146 		c = *bytes++;
147 #ifdef DEBUG
148 		__CTRACE(__CTRACE_INPUT, "ADDBYTES('%c', %x) at (%d, %d)\n",
149 		    c, attr, *py, *px);
150 #endif
151 		err = _cursesi_addbyte(win, &lp, py, px, c, attr, char_interp);
152 		count--;
153 #else
154 		/*
155 		 * For wide-character support only, try to convert the
156 		 * given string into a wide character - we do this because
157 		 * this is how ncurses behaves (not that I think this is
158 		 * actually the correct thing to do but if we don't do it
159 		 * a lot of things that rely on this behaviour will break
160 		 * and we will be blamed).  If the conversion succeeds
161 		 * then we eat the n characters used to make the wide char
162 		 * from the string.
163 		 */
164 		n = (int)mbrtowc(&wc, bytes, (size_t)count, &st);
165 		if (n < 0) {
166 			/* not a valid conversion just eat a char */
167 			wc = *bytes;
168 			n = 1;
169 			(void)memset(&st, 0, sizeof(st));
170 		} else if (wc == 0) {
171 			break;
172 		}
173 #ifdef DEBUG
174 		__CTRACE(__CTRACE_INPUT,
175 		    "ADDBYTES WIDE(0x%x [%s], %x) at (%d, %d), ate %d bytes\n",
176 		    (unsigned)wc, unctrl((unsigned)wc), attr, *py, *px, n);
177 #endif
178 		cc.vals[0] = wc;
179 		cc.elements = 1;
180 		cc.attributes = attr;
181 		err = _cursesi_addwchar(win, &lp, py, px, &cc, char_interp);
182 		bytes += n;
183 		count -= n;
184 #endif
185 	}
186 
187 #ifdef DEBUG
188 	for (i = 0; i < win->maxy; i++) {
189 		assert(win->alines[i]->sentinel == SENTINEL_VALUE);
190 	}
191 #endif
192 
193 	return (err);
194 }
195 
196 /*
197  * _cursesi_addbyte -
198  *	Internal function to add a byte and update the row and column
199  * positions as appropriate.  If char_interp is non-zero then
200  * character interpretation is done on the byte.  This function is
201  * only used in the narrow character version of curses.
202  */
203 int
204 _cursesi_addbyte(WINDOW *win, __LINE **lp, int *y, int *x, int c,
205 		 attr_t attr, int char_interp)
206 {
207 	static char	 blank[] = " ";
208 	int		 tabsize;
209 	int		 newx, i;
210 	attr_t		 attributes;
211 
212 	if (char_interp) {
213 		switch (c) {
214 		case '\t':
215 			tabsize = win->screen->TABSIZE;
216 			newx = tabsize - (*x % tabsize);
217 			for (i = 0; i < newx; i++) {
218 				if (waddbytes(win, blank, 1) == ERR)
219 					return ERR;
220 			}
221 			return OK;
222 
223 		case '\n':
224 			wclrtoeol(win);
225 			(*lp)->flags |= __ISPASTEOL;
226 			break;
227 
228 		case '\r':
229 			*x = 0;
230 			return OK;
231 
232 		case '\b':
233 			if (--(*x) < 0)
234 				*x = 0;
235 			return OK;
236 		}
237 	}
238 
239 #ifdef DEBUG
240 	__CTRACE(__CTRACE_INPUT, "ADDBYTES(%p, %d, %d)\n", win, *y, *x);
241 #endif
242 
243 	if (char_interp && ((*lp)->flags & __ISPASTEOL)) {
244 		*x = 0;
245 		(*lp)->flags &= ~__ISPASTEOL;
246 		if (*y == win->scr_b) {
247 #ifdef DEBUG
248 			__CTRACE(__CTRACE_INPUT,
249 				 "ADDBYTES - on bottom "
250 				 "of scrolling region\n");
251 #endif
252 			if (!(win->flags & __SCROLLOK))
253 				return ERR;
254 			scroll(win);
255 		} else {
256 			(*y)++;
257 		}
258 		*lp = win->alines[*y];
259 		if (c == '\n')
260 			return OK;
261 	}
262 
263 #ifdef DEBUG
264 	__CTRACE(__CTRACE_INPUT,
265 		 "ADDBYTES: 1: y = %d, x = %d, firstch = %d, lastch = %d\n",
266 		 *y, *x, *win->alines[*y]->firstchp,
267 		 *win->alines[*y]->lastchp);
268 #endif
269 
270 	attributes = (win->wattr | attr) & (__ATTRIBUTES & ~__COLOR);
271 	if (attr & __COLOR)
272 		attributes |= attr & __COLOR;
273 	else if (win->wattr & __COLOR)
274 		attributes |= win->wattr & __COLOR;
275 
276 	/*
277 	 * Always update the change pointers.  Otherwise,
278 	 * we could end up not displaying 'blank' characters
279 	 * when overlapping windows are displayed.
280 	 */
281 	newx = *x + win->ch_off;
282 	(*lp)->flags |= __ISDIRTY;
283 	/*
284 	 * firstchp/lastchp are shared between
285 	 * parent window and sub-window.
286 	 */
287 	if (newx < *(*lp)->firstchp)
288 		*(*lp)->firstchp = newx;
289 	if (newx > *(*lp)->lastchp)
290 		*(*lp)->lastchp = newx;
291 #ifdef DEBUG
292 	__CTRACE(__CTRACE_INPUT, "ADDBYTES: change gives f/l: %d/%d [%d/%d]\n",
293 		 *(*lp)->firstchp, *(*lp)->lastchp,
294 		 *(*lp)->firstchp - win->ch_off,
295 		 *(*lp)->lastchp - win->ch_off);
296 #endif
297 	if (win->bch != ' ' && c == ' ')
298 		(*lp)->line[*x].ch = win->bch;
299 	else
300 		(*lp)->line[*x].ch = c;
301 
302 	if (attributes & __COLOR)
303 		(*lp)->line[*x].attr =
304 			attributes | (win->battr & ~__COLOR);
305 	else
306 		(*lp)->line[*x].attr = attributes | win->battr;
307 
308 	if (*x == win->maxx - 1)
309 		(*lp)->flags |= __ISPASTEOL;
310 	else
311 		(*x)++;
312 
313 #ifdef DEBUG
314 	__CTRACE(__CTRACE_INPUT,
315 		 "ADDBYTES: 2: y = %d, x = %d, firstch = %d, lastch = %d\n",
316 		 *y, *x, *win->alines[*y]->firstchp,
317 		 *win->alines[*y]->lastchp);
318 #endif
319 	__sync(win);
320 	return OK;
321 }
322 
323 /*
324  * _cursesi_addwchar -
325  *	Internal function to add a wide character and update the row
326  * and column positions.
327  */
328 int
329 _cursesi_addwchar(WINDOW *win, __LINE **lnp, int *y, int *x,
330 		  const cchar_t *wch, int char_interp)
331 {
332 #ifndef HAVE_WCHAR
333 	return ERR;
334 #else
335 	int sx = 0, ex = 0, cw = 0, i = 0, newx = 0, tabsize;
336 	__LDATA *lp = &win->alines[*y]->line[*x], *tp = NULL;
337 	nschar_t *np = NULL;
338 	cchar_t cc;
339 	attr_t attributes;
340 
341 	if (char_interp) {
342 		/* special characters handling */
343 		switch (wch->vals[0]) {
344 		case L'\b':
345 			if (--*x < 0)
346 				*x = 0;
347 			return OK;
348 		case L'\r':
349 			*x = 0;
350 			return OK;
351 		case L'\n':
352 			wclrtoeol(win);
353 			*x = 0;
354 			(*lnp)->flags &= ~__ISPASTEOL;
355 			if (*y == win->scr_b) {
356 				if (!(win->flags & __SCROLLOK))
357 					return ERR;
358 				scroll(win);
359 			} else {
360 				(*y)++;
361 			}
362 			return OK;
363 		case L'\t':
364 			cc.vals[0] = L' ';
365 			cc.elements = 1;
366 			cc.attributes = win->wattr;
367 			tabsize = win->screen->TABSIZE;
368 			newx = tabsize - (*x % tabsize);
369 			for (i = 0; i < newx; i++) {
370 				if (wadd_wch(win, &cc) == ERR)
371 					return ERR;
372 			}
373 			return OK;
374 		}
375 	}
376 
377 	/* check for non-spacing character */
378 	if (!wcwidth(wch->vals[0])) {
379 #ifdef DEBUG
380 		__CTRACE(__CTRACE_INPUT,
381 			 "_cursesi_addwchar: char '%c' is non-spacing\n",
382 			 wch->vals[0]);
383 #endif /* DEBUG */
384 		cw = WCOL(*lp);
385 		if (cw < 0) {
386 			lp += cw;
387 			*x += cw;
388 		}
389 		for (i = 0; i < wch->elements; i++) {
390 			if (!(np = (nschar_t *) malloc(sizeof(nschar_t))))
391 				return ERR;;
392 			np->ch = wch->vals[i];
393 			np->next = lp->nsp;
394 			lp->nsp = np;
395 		}
396 		(*lnp)->flags |= __ISDIRTY;
397 		newx = *x + win->ch_off;
398 		if (newx < *(*lnp)->firstchp)
399 			*(*lnp)->firstchp = newx;
400 		if (newx > *(*lnp)->lastchp)
401 			*(*lnp)->lastchp = newx;
402 		__touchline(win, *y, *x, *x);
403 		return OK;
404 	}
405 	/* check for new line first */
406 	if (char_interp && ((*lnp)->flags & __ISPASTEOL)) {
407 		*x = 0;
408 		(*lnp)->flags &= ~__ISPASTEOL;
409 		if (*y == win->scr_b) {
410 			if (!(win->flags & __SCROLLOK))
411 				return ERR;
412 			scroll(win);
413 		} else {
414 			(*y)++;
415 		}
416 		(*lnp) = win->alines[*y];
417 		lp = &win->alines[*y]->line[*x];
418 	}
419 	/* clear out the current character */
420 	cw = WCOL(*lp);
421 	if (cw >= 0) {
422 		sx = *x;
423 	} else {
424 		for (sx = *x - 1; sx >= max(*x + cw, 0); sx--) {
425 #ifdef DEBUG
426 			__CTRACE(__CTRACE_INPUT,
427 				 "_cursesi_addwchar: clear current char (%d,%d)\n",
428 				 *y, sx);
429 #endif /* DEBUG */
430 			tp = &win->alines[*y]->line[sx];
431 			tp->ch = (wchar_t) btowc((int) win->bch);
432 			if (_cursesi_copy_nsp(win->bnsp, tp) == ERR)
433 				return ERR;
434 
435 			tp->attr = win->battr;
436 			SET_WCOL(*tp, 1);
437 		}
438 		sx = *x + cw;
439 		(*lnp)->flags |= __ISDIRTY;
440 		newx = sx + win->ch_off;
441 		if (newx < *(*lnp)->firstchp)
442 			*(*lnp)->firstchp = newx;
443 	}
444 
445 	/* check for enough space before the end of line */
446 	cw = wcwidth(wch->vals[0]);
447 	if (cw < 0)
448 		cw = 1;
449 
450 	if (cw > win->maxx - *x) {
451 #ifdef DEBUG
452 		__CTRACE(__CTRACE_INPUT,
453 			 "_cursesi_addwchar: clear EOL (%d,%d)\n",
454 			 *y, *x);
455 #endif /* DEBUG */
456 		(*lnp)->flags |= __ISDIRTY;
457 		newx = *x + win->ch_off;
458 		if (newx < *(*lnp)->firstchp)
459 			*(*lnp)->firstchp = newx;
460 		for (tp = lp; *x < win->maxx; tp++, (*x)++) {
461 			tp->ch = (wchar_t) btowc((int) win->bch);
462 			if (_cursesi_copy_nsp(win->bnsp, tp) == ERR)
463 				return ERR;
464 			tp->attr = win->battr;
465 			SET_WCOL(*tp, 1);
466 		}
467 		newx = win->maxx - 1 + win->ch_off;
468 		if (newx > *(*lnp)->lastchp)
469 			*(*lnp)->lastchp = newx;
470 		__touchline(win, *y, sx, (int) win->maxx - 1);
471 		sx = *x = 0;
472 		if (*y == win->scr_b) {
473 			if (!(win->flags & __SCROLLOK))
474 				return ERR;
475 			scroll(win);
476 		} else {
477 			(*y)++;
478 		}
479 		lp = &win->alines[*y]->line[0];
480 		(*lnp) = win->alines[*y];
481 	}
482 
483 	/* add spacing character */
484 #ifdef DEBUG
485 	__CTRACE(__CTRACE_INPUT,
486 		 "_cursesi_addwchar: add character (%d,%d) 0x%x\n",
487 		 *y, *x, wch->vals[0]);
488 #endif /* DEBUG */
489 	(*lnp)->flags |= __ISDIRTY;
490 	newx = *x + win->ch_off;
491 	if (newx < *(*lnp)->firstchp)
492 		*(*lnp)->firstchp = newx;
493 	if (lp->nsp) {
494 		__cursesi_free_nsp(lp->nsp);
495 		lp->nsp = NULL;
496 	}
497 
498 	lp->ch = wch->vals[0];
499 
500 	attributes = (win->wattr | wch->attributes)
501 		& (WA_ATTRIBUTES & ~__COLOR);
502 	if (wch->attributes & __COLOR)
503 		attributes |= wch->attributes & __COLOR;
504 	else if (win->wattr & __COLOR)
505 		attributes |= win->wattr & __COLOR;
506 	if (attributes & __COLOR)
507 		lp->attr = attributes | (win->battr & ~__COLOR);
508 	else
509 		lp->attr = attributes | win->battr;
510 
511 	SET_WCOL(*lp, cw);
512 
513 #ifdef DEBUG
514 	__CTRACE(__CTRACE_INPUT,
515 		 "_cursesi_addwchar: add spacing char 0x%x, attr 0x%x\n",
516 		 lp->ch, lp->attr);
517 #endif /* DEBUG */
518 
519 	if (wch->elements > 1) {
520 		for (i = 1; i < wch->elements; i++) {
521 			np = malloc(sizeof(nschar_t));
522 			if (!np)
523 				return ERR;;
524 			np->ch = wch->vals[i];
525 			np->next = lp->nsp;
526 #ifdef DEBUG
527 			__CTRACE(__CTRACE_INPUT,
528 			    "_cursesi_addwchar: add non-spacing char 0x%x\n", np->ch);
529 #endif /* DEBUG */
530 			lp->nsp = np;
531 		}
532 	}
533 #ifdef DEBUG
534 	__CTRACE(__CTRACE_INPUT, "_cursesi_addwchar: non-spacing list header: %p\n",
535 	    lp->nsp);
536 	__CTRACE(__CTRACE_INPUT, "_cursesi_addwchar: add rest columns (%d:%d)\n",
537 		sx + 1, sx + cw - 1);
538 	__CTRACE(__CTRACE_INPUT, "_cursesi_addwchar: *x = %d, win->maxx = %d\n", *x, win->maxx);
539 #endif /* DEBUG */
540 	for (tp = lp + 1, *x = sx + 1; *x - sx <= cw - 1; tp++, (*x)++) {
541 		if (tp->nsp) {
542 			__cursesi_free_nsp(tp->nsp);
543 			tp->nsp = NULL;
544 		}
545 		tp->ch = wch->vals[0];
546 		tp->attr = lp->attr & WA_ATTRIBUTES;
547 		/* Mark as "continuation" cell */
548 		tp->attr |= __WCWIDTH;
549 	}
550 
551 	if (*x == win->maxx) {
552 #ifdef DEBUG
553 	__CTRACE(__CTRACE_INPUT, "_cursesi_addwchar: do line wrap\n");
554 #endif /* DEBUG */
555 		newx = win->maxx - 1 + win->ch_off;
556 		if (newx > *(*lnp)->lastchp)
557 			*(*lnp)->lastchp = newx;
558 		__touchline(win, *y, sx, (int) win->maxx - 1);
559 		*x = sx = 0;
560 		if (*y == win->scr_b) {
561 			if (!(win->flags & __SCROLLOK))
562 				return ERR;
563 			scroll(win);
564 		} else {
565 			(*y)++;
566 		}
567 		lp = &win->alines[*y]->line[0];
568 		(*lnp) = win->alines[*y];
569 	} else {
570 
571 		/* clear the remaining of the current character */
572 		if (*x && *x < win->maxx) {
573 			ex = sx + cw;
574 			tp = &win->alines[*y]->line[ex];
575 			while (ex < win->maxx && WCOL(*tp) < 0) {
576 #ifdef DEBUG
577 				__CTRACE(__CTRACE_INPUT,
578 				    "_cursesi_addwchar: clear "
579 				    "remaining of current char (%d,%d)nn",
580 				    *y, ex);
581 #endif /* DEBUG */
582 				tp->ch = (wchar_t) btowc((int) win->bch);
583 				if (_cursesi_copy_nsp(win->bnsp, tp) == ERR)
584 					return ERR;
585 				tp->attr = win->battr;
586 				SET_WCOL(*tp, 1);
587 				tp++, ex++;
588 			}
589 			newx = ex - 1 + win->ch_off;
590 			if (newx > *(*lnp)->lastchp)
591 				*(*lnp)->lastchp = newx;
592 			__touchline(win, *y, sx, ex - 1);
593 		}
594 	}
595 
596 #ifdef DEBUG
597 	__CTRACE(__CTRACE_INPUT, "_cursesi_addwchar: %d : 0x%x\n", lp->ch, lp->attr);
598 	__CTRACE(__CTRACE_INPUT, "_cursesi_addwchar: *x = %d, *y = %d, win->maxx = %d\n", *x, *y, win->maxx);
599 #endif /* DEBUG */
600 	__sync(win);
601 	return OK;
602 #endif
603 }
604