LCOV - code coverage report
Current view: top level - devices/grotty - tty.cpp (source / functions) Hit Total Coverage
Test: GNU roff Lines: 400 592 67.6 %
Date: 2026-01-16 17:51:41 Functions: 37 43 86.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* Copyright 1989-2025 Free Software Foundation, Inc.
       2             :      Written by James Clark (jjc@jclark.com)
       3             :      OSC 8 support by G. Branden Robinson
       4             : 
       5             : This file is part of groff, the GNU roff typesetting system.
       6             : 
       7             : groff is free software; you can redistribute it and/or modify it under
       8             : the terms of the GNU General Public License as published by the Free
       9             : Software Foundation, either version 3 of the License, or
      10             : (at your option) any later version.
      11             : 
      12             : groff is distributed in the hope that it will be useful, but WITHOUT ANY
      13             : WARRANTY; without even the implied warranty of MERCHANTABILITY or
      14             : FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
      15             : for more details.
      16             : 
      17             : You should have received a copy of the GNU General Public License
      18             : along with this program.  If not, see <http://www.gnu.org/licenses/>. */
      19             : 
      20             : #ifdef HAVE_CONFIG_H
      21             : #include <config.h>
      22             : #endif
      23             : 
      24             : #include <limits.h> // CHAR_MAX
      25             : #include <locale.h> // setlocale()
      26             : #include <stdio.h> // EOF, FILE, fprintf(), fputs(), printf(),
      27             :                    // putchar(), setbuf(), stderr, stdout
      28             : #include <stdlib.h> // exit(), EXIT_SUCCESS, getenv(), strtol()
      29             : 
      30             : #include <getopt.h> // getopt_long()
      31             : 
      32             : #include "device.h"
      33             : #include "driver.h"
      34             : #include "ptable.h"
      35             : 
      36             : typedef signed char schar;
      37             : 
      38             : declare_ptable(schar)
      39      351168 : implement_ptable(schar)
      40             : 
      41             : extern "C" const char *Version_string;
      42             : 
      43             : #define putstring(s) fputs(s, stdout)
      44             : 
      45             : #ifndef SHRT_MIN
      46             : #define SHRT_MIN (-32768)
      47             : #endif
      48             : 
      49             : #ifndef SHRT_MAX
      50             : #define SHRT_MAX 32767
      51             : #endif
      52             : 
      53             : #define TAB_WIDTH 8
      54             : 
      55             : // A character of the output device fits in a 32-bit word.
      56             : typedef unsigned int output_character;
      57             : 
      58             : static bool want_horizontal_tabs = false;
      59             : static bool want_form_feeds = false;
      60             : static bool want_emboldening_by_overstriking = true;
      61             : static bool do_bold;
      62             : static bool want_italics_by_underlining = true;
      63             : static bool do_underline;
      64             : static bool want_glyph_composition_by_overstriking = true;
      65             : static bool allow_drawing_commands = true;
      66             : static bool want_sgr_italics = false;
      67             : static bool do_sgr_italics;
      68             : static bool want_reverse_video_for_italics = false;
      69             : static bool do_reverse_video;
      70             : static bool use_overstriking_drawing_scheme = false;
      71             : static bool want_sgr_truecolor = false;
      72             : static void update_options();
      73             : static void usage(FILE *stream);
      74             : 
      75             : static int hline_char = '-';
      76             : static int vline_char = '|';
      77             : 
      78             : enum {
      79             :   UNDERLINE_MODE = 0x01,
      80             :   BOLD_MODE = 0x02,
      81             :   VDRAW_MODE = 0x04,
      82             :   HDRAW_MODE = 0x08,
      83             :   CU_MODE = 0x10,
      84             :   COLOR_CHANGE = 0x20,
      85             :   START_LINE = 0x40,
      86             :   END_LINE = 0x80
      87             : };
      88             : 
      89             : // Mode to use for bold-underlining.
      90             : static unsigned char bold_underline_mode_option = BOLD_MODE|UNDERLINE_MODE;
      91             : static unsigned char bold_underline_mode;
      92             : 
      93             : #define CSI "\033["
      94             : #define OSC8 "\033]8"
      95             : #define ST "\033\\"
      96             : 
      97             : // SGR handling (ISO 6429)
      98             : #define SGR_BOLD CSI "1m"
      99             : #define SGR_NO_BOLD CSI "22m"
     100             : #define SGR_ITALIC CSI "3m"
     101             : #define SGR_NO_ITALIC CSI "23m"
     102             : #define SGR_UNDERLINE CSI "4m"
     103             : #define SGR_NO_UNDERLINE CSI "24m"
     104             : #define SGR_REVERSE CSI "7m"
     105             : #define SGR_NO_REVERSE CSI "27m"
     106             : // many terminals can't handle 'CSI 39 m' and 'CSI 49 m' to reset
     107             : // the foreground and background color, respectively; we thus use
     108             : // 'CSI 0 m' exclusively
     109             : #define SGR_DEFAULT CSI "0m"
     110             : 
     111             : const int DEFAULT_COLOR_IDX = -1;
     112             : 
     113             : class tty_font : public font {
     114             :   tty_font(const char *);
     115             :   unsigned char mode;
     116             : public:
     117             :   ~tty_font();
     118     1040805 :   unsigned char get_mode() { return mode; }
     119             : #if 0
     120             :   void handle_x_command(int argc, const char **argv);
     121             : #endif
     122             :   static tty_font *load_tty_font(const char *);
     123             : };
     124             : 
     125        1300 : tty_font *tty_font::load_tty_font(const char *s)
     126             : {
     127        1300 :   tty_font *f = new tty_font(s);
     128        1300 :   if (!f->load()) {
     129           0 :     delete f;
     130           0 :     return 0;
     131             :   }
     132        1300 :   const char *num = f->get_internal_name();
     133             :   long n;
     134        1300 :   if (num != 0 && (n = strtol(num, 0, 0)) != 0)
     135         363 :     f->mode = (unsigned char)(n & (BOLD_MODE|UNDERLINE_MODE));
     136        1300 :   if (!do_underline)
     137         725 :     f->mode &= ~UNDERLINE_MODE;
     138        1300 :   if (!do_bold)
     139         725 :     f->mode &= ~BOLD_MODE;
     140        1300 :   if ((f->mode & (BOLD_MODE|UNDERLINE_MODE)) == (BOLD_MODE|UNDERLINE_MODE))
     141           3 :     f->mode = (unsigned char)((f->mode & ~(BOLD_MODE|UNDERLINE_MODE))
     142           3 :                               | bold_underline_mode);
     143        1300 :   return f;
     144             : }
     145             : 
     146        1300 : tty_font::tty_font(const char *nm)
     147        1300 : : font(nm), mode(0)
     148             : {
     149        1300 : }
     150             : 
     151        2600 : tty_font::~tty_font()
     152             : {
     153        2600 : }
     154             : 
     155             : #if 0
     156             : void tty_font::handle_x_command(int argc, const char **argv)
     157             : {
     158             :   if (argc >= 1 && strcmp(argv[0], "bold") == 0)
     159             :     mode |= BOLD_MODE;
     160             :   else if (argc >= 1 && strcmp(argv[0], "underline") == 0)
     161             :     mode |= UNDERLINE_MODE;
     162             : }
     163             : #endif
     164             : 
     165             : class tty_glyph {
     166             : public:
     167             :   tty_glyph *next;
     168             :   int w;
     169             :   int hpos;
     170             :   unsigned int code;
     171             :   unsigned char mode;
     172             :   long back_color_idx;
     173             :   long fore_color_idx;
     174      108492 :   inline int draw_mode() { return mode & (VDRAW_MODE|HDRAW_MODE); }
     175       71956 :   inline int order() {
     176       71956 :     return mode & (VDRAW_MODE|HDRAW_MODE|CU_MODE|COLOR_CHANGE); }
     177             : };
     178             : 
     179             : 
     180             : class tty_printer : public printer {
     181             :   tty_glyph **lines;
     182             :   int nlines;
     183             :   int cached_v;
     184             :   int cached_vpos;
     185             :   long curr_fore_idx;
     186             :   long curr_back_idx;
     187             :   bool is_underlining;
     188             :   bool is_boldfacing;
     189             :   bool is_continuously_underlining;
     190             :   PTABLE(schar) tty_colors;
     191             :   void make_underline(int);
     192             :   void make_bold(output_character, int);
     193             :   long color_to_idx(color *);
     194             :   void add_char(output_character, int, int, int, color *, color *,
     195             :                 unsigned char);
     196             :   void simple_add_char(const output_character, const environment *);
     197             :   char *make_rgb_string(unsigned int, unsigned int, unsigned int);
     198             :   bool has_color(unsigned int, unsigned int, unsigned int, long *,
     199             :                  schar = DEFAULT_COLOR_IDX);
     200             :   void line(int, int, int, int, color *, color *);
     201             :   void draw_line(int *, int, const environment *);
     202             :   void draw_polygon(int *, int, const environment *);
     203             :   void special_link(const char *, const environment *);
     204             : public:
     205             :   tty_printer();
     206             :   ~tty_printer();
     207             :   void set_char(glyph *, font *, const environment *, int, const char *);
     208             :   void draw(int, int *, int, const environment *);
     209             :   void special(char *, const environment *, char);
     210             :   void change_color(const environment * const);
     211             :   void change_fill_color(const environment * const);
     212             :   void put_char(output_character);
     213             :   void put_color(long, int);
     214        1058 :   void begin_page(int) { }
     215             :   void end_page(int);
     216             :   font *make_font(const char *);
     217             : };
     218             : 
     219        7552 : char *tty_printer::make_rgb_string(unsigned int r,
     220             :                                    unsigned int g,
     221             :                                    unsigned int b)
     222             : {
     223        7552 :   char *s = new char[8];
     224        7552 :   s[0] = char(r >> 8);
     225        7552 :   s[1] = char(r & 0xff);
     226        7552 :   s[2] = char(g >> 8);
     227        7552 :   s[3] = char(g & 0xff);
     228        7552 :   s[4] = char(b >> 8);
     229        7552 :   s[5] = char(b & 0xff);
     230        7552 :   s[6] = char(0x80);
     231        7552 :   s[7] = 0;
     232             :   // avoid null-bytes in string
     233       52864 :   for (int i = 0; i < 6; i++)
     234       45312 :     if (!s[i]) {
     235       22656 :       s[i] = 1;
     236       22656 :       s[6] |= 1 << i;
     237             :     }
     238        7552 :   return s;
     239             : }
     240             : 
     241        7552 : bool tty_printer::has_color(unsigned int r,
     242             :                             unsigned int g,
     243             :                             unsigned int b, long *idx, schar value)
     244             : {
     245        7552 :   bool is_known_color = true;
     246        7552 :   if (!want_sgr_truecolor) {
     247        7552 :     char *s = make_rgb_string(r, g, b);
     248        7552 :     schar *i = tty_colors.lookup(s);
     249        7552 :     if (0 /* nullptr */ == i) {
     250        7552 :       is_known_color = false;
     251        7552 :       i = new schar[1];
     252        7552 :       *i = value;
     253        7552 :       tty_colors.define(s, i);
     254             :     }
     255        7552 :     *idx = *i;
     256        7552 :     delete[] s;
     257             :   }
     258             :   else {
     259           0 :     *idx=((r>>8)<<16) + ((g>>8)<<8) + (b>>8);
     260             :   }
     261        7552 :   return is_known_color;
     262             : }
     263             : 
     264         944 : tty_printer::tty_printer() : cached_v(0)
     265             : {
     266         944 :   if (font::is_unicode) {
     267          31 :     hline_char = 0x2500;
     268          31 :     vline_char = 0x2502;
     269             :   }
     270             :   // TODO: Skip color setup if terminfo `colors` capability is "-1".
     271             :   long dummy;
     272             :   // Create the eight ANSI X3.64/ECMA-48/ISO 6429 standard colors.
     273             :   // black, white
     274         944 :   (void) has_color(0, 0, 0, &dummy, 0);
     275         944 :   (void) has_color(color::MAX_COLOR_VAL,
     276             :                    color::MAX_COLOR_VAL,
     277             :                    color::MAX_COLOR_VAL, &dummy, 7);
     278             :   // red, green, blue
     279         944 :   (void) has_color(color::MAX_COLOR_VAL, 0, 0, &dummy, 1);
     280         944 :   (void) has_color(0, color::MAX_COLOR_VAL, 0, &dummy, 2);
     281         944 :   (void) has_color(0, 0, color::MAX_COLOR_VAL, &dummy, 4);
     282             :   // yellow, magenta, cyan
     283         944 :   (void) has_color(color::MAX_COLOR_VAL, color::MAX_COLOR_VAL, 0,
     284             :                    &dummy, 3);
     285         944 :   (void) has_color(color::MAX_COLOR_VAL, 0, color::MAX_COLOR_VAL,
     286             :                    &dummy, 5);
     287         944 :   (void) has_color(0, color::MAX_COLOR_VAL, color::MAX_COLOR_VAL,
     288             :                    &dummy, 6);
     289         944 :   nlines = 66;
     290         944 :   lines = new tty_glyph *[nlines];
     291       63248 :   for (int i = 0; i < nlines; i++)
     292       62304 :     lines[i] = 0;
     293         944 :   is_continuously_underlining = false;
     294         944 : }
     295             : 
     296        1888 : tty_printer::~tty_printer()
     297             : {
     298         944 :   current_lineno = 0; // At this point, we've read all the input.
     299         944 :   delete[] lines;
     300        1888 : }
     301             : 
     302       74987 : void tty_printer::make_underline(int w)
     303             : {
     304       74987 :   if (use_overstriking_drawing_scheme) {
     305           0 :     if (!w)
     306           0 :       warning("can't underline zero-width character");
     307             :     else {
     308           0 :       putchar('_');
     309           0 :       putchar('\b');
     310             :     }
     311             :   }
     312             :   else {
     313       74987 :     if (!is_underlining) {
     314       12013 :       if (do_sgr_italics)
     315           0 :         putstring(SGR_ITALIC);
     316       12013 :       else if (do_reverse_video)
     317           0 :         putstring(SGR_REVERSE);
     318             :       else
     319       12013 :         putstring(SGR_UNDERLINE);
     320             :     }
     321       74987 :     is_underlining = true;
     322             :   }
     323       74987 : }
     324             : 
     325       57715 : void tty_printer::make_bold(output_character c, int w)
     326             : {
     327       57715 :   if (use_overstriking_drawing_scheme) {
     328           0 :     if (!w)
     329           0 :       warning("can't print zero-width character in bold");
     330             :     else {
     331           0 :       put_char(c);
     332           0 :       putchar('\b');
     333             :     }
     334             :   }
     335             :   else {
     336       57715 :     if (!is_boldfacing)
     337       12279 :       putstring(SGR_BOLD);
     338       57715 :     is_boldfacing = true;
     339             :   }
     340       57715 : }
     341             : 
     342     2185952 : long tty_printer::color_to_idx(color *col)
     343             : {
     344     2185952 :   if (col->is_default())
     345     2185952 :     return DEFAULT_COLOR_IDX;
     346             :   unsigned int r, g, b;
     347           0 :   col->get_rgb(&r, &g, &b);
     348             :   long idx;
     349           0 :   if (!has_color(r, g, b, &idx)) {
     350           0 :     char *s = col->print_color();
     351           0 :     error("unsupported color '%1' mapped to default", s);
     352           0 :     delete[] s;
     353             :   }
     354           0 :   return idx;
     355             : }
     356             : 
     357     1040805 : void tty_printer::set_char(glyph *g, font *f, const environment *env,
     358             :                            int w, const char *)
     359             : {
     360     1040805 :   if (w % font::hor != 0)
     361           0 :     fatal("glyph width is not a multiple of horizontal motion quantum");
     362     1040805 :   add_char(f->get_code(g), w,
     363     1040805 :            env->hpos, env->vpos,
     364     1040805 :            env->col, env->fill,
     365     1040805 :            ((tty_font *)f)->get_mode());
     366     1040805 : }
     367             : 
     368     1092976 : void tty_printer::add_char(output_character c, int w,
     369             :                            int h, int v,
     370             :                            color *fore, color *back,
     371             :                            unsigned char mode)
     372             : {
     373             : #if 0
     374             :   // This is too expensive.
     375             :   if (h % font::hor != 0)
     376             :     fatal("horizontal position not a multiple of horizontal motion quantum");
     377             : #endif
     378     1092976 :   int hpos = h / font::hor;
     379     1092976 :   if (hpos < SHRT_MIN || hpos > SHRT_MAX) {
     380           0 :     error("character with ridiculous horizontal position discarded");
     381           0 :     return;
     382             :   }
     383             :   int vpos;
     384     1092976 :   if (v == cached_v && cached_v != 0)
     385     1065345 :     vpos = cached_vpos;
     386             :   else {
     387       27631 :     if (v % font::vert != 0)
     388           0 :       fatal("vertical position not a multiple of vertical motion"
     389             :             " quantum");
     390       27631 :     vpos = v / font::vert;
     391       27631 :     if (vpos > nlines) {
     392       10196 :       tty_glyph **old_lines = lines;
     393       10196 :       lines = new tty_glyph *[vpos + 1];
     394       10196 :       memcpy(lines, old_lines, nlines * sizeof(tty_glyph *));
     395       32493 :       for (int i = nlines; i <= vpos; i++)
     396       22297 :         lines[i] = 0;
     397       10196 :       delete[] old_lines;
     398       10196 :       nlines = vpos + 1;
     399             :     }
     400             :     // Note that the first output line corresponds to groff
     401             :     // position font::vert.
     402       27631 :     if (vpos <= 0) {
     403           0 :       error("output above first line discarded");
     404           0 :       return;
     405             :     }
     406       27631 :     cached_v = v;
     407       27631 :     cached_vpos = vpos;
     408             :   }
     409     1092976 :   tty_glyph *g = new tty_glyph;
     410     1092976 :   g->w = w;
     411     1092976 :   g->hpos = hpos;
     412     1092976 :   g->code = c;
     413     1092976 :   g->fore_color_idx = color_to_idx(fore);
     414     1092976 :   g->back_color_idx = color_to_idx(back);
     415     1092976 :   g->mode = mode;
     416             : 
     417             :   // The list will be reversed later.  After reversal, it must be in
     418             :   // increasing order of hpos, with COLOR_CHANGE and CU specials before
     419             :   // HDRAW characters before VDRAW characters before normal characters
     420             :   // at each hpos, and otherwise in order of occurrence.
     421             : 
     422             :   tty_glyph **pp;
     423     1106073 :   for (pp = lines + (vpos - 1); *pp; pp = &(*pp)->next)
     424     2158102 :     if ((*pp)->hpos < hpos
     425     1079051 :         || ((*pp)->hpos == hpos && (*pp)->order() >= g->order()))
     426     1065954 :       break;
     427     1092976 :   g->next = *pp;
     428     1092976 :   *pp = g;
     429             : }
     430             : 
     431       34044 : void tty_printer::simple_add_char(const output_character c,
     432             :                                   const environment *env)
     433             : {
     434       34044 :   add_char(c, 0, env->hpos, env->vpos, env->col, env->fill, 0);
     435       34044 : }
     436             : 
     437        2375 : void tty_printer::special(char *arg, const environment *env, char type)
     438             : {
     439        2375 :   if (type == 'u') {
     440           0 :     add_char(*arg - '0', 0, env->hpos, env->vpos, env->col, env->fill,
     441             :              CU_MODE);
     442           0 :     return;
     443             :   }
     444        2375 :   if (type != 'p')
     445           0 :     return;
     446             :   char *p;
     447        2375 :   for (p = arg; *p == ' ' || *p == '\n'; p++)
     448             :     ;
     449        2375 :   char *tag = p;
     450        9500 :   for (; *p != '\0' && *p != ':' && *p != ' ' && *p != '\n'; p++)
     451             :     ;
     452        2375 :   if (*p == '\0' || strncmp(tag, "tty", p - tag) != 0) {
     453           0 :     *p = '\0'; // terminate string at colon
     454           0 :     error("X command with '%1' tag ignored; expected 'tty'", tag);
     455           0 :     return;
     456             :   }
     457        2375 :   p++;
     458        4747 :   for (; *p == ' ' || *p == '\n'; p++)
     459             :     ;
     460        2375 :   char *command = p;
     461       11863 :   for (; *p != '\0' && *p != ' ' && *p != '\n'; p++)
     462             :     ;
     463        2375 :   if (*command == '\0') {
     464           3 :     error("empty X command ignored");
     465           3 :     return;
     466             :   }
     467        2372 :   if (strncmp(command, "link", p - command) == 0)
     468        2372 :     special_link(p, env);
     469             :   else
     470           0 :     warning("unrecognized X command '%1' ignored", command);
     471             : }
     472             : 
     473             : // Produce an OSC 8 hyperlink.  Given ditroff input of the form:
     474             : //   x X tty: link [URI[ KEY=VALUE] ...]
     475             : // produce "OSC 8 [;KEY=VALUE];[URI] ST".  KEY/VALUE pairs can be
     476             : // repeated arbitrarily and are separated by colons.  Omission of the
     477             : // URI ends the hyperlink that was begun by specifying it.  See
     478             : // <https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda>.
     479        2372 : void tty_printer::special_link(const char *arg, const environment *env)
     480             : {
     481             :   static bool is_link_active = false;
     482        2372 :   if (use_overstriking_drawing_scheme)
     483          64 :     return;
     484        9232 :   for (const char *s = OSC8; *s != '\0'; s++)
     485        6924 :     simple_add_char(*s, env);
     486        2308 :   simple_add_char(';', env);
     487        2308 :   char c = *arg;
     488        2308 :   if ('\0' == c || '\n' == c) {
     489        1154 :     simple_add_char(';', env);
     490        1154 :     if (!is_link_active)
     491           1 :       warning("ending hyperlink when none was started");
     492        1154 :     is_link_active = false;
     493             :   }
     494             :   else {
     495             :     // Our caller ensures that we see whitespace after 'link'.
     496        1154 :     assert((' ' == c) || ('\t' == c));
     497        1154 :     if (is_link_active) {
     498           1 :       warning("new hyperlink started without ending previous one;"
     499             :               " recovering");
     500           1 :       simple_add_char(';', env);
     501           6 :         for (const char *s = ST OSC8; *s != '\0'; s++)
     502           5 :           simple_add_char(*s, env);
     503           1 :         simple_add_char(';', env);
     504             :     }
     505        1154 :     is_link_active = true;
     506        1154 :     do
     507        2308 :       c = *arg++;
     508        2308 :     while ((' ' == c) || ('\t' == c));
     509        1154 :     arg--;
     510             :     // The first argument is the URI.
     511        1154 :     const char *uri = arg;
     512       17881 :     do
     513       19035 :       c = *arg++;
     514       19035 :     while ((c != '\0') && (c != ' ') && (c != '\t'));
     515        1154 :     arg--;
     516        1154 :     ptrdiff_t uri_len = arg - uri;
     517             :     // Any remaining arguments are "key=value" pairs.
     518        1154 :     const char *pair = 0;
     519        1154 :     bool done = false;
     520           0 :     do {
     521        1154 :       if (pair != 0)
     522           0 :         simple_add_char(':', env);
     523        1154 :       pair = arg;
     524        1154 :       bool in_pair = true;
     525           0 :       do {
     526        1154 :         c = *arg++;
     527        1154 :         if ('\0' == c)
     528        1154 :           done = true;
     529           0 :         else if ((' ' == c) || ('\t' == c))
     530           0 :           in_pair = false;
     531             :         else
     532           0 :           simple_add_char(c, env);
     533        1154 :       } while (!done && in_pair);
     534        1154 :     } while (!done);
     535        1154 :     simple_add_char(';', env);
     536       19035 :     for (size_t i = uri_len; i > 0; i--)
     537       17881 :       simple_add_char(*uri++, env);
     538             :   }
     539        6924 :   for (const char *s = ST; *s != '\0'; s++)
     540        4616 :     simple_add_char(*s, env);
     541             : }
     542             : 
     543         940 : void tty_printer::change_color(const environment * const env)
     544             : {
     545         940 :   add_char(0, 0, env->hpos, env->vpos, env->col, env->fill, COLOR_CHANGE);
     546         940 : }
     547             : 
     548         941 : void tty_printer::change_fill_color(const environment * const env)
     549             : {
     550         941 :   add_char(0, 0, env->hpos, env->vpos, env->col, env->fill, COLOR_CHANGE);
     551         941 : }
     552             : 
     553         430 : void tty_printer::draw(int code, int *p, int np, const environment *env)
     554             : {
     555         430 :   if (!allow_drawing_commands)
     556           0 :     return;
     557         430 :   if (code == 'l')
     558         430 :     draw_line(p, np, env);
     559           0 :   else if (code == 'p')
     560           0 :     draw_polygon(p, np, env);
     561             :   else
     562           0 :     warning("ignoring unsupported drawing command '%1'", char(code));
     563             : }
     564             : 
     565           0 : void tty_printer::draw_polygon(int *p, int np, const environment *env)
     566             : {
     567           0 :   if (np & 1) {
     568           0 :     error("even number of arguments required for polygon");
     569           0 :     return;
     570             :   }
     571           0 :   if (np == 0) {
     572           0 :     error("no arguments for polygon");
     573           0 :     return;
     574             :   }
     575             :   // We draw only polygons that consist entirely of horizontal and
     576             :   // vertical lines.
     577           0 :   int hpos = 0;
     578           0 :   int vpos = 0;
     579           0 :   for (int i = 0; i < np; i += 2) {
     580           0 :     if (!(p[i] == 0 || p[i + 1] == 0))
     581           0 :       return;
     582           0 :     hpos += p[i];
     583           0 :     vpos += p[i + 1];
     584             :   }
     585           0 :   if (!(hpos == 0 || vpos == 0))
     586           0 :     return;
     587           0 :   int start_hpos = env->hpos;
     588           0 :   int start_vpos = env->vpos;
     589           0 :   hpos = start_hpos;
     590           0 :   vpos = start_vpos;
     591           0 :   for (int i = 0; i < np; i += 2) {
     592           0 :     line(hpos, vpos, p[i], p[i + 1], env->col, env->fill);
     593           0 :     hpos += p[i];
     594           0 :     vpos += p[i + 1];
     595             :   }
     596           0 :   line(hpos, vpos, start_hpos - hpos, start_vpos - vpos,
     597           0 :        env->col, env->fill);
     598             : }
     599             : 
     600         430 : void tty_printer::draw_line(int *p, int np, const environment *env)
     601             : {
     602         430 :   if (np != 2) {
     603           0 :     error("2 arguments required for line");
     604           0 :     return;
     605             :   }
     606         430 :   line(env->hpos, env->vpos, p[0], p[1], env->col, env->fill);
     607             : }
     608             : 
     609         430 : void tty_printer::line(int hpos, int vpos, int dx, int dy,
     610             :                        color *col, color *fill)
     611             : {
     612             :   // XXX: zero-length lines get drawn as '+' crossings in nroff, even
     613             :   // when there is no crossing, but they nevertheless occur frequently
     614             :   // in input.  Does tbl produce them?
     615             : #if 0
     616             :   if (0 == dx)
     617             :     fatal("cannot draw zero-length horizontal line");
     618             :   if (0 == dy)
     619             :     fatal("cannot draw zero-length vertical line");
     620             : #endif
     621         430 :   if ((dx != 0) && (dy != 0))
     622           0 :     warning("cannot draw diagonal line");
     623         430 :   if (dx % font::hor != 0)
     624           0 :     fatal("length of horizontal line %1 is not a multiple of horizontal"
     625           0 :         " motion quantum %2", dx, font::hor);
     626         430 :   if (dy % font::vert != 0)
     627           0 :     fatal("length of vertical line %1 is not a multiple of vertical"
     628           0 :         " motion quantum %2", dy, font::vert);
     629         430 :   if (dx == 0) {
     630             :     // vertical line
     631         166 :     int v = vpos;
     632         166 :     int len = dy;
     633         166 :     if (len < 0) {
     634         120 :       v += len;
     635         120 :       len = -len;
     636             :     }
     637         166 :     if (len == 0)
     638           4 :       add_char(vline_char, font::hor, hpos, v, col, fill,
     639             :                VDRAW_MODE|START_LINE|END_LINE);
     640             :     else {
     641         162 :       add_char(vline_char, font::hor, hpos, v, col, fill,
     642             :                VDRAW_MODE|START_LINE);
     643         162 :       len -= font::vert;
     644         162 :       v += font::vert;
     645         628 :       while (len > 0) {
     646         466 :         add_char(vline_char, font::hor, hpos, v, col, fill,
     647             :                  VDRAW_MODE|START_LINE|END_LINE);
     648         466 :         len -= font::vert;
     649         466 :         v += font::vert;
     650             :       }
     651         162 :       add_char(vline_char, font::hor, hpos, v, col, fill,
     652             :                VDRAW_MODE|END_LINE);
     653             :     }
     654             :   }
     655         430 :   if (dy == 0) {
     656             :     // horizontal line
     657         268 :     int h = hpos;
     658         268 :     int len = dx;
     659         268 :     if (len < 0) {
     660           0 :       h += len;
     661           0 :       len = -len;
     662             :     }
     663         268 :     if (len == 0)
     664           4 :       add_char(hline_char, font::hor, h, vpos, col, fill,
     665             :                HDRAW_MODE|START_LINE|END_LINE);
     666             :     else {
     667         264 :       add_char(hline_char, font::hor, h, vpos, col, fill,
     668             :                HDRAW_MODE|START_LINE);
     669         264 :       len -= font::hor;
     670         264 :       h += font::hor;
     671       15184 :       while (len > 0) {
     672       14920 :         add_char(hline_char, font::hor, h, vpos, col, fill,
     673             :                  HDRAW_MODE|START_LINE|END_LINE);
     674       14920 :         len -= font::hor;
     675       14920 :         h += font::hor;
     676             :       }
     677         264 :       add_char(hline_char, font::hor, h, vpos, col, fill,
     678             :                HDRAW_MODE|END_LINE);
     679             :     }
     680             :   }
     681         430 : }
     682             : 
     683     1090518 : void tty_printer::put_char(output_character wc)
     684             : {
     685     1090518 :   if (font::is_unicode && wc >= 0x80) {
     686             :     char buf[6 + 1];
     687             :     int count;
     688       24525 :     char *p = buf;
     689       24525 :     if (wc < 0x800)
     690         490 :       count = 1, *p = (unsigned char)((wc >> 6) | 0xc0);
     691       24035 :     else if (wc < 0x10000)
     692       24035 :       count = 2, *p = (unsigned char)((wc >> 12) | 0xe0);
     693           0 :     else if (wc < 0x200000)
     694           0 :       count = 3, *p = (unsigned char)((wc >> 18) | 0xf0);
     695           0 :     else if (wc < 0x4000000)
     696           0 :       count = 4, *p = (unsigned char)((wc >> 24) | 0xf8);
     697           0 :     else if (wc <= 0x7fffffff)
     698           0 :       count = 5, *p = (unsigned char)((wc >> 30) | 0xfC);
     699             :     else
     700           0 :       return;
     701       24035 :     do *++p = (unsigned char)(((wc >> (6 * --count)) & 0x3f) | 0x80);
     702       48560 :       while (count > 0);
     703       24525 :     *++p = '\0';
     704       24525 :     putstring(buf);
     705             :   }
     706             :   else
     707     1065993 :     putchar(wc);
     708             : }
     709             : 
     710           0 : void tty_printer::put_color(long color_index, int back)
     711             : {
     712           0 :   if (!want_sgr_truecolor) {
     713           0 :     if (DEFAULT_COLOR_IDX == color_index) {
     714           0 :       putstring(SGR_DEFAULT);
     715             :       // set bold and underline again
     716           0 :       if (is_boldfacing)
     717           0 :         putstring(SGR_BOLD);
     718           0 :       if (is_underlining) {
     719           0 :         if (do_sgr_italics)
     720           0 :           putstring(SGR_ITALIC);
     721           0 :         else if (do_reverse_video)
     722           0 :           putstring(SGR_REVERSE);
     723             :         else
     724           0 :           putstring(SGR_UNDERLINE);
     725             :       }
     726             :       // set other color again
     727           0 :       back = !back;
     728           0 :       color_index = back ? curr_back_idx : curr_fore_idx;
     729             :     }
     730           0 :     if (color_index != DEFAULT_COLOR_IDX) {
     731           0 :       putstring(CSI);
     732           0 :       if (back)
     733           0 :         putchar('4');
     734             :       else
     735           0 :         putchar('3');
     736           0 :       putchar(color_index + '0');
     737           0 :       putchar('m');
     738             :     }
     739             :   }
     740             :   else {
     741           0 :     if (DEFAULT_COLOR_IDX == color_index) {
     742           0 :       putstring(SGR_DEFAULT);
     743           0 :       back = !back;
     744           0 :       color_index = back ? curr_back_idx : curr_fore_idx;
     745           0 :       if (DEFAULT_COLOR_IDX == color_index)
     746           0 :         return;
     747             :     }
     748           0 :     putstring(CSI);
     749           0 :     int fb = back ? 48 : 38;
     750           0 :     const size_t buflen = sizeof "48;2;255;255;255m";
     751             :     char buf[buflen];
     752           0 :     size_t written = snprintf(buf, buflen, "%d;2;%lu;%lu;%lum", fb,
     753             :                               (color_index >> 16),
     754           0 :                               ((color_index >> 8) & 0xff),
     755           0 :                               (color_index & 0xff));
     756           0 :     assert(written < buflen);
     757           0 :     putstring(buf);
     758             :   }
     759             : }
     760             : 
     761             : // The possible Unicode combinations for crossing characters.
     762             : //
     763             : // '  ' = 0, ' -' = 4, '- ' = 8, '--' = 12,
     764             : //
     765             : // '  ' = 0, ' ' = 1, '|' = 2, '|' = 3
     766             : //            |                 |
     767             : 
     768             : static output_character crossings[4*4] = {
     769             :   0x0000, 0x2577, 0x2575, 0x2502,
     770             :   0x2576, 0x250C, 0x2514, 0x251C,
     771             :   0x2574, 0x2510, 0x2518, 0x2524,
     772             :   0x2500, 0x252C, 0x2534, 0x253C
     773             : };
     774             : 
     775        1058 : void tty_printer::end_page(int page_length)
     776             : {
     777        1058 :   if (page_length % font::vert != 0)
     778           0 :     error("vertical position at end of page not multiple of vertical"
     779             :           " motion quantum");
     780        1058 :   int lines_per_page = page_length / font::vert;
     781             :   int last_line;
     782       76770 :   for (last_line = nlines; last_line > 0; last_line--)
     783       76752 :     if (lines[last_line - 1])
     784        1040 :       break;
     785             : #if 0
     786             :   if (last_line > lines_per_page) {
     787             :     error("characters past last line discarded");
     788             :     do {
     789             :       --last_line;
     790             :       while (lines[last_line]) {
     791             :         tty_glyph *tem = lines[last_line];
     792             :         lines[last_line] = tem->next;
     793             :         delete tem;
     794             :       }
     795             :     } while (last_line > lines_per_page);
     796             :   }
     797             : #endif
     798       39734 :   for (int i = 0; i < last_line; i++) {
     799       38676 :     tty_glyph *p = lines[i];
     800       38676 :     lines[i] = 0;
     801       38676 :     tty_glyph *g = 0;
     802     1131652 :     while (p) {
     803     1092976 :       tty_glyph *tem = p->next;
     804     1092976 :       p->next = g;
     805     1092976 :       g = p;
     806     1092976 :       p = tem;
     807             :     }
     808       38676 :     int hpos = 0;
     809             :     tty_glyph *nextp;
     810       38676 :     curr_fore_idx = DEFAULT_COLOR_IDX;
     811       38676 :     curr_back_idx = DEFAULT_COLOR_IDX;
     812       38676 :     is_underlining = false;
     813       38676 :     is_boldfacing = false;
     814     1131652 :     for (p = g; p; delete p, p = nextp) {
     815     1092976 :       nextp = p->next;
     816     1092976 :       if (p->mode & CU_MODE) {
     817           0 :         is_continuously_underlining = (p->code != 0);
     818           0 :         continue;
     819             :       }
     820     1092976 :       if (nextp && p->hpos == nextp->hpos) {
     821             :         // We expect HDRAW_MODE glyphs to always precede VDRAW_MODEs.
     822       35974 :         assert (!(p->draw_mode() == VDRAW_MODE
     823             :                   && nextp->draw_mode() == HDRAW_MODE));
     824       36302 :         if (p->draw_mode() == HDRAW_MODE &&
     825         328 :             nextp->draw_mode() == VDRAW_MODE) {
     826         273 :           if (font::is_unicode)
     827         185 :             nextp->code =
     828         185 :               crossings[((p->mode & (START_LINE|END_LINE)) >> 4)
     829         185 :                         + ((nextp->mode & (START_LINE|END_LINE)) >> 6)];
     830             :           else
     831          88 :             nextp->code = '+';
     832         273 :           continue;
     833             :         }
     834       35701 :         if (p->draw_mode() != 0 && p->draw_mode() == nextp->draw_mode())
     835             :         {
     836         189 :           nextp->code = p->code;
     837         189 :           continue;
     838             :         }
     839       35512 :         if (!want_glyph_composition_by_overstriking)
     840         812 :           continue;
     841             :       }
     842     1091702 :       if (hpos > p->hpos) {
     843          16 :         do {
     844          54 :           putchar('\b');
     845          54 :           hpos--;
     846          54 :         } while (hpos > p->hpos);
     847             :       }
     848             :       else {
     849     1091664 :         if (want_horizontal_tabs) {
     850             :           for (;;) {
     851          49 :             int next_tab_pos = ((hpos + TAB_WIDTH) / TAB_WIDTH) * TAB_WIDTH;
     852          49 :             if (next_tab_pos > p->hpos)
     853          47 :               break;
     854           2 :             if (is_continuously_underlining)
     855           0 :               make_underline(p->w);
     856           2 :             else if (!use_overstriking_drawing_scheme
     857           2 :                      && is_underlining) {
     858           0 :               if (do_sgr_italics)
     859           0 :                 putstring(SGR_NO_ITALIC);
     860           0 :               else if (do_reverse_video)
     861           0 :                 putstring(SGR_NO_REVERSE);
     862             :               else
     863           0 :                 putstring(SGR_NO_UNDERLINE);
     864           0 :               is_underlining = false;
     865             :             }
     866           2 :             if ((next_tab_pos - hpos) > 1)
     867           1 :               putchar('\t');
     868             :             else
     869           1 :               putchar(' ');
     870           2 :             hpos = next_tab_pos;
     871           2 :           }
     872             :         }
     873     1578540 :         for (; hpos < p->hpos; hpos++) {
     874      486876 :           if (is_continuously_underlining)
     875           0 :             make_underline(p->w);
     876      486876 :           else if (!use_overstriking_drawing_scheme && is_underlining) {
     877        6822 :             if (do_sgr_italics)
     878           0 :               putstring(SGR_NO_ITALIC);
     879        6822 :             else if (do_reverse_video)
     880           0 :               putstring(SGR_NO_REVERSE);
     881             :             else
     882        6822 :               putstring(SGR_NO_UNDERLINE);
     883        6822 :             is_underlining = false;
     884             :           }
     885      486876 :           putchar(' ');
     886             :         }
     887             :       }
     888     1091702 :       assert(hpos == p->hpos);
     889     1091702 :       if (p->mode & COLOR_CHANGE) {
     890        1184 :         if (!use_overstriking_drawing_scheme) {
     891        1126 :           if (p->fore_color_idx != curr_fore_idx) {
     892           0 :             put_color(p->fore_color_idx, 0);
     893           0 :             curr_fore_idx = p->fore_color_idx;
     894             :           }
     895        1126 :           if (p->back_color_idx != curr_back_idx) {
     896           0 :             put_color(p->back_color_idx, 1);
     897           0 :             curr_back_idx = p->back_color_idx;
     898             :           }
     899             :         }
     900        1184 :         continue;
     901             :       }
     902     1090518 :       if (p->mode & UNDERLINE_MODE)
     903       74987 :         make_underline(p->w);
     904     1015531 :       else if (!use_overstriking_drawing_scheme && is_underlining) {
     905        3879 :         if (do_sgr_italics)
     906           0 :           putstring(SGR_NO_ITALIC);
     907        3879 :         else if (do_reverse_video)
     908           0 :           putstring(SGR_NO_REVERSE);
     909             :         else
     910        3879 :           putstring(SGR_NO_UNDERLINE);
     911        3879 :         is_underlining = false;
     912             :       }
     913     1090518 :       if (p->mode & BOLD_MODE)
     914       57715 :         make_bold(p->code, p->w);
     915     1032803 :       else if (!use_overstriking_drawing_scheme && is_boldfacing) {
     916       10432 :         putstring(SGR_NO_BOLD);
     917       10432 :         is_boldfacing = false;
     918             :       }
     919     1090518 :       if (!use_overstriking_drawing_scheme) {
     920      995318 :         if (p->fore_color_idx != curr_fore_idx) {
     921           0 :           put_color(p->fore_color_idx, 0);
     922           0 :           curr_fore_idx = p->fore_color_idx;
     923             :         }
     924      995318 :         if (p->back_color_idx != curr_back_idx) {
     925           0 :           put_color(p->back_color_idx, 1);
     926           0 :           curr_back_idx = p->back_color_idx;
     927             :         }
     928             :       }
     929     1090518 :       put_char(p->code);
     930     1090518 :       hpos += p->w / font::hor;
     931             :     }
     932       38676 :     if (!use_overstriking_drawing_scheme
     933       28622 :         && (is_boldfacing || is_underlining
     934       25473 :             || curr_fore_idx != DEFAULT_COLOR_IDX
     935       25473 :             || curr_back_idx != DEFAULT_COLOR_IDX))
     936        3149 :       putstring(SGR_DEFAULT);
     937       38676 :     putchar('\n');
     938             :   }
     939        1058 :   if (want_form_feeds) {
     940           0 :     if (last_line < lines_per_page)
     941           0 :       putchar('\f');
     942             :   }
     943             :   else {
     944       45645 :     for (; last_line < lines_per_page; last_line++)
     945       44587 :       putchar('\n');
     946             :   }
     947        1058 : }
     948             : 
     949        1300 : font *tty_printer::make_font(const char *nm)
     950             : {
     951        1300 :   return tty_font::load_tty_font(nm);
     952             : }
     953             : 
     954         944 : printer *make_printer()
     955             : {
     956         944 :   return new tty_printer();
     957             : }
     958             : 
     959         991 : static void update_options()
     960             : {
     961         991 :   if (use_overstriking_drawing_scheme) {
     962         422 :     do_sgr_italics = false;
     963         422 :     do_reverse_video = false;
     964         422 :     bold_underline_mode = bold_underline_mode_option;
     965         422 :     do_bold = want_emboldening_by_overstriking;
     966         422 :     do_underline = want_italics_by_underlining;
     967             :   }
     968             :   else {
     969         569 :     do_sgr_italics = want_sgr_italics;
     970         569 :     do_reverse_video = want_reverse_video_for_italics;
     971         569 :     bold_underline_mode = BOLD_MODE|UNDERLINE_MODE;
     972         569 :     do_bold = true;
     973         569 :     do_underline = true;
     974             :   }
     975         991 : }
     976             : 
     977         993 : int main(int argc, char **argv)
     978             : {
     979         993 :   program_name = argv[0];
     980             :   static char stderr_buf[BUFSIZ];
     981         993 :   if (getenv("GROFF_NO_SGR"))
     982           0 :     use_overstriking_drawing_scheme = true;
     983         993 :   setbuf(stderr, stderr_buf);
     984         993 :   setlocale(LC_CTYPE, "");
     985             :   int c;
     986             :   static const struct option long_options[] = {
     987             :     { "help", no_argument, 0 /* nullptr */, CHAR_MAX + 1 },
     988             :     { "version", no_argument, 0 /* nullptr */, 'v' },
     989             :     { 0 /* nullptr */, 0, 0 /* nullptr */, 0 }
     990             :   };
     991        2714 :   while ((c = getopt_long(argc, argv, ":bBcdfF:hiI:ortuUv",
     992             :                           long_options, 0 /* nullptr */))
     993        2714 :          != EOF)
     994        1723 :     switch (c) {
     995           2 :     case 'v':
     996           2 :       printf("GNU grotty (groff) version %s\n", Version_string);
     997           2 :       exit(EXIT_SUCCESS);
     998             :       break;
     999           0 :     case 'i':
    1000             :       // Use italic font instead of underlining.
    1001           0 :       want_sgr_italics = true;
    1002           0 :       break;
    1003           0 :     case 'I':
    1004             :       // ignore include search path
    1005           0 :       break;
    1006         422 :     case 'b':
    1007             :       // Do not embolden by overstriking.
    1008         422 :       want_emboldening_by_overstriking = false;
    1009         422 :       break;
    1010         422 :     case 'c':
    1011             :       // Use old scheme for emboldening and underline.
    1012         422 :       use_overstriking_drawing_scheme = true;
    1013         422 :       break;
    1014         422 :     case 'u':
    1015             :       // Do not underline.
    1016         422 :       want_italics_by_underlining = false;
    1017         422 :       break;
    1018         422 :     case 'o':
    1019             :       // Do not overstrike (other than emboldening and underlining).
    1020         422 :       want_glyph_composition_by_overstriking = false;
    1021         422 :       break;
    1022           0 :     case 'r':
    1023             :       // Use reverse mode instead of underlining.
    1024           0 :       want_reverse_video_for_italics = true;
    1025           0 :       break;
    1026           0 :     case 'B':
    1027             :       // Do bold-underlining as bold.
    1028           0 :       bold_underline_mode_option = BOLD_MODE;
    1029           0 :       break;
    1030           0 :     case 'U':
    1031             :       // Do bold-underlining as underlining.
    1032           0 :       bold_underline_mode_option = UNDERLINE_MODE;
    1033           0 :       break;
    1034           1 :     case 'h':
    1035             :       // Use horizontal tabs.
    1036           1 :       want_horizontal_tabs = true;
    1037           1 :       break;
    1038           0 :     case 'f':
    1039           0 :       want_form_feeds = true;
    1040           0 :       break;
    1041          32 :     case 'F':
    1042          32 :       font::command_line_font_dir(optarg);
    1043          32 :       break;
    1044           0 :     case 'd':
    1045             :       // Ignore \D commands.
    1046           0 :       allow_drawing_commands = false;
    1047           0 :       break;
    1048           0 :     case 't':
    1049             :       // Use SGR 38 and 48 sequences instead of SGR 30-37 and 40-47.
    1050           0 :       want_sgr_truecolor = true;
    1051           0 :       break;
    1052           0 :     case CHAR_MAX + 1: // --help
    1053           0 :       usage(stdout);
    1054           0 :       exit(EXIT_SUCCESS);
    1055             :       break;
    1056           0 :     case '?':
    1057           0 :       if (optopt != 0)
    1058           0 :         error("unrecognized command-line option '%1'", char(optopt));
    1059             :       else
    1060           0 :         error("unrecognized command-line option '%1'",
    1061           0 :               argv[(optind - 1)]);
    1062           0 :       usage(stderr);
    1063           0 :       exit(2);
    1064             :       break;
    1065           0 :     case ':':
    1066           0 :       error("command-line option '%1' requires an argument",
    1067           0 :             char(optopt));
    1068           0 :       usage(stderr);
    1069           0 :       exit(2);
    1070             :       break;
    1071           0 :     default:
    1072           0 :       assert(0 == "unhandled getopt_long return value");
    1073             :     }
    1074         991 :   update_options();
    1075         991 :   if (optind >= argc)
    1076         991 :     do_file("-");
    1077             :   else {
    1078           0 :     for (int i = optind; i < argc; i++)
    1079           0 :       do_file(argv[i]);
    1080             :   }
    1081         991 :   return 0;
    1082             : }
    1083             : 
    1084           0 : static void usage(FILE *stream)
    1085             : {
    1086           0 :   fprintf(stream,
    1087             : "usage: %s [-dfhot] [-i|-r] [-F font-directory] [file ...]\n"
    1088             : "usage: %s -c [-bBdfhouU] [-F font-directory] [file ...]\n"
    1089             : "usage: %s {-v | --version}\n"
    1090             : "usage: %s --help\n",
    1091             :           program_name, program_name, program_name, program_name);
    1092           0 :   if (stdout == stream)
    1093           0 :     fputs("\n"
    1094             : "Translate the output of troff(1) into a form suitable for\n"
    1095             : "typewriter‐like devices, including terminal emulators.  See the\n"
    1096             : "grotty(1) manual page.\n",
    1097             :           stream);
    1098           0 : }
    1099             : 
    1100             : // Local Variables:
    1101             : // fill-column: 72
    1102             : // mode: C++
    1103             : // End:
    1104             : // vim: set cindent noexpandtab shiftwidth=2 textwidth=72:

Generated by: LCOV version 1.14