ul.c revision 303075
1/*
2 * Copyright (c) 1980, 1993
3 *	The Regents of the University of California.  All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 * 4. Neither the name of the University nor the names of its contributors
14 *    may be used to endorse or promote products derived from this software
15 *    without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
28 */
29
30#ifndef lint
31static const char copyright[] =
32"@(#) Copyright (c) 1980, 1993\n\
33	The Regents of the University of California.  All rights reserved.\n";
34#endif /* not lint */
35
36#ifndef lint
37#if 0
38static char sccsid[] = "@(#)ul.c	8.1 (Berkeley) 6/6/93";
39#endif
40static const char rcsid[] =
41  "$FreeBSD: stable/10/usr.bin/ul/ul.c 303075 2016-07-20 07:33:48Z gahr $";
42#endif /* not lint */
43
44#include <err.h>
45#include <locale.h>
46#include <stdio.h>
47#include <stdlib.h>
48#include <string.h>
49#include <termcap.h>
50#include <unistd.h>
51#include <wchar.h>
52#include <wctype.h>
53
54#define	IESC	'\033'
55#define	SO	'\016'
56#define	SI	'\017'
57#define	HFWD	'9'
58#define	HREV	'8'
59#define	FREV	'7'
60#define	MAXBUF	512
61
62#define	NORMAL	000
63#define	ALTSET	001	/* Reverse */
64#define	SUPERSC	002	/* Dim */
65#define	SUBSC	004	/* Dim | Ul */
66#define	UNDERL	010	/* Ul */
67#define	BOLD	020	/* Bold */
68
69static int	must_use_uc, must_overstrike;
70static const char
71	*CURS_UP, *CURS_RIGHT, *CURS_LEFT,
72	*ENTER_STANDOUT, *EXIT_STANDOUT, *ENTER_UNDERLINE, *EXIT_UNDERLINE,
73	*ENTER_DIM, *ENTER_BOLD, *ENTER_REVERSE, *UNDER_CHAR, *EXIT_ATTRIBUTES;
74
75struct	CHAR	{
76	char	c_mode;
77	wchar_t	c_char;
78	int	c_width;	/* width or -1 if multi-column char. filler */
79} ;
80
81static struct	CHAR	sobuf[MAXBUF]; /* static output buffer */
82static struct	CHAR	*obuf = sobuf;
83static int	buflen = MAXBUF;
84static int	col, maxcol;
85static int	mode;
86static int	halfpos;
87static int	upln;
88static int	iflag;
89
90static void usage(void);
91static void setnewmode(int);
92static void initcap(void);
93static void reverse(void);
94static int outchar(int);
95static void fwd(void);
96static void initbuf(void);
97static void iattr(void);
98static void overstrike(void);
99static void flushln(void);
100static void filter(FILE *);
101static void outc(wint_t, int);
102
103#define	PRINT(s)	if (s == NULL) /* void */; else tputs(s, 1, outchar)
104
105int
106main(int argc, char **argv)
107{
108	int c;
109	const char *termtype;
110	FILE *f;
111	char termcap[1024];
112
113	setlocale(LC_ALL, "");
114
115	termtype = getenv("TERM");
116	if (termtype == NULL || (argv[0][0] == 'c' && !isatty(1)))
117		termtype = "lpr";
118	while ((c=getopt(argc, argv, "it:T:")) != -1)
119		switch(c) {
120
121		case 't':
122		case 'T': /* for nroff compatibility */
123			termtype = optarg;
124			break;
125		case 'i':
126			iflag = 1;
127			break;
128		default:
129			usage();
130		}
131
132	switch(tgetent(termcap, termtype)) {
133
134	case 1:
135		break;
136
137	default:
138		warnx("trouble reading termcap");
139		/* FALLTHROUGH */
140
141	case 0:
142		/* No such terminal type - assume dumb */
143		(void)strcpy(termcap, "dumb:os:col#80:cr=^M:sf=^J:am:");
144		break;
145	}
146	initcap();
147	if (    (tgetflag("os") && ENTER_BOLD==NULL ) ||
148		(tgetflag("ul") && ENTER_UNDERLINE==NULL && UNDER_CHAR==NULL))
149			must_overstrike = 1;
150	initbuf();
151	if (optind == argc)
152		filter(stdin);
153	else for (; optind<argc; optind++) {
154		f = fopen(argv[optind],"r");
155		if (f == NULL)
156			err(1, "%s", argv[optind]);
157		else
158			filter(f);
159	}
160	if (obuf != sobuf) {
161		free(obuf);
162	}
163	exit(0);
164}
165
166static void
167usage(void)
168{
169	fprintf(stderr, "usage: ul [-i] [-t terminal] [file ...]\n");
170	exit(1);
171}
172
173static void
174filter(FILE *f)
175{
176	wint_t c;
177	int i, w;
178	int copy;
179
180	copy = 0;
181
182	while ((c = getwc(f)) != WEOF) {
183		if (col == buflen) {
184			if (obuf == sobuf) {
185				obuf = NULL;
186				copy = 1;
187			}
188			obuf = realloc(obuf, sizeof(*obuf) * 2 * buflen);
189			if (obuf == NULL) {
190				obuf = sobuf;
191				break;
192			} else if (copy) {
193				memcpy(obuf, sobuf, sizeof(*obuf) * buflen);
194				copy = 0;
195			}
196			bzero((char *)(obuf + buflen), sizeof(*obuf) * buflen);
197			buflen *= 2;
198		}
199		switch(c) {
200		case '\b':
201			if (col > 0)
202				col--;
203			continue;
204
205		case '\t':
206			col = (col+8) & ~07;
207			if (col > maxcol)
208				maxcol = col;
209			continue;
210
211		case '\r':
212			col = 0;
213			continue;
214
215		case SO:
216			mode |= ALTSET;
217			continue;
218
219		case SI:
220			mode &= ~ALTSET;
221			continue;
222
223		case IESC:
224			switch (c = getwc(f)) {
225
226			case HREV:
227				if (halfpos == 0) {
228					mode |= SUPERSC;
229					halfpos--;
230				} else if (halfpos > 0) {
231					mode &= ~SUBSC;
232					halfpos--;
233				} else {
234					halfpos = 0;
235					reverse();
236				}
237				continue;
238
239			case HFWD:
240				if (halfpos == 0) {
241					mode |= SUBSC;
242					halfpos++;
243				} else if (halfpos < 0) {
244					mode &= ~SUPERSC;
245					halfpos++;
246				} else {
247					halfpos = 0;
248					fwd();
249				}
250				continue;
251
252			case FREV:
253				reverse();
254				continue;
255
256			default:
257				errx(1, "unknown escape sequence in input: %o, %o", IESC, c);
258			}
259			continue;
260
261		case '_':
262			if (obuf[col].c_char || obuf[col].c_width < 0) {
263				while (col > 0 && obuf[col].c_width < 0)
264					col--;
265				w = obuf[col].c_width;
266				for (i = 0; i < w; i++)
267					obuf[col++].c_mode |= UNDERL | mode;
268				if (col > maxcol)
269					maxcol = col;
270				continue;
271			}
272			obuf[col].c_char = '_';
273			obuf[col].c_width = 1;
274			/* FALLTHROUGH */
275		case ' ':
276			col++;
277			if (col > maxcol)
278				maxcol = col;
279			continue;
280
281		case '\n':
282			flushln();
283			continue;
284
285		case '\f':
286			flushln();
287			putwchar('\f');
288			continue;
289
290		default:
291			if ((w = wcwidth(c)) <= 0)	/* non printing */
292				continue;
293			if (obuf[col].c_char == '\0') {
294				obuf[col].c_char = c;
295				for (i = 0; i < w; i++)
296					obuf[col + i].c_mode = mode;
297				obuf[col].c_width = w;
298				for (i = 1; i < w; i++)
299					obuf[col + i].c_width = -1;
300			} else if (obuf[col].c_char == '_') {
301				obuf[col].c_char = c;
302				for (i = 0; i < w; i++)
303					obuf[col + i].c_mode |= UNDERL|mode;
304				obuf[col].c_width = w;
305				for (i = 1; i < w; i++)
306					obuf[col + i].c_width = -1;
307			} else if ((wint_t)obuf[col].c_char == c) {
308				for (i = 0; i < w; i++)
309					obuf[col + i].c_mode |= BOLD|mode;
310			} else {
311				w = obuf[col].c_width;
312				for (i = 0; i < w; i++)
313					obuf[col + i].c_mode = mode;
314			}
315			col += w;
316			if (col > maxcol)
317				maxcol = col;
318			continue;
319		}
320	}
321	if (ferror(f))
322		err(1, NULL);
323	if (maxcol)
324		flushln();
325}
326
327static void
328flushln(void)
329{
330	int lastmode;
331	int i;
332	int hadmodes = 0;
333
334	lastmode = NORMAL;
335	for (i=0; i<maxcol; i++) {
336		if (obuf[i].c_mode != lastmode) {
337			hadmodes++;
338			setnewmode(obuf[i].c_mode);
339			lastmode = obuf[i].c_mode;
340		}
341		if (obuf[i].c_char == '\0') {
342			if (upln)
343				PRINT(CURS_RIGHT);
344			else
345				outc(' ', 1);
346		} else
347			outc(obuf[i].c_char, obuf[i].c_width);
348		if (obuf[i].c_width > 1)
349			i += obuf[i].c_width - 1;
350	}
351	if (lastmode != NORMAL) {
352		setnewmode(0);
353	}
354	if (must_overstrike && hadmodes)
355		overstrike();
356	putwchar('\n');
357	if (iflag && hadmodes)
358		iattr();
359	(void)fflush(stdout);
360	if (upln)
361		upln--;
362	initbuf();
363}
364
365/*
366 * For terminals that can overstrike, overstrike underlines and bolds.
367 * We don't do anything with halfline ups and downs, or Greek.
368 */
369static void
370overstrike(void)
371{
372	int i;
373	wchar_t lbuf[256];
374	wchar_t *cp = lbuf;
375	int hadbold=0;
376
377	/* Set up overstrike buffer */
378	for (i=0; i<maxcol; i++)
379		switch (obuf[i].c_mode) {
380		case NORMAL:
381		default:
382			*cp++ = ' ';
383			break;
384		case UNDERL:
385			*cp++ = '_';
386			break;
387		case BOLD:
388			*cp++ = obuf[i].c_char;
389			if (obuf[i].c_width > 1)
390				i += obuf[i].c_width - 1;
391			hadbold=1;
392			break;
393		}
394	putwchar('\r');
395	for (*cp=' '; *cp==' '; cp--)
396		*cp = 0;
397	for (cp=lbuf; *cp; cp++)
398		putwchar(*cp);
399	if (hadbold) {
400		putwchar('\r');
401		for (cp=lbuf; *cp; cp++)
402			putwchar(*cp=='_' ? ' ' : *cp);
403		putwchar('\r');
404		for (cp=lbuf; *cp; cp++)
405			putwchar(*cp=='_' ? ' ' : *cp);
406	}
407}
408
409static void
410iattr(void)
411{
412	int i;
413	wchar_t lbuf[256];
414	wchar_t *cp = lbuf;
415
416	for (i=0; i<maxcol; i++)
417		switch (obuf[i].c_mode) {
418		case NORMAL:	*cp++ = ' '; break;
419		case ALTSET:	*cp++ = 'g'; break;
420		case SUPERSC:	*cp++ = '^'; break;
421		case SUBSC:	*cp++ = 'v'; break;
422		case UNDERL:	*cp++ = '_'; break;
423		case BOLD:	*cp++ = '!'; break;
424		default:	*cp++ = 'X'; break;
425		}
426	for (*cp=' '; *cp==' '; cp--)
427		*cp = 0;
428	for (cp=lbuf; *cp; cp++)
429		putwchar(*cp);
430	putwchar('\n');
431}
432
433static void
434initbuf(void)
435{
436
437	bzero((char *)obuf, buflen * sizeof(*obuf)); /* depends on NORMAL == 0 */
438	col = 0;
439	maxcol = 0;
440	mode &= ALTSET;
441}
442
443static void
444fwd(void)
445{
446	int oldcol, oldmax;
447
448	oldcol = col;
449	oldmax = maxcol;
450	flushln();
451	col = oldcol;
452	maxcol = oldmax;
453}
454
455static void
456reverse(void)
457{
458	upln++;
459	fwd();
460	PRINT(CURS_UP);
461	PRINT(CURS_UP);
462	upln++;
463}
464
465static void
466initcap(void)
467{
468	static char tcapbuf[512];
469	char *bp = tcapbuf;
470
471	/* This nonsense attempts to work with both old and new termcap */
472	CURS_UP =		tgetstr("up", &bp);
473	CURS_RIGHT =		tgetstr("ri", &bp);
474	if (CURS_RIGHT == NULL)
475		CURS_RIGHT =	tgetstr("nd", &bp);
476	CURS_LEFT =		tgetstr("le", &bp);
477	if (CURS_LEFT == NULL)
478		CURS_LEFT =	tgetstr("bc", &bp);
479	if (CURS_LEFT == NULL && tgetflag("bs"))
480		CURS_LEFT =	"\b";
481
482	ENTER_STANDOUT =	tgetstr("so", &bp);
483	EXIT_STANDOUT =		tgetstr("se", &bp);
484	ENTER_UNDERLINE =	tgetstr("us", &bp);
485	EXIT_UNDERLINE =	tgetstr("ue", &bp);
486	ENTER_DIM =		tgetstr("mh", &bp);
487	ENTER_BOLD =		tgetstr("md", &bp);
488	ENTER_REVERSE =		tgetstr("mr", &bp);
489	EXIT_ATTRIBUTES =	tgetstr("me", &bp);
490
491	if (!ENTER_BOLD && ENTER_REVERSE)
492		ENTER_BOLD = ENTER_REVERSE;
493	if (!ENTER_BOLD && ENTER_STANDOUT)
494		ENTER_BOLD = ENTER_STANDOUT;
495	if (!ENTER_UNDERLINE && ENTER_STANDOUT) {
496		ENTER_UNDERLINE = ENTER_STANDOUT;
497		EXIT_UNDERLINE = EXIT_STANDOUT;
498	}
499	if (!ENTER_DIM && ENTER_STANDOUT)
500		ENTER_DIM = ENTER_STANDOUT;
501	if (!ENTER_REVERSE && ENTER_STANDOUT)
502		ENTER_REVERSE = ENTER_STANDOUT;
503	if (!EXIT_ATTRIBUTES && EXIT_STANDOUT)
504		EXIT_ATTRIBUTES = EXIT_STANDOUT;
505
506	/*
507	 * Note that we use REVERSE for the alternate character set,
508	 * not the as/ae capabilities.  This is because we are modelling
509	 * the model 37 teletype (since that's what nroff outputs) and
510	 * the typical as/ae is more of a graphics set, not the greek
511	 * letters the 37 has.
512	 */
513
514	UNDER_CHAR =		tgetstr("uc", &bp);
515	must_use_uc = (UNDER_CHAR && !ENTER_UNDERLINE);
516}
517
518static int
519outchar(int c)
520{
521	return (putwchar(c) != WEOF ? c : EOF);
522}
523
524static int curmode = 0;
525
526static void
527outc(wint_t c, int width)
528{
529	int i;
530
531	putwchar(c);
532	if (must_use_uc && (curmode&UNDERL)) {
533		for (i = 0; i < width; i++)
534			PRINT(CURS_LEFT);
535		for (i = 0; i < width; i++)
536			PRINT(UNDER_CHAR);
537	}
538}
539
540static void
541setnewmode(int newmode)
542{
543	if (!iflag) {
544		if (curmode != NORMAL && newmode != NORMAL)
545			setnewmode(NORMAL);
546		switch (newmode) {
547		case NORMAL:
548			switch(curmode) {
549			case NORMAL:
550				break;
551			case UNDERL:
552				PRINT(EXIT_UNDERLINE);
553				break;
554			default:
555				/* This includes standout */
556				PRINT(EXIT_ATTRIBUTES);
557				break;
558			}
559			break;
560		case ALTSET:
561			PRINT(ENTER_REVERSE);
562			break;
563		case SUPERSC:
564			/*
565			 * This only works on a few terminals.
566			 * It should be fixed.
567			 */
568			PRINT(ENTER_UNDERLINE);
569			PRINT(ENTER_DIM);
570			break;
571		case SUBSC:
572			PRINT(ENTER_DIM);
573			break;
574		case UNDERL:
575			PRINT(ENTER_UNDERLINE);
576			break;
577		case BOLD:
578			PRINT(ENTER_BOLD);
579			break;
580		default:
581			/*
582			 * We should have some provision here for multiple modes
583			 * on at once.  This will have to come later.
584			 */
585			PRINT(ENTER_STANDOUT);
586			break;
587		}
588	}
589	curmode = newmode;
590}
591