[gs-commits] ghostpdl branch, pdfi, updated. jbig2dec-0.14-2551-gad96b0e

[email protected] (Ken Sharp) Wed, 20 Nov 2019 10:05:24 +0000 (UTC)
Newsgroups gmane.comp.printing.ghostscript.cvs
Message-ID <[email protected]>
The ghostpdl branch, pdfi has been updated
       via  ad96b0e2ee0ee50530ceb217ae57894791d11052 (commit)
       via  c09fbcd400e41d621659fd9323031b157f6937a8 (commit)
       via  c5eb242fa82585aa21371919ca4a7a2d4d4effc3 (commit)
       via  69bdf79e10370ed16a17a346690fd630272f7346 (commit)
       via  b5956ac8be8c923ad9e9fd3045724a5579cf4dce (commit)
       via  0329372164611fdff22f8c02201ff1fbbf94aae7 (commit)
       via  cd8747382f47393efd0f0013700a8294c0ded15c (commit)
       via  e1b7dbc95945ef2afa38faeebfda56830f2762ab (commit)
       via  e93430d762400247cdc4550be403a1f7c291112d (commit)
       via  bb2c2fbbc96b1f2de8e454ecb2adf3e58ddb52c3 (commit)
       via  a4c44cc7579ab80a1d49d721fad48e1ed9bbe976 (commit)
       via  f804ebfa526bd36a808319225488fe9de416f350 (commit)
       via  7ff87e872888da0f5cfd392b3f8b90fa7ad0e27c (commit)
       via  7e50bda19813d11555ff1ab4f7cb5c3e55ff0653 (commit)
       via  05cfc3afb00404ddfd3c1eac635f369491995e5b (commit)
       via  81e179af142575eab21633b2023e752e1e244c9a (commit)
       via  d57aff5bbdb206f00e4b0cb3b324616be10d358e (commit)
       via  3aa36ebc3df83d339695fff58a73332d5304c47d (commit)
       via  4a1a4e8ad165e678d3affab483460ec8b926d5dd (commit)
       via  1ee350a81d5799785ab53b3eece6e853cc4c607b (commit)
       via  884690a576e16043e674efb9d047a00760ac8443 (commit)
       via  9763a006f7c566e92f60e799fb2270c09807e9c8 (commit)
       via  dd84fd3a36d0ef809ed697ed12f1bb04655a0388 (commit)
       via  a631e0d40337da58ba596a9499b71c1d7888c9d5 (commit)
       via  6fbd278d11e9ea567ea2dce0c7aa02e706d9a9e2 (commit)
       via  e05d0e17c75adab181413c8bf5bf6760c76683e4 (commit)
       via  da03855bf9ca18eab05d4ac870d73f457758a77f (commit)
       via  0aa9d4f8237f7a0b459fc567e76ab8f0c4c2eebd (commit)
       via  ea274a722040b1186374a96d61715ae96d58c36b (commit)
       via  3a913d9dc801508b19a8d5605e785852e1bfb082 (commit)
       via  8bf3152fe37368b49dcd23535a407c632d7157d2 (commit)
       via  5a4c29eeea1f0b63cc0b2fff3a1ca0f5b2d143b6 (commit)
       via  7fc7881b00a43fe1017e593846a0baf28e8837db (commit)
       via  161adc70e0d6625618c5a6afcb5e04161acc2e2c (commit)
       via  b8de03bdb8696517bff0259e827ece4f5746663c (commit)
       via  58bee0ad2c67888a52fa02e31de3120673525fda (commit)
       via  372e4f29d3fcbfeaa83a5ee8cc530cbb948edab7 (commit)
       via  194c53ed02d657159a7be894b6d4b54ce1f435d4 (commit)
       via  83c6e520ee6eddeaa1e548e519eb1b42941e3153 (commit)
       via  76c088b2b5468c1551c4636dc9d20f5b1ea5da36 (commit)
       via  b51e2be557128054b0f1e8f6eb4c3b7de506cca2 (commit)
       via  dd95eb235f00a600f8b8b99b8929e597283fa96c (commit)
       via  95706a3ced4f1cfa50b2ea48f0095ed9423bafe5 (commit)
       via  5f24a32f0cc4513dfb9825900d561b3a6771b87c (commit)
       via  de9dc99614f86e4aaa0a447766a58447e76ca8c1 (commit)
       via  0ea433a1a172298eda11203d698ce6fe69be58d2 (commit)
       via  51aff033bdc8744adfd86ce8b35ef00f23621de2 (commit)
       via  33f4f0c0ba5449fd535061e785090bc57849ae51 (commit)
       via  98a8b291b135d49ffca23d9a289786971cf33a12 (commit)
       via  91b104936ec55b3f1f22673d7e286ff67e4e2475 (commit)
       via  af558a708b40a78eef9c0bc5b2ae229d771a3537 (commit)
       via  f63270230f329c6e89375af18d2bf2930421174a (commit)
       via  97277fcf0f70d482481ac329ce473e5687e7a935 (commit)
       via  6690c60c3e1be875134150fa4909bff0de7d583f (commit)
       via  9e1d2f76cabf84576424af0c38dceef5881c2b21 (commit)
       via  e6d0fcd87934f01d4fb3c3029558fea134fbd086 (commit)
       via  d11a43b64a2e623aebdfa34f8ee992887dc5b2b2 (commit)
       via  24ec06a27df63297796a379c95ee5d4b39040410 (commit)
       via  592fb032180aac0fc22a7c860ef116b38da8ca71 (commit)
       via  7da60037b788bec4217f6ee7017e238f541714ae (commit)
       via  658e14023fa84232c569ecc5528b77fd35bcf290 (commit)
       via  11f8439f49a3704bb4506e02382a0e697e5edf8e (commit)
       via  a1747b9267b4aac28e256820c5cea37af299af86 (commit)
       via  37a322fb93c5cd1e96a4cad2ffccdc397f7c0ea5 (commit)
       via  aad7bcab6a3aedc4e2bfecd7b1e6bf893c299d0e (commit)
       via  2de09275898086e903a19812fd78e73381f7e71e (commit)
       via  c198ac5dcb26fadc53074525cabb413e5395cdba (commit)
       via  7174b69e6c0027277fe1a9ece341c3380fe7979d (commit)
       via  ba5b47dc979ca1590f280f482d432705e9b535bf (commit)
       via  4a3401e5e4f5828c67d7c65f77a3bb0106753649 (commit)
       via  f44249d69a36fa7233504bdb137af9ff5008ec37 (commit)
       via  2029235dc8997314c1925e5786212a6c7331f0cf (commit)
       via  5605c6c6a9677013560a6a3c947f4b100eae4e09 (commit)
       via  95151ae3c3c63100bc7ae958f59b8096e7a0f211 (commit)
       via  0f7653fd4f1d1c5bf36719efcd8464eafcd46faf (commit)
       via  6882df85d37819d849476cf2b4822a90e64d9b24 (commit)
       via  b7fac758d046c82d65fa69dd2c4bc0f1cf9712bc (commit)
       via  328d27b3cccc89c4ff60529106489a9a635f0f72 (commit)
       via  68aeff88e95ccfd1af430fe180597d624ad9f47e (commit)
       via  713645137a2fc483e5509bfd0f92d7311faeb614 (commit)
       via  6c3af6e12e4448b519d2b48c2047c0c83b3cf082 (commit)
       via  66878fb30f37b06a532fdce39991f31642cdb09b (commit)
       via  76db3df8090127482960b742f15831e59816a05e (commit)
       via  9caccf9f09e06324f07822cc55e1737d881c6eb3 (commit)
       via  018003614ad80910ca2c009eb1d523e8015756bc (commit)
      from  70d25d8ffdd55c6c57e50541e68772b4bc341196 (commit)

----------------------------------------------------------------------
commit ad96b0e2ee0ee50530ceb217ae57894791d11052
Author: Ken Sharp <[email protected]>
Date:   Wed Nov 20 09:39:18 2019 +0000

    squash into prior

diff --git a/gpdl/jpgtop.c b/gpdl/jpgtop.c
new file mode 100644
index 0000000..c2b46ed
--- /dev/null
+++ b/gpdl/jpgtop.c
@@ -0,0 +1,869 @@
+/* Copyright (C) 2019 Artifex Software, Inc.
+   All Rights Reserved.
+
+   This software is provided AS-IS with no warranty, either express or
+   implied.
+
+   This software is distributed under license and may not be copied,
+   modified or distributed except as expressly authorized under the terms
+   of the license contained in the file LICENSE in this distribution.
+
+   Refer to licensing information at http://www.artifex.com or contact
+   Artifex Software, Inc.,  1305 Grant Avenue - Suite 200, Novato,
+   CA 94945, U.S.A., +1(415)492-9861, for further information.
+*/
+
+/* jpgtop.c */
+/* Top-level API implementation of "JPG" Language Interface */
+
+#include "pltop.h"
+#include "gserrors.h"
+#include "gxdevice.h"
+#include "gsstate.h"
+#include "strimpl.h"
+#include "gscoord.h"
+#include "jpeglib.h"
+#include "setjmp_.h"
+#include "sjpeg.h"
+
+/* Forward decls */
+
+/************************************************************/
+/******** Language wrapper implementation (see pltop.h) *****/
+/************************************************************/
+
+typedef enum
+{
+    ii_state_identifying = 0,
+    ii_state_jpeg,
+    ii_state_jpeg_header,
+    ii_state_jpeg_start,
+    ii_state_jpeg_rows,
+    ii_state_jpeg_finish,
+    ii_state_flush
+} ii_state;
+
+typedef struct {
+    struct jpeg_error_mgr pub;
+    jmp_buf setjmp_buffer;
+} jpg_error_mgr;
+
+/*
+ * Jpg interpreter instance
+ */
+typedef struct jpg_interp_instance_s {
+    gs_memory_t       *memory;
+    gs_memory_t       *cmemory;
+    gx_device         *dev;
+    gx_device         *nulldev;
+
+    gs_color_space    *gray;
+    gs_color_space    *rgb;
+    gs_color_space    *cmyk;
+
+    /* Jpg parser state machine */
+    ii_state           state;
+
+    int                pages;
+
+    uint8_t            bpp;
+    uint8_t            cs;
+    uint32_t           width;
+    uint32_t           height;
+    uint32_t           xresolution;
+    uint32_t           yresolution;
+
+    uint32_t           num_comps;
+    uint32_t           byte_width;
+    uint32_t           y;
+
+    uint32_t           bytes_available_on_entry;
+
+    gs_image_t         image;
+    gs_image_enum     *penum;
+    gs_gstate         *pgs;
+
+    struct jpeg_decompress_struct cinfo;
+    struct jpeg_source_mgr        jsrc;
+    size_t             bytes_to_skip;
+    jpg_error_mgr      jerr;
+
+    byte              *samples;
+
+} jpg_interp_instance_t;
+
+static int
+jpg_detect_language(const char *s, int len)
+{
+    const byte *hdr = (const byte *)s;
+    if (len >= 11) {
+        if (hdr[0] == 0xFF && hdr[1] == 0xd8 && hdr[2] == 0xff && hdr[3] == 0xe0 &&
+            strncmp("JFIF", s+6, 5) == 0)
+            return 100;
+    }
+    /* FIXME: Other formats go here */
+
+    return 0;
+}
+
+static const pl_interp_characteristics_t jpg_characteristics = {
+    "JPG",
+    jpg_detect_language,
+    "Artifex",
+    "0.01",
+    "11 Nov 2019",
+    1 /* minimum input size */
+};
+
+/* GS's fakakta jpeg integration insists on putting a
+ * memory structure pointer in the decompress structs client data.
+ * This is no good to find our instance from. We therefore find
+ * it by offsetting back from the address of the cinfo. This is
+ * a nasty bit of casting, so wrap it in a macro. */
+#define JPG_FROM_CINFO(CINFO) \
+    (jpg_interp_instance_t *)(((char *)(CINFO))-offsetof(jpg_interp_instance_t,cinfo))
+
+
+/* Get implementation's characteristics */
+static const pl_interp_characteristics_t * /* always returns a descriptor */
+jpg_impl_characteristics(const pl_interp_implementation_t *impl)     /* implementation of interpreter to alloc */
+{
+  return &jpg_characteristics;
+}
+
+static void
+jpg_deallocate(jpg_interp_instance_t *jpg)
+{
+    if (jpg == NULL)
+        return;
+
+    rc_decrement_cs(jpg->gray, "jpg_deallocate");
+    rc_decrement_cs(jpg->rgb, "jpg_deallocate");
+    rc_decrement_cs(jpg->cmyk, "jpg_deallocate");
+
+    if (jpg->pgs != NULL)
+        gs_gstate_free_chain(jpg->pgs);
+    gs_free_object(jpg->memory, jpg, "jpg_impl_allocate_interp_instance");
+}
+
+/* Deallocate a interpreter instance */
+static int
+jpg_impl_deallocate_interp_instance(pl_interp_implementation_t *impl)
+{
+    jpg_interp_instance_t *jpg = (jpg_interp_instance_t *)impl->interp_client_data;
+
+    jpg_deallocate(jpg);
+    impl->interp_client_data = NULL;
+
+    return 0;
+}
+
+/* Do per-instance interpreter allocation/init. */
+static int
+jpg_impl_allocate_interp_instance(pl_interp_implementation_t *impl, gs_memory_t *mem)
+{
+    int code;
+    jpg_interp_instance_t *jpg
+        = (jpg_interp_instance_t *)gs_alloc_bytes(mem,
+                                                  sizeof(jpg_interp_instance_t),
+                                                  "jpg_impl_allocate_interp_instance");
+    if (!jpg)
+        return_error(gs_error_VMerror);
+    memset(jpg, 0, sizeof(*jpg));
+
+    jpg->memory = mem;
+    jpg->pgs = gs_gstate_alloc(mem);
+    if (jpg->pgs == NULL)
+        goto failVM;
+
+    /* Push one save level onto the stack to assuage the memory handling */
+    code = gs_gsave(jpg->pgs);
+    if (code < 0)
+        goto fail;
+
+    code = gsicc_init_iccmanager(jpg->pgs);
+    if (code < 0)
+        goto fail;
+
+    jpg->gray = gs_cspace_new_ICC(mem, jpg->pgs, 1);
+    jpg->rgb  = gs_cspace_new_ICC(mem, jpg->pgs, 3);
+    jpg->cmyk = gs_cspace_new_ICC(mem, jpg->pgs, 4);
+
+    impl->interp_client_data = jpg;
+
+    return 0;
+
+failVM:
+    code = gs_note_error(gs_error_VMerror);
+fail:
+    (void)jpg_deallocate(jpg);
+    return code;
+}
+
+/*
+ * Get the allocator with which to allocate a device
+ */
+static gs_memory_t *
+jpg_impl_get_device_memory(pl_interp_implementation_t *impl)
+{
+    jpg_interp_instance_t *jpg = (jpg_interp_instance_t *)impl->interp_client_data;
+
+    return jpg->dev ? jpg->dev->memory : NULL;
+}
+
+#if 0 /* UNUSED */
+static int
+jpg_impl_set_param(pl_interp_implementation_t *impl,
+                   pl_set_param_type           type,
+                   const char                 *param,
+                   const void                 *val)
+{
+    jpg_interp_instance_t *jpg = (jpg_interp_instance_t *)impl->interp_client_data;
+
+    /* No params set here */
+    return 0;
+}
+
+static int
+jpg_impl_add_path(pl_interp_implementation_t *impl,
+                  const char                 *path)
+{
+    jpg_interp_instance_t *jpg = (jpg_interp_instance_t *)impl->interp_client_data;
+
+    /* No paths to add */
+    return 0;
+}
+
+static int
+jpg_impl_post_args_init(pl_interp_implementation_t *impl)
+{
+    jpg_interp_instance_t *jpg = (jpg_interp_instance_t *)impl->interp_client_data;
+
+    /* No post args processing */
+    return 0;
+}
+#endif
+
+/* Prepare interp instance for the next "job" */
+static int
+jpg_impl_init_job(pl_interp_implementation_t *impl,
+                  gx_device                  *device)
+{
+    jpg_interp_instance_t *jpg = (jpg_interp_instance_t *)impl->interp_client_data;
+
+    jpg->dev = device;
+    jpg->state = ii_state_identifying;
+
+    return 0;
+}
+
+#if 0 /* UNUSED */
+static int
+jpg_impl_run_prefix_commands(pl_interp_implementation_t *impl,
+                             const char                 *prefix)
+{
+    jpg_interp_instance_t *jpg = (jpg_interp_instance_t *)impl->interp_client_data;
+
+    return 0;
+}
+
+static int
+jpg_impl_process_file(pl_interp_implementation_t *impl, const char *filename)
+{
+    jpg_interp_instance_t *jpg = (jpg_interp_instance_t *)impl->interp_client_data;
+
+    return 0;
+}
+#endif
+
+/* Do any setup for parser per-cursor */
+static int                      /* ret 0 or +ve if ok, else -ve error code */
+jpg_impl_process_begin(pl_interp_implementation_t * impl)
+{
+    jpg_interp_instance_t *jpg = (jpg_interp_instance_t *)impl->interp_client_data;
+
+    return 0;
+}
+
+/* Ensure we have 'required' bytes to read, and further ensure
+ * that we have no UEL's within those bytes. */
+static int
+ensure_bytes(jpg_interp_instance_t *jpg, stream_cursor_read *pr, int required)
+{
+    int n;
+    const uint8_t *p = pr->ptr+1;
+    const uint8_t *q;
+    int avail;
+
+    /* Find out how many bytes we need to check */
+    n = pr->limit - pr->ptr;
+    if (n > required)
+        n = required;
+
+    /* Make sure there are no UELs in that block */
+    q = p + n;
+    while (p != q) {
+        while (p != q && *p != '\033')
+            p++;
+        if (p == q)
+            break;
+        avail = pr->limit - pr->ptr;
+        if (memcmp(p, "\033%-12345X", min(avail, 9)) == 0) {
+            /* At least a partial match to a UEL */
+            return avail < 9 ? gs_error_NeedInput : gs_error_InterpreterExit;
+        }
+        p++;
+    }
+
+    /* If we have enough bytes, great, if not, get some more */
+    return (n < required) ? gs_error_NeedInput : 0;
+}
+
+static int
+flush_to_uel(stream_cursor_read *pr)
+{
+    const uint8_t *p = pr->ptr+1;
+    const uint8_t *q = pr->limit+1;
+    int avail;
+
+    while (p != q) {
+        while (p != q && *p != '\033')
+            p++;
+        if (p == q)
+            break;
+        avail = pr->limit - pr->ptr;
+        if (memcmp(p, "\033%-12345X", min(avail, 9)) == 0) {
+            /* At least a partial match to a UEL. Bin everything to
+             * the start of the match. */
+            pr->ptr = p-1;
+            if (avail == 9) /* Complete match. Exit! */
+                return gs_error_InterpreterExit;
+            /* Partial match. Get more data. */
+            return gs_error_NeedInput;
+        }
+        p++;
+    }
+
+    pr->ptr = pr->limit;
+
+    return 0;
+}
+
+static int
+bytes_until_uel(const stream_cursor_read *pr)
+{
+    const uint8_t *p = pr->ptr+1;
+    const uint8_t *q = pr->limit+1;
+    int avail;
+
+    while (p != q) {
+        while (p != q && *p != '\033')
+            p++;
+        if (p == q)
+            break;
+        avail = pr->limit - pr->ptr;
+        if (memcmp(p, "\033%-12345X", min(avail, 9)) == 0) {
+            /* At least a partial match to a UEL. Everything up to
+             * the start of the match is up for grabs. */
+            return p - (pr->ptr+1);
+        }
+        p++;
+    }
+
+    return pr->limit - pr->ptr;
+}
+
+static int
+get32be(stream_cursor_read *pr)
+{
+    int v = pr->ptr[1] << 24;
+    v |= pr->ptr[2] << 16;
+    v |= pr->ptr[3] << 8;
+    v |= pr->ptr[4];
+    pr->ptr += 4;
+
+    return v;
+}
+
+static int
+get8(stream_cursor_read *pr)
+{
+    return *++(pr->ptr);
+}
+
+static void
+jpg_jpeg_init_source(j_decompress_ptr cinfo)
+{
+    /* We've already inited the source */
+}
+
+static boolean
+jpg_fill_input_buffer(j_decompress_ptr cinfo)
+{
+    return FALSE; /* We've filled the buffer already */
+}
+
+static void
+jpg_skip_input_data(j_decompress_ptr cinfo, long num_bytes)
+{
+    jpg_interp_instance_t *jpg = JPG_FROM_CINFO(cinfo);
+    size_t n = cinfo->src->bytes_in_buffer;
+
+    if (n > num_bytes)
+        n = num_bytes;
+    jpg->jsrc.next_input_byte += num_bytes;
+    jpg->jsrc.bytes_in_buffer -= n;
+}
+
+static boolean
+jpg_resync_to_restart(j_decompress_ptr cinfo, int desired)
+{
+    return FALSE;
+}
+
+static void
+jpg_term_source(j_decompress_ptr cinfo)
+{
+}
+
+static void
+jpg_error_exit(j_common_ptr cinfo)
+{
+    jpg_error_mgr *jerr = (jpg_error_mgr *)cinfo->err;
+
+    longjmp(jerr->setjmp_buffer, 1);
+}
+
+static int
+fill_jpeg_source(jpg_interp_instance_t *jpg, stream_cursor_read * pr)
+{
+    size_t n = pr->limit - pr->ptr;
+    size_t skip = jpg->bytes_to_skip;
+
+    /* Skip any bytes we are supposed to be skipping. */
+    if (skip > 0) {
+        if (skip > n)
+            skip = n;
+        pr->ptr += skip;
+        n -= skip;
+        jpg->bytes_to_skip -= skip;
+        if (jpg->bytes_to_skip != 0)
+            return 1; /* Still more to skip */
+    }
+
+    /* Set up for the call into the jpeg lib */
+    jpg->jsrc.next_input_byte = pr->ptr+1;
+    jpg->jsrc.bytes_in_buffer = pr->limit - pr->ptr;
+    jpg->bytes_available_on_entry = jpg->jsrc.bytes_in_buffer;
+
+    return 0;
+}
+
+static int
+consume_jpeg_data(jpg_interp_instance_t *jpg, stream_cursor_read *pr)
+{
+    size_t bytes_read = jpg->jsrc.next_input_byte - (pr->ptr+1);
+    size_t n = pr->limit - pr->ptr;
+
+    if (n > bytes_read)
+        n = bytes_read;
+    pr->ptr += n;
+    bytes_read -= n;
+
+    /* We need to skip the next bytes_read bytes */
+    jpg->bytes_to_skip = bytes_read;
+
+    return (pr->limit - pr->ptr == jpg->bytes_available_on_entry);
+}
+
+static int
+jpg_impl_process(pl_interp_implementation_t * impl, stream_cursor_read * pr)
+{
+    jpg_interp_instance_t *jpg = (jpg_interp_instance_t *)impl->interp_client_data;
+    int code = 0;
+    int need_more_data;
+
+    do
+    {
+        need_more_data = 0;
+        switch(jpg->state)
+        {
+        case ii_state_identifying:
+        {
+            const byte *hdr;
+            /* Try and get us 11 bytes */
+            code = ensure_bytes(jpg, pr, 11);
+            if (code < 0)
+                return code;
+            hdr = pr->ptr+1;
+            if (hdr[0] == 0xFF && hdr[1] == 0xd8 && hdr[2] == 0xff && hdr[3] == 0xe0 &&
+                memcmp("JFIF", hdr+6, 5) == 0)
+            {
+                jpg->state = ii_state_jpeg;
+                break;
+            }
+            jpg->state = ii_state_flush;
+            break;
+        }
+        case ii_state_jpeg:
+            code = gs_jpeg_mem_init(jpg->memory, (j_common_ptr)&jpg->cinfo);
+            if (code < 0) {
+                jpg->state = ii_state_flush;
+                break;
+            }
+
+            jpg->cinfo.err = jpeg_std_error(&jpg->jerr.pub);
+            jpg->jerr.pub.error_exit = jpg_error_exit;
+            if (setjmp(jpg->jerr.setjmp_buffer)) {
+                jpg->state = ii_state_flush;
+                break;
+            }
+
+            jpeg_create_decompress(&jpg->cinfo);
+
+            jpg->cinfo.src = &jpg->jsrc;
+            jpg->jsrc.init_source = jpg_jpeg_init_source;
+            jpg->jsrc.fill_input_buffer = jpg_fill_input_buffer;
+            jpg->jsrc.skip_input_data = jpg_skip_input_data;
+            jpg->jsrc.resync_to_restart = jpg_resync_to_restart;
+            jpg->jsrc.term_source = jpg_term_source;
+
+            jpg->state = ii_state_jpeg_header;
+            break;
+        case ii_state_jpeg_header:
+        {
+            int ok;
+            gs_color_space *cs;
+            float scale, xext, yext, xoffset, yoffset;
+
+            if (fill_jpeg_source(jpg, pr)) {
+                need_more_data = 1;
+                break; /* No bytes left after skipping */
+            }
+            if (setjmp(jpg->jerr.setjmp_buffer)) {
+                jpeg_destroy_decompress(&jpg->cinfo);
+                jpg->state = ii_state_flush;
+                break;
+            }
+            ok = jpeg_read_header(&jpg->cinfo, TRUE);
+            need_more_data = consume_jpeg_data(jpg, pr);
+            if (ok == JPEG_SUSPENDED)
+                break;
+            need_more_data = 0;
+
+            jpg->width = jpg->cinfo.image_width;
+            jpg->height = jpg->cinfo.image_height;
+            jpg->num_comps = jpg->cinfo.num_components;
+            jpg->bpp = 8 * jpg->num_comps;
+            switch(jpg->num_comps) {
+            default:
+            case 1:
+                cs = jpg->gray;
+                jpg->cinfo.out_color_space = JCS_GRAYSCALE;
+                break;
+            case 3:
+                cs = jpg->rgb;
+                jpg->cinfo.out_color_space = JCS_RGB;
+                break;
+            case 4:
+                cs = jpg->cmyk;
+                jpg->cinfo.out_color_space = JCS_CMYK;
+                break;
+            }
+
+            /* Find us some X and Y resolutions */
+            jpg->xresolution = jpg->cinfo.X_density;
+            jpg->yresolution = jpg->cinfo.Y_density;
+            if (jpg->xresolution == 0)
+                jpg->xresolution = jpg->yresolution;
+            if (jpg->yresolution == 0)
+                jpg->yresolution = jpg->xresolution;
+            if (jpg->xresolution == 0)
+                jpg->xresolution = 72;
+            if (jpg->yresolution == 0)
+                jpg->yresolution = 72;
+
+            /* Scale to fit, if too large. */
+            scale = 1.0f;
+            if (jpg->width * jpg->dev->HWResolution[0] > jpg->dev->width * jpg->xresolution)
+                scale = ((float)jpg->dev->width * jpg->xresolution) / (jpg->width * jpg->dev->HWResolution[0]);
+            if (scale * jpg->height * jpg->dev->HWResolution[1] > jpg->dev->height * jpg->yresolution)
+                scale = ((float)jpg->dev->height * jpg->yresolution) / (jpg->height * jpg->dev->HWResolution[1]);
+
+            /* Centre - Extents and offsets are all calculated in points (1/72 of an inch) */
+            xext = ((float)jpg->width * 72 * scale / jpg->xresolution);
+            xoffset = (jpg->dev->width * 72 / jpg->dev->HWResolution[0] - xext)/2;
+            yext = ((float)jpg->height * 72 * scale / jpg->yresolution);
+            yoffset = (jpg->dev->height * 72 / jpg->dev->HWResolution[1] - yext)/2;
+
+            /* Now we've got the data from the image header, we can
+             * make the gs image instance */
+            jpg->byte_width = (jpg->bpp>>3)*jpg->width;
+
+            jpg->nulldev = gs_currentdevice(jpg->pgs);
+            rc_increment(jpg->nulldev);
+            code = gs_setdevice_no_erase(jpg->pgs, jpg->dev);
+            if (code < 0) {
+                jpg->state = ii_state_flush;
+                break;
+            }
+            gs_initmatrix(jpg->pgs);
+
+            /* By default the ctm is set to:
+             *   xres/72   0
+             *   0         -yres/72
+             *   0         dev->height * yres/72
+             * i.e. it moves the origin from being top right to being bottom left.
+             * We want to move it back, as without this, the image will be displayed
+             * upside down.
+             */
+            code = gs_translate(jpg->pgs, 0.0, jpg->dev->height * 72 / jpg->dev->HWResolution[1]);
+            if (code >= 0)
+                code = gs_translate(jpg->pgs, xoffset, -yoffset);
+            if (code >= 0)
+                code = gs_scale(jpg->pgs, scale, -scale);
+            /* At this point, the ctm is set to:
+             *   xres/72  0
+             *   0        yres/72
+             *   0        0
+             */
+            if (code >= 0)
+                code = gs_erasepage(jpg->pgs);
+            if (code < 0) {
+                jpg->state = ii_state_flush;
+                break;
+            }
+
+            jpg->samples = gs_alloc_bytes(jpg->memory, jpg->byte_width, "jpg_impl_process(samples)");
+            if (jpg->samples == NULL) {
+                jpg->state = ii_state_flush;
+                break;
+            }
+
+            memset(&jpg->image, 0, sizeof(jpg->image));
+            gs_image_t_init(&jpg->image, cs);
+            jpg->image.BitsPerComponent = jpg->bpp/jpg->num_comps;
+            jpg->image.Width = jpg->width;
+            jpg->image.Height = jpg->height;
+
+            jpg->image.ImageMatrix.xx = jpg->xresolution / 72.0f;
+            jpg->image.ImageMatrix.yy = jpg->yresolution / 72.0f;
+
+            jpg->penum = gs_image_enum_alloc(jpg->memory, "jpg_impl_process(penum)");
+            if (jpg->penum == NULL) {
+                code = gs_note_error(gs_error_VMerror);
+                jpg->state = ii_state_flush;
+                return code;
+            }
+
+            code = gs_image_init(jpg->penum,
+                                 &jpg->image,
+                                 false,
+                                 false,
+                                 jpg->pgs);
+            if (code < 0) {
+                jpg->state = ii_state_flush;
+                return code;
+            }
+
+            jpg->state = ii_state_jpeg_start;
+            break;
+        }
+        case ii_state_jpeg_start:
+        {
+            int ok;
+            if (fill_jpeg_source(jpg, pr)) {
+                need_more_data = 1;
+                break; /* No bytes left after skipping */
+            }
+            if (setjmp(jpg->jerr.setjmp_buffer)) {
+                jpeg_destroy_decompress(&jpg->cinfo);
+                jpg->state = ii_state_flush;
+                break;
+            }
+            ok = jpeg_start_decompress(&jpg->cinfo);
+            (void)consume_jpeg_data(jpg, pr);
+            if (ok == FALSE)
+                break;
+            jpg->state = ii_state_jpeg_rows;
+            break;
+        }
+        case ii_state_jpeg_rows:
+        {
+            int rows_decoded;
+            unsigned int used;
+
+            if (fill_jpeg_source(jpg, pr)) {
+                need_more_data = 1;
+                break; /* No bytes left after skipping */
+            }
+            if (setjmp(jpg->jerr.setjmp_buffer)) {
+                jpeg_destroy_decompress(&jpg->cinfo);
+                jpg->state = ii_state_flush;
+                break;
+            }
+            rows_decoded = jpeg_read_scanlines(&jpg->cinfo, &jpg->samples, 1);
+            need_more_data = consume_jpeg_data(jpg, pr);
+            if (rows_decoded == 0)
+                break; /* Not enough data for a scanline yet */
+            need_more_data = 0;
+
+            code = gs_image_next(jpg->penum, jpg->samples, jpg->byte_width, &used);
+            if (code < 0) {
+                jpg->state = ii_state_flush;
+                break;
+            }
+            jpg->y++;
+            if (jpg->y == jpg->height) {
+                code = gs_image_cleanup_and_free_enum(jpg->penum, jpg->pgs);
+                jpg->penum = NULL;
+                if (code < 0) {
+                    jpg->state = ii_state_flush;
+                    break;
+                }
+                code = pl_finish_page(jpg->memory->gs_lib_ctx->top_of_system,
+                                      jpg->pgs, 1, true);
+                if (code < 0) {
+                    jpg->state = ii_state_flush;
+                    break;
+                }
+                jpg->state = ii_state_jpeg_finish;
+            }
+            break;
+        }
+        case ii_state_jpeg_finish:
+        {
+            int ok;
+
+            if (fill_jpeg_source(jpg, pr)) {
+                need_more_data = 1;
+                break; /* No bytes left after skipping */
+            }
+            if (setjmp(jpg->jerr.setjmp_buffer)) {
+                jpeg_destroy_decompress(&jpg->cinfo);
+                jpg->state = ii_state_flush;
+                break;
+            }
+            ok = jpeg_finish_decompress(&jpg->cinfo);
+            need_more_data = consume_jpeg_data(jpg, pr);
+            if (ok == FALSE)
+                break;
+            need_more_data = 0;
+            jpg->state = ii_state_flush;
+            break;
+        }
+        default:
+        case ii_state_flush:
+            if (setjmp(jpg->jerr.setjmp_buffer))
+                break;
+            jpeg_destroy_decompress(&jpg->cinfo);
+
+            gs_jpeg_mem_term((j_common_ptr)&jpg->cinfo);
+
+            if (jpg->penum) {
+                (void)gs_image_cleanup_and_free_enum(jpg->penum, jpg->pgs);
+                jpg->penum = NULL;
+            }
+
+            gs_free_object(jpg->memory, jpg->samples, "jpg_impl_process(samples)");
+            jpg->samples = NULL;
+            /* We want to bin any data we get up to, but not including
+             * a UEL. */
+            return flush_to_uel(pr);
+        }
+    } while (!need_more_data);
+
+    return code;
+}
+
+static int
+jpg_impl_process_end(pl_interp_implementation_t * impl)
+{
+    jpg_interp_instance_t *jpg = (jpg_interp_instance_t *)impl->interp_client_data;
+    int code = 0;
+
+    /* FIXME: */
+    if (code == gs_error_InterpreterExit || code == gs_error_NeedInput)
+        code = 0;
+
+    return code;
+}
+
+/* Not implemented */
+static int
+jpg_impl_flush_to_eoj(pl_interp_implementation_t *impl, stream_cursor_read *cursor)
+{
+    const byte *p = cursor->ptr;
+    const byte *rlimit = cursor->limit;
+
+    /* Skip to, but leave UEL in buffer for PJL to find later */
+    for (; p < rlimit; ++p)
+        if (p[1] == '\033') {
+            uint avail = rlimit - p;
+
+            if (memcmp(p + 1, "\033%-12345X", min(avail, 9)))
+                continue;
+            if (avail < 9)
+                break;
+            cursor->ptr = p;
+            return 1;           /* found eoj */
+        }
+    cursor->ptr = p;
+    return 0;                   /* need more */
+}
+
+/* Parser action for end-of-file */
+static int
+jpg_impl_process_eof(pl_interp_implementation_t *impl)
+{
+    return 0;
+}
+
+/* Report any errors after running a job */
+static int
+jpg_impl_report_errors(pl_interp_implementation_t *impl,          /* interp instance to wrap up job in */
+                       int                         code,          /* prev termination status */
+                       long                        file_position, /* file position of error, -1 if unknown */
+                       bool                        force_to_cout  /* force errors to cout */
+)
+{
+    return 0;
+}
+
+/* Wrap up interp instance after a "job" */
+static int
+jpg_impl_dnit_job(pl_interp_implementation_t *impl)
+{
+    jpg_interp_instance_t *jpg = (jpg_interp_instance_t *)impl->interp_client_data;
+
+    if (jpg->nulldev) {
+        int code = gs_setdevice(jpg->pgs, jpg->nulldev);
+        jpg->dev = NULL;
+        rc_decrement(jpg->nulldev, "jpg_impl_dnit_job(nulldevice)");
+        jpg->nulldev = NULL;
+        return code;
+    }
+    return 0;
+}
+
+/* Parser implementation descriptor */
+const pl_interp_implementation_t jpg_implementation = {
+  jpg_impl_characteristics,
+  jpg_impl_allocate_interp_instance,
+  jpg_impl_get_device_memory,
+  NULL, /* jpg_impl_set_param */
+  NULL, /* jpg_impl_add_path */
+  NULL, /* jpg_impl_post_args_init */
+  jpg_impl_init_job,
+  NULL, /* jpg_impl_run_prefix_commands */
+  NULL, /* jpg_impl_process_file */
+  jpg_impl_process_begin,
+  jpg_impl_process,
+  jpg_impl_process_end,
+  jpg_impl_flush_to_eoj,
+  jpg_impl_process_eof,
+  jpg_impl_report_errors,
+  jpg_impl_dnit_job,
+  jpg_impl_deallocate_interp_instance,
+  NULL
+};
diff --git a/gpdl/psitop.c b/gpdl/psitop.c
new file mode 100644
index 0000000..f7ca110
--- /dev/null
+++ b/gpdl/psitop.c
@@ -0,0 +1,610 @@
+/* Copyright (C) 2001-2019 Artifex Software, Inc.
+   All Rights Reserved.
+
+   This software is provided AS-IS with no warranty, either express or
+   implied.
+
+   This software is distributed under license and may not be copied,
+   modified or distributed except as expressly authorized under the terms
+   of the license contained in the file LICENSE in this distribution.
+
+   Refer to licensing information at http://www.artifex.com or contact
+   Artifex Software, Inc.,  1305 Grant Avenue - Suite 200, Novato,
+   CA 94945, U.S.A., +1(415)492-9861, for further information.
+*/
+
+/* psitop.c */
+/* Top-level API implementation of PS Language Interface */
+
+#include "stdio_.h"
+#include "ghost.h"
+#include "imain.h"
+#include "imainarg.h"
+#include "iapi.h"
+#include "psapi.h"
+#include "string_.h"
+#include "gdebug.h"
+#include "gp.h"
+#include "gserrors.h"
+#include "gstypes.h"
+#include "gsmemory.h"
+#include "gsmalloc.h"
+#include "gsstate.h"		/* must precede gsdevice.h */
+#include "gxdevice.h"		/* must precede gsdevice.h */
+#include "gsdevice.h"
+#include "icstate.h"		/* for i_ctx_t */
+#include "iminst.h"
+#include "gsstruct.h"		/* for gxalloc.h */
+#include "gspaint.h"
+#include "gxalloc.h"
+#include "gxstate.h"
+#include "plparse.h"
+#include "pltop.h"
+#include "plmain.h"
+#include "gzstate.h"
+#include "gsicc_manage.h"
+
+/* Forward decls */
+
+/************************************************************/
+/******** Language wrapper implementation (see pltop.h) *****/
+/************************************************************/
+
+/* Import operator procedures */
+extern int zflush(i_ctx_t *);
+
+/*
+ * PS interpreter instance: derived from pl_interp_implementation_t
+ */
+typedef struct ps_interp_instance_s {
+    gs_memory_t  *memory;
+    uint          bytes_fed;
+    gs_lib_ctx_t *psapi_instance;
+} ps_interp_instance_t;
+
+static int
+check_token(int token_type, const char *s, const char *e, int *score)
+{
+    /* Empty tokens are always OK */
+    if (s == e)
+        return 0;
+
+    switch (token_type) {
+        case 'a':
+            /* Angle bracket - mainly to catch << */
+            break;
+        case 'n':
+            /* Name */
+            /* FIXME: Check it's a valid name */
+            break;
+        case 'd':
+            /* Dictionary */
+            break;
+        case 'i':
+            /* int - ok by construction. */
+            return 0;
+        case 'f':
+            /* float - ok by construction. */
+            return 0;
+    }
+
+#define TOKEN_CHECK(n) else if (e-s == strlen(n) && memcmp(s, n, e-s) == 0) { score[0] += e-s; score[1]++; }
+
+    if (0) {}
+    TOKEN_CHECK("dup")
+    TOKEN_CHECK("exch")
+    TOKEN_CHECK("grestore")
+    TOKEN_CHECK("gsave")
+    TOKEN_CHECK("idiv")
+    TOKEN_CHECK("lineto")
+    TOKEN_CHECK("mod")
+    TOKEN_CHECK("mul")
+    TOKEN_CHECK("moveto")
+    TOKEN_CHECK("setflat")
+    TOKEN_CHECK("setlinecap")
+    TOKEN_CHECK("setlinejoin")
+    TOKEN_CHECK("showpage")
+    TOKEN_CHECK("stroke")
+    TOKEN_CHECK("translate")
+    TOKEN_CHECK("systemdict")
+
+    if (score[0] > 1024 && score[2] >= 3)
+        return 1;
+    if (score[0] < -1024)
+        return 1;
+
+    return 0;
+}
+
+static int
+score_comment(const char *s, const char *e, int *score)
+{
+#define COMMENT_CHECK(n) else if (e-s >= strlen(n) && memcmp(s, n, strlen(n)) == 0) { score[0] += 100; score[1]++; }
+
+    if (0) {}
+    COMMENT_CHECK("!PS")
+    COMMENT_CHECK("%Title:")
+    COMMENT_CHECK("%Version:")
+    COMMENT_CHECK("%Creator:")
+    COMMENT_CHECK("%CreationDate:")
+    COMMENT_CHECK("%Document")
+    COMMENT_CHECK("%BoundingBox:")
+    COMMENT_CHECK("%HiResBoundingBox:")
+    COMMENT_CHECK("%Pages:")
+    COMMENT_CHECK("%+ procset")
+    COMMENT_CHECK("%End")
+    COMMENT_CHECK("%Begin")
+    COMMENT_CHECK("%Copyright")
+    else {
+        score[0]++; score[1]++;
+    }
+
+    if (score[0] > 1024 && score[1] >= 3)
+        return 1;
+
+    return 0;
+}
+
+static int
+ps_detect_language(const char *s, int len)
+{
+    /* For postscript, we look for %! */
+    if (len >= 2) {
+        if (s[0] != '%' || s[1] != '!') {
+            /* Not what we were looking for */
+        } else if (len >= 12 && memcmp(s+2, "Postscript", 10) == 0) {
+            return 100;
+        } else if (len >= 4 && memcmp(s+2, "PS", 2) == 0) {
+            return 100;
+        } else if (len >= 3 && s[2] == '/') {
+            /* Looks like a shell script. Don't want that. */
+            return 0;
+        } else {
+            /* If it begins %!, then it's PROBABLY postscript */
+            return 80;
+        }
+    }
+    /* For PDF, we allow for leading crap, then a postscript version marker */
+    {
+        const char *t = s;
+        int left = len-22;
+        /* Search within just the first 4K, plus a bit for the marker length. */
+        if (left > 4096+22)
+            left = 4096+22;
+        while (left > 22) {
+            if (memcmp(t, "%PDF-", 5) == 0 &&
+                t[5] >= '1' && t[5] <= '9' &&
+                t[6] == '.' &&
+                t[7] >= '0' && t[7] <= '9') {
+                return 100;
+            }
+            if (memcmp(t, "%!PS-Adobe-", 11) == 0 &&
+                t[11] >= '0' && t[11] <= '9' &&
+                t[12] == '.' &&
+                t[13] >= '0' && t[13] <= '9' &&
+                memcmp(t+14, " PDF-", 5) == 0 &&
+                t[19] >= '0' && t[19] <= '9' &&
+                t[20] == '.' &&
+                t[21] >= '0' && t[21] <= '9') {
+                return 100;
+            }
+            t++;
+            left--;
+        }
+    }
+
+    /* Now we do some seriously hairy stuff */
+    {
+        const char *t = s;
+        const char *token = t;
+        int left = len;
+        int token_type = 0;
+        int score[2] = { 0, 0 };
+
+        while (left--) {
+            if (*t == '%') {
+                if (check_token(token_type, token, t, score))
+                    break;
+                /* Skip to end of line */
+                left--;
+                token = ++t;
+                while (left && *t != '\r' && *t != '\n') {
+                    left--; t++;
+                }
+                if (score_comment(token, t, score))
+                    break;
+                /* Skip any combination of newlines */
+                while (left && (*t == '\r' || *t == '\n')) {
+                    left--; t++;
+                }
+                token_type = 0;
+                continue;
+            } else if (*t == 27) {
+                /* Give up if we meet an ESC. It could be a UEL. */
+                break;
+            } else if (*t <= 32 || *t > 127) {
+                if (check_token(token_type, token, t, score))
+                    break;
+                if (*t != 9 && *t != 10 && *t != 12 && *t != 13 && *t != 32)
+                    score[0]--;
+                token = t+1;
+                token_type = 0;
+            } else if (*t == '/') {
+                if (check_token(token_type, token, t, score))
+                    break;
+                token = t+1;
+                token_type = 'n';
+            } else if (*t == '[' || *t == ']' || *t == '{' || *t == '}') {
+                if (check_token(token_type, token, t, score))
+                    break;
+                token = t+1;
+                token_type = 0;
+            } else if (*t == '<') {
+                if (token_type == 'a') {
+                    /* << */
+                    token = t+1;
+                    token_type = 'd';
+                } else if (token_type == 'd') {
+                    /* <<< ???!? */
+                    score[0] -= 10;
+                } else {
+                    if (check_token(token_type, token, t, score))
+                        break;
+                    token = t+1;
+                    token_type = 'a';
+                }
+            } else if (*t == '>') {
+                if (check_token(token_type, token, t, score))
+                    break;
+                token = t+1;
+                token_type = 0;
+            } else if (*t >= '0' && *t <= '9') {
+                if (token_type == 'i') {
+                    /* Keep going */
+                } else if (token_type == 'f') {
+                    /* Keep going */
+                } else {
+                    if (check_token(token_type, token, t, score))
+                        break;
+                    token = t;
+                    token_type = 'i';
+                }
+            } else if (*t == '.') {
+                if (token_type == 'f') {
+                    /* seems unlikely */
+                    score[0]--;
+                    break;
+                } else if (token_type == 'i') {
+                    token = t;
+                    token_type = 'f';
+                } else {
+                    if (check_token(token_type, token, t, score))
+                        break;
+                    token = t;
+                    token_type = 'f';
+                }
+            } else {
+                /* Assume anything else goes into the token */
+            }
+            t++;
+        }
+        if (score[0] < 0 || score[1] < 3)
+            return 0; /* Unlikely to be PS */
+        else if (score[0] > 0)
+            return 75; /* Could be PS */
+    }
+
+    return 0;
+}
+
+/* Get implementation's characteristics */
+static const pl_interp_characteristics_t * /* always returns a descriptor */
+ps_impl_characteristics(const pl_interp_implementation_t *impl)     /* implementation of interpreter to alloc */
+{
+    /* version and build date are not currently used */
+#define PSVERSION NULL
+#define PSBUILDDATE NULL
+  static const pl_interp_characteristics_t ps_characteristics = {
+    "POSTSCRIPT",
+    ps_detect_language,
+    "Artifex",
+    PSVERSION,
+    PSBUILDDATE,
+    1				/* minimum input size to PostScript */
+  };
+#undef PSVERSION
+#undef PSBUILDDATE
+
+  return &ps_characteristics;
+}
+
+/* Do per-instance interpreter allocation/init. */
+static int
+ps_impl_allocate_interp_instance(pl_interp_implementation_t *impl, gs_memory_t *mem)
+{
+    ps_interp_instance_t *psi
+        = (ps_interp_instance_t *)gs_alloc_bytes( mem,
+                                                  sizeof(ps_interp_instance_t),
+                                                  "ps_impl_allocate_interp_instance");
+
+    int code;
+#define GS_MAX_NUM_ARGS 10
+    const char *gsargs[GS_MAX_NUM_ARGS] = {0};
+    int nargs = 0;
+
+    if (!psi)
+        return gs_error_VMerror;
+
+    gsargs[nargs++] = "gpdl";
+    /* We start gs with the nullpage device, and replace the device with the
+     * set_device call from the language independent code.
+     */
+    gsargs[nargs++] = "-dNODISPLAY";
+    /* As we're "printer targetted, use a jobserver */
+    gsargs[nargs++] = "-dJOBSERVER";
+
+    psi->memory = mem;
+    psi->bytes_fed = 0;
+    psi->psapi_instance = gs_lib_ctx_get_interp_instance(mem);
+    code = psapi_new_instance(&psi->psapi_instance, NULL);
+    if (code < 0)
+        gs_free_object(mem, psi, "ps_impl_allocate_interp_instance");
+
+    impl->interp_client_data = psi;
+
+    /* Tell gs not to ignore a UEL, but do an interpreter exit */
+    psapi_act_on_uel(psi->psapi_instance);
+
+    code = psapi_init_with_args01(psi->psapi_instance, nargs, (char **)gsargs);
+    if (code < 0) {
+        psapi_delete_instance(psi->psapi_instance);
+        gs_free_object(mem, psi, "ps_impl_allocate_interp_instance");
+    }
+    return code;
+}
+
+/*
+ * Get the allocator with which to allocate a device
+ */
+static gs_memory_t *
+ps_impl_get_device_memory(pl_interp_implementation_t *impl)
+{
+    ps_interp_instance_t *psi = (ps_interp_instance_t *)impl->interp_client_data;
+
+    return psapi_get_device_memory(psi->psapi_instance);
+}
+
+static int
+ps_impl_set_param(pl_interp_implementation_t *impl,
+                  pl_set_param_type           type,
+                  const char                 *param,
+                  const void                 *val)
+{
+    ps_interp_instance_t *psi = (ps_interp_instance_t *)impl->interp_client_data;
+
+    return psapi_set_param(psi->psapi_instance, type, param, val);
+}
+
+static int
+ps_impl_add_path(pl_interp_implementation_t *impl,
+                 const char                 *path)
+{
+    ps_interp_instance_t *psi = (ps_interp_instance_t *)impl->interp_client_data;
+
+    return psapi_add_path(psi->psapi_instance, path);
+}
+
+static int
+ps_impl_post_args_init(pl_interp_implementation_t *impl)
+{
+    ps_interp_instance_t *psi = (ps_interp_instance_t *)impl->interp_client_data;
+    const float *resolutions;
+    const long *page_sizes;
+
+    pl_main_get_forced_geometry(psi->memory, &resolutions, &page_sizes);
+    psapi_force_geometry(psi->psapi_instance, resolutions, page_sizes);
+
+    return psapi_init_with_args2(psi->psapi_instance);
+}
+
+/* Prepare interp instance for the next "job" */
+static int
+ps_impl_init_job(pl_interp_implementation_t *impl,
+                 gx_device                  *device)
+{
+    ps_interp_instance_t *psi = (ps_interp_instance_t *)impl->interp_client_data;
+    int exit_code;
+    int code = 0;
+
+    /* Any error after here *must* reset the device to null */
+    if (code >= 0)
+        code = psapi_set_device(psi->psapi_instance, device);
+
+    if (code >= 0)
+        code = psapi_run_string(psi->psapi_instance, "erasepage", 0, &exit_code);
+
+    if (code < 0) {
+        int code1 = psapi_set_device(psi->psapi_instance, NULL);
+        (void)code1;
+    }
+
+    return code;
+}
+
+static int
+ps_impl_run_prefix_commands(pl_interp_implementation_t *impl,
+                            const char                 *prefix)
+{
+    ps_interp_instance_t *psi = (ps_interp_instance_t *)impl->interp_client_data;
+    int exit_code;
+    int code = 0;
+
+    if (prefix == NULL)
+        return 0;
+
+    /* Any error after here *must* reset the device to null */
+    code = psapi_run_string(psi->psapi_instance, prefix, 0, &exit_code);
+
+    if (code < 0) {
+        int code1 = psapi_set_device(psi->psapi_instance, NULL);
+        (void)code1;
+    }
+
+    return code;
+}
+
+static int
+ps_impl_process_file(pl_interp_implementation_t *impl, const char *filename)
+{
+    ps_interp_instance_t *psi = (ps_interp_instance_t *)impl->interp_client_data;
+    int exit_code;
+
+    return psapi_run_file(psi->psapi_instance, filename, 0, &exit_code);
+}
+
+/* Do any setup for parser per-cursor */
+static int                      /* ret 0 or +ve if ok, else -ve error code */
+ps_impl_process_begin(pl_interp_implementation_t * impl)
+{
+    ps_interp_instance_t *psi = (ps_interp_instance_t *)impl->interp_client_data;
+    int exit_code;
+
+    psi->bytes_fed = 0;
+    return psapi_run_string_begin(psi->psapi_instance, 0, &exit_code);
+}
+
+/* TODO: in some fashion have gs pass back how far into the input buffer it
+ * had read, so we don't need to explicitly search the buffer for the UEL
+ */
+static int
+ps_impl_process(pl_interp_implementation_t * impl, stream_cursor_read * pr)
+{
+    ps_interp_instance_t *psi = (ps_interp_instance_t *)impl->interp_client_data;
+    const unsigned int len = pr->limit - pr->ptr;
+    int code, exit_code = 0;
+
+    code = psapi_run_string_continue(psi->psapi_instance, (const char *)pr->ptr + 1, len, 0, &exit_code);
+    if (exit_code == gs_error_InterpreterExit) {
+        int64_t offset;
+
+        offset = psapi_get_uel_offset(psi->psapi_instance) - psi->bytes_fed;
+        pr->ptr += offset;
+        psi->bytes_fed += offset + 1;
+
+#ifdef SEND_CTRLD_AFTER_UEL
+        {
+            const char eot[1] = {4};
+            code = psapi_run_string_continue(psi->psapi_instance, eot, 1, 0, &exit_code);
+            (void)code; /* Ignoring code here */
+        }
+#endif
+        return gs_error_InterpreterExit;
+    }
+    else {
+        pr->ptr = pr->limit;
+        psi->bytes_fed += len;
+    }
+    return code;
+}
+
+static int                      /* ret 0 or +ve if ok, else -ve error code */
+ps_impl_process_end(pl_interp_implementation_t * impl)
+{
+    ps_interp_instance_t *psi = (ps_interp_instance_t *)impl->interp_client_data;
+    int exit_code, code;
+
+    code = psapi_run_string_end(psi->psapi_instance, 0, &exit_code);
+
+    if (exit_code == gs_error_InterpreterExit || code == gs_error_NeedInput)
+        code = 0;
+
+    return code;
+}
+
+/* Not implemented */
+static int
+ps_impl_flush_to_eoj(pl_interp_implementation_t *impl, stream_cursor_read *cursor)
+{
+    const byte *p = cursor->ptr;
+    const byte *rlimit = cursor->limit;
+
+    /* Skip to, but leave UEL in buffer for PJL to find later */
+    for (; p < rlimit; ++p)
+        if (p[1] == '\033') {
+            uint avail = rlimit - p;
+
+            if (memcmp(p + 1, "\033%-12345X", min(avail, 9)))
+                continue;
+            if (avail < 9)
+                break;
+            cursor->ptr = p;
+            return 1;           /* found eoj */
+        }
+    cursor->ptr = p;
+    return 0;                   /* need more */
+}
+
+/* Parser action for end-of-file */
+static int
+ps_impl_process_eof(pl_interp_implementation_t *impl)
+{
+    return 0;
+}
+
+/* Report any errors after running a job */
+static int
+ps_impl_report_errors(pl_interp_implementation_t *impl,      /* interp instance to wrap up job in */
+                      int                  code,           /* prev termination status */
+                      long                 file_position,  /* file position of error, -1 if unknown */
+                      bool                 force_to_cout    /* force errors to cout */
+)
+{
+    return 0;
+}
+
+/* Wrap up interp instance after a "job" */
+static int
+ps_impl_dnit_job(pl_interp_implementation_t *impl)
+{
+    ps_interp_instance_t *psi = (ps_interp_instance_t *)impl->interp_client_data;
+
+    return psapi_set_device(psi->psapi_instance, NULL);
+}
+
+/* Deallocate a interpreter instance */
+static int
+ps_impl_deallocate_interp_instance(pl_interp_implementation_t *impl)
+{
+    ps_interp_instance_t *psi = (ps_interp_instance_t *)impl->interp_client_data;
+    int                   code;
+
+    if (psi == NULL)
+        return 0;
+    code = psapi_exit(psi->psapi_instance);
+    psapi_delete_instance(psi->psapi_instance);
+    gs_free_object(psi->memory, psi, "ps_impl_allocate_interp_instance");
+    impl->interp_client_data = NULL;
+    return code;
+}
+
+/* Parser implementation descriptor */
+const pl_interp_implementation_t ps_implementation = {
+  ps_impl_characteristics,
+  ps_impl_allocate_interp_instance,
+  ps_impl_get_device_memory,
+  ps_impl_set_param,
+  ps_impl_add_path,
+  ps_impl_post_args_init,
+  ps_impl_init_job,
+  ps_impl_run_prefix_commands,
+  ps_impl_process_file,
+  ps_impl_process_begin,
+  ps_impl_process,
+  ps_impl_process_end,
+  ps_impl_flush_to_eoj,
+  ps_impl_process_eof,
+  ps_impl_report_errors,
+  ps_impl_dnit_job,
+  ps_impl_deallocate_interp_instance,
+  NULL
+};
diff --git a/gpdl/pwgtop.c b/gpdl/pwgtop.c
new file mode 100644
index 0000000..1083508
--- /dev/null
+++ b/gpdl/pwgtop.c
@@ -0,0 +1,731 @@
+/* Copyright (C) 2019 Artifex Software, Inc.
+   All Rights Reserved.
+
+   This software is provided AS-IS with no warranty, either express or
+   implied.
+
+   This software is distributed under license and may not be copied,
+   modified or distributed except as expressly authorized under the terms
+   of the license contained in the file LICENSE in this distribution.
+
+   Refer to licensing information at http://www.artifex.com or contact
+   Artifex Software, Inc.,  1305 Grant Avenue - Suite 200, Novato,
+   CA 94945, U.S.A., +1(415)492-9861, for further information.
+*/
+
+/* pwgtop.c */
+/* Top-level API implementation of "PWG" Language Interface */
+
+#include "pltop.h"
+#include "gserrors.h"
+#include "gxdevice.h"
+#include "gsstate.h"
+#include "spwgx.h"
+#include "strimpl.h"
+#include "gscoord.h"
+
+/* Forward decls */
+
+/************************************************************/
+/******** Language wrapper implementation (see pltop.h) *****/
+/************************************************************/
+
+typedef enum
+{
+    ii_state_identifying = 0,
+    ii_state_pwg,
+    ii_state_pwg_header,
+    ii_state_pwg_data,
+    ii_state_flush
+} ii_state;
+
+/*
+ * PWG interpreter instance
+ */
+typedef struct pwg_interp_instance_s {
+    gs_memory_t       *memory;
+    gx_device         *dev;
+    gx_device         *nulldev;
+
+    gs_color_space    *gray;
+    gs_color_space    *rgb;
+    gs_color_space    *cmyk;
+
+    /* PWG parser state machine */
+    ii_state           state;
+
+    int                pages;
+
+    uint32_t           bpc;
+    uint32_t           bpp;
+    uint32_t           bpl;
+    uint32_t           colororder;
+    uint32_t           cs;
+    uint32_t           duplexMode;
+    uint32_t           printQuality;
+    uint32_t           mediaType;
+    uint32_t           copies;
+    uint32_t           width;
+    uint32_t           height;
+    uint32_t           xresolution;
+    uint32_t           yresolution;
+
+    uint32_t           num_comps;
+    uint32_t           byte_width;
+    uint32_t           x;
+    uint32_t           y;
+
+    gs_image_t         image;
+    gs_image_enum     *penum;
+    gs_gstate         *pgs;
+
+    stream_PWGD_state  pwgd_state;
+    byte               stream_buffer[2048];
+
+} pwg_interp_instance_t;
+
+static int
+pwg_detect_language(const char *s, int len)
+{
+    /* For postscript, we look for %! */
+    if (len >= 4) {
+        if (memcmp(s, "RaS2", 4) == 0)
+            return 100;
+    }
+
+    return 0;
+}
+
+static const pl_interp_characteristics_t pwg_characteristics = {
+    "PWG",
+    pwg_detect_language,
+    "Artifex",
+    "0.01",
+    "13 Nov 2019",
+    1 /* minimum input size */
+};
+
+/* Get implementation's characteristics */
+static const pl_interp_characteristics_t * /* always returns a descriptor */
+pwg_impl_characteristics(const pl_interp_implementation_t *impl)     /* implementation of interpreter to alloc */
+{
+  return &pwg_characteristics;
+}
+
+static void
+pwg_deallocate(pwg_interp_instance_t *pwg)
+{
+    if (pwg == NULL)
+        return;
+
+    rc_decrement_cs(pwg->gray, "pwg_deallocate");
+    rc_decrement_cs(pwg->rgb, "pwg_deallocate");
+    rc_decrement_cs(pwg->cmyk, "pwg_deallocate");
+
+    if (pwg->pgs != NULL)
+        gs_gstate_free_chain(pwg->pgs);
+    gs_free_object(pwg->memory, pwg, "pwg_impl_allocate_interp_instance");
+}
+
+/* Deallocate a interpreter instance */
+static int
+pwg_impl_deallocate_interp_instance(pl_interp_implementation_t *impl)
+{
+    pwg_interp_instance_t *pwg = (pwg_interp_instance_t *)impl->interp_client_data;
+
+    pwg_deallocate(pwg);
+    impl->interp_client_data = NULL;
+
+    return 0;
+}
+
+/* Do per-instance interpreter allocation/init. */
+static int
+pwg_impl_allocate_interp_instance(pl_interp_implementation_t *impl, gs_memory_t *mem)
+{
+    int code;
+    pwg_interp_instance_t *pwg
+        = (pwg_interp_instance_t *)gs_alloc_bytes(mem,
+                                                  sizeof(pwg_interp_instance_t),
+                                                  "pwg_impl_allocate_interp_instance");
+    if (!pwg)
+        return_error(gs_error_VMerror);
+    memset(pwg, 0, sizeof(*pwg));
+
+    pwg->memory = mem;
+    pwg->pgs = gs_gstate_alloc(mem);
+    if (pwg->pgs == NULL)
+        goto failVM;
+
+    /* Push one save level onto the stack to assuage the memory handling */
+    code = gs_gsave(pwg->pgs);
+    if (code < 0)
+        goto fail;
+
+    code = gsicc_init_iccmanager(pwg->pgs);
+    if (code < 0)
+        goto fail;
+
+    pwg->gray = gs_cspace_new_ICC(mem, pwg->pgs, 1);
+    pwg->rgb  = gs_cspace_new_ICC(mem, pwg->pgs, 3);
+    pwg->cmyk = gs_cspace_new_ICC(mem, pwg->pgs, 4);
+
+    impl->interp_client_data = pwg;
+
+    return 0;
+
+failVM:
+    code = gs_note_error(gs_error_VMerror);
+fail:
+    (void)pwg_deallocate(pwg);
+    return code;
+}
+
+/*
+ * Get the allocator with which to allocate a device
+ */
+static gs_memory_t *
+pwg_impl_get_device_memory(pl_interp_implementation_t *impl)
+{
+    pwg_interp_instance_t *pwg = (pwg_interp_instance_t *)impl->interp_client_data;
+
+    return pwg->dev ? pwg->dev->memory : NULL;
+}
+
+#if 0 /* UNUSED */
+static int
+pwg_impl_set_param(pl_interp_implementation_t *impl,
+                   pl_set_param_type           type,
+                   const char                 *param,
+                   const void                 *val)
+{
+    pwg_interp_instance_t *pwg = (pwg_interp_instance_t *)impl->interp_client_data;
+
+    /* No params set here */
+    return 0;
+}
+
+static int
+pwg_impl_add_path(pl_interp_implementation_t *impl,
+                  const char                 *path)
+{
+    pwg_interp_instance_t *pwg = (pwg_interp_instance_t *)impl->interp_client_data;
+
+    /* No paths to add */
+    return 0;
+}
+
+static int
+pwg_impl_post_args_init(pl_interp_implementation_t *impl)
+{
+    pwg_interp_instance_t *pwg = (pwg_interp_instance_t *)impl->interp_client_data;
+
+    /* No post args processing */
+    return 0;
+}
+#endif
+
+/* Prepare interp instance for the next "job" */
+static int
+pwg_impl_init_job(pl_interp_implementation_t *impl,
+                  gx_device                  *device)
+{
+    pwg_interp_instance_t *pwg = (pwg_interp_instance_t *)impl->interp_client_data;
+
+    pwg->dev = device;
+    pwg->state = ii_state_identifying;
+
+    return 0;
+}
+
+#if 0 /* UNUSED */
+static int
+pwg_impl_run_prefix_commands(pl_interp_implementation_t *impl,
+                             const char                 *prefix)
+{
+    pwg_interp_instance_t *pwg = (pwg_interp_instance_t *)impl->interp_client_data;
+
+    return 0;
+}
+
+static int
+pwg_impl_process_file(pl_interp_implementation_t *impl, const char *filename)
+{
+    pwg_interp_instance_t *pwg = (pwg_interp_instance_t *)impl->interp_client_data;
+
+    return 0;
+}
+#endif
+
+/* Do any setup for parser per-cursor */
+static int                      /* ret 0 or +ve if ok, else -ve error code */
+pwg_impl_process_begin(pl_interp_implementation_t * impl)
+{
+    pwg_interp_instance_t *pwg = (pwg_interp_instance_t *)impl->interp_client_data;
+
+    return 0;
+}
+
+/* Ensure we have 'required' bytes to read, and further ensure
+ * that we have no UEL's within those bytes. */
+static int
+ensure_bytes(pwg_interp_instance_t *pwg, stream_cursor_read *pr, int required)
+{
+    int n;
+    const uint8_t *p = pr->ptr+1;
+    const uint8_t *q;
+    int avail;
+
+    /* Find out how many bytes we need to check */
+    n = pr->limit - pr->ptr;
+    if (n > required)
+        n = required;
+
+    /* Make sure there are no UELs in that block */
+    q = p + n;
+    while (p != q) {
+        while (p != q && *p != '\033')
+            p++;
+        if (p == q)
+            break;
+        avail = pr->limit - pr->ptr;
+        if (memcmp(p, "\033%-12345X", min(avail, 9)) == 0) {
+            /* At least a partial match to a UEL */
+            return avail < 9 ? gs_error_NeedInput : gs_error_InterpreterExit;
+        }
+        p++;
+    }
+
+    /* If we have enough bytes, great, if not, get some more */
+    return (n < required) ? gs_error_NeedInput : 0;
+}
+
+static int
+flush_to_uel(stream_cursor_read *pr)
+{
+    const uint8_t *p = pr->ptr+1;
+    const uint8_t *q = pr->limit+1;
+    int avail;
+
+    while (p != q) {
+        while (p != q && *p != '\033')
+            p++;
+        if (p == q)
+            break;
+        avail = pr->limit - pr->ptr;
+        if (memcmp(p, "\033%-12345X", min(avail, 9)) == 0) {
+            /* At least a partial match to a UEL. Bin everything to
+             * the start of the match. */
+            pr->ptr = p-1;
+            if (avail == 9) /* Complete match. Exit! */
+                return gs_error_InterpreterExit;
+            /* Partial match. Get more data. */
+            return gs_error_NeedInput;
+        }
+        p++;
+    }
+
+    pr->ptr = pr->limit;
+
+    return 0;
+}
+
+static int
+bytes_until_uel(const stream_cursor_read *pr)
+{
+    const uint8_t *p = pr->ptr+1;
+    const uint8_t *q = pr->limit+1;
+    int avail;
+
+    while (p != q) {
+        while (p != q && *p != '\033')
+            p++;
+        if (p == q)
+            break;
+        avail = pr->limit - pr->ptr;
+        if (memcmp(p, "\033%-12345X", min(avail, 9)) == 0) {
+            /* At least a partial match to a UEL. Everything up to
+             * the start of the match is up for grabs. */
+            return p - (pr->ptr+1);
+        }
+        p++;
+    }
+
+    return pr->limit - pr->ptr;
+}
+
+static int
+get32be(stream_cursor_read *pr)
+{
+    int v = pr->ptr[1] << 24;
+    v |= pr->ptr[2] << 16;
+    v |= pr->ptr[3] << 8;
+    v |= pr->ptr[4];
+    pr->ptr += 4;
+
+    return v;
+}
+
+static int
+get8(stream_cursor_read *pr)
+{
+    return *++(pr->ptr);
+}
+
+static int
+pwg_impl_process(pl_interp_implementation_t * impl, stream_cursor_read * pr)
+{
+    pwg_interp_instance_t *pwg = (pwg_interp_instance_t *)impl->interp_client_data;
+    int code = 0;
+    gs_color_space *cs;
+
+    /* Loop while we have bytes to read */
+    while (pr->limit > pr->ptr)
+    {
+        switch(pwg->state)
+        {
+        case ii_state_identifying:
+            /* Try and get us 4 bytes for "RaS2" */
+            code = ensure_bytes(pwg, pr, 4);
+            if (code < 0)
+                return code;
+            if (memcmp("RaS2", (const char *)pr->ptr+1, 4) == 0)
+            {
+                pr->ptr += 4;
+                pwg->state = ii_state_pwg_header;
+                break;
+            }
+            pwg->state = ii_state_flush;
+            break;
+        case ii_state_pwg_header:
+            /* Try and get us 1796 bytes for the page header */
+            code = ensure_bytes(pwg, pr, 1796);
+            if (code < 0)
+                return code;
+	    pr->ptr          += 64;            /* CString PwgRaster */
+	    pr->ptr          += 64;            /* CString MediaColor */
+	    pr->ptr          += 64;            /* CString MediaType */
+	    pr->ptr          += 64;            /* CString PrintContentOptimize */
+	    pr->ptr          += 1-(256-267);   /* Reserved */
+            (void)get32be(pr);                 /* WhenEnum CutMedia */
+            pwg->duplexMode   = !!get32be(pr); /* Boolean Duplex */
+	    pwg->xresolution  = get32be(pr);
+	    pwg->yresolution  = get32be(pr);
+	    pr->ptr          += 1-(284-299);   /* Reserved */
+	    (void)!!get32be(pr);               /* Boolean InsertSheet */
+	    (void)get32be(pr);                 /* WhenEnum Jog */
+	    (void)get32be(pr);                 /* EdgeEnum LeadingEdge FeedDirection */
+	    pr->ptr          += 1-(312-323);   /* Reserved */
+	    (void)get32be(pr);                 /* MediaPosition */
+	    (void)get32be(pr);                 /* MediaWeightMetric */
+	    pr->ptr          += 1-(332-339);   /* Reserved */
+	    pwg->copies       = get32be(pr);   /* NumCopies */
+            if (pwg->copies == 0)
+                pwg->copies = 1;
+	    (void)get32be(pr);                 /* OrientationEnum Orientation OrientationRequested */
+	    pr->ptr          += 1-(348-351);   /* Reserved */
+	    (void)get32be(pr);                 /* UnsignedInt PageSize */
+	    (void)get32be(pr);                 /* UnsignedInt PageSize */
+	    pr->ptr          += 1-(360-367);   /* Reserved */
+	    (void)!!get32be(pr);               /* Boolean Tumble */
+            pwg->width        = get32be(pr);   /* Width */
+            pwg->height       = get32be(pr);   /* Height */
+	    pr->ptr          += 1-(380-383);   /* Reserved */
+            pwg->bpc          = get32be(pr);   /* Bits Per Color */
+            pwg->bpp          = get32be(pr);   /* Bits Per Pixel */
+            pwg->bpl          = get32be(pr);   /* Bytes Per Line */
+	    pwg->colororder   = get32be(pr);   /* ColorOrderEnum ColorOrder */
+	    pwg->cs           = get32be(pr);   /* ColorSpaceEnum ColorSpace */
+	    pr->ptr          += 1-(404-419);   /* Reserved */
+	    (void)get32be(pr);                 /* NumColors */
+	    pr->ptr          += 1-(424-451);   /* Reserved */
+	    (void)get32be(pr);                 /* TotalPageCount */
+	    (void)get32be(pr);                 /* CrossFeedTransform */
+	    (void)get32be(pr);                 /* FeedTransform */
+	    (void)get32be(pr);                 /* ImageBoxLeft */
+	    (void)get32be(pr);                 /* ImageBoxTop */
+	    (void)get32be(pr);                 /* ImageBoxRight */
+	    (void)get32be(pr);                 /* ImageBoxBottom */
+	    (void)get32be(pr);                 /* SrgbColor AlternatePrimary */
+            pwg->printQuality = get32be(pr);   /* PrintQuality */
+	    pr->ptr          += 1-(488-507);   /* Reserved */
+	    (void)get32be(pr);                 /* VendorIdentifier */
+	    (void)get32be(pr);                 /* VendorLength */
+	    pr->ptr          += 1-(516-1603);  /* VendorData */
+	    pr->ptr          += 1-(1604-1667); /* Reserved */
+	    pr->ptr          += 1-(1668-1731); /* RenderingIntent */
+	    pr->ptr          += 1-(1732-1795); /* PageSizeName */
+
+            /* Decode the header */
+	    if (pwg->colororder != 0)
+	      goto bad_header; /* 0 = chunky = only defined one so far */
+
+            switch(pwg->cs) {
+            case 3:  /* DeviceBlack */
+            case 18: /* Sgray */
+            case 48: /* Device1 (Device color, 1 colorant) */
+                pwg->num_comps = 1;
+                cs = pwg->gray;
+                break;
+            case 1:  /* DeviceRGB */
+            case 19: /* sRGB */
+            case 20: /* AdobeRGB */
+                pwg->num_comps = 3;
+                cs = pwg->rgb;
+                break;
+            case 6: /* DeviceCMYK */
+                pwg->num_comps = 4;
+                cs = pwg->cmyk;
+                break;
+            default:
+                goto bad_header;
+            }
+            if (pwg->bpc != 8)
+                goto bad_header;
+            if (pwg->bpp != 8*pwg->num_comps && pwg->bpp != 16*pwg->num_comps) {
+bad_header:
+                pwg->state = ii_state_flush;
+                break;
+            }
+            pwg->byte_width = (pwg->bpp>>3)*pwg->width;
+            if (pwg->bpl != pwg->byte_width)
+                goto bad_header;
+
+            pwg->nulldev = gs_currentdevice(pwg->pgs);
+            rc_increment(pwg->nulldev);
+            code = gs_setdevice_no_erase(pwg->pgs, pwg->dev);
+            if (code < 0)
+                goto early_flush;
+            gs_initmatrix(pwg->pgs);
+
+            /* By default the ctm is set to:
+             *   xres/72   0
+             *   0         -yres/72
+             *   0         dev->height * yres/72
+             * i.e. it moves the origin from being top right to being bottom left.
+             * We want to move it back, as without this, the image will be displayed
+             * upside down.
+             */
+            code = gs_translate(pwg->pgs, 0.0, -pwg->dev->height * 72 / pwg->dev->HWResolution[1]);
+            if (code >= 0)
+                code = gs_scale(pwg->pgs, 1, -1);
+            /* At this point, the ctm is set to:
+             *   xres/72  0
+             *   0        yres/72
+             *   0        0
+             */
+            if (code >= 0)
+                code = gs_erasepage(pwg->pgs);
+            if (code < 0)
+                goto early_flush;
+
+            memset(&pwg->image, 0, sizeof(pwg->image));
+            gs_image_t_init(&pwg->image, cs);
+            pwg->image.BitsPerComponent = pwg->bpp/pwg->num_comps;
+            pwg->image.Width = pwg->width;
+            pwg->image.Height = pwg->height;
+
+            pwg->image.ImageMatrix.xx = pwg->xresolution / 72.0f;
+            pwg->image.ImageMatrix.yy = pwg->yresolution / 72.0f;
+
+            pwg->penum = gs_image_enum_alloc(pwg->memory, "pwg_impl_process(penum)");
+            if (pwg->penum == NULL) {
+                code = gs_note_error(gs_error_VMerror);
+                goto early_flush;
+            }
+
+            code = gs_image_init(pwg->penum,
+                                 &pwg->image,
+                                 false,
+                                 false,
+                                 pwg->pgs);
+            if (code < 0)
+                goto early_flush;
+
+            s_init_state((stream_state *)&pwg->pwgd_state, &s_PWGD_template, pwg->memory);
+            if (s_PWGD_template.set_defaults)
+                s_PWGD_template.set_defaults((stream_state *)&pwg->pwgd_state);
+
+            pwg->pwgd_state.width = pwg->width;
+            pwg->pwgd_state.bpp = pwg->bpp;
+
+            code = (s_PWGD_template.init)((stream_state *)&pwg->pwgd_state);
+            if (code < 0)
+                goto early_flush;
+            pwg->x = pwg->y = 0;
+            pwg->state = ii_state_pwg_data;
+            break;
+        case ii_state_pwg_data:
+        {
+            int n = bytes_until_uel(pr);
+            stream_cursor_read local_r;
+            stream_cursor_write local_w;
+            int status;
+            unsigned int used;
+            int decoded_any = 0;
+
+            local_r.ptr = pr->ptr;
+            local_r.limit = local_r.ptr + n;
+
+            do {
+                local_w.ptr = pwg->stream_buffer-1;
+                local_w.limit = local_w.ptr + sizeof(pwg->stream_buffer);
+
+                status = (s_PWGD_template.process)((stream_state *)&pwg->pwgd_state,
+                                                                   &local_r, &local_w,
+                                                                   true);
+                /* status = 0 => need data
+                 *          1 => need output space */
+                /* Copy the updated pointer back */
+                pr->ptr = local_r.ptr;
+                if (local_w.ptr + 1 == pwg->stream_buffer)
+                    break; /* Failed to decode any data */
+                decoded_any = 1;
+
+                code = gs_image_next(pwg->penum, pwg->stream_buffer, local_w.ptr + 1 - pwg->stream_buffer, &used);
+                if (code < 0)
+                    goto flush;
+
+                pwg->x += used;
+                while (pwg->x >= pwg->byte_width) {
+                    pwg->x -= pwg->byte_width;
+                    pwg->y++;
+                }
+                /* Loop while we haven't had all the lines, the decompression
+                 * didn't ask for more data, and the decompression didn't give
+                 * us more data. */
+            } while (pwg->y < pwg->height);
+
+            if (pwg->y == pwg->height) {
+                code = gs_image_cleanup_and_free_enum(pwg->penum, pwg->pgs);
+                pwg->penum = NULL;
+                if (code < 0)
+                    goto flush;
+                code = pl_finish_page(pwg->memory->gs_lib_ctx->top_of_system,
+                                      pwg->pgs, pwg->copies, true);
+                if (code < 0)
+                    goto flush;
+                if (pwg->pages > 0) {
+                    pwg->pages--;
+                    /* If we've reached the expected end, we should probably flush to UEL */
+                    if (pwg->pages == 0)
+                        pwg->state = ii_state_flush;
+                }
+                if (pwg->pwgd_state.templat->release)
+                    pwg->pwgd_state.templat->release((stream_state *)&pwg->pwgd_state);
+                pwg->state = ii_state_pwg_header;
+            } else if (decoded_any == 0) {
+                /* Failed to make progress. Just give up to avoid livelocking. */
+                goto flush;
+            }
+            break;
+        }
+        default:
+        case ii_state_flush:
+            if (0) {
+flush:
+                if (pwg->pwgd_state.templat->release)
+                    pwg->pwgd_state.templat->release((stream_state *)&pwg->pwgd_state);
+early_flush:
+                pwg->state = ii_state_flush;
+            }
+            /* We want to bin any data we get up to, but not including
+             * a UEL. */
+            return flush_to_uel(pr);
+        }
+    }
+
+    return code;
+}
+
+static int
+pwg_impl_process_end(pl_interp_implementation_t * impl)
+{
+    pwg_interp_instance_t *pwg = (pwg_interp_instance_t *)impl->interp_client_data;
+    int code = 0;
+
+    /* FIXME: */
+    if (code == gs_error_InterpreterExit || code == gs_error_NeedInput)
+        code = 0;
+
+    return code;
+}
+
+/* Not implemented */
+static int
+pwg_impl_flush_to_eoj(pl_interp_implementation_t *impl, stream_cursor_read *cursor)
+{
+    const byte *p = cursor->ptr;
+    const byte *rlimit = cursor->limit;
+
+    /* Skip to, but leave UEL in buffer for PJL to find later */
+    for (; p < rlimit; ++p)
+        if (p[1] == '\033') {
+            uint avail = rlimit - p;
+
+            if (memcmp(p + 1, "\033%-12345X", min(avail, 9)))
+                continue;
+            if (avail < 9)
+                break;
+            cursor->ptr = p;
+            return 1;           /* found eoj */
+        }
+    cursor->ptr = p;
+    return 0;                   /* need more */
+}
+
+/* Parser action for end-of-file */
+static int
+pwg_impl_process_eof(pl_interp_implementation_t *impl)
+{
+    return 0;
+}
+
+/* Report any errors after running a job */
+static int
+pwg_impl_report_errors(pl_interp_implementation_t *impl,          /* interp instance to wrap up job in */
+                       int                         code,          /* prev termination status */
+                       long                        file_position, /* file position of error, -1 if unknown */
+                       bool                        force_to_cout  /* force errors to cout */
+)
+{
+    return 0;
+}
+
+/* Wrap up interp instance after a "job" */
+static int
+pwg_impl_dnit_job(pl_interp_implementation_t *impl)
+{
+    pwg_interp_instance_t *pwg = (pwg_interp_instance_t *)impl->interp_client_data;
+
+    if (pwg->nulldev) {
+        int code = gs_setdevice(pwg->pgs, pwg->nulldev);
+        pwg->dev = NULL;
+        rc_decrement(pwg->nulldev, "pwg_impl_dnit_job(nulldevice)");
+        pwg->nulldev = NULL;
+        return code;
+    }
+    return 0;
+}
+
+/* Parser implementation descriptor */
+const pl_interp_implementation_t pwg_implementation = {
+  pwg_impl_characteristics,
+  pwg_impl_allocate_interp_instance,
+  pwg_impl_get_device_memory,
+  NULL, /* pwg_impl_set_param */
+  NULL, /* pwg_impl_add_path */
+  NULL, /* pwg_impl_post_args_init */
+  pwg_impl_init_job,
+  NULL, /* pwg_impl_run_prefix_commands */
+  NULL, /* pwg_impl_process_file */
+  pwg_impl_process_begin,
+  pwg_impl_process,
+  pwg_impl_process_end,
+  pwg_impl_flush_to_eoj,
+  pwg_impl_process_eof,
+  pwg_impl_report_errors,
+  pwg_impl_dnit_job,
+  pwg_impl_deallocate_interp_instance,
+  NULL
+};
diff --git a/gpdl/tifftop.c b/gpdl/tifftop.c
new file mode 100644
index 0000000..2f55e1b
--- /dev/null
+++ b/gpdl/tifftop.c
@@ -0,0 +1,829 @@
+/* Copyright (C) 2019 Artifex Software, Inc.
+   All Rights Reserved.
+
+   This software is provided AS-IS with no warranty, either express or
+   implied.
+
+   This software is distributed under license and may not be copied,
+   modified or distributed except as expressly authorized under the terms
+   of the license contained in the file LICENSE in this distribution.
+
+   Refer to licensing information at http://www.artifex.com or contact
+   Artifex Software, Inc.,  1305 Grant Avenue - Suite 200, Novato,
+   CA 94945, U.S.A., +1(415)492-9861, for further information.
+*/
+
+/* tifftop.c */
+/* Top-level API implementation of "TIF" Language Interface */
+
+#include "pltop.h"
+#include "gserrors.h"
+#include "gxdevice.h"
+#include "gsstate.h"
+#include "strimpl.h"
+#include "gscoord.h"
+#include "tiffio.h"
+
+/* Forward decls */
+
+/************************************************************/
+/******** Language wrapper implementation (see pltop.h) *****/
+/************************************************************/
+
+typedef enum
+{
+    ii_state_identifying = 0,
+    ii_state_tiff,
+    ii_state_tiff_header,
+    ii_state_tiff_decode,
+    ii_state_flush
+} ii_state;
+
+/*
+ * Tiff interpreter instance
+ */
+typedef struct tiff_interp_instance_s {
+    gs_memory_t       *memory;
+    gs_memory_t       *cmemory;
+    gx_device         *dev;
+    gx_device         *nulldev;
+
+    gs_color_space    *gray;
+    gs_color_space    *rgb;
+    gs_color_space    *cmyk;
+
+    /* Tiff parser state machine */
+    ii_state           state;
+
+    int                pages;
+
+    uint32_t           bpp;
+    uint32_t           bpc;
+    uint32_t           cs;
+    uint32_t           width;
+    uint32_t           height;
+    uint32_t           xresolution;
+    uint32_t           yresolution;
+    uint32_t           tile_height;
+    uint32_t           tile_width;
+
+    uint32_t           num_comps;
+    uint32_t           byte_width;
+    uint32_t           y;
+
+    uint32_t           bytes_available_on_entry;
+
+    gs_image_t         image;
+    gs_image_enum     *penum;
+    gs_gstate         *pgs;
+
+    size_t             buffer_full;
+    size_t             buffer_max;
+    byte              *tiff_buffer;
+    size_t             file_pos;
+    TIFF              *handle;
+
+    byte              *samples;
+
+} tiff_interp_instance_t;
+
+static int
+tiff_detect_language(const char *s, int len)
+{
+    const byte *hdr = (const byte *)s;
+    if (len >= 4) {
+        if (hdr[0] == 'I' && hdr[1] == 'I' && hdr[2] == 42 && hdr[3] == 0)
+            return 100; /* Intel (LSB) order */
+        if (hdr[0] == 'M' && hdr[1] == 'M' && hdr[2] == 0 && hdr[3] == 42)
+            return 100; /* Motorola (MSB) order */
+    }
+
+    return 0;
+}
+
+static const pl_interp_characteristics_t tiff_characteristics = {
+    "TIFF",
+    tiff_detect_language,
+    "Artifex",
+    "0.01",
+    "18 Nov 2019",
+    1 /* minimum input size */
+};
+
+/* Get implementation's characteristics */
+static const pl_interp_characteristics_t * /* always returns a descriptor */
+tiff_impl_characteristics(const pl_interp_implementation_t *impl)     /* implementation of interpreter to alloc */
+{
+  return &tiff_characteristics;
+}
+
+static void
+tiff_deallocate(tiff_interp_instance_t *tiff)
+{
+    if (tiff == NULL)
+        return;
+
+    rc_decrement_cs(tiff->gray, "tiff_deallocate");
+    rc_decrement_cs(tiff->rgb, "tiff_deallocate");
+    rc_decrement_cs(tiff->cmyk, "tiff_deallocate");
+
+    if (tiff->pgs != NULL)
+        gs_gstate_free_chain(tiff->pgs);
+    gs_free_object(tiff->memory, tiff, "tiff_impl_allocate_interp_instance");
+}
+
+/* Deallocate a interpreter instance */
+static int
+tiff_impl_deallocate_interp_instance(pl_interp_implementation_t *impl)
+{
+    tiff_interp_instance_t *tiff = (tiff_interp_instance_t *)impl->interp_client_data;
+
+    tiff_deallocate(tiff);
+    impl->interp_client_data = NULL;
+
+    return 0;
+}
+
+/* Do per-instance interpreter allocation/init. */
+static int
+tiff_impl_allocate_interp_instance(pl_interp_implementation_t *impl, gs_memory_t *mem)
+{
+    int code;
+    tiff_interp_instance_t *tiff
+        = (tiff_interp_instance_t *)gs_alloc_bytes(mem,
+                                                  sizeof(tiff_interp_instance_t),
+                                                  "tiff_impl_allocate_interp_instance");
+    if (!tiff)
+        return_error(gs_error_VMerror);
+    memset(tiff, 0, sizeof(*tiff));
+
+    tiff->memory = mem;
+    tiff->pgs = gs_gstate_alloc(mem);
+    if (tiff->pgs == NULL)
+        goto failVM;
+
+    /* Push one save level onto the stack to assuage the memory handling */
+    code = gs_gsave(tiff->pgs);
+    if (code < 0)
+        goto fail;
+
+    code = gsicc_init_iccmanager(tiff->pgs);
+    if (code < 0)
+        goto fail;
+
+    tiff->gray = gs_cspace_new_ICC(mem, tiff->pgs, 1);
+    tiff->rgb  = gs_cspace_new_ICC(mem, tiff->pgs, 3);
+    tiff->cmyk = gs_cspace_new_ICC(mem, tiff->pgs, 4);
+
+    impl->interp_client_data = tiff;
+
+    return 0;
+
+failVM:
+    code = gs_note_error(gs_error_VMerror);
+fail:
+    (void)tiff_deallocate(tiff);
+    return code;
+}
+
+/*
+ * Get the allocator with which to allocate a device
+ */
+static gs_memory_t *
+tiff_impl_get_device_memory(pl_interp_implementation_t *impl)
+{
+    tiff_interp_instance_t *tiff = (tiff_interp_instance_t *)impl->interp_client_data;
+
+    return tiff->dev ? tiff->dev->memory : NULL;
+}
+
+#if 0 /* UNUSED */
+static int
+tiff_impl_set_param(pl_interp_implementation_t *impl,
+                   pl_set_param_type           type,
+                   const char                 *param,
+                   const void                 *val)
+{
+    tiff_interp_instance_t *tiff = (tiff_interp_instance_t *)impl->interp_client_data;
+
+    /* No params set here */
+    return 0;
+}
+
+static int
+tiff_impl_add_path(pl_interp_implementation_t *impl,
+                  const char                 *path)
+{
+    tiff_interp_instance_t *tiff = (tiff_interp_instance_t *)impl->interp_client_data;
+
+    /* No paths to add */
+    return 0;
+}
+
+static int
+tiff_impl_post_args_init(pl_interp_implementation_t *impl)
+{
+    tiff_interp_instance_t *tiff = (jpg_interp_instance_t *)impl->interp_client_data;
+
+    /* No post args processing */
+    return 0;
+}
+#endif
+
+/* Prepare interp instance for the next "job" */
+static int
+tiff_impl_init_job(pl_interp_implementation_t *impl,
+                  gx_device                  *device)
+{
+    tiff_interp_instance_t *tiff = (tiff_interp_instance_t *)impl->interp_client_data;
+
+    tiff->dev = device;
+    tiff->state = ii_state_identifying;
+
+    return 0;
+}
+
+#if 0 /* UNUSED */
+static int
+tiff_impl_run_prefix_commands(pl_interp_implementation_t *impl,
+                             const char                 *prefix)
+{
+    tiff_interp_instance_t *tiff = (tiff_interp_instance_t *)impl->interp_client_data;
+
+    return 0;
+}
+
+static int
+tiff_impl_process_file(pl_interp_implementation_t *impl, const char *filename)
+{
+    tiff_interp_instance_t *tiff = (tiff_interp_instance_t *)impl->interp_client_data;
+
+    return 0;
+}
+#endif
+
+/* Do any setup for parser per-cursor */
+static int                      /* ret 0 or +ve if ok, else -ve error code */
+tiff_impl_process_begin(pl_interp_implementation_t * impl)
+{
+    tiff_interp_instance_t *tiff = (tiff_interp_instance_t *)impl->interp_client_data;
+
+    return 0;
+}
+
+/* Ensure we have 'required' bytes to read, and further ensure
+ * that we have no UEL's within those bytes. */
+static int
+ensure_bytes(tiff_interp_instance_t *jpg, stream_cursor_read *pr, int required)
+{
+    int n;
+    const uint8_t *p = pr->ptr+1;
+    const uint8_t *q;
+    int avail;
+
+    /* Find out how many bytes we need to check */
+    n = pr->limit - pr->ptr;
+    if (n > required)
+        n = required;
+
+    /* Make sure there are no UELs in that block */
+    q = p + n;
+    while (p != q) {
+        while (p != q && *p != '\033')
+            p++;
+        if (p == q)
+            break;
+        avail = pr->limit - pr->ptr;
+        if (memcmp(p, "\033%-12345X", min(avail, 9)) == 0) {
+            /* At least a partial match to a UEL */
+            return avail < 9 ? gs_error_NeedInput : gs_error_InterpreterExit;
+        }
+        p++;
+    }
+
+    /* If we have enough bytes, great, if not, get some more */
+    return (n < required) ? gs_error_NeedInput : 0;
+}
+
+static int
+flush_to_uel(stream_cursor_read *pr)
+{
+    const uint8_t *p = pr->ptr+1;
+    const uint8_t *q = pr->limit+1;
+    int avail;
+
+    while (p != q) {
+        while (p != q && *p != '\033')
+            p++;
+        if (p == q)
+            break;
+        avail = pr->limit - pr->ptr;
+        if (memcmp(p, "\033%-12345X", min(avail, 9)) == 0) {
+            /* At least a partial match to a UEL. Bin everything to
+             * the start of the match. */
+            pr->ptr = p-1;
+            if (avail == 9) /* Complete match. Exit! */
+                return gs_error_InterpreterExit;
+            /* Partial match. Get more data. */
+            return gs_error_NeedInput;
+        }
+        p++;
+    }
+
+    pr->ptr = pr->limit;
+
+    return 0;
+}
+
+static int
+bytes_until_uel(const stream_cursor_read *pr)
+{
+    const uint8_t *p = pr->ptr+1;
+    const uint8_t *q = pr->limit+1;
+    int avail;
+
+    while (p != q) {
+        while (p != q && *p != '\033')
+            p++;
+        if (p == q)
+            break;
+        avail = pr->limit - pr->ptr;
+        if (memcmp(p, "\033%-12345X", min(avail, 9)) == 0) {
+            /* At least a partial match to a UEL. Everything up to
+             * the start of the match is up for grabs. */
+            return p - (pr->ptr+1);
+        }
+        p++;
+    }
+
+    return pr->limit - pr->ptr;
+}
+
+static int
+get32be(stream_cursor_read *pr)
+{
+    int v = pr->ptr[1] << 24;
+    v |= pr->ptr[2] << 16;
+    v |= pr->ptr[3] << 8;
+    v |= pr->ptr[4];
+    pr->ptr += 4;
+
+    return v;
+}
+
+static int
+get8(stream_cursor_read *pr)
+{
+    return *++(pr->ptr);
+}
+
+static tmsize_t tifsReadProc(thandle_t  tiff_,
+                             void      *buf,
+                             tmsize_t   size)
+{
+    tiff_interp_instance_t *tiff = (tiff_interp_instance_t *)tiff_;
+    tmsize_t available = tiff->buffer_full - tiff->file_pos;
+    if (available > size)
+        available = size;
+
+    memcpy(buf, &tiff->tiff_buffer[tiff->file_pos], available);
+    tiff->file_pos += available;
+
+    return available;
+}
+
+static tmsize_t tifsWriteProc(thandle_t  tiff_,
+                              void      *buf,
+                              tmsize_t   size)
+{
+    return 0;
+}
+
+static toff_t tifsSeekProc(thandle_t tiff_, toff_t offset, int whence)
+{
+    tiff_interp_instance_t *tiff = (tiff_interp_instance_t *)tiff_;
+    size_t pos = tiff->file_pos;
+
+    if (whence == 1) { /* SEEK_CURR */
+        offset += pos;
+    } else if (whence == 2) { /* SEEK_END */
+        offset += tiff->buffer_full;
+    }
+    /* else assume SEEK_SET */
+
+    /* Clamp */
+    if (offset < 0)
+        offset = 0;
+    else if (offset > tiff->buffer_full)
+        offset = tiff->buffer_full;
+
+    tiff->file_pos = offset;
+
+    return offset;
+}
+
+static tifsCloseProc(thandle_t tiff_)
+{
+    return 0;
+}
+
+static toff_t tifsSizeProc(thandle_t tiff_)
+{
+    tiff_interp_instance_t *tiff = (tiff_interp_instance_t *)tiff_;
+
+    return tiff->buffer_full;
+}
+
+static int
+do_impl_process(pl_interp_implementation_t * impl, stream_cursor_read * pr, int eof)
+{
+    tiff_interp_instance_t *tiff = (tiff_interp_instance_t *)impl->interp_client_data;
+    int code = 0;
+    ii_state ostate = (ii_state)-1;
+    int bytes_left = 0;
+
+    while (tiff->state != ostate || pr->limit - pr->ptr != bytes_left)
+    {
+        ostate = tiff->state;
+        bytes_left = pr->limit - pr->ptr;
+        switch(tiff->state)
+        {
+        case ii_state_identifying:
+        {
+            const byte *hdr;
+            /* Try and get us 4 bytes */
+            code = ensure_bytes(tiff, pr, 4);
+            if (code < 0)
+                return code;
+            hdr = pr->ptr+1;
+            if (hdr[0] == 'I' && hdr[1] == 'I' && hdr[2] == 42 && hdr[3] == 0) {
+                tiff->state = ii_state_tiff;
+                break;
+            }
+            if (hdr[0] == 'M' && hdr[1] == 'M' && hdr[2] == 0 && hdr[3] == 42) {
+                tiff->state = ii_state_tiff;
+                break;
+            }
+            tiff->state = ii_state_flush;
+            break;
+        }
+        case ii_state_tiff:
+        {
+            /* Gather data into a buffer */
+            int bytes = bytes_until_uel(pr);
+
+            if (bytes == 0 && pr->limit - pr->ptr > 9) {
+                /* No bytes until UEL, and there is space for a UEL in the buffer */
+                tiff->state = ii_state_tiff_decode;
+                tiff->file_pos = 0;
+                break;
+            }
+            if (bytes == 0 && eof) {
+                /* No bytes until UEL, and we are at eof */
+                tiff->state = ii_state_tiff_decode;
+                tiff->file_pos = 0;
+                break;
+            }
+
+            if (tiff->buffer_full + bytes > tiff->buffer_max) {
+                /* Need to expand our buffer */
+                size_t proposed = tiff->buffer_full*2;
+                if (proposed == 0)
+                    proposed = 32768;
+                while (proposed < tiff->buffer_full + bytes)
+                    proposed *= 2;
+
+                if (tiff->tiff_buffer == NULL) {
+                    tiff->tiff_buffer = gs_alloc_bytes(tiff->memory, proposed, "tiff_buffer");
+                    if (tiff->tiff_buffer == NULL) {
+                        tiff->state = ii_state_flush;
+                        break;
+                    }
+                } else {
+                    void *new_buf = gs_resize_object(tiff->memory, tiff->tiff_buffer, proposed, "tiff_buffer");
+                    if (new_buf == NULL) {
+                        tiff->state = ii_state_flush;
+                        break;
+                    }
+                    tiff->tiff_buffer = new_buf;
+                }
+                tiff->buffer_max = proposed;
+            }
+
+            memcpy(&tiff->tiff_buffer[tiff->buffer_full], pr->ptr+1, bytes);
+            tiff->buffer_full += bytes;
+            pr->ptr += bytes;
+            break;
+        }
+        case ii_state_tiff_decode:
+        {
+            int tx, ty;
+            short planar;
+            float f, scale;
+            gs_color_space *cs;
+            unsigned int used;
+
+            tiff->handle = TIFFClientOpen("dummy", "rm",
+                                          (thandle_t)tiff,
+                                          tifsReadProc,
+                                          tifsWriteProc,
+                                          tifsSeekProc,
+                                          tifsCloseProc,
+                                          tifsSizeProc,
+                                          NULL,
+                                          NULL);
+            if (tiff->handle == NULL) {
+                tiff->state = ii_state_flush;
+                break;
+            }
+
+            TIFFGetField(tiff->handle, TIFFTAG_IMAGEWIDTH, &tiff->width);
+            TIFFGetField(tiff->handle, TIFFTAG_IMAGELENGTH, &tiff->height);
+            TIFFGetField(tiff->handle, TIFFTAG_TILEWIDTH, &tiff->tile_width);
+            TIFFGetField(tiff->handle, TIFFTAG_TILELENGTH, &tiff->tile_height);
+            TIFFGetField(tiff->handle, TIFFTAG_BITSPERSAMPLE, &tiff->bpc);
+            TIFFGetField(tiff->handle, TIFFTAG_SAMPLESPERPIXEL, &tiff->num_comps);
+            TIFFGetField(tiff->handle, TIFFTAG_PLANARCONFIG, &planar);
+            TIFFGetField(tiff->handle, TIFFTAG_XRESOLUTION, &f);
+            tiff->xresolution = (uint32_t)(f+0.5);
+            TIFFGetField(tiff->handle, TIFFTAG_YRESOLUTION, &f);
+            tiff->yresolution = (uint32_t)(f+0.5);
+
+            if (TIFFIsTiled(tiff->handle)) {
+                int x, y;
+                tiff->samples = gs_alloc_bytes(tiff->memory, TIFFTileSize(tiff->handle), "tiff_tile");
+            } else {
+                tiff->tile_width = tiff->width;
+                tiff->tile_height = tiff->height;
+                tiff->samples = gs_alloc_bytes(tiff->memory, TIFFScanlineSize(tiff->handle), "tiff_scan");
+            }
+            if (tiff->samples == NULL) {
+                TIFFClose(tiff->handle);
+                tiff->state = ii_state_flush;
+                break;
+            }
+
+            /* FIXME: Need to allow for LAB, YUV etc too */
+            tiff->bpp = tiff->bpc * tiff->num_comps;
+            switch(tiff->num_comps) {
+            default:
+            case 1:
+                cs = tiff->gray;
+                break;
+            case 3:
+                cs = tiff->rgb;
+                break;
+            case 4:
+                cs = tiff->cmyk;
+                break;
+            }
+
+            /* Scale to fit, if too large. */
+            scale = 1.0f;
+            if (tiff->width * tiff->dev->HWResolution[0] > tiff->dev->width * tiff->xresolution)
+                scale = ((float)tiff->dev->width * tiff->xresolution) / (tiff->width * tiff->dev->HWResolution[0]);
+            if (scale * tiff->height * tiff->dev->HWResolution[1] > tiff->dev->height * tiff->yresolution)
+                scale = ((float)tiff->dev->height * tiff->yresolution) / (tiff->height * tiff->dev->HWResolution[1]);
+
+            tiff->nulldev = gs_currentdevice(tiff->pgs);
+            rc_increment(tiff->nulldev);
+            code = gs_setdevice_no_erase(tiff->pgs, tiff->dev);
+            if (code < 0) {
+                tiff->state = ii_state_flush;
+                break;
+            }
+
+            if (TIFFIsTiled(tiff->handle) || planar == PLANARCONFIG_CONTIG) {
+                tiff->byte_width = ((tiff->bpc * tiff->num_comps * tiff->tile_width + 7)>>3);
+            } else {
+                tiff->byte_width = ((tiff->bpc * tiff->tile_width + 7)>>3) * tiff->num_comps;
+            }
+            code = gs_erasepage(tiff->pgs);
+            if (code < 0) {
+                tiff->state = ii_state_flush;
+                return code;
+            }
+            for (ty = 0; ty < tiff->height; ty += tiff->tile_height)
+                for (tx = 0; tx < tiff->width; tx += tiff->tile_width) {
+                    int y, s;
+                    byte *row;
+                    float xext, xoffset, yext, yoffset;
+
+                    tiff->penum = gs_image_enum_alloc(tiff->memory, "tiff_impl_process(penum)");
+                    if (tiff->penum == NULL) {
+                        code = gs_note_error(gs_error_VMerror);
+                        tiff->state = ii_state_flush;
+                        return code;
+                    }
+
+                    /* Centre - Extents and offsets are all calculated in points (1/72 of an inch) */
+                    xext = (((float)tiff->width - tx * 2) * 72 * scale / tiff->xresolution);
+                    xoffset = (tiff->dev->width * 72 / tiff->dev->HWResolution[0] - xext)/2;
+                    yext = (((float)tiff->height - ty * 2) * 72 * scale / tiff->yresolution);
+                    yoffset = (tiff->dev->height * 72 / tiff->dev->HWResolution[1] - yext)/2;
+
+                    gs_initmatrix(tiff->pgs);
+
+                    /* By default the ctm is set to:
+                     *   xres/72   0
+                     *   0         -yres/72
+                     *   0         dev->height * yres/72
+                     * i.e. it moves the origin from being top right to being bottom left.
+                     * We want to move it back, as without this, the image will be displayed
+                     * upside down.
+                     */
+                    code = gs_translate(tiff->pgs, 0.0, tiff->dev->height * 72 / tiff->dev->HWResolution[1]);
+                    if (code >= 0)
+                        code = gs_translate(tiff->pgs, xoffset, -yoffset);
+                    if (code >= 0)
+                        code = gs_scale(tiff->pgs, scale, -scale);
+                    if (code < 0) {
+                        tiff->state = ii_state_flush;
+                        break;
+                    }
+
+                    memset(&tiff->image, 0, sizeof(tiff->image));
+                    gs_image_t_init(&tiff->image, cs);
+                    tiff->image.BitsPerComponent = tiff->bpp/tiff->num_comps;
+                    tiff->image.Width = tiff->tile_width;
+                    tiff->image.Height = tiff->tile_height;
+
+                    tiff->image.ImageMatrix.xx = tiff->xresolution / 72.0f;
+                    tiff->image.ImageMatrix.yy = tiff->yresolution / 72.0f;
+
+                    code = gs_image_init(tiff->penum,
+                                         &tiff->image,
+                                         false,
+                                         false,
+                                         tiff->pgs);
+                    if (code < 0) {
+                        tiff->state = ii_state_flush;
+                        return code;
+                    }
+
+                    if (TIFFIsTiled(tiff->handle)) {
+                        TIFFReadTile(tiff->handle, tiff->samples, tx, ty, 0, 0);
+                    }
+
+                    for (y = 0; y < tiff->tile_height; y++) {
+                        if (TIFFIsTiled(tiff->handle)) {
+                            row = tiff->samples + tiff->byte_width * y;
+                        } else if (planar == PLANARCONFIG_CONTIG) {
+                            row = tiff->samples;
+                            TIFFReadScanline(tiff->handle, tiff->samples, ty+y, 0);
+                        } else {
+                            int span = tiff->byte_width / tiff->num_comps; /* FIXME: Avoid division */
+                            row = tiff->samples;
+                            for (s = 0; s < tiff->num_comps; s++)
+                                TIFFReadScanline(tiff->handle, tiff->samples + span*s, ty+y, s);
+                        }
+
+                        code = gs_image_next(tiff->penum, row, tiff->byte_width, &used);
+                        if (code < 0) {
+                            tiff->state = ii_state_flush;
+                            break;
+                        }
+                    }
+                    code = gs_image_cleanup_and_free_enum(tiff->penum, tiff->pgs);
+                    tiff->penum = NULL;
+                    if (code < 0) {
+                        tiff->state = ii_state_flush;
+                        break;
+                    }
+            }
+            tiff->state = ii_state_flush;
+            (void)pl_finish_page(tiff->memory->gs_lib_ctx->top_of_system,
+                                 tiff->pgs, 1, true);
+            break;
+        }
+        default:
+        case ii_state_flush:
+
+            if (tiff->handle) {
+                TIFFClose(tiff->handle);
+                tiff->handle = NULL;
+            }
+
+            if (tiff->penum) {
+                (void)gs_image_cleanup_and_free_enum(tiff->penum, tiff->pgs);
+                tiff->penum = NULL;
+            }
+
+            if (tiff->samples) {
+                gs_free_object(tiff->memory, tiff->samples, "tiff_impl_process(samples)");
+                tiff->samples = NULL;
+            }
+
+            if (tiff->tiff_buffer) {
+                gs_free_object(tiff->memory, tiff->tiff_buffer, "tiff_impl_process(tiff_buffer)");
+                tiff->tiff_buffer = NULL;
+            }
+            /* We want to bin any data we get up to, but not including
+             * a UEL. */
+            return flush_to_uel(pr);
+        }
+    }
+
+    return code;
+}
+
+static int
+tiff_impl_process(pl_interp_implementation_t * impl, stream_cursor_read * pr) {
+    return do_impl_process(impl, pr, 0);
+}
+
+static int
+tiff_impl_process_end(pl_interp_implementation_t * impl)
+{
+    tiff_interp_instance_t *tiff = (tiff_interp_instance_t *)impl->interp_client_data;
+    int code = 0;
+
+    /* FIXME: */
+    if (code == gs_error_InterpreterExit || code == gs_error_NeedInput)
+        code = 0;
+
+    return code;
+}
+
+/* Not implemented */
+static int
+tiff_impl_flush_to_eoj(pl_interp_implementation_t *impl, stream_cursor_read *cursor)
+{
+    const byte *p = cursor->ptr;
+    const byte *rlimit = cursor->limit;
+
+    /* Skip to, but leave UEL in buffer for PJL to find later */
+    for (; p < rlimit; ++p)
+        if (p[1] == '\033') {
+            uint avail = rlimit - p;
+
+            if (memcmp(p + 1, "\033%-12345X", min(avail, 9)))
+                continue;
+            if (avail < 9)
+                break;
+            cursor->ptr = p;
+            return 1;           /* found eoj */
+        }
+    cursor->ptr = p;
+    return 0;                   /* need more */
+}
+
+/* Parser action for end-of-file */
+static int
+tiff_impl_process_eof(pl_interp_implementation_t *impl)
+{
+    stream_cursor_read r;
+
+    r.ptr = NULL;
+    r.limit = NULL;
+    return do_impl_process(impl, &r, 1);
+}
+
+/* Report any errors after running a job */
+static int
+tiff_impl_report_errors(pl_interp_implementation_t *impl,          /* interp instance to wrap up job in */
+                        int                         code,          /* prev termination status */
+                        long                        file_position, /* file position of error, -1 if unknown */
+                        bool                        force_to_cout  /* force errors to cout */
+)
+{
+    return 0;
+}
+
+/* Wrap up interp instance after a "job" */
+static int
+tiff_impl_dnit_job(pl_interp_implementation_t *impl)
+{
+    tiff_interp_instance_t *tiff = (tiff_interp_instance_t *)impl->interp_client_data;
+
+    if (tiff->nulldev) {
+        int code = gs_setdevice(tiff->pgs, tiff->nulldev);
+        tiff->dev = NULL;
+        rc_decrement(tiff->nulldev, "tiff_impl_dnit_job(nulldevice)");
+        tiff->nulldev = NULL;
+        return code;
+    }
+    return 0;
+}
+
+/* Parser implementation descriptor */
+const pl_interp_implementation_t tiff_implementation = {
+  tiff_impl_characteristics,
+  tiff_impl_allocate_interp_instance,
+  tiff_impl_get_device_memory,
+  NULL, /* tiff_impl_set_param */
+  NULL, /* tiff_impl_add_path */
+  NULL, /* tiff_impl_post_args_init */
+  tiff_impl_init_job,
+  NULL, /* tiff_impl_run_prefix_commands */
+  NULL, /* tiff_impl_process_file */
+  tiff_impl_process_begin,
+  tiff_impl_process,
+  tiff_impl_process_end,
+  tiff_impl_flush_to_eoj,
+  tiff_impl_process_eof,
+  tiff_impl_report_errors,
+  tiff_impl_dnit_job,
+  tiff_impl_deallocate_interp_instance,
+  NULL
+};

----------------------------------------------------------------------
commit c09fbcd400e41d621659fd9323031b157f6937a8
Merge: 70d25d8 c5eb242
Author: Ken Sharp <[email protected]>
Date:   Wed Nov 20 09:05:35 2019 +0000

    Merge branch 'master' into pdfi

diff --cc pcl/pcl/pcfrgrnd.c
index 8463f33,31968a3..01a8f14
--- a/pcl/pcl/pcfrgrnd.c
+++ b/pcl/pcl/pcfrgrnd.c
@@@ -82,7 -82,11 +82,11 @@@ build_foreground(pcl_state_t * pcs
      int num_entries;
      bool is_default = false;
      int code = 0;
 -    
 +
+     if (!pindexed) {
+         return_error(gs_error_invalidaccess);
+     }
+ 
      /*
       * Check for a request for the default foreground. Since there are only
       * three fixed palettes, it is sufficient to check that the palette provided
diff --cc pcl/pxl/pxpthr.c
index 1b52a57,a0a1527..f103fc3
--- a/pcl/pxl/pxpthr.c
+++ b/pcl/pxl/pxpthr.c
@@@ -85,10 -85,15 +85,15 @@@ pxPassthrough_pcl_state_nonpage_excepti
  {
      /* xl cursor -> pcl cursor position */
      gs_point xlcp, pclcp, dp;
+     int code;
 -    
 +
      /* make the pcl ctm active, after resets the hpgl/2 ctm is
         active. */
-     pcl_set_graphics_state(global_pcs);
+     code = pcl_set_graphics_state(global_pcs);
+     if (code < 0) {
+         return code;
+     }
+ 
      /* xl current point -> device point -> pcl current
         point.  If anything fails we assume the current
         point is not valid and use the cap from the pcl


Summary of changes:
 Makefile.in                         |   5 +-
 base/gdevdflt.c                     |   4 +-
 base/gdevdrop.c                     |   9 +-
 base/gdevp14.c                      |  12 +-
 base/gp.h                           |   2 +-
 base/gp_dosfs.c                     |   2 +-
 base/gp_ntfs.c                      |   2 +-
 base/gp_os2fs.c                     |   2 +-
 base/gp_unifn.c                     |   4 +-
 base/gp_unifs.c                     |   4 +
 base/gp_vms.c                       |   2 +-
 base/gsicc.c                        |   3 +
 base/gxclimag.c                     |   4 +-
 base/gxpcmap.c                      |  10 +-
 base/gxpcolor.h                     |   2 +-
 configure.ac                        |   8 +-
 contrib/gdevlx32.c                  |  13 +
 gpdl/gpdl.mak                       |  50 ++-
 gpdl/{image/imagetop.c => jpgtop.c} | 494 ++++++++++-----------
 gpdl/{psi => }/psitop.c             |   0
 gpdl/{pwg => }/pwgtop.c             |  21 +-
 gpdl/tifftop.c                      | 829 ++++++++++++++++++++++++++++++++++++
 jbig2dec/jbig2.c                    |   5 +-
 pcl/pcl/pcfrgrnd.c                  |   7 +-
 pcl/pcl/pctext.c                    |  78 ++--
 pcl/pcl/pgdraw.c                    |   2 +-
 pcl/pl/plchar.c                     |   4 +-
 pcl/pl/plimpl.c                     |  11 +-
 pcl/pl/plmain.c                     |   1 -
 pcl/pxl/pxpthr.c                    |  32 +-
 pcl/pxl/pxpthr.h                    |   2 +-
 psi/msvc.mak                        |   1 +
 psi/zpcolor.c                       |   2 +-
 windows/ghostpdl.vcproj             |  16 +-
 34 files changed, 1284 insertions(+), 359 deletions(-)
 copy gpdl/{image/imagetop.c => jpgtop.c} (51%)
 rename gpdl/{psi => }/psitop.c (100%)
 copy gpdl/{pwg => }/pwgtop.c (96%)
 create mode 100644 gpdl/tifftop.c