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