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