xref: /openbsd-src/usr.bin/ul/ul.c (revision b2ea75c1b17e1a9a339660e7ed45cd24946b230e)
1 /*	$OpenBSD: ul.c,v 1.6 2001/07/12 05:17:27 deraadt Exp $	*/
2 /*	$NetBSD: ul.c,v 1.3 1994/12/07 00:28:24 jtc Exp $	*/
3 
4 /*
5  * Copyright (c) 1980, 1993
6  *	The Regents of the University of California.  All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. All advertising materials mentioning features or use of this software
17  *    must display the following acknowledgement:
18  *	This product includes software developed by the University of
19  *	California, Berkeley and its contributors.
20  * 4. Neither the name of the University nor the names of its contributors
21  *    may be used to endorse or promote products derived from this software
22  *    without specific prior written permission.
23  *
24  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34  * SUCH DAMAGE.
35  */
36 
37 #ifndef lint
38 static char copyright[] =
39 "@(#) Copyright (c) 1980, 1993\n\
40 	The Regents of the University of California.  All rights reserved.\n";
41 #endif /* not lint */
42 
43 #ifndef lint
44 #if 0
45 static char sccsid[] = "@(#)ul.c	8.1 (Berkeley) 6/6/93";
46 #endif
47 static char rcsid[] = "$OpenBSD: ul.c,v 1.6 2001/07/12 05:17:27 deraadt Exp $";
48 #endif /* not lint */
49 
50 #include <stdio.h>
51 #include <stdlib.h>
52 #include <string.h>
53 #include <unistd.h>
54 #include <stdlib.h>
55 #include <curses.h>
56 #include <term.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 char	*CURS_UP, *CURS_RIGHT, *CURS_LEFT,
75 	*ENTER_STANDOUT, *EXIT_STANDOUT, *ENTER_UNDERLINE, *EXIT_UNDERLINE,
76 	*ENTER_DIM, *ENTER_BOLD, *ENTER_REVERSE, *UNDER_CHAR, *EXIT_ATTRIBUTES;
77 
78 struct	CHAR	{
79 	char	c_mode;
80 	char	c_char;
81 } ;
82 
83 struct	CHAR	obuf[MAXBUF];
84 int	col, maxcol;
85 int	mode;
86 int	halfpos;
87 int	upln;
88 int	iflag;
89 
90 int	outchar(int);
91 void	initcap(void);
92 void	initbuf(void);
93 void	mfilter(FILE *);
94 void	reverse(void);
95 void	fwd(void);
96 void	flushln(void);
97 void	msetmode(int);
98 void	outc(int);
99 void	overstrike(void);
100 void	iattr(void);
101 
102 #define	PRINT(s) \
103 	do { \
104 		if (s) \
105 			tputs(s, 1, outchar); \
106 	} while (0)
107 
108 int
109 main(argc, argv)
110 	int argc;
111 	char **argv;
112 {
113 	extern int optind;
114 	extern char *optarg;
115 	int c;
116 	char *termtype;
117 	FILE *f;
118 	char termcap[1024];
119 
120 	termtype = getenv("TERM");
121 	if (termtype == NULL || (argv[0][0] == 'c' && !isatty(1)))
122 		termtype = "lpr";
123 	while ((c=getopt(argc, argv, "it:T:")) != -1)
124 		switch(c) {
125 
126 		case 't':
127 		case 'T': /* for nroff compatibility */
128 				termtype = optarg;
129 			break;
130 		case 'i':
131 			iflag = 1;
132 			break;
133 
134 		default:
135 			fprintf(stderr,
136 				"usage: %s [ -i ] [ -tTerm ] file...\n",
137 				argv[0]);
138 			exit(1);
139 		}
140 
141 	switch(tgetent(termcap, termtype)) {
142 
143 	case 1:
144 		break;
145 
146 	default:
147 		fprintf(stderr,"trouble reading termcap");
148 		/* fall through to ... */
149 
150 	case 0:
151 		/* No such terminal type - assume dumb */
152 		(void)strcpy(termcap, "dumb:os:col#80:cr=^M:sf=^J:am:");
153 		break;
154 	}
155 	initcap();
156 	if (    (tgetflag("os") && ENTER_BOLD==NULL ) ||
157 		(tgetflag("ul") && ENTER_UNDERLINE==NULL && UNDER_CHAR==NULL))
158 			must_overstrike = 1;
159 	initbuf();
160 	if (optind == argc)
161 		mfilter(stdin);
162 	else for (; optind<argc; optind++) {
163 		f = fopen(argv[optind],"r");
164 		if (f == NULL) {
165 			perror(argv[optind]);
166 			exit(1);
167 		}
168 
169 		mfilter(f);
170 		fclose(f);
171 	}
172 	exit(0);
173 }
174 
175 void
176 mfilter(f)
177 	FILE *f;
178 {
179 	int c;
180 
181 	while ((c = getc(f)) != EOF && col < MAXBUF) switch(c) {
182 
183 	case '\b':
184 		if (col > 0)
185 			col--;
186 		continue;
187 
188 	case '\t':
189 		col = (col+8) & ~07;
190 		if (col > maxcol)
191 			maxcol = col;
192 		continue;
193 
194 	case '\r':
195 		col = 0;
196 		continue;
197 
198 	case SO:
199 		mode |= ALTSET;
200 		continue;
201 
202 	case SI:
203 		mode &= ~ALTSET;
204 		continue;
205 
206 	case IESC:
207 		switch (c = getc(f)) {
208 
209 		case HREV:
210 			if (halfpos == 0) {
211 				mode |= SUPERSC;
212 				halfpos--;
213 			} else if (halfpos > 0) {
214 				mode &= ~SUBSC;
215 				halfpos--;
216 			} else {
217 				halfpos = 0;
218 				reverse();
219 			}
220 			continue;
221 
222 		case HFWD:
223 			if (halfpos == 0) {
224 				mode |= SUBSC;
225 				halfpos++;
226 			} else if (halfpos < 0) {
227 				mode &= ~SUPERSC;
228 				halfpos++;
229 			} else {
230 				halfpos = 0;
231 				fwd();
232 			}
233 			continue;
234 
235 		case FREV:
236 			reverse();
237 			continue;
238 
239 		default:
240 			fprintf(stderr,
241 				"Unknown escape sequence in input: %o, %o\n",
242 				IESC, c);
243 			exit(1);
244 		}
245 		continue;
246 
247 	case '_':
248 		if (obuf[col].c_char)
249 			obuf[col].c_mode |= UNDERL | mode;
250 		else
251 			obuf[col].c_char = '_';
252 	case ' ':
253 		col++;
254 		if (col > maxcol)
255 			maxcol = col;
256 		continue;
257 
258 	case '\n':
259 		flushln();
260 		continue;
261 
262 	case '\f':
263 		flushln();
264 		putchar('\f');
265 		continue;
266 
267 	default:
268 		if (c < ' ')	/* non printing */
269 			continue;
270 		if (obuf[col].c_char == '\0') {
271 			obuf[col].c_char = c;
272 			obuf[col].c_mode = mode;
273 		} else if (obuf[col].c_char == '_') {
274 			obuf[col].c_char = c;
275 			obuf[col].c_mode |= UNDERL|mode;
276 		} else if (obuf[col].c_char == c)
277 			obuf[col].c_mode |= BOLD|mode;
278 		else
279 			obuf[col].c_mode = mode;
280 		col++;
281 		if (col > maxcol)
282 			maxcol = col;
283 		continue;
284 	}
285 	if (maxcol)
286 		flushln();
287 }
288 
289 void
290 flushln()
291 {
292 	int lastmode, i;
293 	int hadmodes = 0;
294 
295 	lastmode = NORMAL;
296 	for (i=0; i<maxcol; i++) {
297 		if (obuf[i].c_mode != lastmode) {
298 			hadmodes++;
299 			msetmode(obuf[i].c_mode);
300 			lastmode = obuf[i].c_mode;
301 		}
302 		if (obuf[i].c_char == '\0') {
303 			if (upln)
304 				PRINT(CURS_RIGHT);
305 			else
306 				outc(' ');
307 		} else
308 			outc(obuf[i].c_char);
309 	}
310 	if (lastmode != NORMAL) {
311 		msetmode(0);
312 	}
313 	if (must_overstrike && hadmodes)
314 		overstrike();
315 	putchar('\n');
316 	if (iflag && hadmodes)
317 		iattr();
318 	(void)fflush(stdout);
319 	if (upln)
320 		upln--;
321 	initbuf();
322 }
323 
324 /*
325  * For terminals that can overstrike, overstrike underlines and bolds.
326  * We don't do anything with halfline ups and downs, or Greek.
327  */
328 void
329 overstrike()
330 {
331 	register int i;
332 	char lbuf[256];
333 	register char *cp = lbuf;
334 	int hadbold=0;
335 
336 	/* Set up overstrike buffer */
337 	for (i=0; i<maxcol; i++)
338 		switch (obuf[i].c_mode) {
339 		case NORMAL:
340 		default:
341 			*cp++ = ' ';
342 			break;
343 		case UNDERL:
344 			*cp++ = '_';
345 			break;
346 		case BOLD:
347 			*cp++ = obuf[i].c_char;
348 			hadbold=1;
349 			break;
350 		}
351 	putchar('\r');
352 	for (*cp=' '; *cp==' '; cp--)
353 		*cp = 0;
354 	for (cp=lbuf; *cp; cp++)
355 		putchar(*cp);
356 	if (hadbold) {
357 		putchar('\r');
358 		for (cp=lbuf; *cp; cp++)
359 			putchar(*cp=='_' ? ' ' : *cp);
360 		putchar('\r');
361 		for (cp=lbuf; *cp; cp++)
362 			putchar(*cp=='_' ? ' ' : *cp);
363 	}
364 }
365 
366 void
367 iattr()
368 {
369 	register int i;
370 	char lbuf[256];
371 	register char *cp = lbuf;
372 
373 	for (i=0; i<maxcol; i++)
374 		switch (obuf[i].c_mode) {
375 		case NORMAL:	*cp++ = ' '; break;
376 		case ALTSET:	*cp++ = 'g'; break;
377 		case SUPERSC:	*cp++ = '^'; break;
378 		case SUBSC:	*cp++ = 'v'; break;
379 		case UNDERL:	*cp++ = '_'; break;
380 		case BOLD:	*cp++ = '!'; break;
381 		default:	*cp++ = 'X'; break;
382 		}
383 	for (*cp=' '; *cp==' '; cp--)
384 		*cp = 0;
385 	for (cp=lbuf; *cp; cp++)
386 		putchar(*cp);
387 	putchar('\n');
388 }
389 
390 void
391 initbuf()
392 {
393 
394 	bzero((char *)obuf, sizeof (obuf));	/* depends on NORMAL == 0 */
395 	col = 0;
396 	maxcol = 0;
397 	mode &= ALTSET;
398 }
399 
400 void
401 fwd()
402 {
403 	int oldcol, oldmax;
404 
405 	oldcol = col;
406 	oldmax = maxcol;
407 	flushln();
408 	col = oldcol;
409 	maxcol = oldmax;
410 }
411 
412 void
413 reverse()
414 {
415 	upln++;
416 	fwd();
417 	PRINT(CURS_UP);
418 	PRINT(CURS_UP);
419 	upln++;
420 }
421 
422 void
423 initcap()
424 {
425 	static char tcapbuf[512];
426 	char *bp = tcapbuf;
427 	char *getenv(), *tgetstr();
428 
429 	/* This nonsense attempts to work with both old and new termcap */
430 	CURS_UP =		tgetstr("up", &bp);
431 	CURS_RIGHT =		tgetstr("ri", &bp);
432 	if (CURS_RIGHT == NULL)
433 		CURS_RIGHT =	tgetstr("nd", &bp);
434 	CURS_LEFT =		tgetstr("le", &bp);
435 	if (CURS_LEFT == NULL)
436 		CURS_LEFT =	tgetstr("bc", &bp);
437 	if (CURS_LEFT == NULL && tgetflag("bs"))
438 		CURS_LEFT =	"\b";
439 
440 	ENTER_STANDOUT =	tgetstr("so", &bp);
441 	EXIT_STANDOUT =		tgetstr("se", &bp);
442 	ENTER_UNDERLINE =	tgetstr("us", &bp);
443 	EXIT_UNDERLINE =	tgetstr("ue", &bp);
444 	ENTER_DIM =		tgetstr("mh", &bp);
445 	ENTER_BOLD =		tgetstr("md", &bp);
446 	ENTER_REVERSE =		tgetstr("mr", &bp);
447 	EXIT_ATTRIBUTES =	tgetstr("me", &bp);
448 
449 	if (!ENTER_BOLD && ENTER_REVERSE)
450 		ENTER_BOLD = ENTER_REVERSE;
451 	if (!ENTER_BOLD && ENTER_STANDOUT)
452 		ENTER_BOLD = ENTER_STANDOUT;
453 	if (!ENTER_UNDERLINE && ENTER_STANDOUT) {
454 		ENTER_UNDERLINE = ENTER_STANDOUT;
455 		EXIT_UNDERLINE = EXIT_STANDOUT;
456 	}
457 	if (!ENTER_DIM && ENTER_STANDOUT)
458 		ENTER_DIM = ENTER_STANDOUT;
459 	if (!ENTER_REVERSE && ENTER_STANDOUT)
460 		ENTER_REVERSE = ENTER_STANDOUT;
461 	if (!EXIT_ATTRIBUTES && EXIT_STANDOUT)
462 		EXIT_ATTRIBUTES = EXIT_STANDOUT;
463 
464 	/*
465 	 * Note that we use REVERSE for the alternate character set,
466 	 * not the as/ae capabilities.  This is because we are modelling
467 	 * the model 37 teletype (since that's what nroff outputs) and
468 	 * the typical as/ae is more of a graphics set, not the greek
469 	 * letters the 37 has.
470 	 */
471 
472 	UNDER_CHAR =		tgetstr("uc", &bp);
473 	must_use_uc = (UNDER_CHAR && !ENTER_UNDERLINE);
474 }
475 
476 int
477 outchar(c)
478 	int c;
479 {
480 	putchar(c & 0177);
481 	return (0);
482 }
483 
484 static int curmode = 0;
485 
486 void
487 outc(c)
488 	int c;
489 {
490 	putchar(c);
491 	if (must_use_uc && (curmode&UNDERL)) {
492 		PRINT(CURS_LEFT);
493 		PRINT(UNDER_CHAR);
494 	}
495 }
496 
497 void
498 msetmode(newmode)
499 	int newmode;
500 {
501 	if (!iflag) {
502 		if (curmode != NORMAL && newmode != NORMAL)
503 			msetmode(NORMAL);
504 		switch (newmode) {
505 		case NORMAL:
506 			switch(curmode) {
507 			case NORMAL:
508 				break;
509 			case UNDERL:
510 				PRINT(EXIT_UNDERLINE);
511 				break;
512 			default:
513 				/* This includes standout */
514 				PRINT(EXIT_ATTRIBUTES);
515 				break;
516 			}
517 			break;
518 		case ALTSET:
519 			PRINT(ENTER_REVERSE);
520 			break;
521 		case SUPERSC:
522 			/*
523 			 * This only works on a few terminals.
524 			 * It should be fixed.
525 			 */
526 			PRINT(ENTER_UNDERLINE);
527 			PRINT(ENTER_DIM);
528 			break;
529 		case SUBSC:
530 			PRINT(ENTER_DIM);
531 			break;
532 		case UNDERL:
533 			PRINT(ENTER_UNDERLINE);
534 			break;
535 		case BOLD:
536 			PRINT(ENTER_BOLD);
537 			break;
538 		default:
539 			/*
540 			 * We should have some provision here for multiple modes
541 			 * on at once.  This will have to come later.
542 			 */
543 			PRINT(ENTER_STANDOUT);
544 			break;
545 		}
546 	}
547 	curmode = newmode;
548 }
549