diff mupdf-source/thirdparty/leptonica/src/pixcomp.c @ 2:b50eed0cc0ef upstream

ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4. The directory name has changed: no version number in the expanded directory now.
author Franz Glasner <fzglas.hg@dom66.de>
date Mon, 15 Sep 2025 11:43:07 +0200
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mupdf-source/thirdparty/leptonica/src/pixcomp.c	Mon Sep 15 11:43:07 2025 +0200
@@ -0,0 +1,2390 @@
+/*====================================================================*
+ -  Copyright (C) 2001 Leptonica.  All rights reserved.
+ -
+ -  Redistribution and use in source and binary forms, with or without
+ -  modification, are permitted provided that the following conditions
+ -  are met:
+ -  1. Redistributions of source code must retain the above copyright
+ -     notice, this list of conditions and the following disclaimer.
+ -  2. Redistributions in binary form must reproduce the above
+ -     copyright notice, this list of conditions and the following
+ -     disclaimer in the documentation and/or other materials
+ -     provided with the distribution.
+ -
+ -  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ -  ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ -  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ -  A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL ANY
+ -  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ -  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ -  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ -  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ -  OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ -  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ -  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file  pixcomp.c
+ * <pre>
+ *
+ *      Pixcomp creation and destruction
+ *           PIXC     *pixcompCreateFromPix()
+ *           PIXC     *pixcompCreateFromString()
+ *           PIXC     *pixcompCreateFromFile()
+ *           void      pixcompDestroy()
+ *           PIXC     *pixcompCopy()
+
+ *      Pixcomp accessors
+ *           l_int32   pixcompGetDimensions()
+ *           l_int32   pixcompGetParameters()
+ *
+ *      Pixcomp compression selection
+ *           l_int32   pixcompDetermineFormat()
+ *
+ *      Pixcomp conversion to Pix
+ *           PIX      *pixCreateFromPixcomp()
+ *
+ *      Pixacomp creation and destruction
+ *           PIXAC    *pixacompCreate()
+ *           PIXAC    *pixacompCreateWithInit()
+ *           PIXAC    *pixacompCreateFromPixa()
+ *           PIXAC    *pixacompCreateFromFiles()
+ *           PIXAC    *pixacompCreateFromSA()
+ *           void      pixacompDestroy()
+ *
+ *      Pixacomp addition/replacement
+ *           l_int32   pixacompAddPix()
+ *           l_int32   pixacompAddPixcomp()
+ *           static l_int32  pixacompExtendArray()
+ *           l_int32   pixacompReplacePix()
+ *           l_int32   pixacompReplacePixcomp()
+ *           l_int32   pixacompAddBox()
+ *
+ *      Pixacomp accessors
+ *           l_int32   pixacompGetCount()
+ *           PIXC     *pixacompGetPixcomp()
+ *           PIX      *pixacompGetPix()
+ *           l_int32   pixacompGetPixDimensions()
+ *           BOXA     *pixacompGetBoxa()
+ *           l_int32   pixacompGetBoxaCount()
+ *           BOX      *pixacompGetBox()
+ *           l_int32   pixacompGetBoxGeometry()
+ *           l_int32   pixacompGetOffset()
+ *           l_int32   pixacompSetOffset()
+ *
+ *      Pixacomp conversion to Pixa
+ *           PIXA     *pixaCreateFromPixacomp()
+ *
+ *      Combining pixacomp
+ *           l_int32   pixacompJoin()
+ *           PIXAC    *pixacompInterleave()
+ *
+ *      Pixacomp serialized I/O
+ *           PIXAC    *pixacompRead()
+ *           PIXAC    *pixacompReadStream()
+ *           PIXAC    *pixacompReadMem()
+ *           l_int32   pixacompWrite()
+ *           l_int32   pixacompWriteStream()
+ *           l_int32   pixacompWriteMem()
+ *
+ *      Conversion to pdf
+ *           l_int32   pixacompConvertToPdf()
+ *           l_int32   pixacompConvertToPdfData()
+ *           l_int32   pixacompFastConvertToPdfData()
+ *
+ *      Output for debugging
+ *           l_int32   pixacompWriteStreamInfo()
+ *           l_int32   pixcompWriteStreamInfo()
+ *           PIX      *pixacompDisplayTiledAndScaled()
+ *           l_int32   pixacompWriteFiles()
+ *           l_int32   pixcompWriteFile()
+ *
+ *   The Pixacomp is an array of Pixcomp, where each Pixcomp is a compressed
+ *   string of the image.  We don't use reference counting here.
+ *   The basic application is to allow a large array of highly
+ *   compressible images to reside in memory.  We purposely don't
+ *   reuse the Pixa for this, to avoid confusion and programming errors.
+ *
+ *   Three compression formats are used: g4, png and jpeg.
+ *   The compression type can be either specified or defaulted.
+ *   If specified and it is not possible to compress (for example,
+ *   you specify a jpeg on a 1 bpp image or one with a colormap),
+ *   the compression type defaults to png.  The jpeg compression quality
+ *   can be specified using l_setJpegQuality(); otherwise the default is 75.
+ *
+ *   The serialized version of the Pixacomp is similar to that for
+ *   a Pixa, except that each Pixcomp can be compressed by one of
+ *   tiffg4, png, or jpeg.  Unlike serialization of the Pixa,
+ *   serialization of the Pixacomp does not require any imaging
+ *   libraries because it simply reads and writes the compressed data.
+ *
+ *   There are two modes of use in accumulating images:
+ *     (1) addition to the end of the array
+ *     (2) random insertion (replacement) into the array
+ *
+ *   In use, we assume that the array is fully populated up to the
+ *   index value (n - 1), where n is the value of the pixcomp field n.
+ *   Addition can only be made to the end of the fully populated array,
+ *   at the index value n.  Insertion can be made randomly, but again
+ *   only within the array of pixcomps; i.e., within the set of
+ *   indices {0 .... n-1}.  The functions are pixacompReplacePix()
+ *   and pixacompReplacePixcomp(), and they destroy the existing pixcomp.
+ *
+ *   For addition to the end of the array, initialize the pixacomp with
+ *   pixacompCreate(), which generates an empty array of pixcomps ptrs.
+ *   For random insertion and replacement of pixcomp into a pixacomp,
+ *   initialize a fully populated array using pixacompCreateWithInit().
+ *
+ *   The offset field allows you to use an offset-based index to
+ *   access the 0-based ptr array in the pixacomp.  This would typically
+ *   be used to map the pixacomp array index to a page number, or v.v.
+ *   By default, the offset is 0.  For example, suppose you have 50 images,
+ *   corresponding to page numbers 10 - 59.  Then you could use
+ *      pixac = pixacompCreateWithInit(50, 10, ...);
+ *   This would allocate an array of 50 pixcomps, but if you asked for
+ *   the pix at index 10, using pixacompGetPix(pixac, 10), it would
+ *   apply the offset internally, returning the pix at index 0 in the array.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif  /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+#include "pix_internal.h"
+
+    /* Bounds on pixacomp array size */
+static const l_uint32  MaxPtrArraySize = 1000000;
+static const l_int32  InitialPtrArraySize = 20;      /*!< n'importe quoi */
+
+    /* Bound on size for a compressed data string */
+static const size_t  MaxDataSize = 1000000000;   /* 1 GB */
+
+    /* These two globals are defined in writefile.c */
+extern l_int32  NumImageFileFormatExtensions;
+extern const char  *ImageFileFormatExtensions[];
+
+    /* Static functions */
+static l_int32 pixacompExtendArray(PIXAC *pixac);
+static l_int32 pixcompFastConvertToPdfData(PIXC *pixc, const char *title,
+                                           l_uint8 **pdata, size_t *pnbytes);
+
+
+/*---------------------------------------------------------------------*
+ *                  Pixcomp creation and destruction                   *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief   pixcompCreateFromPix()
+ *
+ * \param[in]    pix
+ * \param[in]    comptype   IFF_DEFAULT, IFF_TIFF_G4, IFF_PNG, IFF_JFIF_JPEG
+ * \return  pixc, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) Use %comptype == IFF_DEFAULT to have the compression
+ *          type automatically determined.
+ *      (2) To compress jpeg with a quality other than the default (75), use
+ *             l_jpegSetQuality()
+ * </pre>
+ */
+PIXC *
+pixcompCreateFromPix(PIX     *pix,
+                     l_int32  comptype)
+{
+size_t    size;
+char     *text;
+l_int32   ret, format;
+l_uint8  *data;
+PIXC     *pixc;
+
+    if (!pix)
+        return (PIXC *)ERROR_PTR("pix not defined", __func__, NULL);
+    if (comptype != IFF_DEFAULT && comptype != IFF_TIFF_G4 &&
+        comptype != IFF_PNG && comptype != IFF_JFIF_JPEG)
+        return (PIXC *)ERROR_PTR("invalid comptype", __func__, NULL);
+
+    pixc = (PIXC *)LEPT_CALLOC(1, sizeof(PIXC));
+    pixGetDimensions(pix, &pixc->w, &pixc->h, &pixc->d);
+    pixGetResolution(pix, &pixc->xres, &pixc->yres);
+    if (pixGetColormap(pix))
+        pixc->cmapflag = 1;
+    if ((text = pixGetText(pix)) != NULL)
+        pixc->text = stringNew(text);
+
+    pixcompDetermineFormat(comptype, pixc->d, pixc->cmapflag, &format);
+    pixc->comptype = format;
+    ret = pixWriteMem(&data, &size, pix, format);
+    if (ret) {
+        L_ERROR("write to memory failed\n", __func__);
+        pixcompDestroy(&pixc);
+        return NULL;
+    }
+    pixc->data = data;
+    pixc->size = size;
+
+    return pixc;
+}
+
+
+/*!
+ * \brief   pixcompCreateFromString()
+ *
+ * \param[in]    data        compressed string
+ * \param[in]    size        number of bytes
+ * \param[in]    copyflag    L_INSERT or L_COPY
+ * \return  pixc, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) This works when the compressed string is png, jpeg or tiffg4.
+ *      (2) The copyflag determines if the data in the new Pixcomp is
+ *          a copy of the input data.
+ * </pre>
+ */
+PIXC *
+pixcompCreateFromString(l_uint8  *data,
+                        size_t    size,
+                        l_int32   copyflag)
+{
+l_int32  format, w, h, d, bps, spp, iscmap;
+PIXC    *pixc;
+
+    if (!data)
+        return (PIXC *)ERROR_PTR("data not defined", __func__, NULL);
+    if (copyflag != L_INSERT && copyflag != L_COPY)
+        return (PIXC *)ERROR_PTR("invalid copyflag", __func__, NULL);
+
+    if (pixReadHeaderMem(data, size, &format, &w, &h, &bps, &spp, &iscmap) == 1)
+        return (PIXC *)ERROR_PTR("header data not read", __func__, NULL);
+    pixc = (PIXC *)LEPT_CALLOC(1, sizeof(PIXC));
+    d = (spp == 3) ? 32 : bps * spp;
+    pixc->w = w;
+    pixc->h = h;
+    pixc->d = d;
+    pixc->comptype = format;
+    pixc->cmapflag = iscmap;
+    if (copyflag == L_INSERT)
+        pixc->data = data;
+    else
+        pixc->data = l_binaryCopy(data, size);
+    pixc->size = size;
+    return pixc;
+}
+
+
+/*!
+ * \brief   pixcompCreateFromFile()
+ *
+ * \param[in]    filename
+ * \param[in]    comptype    IFF_DEFAULT, IFF_TIFF_G4, IFF_PNG, IFF_JFIF_JPEG
+ * \return  pixc, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) Use %comptype == IFF_DEFAULT to have the compression
+ *          type automatically determined.
+ *      (2) If the comptype is invalid for this file, the default will
+ *          be substituted.
+ * </pre>
+ */
+PIXC *
+pixcompCreateFromFile(const char  *filename,
+                      l_int32      comptype)
+{
+l_int32   format;
+size_t    nbytes;
+l_uint8  *data;
+PIX      *pix;
+PIXC     *pixc;
+
+    if (!filename)
+        return (PIXC *)ERROR_PTR("filename not defined", __func__, NULL);
+    if (comptype != IFF_DEFAULT && comptype != IFF_TIFF_G4 &&
+        comptype != IFF_PNG && comptype != IFF_JFIF_JPEG)
+        return (PIXC *)ERROR_PTR("invalid comptype", __func__, NULL);
+
+    findFileFormat(filename, &format);
+    if (format == IFF_UNKNOWN) {
+        L_ERROR("unreadable file: %s\n", __func__, filename);
+        return NULL;
+    }
+
+        /* Can we accept the encoded file directly?  Remember that
+         * png is the "universal" compression type, so if requested
+         * it takes precedence.  Otherwise, if the file is already
+         * compressed in g4 or jpeg, just accept the string. */
+    if ((format == IFF_TIFF_G4 && comptype != IFF_PNG) ||
+        (format == IFF_JFIF_JPEG && comptype != IFF_PNG))
+        comptype = format;
+    if (comptype != IFF_DEFAULT && comptype == format) {
+        data = l_binaryRead(filename, &nbytes);
+        if ((pixc = pixcompCreateFromString(data, nbytes, L_INSERT)) == NULL) {
+            LEPT_FREE(data);
+            return (PIXC *)ERROR_PTR("pixc not made (string)", __func__, NULL);
+        }
+        return pixc;
+    }
+
+        /* Need to recompress in the default format */
+    if ((pix = pixRead(filename)) == NULL)
+        return (PIXC *)ERROR_PTR("pix not read", __func__, NULL);
+    if ((pixc = pixcompCreateFromPix(pix, comptype)) == NULL) {
+        pixDestroy(&pix);
+        return (PIXC *)ERROR_PTR("pixc not made", __func__, NULL);
+    }
+    pixDestroy(&pix);
+    return pixc;
+}
+
+
+/*!
+ * \brief   pixcompDestroy()
+ *
+ * \param[in,out]   ppixc   use ptr address so it will be nulled
+ * \return  void
+ *
+ * <pre>
+ * Notes:
+ *      (1) Always nulls the input ptr.
+ * </pre>
+ */
+void
+pixcompDestroy(PIXC  **ppixc)
+{
+PIXC  *pixc;
+
+    if (!ppixc) {
+        L_WARNING("ptr address is null!\n", __func__);
+        return;
+    }
+
+    if ((pixc = *ppixc) == NULL)
+        return;
+
+    LEPT_FREE(pixc->data);
+    if (pixc->text)
+        LEPT_FREE(pixc->text);
+    LEPT_FREE(pixc);
+    *ppixc = NULL;
+}
+
+
+/*!
+ * \brief   pixcompCopy()
+ *
+ * \param[in]    pixcs
+ * \return  pixcd, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) Limit the size of the compressed pix to 500 MB.
+ * </pre>
+ */
+PIXC *
+pixcompCopy(PIXC  *pixcs)
+{
+size_t    size;
+l_uint8  *datas, *datad;
+PIXC     *pixcd;
+
+    if (!pixcs)
+        return (PIXC *)ERROR_PTR("pixcs not defined", __func__, NULL);
+    size = pixcs->size;
+    if (size > MaxDataSize)
+        return (PIXC *)ERROR_PTR("size > 1 GB; too big", __func__, NULL);
+
+    pixcd = (PIXC *)LEPT_CALLOC(1, sizeof(PIXC));
+    pixcd->w = pixcs->w;
+    pixcd->h = pixcs->h;
+    pixcd->d = pixcs->d;
+    pixcd->xres = pixcs->xres;
+    pixcd->yres = pixcs->yres;
+    pixcd->comptype = pixcs->comptype;
+    if (pixcs->text != NULL)
+        pixcd->text = stringNew(pixcs->text);
+    pixcd->cmapflag = pixcs->cmapflag;
+
+        /* Copy image data */
+    datas = pixcs->data;
+    if ((datad = (l_uint8 *)LEPT_CALLOC(size, sizeof(l_int8))) == NULL) {
+        pixcompDestroy(&pixcd);
+        return (PIXC *)ERROR_PTR("pixcd not made", __func__, NULL);
+    }
+    memcpy(datad, datas, size);
+    pixcd->data = datad;
+    pixcd->size = size;
+    return pixcd;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                           Pixcomp accessors                         *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief   pixcompGetDimensions()
+ *
+ * \param[in]    pixc
+ * \param[out]   pw, ph, pd    [optional]
+ * \return  0 if OK, 1 on error
+ */
+l_ok
+pixcompGetDimensions(PIXC     *pixc,
+                     l_int32  *pw,
+                     l_int32  *ph,
+                     l_int32  *pd)
+{
+    if (!pixc)
+        return ERROR_INT("pixc not defined", __func__, 1);
+    if (pw) *pw = pixc->w;
+    if (ph) *ph = pixc->h;
+    if (pd) *pd = pixc->d;
+    return 0;
+}
+
+
+/*!
+ * \brief   pixcompGetParameters()
+ *
+ * \param[in]    pixc
+ * \param[out]   pxres, pyres, pcomptype, pcmapflag   [optional]
+ * \return  0 if OK, 1 on error
+ */
+l_ok
+pixcompGetParameters(PIXC     *pixc,
+                     l_int32  *pxres,
+                     l_int32  *pyres,
+                     l_int32  *pcomptype,
+                     l_int32  *pcmapflag)
+{
+    if (!pixc)
+        return ERROR_INT("pixc not defined", __func__, 1);
+    if (pxres) *pxres = pixc->xres;
+    if (pyres) *pyres = pixc->yres;
+    if (pcomptype) *pcomptype = pixc->comptype;
+    if (pcmapflag) *pcmapflag = pixc->cmapflag;
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                    Pixcomp compression selection                    *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief   pixcompDetermineFormat()
+ *
+ * \param[in]    comptype   IFF_DEFAULT, IFF_TIFF_G4, IFF_PNG, IFF_JFIF_JPEG
+ * \param[in]    d          pix depth
+ * \param[in]    cmapflag   1 if pix to be compressed as a colormap; 0 otherwise
+ * \param[out]   pformat    IFF_TIFF, IFF_PNG or IFF_JFIF_JPEG
+ * \return  0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) This determines the best format for a pix, given both
+ *          the request (%comptype) and the image characteristics.
+ *      (2) If %comptype == IFF_DEFAULT, this does not necessarily result
+ *          in png encoding.  Instead, it returns one of the three formats
+ *          that is both valid and most likely to give best compression.
+ *      (3) If %d == 8 with no colormap and:
+ *          * you wish to compress with png, use %comptype == IFF_PNG
+ *          * you wish to compress with jpeg, use either
+ *            %comptype == IFF_JFIF_JPEG or %comptype == IFF_DEFAULT.
+ *      (4) If the pix cannot be compressed by the input value of
+ *          %comptype, this selects IFF_PNG, which can compress all pix.
+ * </pre>
+ */
+l_ok
+pixcompDetermineFormat(l_int32   comptype,
+                       l_int32   d,
+                       l_int32   cmapflag,
+                       l_int32  *pformat)
+{
+
+    if (!pformat)
+        return ERROR_INT("&format not defined", __func__, 1);
+    *pformat = IFF_PNG;  /* init value and default */
+    if (comptype != IFF_DEFAULT && comptype != IFF_TIFF_G4 &&
+        comptype != IFF_PNG && comptype != IFF_JFIF_JPEG)
+        return ERROR_INT("invalid comptype", __func__, 1);
+
+    if (comptype == IFF_DEFAULT) {
+        if (d == 1)
+            *pformat = IFF_TIFF_G4;
+        else if (d == 16)
+            *pformat = IFF_PNG;
+        else if (d >= 8 && !cmapflag)
+            *pformat = IFF_JFIF_JPEG;
+    } else if (comptype == IFF_TIFF_G4 && d == 1) {
+        *pformat = IFF_TIFF_G4;
+    } else if (comptype == IFF_JFIF_JPEG && d >= 8 && !cmapflag) {
+        *pformat = IFF_JFIF_JPEG;
+    }
+
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                      Pixcomp conversion to Pix                      *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief   pixCreateFromPixcomp()
+ *
+ * \param[in]    pixc
+ * \return  pix, or NULL on error
+ */
+PIX *
+pixCreateFromPixcomp(PIXC  *pixc)
+{
+l_int32  w, h, d, cmapinpix, format;
+PIX     *pix;
+
+    if (!pixc)
+        return (PIX *)ERROR_PTR("pixc not defined", __func__, NULL);
+
+    if ((pix = pixReadMem(pixc->data, pixc->size)) == NULL)
+        return (PIX *)ERROR_PTR("pix not read", __func__, NULL);
+    pixSetResolution(pix, pixc->xres, pixc->yres);
+    if (pixc->text)
+        pixSetText(pix, pixc->text);
+
+        /* Check fields for consistency */
+    pixGetDimensions(pix, &w, &h, &d);
+    if (pixc->w != w) {
+        L_INFO("pix width %d != pixc width %d\n", __func__, w, pixc->w);
+        L_ERROR("pix width %d != pixc width\n", __func__, w);
+    }
+    if (pixc->h != h)
+        L_ERROR("pix height %d != pixc height\n", __func__, h);
+    if (pixc->d != d) {
+        if (pixc->d == 16)  /* we strip 16 --> 8 bpp by default */
+            L_WARNING("pix depth %d != pixc depth 16\n", __func__, d);
+        else
+            L_ERROR("pix depth %d != pixc depth\n", __func__, d);
+    }
+    cmapinpix = (pixGetColormap(pix) != NULL);
+    if ((cmapinpix && !pixc->cmapflag) || (!cmapinpix && pixc->cmapflag))
+        L_ERROR("pix cmap flag inconsistent\n", __func__);
+    format = pixGetInputFormat(pix);
+    if (format != pixc->comptype) {
+        L_ERROR("pix comptype %d not equal to pixc comptype\n",
+                    __func__, format);
+    }
+
+    return pix;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                Pixacomp creation and destruction                    *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief   pixacompCreate()
+ *
+ * \param[in]    n    initial number of ptrs
+ * \return  pixac, or NULL on error
+ */
+PIXAC *
+pixacompCreate(l_int32  n)
+{
+PIXAC  *pixac;
+
+    if (n <= 0 || n > (l_int32)MaxPtrArraySize)
+        n = InitialPtrArraySize;
+
+    pixac = (PIXAC *)LEPT_CALLOC(1, sizeof(PIXAC));
+    pixac->n = 0;
+    pixac->nalloc = n;
+    pixac->offset = 0;
+    if ((pixac->pixc = (PIXC **)LEPT_CALLOC(n, sizeof(PIXC *))) == NULL) {
+        pixacompDestroy(&pixac);
+        return (PIXAC *)ERROR_PTR("pixc ptrs not made", __func__, NULL);
+    }
+    if ((pixac->boxa = boxaCreate(n)) == NULL) {
+        pixacompDestroy(&pixac);
+        return (PIXAC *)ERROR_PTR("boxa not made", __func__, NULL);
+    }
+
+    return pixac;
+}
+
+
+/*!
+ * \brief   pixacompCreateWithInit()
+ *
+ * \param[in]    n          initial number of ptrs
+ * \param[in]    offset     difference: accessor index - pixacomp array index
+ * \param[in]    pix        [optional] initialize each ptr in pixacomp
+ *                          to this pix; can be NULL
+ * \param[in]    comptype   IFF_DEFAULT, IFF_TIFF_G4, IFF_PNG, IFF_JFIF_JPEG
+ * \return  pixac, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) Initializes a pixacomp to be fully populated with %pix,
+ *          compressed using %comptype.  If %pix == NULL, %comptype
+ *          is ignored.
+ *      (2) Typically, the array is initialized with a tiny pix.
+ *          This is most easily done by setting %pix == NULL, causing
+ *          initialization of each array element with a tiny placeholder
+ *          pix (w = h = d = 1), using comptype = IFF_TIFF_G4 .
+ *      (3) Example usage:
+ *            // Generate pixacomp for pages 30 - 49.  This has an array
+ *            // size of 20 and the page number offset is 30.
+ *            PixaComp *pixac = pixacompCreateWithInit(20, 30, NULL,
+ *                                                     IFF_TIFF_G4);
+ *            // Now insert png-compressed images into the initialized array
+ *            for (pageno = 30; pageno < 50; pageno++) {
+ *                Pix *pixt = ...   // derived from image[pageno]
+ *                if (pixt)
+ *                    pixacompReplacePix(pixac, pageno, pixt, IFF_PNG);
+ *                pixDestroy(&pixt);
+ *            }
+ *          The result is a pixac with 20 compressed strings, and with
+ *          selected pixt replacing the placeholders.
+ *          To extract the image for page 38, which is decompressed
+ *          from element 8 in the array, use:
+ *            pixt = pixacompGetPix(pixac, 38);
+ * </pre>
+ */
+PIXAC *
+pixacompCreateWithInit(l_int32  n,
+                       l_int32  offset,
+                       PIX     *pix,
+                       l_int32  comptype)
+{
+l_int32  i;
+PIX     *pixt;
+PIXC    *pixc;
+PIXAC   *pixac;
+
+    if (n <= 0 || n > (l_int32)MaxPtrArraySize)
+        return (PIXAC *)ERROR_PTR("n out of valid bounds", __func__, NULL);
+    if (pix) {
+        if (comptype != IFF_DEFAULT && comptype != IFF_TIFF_G4 &&
+            comptype != IFF_PNG && comptype != IFF_JFIF_JPEG)
+            return (PIXAC *)ERROR_PTR("invalid comptype", __func__, NULL);
+    } else {
+        comptype = IFF_TIFF_G4;
+    }
+    if (offset < 0) {
+        L_WARNING("offset < 0; setting to 0\n", __func__);
+        offset = 0;
+    }
+
+    if ((pixac = pixacompCreate(n)) == NULL)
+        return (PIXAC *)ERROR_PTR("pixac not made", __func__, NULL);
+    pixacompSetOffset(pixac, offset);
+    if (pix)
+        pixt = pixClone(pix);
+    else
+        pixt = pixCreate(1, 1, 1);
+    for (i = 0; i < n; i++) {
+        pixc = pixcompCreateFromPix(pixt, comptype);
+        pixacompAddPixcomp(pixac, pixc, L_INSERT);
+    }
+    pixDestroy(&pixt);
+
+    return pixac;
+}
+
+
+/*!
+ * \brief   pixacompCreateFromPixa()
+ *
+ * \param[in]    pixa
+ * \param[in]    comptype    IFF_DEFAULT, IFF_TIFF_G4, IFF_PNG, IFF_JFIF_JPEG
+ * \param[in]    accesstype  L_COPY, L_CLONE, L_COPY_CLONE
+ * \return  0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) If %format == IFF_DEFAULT, the conversion format for each
+ *          image is chosen automatically.  Otherwise, we use the
+ *          specified format unless it can't be done (e.g., jpeg
+ *          for a 1, 2 or 4 bpp pix, or a pix with a colormap),
+ *          in which case we use the default (assumed best) compression.
+ *      (2) %accesstype is used to extract a boxa from %pixa.
+ *      (3) To compress jpeg with a quality other than the default (75), use
+ *             l_jpegSetQuality()
+ * </pre>
+ */
+PIXAC *
+pixacompCreateFromPixa(PIXA    *pixa,
+                       l_int32  comptype,
+                       l_int32  accesstype)
+{
+l_int32  i, n;
+BOXA    *boxa;
+PIX     *pix;
+PIXAC   *pixac;
+
+    if (!pixa)
+        return (PIXAC *)ERROR_PTR("pixa not defined", __func__, NULL);
+    if (comptype != IFF_DEFAULT && comptype != IFF_TIFF_G4 &&
+        comptype != IFF_PNG && comptype != IFF_JFIF_JPEG)
+        return (PIXAC *)ERROR_PTR("invalid comptype", __func__, NULL);
+    if (accesstype != L_COPY && accesstype != L_CLONE &&
+        accesstype != L_COPY_CLONE)
+        return (PIXAC *)ERROR_PTR("invalid accesstype", __func__, NULL);
+
+    n = pixaGetCount(pixa);
+    if ((pixac = pixacompCreate(n)) == NULL)
+        return (PIXAC *)ERROR_PTR("pixac not made", __func__, NULL);
+    for (i = 0; i < n; i++) {
+        pix = pixaGetPix(pixa, i, L_CLONE);
+        pixacompAddPix(pixac, pix, comptype);
+        pixDestroy(&pix);
+    }
+    if ((boxa = pixaGetBoxa(pixa, accesstype)) != NULL) {
+        boxaDestroy(&pixac->boxa);
+        pixac->boxa = boxa;
+    }
+
+    return pixac;
+}
+
+
+/*!
+ * \brief   pixacompCreateFromFiles()
+ *
+ * \param[in]    dirname
+ * \param[in]    substr    [optional] substring filter on filenames; can be null
+ * \param[in]    comptype  IFF_DEFAULT, IFF_TIFF_G4, IFF_PNG, IFF_JFIF_JPEG
+ * \return  pixac, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) %dirname is the full path for the directory.
+ *      (2) %substr is the part of the file name (excluding
+ *          the directory) that is to be matched.  All matching
+ *          filenames are read into the Pixa.  If substr is NULL,
+ *          all filenames are read into the Pixa.
+ *      (3) Use %comptype == IFF_DEFAULT to have the compression
+ *          type automatically determined for each file.
+ *      (4) If the comptype is invalid for a file, the default will
+ *          be substituted.
+ * </pre>
+ */
+PIXAC *
+pixacompCreateFromFiles(const char  *dirname,
+                        const char  *substr,
+                        l_int32      comptype)
+{
+PIXAC    *pixac;
+SARRAY   *sa;
+
+    if (!dirname)
+        return (PIXAC *)ERROR_PTR("dirname not defined", __func__, NULL);
+    if (comptype != IFF_DEFAULT && comptype != IFF_TIFF_G4 &&
+        comptype != IFF_PNG && comptype != IFF_JFIF_JPEG)
+        return (PIXAC *)ERROR_PTR("invalid comptype", __func__, NULL);
+
+    if ((sa = getSortedPathnamesInDirectory(dirname, substr, 0, 0)) == NULL)
+        return (PIXAC *)ERROR_PTR("sa not made", __func__, NULL);
+    pixac = pixacompCreateFromSA(sa, comptype);
+    sarrayDestroy(&sa);
+    return pixac;
+}
+
+
+/*!
+ * \brief   pixacompCreateFromSA()
+ *
+ * \param[in]    sa         full pathnames for all files
+ * \param[in]    comptype   IFF_DEFAULT, IFF_TIFF_G4, IFF_PNG, IFF_JFIF_JPEG
+ * \return  pixac, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) Use %comptype == IFF_DEFAULT to have the compression
+ *          type automatically determined for each file.
+ *      (2) If the comptype is invalid for a file, the default will
+ *          be substituted.
+ * </pre>
+ */
+PIXAC *
+pixacompCreateFromSA(SARRAY  *sa,
+                     l_int32  comptype)
+{
+char    *str;
+l_int32  i, n;
+PIXC    *pixc;
+PIXAC   *pixac;
+
+    if (!sa)
+        return (PIXAC *)ERROR_PTR("sarray not defined", __func__, NULL);
+    if (comptype != IFF_DEFAULT && comptype != IFF_TIFF_G4 &&
+        comptype != IFF_PNG && comptype != IFF_JFIF_JPEG)
+        return (PIXAC *)ERROR_PTR("invalid comptype", __func__, NULL);
+
+    n = sarrayGetCount(sa);
+    pixac = pixacompCreate(n);
+    for (i = 0; i < n; i++) {
+        str = sarrayGetString(sa, i, L_NOCOPY);
+        if ((pixc = pixcompCreateFromFile(str, comptype)) == NULL) {
+            L_ERROR("pixc not read from file: %s\n", __func__, str);
+            continue;
+        }
+        pixacompAddPixcomp(pixac, pixc, L_INSERT);
+    }
+    return pixac;
+}
+
+
+/*!
+ * \brief   pixacompDestroy()
+ *
+ * \param[in,out]   ppixac   use ptr address so it will be nulled
+ * \return  void
+ *
+ * <pre>
+ * Notes:
+ *      (1) Always nulls the input ptr.
+ * </pre>
+ */
+void
+pixacompDestroy(PIXAC  **ppixac)
+{
+l_int32  i;
+PIXAC   *pixac;
+
+    if (ppixac == NULL) {
+        L_WARNING("ptr address is NULL!\n", __func__);
+        return;
+    }
+
+    if ((pixac = *ppixac) == NULL)
+        return;
+
+    for (i = 0; i < pixac->n; i++)
+        pixcompDestroy(&pixac->pixc[i]);
+    LEPT_FREE(pixac->pixc);
+    boxaDestroy(&pixac->boxa);
+    LEPT_FREE(pixac);
+    *ppixac = NULL;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                          Pixacomp addition                          *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief   pixacompAddPix()
+ *
+ * \param[in]    pixac
+ * \param[in]    pix        to be added
+ * \param[in]    comptype   IFF_DEFAULT, IFF_TIFF_G4, IFF_PNG, IFF_JFIF_JPEG
+ * \return  0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) The array is filled up to the (n-1)-th element, and this
+ *          converts the input pix to a pixc and adds it at
+ *          the n-th position.
+ *      (2) The pixc produced from the pix is owned by the pixac.
+ *          The input pix is not affected.
+ * </pre>
+ */
+l_ok
+pixacompAddPix(PIXAC   *pixac,
+               PIX     *pix,
+               l_int32  comptype)
+{
+l_int32  cmapflag, format;
+PIXC    *pixc;
+
+    if (!pixac)
+        return ERROR_INT("pixac not defined", __func__, 1);
+    if (!pix)
+        return ERROR_INT("pix not defined", __func__, 1);
+    if (comptype != IFF_DEFAULT && comptype != IFF_TIFF_G4 &&
+        comptype != IFF_PNG && comptype != IFF_JFIF_JPEG)
+        return ERROR_INT("invalid format", __func__, 1);
+
+    cmapflag = pixGetColormap(pix) ? 1 : 0;
+    pixcompDetermineFormat(comptype, pixGetDepth(pix), cmapflag, &format);
+    if ((pixc = pixcompCreateFromPix(pix, format)) == NULL)
+        return ERROR_INT("pixc not made", __func__, 1);
+    pixacompAddPixcomp(pixac, pixc, L_INSERT);
+    return 0;
+}
+
+
+/*!
+ * \brief   pixacompAddPixcomp()
+ *
+ * \param[in]    pixac
+ * \param[in]    pixc       to be added by insertion
+ * \param[in]    copyflag   L_INSERT, L_COPY
+ * \return  0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) Anything added to a pixac is owned by the pixac.
+ *          So do not L_INSERT a pixc that is owned by another pixac,
+ *          or destroy a pixc that has been L_INSERTed.
+ * </pre>
+ */
+l_ok
+pixacompAddPixcomp(PIXAC   *pixac,
+                   PIXC    *pixc,
+                   l_int32  copyflag)
+{
+l_int32  n;
+
+    if (!pixac)
+        return ERROR_INT("pixac not defined", __func__, 1);
+    if (!pixc)
+        return ERROR_INT("pixc not defined", __func__, 1);
+    if (copyflag != L_INSERT && copyflag != L_COPY)
+        return ERROR_INT("invalid copyflag", __func__, 1);
+
+    n = pixac->n;
+    if (n >= pixac->nalloc) {
+        if (pixacompExtendArray(pixac))
+            return ERROR_INT("extension failed", __func__, 1);
+    }
+
+    if (copyflag == L_INSERT)
+        pixac->pixc[n] = pixc;
+    else  /* L_COPY */
+        pixac->pixc[n] = pixcompCopy(pixc);
+    pixac->n++;
+
+    return 0;
+}
+
+
+/*!
+ * \brief   pixacompExtendArray()
+ *
+ * \param[in]    pixac
+ * \return  0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) We extend the boxa array simultaneously.  This is
+ *          necessary in case we are NOT adding boxes simultaneously
+ *          with adding pixc.  We always want the sizes of the
+ *          pixac and boxa ptr arrays to be equal.
+ *      (2) The max number of pixcomp ptrs is 1M.
+ * </pre>
+ */
+static l_int32
+pixacompExtendArray(PIXAC  *pixac)
+{
+size_t  oldsize, newsize;
+
+    if (!pixac)
+        return ERROR_INT("pixac not defined", __func__, 1);
+    if (pixac->nalloc > (l_int32)MaxPtrArraySize)  /* belt & suspenders */
+        return ERROR_INT("pixac has too many ptrs", __func__, 1);
+    oldsize = pixac->nalloc * sizeof(PIXC *);
+    newsize = 2 * oldsize;
+    if (newsize > 8 * MaxPtrArraySize)  /* ptrs for 1M pixcomp */
+        return ERROR_INT("newsize > 8 MB; too large", __func__, 1);
+
+    if ((pixac->pixc = (PIXC **)reallocNew((void **)&pixac->pixc,
+                                           oldsize, newsize)) == NULL)
+        return ERROR_INT("new ptr array not returned", __func__, 1);
+    pixac->nalloc *= 2;
+    boxaExtendArray(pixac->boxa);
+    return 0;
+}
+
+
+/*!
+ * \brief   pixacompReplacePix()
+ *
+ * \param[in]    pixac
+ * \param[in]    index      caller's view of index within pixac; includes offset
+ * \param[in]    pix        owned by the caller
+ * \param[in]    comptype   IFF_DEFAULT, IFF_TIFF_G4, IFF_PNG, IFF_JFIF_JPEG
+ * \return  0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) The %index includes the offset, which must be subtracted
+ *          to get the actual index into the ptr array.
+ *      (2) The input %pix is converted to a pixc, which is then inserted
+ *          into the pixac.
+ * </pre>
+ */
+l_ok
+pixacompReplacePix(PIXAC   *pixac,
+                   l_int32  index,
+                   PIX     *pix,
+                   l_int32  comptype)
+{
+l_int32  n, aindex;
+PIXC    *pixc;
+
+    if (!pixac)
+        return ERROR_INT("pixac not defined", __func__, 1);
+    n = pixacompGetCount(pixac);
+    aindex = index - pixac->offset;
+    if (aindex < 0 || aindex >= n)
+        return ERROR_INT("array index out of bounds", __func__, 1);
+    if (!pix)
+        return ERROR_INT("pix not defined", __func__, 1);
+    if (comptype != IFF_DEFAULT && comptype != IFF_TIFF_G4 &&
+        comptype != IFF_PNG && comptype != IFF_JFIF_JPEG)
+        return ERROR_INT("invalid format", __func__, 1);
+
+    pixc = pixcompCreateFromPix(pix, comptype);
+    pixacompReplacePixcomp(pixac, index, pixc);
+    return 0;
+}
+
+
+/*!
+ * \brief   pixacompReplacePixcomp()
+ *
+ * \param[in]    pixac
+ * \param[in]    index   caller's view of index within pixac; includes offset
+ * \param[in]    pixc    to replace existing one, which is destroyed
+ * \return  0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) The %index includes the offset, which must be subtracted
+ *          to get the actual index into the ptr array.
+ *      (2) The inserted %pixc is now owned by the pixac.  The caller
+ *          must not destroy it.
+ * </pre>
+ */
+l_ok
+pixacompReplacePixcomp(PIXAC   *pixac,
+                       l_int32  index,
+                       PIXC    *pixc)
+{
+l_int32  n, aindex;
+PIXC    *pixct;
+
+    if (!pixac)
+        return ERROR_INT("pixac not defined", __func__, 1);
+    n = pixacompGetCount(pixac);
+    aindex = index - pixac->offset;
+    if (aindex < 0 || aindex >= n)
+        return ERROR_INT("array index out of bounds", __func__, 1);
+    if (!pixc)
+        return ERROR_INT("pixc not defined", __func__, 1);
+
+    pixct = pixacompGetPixcomp(pixac, index, L_NOCOPY);  /* use %index */
+    pixcompDestroy(&pixct);
+    pixac->pixc[aindex] = pixc;  /* replace; use array index */
+
+    return 0;
+}
+
+
+/*!
+ * \brief   pixacompAddBox()
+ *
+ * \param[in]    pixac
+ * \param[in]    box
+ * \param[in]    copyflag   L_INSERT, L_COPY
+ * \return  0 if OK, 1 on error
+ */
+l_ok
+pixacompAddBox(PIXAC   *pixac,
+               BOX     *box,
+               l_int32  copyflag)
+{
+    if (!pixac)
+        return ERROR_INT("pixac not defined", __func__, 1);
+    if (!box)
+        return ERROR_INT("box not defined", __func__, 1);
+    if (copyflag != L_INSERT && copyflag != L_COPY)
+        return ERROR_INT("invalid copyflag", __func__, 1);
+
+    boxaAddBox(pixac->boxa, box, copyflag);
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                         Pixacomp accessors                          *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief   pixacompGetCount()
+ *
+ * \param[in]    pixac
+ * \return  count, or 0 if no pixa
+ */
+l_int32
+pixacompGetCount(PIXAC  *pixac)
+{
+    if (!pixac)
+        return ERROR_INT("pixac not defined", __func__, 0);
+
+    return pixac->n;
+}
+
+
+/*!
+ * \brief   pixacompGetPixcomp()
+ *
+ * \param[in]    pixac
+ * \param[in]    index      caller's view of index within pixac; includes offset
+ * \param[in]    copyflag   L_NOCOPY, L_COPY
+ * \return  pixc, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) The %index includes the offset, which must be subtracted
+ *          to get the actual index into the ptr array.
+ *      (2) If copyflag == L_NOCOPY, the pixc is owned by %pixac; do
+ *          not destroy.
+ * </pre>
+ */
+PIXC *
+pixacompGetPixcomp(PIXAC   *pixac,
+                   l_int32  index,
+                   l_int32  copyflag)
+{
+l_int32  aindex;
+
+    if (!pixac)
+        return (PIXC *)ERROR_PTR("pixac not defined", __func__, NULL);
+    if (copyflag != L_NOCOPY && copyflag != L_COPY)
+        return (PIXC *)ERROR_PTR("invalid copyflag", __func__, NULL);
+    aindex = index - pixac->offset;
+    if (aindex < 0 || aindex >= pixac->n)
+        return (PIXC *)ERROR_PTR("array index not valid", __func__, NULL);
+
+    if (copyflag == L_NOCOPY)
+        return pixac->pixc[aindex];
+    else  /* L_COPY */
+        return pixcompCopy(pixac->pixc[aindex]);
+}
+
+
+/*!
+ * \brief   pixacompGetPix()
+ *
+ * \param[in]    pixac
+ * \param[in]    index   caller's view of index within pixac; includes offset
+ * \return  pix, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) The %index includes the offset, which must be subtracted
+ *          to get the actual index into the ptr array.
+ * </pre>
+ */
+PIX *
+pixacompGetPix(PIXAC   *pixac,
+               l_int32  index)
+{
+l_int32  aindex;
+PIXC    *pixc;
+
+    if (!pixac)
+        return (PIX *)ERROR_PTR("pixac not defined", __func__, NULL);
+    aindex = index - pixac->offset;
+    if (aindex < 0 || aindex >= pixac->n)
+        return (PIX *)ERROR_PTR("array index not valid", __func__, NULL);
+
+    pixc = pixacompGetPixcomp(pixac, index, L_NOCOPY);
+    return pixCreateFromPixcomp(pixc);
+}
+
+
+/*!
+ * \brief   pixacompGetPixDimensions()
+ *
+ * \param[in]    pixac
+ * \param[in]    index       caller's view of index within pixac;
+ *                           includes offset
+ * \param[out]   pw, ph, pd  [optional] each can be null
+ * \return  0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) The %index includes the offset, which must be subtracted
+ *          to get the actual index into the ptr array.
+ * </pre>
+ */
+l_ok
+pixacompGetPixDimensions(PIXAC    *pixac,
+                         l_int32   index,
+                         l_int32  *pw,
+                         l_int32  *ph,
+                         l_int32  *pd)
+{
+l_int32  aindex;
+PIXC    *pixc;
+
+    if (!pixac)
+        return ERROR_INT("pixac not defined", __func__, 1);
+    aindex = index - pixac->offset;
+    if (aindex < 0 || aindex >= pixac->n)
+        return ERROR_INT("array index not valid", __func__, 1);
+
+    if ((pixc = pixac->pixc[aindex]) == NULL)
+        return ERROR_INT("pixc not found!", __func__, 1);
+    pixcompGetDimensions(pixc, pw, ph, pd);
+    return 0;
+}
+
+
+/*!
+ * \brief   pixacompGetBoxa()
+ *
+ * \param[in]    pixac
+ * \param[in]    accesstype   L_COPY, L_CLONE, L_COPY_CLONE
+ * \return  boxa, or NULL on error
+ */
+BOXA *
+pixacompGetBoxa(PIXAC   *pixac,
+                l_int32  accesstype)
+{
+    if (!pixac)
+        return (BOXA *)ERROR_PTR("pixac not defined", __func__, NULL);
+    if (!pixac->boxa)
+        return (BOXA *)ERROR_PTR("boxa not defined", __func__, NULL);
+    if (accesstype != L_COPY && accesstype != L_CLONE &&
+        accesstype != L_COPY_CLONE)
+        return (BOXA *)ERROR_PTR("invalid accesstype", __func__, NULL);
+
+    return boxaCopy(pixac->boxa, accesstype);
+}
+
+
+/*!
+ * \brief   pixacompGetBoxaCount()
+ *
+ * \param[in]    pixac
+ * \return  count, or 0 on error
+ */
+l_int32
+pixacompGetBoxaCount(PIXAC  *pixac)
+{
+    if (!pixac)
+        return ERROR_INT("pixac not defined", __func__, 0);
+
+    return boxaGetCount(pixac->boxa);
+}
+
+
+/*!
+ * \brief   pixacompGetBox()
+ *
+ * \param[in]    pixac
+ * \param[in]    index        caller's view of index within pixac;
+ *                            includes offset
+ * \param[in]    accesstype   L_COPY or L_CLONE
+ * \return  box if null, not automatically an error, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) The %index includes the offset, which must be subtracted
+ *          to get the actual index into the ptr array.
+ *      (2) There is always a boxa with a pixac, and it is initialized so
+ *          that each box ptr is NULL.
+ *      (3) In general, we expect that there is either a box associated
+ *          with each pixc, or no boxes at all in the boxa.
+ *      (4) Having no boxes is thus not an automatic error.  Whether it
+ *          is an actual error is determined by the calling program.
+ *          If the caller expects to get a box, it is an error; see, e.g.,
+ *          pixacGetBoxGeometry().
+ * </pre>
+ */
+BOX *
+pixacompGetBox(PIXAC    *pixac,
+               l_int32   index,
+               l_int32   accesstype)
+{
+l_int32  aindex;
+BOX     *box;
+
+    if (!pixac)
+        return (BOX *)ERROR_PTR("pixac not defined", __func__, NULL);
+    if (!pixac->boxa)
+        return (BOX *)ERROR_PTR("boxa not defined", __func__, NULL);
+    aindex = index - pixac->offset;
+    if (aindex < 0 || aindex >= pixac->boxa->n)
+        return (BOX *)ERROR_PTR("array index not valid", __func__, NULL);
+    if (accesstype != L_COPY && accesstype != L_CLONE)
+        return (BOX *)ERROR_PTR("invalid accesstype", __func__, NULL);
+
+    box = pixac->boxa->box[aindex];
+    if (box) {
+        if (accesstype == L_COPY)
+            return boxCopy(box);
+        else  /* accesstype == L_CLONE */
+            return boxClone(box);
+    } else {
+        return NULL;
+    }
+}
+
+
+/*!
+ * \brief   pixacompGetBoxGeometry()
+ *
+ * \param[in]    pixac
+ * \param[in]    index            caller's view of index within pixac;
+ *                                includes offset
+ * \param[out]   px, py, pw, ph   [optional] each can be null
+ * \return  0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) The %index includes the offset, which must be subtracted
+ *          to get the actual index into the ptr array.
+ * </pre>
+ */
+l_ok
+pixacompGetBoxGeometry(PIXAC    *pixac,
+                       l_int32   index,
+                       l_int32  *px,
+                       l_int32  *py,
+                       l_int32  *pw,
+                       l_int32  *ph)
+{
+l_int32  aindex;
+BOX     *box;
+
+    if (!pixac)
+        return ERROR_INT("pixac not defined", __func__, 1);
+    aindex = index - pixac->offset;
+    if (aindex < 0 || aindex >= pixac->n)
+        return ERROR_INT("array index not valid", __func__, 1);
+
+    if ((box = pixacompGetBox(pixac, aindex, L_CLONE)) == NULL)
+        return ERROR_INT("box not found!", __func__, 1);
+    boxGetGeometry(box, px, py, pw, ph);
+    boxDestroy(&box);
+    return 0;
+}
+
+
+/*!
+ * \brief   pixacompGetOffset()
+ *
+ * \param[in]    pixac
+ * \return  offset, or 0 on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) The offset is the difference between the caller's view of
+ *          the index into the array and the actual array index.
+ *          By default it is 0.
+ * </pre>
+ */
+l_int32
+pixacompGetOffset(PIXAC   *pixac)
+{
+    if (!pixac)
+        return ERROR_INT("pixac not defined", __func__, 0);
+    return pixac->offset;
+}
+
+
+/*!
+ * \brief   pixacompSetOffset()
+ *
+ * \param[in]    pixac
+ * \param[in]    offset    non-negative
+ * \return  0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) The offset is the difference between the caller's view of
+ *          the index into the array and the actual array index.
+ *          By default it is 0.
+ * </pre>
+ */
+l_ok
+pixacompSetOffset(PIXAC   *pixac,
+                  l_int32  offset)
+{
+    if (!pixac)
+        return ERROR_INT("pixac not defined", __func__, 1);
+    pixac->offset = L_MAX(0, offset);
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                      Pixacomp conversion to Pixa                    *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief   pixaCreateFromPixacomp()
+ *
+ * \param[in]    pixac
+ * \param[in]    accesstype   L_COPY, L_CLONE, L_COPY_CLONE; for boxa
+ * \return  pixa if OK, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) Because the pixa has no notion of offset, the offset must
+ *          be set to 0 before the conversion, so that pixacompGetPix()
+ *          fetches all the pixcomps.  It is reset at the end.
+ * </pre>
+ */
+PIXA *
+pixaCreateFromPixacomp(PIXAC   *pixac,
+                       l_int32  accesstype)
+{
+l_int32  i, n, offset;
+PIX     *pix;
+PIXA    *pixa;
+
+    if (!pixac)
+        return (PIXA *)ERROR_PTR("pixac not defined", __func__, NULL);
+    if (accesstype != L_COPY && accesstype != L_CLONE &&
+        accesstype != L_COPY_CLONE)
+        return (PIXA *)ERROR_PTR("invalid accesstype", __func__, NULL);
+
+    n = pixacompGetCount(pixac);
+    offset = pixacompGetOffset(pixac);
+    pixacompSetOffset(pixac, 0);
+    if ((pixa = pixaCreate(n)) == NULL)
+        return (PIXA *)ERROR_PTR("pixa not made", __func__, NULL);
+    for (i = 0; i < n; i++) {
+        if ((pix = pixacompGetPix(pixac, i)) == NULL) {
+            L_WARNING("pix %d not made\n", __func__, i);
+            continue;
+        }
+        pixaAddPix(pixa, pix, L_INSERT);
+    }
+    if (pixa->boxa) {
+        boxaDestroy(&pixa->boxa);
+        pixa->boxa = pixacompGetBoxa(pixac, accesstype);
+    }
+    pixacompSetOffset(pixac, offset);
+
+    return pixa;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                         Combining pixacomp
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief   pixacompJoin()
+ *
+ * \param[in]    pixacd    dest pixac; add to this one
+ * \param[in]    pixacs    [optional] source pixac; add from this one
+ * \param[in]    istart    starting index in pixacs
+ * \param[in]    iend      ending index in pixacs; use -1 to cat all
+ * \return  0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) This appends a clone of each indicated pixc in pixcas to pixcad
+ *      (2) istart < 0 is taken to mean 'read from the start' (istart = 0)
+ *      (3) iend < 0 means 'read to the end'
+ *      (4) If pixacs is NULL or contains no pixc, this is a no-op.
+ * </pre>
+ */
+l_ok
+pixacompJoin(PIXAC   *pixacd,
+             PIXAC   *pixacs,
+             l_int32  istart,
+             l_int32  iend)
+{
+l_int32  i, n, nb;
+BOXA    *boxas, *boxad;
+PIXC    *pixc;
+
+    if (!pixacd)
+        return ERROR_INT("pixacd not defined", __func__, 1);
+    if (!pixacs || ((n = pixacompGetCount(pixacs)) == 0))
+        return 0;
+
+    if (istart < 0)
+        istart = 0;
+    if (iend < 0 || iend >= n)
+        iend = n - 1;
+    if (istart > iend)
+        return ERROR_INT("istart > iend; nothing to add", __func__, 1);
+
+    for (i = istart; i <= iend; i++) {
+        pixc = pixacompGetPixcomp(pixacs, i, L_NOCOPY);
+        pixacompAddPixcomp(pixacd, pixc, L_COPY);
+    }
+
+    boxas = pixacompGetBoxa(pixacs, L_CLONE);
+    boxad = pixacompGetBoxa(pixacd, L_CLONE);
+    nb = pixacompGetBoxaCount(pixacs);
+    iend = L_MIN(iend, nb - 1);
+    boxaJoin(boxad, boxas, istart, iend);
+    boxaDestroy(&boxas);  /* just the clones */
+    boxaDestroy(&boxad);  /* ditto */
+    return 0;
+}
+
+
+/*!
+ * \brief   pixacompInterleave()
+ *
+ * \param[in]    pixac1    first src pixac
+ * \param[in]    pixac2    second src pixac
+ * \return  pixacd  interleaved from sources, or NULL on error.
+ *
+ * <pre>
+ * Notes:
+ *      (1) If the two pixac have different sizes, a warning is issued,
+ *          and the number of pairs returned is the minimum size.
+ * </pre>
+ */
+PIXAC *
+pixacompInterleave(PIXAC   *pixac1,
+                   PIXAC   *pixac2)
+{
+l_int32  i, n1, n2, n, nb1, nb2;
+BOX     *box;
+PIXC    *pixc1, *pixc2;
+PIXAC   *pixacd;
+
+    if (!pixac1)
+        return (PIXAC *)ERROR_PTR("pixac1 not defined", __func__, NULL);
+    if (!pixac2)
+        return (PIXAC *)ERROR_PTR("pixac2 not defined", __func__, NULL);
+    n1 = pixacompGetCount(pixac1);
+    n2 = pixacompGetCount(pixac2);
+    n = L_MIN(n1, n2);
+    if (n == 0)
+        return (PIXAC *)ERROR_PTR("at least one input pixac is empty",
+                                   __func__, NULL);
+    if (n1 != n2)
+        L_WARNING("counts differ: %d != %d\n", __func__, n1, n2);
+
+    pixacd = pixacompCreate(2 * n);
+    nb1 = pixacompGetBoxaCount(pixac1);
+    nb2 = pixacompGetBoxaCount(pixac2);
+    for (i = 0; i < n; i++) {
+        pixc1 = pixacompGetPixcomp(pixac1, i, L_COPY);
+        pixacompAddPixcomp(pixacd, pixc1, L_INSERT);
+        if (i < nb1) {
+            box = pixacompGetBox(pixac1, i, L_COPY);
+            pixacompAddBox(pixacd, box, L_INSERT);
+        }
+        pixc2 = pixacompGetPixcomp(pixac2, i, L_COPY);
+        pixacompAddPixcomp(pixacd, pixc2, L_INSERT);
+        if (i < nb2) {
+            box = pixacompGetBox(pixac2, i, L_COPY);
+            pixacompAddBox(pixacd, box, L_INSERT);
+        }
+    }
+
+    return pixacd;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                       Pixacomp serialized I/O                       *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief   pixacompRead()
+ *
+ * \param[in]    filename
+ * \return  pixac, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) Unlike the situation with serialized Pixa, where the image
+ *          data is stored in png format, the Pixacomp image data
+ *          can be stored in tiffg4, png and jpg formats.
+ * </pre>
+ */
+PIXAC *
+pixacompRead(const char  *filename)
+{
+FILE   *fp;
+PIXAC  *pixac;
+
+    if (!filename)
+        return (PIXAC *)ERROR_PTR("filename not defined", __func__, NULL);
+
+    if ((fp = fopenReadStream(filename)) == NULL)
+        return (PIXAC *)ERROR_PTR_1("stream not opened",
+                                    filename, __func__, NULL);
+    pixac = pixacompReadStream(fp);
+    fclose(fp);
+    if (!pixac)
+        return (PIXAC *)ERROR_PTR_1("pixac not read",
+                                    filename, __func__, NULL);
+    return pixac;
+}
+
+
+/*!
+ * \brief   pixacompReadStream()
+ *
+ * \param[in]    fp   file stream
+ * \return  pixac, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) It is OK for the pixacomp to be empty.
+ * </pre>
+ */
+PIXAC *
+pixacompReadStream(FILE  *fp)
+{
+char      buf[256];
+l_uint8  *data;
+l_int32   n, offset, i, w, h, d, ignore;
+l_int32   comptype, cmapflag, version, xres, yres;
+size_t    size;
+BOXA     *boxa;
+PIXC     *pixc;
+PIXAC    *pixac;
+
+    if (!fp)
+        return (PIXAC *)ERROR_PTR("stream not defined", __func__, NULL);
+
+    if (fscanf(fp, "\nPixacomp Version %d\n", &version) != 1)
+        return (PIXAC *)ERROR_PTR("not a pixacomp file", __func__, NULL);
+    if (version != PIXACOMP_VERSION_NUMBER)
+        return (PIXAC *)ERROR_PTR("invalid pixacomp version", __func__, NULL);
+    if (fscanf(fp, "Number of pixcomp = %d\n", &n) != 1)
+        return (PIXAC *)ERROR_PTR("not a pixacomp file", __func__, NULL);
+    if (fscanf(fp, "Offset of index into array = %d", &offset) != 1)
+        return (PIXAC *)ERROR_PTR("offset not read", __func__, NULL);
+    if (n < 0)
+        return (PIXAC *)ERROR_PTR("num pixcomp ptrs < 0", __func__, NULL);
+    if (n > (l_int32)MaxPtrArraySize)
+        return (PIXAC *)ERROR_PTR("too many pixcomp ptrs", __func__, NULL);
+    if (n == 0) L_INFO("the pixacomp is empty\n", __func__);
+
+    if ((pixac = pixacompCreate(n)) == NULL)
+        return (PIXAC *)ERROR_PTR("pixac not made", __func__, NULL);
+    if ((boxa = boxaReadStream(fp)) == NULL) {
+        pixacompDestroy(&pixac);
+        return (PIXAC *)ERROR_PTR("boxa not made", __func__, NULL);
+    }
+    boxaDestroy(&pixac->boxa);  /* empty */
+    pixac->boxa = boxa;
+    pixacompSetOffset(pixac, offset);
+
+    for (i = 0; i < n; i++) {
+        if (fscanf(fp, "\nPixcomp[%d]: w = %d, h = %d, d = %d\n",
+                   &ignore, &w, &h, &d) != 4) {
+            pixacompDestroy(&pixac);
+            return (PIXAC *)ERROR_PTR("dimension reading", __func__, NULL);
+        }
+        if (fscanf(fp, "  comptype = %d, size = %zu, cmapflag = %d\n",
+                   &comptype, &size, &cmapflag) != 3) {
+            pixacompDestroy(&pixac);
+            return (PIXAC *)ERROR_PTR("comptype/size reading", __func__, NULL);
+        }
+        if (size > MaxDataSize) {
+            pixacompDestroy(&pixac);
+            L_ERROR("data size = %zu is too big", __func__, size);
+            return NULL;
+        }
+
+           /* Use fgets() and sscanf(); not fscanf(), for the last
+             * bit of header data before the binary data.  The reason is
+             * that fscanf throws away white space, and if the binary data
+             * happens to begin with ascii character(s) that are white
+             * space, it will swallow them and all will be lost!  */
+        if (fgets(buf, sizeof(buf), fp) == NULL) {
+            pixacompDestroy(&pixac);
+            return (PIXAC *)ERROR_PTR("fgets read fail", __func__, NULL);
+        }
+        if (sscanf(buf, "  xres = %d, yres = %d\n", &xres, &yres) != 2) {
+            pixacompDestroy(&pixac);
+            return (PIXAC *)ERROR_PTR("read fail for res", __func__, NULL);
+        }
+        if ((data = (l_uint8 *)LEPT_CALLOC(1, size)) == NULL) {
+            pixacompDestroy(&pixac);
+            return (PIXAC *)ERROR_PTR("calloc fail for data", __func__, NULL);
+        }
+        if (fread(data, 1, size, fp) != size) {
+            pixacompDestroy(&pixac);
+            LEPT_FREE(data);
+            return (PIXAC *)ERROR_PTR("error reading data", __func__, NULL);
+        }
+        fgetc(fp);  /* swallow the ending nl */
+        pixc = (PIXC *)LEPT_CALLOC(1, sizeof(PIXC));
+        pixc->w = w;
+        pixc->h = h;
+        pixc->d = d;
+        pixc->xres = xres;
+        pixc->yres = yres;
+        pixc->comptype = comptype;
+        pixc->cmapflag = cmapflag;
+        pixc->data = data;
+        pixc->size = size;
+        pixacompAddPixcomp(pixac, pixc, L_INSERT);
+    }
+    return pixac;
+}
+
+
+/*!
+ * \brief   pixacompReadMem()
+ *
+ * \param[in]    data     in pixacomp format
+ * \param[in]    size     of data
+ * \return  pixac, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) Deseralizes a buffer of pixacomp data into a pixac in memory.
+ * </pre>
+ */
+PIXAC *
+pixacompReadMem(const l_uint8  *data,
+                size_t          size)
+{
+FILE   *fp;
+PIXAC  *pixac;
+
+    if (!data)
+        return (PIXAC *)ERROR_PTR("data not defined", __func__, NULL);
+    if ((fp = fopenReadFromMemory(data, size)) == NULL)
+        return (PIXAC *)ERROR_PTR("stream not opened", __func__, NULL);
+
+    pixac = pixacompReadStream(fp);
+    fclose(fp);
+    if (!pixac) L_ERROR("pixac not read\n", __func__);
+    return pixac;
+}
+
+
+/*!
+ * \brief   pixacompWrite()
+ *
+ * \param[in]    filename
+ * \param[in]    pixac
+ * \return  0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) Unlike the situation with serialized Pixa, where the image
+ *          data is stored in png format, the Pixacomp image data
+ *          can be stored in tiffg4, png and jpg formats.
+ * </pre>
+ */
+l_ok
+pixacompWrite(const char  *filename,
+              PIXAC       *pixac)
+{
+l_int32  ret;
+FILE    *fp;
+
+    if (!filename)
+        return ERROR_INT("filename not defined", __func__, 1);
+    if (!pixac)
+        return ERROR_INT("pixacomp not defined", __func__, 1);
+
+    if ((fp = fopenWriteStream(filename, "wb")) == NULL)
+        return ERROR_INT_1("stream not opened", filename, __func__, 1);
+    ret = pixacompWriteStream(fp, pixac);
+    fclose(fp);
+    if (ret)
+        return ERROR_INT_1("pixacomp not written to stream",
+                           filename, __func__, 1);
+    return 0;
+}
+
+
+/*!
+ * \brief   pixacompWriteStream()
+ *
+ * \param[in]    fp     file stream
+ * \param[in]    pixac
+ * \return  0 if OK, 1 on error
+ */
+l_ok
+pixacompWriteStream(FILE   *fp,
+                    PIXAC  *pixac)
+{
+l_int32  n, i;
+PIXC    *pixc;
+
+    if (!fp)
+        return ERROR_INT("stream not defined", __func__, 1);
+    if (!pixac)
+        return ERROR_INT("pixac not defined", __func__, 1);
+
+    n = pixacompGetCount(pixac);
+    fprintf(fp, "\nPixacomp Version %d\n", PIXACOMP_VERSION_NUMBER);
+    fprintf(fp, "Number of pixcomp = %d\n", n);
+    fprintf(fp, "Offset of index into array = %d", pixac->offset);
+    boxaWriteStream(fp, pixac->boxa);
+    for (i = 0; i < n; i++) {
+        if ((pixc = pixacompGetPixcomp(pixac, pixac->offset + i, L_NOCOPY))
+                == NULL)
+            return ERROR_INT("pixc not found", __func__, 1);
+        fprintf(fp, "\nPixcomp[%d]: w = %d, h = %d, d = %d\n",
+                i, pixc->w, pixc->h, pixc->d);
+        fprintf(fp, "  comptype = %d, size = %zu, cmapflag = %d\n",
+                pixc->comptype, pixc->size, pixc->cmapflag);
+        fprintf(fp, "  xres = %d, yres = %d\n", pixc->xres, pixc->yres);
+        fwrite(pixc->data, 1, pixc->size, fp);
+        fprintf(fp, "\n");
+    }
+    return 0;
+}
+
+
+/*!
+ * \brief   pixacompWriteMem()
+ *
+ * \param[out]   pdata   serialized data of pixac
+ * \param[out]   psize   size of serialized data
+ * \param[in]    pixac
+ * \return  0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) Serializes a pixac in memory and puts the result in a buffer.
+ * </pre>
+ */
+l_ok
+pixacompWriteMem(l_uint8  **pdata,
+                 size_t    *psize,
+                 PIXAC     *pixac)
+{
+l_int32  ret;
+FILE    *fp;
+
+    if (pdata) *pdata = NULL;
+    if (psize) *psize = 0;
+    if (!pdata)
+        return ERROR_INT("&data not defined", __func__, 1);
+    if (!psize)
+        return ERROR_INT("&size not defined", __func__, 1);
+    if (!pixac)
+        return ERROR_INT("&pixac not defined", __func__, 1);
+
+#if HAVE_FMEMOPEN
+    if ((fp = open_memstream((char **)pdata, psize)) == NULL)
+        return ERROR_INT("stream not opened", __func__, 1);
+    ret = pixacompWriteStream(fp, pixac);
+    fputc('\0', fp);
+    fclose(fp);
+    if (*psize > 0) *psize = *psize - 1;
+#else
+    L_INFO("no fmemopen API --> work-around: write to temp file\n", __func__);
+  #ifdef _WIN32
+    if ((fp = fopenWriteWinTempfile()) == NULL)
+        return ERROR_INT("tmpfile stream not opened", __func__, 1);
+  #else
+    if ((fp = tmpfile()) == NULL)
+        return ERROR_INT("tmpfile stream not opened", __func__, 1);
+  #endif  /* _WIN32 */
+    ret = pixacompWriteStream(fp, pixac);
+    rewind(fp);
+    *pdata = l_binaryReadStream(fp, psize);
+    fclose(fp);
+#endif  /* HAVE_FMEMOPEN */
+    return ret;
+}
+
+
+/*--------------------------------------------------------------------*
+ *                         Conversion to pdf                          *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief   pixacompConvertToPdf()
+ *
+ * \param[in]    pixac         containing images all at the same resolution
+ * \param[in]    res           override the resolution of each input image,
+ *                             in ppi; 0 to respect the resolution embedded
+ *                             in the input
+ * \param[in]    scalefactor   scaling factor applied to each image; > 0.0
+ * \param[in]    type          encoding type (L_JPEG_ENCODE, L_G4_ENCODE,
+ *                             L_FLATE_ENCODE, L_JP2K_ENCODE, or
+ *                             L_DEFAULT_ENCODE for default)
+ * \param[in]    quality       used for JPEG only; 0 for default (75)
+ * \param[in]    title         [optional] pdf title
+ * \param[in]    fileout       pdf file of all images
+ * \return  0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) This follows closely the function pixaConvertToPdf() in pdfio.c.
+ *      (2) The images are encoded with G4 if 1 bpp; JPEG if 8 bpp without
+ *          colormap and many colors, or 32 bpp; FLATE for anything else.
+ *      (3) The scalefactor must be > 0.0; otherwise it is set to 1.0.
+ *      (4) Specifying one of the three encoding types for %type forces
+ *          all images to be compressed with that type.  Use 0 to have
+ *          the type determined for each image based on depth and whether
+ *          or not it has a colormap.
+ *      (5) If all images are jpeg compressed, don't require scaling
+ *          and have the same resolution, it is much faster to skip
+ *          transcoding with pixacompFastConvertToPdfData(), and then
+ *          write the data out to file.
+ * </pre>
+ */
+l_ok
+pixacompConvertToPdf(PIXAC       *pixac,
+                     l_int32      res,
+                     l_float32    scalefactor,
+                     l_int32      type,
+                     l_int32      quality,
+                     const char  *title,
+                     const char  *fileout)
+{
+l_uint8  *data;
+l_int32   ret;
+size_t    nbytes;
+
+    if (!pixac)
+        return ERROR_INT("pixac not defined", __func__, 1);
+
+    ret = pixacompConvertToPdfData(pixac, res, scalefactor, type, quality,
+                                   title, &data, &nbytes);
+    if (ret) {
+        LEPT_FREE(data);
+        return ERROR_INT("conversion to pdf failed", __func__, 1);
+    }
+
+    ret = l_binaryWrite(fileout, "w", data, nbytes);
+    LEPT_FREE(data);
+    if (ret)
+        L_ERROR("pdf data not written to file\n", __func__);
+    return ret;
+}
+
+
+/*!
+ * \brief   pixacompConvertToPdfData()
+ *
+ * \param[in]    pixac         containing images all at the same resolution
+ * \param[in]    res           input resolution of all images
+ * \param[in]    scalefactor   scaling factor applied to each image; > 0.0
+ * \param[in]    type          encoding type (L_JPEG_ENCODE, L_G4_ENCODE,
+ *                             L_FLATE_ENCODE, L_JP2K_ENCODE, or
+ *                             L_DEFAULT_ENCODE for default)
+ * \param[in]    quality       used for JPEG only; 0 for default (75)
+ * \param[in]    title         [optional] pdf title
+ * \param[out]   pdata         output pdf data (of all images
+ * \param[out]   pnbytes       size of output pdf data
+ * \return  0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) See pixacompConvertToPdf().
+ * </pre>
+ */
+l_ok
+pixacompConvertToPdfData(PIXAC       *pixac,
+                         l_int32      res,
+                         l_float32    scalefactor,
+                         l_int32      type,
+                         l_int32      quality,
+                         const char  *title,
+                         l_uint8    **pdata,
+                         size_t      *pnbytes)
+{
+l_uint8  *imdata;
+l_int32   i, n, ret, scaledres, pagetype;
+size_t    imbytes;
+L_BYTEA  *ba;
+PIX      *pixs, *pix;
+L_PTRA   *pa_data;
+
+    if (!pdata)
+        return ERROR_INT("&data not defined", __func__, 1);
+    *pdata = NULL;
+    if (!pnbytes)
+        return ERROR_INT("&nbytes not defined", __func__, 1);
+    *pnbytes = 0;
+    if (!pixac)
+        return ERROR_INT("pixac not defined", __func__, 1);
+    if (scalefactor <= 0.0) scalefactor = 1.0;
+    if (type != L_DEFAULT_ENCODE && type != L_JPEG_ENCODE &&
+        type != L_G4_ENCODE && type != L_FLATE_ENCODE &&
+        type != L_JP2K_ENCODE) {
+        L_WARNING("invalid compression type; using per-page default\n",
+                  __func__);
+        type = L_DEFAULT_ENCODE;
+    }
+
+        /* Generate all the encoded pdf strings */
+    n = pixacompGetCount(pixac);
+    pa_data = ptraCreate(n);
+    for (i = 0; i < n; i++) {
+        if ((pixs =
+             pixacompGetPix(pixac, pixacompGetOffset(pixac) + i)) == NULL) {
+            L_ERROR("pix[%d] not retrieved\n", __func__, i);
+            continue;
+        }
+        if (pixGetWidth(pixs) == 1) {  /* used sometimes as placeholders */
+            L_INFO("placeholder image[%d] has w = 1\n", __func__, i);
+            pixDestroy(&pixs);
+            continue;
+        }
+        if (scalefactor != 1.0)
+            pix = pixScale(pixs, scalefactor, scalefactor);
+        else
+            pix = pixClone(pixs);
+        pixDestroy(&pixs);
+        scaledres = (l_int32)(res * scalefactor);
+
+            /* Select the encoding type */
+        if (type != L_DEFAULT_ENCODE) {
+            pagetype = type;
+        } else if (selectDefaultPdfEncoding(pix, &pagetype) != 0) {
+            L_ERROR("encoding type selection failed for pix[%d]\n",
+                    __func__, i);
+            pixDestroy(&pix);
+            continue;
+        }
+
+        ret = pixConvertToPdfData(pix, pagetype, quality, &imdata, &imbytes,
+                                  0, 0, scaledres, title, NULL, 0);
+        pixDestroy(&pix);
+        if (ret) {
+            L_ERROR("pdf encoding failed for pix[%d]\n", __func__, i);
+            continue;
+        }
+        ba = l_byteaInitFromMem(imdata, imbytes);
+        LEPT_FREE(imdata);
+        ptraAdd(pa_data, ba);
+    }
+    ptraGetActualCount(pa_data, &n);
+    if (n == 0) {
+        L_ERROR("no pdf files made\n", __func__);
+        ptraDestroy(&pa_data, FALSE, FALSE);
+        return 1;
+    }
+
+        /* Concatenate them */
+    ret = ptraConcatenatePdfToData(pa_data, NULL, pdata, pnbytes);
+
+    ptraGetActualCount(pa_data, &n);  /* recalculate in case it changes */
+    for (i = 0; i < n; i++) {
+        ba = (L_BYTEA *)ptraRemove(pa_data, i, L_NO_COMPACTION);
+        l_byteaDestroy(&ba);
+    }
+    ptraDestroy(&pa_data, FALSE, FALSE);
+    return ret;
+}
+
+
+/*!
+ * \brief   pixacompFastConvertToPdfData()
+ *
+ * \param[in]    pixac     containing images all at the same resolution
+ * \param[in]    title     [optional] pdf title
+ * \param[out]   pdata     output pdf data (of all images
+ * \param[out]   pnbytes   size of output pdf data
+ * \return  0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) This generates the pdf without transcoding if all the
+ *          images in %pixac are compressed with jpeg.
+ *          Images not jpeg compressed are skipped.
+ *      (2) It assumes all images have the same resolution, and that
+ *          the resolution embedded in each jpeg file is correct.
+ * </pre>
+ */
+l_ok
+pixacompFastConvertToPdfData(PIXAC       *pixac,
+                             const char  *title,
+                             l_uint8    **pdata,
+                             size_t      *pnbytes)
+{
+l_uint8  *imdata;
+l_int32   i, n, ret, comptype;
+size_t    imbytes;
+L_BYTEA  *ba;
+PIXC     *pixc;
+L_PTRA   *pa_data;
+
+    if (!pdata)
+        return ERROR_INT("&data not defined", __func__, 1);
+    *pdata = NULL;
+    if (!pnbytes)
+        return ERROR_INT("&nbytes not defined", __func__, 1);
+    *pnbytes = 0;
+    if (!pixac)
+        return ERROR_INT("pixac not defined", __func__, 1);
+
+        /* Generate all the encoded pdf strings */
+    n = pixacompGetCount(pixac);
+    pa_data = ptraCreate(n);
+    for (i = 0; i < n; i++) {
+        if ((pixc = pixacompGetPixcomp(pixac, i, L_NOCOPY)) == NULL) {
+            L_ERROR("pixc[%d] not retrieved\n", __func__, i);
+            continue;
+        }
+        pixcompGetParameters(pixc, NULL, NULL, &comptype, NULL);
+        if (comptype != IFF_JFIF_JPEG) {
+            L_ERROR("pixc[%d] not jpeg compressed\n", __func__, i);
+            continue;
+        }
+        ret = pixcompFastConvertToPdfData(pixc, title, &imdata, &imbytes);
+        if (ret) {
+            L_ERROR("pdf encoding failed for pixc[%d]\n", __func__, i);
+            continue;
+        }
+        ba = l_byteaInitFromMem(imdata, imbytes);
+        LEPT_FREE(imdata);
+        ptraAdd(pa_data, ba);
+    }
+    ptraGetActualCount(pa_data, &n);
+    if (n == 0) {
+        L_ERROR("no pdf files made\n", __func__);
+        ptraDestroy(&pa_data, FALSE, FALSE);
+        return 1;
+    }
+
+        /* Concatenate them */
+    ret = ptraConcatenatePdfToData(pa_data, NULL, pdata, pnbytes);
+
+        /* Clean up */
+    ptraGetActualCount(pa_data, &n);  /* recalculate in case it changes */
+    for (i = 0; i < n; i++) {
+        ba = (L_BYTEA *)ptraRemove(pa_data, i, L_NO_COMPACTION);
+        l_byteaDestroy(&ba);
+    }
+    ptraDestroy(&pa_data, FALSE, FALSE);
+    return ret;
+}
+
+
+/*!
+ * \brief   pixcompFastConvertToPdfData()
+ *
+ * \param[in]    pixc      containing images all at the same resolution
+ * \param[in]    title     [optional] pdf title
+ * \param[out]   pdata     output pdf data (of all images
+ * \param[out]   pnbytes   size of output pdf data
+ * \return  0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) This generates the pdf without transcoding.
+ *      (2) It assumes all images are jpeg encoded, have the same
+ *          resolution, and that the resolution embedded in each
+ *          jpeg file is correct.  (It is transferred to the pdf
+ *          via the cid.)
+ * </pre>
+ */
+static l_int32
+pixcompFastConvertToPdfData(PIXC        *pixc,
+                            const char  *title,
+                            l_uint8    **pdata,
+                            size_t      *pnbytes)
+{
+l_uint8      *data;
+L_COMP_DATA  *cid;
+
+    if (!pdata)
+        return ERROR_INT("&data not defined", __func__, 1);
+    *pdata = NULL;
+    if (!pnbytes)
+        return ERROR_INT("&nbytes not defined", __func__, 1);
+    *pnbytes = 0;
+    if (!pixc)
+        return ERROR_INT("pixc not defined", __func__, 1);
+
+        /* Make a copy of the data */
+    data = l_binaryCopy(pixc->data, pixc->size);
+    cid = l_generateJpegDataMem(data, pixc->size, 0);
+
+        /* Note: cid is destroyed, along with data, by this function */
+    return cidConvertToPdfData(cid, title, pdata, pnbytes);
+}
+
+
+/*--------------------------------------------------------------------*
+ *                        Output for debugging                        *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief   pixacompWriteStreamInfo()
+ *
+ * \param[in]    fp      file stream
+ * \param[in]    pixac
+ * \param[in]    text    [optional] identifying string; can be null
+ * \return  0 if OK, 1 on error
+ */
+l_ok
+pixacompWriteStreamInfo(FILE        *fp,
+                        PIXAC       *pixac,
+                        const char  *text)
+{
+l_int32  i, n, nboxes;
+PIXC    *pixc;
+
+    if (!fp)
+        return ERROR_INT("fp not defined", __func__, 1);
+    if (!pixac)
+        return ERROR_INT("pixac not defined", __func__, 1);
+
+    if (text)
+        fprintf(fp, "Pixacomp Info for %s:\n", text);
+    else
+        fprintf(fp, "Pixacomp Info:\n");
+    n = pixacompGetCount(pixac);
+    nboxes = pixacompGetBoxaCount(pixac);
+    fprintf(fp, "Number of pixcomp: %d\n", n);
+    fprintf(fp, "Size of pixcomp array alloc: %d\n", pixac->nalloc);
+    fprintf(fp, "Offset of index into array: %d\n", pixac->offset);
+    if (nboxes  > 0)
+        fprintf(fp, "Boxa has %d boxes\n", nboxes);
+    else
+        fprintf(fp, "Boxa is empty\n");
+    for (i = 0; i < n; i++) {
+        pixc = pixacompGetPixcomp(pixac, pixac->offset + i, L_NOCOPY);
+        pixcompWriteStreamInfo(fp, pixc, NULL);
+    }
+    return 0;
+}
+
+
+/*!
+ * \brief   pixcompWriteStreamInfo()
+ *
+ * \param[in]    fp     file stream
+ * \param[in]    pixc
+ * \param[in]    text   [optional] identifying string; can be null
+ * \return  0 if OK, 1 on error
+ */
+l_ok
+pixcompWriteStreamInfo(FILE        *fp,
+                       PIXC        *pixc,
+                       const char  *text)
+{
+    if (!fp)
+        return ERROR_INT("fp not defined", __func__, 1);
+    if (!pixc)
+        return ERROR_INT("pixc not defined", __func__, 1);
+
+    if (text)
+        fprintf(fp, "  Pixcomp Info for %s:", text);
+    else
+        fprintf(fp, "  Pixcomp Info:");
+    fprintf(fp, " width = %d, height = %d, depth = %d\n",
+            pixc->w, pixc->h, pixc->d);
+    fprintf(fp, "    xres = %d, yres = %d, size in bytes = %zu\n",
+            pixc->xres, pixc->yres, pixc->size);
+    if (pixc->cmapflag)
+        fprintf(fp, "    has colormap\n");
+    else
+        fprintf(fp, "    no colormap\n");
+    if (pixc->comptype < NumImageFileFormatExtensions) {
+        fprintf(fp, "    comptype = %s (%d)\n",
+                ImageFileFormatExtensions[pixc->comptype], pixc->comptype);
+    } else {
+        fprintf(fp, "    Error!! Invalid comptype index: %d\n", pixc->comptype);
+    }
+    return 0;
+}
+
+
+/*!
+ * \brief   pixacompDisplayTiledAndScaled()
+ *
+ * \param[in]    pixac
+ * \param[in]    outdepth     output depth: 1, 8 or 32 bpp
+ * \param[in]    tilewidth    each pix is scaled to this width
+ * \param[in]    ncols        number of tiles in each row
+ * \param[in]    background   0 for white, 1 for black; this is the color
+ *                            of the spacing between the images
+ * \param[in]    spacing      between images, and on outside
+ * \param[in]    border       width of additional black border on each image;
+ *                            use 0 for no border
+ * \return  pix of tiled images, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) This is the same function as pixaDisplayTiledAndScaled(),
+ *          except it works on a Pixacomp instead of a Pix.  It is particularly
+ *          useful for showing the images in a Pixacomp at reduced resolution.
+ *      (2) See pixaDisplayTiledAndScaled() for details.
+ * </pre>
+ */
+PIX *
+pixacompDisplayTiledAndScaled(PIXAC   *pixac,
+                              l_int32  outdepth,
+                              l_int32  tilewidth,
+                              l_int32  ncols,
+                              l_int32  background,
+                              l_int32  spacing,
+                              l_int32  border)
+{
+PIX   *pixd;
+PIXA  *pixa;
+
+    if (!pixac)
+        return (PIX *)ERROR_PTR("pixac not defined", __func__, NULL);
+
+    if ((pixa = pixaCreateFromPixacomp(pixac, L_COPY)) == NULL)
+        return (PIX *)ERROR_PTR("pixa not made", __func__, NULL);
+
+    pixd = pixaDisplayTiledAndScaled(pixa, outdepth, tilewidth, ncols,
+                                     background, spacing, border);
+    pixaDestroy(&pixa);
+    return pixd;
+}
+
+
+/*!
+ * \brief   pixacompWriteFiles()
+ *
+ * \param[in]    pixac
+ * \param[in]    subdir    subdirectory of /tmp
+ * \return  0 if OK, 1 on error
+ */
+l_ok
+pixacompWriteFiles(PIXAC       *pixac,
+                   const char  *subdir)
+{
+char     buf[128];
+l_int32  i, n;
+PIXC    *pixc;
+
+    if (!pixac)
+        return ERROR_INT("pixac not defined", __func__, 1);
+
+    if (lept_mkdir(subdir) > 0)
+        return ERROR_INT("invalid subdir", __func__, 1);
+
+    n = pixacompGetCount(pixac);
+    for (i = 0; i < n; i++) {
+        pixc = pixacompGetPixcomp(pixac, i, L_NOCOPY);
+        snprintf(buf, sizeof(buf), "/tmp/%s/%03d", subdir, i);
+        pixcompWriteFile(buf, pixc);
+    }
+    return 0;
+}
+
+extern const char *ImageFileFormatExtensions[];
+
+/*!
+ * \brief   pixcompWriteFile()
+ *
+ * \param[in]    rootname
+ * \param[in]    pixc
+ * \return  0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) The compressed data is written to file, and the filename is
+ *          generated by appending the format extension to %rootname.
+ * </pre>
+ */
+l_ok
+pixcompWriteFile(const char  *rootname,
+                 PIXC        *pixc)
+{
+char   buf[128];
+
+    if (!pixc)
+        return ERROR_INT("pixc not defined", __func__, 1);
+
+    snprintf(buf, sizeof(buf), "%s.%s", rootname,
+             ImageFileFormatExtensions[pixc->comptype]);
+    l_binaryWrite(buf, "w", pixc->data, pixc->size);
+    return 0;
+}