[gimp] plug-ins: add the new file-pdf-save plugin (Bug #382688)



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 (&region, drawable, 0, 0, width, height, FALSE, FALSE);
+  bpp = region.bpp;
+  data = g_new (guchar, width*height*bpp);
+  gimp_pixel_rgn_get_rect (&region, 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]