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