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