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