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