1 /* 2 * Copyright (c) 1980, 1993 3 * The Regents of the University of California. All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 3. Neither the name of the University nor the names of its contributors 14 * may be used to endorse or promote products derived from this software 15 * without specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 * SUCH DAMAGE. 28 * 29 * @(#) Copyright (c) 1980, 1993 The Regents of the University of California. All rights reserved. 30 * @(#)ul.c 8.1 (Berkeley) 6/6/93 31 * FreeBSD: head/usr.bin/ul/ul.c 245093 2013-01-06 03:08:27Z andrew $ 32 */ 33 34 #include <err.h> 35 #include <locale.h> 36 #include <stdio.h> 37 #include <stdlib.h> 38 #include <string.h> 39 #include <termcap.h> 40 #include <unistd.h> 41 #include <wchar.h> 42 #include <wctype.h> 43 44 #define IESC '\033' 45 #define SO '\016' 46 #define SI '\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 59 static int must_use_uc, must_overstrike; 60 static const char 61 *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; /* width or -1 if multi-column char. filler */ 69 } ; 70 71 static struct CHAR obuf[MAXBUF]; 72 static int col, maxcol; 73 static int mode; 74 static int halfpos; 75 static int upln; 76 static int iflag; 77 78 static void usage(void); 79 static void setnewmode(int); 80 static void initcap(void); 81 static void reverse(void); 82 static int outchar(int); 83 static void fwd(void); 84 static void initbuf(void); 85 static void iattr(void); 86 static void overstrike(void); 87 static void flushln(void); 88 static void filter(FILE *); 89 static void outc(wint_t, int); 90 91 #define PRINT(s) if (s == NULL) /* void */; else tputs(s, 1, outchar) 92 93 int 94 main(int argc, char **argv) 95 { 96 int c; 97 const char *termtype; 98 FILE *f; 99 char termcap[1024]; 100 101 setlocale(LC_ALL, ""); 102 103 termtype = getenv("TERM"); 104 if (termtype == NULL || (argv[0][0] == 'c' && !isatty(1))) 105 termtype = "lpr"; 106 while ((c=getopt(argc, argv, "it:T:")) != -1) 107 switch(c) { 108 109 case 't': 110 case 'T': /* for nroff compatibility */ 111 termtype = optarg; 112 break; 113 case 'i': 114 iflag = 1; 115 break; 116 default: 117 usage(); 118 } 119 120 switch(tgetent(termcap, termtype)) { 121 122 case 1: 123 break; 124 125 default: 126 warnx("trouble reading termcap"); 127 /* FALLTHROUGH */ 128 129 case 0: 130 /* No such terminal type - assume dumb */ 131 (void)strcpy(termcap, "dumb:os:col#80:cr=^M:sf=^J:am:"); 132 break; 133 } 134 initcap(); 135 if ( (tgetflag("os") && ENTER_BOLD==NULL ) || 136 (tgetflag("ul") && ENTER_UNDERLINE==NULL && UNDER_CHAR==NULL)) 137 must_overstrike = 1; 138 initbuf(); 139 if (optind == argc) 140 filter(stdin); 141 else for (; optind<argc; optind++) { 142 f = fopen(argv[optind],"r"); 143 if (f == NULL) 144 err(1, "%s", argv[optind]); 145 else 146 filter(f); 147 } 148 exit(0); 149 } 150 151 static void 152 usage(void) 153 { 154 fprintf(stderr, "usage: ul [-i] [-t terminal] [file ...]\n"); 155 exit(1); 156 } 157 158 static void 159 filter(FILE *f) 160 { 161 wint_t c; 162 int i, w; 163 164 while ((c = getwc(f)) != WEOF && col < MAXBUF) switch(c) { 165 166 case '\b': 167 if (col > 0) 168 col--; 169 continue; 170 171 case '\t': 172 col = (col+8) & ~07; 173 if (col > maxcol) 174 maxcol = col; 175 continue; 176 177 case '\r': 178 col = 0; 179 continue; 180 181 case SO: 182 mode |= ALTSET; 183 continue; 184 185 case SI: 186 mode &= ~ALTSET; 187 continue; 188 189 case IESC: 190 switch (c = getwc(f)) { 191 192 case HREV: 193 if (halfpos == 0) { 194 mode |= SUPERSC; 195 halfpos--; 196 } else if (halfpos > 0) { 197 mode &= ~SUBSC; 198 halfpos--; 199 } else { 200 halfpos = 0; 201 reverse(); 202 } 203 continue; 204 205 case HFWD: 206 if (halfpos == 0) { 207 mode |= SUBSC; 208 halfpos++; 209 } else if (halfpos < 0) { 210 mode &= ~SUPERSC; 211 halfpos++; 212 } else { 213 halfpos = 0; 214 fwd(); 215 } 216 continue; 217 218 case FREV: 219 reverse(); 220 continue; 221 222 default: 223 errx(1, "unknown escape sequence in input: %o, %o", IESC, c); 224 } 225 continue; 226 227 case '_': 228 if (obuf[col].c_char || obuf[col].c_width < 0) { 229 while (col > 0 && obuf[col].c_width < 0) 230 col--; 231 w = obuf[col].c_width; 232 for (i = 0; i < w; i++) 233 obuf[col++].c_mode |= UNDERL | mode; 234 if (col > maxcol) 235 maxcol = col; 236 continue; 237 } 238 obuf[col].c_char = '_'; 239 obuf[col].c_width = 1; 240 /* FALLTHROUGH */ 241 case ' ': 242 col++; 243 if (col > maxcol) 244 maxcol = col; 245 continue; 246 247 case '\n': 248 flushln(); 249 continue; 250 251 case '\f': 252 flushln(); 253 putwchar('\f'); 254 continue; 255 256 default: 257 if ((w = wcwidth(c)) <= 0) /* non printing */ 258 continue; 259 if (obuf[col].c_char == '\0') { 260 obuf[col].c_char = c; 261 for (i = 0; i < w; i++) 262 obuf[col + i].c_mode = mode; 263 obuf[col].c_width = w; 264 for (i = 1; i < w; i++) 265 obuf[col + i].c_width = -1; 266 } else if (obuf[col].c_char == '_') { 267 obuf[col].c_char = c; 268 for (i = 0; i < w; i++) 269 obuf[col + i].c_mode |= UNDERL|mode; 270 obuf[col].c_width = w; 271 for (i = 1; i < w; i++) 272 obuf[col + i].c_width = -1; 273 } else if ((wint_t)obuf[col].c_char == c) { 274 for (i = 0; i < w; i++) 275 obuf[col + i].c_mode |= BOLD|mode; 276 } else { 277 w = obuf[col].c_width; 278 for (i = 0; i < w; i++) 279 obuf[col + i].c_mode = mode; 280 } 281 col += w; 282 if (col > maxcol) 283 maxcol = col; 284 continue; 285 } 286 if (ferror(f)) 287 err(1, NULL); 288 if (maxcol) 289 flushln(); 290 } 291 292 static void 293 flushln(void) 294 { 295 int lastmode; 296 int i; 297 int hadmodes = 0; 298 299 lastmode = NORMAL; 300 for (i=0; i<maxcol; i++) { 301 if (obuf[i].c_mode != lastmode) { 302 hadmodes++; 303 setnewmode(obuf[i].c_mode); 304 lastmode = obuf[i].c_mode; 305 } 306 if (obuf[i].c_char == '\0') { 307 if (upln) 308 PRINT(CURS_RIGHT); 309 else 310 outc(' ', 1); 311 } else 312 outc(obuf[i].c_char, obuf[i].c_width); 313 if (obuf[i].c_width > 1) 314 i += obuf[i].c_width - 1; 315 } 316 if (lastmode != NORMAL) { 317 setnewmode(0); 318 } 319 if (must_overstrike && hadmodes) 320 overstrike(); 321 putwchar('\n'); 322 if (iflag && hadmodes) 323 iattr(); 324 (void)fflush(stdout); 325 if (upln) 326 upln--; 327 initbuf(); 328 } 329 330 /* 331 * For terminals that can overstrike, overstrike underlines and bolds. 332 * We don't do anything with halfline ups and downs, or Greek. 333 */ 334 static void 335 overstrike(void) 336 { 337 int i; 338 wchar_t lbuf[256]; 339 wchar_t *cp = lbuf; 340 int hadbold=0; 341 342 /* Set up overstrike buffer */ 343 for (i=0; i<maxcol; i++) 344 switch (obuf[i].c_mode) { 345 case NORMAL: 346 default: 347 *cp++ = ' '; 348 break; 349 case UNDERL: 350 *cp++ = '_'; 351 break; 352 case BOLD: 353 *cp++ = obuf[i].c_char; 354 if (obuf[i].c_width > 1) 355 i += obuf[i].c_width - 1; 356 hadbold=1; 357 break; 358 } 359 putwchar('\r'); 360 for (*cp=' '; *cp==' '; cp--) 361 *cp = 0; 362 for (cp=lbuf; *cp; cp++) 363 putwchar(*cp); 364 if (hadbold) { 365 putwchar('\r'); 366 for (cp=lbuf; *cp; cp++) 367 putwchar(*cp=='_' ? ' ' : *cp); 368 putwchar('\r'); 369 for (cp=lbuf; *cp; cp++) 370 putwchar(*cp=='_' ? ' ' : *cp); 371 } 372 } 373 374 static void 375 iattr(void) 376 { 377 int i; 378 wchar_t lbuf[256]; 379 wchar_t *cp = lbuf; 380 381 for (i=0; i<maxcol; i++) 382 switch (obuf[i].c_mode) { 383 case NORMAL: *cp++ = ' '; break; 384 case ALTSET: *cp++ = 'g'; break; 385 case SUPERSC: *cp++ = '^'; break; 386 case SUBSC: *cp++ = 'v'; break; 387 case UNDERL: *cp++ = '_'; break; 388 case BOLD: *cp++ = '!'; break; 389 default: *cp++ = 'X'; break; 390 } 391 for (*cp=' '; *cp==' '; cp--) 392 *cp = 0; 393 for (cp=lbuf; *cp; cp++) 394 putwchar(*cp); 395 putwchar('\n'); 396 } 397 398 static void 399 initbuf(void) 400 { 401 402 bzero((char *)obuf, sizeof (obuf)); /* depends on NORMAL == 0 */ 403 col = 0; 404 maxcol = 0; 405 mode &= ALTSET; 406 } 407 408 static void 409 fwd(void) 410 { 411 int oldcol, oldmax; 412 413 oldcol = col; 414 oldmax = maxcol; 415 flushln(); 416 col = oldcol; 417 maxcol = oldmax; 418 } 419 420 static void 421 reverse(void) 422 { 423 upln++; 424 fwd(); 425 PRINT(CURS_UP); 426 PRINT(CURS_UP); 427 upln++; 428 } 429 430 static void 431 initcap(void) 432 { 433 static char tcapbuf[512]; 434 char *bp = tcapbuf; 435 436 /* This nonsense attempts to work with both old and new termcap */ 437 CURS_UP = tgetstr("up", &bp); 438 CURS_RIGHT = tgetstr("ri", &bp); 439 if (CURS_RIGHT == NULL) 440 CURS_RIGHT = tgetstr("nd", &bp); 441 CURS_LEFT = tgetstr("le", &bp); 442 if (CURS_LEFT == NULL) 443 CURS_LEFT = tgetstr("bc", &bp); 444 if (CURS_LEFT == NULL && tgetflag("bs")) 445 CURS_LEFT = "\b"; 446 447 ENTER_STANDOUT = tgetstr("so", &bp); 448 EXIT_STANDOUT = tgetstr("se", &bp); 449 ENTER_UNDERLINE = tgetstr("us", &bp); 450 EXIT_UNDERLINE = tgetstr("ue", &bp); 451 ENTER_DIM = tgetstr("mh", &bp); 452 ENTER_BOLD = tgetstr("md", &bp); 453 ENTER_REVERSE = tgetstr("mr", &bp); 454 EXIT_ATTRIBUTES = tgetstr("me", &bp); 455 456 if (!ENTER_BOLD && ENTER_REVERSE) 457 ENTER_BOLD = ENTER_REVERSE; 458 if (!ENTER_BOLD && ENTER_STANDOUT) 459 ENTER_BOLD = ENTER_STANDOUT; 460 if (!ENTER_UNDERLINE && ENTER_STANDOUT) { 461 ENTER_UNDERLINE = ENTER_STANDOUT; 462 EXIT_UNDERLINE = EXIT_STANDOUT; 463 } 464 if (!ENTER_DIM && ENTER_STANDOUT) 465 ENTER_DIM = ENTER_STANDOUT; 466 if (!ENTER_REVERSE && ENTER_STANDOUT) 467 ENTER_REVERSE = ENTER_STANDOUT; 468 if (!EXIT_ATTRIBUTES && EXIT_STANDOUT) 469 EXIT_ATTRIBUTES = EXIT_STANDOUT; 470 471 /* 472 * Note that we use REVERSE for the alternate character set, 473 * not the as/ae capabilities. This is because we are modelling 474 * the model 37 teletype (since that's what nroff outputs) and 475 * the typical as/ae is more of a graphics set, not the greek 476 * letters the 37 has. 477 */ 478 479 UNDER_CHAR = tgetstr("uc", &bp); 480 must_use_uc = (UNDER_CHAR && !ENTER_UNDERLINE); 481 } 482 483 static int 484 outchar(int c) 485 { 486 return (putwchar(c) != WEOF ? c : EOF); 487 } 488 489 static int curmode = 0; 490 491 static void 492 outc(wint_t c, int width) 493 { 494 int i; 495 496 putwchar(c); 497 if (must_use_uc && (curmode&UNDERL)) { 498 for (i = 0; i < width; i++) 499 PRINT(CURS_LEFT); 500 for (i = 0; i < width; i++) 501 PRINT(UNDER_CHAR); 502 } 503 } 504 505 static void 506 setnewmode(int newmode) 507 { 508 if (!iflag) { 509 if (curmode != NORMAL && newmode != NORMAL) 510 setnewmode(NORMAL); 511 switch (newmode) { 512 case NORMAL: 513 switch(curmode) { 514 case NORMAL: 515 break; 516 case UNDERL: 517 PRINT(EXIT_UNDERLINE); 518 break; 519 default: 520 /* This includes standout */ 521 PRINT(EXIT_ATTRIBUTES); 522 break; 523 } 524 break; 525 case ALTSET: 526 PRINT(ENTER_REVERSE); 527 break; 528 case SUPERSC: 529 /* 530 * This only works on a few terminals. 531 * It should be fixed. 532 */ 533 PRINT(ENTER_UNDERLINE); 534 PRINT(ENTER_DIM); 535 break; 536 case SUBSC: 537 PRINT(ENTER_DIM); 538 break; 539 case UNDERL: 540 PRINT(ENTER_UNDERLINE); 541 break; 542 case BOLD: 543 PRINT(ENTER_BOLD); 544 break; 545 default: 546 /* 547 * We should have some provision here for multiple modes 548 * on at once. This will have to come later. 549 */ 550 PRINT(ENTER_STANDOUT); 551 break; 552 } 553 } 554 curmode = newmode; 555 } 556