diff options
Diffstat (limited to 'text-utils/ul.c')
-rw-r--r-- | text-utils/ul.c | 646 |
1 files changed, 646 insertions, 0 deletions
diff --git a/text-utils/ul.c b/text-utils/ul.c new file mode 100644 index 0000000..d5bca45 --- /dev/null +++ b/text-utils/ul.c @@ -0,0 +1,646 @@ +/* + * 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 <jongk@cs.utwente.nl> + * to use terminfo instead of termcap. + * 1999-02-22 Arkadiusz MiĆkiewicz <misiek@pld.ORG.PL> + * added Native Language Support + * 1999-09-19 Bruno Haible <haible@clisp.cons.org> + * modified to work correctly in multi-byte locales + */ + +#include <stdio.h> +#include <unistd.h> /* for getopt(), isatty() */ +#include <string.h> /* for memset(), strcpy() */ +#include <stdlib.h> /* for getenv() */ +#include <limits.h> /* for INT_MAX */ +#include <signal.h> /* for signal() */ +#include <errno.h> +#include <getopt.h> + +#if defined(HAVE_NCURSESW_TERM_H) +# include <ncursesw/term.h> +#elif defined(HAVE_NCURSES_TERM_H) +# include <ncurses/term.h> +#elif defined(HAVE_TERM_H) +# include <term.h> +#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] [<file> ...]\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; +} |