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$";
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	obuf[MAXBUF];
82static int	col, maxcol;
83static int	mode;
84static int	halfpos;
85static int	upln;
86static int	iflag;
87
88static void usage(void);
89static void setnewmode(int);
90static void initcap(void);
91static void reverse(void);
92static int outchar(int);
93static void fwd(void);
94static void initbuf(void);
95static void iattr(void);
96static void overstrike(void);
97static void flushln(void);
98static void filter(FILE *);
99static void outc(wint_t, int);
100
101#define	PRINT(s)	if (s == NULL) /* void */; else tputs(s, 1, outchar)
102
103int
104main(int argc, char **argv)
105{
106	int c;
107	const char *termtype;
108	FILE *f;
109	char termcap[1024];
110
111	setlocale(LC_ALL, "");
112
113	termtype = getenv("TERM");
114	if (termtype == NULL || (argv[0][0] == 'c' && !isatty(1)))
115		termtype = "lpr";
116	while ((c=getopt(argc, argv, "it:T:")) != -1)
117		switch(c) {
118
119		case 't':
120		case 'T': /* for nroff compatibility */
121			termtype = optarg;
122			break;
123		case 'i':
124			iflag = 1;
125			break;
126		default:
127			usage();
128		}
129
130	switch(tgetent(termcap, termtype)) {
131
132	case 1:
133		break;
134
135	default:
136		warnx("trouble reading termcap");
137		/* FALLTHROUGH */
138
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	exit(0);
159}
160
161static void
162usage(void)
163{
164	fprintf(stderr, "usage: ul [-i] [-t terminal] [file ...]\n");
165	exit(1);
166}
167
168static void
169filter(FILE *f)
170{
171	wint_t c;
172	int i, w;
173
174	while ((c = getwc(f)) != WEOF && col < MAXBUF) switch(c) {
175
176	case '\b':
177		if (col > 0)
178			col--;
179		continue;
180
181	case '\t':
182		col = (col+8) & ~07;
183		if (col > maxcol)
184			maxcol = col;
185		continue;
186
187	case '\r':
188		col = 0;
189		continue;
190
191	case SO:
192		mode |= ALTSET;
193		continue;
194
195	case SI:
196		mode &= ~ALTSET;
197		continue;
198
199	case IESC:
200		switch (c = getwc(f)) {
201
202		case HREV:
203			if (halfpos == 0) {
204				mode |= SUPERSC;
205				halfpos--;
206			} else if (halfpos > 0) {
207				mode &= ~SUBSC;
208				halfpos--;
209			} else {
210				halfpos = 0;
211				reverse();
212			}
213			continue;
214
215		case HFWD:
216			if (halfpos == 0) {
217				mode |= SUBSC;
218				halfpos++;
219			} else if (halfpos < 0) {
220				mode &= ~SUPERSC;
221				halfpos++;
222			} else {
223				halfpos = 0;
224				fwd();
225			}
226			continue;
227
228		case FREV:
229			reverse();
230			continue;
231
232		default:
233			errx(1, "unknown escape sequence in input: %o, %o", IESC, c);
234		}
235		continue;
236
237	case '_':
238		if (obuf[col].c_char || obuf[col].c_width < 0) {
239			while (col > 0 && obuf[col].c_width < 0)
240				col--;
241			w = obuf[col].c_width;
242			for (i = 0; i < w; i++)
243				obuf[col++].c_mode |= UNDERL | mode;
244			if (col > maxcol)
245				maxcol = col;
246			continue;
247		}
248		obuf[col].c_char = '_';
249		obuf[col].c_width = 1;
250		/* FALLTHROUGH */
251	case ' ':
252		col++;
253		if (col > maxcol)
254			maxcol = col;
255		continue;
256
257	case '\n':
258		flushln();
259		continue;
260
261	case '\f':
262		flushln();
263		putwchar('\f');
264		continue;
265
266	default:
267		if ((w = wcwidth(c)) <= 0)	/* non printing */
268			continue;
269		if (obuf[col].c_char == '\0') {
270			obuf[col].c_char = c;
271			for (i = 0; i < w; i++)
272				obuf[col + i].c_mode = mode;
273			obuf[col].c_width = w;
274			for (i = 1; i < w; i++)
275				obuf[col + i].c_width = -1;
276		} else if (obuf[col].c_char == '_') {
277			obuf[col].c_char = c;
278			for (i = 0; i < w; i++)
279				obuf[col + i].c_mode |= UNDERL|mode;
280			obuf[col].c_width = w;
281			for (i = 1; i < w; i++)
282				obuf[col + i].c_width = -1;
283		} else if ((wint_t)obuf[col].c_char == c) {
284			for (i = 0; i < w; i++)
285				obuf[col + i].c_mode |= BOLD|mode;
286		} else {
287			w = obuf[col].c_width;
288			for (i = 0; i < w; i++)
289				obuf[col + i].c_mode = mode;
290		}
291		col += w;
292		if (col > maxcol)
293			maxcol = col;
294		continue;
295	}
296	if (ferror(f))
297		err(1, NULL);
298	if (maxcol)
299		flushln();
300}
301
302static void
303flushln(void)
304{
305	int lastmode;
306	int i;
307	int hadmodes = 0;
308
309	lastmode = NORMAL;
310	for (i=0; i<maxcol; i++) {
311		if (obuf[i].c_mode != lastmode) {
312			hadmodes++;
313			setnewmode(obuf[i].c_mode);
314			lastmode = obuf[i].c_mode;
315		}
316		if (obuf[i].c_char == '\0') {
317			if (upln)
318				PRINT(CURS_RIGHT);
319			else
320				outc(' ', 1);
321		} else
322			outc(obuf[i].c_char, obuf[i].c_width);
323		if (obuf[i].c_width > 1)
324			i += obuf[i].c_width - 1;
325	}
326	if (lastmode != NORMAL) {
327		setnewmode(0);
328	}
329	if (must_overstrike && hadmodes)
330		overstrike();
331	putwchar('\n');
332	if (iflag && hadmodes)
333		iattr();
334	(void)fflush(stdout);
335	if (upln)
336		upln--;
337	initbuf();
338}
339
340/*
341 * For terminals that can overstrike, overstrike underlines and bolds.
342 * We don't do anything with halfline ups and downs, or Greek.
343 */
344static void
345overstrike(void)
346{
347	int i;
348	wchar_t lbuf[256];
349	wchar_t *cp = lbuf;
350	int hadbold=0;
351
352	/* Set up overstrike buffer */
353	for (i=0; i<maxcol; i++)
354		switch (obuf[i].c_mode) {
355		case NORMAL:
356		default:
357			*cp++ = ' ';
358			break;
359		case UNDERL:
360			*cp++ = '_';
361			break;
362		case BOLD:
363			*cp++ = obuf[i].c_char;
364			if (obuf[i].c_width > 1)
365				i += obuf[i].c_width - 1;
366			hadbold=1;
367			break;
368		}
369	putwchar('\r');
370	for (*cp=' '; *cp==' '; cp--)
371		*cp = 0;
372	for (cp=lbuf; *cp; cp++)
373		putwchar(*cp);
374	if (hadbold) {
375		putwchar('\r');
376		for (cp=lbuf; *cp; cp++)
377			putwchar(*cp=='_' ? ' ' : *cp);
378		putwchar('\r');
379		for (cp=lbuf; *cp; cp++)
380			putwchar(*cp=='_' ? ' ' : *cp);
381	}
382}
383
384static void
385iattr(void)
386{
387	int i;
388	wchar_t lbuf[256];
389	wchar_t *cp = lbuf;
390
391	for (i=0; i<maxcol; i++)
392		switch (obuf[i].c_mode) {
393		case NORMAL:	*cp++ = ' '; break;
394		case ALTSET:	*cp++ = 'g'; break;
395		case SUPERSC:	*cp++ = '^'; break;
396		case SUBSC:	*cp++ = 'v'; break;
397		case UNDERL:	*cp++ = '_'; break;
398		case BOLD:	*cp++ = '!'; break;
399		default:	*cp++ = 'X'; break;
400		}
401	for (*cp=' '; *cp==' '; cp--)
402		*cp = 0;
403	for (cp=lbuf; *cp; cp++)
404		putwchar(*cp);
405	putwchar('\n');
406}
407
408static void
409initbuf(void)
410{
411
412	bzero((char *)obuf, sizeof (obuf));	/* depends on NORMAL == 0 */
413	col = 0;
414	maxcol = 0;
415	mode &= ALTSET;
416}
417
418static void
419fwd(void)
420{
421	int oldcol, oldmax;
422
423	oldcol = col;
424	oldmax = maxcol;
425	flushln();
426	col = oldcol;
427	maxcol = oldmax;
428}
429
430static void
431reverse(void)
432{
433	upln++;
434	fwd();
435	PRINT(CURS_UP);
436	PRINT(CURS_UP);
437	upln++;
438}
439
440static void
441initcap(void)
442{
443	static char tcapbuf[512];
444	char *bp = tcapbuf;
445
446	/* This nonsense attempts to work with both old and new termcap */
447	CURS_UP =		tgetstr("up", &bp);
448	CURS_RIGHT =		tgetstr("ri", &bp);
449	if (CURS_RIGHT == NULL)
450		CURS_RIGHT =	tgetstr("nd", &bp);
451	CURS_LEFT =		tgetstr("le", &bp);
452	if (CURS_LEFT == NULL)
453		CURS_LEFT =	tgetstr("bc", &bp);
454	if (CURS_LEFT == NULL && tgetflag("bs"))
455		CURS_LEFT =	"\b";
456
457	ENTER_STANDOUT =	tgetstr("so", &bp);
458	EXIT_STANDOUT =		tgetstr("se", &bp);
459	ENTER_UNDERLINE =	tgetstr("us", &bp);
460	EXIT_UNDERLINE =	tgetstr("ue", &bp);
461	ENTER_DIM =		tgetstr("mh", &bp);
462	ENTER_BOLD =		tgetstr("md", &bp);
463	ENTER_REVERSE =		tgetstr("mr", &bp);
464	EXIT_ATTRIBUTES =	tgetstr("me", &bp);
465
466	if (!ENTER_BOLD && ENTER_REVERSE)
467		ENTER_BOLD = ENTER_REVERSE;
468	if (!ENTER_BOLD && ENTER_STANDOUT)
469		ENTER_BOLD = ENTER_STANDOUT;
470	if (!ENTER_UNDERLINE && ENTER_STANDOUT) {
471		ENTER_UNDERLINE = ENTER_STANDOUT;
472		EXIT_UNDERLINE = EXIT_STANDOUT;
473	}
474	if (!ENTER_DIM && ENTER_STANDOUT)
475		ENTER_DIM = ENTER_STANDOUT;
476	if (!ENTER_REVERSE && ENTER_STANDOUT)
477		ENTER_REVERSE = ENTER_STANDOUT;
478	if (!EXIT_ATTRIBUTES && EXIT_STANDOUT)
479		EXIT_ATTRIBUTES = EXIT_STANDOUT;
480
481	/*
482	 * Note that we use REVERSE for the alternate character set,
483	 * not the as/ae capabilities.  This is because we are modelling
484	 * the model 37 teletype (since that's what nroff outputs) and
485	 * the typical as/ae is more of a graphics set, not the greek
486	 * letters the 37 has.
487	 */
488
489	UNDER_CHAR =		tgetstr("uc", &bp);
490	must_use_uc = (UNDER_CHAR && !ENTER_UNDERLINE);
491}
492
493static int
494outchar(int c)
495{
496	return (putwchar(c) != WEOF ? c : EOF);
497}
498
499static int curmode = 0;
500
501static void
502outc(wint_t c, int width)
503{
504	int i;
505
506	putwchar(c);
507	if (must_use_uc && (curmode&UNDERL)) {
508		for (i = 0; i < width; i++)
509			PRINT(CURS_LEFT);
510		for (i = 0; i < width; i++)
511			PRINT(UNDER_CHAR);
512	}
513}
514
515static void
516setnewmode(int newmode)
517{
518	if (!iflag) {
519		if (curmode != NORMAL && newmode != NORMAL)
520			setnewmode(NORMAL);
521		switch (newmode) {
522		case NORMAL:
523			switch(curmode) {
524			case NORMAL:
525				break;
526			case UNDERL:
527				PRINT(EXIT_UNDERLINE);
528				break;
529			default:
530				/* This includes standout */
531				PRINT(EXIT_ATTRIBUTES);
532				break;
533			}
534			break;
535		case ALTSET:
536			PRINT(ENTER_REVERSE);
537			break;
538		case SUPERSC:
539			/*
540			 * This only works on a few terminals.
541			 * It should be fixed.
542			 */
543			PRINT(ENTER_UNDERLINE);
544			PRINT(ENTER_DIM);
545			break;
546		case SUBSC:
547			PRINT(ENTER_DIM);
548			break;
549		case UNDERL:
550			PRINT(ENTER_UNDERLINE);
551			break;
552		case BOLD:
553			PRINT(ENTER_BOLD);
554			break;
555		default:
556			/*
557			 * We should have some provision here for multiple modes
558			 * on at once.  This will have to come later.
559			 */
560			PRINT(ENTER_STANDOUT);
561			break;
562		}
563	}
564	curmode = newmode;
565}
566