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