view mupdf-source/thirdparty/leptonica/src/numabasic.c @ 46:7ee69f120f19 default tip

>>>>> tag v1.26.5+1 for changeset b74429b0f5c4
author Franz Glasner <fzglas.hg@dom66.de>
date Sat, 11 Oct 2025 17:17:30 +0200
parents b50eed0cc0ef
children
line wrap: on
line source

/*====================================================================*
 -  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  numabasic.c
 * <pre>
 *
 *      Numa creation, destruction, copy, clone, etc.
 *          NUMA        *numaCreate()
 *          NUMA        *numaCreateFromIArray()
 *          NUMA        *numaCreateFromFArray()
 *          NUMA        *numaCreateFromString()
 *          void        *numaDestroy()
 *          NUMA        *numaCopy()
 *          NUMA        *numaClone()
 *          l_int32      numaEmpty()
 *
 *      Add/remove number (float or integer)
 *          l_int32      numaAddNumber()
 *          static l_int32  numaExtendArray()
 *          l_int32      numaInsertNumber()
 *          l_int32      numaRemoveNumber()
 *          l_int32      numaReplaceNumber()
 *
 *      Numa accessors
 *          l_int32      numaGetCount()
 *          l_int32      numaSetCount()
 *          l_int32      numaGetIValue()
 *          l_int32      numaGetFValue()
 *          l_int32      numaSetValue()
 *          l_int32      numaShiftValue()
 *          l_int32     *numaGetIArray()
 *          l_float32   *numaGetFArray()
 *          l_int32      numaGetParameters()
 *          l_int32      numaSetParameters()
 *          l_int32      numaCopyParameters()
 *
 *      Convert to string array
 *          SARRAY      *numaConvertToSarray()
 *
 *      Serialize numa for I/O
 *          NUMA        *numaRead()
 *          NUMA        *numaReadStream()
 *          NUMA        *numaReadMem()
 *          l_int32      numaWriteDebug()
 *          l_int32      numaWrite()
 *          l_int32      numaWriteStream()
 *          l_int32      numaWriteStderr()
 *          l_int32      numaWriteMem()
 *
 *      Numaa creation, destruction, truncation
 *          NUMAA       *numaaCreate()
 *          NUMAA       *numaaCreateFull()
 *          NUMAA       *numaaTruncate()
 *          void        *numaaDestroy()
 *
 *      Add Numa to Numaa
 *          l_int32      numaaAddNuma()
 *          static l_int32   numaaExtendArray()
 *
 *      Numaa accessors
 *          l_int32      numaaGetCount()
 *          l_int32      numaaGetNumaCount()
 *          l_int32      numaaGetNumberCount()
 *          NUMA       **numaaGetPtrArray()
 *          NUMA        *numaaGetNuma()
 *          NUMA        *numaaReplaceNuma()
 *          l_int32      numaaGetValue()
 *          l_int32      numaaAddNumber()
 *
 *      Serialize numaa for I/O
 *          NUMAA       *numaaRead()
 *          NUMAA       *numaaReadStream()
 *          NUMAA       *numaaReadMem()
 *          l_int32      numaaWrite()
 *          l_int32      numaaWriteStream()
 *          l_int32      numaaWriteMem()
 *
 *    (1) The Numa is a struct holding an array of floats.  It can also
 *        be used to store l_int32 values, with some loss of precision
 *        for floats larger than about 10 million.  Use the L_Dna instead
 *        if integers larger than a few million need to be stored.
 *
 *    (2) Always use the accessors in this file, never the fields directly.
 *
 *    (3) Storing and retrieving numbers:
 *
 *       * to append a new number to the array, use numaAddNumber().  If
 *         the number is an int, it will will automatically be converted
 *         to l_float32 and stored.
 *
 *       * to reset a value stored in the array, use numaSetValue().
 *
 *       * to increment or decrement a value stored in the array,
 *         use numaShiftValue().
 *
 *       * to obtain a value from the array, use either numaGetIValue()
 *         or numaGetFValue(), depending on whether you are retrieving
 *         an integer or a float.  This avoids doing an explicit cast,
 *         such as
 *           (a) return a l_float32 and cast it to an l_int32
 *           (b) cast the return directly to (l_float32 *) to
 *               satisfy the function prototype, as in
 *                 numaGetFValue(na, index, (l_float32 *)&ival);   [ugly!]
 *
 *    (4) int <--> float conversions:
 *
 *        Tradition dictates that type conversions go automatically from
 *        l_int32 --> l_float32, even though it is possible to lose
 *        precision for large integers, whereas you must cast (l_int32)
 *        to go from l_float32 --> l_int32 because you're truncating
 *        to the integer value.
 *
 *    (5) As with other arrays in leptonica, the numa has both an allocated
 *        size and a count of the stored numbers.  When you add a number, it
 *        goes on the end of the array, and causes a realloc if the array
 *        is already filled.  However, in situations where you want to
 *        add numbers randomly into an array, such as when you build a
 *        histogram, you must set the count of stored numbers in advance.
 *        This is done with numaSetCount().  If you set a count larger
 *        than the allocated array, it does a realloc to the size requested.
 *
 *    (6) In situations where the data in a numa correspond to a function
 *        y(x), the values can be either at equal spacings in x or at
 *        arbitrary spacings.  For the former, we can represent all x values
 *        by two parameters: startx (corresponding to y[0]) and delx
 *        for the change in x for adjacent values y[i] and y[i+1].
 *        startx and delx are initialized to 0.0 and 1.0, rsp.
 *        For arbitrary spacings, we use a second numa, and the two
 *        numas are typically denoted nay and nax.
 *
 *    (7) The numa is also the basic struct used for histograms.  Every numa
 *        has startx and delx fields, initialized to 0.0 and 1.0, that can
 *        be used to represent the "x" value for the location of the
 *        first bin and the bin width, respectively.  Accessors are the
 *        numa*Parameters() functions.  All functions that make numa
 *        histograms must set these fields properly, and many functions
 *        that use numa histograms rely on the correctness of these values.
 * </pre>
 */

#ifdef HAVE_CONFIG_H
#include <config_auto.h>
#endif  /* HAVE_CONFIG_H */

#include <string.h>
#include <math.h>
#include "allheaders.h"
#include "array_internal.h"

    /* Bounds on initial array size */
static const l_uint32  MaxFloatArraySize = 100000000;  /* for numa */
static const l_uint32  MaxPtrArraySize = 1000000;  /* for numaa */
static const l_int32 InitialArraySize = 50;      /*!< n'importe quoi */

    /* Static functions */
static l_int32 numaExtendArray(NUMA  *na);
static l_int32 numaaExtendArray(NUMAA  *naa);

/*--------------------------------------------------------------------------*
 *               Numa creation, destruction, copy, clone, etc.              *
 *--------------------------------------------------------------------------*/
/*!
 * \brief   numaCreate()
 *
 * \param[in]    n    size of number array to be alloc'd 0 for default
 * \return  na, or NULL on error
 */
NUMA *
numaCreate(l_int32  n)
{
NUMA  *na;

    if (n <= 0 || n > MaxFloatArraySize)
        n = InitialArraySize;

    na = (NUMA *)LEPT_CALLOC(1, sizeof(NUMA));
    if ((na->array = (l_float32 *)LEPT_CALLOC(n, sizeof(l_float32))) == NULL) {
        numaDestroy(&na);
        return (NUMA *)ERROR_PTR("number array not made", __func__, NULL);
    }

    na->nalloc = n;
    na->n = 0;
    na->refcount = 1;
    na->startx = 0.0;
    na->delx = 1.0;
    return na;
}


/*!
 * \brief   numaCreateFromIArray()
 *
 * \param[in]    iarray    integer array
 * \param[in]    size      of the array
 * \return  na, or NULL on error
 *
 * <pre>
 * Notes:
 *      (1) We can't insert this int array into the numa, because a numa
 *          takes a float array.  So this just copies the data from the
 *          input array into the numa.  The input array continues to be
 *          owned by the caller.
 * </pre>
 */
NUMA *
numaCreateFromIArray(l_int32  *iarray,
                     l_int32   size)
{
l_int32  i;
NUMA    *na;

    if (!iarray)
        return (NUMA *)ERROR_PTR("iarray not defined", __func__, NULL);
    if (size <= 0)
        return (NUMA *)ERROR_PTR("size must be > 0", __func__, NULL);

    na = numaCreate(size);
    for (i = 0; i < size; i++)
        numaAddNumber(na, iarray[i]);

    return na;
}


/*!
 * \brief   numaCreateFromFArray()
 *
 * \param[in]    farray     float array
 * \param[in]    size       of the array
 * \param[in]    copyflag   L_INSERT or L_COPY
 * \return  na, or NULL on error
 *
 * <pre>
 * Notes:
 *      (1) With L_INSERT, ownership of the input array is transferred
 *          to the returned numa, and all %size elements are considered
 *          to be valid.
 * </pre>
 */
NUMA *
numaCreateFromFArray(l_float32  *farray,
                     l_int32     size,
                     l_int32     copyflag)
{
l_int32  i;
NUMA    *na;

    if (!farray)
        return (NUMA *)ERROR_PTR("farray not defined", __func__, NULL);
    if (size <= 0)
        return (NUMA *)ERROR_PTR("size must be > 0", __func__, NULL);
    if (copyflag != L_INSERT && copyflag != L_COPY)
        return (NUMA *)ERROR_PTR("invalid copyflag", __func__, NULL);

    na = numaCreate(size);
    if (copyflag == L_INSERT) {
        if (na->array) LEPT_FREE(na->array);
        na->array = farray;
        na->n = size;
    } else {  /* just copy the contents */
        for (i = 0; i < size; i++)
            numaAddNumber(na, farray[i]);
    }

    return na;
}


/*!
 * \brief   numaCreateFromString()
 *
 * \param[in]    str    string of comma-separated numbers
 * \return  na, or NULL on error
 *
 * <pre>
 * Notes:
 *      (1) The numbers can be ints or floats; they will be interpreted
 *          and stored as floats.  To use them as integers (e.g., for
 *          indexing into arrays), use numaGetIValue(...).
 * </pre>
 */
NUMA *
numaCreateFromString(const char  *str)
{
char      *substr;
l_int32    i, n, nerrors;
l_float32  val;
NUMA      *na;
SARRAY    *sa;

    if (!str || (strlen(str) == 0))
        return (NUMA *)ERROR_PTR("str not defined or empty", __func__, NULL);

    sa = sarrayCreate(0);
    sarraySplitString(sa, str, ",");
    n = sarrayGetCount(sa);
    na = numaCreate(n);
    nerrors = 0;
    for (i = 0; i < n; i++) {
        substr = sarrayGetString(sa, i, L_NOCOPY);
        if (sscanf(substr, "%f", &val) != 1) {
            L_ERROR("substr %d not float\n", __func__, i);
            nerrors++;
        } else {
            numaAddNumber(na, val);
        }
    }

    sarrayDestroy(&sa);
    if (nerrors > 0) {
        numaDestroy(&na);
        return (NUMA *)ERROR_PTR("non-floats in string", __func__, NULL);
    }

    return na;
}


/*!
 * \brief   numaDestroy()
 *
 * \param[in,out] pna   numa to be destroyed and nulled if it exists
 * \return  void
 *
 * <pre>
 * Notes:
 *      (1) Decrements the ref count and, if 0, destroys the numa.
 *      (2) Always nulls the input ptr.
 * </pre>
 */
void
numaDestroy(NUMA  **pna)
{
NUMA  *na;

    if (pna == NULL) {
        L_WARNING("ptr address is NULL\n", __func__);
        return;
    }

    if ((na = *pna) == NULL)
        return;

        /* Decrement the ref count.  If it is 0, destroy the numa. */
    if (--na->refcount == 0) {
        if (na->array)
            LEPT_FREE(na->array);
        LEPT_FREE(na);
    }

    *pna = NULL;
}


/*!
 * \brief   numaCopy()
 *
 * \param[in]    na
 * \return  copy of numa, or NULL on error
 */
NUMA *
numaCopy(NUMA  *na)
{
l_int32  i;
NUMA    *cna;

    if (!na)
        return (NUMA *)ERROR_PTR("na not defined", __func__, NULL);

    if ((cna = numaCreate(na->nalloc)) == NULL)
        return (NUMA *)ERROR_PTR("cna not made", __func__, NULL);
    cna->startx = na->startx;
    cna->delx = na->delx;

    for (i = 0; i < na->n; i++)
        numaAddNumber(cna, na->array[i]);

    return cna;
}


/*!
 * \brief   numaClone()
 *
 * \param[in]    na
 * \return  ptr to same numa, or NULL on error
 */
NUMA *
numaClone(NUMA  *na)
{
    if (!na)
        return (NUMA *)ERROR_PTR("na not defined", __func__, NULL);

    ++na->refcount;
    return na;
}


/*!
 * \brief   numaEmpty()
 *
 * \param[in]    na
 * \return  0 if OK; 1 on error
 *
 * <pre>
 * Notes:
 *      (1) This does not change the allocation of the array.
 *          It just clears the number of stored numbers, so that
 *          the array appears to be empty.
 * </pre>
 */
l_ok
numaEmpty(NUMA  *na)
{
    if (!na)
        return ERROR_INT("na not defined", __func__, 1);

    na->n = 0;
    return 0;
}



/*--------------------------------------------------------------------------*
 *                 Number array: add number and extend array                *
 *--------------------------------------------------------------------------*/
/*!
 * \brief   numaAddNumber()
 *
 * \param[in]    na
 * \param[in]    val    float or int to be added; stored as a float
 * \return  0 if OK, 1 on error
 */
l_ok
numaAddNumber(NUMA      *na,
              l_float32  val)
{
l_int32  n;

    if (!na)
        return ERROR_INT("na not defined", __func__, 1);

    n = numaGetCount(na);
    if (n >= na->nalloc) {
        if (numaExtendArray(na))
            return ERROR_INT("extension failed", __func__, 1);
    }
    na->array[n] = val;
    na->n++;
    return 0;
}


/*!
 * \brief   numaExtendArray()
 *
 * \param[in]    na
 * \return  0 if OK, 1 on error
 *
 * <pre>
 * Notes:
 *      (1) The max number of floats is 100M.
 * </pre>
 */
static l_int32
numaExtendArray(NUMA  *na)
{
size_t  oldsize, newsize;

    if (!na)
        return ERROR_INT("na not defined", __func__, 1);
    if (na->nalloc > MaxFloatArraySize)  /* belt & suspenders */
        return ERROR_INT("na has too many ptrs", __func__, 1);
    oldsize = na->nalloc * sizeof(l_float32);
    newsize = 2 * oldsize;
    if (newsize > 4 * MaxFloatArraySize)
        return ERROR_INT("newsize > 400 MB; too large", __func__, 1);

    if ((na->array = (l_float32 *)reallocNew((void **)&na->array,
                                             oldsize, newsize)) == NULL)
        return ERROR_INT("new ptr array not returned", __func__, 1);

    na->nalloc *= 2;
    return 0;
}


/*!
 * \brief   numaInsertNumber()
 *
 * \param[in]    na
 * \param[in]    index    location in na to insert new value
 * \param[in]    val      float32 or integer to be added
 * \return  0 if OK, 1 on error
 *
 * <pre>
 * Notes:
 *      (1) This shifts na[i] --> na[i + 1] for all i >= index,
 *          and then inserts val as na[index].
 *      (2) It should not be used repeatedly on large arrays,
 *          because the function is O(n).
 *
 * </pre>
 */
l_ok
numaInsertNumber(NUMA      *na,
                 l_int32    index,
                 l_float32  val)
{
l_int32  i, n;

    if (!na)
        return ERROR_INT("na not defined", __func__, 1);
    n = numaGetCount(na);
    if (index < 0 || index > n) {
        L_ERROR("index %d not in [0,...,%d]\n", __func__, index, n);
        return 1;
    }

    if (n >= na->nalloc) {
        if (numaExtendArray(na))
            return ERROR_INT("extension failed", __func__, 1);
    }
    for (i = n; i > index; i--)
        na->array[i] = na->array[i - 1];
    na->array[index] = val;
    na->n++;
    return 0;
}


/*!
 * \brief   numaRemoveNumber()
 *
 * \param[in]    na
 * \param[in]    index    element to be removed
 * \return  0 if OK, 1 on error
 *
 * <pre>
 * Notes:
 *      (1) This shifts na[i] --> na[i - 1] for all i > index.
 *      (2) It should not be used repeatedly on large arrays,
 *          because the function is O(n).
 * </pre>
 */
l_ok
numaRemoveNumber(NUMA    *na,
                 l_int32  index)
{
l_int32  i, n;

    if (!na)
        return ERROR_INT("na not defined", __func__, 1);
    n = numaGetCount(na);
    if (index < 0 || index >= n) {
        L_ERROR("index %d not in [0,...,%d]\n", __func__, index, n - 1);
        return 1;
    }

    for (i = index + 1; i < n; i++)
        na->array[i - 1] = na->array[i];
    na->n--;
    return 0;
}


/*!
 * \brief   numaReplaceNumber()
 *
 * \param[in]    na
 * \param[in]    index    element to be replaced
 * \param[in]    val      new value to replace old one
 * \return  0 if OK, 1 on error
 */
l_ok
numaReplaceNumber(NUMA      *na,
                  l_int32    index,
                  l_float32  val)
{
l_int32  n;

    if (!na)
        return ERROR_INT("na not defined", __func__, 1);
    n = numaGetCount(na);
    if (index < 0 || index >= n) {
        L_ERROR("index %d not in [0,...,%d]\n", __func__, index, n - 1);
        return 1;
    }

    na->array[index] = val;
    return 0;
}


/*----------------------------------------------------------------------*
 *                            Numa accessors                            *
 *----------------------------------------------------------------------*/
/*!
 * \brief   numaGetCount()
 *
 * \param[in]    na
 * \return  count, or 0 if no numbers or on error
 */
l_int32
numaGetCount(NUMA  *na)
{
    if (!na)
        return ERROR_INT("na not defined", __func__, 0);
    return na->n;
}


/*!
 * \brief   numaSetCount()
 *
 * \param[in]    na
 * \param[in]    newcount
 * \return  0 if OK, 1 on error
 *
 * <pre>
 * Notes:
 *      (1) If newcount <= na->nalloc, this resets na->n.
 *          Using newcount = 0 is equivalent to numaEmpty().
 *      (2) If newcount > na->nalloc, this causes a realloc
 *          to a size na->nalloc = newcount.
 *      (3) All the previously unused values in na are set to 0.0.
 * </pre>
 */
l_ok
numaSetCount(NUMA    *na,
             l_int32  newcount)
{
    if (!na)
        return ERROR_INT("na not defined", __func__, 1);
    if (newcount > na->nalloc) {
        if ((na->array = (l_float32 *)reallocNew((void **)&na->array,
                         sizeof(l_float32) * na->nalloc,
                         sizeof(l_float32) * newcount)) == NULL)
            return ERROR_INT("new ptr array not returned", __func__, 1);
        na->nalloc = newcount;
    }
    na->n = newcount;
    return 0;
}


/*!
 * \brief   numaGetFValue()
 *
 * \param[in]    na
 * \param[in]    index    into numa
 * \param[out]   pval     float value; set to 0.0 on error
 * \return  0 if OK; 1 on error
 *
 * <pre>
 * Notes:
 *      (1) Caller may need to check the function return value to
 *          decide if a 0.0 in the returned ival is valid.
 * </pre>
 */
l_ok
numaGetFValue(NUMA       *na,
              l_int32     index,
              l_float32  *pval)
{
    if (!pval)
        return ERROR_INT("&val not defined", __func__, 1);
    *pval = 0.0;
    if (!na)
        return ERROR_INT("na not defined", __func__, 1);

    if (index < 0 || index >= na->n)
        return ERROR_INT("index not valid", __func__, 1);

    *pval = na->array[index];
    return 0;
}


/*!
 * \brief   numaGetIValue()
 *
 * \param[in]    na
 * \param[in]    index into numa
 * \param[out]   pival  integer value; set to 0 on error
 * \return  0 if OK; 1 on error
 *
 * <pre>
 * Notes:
 *      (1) Caller may need to check the function return value to
 *          decide if a 0 in the returned ival is valid.
 * </pre>
 */
l_ok
numaGetIValue(NUMA     *na,
              l_int32   index,
              l_int32  *pival)
{
l_float32  val;

    if (!pival)
        return ERROR_INT("&ival not defined", __func__, 1);
    *pival = 0;
    if (!na)
        return ERROR_INT("na not defined", __func__, 1);

    if (index < 0 || index >= na->n)
        return ERROR_INT("index not valid", __func__, 1);

    val = na->array[index];
    *pival = (l_int32)(val + L_SIGN(val) * 0.5);
    return 0;
}


/*!
 * \brief   numaSetValue()
 *
 * \param[in]    na
 * \param[in]    index   to element to be set
 * \param[in]    val     to set
 * \return  0 if OK; 1 on error
 */
l_ok
numaSetValue(NUMA      *na,
             l_int32    index,
             l_float32  val)
{
    if (!na)
        return ERROR_INT("na not defined", __func__, 1);
    if (index < 0 || index >= na->n)
        return ERROR_INT("index not valid", __func__, 1);

    na->array[index] = val;
    return 0;
}


/*!
 * \brief   numaShiftValue()
 *
 * \param[in]    na
 * \param[in]    index   to element to change relative to the current value
 * \param[in]    diff    increment if diff > 0 or decrement if diff < 0
 * \return  0 if OK; 1 on error
 */
l_ok
numaShiftValue(NUMA      *na,
               l_int32    index,
               l_float32  diff)
{
    if (!na)
        return ERROR_INT("na not defined", __func__, 1);
    if (index < 0 || index >= na->n)
        return ERROR_INT("index not valid", __func__, 1);

    na->array[index] += diff;
    return 0;
}


/*!
 * \brief   numaGetIArray()
 *
 * \param[in]    na
 * \return  a copy of the bare internal array, integerized
 *              by rounding, or NULL on error
 * <pre>
 * Notes:
 *      (1) A copy of the array is always made, because we need to
 *          generate an integer array from the bare float array.
 *          The caller is responsible for freeing the array.
 *      (2) The array size is determined by the number of stored numbers,
 *          not by the size of the allocated array in the Numa.
 *      (3) This function is provided to simplify calculations
 *          using the bare internal array, rather than continually
 *          calling accessors on the numa.  It is typically used
 *          on an array of size 256.
 * </pre>
 */
l_int32 *
numaGetIArray(NUMA  *na)
{
l_int32   i, n, ival;
l_int32  *array;

    if (!na)
        return (l_int32 *)ERROR_PTR("na not defined", __func__, NULL);

    if ((n = numaGetCount(na)) == 0)
        return (l_int32 *)ERROR_PTR("na is empty", __func__, NULL);
    if ((array = (l_int32 *)LEPT_CALLOC(n, sizeof(l_int32))) == NULL)
        return (l_int32 *)ERROR_PTR("array not made", __func__, NULL);
    for (i = 0; i < n; i++) {
        numaGetIValue(na, i, &ival);
        array[i] = ival;
    }

    return array;
}


/*!
 * \brief   numaGetFArray()
 *
 * \param[in]    na
 * \param[in]    copyflag    L_NOCOPY or L_COPY
 * \return  either the bare internal array or a copy of it,
 *              or NULL on error
 *
 * <pre>
 * Notes:
 *      (1) If copyflag == L_COPY, it makes a copy which the caller
 *          is responsible for freeing.  Otherwise, it operates
 *          directly on the bare array of the numa.
 *      (2) Very important: for L_NOCOPY, any writes to the array
 *          will be in the numa.  Do not write beyond the size of
 *          the count field, because it will not be accessible
 *          from the numa!  If necessary, be sure to set the count
 *          field to a larger number (such as the alloc size)
 *          BEFORE calling this function.  Creating with numaMakeConstant()
 *          is another way to insure full initialization.
 * </pre>
 */
l_float32 *
numaGetFArray(NUMA    *na,
              l_int32  copyflag)
{
l_int32     i, n;
l_float32  *array;

    if (!na)
        return (l_float32 *)ERROR_PTR("na not defined", __func__, NULL);

    if (copyflag == L_NOCOPY) {
        array = na->array;
    } else {  /* copyflag == L_COPY */
        if ((n = numaGetCount(na)) == 0)
            return (l_float32 *)ERROR_PTR("na is empty", __func__, NULL);
        if ((array = (l_float32 *)LEPT_CALLOC(n, sizeof(l_float32))) == NULL)
            return (l_float32 *)ERROR_PTR("array not made", __func__, NULL);
        for (i = 0; i < n; i++)
            array[i] = na->array[i];
    }

    return array;
}


/*!
 * \brief   numaGetParameters()
 *
 * \param[in]    na
 * \param[out]   pstartx    [optional] startx
 * \param[out]   pdelx      [optional] delx
 * \return  0 if OK, 1 on error
 */
l_ok
numaGetParameters(NUMA       *na,
                  l_float32  *pstartx,
                  l_float32  *pdelx)
{
    if (!pdelx && !pstartx)
        return ERROR_INT("no return val requested", __func__, 1);
    if (pstartx) *pstartx = 0.0;
    if (pdelx) *pdelx = 1.0;
    if (!na)
        return ERROR_INT("na not defined", __func__, 1);

    if (pstartx) *pstartx = na->startx;
    if (pdelx) *pdelx = na->delx;
    return 0;
}


/*!
 * \brief   numaSetParameters()
 *
 * \param[in]    na
 * \param[in]    startx  x value corresponding to na[0]
 * \param[in]    delx    difference in x values for the situation where the
 *                       elements of na correspond to the evaluation of a
 *                       function at equal intervals of size %delx
 * \return  0 if OK, 1 on error
 */
l_ok
numaSetParameters(NUMA      *na,
                  l_float32  startx,
                  l_float32  delx)
{
    if (!na)
        return ERROR_INT("na not defined", __func__, 1);

    na->startx = startx;
    na->delx = delx;
    return 0;
}


/*!
 * \brief   numaCopyParameters()
 *
 * \param[in]    nad    destination Numa
 * \param[in]    nas    source Numa
 * \return  0 if OK, 1 on error
 */
l_ok
numaCopyParameters(NUMA  *nad,
                   NUMA  *nas)
{
l_float32  start, binsize;

    if (!nas || !nad)
        return ERROR_INT("nas and nad not both defined", __func__, 1);

    numaGetParameters(nas, &start, &binsize);
    numaSetParameters(nad, start, binsize);
    return 0;
}


/*----------------------------------------------------------------------*
 *                      Convert to string array                         *
 *----------------------------------------------------------------------*/
/*!
 * \brief   numaConvertToSarray()
 *
 * \param[in]    na
 * \param[in]    size1      size of conversion field
 * \param[in]    size2      for float conversion: size of field to the right
 *                          of the decimal point
 * \param[in]    addzeros   for integer conversion: to add lead zeros
 * \param[in]    type       L_INTEGER_VALUE, L_FLOAT_VALUE
 * \return  a sarray of the float values converted to strings
 *              representing either integer or float values; or NULL on error.
 *
 * <pre>
 * Notes:
 *      (1) For integer conversion, size2 is ignored.
 *          For float conversion, addzeroes is ignored.
 * </pre>
 */
SARRAY *
numaConvertToSarray(NUMA    *na,
                    l_int32  size1,
                    l_int32  size2,
                    l_int32  addzeros,
                    l_int32  type)
{
char       fmt[32], strbuf[64];
l_int32    i, n, ival;
l_float32  fval;
SARRAY    *sa;

    if (!na)
        return (SARRAY *)ERROR_PTR("na not defined", __func__, NULL);
    if (type != L_INTEGER_VALUE && type != L_FLOAT_VALUE)
        return (SARRAY *)ERROR_PTR("invalid type", __func__, NULL);

    if (type == L_INTEGER_VALUE) {
        if (addzeros)
            snprintf(fmt, sizeof(fmt), "%%0%dd", size1);
        else
            snprintf(fmt, sizeof(fmt), "%%%dd", size1);
    } else {  /* L_FLOAT_VALUE */
        snprintf(fmt, sizeof(fmt), "%%%d.%df", size1, size2);
    }

    n = numaGetCount(na);
    if ((sa = sarrayCreate(n)) == NULL)
        return (SARRAY *)ERROR_PTR("sa not made", __func__, NULL);

    for (i = 0; i < n; i++) {
        if (type == L_INTEGER_VALUE) {
            numaGetIValue(na, i, &ival);
            snprintf(strbuf, sizeof(strbuf), fmt, ival);
        } else {  /* L_FLOAT_VALUE */
            numaGetFValue(na, i, &fval);
            snprintf(strbuf, sizeof(strbuf), fmt, fval);
        }
        sarrayAddString(sa, strbuf, L_COPY);
    }

    return sa;
}


/*----------------------------------------------------------------------*
 *                       Serialize numa for I/O                         *
 *----------------------------------------------------------------------*/
/*!
 * \brief   numaRead()
 *
 * \param[in]    filename
 * \return  na, or NULL on error
 */
NUMA *
numaRead(const char  *filename)
{
FILE  *fp;
NUMA  *na;

    if (!filename)
        return (NUMA *)ERROR_PTR("filename not defined", __func__, NULL);

    if ((fp = fopenReadStream(filename)) == NULL)
        return (NUMA *)ERROR_PTR_1("stream not opened",
                                   filename, __func__, NULL);
    na = numaReadStream(fp);
    fclose(fp);
    if (!na)
        return (NUMA *)ERROR_PTR_1("na not read",
                                   filename, __func__, NULL);
    return na;
}


/*!
 * \brief   numaReadStream()
 *
 * \param[in]    fp    file stream
 * \return  numa, or NULL on error
 */
NUMA *
numaReadStream(FILE  *fp)
{
l_int32    i, n, index, ret, version;
l_float32  val, startx, delx;
NUMA      *na;

    if (!fp)
        return (NUMA *)ERROR_PTR("stream not defined", __func__, NULL);

    ret = fscanf(fp, "\nNuma Version %d\n", &version);
    if (ret != 1)
        return (NUMA *)ERROR_PTR("not a numa file", __func__, NULL);
    if (version != NUMA_VERSION_NUMBER)
        return (NUMA *)ERROR_PTR("invalid numa version", __func__, NULL);
    if (fscanf(fp, "Number of numbers = %d\n", &n) != 1)
        return (NUMA *)ERROR_PTR("invalid number of numbers", __func__, NULL);

    if (n > MaxFloatArraySize) {
        L_ERROR("n = %d > %d\n", __func__, n, MaxFloatArraySize);
        return NULL;
    }
    if ((na = numaCreate(n)) == NULL)
        return (NUMA *)ERROR_PTR("na not made", __func__, NULL);

    for (i = 0; i < n; i++) {
        if (fscanf(fp, "  [%d] = %f\n", &index, &val) != 2) {
            numaDestroy(&na);
            return (NUMA *)ERROR_PTR("bad input data", __func__, NULL);
        }
        numaAddNumber(na, val);
    }

        /* Optional data */
    if (fscanf(fp, "startx = %f, delx = %f\n", &startx, &delx) == 2)
        numaSetParameters(na, startx, delx);

    return na;
}


/*!
 * \brief   numaReadMem()
 *
 * \param[in]    data    numa serialization; in ascii
 * \param[in]    size    of data; can use strlen to get it
 * \return  na, or NULL on error
 */
NUMA *
numaReadMem(const l_uint8  *data,
            size_t          size)
{
FILE  *fp;
NUMA  *na;

    if (!data)
        return (NUMA *)ERROR_PTR("data not defined", __func__, NULL);
    if ((fp = fopenReadFromMemory(data, size)) == NULL)
        return (NUMA *)ERROR_PTR("stream not opened", __func__, NULL);

    na = numaReadStream(fp);
    fclose(fp);
    if (!na) L_ERROR("numa not read\n", __func__);
    return na;
}


/*!
 * \brief   numaWriteDebug()
 *
 * \param[in]    filename
 * \param[in]    na
 * \return  0 if OK; 1 on error
 *
 * <pre>
 * Notes:
 *      (1) Debug version, intended for use in the library when writing
 *          to files in a temp directory with names that are compiled in.
 *          This is used instead of numaWrite() for all such library calls.
 *      (2) The global variable LeptDebugOK defaults to 0, and can be set
 *          or cleared by the function setLeptDebugOK().
 * </pre>
 */
l_ok
numaWriteDebug(const char  *filename,
               NUMA        *na)
{
    if (LeptDebugOK) {
        return numaWrite(filename, na);
    } else {
        L_INFO("write to named temp file %s is disabled\n", __func__, filename);
        return 0;
    }
}


/*!
 * \brief   numaWrite()
 *
 * \param[in]    filename
 * \param[in]    na
 * \return  0 if OK, 1 on error
 */
l_ok
numaWrite(const char  *filename,
          NUMA        *na)
{
l_int32  ret;
FILE    *fp;

    if (!filename)
        return ERROR_INT("filename not defined", __func__, 1);
    if (!na)
        return ERROR_INT("na not defined", __func__, 1);

    if ((fp = fopenWriteStream(filename, "w")) == NULL)
        return ERROR_INT_1("stream not opened", filename, __func__, 1);
    ret = numaWriteStream(fp, na);
    fclose(fp);
    if (ret)
        return ERROR_INT_1("na not written to stream", filename, __func__, 1);
    return 0;
}


/*!
 * \brief   numaWriteStream()
 *
 * \param[in]    fp    file stream; use NULL to write to stderr
 * \param[in]    na
 * \return  0 if OK, 1 on error
 */
l_ok
numaWriteStream(FILE  *fp,
                NUMA  *na)
{
l_int32    i, n;
l_float32  startx, delx;

    if (!na)
        return ERROR_INT("na not defined", __func__, 1);
    if (!fp)
        return numaWriteStderr(na);

    n = numaGetCount(na);
    fprintf(fp, "\nNuma Version %d\n", NUMA_VERSION_NUMBER);
    fprintf(fp, "Number of numbers = %d\n", n);
    for (i = 0; i < n; i++)
        fprintf(fp, "  [%d] = %f\n", i, na->array[i]);
    fprintf(fp, "\n");

        /* Optional data */
    numaGetParameters(na, &startx, &delx);
    if (startx != 0.0 || delx != 1.0)
        fprintf(fp, "startx = %f, delx = %f\n", startx, delx);

    return 0;
}


/*!
 * \brief   numaWriteStderr()
 *
 * \param[in]    na
 * \return  0 if OK, 1 on error
 */
l_ok
numaWriteStderr(NUMA  *na)
{
l_int32    i, n;
l_float32  startx, delx;

    if (!na)
        return ERROR_INT("na not defined", __func__, 1);

    n = numaGetCount(na);
    lept_stderr("\nNuma Version %d\n", NUMA_VERSION_NUMBER);
    lept_stderr("Number of numbers = %d\n", n);
    for (i = 0; i < n; i++)
        lept_stderr("  [%d] = %f\n", i, na->array[i]);
    lept_stderr("\n");

        /* Optional data */
    numaGetParameters(na, &startx, &delx);
    if (startx != 0.0 || delx != 1.0)
        lept_stderr("startx = %f, delx = %f\n", startx, delx);

    return 0;
}


/*!
 * \brief   numaWriteMem()
 *
 * \param[out]   pdata    data of serialized numa; ascii
 * \param[out]   psize    size of returned data
 * \param[in]    na
 * \return  0 if OK, 1 on error
 *
 * <pre>
 * Notes:
 *      (1) Serializes a numa in memory and puts the result in a buffer.
 * </pre>
 */
l_ok
numaWriteMem(l_uint8  **pdata,
             size_t    *psize,
             NUMA      *na)
{
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 (!na)
        return ERROR_INT("na not defined", __func__, 1);

#if HAVE_FMEMOPEN
    if ((fp = open_memstream((char **)pdata, psize)) == NULL)
        return ERROR_INT("stream not opened", __func__, 1);
    ret = numaWriteStream(fp, na);
    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 = numaWriteStream(fp, na);
    rewind(fp);
    *pdata = l_binaryReadStream(fp, psize);
    fclose(fp);
#endif  /* HAVE_FMEMOPEN */
    return ret;
}


/*--------------------------------------------------------------------------*
 *                     Numaa creation, destruction                          *
 *--------------------------------------------------------------------------*/
/*!
 * \brief   numaaCreate()
 *
 * \param[in]    n     size of numa ptr array to be alloc'd 0 for default
 * \return  naa, or NULL on error
 *
 */
NUMAA *
numaaCreate(l_int32  n)
{
NUMAA  *naa;

    if (n <= 0 || n > MaxPtrArraySize)
        n = InitialArraySize;

    naa = (NUMAA *)LEPT_CALLOC(1, sizeof(NUMAA));
    if ((naa->numa = (NUMA **)LEPT_CALLOC(n, sizeof(NUMA *))) == NULL) {
        numaaDestroy(&naa);
        return (NUMAA *)ERROR_PTR("numa ptr array not made", __func__, NULL);
    }

    naa->nalloc = n;
    naa->n = 0;
    return naa;
}


/*!
 * \brief   numaaCreateFull()
 *
 * \param[in]    nptr   size of numa ptr array to be alloc'd
 * \param[in]    n      size of individual numa arrays to be allocated
 *                      to 0 for default
 * \return  naa, or NULL on error
 *
 * <pre>
 * Notes:
 *      (1) This allocates numaa and fills the array with allocated numas.
 *          In use, after calling this function, use
 *              numaaAddNumber(naa, index, val);
 *          to add val to the index-th numa in naa.
 * </pre>
 */
NUMAA *
numaaCreateFull(l_int32  nptr,
                l_int32  n)
{
l_int32  i;
NUMAA   *naa;
NUMA    *na;

    naa = numaaCreate(nptr);
    for (i = 0; i < nptr; i++) {
        na = numaCreate(n);
        numaaAddNuma(naa, na, L_INSERT);
    }

    return naa;
}


/*!
 * \brief   numaaTruncate()
 *
 * \param[in]    naa
 * \return  0 if OK, 1 on error
 *
 * <pre>
 * Notes:
 *      (1) This identifies the largest index containing a numa that
 *          has any numbers within it, destroys all numa beyond that
 *          index, and resets the count.
 * </pre>
 */
l_ok
numaaTruncate(NUMAA  *naa)
{
l_int32  i, n, nn;
NUMA    *na;

    if (!naa)
        return ERROR_INT("naa not defined", __func__, 1);

    n = numaaGetCount(naa);
    for (i = n - 1; i >= 0; i--) {
        na = numaaGetNuma(naa, i, L_CLONE);
        if (!na)
            continue;
        nn = numaGetCount(na);
        numaDestroy(&na);
        if (nn == 0)
            numaDestroy(&naa->numa[i]);
        else
            break;
    }
    naa->n = i + 1;
    return 0;
}


/*!
 * \brief   numaaDestroy()
 *
 * \param[in,out]  pnaa   to be destroyed and nulled, if it exists
 * \return  void
 */
void
numaaDestroy(NUMAA  **pnaa)
{
l_int32  i;
NUMAA   *naa;

    if (pnaa == NULL) {
        L_WARNING("ptr address is NULL!\n", __func__);
        return;
    }

    if ((naa = *pnaa) == NULL)
        return;

    for (i = 0; i < naa->n; i++)
        numaDestroy(&naa->numa[i]);
    LEPT_FREE(naa->numa);
    LEPT_FREE(naa);
    *pnaa = NULL;
}



/*--------------------------------------------------------------------------*
 *                              Add Numa to Numaa                           *
 *--------------------------------------------------------------------------*/
/*!
 * \brief   numaaAddNuma()
 *
 * \param[in]    naa
 * \param[in]    na         to be added
 * \param[in]    copyflag   L_INSERT, L_COPY, L_CLONE
 * \return  0 if OK, 1 on error
 */
l_ok
numaaAddNuma(NUMAA   *naa,
             NUMA    *na,
             l_int32  copyflag)
{
l_int32  n;
NUMA    *nac;

    if (!naa)
        return ERROR_INT("naa not defined", __func__, 1);
    if (!na)
        return ERROR_INT("na not defined", __func__, 1);

    if (copyflag == L_INSERT) {
        nac = na;
    } else if (copyflag == L_COPY) {
        if ((nac = numaCopy(na)) == NULL)
            return ERROR_INT("nac not made", __func__, 1);
    } else if (copyflag == L_CLONE) {
        nac = numaClone(na);
    } else {
        return ERROR_INT("invalid copyflag", __func__, 1);
    }

    n = numaaGetCount(naa);
    if (n >= naa->nalloc) {
        if (numaaExtendArray(naa)) {
            if (copyflag != L_INSERT)
                numaDestroy(&nac);
            return ERROR_INT("extension failed", __func__, 1);
        }
    }
    naa->numa[n] = nac;
    naa->n++;
    return 0;
}


/*!
 * \brief   numaaExtendArray()
 *
 * \param[in]    naa
 * \return  0 if OK, 1 on error
 *
 * <pre>
 * Notes:
 *      (1) The max number of numa ptrs is 1M.
 * </pre>
 */
static l_int32
numaaExtendArray(NUMAA  *naa)
{
size_t  oldsize, newsize;

    if (!naa)
        return ERROR_INT("naa not defined", __func__, 1);
    if (naa->nalloc > MaxPtrArraySize)  /* belt & suspenders */
        return ERROR_INT("naa has too many ptrs", __func__, 1);
    oldsize = naa->nalloc * sizeof(NUMA *);
    newsize = 2 * oldsize;
    if (newsize > 8 * MaxPtrArraySize)
        return ERROR_INT("newsize > 8 MB; too large", __func__, 1);

    if ((naa->numa = (NUMA **)reallocNew((void **)&naa->numa,
                                         oldsize, newsize)) == NULL)
        return ERROR_INT("new ptr array not returned", __func__, 1);

    naa->nalloc *= 2;
    return 0;
}


/*----------------------------------------------------------------------*
 *                           Numaa accessors                            *
 *----------------------------------------------------------------------*/
/*!
 * \brief   numaaGetCount()
 *
 * \param[in]    naa
 * \return  count number of numa, or 0 if no numa or on error
 */
l_int32
numaaGetCount(NUMAA  *naa)
{
    if (!naa)
        return ERROR_INT("naa not defined", __func__, 0);
    return naa->n;
}


/*!
 * \brief   numaaGetNumaCount()
 *
 * \param[in]    naa
 * \param[in]    index     of numa in naa
 * \return  count of numbers in the referenced numa, or 0 on error.
 */
l_int32
numaaGetNumaCount(NUMAA   *naa,
                  l_int32  index)
{
    if (!naa)
        return ERROR_INT("naa not defined", __func__, 0);
    if (index < 0 || index >= naa->n)
        return ERROR_INT("invalid index into naa", __func__, 0);
    return numaGetCount(naa->numa[index]);
}


/*!
 * \brief   numaaGetNumberCount()
 *
 * \param[in]    naa
 * \return  count total number of numbers in the numaa,
 *          or 0 if no numbers or on error
 */
l_int32
numaaGetNumberCount(NUMAA  *naa)
{
NUMA    *na;
l_int32  n, sum, i;

    if (!naa)
        return ERROR_INT("naa not defined", __func__, 0);

    n = numaaGetCount(naa);
    for (sum = 0, i = 0; i < n; i++) {
        na = numaaGetNuma(naa, i, L_CLONE);
        sum += numaGetCount(na);
        numaDestroy(&na);
    }

    return sum;
}


/*!
 * \brief   numaaGetPtrArray()
 *
 * \param[in]    naa
 * \return  the internal array of ptrs to Numa, or NULL on error
 *
 * <pre>
 * Notes:
 *      (1) This function is convenient for doing direct manipulation on
 *          a fixed size array of Numas.  To do this, it sets the count
 *          to the full size of the allocated array of Numa ptrs.
 *          The originating Numaa owns this array: DO NOT free it!
 *      (2) Intended usage:
 *            Numaa *naa = numaaCreate(n);
 *            Numa **array = numaaGetPtrArray(naa);
 *             ...  [manipulate Numas directly on the array]
 *            numaaDestroy(&naa);
 *      (3) Cautions:
 *           ~ Do not free this array; it is owned by tne Numaa.
 *           ~ Do not call any functions on the Numaa, other than
 *             numaaDestroy() when you're finished with the array.
 *             Adding a Numa will force a resize, destroying the ptr array.
 *           ~ Do not address the array outside its allocated size.
 *             With the bare array, there are no protections.  If the
 *             allocated size is n, array[n] is an error.
 * </pre>
 */
NUMA **
numaaGetPtrArray(NUMAA  *naa)
{
    if (!naa)
        return (NUMA **)ERROR_PTR("naa not defined", __func__, NULL);

    naa->n = naa->nalloc;
    return naa->numa;
}


/*!
 * \brief   numaaGetNuma()
 *
 * \param[in]    naa
 * \param[in]    index        to the index-th numa
 * \param[in]    accessflag   L_COPY or L_CLONE
 * \return  numa, or NULL on error
 */
NUMA *
numaaGetNuma(NUMAA   *naa,
             l_int32  index,
             l_int32  accessflag)
{
    if (!naa)
        return (NUMA *)ERROR_PTR("naa not defined", __func__, NULL);
    if (index < 0 || index >= naa->n)
        return (NUMA *)ERROR_PTR("index not valid", __func__, NULL);

    if (accessflag == L_COPY)
        return numaCopy(naa->numa[index]);
    else if (accessflag == L_CLONE)
        return numaClone(naa->numa[index]);
    else
        return (NUMA *)ERROR_PTR("invalid accessflag", __func__, NULL);
}


/*!
 * \brief   numaaReplaceNuma()
 *
 * \param[in]    naa
 * \param[in]    index    to the index-th numa
 * \param[in]    na       insert and replace any existing one
 * \return  0 if OK, 1 on error
 *
 * <pre>
 * Notes:
 *      (1) Any existing numa is destroyed, and the input one
 *          is inserted in its place.
 *      (2) If the index is invalid, return 1 (error)
 * </pre>
 */
l_ok
numaaReplaceNuma(NUMAA   *naa,
                 l_int32  index,
                 NUMA    *na)
{
l_int32  n;

    if (!naa)
        return ERROR_INT("naa not defined", __func__, 1);
    if (!na)
        return ERROR_INT("na not defined", __func__, 1);
    n = numaaGetCount(naa);
    if (index < 0 || index >= n)
        return ERROR_INT("index not valid", __func__, 1);

    numaDestroy(&naa->numa[index]);
    naa->numa[index] = na;
    return 0;
}


/*!
 * \brief   numaaGetValue()
 *
 * \param[in]    naa
 * \param[in]    i       index of numa within numaa
 * \param[in]    j       index into numa
 * \param[out]   pfval   [optional] float value
 * \param[out]   pival   [optional] int value
 * \return  0 if OK, 1 on error
 */
l_ok
numaaGetValue(NUMAA      *naa,
              l_int32     i,
              l_int32     j,
              l_float32  *pfval,
              l_int32    *pival)
{
l_int32  n;
NUMA    *na;

    if (!pfval && !pival)
        return ERROR_INT("no return val requested", __func__, 1);
    if (pfval) *pfval = 0.0;
    if (pival) *pival = 0;
    if (!naa)
        return ERROR_INT("naa not defined", __func__, 1);
    n = numaaGetCount(naa);
    if (i < 0 || i >= n)
        return ERROR_INT("invalid index into naa", __func__, 1);
    na = naa->numa[i];
    if (j < 0 || j >= na->n)
        return ERROR_INT("invalid index into na", __func__, 1);
    if (pfval) *pfval = na->array[j];
    if (pival) *pival = (l_int32)(na->array[j]);
    return 0;
}


/*!
 * \brief   numaaAddNumber()
 *
 * \param[in]    naa
 * \param[in]    index    of numa within numaa
 * \param[in]    val      float or int to be added; stored as a float
 * \return  0 if OK, 1 on error
 *
 * <pre>
 * Notes:
 *      (1) Adds to an existing numa only.
 * </pre>
 */
l_ok
numaaAddNumber(NUMAA     *naa,
               l_int32    index,
               l_float32  val)
{
l_int32  n;
NUMA    *na;

    if (!naa)
        return ERROR_INT("naa not defined", __func__, 1);
    n = numaaGetCount(naa);
    if (index < 0 || index >= n)
        return ERROR_INT("invalid index in naa", __func__, 1);

    na = numaaGetNuma(naa, index, L_CLONE);
    numaAddNumber(na, val);
    numaDestroy(&na);
    return 0;
}


/*----------------------------------------------------------------------*
 *                      Serialize numaa for I/O                         *
 *----------------------------------------------------------------------*/
/*!
 * \brief   numaaRead()
 *
 * \param[in]    filename
 * \return  naa, or NULL on error
 */
NUMAA *
numaaRead(const char  *filename)
{
FILE   *fp;
NUMAA  *naa;

    if (!filename)
        return (NUMAA *)ERROR_PTR("filename not defined", __func__, NULL);

    if ((fp = fopenReadStream(filename)) == NULL)
        return (NUMAA *)ERROR_PTR_1("stream not opened",
                                    filename, __func__, NULL);
    naa = numaaReadStream(fp);
    fclose(fp);
    if (!naa)
        return (NUMAA *)ERROR_PTR_1("naa not read",
                                    filename, __func__, NULL);
    return naa;
}


/*!
 * \brief   numaaReadStream()
 *
 * \param[in]    fp     file stream
 * \return  naa, or NULL on error
 */
NUMAA *
numaaReadStream(FILE  *fp)
{
l_int32    i, n, index, ret, version;
NUMA      *na;
NUMAA     *naa;

    if (!fp)
        return (NUMAA *)ERROR_PTR("stream not defined", __func__, NULL);

    ret = fscanf(fp, "\nNumaa Version %d\n", &version);
    if (ret != 1)
        return (NUMAA *)ERROR_PTR("not a numa file", __func__, NULL);
    if (version != NUMA_VERSION_NUMBER)
        return (NUMAA *)ERROR_PTR("invalid numaa version", __func__, NULL);
    if (fscanf(fp, "Number of numa = %d\n\n", &n) != 1)
        return (NUMAA *)ERROR_PTR("invalid number of numa", __func__, NULL);

    if (n > MaxPtrArraySize) {
        L_ERROR("n = %d > %d\n", __func__, n, MaxPtrArraySize);
        return NULL;
    }
    if ((naa = numaaCreate(n)) == NULL)
        return (NUMAA *)ERROR_PTR("naa not made", __func__, NULL);

    for (i = 0; i < n; i++) {
        if (fscanf(fp, "Numa[%d]:", &index) != 1) {
            numaaDestroy(&naa);
            return (NUMAA *)ERROR_PTR("invalid numa header", __func__, NULL);
        }
        if ((na = numaReadStream(fp)) == NULL) {
            numaaDestroy(&naa);
            return (NUMAA *)ERROR_PTR("na not made", __func__, NULL);
        }
        numaaAddNuma(naa, na, L_INSERT);
    }

    return naa;
}


/*!
 * \brief   numaaReadMem()
 *
 * \param[in]    data     numaa serialization; in ascii
 * \param[in]    size     of data; can use strlen to get it
 * \return  naa, or NULL on error
 */
NUMAA *
numaaReadMem(const l_uint8  *data,
             size_t          size)
{
FILE   *fp;
NUMAA  *naa;

    if (!data)
        return (NUMAA *)ERROR_PTR("data not defined", __func__, NULL);
    if ((fp = fopenReadFromMemory(data, size)) == NULL)
        return (NUMAA *)ERROR_PTR("stream not opened", __func__, NULL);

    naa = numaaReadStream(fp);
    fclose(fp);
    if (!naa) L_ERROR("naa not read\n", __func__);
    return naa;
}


/*!
 * \brief   numaaWrite()
 *
 * \param[in]    filename
 * \param[in]    naa
 * \return  0 if OK, 1 on error
 */
l_ok
numaaWrite(const char  *filename,
           NUMAA       *naa)
{
l_int32  ret;
FILE    *fp;

    if (!filename)
        return ERROR_INT("filename not defined", __func__, 1);
    if (!naa)
        return ERROR_INT("naa not defined", __func__, 1);

    if ((fp = fopenWriteStream(filename, "w")) == NULL)
        return ERROR_INT_1("stream not opened", filename, __func__, 1);
    ret = numaaWriteStream(fp, naa);
    fclose(fp);
    if (ret)
        return ERROR_INT_1("naa not written to stream", filename, __func__, 1);
    return 0;
}


/*!
 * \brief   numaaWriteStream()
 *
 * \param[in]    fp     file stream
 * \param[in]    naa
 * \return  0 if OK, 1 on error
 */
l_ok
numaaWriteStream(FILE   *fp,
                 NUMAA  *naa)
{
l_int32  i, n;
NUMA    *na;

    if (!fp)
        return ERROR_INT("stream not defined", __func__, 1);
    if (!naa)
        return ERROR_INT("naa not defined", __func__, 1);

    n = numaaGetCount(naa);
    fprintf(fp, "\nNumaa Version %d\n", NUMA_VERSION_NUMBER);
    fprintf(fp, "Number of numa = %d\n\n", n);
    for (i = 0; i < n; i++) {
        if ((na = numaaGetNuma(naa, i, L_CLONE)) == NULL)
            return ERROR_INT("na not found", __func__, 1);
        fprintf(fp, "Numa[%d]:", i);
        numaWriteStream(fp, na);
        numaDestroy(&na);
    }

    return 0;
}


/*!
 * \brief   numaaWriteMem()
 *
 * \param[out]   pdata    data of serialized numaa; ascii
 * \param[out]   psize    size of returned data
 * \param[in]    naa
 * \return  0 if OK, 1 on error
 *
 * <pre>
 * Notes:
 *      (1) Serializes a numaa in memory and puts the result in a buffer.
 * </pre>
 */
l_ok
numaaWriteMem(l_uint8  **pdata,
              size_t    *psize,
              NUMAA     *naa)
{
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 (!naa)
        return ERROR_INT("naa not defined", __func__, 1);

#if HAVE_FMEMOPEN
    if ((fp = open_memstream((char **)pdata, psize)) == NULL)
        return ERROR_INT("stream not opened", __func__, 1);
    ret = numaaWriteStream(fp, naa);
    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 = numaaWriteStream(fp, naa);
    rewind(fp);
    *pdata = l_binaryReadStream(fp, psize);
    fclose(fp);
#endif  /* HAVE_FMEMOPEN */
    return ret;
}