/* pnmtops.c - read a PNM image and produce a PostScript program.

   Copyright information is at end of file.

   We produce two main kinds of Postscript program:

      1) Use built in Postscript filters /ASCII85Decode, /ASCIIHexDecode,
         /RunLengthDecode, and /FlateDecode; and

         We use methods we learned from Dirk Krause's program Bmeps and
         raster encoding code copied almost directly from Bmeps.

      2) Use our own filters and redefine /readstring .  This is aboriginal
         Netpbm code, from when Postscript was young.

   (2) is the default, because it's been working for ages and we have
   more confidence in it.  But (1) gives more options.  The user
   selects (1) with the -psfilter option.

   We also do a few other bold new things only when the user specifies
   -psfilter, because we're not sure they work for everyone.

   (I actually don't know Postscript, so some of this description, not to
   mention the code, may be totally bogus.)

   NOTE: it is possible to put transparency information in an
   encapsulated Postscript program.  Bmeps does this.  We don't.  It
   might be hard to do, because in Postscript, the transparency information
   goes in separate from the rest of the raster.
*/

#define _BSD_SOURCE  /* Make sure string.h contains strdup() */
#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */

#include <string.h>
#include <libgen.h>
#include <assert.h>
#include "pam.h"
#include "mallocvar.h"
#include "shhopt.h"
#include "nstring.h"
#include "bmepsoe.h"

#define STRSEQ(a,b) (strncmp((a), (b), sizeof(a)) == 0)

struct cmdline_info {
    /* All the information the user supplied in the command line,
       in a form easy for the program to use.
    */
    const char *input_filespec;  /* Filespecs of input file */
    float scale;
    unsigned int dpiX;     /* horiz component of DPI option */
    unsigned int dpiY;     /* vert component of DPI option */
    unsigned int width;              /* in 1/72 inch */
    unsigned int height;             /* in 1/72 inch */
    unsigned int mustturn;
    bool         canturn;
    unsigned int rle;
    bool         center;
    unsigned int imagewidth;         /* in 1/72 inch; zero if unspec */
    unsigned int imageheight;        /* in 1/72 inch; zero if unspec */
    unsigned int equalpixels;
    unsigned int setpage;
    bool         showpage;
    unsigned int level;
    unsigned int levelSpec;
    unsigned int psfilter;
    unsigned int flate;
    unsigned int ascii85;
    unsigned int dict;
    unsigned int vmreclaim;
    unsigned int verbose;
};


static bool verbose;


static void
parse_dpi(const char *   const dpiOpt, 
          unsigned int * const dpiXP, 
          unsigned int * const dpiYP) {

    char *dpistr2;
    unsigned int dpiX, dpiY;

    dpiX = strtol(dpiOpt, &dpistr2, 10);
    if (dpistr2 == dpiOpt) 
        pm_error("Invalid value for -dpi: '%s'.  Must be either number "
                 "or NxN ", dpiOpt);
    else {
        if (*dpistr2 == '\0') {
            *dpiXP = dpiX;
            *dpiYP = dpiX;
        } else if (*dpistr2 == 'x') {
            char * dpistr3;

            dpistr2++;  /* Move past 'x' */
            dpiY = strtol(dpistr2, &dpistr3, 10);        
            if (dpistr3 != dpistr2 && *dpistr3 == '\0') {
                *dpiXP = dpiX;
                *dpiYP = dpiY;
            } else {
                pm_error("Invalid value for -dpi: '%s'.  Must be either "
                         "number or NxN", dpiOpt);
            }
        }
    }
}



static void
parseCommandLine(int argc, char ** argv,
                 struct cmdline_info *cmdlineP) {

    unsigned int imagewidth_spec, imageheight_spec;
    float imagewidth, imageheight;
    unsigned int center, nocenter;
    unsigned int nosetpage;
    float width, height;
    unsigned int noturn;
    unsigned int showpage, noshowpage;
    const char *dpiOpt;
    unsigned int dpiSpec;

    optStruct3 opt;
    unsigned int option_def_index = 0;
    optEntry *option_def;

    MALLOCARRAY_NOFAIL(option_def, 100);

    OPTENT3(0, "scale",       OPT_FLOAT, &cmdlineP->scale, NULL,         0);
    OPTENT3(0, "dpi",         OPT_STRING, &dpiOpt,         &dpiSpec,     0);
    OPTENT3(0, "width",       OPT_FLOAT, &width,           NULL,         0);
    OPTENT3(0, "height",      OPT_FLOAT, &height,          NULL,         0);
    OPTENT3(0, "psfilter",    OPT_FLAG,  NULL, &cmdlineP->psfilter,      0);
    OPTENT3(0, "turn",        OPT_FLAG,  NULL, &cmdlineP->mustturn,      0);
    OPTENT3(0, "noturn",      OPT_FLAG,  NULL, &noturn,                  0);
    OPTENT3(0, "rle",         OPT_FLAG,  NULL, &cmdlineP->rle,           0);
    OPTENT3(0, "runlength",   OPT_FLAG,  NULL, &cmdlineP->rle,           0);
    OPTENT3(0, "ascii85",     OPT_FLAG,  NULL, &cmdlineP->ascii85,       0);
    OPTENT3(0, "center",      OPT_FLAG,  NULL, &center,                  0);
    OPTENT3(0, "nocenter",    OPT_FLAG,  NULL, &nocenter,                0);
    OPTENT3(0, "equalpixels", OPT_FLAG,  NULL, &cmdlineP->equalpixels,   0);
    OPTENT3(0, "imagewidth",  OPT_FLOAT, &imagewidth,  &imagewidth_spec, 0);
    OPTENT3(0, "imageheight", OPT_FLOAT, &imageheight, &imageheight_spec,0);
    OPTENT3(0, "nosetpage",   OPT_FLAG,  NULL, &nosetpage,               0);
    OPTENT3(0, "setpage",     OPT_FLAG,  NULL, &cmdlineP->setpage,       0);
    OPTENT3(0, "noshowpage",  OPT_FLAG,  NULL, &noshowpage,              0);
    OPTENT3(0, "flate",       OPT_FLAG,  NULL, &cmdlineP->flate,         0);
    OPTENT3(0, "dict",        OPT_FLAG,  NULL, &cmdlineP->dict,          0);
    OPTENT3(0, "vmreclaim",   OPT_FLAG,  NULL, &cmdlineP->vmreclaim,     0);
    OPTENT3(0, "showpage",    OPT_FLAG,  NULL, &showpage,                0);
    OPTENT3(0, "verbose",     OPT_FLAG,  NULL, &cmdlineP->verbose,       0);
    OPTENT3(0, "level",       OPT_UINT, &cmdlineP->level, 
            &cmdlineP->levelSpec,              0);
    
    /* DEFAULTS */
    cmdlineP->scale = 1.0;
    width = 8.5;
    height = 11.0;

    opt.opt_table = option_def;
    opt.short_allowed = FALSE;
    opt.allowNegNum = FALSE;

    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);

    if (cmdlineP->mustturn && noturn)
        pm_error("You cannot specify both -turn and -noturn");
    if (center && nocenter)
        pm_error("You cannot specify both -center and -nocenter");
    if (showpage && noshowpage)
        pm_error("You cannot specify both -showpage and -noshowpage");
    if (cmdlineP->setpage && nosetpage)
        pm_error("You cannot specify both -setpage and -nosetpage");

    if (dpiSpec)
        parse_dpi(dpiOpt, &cmdlineP->dpiX, &cmdlineP->dpiY);
    else {
        cmdlineP->dpiX = 300;
        cmdlineP->dpiY = 300;
    }

    cmdlineP->center  =  !nocenter;
    cmdlineP->canturn =  !noturn;
    cmdlineP->showpage = !noshowpage;
    
    cmdlineP->width  = width * 72;
    cmdlineP->height = height * 72;

    if (imagewidth_spec)
        cmdlineP->imagewidth = imagewidth * 72;
    else
        cmdlineP->imagewidth = 0;
    if (imageheight_spec)
        cmdlineP->imageheight = imageheight * 72;
    else
        cmdlineP->imageheight = 0;

    if (!cmdlineP->psfilter &&
        (cmdlineP->flate || cmdlineP->ascii85))
        pm_error("You must specify -psfilter in order to specify "
                 "-flate or -ascii85");

    if (cmdlineP->rle && cmdlineP->flate)
        pm_error("You cannot specify both -rle and -flate");

    if (argc-1 == 0) 
        cmdlineP->input_filespec = "-";
    else if (argc-1 != 1)
        pm_error("Program takes zero or one argument (filename).  You "
                 "specified %d", argc-1);
    else
        cmdlineP->input_filespec = argv[1];

}


/*===========================================================================
  The native output encoder.  This is archaic and uses global variables.
  It is probably obsoleted by the bmeps output encoder; we just haven't
  had a chance to verify that yet.
===========================================================================*/

/*----------------------------------------------------------------------------
   The following global variables are the native output encoder state.
-----------------------------------------------------------------------------*/
static unsigned int itemsinline;
    /* The number of items in the line we are currently building */
static unsigned int bitsinitem;
    /* The number of bits filled so far in the item we are currently
       building 
    */
static unsigned int rlebitsinitem;
    /* The number of bits filled so far in the item we are currently
       building 
    */
static unsigned int bitspersample;
static unsigned int item, bitshift, items;
static unsigned int rleitem, rlebitshift;
static unsigned int repeat, itembuf[128], count, repeatitem, repeatcount;



static void
initNativeOutputEncoder(bool const rle, unsigned int const bitspersample) {
/*----------------------------------------------------------------------------
   Initialize the native output encoder.  Call this once per
   Postscript image that you will write with putitem(), before for the
   first putitem().

   We initialize the item putter state variables, which are the
   global variable defined above.
-----------------------------------------------------------------------------*/
    itemsinline = 0;
    items = 0;

    if (rle) {
        rleitem = 0;
        rlebitsinitem = 0;
        rlebitshift = 8 - bitspersample;
        repeat = 1;
        count = 0;
    } else {
        item = 0;
        bitsinitem = 0;
        bitshift = 8 - bitspersample;
    }

}



static void
putitem(void) {
    const char* const hexits = "0123456789abcdef";

    if (itemsinline == 30) {
        putchar('\n');
        itemsinline = 0;
    }
    putchar(hexits[item >> 4]);
    putchar(hexits[item & 15]);
    ++itemsinline;
    ++items;
    item = 0;
    bitsinitem = 0;
    bitshift = 8 - bitspersample;
}



static void
flushitem() {
    if (bitsinitem > 0)
        putitem();
}



static void 
putxelval(xelval const xv) {
    if (bitsinitem == 8)
        putitem();
    item += xv << bitshift;
    bitsinitem += bitspersample;
    bitshift -= bitspersample;
}



static void
rleputbuffer() {
    if (repeat) {
        item = 256 - count;
        putitem();
        item = repeatitem;
        putitem();
    } else {
        unsigned int i;
    
        item = count - 1;
        putitem();
        for (i = 0; i < count; ++i) {
            item = itembuf[i];
            putitem();
        }
    }
    repeat = 1;
    count = 0;
}



static void
rleputitem() {
    int i;

    if ( count == 128 )
        rleputbuffer();

    if ( repeat && count == 0 )
    { /* Still initializing a repeat buf. */
        itembuf[count] = repeatitem = rleitem;
        ++count;
    }
    else if ( repeat )
    { /* Repeating - watch for end of run. */
        if ( rleitem == repeatitem )
        { /* Run continues. */
            itembuf[count] = rleitem;
            ++count;
        }
        else
        { /* Run ended - is it long enough to dump? */
            if ( count > 2 )
            { /* Yes, dump a repeat-mode buffer and start a new one. */
                rleputbuffer();
                itembuf[count] = repeatitem = rleitem;
                ++count;
            }
            else
            { /* Not long enough - convert to non-repeat mode. */
                repeat = 0;
                itembuf[count] = repeatitem = rleitem;
                ++count;
                repeatcount = 1;
            }
        }
    }
    else
    { /* Not repeating - watch for a run worth repeating. */
        if ( rleitem == repeatitem )
        { /* Possible run continues. */
            ++repeatcount;
            if ( repeatcount > 3 )
            { /* Long enough - dump non-repeat part and start repeat. */
                count = count - ( repeatcount - 1 );
                rleputbuffer();
                count = repeatcount;
                for ( i = 0; i < count; ++i )
                    itembuf[i] = rleitem;
            }
            else
            { /* Not long enough yet - continue as non-repeat buf. */
                itembuf[count] = rleitem;
                ++count;
            }
        }
        else
        { /* Broken run. */
            itembuf[count] = repeatitem = rleitem;
            ++count;
            repeatcount = 1;
        }
    }

    rleitem = 0;
    rlebitsinitem = 0;
    rlebitshift = 8 - bitspersample;
}



static void 
rleputxelval(xelval const xv) {
    if (rlebitsinitem == 8)
        rleputitem();
    rleitem += xv << rlebitshift;
    rlebitsinitem += bitspersample;
    rlebitshift -= bitspersample;
}



static void
rleflush() {
    if (rlebitsinitem > 0)
        rleputitem();
    if (count > 0)
        rleputbuffer();
}


static void
flushNativeOutput(bool const rle) {
    if (rle)
        rleflush();
    else
        flushitem();
    printf("\n");
}
        
/*===========================================================================
  The BMEPS output encoder.
===========================================================================*/

/* This code is just a wrapper around the output encoder that is part of
   Bmeps, to give it better modularity.
*/

struct bmepsoe {
    Output_Encoder * oeP;
    int * rleBuffer;
    Byte * flateInBuffer;
    Byte * flateOutBuffer;
};



static void
createBmepsOutputEncoder(struct bmepsoe ** const bmepsoePP,
                         FILE *            const ofP,
                         bool              const rle,
                         bool              const flate,
                         bool              const ascii85) {

    unsigned int const FLATE_IN_SIZE = 16384;
    unsigned int const FLATE_OUT_SIZE = 17408;

    struct bmepsoe * bmepsoeP;
    int mode;

    MALLOCVAR_NOFAIL(bmepsoeP);
    MALLOCVAR_NOFAIL(bmepsoeP->oeP);
    MALLOCARRAY_NOFAIL(bmepsoeP->rleBuffer, 129);
    MALLOCARRAY_NOFAIL(bmepsoeP->flateInBuffer, FLATE_IN_SIZE);
    MALLOCARRAY_NOFAIL(bmepsoeP->flateOutBuffer, FLATE_OUT_SIZE);

    mode = 0;
    if (rle)
        mode |= OE_RL;
    if (flate)
        mode |= OE_FLATE;
    if (ascii85)
        mode |= OE_ASC85;

    oe_init(bmepsoeP->oeP, ofP, mode, 9, 
            bmepsoeP->rleBuffer, 
            bmepsoeP->flateInBuffer, FLATE_IN_SIZE,
            bmepsoeP->flateOutBuffer, FLATE_OUT_SIZE);

    *bmepsoePP = bmepsoeP;
}



static void
destroyBmepsOutputEncoder(struct bmepsoe * const bmepsoeP) {
    
    free(bmepsoeP->rleBuffer);
    free(bmepsoeP->flateInBuffer);
    free(bmepsoeP->flateOutBuffer);
    
    free(bmepsoeP);
}



static void
outputBmepsSample(struct bmepsoe * const bmepsoeP,
                  unsigned int     const sampleValue) {

    assert(sampleValue < 256);

    oe_byte_add(bmepsoeP->oeP, sampleValue);
}



static void
flushBmepsOutput(struct bmepsoe * const bmepsoeP) {
    oe_byte_flush(bmepsoeP->oeP);
}


/*============================================================================
   END OF OUTPUT ENCODERS
============================================================================*/


static void
putEnd(bool const showpage, 
       bool const psfilter,
       bool const ascii85,
       bool const dict,
       bool const vmreclaim) {

    if (psfilter) {
        if (ascii85)
            printf("%s\n", "~>");
        else
            printf("%s\n", ">");
    }

    printf("currentdict /inputf undef\n");
    printf("currentdict /picstr undef\n");
    printf("currentdict /rpicstr undef\n");
    printf("currentdict /gpicstr undef\n");
    printf("currentdict /bpicstr undef\n");

    if (dict)
        printf("end\n");

    if (vmreclaim)
        printf("1 vmreclaim\n");

    printf( "grestore\n" );

    if (showpage)
        printf( "showpage\n" );
    printf( "%%%%Trailer\n" );
}



static void
computeImagePosition(int     const dpiX, 
                     int     const dpiY, 
                     int     const icols, 
                     int     const irows,
                     bool    const mustturn,
                     bool    const canturn,
                     bool    const center,
                     int     const pagewid, 
                     int     const pagehgt, 
                     float   const requested_scale,
                     float   const imagewidth,
                     float   const imageheight,
                     bool    const equalpixels,
                     float * const scolsP,
                     float * const srowsP,
                     float * const llxP, 
                     float * const llyP,
                     bool *  const turnedP ) {
/*----------------------------------------------------------------------------
   Determine where on the page the image is to go.  This means position,
   dimensions, and orientation.

   icols/irows are the dimensions of the PNM input in xels.

   'mustturn' means we are required to rotate the image.

   'canturn' means we may rotate the image if it fits better, but don't
   have to.

   *scolsP, *srowsP are the dimensions of the image in 1/72 inch.

   *llxP, *llyP are the coordinates, in 1/72 inch, of the lower left
   corner of the image on the page.

   *turnedP is true iff the image is to be rotated 90 degrees on the page.

   imagewidth/imageheight are the requested dimensions of the image on
   the page, in 1/72 inch.  Image will be as large as possible within
   those dimensions.  Zero means unspecified, so 'scale', 'pagewid',
   'pagehgt', 'irows', and 'icols' determine image size.

   'equalpixels' means the user wants one printed pixel per input pixel.
   It is inconsistent with imagewidth or imageheight != 0

   'requested_scale' is meaningful only when imageheight/imagewidth == 0
   and equalpixels == FALSE.  It tells how many inches the user wants
   72 pixels of input to occupy, if it fits on the page.
-----------------------------------------------------------------------------*/
    int cols, rows;
        /* Number of columns, rows of input xels in the output, as
           rotated if applicable
        */
    bool shouldturn;  /* The image fits the page better if we turn it */
    
    if (icols > irows && pagehgt > pagewid)
        shouldturn = TRUE;
    else if (irows > icols && pagewid > pagehgt)
        shouldturn = TRUE;
    else
        shouldturn = FALSE;

    if (mustturn || (canturn && shouldturn)) {
        *turnedP = TRUE;
        cols = irows;
        rows = icols;
    } else {
        *turnedP = FALSE;
        cols = icols;
        rows = irows;
    }
    if (equalpixels) {
        *scolsP = (72.0/dpiX)*cols;
        *srowsP = (72.0/dpiY)*rows;
    } else if (imagewidth > 0 || imageheight > 0) {
        float scale;

        if (imagewidth == 0)
            scale = (float) imageheight/rows;
        else if (imageheight == 0)
            scale = (float) imagewidth/cols;
        else
            scale = MIN((float)imagewidth/cols, (float)imageheight/rows);
        
        *scolsP = cols*scale;
        *srowsP = rows*scale;
    } else {
        /* He didn't give us a bounding box for the image so figure
           out output image size from other inputs.
        */
        const int devpixX = dpiX / 72.0 + 0.5;        
        const int devpixY = dpiY / 72.0 + 0.5;        
            /* How many device pixels make up 1/72 inch, rounded to
               nearest integer */
        const float pixfacX = 72.0 / dpiX * devpixX;  /* 1, approx. */
        const float pixfacY = 72.0 / dpiY * devpixY;  /* 1, approx. */
        float scale;

        scale = MIN(requested_scale, 
                    MIN((float)pagewid/cols, (float)pagehgt/rows));

        *scolsP = scale * cols * pixfacX;
        *srowsP = scale * rows * pixfacY;
        
        if (scale != requested_scale)
            pm_message("warning, image too large for page, rescaling to %g", 
                       scale );

        /* Before May 2001, Pnmtops enforced a 5% margin around the page.
           If the image would be too big to leave a 5% margin, Pnmtops would
           scale it down.  But people have images that are exacty the size
           of a page, e.g. because they created them with Sane's 'scanimage'
           program from a full page of input.  So we removed the gratuitous
           5% margin.  -Bryan.
        */
    }
    *llxP = (center) ? ( pagewid - *scolsP ) / 2 : 0;
    *llyP = (center) ? ( pagehgt - *srowsP ) / 2 : 0;


    if (verbose)
        pm_message("Image will be %3.2f points wide by %3.2f points high, "
                   "left edge %3.2f points from left edge of page, "
                   "bottom edge %3.2f points from top of page; "
                   "%sturned to landscape orientation",
                   *scolsP, *srowsP, *llxP, *llyP, *turnedP ? "" : "NOT ");
}




static void
setupReadstringPsFilter(bool         const rle,
                        bool         const flate,
                        bool         const ascii85,
                        bool         const color,
                        unsigned int const width) {
/*----------------------------------------------------------------------------
   Output to Standard Output Postscript statements to set up the arguments
   to /readstring for using built in Postscript input filters.

   (These filters exist only in Postscript Level 2 and higher).

   The statements set /picstr and /inputf.
-----------------------------------------------------------------------------*/
    printf("/picstr\n  %u string\ndef\n", color ? 3 * width : width);
    
    printf("/inputf\n  currentfile\n");
    if (ascii85)
        printf("  /ASCII85Decode filter\n");
    else 
        printf("  /ASCIIHexDecode filter\n");
    if (rle)
        printf("  /RunLengthDecode filter\n");
    if (flate)
        printf("  /FlateDecode filter\n");
    printf("def\n");
}



static void
defineReadstring(bool const rle) {
/*----------------------------------------------------------------------------
   Write to Standard Output Postscript statements to define /readstring.
-----------------------------------------------------------------------------*/
    if (rle) {
        printf("/rlestr1 1 string def\n");
        printf("/readrlestring {\n");             /* s -- nr */
        printf("  /rlestr exch def\n");           /* - */
        printf("  currentfile rlestr1 readhexstring pop\n");  /* s1 */
        printf("  0 get\n");                  /* c */
        printf("  dup 127 le {\n");               /* c */
        printf("    currentfile rlestr 0\n");         /* c f s 0 */
        printf("    4 3 roll\n");             /* f s 0 c */
        printf("    1 add  getinterval\n");           /* f s */
        printf("    readhexstring pop\n");            /* s */
        printf("    length\n");               /* nr */
        printf("  } {\n");                    /* c */
        printf("    256 exch sub dup\n");         /* n n */
        printf("    currentfile rlestr1 readhexstring pop\n");/* n n s1 */
        printf("    0 get\n");                /* n n c */
        printf("    exch 0 exch 1 exch 1 sub {\n");       /* n c 0 1 n-1*/
        printf("      rlestr exch 2 index put\n");
        printf("    } for\n");                /* n c */
        printf("    pop\n");                  /* nr */
        printf("  } ifelse\n");               /* nr */
        printf("} bind def\n");
        printf("/readstring {\n");                /* s -- s */
        printf("  dup length 0 {\n");             /* s l 0 */
        printf("    3 copy exch\n");              /* s l n s n l*/
        printf("    1 index sub\n");              /* s l n s n r*/
        printf("    getinterval\n");              /* s l n ss */
        printf("    readrlestring\n");            /* s l n nr */
        printf("    add\n");                  /* s l n */
        printf("    2 copy le { exit } if\n");        /* s l n */
        printf("  } loop\n");                 /* s l l */
        printf("  pop pop\n");                /* s */
        printf("} bind def\n");
    } else {
        printf("/readstring {\n");                /* s -- s */
        printf("  currentfile exch readhexstring pop\n");
        printf("} bind def\n");
    }
}



static void
setupReadstringNative(bool const rle,
                      bool const color,
                      unsigned int const icols, 
                      unsigned int const padright, 
                      unsigned int const bps) {
/*----------------------------------------------------------------------------
   Write to Standard Output statements to define /readstring and also
   arguments for it (/picstr or /rpicstr, /gpicstr, and /bpicstr).
-----------------------------------------------------------------------------*/
    unsigned int const bytesPerRow = (icols + padright) * bps / 8;

    defineReadstring(rle);
    
    if (color) {
        printf("/rpicstr %d string def\n", bytesPerRow);
        printf("/gpicstr %d string def\n", bytesPerRow);
        printf("/bpicstr %d string def\n", bytesPerRow);
    } else
        printf("/picstr %d string def\n", bytesPerRow);
}



static void
putReadstringPsfilter(unsigned int const postscriptLevel,
                      bool         const color) {

    if (postscriptLevel > 1) {
        printf("{ inputf picstr readstring pop }\n");
        if (color)
            printf("false 3\n");
    } else
        printf("{ currentfile pstr readhexstring pop }\n");
}



static void
putReadstringNative(bool const color) {
    if (color) {
        printf("{ rpicstr readstring }\n");
        printf("{ gpicstr readstring }\n");
        printf("{ bpicstr readstring }\n");
        printf("true 3\n");
    } else
        printf("{ picstr readstring }\n");
}



static void
putinit(unsigned int const postscriptLevel,
        char         const name[], 
        int          const icols, 
        int          const irows, 
        float        const scols, 
        float        const srows,
        float        const llx, 
        float        const lly,
        int          const padright, 
        int          const bps,
        int          const pagewid, 
        int          const pagehgt,
        bool         const color, 
        bool         const turned, 
        bool         const rle,
        bool         const flate,
        bool         const ascii85,
        bool         const setpage,
        bool         const psfilter,
        bool         const dict) {
/*----------------------------------------------------------------------------
   Write out to Standard Output the headers stuff for the Postscript
   program (everything up to the raster).
-----------------------------------------------------------------------------*/
    printf("%%!PS-Adobe-%u.0 EPSF-%u.0\n", postscriptLevel, postscriptLevel);
    printf("%%%%Creator: pnmtops\n");
    printf("%%%%Title: %s.ps\n", name);
    printf("%%%%Pages: 1\n");
    printf(
        "%%%%BoundingBox: %d %d %d %d\n",
        (int) llx, (int) lly,
        (int) (llx + scols + 0.5), (int) (lly + srows + 0.5));
    printf("%%%%EndComments\n");
    printf("%%%%Page: Image0 1\n");

    if (dict)
        printf("4 dict begin\n");
    
    if (psfilter)
        setupReadstringPsFilter(rle, flate, ascii85, color, icols);
    else 
        setupReadstringNative(rle, color, icols, padright, bps);

    printf("%%%%EndProlog\n");
    printf("%%%%Page: 1 1\n");
    if (setpage)
        printf("<< /PageSize [ %d %d ] /ImagingBBox null >> setpagedevice\n",
                pagewid, pagehgt);
    printf("gsave\n");
    printf("%g %g translate\n", llx, lly);
    printf("%g %g scale\n", scols, srows);
    if (turned)
        printf("0.5 0.5 translate  90 rotate  -0.5 -0.5 translate\n");
    printf("%d %d %d\n", icols, irows, bps);
    printf("[ %d 0 0 -%d 0 %d ]\n", icols, irows, irows);

    if (psfilter)
        putReadstringPsfilter(postscriptLevel, color);
    else
        putReadstringNative(color);

    if (color)
        printf("colorimage\n");
    else
        printf("image\n");
}



static void
computeDepth(xelval         const input_maxval, 
             unsigned int * const bitspersampleP,
             unsigned int * const ps_maxvalP) {
/*----------------------------------------------------------------------------
   Figure out how many bits will represent each sample in the Postscript
   program, and the maxval of the Postscript program samples.  The maxval
   is just the maximum value allowable in the number of bits.
-----------------------------------------------------------------------------*/
    unsigned int const bitsRequiredByMaxval = pm_maxvaltobits(input_maxval);

    if (bitsRequiredByMaxval <= 1)
        *bitspersampleP = 1;
    else if (bitsRequiredByMaxval > 1 && bitsRequiredByMaxval <= 4)
        *bitspersampleP = 4;
    else 
        *bitspersampleP = 8;

    if (*bitspersampleP < bitsRequiredByMaxval)
        pm_message("Maxval of input requires %u bit samples for full "
                   "resolution, but we are using the Postscript maximum "
                   "of %u", bitsRequiredByMaxval, *bitspersampleP);

    *ps_maxvalP = pm_bitstomaxval(*bitspersampleP);

    if (verbose)
        pm_message("Input maxval is %u.  Postscript raster will have "
                   "%u bits per sample, so maxval = %u",
                   input_maxval, *bitspersampleP, *ps_maxvalP);
}    



static void
convertRowNative(struct pam * const pamP, 
                 tuple *      const tuplerow, 
                 unsigned int const ps_maxval, 
                 bool         const rle, 
                 unsigned int const padright) {

    unsigned int plane;

    for (plane = 0; plane < pamP->depth; ++plane) {
        unsigned int col;
        for (col= 0; col < pamP->width; ++col) {
            sample const scaledSample = 
                pnm_scalesample(tuplerow[col][plane], pamP->maxval, ps_maxval);

            if ( rle )
                rleputxelval(scaledSample);
            else
                putxelval(scaledSample);
        }
        for (col = 0; col < padright; ++col)
            if (rle)
                rleputxelval(0);
        else
            putxelval(0);
        if (rle)
            rleflush();
    }
}



static void
convertRowPsFilter(struct pam *     const pamP,
                   tuple *          const tuplerow,
                   struct bmepsoe * const bmepsoeP) {

    unsigned int const psMaxval = 255;
    unsigned int col;

    tuple scaledTuple;
    
    scaledTuple = pnm_allocpamtuple(pamP);

    for (col = 0; col < pamP->width; ++col) {
        unsigned int plane;
        pnm_scaletuple(pamP, scaledTuple, tuplerow[col], psMaxval);
        
        for (plane = 0; plane < pamP->depth; ++plane)
            outputBmepsSample(bmepsoeP, scaledTuple[plane]);
    }
    pnm_freepamtuple(scaledTuple);
}



static void
selectPostscriptLevel(bool           const levelIsGiven,
                      unsigned int   const levelGiven,
                      bool           const color,
                      bool           const dict,
                      bool           const flate,
                      bool           const ascii85,
                      unsigned int * const postscriptLevelP) {

    unsigned int const maxPermittedLevel = 
        levelIsGiven ? levelGiven : UINT_MAX;
    unsigned int minPossibleLevel;

    if (color) {
        minPossibleLevel = MAX(minPossibleLevel, 2);
        if (2 > maxPermittedLevel)
            pm_error("Color requires at least Postscript level 2");
    }
    if (dict) {
        minPossibleLevel = MAX(minPossibleLevel, 2);
        if (2 > maxPermittedLevel)
            pm_error("-dict requires at least Postscript level 2");
    }
    if (flate) {
        minPossibleLevel = MAX(minPossibleLevel, 2);
        if (2 > maxPermittedLevel)
            pm_error("flate compression requires at least Postscript level 2");
    }
    if (ascii85) {
        minPossibleLevel = MAX(minPossibleLevel, 2);
        if (2 > maxPermittedLevel)
            pm_error("ascii85 encoding requires at least Postscript level 2");
    }
    if (levelIsGiven)
        *postscriptLevelP = levelGiven;
    else
        *postscriptLevelP = minPossibleLevel;
}



static void
convertPage(FILE * const ifP, 
            int    const turnflag, 
            int    const turnokflag, 
            bool   const psfilter,
            bool   const rle, 
            bool   const flate,
            bool   const ascii85,
            bool   const setpage,
            bool   const showpage,
            bool   const center, 
            float  const scale,
            int    const dpiX, 
            int    const dpiY, 
            int    const pagewid, 
            int    const pagehgt,
            int    const imagewidth, 
            int    const imageheight, 
            bool   const equalpixels,
            char   const name[],
            bool   const dict,
            bool   const vmreclaim,
            bool   const levelIsGiven,
            bool   const levelGiven) {
    
    struct pam inpam;
    tuple* tuplerow;
    unsigned int padright;
        /* Number of bits we must add to the right end of each Postscript
           output line in order to have an integral number of bytes of output.
           E.g. at 2 bits per sample with 10 columns, this would be 4.
        */
    int row;
    unsigned int ps_maxval;
        /* The maxval of the Postscript program */
    float scols, srows;
    float llx, lly;
    bool turned;
    bool color;
    unsigned int postscriptLevel;
    struct bmepsoe * bmepsoeP;

    pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
    
    if (!STRSEQ(inpam.tuple_type, PAM_PBM_TUPLETYPE) &&
        !STRSEQ(inpam.tuple_type, PAM_PGM_TUPLETYPE) &&
        !STRSEQ(inpam.tuple_type, PAM_PPM_TUPLETYPE))
        pm_error("Unrecognized tuple type %s.  This program accepts only "
                 "PBM, PGM, PPM, and equivalent PAM input images", 
                 inpam.tuple_type);

    color = STRSEQ(inpam.tuple_type, PAM_PPM_TUPLETYPE);
    
    selectPostscriptLevel(levelIsGiven, levelGiven, color, 
                          dict, flate, ascii85, &postscriptLevel);
    
    if (color)
        pm_message("generating color Postscript program.");

    computeDepth(inpam.maxval, &bitspersample, &ps_maxval);
    {
        unsigned int const realBitsPerLine = inpam.width * bitspersample;
        unsigned int const paddedBitsPerLine = ((realBitsPerLine + 7) / 8) * 8;
        padright = (paddedBitsPerLine - realBitsPerLine) / bitspersample;
    }
    /* In positioning/scaling the image, we treat the input image as if
       it has a density of 72 pixels per inch.
    */
    computeImagePosition(dpiX, dpiY, inpam.width, inpam.height, 
                         turnflag, turnokflag, center,
                         pagewid, pagehgt, scale, imagewidth, imageheight,
                         equalpixels,
                         &scols, &srows, &llx, &lly, &turned);
    
    putinit(postscriptLevel, name, inpam.width, inpam.height, 
            scols, srows, llx, lly, padright, bitspersample, 
            pagewid, pagehgt, color,
            turned, rle, flate, ascii85, setpage, psfilter, dict);

    createBmepsOutputEncoder(&bmepsoeP, stdout, rle, flate, ascii85);
    initNativeOutputEncoder(rle, bitspersample);

    tuplerow = pnm_allocpamrow(&inpam);

    for (row = 0; row < inpam.height; ++row) {
        pnm_readpamrow(&inpam, tuplerow);
        if (psfilter)
            convertRowPsFilter(&inpam, tuplerow, bmepsoeP);
        else
            convertRowNative(&inpam, tuplerow, ps_maxval, rle, padright);
    }

    pnm_freepamrow(tuplerow);

    if (psfilter)
        flushBmepsOutput(bmepsoeP);
    else
        flushNativeOutput(rle);

    destroyBmepsOutputEncoder(bmepsoeP);

    putEnd(showpage, psfilter, ascii85, dict, vmreclaim);
}



static const char *
basebasename(const char * const filespec) {
/*----------------------------------------------------------------------------
    Return filename up to first period
-----------------------------------------------------------------------------*/
    char * filespec2;
    char * name;
    char * dotPosition;
    
    filespec2 = strdup(filespec);

    name = strdup(basename(filespec2));

    strfree(filespec2);

    dotPosition = strchr(name, '.');
    if (dotPosition)
        *dotPosition = '\0';

    return name;
}



int
main(int argc, char * argv[]) {

    FILE* ifp;
    const char *name;  /* malloc'ed */
    struct cmdline_info cmdline;

    pnm_init( &argc, argv );

    parseCommandLine(argc, argv, &cmdline);

    verbose = cmdline.verbose;

    ifp = pm_openr(cmdline.input_filespec);

    if (strcmp(cmdline.input_filespec, "-") == 0 )
        name = strdup("noname");
    else
        name = basebasename(cmdline.input_filespec);
    {
        int eof;  /* There are no more images in the input file */
        unsigned int image_seq;

        /* I don't know if this works at all for multi-image PNM input.
           Before July 2000, it ignored everything after the first image,
           so this probably is at least as good -- it should be identical
           for a single-image file, which is the only kind which was legal
           before July 2000.

           Maybe there needs to be some per-file header and trailers stuff
           in the Postscript program, with some per-page header and trailer
           stuff inside.  I don't know Postscript.  - Bryan 2000.06.19.
        */

        eof = FALSE;  /* There is always at least one image */
        for (image_seq = 0; !eof; image_seq++) {
            convertPage(ifp, cmdline.mustturn, cmdline.canturn, 
                        cmdline.psfilter,
                        cmdline.rle, cmdline.flate, cmdline.ascii85, 
                        cmdline.setpage, cmdline.showpage,
                        cmdline.center, cmdline.scale,
                        cmdline.dpiX, cmdline.dpiY,
                        cmdline.width, cmdline.height, 
                        cmdline.imagewidth, cmdline.imageheight, 
                        cmdline.equalpixels, name, 
                        cmdline.dict, cmdline.vmreclaim,
                        cmdline.levelSpec, cmdline.level);
            pnm_nextimage(ifp, &eof);
        }
    }
    strfree(name);

    pm_close(ifp);
    
    return 0;
}



/*
** Copyright (C) 1989 by Jef Poskanzer.
**
** Permission to use, copy, modify, and distribute this software and its
** documentation for any purpose and without fee is hereby granted, provided
** that the above copyright notice appear in all copies and that both that
** copyright notice and this permission notice appear in supporting
** documentation.  This software is provided "as is" without express or
** implied warranty.
**
**
** -nocenter option added November 1993 by Wolfgang Stuerzlinger,
**  wrzl@gup.uni-linz.ac.at.
**
*/
