• Home
  • History
  • Annotate
  • Line#
  • Navigate
  • Raw
  • Download
  • only in /netgear-WNDR4500v2-V1.0.0.60_1.0.38/ap/gpl/timemachine/gettext-0.17/gnulib-local/lib/
1/* Output stream for CSS styled text, producing ANSI escape sequences.
2   Copyright (C) 2006-2007 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.  */
53typedef 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
62struct term_styled_ostream : struct styled_ostream
63{
64fields:
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
85static void
86term_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
98static void
99term_styled_ostream::flush (term_styled_ostream_t stream)
100{
101  term_ostream_flush (stream->destination);
102}
103
104static void
105term_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
131enum 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
141typedef 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.  */
149static CRXStyle *
150crx_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.  */
170static void
171crx_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.  */
178static enum CRStatus
179crx_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
281static term_color_t
282style_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
313static term_weight_t
314style_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
370static term_posture_t
371style_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
393static term_underline_t
394style_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.  */
419static attributes_t *
420match (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.  */
516static void
517match_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
527static void
528term_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
565static void
566term_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
600term_styled_ostream_t
601term_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}
632