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 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 151 usage(void) 152 { 153 fprintf(stderr, "usage: ul [-i] [-t terminal] [file ...]\n"); 154 exit(1); 155 } 156 157 static void 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 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 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 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 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 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 440 reverse(void) 441 { 442 upln++; 443 fwd(); 444 PRINT(CURS_UP); 445 PRINT(CURS_UP); 446 upln++; 447 } 448 449 static 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 503 outchar(int c) 504 { 505 return (putwchar(c) != WEOF ? c : EOF); 506 } 507 508 static int curmode = 0; 509 510 static void 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 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