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