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