/* 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.
*/

#include "stdint_.h"
#include "gdevprn.h"
#include "gxdownscale.h"
#include "gdevkrnlsclass.h" /* 'standard' built in subclasses, currently First/Last Page and obejct filter */
#include "stream.h"
#include "spprint.h"
#include "time_.h"
#include "smd5.h"
#include "sstring.h"
#include "strimpl.h"
#include "slzwx.h"
#include "szlibx.h"
#include "sdct.h"
#include "srlx.h"
#include "gsicc_cache.h"
#include "sjpeg.h"

#define	    COMPRESSION_NONE	1	/* dump mode */
#define	    COMPRESSION_LZW		2       /* Lempel-Ziv  & Welch */
#define	    COMPRESSION_FLATE	3
#define	    COMPRESSION_JPEG	4
#define	    COMPRESSION_RLE		5

static struct compression_string {
    unsigned char id;
    const char *str;
} compression_strings [] = {
    { COMPRESSION_NONE, "None" },
    { COMPRESSION_LZW, "LZW" },     /* Not supported in PCLm */
    { COMPRESSION_FLATE, "Flate" },
    { COMPRESSION_JPEG, "JPEG" },
    { COMPRESSION_RLE, "RLE" },
    { 0, NULL }
};

int pdf_image_open(gx_device *pdev);
int PCLm_open(gx_device *pdev);
int pdf_image_close(gx_device * pdev);
int PCLm_close(gx_device * pdev);
int pdf_image_put_params_downscale(gx_device * dev, gs_param_list * plist);
int pdf_image_put_params_downscale_cmyk(gx_device * dev, gs_param_list * plist);
int pdf_image_put_params_downscale_cmyk_ets(gx_device * dev, gs_param_list * plist);
int pdf_image_get_params_downscale(gx_device * dev, gs_param_list * plist);
int pdf_image_get_params_downscale_cmyk(gx_device * dev, gs_param_list * plist);
int pdf_image_get_params_downscale_cmyk_ets(gx_device * dev, gs_param_list * plist);

typedef struct pdfimage_page_s {
    int ImageObjectNumber;
    gs_offset_t ImageOffset;int LengthObjectNumber;
    gs_offset_t LengthOffset;
    int PageStreamObjectNumber;
    gs_offset_t PageStreamOffset;
    int PageDictObjectNumber;
    gs_offset_t PageDictOffset;
    void *next;
} pdfimage_page;

typedef struct PCLm_temp_file_s {
    char file_name[gp_file_name_sizeof];
    FILE *file;
    stream *strm;
    stream *save;
    byte *strm_buf;
} PCLm_temp_file_t;

typedef struct gx_device_pdf_image_s {
    gx_device_common;
    gx_prn_device_common;
    unsigned char Compression;
    gx_downscaler_params downscale;
    int StripHeight;
    float QFactor;
    int JPEGQ;
    gsicc_link_t *icclink;
    stream *strm;
    byte *strm_buf;
    int NumPages;
    gs_offset_t RootOffset;
    gs_offset_t PagesOffset;
    gs_offset_t xrefOffset;
    pdfimage_page *Pages;
    PCLm_temp_file_t xref_stream;
    PCLm_temp_file_t temp_stream;
    int NextObject;
} gx_device_pdf_image;

static dev_proc_print_page(pdf_image_print_page);

/* ------ The pdfimage8 device ------ */

static const gx_device_procs pdfimage8_procs =
prn_color_params_procs(pdf_image_open,
                       gdev_prn_output_page_seekable,
                       pdf_image_close,
                       gx_default_gray_map_rgb_color,
                       gx_default_gray_map_color_rgb,
                       pdf_image_get_params_downscale,
                       pdf_image_put_params_downscale);

const gx_device_pdf_image gs_pdfimage8_device = {
    prn_device_body(gx_device_pdf_image,
                    pdfimage8_procs,
                    "pdfimage8",
                    DEFAULT_WIDTH_10THS, DEFAULT_HEIGHT_10THS,
                    600, 600,   /* 600 dpi by default */
                    0, 0, 0, 0, /* Margins */
                    1,          /* num components */
                    8,          /* bits per sample */
                    255, 0, 256, 0,
                    pdf_image_print_page),
                    3,
    GX_DOWNSCALER_PARAMS_DEFAULTS,
    0,  /* StripHeight */
    0.0, /* QFactor */
    0    /* JPEGQ */
};

/* ------ The pdfimage24 device ------ */

static const gx_device_procs pdfimage24_procs =
prn_color_params_procs(pdf_image_open,
                       gdev_prn_output_page_seekable,
                       pdf_image_close,
                       gx_default_rgb_map_rgb_color,
                       gx_default_rgb_map_color_rgb,
                       pdf_image_get_params_downscale,
                       pdf_image_put_params_downscale);

const gx_device_pdf_image gs_pdfimage24_device = {
    prn_device_body(gx_device_pdf_image,
                    pdfimage24_procs,
                    "pdfimage24",
                    DEFAULT_WIDTH_10THS, DEFAULT_HEIGHT_10THS,
                    600, 600,   /* 600 dpi by default */
                    0, 0, 0, 0, /* Margins */
                    3,          /* num components */
                    24,         /* bits per sample */
                    255, 255, 256, 256,
                    pdf_image_print_page),
                    3,
    GX_DOWNSCALER_PARAMS_DEFAULTS,
    0,  /* StripHeight */
    0.0, /* QFactor */
    0    /* JPEGQ */
};

/* ------ The pdfimage32 device ------ */

static const gx_device_procs pdfimage32_procs = {
    pdf_image_open, NULL, NULL, gdev_prn_output_page_seekable, pdf_image_close,
    NULL, cmyk_8bit_map_color_cmyk, NULL, NULL, NULL, NULL, NULL, NULL,
    pdf_image_get_params_downscale_cmyk, pdf_image_put_params_downscale_cmyk,
    cmyk_8bit_map_cmyk_color, NULL, NULL, NULL, gx_page_device_get_page_device
};

const gx_device_pdf_image gs_pdfimage32_device = {
    prn_device_body(gx_device_pdf_image,
                    pdfimage32_procs,
                    "pdfimage32",
                    DEFAULT_WIDTH_10THS, DEFAULT_HEIGHT_10THS,
                    600, 600,   /* 600 dpi by default */
                    0, 0, 0, 0, /* Margins */
                    4,          /* num components */
                    32,         /* bits per sample */
                    255, 255, 256, 256,
                    pdf_image_print_page),
                    3,
    GX_DOWNSCALER_PARAMS_DEFAULTS,
    0,  /* StripHeight */
    0.0, /* QFactor */
    0    /* JPEGQ */
};

static int gdev_pdf_image_begin_page(gx_device_pdf_image *pdf_dev,
                         FILE *file)
{
    gx_device_printer *const pdev = (gx_device_printer *)pdf_dev;
    cmm_dev_profile_t *profile_struct;
    gsicc_rendering_param_t rendering_params;
    int code;
    pdfimage_page *page;

    page = (pdfimage_page *)gs_alloc_bytes(pdf_dev->memory->non_gc_memory, sizeof(pdfimage_page), "pdfimage create new page");
    if (page == NULL)
        return_error(gs_error_VMerror);

    memset(page, 0x00, sizeof(pdfimage_page));

    if (gdev_prn_file_is_new(pdev)) {
        /* Set up the icc link settings at this time */
        code = dev_proc(pdev, get_profile)((gx_device *)pdev, &profile_struct);
        if (code < 0)
            return_error(gs_error_undefined);
        if (profile_struct->postren_profile != NULL) {
            rendering_params.black_point_comp = gsBLACKPTCOMP_ON;
            rendering_params.graphics_type_tag = GS_UNKNOWN_TAG;
            rendering_params.override_icc = false;
            rendering_params.preserve_black = gsBLACKPRESERVE_OFF;
            rendering_params.rendering_intent = gsRELATIVECOLORIMETRIC;
            rendering_params.cmm = gsCMM_DEFAULT;
            if (profile_struct->oi_profile != NULL) {
                pdf_dev->icclink = gsicc_alloc_link_dev(pdev->memory,
                    profile_struct->oi_profile, profile_struct->postren_profile,
                    &rendering_params);
            } else if (profile_struct->link_profile != NULL) {
                pdf_dev->icclink = gsicc_alloc_link_dev(pdev->memory,
                    profile_struct->link_profile, profile_struct->postren_profile,
                    &rendering_params);
            } else {
                pdf_dev->icclink = gsicc_alloc_link_dev(pdev->memory,
                    profile_struct->device_profile[0], profile_struct->postren_profile,
                    &rendering_params);
            }
            /* If it is identity, release it now and set link to NULL */
            if (pdf_dev->icclink->is_identity) {
                pdf_dev->icclink->procs.free_link(pdf_dev->icclink);
                gsicc_free_link_dev(pdev->memory, pdf_dev->icclink);
                pdf_dev->icclink = NULL;
            }
        }

        /* Set up the stream and insert the file header */
        pdf_dev->strm = s_alloc(pdf_dev->memory->non_gc_memory, "pdfimage_open_temp_stream(strm)");
        if (pdf_dev->strm == 0)
            return_error(gs_error_VMerror);
        pdf_dev->strm_buf = gs_alloc_bytes(pdf_dev->memory->non_gc_memory, pdf_dev->width * (pdf_dev->color_info.depth / 8),
                                       "pdfimage_open_temp_stream(strm_buf)");
        if (pdf_dev->strm_buf == 0) {
            pdf_dev->strm->file = NULL; /* Don't close underlying file when we free the stream */
            gs_free_object(pdf_dev->memory->non_gc_memory, pdf_dev->strm,
                           "pdfimage_open_temp_stream(strm)");
            pdf_dev->strm = 0;
            return_error(gs_error_VMerror);
        }
        swrite_file(pdf_dev->strm, pdf_dev->file, pdf_dev->strm_buf, pdf_dev->width * (pdf_dev->color_info.depth / 8));

        stream_puts(pdf_dev->strm, "%PDF-1.3\n");
        stream_puts(pdf_dev->strm, "%\307\354\217\242\n");
        pdf_dev->Pages = page;
    } else {
        pdfimage_page *current = pdf_dev->Pages;
        while(current->next)
            current = current->next;
        current->next = page;
    }
    page->ImageObjectNumber = (pdf_dev->NumPages * 4) + 3;
    page->LengthObjectNumber = page->ImageObjectNumber + 1;
    page->PageStreamObjectNumber = page->LengthObjectNumber + 1;
    page->PageDictObjectNumber = page->PageStreamObjectNumber + 1;
    page->ImageOffset = stell(pdf_dev->strm);
    pdf_dev->StripHeight = pdf_dev->height;

    return 0;
}
static int pdf_image_chunky_post_cm(void  *arg, byte **dst, byte **src, int w, int h,
    int raster)
{
    gsicc_bufferdesc_t input_buffer_desc, output_buffer_desc;
    gsicc_link_t *icclink = (gsicc_link_t*)arg;

    gsicc_init_buffer(&input_buffer_desc, icclink->num_input, 1, false,
        false, false, 0, raster, h, w);
    gsicc_init_buffer(&output_buffer_desc, icclink->num_output, 1, false,
        false, false, 0, raster, h, w);
    icclink->procs.map_buffer(NULL, icclink, &input_buffer_desc, &output_buffer_desc,
        src[0], dst[0]);
    return 0;
}

static int
encode(gx_device *pdev, stream **s, const stream_template *t, gs_memory_t *mem)
{
    gx_device_pdf_image *pdf_dev = (gx_device_pdf_image *)pdev;
    stream_state *st;

    if (t == &s_DCTE_template) {
        int code;
        stream_DCT_state *sDCT;
        jpeg_compress_data *jcdp;

        st = s_alloc_state(mem, t->stype, "pdfimage.encode");
        if (st == 0)
            return_error(gs_error_VMerror);

        sDCT = (stream_DCT_state *)st;
        st->templat = t;
        if (t->set_defaults)
            t->set_defaults(st);

        jcdp = gs_alloc_struct_immovable(mem, jpeg_compress_data,
           &st_jpeg_compress_data, "zDCTE");
        if (jcdp == 0) {
            gs_free_object(mem, st, "pdfimage.encode");
            return_error(gs_error_VMerror);
        }
        sDCT->data.compress = jcdp;
        sDCT->icc_profile = NULL;
        jcdp->memory = sDCT->jpeg_memory = mem;	/* set now for allocation */
        if ((code = gs_jpeg_create_compress(sDCT)) < 0) {
            gs_jpeg_destroy(sDCT);
            gs_free_object(mem, jcdp, "setup_image_compression");
            sDCT->data.compress = NULL; /* Avoid problems with double frees later */
            return code;
        }

        jcdp->Picky = 0;
        jcdp->Relax = 0;
        jcdp->cinfo.image_width = gx_downscaler_scale(pdf_dev->width, pdf_dev->downscale.downscale_factor);
        jcdp->cinfo.image_height = pdf_dev->StripHeight;
        switch (pdf_dev->color_info.depth) {
            case 32:
                jcdp->cinfo.input_components = 4;
                jcdp->cinfo.in_color_space = JCS_CMYK;
                break;
            case 24:
                jcdp->cinfo.input_components = 3;
                jcdp->cinfo.in_color_space = JCS_RGB;
                break;
            case 8:
                jcdp->cinfo.input_components = 1;
                jcdp->cinfo.in_color_space = JCS_GRAYSCALE;
                break;
        }
        if ((code = gs_jpeg_set_defaults(sDCT)) < 0) {
            gs_jpeg_destroy(sDCT);
            gs_free_object(mem, jcdp, "setup_image_compression");
            sDCT->data.compress = NULL; /* Avoid problems with double frees later */
            return code;
        }
        if (pdf_dev->JPEGQ > 0) {
            code = gs_jpeg_set_quality(sDCT, pdf_dev->JPEGQ, TRUE);
            if (code < 0) {
                gs_jpeg_destroy(sDCT);
                gs_free_object(mem, jcdp, "setup_image_compression");
                sDCT->data.compress = NULL; /* Avoid problems with double frees later */
                return code;
            }
        } else if (pdf_dev->QFactor > 0.0) {
            code = gs_jpeg_set_linear_quality(sDCT,
                                         (int)(min(pdf_dev->QFactor, 100.0)
                                               * 100.0 + 0.5),
                                              TRUE);
            if (code < 0) {
                gs_jpeg_destroy(sDCT);
                gs_free_object(mem, jcdp, "setup_image_compression");
                sDCT->data.compress = NULL; /* Avoid problems with double frees later */
                return code;
            }
        }
        jcdp->cinfo.write_JFIF_header = FALSE;
        jcdp->cinfo.write_Adobe_marker = FALSE;	/* must do it myself */

        jcdp->templat = s_DCTE_template;
        /* Make sure we get at least a full scan line of input. */
        sDCT->scan_line_size = jcdp->cinfo.input_components *
            jcdp->cinfo.image_width;
        jcdp->templat.min_in_size =
            max(s_DCTE_template.min_in_size, sDCT->scan_line_size);
        /* Make sure we can write the user markers in a single go. */
        jcdp->templat.min_out_size =
            max(s_DCTE_template.min_out_size, sDCT->Markers.size);
        if (s_add_filter(s, &jcdp->templat, st, mem) == 0) {
            gs_jpeg_destroy(sDCT);
            gs_free_object(mem, jcdp, "setup_image_compression");
            sDCT->data.compress = NULL; /* Avoid problems with double frees later */
            return_error(gs_error_VMerror);
        }
    } else {
        st = s_alloc_state(mem, t->stype, "pdfimage.encode");
        if (st == 0)
            return_error(gs_error_VMerror);
        if (t->set_defaults)
            t->set_defaults(st);
        if (s_add_filter(s, t, st, mem) == 0) {
            gs_free_object(mem, st, "pdfimage.encode");
            return_error(gs_error_VMerror);
        }
    }

    return 0;
}

static int
pdf_image_downscale_and_print_page(gx_device_printer *dev, int factor,
                              int mfs, int bpc, int num_comps,
                              int trap_w, int trap_h, const int *trap_order,
                              int ets)
{
    gx_device_pdf_image *const pdf_dev = (gx_device_pdf_image *)dev;
    int code = 0;
    byte *data = NULL;
    int size = gdev_mem_bytes_per_scan_line((gx_device *)dev);
    int max_size =size;
    int row;
    int height = dev->height/factor;
    int width = dev->width/factor;
    gx_downscaler_t ds;
    gs_offset_t stream_pos = 0;
    pdfimage_page *page = pdf_dev->Pages;
    char Buffer[1024];
    stream *s = pdf_dev->strm;

    if (page == NULL)
        return_error(gs_error_undefined);

    while(page->next)
        page = page->next;

    if (num_comps == 4) {
        if (pdf_dev->icclink == NULL) {
            code = gx_downscaler_init_trapped_ets(&ds, (gx_device *)dev, 8, bpc, num_comps,
                factor, mfs, NULL, /*&fax_adjusted_width*/ 0, /*aw*/ trap_w, trap_h, trap_order, ets);
        } else {
            code = gx_downscaler_init_trapped_cm_ets(&ds, (gx_device *)dev, 8, bpc, num_comps,
                factor, mfs, NULL, /*&fax_adjusted_width*/ 0, /*aw*/ trap_w, trap_h, trap_order,
                pdf_image_chunky_post_cm, pdf_dev->icclink, pdf_dev->icclink->num_output, ets);
        }
    } else {
        if (pdf_dev->icclink == NULL) {
            code = gx_downscaler_init_ets(&ds, (gx_device *)dev, 8, bpc, num_comps,
                factor, mfs, NULL, /*&fax_adjusted_width*/ 0, /*aw*/ ets);
        } else {
            code = gx_downscaler_init_cm_ets(&ds, (gx_device *)dev, 8, bpc, num_comps,
                factor, mfs, NULL, /*&fax_adjusted_width*/ 0, /*aw*/ pdf_image_chunky_post_cm, pdf_dev->icclink,
                pdf_dev->icclink->num_output, ets);
        }
    }
    if (code < 0)
        return code;

    data = gs_alloc_bytes(dev->memory, max_size, "pdf_image_print_page(data)");
    if (data == NULL) {
        gx_downscaler_fin(&ds);
        return_error(gs_error_VMerror);
    }

    pprintd1(pdf_dev->strm, "%d 0 obj\n", page->ImageObjectNumber);
    pprintd1(pdf_dev->strm, "<<\n/Length %d 0 R\n", page->LengthObjectNumber);
    stream_puts(pdf_dev->strm, "/Subtype /Image\n");
    pprintd1(pdf_dev->strm, "/Width %d\n", width);
    pprintd1(pdf_dev->strm, "/Height %d\n", height);
    switch(num_comps) {
        case 1:
            stream_puts(pdf_dev->strm, "/ColorSpace /DeviceGray\n");
            stream_puts(pdf_dev->strm, "/BitsPerComponent 8\n");
            break;
        case 3:
            stream_puts(pdf_dev->strm, "/ColorSpace /DeviceRGB\n");
            stream_puts(pdf_dev->strm, "/BitsPerComponent 8\n");
            break;
        case 4:
            stream_puts(pdf_dev->strm, "/ColorSpace /DeviceCMYK\n");
            stream_puts(pdf_dev->strm, "/BitsPerComponent 8\n");
            break;
    }
    switch (pdf_dev->Compression) {
        case COMPRESSION_LZW:
            stream_puts(pdf_dev->strm, "/Filter /LZWDecode\n");
            stream_puts(pdf_dev->strm, ">>\nstream\n");
            stream_pos = stell(pdf_dev->strm);
            encode((gx_device *)pdf_dev, &pdf_dev->strm, &s_LZWE_template, pdf_dev->memory->non_gc_memory);
            break;
        case COMPRESSION_FLATE:
            stream_puts(pdf_dev->strm, "/Filter /FlateDecode\n");
            stream_puts(pdf_dev->strm, ">>\nstream\n");
            stream_pos = stell(pdf_dev->strm);
            encode((gx_device *)pdf_dev, &pdf_dev->strm, &s_zlibE_template, pdf_dev->memory->non_gc_memory);
            break;
        case COMPRESSION_JPEG:
            stream_puts(pdf_dev->strm, "/Filter /DCTDecode\n");
            stream_puts(pdf_dev->strm, ">>\nstream\n");
            stream_pos = stell(pdf_dev->strm);
            encode((gx_device *)pdf_dev, &pdf_dev->strm, &s_DCTE_template, pdf_dev->memory->non_gc_memory);
            break;
        case COMPRESSION_RLE:
            stream_puts(pdf_dev->strm, "/Filter /RunLengthDecode\n");
            stream_puts(pdf_dev->strm, ">>\nstream\n");
            stream_pos = stell(pdf_dev->strm);
            encode((gx_device *)pdf_dev, &pdf_dev->strm, &s_RLE_template, pdf_dev->memory->non_gc_memory);
            break;
        default:
        case COMPRESSION_NONE:
            stream_puts(pdf_dev->strm, ">>\nstream\n");
            stream_pos = stell(pdf_dev->strm);
            break;
    }


    for (row = 0; row < height && code >= 0; row++) {
        code = gx_downscaler_getbits(&ds, data, row);
        if (code < 0)
            break;
        stream_write(pdf_dev->strm, data, width * num_comps);
    }

    if (code < 0) {
        gs_free_object(dev->memory, data, "pdf_image_print_page(data)");
        gx_downscaler_fin(&ds);
        return code;;
    }

    switch(pdf_dev->Compression) {
        case COMPRESSION_LZW:
        case COMPRESSION_FLATE:
        case COMPRESSION_JPEG:
        case COMPRESSION_RLE:
            s_close_filters(&pdf_dev->strm, s);
            break;
        default:
        case COMPRESSION_NONE:
            break;
    }

    stream_puts(pdf_dev->strm, "\nendstream\nendobj\n");
    page->LengthOffset = stell(pdf_dev->strm);

    pprintd1(pdf_dev->strm, "%d 0 obj\n", page->LengthObjectNumber);
    pprintd1(pdf_dev->strm, "%d\n", page->LengthOffset - stream_pos - 18); /* 18 is the length of \nendstream\nendobj\n we need to take that off for the stream length */
    stream_puts(pdf_dev->strm, "endobj\n");

    page->PageStreamOffset = stell(pdf_dev->strm);
    pprintd1(pdf_dev->strm, "%d 0 obj\n", page->PageStreamObjectNumber);
    gs_sprintf(Buffer, "%f 0 0 %f 0 0 cm\n/Im1 Do", (width / (pdf_dev->HWResolution[0] / 72)) * factor, (height / (pdf_dev->HWResolution[1] / 72)) * factor);
    pprintd1(pdf_dev->strm, "<<\n/Length %d\n>>\nstream\n", strlen(Buffer));
    stream_puts(pdf_dev->strm, Buffer);
    stream_puts(pdf_dev->strm, "\nendstream\nendobj\n");

    page->PageDictOffset = stell(pdf_dev->strm);
    pprintd1(pdf_dev->strm, "%d 0 obj\n", page->PageDictObjectNumber);
    pprintd1(pdf_dev->strm, "<<\n/Contents %d 0 R\n", page->PageStreamObjectNumber);
    stream_puts(pdf_dev->strm, "/Type /Page\n/Parent 2 0 R\n");
    gs_sprintf(Buffer, "/MediaBox [0 0 %f %f]\n", ((double)pdf_dev->width / pdf_dev->HWResolution[0]) * 72, ((double)pdf_dev->height / pdf_dev->HWResolution[1]) * 72);
    stream_puts(pdf_dev->strm, Buffer);
    pprintd1(pdf_dev->strm, "/Resources <<\n/XObject <<\n/Im1 %d 0 R\n>>\n>>\n>>\n", page->ImageObjectNumber);
    stream_puts(pdf_dev->strm, "endobj\n");

    gx_downscaler_fin(&ds);
    gs_free_object(dev->memory, data, "pdf_image_print_page(data)");

    pdf_dev->NumPages++;
    return code;
}

static void write_fileID(stream *s, const byte *str, int size)
{
    const stream_template *templat;
    stream_AXE_state state;
    stream_state *st = NULL;

    templat = &s_AXE_template;
    st = (stream_state *) & state;
    s_AXE_init_inline(&state);
    stream_putc(s, '<');
    {
        byte buf[100];		/* size is arbitrary */
        stream_cursor_read r;
        stream_cursor_write w;
        int status;

        r.ptr = str - 1;
        r.limit = r.ptr + size;
        w.limit = buf + sizeof(buf) - 1;
        do {
            /* One picky compiler complains if we initialize to buf - 1. */
            w.ptr = buf;  w.ptr--;
            status = (*templat->process) (st, &r, &w, true);
            stream_write(s, buf, (uint) (w.ptr + 1 - buf));
        }
        while (status == 1);
    }
}

static int
pdf_compute_fileID(gx_device_pdf_image * pdev, byte fileID[16], char *CreationDate, char *Title, char *Producer)
{
    /* We compute a file identifier when beginning a document
       to allow its usage with PDF encryption. Due to that,
       in contradiction to the Adobe recommendation, our
       ID doesn't depend on the document size.
    */
    gs_memory_t *mem = pdev->memory->non_gc_memory;
    uint ignore;
    stream *s = s_MD5E_make_stream(mem, fileID, 16);
    long secs_ns[2];

    if (s == NULL)
        return_error(gs_error_VMerror);

    gp_get_usertime(secs_ns);
    sputs(s, (byte *)secs_ns, sizeof(secs_ns), &ignore);
    sputs(s, (const byte *)pdev->fname, strlen(pdev->fname), &ignore);

    stream_puts(s, "/ModDate ");
    stream_puts(s, CreationDate);
    stream_puts(s, "\n/CreationDate ");
    stream_puts(s, CreationDate);
    stream_puts(s, "\n/Title (");
    stream_puts(s, Title);
    stream_puts(s, ")\n/Producer (");
    stream_puts(s, Producer);
    stream_puts(s, ")\n");

    sclose(s);
    gs_free_object(mem, s, "pdf_compute_fileID");
    return 0;
}

static void write_xref_entry (stream *s, gs_offset_t Offset)
{
    char O[11];
    int i;

    if (Offset > 9999999999){
        Offset = 0;
    }
    gs_sprintf(O, "%d", Offset);
    for (i=0; i< (10 - strlen(O)); i++)
        stream_puts(s, "0");
    stream_puts(s, O);
    stream_puts(s, " 00000 n \n");
}

static void
pdf_store_default_Producer(char *buf)
{
    if ((gs_revision % 100) == 0)
        gs_sprintf(buf, "(%s %1.1f)", gs_product, gs_revision / 100.0);
    else
        gs_sprintf(buf, "(%s %1.2f)", gs_product, gs_revision / 100.0);
}

static int pdf_image_finish_file(gx_device_pdf_image *pdf_dev, int PCLm)
{
    pdfimage_page *page = pdf_dev->Pages;
    struct tm tms;
    time_t t;
    int timeoffset;
    char timesign;
    char CreationDate[26], Title[] = "Untitled", Producer[256];

    if (pdf_dev->strm != NULL) {
        byte fileID[16];

        pdf_store_default_Producer(Producer);

        pdf_dev->RootOffset = stell(pdf_dev->strm);
        stream_puts(pdf_dev->strm, "1 0 obj\n<<\n/Pages 2 0 R\n/Type /Catalog\n>>\nendobj\n");

        pdf_dev->PagesOffset = stell(pdf_dev->strm);
        pprintd1(pdf_dev->strm, "2 0 obj\n<<\n/Count %d\n", pdf_dev->NumPages);
        stream_puts(pdf_dev->strm, "/Kids [");

        while(page){
            pprintd1(pdf_dev->strm, "%d 0 R ", page->PageDictObjectNumber);
            page = page->next;
        }

        stream_puts(pdf_dev->strm, "]\n/Type /Pages\n>>\nendobj\n");

        time(&t);
        tms = *gmtime(&t);
        tms.tm_isdst = -1;
        timeoffset = (int)difftime(t, mktime(&tms)); /* tz+dst in seconds */
        timesign = (timeoffset == 0 ? 'Z' : timeoffset < 0 ? '-' : '+');
        timeoffset = any_abs(timeoffset) / 60;
        tms = *localtime(&t);

        gs_sprintf(CreationDate, "(D:%04d%02d%02d%02d%02d%02d%c%02d\'%02d\')",
            tms.tm_year + 1900, tms.tm_mon + 1, tms.tm_mday,
            tms.tm_hour, tms.tm_min, tms.tm_sec,
            timesign, timeoffset / 60, timeoffset % 60);

        pdf_dev->xrefOffset = stell(pdf_dev->strm);
        if (PCLm)
            pprintd1(pdf_dev->strm, "xref\n0 %d\n0000000000 65536 f \n", pdf_dev->NextObject);
        else
            pprintd1(pdf_dev->strm, "xref\n0 %d\n0000000000 65536 f \n", (pdf_dev->NumPages * 4) + 3);
        write_xref_entry(pdf_dev->strm, pdf_dev->RootOffset);
        write_xref_entry(pdf_dev->strm, pdf_dev->PagesOffset);

        if (!PCLm) {
            page = pdf_dev->Pages;
            while(page){
                write_xref_entry(pdf_dev->strm, page->ImageOffset);
                write_xref_entry(pdf_dev->strm, page->LengthOffset);
                write_xref_entry(pdf_dev->strm, page->PageStreamOffset);
                write_xref_entry(pdf_dev->strm, page->PageDictOffset);
                page = page->next;
            }
            pprintd1(pdf_dev->strm, "trailer\n<<\n/Size %d\n/Root 1 0 R\n/ID [", (pdf_dev->NumPages * 4) + 3);
            pdf_compute_fileID(pdf_dev, fileID, CreationDate, Title, Producer);
            write_fileID(pdf_dev->strm, (const byte *)&fileID, 16);
            write_fileID(pdf_dev->strm, (const byte *)&fileID, 16);
            pprintd1(pdf_dev->strm, "]\n>>\nstartxref\n%d\n%%%%EOF\n", pdf_dev->xrefOffset);
        } else {
            gs_offset_t streamsize, R = 0;
            char Buffer[1024];

            sflush(pdf_dev->xref_stream.strm);
            streamsize = gp_ftell_64(pdf_dev->xref_stream.file);
            if (gp_fseek_64(pdf_dev->xref_stream.file, 0, SEEK_SET) != 0)
                return_error(gs_error_ioerror);

            while(streamsize > 0) {
                if (streamsize > 1024) {
                    streamsize -= gp_fpread(Buffer, 1024, R, pdf_dev->xref_stream.file);
                    R += 1024;
                    stream_write(pdf_dev->strm, Buffer, 1024);
                }
                else {
                    gp_fpread(Buffer, streamsize, R, pdf_dev->xref_stream.file);
                    stream_write(pdf_dev->strm, Buffer, streamsize);
                    streamsize = 0;
                }
            }
            if (gp_fseek_64(pdf_dev->xref_stream.file, 0, SEEK_SET) != 0)
                return_error(gs_error_ioerror);

            pprintd1(pdf_dev->strm, "trailer\n<<\n/Size %d\n/Root 1 0 R\n/ID [", pdf_dev->NextObject);
            pdf_compute_fileID(pdf_dev, fileID, CreationDate, Title, Producer);
            write_fileID(pdf_dev->strm, (const byte *)&fileID, 16);
            write_fileID(pdf_dev->strm, (const byte *)&fileID, 16);
            pprintd1(pdf_dev->strm, "]\n>>\nstartxref\n%d\n%%%%EOF\n", pdf_dev->xrefOffset);
        }

        sflush(pdf_dev->strm);
        pdf_dev->strm->file = NULL; /* Don't close underlying file when we free the stream */
        gs_free_object(pdf_dev->memory->non_gc_memory, pdf_dev->strm, "pdfimage_close(strm)");
        pdf_dev->strm = 0;
        gs_free_object(pdf_dev->memory->non_gc_memory, pdf_dev->strm_buf, "pdfimage_close(strmbuf)");
        pdf_dev->strm_buf = 0;
    }
    if (pdf_dev->Pages) {
        pdfimage_page *p = pdf_dev->Pages, *n;
        do {
            n = p->next;
            gs_free_object(pdf_dev->memory->non_gc_memory, p,
                           "pdfimage free a page");
            p = n;
        }while (p);
        pdf_dev->Pages = NULL;
        pdf_dev->NumPages = 0;
    }
    if (pdf_dev->icclink != NULL)
    {
        pdf_dev->icclink->procs.free_link(pdf_dev->icclink);
        gsicc_free_link_dev(pdf_dev->memory, pdf_dev->icclink);
        pdf_dev->icclink = NULL;
    }
    pdf_dev->RootOffset = 0;
    pdf_dev->PagesOffset = 0;
    pdf_dev->xrefOffset = 0;
    if (!PCLm)
        pdf_dev->StripHeight = 0;
    else
        pdf_dev->NextObject = 0;
    return 0;
}

static int
pdf_image_print_page(gx_device_printer * pdev, FILE * file)
{
    gx_device_pdf_image *const pdf_dev = (gx_device_pdf_image *)pdev;
    const char *fmt;
    gs_parsed_file_name_t parsed;
    int code;

    code = gdev_pdf_image_begin_page(pdf_dev, file);
    if (code < 0)
        return code;

    code = pdf_image_downscale_and_print_page(pdev,
                                         pdf_dev->downscale.downscale_factor,
                                         pdf_dev->downscale.min_feature_size,
                                         8, pdf_dev->color_info.num_components,
                                         0, 0, NULL,
                                         pdf_dev->downscale.ets);
    if (code < 0)
        return code;

    code = gx_parse_output_file_name(&parsed, &fmt, pdev->fname,
                                         strlen(pdev->fname), pdev->memory);

    if (code >= 0 && fmt) {
        code = pdf_image_finish_file(pdf_dev, 0);
    }
    return code;
}

int
pdf_image_open(gx_device *pdev)
{
    gx_device_pdf_image *ppdev = (gx_device_pdf_image *)pdev;
    int code;
    bool update_procs = false;

    code = install_internal_subclass_devices((gx_device **)&pdev, &update_procs);
    if (code < 0)
        return code;
    /* If we've been subclassed, find the terminal device */
    while(pdev->child)
        pdev = pdev->child;

    ppdev->file = NULL;
    ppdev->Pages = NULL;
    ppdev->NumPages = 0;
    ppdev->RootOffset = 0;
    ppdev->PagesOffset = 0;
    ppdev->xrefOffset = 0;
    ppdev->StripHeight = 0;
    code = gdev_prn_allocate_memory(pdev, NULL, 0, 0);
    if (code < 0)
        return code;
    if (update_procs) {
        if (pdev->ObjectHandlerPushed) {
            gx_copy_device_procs(pdev->parent, pdev, &gs_obj_filter_device);
            pdev = pdev->parent;
        }
        if (pdev->PageHandlerPushed)
            gx_copy_device_procs(pdev->parent, pdev, &gs_flp_device);
    }
    if (ppdev->OpenOutputFile)
        code = gdev_prn_open_printer_seekable(pdev, 1, true);
    return code;
}

int
pdf_image_close(gx_device * pdev)
{
    gx_device_pdf_image *const pdf_dev = (gx_device_pdf_image *)pdev;
    int code;

    code = pdf_image_finish_file(pdf_dev, 0);
    if (code < 0)
        return code;

    return gdev_prn_close(pdev);
}

static int
pdf_image_compression_param_string(gs_param_string *param, unsigned char id)
{
    struct compression_string *c;
    for (c = compression_strings; c->str; c++)
        if (id == c->id) {
            param_string_from_string(*param, c->str);
            return 0;
        }
    return_error(gs_error_undefined);
}

static int
pdf_image_compression_id(unsigned char *id, gs_param_string *param)
{
    struct compression_string *c;
    for (c = compression_strings; c->str; c++)
        if (!bytes_compare(param->data, param->size,
                           (const byte *)c->str, strlen(c->str)))
        {
            *id = c->id;
            return 0;
        }
    return_error(gs_error_undefined);
}

static int
pdf_image_get_some_params(gx_device * dev, gs_param_list * plist, int which)
{
    gx_device_pdf_image *const pdf_dev = (gx_device_pdf_image *)dev;
    int code = gdev_prn_get_params(dev, plist);
    int ecode = code;
    gs_param_string comprstr;

    if (ecode < 0)
        return ecode;

    ecode = param_write_int(plist, "StripHeight", &pdf_dev->StripHeight);
    if (ecode < 0)
        return ecode;
    ecode = param_write_int(plist, "JPEGQ", &pdf_dev->JPEGQ);
    if (ecode < 0)
        return ecode;
    ecode = param_write_float(plist, "QFactor", &pdf_dev->QFactor);
    if (ecode < 0)
        return ecode;

    if ((code = pdf_image_compression_param_string(&comprstr, pdf_dev->Compression)) < 0 ||
        (code = param_write_string(plist, "Compression", &comprstr)) < 0)
        ecode = code;
    if (which & 1) {
        if ((code = gx_downscaler_write_params(plist, &pdf_dev->downscale,
                                               GX_DOWNSCALER_PARAMS_MFS |
                                               (which & 2 ? GX_DOWNSCALER_PARAMS_TRAP : 0) |
                                               (which & 4 ? GX_DOWNSCALER_PARAMS_ETS : 0))) < 0)
            ecode = code;
    }
    return ecode;
}

int
pdf_image_get_params_downscale(gx_device * dev, gs_param_list * plist)
{
    return pdf_image_get_some_params(dev, plist, 1);
}

int
pdf_image_get_params_downscale_cmyk(gx_device * dev, gs_param_list * plist)
{
    return pdf_image_get_some_params(dev, plist, 3);
}

int
pdf_image_get_params_downscale_cmyk_ets(gx_device * dev, gs_param_list * plist)
{
    return pdf_image_get_some_params(dev, plist, 7);
}

static int
pdf_image_put_some_params(gx_device * dev, gs_param_list * plist, int which)
{
    gx_device_pdf_image *const pdf_dev = (gx_device_pdf_image *)dev;
    int ecode = 0;
    int code;
    unsigned char compr = pdf_dev->Compression;
    gs_param_string comprstr;
    const char *param_name;

    code = param_read_int(plist, param_name = "StripHeight", &pdf_dev->StripHeight);
    if (code < 0) {
        errprintf(pdf_dev->memory, "Invalid StripHeight setting\n");
        param_signal_error(plist, param_name, ecode);
        return code;
    }
    code = param_read_int(plist, param_name = "JPEGQ", &pdf_dev->JPEGQ);
    if (code < 0) {
        errprintf(pdf_dev->memory, "Invalid JPEQG setting\n");
        param_signal_error(plist, param_name, ecode);
        return code;
    }
    code = param_read_float(plist, param_name = "QFactor", &pdf_dev->QFactor);
    if (code < 0) {
        errprintf(pdf_dev->memory, "Invalid QFactor setting\n");
        param_signal_error(plist, param_name, ecode);
        return code;
    }
    /* Read Compression */
    switch (code = param_read_string(plist, (param_name = "Compression"), &comprstr)) {
        case 0:
            if ((ecode = pdf_image_compression_id(&compr, &comprstr)) < 0) {

                errprintf(pdf_dev->memory, "Unknown compression setting\n");
                param_signal_error(plist, param_name, ecode);
                return ecode;
            }
            pdf_dev->Compression = compr;
            break;
        case 1:
            break;
        default:
            ecode = code;
            param_signal_error(plist, param_name, ecode);
    }
    if (which & 1)
    {
        code = gx_downscaler_read_params(plist, &pdf_dev->downscale,
                                         (GX_DOWNSCALER_PARAMS_MFS |
                                          (which & 2 ? GX_DOWNSCALER_PARAMS_TRAP : 0) |
                                          (which & 4 ? GX_DOWNSCALER_PARAMS_ETS : 0)));
        if (code < 0)
        {
            ecode = code;
            param_signal_error(plist, param_name, ecode);
        }
    }
    if (ecode < 0)
        return ecode;
    code = gdev_prn_put_params(dev, plist);
    if (code < 0)
        return code;

    return code;
}

int
pdf_image_put_params_downscale(gx_device * dev, gs_param_list * plist)
{
    return pdf_image_put_some_params(dev, plist, 1);
}

int
pdf_image_put_params_downscale_cmyk(gx_device * dev, gs_param_list * plist)
{
    return pdf_image_put_some_params(dev, plist, 3);
}

int
pdf_image_put_params_downscale_cmyk_ets(gx_device * dev, gs_param_list * plist)
{
    return pdf_image_put_some_params(dev, plist, 7);
}

/* ------ The PCLm device ------ */

static dev_proc_print_page(PCLm_print_page);

static const gx_device_procs PCLm_procs =
prn_color_params_procs(PCLm_open,
                       gdev_prn_output_page_seekable,
                       PCLm_close,
                       gx_default_rgb_map_rgb_color,
                       gx_default_rgb_map_color_rgb,
                       pdf_image_get_params_downscale,
                       pdf_image_put_params_downscale);

const gx_device_pdf_image gs_PCLm_device = {
    prn_device_body(gx_device_pdf_image,
                    PCLm_procs,
                    "pclm",
                    DEFAULT_WIDTH_10THS, DEFAULT_HEIGHT_10THS,
                    600, 600,   /* 600 dpi by default */
                    0, 0, 0, 0, /* Margins */
                    3,          /* num components */
                    24,         /* bits per sample */
                    255, 255, 256, 256,
                    PCLm_print_page),
                    3,
    GX_DOWNSCALER_PARAMS_DEFAULTS,
    16, /* StripHeight */
    0.0, /* QFactor */
    0  /* JPEGQ */
};

/* Open a temporary file, with or without a stream. */
static int
PCLm_open_temp_file(gx_device_pdf_image *pdev, PCLm_temp_file_t *ptf)
{
    char fmode[4];

    if (strlen(gp_fmode_binary_suffix) > 2)
        return_error(gs_error_invalidfileaccess);

    strcpy(fmode, "w+");
    strcat(fmode, gp_fmode_binary_suffix);
    ptf->file =	gp_open_scratch_file_64(pdev->memory,
                                     gp_scratch_file_name_prefix,
                                     ptf->file_name,
                                     fmode);
    if (ptf->file == 0)
        return_error(gs_error_invalidfileaccess);
    return 0;
}
/* Close and remove temporary files. */
static int
PCLm_close_temp_file(gx_device_pdf_image *pdev, PCLm_temp_file_t *ptf, int code)
{
    int err = 0;
    FILE *file = ptf->file;

    /*
     * ptf->strm == 0 or ptf->file == 0 is only possible if this procedure
     * is called to clean up during initialization failure, but ptf->strm
     * might not be open if it was finalized before the device was closed.
     */
    if (ptf->strm) {
        if (s_is_valid(ptf->strm)) {
            sflush(ptf->strm);
            /* Prevent freeing the stream from closing the file. */
            ptf->strm->file = 0;
        } else
            ptf->file = file = 0;	/* file was closed by finalization */
        gs_free_object(pdev->memory->non_gc_memory, ptf->strm_buf,
                       "pdf_close_temp_file(strm_buf)");
        ptf->strm_buf = 0;
        gs_free_object(pdev->memory->non_gc_memory, ptf->strm,
                       "pdf_close_temp_file(strm)");
        ptf->strm = 0;
    }
    if (file) {
        err = ferror(file) | fclose(file);
        unlink(ptf->file_name);
        ptf->file = 0;
    }
    return
        (code < 0 ? code : err != 0 ? gs_note_error(gs_error_ioerror) : code);
}

static int
PCLm_open_temp_stream(gx_device_pdf_image *pdev, PCLm_temp_file_t *ptf)
{
    int code = PCLm_open_temp_file(pdev, ptf);

    if (code < 0)
        return code;
    ptf->strm = s_alloc(pdev->memory->non_gc_memory, "pdf_open_temp_stream(strm)");
    if (ptf->strm == 0) {
        PCLm_close_temp_file(pdev, ptf, 0);
        return_error(gs_error_VMerror);
    }
    ptf->strm_buf = gs_alloc_bytes(pdev->memory->non_gc_memory, 512,
                                   "pdf_open_temp_stream(strm_buf)");
    if (ptf->strm_buf == 0) {
        gs_free_object(pdev->memory->non_gc_memory, ptf->strm,
                       "pdf_open_temp_stream(strm)");
        ptf->strm = 0;
        PCLm_close_temp_file(pdev, ptf, 0);
        return_error(gs_error_VMerror);
    }
    swrite_file(ptf->strm, ptf->file, ptf->strm_buf, 512);
    return 0;
}
int
PCLm_open(gx_device *pdev)
{
    gx_device_pdf_image *ppdev = (gx_device_pdf_image *)pdev;
    int code;
    bool update_procs = false;

    code = install_internal_subclass_devices((gx_device **)&pdev, &update_procs);
    if (code < 0)
        return code;
    /* If we've been subclassed, find the terminal device */
    while(pdev->child)
        pdev = pdev->child;

    ppdev->file = NULL;
    ppdev->Pages = NULL;
    ppdev->NumPages = 0;
    ppdev->RootOffset = 0;
    ppdev->PagesOffset = 0;
    ppdev->xrefOffset = 0;
    ppdev->NextObject = 0;
    code = gdev_prn_allocate_memory(pdev, NULL, 0, 0);
    if (code < 0)
        return code;
    if (update_procs) {
        if (pdev->ObjectHandlerPushed) {
            gx_copy_device_procs(pdev->parent, pdev, &gs_obj_filter_device);
            pdev = pdev->parent;
        }
        if (pdev->PageHandlerPushed)
            gx_copy_device_procs(pdev->parent, pdev, &gs_flp_device);
    }
    if (ppdev->OpenOutputFile)
        code = gdev_prn_open_printer_seekable(pdev, 1, true);

    if(code < 0)
        return code;

    code = PCLm_open_temp_stream(ppdev, &ppdev->xref_stream);
    if (code < 0)
        return code;

    code = PCLm_open_temp_stream(ppdev, &ppdev->temp_stream);
    if (code < 0)
        PCLm_close_temp_file(ppdev, &ppdev->xref_stream, 0);
    return code;
}

static int gdev_PCLm_begin_page(gx_device_pdf_image *pdf_dev,
                         FILE *file)
{
    gx_device_printer *const pdev = (gx_device_printer *)pdf_dev;
    cmm_dev_profile_t *profile_struct;
    gsicc_rendering_param_t rendering_params;
    int code;
    pdfimage_page *page;

    page = (pdfimage_page *)gs_alloc_bytes(pdf_dev->memory->non_gc_memory, sizeof(pdfimage_page), "pdfimage create new page");
    if (page == NULL)
        return_error(gs_error_VMerror);

    memset(page, 0x00, sizeof(pdfimage_page));

    if (gdev_prn_file_is_new(pdev)) {
        /* Set up the icc link settings at this time */
        code = dev_proc(pdev, get_profile)((gx_device *)pdev, &profile_struct);
        if (code < 0)
            return_error(gs_error_undefined);
        if (profile_struct->postren_profile != NULL) {
            rendering_params.black_point_comp = gsBLACKPTCOMP_ON;
            rendering_params.graphics_type_tag = GS_UNKNOWN_TAG;
            rendering_params.override_icc = false;
            rendering_params.preserve_black = gsBLACKPRESERVE_OFF;
            rendering_params.rendering_intent = gsRELATIVECOLORIMETRIC;
            rendering_params.cmm = gsCMM_DEFAULT;
            if (profile_struct->oi_profile != NULL) {
                pdf_dev->icclink = gsicc_alloc_link_dev(pdev->memory,
                    profile_struct->oi_profile, profile_struct->postren_profile,
                    &rendering_params);
            } else if (profile_struct->link_profile != NULL) {
                pdf_dev->icclink = gsicc_alloc_link_dev(pdev->memory,
                    profile_struct->link_profile, profile_struct->postren_profile,
                    &rendering_params);
            } else {
                pdf_dev->icclink = gsicc_alloc_link_dev(pdev->memory,
                    profile_struct->device_profile[0], profile_struct->postren_profile,
                    &rendering_params);
            }
            /* If it is identity, release it now and set link to NULL */
            if (pdf_dev->icclink->is_identity) {
                pdf_dev->icclink->procs.free_link(pdf_dev->icclink);
                gsicc_free_link_dev(pdev->memory, pdf_dev->icclink);
                pdf_dev->icclink = NULL;
            }
        }

        /* Set up the stream and insert the file header */
        pdf_dev->strm = s_alloc(pdf_dev->memory->non_gc_memory, "pdfimage_open_temp_stream(strm)");
        if (pdf_dev->strm == 0)
            return_error(gs_error_VMerror);
        pdf_dev->strm_buf = gs_alloc_bytes(pdf_dev->memory->non_gc_memory, 512,
                                       "pdfimage_open_temp_stream(strm_buf)");
        if (pdf_dev->strm_buf == 0) {
            pdf_dev->strm->file = NULL; /* Don't close underlying file when we free the stream */
            gs_free_object(pdf_dev->memory->non_gc_memory, pdf_dev->strm,
                           "pdfimage_open_temp_stream(strm)");
            pdf_dev->strm = 0;
            return_error(gs_error_VMerror);
        }
        swrite_file(pdf_dev->strm, pdf_dev->file, pdf_dev->strm_buf, 512);

        stream_puts(pdf_dev->strm, "%PDF-1.3\n");
        stream_puts(pdf_dev->strm, "%PCLm 1.0\n");
        pdf_dev->Pages = page;
        pdf_dev->NextObject = 3;
    } else {
        pdfimage_page *current = pdf_dev->Pages;
        while(current->next)
            current = current->next;
        current->next = page;
    }
    page->PageDictObjectNumber = pdf_dev->NextObject++;
    page->PageStreamObjectNumber = pdf_dev->NextObject++;
    page->ImageObjectNumber = pdf_dev->NextObject++;

    return 0;
}

static int
PCLm_downscale_and_print_page(gx_device_printer *dev, int factor,
                              int mfs, int bpc, int num_comps,
                              int trap_w, int trap_h, const int *trap_order,
                              int ets)
{
    gx_device_pdf_image *const pdf_dev = (gx_device_pdf_image *)dev;
    int code = 0;
    uint Read;
    byte *data = NULL;
    int size = gdev_mem_bytes_per_scan_line((gx_device *)dev);
    int max_size =size;
    int row, NumStrips;
    double StripDecrement;
    int height = dev->height/factor;
    int width = dev->width/factor;
    gx_downscaler_t ds;
    gs_offset_t stream_pos = 0;
    pdfimage_page *page = pdf_dev->Pages;
    char Buffer[1024];

    if (page == NULL)
        return_error(gs_error_undefined);

    while(page->next)
        page = page->next;

    if (pdf_dev->icclink == NULL) {
        code = gx_downscaler_init_ets(&ds, (gx_device *)dev, 8, bpc, num_comps,
            factor, mfs, NULL, /*&fax_adjusted_width*/ 0, /*aw*/ ets);
    } else {
        code = gx_downscaler_init_cm_ets(&ds, (gx_device *)dev, 8, bpc, num_comps,
            factor, mfs, NULL, /*&fax_adjusted_width*/ 0, /*aw*/ pdf_image_chunky_post_cm, pdf_dev->icclink,
            pdf_dev->icclink->num_output, ets);
    }
    if (code < 0)
        return code;

    data = gs_alloc_bytes(dev->memory, max_size, "pdf_image_print_page(data)");
    if (data == NULL) {
        gx_downscaler_fin(&ds);
        return_error(gs_error_VMerror);
    }

    if (pdf_dev->StripHeight == 0) {
        NumStrips = 1;
        pdf_dev->StripHeight = pdf_dev->height;
    }
    else
        NumStrips = (int)ceil((float)height / pdf_dev->StripHeight);

    page->PageDictOffset = stell(pdf_dev->strm);
    write_xref_entry(pdf_dev->xref_stream.strm, page->PageDictOffset);
    pprintd1(pdf_dev->strm, "%d 0 obj\n", page->PageDictObjectNumber);
    pprintd1(pdf_dev->strm, "<<\n/Contents %d 0 R\n", page->PageStreamObjectNumber);
    stream_puts(pdf_dev->strm, "/Type /Page\n/Parent 2 0 R\n");
    gs_sprintf(Buffer, "/MediaBox [0 0 %f %f]\n", ((double)pdf_dev->width / pdf_dev->HWResolution[0]) * 72, ((double)pdf_dev->height / pdf_dev->HWResolution[1]) * 72);
    stream_puts(pdf_dev->strm, Buffer);
    stream_puts(pdf_dev->strm, "/Resources <<\n/XObject <<\n");

    if (gp_fseek_64(pdf_dev->temp_stream.file, 0, SEEK_SET) != 0) {
        gs_free_object(dev->memory, data, "pdf_image_print_page(data)");
        gx_downscaler_fin(&ds);
        return_error(gs_error_ioerror);
    }

    StripDecrement = pdf_dev->StripHeight / (pdf_dev->HWResolution[1] / (factor * 72));

    for (row = 0; row < NumStrips;row++) {
        stream_puts(pdf_dev->temp_stream.strm, "/P <</MCID 0>> BDC q\n");
        /* Special handling for the last strip, potentially shorter than all the previous ones */
        if (row == NumStrips - 1) {
            double adjusted;
            adjusted = height - (row * pdf_dev->StripHeight);
            adjusted = adjusted / (pdf_dev->HWResolution[1] / (factor * 72));
            gs_sprintf(Buffer, "%f 0 0 %f 0 0 cm\n/Im%d Do Q\n", (width / (pdf_dev->HWResolution[0] / 72)) * factor, adjusted, row);
        } else
            gs_sprintf(Buffer, "%f 0 0 %f 0 %f cm\n/Im%d Do Q\n", (width / (pdf_dev->HWResolution[0] / 72)) * factor, StripDecrement, ((height / (pdf_dev->HWResolution[1] / 72)) * factor) - (StripDecrement * (row + 1)), row);
        stream_puts(pdf_dev->temp_stream.strm, Buffer);
        pprintd2(pdf_dev->strm, "/Im%d %d 0 R\n", row, page->ImageObjectNumber + (row * 2));
    }
    sflush(pdf_dev->temp_stream.strm);
    stream_pos = gp_ftell_64(pdf_dev->temp_stream.file);
    stream_puts(pdf_dev->strm, ">>\n>>\n>>\nendobj\n");

    page->PageStreamOffset = stell(pdf_dev->strm);
    write_xref_entry(pdf_dev->xref_stream.strm, page->PageStreamOffset);
    pprintd1(pdf_dev->strm, "%d 0 obj\n", page->PageStreamObjectNumber);
    pprintd1(pdf_dev->strm, "<<\n/Length %d\n>>\nstream\n", stream_pos);

    if (gp_fseek_64(pdf_dev->temp_stream.file, 0, SEEK_SET) != 0) {
        gs_free_object(dev->memory, data, "pdf_image_print_page(data)");
        gx_downscaler_fin(&ds);
        return_error(gs_error_ioerror);
    }

    Read = 0;
    while(stream_pos > 0) {
        if (stream_pos > 1024) {
            stream_pos -= gp_fpread(Buffer, 1024, Read, pdf_dev->temp_stream.file);
            Read += 1024;
            stream_write(pdf_dev->strm, Buffer, 1024);
        }
        else {
            gp_fpread(Buffer, stream_pos, Read, pdf_dev->temp_stream.file);
            stream_write(pdf_dev->strm, Buffer, stream_pos);
            stream_pos = 0;
        }
    }

    stream_puts(pdf_dev->strm, "endstream\nendobj\n");

    if (gp_fseek_64(pdf_dev->temp_stream.file, 0, SEEK_SET) != 0) {
        gs_free_object(dev->memory, data, "pdf_image_print_page(data)");
        gx_downscaler_fin(&ds);
        return_error(gs_error_ioerror);
    }

    pdf_dev->temp_stream.save = pdf_dev->temp_stream.strm;
    switch (pdf_dev->Compression) {
        case COMPRESSION_FLATE:
            encode((gx_device *)pdf_dev, &pdf_dev->temp_stream.strm, &s_zlibE_template, pdf_dev->memory->non_gc_memory);
            break;
        case COMPRESSION_JPEG:
            encode((gx_device *)pdf_dev, &pdf_dev->temp_stream.strm, &s_DCTE_template, pdf_dev->memory->non_gc_memory);
            break;
        case COMPRESSION_RLE:
            encode((gx_device *)pdf_dev, &pdf_dev->temp_stream.strm, &s_RLE_template, pdf_dev->memory->non_gc_memory);
            break;
        default:
            break;
    }

    Read = 0;
    for (row = 0; row < height;row++) {
        code = gx_downscaler_getbits(&ds, data, row);
        if (code < 0)
            break;
        stream_write(pdf_dev->temp_stream.strm, data, width * num_comps);
        Read++;
        if (Read == pdf_dev->StripHeight) {
            uint R = 0;

            if (pdf_dev->temp_stream.save != pdf_dev->temp_stream.strm)
                s_close_filters(&pdf_dev->temp_stream.strm, pdf_dev->temp_stream.save);
            sflush(pdf_dev->temp_stream.strm);
            stream_pos = gp_ftell_64(pdf_dev->temp_stream.file);

            page->ImageOffset = stell(pdf_dev->strm);
            write_xref_entry(pdf_dev->xref_stream.strm, page->ImageOffset);
            pprintd1(pdf_dev->strm, "%d 0 obj\n", page->ImageObjectNumber++);
            pprintd1(pdf_dev->strm, "<<\n/Length %d\n", stream_pos);
            stream_puts(pdf_dev->strm, "/Subtype /Image\n");
            pprintd1(pdf_dev->strm, "/Width %d\n", width);
            pprintd1(pdf_dev->strm, "/Height %d\n", Read);
            stream_puts(pdf_dev->strm, "/ColorSpace /DeviceRGB\n");
            stream_puts(pdf_dev->strm, "/BitsPerComponent 8\n");
            switch (pdf_dev->Compression) {
                case COMPRESSION_FLATE:
                    stream_puts(pdf_dev->strm, "/Filter /FlateDecode\n");
                    stream_puts(pdf_dev->strm, ">>\nstream\n");
                    break;
                case COMPRESSION_JPEG:
                    stream_puts(pdf_dev->strm, "/Filter /DCTDecode\n");
                    stream_puts(pdf_dev->strm, ">>\nstream\n");
                    break;
                case COMPRESSION_RLE:
                    stream_puts(pdf_dev->strm, "/Filter /RunLengthDecode\n");
                    stream_puts(pdf_dev->strm, ">>\nstream\n");
                    break;
                default:
                case COMPRESSION_LZW:
                case COMPRESSION_NONE:
                    stream_puts(pdf_dev->strm, ">>\nstream\n");
                    break;
            }
            if (gp_fseek_64(pdf_dev->temp_stream.file, 0, SEEK_SET) != 0) {
                gs_free_object(dev->memory, data, "pdf_image_print_page(data)");
                gx_downscaler_fin(&ds);
                return_error(gs_error_ioerror);
            }

            while(stream_pos > 0) {
                if (stream_pos > 1024) {
                    stream_pos -= gp_fpread(Buffer, 1024, R, pdf_dev->temp_stream.file);
                    R += 1024;
                    stream_write(pdf_dev->strm, Buffer, 1024);
                }
                else {
                    memset(Buffer, 0xf5, 1024);
                    gp_fpread(Buffer, stream_pos, R, pdf_dev->temp_stream.file);
                    stream_write(pdf_dev->strm, Buffer, stream_pos);
                    stream_pos = 0;
                }
            }

            stream_puts(pdf_dev->strm, "\nendstream\nendobj\n");

            if (gp_fseek_64(pdf_dev->temp_stream.file, 0, SEEK_SET) != 0) {
                gs_free_object(dev->memory, data, "pdf_image_print_page(data)");
                gx_downscaler_fin(&ds);
                return_error(gs_error_ioerror);
            }

            switch (pdf_dev->Compression) {
                case COMPRESSION_FLATE:
                    encode((gx_device *)pdf_dev, &pdf_dev->temp_stream.strm, &s_zlibE_template, pdf_dev->memory->non_gc_memory);
                    break;
                case COMPRESSION_JPEG:
                    {
                        int t = pdf_dev->StripHeight;

                        if (height - (row + 1) < pdf_dev->StripHeight)
                            pdf_dev->StripHeight = height - (row + 1);
                        encode((gx_device *)pdf_dev, &pdf_dev->temp_stream.strm, &s_DCTE_template, pdf_dev->memory->non_gc_memory);
                        pdf_dev->StripHeight = t;
                    }
                    break;
                case COMPRESSION_RLE:
                    encode((gx_device *)pdf_dev, &pdf_dev->temp_stream.strm, &s_RLE_template, pdf_dev->memory->non_gc_memory);
                    break;
                default:
                    break;
            }
            Read = 0;

            page->ImageOffset = stell(pdf_dev->strm);
            write_xref_entry(pdf_dev->xref_stream.strm, page->ImageOffset);
            pprintd1(pdf_dev->strm, "%d 0 obj\n", page->ImageObjectNumber++);
            stream_puts(pdf_dev->strm, "<</Length 14>>\nstream\nq /image Do Q\nendstream\nendobj\n");
        }
    }

    if (code < 0) {
        gs_free_object(dev->memory, data, "pdf_image_print_page(data)");
        gx_downscaler_fin(&ds);
        return code;
    }

    if (Read) {
        uint R = 0, saved = pdf_dev->StripHeight;

        pdf_dev->StripHeight = Read;
        if (pdf_dev->temp_stream.save != pdf_dev->temp_stream.strm)
            s_close_filters(&pdf_dev->temp_stream.strm, pdf_dev->temp_stream.save);
        sflush(pdf_dev->temp_stream.strm);
        stream_pos = gp_ftell_64(pdf_dev->temp_stream.file);

        page->ImageOffset = stell(pdf_dev->strm);
        write_xref_entry(pdf_dev->xref_stream.strm, page->ImageOffset);
        pprintd1(pdf_dev->strm, "%d 0 obj\n", page->ImageObjectNumber++);
        pprintd1(pdf_dev->strm, "<<\n/Length %d\n", stream_pos);
        stream_puts(pdf_dev->strm, "/Subtype /Image\n");
        pprintd1(pdf_dev->strm, "/Width %d\n", width);
        pprintd1(pdf_dev->strm, "/Height %d\n", Read);
        stream_puts(pdf_dev->strm, "/ColorSpace /DeviceRGB\n");
        stream_puts(pdf_dev->strm, "/BitsPerComponent 8\n");
        switch (pdf_dev->Compression) {
            case COMPRESSION_FLATE:
                stream_puts(pdf_dev->strm, "/Filter /FlateDecode\n");
                stream_puts(pdf_dev->strm, ">>\nstream\n");
                break;
            default:
            case COMPRESSION_JPEG:
                stream_puts(pdf_dev->strm, "/Filter /DCTDecode\n");
                stream_puts(pdf_dev->strm, ">>\nstream\n");
                break;
            case COMPRESSION_RLE:
                stream_puts(pdf_dev->strm, "/Filter /RunLengthDecode\n");
                stream_puts(pdf_dev->strm, ">>\nstream\n");
                break;
            case COMPRESSION_LZW:
            case COMPRESSION_NONE:
                stream_puts(pdf_dev->strm, ">>\nstream\n");
                break;
        }
        if (gp_fseek_64(pdf_dev->temp_stream.file, 0, SEEK_SET) != 0) {
            gs_free_object(dev->memory, data, "pdf_image_print_page(data)");
            gx_downscaler_fin(&ds);
            return_error(gs_error_ioerror);
        }

        while(stream_pos > 0) {
            if (stream_pos > 1024) {
                stream_pos -= gp_fpread(Buffer, 1024, R, pdf_dev->temp_stream.file);
                R += 1024;
                stream_write(pdf_dev->strm, Buffer, 1024);
            }
            else {
                gp_fpread(Buffer, stream_pos, R, pdf_dev->temp_stream.file);
                stream_write(pdf_dev->strm, Buffer, stream_pos);
                stream_pos = 0;
            }
        }

        stream_puts(pdf_dev->strm, "\nendstream\nendobj\n");

        if (gp_fseek_64(pdf_dev->temp_stream.file, 0, SEEK_SET) != 0) {
            gs_free_object(dev->memory, data, "pdf_image_print_page(data)");
            gx_downscaler_fin(&ds);
            return_error(gs_error_ioerror);
        }
        page->ImageOffset = stell(pdf_dev->strm);
        write_xref_entry(pdf_dev->xref_stream.strm, page->ImageOffset);
        pprintd1(pdf_dev->strm, "%d 0 obj\n", page->ImageObjectNumber++);
        stream_puts(pdf_dev->strm, "<</Length 14>>\nstream\nq /image Do Q\nendstream\nendobj\n");
        pdf_dev->StripHeight = saved;
    }
    pdf_dev->NextObject = page->ImageObjectNumber;

    gx_downscaler_fin(&ds);
    gs_free_object(dev->memory, data, "pdf_image_print_page(data)");

    pdf_dev->NumPages++;
    return code;
}

static int
PCLm_print_page(gx_device_printer * pdev, FILE * file)
{
    gx_device_pdf_image *const pdf_dev = (gx_device_pdf_image *)pdev;
    const char *fmt;
    gs_parsed_file_name_t parsed;
    int code;

    code = gdev_PCLm_begin_page(pdf_dev, file);
    if (code < 0)
        return code;

    code = PCLm_downscale_and_print_page(pdev,
                                         pdf_dev->downscale.downscale_factor,
                                         pdf_dev->downscale.min_feature_size,
                                         8, pdf_dev->color_info.num_components,
                                         0, 0, NULL,
                                         pdf_dev->downscale.ets);
    if (code < 0)
        return code;

    code = gx_parse_output_file_name(&parsed, &fmt, pdev->fname,
                                         strlen(pdev->fname), pdev->memory);

    if (code >= 0 && fmt) {
        code = pdf_image_finish_file(pdf_dev, 1);
    }
    return code;
}

int
PCLm_close(gx_device * pdev)
{
    gx_device_pdf_image *const pdf_dev = (gx_device_pdf_image *)pdev;
    int code, code1;

    code = pdf_image_finish_file(pdf_dev, 1);
    if (code < 0)
        return code;

    code = PCLm_close_temp_file(pdf_dev, &pdf_dev->xref_stream, 0);
    code1 = PCLm_close_temp_file(pdf_dev, &pdf_dev->temp_stream, 0);
    if (code == 0)
        code = code1;
    code1 = gdev_prn_close(pdev);
    if (code == 0)
        code = code1;
    return code;
}
