(root)/
gettext-0.22.4/
libtextstyle/
gnulib-local/
lib/
term-styled-ostream.oo.c
       1  /* Output stream for CSS styled text, producing ANSI escape sequences.
       2     Copyright (C) 2006-2007, 2019-2020 Free Software Foundation, Inc.
       3     Written by Bruno Haible <bruno@clisp.org>, 2006.
       4  
       5     This program is free software: you can redistribute it and/or modify
       6     it under the terms of the GNU General Public License as published by
       7     the Free Software Foundation; either version 3 of the License, or
       8     (at your option) any later version.
       9  
      10     This program is distributed in the hope that it will be useful,
      11     but WITHOUT ANY WARRANTY; without even the implied warranty of
      12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      13     GNU General Public License for more details.
      14  
      15     You should have received a copy of the GNU General Public License
      16     along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
      17  
      18  #include <config.h>
      19  
      20  /* Specification.  */
      21  #include "term-styled-ostream.h"
      22  
      23  #include <stdlib.h>
      24  
      25  #include <cr-om-parser.h>
      26  #include <cr-sel-eng.h>
      27  #include <cr-style.h>
      28  #include <cr-rgb.h>
      29  /* <cr-fonts.h> has a broken double-inclusion guard in libcroco-0.6.1.  */
      30  #ifndef __CR_FONTS_H__
      31  # include <cr-fonts.h>
      32  #endif
      33  #include <cr-string.h>
      34  
      35  #include "term-ostream.h"
      36  #include "mem-hash-map.h"
      37  #include "xalloc.h"
      38  
      39  
      40  /* CSS matching works as follows:
      41     Suppose we have an element inside class "header" inside class "table".
      42     We pretend to have an XML tree that looks like this:
      43  
      44       (root)
      45         +----table
      46                +----header
      47  
      48     For each of these XML nodes, the CSS matching engine can report the
      49     matching CSS declarations.  We extract the CSS property values that
      50     matter for terminal styling and cache them.  */
      51  
      52  /* Attributes that can be set on a character.  */
      53  typedef struct
      54  {
      55    term_color_t     color;
      56    term_color_t     bgcolor;
      57    term_weight_t    weight;
      58    term_posture_t   posture;
      59    term_underline_t underline;
      60  } attributes_t;
      61  
      62  struct term_styled_ostream : struct styled_ostream
      63  {
      64  fields:
      65    /* The destination stream.  */
      66    term_ostream_t destination;
      67    /* The CSS filename.  */
      68    char *css_filename;
      69    /* The CSS document.  */
      70    CRCascade *css_document;
      71    /* The CSS matching engine.  */
      72    CRSelEng *css_engine;
      73    /* The list of active XML elements, with a space before each.
      74       For example, in above example, it is " table header".  */
      75    char *curr_classes;
      76    size_t curr_classes_length;
      77    size_t curr_classes_allocated;
      78    /* A hash table mapping a list of classes (as a string) to an
      79       'attributes_t *'.  */
      80    hash_table cache;
      81    /* The current attributes.  */
      82    attributes_t *curr_attr;
      83  };
      84  
      85  /* Implementation of ostream_t methods.  */
      86  
      87  static void
      88  term_styled_ostream::write_mem (term_styled_ostream_t stream,
      89                                  const void *data, size_t len)
      90  {
      91    term_ostream_set_color (stream->destination, stream->curr_attr->color);
      92    term_ostream_set_bgcolor (stream->destination, stream->curr_attr->bgcolor);
      93    term_ostream_set_weight (stream->destination, stream->curr_attr->weight);
      94    term_ostream_set_posture (stream->destination, stream->curr_attr->posture);
      95    term_ostream_set_underline (stream->destination, stream->curr_attr->underline);
      96  
      97    term_ostream_write_mem (stream->destination, data, len);
      98  }
      99  
     100  static void
     101  term_styled_ostream::flush (term_styled_ostream_t stream, ostream_flush_scope_t scope)
     102  {
     103    term_ostream_flush (stream->destination, scope);
     104  }
     105  
     106  static void
     107  term_styled_ostream::free (term_styled_ostream_t stream)
     108  {
     109    free (stream->css_filename);
     110    term_ostream_free (stream->destination);
     111    cr_cascade_destroy (stream->css_document);
     112    cr_sel_eng_destroy (stream->css_engine);
     113    free (stream->curr_classes);
     114    {
     115      void *ptr = NULL;
     116      const void *key;
     117      size_t keylen;
     118      void *data;
     119  
     120      while (hash_iterate (&stream->cache, &ptr, &key, &keylen, &data) == 0)
     121        {
     122          free (data);
     123        }
     124    }
     125    hash_destroy (&stream->cache);
     126    free (stream);
     127  }
     128  
     129  /* Implementation of styled_ostream_t methods.  */
     130  
     131  /* CRStyle doesn't contain a value for the 'text-decoration' property.
     132     So we have to extend it.  */
     133  
     134  enum CRXTextDecorationType
     135  {
     136    TEXT_DECORATION_NONE,
     137    TEXT_DECORATION_UNDERLINE,
     138    TEXT_DECORATION_OVERLINE,
     139    TEXT_DECORATION_LINE_THROUGH,
     140    TEXT_DECORATION_BLINK,
     141    TEXT_DECORATION_INHERIT
     142  };
     143  
     144  typedef struct _CRXStyle
     145  {
     146    struct _CRXStyle *parent_style;
     147    CRStyle *base;
     148    enum CRXTextDecorationType text_decoration;
     149  } CRXStyle;
     150  
     151  /* An extended version of cr_style_new.  */
     152  static CRXStyle *
     153  crx_style_new (gboolean a_set_props_to_initial_values)
     154  {
     155    CRStyle *base;
     156    CRXStyle *result;
     157  
     158    base = cr_style_new (a_set_props_to_initial_values);
     159    if (base == NULL)
     160      return NULL;
     161  
     162    result = XMALLOC (CRXStyle);
     163    result->base = base;
     164    if (a_set_props_to_initial_values)
     165      result->text_decoration = TEXT_DECORATION_NONE;
     166    else
     167      result->text_decoration = TEXT_DECORATION_INHERIT;
     168  
     169    return result;
     170  }
     171  
     172  /* An extended version of cr_style_destroy.  */
     173  static void
     174  crx_style_destroy (CRXStyle *a_style)
     175  {
     176    cr_style_destroy (a_style->base);
     177    free (a_style);
     178  }
     179  
     180  /* An extended version of cr_sel_eng_get_matched_style.  */
     181  static enum CRStatus
     182  crx_sel_eng_get_matched_style (CRSelEng * a_this, CRCascade * a_cascade,
     183                                 xmlNode * a_node,
     184                                 CRXStyle * a_parent_style, CRXStyle ** a_style,
     185                                 gboolean a_set_props_to_initial_values)
     186  {
     187    enum CRStatus status;
     188    CRPropList *props = NULL;
     189  
     190    if (!(a_this && a_cascade && a_node && a_style))
     191      return CR_BAD_PARAM_ERROR;
     192  
     193    status = cr_sel_eng_get_matched_properties_from_cascade (a_this, a_cascade,
     194                                                             a_node, &props);
     195    if (!(status == CR_OK))
     196      return status;
     197  
     198    if (props)
     199      {
     200        CRXStyle *style;
     201  
     202        if (!*a_style)
     203          {
     204            *a_style = crx_style_new (a_set_props_to_initial_values);
     205            if (!*a_style)
     206              return CR_ERROR;
     207          }
     208        else
     209          {
     210            if (a_set_props_to_initial_values)
     211              {
     212                cr_style_set_props_to_initial_values ((*a_style)->base);
     213                (*a_style)->text_decoration = TEXT_DECORATION_NONE;
     214              }
     215            else
     216              {
     217                cr_style_set_props_to_default_values ((*a_style)->base);
     218                (*a_style)->text_decoration = TEXT_DECORATION_INHERIT;
     219              }
     220          }
     221        style = *a_style;
     222        style->parent_style = a_parent_style;
     223        style->base->parent_style =
     224          (a_parent_style != NULL ? a_parent_style->base : NULL);
     225  
     226        {
     227          CRPropList *cur;
     228  
     229          for (cur = props; cur != NULL; cur = cr_prop_list_get_next (cur))
     230            {
     231              CRDeclaration *decl = NULL;
     232  
     233              cr_prop_list_get_decl (cur, &decl);
     234              cr_style_set_style_from_decl (style->base, decl);
     235              if (decl != NULL
     236                  && decl->property != NULL
     237                  && decl->property->stryng != NULL
     238                  && decl->property->stryng->str != NULL)
     239                {
     240                  if (strcmp (decl->property->stryng->str, "text-decoration") == 0
     241                      && decl->value != NULL
     242                      && decl->value->type == TERM_IDENT
     243                      && decl->value->content.str != NULL)
     244                    {
     245                      const char *value =
     246                        cr_string_peek_raw_str (decl->value->content.str);
     247  
     248                      if (value != NULL)
     249                        {
     250                          if (strcmp (value, "none") == 0)
     251                            style->text_decoration = TEXT_DECORATION_NONE;
     252                          else if (strcmp (value, "underline") == 0)
     253                            style->text_decoration = TEXT_DECORATION_UNDERLINE;
     254                          else if (strcmp (value, "overline") == 0)
     255                            style->text_decoration = TEXT_DECORATION_OVERLINE;
     256                          else if (strcmp (value, "line-through") == 0)
     257                            style->text_decoration = TEXT_DECORATION_LINE_THROUGH;
     258                          else if (strcmp (value, "blink") == 0)
     259                            style->text_decoration = TEXT_DECORATION_BLINK;
     260                          else if (strcmp (value, "inherit") == 0)
     261                            style->text_decoration = TEXT_DECORATION_INHERIT;
     262                        }
     263                    }
     264                }
     265            }
     266        }
     267  
     268        cr_prop_list_destroy (props);
     269      }
     270  
     271    return CR_OK;
     272  }
     273  
     274  /* According to the CSS2 spec, sections 6.1 and 6.2, we need to do a
     275     propagation: specified values -> computed values -> actual values.
     276     The computed values are necessary.  libcroco does not compute them for us.
     277     The function cr_style_resolve_inherited_properties is also not sufficient:
     278     it handles only the case of inheritance, not the case of non-inheritance.
     279     So we write style accessors that fetch the computed value, doing the
     280     inheritance on the fly.
     281     We then compute the actual values from the computed values; for colors,
     282     this is done through the rgb_to_color method.  */
     283  
     284  static term_color_t
     285  style_compute_color_value (CRStyle *style, enum CRRgbProp which,
     286                             term_ostream_t stream)
     287  {
     288    for (;;)
     289      {
     290        if (style == NULL)
     291          return COLOR_DEFAULT;
     292        if (cr_rgb_is_set_to_inherit (&style->rgb_props[which].sv))
     293          style = style->parent_style;
     294        else if (cr_rgb_is_set_to_transparent (&style->rgb_props[which].sv))
     295          /* A transparent color occurs as default background color, set by
     296             cr_style_set_props_to_default_values.  */
     297          return COLOR_DEFAULT;
     298        else
     299          {
     300            CRRgb rgb;
     301            int r;
     302            int g;
     303            int b;
     304  
     305            cr_rgb_copy (&rgb, &style->rgb_props[which].sv);
     306            if (cr_rgb_compute_from_percentage (&rgb) != CR_OK)
     307              abort ();
     308            r = rgb.red & 0xff;
     309            g = rgb.green & 0xff;
     310            b = rgb.blue & 0xff;
     311            return term_ostream_rgb_to_color (stream, r, g, b);
     312          }
     313      }
     314  }
     315  
     316  static term_weight_t
     317  style_compute_font_weight_value (const CRStyle *style)
     318  {
     319    int value = 0;
     320    for (;;)
     321      {
     322        if (style == NULL)
     323          value += 4;
     324        else
     325          switch (style->font_weight)
     326            {
     327            case FONT_WEIGHT_INHERIT:
     328              style = style->parent_style;
     329              continue;
     330            case FONT_WEIGHT_BOLDER:
     331              value += 1;
     332              style = style->parent_style;
     333              continue;
     334            case FONT_WEIGHT_LIGHTER:
     335              value -= 1;
     336              style = style->parent_style;
     337              continue;
     338            case FONT_WEIGHT_100:
     339              value += 1;
     340              break;
     341            case FONT_WEIGHT_200:
     342              value += 2;
     343              break;
     344            case FONT_WEIGHT_300:
     345              value += 3;
     346              break;
     347            case FONT_WEIGHT_400: case FONT_WEIGHT_NORMAL:
     348              value += 4;
     349              break;
     350            case FONT_WEIGHT_500:
     351              value += 5;
     352              break;
     353            case FONT_WEIGHT_600:
     354              value += 6;
     355              break;
     356            case FONT_WEIGHT_700: case FONT_WEIGHT_BOLD:
     357              value += 7;
     358              break;
     359            case FONT_WEIGHT_800:
     360              value += 8;
     361              break;
     362            case FONT_WEIGHT_900:
     363              value += 9;
     364              break;
     365            default:
     366              abort ();
     367            }
     368        /* Value >= 600 -> WEIGHT_BOLD.  Value <= 500 -> WEIGHT_NORMAL.  */
     369        return (value >= 6 ? WEIGHT_BOLD : WEIGHT_NORMAL);
     370      }
     371  }
     372  
     373  static term_posture_t
     374  style_compute_font_posture_value (const CRStyle *style)
     375  {
     376    for (;;)
     377      {
     378        if (style == NULL)
     379          return POSTURE_DEFAULT;
     380        switch (style->font_style)
     381          {
     382          case FONT_STYLE_INHERIT:
     383            style = style->parent_style;
     384            break;
     385          case FONT_STYLE_NORMAL:
     386            return POSTURE_NORMAL;
     387          case FONT_STYLE_ITALIC:
     388          case FONT_STYLE_OBLIQUE:
     389            return POSTURE_ITALIC;
     390          default:
     391            abort ();
     392          }
     393      }
     394  }
     395  
     396  static term_underline_t
     397  style_compute_text_underline_value (const CRXStyle *style)
     398  {
     399    for (;;)
     400      {
     401        if (style == NULL)
     402          return UNDERLINE_DEFAULT;
     403        switch (style->text_decoration)
     404          {
     405          case TEXT_DECORATION_INHERIT:
     406            style = style->parent_style;
     407            break;
     408          case TEXT_DECORATION_NONE:
     409          case TEXT_DECORATION_OVERLINE:
     410          case TEXT_DECORATION_LINE_THROUGH:
     411          case TEXT_DECORATION_BLINK:
     412            return UNDERLINE_OFF;
     413          case TEXT_DECORATION_UNDERLINE:
     414            return UNDERLINE_ON;
     415          default:
     416            abort ();
     417          }
     418      }
     419  }
     420  
     421  /* Match the current list of CSS classes to the CSS and return the result.  */
     422  static attributes_t *
     423  match (term_styled_ostream_t stream)
     424  {
     425    xmlNodePtr root;
     426    xmlNodePtr curr;
     427    char *p_end;
     428    char *p_start;
     429    CRXStyle *curr_style;
     430    CRStyle *curr_style_base;
     431    attributes_t *attr;
     432  
     433    /* Create a hierarchy of XML nodes.  */
     434    root = xmlNewNode (NULL, (const xmlChar *) "__root__");
     435    root->type = XML_ELEMENT_NODE;
     436    curr = root;
     437    p_end = &stream->curr_classes[stream->curr_classes_length];
     438    p_start = stream->curr_classes;
     439    while (p_start < p_end)
     440      {
     441        char *p;
     442        xmlNodePtr child;
     443  
     444        if (!(*p_start == ' '))
     445          abort ();
     446        p_start++;
     447        for (p = p_start; p < p_end && *p != ' '; p++)
     448          ;
     449  
     450        /* Temporarily replace the ' ' by '\0'.  */
     451        *p = '\0';
     452        child = xmlNewNode (NULL, (const xmlChar *) p_start);
     453        child->type = XML_ELEMENT_NODE;
     454        xmlSetProp (child, (const xmlChar *) "class", (const xmlChar *) p_start);
     455        *p = ' ';
     456  
     457        if (xmlAddChild (curr, child) == NULL)
     458          /* Error! Shouldn't happen.  */
     459          abort ();
     460  
     461        curr = child;
     462        p_start = p;
     463      }
     464  
     465    /* Retrieve the matching CSS declarations.  */
     466    /* Not curr_style = crx_style_new (TRUE); because that assumes that the
     467       default foreground color is black and that the default background color
     468       is white, which is not necessarily true in a terminal context.  */
     469    curr_style = NULL;
     470    for (curr = root; curr != NULL; curr = curr->children)
     471      {
     472        CRXStyle *parent_style = curr_style;
     473        curr_style = NULL;
     474  
     475        if (crx_sel_eng_get_matched_style (stream->css_engine,
     476                                           stream->css_document,
     477                                           curr,
     478                                           parent_style, &curr_style,
     479                                           FALSE) != CR_OK)
     480          abort ();
     481        if (curr_style == NULL)
     482          /* No declarations matched this node.  Inherit all values.  */
     483          curr_style = parent_style;
     484        else
     485          /* curr_style is a new style, inheriting from parent_style.  */
     486          ;
     487      }
     488    curr_style_base = (curr_style != NULL ? curr_style->base : NULL);
     489  
     490    /* Extract the CSS declarations that we can use.  */
     491    attr = XMALLOC (attributes_t);
     492    attr->color =
     493      style_compute_color_value (curr_style_base, RGB_PROP_COLOR,
     494                                 stream->destination);
     495    attr->bgcolor =
     496      style_compute_color_value (curr_style_base, RGB_PROP_BACKGROUND_COLOR,
     497                                 stream->destination);
     498    attr->weight = style_compute_font_weight_value (curr_style_base);
     499    attr->posture = style_compute_font_posture_value (curr_style_base);
     500    attr->underline = style_compute_text_underline_value (curr_style);
     501  
     502    /* Free the style chain.  */
     503    while (curr_style != NULL)
     504      {
     505        CRXStyle *parent_style = curr_style->parent_style;
     506  
     507        crx_style_destroy (curr_style);
     508        curr_style = parent_style;
     509      }
     510  
     511    /* Free the XML nodes.  */
     512    xmlFreeNodeList (root);
     513  
     514    return attr;
     515  }
     516  
     517  /* Match the current list of CSS classes to the CSS and store the result in
     518     stream->curr_attr and in the cache.  */
     519  static void
     520  match_and_cache (term_styled_ostream_t stream)
     521  {
     522    attributes_t *attr = match (stream);
     523    if (hash_insert_entry (&stream->cache,
     524                           stream->curr_classes, stream->curr_classes_length,
     525                           attr) == NULL)
     526      abort ();
     527    stream->curr_attr = attr;
     528  }
     529  
     530  static void
     531  term_styled_ostream::begin_use_class (term_styled_ostream_t stream,
     532                                        const char *classname)
     533  {
     534    size_t classname_len;
     535    char *p;
     536    void *found;
     537  
     538    if (classname[0] == '\0' || strchr (classname, ' ') != NULL)
     539      /* Invalid classname argument.  */
     540      abort ();
     541  
     542    /* Push the classname onto the classname list.  */
     543    classname_len = strlen (classname);
     544    if (stream->curr_classes_length + 1 + classname_len + 1
     545        > stream->curr_classes_allocated)
     546      {
     547        size_t new_allocated = stream->curr_classes_length + 1 + classname_len + 1;
     548        if (new_allocated < 2 * stream->curr_classes_allocated)
     549          new_allocated = 2 * stream->curr_classes_allocated;
     550  
     551        stream->curr_classes = xrealloc (stream->curr_classes, new_allocated);
     552        stream->curr_classes_allocated = new_allocated;
     553      }
     554    p = &stream->curr_classes[stream->curr_classes_length];
     555    *p++ = ' ';
     556    memcpy (p, classname, classname_len);
     557    stream->curr_classes_length += 1 + classname_len;
     558  
     559    /* Uodate stream->curr_attr.  */
     560    if (hash_find_entry (&stream->cache,
     561                         stream->curr_classes, stream->curr_classes_length,
     562                         &found) < 0)
     563      match_and_cache (stream);
     564    else
     565      stream->curr_attr = (attributes_t *) found;
     566  }
     567  
     568  static void
     569  term_styled_ostream::end_use_class (term_styled_ostream_t stream,
     570                                      const char *classname)
     571  {
     572    char *p_end;
     573    char *p_start;
     574    char *p;
     575    void *found;
     576  
     577    if (stream->curr_classes_length == 0)
     578      /* No matching call to begin_use_class.  */
     579      abort ();
     580  
     581    /* Remove the trailing classname.  */
     582    p_end = &stream->curr_classes[stream->curr_classes_length];
     583    p = p_end;
     584    while (*--p != ' ')
     585      ;
     586    p_start = p + 1;
     587    if (!(p_end - p_start == strlen (classname)
     588          && memcmp (p_start, classname, p_end - p_start) == 0))
     589      /* The match ing call to begin_use_class used a different classname.  */
     590      abort ();
     591    stream->curr_classes_length = p - stream->curr_classes;
     592  
     593    /* Update stream->curr_attr.  */
     594    if (hash_find_entry (&stream->cache,
     595                         stream->curr_classes, stream->curr_classes_length,
     596                         &found) < 0)
     597      abort ();
     598    stream->curr_attr = (attributes_t *) found;
     599  }
     600  
     601  static const char *
     602  term_styled_ostream::get_hyperlink_ref (term_styled_ostream_t stream)
     603  {
     604    return term_ostream_get_hyperlink_ref (stream->destination);
     605  }
     606  
     607  static const char *
     608  term_styled_ostream::get_hyperlink_id (term_styled_ostream_t stream)
     609  {
     610    return term_ostream_get_hyperlink_id (stream->destination);
     611  }
     612  
     613  static void
     614  term_styled_ostream::set_hyperlink (term_styled_ostream_t stream,
     615                                      const char *ref, const char *id)
     616  {
     617    term_ostream_set_hyperlink (stream->destination, ref, id);
     618  }
     619  
     620  static void
     621  term_styled_ostream::flush_to_current_style (term_styled_ostream_t stream)
     622  {
     623    term_ostream_set_color (stream->destination, stream->curr_attr->color);
     624    term_ostream_set_bgcolor (stream->destination, stream->curr_attr->bgcolor);
     625    term_ostream_set_weight (stream->destination, stream->curr_attr->weight);
     626    term_ostream_set_posture (stream->destination, stream->curr_attr->posture);
     627    term_ostream_set_underline (stream->destination, stream->curr_attr->underline);
     628  
     629    term_ostream_flush_to_current_style (stream->destination);
     630  }
     631  
     632  /* Constructor.  */
     633  
     634  term_styled_ostream_t
     635  term_styled_ostream_create (int fd, const char *filename, ttyctl_t tty_control,
     636                              const char *css_filename)
     637  {
     638    term_styled_ostream_t stream;
     639    CRStyleSheet *css_file_contents;
     640  
     641    /* If css_filename is NULL, no styling is desired.  The code below would end
     642       up returning NULL anyway.  But it's better to not rely on such details of
     643       libcroco behaviour.  */
     644    if (css_filename == NULL)
     645      return NULL;
     646  
     647    stream = XMALLOC (struct term_styled_ostream_representation);
     648  
     649    stream->base.base.vtable = &term_styled_ostream_vtable;
     650    stream->destination = term_ostream_create (fd, filename, tty_control);
     651    stream->css_filename = xstrdup (css_filename);
     652  
     653    if (cr_om_parser_simply_parse_file ((const guchar *) css_filename,
     654                                        CR_UTF_8, /* CR_AUTO is not supported */
     655                                        &css_file_contents) != CR_OK)
     656      {
     657        free (stream->css_filename);
     658        term_ostream_free (stream->destination);
     659        free (stream);
     660        return NULL;
     661      }
     662    stream->css_document = cr_cascade_new (NULL, css_file_contents, NULL);
     663    stream->css_engine = cr_sel_eng_new ();
     664  
     665    stream->curr_classes_allocated = 60;
     666    stream->curr_classes = XNMALLOC (stream->curr_classes_allocated, char);
     667    stream->curr_classes_length = 0;
     668  
     669    hash_init (&stream->cache, 10);
     670  
     671    match_and_cache (stream);
     672  
     673    return stream;
     674  }
     675  
     676  /* Accessors.  */
     677  
     678  static term_ostream_t
     679  term_styled_ostream::get_destination (term_styled_ostream_t stream)
     680  {
     681    return stream->destination;
     682  }
     683  
     684  static const char *
     685  term_styled_ostream::get_css_filename (term_styled_ostream_t stream)
     686  {
     687    return stream->css_filename;
     688  }
     689  
     690  /* Instanceof test.  */
     691  
     692  bool
     693  is_instance_of_term_styled_ostream (ostream_t stream)
     694  {
     695    return IS_INSTANCE (stream, ostream, term_styled_ostream);
     696  }