ul(1): Sync with FreeBSD (mainly for multibyte char support).
[dragonfly.git] / usr.bin / ul / ul.c
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 }