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