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