Gimp/app/text/gimptextlayer.c
Jehan 926d5c6daa app, libgimp*, plug-ins: fix previous commit and add gimp_cairo_surface_get_buffer().
The previous commit was not taking into account usages of
gimp_cairo_surface_create_buffer(), other than the text layer rendering
usage, as it was focused on fixing #14970.

In particular, in other pieces of code, we were using a temporary
GeglBuffer as a way to edit the original Cairo surface but the syncing
back was not happening anymore. So previous commit was breaking all
thumbnails of layers or images, as well as PDF export for all raster
layers.

After some thought, I decided to implement a new function
gimp_cairo_surface_get_buffer() which would do what
gimp_cairo_surface_create_buffer() was doing, but adding a boolean flag
to declare whether or not we were expecting to sync back into the Cairo
surface's data. After all, when we don't need to sync back, better avoid
the useless work of copying possibly huge data.

I also added back the possibility of returning a linear-memory backed
buffer, after some discussion with pippin. Because for very small
surfaces, and if we are indeed looking to sync back into the source
Cairo surface, the advantages of using a linear buffer may still be
worth it, even though it's not true for the general case (where tiled
buffers will be much more efficient). I used 4096 (64^2 because our
current tile dimensions are 64 right now in GIMP), though this number is
kinda random and doesn't matter too much (it doesn't have to be the
square of the tile's width or height). I don't put any info about this
in the new function's documentation because developers should not have
any expectations on the type of buffer they get (only whether or not it
will sync back to the source).

As a consequence of all this, gimp_cairo_surface_create_buffer() has
been deprecated as of GIMP 3.2, though it will still work the same. The
new function will be recommended as a replacement.
2025-11-08 20:34:55 +01:00

1126 lines
36 KiB
C

/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* GimpTextLayer
* 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 "libgimpbase/gimpbase.h"
#include "libgimpcolor/gimpcolor.h"
#include "libgimpconfig/gimpconfig.h"
#include "text-types.h"
#include "gegl/gimp-babl.h"
#include "gegl/gimp-gegl-loops.h"
#include "gegl/gimp-gegl-utils.h"
#include "core/gimp.h"
#include "core/gimp-utils.h"
#include "core/gimpcontainer.h"
#include "core/gimpcontext.h"
#include "core/gimpdatafactory.h"
#include "core/gimpimage.h"
#include "core/gimpimage-color-profile.h"
#include "core/gimpimage-undo.h"
#include "core/gimpimage-undo-push.h"
#include "core/gimpparasitelist.h"
#include "core/gimppattern.h"
#include "core/gimprasterizable.h"
#include "core/gimptempbuf.h"
#include "gimptext.h"
#include "gimptextlayer.h"
#include "gimptextlayout.h"
#include "gimptextlayout-render.h"
#include "gimp-intl.h"
enum
{
PROP_0,
PROP_TEXT,
};
struct _GimpTextLayerPrivate
{
GimpTextDirection base_dir;
/* on-canvas editor position */
gboolean style_overlay_positioned;
gdouble style_overlay_x;
gdouble style_overlay_y;
gdouble style_overlay_offset_x;
gdouble style_overlay_offset_y;
};
static void gimp_text_layer_rasterizable_iface_init
(GimpRasterizableInterface *iface);
static void gimp_text_layer_finalize (GObject *object);
static void gimp_text_layer_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec);
static void gimp_text_layer_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
static void gimp_text_layer_set_rasterized (GimpRasterizable *rasterizable,
gboolean rasterized);
static gint64 gimp_text_layer_get_memsize (GimpObject *object,
gint64 *gui_size);
static void gimp_text_layer_size_changed (GimpViewable *viewable);
static GimpItem * gimp_text_layer_duplicate (GimpItem *item,
GType new_type);
static void gimp_text_layer_set_buffer (GimpDrawable *drawable,
gboolean push_undo,
const gchar *undo_desc,
GeglBuffer *buffer,
const GeglRectangle *bounds);
static void gimp_text_layer_push_undo (GimpDrawable *drawable,
const gchar *undo_desc,
GeglBuffer *buffer,
gint x,
gint y,
gint width,
gint height);
static void gimp_text_layer_convert_type (GimpLayer *layer,
GimpImage *dest_image,
const Babl *new_format,
GimpColorProfile *src_profile,
GimpColorProfile *dest_profile,
GeglDitherMethod layer_dither_type,
GeglDitherMethod mask_dither_type,
gboolean push_undo,
GimpProgress *progress);
static void gimp_text_layer_text_changed (GimpTextLayer *layer);
static gboolean gimp_text_layer_render (GimpTextLayer *layer);
static void gimp_text_layer_render_layout (GimpTextLayer *layer,
GimpTextLayout *layout);
G_DEFINE_TYPE_WITH_CODE (GimpTextLayer, gimp_text_layer, GIMP_TYPE_LAYER,
G_ADD_PRIVATE (GimpTextLayer)
G_IMPLEMENT_INTERFACE (GIMP_TYPE_RASTERIZABLE,
gimp_text_layer_rasterizable_iface_init))
#define parent_class gimp_text_layer_parent_class
static void
gimp_text_layer_class_init (GimpTextLayerClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
GimpViewableClass *viewable_class = GIMP_VIEWABLE_CLASS (klass);
GimpItemClass *item_class = GIMP_ITEM_CLASS (klass);
GimpDrawableClass *drawable_class = GIMP_DRAWABLE_CLASS (klass);
GimpLayerClass *layer_class = GIMP_LAYER_CLASS (klass);
object_class->finalize = gimp_text_layer_finalize;
object_class->get_property = gimp_text_layer_get_property;
object_class->set_property = gimp_text_layer_set_property;
gimp_object_class->get_memsize = gimp_text_layer_get_memsize;
viewable_class->default_icon_name = "gimp-text-layer";
viewable_class->default_name = _("Text Layer");
viewable_class->size_changed = gimp_text_layer_size_changed;
item_class->duplicate = gimp_text_layer_duplicate;
item_class->rename = gimp_rasterizable_rename;
#if 0
item_class->scale = gimp_text_layer_scale;
item_class->flip = gimp_text_layer_flip;
item_class->rotate = gimp_text_layer_rotate;
item_class->transform = gimp_text_layer_transform;
#endif
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 = gimp_text_layer_set_buffer;
drawable_class->push_undo = gimp_text_layer_push_undo;
layer_class->convert_type = gimp_text_layer_convert_type;
GIMP_CONFIG_PROP_OBJECT (object_class, PROP_TEXT,
"text",
NULL, NULL,
GIMP_TYPE_TEXT,
GIMP_PARAM_STATIC_STRINGS);
}
static void
gimp_text_layer_init (GimpTextLayer *layer)
{
layer->text = NULL;
layer->text_parasite = NULL;
layer->text_parasite_is_old = FALSE;
layer->private = gimp_text_layer_get_instance_private (layer);
}
static void
gimp_text_layer_rasterizable_iface_init (GimpRasterizableInterface *iface)
{
iface->set_rasterized = gimp_text_layer_set_rasterized;
}
static void
gimp_text_layer_finalize (GObject *object)
{
GimpTextLayer *layer = GIMP_TEXT_LAYER (object);
g_clear_object (&layer->text);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
gimp_text_layer_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
GimpTextLayer *text_layer = GIMP_TEXT_LAYER (object);
switch (property_id)
{
case PROP_TEXT:
g_value_set_object (value, text_layer->text);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gimp_text_layer_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
GimpTextLayer *text_layer = GIMP_TEXT_LAYER (object);
switch (property_id)
{
case PROP_TEXT:
gimp_text_layer_set_text (text_layer, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gimp_text_layer_set_rasterized (GimpRasterizable *rasterizable,
gboolean rasterized)
{
if (! rasterized)
{
if (! gimp_drawable_has_alpha (GIMP_DRAWABLE (rasterizable)))
gimp_layer_add_alpha (GIMP_LAYER (rasterizable));
gimp_text_layer_render (GIMP_TEXT_LAYER (rasterizable));
}
}
static gint64
gimp_text_layer_get_memsize (GimpObject *object,
gint64 *gui_size)
{
GimpTextLayer *text_layer = GIMP_TEXT_LAYER (object);
gint64 memsize = 0;
memsize += gimp_object_get_memsize (GIMP_OBJECT (text_layer->text),
gui_size);
return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
gui_size);
}
static void
gimp_text_layer_size_changed (GimpViewable *viewable)
{
GimpTextLayer *text_layer = GIMP_TEXT_LAYER (viewable);
/* TODO: For now, we only let the size_changed () call bubble up to
* gimp_drawable_size_changed () if the layer has been rasterized by
* a transform. This prevents filters like Drop Shadow from being
* cropped just by typing */
if (gimp_rasterizable_is_rasterized (GIMP_RASTERIZABLE (text_layer)))
GIMP_VIEWABLE_CLASS (parent_class)->size_changed (viewable);
}
static GimpItem *
gimp_text_layer_duplicate (GimpItem *item,
GType new_type)
{
GimpItem *new_item;
g_return_val_if_fail (g_type_is_a (new_type, GIMP_TYPE_DRAWABLE), NULL);
new_item = GIMP_ITEM_CLASS (parent_class)->duplicate (item, new_type);
if (GIMP_IS_TEXT_LAYER (new_item))
{
GimpTextLayer *layer = GIMP_TEXT_LAYER (item);
GimpTextLayer *new_layer = GIMP_TEXT_LAYER (new_item);
gimp_config_sync (G_OBJECT (layer), G_OBJECT (new_layer), 0);
if (layer->text)
{
GimpText *text = gimp_config_duplicate (GIMP_CONFIG (layer->text));
gimp_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;
gimp_rasterizable_set_auto_rename (GIMP_RASTERIZABLE (new_layer),
gimp_rasterizable_get_auto_rename (GIMP_RASTERIZABLE (layer)));
if (gimp_rasterizable_is_rasterized (GIMP_RASTERIZABLE (layer)))
gimp_rasterizable_set_undo_rasterized (GIMP_RASTERIZABLE (new_layer), TRUE);
}
return new_item;
}
static void
gimp_text_layer_set_buffer (GimpDrawable *drawable,
gboolean push_undo,
const gchar *undo_desc,
GeglBuffer *buffer,
const GeglRectangle *bounds)
{
GimpTextLayer *layer = GIMP_TEXT_LAYER (drawable);
GimpImage *image = gimp_item_get_image (GIMP_ITEM (layer));
gboolean is_rasterized;
is_rasterized = gimp_rasterizable_is_rasterized (GIMP_RASTERIZABLE (layer));
if (push_undo && ! is_rasterized)
gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_DRAWABLE_MOD,
undo_desc);
GIMP_DRAWABLE_CLASS (parent_class)->set_buffer (drawable,
push_undo, undo_desc,
buffer, bounds);
if (push_undo && ! is_rasterized)
{
gimp_rasterizable_rasterize (GIMP_RASTERIZABLE (layer), push_undo);
gimp_image_undo_group_end (image);
}
}
static void
gimp_text_layer_push_undo (GimpDrawable *drawable,
const gchar *undo_desc,
GeglBuffer *buffer,
gint x,
gint y,
gint width,
gint height)
{
GimpTextLayer *layer = GIMP_TEXT_LAYER (drawable);
GimpImage *image = gimp_item_get_image (GIMP_ITEM (layer));
gboolean is_rasterized;
is_rasterized = gimp_rasterizable_is_rasterized (GIMP_RASTERIZABLE (layer));
if (! is_rasterized)
gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_DRAWABLE, undo_desc);
GIMP_DRAWABLE_CLASS (parent_class)->push_undo (drawable, undo_desc,
buffer,
x, y, width, height);
if (! is_rasterized)
{
gimp_rasterizable_rasterize (GIMP_RASTERIZABLE (layer), TRUE);
gimp_image_undo_group_end (image);
}
}
static void
gimp_text_layer_convert_type (GimpLayer *layer,
GimpImage *dest_image,
const Babl *new_format,
GimpColorProfile *src_profile,
GimpColorProfile *dest_profile,
GeglDitherMethod layer_dither_type,
GeglDitherMethod mask_dither_type,
gboolean push_undo,
GimpProgress *progress)
{
GimpTextLayer *text_layer = GIMP_TEXT_LAYER (layer);
GimpImage *image = gimp_item_get_image (GIMP_ITEM (text_layer));
if (! text_layer->text ||
gimp_rasterizable_is_rasterized (GIMP_RASTERIZABLE (layer)) ||
layer_dither_type != GEGL_DITHER_NONE)
{
GIMP_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)
gimp_image_undo_push_text_layer_convert (image, NULL, text_layer);
text_layer->convert_format = new_format;
gimp_text_layer_render (text_layer);
text_layer->convert_format = NULL;
}
}
/* public functions */
/**
* gimp_text_layer_new:
* @image: the #GimpImage the layer should belong to
* @text: a #GimpText object
*
* Creates a new text layer.
*
* Returns: (nullable): a new #GimpTextLayer or %NULL in case of a problem
**/
GimpLayer *
gimp_text_layer_new (GimpImage *image,
GimpText *text)
{
GimpTextLayer *layer;
g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
g_return_val_if_fail (GIMP_IS_TEXT (text), NULL);
if (! text->text && ! text->markup)
return NULL;
layer =
GIMP_TEXT_LAYER (gimp_drawable_new (GIMP_TYPE_TEXT_LAYER,
image, NULL,
0, 0, 1, 1,
gimp_image_get_layer_format (image,
TRUE)));
gimp_layer_set_mode (GIMP_LAYER (layer),
gimp_image_get_default_new_layer_mode (image),
FALSE);
gimp_text_layer_set_text (layer, text);
if (! gimp_text_layer_render (layer))
{
g_object_unref (layer);
return NULL;
}
return GIMP_LAYER (layer);
}
void
gimp_text_layer_set_text (GimpTextLayer *layer,
GimpText *text)
{
g_return_if_fail (GIMP_IS_TEXT_LAYER (layer));
g_return_if_fail (text == NULL || GIMP_IS_TEXT (text));
if (layer->text == text)
return;
if (layer->text)
{
g_signal_handlers_disconnect_by_func (layer->text,
G_CALLBACK (gimp_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 (gimp_text_layer_text_changed),
layer, G_CONNECT_SWAPPED);
}
g_object_notify (G_OBJECT (layer), "text");
gimp_viewable_invalidate_preview (GIMP_VIEWABLE (layer));
}
GimpText *
gimp_text_layer_get_text (GimpTextLayer *layer)
{
g_return_val_if_fail (GIMP_IS_TEXT_LAYER (layer), NULL);
return layer->text;
}
void
gimp_text_layer_set (GimpTextLayer *layer,
const gchar *undo_desc,
const gchar *first_property_name,
...)
{
GimpImage *image;
GimpText *text;
va_list var_args;
g_return_if_fail (gimp_item_is_text_layer (GIMP_ITEM (layer)));
g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (layer)));
text = gimp_text_layer_get_text (layer);
if (! text)
return;
image = gimp_item_get_image (GIMP_ITEM (layer));
gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_TEXT, undo_desc);
g_object_freeze_notify (G_OBJECT (layer));
if (gimp_rasterizable_is_rasterized (GIMP_RASTERIZABLE (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.
*/
gimp_image_undo_push_drawable_mod (image, NULL,
GIMP_DRAWABLE (layer), TRUE);
}
gimp_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);
gimp_rasterizable_restore (GIMP_RASTERIZABLE (layer));
g_object_thaw_notify (G_OBJECT (layer));
gimp_image_undo_group_end (image);
}
gboolean
gimp_item_is_text_layer (GimpItem *item)
{
return (GIMP_IS_TEXT_LAYER (item) &&
! gimp_rasterizable_is_rasterized (GIMP_RASTERIZABLE (item)));
}
void
gimp_text_layer_set_style_overlay_position (GimpTextLayer *layer,
gboolean positioned,
gdouble x,
gdouble y)
{
GimpTextLayerPrivate *priv;
g_return_if_fail (GIMP_IS_TEXT_LAYER (layer));
priv = layer->private;
priv->style_overlay_positioned = positioned;
/* We want to set "style_overlay_x" and "style_overlay_y" only
* when "positioned" is TRUE. Otherwhise, we only want to set
* "style_overlay_positioned" to FALSE */
if (positioned)
{
priv->style_overlay_x = x;
priv->style_overlay_y = y;
}
}
gboolean
gimp_text_layer_get_style_overlay_position (GimpTextLayer *layer,
gdouble *x,
gdouble *y)
{
GimpTextLayerPrivate *priv;
g_return_val_if_fail (GIMP_IS_TEXT_LAYER (layer), FALSE);
priv = layer->private;
if (! priv->style_overlay_positioned)
return FALSE;
*x = priv->style_overlay_x;
*y = priv->style_overlay_y;
return TRUE;
}
gboolean
gimp_text_layer_is_style_overlay_positioned (GimpTextLayer *layer)
{
GimpTextLayerPrivate *priv;
g_return_val_if_fail (GIMP_IS_TEXT_LAYER (layer), FALSE);
priv = layer->private;
return priv->style_overlay_positioned;
}
void
gimp_text_layer_set_style_overlay_offset (GimpTextLayer *layer,
gdouble offset_x,
gdouble offset_y)
{
GimpTextLayerPrivate *priv;
g_return_if_fail (GIMP_IS_TEXT_LAYER (layer));
priv = layer->private;
priv->style_overlay_offset_x = offset_x;
priv->style_overlay_offset_y = offset_y;
}
void
gimp_text_layer_get_style_overlay_offset (GimpTextLayer *layer,
gdouble *offset_x,
gdouble *offset_y)
{
GimpTextLayerPrivate *priv;
g_return_if_fail (GIMP_IS_TEXT_LAYER (layer));
priv = layer->private;
*offset_x = priv->style_overlay_offset_x;
*offset_y = priv->style_overlay_offset_y;
}
/* private functions */
static const Babl *
gimp_text_layer_get_format (GimpTextLayer *layer)
{
if (layer->convert_format)
return layer->convert_format;
return gimp_drawable_get_format (GIMP_DRAWABLE (layer));
}
static void
gimp_text_layer_text_changed (GimpTextLayer *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.
*/
gimp_item_parasite_detach (GIMP_ITEM (layer), layer->text_parasite,
FALSE);
layer->text_parasite = NULL;
layer->text_parasite_is_old = FALSE;
}
if (layer->text->box_mode == GIMP_TEXT_BOX_DYNAMIC)
{
gint old_width;
gint new_width;
GimpItem *item = GIMP_ITEM (layer);
GimpTextDirection old_base_dir = layer->private->base_dir;
GimpTextDirection new_base_dir = layer->text->base_dir;
old_width = gimp_item_get_width (item);
gimp_text_layer_render (layer);
new_width = gimp_item_get_width (item);
if (old_base_dir != new_base_dir)
{
switch (old_base_dir)
{
case GIMP_TEXT_DIRECTION_LTR:
case GIMP_TEXT_DIRECTION_RTL:
case GIMP_TEXT_DIRECTION_TTB_LTR:
case GIMP_TEXT_DIRECTION_TTB_LTR_UPRIGHT:
switch (new_base_dir)
{
case GIMP_TEXT_DIRECTION_TTB_RTL:
case GIMP_TEXT_DIRECTION_TTB_RTL_UPRIGHT:
gimp_item_translate (item, -new_width, 0, FALSE);
break;
case GIMP_TEXT_DIRECTION_LTR:
case GIMP_TEXT_DIRECTION_RTL:
case GIMP_TEXT_DIRECTION_TTB_LTR:
case GIMP_TEXT_DIRECTION_TTB_LTR_UPRIGHT:
break;
}
break;
case GIMP_TEXT_DIRECTION_TTB_RTL:
case GIMP_TEXT_DIRECTION_TTB_RTL_UPRIGHT:
switch (new_base_dir)
{
case GIMP_TEXT_DIRECTION_LTR:
case GIMP_TEXT_DIRECTION_RTL:
case GIMP_TEXT_DIRECTION_TTB_LTR:
case GIMP_TEXT_DIRECTION_TTB_LTR_UPRIGHT:
gimp_item_translate (item, old_width, 0, FALSE);
break;
case GIMP_TEXT_DIRECTION_TTB_RTL:
case GIMP_TEXT_DIRECTION_TTB_RTL_UPRIGHT:
break;
}
break;
}
}
else if ((new_base_dir == GIMP_TEXT_DIRECTION_TTB_RTL ||
new_base_dir == GIMP_TEXT_DIRECTION_TTB_RTL_UPRIGHT))
{
if (old_width != new_width)
gimp_item_translate (item, old_width - new_width, 0, FALSE);
}
}
else
gimp_text_layer_render (layer);
layer->private->base_dir = layer->text->base_dir;
}
static gboolean
gimp_text_layer_render (GimpTextLayer *layer)
{
GimpDrawable *drawable;
GimpItem *item;
GimpImage *image;
GimpContainer *container;
GimpTextLayout *layout;
gchar *name = NULL;
gdouble xres;
gdouble yres;
gint width;
gint height;
GError *error = NULL;
if (! layer->text)
return FALSE;
drawable = GIMP_DRAWABLE (layer);
item = GIMP_ITEM (layer);
image = gimp_item_get_image (item);
container = gimp_data_factory_get_container (image->gimp->font_factory);
gimp_data_factory_data_wait (image->gimp->font_factory);
if (gimp_container_is_empty (container))
{
gimp_message_literal (image->gimp, NULL, GIMP_MESSAGE_ERROR,
_("Due to lack of any fonts, "
"text functionality is not available."));
return FALSE;
}
gimp_image_get_resolution (image, &xres, &yres);
layout = gimp_text_layout_new (layer->text, image, xres, yres, &error);
if (error)
{
gimp_message_literal (image->gimp, NULL, GIMP_MESSAGE_ERROR, error->message);
g_error_free (error);
}
g_object_freeze_notify (G_OBJECT (drawable));
if (gimp_text_layout_get_size (layout, &width, &height) &&
(width != gimp_item_get_width (item) ||
height != gimp_item_get_height (item) ||
gimp_text_layer_get_format (layer) !=
gimp_drawable_get_format (drawable)))
{
GeglBuffer *new_buffer;
new_buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0, width, height),
gimp_text_layer_get_format (layer));
gimp_drawable_set_buffer (drawable, FALSE, NULL, new_buffer);
g_object_unref (new_buffer);
if (gimp_layer_get_mask (GIMP_LAYER (layer)))
{
GimpLayerMask *mask = gimp_layer_get_mask (GIMP_LAYER (layer));
static GimpContext *unused_eek = NULL;
if (! unused_eek)
unused_eek = gimp_context_new (image->gimp, "eek", NULL);
gimp_item_resize (GIMP_ITEM (mask),
unused_eek, GIMP_FILL_TRANSPARENT,
width, height, 0, 0);
}
}
if (layer->text->text)
{
name = gimp_utf8_strtrim (layer->text->text, 30);
}
else if (layer->text->markup)
{
gchar *tmp = gimp_markup_extract_text (layer->text->markup);
name = gimp_utf8_strtrim (tmp, 30);
g_free (tmp);
}
gimp_rasterizable_auto_rename (GIMP_RASTERIZABLE (layer), NULL, name);
g_free (name);
if (width > 0 && height > 0)
gimp_text_layer_render_layout (layer, layout);
g_object_unref (layout);
g_object_thaw_notify (G_OBJECT (drawable));
return (width > 0 && height > 0);
}
static void
gimp_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 *
gimp_temp_buf_create_cairo_surface (GimpTempBuf *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 = gimp_temp_buf_get_data (temp_buf);
format = gimp_temp_buf_get_format (temp_buf);
width = gimp_temp_buf_get_width (temp_buf);
height = gimp_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
gimp_text_layer_render_layout (GimpTextLayer *layer,
GimpTextLayout *layout)
{
GimpDrawable *drawable = GIMP_DRAWABLE (layer);
GimpItem *item = GIMP_ITEM (layer);
const Babl *format;
GeglBuffer *buffer;
cairo_t *cr;
cairo_surface_t *surface;
gint width;
gint height;
cairo_status_t status;
g_return_if_fail (gimp_drawable_has_alpha (drawable));
width = gimp_item_get_width (item);
height = gimp_item_get_height (item);
#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 17, 2)
surface = cairo_image_surface_create (CAIRO_FORMAT_RGBA128F, width, height);
#else
surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
#endif
status = cairo_surface_status (surface);
if (status != CAIRO_STATUS_SUCCESS)
{
GimpImage *image = gimp_item_get_image (item);
gimp_message_literal (image->gimp, NULL, GIMP_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 != GIMP_TEXT_OUTLINE_STROKE_ONLY)
{
cairo_save (cr);
gimp_text_layout_render (layout, cr, layer->text->base_dir, FALSE);
cairo_restore (cr);
}
if (layer->text->outline != GIMP_TEXT_OUTLINE_NONE)
{
GimpText *text = layer->text;
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 == GIMP_CAP_BUTT ? CAIRO_LINE_CAP_BUTT :
text->outline_cap_style == GIMP_CAP_ROUND ? CAIRO_LINE_CAP_ROUND :
CAIRO_LINE_CAP_SQUARE);
cairo_set_line_join (cr, text->outline_join_style == GIMP_JOIN_MITER ? CAIRO_LINE_JOIN_MITER :
text->outline_join_style == GIMP_JOIN_ROUND ? CAIRO_LINE_JOIN_ROUND :
CAIRO_LINE_JOIN_BEVEL);
cairo_set_miter_limit (cr, text->outline_miter_limit);
if (text->outline_dash_info)
gimp_text_layer_set_dash_info (cr, text->outline_width, text->outline_dash_offset, text->outline_dash_info);
if (text->outline_style == GIMP_CUSTOM_STYLE_PATTERN && text->outline_pattern)
{
GimpTempBuf *tempbuf = gimp_pattern_get_mask (text->outline_pattern);
cairo_surface_t *surface = gimp_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
{
GeglColor *col = text->outline_foreground;
gdouble color[3];
format = gimp_text_layout_get_format (layout, "double");
gegl_color_get_pixel (col, format, color);
/* Text layout can be either grayscale or RGB without alpha. */
if (! babl_space_is_gray (babl_format_get_space (format)))
cairo_set_source_rgba (cr, color[0], color[1], color[2], 1.0);
else
cairo_set_source_rgba (cr, color[0], color[0], color[0], 1.0);
}
cairo_set_line_width (cr, text->outline_width * 2);
gimp_text_layout_render (layout, cr, text->base_dir, TRUE);
if (text->outline_direction == GIMP_TEXT_OUTLINE_DIRECTION_INNER)
cairo_clip_preserve (cr);
cairo_stroke_preserve (cr);
/* Clears inner outline if outline direction is outward */
if (text->outline_direction == GIMP_TEXT_OUTLINE_DIRECTION_OUTER &&
text->outline == GIMP_TEXT_OUTLINE_STROKE_ONLY)
{
cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
cairo_fill_preserve (cr);
}
cairo_restore (cr);
if (text->outline_direction == GIMP_TEXT_OUTLINE_DIRECTION_OUTER &&
text->outline != GIMP_TEXT_OUTLINE_STROKE_ONLY)
{
cairo_save (cr);
gimp_text_layout_render (layout, cr, layer->text->base_dir, FALSE);
cairo_restore (cr);
}
}
cairo_destroy (cr);
cairo_surface_flush (surface);
#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 17, 2)
/* The CAIRO_FORMAT_RGBA128F surface maps to the layout TRC and space. */
switch (gimp_text_layout_get_trc (layout))
{
case GIMP_TRC_LINEAR:
format = babl_format_with_space ("RaGaBaA float", gimp_text_layout_get_space (layout));
break;
case GIMP_TRC_NON_LINEAR:
format = babl_format_with_space ("R'aG'aB'aA float", gimp_text_layout_get_space (layout));
break;
case GIMP_TRC_PERCEPTUAL:
format = babl_format_with_space ("R~aG~aB~aA float", gimp_text_layout_get_space (layout));
break;
default:
g_return_if_reached ();
}
#else
format = babl_format_with_space ("cairo-ARGB32", gimp_text_layout_get_space (layout));
#endif
buffer = gimp_cairo_surface_get_buffer (surface, format, FALSE);
gimp_gegl_buffer_copy (buffer, NULL, GEGL_ABYSS_NONE,
gimp_drawable_get_buffer (drawable), NULL);
g_object_unref (buffer);
cairo_surface_destroy (surface);
gimp_drawable_update (drawable, 0, 0, width, height);
}