xref: /titanic_51/usr/src/cmd/ul/ul.c (revision 989f28072d20c73ae0955d6a1e3e2fc74831cb39)
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