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