1 /* 2 * Copyright 2000 Sun Microsystems, Inc. All rights reserved. 3 * Use is subject to license terms. 4 */ 5 6 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ 7 /* All Rights Reserved */ 8 9 /* 10 * Copyright (c) 1980 Regents of the University of California. 11 * All rights reserved. The Berkeley software License Agreement 12 * specifies the terms and conditions for redistribution. 13 */ 14 15 /* 16 * Copyright (c) 2018, Joyent, Inc. 17 */ 18 19 #include <stdio.h> 20 #include <locale.h> 21 #include <wctype.h> 22 #include <widec.h> 23 #include <euc.h> 24 #include <getwidth.h> 25 #include <limits.h> 26 #include <stdlib.h> 27 #include <curses.h> 28 #include <term.h> 29 #include <string.h> 30 31 #define IESC L'\033' 32 #define SO L'\016' 33 #define SI L'\017' 34 #define HFWD L'9' 35 #define HREV L'8' 36 #define FREV L'7' 37 #define CDUMMY -1 38 39 #define NORMAL 000 40 #define ALTSET 001 /* Reverse */ 41 #define SUPERSC 002 /* Dim */ 42 #define SUBSC 004 /* Dim | Ul */ 43 #define UNDERL 010 /* Ul */ 44 #define BOLD 020 /* Bold */ 45 46 #define MEMFCT 16 47 /* 48 * MEMFCT is a number that is likely to be large enough as a factor for 49 * allocating more memory and to be small enough so as not wasting memory 50 */ 51 52 int must_use_uc, must_overstrike; 53 char *CURS_UP, *CURS_RIGHT, *CURS_LEFT, 54 *ENTER_STANDOUT, *EXIT_STANDOUT, *ENTER_UNDERLINE, *EXIT_UNDERLINE, 55 *ENTER_DIM, *ENTER_BOLD, *ENTER_REVERSE, *UNDER_CHAR, *EXIT_ATTRIBUTES; 56 57 struct CHAR { 58 char c_mode; 59 wchar_t c_char; 60 }; 61 62 struct CHAR obuf[LINE_MAX]; 63 int col, maxcol; 64 int mode; 65 int halfpos; 66 int upln; 67 int iflag; 68 69 eucwidth_t wp; 70 int scrw[4]; 71 72 void setmode(int newmode); 73 void outc(wchar_t c); 74 int outchar(char c); 75 void initcap(void); 76 void reverse(void); 77 void fwd(void); 78 void initbuf(void); 79 void iattr(void); 80 void overstrike(void); 81 void flushln(void); 82 void ul_filter(FILE *f); 83 void ul_puts(char *str); 84 85 int 86 main(int argc, char **argv) 87 { 88 int c; 89 char *termtype; 90 FILE *f; 91 char termcap[1024]; 92 extern int optind; 93 extern char *optarg; 94 95 (void) setlocale(LC_ALL, ""); 96 #if !defined(TEXT_DOMAIN) 97 #define TEXT_DOMAIN "SYS_TEST" 98 #endif 99 (void) textdomain(TEXT_DOMAIN); 100 101 getwidth(&wp); 102 scrw[0] = 1; 103 scrw[1] = wp._scrw1; 104 scrw[2] = wp._scrw2; 105 scrw[3] = wp._scrw3; 106 107 termtype = getenv("TERM"); 108 if (termtype == NULL || (argv[0][0] == 'c' && !isatty(1))) 109 termtype = "lpr"; 110 while ((c = getopt(argc, argv, "it:T:")) != EOF) 111 switch (c) { 112 113 case 't': 114 case 'T': /* for nroff compatibility */ 115 termtype = optarg; 116 break; 117 case 'i': 118 iflag = 1; 119 break; 120 121 default: 122 (void) fprintf(stderr, 123 gettext("\ 124 Usage: %s [ -i ] [ -t terminal ] [ filename...]\n"), 125 argv[0]); 126 exit(1); 127 } 128 129 switch (tgetent(termcap, termtype)) { 130 131 case 1: 132 break; 133 134 default: 135 (void) fprintf(stderr, gettext("trouble reading termcap")); 136 /*FALLTHROUGH*/ 137 138 case 0: 139 /* No such terminal type - assume dumb */ 140 (void) strcpy(termcap, "dumb:os:col#80:cr=^M:sf=^J:am:"); 141 break; 142 } 143 initcap(); 144 if ((tgetflag("os") && ENTER_BOLD == NULL) || (tgetflag("ul") && 145 ENTER_UNDERLINE == NULL && UNDER_CHAR == NULL)) 146 must_overstrike = 1; 147 initbuf(); 148 if (optind == argc) 149 ul_filter(stdin); 150 else for (; optind < argc; optind++) { 151 f = fopen(argv[optind], "r"); 152 if (f == NULL) { 153 perror(argv[optind]); 154 exit(1); 155 } else 156 ul_filter(f); 157 } 158 return (0); 159 } 160 161 void 162 ul_filter(FILE *f) 163 { 164 wchar_t c; 165 int i; 166 167 while ((c = getwc(f)) != EOF) { 168 if (maxcol >= LINE_MAX) { 169 (void) fprintf(stderr, 170 gettext("Input line longer than %d characters\n"), LINE_MAX); 171 exit(1); 172 } 173 switch (c) { 174 175 case L'\b': 176 if (col > 0) 177 col--; 178 continue; 179 180 case L'\t': 181 col = (col+8) & ~07; 182 if (col > maxcol) 183 maxcol = col; 184 continue; 185 186 case L'\r': 187 col = 0; 188 continue; 189 190 case SO: 191 mode |= ALTSET; 192 continue; 193 194 case SI: 195 mode &= ~ALTSET; 196 continue; 197 198 case IESC: 199 switch (c = getwc(f)) { 200 case HREV: 201 if (halfpos == 0) { 202 mode |= SUPERSC; 203 halfpos--; 204 } else if (halfpos > 0) { 205 mode &= ~SUBSC; 206 halfpos--; 207 } else { 208 halfpos = 0; 209 reverse(); 210 } 211 continue; 212 213 case HFWD: 214 if (halfpos == 0) { 215 mode |= SUBSC; 216 halfpos++; 217 } else if (halfpos < 0) { 218 mode &= ~SUPERSC; 219 halfpos++; 220 } else { 221 halfpos = 0; 222 fwd(); 223 } 224 continue; 225 case FREV: 226 reverse(); 227 continue; 228 229 default: 230 (void) fprintf(stderr, 231 gettext("Unknown escape sequence in input: %o, %o\n"), 232 IESC, c); 233 exit(1); 234 } 235 continue; 236 237 case L'_': 238 if (obuf[col].c_char) 239 obuf[col].c_mode |= UNDERL | mode; 240 else 241 obuf[col].c_char = '_'; 242 /*FALLTHROUGH*/ 243 244 case L' ': 245 col++; 246 if (col > maxcol) 247 maxcol = col; 248 continue; 249 250 case L'\n': 251 flushln(); 252 continue; 253 254 default: 255 if (c < L' ') /* non printing */ 256 continue; 257 if (obuf[col].c_char == L'\0') { 258 obuf[col].c_char = c; 259 obuf[col].c_mode = mode; 260 i = scrw[wcsetno(c)]; 261 while (--i > 0) 262 obuf[++col].c_char = CDUMMY; 263 } else if (obuf[col].c_char == L'_') { 264 obuf[col].c_char = c; 265 obuf[col].c_mode |= UNDERL|mode; 266 i = scrw[wcsetno(c)]; 267 while (--i > 0) 268 obuf[++col].c_char = CDUMMY; 269 } else if (obuf[col].c_char == c) 270 obuf[col].c_mode |= BOLD|mode; 271 else { 272 obuf[col].c_char = c; 273 obuf[col].c_mode = mode; 274 } 275 col++; 276 if (col > maxcol) 277 maxcol = col; 278 continue; 279 } 280 } 281 if (maxcol) 282 flushln(); 283 } 284 285 void 286 flushln(void) 287 { 288 int lastmode; 289 int i; 290 int hadmodes = 0; 291 292 lastmode = NORMAL; 293 for (i = 0; i < maxcol; i++) { 294 if (obuf[i].c_mode != lastmode) { 295 hadmodes++; 296 setmode(obuf[i].c_mode); 297 lastmode = obuf[i].c_mode; 298 } 299 if (obuf[i].c_char == L'\0') { 300 if (upln) { 301 ul_puts(CURS_RIGHT); 302 } else 303 outc(L' '); 304 } else 305 outc(obuf[i].c_char); 306 } 307 if (lastmode != NORMAL) { 308 setmode(0); 309 } 310 if (must_overstrike && hadmodes) 311 overstrike(); 312 (void) putwchar(L'\n'); 313 if (iflag && hadmodes) 314 iattr(); 315 if (upln) 316 upln--; 317 initbuf(); 318 } 319 320 /* 321 * For terminals that can overstrike, overstrike underlines and bolds. 322 * We don't do anything with halfline ups and downs, or Greek. 323 */ 324 void 325 overstrike(void) 326 { 327 int i, n; 328 wchar_t *cp, *scp; 329 size_t szbf = 256, tszbf; 330 int hadbold = 0; 331 332 scp = (wchar_t *)malloc(sizeof (wchar_t) * szbf); 333 if (!scp) { 334 /* this kind of message need not to be gettext'ed */ 335 (void) fprintf(stderr, "malloc failed\n"); 336 exit(1); 337 } 338 cp = scp; 339 tszbf = szbf; 340 #ifdef DEBUG 341 /* 342 * to allocate a memory after the chunk of the current scp 343 * and to make sure the following realloc() allocates 344 * memory from different chunks. 345 */ 346 (void) malloc(1024 * 1024); 347 #endif 348 349 /* Set up overstrike buffer */ 350 for (i = 0; i < maxcol; i++) { 351 n = scrw[wcsetno(obuf[i].c_char)]; 352 if (tszbf <= n) { 353 /* may not enough buffer for this char */ 354 size_t pos; 355 356 /* obtain the offset of cp */ 357 pos = cp - scp; 358 /* reallocate another (n * MEMFCT) * sizeof (wchar_t) */ 359 scp = (wchar_t *)realloc(scp, 360 sizeof (wchar_t) * (szbf + (n * MEMFCT))); 361 if (!scp) { 362 (void) fprintf(stderr, "malloc failed\n"); 363 exit(1); 364 } 365 /* get the new address of cp */ 366 cp = scp + pos; 367 szbf += n * MEMFCT; 368 tszbf += n * MEMFCT; 369 } 370 switch (obuf[i].c_mode) { 371 case NORMAL: 372 default: 373 tszbf -= n; 374 *cp++ = L' '; 375 while (--n > 0) { 376 *cp++ = L' '; 377 i++; 378 } 379 break; 380 case UNDERL: 381 tszbf -= n; 382 *cp++ = L'_'; 383 while (--n > 0) { 384 *cp++ = L'_'; 385 i++; 386 } 387 break; 388 case BOLD: 389 tszbf--; 390 *cp++ = obuf[i].c_char; 391 hadbold = 1; 392 break; 393 } 394 } 395 (void) putwchar(L'\r'); 396 for (*cp = L' '; *cp == L' '; cp--) 397 *cp = L'\0'; 398 for (cp = scp; *cp; cp++) 399 (void) putwchar(*cp); 400 if (hadbold) { 401 (void) putwchar(L'\r'); 402 for (cp = scp; *cp; cp++) 403 (void) putwchar(*cp == L'_' ? L' ' : *cp); 404 (void) putwchar(L'\r'); 405 for (cp = scp; *cp; cp++) 406 (void) putwchar(*cp == L'_' ? L' ' : *cp); 407 } 408 free(scp); 409 } 410 411 void 412 iattr(void) 413 { 414 int i, n; 415 wchar_t *cp, *scp; 416 wchar_t cx; 417 size_t szbf = 256, tszbf; 418 419 scp = (wchar_t *)malloc(sizeof (wchar_t) * szbf); 420 if (!scp) { 421 /* this kind of message need not to be gettext'ed */ 422 (void) fprintf(stderr, "malloc failed\n"); 423 exit(1); 424 } 425 cp = scp; 426 tszbf = szbf; 427 #ifdef DEBUG 428 /* 429 * to allocate a memory after the chunk of the current scp 430 * and to make sure the following realloc() allocates 431 * memory from different chunks. 432 */ 433 (void) malloc(1024 * 1024); 434 #endif 435 for (i = 0; i < maxcol; i++) { 436 switch (obuf[i].c_mode) { 437 case NORMAL: cx = ' '; break; 438 case ALTSET: cx = 'g'; break; 439 case SUPERSC: cx = '^'; break; 440 case SUBSC: cx = 'v'; break; 441 case UNDERL: cx = '_'; break; 442 case BOLD: cx = '!'; break; 443 default: cx = 'X'; break; 444 } 445 n = scrw[wcsetno(obuf[i].c_char)]; 446 if (tszbf <= n) { 447 /* may not enough buffer for this char */ 448 size_t pos; 449 450 /* obtain the offset of cp */ 451 pos = cp - scp; 452 /* reallocate another (n * MEMFCT) * sizeof (wchar_t) */ 453 scp = (wchar_t *)realloc(scp, 454 sizeof (wchar_t) * (szbf + (n * MEMFCT))); 455 if (!scp) { 456 (void) fprintf(stderr, "malloc failed\n"); 457 exit(1); 458 } 459 /* get the new address of cp */ 460 cp = scp + pos; 461 szbf += n * MEMFCT; 462 tszbf += n * MEMFCT; 463 } 464 tszbf -= n; 465 *cp++ = cx; 466 while (--n > 0) { 467 *cp++ = cx; 468 i++; 469 } 470 } 471 for (*cp = L' '; *cp == L' '; cp--) 472 *cp = L'\0'; 473 for (cp = scp; *cp; cp++) 474 (void) putwchar(*cp); 475 (void) putwchar(L'\n'); 476 free(scp); 477 } 478 479 void 480 initbuf(void) 481 { 482 int i; 483 484 /* following depends on NORMAL == 000 */ 485 for (i = 0; i < LINE_MAX; i++) 486 obuf[i].c_char = obuf[i].c_mode = 0; 487 488 col = 0; 489 maxcol = 0; 490 mode &= ALTSET; 491 } 492 493 void 494 fwd(void) 495 { 496 int oldcol, oldmax; 497 498 oldcol = col; 499 oldmax = maxcol; 500 flushln(); 501 col = oldcol; 502 maxcol = oldmax; 503 } 504 505 void 506 reverse(void) 507 { 508 upln++; 509 fwd(); 510 ul_puts(CURS_UP); 511 ul_puts(CURS_UP); 512 upln++; 513 } 514 515 void 516 initcap(void) 517 { 518 static char tcapbuf[512]; 519 char *bp = tcapbuf; 520 521 /* This nonsense attempts to work with both old and new termcap */ 522 CURS_UP = tgetstr("up", &bp); 523 CURS_RIGHT = tgetstr("ri", &bp); 524 if (CURS_RIGHT == NULL) 525 CURS_RIGHT = tgetstr("nd", &bp); 526 CURS_LEFT = tgetstr("le", &bp); 527 if (CURS_LEFT == NULL) 528 CURS_LEFT = tgetstr("bc", &bp); 529 if (CURS_LEFT == NULL && tgetflag("bs")) 530 CURS_LEFT = "\b"; 531 532 ENTER_STANDOUT = tgetstr("so", &bp); 533 EXIT_STANDOUT = tgetstr("se", &bp); 534 ENTER_UNDERLINE = tgetstr("us", &bp); 535 EXIT_UNDERLINE = tgetstr("ue", &bp); 536 ENTER_DIM = tgetstr("mh", &bp); 537 ENTER_BOLD = tgetstr("md", &bp); 538 ENTER_REVERSE = tgetstr("mr", &bp); 539 EXIT_ATTRIBUTES = tgetstr("me", &bp); 540 541 if (!ENTER_BOLD && ENTER_REVERSE) 542 ENTER_BOLD = ENTER_REVERSE; 543 if (!ENTER_BOLD && ENTER_STANDOUT) 544 ENTER_BOLD = ENTER_STANDOUT; 545 if (!ENTER_UNDERLINE && ENTER_STANDOUT) { 546 ENTER_UNDERLINE = ENTER_STANDOUT; 547 EXIT_UNDERLINE = EXIT_STANDOUT; 548 } 549 if (!ENTER_DIM && ENTER_STANDOUT) 550 ENTER_DIM = ENTER_STANDOUT; 551 if (!ENTER_REVERSE && ENTER_STANDOUT) 552 ENTER_REVERSE = ENTER_STANDOUT; 553 if (!EXIT_ATTRIBUTES && EXIT_STANDOUT) 554 EXIT_ATTRIBUTES = EXIT_STANDOUT; 555 556 /* 557 * Note that we use REVERSE for the alternate character set, 558 * not the as/ae capabilities. This is because we are modelling 559 * the model 37 teletype (since that's what nroff outputs) and 560 * the typical as/ae is more of a graphics set, not the greek 561 * letters the 37 has. 562 */ 563 564 #ifdef notdef 565 printf("so %s se %s us %s ue %s me %s\n", 566 ENTER_STANDOUT, EXIT_STANDOUT, ENTER_UNDERLINE, 567 EXIT_UNDERLINE, EXIT_ATTRIBUTES); 568 #endif 569 UNDER_CHAR = tgetstr("uc", &bp); 570 must_use_uc = (UNDER_CHAR && !ENTER_UNDERLINE); 571 } 572 573 int 574 outchar(char c) 575 { 576 (void) putchar(c&0177); 577 return (0); 578 } 579 580 void 581 ul_puts(char *str) 582 { 583 if (str) 584 (void) tputs(str, 1, outchar); 585 } 586 587 static int curmode = 0; 588 589 void 590 outc(wchar_t c) 591 { 592 int m, n; 593 594 if (c == CDUMMY) 595 return; 596 (void) putwchar(c); 597 if (must_use_uc && (curmode & UNDERL)) { 598 m = n = scrw[wcsetno(c)]; 599 ul_puts(CURS_LEFT); 600 while (--m > 0) 601 ul_puts(CURS_LEFT); 602 ul_puts(UNDER_CHAR); 603 while (--n > 0) 604 ul_puts(UNDER_CHAR); 605 } 606 } 607 608 void 609 setmode(int newmode) 610 { 611 if (!iflag) { 612 if (curmode != NORMAL && newmode != NORMAL) 613 setmode(NORMAL); 614 switch (newmode) { 615 case NORMAL: 616 switch (curmode) { 617 case NORMAL: 618 break; 619 case UNDERL: 620 ul_puts(EXIT_UNDERLINE); 621 break; 622 default: 623 /* This includes standout */ 624 ul_puts(EXIT_ATTRIBUTES); 625 break; 626 } 627 break; 628 case ALTSET: 629 ul_puts(ENTER_REVERSE); 630 break; 631 case SUPERSC: 632 /* 633 * This only works on a few terminals. 634 * It should be fixed. 635 */ 636 ul_puts(ENTER_UNDERLINE); 637 ul_puts(ENTER_DIM); 638 break; 639 case SUBSC: 640 ul_puts(ENTER_DIM); 641 break; 642 case UNDERL: 643 ul_puts(ENTER_UNDERLINE); 644 break; 645 case BOLD: 646 ul_puts(ENTER_BOLD); 647 break; 648 default: 649 /* 650 * We should have some provision here for multiple modes 651 * on at once. This will have to come later. 652 */ 653 ul_puts(ENTER_STANDOUT); 654 break; 655 } 656 } 657 curmode = newmode; 658 } 659