/* 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 * * 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 . */ #include "config.h" #include #include #include #include #include #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); }