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