[gimp] plug-ins: add the new file-pdf-save plugin (Bug #382688)
- From: Barak Itkin <barakitkin src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gimp] plug-ins: add the new file-pdf-save plugin (Bug #382688)
- Date: Fri, 27 Aug 2010 21:10:29 +0000 (UTC)
commit 6f8c412735637403063dfb929c3626d4d506d8d4
Author: Barak Itkin <lightningismyname gmail com>
Date: Fri Aug 27 23:22:10 2010 +0300
plug-ins: add the new file-pdf-save plugin (Bug #382688)
configure.ac | 25 +
plug-ins/common/.gitignore | 2 +
plug-ins/common/Makefile.am | 21 +
plug-ins/common/file-pdf-save.c | 1475 +++++++++++++++++++++++++++++++++++++++
plug-ins/common/plugin-defs.pl | 1 +
5 files changed, 1524 insertions(+), 0 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 5608494..b3cb09b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -46,6 +46,7 @@ m4_define([glib_required_version], [2.24.0])
m4_define([gtk_required_version], [2.20.0])
m4_define([gdk_pixbuf_required_version], [gtk_required_version])
m4_define([cairo_required_version], [1.8.0])
+m4_define([cairo_pdf_required_version], [1.8.0])
m4_define([pangocairo_required_version], [1.20.1])
m4_define([fontconfig_required_version], [2.2.0])
m4_define([gtkdoc_required_version], [1.0])
@@ -1328,6 +1329,29 @@ fi
AC_SUBST(FILE_PDF_LOAD)
+#####################
+# Check for cairo-pdf
+#####################
+
+AC_ARG_WITH(cairo_pdf,[ --without-cairo-pdf build without cairo-pdf support])
+have_cairo_pdf=no
+if test "x$with_cairo_pdf" != xno; then
+ have_cairo_pdf=yes
+ PKG_CHECK_MODULES(CAIRO_PDF, cairo-pdf >= cairo_pdf_required_version,
+ FILE_PDF_LOAD='file-pdf-load$(EXEEXT)',
+ have_cairo_pdf="no (cairo-pdf not found)")
+else
+ have_cairo_pdf="no (cairo-pdf support disabled)"
+fi
+
+AC_SUBST(FILE_PDF_SAVE)
+
+## If both poppler and cairo-pdf are enabled then we have complete PDF support
+if test "x$have_cairo_pdf" = xyes && test "x$have_poppler" = xyes; then
+ MIME_TYPES="$MIME_TYPES;application/pdf"
+fi
+
+
################################
# Check for gio/gvfs and libcurl
################################
@@ -2195,6 +2219,7 @@ Optional Plug-Ins:
JPEG 2000: $have_jp2
MNG: $have_libmng
PDF (import): $have_poppler
+ PDF (export): $have_cairo_pdf
PNG: $have_libpng
Print: $enable_print
PSP: $have_zlib
diff --git a/plug-ins/common/.gitignore b/plug-ins/common/.gitignore
index d94e5c1..11db5ef 100644
--- a/plug-ins/common/.gitignore
+++ b/plug-ins/common/.gitignore
@@ -132,6 +132,8 @@
/file-pcx.exe
/file-pdf-load
/file-pdf-load.exe
+/file-pdf-save
+/file-pdf-save.exe
/file-pix
/file-pix.exe
/file-png
diff --git a/plug-ins/common/Makefile.am b/plug-ins/common/Makefile.am
index 40eb6b7..a45f75d 100644
--- a/plug-ins/common/Makefile.am
+++ b/plug-ins/common/Makefile.am
@@ -102,6 +102,7 @@ libexec_PROGRAMS = \
file-pat \
file-pcx \
$(FILE_PDF_LOAD) \
+ $(FILE_PDF_SAVE) \
file-pix \
$(FILE_PNG) \
file-pnm \
@@ -187,6 +188,7 @@ EXTRA_PROGRAMS = \
file-jp2-load \
file-mng \
file-pdf-load \
+ file-pdf-save \
file-png \
file-psp \
file-svg \
@@ -1217,6 +1219,25 @@ file_pdf_load_LDADD = \
$(RT_LIBS) \
$(INTLLIBS)
+file_pdf_save_CFLAGS = $(CAIRO_PDF_CFLAGS)
+
+file_pdf_save_SOURCES = \
+ file-pdf-save.c
+
+file_pdf_save_LDADD = \
+ $(libgimpui) \
+ $(libgimpwidgets) \
+ $(libgimpmodule) \
+ $(libgimp) \
+ $(libgimpmath) \
+ $(libgimpconfig) \
+ $(libgimpcolor) \
+ $(libgimpbase) \
+ $(GTK_LIBS) \
+ $(CAIRO_PDF_LIBS) \
+ $(RT_LIBS) \
+ $(INTLLIBS)
+
file_pix_SOURCES = \
file-pix.c
diff --git a/plug-ins/common/file-pdf-save.c b/plug-ins/common/file-pdf-save.c
new file mode 100755
index 0000000..6dc12c0
--- /dev/null
+++ b/plug-ins/common/file-pdf-save.c
@@ -0,0 +1,1475 @@
+/* GIMP - The GNU Image Manipulation Program
+ *
+ * file-pdf-save.c - PDF file exporter, based on the cairo PDF surface
+ *
+ * Copyright (C) 2010 Barak Itkin <lightningismyname gmail com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+/* The PDF export plugin has 3 main procedures:
+ * 1. file-pdf-save
+ * This is the main procedure. It has 3 options for optimizations of
+ * the pdf file, and it can show a gui. This procedure works on a single
+ * image.
+ * 2. file-pdf-save-defaults
+ * This procedures is the one that will be invoked by gimp's file-save,
+ * when the pdf extension is chosen. If it's in RUN_INTERACTIVE, it will
+ * pop a user interface with more options, like file-pdf-save. If it's in
+ * RUN_NONINTERACTIVE, it will simply use the default values. Note that on
+ * RUN_WITH_LAST_VALS there will be no gui, however the values will be the
+ * ones that were used in the last interactive run (or the defaults if none
+ * are available.
+ * 3. file-pdf-save-multi
+ * This procedures is more advanced, and it allows the creation of multiple
+ * paged pdf files. It will be located in File/Create/Multiple page PDF...
+ *
+ * It was suggested that file-pdf-save-multi will be removed from the UI as it
+ * does not match the product vision (GIMP isn't a program for editing multiple
+ * paged documents).
+ */
+
+/* Known Issues (except for the coding style issues):
+ * 0. Need to add support for i18n.
+ * 1. Grayscale layers are inverted (although layer masks which are not grayscale,
+ * are not inverted)
+ * 2. Exporting some fonts doesn't work since gimp_text_layer_get_font Returns a
+ * font which is sometimes incompatiable with pango_font_description_from_string
+ * (gimp_text_layer_get_font sometimes returns suffixes such as "semi-expanded" to
+ * the font's name although the GIMP's font selection dialog shows the don'ts name
+ * normally - This should be checked again in GIMP 2.7)
+ * 3. Indexed layers can't be optimized yet (Since gimp_histogram won't work on
+ * indexed layers)
+ * 4. Rendering the pango layout requires multiplying the size in PANGO_SCALE. This
+ * means I'll need to do some hacking on the markup returned from GIMP.
+ * 5. When accessing the contents of layer groups is supported, we should do use it
+ * (since this plugin should preserve layers).
+ *
+ * Also, there are 2 things which we should warn the user about:
+ * 1. Cairo does not support bitmap masks for text.
+ * 2. Currently layer modes are ignored. We do support layers, including
+ * transparency and opacity, but layer modes are not supported.
+ */
+
+/* Changelog
+ *
+ * April 29, 2009 | Barak Itkin <lightningismyname gmail com>
+ * First version of the plugin. This is only a proof of concept and not a full
+ * working plugin.
+ *
+ * May 6, 2009 Barak | Itkin <lightningismyname gmail com>
+ * Added new features and several bugfixes:
+ * - Added handling for image resolutions
+ * - fixed the behaviour of getting font sizes
+ * - Added various optimizations (solid rectangles instead of bitmaps, ignoring
+ * invisible layers, etc.) as a macro flag.
+ * - Added handling for layer masks, use CAIRO_FORMAT_A8 for grayscale drawables.
+ * - Indexed layers are now supported
+ *
+ * August 17, 2009 | Barak Itkin <lightningismyname gmail com>
+ * Most of the plugin was rewritten from scratch and it now has several new
+ * features:
+ * - Got rid of the optimization macros in the code. The gui now allows to
+ * select which optimizations to apply.
+ * - Added a procedure to allow the creation of multiple paged PDF's
+ * - Registered the plugin on "<Image>/File/Create/PDF"
+ *
+ * August 21, 2009 | Barak Itkin <lightningismyname gmail com>
+ * Fixed a typo that prevented the plugin from compiling...
+ * A migration to the new GIMP 2.8 api, which includes:
+ * - Now using gimp_export_dialog_new
+ * - Using gimp_text_layer_get_hint_style (2.8) instead of the depreceated
+ * gimp_text_layer_get_hinting (2.6).
+ *
+ * August 24, 2010 | Barak Itkin <lightningismyname gmail com>
+ * More migrations to the new GIMP 2.8 api:
+ * - Now using the GimpItem api
+ * - Using gimp_text_layer_get_markup where possible
+ * - Fixed some compiler warnings
+ * Also merged the header and c file into one file, Updated some of the comments
+ * and documentation, and moved this into the main source repository.
+ */
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#include <gtk/gtk.h>
+
+#include <cairo.h>
+#include <cairo-pdf.h>
+#include <pango/pangocairo.h>
+#include <pango/pango.h>
+
+#define SAVE_PROC "file-pdf-save"
+#define SAVE_DEFAULTS_PROC "file-pdf-save-defaults"
+#define SAVE_MULTI_PROC "file-pdf-save-multi"
+#define PLUG_IN_BINARY "file-pdf-save"
+
+#define DATA_OPTIMIZE "file-pdf-data-optimize"
+#define DATA_IMAGE_LIST "file-pdf-data-multi-page"
+
+/* Gimp will crash before you reach this limitation :D */
+#define MAX_PAGE_COUNT 350
+#define MAX_FILE_NAME_LENGTH 350
+
+#define THUMB_WIDTH 90
+#define THUMB_HEIGHT 120
+
+#define PDF_DEBUG FALSE
+
+typedef struct {
+ gboolean convert;
+ gboolean ignore;
+ gboolean apply_masks;
+} PdfOptimize;
+
+typedef struct {
+ gint32 images[MAX_PAGE_COUNT];
+ guint32 image_count;
+ gchar file_name[MAX_FILE_NAME_LENGTH];
+} PdfMultiPage;
+
+typedef struct {
+ PdfOptimize optimize;
+ GArray *images;
+} PdfMultiVals;
+
+enum {
+ THUMB,
+ PAGE_NUMBER,
+ IMAGE_NAME,
+ IMAGE_ID
+};
+
+typedef struct {
+ GdkPixbuf *thumb;
+ gint32 page_number;
+ gchar* image_name;
+} Page;
+
+static void init_vals (const gchar *name,
+ gint nparams,
+ const GimpParam *param,
+ gboolean *single,
+ gboolean *defaults,
+ GimpRunMode *run_mode);
+
+static void init_image_list_defaults (gint32 image);
+
+static void validate_image_list (void);
+
+static gboolean gui_single (void);
+static gboolean gui_multi (void);
+
+static void choose_file_call (GtkWidget* browse_button,
+ gpointer file_entry);
+
+static gboolean get_image_list (void);
+static GtkTreeModel* create_model (void);
+
+static void add_image_call (GtkWidget *widget,
+ gpointer img_combo);
+static void del_image_call (GtkWidget *widget,
+ gpointer icon_view);
+static void remove_call (GtkTreeModel *tree_model,
+ GtkTreePath *path,
+ gpointer user_data);
+static void recount_pages ();
+
+static cairo_surface_t *get_drawable_image (GimpDrawable *drawable);
+static GimpRGB get_layer_color (GimpDrawable *layer,
+ gboolean *single);
+static void drawText (GimpDrawable* text_layer,
+ gdouble opacity,
+ cairo_t *cr,
+ gdouble x_res,
+ gdouble y_res);
+
+static void query (void);
+static void run (const gchar *name,
+ gint nparams,
+ const GimpParam *param,
+ gint *nreturn_vals,
+ GimpParam **return_vals);
+
+static gboolean dnd_remove = TRUE;
+static PdfMultiPage multi_page;
+
+static PdfOptimize optimize = {
+ TRUE, /* convert */
+ TRUE, /* ignore */
+ TRUE /* apply_masks */
+};
+
+static GtkTreeModel *model;
+static GtkWidget *file_choose;
+static gchar* file_name;
+
+GimpPlugInInfo PLUG_IN_INFO =
+ {
+ NULL,
+ NULL,
+ query,
+ run
+ };
+
+MAIN()
+
+ static void
+query (void)
+{
+ static GimpParamDef save_defaults_args[] =
+ {
+ {GIMP_PDB_INT32, "run-mode", "Run mode"},
+ {GIMP_PDB_IMAGE, "image", "Input image"},
+ {GIMP_PDB_DRAWABLE, "drawable", "Input drawable"},
+ {GIMP_PDB_STRING, "filename", "The name of the file to save the image in"},
+ {GIMP_PDB_STRING, "raw-filename", "The name of the file to save the image in"}
+ };
+
+ static GimpParamDef save_args[] =
+ {
+ {GIMP_PDB_INT32, "run-mode", "Run mode"},
+ {GIMP_PDB_IMAGE, "image", "Input image"},
+ {GIMP_PDB_DRAWABLE, "drawable", "Input drawable"},
+ {GIMP_PDB_INT32, "convert", "Convert bitmaps to vector where possible? TRUE or FALSE"},
+ {GIMP_PDB_INT32, "ignore", "Don't Save layers which are hidden or have their opacity set to zero? TRUE or FALSE"},
+ {GIMP_PDB_INT32, "apply-masks", "Apply layer masks before saving? TRUE or FALSE (Keeping the masks will not change the final result)"},
+ {GIMP_PDB_STRING, "filename", "The name of the file to save the image in"},
+ {GIMP_PDB_STRING, "raw-filename", "The name of the file to save the image in"}
+ };
+
+ static GimpParamDef save_multi_args[] =
+ {
+ {GIMP_PDB_INT32, "run-mode", "Run mode"},
+ {GIMP_PDB_INT32ARRAY, "images", "Input image for each page (An image can appear more than once)"},
+ {GIMP_PDB_INT32, "count", "The amount of images entered (This will be the amount of pages). 1 <= count <= MAX_PAGE_COUNT"},
+ {GIMP_PDB_INT32, "convert", "Convert bitmaps to vector where possible? TRUE or FALSE"},
+ {GIMP_PDB_INT32, "ignore", "Don't Save layers which are hidden or have their opacity set to zero? TRUE or FALSE"},
+ {GIMP_PDB_INT32, "apply-masks", "Apply layer masks before saving? TRUE or FALSE (Keeping the masks will not change the final result)"},
+ {GIMP_PDB_STRING, "filename", "The name of the file to save the image in"},
+ {GIMP_PDB_STRING, "raw-filename", "The name of the file to save the image in"}
+ };
+
+ gimp_install_procedure (SAVE_DEFAULTS_PROC,
+ "Save files as PDF",
+ "Save files in the Portable Document Format (PDF)",
+ "Barak Itkin",
+ "Copyright Barak Itkin",
+ "August 2009",
+ "_Save as PDF...",
+ "RGB*, GRAY*, INDEXED*",
+ GIMP_PLUGIN,
+ G_N_ELEMENTS (save_defaults_args), 0,
+ save_defaults_args, NULL);
+
+ gimp_install_procedure (SAVE_PROC,
+ "Save files as PDF",
+ "Save files in the Portable Document Format (PDF)",
+ "Barak Itkin",
+ "Copyright Barak Itkin",
+ "August 2009",
+ "_Save as PDF...",
+ "RGB*, GRAY*, INDEXED*",
+ GIMP_PLUGIN,
+ G_N_ELEMENTS (save_args), 0,
+ save_args, NULL);
+
+ gimp_install_procedure (SAVE_MULTI_PROC,
+ "Save files as PDF",
+ "Save files in the Portable Document Format (PDF)",
+ "Barak Itkin",
+ "Copyright Barak Itkin",
+ "August 2009",
+ "_Create multiple paged PDF...",
+ "RGB*, GRAY*, INDEXED*",
+ GIMP_PLUGIN,
+ G_N_ELEMENTS (save_multi_args), 0,
+ save_multi_args, NULL);
+
+ gimp_plugin_menu_register (SAVE_MULTI_PROC,
+ "<Image>/File/Create/PDF");
+
+ gimp_register_file_handler_mime (SAVE_DEFAULTS_PROC, "application/pdf");
+ gimp_register_save_handler (SAVE_DEFAULTS_PROC, "pdf", "");
+}
+
+static void
+run (const gchar *name,
+ gint nparams,
+ const GimpParam *param,
+ gint *nreturn_vals,
+ GimpParam **return_vals)
+{
+ static GimpParam values[1];
+ GimpPDBStatusType status = GIMP_PDB_SUCCESS;
+ GimpRunMode run_mode;
+
+ /* Plug-in variables */
+ gboolean single_image;
+ gboolean defaults_proc;
+
+ /* Plug-In variables */
+ cairo_surface_t *pdf_file;
+ cairo_t *cr;
+ GimpExportCapabilities capabilities;
+
+ guint32 i = 0;
+ gint32 j = 0;
+
+ gdouble x_res, y_res;
+ gdouble x_scale, y_scale;
+
+ gint32 image_id;
+ gboolean exported;
+ GimpImageBaseType type;
+
+ gint32 temp;
+
+#if PDF_DEBUG
+ const gchar *cairo_status;
+#endif
+
+ gint *layers;
+ gint32 num_of_layers;
+ GimpDrawable *layer;
+ cairo_surface_t *layer_image;
+ gdouble opacity;
+ gint x, y;
+ GimpRGB layer_color;
+ gboolean single_color;
+
+ gint32 mask_id;
+ GimpDrawable *mask;
+ cairo_surface_t *mask_image;
+
+ /* Setting mandatory output values */
+ *nreturn_vals = 1;
+ *return_vals = values;
+
+ values[0].type = GIMP_PDB_STATUS;
+ values[0].data.d_status = status;
+
+ /* Initializing all the settings */
+ multi_page.image_count = 0;
+
+ init_vals (name, nparams, param, &single_image,
+ &defaults_proc, &run_mode);
+
+ /* Starting the executions */
+ if (run_mode == GIMP_RUN_INTERACTIVE)
+ {
+ if (single_image)
+ {
+ if (! gui_single ())
+ {
+ values[0].data.d_status = GIMP_PDB_CANCEL;
+ return;
+ }
+ }
+ else if (! gui_multi ())
+ {
+ values[0].data.d_status = GIMP_PDB_CANCEL;
+ return;
+ }
+
+ if (file_name == NULL)
+ {
+ values[0].data.d_status = GIMP_PDB_CALLING_ERROR;
+ gimp_message ("You must select a file to save!");
+ return;
+ }
+ }
+
+ pdf_file = cairo_pdf_surface_create (file_name, 1, 1);
+ if (cairo_surface_status (pdf_file) != CAIRO_STATUS_SUCCESS)
+ {
+#if PDF_DEBUG
+ cairo_status = cairo_status_to_string (cairo_surface_status (pdf_file));
+#endif
+ gimp_message ("An error occured while creating the PDF file!\n"
+#if PDF_DEBUG
+ cairo_status "\n"
+#endif
+ "Make sure you entered a valid filename and that the selected location isn't read only!");
+ values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR;
+ return;
+ }
+ cr = cairo_create (pdf_file);
+
+ capabilities = GIMP_EXPORT_CAN_HANDLE_RGB | GIMP_EXPORT_CAN_HANDLE_ALPHA |
+ GIMP_EXPORT_CAN_HANDLE_GRAY | GIMP_EXPORT_CAN_HANDLE_LAYERS |
+ GIMP_EXPORT_CAN_HANDLE_INDEXED;
+ if (optimize.apply_masks)
+ capabilities |= GIMP_EXPORT_CAN_HANDLE_LAYER_MASKS;
+
+ for (i = 0; i < multi_page.image_count; i++)
+ {
+ /* Save the state of the surface before any changes, so that settings
+ * from one page won't affect all the others */
+ cairo_save (cr);
+
+ image_id = multi_page.images[i];
+
+ /* We need the active layer in order to use gimp_image_export */
+ temp = gimp_image_get_active_drawable (image_id);
+ if (temp == -1)
+ exported = gimp_export_image (&image_id, &temp, NULL, capabilities) == GIMP_EXPORT_EXPORT;
+ else
+ exported = FALSE;
+ type = gimp_image_base_type (image_id);
+
+ gimp_image_get_resolution (image_id, &x_res, &y_res);
+ x_scale = 72.0 / x_res;
+ y_scale = 72.0 / y_res;
+
+ cairo_pdf_surface_set_size (pdf_file,
+ gimp_image_width (image_id) * x_scale,
+ gimp_image_height (image_id) * y_scale);
+
+ /* This way we set how many pixels are there in every inch.
+ * It's very important for PangoCairo */
+ cairo_surface_set_fallback_resolution (pdf_file, x_res, y_res);
+
+ /* PDF is usually 72 points per inch. If we have a different resolution,
+ * we will need this to fit our drawings */
+ cairo_scale (cr, x_scale, y_scale);
+
+ /* Now, we should loop over the layers of each image */
+ layers = gimp_image_get_layers (image_id, &num_of_layers);
+
+ for (j = 0; j < num_of_layers; j++)
+ {
+ layer = gimp_drawable_get (layers [num_of_layers-j-1]);
+ opacity = gimp_layer_get_opacity (layer->drawable_id)/100.0;
+
+ /* Gimp doesn't display indexed layers with opacity below 50%
+ * And if it's above 50%, it will be rounded to 100% */
+ if (type == GIMP_INDEXED)
+ {
+ if (opacity <= 0.5)
+ opacity = 0.0;
+ else
+ opacity = 1.0;
+ }
+
+ if (gimp_item_get_visible (layer->drawable_id)
+ && (! optimize.ignore || optimize.ignore && opacity > 0.0))
+ {
+ mask_id = gimp_layer_get_mask (layer->drawable_id);
+ if (mask_id != -1)
+ {
+ mask = gimp_drawable_get (mask_id);
+ mask_image = get_drawable_image (mask);
+ }
+
+ gimp_drawable_offsets (layer->drawable_id, &x, &y);
+
+ /* For raster layers */
+ if (!gimp_drawable_is_text_layer (layer->drawable_id))
+ {
+ layer_color = get_layer_color (layer, &single_color);
+ cairo_rectangle (cr, x, y, layer->width, layer->height);
+
+ if (optimize.convert && single_color)
+ {
+ cairo_set_source_rgba (cr, layer_color.r, layer_color.g, layer_color.b, layer_color.a * opacity);
+ if (mask_id != -1)
+ cairo_mask_surface (cr, mask_image, x, y);
+ else
+ cairo_fill (cr);
+ }
+ else
+ {
+ cairo_clip (cr);
+ layer_image = get_drawable_image (layer);
+ cairo_set_source_surface (cr, layer_image, x, y);
+ cairo_push_group (cr);
+ cairo_paint_with_alpha (cr, opacity);
+ cairo_pop_group_to_source (cr);
+ if (mask_id != -1)
+ cairo_mask_surface (cr, mask_image, x, y);
+ else
+ cairo_paint (cr);
+ cairo_reset_clip (cr);
+
+ cairo_surface_destroy (layer_image);
+ }
+ }
+ /* For text layers */
+ else
+ {
+ drawText (layer, opacity, cr, x_res, y_res);
+ }
+ }
+
+ /* We are done with the layer - time to free some resources */
+ gimp_drawable_detach (layer);
+ if (mask_id != -1)
+ {
+ gimp_drawable_detach (mask);
+ cairo_surface_destroy (mask_image);
+ }
+ }
+ /* We are done with this image - Show it! */
+ cairo_show_page (cr);
+ cairo_restore (cr);
+
+ if (exported)
+ gimp_image_delete (image_id);
+ }
+
+ /* We are done with all the images - time to free the resources */
+ cairo_surface_destroy (pdf_file);
+ cairo_destroy (cr);
+
+ /* Finally done, let's save the parameters */
+ gimp_set_data (DATA_OPTIMIZE, &optimize, sizeof (optimize));
+ if (!single_image)
+ {
+ g_strlcpy (multi_page.file_name, file_name, MAX_FILE_NAME_LENGTH);
+ gimp_set_data (DATA_IMAGE_LIST, &multi_page, sizeof (multi_page));
+ }
+
+}
+
+/******************************************************/
+/* Begining of parameter handling functions */
+/******************************************************/
+
+/* A function that takes care of loading the basic
+ * parameters */
+static void
+init_vals (const gchar *name,
+ gint nparams,
+ const GimpParam *param,
+ gboolean *single_image,
+ gboolean *defaults_proc,
+ GimpRunMode *run_mode)
+{
+ gboolean had_saved_list = FALSE;
+ gboolean single = TRUE;
+ gboolean defaults = FALSE;
+ gint32 i;
+ gint32 image;
+
+ *run_mode = param[0].data.d_int32;
+
+ if (g_str_equal (name, SAVE_DEFAULTS_PROC))
+ defaults = TRUE;
+ else if (g_str_equal (name, SAVE_MULTI_PROC))
+ single = FALSE;
+
+ if (single)
+ {
+ image = param[1].data.d_int32;
+ file_name = param[3].data.d_string;
+ }
+ else {
+ image = -1;
+ file_name = param[6].data.d_string;
+ }
+
+ switch (*run_mode)
+ {
+
+ case GIMP_RUN_NONINTERACTIVE:
+ if (single)
+ {
+ if (!defaults)
+ {
+ optimize.apply_masks = param[3].data.d_int32;
+ optimize.convert = param[4].data.d_int32;
+ optimize.ignore = param[5].data.d_int32;
+ }
+ init_image_list_defaults (image);
+ }
+ else
+ {
+ multi_page.image_count = param[2].data.d_int32;
+ if (param[1].data.d_int32array != NULL)
+ for (i = 0; i < param[2].data.d_int32; i++)
+ multi_page.images[i] = param[1].data.d_int32array[i];
+
+ optimize.apply_masks = param[3].data.d_int32;
+ optimize.convert = param[4].data.d_int32;
+ optimize.ignore = param[5].data.d_int32;
+ }
+
+ break;
+
+ case GIMP_RUN_INTERACTIVE:
+ /* Possibly retrieve data */
+ gimp_get_data (DATA_OPTIMIZE, &optimize);
+ had_saved_list = gimp_get_data (DATA_IMAGE_LIST, &multi_page);
+ if (had_saved_list)
+ {
+ file_name = multi_page.file_name;
+ }
+
+ if (single || !had_saved_list )
+ init_image_list_defaults (image);
+
+ break;
+
+ case GIMP_RUN_WITH_LAST_VALS:
+ /* Possibly retrieve data */
+ if (!single)
+ {
+ had_saved_list = gimp_get_data (DATA_IMAGE_LIST, &multi_page);
+ if (had_saved_list)
+ {
+ file_name = multi_page.file_name;
+ }
+ }
+ else
+ init_image_list_defaults (image);
+ gimp_get_data (DATA_OPTIMIZE, &optimize);
+
+ break;
+ }
+
+ *defaults_proc = defaults;
+ *single_image = single;
+
+ validate_image_list ();
+}
+
+/* A function that initializes the image list to default values */
+static void
+init_image_list_defaults (gint32 image)
+{
+ if (image != -1)
+ {
+ multi_page.images[0] = image;
+ multi_page.image_count = 1;
+ }
+ else {
+ multi_page.image_count = 0;
+ }
+}
+
+/* A function that removes images that are no longer valid from
+ * the image list */
+static void
+validate_image_list (void)
+{
+ gint32 valid = 0;
+ guint32 i = 0;
+
+ for (i = 0 ; i < MAX_PAGE_COUNT && i < multi_page.image_count ; i++)
+ {
+ if (gimp_image_is_valid (multi_page.images[i]))
+ {
+ multi_page.images[valid] = multi_page.images[i];
+ valid++;
+ }
+ }
+ multi_page.image_count = valid;
+}
+
+/******************************************************/
+/* Begining of GUI functions */
+/******************************************************/
+/* The main GUI function for saving single-paged PDFs */
+static gboolean
+gui_single (void)
+{
+ GtkWidget *window;
+ GtkWidget *vbox;
+
+ GtkWidget *convert_c;
+ GtkWidget *ignore_c;
+ GtkWidget *apply_c;
+
+ gboolean run;
+
+ gimp_ui_init (PLUG_IN_BINARY, FALSE);
+
+ window = gimp_export_dialog_new ("PDF", PLUG_IN_BINARY, SAVE_PROC);
+
+ vbox = gtk_vbox_new (FALSE, 12);
+ gtk_box_pack_start (GTK_BOX (gimp_export_dialog_get_content_area (window)),
+ vbox, TRUE, TRUE, 0);
+
+ gtk_container_set_border_width (GTK_CONTAINER (window), 12);
+
+ ignore_c = gtk_check_button_new_with_label ("Don't Save layers which are hidden or have their opacity set to zero?");
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ignore_c), optimize.ignore);
+ gtk_box_pack_end_defaults (GTK_BOX (vbox), ignore_c);
+
+ convert_c = gtk_check_button_new_with_label ("Convert bitmaps to vector where possible?");
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (convert_c), optimize.convert);
+ gtk_box_pack_end_defaults (GTK_BOX (vbox), convert_c);
+
+ apply_c = gtk_check_button_new_with_label ("Apply layer masks before saving? (Keeping the masks will not change the final result)");
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (apply_c), optimize.apply_masks);
+ gtk_box_pack_end_defaults (GTK_BOX (vbox), apply_c);
+
+ gtk_widget_show_all (window);
+
+ run = gtk_dialog_run (GTK_DIALOG (window)) == GTK_RESPONSE_OK;
+
+ optimize.ignore = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ignore_c));
+ optimize.convert = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (convert_c));
+ optimize.apply_masks = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (apply_c));
+
+ gtk_widget_destroy (window);
+ return run;
+}
+/* The main GUI function for saving multi-paged PDFs */
+static gboolean
+gui_multi (void)
+{
+ GtkWidget *window;
+ GtkWidget *vbox;
+
+ GtkWidget *file_label;
+ GtkWidget *file_entry;
+ GtkWidget *file_browse;
+ GtkWidget *file_hbox;
+
+ GtkWidget *convert_c;
+ GtkWidget *ignore_c;
+ GtkWidget *apply_c;
+
+ GtkWidget *scroll;
+ GtkWidget *page_view;
+
+ GtkWidget *h_but_box;
+ GtkWidget *del;
+
+ GtkWidget *h_box;
+ GtkWidget *img_combo;
+ GtkWidget *add_image;
+
+ gboolean run;
+ const gchar *temp;
+
+ gimp_ui_init (PLUG_IN_BINARY, FALSE);
+
+ window = gimp_export_dialog_new ("PDF", PLUG_IN_BINARY, SAVE_MULTI_PROC);
+
+ vbox = gtk_vbox_new (FALSE, 10);
+ gtk_box_pack_start (GTK_BOX (gimp_export_dialog_get_content_area (window)),
+ vbox, TRUE, TRUE, 0);
+
+ gtk_container_set_border_width (GTK_CONTAINER (window), 12);
+
+ file_hbox = gtk_hbox_new (FALSE, 5);
+ file_label = gtk_label_new ("Choose the place to save the file in:");
+ file_entry = gtk_entry_new ();
+ if (file_name != NULL)
+ gtk_entry_set_text (GTK_ENTRY (file_entry), file_name);
+ file_browse = gtk_button_new_with_label ("Browse...");
+ file_choose = gtk_file_chooser_dialog_new ("Multipage PDF export",
+ GTK_WINDOW (window), GTK_FILE_CHOOSER_ACTION_SAVE,
+ "gtk-save", GTK_RESPONSE_OK,
+ "gtk-cancel", GTK_RESPONSE_CANCEL,
+ NULL);
+
+ gtk_box_pack_start (GTK_BOX (file_hbox), file_label, FALSE, FALSE, 0);
+ gtk_box_pack_start_defaults (GTK_BOX (file_hbox), file_entry);
+ gtk_box_pack_start (GTK_BOX (file_hbox), file_browse, FALSE, FALSE, 0);
+
+ gtk_box_pack_start_defaults (GTK_BOX (vbox), file_hbox);
+
+ page_view = gtk_icon_view_new ();
+ model = create_model ();
+ gtk_icon_view_set_model (GTK_ICON_VIEW (page_view), model);
+ gtk_icon_view_set_reorderable (GTK_ICON_VIEW (page_view), TRUE);
+ gtk_icon_view_set_selection_mode (GTK_ICON_VIEW (page_view), GTK_SELECTION_MULTIPLE);
+
+ gtk_icon_view_set_pixbuf_column (GTK_ICON_VIEW (page_view), THUMB);
+ gtk_icon_view_set_text_column (GTK_ICON_VIEW (page_view), PAGE_NUMBER);
+ gtk_icon_view_set_tooltip_column (GTK_ICON_VIEW (page_view), IMAGE_NAME);
+
+ scroll = gtk_scrolled_window_new (NULL, NULL);
+ gtk_widget_set_size_request (scroll, -1, 300);
+
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
+ gtk_container_add (GTK_CONTAINER (scroll), page_view);
+
+ gtk_box_pack_start_defaults (GTK_BOX (vbox), scroll);
+
+ h_but_box = gtk_hbutton_box_new ();
+ gtk_button_box_set_layout (GTK_BUTTON_BOX (h_but_box), GTK_BUTTONBOX_START);
+
+ del = gtk_button_new_with_label ("Remove the selected pages");
+ gtk_box_pack_start_defaults (GTK_BOX (h_but_box), del);
+
+ gtk_box_pack_start (GTK_BOX (vbox), h_but_box, FALSE, FALSE, 0);
+
+ h_box = gtk_hbox_new (FALSE, 5);
+
+ img_combo = gimp_image_combo_box_new (NULL, NULL);
+ gtk_box_pack_start (GTK_BOX (h_box), img_combo, FALSE, FALSE, 0);
+
+ add_image = gtk_button_new_with_label ("Add this image");
+ gtk_box_pack_start (GTK_BOX (h_box), add_image, FALSE, FALSE, 0);
+
+ gtk_box_pack_start (GTK_BOX (vbox), h_box, FALSE, FALSE, 0);
+
+ ignore_c = gtk_check_button_new_with_label ("Don't Save layers which are hidden or have their opacity set to zero?");
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ignore_c), optimize.ignore);
+ gtk_box_pack_end (GTK_BOX (vbox), ignore_c, FALSE, FALSE, 0);
+
+ convert_c = gtk_check_button_new_with_label ("Convert bitmaps to vector where possible?");
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (convert_c), optimize.convert);
+ gtk_box_pack_end (GTK_BOX (vbox), convert_c, FALSE, FALSE, 0);
+
+ apply_c = gtk_check_button_new_with_label ("Apply layer masks before saving? (Keeping the masks will not change the final result)");
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (apply_c), optimize.apply_masks);
+ gtk_box_pack_end (GTK_BOX (vbox), apply_c, FALSE, FALSE, 0);
+
+ gtk_widget_show_all (window);
+
+ g_signal_connect (G_OBJECT (file_browse), "clicked",
+ G_CALLBACK (choose_file_call), G_OBJECT (file_entry));
+
+ g_signal_connect (G_OBJECT (add_image), "clicked",
+ G_CALLBACK (add_image_call), G_OBJECT (img_combo));
+
+ g_signal_connect (G_OBJECT (del), "clicked",
+ G_CALLBACK (del_image_call), G_OBJECT (page_view));
+
+ g_signal_connect (G_OBJECT (model), "row-deleted",
+ G_CALLBACK (remove_call), NULL);
+
+ run = gtk_dialog_run (GTK_DIALOG (window)) == GTK_RESPONSE_OK;
+
+ run &= get_image_list ();
+
+ temp = gtk_entry_get_text (GTK_ENTRY (file_entry));
+ g_stpcpy (file_name, temp);
+
+ optimize.ignore = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ignore_c));
+ optimize.convert = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (convert_c));
+ optimize.apply_masks = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (apply_c));
+
+ gtk_widget_destroy (window);
+ return run;
+}
+
+/* A function that is called when the button for browsing for file
+ * locations was clicked */
+static void
+choose_file_call (GtkWidget *browse_button,
+ gpointer file_entry)
+{
+ GFile *file = g_file_new_for_path (gtk_entry_get_text (GTK_ENTRY (file_entry)));
+ gtk_file_chooser_set_uri (GTK_FILE_CHOOSER (file_choose), g_file_get_uri (file));
+
+ if (gtk_dialog_run (GTK_DIALOG (file_choose)) == GTK_RESPONSE_OK)
+ {
+ file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (file_choose));
+ gtk_entry_set_text (GTK_ENTRY (file_entry), g_file_get_path (file));
+ };
+
+ file_name = g_file_get_path (file);
+ gtk_widget_hide (file_choose);
+}
+
+/* A function to create the basic GtkTreeModel for the icon view */
+static GtkTreeModel*
+create_model (void)
+{
+ GtkListStore *model;
+ GtkTreeIter iter;
+ guint32 i;
+ gint32 image = multi_page.images[0];
+
+ /* validate_image_list was called earlier, so all the images
+ * up to multi_page.image_count are valid */
+ model = gtk_list_store_new (4,
+ GDK_TYPE_PIXBUF, /* THUMB */
+ G_TYPE_STRING, /* PAGE_NUMBER */
+ G_TYPE_STRING, /* IMAGE_NAME */
+ G_TYPE_INT); /* IMAGE_ID */
+
+ for (i = 0 ; i < multi_page.image_count && i < MAX_PAGE_COUNT ; i++)
+ {
+ image = multi_page.images[i];
+
+ gtk_list_store_append (model, &iter);
+ gtk_list_store_set (model, &iter,
+ THUMB, gimp_image_get_thumbnail (image, THUMB_WIDTH, THUMB_HEIGHT, GIMP_PIXBUF_SMALL_CHECKS),
+ PAGE_NUMBER, g_strdup_printf ("Page %d", i+1),
+ IMAGE_NAME, gimp_image_get_name (image),
+ IMAGE_ID, image,
+ -1);
+
+ }
+
+ return GTK_TREE_MODEL (model);
+}
+
+/* A function that puts the images from the model inside the
+ * images (pages) array */
+static gboolean
+get_image_list (void)
+{
+ GtkTreeIter iter;
+ gboolean valid = gtk_tree_model_get_iter_first (model, &iter);
+ gint32 image;
+
+ multi_page.image_count = 0;
+
+ if (!valid)
+ {
+ gimp_message ("Error! In order to save the file, at least one image should be added!");
+ return FALSE;
+ }
+
+ while (valid)
+ {
+ gtk_tree_model_get (model, &iter,
+ IMAGE_ID, &image,
+ -1);
+ multi_page.images[multi_page.image_count] = image;
+
+ valid = gtk_tree_model_iter_next (model, &iter);
+ multi_page.image_count++;
+ }
+
+ return TRUE;
+}
+
+/* A function that is called when the button for adding an image
+ * was clicked */
+static void
+add_image_call (GtkWidget *widget,
+ gpointer img_combo)
+{
+ GtkListStore *store;
+ GtkTreeIter iter;
+ gint32 image;
+
+ dnd_remove = FALSE;
+
+ gimp_int_combo_box_get_active (img_combo, &image);
+
+ store = GTK_LIST_STORE (model);
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ PAGE_NUMBER, g_strdup_printf ("Page %d", multi_page.image_count+1),
+ THUMB, gimp_image_get_thumbnail (image, THUMB_WIDTH, THUMB_HEIGHT, GIMP_PIXBUF_SMALL_CHECKS),
+ IMAGE_NAME, gimp_image_get_name (image),
+ IMAGE_ID, image,
+ -1
+ );
+
+ multi_page.image_count++;
+
+ dnd_remove = TRUE;
+}
+
+/* A function that is called when the button for deleting the
+ * selected images was clicked */
+static void
+del_image_call (GtkWidget *widget,
+ gpointer icon_view)
+{
+ GList *list;
+ GtkTreeRowReference **items;
+ GtkTreePath *item_path;
+ GtkTreeIter item;
+ guint32 i;
+ gpointer temp;
+
+ guint32 len;
+
+ GdkPixbuf *thumb;
+ gchar* name;
+
+ dnd_remove = FALSE;
+
+ list = gtk_icon_view_get_selected_items (GTK_ICON_VIEW (icon_view));
+
+ len = g_list_length (list);
+ if (len > 0)
+ {
+ items = g_newa (GtkTreeRowReference*, len);
+
+ for (i = 0; i < len; i++)
+ {
+ temp = g_list_nth_data (list, i);
+ items[i] = gtk_tree_row_reference_new (model, temp);
+ gtk_tree_path_free (temp);
+ }
+ g_list_free (list);
+
+ for (i = 0; i < len; i++)
+ {
+ item_path = gtk_tree_row_reference_get_path (items[i]);
+ gtk_tree_model_get_iter (model, &item, item_path);
+
+ /* Get the data that should be freed */
+ gtk_tree_model_get (model, &item,
+ THUMB, &thumb, IMAGE_NAME, &name, -1);
+
+ /* Only after you have the pointers, remove them from the tree */
+ gtk_list_store_remove (GTK_LIST_STORE (model), &item);
+
+ /* Now you can free the data */
+ gdk_pixbuf_unref (thumb);
+ g_free (name);
+
+ gtk_tree_path_free (item_path);
+ gtk_tree_row_reference_free (items[i]);
+ multi_page.image_count--;
+ }
+
+ g_free (items);
+ }
+
+ dnd_remove = TRUE;
+
+ recount_pages ();
+}
+
+/* A function that is called on rows-deleted signal. It will
+ * call the function to relabel the pages */
+static void
+remove_call (GtkTreeModel *tree_model,
+ GtkTreePath *path,
+ gpointer user_data)
+{
+
+ if (dnd_remove)
+ /* The gtk documentation says that we should not free the indices array */
+ recount_pages ();
+}
+
+/* A function to relabel the pages in the icon view, when
+ * their order was changed */
+static void
+recount_pages (void)
+{
+ GtkListStore *store;
+ GtkTreeIter iter;
+ gboolean valid;
+ gint32 i = 0;
+
+ store = GTK_LIST_STORE (model);
+
+ valid = gtk_tree_model_get_iter_first (model, &iter);
+ while (valid)
+ {
+ gtk_list_store_set (store, &iter,
+ PAGE_NUMBER, g_strdup_printf ("Page %d", i + 1),
+ -1);
+ valid = gtk_tree_model_iter_next (model, &iter);
+ i++;
+ }
+}
+
+/******************************************************/
+/* Begining of the actual PDF functions */
+/******************************************************/
+
+/* A function to get a cairo image surface from a drawable.
+ * Some of the code was taken from the gimp-print plugin */
+static cairo_surface_t *
+get_drawable_image (GimpDrawable *drawable)
+{
+ cairo_surface_t *surface;
+ cairo_format_t format;
+ guchar *data;
+ guchar *dest;
+ const guchar *src;
+ gint dest_stride;
+ gint y;
+ gint bpp;
+
+ gint32 drawable_id;
+ GimpPixelRgn region;
+ gint width;
+ gint height;
+
+ guchar* colors;
+ gint num_colors;
+ gboolean indexed;
+
+ drawable_id = drawable->drawable_id;
+ width = drawable->width;
+ height = drawable->height;
+ gimp_pixel_rgn_init (®ion, drawable, 0, 0, width, height, FALSE, FALSE);
+ bpp = region.bpp;
+ data = g_new (guchar, width*height*bpp);
+ gimp_pixel_rgn_get_rect (®ion, data, 0, 0, width, height);
+
+ indexed = gimp_drawable_is_indexed (drawable->drawable_id);
+ if (indexed)
+ colors = gimp_image_get_colormap (gimp_item_get_image (drawable->drawable_id), &num_colors);
+
+ switch (bpp)
+ {
+ case 1: /* GRAY or INDEXED */
+ if (!indexed)
+ format = CAIRO_FORMAT_A8;
+ else
+ format = CAIRO_FORMAT_RGB24;
+ break;
+ case 3: /* RGB */
+ format = CAIRO_FORMAT_RGB24;
+ break;
+
+ case 2: /* GRAYA or INDEXEDA */
+ case 4: /* RGBA */
+ format = CAIRO_FORMAT_ARGB32;
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ surface = cairo_image_surface_create (format, width, height);
+
+ src = data;
+
+ dest = cairo_image_surface_get_data (surface);
+ dest_stride = cairo_image_surface_get_stride (surface);
+
+ for (y = 0; y < height; y++)
+ {
+ const guchar *s = src;
+ guchar *d = dest;
+ gint w = width;
+
+ switch (bpp)
+ {
+ case 1:
+ if (!indexed)
+ {
+ while (w--)
+ {
+ d[0] = s[0];
+ s += 1;
+ d += 1;
+
+ }
+ }
+ else {
+ while (w--)
+ {
+ GIMP_CAIRO_RGB24_SET_PIXEL (d, colors[s[0]*3], colors[s[0]*3+1], colors[s[0]*3+2]);
+ s += 1;
+ d += 4;
+
+ }
+ }
+ break;
+
+ case 2:
+ if (!indexed)
+ {
+ while (w--)
+ {
+ GIMP_CAIRO_ARGB32_SET_PIXEL (d, s[0], s[0], s[0], s[1]);
+ s += 2;
+ d += 4;
+ }
+ }
+ else {
+ while (w--)
+ {
+ GIMP_CAIRO_ARGB32_SET_PIXEL (d, colors[s[0]*3], colors[s[0]*3+1], colors[s[0]*3+2], s[1]);
+ s += 2;
+ d += 4;
+ }
+ }
+ break;
+
+ case 3:
+ while (w--)
+ {
+ GIMP_CAIRO_RGB24_SET_PIXEL (d, s[0], s[1], s[2]);
+ s += 3;
+ d += 4;
+ }
+ break;
+
+ case 4:
+ while (w--)
+ {
+ GIMP_CAIRO_ARGB32_SET_PIXEL (d, s[0], s[1], s[2], s[3]);
+ s += 4;
+ d += 4;
+ }
+ break;
+ }
+ src += width*bpp;
+ dest += dest_stride;
+ }
+ if (indexed)
+ g_free (colors);
+ g_free (data);
+
+ return surface;
+}
+
+/* A function to check if a drawable is single colored
+ * This allows to convert bitmaps to vector where possible */
+static GimpRGB
+get_layer_color (GimpDrawable *layer,
+ gboolean *single)
+{
+ GimpRGB col;
+ gdouble red, green, blue, alpha;
+ gdouble dev, devSum;
+ gdouble median, pixels, count, precentile;
+ gint32 id;
+
+ id = layer->drawable_id;
+ devSum = 0;
+ red = 0;
+ green = 0;
+ blue = 0;
+ alpha = 0;
+ dev = 0;
+
+ if (gimp_drawable_is_indexed (id))
+ {
+ /* FIXME: We can't do a propper histogram on indexed layers! */
+ *single = FALSE;
+ col. r = col.g = col.b = col.a = 0;
+ return col;
+ }
+
+ /* Are we in RGB mode? */
+ if (layer->bpp >= 3)
+ {
+ gimp_histogram (id, GIMP_HISTOGRAM_RED, 0, 255, &red, &dev, &median, &pixels, &count, &precentile);
+ devSum += dev;
+ gimp_histogram (id, GIMP_HISTOGRAM_GREEN, 0, 255, &green, &dev, &median, &pixels, &count, &precentile);
+ devSum += dev;
+ gimp_histogram (id, GIMP_HISTOGRAM_BLUE, 0, 255, &blue, &dev, &median, &pixels, &count, &precentile);
+ devSum += dev;
+ }
+ /* We are in Grayscale mode (or Indexed) */
+ else
+ {
+ gimp_histogram (id, GIMP_HISTOGRAM_VALUE, 0, 255, &red, &dev, &median, &pixels, &count, &precentile);
+ devSum += dev;
+ green = red;
+ blue = red;
+ }
+ if (gimp_drawable_has_alpha (id))
+ gimp_histogram (id, GIMP_HISTOGRAM_ALPHA, 0, 255, &alpha, &dev, &median, &pixels, &count, &precentile);
+ else
+ alpha = 255;
+
+ devSum += dev;
+ *single = devSum == 0;
+
+ col.r = red/255;
+ col.g = green/255;
+ col.b = blue/255;
+ col.a = alpha/255;
+
+ return col;
+}
+
+/* A function that uses Pango to render the text to our cairo surface,
+ * in the same way it was the user saw it inside gimp.
+ * Needs some work on choosing the font name better, and on hinting
+ * (freetype and pango differences)
+ */
+static void
+drawText (GimpDrawable *text_layer,
+ gdouble opacity,
+ cairo_t *cr,
+ gdouble x_res,
+ gdouble y_res)
+{
+ gint32 text_id = text_layer->drawable_id;
+ GimpImageBaseType type = gimp_drawable_type (text_id);
+
+ gchar *text = gimp_text_layer_get_text (text_id);
+ gchar *markup = gimp_text_layer_get_markup (text_id);
+ gchar *font_family;
+
+ cairo_font_options_t *options;
+
+ gint x;
+ gint y;
+
+ GimpRGB color;
+
+ GimpUnit unit;
+ gdouble size;
+
+ GimpTextHintStyle hinting;
+
+ GimpTextJustification j;
+ gboolean justify;
+ PangoAlignment align;
+ GimpTextDirection dir;
+ PangoDirection pango_dir;
+
+ PangoLayout *layout;
+ PangoContext *context;
+ PangoFontDescription *font_description;
+
+ gdouble indent;
+ gdouble line_spacing;
+ gdouble letter_spacing;
+ PangoAttribute *letter_spacing_at;
+ PangoAttrList *attr_list = pango_attr_list_new ();
+
+ cairo_save (cr);
+
+ options = cairo_font_options_create ();
+ attr_list = pango_attr_list_new ();
+ cairo_get_font_options (cr, options);
+
+ /* Position */
+ gimp_drawable_offsets (text_id, &x, &y);
+ cairo_move_to (cr, x, y);
+
+ /* Color */
+ /* When dealing with a gray/indexed image, the viewed color of the text layer
+ * can be different than the one kept in the memory */
+ if (type = GIMP_RGB)
+ gimp_text_layer_get_color (text_id, &color);
+ else
+ gimp_image_pick_color (gimp_item_get_image (text_id), text_id, x, y, FALSE, FALSE, 0, &color);
+
+ cairo_set_source_rgba (cr, color.r, color.g, color.b, opacity);
+
+ /* Hinting */
+ hinting = gimp_text_layer_get_hint_style (text_id);
+ switch (hinting)
+ {
+ case GIMP_TEXT_HINT_STYLE_NONE:
+ cairo_font_options_set_hint_style (options, CAIRO_HINT_STYLE_NONE);
+ break;
+
+ case GIMP_TEXT_HINT_STYLE_SLIGHT:
+ cairo_font_options_set_hint_style (options, CAIRO_HINT_STYLE_SLIGHT);
+ break;
+
+ case GIMP_TEXT_HINT_STYLE_MEDIUM:
+ cairo_font_options_set_hint_style (options, CAIRO_HINT_STYLE_MEDIUM);
+ break;
+
+ case GIMP_TEXT_HINT_STYLE_FULL:
+ cairo_font_options_set_hint_style (options, CAIRO_HINT_STYLE_FULL);
+ break;
+ }
+
+ /* Antialiasing */
+ if (gimp_text_layer_get_antialias (text_id))
+ cairo_font_options_set_antialias (options, CAIRO_ANTIALIAS_DEFAULT);
+ else
+ cairo_font_options_set_antialias (options, CAIRO_ANTIALIAS_NONE);
+
+ /* We are done with cairo's settings.
+ * It's time to create the context */
+ cairo_set_font_options (cr, options);
+ context = pango_cairo_create_context (cr);
+ pango_cairo_context_set_font_options (context, options);
+
+ /* Text Direction */
+ dir = gimp_text_layer_get_base_direction (text_id);
+
+ if (dir == GIMP_TEXT_DIRECTION_RTL)
+ pango_dir = PANGO_DIRECTION_RTL;
+ else
+ pango_dir = PANGO_DIRECTION_LTR;
+
+ pango_context_set_base_dir (context, pango_dir);
+
+ /* We are done with the context's settings.
+ * It's time to create the layout */
+ layout = pango_layout_new (context);
+
+ /* Font */
+ font_family = gimp_text_layer_get_font (text_id);
+ /* We need to find a way to convert GIMP's returned font name to
+ * a normal Pango name... Hopefully GIMP 2.8 with Pango will fix it. */
+ font_description = pango_font_description_from_string (font_family);
+
+ /* Font Size */
+ size = gimp_text_layer_get_font_size (text_id, &unit);
+ if (! g_strcmp0 (gimp_unit_get_abbreviation (unit), "px") == 0)
+ size *= 1.0 / gimp_unit_get_factor (unit) * x_res / y_res;
+ pango_font_description_set_absolute_size (font_description, size * PANGO_SCALE);
+
+ pango_layout_set_font_description (layout, font_description);
+
+ /* Width and height */
+ pango_layout_set_width (layout, text_layer->width * PANGO_SCALE);
+ pango_layout_set_height (layout, text_layer->height * PANGO_SCALE);
+
+ /* Justification, and Alignment */
+ justify = FALSE;
+ j = gimp_text_layer_get_justification (text_id);
+
+ if (j == GIMP_TEXT_JUSTIFY_CENTER)
+ align = PANGO_ALIGN_CENTER;
+ else if (j == GIMP_TEXT_JUSTIFY_LEFT)
+ align = PANGO_ALIGN_LEFT;
+ else if (j == GIMP_TEXT_JUSTIFY_RIGHT)
+ align = PANGO_ALIGN_RIGHT;
+ else /* We have GIMP_TEXT_JUSTIFY_FILL */
+ {
+ if (dir == GIMP_TEXT_DIRECTION_LTR)
+ align = PANGO_ALIGN_LEFT;
+ else
+ align = PANGO_ALIGN_RIGHT;
+ justify = TRUE;
+ }
+
+ /* Indentation */
+ indent = gimp_text_layer_get_indent (text_id);
+ pango_layout_set_indent (layout, (int)(PANGO_SCALE * indent));
+
+ /* Line Spacing */
+ line_spacing = gimp_text_layer_get_line_spacing (text_id);
+ pango_layout_set_spacing (layout, (int)(PANGO_SCALE * line_spacing));
+
+ /* Letter Spacing */
+ letter_spacing = gimp_text_layer_get_letter_spacing (text_id);
+ letter_spacing_at = pango_attr_letter_spacing_new ((int)(PANGO_SCALE * letter_spacing));
+ pango_attr_list_insert (attr_list, letter_spacing_at);
+
+ pango_layout_set_justify (layout, justify);
+ pango_layout_set_alignment (layout, align);
+
+ pango_layout_set_attributes (layout, attr_list);
+
+ /* Use the pango markup of the text layer */
+
+ if (markup != NULL && markup[0] != '\0')
+ pango_layout_set_markup (layout, markup, -1);
+ else /* If we can't find a markup, then it has just text */
+ pango_layout_set_text (layout, text, -1);
+
+ pango_cairo_show_layout (cr, layout);
+
+ g_free (text);
+ g_free (font_family);
+
+ g_object_unref (layout);
+ pango_font_description_free (font_description);
+ g_object_unref (context);
+ pango_attr_list_unref (attr_list);
+
+ cairo_font_options_destroy (options);
+
+ cairo_restore (cr);
+}
diff --git a/plug-ins/common/plugin-defs.pl b/plug-ins/common/plugin-defs.pl
index 3083242..5feb537 100644
--- a/plug-ins/common/plugin-defs.pl
+++ b/plug-ins/common/plugin-defs.pl
@@ -67,6 +67,7 @@
'file-png' => { ui => 1, optional => 1, libs => 'PNG_LIBS', cflags => 'PNG_CFLAGS' },
'file-pnm' => { ui => 1 },
'file-pdf-load' => { ui => 1, optional => 1, libs => 'POPPLER_LIBS', cflags => 'POPPLER_CFLAGS' },
+ 'file-pdf-save' => { ui => 1, optional => 1, libs => 'CAIRO_PDF_LIBS', cflags => 'CAIRO_PDF_CFLAGS' },
'file-ps' => { ui => 1 },
'file-psp' => { ui => 1, optional => 1, libs => 'Z_LIBS' },
'file-raw' => { ui => 1 },
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]