xref: /illumos-gate/usr/src/lib/libxcurses/src/libc/xcurses/m_cc.c (revision 1e56f352c1c208679012bca47d552e127f5b1072)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*
28  * m_cc.c
29  *
30  * XCurses Library
31  *
32  * Copyright 1990, 1995 by Mortice Kern Systems Inc.  All rights reserved.
33  *
34  */
35 
36 #if M_RCSID
37 #ifndef lint
38 static char rcsID[] = "$Header: /rd/src/libc/xcurses/rcs/m_cc.c 1.8 1995/09/20 15:26:52 ant Exp $";
39 #endif
40 #endif
41 
42 #include <private.h>
43 #include <errno.h>
44 #include <limits.h>
45 #include <string.h>
46 #include <m_wio.h>
47 
48 typedef struct {
49 	int max;
50 	int used;
51 	char *mbs;
52 } t_string;
53 
54 static int
55 write_string(byte, sp)
56 int byte;
57 t_string *sp;
58 {
59 	if (sp->max <= sp->used)
60 		return EOF;
61 
62 	sp->mbs[sp->used++] = byte;
63 
64 	return byte;
65 }
66 
67 /*
68  * Convert a wint_t string into a multibyte string.
69  *
70  * The conversion stops at the end of string or the first WEOF.
71  * Return the number of bytes successfully placed into mbs.
72  */
73 int
74 wistombs(mbs, wis, n)
75 char *mbs;
76 const wint_t *wis;
77 int n;
78 {
79 	int last;
80 	t_string string = { 0 };
81 	t_wide_io convert = { 0 };
82 
83 	string.max = n;
84 	string.mbs = mbs;
85 	convert.object = (void *) &string;
86 	convert.put = (int (*)(int, void *)) write_string;
87 
88 	for (;; ++wis) {
89 		/* In case of error, rewind string to the last character. */
90 		last = string.used;
91 
92 		if (m_wio_put(*wis, &convert) < 0) {
93 			string.used = last;
94 			break;
95 		}
96 
97 		/* Test for end of string AFTER trying to copy into the
98 		 * buffer, because m_wio_put() has to handle state changes
99 		 * back to the initial state on '\0' or WEOF.
100 		 */
101 		if (*wis == '\0' || *wis == WEOF)
102 			break;
103 	}
104 
105 	/* m_wio_put() does not write '\0', because the "stream"
106 	 * object is considered to be in "text" mode, which in the
107 	 * case of file I/O produces undefined results for systems
108 	 * using locking-shift character sets.
109 	 */
110 	string.mbs[string.used] = '\0';
111 
112 	return string.used;
113 }
114 
115 /*
116  * Convert a wint_t string (filled in by wgetn_wstr()) to a wchar_t string.
117  * The conversion stops at the end of string or the first WEOF.  Return the
118  * number of successfully copied characters.
119  *
120  * This routinue should be used when sizeof (wchar_t) < sizeof (wint_t).
121  */
122 int
123 wistowcs(wcs, wis, n)
124 wchar_t *wcs;
125 const wint_t *wis;
126 int n;
127 {
128 	wchar_t *start;
129 
130 	if (n < 0)
131 		n = INT_MAX;
132 
133 	for (start = wcs; *wis != '\0' && 0 < n; ++wis, ++wcs, --n) {
134 		if (*wis == WEOF)
135 			break;
136 		*wcs = (wchar_t) *wis;
137 	}
138 	*wcs = '\0';
139 
140 	return (int) (wcs - start);
141 }
142 
143 /*
144  * Convert a chtype to a cchar_t.
145  */
146 int
147 __m_chtype_cc(ch, cc)
148 chtype ch;
149 cchar_t *cc;
150 {
151 	char mb;
152 
153 	cc->_f = 1;
154         cc->_n = 1;
155 	mb = (char)(ch & A_CHARTEXT);
156 
157 	if (mbtowc(cc->_wc, &mb, 1) < 0)
158 		return ERR;
159 
160         cc->_co = (short) PAIR_NUMBER(ch);
161         cc->_at = (attr_t) ((ch & (A_ATTRIBUTES & ~A_COLOR)) >> 16);
162 
163 	return OK;
164 }
165 
166 /*
167  * Return a complex character as a chtype.
168  */
169 chtype
170 __m_cc_chtype(cc)
171 const cchar_t *cc;
172 {
173 	chtype ch;
174 	unsigned char mb[MB_LEN_MAX];
175 
176 	/* Is it a single-byte character? */
177 	if (cc->_n != 1 || wctomb((char *) mb, cc->_wc[0]) != 1)
178 		return (chtype) ERR;
179 
180 	ch = ((chtype) cc->_at << 16) & ~A_COLOR;
181 	ch |= COLOR_PAIR(cc->_co) | mb[0];
182 
183 	return ch;
184 }
185 
186 /*
187  * Convert a complex character's "character" into a multibyte string.
188  * The attribute and colour are ignored.
189  *
190  * If 0 < n, set a new multibyte string and convert the first character,
191  * returning either -1 on error or the number of bytes used to convert the
192  * character.
193  *
194  * If n == 0, continue appending to the current multibyte string and return
195  * a value as for 0 < n case.
196  *
197  * If n < 0, return the accumulated byte length of the current multibyte
198  * string and do nothing else.
199  *
200  * When converting a character, a null cchar_t pointer will force the initial
201  * shift state and append a '\0' to the multibyte string.  The return value
202  * will instead by the number of bytes used to shift to the initial state,
203  * and exclude the '\0'.
204  */
205 int
206 __m_cc_mbs(cc, mbs, n)
207 const cchar_t *cc;
208 char *mbs;
209 int n;
210 {
211 	cchar_t *cp;
212 	int i, bytes, count, last;
213 	mbstate_t initial = { 0 };
214 	static t_string string = { 0 };
215 	static t_wide_io convert = { 0 };
216 
217 	if (n < 0) {
218 		/* Return total number of bytes written to multibyte string. */
219 		return string.used;
220 	} else if (0 < n) {
221 		/* Start a new conversion. */
222 		string.max = n;
223 		string.used = 0;
224 		string.mbs = mbs;
225 
226 		convert._state = initial;
227 		convert._next = convert._size = 0;
228 		convert.object = (void *) &string;
229 		convert.put = (int (*)(int, void *)) write_string;
230 	} /* else n == 0, continue appending to previous mbs. */
231 
232 	/* In case of error, rewind string to the last character. */
233 	last = string.used;
234 
235 	if (cc == (cchar_t *) 0) {
236 		/* Force initial shift state. */
237 		if ((count = m_wio_put('\0', &convert)) < 0) {
238 			string.used = last;
239 			return -1;
240 		}
241 
242 		if (string.used < string.max)
243 			string.mbs[string.used++] = '\0';
244 	} else {
245 		for (count = i = 0; i < cc->_n; ++i, count += bytes)
246 			if ((bytes = m_wio_put(cc->_wc[i], &convert)) < 0) {
247 				string.used = last;
248 				return -1;
249 			}
250 	}
251 
252 	return count;
253 }
254 
255 /*
256  * Convert a stty character into a wchar_t.
257  */
258 int
259 __m_tty_wc(index, wcp)
260 int index;
261 wchar_t *wcp;
262 {
263 	char mb;
264 	int code;
265 
266 	/* Refer to _shell instead of _prog, since _shell will
267 	 * correctly reflect the user's prefered settings, whereas
268 	 * _prog may not have been initialised if both input and
269 	 * output have been redirected.
270 	 */
271 	mb = cur_term->_shell.c_cc[index];
272 	code = mbtowc(wcp, &mb, 1) < 0 ? ERR : OK;
273 
274 	return code;
275 }
276 
277 /*
278  * Build a cchar_t from the leading spacing and non-spacing characters
279  * in the multibyte character string.  Only one spacing character is copied
280  * from the multibyte character string.
281  *
282  * Return the number of characters copied from the string, or -1 on error.
283  */
284 int
285 __m_mbs_cc(const char *mbs, attr_t at, short co, cchar_t *cc)
286 {
287 	wchar_t wc;
288 	const char *start;
289 	int i, nbytes, width, have_one;
290 
291 	for (start = mbs, have_one = i = 0; *mbs != '\0'; mbs += nbytes, ++i) {
292 		if (sizeof cc->_wc <= i)
293 			/* Too many characters. */
294 			return -1;
295 
296 		if ((nbytes = mbtowc(&wc, mbs, UINT_MAX)) < 0)
297 			/* Invalid multibyte sequence. */
298 			return -1;
299 
300 		if (nbytes == 0)
301 			/* Remainder of string evaluates to the null byte. */
302 			break;
303 
304 		if (iscntrl(*mbs))
305 			/* Treat control codes like a spacing character. */
306 			width = 1;
307 		else if ((width = wcwidth(wc)) < 0)
308 			return -1;
309 
310 		/* Do we have a spacing character? */
311 		if (0 < width) {
312 			if (have_one)
313 				break;
314 			have_one = 1;
315 		}
316 
317 		cc->_wc[i] = wc;
318 	}
319 
320         cc->_f = 1;
321 	cc->_n = i;
322         cc->_co = co;
323         cc->_at = at;
324 
325 	(void) __m_cc_sort(cc);
326 
327 	return (int) (mbs - start);
328 }
329 
330 /*
331  * Build a cchar_t from the leading spacing and non-spacing characters
332  * in the wide character string.  Only one spacinig character is copied
333  * from the wide character string.
334  *
335  * Return the number of characters copied from the string, or -1 on error.
336  */
337 int
338 __m_wcs_cc(const wchar_t *wcs, attr_t at, short co, cchar_t *cc)
339 {
340 	short i;
341 	int width, have_one;
342 	const wchar_t *start;
343 
344 	for (start = wcs, have_one = i = 0; *wcs != '\0'; ++wcs, ++i) {
345 		if (sizeof cc->_wc <= i)
346 			/* Too many characters. */
347 			return -1;
348 
349 		if ((width = wcwidth(*wcs)) < 0)
350 			return -1;
351 
352 		if (0 < width) {
353 			if (have_one)
354 				break;
355 			have_one = 1;
356 		}
357 
358 		cc->_wc[i] = *wcs;
359 	}
360 
361         cc->_f = 1;
362 	cc->_n = i;
363         cc->_co = co;
364         cc->_at = at;
365 
366 	(void) __m_cc_sort(cc);
367 
368 	return (int) (wcs - start);
369 }
370 
371 /*
372  * Convert a single wide character into a complex character.
373  */
374 int
375 __m_wc_cc(wint_t wc, cchar_t *cc)
376 {
377 	wchar_t wcs[2];
378 
379 	if (wc == WEOF)
380 		return -1;
381 
382 	wcs[0] = (wchar_t)wc;
383 	wcs[1] = '\0';
384 	(void) __m_wcs_cc(wcs, WA_NORMAL, 0, cc);
385 
386 	return 0;
387 }
388 
389 /*
390  * Sort a complex character into a spacing character followed
391  * by any non-spacing characters in increasing order of oridinal
392  * values.  This facilitates both comparision and writting of
393  * complex characters.  More than one spacing character is
394  * considered an error.
395  *
396  * Return the spacing character's column width or -1 if more
397  * than one spacing character appears in cc.
398  */
399 int
400 __m_cc_sort(cc)
401 cchar_t *cc;
402 {
403 	wchar_t wc;
404 	int width, i, j, spacing;
405 
406 	/* Find spacing character and place in as first element. */
407 	for (width = spacing = i = 0; i < cc->_n; ++i) {
408 		j = wcwidth(cc->_wc[i]);
409 		if (0 < j) {
410 			/* More than one spacing character is an error. */
411 			if (0 < width)
412 				return -1;
413 
414 			wc = cc->_wc[0];
415 			cc->_wc[0] = cc->_wc[i];
416 			cc->_wc[i]  = wc;
417 
418 			spacing = 1;
419 			width = j;
420 			break;
421 		}
422 	}
423 
424 	/* Bubble sort small array. */
425 	for (i = spacing; i < cc->_n; ++i) {
426 		for (j = cc->_n - 1; i < j; --j) {
427 			if (cc->_wc[j-1] > cc->_wc[j]) {
428 				wc = cc->_wc[j];
429 				cc->_wc[j] = cc->_wc[j-1];
430 				cc->_wc[j-1]  = wc;
431 			}
432 		}
433 	}
434 
435 	return width;
436 }
437 
438 /*
439  * Return width inn screen columns of the character.
440  */
441 int
442 __m_cc_width(cc)
443 const cchar_t *cc;
444 {
445 	return wcwidth(cc->_wc[0]);
446 }
447 
448 /*
449  * Return the first column of a multi-column character, in window.
450  */
451 int
452 __m_cc_first(w, y, x)
453 WINDOW *w;
454 int y, x;
455 {
456 	register cchar_t *lp;
457 
458 	for (lp = w->_line[y]; 0 < x; --x) {
459 		if (lp[x]._f)
460 			break;
461 	}
462 
463 	return x;
464 }
465 
466 /*
467  * Return the start of the next multi-column character, in window.
468  */
469 int
470 __m_cc_next(w, y, x)
471 WINDOW *w;
472 int y, x;
473 {
474 	cchar_t *lp;
475 
476 	for (lp = w->_line[y]; ++x < w->_maxx; ) {
477 		if (lp[x]._f)
478 			break;
479 	}
480 
481 	return x;
482 }
483 
484 /*
485  * Return true if valid last column of a multi-column character.
486  */
487 int
488 __m_cc_islast(w, y, x)
489 WINDOW *w;
490 int y, x;
491 {
492 	int first, width;
493 
494 	first = __m_cc_first(w, y, x);
495 	width = __m_cc_width(&w->_line[y][x]);
496 
497 	return first + width == x + 1;
498 }
499 
500 /*
501  * Replace the character at the current cursor location
502  * according to the column width of the character.  The
503  * cursor does not advance.
504  *
505  * Return -1 if the character won't fit on the line and the background
506  * was written in its place; else return the width of the character in
507  * screen columns.
508  */
509 int
510 __m_cc_replace(w, y, x, cc, as_is)
511 WINDOW *w;
512 int y, x;
513 const cchar_t *cc;
514 int as_is;
515 {
516 	int i, width;
517 	cchar_t *cp, *np;
518 
519 	width = __m_cc_width(cc);
520 
521         /* If we try to write a broad character that would exceed the
522          * right margin, then write the background character instead.
523          */
524 	if (0 < width && w->_maxx < x + width) {
525 		(void) __m_cc_erase(w, y, x, y, w->_maxx-1);
526 		return -1;
527 	}
528 
529 	/* Erase the region to be occupied by the new character.
530 	 * __m_cc_erase() will erase whole characters so that
531 	 * writing a multicolumn character that overwrites the
532 	 * trailing and leading portions of two already existing
533 	 * multicolumn characters, erases the remaining portions.
534 	 */
535 	(void) __m_cc_erase(w, y, x, y, x + width - 1);
536 
537 	/* Write the first column of the character. */
538 	cp = &w->_line[y][x++];
539 	if (cc->_wc[0] == ' ' || cc->_wc[0] == M_MB_L(' ')) {
540 		*cp = w->_bg;
541 		cp->_at |= cc->_at;
542 		if (cc->_co != 0)
543 			cp->_co = cc->_co;
544 	} else {
545 		(void) __m_wacs_cc(cc, cp);
546 		if (cc->_co == 0)
547 			cp->_co = w->_fg._co;
548 	}
549 
550 	cp->_at |= w->_fg._at | w->_bg._at;
551 
552 	/* Mark this as the first column of the character. */
553 	cp->_f = 1;
554 
555 	/* Duplicate the character in every column the character occupies. */
556 	for (np = cp + 1, i = 1; i < width; ++i, ++x, ++np) {
557 		*np = *cp;
558 		np->_f = 0;
559 	}
560 
561 	return width;
562 }
563 
564 int
565 __m_do_scroll(WINDOW *w, int y, int x, int *yp, int *xp)
566 {
567 	if (w->_maxx <= x)
568 		x = w->_maxx-1;
569 
570 	++y;
571 
572 	if (y == w->_bottom) {
573 		--y;
574 		if (w->_flags & W_CAN_SCROLL) {
575 			if (wscrl(w, 1) == ERR)
576 				return ERR;
577 			x = 0;
578 		}
579 	} else if (w->_maxy <= y) {
580 		y = w->_maxy-1;
581 	} else {
582 		/* The cursor wraps for any line (in and out of the scroll
583 		 * region) except for the last line of the scroll region.
584 		 */
585 		x = 0;
586 	}
587 
588 	*yp = y;
589 	*xp = x;
590 
591 	return OK;
592 }
593 
594 /*
595  * Add the character at the current cursor location
596  * according to the column width of the character.
597  * The cursor will be advanced.
598  *
599  * Return ERR if adding the character causes the
600  * screen to scroll, when it is disallowed.
601  */
602 int
603 __m_cc_add(w, y, x, cc, as_is, yp, xp)
604 WINDOW *w;
605 int y, x;
606 const cchar_t *cc;
607 int as_is, *yp, *xp;
608 {
609 	int nx, width, code = ERR;
610 
611 #ifdef M_CURSES_TRACE
612 	__m_trace(
613 		"__m_cc_add(%p, %d, %d, %p, %d, %p, %p)",
614 		w, y, x, cc, as_is, yp, xp
615 	);
616 #endif
617 
618 	switch (cc->_wc[0]) {
619 	case '\t':
620 		nx = x + (8 - (x & 07));
621 		if (__m_cc_erase(w, y, x, y, nx-1) == -1)
622 			goto error;
623 		x = nx;
624 
625 		if (w->_maxx <= x) {
626 			if (__m_do_scroll(w, y, x, &y, &x) == ERR)
627 				goto error;
628 		}
629 		break;
630 	case '\n':
631 		if (__m_cc_erase(w, y, x, y, w->_maxx-1) == -1)
632 			goto error;
633 
634 		if (__m_do_scroll(w, y, x, &y, &x) == ERR)
635 			goto error;
636 		break;
637 	case '\r':
638 		x = 0;
639 		break;
640 	case '\b':
641 		if (0 < x)
642 			--x;
643 		break;
644 	default:
645 		width = __m_cc_replace(w, y, x, cc, as_is);
646 
647 		x += width;
648 
649 		if (width < 0 || w->_maxx <= x) {
650 			if (__m_do_scroll(w, y, x, &y, &x) == ERR)
651 				goto error;
652 
653 			if (width < 0)
654 				x += __m_cc_replace(w, y, x, cc, as_is);
655 		}
656 	}
657 
658 	code = OK;
659 error:
660 	*yp = y;
661 	*xp = x;
662 
663 	return __m_return_code("__m_cc_add", code);
664 }
665 
666 /*
667  * Erase region from (y,x) to (ly, lx) inclusive.  The
668  * region is extended left and right in the case where
669  * the portions of a multicolumn characters are erased.
670  *
671  * Return -1 if the region is not an integral multiple
672  * of the background character, else zero for success.
673  */
674 int
675 __m_cc_erase(w, y, x, ly, lx)
676 WINDOW *w;
677 int y, x, ly, lx;
678 {
679 	cchar_t *cp;
680 	int i, width;
681 
682 	if (ly < y)
683 		return -1;
684 
685 	if (w->_maxy <= ly)
686 		ly = w->_maxy - 1;
687 	if (w->_maxx <= lx)
688 		lx = w->_maxx - 1;
689 
690 	/* Erase from first whole character (inclusive) to next
691 	 * character (exclusive).
692 	 */
693 	x = __m_cc_first(w, y, x);
694 	lx = __m_cc_next(w, ly, lx) - 1;
695 
696 	/* Is the region to blank out an integral width of the
697 	 * background character?
698 	 */
699 	width = __m_cc_width(&w->_bg);
700 
701 	if (y < ly && (lx + 1) % width != 0)
702 		return -1;
703 	if ((lx - x + 1) % width != 0)
704 		return -1;
705 
706 	for (; y < ly; ++y, x = 0) {
707 		if (x < w->_first[y])
708 			w->_first[y] = (short) x;
709 
710 		for (cp = w->_line[y], i = 0; x < w->_maxx; ++x, ++i) {
711 			cp[x] = w->_bg;
712 
713 			/* The start of each new character will be set true
714 			 * while internal columns of the character will be
715 			 * reset to false.
716 			 */
717 			cp[x]._f = (short) (i % width == 0);
718 		}
719 
720 		if (w->_last[y] < x)
721 			w->_last[y] = (short) x;
722 	}
723 
724 	if (x < w->_first[y])
725 		w->_first[y] = (short) x;
726 
727 	for (cp = w->_line[y], i = 0; x <= lx; ++x, ++i) {
728 		cp[x] = w->_bg;
729 
730 		/* The start of each new character will be set true
731 		 * while internal columns of the character will be
732 		 * reset to false.
733 		 */
734 		cp[x]._f = (short) (i % width == 0);
735 	}
736 
737 	if (w->_last[y] < x)
738 		w->_last[y] = (short) x;
739 
740 	return 0;
741 }
742 
743 /*
744  * Expand the character to the left or right of the given position.
745  * Return the value returned by __m_cc_replace().
746  */
747 int
748 __m_cc_expand(w, y, x, side)
749 WINDOW *w;
750 int y, x, side;
751 {
752 	cchar_t cc;
753 	int dx, width;
754 
755 	width = __m_cc_width(&w->_line[y][x]);
756 
757 	if (side < 0)
758 		dx = __m_cc_next(w, y, x) - width;
759 	else if (0 < side)
760 		dx = __m_cc_first(w, y, x);
761 	else
762 		return -1;
763 
764 	/* __m_cc_replace() will erase the region containing
765 	 * the character we want to expand.
766 	 */
767 	cc = w->_line[y][x];
768 
769 	return __m_cc_replace(w, y, dx, &cc, 0);
770 }
771 
772 /*
773  * Return true if characters are equal.
774  *
775  * NOTE to guarantee correct results, make sure that both
776  * characters have been passed through __m_cc_sort().
777  */
778 int
779 __m_cc_compare(c1, c2, exact)
780 const cchar_t *c1, *c2;
781 int exact;
782 {
783 	int i;
784 
785 	if (exact && c1->_f != c2->_f)
786 		return 0;
787 	if (c1->_n != c2->_n)
788 		return 0;
789 	if ((c1->_at & ~WA_COOKIE) != (c2->_at & ~WA_COOKIE))
790 		return 0;
791 	if (c1->_co != c2->_co)
792 		return 0;
793 
794 	for (i = 0; i < c1->_n; ++i)
795 		if (c1->_wc[i] != c2->_wc[i])
796 			return 0;
797 
798 	return 1;
799 }
800 
801 /*
802  * Write to the stream the character portion of a cchar_t.
803  */
804 int
805 __m_cc_write(cc)
806 const cchar_t *cc;
807 {
808 	size_t i, j;
809 	char mb[MB_LEN_MAX];
810 
811 	errno = 0;
812 	for (i = 0; i < cc->_n; ++i) {
813 		j = wcrtomb(mb, cc->_wc[i], &__m_screen->_state);
814 		if (errno != 0)
815 			return EOF;
816 		if (fwrite(mb, sizeof *mb, j, __m_screen->_of) == 0)
817 			return EOF;
818 	}
819 
820 	return 0;
821 }
822