PIKApp/app/widgets/pikatextbuffer.c

1861 lines
51 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* PIKA - Photo and Image Kooker Application
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
*
* Original copyright, applying to most contents (license remains unchanged):
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* PikaTextBuffer
* Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <stdlib.h>
#include <gegl.h>
#include <gtk/gtk.h>
#include "libpikabase/pikabase.h"
#include "libpikacolor/pikacolor.h"
#include "widgets-types.h"
#include "pikatextbuffer.h"
#include "pikatextbuffer-serialize.h"
#include "pikatexttag.h"
#include "pika-intl.h"
enum
{
COLOR_APPLIED,
LAST_SIGNAL
};
/* local function prototypes */
static void pika_text_buffer_constructed (GObject *object);
static void pika_text_buffer_dispose (GObject *object);
static void pika_text_buffer_finalize (GObject *object);
static void pika_text_buffer_mark_set (GtkTextBuffer *buffer,
const GtkTextIter *location,
GtkTextMark *mark);
G_DEFINE_TYPE (PikaTextBuffer, pika_text_buffer, GTK_TYPE_TEXT_BUFFER)
#define parent_class pika_text_buffer_parent_class
static guint buffer_signals[LAST_SIGNAL] = { 0, };
static void
pika_text_buffer_class_init (PikaTextBufferClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkTextBufferClass *buffer_class = GTK_TEXT_BUFFER_CLASS (klass);
object_class->constructed = pika_text_buffer_constructed;
object_class->dispose = pika_text_buffer_dispose;
object_class->finalize = pika_text_buffer_finalize;
buffer_class->mark_set = pika_text_buffer_mark_set;
buffer_signals[COLOR_APPLIED] =
g_signal_new ("color-applied",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (PikaTextBufferClass, color_applied),
NULL, NULL, NULL,
G_TYPE_NONE, 1,
PIKA_TYPE_RGB);
}
static void
pika_text_buffer_init (PikaTextBuffer *buffer)
{
buffer->markup_atom =
gtk_text_buffer_register_serialize_format (GTK_TEXT_BUFFER (buffer),
"application/x-pika-pango-markup",
pika_text_buffer_serialize,
NULL, NULL);
gtk_text_buffer_register_deserialize_format (GTK_TEXT_BUFFER (buffer),
"application/x-pika-pango-markup",
pika_text_buffer_deserialize,
NULL, NULL);
}
static void
pika_text_buffer_constructed (GObject *object)
{
PikaTextBuffer *buffer = PIKA_TEXT_BUFFER (object);
G_OBJECT_CLASS (parent_class)->constructed (object);
gtk_text_buffer_set_text (GTK_TEXT_BUFFER (buffer), "", -1);
buffer->bold_tag = gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (buffer),
"bold",
"weight", PANGO_WEIGHT_BOLD,
NULL);
buffer->italic_tag = gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (buffer),
"italic",
"style", PANGO_STYLE_ITALIC,
NULL);
buffer->underline_tag = gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (buffer),
"underline",
"underline", PANGO_UNDERLINE_SINGLE,
NULL);
buffer->preedit_underline_tag = gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (buffer),
"preedit-underline",
"underline", PANGO_UNDERLINE_SINGLE,
NULL);
buffer->strikethrough_tag = gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (buffer),
"strikethrough",
"strikethrough", TRUE,
NULL);
}
static void
pika_text_buffer_dispose (GObject *object)
{
/* PikaTextBuffer *buffer = PIKA_TEXT_BUFFER (object); */
G_OBJECT_CLASS (parent_class)->dispose (object);
}
static void
pika_text_buffer_finalize (GObject *object)
{
PikaTextBuffer *buffer = PIKA_TEXT_BUFFER (object);
if (buffer->size_tags)
{
g_list_free (buffer->size_tags);
buffer->size_tags = NULL;
}
if (buffer->baseline_tags)
{
g_list_free (buffer->baseline_tags);
buffer->baseline_tags = NULL;
}
if (buffer->kerning_tags)
{
g_list_free (buffer->kerning_tags);
buffer->kerning_tags = NULL;
}
if (buffer->font_tags)
{
g_list_free (buffer->font_tags);
buffer->font_tags = NULL;
}
if (buffer->color_tags)
{
g_list_free (buffer->color_tags);
buffer->color_tags = NULL;
}
if (buffer->preedit_color_tags)
{
g_list_free (buffer->preedit_color_tags);
buffer->preedit_color_tags = NULL;
}
if (buffer->preedit_bg_color_tags)
{
g_list_free (buffer->preedit_bg_color_tags);
buffer->preedit_bg_color_tags = NULL;
}
pika_text_buffer_clear_insert_tags (buffer);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
pika_text_buffer_mark_set (GtkTextBuffer *buffer,
const GtkTextIter *location,
GtkTextMark *mark)
{
pika_text_buffer_clear_insert_tags (PIKA_TEXT_BUFFER (buffer));
GTK_TEXT_BUFFER_CLASS (parent_class)->mark_set (buffer, location, mark);
}
/* public functions */
PikaTextBuffer *
pika_text_buffer_new (void)
{
return g_object_new (PIKA_TYPE_TEXT_BUFFER, NULL);
}
void
pika_text_buffer_set_text (PikaTextBuffer *buffer,
const gchar *text)
{
g_return_if_fail (PIKA_IS_TEXT_BUFFER (buffer));
if (text == NULL)
text = "";
gtk_text_buffer_set_text (GTK_TEXT_BUFFER (buffer), text, -1);
pika_text_buffer_clear_insert_tags (buffer);
}
gchar *
pika_text_buffer_get_text (PikaTextBuffer *buffer)
{
GtkTextIter start, end;
g_return_val_if_fail (PIKA_IS_TEXT_BUFFER (buffer), NULL);
gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (buffer), &start, &end);
return gtk_text_buffer_get_text (GTK_TEXT_BUFFER (buffer),
&start, &end, TRUE);
}
void
pika_text_buffer_set_markup (PikaTextBuffer *buffer,
const gchar *markup)
{
g_return_if_fail (PIKA_IS_TEXT_BUFFER (buffer));
pika_text_buffer_set_text (buffer, NULL);
if (markup)
{
GtkTextTagTable *tag_table;
GtkTextBuffer *content;
GtkTextIter insert;
GError *error = NULL;
tag_table = gtk_text_buffer_get_tag_table (GTK_TEXT_BUFFER (buffer));
content = gtk_text_buffer_new (tag_table);
gtk_text_buffer_get_start_iter (content, &insert);
if (! gtk_text_buffer_deserialize (GTK_TEXT_BUFFER (buffer),
content,
buffer->markup_atom,
&insert,
(const guint8 *) markup, -1,
&error))
{
g_printerr ("EEK: %s\n", error->message);
g_clear_error (&error);
}
else
{
GtkTextIter start, end;
pika_text_buffer_post_deserialize (buffer, content);
gtk_text_buffer_get_bounds (content, &start, &end);
gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (buffer), &insert);
gtk_text_buffer_insert_range (GTK_TEXT_BUFFER (buffer),
&insert, &start, &end);
}
g_object_unref (content);
}
pika_text_buffer_clear_insert_tags (buffer);
}
gchar *
pika_text_buffer_get_markup (PikaTextBuffer *buffer)
{
GtkTextTagTable *tag_table;
GtkTextBuffer *content;
GtkTextIter insert;
GtkTextIter start, end;
gchar *markup;
gsize length;
g_return_val_if_fail (PIKA_IS_TEXT_BUFFER (buffer), NULL);
tag_table = gtk_text_buffer_get_tag_table (GTK_TEXT_BUFFER (buffer));
content = gtk_text_buffer_new (tag_table);
gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (buffer), &start, &end);
gtk_text_buffer_get_start_iter (content, &insert);
gtk_text_buffer_insert_range (content, &insert, &start, &end);
pika_text_buffer_pre_serialize (buffer, content);
gtk_text_buffer_get_bounds (content, &start, &end);
markup = (gchar *) gtk_text_buffer_serialize (GTK_TEXT_BUFFER (buffer),
content,
buffer->markup_atom,
&start, &end,
&length);
g_object_unref (content);
return markup;
}
gboolean
pika_text_buffer_has_markup (PikaTextBuffer *buffer)
{
GtkTextIter iter;
g_return_val_if_fail (PIKA_IS_TEXT_BUFFER (buffer), FALSE);
gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (buffer), &iter);
do
{
GSList *tags = gtk_text_iter_get_tags (&iter);
if (tags)
{
g_slist_free (tags);
return TRUE;
}
}
while (gtk_text_iter_forward_char (&iter));
return FALSE;
}
GtkTextTag *
pika_text_buffer_get_iter_size (PikaTextBuffer *buffer,
const GtkTextIter *iter,
gint *size)
{
GList *list;
for (list = buffer->size_tags; list; list = g_list_next (list))
{
GtkTextTag *tag = list->data;
if (gtk_text_iter_has_tag (iter, tag))
{
if (size)
*size = pika_text_tag_get_size (tag);
return tag;
}
}
if (size)
*size = 0;
return NULL;
}
GtkTextTag *
pika_text_buffer_get_size_tag (PikaTextBuffer *buffer,
gint size)
{
GList *list;
GtkTextTag *tag;
gchar name[32];
for (list = buffer->size_tags; list; list = g_list_next (list))
{
tag = list->data;
if (size == pika_text_tag_get_size (tag))
return tag;
}
g_snprintf (name, sizeof (name), "size-%d", size);
tag = gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (buffer),
name,
PIKA_TEXT_PROP_NAME_SIZE, size,
NULL);
buffer->size_tags = g_list_prepend (buffer->size_tags, tag);
return tag;
}
void
pika_text_buffer_set_size (PikaTextBuffer *buffer,
const GtkTextIter *start,
const GtkTextIter *end,
gint size)
{
GList *list;
g_return_if_fail (PIKA_IS_TEXT_BUFFER (buffer));
g_return_if_fail (start != NULL);
g_return_if_fail (end != NULL);
if (gtk_text_iter_equal (start, end))
return;
gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER (buffer));
for (list = buffer->size_tags; list; list = g_list_next (list))
{
gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (buffer), list->data,
start, end);
}
if (size != 0)
{
GtkTextTag *tag;
tag = pika_text_buffer_get_size_tag (buffer, size);
gtk_text_buffer_apply_tag (GTK_TEXT_BUFFER (buffer), tag,
start, end);
}
gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (buffer));
}
void
pika_text_buffer_change_size (PikaTextBuffer *buffer,
const GtkTextIter *start,
const GtkTextIter *end,
gint count)
{
GtkTextIter iter;
GtkTextIter span_start;
GtkTextIter span_end;
GtkTextTag *span_tag;
gint span_size;
g_return_if_fail (PIKA_IS_TEXT_BUFFER (buffer));
g_return_if_fail (start != NULL);
g_return_if_fail (end != NULL);
if (gtk_text_iter_equal (start, end))
return;
iter = *start;
span_start = *start;
span_tag = pika_text_buffer_get_iter_size (buffer, &iter,
&span_size);
gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER (buffer));
do
{
GtkTextTag *iter_tag;
gint iter_size;
gtk_text_iter_forward_char (&iter);
iter_tag = pika_text_buffer_get_iter_size (buffer, &iter,
&iter_size);
span_end = iter;
if (iter_size != span_size ||
gtk_text_iter_compare (&iter, end) >= 0)
{
if (span_size != 0)
{
gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (buffer), span_tag,
&span_start, &span_end);
}
if ((span_size + count) > 0)
{
span_tag = pika_text_buffer_get_size_tag (buffer,
span_size + count);
gtk_text_buffer_apply_tag (GTK_TEXT_BUFFER (buffer), span_tag,
&span_start, &span_end);
}
span_start = iter;
span_size = iter_size;
span_tag = iter_tag;
}
/* We might have moved too far */
if (gtk_text_iter_compare (&iter, end) > 0)
iter = *end;
}
while (! gtk_text_iter_equal (&iter, end));
gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (buffer));
}
GtkTextTag *
pika_text_buffer_get_iter_baseline (PikaTextBuffer *buffer,
const GtkTextIter *iter,
gint *baseline)
{
GList *list;
for (list = buffer->baseline_tags; list; list = g_list_next (list))
{
GtkTextTag *tag = list->data;
if (gtk_text_iter_has_tag (iter, tag))
{
if (baseline)
*baseline = pika_text_tag_get_baseline (tag);
return tag;
}
}
if (baseline)
*baseline = 0;
return NULL;
}
static GtkTextTag *
pika_text_buffer_get_baseline_tag (PikaTextBuffer *buffer,
gint baseline)
{
GList *list;
GtkTextTag *tag;
gchar name[32];
for (list = buffer->baseline_tags; list; list = g_list_next (list))
{
tag = list->data;
if (baseline == pika_text_tag_get_baseline (tag))
return tag;
}
g_snprintf (name, sizeof (name), "baseline-%d", baseline);
tag = gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (buffer),
name,
PIKA_TEXT_PROP_NAME_BASELINE, baseline,
NULL);
buffer->baseline_tags = g_list_prepend (buffer->baseline_tags, tag);
return tag;
}
void
pika_text_buffer_set_baseline (PikaTextBuffer *buffer,
const GtkTextIter *start,
const GtkTextIter *end,
gint baseline)
{
GList *list;
g_return_if_fail (PIKA_IS_TEXT_BUFFER (buffer));
g_return_if_fail (start != NULL);
g_return_if_fail (end != NULL);
if (gtk_text_iter_equal (start, end))
return;
gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER (buffer));
for (list = buffer->baseline_tags; list; list = g_list_next (list))
{
gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (buffer), list->data,
start, end);
}
if (baseline != 0)
{
GtkTextTag *tag;
tag = pika_text_buffer_get_baseline_tag (buffer, baseline);
gtk_text_buffer_apply_tag (GTK_TEXT_BUFFER (buffer), tag,
start, end);
}
gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (buffer));
}
void
pika_text_buffer_change_baseline (PikaTextBuffer *buffer,
const GtkTextIter *start,
const GtkTextIter *end,
gint count)
{
GtkTextIter iter;
GtkTextIter span_start;
GtkTextIter span_end;
GtkTextTag *span_tag;
gint span_baseline;
g_return_if_fail (PIKA_IS_TEXT_BUFFER (buffer));
g_return_if_fail (start != NULL);
g_return_if_fail (end != NULL);
if (gtk_text_iter_equal (start, end))
return;
iter = *start;
span_start = *start;
span_tag = pika_text_buffer_get_iter_baseline (buffer, &iter,
&span_baseline);
gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER (buffer));
do
{
GtkTextTag *iter_tag;
gint iter_baseline;
gtk_text_iter_forward_char (&iter);
iter_tag = pika_text_buffer_get_iter_baseline (buffer, &iter,
&iter_baseline);
span_end = iter;
if (iter_baseline != span_baseline ||
gtk_text_iter_compare (&iter, end) >= 0)
{
if (span_baseline != 0)
{
gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (buffer), span_tag,
&span_start, &span_end);
}
if (span_baseline + count != 0)
{
span_tag = pika_text_buffer_get_baseline_tag (buffer,
span_baseline + count);
gtk_text_buffer_apply_tag (GTK_TEXT_BUFFER (buffer), span_tag,
&span_start, &span_end);
}
span_start = iter;
span_baseline = iter_baseline;
span_tag = iter_tag;
}
/* We might have moved too far */
if (gtk_text_iter_compare (&iter, end) > 0)
iter = *end;
}
while (! gtk_text_iter_equal (&iter, end));
gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (buffer));
}
GtkTextTag *
pika_text_buffer_get_iter_kerning (PikaTextBuffer *buffer,
const GtkTextIter *iter,
gint *kerning)
{
GList *list;
for (list = buffer->kerning_tags; list; list = g_list_next (list))
{
GtkTextTag *tag = list->data;
if (gtk_text_iter_has_tag (iter, tag))
{
if (kerning)
*kerning = pika_text_tag_get_kerning (tag);
return tag;
}
}
if (kerning)
*kerning = 0;
return NULL;
}
static GtkTextTag *
pika_text_buffer_get_kerning_tag (PikaTextBuffer *buffer,
gint kerning)
{
GList *list;
GtkTextTag *tag;
gchar name[32];
for (list = buffer->kerning_tags; list; list = g_list_next (list))
{
tag = list->data;
if (kerning == pika_text_tag_get_kerning (tag))
return tag;
}
g_snprintf (name, sizeof (name), "kerning-%d", kerning);
tag = gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (buffer),
name,
PIKA_TEXT_PROP_NAME_KERNING, kerning,
NULL);
buffer->kerning_tags = g_list_prepend (buffer->kerning_tags, tag);
return tag;
}
void
pika_text_buffer_set_kerning (PikaTextBuffer *buffer,
const GtkTextIter *start,
const GtkTextIter *end,
gint kerning)
{
GList *list;
g_return_if_fail (PIKA_IS_TEXT_BUFFER (buffer));
g_return_if_fail (start != NULL);
g_return_if_fail (end != NULL);
if (gtk_text_iter_equal (start, end))
return;
gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER (buffer));
for (list = buffer->kerning_tags; list; list = g_list_next (list))
{
gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (buffer), list->data,
start, end);
}
if (kerning != 0)
{
GtkTextTag *tag;
tag = pika_text_buffer_get_kerning_tag (buffer, kerning);
gtk_text_buffer_apply_tag (GTK_TEXT_BUFFER (buffer), tag,
start, end);
}
gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (buffer));
}
void
pika_text_buffer_change_kerning (PikaTextBuffer *buffer,
const GtkTextIter *start,
const GtkTextIter *end,
gint count)
{
GtkTextIter iter;
GtkTextIter span_start;
GtkTextIter span_end;
GtkTextTag *span_tag;
gint span_kerning;
g_return_if_fail (PIKA_IS_TEXT_BUFFER (buffer));
g_return_if_fail (start != NULL);
g_return_if_fail (end != NULL);
if (gtk_text_iter_equal (start, end))
return;
iter = *start;
span_start = *start;
span_tag = pika_text_buffer_get_iter_kerning (buffer, &iter,
&span_kerning);
gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER (buffer));
do
{
GtkTextTag *iter_tag;
gint iter_kerning;
gtk_text_iter_forward_char (&iter);
iter_tag = pika_text_buffer_get_iter_kerning (buffer, &iter,
&iter_kerning);
span_end = iter;
if (iter_kerning != span_kerning ||
gtk_text_iter_compare (&iter, end) >= 0)
{
if (span_kerning != 0)
{
gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (buffer), span_tag,
&span_start, &span_end);
}
if (span_kerning + count != 0)
{
span_tag = pika_text_buffer_get_kerning_tag (buffer,
span_kerning + count);
gtk_text_buffer_apply_tag (GTK_TEXT_BUFFER (buffer), span_tag,
&span_start, &span_end);
}
span_start = iter;
span_kerning = iter_kerning;
span_tag = iter_tag;
}
/* We might have moved too far */
if (gtk_text_iter_compare (&iter, end) > 0)
iter = *end;
}
while (! gtk_text_iter_equal (&iter, end));
gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (buffer));
}
GtkTextTag *
pika_text_buffer_get_iter_font (PikaTextBuffer *buffer,
const GtkTextIter *iter,
gchar **font)
{
GList *list;
for (list = buffer->font_tags; list; list = g_list_next (list))
{
GtkTextTag *tag = list->data;
if (gtk_text_iter_has_tag (iter, tag))
{
if (font)
*font = pika_text_tag_get_font (tag);
return tag;
}
}
if (font)
*font = NULL;
return NULL;
}
GtkTextTag *
pika_text_buffer_get_font_tag (PikaTextBuffer *buffer,
const gchar *font)
{
GList *list;
GtkTextTag *tag;
gchar name[256];
PangoFontDescription *pfd = pango_font_description_from_string (font);
char *description = pango_font_description_to_string (pfd);
pango_font_description_free (pfd);
for (list = buffer->font_tags; list; list = g_list_next (list))
{
gchar *tag_font;
tag = list->data;
tag_font = pika_text_tag_get_font (tag);
if (! strcmp (description, tag_font))
{
g_free (tag_font);
g_free (description);
return tag;
}
g_free (tag_font);
}
g_snprintf (name, sizeof (name), "font-%s", description);
tag = gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (buffer),
name,
"font", description,
NULL);
gtk_text_tag_set_priority (tag, 0);
g_free (description);
buffer->font_tags = g_list_prepend (buffer->font_tags, tag);
return tag;
}
void
pika_text_buffer_set_font (PikaTextBuffer *buffer,
const GtkTextIter *start,
const GtkTextIter *end,
const gchar *font)
{
GList *list;
g_return_if_fail (PIKA_IS_TEXT_BUFFER (buffer));
g_return_if_fail (start != NULL);
g_return_if_fail (end != NULL);
if (gtk_text_iter_equal (start, end))
return;
gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER (buffer));
for (list = buffer->font_tags; list; list = g_list_next (list))
{
gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (buffer), list->data,
start, end);
}
if (font)
{
GtkTextTag *tag = pika_text_buffer_get_font_tag (buffer, font);
gtk_text_buffer_apply_tag (GTK_TEXT_BUFFER (buffer), tag,
start, end);
}
gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (buffer));
}
GtkTextTag *
pika_text_buffer_get_iter_color (PikaTextBuffer *buffer,
const GtkTextIter *iter,
PikaRGB *color)
{
GList *list;
for (list = buffer->color_tags; list; list = g_list_next (list))
{
GtkTextTag *tag = list->data;
if (gtk_text_iter_has_tag (iter, tag))
{
if (color)
pika_text_tag_get_fg_color (tag, color);
return tag;
}
}
return NULL;
}
GtkTextTag *
pika_text_buffer_get_color_tag (PikaTextBuffer *buffer,
const PikaRGB *color)
{
GList *list;
GtkTextTag *tag;
gchar name[256];
guchar r, g, b;
pika_rgb_get_uchar (color, &r, &g, &b);
for (list = buffer->color_tags; list; list = g_list_next (list))
{
PikaRGB tag_color;
guchar tag_r, tag_g, tag_b;
tag = list->data;
pika_text_tag_get_fg_color (tag, &tag_color);
pika_rgb_get_uchar (&tag_color, &tag_r, &tag_g, &tag_b);
/* Do not compare the alpha channel, since it's unused */
if (tag_r == r &&
tag_g == g &&
tag_b == b)
{
return tag;
}
}
g_snprintf (name, sizeof (name), "color-#%02x%02x%02x",
r, g, b);
tag = gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (buffer),
name,
"foreground-rgba", (GdkRGBA *) color,
"foreground-set", TRUE,
NULL);
buffer->color_tags = g_list_prepend (buffer->color_tags, tag);
return tag;
}
void
pika_text_buffer_set_color (PikaTextBuffer *buffer,
const GtkTextIter *start,
const GtkTextIter *end,
const PikaRGB *color)
{
GList *list;
g_return_if_fail (PIKA_IS_TEXT_BUFFER (buffer));
g_return_if_fail (start != NULL);
g_return_if_fail (end != NULL);
if (gtk_text_iter_equal (start, end))
return;
gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER (buffer));
for (list = buffer->color_tags; list; list = g_list_next (list))
{
gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (buffer), list->data,
start, end);
}
if (color)
{
GtkTextTag *tag = pika_text_buffer_get_color_tag (buffer, color);
gtk_text_buffer_apply_tag (GTK_TEXT_BUFFER (buffer), tag,
start, end);
g_signal_emit (buffer, buffer_signals[COLOR_APPLIED], 0, color);
}
gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (buffer));
}
GtkTextTag *
pika_text_buffer_get_preedit_color_tag (PikaTextBuffer *buffer,
const PikaRGB *color)
{
GList *list;
GtkTextTag *tag;
gchar name[256];
guchar r, g, b;
pika_rgb_get_uchar (color, &r, &g, &b);
for (list = buffer->preedit_color_tags; list; list = g_list_next (list))
{
PikaRGB tag_color;
guchar tag_r, tag_g, tag_b;
tag = list->data;
pika_text_tag_get_fg_color (tag, &tag_color);
pika_rgb_get_uchar (&tag_color, &tag_r, &tag_g, &tag_b);
/* Do not compare the alpha channel, since it's unused */
if (tag_r == r &&
tag_g == g &&
tag_b == b)
{
return tag;
}
}
g_snprintf (name, sizeof (name), "preedit-color-#%02x%02x%02x",
r, g, b);
tag = gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (buffer),
name,
"foreground-rgba", (GdkRGBA *) color,
"foreground-set", TRUE,
NULL);
buffer->preedit_color_tags = g_list_prepend (buffer->preedit_color_tags, tag);
return tag;
}
void
pika_text_buffer_set_preedit_color (PikaTextBuffer *buffer,
const GtkTextIter *start,
const GtkTextIter *end,
const PikaRGB *color)
{
GList *list;
g_return_if_fail (PIKA_IS_TEXT_BUFFER (buffer));
g_return_if_fail (start != NULL);
g_return_if_fail (end != NULL);
if (gtk_text_iter_equal (start, end))
return;
gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER (buffer));
for (list = buffer->preedit_color_tags; list; list = g_list_next (list))
{
gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (buffer), list->data,
start, end);
}
if (color)
{
GtkTextTag *tag = pika_text_buffer_get_preedit_color_tag (buffer, color);
gtk_text_buffer_apply_tag (GTK_TEXT_BUFFER (buffer), tag,
start, end);
}
gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (buffer));
}
GtkTextTag *
pika_text_buffer_get_preedit_bg_color_tag (PikaTextBuffer *buffer,
const PikaRGB *color)
{
GList *list;
GtkTextTag *tag;
gchar name[256];
guchar r, g, b;
pika_rgb_get_uchar (color, &r, &g, &b);
for (list = buffer->preedit_bg_color_tags; list; list = g_list_next (list))
{
PikaRGB tag_color;
guchar tag_r, tag_g, tag_b;
tag = list->data;
pika_text_tag_get_bg_color (tag, &tag_color);
pika_rgb_get_uchar (&tag_color, &tag_r, &tag_g, &tag_b);
/* Do not compare the alpha channel, since it's unused */
if (tag_r == r &&
tag_g == g &&
tag_b == b)
{
return tag;
}
}
g_snprintf (name, sizeof (name), "bg-color-#%02x%02x%02x",
r, g, b);
tag = gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (buffer),
name,
"background-rgba", (GdkRGBA *) color,
"background-set", TRUE,
NULL);
buffer->preedit_bg_color_tags = g_list_prepend (buffer->preedit_bg_color_tags, tag);
return tag;
}
void
pika_text_buffer_set_preedit_bg_color (PikaTextBuffer *buffer,
const GtkTextIter *start,
const GtkTextIter *end,
const PikaRGB *color)
{
GList *list;
g_return_if_fail (PIKA_IS_TEXT_BUFFER (buffer));
g_return_if_fail (start != NULL);
g_return_if_fail (end != NULL);
if (gtk_text_iter_equal (start, end))
return;
gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER (buffer));
for (list = buffer->preedit_bg_color_tags; list; list = g_list_next (list))
{
gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (buffer), list->data,
start, end);
}
if (color)
{
GtkTextTag *tag = pika_text_buffer_get_preedit_bg_color_tag (buffer, color);
gtk_text_buffer_apply_tag (GTK_TEXT_BUFFER (buffer), tag,
start, end);
}
gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (buffer));
}
/* Pango markup attribute names */
#define PIKA_TEXT_ATTR_NAME_SIZE "size"
#define PIKA_TEXT_ATTR_NAME_BASELINE "rise"
#define PIKA_TEXT_ATTR_NAME_KERNING "letter_spacing"
#define PIKA_TEXT_ATTR_NAME_FONT "font"
#define PIKA_TEXT_ATTR_NAME_STYLE "style"
#define PIKA_TEXT_ATTR_NAME_COLOR "foreground"
#define PIKA_TEXT_ATTR_NAME_FG_COLOR "fgcolor"
#define PIKA_TEXT_ATTR_NAME_BG_COLOR "background"
#define PIKA_TEXT_ATTR_NAME_UNDERLINE "underline"
const gchar *
pika_text_buffer_tag_to_name (PikaTextBuffer *buffer,
GtkTextTag *tag,
const gchar **attribute,
gchar **value)
{
g_return_val_if_fail (PIKA_IS_TEXT_BUFFER (buffer), NULL);
g_return_val_if_fail (GTK_IS_TEXT_TAG (tag), NULL);
if (attribute)
*attribute = NULL;
if (value)
*value = NULL;
if (tag == buffer->bold_tag)
{
return "b";
}
else if (tag == buffer->italic_tag)
{
return "i";
}
else if (tag == buffer->underline_tag)
{
return "u";
}
else if (tag == buffer->strikethrough_tag)
{
return "s";
}
else if (g_list_find (buffer->size_tags, tag))
{
if (attribute)
*attribute = PIKA_TEXT_ATTR_NAME_SIZE;
if (value)
*value = g_strdup_printf ("%d", pika_text_tag_get_size (tag));
return "span";
}
else if (g_list_find (buffer->baseline_tags, tag))
{
if (attribute)
*attribute = PIKA_TEXT_ATTR_NAME_BASELINE;
if (value)
*value = g_strdup_printf ("%d", pika_text_tag_get_baseline (tag));
return "span";
}
else if (g_list_find (buffer->kerning_tags, tag))
{
if (attribute)
*attribute = PIKA_TEXT_ATTR_NAME_KERNING;
if (value)
*value = g_strdup_printf ("%d", pika_text_tag_get_kerning (tag));
return "span";
}
else if (g_list_find (buffer->font_tags, tag))
{
if (attribute)
*attribute = PIKA_TEXT_ATTR_NAME_FONT;
if (value)
*value = pika_text_tag_get_font (tag);
return "span";
}
else if (g_list_find (buffer->color_tags, tag))
{
if (attribute)
*attribute = PIKA_TEXT_ATTR_NAME_COLOR;
if (value)
{
PikaRGB color;
guchar r, g, b;
pika_text_tag_get_fg_color (tag, &color);
pika_rgb_get_uchar (&color, &r, &g, &b);
*value = g_strdup_printf ("#%02x%02x%02x", r, g, b);
}
return "span";
}
else if (g_list_find (buffer->preedit_color_tags, tag))
{
/* "foreground" and "fgcolor" attributes are similar, but I use
* one or the other as a trick to differentiate the color chosen
* from the user and a display color for preedit. */
if (attribute)
*attribute = PIKA_TEXT_ATTR_NAME_FG_COLOR;
if (value)
{
PikaRGB color;
guchar r, g, b;
pika_text_tag_get_fg_color (tag, &color);
pika_rgb_get_uchar (&color, &r, &g, &b);
*value = g_strdup_printf ("#%02x%02x%02x", r, g, b);
}
return "span";
}
else if (g_list_find (buffer->preedit_bg_color_tags, tag))
{
if (attribute)
*attribute = PIKA_TEXT_ATTR_NAME_BG_COLOR;
if (value)
{
PikaRGB color;
guchar r, g, b;
pika_text_tag_get_bg_color (tag, &color);
pika_rgb_get_uchar (&color, &r, &g, &b);
*value = g_strdup_printf ("#%02x%02x%02x", r, g, b);
}
return "span";
}
else if (tag == buffer->preedit_underline_tag)
{
if (attribute)
*attribute = PIKA_TEXT_ATTR_NAME_UNDERLINE;
if (value)
*value = g_strdup ("single");
return "span";
}
return NULL;
}
GtkTextTag *
pika_text_buffer_name_to_tag (PikaTextBuffer *buffer,
const gchar *name,
const gchar *attribute,
const gchar *value)
{
g_return_val_if_fail (PIKA_IS_TEXT_BUFFER (buffer), NULL);
g_return_val_if_fail (name != NULL, NULL);
if (! strcmp (name, "b"))
{
return buffer->bold_tag;
}
else if (! strcmp (name, "i"))
{
return buffer->italic_tag;
}
else if (! strcmp (name, "u"))
{
return buffer->underline_tag;
}
else if (! strcmp (name, "s"))
{
return buffer->strikethrough_tag;
}
else if (! strcmp (name, "span") &&
attribute != NULL &&
value != NULL)
{
if (! strcmp (attribute, PIKA_TEXT_ATTR_NAME_SIZE))
{
return pika_text_buffer_get_size_tag (buffer, atoi (value));
}
else if (! strcmp (attribute, PIKA_TEXT_ATTR_NAME_BASELINE))
{
return pika_text_buffer_get_baseline_tag (buffer, atoi (value));
}
else if (! strcmp (attribute, PIKA_TEXT_ATTR_NAME_KERNING))
{
return pika_text_buffer_get_kerning_tag (buffer, atoi (value));
}
else if (! strcmp (attribute, PIKA_TEXT_ATTR_NAME_FONT))
{
return pika_text_buffer_get_font_tag (buffer, value);
}
else if (! strcmp (attribute, PIKA_TEXT_ATTR_NAME_COLOR))
{
PikaRGB color;
guint r, g, b;
sscanf (value, "#%02x%02x%02x", &r, &g, &b);
pika_rgb_set_alpha (&color, 1.0);
pika_rgb_set_uchar (&color, r, g, b);
return pika_text_buffer_get_color_tag (buffer, &color);
}
}
return NULL;
}
void
pika_text_buffer_set_insert_tags (PikaTextBuffer *buffer,
GList *insert_tags,
GList *remove_tags)
{
g_return_if_fail (PIKA_IS_TEXT_BUFFER (buffer));
buffer->insert_tags_set = TRUE;
g_list_free (buffer->insert_tags);
g_list_free (buffer->remove_tags);
buffer->insert_tags = insert_tags;
buffer->remove_tags = remove_tags;
}
void
pika_text_buffer_clear_insert_tags (PikaTextBuffer *buffer)
{
g_return_if_fail (PIKA_IS_TEXT_BUFFER (buffer));
buffer->insert_tags_set = FALSE;
g_list_free (buffer->insert_tags);
g_list_free (buffer->remove_tags);
buffer->insert_tags = NULL;
buffer->remove_tags = NULL;
}
void
pika_text_buffer_insert (PikaTextBuffer *buffer,
const gchar *text)
{
GtkTextIter iter, start;
gint start_offset;
gboolean insert_tags_set;
GList *insert_tags;
GList *remove_tags;
GSList *tags_off = NULL;
PikaRGB color;
g_return_if_fail (PIKA_IS_TEXT_BUFFER (buffer));
gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (buffer), &iter,
gtk_text_buffer_get_insert (GTK_TEXT_BUFFER (buffer)));
start_offset = gtk_text_iter_get_offset (&iter);
insert_tags_set = buffer->insert_tags_set;
insert_tags = buffer->insert_tags;
remove_tags = buffer->remove_tags;
buffer->insert_tags_set = FALSE;
buffer->insert_tags = NULL;
buffer->remove_tags = NULL;
tags_off = gtk_text_iter_get_toggled_tags (&iter, FALSE);
gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER (buffer));
gtk_text_buffer_insert (GTK_TEXT_BUFFER (buffer), &iter, text, -1);
gtk_text_buffer_get_iter_at_offset (GTK_TEXT_BUFFER (buffer), &start,
start_offset);
if (insert_tags_set)
{
GList *list;
for (list = remove_tags; list; list = g_list_next (list))
{
GtkTextTag *tag = list->data;
gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (buffer), tag,
&start, &iter);
}
for (list = insert_tags; list; list = g_list_next (list))
{
GtkTextTag *tag = list->data;
gtk_text_buffer_apply_tag (GTK_TEXT_BUFFER (buffer), tag,
&start, &iter);
}
}
if (tags_off)
{
GSList *slist;
for (slist = tags_off; slist; slist = g_slist_next (slist))
{
GtkTextTag *tag = slist->data;
if (! g_list_find (remove_tags, tag) &&
! g_list_find (buffer->kerning_tags, tag))
{
gtk_text_buffer_apply_tag (GTK_TEXT_BUFFER (buffer), tag,
&start, &iter);
}
}
g_slist_free (tags_off);
}
g_list_free (remove_tags);
g_list_free (insert_tags);
if (pika_text_buffer_get_iter_color (buffer, &start, &color))
{
g_signal_emit (buffer, buffer_signals[COLOR_APPLIED], 0, &color);
}
gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (buffer));
}
gint
pika_text_buffer_get_iter_index (PikaTextBuffer *buffer,
GtkTextIter *iter,
gboolean layout_index)
{
GtkTextIter start;
gchar *string;
gint index;
g_return_val_if_fail (PIKA_IS_TEXT_BUFFER (buffer), 0);
gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (buffer), &start);
string = gtk_text_buffer_get_text (GTK_TEXT_BUFFER (buffer),
&start, iter, TRUE);
index = strlen (string);
g_free (string);
if (layout_index)
{
do
{
GSList *tags = gtk_text_iter_get_tags (&start);
GSList *list;
for (list = tags; list; list = g_slist_next (list))
{
GtkTextTag *tag = list->data;
if (g_list_find (buffer->kerning_tags, tag))
{
index += WORD_JOINER_LENGTH;
break;
}
}
g_slist_free (tags);
gtk_text_iter_forward_char (&start);
/* We might have moved too far */
if (gtk_text_iter_compare (&start, iter) > 0)
start = *iter;
}
while (! gtk_text_iter_equal (&start, iter));
}
return index;
}
void
pika_text_buffer_get_iter_at_index (PikaTextBuffer *buffer,
GtkTextIter *iter,
gint index,
gboolean layout_index)
{
GtkTextIter start;
GtkTextIter end;
gchar *string;
g_return_if_fail (PIKA_IS_TEXT_BUFFER (buffer));
gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (buffer), &start, &end);
string = gtk_text_buffer_get_text (GTK_TEXT_BUFFER (buffer),
&start, &end, TRUE);
if (layout_index)
{
gchar *my_string = string;
gint my_index = 0;
gchar *tmp;
do
{
GSList *tags = gtk_text_iter_get_tags (&start);
GSList *list;
tmp = g_utf8_next_char (my_string);
my_index += (tmp - my_string);
my_string = tmp;
for (list = tags; list; list = g_slist_next (list))
{
GtkTextTag *tag = list->data;
if (g_list_find (buffer->kerning_tags, tag))
{
index = MAX (0, index - WORD_JOINER_LENGTH);
break;
}
}
g_slist_free (tags);
gtk_text_iter_forward_char (&start);
/* We might have moved too far */
if (gtk_text_iter_compare (&start, &end) > 0)
start = end;
}
while (my_index < index &&
! gtk_text_iter_equal (&start, &end));
}
string[index] = '\0';
gtk_text_buffer_get_iter_at_offset (GTK_TEXT_BUFFER (buffer), iter,
g_utf8_strlen (string, -1));
g_free (string);
}
gboolean
pika_text_buffer_load (PikaTextBuffer *buffer,
GFile *file,
GError **error)
{
GInputStream *input;
gchar buf[2048];
gint to_read;
gsize bytes_read;
gsize total_read = 0;
gint remaining = 0;
GtkTextIter iter;
GError *my_error = NULL;
g_return_val_if_fail (PIKA_IS_TEXT_BUFFER (buffer), FALSE);
g_return_val_if_fail (G_IS_FILE (file), FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
input = G_INPUT_STREAM (g_file_read (file, NULL, &my_error));
if (! input)
{
g_set_error (error, my_error->domain, my_error->code,
_("Could not open '%s' for reading: %s"),
pika_file_get_utf8_name (file), my_error->message);
g_clear_error (&my_error);
return FALSE;
}
gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER (buffer));
pika_text_buffer_set_text (buffer, NULL);
gtk_text_buffer_get_end_iter (GTK_TEXT_BUFFER (buffer), &iter);
do
{
gboolean success;
const char *leftover;
to_read = sizeof (buf) - remaining - 1;
success = g_input_stream_read_all (input, buf + remaining, to_read,
&bytes_read, NULL, &my_error);
total_read += bytes_read;
buf[bytes_read + remaining] = '\0';
g_utf8_validate (buf, bytes_read + remaining, &leftover);
gtk_text_buffer_insert (GTK_TEXT_BUFFER (buffer), &iter,
buf, leftover - buf);
gtk_text_buffer_get_end_iter (GTK_TEXT_BUFFER (buffer), &iter);
remaining = (buf + remaining + bytes_read) - leftover;
memmove (buf, leftover, remaining);
if (! success)
{
if (total_read > 0)
{
g_message (_("Input file '%s' appears truncated: %s"),
pika_file_get_utf8_name (file),
my_error->message);
g_clear_error (&my_error);
break;
}
gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (buffer));
g_object_unref (input);
g_propagate_error (error, my_error);
return FALSE;
}
}
while (remaining <= 6 && bytes_read == to_read);
if (remaining)
g_message (_("Invalid UTF-8 data in file '%s'."),
pika_file_get_utf8_name (file));
gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (buffer));
g_object_unref (input);
return TRUE;
}
gboolean
pika_text_buffer_save (PikaTextBuffer *buffer,
GFile *file,
gboolean selection_only,
GError **error)
{
GOutputStream *output;
GtkTextIter start_iter;
GtkTextIter end_iter;
gchar *text_contents;
GError *my_error = NULL;
g_return_val_if_fail (PIKA_IS_TEXT_BUFFER (buffer), FALSE);
g_return_val_if_fail (G_IS_FILE (file), FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
output = G_OUTPUT_STREAM (g_file_replace (file,
NULL, FALSE, G_FILE_CREATE_NONE,
NULL, error));
if (! output)
return FALSE;
if (selection_only)
gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (buffer),
&start_iter, &end_iter);
else
gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (buffer),
&start_iter, &end_iter);
text_contents = gtk_text_buffer_get_text (GTK_TEXT_BUFFER (buffer),
&start_iter, &end_iter, TRUE);
if (text_contents)
{
gint text_length = strlen (text_contents);
if (! g_output_stream_write_all (output, text_contents, text_length,
NULL, NULL, &my_error))
{
GCancellable *cancellable = g_cancellable_new ();
g_set_error (error, my_error->domain, my_error->code,
_("Writing text file '%s' failed: %s"),
pika_file_get_utf8_name (file), my_error->message);
g_clear_error (&my_error);
g_free (text_contents);
/* Cancel the overwrite initiated by g_file_replace(). */
g_cancellable_cancel (cancellable);
g_output_stream_close (output, cancellable, NULL);
g_object_unref (cancellable);
g_object_unref (output);
return FALSE;
}
g_free (text_contents);
}
g_object_unref (output);
return TRUE;
}
/**
* pika_text_buffer_get_tags_on_iter:
* @buffer: a #PikaTextBuffer
* @iter: a position in @buffer
*
* Returns a list of tags that apply to @iter, in ascending order
* of priority (highest-priority tags are last). The GtkTextTag in
* the list dont have a reference added, but you have to free the
* list itself.
*
* Returns: (element-type GtkTextTag) (transfer container): GList of #GtkTextTag
*/
GList *
pika_text_buffer_get_tags_on_iter (PikaTextBuffer *buffer,
const GtkTextIter *iter)
{
GList *result = NULL;
GSList *tag = NULL;
GSList *tags_list = NULL;
g_return_val_if_fail (PIKA_IS_TEXT_BUFFER (buffer), NULL);
g_return_val_if_fail (iter != NULL, NULL);
g_return_val_if_fail (gtk_text_iter_get_buffer (iter) == GTK_TEXT_BUFFER (buffer), NULL);
tags_list = gtk_text_iter_get_tags (iter);
for (tag = tags_list; tag != NULL; tag = g_slist_next (tag))
{
result = g_list_prepend (result, tag->data);
}
g_slist_free (tags_list);
result = g_list_reverse (result);
return result;
}
/**
* pika_text_buffer_get_all_tags:
* @buffer: a #PikaTextBuffer
*
* Returns a list of all tags for a @buffer, The GtkTextTag
* in the list dont have a reference added, but you have to
* free the list itself.
*
* Returns: (element-type GtkTextTag) (transfer container): GList of #GtkTextTag
*/
GList *
pika_text_buffer_get_all_tags (PikaTextBuffer *buffer)
{
GList *result = NULL;
g_return_val_if_fail (PIKA_IS_TEXT_BUFFER (buffer), NULL);
result = g_list_prepend (result, buffer->bold_tag);
result = g_list_prepend (result, buffer->italic_tag);
result = g_list_prepend (result, buffer->underline_tag);
result = g_list_prepend (result, buffer->strikethrough_tag);
result = g_list_concat (result, g_list_copy (buffer->size_tags));
result = g_list_concat (result, g_list_copy (buffer->baseline_tags));
result = g_list_concat (result, g_list_copy (buffer->kerning_tags));
result = g_list_concat (result, g_list_copy (buffer->font_tags));
result = g_list_concat (result, g_list_copy (buffer->color_tags));
return result;
}