diff options
Diffstat (limited to 'pango2/pango-markup.c')
-rw-r--r-- | pango2/pango-markup.c | 2016 |
1 files changed, 2016 insertions, 0 deletions
diff --git a/pango2/pango-markup.c b/pango2/pango-markup.c new file mode 100644 index 00000000..c03196e8 --- /dev/null +++ b/pango2/pango-markup.c @@ -0,0 +1,2016 @@ +/* Pango2 + * pango-markup.c: Parse markup into attributed text + * + * Copyright (C) 2000 Red Hat Software + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include <string.h> +#include <stdlib.h> +#include <errno.h> + +#include "pango-markup.h" + +#include "pango-attributes.h" +#include "pango-attr-private.h" +#include "pango-font.h" +#include "pango-enum-types.h" +#include "pango-impl-utils.h" +#include "pango-font-description-private.h" + +/* CSS size levels */ +typedef enum +{ + XXSmall = -3, + XSmall = -2, + Small = -1, + Medium = 0, + Large = 1, + XLarge = 2, + XXLarge = 3 +} SizeLevel; + +typedef struct _MarkupData MarkupData; + +struct _MarkupData +{ + Pango2AttrList *attr_list; + GString *text; + GSList *tag_stack; + gsize index; + GSList *to_apply; + gunichar accel_marker; + gunichar accel_char; +}; + +typedef struct _OpenTag OpenTag; + +struct _OpenTag +{ + GSList *attrs; + gsize start_index; + /* Current total scale level; reset whenever + * an absolute size is set. + * Each "larger" ups it 1, each "smaller" decrements it 1 + */ + int scale_level; + /* Our impact on scale_level, so we know whether we + * need to create an attribute ourselves on close + */ + int scale_level_delta; + /* Base scale factor currently in effect + * or size that this tag + * forces, or parent's scale factor or size. + */ + double base_scale_factor; + int base_font_size; + guint has_base_font_size : 1; +}; + +typedef gboolean (*TagParseFunc) (MarkupData *md, + OpenTag *tag, + const char **names, + const char **values, + GMarkupParseContext *context, + GError **error); + +static gboolean b_parse_func (MarkupData *md, + OpenTag *tag, + const char **names, + const char **values, + GMarkupParseContext *context, + GError **error); +static gboolean big_parse_func (MarkupData *md, + OpenTag *tag, + const char **names, + const char **values, + GMarkupParseContext *context, + GError **error); +static gboolean span_parse_func (MarkupData *md, + OpenTag *tag, + const char **names, + const char **values, + GMarkupParseContext *context, + GError **error); +static gboolean i_parse_func (MarkupData *md, + OpenTag *tag, + const char **names, + const char **values, + GMarkupParseContext *context, + GError **error); +static gboolean markup_parse_func (MarkupData *md, + OpenTag *tag, + const char **names, + const char **values, + GMarkupParseContext *context, + GError **error); +static gboolean s_parse_func (MarkupData *md, + OpenTag *tag, + const char **names, + const char **values, + GMarkupParseContext *context, + GError **error); +static gboolean sub_parse_func (MarkupData *md, + OpenTag *tag, + const char **names, + const char **values, + GMarkupParseContext *context, + GError **error); +static gboolean sup_parse_func (MarkupData *md, + OpenTag *tag, + const char **names, + const char **values, + GMarkupParseContext *context, + GError **error); +static gboolean small_parse_func (MarkupData *md, + OpenTag *tag, + const char **names, + const char **values, + GMarkupParseContext *context, + GError **error); +static gboolean tt_parse_func (MarkupData *md, + OpenTag *tag, + const char **names, + const char **values, + GMarkupParseContext *context, + GError **error); +static gboolean u_parse_func (MarkupData *md, + OpenTag *tag, + const char **names, + const char **values, + GMarkupParseContext *context, + GError **error); + +static gboolean +_pango2_scan_int (const char **pos, int *out) +{ + char *end; + long temp; + + errno = 0; + temp = strtol (*pos, &end, 10); + if (errno == ERANGE) + { + errno = 0; + return FALSE; + } + + *out = (int)temp; + if ((long)(*out) != temp) + { + return FALSE; + } + + *pos = end; + + return TRUE; +} + +static gboolean +parse_int (const char *word, + int *out) +{ + char *end; + long val; + int i; + + if (word == NULL) + return FALSE; + + val = strtol (word, &end, 10); + i = val; + + if (end != word && *end == '\0' && val >= 0 && val == i) + { + if (out) + *out = i; + + return TRUE; + } + + return FALSE; +} + +static gboolean +_pango2_parse_enum (GType type, + const char *str, + int *value, + gboolean warn, + char **possible_values) +{ + GEnumClass *class = NULL; + gboolean ret = TRUE; + GEnumValue *v = NULL; + + class = g_type_class_ref (type); + + if (G_LIKELY (str)) + v = g_enum_get_value_by_nick (class, str); + + if (v) + { + if (G_LIKELY (value)) + *value = v->value; + } + else if (!parse_int (str, value)) + { + ret = FALSE; + if (G_LIKELY (warn || possible_values)) + { + int i; + GString *s = g_string_new (NULL); + + for (i = 0, v = g_enum_get_value (class, i); v; + i++ , v = g_enum_get_value (class, i)) + { + if (i) + g_string_append_c (s, '/'); + g_string_append (s, v->value_nick); + } + + if (warn) + g_warning ("%s must be one of %s", + G_ENUM_CLASS_TYPE_NAME(class), + s->str); + + if (possible_values) + *possible_values = s->str; + + g_string_free (s, possible_values ? FALSE : TRUE); + } + } + + g_type_class_unref (class); + + return ret; +} + +static gboolean +pango2_parse_flags (GType type, + const char *str, + int *value, + char **possible_values) +{ + GFlagsClass *class = NULL; + gboolean ret = TRUE; + GFlagsValue *v = NULL; + + class = g_type_class_ref (type); + + v = g_flags_get_value_by_nick (class, str); + + if (v) + { + *value = v->value; + } + else if (!parse_int (str, value)) + { + char **strv = g_strsplit (str, "|", 0); + int i; + + *value = 0; + + for (i = 0; strv[i]; i++) + { + strv[i] = g_strstrip (strv[i]); + v = g_flags_get_value_by_nick (class, strv[i]); + if (!v) + { + ret = FALSE; + break; + } + *value |= v->value; + } + g_strfreev (strv); + + if (!ret && possible_values) + { + int i; + GString *s = g_string_new (NULL); + + for (i = 0; i < class->n_values; i++) + { + v = &class->values[i]; + if (i) + g_string_append_c (s, '/'); + g_string_append (s, v->value_nick); + } + + *possible_values = s->str; + + g_string_free (s, FALSE); + } + } + + g_type_class_unref (class); + + return ret; +} + +static double +scale_factor (int scale_level, double base) +{ + double factor = base; + int i; + + /* 1.2 is the CSS scale factor between sizes */ + + if (scale_level > 0) + { + i = 0; + while (i < scale_level) + { + factor *= 1.2; + + ++i; + } + } + else if (scale_level < 0) + { + i = scale_level; + while (i < 0) + { + factor /= 1.2; + + ++i; + } + } + + return factor; +} + +static void +open_tag_free (OpenTag *ot) +{ + g_slist_foreach (ot->attrs, (GFunc) pango2_attribute_destroy, NULL); + g_slist_free (ot->attrs); + g_slice_free (OpenTag, ot); +} + +static void +open_tag_set_absolute_font_size (OpenTag *ot, + int font_size) +{ + ot->base_font_size = font_size; + ot->has_base_font_size = TRUE; + ot->scale_level = 0; + ot->scale_level_delta = 0; +} + +static void +open_tag_set_absolute_font_scale (OpenTag *ot, + double scale) +{ + ot->base_scale_factor = scale; + ot->has_base_font_size = FALSE; + ot->scale_level = 0; + ot->scale_level_delta = 0; +} + +static OpenTag* +markup_data_open_tag (MarkupData *md) +{ + OpenTag *ot; + OpenTag *parent = NULL; + + if (md->attr_list == NULL) + return NULL; + + if (md->tag_stack) + parent = md->tag_stack->data; + + ot = g_slice_new (OpenTag); + ot->attrs = NULL; + ot->start_index = md->index; + ot->scale_level_delta = 0; + + if (parent == NULL) + { + ot->base_scale_factor = 1.0; + ot->base_font_size = 0; + ot->has_base_font_size = FALSE; + ot->scale_level = 0; + } + else + { + ot->base_scale_factor = parent->base_scale_factor; + ot->base_font_size = parent->base_font_size; + ot->has_base_font_size = parent->has_base_font_size; + ot->scale_level = parent->scale_level; + } + + md->tag_stack = g_slist_prepend (md->tag_stack, ot); + + return ot; +} + +static void +markup_data_close_tag (MarkupData *md) +{ + OpenTag *ot; + GSList *tmp_list; + + if (md->attr_list == NULL) + return; + + /* pop the stack */ + ot = md->tag_stack->data; + md->tag_stack = g_slist_delete_link (md->tag_stack, + md->tag_stack); + + /* Adjust end indexes, and push each attr onto the front of the + * to_apply list. This means that outermost tags are on the front of + * that list; if we apply the list in order, then the innermost + * tags will "win" which is correct. + */ + tmp_list = ot->attrs; + while (tmp_list != NULL) + { + Pango2Attribute *a = tmp_list->data; + + a->start_index = ot->start_index; + a->end_index = md->index; + + md->to_apply = g_slist_prepend (md->to_apply, a); + + tmp_list = g_slist_next (tmp_list); + } + + if (ot->scale_level_delta != 0) + { + /* We affected relative font size; create an appropriate + * attribute and reverse our effects on the current level + */ + Pango2Attribute *a; + + if (ot->has_base_font_size) + { + /* Create a font using the absolute point size as the base size + * to be scaled from. + * We need to use a local variable to ensure that the compiler won't + * implicitly cast it to integer while the result is kept in registers, + * leading to a wrong approximation in i386 (with 387 FPU) + */ + volatile double size; + + size = scale_factor (ot->scale_level, 1.0) * ot->base_font_size; + a = pango2_attr_size_new (size); + } + else + { + /* Create a font using the current scale factor + * as the base size to be scaled from + */ + a = pango2_attr_scale_new (scale_factor (ot->scale_level, + ot->base_scale_factor)); + } + + a->start_index = ot->start_index; + a->end_index = md->index; + + md->to_apply = g_slist_prepend (md->to_apply, a); + } + + g_slist_free (ot->attrs); + g_slice_free (OpenTag, ot); +} + +static void +start_element_handler (GMarkupParseContext *context, + const char *element_name, + const char **attribute_names, + const char **attribute_values, + gpointer user_data, + GError **error) +{ + TagParseFunc parse_func = NULL; + OpenTag *ot; + + switch (*element_name) + { + case 'b': + if (strcmp ("b", element_name) == 0) + parse_func = b_parse_func; + else if (strcmp ("big", element_name) == 0) + parse_func = big_parse_func; + break; + + case 'i': + if (strcmp ("i", element_name) == 0) + parse_func = i_parse_func; + break; + + case 'm': + if (strcmp ("markup", element_name) == 0) + parse_func = markup_parse_func; + break; + + case 's': + if (strcmp ("span", element_name) == 0) + parse_func = span_parse_func; + else if (strcmp ("s", element_name) == 0) + parse_func = s_parse_func; + else if (strcmp ("sub", element_name) == 0) + parse_func = sub_parse_func; + else if (strcmp ("sup", element_name) == 0) + parse_func = sup_parse_func; + else if (strcmp ("small", element_name) == 0) + parse_func = small_parse_func; + break; + + case 't': + if (strcmp ("tt", element_name) == 0) + parse_func = tt_parse_func; + break; + + case 'u': + if (strcmp ("u", element_name) == 0) + parse_func = u_parse_func; + break; + + default: + break; + } + + if (parse_func == NULL) + { + int line_number, char_number; + + g_markup_parse_context_get_position (context, + &line_number, &char_number); + + g_set_error (error, + G_MARKUP_ERROR, + G_MARKUP_ERROR_UNKNOWN_ELEMENT, + "Unknown tag '%s' on line %d char %d", + element_name, + line_number, char_number); + + return; + } + + ot = markup_data_open_tag (user_data); + + /* note ot may be NULL if the user didn't want the attribute list */ + + if (!(*parse_func) (user_data, ot, + attribute_names, attribute_values, + context, error)) + { + /* there's nothing to do; we return an error, and end up + * freeing ot off the tag stack later. + */ + } +} + +static void +end_element_handler (GMarkupParseContext *context G_GNUC_UNUSED, + const char *element_name G_GNUC_UNUSED, + gpointer user_data, + GError **error G_GNUC_UNUSED) +{ + markup_data_close_tag (user_data); +} + +static void +text_handler (GMarkupParseContext *context G_GNUC_UNUSED, + const char *text, + gsize text_len, + gpointer user_data, + GError **error G_GNUC_UNUSED) +{ + MarkupData *md = user_data; + + if (md->accel_marker == 0) + { + /* Just append all the text */ + + md->index += text_len; + + g_string_append_len (md->text, text, text_len); + } + else + { + /* Parse the accelerator */ + const char *p; + const char *end; + const char *range_start; + const char *range_end; + gssize uline_index = -1; + gsize uline_len = 0; /* Quiet GCC */ + + range_end = NULL; + range_start = text; + p = text; + end = text + text_len; + + while (p != end) + { + gunichar c; + + c = g_utf8_get_char (p); + + if (range_end) + { + if (c == md->accel_marker) + { + /* escaped accel marker; move range_end + * past the accel marker that came before, + * append the whole thing + */ + range_end = g_utf8_next_char (range_end); + g_string_append_len (md->text, + range_start, + range_end - range_start); + md->index += range_end - range_start; + + /* set next range_start, skipping accel marker */ + range_start = g_utf8_next_char (p); + } + else + { + /* Don't append the accel marker (leave range_end + * alone); set the accel char to c; record location for + * underline attribute + */ + if (md->accel_char == 0) + md->accel_char = c; + + g_string_append_len (md->text, + range_start, + range_end - range_start); + md->index += range_end - range_start; + + /* The underline should go underneath the char + * we're setting as the next range_start + */ + if (md->attr_list != NULL) + { + /* Add the underline indicating the accelerator */ + Pango2Attribute *attr; + + attr = pango2_attr_underline_new (PANGO2_LINE_STYLE_SOLID); + + uline_index = md->index; + uline_len = g_utf8_next_char (p) - p; + + attr->start_index = uline_index; + attr->end_index = uline_index + uline_len; + + pango2_attr_list_change (md->attr_list, attr); + + attr = pango2_attr_underline_position_new (PANGO2_UNDERLINE_POSITION_UNDER); + + attr->start_index = uline_index; + attr->end_index = uline_index + uline_len; + + pango2_attr_list_change (md->attr_list, attr); + } + + /* set next range_start to include this char */ + range_start = p; + } + + /* reset range_end */ + range_end = NULL; + } + else if (c == md->accel_marker) + { + range_end = p; + } + + p = g_utf8_next_char (p); + } + + g_string_append_len (md->text, + range_start, + end - range_start); + md->index += end - range_start; + } +} + +static gboolean +xml_isspace (char c) +{ + return c == ' ' || c == '\t' || c == '\n' || c == '\r'; +} + +static const GMarkupParser pango2_markup_parser = { + start_element_handler, + end_element_handler, + text_handler, + NULL, + NULL +}; + +static void +destroy_markup_data (MarkupData *md) +{ + g_slist_free_full (md->tag_stack, (GDestroyNotify) open_tag_free); + g_slist_free_full (md->to_apply, (GDestroyNotify) pango2_attribute_destroy); + if (md->text) + g_string_free (md->text, TRUE); + + if (md->attr_list) + pango2_attr_list_unref (md->attr_list); + + g_slice_free (MarkupData, md); +} + +static GMarkupParseContext * +pango2_markup_parser_new_internal (char accel_marker, + GError **error, + gboolean want_attr_list) +{ + MarkupData *md; + GMarkupParseContext *context; + + md = g_slice_new (MarkupData); + + /* Don't bother creating these if they weren't requested; + * might be useful e.g. if you just want to validate + * some markup. + */ + if (want_attr_list) + md->attr_list = pango2_attr_list_new (); + else + md->attr_list = NULL; + + md->text = g_string_new (NULL); + + md->accel_marker = accel_marker; + md->accel_char = 0; + + md->index = 0; + md->tag_stack = NULL; + md->to_apply = NULL; + + context = g_markup_parse_context_new (&pango2_markup_parser, + 0, md, + (GDestroyNotify)destroy_markup_data); + + if (!g_markup_parse_context_parse (context, "<markup>", -1, error)) + g_clear_pointer (&context, g_markup_parse_context_free); + + return context; +} + +/** + * pango2_parse_markup: + * @markup_text: markup to parse (see the [Pango2 Markup](pango2_markup.html) docs) + * @length: length of @markup_text, or -1 if nul-terminated + * @accel_marker: character that precedes an accelerator, or 0 for none + * @attr_list: (out) (optional): address of return location for a `Pango2AttrList` + * @text: (out) (optional): address of return location for text with tags stripped + * @accel_char: (out) (optional): address of return location for accelerator char + * @error: address of return location for errors + * + * Parses marked-up text to create a plain-text string and an attribute list. + * + * See the [Pango2 Markup](pango2_markup.html) docs for details about the + * supported markup. + * + * If @accel_marker is nonzero, the given character will mark the + * character following it as an accelerator. For example, @accel_marker + * might be an ampersand or underscore. All characters marked + * as an accelerator will receive a %PANGO2_UNDERLINE_LOW attribute, + * and the first character so marked will be returned in @accel_char. + * Two @accel_marker characters following each other produce a single + * literal @accel_marker character. + * + * To parse a stream of pango markup incrementally, use [func@markup_parser_new]. + * + * If any error happens, none of the output arguments are touched except + * for @error. + * + * Return value: %FALSE if @error is set, otherwise %TRUE + **/ +gboolean +pango2_parse_markup (const char *markup_text, + int length, + gunichar accel_marker, + Pango2AttrList **attr_list, + char **text, + gunichar *accel_char, + GError **error) +{ + GMarkupParseContext *context = NULL; + gboolean ret = FALSE; + const char *p; + const char *end; + + g_return_val_if_fail (markup_text != NULL, FALSE); + + if (length < 0) + length = strlen (markup_text); + + p = markup_text; + end = markup_text + length; + while (p != end && xml_isspace (*p)) + ++p; + + context = pango2_markup_parser_new_internal (accel_marker, + error, + (attr_list != NULL)); + + if (!g_markup_parse_context_parse (context, + markup_text, + length, + error)) + goto out; + + if (!pango2_markup_parser_finish (context, + attr_list, + text, + accel_char, + error)) + goto out; + + ret = TRUE; + + out: + if (context != NULL) + g_markup_parse_context_free (context); + return ret; +} + +/** + * pango2_markup_parser_new: + * @accel_marker: character that precedes an accelerator, or 0 for none + * + * Incrementally parses marked-up text to create a plain-text string + * and an attribute list. + * + * See the [Pango2 Markup](pango2_markup.html) docs for details about the + * supported markup. + * + * If @accel_marker is nonzero, the given character will mark the + * character following it as an accelerator. For example, @accel_marker + * might be an ampersand or underscore. All characters marked + * as an accelerator will receive a %PANGO2_UNDERLINE_LOW attribute, + * and the first character so marked will be returned in @accel_char, + * when calling [func@markup_parser_finish]. Two @accel_marker characters + * following each other produce a single literal @accel_marker character. + * + * To feed markup to the parser, use [method@GLib.MarkupParseContext.parse] + * on the returned [struct@GLib.MarkupParseContext]. When done with feeding markup + * to the parser, use [func@markup_parser_finish] to get the data out + * of it, and then use [method@GLib.MarkupParseContext.free] to free it. + * + * This function is designed for applications that read Pango2 markup + * from streams. To simply parse a string containing Pango2 markup, + * the [func@Pango2.parse_markup] API is recommended instead. + * + * Return value: (transfer none): a `GMarkupParseContext` that should be + * destroyed with [method@GLib.MarkupParseContext.free]. + **/ +GMarkupParseContext * +pango2_markup_parser_new (gunichar accel_marker) +{ + return pango2_markup_parser_new_internal (accel_marker, NULL, TRUE); +} + +/** + * pango2_markup_parser_finish: + * @context: A valid parse context that was returned from [func@markup_parser_new] + * @attr_list: (out) (optional): address of return location for a `Pango2AttrList` + * @text: (out) (optional): address of return location for text with tags stripped + * @accel_char: (out) (optional): address of return location for accelerator char + * @error: address of return location for errors + * + * Finishes parsing markup. + * + * After feeding a Pango2 markup parser some data with [method@GLib.MarkupParseContext.parse], + * use this function to get the list of attributes and text out of the + * markup. This function will not free @context, use [method@GLib.MarkupParseContext.free] + * to do so. + * + * Return value: %FALSE if @error is set, otherwise %TRUE + */ +gboolean +pango2_markup_parser_finish (GMarkupParseContext *context, + Pango2AttrList **attr_list, + char **text, + gunichar *accel_char, + GError **error) +{ + gboolean ret = FALSE; + MarkupData *md = g_markup_parse_context_get_user_data (context); + GSList *tmp_list; + + if (!g_markup_parse_context_parse (context, + "</markup>", + -1, + error)) + goto out; + + if (!g_markup_parse_context_end_parse (context, error)) + goto out; + + if (md->attr_list) + { + /* The apply list has the most-recently-closed tags first; + * we want to apply the least-recently-closed tag last. + */ + tmp_list = md->to_apply; + while (tmp_list != NULL) + { + Pango2Attribute *attr = tmp_list->data; + + /* Innermost tags before outermost */ + pango2_attr_list_insert (md->attr_list, attr); + + tmp_list = g_slist_next (tmp_list); + } + g_slist_free (md->to_apply); + md->to_apply = NULL; + } + + if (attr_list) + { + *attr_list = md->attr_list; + md->attr_list = NULL; + } + + if (text) + { + *text = g_string_free (md->text, FALSE); + md->text = NULL; + } + + if (accel_char) + *accel_char = md->accel_char; + + g_assert (md->tag_stack == NULL); + ret = TRUE; + + out: + return ret; +} + +static void +set_bad_attribute (GError **error, + GMarkupParseContext *context, + const char *element_name, + const char *attribute_name) +{ + int line_number, char_number; + + g_markup_parse_context_get_position (context, + &line_number, &char_number); + + g_set_error (error, + G_MARKUP_ERROR, + G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE, + "Tag '%s' does not support attribute '%s' on line %d char %d", + element_name, + attribute_name, + line_number, char_number); +} + +static void +add_attribute (OpenTag *ot, + Pango2Attribute *attr) +{ + if (ot == NULL) + pango2_attribute_destroy (attr); + else + ot->attrs = g_slist_prepend (ot->attrs, attr); +} + +#define CHECK_NO_ATTRS(elem) G_STMT_START { \ + if (*names != NULL) { \ + set_bad_attribute (error, context, (elem), *names); \ + return FALSE; \ + } }G_STMT_END + +static gboolean +b_parse_func (MarkupData *md G_GNUC_UNUSED, + OpenTag *tag, + const char **names, + const char **values G_GNUC_UNUSED, + GMarkupParseContext *context, + GError **error) +{ + CHECK_NO_ATTRS("b"); + add_attribute (tag, pango2_attr_weight_new (PANGO2_WEIGHT_BOLD)); + return TRUE; +} + +static gboolean +big_parse_func (MarkupData *md G_GNUC_UNUSED, + OpenTag *tag, + const char **names, + const char **values G_GNUC_UNUSED, + GMarkupParseContext *context, + GError **error) +{ + CHECK_NO_ATTRS("big"); + + /* Grow text one level */ + if (tag) + { + tag->scale_level_delta += 1; + tag->scale_level += 1; + } + + return TRUE; +} + +static gboolean +parse_percentage (const char *input, + double *val) +{ + double v; + char *end; + + v = g_ascii_strtod (input, &end); + if (errno == 0 && strcmp (end, "%") == 0 && v > 0) + { + *val = v; + return TRUE; + } + + return FALSE; +} + +static gboolean +parse_absolute_size (OpenTag *tag, + const char *size) +{ + SizeLevel level = Medium; + double val; + double factor; + + if (strcmp (size, "xx-small") == 0) + level = XXSmall; + else if (strcmp (size, "x-small") == 0) + level = XSmall; + else if (strcmp (size, "small") == 0) + level = Small; + else if (strcmp (size, "medium") == 0) + level = Medium; + else if (strcmp (size, "large") == 0) + level = Large; + else if (strcmp (size, "x-large") == 0) + level = XLarge; + else if (strcmp (size, "xx-large") == 0) + level = XXLarge; + else if (parse_percentage (size, &val)) + { + factor = val / 100.0; + goto done; + } + else + return FALSE; + + /* This is "absolute" in that it's relative to the base font, + * but not to sizes created by any other tags + */ + factor = scale_factor (level, 1.0); + +done: + add_attribute (tag, pango2_attr_scale_new (factor)); + if (tag) + open_tag_set_absolute_font_scale (tag, factor); + + return TRUE; +} + +/* a string compare func that ignores '-' vs '_' differences */ +static int +attr_strcmp (gconstpointer pa, + gconstpointer pb) +{ + const char *a = pa; + const char *b = pb; + + int ca; + int cb; + + while (*a && *b) + { + ca = *a++; + cb = *b++; + + if (ca == cb) + continue; + + ca = ca == '_' ? '-' : ca; + cb = cb == '_' ? '-' : cb; + + if (ca != cb) + return cb - ca; + } + + ca = *a; + cb = *b; + + return cb - ca; +} + +static gboolean +span_parse_int (const char *attr_name, + const char *attr_val, + int *val, + int line_number, + GError **error) +{ + const char *end = attr_val; + + if (!_pango2_scan_int (&end, val) || *end != '\0') + { + g_set_error (error, + G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "Value of '%s' attribute on <span> tag " + "on line %d could not be parsed; " + "should be an integer, not '%s'", + attr_name, line_number, attr_val); + return FALSE; + } + + return TRUE; +} + +static gboolean +span_parse_float (const char *attr_name, + const char *attr_val, + double *val, + int line_number, + GError **error) +{ + *val = g_ascii_strtod (attr_val, NULL); + if (errno != 0) + { + g_set_error (error, + G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "Value of '%s' attribute on <span> tag " + "on line %d could not be parsed; " + "should be a number, not '%s'", + attr_name, line_number, attr_val); + return FALSE; + } + + return TRUE; +} + +static gboolean +span_parse_boolean (const char *attr_name, + const char *attr_val, + gboolean *val, + int line_number, + GError **error) +{ + if (strcmp (attr_val, "true") == 0 || + strcmp (attr_val, "yes") == 0 || + strcmp (attr_val, "t") == 0 || + strcmp (attr_val, "y") == 0) + *val = TRUE; + else if (strcmp (attr_val, "false") == 0 || + strcmp (attr_val, "no") == 0 || + strcmp (attr_val, "f") == 0 || + strcmp (attr_val, "n") == 0) + *val = FALSE; + else + { + g_set_error (error, + G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "Value of '%s' attribute on <span> tag " + "line %d should have one of " + "'true/yes/t/y' or 'false/no/f/n': '%s' is not valid", + attr_name, line_number, attr_val); + return FALSE; + } + + return TRUE; +} + +static gboolean +span_parse_color (const char *attr_name, + const char *attr_val, + Pango2Color *color, + int line_number, + GError **error) +{ + if (!pango2_color_parse (color, attr_val)) + { + g_set_error (error, + G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "Value of '%s' attribute on <span> tag " + "on line %d could not be parsed; " + "should be a color specification, not '%s'", + attr_name, line_number, attr_val); + return FALSE; + } + + return TRUE; +} + +static gboolean +span_parse_enum (const char *attr_name, + const char *attr_val, + GType type, + int *val, + int line_number, + GError **error) +{ + char *possible_values = NULL; + + if (!_pango2_parse_enum (type, attr_val, val, FALSE, &possible_values)) + { + g_set_error (error, + G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "'%s' is not a valid value for the '%s' " + "attribute on <span> tag, line %d; valid " + "values are %s", + attr_val, attr_name, line_number, possible_values); + g_free (possible_values); + return FALSE; + } + + return TRUE; +} + +static gboolean +span_parse_flags (const char *attr_name, + const char *attr_val, + GType type, + int *val, + int line_number, + GError **error) +{ + char *possible_values = NULL; + + if (!pango2_parse_flags (type, attr_val, val, &possible_values)) + { + g_set_error (error, + G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "'%s' is not a valid value for the '%s' " + "attribute on <span> tag, line %d; valid " + "values are %s or combinations with |", + attr_val, attr_name, line_number, possible_values); + g_free (possible_values); + return FALSE; + } + + return TRUE; +} + +static gboolean +parse_length (const char *attr_val, + int *result) +{ + const char *attr; + int n; + + attr = attr_val; + if (_pango2_scan_int (&attr, &n) && *attr == '\0') + { + *result = n; + return TRUE; + } + else + { + double val; + char *end; + + val = g_ascii_strtod (attr_val, &end); + if (errno == 0 && strcmp (end, "pt") == 0) + { + *result = val * PANGO2_SCALE; + return TRUE; + } + } + + return FALSE; +} + +static gboolean +span_parse_func (MarkupData *md G_GNUC_UNUSED, + OpenTag *tag, + const char **names, + const char **values, + GMarkupParseContext *context, + GError **error) +{ + int line_number, char_number; + int i; + + const char *family = NULL; + const char *size = NULL; + const char *style = NULL; + const char *weight = NULL; + const char *variant = NULL; + const char *stretch = NULL; + const char *desc = NULL; + const char *foreground = NULL; + const char *background = NULL; + const char *underline = NULL; + const char *underline_position = NULL; + const char *underline_color = NULL; + const char *overline = NULL; + const char *overline_color = NULL; + const char *strikethrough = NULL; + const char *strikethrough_color = NULL; + const char *rise = NULL; + const char *baseline_shift = NULL; + const char *letter_spacing = NULL; + const char *lang = NULL; + const char *fallback = NULL; + const char *gravity = NULL; + const char *gravity_hint = NULL; + const char *font_features = NULL; + const char *allow_breaks = NULL; + const char *insert_hyphens = NULL; + const char *show = NULL; + const char *line_height = NULL; + const char *text_transform = NULL; + const char *segment = NULL; + const char *font_scale = NULL; + + g_markup_parse_context_get_position (context, + &line_number, &char_number); + +#define CHECK_DUPLICATE(var) G_STMT_START{ \ + if ((var) != NULL) { \ + g_set_error (error, G_MARKUP_ERROR, \ + G_MARKUP_ERROR_INVALID_CONTENT, \ + "Attribute '%s' occurs twice on <span> tag " \ + "on line %d char %d, may only occur once", \ + names[i], line_number, char_number); \ + return FALSE; \ + }}G_STMT_END +#define CHECK_ATTRIBUTE2(var, name) \ + if (attr_strcmp (names[i], (name)) == 0) { \ + CHECK_DUPLICATE (var); \ + (var) = values[i]; \ + found = TRUE; \ + break; \ + } +#define CHECK_ATTRIBUTE(var) CHECK_ATTRIBUTE2 (var, G_STRINGIFY (var)) + + i = 0; + while (names[i]) + { + gboolean found = FALSE; + + switch (names[i][0]) { + case 'a': + CHECK_ATTRIBUTE (allow_breaks); + break; + case 'b': + CHECK_ATTRIBUTE (background); + CHECK_ATTRIBUTE2 (background, "bgcolor"); + CHECK_ATTRIBUTE (baseline_shift); + break; + case 'c': + CHECK_ATTRIBUTE2 (foreground, "color"); + break; + case 'f': + CHECK_ATTRIBUTE (fallback); + CHECK_ATTRIBUTE2 (desc, "font"); + CHECK_ATTRIBUTE2 (desc, "font_desc"); + CHECK_ATTRIBUTE2 (family, "face"); + + CHECK_ATTRIBUTE2 (family, "font_family"); + CHECK_ATTRIBUTE2 (size, "font_size"); + CHECK_ATTRIBUTE2 (stretch, "font_stretch"); + CHECK_ATTRIBUTE2 (style, "font_style"); + CHECK_ATTRIBUTE2 (variant, "font_variant"); + CHECK_ATTRIBUTE2 (weight, "font_weight"); + CHECK_ATTRIBUTE (font_scale); + + CHECK_ATTRIBUTE (foreground); + CHECK_ATTRIBUTE2 (foreground, "fgcolor"); + + CHECK_ATTRIBUTE (font_features); + break; + case 's': + CHECK_ATTRIBUTE (show); + CHECK_ATTRIBUTE (size); + CHECK_ATTRIBUTE (stretch); + CHECK_ATTRIBUTE (strikethrough); + CHECK_ATTRIBUTE (strikethrough_color); + CHECK_ATTRIBUTE (style); + CHECK_ATTRIBUTE (segment); + break; + case 't': + CHECK_ATTRIBUTE (text_transform); + break; + case 'g': + CHECK_ATTRIBUTE (gravity); + CHECK_ATTRIBUTE (gravity_hint); + break; + case 'i': + CHECK_ATTRIBUTE (insert_hyphens); + break; + case 'l': + CHECK_ATTRIBUTE (lang); + CHECK_ATTRIBUTE (letter_spacing); + CHECK_ATTRIBUTE (line_height); + break; + case 'o': + CHECK_ATTRIBUTE (overline); + CHECK_ATTRIBUTE (overline_color); + break; + case 'u': + CHECK_ATTRIBUTE (underline); + CHECK_ATTRIBUTE (underline_position); + CHECK_ATTRIBUTE (underline_color); + break; + case 'r': + CHECK_ATTRIBUTE (rise); + break; + case 'v': + CHECK_ATTRIBUTE (variant); + break; + case 'w': + CHECK_ATTRIBUTE (weight); + break; + default:; + } + + if (!found) + { + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE, + "Attribute '%s' is not allowed on the <span> tag " + "on line %d char %d", + names[i], line_number, char_number); + return FALSE; + } + + ++i; + } + + /* Parse desc first, then modify it with other font-related attributes. */ + if (G_UNLIKELY (desc)) + { + Pango2FontDescription *parsed; + + parsed = pango2_font_description_from_string (desc); + if (parsed) + { + add_attribute (tag, pango2_attr_font_desc_new (parsed)); + if (tag) + open_tag_set_absolute_font_size (tag, pango2_font_description_get_size (parsed)); + pango2_font_description_free (parsed); + } + } + + if (G_UNLIKELY (family)) + { + add_attribute (tag, pango2_attr_family_new (family)); + } + + if (G_UNLIKELY (size)) + { + int n; + + if (parse_length (size, &n) && n > 0) + { + add_attribute (tag, pango2_attr_size_new (n)); + if (tag) + open_tag_set_absolute_font_size (tag, n); + } + else if (strcmp (size, "smaller") == 0) + { + if (tag) + { + tag->scale_level_delta -= 1; + tag->scale_level -= 1; + } + } + else if (strcmp (size, "larger") == 0) + { + if (tag) + { + tag->scale_level_delta += 1; + tag->scale_level += 1; + } + } + else if (parse_absolute_size (tag, size)) + ; /* nothing */ + else + { + g_set_error (error, + G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "Value of 'size' attribute on <span> tag on line %d " + "could not be parsed; should be an integer, or a " + "string such as 'small', not '%s'", + line_number, size); + goto error; + } + } + + if (G_UNLIKELY (style)) + { + Pango2Style pango2_style; + + if (pango2_parse_style (style, &pango2_style, FALSE)) + add_attribute (tag, pango2_attr_style_new (pango2_style)); + else + { + g_set_error (error, + G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "'%s' is not a valid value for the 'style' attribute " + "on <span> tag, line %d; valid values are " + "'normal', 'oblique', 'italic'", + style, line_number); + goto error; + } + } + + if (G_UNLIKELY (weight)) + { + Pango2Weight pango2_weight; + + if (pango2_parse_weight (weight, &pango2_weight, FALSE)) + add_attribute (tag, + pango2_attr_weight_new (pango2_weight)); + else + { + g_set_error (error, + G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "'%s' is not a valid value for the 'weight' " + "attribute on <span> tag, line %d; valid " + "values are for example 'light', 'ultrabold' or a number", + weight, line_number); + goto error; + } + } + + if (G_UNLIKELY (variant)) + { + Pango2Variant pango2_variant; + + if (pango2_parse_variant (variant, &pango2_variant, FALSE)) + add_attribute (tag, pango2_attr_variant_new (pango2_variant)); + else + { + g_set_error (error, + G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "'%s' is not a valid value for the 'variant' " + "attribute on <span> tag, line %d; valid values are " + "'normal', 'smallcaps'", + variant, line_number); + goto error; + } + } + + if (G_UNLIKELY (stretch)) + { + Pango2Stretch pango2_stretch; + + if (pango2_parse_stretch (stretch, &pango2_stretch, FALSE)) + add_attribute (tag, pango2_attr_stretch_new (pango2_stretch)); + else + { + g_set_error (error, + G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "'%s' is not a valid value for the 'stretch' " + "attribute on <span> tag, line %d; valid " + "values are for example 'condensed', " + "'ultraexpanded', 'normal'", + stretch, line_number); + goto error; + } + } + + if (G_UNLIKELY (foreground)) + { + Pango2Color color; + + if (!span_parse_color ("foreground", foreground, &color, line_number, error)) + goto error; + + add_attribute (tag, pango2_attr_foreground_new (&color)); + } + + if (G_UNLIKELY (background)) + { + Pango2Color color; + + if (!span_parse_color ("background", background, &color, line_number, error)) + goto error; + + add_attribute (tag, pango2_attr_background_new (&color)); + } + + if (G_UNLIKELY (underline)) + { + Pango2LineStyle style = PANGO2_LINE_STYLE_NONE; + + if (!span_parse_enum ("underline", underline, PANGO2_TYPE_LINE_STYLE, (int*)(void*)&style, line_number, error)) + goto error; + + add_attribute (tag, pango2_attr_underline_new (style)); + } + + if (G_UNLIKELY (underline_position)) + { + Pango2UnderlinePosition pos = PANGO2_UNDERLINE_POSITION_NORMAL; + + if (!span_parse_enum ("underline_position", underline_position, PANGO2_TYPE_UNDERLINE_POSITION, (int*)(void*)&pos, line_number, error)) + goto error; + + add_attribute (tag, pango2_attr_underline_position_new (pos)); + } + + if (G_UNLIKELY (underline_color)) + { + Pango2Color color; + + if (!span_parse_color ("underline_color", underline_color, &color, line_number, error)) + goto error; + + add_attribute (tag, pango2_attr_underline_color_new (&color)); + } + + if (G_UNLIKELY (overline)) + { + Pango2LineStyle style = PANGO2_LINE_STYLE_NONE; + + if (!span_parse_enum ("overline", overline, PANGO2_TYPE_LINE_STYLE, (int*)(void*)&style, line_number, error)) + goto error; + + add_attribute (tag, pango2_attr_overline_new (style)); + } + + if (G_UNLIKELY (overline_color)) + { + Pango2Color color; + + if (!span_parse_color ("overline_color", overline_color, &color, line_number, error)) + goto error; + + add_attribute (tag, pango2_attr_overline_color_new (&color)); + } + + if (G_UNLIKELY (gravity)) + { + Pango2Gravity gr = PANGO2_GRAVITY_SOUTH; + + if (!span_parse_enum ("gravity", gravity, PANGO2_TYPE_GRAVITY, (int*)(void*)&gr, line_number, error)) + goto error; + + if (gr == PANGO2_GRAVITY_AUTO) + { + g_set_error (error, + G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "'%s' is not a valid value for the 'gravity' " + "attribute on <span> tag, line %d; valid " + "values are for example 'south', 'east', " + "'north', 'west'", + gravity, line_number); + goto error; + } + + add_attribute (tag, pango2_attr_gravity_new (gr)); + } + + if (G_UNLIKELY (gravity_hint)) + { + Pango2GravityHint hint = PANGO2_GRAVITY_HINT_NATURAL; + + if (!span_parse_enum ("gravity_hint", gravity_hint, PANGO2_TYPE_GRAVITY_HINT, (int*)(void*)&hint, line_number, error)) + goto error; + + add_attribute (tag, pango2_attr_gravity_hint_new (hint)); + } + + if (G_UNLIKELY (strikethrough)) + { + Pango2LineStyle style = PANGO2_LINE_STYLE_NONE; + + if (!span_parse_enum ("strikethrough", strikethrough, PANGO2_TYPE_LINE_STYLE, (int*)(void*)&style, line_number, error)) + goto error; + + add_attribute (tag, pango2_attr_strikethrough_new (style)); + } + + if (G_UNLIKELY (strikethrough_color)) + { + Pango2Color color; + + if (!span_parse_color ("strikethrough_color", strikethrough_color, &color, line_number, error)) + goto error; + + add_attribute (tag, pango2_attr_strikethrough_color_new (&color)); + } + + if (G_UNLIKELY (fallback)) + { + gboolean b = FALSE; + + if (!span_parse_boolean ("fallback", fallback, &b, line_number, error)) + goto error; + + add_attribute (tag, pango2_attr_fallback_new (b)); + } + + if (G_UNLIKELY (show)) + { + Pango2ShowFlags flags; + + if (!span_parse_flags ("show", show, PANGO2_TYPE_SHOW_FLAGS, (int*)(void*)&flags, line_number, error)) + goto error; + + add_attribute (tag, pango2_attr_show_new (flags)); + } + + if (G_UNLIKELY (text_transform)) + { + Pango2TextTransform tf; + + if (!span_parse_enum ("text_transform", text_transform, PANGO2_TYPE_TEXT_TRANSFORM, (int*)(void*)&tf, line_number, error)) + goto error; + + add_attribute (tag, pango2_attr_text_transform_new (tf)); + } + + if (G_UNLIKELY (rise)) + { + int n = 0; + + if (!parse_length (rise, &n)) + { + g_set_error (error, + G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "Value of 'rise' attribute on <span> tag on line %d " + "could not be parsed; should be an integer, or a " + "string such as '5.5pt', not '%s'", + line_number, rise); + goto error; + } + + add_attribute (tag, pango2_attr_rise_new (n)); + } + + if (G_UNLIKELY (baseline_shift)) + { + int shift = 0; + + if (span_parse_enum ("baseline_shift", baseline_shift, PANGO2_TYPE_BASELINE_SHIFT, (int*)(void*)&shift, line_number, NULL)) + add_attribute (tag, pango2_attr_baseline_shift_new (shift)); + else if (parse_length (baseline_shift, &shift) && (shift > 1024 || shift < -1024)) + add_attribute (tag, pango2_attr_baseline_shift_new (shift)); + else + { + g_set_error (error, + G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "Value of 'baseline_shift' attribute on <span> tag on line %d " + "could not be parsed; should be 'superscript' or 'subscript' or " + "an integer, or a string such as '5.5pt', not '%s'", + line_number, baseline_shift); + goto error; + } + + } + + if (G_UNLIKELY (font_scale)) + { + Pango2FontScale scale; + + if (!span_parse_enum ("font_scale", font_scale, PANGO2_TYPE_FONT_SCALE, (int*)(void*)&scale, line_number, error)) + goto error; + + add_attribute (tag, pango2_attr_font_scale_new (scale)); + } + + if (G_UNLIKELY (letter_spacing)) + { + int n = 0; + + if (!span_parse_int ("letter_spacing", letter_spacing, &n, line_number, error)) + goto error; + + add_attribute (tag, pango2_attr_letter_spacing_new (n)); + } + + if (G_UNLIKELY (line_height)) + { + double f = 0; + + if (!span_parse_float ("line_height", line_height, &f, line_number, error)) + goto error; + + if (f > 1024.0 && strchr (line_height, '.') == 0) + add_attribute (tag, pango2_attr_line_height_new_absolute ((int)f)); + else + add_attribute (tag, pango2_attr_line_height_new (f)); + } + + if (G_UNLIKELY (lang)) + { + add_attribute (tag, + pango2_attr_language_new (pango2_language_from_string (lang))); + } + + if (G_UNLIKELY (font_features)) + { + add_attribute (tag, pango2_attr_font_features_new (font_features)); + } + + if (G_UNLIKELY (allow_breaks)) + { + gboolean b = FALSE; + + if (!span_parse_boolean ("allow_breaks", allow_breaks, &b, line_number, error)) + goto error; + + add_attribute (tag, pango2_attr_allow_breaks_new (b)); + } + + if (G_UNLIKELY (insert_hyphens)) + { + gboolean b = FALSE; + + if (!span_parse_boolean ("insert_hyphens", insert_hyphens, &b, line_number, error)) + goto error; + + add_attribute (tag, pango2_attr_insert_hyphens_new (b)); + } + + if (G_UNLIKELY (segment)) + { + if (strcmp (segment, "word") == 0) + add_attribute (tag, pango2_attr_word_new ()); + else if (strcmp (segment, "sentence") == 0) + add_attribute (tag, pango2_attr_sentence_new ()); + else if (strcmp (segment, "paragraph") == 0) + add_attribute (tag, pango2_attr_paragraph_new ()); + else + { + g_set_error (error, + G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "Value of 'segment' attribute on <span> tag on line %d " + "could not be parsed; should be one of 'word', 'sentence' " + "or 'paragraph', not '%s'", + line_number, segment); + goto error; + } + } + + return TRUE; + + error: + + return FALSE; +} + +static gboolean +i_parse_func (MarkupData *md G_GNUC_UNUSED, + OpenTag *tag, + const char **names, + const char **values G_GNUC_UNUSED, + GMarkupParseContext *context, + GError **error) +{ + CHECK_NO_ATTRS ("i"); + + add_attribute (tag, pango2_attr_style_new (PANGO2_STYLE_ITALIC)); + + return TRUE; +} + +static gboolean +markup_parse_func (MarkupData *md G_GNUC_UNUSED, + OpenTag *tag G_GNUC_UNUSED, + const char **names G_GNUC_UNUSED, + const char **values G_GNUC_UNUSED, + GMarkupParseContext *context G_GNUC_UNUSED, + GError **error G_GNUC_UNUSED) +{ + /* We don't do anything with this tag at the moment. */ + CHECK_NO_ATTRS ("markup"); + + return TRUE; +} + +static gboolean +s_parse_func (MarkupData *md G_GNUC_UNUSED, + OpenTag *tag, + const char **names, + const char **values G_GNUC_UNUSED, + GMarkupParseContext *context, + GError **error) +{ + CHECK_NO_ATTRS ("s"); + + add_attribute (tag, pango2_attr_strikethrough_new (TRUE)); + + return TRUE; +} + +static gboolean +sub_parse_func (MarkupData *md G_GNUC_UNUSED, + OpenTag *tag, + const char **names, + const char **values G_GNUC_UNUSED, + GMarkupParseContext *context, + GError **error) +{ + CHECK_NO_ATTRS ("sub"); + + add_attribute (tag, pango2_attr_font_scale_new (PANGO2_FONT_SCALE_SUBSCRIPT)); + add_attribute (tag, pango2_attr_baseline_shift_new (PANGO2_BASELINE_SHIFT_SUBSCRIPT)); + + return TRUE; +} + +static gboolean +sup_parse_func (MarkupData *md G_GNUC_UNUSED, + OpenTag *tag, + const char **names, + const char **values G_GNUC_UNUSED, + GMarkupParseContext *context, + GError **error) +{ + CHECK_NO_ATTRS ("sup"); + + add_attribute (tag, pango2_attr_font_scale_new (PANGO2_FONT_SCALE_SUPERSCRIPT)); + add_attribute (tag, pango2_attr_baseline_shift_new (PANGO2_BASELINE_SHIFT_SUPERSCRIPT)); + + return TRUE; +} + +static gboolean +small_parse_func (MarkupData *md G_GNUC_UNUSED, + OpenTag *tag, + const char **names, + const char **values G_GNUC_UNUSED, + GMarkupParseContext *context, + GError **error) +{ + CHECK_NO_ATTRS ("small"); + + /* Shrink text one level */ + if (tag) + { + tag->scale_level_delta -= 1; + tag->scale_level -= 1; + } + + return TRUE; +} + +static gboolean +tt_parse_func (MarkupData *md G_GNUC_UNUSED, + OpenTag *tag, + const char **names, + const char **values G_GNUC_UNUSED, + GMarkupParseContext *context, + GError **error) +{ + CHECK_NO_ATTRS ("tt"); + + add_attribute (tag, pango2_attr_family_new ("Monospace")); + + return TRUE; +} + +static gboolean +u_parse_func (MarkupData *md G_GNUC_UNUSED, + OpenTag *tag, + const char **names, + const char **values G_GNUC_UNUSED, + GMarkupParseContext *context, + GError **error) +{ + CHECK_NO_ATTRS ("u"); + + add_attribute (tag, pango2_attr_underline_new (PANGO2_LINE_STYLE_SOLID)); + + return TRUE; +} |