1 /* 2 * Copyright (c) 1980, 1993 3 * The Regents of the University of California. All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 4. Neither the name of the University nor the names of its contributors 14 * may be used to endorse or promote products derived from this software 15 * without specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 * SUCH DAMAGE. 28 */ 29 30 #ifndef lint 31 static const char copyright[] = 32 "@(#) Copyright (c) 1980, 1993\n\ 33 The Regents of the University of California. All rights reserved.\n"; 34 #endif /* not lint */ 35 36 #ifndef lint 37 #if 0 38 static char sccsid[] = "@(#)ul.c 8.1 (Berkeley) 6/6/93"; 39 #endif 40 static const char rcsid[] = 41 "$FreeBSD$"; 42 #endif /* not lint */ 43 44 #include <err.h> 45 #include <locale.h> 46 #include <stdio.h> 47 #include <stdlib.h> 48 #include <string.h> 49 #include <termcap.h> 50 #include <unistd.h> 51 #include <wchar.h> 52 #include <wctype.h> 53 54 #define IESC '\033' 55 #define SO '\016' 56 #define SI '\017' 57 #define HFWD '9' 58 #define HREV '8' 59 #define FREV '7' 60 #define MAXBUF 512 61 62 #define NORMAL 000 63 #define ALTSET 001 /* Reverse */ 64 #define SUPERSC 002 /* Dim */ 65 #define SUBSC 004 /* Dim | Ul */ 66 #define UNDERL 010 /* Ul */ 67 #define BOLD 020 /* Bold */ 68 69 static int must_use_uc, must_overstrike; 70 static const char 71 *CURS_UP, *CURS_RIGHT, *CURS_LEFT, 72 *ENTER_STANDOUT, *EXIT_STANDOUT, *ENTER_UNDERLINE, *EXIT_UNDERLINE, 73 *ENTER_DIM, *ENTER_BOLD, *ENTER_REVERSE, *UNDER_CHAR, *EXIT_ATTRIBUTES; 74 75 struct CHAR { 76 char c_mode; 77 wchar_t c_char; 78 int c_width; /* width or -1 if multi-column char. filler */ 79 } ; 80 81 static struct CHAR obuf[MAXBUF]; 82 static int col, maxcol; 83 static int mode; 84 static int halfpos; 85 static int upln; 86 static int iflag; 87 88 static void usage(void); 89 static void setnewmode(int); 90 static void initcap(void); 91 static void reverse(void); 92 static int outchar(int); 93 static void fwd(void); 94 static void initbuf(void); 95 static void iattr(void); 96 static void overstrike(void); 97 static void flushln(void); 98 static void filter(FILE *); 99 static void outc(wint_t, int); 100 101 #define PRINT(s) if (s == NULL) /* void */; else tputs(s, 1, outchar) 102 103 int 104 main(int argc, char **argv) 105 { 106 int c; 107 const char *termtype; 108 FILE *f; 109 char termcap[1024]; 110 111 setlocale(LC_ALL, ""); 112 113 termtype = getenv("TERM"); 114 if (termtype == NULL || (argv[0][0] == 'c' && !isatty(1))) 115 termtype = "lpr"; 116 while ((c=getopt(argc, argv, "it:T:")) != -1) 117 switch(c) { 118 119 case 't': 120 case 'T': /* for nroff compatibility */ 121 termtype = optarg; 122 break; 123 case 'i': 124 iflag = 1; 125 break; 126 default: 127 usage(); 128 } 129 130 switch(tgetent(termcap, termtype)) { 131 132 case 1: 133 break; 134 135 default: 136 warnx("trouble reading termcap"); 137 /* FALLTHROUGH */ 138 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 exit(0); 159 } 160 161 static void 162 usage(void) 163 { 164 fprintf(stderr, "usage: ul [-i] [-t terminal] [file ...]\n"); 165 exit(1); 166 } 167 168 static void 169 filter(FILE *f) 170 { 171 wint_t c; 172 int i, w; 173 174 while ((c = getwc(f)) != WEOF && col < MAXBUF) switch(c) { 175 176 case '\b': 177 if (col > 0) 178 col--; 179 continue; 180 181 case '\t': 182 col = (col+8) & ~07; 183 if (col > maxcol) 184 maxcol = col; 185 continue; 186 187 case '\r': 188 col = 0; 189 continue; 190 191 case SO: 192 mode |= ALTSET; 193 continue; 194 195 case SI: 196 mode &= ~ALTSET; 197 continue; 198 199 case IESC: 200 switch (c = getwc(f)) { 201 202 case HREV: 203 if (halfpos == 0) { 204 mode |= SUPERSC; 205 halfpos--; 206 } else if (halfpos > 0) { 207 mode &= ~SUBSC; 208 halfpos--; 209 } else { 210 halfpos = 0; 211 reverse(); 212 } 213 continue; 214 215 case HFWD: 216 if (halfpos == 0) { 217 mode |= SUBSC; 218 halfpos++; 219 } else if (halfpos < 0) { 220 mode &= ~SUPERSC; 221 halfpos++; 222 } else { 223 halfpos = 0; 224 fwd(); 225 } 226 continue; 227 228 case FREV: 229 reverse(); 230 continue; 231 232 default: 233 errx(1, "unknown escape sequence in input: %o, %o", IESC, c); 234 } 235 continue; 236 237 case '_': 238 if (obuf[col].c_char || obuf[col].c_width < 0) { 239 while (col > 0 && obuf[col].c_width < 0) 240 col--; 241 w = obuf[col].c_width; 242 for (i = 0; i < w; i++) 243 obuf[col++].c_mode |= UNDERL | mode; 244 if (col > maxcol) 245 maxcol = col; 246 continue; 247 } 248 obuf[col].c_char = '_'; 249 obuf[col].c_width = 1; 250 /* FALLTHROUGH */ 251 case ' ': 252 col++; 253 if (col > maxcol) 254 maxcol = col; 255 continue; 256 257 case '\n': 258 flushln(); 259 continue; 260 261 case '\f': 262 flushln(); 263 putwchar('\f'); 264 continue; 265 266 default: 267 if ((w = wcwidth(c)) <= 0) /* non printing */ 268 continue; 269 if (obuf[col].c_char == '\0') { 270 obuf[col].c_char = c; 271 for (i = 0; i < w; i++) 272 obuf[col + i].c_mode = mode; 273 obuf[col].c_width = w; 274 for (i = 1; i < w; i++) 275 obuf[col + i].c_width = -1; 276 } else if (obuf[col].c_char == '_') { 277 obuf[col].c_char = c; 278 for (i = 0; i < w; i++) 279 obuf[col + i].c_mode |= UNDERL|mode; 280 obuf[col].c_width = w; 281 for (i = 1; i < w; i++) 282 obuf[col + i].c_width = -1; 283 } else if (obuf[col].c_char == c) { 284 for (i = 0; i < w; i++) 285 obuf[col + i].c_mode |= BOLD|mode; 286 } else { 287 w = obuf[col].c_width; 288 for (i = 0; i < w; i++) 289 obuf[col + i].c_mode = mode; 290 } 291 col += w; 292 if (col > maxcol) 293 maxcol = col; 294 continue; 295 } 296 if (ferror(f)) 297 err(1, NULL); 298 if (maxcol) 299 flushln(); 300 } 301 302 static void 303 flushln(void) 304 { 305 int lastmode; 306 int i; 307 int hadmodes = 0; 308 309 lastmode = NORMAL; 310 for (i=0; i<maxcol; i++) { 311 if (obuf[i].c_mode != lastmode) { 312 hadmodes++; 313 setnewmode(obuf[i].c_mode); 314 lastmode = obuf[i].c_mode; 315 } 316 if (obuf[i].c_char == '\0') { 317 if (upln) 318 PRINT(CURS_RIGHT); 319 else 320 outc(' ', 1); 321 } else 322 outc(obuf[i].c_char, obuf[i].c_width); 323 if (obuf[i].c_width > 1) 324 i += obuf[i].c_width - 1; 325 } 326 if (lastmode != NORMAL) { 327 setnewmode(0); 328 } 329 if (must_overstrike && hadmodes) 330 overstrike(); 331 putwchar('\n'); 332 if (iflag && hadmodes) 333 iattr(); 334 (void)fflush(stdout); 335 if (upln) 336 upln--; 337 initbuf(); 338 } 339 340 /* 341 * For terminals that can overstrike, overstrike underlines and bolds. 342 * We don't do anything with halfline ups and downs, or Greek. 343 */ 344 static void 345 overstrike(void) 346 { 347 int i; 348 wchar_t lbuf[256]; 349 wchar_t *cp = lbuf; 350 int hadbold=0; 351 352 /* Set up overstrike buffer */ 353 for (i=0; i<maxcol; i++) 354 switch (obuf[i].c_mode) { 355 case NORMAL: 356 default: 357 *cp++ = ' '; 358 break; 359 case UNDERL: 360 *cp++ = '_'; 361 break; 362 case BOLD: 363 *cp++ = obuf[i].c_char; 364 if (obuf[i].c_width > 1) 365 i += obuf[i].c_width - 1; 366 hadbold=1; 367 break; 368 } 369 putwchar('\r'); 370 for (*cp=' '; *cp==' '; cp--) 371 *cp = 0; 372 for (cp=lbuf; *cp; cp++) 373 putwchar(*cp); 374 if (hadbold) { 375 putwchar('\r'); 376 for (cp=lbuf; *cp; cp++) 377 putwchar(*cp=='_' ? ' ' : *cp); 378 putwchar('\r'); 379 for (cp=lbuf; *cp; cp++) 380 putwchar(*cp=='_' ? ' ' : *cp); 381 } 382 } 383 384 static void 385 iattr(void) 386 { 387 int i; 388 wchar_t lbuf[256]; 389 wchar_t *cp = lbuf; 390 391 for (i=0; i<maxcol; i++) 392 switch (obuf[i].c_mode) { 393 case NORMAL: *cp++ = ' '; break; 394 case ALTSET: *cp++ = 'g'; break; 395 case SUPERSC: *cp++ = '^'; break; 396 case SUBSC: *cp++ = 'v'; break; 397 case UNDERL: *cp++ = '_'; break; 398 case BOLD: *cp++ = '!'; break; 399 default: *cp++ = 'X'; break; 400 } 401 for (*cp=' '; *cp==' '; cp--) 402 *cp = 0; 403 for (cp=lbuf; *cp; cp++) 404 putwchar(*cp); 405 putwchar('\n'); 406 } 407 408 static void 409 initbuf(void) 410 { 411 412 bzero((char *)obuf, sizeof (obuf)); /* depends on NORMAL == 0 */ 413 col = 0; 414 maxcol = 0; 415 mode &= ALTSET; 416 } 417 418 static void 419 fwd(void) 420 { 421 int oldcol, oldmax; 422 423 oldcol = col; 424 oldmax = maxcol; 425 flushln(); 426 col = oldcol; 427 maxcol = oldmax; 428 } 429 430 static void 431 reverse(void) 432 { 433 upln++; 434 fwd(); 435 PRINT(CURS_UP); 436 PRINT(CURS_UP); 437 upln++; 438 } 439 440 static void 441 initcap(void) 442 { 443 static char tcapbuf[512]; 444 char *bp = tcapbuf; 445 446 /* This nonsense attempts to work with both old and new termcap */ 447 CURS_UP = tgetstr("up", &bp); 448 CURS_RIGHT = tgetstr("ri", &bp); 449 if (CURS_RIGHT == NULL) 450 CURS_RIGHT = tgetstr("nd", &bp); 451 CURS_LEFT = tgetstr("le", &bp); 452 if (CURS_LEFT == NULL) 453 CURS_LEFT = tgetstr("bc", &bp); 454 if (CURS_LEFT == NULL && tgetflag("bs")) 455 CURS_LEFT = "\b"; 456 457 ENTER_STANDOUT = tgetstr("so", &bp); 458 EXIT_STANDOUT = tgetstr("se", &bp); 459 ENTER_UNDERLINE = tgetstr("us", &bp); 460 EXIT_UNDERLINE = tgetstr("ue", &bp); 461 ENTER_DIM = tgetstr("mh", &bp); 462 ENTER_BOLD = tgetstr("md", &bp); 463 ENTER_REVERSE = tgetstr("mr", &bp); 464 EXIT_ATTRIBUTES = tgetstr("me", &bp); 465 466 if (!ENTER_BOLD && ENTER_REVERSE) 467 ENTER_BOLD = ENTER_REVERSE; 468 if (!ENTER_BOLD && ENTER_STANDOUT) 469 ENTER_BOLD = ENTER_STANDOUT; 470 if (!ENTER_UNDERLINE && ENTER_STANDOUT) { 471 ENTER_UNDERLINE = ENTER_STANDOUT; 472 EXIT_UNDERLINE = EXIT_STANDOUT; 473 } 474 if (!ENTER_DIM && ENTER_STANDOUT) 475 ENTER_DIM = ENTER_STANDOUT; 476 if (!ENTER_REVERSE && ENTER_STANDOUT) 477 ENTER_REVERSE = ENTER_STANDOUT; 478 if (!EXIT_ATTRIBUTES && EXIT_STANDOUT) 479 EXIT_ATTRIBUTES = EXIT_STANDOUT; 480 481 /* 482 * Note that we use REVERSE for the alternate character set, 483 * not the as/ae capabilities. This is because we are modelling 484 * the model 37 teletype (since that's what nroff outputs) and 485 * the typical as/ae is more of a graphics set, not the greek 486 * letters the 37 has. 487 */ 488 489 UNDER_CHAR = tgetstr("uc", &bp); 490 must_use_uc = (UNDER_CHAR && !ENTER_UNDERLINE); 491 } 492 493 static int 494 outchar(int c) 495 { 496 return (putwchar(c) != WEOF ? c : EOF); 497 } 498 499 static int curmode = 0; 500 501 static void 502 outc(wint_t c, int width) 503 { 504 int i; 505 506 putwchar(c); 507 if (must_use_uc && (curmode&UNDERL)) { 508 for (i = 0; i < width; i++) 509 PRINT(CURS_LEFT); 510 for (i = 0; i < width; i++) 511 PRINT(UNDER_CHAR); 512 } 513 } 514 515 static void 516 setnewmode(int newmode) 517 { 518 if (!iflag) { 519 if (curmode != NORMAL && newmode != NORMAL) 520 setnewmode(NORMAL); 521 switch (newmode) { 522 case NORMAL: 523 switch(curmode) { 524 case NORMAL: 525 break; 526 case UNDERL: 527 PRINT(EXIT_UNDERLINE); 528 break; 529 default: 530 /* This includes standout */ 531 PRINT(EXIT_ATTRIBUTES); 532 break; 533 } 534 break; 535 case ALTSET: 536 PRINT(ENTER_REVERSE); 537 break; 538 case SUPERSC: 539 /* 540 * This only works on a few terminals. 541 * It should be fixed. 542 */ 543 PRINT(ENTER_UNDERLINE); 544 PRINT(ENTER_DIM); 545 break; 546 case SUBSC: 547 PRINT(ENTER_DIM); 548 break; 549 case UNDERL: 550 PRINT(ENTER_UNDERLINE); 551 break; 552 case BOLD: 553 PRINT(ENTER_BOLD); 554 break; 555 default: 556 /* 557 * We should have some provision here for multiple modes 558 * on at once. This will have to come later. 559 */ 560 PRINT(ENTER_STANDOUT); 561 break; 562 } 563 } 564 curmode = newmode; 565 } 566