/* * Copyright (c) 1980, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * modified by Kars de Jong * to use terminfo instead of termcap. * 1999-02-22 Arkadiusz Miƛkiewicz * added Native Language Support * 1999-09-19 Bruno Haible * modified to work correctly in multi-byte locales */ #include #include /* for getopt(), isatty() */ #include /* for memset(), strcpy() */ #include /* for getenv() */ #include /* for INT_MAX */ #include /* for signal() */ #include #include #if defined(HAVE_NCURSESW_TERM_H) # include #elif defined(HAVE_NCURSES_TERM_H) # include #elif defined(HAVE_TERM_H) # include #endif #include "nls.h" #include "xalloc.h" #include "widechar.h" #include "c.h" #include "closestream.h" #define ESC '\033' #define SO '\016' #define SI '\017' #define HFWD '9' #define HREV '8' #define FREV '7' enum { NORMAL_CHARSET = 0, /* Must be zero, see initbuf() */ ALTERNATIVE_CHARSET = 1 << 0, /* Reverse */ SUPERSCRIPT = 1 << 1, /* Dim */ SUBSCRIPT = 1 << 2, /* Dim | Ul */ UNDERLINE = 1 << 3, /* Ul */ BOLD = 1 << 4, /* Bold */ }; struct term_caps { char *curs_up; char *curs_right; char *curs_left; char *enter_standout; char *exit_standout; char *enter_underline; char *exit_underline; char *enter_dim; char *enter_bold; char *enter_reverse; char *under_char; char *exit_attributes; }; struct ul_char { wchar_t c_char; int c_width; char c_mode; }; struct ul_ctl { size_t column; size_t max_column; int half_position; int up_line; int mode; int current_mode; size_t buflen; struct ul_char *buf; unsigned int indicated_opt:1, must_use_uc:1, must_overstrike:1; }; static void __attribute__((__noreturn__)) usage(void) { FILE *out = stdout; fputs(USAGE_HEADER, out); fprintf(out, _(" %s [options] [ ...]\n"), program_invocation_short_name); fputs(USAGE_SEPARATOR, out); fputs(_("Do underlining.\n"), out); fputs(USAGE_OPTIONS, out); fputs(_(" -t, -T, --terminal TERMINAL override the TERM environment variable\n"), out); fputs(_(" -i, --indicated underlining is indicated via a separate line\n"), out); printf(USAGE_HELP_OPTIONS(30)); printf(USAGE_MAN_TAIL("ul(1)")); exit(EXIT_SUCCESS); } static void need_column(struct ul_ctl *ctl, size_t new_max) { ctl->max_column = new_max; while (new_max >= ctl->buflen) { ctl->buflen *= 2; ctl->buf = xrealloc(ctl->buf, sizeof(struct ul_char) * ctl->buflen); } } static void set_column(struct ul_ctl *ctl, size_t column) { ctl->column = column; if (ctl->max_column < ctl->column) need_column(ctl, ctl->column); } static void init_buffer(struct ul_ctl *ctl) { if (ctl->buf == NULL) { /* First time. */ ctl->buflen = BUFSIZ; ctl->buf = xcalloc(ctl->buflen, sizeof(struct ul_char)); } else /* assumes NORMAL_CHARSET == 0 */ memset(ctl->buf, 0, sizeof(struct ul_char) * ctl->max_column); set_column(ctl, 0); ctl->max_column = 0; ctl->mode &= ALTERNATIVE_CHARSET; } static void init_term_caps(struct ul_ctl *ctl, struct term_caps *const tcs) { tcs->curs_up = tigetstr("cuu1"); tcs->curs_right = tigetstr("cuf1"); tcs->curs_left = tigetstr("cub1"); if (tcs->curs_left == NULL) tcs->curs_left = "\b"; tcs->enter_standout = tigetstr("smso"); tcs->exit_standout = tigetstr("rmso"); tcs->enter_underline = tigetstr("smul"); tcs->exit_underline = tigetstr("rmul"); tcs->enter_dim = tigetstr("dim"); tcs->enter_bold = tigetstr("bold"); tcs->enter_reverse = tigetstr("rev"); tcs->exit_attributes = tigetstr("sgr0"); if (!tcs->enter_bold && tcs->enter_reverse) tcs->enter_bold = tcs->enter_reverse; if (!tcs->enter_bold && tcs->enter_standout) tcs->enter_bold = tcs->enter_standout; if (!tcs->enter_underline && tcs->enter_standout) { tcs->enter_underline = tcs->enter_standout; tcs->exit_underline = tcs->exit_standout; } if (!tcs->enter_dim && tcs->enter_standout) tcs->enter_dim = tcs->enter_standout; if (!tcs->enter_reverse && tcs->enter_standout) tcs->enter_reverse = tcs->enter_standout; if (!tcs->exit_attributes && tcs->exit_standout) tcs->exit_attributes = tcs->exit_standout; /* * Note that we use REVERSE for the alternate character set, * not the as/ae capabilities. This is because we are modeling * the model 37 teletype (since that's what nroff outputs) and * the typical as/ae is more of a graphics set, not the greek * letters the 37 has. */ tcs->under_char = tigetstr("uc"); ctl->must_use_uc = (tcs->under_char && !tcs->enter_underline); if ((tigetflag("os") && tcs->enter_bold == NULL) || (tigetflag("ul") && tcs->enter_underline == NULL && tcs->under_char == NULL)) ctl->must_overstrike = 1; } static void sig_handler(int signo __attribute__((__unused__))) { _exit(EXIT_SUCCESS); } static int ul_putwchar(int c) { if (putwchar(c) == WEOF) return EOF; return c; } static void print_line(char *line) { if (line == NULL) return; tputs(line, STDOUT_FILENO, ul_putwchar); } static void ul_setmode(struct ul_ctl *ctl, struct term_caps const *const tcs, int new_mode) { if (!ctl->indicated_opt) { if (ctl->current_mode != NORMAL_CHARSET && new_mode != NORMAL_CHARSET) ul_setmode(ctl, tcs, NORMAL_CHARSET); switch (new_mode) { case NORMAL_CHARSET: switch (ctl->current_mode) { case NORMAL_CHARSET: break; case UNDERLINE: print_line(tcs->exit_underline); break; default: /* This includes standout */ print_line(tcs->exit_attributes); break; } break; case ALTERNATIVE_CHARSET: print_line(tcs->enter_reverse); break; case SUPERSCRIPT: /* * This only works on a few terminals. * It should be fixed. */ print_line(tcs->enter_underline); print_line(tcs->enter_dim); break; case SUBSCRIPT: print_line(tcs->enter_dim); break; case UNDERLINE: print_line(tcs->enter_underline); break; case BOLD: print_line(tcs->enter_bold); break; default: /* * We should have some provision here for multiple modes * on at once. This will have to come later. */ print_line(tcs->enter_standout); break; } } ctl->current_mode = new_mode; } static void indicate_attribute(struct ul_ctl *ctl) { size_t i; wchar_t *buf = xcalloc(ctl->max_column + 1, sizeof(wchar_t)); wchar_t *p = buf; for (i = 0; i < ctl->max_column; i++) { switch (ctl->buf[i].c_mode) { case NORMAL_CHARSET: *p++ = ' '; break; case ALTERNATIVE_CHARSET: *p++ = 'g'; break; case SUPERSCRIPT: *p++ = '^'; break; case SUBSCRIPT: *p++ = 'v'; break; case UNDERLINE: *p++ = '_'; break; case BOLD: *p++ = '!'; break; default: *p++ = 'X'; break; } } for (*p = ' '; *p == ' '; p--) *p = 0; fputws(buf, stdout); putwchar('\n'); free(buf); } static void output_char(struct ul_ctl *ctl, struct term_caps const *const tcs, wint_t c, int width) { int i; putwchar(c); if (ctl->must_use_uc && (ctl->current_mode & UNDERLINE)) { for (i = 0; i < width; i++) print_line(tcs->curs_left); for (i = 0; i < width; i++) print_line(tcs->under_char); } } /* * For terminals that can overstrike, overstrike underlines and bolds. * We don't do anything with halfline ups and downs, or Greek. */ static void overstrike(struct ul_ctl *ctl) { size_t i; wchar_t *buf = xcalloc(ctl->max_column + 1, sizeof(wchar_t)); wchar_t *p = buf; int had_bold = 0; /* Set up overstrike buffer */ for (i = 0; i < ctl->max_column; i++) { switch (ctl->buf[i].c_mode) { case NORMAL_CHARSET: default: *p++ = ' '; break; case UNDERLINE: *p++ = '_'; break; case BOLD: *p++ = ctl->buf[i].c_char; if (1 < ctl->buf[i].c_width) i += ctl->buf[i].c_width - 1; had_bold = 1; break; } } putwchar('\r'); for (*p = ' '; *p == ' '; p--) *p = 0; fputws(buf, stdout); if (had_bold) { putwchar('\r'); for (p = buf; *p; p++) putwchar(*p == '_' ? ' ' : *p); putwchar('\r'); for (p = buf; *p; p++) putwchar(*p == '_' ? ' ' : *p); } free(buf); } static void flush_line(struct ul_ctl *ctl, struct term_caps const *const tcs) { int last_mode; size_t i; int had_mode = 0; last_mode = NORMAL_CHARSET; for (i = 0; i < ctl->max_column; i++) { if (ctl->buf[i].c_mode != last_mode) { had_mode = 1; ul_setmode(ctl, tcs, ctl->buf[i].c_mode); last_mode = ctl->buf[i].c_mode; } if (ctl->buf[i].c_char == '\0') { if (ctl->up_line) print_line(tcs->curs_right); else output_char(ctl, tcs, ' ', 1); } else output_char(ctl, tcs, ctl->buf[i].c_char, ctl->buf[i].c_width); if (1 < ctl->buf[i].c_width) i += ctl->buf[i].c_width - 1; } if (last_mode != NORMAL_CHARSET) ul_setmode(ctl, tcs, NORMAL_CHARSET); if (ctl->must_overstrike && had_mode) overstrike(ctl); putwchar('\n'); if (ctl->indicated_opt && had_mode) indicate_attribute(ctl); fflush(stdout); if (ctl->up_line) ctl->up_line--; init_buffer(ctl); } static void forward(struct ul_ctl *ctl, struct term_caps const *const tcs) { int old_column, old_maximum; old_column = ctl->column; old_maximum = ctl->max_column; flush_line(ctl, tcs); set_column(ctl, old_column); ctl->max_column = old_maximum; } static void reverse(struct ul_ctl *ctl, struct term_caps const *const tcs) { ctl->up_line++; forward(ctl, tcs); print_line(tcs->curs_up); print_line(tcs->curs_up); ctl->up_line++; } static int handle_escape(struct ul_ctl *ctl, struct term_caps const *const tcs, FILE *f) { wint_t c; switch (c = getwc(f)) { case HREV: if (0 < ctl->half_position) { ctl->mode &= ~SUBSCRIPT; ctl->half_position--; } else if (ctl->half_position == 0) { ctl->mode |= SUPERSCRIPT; ctl->half_position--; } else { ctl->half_position = 0; reverse(ctl, tcs); } return 0; case HFWD: if (ctl->half_position < 0) { ctl->mode &= ~SUPERSCRIPT; ctl->half_position++; } else if (ctl->half_position == 0) { ctl->mode |= SUBSCRIPT; ctl->half_position++; } else { ctl->half_position = 0; forward(ctl, tcs); } return 0; case FREV: reverse(ctl, tcs); return 0; default: /* unknown escape */ ungetwc(c, f); return 1; } } static void filter(struct ul_ctl *ctl, struct term_caps const *const tcs, FILE *f) { wint_t c; int i, width; while ((c = getwc(f)) != WEOF) { switch (c) { case '\b': set_column(ctl, ctl->column && 0 < ctl->column ? ctl->column - 1 : 0); continue; case '\t': set_column(ctl, (ctl->column + 8) & ~07); continue; case '\r': set_column(ctl, 0); continue; case SO: ctl->mode |= ALTERNATIVE_CHARSET; continue; case SI: ctl->mode &= ~ALTERNATIVE_CHARSET; continue; case ESC: if (handle_escape(ctl, tcs, f)) { c = getwc(f); errx(EXIT_FAILURE, _("unknown escape sequence in input: %o, %o"), ESC, c); } continue; case '_': if (ctl->buf[ctl->column].c_char || ctl->buf[ctl->column].c_width < 0) { while (ctl->buf[ctl->column].c_width < 0 && 0 < ctl->column) ctl->column--; width = ctl->buf[ctl->column].c_width; for (i = 0; i < width; i++) ctl->buf[ctl->column++].c_mode |= UNDERLINE | ctl->mode; set_column(ctl, 0 < ctl->column ? ctl->column : 0); continue; } ctl->buf[ctl->column].c_char = '_'; ctl->buf[ctl->column].c_width = 1; /* fallthrough */ case ' ': set_column(ctl, ctl->column + 1); continue; case '\n': flush_line(ctl, tcs); continue; case '\f': flush_line(ctl, tcs); putwchar('\f'); continue; default: if (!iswprint(c)) /* non printable */ continue; width = wcwidth(c); need_column(ctl, ctl->column + width); if (ctl->buf[ctl->column].c_char == '\0') { ctl->buf[ctl->column].c_char = c; for (i = 0; i < width; i++) ctl->buf[ctl->column + i].c_mode = ctl->mode; ctl->buf[ctl->column].c_width = width; for (i = 1; i < width; i++) ctl->buf[ctl->column + i].c_width = -1; } else if (ctl->buf[ctl->column].c_char == '_') { ctl->buf[ctl->column].c_char = c; for (i = 0; i < width; i++) ctl->buf[ctl->column + i].c_mode |= UNDERLINE | ctl->mode; ctl->buf[ctl->column].c_width = width; for (i = 1; i < width; i++) ctl->buf[ctl->column + i].c_width = -1; } else if ((wint_t) ctl->buf[ctl->column].c_char == c) { for (i = 0; i < width; i++) ctl->buf[ctl->column + i].c_mode |= BOLD | ctl->mode; } else { width = ctl->buf[ctl->column].c_width; for (i = 0; i < width; i++) ctl->buf[ctl->column + i].c_mode = ctl->mode; } set_column(ctl, ctl->column + width); continue; } } if (ctl->max_column) flush_line(ctl, tcs); } int main(int argc, char **argv) { int c, ret, opt_terminal = 0; char *termtype; struct term_caps tcs = { 0 }; struct ul_ctl ctl = { .current_mode = NORMAL_CHARSET }; FILE *f; static const struct option longopts[] = { { "terminal", required_argument, NULL, 't' }, { "indicated", no_argument, NULL, 'i' }, { "version", no_argument, NULL, 'V' }, { "help", no_argument, NULL, 'h' }, { NULL, 0, NULL, 0 } }; setlocale(LC_ALL, ""); bindtextdomain(PACKAGE, LOCALEDIR); textdomain(PACKAGE); close_stdout_atexit(); signal(SIGINT, sig_handler); signal(SIGTERM, sig_handler); termtype = getenv("TERM"); while ((c = getopt_long(argc, argv, "it:T:Vh", longopts, NULL)) != -1) { switch (c) { case 't': case 'T': /* for nroff compatibility */ termtype = optarg; opt_terminal = 1; break; case 'i': ctl.indicated_opt = 1; break; case 'V': print_version(EXIT_SUCCESS); case 'h': usage(); default: errtryhelp(EXIT_FAILURE); } } setupterm(termtype, STDOUT_FILENO, &ret); switch (ret) { case 1: break; default: warnx(_("trouble reading terminfo")); /* fallthrough */ case 0: if (opt_terminal) warnx(_("terminal `%s' is not known, defaulting to `dumb'"), termtype); setupterm("dumb", STDOUT_FILENO, (int *)0); break; } init_term_caps(&ctl, &tcs); init_buffer(&ctl); if (optind == argc) filter(&ctl, &tcs, stdin); else { for (; optind < argc; optind++) { f = fopen(argv[optind], "r"); if (!f) err(EXIT_FAILURE, _("cannot open %s"), argv[optind]); filter(&ctl, &tcs, f); fclose(f); } } free(ctl.buf); del_curterm(cur_term); return EXIT_SUCCESS; }