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