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
41 static 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 
53 typedef struct {
54 	int	max;
55 	int	used;
56 	char	*mbs;
57 } t_string;
58 
59 static int
60 write_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  */
76 int
77 wistombs(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  */
124 int
125 wistowcs(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 
143 void
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  */
157 int
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  */
180 chtype
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  */
215 int
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  */
262 int
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  */
290 int
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  */
344 int
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  */
382 int
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  */
422 int
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  */
463 int
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  */
479 int
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  */
495 int
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 */
516 int
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 
582 int
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  */
631 int
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;
687 error:
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  */
703 int
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;
723 error:
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  */
734 int
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 
768 static 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 */
791 static 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  */
860 int
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  */
921 int
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 
947 int
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  */
968 int
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  */
992 int
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