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

source code of gtk/subprojects/pango/pango/pango-markup.c