xref: /freebsd/usr.bin/ul/ul.c (revision 4f29da19bd44f0e99f021510460a81bf754c21d2)
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  * 3. All advertising materials mentioning features or use of this software
14  *    must display the following acknowledgement:
15  *	This product includes software developed by the University of
16  *	California, Berkeley and its contributors.
17  * 4. Neither the name of the University nor the names of its contributors
18  *    may be used to endorse or promote products derived from this software
19  *    without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  */
33 
34 #ifndef lint
35 static const char copyright[] =
36 "@(#) Copyright (c) 1980, 1993\n\
37 	The Regents of the University of California.  All rights reserved.\n";
38 #endif /* not lint */
39 
40 #ifndef lint
41 #if 0
42 static char sccsid[] = "@(#)ul.c	8.1 (Berkeley) 6/6/93";
43 #endif
44 static const char rcsid[] =
45   "$FreeBSD$";
46 #endif /* not lint */
47 
48 #include <err.h>
49 #include <locale.h>
50 #include <stdio.h>
51 #include <stdlib.h>
52 #include <string.h>
53 #include <termcap.h>
54 #include <unistd.h>
55 #include <wchar.h>
56 #include <wctype.h>
57 
58 #define	IESC	'\033'
59 #define	SO	'\016'
60 #define	SI	'\017'
61 #define	HFWD	'9'
62 #define	HREV	'8'
63 #define	FREV	'7'
64 #define	MAXBUF	512
65 
66 #define	NORMAL	000
67 #define	ALTSET	001	/* Reverse */
68 #define	SUPERSC	002	/* Dim */
69 #define	SUBSC	004	/* Dim | Ul */
70 #define	UNDERL	010	/* Ul */
71 #define	BOLD	020	/* Bold */
72 
73 int	must_use_uc, must_overstrike;
74 const char
75 	*CURS_UP, *CURS_RIGHT, *CURS_LEFT,
76 	*ENTER_STANDOUT, *EXIT_STANDOUT, *ENTER_UNDERLINE, *EXIT_UNDERLINE,
77 	*ENTER_DIM, *ENTER_BOLD, *ENTER_REVERSE, *UNDER_CHAR, *EXIT_ATTRIBUTES;
78 
79 struct	CHAR	{
80 	char	c_mode;
81 	wchar_t	c_char;
82 	int	c_width;	/* width or -1 if multi-column char. filler */
83 } ;
84 
85 struct	CHAR	obuf[MAXBUF];
86 int	col, maxcol;
87 int	mode;
88 int	halfpos;
89 int	upln;
90 int	iflag;
91 
92 static void usage(void);
93 void setnewmode(int);
94 void initcap(void);
95 void reverse(void);
96 int outchar(int);
97 void fwd(void);
98 void initbuf(void);
99 void iattr(void);
100 void overstrike(void);
101 void flushln(void);
102 void filter(FILE *);
103 void outc(wint_t, int);
104 
105 #define	PRINT(s)	if (s == NULL) /* void */; else tputs(s, 1, outchar)
106 
107 int
108 main(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 
123 		case 't':
124 		case 'T': /* for nroff compatibility */
125 			termtype = optarg;
126 			break;
127 		case 'i':
128 			iflag = 1;
129 			break;
130 		default:
131 			usage();
132 		}
133 
134 	switch(tgetent(termcap, termtype)) {
135 
136 	case 1:
137 		break;
138 
139 	default:
140 		warnx("trouble reading termcap");
141 		/* FALLTHROUGH */
142 
143 	case 0:
144 		/* No such terminal type - assume dumb */
145 		(void)strcpy(termcap, "dumb:os:col#80:cr=^M:sf=^J:am:");
146 		break;
147 	}
148 	initcap();
149 	if (    (tgetflag("os") && ENTER_BOLD==NULL ) ||
150 		(tgetflag("ul") && ENTER_UNDERLINE==NULL && UNDER_CHAR==NULL))
151 			must_overstrike = 1;
152 	initbuf();
153 	if (optind == argc)
154 		filter(stdin);
155 	else for (; optind<argc; optind++) {
156 		f = fopen(argv[optind],"r");
157 		if (f == NULL)
158 			err(1, "%s", argv[optind]);
159 		else
160 			filter(f);
161 	}
162 	exit(0);
163 }
164 
165 static void
166 usage(void)
167 {
168 	fprintf(stderr, "usage: ul [-i] [-t terminal] [file ...]\n");
169 	exit(1);
170 }
171 
172 void
173 filter(FILE *f)
174 {
175 	wint_t c;
176 	int i, w;
177 
178 	while ((c = getwc(f)) != WEOF && col < MAXBUF) switch(c) {
179 
180 	case '\b':
181 		if (col > 0)
182 			col--;
183 		continue;
184 
185 	case '\t':
186 		col = (col+8) & ~07;
187 		if (col > maxcol)
188 			maxcol = col;
189 		continue;
190 
191 	case '\r':
192 		col = 0;
193 		continue;
194 
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 
306 void
307 flushln(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  */
348 void
349 overstrike(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 
388 void
389 iattr(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 
412 void
413 initbuf(void)
414 {
415 
416 	bzero((char *)obuf, sizeof (obuf));	/* depends on NORMAL == 0 */
417 	col = 0;
418 	maxcol = 0;
419 	mode &= ALTSET;
420 }
421 
422 void
423 fwd(void)
424 {
425 	int oldcol, oldmax;
426 
427 	oldcol = col;
428 	oldmax = maxcol;
429 	flushln();
430 	col = oldcol;
431 	maxcol = oldmax;
432 }
433 
434 void
435 reverse(void)
436 {
437 	upln++;
438 	fwd();
439 	PRINT(CURS_UP);
440 	PRINT(CURS_UP);
441 	upln++;
442 }
443 
444 void
445 initcap(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 
497 int
498 outchar(int c)
499 {
500 	return (putwchar(c) != WEOF ? c : EOF);
501 }
502 
503 static int curmode = 0;
504 
505 void
506 outc(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 
519 void
520 setnewmode(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