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