diff options
Diffstat (limited to 'gnulib-local/lib/term-styled-ostream.oo.c')
-rw-r--r-- | gnulib-local/lib/term-styled-ostream.oo.c | 631 |
1 files changed, 631 insertions, 0 deletions
diff --git a/gnulib-local/lib/term-styled-ostream.oo.c b/gnulib-local/lib/term-styled-ostream.oo.c new file mode 100644 index 0000000..a08bf65 --- /dev/null +++ b/gnulib-local/lib/term-styled-ostream.oo.c @@ -0,0 +1,631 @@ +/* Output stream for CSS styled text, producing ANSI escape sequences. + Copyright (C) 2006-2007 Free Software Foundation, Inc. + Written by Bruno Haible <bruno@clisp.org>, 2006. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#include <config.h> + +/* Specification. */ +#include "term-styled-ostream.h" + +#include <stdlib.h> + +#include <cr-om-parser.h> +#include <cr-sel-eng.h> +#include <cr-style.h> +#include <cr-rgb.h> +/* <cr-fonts.h> has a broken double-inclusion guard in libcroco-0.6.1. */ +#ifndef __CR_FONTS_H__ +# include <cr-fonts.h> +#endif +#include <cr-string.h> + +#include "term-ostream.h" +#include "hash.h" +#include "xalloc.h" + + +/* CSS matching works as follows: + Suppose we have an element inside class "header" inside class "table". + We pretend to have an XML tree that looks like this: + + (root) + +----table + +----header + + For each of these XML nodes, the CSS matching engine can report the + matching CSS declarations. We extract the CSS property values that + matter for terminal styling and cache them. */ + +/* Attributes that can be set on a character. */ +typedef struct +{ + term_color_t color; + term_color_t bgcolor; + term_weight_t weight; + term_posture_t posture; + term_underline_t underline; +} attributes_t; + +struct term_styled_ostream : struct styled_ostream +{ +fields: + /* The destination stream. */ + term_ostream_t destination; + /* The CSS document. */ + CRCascade *css_document; + /* The CSS matching engine. */ + CRSelEng *css_engine; + /* The list of active XML elements, with a space before each. + For example, in above example, it is " table header". */ + char *curr_classes; + size_t curr_classes_length; + size_t curr_classes_allocated; + /* A hash table mapping a list of classes (as a string) to an + 'attributes_t *'. */ + hash_table cache; + /* The current attributes. */ + attributes_t *curr_attr; +}; + +/* Implementation of ostream_t methods. */ + +static void +term_styled_ostream::write_mem (term_styled_ostream_t stream, + const void *data, size_t len) +{ + term_ostream_set_color (stream->destination, stream->curr_attr->color); + term_ostream_set_bgcolor (stream->destination, stream->curr_attr->bgcolor); + term_ostream_set_weight (stream->destination, stream->curr_attr->weight); + term_ostream_set_posture (stream->destination, stream->curr_attr->posture); + term_ostream_set_underline (stream->destination, stream->curr_attr->underline); + + term_ostream_write_mem (stream->destination, data, len); +} + +static void +term_styled_ostream::flush (term_styled_ostream_t stream) +{ + term_ostream_flush (stream->destination); +} + +static void +term_styled_ostream::free (term_styled_ostream_t stream) +{ + term_ostream_free (stream->destination); + cr_cascade_destroy (stream->css_document); + cr_sel_eng_destroy (stream->css_engine); + free (stream->curr_classes); + { + void *ptr = NULL; + const void *key; + size_t keylen; + void *data; + + while (hash_iterate (&stream->cache, &ptr, &key, &keylen, &data) == 0) + { + free (data); + } + } + hash_destroy (&stream->cache); + free (stream); +} + +/* Implementation of styled_ostream_t methods. */ + +/* CRStyle doesn't contain a value for the 'text-decoration' property. + So we have to extend it. */ + +enum CRXTextDecorationType +{ + TEXT_DECORATION_NONE, + TEXT_DECORATION_UNDERLINE, + TEXT_DECORATION_OVERLINE, + TEXT_DECORATION_LINE_THROUGH, + TEXT_DECORATION_BLINK, + TEXT_DECORATION_INHERIT +}; + +typedef struct _CRXStyle +{ + struct _CRXStyle *parent_style; + CRStyle *base; + enum CRXTextDecorationType text_decoration; +} CRXStyle; + +/* An extended version of cr_style_new. */ +static CRXStyle * +crx_style_new (gboolean a_set_props_to_initial_values) +{ + CRStyle *base; + CRXStyle *result; + + base = cr_style_new (a_set_props_to_initial_values); + if (base == NULL) + return NULL; + + result = XMALLOC (CRXStyle); + result->base = base; + if (a_set_props_to_initial_values) + result->text_decoration = TEXT_DECORATION_NONE; + else + result->text_decoration = TEXT_DECORATION_INHERIT; + + return result; +} + +/* An extended version of cr_style_destroy. */ +static void +crx_style_destroy (CRXStyle *a_style) +{ + cr_style_destroy (a_style->base); + free (a_style); +} + +/* An extended version of cr_sel_eng_get_matched_style. */ +static enum CRStatus +crx_sel_eng_get_matched_style (CRSelEng * a_this, CRCascade * a_cascade, + xmlNode * a_node, + CRXStyle * a_parent_style, CRXStyle ** a_style, + gboolean a_set_props_to_initial_values) +{ + enum CRStatus status; + CRPropList *props = NULL; + + if (!(a_this && a_cascade && a_node && a_style)) + return CR_BAD_PARAM_ERROR; + + status = cr_sel_eng_get_matched_properties_from_cascade (a_this, a_cascade, + a_node, &props); + if (!(status == CR_OK)) + return status; + + if (props) + { + CRXStyle *style; + + if (!*a_style) + { + *a_style = crx_style_new (a_set_props_to_initial_values); + if (!*a_style) + return CR_ERROR; + } + else + { + if (a_set_props_to_initial_values) + { + cr_style_set_props_to_initial_values ((*a_style)->base); + (*a_style)->text_decoration = TEXT_DECORATION_NONE; + } + else + { + cr_style_set_props_to_default_values ((*a_style)->base); + (*a_style)->text_decoration = TEXT_DECORATION_INHERIT; + } + } + style = *a_style; + style->parent_style = a_parent_style; + style->base->parent_style = + (a_parent_style != NULL ? a_parent_style->base : NULL); + + { + CRPropList *cur; + + for (cur = props; cur != NULL; cur = cr_prop_list_get_next (cur)) + { + CRDeclaration *decl = NULL; + + cr_prop_list_get_decl (cur, &decl); + cr_style_set_style_from_decl (style->base, decl); + if (decl != NULL + && decl->property != NULL + && decl->property->stryng != NULL + && decl->property->stryng->str != NULL) + { + if (strcmp (decl->property->stryng->str, "text-decoration") == 0 + && decl->value != NULL + && decl->value->type == TERM_IDENT + && decl->value->content.str != NULL) + { + const char *value = + cr_string_peek_raw_str (decl->value->content.str); + + if (value != NULL) + { + if (strcmp (value, "none") == 0) + style->text_decoration = TEXT_DECORATION_NONE; + else if (strcmp (value, "underline") == 0) + style->text_decoration = TEXT_DECORATION_UNDERLINE; + else if (strcmp (value, "overline") == 0) + style->text_decoration = TEXT_DECORATION_OVERLINE; + else if (strcmp (value, "line-through") == 0) + style->text_decoration = TEXT_DECORATION_LINE_THROUGH; + else if (strcmp (value, "blink") == 0) + style->text_decoration = TEXT_DECORATION_BLINK; + else if (strcmp (value, "inherit") == 0) + style->text_decoration = TEXT_DECORATION_INHERIT; + } + } + } + } + } + + cr_prop_list_destroy (props); + } + + return CR_OK; +} + +/* According to the CSS2 spec, sections 6.1 and 6.2, we need to do a + propagation: specified values -> computed values -> actual values. + The computed values are necessary. libcroco does not compute them for us. + The function cr_style_resolve_inherited_properties is also not sufficient: + it handles only the case of inheritance, not the case of non-inheritance. + So we write style accessors that fetch the computed value, doing the + inheritance on the fly. + We then compute the actual values from the computed values; for colors, + this is done through the rgb_to_color method. */ + +static term_color_t +style_compute_color_value (CRStyle *style, enum CRRgbProp which, + term_ostream_t stream) +{ + for (;;) + { + if (style == NULL) + return COLOR_DEFAULT; + if (cr_rgb_is_set_to_inherit (&style->rgb_props[which].sv)) + style = style->parent_style; + else if (cr_rgb_is_set_to_transparent (&style->rgb_props[which].sv)) + /* A transparent color occurs as default background color, set by + cr_style_set_props_to_default_values. */ + return COLOR_DEFAULT; + else + { + CRRgb rgb; + int r; + int g; + int b; + + cr_rgb_copy (&rgb, &style->rgb_props[which].sv); + if (cr_rgb_compute_from_percentage (&rgb) != CR_OK) + abort (); + r = rgb.red & 0xff; + g = rgb.green & 0xff; + b = rgb.blue & 0xff; + return term_ostream_rgb_to_color (stream, r, g, b); + } + } +} + +static term_weight_t +style_compute_font_weight_value (const CRStyle *style) +{ + int value = 0; + for (;;) + { + if (style == NULL) + value += 4; + else + switch (style->font_weight) + { + case FONT_WEIGHT_INHERIT: + style = style->parent_style; + continue; + case FONT_WEIGHT_BOLDER: + value += 1; + style = style->parent_style; + continue; + case FONT_WEIGHT_LIGHTER: + value -= 1; + style = style->parent_style; + continue; + case FONT_WEIGHT_100: + value += 1; + break; + case FONT_WEIGHT_200: + value += 2; + break; + case FONT_WEIGHT_300: + value += 3; + break; + case FONT_WEIGHT_400: case FONT_WEIGHT_NORMAL: + value += 4; + break; + case FONT_WEIGHT_500: + value += 5; + break; + case FONT_WEIGHT_600: + value += 6; + break; + case FONT_WEIGHT_700: case FONT_WEIGHT_BOLD: + value += 7; + break; + case FONT_WEIGHT_800: + value += 8; + break; + case FONT_WEIGHT_900: + value += 9; + break; + default: + abort (); + } + /* Value >= 600 -> WEIGHT_BOLD. Value <= 500 -> WEIGHT_NORMAL. */ + return (value >= 6 ? WEIGHT_BOLD : WEIGHT_NORMAL); + } +} + +static term_posture_t +style_compute_font_posture_value (const CRStyle *style) +{ + for (;;) + { + if (style == NULL) + return POSTURE_DEFAULT; + switch (style->font_style) + { + case FONT_STYLE_INHERIT: + style = style->parent_style; + break; + case FONT_STYLE_NORMAL: + return POSTURE_NORMAL; + case FONT_STYLE_ITALIC: + case FONT_STYLE_OBLIQUE: + return POSTURE_ITALIC; + default: + abort (); + } + } +} + +static term_underline_t +style_compute_text_underline_value (const CRXStyle *style) +{ + for (;;) + { + if (style == NULL) + return UNDERLINE_DEFAULT; + switch (style->text_decoration) + { + case TEXT_DECORATION_INHERIT: + style = style->parent_style; + break; + case TEXT_DECORATION_NONE: + case TEXT_DECORATION_OVERLINE: + case TEXT_DECORATION_LINE_THROUGH: + case TEXT_DECORATION_BLINK: + return UNDERLINE_OFF; + case TEXT_DECORATION_UNDERLINE: + return UNDERLINE_ON; + default: + abort (); + } + } +} + +/* Match the current list of CSS classes to the CSS and return the result. */ +static attributes_t * +match (term_styled_ostream_t stream) +{ + xmlNodePtr root; + xmlNodePtr curr; + char *p_end; + char *p_start; + CRXStyle *curr_style; + CRStyle *curr_style_base; + attributes_t *attr; + + /* Create a hierarchy of XML nodes. */ + root = xmlNewNode (NULL, (const xmlChar *) "__root__"); + root->type = XML_ELEMENT_NODE; + curr = root; + p_end = &stream->curr_classes[stream->curr_classes_length]; + p_start = stream->curr_classes; + while (p_start < p_end) + { + char *p; + xmlNodePtr child; + + if (!(*p_start == ' ')) + abort (); + p_start++; + for (p = p_start; p < p_end && *p != ' '; p++) + ; + + /* Temporarily replace the ' ' by '\0'. */ + *p = '\0'; + child = xmlNewNode (NULL, (const xmlChar *) p_start); + child->type = XML_ELEMENT_NODE; + xmlSetProp (child, (const xmlChar *) "class", (const xmlChar *) p_start); + *p = ' '; + + if (xmlAddChild (curr, child) == NULL) + /* Error! Shouldn't happen. */ + abort (); + + curr = child; + p_start = p; + } + + /* Retrieve the matching CSS declarations. */ + /* Not curr_style = crx_style_new (TRUE); because that assumes that the + default foreground color is black and that the default background color + is white, which is not necessarily true in a terminal context. */ + curr_style = NULL; + for (curr = root; curr != NULL; curr = curr->children) + { + CRXStyle *parent_style = curr_style; + curr_style = NULL; + + if (crx_sel_eng_get_matched_style (stream->css_engine, + stream->css_document, + curr, + parent_style, &curr_style, + FALSE) != CR_OK) + abort (); + if (curr_style == NULL) + /* No declarations matched this node. Inherit all values. */ + curr_style = parent_style; + else + /* curr_style is a new style, inheriting from parent_style. */ + ; + } + curr_style_base = (curr_style != NULL ? curr_style->base : NULL); + + /* Extract the CSS declarations that we can use. */ + attr = XMALLOC (attributes_t); + attr->color = + style_compute_color_value (curr_style_base, RGB_PROP_COLOR, + stream->destination); + attr->bgcolor = + style_compute_color_value (curr_style_base, RGB_PROP_BACKGROUND_COLOR, + stream->destination); + attr->weight = style_compute_font_weight_value (curr_style_base); + attr->posture = style_compute_font_posture_value (curr_style_base); + attr->underline = style_compute_text_underline_value (curr_style); + + /* Free the style chain. */ + while (curr_style != NULL) + { + CRXStyle *parent_style = curr_style->parent_style; + + crx_style_destroy (curr_style); + curr_style = parent_style; + } + + /* Free the XML nodes. */ + xmlFreeNodeList (root); + + return attr; +} + +/* Match the current list of CSS classes to the CSS and store the result in + stream->curr_attr and in the cache. */ +static void +match_and_cache (term_styled_ostream_t stream) +{ + attributes_t *attr = match (stream); + if (hash_insert_entry (&stream->cache, + stream->curr_classes, stream->curr_classes_length, + attr) == NULL) + abort (); + stream->curr_attr = attr; +} + +static void +term_styled_ostream::begin_use_class (term_styled_ostream_t stream, + const char *classname) +{ + size_t classname_len; + char *p; + void *found; + + if (classname[0] == '\0' || strchr (classname, ' ') != NULL) + /* Invalid classname argument. */ + abort (); + + /* Push the classname onto the classname list. */ + classname_len = strlen (classname); + if (stream->curr_classes_length + 1 + classname_len + 1 + > stream->curr_classes_allocated) + { + size_t new_allocated = stream->curr_classes_length + 1 + classname_len + 1; + if (new_allocated < 2 * stream->curr_classes_allocated) + new_allocated = 2 * stream->curr_classes_allocated; + + stream->curr_classes = xrealloc (stream->curr_classes, new_allocated); + stream->curr_classes_allocated = new_allocated; + } + p = &stream->curr_classes[stream->curr_classes_length]; + *p++ = ' '; + memcpy (p, classname, classname_len); + stream->curr_classes_length += 1 + classname_len; + + /* Uodate stream->curr_attr. */ + if (hash_find_entry (&stream->cache, + stream->curr_classes, stream->curr_classes_length, + &found) < 0) + match_and_cache (stream); + else + stream->curr_attr = (attributes_t *) found; +} + +static void +term_styled_ostream::end_use_class (term_styled_ostream_t stream, + const char *classname) +{ + char *p_end; + char *p_start; + char *p; + void *found; + + if (stream->curr_classes_length == 0) + /* No matching call to begin_use_class. */ + abort (); + + /* Remove the trailing classname. */ + p_end = &stream->curr_classes[stream->curr_classes_length]; + p = p_end; + while (*--p != ' ') + ; + p_start = p + 1; + if (!(p_end - p_start == strlen (classname) + && memcmp (p_start, classname, p_end - p_start) == 0)) + /* The match ing call to begin_use_class used a different classname. */ + abort (); + stream->curr_classes_length = p - stream->curr_classes; + + /* Update stream->curr_attr. */ + if (hash_find_entry (&stream->cache, + stream->curr_classes, stream->curr_classes_length, + &found) < 0) + abort (); + stream->curr_attr = (attributes_t *) found; +} + +/* Constructor. */ + +term_styled_ostream_t +term_styled_ostream_create (int fd, const char *filename, + const char *css_filename) +{ + term_styled_ostream_t stream = + XMALLOC (struct term_styled_ostream_representation); + CRStyleSheet *css_file_contents; + + stream->base.base.vtable = &term_styled_ostream_vtable; + stream->destination = term_ostream_create (fd, filename); + + if (cr_om_parser_simply_parse_file ((const guchar *) css_filename, + CR_UTF_8, /* CR_AUTO is not supported */ + &css_file_contents) != CR_OK) + { + term_ostream_free (stream->destination); + free (stream); + return NULL; + } + stream->css_document = cr_cascade_new (NULL, css_file_contents, NULL); + stream->css_engine = cr_sel_eng_new (); + + stream->curr_classes_allocated = 60; + stream->curr_classes = XNMALLOC (stream->curr_classes_allocated, char); + stream->curr_classes_length = 0; + + hash_init (&stream->cache, 10); + + match_and_cache (stream); + + return stream; +} |