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