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 119 kB image not shown  

Quelle  file-ps.c   Sprache: C

 

/* GIMP - The GNU Image Manipulation Program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 * PostScript file plugin
 * PostScript writing and GhostScript interfacing code
 * Copyright (C) 1997-98 Peter Kirchgessner
 * (email: peter@kirchgessner.net, WWW: http://www.kirchgessner.net)
 *
 * Added controls for TextAlphaBits and GraphicsAlphaBits
 *   George White <aa056@chebucto.ns.ca>
 *
 * Added Ascii85 encoding
 *   Austin Donnelly <austin@gimp.org>
 *
 * 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/>.
 *
 */


/* Event history:
 * V 0.90, PK, 28-Mar-97: Creation.
 * V 0.91, PK, 03-Apr-97: Clip everything outside BoundingBox.
 *             24-Apr-97: Multi page read support.
 * V 1.00, PK, 30-Apr-97: PDF support.
 * V 1.01, PK, 05-Oct-97: Parse rc-file.
 * V 1.02, GW, 09-Oct-97: Antialiasing support.
 *         PK, 11-Oct-97: No progress bars when running non-interactive.
 *                        New procedure file_ps_load_setargs to set
 *                        load-arguments non-interactively.
 *                        If GS_OPTIONS are not set, use at least "-dSAFER"
 * V 1.03, nn, 20-Dec-97: Initialize some variables
 * V 1.04, PK, 20-Dec-97: Add Encapsulated PostScript output and preview
 * V 1.05, PK, 21-Sep-98: Write b/w-images (indexed) using image-operator
 * V 1.06, PK, 22-Dec-98: Fix problem with writing color PS files.
 *                        Ghostview may hang when displaying the files.
 * V 1.07, PK, 14-Sep-99: Add resolution to image
 * V 1.08, PK, 16-Jan-2000: Add PostScript-Level 2 by Austin Donnelly
 * V 1.09, PK, 15-Feb-2000: Force showpage on EPS-files
 *                          Add "RunLength" compression
 *                          Fix problem with "Level 2" toggle
 * V 1.10, PK, 15-Mar-2000: For load EPSF, allow negative Bounding Box Values
 *                          Save PS: don't start lines of image data with %%
 *                          to prevent problems with stupid PostScript
 *                          analyzer programs (Stanislav Brabec)
 *                          Add BeginData/EndData comments
 *                          Save PS: Set default rotation to 0
 * V 1.11, PK, 20-Aug-2000: Fix problem with BoundingBox recognition
 *                          for Mac files.
 *                          Fix problem with loop when reading not all
 *                          images of a multi page file.
 *         PK, 31-Aug-2000: Load PS: Add checks for space in filename.
 * V 1.12  PK, 19-Jun-2001: Fix problem with command line switch --
 *                          (reported by Ferenc Wagner)
 * V 1.13  PK, 07-Apr-2002: Fix problem with DOS binary EPS files
 * V 1.14  PK, 14-May-2002: Workaround EPS files of Adb. Ill. 8.0
 * V 1.15  PK, 04-Oct-2002: Be more accurate with using BoundingBox
 * V 1.16  PK, 22-Jan-2004: Don't use popen(), use g_spawn_async_with_pipes()
 *                          or g_spawn_sync().
 * V 1.17  PK, 19-Sep-2004: Fix problem with interpretation of bounding box
 */


#include "config.h"

#include <errno.h>
#include <string.h>
#include <time.h>

#include <sys/types.h>

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#include <glib/gstdio.h>

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

#include "libgimp/stdplugins-intl.h"

#include <ghostscript/ierrors.h>
#include <ghostscript/iapi.h>
#include <ghostscript/gdevdsp.h>


#define VERSION 1.17
static const gchar dversion[] = "v1.17  19-Sep-2004";

#define LOAD_PS_PROC       "file-ps-load"
#define LOAD_EPS_PROC      "file-eps-load"
#define LOAD_PS_THUMB_PROC "file-ps-load-thumb"
#define EXPORT_PS_PROC     "file-ps-export"
#define EXPORT_EPS_PROC    "file-eps-export"
#define PLUG_IN_BINARY     "file-ps"
#define PLUG_IN_ROLE       "gimp-file-ps"

#define STR_LENGTH     64


typedef struct _PostScript      PostScript;
typedef struct _PostScriptClass PostScriptClass;

struct _PostScript
{
  GimpPlugIn      parent_instance;
};

struct _PostScriptClass
{
  GimpPlugInClass parent_class;
};


#define PS_TYPE  (ps_get_type ())
#define PS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PS_TYPE, PostScript))

GType                   ps_get_type         (void) G_GNUC_CONST;

static GList          * ps_query_procedures (GimpPlugIn            *plug_in);
static GimpProcedure  * ps_create_procedure (GimpPlugIn            *plug_in,
                                             const gchar           *name);

static gboolean         ps_extract          (GimpProcedure         *procedure,
                                             GimpRunMode            run_mode,
                                             GFile                 *file,
                                             GimpMetadata          *metadata,
                                             GimpProcedureConfig   *config,
                                             GimpVectorLoadData    *extracted_dimensions,
                                             gpointer              *data_for_run,
                                             GDestroyNotify        *data_for_run_destroy,
                                             gpointer               extract_data,
                                             GError               **error);
static GimpValueArray * ps_load             (GimpProcedure         *procedure,
                                             GimpRunMode            run_mode,
                                             GFile                 *file,
                                             gint                   width,
                                             gint                   height,
                                             GimpVectorLoadData     extracted_data,
                                             GimpMetadata          *metadata,
                                             GimpMetadataLoadFlags *flags,
                                             GimpProcedureConfig   *config,
                                             gpointer               data_from_extract,
                                             gpointer               run_data);
static GimpValueArray * ps_load_thumb       (GimpProcedure         *procedure,
                                             GFile                 *file,
                                             gint                   size,
                                             GimpProcedureConfig   *config,
                                             gpointer               run_data);
static GimpValueArray * ps_export           (GimpProcedure         *procedure,
                                             GimpRunMode            run_mode,
                                             GimpImage             *image,
                                             GFile                 *file,
                                             GimpExportOptions     *options,
                                             GimpMetadata          *metadata,
                                             GimpProcedureConfig   *config,
                                             gpointer               run_data);

static GimpImage      * load_image          (GFile                 *file,
                                             GimpProcedureConfig   *config,
                                             GError               **error);
static gboolean         export_image        (GFile                 *file,
                                             GObject               *config,
                                             GimpImage             *image,
                                             GimpDrawable          *drawable,
                                             GError               **error);

static void      ps_set_save_size (GObject           *config,
                                   GimpImage         *image);

static gboolean  save_ps_header   (GOutputStream     *output,
                                   GFile             *file,
                                   GObject           *config,
                                   GError           **error);
static gboolean  save_ps_setup    (GOutputStream     *output,
                                   GimpDrawable      *drawable,
                                   gint               width,
                                   gint               height,
                                   GObject           *config,
                                   gint               bpp,
                                   GError           **error);
static gboolean  save_ps_trailer  (GOutputStream     *output,
                                   GError           **error);

static gboolean  save_ps_preview  (GOutputStream     *output,
                                   GimpDrawable      *drawable,
                                   GObject           *config,
                                   GError           **error);

static gboolean  save_gray        (GOutputStream     *output,
                                   GimpImage         *image,
                                   GimpDrawable      *drawable,
                                   GObject           *config,
                                   GError           **error);
static gboolean  save_bw          (GOutputStream     *output,
                                   GimpImage         *image,
                                   GimpDrawable      *drawable,
                                   GObject           *config,
                                   GError           **error);
static gboolean  save_index       (GOutputStream     *output,
                                   GimpImage         *image,
                                   GimpDrawable      *drawable,
                                   GObject           *config,
                                   GError           **error);
static gboolean  save_rgb         (GOutputStream     *output,
                                   GimpImage         *image,
                                   GimpDrawable      *drawable,
                                   GObject           *config,
                                   GError           **error);

static gboolean  print            (GOutputStream     *output,
                                   GError           **error,
                                   const gchar       *format,
                                   ...) G_GNUC_PRINTF (3, 4);

static GimpImage * create_new_image (GFile             *file,
                                     guint              pagenum,
                                     guint              width,
                                     guint              height,
                                     GimpImageBaseType  type,
                                     GimpLayer        **layer);

static void      check_load_vals  (GimpProcedureConfig *config);
static void      check_save_vals  (GimpProcedureConfig *config);

static gint      page_in_list     (gchar               *list,
                                   guint                pagenum);

static gint      get_bbox         (GFile               *file,
                                   gint                *x0,
                                   gint                *y0,
                                   gint                *x1,
                                   gint                *y1);

static gboolean  ps_read_header   (GFile               *file,
                                   GimpProcedureConfig *config,
                                   gboolean            *is_pdf,
                                   gboolean            *is_epsf,
                                   gint                *bbox_x0,
                                   gint                *bbox_y0,
                                   gint                *bbox_x1,
                                   gint                *bbox_y1);
static FILE    * ps_open          (GFile               *file,
                                   GimpProcedureConfig *config,
                                   gint                *llx,
                                   gint                *lly,
                                   gint                *urx,
                                   gint                *ury,
                                   gboolean            *is_epsf,
                                   gchar              **tmp_filename);

static void      ps_close         (FILE              *ifp,
                                   gchar             *tmp_filename);
static gint      read_pnmraw_type (FILE              *ifp,
                                   gint              *width,
                                   gint              *height,
                                   gint              *maxval);

static gboolean  skip_ps          (FILE              *ifp);

static GimpImage * load_ps        (GFile             *file,
                                   guint              pagenum,
                                   FILE              *ifp,
                                   gint               llx,
                                   gint               lly,
                                   gint               urx,
                                   gint               ury);

static void      dither_grey      (const guchar      *grey,
                                   guchar            *bw,
                                   gint               npix,
                                   gint               linecount);


/* Dialog-handling */

static gint32    count_ps_pages   (GFile              *file);
static gboolean  load_dialog      (GFile              *file,
                                   GimpVectorLoadData  extracted_data,
                                   GimpProcedure      *procedure,
                                   GObject            *config);


typedef struct
{
  GtkAdjustment *adjustment[4];
  gint           level;
} SaveDialogVals;

static gboolean  save_dialog               (GimpProcedure       *procedure,
                                            GObject             *config,
                                            GimpImage           *image);
static void      save_unit_changed_update  (GimpProcedureConfig *config);


G_DEFINE_TYPE (PostScript, ps, GIMP_TYPE_PLUG_IN)

GIMP_MAIN (PS_TYPE)
DEFINE_STD_SET_I18N


static const char hex[] = "0123456789abcdef";

/* The run mode */
static GimpRunMode l_run_mode;


static void
ps_class_init (PostScriptClass *klass)
{
  GimpPlugInClass *plug_in_class = GIMP_PLUG_IN_CLASS (klass);

  plug_in_class->query_procedures = ps_query_procedures;
  plug_in_class->create_procedure = ps_create_procedure;
  plug_in_class->set_i18n         = STD_SET_I18N;
}

static void
ps_init (PostScript *ps)
{
}

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

  list = g_list_append (list, g_strdup (LOAD_PS_THUMB_PROC));
  list = g_list_append (list, g_strdup (LOAD_PS_PROC));
  list = g_list_append (list, g_strdup (LOAD_EPS_PROC));
  list = g_list_append (list, g_strdup (EXPORT_PS_PROC));
  list = g_list_append (list, g_strdup (EXPORT_EPS_PROC));

  return list;
}

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

  if (! strcmp (name, LOAD_PS_PROC) ||
      ! strcmp (name, LOAD_EPS_PROC))
    {
      procedure = gimp_vector_load_procedure_new (plug_in, name,
                                                  GIMP_PDB_PROC_TYPE_PLUGIN,
                                                  ps_extract, NULL, NULL,
                                                  ps_load, NULL, NULL);

      if (! strcmp (name, LOAD_PS_PROC))
        {
          gimp_procedure_set_menu_label (procedure, _("PostScript document"));

          gimp_procedure_set_documentation (procedure,
                                            _("Load PostScript documents"),
                                            _("Load PostScript documents"),
                                            name);

          gimp_file_procedure_set_format_name (GIMP_FILE_PROCEDURE (procedure),
                                               _("PostScript"));
          gimp_file_procedure_set_mime_types (GIMP_FILE_PROCEDURE (procedure),
                                              "application/postscript");
          gimp_file_procedure_set_extensions (GIMP_FILE_PROCEDURE (procedure),
                                              "ps");
          gimp_file_procedure_set_magics (GIMP_FILE_PROCEDURE (procedure),
                                          "0,string,%!,0,long,0xc5d0d3c6");
        }
      else
        {
          gimp_procedure_set_menu_label (procedure,
                                         _("Encapsulated PostScript image"));

          gimp_procedure_set_documentation (procedure,
                                            _("Load Encapsulated PostScript images"),
                                            _("Load Encapsulated PostScript images"),
                                            name);

          gimp_file_procedure_set_format_name (GIMP_FILE_PROCEDURE (procedure),
                                               _("Encapsulated PostScript"));
          gimp_file_procedure_set_mime_types (GIMP_FILE_PROCEDURE (procedure),
                                              "image/x-eps");
          gimp_file_procedure_set_extensions (GIMP_FILE_PROCEDURE (procedure),
                                              "eps");
          gimp_file_procedure_set_magics (GIMP_FILE_PROCEDURE (procedure),
                                          "0,string,%!,0,long,0xc5d0d3c6");
        }

      gimp_procedure_set_attribution (procedure,
                                      "Peter Kirchgessner <peter@kirchgessner.net>",
                                      "Peter Kirchgessner",
                                      dversion);

      gimp_load_procedure_set_thumbnail_loader (GIMP_LOAD_PROCEDURE (procedure),
                                                LOAD_PS_THUMB_PROC);

      gimp_procedure_add_boolean_argument (procedure, "check-bbox",
                                           _("Try _Bounding Box"),
                                           _("FALSE: Use width/height, TRUE: Use BoundingBox"),
                                           TRUE,
                                           GIMP_PARAM_READWRITE);

      gimp_procedure_add_string_argument (procedure, "pages",
                                          _("_Pages"),
                                          _("Pages to load (e.g.: 1,3,5-7)"),
                                          "1",
                                          GIMP_PARAM_READWRITE);

      gimp_procedure_add_choice_argument (procedure, "coloring",
                                          _("Colorin_g"),
                                          _("Import color format"),
                                          gimp_choice_new_with_values ("bw",        4, _("B/W"),        NULL,
                                                                       "grayscale", 5, _("Gray"),       NULL,
                                                                       "rgb",       6, _("Color"),      NULL,
                                                                       "automatic", 7, _("Automatic"),  NULL,
                                                                       NULL),
                                          "rgb", G_PARAM_READWRITE);

      gimp_procedure_add_choice_argument (procedure, "text-alpha-bits",
                                          _("Te_xt anti-aliasing"),
                                          _("Text anti-aliasing strength"),
                                          gimp_choice_new_with_values ("none",   1, _("None"),   NULL,
                                                                       "weak",   2, _("Weak"),   NULL,
                                                                       "strong", 4, _("Strong"), NULL,
                                                                       NULL),
                                          "none", G_PARAM_READWRITE);

      gimp_procedure_add_choice_argument (procedure, "graphic-alpha-bits",
                                          _("Gra_phic anti-aliasing"),
                                          _("Graphic anti-aliasing strength"),
                                          gimp_choice_new_with_values ("none",   1, _("None"),   NULL,
                                                                       "weak",   2, _("Weak"),   NULL,
                                                                       "strong", 4, _("Strong"), NULL,
                                                                       NULL),
                                          "none", G_PARAM_READWRITE);
    }
  else if (! strcmp (name, LOAD_PS_THUMB_PROC))
    {
      procedure = gimp_thumbnail_procedure_new (plug_in, name,
                                                GIMP_PDB_PROC_TYPE_PLUGIN,
                                                ps_load_thumb, NULL, NULL);

      gimp_procedure_set_documentation (procedure,
                                        _("Loads a small preview from a "
                                          "PostScript or PDF document"),
                                        "",
                                        name);
      gimp_procedure_set_attribution (procedure,
                                      "Peter Kirchgessner <peter@kirchgessner.net>",
                                      "Peter Kirchgessner",
                                      dversion);
    }
  else if (! strcmp (name, EXPORT_PS_PROC) ||
           ! strcmp (name, EXPORT_EPS_PROC))
    {
      procedure = gimp_export_procedure_new (plug_in, name,
                                             GIMP_PDB_PROC_TYPE_PLUGIN,
                                             FALSE, ps_export, NULL, NULL);

      if (! strcmp (name, EXPORT_PS_PROC))
        {
          gimp_procedure_set_menu_label (procedure, _("PostScript document"));

          gimp_procedure_set_documentation (procedure,
                                            _("Export image as PostScript document"),
                                            _("PostScript exporting handles all "
                                              "image types except those with alpha "
                                              "channels."),
                                            name);

          gimp_file_procedure_set_format_name (GIMP_FILE_PROCEDURE (procedure),
                                               _("PS"));
          gimp_file_procedure_set_mime_types (GIMP_FILE_PROCEDURE (procedure),
                                              "application/postscript");
          gimp_file_procedure_set_extensions (GIMP_FILE_PROCEDURE (procedure),
                                              "ps");
        }
      else
        {
          gimp_procedure_set_menu_label (procedure,
                                         _("Encapsulated PostScript"));

          gimp_procedure_set_documentation (procedure,
                                            _("Export image as Encapsulated "
                                              "PostScript image"),
                                            _("PostScript exporting handles all "
                                              "image types except those with alpha "
                                              "channels."),
                                            name);

          gimp_file_procedure_set_format_name (GIMP_FILE_PROCEDURE (procedure),
                                               _("EPS"));
          gimp_file_procedure_set_mime_types (GIMP_FILE_PROCEDURE (procedure),
                                              "application/x-eps");
          gimp_file_procedure_set_extensions (GIMP_FILE_PROCEDURE (procedure),
                                              "eps");
        }

      gimp_procedure_set_image_types (procedure, "RGB, GRAY, INDEXED");

      gimp_procedure_set_attribution (procedure,
                                      "Peter Kirchgessner <peter@kirchgessner.net>",
                                      "Peter Kirchgessner",
                                      dversion);

      gimp_file_procedure_set_handles_remote (GIMP_FILE_PROCEDURE (procedure),
                                              TRUE);

      gimp_export_procedure_set_capabilities (GIMP_EXPORT_PROCEDURE (procedure),
                                              GIMP_EXPORT_CAN_HANDLE_RGB  |
                                              GIMP_EXPORT_CAN_HANDLE_GRAY |
                                              GIMP_EXPORT_CAN_HANDLE_INDEXED,
                                              NULL, NULL, NULL);

      gimp_procedure_add_double_argument (procedure, "width",
                                          _("_Width"),
                                          _("Width of the image in PostScript file "
                                            "(0: use input image size)"),
                                          0, GIMP_MAX_IMAGE_SIZE, 287.0,
                                          G_PARAM_READWRITE);

      gimp_procedure_add_double_argument (procedure, "height",
                                          _("_Height"),
                                          _("Height of the image in PostScript file "
                                            "(0: use input image size)"),
                                          0, GIMP_MAX_IMAGE_SIZE, 200.0,
                                          G_PARAM_READWRITE);

      gimp_procedure_add_double_argument (procedure, "x-offset",
                                          _("_X offset"),
                                          _("X-offset to image from lower left corner"),
                                          -GIMP_MAX_IMAGE_SIZE, GIMP_MAX_IMAGE_SIZE, 5.0,
                                          G_PARAM_READWRITE);

      gimp_procedure_add_double_argument (procedure, "y-offset",
                                          _("Y o_ffset"),
                                          _("Y-offset to image from lower left corner"),
                                          -GIMP_MAX_IMAGE_SIZE, GIMP_MAX_IMAGE_SIZE, 5.0,
                                          G_PARAM_READWRITE);

      gimp_procedure_add_choice_argument (procedure, "unit",
                                          _("_Unit"),
                                          _("Unit of measure for offset values"),
                                          gimp_choice_new_with_values ("inch",       0, _("Inch"),        NULL,
                                                                       "millimeter", 1, _("Millimeter"),   NULL,
                                                                       NULL),
                                          "millimeter", G_PARAM_READWRITE);

      gimp_procedure_add_boolean_argument (procedure, "keep-ratio",
                                           _("_Keep aspect ratio"),
                                           _("If enabled, aspect ratio will be maintained on export. "
                                             "Otherwise, the width and height values will be used."),
                                           TRUE,
                                           G_PARAM_READWRITE);

      gimp_procedure_add_int_argument (procedure, "rotation",
                                       _("Rotation"),
                                       "0, 90, 180, 270",
                                       0, 270, 0,
                                       G_PARAM_READWRITE);

      gimp_procedure_add_boolean_argument (procedure, "level",
                                           _("PostScript Level _2"),
                                           _("If enabled, export in PostScript Level 2 format. "
                                             "Otherwise, export in PostScript Level 1 format."),
                                           TRUE,
                                           G_PARAM_READWRITE);

      gimp_procedure_add_boolean_argument (procedure, "eps-flag",
                                           _("Encapsula_ted PostScript"),
                                           _("If enabled, export as Encapsulated PostScript. "
                                             "Otherwise, export as PostScript."),
                                           FALSE,
                                           G_PARAM_READWRITE);

      gimp_procedure_add_boolean_argument (procedure, "show-preview",
                                           _("_Preview"),
                                           _("Show Preview"),
                                           FALSE,
                                           G_PARAM_READWRITE);

      gimp_procedure_add_int_argument (procedure, "preview",
                                       _("Preview Si_ze"),
                                       _("Maximum size of preview. Set to 0 for no preview."),
                                       0, GIMP_MAX_IMAGE_SIZE, 256,
                                       G_PARAM_READWRITE);
    }

  return procedure;
}

static gboolean
ps_extract (GimpProcedure        *procedure,
            GimpRunMode           run_mode,
            GFile                *file,
            GimpMetadata         *metadata,
            GimpProcedureConfig  *config,
            GimpVectorLoadData   *extracted_dimensions,
            gpointer             *data_for_run,
            GDestroyNotify       *data_for_run_destroy,
            gpointer              extract_data,
            GError              **error)
{
  gboolean has_bbox;
  gboolean is_pdf;
  gboolean is_epsf;
  gint     bbox_x0 = 0;
  gint     bbox_y0 = 0;
  gint     bbox_x1 = 0;
  gint     bbox_y1 = 0;

  has_bbox = ps_read_header (file, NULL, &is_pdf, &is_epsf,
                             &bbox_x0, &bbox_y0, &bbox_x1, &bbox_y1);

  if (has_bbox)
    {
      extracted_dimensions->width         = (gdouble) bbox_x1 - bbox_x0;
      extracted_dimensions->height        = (gdouble) bbox_y1 - bbox_y0;
      extracted_dimensions->width_unit    = gimp_unit_point ();
      extracted_dimensions->height_unit   = gimp_unit_point ();
      extracted_dimensions->exact_width   = TRUE;
      extracted_dimensions->exact_height  = TRUE;
      extracted_dimensions->correct_ratio = TRUE;

      return TRUE;
    }
  else
    {
      return FALSE;
    }
}

static GimpValueArray *
ps_load (GimpProcedure         *procedure,
         GimpRunMode            run_mode,
         GFile                 *file,
         gint                   width,
         gint                   height,
         GimpVectorLoadData     extracted_data,
         GimpMetadata          *metadata,
         GimpMetadataLoadFlags *flags,
         GimpProcedureConfig   *config,
         gpointer               data_from_extract,
         gpointer               run_data)
{
  GimpValueArray *return_vals;
  GimpImage      *image  = NULL;
  GError         *error  = NULL;

  gegl_init (NULL, NULL);

  l_run_mode = run_mode;

  /* Why these values? No idea, they were the default until now, I just reuse
   * them. XXX
   */

  if (width == 0)
    g_object_set (config, "width", 826, NULL);
  if (height == 0)
    g_object_set (config, "height", 1170, NULL);

  switch (run_mode)
    {
    case GIMP_RUN_INTERACTIVE:
      if (! load_dialog (file, extracted_data, procedure, G_OBJECT (config)))
        return gimp_procedure_new_return_values (procedure,
                                                 GIMP_PDB_CANCEL,
                                                 NULL);
      break;

    default:
      break;
    }

  check_load_vals (config);

  image = load_image (file, config, &error);

  if (! image)
    return gimp_procedure_new_return_values (procedure,
                                             GIMP_PDB_EXECUTION_ERROR,
                                             error);

  return_vals = gimp_procedure_new_return_values (procedure,
                                                  GIMP_PDB_SUCCESS,
                                                  NULL);

  GIMP_VALUES_SET_IMAGE (return_vals, 1, image);

  return return_vals;
}

static GimpValueArray *
ps_load_thumb (GimpProcedure       *procedure,
               GFile               *file,
               gint                 size,
               GimpProcedureConfig *config,
               gpointer             run_data)
{
  GimpValueArray *return_vals;
  GimpImage      *image = NULL;
  GError         *error = NULL;

  gegl_init (NULL, NULL);

  /*  We should look for an embedded preview but for now we
   *  just load the document at a small resolution and the
   *  first page only.
   */

  check_load_vals (NULL);

  image = load_image (file, NULL, &error);

  if (! image)
    return gimp_procedure_new_return_values (procedure,
                                             GIMP_PDB_EXECUTION_ERROR,
                                             error);

  return_vals = gimp_procedure_new_return_values (procedure,
                                                  GIMP_PDB_SUCCESS,
                                                  NULL);

  GIMP_VALUES_SET_IMAGE (return_vals, 1, image);

  gimp_value_array_truncate (return_vals, 2);

  return return_vals;
}

static GimpValueArray *
ps_export (GimpProcedure        *procedure,
           GimpRunMode           run_mode,
           GimpImage            *image,
           GFile                *file,
           GimpExportOptions    *options,
           GimpMetadata         *metadata,
           GimpProcedureConfig  *config,
           gpointer              run_data)
{
  GimpPDBStatusType  status   = GIMP_PDB_SUCCESS;
  GimpExportReturn   export   = GIMP_EXPORT_IGNORE;
  GList             *drawables;
  GimpImage         *orig_image;
  gboolean           eps_flag = FALSE;
  GError            *error    = NULL;

  gegl_init (NULL, NULL);

  eps_flag = strcmp (gimp_procedure_get_name (procedure), EXPORT_PS_PROC);
  g_object_set (config,
                "eps-flag", eps_flag ? 1 : 0,
                NULL);

  orig_image = image;

  switch (run_mode)
    {
    case GIMP_RUN_INTERACTIVE:
      gimp_ui_init (PLUG_IN_BINARY);
      ps_set_save_size (G_OBJECT (config), orig_image);

      if (! save_dialog (procedure, G_OBJECT (config), image))
        status = GIMP_PDB_CANCEL;
      break;

    case GIMP_RUN_NONINTERACTIVE:
    case GIMP_RUN_WITH_LAST_VALS:
      break;

    default:
      break;
    }

  export    = gimp_export_options_get_image (options, &image);
  drawables = gimp_image_list_layers (image);

  if (status == GIMP_PDB_SUCCESS)
    {
      gdouble width  = 0.0;
      gdouble height = 0.0;

      if (config)
        g_object_get (config,
                      "width",  &width,
                      "height", &height,
                      NULL);

      if ((width == 0.0) || (height == 0.0))
        ps_set_save_size (G_OBJECT (config), orig_image);

      check_save_vals (config);

      if (! export_image (file, G_OBJECT (config), image, drawables->data,
                          &error))
        status = GIMP_PDB_EXECUTION_ERROR;
    }

  if (export == GIMP_EXPORT_EXPORT)
    gimp_image_delete (image);

  g_list_free (drawables);
  return gimp_procedure_new_return_values (procedure, status, error);
}


static void compress_packbits (int            nin,
                               unsigned char *src,
                               int           *nout,
                               unsigned char *dst);

static guint32  ascii85_buf       = 0;
static gint     ascii85_len       = 0;
static gint     ascii85_linewidth = 0;

static GimpPageSelectorTarget ps_pagemode = GIMP_PAGE_SELECTOR_TARGET_LAYERS;

static void
ascii85_init (void)
{
  ascii85_len       = 0;
  ascii85_linewidth = 0;
}

static gboolean
ascii85_flush (GOutputStream  *output,
               GError        **error)
{
  gchar    c[5];
  gint     i;
  gboolean zero_case = (ascii85_buf == 0);
  GString  *string   = g_string_new (NULL);

  static gint max_linewidth = 75;

  for (i = 4; i >= 0; i--)
    {
      c[i] = (ascii85_buf % 85) + '!';
      ascii85_buf /= 85;
    }

  /* check for special case: "!!!!!" becomes "z", but only if not
   * at end of data. */

  if (zero_case && (ascii85_len == 4))
    {
      if (ascii85_linewidth >= max_linewidth)
        {
          g_string_append_c (string, '\n');

          ascii85_linewidth = 0;
        }

      g_string_append_c (string, 'z');

      ascii85_linewidth++;
    }
  else
    {
      for (i = 0; i < ascii85_len + 1; i++)
        {
          if ((ascii85_linewidth >= max_linewidth) && (c[i] != '%'))
            {
              g_string_append_c (string, '\n');

              ascii85_linewidth = 0;
            }

          g_string_append_c (string, c[i]);

          ascii85_linewidth++;
        }
    }

  ascii85_len = 0;
  ascii85_buf = 0;

  if (string->len > 0 &&
      ! g_output_stream_write_all (output,
                                   string->str, string->len, NULL,
                                   NULL, error))
    {
      g_string_free (string, TRUE);

      return FALSE;
    }

  g_string_free (string, TRUE);

  return TRUE;
}

static inline gboolean
ascii85_out (GOutputStream  *output,
             guchar          byte,
             GError        **error)
{
  if (ascii85_len == 4)
    if (! ascii85_flush (output, error))
      return FALSE;

  ascii85_buf <<= 8;
  ascii85_buf |= byte;
  ascii85_len++;

  return TRUE;
}

static gboolean
ascii85_nout (GOutputStream  *output,
              gint            n,
              guchar         *uptr,
              GError        **error)
{
  while (n-- > 0)
    {
      if (! ascii85_out (output, *uptr, error))
        return FALSE;

      uptr++;
    }

  return TRUE;
}

static gboolean
ascii85_done (GOutputStream  *output,
              GError        **error)
{
  if (ascii85_len)
    {
      /* zero any unfilled buffer portion, then flush */
      ascii85_buf <<= (8 * (4 - ascii85_len));

      if (! ascii85_flush (output, error))
        return FALSE;
    }

  if (! print (output, error, "~>\n"))
    return FALSE;

  return TRUE;
}


static void
compress_packbits (int nin,
                   unsigned char *src,
                   int *nout,
                   unsigned char *dst)

{
 unsigned char c;
 int nrepeat, nliteral;
 unsigned char *run_start;
 unsigned char *start_dst = dst;
 unsigned char *last_literal = NULL;

 for (;;)
 {
   if (nin <= 0) break;

   run_start = src;
   c = *run_start;

   /* Search repeat bytes */
   if ((nin > 1) && (c == src[1]))
   {
     nrepeat = 1;
     nin -= 2;
     src += 2;
     while ((nin > 0) && (c == *src))
     {
       nrepeat++;
       src++;
       nin--;
       if (nrepeat == 127) break; /* Maximum repeat */
     }

     /* Add two-byte repeat to last literal run ? */
     if (   (nrepeat == 1)
         && (last_literal != NULL) && (((*last_literal)+1)+2 <= 128))
     {
       *last_literal += 2;
       *(dst++) = c;
       *(dst++) = c;
       continue;
     }

     /* Add repeat run */
     *(dst++) = (unsigned char)((-nrepeat) & 0xff);
     *(dst++) = c;
     last_literal = NULL;
     continue;
   }
   /* Search literal bytes */
   nliteral = 1;
   nin--;
   src++;

   for (;;)
   {
     if (nin <= 0) break;

     if ((nin >= 2) && (src[0] == src[1])) /* A two byte repeat ? */
       break;

     nliteral++;
     nin--;
     src++;
     if (nliteral == 128) break; /* Maximum literal run */
   }

   /* Could be added to last literal run ? */
   if ((last_literal != NULL) && (((*last_literal)+1)+nliteral <= 128))
   {
     *last_literal += nliteral;
   }
   else
   {
     last_literal = dst;
     *(dst++) = (unsigned char)(nliteral-1);
   }
   while (nliteral-- > 0) *(dst++) = *(run_start++);
 }
 *nout = dst - start_dst;
}


typedef struct
{
  goffset eol;
  goffset begin_data;
} PS_DATA_POS;

static PS_DATA_POS ps_data_pos = { 0, 0 };

static gboolean
ps_begin_data (GOutputStream  *output,
               GError        **error)
{
  /*                                 %%BeginData: 123456789012 ASCII Bytes */
  if (! print (output, error, "%s", "%%BeginData:                         "))
    return FALSE;

  ps_data_pos.eol = g_seekable_tell (G_SEEKABLE (output));

  if (! print (output, error, "\n"))
    return FALSE;

  ps_data_pos.begin_data = g_seekable_tell (G_SEEKABLE (output));

  return TRUE;
}

static gboolean
ps_end_data (GOutputStream  *output,
             GError        **error)
{
  goffset end_data;
  gchar   s[64];

  if ((ps_data_pos.begin_data > 0) && (ps_data_pos.eol > 0))
    {
      end_data = g_seekable_tell (G_SEEKABLE (output));

      if (end_data > 0)
        {
          g_snprintf (s, sizeof (s),
                      "%"G_GOFFSET_FORMAT" ASCII Bytes", end_data - ps_data_pos.begin_data);

          if (! g_seekable_seek (G_SEEKABLE (output),
                                 ps_data_pos.eol - strlen (s), G_SEEK_SET,
                                 NULL, error))
            return FALSE;

          if (! print (output, error, "%s", s))
            return FALSE;

          if (! g_seekable_seek (G_SEEKABLE (output),
                                 end_data, G_SEEK_SET,
                                 NULL, error))
            return FALSE;
        }
    }

  if (! print (output, error, "%s\n", "%%EndData"))
    return FALSE;

  return TRUE;
}

static GimpImage *
load_image (GFile                *file,
            GimpProcedureConfig  *config,
            GError              **error)
{
  GimpImage    *image = NULL;
  GimpImage   **image_list, **nl;
  const gchar  *filename;
  gint          width;
  gint          height;
  gdouble       resolution;
  gchar        *pnm_type = NULL;
  gchar        *text_alpha = NULL;
  gchar        *graphics_alpha = NULL;
  gchar        *pages;
  guint         page_count;
  FILE         *ifp;
  gchar        *temp;
  gint          llx, lly, urx, ury;
  gint          k, n_images, max_images, max_pagenum;
  gboolean      is_epsf;
  GdkPixbuf    *pixbuf = NULL;
  gchar        *tmp_filename = NULL;

  if (config)
    {
      g_object_get (config,
                    "pixel-density",      &resolution,
                    "width",              &width,
                    "height",             &height,
                    "pages",              &pages,
                    "coloring",           &pnm_type,
                    "text-alpha-bits",    &text_alpha,
                    "graphic-alpha-bits", &graphics_alpha,
                    NULL);
    }
  else
    {
      pages = g_strdup_printf ("1");
    }

#ifdef PS_DEBUG
  g_print ("load_image:\n resolution = %f\n", resolution);
  g_print (" %dx%d pixels\n", width, height);
  g_print (" Coloring: %s\n", pnm_type);
  g_print (" TextAlphaBits: %s\n", text_alpha);
  g_print (" GraphicsAlphaBits: %s\n", graphics_alpha);
#endif

  gimp_progress_init_printf (_("Opening '%s'"),
                             gimp_file_get_utf8_name (file));

  /* Try to see if PostScript file is available */
  filename = gimp_file_get_utf8_name (file);
  ifp = g_fopen (filename, "r");

  if (! ifp)
    {
      g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
                   _("Could not open '%s' for reading: %s"),
                   gimp_file_get_utf8_name (file), g_strerror (errno));
      return NULL;
    }
  fclose (ifp);

  ifp = ps_open (file, config, &llx, &lly, &urx, &ury, &is_epsf,
                 &tmp_filename);
  if (!ifp)
    {
      g_set_error (error, G_FILE_ERROR, G_FILE_ERROR,
                   _("Could not interpret PostScript file '%s'"),
                   gimp_file_get_utf8_name (file));
      return NULL;
    }

  image_list = g_new (GimpImage *, 10);
  n_images = 0;
  max_images = 10;

  max_pagenum = 9999;  /* Try to get the maximum pagenumber to read */
  if (is_epsf)
    {
      max_pagenum = 1;
      /* Use pixbuf to load transparent EPS as PNGs */
      pixbuf = gdk_pixbuf_new_from_file (tmp_filename, error);
      if (! pixbuf)
        return NULL;
    }

  if (! page_in_list (pages, max_pagenum)) /* Is there a limit in list ? */
    {
      max_pagenum = -1;
      for (temp = pages; *temp != '\0'; temp++)
        {
          if ((*temp < '0') || (*temp > '9'))
            continue; /* Search next digit */
          sscanf (temp, "%d", &k);
          if (k > max_pagenum)
            max_pagenum = k;
          while ((*temp >= '0') && (*temp <= '9'))
            temp++;
          temp--;
        }

      if (max_pagenum < 1)
        max_pagenum = 9999;
    }

  /* Load all images */
  for (page_count = 1; page_count <= max_pagenum; page_count++)
    {
      if (page_in_list (pages, page_count))
        {
          image = load_ps (file, page_count, ifp, llx, lly, urx, ury);
          if (! image)
            break;

          gimp_image_set_resolution (image, resolution, resolution);

          if (n_images == max_images)
            {
              nl = (GimpImage **) g_realloc (image_list,
                                             (max_images+10)*sizeof (GimpImage *));
              if (nl == NULL) break;
              image_list = nl;
              max_images += 10;
            }
          image_list[n_images++] = image;
        }
      else  /* Skip an image */
        {
          image = NULL;
          if (! skip_ps (ifp))
            break;
        }
    }

  ps_close (ifp, tmp_filename);

  /* EPS are now imported using pngalpha, so they can be converted
   * to a layer with gimp_layer_new_from_pixbuf () and exported at
   * this part of the loading process
   */

  if (is_epsf)
    {
      GimpLayer *layer;

      image = gimp_image_new (urx, ury, GIMP_RGB);

      gimp_image_undo_disable (image);
      gimp_image_set_resolution (image, resolution, resolution);

      layer = gimp_layer_new_from_pixbuf (image, _("Rendered EPS"), pixbuf,
                                          100,
                                          gimp_image_get_default_new_layer_mode (image),
                                          0.0, 1.0);
      gimp_image_insert_layer (image, layer, NULL, 0);

      gimp_image_undo_enable (image);

      g_free (image_list);
      g_object_unref (pixbuf);

      return image;
    }

  if (ps_pagemode == GIMP_PAGE_SELECTOR_TARGET_LAYERS)
    {
      for (k = 0; k < n_images; k++)
        {
          if (k == 0)
            {
              GFile *new_file;
              gchar *uri;
              gchar *new_uri;

              image = image_list[0];

              uri = g_file_get_uri (file);

              new_uri = g_strdup_printf (_("%s-pages"), uri);
              g_free (uri);

              new_file = g_file_new_for_uri (new_uri);
              g_free (new_uri);

              g_object_unref (new_file);
            }
          else
            {
              GimpLayer     *current_layer;
              gchar         *name;
              GimpDrawable **tmp_drawables;

              tmp_drawables = gimp_image_get_selected_drawables (image_list[k]);

              name = gimp_item_get_name (GIMP_ITEM (tmp_drawables[0]));

              current_layer = gimp_layer_new_from_drawable (GIMP_DRAWABLE (tmp_drawables[0]),
                                                            image);
              gimp_item_set_name (GIMP_ITEM (current_layer), name);
              gimp_image_insert_layer (image, current_layer, NULL, -1);
              gimp_image_delete (image_list[k]);

              g_free (tmp_drawables);
              g_free (name);
            }
        }

      gimp_image_undo_enable (image);
    }
  else
    {
      /* Display images in reverse order.
       * The last will be displayed by GIMP itself
       */

      for (k = n_images - 1; k >= 0; k--)
        {
          gimp_image_undo_enable (image_list[k]);
          gimp_image_clean_all (image_list[k]);

          if (l_run_mode != GIMP_RUN_NONINTERACTIVE && k > 0)
            gimp_display_new (image_list[k]);
        }

      image = (n_images > 0) ? image_list[0] : NULL;
    }

  g_free (image_list);

  return image;
}


static gboolean
export_image (GFile         *file,
              GObject       *config,
              GimpImage     *image,
              GimpDrawable  *drawable,
              GError       **error)
{
  GOutputStream *output;
  GCancellable  *cancellable;
  GimpImageType  drawable_type;

  drawable_type = gimp_drawable_type (drawable);

  /*  Make sure we're not exporting an image with an alpha channel  */
  if (gimp_drawable_has_alpha (drawable))
    {
      g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
                   _("PostScript export cannot handle images with alpha channels"));
      return FALSE;
    }

  switch (drawable_type)
    {
    case GIMP_INDEXED_IMAGE:
    case GIMP_GRAY_IMAGE:
    case GIMP_RGB_IMAGE:
      break;

    default:
      g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
                   _("Cannot operate on unknown image types."));
      return FALSE;
      break;
    }

  gimp_progress_init_printf (_("Exporting '%s'"),
                             gimp_file_get_utf8_name (file));

  output = G_OUTPUT_STREAM (g_file_replace (file,
                                            NULL, FALSE, G_FILE_CREATE_NONE,
                                            NULL, error));
  if (output)
    {
      GOutputStream *buffered;

      buffered = g_buffered_output_stream_new (output);
      g_object_unref (output);

      output = buffered;
    }
  else
    {
      return FALSE;
    }

  if (! save_ps_header (output, file, config, error))
    goto fail;

  switch (drawable_type)
    {
    case GIMP_INDEXED_IMAGE:
      if (! save_index (output, image, drawable, config, error))
        goto fail;
      break;

    case GIMP_GRAY_IMAGE:
      if (! save_gray (output, image, drawable, config, error))
        goto fail;
      break;

    case GIMP_RGB_IMAGE:
      if (! save_rgb (output, image, drawable, config, error))
        goto fail;
      break;

    default:
      g_return_val_if_reached (FALSE);
    }

  if (! save_ps_trailer (output, error))
    goto fail;

  if (! g_output_stream_close (output, NULL, error))
    goto fail;

  g_object_unref (output);

  return TRUE;

 fail:

  cancellable = g_cancellable_new ();
  g_cancellable_cancel (cancellable);
  g_output_stream_close (output, cancellable, NULL);

  g_object_unref (output);
  g_object_unref (cancellable);

  return FALSE;
}


/* Check (and correct) the load values */
static void
check_load_vals (GimpProcedureConfig *config)
{
  gdouble  resolution     = GIMP_MIN_RESOLUTION;
  gint     width          = 2;
  gint     height         = 2;
  gboolean use_bbox       = FALSE;
  gint     pnm_type       = 6;
  gint     text_alpha     = 1;
  gint     graphics_alpha = 1;
  gchar   *pages          = NULL;

  if (config)
    {
      g_object_get (config,
                    "pixel-density",      &resolution,
                    "width",              &width,
                    "height",             &height,
                    "check-bbox",         &use_bbox,
                    "pages",              &pages,
                    NULL);
      pnm_type       = gimp_procedure_config_get_choice_id (config, "coloring");
      text_alpha     = gimp_procedure_config_get_choice_id (config, "text-alpha-bits");
      graphics_alpha = gimp_procedure_config_get_choice_id (config, "graphic-alpha-bits");
    }

  if (resolution < GIMP_MIN_RESOLUTION)
    resolution = GIMP_MIN_RESOLUTION;
  else if (resolution > GIMP_MAX_RESOLUTION)
    resolution = GIMP_MAX_RESOLUTION;

  if (width < 2)
    width = 2;
  if (height < 2)
    height = 2;
  use_bbox = (use_bbox != 0);
  if (! pages)
    pages = g_strdup_printf ("1-99");
  if ((pnm_type < 4) || (pnm_type > 7))
    pnm_type = 6;
  if ((text_alpha != 1) && (text_alpha != 2) &&
      (text_alpha != 4))
    text_alpha = 1;
  if ((graphics_alpha != 1) && (graphics_alpha != 2) &&
      (graphics_alpha != 4))
    graphics_alpha = 1;

  if (config)
    {
      g_object_set (config,
                    "pixel-density",      resolution,
                    "width",              width,
                    "height",             height,
                    "check_bbox",         use_bbox ? 1 : 0,
                    "pages",              pages,
                    NULL);

      switch (pnm_type)
        {
        case 4:
          g_object_set (config, "coloring", "bw", NULL);
          break;
        case 5:
          g_object_set (config, "coloring", "grayscale", NULL);
          break;
        case 6:
          g_object_set (config, "coloring", "rgb", NULL);
          break;
        case 7:
        default:
          g_object_set (config, "coloring", "automatic", NULL);
        }

      switch (text_alpha)
        {
          case 2:
            g_object_set (config, "text-alpha-bits", "weak", NULL);
            break;
          case 4:
            g_object_set (config, "text-alpha-bits", "strong", NULL);
            break;
          case 1:
          default:
            g_object_set (config, "text-alpha-bits", "none", NULL);
        }

      switch (graphics_alpha)
        {
          case 2:
            g_object_set (config, "graphic-alpha-bits", "weak", NULL);
            break;
          case 4:
            g_object_set (config, "graphic-alpha-bits", "strong", NULL);
            break;
          case 1:
          default:
            g_object_set (config, "graphic-alpha-bits", "none", NULL);
        }
    }

  g_free (pages);
}


/* Check (and correct) the save values */
static void
check_save_vals (GimpProcedureConfig *config)
{
  gint     i            = 90;
  gint     preview_size = 0;
  gboolean preview      = TRUE;

  g_object_get (config,
                "rotation",     &i,
                "show-preview", &preview,
                "preview",      &preview_size,
                NULL);

  if ((i != 0) && (i != 90) && (i != 180) && (i != 270))
    i = 90;
  if (preview_size <= 0)
    preview = FALSE;

  g_object_set (config,
                "rotation",     i,
                "show-preview", preview ? 1 : 0,
                "preview",      preview_size,
                NULL);
}

static void
ps_set_save_size (GObject   *config,
                  GimpImage *image)
{
  gdouble    xres, yres, factor, iw, ih;
  guint      width, height;
  GimpUnit  *unit;
  gboolean   unit_mm = FALSE;

  gimp_image_get_resolution (image, &xres, &yres);

  if ((xres < 1e-5) || (yres < 1e-5))
    xres = yres = 72.0;

  /* Calculate size of image in inches */
  width  = gimp_image_get_width (image);
  height = gimp_image_get_height (image);
  iw = width  / xres;
  ih = height / yres;

  unit = gimp_image_get_unit (image);
  factor = gimp_unit_get_factor (unit);

  if (factor == 0.0254 ||
      factor == 0.254 ||
      factor == 2.54 ||
      factor == 25.4)
    {
      unit_mm = TRUE;
    }

  if (unit_mm)
    {
      iw *= 25.4;
      ih *= 25.4;
    }

  g_object_set (config,
                "unit",   unit_mm ? "millimeter" : "inch",
                "width",  iw,
                "height", ih,
                NULL);
}

/* Check if a page is in a given list */
static gint
page_in_list (gchar *list,
              guint  page_num)
{
  char tmplist[STR_LENGTH], *c0, *c1;
  int state, start_num, end_num;
#define READ_STARTNUM  0
#define READ_ENDNUM    1
#define CHK_LIST(a,b,c) {int low=(a),high=(b),swp; \
  if ((low>0) && (high>0)) { \
  if (low>high) {swp=low; low=high; high=swp;} \
  if ((low<=(c))&&(high>=(c))) return (1); } }

  if ((list == NULL) || (*list == '\0'))
    return 1;

  g_strlcpy (tmplist, list, STR_LENGTH);

  c0 = c1 = tmplist;
  while (*c1)    /* Remove all whitespace and break on unsupported characters */
    {
      if ((*c1 >= '0') && (*c1 <= '9'))
        {
          *(c0++) = *c1;
        }
      else if ((*c1 == '-') || (*c1 == ','))
        { /* Try to remove double occurrences of these characters */
          if (c0 == tmplist)
            {
              *(c0++) = *c1;
            }
          else
            {
              if (*(c0-1) != *c1)
                *(c0++) = *c1;
            }
        }
      else
        break;

      c1++;
    }

  if (c0 == tmplist)
    return 1;

  *c0 = '\0';

  /* Now we have a comma separated list like 1-4-1,-3,1- */

  start_num = end_num = -1;
  state = READ_STARTNUM;
  for (c0 = tmplist; *c0 != '\0'; c0++)
    {
      switch (state)
        {
        case READ_STARTNUM:
          if (*c0 == ',')
            {
              if ((start_num > 0) && (start_num == (int)page_num))
                return -1;
              start_num = -1;
            }
          else if (*c0 == '-')
            {
              if (start_num < 0) start_num = 1;
              state = READ_ENDNUM;
            }
          else /* '0' - '9' */
            {
              if (start_num < 0) start_num = 0;
              start_num *= 10;
              start_num += *c0 - '0';
            }
          break;

        case READ_ENDNUM:
          if (*c0 == ',')
            {
              if (end_num < 0) end_num = 9999;
              CHK_LIST (start_num, end_num, (int)page_num);
              start_num = end_num = -1;
              state = READ_STARTNUM;
            }
          else if (*c0 == '-')
            {
              CHK_LIST (start_num, end_num, (int)page_num);
              start_num = end_num;
              end_num = -1;
            }
          else /* '0' - '9' */
            {
              if (end_num < 0) end_num = 0;
              end_num *= 10;
              end_num += *c0 - '0';
            }
          break;
        }
    }
  if (state == READ_STARTNUM)
    {
      if (start_num > 0)
        return (start_num == (int) page_num);
    }
  else
    {
      if (end_num < 0) end_num = 9999;
      CHK_LIST (start_num, end_num, (int)page_num);
    }

  return 0;
#undef CHK_LIST
}


/* A function like fgets, but treats single CR-character as line break. */
/* As a line break the newline-character is returned. */
static char *psfgets (char *s, int size, FILE *stream)

{
  int c;
  char *sptr = s;

  if (size <= 0)
    return NULL;

  if (size == 1)
    {
      *s = '\0';
      return NULL;
    }

  c = getc (stream);
  if (c == EOF)
    return NULL;

  for (;;)
    {
      /* At this point we have space in sptr for at least two characters */
      if (c == '\n')    /* Got end of line (UNIX line end) ? */
        {
          *(sptr++) = '\n';
          break;
        }
      else if (c == '\r')  /* Got a carriage return. Check next character */
        {
          c = getc (stream);
          if ((c == EOF) || (c == '\n')) /* EOF or DOS line end ? */
            {
              *(sptr++) = '\n';  /* Return UNIX line end */
              break;
            }
          else  /* Single carriage return. Return UNIX line end. */
            {
              ungetc (c, stream);  /* Save the extra character */
              *(sptr++) = '\n';
              break;
            }
        }
      else   /* no line end character */
        {
          *(sptr++) = (char)c;
          size--;
        }
      if (size == 1)
        break;  /* Only space for the nul-character ? */

      c = getc (stream);
      if (c == EOF)
        break;
    }

  *sptr = '\0';

  return s;
}


/* Get the BoundingBox of a PostScript file. On success, 0 is returned. */
/* On failure, -1 is returned. */
static gint
get_bbox (GFile *file,
          gint  *x0,
          gint  *y0,
          gint  *x1,
          gint  *y1)
{
  char   line[1024], *src;
  FILE  *ifp;
  int    retval = -1;

  ifp = g_fopen (g_file_peek_path (file), "rb");

  if (! ifp)
    return -1;

  for (;;)
    {
      if (psfgets (line, sizeof (line)-1, ifp) == NULL) break;
      if ((line[0] != '%') || (line[1] != '%')) continue;
      src = &(line[2]);
      while ((*src == ' ') || (*src == '\t')) src++;
      if (strncmp (src, "BoundingBox", 11) != 0) continue;
      src += 11;
      while ((*src == ' ') || (*src == '\t') || (*src == ':')) src++;
      if (strncmp (src, "(atend)", 7) == 0) continue;
      if (sscanf (src, "%d%d%d%d", x0, y0, x1, y1) == 4)
        retval = 0;
      break;
    }
  fclose (ifp);

  return retval;
}

static gboolean
ps_read_header (GFile               *file,
                GimpProcedureConfig *config,
                gboolean            *is_pdf,
                gboolean            *is_epsf,
                gint                *bbox_x0,
                gint                *bbox_y0,
                gint                *bbox_x1,
                gint                *bbox_y1)
{
  FILE        *eps_file;
  const gchar *filename;
  gboolean     use_bbox   = TRUE;
  gboolean     maybe_epsf = FALSE;
  gboolean     has_bbox   = FALSE;

  if (config)
    g_object_get (config, "check-bbox", &use_bbox, NULL);

  /* Check if the file is a PDF. For PDF, we can't set geometry */
  *is_pdf = FALSE;

  /* Check if it is a EPS-file */
  *is_epsf = FALSE;

  *bbox_x0 = 0.0;
  *bbox_y0 = 0.0;
  *bbox_x1 = 0.0;
  *bbox_y1 = 0.0;

  filename = gimp_file_get_utf8_name (file);
  eps_file = g_fopen (filename, "rb");

  if (eps_file)
    {
      gchar hdr[512];

      fread (hdr, 1, sizeof(hdr), eps_file);
      *is_pdf = (strncmp (hdr, "%PDF", 4) == 0);

      if (!*is_pdf)  /* Check for EPSF */
        {
          char                 *adobe, *epsf;
          int                   ds = 0;
          static unsigned char  doseps[5] = { 0xc5, 0xd0, 0xd3, 0xc6, 0 };

          hdr[sizeof(hdr)-1] = '\0';
          adobe = strstr (hdr, "PS-Adobe-");
          epsf = strstr (hdr, "EPSF-");

          if ((adobe != NULL) && (epsf != NULL))
            ds = epsf - adobe;

          *is_epsf = ((ds >= 11) && (ds <= 15));

          /* Illustrator uses negative values in BoundingBox without marking */
          /* files as EPSF. Try to handle that. */
          maybe_epsf =
            (strstr (hdr, "%%Creator: Adobe Illustrator(R) 8.0") != 0);

          /* Check DOS EPS binary file */
          if ((!*is_epsf) && (strncmp (hdr, (char *)doseps, 4) == 0))
            *is_epsf = 1;
        }

      fclose (eps_file);
    }

  if ((!*is_pdf) && (use_bbox))    /* Try the BoundingBox ? */
    {
      if (get_bbox (file, bbox_x0, bbox_y0, bbox_x1, bbox_y1) == 0)
        {
          has_bbox = TRUE;

          if (maybe_epsf && ((bbox_x0 < 0) || (bbox_y0 < 0)))
            *is_epsf = 1;
        }
    }

  return has_bbox;
}

/* Open the PostScript file. On failure, NULL is returned. */
/* The filepointer returned will give a PNM-file generated */
/* by the PostScript-interpreter. */
static FILE *
ps_open (GFile               *file,
         GimpProcedureConfig *config,
         gint                *llx,
         gint                *lly,
         gint                *urx,
         gint                *ury,
         gboolean            *is_epsf,
         gchar              **tmp_filename)
{
  const gchar  *driver;
  GPtrArray    *cmdA;
  gchar       **pcmdA;
  FILE         *fd_popen = NULL;
  gint          width, height;
  gdouble       resolution;
  gint          pnm_type       = 0;
  gint          text_alpha     = 0;
  gint          graphics_alpha = 0;
  gboolean      has_bbox;
  gint          bbox_x0 = 0;
  gint          bbox_y0 = 0;
  gint          bbox_x1 = 0;
  gint          bbox_y1 = 0;
  gint          offx    = 0;
  gint          offy    = 0;
  gboolean      is_pdf;
  int           code;
  void         *instance = NULL;

  if (config)
    {
      g_object_get (config,
                    "width",              &width,
                    "height",             &height,
                    "pixel-density",      &resolution,
                    NULL);
      pnm_type       = gimp_procedure_config_get_choice_id (config, "coloring");
      text_alpha     = gimp_procedure_config_get_choice_id (config, "text-alpha-bits");
      graphics_alpha = gimp_procedure_config_get_choice_id (config, "graphic-alpha-bits");
    }
  else
    {
      width = 256;
      height = 256;
      resolution = 256.0 / 4.0;
    }

  *llx = *lly = 0;
  *urx = width - 1;
  *ury = height - 1;

  has_bbox = ps_read_header (file, config, &is_pdf, is_epsf, &bbox_x0, &bbox_y0, &bbox_x1, &bbox_y1);

  if (has_bbox)
    {
      if (*is_epsf)  /* Handle negative BoundingBox for EPSF */
        {
          offx = -bbox_x0; bbox_x1 += offx; bbox_x0 += offx;
          offy = -bbox_y0; bbox_y1 += offy; bbox_y0 += offy;
        }

      if (bbox_x0 >= 0 && bbox_y0 >= 0 && bbox_x1 > bbox_x0 && bbox_y1 > bbox_y0)
        {
          *llx = (int) (bbox_x0 / 72.0 * resolution + 0.0001);
          *lly = (int) (bbox_y0 / 72.0 * resolution + 0.0001);
          /* Use upper bbox values as image size */
          width = (int) (bbox_x1 / 72.0 * resolution + 0.5);
          height = (int) (bbox_y1 / 72.0 * resolution + 0.5);
          /* Pixel coordinates must be one less */
          *urx = width - 1;
          *ury = height - 1;
          if (*urx < *llx) *urx = *llx;
          if (*ury < *lly) *ury = *lly;
        }
    }

  switch (pnm_type)
    {
    case 4:
      driver = "pbmraw";
      break;
    case 5:
      driver = "pgmraw";
      break;
    case 7:
      driver = "pnmraw";
      break;
    default:
      driver = "ppmraw";
      break;
    }

  /* For instance, the Win32 port of ghostscript doesn't work correctly when
   * using standard output as output file.
   * Thus, use a real output file.
   */

  if (*is_epsf)
    {
      driver = "pngalpha";
      *tmp_filename = g_file_get_path (gimp_temp_file ("png"));
    }
  else
    {
      *tmp_filename = g_file_get_path (gimp_temp_file ("pnm"));
    }

  /* Build command array */
  cmdA = g_ptr_array_new ();

  g_ptr_array_add (cmdA, g_strdup (g_get_prgname ()));
  g_ptr_array_add (cmdA, g_strdup_printf ("-sDEVICE=%s", driver));
  g_ptr_array_add (cmdA, g_strdup_printf ("-r%d", (gint) resolution));

  if (is_pdf)
    {
      /* Acrobat Reader honors CropBox over MediaBox, so let's match that
       * behavior.
       */

      g_ptr_array_add (cmdA, g_strdup ("-dUseCropBox"));
    }
  else
    {
      /* For PDF, we can't set geometry */
      g_ptr_array_add (cmdA, g_strdup_printf ("-g%dx%d", width, height));
    }

  /* Antialiasing not available for PBM-device */
  if ((pnm_type != 4) && (text_alpha != 1))
    g_ptr_array_add (cmdA, g_strdup_printf ("-dTextAlphaBits=%d",
                                            text_alpha));
  if ((pnm_type != 4) && (graphics_alpha != 1))
    g_ptr_array_add (cmdA, g_strdup_printf ("-dGraphicsAlphaBits=%d",
                                            graphics_alpha));
  g_ptr_array_add (cmdA, g_strdup ("-q"));
  g_ptr_array_add (cmdA, g_strdup ("-dBATCH"));
  g_ptr_array_add (cmdA, g_strdup ("-dNOPAUSE"));

  /* If no additional options specified, use at least -dSAFER */
  if (g_getenv ("GS_OPTIONS") == NULL)
    g_ptr_array_add (cmdA, g_strdup ("-dSAFER"));

  /* Output file name */
  g_ptr_array_add (cmdA, g_strdup_printf ("-sOutputFile=%s", *tmp_filename));

  /* Offset command for gs to get image part with negative x/y-coord. */
  if ((offx != 0) || (offy != 0))
    {
      g_ptr_array_add (cmdA, g_strdup ("-c"));
      g_ptr_array_add (cmdA, g_strdup_printf ("%d", offx));
      g_ptr_array_add (cmdA, g_strdup_printf ("%d", offy));
      g_ptr_array_add (cmdA, g_strdup ("translate"));
    }

  /* input file name */
  g_ptr_array_add (cmdA, g_strdup ("-f"));
  g_ptr_array_add (cmdA, g_strdup (gimp_file_get_utf8_name (file)));

  if (*is_epsf)
    {
      g_ptr_array_add (cmdA, g_strdup ("-c"));
      g_ptr_array_add (cmdA, g_strdup ("showpage"));
    }

  g_ptr_array_add (cmdA, g_strdup ("-c"));
  g_ptr_array_add (cmdA, g_strdup ("quit"));
  g_ptr_array_add (cmdA, NULL);

  pcmdA = (gchar **) cmdA->pdata;

#ifdef PS_DEBUG
  {
    gchar **p = pcmdA;
    g_print ("Passing args (argc=%d):\n", cmdA->len - 1);

    while (*p)
      {
        g_print ("%s\n", *p);
        p++;
      }
  }
#endif

  code = gsapi_new_instance (&instance, NULL);
  if (code == 0) {
    code = gsapi_set_arg_encoding(instance, GS_ARG_ENCODING_UTF8);
    code = gsapi_init_with_args (instance, cmdA->len - 1, pcmdA);
    code = gsapi_exit (instance);
    gsapi_delete_instance (instance);
  }

  /* Don't care about exit status of ghostscript. */
  /* Just try to read what it wrote. */

  fd_popen = g_fopen (*tmp_filename, "rb");

  g_ptr_array_free (cmdA, FALSE);
  g_strfreev (pcmdA);

  return fd_popen;
}


/* Close the PNM-File of the PostScript interpreter */
static void
ps_close (FILE *ifp, gchar *tmp_filename)
{
 /* If a real outputfile was used, close the file and remove it. */
  fclose (ifp);
  g_unlink (tmp_filename);
}


/* Read the header of a raw PNM-file and return type (4-6) or -1 on failure */
static gint
read_pnmraw_type (FILE *ifp,
                  gint *width,
                  gint *height,
                  gint *maxval)
{
  int frst, scnd, thrd;
  gint  pnmtype;
  gchar line[1024];

  /* GhostScript may write some informational messages infront of the header. */
  /* We are just looking at a Px\n in the input stream. */
  frst = getc (ifp);
  scnd = getc (ifp);
  thrd = getc (ifp);
  for (;;)
    {
      if (thrd == EOF) return -1;
#ifdef G_OS_WIN32
      if (thrd == '\r') thrd = getc (ifp);
#endif
      if ((thrd == '\n') && (frst == 'P') && (scnd >= '1') && (scnd <= '6'))
        break;
      frst = scnd;
      scnd = thrd;
      thrd = getc (ifp);
    }
  pnmtype = scnd - '0';
  /* We don't use the ASCII-versions */
  if ((pnmtype >= 1) && (pnmtype <= 3))
    return -1;

  /* Read width/height */
  for (;;)
    {
      if (fgets (line, sizeof (line)-1, ifp) == NULL)
        return -1;
      if (line[0] != '#')
        break;
    }
  if (sscanf (line, "%d%d", width, height) != 2)
    return -1;

  *maxval = 255;

  if (pnmtype != 4)  /* Read maxval */
    {
      for (;;)
        {
          if (fgets (line, sizeof (line)-1, ifp) == NULL)
            return -1;
          if (line[0] != '#')
            break;
        }
      if (sscanf (line, "%d", maxval) != 1)
        return -1;
    }

  return pnmtype;
}


/* Create an image. Sets layer, drawable and rgn. Returns image */
static GimpImage *
create_new_image (GFile              *file,
                  guint               pagenum,
                  guint               width,
                  guint               height,
                  GimpImageBaseType   type,
                  GimpLayer         **layer)
{
  GimpImage     *image;
  GimpImageType  gdtype;
  gchar         *tmp;

  switch (type)
    {
    case GIMP_GRAY:
      gdtype = GIMP_GRAY_IMAGE;
      break;
    case GIMP_INDEXED:
      gdtype = GIMP_INDEXED_IMAGE;
      break;
    case GIMP_RGB:
    default:
      gdtype = GIMP_RGB_IMAGE;
    }

  image = gimp_image_new_with_precision (width, height, type,
                                         GIMP_PRECISION_U8_NON_LINEAR);
  gimp_image_undo_disable (image);

  tmp = g_strdup_printf (_("Page %d"), pagenum);
  *layer = gimp_layer_new (image, tmp, width, height,
                           gdtype,
                           100,
                           gimp_image_get_default_new_layer_mode (image));
  g_free (tmp);

  gimp_image_insert_layer (image, *layer, NULL, 0);

  return image;
}


/* Skip PNM image generated from PostScript file. */
/* Return TRUE on success, FALSE otherwise.       */
static gboolean
skip_ps (FILE *ifp)
{
  guchar  buf[8192];
  gsize   len;
  gint    pnmtype, width, height, maxval, bpl;

  pnmtype = read_pnmraw_type (ifp, &width, &height, &maxval);

  if (pnmtype == 4)    /* Portable bitmap */
    bpl = (width + 7) / 8;
  else if (pnmtype == 5)
    bpl = width;
  else if (pnmtype == 6)
    bpl = width * 3;
  else
    return FALSE;

  len = bpl * height;
  while (len)
    {
      gsize  bytes = fread (buf, 1, MIN (len, sizeof (buf)), ifp);

      if (bytes < MIN (len, sizeof (buf)))
        return FALSE;

      len -= bytes;
    }

  return TRUE;
}


/* Load PNM image generated from PostScript file */
static GimpImage *
load_ps (GFile *file,
         guint  pagenum,
         FILE  *ifp,
         gint   llx,
         gint   lly,
         gint   urx,
         gint   ury)
{
  guchar *dest;
  guchar *data, *bitline = NULL, *byteline = NULL, *byteptr, *temp;
  guchar  bit2byte[256*8];
  int     width, height, tile_height, scan_lines, total_scan_lines;
  int     image_width, image_height;
  int     skip_left, skip_bottom;
  int     i, j, pnmtype, maxval, bpp, nread;
  GimpImageBaseType imagetype;
  GimpImage  *image;
  GimpLayer  *layer;
  GeglBuffer *buffer = NULL;
  int         err = 0;
  int         e;

  pnmtype = read_pnmraw_type (ifp, &width, &height, &maxval);

  if ((width == urx+1) && (height == ury+1))  /* gs respected BoundingBox ? */
    {
      skip_left = llx;    skip_bottom = lly;
      image_width = width - skip_left;
      image_height = height - skip_bottom;
    }
  else
    {
      skip_left = skip_bottom = 0;
      image_width = width;
      image_height = height;
    }
  if (pnmtype == 4)   /* Portable Bitmap */
    {
      imagetype = GIMP_INDEXED;
      nread = (width+7)/8;
      bpp = 1;
      bitline = g_new (guchar, nread);
      byteline = g_new (guchar, nread * 8);

      /* Get an array for mapping 8 bits in a byte to 8 bytes */
      temp = bit2byte;
      for (j = 0; j < 256; j++)
        for (i = 7; i >= 0; i--)
          *(temp++) = ((j & (1 << i)) != 0);
    }
  else if (pnmtype == 5)  /* Portable Greymap */
    {
      imagetype = GIMP_GRAY;
      nread = width;
      bpp = 1;
      byteline = g_new (guchar, nread);
    }
  else if (pnmtype == 6)  /* Portable Pixmap */
    {
      imagetype = GIMP_RGB;
      nread = width * 3;
      bpp = 3;
      byteline = g_new (guchar, nread);
    }
  else
    return NULL;

  image = create_new_image (file, pagenum,
                            image_width, image_height, imagetype,
                            &layer);
  buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (layer));

  tile_height = gimp_tile_height ();
  data = g_malloc (tile_height * image_width * bpp);

  dest = data;
  total_scan_lines = scan_lines = 0;

  if (pnmtype == 4)   /* Read bitimage ? Must be mapped to indexed */
    {
      const guchar BWColorMap[2*3] = { 255, 255, 255, 0, 0, 0 };

      gimp_palette_set_colormap (gimp_image_get_palette (image), babl_format ("R'G'B' u8"), (guint8 *) BWColorMap, 2 * 3);

      for (i = 0; i < height; i++)
        {
          e = (fread (bitline, 1, nread, ifp) != nread);
          if (total_scan_lines >= image_height)
            continue;
          err |= e;
          if (err)
            break;

          j = width;   /* Map 1 byte of bitimage to 8 bytes of indexed image */
          temp = bitline;
          byteptr = byteline;
          while (j >= 8)
            {
              memcpy (byteptr, bit2byte + *(temp++)*8, 8);
              byteptr += 8;
              j -= 8;
            }
          if (j > 0)
            memcpy (byteptr, bit2byte + *temp*8, j);

          memcpy (dest, byteline+skip_left, image_width);
          dest += image_width;
          scan_lines++;
          total_scan_lines++;

          if ((scan_lines == tile_height) || ((i + 1) == image_height))
            {
              gegl_buffer_set (buffer,
                               GEGL_RECTANGLE (0, i-scan_lines+1,
                                               image_width, scan_lines),
                               0,
                               NULL,
                               data,
                               GEGL_AUTO_ROWSTRIDE);
              scan_lines = 0;
              dest = data;
            }

          if ((i % 20) == 0)
            gimp_progress_update ((gdouble) (i + 1) / (gdouble) image_height);

          if (err)
            break;
        }
    }
  else   /* Read gray/rgb-image */
    {
      for (i = 0; i < height; i++)
        {
          e = (fread (byteline, bpp, width, ifp) != width);
          if (total_scan_lines >= image_height)
            continue;
          err |= e;
          if (err)
            break;

          memcpy (dest, byteline+skip_left*bpp, image_width*bpp);
          dest += image_width*bpp;
          scan_lines++;
          total_scan_lines++;

          if ((scan_lines == tile_height) || ((i + 1) == image_height))
            {
              gegl_buffer_set (buffer,
                               GEGL_RECTANGLE (0, i-scan_lines+1,
                                               image_width, scan_lines),
                               0,
                               NULL,
                               data,
                               GEGL_AUTO_ROWSTRIDE);
              scan_lines = 0;
              dest = data;
            }

          if ((i % 20) == 0)
            gimp_progress_update ((gdouble) (i + 1) / (gdouble) image_height);

          if (err)
            break;
        }
    }
  gimp_progress_update (1.0);

  g_free (data);
  g_free (byteline);
  g_free (bitline);

  if (err)
    g_message ("EOF encountered on reading");

  g_object_unref (buffer);

  return (err ? NULL : image);
}


/* Write out the PostScript file header */
static gboolean
save_ps_header (GOutputStream  *output,
                GFile          *file,
                GObject        *config,
                GError        **error)
{
  gchar   *basename = g_path_get_basename (gimp_file_get_utf8_name (file));
  gboolean eps      = FALSE;
  gboolean level2   = FALSE;
  time_t   cutime   = time (NULL);

  g_object_get (config,
                "eps-flag", &eps,
                "level",    &level2,
                NULL);

  if (! print (output, error,
               "%%!PS-Adobe-3.0%s\n", eps ? " EPSF-3.0" : ""))
    goto fail;

  if (! print (output, error,
               "%%%%Creator: GIMP PostScript file plug-in V %4.2f "
               "by Peter Kirchgessner\n", VERSION))
    goto fail;

  if (! print (output, error,
               "%%%%Title: %s\n"
               "%%%%CreationDate: %s"
               "%%%%DocumentData: Clean7Bit\n",
               basename, ctime (&cutime)))
    goto fail;

  if (eps || level2)
    if (! print (output, error,"%%%%LanguageLevel: 2\n"))
      goto fail;

  if (! print (output, error, "%%%%Pages: 1\n"))
    goto fail;

  g_free (basename);

  return TRUE;

 fail:

  g_free (basename);

  return FALSE;
}


/* Write out transformation for image */
static gboolean
save_ps_setup (GOutputStream  *output,
               GimpDrawable   *drawable,
               gint            width,
               gint            height,
               GObject        *config,
               gint            bpp,
               GError        **error)
{
  gdouble  config_width;
  gdouble  config_height;
  gdouble  x_offset, y_offset, x_size, y_size;
  gdouble  urx, ury;
  gdouble  width_inch, height_inch;
  gdouble  f1, f2, dx, dy;
  gint     rotation;
  gint     xtrans, ytrans;
  gint     i_urx, i_ury;
  gint     preview_size;
  gboolean unit_mm;
  gboolean keep_ratio;
  gboolean show_preview;
  gboolean level2;
  gchar    tmpbuf1[G_ASCII_DTOSTR_BUF_SIZE];
  gchar    tmpbuf2[G_ASCII_DTOSTR_BUF_SIZE];

  /* initialize */
  g_object_get (config,
                "width",        &config_width,
                "height",       &config_height,
                "x-offset",     &x_offset,
                "y-offset",     &y_offset,
                "keep-ratio",   &keep_ratio,
                "level",        &level2,
                "rotation",     &rotation,
                "show-preview", &show_preview,
                "preview",      &preview_size,
                NULL);
  unit_mm = gimp_procedure_config_get_choice_id (GIMP_PROCEDURE_CONFIG (config),
                                                 "unit");

  dx = 0.0;
  dy = 0.0;

  width_inch  = fabs (config_width);
  height_inch = fabs (config_height);

  if (unit_mm)
    {
      x_offset /= 25.4; y_offset /= 25.4;
      width_inch /= 25.4; height_inch /= 25.4;
    }

  if (keep_ratio)   /* Proportions to keep ? */
    {                        /* Fit the image into the allowed size */
      f1 = width_inch / (gint) width;
      f2 = height_inch / (gint) height;
      if (f1 < f2)
        height_inch = width_inch * (gdouble) height / (gdouble) width;
      else
        width_inch = fabs (height_inch) * (gdouble) width / (gdouble) height;
    }

  if ((rotation == 0) || (rotation == 180))
    {
      x_size = width_inch;
      y_size = height_inch;
    }
  else
    {
      y_size = width_inch;
      x_size = height_inch;
    }

  /* Round up upper right corner only for non-integer values */
  urx = (x_offset + x_size) * 72.0;
  ury = (y_offset + y_size) * 72.0;
  i_urx = (gint) urx;
  i_ury = (gint) ury;
  if (urx != (gdouble) i_urx) i_urx++;  /* Check for non-integer value */
  if (ury != (gdouble) i_ury) i_ury++;

  if (! print (output, error,
               "%%%%BoundingBox: %d %d %d %d\n"
               "%%%%EndComments\n",
               (gint) (x_offset * 72.0), (gint) (y_offset * 72.0), i_urx, i_ury))
    return FALSE;

  if (show_preview && (preview_size > 0))
    {
      if (! save_ps_preview (output, drawable, config, error))
        return FALSE;
    }

  if (! print (output, error,
               "%%%%BeginProlog\n"
               "%% Use own dictionary to avoid conflicts\n"
               "10 dict begin\n"
               "%%%%EndProlog\n"
               "%%%%Page: 1 1\n"
               "%% Translate for offset\n"
               "%s %s translate\n",
               g_ascii_dtostr (tmpbuf1, sizeof (tmpbuf1), x_offset * 72.0),
               g_ascii_dtostr (tmpbuf2, sizeof (tmpbuf2), y_offset * 72.0)))
    return FALSE;

  /* Calculate translation to startpoint of first scanline */
  switch (rotation)
    {
    case   0: dx = 0.0; dy = y_size * 72.0;
      break;
    case  90: dx = dy = 0.0;
      break;
    case 180: dx = x_size * 72.0; dy = 0.0;
      break;
    case 270: dx = x_size * 72.0; dy = y_size * 72.0;
      break;
    }

  if ((dx != 0.0) || (dy != 0.0))
    {
      if (! print (output, error,
                   "%% Translate to begin of first scanline\n"
                   "%s %s translate\n",
                   g_ascii_dtostr (tmpbuf1, sizeof (tmpbuf1), dx),
                   g_ascii_dtostr (tmpbuf2, sizeof (tmpbuf2), dy)))
        return FALSE;
    }

  if (rotation)
    if (! print (output, error, "%d rotate\n", (gint) rotation))
      return FALSE;

  if (! print (output, error,
               "%s %s scale\n",
               g_ascii_dtostr (tmpbuf1, sizeof (tmpbuf1),  72.0 * width_inch),
               g_ascii_dtostr (tmpbuf2, sizeof (tmpbuf2), -72.0 * height_inch)))
    return FALSE;

  /* Write the PostScript procedures to read the image */
  if (! level2)
    {
      if (! print (output, error,
                   "%% Variable to keep one line of raster data\n"))
        return FALSE;

      if (bpp == 1)
        {
          if (! print (output, error,
                       "/scanline %d string def\n", ((gint) width + 7) / 8))
            return FALSE;
        }
      else
        {
          if (! print (output, error,
                       "/scanline %d %d mul string def\n",
                       (gint) width, bpp / 8))
            return FALSE;
        }
    }

  if (! print (output, error,
               "%% Image geometry\n%d %d %d\n"
               "%% Transformation matrix\n",
               (gint) width, (gint) height, (bpp == 1) ? 1 : 8))
    return FALSE;

  xtrans = ytrans = 0;
  if (config_width  < 0.0)
    {
      width  = -width;
      xtrans = -width;
    }
  if (config_height < 0.0)
    {
      height = -height;
      ytrans = -height;
    }

  if (! print (output, error,
               "[ %d 0 0 %d %d %d ]\n", (gint) width, (gint) height, xtrans, ytrans))
    return FALSE;

  return TRUE;
}

static gboolean
save_ps_trailer (GOutputStream  *output,
                 GError        **error)
{
  return print (output, error,
                "%%%%Trailer\n"
                "end\n%%%%EOF\n");
}

/* Do a Floyd-Steinberg dithering on a grayscale scanline. */
/* linecount must keep the counter for the actual scanline (0, 1, 2, ...). */
/* If linecount is less than zero, all used memory is freed. */

static void
dither_grey (const guchar *grey,
             guchar       *bw,
             gint          npix,
             gint          linecount)
{
  static gboolean  do_init_arrays = TRUE;
  static gint     *fs_error = NULL;
  static gint      limit[1278];
  static gint      east_error[256];
  static gint      seast_error[256];
  static gint      south_error[256];
  static gint      swest_error[256];

  const guchar *greyptr;
  guchar       *bwptr, mask;
  gint         *fse;
  gint          x, greyval, fse_inline;

  if (linecount <= 0)
    {
      g_free (fs_error);

      if (linecount < 0)
        return;

      fs_error = g_new0 (gint, npix + 2);

      /* Initialize some arrays that speed up dithering */
      if (do_init_arrays)
        {
          gint i;

          do_init_arrays = FALSE;

          for (i = 0, x = -511; x <= 766; i++, x++)
            limit[i] = (x < 0) ? 0 : ((x > 255) ? 255 : x);

          for (greyval = 0; greyval < 256; greyval++)
            {
              east_error[greyval] = (greyval < 128) ?
                ((greyval * 79) >> 8) : (((greyval - 255) * 79) >> 8);
              seast_error[greyval] = (greyval < 128) ?
                ((greyval * 34) >> 8) : (((greyval - 255) * 34) >> 8);
              south_error[greyval] = (greyval < 128) ?
                ((greyval * 56) >> 8) : (((greyval - 255) * 56) >> 8);
              swest_error[greyval] = (greyval < 128) ?
                ((greyval * 12) >> 8) : (((greyval - 255) * 12) >> 8);
            }
        }
    }

  g_return_if_fail (fs_error != NULL);

  memset (bw, 0, (npix + 7) / 8); /* Initialize with white */

  greyptr = grey;
  bwptr = bw;
  mask = 0x80;

  fse_inline = fs_error[1];

  for (x = 0, fse = fs_error + 1; x < npix; x++, fse++)
    {
      greyval =
        limit[*(greyptr++) + fse_inline + 512];  /* 0 <= greyval <= 255 */

      if (greyval < 128)
        *bwptr |= mask;  /* Set a black pixel */

      /* Error distribution */
      fse_inline = east_error[greyval] + fse[1];
      fse[1] = seast_error[greyval];
      fse[0] += south_error[greyval];
      fse[-1] += swest_error[greyval];

      mask >>= 1;   /* Get mask for next b/w-pixel */

      if (!mask)
        {
          mask = 0x80;
          bwptr++;
        }
    }
}

/* Write a device independent screen preview */
static gboolean
save_ps_preview (GOutputStream  *output,
                 GimpDrawable   *drawable,
                 GObject        *config,
                 GError        **error)
{
  GimpImageType  drawable_type;
  GeglBuffer    *buffer = NULL;
  const Babl    *format;
  gint           bpp;
  guchar        *bwptr, *greyptr;
  gint           width, height, x, y, nbsl, out_count;
  gint           nchar_pl = 72, src_y;
  gdouble        f1, f2;
  guchar        *grey, *bw, *src_row, *src_ptr;
  guchar        *cmap;
  gint           ncols, cind;
  gint           preview_size;

  g_object_get (config,
                "preview", &preview_size,
                NULL);

  if (preview_size <= 0)
    return TRUE;

  buffer = gimp_drawable_get_buffer (drawable);
  cmap = NULL;

  drawable_type = gimp_drawable_type (drawable);
  switch (drawable_type)
    {
    case GIMP_GRAY_IMAGE:
      format = babl_format ("Y' u8");
      break;

    case GIMP_INDEXED_IMAGE:
      cmap = gimp_palette_get_colormap (gimp_image_get_palette (gimp_item_get_image (GIMP_ITEM (drawable))),
                                        babl_format ("R'G'B' u8"), &ncols, NULL);
      format = gimp_drawable_get_format (drawable);
      break;

    case GIMP_RGB_IMAGE:
    default:
      format = babl_format ("R'G'B' u8");
      break;
    }

  bpp = babl_format_get_bytes_per_pixel (format);

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

  /* Calculate size of preview */
  if ((width > preview_size) ||
      (height > preview_size))
    {
      f1 = (gdouble) preview_size / (gdouble) width;
      f2 = (gdouble) preview_size / (gdouble) height;

      if (f1 < f2)
        {
          width = preview_size;
          height *= f1;
          if (height <= 0)
            height = 1;
        }
      else
        {
          height = preview_size;
          width *= f1;
          if (width <= 0)
            width = 1;
        }
    }

  nbsl = (width + 7) / 8;  /* Number of bytes per scanline in bitmap */

  grey    = g_new (guchar, width);
  bw      = g_new (guchar, nbsl);
  src_row = g_new (guchar, gegl_buffer_get_width (buffer) * bpp);

  if (! print (output, error,
               "%%%%BeginPreview: %d %d 1 %d\n",
               width, height,
               ((nbsl * 2 + nchar_pl - 1) / nchar_pl) * height))
    goto fail;

  for (y = 0; y < height; y++)
    {
      /* Get a scanline from the input image and scale it to the desired
         width */

      src_y = (y * gegl_buffer_get_height (buffer)) / height;
      gegl_buffer_get (buffer,
                       GEGL_RECTANGLE (0, src_y,
                                       gegl_buffer_get_width (buffer), 1),
                       1.0, format, src_row,
                       GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);

      greyptr = grey;
      if (bpp == 3)   /* RGB-image */
        {
          for (x = 0; x < width; x++)
            {                       /* Convert to grey */
              src_ptr = src_row + ((x * gegl_buffer_get_width (buffer)) / width) * 3;
              *(greyptr++) = (3*src_ptr[0] + 6*src_ptr[1] + src_ptr[2]) / 10;
            }
        }
      else if (cmap)    /* Indexed image */
        {
          for (x = 0; x < width; x++)
            {
              src_ptr = src_row + ((x * gegl_buffer_get_width (buffer)) / width);
              cind = *src_ptr;   /* Get color index and convert to grey */
              src_ptr = (cind >= ncols) ? cmap : (cmap + 3*cind);
              *(greyptr++) = (3*src_ptr[0] + 6*src_ptr[1] + src_ptr[2]) / 10;
            }
        }
      else             /* Grey image */
        {
          for (x = 0; x < width; x++)
            *(greyptr++) = *(src_row + ((x * gegl_buffer_get_width (buffer)) / width));
        }

      /* Now we have a grayscale line for the desired width. */
      /* Dither it to b/w */
      dither_grey (grey, bw, width, y);

      /* Write out the b/w line */
      out_count = 0;
      bwptr = bw;
      for (x = 0; x < nbsl; x++)
        {
          if (out_count == 0)
            if (! print (output, error, "%% "))
              goto fail;

          if (! print (output, error, "%02x", *(bwptr++)))
            goto fail;

          out_count += 2;
          if (out_count >= nchar_pl)
            {
              if (! print (output, error, "\n"))
                goto fail;

              out_count = 0;
            }
        }

      if (! print (output, error, "\n"))
        goto fail;

      if ((y % 20) == 0)
        gimp_progress_update ((gdouble) y / (gdouble) height);
    }

  gimp_progress_update (1.0);

  if (! print (output, error, "%%%%EndPreview\n"))
    goto fail;

  dither_grey (grey, bw, width, -1);
  g_free (src_row);
  g_free (bw);
  g_free (grey);

  g_object_unref (buffer);

  return TRUE;

 fail:

  g_free (src_row);
  g_free (bw);
  g_free (grey);

  g_object_unref (buffer);

  return FALSE;
}

static gboolean
save_gray (GOutputStream  *output,
           GimpImage      *image,
           GimpDrawable   *drawable,
           GObject        *config,
           GError        **error)
{
  GeglBuffer *buffer = NULL;
  const Babl *format;
  gboolean    level2;
  gint        bpp;
  gint        height, width, i, j;
  gint        tile_height;
  guchar     *data;
  guchar     *src;
  guchar     *packb = NULL;

  g_object_get (config,
                "level", &level2,
                NULL);

  buffer = gimp_drawable_get_buffer (drawable);
  format = babl_format ("Y' u8");
  bpp    = babl_format_get_bytes_per_pixel (format);
  width  = gegl_buffer_get_width (buffer);
  height = gegl_buffer_get_height (buffer);

  tile_height = gimp_tile_height ();

  /* allocate a buffer for retrieving information from the pixel region  */
  src = data = (guchar *) g_malloc (tile_height * width * bpp);

  /* Set up transformation in PostScript */
  if (! save_ps_setup (output, drawable, width, height, config, 1 * 8, error))
    goto fail;

  /* Write read image procedure */
  if (! level2)
    {
      if (! print (output, error,
                   "{ currentfile scanline readhexstring pop }\n"))
        goto fail;
    }
  else
    {
      if (! print (output, error,
                   "currentfile /ASCII85Decode filter /RunLengthDecode filter\n"))
        goto fail;

      ascii85_init ();

      /* Allocate buffer for packbits data. Worst case: Less than 1% increase */
      packb = (guchar *) g_malloc ((width * 105) / 100 + 2);
    }

  if (! ps_begin_data (output, error))
    goto fail;

  if (! print (output, error, "image\n"))
    goto fail;

#define GET_GRAY_TILE(begin) \
  { gint scan_lines;                                                    \
    scan_lines = (i+tile_height-1 < height) ? tile_height : (height-i); \
    gegl_buffer_get (buffer, GEGL_RECTANGLE (0, i, width, scan_lines),  \
                     1.0, format, begin,                                \
                     GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);             \
    src = begin; }

  for (i = 0; i < height; i++)
    {
      if ((i % tile_height) == 0)
        GET_GRAY_TILE (data); /* Get more data */

      if (! level2)
        {
          for (j = 0; j < width; j++)
            {
              if (! print (output, error, "%c", hex[(*src) >> 4]))
                goto fail;

              if (! print (output, error, "%c", hex[(*(src++)) & 0x0f]))
                goto fail;

              if (((j + 1) % 39) == 0)
                if (! print (output, error, "\n"))
                  goto fail;
           }

          if (! print (output, error, "\n"))
            goto fail;
        }
      else
        {
          gint nout;

          compress_packbits (width, src, &nout, packb);

          if (! ascii85_nout (output, nout, packb, error))
            goto fail;

          src += width;
        }

      if ((i % 20) == 0)
        gimp_progress_update ((gdouble) i / (gdouble) height);
    }

  gimp_progress_update (1.0);

  if (level2)
    {
      /* Write EOD of RunLengthDecode filter */
      if (! ascii85_out (output, 128, error))
        goto fail;

      if (! ascii85_done (output, error))
        goto fail;
    }

  if (! ps_end_data (output, error))
    return FALSE;

  if (! print (output, error, "showpage\n"))
    goto fail;

  g_free (data);
  g_free (packb);

  g_object_unref (buffer);

  return TRUE;

 fail:

  g_free (data);
  g_free (packb);

  g_object_unref (buffer);

  return FALSE;

#undef GET_GRAY_TILE
}

static gboolean
save_bw (GOutputStream  *output,
         GimpImage      *image,
         GimpDrawable   *drawable,
         GObject        *config,
         GError        **error)
{
  GeglBuffer *buffer = NULL;
  const Babl *format;
  gboolean    level2;
  gint        bpp;
  gint        height, width, i, j;
  gint        ncols, nbsl, nwrite;
  gint        tile_height;
  guchar     *cmap, *ct;
  guchar     *data, *src;
  guchar     *packb = NULL;
  guchar     *scanline, *dst, mask;
  guchar     *hex_scanline;

  g_object_get (config,
                "level", &level2,
                NULL);

  cmap = gimp_palette_get_colormap (gimp_image_get_palette (image), babl_format ("R'G'B' u8"), &ncols, NULL);

  buffer = gimp_drawable_get_buffer (drawable);
  format = gimp_drawable_get_format (drawable);
  bpp    = babl_format_get_bytes_per_pixel (format);
  width  = gegl_buffer_get_width (buffer);
  height = gegl_buffer_get_height (buffer);

  tile_height = gimp_tile_height ();

  /* allocate a buffer for retrieving information from the pixel region  */
  src = data = g_new (guchar, tile_height * width * bpp);

  nbsl = (width + 7) / 8;

  scanline     = g_new (guchar, nbsl + 1);
  hex_scanline = g_new (guchar, (nbsl + 1) * 2);

  /* Set up transformation in PostScript */
  if (! save_ps_setup (output, drawable, width, height, config, 1, error))
    goto fail;

  /* Write read image procedure */
  if (! level2)
    {
      if (! print (output, error,
                   "{ currentfile scanline readhexstring pop }\n"))
        goto fail;
    }
  else
    {
      if (! print (output, error,
                   "currentfile /ASCII85Decode filter /RunLengthDecode filter\n"))
        goto fail;

      ascii85_init ();

      /* Allocate buffer for packbits data. Worst case: Less than 1% increase */
      packb = g_new (guchar, ((nbsl+1) * 105) / 100 + 2);
    }

  if (! ps_begin_data (output, error))
    goto fail;

  if (! print (output, error, "image\n"))
    goto fail;

#define GET_BW_TILE(begin) \
  { gint scan_lines;                                                    \
    scan_lines = (i+tile_height-1 < height) ? tile_height : (height-i); \
    gegl_buffer_get (buffer, GEGL_RECTANGLE (0, i, width, scan_lines),  \
                     1.0, format, begin,                                \
                     GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);             \
    src = begin; }

  for (i = 0; i < height; i++)
    {
      if ((i % tile_height) == 0)
        GET_BW_TILE (data); /* Get more data */

      dst = scanline;
      memset (dst, 0, nbsl);
      mask = 0x80;

      /* Build a bitmap for a scanline */
      for (j = 0; j < width; j++)
        {
          ct = cmap + *(src++)*3;
          if (ct[0] || ct[1] || ct[2])
            *dst |= mask;

          if (mask == 0x01)
            {
              mask = 0x80; dst++;
            }
          else
            {
              mask >>= 1;
            }
        }

      if (! level2)
        {
          /* Convert to hexstring */
          for (j = 0; j < nbsl; j++)
            {
              hex_scanline[j * 2]     = (guchar) hex[scanline[j] >> 4];
              hex_scanline[j * 2 + 1] = (guchar) hex[scanline[j] & 0x0f];
            }

          /* Write out hexstring */
          j = nbsl * 2;
          dst = hex_scanline;

          while (j > 0)
            {
              nwrite = (j > 78) ? 78 : j;

              if (! g_output_stream_write_all (output,
                                               dst, nwrite, NULL,
                                               NULL, error))
                goto fail;

              if (! print (output, error, "\n"))
                goto fail;

              j -= nwrite;
              dst += nwrite;
            }
        }
      else
        {
          gint nout;

          compress_packbits (nbsl, scanline, &nout, packb);

          if (! ascii85_nout (output, nout, packb, error))
            goto fail;
        }

      if ((i % 20) == 0)
        gimp_progress_update ((gdouble) i / (gdouble) height);
    }

  gimp_progress_update (1.0);

  if (level2)
    {
      /* Write EOD of RunLengthDecode filter */
      if (! ascii85_out (output, 128, error))
        goto fail;

      if (! ascii85_done (output, error))
        goto fail;
    }

  if (! ps_end_data (output, error))
    goto fail;

  if (! print (output, error, "showpage\n"))
    goto fail;

  g_free (hex_scanline);
  g_free (scanline);
  g_free (data);
  g_free (packb);

  g_object_unref (buffer);

  return TRUE;

 fail:

  g_free (hex_scanline);
  g_free (scanline);
  g_free (data);
  g_free (packb);

  g_object_unref (buffer);

  return FALSE;

#undef GET_BW_TILE
}

static gboolean
save_index (GOutputStream  *output,
            GimpImage      *image,
            GimpDrawable   *drawable,
            GObject        *config,
            GError        **error)
{
  GeglBuffer *buffer = NULL;
  const Babl *format;
  gboolean    level2;
  gint        bpp;
  gint        height, width, i, j;
  gint        ncols, bw;
  gint        tile_height;
  guchar     *cmap, *cmap_start;
  guchar     *data, *src;
  guchar     *packb = NULL;
  guchar     *plane = NULL;
  gchar       coltab[256 * 6], *ct;

  g_object_get (config,
                "level", &level2,
                NULL);

  cmap = cmap_start = gimp_palette_get_colormap (gimp_image_get_palette (image), babl_format ("R'G'B' u8"), &ncols, NULL);

  ct = coltab;
  bw = 1;
  for (j = 0; j < 256; j++)
    {
      if (j >= ncols)
        {
          memset (ct, 0, 6);
          ct += 6;
        }
      else
        {
          bw &= ((cmap[0] == 0)   && (cmap[1] == 0)   && (cmap[2] == 0)) ||
                ((cmap[0] == 255) && (cmap[1] == 255) && (cmap[2] == 255));

          *(ct++) = (guchar) hex[(*cmap) >> 4];
          *(ct++) = (guchar) hex[(*(cmap++)) & 0x0f];
          *(ct++) = (guchar) hex[(*cmap) >> 4];
          *(ct++) = (guchar) hex[(*(cmap++)) & 0x0f];
          *(ct++) = (guchar) hex[(*cmap) >> 4];
          *(ct++) = (guchar) hex[(*(cmap++)) & 0x0f];
        }
    }

  if (bw)
    return save_bw (output, image, drawable, config, error);

  buffer = gimp_drawable_get_buffer (drawable);
  format = gimp_drawable_get_format (drawable);
  bpp    = babl_format_get_bytes_per_pixel (format);
  width  = gegl_buffer_get_width (buffer);
  height = gegl_buffer_get_height (buffer);

  tile_height = gimp_tile_height ();

  /* allocate a buffer for retrieving information from the pixel region  */
  src = data = (guchar *) g_malloc (tile_height * width * bpp);

  /* Set up transformation in PostScript */
  if (! save_ps_setup (output, drawable, width, height, config, 3 * 8, error))
    goto fail;

  /* Write read image procedure */
  if (! level2)
    {
      if (! print (output, error,
                   "{ currentfile scanline readhexstring pop } false 3\n"))
        goto fail;
    }
  else
    {
      if (! print (output, error,
                   "%% Strings to hold RGB-samples per scanline\n"
                   "/rstr %d string def\n"
                   "/gstr %d string def\n"
                   "/bstr %d string def\n",
                   width, width, width))
        goto fail;

      if (! print (output, error,
                   "{currentfile /ASCII85Decode filter /RunLengthDecode filter\
 rstr readstring pop}\n"
                   "{currentfile /ASCII85Decode filter /RunLengthDecode filter\
 gstr readstring pop}\n"
                   "{currentfile /ASCII85Decode filter /RunLengthDecode filter\
 bstr readstring pop}\n"
                   "true 3\n"))
        goto fail;

      /* Allocate buffer for packbits data. Worst case: Less than 1% increase */
      packb = (guchar *) g_malloc ((width * 105) / 100 + 2);
      plane = (guchar *) g_malloc (width);
    }

  if (! ps_begin_data (output, error))
    goto fail;

  if (! print (output, error, "colorimage\n"))
    goto fail;

#define GET_INDEX_TILE(begin) \
  { gint scan_lines;                                                    \
    scan_lines = (i+tile_height-1 < height) ? tile_height : (height-i); \
    gegl_buffer_get (buffer, GEGL_RECTANGLE (0, i, width, scan_lines),  \
                     1.0, format, begin,                                \
                     GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);             \
    src = begin; }

  for (i = 0; i < height; i++)
    {
      if ((i % tile_height) == 0)
        GET_INDEX_TILE (data); /* Get more data */

      if (! level2)
        {
          for (j = 0; j < width; j++)
            {
              if (! g_output_stream_write_all (output,
                                               coltab + (*(src++)) * 6, 6, NULL,
                                               NULL, error))
                goto fail;

              if (((j + 1) % 13) == 0)
                if (! print (output, error, "\n"))
                  goto fail;
            }

          if (! print (output, error, "\n"))
            goto fail;
        }
      else
        {
          gint rgb;

          for (rgb = 0; rgb < 3; rgb++)
            {
              guchar *src_ptr   = src;
              guchar *plane_ptr = plane;
              gint    nout;

              for (j = 0; j < width; j++)
                *(plane_ptr++) = cmap_start[3 * *(src_ptr++) + rgb];

              compress_packbits (width, plane, &nout, packb);

              ascii85_init ();

              if (! ascii85_nout (output, nout, packb, error))
                goto fail;

              /* Write EOD of RunLengthDecode filter */
              if (! ascii85_out (output, 128, error))
                goto fail;

              if (! ascii85_done (output, error))
                goto fail;
            }

          src += width;
        }

      if ((i % 20) == 0)
        gimp_progress_update ((gdouble) i / (gdouble) height);
    }

  gimp_progress_update (1.0);

  if (! ps_end_data (output, error))
    goto fail;

  if (! print (output, error, "showpage\n"))
    goto fail;

  g_free (data);
  g_free (packb);
  g_free (plane);

  g_object_unref (buffer);

  return TRUE;

 fail:

  g_free (data);
  g_free (packb);
  g_free (plane);

  g_object_unref (buffer);

  return FALSE;

#undef GET_INDEX_TILE
}

static gboolean
save_rgb (GOutputStream  *output,
          GimpImage      *image,
          GimpDrawable   *drawable,
          GObject        *config,
          GError        **error)
{
  GeglBuffer *buffer = NULL;
  const Babl *format;
  gboolean    level2;
  gint        bpp;
  gint        height, width, tile_height;
  gint        i, j;
  guchar     *data, *src;
  guchar     *packb = NULL;
  guchar     *plane = NULL;

  g_object_get (config,
                "level", &level2,
                NULL);

  buffer = gimp_drawable_get_buffer (drawable);
  format = babl_format ("R'G'B' u8");
  bpp    = babl_format_get_bytes_per_pixel (format);
  width  = gegl_buffer_get_width (buffer);
  height = gegl_buffer_get_height (buffer);

  tile_height = gimp_tile_height ();

  /* allocate a buffer for retrieving information from the pixel region  */
  src = data = g_new (guchar, tile_height * width * bpp);

  /* Set up transformation in PostScript */
  if (! save_ps_setup (output, drawable, width, height, config, 3 * 8, error))
    goto fail;

  /* Write read image procedure */
  if (! level2)
    {
      if (! print (output, error,
                   "{ currentfile scanline readhexstring pop } false 3\n"))
        goto fail;
    }
  else
    {
      if (! print (output, error,
                   "%% Strings to hold RGB-samples per scanline\n"
                   "/rstr %d string def\n"
                   "/gstr %d string def\n"
                   "/bstr %d string def\n",
                   width, width, width))
        goto fail;

      if (! print (output, error,
                   "{currentfile /ASCII85Decode filter /RunLengthDecode filter\
 rstr readstring pop}\n"
                   "{currentfile /ASCII85Decode filter /RunLengthDecode filter\
 gstr readstring pop}\n"
                   "{currentfile /ASCII85Decode filter /RunLengthDecode filter\
 bstr readstring pop}\n"
                   "true 3\n"))
        goto fail;

      /* Allocate buffer for packbits data. Worst case: Less than 1% increase */
      packb = g_new (guchar, (width * 105) / 100 + 2);
      plane = g_new (guchar, width);
    }

  if (! ps_begin_data (output, error))
    goto fail;

  if (! print (output, error, "colorimage\n"))
    goto fail;

#define GET_RGB_TILE(begin) \
  { gint scan_lines;                                                    \
    scan_lines = (i+tile_height-1 < height) ? tile_height : (height-i); \
        gegl_buffer_get Manipulation Program
                     0  ,\
                     GEGL_AUTO_ROWSTRIDE )             
     ;}

   (i=0; i < height; i+)
    {
      if ((i % tile_height) == 0)
        GET_RGB_TILE (data); /* Get more data */

      if *
        {
for(  0;j<width;j+)
            {
              if (! print (output, error, "%c",
                           hex[(*src) >> 4]))        /* Red */
                goto fail;

              if (! print (output, error, "%c",
                           hex[(**                                                  --
                goto fail;

              if (! print (output, error, "%c",
                           hex(src) > ])/*Green*
                gotofail;

              if !print (utput,error,"c,
                           hex[(*(src++)) &  *java.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
                .java.lang.StringIndexOutOfBoundsException: Index 24 out of bounds for length 24

              if (! print (utput,error,"c,
c)>4))       /* Blue */
                ;

              if (!print (utput,error, "c",
                           hex[(*#               5/    expand map*java.lang.StringIndexOutOfBoundsException: Index 77 out of bounds for length 77
                goto fail;

               (((+1)%13 = 0)
                  gboolean  closed
                  goto fail;
            }

          if( print (utput, error, "n")
             fail;
        }
      else
        {
          gint rgb;

                     GdkModifierType        state,
            {
              guchar src_ptr   = src +rgb;
              guchar *plane_ptr = plane;
              gint              gimp_igimp_iscissors_tool_oper_updateGimpTool              tool

               ( =0;j width;j+)
                
                  plane_ptr++ =*rc_ptr;
                  src_ptr += 3;
                }

             (  n,;

              ascii85_init ();

              if static voidgimp_iscissors_tool_pop_undo       (impIscissorsTool*)
                goto fail;

              /* Write EOD of RunLengthDecode filter */           display)
               !ascii85_out (output 128, error)
                goto fail;

if!java.lang.StringIndexOutOfBoundsException: Range [33, 32) out of bounds for length 49
                goto fail;
            }

src+3 *;
        }

      if ((i % 20) == 0)
_ ((gdouble)i /(double height)
    }java.lang.StringIndexOutOfBoundsException: Range [7, 6) out of bounds for length 77

gimp_progress_update10)

  ifgintye;
    

  if (! print (output, error, "showpagestatic ISegment    *isegment_copy             (ISegment          *segment);
    goto fail;

  g_free (data);
  (ackb;
  g_free (plane);

  g_object_unref (buffer);

  return                                                 ,

 fail:

  g_free (
  static const gint [][2]=
  g_free (plane) {-1,   }

  g_object_unref (buffer);

  return FALSE;

#undef GET_RGB_TILE
}

static gboolean
(  callback
       GError        **error,
       const gchar    *format,
       ...)
{
  va_list  args;
  java.lang.StringIndexOutOfBoundsException: Index 9 out of bounds for length 1

  va_start (args, format);
  success = 
                                     error, format, args);  - = ;
  va_end (args        ;

return;
}

/*  Load interface functions  */

java.lang.StringIndexOutOfBoundsException: Index 2 out of bounds for length 0
count_ps_pages(File file
{
 FILE   *psfile;
  distance_weigh[ *GRADIENT_SEARCH+j =
  gchar   buf[1024
  gint32  num_pages      = 0;
  gint32  showpage_count = 0;

  extension = strrchr (g_file_peek_path (file), '.');
  if (extension)
    {
      void

        " )
java.lang.StringIndexOutOfBoundsException: Index 9 out of bounds for length 9
          g_free (case GIMP_TOOL_ACTION_PAU
          return 1       i,)java.lang.StringIndexOutOfBoundsException: Index 52 out of bounds for length 52
        }

       )
    }

    (file_peek_pathfile) r"java.lang.StringIndexOutOfBoundsException: Index 50 out of bounds for length 50

ifpsfile= 
    java.lang.StringIndexOutOfBoundsException: Index 5 out of bounds for length 5
      g_message java.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
                  file,g_strerror(rrno)java.lang.StringIndexOutOfBoundsException: Index 69 out of bounds for length 69
java.lang.StringIndexOutOfBoundsException: Index 9 out of bounds for length 9
    }

  while (num_pages
     }
      fgets (buf, sizeof                                                -

      if (strncmp (buf ns>interactive)
}
      else if (java.lang.StringIndexOutOfBoundsException: Index 22 out of bounds for length 0
        showpage_count ( *,
    }

  if (   (>java.lang.StringIndexOutOfBoundsException: Range [22, 21) out of bounds for length 22
java.lang.StringIndexOutOfBoundsException: Range [65, 64) out of bounds for length 74

  fclose (psfile

  return num_pages;
}

static gboolean
  /* Make sure X didn't skip the button release event -- as it's known
             GimpVectorLoadData  extracted_data,
                  procedurejava.lang.StringIndexOutOfBoundsException: Index 43 out of bounds for length 43
             GObject segment*
{
*dialog;
GtkWidget*hbox;
GtkWidget     vbox;
  GtkWidget     *target   = NULL;
java.lang.StringIndexOutOfBoundsException: Index 44 out of bounds for length 33
  gint32         page_count;
  gchar         *range    = NULL;
  gboolean       run;

  page_count = count_ps_pages{

gimp_ui_init P)

  dialog = gimp_vector_load_procedure_dialog_new (java.lang.StringIndexOutOfBoundsException: Index 66 out of bounds for length 21
                                                  GIMP_PROCEDURE_CONFIG (config),
       &,NULL);

  if (page_count > 1)
    java.lang.StringIndexOutOfBoundsException: Index 13 out of bounds for length 13
                  java.lang.StringIndexOutOfBoundsException: Index 17 out of bounds for length 17
( (tk_dialog_get_content_area (TK_DIALOG)
                          ,TRUE, );
      gimp_page_selector_set_n_pages (java.lang.StringIndexOutOfBoundsException: Index 56 out of bounds for length 16
                                      page_count)  
      gimp_page_selector_set_target (GIMP_PAGE_SELECTOR (selector),
                                     ps_pagemode)        

      gtk_widget_set_visible (selector,   *    /

      g_signal_connect_swapped (selector, "activate",
                      g)
                                dialog);
    }

  vbox = gimp_procedure_dialog_fill_box java.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
                                         "rendering-box",
                                         "check-bbox",
                                               segment = g_queue_peek_tail (iscissors>)

  if         
    
      SEED_ADJUSTMENT:
      GtkWidget *entry = NULL;

      grid = gtk_grid_new ();
       G (grid) 6;
      gtk_grid_set_column_spacing (GTK_GRID (grid),             i >;
      gtk_box_pack_start (GTK_BOX (vbox    java.lang.StringIndexOutOfBoundsException: Index 5 out of bounds for length 5
      (rid,TRUE);

       
                                                 list   i-curve>;
      gtk_widget_set_size_request (entry, 80, -1);
      gimp_grid_attach_aligned            (s= java.lang.StringIndexOutOfBoundsException: Range [51, 49) out of bounds for length 53
                                _rif !options-i)
                                entry, 1);

      gimp_help_set_help_data (GTK_WIDGET (entry
                               _("Pages to load (e.g.: 1-java.lang.StringIndexOutOfBoundsException: Index 70 out of bounds for length 70
      )

      target =         
      gtk_combo_box_set_active (GTK_COMBO_BOXplacementjava.lang.StringIndexOutOfBoundsException: Index 55 out of bounds for length 55
      gimp_grid_attach_aligned (GTK_GRID (grid), 0, 1,
                                           placement)
                                    java.lang.StringIndexOutOfBoundsException: Index 5 out of bounds for length 5
    }

  /* Dialog formatting */

  hbox = gimp_procedure_dialog_fill_box (GIMP_PROCEDURE_DIALOG
                                         "
                                         "textalpha-,
                                         "graphic-alpha+,point++
                                         NULL);
  java.lang.StringIndexOutOfBoundsException: Index 5 out of bounds for length 5
  gtk_box_set_homogeneous
  gtk_widget_set_margin_bottom (hbox, gimp_iscissors_tool_oper_updateG         *,
  gtk_orientable_set_orientation (GTK_ORIENTABLE (hbox),
                                  GTK_ORIENTATION_HORIZONTAL

     ( (ialog,
                                         "-row"java.lang.StringIndexOutOfBoundsException: Index 57 out of bounds for length 57
                                         c,
                                         "ps-anti-aliasing- {
                                         NULL);
  gtk_box_set_spacing (GTK_BOX (vbox), 12)}

gimp_procedure_dialog_fillGIMP_PROCEDURE_DIALOGdialog,
                              "rendering_(" this
                              "ps-bottom-row",
                              NULL

  gtk_widget_set_visible (dialog, TRUE);

  GIMP_HANDLE_CIRCLE,

  if (selector)
    {
     =gimp_page_selector_get_selected_rangeGIMP_PAGE_SELECTORselector));

      if (strlen (range) < 1)
        {
ECTOR (selector)java.lang.StringIndexOutOfBoundsException: Index 72 out of bounds for length 72
          range                                         coords>,
        java.lang.StringIndexOutOfBoundsException: Index 9 out of bounds for length 9
gimp_page_selector_unselect_alljava.lang.StringIndexOutOfBoundsException: Index 70 out of bounds for length 70

config
                    "pages", range,
       )
      ps_pagemode =   
    }
  else if (page_count                snap_mask=gimp_get_extend_selection_mask;
    {
      ps_pagemode = gtk_combo_box_get_active (GTK_COMBO_BOX (target));                                                &~,
    }
  else
    {
      java.lang.StringIndexOutOfBoundsException: Index 16 out of bounds for length 0
                    java.lang.StringIndexOutOfBoundsException: Index 16 out of bounds for length 16
                    NULL);
      ps_pagemode = GIMP_PAGE_SELECTOR_TARGET_IMAGES;
    }

  gtk_widget_destroy (

  return run
}

/*  Save interface functions  */

static gboolean
ave_dialog(impProcedure *procedure,
             GObject       *config,
             GimpImage     *image)
{
  GtkWidget    *dialog;
  GtkListStore *store;
  GtkWidget    *toggle;
  GtkWidget    
  gboolean      run;

  =gimp_export_procedure_dialog_new p),
                                             GIMP_PROCEDURE_CONFIG (config),
                                             image);

  /* Image Size */
  /* Width/Height/X-/Y-offset labels */
  toggle = gimp_iscissors_tool_key_press (GimpTool    *tool,
                                             "keep-ratio", G_TYPE_NONE);

  gimp_help_set_help_data toggle,
                           (When toggled,the resultingimage willbe"
                             "scaled to fit into java.lang.StringIndexOutOfBoundsException: Index 52 out of bounds for length 25
                             "changing the aspect ratio."),
                           "#keep_aspect_ratio"),

  gimp_procedure_dialog_get_label (            {
                                   gimp_iscissors_tool_push_undo()
                                   FALSE, FALSE);

  gimp_procedure_dialog_fill_box (
                                  "gimp_iscissors_tool_push_undoi;
                                  "keep-ratio" ptr_array_remove_range>java.lang.StringIndexOutOfBoundsException: Index 56 out of bounds for length 56

  gimp_procedure_dialog_fill_frame (GIMP_PROCEDURE_DIALOG (dialog)            java.lang.StringIndexOutOfBoundsException: Index 13 out of bounds for length 13
                                    "image-frame", "image-title",
             image";

  /* Unit */
  g_signal_connect (config, "notify::unit",
                    G_CALLBACK (save_unit_changed_update}
                    )

/
  store = gimp_int_store_newjava.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
                              _("_90"),  90,
                              _("_180"), 180,                 (scissors>java.lang.StringIndexOutOfBoundsException: Index 64 out of bounds for length 64
                              _("_              iscissors_convert iscissors,;
                              NULL);
  gimp_procedure_dialog_get_int_radio (GIMP_PROCEDURE_DIALOG (dialog),
                                       "rotationjava.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0


    GimpIscissorsTool=java.lang.StringIndexOutOfBoundsException: Range [54, 52) out of bounds for length 60
"output-" (Output)java.lang.StringIndexOutOfBoundsException: Index 63 out of bounds for length 63
                                   FALSE, FALSE);

  gimp_procedure_dialog_set_sensitive java.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
                                       "java.lang.StringIndexOutOfBoundsException: Range [0, 47) out of bounds for length 36
                                       TRUE (    tool,


  gimp_procedure_dialog_fill_box (GIMP_PROCEDURE_DIALOG (dialog), "output-box",
                                  "level", "eps-flag", "showiscissors-curve)
                                  NULLgimp_draw_tool_stopGIMP_DRAW_TOOL tool)

  gimp_procedure_dialog_fill_frame (static 
"-" ,
                                    java.lang.StringIndexOutOfBoundsException: Index 41 out of bounds for length 0

  /* Dialog Formatting */

  vbox = gimp_procedure_dialog_fill_box (GIMP_PROCEDURE_DIALOG (dialog), "left-column",
                                         "iscissors->redo_stack = g_list_remove (iscissors,
                                         NULL);
  gtk_box_set_spacing (GTK_BOX  return;
  vbox = gimp_procedure_dialog_fill_box (GIMP_PROCEDURE_DIALOG (GimpIscissorsTool*)
                                         "rotation->ndo_stack,
                                         NULL);
  gtk_box_set_spacing (GTK_BOX (vbox), 12);
  hbox = gimp_procedure_dialog_fill_box (GIMP_PROCEDURE_DIALOG (dialog  >=g_list_removei-undo_stack
                                         "left-column"if!-undo_stack
                                         NULL);
  gtk_box_set_spacing (GTK_BOX (hbox), 12);
  gtk_orientable_set_orientation (GTK_ORIENTABLE (hbox),
                                  )

  gimp_procedure_dialog_fill (GIMP_PROCEDURE_DIALOG (dialog),
                              "ps-hbox",
                              java.lang.StringIndexOutOfBoundsException: Index 31 out of bounds for length 29

   dialogTRUE

  run = gimp_procedure_dialog_run(scissors> GDestroyNotify)

  gtk_widget_destroy (dialog);

  return run;
}

static void
save_unit_changed_update (GimpProcedureConfig *config)
java.lang.StringIndexOutOfBoundsException: Index 1 out of bounds for length 1
gdoublefactor;
gbooleanunit_mm;
                   firstx,
  gdouble  height;
  gdouble  x_offset;
  gdouble  y_offset;

  g_object_get (config,
                "width",    &width                                   
                "height",   &height,
                "x
                "y-java.lang.StringIndexOutOfBoundsException: Index 23 out of bounds for length 1
                NULLjava.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
  unit_mm = gimp_procedure_config_get_choice_id (config, "unit");

  if (unit_mm)
    factor = 25.4;
  else
    factor = 1.0 / 25.4;

  g_object_set (config,
                 for (   (scissors>-segments)
                "height",{
                x, *factor
                "y-offset", y_offsetGIMP_HANDLE_CIRCLE
                NULL);
}

Messung
CHZG
829510092

¤ Dauer der Verarbeitung: 0.110 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.