xref: /illumos-gate/usr/src/lib/libxcurses2/src/libc/xcurses/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
42 static 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 
54 typedef struct {
55 	int	max;
56 	int	used;
57 	char	*mbs;
58 } t_string;
59 
60 static int
61 write_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  */
77 int
78 wistombs(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  */
125 int
126 wistowcs(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 
144 void
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  */
158 int
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  */
181 chtype
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  */
216 int
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  */
263 int
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  */
291 int
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  */
345 int
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  */
383 int
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  */
423 int
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  */
464 int
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  */
480 int
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  */
496 int
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 */
517 int
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 
583 int
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  */
632 int
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;
688 error:
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  */
704 int
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;
724 error:
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  */
735 int
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 
769 static 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 */
792 static 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  */
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