/*
 * Copyright (C) 2016 The Android Open Source Project
 * Copyright (C) 2016 Mopria Alliance, Inc.
 * Copyright (C) 2013 Hewlett-Packard Development Company, L.P.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "../../media.h"
#include <PCLmGenerator.h>

#include <assert.h>
#include <math.h>
#include <zlib.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <genPCLm.h>

#define TAG "genPCLm"

#define STRIP_HEIGHT 16
#define JPEG_QUALITY 100
#define TEMP_BUFF_SIZE 10000000
#define DEFAULT_OUTBUFF_SIZE 64*5120*3*10
#define STANDARD_SCALE_FOR_PDF 72.0
#define KID_STRING_SIZE 1000
#define CATALOG_OBJ_NUMBER 1
#define PAGES_OBJ_NUMBER   2
#define ADOBE_RGB_SIZE 284

#define rgb_2_gray(r, g, b) (ubyte)(0.299*(double)r+0.587*(double)g+0.114*(double)b)

static PCLmSUserSettingsType PCLmSSettings;

/*
 * Shift the strip image right in the strip buffer by leftMargin pixels.
 *
 * Assumptions: The strip buffer was allocated large enough to handle the shift; if not
 * then the image data on the right will get clipped.
 *
 * We allocate a full strip (height and width), but then only copy numLinesThisCall from
 * the original buffer to the newly allocated buffer.  This pads the strips for JPEG processing.
 */
static ubyte *shiftStripByLeftMargin(ubyte *ptrToStrip, sint32 currSourceWidth,
        sint32 currStripHeight, sint32 numLinesThisCall, sint32 currMediaWidth, sint32 leftMargin,
        colorSpaceDisposition destColorSpace) {
    ubyte *fromPtr, *toPtr, *newStrip;
    sint32 scanLineWidth;

    if (destColorSpace == grayScale) {
        scanLineWidth = currMediaWidth;

        // Allocate a full strip
        newStrip = (ubyte *) malloc(scanLineWidth * currStripHeight);
        memset(newStrip, 0xff, scanLineWidth * currStripHeight);
        for (int i = 0; i < numLinesThisCall; i++) {
            toPtr = newStrip + leftMargin + (i * currMediaWidth);
            fromPtr = ptrToStrip + (i * currSourceWidth);
            memcpy(toPtr, fromPtr, currSourceWidth);
        }
    } else {
        scanLineWidth = currMediaWidth * 3;
        sint32 srcScanlineWidth = currSourceWidth * 3;
        sint32 shiftAmount = leftMargin * 3;
        newStrip = (ubyte *) malloc(scanLineWidth * currStripHeight);
        memset(newStrip, 0xff, scanLineWidth * currStripHeight);
        for (int i = 0; i < numLinesThisCall; i++) {
            toPtr = newStrip + shiftAmount + (i * scanLineWidth);
            fromPtr = ptrToStrip + (i * srcScanlineWidth);
            memcpy(toPtr, fromPtr, srcScanlineWidth);
        }
    }

    return newStrip;
}

#ifdef SUPPORT_WHITE_STRIPS

bool PCLmGenerator::isWhiteStrip(void *pInBuffer, int inBufferSize) {
    uint32 *ptr = (uint32 *) pInBuffer;
    for (int i = 0; i < inBufferSize / 4; i++, ptr++) {
        if (*ptr != 0xffffffff) {
            return false;
        }
    }
    return true;
}

#endif

void PCLmGenerator::Cleanup(void) {
    if (allocatedOutputBuffer) {
        free(allocatedOutputBuffer);
        allocatedOutputBuffer = NULL;
        currOutBuffSize = 0;
    }

    if (leftoverScanlineBuffer) {
        free(leftoverScanlineBuffer);
        leftoverScanlineBuffer = NULL;
    }
    if (scratchBuffer) {
        free(scratchBuffer);
        scratchBuffer = NULL;
    }
    if (xRefTable) {
        free(xRefTable);
        xRefTable = NULL;
    }
    if (KidsArray) {
        free(KidsArray);
        KidsArray = NULL;
    }
}

int PCLmGenerator::errorOutAndCleanUp() {
    Cleanup();
    jobOpen = job_errored;
    return genericFailure;
}

static sint32 startXRef = 0;
static sint32 endXRef = 0;

/*
 * DO NOT EDIT UNTIL YOU READ THE HEADER FILE DESCRIPTION.
 */
void PCLmGenerator::fixXRef() {
    if (!startXRef || !mirrorBackside) {
        return;
    }

    if (currDuplexDisposition == duplex_longEdge && !(pageCount % 2) && mirrorBackside) {
        assert(startXRef);
        sint32 start = startXRef;
        sint32 end = endXRef - 1;
        sint32 aSize = endXRef - startXRef - 1;

        sint32 *tmpArray = (sint32 *) malloc(aSize * 20);

        sint32 xRefI = startXRef;
        for (int i = 0; i < aSize + 1; i++, xRefI++) {
            *(tmpArray + i) = xRefTable[xRefI + 1] - xRefTable[xRefI];
        }

        // Reorder header and image sizes
        for (int i = 0; i < aSize + 1; i += 2, xRefI++) {
            sint32 t = *(tmpArray + i);
            *(tmpArray + i) = *(tmpArray + i + 1);
            *(tmpArray + i + 1) = t;
        }

        xRefI = aSize;
        for (int i = start + 1, j = aSize; i < end + 2; i++, start++, xRefI--, j--) {
            xRefTable[i] = (xRefTable[i - 1] + *(tmpArray + j));
        }

        for (int i = startXRef + 2; i < endXRef; i++) {
            xRefTable[i] += 2;
        }

        sint32 k = endXRef - 1;
        int i;
        sint32 lSize = (endXRef - startXRef) / 2;
        for (i = startXRef; i < startXRef + lSize; i++, k--) {
            sint32 t = xRefTable[i];
            xRefTable[i] = xRefTable[k];
            xRefTable[k] = t;
        }
        free(tmpArray);
    }

    startXRef = 0;
}

bool PCLmGenerator::addXRef(sint32 xRefObj) {
#define XREF_ARRAY_SIZE 100
    if (!xRefTable) {
        xRefTable = (sint32 *) malloc(XREF_ARRAY_SIZE * sizeof(sint32));
        assert(xRefTable);
        xRefTable[0] = 0;
        xRefIndex++;
    }

    xRefTable[xRefIndex] = xRefObj;
    xRefIndex++;

    if (!(xRefIndex % XREF_ARRAY_SIZE)) {
        xRefTable = (sint32 *) realloc(xRefTable, (((xRefIndex + XREF_ARRAY_SIZE) *
                sizeof(sint32))));
    }
    return true;
}

bool PCLmGenerator::addKids(sint32 kidObj) {
#define KID_ARRAY_SIZE 20
    if (!KidsArray) {
        KidsArray = (sint32 *) malloc(KID_ARRAY_SIZE * sizeof(sint32));
        assert(KidsArray);
    }

    KidsArray[numKids] = kidObj;
    numKids++;

    if (!(numKids % KID_ARRAY_SIZE)) {
        KidsArray = (sint32 *) realloc(KidsArray, ((numKids + KID_ARRAY_SIZE) * sizeof(sint32)));
    }
    return true;
}

void PCLmGenerator::initOutBuff(char *buff, sint32 size) {
    currBuffPtr = outBuffPtr = buff;
    outBuffSize = size;
    totalBytesWrittenToCurrBuff = 0;
    memset(buff, 0, size);
}

void PCLmGenerator::writeStr2OutBuff(char *str) {
    sint32 strSize = strlen(str);
    // Make sure we have enough room for the copy
    char *maxSize = currBuffPtr + strSize;
    assert(maxSize - outBuffPtr < outBuffSize);
    memcpy(currBuffPtr, str, strSize);
    currBuffPtr += strSize;
    totalBytesWrittenToCurrBuff += strSize;
    totalBytesWrittenToPCLmFile += strSize;
}

void PCLmGenerator::write2Buff(ubyte *buff, int buffSize) {
    char *maxSize = currBuffPtr + buffSize;
    if (maxSize - outBuffPtr > outBuffSize) {
        assert(0);
    }
    memcpy(currBuffPtr, buff, buffSize);
    currBuffPtr += buffSize;
    totalBytesWrittenToCurrBuff += buffSize;
    totalBytesWrittenToPCLmFile += buffSize;
}

int PCLmGenerator::statOutputFileSize() {
    addXRef(totalBytesWrittenToPCLmFile);
    return (1);
}

void PCLmGenerator::writePDFGrammarTrailer(int imageWidth, int imageHeight) {
    int i;
    char KidsString[KID_STRING_SIZE];

    sprintf(pOutStr, "%%============= PCLm: FileBody: Object 1 - Catalog\n");
    writeStr2OutBuff(pOutStr);
    statOutputFileSize();
    sprintf(pOutStr, "%d 0 obj\n", CATALOG_OBJ_NUMBER);
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "<<\n");
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "/Type /Catalog\n");
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "/Pages %d 0 R\n", PAGES_OBJ_NUMBER);
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, ">>\n");
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "endobj\n");
    writeStr2OutBuff(pOutStr);

    sprintf(pOutStr, "%%============= PCLm: FileBody: Object 2 - page tree \n");
    writeStr2OutBuff(pOutStr);
    statOutputFileSize();
    sprintf(pOutStr, "%d 0 obj\n", PAGES_OBJ_NUMBER);
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "<<\n");
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "/Count %ld\n", numKids);
    writeStr2OutBuff(pOutStr);

    // Define the Kids for this document as an indirect array
    sprintf(KidsString, "/Kids [ ");
    writeStr2OutBuff(KidsString);
    for (i = 0; i < numKids; i++) {
        sprintf(KidsString, "%ld 0 R ", KidsArray[i]);
        writeStr2OutBuff(KidsString);
    }

    sprintf(KidsString, "]\n");
    writeStr2OutBuff(KidsString);

    sprintf(pOutStr, "/Type /Pages\n");
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, ">>\n");
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "endobj\n");
    writeStr2OutBuff(pOutStr);

    sprintf(pOutStr, "%%============= PCLm: cross-reference section: object 0, 6 entries\n");
    writeStr2OutBuff(pOutStr);
    statOutputFileSize();

    // Fix up the xref table for backside duplex
    fixXRef();

    xRefStart = xRefIndex - 1;

    sprintf(pOutStr, "xref\n");
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "0 %d\n", 1);
    writeStr2OutBuff(pOutStr);

    // Note the attempt to write exactly 20 bytes
    sprintf(pOutStr, "0000000000 65535 f \n");
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "%d %ld\n", PAGES_OBJ_NUMBER + 1, xRefIndex - 4);
    writeStr2OutBuff(pOutStr);
    for (i = 1; i < xRefIndex - 3; i++) {
        sprintf(pOutStr, "%010ld %05d n \n", xRefTable[i], 0);
        writeStr2OutBuff(pOutStr);
    }

    // sprintf(pOutStr,"<</AIMetaData 32 0 R/AIPDFPrivateData1 33 0 R/AIPDFPrivateData10 34 0\n");

    // Now add the catalog and page object
    sprintf(pOutStr, "%d 2\n", CATALOG_OBJ_NUMBER);
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "%010ld %05d n \n", xRefTable[xRefIndex - 3], 0);
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "%010ld %05d n \n", xRefTable[xRefIndex - 2], 0);
    writeStr2OutBuff(pOutStr);

    sprintf(pOutStr, "%%============= PCLm: File Trailer\n");
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "trailer\n");
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "<<\n");
    writeStr2OutBuff(pOutStr);
    // sprintf(pOutStr,"/Info %d 0\n", infoObj); writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "/Size %ld\n", xRefIndex - 1);
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "/Root %d 0 R\n", CATALOG_OBJ_NUMBER);
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, ">>\n");
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "startxref\n");
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "%ld\n", xRefTable[xRefStart]);
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "%%%%EOF\n");
    writeStr2OutBuff(pOutStr);
}

bool PCLmGenerator::injectAdobeRGBCS() {
    if (adobeRGBCS_firstTime) {
        // We need to inject the ICC object for AdobeRGB
        sprintf(pOutStr, "%%============= PCLm: ICC Profile\n");
        writeStr2OutBuff(pOutStr);
        statOutputFileSize();
        sprintf(pOutStr, "%ld 0 obj\n", objCounter);
        objCounter++;
        writeStr2OutBuff(pOutStr);
        sprintf(pOutStr, "[/ICCBased %ld 0 R]\n", objCounter);
        writeStr2OutBuff(pOutStr);

        sprintf(pOutStr, "endobj\n");
        writeStr2OutBuff(pOutStr);
        statOutputFileSize();
        sprintf(pOutStr, "%ld 0 obj\n", objCounter);
        objCounter++;
        writeStr2OutBuff(pOutStr);
        sprintf(pOutStr, "<<\n");
        writeStr2OutBuff(pOutStr);
        sprintf(pOutStr, "/N 3\n");
        writeStr2OutBuff(pOutStr);
        sprintf(pOutStr, "/Alternate /DeviceRGB\n");
        writeStr2OutBuff(pOutStr);
        sprintf(pOutStr, "/Length %u\n", ADOBE_RGB_SIZE + 1);
        writeStr2OutBuff(pOutStr);
        sprintf(pOutStr, "/Filter /FlateDecode\n");
        writeStr2OutBuff(pOutStr);
        sprintf(pOutStr, ">>\n");
        writeStr2OutBuff(pOutStr);
        sprintf(pOutStr, "stream\n");
        writeStr2OutBuff(pOutStr);

        FILE *inFile;
        if (!(inFile = fopen("flate_colorspace.bin", "rb"))) {
            fprintf(stderr, "can't open %s\n", "flate_colorspace.bin");
            return 0;
        }

        ubyte *buffIn = (unsigned char *) malloc(ADOBE_RGB_SIZE);
        assert(buffIn);

        sint32 bytesRead = fread(buffIn, 1, ADOBE_RGB_SIZE, inFile);
        assert(bytesRead == ADOBE_RGB_SIZE);
        fclose(inFile);
        write2Buff(buffIn, bytesRead);
        if (buffIn) {
            free(buffIn);
        }

        sprintf(pOutStr, "\nendstream\n");
        writeStr2OutBuff(pOutStr);
        sprintf(pOutStr, "endobj\n");
        writeStr2OutBuff(pOutStr);
    }

    adobeRGBCS_firstTime = false;
    return true;
}

bool PCLmGenerator::colorConvertSource(colorSpaceDisposition srcCS, colorSpaceDisposition dstCS,
        ubyte *strip, sint32 stripWidth, sint32 stripHeight) {
    if (srcCS == deviceRGB && dstCS == grayScale) {
        // Do an inplace conversion from RGB -> 8 bpp gray
        ubyte *srcPtr = strip;
        ubyte *dstPtr = strip;
        for (int h = 0; h < stripHeight; h++) {
            for (int w = 0; w < stripWidth; w++, dstPtr++, srcPtr += 3) {
                *dstPtr = (ubyte) rgb_2_gray(*srcPtr, *(srcPtr + 1), *(srcPtr + 2));
            }
        }
        dstNumComponents = 1;
    } else {
        assert(0);
    }

    return true;
}

int PCLmGenerator::injectRLEStrip(ubyte *RLEBuffer, int numBytes, int imageWidth, int imageHeight,
        colorSpaceDisposition destColorSpace, bool whiteStrip) {
    bool printedImageTransform = false;

    if (currDuplexDisposition == duplex_longEdge && !(pageCount % 2) && mirrorBackside) {
        if (!startXRef) {
            startXRef = xRefIndex;
        }

        injectImageTransform();
        printedImageTransform = true;
    }

    if (destColorSpace == adobeRGB) {
        injectAdobeRGBCS();
    }

    // Inject LZ compressed image into PDF file
    sprintf(pOutStr, "%%============= PCLm: FileBody: Strip Stream: RLE Image \n");
    writeStr2OutBuff(pOutStr);
    statOutputFileSize();

    if (currDuplexDisposition == duplex_longEdge && !(pageCount % 2) && mirrorBackside) {
        sprintf(pOutStr, "%ld 0 obj\n", objCounter - 1);
        objCounter++;
        writeStr2OutBuff(pOutStr);
    } else {
        sprintf(pOutStr, "%ld 0 obj\n", objCounter);
        objCounter++;
        writeStr2OutBuff(pOutStr);
    }

    sprintf(pOutStr, "<<\n");
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "/Width %d\n", imageWidth);
    writeStr2OutBuff(pOutStr);
    if (destColorSpace == deviceRGB) {
        sprintf(pOutStr, "/ColorSpace /DeviceRGB\n");
        writeStr2OutBuff(pOutStr);
    } else if (destColorSpace == adobeRGB) {
        sprintf(pOutStr, "/ColorSpace 5 0 R\n");
        writeStr2OutBuff(pOutStr);
    } else {
        sprintf(pOutStr, "/ColorSpace /DeviceGray\n");
        writeStr2OutBuff(pOutStr);
    }
    sprintf(pOutStr, "/Height %d\n", imageHeight);
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "/Filter /RunLengthDecode\n");
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "/Subtype /Image\n");
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "/Length %d\n", numBytes);
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "/Type /XObject\n");
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "/BitsPerComponent 8\n");
    writeStr2OutBuff(pOutStr);
#ifdef SUPPORT_WHITE_STRIPS
    if (whiteStrip) {
        sprintf(pOutStr, "/Name /WhiteStrip\n");
        writeStr2OutBuff(pOutStr);
    } else {
        sprintf(pOutStr, "/Name /ColorStrip\n");
        writeStr2OutBuff(pOutStr);
    }
#endif

    sprintf(pOutStr, ">>\n");
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "stream\n");
    writeStr2OutBuff(pOutStr);

    // Write the zlib compressed strip to the PDF output file
    write2Buff(RLEBuffer, numBytes);
    sprintf(pOutStr, "\nendstream\n");
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "endobj\n");
    writeStr2OutBuff(pOutStr);

    if (!printedImageTransform) {
        injectImageTransform();
    }

    endXRef = xRefIndex;

    return (1);
}

int PCLmGenerator::injectLZStrip(ubyte *LZBuffer, int numBytes, int imageWidth, int imageHeight,
        colorSpaceDisposition destColorSpace, bool whiteStrip) {
    bool printedImageTransform = false;

    if (currDuplexDisposition == duplex_longEdge && !(pageCount % 2) && mirrorBackside) {
        if (!startXRef) {
            startXRef = xRefIndex;
        }

        injectImageTransform();
        printedImageTransform = true;
    }

    if (destColorSpace == adobeRGB) {
        injectAdobeRGBCS();
    }

    // Inject LZ compressed image into PDF file
    sprintf(pOutStr, "%%============= PCLm: FileBody: Strip Stream: zlib Image \n");
    writeStr2OutBuff(pOutStr);
    statOutputFileSize();

    if (currDuplexDisposition == duplex_longEdge && !(pageCount % 2) && mirrorBackside) {
        sprintf(pOutStr, "%ld 0 obj\n", objCounter - 1);
        objCounter++;
        writeStr2OutBuff(pOutStr);
    } else {
        sprintf(pOutStr, "%ld 0 obj\n", objCounter);
        objCounter++;
        writeStr2OutBuff(pOutStr);
    }

    sprintf(pOutStr, "<<\n");
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "/Width %d\n", imageWidth);
    writeStr2OutBuff(pOutStr);
    if (destColorSpace == deviceRGB) {
        sprintf(pOutStr, "/ColorSpace /DeviceRGB\n");
        writeStr2OutBuff(pOutStr);
    } else if (destColorSpace == adobeRGB) {
        sprintf(pOutStr, "/ColorSpace 5 0 R\n");
        writeStr2OutBuff(pOutStr);
    } else {
        sprintf(pOutStr, "/ColorSpace /DeviceGray\n");
        writeStr2OutBuff(pOutStr);
    }
    sprintf(pOutStr, "/Height %d\n", imageHeight);
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "/Filter /FlateDecode\n");
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "/Subtype /Image\n");
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "/Length %d\n", numBytes);
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "/Type /XObject\n");
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "/BitsPerComponent 8\n");
    writeStr2OutBuff(pOutStr);
#ifdef SUPPORT_WHITE_STRIPS
    if (whiteStrip) {
        sprintf(pOutStr, "/Name /WhiteStrip\n");
        writeStr2OutBuff(pOutStr);
    } else {
        sprintf(pOutStr, "/Name /ColorStrip\n");
        writeStr2OutBuff(pOutStr);
    }
#endif

    sprintf(pOutStr, ">>\n");
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "stream\n");
    writeStr2OutBuff(pOutStr);

    // Write the zlib compressed strip to the PDF output file
    write2Buff(LZBuffer, numBytes);
    sprintf(pOutStr, "\nendstream\n");
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "endobj\n");
    writeStr2OutBuff(pOutStr);

    if (!printedImageTransform) {
        injectImageTransform();
    }

    endXRef = xRefIndex;

    return (1);
}

void PCLmGenerator::injectImageTransform() {
    char str[512];
    int strLength;
    sprintf(str, "q /image Do Q\n");
    strLength = strlen(str);

    // Output image transformation information
    sprintf(pOutStr, "%%============= PCLm: Object - Image Transformation \n");
    writeStr2OutBuff(pOutStr);
    statOutputFileSize();
    if (currDuplexDisposition == duplex_longEdge && !(pageCount % 2) && mirrorBackside) {
        sprintf(pOutStr, "%ld 0 obj\n", objCounter + 1);
        objCounter++;
        writeStr2OutBuff(pOutStr);
    } else {
        sprintf(pOutStr, "%ld 0 obj\n", objCounter);
        objCounter++;
        writeStr2OutBuff(pOutStr);
    }
    sprintf(pOutStr, "<<\n");
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "/Length %d\n", strLength);
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, ">>\n");
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "stream\n");
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "%s", str);
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "endstream\n");
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "endobj\n");
    writeStr2OutBuff(pOutStr);
}

int PCLmGenerator::injectJPEG(char *jpeg_Buff, int imageWidth, int imageHeight, int numCompBytes,
        colorSpaceDisposition destColorSpace, bool whiteStrip) {
    char str[512];

    bool printedImageTransform = false;

    if (currDuplexDisposition == duplex_longEdge && !(pageCount % 2) && mirrorBackside) {
        if (!startXRef) {
            startXRef = xRefIndex;
        }

        injectImageTransform();
        printedImageTransform = true;
    }

    yPosition += imageHeight;

    if (destColorSpace == adobeRGB) {
        injectAdobeRGBCS();
    }

    // Inject PDF JPEG into output file
    sprintf(pOutStr, "%%============= PCLm: FileBody: Strip Stream: jpeg Image \n");
    writeStr2OutBuff(pOutStr);
    statOutputFileSize();
    if (currDuplexDisposition == duplex_longEdge && !(pageCount % 2) && mirrorBackside) {
        sprintf(pOutStr, "%ld 0 obj\n", objCounter - 1);
        objCounter++;
        writeStr2OutBuff(pOutStr);
    } else {
        sprintf(pOutStr, "%ld 0 obj\n", objCounter);
        objCounter++;
        writeStr2OutBuff(pOutStr);
    }
    sprintf(pOutStr, "<<\n");
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "/Width %d\n", imageWidth);
    writeStr2OutBuff(pOutStr);
    if (destColorSpace == deviceRGB) {
        sprintf(pOutStr, "/ColorSpace /DeviceRGB\n");
        writeStr2OutBuff(pOutStr);
    } else if (destColorSpace == adobeRGB) {
        sprintf(pOutStr, "/ColorSpace 5 0 R\n");
        writeStr2OutBuff(pOutStr);
    } else {
        sprintf(pOutStr, "/ColorSpace /DeviceGray\n");
        writeStr2OutBuff(pOutStr);
    }
    sprintf(pOutStr, "/Height %d\n", imageHeight);
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "/Filter /DCTDecode\n");
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "/Subtype /Image\n");
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "/Length %d\n", numCompBytes);
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "/Type /XObject\n");
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "/BitsPerComponent 8\n");
    writeStr2OutBuff(pOutStr);
#ifdef SUPPORT_WHITE_STRIPS
    if (whiteStrip) {
        sprintf(pOutStr, "/Name /WhiteStrip\n");
        writeStr2OutBuff(pOutStr);
    } else {
        sprintf(pOutStr, "/Name /ColorStrip\n");
        writeStr2OutBuff(pOutStr);
    }
#endif
    sprintf(pOutStr, ">>\n");
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "stream\n");
    writeStr2OutBuff(pOutStr);

    write2Buff((ubyte *) jpeg_Buff, numCompBytes);
    sprintf(pOutStr, "\nendstream\n");
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "endobj\n");
    writeStr2OutBuff(pOutStr);

    sprintf(str, "q /image Do Q\n");

    if (!printedImageTransform) {
        injectImageTransform();
    }

    endXRef = xRefIndex;

    return (1);
}

/*
 * Writes str to buffer if the size of buffer is less than TEMP_BUFF_SIZE
 */
void writeStr2Buff(char *buffer, char *str) {
    int buffSize;
    char *buffPos;

    buffSize = strlen(buffer) + strlen(str);
    if (buffSize > TEMP_BUFF_SIZE) {
        assert(0);
    }

    buffSize = strlen(buffer);
    buffPos = buffer + buffSize;
    sprintf(buffPos, "%s", str);

    buffSize = strlen(buffer);
    if (buffSize > TEMP_BUFF_SIZE) {
        printf("tempBuff size exceeded: buffSize=%d\n", buffSize);
        assert(0);
    }
}

void PCLmGenerator::writePDFGrammarPage(int imageWidth, int imageHeight, int numStrips,
        colorSpaceDisposition destColorSpace) {
    int i, imageRef = objCounter + 2, buffSize;
    int yAnchor;
    char str[512];
    char *tempBuffer;
    int startImageIndex = 0;
    int numLinesLeft = 0;

    if (destColorSpace == adobeRGB && 1 == pageCount) {
        imageRef += 2; // Add 2 for AdobeRGB
    }

    tempBuffer = (char *) malloc(TEMP_BUFF_SIZE);
    assert(tempBuffer);
    memset(tempBuffer, 0x0, TEMP_BUFF_SIZE);

    sprintf(pOutStr, "%%============= PCLm: FileBody: Object 3 - page object\n");
    writeStr2OutBuff(pOutStr);
    statOutputFileSize();
    sprintf(pOutStr, "%ld 0 obj\n", objCounter);
    writeStr2OutBuff(pOutStr);
    addKids(objCounter);
    objCounter++;
    sprintf(pOutStr, "<<\n");
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "/Type /Page\n");
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "/Parent %d 0 R\n", PAGES_OBJ_NUMBER);
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "/Resources <<\n");
    writeStr2OutBuff(pOutStr);
    // sprintf(pOutStr,"/ProcSet [ /PDF /ImageC ]\n"); writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "/XObject <<\n");
    writeStr2OutBuff(pOutStr);

    if (topMarginInPix) {
        for (i = 0; i < numFullInjectedStrips; i++, startImageIndex++) {
            sprintf(str, "/Image%d %d 0 R\n", startImageIndex, imageRef);
            sprintf(pOutStr, "%s", str);
            writeStr2OutBuff(pOutStr);
            imageRef += 2;
        }
        if (numPartialScanlinesToInject) {
            sprintf(str, "/Image%d %d 0 R\n", startImageIndex, imageRef);
            sprintf(pOutStr, "%s", str);
            writeStr2OutBuff(pOutStr);
            imageRef += 2;
            startImageIndex++;
        }
    }

    for (i = startImageIndex; i < numStrips + startImageIndex; i++) {
        sprintf(str, "/Image%d %d 0 R\n", i, imageRef);
        // sprintf(pOutStr,"/ImageA 4 0 R /ImageB 6 0 R >>\n"); writeStr2OutBuff(pOutStr);
        sprintf(pOutStr, "%s", str);
        writeStr2OutBuff(pOutStr);
        imageRef += 2;
    }
    sprintf(pOutStr, ">>\n");
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, ">>\n");
    writeStr2OutBuff(pOutStr);
    if (currMediaOrientationDisposition == landscapeOrientation) {
        pageOrigin = mediaWidth;
        sprintf(pOutStr, "/MediaBox [ 0 0 %d %d ]\n", mediaHeight, mediaWidth);
        writeStr2OutBuff(pOutStr);
    } else {
        pageOrigin = mediaHeight;
        sprintf(pOutStr, "/MediaBox [ 0 0 %d %d ]\n", mediaWidth, mediaHeight);
        writeStr2OutBuff(pOutStr);
    }
    sprintf(pOutStr, "/Contents [ %ld 0 R ]\n", objCounter);
    writeStr2OutBuff(pOutStr);
#ifdef PIECEINFO_SUPPORTED
    sprintf(pOutStr,"/PieceInfo <</HPAddition %d 0 R >> \n",9997); writeStr2OutBuff(pOutStr);
#endif
    sprintf(pOutStr, ">>\n");
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "endobj\n");
    writeStr2OutBuff(pOutStr);

    // Create the FileBody stream first, so we know the Length of the stream
    if (reverseOrder) {
        yAnchor = 0;
    } else {
        yAnchor = (int) ((pageOrigin * STANDARD_SCALE) + 0.99); // Round up
    }

    // Setup the CTM so that we can send device-resolution coordinates
    sprintf(pOutStr,
            "%%Image Transformation Matrix: width, skewX, skewY, height, xAnchor, yAnchor\n");
    writeStr2OutBuff(pOutStr);
    sprintf(str, "%f 0 0 %f 0 0 cm\n", STANDARD_SCALE_FOR_PDF / currRenderResolutionInteger,
            STANDARD_SCALE_FOR_PDF / currRenderResolutionInteger);
    writeStr2Buff(tempBuffer, str);

    startImageIndex = 0;
    if (topMarginInPix) {
        for (i = 0; i < numFullInjectedStrips; i++) {
            if (reverseOrder) {
                yAnchor += numFullScanlinesToInject;
            } else {
                yAnchor -= numFullScanlinesToInject;
            }

            sprintf(str, "/P <</MCID 0>> BDC q\n");
            writeStr2Buff(tempBuffer, str);

            sprintf(str, "%%Image Transformation Matrix: width, skewX, skewY, height, "
                    "xAnchor, yAnchor\n");
            writeStr2Buff(tempBuffer, str);

            sprintf(str, "%d 0 0 %ld 0 %d cm\n", imageWidth * scaleFactor,
                    numFullScanlinesToInject * scaleFactor, yAnchor * scaleFactor);
            writeStr2Buff(tempBuffer, str);

            sprintf(str, "/Image%d Do Q\n", startImageIndex);
            writeStr2Buff(tempBuffer, str);

            startImageIndex++;
        }
        if (numPartialScanlinesToInject) {
            if (reverseOrder) {
                yAnchor += numPartialScanlinesToInject;
            } else {
                yAnchor -= numPartialScanlinesToInject;
            }

            sprintf(str, "/P <</MCID 0>> BDC q\n");
            writeStr2Buff(tempBuffer, str);

            sprintf(str, "%%Image Transformation Matrix: width, skewX, skewY, height, xAnchor, "
                    "yAnchor\n");

            writeStr2Buff(tempBuffer, str);

            sprintf(str, "%d 0 0 %ld 0 %d cm\n", imageWidth * scaleFactor,
                    numPartialScanlinesToInject * scaleFactor, yAnchor * scaleFactor);
            writeStr2Buff(tempBuffer, str);

            sprintf(str, "/Image%d Do Q\n", startImageIndex);
            writeStr2Buff(tempBuffer, str);

            startImageIndex++;
        }
    }

    for (i = startImageIndex; i < numStrips + startImageIndex; i++) {
        // last strip may have less lines than currStripHeight. Update yAnchor using left over lines
        if (i == (numStrips + startImageIndex - 1)) {
            numLinesLeft = currSourceHeight - ((numStrips - 1) * currStripHeight);

            if (reverseOrder) {
                yAnchor += numLinesLeft;
            } else {
                yAnchor -= numLinesLeft;
            }
        } else {
            if (reverseOrder) {
                yAnchor += currStripHeight;
            } else {
                yAnchor -= currStripHeight;
            }
        }

        sprintf(str, "/P <</MCID 0>> BDC q\n");
        writeStr2Buff(tempBuffer, str);

        sprintf(str,
                "%%Image Transformation Matrix: width, skewX, skewY, height, xAnchor, yAnchor\n");
        writeStr2Buff(tempBuffer, str);

        // last strip may have less lines than currStripHeight
        if (i == (numStrips + startImageIndex - 1)) {
            sprintf(str, "%d 0 0 %d 0 %d cm\n", imageWidth * scaleFactor,
                    numLinesLeft * scaleFactor, yAnchor * scaleFactor);
            writeStr2Buff(tempBuffer, str);
        } else if (yAnchor < 0) {
            sint32 newH = currStripHeight + yAnchor;
            sprintf(str, "%d 0 0 %ld 0 %d cm\n", imageWidth * scaleFactor, newH * scaleFactor,
                    0 * scaleFactor);
            writeStr2Buff(tempBuffer, str);
        } else {
            sprintf(str, "%d 0 0 %ld 0 %d cm\n", imageWidth * scaleFactor,
                    currStripHeight * scaleFactor, yAnchor * scaleFactor);
            writeStr2Buff(tempBuffer, str);
        }

        sprintf(str, "/Image%d Do Q\n", i);
        writeStr2Buff(tempBuffer, str);
    }

    // Resulting buffer size
    buffSize = strlen(tempBuffer);

    sprintf(pOutStr, "%%============= PCLm: FileBody: Page Content Stream object\n");
    writeStr2OutBuff(pOutStr);
    statOutputFileSize();
    sprintf(pOutStr, "%ld 0 obj\n", objCounter);
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "<<\n");
    writeStr2OutBuff(pOutStr);

    sprintf(pOutStr, "/Length %d\n", buffSize);
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, ">>\n");
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "stream\n");
    writeStr2OutBuff(pOutStr);

    // Now write the FileBody stream
    write2Buff((ubyte *) tempBuffer, buffSize);

    sprintf(pOutStr, "endstream\n");
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "endobj\n");
    writeStr2OutBuff(pOutStr);
    objCounter++;
    if (tempBuffer) {
        free(tempBuffer);
    }
}

/*
 * Mirrors the source image in preparation for backside duplex support
 */
static bool prepImageForBacksideDuplex(ubyte *imagePtr, sint32 imageHeight, sint32 imageWidth,
        sint32 numComponents) {
    sint32 numBytes = imageHeight * imageWidth * numComponents;
    ubyte *head, *tail, t0, t1, t2;

    if (numComponents == 3) {
        for (head = imagePtr, tail = imagePtr + numBytes - 1; tail > head;) {
            t0 = *head;
            t1 = *(head + 1);
            t2 = *(head + 2);

            *head = *(tail - 2);
            *(head + 1) = *(tail - 1);
            *(head + 2) = *(tail - 0);
            *tail = t2;
            *(tail - 1) = t1;
            *(tail - 2) = t0;

            head += 3;
            tail -= 3;
        }
    } else {
        for (head = imagePtr, tail = imagePtr + numBytes; tail > head;) {
            t0 = *head;
            *head = *tail;
            *tail = t0;
            head++;
            tail--;
        }
    }
//origTail++;
    return true;
}

bool PCLmGenerator::getInputBinString(jobInputBin bin, char *returnStr) {
    returnStr[0] = '\0';

    switch (bin) {
        case alternate:
            strcpy(returnStr, "alternate");
            break;
        case alternate_roll:
            strcpy(returnStr, "alternate_roll");
            break;
        case auto_select:
            strcpy(returnStr, "auto_select");
            break;
        case bottom:
            strcpy(returnStr, "bottom");
            break;
        case center:
            strcpy(returnStr, "center");
            break;
        case disc:
            strcpy(returnStr, "disc");
            break;
        case envelope:
            strcpy(returnStr, "envelope");
            break;
        case hagaki:
            strcpy(returnStr, "hagaki");
            break;
        case large_capacity:
            strcpy(returnStr, "large_capacity");
            break;
        case left:
            strcpy(returnStr, "left");
            break;
        case main_tray:
            strcpy(returnStr, "main_tray");
            break;
        case main_roll:
            strcpy(returnStr, "main_roll");
            break;
        case manual:
            strcpy(returnStr, "manual");
            break;
        case middle:
            strcpy(returnStr, "middle");
            break;
        case photo:
            strcpy(returnStr, "photo");
            break;
        case rear:
            strcpy(returnStr, "rear");
            break;
        case right:
            strcpy(returnStr, "right");
            break;
        case side:
            strcpy(returnStr, "side");
            break;
        case top:
            strcpy(returnStr, "top");
            break;
        case tray_1:
            strcpy(returnStr, "tray_1");
            break;
        case tray_2:
            strcpy(returnStr, "tray_2");
            break;
        case tray_3:
            strcpy(returnStr, "tray_3");
            break;
        case tray_4:
            strcpy(returnStr, "tray_4");
            break;
        case tray_5:
            strcpy(returnStr, "tray_5");
            break;
        case tray_N:
            strcpy(returnStr, "tray_N");
            break;
        default:
            assert(0);
            break;
    }
    return true;
}

bool PCLmGenerator::getOutputBin(jobOutputBin bin, char *returnStr) {
    if (returnStr) {
        returnStr[0] = '\0';
    }

    switch (bin) {
        case top_output:
            strcpy(returnStr, "top_output");
            break;
        case middle_output:
            strcpy(returnStr, "middle_output");
            break;
        case bottom_output:
            strcpy(returnStr, "bottom_output");
            break;
        case side_output:
            strcpy(returnStr, "side_output");
            break;
        case center_output:
            strcpy(returnStr, "center_output");
            break;
        case rear_output:
            strcpy(returnStr, "rear_output");
            break;
        case face_up:
            strcpy(returnStr, "face_up");
            break;
        case face_down:
            strcpy(returnStr, "face_down");
            break;
        case large_capacity_output:
            strcpy(returnStr, "large_capacity_output");
            break;
        case stacker_N:
            strcpy(returnStr, "stacker_N");
            break;
        case mailbox_N:
            strcpy(returnStr, "mailbox_N");
            break;
        case tray_1_output:
            strcpy(returnStr, "tray_1_output");
            break;
        case tray_2_output:
            strcpy(returnStr, "tray_2_output");
            break;
        case tray_3_output:
            strcpy(returnStr, "tray_3_output");
            break;
        case tray_4_output:
            strcpy(returnStr, "tray_4_output");
            break;
        default:
            assert(0);
            break;
    }
    return true;
}

void PCLmGenerator::writeJobTicket() {
    // Write JobTicket
    char inputBin[256];
    char outputBin[256];

    if (!m_pPCLmSSettings) {
        return;
    }

    getInputBinString(m_pPCLmSSettings->userInputBin, &inputBin[0]);
    getOutputBin(m_pPCLmSSettings->userOutputBin, &outputBin[0]);
    strcpy(inputBin, inputBin);
    strcpy(outputBin, outputBin);

    sprintf(pOutStr, "%%  genPCLm (Ver: %f)\n", PCLM_Ver);
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "%%============= Job Ticket =============\n");
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "%%  PCLmS-Job-Ticket\n");
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "%%      job-ticket-version: 0.1\n");
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "%%      epcl-version: 1.01\n");
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "%%    JobSection\n");
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "%%      job-id: xxx\n");
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "%%    MediaHandlingSection\n");
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "%%      media-size-name: %s\n", currMediaName);
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "%%      media-type: %s\n", m_pPCLmSSettings->userMediaType);
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "%%      media-source: %s\n", inputBin);
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "%%      sides: xxx\n");
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "%%      output-bin: %s\n", outputBin);
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "%%    RenderingSection\n");
    writeStr2OutBuff(pOutStr);
    if (currCompressionDisposition == compressDCT) {
        sprintf(pOutStr, "%%      pclm-compression-method: JPEG\n");
        writeStr2OutBuff(pOutStr);
    } else if (currCompressionDisposition == compressFlate) {
        sprintf(pOutStr, "%%      pclm-compression-method: FLATE\n");
        writeStr2OutBuff(pOutStr);
    } else {
        sprintf(pOutStr, "%%      pclm-compression-method: RLE\n");
        writeStr2OutBuff(pOutStr);
    }
    sprintf(pOutStr, "%%      strip-height: %ld\n", currStripHeight);
    writeStr2OutBuff(pOutStr);

    if (destColorSpace == deviceRGB) {
        sprintf(pOutStr, "%%      print-color-mode: deviceRGB\n");
        writeStr2OutBuff(pOutStr);
    } else if (destColorSpace == adobeRGB) {
        sprintf(pOutStr, "%%      print-color-mode: adobeRGB\n");
        writeStr2OutBuff(pOutStr);
    } else if (destColorSpace == grayScale) {
        sprintf(pOutStr, "%%      print-color-mode: gray\n");
        writeStr2OutBuff(pOutStr);
    }

    sprintf(pOutStr, "%%      print-quality: %d\n", m_pPCLmSSettings->userPageQuality);
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "%%      printer-resolution: %d\n", currRenderResolutionInteger);
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "%%      print-content-optimized: xxx\n");
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "%%      orientation-requested: %d\n", m_pPCLmSSettings->userOrientation);
    writeStr2OutBuff(pOutStr);

    if (PCLmSSettings.userCopies == 0) {
        PCLmSSettings.userCopies = 1;
    }

    sprintf(pOutStr, "%%      copies: %d\n", m_pPCLmSSettings->userCopies);
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "%%      pclm-raster-back-side: xxx\n");
    writeStr2OutBuff(pOutStr);
    if (currRenderResolutionInteger) {
        sprintf(pOutStr, "%%      margins-pre-applied: TRUE\n");
        writeStr2OutBuff(pOutStr);
    } else {
        sprintf(pOutStr, "%%      margins-pre-applied: FALSE\n");
        writeStr2OutBuff(pOutStr);
    }
    sprintf(pOutStr, "%%  PCLmS-Job-Ticket-End\n");
    writeStr2OutBuff(pOutStr);
}

void PCLmGenerator::writePDFGrammarHeader() {
    // sprintf(pOutStr,"%%============= PCLm: File Header \n"); writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "%%PDF-1.7\n");
    writeStr2OutBuff(pOutStr);
    sprintf(pOutStr, "%%PCLm 1.0\n");
    writeStr2OutBuff(pOutStr);
}

int PCLmGenerator::RLEEncodeImage(ubyte *in, ubyte *out, int inLength) {
    ubyte *imgPtr = in;
    ubyte *endPtr = in + inLength;
    ubyte *origOut = out;
    ubyte c;
    sint32 cnt = 0;

    while (imgPtr < endPtr) {
        c = *imgPtr++;
        cnt = 1;

        // Figure out how many repeating bytes are in the image
        while (*imgPtr == c && cnt < inLength) {
            if (imgPtr > endPtr) {
                break;
            }
            cnt++;
            imgPtr++;
        }

        if (cnt > 1) {
            /* If cnt > 1, then output repeating byte specification
             * The syntax is "byte-count repeateByte", where byte-count is 257-byte-count.
             * Since the cnt value is a byte, if the repeateCnt is > 128 then we need to put
             * out multiple repeat-blocks (Referred to as method 1) range is 128-256
             */
            while (cnt > 128) {
                *out++ = 129; // i.e. 257-129==128
                *out++ = c;
                cnt -= 128;
            }
            // Now handle the repeats that are < 128
            if (cnt) {
                *out++ = (257 - cnt); // i.e. cnt==2: 257-255=2
                *out++ = c;
            }
        } else {
            /* If cnt==1, then this is a literal run - no repeating bytes found.
             * The syntax is "byte-count literal-run", where byte-count is < 128 and
             * literal-run is the non-repeating bytes of the input stream.
             * Referred to as method 2, range is 0-127
             */
            ubyte *start, *p;
            sint32 i;
            start = (imgPtr - 1);  // The first byte of the literal run

            // Now find the end of the literal run
            for (cnt = 1, p = start; *p != *imgPtr; p++, imgPtr++, cnt++) {
                if (imgPtr >= endPtr) break;
            }
            if (!(imgPtr == endPtr)) {
                imgPtr--;
                // imgPtr incremented 1 too many
            }
            cnt--;
            // Blocks of literal bytes can't exceed 128 bytes, so output multiple
            //    literal-run blocks if > 128
            while (cnt > 128) {
                *out++ = 127;
                for (i = 0; i < 128; i++) {
                    *out++ = *start++;
                }
                cnt -= 128;
            }
            // Now output the leftover literal run
            *out++ = cnt - 1;
            for (i = 0; i < cnt; i++) {
                *out++ = *start++;
            }
        }
    }
    // Now, write the end-of-compression marker (byte 128) into the output stream
    *out++ = 128;
    // Return the compressed size
    return ((int) (out - origOut));
}

PCLmGenerator::PCLmGenerator() {
    strcpy(currMediaName, "LETTER");
    currDuplexDisposition = simplex;
    currCompressionDisposition = compressDCT;
    currMediaOrientationDisposition = portraitOrientation;
    currRenderResolution = res600;
    currStripHeight = STRIP_HEIGHT;

    // Default media h/w to letter specification
    mediaWidthInPixels = 0;
    mediaHeightInPixels = 0;
    mediaWidth = 612;
    mediaHeight = 792;
    destColorSpace = deviceRGB;
    sourceColorSpace = deviceRGB;
    scaleFactor = 1;
    jobOpen = job_closed;
    scratchBuffer = NULL;
    pageCount = 0;

    currRenderResolutionInteger = 600;
    STANDARD_SCALE = (float) currRenderResolutionInteger / (float) STANDARD_SCALE_FOR_PDF;
    yPosition = 0;
    numKids = 0;

    // XRefTable storage
    xRefIndex = 0;
    xRefStart = 0;

    objCounter = PAGES_OBJ_NUMBER + 1;
    totalBytesWrittenToPCLmFile = 0;

    // Initialize first index in xRefTable
    xRefTable = NULL;
    KidsArray = NULL;

    // Initialize the output Buffer
    allocatedOutputBuffer = NULL;

    // Initialize the leftover scanline logic
    leftoverScanlineBuffer = 0;

    adobeRGBCS_firstTime = true;
    mirrorBackside = true;

    topMarginInPix = 0;
    leftMarginInPix = 0;
    m_pPCLmSSettings = NULL;
}

PCLmGenerator::~PCLmGenerator() {
    Cleanup();
}

int PCLmGenerator::StartJob(void **pOutBuffer, int *iOutBufferSize) {
    /* Allocate the output buffer; we don't know much at this point, so make the output buffer size
     * the worst case dimensions; when we get a startPage, we will resize it appropriately
     */
    outBuffSize = DEFAULT_OUTBUFF_SIZE;
    *iOutBufferSize = outBuffSize;
    *pOutBuffer = (ubyte *) malloc(outBuffSize); // This multipliy by 10 needs to be removed...

    if (NULL == *pOutBuffer) {
        return (errorOutAndCleanUp());
    }

    currOutBuffSize = outBuffSize;

    if (NULL == *pOutBuffer) {
        return (errorOutAndCleanUp());
    }

    allocatedOutputBuffer = *pOutBuffer;
    initOutBuff((char *) *pOutBuffer, outBuffSize);
    writePDFGrammarHeader();
    *iOutBufferSize = totalBytesWrittenToCurrBuff;
    jobOpen = job_open;

    return success;
}

int PCLmGenerator::EndJob(void **pOutBuffer, int *iOutBufferSize) {
    if (NULL == allocatedOutputBuffer) {
        return (errorOutAndCleanUp());
    }

    *pOutBuffer = allocatedOutputBuffer;

    initOutBuff((char *) *pOutBuffer, outBuffSize);

    // Write PDF trailer
    writePDFGrammarTrailer(currSourceWidth, currSourceHeight);

    *iOutBufferSize = totalBytesWrittenToCurrBuff;

    jobOpen = job_closed;

    if (xRefTable) {
        free(xRefTable);
        xRefTable = NULL;
    }
    if (KidsArray) {
        free(KidsArray);
        KidsArray = NULL;
    }

    return success;
}

int PCLmGenerator::StartPage(PCLmPageSetup *PCLmPageContent, void **pOutBuffer,
        int *iOutBufferSize) {
    int numImageStrips;
    // Save the resolution information
    currRenderResolution = PCLmPageContent->destinationResolution;

    *pOutBuffer = allocatedOutputBuffer;

    if (currRenderResolution == res300) {
        currRenderResolutionInteger = 300;
    } else if (currRenderResolution == res600) {
        currRenderResolutionInteger = 600;
    } else if (currRenderResolution == res1200) {
        currRenderResolutionInteger = 1200;
    } else {
        assert(0);
    }

    // Recalculate STANDARD_SCALE to reflect the job resolution
    STANDARD_SCALE = (float) currRenderResolutionInteger / (float) STANDARD_SCALE_FOR_PDF;

    // Use the values set by the caller
    currSourceWidth = PCLmPageContent->SourceWidthPixels;
    currSourceHeight = PCLmPageContent->SourceHeightPixels;

    // Save off the media information
    mediaWidth = (int) (PCLmPageContent->mediaWidth);
    mediaHeight = (int) (PCLmPageContent->mediaHeight);

    // Use the values set by the caller
    mediaWidthInPixels = PCLmPageContent->mediaWidthInPixels;
    mediaHeightInPixels = PCLmPageContent->mediaHeightInPixels;

    topMarginInPix = (int) (((PCLmPageContent->mediaHeightOffset / STANDARD_SCALE_FOR_PDF) *
            currRenderResolutionInteger) + 0.50);
    leftMarginInPix = (int) (((PCLmPageContent->mediaWidthOffset / STANDARD_SCALE_FOR_PDF) *
            currRenderResolutionInteger) + 0.50);

    if (topMarginInPix % 16) {
        // Round to nearest 16 scanline boundary to ensure decompressability.
        int i = topMarginInPix % 16;
        if (i < (16 / 2)) {
            topMarginInPix -= i;
        } else {
            topMarginInPix += (16 - i);
        }
    }

    if (leftMarginInPix % 16) {
        // Round to nearest 16 scanline boundary to ensure decompressability.
        int i = leftMarginInPix % 16;
        if (i < (16 / 2)) {
            leftMarginInPix -= i;
        } else {
            leftMarginInPix += (16 - i);
        }
    }

    currCompressionDisposition = PCLmPageContent->compTypeRequested;

    if (strlen(PCLmPageContent->mediaSizeName)) {
        strcpy(currMediaName, PCLmPageContent->mediaSizeName);
    }

    currStripHeight = PCLmPageContent->stripHeight;
    if (!currStripHeight) {
        numImageStrips = 1;
        currStripHeight = currSourceHeight;
    } else {
        // Need to know how many strips will be inserted into PDF file
        float numImageStripsReal = ceil((float) currSourceHeight / (float) currStripHeight);
        numImageStrips = (int) numImageStripsReal;
    }

    if (PCLmPageContent->srcColorSpaceSpefication == grayScale) {
        srcNumComponents = 1;
    } else {
        srcNumComponents = 3;
    }

    if (PCLmPageContent->dstColorSpaceSpefication == grayScale) {
        dstNumComponents = 1;
    } else {
        dstNumComponents = 3;
    }

    currDuplexDisposition = PCLmPageContent->duplexDisposition;

    destColorSpace = PCLmPageContent->dstColorSpaceSpefication;

    // Calculate how large the output buffer needs to be based upon the page specifications
    int tmp_outBuffSize = mediaWidthInPixels * currStripHeight * dstNumComponents;

    if (tmp_outBuffSize > currOutBuffSize) {
        // Realloc the pOutBuffer to the correct size
        *pOutBuffer = realloc(*pOutBuffer, tmp_outBuffSize);

        if (*pOutBuffer == NULL) {
            // realloc failed and prev buffer not freed
            return errorOutAndCleanUp();
        }

        outBuffSize = currOutBuffSize = tmp_outBuffSize;
        allocatedOutputBuffer = *pOutBuffer;
        if (NULL == allocatedOutputBuffer) {
            return (errorOutAndCleanUp());
        }
    }

    initOutBuff((char *) *pOutBuffer, outBuffSize);

    // Keep track of the page count
    pageCount++;

    // If we are on a backside and doing duplex, prep for reverse strip order
    if (currDuplexDisposition == duplex_longEdge && !(pageCount % 2) && mirrorBackside) {
        reverseOrder = true;
    } else {
        reverseOrder = false;
    }

    // Calculate the number of injected strips, if any
    if (topMarginInPix) {
        if (topMarginInPix <= currStripHeight) {
            numFullInjectedStrips = 1;
            numFullScanlinesToInject = topMarginInPix;
            numPartialScanlinesToInject = 0;
        } else {
            numFullInjectedStrips = topMarginInPix / currStripHeight;
            numFullScanlinesToInject = currStripHeight;
            numPartialScanlinesToInject =
                    topMarginInPix - (numFullInjectedStrips * currStripHeight);
        }
    }

    writeJobTicket();
    writePDFGrammarPage(mediaWidthInPixels, mediaHeightInPixels, numImageStrips, destColorSpace);
    *iOutBufferSize = totalBytesWrittenToCurrBuff;

    if (!scratchBuffer) {
        // We need to pad the scratchBuffer size to allow for compression expansion (RLE can create
        // compressed segments that are slightly larger than the source.
        size_t len = (size_t) currStripHeight * mediaWidthInPixels * srcNumComponents * 2;
        scratchBuffer = (ubyte *) malloc(len);
        if (!scratchBuffer) {
            return errorOutAndCleanUp();
        }
    }

    mirrorBackside = PCLmPageContent->mirrorBackside;
    firstStrip = true;

    return success;
}

int PCLmGenerator::EndPage(void **pOutBuffer, int *iOutBufferSize) {
    *pOutBuffer = allocatedOutputBuffer;
    initOutBuff((char *) *pOutBuffer, outBuffSize);
    *iOutBufferSize = totalBytesWrittenToCurrBuff;

    // Free up the scratchbuffer at endpage, to allow the next page to have a different size
    if (scratchBuffer) {
        free(scratchBuffer);
        scratchBuffer = NULL;
    }

    return success;
}

int PCLmGenerator::Encapsulate(void *pInBuffer, int inBufferSize, int thisHeight,
        void **pOutBuffer, int *iOutBufferSize) {
    int numCompBytes;
    int scanlineWidth = mediaWidthInPixels * srcNumComponents;
    int numLinesThisCall = thisHeight;
    void *savedInBufferPtr = NULL;
    void *tmpBuffer = NULL;
    void *localInBuffer;
    ubyte *newStripPtr = NULL;

    if (leftoverScanlineBuffer) {
        ubyte *whereAreWe;
        sint32 scanlinesThisTime;
        // The leftover scanlines have already been processed (color-converted and flipped), so
        // just put them into the output buffer.

        // Allocate a temporary buffer to copy leftover and new data into
        tmpBuffer = malloc(scanlineWidth * currStripHeight);
        if (!tmpBuffer) {
            return (errorOutAndCleanUp());
        }

        // Copy leftover scanlines into tmpBuffer
        memcpy(tmpBuffer, leftoverScanlineBuffer, scanlineWidth * numLeftoverScanlines);

        whereAreWe = (ubyte *) tmpBuffer + (scanlineWidth * numLeftoverScanlines);

        scanlinesThisTime = currStripHeight - numLeftoverScanlines;

        // Copy enough scanlines from the real inBuffer to fill out the tmpBuffer
        memcpy(whereAreWe, pInBuffer, scanlinesThisTime * scanlineWidth);

        // Now copy the remaining scanlines from pInBuffer to the leftoverBuffer
        numLeftoverScanlines = thisHeight - scanlinesThisTime;
        assert(leftoverScanlineBuffer);
        whereAreWe = (ubyte *) pInBuffer + (scanlineWidth * numLeftoverScanlines);
        memcpy(leftoverScanlineBuffer, whereAreWe, scanlineWidth * numLeftoverScanlines);
        numLinesThisCall = thisHeight = currStripHeight;

        savedInBufferPtr = pInBuffer;
        localInBuffer = tmpBuffer;
    } else {
        localInBuffer = pInBuffer;
    }

    if (thisHeight > currStripHeight) {
        // Copy raw raster into leftoverScanlineBuffer
        ubyte *ptr;
        numLeftoverScanlines = thisHeight - currStripHeight;
        leftoverScanlineBuffer = malloc(scanlineWidth * numLeftoverScanlines);
        if (!leftoverScanlineBuffer) {
            return (errorOutAndCleanUp());
        }
        ptr = (ubyte *) localInBuffer + scanlineWidth * numLeftoverScanlines;
        memcpy(leftoverScanlineBuffer, ptr, scanlineWidth * numLeftoverScanlines);
        thisHeight = currStripHeight;
    }

    if (NULL == allocatedOutputBuffer) {
        if (tmpBuffer) {
            free(tmpBuffer);
        }
        return (errorOutAndCleanUp());
    }
    *pOutBuffer = allocatedOutputBuffer;
    initOutBuff((char *) *pOutBuffer, outBuffSize);

    if (currDuplexDisposition == duplex_longEdge && !(pageCount % 2)) {
        if (mirrorBackside) {
            prepImageForBacksideDuplex((ubyte *) localInBuffer, numLinesThisCall, currSourceWidth,
                    srcNumComponents);
        }
    }

    if (destColorSpace == grayScale &&
            (sourceColorSpace == deviceRGB || sourceColorSpace == adobeRGB)) {
        colorConvertSource(sourceColorSpace, grayScale, (ubyte *) localInBuffer, currSourceWidth,
                numLinesThisCall);
        // Adjust the scanline width accordingly
        scanlineWidth = mediaWidthInPixels * dstNumComponents;
    }

    if (leftMarginInPix) {
        newStripPtr = shiftStripByLeftMargin((ubyte *) localInBuffer, currSourceWidth,
                currStripHeight, numLinesThisCall, mediaWidthInPixels, leftMarginInPix,
                destColorSpace);
    }

    bool whiteStrip = false;
#ifdef SUPPORT_WHITE_STRIPS
    if (!firstStrip) {
        // PCLm does not print a blank page if all the strips are marked as "/Name /WhiteStrip"
        // so only apply /WhiteStrip to strips after the first
        whiteStrip = isWhiteStrip(pInBuffer, thisHeight * currSourceWidth * srcNumComponents);
    }
#endif

    if (currCompressionDisposition == compressDCT) {
        if (firstStrip && topMarginInPix) {
            ubyte whitePt = 0xff;

            ubyte *tmpStrip = (ubyte *) malloc(scanlineWidth * topMarginInPix);
            memset(tmpStrip, whitePt, scanlineWidth * topMarginInPix);

            for (sint32 stripCntr = 0; stripCntr < numFullInjectedStrips; stripCntr++) {
                write_JPEG_Buff(scratchBuffer, JPEG_QUALITY, mediaWidthInPixels,
                        (sint32) numFullScanlinesToInject, tmpStrip, currRenderResolutionInteger,
                        destColorSpace, &numCompBytes);
                injectJPEG((char *) scratchBuffer, mediaWidthInPixels,
                        (sint32) numFullScanlinesToInject, numCompBytes, destColorSpace, true);
            }

            if (numPartialScanlinesToInject) {
                // Handle the leftover strip
                write_JPEG_Buff(scratchBuffer, JPEG_QUALITY, mediaWidthInPixels,
                        numPartialScanlinesToInject, tmpStrip, currRenderResolutionInteger,
                        destColorSpace, &numCompBytes);
                injectJPEG((char *) scratchBuffer, mediaWidthInPixels, numPartialScanlinesToInject,
                        numCompBytes, destColorSpace, true);
            }

            free(tmpStrip);
        }
        firstStrip = false;

        // We are always going to compress the full strip height, even though the image may be less;
        // this allows the compressed images to be symmetric
        if (numLinesThisCall < currStripHeight) {
            sint32 numLeftoverBytes = (currStripHeight - numLinesThisCall) * currSourceWidth * 3;
            sint32 numImagedBytes = numLinesThisCall * currSourceWidth * 3;

            // End-of-page: we have to white-out the unused section of the source image
            memset((ubyte *) localInBuffer + numImagedBytes, 0xff, numLeftoverBytes);
        }

        if (newStripPtr) {
            write_JPEG_Buff(scratchBuffer, JPEG_QUALITY, mediaWidthInPixels, currStripHeight,
                    newStripPtr, currRenderResolutionInteger, destColorSpace, &numCompBytes);

            free(newStripPtr);
            newStripPtr = NULL;
        } else {
            write_JPEG_Buff(scratchBuffer, JPEG_QUALITY, mediaWidthInPixels, currStripHeight,
                    (JSAMPLE *) localInBuffer, currRenderResolutionInteger, destColorSpace,
                    &numCompBytes);
        }

        injectJPEG((char *) scratchBuffer, mediaWidthInPixels, currStripHeight, numCompBytes,
                destColorSpace, whiteStrip);
    } else if (currCompressionDisposition == compressFlate) {
        uint32 len = numLinesThisCall * scanlineWidth;
        uLongf destSize = len;
        int result;

        if (firstStrip && topMarginInPix) {
            ubyte whitePt = 0xff;

            // We need to inject a blank image-strip with a height==topMarginInPix
            ubyte *tmpStrip = (ubyte *) malloc(scanlineWidth * topMarginInPix);
            uLongf tmpDestSize = destSize;
            memset(tmpStrip, whitePt, scanlineWidth * topMarginInPix);

            for (sint32 stripCntr = 0; stripCntr < numFullInjectedStrips; stripCntr++) {
                result = compress(scratchBuffer, &tmpDestSize, (const Bytef *) tmpStrip,
                        scanlineWidth * numFullScanlinesToInject);
                injectLZStrip(scratchBuffer, tmpDestSize, mediaWidthInPixels,
                        numFullScanlinesToInject, destColorSpace, true);
            }
            if (numPartialScanlinesToInject) {
                result = compress(scratchBuffer, &tmpDestSize, (const Bytef *) tmpStrip,
                        scanlineWidth * numPartialScanlinesToInject);
                injectLZStrip(scratchBuffer, tmpDestSize, mediaWidthInPixels,
                        numPartialScanlinesToInject, destColorSpace, true);
            }
            free(tmpStrip);
        }
        firstStrip = false;

        if (newStripPtr) {
            result = compress(scratchBuffer, &destSize, (const Bytef *) newStripPtr,
                    scanlineWidth * numLinesThisCall);
            free(newStripPtr);
            newStripPtr = NULL;
        } else {
            // Dump the source data
            result = compress(scratchBuffer, &destSize, (const Bytef *) localInBuffer,
                    scanlineWidth * numLinesThisCall);
        }
        injectLZStrip(scratchBuffer, destSize, mediaWidthInPixels, numLinesThisCall, destColorSpace,
                whiteStrip);
    } else if (currCompressionDisposition == compressRLE) {
        int compSize;
        if (firstStrip && topMarginInPix) {
            ubyte whitePt = 0xff;

            // We need to inject a blank image-strip with a height==topMarginInPix

            ubyte *tmpStrip = (ubyte *) malloc(scanlineWidth * topMarginInPix);
            memset(tmpStrip, whitePt, scanlineWidth * topMarginInPix);

            for (sint32 stripCntr = 0; stripCntr < numFullInjectedStrips; stripCntr++) {
                compSize = RLEEncodeImage(tmpStrip, scratchBuffer,
                        scanlineWidth * numFullScanlinesToInject);
                injectRLEStrip(scratchBuffer, compSize, mediaWidthInPixels,
                        numFullScanlinesToInject, destColorSpace, true);
            }

            if (numPartialScanlinesToInject) {
                compSize = RLEEncodeImage(tmpStrip, scratchBuffer,
                        scanlineWidth * numPartialScanlinesToInject);
                injectRLEStrip(scratchBuffer, compSize, mediaWidthInPixels,
                        numPartialScanlinesToInject, destColorSpace, true);
            }

            free(tmpStrip);
        }
        firstStrip = false;

        if (newStripPtr) {
            compSize = RLEEncodeImage(newStripPtr, scratchBuffer,
                    scanlineWidth * numLinesThisCall);
            free(newStripPtr);
            newStripPtr = NULL;
        } else {
            compSize = RLEEncodeImage((ubyte *) localInBuffer, scratchBuffer,
                    scanlineWidth * numLinesThisCall);
        }

        injectRLEStrip(scratchBuffer, compSize, mediaWidthInPixels, numLinesThisCall,
                destColorSpace, whiteStrip);
    } else {
        assert(0);
    }

    *iOutBufferSize = totalBytesWrittenToCurrBuff;

    if (savedInBufferPtr) {
        pInBuffer = savedInBufferPtr;
    }

    if (tmpBuffer) {
        free(tmpBuffer);
    }

    if (newStripPtr) {
        free(newStripPtr);
    }

    return success;
}

int PCLmGenerator::GetPclmMediaDimensions(const char *mediaRequested,
        PCLmPageSetup *myPageInfo) {
    int i = 0;
    int result = 99;

    int iRenderResolutionInteger = 0;
    if (myPageInfo->destinationResolution == res300) {
        iRenderResolutionInteger = 300;
    } else if (myPageInfo->destinationResolution == res600) {
        iRenderResolutionInteger = 600;
    } else if (myPageInfo->destinationResolution == res1200) {
        iRenderResolutionInteger = 1200;
    } else {
        assert(0);
    }

    for (i = 0; i < SUPPORTED_MEDIA_SIZE_COUNT; i++) {
        if (strcasecmp(mediaRequested, SupportedMediaSizes[i].PCL6Name) == 0) {
            myPageInfo->mediaWidth = floorf(
                    _MI_TO_POINTS(SupportedMediaSizes[i].WidthInInches));
            myPageInfo->mediaHeight = floorf(
                    _MI_TO_POINTS(SupportedMediaSizes[i].HeightInInches));
            myPageInfo->mediaWidthInPixels = floorf(
                    _MI_TO_PIXELS(SupportedMediaSizes[i].WidthInInches,
                            iRenderResolutionInteger));
            myPageInfo->mediaHeightInPixels = floorf(
                    _MI_TO_PIXELS(SupportedMediaSizes[i].HeightInInches,
                            iRenderResolutionInteger));
            result = i;
            break;  // we found a match, so break out of loop
        }
    }

    if (i == SUPPORTED_MEDIA_SIZE_COUNT) {
        // media size not found, defaulting to letter
        printf("PCLmGenerator get_pclm_media_size(): media size, %s, NOT FOUND, setting to letter",
                mediaRequested);
        result = GetPclmMediaDimensions("LETTER", myPageInfo);
    }

    return result;
}

void PCLmGenerator::FreeBuffer(void *pBuffer) {
    if (jobOpen == job_closed && pBuffer) {
        if (pBuffer == allocatedOutputBuffer) {
            allocatedOutputBuffer = NULL;
        }
        free(pBuffer);
    }
}