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