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