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