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