diff mupdf-source/thirdparty/leptonica/src/numabasic.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/numabasic.c	Mon Sep 15 11:43:07 2025 +0200
@@ -0,0 +1,1966 @@
+/*====================================================================*
+ -  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;
+}
+