1 /* Pango
2  * pango-markup.c: Parse markup into attributed text
3  *
4  * Copyright (C) 2000 Red Hat Software
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, USA.
20  */
21 
22 #include "config.h"
23 #include <string.h>
24 #include <stdlib.h>
25 #include <errno.h>
26 
27 #include "pango-attributes.h"
28 #include "pango-font.h"
29 #include "pango-enum-types.h"
30 #include "pango-impl-utils.h"
31 #include "pango-utils-internal.h"
32 
33 /* FIXME */
34 #define _(x) x
35 
36 /* CSS size levels */
37 typedef enum
38 {
39   XXSmall = -3,
40   XSmall = -2,
41   Small = -1,
42   Medium = 0,
43   Large = 1,
44   XLarge = 2,
45   XXLarge = 3
46 } SizeLevel;
47 
48 typedef struct _MarkupData MarkupData;
49 
50 struct _MarkupData
51 {
52   PangoAttrList *attr_list;
53   GString *text;
54   GSList *tag_stack;
55   gsize index;
56   GSList *to_apply;
57   gunichar accel_marker;
58   gunichar accel_char;
59 };
60 
61 typedef struct _OpenTag OpenTag;
62 
63 struct _OpenTag
64 {
65   GSList *attrs;
66   gsize start_index;
67   /* Current total scale level; reset whenever
68    * an absolute size is set.
69    * Each "larger" ups it 1, each "smaller" decrements it 1
70    */
71   gint scale_level;
72   /* Our impact on scale_level, so we know whether we
73    * need to create an attribute ourselves on close
74    */
75   gint scale_level_delta;
76   /* Base scale factor currently in effect
77    * or size that this tag
78    * forces, or parent's scale factor or size.
79    */
80   double base_scale_factor;
81   int base_font_size;
82   guint has_base_font_size : 1;
83 };
84 
85 typedef gboolean (*TagParseFunc) (MarkupData            *md,
86 				  OpenTag               *tag,
87 				  const gchar          **names,
88 				  const gchar          **values,
89 				  GMarkupParseContext   *context,
90 				  GError               **error);
91 
92 static gboolean b_parse_func        (MarkupData           *md,
93 				     OpenTag              *tag,
94 				     const gchar         **names,
95 				     const gchar         **values,
96 				     GMarkupParseContext  *context,
97 				     GError              **error);
98 static gboolean big_parse_func      (MarkupData           *md,
99 				     OpenTag              *tag,
100 				     const gchar         **names,
101 				     const gchar         **values,
102 				     GMarkupParseContext  *context,
103 				     GError              **error);
104 static gboolean span_parse_func     (MarkupData           *md,
105 				     OpenTag              *tag,
106 				     const gchar         **names,
107 				     const gchar         **values,
108 				     GMarkupParseContext  *context,
109 				     GError              **error);
110 static gboolean i_parse_func        (MarkupData           *md,
111 				     OpenTag              *tag,
112 				     const gchar         **names,
113 				     const gchar         **values,
114 				     GMarkupParseContext  *context,
115 				     GError              **error);
116 static gboolean markup_parse_func   (MarkupData           *md,
117 				     OpenTag              *tag,
118 				     const gchar         **names,
119 				     const gchar         **values,
120 				     GMarkupParseContext  *context,
121 				     GError              **error);
122 static gboolean s_parse_func        (MarkupData           *md,
123 				     OpenTag              *tag,
124 				     const gchar         **names,
125 				     const gchar         **values,
126 				     GMarkupParseContext  *context,
127 				     GError              **error);
128 static gboolean sub_parse_func      (MarkupData           *md,
129 				     OpenTag              *tag,
130 				     const gchar         **names,
131 				     const gchar         **values,
132 				     GMarkupParseContext  *context,
133 				     GError              **error);
134 static gboolean sup_parse_func      (MarkupData           *md,
135 				     OpenTag              *tag,
136 				     const gchar         **names,
137 				     const gchar         **values,
138 				     GMarkupParseContext  *context,
139 				     GError              **error);
140 static gboolean small_parse_func    (MarkupData           *md,
141 				     OpenTag              *tag,
142 				     const gchar         **names,
143 				     const gchar         **values,
144 				     GMarkupParseContext  *context,
145 				     GError              **error);
146 static gboolean tt_parse_func       (MarkupData           *md,
147 				     OpenTag              *tag,
148 				     const gchar         **names,
149 				     const gchar         **values,
150 				     GMarkupParseContext  *context,
151 				     GError              **error);
152 static gboolean u_parse_func        (MarkupData           *md,
153 				     OpenTag              *tag,
154 				     const gchar         **names,
155 				     const gchar         **values,
156 				     GMarkupParseContext  *context,
157 				     GError              **error);
158 
159 static double
scale_factor(int scale_level,double base)160 scale_factor (int scale_level, double base)
161 {
162   double factor = base;
163   int i;
164 
165   /* 1.2 is the CSS scale factor between sizes */
166 
167   if (scale_level > 0)
168     {
169       i = 0;
170       while (i < scale_level)
171 	{
172 	  factor *= 1.2;
173 
174 	  ++i;
175 	}
176     }
177   else if (scale_level < 0)
178     {
179       i = scale_level;
180       while (i < 0)
181 	{
182 	  factor /= 1.2;
183 
184 	  ++i;
185 	}
186     }
187 
188   return factor;
189 }
190 
191 static void
open_tag_free(OpenTag * ot)192 open_tag_free (OpenTag *ot)
193 {
194   g_slist_foreach (ot->attrs, (GFunc) pango_attribute_destroy, NULL);
195   g_slist_free (ot->attrs);
196   g_slice_free (OpenTag, ot);
197 }
198 
199 static void
open_tag_set_absolute_font_size(OpenTag * ot,int font_size)200 open_tag_set_absolute_font_size (OpenTag *ot,
201 				 int      font_size)
202 {
203   ot->base_font_size = font_size;
204   ot->has_base_font_size = TRUE;
205   ot->scale_level = 0;
206   ot->scale_level_delta = 0;
207 }
208 
209 static void
open_tag_set_absolute_font_scale(OpenTag * ot,double scale)210 open_tag_set_absolute_font_scale (OpenTag *ot,
211 				  double   scale)
212 {
213   ot->base_scale_factor = scale;
214   ot->has_base_font_size = FALSE;
215   ot->scale_level = 0;
216   ot->scale_level_delta = 0;
217 }
218 
219 static OpenTag*
markup_data_open_tag(MarkupData * md)220 markup_data_open_tag (MarkupData   *md)
221 {
222   OpenTag *ot;
223   OpenTag *parent = NULL;
224 
225   if (md->attr_list == NULL)
226     return NULL;
227 
228   if (md->tag_stack)
229     parent = md->tag_stack->data;
230 
231   ot = g_slice_new (OpenTag);
232   ot->attrs = NULL;
233   ot->start_index = md->index;
234   ot->scale_level_delta = 0;
235 
236   if (parent == NULL)
237     {
238       ot->base_scale_factor = 1.0;
239       ot->base_font_size = 0;
240       ot->has_base_font_size = FALSE;
241       ot->scale_level = 0;
242     }
243   else
244     {
245       ot->base_scale_factor = parent->base_scale_factor;
246       ot->base_font_size = parent->base_font_size;
247       ot->has_base_font_size = parent->has_base_font_size;
248       ot->scale_level = parent->scale_level;
249     }
250 
251   md->tag_stack = g_slist_prepend (md->tag_stack, ot);
252 
253   return ot;
254 }
255 
256 static void
markup_data_close_tag(MarkupData * md)257 markup_data_close_tag (MarkupData *md)
258 {
259   OpenTag *ot;
260   GSList *tmp_list;
261 
262   if (md->attr_list == NULL)
263     return;
264 
265   /* pop the stack */
266   ot = md->tag_stack->data;
267   md->tag_stack = g_slist_delete_link (md->tag_stack,
268 				       md->tag_stack);
269 
270   /* Adjust end indexes, and push each attr onto the front of the
271    * to_apply list. This means that outermost tags are on the front of
272    * that list; if we apply the list in order, then the innermost
273    * tags will "win" which is correct.
274    */
275   tmp_list = ot->attrs;
276   while (tmp_list != NULL)
277     {
278       PangoAttribute *a = tmp_list->data;
279 
280       a->start_index = ot->start_index;
281       a->end_index = md->index;
282 
283       md->to_apply = g_slist_prepend (md->to_apply, a);
284 
285       tmp_list = g_slist_next (tmp_list);
286     }
287 
288   if (ot->scale_level_delta != 0)
289     {
290       /* We affected relative font size; create an appropriate
291        * attribute and reverse our effects on the current level
292        */
293       PangoAttribute *a;
294 
295       if (ot->has_base_font_size)
296 	{
297           /* Create a font using the absolute point size as the base size
298            * to be scaled from.
299            * We need to use a local variable to ensure that the compiler won't
300            * implicitly cast it to integer while the result is kept in registers,
301            * leading to a wrong approximation in i386 (with 387 FPU)
302            */
303           volatile double size;
304 
305           size = scale_factor (ot->scale_level, 1.0) * ot->base_font_size;
306           a = pango_attr_size_new (size);
307 	}
308       else
309 	{
310 	  /* Create a font using the current scale factor
311 	   * as the base size to be scaled from
312 	   */
313 	  a = pango_attr_scale_new (scale_factor (ot->scale_level,
314 						  ot->base_scale_factor));
315 	}
316 
317       a->start_index = ot->start_index;
318       a->end_index = md->index;
319 
320       md->to_apply = g_slist_prepend (md->to_apply, a);
321     }
322 
323   g_slist_free (ot->attrs);
324   g_slice_free (OpenTag, ot);
325 }
326 
327 static void
start_element_handler(GMarkupParseContext * context,const gchar * element_name,const gchar ** attribute_names,const gchar ** attribute_values,gpointer user_data,GError ** error)328 start_element_handler  (GMarkupParseContext *context,
329 			const gchar         *element_name,
330 			const gchar        **attribute_names,
331 			const gchar        **attribute_values,
332 			gpointer             user_data,
333 			GError             **error)
334 {
335   TagParseFunc parse_func = NULL;
336   OpenTag *ot;
337 
338   switch (*element_name)
339     {
340     case 'b':
341       if (strcmp ("b", element_name) == 0)
342 	parse_func = b_parse_func;
343       else if (strcmp ("big", element_name) == 0)
344 	parse_func = big_parse_func;
345       break;
346 
347     case 'i':
348       if (strcmp ("i", element_name) == 0)
349 	parse_func = i_parse_func;
350       break;
351 
352     case 'm':
353       if (strcmp ("markup", element_name) == 0)
354 	parse_func = markup_parse_func;
355       break;
356 
357     case 's':
358       if (strcmp ("span", element_name) == 0)
359 	parse_func = span_parse_func;
360       else if (strcmp ("s", element_name) == 0)
361 	parse_func = s_parse_func;
362       else if (strcmp ("sub", element_name) == 0)
363 	parse_func = sub_parse_func;
364       else if (strcmp ("sup", element_name) == 0)
365 	parse_func = sup_parse_func;
366       else if (strcmp ("small", element_name) == 0)
367 	parse_func = small_parse_func;
368       break;
369 
370     case 't':
371       if (strcmp ("tt", element_name) == 0)
372 	parse_func = tt_parse_func;
373       break;
374 
375     case 'u':
376       if (strcmp ("u", element_name) == 0)
377 	parse_func = u_parse_func;
378       break;
379     }
380 
381   if (parse_func == NULL)
382     {
383       gint line_number, char_number;
384 
385       g_markup_parse_context_get_position (context,
386 					   &line_number, &char_number);
387 
388       g_set_error (error,
389 		   G_MARKUP_ERROR,
390 		   G_MARKUP_ERROR_UNKNOWN_ELEMENT,
391 		   _("Unknown tag '%s' on line %d char %d"),
392 		   element_name,
393 		   line_number, char_number);
394 
395       return;
396     }
397 
398   ot = markup_data_open_tag (user_data);
399 
400   /* note ot may be NULL if the user didn't want the attribute list */
401 
402   if (!(*parse_func) (user_data, ot,
403 		      attribute_names, attribute_values,
404 		      context, error))
405     {
406       /* there's nothing to do; we return an error, and end up
407        * freeing ot off the tag stack later.
408        */
409     }
410 }
411 
412 static void
end_element_handler(GMarkupParseContext * context G_GNUC_UNUSED,const gchar * element_name G_GNUC_UNUSED,gpointer user_data,GError ** error G_GNUC_UNUSED)413 end_element_handler    (GMarkupParseContext *context G_GNUC_UNUSED,
414 			const gchar         *element_name G_GNUC_UNUSED,
415 			gpointer             user_data,
416 			GError             **error G_GNUC_UNUSED)
417 {
418   markup_data_close_tag (user_data);
419 }
420 
421 static void
text_handler(GMarkupParseContext * context G_GNUC_UNUSED,const gchar * text,gsize text_len,gpointer user_data,GError ** error G_GNUC_UNUSED)422 text_handler           (GMarkupParseContext *context G_GNUC_UNUSED,
423 			const gchar         *text,
424 			gsize                text_len,
425 			gpointer             user_data,
426 			GError             **error G_GNUC_UNUSED)
427 {
428   MarkupData *md = user_data;
429 
430   if (md->accel_marker == 0)
431     {
432       /* Just append all the text */
433 
434       md->index += text_len;
435 
436       g_string_append_len (md->text, text, text_len);
437     }
438   else
439     {
440       /* Parse the accelerator */
441       const gchar *p;
442       const gchar *end;
443       const gchar *range_start;
444       const gchar *range_end;
445       gssize uline_index = -1;
446       gsize uline_len = 0;	/* Quiet GCC */
447 
448       range_end = NULL;
449       range_start = text;
450       p = text;
451       end = text + text_len;
452 
453       while (p != end)
454 	{
455 	  gunichar c;
456 
457 	  c = g_utf8_get_char (p);
458 
459 	  if (range_end)
460 	    {
461 	      if (c == md->accel_marker)
462 		{
463 		  /* escaped accel marker; move range_end
464 		   * past the accel marker that came before,
465 		   * append the whole thing
466 		   */
467 		  range_end = g_utf8_next_char (range_end);
468 		  g_string_append_len (md->text,
469 				       range_start,
470 				       range_end - range_start);
471 		  md->index += range_end - range_start;
472 
473 		  /* set next range_start, skipping accel marker */
474 		  range_start = g_utf8_next_char (p);
475 		}
476 	      else
477 		{
478 		  /* Don't append the accel marker (leave range_end
479 		   * alone); set the accel char to c; record location for
480 		   * underline attribute
481 		   */
482 		  if (md->accel_char == 0)
483 		    md->accel_char = c;
484 
485 		  g_string_append_len (md->text,
486 				       range_start,
487 				       range_end - range_start);
488 		  md->index += range_end - range_start;
489 
490 		  /* The underline should go underneath the char
491 		   * we're setting as the next range_start
492 		   */
493                   if (md->attr_list != NULL)
494                     {
495                       /* Add the underline indicating the accelerator */
496                       PangoAttribute *attr;
497 
498                       attr = pango_attr_underline_new (PANGO_UNDERLINE_LOW);
499 
500                       uline_index = md->index;
501                       uline_len = g_utf8_next_char (p) - p;
502 
503                       attr->start_index = uline_index;
504                       attr->end_index = uline_index + uline_len;
505 
506                       pango_attr_list_change (md->attr_list, attr);
507                     }
508 
509 		  /* set next range_start to include this char */
510 		  range_start = p;
511 		}
512 
513 	      /* reset range_end */
514 	      range_end = NULL;
515 	    }
516 	  else if (c == md->accel_marker)
517 	    {
518 	      range_end = p;
519 	    }
520 
521 	  p = g_utf8_next_char (p);
522         }
523 
524       g_string_append_len (md->text,
525                            range_start,
526                            end - range_start);
527                            md->index += end - range_start;
528     }
529 }
530 
531 static gboolean
xml_isspace(char c)532 xml_isspace (char c)
533 {
534   return c == ' ' || c == '\t' || c == '\n' || c == '\r';
535 }
536 
537 static const GMarkupParser pango_markup_parser = {
538   start_element_handler,
539   end_element_handler,
540   text_handler,
541   NULL,
542   NULL
543 };
544 
545 static void
destroy_markup_data(MarkupData * md)546 destroy_markup_data (MarkupData *md)
547 {
548   g_slist_free_full (md->tag_stack, (GDestroyNotify) open_tag_free);
549   g_slist_free_full (md->to_apply, (GDestroyNotify) pango_attribute_destroy);
550   if (md->text)
551       g_string_free (md->text, TRUE);
552 
553   if (md->attr_list)
554     pango_attr_list_unref (md->attr_list);
555 
556   g_slice_free (MarkupData, md);
557 }
558 
559 static GMarkupParseContext *
pango_markup_parser_new_internal(char accel_marker,GError ** error,gboolean want_attr_list)560 pango_markup_parser_new_internal (char       accel_marker,
561 				  GError   **error,
562 				  gboolean   want_attr_list)
563 {
564   MarkupData *md;
565   GMarkupParseContext *context;
566 
567   md = g_slice_new (MarkupData);
568 
569   /* Don't bother creating these if they weren't requested;
570    * might be useful e.g. if you just want to validate
571    * some markup.
572    */
573   if (want_attr_list)
574     md->attr_list = pango_attr_list_new ();
575   else
576     md->attr_list = NULL;
577 
578   md->text = g_string_new (NULL);
579 
580   md->accel_marker = accel_marker;
581   md->accel_char = 0;
582 
583   md->index = 0;
584   md->tag_stack = NULL;
585   md->to_apply = NULL;
586 
587   context = g_markup_parse_context_new (&pango_markup_parser,
588 					0, md,
589                                         (GDestroyNotify)destroy_markup_data);
590 
591   if (!g_markup_parse_context_parse (context, "<markup>", -1, error))
592     g_clear_pointer (&context, g_markup_parse_context_free);
593 
594   return context;
595 }
596 
597 /**
598  * pango_parse_markup:
599  * @markup_text: markup to parse (see the Pango Markup docs)
600  * @length: length of @markup_text, or -1 if nul-terminated
601  * @accel_marker: character that precedes an accelerator, or 0 for none
602  * @attr_list: (out) (optional): address of return location for a `PangoAttrList`
603  * @text: (out) (optional): address of return location for text with tags stripped
604  * @accel_char: (out) (optional): address of return location for accelerator char
605  * @error: address of return location for errors
606  *
607  * Parses marked-up text to create a plain-text string and an attribute list.
608  *
609  * See the [Pango Markup](pango_markup.html) docs for details about the
610  * supported markup.
611  *
612  * If @accel_marker is nonzero, the given character will mark the
613  * character following it as an accelerator. For example, @accel_marker
614  * might be an ampersand or underscore. All characters marked
615  * as an accelerator will receive a %PANGO_UNDERLINE_LOW attribute,
616  * and the first character so marked will be returned in @accel_char.
617  * Two @accel_marker characters following each other produce a single
618  * literal @accel_marker character.
619  *
620  * To parse a stream of pango markup incrementally, use [func@markup_parser_new].
621  *
622  * If any error happens, none of the output arguments are touched except
623  * for @error.
624  *
625  * Return value: %FALSE if @error is set, otherwise %TRUE
626  **/
627 gboolean
pango_parse_markup(const char * markup_text,int length,gunichar accel_marker,PangoAttrList ** attr_list,char ** text,gunichar * accel_char,GError ** error)628 pango_parse_markup (const char                 *markup_text,
629 		    int                         length,
630 		    gunichar                    accel_marker,
631 		    PangoAttrList             **attr_list,
632 		    char                      **text,
633 		    gunichar                   *accel_char,
634 		    GError                    **error)
635 {
636   GMarkupParseContext *context = NULL;
637   gboolean ret = FALSE;
638   const char *p;
639   const char *end;
640 
641   g_return_val_if_fail (markup_text != NULL, FALSE);
642 
643   if (length < 0)
644     length = strlen (markup_text);
645 
646   p = markup_text;
647   end = markup_text + length;
648   while (p != end && xml_isspace (*p))
649     ++p;
650 
651   context = pango_markup_parser_new_internal (accel_marker,
652                                               error,
653                                               (attr_list != NULL));
654 
655   if (!g_markup_parse_context_parse (context,
656                                      markup_text,
657                                      length,
658                                      error))
659     goto out;
660 
661   if (!pango_markup_parser_finish (context,
662                                    attr_list,
663                                    text,
664                                    accel_char,
665                                    error))
666     goto out;
667 
668   ret = TRUE;
669 
670  out:
671   if (context != NULL)
672     g_markup_parse_context_free (context);
673   return ret;
674 }
675 
676 /**
677  * pango_markup_parser_new:
678  * @accel_marker: character that precedes an accelerator, or 0 for none
679  *
680  * Incrementally parses marked-up text to create a plain-text string
681  * and an attribute list.
682  *
683  * See the [Pango Markup](pango_markup.html) docs for details about the
684  * supported markup.
685  *
686  * If @accel_marker is nonzero, the given character will mark the
687  * character following it as an accelerator. For example, @accel_marker
688  * might be an ampersand or underscore. All characters marked
689  * as an accelerator will receive a %PANGO_UNDERLINE_LOW attribute,
690  * and the first character so marked will be returned in @accel_char,
691  * when calling [func@markup_parser_finish]. Two @accel_marker characters
692  * following each other produce a single literal @accel_marker character.
693  *
694  * To feed markup to the parser, use g_markup_parse_context_parse()
695  * on the returned `GMarkupParseContext`. When done with feeding markup
696  * to the parser, use [func@markup_parser_finish] to get the data out
697  * of it, and then use g_markup_parse_context_free() to free it.
698  *
699  * This function is designed for applications that read Pango markup
700  * from streams. To simply parse a string containing Pango markup,
701  * the [func@parse_markup] API is recommended instead.
702  *
703  * Return value: (transfer none): a `GMarkupParseContext` that should be
704  * destroyed with g_markup_parse_context_free().
705  *
706  * Since: 1.31.0
707  **/
708 GMarkupParseContext *
pango_markup_parser_new(gunichar accel_marker)709 pango_markup_parser_new (gunichar accel_marker)
710 {
711   return pango_markup_parser_new_internal (accel_marker, NULL, TRUE);
712 }
713 
714 /**
715  * pango_markup_parser_finish:
716  * @context: A valid parse context that was returned from [func@markup_parser_new]
717  * @attr_list: (out) (optional): address of return location for a `PangoAttrList`
718  * @text: (out) (optional): address of return location for text with tags stripped
719  * @accel_char: (out) (optional): address of return location for accelerator char
720  * @error: address of return location for errors
721  *
722  * Finishes parsing markup.
723  *
724  * After feeding a Pango markup parser some data with g_markup_parse_context_parse(),
725  * use this function to get the list of attributes and text out of the
726  * markup. This function will not free @context, use g_markup_parse_context_free()
727  * to do so.
728  *
729  * Return value: %FALSE if @error is set, otherwise %TRUE
730  *
731  * Since: 1.31.0
732  */
733 gboolean
pango_markup_parser_finish(GMarkupParseContext * context,PangoAttrList ** attr_list,char ** text,gunichar * accel_char,GError ** error)734 pango_markup_parser_finish (GMarkupParseContext   *context,
735                             PangoAttrList        **attr_list,
736                             char                 **text,
737                             gunichar              *accel_char,
738                             GError               **error)
739 {
740   gboolean ret = FALSE;
741   MarkupData *md = g_markup_parse_context_get_user_data (context);
742   GSList *tmp_list;
743 
744   if (!g_markup_parse_context_parse (context,
745                                      "</markup>",
746                                      -1,
747                                      error))
748     goto out;
749 
750   if (!g_markup_parse_context_end_parse (context, error))
751     goto out;
752 
753   if (md->attr_list)
754     {
755       /* The apply list has the most-recently-closed tags first;
756        * we want to apply the least-recently-closed tag last.
757        */
758       tmp_list = md->to_apply;
759       while (tmp_list != NULL)
760 	{
761 	  PangoAttribute *attr = tmp_list->data;
762 
763 	  /* Innermost tags before outermost */
764 	  pango_attr_list_insert (md->attr_list, attr);
765 
766 	  tmp_list = g_slist_next (tmp_list);
767 	}
768       g_slist_free (md->to_apply);
769       md->to_apply = NULL;
770     }
771 
772   if (attr_list)
773     {
774       *attr_list = md->attr_list;
775       md->attr_list = NULL;
776     }
777 
778   if (text)
779     {
780       *text = g_string_free (md->text, FALSE);
781       md->text = NULL;
782     }
783 
784   if (accel_char)
785     *accel_char = md->accel_char;
786 
787   g_assert (md->tag_stack == NULL);
788   ret = TRUE;
789 
790  out:
791   return ret;
792 }
793 
794 static void
set_bad_attribute(GError ** error,GMarkupParseContext * context,const char * element_name,const char * attribute_name)795 set_bad_attribute (GError             **error,
796 		   GMarkupParseContext *context,
797 		   const char          *element_name,
798 		   const char          *attribute_name)
799 {
800   gint line_number, char_number;
801 
802   g_markup_parse_context_get_position (context,
803 				       &line_number, &char_number);
804 
805   g_set_error (error,
806 	       G_MARKUP_ERROR,
807 	       G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
808 	       _("Tag '%s' does not support attribute '%s' on line %d char %d"),
809 	       element_name,
810 	       attribute_name,
811 	       line_number, char_number);
812 }
813 
814 static void
add_attribute(OpenTag * ot,PangoAttribute * attr)815 add_attribute (OpenTag        *ot,
816 	       PangoAttribute *attr)
817 {
818   if (ot == NULL)
819     pango_attribute_destroy (attr);
820   else
821     ot->attrs = g_slist_prepend (ot->attrs, attr);
822 }
823 
824 #define CHECK_NO_ATTRS(elem) G_STMT_START {                    \
825 	 if (*names != NULL) {                                 \
826 	   set_bad_attribute (error, context, (elem), *names); \
827 	   return FALSE;                                       \
828 	 } }G_STMT_END
829 
830 static gboolean
b_parse_func(MarkupData * md G_GNUC_UNUSED,OpenTag * tag,const gchar ** names,const gchar ** values G_GNUC_UNUSED,GMarkupParseContext * context,GError ** error)831 b_parse_func        (MarkupData            *md G_GNUC_UNUSED,
832 		     OpenTag               *tag,
833 		     const gchar          **names,
834 		     const gchar          **values G_GNUC_UNUSED,
835 		     GMarkupParseContext   *context,
836 		     GError               **error)
837 {
838   CHECK_NO_ATTRS("b");
839   add_attribute (tag, pango_attr_weight_new (PANGO_WEIGHT_BOLD));
840   return TRUE;
841 }
842 
843 static gboolean
big_parse_func(MarkupData * md G_GNUC_UNUSED,OpenTag * tag,const gchar ** names,const gchar ** values G_GNUC_UNUSED,GMarkupParseContext * context,GError ** error)844 big_parse_func      (MarkupData            *md G_GNUC_UNUSED,
845 		     OpenTag               *tag,
846 		     const gchar          **names,
847 		     const gchar          **values G_GNUC_UNUSED,
848 		     GMarkupParseContext   *context,
849 		     GError               **error)
850 {
851   CHECK_NO_ATTRS("big");
852 
853   /* Grow text one level */
854   if (tag)
855     {
856       tag->scale_level_delta += 1;
857       tag->scale_level += 1;
858     }
859 
860   return TRUE;
861 }
862 
863 static gboolean
parse_absolute_size(OpenTag * tag,const char * size)864 parse_absolute_size (OpenTag               *tag,
865 		     const char            *size)
866 {
867   SizeLevel level = Medium;
868   double factor;
869 
870   if (strcmp (size, "xx-small") == 0)
871     level = XXSmall;
872   else if (strcmp (size, "x-small") == 0)
873     level = XSmall;
874   else if (strcmp (size, "small") == 0)
875     level = Small;
876   else if (strcmp (size, "medium") == 0)
877     level = Medium;
878   else if (strcmp (size, "large") == 0)
879     level = Large;
880   else if (strcmp (size, "x-large") == 0)
881     level = XLarge;
882   else if (strcmp (size, "xx-large") == 0)
883     level = XXLarge;
884   else
885     return FALSE;
886 
887   /* This is "absolute" in that it's relative to the base font,
888    * but not to sizes created by any other tags
889    */
890   factor = scale_factor (level, 1.0);
891   add_attribute (tag, pango_attr_scale_new (factor));
892   if (tag)
893     open_tag_set_absolute_font_scale (tag, factor);
894 
895   return TRUE;
896 }
897 
898 /* a string compare func that ignores '-' vs '_' differences */
899 static gint
attr_strcmp(gconstpointer pa,gconstpointer pb)900 attr_strcmp (gconstpointer pa,
901 	     gconstpointer pb)
902 {
903   const char *a = pa;
904   const char *b = pb;
905 
906   int ca;
907   int cb;
908 
909   while (*a && *b)
910     {
911       ca = *a++;
912       cb = *b++;
913 
914       if (ca == cb)
915 	continue;
916 
917       ca = ca == '_' ? '-' : ca;
918       cb = cb == '_' ? '-' : cb;
919 
920       if (ca != cb)
921 	return cb - ca;
922     }
923 
924   ca = *a;
925   cb = *b;
926 
927   return cb - ca;
928 }
929 
930 static gboolean
span_parse_int(const char * attr_name,const char * attr_val,int * val,int line_number,GError ** error)931 span_parse_int (const char *attr_name,
932 		const char *attr_val,
933 		int *val,
934 		int line_number,
935 		GError **error)
936 {
937   const char *end = attr_val;
938 
939   if (!_pango_scan_int (&end, val) || *end != '\0')
940     {
941       g_set_error (error,
942 		   G_MARKUP_ERROR,
943 		   G_MARKUP_ERROR_INVALID_CONTENT,
944 		   _("Value of '%s' attribute on <span> tag "
945 		     "on line %d could not be parsed; "
946 		     "should be an integer, not '%s'"),
947 		   attr_name, line_number, attr_val);
948       return FALSE;
949     }
950 
951   return TRUE;
952 }
953 
954 static gboolean
span_parse_boolean(const char * attr_name,const char * attr_val,gboolean * val,int line_number,GError ** error)955 span_parse_boolean (const char *attr_name,
956 		    const char *attr_val,
957 		    gboolean *val,
958 		    int line_number,
959 		    GError **error)
960 {
961   if (strcmp (attr_val, "true") == 0 ||
962       strcmp (attr_val, "yes") == 0 ||
963       strcmp (attr_val, "t") == 0 ||
964       strcmp (attr_val, "y") == 0)
965     *val = TRUE;
966   else if (strcmp (attr_val, "false") == 0 ||
967 	   strcmp (attr_val, "no") == 0 ||
968 	   strcmp (attr_val, "f") == 0 ||
969 	   strcmp (attr_val, "n") == 0)
970     *val = FALSE;
971   else
972     {
973       g_set_error (error,
974 		   G_MARKUP_ERROR,
975 		   G_MARKUP_ERROR_INVALID_CONTENT,
976 		   _("Value of '%s' attribute on <span> tag "
977 		     "line %d should have one of "
978 		     "'true/yes/t/y' or 'false/no/f/n': '%s' is not valid"),
979 		   attr_name, line_number, attr_val);
980       return FALSE;
981     }
982 
983   return TRUE;
984 }
985 
986 static gboolean
span_parse_color(const char * attr_name,const char * attr_val,PangoColor * color,guint16 * alpha,int line_number,GError ** error)987 span_parse_color (const char *attr_name,
988 		  const char *attr_val,
989 		  PangoColor *color,
990                   guint16 *alpha,
991 		  int line_number,
992 		  GError **error)
993 {
994   if (!pango_color_parse_with_alpha (color, alpha, attr_val))
995     {
996       g_set_error (error,
997 		   G_MARKUP_ERROR,
998 		   G_MARKUP_ERROR_INVALID_CONTENT,
999 		   _("Value of '%s' attribute on <span> tag "
1000 		     "on line %d could not be parsed; "
1001 		     "should be a color specification, not '%s'"),
1002 		   attr_name, line_number, attr_val);
1003       return FALSE;
1004     }
1005 
1006   return TRUE;
1007 }
1008 
1009 static gboolean
span_parse_alpha(const char * attr_name,const char * attr_val,guint16 * val,int line_number,GError ** error)1010 span_parse_alpha (const char  *attr_name,
1011                   const char  *attr_val,
1012                   guint16     *val,
1013                   int          line_number,
1014                   GError     **error)
1015 {
1016   const char *end = attr_val;
1017   int int_val;
1018 
1019   if (_pango_scan_int (&end, &int_val))
1020     {
1021       if (*end == '\0' && int_val > 0 && int_val <= 0xffff)
1022         {
1023           *val = (guint16)int_val;
1024           return TRUE;
1025         }
1026       else if (*end == '%' && int_val > 0 && int_val <= 100)
1027         {
1028           *val = (guint16)(int_val * 0xffff / 100);
1029           return TRUE;
1030         }
1031       else
1032         {
1033           g_set_error (error,
1034                        G_MARKUP_ERROR,
1035                        G_MARKUP_ERROR_INVALID_CONTENT,
1036                        _("Value of '%s' attribute on <span> tag "
1037                          "on line %d could not be parsed; "
1038                          "should be between 0 and 65536 or a "
1039                          "percentage, not '%s'"),
1040                          attr_name, line_number, attr_val);
1041           return FALSE;
1042         }
1043     }
1044   else
1045     {
1046       g_set_error (error,
1047 		   G_MARKUP_ERROR,
1048 		   G_MARKUP_ERROR_INVALID_CONTENT,
1049 		   _("Value of '%s' attribute on <span> tag "
1050 		     "on line %d could not be parsed; "
1051 		     "should be an integer, not '%s'"),
1052 		   attr_name, line_number, attr_val);
1053       return FALSE;
1054     }
1055 
1056   return TRUE;
1057 }
1058 
1059 static gboolean
span_parse_enum(const char * attr_name,const char * attr_val,GType type,int * val,int line_number,GError ** error)1060 span_parse_enum (const char *attr_name,
1061 		 const char *attr_val,
1062 		 GType type,
1063 		 int *val,
1064 		 int line_number,
1065 		 GError **error)
1066 {
1067   char *possible_values = NULL;
1068 
1069   if (!_pango_parse_enum (type, attr_val, val, FALSE, &possible_values))
1070     {
1071       g_set_error (error,
1072 		   G_MARKUP_ERROR,
1073 		   G_MARKUP_ERROR_INVALID_CONTENT,
1074 		   _("'%s' is not a valid value for the '%s' "
1075 		     "attribute on <span> tag, line %d; valid "
1076 		     "values are %s"),
1077 		   attr_val, attr_name, line_number, possible_values);
1078       g_free (possible_values);
1079       return FALSE;
1080     }
1081 
1082   return TRUE;
1083 }
1084 
1085 static gboolean
span_parse_flags(const char * attr_name,const char * attr_val,GType type,int * val,int line_number,GError ** error)1086 span_parse_flags (const char  *attr_name,
1087                   const char  *attr_val,
1088                   GType        type,
1089                   int         *val,
1090                   int          line_number,
1091                   GError     **error)
1092 {
1093   char *possible_values = NULL;
1094 
1095   if (!pango_parse_flags (type, attr_val, val, &possible_values))
1096     {
1097       g_set_error (error,
1098                    G_MARKUP_ERROR,
1099                    G_MARKUP_ERROR_INVALID_CONTENT,
1100                    _("'%s' is not a valid value for the '%s' "
1101                      "attribute on <span> tag, line %d; valid "
1102                      "values are %s or combinations with |"),
1103                    attr_val, attr_name, line_number, possible_values);
1104       g_free (possible_values);
1105       return FALSE;
1106     }
1107 
1108   return TRUE;
1109 }
1110 
1111 static gboolean
span_parse_func(MarkupData * md G_GNUC_UNUSED,OpenTag * tag,const gchar ** names,const gchar ** values,GMarkupParseContext * context,GError ** error)1112 span_parse_func     (MarkupData            *md G_GNUC_UNUSED,
1113 		     OpenTag               *tag,
1114 		     const gchar          **names,
1115 		     const gchar          **values,
1116 		     GMarkupParseContext   *context,
1117 		     GError               **error)
1118 {
1119   int line_number, char_number;
1120   int i;
1121 
1122   const char *family = NULL;
1123   const char *size = NULL;
1124   const char *style = NULL;
1125   const char *weight = NULL;
1126   const char *variant = NULL;
1127   const char *stretch = NULL;
1128   const char *desc = NULL;
1129   const char *foreground = NULL;
1130   const char *background = NULL;
1131   const char *underline = NULL;
1132   const char *underline_color = NULL;
1133   const char *overline = NULL;
1134   const char *overline_color = NULL;
1135   const char *strikethrough = NULL;
1136   const char *strikethrough_color = NULL;
1137   const char *rise = NULL;
1138   const char *letter_spacing = NULL;
1139   const char *lang = NULL;
1140   const char *fallback = NULL;
1141   const char *gravity = NULL;
1142   const char *gravity_hint = NULL;
1143   const char *font_features = NULL;
1144   const char *alpha = NULL;
1145   const char *background_alpha = NULL;
1146   const char *allow_breaks = NULL;
1147   const char *insert_hyphens = NULL;
1148   const char *show = NULL;
1149 
1150   g_markup_parse_context_get_position (context,
1151 				       &line_number, &char_number);
1152 
1153 #define CHECK_DUPLICATE(var) G_STMT_START{                              \
1154 	  if ((var) != NULL) {                                          \
1155 	    g_set_error (error, G_MARKUP_ERROR,                         \
1156 			 G_MARKUP_ERROR_INVALID_CONTENT,                \
1157 			 _("Attribute '%s' occurs twice on <span> tag " \
1158 			   "on line %d char %d, may only occur once"),  \
1159 			 names[i], line_number, char_number);           \
1160 	    return FALSE;                                               \
1161 	  }}G_STMT_END
1162 #define CHECK_ATTRIBUTE2(var, name) \
1163 	if (attr_strcmp (names[i], (name)) == 0) { \
1164 	  CHECK_DUPLICATE (var); \
1165 	  (var) = values[i]; \
1166 	  found = TRUE; \
1167 	  break; \
1168 	}
1169 #define CHECK_ATTRIBUTE(var) CHECK_ATTRIBUTE2 (var, G_STRINGIFY (var))
1170 
1171   i = 0;
1172   while (names[i])
1173     {
1174       gboolean found = FALSE;
1175 
1176       switch (names[i][0]) {
1177       case 'a':
1178         CHECK_ATTRIBUTE (allow_breaks);
1179         CHECK_ATTRIBUTE (alpha);
1180         break;
1181       case 'b':
1182 	CHECK_ATTRIBUTE (background);
1183 	CHECK_ATTRIBUTE2(background, "bgcolor");
1184         CHECK_ATTRIBUTE (background_alpha);
1185         CHECK_ATTRIBUTE2(background_alpha, "bgalpha");
1186         break;
1187       case 'c':
1188 	CHECK_ATTRIBUTE2(foreground, "color");
1189         break;
1190       case 'f':
1191 	CHECK_ATTRIBUTE (fallback);
1192 	CHECK_ATTRIBUTE2(desc, "font");
1193 	CHECK_ATTRIBUTE2(desc, "font_desc");
1194 	CHECK_ATTRIBUTE2(family, "face");
1195 
1196 	CHECK_ATTRIBUTE2(family, "font_family");
1197 	CHECK_ATTRIBUTE2(size, "font_size");
1198 	CHECK_ATTRIBUTE2(stretch, "font_stretch");
1199 	CHECK_ATTRIBUTE2(style, "font_style");
1200 	CHECK_ATTRIBUTE2(variant, "font_variant");
1201 	CHECK_ATTRIBUTE2(weight, "font_weight");
1202 
1203 	CHECK_ATTRIBUTE (foreground);
1204 	CHECK_ATTRIBUTE2(foreground, "fgcolor");
1205 	CHECK_ATTRIBUTE2(alpha, "fgalpha");
1206 
1207 	CHECK_ATTRIBUTE (font_features);
1208 	break;
1209       case 's':
1210 	CHECK_ATTRIBUTE (show);
1211 	CHECK_ATTRIBUTE (size);
1212 	CHECK_ATTRIBUTE (stretch);
1213 	CHECK_ATTRIBUTE (strikethrough);
1214 	CHECK_ATTRIBUTE (strikethrough_color);
1215 	CHECK_ATTRIBUTE (style);
1216 	break;
1217       case 'g':
1218 	CHECK_ATTRIBUTE (gravity);
1219 	CHECK_ATTRIBUTE (gravity_hint);
1220 	break;
1221       case 'i':
1222         CHECK_ATTRIBUTE (insert_hyphens);
1223         break;
1224       case 'l':
1225 	CHECK_ATTRIBUTE (lang);
1226 	CHECK_ATTRIBUTE (letter_spacing);
1227 	break;
1228       case 'o':
1229 	CHECK_ATTRIBUTE (overline);
1230 	CHECK_ATTRIBUTE (overline_color);
1231 	break;
1232       case 'u':
1233 	CHECK_ATTRIBUTE (underline);
1234 	CHECK_ATTRIBUTE (underline_color);
1235 	break;
1236       case 'r':
1237 	CHECK_ATTRIBUTE (rise);
1238         break;
1239       case 'v':
1240 	CHECK_ATTRIBUTE (variant);
1241         break;
1242       case 'w':
1243 	CHECK_ATTRIBUTE (weight);
1244 	break;
1245       default:;
1246       }
1247 
1248       if (!found)
1249 	{
1250 	  g_set_error (error, G_MARKUP_ERROR,
1251 		       G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
1252 		       _("Attribute '%s' is not allowed on the <span> tag "
1253 			 "on line %d char %d"),
1254 		       names[i], line_number, char_number);
1255 	  return FALSE;
1256 	}
1257 
1258       ++i;
1259     }
1260 
1261   /* Parse desc first, then modify it with other font-related attributes. */
1262   if (G_UNLIKELY (desc))
1263     {
1264       PangoFontDescription *parsed;
1265 
1266       parsed = pango_font_description_from_string (desc);
1267       if (parsed)
1268 	{
1269 	  add_attribute (tag, pango_attr_font_desc_new (parsed));
1270 	  if (tag)
1271 	    open_tag_set_absolute_font_size (tag, pango_font_description_get_size (parsed));
1272 	  pango_font_description_free (parsed);
1273 	}
1274     }
1275 
1276   if (G_UNLIKELY (family))
1277     {
1278       add_attribute (tag, pango_attr_family_new (family));
1279     }
1280 
1281   if (G_UNLIKELY (size))
1282     {
1283       if (g_ascii_isdigit (*size))
1284 	{
1285 	  const char *end;
1286 	  gint n;
1287 
1288 	  if ((end = size, !_pango_scan_int (&end, &n)) || *end != '\0' || n < 0)
1289 	    {
1290 	      g_set_error (error,
1291 			   G_MARKUP_ERROR,
1292 			   G_MARKUP_ERROR_INVALID_CONTENT,
1293 			   _("Value of 'size' attribute on <span> tag on line %d "
1294 			     "could not be parsed; should be an integer no more than %d,"
1295 			     " or a string such as 'small', not '%s'"),
1296 			   line_number, INT_MAX, size);
1297 	      goto error;
1298 	    }
1299 
1300 	  add_attribute (tag, pango_attr_size_new (n));
1301 	  if (tag)
1302 	    open_tag_set_absolute_font_size (tag, n);
1303 	}
1304       else if (strcmp (size, "smaller") == 0)
1305 	{
1306 	  if (tag)
1307 	    {
1308 	      tag->scale_level_delta -= 1;
1309 	      tag->scale_level -= 1;
1310 	    }
1311 	}
1312       else if (strcmp (size, "larger") == 0)
1313 	{
1314 	  if (tag)
1315 	    {
1316 	      tag->scale_level_delta += 1;
1317 	      tag->scale_level += 1;
1318 	    }
1319 	}
1320       else if (parse_absolute_size (tag, size))
1321 	; /* nothing */
1322       else
1323 	{
1324 	  g_set_error (error,
1325 		       G_MARKUP_ERROR,
1326 		       G_MARKUP_ERROR_INVALID_CONTENT,
1327 		       _("Value of 'size' attribute on <span> tag on line %d "
1328 			 "could not be parsed; should be an integer, or a "
1329 			 "string such as 'small', not '%s'"),
1330 		       line_number, size);
1331 	  goto error;
1332 	}
1333     }
1334 
1335   if (G_UNLIKELY (style))
1336     {
1337       PangoStyle pango_style;
1338 
1339       if (pango_parse_style (style, &pango_style, FALSE))
1340 	add_attribute (tag, pango_attr_style_new (pango_style));
1341       else
1342 	{
1343 	  g_set_error (error,
1344 		       G_MARKUP_ERROR,
1345 		       G_MARKUP_ERROR_INVALID_CONTENT,
1346 		       _("'%s' is not a valid value for the 'style' attribute "
1347 			 "on <span> tag, line %d; valid values are "
1348 			 "'normal', 'oblique', 'italic'"),
1349 		       style, line_number);
1350 	  goto error;
1351 	}
1352     }
1353 
1354   if (G_UNLIKELY (weight))
1355     {
1356       PangoWeight pango_weight;
1357 
1358       if (pango_parse_weight (weight, &pango_weight, FALSE))
1359 	add_attribute (tag,
1360 		       pango_attr_weight_new (pango_weight));
1361       else
1362 	{
1363 	  g_set_error (error,
1364 		       G_MARKUP_ERROR,
1365 		       G_MARKUP_ERROR_INVALID_CONTENT,
1366 		       _("'%s' is not a valid value for the 'weight' "
1367 			 "attribute on <span> tag, line %d; valid "
1368 			 "values are for example 'light', 'ultrabold' or a number"),
1369 		       weight, line_number);
1370 	  goto error;
1371 	}
1372     }
1373 
1374   if (G_UNLIKELY (variant))
1375     {
1376       PangoVariant pango_variant;
1377 
1378       if (pango_parse_variant (variant, &pango_variant, FALSE))
1379 	add_attribute (tag, pango_attr_variant_new (pango_variant));
1380       else
1381 	{
1382 	  g_set_error (error,
1383 		       G_MARKUP_ERROR,
1384 		       G_MARKUP_ERROR_INVALID_CONTENT,
1385 		       _("'%s' is not a valid value for the 'variant' "
1386 			 "attribute on <span> tag, line %d; valid values are "
1387 			 "'normal', 'smallcaps'"),
1388 		       variant, line_number);
1389 	  goto error;
1390 	}
1391     }
1392 
1393   if (G_UNLIKELY (stretch))
1394     {
1395       PangoStretch pango_stretch;
1396 
1397       if (pango_parse_stretch (stretch, &pango_stretch, FALSE))
1398 	add_attribute (tag, pango_attr_stretch_new (pango_stretch));
1399       else
1400 	{
1401 	  g_set_error (error,
1402 		       G_MARKUP_ERROR,
1403 		       G_MARKUP_ERROR_INVALID_CONTENT,
1404 		       _("'%s' is not a valid value for the 'stretch' "
1405 			 "attribute on <span> tag, line %d; valid "
1406 			 "values are for example 'condensed', "
1407 			 "'ultraexpanded', 'normal'"),
1408 		       stretch, line_number);
1409 	  goto error;
1410 	}
1411     }
1412 
1413   if (G_UNLIKELY (foreground))
1414     {
1415       PangoColor color;
1416       guint16 alpha;
1417 
1418       if (!span_parse_color ("foreground", foreground, &color, &alpha, line_number, error))
1419 	goto error;
1420 
1421       add_attribute (tag, pango_attr_foreground_new (color.red, color.green, color.blue));
1422       if (alpha != 0xffff)
1423         add_attribute (tag, pango_attr_foreground_alpha_new (alpha));
1424     }
1425 
1426   if (G_UNLIKELY (background))
1427     {
1428       PangoColor color;
1429       guint16 alpha;
1430 
1431       if (!span_parse_color ("background", background, &color, &alpha, line_number, error))
1432 	goto error;
1433 
1434       add_attribute (tag, pango_attr_background_new (color.red, color.green, color.blue));
1435       if (alpha != 0xffff)
1436         add_attribute (tag, pango_attr_background_alpha_new (alpha));
1437     }
1438 
1439   if (G_UNLIKELY (alpha))
1440     {
1441       guint16 val;
1442 
1443       if (!span_parse_alpha ("alpha", alpha, &val, line_number, error))
1444         goto error;
1445 
1446       add_attribute (tag, pango_attr_foreground_alpha_new (val));
1447     }
1448 
1449   if (G_UNLIKELY (background_alpha))
1450     {
1451       guint16 val;
1452 
1453       if (!span_parse_alpha ("background_alpha", background_alpha, &val, line_number, error))
1454         goto error;
1455 
1456       add_attribute (tag, pango_attr_background_alpha_new (val));
1457     }
1458 
1459   if (G_UNLIKELY (underline))
1460     {
1461       PangoUnderline ul = PANGO_UNDERLINE_NONE;
1462 
1463       if (!span_parse_enum ("underline", underline, PANGO_TYPE_UNDERLINE, (int*)(void*)&ul, line_number, error))
1464 	goto error;
1465 
1466       add_attribute (tag, pango_attr_underline_new (ul));
1467     }
1468 
1469   if (G_UNLIKELY (underline_color))
1470     {
1471       PangoColor color;
1472 
1473       if (!span_parse_color ("underline_color", underline_color, &color, NULL, line_number, error))
1474 	goto error;
1475 
1476       add_attribute (tag, pango_attr_underline_color_new (color.red, color.green, color.blue));
1477     }
1478 
1479   if (G_UNLIKELY (overline))
1480     {
1481       PangoOverline ol = PANGO_OVERLINE_NONE;
1482 
1483       if (!span_parse_enum ("overline", overline, PANGO_TYPE_OVERLINE, (int*)(void*)&ol, line_number, error))
1484 	goto error;
1485 
1486       add_attribute (tag, pango_attr_overline_new (ol));
1487     }
1488 
1489   if (G_UNLIKELY (overline_color))
1490     {
1491       PangoColor color;
1492 
1493       if (!span_parse_color ("overline_color", overline_color, &color, NULL, line_number, error))
1494 	goto error;
1495 
1496       add_attribute (tag, pango_attr_overline_color_new (color.red, color.green, color.blue));
1497     }
1498 
1499   if (G_UNLIKELY (gravity))
1500     {
1501       PangoGravity gr = PANGO_GRAVITY_SOUTH;
1502 
1503       if (!span_parse_enum ("gravity", gravity, PANGO_TYPE_GRAVITY, (int*)(void*)&gr, line_number, error))
1504 	goto error;
1505 
1506       if (gr == PANGO_GRAVITY_AUTO)
1507         {
1508 	  g_set_error (error,
1509 		       G_MARKUP_ERROR,
1510 		       G_MARKUP_ERROR_INVALID_CONTENT,
1511 		       _("'%s' is not a valid value for the 'stretch' "
1512 			 "attribute on <span> tag, line %d; valid "
1513 			 "values are for example 'south', 'east', "
1514 			 "'north', 'west'"),
1515 		       gravity, line_number);
1516 	  goto error;
1517         }
1518 
1519       add_attribute (tag, pango_attr_gravity_new (gr));
1520     }
1521 
1522   if (G_UNLIKELY (gravity_hint))
1523     {
1524       PangoGravityHint hint = PANGO_GRAVITY_HINT_NATURAL;
1525 
1526       if (!span_parse_enum ("gravity_hint", gravity_hint, PANGO_TYPE_GRAVITY_HINT, (int*)(void*)&hint, line_number, error))
1527 	goto error;
1528 
1529       add_attribute (tag, pango_attr_gravity_hint_new (hint));
1530     }
1531 
1532   if (G_UNLIKELY (strikethrough))
1533     {
1534       gboolean b = FALSE;
1535 
1536       if (!span_parse_boolean ("strikethrough", strikethrough, &b, line_number, error))
1537 	goto error;
1538 
1539       add_attribute (tag, pango_attr_strikethrough_new (b));
1540     }
1541 
1542   if (G_UNLIKELY (strikethrough_color))
1543     {
1544       PangoColor color;
1545 
1546       if (!span_parse_color ("strikethrough_color", strikethrough_color, &color, NULL, line_number, error))
1547 	goto error;
1548 
1549       add_attribute (tag, pango_attr_strikethrough_color_new (color.red, color.green, color.blue));
1550     }
1551 
1552   if (G_UNLIKELY (fallback))
1553     {
1554       gboolean b = FALSE;
1555 
1556       if (!span_parse_boolean ("fallback", fallback, &b, line_number, error))
1557 	goto error;
1558 
1559       add_attribute (tag, pango_attr_fallback_new (b));
1560     }
1561 
1562   if (G_UNLIKELY (show))
1563     {
1564       PangoShowFlags flags;
1565 
1566       if (!span_parse_flags ("show", show, PANGO_TYPE_SHOW_FLAGS, (int*)(void*)&flags, line_number, error))
1567 	goto error;
1568 
1569       add_attribute (tag, pango_attr_show_new (flags));
1570     }
1571 
1572   if (G_UNLIKELY (rise))
1573     {
1574       gint n = 0;
1575 
1576       if (!span_parse_int ("rise", rise, &n, line_number, error))
1577 	goto error;
1578 
1579       add_attribute (tag, pango_attr_rise_new (n));
1580     }
1581 
1582   if (G_UNLIKELY (letter_spacing))
1583     {
1584       gint n = 0;
1585 
1586       if (!span_parse_int ("letter_spacing", letter_spacing, &n, line_number, error))
1587 	goto error;
1588 
1589       add_attribute (tag, pango_attr_letter_spacing_new (n));
1590     }
1591 
1592   if (G_UNLIKELY (lang))
1593     {
1594       add_attribute (tag,
1595 		     pango_attr_language_new (pango_language_from_string (lang)));
1596     }
1597 
1598   if (G_UNLIKELY (font_features))
1599     {
1600       add_attribute (tag, pango_attr_font_features_new (font_features));
1601     }
1602 
1603   if (G_UNLIKELY (allow_breaks))
1604     {
1605       gboolean b = FALSE;
1606 
1607       if (!span_parse_boolean ("allow_breaks", allow_breaks, &b, line_number, error))
1608 	goto error;
1609 
1610       add_attribute (tag, pango_attr_allow_breaks_new (b));
1611     }
1612 
1613   if (G_UNLIKELY (insert_hyphens))
1614     {
1615       gboolean b = FALSE;
1616 
1617       if (!span_parse_boolean ("insert_hyphens", insert_hyphens, &b, line_number, error))
1618 	goto error;
1619 
1620       add_attribute (tag, pango_attr_insert_hyphens_new (b));
1621     }
1622 
1623   return TRUE;
1624 
1625  error:
1626 
1627   return FALSE;
1628 }
1629 
1630 static gboolean
i_parse_func(MarkupData * md G_GNUC_UNUSED,OpenTag * tag,const gchar ** names,const gchar ** values G_GNUC_UNUSED,GMarkupParseContext * context,GError ** error)1631 i_parse_func        (MarkupData            *md G_GNUC_UNUSED,
1632 		     OpenTag               *tag,
1633 		     const gchar          **names,
1634 		     const gchar          **values G_GNUC_UNUSED,
1635 		     GMarkupParseContext   *context,
1636 		     GError               **error)
1637 {
1638   CHECK_NO_ATTRS("i");
1639   add_attribute (tag, pango_attr_style_new (PANGO_STYLE_ITALIC));
1640 
1641   return TRUE;
1642 }
1643 
1644 static gboolean
markup_parse_func(MarkupData * md G_GNUC_UNUSED,OpenTag * tag G_GNUC_UNUSED,const gchar ** names G_GNUC_UNUSED,const gchar ** values G_GNUC_UNUSED,GMarkupParseContext * context G_GNUC_UNUSED,GError ** error G_GNUC_UNUSED)1645 markup_parse_func (MarkupData            *md G_GNUC_UNUSED,
1646 		   OpenTag               *tag G_GNUC_UNUSED,
1647 		   const gchar          **names G_GNUC_UNUSED,
1648 		   const gchar          **values G_GNUC_UNUSED,
1649 		   GMarkupParseContext   *context G_GNUC_UNUSED,
1650 		   GError               **error G_GNUC_UNUSED)
1651 {
1652   /* We don't do anything with this tag at the moment. */
1653   CHECK_NO_ATTRS("markup");
1654 
1655   return TRUE;
1656 }
1657 
1658 static gboolean
s_parse_func(MarkupData * md G_GNUC_UNUSED,OpenTag * tag,const gchar ** names,const gchar ** values G_GNUC_UNUSED,GMarkupParseContext * context,GError ** error)1659 s_parse_func        (MarkupData            *md G_GNUC_UNUSED,
1660 		     OpenTag               *tag,
1661 		     const gchar          **names,
1662 		     const gchar          **values G_GNUC_UNUSED,
1663 		     GMarkupParseContext   *context,
1664 		     GError               **error)
1665 {
1666   CHECK_NO_ATTRS("s");
1667   add_attribute (tag, pango_attr_strikethrough_new (TRUE));
1668 
1669   return TRUE;
1670 }
1671 
1672 #define SUPERSUB_RISE 5000
1673 
1674 static gboolean
sub_parse_func(MarkupData * md G_GNUC_UNUSED,OpenTag * tag,const gchar ** names,const gchar ** values G_GNUC_UNUSED,GMarkupParseContext * context,GError ** error)1675 sub_parse_func      (MarkupData            *md G_GNUC_UNUSED,
1676 		     OpenTag               *tag,
1677 		     const gchar          **names,
1678 		     const gchar          **values G_GNUC_UNUSED,
1679 		     GMarkupParseContext   *context,
1680 		     GError               **error)
1681 {
1682   CHECK_NO_ATTRS("sub");
1683 
1684   /* Shrink font, and set a negative rise */
1685   if (tag)
1686     {
1687       tag->scale_level_delta -= 1;
1688       tag->scale_level -= 1;
1689     }
1690 
1691   add_attribute (tag, pango_attr_rise_new (-SUPERSUB_RISE));
1692 
1693   return TRUE;
1694 }
1695 
1696 static gboolean
sup_parse_func(MarkupData * md G_GNUC_UNUSED,OpenTag * tag,const gchar ** names,const gchar ** values G_GNUC_UNUSED,GMarkupParseContext * context,GError ** error)1697 sup_parse_func      (MarkupData            *md G_GNUC_UNUSED,
1698 		     OpenTag               *tag,
1699 		     const gchar          **names,
1700 		     const gchar          **values G_GNUC_UNUSED,
1701 		     GMarkupParseContext   *context,
1702 		     GError               **error)
1703 {
1704   CHECK_NO_ATTRS("sup");
1705 
1706   /* Shrink font, and set a positive rise */
1707   if (tag)
1708     {
1709       tag->scale_level_delta -= 1;
1710       tag->scale_level -= 1;
1711     }
1712 
1713   add_attribute (tag, pango_attr_rise_new (SUPERSUB_RISE));
1714 
1715   return TRUE;
1716 }
1717 
1718 static gboolean
small_parse_func(MarkupData * md G_GNUC_UNUSED,OpenTag * tag,const gchar ** names,const gchar ** values G_GNUC_UNUSED,GMarkupParseContext * context,GError ** error)1719 small_parse_func    (MarkupData            *md G_GNUC_UNUSED,
1720 		     OpenTag               *tag,
1721 		     const gchar          **names,
1722 		     const gchar          **values G_GNUC_UNUSED,
1723 		     GMarkupParseContext   *context,
1724 		     GError               **error)
1725 {
1726   CHECK_NO_ATTRS("small");
1727 
1728   /* Shrink text one level */
1729   if (tag)
1730     {
1731       tag->scale_level_delta -= 1;
1732       tag->scale_level -= 1;
1733     }
1734 
1735   return TRUE;
1736 }
1737 
1738 static gboolean
tt_parse_func(MarkupData * md G_GNUC_UNUSED,OpenTag * tag,const gchar ** names,const gchar ** values G_GNUC_UNUSED,GMarkupParseContext * context,GError ** error)1739 tt_parse_func       (MarkupData            *md G_GNUC_UNUSED,
1740 		     OpenTag               *tag,
1741 		     const gchar          **names,
1742 		     const gchar          **values G_GNUC_UNUSED,
1743 		     GMarkupParseContext   *context,
1744 		     GError               **error)
1745 {
1746   CHECK_NO_ATTRS("tt");
1747 
1748   add_attribute (tag, pango_attr_family_new ("Monospace"));
1749 
1750   return TRUE;
1751 }
1752 
1753 static gboolean
u_parse_func(MarkupData * md G_GNUC_UNUSED,OpenTag * tag,const gchar ** names,const gchar ** values G_GNUC_UNUSED,GMarkupParseContext * context,GError ** error)1754 u_parse_func        (MarkupData            *md G_GNUC_UNUSED,
1755 		     OpenTag               *tag,
1756 		     const gchar          **names,
1757 		     const gchar          **values G_GNUC_UNUSED,
1758 		     GMarkupParseContext   *context,
1759 		     GError               **error)
1760 {
1761   CHECK_NO_ATTRS("u");
1762   add_attribute (tag, pango_attr_underline_new (PANGO_UNDERLINE_SINGLE));
1763 
1764   return TRUE;
1765 }
1766