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