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