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