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