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