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