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