ul.c revision 200462
1176491Smarcel/* 2176491Smarcel * Copyright (c) 1980, 1993 3176491Smarcel * The Regents of the University of California. All rights reserved. 4176491Smarcel * 5176491Smarcel * Redistribution and use in source and binary forms, with or without 6176491Smarcel * modification, are permitted provided that the following conditions 7176491Smarcel * are met: 8176491Smarcel * 1. Redistributions of source code must retain the above copyright 9176491Smarcel * notice, this list of conditions and the following disclaimer. 10176491Smarcel * 2. Redistributions in binary form must reproduce the above copyright 11176491Smarcel * notice, this list of conditions and the following disclaimer in the 12176491Smarcel * documentation and/or other materials provided with the distribution. 13176491Smarcel * 3. All advertising materials mentioning features or use of this software 14176491Smarcel * must display the following acknowledgement: 15176491Smarcel * This product includes software developed by the University of 16176491Smarcel * California, Berkeley and its contributors. 17176491Smarcel * 4. Neither the name of the University nor the names of its contributors 18176491Smarcel * may be used to endorse or promote products derived from this software 19176491Smarcel * without specific prior written permission. 20176491Smarcel * 21176491Smarcel * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 22176491Smarcel * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23176491Smarcel * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24176491Smarcel * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 25176491Smarcel * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26176491Smarcel * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 27176491Smarcel * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 28176491Smarcel * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29176491Smarcel * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 30176491Smarcel * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 31176491Smarcel * SUCH DAMAGE. 32176491Smarcel */ 33176491Smarcel 34176491Smarcel#ifndef lint 35176491Smarcelstatic const char copyright[] = 36176491Smarcel"@(#) Copyright (c) 1980, 1993\n\ 37176491Smarcel The Regents of the University of California. All rights reserved.\n"; 38176491Smarcel#endif /* not lint */ 39176491Smarcel 40176491Smarcel#ifndef lint 41176491Smarcel#if 0 42176491Smarcelstatic char sccsid[] = "@(#)ul.c 8.1 (Berkeley) 6/6/93"; 43176491Smarcel#endif 44176491Smarcelstatic const char rcsid[] = 45176491Smarcel "$FreeBSD: head/usr.bin/ul/ul.c 200462 2009-12-13 03:14:06Z delphij $"; 46176491Smarcel#endif /* not lint */ 47176491Smarcel 48176491Smarcel#include <err.h> 49176491Smarcel#include <locale.h> 50176491Smarcel#include <stdio.h> 51176491Smarcel#include <stdlib.h> 52176491Smarcel#include <string.h> 53176491Smarcel#include <termcap.h> 54176491Smarcel#include <unistd.h> 55176491Smarcel#include <wchar.h> 56176491Smarcel#include <wctype.h> 57176491Smarcel 58176491Smarcel#define IESC '\033' 59176491Smarcel#define SO '\016' 60176491Smarcel#define SI '\017' 61176491Smarcel#define HFWD '9' 62176491Smarcel#define HREV '8' 63176491Smarcel#define FREV '7' 64176491Smarcel#define MAXBUF 512 65176491Smarcel 66176491Smarcel#define NORMAL 000 67176491Smarcel#define ALTSET 001 /* Reverse */ 68176491Smarcel#define SUPERSC 002 /* Dim */ 69176491Smarcel#define SUBSC 004 /* Dim | Ul */ 70176491Smarcel#define UNDERL 010 /* Ul */ 71176491Smarcel#define BOLD 020 /* Bold */ 72176491Smarcel 73176491Smarcelint must_use_uc, must_overstrike; 74176491Smarcelconst char 75176491Smarcel *CURS_UP, *CURS_RIGHT, *CURS_LEFT, 76176491Smarcel *ENTER_STANDOUT, *EXIT_STANDOUT, *ENTER_UNDERLINE, *EXIT_UNDERLINE, 77176491Smarcel *ENTER_DIM, *ENTER_BOLD, *ENTER_REVERSE, *UNDER_CHAR, *EXIT_ATTRIBUTES; 78176491Smarcel 79176491Smarcelstruct CHAR { 80176491Smarcel char c_mode; 81176491Smarcel wchar_t c_char; 82176491Smarcel int c_width; /* width or -1 if multi-column char. filler */ 83176491Smarcel} ; 84176491Smarcel 85176491Smarcelstruct CHAR obuf[MAXBUF]; 86176491Smarcelint col, maxcol; 87176491Smarcelint mode; 88176491Smarcelint halfpos; 89176491Smarcelint upln; 90176491Smarcelint iflag; 91176491Smarcel 92176491Smarcelstatic void usage(void); 93176491Smarcelvoid setnewmode(int); 94176491Smarcelvoid initcap(void); 95176491Smarcelvoid reverse(void); 96176491Smarcelint outchar(int); 97176491Smarcelvoid fwd(void); 98176491Smarcelvoid initbuf(void); 99176491Smarcelvoid iattr(void); 100176491Smarcelvoid overstrike(void); 101176491Smarcelvoid flushln(void); 102176491Smarcelvoid filter(FILE *); 103176491Smarcelvoid outc(wint_t, int); 104176491Smarcel 105176491Smarcel#define PRINT(s) if (s == NULL) /* void */; else tputs(s, 1, outchar) 106176491Smarcel 107176491Smarcelint 108176491Smarcelmain(int argc, char **argv) 109176491Smarcel{ 110176491Smarcel int c; 111176491Smarcel const char *termtype; 112176491Smarcel FILE *f; 113176491Smarcel char termcap[1024]; 114176491Smarcel 115176491Smarcel setlocale(LC_ALL, ""); 116176491Smarcel 117176491Smarcel termtype = getenv("TERM"); 118176491Smarcel if (termtype == NULL || (argv[0][0] == 'c' && !isatty(1))) 119176491Smarcel termtype = "lpr"; 120176491Smarcel while ((c=getopt(argc, argv, "it:T:")) != -1) 121176491Smarcel switch(c) { 122176491Smarcel 123176491Smarcel case 't': 124176491Smarcel case 'T': /* for nroff compatibility */ 125176491Smarcel termtype = optarg; 126176491Smarcel break; 127176491Smarcel case 'i': 128176491Smarcel iflag = 1; 129176491Smarcel break; 130176491Smarcel default: 131176491Smarcel usage(); 132176491Smarcel } 133176491Smarcel 134176491Smarcel switch(tgetent(termcap, termtype)) { 135176491Smarcel 136176491Smarcel case 1: 137176491Smarcel break; 138176491Smarcel 139176491Smarcel default: 140176491Smarcel warnx("trouble reading termcap"); 141176491Smarcel /* FALLTHROUGH */ 142176491Smarcel 143176491Smarcel case 0: 144176491Smarcel /* No such terminal type - assume dumb */ 145176491Smarcel (void)strcpy(termcap, "dumb:os:col#80:cr=^M:sf=^J:am:"); 146176491Smarcel break; 147176491Smarcel } 148176491Smarcel initcap(); 149176491Smarcel if ( (tgetflag("os") && ENTER_BOLD==NULL ) || 150176491Smarcel (tgetflag("ul") && ENTER_UNDERLINE==NULL && UNDER_CHAR==NULL)) 151176491Smarcel must_overstrike = 1; 152176491Smarcel initbuf(); 153176491Smarcel if (optind == argc) 154176491Smarcel filter(stdin); 155176491Smarcel else for (; optind<argc; optind++) { 156176491Smarcel f = fopen(argv[optind],"r"); 157176491Smarcel if (f == NULL) 158176491Smarcel err(1, "%s", argv[optind]); 159176491Smarcel else 160176491Smarcel filter(f); 161176491Smarcel } 162176491Smarcel exit(0); 163176491Smarcel} 164176491Smarcel 165176491Smarcelstatic void 166176491Smarcelusage(void) 167176491Smarcel{ 168176491Smarcel fprintf(stderr, "usage: ul [-i] [-t terminal] [file ...]\n"); 169176491Smarcel exit(1); 170176501Smarcel} 171176501Smarcel 172176491Smarcelvoid 173176491Smarcelfilter(FILE *f) 174176491Smarcel{ 175176491Smarcel wint_t c; 176176491Smarcel int i, w; 177176491Smarcel 178176491Smarcel while ((c = getwc(f)) != WEOF && col < MAXBUF) switch(c) { 179176491Smarcel 180176491Smarcel case '\b': 181176491Smarcel if (col > 0) 182176491Smarcel col--; 183176491Smarcel continue; 184176491Smarcel 185176491Smarcel case '\t': 186176491Smarcel col = (col+8) & ~07; 187176491Smarcel if (col > maxcol) 188176491Smarcel maxcol = col; 189176491Smarcel continue; 190176491Smarcel 191176491Smarcel case '\r': 192176491Smarcel col = 0; 193176491Smarcel continue; 194176491Smarcel 195 case SO: 196 mode |= ALTSET; 197 continue; 198 199 case SI: 200 mode &= ~ALTSET; 201 continue; 202 203 case IESC: 204 switch (c = getwc(f)) { 205 206 case HREV: 207 if (halfpos == 0) { 208 mode |= SUPERSC; 209 halfpos--; 210 } else if (halfpos > 0) { 211 mode &= ~SUBSC; 212 halfpos--; 213 } else { 214 halfpos = 0; 215 reverse(); 216 } 217 continue; 218 219 case HFWD: 220 if (halfpos == 0) { 221 mode |= SUBSC; 222 halfpos++; 223 } else if (halfpos < 0) { 224 mode &= ~SUPERSC; 225 halfpos++; 226 } else { 227 halfpos = 0; 228 fwd(); 229 } 230 continue; 231 232 case FREV: 233 reverse(); 234 continue; 235 236 default: 237 errx(1, "unknown escape sequence in input: %o, %o", IESC, c); 238 } 239 continue; 240 241 case '_': 242 if (obuf[col].c_char || obuf[col].c_width < 0) { 243 while (col > 0 && obuf[col].c_width < 0) 244 col--; 245 w = obuf[col].c_width; 246 for (i = 0; i < w; i++) 247 obuf[col++].c_mode |= UNDERL | mode; 248 if (col > maxcol) 249 maxcol = col; 250 continue; 251 } 252 obuf[col].c_char = '_'; 253 obuf[col].c_width = 1; 254 /* FALLTHROUGH */ 255 case ' ': 256 col++; 257 if (col > maxcol) 258 maxcol = col; 259 continue; 260 261 case '\n': 262 flushln(); 263 continue; 264 265 case '\f': 266 flushln(); 267 putwchar('\f'); 268 continue; 269 270 default: 271 if ((w = wcwidth(c)) <= 0) /* non printing */ 272 continue; 273 if (obuf[col].c_char == '\0') { 274 obuf[col].c_char = c; 275 for (i = 0; i < w; i++) 276 obuf[col + i].c_mode = mode; 277 obuf[col].c_width = w; 278 for (i = 1; i < w; i++) 279 obuf[col + i].c_width = -1; 280 } else if (obuf[col].c_char == '_') { 281 obuf[col].c_char = c; 282 for (i = 0; i < w; i++) 283 obuf[col + i].c_mode |= UNDERL|mode; 284 obuf[col].c_width = w; 285 for (i = 1; i < w; i++) 286 obuf[col + i].c_width = -1; 287 } else if (obuf[col].c_char == c) { 288 for (i = 0; i < w; i++) 289 obuf[col + i].c_mode |= BOLD|mode; 290 } else { 291 w = obuf[col].c_width; 292 for (i = 0; i < w; i++) 293 obuf[col + i].c_mode = mode; 294 } 295 col += w; 296 if (col > maxcol) 297 maxcol = col; 298 continue; 299 } 300 if (ferror(f)) 301 err(1, NULL); 302 if (maxcol) 303 flushln(); 304} 305 306void 307flushln(void) 308{ 309 int lastmode; 310 int i; 311 int hadmodes = 0; 312 313 lastmode = NORMAL; 314 for (i=0; i<maxcol; i++) { 315 if (obuf[i].c_mode != lastmode) { 316 hadmodes++; 317 setnewmode(obuf[i].c_mode); 318 lastmode = obuf[i].c_mode; 319 } 320 if (obuf[i].c_char == '\0') { 321 if (upln) 322 PRINT(CURS_RIGHT); 323 else 324 outc(' ', 1); 325 } else 326 outc(obuf[i].c_char, obuf[i].c_width); 327 if (obuf[i].c_width > 1) 328 i += obuf[i].c_width - 1; 329 } 330 if (lastmode != NORMAL) { 331 setnewmode(0); 332 } 333 if (must_overstrike && hadmodes) 334 overstrike(); 335 putwchar('\n'); 336 if (iflag && hadmodes) 337 iattr(); 338 (void)fflush(stdout); 339 if (upln) 340 upln--; 341 initbuf(); 342} 343 344/* 345 * For terminals that can overstrike, overstrike underlines and bolds. 346 * We don't do anything with halfline ups and downs, or Greek. 347 */ 348void 349overstrike(void) 350{ 351 int i; 352 wchar_t lbuf[256]; 353 wchar_t *cp = lbuf; 354 int hadbold=0; 355 356 /* Set up overstrike buffer */ 357 for (i=0; i<maxcol; i++) 358 switch (obuf[i].c_mode) { 359 case NORMAL: 360 default: 361 *cp++ = ' '; 362 break; 363 case UNDERL: 364 *cp++ = '_'; 365 break; 366 case BOLD: 367 *cp++ = obuf[i].c_char; 368 if (obuf[i].c_width > 1) 369 i += obuf[i].c_width - 1; 370 hadbold=1; 371 break; 372 } 373 putwchar('\r'); 374 for (*cp=' '; *cp==' '; cp--) 375 *cp = 0; 376 for (cp=lbuf; *cp; cp++) 377 putwchar(*cp); 378 if (hadbold) { 379 putwchar('\r'); 380 for (cp=lbuf; *cp; cp++) 381 putwchar(*cp=='_' ? ' ' : *cp); 382 putwchar('\r'); 383 for (cp=lbuf; *cp; cp++) 384 putwchar(*cp=='_' ? ' ' : *cp); 385 } 386} 387 388void 389iattr(void) 390{ 391 int i; 392 wchar_t lbuf[256]; 393 wchar_t *cp = lbuf; 394 395 for (i=0; i<maxcol; i++) 396 switch (obuf[i].c_mode) { 397 case NORMAL: *cp++ = ' '; break; 398 case ALTSET: *cp++ = 'g'; break; 399 case SUPERSC: *cp++ = '^'; break; 400 case SUBSC: *cp++ = 'v'; break; 401 case UNDERL: *cp++ = '_'; break; 402 case BOLD: *cp++ = '!'; break; 403 default: *cp++ = 'X'; break; 404 } 405 for (*cp=' '; *cp==' '; cp--) 406 *cp = 0; 407 for (cp=lbuf; *cp; cp++) 408 putwchar(*cp); 409 putwchar('\n'); 410} 411 412void 413initbuf(void) 414{ 415 416 bzero((char *)obuf, sizeof (obuf)); /* depends on NORMAL == 0 */ 417 col = 0; 418 maxcol = 0; 419 mode &= ALTSET; 420} 421 422void 423fwd(void) 424{ 425 int oldcol, oldmax; 426 427 oldcol = col; 428 oldmax = maxcol; 429 flushln(); 430 col = oldcol; 431 maxcol = oldmax; 432} 433 434void 435reverse(void) 436{ 437 upln++; 438 fwd(); 439 PRINT(CURS_UP); 440 PRINT(CURS_UP); 441 upln++; 442} 443 444void 445initcap(void) 446{ 447 static char tcapbuf[512]; 448 char *bp = tcapbuf; 449 450 /* This nonsense attempts to work with both old and new termcap */ 451 CURS_UP = tgetstr("up", &bp); 452 CURS_RIGHT = tgetstr("ri", &bp); 453 if (CURS_RIGHT == NULL) 454 CURS_RIGHT = tgetstr("nd", &bp); 455 CURS_LEFT = tgetstr("le", &bp); 456 if (CURS_LEFT == NULL) 457 CURS_LEFT = tgetstr("bc", &bp); 458 if (CURS_LEFT == NULL && tgetflag("bs")) 459 CURS_LEFT = "\b"; 460 461 ENTER_STANDOUT = tgetstr("so", &bp); 462 EXIT_STANDOUT = tgetstr("se", &bp); 463 ENTER_UNDERLINE = tgetstr("us", &bp); 464 EXIT_UNDERLINE = tgetstr("ue", &bp); 465 ENTER_DIM = tgetstr("mh", &bp); 466 ENTER_BOLD = tgetstr("md", &bp); 467 ENTER_REVERSE = tgetstr("mr", &bp); 468 EXIT_ATTRIBUTES = tgetstr("me", &bp); 469 470 if (!ENTER_BOLD && ENTER_REVERSE) 471 ENTER_BOLD = ENTER_REVERSE; 472 if (!ENTER_BOLD && ENTER_STANDOUT) 473 ENTER_BOLD = ENTER_STANDOUT; 474 if (!ENTER_UNDERLINE && ENTER_STANDOUT) { 475 ENTER_UNDERLINE = ENTER_STANDOUT; 476 EXIT_UNDERLINE = EXIT_STANDOUT; 477 } 478 if (!ENTER_DIM && ENTER_STANDOUT) 479 ENTER_DIM = ENTER_STANDOUT; 480 if (!ENTER_REVERSE && ENTER_STANDOUT) 481 ENTER_REVERSE = ENTER_STANDOUT; 482 if (!EXIT_ATTRIBUTES && EXIT_STANDOUT) 483 EXIT_ATTRIBUTES = EXIT_STANDOUT; 484 485 /* 486 * Note that we use REVERSE for the alternate character set, 487 * not the as/ae capabilities. This is because we are modelling 488 * the model 37 teletype (since that's what nroff outputs) and 489 * the typical as/ae is more of a graphics set, not the greek 490 * letters the 37 has. 491 */ 492 493 UNDER_CHAR = tgetstr("uc", &bp); 494 must_use_uc = (UNDER_CHAR && !ENTER_UNDERLINE); 495} 496 497int 498outchar(int c) 499{ 500 return (putwchar(c) != WEOF ? c : EOF); 501} 502 503static int curmode = 0; 504 505void 506outc(wint_t c, int width) 507{ 508 int i; 509 510 putwchar(c); 511 if (must_use_uc && (curmode&UNDERL)) { 512 for (i = 0; i < width; i++) 513 PRINT(CURS_LEFT); 514 for (i = 0; i < width; i++) 515 PRINT(UNDER_CHAR); 516 } 517} 518 519void 520setnewmode(int newmode) 521{ 522 if (!iflag) { 523 if (curmode != NORMAL && newmode != NORMAL) 524 setnewmode(NORMAL); 525 switch (newmode) { 526 case NORMAL: 527 switch(curmode) { 528 case NORMAL: 529 break; 530 case UNDERL: 531 PRINT(EXIT_UNDERLINE); 532 break; 533 default: 534 /* This includes standout */ 535 PRINT(EXIT_ATTRIBUTES); 536 break; 537 } 538 break; 539 case ALTSET: 540 PRINT(ENTER_REVERSE); 541 break; 542 case SUPERSC: 543 /* 544 * This only works on a few terminals. 545 * It should be fixed. 546 */ 547 PRINT(ENTER_UNDERLINE); 548 PRINT(ENTER_DIM); 549 break; 550 case SUBSC: 551 PRINT(ENTER_DIM); 552 break; 553 case UNDERL: 554 PRINT(ENTER_UNDERLINE); 555 break; 556 case BOLD: 557 PRINT(ENTER_BOLD); 558 break; 559 default: 560 /* 561 * We should have some provision here for multiple modes 562 * on at once. This will have to come later. 563 */ 564 PRINT(ENTER_STANDOUT); 565 break; 566 } 567 } 568 curmode = newmode; 569} 570