m_cc.c revision 759f6fc92e52ccbe95dcc3cb8d747078713d4a4d
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 (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21/*
22 * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
23 * Use is subject to license terms.
24 */
25
26#pragma ident	"%Z%%M%	%I%	%E% SMI"
27
28/* LINTLIBRARY */
29
30/*
31 * m_cc.c
32 *
33 * XCurses Library
34 *
35 * Copyright 1990, 1995 by Mortice Kern Systems Inc.  All rights reserved.
36 *
37 */
38
39#if M_RCSID
40#ifndef lint
41static char rcsID[] =
42"$Header: /team/ps/sun_xcurses/archive/local_changes/xcurses/src/lib/"
43"libxcurses/src/libc/xcurses/rcs/m_cc.c 1.40 1998/06/12 12:45:39 "
44"cbates Exp $";
45#endif
46#endif
47
48#include <private.h>
49#include <limits.h>
50#include <m_wio.h>
51#include <string.h>
52
53typedef struct {
54	int	max;
55	int	used;
56	char	*mbs;
57} t_string;
58
59static int
60write_string(int byte, t_string *sp)
61{
62	if (sp->max <= sp->used)
63		return (EOF);
64
65	sp->mbs[sp->used++] = (char)byte;
66
67	return (byte);
68}
69
70/*
71 * Convert a wint_t string into a multibyte string.
72 *
73 * The conversion stops at the end of string or the first WEOF.
74 * Return the number of bytes successfully placed into mbs.
75 */
76int
77wistombs(char *mbs, const wint_t *wis, 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		/*
98		 * Test for end of string AFTER trying to copy into the
99		 * buffer, because m_wio_put() has to handle state changes
100		 * back to the initial state on '\0' or WEOF.
101		 */
102		if (*wis == '\0' || *wis == WEOF)
103			break;
104	}
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(wchar_t *wcs, const wint_t *wis, int n)
126{
127	wchar_t	*start;
128
129	if (n < 0)
130		n = INT_MAX;
131
132	for (start = wcs; *wis != '\0' && 0 < n; ++wis, ++wcs, --n) {
133		if (*wis == WEOF)
134			break;
135		*wcs = (wchar_t)*wis;
136	}
137	*wcs = '\0';
138
139	/* (wcs - start) should be enough small to fit in "int" */
140	return ((int)(wcs - start));
141}
142
143void
144__m_touch_locs(WINDOW *w, int row, int firstCol, int lastCol)
145{
146	if (w) {
147		if (firstCol < w->_first[row])
148			w->_first[row] = (short)firstCol;
149		if (lastCol > w->_last[row])
150			w->_last[row] = (short)lastCol;
151	}
152}
153
154/*
155 * Convert a chtype to a cchar_t.
156 */
157int
158__m_chtype_cc(chtype ch, cchar_t *cc)
159{
160	char	mb;
161
162	cc->_f = 1;
163	cc->_n = 1;
164	mb = (char)(ch & A_CHARTEXT);
165
166	cc->_co = (short)PAIR_NUMBER((int)ch);
167	cc->_at = (attr_t)((ch & (A_ATTRIBUTES & ~A_COLOR)) >> 16);
168
169	if (mb == 0)
170		cc->_wc[0] = cc->_wc[1] = 0;
171	else if (mbtowc(cc->_wc, &mb, 1) < 0) {
172		return (ERR);
173	}
174	return (OK);
175}
176
177/*
178 * Return a complex character as a chtype.
179 */
180chtype
181__m_cc_chtype(const cchar_t *cc)
182{
183	chtype	ch;
184	unsigned char	mb[MB_LEN_MAX];
185
186	/* Is it a single-byte character? */
187	if (cc->_n != 1 || wctomb((char *)mb, cc->_wc[0]) != 1)
188		return ((chtype) ERR);
189
190	ch = ((chtype) cc->_at << 16) & ~A_COLOR;
191	ch |= COLOR_PAIR(cc->_co) | mb[0];
192
193	return (ch);
194}
195
196/*
197 * Convert a complex character's "character" into a multibyte string.
198 * The attribute and colour are ignored.
199 *
200 * If 0 < n, set a new multibyte string and convert the first character,
201 * returning either -1 on error or the number of bytes used to convert the
202 * character.
203 *
204 * If n == 0, continue appending to the current multibyte string and return
205 * a value as for 0 < n case.
206 *
207 * If n < 0, return the accumulated byte length of the current multibyte
208 * string and do nothing else.
209 *
210 * When converting a character, a null cchar_t pointer will force the initial
211 * shift state and append a '\0' to the multibyte string.  The return value
212 * will instead by the number of bytes used to shift to the initial state,
213 * and exclude the '\0'.
214 */
215int
216__m_cc_mbs(const cchar_t *cc, char *mbs, int n)
217{
218	int	i, bytes, count, last;
219	static t_string	string = { 0 };
220	static t_wide_io	convert = { 0 };
221
222	if (n < 0) {
223		/* Return total number of bytes written to multibyte string. */
224		return (string.used);
225	} else if (0 < n) {
226		/* Start a new conversion. */
227		string.max = n;
228		string.used = 0;
229		string.mbs = mbs;
230
231		convert._next = convert._size = 0;
232		convert.object = (void *) &string;
233		convert.put = (int (*)(int, void *)) write_string;
234	} /* else n == 0, continue appending to previous mbs. */
235
236	/* In case of error, rewind string to the last character. */
237	last = string.used;
238
239	if (cc == NULL) {
240		/* Force initial shift state. */
241		if ((count = m_wio_put('\0', &convert)) < 0) {
242			string.used = last;
243			return (-1);
244		}
245
246		if (string.used < string.max)
247			string.mbs[string.used++] = '\0';
248	} else {
249		for (count = i = 0; i < cc->_n; ++i, count += bytes)
250			if ((bytes = m_wio_put(cc->_wc[i], &convert)) < 0) {
251				string.used = last;
252				return (-1);
253			}
254	}
255
256	return (count);
257}
258
259/*
260 * Convert a stty character into a wchar_t.
261 */
262int
263__m_tty_wc(int index, wchar_t *wcp)
264{
265	char	mb;
266	int	code;
267
268	/*
269	 * Refer to _shell instead of _prog, since _shell will
270	 * correctly reflect the user's prefered settings, whereas
271	 * _prog may not have been initialised if both input and
272	 * output have been redirected.
273	 */
274	mb = (char)PTERMIOS(_shell)->c_cc[index];
275	if (mb)
276	    code = mbtowc(wcp, &mb, 1) < 0 ? ERR : OK;
277	else
278	    code = ERR;
279
280	return (code);
281}
282
283/*
284 * Build a cchar_t from the leading spacing and non-spacing characters
285 * in the multibyte character string.  Only one spacing character is copied
286 * from the multibyte character string.
287 *
288 * Return the number of characters copied from the string, or -1 on error.
289 */
290int
291__m_mbs_cc(const char *mbs, attr_t at, short co, cchar_t *cc)
292{
293	wchar_t	wc;
294	const char	*start;
295	int	i, nbytes, width, have_one;
296
297	for (start = mbs, have_one = i = 0; *mbs != '\0'; mbs += nbytes, ++i) {
298		if (sizeof (cc->_wc) <= i)
299			/* Too many characters. */
300			return (-1);
301
302		if ((nbytes = mbtowc(&wc, mbs, UINT_MAX)) < 0)
303			/* Invalid multibyte sequence. */
304			return (-1);
305
306		if (nbytes == 0)
307			/* Remainder of string evaluates to the null byte. */
308			break;
309
310		if (iscntrl(*mbs))
311			/* Treat control codes like a spacing character. */
312			width = 1;
313		else
314			width = wcwidth(wc);
315
316		/* Do we have a spacing character? */
317		if (0 < width) {
318			if (have_one)
319				break;
320			have_one = 1;
321		}
322
323		cc->_wc[i] = wc;
324	}
325
326	cc->_f = 1;
327	cc->_n = (short)i;
328	cc->_co = co;
329	cc->_at = at;
330
331	(void) __m_cc_sort(cc);
332
333	/* (mbs - start) should be enough small to fit in "int" */
334	return ((int)(mbs - start));
335}
336
337/*
338 * Build a cchar_t from the leading spacing and non-spacing characters
339 * in the wide character string.  Only one spacinig character is copied
340 * from the wide character string.
341 *
342 * Return the number of characters copied from the string, or -1 on error.
343 */
344int
345__m_wcs_cc(const wchar_t *wcs, attr_t at, short co, cchar_t *cc)
346{
347	short	i;
348	const wchar_t	*start;
349
350	for (start = wcs, i = 0; *wcs != '\0'; ++wcs, ++i) {
351		if (sizeof (cc->_wc) <= i) {
352			/* Too many characters. */
353			return (-1);
354		}
355
356		if (wcwidth(*wcs) > 0) {
357			if (i != 0)
358				break;
359		} else if ((*wcs == L'\n') || (*wcs == L'\t') ||
360			(*wcs == L'\b') || (*wcs == L'\r'))	{
361			if (i != 0)
362				break;
363			cc->_wc[i++] = *wcs++;
364			break;
365		}
366
367		cc->_wc[i] = *wcs;
368	}
369
370	cc->_f = 1;
371	cc->_n = i;
372	cc->_co = co;
373	cc->_at = at;
374
375	/* (wcs - start) should be enough small to fit in "int" */
376	return ((int)(wcs - start));
377}
378
379/*
380 * Convert a single wide character into a complex character.
381 */
382int
383__m_wc_cc(wint_t wc, cchar_t *cc)
384{
385	wchar_t	wcs[2];
386
387	if (wc == WEOF)
388		return (-1);
389
390	if (wc == 0) {
391		/*
392		 * converting a null character to a complex character.
393		 * __m_wcs_cc assumes that the string is empty, so
394		 * just do it here.
395		 */
396		cc->_f = 1;
397		cc->_n = 1;
398		cc->_co = 0;
399		cc->_at = WA_NORMAL;
400		cc->_wc[0] = 0;
401		cc->_wc[1] = 0;
402	} else {
403		/* A real character */
404		wcs[0] = (wchar_t)wc;
405		wcs[1] = '\0';
406		(void) __m_wcs_cc(wcs, WA_NORMAL, 0, cc);
407	}
408
409	return (0);
410}
411
412/*
413 * Sort a complex character into a spacing character followed
414 * by any non-spacing characters in increasing order of oridinal
415 * values.  This facilitates both comparision and writting of
416 * complex characters.  More than one spacing character is
417 * considered an error.
418 *
419 * Return the spacing character's column width or -1 if more
420 * than one spacing character appears in cc.
421 */
422int
423__m_cc_sort(cchar_t *cc)
424{
425	wchar_t	wc;
426	int	width, i, j, spacing;
427
428	/* Find spacing character and place in as first element. */
429	for (width = spacing = i = 0; i < cc->_n; ++i) {
430		j = wcwidth(cc->_wc[i]);
431		if (0 < j) {
432			/* More than one spacing character is an error. */
433			if (0 < width)
434				return (-1);
435
436			wc = cc->_wc[0];
437			cc->_wc[0] = cc->_wc[i];
438			cc->_wc[i] = wc;
439
440			spacing = 1;
441			width = j;
442			break;
443		}
444	}
445
446	/* Bubble sort small array. */
447	for (i = spacing; i < cc->_n; ++i) {
448		for (j = cc->_n - 1; i < j; --j) {
449			if (cc->_wc[j-1] > cc->_wc[j]) {
450				wc = cc->_wc[j];
451				cc->_wc[j] = cc->_wc[j-1];
452				cc->_wc[j-1]  = wc;
453			}
454		}
455	}
456
457	return (width);
458}
459
460/*
461 * Return the first column of a multi-column character, in window.
462 */
463int
464__m_cc_first(WINDOW *w, int y, int x)
465{
466	cchar_t	*lp;
467
468	for (lp = w->_line[y]; 0 < x; --x) {
469		if (lp[x]._f)
470			break;
471	}
472
473	return (x);
474}
475
476/*
477 * Return the start of the next multi-column character, in window.
478 */
479int
480__m_cc_next(WINDOW *w, int y, int x)
481{
482	cchar_t	*lp;
483
484	for (lp = w->_line[y]; ++x < w->_maxx; ) {
485		if (lp[x]._f)
486			break;
487	}
488
489	return (x);
490}
491
492/*
493 * Return true if valid last column of a multi-column character.
494 */
495int
496__m_cc_islast(WINDOW *w, int y, int x)
497{
498	int	first, width;
499
500	first = __m_cc_first(w, y, x);
501	width = __m_cc_width(&w->_line[y][x]);
502
503	return ((first + width) == (x + 1));
504}
505
506/*
507 * Replace the character at the current cursor location
508 * according to the column width of the character.  The
509 * cursor does not advance.
510 *
511 * Return -1 if the character won't fit on the line and the background
512 * was written in its place; else return the width of the character in
513 * screen columns.
514 */
515/* ARGSUSED */
516int
517__m_cc_replace(WINDOW *w, int y, int x,
518	const cchar_t *cc, int as_is)
519{
520	int	i, width;
521	cchar_t	*cp, *np;
522
523	width = __m_cc_width(cc);
524
525	if (width <= 0)
526		return (__m_cc_modify(w, y, x, cc));
527
528	/*
529	 * If we try to write a broad character that would exceed the
530	 * right margin, then write the background character instead.
531	 */
532	if (0 < width && w->_maxx < x + width) {
533		(void) __m_cc_erase(w, y, x, y, w->_maxx-1);
534		return (-1);
535	}
536
537	/*
538	 * Erase the region to be occupied by the new character.
539	 * __m_cc_erase() will erase whole characters so that
540	 * writing a multicolumn character that overwrites the
541	 * trailing and leading portions of two already existing
542	 * multicolumn characters, erases the remaining portions.
543	 */
544	(void) __m_cc_erase(w, y, x, y, x + width - 1);
545
546	/* Write the first column of the character. */
547	cp = &w->_line[y][x++];
548	if (cc->_wc[0] == L' ') {
549		*cp = w->_bg;
550		cp->_at = cc->_at | w->_fg._at;
551		/*
552		 * This method fixes:
553		 * /tset/CAPIxcurses/fmvwaddchs/fmvwaddchs1{3}
554		 * /tset/CAPIxcurses/fwins_wch/fwins_wch1{5}
555		 */
556		cp->_co = (cc->_co) ? cc->_co : w->_fg._co;
557	} else {
558		if (__m_wacs_cc(cc, cp)) {
559			/*
560			 * __m_wacs_cc says ALTCHARSET should be cleared
561			 * ... Takes priority
562			 */
563		    cp->_at = (cc->_at | w->_fg._at) & ~WA_ALTCHARSET;
564		} else {
565		    cp->_at = cc->_at | w->_fg._at;
566		}
567		cp->_co = (cc->_co) ? cc->_co : w->_fg._co;
568	}
569
570	/* Mark this as the first column of the character. */
571	cp->_f = 1;
572
573	/* Duplicate the character in every column the character occupies. */
574	for (np = cp + 1, i = 1; i < width; ++i, ++x, ++np) {
575		*np = *cp;
576		np->_f = 0;
577	}
578
579	return (width);
580}
581
582int
583__m_do_scroll(WINDOW *w, int y, int x, int *yp, int *xp)
584{
585	int	code = OK;
586	if (w->_maxx <= x)
587		x = w->_maxx - 1;
588
589	++y;
590
591	if (y == w->_bottom) {
592		--y;
593		if (w->_flags & W_CAN_SCROLL) {
594			if (wscrl(w, 1) == ERR)
595				return (ERR);
596			x = 0;
597			/* Test suite seems to want this */
598			w->_flags |= W_FLUSH;
599		} else {
600#ifdef	BREAKS
601			w->_curx = x;	/* Cheezy doing it here	*/
602			w->_cury = y;
603#endif	/* BREAKS */
604			code = ERR;	/* No scrolling allowed */
605		}
606	} else if (w->_maxy <= y) {
607		y = w->_maxy - 1;
608	} else {
609		/*
610		 * The cursor wraps for any line (in and out of the scroll
611		 * region) except for the last line of the scroll region.
612		 */
613		x = 0;
614	}
615
616	*yp = y;
617	*xp = x;
618
619	return (code);
620}
621
622/*
623 * Add the character at the current cursor location
624 * according to the column width of the character.
625 * The cursor will be advanced.
626 * Wrapping is done.
627 *
628 * Return ERR if adding the character causes the
629 * screen to scroll, when it is disallowed.
630 */
631int
632__m_cc_add(WINDOW *w, int y, int x,
633	const cchar_t *cc, int as_is, int *yp, int *xp)
634{
635	int	nx, width, code = ERR;
636
637	switch (cc->_wc[0]) {
638	case L'\t':
639		nx = x + (8 - (x & 07));
640		if (nx >= w->_maxx)	{
641			/* This fixes (scroll-disabled) */
642			/* /tset/CAPIxcurses/fwaddch/fwaddch1{4} but */
643			/* what does it break? */
644			nx = w->_maxx;
645		}
646		if (__m_cc_erase(w, y, x, y, nx-1) == -1)
647			goto error;
648		x = nx;
649
650		if (w->_maxx <= x) {
651			if (__m_do_scroll(w, y, x, &y, &x) == ERR)
652				goto error;
653		}
654		break;
655	case L'\n':
656		if (__m_cc_erase(w, y, x, y, w->_maxx-1) == -1)
657			goto error;
658
659		if (__m_do_scroll(w, y, x, &y, &x) == ERR)
660			goto error;
661		break;
662	case L'\r':
663		x = 0;
664		break;
665	case L'\b':
666		if (0 < x)
667			--x;
668		else
669			(void) beep();
670		break;
671	default:
672		width = __m_cc_replace(w, y, x, cc, as_is);
673
674		x += width;
675
676		if (width < 0 || w->_maxx <= x) {
677			if (__m_do_scroll(w, y, x, &y, &x) == ERR) {
678				goto error;
679			}
680
681			if (width < 0)
682				x += __m_cc_replace(w, y, x, cc, as_is);
683		}
684	}
685
686	code = OK;
687error:
688	*yp = y;
689	*xp = x;
690
691	return (code);
692}
693
694/*
695 * Stripped version of __m_cc_add which does much less special character
696 * processing. Functions such as waddchnstr() are not supposed to do
697 * any special character processing but what does one do when a '\n'
698 * is sent? The test suite expects a new line to start...
699 *
700 * Return ERR if adding the character causes the
701 * screen to scroll, when it is disallowed.
702 */
703int
704__m_cc_add_k(WINDOW *w, int y, int x,
705	const cchar_t *cc, int as_is, int *yp, int *xp)
706{
707	int	width, code = ERR;
708
709	switch (cc->_wc[0]) {
710	case L'\n':
711		if (__m_cc_erase(w, y, x, y, w->_maxx-1) == -1)
712			goto error;
713
714		if (__m_do_scroll(w, y, x, &y, &x) == ERR)
715			goto error;
716		break;
717	default:
718		width = __m_cc_replace(w, y, x, cc, as_is);
719		x += width;
720	}
721
722	code = OK;
723error:
724	*yp = y;
725	*xp = x;
726
727	return (code);
728}
729
730/*
731 * Append non-spacing characters to the a spacing character at (y, x).
732 * Return -1 on error, else 0.
733 */
734int
735__m_cc_modify(WINDOW *w, int y, int x, const cchar_t *cc)
736{
737	cchar_t	*cp, tch;
738	int	i, j, width;
739
740	x = __m_cc_first(w, y, x);
741	cp = &w->_line[y][x];
742
743	/* Is there enough room for the non-spacing characters. */
744	if (_M_CCHAR_MAX < cp->_n + cc->_n)
745		return (-1);
746
747	for (i = cp->_n, j = 0; j < cc->_n; ++i, ++j)
748		cp->_wc[i] = cc->_wc[j];
749	cp->_n = (short)i;
750
751	width = __m_cc_width(cp);
752
753	__m_touch_locs(w, y, x, x + width);
754
755	/* Assert that the modified spacing character is sorted. */
756	(void) __m_cc_sort(cp);
757
758	/* Dulicate in every column occupied by the spacing character. */
759	while (0 < --width) {
760		tch = *cp;
761		cp[1] = tch;
762		cp++;
763	}
764
765	return (0);
766}
767
768static void
769__m_cc_erase_in_line(WINDOW *w, int y, int x, int lx, int bgWidth)
770{
771	cchar_t	*cp;
772	int	i;
773
774	if (x < w->_first[y])
775		w->_first[y] = (short)x;
776
777	for (cp = w->_line[y], i = 0; x <= lx; ++x, ++i) {
778		cp[x] = w->_bg;
779		/*
780		 * The start of each new character will be set true
781		 * while internal columns of the character will be
782		 * reset to false.
783		 */
784		cp[x]._f = (short)(i % bgWidth == 0);
785	}
786	if (w->_last[y] < x)
787		w->_last[y] = (short)x;
788}
789
790/* Window has a parent. Handle width chars overlapping with parent */
791static void
792__m_cc_erase_in_line_sub(WINDOW *w, int y, int x,
793	int lx, int bgWidth, int parentBGWidth)
794{
795	cchar_t	*cp;
796	int	i;
797	int	xi;
798	int	wmin, wmax;
799	int	wlx;
800	WINDOW	*parent = w->_parent;
801	int 	parentY = w->_begy + y - parent->_begy;
802	int	dx = w->_begx - parent->_begx;
803
804	/* Switch to parent context and calculate limits */
805	xi = x = __m_cc_first(parent, parentY, dx + x);
806	wlx = lx = __m_cc_next(parent, parentY, dx + lx) - 1;
807	if (wlx >= dx + w->_maxx) wlx = dx + w->_maxx - 1;
808
809	for (cp = parent->_line[parentY]; x <= lx; ) {
810		if ((x < dx) || (x >= (dx + w->_maxx))) {
811			/* Outside target window */
812			for (i = 0; x <= lx && i <= parentBGWidth; x++, i++) {
813				cp[x] = parent->_bg;
814				cp[x]._f = (i == 0);
815			}
816		} else {
817			/* Inside target window */
818			for (i = 0; x <= wlx; x++, i++) {
819				cp[x] = w->_bg;
820				cp[x]._f = (short)(i % bgWidth == 0);
821			}
822		}
823	}
824	wmax = x - dx;		/* Defaults */
825	wmin = xi - dx;
826	if ((xi < dx) || (x >= dx + w->_maxx)) {
827		/* Overlaps parent. Must touch parent and child */
828		int	pmin, pmax;
829
830		pmax = dx;		/* Defaults */
831		pmin = dx + w->_maxx;
832		if (xi < dx) {
833			wmin = 0;
834			pmin = xi;
835		}
836		if (x >= dx + w->_maxx) {
837			/* Ends right of target window */
838			wmax = w->_maxx;
839			pmax = x;
840		}
841		if (pmin < parent->_first[parentY])
842			parent->_first[parentY] = (short)pmin;
843		if (pmax > parent->_last[parentY])
844			parent->_last[parentY] = (short)pmax;
845	}
846	if (wmin < w->_first[y])
847		w->_first[y] = (short)wmin;
848	if (wmax > w->_last[y])
849		w->_last[y] = (short)wmax;
850}
851
852/*
853 * Erase region from (y,x) to (ly, lx) inclusive.  The
854 * region is extended left and right in the case where
855 * the portions of a multicolumn characters are erased.
856 *
857 * Return -1 if the region is not an integral multiple
858 * of the background character, else zero for success.
859 */
860int
861__m_cc_erase(WINDOW *w, int y, int x, int ly, int lx)
862{
863	int	bgWidth;
864
865	if (ly < y)
866		return (-1);
867
868	if (w->_maxy <= ly)
869		ly = w->_maxy - 1;
870
871	/*
872	 * Is the region to blank out an integral width of the
873	 * background character?
874	 */
875	bgWidth = __m_cc_width(&w->_bg);
876
877	if (bgWidth <= 0)
878		return (-1);
879
880	/*
881	 * Erase Pattern will look like:
882	 *			EEEEEEE|
883	 *	EEEEEEEEEEEEEEE|
884	 *	EEEEEEEEEEE    |
885	 */
886	if (w->_parent) {
887		/*
888		 * Use slower alg. for subwindows.
889		 * They might erase stuff in parent-context
890		 */
891		int	parentBGWidth = __m_cc_width(&w->_parent->_bg);
892		for (; y < ly; ++y, x = 0) {
893			__m_cc_erase_in_line_sub(w, y, x, w->_maxx-1,
894				bgWidth, parentBGWidth);
895		}
896		__m_cc_erase_in_line_sub(w, y, x, lx, bgWidth, parentBGWidth);
897	} else {
898		/* Root windows - no need to work in parent context at all */
899		if (w->_maxx <= lx)
900			lx = w->_maxx - 1;
901
902		/*
903		 * Erase from first whole character (inclusive) to next
904		 * character (exclusive).
905		 */
906		x = __m_cc_first(w, y, x);
907		lx = __m_cc_next(w, ly, lx) - 1;
908
909		for (; y < ly; ++y, x = 0) {
910			__m_cc_erase_in_line(w, y, x, w->_maxx-1, bgWidth);
911		}
912		__m_cc_erase_in_line(w, y, x, lx, bgWidth);
913	}
914	return (0);
915}
916
917/*
918 * Expand the character to the left or right of the given position.
919 * Return the value returned by __m_cc_replace().
920 */
921int
922__m_cc_expand(WINDOW *w, int y, int x, int side)
923{
924	cchar_t	cc;
925	int	dx, width;
926
927	width = __m_cc_width(&w->_line[y][x]);
928
929	if (side < 0)
930		dx = __m_cc_next(w, y, x) - width;
931	else if (0 < side)
932		dx = __m_cc_first(w, y, x);
933	else
934		return (-1);
935
936	/*
937	 * __m_cc_replace() will erase the region containing
938	 * the character we want to expand.
939	 */
940	cc = w->_line[y][x];
941
942	return (__m_cc_replace(w, y, dx, &cc, 0));
943}
944
945/* Revised version of __m_cc_compare() to compare only the char parts */
946
947int
948__m_cc_equal(const cchar_t *c1, const cchar_t *c2)
949{
950	int	i;
951
952	if (c1->_f != c2->_f)
953		return (0);
954	if (c1->_n != c2->_n)
955		return (0);
956	for (i = 0; i < c1->_n; ++i)
957		if (c1->_wc[i] != c2->_wc[i])
958			return (0);
959	return (1);
960}
961
962/*
963 * Return true if characters are equal.
964 *
965 * NOTE to guarantee correct results, make sure that both
966 * characters have been passed through __m_cc_sort().
967 */
968int
969__m_cc_compare(const cchar_t *c1, const cchar_t *c2, int exact)
970{
971	int	i;
972
973	if (exact && c1->_f != c2->_f)
974		return (0);
975	if (c1->_n != c2->_n)
976		return (0);
977	if ((c1->_at & ~WA_COOKIE) != (c2->_at & ~WA_COOKIE))
978		return (0);
979	if (c1->_co != c2->_co)
980		return (0);
981
982	for (i = 0; i < c1->_n; ++i)
983		if (c1->_wc[i] != c2->_wc[i])
984			return (0);
985
986	return (1);
987}
988
989/*
990 * Write to the stream the character portion of a cchar_t.
991 */
992int
993__m_cc_write(const cchar_t *cc)
994{
995	int	j;
996	size_t	i;
997	char	mb[MB_LEN_MAX];
998/*
999 * 4131273 UNIX98: xcurses library renders complex characters incorrectly
1000 */
1001	int	backed_up = 0;
1002
1003	for (i = 0; i < cc->_n; ++i) {
1004		j = wctomb(mb, cc->_wc[i]);
1005		if (j == -1)
1006			return (EOF);
1007		if (i == 1) {
1008			/*
1009			 * Move cursor back where it was
1010			 */
1011			if (fwrite(cursor_left, 1, strlen(cursor_left),
1012				__m_screen->_of) == 0) {
1013				return (EOF);
1014			}
1015			backed_up = 1;
1016		}
1017		if (fwrite(mb, sizeof (*mb), (size_t)j, __m_screen->_of) == 0) {
1018			return (EOF);
1019		}
1020	}
1021	if (backed_up) {
1022		/*
1023		 * Move cursor back where it was
1024		 */
1025		if (fwrite(cursor_right, 1, strlen(cursor_right),
1026			__m_screen->_of) == 0) {
1027			return (EOF);
1028		}
1029	}
1030
1031	__m_screen->_flags |= W_FLUSH;
1032	return (0);
1033}
1034