// -*- C++ -*- /* Copyright (C) 1989-2000, 2001, 2002 Free Software Foundation, Inc. Written by James Clark (jjc@jclark.com) This file is part of groff. groff is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. groff is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with groff; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "driver.h" #include "device.h" extern "C" const char *Version_string; #define putstring(s) fputs(s, stdout) #ifndef SHRT_MIN #define SHRT_MIN (-32768) #endif #ifndef SHRT_MAX #define SHRT_MAX 32767 #endif #define TAB_WIDTH 8 static int horizontal_tab_flag = 0; static int form_feed_flag = 0; static int bold_flag = 1; static int underline_flag = 1; static int overstrike_flag = 1; static int draw_flag = 1; static int italic_flag = 0; static int old_drawing_scheme = 0; enum { UNDERLINE_MODE = 0x01, BOLD_MODE = 0x02, VDRAW_MODE = 0x04, HDRAW_MODE = 0x08, CU_MODE = 0x10, COLOR_CHANGE = 0x20 }; // Mode to use for bold-underlining. static unsigned char bold_underline_mode = BOLD_MODE|UNDERLINE_MODE; #ifndef IS_EBCDIC_HOST #define CSI "\033[" #else #define CSI "\047[" #endif // SGR handling (ISO 6429) #define SGR_BOLD CSI "1m" #define SGR_NO_BOLD CSI "22m" #define SGR_ITALIC CSI "3m" #define SGR_NO_ITALIC CSI "23m" #define SGR_UNDERLINE CSI "4m" #define SGR_NO_UNDERLINE CSI "24m" // many terminals can't handle `CSI 39 m' and `CSI 49 m' to reset // the foreground and bachground color, respectively; thus we use // `CSI 0 m' exclusively #define SGR_DEFAULT CSI "0m" #define TTY_MAX_COLORS 8 #define DEFAULT_COLOR_IDX TTY_MAX_COLORS class tty_font : public font { tty_font(const char *); unsigned char mode; public: ~tty_font(); unsigned char get_mode() { return mode; } #if 0 void handle_x_command(int argc, const char **argv); #endif static tty_font *load_tty_font(const char *); }; tty_font *tty_font::load_tty_font(const char *s) { tty_font *f = new tty_font(s); if (!f->load()) { delete f; return 0; } const char *num = f->get_internal_name(); long n; if (num != 0 && (n = strtol(num, 0, 0)) != 0) f->mode = int(n & (BOLD_MODE|UNDERLINE_MODE)); if (!underline_flag) f->mode &= ~UNDERLINE_MODE; if (!bold_flag) f->mode &= ~BOLD_MODE; if ((f->mode & (BOLD_MODE|UNDERLINE_MODE)) == (BOLD_MODE|UNDERLINE_MODE)) f->mode = (f->mode & ~(BOLD_MODE|UNDERLINE_MODE)) | bold_underline_mode; return f; } tty_font::tty_font(const char *nm) : font(nm), mode(0) { } tty_font::~tty_font() { } #if 0 void tty_font::handle_x_command(int argc, const char **argv) { if (argc >= 1 && strcmp(argv[0], "bold") == 0) mode |= BOLD_MODE; else if (argc >= 1 && strcmp(argv[0], "underline") == 0) mode |= UNDERLINE_MODE; } #endif class glyph { static glyph *free_list; public: glyph *next; short hpos; unsigned int code; unsigned char mode; unsigned char back_color_idx; unsigned char fore_color_idx; void *operator new(size_t); void operator delete(void *); inline int draw_mode() { return mode & (VDRAW_MODE|HDRAW_MODE); } inline int order() { return mode & (VDRAW_MODE|HDRAW_MODE|CU_MODE|COLOR_CHANGE); } }; glyph *glyph::free_list = 0; void *glyph::operator new(size_t) { if (!free_list) { const int BLOCK = 1024; free_list = (glyph *)new char[sizeof(glyph) * BLOCK]; for (int i = 0; i < BLOCK - 1; i++) free_list[i].next = free_list + i + 1; free_list[BLOCK - 1].next = 0; } glyph *p = free_list; free_list = free_list->next; p->next = 0; return p; } void glyph::operator delete(void *p) { if (p) { ((glyph *)p)->next = free_list; free_list = (glyph *)p; } } class tty_printer : public printer { int is_utf8; glyph **lines; int nlines; int cached_v; int cached_vpos; unsigned char curr_fore_idx; unsigned char curr_back_idx; int is_underline; int is_bold; int cu_flag; color tty_colors[TTY_MAX_COLORS]; void make_underline(); void make_bold(unsigned int); unsigned char color_to_idx(color *col); void add_char(unsigned int, int, int, color *, color *, unsigned char); public: tty_printer(const char *device); ~tty_printer(); void set_char(int, font *, const environment *, int, const char *name); void draw(int code, int *p, int np, const environment *env); void special(char *arg, const environment *env, char type); void change_color(const environment *env); void change_fill_color(const environment *env); void put_char(unsigned int); void put_color(unsigned char, int); void begin_page(int) { } void end_page(int page_length); font *make_font(const char *); }; tty_printer::tty_printer(const char *device) : cached_v(0) { is_utf8 = !strcmp(device, "utf8"); tty_colors[0].set_rgb(0, // black 0, 0); tty_colors[1].set_rgb(color::MAX_COLOR_VAL, // red 0, 0); tty_colors[2].set_rgb(0, // green color::MAX_COLOR_VAL, 0); tty_colors[3].set_rgb(color::MAX_COLOR_VAL, // yellow color::MAX_COLOR_VAL, 0); tty_colors[4].set_rgb(0, // blue 0, color::MAX_COLOR_VAL); tty_colors[5].set_rgb(color::MAX_COLOR_VAL, // magenta 0, color::MAX_COLOR_VAL); tty_colors[6].set_rgb(0, // cyan color::MAX_COLOR_VAL, color::MAX_COLOR_VAL); tty_colors[7].set_rgb(color::MAX_COLOR_VAL, // white color::MAX_COLOR_VAL, color::MAX_COLOR_VAL); nlines = 66; lines = new glyph *[nlines]; for (int i = 0; i < nlines; i++) lines[i] = 0; cu_flag = 0; } tty_printer::~tty_printer() { a_delete lines; } void tty_printer::make_underline() { if (old_drawing_scheme) { putchar('_'); putchar('\b'); } else { if (!is_underline) { if (italic_flag) putstring(SGR_ITALIC); else putstring(SGR_UNDERLINE); } is_underline = 1; } } void tty_printer::make_bold(unsigned int c) { if (old_drawing_scheme) { put_char(c); putchar('\b'); } else { if (!is_bold) putstring(SGR_BOLD); is_bold = 1; } } unsigned char tty_printer::color_to_idx(color *col) { if (col->is_default()) return DEFAULT_COLOR_IDX; for (int i = 0; i < TTY_MAX_COLORS; i++) if (*col == tty_colors[i]) return (unsigned char)i; unsigned r, g, b; col->get_rgb(&r, &g, &b); error("Unknown color (%1, %2, %3) mapped to default", r, g, b); return DEFAULT_COLOR_IDX; } void tty_printer::set_char(int i, font *f, const environment *env, int w, const char *name) { if (w != font::hor) fatal("width of character not equal to horizontal resolution"); add_char(f->get_code(i), env->hpos, env->vpos, env->col, env->fill, ((tty_font *)f)->get_mode()); } void tty_printer::add_char(unsigned int c, int h, int v, color *fore, color *back, unsigned char mode) { #if 0 // This is too expensive. if (h % font::hor != 0) fatal("horizontal position not a multiple of horizontal resolution"); #endif int hpos = h / font::hor; if (hpos < SHRT_MIN || hpos > SHRT_MAX) { error("character with ridiculous horizontal position discarded"); return; } int vpos; if (v == cached_v && cached_v != 0) vpos = cached_vpos; else { if (v % font::vert != 0) fatal("vertical position not a multiple of vertical resolution"); vpos = v / font::vert; if (vpos > nlines) { glyph **old_lines = lines; lines = new glyph *[vpos + 1]; memcpy(lines, old_lines, nlines * sizeof(glyph *)); for (int i = nlines; i <= vpos; i++) lines[i] = 0; a_delete old_lines; nlines = vpos + 1; } // Note that the first output line corresponds to groff // position font::vert. if (vpos <= 0) { error("character above first line discarded"); return; } cached_v = v; cached_vpos = vpos; } glyph *g = new glyph; g->hpos = hpos; g->code = c; g->fore_color_idx = color_to_idx(fore); g->back_color_idx = color_to_idx(back); g->mode = mode; // The list will be reversed later. After reversal, it must be in // increasing order of hpos, with COLOR_CHANGE and CU specials before // HDRAW characters before VDRAW characters before normal characters // at each hpos, and otherwise in order of occurrence. glyph **pp; for (pp = lines + (vpos - 1); *pp; pp = &(*pp)->next) if ((*pp)->hpos < hpos || ((*pp)->hpos == hpos && (*pp)->order() >= g->order())) break; g->next = *pp; *pp = g; } void tty_printer::special(char *arg, const environment *env, char type) { if (type == 'u') { add_char(*arg - '0', env->hpos, env->vpos, env->col, env->fill, CU_MODE); return; } if (type != 'p') return; char *p; for (p = arg; *p == ' ' || *p == '\n'; p++) ; char *tag = p; for (; *p != '\0' && *p != ':' && *p != ' ' && *p != '\n'; p++) ; if (*p == '\0' || strncmp(tag, "tty", p - tag) != 0) { error("X command without `tty:' tag ignored"); return; } p++; for (; *p == ' ' || *p == '\n'; p++) ; char *command = p; for (; *p != '\0' && *p != ' ' && *p != '\n'; p++) ; if (*command == '\0') { error("empty X command ignored"); return; } if (strncmp(command, "sgr", p - command) == 0) { for (; *p == ' ' || *p == '\n'; p++) ; int n; if (*p != '\0' && sscanf(p, "%d", &n) == 1 && n == 0) old_drawing_scheme = 1; else old_drawing_scheme = 0; } } void tty_printer::change_color(const environment *env) { add_char(0, env->hpos, env->vpos, env->col, env->fill, COLOR_CHANGE); } void tty_printer::change_fill_color(const environment *env) { add_char(0, env->hpos, env->vpos, env->col, env->fill, COLOR_CHANGE); } void tty_printer::draw(int code, int *p, int np, const environment *env) { if (code != 'l' || !draw_flag) return; if (np != 2) { error("2 arguments required for line"); return; } if (p[0] == 0) { // vertical line int v = env->vpos; int len = p[1]; if (len < 0) { v += len; len = -len; } while (len >= 0) { add_char('|', env->hpos, v, env->col, env->fill, VDRAW_MODE); len -= font::vert; v += font::vert; } } if (p[1] == 0) { // horizontal line int h = env->hpos; int len = p[0]; if (len < 0) { h += len; len = -len; } while (len >= 0) { add_char('-', h, env->vpos, env->col, env->fill, HDRAW_MODE); len -= font::hor; h += font::hor; } } } void tty_printer::put_char(unsigned int wc) { if (is_utf8 && wc >= 0x80) { char buf[6 + 1]; int count; char *p = buf; if (wc < 0x800) count = 1, *p = (unsigned char)((wc >> 6) | 0xc0); else if (wc < 0x10000) count = 2, *p = (unsigned char)((wc >> 12) | 0xe0); else if (wc < 0x200000) count = 3, *p = (unsigned char)((wc >> 18) | 0xf0); else if (wc < 0x4000000) count = 4, *p = (unsigned char)((wc >> 24) | 0xf8); else if (wc <= 0x7fffffff) count = 5, *p = (unsigned char)((wc >> 30) | 0xfC); else return; do *++p = (unsigned char)(((wc >> (6 * --count)) & 0x3f) | 0x80); while (count > 0); *++p = '\0'; putstring(buf); } else putchar(wc); } void tty_printer::put_color(unsigned char color_index, int back) { if (color_index == DEFAULT_COLOR_IDX) { putstring(SGR_DEFAULT); // set bold and underline again if (is_bold) putstring(SGR_BOLD); if (is_underline) { if (italic_flag) putstring(SGR_ITALIC); else putstring(SGR_UNDERLINE); } // set other color again back = !back; color_index = back ? curr_back_idx : curr_fore_idx; } putstring(CSI); if (back) putchar('4'); else putchar('3'); putchar(color_index + '0'); putchar('m'); } void tty_printer::end_page(int page_length) { if (page_length % font::vert != 0) error("vertical position at end of page not multiple of vertical resolution"); int lines_per_page = page_length / font::vert; int last_line; for (last_line = nlines; last_line > 0; last_line--) if (lines[last_line - 1]) break; #if 0 if (last_line > lines_per_page) { error("characters past last line discarded"); do { --last_line; while (lines[last_line]) { glyph *tem = lines[last_line]; lines[last_line] = tem->next; delete tem; } } while (last_line > lines_per_page); } #endif for (int i = 0; i < last_line; i++) { glyph *p = lines[i]; lines[i] = 0; glyph *g = 0; while (p) { glyph *tem = p->next; p->next = g; g = p; p = tem; } int hpos = 0; glyph *nextp; curr_fore_idx = DEFAULT_COLOR_IDX; curr_back_idx = DEFAULT_COLOR_IDX; is_underline = 0; is_bold = 0; for (p = g; p; delete p, p = nextp) { nextp = p->next; if (p->mode & CU_MODE) { cu_flag = p->code; continue; } if (nextp && p->hpos == nextp->hpos) { if (p->draw_mode() == HDRAW_MODE && nextp->draw_mode() == VDRAW_MODE) { nextp->code = '+'; continue; } if (p->draw_mode() != 0 && p->draw_mode() == nextp->draw_mode()) { nextp->code = p->code; continue; } if (!overstrike_flag) continue; } if (hpos > p->hpos) { do { putchar('\b'); hpos--; } while (hpos > p->hpos); } else { if (horizontal_tab_flag) { for (;;) { int next_tab_pos = ((hpos + TAB_WIDTH) / TAB_WIDTH) * TAB_WIDTH; if (next_tab_pos > p->hpos) break; if (cu_flag) make_underline(); else if (!old_drawing_scheme && is_underline) { if (italic_flag) putstring(SGR_NO_ITALIC); else putstring(SGR_NO_UNDERLINE); is_underline = 0; } putchar('\t'); hpos = next_tab_pos; } } for (; hpos < p->hpos; hpos++) { if (cu_flag) make_underline(); else if (!old_drawing_scheme && is_underline) { if (italic_flag) putstring(SGR_NO_ITALIC); else putstring(SGR_NO_UNDERLINE); is_underline = 0; } putchar(' '); } } assert(hpos == p->hpos); if (p->mode & COLOR_CHANGE) { if (!old_drawing_scheme) { if (p->fore_color_idx != curr_fore_idx) { put_color(p->fore_color_idx, 0); curr_fore_idx = p->fore_color_idx; } if (p->back_color_idx != curr_back_idx) { put_color(p->back_color_idx, 1); curr_back_idx = p->back_color_idx; } } continue; } if (p->mode & UNDERLINE_MODE) make_underline(); else if (!old_drawing_scheme && is_underline) { if (italic_flag) putstring(SGR_NO_ITALIC); else putstring(SGR_NO_UNDERLINE); is_underline = 0; } if (p->mode & BOLD_MODE) make_bold(p->code); else if (!old_drawing_scheme && is_bold) { putstring(SGR_NO_BOLD); is_bold = 0; } if (!old_drawing_scheme) { if (p->fore_color_idx != curr_fore_idx) { put_color(p->fore_color_idx, 0); curr_fore_idx = p->fore_color_idx; } if (p->back_color_idx != curr_back_idx) { put_color(p->back_color_idx, 1); curr_back_idx = p->back_color_idx; } } put_char(p->code); hpos++; } if (!old_drawing_scheme && (is_bold || is_underline || curr_fore_idx != DEFAULT_COLOR_IDX || curr_back_idx != DEFAULT_COLOR_IDX)) putstring(SGR_DEFAULT); putchar('\n'); } if (form_feed_flag) { if (last_line < lines_per_page) putchar('\f'); } else { for (; last_line < lines_per_page; last_line++) putchar('\n'); } } font *tty_printer::make_font(const char *nm) { return tty_font::load_tty_font(nm); } printer *make_printer() { return new tty_printer(device); } static void usage(FILE *stream); int main(int argc, char **argv) { program_name = argv[0]; static char stderr_buf[BUFSIZ]; if (getenv("GROFF_NO_SGR")) old_drawing_scheme = 1; setbuf(stderr, stderr_buf); int c; static const struct option long_options[] = { { "help", no_argument, 0, CHAR_MAX + 1 }, { "version", no_argument, 0, 'v' }, { NULL, 0, 0, 0 } }; while ((c = getopt_long(argc, argv, "F:vhfbciuoBUd", long_options, NULL)) != EOF) switch(c) { case 'v': printf("GNU grotty (groff) version %s\n", Version_string); exit(0); break; case 'i': // Use italic font instead of underlining. italic_flag = 1; break; case 'b': // Do not embolden by overstriking. bold_flag = 0; break; case 'c': // Use old scheme for emboldening and underline. old_drawing_scheme = 1; break; case 'u': // Do not underline. underline_flag = 0; break; case 'o': // Do not overstrike (other than emboldening and underlining). overstrike_flag = 0; break; case 'B': // Do bold-underlining as bold. bold_underline_mode = BOLD_MODE; break; case 'U': // Do bold-underlining as underlining. bold_underline_mode = UNDERLINE_MODE; break; case 'h': // Use horizontal tabs. horizontal_tab_flag = 1; break; case 'f': form_feed_flag = 1; break; case 'F': font::command_line_font_dir(optarg); break; case 'd': // Ignore \D commands. draw_flag = 0; break; case CHAR_MAX + 1: // --help usage(stdout); exit(0); break; case '?': usage(stderr); exit(1); break; default: assert(0); } if (old_drawing_scheme) italic_flag = 0; else { bold_underline_mode = BOLD_MODE|UNDERLINE_MODE; bold_flag = 1; underline_flag = 1; } if (optind >= argc) do_file("-"); else { for (int i = optind; i < argc; i++) do_file(argv[i]); } delete pr; return 0; } static void usage(FILE *stream) { fprintf(stream, "usage: %s [-hfvbciuodBU] [-F dir] [files ...]\n", program_name); }