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