Imported Upstream version 0.19.7
[platform/upstream/gettext.git] / gnulib-local / lib / term-styled-ostream.oo.c
1 /* Output stream for CSS styled text, producing ANSI escape sequences.
2    Copyright (C) 2006-2007, 2015 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 <http://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 "hash.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 document.  */
68   CRCascade *css_document;
69   /* The CSS matching engine.  */
70   CRSelEng *css_engine;
71   /* The list of active XML elements, with a space before each.
72      For example, in above example, it is " table header".  */
73   char *curr_classes;
74   size_t curr_classes_length;
75   size_t curr_classes_allocated;
76   /* A hash table mapping a list of classes (as a string) to an
77      'attributes_t *'.  */
78   hash_table cache;
79   /* The current attributes.  */
80   attributes_t *curr_attr;
81 };
82
83 /* Implementation of ostream_t methods.  */
84
85 static void
86 term_styled_ostream::write_mem (term_styled_ostream_t stream,
87                                 const void *data, size_t len)
88 {
89   term_ostream_set_color (stream->destination, stream->curr_attr->color);
90   term_ostream_set_bgcolor (stream->destination, stream->curr_attr->bgcolor);
91   term_ostream_set_weight (stream->destination, stream->curr_attr->weight);
92   term_ostream_set_posture (stream->destination, stream->curr_attr->posture);
93   term_ostream_set_underline (stream->destination, stream->curr_attr->underline);
94
95   term_ostream_write_mem (stream->destination, data, len);
96 }
97
98 static void
99 term_styled_ostream::flush (term_styled_ostream_t stream)
100 {
101   term_ostream_flush (stream->destination);
102 }
103
104 static void
105 term_styled_ostream::free (term_styled_ostream_t stream)
106 {
107   term_ostream_free (stream->destination);
108   cr_cascade_destroy (stream->css_document);
109   cr_sel_eng_destroy (stream->css_engine);
110   free (stream->curr_classes);
111   {
112     void *ptr = NULL;
113     const void *key;
114     size_t keylen;
115     void *data;
116
117     while (hash_iterate (&stream->cache, &ptr, &key, &keylen, &data) == 0)
118       {
119         free (data);
120       }
121   }
122   hash_destroy (&stream->cache);
123   free (stream);
124 }
125
126 /* Implementation of styled_ostream_t methods.  */
127
128 /* CRStyle doesn't contain a value for the 'text-decoration' property.
129    So we have to extend it.  */
130
131 enum CRXTextDecorationType
132 {
133   TEXT_DECORATION_NONE,
134   TEXT_DECORATION_UNDERLINE,
135   TEXT_DECORATION_OVERLINE,
136   TEXT_DECORATION_LINE_THROUGH,
137   TEXT_DECORATION_BLINK,
138   TEXT_DECORATION_INHERIT
139 };
140
141 typedef struct _CRXStyle
142 {
143   struct _CRXStyle *parent_style;
144   CRStyle *base;
145   enum CRXTextDecorationType text_decoration;
146 } CRXStyle;
147
148 /* An extended version of cr_style_new.  */
149 static CRXStyle *
150 crx_style_new (gboolean a_set_props_to_initial_values)
151 {
152   CRStyle *base;
153   CRXStyle *result;
154
155   base = cr_style_new (a_set_props_to_initial_values);
156   if (base == NULL)
157     return NULL;
158
159   result = XMALLOC (CRXStyle);
160   result->base = base;
161   if (a_set_props_to_initial_values)
162     result->text_decoration = TEXT_DECORATION_NONE;
163   else
164     result->text_decoration = TEXT_DECORATION_INHERIT;
165
166   return result;
167 }
168
169 /* An extended version of cr_style_destroy.  */
170 static void
171 crx_style_destroy (CRXStyle *a_style)
172 {
173   cr_style_destroy (a_style->base);
174   free (a_style);
175 }
176
177 /* An extended version of cr_sel_eng_get_matched_style.  */
178 static enum CRStatus
179 crx_sel_eng_get_matched_style (CRSelEng * a_this, CRCascade * a_cascade,
180                                xmlNode * a_node,
181                                CRXStyle * a_parent_style, CRXStyle ** a_style,
182                                gboolean a_set_props_to_initial_values)
183 {
184   enum CRStatus status;
185   CRPropList *props = NULL;
186
187   if (!(a_this && a_cascade && a_node && a_style))
188     return CR_BAD_PARAM_ERROR;
189
190   status = cr_sel_eng_get_matched_properties_from_cascade (a_this, a_cascade,
191                                                            a_node, &props);
192   if (!(status == CR_OK))
193     return status;
194
195   if (props)
196     {
197       CRXStyle *style;
198
199       if (!*a_style)
200         {
201           *a_style = crx_style_new (a_set_props_to_initial_values);
202           if (!*a_style)
203             return CR_ERROR;
204         }
205       else
206         {
207           if (a_set_props_to_initial_values)
208             {
209               cr_style_set_props_to_initial_values ((*a_style)->base);
210               (*a_style)->text_decoration = TEXT_DECORATION_NONE;
211             }
212           else
213             {
214               cr_style_set_props_to_default_values ((*a_style)->base);
215               (*a_style)->text_decoration = TEXT_DECORATION_INHERIT;
216             }
217         }
218       style = *a_style;
219       style->parent_style = a_parent_style;
220       style->base->parent_style =
221         (a_parent_style != NULL ? a_parent_style->base : NULL);
222
223       {
224         CRPropList *cur;
225
226         for (cur = props; cur != NULL; cur = cr_prop_list_get_next (cur))
227           {
228             CRDeclaration *decl = NULL;
229
230             cr_prop_list_get_decl (cur, &decl);
231             cr_style_set_style_from_decl (style->base, decl);
232             if (decl != NULL
233                 && decl->property != NULL
234                 && decl->property->stryng != NULL
235                 && decl->property->stryng->str != NULL)
236               {
237                 if (strcmp (decl->property->stryng->str, "text-decoration") == 0
238                     && decl->value != NULL
239                     && decl->value->type == TERM_IDENT
240                     && decl->value->content.str != NULL)
241                   {
242                     const char *value =
243                       cr_string_peek_raw_str (decl->value->content.str);
244
245                     if (value != NULL)
246                       {
247                         if (strcmp (value, "none") == 0)
248                           style->text_decoration = TEXT_DECORATION_NONE;
249                         else if (strcmp (value, "underline") == 0)
250                           style->text_decoration = TEXT_DECORATION_UNDERLINE;
251                         else if (strcmp (value, "overline") == 0)
252                           style->text_decoration = TEXT_DECORATION_OVERLINE;
253                         else if (strcmp (value, "line-through") == 0)
254                           style->text_decoration = TEXT_DECORATION_LINE_THROUGH;
255                         else if (strcmp (value, "blink") == 0)
256                           style->text_decoration = TEXT_DECORATION_BLINK;
257                         else if (strcmp (value, "inherit") == 0)
258                           style->text_decoration = TEXT_DECORATION_INHERIT;
259                       }
260                   }
261               }
262           }
263       }
264
265       cr_prop_list_destroy (props);
266     }
267
268   return CR_OK;
269 }
270
271 /* According to the CSS2 spec, sections 6.1 and 6.2, we need to do a
272    propagation: specified values -> computed values -> actual values.
273    The computed values are necessary.  libcroco does not compute them for us.
274    The function cr_style_resolve_inherited_properties is also not sufficient:
275    it handles only the case of inheritance, not the case of non-inheritance.
276    So we write style accessors that fetch the computed value, doing the
277    inheritance on the fly.
278    We then compute the actual values from the computed values; for colors,
279    this is done through the rgb_to_color method.  */
280
281 static term_color_t
282 style_compute_color_value (CRStyle *style, enum CRRgbProp which,
283                            term_ostream_t stream)
284 {
285   for (;;)
286     {
287       if (style == NULL)
288         return COLOR_DEFAULT;
289       if (cr_rgb_is_set_to_inherit (&style->rgb_props[which].sv))
290         style = style->parent_style;
291       else if (cr_rgb_is_set_to_transparent (&style->rgb_props[which].sv))
292         /* A transparent color occurs as default background color, set by
293            cr_style_set_props_to_default_values.  */
294         return COLOR_DEFAULT;
295       else
296         {
297           CRRgb rgb;
298           int r;
299           int g;
300           int b;
301
302           cr_rgb_copy (&rgb, &style->rgb_props[which].sv);
303           if (cr_rgb_compute_from_percentage (&rgb) != CR_OK)
304             abort ();
305           r = rgb.red & 0xff;
306           g = rgb.green & 0xff;
307           b = rgb.blue & 0xff;
308           return term_ostream_rgb_to_color (stream, r, g, b);
309         }
310     }
311 }
312
313 static term_weight_t
314 style_compute_font_weight_value (const CRStyle *style)
315 {
316   int value = 0;
317   for (;;)
318     {
319       if (style == NULL)
320         value += 4;
321       else
322         switch (style->font_weight)
323           {
324           case FONT_WEIGHT_INHERIT:
325             style = style->parent_style;
326             continue;
327           case FONT_WEIGHT_BOLDER:
328             value += 1;
329             style = style->parent_style;
330             continue;
331           case FONT_WEIGHT_LIGHTER:
332             value -= 1;
333             style = style->parent_style;
334             continue;
335           case FONT_WEIGHT_100:
336             value += 1;
337             break;
338           case FONT_WEIGHT_200:
339             value += 2;
340             break;
341           case FONT_WEIGHT_300:
342             value += 3;
343             break;
344           case FONT_WEIGHT_400: case FONT_WEIGHT_NORMAL:
345             value += 4;
346             break;
347           case FONT_WEIGHT_500:
348             value += 5;
349             break;
350           case FONT_WEIGHT_600:
351             value += 6;
352             break;
353           case FONT_WEIGHT_700: case FONT_WEIGHT_BOLD:
354             value += 7;
355             break;
356           case FONT_WEIGHT_800:
357             value += 8;
358             break;
359           case FONT_WEIGHT_900:
360             value += 9;
361             break;
362           default:
363             abort ();
364           }
365       /* Value >= 600 -> WEIGHT_BOLD.  Value <= 500 -> WEIGHT_NORMAL.  */
366       return (value >= 6 ? WEIGHT_BOLD : WEIGHT_NORMAL);
367     }
368 }
369
370 static term_posture_t
371 style_compute_font_posture_value (const CRStyle *style)
372 {
373   for (;;)
374     {
375       if (style == NULL)
376         return POSTURE_DEFAULT;
377       switch (style->font_style)
378         {
379         case FONT_STYLE_INHERIT:
380           style = style->parent_style;
381           break;
382         case FONT_STYLE_NORMAL:
383           return POSTURE_NORMAL;
384         case FONT_STYLE_ITALIC:
385         case FONT_STYLE_OBLIQUE:
386           return POSTURE_ITALIC;
387         default:
388           abort ();
389         }
390     }
391 }
392
393 static term_underline_t
394 style_compute_text_underline_value (const CRXStyle *style)
395 {
396   for (;;)
397     {
398       if (style == NULL)
399         return UNDERLINE_DEFAULT;
400       switch (style->text_decoration)
401         {
402         case TEXT_DECORATION_INHERIT:
403           style = style->parent_style;
404           break;
405         case TEXT_DECORATION_NONE:
406         case TEXT_DECORATION_OVERLINE:
407         case TEXT_DECORATION_LINE_THROUGH:
408         case TEXT_DECORATION_BLINK:
409           return UNDERLINE_OFF;
410         case TEXT_DECORATION_UNDERLINE:
411           return UNDERLINE_ON;
412         default:
413           abort ();
414         }
415     }
416 }
417
418 /* Match the current list of CSS classes to the CSS and return the result.  */
419 static attributes_t *
420 match (term_styled_ostream_t stream)
421 {
422   xmlNodePtr root;
423   xmlNodePtr curr;
424   char *p_end;
425   char *p_start;
426   CRXStyle *curr_style;
427   CRStyle *curr_style_base;
428   attributes_t *attr;
429
430   /* Create a hierarchy of XML nodes.  */
431   root = xmlNewNode (NULL, (const xmlChar *) "__root__");
432   root->type = XML_ELEMENT_NODE;
433   curr = root;
434   p_end = &stream->curr_classes[stream->curr_classes_length];
435   p_start = stream->curr_classes;
436   while (p_start < p_end)
437     {
438       char *p;
439       xmlNodePtr child;
440
441       if (!(*p_start == ' '))
442         abort ();
443       p_start++;
444       for (p = p_start; p < p_end && *p != ' '; p++)
445         ;
446
447       /* Temporarily replace the ' ' by '\0'.  */
448       *p = '\0';
449       child = xmlNewNode (NULL, (const xmlChar *) p_start);
450       child->type = XML_ELEMENT_NODE;
451       xmlSetProp (child, (const xmlChar *) "class", (const xmlChar *) p_start);
452       *p = ' ';
453
454       if (xmlAddChild (curr, child) == NULL)
455         /* Error! Shouldn't happen.  */
456         abort ();
457
458       curr = child;
459       p_start = p;
460     }
461
462   /* Retrieve the matching CSS declarations.  */
463   /* Not curr_style = crx_style_new (TRUE); because that assumes that the
464      default foreground color is black and that the default background color
465      is white, which is not necessarily true in a terminal context.  */
466   curr_style = NULL;
467   for (curr = root; curr != NULL; curr = curr->children)
468     {
469       CRXStyle *parent_style = curr_style;
470       curr_style = NULL;
471
472       if (crx_sel_eng_get_matched_style (stream->css_engine,
473                                          stream->css_document,
474                                          curr,
475                                          parent_style, &curr_style,
476                                          FALSE) != CR_OK)
477         abort ();
478       if (curr_style == NULL)
479         /* No declarations matched this node.  Inherit all values.  */
480         curr_style = parent_style;
481       else
482         /* curr_style is a new style, inheriting from parent_style.  */
483         ;
484     }
485   curr_style_base = (curr_style != NULL ? curr_style->base : NULL);
486
487   /* Extract the CSS declarations that we can use.  */
488   attr = XMALLOC (attributes_t);
489   attr->color =
490     style_compute_color_value (curr_style_base, RGB_PROP_COLOR,
491                                stream->destination);
492   attr->bgcolor =
493     style_compute_color_value (curr_style_base, RGB_PROP_BACKGROUND_COLOR,
494                                stream->destination);
495   attr->weight = style_compute_font_weight_value (curr_style_base);
496   attr->posture = style_compute_font_posture_value (curr_style_base);
497   attr->underline = style_compute_text_underline_value (curr_style);
498
499   /* Free the style chain.  */
500   while (curr_style != NULL)
501     {
502       CRXStyle *parent_style = curr_style->parent_style;
503
504       crx_style_destroy (curr_style);
505       curr_style = parent_style;
506     }
507
508   /* Free the XML nodes.  */
509   xmlFreeNodeList (root);
510
511   return attr;
512 }
513
514 /* Match the current list of CSS classes to the CSS and store the result in
515    stream->curr_attr and in the cache.  */
516 static void
517 match_and_cache (term_styled_ostream_t stream)
518 {
519   attributes_t *attr = match (stream);
520   if (hash_insert_entry (&stream->cache,
521                          stream->curr_classes, stream->curr_classes_length,
522                          attr) == NULL)
523     abort ();
524   stream->curr_attr = attr;
525 }
526
527 static void
528 term_styled_ostream::begin_use_class (term_styled_ostream_t stream,
529                                       const char *classname)
530 {
531   size_t classname_len;
532   char *p;
533   void *found;
534
535   if (classname[0] == '\0' || strchr (classname, ' ') != NULL)
536     /* Invalid classname argument.  */
537     abort ();
538
539   /* Push the classname onto the classname list.  */
540   classname_len = strlen (classname);
541   if (stream->curr_classes_length + 1 + classname_len + 1
542       > stream->curr_classes_allocated)
543     {
544       size_t new_allocated = stream->curr_classes_length + 1 + classname_len + 1;
545       if (new_allocated < 2 * stream->curr_classes_allocated)
546         new_allocated = 2 * stream->curr_classes_allocated;
547
548       stream->curr_classes = xrealloc (stream->curr_classes, new_allocated);
549       stream->curr_classes_allocated = new_allocated;
550     }
551   p = &stream->curr_classes[stream->curr_classes_length];
552   *p++ = ' ';
553   memcpy (p, classname, classname_len);
554   stream->curr_classes_length += 1 + classname_len;
555
556   /* Uodate stream->curr_attr.  */
557   if (hash_find_entry (&stream->cache,
558                        stream->curr_classes, stream->curr_classes_length,
559                        &found) < 0)
560     match_and_cache (stream);
561   else
562     stream->curr_attr = (attributes_t *) found;
563 }
564
565 static void
566 term_styled_ostream::end_use_class (term_styled_ostream_t stream,
567                                     const char *classname)
568 {
569   char *p_end;
570   char *p_start;
571   char *p;
572   void *found;
573
574   if (stream->curr_classes_length == 0)
575     /* No matching call to begin_use_class.  */
576     abort ();
577
578   /* Remove the trailing classname.  */
579   p_end = &stream->curr_classes[stream->curr_classes_length];
580   p = p_end;
581   while (*--p != ' ')
582     ;
583   p_start = p + 1;
584   if (!(p_end - p_start == strlen (classname)
585         && memcmp (p_start, classname, p_end - p_start) == 0))
586     /* The match ing call to begin_use_class used a different classname.  */
587     abort ();
588   stream->curr_classes_length = p - stream->curr_classes;
589
590   /* Update stream->curr_attr.  */
591   if (hash_find_entry (&stream->cache,
592                        stream->curr_classes, stream->curr_classes_length,
593                        &found) < 0)
594     abort ();
595   stream->curr_attr = (attributes_t *) found;
596 }
597
598 /* Constructor.  */
599
600 term_styled_ostream_t
601 term_styled_ostream_create (int fd, const char *filename,
602                             const char *css_filename)
603 {
604   term_styled_ostream_t stream =
605     XMALLOC (struct term_styled_ostream_representation);
606   CRStyleSheet *css_file_contents;
607
608   stream->base.base.vtable = &term_styled_ostream_vtable;
609   stream->destination = term_ostream_create (fd, filename);
610
611   if (cr_om_parser_simply_parse_file ((const guchar *) css_filename,
612                                       CR_UTF_8, /* CR_AUTO is not supported */
613                                       &css_file_contents) != CR_OK)
614     {
615       term_ostream_free (stream->destination);
616       free (stream);
617       return NULL;
618     }
619   stream->css_document = cr_cascade_new (NULL, css_file_contents, NULL);
620   stream->css_engine = cr_sel_eng_new ();
621
622   stream->curr_classes_allocated = 60;
623   stream->curr_classes = XNMALLOC (stream->curr_classes_allocated, char);
624   stream->curr_classes_length = 0;
625
626   hash_init (&stream->cache, 10);
627
628   match_and_cache (stream);
629
630   return stream;
631 }