xref: /illumos-gate/usr/src/cmd/ul/ul.c (revision 945e3b4bc345679cad92f0a896670030a411c8f8)
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
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
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
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
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
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
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
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
506 reverse(void)
507 {
508 	upln++;
509 	fwd();
510 	ul_puts(CURS_UP);
511 	ul_puts(CURS_UP);
512 	upln++;
513 }
514 
515 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
574 outchar(char c)
575 {
576 	(void) putchar(c&0177);
577 	return (0);
578 }
579 
580 void
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
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
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