xref: /freebsd/usr.bin/ul/ul.c (revision 93b0017f88462f9af15368440324aa3be58eb18f)
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(argc, argv)
105 	int argc;
106 	char **argv;
107 {
108 	int c;
109 	const char *termtype;
110 	FILE *f;
111 	char termcap[1024];
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 
161 static void
162 usage()
163 {
164 	fprintf(stderr, "usage: ul [-i] [-t terminal] file...\n");
165 	exit(1);
166 }
167 
168 void
169 filter(f)
170 	FILE *f;
171 {
172 	int c;
173 
174 	while ((c = getc(f)) != EOF && 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 = getc(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)
239 			obuf[col].c_mode |= UNDERL | mode;
240 		else
241 			obuf[col].c_char = '_';
242 	case ' ':
243 		col++;
244 		if (col > maxcol)
245 			maxcol = col;
246 		continue;
247 
248 	case '\n':
249 		flushln();
250 		continue;
251 
252 	case '\f':
253 		flushln();
254 		putchar('\f');
255 		continue;
256 
257 	default:
258 		if (c < ' ')	/* non printing */
259 			continue;
260 		if (obuf[col].c_char == '\0') {
261 			obuf[col].c_char = c;
262 			obuf[col].c_mode = mode;
263 		} else if (obuf[col].c_char == '_') {
264 			obuf[col].c_char = c;
265 			obuf[col].c_mode |= UNDERL|mode;
266 		} else if (obuf[col].c_char == c)
267 			obuf[col].c_mode |= BOLD|mode;
268 		else
269 			obuf[col].c_mode = mode;
270 		col++;
271 		if (col > maxcol)
272 			maxcol = col;
273 		continue;
274 	}
275 	if (maxcol)
276 		flushln();
277 }
278 
279 void
280 flushln()
281 {
282 	int lastmode;
283 	int i;
284 	int hadmodes = 0;
285 
286 	lastmode = NORMAL;
287 	for (i=0; i<maxcol; i++) {
288 		if (obuf[i].c_mode != lastmode) {
289 			hadmodes++;
290 			setnewmode(obuf[i].c_mode);
291 			lastmode = obuf[i].c_mode;
292 		}
293 		if (obuf[i].c_char == '\0') {
294 			if (upln)
295 				PRINT(CURS_RIGHT);
296 			else
297 				outc(' ');
298 		} else
299 			outc(obuf[i].c_char);
300 	}
301 	if (lastmode != NORMAL) {
302 		setnewmode(0);
303 	}
304 	if (must_overstrike && hadmodes)
305 		overstrike();
306 	putchar('\n');
307 	if (iflag && hadmodes)
308 		iattr();
309 	(void)fflush(stdout);
310 	if (upln)
311 		upln--;
312 	initbuf();
313 }
314 
315 /*
316  * For terminals that can overstrike, overstrike underlines and bolds.
317  * We don't do anything with halfline ups and downs, or Greek.
318  */
319 void
320 overstrike()
321 {
322 	register int i;
323 	char lbuf[256];
324 	register char *cp = lbuf;
325 	int hadbold=0;
326 
327 	/* Set up overstrike buffer */
328 	for (i=0; i<maxcol; i++)
329 		switch (obuf[i].c_mode) {
330 		case NORMAL:
331 		default:
332 			*cp++ = ' ';
333 			break;
334 		case UNDERL:
335 			*cp++ = '_';
336 			break;
337 		case BOLD:
338 			*cp++ = obuf[i].c_char;
339 			hadbold=1;
340 			break;
341 		}
342 	putchar('\r');
343 	for (*cp=' '; *cp==' '; cp--)
344 		*cp = 0;
345 	for (cp=lbuf; *cp; cp++)
346 		putchar(*cp);
347 	if (hadbold) {
348 		putchar('\r');
349 		for (cp=lbuf; *cp; cp++)
350 			putchar(*cp=='_' ? ' ' : *cp);
351 		putchar('\r');
352 		for (cp=lbuf; *cp; cp++)
353 			putchar(*cp=='_' ? ' ' : *cp);
354 	}
355 }
356 
357 void
358 iattr()
359 {
360 	register int i;
361 	char lbuf[256];
362 	register char *cp = lbuf;
363 
364 	for (i=0; i<maxcol; i++)
365 		switch (obuf[i].c_mode) {
366 		case NORMAL:	*cp++ = ' '; break;
367 		case ALTSET:	*cp++ = 'g'; break;
368 		case SUPERSC:	*cp++ = '^'; break;
369 		case SUBSC:	*cp++ = 'v'; break;
370 		case UNDERL:	*cp++ = '_'; break;
371 		case BOLD:	*cp++ = '!'; break;
372 		default:	*cp++ = 'X'; break;
373 		}
374 	for (*cp=' '; *cp==' '; cp--)
375 		*cp = 0;
376 	for (cp=lbuf; *cp; cp++)
377 		putchar(*cp);
378 	putchar('\n');
379 }
380 
381 void
382 initbuf()
383 {
384 
385 	bzero((char *)obuf, sizeof (obuf));	/* depends on NORMAL == 0 */
386 	col = 0;
387 	maxcol = 0;
388 	mode &= ALTSET;
389 }
390 
391 void
392 fwd()
393 {
394 	int oldcol, oldmax;
395 
396 	oldcol = col;
397 	oldmax = maxcol;
398 	flushln();
399 	col = oldcol;
400 	maxcol = oldmax;
401 }
402 
403 void
404 reverse()
405 {
406 	upln++;
407 	fwd();
408 	PRINT(CURS_UP);
409 	PRINT(CURS_UP);
410 	upln++;
411 }
412 
413 void
414 initcap()
415 {
416 	static char tcapbuf[512];
417 	char *bp = tcapbuf;
418 
419 	/* This nonsense attempts to work with both old and new termcap */
420 	CURS_UP =		tgetstr("up", &bp);
421 	CURS_RIGHT =		tgetstr("ri", &bp);
422 	if (CURS_RIGHT == NULL)
423 		CURS_RIGHT =	tgetstr("nd", &bp);
424 	CURS_LEFT =		tgetstr("le", &bp);
425 	if (CURS_LEFT == NULL)
426 		CURS_LEFT =	tgetstr("bc", &bp);
427 	if (CURS_LEFT == NULL && tgetflag("bs"))
428 		CURS_LEFT =	"\b";
429 
430 	ENTER_STANDOUT =	tgetstr("so", &bp);
431 	EXIT_STANDOUT =		tgetstr("se", &bp);
432 	ENTER_UNDERLINE =	tgetstr("us", &bp);
433 	EXIT_UNDERLINE =	tgetstr("ue", &bp);
434 	ENTER_DIM =		tgetstr("mh", &bp);
435 	ENTER_BOLD =		tgetstr("md", &bp);
436 	ENTER_REVERSE =		tgetstr("mr", &bp);
437 	EXIT_ATTRIBUTES =	tgetstr("me", &bp);
438 
439 	if (!ENTER_BOLD && ENTER_REVERSE)
440 		ENTER_BOLD = ENTER_REVERSE;
441 	if (!ENTER_BOLD && ENTER_STANDOUT)
442 		ENTER_BOLD = ENTER_STANDOUT;
443 	if (!ENTER_UNDERLINE && ENTER_STANDOUT) {
444 		ENTER_UNDERLINE = ENTER_STANDOUT;
445 		EXIT_UNDERLINE = EXIT_STANDOUT;
446 	}
447 	if (!ENTER_DIM && ENTER_STANDOUT)
448 		ENTER_DIM = ENTER_STANDOUT;
449 	if (!ENTER_REVERSE && ENTER_STANDOUT)
450 		ENTER_REVERSE = ENTER_STANDOUT;
451 	if (!EXIT_ATTRIBUTES && EXIT_STANDOUT)
452 		EXIT_ATTRIBUTES = EXIT_STANDOUT;
453 
454 	/*
455 	 * Note that we use REVERSE for the alternate character set,
456 	 * not the as/ae capabilities.  This is because we are modelling
457 	 * the model 37 teletype (since that's what nroff outputs) and
458 	 * the typical as/ae is more of a graphics set, not the greek
459 	 * letters the 37 has.
460 	 */
461 
462 	UNDER_CHAR =		tgetstr("uc", &bp);
463 	must_use_uc = (UNDER_CHAR && !ENTER_UNDERLINE);
464 }
465 
466 int
467 outchar(c)
468 	int c;
469 {
470 	return(putchar(c & 0177));
471 }
472 
473 static int curmode = 0;
474 
475 void
476 outc(c)
477 	int c;
478 {
479 	putchar(c);
480 	if (must_use_uc && (curmode&UNDERL)) {
481 		PRINT(CURS_LEFT);
482 		PRINT(UNDER_CHAR);
483 	}
484 }
485 
486 void
487 setnewmode(newmode)
488 	int newmode;
489 {
490 	if (!iflag) {
491 		if (curmode != NORMAL && newmode != NORMAL)
492 			setnewmode(NORMAL);
493 		switch (newmode) {
494 		case NORMAL:
495 			switch(curmode) {
496 			case NORMAL:
497 				break;
498 			case UNDERL:
499 				PRINT(EXIT_UNDERLINE);
500 				break;
501 			default:
502 				/* This includes standout */
503 				PRINT(EXIT_ATTRIBUTES);
504 				break;
505 			}
506 			break;
507 		case ALTSET:
508 			PRINT(ENTER_REVERSE);
509 			break;
510 		case SUPERSC:
511 			/*
512 			 * This only works on a few terminals.
513 			 * It should be fixed.
514 			 */
515 			PRINT(ENTER_UNDERLINE);
516 			PRINT(ENTER_DIM);
517 			break;
518 		case SUBSC:
519 			PRINT(ENTER_DIM);
520 			break;
521 		case UNDERL:
522 			PRINT(ENTER_UNDERLINE);
523 			break;
524 		case BOLD:
525 			PRINT(ENTER_BOLD);
526 			break;
527 		default:
528 			/*
529 			 * We should have some provision here for multiple modes
530 			 * on at once.  This will have to come later.
531 			 */
532 			PRINT(ENTER_STANDOUT);
533 			break;
534 		}
535 	}
536 	curmode = newmode;
537 }
538