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