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