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 }