/* 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
 *
 * PikaTextLayer
 * Copyright (C) 2002-2004  Sven Neumann <sven@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 <string.h>

#include <cairo.h>
#include <gegl.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <pango/pangocairo.h>

#include "libpikabase/pikabase.h"
#include "libpikacolor/pikacolor.h"
#include "libpikaconfig/pikaconfig.h"

#include "text-types.h"

#include "gegl/pika-babl.h"
#include "gegl/pika-gegl-loops.h"
#include "gegl/pika-gegl-utils.h"

#include "core/pika.h"
#include "core/pika-utils.h"
#include "core/pikacontainer.h"
#include "core/pikacontext.h"
#include "core/pikadatafactory.h"
#include "core/pikaimage.h"
#include "core/pikaimage-color-profile.h"
#include "core/pikaimage-undo.h"
#include "core/pikaimage-undo-push.h"
#include "core/pikaitemtree.h"
#include "core/pikaparasitelist.h"
#include "core/pikapattern.h"
#include "core/pikatempbuf.h"

#include "pikatext.h"
#include "pikatextlayer.h"
#include "pikatextlayer-transform.h"
#include "pikatextlayout.h"
#include "pikatextlayout-render.h"

#include "pika-intl.h"


enum
{
  PROP_0,
  PROP_TEXT,
  PROP_AUTO_RENAME,
  PROP_MODIFIED
};

struct _PikaTextLayerPrivate
{
  PikaTextDirection base_dir;
};

static void       pika_text_layer_finalize       (GObject           *object);
static void       pika_text_layer_get_property   (GObject           *object,
                                                  guint              property_id,
                                                  GValue            *value,
                                                  GParamSpec        *pspec);
static void       pika_text_layer_set_property   (GObject           *object,
                                                  guint              property_id,
                                                  const GValue      *value,
                                                  GParamSpec        *pspec);

static gint64     pika_text_layer_get_memsize    (PikaObject        *object,
                                                  gint64            *gui_size);

static PikaItem * pika_text_layer_duplicate      (PikaItem          *item,
                                                  GType              new_type);
static gboolean   pika_text_layer_rename         (PikaItem          *item,
                                                  const gchar       *new_name,
                                                  const gchar       *undo_desc,
                                                  GError           **error);

static void       pika_text_layer_set_buffer     (PikaDrawable      *drawable,
                                                  gboolean           push_undo,
                                                  const gchar       *undo_desc,
                                                  GeglBuffer        *buffer,
                                                  const GeglRectangle *bounds);
static void       pika_text_layer_push_undo      (PikaDrawable      *drawable,
                                                  const gchar       *undo_desc,
                                                  GeglBuffer        *buffer,
                                                  gint               x,
                                                  gint               y,
                                                  gint               width,
                                                  gint               height);

static void       pika_text_layer_convert_type   (PikaLayer         *layer,
                                                  PikaImage         *dest_image,
                                                  const Babl        *new_format,
                                                  PikaColorProfile  *src_profile,
                                                  PikaColorProfile  *dest_profile,
                                                  GeglDitherMethod   layer_dither_type,
                                                  GeglDitherMethod   mask_dither_type,
                                                  gboolean           push_undo,
                                                  PikaProgress      *progress);

static void       pika_text_layer_text_changed   (PikaTextLayer     *layer);
static gboolean   pika_text_layer_render         (PikaTextLayer     *layer);
static void       pika_text_layer_render_layout  (PikaTextLayer     *layer,
                                                  PikaTextLayout    *layout);


G_DEFINE_TYPE_WITH_PRIVATE (PikaTextLayer, pika_text_layer, PIKA_TYPE_LAYER)

#define parent_class pika_text_layer_parent_class


static void
pika_text_layer_class_init (PikaTextLayerClass *klass)
{
  GObjectClass      *object_class      = G_OBJECT_CLASS (klass);
  PikaObjectClass   *pika_object_class = PIKA_OBJECT_CLASS (klass);
  PikaViewableClass *viewable_class    = PIKA_VIEWABLE_CLASS (klass);
  PikaItemClass     *item_class        = PIKA_ITEM_CLASS (klass);
  PikaDrawableClass *drawable_class    = PIKA_DRAWABLE_CLASS (klass);
  PikaLayerClass    *layer_class       = PIKA_LAYER_CLASS (klass);

  object_class->finalize            = pika_text_layer_finalize;
  object_class->get_property        = pika_text_layer_get_property;
  object_class->set_property        = pika_text_layer_set_property;

  pika_object_class->get_memsize    = pika_text_layer_get_memsize;

  viewable_class->default_icon_name = "pika-text-layer";

  item_class->duplicate             = pika_text_layer_duplicate;
  item_class->rename                = pika_text_layer_rename;

#if 0
  item_class->scale                 = pika_text_layer_scale;
  item_class->flip                  = pika_text_layer_flip;
  item_class->rotate                = pika_text_layer_rotate;
  item_class->transform             = pika_text_layer_transform;
#endif

  item_class->default_name          = _("Text Layer");
  item_class->rename_desc           = _("Rename Text Layer");
  item_class->translate_desc        = _("Move Text Layer");
  item_class->scale_desc            = _("Scale Text Layer");
  item_class->resize_desc           = _("Resize Text Layer");
  item_class->flip_desc             = _("Flip Text Layer");
  item_class->rotate_desc           = _("Rotate Text Layer");
  item_class->transform_desc        = _("Transform Text Layer");

  drawable_class->set_buffer        = pika_text_layer_set_buffer;
  drawable_class->push_undo         = pika_text_layer_push_undo;

  layer_class->convert_type         = pika_text_layer_convert_type;

  PIKA_CONFIG_PROP_OBJECT (object_class, PROP_TEXT,
                           "text",
                           NULL, NULL,
                           PIKA_TYPE_TEXT,
                           PIKA_PARAM_STATIC_STRINGS);

  PIKA_CONFIG_PROP_BOOLEAN (object_class, PROP_AUTO_RENAME,
                            "auto-rename",
                            NULL, NULL,
                            TRUE,
                            PIKA_PARAM_STATIC_STRINGS);

  PIKA_CONFIG_PROP_BOOLEAN (object_class, PROP_MODIFIED,
                            "modified",
                            NULL, NULL,
                            FALSE,
                            PIKA_PARAM_STATIC_STRINGS);
}

static void
pika_text_layer_init (PikaTextLayer *layer)
{
  layer->text                 = NULL;
  layer->text_parasite        = NULL;
  layer->text_parasite_is_old = FALSE;
  layer->private              = pika_text_layer_get_instance_private (layer);
}

static void
pika_text_layer_finalize (GObject *object)
{
  PikaTextLayer *layer = PIKA_TEXT_LAYER (object);

  g_clear_object (&layer->text);

  G_OBJECT_CLASS (parent_class)->finalize (object);
}

static void
pika_text_layer_get_property (GObject      *object,
                              guint         property_id,
                              GValue       *value,
                              GParamSpec   *pspec)
{
  PikaTextLayer *text_layer = PIKA_TEXT_LAYER (object);

  switch (property_id)
    {
    case PROP_TEXT:
      g_value_set_object (value, text_layer->text);
      break;
    case PROP_AUTO_RENAME:
      g_value_set_boolean (value, text_layer->auto_rename);
      break;
    case PROP_MODIFIED:
      g_value_set_boolean (value, text_layer->modified);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
pika_text_layer_set_property (GObject      *object,
                              guint         property_id,
                              const GValue *value,
                              GParamSpec   *pspec)
{
  PikaTextLayer *text_layer = PIKA_TEXT_LAYER (object);

  switch (property_id)
    {
    case PROP_TEXT:
      pika_text_layer_set_text (text_layer, g_value_get_object (value));
      break;
    case PROP_AUTO_RENAME:
      text_layer->auto_rename = g_value_get_boolean (value);
      break;
    case PROP_MODIFIED:
      text_layer->modified = g_value_get_boolean (value);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static gint64
pika_text_layer_get_memsize (PikaObject *object,
                             gint64     *gui_size)
{
  PikaTextLayer *text_layer = PIKA_TEXT_LAYER (object);
  gint64         memsize    = 0;

  memsize += pika_object_get_memsize (PIKA_OBJECT (text_layer->text),
                                      gui_size);

  return memsize + PIKA_OBJECT_CLASS (parent_class)->get_memsize (object,
                                                                  gui_size);
}

static PikaItem *
pika_text_layer_duplicate (PikaItem *item,
                           GType     new_type)
{
  PikaItem *new_item;

  g_return_val_if_fail (g_type_is_a (new_type, PIKA_TYPE_DRAWABLE), NULL);

  new_item = PIKA_ITEM_CLASS (parent_class)->duplicate (item, new_type);

  if (PIKA_IS_TEXT_LAYER (new_item))
    {
      PikaTextLayer *layer     = PIKA_TEXT_LAYER (item);
      PikaTextLayer *new_layer = PIKA_TEXT_LAYER (new_item);

      pika_config_sync (G_OBJECT (layer), G_OBJECT (new_layer), 0);

      if (layer->text)
        {
          PikaText *text = pika_config_duplicate (PIKA_CONFIG (layer->text));

          pika_text_layer_set_text (new_layer, text);

          g_object_unref (text);
        }

      /*  this is just the parasite name, not a pointer to the parasite  */
      if (layer->text_parasite)
        {
          new_layer->text_parasite        = layer->text_parasite;
          new_layer->text_parasite_is_old = layer->text_parasite_is_old;
        }

      new_layer->private->base_dir = layer->private->base_dir;
    }

  return new_item;
}

static gboolean
pika_text_layer_rename (PikaItem     *item,
                        const gchar  *new_name,
                        const gchar  *undo_desc,
                        GError      **error)
{
  if (PIKA_ITEM_CLASS (parent_class)->rename (item, new_name, undo_desc, error))
    {
      g_object_set (item, "auto-rename", FALSE, NULL);

      return TRUE;
    }

  return FALSE;
}

static void
pika_text_layer_set_buffer (PikaDrawable        *drawable,
                            gboolean             push_undo,
                            const gchar         *undo_desc,
                            GeglBuffer          *buffer,
                            const GeglRectangle *bounds)
{
  PikaTextLayer *layer = PIKA_TEXT_LAYER (drawable);
  PikaImage     *image = pika_item_get_image (PIKA_ITEM (layer));

  if (push_undo && ! layer->modified)
    pika_image_undo_group_start (image, PIKA_UNDO_GROUP_DRAWABLE_MOD,
                                 undo_desc);

  PIKA_DRAWABLE_CLASS (parent_class)->set_buffer (drawable,
                                                  push_undo, undo_desc,
                                                  buffer, bounds);

  if (push_undo && ! layer->modified)
    {
      pika_image_undo_push_text_layer_modified (image, NULL, layer);

      g_object_set (drawable, "modified", TRUE, NULL);

      pika_image_undo_group_end (image);
    }
}

static void
pika_text_layer_push_undo (PikaDrawable *drawable,
                           const gchar  *undo_desc,
                           GeglBuffer   *buffer,
                           gint          x,
                           gint          y,
                           gint          width,
                           gint          height)
{
  PikaTextLayer *layer = PIKA_TEXT_LAYER (drawable);
  PikaImage     *image = pika_item_get_image (PIKA_ITEM (layer));

  if (! layer->modified)
    pika_image_undo_group_start (image, PIKA_UNDO_GROUP_DRAWABLE, undo_desc);

  PIKA_DRAWABLE_CLASS (parent_class)->push_undo (drawable, undo_desc,
                                                 buffer,
                                                 x, y, width, height);

  if (! layer->modified)
    {
      pika_image_undo_push_text_layer_modified (image, NULL, layer);

      g_object_set (drawable, "modified", TRUE, NULL);

      pika_image_undo_group_end (image);
    }
}

static void
pika_text_layer_convert_type (PikaLayer         *layer,
                              PikaImage         *dest_image,
                              const Babl        *new_format,
                              PikaColorProfile  *src_profile,
                              PikaColorProfile  *dest_profile,
                              GeglDitherMethod   layer_dither_type,
                              GeglDitherMethod   mask_dither_type,
                              gboolean           push_undo,
                              PikaProgress      *progress)
{
  PikaTextLayer *text_layer = PIKA_TEXT_LAYER (layer);
  PikaImage     *image      = pika_item_get_image (PIKA_ITEM (text_layer));

  if (! text_layer->text   ||
      text_layer->modified ||
      layer_dither_type != GEGL_DITHER_NONE)
    {
      PIKA_LAYER_CLASS (parent_class)->convert_type (layer, dest_image,
                                                     new_format,
                                                     src_profile,
                                                     dest_profile,
                                                     layer_dither_type,
                                                     mask_dither_type,
                                                     push_undo,
                                                     progress);
    }
  else
    {
      if (push_undo)
        pika_image_undo_push_text_layer_convert (image, NULL, text_layer);

      text_layer->convert_format = new_format;

      pika_text_layer_render (text_layer);

      text_layer->convert_format = NULL;
    }
}


/*  public functions  */

/**
 * pika_text_layer_new:
 * @image: the #PikaImage the layer should belong to
 * @text: a #PikaText object
 *
 * Creates a new text layer.
 *
 * Returns: (nullable): a new #PikaTextLayer or %NULL in case of a problem
 **/
PikaLayer *
pika_text_layer_new (PikaImage *image,
                     PikaText  *text)
{
  PikaTextLayer *layer;

  g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL);
  g_return_val_if_fail (PIKA_IS_TEXT (text), NULL);

  if (! text->text && ! text->markup)
    return NULL;

  layer =
    PIKA_TEXT_LAYER (pika_drawable_new (PIKA_TYPE_TEXT_LAYER,
                                        image, NULL,
                                        0, 0, 1, 1,
                                        pika_image_get_layer_format (image,
                                                                     TRUE)));

  pika_layer_set_mode (PIKA_LAYER (layer),
                       pika_image_get_default_new_layer_mode (image),
                       FALSE);

  pika_text_layer_set_text (layer, text);

  if (! pika_text_layer_render (layer))
    {
      g_object_unref (layer);
      return NULL;
    }

  return PIKA_LAYER (layer);
}

void
pika_text_layer_set_text (PikaTextLayer *layer,
                          PikaText      *text)
{
  g_return_if_fail (PIKA_IS_TEXT_LAYER (layer));
  g_return_if_fail (text == NULL || PIKA_IS_TEXT (text));

  if (layer->text == text)
    return;

  if (layer->text)
    {
      g_signal_handlers_disconnect_by_func (layer->text,
                                            G_CALLBACK (pika_text_layer_text_changed),
                                            layer);

      g_clear_object (&layer->text);
    }

  if (text)
    {
      layer->text = g_object_ref (text);
      layer->private->base_dir = layer->text->base_dir;

      g_signal_connect_object (text, "changed",
                               G_CALLBACK (pika_text_layer_text_changed),
                               layer, G_CONNECT_SWAPPED);
    }

  g_object_notify (G_OBJECT (layer), "text");
  pika_viewable_invalidate_preview (PIKA_VIEWABLE (layer));
}

PikaText *
pika_text_layer_get_text (PikaTextLayer *layer)
{
  g_return_val_if_fail (PIKA_IS_TEXT_LAYER (layer), NULL);

  return layer->text;
}

void
pika_text_layer_set (PikaTextLayer *layer,
                     const gchar   *undo_desc,
                     const gchar   *first_property_name,
                     ...)
{
  PikaImage *image;
  PikaText  *text;
  va_list    var_args;

  g_return_if_fail (pika_item_is_text_layer (PIKA_ITEM (layer)));
  g_return_if_fail (pika_item_is_attached (PIKA_ITEM (layer)));

  text = pika_text_layer_get_text (layer);
  if (! text)
    return;

  image = pika_item_get_image (PIKA_ITEM (layer));

  pika_image_undo_group_start (image, PIKA_UNDO_GROUP_TEXT, undo_desc);

  g_object_freeze_notify (G_OBJECT (layer));

  if (layer->modified)
    {
      pika_image_undo_push_text_layer_modified (image, NULL, layer);

      /*  pass copy_tiles = TRUE so we not only ref the tiles; after
       *  being a text layer again, undo doesn't care about the
       *  layer's pixels any longer because they are generated, so
       *  changing the text would happily overwrite the layer's
       *  pixels, changing the pixels on the undo stack too without
       *  any chance to ever undo again.
       */
      pika_image_undo_push_drawable_mod (image, NULL,
                                         PIKA_DRAWABLE (layer), TRUE);
    }

  pika_image_undo_push_text_layer (image, undo_desc, layer, NULL);

  va_start (var_args, first_property_name);

  g_object_set_valist (G_OBJECT (text), first_property_name, var_args);

  va_end (var_args);

  g_object_set (layer, "modified", FALSE, NULL);

  g_object_thaw_notify (G_OBJECT (layer));

  pika_image_undo_group_end (image);
}

/**
 * pika_text_layer_discard:
 * @layer: a #PikaTextLayer
 *
 * Discards the text information. This makes @layer behave like a
 * normal layer.
 */
void
pika_text_layer_discard (PikaTextLayer *layer)
{
  g_return_if_fail (PIKA_IS_TEXT_LAYER (layer));
  g_return_if_fail (pika_item_is_attached (PIKA_ITEM (layer)));

  if (! layer->text)
    return;

  pika_image_undo_push_text_layer (pika_item_get_image (PIKA_ITEM (layer)),
                                   _("Discard Text Information"),
                                   layer, NULL);

  pika_text_layer_set_text (layer, NULL);
}

gboolean
pika_item_is_text_layer (PikaItem *item)
{
  return (PIKA_IS_TEXT_LAYER (item)    &&
          PIKA_TEXT_LAYER (item)->text &&
          PIKA_TEXT_LAYER (item)->modified == FALSE);
}


/*  private functions  */

static const Babl *
pika_text_layer_get_format (PikaTextLayer *layer)
{
  if (layer->convert_format)
    return layer->convert_format;

  return pika_drawable_get_format (PIKA_DRAWABLE (layer));
}

static void
pika_text_layer_text_changed (PikaTextLayer *layer)
{
  /*  If the text layer was created from a parasite, it's time to
   *  remove that parasite now.
   */
  if (layer->text_parasite)
    {
      /*  Don't push an undo because the parasite only exists temporarily
       *  while the text layer is loaded from XCF.
       */
      pika_item_parasite_detach (PIKA_ITEM (layer), layer->text_parasite,
                                 FALSE);
      layer->text_parasite        = NULL;
      layer->text_parasite_is_old = FALSE;
    }

  if (layer->text->box_mode == PIKA_TEXT_BOX_DYNAMIC)
    {
      gint                old_width;
      gint                new_width;
      PikaItem           *item         = PIKA_ITEM (layer);
      PikaTextDirection   old_base_dir = layer->private->base_dir;
      PikaTextDirection   new_base_dir = layer->text->base_dir;

      old_width = pika_item_get_width (item);
      pika_text_layer_render (layer);
      new_width = pika_item_get_width (item);

      if (old_base_dir != new_base_dir)
        {
          switch (old_base_dir)
            {
            case PIKA_TEXT_DIRECTION_LTR:
            case PIKA_TEXT_DIRECTION_RTL:
            case PIKA_TEXT_DIRECTION_TTB_LTR:
            case PIKA_TEXT_DIRECTION_TTB_LTR_UPRIGHT:
              switch (new_base_dir)
                {
                case PIKA_TEXT_DIRECTION_TTB_RTL:
                case PIKA_TEXT_DIRECTION_TTB_RTL_UPRIGHT:
                  pika_item_translate (item, -new_width, 0, FALSE);
                  break;

                case PIKA_TEXT_DIRECTION_LTR:
                case PIKA_TEXT_DIRECTION_RTL:
                case PIKA_TEXT_DIRECTION_TTB_LTR:
                case PIKA_TEXT_DIRECTION_TTB_LTR_UPRIGHT:
                  break;
                }
              break;

            case PIKA_TEXT_DIRECTION_TTB_RTL:
            case PIKA_TEXT_DIRECTION_TTB_RTL_UPRIGHT:
              switch (new_base_dir)
                {
                case PIKA_TEXT_DIRECTION_LTR:
                case PIKA_TEXT_DIRECTION_RTL:
                case PIKA_TEXT_DIRECTION_TTB_LTR:
                case PIKA_TEXT_DIRECTION_TTB_LTR_UPRIGHT:
                  pika_item_translate (item, old_width, 0, FALSE);
                  break;

                case PIKA_TEXT_DIRECTION_TTB_RTL:
                case PIKA_TEXT_DIRECTION_TTB_RTL_UPRIGHT:
                  break;
                }
              break;
            }
        }
      else if ((new_base_dir == PIKA_TEXT_DIRECTION_TTB_RTL ||
              new_base_dir == PIKA_TEXT_DIRECTION_TTB_RTL_UPRIGHT))
        {
          if (old_width != new_width)
            pika_item_translate (item, old_width - new_width, 0, FALSE);
        }
    }
  else
    pika_text_layer_render (layer);

  layer->private->base_dir = layer->text->base_dir;
}

static gboolean
pika_text_layer_render (PikaTextLayer *layer)
{
  PikaDrawable   *drawable;
  PikaItem       *item;
  PikaImage      *image;
  PikaContainer  *container;
  PikaTextLayout *layout;
  gdouble         xres;
  gdouble         yres;
  gint            width;
  gint            height;
  GError         *error = NULL;

  if (! layer->text)
    return FALSE;

  drawable  = PIKA_DRAWABLE (layer);
  item      = PIKA_ITEM (layer);
  image     = pika_item_get_image (item);
  container = pika_data_factory_get_container (image->pika->font_factory);

  pika_data_factory_data_wait (image->pika->font_factory);

  if (pika_container_is_empty (container))
    {
      pika_message_literal (image->pika, NULL, PIKA_MESSAGE_ERROR,
                            _("Due to lack of any fonts, "
                              "text functionality is not available."));
      return FALSE;
    }

  pika_image_get_resolution (image, &xres, &yres);

  layout = pika_text_layout_new (layer->text, xres, yres, &error);
  if (error)
    {
      pika_message_literal (image->pika, NULL, PIKA_MESSAGE_ERROR, error->message);
      g_error_free (error);
    }

  g_object_freeze_notify (G_OBJECT (drawable));

  if (pika_text_layout_get_size (layout, &width, &height) &&
      (width  != pika_item_get_width  (item) ||
       height != pika_item_get_height (item) ||
       pika_text_layer_get_format (layer) !=
       pika_drawable_get_format (drawable)))
    {
      GeglBuffer *new_buffer;

      new_buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0, width, height),
                                    pika_text_layer_get_format (layer));
      pika_drawable_set_buffer (drawable, FALSE, NULL, new_buffer);
      g_object_unref (new_buffer);

      if (pika_layer_get_mask (PIKA_LAYER (layer)))
        {
          PikaLayerMask *mask = pika_layer_get_mask (PIKA_LAYER (layer));

          static PikaContext *unused_eek = NULL;

          if (! unused_eek)
            unused_eek = pika_context_new (image->pika, "eek", NULL);

          pika_item_resize (PIKA_ITEM (mask),
                            unused_eek, PIKA_FILL_TRANSPARENT,
                            width, height, 0, 0);
        }
    }

  if (layer->auto_rename)
    {
      PikaItem *item = PIKA_ITEM (layer);
      gchar    *name = NULL;

      if (layer->text->text)
        {
          name = pika_utf8_strtrim (layer->text->text, 30);
        }
      else if (layer->text->markup)
        {
          gchar *tmp = pika_markup_extract_text (layer->text->markup);
          name = pika_utf8_strtrim (tmp, 30);
          g_free (tmp);
        }

      if (! name || ! name[0])
        {
          g_free (name);
          name = g_strdup (_("Empty Text Layer"));
        }

      if (pika_item_is_attached (item))
        {
          pika_item_tree_rename_item (pika_item_get_tree (item), item,
                                      name, FALSE, NULL);
          g_free (name);
        }
      else
        {
          pika_object_take_name (PIKA_OBJECT (layer), name);
        }
    }

  if (width > 0 && height > 0)
    pika_text_layer_render_layout (layer, layout);

  g_object_unref (layout);

  g_object_thaw_notify (G_OBJECT (drawable));

  return (width > 0 && height > 0);
}

static void
pika_text_layer_set_dash_info (cairo_t      *cr,
                               gdouble       width,
                               gdouble       dash_offset,
                               const GArray *dash_info)
{
  if (dash_info && dash_info->len >= 2)
    {
      gint     n_dashes = dash_info->len;
      gdouble *dashes   = g_new (gdouble, dash_info->len);
      gint     i;

      dash_offset = dash_offset * MAX (width, 1.0);

      for (i = 0; i < n_dashes; i++)
        dashes[i] = MAX (width, 1.0) * g_array_index (dash_info, gdouble, i);

      /* correct 0.0 in the first element (starts with a gap) */

      if (dashes[0] == 0.0)
        {
          gdouble first;

          first = dashes[1];

          /* shift the pattern to really starts with a dash and
           * use the offset to skip into it.
           */
          for (i = 0; i < n_dashes - 2; i++)
            {
              dashes[i] = dashes[i + 2];
              dash_offset += dashes[i];
            }

          if (n_dashes % 2 == 1)
            {
              dashes[n_dashes - 2] = first;
              n_dashes--;
            }
          else if (dash_info->len > 2)
            {
              dashes[n_dashes - 3] += first;
              n_dashes -= 2;
            }
        }

      /* correct odd number of dash specifiers */

      if (n_dashes % 2 == 1)
        {
          gdouble last = dashes[n_dashes - 1];

          dashes[0] += last;
          dash_offset += last;
          n_dashes--;
        }

      if (n_dashes >= 2)
        cairo_set_dash (cr,
                        dashes,
                        n_dashes,
                        dash_offset);

      g_free (dashes);
    }
}

static cairo_surface_t *
pika_temp_buf_create_cairo_surface (PikaTempBuf *temp_buf)
{
  cairo_surface_t *surface;
  gboolean         has_alpha;
  const Babl      *format;
  const Babl      *fish = NULL;
  const guchar    *data;
  gint             width;
  gint             height;
  gint             bpp;
  guchar          *pixels;
  gint             rowstride;
  gint             i;

  g_return_val_if_fail (temp_buf != NULL, NULL);

  data      = pika_temp_buf_get_data (temp_buf);
  format    = pika_temp_buf_get_format (temp_buf);
  width     = pika_temp_buf_get_width (temp_buf);
  height    = pika_temp_buf_get_height (temp_buf);
  bpp       = babl_format_get_bytes_per_pixel (format);
  has_alpha = babl_format_has_alpha (format);

  surface = cairo_image_surface_create (has_alpha ?
                                        CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24,
                                        width, height);

  pixels    = cairo_image_surface_get_data (surface);
  rowstride = cairo_image_surface_get_stride (surface);

  if (format != babl_format (has_alpha ? "cairo-ARGB32" : "cairo-RGB24"))
    fish = babl_fish (format, babl_format (has_alpha ? "cairo-ARGB32" : "cairo-RGB24"));

  for (i = 0; i < height; i++)
    {
      if (fish)
        babl_process (fish, data, pixels, width);
      else
        memcpy (pixels, data, width * bpp);

      data += width * bpp;
      pixels += rowstride;
    }

  return surface;
}

static void
pika_text_layer_render_layout (PikaTextLayer  *layer,
                               PikaTextLayout *layout)
{
  PikaDrawable       *drawable = PIKA_DRAWABLE (layer);
  PikaItem           *item     = PIKA_ITEM (layer);
  PikaImage          *image    = pika_item_get_image (item);
  GeglBuffer         *buffer;
  PikaColorTransform *transform;
  cairo_t            *cr;
  cairo_surface_t    *surface;
  gint                width;
  gint                height;
  cairo_status_t      status;

  g_return_if_fail (pika_drawable_has_alpha (drawable));

  width  = pika_item_get_width  (item);
  height = pika_item_get_height (item);

  surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
  status = cairo_surface_status (surface);

  if (status != CAIRO_STATUS_SUCCESS)
    {
      PikaImage *image = pika_item_get_image (item);

      pika_message_literal (image->pika, NULL, PIKA_MESSAGE_ERROR,
                            _("Your text cannot be rendered. It is likely too big. "
                              "Please make it shorter or use a smaller font."));
      cairo_surface_destroy (surface);
      return;
    }

  cr = cairo_create (surface);
  if (layer->text->outline != PIKA_TEXT_OUTLINE_STROKE_ONLY)
    {
      cairo_save (cr);

      pika_text_layout_render (layout, cr, layer->text->base_dir, FALSE);

      cairo_restore (cr);
    }

  if (layer->text->outline != PIKA_TEXT_OUTLINE_NONE)
    {
      PikaText *text = layer->text;
      PikaRGB   col  = text->outline_foreground;

      cairo_save (cr);

      cairo_set_antialias (cr, text->outline_antialias ?
                           CAIRO_ANTIALIAS_GRAY : CAIRO_ANTIALIAS_NONE);
      cairo_set_line_cap (cr,
                          text->outline_cap_style == PIKA_CAP_BUTT ? CAIRO_LINE_CAP_BUTT :
                          text->outline_cap_style == PIKA_CAP_ROUND ? CAIRO_LINE_CAP_ROUND :
                          CAIRO_LINE_CAP_SQUARE);
      cairo_set_line_join (cr, text->outline_join_style == PIKA_JOIN_MITER ? CAIRO_LINE_JOIN_MITER :
                               text->outline_join_style == PIKA_JOIN_ROUND ? CAIRO_LINE_JOIN_ROUND :
                               CAIRO_LINE_JOIN_BEVEL);
      cairo_set_miter_limit (cr, text->outline_miter_limit);

      if (text->outline_dash_info)
        pika_text_layer_set_dash_info (cr, text->outline_width, text->outline_dash_offset, text->outline_dash_info);

      if (text->outline_style == PIKA_CUSTOM_STYLE_PATTERN && text->outline_pattern)
        {
          PikaTempBuf     *tempbuf = pika_pattern_get_mask (text->outline_pattern);
          cairo_surface_t *surface = pika_temp_buf_create_cairo_surface (tempbuf);

          cairo_set_source_surface (cr, surface, 0.0, 0.0);
          cairo_surface_destroy (surface);

          cairo_pattern_set_extend (cairo_get_source (cr), CAIRO_EXTEND_REPEAT);
        }
      else
        {
          cairo_set_source_rgba (cr, col.r, col.g, col.b, col.a);
        }

      cairo_set_line_width (cr, text->outline_width * 2);

      pika_text_layout_render (layout, cr, text->base_dir, TRUE);
      cairo_clip_preserve (cr);
      cairo_stroke (cr);

      cairo_restore (cr);
    }

  cairo_destroy (cr);

  cairo_surface_flush (surface);

  buffer = pika_cairo_surface_create_buffer (surface);

  transform = pika_image_get_color_transform_from_srgb_u8 (image);

  if (transform)
    {
      pika_color_transform_process_buffer (transform,
                                           buffer,
                                           NULL,
                                           pika_drawable_get_buffer (drawable),
                                           NULL);
    }
  else
    {
      pika_gegl_buffer_copy (buffer, NULL, GEGL_ABYSS_NONE,
                             pika_drawable_get_buffer (drawable), NULL);
    }

  g_object_unref (buffer);
  cairo_surface_destroy (surface);

  pika_drawable_update (drawable, 0, 0, width, height);
}
