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