From ca6b58e970ee5d6089488cecb18a43e531962fed Mon Sep 17 00:00:00 2001 From: Alx Sa Date: Mon, 3 Oct 2022 19:45:52 +0000 Subject: [PATCH] text: Add outline options to text editor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This ports Massimo’s code to work in the latest version of GIMP. It adds new outline-related properties to GimpText and GimpTextOptions. These are controlled via the Text Tool Editor. Cairo is currently used to draw the outline around the text. --- app/core/gimp-memsize.c | 21 +- app/text/gimptext-parasite.c | 3 +- app/text/gimptext-parasite.h | 1 + app/text/gimptext.c | 265 ++++++++++++++++++++- app/text/gimptext.h | 11 + app/text/gimptextlayer-xcf.c | 4 +- app/text/gimptextlayer.c | 174 +++++++++++++- app/text/text-enums.c | 6 +- app/text/text-enums.h | 6 +- app/tools/gimptextoptions.c | 435 ++++++++++++++++++++++++++++++++--- app/tools/gimptextoptions.h | 13 ++ app/tools/gimptexttool.c | 3 +- app/widgets/gimpfilleditor.c | 2 +- 13 files changed, 890 insertions(+), 54 deletions(-) diff --git a/app/core/gimp-memsize.c b/app/core/gimp-memsize.c index fc674d0842..c14436b490 100644 --- a/app/core/gimp-memsize.c +++ b/app/core/gimp-memsize.c @@ -243,6 +243,20 @@ gimp_g_value_get_memsize (GValue *value) memsize += gimp_string_get_memsize (array[i]); } } + else if (strcmp ("GimpValueArray", G_VALUE_TYPE_NAME (value)) == 0) + { + GimpValueArray *array = g_value_get_boxed (value); + + if (array) + { + gint n_values = gimp_value_array_length (array), i; + + memsize += /* sizeof (GimpValueArray) */ sizeof (GValue *) + 3 * sizeof (gint); + + for (i = 0; i < n_values; i++) + memsize += gimp_g_value_get_memsize (gimp_value_array_index (array, i)); + } + } else { g_printerr ("%s: unhandled boxed value type: %s\n", @@ -251,8 +265,11 @@ gimp_g_value_get_memsize (GValue *value) } else if (G_VALUE_HOLDS_OBJECT (value)) { - g_printerr ("%s: unhandled object value type: %s\n", - G_STRFUNC, G_VALUE_TYPE_NAME (value)); + if (strcmp ("GimpPattern", G_VALUE_TYPE_NAME (value)) == 0) + memsize += gimp_g_object_get_memsize (g_value_get_object (value)); + else + g_printerr ("%s: unhandled object value type: %s\n", + G_STRFUNC, G_VALUE_TYPE_NAME (value)); } return memsize + sizeof (GValue); diff --git a/app/text/gimptext-parasite.c b/app/text/gimptext-parasite.c index bc0f7d0363..b6a113013c 100644 --- a/app/text/gimptext-parasite.c +++ b/app/text/gimptext-parasite.c @@ -65,6 +65,7 @@ gimp_text_to_parasite (GimpText *text) GimpText * gimp_text_from_parasite (const GimpParasite *parasite, + Gimp *gimp, GError **error) { GimpText *text; @@ -76,7 +77,7 @@ gimp_text_from_parasite (const GimpParasite *parasite, gimp_text_parasite_name ()) == 0, NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); - text = g_object_new (GIMP_TYPE_TEXT, NULL); + text = g_object_new (GIMP_TYPE_TEXT, "gimp", gimp, NULL); parasite_data = (gchar *) gimp_parasite_get_data (parasite, ¶site_data_size); if (parasite_data) diff --git a/app/text/gimptext-parasite.h b/app/text/gimptext-parasite.h index 2e2847a497..eae1087b0d 100644 --- a/app/text/gimptext-parasite.h +++ b/app/text/gimptext-parasite.h @@ -25,6 +25,7 @@ const gchar * gimp_text_parasite_name (void) G_GNUC_CONST; GimpParasite * gimp_text_to_parasite (GimpText *text); GimpText * gimp_text_from_parasite (const GimpParasite *parasite, + Gimp *gimp, GError **error); const gchar * gimp_text_gdyntext_parasite_name (void) G_GNUC_CONST; diff --git a/app/text/gimptext.c b/app/text/gimptext.c index ed2f041294..b3ce6acc71 100644 --- a/app/text/gimptext.c +++ b/app/text/gimptext.c @@ -34,9 +34,14 @@ #include "text-types.h" +#include "core/gimp.h" #include "core/gimp-memsize.h" #include "core/gimp-utils.h" +#include "core/gimpcontainer.h" +#include "core/gimpdashpattern.h" +#include "core/gimpdatafactory.h" #include "core/gimpstrokeoptions.h" +#include "core/gimppattern.h" #include "gimptext.h" @@ -68,8 +73,22 @@ enum PROP_OFFSET_X, PROP_OFFSET_Y, PROP_BORDER, + + PROP_OUTLINE_STYLE, /* fill-options */ + PROP_OUTLINE_FOREGROUND, /* context */ + PROP_OUTLINE_PATTERN, /* context */ + PROP_OUTLINE_WIDTH, /* stroke-options */ + PROP_OUTLINE_UNIT, + PROP_OUTLINE_CAP_STYLE, + PROP_OUTLINE_JOIN_STYLE, + PROP_OUTLINE_MITER_LIMIT, + PROP_OUTLINE_ANTIALIAS, /* fill-options */ + PROP_OUTLINE_DASH_OFFSET, + PROP_OUTLINE_DASH_INFO, /* for backward compatibility */ - PROP_HINTING + PROP_HINTING, + + PROP_GIMP }; enum @@ -78,6 +97,18 @@ enum LAST_SIGNAL }; +static void gimp_text_config_iface_init (GimpConfigInterface *iface); +static gboolean gimp_text_serialize_property (GimpConfig *config, + guint property_id, + const GValue *value, + GParamSpec *pspec, + GimpConfigWriter *writer); +static gboolean gimp_text_deserialize_property (GimpConfig *config, + guint property_id, + GValue *value, + GParamSpec *pspec, + GScanner *scanner, + GTokenType *expected); static void gimp_text_finalize (GObject *object); static void gimp_text_get_property (GObject *object, @@ -96,7 +127,8 @@ static gint64 gimp_text_get_memsize (GimpObject *object, G_DEFINE_TYPE_WITH_CODE (GimpText, gimp_text, GIMP_TYPE_OBJECT, - G_IMPLEMENT_INTERFACE (GIMP_TYPE_CONFIG, NULL)) + G_IMPLEMENT_INTERFACE (GIMP_TYPE_CONFIG, + gimp_text_config_iface_init)) #define parent_class gimp_text_parent_class @@ -109,8 +141,10 @@ gimp_text_class_init (GimpTextClass *klass) GObjectClass *object_class = G_OBJECT_CLASS (klass); GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass); GimpRGB black; + GimpRGB gray; GimpMatrix2 identity; gchar *language; + GParamSpec *array_spec; text_signals[CHANGED] = g_signal_new ("changed", @@ -128,6 +162,7 @@ gimp_text_class_init (GimpTextClass *klass) gimp_object_class->get_memsize = gimp_text_get_memsize; gimp_rgba_set (&black, 0.0, 0.0, 0.0, GIMP_OPACITY_OPAQUE); + gimp_rgba_set (&gray, 0.75, 0.75, 0.75, GIMP_OPACITY_OPAQUE); gimp_matrix2_identity (&identity); GIMP_CONFIG_PROP_STRING (object_class, PROP_TEXT, @@ -298,12 +333,68 @@ gimp_text_class_init (GimpTextClass *klass) G_PARAM_CONSTRUCT | GIMP_PARAM_WRITABLE)); + GIMP_CONFIG_PROP_ENUM (object_class, PROP_OUTLINE_STYLE, + "outline-style", NULL, NULL, + GIMP_TYPE_FILL_STYLE, + GIMP_FILL_STYLE_SOLID, + GIMP_PARAM_STATIC_STRINGS); + GIMP_CONFIG_PROP_OBJECT (object_class, PROP_OUTLINE_PATTERN, + "outline-pattern", NULL, NULL, + GIMP_TYPE_PATTERN, + GIMP_PARAM_STATIC_STRINGS); + GIMP_CONFIG_PROP_RGB (object_class, PROP_OUTLINE_FOREGROUND, + "outline-foreground", NULL, NULL, + FALSE, &gray, + GIMP_PARAM_STATIC_STRINGS); + GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_OUTLINE_WIDTH, + "outline-width", NULL, NULL, + 0.0, 8192.0, 4.0, + GIMP_PARAM_STATIC_STRINGS | + GIMP_CONFIG_PARAM_DEFAULTS); + GIMP_CONFIG_PROP_ENUM (object_class, PROP_OUTLINE_CAP_STYLE, + "outline-cap-style", NULL, NULL, + GIMP_TYPE_CAP_STYLE, GIMP_CAP_BUTT, + GIMP_PARAM_STATIC_STRINGS); + GIMP_CONFIG_PROP_ENUM (object_class, PROP_OUTLINE_JOIN_STYLE, + "outline-join-style", NULL, NULL, + GIMP_TYPE_JOIN_STYLE, GIMP_JOIN_MITER, + GIMP_PARAM_STATIC_STRINGS); + GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_OUTLINE_MITER_LIMIT, + "outline-miter-limit", + NULL, NULL, + 0.0, 100.0, 10.0, + GIMP_PARAM_STATIC_STRINGS); + GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_OUTLINE_ANTIALIAS, + "outline-antialias", NULL, NULL, + TRUE, + GIMP_PARAM_STATIC_STRINGS); + GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_OUTLINE_DASH_OFFSET, + "outline-dash-offset", NULL, NULL, + 0.0, 2000.0, 0.0, + GIMP_PARAM_STATIC_STRINGS); + + array_spec = g_param_spec_double ("outline-dash-length", NULL, NULL, + 0.0, 2000.0, 1.0, GIMP_PARAM_READWRITE); + g_object_class_install_property (object_class, PROP_OUTLINE_DASH_INFO, + gimp_param_spec_value_array ("outline-dash-info", + NULL, NULL, + array_spec, + GIMP_PARAM_STATIC_STRINGS | + GIMP_CONFIG_PARAM_FLAGS)); + /* the old hinting options have been replaced by 'hint-style' */ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_HINTING, "hinting", NULL, NULL, TRUE, GIMP_PARAM_STATIC_STRINGS); + + g_object_class_install_property (object_class, PROP_GIMP, + g_param_spec_object ("gimp", NULL, NULL, + GIMP_TYPE_GIMP, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + } static void @@ -311,6 +402,13 @@ gimp_text_init (GimpText *text) { } +static void +gimp_text_config_iface_init (GimpConfigInterface *iface) +{ + iface->serialize_property = gimp_text_serialize_property; + iface->deserialize_property = gimp_text_deserialize_property; +} + static void gimp_text_finalize (GObject *object) { @@ -403,10 +501,48 @@ gimp_text_get_property (GObject *object, case PROP_OFFSET_Y: g_value_set_double (value, text->offset_y); break; + case PROP_OUTLINE_STYLE: + g_value_set_enum (value, text->outline_style); + break; + case PROP_OUTLINE_FOREGROUND: + g_value_set_boxed (value, &text->outline_foreground); + break; + case PROP_OUTLINE_PATTERN: + g_value_set_object (value, text->outline_pattern); + break; + case PROP_OUTLINE_WIDTH: + g_value_set_double (value, text->outline_width); + break; + case PROP_OUTLINE_CAP_STYLE: + g_value_set_enum (value, text->outline_cap_style); + break; + case PROP_OUTLINE_JOIN_STYLE: + g_value_set_enum (value, text->outline_join_style); + break; + case PROP_OUTLINE_MITER_LIMIT: + g_value_set_double (value, text->outline_miter_limit); + break; + case PROP_OUTLINE_ANTIALIAS: + g_value_set_boolean (value, text->outline_antialias); + break; + case PROP_OUTLINE_DASH_OFFSET: + g_value_set_double (value, text->outline_dash_offset); + break; + case PROP_OUTLINE_DASH_INFO: + { + GimpValueArray *value_array; + + value_array = gimp_dash_pattern_to_value_array (text->outline_dash_info); + g_value_take_boxed (value, value_array); + } + break; case PROP_HINTING: g_value_set_boolean (value, text->hint_style != GIMP_TEXT_HINT_STYLE_NONE); break; + case PROP_GIMP: + g_value_set_object (value, text->gimp); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -527,6 +663,50 @@ gimp_text_set_property (GObject *object, case PROP_OFFSET_Y: text->offset_y = g_value_get_double (value); break; + case PROP_OUTLINE_STYLE: + text->outline_style = g_value_get_enum (value); + break; + case PROP_OUTLINE_FOREGROUND: + color = g_value_get_boxed (value); + text->outline_foreground = *color; + break; + case PROP_OUTLINE_PATTERN: + { + GimpPattern *pattern = g_value_get_object (value); + + if (text->outline_pattern != pattern) + { + if (text->outline_pattern) + g_object_unref (text->outline_pattern); + + text->outline_pattern = pattern ? g_object_ref (pattern) : pattern; + } + break; + } + case PROP_OUTLINE_WIDTH: + text->outline_width = g_value_get_double (value); + break; + case PROP_OUTLINE_CAP_STYLE: + text->outline_cap_style = g_value_get_enum (value); + break; + case PROP_OUTLINE_JOIN_STYLE: + text->outline_join_style = g_value_get_enum (value); + break; + case PROP_OUTLINE_MITER_LIMIT: + text->outline_miter_limit = g_value_get_double (value); + break; + case PROP_OUTLINE_ANTIALIAS: + text->outline_antialias = g_value_get_boolean (value); + break; + case PROP_OUTLINE_DASH_OFFSET: + text->outline_dash_offset = g_value_get_double (value); + break; + case PROP_OUTLINE_DASH_INFO: + { + GimpValueArray *value_array = g_value_get_boxed (value); + text->outline_dash_info = gimp_dash_pattern_from_value_array (value_array); + } + break; case PROP_BORDER: text->border = g_value_get_int (value); break; @@ -540,6 +720,8 @@ gimp_text_set_property (GObject *object, GIMP_TEXT_HINT_STYLE_MEDIUM : GIMP_TEXT_HINT_STYLE_NONE); break; + case PROP_GIMP: + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -592,3 +774,82 @@ gimp_text_get_transformation (GimpText *text, matrix->coeff[2][1] = 0.0; matrix->coeff[2][2] = 1.0; } + +static gboolean +gimp_text_serialize_property (GimpConfig *config, + guint property_id, + const GValue *value, + GParamSpec *pspec, + GimpConfigWriter *writer) +{ + if (property_id == PROP_OUTLINE_PATTERN) + { + GimpObject *serialize_obj = g_value_get_object (value); + + gimp_config_writer_open (writer, pspec->name); + + if (serialize_obj) + gimp_config_writer_string (writer, gimp_object_get_name (serialize_obj)); + else + gimp_config_writer_print (writer, "NULL", 4); + + gimp_config_writer_close (writer); + + return TRUE; + } + + return FALSE; +} + +static gboolean +gimp_text_deserialize_property (GimpConfig *object, + guint property_id, + GValue *value, + GParamSpec *pspec, + GScanner *scanner, + GTokenType *expected) +{ + if (property_id == PROP_OUTLINE_PATTERN) + { + gchar *object_name; + + if (gimp_scanner_parse_identifier (scanner, "NULL")) + { + g_value_set_object (value, NULL); + } + else if (gimp_scanner_parse_string (scanner, &object_name)) + { + GimpText *text = GIMP_TEXT (object); + GimpContainer *container; + GimpObject *deserialize_obj; + + if (! object_name) + object_name = g_strdup (""); + + container = gimp_data_factory_get_container (text->gimp->pattern_factory); + + deserialize_obj = gimp_container_get_child_by_name (container, + object_name); + + g_value_set_object (value, deserialize_obj); + + g_free (object_name); + } + else + { + *expected = G_TOKEN_STRING; + } + + return TRUE; + } + else if (property_id == PROP_OUTLINE_DASH_INFO) + { + if (gimp_scanner_parse_identifier (scanner, "NULL")) + { + g_value_take_boxed (value, NULL); + return TRUE; + } + } + + return FALSE; +} diff --git a/app/text/gimptext.h b/app/text/gimptext.h index 8a292be896..4ca3b333be 100644 --- a/app/text/gimptext.h +++ b/app/text/gimptext.h @@ -50,6 +50,16 @@ struct _GimpText gchar *language; GimpTextDirection base_dir; GimpRGB color; + GimpFillStyle outline_style; + GimpPattern *outline_pattern; + GimpRGB outline_foreground; + gdouble outline_width; + GimpCapStyle outline_cap_style; + GimpJoinStyle outline_join_style; + gdouble outline_miter_limit; + gboolean outline_antialias; + gdouble outline_dash_offset; + GArray *outline_dash_info; GimpTextOutline outline; GimpTextJustification justify; gdouble indent; @@ -64,6 +74,7 @@ struct _GimpText gdouble offset_y; gdouble border; + Gimp *gimp; }; struct _GimpTextClass diff --git a/app/text/gimptextlayer-xcf.c b/app/text/gimptextlayer-xcf.c index d0961ee148..8a93d0f8e8 100644 --- a/app/text/gimptextlayer-xcf.c +++ b/app/text/gimptextlayer-xcf.c @@ -69,7 +69,9 @@ gimp_text_layer_xcf_load_hack (GimpLayer **layer) { GError *error = NULL; - text = gimp_text_from_parasite (parasite, &error); + text = gimp_text_from_parasite (parasite, + gimp_item_get_image (GIMP_ITEM (*layer))->gimp, + &error); if (error) { diff --git a/app/text/gimptextlayer.c b/app/text/gimptextlayer.c index 002cec1904..c3ddb29ec9 100644 --- a/app/text/gimptextlayer.c +++ b/app/text/gimptextlayer.c @@ -39,8 +39,8 @@ #include "core/gimp.h" #include "core/gimp-utils.h" -#include "core/gimpcontext.h" #include "core/gimpcontainer.h" +#include "core/gimpcontext.h" #include "core/gimpdatafactory.h" #include "core/gimpimage.h" #include "core/gimpimage-color-profile.h" @@ -48,6 +48,8 @@ #include "core/gimpimage-undo-push.h" #include "core/gimpitemtree.h" #include "core/gimpparasitelist.h" +#include "core/gimppattern.h" +#include "core/gimptempbuf.h" #include "gimptext.h" #include "gimptextlayer.h" @@ -799,6 +801,121 @@ gimp_text_layer_render (GimpTextLayer *layer) 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) @@ -834,7 +951,60 @@ gimp_text_layer_render_layout (GimpTextLayer *layer, } cr = cairo_create (surface); - gimp_text_layout_render (layout, cr, layer->text->base_dir, FALSE); + 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; + GimpRGB 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 == 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_FILL_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 + { + cairo_set_source_rgba (cr, col.r, col.g, col.b, col.a); + } + + cairo_set_line_width (cr, text->outline_width * 2); + + gimp_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); diff --git a/app/text/text-enums.c b/app/text/text-enums.c index ab9623838b..271b2065dd 100644 --- a/app/text/text-enums.c +++ b/app/text/text-enums.c @@ -51,9 +51,9 @@ gimp_text_outline_get_type (void) static const GimpEnumDesc descs[] = { - { GIMP_TEXT_OUTLINE_NONE, "GIMP_TEXT_OUTLINE_NONE", NULL }, - { GIMP_TEXT_OUTLINE_STROKE_ONLY, "GIMP_TEXT_OUTLINE_STROKE_ONLY", NULL }, - { GIMP_TEXT_OUTLINE_STROKE_FILL, "GIMP_TEXT_OUTLINE_STROKE_FILL", NULL }, + { GIMP_TEXT_OUTLINE_NONE, NC_("text-outline", "Filled"), NULL }, + { GIMP_TEXT_OUTLINE_STROKE_ONLY, NC_("text-outline", "Outlined"), NULL }, + { GIMP_TEXT_OUTLINE_STROKE_FILL, NC_("text-outline", "Outlined and filled"), NULL }, { 0, NULL, NULL } }; diff --git a/app/text/text-enums.h b/app/text/text-enums.h index 20bd327d63..f032ce690b 100644 --- a/app/text/text-enums.h +++ b/app/text/text-enums.h @@ -36,9 +36,9 @@ GType gimp_text_outline_get_type (void) G_GNUC_CONST; typedef enum { - GIMP_TEXT_OUTLINE_NONE, - GIMP_TEXT_OUTLINE_STROKE_ONLY, - GIMP_TEXT_OUTLINE_STROKE_FILL + GIMP_TEXT_OUTLINE_NONE, /*< desc="Filled" >*/ + GIMP_TEXT_OUTLINE_STROKE_ONLY, /*< desc="Outlined" >*/ + GIMP_TEXT_OUTLINE_STROKE_FILL /*< desc="Outlined and filled" >*/ } GimpTextOutline; diff --git a/app/tools/gimptextoptions.c b/app/tools/gimptextoptions.c index 48a75ee187..7a25ccf85e 100644 --- a/app/tools/gimptextoptions.c +++ b/app/tools/gimptextoptions.c @@ -21,6 +21,7 @@ #include #include "libgimpbase/gimpbase.h" +#include "libgimpcolor/gimpcolor.h" #include "libgimpconfig/gimpconfig.h" #include "libgimpwidgets/gimpwidgets.h" @@ -29,7 +30,11 @@ #include "config/gimpconfig-utils.h" #include "core/gimp.h" +#include "core/gimpcontainer.h" +#include "core/gimpdashpattern.h" #include "core/gimpdatafactory.h" +#include "core/gimppattern.h" +#include "core/gimpstrokeoptions.h" #include "core/gimptoolinfo.h" #include "core/gimpviewable.h" @@ -38,6 +43,7 @@ #include "widgets/gimpcolorpanel.h" #include "widgets/gimpmenufactory.h" #include "widgets/gimppropwidgets.h" +#include "widgets/gimpstrokeeditor.h" #include "widgets/gimptextbuffer.h" #include "widgets/gimptexteditor.h" #include "widgets/gimpviewablebox.h" @@ -64,6 +70,18 @@ enum PROP_LETTER_SPACING, PROP_BOX_MODE, + PROP_OUTLINE, + PROP_OUTLINE_STYLE, /* fill-options */ + PROP_OUTLINE_FOREGROUND, /* context */ + PROP_OUTLINE_PATTERN, /* context */ + PROP_OUTLINE_WIDTH, /* stroke-options */ + PROP_OUTLINE_UNIT, + PROP_OUTLINE_CAP_STYLE, + PROP_OUTLINE_JOIN_STYLE, + PROP_OUTLINE_MITER_LIMIT, + PROP_OUTLINE_ANTIALIAS, /* fill-options */ + PROP_OUTLINE_DASH_OFFSET, + PROP_OUTLINE_DASH_INFO, PROP_USE_EDITOR, PROP_FONT_VIEW_TYPE, @@ -71,32 +89,49 @@ enum }; -static void gimp_text_options_config_iface_init (GimpConfigInterface *config_iface); +static void + gimp_text_options_config_iface_init (GimpConfigInterface *config_iface); +static gboolean + gimp_text_options_serialize_property (GimpConfig *config, + guint property_id, + const GValue *value, + GParamSpec *pspec, + GimpConfigWriter *writer); +static gboolean + gimp_text_options_deserialize_property (GimpConfig *config, + guint property_id, + GValue *value, + GParamSpec *pspec, + GScanner *scanner, + GTokenType *expected); -static void gimp_text_options_finalize (GObject *object); -static void gimp_text_options_set_property (GObject *object, - guint property_id, - const GValue *value, - GParamSpec *pspec); -static void gimp_text_options_get_property (GObject *object, - guint property_id, - GValue *value, - GParamSpec *pspec); +static void gimp_text_options_finalize (GObject *object); +static void gimp_text_options_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_text_options_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); -static void gimp_text_options_reset (GimpConfig *config); +static void gimp_text_options_reset (GimpConfig *config); + +static void gimp_text_options_notify_font (GimpContext *context, + GParamSpec *pspec, + GimpText *text); +static void gimp_text_options_notify_text_font (GimpText *text, + GParamSpec *pspec, + GimpContext *context); +static void gimp_text_options_notify_color (GimpContext *context, + GParamSpec *pspec, + GimpText *text); +static void gimp_text_options_notify_text_color (GimpText *text, + GParamSpec *pspec, + GimpContext *context); +static void gimp_text_options_outline_changed (GtkWidget *combo, + GtkWidget *vbox); -static void gimp_text_options_notify_font (GimpContext *context, - GParamSpec *pspec, - GimpText *text); -static void gimp_text_options_notify_text_font (GimpText *text, - GParamSpec *pspec, - GimpContext *context); -static void gimp_text_options_notify_color (GimpContext *context, - GParamSpec *pspec, - GimpText *text); -static void gimp_text_options_notify_text_color (GimpText *text, - GParamSpec *pspec, - GimpContext *context); G_DEFINE_TYPE_WITH_CODE (GimpTextOptions, gimp_text_options, @@ -113,7 +148,10 @@ static void gimp_text_options_class_init (GimpTextOptionsClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpRGB gray; + GParamSpec *array_spec; + gimp_rgba_set (&gray, 0.75, 0.75, 0.75, GIMP_OPACITY_OPAQUE); object_class->finalize = gimp_text_options_finalize; object_class->set_property = gimp_text_options_set_property; object_class->get_property = gimp_text_options_get_property; @@ -224,6 +262,82 @@ gimp_text_options_class_init (GimpTextOptionsClass *klass) GIMP_VIEWABLE_MAX_BUTTON_SIZE, GIMP_VIEW_SIZE_SMALL, GIMP_PARAM_STATIC_STRINGS); + GIMP_CONFIG_PROP_ENUM (object_class, PROP_OUTLINE, + "outline", + NULL, NULL, + GIMP_TYPE_TEXT_OUTLINE, + GIMP_TEXT_OUTLINE_NONE, + GIMP_PARAM_STATIC_STRINGS | + GIMP_CONFIG_PARAM_DEFAULTS); + GIMP_CONFIG_PROP_ENUM (object_class, PROP_OUTLINE_STYLE, + "outline-style", + NULL, NULL, + GIMP_TYPE_FILL_STYLE, + GIMP_FILL_STYLE_SOLID, + GIMP_PARAM_STATIC_STRINGS); + GIMP_CONFIG_PROP_RGB (object_class, PROP_OUTLINE_FOREGROUND, + "outline-foreground", + NULL, NULL, + FALSE, &gray, + GIMP_PARAM_STATIC_STRINGS); + GIMP_CONFIG_PROP_OBJECT (object_class, PROP_OUTLINE_PATTERN, + "outline-pattern", + NULL, NULL, + GIMP_TYPE_PATTERN, + GIMP_PARAM_STATIC_STRINGS); + GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_OUTLINE_WIDTH, + "outline-width", + _("Outline width"), + _("Adjust outline width"), + 0, 8192.0, 2.0, + GIMP_PARAM_STATIC_STRINGS | + GIMP_CONFIG_PARAM_DEFAULTS); + GIMP_CONFIG_PROP_UNIT (object_class, PROP_OUTLINE_UNIT, + "outline-unit", + _("Unit"), + _("Outline width unit"), + TRUE, FALSE, GIMP_UNIT_PIXEL, + GIMP_PARAM_STATIC_STRINGS); + GIMP_CONFIG_PROP_ENUM (object_class, PROP_OUTLINE_CAP_STYLE, + "outline-cap-style", + NULL, NULL, + GIMP_TYPE_CAP_STYLE, GIMP_CAP_BUTT, + GIMP_PARAM_STATIC_STRINGS); + GIMP_CONFIG_PROP_ENUM (object_class, PROP_OUTLINE_JOIN_STYLE, + "outline-join-style", + NULL, NULL, + GIMP_TYPE_JOIN_STYLE, GIMP_JOIN_MITER, + GIMP_PARAM_STATIC_STRINGS); + GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_OUTLINE_MITER_LIMIT, + "outline-miter-limit", + _("Outline miter limit"), + _("Convert a mitered join to a bevelled " + "join if the miter would extend to a " + "distance of more than miter-limit * " + "line-width from the actual join point."), + 0.0, 100.0, 10.0, + GIMP_PARAM_STATIC_STRINGS); + GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_OUTLINE_ANTIALIAS, + "outline-antialias", + NULL, NULL, + TRUE, + GIMP_PARAM_STATIC_STRINGS); + GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_OUTLINE_DASH_OFFSET, + "outline-dash-offset", + NULL, NULL, + 0.0, 2000.0, 0.0, + GIMP_PARAM_STATIC_STRINGS); + + array_spec = g_param_spec_double ("outline-dash-length", + NULL, NULL, + 0.0, 2000.0, 1.0, + GIMP_PARAM_READWRITE); + g_object_class_install_property (object_class, PROP_OUTLINE_DASH_INFO, + gimp_param_spec_value_array ("outline-dash-info", + NULL, NULL, + array_spec, + GIMP_PARAM_STATIC_STRINGS | + GIMP_CONFIG_PARAM_FLAGS)); } static void @@ -232,6 +346,9 @@ gimp_text_options_config_iface_init (GimpConfigInterface *config_iface) parent_config_iface = g_type_interface_peek_parent (config_iface); config_iface->reset = gimp_text_options_reset; + + config_iface->serialize_property = gimp_text_options_serialize_property; + config_iface->deserialize_property = gimp_text_options_deserialize_property; } static void @@ -294,6 +411,48 @@ gimp_text_options_get_property (GObject *object, g_value_set_enum (value, options->box_mode); break; + case PROP_OUTLINE: + g_value_set_enum (value, options->outline); + break; + case PROP_OUTLINE_STYLE: + g_value_set_enum (value, options->outline_style); + break; + case PROP_OUTLINE_FOREGROUND: + g_value_set_boxed (value, &options->outline_foreground); + break; + case PROP_OUTLINE_PATTERN: + g_value_set_object (value, options->outline_pattern); + break; + case PROP_OUTLINE_WIDTH: + g_value_set_double (value, options->outline_width); + break; + case PROP_OUTLINE_UNIT: + g_value_set_int (value, options->outline_unit); + break; + case PROP_OUTLINE_CAP_STYLE: + g_value_set_enum (value, options->outline_cap_style); + break; + case PROP_OUTLINE_JOIN_STYLE: + g_value_set_enum (value, options->outline_join_style); + break; + case PROP_OUTLINE_MITER_LIMIT: + g_value_set_double (value, options->outline_miter_limit); + break; + case PROP_OUTLINE_ANTIALIAS: + g_value_set_boolean (value, options->outline_antialias); + break; + case PROP_OUTLINE_DASH_OFFSET: + g_value_set_double (value, options->outline_dash_offset); + break; + case PROP_OUTLINE_DASH_INFO: + { + GimpValueArray *value_array; + + value_array = gimp_dash_pattern_to_value_array (options->outline_dash_info); + g_value_take_boxed (value, value_array); + } + break; + case PROP_USE_EDITOR: g_value_set_boolean (value, options->use_editor); break; @@ -318,6 +477,7 @@ gimp_text_options_set_property (GObject *object, GParamSpec *pspec) { GimpTextOptions *options = GIMP_TEXT_OPTIONS (object); + GimpRGB *color; switch (property_id) { @@ -356,6 +516,57 @@ gimp_text_options_set_property (GObject *object, options->box_mode = g_value_get_enum (value); break; + case PROP_OUTLINE: + options->outline = g_value_get_enum (value); + break; + case PROP_OUTLINE_STYLE: + options->outline_style = g_value_get_enum (value); + break; + case PROP_OUTLINE_FOREGROUND: + color = g_value_get_boxed (value); + options->outline_foreground = *color; + break; + case PROP_OUTLINE_PATTERN: + { + GimpPattern *pattern = g_value_get_object (value); + + if (options->outline_pattern != pattern) + { + if (options->outline_pattern) + g_object_unref (options->outline_pattern); + + options->outline_pattern = pattern ? g_object_ref (pattern) : pattern; + } + break; + } + case PROP_OUTLINE_WIDTH: + options->outline_width = g_value_get_double (value); + break; + case PROP_OUTLINE_UNIT: + options->outline_unit = g_value_get_int (value); + break; + case PROP_OUTLINE_CAP_STYLE: + options->outline_cap_style = g_value_get_enum (value); + break; + case PROP_OUTLINE_JOIN_STYLE: + options->outline_join_style = g_value_get_enum (value); + break; + case PROP_OUTLINE_MITER_LIMIT: + options->outline_miter_limit = g_value_get_double (value); + break; + case PROP_OUTLINE_ANTIALIAS: + options->outline_antialias = g_value_get_boolean (value); + break; + case PROP_OUTLINE_DASH_OFFSET: + options->outline_dash_offset = g_value_get_double (value); + break; + case PROP_OUTLINE_DASH_INFO: + { + GimpValueArray *value_array = g_value_get_boxed (value); + options->outline_dash_info = gimp_dash_pattern_from_value_array (value_array); + } + break; + case PROP_USE_EDITOR: options->use_editor = g_value_get_boolean (value); break; @@ -399,6 +610,20 @@ gimp_text_options_reset (GimpConfig *config) gimp_config_reset_property (object, "line-spacing"); gimp_config_reset_property (object, "letter-spacing"); gimp_config_reset_property (object, "box-mode"); + + gimp_config_reset_property (object, "outline"); + gimp_config_reset_property (object, "outline-style"); + gimp_config_reset_property (object, "outline-foreground"); + gimp_config_reset_property (object, "outline-pattern"); + gimp_config_reset_property (object, "outline-width"); + gimp_config_reset_property (object, "outline-unit"); + gimp_config_reset_property (object, "outline-cap-style"); + gimp_config_reset_property (object, "outline-join-style"); + gimp_config_reset_property (object, "outline-miter-limit"); + gimp_config_reset_property (object, "outline-antialias"); + gimp_config_reset_property (object, "outline-dash-offset"); + gimp_config_reset_property (object, "outline-dash-info"); + gimp_config_reset_property (object, "use-editor"); } @@ -510,21 +735,24 @@ gimp_text_options_connect_text (GimpTextOptions *options, GtkWidget * gimp_text_options_gui (GimpToolOptions *tool_options) { - GObject *config = G_OBJECT (tool_options); - GimpTextOptions *options = GIMP_TEXT_OPTIONS (tool_options); - GtkWidget *main_vbox = gimp_tool_options_gui (tool_options); - GimpAsyncSet *async_set; - GtkWidget *options_vbox; - GtkWidget *grid; - GtkWidget *vbox; - GtkWidget *hbox; - GtkWidget *button; - GtkWidget *entry; - GtkWidget *box; - GtkWidget *spinbutton; - GtkWidget *combo; - GtkSizeGroup *size_group; - gint row = 0; + GObject *config = G_OBJECT (tool_options); + GimpTextOptions *options = GIMP_TEXT_OPTIONS (tool_options); + GtkWidget *main_vbox = gimp_tool_options_gui (tool_options); + GimpAsyncSet *async_set; + GtkWidget *options_vbox; + GtkWidget *grid; + GtkWidget *vbox; + GtkWidget *hbox; + GtkWidget *button; + GtkWidget *entry; + GtkWidget *box; + GtkWidget *spinbutton; + GtkWidget *combo; + GtkWidget *editor; + GimpStrokeOptions *stroke_options; + GtkWidget *outline_frame; + GtkSizeGroup *size_group; + gint row = 0; async_set = gimp_data_factory_get_async_set (tool_options->tool_info->gimp->font_factory); @@ -605,6 +833,29 @@ gimp_text_options_gui (GimpToolOptions *tool_options) button, 1); gtk_size_group_add_widget (size_group, button); + button = gimp_prop_enum_combo_box_new (config, "outline", -1, -1); + gimp_grid_attach_aligned (GTK_GRID (grid), 0, row++, + _("Style:"), 0.0, 0.5, + button, 1); + gtk_size_group_add_widget (size_group, button); + + outline_frame = gimp_frame_new (_("Outline Options")); + gtk_box_pack_start (GTK_BOX (options_vbox), outline_frame, FALSE, FALSE, 0); + gtk_widget_show (outline_frame); + + g_signal_connect (button, "changed", + G_CALLBACK (gimp_text_options_outline_changed), + outline_frame); + gimp_text_options_outline_changed (button, outline_frame); + + grid = gtk_grid_new (); + gtk_grid_set_column_spacing (GTK_GRID (grid), 2); + gtk_grid_set_row_spacing (GTK_GRID (grid), 2); + gtk_box_pack_start (GTK_BOX (options_vbox), grid, FALSE, FALSE, 0); + gtk_widget_show (grid); + + row = 0; + box = gimp_prop_enum_icon_box_new (config, "justify", "format-justify", 0, 0); gtk_widget_set_halign (box, GTK_ALIGN_START); gimp_grid_attach_aligned (GTK_GRID (grid), 0, row++, @@ -640,6 +891,29 @@ gimp_text_options_gui (GimpToolOptions *tool_options) gimp_grid_attach_aligned (GTK_GRID (grid), 0, row++, _("Box:"), 0.0, 0.5, combo, 1); + stroke_options = gimp_stroke_options_new (GIMP_CONTEXT (options)->gimp, + NULL, FALSE); +#define BIND(a) \ + g_object_bind_property (options, "outline-" #a, \ + stroke_options, #a, \ + G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE) + BIND (style); + BIND (foreground); + BIND (pattern); + BIND (width); + BIND (unit); + BIND (cap-style); + BIND (join-style); + BIND (miter-limit); + BIND (antialias); + BIND (dash-offset); + BIND (dash-info); + + editor = gimp_stroke_editor_new (stroke_options, 72.0, TRUE); + gtk_container_add (GTK_CONTAINER (outline_frame), editor); + gtk_widget_show (editor); + + g_object_unref (stroke_options); /* Only add the language entry if the iso-codes package is available. */ @@ -702,6 +976,20 @@ gimp_text_options_editor_notify_font (GimpTextOptions *options, gimp_text_editor_set_font_name (editor, font_name); } +static void +gimp_text_options_outline_changed (GtkWidget *combo, + GtkWidget *vbox) +{ + GimpTextOutline active; + + active = (GimpTextOutline) gtk_combo_box_get_active (GTK_COMBO_BOX (combo)); + + if (active == GIMP_TEXT_OUTLINE_NONE) + gtk_widget_hide (vbox); + else + gtk_widget_show (vbox); +} + GtkWidget * gimp_text_options_editor_new (GtkWindow *parent, Gimp *gimp, @@ -745,3 +1033,74 @@ gimp_text_options_editor_new (GtkWindow *parent, return editor; } + +static gboolean +gimp_text_options_serialize_property (GimpConfig *config, + guint property_id, + const GValue *value, + GParamSpec *pspec, + GimpConfigWriter *writer) +{ + if (property_id == PROP_OUTLINE_PATTERN) + { + GimpObject *serialize_obj = g_value_get_object (value); + + gimp_config_writer_open (writer, pspec->name); + + if (serialize_obj) + gimp_config_writer_string (writer, gimp_object_get_name (serialize_obj)); + else + gimp_config_writer_print (writer, "NULL", 4); + + gimp_config_writer_close (writer); + + return TRUE; + } + + return FALSE; +} + +static gboolean +gimp_text_options_deserialize_property (GimpConfig *object, + guint property_id, + GValue *value, + GParamSpec *pspec, + GScanner *scanner, + GTokenType *expected) +{ + if (property_id == PROP_OUTLINE_PATTERN) + { + gchar *object_name; + + if (gimp_scanner_parse_identifier (scanner, "NULL")) + { + g_value_set_object (value, NULL); + } + else if (gimp_scanner_parse_string (scanner, &object_name)) + { + GimpContext *context = GIMP_CONTEXT (object); + GimpContainer *container; + GimpObject *deserialize_obj; + + if (! object_name) + object_name = g_strdup (""); + + container = gimp_data_factory_get_container (context->gimp->pattern_factory); + + deserialize_obj = gimp_container_get_child_by_name (container, + object_name); + + g_value_set_object (value, deserialize_obj); + + g_free (object_name); + } + else + { + *expected = G_TOKEN_STRING; + } + + return TRUE; + } + + return FALSE; +} diff --git a/app/tools/gimptextoptions.h b/app/tools/gimptextoptions.h index d80c21f5f8..b45d8625d8 100644 --- a/app/tools/gimptextoptions.h +++ b/app/tools/gimptextoptions.h @@ -49,6 +49,19 @@ struct _GimpTextOptions gdouble letter_spacing; GimpTextBoxMode box_mode; + GimpTextOutline outline; + GimpFillStyle outline_style; + GimpRGB outline_foreground; + GimpPattern *outline_pattern; + gdouble outline_width; + GimpUnit outline_unit; + GimpCapStyle outline_cap_style; + GimpJoinStyle outline_join_style; + gdouble outline_miter_limit; + gboolean outline_antialias; + gdouble outline_dash_offset; + GArray *outline_dash_info; + GimpViewType font_view_type; GimpViewSize font_view_size; diff --git a/app/tools/gimptexttool.c b/app/tools/gimptexttool.c index bc8309aa3e..0202d4cfa0 100644 --- a/app/tools/gimptexttool.c +++ b/app/tools/gimptexttool.c @@ -2117,7 +2117,8 @@ gimp_text_tool_apply (GimpTextTool *text_tool, GIMP_DRAWABLE (layer), TRUE); } - gimp_image_undo_push_text_layer (image, NULL, layer, pspec); + if (pspec) + gimp_image_undo_push_text_layer (image, NULL, layer, pspec); } gimp_text_tool_apply_list (text_tool, list); diff --git a/app/widgets/gimpfilleditor.c b/app/widgets/gimpfilleditor.c index 50d110e150..0f16ccf4d4 100644 --- a/app/widgets/gimpfilleditor.c +++ b/app/widgets/gimpfilleditor.c @@ -121,7 +121,7 @@ gimp_fill_editor_constructed (GObject *object) color_button = gimp_prop_color_button_new (G_OBJECT (editor->options), "foreground", _("Fill Color"), - -1, 24, + 1, 24, GIMP_COLOR_AREA_SMALL_CHECKS); gimp_color_panel_set_context (GIMP_COLOR_PANEL (color_button), GIMP_CONTEXT (editor->options));