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