/*====================================================================*
 -  Copyright (C) 2001 Leptonica.  All rights reserved.
 -  This software is distributed in the hope that it will be
 -  useful, but with NO WARRANTY OF ANY KIND.
 -  No author or distributor accepts responsibility to anyone for the
 -  consequences of using this software, or for whether it serves any
 -  particular purpose or works at all, unless he or she says so in
 -  writing.  Everyone is granted permission to copy, modify and
 -  redistribute this source code, for commercial or non-commercial
 -  purposes, with the following restrictions: (1) the origin of this
 -  source code must not be misrepresented; (2) modified versions must
 -  be plainly marked as such; and (3) this notice may not be removed
 -  or altered from any source or modified source distribution.
 *====================================================================*/

/*
 *  psio.c
 *
 *    |=============================================================|
 *    |                         Important note                      |
 *    |=============================================================|
 *    |Some of these functions require libtiff and libjpeg.         |
 *    |If you do not have both of these libraries, you must set     |
 *    |     #define  USE_PSIO     0                                 |
 *    |in environ.h.  This will link psiostub.c                     |
 *    |=============================================================|
 *                     
 *     This is a PostScript "device driver" for wrapping images
 *     in PostScript.  The images can be rendered by a PostScript
 *     interpreter for viewing, using evince or gv.  They can also be
 *     rasterized for printing, using gs or an embedded interpreter
 *     in a PostScript printer.  And they can be converted to a pdf
 *     using gs (ps2pdf).
 *
 *     Convert specified files to PS
 *          l_int32          convertFilesToPS()
 *          l_int32          sarrayConvertFilesToPS()
 *          l_int32          convertFilesFittedToPS()
 *          l_int32          sarrayConvertFilesFittedToPS()
 *          static l_int32   writeImageCompressedToPSFile()
 *
 *     Convert mixed text/image files to PS
 *          l_int32          convertSegmentedPagesToPS()
 *          l_int32          pixWriteSegmentedPageToPS()
 *          l_int32          pixWriteMixedToPS()
 *          NUMA            *sarrayFindMaskAndPagePairings()
 *
 *     Convert any image file to PS for embedding
 *          l_int32          convertToPSEmbed()
 *
 *     For uncompressed images
 *          l_int32          pixWritePSEmbed()
 *          l_int32          pixWriteStreamPS()
 *          char            *pixWriteStringPS()
 *          void             getScaledParametersPS()
 *          l_int32          convertByteToHexAscii()
 *
 *     For jpeg compressed images
 *          l_int32          convertJpegToPSEmbed()
 *          l_int32          convertJpegToPS()
 *          l_int32          convertJpegToPSString()
 *
 *     For tiff g4 compressed images
 *          l_int32          convertTiffG4ToPSEmbed()
 *          l_int32          convertTiffG4ToPS()
 *          l_int32          convertTiffG4ToPSString()
 *
 *     For multipage tiff images
 *          l_int32          convertTiffMultipageToPS()
 *
 *     Write to memory
 *          l_int32          pixWriteMemPS()
 *
 *     Converting resolution
 *          l_int32          getResLetterPage()
 *          l_int32          getResA4Page()
 *
 *     Utility for encoding and decoding data with ascii85
 *          char            *encodeAscii85()
 *          l_int32         *convertChunkToAscii85()
 *          l_uint8         *decodeAscii85()
 *
 *  These PostScript converters are used in three different ways:
 *
 *  (1) For embedding a PS file in a program like TeX.  We must have
 *      a bounding box.  convertToPSEmbed() handles this for
 *      both level 1 and level 2 output, and prog/converttops
 *      wraps this in an executable.  converttops is a generalization
 *      of Thomas Merz's jpeg2ps wrapper, in that it works for
 *      all types (formats, depth, colormap) of input images and
 *      gives PS output in either compressed or uncompressed format,
 *      depending on an input flag.
 *
 *  (2) For composing a set of pages with any number of images
 *      painted on them, in DCT or G4 compressed format depending
 *      on if the image is grayscale/color or binary.  Because we
 *      append each PS string and specify the scaling and placement
 *      explicitly, one must NOT have a bounding box attached to
 *      each separate image.  
 *
 *  (3) For printing a page image or a set of page images, at a
 *      resolution that optimally fills the page.  Here we use
 *      a bounding box and scale the image appropriately.
 *
 *  The top-level calls of utilities in category 2, which can compose
 *  multiple images on a page, and which generate a PostScript file for
 *  printing or display (e.g., conversion to pdf), are:
 *      convertFilesToPS()
 *      convertFilesFittedToPS()
 *      convertSegmentedPagesToPS()
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "allheaders.h"

/* --------------------------------------------*/
#if  USE_PSIO   /* defined in environ.h */
 /* --------------------------------------------*/

    /* Static helper for writing or appending images to an output file */
static l_int32 writeImageCompressedToPSFile(const char *filein,
                                            const char *fileout,
                                            l_int32 format, l_int32 res,
                                            l_int32 *pfirstfile,
                                            l_int32 *pindex);

static const char *TEMP_G4TIFF_FILE = "/tmp/junk_temp_g4tiff.tif";
static const char *TEMP_JPEG_FILE   = "/tmp/junk_temp_jpeg.jpg";

    /* MS VC++ can't handle array initialization with static consts ! */
#define L_BUF_SIZE      512

static const l_int32  DEFAULT_PRINTER_RES     = 300; /* default printing ppi */
static const l_int32  MIN_RES                 = 5;
static const l_int32  MAX_RES                 = 3000;
static const l_int32  MAX_85_LINE_COUNT       = 64;

    /* For computing resolution that fills page to desired amount */
static const l_int32  LETTER_WIDTH            = 612;   /* points */
static const l_int32  LETTER_HEIGHT           = 792;   /* points */
static const l_int32  A4_WIDTH                = 595;   /* points */
static const l_int32  A4_HEIGHT               = 842;   /* points */
static const l_float32  DEFAULT_FILL_FRACTION = 0.95;

static const l_uint32  power85[5] = {1,
                                     85,
                                     85 * 85,
                                     85 * 85 * 85,
                                     85 * 85 * 85 * 85};

#ifndef  NO_CONSOLE_IO
#define  DEBUG_MIXED_PS   0
#define  DEBUG_JPEG       0
#define  DEBUG_G4         0
#endif  /* ~NO_CONSOLE_IO */

    /* This should be false for documents that are composited from
     * sequences of painted images, where more than one image can
     * be placed in an arbitrary location on any page.  
     * However, for images that are composited, we use special *Embed()
     * functions for writing PostScript with bounding boxes, so they
     * can be embedded in TeX files, e.g. */
#define  PRINT_BOUNDING_BOX      0


/*-------------------------------------------------------------*
 *                Convert files in a directory to PS           *
 *-------------------------------------------------------------*/
/*
 *  convertFilesToPS()
 *
 *      Input:  dirin (input directory)
 *              substr (<optional> substring filter on filenames; can be NULL)
 *              res (typ. 300 or 600 ppi)
 *              fileout (output ps file)
 *      Return: 0 if OK, 1 on error
 *
 *  Notes:
 *      (1) This generates a PS file for all image files in a specified
 *          directory that contain the substr pattern to be matched.
 *      (2) Each image is written to a separate page in the output PS file.
 *      (3) All images are written with level 2 compression.
 *          If the image is 1 bpp, use G4.  Otherwise, use DCT.
 *          All colormaps are removed.  If the image is neither 1 bpp nor
 *          initially jpeg compressed, it is jpeg compressed with
 *          quality = 75, which will in general cause some degradation.
 *      (4) The resolution is often confusing.  It is interpreted
 *          as the resolution of the output display device:  "If the
 *          input image were digitized at 300 ppi, what would it
 *          look like when displayed at res ppi."  So, for example,
 *          if res = 100 ppi, then the display pixels are 3x larger
 *          than the 300 ppi pixels, and the image will be rendered
 *          3x larger.
 *      (5) The size of the PostScript file is independent of the resolution,
 *          because the entire file is encoded.  The res parameter just
 *          tells the PS decomposer how to render the page.  Therefore,
 *          for minimum file size without loss of visual information,
 *          if the output res is less than 300, you should downscale
 *          the image to the output resolution before wrapping in PS.
 *      (6) The "canvas" on which the image is rendered, at the given
 *          output resolution, is a standard page size (8.5 x 11 in).
 *      (7) If the image is jpeg or tiffg4, we use the existing
 *          compressed string; otherwise it is necessary to decompress
 *          it, remove any existing colormap, and write it out in
 *          a temp file in one of these two formats.
 */
l_int32
convertFilesToPS(const char  *dirin,
                 const char  *substr,
                 l_int32      res,
                 const char  *fileout)
{
SARRAY  *sa;

    PROCNAME("convertFilesToPS");

    if (!dirin)
        return ERROR_INT("dirin not defined", procName, 1);
    if (!fileout)
        return ERROR_INT("fileout not defined", procName, 1);
    if (res <= 0) {
        L_INFO("setting res to 300 ppi", procName);
        res = 300;
    }
    if (res < 10 || res > 4000)
        L_WARNING("res is typically in the range 300-600 ppi", procName);

        /* Get all filtered and sorted full pathnames. */
    sa = getSortedPathnamesInDirectory(dirin, substr, 0, 0);

        /* Generate the PS file. */
    sarrayConvertFilesToPS(sa, res, fileout);
    sarrayDestroy(&sa);
    return 0;
}


/*
 *  sarrayConvertFilesToPS()
 *
 *      Input:  sarray (of full path names)
 *              res (typ. 300 or 600 ppi)
 *              fileout (output ps file)
 *      Return: 0 if OK, 1 on error
 *
 *  Notes:
 *      (1) See convertFilesToPS()
 */
l_int32
sarrayConvertFilesToPS(SARRAY      *sa,
                       l_int32      res,
                       const char  *fileout)
{
char    *fname;
l_int32  i, d, nfiles, index, firstfile, format;
FILE    *fp;
PIX     *pix, *pixt;

    PROCNAME("sarrayConvertFilesToPS");

    if (!sa)
        return ERROR_INT("sa not defined", procName, 1);
    if (!fileout)
        return ERROR_INT("fileout not defined", procName, 1);
    if (res <= 0) {
        L_INFO("setting res to 300 ppi", procName);
        res = 300;
    }
    if (res < 10 || res > 4000)
        L_WARNING("res is typically in the range 300-600 ppi", procName);

    nfiles = sarrayGetCount(sa);
    firstfile = TRUE;
    for (i = 0, index = 0; i < nfiles; i++) {
        fname = sarrayGetString(sa, i, L_NOCOPY);
        if ((fp = fopen(fname, "r")) == NULL)
            continue;
        if(findFileFormat(fp, &format))
            continue;
        fclose(fp);

            /* Convert to tiffg4 or jpeg if necessary */
        if (format != IFF_JFIF_JPEG && format != IFF_TIFF_G4) {
            if ((pix = pixRead(fname)) == NULL)
                continue;
            d = pixGetDepth(pix);
            if (d == 1) {
                pixWrite(TEMP_G4TIFF_FILE, pix, IFF_TIFF_G4);
                fname = stringNew(TEMP_G4TIFF_FILE);
                format = IFF_TIFF_G4;
            }
            else {
                pixt = pixRemoveColormap(pix, REMOVE_CMAP_BASED_ON_SRC);
                pixWrite(TEMP_JPEG_FILE, pixt, IFF_JFIF_JPEG);
                pixDestroy(&pixt);
                fname = stringNew(TEMP_JPEG_FILE);
                format = IFF_JFIF_JPEG;
            }
            pixDestroy(&pix);
        }
        else  /* wrap it up as is */
            fname = stringNew(fname);

        writeImageCompressedToPSFile(fname, fileout, format, res,
                                     &firstfile, &index);
        FREE(fname);
    }
    
    return 0;
}


/*
 *  convertFilesFittedToPS()
 *
 *      Input:  dirin (input directory)
 *              substr (<optional> substring filter on filenames; can be NULL)
 *              xpts, ypts (desired size in printer points; use 0 for default)
 *              fileout (output ps file)
 *      Return: 0 if OK, 1 on error
 *
 *  Notes:
 *      (1) This generates a PS file for all files in a specified directory
 *          that contain the substr pattern to be matched.
 *      (2) Each image is written to a separate page in the output PS file.
 *      (3) All images are written with level 2 compression.
 *          If the image is 1 bpp, use G4.  Otherwise, use DCT.
 *          All colormaps are removed.  If the image is neither 1 bpp nor
 *          initially jpeg compressed, it is jpeg compressed with
 *          quality = 75, which will in general cause some degradation.
 *      (4) The resolution is internally determined such that the images
 *          are rendered, in at least one direction, at 100% of the given
 *          size in printer points.  Use 0.0 for xpts or ypts to get
 *          the default value, which is 612.0 or 792.0, rsp.
 *      (5) The size of the PostScript file is independent of the resolution,
 *          because the entire file is encoded.  The @xpts and @ypts
 *          parameter tells the PS decomposer how to render the page.
 *      (6) If the image is jpeg or tiffg4, we use the existing
 *          compressed string; otherwise it is necessary to decompress
 *          it, remove any existing colormap, and write it out in
 *          a temp file in one of these two formats.
 */
l_int32
convertFilesFittedToPS(const char  *dirin,
                       const char  *substr,
                       l_float32    xpts,
                       l_float32    ypts,
                       const char  *fileout)
{
SARRAY  *sa;

    PROCNAME("convertFilesFittedToPS");

    if (!dirin)
        return ERROR_INT("dirin not defined", procName, 1);
    if (!fileout)
        return ERROR_INT("fileout not defined", procName, 1);
    if (xpts <= 0.0) {
        L_INFO("setting xpts to 612.0 ppi", procName);
        xpts = 612.0;
    }
    if (ypts <= 0.0) {
        L_INFO("setting ypts to 792.0 ppi", procName);
        ypts = 792.0;
    }
    if (xpts < 100.0 || xpts > 2000.0 || ypts < 100.0 || ypts > 2000.0)
        L_WARNING("xpts,ypts are typically in the range 500-800", procName);

        /* Get all filtered and sorted full pathnames. */
    sa = getSortedPathnamesInDirectory(dirin, substr, 0, 0);

        /* Generate the PS file. */
    sarrayConvertFilesFittedToPS(sa, xpts, ypts, fileout);
    sarrayDestroy(&sa);
    return 0;
}


/*
 *  sarrayConvertFilesFittedToPS()
 *
 *      Input:  sarray (of full path names)
 *              xpts, ypts (desired size in printer points; use 0 for default)
 *              fileout (output ps file)
 *      Return: 0 if OK, 1 on error
 *
 *  Notes:
 *      (1) See convertFilesFittedToPS()
 */
l_int32
sarrayConvertFilesFittedToPS(SARRAY      *sa,
                             l_float32    xpts,
                             l_float32    ypts,
                             const char  *fileout)
{
char    *fname;
l_int32  i, w, h, d, nfiles, index, firstfile, format, res;
FILE    *fp;
PIX     *pix, *pixt;

    PROCNAME("sarrayConvertFilesFittedToPS");

    if (!sa)
        return ERROR_INT("sa not defined", procName, 1);
    if (!fileout)
        return ERROR_INT("fileout not defined", procName, 1);
    if (xpts <= 0.0) {
        L_INFO("setting xpts to 612.0 ppi", procName);
        xpts = 612.0;
    }
    if (ypts <= 0.0) {
        L_INFO("setting ypts to 792.0 ppi", procName);
        ypts = 792.0;
    }
    if (xpts < 100.0 || xpts > 2000.0 || ypts < 100.0 || ypts > 2000.0)
        L_WARNING("xpts,ypts are typically in the range 500-800", procName);

    nfiles = sarrayGetCount(sa);
    firstfile = TRUE;
    for (i = 0, index = 0; i < nfiles; i++) {
        fname = sarrayGetString(sa, i, L_NOCOPY);
        if ((fp = fopen(fname, "r")) == NULL)
            continue;
        if(findFileFormat(fp, &format))
            continue;
        pix = pixReadStream(fp, 0);
        fclose(fp);
        if (!pix)
            continue;

            /* Be sure the entire image is contained in the result */
        pixGetDimensions(pix, &w, &h, &d);
        if (xpts * h <  ypts * w)
            res = (l_int32)((l_float32)w * 72.0 / xpts);
        else
            res = (l_int32)((l_float32)h * 72.0 / ypts);

            /* Convert to tiffg4 or jpeg if necessary */
        if (format != IFF_JFIF_JPEG && format != IFF_TIFF_G4) {
            if (d == 1) {
                pixWrite(TEMP_G4TIFF_FILE, pix, IFF_TIFF_G4);
                fname = stringNew(TEMP_G4TIFF_FILE);
                format = IFF_TIFF_G4;
            }
            else {
                pixt = pixRemoveColormap(pix, REMOVE_CMAP_BASED_ON_SRC);
                pixWrite(TEMP_JPEG_FILE, pixt, IFF_JFIF_JPEG);
                pixDestroy(&pixt);
                fname = stringNew(TEMP_JPEG_FILE);
                format = IFF_JFIF_JPEG;
            }
        }
        else  /* wrap it up as is */
            fname = stringNew(fname);
        pixDestroy(&pix);

        writeImageCompressedToPSFile(fname, fileout, format, res,
                                     &firstfile, &index);
        FREE(fname);
    }

    return 0;
}


/*
 *  writeImageCompressedToPSFile()
 *
 *      Input:  filein (input image file)
 *              fileout (output ps file)
 *              format (input image file format)
 *              res (output printer resolution)
 *              &firstfile (<input and return> 1 if the first image;
 *                          0 otherwise)
 *              &index (<input and return> index of image in output ps file)
 *      Return: 0 if OK, 1 on error
 *
 *  Notes:
 *      (1) This assumes the compressed format is either tiffg4 or jpeg.
 *      (2) @index is incremented if the page is successfully written.
 */
static l_int32
writeImageCompressedToPSFile(const char *filein,
                             const char *fileout,
                             l_int32     format,
                             l_int32     res,
                             l_int32    *pfirstfile,
                             l_int32    *pindex)
{
l_int32  retval;

    PROCNAME("writeImageCompressedToPSFile");

    if (!pfirstfile || !pindex)
        return ERROR_INT("&firstfile and &index not defined", procName, 1);

    if (format == IFF_JFIF_JPEG) {
        if (*pfirstfile) {
            retval = convertJpegToPS(filein, fileout, "w", 0, 0,
                                     res, 1.0, *pindex + 1, TRUE);
            if (retval == 0) {
                *pfirstfile = FALSE;
                (*pindex)++;
            }
        }
        else {
            retval = convertJpegToPS(filein, fileout, "a", 0, 0,
                                     res, 1.0, *pindex + 1, TRUE);
            if (retval == 0)
                (*pindex)++;
        }
    }
    else if (format == IFF_TIFF_G4) {
        if (*pfirstfile) {
            retval = convertTiffG4ToPS(filein, fileout, "w", 0, 0,
                                       res, 1.0, *pindex + 1, FALSE, TRUE);
            if (retval == 0) {
                *pfirstfile = FALSE;
                (*pindex)++;
            }

        }
        else {
            retval = convertTiffG4ToPS(filein, fileout, "a", 0, 0,
                                       res, 1.0, *pindex + 1, FALSE, TRUE);
            if (retval == 0)
                (*pindex)++;
        }
    }
    else
        return ERROR_INT("file format not tiffg4 or jpeg", procName, 1);
    
    return retval;
}


/*-------------------------------------------------------------*
 *              Convert mixed text/image files to PS           *
 *-------------------------------------------------------------*/
/*
 *  convertSegmentedPagesToPS()
 *
 *      Input:  pagedir (input page image directory)
 *              maskdir (input mask image directory)
 *              textscale (scale of text output relative to pixs)
 *              imagescale (scale of image output relative to pixs)
 *              threshold (for binarization; typ. about 190; 0 for default)
 *              numpre (number of characters in name before number)
 *              numpost (number of characters in name after number)
 *              fileout (output ps file)
 *      Return: 0 if OK, 1 on error
 *
 *  Notes:
 *      (1) This generates a PS file for all page image and mask files in two
 *          specified directories that contain the page numbers as
 *          specified below.  The page images are taken in lexicographic order.
 *          Mask images whose numbers match the page images are used to
 *          segment the page images.  Page imaes without a matching 
 *          mask image are scaled, thresholded and rendered entirely as text.
 *      (2) Each PS page is generated as a compressed representation of
 *          the page image, where the part of the image under the mask
 *          is suitably scaled and compressed as DCT (i.e., jpeg), and
 *          the remaining part of the page is suitably scaled, thresholded,
 *          compressed as G4 (i.e., tiff g4), and rendered by painting
 *          black through the resulting text mask.
 *      (3) The scaling is typically 2x down for the DCT component
 *          (@imagescale = 0.5) and 2x up for the G4 component
 *          (@textscale = 2.0).
 *      (4) The resolution is automatically set to fit to a
 *          letter-size (8.5 x 11 inch) page.
 *      (5) Both the DCT and the G4 encoding are PostScript level 2.
 *      (6) It is assumed that the pa/home/massimo/sources/upp-svn/MyApps/Kindler/main.cpp:33: error: invalid use of ‘class Upp::Pix’
ge number is contained within
 *          the basename (the filename without directory or extension).
 *          @numpre is the number of characters in the basename
 *          preceeding the actual page numer; @numpost is the number
 *          following the page number. 
 */
l_int32
convertSegmentedPagesToPS(const char  *pagedir,
                          const char  *maskdir,
                          l_float32    textscale,
                          l_float32    imagescale,
                          l_int32      threshold,
                          l_int32      numpre,
                          l_int32      numpost,
                          const char  *fileout)
{
char       *pagefile, *maskfile; 
l_int32     pageno, i, npages;
l_int32     pageindex, maskindex;
NUMA       *naindex;
PIX        *pixs, *pixm;
SARRAY     *sapage, *samask;

    PROCNAME("convertSegmentedPagesToPS");

    if (!pagedir)
        return ERROR_INT("pagedir not defined", procName, 1);
    if (!maskdir)
        return ERROR_INT("maskdir not defined", procName, 1);
    if (!fileout)
        return ERROR_INT("fileout not defined", procName, 1);
    if (threshold <= 0) {
        L_INFO("setting threshold to 190", procName);
        threshold = 190;
    }

        /* Get sorted full pathnames. */
    sapage = getSortedPathnamesInDirectory(pagedir, NULL, 0, 0);
    samask = getSortedPathnamesInDirectory(maskdir, NULL, 0, 0);

        /* Go through the filenames, locating the page numbers
         * and matching page images with mask images. */
    naindex = sarrayFindMaskAndPagePairings(sapage, samask, numpre,
                                            numpost, 10000);
    npages = numaGetCount(naindex) / 2;

        /* Generate the PS file. */
    pageno = 1;
    for (i = 0; i < 2 * npages; i += 2) {
        numaGetIValue(naindex, i, &pageindex);
        numaGetIValue(naindex, i + 1, &maskindex);
        pagefile = sarrayGetString(sapage, pageindex, L_NOCOPY);
        pixs = pixRead(pagefile);
        pixm = NULL;
        if (maskindex != -1) {
            maskfile = sarrayGetString(samask, maskindex, L_NOCOPY);
            pixm = pixRead(maskfile);
        }
#if DEBUG_MIXED_PS
        fprintf(stderr, "pageindex[%d] = %d, maskindex[%d] = %d\n",
                i, pageindex, i, maskindex);
        fprintf(stderr, "  pagefile[%d]: %s\n", i / 2, pagefile);
        if (pixm)
            fprintf(stderr, "  maskfile[%d]: %s\n", i / 2, maskfile);
#endif  /* DEBUG_MIXED_PS */

        pixWriteSegmentedPageToPS(pixs, pixm, textscale, imagescale,
                                  threshold, pageno, fileout);
        pixDestroy(&pixs);
        pixDestroy(&pixm);
        pageno++;
    }

    sarrayDestroy(&sapage);
    sarrayDestroy(&samask);
    numaDestroy(&naindex);
    return 0;
}


/*
 *  pixWriteSegmentedPageToPS()
 *
 *      Input:  pixs (grayscale or color; colormap ok)
 *              pixm (<optional> 1 bpp segmentation mask over image region)
 *              textscale (scale of text output relative to pixs)
 *              imagescale (scale of image output relative to pixs)
 *              threshold (threshold for binarization; typ. 190)
 *              pageno (page number in set; use 1 for new output file)
 *              fileout (output ps file)
 *      Return: 0 if OK, 1 on error
 *
 *  Notes:
 *      (1) This generates the PS string for a mixed text/image page,
 *          and adds it to an existing file if @pageno > 1.
 *          The PS output is determined by fitting the result to
 *          a letter-size (8.5 x 11 inch) page.
 *      (2) The two images (pixs and pixm) are at the same resolution
 *          (typically 300 ppi).  They are used to generate two compressed
 *          images, pixb and pixc, that are put directly into the output
 *          PS file.
 *      (3) pixb is the text component.  In the PostScript world, we think of
 *          it as a mask through which we paint black.  It is produced by
 *          scaling pixs by @textscale, and thresholding to 1 bpp.
 *      (4) pixc is the image component, which is that part of pixs under
 *          the mask pixm.  It is scaled from pixs by @imagescale.
 *      (5) Typical values are textscale = 2.0 and imagescale = 0.5.
 *      (6) If pixm == NULL, the page has only text.  If it is all black,
 *          the page is all image and has no text.
 *      (7) This can be used to write a multi-page PS file, by using
 *          sequential page numbers with the same output file.  It can
 *          also be used to write separate PS files for each page,
 *          by using different output files with @pageno = 0 or 1.
 */
l_int32
pixWriteSegmentedPageToPS(PIX         *pixs,
                          PIX         *pixm,
                          l_float32    textscale,
                          l_float32    imagescale,
                          l_int32      threshold,
                          l_int32      pageno,
                          const char  *fileout)
{
l_int32    alltext, notext, d, ret;
l_float32  scaleratio;
PIX       *pixmi, *pixt, *pixg, *pixsc, *pixb, *pixc;
 
    PROCNAME("pixWriteSegmentedPageToPS");

    if (!pixs || pixGetDepth(pixs) == 1)
        return ERROR_INT("pixs is 1 bpp or not defined", procName, 1);
    if (!fileout)
        return ERROR_INT("fileout not defined", procName, 1);
    if (imagescale <= 0.0 || textscale <= 0.0)
        return ERROR_INT("relative scales must be > 0.0", procName, 1);

        /* Analyze the page.  Determine the ratio by which the
         * binary text mask is scaled relative to the image part.
         * If there is no image region (alltext == TRUE), the
         * text mask will be rendered directly to fit the page,
         * and scaleratio = 1.0.  */
    alltext = TRUE;
    notext = FALSE;
    scaleratio = 1.0;
    if (pixm) {
        pixZero(pixm, &alltext);  /* pixm empty: all text */
        if (alltext)
            pixm = NULL;  /* treat it as not existing here */
        else {
            pixmi = pixInvert(NULL, pixm);
            pixZero(pixmi, &notext);  /* pixm full; no text */
            pixDestroy(&pixmi);
            scaleratio = textscale / imagescale;
        }
    }

    pixt = pixConvertTo8Or32(pixs, 0, 0);

        /* Get the binary text mask */
    pixb = NULL;
    if (notext == FALSE) {
        d = pixGetDepth(pixt);
        if (d == 8)
            pixg = pixClone(pixt);
        else  /* d == 32 */
            pixg = pixConvertRGBToLuminance(pixt);
        if (pixm)  /* clear out the image parts */
            pixSetMasked(pixg, pixm, 255);
        if (textscale == 1.0)
            pixsc = pixClone(pixg);
        else if (textscale >= 0.7)
            pixsc = pixScaleGrayLI(pixg, textscale, textscale);
        else
            pixsc = pixScaleAreaMap(pixg, textscale, textscale);
        pixb = pixThresholdToBinary(pixsc, threshold);
        pixDestroy(&pixg);
        pixDestroy(&pixsc);
    }

        /* Get the scaled image region */
    pixc = NULL;
    if (pixm) {
        if (imagescale == 1.0)
            pixc = pixClone(pixt);
        else
            pixc = pixScale(pixt, imagescale, imagescale);
    }
    pixDestroy(&pixt);

    ret = pixWriteMixedToPS(pixb, pixc, scaleratio, pageno, fileout);
    pixDestroy(&pixb);
    pixDestroy(&pixc);
    return ret;
}


/*
 *  pixWriteMixedToPS()
 *
 *      Input:  pixb (<optionall> 1 bpp "mask"; typically for text)
 *              pixc (<optional> 8 or 32 bpp image regions)
 *              scale (relative scale factor for rendering pixb
 *                    relative to pixc; typ. 4.0)
 *              pageno (page number in set; use 1 for new output file)
 *              fileout (output ps file)
 *      Return: 0 if OK, 1 on error
 *
 *  Notes:
 *      (1) This low level function generates the PS string for a mixed
 *          text/image page, and adds it to an existing file if
 *          @pageno > 1.
 *      (2) The two images (pixb and pixc) are typically generated at the
 *          resolution that they will be rendered in the PS file.
 *      (3) pixb is the text component.  In the PostScript world, we think of
 *          it as a mask through which we paint black.
 *      (4) pixc is the (typically halftone) image component.  It is
 *          white in the rest of the page.  To minimize the size of the
 *          PS file, it should be rendered at a resolution that is at
 *          least equal to its actual resolution.
 *      (5) @scale gives the ratio of resolution of pixb to pixc.
 *          Typical resolutions are: 600 ppi for pixb, 150 ppi for pixc;
 *          so @scale = 4.0.  If one of the images is not defined,
 *          the value of @scale is ignored.
 *      (6) We write pixc with DCT compression (jpeg).  This is followed
 *          by painting the text as black through the mask pixb.  If
 *          pixc doesn't exist (alltext), we write the text with the
 *          PS "image" operator instead of the "imagemask" operator,
 *          because ghostscript's ps2pdf is flaky when the latter is used.
 *      (7) The actual output resolution is determined by fitting the
 *          result to a letter-size (8.5 x 11 inch) page.
 */
l_int32
pixWriteMixedToPS(PIX         *pixb,
                  PIX         *pixc,
                  l_float32    scale,
                  l_int32      pageno,
                  const char  *fileout)
{
char        *tnameb, *tnamec;
const char  *op;
l_int32      resb, resc, endpage, maskop, ret;
 
    PROCNAME("pixWriteMixedToPS");

    if (!pixb && !pixc)
        return ERROR_INT("pixb and pixc both undefined", procName, 1);
    if (!fileout)
        return ERROR_INT("fileout not defined", procName, 1);

        /* Compute the resolution that fills a letter-size page. */
    if (!pixc)
       resb = getResLetterPage(pixGetWidth(pixb), pixGetHeight(pixb), 0);
    else {
       resc = getResLetterPage(pixGetWidth(pixc), pixGetHeight(pixc), 0);
       if (pixb)
           resb = (l_int32)(scale * resc);
    }

        /* Write the jpeg image first */
    if (pixc) {
        tnamec = genTempFilename("/tmp", NULL, ".jpg");
        pixWrite(tnamec, pixc, IFF_JFIF_JPEG);
        endpage = (pixb) ? FALSE : TRUE;
        op = (pageno <= 1) ? "w" : "a";
        ret = convertJpegToPS(tnamec, fileout, op, 0, 0, resc, 1.0,
                              pageno, endpage);
        FREE(tnamec);
        if (ret)
            return ERROR_INT("jpeg data not written", procName, 1);
    }

        /* Write the binary data, either directly or, if there is
         * a jpeg image on the page, through the mask. */
    if (pixb) {
        tnameb = genTempFilename("/tmp", NULL, ".tif");
        pixWrite(tnameb, pixb, IFF_TIFF_G4);
        op = (pageno <= 1 && !pixc) ? "w" : "a";
        maskop = (pixc) ? 1 : 0;
        ret = convertTiffG4ToPS(tnameb, fileout, op, 0, 0, resb, 1.0,
              pageno, maskop, 1);
        FREE(tnameb);
        if (ret)
            return ERROR_INT("tiff data not written", procName, 1);
    }

    return 0;
}


/*
 *  sarrayFindMaskAndPagePairings()
 *
 *      Input:  sapage (array of full pathnames for page images)
 *              samask (array of full pathnames for mask images)
 *              numpre (number of characters in name before number)
 *              numpost (number of characters in name after number)
 *              maxnum (only consider page numbers up to this value)
 *      Return: 0 if OK, 1 on error
 *
 *  Notes:
 *      (1) The pages and masks are matched by the located numbers, so
 *          their order in @sapage and @samask doesn't matter.
 *      (2) It is assumed that the page number is contained within
 *          the basename (the filename without directory or extension).
 *          @numpre is the number of characters in the basename
 *          preceeding the actual page numer; @numpost is the number
 *          following the page number. 
 *      (3) To use a O(n) matching algorithm, the largest page number
 *          is found and two internal arrays of this size are created.
 *          This maximum is constrained not to exceed @maxsum,
 *          to make sure that an unrealistically large number is not
 *          accidentally used to determine the array sizes.
 */
NUMA *
sarrayFindMaskAndPagePairings(SARRAY  *sapage,
                              SARRAY  *samask,
                              l_int32  numpre,
                              l_int32  numpost,
                              l_int32  maxnum)
{
char      *pagename, *maskname;
l_int32    i, npage, nmask, ipage, imask, num, max, ret;
l_int32   *arraypage, *arraymask;
l_float32  fmax;
NUMA      *napage, *namask, *naindex;

    PROCNAME("sarrayFindMaskAndPagePairings");

    if (!sapage)
        return (NUMA *)ERROR_PTR("sapage not defined", procName, NULL);
    if (!samask)
        return (NUMA *)ERROR_PTR("samask not defined", procName, NULL);

        /* First generate two arrays, corresponding to the filename
         * arrays, that contain the page number extracted from each name. */
    npage = sarrayGetCount(sapage);
    nmask = sarrayGetCount(samask);
    napage = numaCreate(npage);
    namask = numaCreate(nmask);
    for (i = 0; i < npage; i++) {
         pagename = sarrayGetString(sapage, i, L_NOCOPY);
         num = extractNumberFromFilename(pagename, numpre, numpost);
         if (num >= 0)
             numaAddNumber(napage, num);
    }
    for (i = 0; i < nmask; i++) {
         maskname = sarrayGetString(samask, i, L_NOCOPY);
         num = extractNumberFromFilename(maskname, numpre, numpost);
         if (num >= 0)
             numaAddNumber(namask, num);
    }

        /* Generate two new arrays with the page number as the
         * array index and the index of the filename in the sarray
         * as the array content.  If there is no file with
         * a page number, the content is -1.  */
    numaGetMax(napage, &fmax, NULL);
    max = L_MIN(10000, (l_int32)fmax);
    arraypage = (l_int32 *)CALLOC(max + 1, sizeof(l_int32));
    arraymask = (l_int32 *)CALLOC(max + 1, sizeof(l_int32));
    for (i = 0; i <= max; i++) {  /* initialize to -1 */
        arraypage[i] = -1;
        arraymask[i] = -1;
    }
    for (i = 0; i < npage; i++) {
         ret = numaGetIValue(napage, i, &ipage);
         if (ret == 1 || ipage > max) {
             pagename = sarrayGetString(sapage, i, L_NOCOPY);
             L_WARNING_STRING("bad page name: %s", procName, pagename);
         }
         else
             arraypage[ipage] = i;
    }
    for (i = 0; i < nmask; i++) {
         ret = numaGetIValue(namask, i, &imask);
         if (ret == 1 || imask > max) {
             maskname = sarrayGetString(samask, i, L_NOCOPY);
             L_WARNING_STRING("bad mask name = %s", procName, maskname);
         }
         else
             arraymask[imask] = i;
    }


        /* Store the result in a single array that holds each
         * pair of page indices.  There should be no situation where
         * the mask exists and the page doesn't, so if the page
         * is not found, we don't store anything.  */
    naindex = numaCreate(2 * (max + 1));
    for (i = 0; i <= max; i++) {
        ipage = arraypage[i];
        imask = arraymask[i];
        if (ipage == -1) continue;
        numaAddNumber(naindex, ipage);
        numaAddNumber(naindex, imask);
    }

    numaDestroy(&napage);
    numaDestroy(&namask);
    FREE(arraypage);
    FREE(arraymask);
    return naindex;
}
 

/*-------------------------------------------------------------*
 *            Convert any image file to PS for embedding       *
 *-------------------------------------------------------------*/
/*
 *  convertToPSEmbed()
 *
 *      Input:  filein (input image file)
 *              fileout (output ps file)
 *              level (1 - uncompressed,  2 - compressed)
 *      Return: 0 if OK, 1 on error
 *
 *  Notes:
 *      (1) This is a wrapper function that generates a PS file with
 *          a bounding box, from any input image file.
 *      (2) Colormaps are removed.
 *      (3) If the image is not 1 bpp and is not jpeg compressed,
 *          and it is to be written as PS with DCT compression
 *          (level = 2), it will first be written to file as jpeg with
 *          quality = 75.  This will cause some degradation in the image.
 *      (4) The bounding box is required when a program such as TeX
 *          (through epsf) places and rescales the image.
 *      (5) The bounding box is sized for fitting the image to an
 *          8.5 x 11.0 inch page.
 */
l_int32
convertToPSEmbed(const char  *filein,
                 const char  *fileout,
                 l_int32      level)
{
l_int32  d, format;
FILE    *fp;
PIX     *pix, *pixs;

    PROCNAME("convertToPSEmbed");

    if (!filein)
        return ERROR_INT("filein not defined", procName, 1);
    if (!fileout)
        return ERROR_INT("fileout not defined", procName, 1);

    if (level == 1) {
        pixWritePSEmbed(filein, fileout);
        return 0;
    }

        /* We must write out level 2 PS */
    if ((fp = fopen(filein, "r")) == NULL)
        return ERROR_INT("filein not found", procName, 1);
    format = findFileFormat(fp);
    fclose(fp);

        /* Write out directly if in jpeg or tiff g4 formats */
    if (format == IFF_JFIF_JPEG) {
        convertJpegToPSEmbed(filein, fileout);
        return 0;
    }
    else if (format == IFF_TIFF_G4) {
        convertTiffG4ToPSEmbed(filein, fileout);
	return 0;
    }

        /* Must convert to jpeg or tiff g4 */
    if ((pixs = pixRead(filein)) == NULL)
        return ERROR_INT("image not read from file", procName, 1);
    d = pixGetDepth(pixs);
    if (d == 16)
        pix = pixConvert16To8(pixs, 1);
    else
        pix = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC);
    d = pixGetDepth(pix);
    if (d == 1) {
        pixWrite(TEMP_G4TIFF_FILE, pix, IFF_TIFF_G4);
        convertTiffG4ToPSEmbed(TEMP_G4TIFF_FILE, fileout);
    }
    else {
        pixWrite(TEMP_JPEG_FILE, pix, IFF_JFIF_JPEG);
        convertJpegToPSEmbed(TEMP_JPEG_FILE, fileout);
    }

    pixDestroy(&pix);
    pixDestroy(&pixs);
    return 0;
}


/*-------------------------------------------------------------*
 *                  For uncompressed images                    *
 *-------------------------------------------------------------*/
/*!
 *  pixWritePSEmbed()
 *
 *      Input:  filein (input file)
 *              fileout (output ps file)
 *      Return: 0 if OK, 1 on error
 *
 *  Notes:
 *      (1) This is a simple wrapper function that generates an
 *          uncompressed PS file, with a bounding box.
 *      (2) The bounding box is required when a program such as TeX
 *          (through epsf) places and rescales the image.
 *      (3) The bounding box is sized for fitting the image to an
 *          8.5 x 11.0 inch page.
 */
l_int32
pixWritePSEmbed(const char  *filein,
                const char  *fileout)
{
l_int32    w, h;
l_float32  scale;
FILE      *fp;
PIX       *pix;

    PROCNAME("pixWritePSEmbed");

    if (!filein)
        return ERROR_INT("filein not defined", procName, 1);
    if (!fileout)
        return ERROR_INT("fileout not defined", procName, 1);

    if ((pix = pixRead(filein)) == NULL)
        return ERROR_INT("image not read from file", procName, 1);
    w = pixGetWidth(pix);
    h = pixGetHeight(pix);
    if (w * 11.0 > h * 8.5)
        scale = 8.5 * 300. / (l_float32)w;
    else
        scale = 11.0 * 300. / (l_float32)h;

    if ((fp = fopen(fileout, "w")) == NULL)
        return ERROR_INT("file not opened for write", procName, 1);
    pixWriteStreamPS(fp, pix, NULL, 0, scale);
    fclose(fp);

    pixDestroy(&pix);
    return 0;
}


/*!
 *  pixWriteStreamPS()
 *
 *      Input:  stream
 *              pix
 *              box  (<optional>)
 *              res  (can use 0 for default of 300 ppi)
 *              scale (to prevent scaling, use either 1.0 or 0.0)
 *      Return: 0 if OK; 1 on error
 *
 *  Notes:
 *      (1) This writes image in PS format, optionally scaled,
 *          adjusted for the printer resolution, and with
 *          a bounding box.
 *      (2) For details on use of parameters, see pixWriteStringPS().
 */
l_int32
pixWriteStreamPS(FILE      *fp,
                 PIX       *pix,
                 BOX       *box,
                 l_int32    res,
                 l_float32  scale)
{
char    *pstring;
l_int32  length;
PIX     *pixc;

    PROCNAME("pixWriteStreamPS");

    if (!fp)
        return (l_int32)ERROR_INT("stream not open", procName, 1);
    if (!pix)
        return (l_int32)ERROR_INT("pix not defined", procName, 1);

    if ((pixc = pixConvertForPSWrap(pix)) == NULL)
        return (l_int32)ERROR_INT("pixc not made", procName, 1);

    pstring = pixWriteStringPS(pixc, box, res, scale);
    length = strlen(pstring);
    fwrite(pstring, 1, length, fp);
    FREE(pstring);
    pixDestroy(&pixc);

    return 0;
}


/*!
 *  pixWriteStringPS()
 *
 *      Input:  pix:  1, 2, 4, 8 bpp, with or without cmap; 32 bpp (RGB)
 *              box:  (a) If box == null, image is placed, optionally scaled,
 *                        in a standard b.b. at the center of the page.
 *                        This is to be used when another program like
 *                        TeX (through epsf) places the image.
 *                    (b) If box != null, image is placed without a
 *                        b.b. at the specified page location and with
 *                        (optional) scaling.  This is to be used when
 *                        you want to specify exactly where (and optionally
 *                        how big) you want the image to be.
 *                        Note that all coordinates are in PS convention,
 *                        with (0,0) at LL corner of the page:
 *                            (x,y)    location of LL corner of image, in mils.
 *                            (w,h)    scaled size, in mils.  Use 0 to
 *                                     scale with "scale" and "res" input.
 *              res:  resolution, in printer ppi.  Use 0 for default (300 ppi).
 *              scale: scale factor.  If no scaling is desired, use
 *                     either 1.0 or 0.0.   Scaling just resets the resolution
 *                     parameter; the actual scaling is done in the
 *                     interpreter at rendering time.  This is important:
 *                     it allows you to scale the image up without
 *                     increasing the file size.
 *
 *      Return: ps string if OK, or null on error
 *
 *  Notes:
 *      (1) OK, this seems a bit complicated, because there are various
 *          ways to scale and not to scale.  Here's a summary:
 *      (2) If you don't want any scaling at all:
 *           * if you are using a box:
 *               set w = 0, h = 0, and use scale = 1.0; it will print
 *               each pixel unscaled at printer resolution
 *           * if you are not using a box:
 *               set scale = 1.0; it will print at printer resolution
 *      (3) If you want the image to be a certain size in inches:
 *           * you must use a box and set the box (w,h) in mils
 *      (4) If you want the image to be scaled by a scale factor != 1.0:
 *           * if you are using a box:
 *               set w = 0, h = 0, and use the desired scale factor;
 *               the higher the printer resolution, the smaller the
 *               image will actually appear.
 *           * if you are not using a box:
 *               set the desired scale factor; the higher the printer
 *               resolution, the smaller the image will actually appear.
 *      (5) Another complication is the proliferation of distance units:
 *           * The interface distances are in milli-inches.
 *           * Three different units are used internally:
 *              - pixels  (units of 1/res inch)
 *              - printer pts (units of 1/72 inch)
 *              - inches
 *           * Here is a quiz on volume units from a reviewer:
 *             How many UK milli-cups in a US kilo-teaspoon?
 *               (Hint: 1.0 US cup = 0.75 UK cup + 0.2 US gill;
 *                      1.0 US gill = 24.0 US teaspoons)
 */
char *
pixWriteStringPS(PIX       *pixs,
                 BOX       *box,
                 l_int32    res,
                 l_float32  scale)
{
char       nib1, nib2;
char       bigbuf[L_BUF_SIZE];
char      *hexdata, *pstring;
l_uint8    byteval;
l_int32    i, j, k, d, wpix, hpix;
l_float32  wpt, hpt, xpt, ypt;
l_int32    wpl, psbpl, hexbytes, boxflag, sampledepth;
l_uint32  *line, *data;
PIX       *pix;
SARRAY    *sa;

    PROCNAME("pixWriteStringPS");

    if (!pixs)
        return (char *)ERROR_PTR("pix not defined", procName, NULL);

    d = pixGetDepth(pixs);
    if (d == 2)
        pix = pixConvert2To8(pixs, 0, 85, 170, 255, 0);
    else if (d == 4)
        pix = pixConvert4To8(pixs, 0);
    else if (d == 16)
        pix = pixConvert16To8(pixs, 1);
    else
        pix = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC);

        /* Get the factors by which PS scales and translates, in pts */
    pixGetDimensions(pix, &wpix, &hpix, &d);
    if (!box)
        boxflag = 0;  /* no scaling; b.b. at center */
    else
        boxflag = 1;  /* no b.b., specify placement and optional scaling */
    getScaledParametersPS(box, wpix, hpix, res, scale, &xpt, &ypt, &wpt, &hpt);

    if (d == 1)
        sampledepth = 1;
    else  /* d == 8 || d == 32 */
        sampledepth = 8;

        /* Convert image data to hex string */
    wpl = pixGetWpl(pix);
    if (d == 1 || d == 8)
        psbpl = (wpix * d + 7) / 8;   /* packed to byte boundary */
    else /* d == 32 */
        psbpl = 3 * wpix;   /* packed to byte boundary */
    data = pixGetData(pix);
    hexbytes = 2 * psbpl * hpix;  /* size of ps hex array */
    if ((hexdata = (char *)CALLOC(hexbytes + 1, sizeof(char))) == NULL)
        return (char *)ERROR_PTR("hexdata not made", procName, NULL);
    if (d == 1 || d == 8) {
        for (i = 0, k = 0; i < hpix; i++) {
            line = data + i * wpl;
            for (j = 0; j < psbpl; j++) {
                byteval = GET_DATA_BYTE(line, j);
                convertByteToHexAscii(byteval, &nib1, &nib2);
                hexdata[k++] = nib1;
                hexdata[k++] = nib2;
            }
        }
    }
    else  {  /* d == 32; hexdata bytes packed RGBRGB..., 2 per sample */
        for (i = 0, k = 0; i < hpix; i++) {
            line = data + i * wpl;
            for (j = 0; j < wpix; j++) {
                byteval = GET_DATA_BYTE(line + j, 0);  /* red */
                convertByteToHexAscii(byteval, &nib1, &nib2);
                hexdata[k++] = nib1;
                hexdata[k++] = nib2;
                byteval = GET_DATA_BYTE(line + j, 1);  /* green */
                convertByteToHexAscii(byteval, &nib1, &nib2);
                hexdata[k++] = nib1;
                hexdata[k++] = nib2;
                byteval = GET_DATA_BYTE(line + j, 2);  /* blue */
                convertByteToHexAscii(byteval, &nib1, &nib2);
                hexdata[k++] = nib1;
                hexdata[k++] = nib2;
            }
        }
    }
    hexdata[k] = '\0';

    if ((sa = sarrayCreate(0)) == NULL)
        return (char *)ERROR_PTR("sa not made", procName, NULL);

    sarrayAddString(sa, (char *)"%!Adobe-PS", 1);
    if (boxflag == 0) {
        sprintf(bigbuf,
            "%%%%BoundingBox: %7.2f %7.2f %7.2f %7.2f",
            xpt, ypt, xpt + wpt, ypt + hpt);
        sarrayAddString(sa, bigbuf, 1);
    }
    else    /* boxflag == 1 */
        sarrayAddString(sa, (char *)"gsave", 1);

    if (d == 1)
        sarrayAddString(sa,
              (char *)"{1 exch sub} settransfer    %invert binary", 1);

    sprintf(bigbuf, "/bpl %d string def         %%bpl as a string", psbpl);
    sarrayAddString(sa, bigbuf, 1);
    sprintf(bigbuf,
        "%7.2f %7.2f translate         %%set image origin in pts", xpt, ypt);
    sarrayAddString(sa, bigbuf, 1);
    sprintf(bigbuf,
        "%7.2f %7.2f scale             %%set image size in pts", wpt, hpt);
    sarrayAddString(sa, bigbuf, 1);
    sprintf(bigbuf,
        "%d %d %d                 %%image dimensions in pixels",
            wpix, hpix, sampledepth);
    sarrayAddString(sa, bigbuf, 1);
    sprintf(bigbuf,
        "[%d %d %d %d %d %d]     %%mapping matrix: [wpix 0 0 -hpix 0 hpix]",
               wpix, 0, 0, -hpix, 0, hpix);
    sarrayAddString(sa, bigbuf, 1);

    if (boxflag == 0) {
        if (d == 1 || d == 8)
            sarrayAddString(sa,
                (char *)"{currentfile bpl readhexstring pop} image", 1);
        else  /* d == 32 */
            sarrayAddString(sa,
              (char *)"{currentfile bpl readhexstring pop} false 3 colorimage",
              1);
    }
    else {  /* boxflag == 1 */
        if (d == 1 || d == 8)
            sarrayAddString(sa,
                (char *)"{currentfile bpl readhexstring pop} bind image", 1);
        else  /* d == 32 */
            sarrayAddString(sa,
          (char *)"{currentfile bpl readhexstring pop} bind false 3 colorimage",
                 1);
    }

    sarrayAddString(sa, hexdata, 0);

    if (boxflag == 0)
        sarrayAddString(sa, (char *)"\nshowpage", 1);
    else  /* boxflag == 1 */
        sarrayAddString(sa, (char *)"\ngrestore", 1);

    if ((pstring = sarrayToString(sa, 1)) == NULL)
        return (char *)ERROR_PTR("pstring not made", procName, NULL);

    sarrayDestroy(&sa);
    pixDestroy(&pix);
    return pstring;
}


/*!
 *  getScaledParametersPS()
 *
 *      Input:  box (<optional> location of image in mils; with
 *                   (x,y) being the LL corner)
 *              wpix (pix width in pixels)
 *              hpix (pix height in pixels)
 *              res (of printer; use 0 for default)
 *              scale (use 1.0 or 0.0 for no scaling) 
 *              &xpt (location of llx in pts)
 *              &ypt (location of lly in pts)
 *              &wpt (image width in pts)
 *              &hpt (image height in pts)
 *      Return: void (no arg checking)
 *
 *  Notes:
 *      (1) The image is always scaled, depending on res and scale.
 *      (2) If no box, the image is centered on the page.
 *      (3) If there is a box, the image is placed within it.
 */
void
getScaledParametersPS(BOX        *box,
                      l_int32     wpix,
                      l_int32     hpix,
                      l_int32     res,
                      l_float32   scale,
                      l_float32  *pxpt,
                      l_float32  *pypt,
                      l_float32  *pwpt,
                      l_float32  *phpt)
{
l_int32    bx, by, bw, bh;
l_float32  winch, hinch, xinch, yinch, fres;

    PROCNAME("getScaledParametersPS");

    if (res == 0)
        res = DEFAULT_PRINTER_RES;
    fres = (l_float32)res;

        /* Allow the PS interpreter to scale the resolution */
    if (scale == 0.0)
        scale = 1.0;
    if (scale != 1.0) {
        fres = (l_float32)res / scale;
        res = (l_int32)fres;
    }

        /* Limit valid resolution interval */
    if (res < MIN_RES || res > MAX_RES) {
        L_WARNING_INT("res %d out of bounds; using default res; no scaling",
                      procName, res);
        res = DEFAULT_PRINTER_RES;
        fres = (l_float32)res;
    }

    if (!box) {  /* center on page */
        winch = (l_float32)wpix / fres;
        hinch = (l_float32)hpix / fres;
        xinch = (8.5 - winch) / 2.;
        yinch = (11.0 - hinch) / 2.;
    }
    else {
        boxGetGeometry(box, &bx, &by, &bw, &bh);
        if (bw == 0)
            winch = (l_float32)wpix / fres;
        else
            winch = (l_float32)bw / 1000.;
        if (bh == 0)
            hinch = (l_float32)hpix / fres;
        else
            hinch = (l_float32)bh / 1000.;
        xinch = (l_float32)bx / 1000.;
        yinch = (l_float32)by / 1000.;
    }

    if (xinch < 0)
        L_WARNING("left edge < 0.0 inch", procName);
    if (xinch + winch > 8.5)
        L_WARNING("right edge > 8.5 inch", procName);
    if (yinch < 0.0)
        L_WARNING("bottom edge < 0.0 inch", procName);
    if (yinch + hinch > 11.0)
        L_WARNING("top edge > 11.0 inch", procName);

    *pwpt = 72. * winch;
    *phpt = 72. * hinch;
    *pxpt = 72. * xinch;
    *pypt = 72. * yinch;
    return;
}
    

/*!
 *  convertByteToHexAscii()
 *
 *      Input:  byteval  (input byte)
 *              &nib1, &nib2  (<return> two hex ascii characters)
 *      Return: void
 */
void
convertByteToHexAscii(l_uint8  byteval,
                      char    *pnib1,
                      char    *pnib2)
{
l_uint8  nib;

    nib = byteval >> 4;
    if (nib < 10)
        *pnib1 = '0' + nib;
    else
        *pnib1 = 'a' + (nib - 10);
    nib = byteval & 0xf;
    if (nib < 10)
        *pnib2 = '0' + nib;
    else
        *pnib2 = 'a' + (nib - 10);

    return;
}


/*-------------------------------------------------------------*
 *                  For jpeg compressed images                 *
 *-------------------------------------------------------------*/
/*!
 *  convertJpegToPSEmbed()
 *
 *      Input:  filein (input jpeg file)
 *              fileout (output ps file)
 *      Return: 0 if OK, 1 on error
 *
 *  Notes:
 *      (1) This function takes a jpeg file as input and generates a DCT
 *          compressed, ascii85 encoded PS file, with a bounding box.
 *      (2) The bounding box is required when a program such as TeX
 *          (through epsf) places and rescales the image.
 *      (3) The bounding box is sized for fitting the image to an
 *          8.5 x 11.0 inch page.
 */
l_int32
convertJpegToPSEmbed(const char  *filein,
                     const char  *fileout)
{
char      *pstring, *outstr;
char      *data85;  /* ascii85 encoded file */
char       bigbuf[512];
l_uint8   *bindata;  /* binary encoded jpeg data (entire file) */
l_int32    bps, w, h, spp;
l_int32    nbinbytes, psbytes, nbytes85, totbytes;
l_float32  xpt, ypt, wpt, hpt;
SARRAY    *sa;

    PROCNAME("convertJpegToPSEmbed");

    if (!filein)
        return ERROR_INT("filein not defined", procName, 1);
    if (!fileout)
        return ERROR_INT("fileout not defined", procName, 1);

        /* The returned jpeg data in memory is the entire jpeg file,
         * which starts with ffd8 and ends with ffd9 */
    if (extractJpegDataFromFile(filein, &bindata, &nbinbytes,
                                &w, &h, &bps, &spp))
        return ERROR_INT("bindata not extracted from file", procName, 1);

        /* Convert entire jpeg file of encoded DCT data to ascii85 */
    data85 = encodeAscii85(bindata, nbinbytes, &nbytes85);
    FREE(bindata);
    if (!data85)
        return ERROR_INT("data85 not made", procName, 1);

        /* Scale for 20 pt boundary and otherwise full filling
         * in one direction on 8.5 x 11 inch device */
    xpt = 20.0;
    ypt = 20.0;
    if (w * 11.0 > h * 8.5) {
        wpt = 572.0;   /* 612 - 2 * 20 */
        hpt = wpt * (l_float32)h / (l_float32)w;
    }
    else {
        hpt = 752.0;   /* 792 - 2 * 20 */
        wpt = hpt * (l_float32)w / (l_float32)h;
    }

        /*  -------- Generate PostScript output -------- */
    if ((sa = sarrayCreate(50)) == NULL)
        return ERROR_INT("sa not made", procName, 1);

    sarrayAddString(sa, (char *)"%!PS-Adobe-3.0", 1);
    sarrayAddString(sa, (char *)"%%Creator: leptonica", 1);
    sprintf(bigbuf, "%%%%Title: %s", filein);
    sarrayAddString(sa, bigbuf, 1);
    sprintf(bigbuf,
        "%%%%BoundingBox: %7.2f %7.2f %7.2f %7.2f",
                   xpt, ypt, xpt + wpt, ypt + hpt);
    sarrayAddString(sa, bigbuf, 1);
    sarrayAddString(sa, (char *)"%%DocumentData: Clean7Bit", 1);
    sarrayAddString(sa, (char *)"%%LanguageLevel: 2", 1);
    sarrayAddString(sa, (char *)"%%EndComments", 1);
    sarrayAddString(sa, (char *)"%%Page: 1 1", 1);

    sarrayAddString(sa, (char *)"save", 1);
    sarrayAddString(sa,
                 (char *)"/RawData currentfile /ASCII85Decode filter def", 1);
    sarrayAddString(sa, (char *)"/Data RawData << >> /DCTDecode filter def", 1);

    sprintf(bigbuf,
        "%7.2f %7.2f translate         %%set image origin in pts", xpt, ypt);
    sarrayAddString(sa, bigbuf, 1);

    sprintf(bigbuf,
        "%7.2f %7.2f scale             %%set image size in pts", wpt, hpt);
    sarrayAddString(sa, bigbuf, 1);

    if (spp == 1)
        sarrayAddString(sa, (char *)"/DeviceGray setcolorspace", 1);
    else if (spp == 3)
        sarrayAddString(sa, (char *)"/DeviceRGB setcolorspace", 1);
    else  /*spp == 4 */
        sarrayAddString(sa, (char *)"/DeviceCMYK setcolorspace", 1);
    
    sarrayAddString(sa, (char *)"{ << /ImageType 1", 1);
    sprintf(bigbuf, "     /Width %d", w);
    sarrayAddString(sa, bigbuf, 1);
    sprintf(bigbuf, "     /Height %d", h);
    sarrayAddString(sa, bigbuf, 1);
    sprintf(bigbuf, "     /ImageMatrix [ %d 0 0 %d 0 %d ]", w, -h, h);
    sarrayAddString(sa, bigbuf, 1);
    sarrayAddString(sa, (char *)"     /DataSource Data", 1);
    sprintf(bigbuf, "     /BitsPerComponent %d", bps);
    sarrayAddString(sa, bigbuf, 1);

    if (spp == 1)
        sarrayAddString(sa, (char *)"     /Decode [0 1]", 1);
    else if (spp == 3)
        sarrayAddString(sa, (char *)"     /Decode [0 1 0 1 0 1]", 1);
    else   /* spp == 4 */
        sarrayAddString(sa, (char *)"     /Decode [0 1 0 1 0 1 0 1]", 1);
    
    sarrayAddString(sa, (char *)"  >> image", 1);
    sarrayAddString(sa, (char *)"  Data closefile", 1);
    sarrayAddString(sa, (char *)"  RawData flushfile", 1);
    sarrayAddString(sa, (char *)"  showpage", 1);
    sarrayAddString(sa, (char *)"  restore", 1);
    sarrayAddString(sa, (char *)"} exec", 1);

    if ((pstring = sarrayToString(sa, 1)) == NULL)
        return ERROR_INT("pstring not made", procName, 1);
    sarrayDestroy(&sa);
    psbytes = strlen(pstring);

        /* Add the ascii85 data */
    totbytes = psbytes + nbytes85;
    if ((outstr = (char *)CALLOC(totbytes + 4, sizeof(char))) == NULL)
        return ERROR_INT("outstr not made", procName, 1);
    memcpy(outstr, pstring, psbytes);
    memcpy(outstr + psbytes, data85, nbytes85);
    FREE(pstring);
    FREE(data85);

    if (arrayWrite(fileout, "w", outstr, totbytes))
        return ERROR_INT("ps string not written to file", procName, 1);
    FREE(outstr);
    return 0;
}


/*!
 *  convertJpegToPS()
 *
 *      Input:  filein (input jpeg file)
 *              fileout (output ps file)
 *              operation ("w" for write; "a" for append)
 *              x, y (location of LL corner of image, in pixels, relative
 *                    to the PostScript origin (0,0) at the LL corner
 *                    of the page)
 *              res (resolution of the input image, in ppi; use 0 for default)
 *              scale (scaling by printer; use 0.0 or 1.0 for no scaling)
 *              pageno (page number; must start with 1; you can use 0
 *                  if there is only one page.)
 *              endpage (boolean: TRUE if the last image to be
 *                  added to the page; FALSE otherwise)
 *      Return: 0 if OK, 1 on error
 *
 *  Notes:
 *      (1) This is simpler to use than pixWriteStringPS(), and
 *          it outputs in level 2 PS as compressed DCT (overlaid
 *          with ascii85 encoding).
 *      (2) An output file can contain multiple pages, each with
 *          multiple images.  The arguments to convertJpegToPS()
 *          allow you to control placement of jpeg images on multiple
 *          pages within a PostScript file.
 *      (3) For the first image written to a file, use "w", which
 *          opens for write and clears the file.  For all subsequent
 *          images written to that file, use "a".
 *      (4) The (x, y) parameters give the LL corner of the image
 *          relative to the LL corner of the page.  They are in
 *          units of pixels if scale = 1.0.  If you use (e.g.)
 *          scale = 2.0, the image is placed at (2x, 2y) on the page,
 *          and the image dimensions are also doubled.
 *      (5) Display vs printed resolution:
 *           * If your display is 75 ppi and your image was created
 *             at a resolution of 300 ppi, you can get the image
 *             to print at the same size as it appears on your display
 *             by either setting scale = 4.0 or by setting  res = 75.
 *             Both tell the printer to make a 4x enlarged image.
 *           * If your image is generated at 150 ppi and you use scale = 1,
 *             it will be rendered such that 150 pixels correspond
 *             to 72 pts (1 inch on the printer).  This function does
 *             the conversion from pixels (with or without scaling) to
 *             pts, which are the units that the printer uses.
 *           * The printer will choose its own resolution to use
 *             in rendering the image, which will not affect the size
 *             of the rendered image.  That is because the output
 *             PostScript file describes the geometry in terms of pts,
 *             which are defined to be 1/72 inch.  The printer will
 *             only see the size of the image in pts, through the
 *             scale and translate parameters and the affine
 *             transform (the ImageMatrix) of the image.
 *      (6) To render multiple images on the same page, set
 *          endpage = FALSE for each image until you get to the
 *          last, for which you set endpage = TRUE.  This causes the
 *          "showpage" command to be invoked.  Showpage outputs
 *          the entire page and clears the raster buffer for the
 *          next page to be added.  Without a "showpage",
 *          subsequent images from the next page will overlay those
 *          previously put down.
 *      (7) For multiple pages, increment the page number, starting
 *          with page 1.  This allows PostScript (and PDF) to build
 *          a page directory, which viewers use for navigation.
 */
l_int32
convertJpegToPS(const char  *filein,
                const char  *fileout,
                const char  *operation,
                l_int32      x,
                l_int32      y,
                l_int32      res,
                l_float32    scale,
                l_int32      pageno,
                l_int32      endpage)
{
char    *outstr;
l_int32  nbytes;

    PROCNAME("convertJpegToPS");

    if (!filein)
        return ERROR_INT("filein not defined", procName, 1);
    if (!fileout)
        return ERROR_INT("fileout not defined", procName, 1);
    if (strcmp(operation, "w") && strcmp(operation, "a"))
        return ERROR_INT("operation must be \"w\" or \"a\"", procName, 1);

    if (convertJpegToPSString(filein, &outstr, &nbytes, x, y, res, scale,
                          pageno, endpage))
        return ERROR_INT("ps string not made", procName, 1);

    if (arrayWrite(fileout, operation, outstr, nbytes))
        return ERROR_INT("ps string not written to file", procName, 1);

    FREE(outstr);
    return 0;
}


/*!
 *  convertJpegToPSString()
 *
 *      Generates PS string in jpeg format from jpeg file
 *
 *      Input:  filein (input jpeg file)
 *              &poutstr (<return> PS string)
 *              &nbytes (<return> number of bytes in PS string)
 *              x, y (location of LL corner of image, in pixels, relative
 *                    to the PostScript origin (0,0) at the LL corner
 *                    of the page)
 *              res (resolution of the input image, in ppi; use 0 for default)
 *              scale (scaling by printer; use 0.0 or 1.0 for no scaling)
 *              pageno (page number; must start with 1; you can use 0
 *                  if there is only one page.)
 *              endpage (boolean: TRUE if the last image to be
 *                  added to the page; FALSE otherwise)
 *      Return: 0 if OK, 1 on error
 *
 *  Notes:
 *      (1) The returned PS character array is binary string, not a
 *          null-terminated ascii C string.  It has null bytes
 *          embedded in it!
 *
 *  Usage:  See convertJpegToPS()
 */
l_int32
convertJpegToPSString(const char  *filein,
                      char       **poutstr,
                      l_int32     *pnbytes,
                      l_int32      x,
                      l_int32      y,
                      l_int32      res,
                      l_float32    scale,
                      l_int32      pageno,
                      l_int32      endpage)
{
char      *pstring, *outstr;
char      *data85;  /* ascii85 encoded file */
char       bigbuf[L_BUF_SIZE];
l_uint8   *bindata;  /* binary encoded jpeg data (entire file) */
l_int32    bps, w, h, spp;
l_int32    nbinbytes, psbytes, nbytes85, totbytes;
l_float32  xpt, ypt, wpt, hpt;
SARRAY    *sa;

    PROCNAME("convertJpegToPSString");

    if (!filein)
        return ERROR_INT("filein not defined", procName, 1);
    if (!poutstr)
        return ERROR_INT("&outstr not defined", procName, 1);
    if (!pnbytes)
        return ERROR_INT("&nbytes not defined", procName, 1);
    *poutstr = NULL;

        /* The returned jpeg data in memory is the entire jpeg file,
         * which starts with ffd8 and ends with ffd9 */
    if (extractJpegDataFromFile(filein, &bindata, &nbinbytes,
                                &w, &h, &bps, &spp))
        return ERROR_INT("bindata not extracted from file", procName, 1);

        /* Convert entire jpeg file of encoded DCT data to ascii85 */
    data85 = encodeAscii85(bindata, nbinbytes, &nbytes85);
    FREE(bindata);
    if (!data85)
        return ERROR_INT("data85 not made", procName, 1);

#if  DEBUG_JPEG
    fprintf(stderr, "w = %d, h = %d, bps = %d, spp = %d\n", w, h, bps, spp);
    fprintf(stderr, "nbinbytes = %d, nbytes85 = %d, ratio = %5.3f\n",
           nbinbytes, nbytes85, (l_float32)nbytes85 / (l_float32)nbinbytes);
#endif   /* DEBUG_JPEG */

        /* Get scaled location in pts */
    if (scale == 0.0)
        scale = 1.0;
    if (res == 0)
        res = DEFAULT_PRINTER_RES;
    xpt = scale * x * 72. / res;
    ypt = scale * y * 72. / res;
    wpt = scale * w * 72. / res;
    hpt = scale * h * 72. / res;

    if (pageno == 0)
        pageno = 1;

#if  DEBUG_JPEG
    fprintf(stderr, "xpt = %7.2f, ypt = %7.2f, wpt = %7.2f, hpt = %7.2f\n",
             xpt, ypt, wpt, hpt);
#endif   /* DEBUG_JPEG */

        /*  -------- Generate PostScript output -------- */
    if ((sa = sarrayCreate(50)) == NULL)
        return ERROR_INT("sa not made", procName, 1);

    sarrayAddString(sa, (char *)"%!PS-Adobe-3.0", 1);
    sarrayAddString(sa, (char *)"%%Creator: leptonica", 1);
    sprintf(bigbuf, "%%%%Title: %s", filein);
    sarrayAddString(sa, bigbuf, 1);

#if  PRINT_BOUNDING_BOX
    sprintf(bigbuf,
        "%%%%BoundingBox: %7.2f %7.2f %7.2f %7.2f",
                   xpt, ypt, xpt + wpt, ypt + hpt);
    sarrayAddString(sa, bigbuf, 1);
#endif  /* PRINT_BOUNDING_BOX */

    sarrayAddString(sa, (char *)"%%DocumentData: Clean7Bit", 1);
    sarrayAddString(sa, (char *)"%%LanguageLevel: 2", 1);
    sarrayAddString(sa, (char *)"%%EndComments", 1);
    sprintf(bigbuf, "%%%%Page: %d %d", pageno, pageno);
    sarrayAddString(sa, bigbuf, 1);

    sarrayAddString(sa, (char *)"save", 1);
    sarrayAddString(sa,
                  (char *)"/RawData currentfile /ASCII85Decode filter def", 1);
    sarrayAddString(sa, (char *)"/Data RawData << >> /DCTDecode filter def", 1);

    sprintf(bigbuf,
        "%7.2f %7.2f translate         %%set image origin in pts", xpt, ypt);
    sarrayAddString(sa, bigbuf, 1);

    sprintf(bigbuf,
        "%7.2f %7.2f scale             %%set image size in pts", wpt, hpt);
    sarrayAddString(sa, bigbuf, 1);

    if (spp == 1)
        sarrayAddString(sa, (char *)"/DeviceGray setcolorspace", 1);
    else if (spp == 3)
        sarrayAddString(sa, (char *)"/DeviceRGB setcolorspace", 1);
    else  /*spp == 4 */
        sarrayAddString(sa, (char *)"/DeviceCMYK setcolorspace", 1);
    
    sarrayAddString(sa, (char *)"{ << /ImageType 1", 1);
    sprintf(bigbuf, "     /Width %d", w);
    sarrayAddString(sa, bigbuf, 1);
    sprintf(bigbuf, "     /Height %d", h);
    sarrayAddString(sa, bigbuf, 1);
    sprintf(bigbuf, "     /ImageMatrix [ %d 0 0 %d 0 %d ]", w, -h, h);
    sarrayAddString(sa, bigbuf, 1);
    sarrayAddString(sa, (char *)"     /DataSource Data", 1);
    sprintf(bigbuf, "     /BitsPerComponent %d", bps);
    sarrayAddString(sa, bigbuf, 1);

    if (spp == 1)
        sarrayAddString(sa, (char *)"     /Decode [0 1]", 1);
    else if (spp == 3)
        sarrayAddString(sa, (char *)"     /Decode [0 1 0 1 0 1]", 1);
    else   /* spp == 4 */
        sarrayAddString(sa, (char *)"     /Decode [0 1 0 1 0 1 0 1]", 1);
    
    sarrayAddString(sa, (char *)"  >> image", 1);
    sarrayAddString(sa, (char *)"  Data closefile", 1);
    sarrayAddString(sa, (char *)"  RawData flushfile", 1);
    if (endpage == TRUE)
        sarrayAddString(sa, (char *)"  showpage", 1);
    sarrayAddString(sa, (char *)"  restore", 1);
    sarrayAddString(sa, (char *)"} exec", 1);

    if ((pstring = sarrayToString(sa, 1)) == NULL)
        return ERROR_INT("pstring not made", procName, 1);
    psbytes = strlen(pstring);

        /* Add the ascii85 data */
    totbytes = psbytes + nbytes85;
    *pnbytes = totbytes;
    if ((outstr = (char *)CALLOC(totbytes + 4, sizeof(char))) == NULL)
        return ERROR_INT("outstr not made", procName, 1);
    *poutstr = outstr;
    memcpy(outstr, pstring, psbytes);
    memcpy(outstr + psbytes, data85, nbytes85);

    sarrayDestroy(&sa);
    FREE(data85);
    FREE(pstring);
    return 0;
}


/*-------------------------------------------------------------*
 *                  For tiff g4 compressed images              *
 *-------------------------------------------------------------*/
/*!
 *  convertTiffG4ToPSEmbed()
 *
 *      Input:  filein (input jpeg file)
 *              fileout (output ps file)
 *      Return: 0 if OK, 1 on error
 *
 *  Notes:
 *      (1) This function takes a g4 compressed tif file as input and
 *          generates a g4 compressed, ascii85 encoded PS file, with
 *          a bounding box.
 *      (2) The bounding box is required when a program such as TeX
 *          (through epsf) places and rescales the image.
 *      (3) The bounding box is sized for fitting the image to an
 *          8.5 x 11.0 inch page.
 *      (4) We paint this through a mask, over whatever is below.
 */
l_int32
convertTiffG4ToPSEmbed(const char  *filein,
                       const char  *fileout)
{
char      *pstring, *pstring2, *outstr;
char      *data85;  /* ascii85 encoded ccitt g4 data */
char       bigbuf[512];
l_uint8   *bindata;  /* binary encoded ccitt g4 data */
l_int32    minisblack;   /* TRUE or FALSE */
l_int32    w, h;
l_int32    nbinbytes, nbytes85, psbytes, psbytes2, totbytes;
l_float32  xpt, ypt, wpt, hpt;
SARRAY    *sa, *sa2;

    PROCNAME("convertTiffG4ToPSEmbed");

    if (!filein)
        return ERROR_INT("filein not defined", procName, 1);
    if (!fileout)
        return ERROR_INT("fileout not defined", procName, 1);

        /* The returned ccitt g4 data in memory is the block of
         * bytes in the tiff file, starting after 8 bytes and
         * ending before the directory. */ 
    if (extractTiffG4DataFromFile(filein, &bindata, &nbinbytes,
                                  &w, &h, &minisblack))
        return ERROR_INT("bindata not extracted from file", procName, 1);

        /* Convert the ccittg4 encoded data to ascii85 */
    data85 = encodeAscii85(bindata, nbinbytes, &nbytes85);
    FREE(bindata);
    if (!data85)
        return ERROR_INT("data85 not made", procName, 1);

        /* Scale for 20 pt boundary and otherwise full filling
         * in one direction on 8.5 x 11 inch device */
    xpt = 20.0;
    ypt = 20.0;
    if (w * 11.0 > h * 8.5) {
        wpt = 572.0;   /* 612 - 2 * 20 */
        hpt = wpt * (l_float32)h / (l_float32)w;
    }
    else {
        hpt = 752.0;   /* 792 - 2 * 20 */
        wpt = hpt * (l_float32)w / (l_float32)h;
    }

        /*  -------- Generate PostScript output -------- */
    if ((sa = sarrayCreate(50)) == NULL)
        return ERROR_INT("sa not made", procName, 1);

    sarrayAddString(sa, (char *)"%!PS-Adobe-3.0", 1);
    sarrayAddString(sa, (char *)"%%Creator: leptonica", 1);
    sprintf(bigbuf, "%%%%Title: %s", filein);
    sarrayAddString(sa, bigbuf, 1);
    sarrayAddString(sa, (char *)"%%DocumentData: Clean7Bit", 1);
    sprintf(bigbuf,
        "%%%%BoundingBox: %7.2f %7.2f %7.2f %7.2f",
                xpt, ypt, xpt + wpt, ypt + hpt);
    sarrayAddString(sa, bigbuf, 1);

    sarrayAddString(sa, (char *)"%%LanguageLevel: 2", 1);
    sarrayAddString(sa, (char *)"%%EndComments", 1);
    sarrayAddString(sa, (char *)"%%Page: 1 1", 1);

    sarrayAddString(sa, (char *)"save", 1);
    sarrayAddString(sa, (char *)"100 dict begin", 1);

    sprintf(bigbuf,
        "%7.2f %7.2f translate         %%set image origin in pts", xpt, ypt);
    sarrayAddString(sa, bigbuf, 1);

    sprintf(bigbuf,
        "%7.2f %7.2f scale             %%set image size in pts", wpt, hpt);
    sarrayAddString(sa, bigbuf, 1);

    sarrayAddString(sa, (char *)"/DeviceGray setcolorspace", 1);

    sarrayAddString(sa, (char *)"{", 1);
    sarrayAddString(sa,
                 (char *)"  /RawData currentfile /ASCII85Decode filter def", 1);
    sarrayAddString(sa, (char *)"  << ", 1);
    sarrayAddString(sa, (char *)"    /ImageType 1", 1);
    sprintf(bigbuf, "    /Width %d", w);
    sarrayAddString(sa, bigbuf, 1);
    sprintf(bigbuf, "    /Height %d", h);
    sarrayAddString(sa, bigbuf, 1);
    sprintf(bigbuf, "    /ImageMatrix [ %d 0 0 %d 0 %d ]", w, -h, h);
    sarrayAddString(sa, bigbuf, 1);
    sarrayAddString(sa, (char *)"    /BitsPerComponent 1", 1);
    sarrayAddString(sa, (char *)"    /Interpolate true", 1);
    if (minisblack)
        sarrayAddString(sa, (char *)"    /Decode [1 0]", 1);
    else  /* miniswhite; typical for 1 bpp */
        sarrayAddString(sa, (char *)"    /Decode [0 1]", 1);
    sarrayAddString(sa, (char *)"    /DataSource RawData", 1);
    sarrayAddString(sa, (char *)"        <<", 1);
    sarrayAddString(sa, (char *)"          /K -1", 1);
    sprintf(bigbuf, "          /Columns %d", w);
    sarrayAddString(sa, bigbuf, 1);
    sprintf(bigbuf, "          /Rows %d", h);
    sarrayAddString(sa, bigbuf, 1);
    sarrayAddString(sa, (char *)"        >> /CCITTFaxDecode filter", 1);
    sarrayAddString(sa, (char *)"  >> imagemask", 1);
    sarrayAddString(sa, (char *)"  RawData flushfile", 1);
    sarrayAddString(sa, (char *)"  showpage", 1);
    sarrayAddString(sa, (char *)"}", 1);

    sarrayAddString(sa, (char *)"%%BeginData:", 1);
    sarrayAddString(sa, (char *)"exec", 1);

    if ((pstring = sarrayToString(sa, 1)) == NULL)
        return ERROR_INT("pstring not made", procName, 1);
    psbytes = strlen(pstring);
    sarrayDestroy(&sa);

        /* Concat the trailing data */
    sa2 = sarrayCreate(10);
    sarrayAddString(sa2, (char *)"%%EndData", 1);
    sarrayAddString(sa2, (char *)"end", 1);
    sarrayAddString(sa2, (char *)"restore", 1);
    if ((pstring2 = sarrayToString(sa2, 1)) == NULL)
        return ERROR_INT("pstring2 not made", procName, 1);
    psbytes2 = strlen(pstring2);
    sarrayDestroy(&sa2);

        /* Add the ascii85 data */
    totbytes = psbytes + psbytes2 + nbytes85;
    if ((outstr = (char *)CALLOC(totbytes + 4, sizeof(char))) == NULL)
        return ERROR_INT("outstr not made", procName, 1);
    memcpy(outstr, pstring, psbytes);
    memcpy(outstr + psbytes, data85, nbytes85);
    memcpy(outstr + psbytes + nbytes85, pstring2, psbytes2);
    FREE(data85);
    FREE(pstring);
    FREE(pstring2);

    if (arrayWrite(fileout, "w", outstr, totbytes))
        return ERROR_INT("ps string not written to file", procName, 1);
    FREE(outstr);
    return 0;
}


/*!
 *  convertTiffG4ToPS()
 *
 *      Input:  filein (input tiff g4 file)
 *              fileout (output ps file)
 *              operation ("w" for write; "a" for append)
 *              x, y (location of LL corner of image, in pixels, relative
 *                    to the PostScript origin (0,0) at the LL corner
 *                    of the page)
 *              res (resolution of the input image, in ppi; typ. values
 *                   are 300 and 600; use 0 for automatic determination
 *                   based on image size)
 *              scale (scaling by printer; use 0.0 or 1.0 for no scaling)
 *              pageno (page number; must start with 1; you can use 0
 *                  if there is only one page.)
 *              mask (boolean: use TRUE if just painting through fg;
 *                    FALSE if painting both fg and bg.
 *              endpage (boolean: use TRUE if the last image to be
 *                  added to the page; FALSE otherwise)
 *      Return: 0 if OK, 1 on error
 *
 *  Notes:
 *      (1) See the usage comments in convertJpegToPS(), some of
 *          which are repeated here.
 *      (2) This is a wrapper for tiff g4.  The PostScript that
 *          is generated is expanded by about 5/4 (due to the
 *          ascii85 encoding.  If you convert to pdf (ps2pdf), the
 *          ascii85 decoder is automatically invoked, so that the
 *          pdf wrapped g4 file is essentially the same size as
 *          the original g4 file.  It's useful to have the PS
 *          file ascii85 encoded, because many printers will not
 *          print binary PS files.
 *      (3) For the first image written to a file, use "w", which
 *          opens for write and clears the file.  For all subsequent
 *          images written to that file, use "a".
 *      (4) To render multiple images on the same page, set
 *          endpage = FALSE for each image until you get to the
 *          last, for which you set endpage = TRUE.  This causes the
 *          "showpage" command to be invoked.  Showpage outputs
 *          the entire page and clears the raster buffer for the
 *          next page to be added.  Without a "showpage",
 *          subsequent images from the next page will overlay those
 *          previously put down.
 *      (5) For multiple images to the same page, where you are writing
 *          both jpeg and tiff-g4, you have two options:
 *           (a) write the g4 first, as either image (mask == false)
 *               or imagemask (mask == true), and then write the
 *               jpeg over it.
 *           (b) write the jpeg first and as the last item, write
 *               the g4 as an imagemask (mask == true), to paint
 *               through the foreground only.  
 *          We have this flexibility with the tiff-g4 because it is 1 bpp.
 *      (6) For multiple pages, increment the page number, starting
 *          with page 1.  This allows PostScript (and PDF) to build
 *          a page directory, which viewers use for navigation.
 */
l_int32
convertTiffG4ToPS(const char  *filein,
                  const char  *fileout,
                  const char  *operation,
                  l_int32      x,
                  l_int32      y,
                  l_int32      res,
                  l_float32    scale,
                  l_int32      pageno,
                  l_int32      mask,
                  l_int32      endpage)
{
char    *outstr;
l_int32  nbytes;

    PROCNAME("convertTiffG4ToPS");

    if (!filein)
        return ERROR_INT("filein not defined", procName, 1);
    if (!fileout)
        return ERROR_INT("fileout not defined", procName, 1);
    if (strcmp(operation, "w") && strcmp(operation, "a"))
        return ERROR_INT("operation must be \"w\" or \"a\"", procName, 1);

    if (convertTiffG4ToPSString(filein, &outstr, &nbytes, x, y, res, scale,
                          pageno, mask, endpage))
        return ERROR_INT("ps string not made", procName, 1);

    if (arrayWrite(fileout, operation, outstr, nbytes))
        return ERROR_INT("ps string not written to file", procName, 1);

    FREE(outstr);
    return 0;
}


/*!
 *  convertTiffG4ToPSString()
 *
 *      Input:  filein (input tiff g4 file)
 *              &poutstr (<return> PS string)
 *              &nbytes (<return> number of bytes in PS string)
 *              x, y (location of LL corner of image, in pixels, relative
 *                    to the PostScript origin (0,0) at the LL corner
 *                    of the page)
 *              res (resolution of the input image, in ppi; typ. values
 *                   are 300 and 600; use 0 for automatic determination
 *                   based on image size)
 *              scale (scaling by printer; use 0.0 or 1.0 for no scaling)
 *              pageno (page number; must start with 1; you can use 0
 *                  if there is only one page.)
 *              mask (boolean: use TRUE if just painting through fg;
 *                    FALSE if painting both fg and bg.
 *              endpage (boolean: use TRUE if the last image to be
 *                  added to the page; FALSE otherwise)
 *      Return: 0 if OK, 1 on error
 *
 *  Notes:
 *      (1) Generates PS string in G4 compressed tiff format from G4 tiff file.
 *      (2) The returned PS character array is binary string, not a
 *          null-terminated C string.  It has null bytes embedded in it!
 *      (3) For usage, see convertTiffG4ToPS().
 */
l_int32
convertTiffG4ToPSString(const char  *filein,
                        char       **poutstr,
                        l_int32     *pnbytes,
                        l_int32      x,
                        l_int32      y,
                        l_int32      res,
                        l_float32    scale,
                        l_int32      pageno,
                        l_int32      mask,
                        l_int32      endpage)
{
char      *pstring, *pstring2, *outstr;
char      *data85;  /* ascii85 encoded ccitt g4 data */
char       bigbuf[L_BUF_SIZE];
l_uint8   *bindata;  /* binary encoded ccitt g4 data */
l_int32    minisblack;   /* TRUE or FALSE */
l_int32    w, h;
l_int32    nbinbytes, nbytes85, psbytes, psbytes2, totbytes;
l_float32  xpt, ypt, wpt, hpt;
SARRAY    *sa, *sa2;

    PROCNAME("convertTiffG4ToPSString");

    if (!filein)
        return ERROR_INT("filein not defined", procName, 1);
    if (!poutstr)
        return ERROR_INT("&outstr not defined", procName, 1);
    if (!pnbytes)
        return ERROR_INT("&nbytes not defined", procName, 1);
    *poutstr = NULL;

        /* The returned ccitt g4 data in memory is the block of
         * bytes in the tiff file, starting after 8 bytes and
         * ending before the directory. */ 
    if (extractTiffG4DataFromFile(filein, &bindata, &nbinbytes,
                                  &w, &h, &minisblack))
        return ERROR_INT("bindata not extracted from file", procName, 1);

#if  DEBUG_G4
/*    arrayWrite("junkarray", "w", bindata, nbinbytes); */
    fprintf(stderr, "nbinbytes = %d, w = %d, h = %d, minisblack = %d\n",
            nbinbytes, w, h, minisblack);
#endif   /* DEBUG_G4 */

        /* Convert the ccittg4 encoded data to ascii85 */
    data85 = encodeAscii85(bindata, nbinbytes, &nbytes85);
    FREE(bindata);
    if (!data85)
        return ERROR_INT("data85 not made", procName, 1);

        /* Get scaled location in pts */
    if (scale == 0.0)
        scale = 1.0;
    if (res == 0) {
        if (h <= 3300)
            res = 300;
        else
            res = 600;
    }
    xpt = scale * x * 72. / res;
    ypt = scale * y * 72. / res;
    wpt = scale * w * 72. / res;
    hpt = scale * h * 72. / res;

#if  DEBUG_G4
    fprintf(stderr, "xpt = %7.2f, ypt = %7.2f, wpt = %7.2f, hpt = %7.2f\n",
             xpt, ypt, wpt, hpt);
#endif   /* DEBUG_G4 */

        /*  -------- generate PostScript output -------- */
    if ((sa = sarrayCreate(50)) == NULL)
        return ERROR_INT("sa not made", procName, 1);

    sarrayAddString(sa, (char *)"%!PS-Adobe-3.0", 1);
    sarrayAddString(sa, (char *)"%%Creator: leptonica", 1);
    sprintf(bigbuf, "%%%%Title: %s", filein);
    sarrayAddString(sa, bigbuf, 1);
    sarrayAddString(sa, (char *)"%%DocumentData: Clean7Bit", 1);

#if  PRINT_BOUNDING_BOX
    sprintf(bigbuf,
        "%%%%BoundingBox: %7.2f %7.2f %7.2f %7.2f",
                xpt, ypt, xpt + wpt, ypt + hpt);
    sarrayAddString(sa, bigbuf, 1);
#endif  /* PRINT_BOUNDING_BOX */

    sarrayAddString(sa, (char *)"%%LanguageLevel: 2", 1);
    sarrayAddString(sa, (char *)"%%EndComments", 1);
    sprintf(bigbuf, "%%%%Page: %d %d", pageno, pageno);
    sarrayAddString(sa, bigbuf, 1);

    sarrayAddString(sa, (char *)"save", 1);
    sarrayAddString(sa, (char *)"100 dict begin", 1);

    sprintf(bigbuf,
        "%7.2f %7.2f translate         %%set image origin in pts", xpt, ypt);
    sarrayAddString(sa, bigbuf, 1);

    sprintf(bigbuf,
        "%7.2f %7.2f scale             %%set image size in pts", wpt, hpt);
    sarrayAddString(sa, bigbuf, 1);

    sarrayAddString(sa, (char *)"/DeviceGray setcolorspace", 1);

    sarrayAddString(sa, (char *)"{", 1);
    sarrayAddString(sa,
                 (char *)"  /RawData currentfile /ASCII85Decode filter def", 1);
    sarrayAddString(sa, (char *)"  << ", 1);
    sarrayAddString(sa, (char *)"    /ImageType 1", 1);
    sprintf(bigbuf, "    /Width %d", w);
    sarrayAddString(sa, bigbuf, 1);
    sprintf(bigbuf, "    /Height %d", h);
    sarrayAddString(sa, bigbuf, 1);
    sprintf(bigbuf, "    /ImageMatrix [ %d 0 0 %d 0 %d ]", w, -h, h);
    sarrayAddString(sa, bigbuf, 1);
    sarrayAddString(sa, (char *)"    /BitsPerComponent 1", 1);
    sarrayAddString(sa, (char *)"    /Interpolate true", 1);
    if (minisblack)
        sarrayAddString(sa, (char *)"    /Decode [1 0]", 1);
    else  /* miniswhite; typical for 1 bpp */
        sarrayAddString(sa, (char *)"    /Decode [0 1]", 1);
    sarrayAddString(sa, (char *)"    /DataSource RawData", 1);
    sarrayAddString(sa, (char *)"        <<", 1);
    sarrayAddString(sa, (char *)"          /K -1", 1);
    sprintf(bigbuf, "          /Columns %d", w);
    sarrayAddString(sa, bigbuf, 1);
    sprintf(bigbuf, "          /Rows %d", h);
    sarrayAddString(sa, bigbuf, 1);
    sarrayAddString(sa, (char *)"        >> /CCITTFaxDecode filter", 1);
    if (mask == TRUE)  /* just paint through the fg */
        sarrayAddString(sa, (char *)"  >> imagemask", 1);
    else  /* paint full image */
        sarrayAddString(sa, (char *)"  >> image", 1);
    sarrayAddString(sa, (char *)"  RawData flushfile", 1);
    if (endpage == TRUE)
        sarrayAddString(sa, (char *)"  showpage", 1);
    sarrayAddString(sa, (char *)"}", 1);

    sarrayAddString(sa, (char *)"%%BeginData:", 1);
    sarrayAddString(sa, (char *)"exec", 1);

    if ((pstring = sarrayToString(sa, 1)) == NULL)
        return ERROR_INT("pstring not made", procName, 1);
    psbytes = strlen(pstring);

        /* Concat the trailing data */
    sa2 = sarrayCreate(10);
    sarrayAddString(sa2, (char *)"%%EndData", 1);
    sarrayAddString(sa2, (char *)"end", 1);
    sarrayAddString(sa2, (char *)"restore", 1);
    if ((pstring2 = sarrayToString(sa2, 1)) == NULL)
        return ERROR_INT("pstring2 not made", procName, 1);
    psbytes2 = strlen(pstring2);

        /* Add the ascii85 data */
    totbytes = psbytes + psbytes2 + nbytes85;
    *pnbytes = totbytes;
    if ((outstr = (char *)CALLOC(totbytes + 4, sizeof(char))) == NULL)
        return ERROR_INT("outstr not made", procName, 1);
    *poutstr = outstr;
    memcpy(outstr, pstring, psbytes);
    memcpy(outstr + psbytes, data85, nbytes85);
    memcpy(outstr + psbytes + nbytes85, pstring2, psbytes2);

    sarrayDestroy(&sa);
    sarrayDestroy(&sa2);
    FREE(data85);
    FREE(pstring);
    FREE(pstring2);
    return 0;
}


/*-------------------------------------------------------------*
 *                     For tiff multipage files                *
 *-------------------------------------------------------------*/
/*!
 *  convertTiffMultipageToPS()
 *
 *      Input:  filein (input tiff multipage file)
 *              fileout (output ps file)
 *              tempfile (<optional> for temporary g4 tiffs;
 *                        use NULL for default)
 *              factor (for filling 8.5 x 11 inch page;
 *                      use 0.0 for DEFAULT_FILL_FRACTION)
 *      Return: 0 if OK, 1 on error
 *      
 *  Notes:
 *      (1) This converts a multipage tiff file of binary page images
 *          into a ccitt g4 compressed PS file.
 *      (2) If the images are generated from a standard resolution fax,
 *          the vertical resolution is doubled to give a normal-looking
 *          aspect ratio.
 */
l_int32
convertTiffMultipageToPS(const char  *filein,
                         const char  *fileout,
                         const char  *tempfile,
                         l_float32    fillfract)
{
const char   tempdefault[] = "/tmp/junk_temp_g4.tif";
const char  *tempname;
l_int32      i, npages, w, h, istiff;
l_float32    scale;
PIX         *pix, *pixs;
FILE        *fp;

    PROCNAME("convertTiffMultipageToPS");

    if (!filein)
        return ERROR_INT("filein not defined", procName, 1);
    if (!fileout)
        return ERROR_INT("fileout not defined", procName, 1);

    if ((fp = fopen(filein, "r")) == NULL)
        return ERROR_INT("file not found", procName, 1);
    istiff = fileFormatIsTiff(fp);
    if (!istiff) {
        fclose(fp);
        return ERROR_INT("file not tiff format", procName, 1);
    }
    tiffGetCount(fp, &npages);
    fclose(fp);

    if (tempfile)
        tempname = tempfile;
    else
        tempname = tempdefault;

    if (fillfract == 0.0)
        fillfract = DEFAULT_FILL_FRACTION;

    for (i = 0; i < npages; i++) {
        if ((pix = pixReadTiff(filein, i)) == NULL)
             return ERROR_INT("pix not made", procName, 1);

        w = pixGetWidth(pix);
        h = pixGetHeight(pix);
        if (w == 1728 && h < w)   /* it's a std res fax */
            pixs = pixScale(pix, 1.0, 2.0);
        else
            pixs = pixClone(pix);

        pixWrite(tempname, pixs, IFF_TIFF_G4);
        scale = L_MIN(fillfract * 2550 / w, fillfract * 3300 / h);
        if (i == 0)
            convertTiffG4ToPS(tempname, fileout, "w", 0, 0, 300, scale,
                              i + 1, FALSE, TRUE);
        else
            convertTiffG4ToPS(tempname, fileout, "a", 0, 0, 300, scale,
                              i + 1, FALSE, TRUE);
        pixDestroy(&pix);
        pixDestroy(&pixs);
    }

    return 0;
}


/*---------------------------------------------------------------------*
 *                          Write to memory                            *
 *---------------------------------------------------------------------*/
/*!
 *  pixWriteMemPS()
 *
 *      Input:  &data (<return> data of tiff compressed image)
 *              &size (<return> size of returned data)
 *              pix
 *              box  (<optional>)
 *              res  (can use 0 for default of 300 ppi)
 *              scale (to prevent scaling, use either 1.0 or 0.0)
 *      Return: 0 if OK, 1 on error
 *
 *  Notes:
 *      (1) See pixWriteStringPS() for usage.
 *      (2) This is just a wrapper for pixWriteStringPS(), which
 *          writes uncompressed image data to memory.
 */
l_int32
pixWriteMemPS(l_uint8  **pdata,
              size_t    *psize,
              PIX       *pix,
              BOX       *box,
              l_int32    res,
              l_float32  scale)
{
    PROCNAME("pixWriteMemPS");

    if (!pdata)
        return ERROR_INT("&data not defined", procName, 1 );
    if (!psize)
        return ERROR_INT("&size not defined", procName, 1 );
    if (!pix)
        return ERROR_INT("&pix not defined", procName, 1 );

    *pdata = (l_uint8 *)pixWriteStringPS(pix, box, res, scale);
    *psize = strlen((char *)(*pdata));
    return 0;
}


/*-------------------------------------------------------------*
 *                    Converting resolution                    *
 *-------------------------------------------------------------*/
/*!
 *  getResLetterPage()
 *
 *      Input:  w (image width, pixels)
 *              h (image height, pixels)
 *              fillfract (fraction in linear dimension of full page, not
 *                         to be exceeded; use 0 for default)
 *      Return: 0 if OK, 1 on error
 */
l_int32
getResLetterPage(l_int32    w,
                 l_int32    h,
                 l_float32  fillfract)
{
l_int32  resw, resh, res;

    if (fillfract == 0.0)
        fillfract = DEFAULT_FILL_FRACTION;
    resw = (l_int32)((w * 72.) / (LETTER_WIDTH * fillfract));
    resh = (l_int32)((h * 72.) / (LETTER_HEIGHT * fillfract));
    res = L_MAX(resw, resh);
    return res;
}


/*!
 *  getResA4Page()
 *
 *      Input:  w (image width, pixels)
 *              h (image height, pixels)
 *              fillfract (fraction in linear dimension of full page, not
 *                        to be exceeded; use 0 for default)
 *      Return: 0 if OK, 1 on error
 */
l_int32
getResA4Page(l_int32    w,
             l_int32    h,
             l_float32  fillfract)
{
l_int32  resw, resh, res;

    if (fillfract == 0.0)
        fillfract = DEFAULT_FILL_FRACTION;
    resw = (l_int32)((w * 72.) / (A4_WIDTH * fillfract));
    resh = (l_int32)((h * 72.) / (A4_HEIGHT * fillfract));
    res = L_MAX(resw, resh);
    return res;
}



/*-------------------------------------------------------------*
 *      Utility for encoding and decoding data with ascii85    *
 *-------------------------------------------------------------*/
/*!
 *  encodeAscii85()
 *
 *      Input:  inarray (input data)
 *              insize (number of bytes in input array)
 *              &outsize (<return> number of bytes in output char array)
 *      Return: chara (with 64 characters + \n in each line)
 *
 *  Notes:
 *      (1) Ghostscript has a stack break if the last line of
 *          data only has a '>', so we avoid the problem by
 *          always putting '~>' on the last line.
 */
char *
encodeAscii85(l_uint8  *inarray,
              l_int32   insize,
              l_int32  *poutsize)
{
char    *chara;
char    *outbuf;
l_int32  maxsize, i, index, outindex, linecount, nbout, eof;

    PROCNAME("encodeAscii85");

    if (!inarray)
        return (char *)ERROR_PTR("inarray not defined", procName, NULL);

        /* Accumulate results in chara */
    maxsize = (l_int32)(80. + (insize * 5. / 4.) *
                        (1. + 2. / MAX_85_LINE_COUNT));
    if ((chara = (char *)CALLOC(maxsize, sizeof(char))) == NULL)
        return (char *)ERROR_PTR("chara not made", procName, NULL);

    if ((outbuf = (char *)CALLOC(8, sizeof(char))) == NULL)
        return (char *)ERROR_PTR("outbuf not made", procName, NULL);

    linecount = 0;
    index = 0;
    outindex = 0;
    while (1) {
        eof = convertChunkToAscii85(inarray, insize, &index, outbuf, &nbout);
        for (i = 0; i < nbout; i++) {
            chara[outindex++] = outbuf[i];
            linecount++;
            if (linecount >= MAX_85_LINE_COUNT) {
                chara[outindex++] = '\n';
                linecount = 0;
            }
        }
        if (eof == TRUE) {
            if (linecount != 0)
                chara[outindex++] = '\n';
            chara[outindex++] = '~';
            chara[outindex++] = '>';
            chara[outindex++] = '\n';
            break;
        }
    }
    
    FREE(outbuf);
    *poutsize = outindex;
    return chara;
}


/*!
 *  convertChunkToAscii85()
 *
 *      Input:  inarray (input data)
 *              insize  (number of bytes in input array)
 *              &index (use and <return> -- ptr)
 *              outbuf (holds 8 ascii chars; we use no more than 7)
 *              &nbsout (<return> number of bytes written to outbuf)
 *      Return: boolean for eof (0 if more data, 1 if end of file)
 *    
 *  Notes:
 *      (1) Attempts to read 4 bytes and write 5.
 *      (2) Writes 1 byte if the value is 0; writes 2 extra bytes if EOF.
 */
l_int32
convertChunkToAscii85(l_uint8  *inarray,
                      l_int32   insize,
                      l_int32  *pindex,
                      char     *outbuf,
                      l_int32  *pnbout)
{
l_uint8   inbyte;
l_uint32  inword, val;
l_int32   eof, index, nread, nbout, i;

    eof = FALSE;
    index = *pindex;
    nread = L_MIN(4, (insize - index));
    if (insize == index + nread)
        eof = TRUE;
    *pindex += nread;  /* save new index */

        /* Read input data and save in l_uint32 */
    inword = 0;
    for (i = 0; i < nread; i++) {
        inbyte = inarray[index + i];
        inword += inbyte << (8 * (3 - i));
    }

#if 0
    fprintf(stderr, "index = %d, nread = %d\n", index, nread);
    fprintf(stderr, "inword = %x\n", inword);
    fprintf(stderr, "eof = %d\n", eof);
#endif
    
        /* Special case: output 1 byte only */
    if (inword == 0) {
        outbuf[0] = 'z';
        nbout = 1;
    }
    else { /* output nread + 1 bytes */
        for (i = 4; i >= 4 - nread; i--) {
            val = inword / power85[i];
            outbuf[4 - i] = (l_uint8)(val + '!');
            inword -= val * power85[i];
        }
        nbout = nread + 1;
    }
    *pnbout = nbout;

    return eof;
}


/*!
 *  decodeAscii85()
 *
 *      Input:  inarray (ascii85 input data)
 *              insize (number of bytes in input array)
 *              &outsize (<return> number of bytes in output l_uint8 array)
 *      Return: outarray (binary)
 *
 *  Notes:
 *      (1) We assume the data is properly encoded, so we do not check
 *          for invalid characters or the final '>' character.
 *      (2) We permit whitespace to be added to the encoding in an
 *          arbitrary way.
 */
l_uint8 *
decodeAscii85(char     *ina,
              l_int32   insize,
              l_int32  *poutsize)
{
char      inc;
char     *pin;
l_uint8   val;
l_uint8  *outa;
l_int32   maxsize, ocount, bytecount, index;
l_uint32  oword;

    PROCNAME("decodeAscii85");

    if (!ina)
        return (l_uint8 *)ERROR_PTR("ina not defined", procName, NULL);

        /* Accumulate results in outa */
    maxsize = (l_int32)(80. + (insize * 4. / 5.));  /* plenty big */
    if ((outa = (l_uint8 *)CALLOC(maxsize, sizeof(l_uint8))) == NULL)
        return (l_uint8 *)ERROR_PTR("outa not made", procName, NULL);

    pin = ina;
    ocount = 0;  /* byte index into outa */
    oword = 0;
    for (index = 0, bytecount = 0; index < insize; index++, pin++) {
        inc = *pin;

        if (inc == ' ' || inc == '\t' || inc == '\n' ||
            inc == '\f' || inc == '\r' || inc == '\v')  /* ignore white space */
            continue;

        val = inc - '!';
        if (val < 85) {
            oword = oword * 85 + val;
            if (bytecount < 4)
                bytecount++;
            else {  /* we have all 5 input chars for the oword */
                outa[ocount] = (oword >> 24) & 0xff;
                outa[ocount + 1] = (oword >> 16) & 0xff;
                outa[ocount + 2] = (oword >> 8) & 0xff;
                outa[ocount + 3] = oword & 0xff;
                ocount += 4;
                bytecount = 0;
                oword = 0;
            }
        }
        else if (inc == 'z' && bytecount == 0) {
            outa[ocount] = 0;
            outa[ocount + 1] = 0;
            outa[ocount + 2] = 0;
            outa[ocount + 3] = 0;
            ocount += 4;
        }
        else if (inc == '~') {  /* end of data */
            fprintf(stderr, " %d extra bytes output\n", bytecount - 1);
            switch (bytecount) {
            case 0:   /* normal eof */
            case 1:   /* error */
                break;
            case 2:   /* 1 extra byte */
                oword = oword * (85 * 85 * 85) + 0xffffff;
                outa[ocount] = (oword >> 24) & 0xff; 
                break;
            case 3:   /* 2 extra bytes */
                oword = oword * (85 * 85) + 0xffff;
                outa[ocount] = (oword >> 24) & 0xff; 
                outa[ocount + 1] = (oword >> 16) & 0xff; 
                break;
            case 4:   /* 3 extra bytes */
                oword = oword * 85 + 0xff;
                outa[ocount] = (oword >> 24) & 0xff; 
                outa[ocount + 1] = (oword >> 16) & 0xff; 
                outa[ocount + 2] = (oword >> 8) & 0xff; 
                break;
            }
            if (bytecount > 1)
                ocount += (bytecount - 1);
            break;
        }
    }
    *poutsize = ocount;

    return outa;
}

/* --------------------------------------------*/
#endif  /* USE_PSIO */
/* --------------------------------------------*/

