ul.c revision 200462
1176491Smarcel/*
2176491Smarcel * Copyright (c) 1980, 1993
3176491Smarcel *	The Regents of the University of California.  All rights reserved.
4176491Smarcel *
5176491Smarcel * Redistribution and use in source and binary forms, with or without
6176491Smarcel * modification, are permitted provided that the following conditions
7176491Smarcel * are met:
8176491Smarcel * 1. Redistributions of source code must retain the above copyright
9176491Smarcel *    notice, this list of conditions and the following disclaimer.
10176491Smarcel * 2. Redistributions in binary form must reproduce the above copyright
11176491Smarcel *    notice, this list of conditions and the following disclaimer in the
12176491Smarcel *    documentation and/or other materials provided with the distribution.
13176491Smarcel * 3. All advertising materials mentioning features or use of this software
14176491Smarcel *    must display the following acknowledgement:
15176491Smarcel *	This product includes software developed by the University of
16176491Smarcel *	California, Berkeley and its contributors.
17176491Smarcel * 4. Neither the name of the University nor the names of its contributors
18176491Smarcel *    may be used to endorse or promote products derived from this software
19176491Smarcel *    without specific prior written permission.
20176491Smarcel *
21176491Smarcel * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22176491Smarcel * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23176491Smarcel * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24176491Smarcel * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25176491Smarcel * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26176491Smarcel * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27176491Smarcel * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28176491Smarcel * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29176491Smarcel * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30176491Smarcel * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31176491Smarcel * SUCH DAMAGE.
32176491Smarcel */
33176491Smarcel
34176491Smarcel#ifndef lint
35176491Smarcelstatic const char copyright[] =
36176491Smarcel"@(#) Copyright (c) 1980, 1993\n\
37176491Smarcel	The Regents of the University of California.  All rights reserved.\n";
38176491Smarcel#endif /* not lint */
39176491Smarcel
40176491Smarcel#ifndef lint
41176491Smarcel#if 0
42176491Smarcelstatic char sccsid[] = "@(#)ul.c	8.1 (Berkeley) 6/6/93";
43176491Smarcel#endif
44176491Smarcelstatic const char rcsid[] =
45176491Smarcel  "$FreeBSD: head/usr.bin/ul/ul.c 200462 2009-12-13 03:14:06Z delphij $";
46176491Smarcel#endif /* not lint */
47176491Smarcel
48176491Smarcel#include <err.h>
49176491Smarcel#include <locale.h>
50176491Smarcel#include <stdio.h>
51176491Smarcel#include <stdlib.h>
52176491Smarcel#include <string.h>
53176491Smarcel#include <termcap.h>
54176491Smarcel#include <unistd.h>
55176491Smarcel#include <wchar.h>
56176491Smarcel#include <wctype.h>
57176491Smarcel
58176491Smarcel#define	IESC	'\033'
59176491Smarcel#define	SO	'\016'
60176491Smarcel#define	SI	'\017'
61176491Smarcel#define	HFWD	'9'
62176491Smarcel#define	HREV	'8'
63176491Smarcel#define	FREV	'7'
64176491Smarcel#define	MAXBUF	512
65176491Smarcel
66176491Smarcel#define	NORMAL	000
67176491Smarcel#define	ALTSET	001	/* Reverse */
68176491Smarcel#define	SUPERSC	002	/* Dim */
69176491Smarcel#define	SUBSC	004	/* Dim | Ul */
70176491Smarcel#define	UNDERL	010	/* Ul */
71176491Smarcel#define	BOLD	020	/* Bold */
72176491Smarcel
73176491Smarcelint	must_use_uc, must_overstrike;
74176491Smarcelconst char
75176491Smarcel	*CURS_UP, *CURS_RIGHT, *CURS_LEFT,
76176491Smarcel	*ENTER_STANDOUT, *EXIT_STANDOUT, *ENTER_UNDERLINE, *EXIT_UNDERLINE,
77176491Smarcel	*ENTER_DIM, *ENTER_BOLD, *ENTER_REVERSE, *UNDER_CHAR, *EXIT_ATTRIBUTES;
78176491Smarcel
79176491Smarcelstruct	CHAR	{
80176491Smarcel	char	c_mode;
81176491Smarcel	wchar_t	c_char;
82176491Smarcel	int	c_width;	/* width or -1 if multi-column char. filler */
83176491Smarcel} ;
84176491Smarcel
85176491Smarcelstruct	CHAR	obuf[MAXBUF];
86176491Smarcelint	col, maxcol;
87176491Smarcelint	mode;
88176491Smarcelint	halfpos;
89176491Smarcelint	upln;
90176491Smarcelint	iflag;
91176491Smarcel
92176491Smarcelstatic void usage(void);
93176491Smarcelvoid setnewmode(int);
94176491Smarcelvoid initcap(void);
95176491Smarcelvoid reverse(void);
96176491Smarcelint outchar(int);
97176491Smarcelvoid fwd(void);
98176491Smarcelvoid initbuf(void);
99176491Smarcelvoid iattr(void);
100176491Smarcelvoid overstrike(void);
101176491Smarcelvoid flushln(void);
102176491Smarcelvoid filter(FILE *);
103176491Smarcelvoid outc(wint_t, int);
104176491Smarcel
105176491Smarcel#define	PRINT(s)	if (s == NULL) /* void */; else tputs(s, 1, outchar)
106176491Smarcel
107176491Smarcelint
108176491Smarcelmain(int argc, char **argv)
109176491Smarcel{
110176491Smarcel	int c;
111176491Smarcel	const char *termtype;
112176491Smarcel	FILE *f;
113176491Smarcel	char termcap[1024];
114176491Smarcel
115176491Smarcel	setlocale(LC_ALL, "");
116176491Smarcel
117176491Smarcel	termtype = getenv("TERM");
118176491Smarcel	if (termtype == NULL || (argv[0][0] == 'c' && !isatty(1)))
119176491Smarcel		termtype = "lpr";
120176491Smarcel	while ((c=getopt(argc, argv, "it:T:")) != -1)
121176491Smarcel		switch(c) {
122176491Smarcel
123176491Smarcel		case 't':
124176491Smarcel		case 'T': /* for nroff compatibility */
125176491Smarcel			termtype = optarg;
126176491Smarcel			break;
127176491Smarcel		case 'i':
128176491Smarcel			iflag = 1;
129176491Smarcel			break;
130176491Smarcel		default:
131176491Smarcel			usage();
132176491Smarcel		}
133176491Smarcel
134176491Smarcel	switch(tgetent(termcap, termtype)) {
135176491Smarcel
136176491Smarcel	case 1:
137176491Smarcel		break;
138176491Smarcel
139176491Smarcel	default:
140176491Smarcel		warnx("trouble reading termcap");
141176491Smarcel		/* FALLTHROUGH */
142176491Smarcel
143176491Smarcel	case 0:
144176491Smarcel		/* No such terminal type - assume dumb */
145176491Smarcel		(void)strcpy(termcap, "dumb:os:col#80:cr=^M:sf=^J:am:");
146176491Smarcel		break;
147176491Smarcel	}
148176491Smarcel	initcap();
149176491Smarcel	if (    (tgetflag("os") && ENTER_BOLD==NULL ) ||
150176491Smarcel		(tgetflag("ul") && ENTER_UNDERLINE==NULL && UNDER_CHAR==NULL))
151176491Smarcel			must_overstrike = 1;
152176491Smarcel	initbuf();
153176491Smarcel	if (optind == argc)
154176491Smarcel		filter(stdin);
155176491Smarcel	else for (; optind<argc; optind++) {
156176491Smarcel		f = fopen(argv[optind],"r");
157176491Smarcel		if (f == NULL)
158176491Smarcel			err(1, "%s", argv[optind]);
159176491Smarcel		else
160176491Smarcel			filter(f);
161176491Smarcel	}
162176491Smarcel	exit(0);
163176491Smarcel}
164176491Smarcel
165176491Smarcelstatic void
166176491Smarcelusage(void)
167176491Smarcel{
168176491Smarcel	fprintf(stderr, "usage: ul [-i] [-t terminal] [file ...]\n");
169176491Smarcel	exit(1);
170176501Smarcel}
171176501Smarcel
172176491Smarcelvoid
173176491Smarcelfilter(FILE *f)
174176491Smarcel{
175176491Smarcel	wint_t c;
176176491Smarcel	int i, w;
177176491Smarcel
178176491Smarcel	while ((c = getwc(f)) != WEOF && col < MAXBUF) switch(c) {
179176491Smarcel
180176491Smarcel	case '\b':
181176491Smarcel		if (col > 0)
182176491Smarcel			col--;
183176491Smarcel		continue;
184176491Smarcel
185176491Smarcel	case '\t':
186176491Smarcel		col = (col+8) & ~07;
187176491Smarcel		if (col > maxcol)
188176491Smarcel			maxcol = col;
189176491Smarcel		continue;
190176491Smarcel
191176491Smarcel	case '\r':
192176491Smarcel		col = 0;
193176491Smarcel		continue;
194176491Smarcel
195	case SO:
196		mode |= ALTSET;
197		continue;
198
199	case SI:
200		mode &= ~ALTSET;
201		continue;
202
203	case IESC:
204		switch (c = getwc(f)) {
205
206		case HREV:
207			if (halfpos == 0) {
208				mode |= SUPERSC;
209				halfpos--;
210			} else if (halfpos > 0) {
211				mode &= ~SUBSC;
212				halfpos--;
213			} else {
214				halfpos = 0;
215				reverse();
216			}
217			continue;
218
219		case HFWD:
220			if (halfpos == 0) {
221				mode |= SUBSC;
222				halfpos++;
223			} else if (halfpos < 0) {
224				mode &= ~SUPERSC;
225				halfpos++;
226			} else {
227				halfpos = 0;
228				fwd();
229			}
230			continue;
231
232		case FREV:
233			reverse();
234			continue;
235
236		default:
237			errx(1, "unknown escape sequence in input: %o, %o", IESC, c);
238		}
239		continue;
240
241	case '_':
242		if (obuf[col].c_char || obuf[col].c_width < 0) {
243			while (col > 0 && obuf[col].c_width < 0)
244				col--;
245			w = obuf[col].c_width;
246			for (i = 0; i < w; i++)
247				obuf[col++].c_mode |= UNDERL | mode;
248			if (col > maxcol)
249				maxcol = col;
250			continue;
251		}
252		obuf[col].c_char = '_';
253		obuf[col].c_width = 1;
254		/* FALLTHROUGH */
255	case ' ':
256		col++;
257		if (col > maxcol)
258			maxcol = col;
259		continue;
260
261	case '\n':
262		flushln();
263		continue;
264
265	case '\f':
266		flushln();
267		putwchar('\f');
268		continue;
269
270	default:
271		if ((w = wcwidth(c)) <= 0)	/* non printing */
272			continue;
273		if (obuf[col].c_char == '\0') {
274			obuf[col].c_char = c;
275			for (i = 0; i < w; i++)
276				obuf[col + i].c_mode = mode;
277			obuf[col].c_width = w;
278			for (i = 1; i < w; i++)
279				obuf[col + i].c_width = -1;
280		} else if (obuf[col].c_char == '_') {
281			obuf[col].c_char = c;
282			for (i = 0; i < w; i++)
283				obuf[col + i].c_mode |= UNDERL|mode;
284			obuf[col].c_width = w;
285			for (i = 1; i < w; i++)
286				obuf[col + i].c_width = -1;
287		} else if (obuf[col].c_char == c) {
288			for (i = 0; i < w; i++)
289				obuf[col + i].c_mode |= BOLD|mode;
290		} else {
291			w = obuf[col].c_width;
292			for (i = 0; i < w; i++)
293				obuf[col + i].c_mode = mode;
294		}
295		col += w;
296		if (col > maxcol)
297			maxcol = col;
298		continue;
299	}
300	if (ferror(f))
301		err(1, NULL);
302	if (maxcol)
303		flushln();
304}
305
306void
307flushln(void)
308{
309	int lastmode;
310	int i;
311	int hadmodes = 0;
312
313	lastmode = NORMAL;
314	for (i=0; i<maxcol; i++) {
315		if (obuf[i].c_mode != lastmode) {
316			hadmodes++;
317			setnewmode(obuf[i].c_mode);
318			lastmode = obuf[i].c_mode;
319		}
320		if (obuf[i].c_char == '\0') {
321			if (upln)
322				PRINT(CURS_RIGHT);
323			else
324				outc(' ', 1);
325		} else
326			outc(obuf[i].c_char, obuf[i].c_width);
327		if (obuf[i].c_width > 1)
328			i += obuf[i].c_width - 1;
329	}
330	if (lastmode != NORMAL) {
331		setnewmode(0);
332	}
333	if (must_overstrike && hadmodes)
334		overstrike();
335	putwchar('\n');
336	if (iflag && hadmodes)
337		iattr();
338	(void)fflush(stdout);
339	if (upln)
340		upln--;
341	initbuf();
342}
343
344/*
345 * For terminals that can overstrike, overstrike underlines and bolds.
346 * We don't do anything with halfline ups and downs, or Greek.
347 */
348void
349overstrike(void)
350{
351	int i;
352	wchar_t lbuf[256];
353	wchar_t *cp = lbuf;
354	int hadbold=0;
355
356	/* Set up overstrike buffer */
357	for (i=0; i<maxcol; i++)
358		switch (obuf[i].c_mode) {
359		case NORMAL:
360		default:
361			*cp++ = ' ';
362			break;
363		case UNDERL:
364			*cp++ = '_';
365			break;
366		case BOLD:
367			*cp++ = obuf[i].c_char;
368			if (obuf[i].c_width > 1)
369				i += obuf[i].c_width - 1;
370			hadbold=1;
371			break;
372		}
373	putwchar('\r');
374	for (*cp=' '; *cp==' '; cp--)
375		*cp = 0;
376	for (cp=lbuf; *cp; cp++)
377		putwchar(*cp);
378	if (hadbold) {
379		putwchar('\r');
380		for (cp=lbuf; *cp; cp++)
381			putwchar(*cp=='_' ? ' ' : *cp);
382		putwchar('\r');
383		for (cp=lbuf; *cp; cp++)
384			putwchar(*cp=='_' ? ' ' : *cp);
385	}
386}
387
388void
389iattr(void)
390{
391	int i;
392	wchar_t lbuf[256];
393	wchar_t *cp = lbuf;
394
395	for (i=0; i<maxcol; i++)
396		switch (obuf[i].c_mode) {
397		case NORMAL:	*cp++ = ' '; break;
398		case ALTSET:	*cp++ = 'g'; break;
399		case SUPERSC:	*cp++ = '^'; break;
400		case SUBSC:	*cp++ = 'v'; break;
401		case UNDERL:	*cp++ = '_'; break;
402		case BOLD:	*cp++ = '!'; break;
403		default:	*cp++ = 'X'; break;
404		}
405	for (*cp=' '; *cp==' '; cp--)
406		*cp = 0;
407	for (cp=lbuf; *cp; cp++)
408		putwchar(*cp);
409	putwchar('\n');
410}
411
412void
413initbuf(void)
414{
415
416	bzero((char *)obuf, sizeof (obuf));	/* depends on NORMAL == 0 */
417	col = 0;
418	maxcol = 0;
419	mode &= ALTSET;
420}
421
422void
423fwd(void)
424{
425	int oldcol, oldmax;
426
427	oldcol = col;
428	oldmax = maxcol;
429	flushln();
430	col = oldcol;
431	maxcol = oldmax;
432}
433
434void
435reverse(void)
436{
437	upln++;
438	fwd();
439	PRINT(CURS_UP);
440	PRINT(CURS_UP);
441	upln++;
442}
443
444void
445initcap(void)
446{
447	static char tcapbuf[512];
448	char *bp = tcapbuf;
449
450	/* This nonsense attempts to work with both old and new termcap */
451	CURS_UP =		tgetstr("up", &bp);
452	CURS_RIGHT =		tgetstr("ri", &bp);
453	if (CURS_RIGHT == NULL)
454		CURS_RIGHT =	tgetstr("nd", &bp);
455	CURS_LEFT =		tgetstr("le", &bp);
456	if (CURS_LEFT == NULL)
457		CURS_LEFT =	tgetstr("bc", &bp);
458	if (CURS_LEFT == NULL && tgetflag("bs"))
459		CURS_LEFT =	"\b";
460
461	ENTER_STANDOUT =	tgetstr("so", &bp);
462	EXIT_STANDOUT =		tgetstr("se", &bp);
463	ENTER_UNDERLINE =	tgetstr("us", &bp);
464	EXIT_UNDERLINE =	tgetstr("ue", &bp);
465	ENTER_DIM =		tgetstr("mh", &bp);
466	ENTER_BOLD =		tgetstr("md", &bp);
467	ENTER_REVERSE =		tgetstr("mr", &bp);
468	EXIT_ATTRIBUTES =	tgetstr("me", &bp);
469
470	if (!ENTER_BOLD && ENTER_REVERSE)
471		ENTER_BOLD = ENTER_REVERSE;
472	if (!ENTER_BOLD && ENTER_STANDOUT)
473		ENTER_BOLD = ENTER_STANDOUT;
474	if (!ENTER_UNDERLINE && ENTER_STANDOUT) {
475		ENTER_UNDERLINE = ENTER_STANDOUT;
476		EXIT_UNDERLINE = EXIT_STANDOUT;
477	}
478	if (!ENTER_DIM && ENTER_STANDOUT)
479		ENTER_DIM = ENTER_STANDOUT;
480	if (!ENTER_REVERSE && ENTER_STANDOUT)
481		ENTER_REVERSE = ENTER_STANDOUT;
482	if (!EXIT_ATTRIBUTES && EXIT_STANDOUT)
483		EXIT_ATTRIBUTES = EXIT_STANDOUT;
484
485	/*
486	 * Note that we use REVERSE for the alternate character set,
487	 * not the as/ae capabilities.  This is because we are modelling
488	 * the model 37 teletype (since that's what nroff outputs) and
489	 * the typical as/ae is more of a graphics set, not the greek
490	 * letters the 37 has.
491	 */
492
493	UNDER_CHAR =		tgetstr("uc", &bp);
494	must_use_uc = (UNDER_CHAR && !ENTER_UNDERLINE);
495}
496
497int
498outchar(int c)
499{
500	return (putwchar(c) != WEOF ? c : EOF);
501}
502
503static int curmode = 0;
504
505void
506outc(wint_t c, int width)
507{
508	int i;
509
510	putwchar(c);
511	if (must_use_uc && (curmode&UNDERL)) {
512		for (i = 0; i < width; i++)
513			PRINT(CURS_LEFT);
514		for (i = 0; i < width; i++)
515			PRINT(UNDER_CHAR);
516	}
517}
518
519void
520setnewmode(int newmode)
521{
522	if (!iflag) {
523		if (curmode != NORMAL && newmode != NORMAL)
524			setnewmode(NORMAL);
525		switch (newmode) {
526		case NORMAL:
527			switch(curmode) {
528			case NORMAL:
529				break;
530			case UNDERL:
531				PRINT(EXIT_UNDERLINE);
532				break;
533			default:
534				/* This includes standout */
535				PRINT(EXIT_ATTRIBUTES);
536				break;
537			}
538			break;
539		case ALTSET:
540			PRINT(ENTER_REVERSE);
541			break;
542		case SUPERSC:
543			/*
544			 * This only works on a few terminals.
545			 * It should be fixed.
546			 */
547			PRINT(ENTER_UNDERLINE);
548			PRINT(ENTER_DIM);
549			break;
550		case SUBSC:
551			PRINT(ENTER_DIM);
552			break;
553		case UNDERL:
554			PRINT(ENTER_UNDERLINE);
555			break;
556		case BOLD:
557			PRINT(ENTER_BOLD);
558			break;
559		default:
560			/*
561			 * We should have some provision here for multiple modes
562			 * on at once.  This will have to come later.
563			 */
564			PRINT(ENTER_STANDOUT);
565			break;
566		}
567	}
568	curmode = newmode;
569}
570