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