[gs-commits] ghostpdl branch, master, updated. jbig2dec-0.14-1966-g8a589c1

[email protected] (Robin Watts) Mon, 25 Nov 2019 16:16:54 +0000 (UTC)
Newsgroups gmane.comp.printing.ghostscript.cvs
Message-ID <[email protected]>
The ghostpdl branch, master has been updated
       via  8a589c19d62884388cb9eff7d12bfba568e667ca (commit)
       via  79e11b98dc078fcd92cda588ab6157a4aa14d0f0 (commit)
       via  113792dd347ac7b9436b9517aca91e1153fb230d (commit)
       via  12eef95ef9ed7a086ca02f06ef93acce9c9a2676 (commit)
       via  ab089bf9511ccaa30a33d6e269ab0dae98ff5517 (commit)
      from  e056194328312f724089d52398e88c5a6a2fe277 (commit)

----------------------------------------------------------------------
commit 8a589c19d62884388cb9eff7d12bfba568e667ca
Author: Robin Watts <[email protected]>
Date:   Mon Nov 25 16:12:17 2019 +0000

    Fix incorrect transform in gpdl/pwgtop.c

diff --git a/gpdl/pwgtop.c b/gpdl/pwgtop.c
index 5606790..1332dfc 100644
--- a/gpdl/pwgtop.c
+++ b/gpdl/pwgtop.c
@@ -502,7 +502,7 @@ bad_header:
              * 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]);
+            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:

----------------------------------------------------------------------
commit 79e11b98dc078fcd92cda588ab6157a4aa14d0f0
Author: Robin Watts <[email protected]>
Date:   Fri Nov 22 19:43:46 2019 +0000

    Add Jpeg2000 "language" interpreter for gpdl.

diff --git a/gpdl/gpdl.mak b/gpdl/gpdl.mak
index 9b1f33d..d99b172 100644
--- a/gpdl/gpdl.mak
+++ b/gpdl/gpdl.mak
@@ -41,7 +41,11 @@ GPDL_TIFF_TOP_OBJ=$(GPDLOBJ)/$(GPDL_TIFF_TOP_OBJ_FILE)
 GPDL_JBIG2_TOP_OBJ_FILE=jbig2top.$(OBJ)
 GPDL_JBIG2_TOP_OBJ=$(GPDLOBJ)/$(GPDL_JBIG2_TOP_OBJ_FILE)
 
+GPDL_JP2K_TOP_OBJ_FILE=jp2ktop.$(OBJ)
+GPDL_JP2K_TOP_OBJ=$(GPDLOBJ)/$(GPDL_JP2K_TOP_OBJ_FILE)
+
 GPDL_PSI_TOP_OBJS=\
+	$(GPDL_JP2K_TOP_OBJ)\
 	$(GPDL_JBIG2_TOP_OBJ)\
 	$(GPDL_TIFF_TOP_OBJ)\
 	$(GPDL_PWG_TOP_OBJ)\
@@ -58,7 +62,8 @@ LANG_CFLAGS=\
 	$(D_)JPG_INCLUDED$(_D)\
 	$(D_)PWG_INCLUDED$(_D)\
 	$(ENABLE_TIFF)\
-	$(D_)JBIG2_INCLUDED$(_D)
+	$(D_)JBIG2_INCLUDED$(_D)\
+	$(D_)JP2K_INCLUDED$(_D)
 
 GPDLCC=$(CC_) $(LANG_CFLAGS) $(I_)$(PSSRCDIR)$(_I) $(I_)$(PLSRCDIR)$(_I) $(I_)$(GLSRCDIR)$(_I) $(I_)$(DEVSRCDIR)$(_I) $(I_)$(GLGENDIR)$(_I) $(C_)
 
@@ -109,3 +114,8 @@ $(GPDL_JBIG2_TOP_OBJ): $(GPDLSRC)jbig2top.c $(AK)\
  $(gxdevice_h) $(gserrors_h) $(gsstate_h) $(strimpl_h) $(gscoord_h)\
  $(pltop_h) $(gsicc_manage_h) $(gspaint_h) $(plmain_h)
 	$(GPDLCC) $(I_)$(LDF_JB2I_) $(JBIG2_CFLAGS) $(II)$(JB2I_)$(_I) $(GPDLSRC)jbig2top.c $(GPDLO_)$(GPDL_JBIG2_TOP_OBJ_FILE)
+
+$(GPDL_JP2K_TOP_OBJ): $(GPDLSRC)jp2ktop.c $(AK)\
+ $(gxdevice_h) $(gserrors_h) $(gsstate_h) $(strimpl_h) $(gscoord_h)\
+ $(pltop_h) $(gsicc_manage_h) $(gspaint_h) $(plmain_h)
+	$(GPDLCC) $(I_)$(JPX_OPENJPEG_I_)$(D).. $(I_)$(JPX_OPENJPEG_I_) $(II)$(GLI_)$(_I) $(JPXCF_) $(I_)$(LWF_JPXI_) $(GPDLSRC)jp2ktop.c $(GPDLO_)$(GPDL_JP2K_TOP_OBJ_FILE)
diff --git a/gpdl/jp2ktop.c b/gpdl/jp2ktop.c
new file mode 100644
index 0000000..9c0b398
--- /dev/null
+++ b/gpdl/jp2ktop.c
@@ -0,0 +1,737 @@
+/* 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.
+*/
+
+/* jp2ktop.c */
+/* Top-level API implementation of "JP2K" Language Interface */
+
+#include "pltop.h"
+#include "gserrors.h"
+#include "gxdevice.h"
+#include "gsstate.h"
+#include "strimpl.h"
+#include "gscoord.h"
+#include "gsicc_manage.h"
+#include "gspaint.h"
+#include "plmain.h"
+#ifdef USE_LWF_JP2
+#include "sjpx_luratech.h"
+#else
+#include "sjpx_openjpeg.h"
+#endif
+#include "stream.h"
+
+/* Forward decls */
+
+/************************************************************/
+/******** Language wrapper implementation (see pltop.h) *****/
+/************************************************************/
+
+typedef enum
+{
+    ii_state_identifying = 0,
+    ii_state_jp2k,
+    ii_state_jp2k_header,
+    ii_state_jp2k_data,
+    ii_state_flush
+} ii_state;
+
+/*
+ * JP2K interpreter instance
+ */
+typedef struct jp2k_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           bpp;
+    uint32_t           width;
+    uint32_t           height;
+    uint32_t           xresolution;
+    uint32_t           yresolution;
+    uint32_t           copies;
+
+    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_jpxd_state  jp2k_state;
+    byte               stream_buffer[2048];
+
+} jp2k_interp_instance_t;
+
+static int
+jp2k_detect_language(const char *s_, int len)
+{
+    const unsigned char *s = (const unsigned char *)s_;
+    if (len >= 12) {
+        if (s[0] == 0x00 &&
+            s[1] == 0x00 &&
+            s[2] == 0x00 &&
+            s[3] == 0x0C &&
+            s[4] == 0x6a &&
+            s[5] == 0x50 &&
+            s[6] == 0x1a &&
+            s[7] == 0x1a &&
+            s[8] == 0x0d &&
+            s[9] == 0x0a &&
+            s[10] == 0x87 &&
+            s[11] == 0x0a)
+            return 100; /* JP2 file */
+    }
+    if (len >= 4) {
+        if (s[0] == 0xff &&
+            s[1] == 0x4f &&
+            s[2] == 0xff &&
+            s[3] == 0x51)
+            return 100; /* J2K stream */
+    }
+
+    return 0;
+}
+
+static const pl_interp_characteristics_t jp2k_characteristics = {
+    "JP2K",
+    jp2k_detect_language,
+    "Artifex",
+    "0.01",
+    "22 Nov 2019",
+    1 /* minimum input size */
+};
+
+/* Get implementation's characteristics */
+static const pl_interp_characteristics_t * /* always returns a descriptor */
+jp2k_impl_characteristics(const pl_interp_implementation_t *impl)     /* implementation of interpreter to alloc */
+{
+  return &jp2k_characteristics;
+}
+
+static void
+jp2k_deallocate(jp2k_interp_instance_t *jp2k)
+{
+    if (jp2k == NULL)
+        return;
+
+    rc_decrement_cs(jp2k->gray, "jp2k_deallocate");
+    rc_decrement_cs(jp2k->rgb, "jp2k_deallocate");
+    rc_decrement_cs(jp2k->cmyk, "jp2k_deallocate");
+
+    if (jp2k->pgs != NULL)
+        gs_gstate_free_chain(jp2k->pgs);
+    gs_free_object(jp2k->memory, jp2k, "jp2k_impl_allocate_interp_instance");
+}
+
+/* Deallocate a interpreter instance */
+static int
+jp2k_impl_deallocate_interp_instance(pl_interp_implementation_t *impl)
+{
+    jp2k_interp_instance_t *jp2k = (jp2k_interp_instance_t *)impl->interp_client_data;
+
+    jp2k_deallocate(jp2k);
+    impl->interp_client_data = NULL;
+
+    return 0;
+}
+
+/* Do per-instance interpreter allocation/init. */
+static int
+jp2k_impl_allocate_interp_instance(pl_interp_implementation_t *impl, gs_memory_t *mem)
+{
+    int code;
+    jp2k_interp_instance_t *jp2k
+        = (jp2k_interp_instance_t *)gs_alloc_bytes(mem,
+                                                  sizeof(jp2k_interp_instance_t),
+                                                  "jp2k_impl_allocate_interp_instance");
+    if (!jp2k)
+        return_error(gs_error_VMerror);
+    memset(jp2k, 0, sizeof(*jp2k));
+
+    jp2k->memory = mem;
+    jp2k->pgs = gs_gstate_alloc(mem);
+    if (jp2k->pgs == NULL)
+        goto failVM;
+
+    /* Push one save level onto the stack to assuage the memory handling */
+    code = gs_gsave(jp2k->pgs);
+    if (code < 0)
+        goto fail;
+
+    code = gsicc_init_iccmanager(jp2k->pgs);
+    if (code < 0)
+        goto fail;
+
+    jp2k->gray = gs_cspace_new_ICC(mem, jp2k->pgs, 1);
+    jp2k->rgb  = gs_cspace_new_ICC(mem, jp2k->pgs, 3);
+    jp2k->cmyk = gs_cspace_new_ICC(mem, jp2k->pgs, 4);
+
+    impl->interp_client_data = jp2k;
+
+    return 0;
+
+failVM:
+    code = gs_note_error(gs_error_VMerror);
+fail:
+    (void)jp2k_deallocate(jp2k);
+    return code;
+}
+
+/*
+ * Get the allocator with which to allocate a device
+ */
+static gs_memory_t *
+jp2k_impl_get_device_memory(pl_interp_implementation_t *impl)
+{
+    jp2k_interp_instance_t *jp2k = (jp2k_interp_instance_t *)impl->interp_client_data;
+
+    return jp2k->dev ? jp2k->dev->memory : NULL;
+}
+
+#if 0 /* UNUSED */
+static int
+jp2k_impl_set_param(pl_interp_implementation_t *impl,
+                   pl_set_param_type           type,
+                   const char                 *param,
+                   const void                 *val)
+{
+    jp2k_interp_instance_t *jp2k = (jp2k_interp_instance_t *)impl->interp_client_data;
+
+    /* No params set here */
+    return 0;
+}
+
+static int
+jp2k_impl_add_path(pl_interp_implementation_t *impl,
+                  const char                 *path)
+{
+    jp2k_interp_instance_t *jp2k = (jp2k_interp_instance_t *)impl->interp_client_data;
+
+    /* No paths to add */
+    return 0;
+}
+
+static int
+jp2k_impl_post_args_init(pl_interp_implementation_t *impl)
+{
+    jp2k_interp_instance_t *jp2k = (jp2k_interp_instance_t *)impl->interp_client_data;
+
+    /* No post args processing */
+    return 0;
+}
+#endif
+
+/* Prepare interp instance for the next "job" */
+static int
+jp2k_impl_init_job(pl_interp_implementation_t *impl,
+                  gx_device                  *device)
+{
+    jp2k_interp_instance_t *jp2k = (jp2k_interp_instance_t *)impl->interp_client_data;
+
+    jp2k->dev = device;
+    jp2k->state = ii_state_identifying;
+
+    return 0;
+}
+
+#if 0 /* UNUSED */
+static int
+jp2k_impl_run_prefix_commands(pl_interp_implementation_t *impl,
+                             const char                 *prefix)
+{
+    jp2k_interp_instance_t *jp2k = (jp2k_interp_instance_t *)impl->interp_client_data;
+
+    return 0;
+}
+
+static int
+jp2k_impl_process_file(pl_interp_implementation_t *impl, const char *filename)
+{
+    jp2k_interp_instance_t *jp2k = (jp2k_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 */
+jp2k_impl_process_begin(pl_interp_implementation_t * impl)
+{
+    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(jp2k_interp_instance_t *jp2k, 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
+do_process(jp2k_interp_instance_t *jp2k, stream_cursor_read * pr, bool eof)
+{
+    int code = 0;
+    gs_color_space *cs;
+    ii_state ostate = jp2k->state;
+    size_t bytes_in;
+    int advanced;
+
+    /* Loop until we stop 'advancing'. */
+    do
+    {
+        advanced = 0;
+        bytes_in = pr->limit - pr->ptr;
+        switch(jp2k->state)
+        {
+        case ii_state_identifying:
+        {
+            const byte *s = pr->ptr+1;
+
+            code = ensure_bytes(jp2k, pr, 4);
+            if (code < 0)
+                return code;
+            if (s[0] == 0xff &&
+                s[1] == 0x4f &&
+                s[2] == 0xff &&
+                s[3] == 0x51) {
+                jp2k->state = ii_state_jp2k_header;
+                break;
+            }
+
+            code = ensure_bytes(jp2k, pr, 12);
+            if (code < 0)
+                return code;
+            if (s[0] == 0x00 &&
+                s[1] == 0x00 &&
+                s[2] == 0x00 &&
+                s[3] == 0x0C &&
+                s[4] == 0x6a &&
+                s[5] == 0x50 &&
+                s[6] == 0x1a &&
+                s[7] == 0x1a &&
+                s[8] == 0x0d &&
+                s[9] == 0x0a &&
+                s[10] == 0x87 &&
+                s[11] == 0x0a) {
+                jp2k->state = ii_state_jp2k_header;
+                break;
+            }
+            jp2k->state = ii_state_flush;
+            break;
+        }
+        case ii_state_jp2k_header:
+            s_init_state((stream_state *)&jp2k->jp2k_state, &s_jpxd_template, jp2k->memory);
+            if (s_jpxd_template.set_defaults)
+                s_jpxd_template.set_defaults((stream_state *)&jp2k->jp2k_state);
+
+            code = (s_jpxd_template.init)((stream_state *)&jp2k->jp2k_state);
+            if (code < 0)
+                goto early_flush;
+            jp2k->state = ii_state_jp2k_data;
+            break;
+        case ii_state_jp2k_data:
+        {
+            int n = bytes_until_uel(pr);
+            stream_cursor_read local_r;
+            stream_cursor_write local_w;
+            int status;
+            unsigned int used;
+
+            local_r.ptr = pr->ptr;
+            local_r.limit = local_r.ptr + n;
+
+            do {
+                local_w.ptr = jp2k->stream_buffer-1;
+                local_w.limit = local_w.ptr + sizeof(jp2k->stream_buffer);
+
+                status = (s_jpxd_template.process)((stream_state *)&jp2k->jp2k_state,
+                                                                   &local_r, &local_w,
+                                                                   eof);
+                /* status = 0 => need data
+                 *          1 => need output space
+                 * but we don't actually use this currently. */
+                (void)status;
+                /* Copy the updated pointer back */
+                pr->ptr = local_r.ptr;
+                if (local_w.ptr + 1 == jp2k->stream_buffer)
+                    break; /* Failed to decode any data */
+
+                if (jp2k->width == 0) {
+                    float scale, xoffset, yoffset, xext, yext;
+
+                    /* First data we decoded! */
+                    jp2k->width = jp2k->jp2k_state.width;
+                    if (jp2k->width == 0) {
+                        jp2k->state = ii_state_flush;
+                        break;
+                    }
+                    jp2k->height = jp2k->jp2k_state.height;
+#ifdef USE_LWF_JP2
+                    jp2k->bpp = jp2k->jp2k_state.bpc * jp2k->jp2k_state.ncomp;
+#else
+                    jp2k->bpp = jp2k->jp2k_state.bpp;
+#endif
+                    jp2k->xresolution = 72;
+                    jp2k->yresolution = 72;
+                    jp2k->copies = 1;
+                    /* I would have thought we should use jp2k->jp2k_state.out_numcomps,
+                     * but this is wrong! */
+                    jp2k->num_comps = jp2k->bpp/8;
+                    jp2k->byte_width = (jp2k->width * jp2k->bpp + 7)>>3;
+
+#if 0
+                    switch (jp2k->jp2k_state.colorspace) {
+                    default:
+                        goto early_flush;
+                    case gs_jpx_cs_gray:
+                        cs = jp2k->gray;
+                        break;
+                    case gs_jpx_cs_rgb:
+                        cs = jp2k->rgb;
+                        break;
+                    case gs_jpx_cs_cmyk:
+                        cs = jp2k->cmyk;
+                        break;
+                    }
+#else
+                    switch (jp2k->num_comps) {
+                    case 1:
+                        cs = jp2k->gray;
+                        break;
+                    case 3:
+                        cs = jp2k->rgb;
+                        break;
+                    case 4:
+                        cs = jp2k->cmyk;
+                        break;
+                    default:
+                        goto early_flush;
+                    }
+#endif
+
+                    /* Scale to fit, if too large. */
+                    scale = 1.0f;
+                    if (jp2k->width * jp2k->dev->HWResolution[0] > jp2k->dev->width * jp2k->xresolution)
+                        scale = ((float)jp2k->dev->width * jp2k->xresolution) / (jp2k->width * jp2k->dev->HWResolution[0]);
+                    if (scale * jp2k->height * jp2k->dev->HWResolution[1] > jp2k->dev->height * jp2k->yresolution)
+                        scale = ((float)jp2k->dev->height * jp2k->yresolution) / (jp2k->height * jp2k->dev->HWResolution[1]);
+
+                    jp2k->nulldev = gs_currentdevice(jp2k->pgs);
+                    rc_increment(jp2k->nulldev);
+                    code = gs_setdevice_no_erase(jp2k->pgs, jp2k->dev);
+                    if (code < 0)
+                        goto early_flush;
+                    gs_initmatrix(jp2k->pgs);
+
+                    /* Centre - Extents and offsets are all calculated in points (1/72 of an inch) */
+                    xext = (((float)jp2k->width) * 72 * scale / jp2k->xresolution);
+                    xoffset = (jp2k->dev->width * 72 / jp2k->dev->HWResolution[0] - xext)/2;
+                    yext = (((float)jp2k->height) * 72 * scale / jp2k->yresolution);
+                    yoffset = (jp2k->dev->height * 72 / jp2k->dev->HWResolution[1] - yext)/2;
+
+                    /* 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(jp2k->pgs, 0.0, jp2k->dev->height * 72 / jp2k->dev->HWResolution[1]);
+                    if (code >= 0)
+                        code = gs_translate(jp2k->pgs, xoffset, -yoffset);
+                    if (code >= 0)
+                        code = gs_scale(jp2k->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(jp2k->pgs);
+                    if (code < 0)
+                        goto early_flush;
+
+                    memset(&jp2k->image, 0, sizeof(jp2k->image));
+                    gs_image_t_init(&jp2k->image, cs);
+                    jp2k->image.BitsPerComponent = jp2k->bpp/jp2k->num_comps;
+                    jp2k->image.Width = jp2k->width;
+                    jp2k->image.Height = jp2k->height;
+
+                    jp2k->image.ImageMatrix.xx = jp2k->xresolution / 72.0f;
+                    jp2k->image.ImageMatrix.yy = jp2k->yresolution / 72.0f;
+
+                    jp2k->penum = gs_image_enum_alloc(jp2k->memory, "jp2k_impl_process(penum)");
+                    if (jp2k->penum == NULL) {
+                        code = gs_note_error(gs_error_VMerror);
+                        goto early_flush;
+                    }
+
+                    code = gs_image_init(jp2k->penum,
+                                         &jp2k->image,
+                                         false,
+                                         false,
+                                         jp2k->pgs);
+                    if (code < 0)
+                        goto early_flush;
+                }
+
+                code = gs_image_next(jp2k->penum, jp2k->stream_buffer, local_w.ptr + 1 - jp2k->stream_buffer, &used);
+                if (code < 0)
+                    goto flush;
+
+                jp2k->x += used;
+                while (jp2k->x >= jp2k->byte_width) {
+                    jp2k->x -= jp2k->byte_width;
+                    jp2k->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 (jp2k->y < jp2k->height);
+
+            if (jp2k->penum && jp2k->y == jp2k->height) {
+                code = gs_image_cleanup_and_free_enum(jp2k->penum, jp2k->pgs);
+                jp2k->penum = NULL;
+                if (code < 0)
+                    goto flush;
+                code = pl_finish_page(jp2k->memory->gs_lib_ctx->top_of_system,
+                                      jp2k->pgs, jp2k->copies, true);
+                if (code < 0)
+                    goto flush;
+                if (jp2k->pages > 0) {
+                    jp2k->pages--;
+                    /* If we've reached the expected end, we should probably flush to UEL */
+                    if (jp2k->pages == 0)
+                        jp2k->state = ii_state_flush;
+                }
+                if (jp2k->jp2k_state.templat->release)
+                    jp2k->jp2k_state.templat->release((stream_state *)&jp2k->jp2k_state);
+                jp2k->state = ii_state_jp2k_header;
+            }
+            break;
+        }
+        default:
+        case ii_state_flush:
+            if (0) {
+flush:
+                if (jp2k->jp2k_state.templat->release)
+                    jp2k->jp2k_state.templat->release((stream_state *)&jp2k->jp2k_state);
+early_flush:
+                jp2k->state = ii_state_flush;
+            }
+            /* We want to bin any data we get up to, but not including
+             * a UEL. */
+            return flush_to_uel(pr);
+        }
+        advanced |= (ostate != jp2k->state);
+        ostate = jp2k->state;
+        advanced |= (bytes_in != pr->limit - pr->ptr);
+        bytes_in = pr->limit - pr->ptr;
+    } while (advanced);
+
+    return code;
+}
+
+static int
+jp2k_impl_process(pl_interp_implementation_t * impl, stream_cursor_read * pr)
+{
+    jp2k_interp_instance_t *jp2k = (jp2k_interp_instance_t *)impl->interp_client_data;
+
+    return do_process(jp2k, pr, 0);
+}
+
+static int
+jp2k_impl_process_end(pl_interp_implementation_t * impl)
+{
+    return 0;
+}
+
+/* Not implemented */
+static int
+jp2k_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
+jp2k_impl_process_eof(pl_interp_implementation_t *impl)
+{
+    jp2k_interp_instance_t *jp2k = (jp2k_interp_instance_t *)impl->interp_client_data;
+    stream_cursor_read cursor;
+
+    cursor.ptr = NULL;
+    cursor.limit = NULL;
+
+    return do_process(jp2k, &cursor, 1);
+}
+
+/* Report any errors after running a job */
+static int
+jp2k_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
+jp2k_impl_dnit_job(pl_interp_implementation_t *impl)
+{
+    jp2k_interp_instance_t *jp2k = (jp2k_interp_instance_t *)impl->interp_client_data;
+
+    if (jp2k->nulldev) {
+        int code = gs_setdevice(jp2k->pgs, jp2k->nulldev);
+        jp2k->dev = NULL;
+        rc_decrement(jp2k->nulldev, "jp2k_impl_dnit_job(nulldevice)");
+        jp2k->nulldev = NULL;
+        return code;
+    }
+    return 0;
+}
+
+/* Parser implementation descriptor */
+const pl_interp_implementation_t jp2k_implementation = {
+  jp2k_impl_characteristics,
+  jp2k_impl_allocate_interp_instance,
+  jp2k_impl_get_device_memory,
+  NULL, /* jp2k_impl_set_param */
+  NULL, /* jp2k_impl_add_path */
+  NULL, /* jp2k_impl_post_args_init */
+  jp2k_impl_init_job,
+  NULL, /* jp2k_impl_run_prefix_commands */
+  NULL, /* jp2k_impl_process_file */
+  jp2k_impl_process_begin,
+  jp2k_impl_process,
+  jp2k_impl_process_end,
+  jp2k_impl_flush_to_eoj,
+  jp2k_impl_process_eof,
+  jp2k_impl_report_errors,
+  jp2k_impl_dnit_job,
+  jp2k_impl_deallocate_interp_instance,
+  NULL
+};
diff --git a/pcl/pl/plimpl.c b/pcl/pl/plimpl.c
index ff2dba4..71d806c 100644
--- a/pcl/pl/plimpl.c
+++ b/pcl/pl/plimpl.c
@@ -45,6 +45,8 @@ extern pl_interp_implementation_t tiff_implementation;
 
 extern pl_interp_implementation_t jbig2_implementation;
 
+extern pl_interp_implementation_t jp2k_implementation;
+
 /* Zero-terminated list of pointers to implementations */
 pl_interp_implementation_t *pdl_implementations[] = {
     &pjl_implementation,
@@ -73,6 +75,9 @@ pl_interp_implementation_t *pdl_implementations[] = {
 #ifdef JBIG2_INCLUDED
     &jbig2_implementation,
 #endif
+#ifdef JP2K_INCLUDED
+    &jp2k_implementation,
+#endif
     0
 };
 
diff --git a/windows/ghostpdl.vcproj b/windows/ghostpdl.vcproj
index 92694ae..3e884e0 100644
--- a/windows/ghostpdl.vcproj
+++ b/windows/ghostpdl.vcproj
@@ -310,6 +310,10 @@
 				>
 			</File>
 			<File
+				RelativePath="..\gpdl\jp2ktop.c"
+				>
+			</File>
+			<File
 				RelativePath="..\gpdl\jpgtop.c"
 				>
 			</File>

----------------------------------------------------------------------
commit 113792dd347ac7b9436b9517aca91e1153fb230d
Author: Robin Watts <[email protected]>
Date:   Mon Nov 25 15:59:00 2019 +0000

    Ensure oki4w device properly closes device.
    
    This was causing indetermisms in cluster runs.

diff --git a/contrib/gdevop4w.c b/contrib/gdevop4w.c
index bfc6720..1167c7c 100644
--- a/contrib/gdevop4w.c
+++ b/contrib/gdevop4w.c
@@ -137,6 +137,8 @@ oki4w_open(gx_device *pdev)
 static int
 oki4w_close(gx_device *pdev)
 {
+        /* RJW: We must call the close entry point for the class. */
+        return gdev_prn_close(pdev);
 /*
         if ( pdev->Duplex_set >= 0 && pdev->Duplex )
           {	gdev_prn_open_printer(pdev, 1);

----------------------------------------------------------------------
commit 12eef95ef9ed7a086ca02f06ef93acce9c9a2676
Author: Robin Watts <[email protected]>
Date:   Mon Nov 25 14:50:05 2019 +0000

    Remove FIXME and associated dead code in pwgtop.c
    
    This should solve coverity 351087.

diff --git a/gpdl/pwgtop.c b/gpdl/pwgtop.c
index c61fd31..5606790 100644
--- a/gpdl/pwgtop.c
+++ b/gpdl/pwgtop.c
@@ -639,15 +639,7 @@ early_flush:
 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;
-
-    (void)pwg;
-    /* FIXME: */
-    if (code == gs_error_InterpreterExit || code == gs_error_NeedInput)
-        code = 0;
-
-    return code;
+    return 0;
 }
 
 /* Not implemented */

----------------------------------------------------------------------
commit ab089bf9511ccaa30a33d6e269ab0dae98ff5517
Author: Robin Watts <[email protected]>
Date:   Mon Nov 25 14:46:56 2019 +0000

    Fix coverity #351085; toff_t's are unsigned.
    
    The tiff lib uses a 'seek' function that apes 'fseek' in that
    it takes a whence field. In order for the whence field to be
    useful for 'SEEK_SET' (and half the cases of SEEK_CURR), it
    needs to be able to take negative offsets. The tiff seek
    function only passes positive offsets though as it uses an
    unsigned type.
    
    It is therefore pointless to test against 0.

diff --git a/gpdl/tifftop.c b/gpdl/tifftop.c
index bf14110..1028afc 100644
--- a/gpdl/tifftop.c
+++ b/gpdl/tifftop.c
@@ -382,6 +382,9 @@ 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;
 
+    /* toff_t is unsigned, which kind of implies they'll never use
+     * SEEK_CUR, or SEEK_END, which makes you wonder why they include
+     * whence at all, but... */
     if (whence == 1) { /* SEEK_CURR */
         offset += pos;
     } else if (whence == 2) { /* SEEK_END */
@@ -389,10 +392,8 @@ static toff_t tifsSeekProc(thandle_t tiff_, toff_t offset, int whence)
     }
     /* else assume SEEK_SET */
 
-    /* Clamp */
-    if (offset < 0)
-        offset = 0;
-    else if (offset > tiff->buffer_full)
+    /* Clamp (Don't check against 0 as toff_t is unsigned) */
+    if (offset > tiff->buffer_full)
         offset = tiff->buffer_full;
 
     tiff->file_pos = offset;


Summary of changes:
 contrib/gdevop4w.c      |   2 +
 gpdl/gpdl.mak           |  12 +-
 gpdl/jp2ktop.c          | 737 ++++++++++++++++++++++++++++++++++++++++++++++++
 gpdl/pwgtop.c           |  12 +-
 gpdl/tifftop.c          |   9 +-
 pcl/pl/plimpl.c         |   5 +
 windows/ghostpdl.vcproj |   4 +
 7 files changed, 766 insertions(+), 15 deletions(-)
 create mode 100644 gpdl/jp2ktop.c