groff
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH v3] [grotty]: Use terminfo.


From: Lennart Jablonka
Subject: [PATCH v3] [grotty]: Use terminfo.
Date: Tue, 5 Sep 2023 02:09:29 +0000

This has nothing at all to do with making it easier to customize the
look of man pages.

Hyperlinks are still marked up using OSC 8, hoping that whatever
terminal sits in front of the user interprets or ignores those.
Terminfo naturally doesn’t have a capability for hyperlinks.

Autoconf is not informed about the dependence on Curses; we simply link
with -lcurses, as that is what X/Open Curses specifies.

The suggested option to less is changed to -r, which enables passing
through single control characters, because some terminal descriptions
use those; I noticed that tmux’s sgr0 contains ^O.
---
And it works again!   Beside the tests;  I’ll let you look at
those.  :)

I changed how links pass through the program.   Instead of the
bytes of the OSC 8 sequences being embedded in the stream of
glyphs, I add special glyphs for changing the link state.   Those
are interpreted by end_page.

I also tried the other way I noted:  Associating each tty_glyph
with an optional link target URI.   That worked, albeit less well
that this.   Had to introduce a reference-counted string class to
avoid copying the URI for every single glyph.   The links in the
output included space following the link text.   Yeah, no;  this
is better.

Interdiff against v2 with my own comments:
  diff --git a/src/devices/grotty/tty.cpp b/src/devices/grotty/tty.cpp
  index bbca37ee3..bc016149d 100644
  --- a/src/devices/grotty/tty.cpp
  +++ b/src/devices/grotty/tty.cpp
  @@ -21,6 +21,8 @@ along with this program.  If not, see 
<http://www.gnu.org/licenses/>. */
   #include "device.h"
   #include "ptable.h"

  +#include <stringclass.h>
  +
   #include <curses.h>
   #include <term.h>

  @@ -67,20 +69,21 @@ static void usage(FILE *stream);
   static int hline_char = '-';
   static int vline_char = '|';

  +typedef unsigned char glyph_mode;
   enum {
     UNDERLINE_MODE = 0x01,
     BOLD_MODE = 0x02,
     VDRAW_MODE = 0x04,
     HDRAW_MODE = 0x08,
     CU_MODE = 0x10,
  -  COLOR_CHANGE = 0x20,
  +  URI_MODE = 0x20,
     START_LINE = 0x40,
     END_LINE = 0x80
   };

Turns out we don’t need COLOR_CHANGE.   Before I noticed that,
however, I needed place in the mode for nine flags, and
unsigned char doesn’t usually give you that.   So I introduced
a name for the type.   And kept it when it became clear I don’t
need more than eight bits after all.

Btw:  C++ doesn’t have implicit conversions from enumeration types
to integer types.   I found that a little surprising.   That’s why
I use a typedef instead of naming the enum.

   // Mode to use for bold-underlining.
  -static unsigned char bold_underline_mode_option = BOLD_MODE|UNDERLINE_MODE;
  -static unsigned char bold_underline_mode;
  +static glyph_mode bold_underline_mode_option = BOLD_MODE|UNDERLINE_MODE;
  +static glyph_mode bold_underline_mode;

   #ifndef IS_EBCDIC_HOST
   #define OSC8 "\033]8"
  @@ -96,10 +99,10 @@ static char *enter_italics_or_the_like_mode;

   class tty_font : public font {
     tty_font(const char *);
  -  unsigned char mode;
  +  glyph_mode mode;
   public:
     ~tty_font();
  -  unsigned char get_mode() { return mode; }
  +  glyph_mode get_mode() { return mode; }
   #if 0
     void handle_x_command(int argc, const char **argv);
   #endif
  @@ -116,14 +119,14 @@ tty_font *tty_font::load_tty_font(const char *s)
     const char *num = f->get_internal_name();
     long n;
     if (num != 0 && (n = strtol(num, 0, 0)) != 0)
  -    f->mode = (unsigned char)(n & (BOLD_MODE|UNDERLINE_MODE));
  +    f->mode = (glyph_mode)(n & (BOLD_MODE|UNDERLINE_MODE));
     if (!do_underline)
       f->mode &= ~UNDERLINE_MODE;
     if (!do_bold)
       f->mode &= ~BOLD_MODE;
     if ((f->mode & (BOLD_MODE|UNDERLINE_MODE)) == (BOLD_MODE|UNDERLINE_MODE))
  -    f->mode = (unsigned char)((f->mode & ~(BOLD_MODE|UNDERLINE_MODE))
  -                           | bold_underline_mode);
  +    f->mode = ((f->mode & ~(BOLD_MODE|UNDERLINE_MODE))
  +            | bold_underline_mode);
     return f;
   }

  @@ -152,12 +155,13 @@ class tty_glyph {
     int w;
     int hpos;
     unsigned int code;
  -  unsigned char mode;
  +  glyph_mode mode;
     schar back_color_idx;
     schar fore_color_idx;
  -  inline int draw_mode() { return mode & (VDRAW_MODE|HDRAW_MODE); }
  +  string osc8_string;
  +  inline glyph_mode draw_mode() { return mode & (VDRAW_MODE|HDRAW_MODE); }
     inline int order() {
  -    return mode & (VDRAW_MODE|HDRAW_MODE|CU_MODE|COLOR_CHANGE); }
  +    return mode & (VDRAW_MODE|HDRAW_MODE|CU_MODE|URI_MODE); }
   };


  @@ -176,8 +180,7 @@ class tty_printer : public printer {
     void update_attributes(bool, bool, schar, schar);
     schar color_to_idx(color *);
     void add_char(output_character, int, int, int, color *, color *,
  -             unsigned char);
  -  void simple_add_char(const output_character, const environment *);
  +             const string &, glyph_mode);

The function simple_add_char was used by special_link only, which
now produces special tty_glyphs instead.

You see the extra parameter to add_char?   I hear your wailing
from my monitor.   The function add_char creates its own
tty_glyphs using the parameters, so first I thought it would be
good to simply replace all the parameters by a single tty_glyph*.
Turns out, add_char wants to store the glyph’s horizontal position
in the tty_glyph.   The callers don’t know that position.   So
I simply added yet another parameter that will be useless for
almost all calls to add_char.

     char *make_rgb_string(unsigned int, unsigned int, unsigned int);
     bool tty_color(unsigned int, unsigned int, unsigned int, schar *,
                 schar = DEFAULT_COLOR_IDX);
  @@ -191,8 +194,6 @@ public:
     void set_char(glyph *, font *, const environment *, int, const char *);
     void draw(int, int *, int, const environment *);
     void special(char *, const environment *, char);
  -  void change_color(const environment * const);
  -  void change_fill_color(const environment * const);

Ah, yes.   I looked into these functions a little.   What they do
is add special tty_glyphs to the stream just to change the current
color whenever the color needs to be changed.   Their caller does
another thing beside calling these two functions when the color
changes:  It updates the environment.   Which is then picked up by
the tty_glyphs produced.   I looked through end_page and could not
come up with a scenario in which the COLOR_CHANGE tty_glyphs
produced by these functions have any effect.

     void put_char(output_character);
     void begin_page(int) { }
     void end_page(int);
  @@ -271,10 +272,16 @@ tty_printer::~tty_printer()
     delete[] lines_;
   }

  -// Both ovrestrike and update_attributes need be called for text
  -// attributes; only one of them will take effect.  Call overstrike for
  -// every glyph.  The update_attributes function, too, can be called for
  -// every glyph, as it's a noop for the same attributes.
  +// The function overstrike applies text attributes for the next glyph
  +// rendered using the overstriking scheme.  The function
  +// update_attributes uses terminfo capabilities to apply text attributes
  +// for all following glyphs, until it's called again with different
  +// arguments.  If update_attributes is called with the same arguments as
  +// the last time, it doesn't print anything.
  +//
  +// Thus, call overstrike for every single glyph you print and call
  +// update_attributes either whenever the attributes change or for every
  +// glyph, whatever is more convenient.  Do call both.

Is this comment better?

   void tty_printer::overstrike(bool underline, bool bold,
                             output_character c, int w)
  @@ -359,13 +366,15 @@ void tty_printer::set_char(glyph *g, font *f, const 
environment *env,
     add_char(f->get_code(g), w,
           env->hpos, env->vpos,
           env->col, env->fill,
  +        string(),

Ah, yes:  Parameters you don’t care about for which you need know
how to construct them.   Don’t worry, string::string() is cheap.

           ((tty_font *)f)->get_mode());
   }

   void tty_printer::add_char(output_character c, int w,
                           int h, int v,
                           color *fore, color *back,
  -                        unsigned char mode)
  +                        const string &osc8_string,
  +                        glyph_mode mode)
   {
   #if 0
     // This is too expensive.
  @@ -409,12 +418,13 @@ void tty_printer::add_char(output_character c, int w,
     g->code = c;
     g->fore_color_idx = color_to_idx(fore);
     g->back_color_idx = color_to_idx(back);
  +  g->osc8_string = osc8_string;

This copies the ::string.   That’s a useless allocation.

I could have used ::string::move to not reallocate, but that would
require the parameter to be a non-const reference.   Which would
prohibit passing temporaries like string().   Making calling
add_char even more complicated.

You also can’t simply move the allocated string out of a ::string
and /not/ into another ::string, btw.   That would allow the
parameter to be a const char * and the caller to pass either
a null pointer or the pointer gotten from a ::string.   Adding
that wouldn’t be hard.   That might be a good idea.

That would then require either also moving the C string /into/
a ::string or tty_glyph having a destructor to deallocate the
string.   Either would work.

All this C++ stuff is tiring me.

     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.
  +  // increasing order of hpos, with CU and URI specials before HDRAW
  +  // characters before VDRAW characters before normal characters at
  +  // each hpos, and otherwise in order of occurrence.

     tty_glyph **pp;
     for (pp = lines_ + (vpos - 1); *pp; pp = &(*pp)->next)
  @@ -425,17 +435,11 @@ void tty_printer::add_char(output_character c, int w,
     *pp = g;
   }

  -void tty_printer::simple_add_char(const output_character c,
  -                               const environment *env)
  -{
  -  add_char(c, 0, env->hpos, env->vpos, env->col, env->fill, 0);
  -}
  -
   void tty_printer::special(char *arg, const environment *env, char type)
   {
     if (type == 'u') {
       add_char(*arg - '0', 0, env->hpos, env->vpos, env->col, env->fill,
  -          CU_MODE);
  +          string(), CU_MODE);
       return;
     }
     if (type != 'p')
  @@ -466,38 +470,35 @@ void tty_printer::special(char *arg, const environment 
*env, char type)
       warning("unrecognized X command '%1' ignored", command);
   }

  -// Produce an OSC 8 hyperlink.  Given ditroff input of the form:
  +// Produce an OSC 8 hyperlink.  Given ditroff output of the form:

“Ditroff input” would be troff input.   Using “ditroff” as synonym
for troff output is confusing.   An alternative to
“ditroff output” is “troff output.”   Because, y’know, ditroff
and troff are one and the same.

   //   x X tty: link [URI[ KEY=VALUE] ...]
  -// produce "OSC 8 [;KEY=VALUE];[URI] ST".  KEY/VALUE pairs can be
  +// produce "OSC 8 ;[KEY=VALUE];[URI] ST".  KEY/VALUE pairs can be
   // repeated arbitrarily and are separated by colons.  Omission of the
   // URI ends the hyperlink that was begun by specifying it.  See
   // <https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda>.
  +//
  +// TODO: Discard URIs with prohibited bytes.  Only 08-0D and 20-7E are
  +// allowed between OSC and ST.

Putting other bytes there would be neither correct nor necessary.
You can transform IRIs to URIs, after all.

   void tty_printer::special_link(const char *arg, const environment *env)
   {
     static bool is_link_active = false;
     if (use_overstriking_drawing_scheme)
       return;
  -  for (const char *s = OSC8; *s != '\0'; s++)
  -    simple_add_char(*s, env);
  -  simple_add_char(';', env);
     char c = *arg;
     if ('\0' == c || '\n' == c) {
  -    simple_add_char(';', env);
       if (!is_link_active)
         warning("ending hyperlink when none was started");
  +    else
  +      add_char(0, 0, env->hpos, env->vpos, env->col, env->fill,
  +            string(), URI_MODE);
       is_link_active = false;
     }
     else {
       // Our caller ensures that we see whitespace after 'link'.
       assert(c == ' ' || c == '\t');
  -    if (is_link_active) {
  -      warning("new hyperlink started without ending previous one;"
  -           " recovering");
  -      simple_add_char(';', env);
  -     for (const char *s = ST OSC8; *s != '\0'; s++)
  -       simple_add_char(*s, env);
  -     simple_add_char(';', env);

This “recovering” was just unnecessary.   The sequences

        OSC 8 ;;a ST OSC 8 ;;b ST OSC 8 ;; ST OSC 8 ;;d ST

and

        OSC 8 ;;d ST

are equivalent.   A terminal cell has only ever a single
associated URI.   OSC 8 ;; ST lets that URI be the empty string.
That’s what you should and want to do after a link followed by
non-link cells, but inserting OSC 8 ;; ST before OSC 8 ;;a ST is
useless.

  -    }
  +    string s = ";";
  +    if (is_link_active)
  +      warning("new hyperlink started without ending previous one");
       is_link_active = true;
       do
         c = *arg++;
  @@ -515,7 +516,7 @@ void tty_printer::special_link(const char *arg, const 
environment *env)
       bool done = false;
       do {
         if (pair != 0)
  -     simple_add_char(':', env);
  +     s += ':';
         pair = arg;
         bool in_pair = true;
         do {
  @@ -525,25 +526,14 @@ void tty_printer::special_link(const char *arg, const 
environment *env)
        else if (' ' == c || '\t' == c)
          in_pair = false;
        else
  -       simple_add_char(c, env);
  +       s += c;
         } while (!done && in_pair);
       } while (!done);
  -    simple_add_char(';', env);
  -    for (size_t i = uri_len; i > 0; i--)
  -      simple_add_char(*uri++, env);
  -  }
  -  for (const char *s = ST; *s != '\0'; s++)
  -    simple_add_char(*s, env);
  -}
  -
  -void tty_printer::change_color(const environment * const env)
  -{
  -  add_char(0, 0, env->hpos, env->vpos, env->col, env->fill, COLOR_CHANGE);
  -}
  +    s += ';';
  +    s.append(uri, uri_len);

  -void tty_printer::change_fill_color(const environment * const env)
  -{
  -  add_char(0, 0, env->hpos, env->vpos, env->col, env->fill, COLOR_CHANGE);
  +    add_char(0, 0, env->hpos, env->vpos, env->col, env->fill, s, URI_MODE);

Here I present the one place the new parameter to add_char is
used.

  +  }
   }

   void tty_printer::draw(int code, int *p, int np, const environment *env)
  @@ -631,20 +621,20 @@ void tty_printer::line(int hpos, int vpos, int dx, int 
dy,
         len = -len;
       }
       if (len == 0)
  -      add_char(vline_char, font::hor, hpos, v, col, fill,
  +      add_char(vline_char, font::hor, hpos, v, col, fill, string(),
               VDRAW_MODE|START_LINE|END_LINE);
       else {
  -      add_char(vline_char, font::hor, hpos, v, col, fill,
  +      add_char(vline_char, font::hor, hpos, v, col, fill, string(),
               VDRAW_MODE|START_LINE);
         len -= font::vert;
         v += font::vert;
         while (len > 0) {
  -     add_char(vline_char, font::hor, hpos, v, col, fill,
  +     add_char(vline_char, font::hor, hpos, v, col, fill, string(),
                 VDRAW_MODE|START_LINE|END_LINE);
        len -= font::vert;
        v += font::vert;
         }
  -      add_char(vline_char, font::hor, hpos, v, col, fill,
  +      add_char(vline_char, font::hor, hpos, v, col, fill, string(),
               VDRAW_MODE|END_LINE);
       }
     }
  @@ -657,20 +647,20 @@ void tty_printer::line(int hpos, int vpos, int dx, int 
dy,
         len = -len;
       }
       if (len == 0)
  -      add_char(hline_char, font::hor, h, vpos, col, fill,
  +      add_char(hline_char, font::hor, h, vpos, col, fill, string(),
               HDRAW_MODE|START_LINE|END_LINE);
       else {
  -      add_char(hline_char, font::hor, h, vpos, col, fill,
  +      add_char(hline_char, font::hor, h, vpos, col, fill, string(),
               HDRAW_MODE|START_LINE);
         len -= font::hor;
         h += font::hor;
         while (len > 0) {
  -     add_char(hline_char, font::hor, h, vpos, col, fill,
  +     add_char(hline_char, font::hor, h, vpos, col, fill, string(),
                 HDRAW_MODE|START_LINE|END_LINE);
        len -= font::hor;
        h += font::hor;
         }
  -      add_char(hline_char, font::hor, h, vpos, col, fill,
  +      add_char(hline_char, font::hor, h, vpos, col, fill, string(),
               HDRAW_MODE|END_LINE);
       }
     }
  @@ -762,7 +752,8 @@ void tty_printer::end_page(int page_length)
        is_continuously_underlining = (p->code != 0);
        continue;
         }
  -      if (nextp && p->hpos == nextp->hpos) {
  +      if (nextp && p->hpos == nextp->hpos
  +       && !(p->mode & URI_MODE) && !(nextp->mode & URI_MODE)) {

In v2, the bytes in the OSC sequence were overstruck.   Here, we
don’t want the whole OSC sequence to be “overstruck.”

        if (p->draw_mode() == HDRAW_MODE &&
            nextp->draw_mode() == VDRAW_MODE) {
          if (font::is_unicode)
  @@ -780,6 +771,8 @@ void tty_printer::end_page(int page_length)
        if (!do_glyph_composition_by_overstriking)
          continue;
         }
  +      // TODO(humm): Is this branch ever taken?  See the loop at the end
  +      // of add_char.
         if (hpos > p->hpos) {
        do {
          putchar('\b');
  @@ -811,9 +804,15 @@ void tty_printer::end_page(int page_length)
        }
         }
         assert(hpos == p->hpos);
  -      if (p->mode & COLOR_CHANGE) {
  -     update_attributes(is_underlining, is_boldfacing,
  -                       p->fore_color_idx, p->back_color_idx);

See, COLOR_CHANGE changes the attributes, but whenever a glyph is
printed, the attributes are corrected anyway.

  +      if (p->mode & URI_MODE) {
  +     if (nextp && (nextp->mode & URI_MODE))
  +       continue;

Same as in special_link:  Of adjacent OSC 8 sequences, only the
last one affects the following cells.   So we can safely drop the
other ones.

Which leads to test failures, of course.

  +     fputs(OSC8, stdout);
  +     if (p->osc8_string.empty())
  +       fputs(";;", stdout);
  +     else
  +       fwrite(p->osc8_string.contents(), 1, p->osc8_string.length(), stdout);
  +     fputs(ST, stdout);
        continue;
         }
         overstrike(p->mode & UNDERLINE_MODE, p->mode & BOLD_MODE, p->code, 
p->w);

 INSTALL.extra                   |   3 +
 NEWS                            |  10 +
 src/devices/grotty/grotty.1.man |  40 ++-
 src/devices/grotty/grotty.am    |   3 +-
 src/devices/grotty/tty.cpp      | 466 ++++++++++++++++----------------
 5 files changed, 275 insertions(+), 247 deletions(-)

diff --git a/INSTALL.extra b/INSTALL.extra
index 78d4139af..5ef903c77 100644
--- a/INSTALL.extra
+++ b/INSTALL.extra
@@ -128,6 +128,9 @@ requires the 'pkg-config' program to be installed on your 
system, as
 well as the library's C header files--on a package-based host system,
 this can mean installing uchardet's '-dev' or '-devel' package.

+The 'grotty' program depends on a Curses library, which is specified
+by X/Open Curses.
+
 URW fonts
 ---------

diff --git a/NEWS b/NEWS
index 81de79c1b..87990f487 100644
--- a/NEWS
+++ b/NEWS
@@ -87,6 +87,16 @@ o The s (ms) macro package now sets the vertical spacing 
register
   size of 20 points, the vertical spacing now defaults to 24 points
   rather than 22.

+Output drivers
+--------------
+
+grotty
+------
+
+o The terminfo library is now used for applying text attributes (like
+  italic, bold, and colors).  You now need Curses to run grotty.  The
+  legacy overstriking output format remains.
+
 Miscellaneous
 -------------

diff --git a/src/devices/grotty/grotty.1.man b/src/devices/grotty/grotty.1.man
index 24cea9801..6fbd70612 100644
--- a/src/devices/grotty/grotty.1.man
+++ b/src/devices/grotty/grotty.1.man
@@ -135,10 +135,9 @@ Output is written to the standard output stream.
 .P
 By default,
 .I grotty
-emits SGR escape sequences
-(from ISO\~6429,
-popularly called \[lq]ANSI escapes\[rq])
-to change text attributes
+uses
+.MR terminfo 3
+capabilities to change text attributes
 (bold,
 italic,
 underline,
@@ -168,6 +167,11 @@ which is dependent on the settings of the terminal.
 .
 OSC\~8 hyperlinks are produced for these devices.
 .
+If the terminal description provides no way of changing the text
+attributes but does declare that overstriking characters is possible,
+.I grotty
+will use the legacy output format.
+.
 .
 .P
 In keeping with long-standing practice and the rarity of terminals
@@ -180,17 +184,19 @@ option below.
 .
 .
 .\" ====================================================================
-.SS "SGR and OSC support in pagers"
+.SS "Control character support in pagers"
 .\" ====================================================================
 .
 When paging
 .IR grotty 's
 output with
 .MR less 1 ,
-the latter program must be instructed to pass SGR and OSC sequences
+the latter program must be instructed to pass control characters
 through to the device;
 its
-.B \-R
+.\" TODO: using less -r means putting untrusted input on the terminal;
+.\" ponder this
+.B \-r
 option is one way to achieve this
 .RI ( less
 version 566 or later is required for OSC\~8 support).
@@ -220,7 +226,7 @@ and
 .I nroff
 were first developed but which are no longer in wide use.
 .
-SGR escape sequences are not emitted;
+Terminfo capability strings are not emitted;
 bold,
 italic,
 and underlining character attributes are thus not manipulated.
@@ -428,7 +434,7 @@ Use
 legacy output format
 (see subsection \[lq]Legacy output format\[rq] above).
 .
-SGR and OSC escape sequences are not emitted.
+Terminfo capability strings and OSC escape sequences are not emitted.
 .
 .
 .TP
@@ -487,7 +493,8 @@ or
 .B \-h
 Use literal horizontal tab characters in the output.
 .
-Tabs are assumed to be set every 8 columns.
+Unless the terminal description says otherwise,
+tabs are assumed to be set every 8 columns.
 .
 .
 .TP
@@ -496,7 +503,7 @@ Render oblique-styled fonts
 .RB ( I
 and
 .BR BI )
-with the SGR attribute for italic text
+with the terminfo capability for italic text
 rather than underlined text.
 .
 Many terminals don't support this attribute;
@@ -509,6 +516,8 @@ Ignored if
 .B \-c
 is also specified.
 .
+Disables the overstriking fallback for hardcopy terminals.
+.
 .
 .TP
 .B \-o
@@ -523,7 +532,7 @@ Render oblique-styled fonts
 .RB ( I
 and
 .BR BI )
-with the SGR attribute for reverse video text
+with the terminfo capability for reverse video text
 rather than underlined text.
 .
 Ignored if
@@ -569,6 +578,13 @@ option were specified;
 see subsection \[lq]Legacy output format\[rq] above.
 .
 .
+.TP
+.I TERM
+The terminal type interpreted by the
+.MR terminfo 3
+library.
+.
+.
 .br
 .ne 3v \" Keep section heading and paragraph tag together.
 .\" ====================================================================
diff --git a/src/devices/grotty/grotty.am b/src/devices/grotty/grotty.am
index 14921c562..a9d2fe1c1 100644
--- a/src/devices/grotty/grotty.am
+++ b/src/devices/grotty/grotty.am
@@ -20,7 +20,8 @@ grotty_SOURCES = src/devices/grotty/tty.cpp
 grotty_LDADD = $(LIBM) \
   libdriver.a \
   libgroff.a \
-  lib/libgnu.a
+  lib/libgnu.a \
+  -lcurses
 man1_MANS += src/devices/grotty/grotty.1
 EXTRA_DIST += \
   src/devices/grotty/grotty.1.man \
diff --git a/src/devices/grotty/tty.cpp b/src/devices/grotty/tty.cpp
index 1b92ad8c0..bc016149d 100644
--- a/src/devices/grotty/tty.cpp
+++ b/src/devices/grotty/tty.cpp
@@ -21,6 +21,11 @@ along with this program.  If not, see 
<http://www.gnu.org/licenses/>. */
 #include "device.h"
 #include "ptable.h"

+#include <stringclass.h>
+
+#include <curses.h>
+#include <term.h>
+
 typedef signed char schar;

 declare_ptable(schar)
@@ -28,8 +33,6 @@ implement_ptable(schar)

 extern "C" const char *Version_string;

-#define putstring(s) fputs(s, stdout)
-
 #ifndef SHRT_MIN
 #define SHRT_MIN (-32768)
 #endif
@@ -38,7 +41,7 @@ extern "C" const char *Version_string;
 #define SHRT_MAX 32767
 #endif

-#define TAB_WIDTH 8
+static int tab_width = 8;

 // A character of the output device fits in a 32-bit word.
 typedef unsigned int output_character;
@@ -49,67 +52,57 @@ static bool want_emboldening_by_overstriking = true;
 static bool do_bold;
 static bool want_italics_by_underlining = true;
 static bool do_underline;
-static bool want_glyph_composition_by_overstriking = true;
+static bool accept_glyph_composition_by_overstriking = true;
+static bool do_glyph_composition_by_overstriking;
 static bool allow_drawing_commands = true;
-static bool want_sgr_italics = false;
-static bool do_sgr_italics;
+static bool want_real_italics = false;
 static bool want_reverse_video_for_italics = false;
-static bool do_reverse_video;
 static bool use_overstriking_drawing_scheme = false;

 static void update_options();
 static void usage(FILE *stream);

+// TODO(humm): We can get proper line drawing characters from terminfo
+// using acsc, smacs, rmacs; we could do that for -Tascii + terminfo.
+// There are other characters there we could theoreticlly use as well,
+// like degree and pi.
 static int hline_char = '-';
 static int vline_char = '|';

+typedef unsigned char glyph_mode;
 enum {
   UNDERLINE_MODE = 0x01,
   BOLD_MODE = 0x02,
   VDRAW_MODE = 0x04,
   HDRAW_MODE = 0x08,
   CU_MODE = 0x10,
-  COLOR_CHANGE = 0x20,
+  URI_MODE = 0x20,
   START_LINE = 0x40,
   END_LINE = 0x80
 };

 // Mode to use for bold-underlining.
-static unsigned char bold_underline_mode_option = BOLD_MODE|UNDERLINE_MODE;
-static unsigned char bold_underline_mode;
+static glyph_mode bold_underline_mode_option = BOLD_MODE|UNDERLINE_MODE;
+static glyph_mode bold_underline_mode;

 #ifndef IS_EBCDIC_HOST
-#define CSI "\033["
 #define OSC8 "\033]8"
 #define ST "\033\\"
 #else
-#define CSI "\047["
 #define OSC8 "\047]8"
 #define ST "\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"
-#define SGR_REVERSE CSI "7m"
-#define SGR_NO_REVERSE CSI "27m"
-// many terminals can't handle 'CSI 39 m' and 'CSI 49 m' to reset
-// the foreground and background color, respectively; we thus use
-// 'CSI 0 m' exclusively
-#define SGR_DEFAULT CSI "0m"
+static char *enter_italics_or_the_like_mode;

 #define DEFAULT_COLOR_IDX -1

 class tty_font : public font {
   tty_font(const char *);
-  unsigned char mode;
+  glyph_mode mode;
 public:
   ~tty_font();
-  unsigned char get_mode() { return mode; }
+  glyph_mode get_mode() { return mode; }
 #if 0
   void handle_x_command(int argc, const char **argv);
 #endif
@@ -126,14 +119,14 @@ tty_font *tty_font::load_tty_font(const char *s)
   const char *num = f->get_internal_name();
   long n;
   if (num != 0 && (n = strtol(num, 0, 0)) != 0)
-    f->mode = (unsigned char)(n & (BOLD_MODE|UNDERLINE_MODE));
+    f->mode = (glyph_mode)(n & (BOLD_MODE|UNDERLINE_MODE));
   if (!do_underline)
     f->mode &= ~UNDERLINE_MODE;
   if (!do_bold)
     f->mode &= ~BOLD_MODE;
   if ((f->mode & (BOLD_MODE|UNDERLINE_MODE)) == (BOLD_MODE|UNDERLINE_MODE))
-    f->mode = (unsigned char)((f->mode & ~(BOLD_MODE|UNDERLINE_MODE))
-                             | bold_underline_mode);
+    f->mode = ((f->mode & ~(BOLD_MODE|UNDERLINE_MODE))
+              | bold_underline_mode);
   return f;
 }

@@ -162,17 +155,18 @@ public:
   int w;
   int hpos;
   unsigned int code;
-  unsigned char mode;
+  glyph_mode mode;
   schar back_color_idx;
   schar fore_color_idx;
-  inline int draw_mode() { return mode & (VDRAW_MODE|HDRAW_MODE); }
+  string osc8_string;
+  inline glyph_mode draw_mode() { return mode & (VDRAW_MODE|HDRAW_MODE); }
   inline int order() {
-    return mode & (VDRAW_MODE|HDRAW_MODE|CU_MODE|COLOR_CHANGE); }
+    return mode & (VDRAW_MODE|HDRAW_MODE|CU_MODE|URI_MODE); }
 };


 class tty_printer : public printer {
-  tty_glyph **lines;
+  tty_glyph **lines_; // clashes with Curses lines
   int nlines;
   int cached_v;
   int cached_vpos;
@@ -182,12 +176,11 @@ class tty_printer : public printer {
   bool is_boldfacing;
   bool is_continuously_underlining;
   PTABLE(schar) tty_colors;
-  void make_underline(int);
-  void make_bold(output_character, int);
+  void overstrike(bool, bool, output_character, int);
+  void update_attributes(bool, bool, schar, schar);
   schar color_to_idx(color *);
   void add_char(output_character, int, int, int, color *, color *,
-               unsigned char);
-  void simple_add_char(const output_character, const environment *);
+               const string &, glyph_mode);
   char *make_rgb_string(unsigned int, unsigned int, unsigned int);
   bool tty_color(unsigned int, unsigned int, unsigned int, schar *,
                 schar = DEFAULT_COLOR_IDX);
@@ -201,10 +194,7 @@ public:
   void set_char(glyph *, font *, const environment *, int, const char *);
   void draw(int, int *, int, const environment *);
   void special(char *, const environment *, char);
-  void change_color(const environment * const);
-  void change_fill_color(const environment * const);
   void put_char(output_character);
-  void put_color(schar, int);
   void begin_page(int) { }
   void end_page(int);
   font *make_font(const char *);
@@ -271,20 +261,35 @@ tty_printer::tty_printer() : cached_v(0)
   (void)tty_color(color::MAX_COLOR_VAL, 0, color::MAX_COLOR_VAL, &dummy, 5);
   (void)tty_color(0, color::MAX_COLOR_VAL, color::MAX_COLOR_VAL, &dummy, 6);
   nlines = 66;
-  lines = new tty_glyph *[nlines];
+  lines_ = new tty_glyph *[nlines];
   for (int i = 0; i < nlines; i++)
-    lines[i] = 0;
+    lines_[i] = 0;
   is_continuously_underlining = false;
 }

 tty_printer::~tty_printer()
 {
-  delete[] lines;
+  delete[] lines_;
 }

-void tty_printer::make_underline(int w)
+// The function overstrike applies text attributes for the next glyph
+// rendered using the overstriking scheme.  The function
+// update_attributes uses terminfo capabilities to apply text attributes
+// for all following glyphs, until it's called again with different
+// arguments.  If update_attributes is called with the same arguments as
+// the last time, it doesn't print anything.
+//
+// Thus, call overstrike for every single glyph you print and call
+// update_attributes either whenever the attributes change or for every
+// glyph, whatever is more convenient.  Do call both.
+
+void tty_printer::overstrike(bool underline, bool bold,
+                            output_character c, int w)
 {
-  if (use_overstriking_drawing_scheme) {
+  if (!use_overstriking_drawing_scheme)
+    return;
+
+  if (underline) {
     if (!w)
       warning("can't underline zero-width character");
     else {
@@ -292,22 +297,7 @@ void tty_printer::make_underline(int w)
       putchar('\b');
     }
   }
-  else {
-    if (!is_underlining) {
-      if (do_sgr_italics)
-       putstring(SGR_ITALIC);
-      else if (do_reverse_video)
-       putstring(SGR_REVERSE);
-      else
-       putstring(SGR_UNDERLINE);
-    }
-    is_underlining = true;
-  }
-}
-
-void tty_printer::make_bold(output_character c, int w)
-{
-  if (use_overstriking_drawing_scheme) {
+  if (bold) {
     if (!w)
       warning("can't print zero-width character in bold");
     else {
@@ -315,11 +305,42 @@ void tty_printer::make_bold(output_character c, int w)
       putchar('\b');
     }
   }
-  else {
-    if (!is_boldfacing)
-      putstring(SGR_BOLD);
+}
+
+void tty_printer::update_attributes(bool underline, bool bold,
+                                   schar fore_idx, schar back_idx)
+{
+  if (use_overstriking_drawing_scheme)
+    return;
+
+  if (is_underlining && !underline
+      || is_boldfacing && !bold
+      || curr_fore_idx != DEFAULT_COLOR_IDX && fore_idx == DEFAULT_COLOR_IDX
+      || curr_back_idx != DEFAULT_COLOR_IDX && back_idx == DEFAULT_COLOR_IDX) {
+    putp(exit_attribute_mode);
+    is_underlining = is_boldfacing = false;
+    curr_fore_idx = curr_back_idx = DEFAULT_COLOR_IDX;
+  }
+
+  if (!is_underlining && underline) {
+    putp(enter_italics_or_the_like_mode);
+    is_underlining = true;
+  }
+
+  if (!is_boldfacing && bold) {
+    putp(enter_bold_mode);
     is_boldfacing = true;
   }
+
+  if (curr_fore_idx != fore_idx) {
+    putp(tparm(set_a_foreground, fore_idx, 0, 0, 0, 0, 0, 0, 0, 0));
+    curr_fore_idx = fore_idx;
+  }
+
+  if (curr_back_idx != back_idx) {
+    putp(tparm(set_a_background, back_idx, 0, 0, 0, 0, 0, 0, 0, 0));
+    curr_back_idx = back_idx;
+  }
 }

 schar tty_printer::color_to_idx(color *col)
@@ -345,13 +366,15 @@ void tty_printer::set_char(glyph *g, font *f, const 
environment *env,
   add_char(f->get_code(g), w,
           env->hpos, env->vpos,
           env->col, env->fill,
+          string(),
           ((tty_font *)f)->get_mode());
 }

 void tty_printer::add_char(output_character c, int w,
                           int h, int v,
                           color *fore, color *back,
-                          unsigned char mode)
+                          const string &osc8_string,
+                          glyph_mode mode)
 {
 #if 0
   // This is too expensive.
@@ -372,11 +395,11 @@ void tty_printer::add_char(output_character c, int w,
            " quantum");
     vpos = v / font::vert;
     if (vpos > nlines) {
-      tty_glyph **old_lines = lines;
-      lines = new tty_glyph *[vpos + 1];
-      memcpy(lines, old_lines, nlines * sizeof(tty_glyph *));
+      tty_glyph **old_lines = lines_;
+      lines_ = new tty_glyph *[vpos + 1];
+      memcpy(lines_, old_lines, nlines * sizeof(tty_glyph *));
       for (int i = nlines; i <= vpos; i++)
-       lines[i] = 0;
+       lines_[i] = 0;
       delete[] old_lines;
       nlines = vpos + 1;
     }
@@ -395,15 +418,16 @@ void tty_printer::add_char(output_character c, int w,
   g->code = c;
   g->fore_color_idx = color_to_idx(fore);
   g->back_color_idx = color_to_idx(back);
+  g->osc8_string = osc8_string;
   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.
+  // increasing order of hpos, with CU and URI specials before HDRAW
+  // characters before VDRAW characters before normal characters at
+  // each hpos, and otherwise in order of occurrence.

   tty_glyph **pp;
-  for (pp = lines + (vpos - 1); *pp; pp = &(*pp)->next)
+  for (pp = lines_ + (vpos - 1); *pp; pp = &(*pp)->next)
     if ((*pp)->hpos < hpos
        || ((*pp)->hpos == hpos && (*pp)->order() >= g->order()))
       break;
@@ -411,17 +435,11 @@ void tty_printer::add_char(output_character c, int w,
   *pp = g;
 }

-void tty_printer::simple_add_char(const output_character c,
-                                 const environment *env)
-{
-  add_char(c, 0, env->hpos, env->vpos, env->col, env->fill, 0);
-}
-
 void tty_printer::special(char *arg, const environment *env, char type)
 {
   if (type == 'u') {
     add_char(*arg - '0', 0, env->hpos, env->vpos, env->col, env->fill,
-            CU_MODE);
+            string(), CU_MODE);
     return;
   }
   if (type != 'p')
@@ -452,38 +470,35 @@ void tty_printer::special(char *arg, const environment 
*env, char type)
     warning("unrecognized X command '%1' ignored", command);
 }

-// Produce an OSC 8 hyperlink.  Given ditroff input of the form:
+// Produce an OSC 8 hyperlink.  Given ditroff output of the form:
 //   x X tty: link [URI[ KEY=VALUE] ...]
-// produce "OSC 8 [;KEY=VALUE];[URI] ST".  KEY/VALUE pairs can be
+// produce "OSC 8 ;[KEY=VALUE];[URI] ST".  KEY/VALUE pairs can be
 // repeated arbitrarily and are separated by colons.  Omission of the
 // URI ends the hyperlink that was begun by specifying it.  See
 // <https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda>.
+//
+// TODO: Discard URIs with prohibited bytes.  Only 08-0D and 20-7E are
+// allowed between OSC and ST.
 void tty_printer::special_link(const char *arg, const environment *env)
 {
   static bool is_link_active = false;
   if (use_overstriking_drawing_scheme)
     return;
-  for (const char *s = OSC8; *s != '\0'; s++)
-    simple_add_char(*s, env);
-  simple_add_char(';', env);
   char c = *arg;
   if ('\0' == c || '\n' == c) {
-    simple_add_char(';', env);
     if (!is_link_active)
       warning("ending hyperlink when none was started");
+    else
+      add_char(0, 0, env->hpos, env->vpos, env->col, env->fill,
+              string(), URI_MODE);
     is_link_active = false;
   }
   else {
     // Our caller ensures that we see whitespace after 'link'.
     assert(c == ' ' || c == '\t');
-    if (is_link_active) {
-      warning("new hyperlink started without ending previous one;"
-             " recovering");
-      simple_add_char(';', env);
-       for (const char *s = ST OSC8; *s != '\0'; s++)
-         simple_add_char(*s, env);
-       simple_add_char(';', env);
-    }
+    string s = ";";
+    if (is_link_active)
+      warning("new hyperlink started without ending previous one");
     is_link_active = true;
     do
       c = *arg++;
@@ -501,7 +516,7 @@ void tty_printer::special_link(const char *arg, const 
environment *env)
     bool done = false;
     do {
       if (pair != 0)
-       simple_add_char(':', env);
+       s += ':';
       pair = arg;
       bool in_pair = true;
       do {
@@ -511,25 +526,14 @@ void tty_printer::special_link(const char *arg, const 
environment *env)
        else if (' ' == c || '\t' == c)
          in_pair = false;
        else
-         simple_add_char(c, env);
+         s += c;
       } while (!done && in_pair);
     } while (!done);
-    simple_add_char(';', env);
-    for (size_t i = uri_len; i > 0; i--)
-      simple_add_char(*uri++, env);
-  }
-  for (const char *s = ST; *s != '\0'; s++)
-    simple_add_char(*s, env);
-}
+    s += ';';
+    s.append(uri, uri_len);

-void tty_printer::change_color(const environment * const env)
-{
-  add_char(0, 0, env->hpos, env->vpos, env->col, env->fill, COLOR_CHANGE);
-}
-
-void tty_printer::change_fill_color(const environment * const env)
-{
-  add_char(0, 0, env->hpos, env->vpos, env->col, env->fill, COLOR_CHANGE);
+    add_char(0, 0, env->hpos, env->vpos, env->col, env->fill, s, URI_MODE);
+  }
 }

 void tty_printer::draw(int code, int *p, int np, const environment *env)
@@ -617,20 +621,20 @@ void tty_printer::line(int hpos, int vpos, int dx, int dy,
       len = -len;
     }
     if (len == 0)
-      add_char(vline_char, font::hor, hpos, v, col, fill,
+      add_char(vline_char, font::hor, hpos, v, col, fill, string(),
               VDRAW_MODE|START_LINE|END_LINE);
     else {
-      add_char(vline_char, font::hor, hpos, v, col, fill,
+      add_char(vline_char, font::hor, hpos, v, col, fill, string(),
               VDRAW_MODE|START_LINE);
       len -= font::vert;
       v += font::vert;
       while (len > 0) {
-       add_char(vline_char, font::hor, hpos, v, col, fill,
+       add_char(vline_char, font::hor, hpos, v, col, fill, string(),
                 VDRAW_MODE|START_LINE|END_LINE);
        len -= font::vert;
        v += font::vert;
       }
-      add_char(vline_char, font::hor, hpos, v, col, fill,
+      add_char(vline_char, font::hor, hpos, v, col, fill, string(),
               VDRAW_MODE|END_LINE);
     }
   }
@@ -643,20 +647,20 @@ void tty_printer::line(int hpos, int vpos, int dx, int dy,
       len = -len;
     }
     if (len == 0)
-      add_char(hline_char, font::hor, h, vpos, col, fill,
+      add_char(hline_char, font::hor, h, vpos, col, fill, string(),
               HDRAW_MODE|START_LINE|END_LINE);
     else {
-      add_char(hline_char, font::hor, h, vpos, col, fill,
+      add_char(hline_char, font::hor, h, vpos, col, fill, string(),
               HDRAW_MODE|START_LINE);
       len -= font::hor;
       h += font::hor;
       while (len > 0) {
-       add_char(hline_char, font::hor, h, vpos, col, fill,
+       add_char(hline_char, font::hor, h, vpos, col, fill, string(),
                 HDRAW_MODE|START_LINE|END_LINE);
        len -= font::hor;
        h += font::hor;
       }
-      add_char(hline_char, font::hor, h, vpos, col, fill,
+      add_char(hline_char, font::hor, h, vpos, col, fill, string(),
               HDRAW_MODE|END_LINE);
     }
   }
@@ -683,42 +687,12 @@ void tty_printer::put_char(output_character wc)
     do *++p = (unsigned char)(((wc >> (6 * --count)) & 0x3f) | 0x80);
       while (count > 0);
     *++p = '\0';
-    putstring(buf);
+    fputs(buf, stdout);
   }
   else
     putchar(wc);
 }

-void tty_printer::put_color(schar color_index, int back)
-{
-  if (color_index == DEFAULT_COLOR_IDX) {
-    putstring(SGR_DEFAULT);
-    // set bold and underline again
-    if (is_boldfacing)
-      putstring(SGR_BOLD);
-    if (is_underlining) {
-      if (do_sgr_italics)
-       putstring(SGR_ITALIC);
-      else if (do_reverse_video)
-       putstring(SGR_REVERSE);
-      else
-       putstring(SGR_UNDERLINE);
-    }
-    // set other color again
-    back = !back;
-    color_index = back ? curr_back_idx : curr_fore_idx;
-  }
-  if (color_index != DEFAULT_COLOR_IDX) {
-    putstring(CSI);
-    if (back)
-      putchar('4');
-    else
-      putchar('3');
-    putchar(color_index + '0');
-    putchar('m');
-  }
-}
-
 // The possible Unicode combinations for crossing characters.
 //
 // '  ' = 0, ' -' = 4, '- ' = 8, '--' = 12,
@@ -741,7 +715,7 @@ void tty_printer::end_page(int page_length)
   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])
+    if (lines_[last_line - 1])
       break;
 #if 0
   if (last_line > lines_per_page) {
@@ -757,8 +731,8 @@ void tty_printer::end_page(int page_length)
   }
 #endif
   for (int i = 0; i < last_line; i++) {
-    tty_glyph *p = lines[i];
-    lines[i] = 0;
+    tty_glyph *p = lines_[i];
+    lines_[i] = 0;
     tty_glyph *g = 0;
     while (p) {
       tty_glyph *tem = p->next;
@@ -778,7 +752,8 @@ void tty_printer::end_page(int page_length)
        is_continuously_underlining = (p->code != 0);
        continue;
       }
-      if (nextp && p->hpos == nextp->hpos) {
+      if (nextp && p->hpos == nextp->hpos
+         && !(p->mode & URI_MODE) && !(nextp->mode & URI_MODE)) {
        if (p->draw_mode() == HDRAW_MODE &&
            nextp->draw_mode() == VDRAW_MODE) {
          if (font::is_unicode)
@@ -793,101 +768,60 @@ void tty_printer::end_page(int page_length)
          nextp->code = p->code;
          continue;
        }
-       if (!want_glyph_composition_by_overstriking)
+       if (!do_glyph_composition_by_overstriking)
          continue;
       }
+      // TODO(humm): Is this branch ever taken?  See the loop at the end
+      // of add_char.
       if (hpos > p->hpos) {
        do {
          putchar('\b');
          hpos--;
        } while (hpos > p->hpos);
       }
-      else {
+      else if (p->hpos > hpos) {
+       update_attributes(is_continuously_underlining, is_boldfacing,
+                         curr_fore_idx, curr_back_idx);
        if (want_horizontal_tabs) {
          for (;;) {
-           int next_tab_pos = ((hpos + TAB_WIDTH) / TAB_WIDTH) * TAB_WIDTH;
+           int next_tab_pos = ((hpos + tab_width) / tab_width) * tab_width;
            if (next_tab_pos > p->hpos)
              break;
-           if (is_continuously_underlining)
-             make_underline(p->w);
-           else if (!use_overstriking_drawing_scheme
-                    && is_underlining) {
-             if (do_sgr_italics)
-               putstring(SGR_NO_ITALIC);
-             else if (do_reverse_video)
-               putstring(SGR_NO_REVERSE);
-             else
-               putstring(SGR_NO_UNDERLINE);
-             is_underlining = false;
-           }
+           // TODO(humm): Should we not take the width of the actual
+           // tab?  The current status works, albeit not with
+           // typewriters: _\b\t is rendered as multiple underlined
+           // cells by less.  We could pass overstrike the width
+           // the actual tab will have and let it emit multiple
+           // underlines.
+           overstrike(is_continuously_underlining, false, '\t', p->w);
            putchar('\t');
            hpos = next_tab_pos;
          }
        }
        for (; hpos < p->hpos; hpos++) {
-         if (is_continuously_underlining)
-           make_underline(p->w);
-         else if (!use_overstriking_drawing_scheme && is_underlining) {
-           if (do_sgr_italics)
-             putstring(SGR_NO_ITALIC);
-           else if (do_reverse_video)
-             putstring(SGR_NO_REVERSE);
-           else
-             putstring(SGR_NO_UNDERLINE);
-           is_underlining = false;
-         }
+         overstrike(is_continuously_underlining, false, ' ', p->w);
          putchar(' ');
        }
       }
       assert(hpos == p->hpos);
-      if (p->mode & COLOR_CHANGE) {
-       if (!use_overstriking_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(p->w);
-      else if (!use_overstriking_drawing_scheme && is_underlining) {
-       if (do_sgr_italics)
-         putstring(SGR_NO_ITALIC);
-       else if (do_reverse_video)
-         putstring(SGR_NO_REVERSE);
+      if (p->mode & URI_MODE) {
+       if (nextp && (nextp->mode & URI_MODE))
+         continue;
+       fputs(OSC8, stdout);
+       if (p->osc8_string.empty())
+         fputs(";;", stdout);
        else
-         putstring(SGR_NO_UNDERLINE);
-       is_underlining = false;
-      }
-      if (p->mode & BOLD_MODE)
-       make_bold(p->code, p->w);
-      else if (!use_overstriking_drawing_scheme && is_boldfacing) {
-       putstring(SGR_NO_BOLD);
-       is_boldfacing = false;
-      }
-      if (!use_overstriking_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;
-       }
+         fwrite(p->osc8_string.contents(), 1, p->osc8_string.length(), stdout);
+       fputs(ST, stdout);
+       continue;
       }
+      overstrike(p->mode & UNDERLINE_MODE, p->mode & BOLD_MODE, p->code, p->w);
+      update_attributes(p->mode & UNDERLINE_MODE, p->mode & BOLD_MODE,
+                       p->fore_color_idx, p->back_color_idx);
       put_char(p->code);
       hpos += p->w / font::hor;
     }
-    if (!use_overstriking_drawing_scheme
-       && (is_boldfacing || is_underlining
-           || curr_fore_idx != DEFAULT_COLOR_IDX
-           || curr_back_idx != DEFAULT_COLOR_IDX))
-      putstring(SGR_DEFAULT);
+    update_attributes(false, false, DEFAULT_COLOR_IDX, DEFAULT_COLOR_IDX);
     putchar('\n');
   }
   if (want_form_feeds) {
@@ -913,18 +847,82 @@ printer *make_printer()
 static void update_options()
 {
   if (use_overstriking_drawing_scheme) {
-    do_sgr_italics = false;
-    do_reverse_video = false;
     bold_underline_mode = bold_underline_mode_option;
     do_bold = want_emboldening_by_overstriking;
     do_underline = want_italics_by_underlining;
+    do_glyph_composition_by_overstriking =
+      accept_glyph_composition_by_overstriking;
+    return;
   }
-  else {
-    do_sgr_italics = want_sgr_italics;
-    do_reverse_video = want_reverse_video_for_italics;
-    bold_underline_mode = BOLD_MODE|UNDERLINE_MODE;
-    do_bold = true;
-    do_underline = true;
+
+  bold_underline_mode = BOLD_MODE|UNDERLINE_MODE;
+  do_bold = true;
+  do_underline = true;
+
+  int err;
+  if (setupterm(NULL, 1, &err) == ERR)
+    switch (err) {
+      // It's a little sad that we can't use Curses's own error
+      // strings, just to be able to handle hardcopy terminals,
+      // only because ncurses behaves in a non-standard manner when
+      // stumbling upon the hc capability.
+    case -1:
+      fatal("terminfo database not found");
+    case 0:
+      fatal("terminal description not found");
+    case 1: // hardcopy terminal (non standard) / success (standard)
+      // We check for over_strike anyway.  Ncurses is nice enough
+      // to load the capabilities anyway.
+      ;
+    }
+
+  if (want_real_italics)
+    enter_italics_or_the_like_mode = enter_italics_mode;
+  else if (want_reverse_video_for_italics)
+    enter_italics_or_the_like_mode = enter_reverse_mode;
+  else
+    enter_italics_or_the_like_mode = enter_underline_mode;
+
+  if (tab_width == -2)
+    fatal("bad it (init_tabs) capability");
+
+  tab_width = init_tabs != -1 ? init_tabs : 8;
+
+  if (over_strike == -1)
+    fatal("bad os (over_strike) capability");
+
+  do_glyph_composition_by_overstriking =
+    accept_glyph_composition_by_overstriking && over_strike;
+
+  static const struct {
+    char *cap;
+    char const *nullmsg;
+    char const *negonemsg;
+  } string_caps[] = {
+    // all the string capabilities we use in the program
+    {enter_bold_mode, "can't make text bold", "bad bold capability"},
+    {enter_italics_or_the_like_mode, "can't make text italic (or the like)",
+      "bad italics (or the like) capability"},
+    {exit_attribute_mode, "can't disable text attributes",
+      "bad sgr0 (exit attributes) capability"},
+    {set_a_foreground, "can't colorize text",
+      "bad setaf (foreground color) capability"},
+    {set_a_background, "can't colorize text",
+      "bad setab (background color) capability"},
+  };
+
+  for (int i = 0; i < sizeof string_caps / sizeof *string_caps; ++i) {
+    if (string_caps[i].cap == NULL) {
+      if (over_strike && !want_real_italics && 
!want_reverse_video_for_italics) {
+       use_overstriking_drawing_scheme = true;
+       update_options();
+       return;
+      }
+      else
+       fatal(string_caps[i].nullmsg);
+    }
+    else if (string_caps[i].cap == (char *)-1)
+      fatal(string_caps[i].negonemsg);
   }
 }

@@ -951,7 +949,7 @@ int main(int argc, char **argv)
       break;
     case 'i':
       // Use italic font instead of underlining.
-      want_sgr_italics = true;
+      want_real_italics = true;
       break;
     case 'I':
       // ignore include search path
@@ -970,7 +968,7 @@ int main(int argc, char **argv)
       break;
     case 'o':
       // Do not overstrike (other than emboldening and underlining).
-      want_glyph_composition_by_overstriking = false;
+      accept_glyph_composition_by_overstriking = false;
       break;
     case 'r':
       // Use reverse mode instead of underlining.
-- 
2.42.0




reply via email to

[Prev in Thread] Current Thread [Next in Thread]