xref: /freebsd/usr.bin/ul/ul.c (revision bdcbfde31e8e9b343f113a1956384bdf30d1ed62)
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 #endif /* not lint */
40 
41 #include <err.h>
42 #include <locale.h>
43 #include <stdio.h>
44 #include <stdlib.h>
45 #include <string.h>
46 #include <termcap.h>
47 #include <unistd.h>
48 #include <wchar.h>
49 #include <wctype.h>
50 
51 #define	IESC	'\033'
52 #define	SO	'\016'
53 #define	SI	'\017'
54 #define	HFWD	'9'
55 #define	HREV	'8'
56 #define	FREV	'7'
57 #define	MAXBUF	512
58 
59 #define	NORMAL	000
60 #define	ALTSET	001	/* Reverse */
61 #define	SUPERSC	002	/* Dim */
62 #define	SUBSC	004	/* Dim | Ul */
63 #define	UNDERL	010	/* Ul */
64 #define	BOLD	020	/* Bold */
65 
66 static int	must_use_uc, must_overstrike;
67 static const char
68 	*CURS_UP, *CURS_RIGHT, *CURS_LEFT,
69 	*ENTER_STANDOUT, *EXIT_STANDOUT, *ENTER_UNDERLINE, *EXIT_UNDERLINE,
70 	*ENTER_DIM, *ENTER_BOLD, *ENTER_REVERSE, *UNDER_CHAR, *EXIT_ATTRIBUTES;
71 
72 struct	CHAR	{
73 	char	c_mode;
74 	wchar_t	c_char;
75 	int	c_width;	/* width or -1 if multi-column char. filler */
76 } ;
77 
78 static struct	CHAR	sobuf[MAXBUF]; /* static output buffer */
79 static struct	CHAR	*obuf = sobuf;
80 static int	buflen = MAXBUF;
81 static int	col, maxcol;
82 static int	mode;
83 static int	halfpos;
84 static int	upln;
85 static int	iflag;
86 
87 static void usage(void) __dead2;
88 static void setnewmode(int);
89 static void initcap(void);
90 static void reverse(void);
91 static int outchar(int);
92 static void fwd(void);
93 static void initbuf(void);
94 static void iattr(void);
95 static void overstrike(void);
96 static void flushln(void);
97 static void filter(FILE *);
98 static void outc(wint_t, int);
99 
100 #define	PRINT(s)	if (s == NULL) /* void */; else tputs(s, 1, outchar)
101 
102 int
103 main(int argc, char **argv)
104 {
105 	int c;
106 	const char *termtype;
107 	FILE *f;
108 	char termcap[1024];
109 
110 	setlocale(LC_ALL, "");
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 		default:
125 			usage();
126 		}
127 
128 	switch (tgetent(termcap, termtype)) {
129 	case 1:
130 		break;
131 	default:
132 		warnx("trouble reading termcap");
133 		/* FALLTHROUGH */
134 	case 0:
135 		/* No such terminal type - assume dumb */
136 		(void)strcpy(termcap, "dumb:os:col#80:cr=^M:sf=^J:am:");
137 		break;
138 	}
139 	initcap();
140 	if ((tgetflag("os") && ENTER_BOLD == NULL ) ||
141 	    (tgetflag("ul") && ENTER_UNDERLINE == NULL && UNDER_CHAR == NULL))
142 		must_overstrike = 1;
143 	initbuf();
144 	if (optind == argc)
145 		filter(stdin);
146 	else for (; optind<argc; optind++) {
147 		f = fopen(argv[optind],"r");
148 		if (f == NULL)
149 			err(1, "%s", argv[optind]);
150 		else
151 			filter(f);
152 	}
153 	if (obuf != sobuf) {
154 		free(obuf);
155 	}
156 	exit(0);
157 }
158 
159 static void
160 usage(void)
161 {
162 	fprintf(stderr, "usage: ul [-i] [-t terminal] [file ...]\n");
163 	exit(1);
164 }
165 
166 static void
167 filter(FILE *f)
168 {
169 	wint_t c;
170 	int i, w;
171 	int copy;
172 
173 	copy = 0;
174 
175 	while ((c = getwc(f)) != WEOF) {
176 		if (col == buflen) {
177 			if (obuf == sobuf) {
178 				obuf = NULL;
179 				copy = 1;
180 			}
181 			obuf = realloc(obuf, sizeof(*obuf) * 2 * buflen);
182 			if (obuf == NULL) {
183 				obuf = sobuf;
184 				break;
185 			} else if (copy) {
186 				memcpy(obuf, sobuf, sizeof(*obuf) * buflen);
187 				copy = 0;
188 			}
189 			bzero((char *)(obuf + buflen), sizeof(*obuf) * buflen);
190 			buflen *= 2;
191 		}
192 		switch(c) {
193 		case '\b':
194 			if (col > 0)
195 				col--;
196 			continue;
197 
198 		case '\t':
199 			col = (col+8) & ~07;
200 			if (col > maxcol)
201 				maxcol = col;
202 			continue;
203 
204 		case '\r':
205 			col = 0;
206 			continue;
207 
208 		case SO:
209 			mode |= ALTSET;
210 			continue;
211 
212 		case SI:
213 			mode &= ~ALTSET;
214 			continue;
215 
216 		case IESC:
217 			switch (c = getwc(f)) {
218 
219 			case HREV:
220 				if (halfpos == 0) {
221 					mode |= SUPERSC;
222 					halfpos--;
223 				} else if (halfpos > 0) {
224 					mode &= ~SUBSC;
225 					halfpos--;
226 				} else {
227 					halfpos = 0;
228 					reverse();
229 				}
230 				continue;
231 
232 			case HFWD:
233 				if (halfpos == 0) {
234 					mode |= SUBSC;
235 					halfpos++;
236 				} else if (halfpos < 0) {
237 					mode &= ~SUPERSC;
238 					halfpos++;
239 				} else {
240 					halfpos = 0;
241 					fwd();
242 				}
243 				continue;
244 
245 			case FREV:
246 				reverse();
247 				continue;
248 
249 			default:
250 				errx(1, "unknown escape sequence in input: %o, %o", IESC, c);
251 			}
252 			continue;
253 
254 		case '_':
255 			if (obuf[col].c_char || obuf[col].c_width < 0) {
256 				while (col > 0 && obuf[col].c_width < 0)
257 					col--;
258 				w = obuf[col].c_width;
259 				for (i = 0; i < w; i++)
260 					obuf[col++].c_mode |= UNDERL | mode;
261 				if (col > maxcol)
262 					maxcol = col;
263 				continue;
264 			}
265 			obuf[col].c_char = '_';
266 			obuf[col].c_width = 1;
267 			/* FALLTHROUGH */
268 		case ' ':
269 			col++;
270 			if (col > maxcol)
271 				maxcol = col;
272 			continue;
273 
274 		case '\n':
275 			flushln();
276 			continue;
277 
278 		case '\f':
279 			flushln();
280 			putwchar('\f');
281 			continue;
282 
283 		default:
284 			if ((w = wcwidth(c)) <= 0)	/* non printing */
285 				continue;
286 			if (obuf[col].c_char == '\0') {
287 				obuf[col].c_char = c;
288 				for (i = 0; i < w; i++)
289 					obuf[col + i].c_mode = mode;
290 				obuf[col].c_width = w;
291 				for (i = 1; i < w; i++)
292 					obuf[col + i].c_width = -1;
293 			} else if (obuf[col].c_char == '_') {
294 				obuf[col].c_char = c;
295 				for (i = 0; i < w; i++)
296 					obuf[col + i].c_mode |= UNDERL|mode;
297 				obuf[col].c_width = w;
298 				for (i = 1; i < w; i++)
299 					obuf[col + i].c_width = -1;
300 			} else if ((wint_t)obuf[col].c_char == c) {
301 				for (i = 0; i < w; i++)
302 					obuf[col + i].c_mode |= BOLD|mode;
303 			} else {
304 				w = obuf[col].c_width;
305 				for (i = 0; i < w; i++)
306 					obuf[col + i].c_mode = mode;
307 			}
308 			col += w;
309 			if (col > maxcol)
310 				maxcol = col;
311 			continue;
312 		}
313 	}
314 	if (ferror(f))
315 		err(1, NULL);
316 	if (maxcol)
317 		flushln();
318 }
319 
320 static void
321 flushln(void)
322 {
323 	int lastmode;
324 	int i;
325 	int hadmodes = 0;
326 
327 	lastmode = NORMAL;
328 	for (i = 0; i < maxcol; i++) {
329 		if (obuf[i].c_mode != lastmode) {
330 			hadmodes++;
331 			setnewmode(obuf[i].c_mode);
332 			lastmode = obuf[i].c_mode;
333 		}
334 		if (obuf[i].c_char == '\0') {
335 			if (upln)
336 				PRINT(CURS_RIGHT);
337 			else
338 				outc(' ', 1);
339 		} else
340 			outc(obuf[i].c_char, obuf[i].c_width);
341 		if (obuf[i].c_width > 1)
342 			i += obuf[i].c_width - 1;
343 	}
344 	if (lastmode != NORMAL) {
345 		setnewmode(0);
346 	}
347 	if (must_overstrike && hadmodes)
348 		overstrike();
349 	putwchar('\n');
350 	if (iflag && hadmodes)
351 		iattr();
352 	(void)fflush(stdout);
353 	if (upln)
354 		upln--;
355 	initbuf();
356 }
357 
358 /*
359  * For terminals that can overstrike, overstrike underlines and bolds.
360  * We don't do anything with halfline ups and downs, or Greek.
361  */
362 static void
363 overstrike(void)
364 {
365 	int i;
366 	wchar_t lbuf[256];
367 	wchar_t *cp = lbuf;
368 	int hadbold=0;
369 
370 	/* Set up overstrike buffer */
371 	for (i=0; i<maxcol; i++)
372 		switch (obuf[i].c_mode) {
373 		case NORMAL:
374 		default:
375 			*cp++ = ' ';
376 			break;
377 		case UNDERL:
378 			*cp++ = '_';
379 			break;
380 		case BOLD:
381 			*cp++ = obuf[i].c_char;
382 			if (obuf[i].c_width > 1)
383 				i += obuf[i].c_width - 1;
384 			hadbold=1;
385 			break;
386 		}
387 	putwchar('\r');
388 	for (*cp=' '; *cp==' '; cp--)
389 		*cp = 0;
390 	for (cp=lbuf; *cp; cp++)
391 		putwchar(*cp);
392 	if (hadbold) {
393 		putwchar('\r');
394 		for (cp=lbuf; *cp; cp++)
395 			putwchar(*cp=='_' ? ' ' : *cp);
396 		putwchar('\r');
397 		for (cp=lbuf; *cp; cp++)
398 			putwchar(*cp=='_' ? ' ' : *cp);
399 	}
400 }
401 
402 static void
403 iattr(void)
404 {
405 	int i;
406 	wchar_t lbuf[256];
407 	wchar_t *cp = lbuf;
408 
409 	for (i=0; i<maxcol; i++)
410 		switch (obuf[i].c_mode) {
411 		case NORMAL:	*cp++ = ' '; break;
412 		case ALTSET:	*cp++ = 'g'; break;
413 		case SUPERSC:	*cp++ = '^'; break;
414 		case SUBSC:	*cp++ = 'v'; break;
415 		case UNDERL:	*cp++ = '_'; break;
416 		case BOLD:	*cp++ = '!'; break;
417 		default:	*cp++ = 'X'; break;
418 		}
419 	for (*cp=' '; *cp==' '; cp--)
420 		*cp = 0;
421 	for (cp=lbuf; *cp; cp++)
422 		putwchar(*cp);
423 	putwchar('\n');
424 }
425 
426 static void
427 initbuf(void)
428 {
429 
430 	bzero((char *)obuf, buflen * sizeof(*obuf)); /* depends on NORMAL == 0 */
431 	col = 0;
432 	maxcol = 0;
433 	mode &= ALTSET;
434 }
435 
436 static 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 static void
449 reverse(void)
450 {
451 	upln++;
452 	fwd();
453 	PRINT(CURS_UP);
454 	PRINT(CURS_UP);
455 	upln++;
456 }
457 
458 static 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 static int
512 outchar(int c)
513 {
514 	return (putwchar(c) != WEOF ? c : EOF);
515 }
516 
517 static int curmode = 0;
518 
519 static void
520 outc(wint_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 static void
534 setnewmode(int newmode)
535 {
536 	if (!iflag) {
537 		if (curmode != NORMAL && newmode != NORMAL)
538 			setnewmode(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