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