1 /*
2 * Copyright 2000 Sun Microsystems, Inc. All rights reserved.
3 * Use is subject to license terms.
4 */
5
6 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
7 /* All Rights Reserved */
8
9 /*
10 * Copyright (c) 1980 Regents of the University of California.
11 * All rights reserved. The Berkeley software License Agreement
12 * specifies the terms and conditions for redistribution.
13 */
14
15 #pragma ident "%Z%%M% %I% %E% SMI"
16
17 #include <stdio.h>
18 #include <locale.h>
19 #include <wctype.h>
20 #include <widec.h>
21 #include <euc.h>
22 #include <getwidth.h>
23 #include <limits.h>
24 #include <stdlib.h>
25 #include <curses.h>
26 #include <term.h>
27 #include <string.h>
28
29 #define IESC L'\033'
30 #define SO L'\016'
31 #define SI L'\017'
32 #define HFWD L'9'
33 #define HREV L'8'
34 #define FREV L'7'
35 #define CDUMMY -1
36
37 #define NORMAL 000
38 #define ALTSET 001 /* Reverse */
39 #define SUPERSC 002 /* Dim */
40 #define SUBSC 004 /* Dim | Ul */
41 #define UNDERL 010 /* Ul */
42 #define BOLD 020 /* Bold */
43
44 #define MEMFCT 16
45 /*
46 * MEMFCT is a number that is likely to be large enough as a factor for
47 * allocating more memory and to be small enough so as not wasting memory
48 */
49
50 int must_use_uc, must_overstrike;
51 char *CURS_UP, *CURS_RIGHT, *CURS_LEFT,
52 *ENTER_STANDOUT, *EXIT_STANDOUT, *ENTER_UNDERLINE, *EXIT_UNDERLINE,
53 *ENTER_DIM, *ENTER_BOLD, *ENTER_REVERSE, *UNDER_CHAR, *EXIT_ATTRIBUTES;
54
55 struct CHAR {
56 char c_mode;
57 wchar_t c_char;
58 };
59
60 struct CHAR obuf[LINE_MAX];
61 int col, maxcol;
62 int mode;
63 int halfpos;
64 int upln;
65 int iflag;
66
67 eucwidth_t wp;
68 int scrw[4];
69
70 void setmode(int newmode);
71 void outc(wchar_t c);
72 int outchar(char c);
73 void initcap(void);
74 void reverse(void);
75 void fwd(void);
76 void initbuf(void);
77 void iattr(void);
78 void overstrike(void);
79 void flushln(void);
80 void ul_filter(FILE *f);
81 void ul_puts(char *str);
82
83 int
main(int argc,char ** argv)84 main(int argc, char **argv)
85 {
86 int c;
87 char *termtype;
88 FILE *f;
89 char termcap[1024];
90 extern int optind;
91 extern char *optarg;
92
93 (void) setlocale(LC_ALL, "");
94 #if !defined(TEXT_DOMAIN)
95 #define TEXT_DOMAIN "SYS_TEST"
96 #endif
97 (void) textdomain(TEXT_DOMAIN);
98
99 getwidth(&wp);
100 scrw[0] = 1;
101 scrw[1] = wp._scrw1;
102 scrw[2] = wp._scrw2;
103 scrw[3] = wp._scrw3;
104
105 termtype = getenv("TERM");
106 if (termtype == NULL || (argv[0][0] == 'c' && !isatty(1)))
107 termtype = "lpr";
108 while ((c = getopt(argc, argv, "it:T:")) != EOF)
109 switch (c) {
110
111 case 't':
112 case 'T': /* for nroff compatibility */
113 termtype = optarg;
114 break;
115 case 'i':
116 iflag = 1;
117 break;
118
119 default:
120 (void) fprintf(stderr,
121 gettext("\
122 Usage: %s [ -i ] [ -t terminal ] [ filename...]\n"),
123 argv[0]);
124 exit(1);
125 }
126
127 switch (tgetent(termcap, termtype)) {
128
129 case 1:
130 break;
131
132 default:
133 (void) fprintf(stderr, gettext("trouble reading termcap"));
134 /*FALLTHROUGH*/
135
136 case 0:
137 /* No such terminal type - assume dumb */
138 (void) strcpy(termcap, "dumb:os:col#80:cr=^M:sf=^J:am:");
139 break;
140 }
141 initcap();
142 if ((tgetflag("os") && ENTER_BOLD == NULL) || (tgetflag("ul") &&
143 ENTER_UNDERLINE == NULL && UNDER_CHAR == NULL))
144 must_overstrike = 1;
145 initbuf();
146 if (optind == argc)
147 ul_filter(stdin);
148 else for (; optind < argc; optind++) {
149 f = fopen(argv[optind], "r");
150 if (f == NULL) {
151 perror(argv[optind]);
152 exit(1);
153 } else
154 ul_filter(f);
155 }
156 return (0);
157 }
158
159 void
ul_filter(FILE * f)160 ul_filter(FILE *f)
161 {
162 wchar_t c;
163 int i;
164
165 while ((c = getwc(f)) != EOF) {
166 if (maxcol >= LINE_MAX) {
167 (void) fprintf(stderr,
168 gettext("Input line longer than %d characters\n"), LINE_MAX);
169 exit(1);
170 }
171 switch (c) {
172
173 case L'\b':
174 if (col > 0)
175 col--;
176 continue;
177
178 case L'\t':
179 col = (col+8) & ~07;
180 if (col > maxcol)
181 maxcol = col;
182 continue;
183
184 case L'\r':
185 col = 0;
186 continue;
187
188 case SO:
189 mode |= ALTSET;
190 continue;
191
192 case SI:
193 mode &= ~ALTSET;
194 continue;
195
196 case IESC:
197 switch (c = getwc(f)) {
198 case HREV:
199 if (halfpos == 0) {
200 mode |= SUPERSC;
201 halfpos--;
202 } else if (halfpos > 0) {
203 mode &= ~SUBSC;
204 halfpos--;
205 } else {
206 halfpos = 0;
207 reverse();
208 }
209 continue;
210
211 case HFWD:
212 if (halfpos == 0) {
213 mode |= SUBSC;
214 halfpos++;
215 } else if (halfpos < 0) {
216 mode &= ~SUPERSC;
217 halfpos++;
218 } else {
219 halfpos = 0;
220 fwd();
221 }
222 continue;
223 case FREV:
224 reverse();
225 continue;
226
227 default:
228 (void) fprintf(stderr,
229 gettext("Unknown escape sequence in input: %o, %o\n"),
230 IESC, c);
231 exit(1);
232 }
233 continue;
234
235 case L'_':
236 if (obuf[col].c_char)
237 obuf[col].c_mode |= UNDERL | mode;
238 else
239 obuf[col].c_char = '_';
240 /*FALLTHROUGH*/
241
242 case L' ':
243 col++;
244 if (col > maxcol)
245 maxcol = col;
246 continue;
247
248 case L'\n':
249 flushln();
250 continue;
251
252 default:
253 if (c < L' ') /* non printing */
254 continue;
255 if (obuf[col].c_char == L'\0') {
256 obuf[col].c_char = c;
257 obuf[col].c_mode = mode;
258 i = scrw[wcsetno(c)];
259 while (--i > 0)
260 obuf[++col].c_char = CDUMMY;
261 } else if (obuf[col].c_char == L'_') {
262 obuf[col].c_char = c;
263 obuf[col].c_mode |= UNDERL|mode;
264 i = scrw[wcsetno(c)];
265 while (--i > 0)
266 obuf[++col].c_char = CDUMMY;
267 } else if (obuf[col].c_char == c)
268 obuf[col].c_mode |= BOLD|mode;
269 else {
270 obuf[col].c_char = c;
271 obuf[col].c_mode = mode;
272 }
273 col++;
274 if (col > maxcol)
275 maxcol = col;
276 continue;
277 }
278 }
279 if (maxcol)
280 flushln();
281 }
282
283 void
flushln(void)284 flushln(void)
285 {
286 int lastmode;
287 int i;
288 int hadmodes = 0;
289
290 lastmode = NORMAL;
291 for (i = 0; i < maxcol; i++) {
292 if (obuf[i].c_mode != lastmode) {
293 hadmodes++;
294 setmode(obuf[i].c_mode);
295 lastmode = obuf[i].c_mode;
296 }
297 if (obuf[i].c_char == L'\0') {
298 if (upln) {
299 ul_puts(CURS_RIGHT);
300 } else
301 outc(L' ');
302 } else
303 outc(obuf[i].c_char);
304 }
305 if (lastmode != NORMAL) {
306 setmode(0);
307 }
308 if (must_overstrike && hadmodes)
309 overstrike();
310 (void) putwchar(L'\n');
311 if (iflag && hadmodes)
312 iattr();
313 if (upln)
314 upln--;
315 initbuf();
316 }
317
318 /*
319 * For terminals that can overstrike, overstrike underlines and bolds.
320 * We don't do anything with halfline ups and downs, or Greek.
321 */
322 void
overstrike(void)323 overstrike(void)
324 {
325 int i, n;
326 wchar_t *cp, *scp;
327 size_t szbf = 256, tszbf;
328 int hadbold = 0;
329
330 scp = (wchar_t *)malloc(sizeof (wchar_t) * szbf);
331 if (!scp) {
332 /* this kind of message need not to be gettext'ed */
333 (void) fprintf(stderr, "malloc failed\n");
334 exit(1);
335 }
336 cp = scp;
337 tszbf = szbf;
338 #ifdef DEBUG
339 /*
340 * to allocate a memory after the chunk of the current scp
341 * and to make sure the following realloc() allocates
342 * memory from different chunks.
343 */
344 (void) malloc(1024 * 1024);
345 #endif
346
347 /* Set up overstrike buffer */
348 for (i = 0; i < maxcol; i++) {
349 n = scrw[wcsetno(obuf[i].c_char)];
350 if (tszbf <= n) {
351 /* may not enough buffer for this char */
352 size_t pos;
353
354 /* obtain the offset of cp */
355 pos = cp - scp;
356 /* reallocate another (n * MEMFCT) * sizeof (wchar_t) */
357 scp = (wchar_t *)realloc(scp,
358 sizeof (wchar_t) * (szbf + (n * MEMFCT)));
359 if (!scp) {
360 (void) fprintf(stderr, "malloc failed\n");
361 exit(1);
362 }
363 /* get the new address of cp */
364 cp = scp + pos;
365 szbf += n * MEMFCT;
366 tszbf += n * MEMFCT;
367 }
368 switch (obuf[i].c_mode) {
369 case NORMAL:
370 default:
371 tszbf -= n;
372 *cp++ = L' ';
373 while (--n > 0) {
374 *cp++ = L' ';
375 i++;
376 }
377 break;
378 case UNDERL:
379 tszbf -= n;
380 *cp++ = L'_';
381 while (--n > 0) {
382 *cp++ = L'_';
383 i++;
384 }
385 break;
386 case BOLD:
387 tszbf--;
388 *cp++ = obuf[i].c_char;
389 hadbold = 1;
390 break;
391 }
392 }
393 (void) putwchar(L'\r');
394 for (*cp = L' '; *cp == L' '; cp--)
395 *cp = L'\0';
396 for (cp = scp; *cp; cp++)
397 (void) putwchar(*cp);
398 if (hadbold) {
399 (void) putwchar(L'\r');
400 for (cp = scp; *cp; cp++)
401 (void) putwchar(*cp == L'_' ? L' ' : *cp);
402 (void) putwchar(L'\r');
403 for (cp = scp; *cp; cp++)
404 (void) putwchar(*cp == L'_' ? L' ' : *cp);
405 }
406 free(scp);
407 }
408
409 void
iattr(void)410 iattr(void)
411 {
412 int i, n;
413 wchar_t *cp, *scp;
414 wchar_t cx;
415 size_t szbf = 256, tszbf;
416
417 scp = (wchar_t *)malloc(sizeof (wchar_t) * szbf);
418 if (!scp) {
419 /* this kind of message need not to be gettext'ed */
420 (void) fprintf(stderr, "malloc failed\n");
421 exit(1);
422 }
423 cp = scp;
424 tszbf = szbf;
425 #ifdef DEBUG
426 /*
427 * to allocate a memory after the chunk of the current scp
428 * and to make sure the following realloc() allocates
429 * memory from different chunks.
430 */
431 (void) malloc(1024 * 1024);
432 #endif
433 for (i = 0; i < maxcol; i++) {
434 switch (obuf[i].c_mode) {
435 case NORMAL: cx = ' '; break;
436 case ALTSET: cx = 'g'; break;
437 case SUPERSC: cx = '^'; break;
438 case SUBSC: cx = 'v'; break;
439 case UNDERL: cx = '_'; break;
440 case BOLD: cx = '!'; break;
441 default: cx = 'X'; break;
442 }
443 n = scrw[wcsetno(obuf[i].c_char)];
444 if (tszbf <= n) {
445 /* may not enough buffer for this char */
446 size_t pos;
447
448 /* obtain the offset of cp */
449 pos = cp - scp;
450 /* reallocate another (n * MEMFCT) * sizeof (wchar_t) */
451 scp = (wchar_t *)realloc(scp,
452 sizeof (wchar_t) * (szbf + (n * MEMFCT)));
453 if (!scp) {
454 (void) fprintf(stderr, "malloc failed\n");
455 exit(1);
456 }
457 /* get the new address of cp */
458 cp = scp + pos;
459 szbf += n * MEMFCT;
460 tszbf += n * MEMFCT;
461 }
462 tszbf -= n;
463 *cp++ = cx;
464 while (--n > 0) {
465 *cp++ = cx;
466 i++;
467 }
468 }
469 for (*cp = L' '; *cp == L' '; cp--)
470 *cp = L'\0';
471 for (cp = scp; *cp; cp++)
472 (void) putwchar(*cp);
473 (void) putwchar(L'\n');
474 free(scp);
475 }
476
477 void
initbuf(void)478 initbuf(void)
479 {
480 int i;
481
482 /* following depends on NORMAL == 000 */
483 for (i = 0; i < LINE_MAX; i++)
484 obuf[i].c_char = obuf[i].c_mode = 0;
485
486 col = 0;
487 maxcol = 0;
488 mode &= ALTSET;
489 }
490
491 void
fwd(void)492 fwd(void)
493 {
494 int oldcol, oldmax;
495
496 oldcol = col;
497 oldmax = maxcol;
498 flushln();
499 col = oldcol;
500 maxcol = oldmax;
501 }
502
503 void
reverse(void)504 reverse(void)
505 {
506 upln++;
507 fwd();
508 ul_puts(CURS_UP);
509 ul_puts(CURS_UP);
510 upln++;
511 }
512
513 void
initcap(void)514 initcap(void)
515 {
516 static char tcapbuf[512];
517 char *bp = tcapbuf;
518
519 /* This nonsense attempts to work with both old and new termcap */
520 CURS_UP = tgetstr("up", &bp);
521 CURS_RIGHT = tgetstr("ri", &bp);
522 if (CURS_RIGHT == NULL)
523 CURS_RIGHT = tgetstr("nd", &bp);
524 CURS_LEFT = tgetstr("le", &bp);
525 if (CURS_LEFT == NULL)
526 CURS_LEFT = tgetstr("bc", &bp);
527 if (CURS_LEFT == NULL && tgetflag("bs"))
528 CURS_LEFT = "\b";
529
530 ENTER_STANDOUT = tgetstr("so", &bp);
531 EXIT_STANDOUT = tgetstr("se", &bp);
532 ENTER_UNDERLINE = tgetstr("us", &bp);
533 EXIT_UNDERLINE = tgetstr("ue", &bp);
534 ENTER_DIM = tgetstr("mh", &bp);
535 ENTER_BOLD = tgetstr("md", &bp);
536 ENTER_REVERSE = tgetstr("mr", &bp);
537 EXIT_ATTRIBUTES = tgetstr("me", &bp);
538
539 if (!ENTER_BOLD && ENTER_REVERSE)
540 ENTER_BOLD = ENTER_REVERSE;
541 if (!ENTER_BOLD && ENTER_STANDOUT)
542 ENTER_BOLD = ENTER_STANDOUT;
543 if (!ENTER_UNDERLINE && ENTER_STANDOUT) {
544 ENTER_UNDERLINE = ENTER_STANDOUT;
545 EXIT_UNDERLINE = EXIT_STANDOUT;
546 }
547 if (!ENTER_DIM && ENTER_STANDOUT)
548 ENTER_DIM = ENTER_STANDOUT;
549 if (!ENTER_REVERSE && ENTER_STANDOUT)
550 ENTER_REVERSE = ENTER_STANDOUT;
551 if (!EXIT_ATTRIBUTES && EXIT_STANDOUT)
552 EXIT_ATTRIBUTES = EXIT_STANDOUT;
553
554 /*
555 * Note that we use REVERSE for the alternate character set,
556 * not the as/ae capabilities. This is because we are modelling
557 * the model 37 teletype (since that's what nroff outputs) and
558 * the typical as/ae is more of a graphics set, not the greek
559 * letters the 37 has.
560 */
561
562 #ifdef notdef
563 printf("so %s se %s us %s ue %s me %s\n",
564 ENTER_STANDOUT, EXIT_STANDOUT, ENTER_UNDERLINE,
565 EXIT_UNDERLINE, EXIT_ATTRIBUTES);
566 #endif
567 UNDER_CHAR = tgetstr("uc", &bp);
568 must_use_uc = (UNDER_CHAR && !ENTER_UNDERLINE);
569 }
570
571 int
outchar(char c)572 outchar(char c)
573 {
574 (void) putchar(c&0177);
575 return (0);
576 }
577
578 void
ul_puts(char * str)579 ul_puts(char *str)
580 {
581 if (str)
582 (void) tputs(str, 1, outchar);
583 }
584
585 static int curmode = 0;
586
587 void
outc(wchar_t c)588 outc(wchar_t c)
589 {
590 int m, n;
591
592 if (c == CDUMMY)
593 return;
594 (void) putwchar(c);
595 if (must_use_uc && (curmode & UNDERL)) {
596 m = n = scrw[wcsetno(c)];
597 ul_puts(CURS_LEFT);
598 while (--m > 0)
599 ul_puts(CURS_LEFT);
600 ul_puts(UNDER_CHAR);
601 while (--n > 0)
602 ul_puts(UNDER_CHAR);
603 }
604 }
605
606 void
setmode(int newmode)607 setmode(int newmode)
608 {
609 if (!iflag) {
610 if (curmode != NORMAL && newmode != NORMAL)
611 setmode(NORMAL);
612 switch (newmode) {
613 case NORMAL:
614 switch (curmode) {
615 case NORMAL:
616 break;
617 case UNDERL:
618 ul_puts(EXIT_UNDERLINE);
619 break;
620 default:
621 /* This includes standout */
622 ul_puts(EXIT_ATTRIBUTES);
623 break;
624 }
625 break;
626 case ALTSET:
627 ul_puts(ENTER_REVERSE);
628 break;
629 case SUPERSC:
630 /*
631 * This only works on a few terminals.
632 * It should be fixed.
633 */
634 ul_puts(ENTER_UNDERLINE);
635 ul_puts(ENTER_DIM);
636 break;
637 case SUBSC:
638 ul_puts(ENTER_DIM);
639 break;
640 case UNDERL:
641 ul_puts(ENTER_UNDERLINE);
642 break;
643 case BOLD:
644 ul_puts(ENTER_BOLD);
645 break;
646 default:
647 /*
648 * We should have some provision here for multiple modes
649 * on at once. This will have to come later.
650 */
651 ul_puts(ENTER_STANDOUT);
652 break;
653 }
654 }
655 curmode = newmode;
656 }
657