Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Gimp/plug-ins/common/   (Gimp Bildeditor Version 3.2.0©)  Datei vom 14.2.2026 mit Größe 76 kB image not shown  

Quelle  file-pdf-export.c   Sprache: C

 

/* GIMP - The GNU Image Manipulation Program
 *
 * file-pdf-export.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 <https://www.gnu.org/licenses/>.
 */


/* The PDF export plugin has 3 main procedures:
 * 1. file-pdf-export
 *    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-export-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-export. 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-export-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-export-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):
 * 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 incompatible 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 behavior 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 supports
 *     selecting 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 deprecated
 *     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 "config.h"

#include <errno.h>

#include <glib/gstdio.h>
#include <cairo-pdf.h>
#include <pango/pangocairo.h>

#include <libgimp/gimp.h>
#include <libgimp/gimpui.h>

#include "libgimp/stdplugins-intl.h"


#define EXPORT_PROC             "file-pdf-export"
#define EXPORT_MULTI_PROC       "file-pdf-export-multi"
#define PLUG_IN_BINARY          "file-pdf-export"
#define PLUG_IN_ROLE            "gimp-file-pdf-export"

#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 GIMP_PLUGIN_PDF_EXPORT_ERROR gimp_plugin_pdf_export_error_quark ()

typedef enum
{
  GIMP_PLUGIN_PDF_EXPORT_ERROR_FAILED
} GimpPluginPDFError;

GQuark gimp_plugin_pdf_export_error_quark (void);

typedef struct
{
  GimpImage *images[MAX_PAGE_COUNT];
  guint32    image_count;
  gchar      file_name[MAX_FILE_NAME_LENGTH];
} PdfMultiPage;

enum
{
  THUMB,
  PAGE_NUMBER,
  IMAGE_NAME,
  IMAGE
};

typedef struct
{
  GdkPixbuf *thumb;
  gint32     page_number;
  gchar     *image_name;
} Page;


typedef struct _Pdf      Pdf;
typedef struct _PdfClass PdfClass;

struct _Pdf
{
  GimpPlugIn      parent_instance;
};

struct _PdfClass
{
  GimpPlugInClass parent_class;
};


#define PDF_TYPE  (pdf_get_type ())
#define PDF(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PDF_TYPE, Pdf))

GType                           pdf_get_type             (void) G_GNUC_CONST;

static GList                  * pdf_query_procedures     (GimpPlugIn           *plug_in);
static GimpProcedure          * pdf_create_procedure     (GimpPlugIn           *plug_in,
                                                          const gchar          *name);

static GimpValueArray         * pdf_export               (GimpProcedure        *procedure,
                                                          GimpRunMode           run_mode,
                                                          GimpImage            *image,
                                                          GFile                *file,
                                                          GimpExportOptions    *options,
                                                          GimpMetadata         *metadata,
                                                          GimpProcedureConfig  *config,
                                                          gpointer              run_data);
static GimpValueArray         * pdf_export_multi         (GimpProcedure        *procedure,
                                                          GimpProcedureConfig  *config,
                                                          gpointer              run_data);

static GimpPDBStatusType        pdf_export_image         (GimpProcedure        *procedure,
                                                          GimpProcedureConfig  *config,
                                                          GimpExportOptions    *options,
                                                          gboolean              single_image,
                                                          gboolean              show_progress,
                                                          GError              **error);

static GimpExportCapabilities   export_edit_options      (GimpProcedure        *procedure,
                                                          GimpProcedureConfig  *config,
                                                          GimpExportOptions    *options,
                                                          gpointer              create_data);

static void                     init_image_list_defaults (GimpImage            *image);

static void                     validate_image_list      (void);

static gboolean                 gui_single               (GimpProcedure        *procedure,
                                                          GimpProcedureConfig  *config,
                                                          GimpImage            *image);
static gboolean                 gui_multi                (GimpProcedure        *procedure,
                                                          GimpProcedureConfig  *config);

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            (void);

static cairo_surface_t *get_cairo_surface        (GimpDrawable         *drawable,
                                                  gboolean              as_mask,
                                                  GError              **error);

static GeglColor *      get_layer_color          (GimpLayer            *layer,
                                                  gboolean             *single);

static void             drawText                 (GimpLayer            *layer,
                                                  gdouble               opacity,
                                                  cairo_t              *cr,
                                                  gdouble               x_res,
                                                  gdouble               y_res);
static void             drawPath                 (GimpLayer            *layer,
                                                  gdouble               opacity,
                                                  cairo_t              *cr,
                                                  gdouble               x_res,
                                                  gdouble               y_res);
static void             draw_path_pattern        (cairo_t              *cr,
                                                  GimpPattern          *pattern);

static gboolean         draw_layer               (GimpImage            *image,
                                                  GimpLayer           **layers,
                                                  gint                  n_layers,
                                                  GimpProcedureConfig  *config,
                                                  gboolean              single_image,
                                                  gint                  j,
                                                  cairo_t              *cr,
                                                  gdouble               x_res,
                                                  gdouble               y_res,
                                                  const gchar          *name,
                                                  gboolean              show_progress,
                                                  gdouble               progress_start,
                                                  gdouble               progress_end,
                                                  gint                  layer_level,
                                                  GError              **error);


G_DEFINE_TYPE (Pdf, pdf, GIMP_TYPE_PLUG_IN)

GIMP_MAIN (PDF_TYPE)
DEFINE_STD_SET_I18N


static gboolean     dnd_remove = TRUE;
static PdfMultiPage multi_page;

static GtkTreeModel *model;
static GtkWidget    *file_choose;
static gchar        *file_name;


G_DEFINE_QUARK (gimp-plugin-pdf-export-error-quark, gimp_plugin_pdf_export_error)

static void
pdf_class_init (PdfClass *klass)
{
  GimpPlugInClass *plug_in_class = GIMP_PLUG_IN_CLASS (klass);

  plug_in_class->query_procedures = pdf_query_procedures;
  plug_in_class->create_procedure = pdf_create_procedure;
  plug_in_class->set_i18n         = STD_SET_I18N;
}

static void
pdf_init (Pdf *pdf)
{
}

static GList *
pdf_query_procedures (GimpPlugIn *plug_in)
{
  GList *list = NULL;

  list = g_list_append (list, g_strdup (EXPORT_PROC));
  list = g_list_append (list, g_strdup (EXPORT_MULTI_PROC));

  return list;
}

static GimpProcedure *
pdf_create_procedure (GimpPlugIn  *plug_in,
                        const gchar *name)
{
  GimpProcedure *procedure = NULL;

  if (! strcmp (name, EXPORT_PROC))
    {
      procedure = gimp_export_procedure_new (plug_in, name,
                                             GIMP_PDB_PROC_TYPE_PLUGIN,
                                             TRUE, pdf_export, NULL, NULL);

      gimp_procedure_set_image_types (procedure, "*");

      gimp_procedure_set_menu_label (procedure, _("Portable Document Format"));

      gimp_procedure_set_documentation (procedure,
                                        _("Save files in PDF format"),
                                        _("Saves files in Adobe's Portable "
                                          "Document Format. PDF is designed to "
                                          "be easily processed by a variety of "
                                          "different platforms, and is a "
                                          "distant cousin of PostScript."),
                                          name);
      gimp_procedure_set_attribution (procedure,
                                      "Barak Itkin, Lionel N., Jehan",
                                      "Copyright Barak Itkin, Lionel N., Jehan",
                                      "August 2009, 2017");

      gimp_file_procedure_set_format_name (GIMP_FILE_PROCEDURE (procedure),
                                           _("PDF"));
      gimp_file_procedure_set_mime_types (GIMP_FILE_PROCEDURE (procedure),
                                          "application/pdf");
      gimp_file_procedure_set_extensions (GIMP_FILE_PROCEDURE (procedure),
                                          "pdf");

      gimp_export_procedure_set_capabilities (GIMP_EXPORT_PROCEDURE (procedure),
                                              0, export_edit_options, NULL, NULL);

      gimp_procedure_add_boolean_argument (procedure, "vectorize",
                                           _("Convert _bitmaps to vector graphics where possible"),
                                           _("Convert bitmaps to vector graphics where possible"),
                                           TRUE,
                                           G_PARAM_READWRITE);

      gimp_procedure_add_boolean_argument (procedure, "ignore-hidden",
                                           _("O_mit hidden layers and layers with zero opacity"),
                                           _("Non-visible layers will not be exported"),
                                           TRUE,
                                           G_PARAM_READWRITE);

      gimp_procedure_add_boolean_argument (procedure, "apply-masks",
                                           _("_Apply layer masks"),
                                           _("Apply layer masks before saving (Keeping the mask "
                                             "will not change the output, only the PDF structure)"),
                                           TRUE,
                                           G_PARAM_READWRITE);

      gimp_procedure_add_boolean_argument (procedure, "layers-as-pages",
                                           _("La_yers as pages"),
                                           _("Layers as pages (bottom layers first)."),
                                           FALSE,
                                           G_PARAM_READWRITE);

      gimp_procedure_add_boolean_argument (procedure, "reverse-order",
                                           _("Re_verse order"),
                                           _("Reverse the pages order (top layers first)."),
                                           FALSE,
                                           G_PARAM_READWRITE);

      gimp_procedure_add_boolean_argument (procedure, "root-layers-only",
                                           _("Roo_t layers only"),
                                           _("Only the root layers are considered pages"),
                                           TRUE,
                                           G_PARAM_READWRITE);

      gimp_procedure_add_boolean_argument (procedure, "convert-text-layers",
                                           _("Convert te_xt layers to image"),
                                           _("Convert text layers to raster graphics"),
                                           FALSE,
                                           G_PARAM_READWRITE);

      gimp_procedure_add_boolean_argument (procedure, "fill-background-color",
                                           _("_Fill transparent areas with background color"),
                                           _("Fill transparent areas with background color if "
                                             "layer has an alpha channel"),
                                           TRUE,
                                           G_PARAM_READWRITE);
    }
  else if (! strcmp (name, EXPORT_MULTI_PROC))
    {
      procedure = gimp_procedure_new (plug_in, name,
                                      GIMP_PDB_PROC_TYPE_PLUGIN,
                                      pdf_export_multi, NULL, NULL);

      gimp_procedure_set_image_types (procedure, "*");

      gimp_procedure_set_menu_label (procedure, _("_Create multipage PDF..."));
#if 0
      gimp_procedure_add_menu_path (procedure, "<Image>/File/Create/PDF");
#endif

      gimp_procedure_set_documentation (procedure,
                                        _("Save files in PDF format"),
                                        _("Saves files in Adobe's Portable "
                                          "Document Format. PDF is designed to "
                                          "be easily processed by a variety of "
                                          "different platforms, and is a "
                                          "distant cousin of PostScript."),
                                        name);
      gimp_procedure_set_attribution (procedure,
                                      "Barak Itkin",
                                      "Copyright Barak Itkin",
                                      "August 2009");

      gimp_procedure_add_enum_argument (procedure, "run-mode",
                                        "Run mode",
                                        "The run mode",
                                        GIMP_TYPE_RUN_MODE,
                                        GIMP_RUN_INTERACTIVE,
                                        G_PARAM_READWRITE);

      gimp_procedure_add_core_object_array_argument (procedure, "images",
                                                     "Images",
                                                     "Input image for each page (An image can "
                                                     "appear more than once)",
                                                     GIMP_TYPE_IMAGE,
                                                     G_PARAM_READWRITE);

      gimp_procedure_add_boolean_argument (procedure, "vectorize",
                                           _("Convert _bitmaps to vector graphics where possible"),
                                           _("Convert bitmaps to vector graphics where possible"),
                                           TRUE,
                                           G_PARAM_READWRITE);

      gimp_procedure_add_boolean_argument (procedure, "ignore-hidden",
                                           _("O_mit hidden layers and layers with zero opacity"),
                                           _("Non-visible layers will not be exported"),
                                           TRUE,
                                           G_PARAM_READWRITE);

      gimp_procedure_add_boolean_argument (procedure, "apply-masks",
                                           _("_Apply layer masks"),
                                           _("Apply layer masks before saving (Keeping the mask "
                                             "will not change the output, only the PDF structure)"),
                                           TRUE,
                                           G_PARAM_READWRITE);

      gimp_procedure_add_boolean_argument (procedure, "fill-background-color",
                                           _("_Fill transparent areas with background color"),
                                           _("Fill transparent areas with background color if "
                                             "layer has an alpha channel"),
                                           TRUE,
                                           G_PARAM_READWRITE);

      gimp_procedure_add_string_argument (procedure, "uri",
                                          _("Save to"),
                                          _("The URI of the file to save to"),
                                          NULL,
                                          GIMP_PARAM_READWRITE);
    }

  return procedure;
}

static GimpValueArray *
pdf_export (GimpProcedure        *procedure,
            GimpRunMode           run_mode,
            GimpImage            *image,
            GFile                *file,
            GimpExportOptions    *options,
            GimpMetadata         *metadata,
            GimpProcedureConfig  *config,
            gpointer              run_data)
{
  GError            *error       = NULL;
  GimpPDBStatusType  status      = GIMP_PDB_SUCCESS;

  gegl_init (NULL, NULL);

  /* Initializing all the settings */
  multi_page.image_count = 0;

  file_name = g_file_get_path (file);

  init_image_list_defaults (image);

  validate_image_list ();

  if (run_mode == GIMP_RUN_INTERACTIVE)
    {
      if (! gui_single (procedure, config, image))
        status = GIMP_PDB_CANCEL;
    }

  if (status == GIMP_PDB_SUCCESS)
    status = pdf_export_image (procedure, config, options, TRUE,
                               (run_mode != GIMP_RUN_NONINTERACTIVE),
                               &error);

  return gimp_procedure_new_return_values (procedure, status, error);
}

static GimpValueArray *
pdf_export_multi (GimpProcedure        *procedure,
                  GimpProcedureConfig  *config,
                  gpointer              run_data)
{
  GError            *error     = NULL;
  GimpPDBStatusType  status    = GIMP_PDB_SUCCESS;
  GimpRunMode        run_mode;
  GimpExportOptions *options   = NULL;
  gchar             *uri;
  GimpImage        **images    = NULL;
  GimpImage         *image     = NULL;
  GFile             *file      = NULL;

  gegl_init (NULL, NULL);

  g_object_get (config,
                "run-mode", &run_mode,
                "uri",      &uri,
                "images",   &images,
                NULL);

  options = g_object_new (GIMP_TYPE_EXPORT_OPTIONS,
                          "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,
                          NULL);

  if (uri != NULL)
    {
      file = g_file_new_for_uri (uri);
      g_free (uri);
    }

  if (file != NULL)
    file_name = g_file_get_path (file);

  if (images)
    {
      multi_page.image_count = gimp_core_object_array_get_length ((GObject **) images);
      for (gint i = 0; i < multi_page.image_count; i++)
        multi_page.images[i] = images[i];
    }
  else
    {
      init_image_list_defaults (image);
    }

  validate_image_list ();

  /* Starting the executions */
  if (run_mode == GIMP_RUN_INTERACTIVE)
    {
      if (! gui_multi (procedure, config))
        status = GIMP_PDB_CANCEL;
    }

  if (status == GIMP_PDB_SUCCESS)
    status = pdf_export_image (procedure, config, options, FALSE,
                               (run_mode != GIMP_RUN_NONINTERACTIVE),
                               &error);

  return gimp_procedure_new_return_values (procedure, status, error);
}

static cairo_status_t
write_func (void                *fp,
            const unsigned char *data,
            unsigned int         size)
{
  return fwrite (data, 1, size, fp) == size ? CAIRO_STATUS_SUCCESS
                                            : CAIRO_STATUS_WRITE_ERROR;
}

static GList *
get_missing_fonts (GList *layers)
{
  GList *missing_fonts = NULL;
  GList *iter;

  for (iter = layers; iter; iter = iter->next)
    {
      GimpLayer *layer = iter->data;

      if (gimp_item_is_group (GIMP_ITEM (layer)))
        {
          GList *child_missing_fonts;
          GList *iter2;

          child_missing_fonts = get_missing_fonts (gimp_item_list_children (GIMP_ITEM (layer)));
          for (iter2 = child_missing_fonts; iter2; iter2 = iter2->next)
            {
              gchar *missing = iter2->data;

              if (g_list_find_custom (missing_fonts, missing, (GCompareFunc) g_strcmp0) == NULL)
                missing_fonts = g_list_prepend (missing_fonts, missing);
              else
                g_free (missing);
            }
          g_list_free (child_missing_fonts);
        }
      else if (gimp_item_is_text_layer (GIMP_ITEM (layer)))
        {
          GimpFont             *gimp_font;
          PangoFontDescription *font_description;
          PangoFontDescription *font_description2;
          PangoFontMap         *fontmap;
          PangoFont            *font;
          PangoContext         *context;

          fontmap = pango_cairo_font_map_new_for_font_type (CAIRO_FONT_TYPE_FT);
          context = pango_font_map_create_context (fontmap);

          gimp_font        = gimp_text_layer_get_font (GIMP_TEXT_LAYER (layer));
          font_description = gimp_font_get_pango_font_description (gimp_font);

          font = pango_font_map_load_font (fontmap, context, font_description);
          font_description2 = pango_font_describe (font);
          if (g_strcmp0 (pango_font_description_get_family (font_description),
                         pango_font_description_get_family (font_description2)) != 0)
            {
              const gchar *missing = pango_font_description_get_family (font_description);

              if (g_list_find_custom (missing_fonts, missing, (GCompareFunc) g_strcmp0) == NULL)
                missing_fonts = g_list_prepend (missing_fonts,
                                                g_strdup (missing));
            }

          g_object_unref (font);
          pango_font_description_free (font_description);
          pango_font_description_free (font_description2);
          g_object_unref (context);
          g_object_unref (fontmap);
        }
    }

  g_list_free (layers);

  return missing_fonts;
}

static GimpPDBStatusType
pdf_export_image (GimpProcedure        *procedure,
                  GimpProcedureConfig  *config,
                  GimpExportOptions    *options,
                  gboolean              single_image,
                  gboolean              show_progress,
                  GError              **error)
{
  cairo_surface_t *pdf_file;
  cairo_t         *cr;
  FILE            *fp;
  gint             i;
  gboolean         layers_as_pages = FALSE;

  if (single_image)
    g_object_get (config, "layers-as-pages", &layers_as_pages, NULL);

  fp = g_fopen (file_name, "wb");
  if (! fp)
    {
      g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
                   _("Could not open '%s' for writing: %s"),
                   gimp_filename_to_utf8 (file_name), g_strerror (errno));

      return GIMP_PDB_EXECUTION_ERROR;
    }

  pdf_file = cairo_pdf_surface_create_for_stream (write_func, fp, 1, 1);

  if (cairo_surface_status (pdf_file) != CAIRO_STATUS_SUCCESS)
    {
      g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
                   _("An error occurred while creating the PDF file:\n"
                     "%s\n"
                     "Make sure you entered a valid filename and that the "
                     "selected location isn't read only!"),
                   cairo_status_to_string (cairo_surface_status (pdf_file)));

      return GIMP_PDB_EXECUTION_ERROR;
    }

  cr = cairo_create (pdf_file);

  for (i = 0; i < multi_page.image_count; i++)
    {
      GimpImage     *image = multi_page.images[i];
      GimpLayer    **layers;
      gint32         n_layers;
      gdouble        x_res, y_res;
      gdouble        x_scale, y_scale;
      GimpDrawable **drawables;
      gint           j;

      drawables = gimp_image_get_selected_drawables (image);
      if (drawables[0] == NULL)
        {
          g_free (drawables);
          continue;
        }
      g_free (drawables);

      /* Save the state of the surface before any changes, so that
       * settings from one page won't affect all the others
       */

      cairo_save (cr);

      if (! (gimp_export_options_get_image (options,
                                            &image) == GIMP_EXPORT_EXPORT))
        {
          /* gimp_drawable_histogram() only works within the bounds of
           * the selection, which is a problem (see issue #2431).
           * Instead of saving the selection, unselecting to later
           * reselect, let's just always work on a duplicate of the
           * image.
           */

          image = gimp_image_duplicate (image);
        }

      gimp_selection_none (image);

      gimp_image_get_resolution (image, &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_get_width  (image) * x_scale,
                                  gimp_image_get_height (image) * 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);

      /* Cairo has a concept of user-space vs device-space units.
       * From what I understand, by default the user-space unit is the
       * typographical "point". Since we work mostly with pixels, not
       * points, the following call simply scales the transformation
       * matrix from points to pixels, relatively to the image
       * resolution, knowing that 1 typographical point == 1/72 inch.
       */

      cairo_scale (cr, x_scale, y_scale);

      layers   = gimp_image_get_layers (image);
      n_layers = gimp_core_object_array_get_length ((GObject **) layers);

      /* Now, we should loop over the layers of each image */
      for (j = 0; j < n_layers; j++)
        {
          if (! draw_layer (image, layers, n_layers, config,
                            single_image, j, cr, x_res, y_res,
                            gimp_procedure_get_name (procedure),
                            show_progress,
                            /* Progression is showed per image, and would restart at 0
                             * if you open several images.
                             */

                            (gdouble) j / n_layers,
                            (gdouble) (j + 1) / n_layers,
                            0, error))
            {
              /* free the resources */
              g_free (layers);
              cairo_surface_destroy (pdf_file);
              cairo_destroy (cr);
              fclose (fp);

              return GIMP_PDB_EXECUTION_ERROR;
            }
        }
      if (show_progress)
        gimp_progress_update (1.0);

      g_free (layers);

      /* We are done with this image - Show it!
       * Unless that's a multi-page to avoid blank page at the end
       */


      if (! layers_as_pages)
        cairo_show_page (cr);

      cairo_restore (cr);

      gimp_image_delete (image);
    }

  /* We are done with all the images - time to free the resources */
  cairo_surface_destroy (pdf_file);
  cairo_destroy (cr);

  fclose (fp);

  return GIMP_PDB_SUCCESS;
}

static GimpExportCapabilities
export_edit_options (GimpProcedure        *procedure,
                     GimpProcedureConfig  *config,
                     GimpExportOptions    *options,
                     gpointer              create_data)
{
  GimpExportCapabilities capabilities;
  gboolean               apply_masks;

  g_object_get (G_OBJECT (config),
                "apply-masks", &apply_masks,
                NULL);

  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 (! apply_masks)
    capabilities |= GIMP_EXPORT_CAN_HANDLE_LAYER_MASKS;

  return capabilities;
}

/******************************************************/
/* Beginning of parameter handling functions          */
/******************************************************/

/* A function that initializes the image list to default values */
static void
init_image_list_defaults (GimpImage *image)
{
  if (image)
    {
      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;
}


/******************************************************/
/* Beginning of GUI functions                         */
/******************************************************/

/* The main GUI function for saving single-paged PDFs */

static gboolean
gui_single (GimpProcedure       *procedure,
            GimpProcedureConfig *config,
            GimpImage           *image)
{
  GtkWidget  *window;
  GtkWidget  *widget;
  GimpLayer **layers;
  GList      *missing_fonts;
  GList      *dialog_props = NULL;
  gboolean    run;
  gint32      n_layers;

  gimp_ui_init (PLUG_IN_BINARY);

  window = gimp_export_procedure_dialog_new (GIMP_EXPORT_PROCEDURE (procedure),
                                             GIMP_PROCEDURE_CONFIG (config),
                                             image);

  gimp_procedure_dialog_fill_box (GIMP_PROCEDURE_DIALOG (window),
                                  "pages-box",
                                  "reverse-order", "root-layers-only", NULL);
  /* XXX the "layers-as-pages" checkbox label used to be changing,
   * showing "top layers first" or "bottom layers first" depending on
   * the value of "reverse-order". Should we want this? Or do it
   * differently, i.e. maybe with an additional label showing the order?
   */

  widget = gimp_procedure_dialog_fill_frame (GIMP_PROCEDURE_DIALOG (window),
                                            "pages-frame", "layers-as-pages", FALSE,
                                            "pages-box");
  /* Enable "layers-as-pages" if more than one layer, or there's a single
   * layer group has more than one layer */

  layers   = gimp_image_get_layers (multi_page.images[0]);
  n_layers = gimp_core_object_array_get_length ((GObject **) layers);

  if (n_layers == 1 && gimp_item_is_group (GIMP_ITEM (layers[0])))
    {
      GimpItem **children;

      children = gimp_item_get_children (GIMP_ITEM (layers[0]));
      n_layers = gimp_core_object_array_get_length ((GObject **) children);
      g_free (children);
    }
  g_free (layers);

  gtk_widget_set_sensitive (widget, n_layers > 1);

  /* Warning for missing fonts (non-embeddable with rasterization
   * possible).
   */

  missing_fonts = get_missing_fonts (gimp_image_list_layers (multi_page.images[0]));
  if (missing_fonts != NULL)
    {
      GList *iter;
      gchar *font_list = NULL;
      gchar *text;

      for (iter = missing_fonts; iter; iter = iter->next)
        {
          gchar *fontname = iter->data;

          if (font_list == NULL)
            {
              font_list = g_strdup (fontname);
            }
          else
            {
              gchar *tmp = font_list;

              font_list = g_strjoin (", ", tmp, fontname, NULL);
              g_free (tmp);
            }
        }

      text = g_strdup_printf (_("The following fonts cannot be found: %s.\n"
                                "It is recommended to convert your text layers to image "
                                "or to install the missing fonts before exporting, "
                                "otherwise your design may not look right."),
                              font_list);
      /* TODO: we used to have a GtkImage showing a GIMP_ICON_WILBER_EEK
       * icon in GTK_ICON_SIZE_BUTTON size, next to the label, to make
       * the warning more obvious.
       */

      widget = gimp_procedure_dialog_get_label (GIMP_PROCEDURE_DIALOG (window),
                                                "missing-fonts-label",
                                                text, FALSE, FALSE);
      gtk_label_set_line_wrap (GTK_LABEL (widget), TRUE);
      gimp_procedure_dialog_fill_frame (GIMP_PROCEDURE_DIALOG (window),
                                        "convert-text-layers-frame",
                                        "convert-text-layers", TRUE,
                                        "missing-fonts-label");

      dialog_props = g_list_prepend (dialog_props, "convert-text-layers-frame");

      g_list_free_full (missing_fonts, g_free);
      g_free (text);
      g_free (font_list);
    }
  else
    {
      dialog_props = g_list_prepend (dialog_props, "convert-text-layers");
    }

  dialog_props = g_list_prepend (dialog_props, "fill-background-color");
  dialog_props = g_list_prepend (dialog_props, "ignore-hidden");
  dialog_props = g_list_prepend (dialog_props, "vectorize");
  dialog_props = g_list_prepend (dialog_props, "apply-masks");
  dialog_props = g_list_prepend (dialog_props, "pages-frame");
  gimp_procedure_dialog_fill_list (GIMP_PROCEDURE_DIALOG (window),
                                   dialog_props);

  run = gimp_procedure_dialog_run (GIMP_PROCEDURE_DIALOG (window));

  gtk_widget_destroy (window);

  return run;
}

/* The main GUI function for saving multi-paged PDFs */

static gboolean
gui_multi (GimpProcedure       *procedure,
           GimpProcedureConfig *config)
{
  GtkWidget   *window;
  GtkWidget   *vbox;
  GtkWidget   *file_label;
  GtkWidget   *file_entry;
  GtkWidget   *file_browse;
  GtkWidget   *file_hbox;
  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);

  window = gimp_procedure_dialog_new (procedure,
                                      GIMP_PROCEDURE_CONFIG (config),
                                      _("Export Image as Multi-Page PDF"));
  gimp_procedure_dialog_set_ok_label (GIMP_PROCEDURE_DIALOG (window), _("_Export"));

  vbox = gimp_procedure_dialog_fill_box (GIMP_PROCEDURE_DIALOG (window),
                                         "multi-pdf-options-vbox",
                                         "apply-masks", "vectorize",
                                         "fill-background-color",
                                         "ignore-hidden", NULL);

  file_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5);
  file_label = gtk_label_new (_("Save to:"));
  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,
                                             _("_Save"),   GTK_RESPONSE_OK,
                                             _("_Cancel"), GTK_RESPONSE_CANCEL,
                                             NULL);

  gtk_box_pack_start (GTK_BOX (file_hbox), file_label, FALSE, FALSE, 0);
  gtk_widget_set_visible (file_label, TRUE);
  gtk_box_pack_start (GTK_BOX (file_hbox), file_entry, TRUE, TRUE, 0);
  gtk_widget_set_visible (file_entry, TRUE);
  gtk_box_pack_start (GTK_BOX (file_hbox), file_browse, FALSE, FALSE, 0);
  gtk_widget_set_visible (file_browse, TRUE);

  gtk_box_pack_start (GTK_BOX (vbox), file_hbox, TRUE, TRUE, 0);
  gtk_widget_set_visible (file_hbox, TRUE);
  gtk_box_reorder_child (GTK_BOX (vbox), file_hbox, 0);

  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_widget_set_visible (scroll, TRUE);

  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scroll),
                                  GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
  gtk_container_add (GTK_CONTAINER (scroll), page_view);
  gtk_widget_set_visible (page_view, TRUE);

  gtk_box_pack_start (GTK_BOX (vbox), scroll, TRUE, TRUE, 0);
  gtk_box_reorder_child (GTK_BOX (vbox), scroll, 1);

  h_but_box = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL);
  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 (GTK_BOX (h_but_box), del, TRUE, TRUE, 0);
  gtk_widget_set_visible (del, TRUE);

  gtk_box_pack_start (GTK_BOX (vbox), h_but_box, FALSE, FALSE, 0);
  gtk_widget_set_visible (h_but_box, TRUE);
  gtk_box_reorder_child (GTK_BOX (vbox), h_but_box, 2);

  h_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5);

  img_combo = gimp_image_combo_box_new (NULL, NULL, NULL);
  gtk_box_pack_start (GTK_BOX (h_box), img_combo, FALSE, FALSE, 0);
  gtk_widget_set_visible (img_combo, TRUE);

  add_image = gtk_button_new_with_label (_("Add this image"));
  gtk_box_pack_start (GTK_BOX (h_box), add_image, FALSE, FALSE, 0);
  gtk_widget_set_visible (add_image, TRUE);

  gtk_box_pack_start (GTK_BOX (vbox), h_box, FALSE, FALSE, 0);
  gtk_widget_set_visible (h_box, TRUE);
  gtk_box_reorder_child (GTK_BOX (vbox), h_box, 3);

  g_signal_connect (G_OBJECT (file_browse), "clicked",
                    G_CALLBACK (choose_file_call),
                    file_entry);

  g_signal_connect (G_OBJECT (add_image), "clicked",
                    G_CALLBACK (add_image_call),
                    img_combo);

  g_signal_connect (G_OBJECT (del), "clicked",
                    G_CALLBACK (del_image_call),
                    page_view);

  g_signal_connect (G_OBJECT (model), "row-deleted",
                    G_CALLBACK (remove_call),
                    NULL);

  gimp_procedure_dialog_fill (GIMP_PROCEDURE_DIALOG (window),
                              "multi-pdf-options-vbox",
                              NULL);
  run = gimp_procedure_dialog_run (GIMP_PROCEDURE_DIALOG (window));

  if (run)
    {
      run &= get_image_list ();

      temp = gtk_entry_get_text (GTK_ENTRY (file_entry));
      if (temp != NULL)
        {
          g_stpcpy (file_name, temp);
          g_object_set (config, "uri", temp, NULL);
        }
    }

  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_peek_path (file));
    }

  file_name = g_file_get_path (file);
  gtk_widget_set_visible (file_choose, FALSE);
}

/* A function to create the basic GtkTreeModel for the icon view */
static GtkTreeModel*
create_model (void)
{
  GtkListStore *model;
  guint32       i;

  /* 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 */
                              GIMP_TYPE_IMAGE); /* IMAGE */

  for (i = 0 ; i < multi_page.image_count && i < MAX_PAGE_COUNT ; i++)
    {
      GtkTreeIter  iter;
      GimpImage   *image = multi_page.images[i];
      GdkPixbuf   *pixbuf;

      pixbuf = gimp_image_get_thumbnail (image, THUMB_WIDTH, THUMB_HEIGHT,
                                         GIMP_PIXBUF_SMALL_CHECKS);

      gtk_list_store_append (model, &iter);
      gtk_list_store_set (model, &iter,
                          THUMB,       pixbuf,
                          PAGE_NUMBER, g_strdup_printf (_("Page %d"), i + 1),
                          IMAGE_NAME,  gimp_image_get_name (image),
                          IMAGE,       image,
                          -1);

      g_object_unref (pixbuf);
    }

  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;

  multi_page.image_count = 0;

  for (valid = gtk_tree_model_get_iter_first (model, &iter);
       valid;
       valid = gtk_tree_model_iter_next (model, &iter))
    {
      GimpImage *image;

      gtk_tree_model_get (model, &iter,
                          IMAGE, &image,
                          -1);

      multi_page.images[multi_page.image_count] = image;
      multi_page.image_count++;

      g_object_unref (image);
    }

  if (multi_page.image_count == 0)
    {
      g_message (_("Error! In order to save the file, at least one image "
                   "should be added!"));
      return FALSE;
    }

  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_id;
  GimpImage    *image;
  GdkPixbuf    *pixbuf;

  dnd_remove = FALSE;

  gimp_int_combo_box_get_active (img_combo, &image_id);
  image = gimp_image_get_by_id (image_id);

  store = GTK_LIST_STORE (model);

  pixbuf = gimp_image_get_thumbnail (image, THUMB_WIDTH, THUMB_HEIGHT,
                                     GIMP_PIXBUF_SMALL_CHECKS);

  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,       pixbuf,
                      IMAGE_NAME,  gimp_image_get_name (image),
                      IMAGE,       image,
                      -1);

  g_object_unref (pixbuf);

  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;
  gpointer              temp;
  guint32               len;

  dnd_remove = FALSE;

  list = gtk_icon_view_get_selected_items (GTK_ICON_VIEW (icon_view));

  len = g_list_length (list);
  if (len > 0)
    {
      gint i;

      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);
          gtk_list_store_remove (GTK_LIST_STORE (model), &item);

          gtk_tree_path_free (item_path);

          gtk_tree_row_reference_free (items[i]);
          multi_page.image_count--;
        }
    }

  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);

  for (valid = gtk_tree_model_get_iter_first (model, &iter);
       valid;
       valid = gtk_tree_model_iter_next (model, &iter))
    {
      gtk_list_store_set (store, &iter,
                          PAGE_NUMBER, g_strdup_printf (_("Page %d"), i + 1),
                          -1);
      i++;
    }
}


/******************************************************/
/* Beginning of the actual PDF functions              */
/******************************************************/

static cairo_surface_t *
get_cairo_surface (GimpDrawable  *drawable,
                   gboolean       as_mask,
                   GError       **error)
{
  GeglBuffer      *src_buffer;
  GeglBuffer      *dest_buffer;
  cairo_surface_t *surface;
  cairo_status_t   status;
  cairo_format_t   format;
  gint             width;
  gint             height;

  src_buffer = gimp_drawable_get_buffer (drawable);

  width  = gegl_buffer_get_width  (src_buffer);
  height = gegl_buffer_get_height (src_buffer);

  if (as_mask)
    format = CAIRO_FORMAT_A8;
  else if (gimp_drawable_has_alpha (drawable))
    format = CAIRO_FORMAT_ARGB32;
  else
    format = CAIRO_FORMAT_RGB24;

  surface = cairo_image_surface_create (format, width, height);

  status = cairo_surface_status (surface);
  if (status != CAIRO_STATUS_SUCCESS)
    {
      switch (status)
        {
        case CAIRO_STATUS_INVALID_SIZE:
          g_set_error_literal (error,
                               GIMP_PLUGIN_PDF_EXPORT_ERROR,
                               GIMP_PLUGIN_PDF_EXPORT_ERROR_FAILED,
                               _("Cannot handle the size (either width or height) of the image."));
          break;
        default:
          g_set_error (error,
                       GIMP_PLUGIN_PDF_EXPORT_ERROR,
                       GIMP_PLUGIN_PDF_EXPORT_ERROR_FAILED,
                       "Cairo error: %s",
                       cairo_status_to_string (status));
          break;
        }

      return NULL;
    }

  dest_buffer = gimp_cairo_surface_get_buffer (surface, NULL, TRUE);
  if (as_mask)
    {
      /* src_buffer represents a mask in "Y u8", "Y u16", etc. formats.
       * Yet cairo_mask*() functions only care about the alpha channel of
       * the surface. Hence I change the format of dest_buffer so that the
       * Y channel of src_buffer actually refers to the A channel of
       * dest_buffer/surface in Cairo.
       */

      gegl_buffer_set_format (dest_buffer, babl_format ("Y u8"));
    }

  gegl_buffer_freeze_changed (dest_buffer);
  gegl_buffer_copy (src_buffer, NULL, GEGL_ABYSS_NONE, dest_buffer, NULL);
  gegl_buffer_thaw_changed (dest_buffer);

  cairo_surface_mark_dirty (surface);

  g_object_unref (src_buffer);
  g_object_unref (dest_buffer);

  return surface;
}

/* A function to check if a drawable is single colored This allows to
 * convert bitmaps to vector where possible
 */

static GeglColor *
get_layer_color (GimpLayer *layer,
                 gboolean  *single)
{
  GeglColor *col = gegl_color_new (NULL);
  gdouble red, green, blue, alpha;
  gdouble dev, devSum;
  gdouble median, pixels, count, percentile;

  devSum = 0;
  red = 0;
  green = 0;
  blue = 0;
  alpha = 0;
  dev = 0;

  if (gimp_drawable_is_indexed (GIMP_DRAWABLE (layer)))
    {
      /* FIXME: We can't do a proper histogram on indexed layers! */
      *single = FALSE;
      gegl_color_set_rgba (col, 0.0, 0.0, 0.0, 0.0);
      return col;
    }

  if (gimp_drawable_get_bpp (GIMP_DRAWABLE (layer)) >= 3)
    {
      /* Are we in RGB mode? */

      gimp_drawable_histogram (GIMP_DRAWABLE (layer),
                               GIMP_HISTOGRAM_RED, 0.0, 1.0,
                               &red, &dev, &median, &pixels, &count, &percentile);
      devSum += dev;

      gimp_drawable_histogram (GIMP_DRAWABLE (layer),
                               GIMP_HISTOGRAM_GREEN, 0.0, 1.0,
                               &green, &dev, &median, &pixels, &count, &percentile);
      devSum += dev;

      gimp_drawable_histogram (GIMP_DRAWABLE (layer),
                               GIMP_HISTOGRAM_BLUE, 0.0, 1.0,
                               &blue, &dev, &median, &pixels, &count, &percentile);
      devSum += dev;
    }
  else
    {
      /* We are in Grayscale mode (or Indexed) */

      gimp_drawable_histogram (GIMP_DRAWABLE (layer),
                               GIMP_HISTOGRAM_VALUE, 0.0, 1.0,
                               &red, &dev, &median, &pixels, &count, &percentile);
      devSum += dev;
      green = red;
      blue = red;
    }

  if (gimp_drawable_has_alpha (GIMP_DRAWABLE (layer)))
    gimp_drawable_histogram (GIMP_DRAWABLE (layer),
                             GIMP_HISTOGRAM_ALPHA, 0.0, 1.0,
                             &alpha, &dev, &median, &pixels, &count, &percentile);
  else
    alpha = 255;

  devSum += dev;
  *single = devSum == 0;

  gegl_color_set_rgba (col, red, green, blue, alpha);
  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 (GimpLayer *layer,
          gdouble    opacity,
          cairo_t   *cr,
          gdouble    x_res,
          gdouble    y_res)
{
  GimpImageType         type   = gimp_drawable_type (GIMP_DRAWABLE (layer));
  gchar                *text   = gimp_text_layer_get_text (GIMP_TEXT_LAYER (layer));
  gchar                *markup = gimp_text_layer_get_markup (GIMP_TEXT_LAYER (layer));
  gchar                *language;
  cairo_font_options_t *options;
  gint                  x;
  gint                  y;
  GeglColor            *color;
  gdouble               rgb[3];
  GimpUnit             *unit;
  gdouble               size;
  GimpTextHintStyle     hinting;
  GimpTextJustification j;
  gboolean              justify;
  PangoAlignment        align;
  GimpTextDirection     dir;
  PangoLayout          *layout;
  PangoContext         *context;
  GimpFont             *font;
  PangoFontDescription *font_description;
  gdouble               indent;
  gdouble               line_spacing;
  gdouble               letter_spacing;
  PangoAttribute       *letter_spacing_at;
  PangoAttrList        *attr_list = pango_attr_list_new ();
  PangoFontMap         *fontmap;

  cairo_save (cr);

  options = cairo_font_options_create ();
  attr_list = pango_attr_list_new ();
  cairo_get_font_options (cr, options);

  /* Position */
  gimp_drawable_get_offsets (GIMP_DRAWABLE (layer), &x, &y);
  cairo_translate (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_RGBA_IMAGE)
    color = gimp_text_layer_get_color (GIMP_TEXT_LAYER (layer));
  else
    gimp_image_pick_color (gimp_item_get_image (GIMP_ITEM (layer)),
                           (const GimpDrawable*[2]) { GIMP_DRAWABLE (layer), NULL },
                           x, y, FALSE, FALSE, 0, &color);

  /* TODO: this export plug-in is not space-aware yet, so we draw everything as
   * sRGB for the time being.
   */

  gegl_color_get_pixel (color, babl_format_with_space ("R'G'B' double", NULL), rgb);
  cairo_set_source_rgba (cr, rgb[0], rgb[1], rgb[2], opacity);
  g_object_unref (color);

  /* Hinting */
  hinting = gimp_text_layer_get_hint_style (GIMP_TEXT_LAYER (layer));
  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 (GIMP_TEXT_LAYER (layer)))
    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
   */

  fontmap = pango_cairo_font_map_new_for_font_type (CAIRO_FONT_TYPE_FT);

  /* Font */
  font = gimp_text_layer_get_font (GIMP_TEXT_LAYER (layer));
  font_description = gimp_font_get_pango_font_description (font);

  /* This function breaks rendering with some fonts if it's called before
   * gimp_font_get_pango_font_description(). I'm still unsure why yet it
   * probably means there is a bug somewhere we must fix. Until then, let's make
   * sure we keep this order. XXX
   */

  pango_cairo_font_map_set_resolution (PANGO_CAIRO_FONT_MAP (fontmap), y_res);

  context = pango_font_map_create_context (fontmap);
  g_object_unref (fontmap);

  pango_cairo_context_set_font_options (context, options);

  /* Language */
  language = gimp_text_layer_get_language (GIMP_TEXT_LAYER (layer));
  if (language)
    pango_context_set_language (context,
                                pango_language_from_string (language));

  /* Text Direction */
  dir = gimp_text_layer_get_base_direction (GIMP_TEXT_LAYER (layer));

  switch (dir)
    {
    case GIMP_TEXT_DIRECTION_LTR:
      pango_context_set_base_dir (context, PANGO_DIRECTION_LTR);
      pango_context_set_gravity_hint (context, PANGO_GRAVITY_HINT_NATURAL);
      pango_context_set_base_gravity (context, PANGO_GRAVITY_SOUTH);
      break;

    case GIMP_TEXT_DIRECTION_RTL:
      pango_context_set_base_dir (context, PANGO_DIRECTION_RTL);
      pango_context_set_gravity_hint (context, PANGO_GRAVITY_HINT_NATURAL);
      pango_context_set_base_gravity (context, PANGO_GRAVITY_SOUTH);
      break;

    case GIMP_TEXT_DIRECTION_TTB_RTL:
      pango_context_set_base_dir (context, PANGO_DIRECTION_LTR);
      pango_context_set_gravity_hint (context, PANGO_GRAVITY_HINT_LINE);
      pango_context_set_base_gravity (context, PANGO_GRAVITY_EAST);
      break;

    case GIMP_TEXT_DIRECTION_TTB_RTL_UPRIGHT:
      pango_context_set_base_dir (context, PANGO_DIRECTION_LTR);
      pango_context_set_gravity_hint (context, PANGO_GRAVITY_HINT_STRONG);
      pango_context_set_base_gravity (context, PANGO_GRAVITY_EAST);
      break;

    case GIMP_TEXT_DIRECTION_TTB_LTR:
      pango_context_set_base_dir (context, PANGO_DIRECTION_LTR);
      pango_context_set_gravity_hint (context, PANGO_GRAVITY_HINT_LINE);
      pango_context_set_base_gravity (context, PANGO_GRAVITY_WEST);
      break;

    case GIMP_TEXT_DIRECTION_TTB_LTR_UPRIGHT:
      pango_context_set_base_dir (context, PANGO_DIRECTION_LTR);
      pango_context_set_gravity_hint (context, PANGO_GRAVITY_HINT_STRONG);
      pango_context_set_base_gravity (context, PANGO_GRAVITY_WEST);
      break;
    }

  /* We are done with the context's settings. It's time to create the
   * layout
   */

  layout = pango_layout_new (context);
  pango_layout_set_wrap (layout, PANGO_WRAP_WORD_CHAR);

  /* Font Size */
  size = gimp_text_layer_get_font_size (GIMP_TEXT_LAYER (layer), &unit);
  size = gimp_units_to_pixels (size, unit, y_res);
  pango_font_description_set_absolute_size (font_description, size * PANGO_SCALE);

  pango_layout_set_font_description (layout, font_description);

  /* Width */
  if (! PANGO_GRAVITY_IS_VERTICAL (pango_context_get_base_gravity (context)))
    pango_layout_set_width (layout,
                            gimp_drawable_get_width (GIMP_DRAWABLE (layer)) *
                            PANGO_SCALE);
  else
    pango_layout_set_width (layout,
                            gimp_drawable_get_height (GIMP_DRAWABLE (layer)) *
                            PANGO_SCALE);

  /* Justification, and Alignment */
  justify = FALSE;
  j = gimp_text_layer_get_justification (GIMP_TEXT_LAYER (layer));
  align = PANGO_ALIGN_LEFT;
  switch (j)
    {
    case GIMP_TEXT_JUSTIFY_LEFT:
      align = PANGO_ALIGN_LEFT;
      break;
    case GIMP_TEXT_JUSTIFY_RIGHT:
      align = PANGO_ALIGN_RIGHT;
      break;
    case GIMP_TEXT_JUSTIFY_CENTER:
      align = PANGO_ALIGN_CENTER;
      break;
    case GIMP_TEXT_JUSTIFY_FILL:
      align = PANGO_ALIGN_LEFT;
      justify = TRUE;
      break;
    }
  pango_layout_set_alignment (layout, align);
  pango_layout_set_justify (layout, justify);

  /* Indentation */
  indent = gimp_text_layer_get_indent (GIMP_TEXT_LAYER (layer));
  pango_layout_set_indent (layout, (int)(PANGO_SCALE * indent));

  /* Line Spacing */
  line_spacing = gimp_text_layer_get_line_spacing (GIMP_TEXT_LAYER (layer));
  pango_layout_set_spacing (layout, (int)(PANGO_SCALE * line_spacing));

  /* Letter Spacing */
  letter_spacing = gimp_text_layer_get_letter_spacing (GIMP_TEXT_LAYER (layer));
  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_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);

  if (dir == GIMP_TEXT_DIRECTION_TTB_RTL ||
      dir == GIMP_TEXT_DIRECTION_TTB_RTL_UPRIGHT)
    {
      cairo_translate (cr, gimp_drawable_get_width (GIMP_DRAWABLE (layer)), 0);
      cairo_rotate (cr, G_PI_2);
    }

  if (dir == GIMP_TEXT_DIRECTION_TTB_LTR ||
      dir == GIMP_TEXT_DIRECTION_TTB_LTR_UPRIGHT)
    {
      cairo_translate (cr, 0, gimp_drawable_get_height (GIMP_DRAWABLE (layer)));
      cairo_rotate (cr, -G_PI_2);
    }

  pango_cairo_show_layout (cr, layout);

  g_free (text);
  g_free (language);

  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);
}

static void
drawPath (GimpLayer *layer,
          gdouble    opacity,
          cairo_t   *cr,
          gdouble    x_res,
          gdouble    y_res)
{
  GimpVectorLayer *vector_layer;
  GimpPath        *path    = NULL;
  GimpPattern     *pattern = NULL;
  GeglColor       *color;
  gdouble          rgb[3];
  gboolean         set_fill;
  gboolean         set_stroke;
  gsize            num_strokes;
  gint            *strokes;
  gboolean         closed = FALSE;

  vector_layer = GIMP_VECTOR_LAYER (layer);
  path         = gimp_vector_layer_get_path (vector_layer);
  set_fill     = gimp_vector_layer_get_enable_fill (vector_layer);
  set_stroke   = gimp_vector_layer_get_enable_stroke (vector_layer);

  cairo_save (cr);

  /* Create path in Cairo */
  strokes = gimp_path_get_strokes (path, &num_strokes);
  for (gint s = 0; s < num_strokes; s++)
    {
      GimpPathStrokeType  type;
      gdouble            *control_points;
      gsize               num_points;
      gsize               num_indexes;

      type = gimp_path_stroke_get_points (path, strokes[s], &num_points,
                                          &control_points, &closed);

      if (type == GIMP_PATH_STROKE_TYPE_BEZIER)
        {
          num_indexes = num_points / 2;

          if (num_indexes >= 3)
            cairo_move_to (cr, control_points[2], control_points[3]);

          for (gint i = 2; i < (num_indexes + (closed ? 2 : - 1)); i += 3)
            {
              gdouble delta[6] = { 0 };

              for (gint j = 0; j < 3; j++)
                {
                  gint index = ((i + j) % num_indexes) * 2;

                  delta[(j * 2)]     = control_points[index];
                  delta[(j * 2) + 1] = control_points[index + 1];
                }

              cairo_curve_to (cr, delta[0], delta[1], delta[2], delta[3],
                              delta[4], delta[5]);
            }

          if (closed && num_points > 3)
            cairo_close_path (cr);
        }
    }

  /* Add fill/stroke to Cairo path */
  if (set_fill)
    {
      color = gimp_vector_layer_get_fill_color (vector_layer);
      if (color)
        {
          gegl_color_get_pixel (color,
                                babl_format_with_space ("R'G'B' double", NULL),
                                rgb);
          cairo_set_source_rgb (cr, rgb[0], rgb[1], rgb[2]);
          g_object_unref (color);
        }
      else
        {
          pattern = gimp_vector_layer_get_fill_pattern (vector_layer);

          draw_path_pattern (cr, pattern);
        }

      if (set_stroke)
        cairo_fill_preserve (cr);
      else
        cairo_fill (cr);
    }

  if (set_stroke)
    {
      cairo_line_join_t  join_style;
      cairo_line_cap_t   cap_style;
      gdouble            stroke_width;
      gdouble            miter_limit;
      gdouble            dash_offset;
      gsize              num_dashes;
      gdouble           *dash_pattern;

      color = gimp_vector_layer_get_stroke_color (vector_layer);
      if (color)
        {
          gegl_color_get_pixel (color,
                                babl_format_with_space ("R'G'B' double", NULL),
                                rgb);
          cairo_set_source_rgb (cr, rgb[0], rgb[1], rgb[2]);
          g_object_unref (color);
        }
      else
        {
          pattern = gimp_vector_layer_get_stroke_pattern (vector_layer);

          draw_path_pattern (cr, pattern);
        }

      stroke_width = gimp_vector_layer_get_stroke_width (vector_layer);
      cairo_set_line_width (cr, stroke_width);

      miter_limit = gimp_vector_layer_get_stroke_miter_limit (vector_layer);
      cairo_set_miter_limit (cr, miter_limit);

      switch (gimp_vector_layer_get_stroke_cap_style (vector_layer))
        {
        case GIMP_CAP_ROUND:
          cap_style = CAIRO_LINE_CAP_ROUND;
          break;
        case GIMP_CAP_SQUARE:
          cap_style = CAIRO_LINE_CAP_SQUARE;
          break;
        default:
          cap_style = CAIRO_LINE_CAP_BUTT;
          break;
        }
      cairo_set_line_cap (cr, cap_style);

      switch (gimp_vector_layer_get_stroke_join_style (vector_layer))
        {
        case GIMP_JOIN_ROUND:
          join_style = CAIRO_LINE_JOIN_ROUND;
          break;
        case GIMP_JOIN_BEVEL:
          join_style = CAIRO_LINE_JOIN_BEVEL;
          break;
        default:
          join_style = CAIRO_LINE_JOIN_MITER;
          break;
        }
      cairo_set_line_join (cr, join_style);

      dash_offset = gimp_vector_layer_get_stroke_dash_offset (vector_layer);
      gimp_vector_layer_get_stroke_dash_pattern (vector_layer, &num_dashes,
                                                 &dash_pattern);
      if (num_dashes > 0)
        {
          for (gint i = 0; i < num_dashes; i++)
            dash_pattern[i] *= stroke_width;

          cairo_set_dash (cr, dash_pattern, num_dashes, dash_offset);
        }

      cairo_stroke (cr);
    }

  cairo_restore (cr);
}

static void
draw_path_pattern (cairo_t     *cr,
                   GimpPattern *pattern)
{
  cairo_surface_t *pattern_image = NULL;
  GimpImage       *temp_image;
  GimpLayer       *temp_layer;
  gint             width;
  gint             height;
  GeglBuffer      *src_buffer;
  GeglBuffer      *dst_buffer;

  src_buffer = gimp_pattern_get_buffer (pattern, 0, 0,
                                        babl_format ("R'G'B' u8"));
  width      = gegl_buffer_get_width (src_buffer);
  height     = gegl_buffer_get_height (src_buffer);
  temp_image = gimp_image_new (width, height, GIMP_RGB);
  temp_layer = gimp_layer_new (temp_image, NULL, width, height,
                               GIMP_RGBA_IMAGE, 100.0,
                               GIMP_LAYER_MODE_NORMAL);
  gimp_image_insert_layer (temp_image, temp_layer, NULL, 0);
  dst_buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (temp_layer));
  gegl_buffer_copy (src_buffer, NULL, GEGL_ABYSS_NONE, dst_buffer, NULL);

  g_object_unref (src_buffer);
  g_object_unref (dst_buffer);

  pattern_image = get_cairo_surface (GIMP_DRAWABLE (temp_layer), FALSE,
                                     NULL);
  gimp_image_delete (temp_image);

  cairo_set_source_surface (cr, pattern_image, 0, 0);
  cairo_surface_destroy (pattern_image);
  cairo_pattern_set_extend (cairo_get_source (cr), CAIRO_EXTEND_REPEAT);
}

static gboolean
draw_layer (GimpImage            *image,
            GimpLayer           **layers,
            gint                  n_layers,
            GimpProcedureConfig  *config,
            gboolean              single_image,
            gint                  j,
            cairo_t              *cr,
            gdouble               x_res,
            gdouble               y_res,
            const gchar          *name,
            gboolean              show_progress,
            gdouble               progress_start,
            gdouble               progress_end,
            gint                  layer_level,
            GError              **error)
{
  GimpLayer *layer;
  gdouble    opacity;
  gboolean   vectorize;
  gboolean   ignore_hidden;
  gboolean   layers_as_pages = FALSE;
  gboolean   reverse_order   = FALSE;
  gboolean   root_layers_only;
  gboolean   convert_text;
  gboolean   fill_background_color;

  g_object_get (config,
                "vectorize",             &vectorize,
                "ignore-hidden",         &ignore_hidden,
                "fill-background-color", &fill_background_color,
                NULL);

  if (single_image)
    g_object_get (config,
                  "layers-as-pages",     &layers_as_pages,
                  "reverse-order",       &reverse_order,
                  "root-layers-only",    &root_layers_only,
                  "convert-text-layers", &convert_text,
                  NULL);

  if (reverse_order && layers_as_pages)
    layer = layers [j];
  else
    layer = layers [n_layers - j - 1];

  opacity = gimp_layer_get_opacity (layer) / 100.0;
  if ((! gimp_item_get_visible (GIMP_ITEM (layer)) || opacity == 0.0) &&
      ignore_hidden)
    return TRUE;

  if (gimp_item_is_group (GIMP_ITEM (layer)))
    {
      GimpItem **children;
      gint       children_num = 0;
      gint       i;

      children     = gimp_item_get_children (GIMP_ITEM (layer));
      children_num = gimp_core_object_array_get_length ((GObject **) children);

      for (i = 0; children[i] != NULL; i++)
        {
          if (! draw_layer (image, (GimpLayer **) children, children_num,
                            config, single_image, i,
                            cr, x_res, y_res, name,
                            show_progress,
                            progress_start + i * (progress_end - progress_start) / children_num,
                            progress_end,
                            layer_level + 1, error))
            {
              g_free (children);
              return FALSE;
            }
        }
      g_free (children);

      if (root_layers_only && layers_as_pages &&
          children_num > 0 && layer_level == 0)
        cairo_show_page (cr);
    }
  else
    {
      cairo_surface_t *mask_image = NULL;
      GimpLayerMask   *mask       = NULL;
      gint             x, y;
      gboolean         fill_page  = FALSE;

      if (show_progress)
        gimp_progress_update (progress_start);

      /* First fill layer with opaque background color when either
       * multi-layered or this is the first layer at the top level
       * and user chose that option.
       * Later we paint over it with the layer's content.
       */

      if (layers_as_pages)
        {
          if ((root_layers_only && (layer_level == 0 || j == 0)) ||
              ! root_layers_only)
            fill_page = TRUE;
        }
      else
        {
          if (layer_level == 0 && j == 0)
            fill_page = TRUE;
        }

      if (fill_page && fill_background_color)
        {
          GeglColor *color = NULL;
          gdouble    rgb[3];

          cairo_rectangle (cr, 0.0, 0.0,
                           gimp_image_get_width  (image),
                           gimp_image_get_height (image));
          color = gimp_context_get_background ();
          gegl_color_get_pixel (color, babl_format ("R'G'B' double"), rgb);
          cairo_set_source_rgb (cr, rgb[0], rgb[1], rgb[2]);
          cairo_fill (cr);
          g_clear_object (&color);
        }

      /* Only render layer mask if it's not disabled */
      if (gimp_layer_get_mask (layer) && gimp_layer_get_apply_mask (layer))
        mask = gimp_layer_get_mask (layer);

      if (mask)
        {
          mask_image = get_cairo_surface (GIMP_DRAWABLE (mask), TRUE,
                                          error);

          if (*error)
            return FALSE;
        }

      gimp_drawable_get_offsets (GIMP_DRAWABLE (layer), &x, &y);

      if ((! gimp_item_is_text_layer (GIMP_ITEM (layer))    &&
           ! gimp_item_is_vector_layer (GIMP_ITEM (layer))) ||
          convert_text)
        {
          /* For raster layers */

          GeglColor *layer_color;
          gboolean    single_color = FALSE;

          layer_color = get_layer_color (layer, &single_color);

          cairo_rectangle (cr, x, y,
                           gimp_drawable_get_width  (GIMP_DRAWABLE (layer)),
                           gimp_drawable_get_height (GIMP_DRAWABLE (layer)));

          if (vectorize && single_color)
            {
              gimp_cairo_set_source_color (cr, layer_color, NULL, FALSE, NULL);
              if (mask)
                cairo_mask_surface (cr, mask_image, x, y);
              else
                cairo_fill (cr);
            }
          else
            {
              cairo_surface_t *layer_image;

              layer_image = get_cairo_surface (GIMP_DRAWABLE (layer), FALSE,
                                               error);

              if (*error)
                return FALSE;

              cairo_clip (cr);

              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)
                cairo_mask_surface (cr, mask_image, x, y);
              else
                cairo_paint (cr);

              cairo_reset_clip (cr);

              cairo_surface_destroy (layer_image);
            }

          g_object_unref (layer_color);
        }
      else if (gimp_item_is_text_layer (GIMP_ITEM (layer)))
        {
          /* For text layers */
          drawText (layer, opacity, cr, x_res, y_res);
        }
      else if (gimp_item_is_vector_layer (GIMP_ITEM (layer)))
        {
          drawPath (layer, opacity, cr, x_res, y_res);
        }

      /* draw new page if "layers as pages" option is checked */
      if (layers_as_pages && (! root_layers_only || layer_level == 0))
        cairo_show_page (cr);

      /* We are done with the layer - time to free some resources */
      if (mask)
        cairo_surface_destroy (mask_image);
    }

  return TRUE;
}

Messung
CHZG
829710093

¤ Dauer der Verarbeitung: 0.67 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover


Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.