diff --git a/CMakeLists.txt b/CMakeLists.txt index 84f9dfc38..58c70f460 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,7 +42,7 @@ set_property(GLOBAL PROPERTY PREDEFINED_TARGETS_FOLDER "CMake Targets") project( leptonica LANGUAGES C - VERSION 1.84.0) + VERSION 1.84.1) set(CMAKE_C_STANDARD 17) set(CMAKE_C_STANDARD_REQUIRED ON) diff --git a/Doxyfile b/Doxyfile index 79506c1cf..f39d2b39b 100644 --- a/Doxyfile +++ b/Doxyfile @@ -38,7 +38,7 @@ PROJECT_NAME = Leptonica # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 1.84.0 +PROJECT_NUMBER = 1.84.1 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/configure.ac b/configure.ac index de83a0ee2..1d823a966 100644 --- a/configure.ac +++ b/configure.ac @@ -1,5 +1,5 @@ AC_PREREQ([2.69]) -AC_INIT([leptonica], [1.84.0]) +AC_INIT([leptonica], [1.84.1]) AC_CONFIG_AUX_DIR([config]) AC_CONFIG_HEADERS([config_auto.h:config/config.h.in]) AC_CONFIG_SRCDIR([src/adaptmap.c]) diff --git a/prog/boxa3_reg.c b/prog/boxa3_reg.c index e25ba9366..7edfbc025 100644 --- a/prog/boxa3_reg.c +++ b/prog/boxa3_reg.c @@ -75,7 +75,7 @@ TestBoxa(L_REGPARAMS *rp, l_uint8 *data; l_int32 w, h, medw, medh, isame; size_t size; -l_float32 scalefact, devw, ratiowh, fvarp, fvarm; +l_float32 scalefact, ratiowh, fvarp, fvarm; BOXA *boxa1, *boxa2, *boxa3; PIX *pix1; diff --git a/prog/deskew_it.c b/prog/deskew_it.c index c6c9c4a49..55c5a4980 100644 --- a/prog/deskew_it.c +++ b/prog/deskew_it.c @@ -64,7 +64,7 @@ int main(int argc, { char *filein, *fileout; l_int32 threshold, tryboth, format; -l_float32 deg2rad, sweeprange, angle, conf; +l_float32 sweeprange, angle, conf; PIX *pixs, *pix1, *pix2, *pixd; if (argc != 6) @@ -80,7 +80,6 @@ PIX *pixs, *pix1, *pix2, *pixd; setLeptDebugOK(1); pixd = NULL; - deg2rad = 3.1415926535 / 180.; if ((pixs = pixRead(filein)) == NULL) return ERROR_INT("pixs not made", __func__, 1); diff --git a/prog/string_reg.c b/prog/string_reg.c index acfbd03ec..04864fb53 100644 --- a/prog/string_reg.c +++ b/prog/string_reg.c @@ -33,6 +33,7 @@ * * sarray serialization * * file splitting * * sarray splitting + * * string length and string cancatenation */ #ifdef HAVE_CONFIG_H @@ -49,10 +50,10 @@ char substr2[4] = "00"; int main(int argc, char **argv) { -l_int32 i, loc, count; +l_int32 i, loc, count, n; size_t size1, size2; char *str0, *str1, *str2, *str3, *str4, *str5, *str6; -char fname[128]; +char fname[128], smallbuf[8], medbuf[32]; l_uint8 *data1, *data2; L_DNA *da; SARRAY *sa1, *sa2, *sa3, *sa4, *sa5; @@ -225,5 +226,56 @@ L_REGPARAMS *rp; lept_free(str3); lept_free(str4); + /* String length */ + lept_stderr("************************************************\n"); + lept_stderr("* This error message is intentional *\n"); + n = stringLength("", 0); + lept_stderr("************************************************\n"); + regTestCompareValues(rp, 0.0, (l_float32)n, 0.0); /* 27 */ + n = stringLength("", 4); + regTestCompareValues(rp, 0, (l_float32)n, 0.0); /* 28 */ + lept_stderr("************************************************\n"); + lept_stderr("* This error message is intentional *\n"); + n = stringLength("morethan4", 4); + lept_stderr("************************************************\n"); + regTestCompareValues(rp, 4, (l_float32)n, 0.0); /* 29 */ + + /* String concatenation */ + smallbuf[0] = '\0'; + n = stringCat(smallbuf, 8, "abc"); + regTestCompareValues(rp, 3.0, (l_float32)n, 0.0); /* 30 */ + n = stringCat(smallbuf, 8, "def"); + regTestCompareValues(rp, 3.0, (l_float32)n, 0.0); /* 31 */ + n = stringLength(smallbuf, 8); + regTestCompareValues(rp, 6.0, (l_float32)n, 0.0); /* 32 */ + lept_stderr("************************************************\n"); + lept_stderr("* This error message is intentional *\n"); + n = stringCat(smallbuf, 8, "gh"); + lept_stderr("************************************************\n"); + regTestCompareValues(rp, -1.0, (l_float32)n, 0.0); /* 33 */ + stringCopy(medbuf, smallbuf, 32); + n = stringCat(medbuf, 32, smallbuf); + regTestCompareValues(rp, 6.0, (l_float32)n, 0.0); /* 34 */ + n = stringLength(medbuf, 32); + regTestCompareValues(rp, 12.0, (l_float32)n, 0.0); /* 35 */ + n = stringCat(medbuf, 32, medbuf); + regTestCompareValues(rp, 12.0, (l_float32)n, 0.0); /* 36 */ + medbuf[23] = '\0'; /* shorten by 1 byte */ + n = stringLength(medbuf, 32); + regTestCompareValues(rp, 23.0, (l_float32)n, 0.0); /* 37 */ + str1 = stringConcatNew(medbuf, "jkl", NULL); + n = stringLength(str1, 32); + lept_free(str1); + regTestCompareValues(rp, 26.0, (l_float32)n, 0.0); /* 38 */ + stringCopy(smallbuf, medbuf, 6); + n = stringLength(smallbuf, 8); + regTestCompareValues(rp, 6.0, (l_float32)n, 0.0); /* 39 */ + stringCopy(smallbuf, medbuf, 8); + lept_stderr("************************************************\n"); + lept_stderr("* This error message is intentional *\n"); + n = stringLength(smallbuf, 8); + lept_stderr("************************************************\n"); + regTestCompareValues(rp, 8.0, (l_float32)n, 0.0); /* 40 */ + return regTestCleanup(rp); } diff --git a/prog/writetext_reg.c b/prog/writetext_reg.c index ca75f2d8b..8a0b78262 100644 --- a/prog/writetext_reg.c +++ b/prog/writetext_reg.c @@ -166,12 +166,11 @@ AddTextAndSave(PIXA *pixa, l_int32 location, l_uint32 val) { -l_int32 n, newrow, ovf; +l_int32 n, ovf; PIX *pix1; pix1 = pixAddSingleTextblock(pixs, bmf, textstr, val, location, &ovf); n = pixaGetCount(pixa); - newrow = (n % 4) ? 0 : 1; pixaAddPix(pixa, pix1, L_INSERT); if (ovf) lept_stderr("Overflow writing text in image %d\n", n + 1); } diff --git a/src/allheaders.h b/src/allheaders.h index 91f12703e..ac38bbb99 100644 --- a/src/allheaders.h +++ b/src/allheaders.h @@ -30,7 +30,7 @@ #define LIBLEPT_MAJOR_VERSION 1 #define LIBLEPT_MINOR_VERSION 84 -#define LIBLEPT_PATCH_VERSION 0 +#define LIBLEPT_PATCH_VERSION 1 #include "alltypes.h" diff --git a/src/allheaders_top.txt b/src/allheaders_top.txt index cd42691de..55081d21f 100644 --- a/src/allheaders_top.txt +++ b/src/allheaders_top.txt @@ -30,7 +30,7 @@ #define LIBLEPT_MAJOR_VERSION 1 #define LIBLEPT_MINOR_VERSION 84 -#define LIBLEPT_PATCH_VERSION 0 +#define LIBLEPT_PATCH_VERSION 1 #include "alltypes.h" diff --git a/src/blend.c b/src/blend.c index 637bdaa32..a8dce4164 100644 --- a/src/blend.c +++ b/src/blend.c @@ -2208,7 +2208,7 @@ l_uint32 *data, *line; range = (l_int32)(distfract * h); ymin = 0; slope = maxfade / (l_float32)range; - } else if (dir == L_FROM_BOT) { + } else { /* dir == L_FROM_BOT */ range = (l_int32)(distfract * h); ymin = h - range; slope = maxfade / (l_float32)range; diff --git a/src/dnafunc1.c b/src/dnafunc1.c index 31d023aa0..4edcca269 100644 --- a/src/dnafunc1.c +++ b/src/dnafunc1.c @@ -510,7 +510,6 @@ l_hmapCreateFromDna(L_DNA *da) l_int32 i, n; l_uint64 key; l_float64 dval; -L_HASHITEM *hitem; L_HASHMAP *hmap; if (!da) @@ -521,7 +520,7 @@ L_HASHMAP *hmap; for (i = 0; i < n; i++) { l_dnaGetDValue(da, i, &dval); l_hashFloat64ToUint64(dval, &key); - hitem = l_hmapLookup(hmap, key, i, L_HMAP_CREATE); + l_hmapLookup(hmap, key, i, L_HMAP_CREATE); } return hmap; } diff --git a/src/environ.h b/src/environ.h index fdf90d740..e1c1bc457 100644 --- a/src/environ.h +++ b/src/environ.h @@ -144,8 +144,8 @@ typedef uintptr_t l_uintptr_t; /*-----------------------------------------------------------------------* - * Leptonica supports OpenJPEG 2.0+. If you have a version of openjpeg * - * (HAVE_LIBJP2K == 1) that is >= 2.0, set the path to the openjpeg.h * + * Leptonica supports OpenJPEG 2.1+. If you have a version of openjpeg * + * (HAVE_LIBJP2K == 1) that is >= 2.1, set the path to the openjpeg.h * * header in angle brackets here. * *-----------------------------------------------------------------------*/ #define LIBJP2K_HEADER diff --git a/src/jp2kio.c b/src/jp2kio.c index ce4e2dea5..acbf19b57 100644 --- a/src/jp2kio.c +++ b/src/jp2kio.c @@ -66,25 +66,10 @@ * strings. * * N.B. - * * This is based on the most recent openjpeg release: 2.1. - * * The openjpeg interface was massively changed from 1.X. The debian - * distribution is way back at 1.3. We have inquired but are unable - * to determine if or when a debian distribution will be built for 2.1. - * * For version 2.1, the openjpeg.h file is installed in an - * openjpeg-2.1 subdirectory, which is hard to support. - * * In openjpeg-2.1, reading is slow compared to jpeg or webp, - * and writing is very slow compared to jpeg or webp. This is expected - * to improve significantly in future versions. - * * Reading and writing jp2k are supported here for 2.1. - * The high-level interface to openjpeg continues to change. - * From 2.0 to 2.1, the ability to interface to a C file stream - * was removed permanently. Leptonica supports both file stream - * and memory buffer interfaces for every image I/O library, and - * it requires the libraries to support at least one of these. - * However, openjpeg-2.1 provides neither, so we have brought - * several static functions over from openjpeg-2.0 in order to - * retain the file stream interface. See our static function - * opjCreateStream(). + * * Reading and writing jp2k are supported here for releases 2.1 and later. + * * The openjpeg.h file is installed in an openjpeg-2.X subdirectory. + * * In openjpeg-2.X, reading is slow compared to jpeg or webp, + * and writing is very slow compared to jpeg or webp. * * Specifying a quality factor for jpeg2000 requires caution. Unlike * jpeg and webp, which have a sensible scale that goes from 0 (very poor) * to 100 (nearly lossless), kakadu and openjpeg use idiosyncratic and @@ -96,6 +81,15 @@ * quality to jpeg's default standard of 75. For document images, * SNR = 25 is very poor, whereas SNR = 45 is nearly lossless. If you * use the latter, you will pay dearly in the size of the compressed file. + * * The openjpeg interface was massively changed from 1.X to 2.0. + * There were also changes from 2.0 to 2.1. From 2.0 to 2.1, the + * ability to interface to a C file stream was removed permanently. + * Leptonica supports both file stream and memory buffer interfaces + * for every image I/O library, and it requires the libraries to + * support at least one of these. However, because openjpeg-2.1+ provides + * neither, we have brought several static functions over from + * openjpeg-2.0 in order to retain the file stream interface. + * See, for example, our static function opjCreateStream(). * */ @@ -110,16 +104,11 @@ #if HAVE_LIBJP2K /* defined in environ.h */ /* --------------------------------------------*/ - /* Leptonica supports versions 2.0 and newer */ + /* Leptonica supports versions 2.1 and later */ #ifdef LIBJP2K_HEADER #include LIBJP2K_HEADER #else #include -#endif - - /* 2.0 didn't define OPJ_VERSION_MINOR. */ -#ifndef OPJ_VERSION_MINOR -#define OPJ_VERSION_MINOR 0 #endif /* Static generator of opj_stream from file stream. @@ -259,15 +248,13 @@ PIX *pix = NULL; return (PIX *)ERROR_PTR("fp not defined", __func__, NULL); opjVersion = opj_version(); - if (opjVersion[0] != '2') { - L_ERROR("version is %s; must be 2.0 or higher\n", __func__, opjVersion); + if (!opjVersion || opjVersion[0] == '\0') + return (PIX *)ERROR_PTR("opj version not defined", __func__, NULL); + if (opjVersion[0] - 0x30 < 2 || + (opjVersion[0] == '2' && opjVersion[2] - 0x30 == 0)) { + L_ERROR("version is %s; must be 2.1 or higher\n", __func__, opjVersion); return NULL; } - if ((opjVersion[2] - 0x30) != OPJ_VERSION_MINOR) { - L_ERROR("version %s: differs from minor = %d\n", - __func__, opjVersion, OPJ_VERSION_MINOR); - return NULL; - } /* Get the resolution, bits/sample and codec type */ rewind(fp); @@ -323,9 +310,8 @@ PIX *pix = NULL; return NULL; } - /* Open decompression 'stream'. In 2.0, we could call this: - * opj_stream_create_default_file_stream(fp, 1) - * but the file stream interface was removed in 2.1. */ + /* Open decompression 'stream'. This uses our version of the + * function that was removed in 2.1. */ if ((l_stream = opjCreateStream(fp, 1)) == NULL) { L_ERROR("failed to open the stream\n", __func__); opj_destroy_codec(l_codec); @@ -563,15 +549,13 @@ opj_image_t *image = NULL; return ERROR_INT("valid codec not identified\n", __func__, 1); opjVersion = opj_version(); - if (opjVersion[0] != '2') { - L_ERROR("version is %s; must be 2.0 or higher\n", __func__, opjVersion); + if (!opjVersion || opjVersion[0] == '\0') + return ERROR_INT("opj version not defined", __func__, 1); + if (opjVersion[0] - 0x30 < 2 || + (opjVersion[0] == '2' && opjVersion[2] - 0x30 == 0)) { + L_ERROR("version is %s; must be 2.1 or higher\n", __func__, opjVersion); return 1; } - if ((opjVersion[2] - 0x30) != OPJ_VERSION_MINOR) { - L_ERROR("version %s: differs from minor = %d\n", - __func__, opjVersion, OPJ_VERSION_MINOR); - return 1; - } /* Remove colormap if it exists; result is 8 or 32 bpp */ pixGetDimensions(pix, &w, &h, &d); @@ -645,9 +629,8 @@ opj_image_t *image = NULL; /* Set the resolution (TBD) */ - /* Open a compression stream for writing. In 2.0 we could use this: - * opj_stream_create_default_file_stream(fp, 0) - * but the file stream interface was removed in 2.1. */ + /* Open compression stream for writing. This uses our version + * of the function that was removed in 2.1. */ rewind(fp); if ((l_stream = opjCreateStream(fp, 0)) == NULL) { opj_destroy_codec(l_codec); @@ -785,9 +768,8 @@ opj_image_cmptparm_t cmptparm[4]; *
  * Notes:
  *      (1) This crashes when reading through the fmemopen cookie.
- *          Until we can fix this, we use the file-based work-around.
- *          And fixing this may take some time, because the basic
- *          stream interface is no longer supported in openjpeg.
+ *          Until this is fixed, which may take a long time, we use
+ *          the file-based work-around.
  *      (2) See pixReadJp2k() for usage.
  * 
*/ @@ -934,12 +916,8 @@ opj_stream_t *l_stream; if (!l_stream) return (opj_stream_t *)ERROR_PTR("stream not made", __func__, NULL); -#if OPJ_VERSION_MINOR == 0 - opj_stream_set_user_data(l_stream, fp); -#else opj_stream_set_user_data(l_stream, fp, (opj_stream_free_user_data_fn)NULL); -#endif opj_stream_set_user_data_length(l_stream, opj_get_user_data_length(fp)); opj_stream_set_read_function(l_stream, (opj_stream_read_fn)opj_read_from_file); diff --git a/src/makefile.static b/src/makefile.static index 558ea7225..bbc06740b 100644 --- a/src/makefile.static +++ b/src/makefile.static @@ -156,7 +156,7 @@ LIBRARIAN_SHARED = gcc -shared # .X, and , where Y is the minor revision number. MAJOR_REV = 1 MINOR_REV = 84 -PATCH_REV = 0 +PATCH_REV = 1 ######################################################### diff --git a/src/pageseg.c b/src/pageseg.c index b027e8861..76c9eddec 100644 --- a/src/pageseg.c +++ b/src/pageseg.c @@ -581,7 +581,7 @@ pixCropImage(PIX *pixs, BOX **pcropbox) { char cmd[64]; -l_int32 w, h, d, lrc, tbc, val; +l_int32 w, h, d, val; l_int32 left, right, top, bot, leftfinal, rightfinal, topfinal, botfinal; static l_int32 first_time = TRUE; l_float32 hscale; @@ -632,7 +632,7 @@ BOX *box1, *box2; pixClipToForeground(pix2, NULL, &box1); } else { val = edgeclean + 1; - snprintf(cmd, 32, "c%d.%d + o%d.%d", val, val, val, val); + snprintf(cmd, 64, "c%d.%d + o%d.%d", val, val, val, val); pix3 = pixMorphSequence(pix2, cmd, 0); pixClipToForeground(pix3, NULL, &box1); pixDestroy(&pix3); diff --git a/src/partify.c b/src/partify.c index 8466b7128..31cdfccc1 100644 --- a/src/partify.c +++ b/src/partify.c @@ -253,6 +253,9 @@ pixLocateStaveSets(PIX *pixs, BOXA *boxa1, *boxa2, *boxa3, *boxa4; PIX *pix1, *pix2; + if (!pixs) + return (BOXA *)ERROR_PTR("pixs not defined", __func__, NULL); + /* Find the stave sets at 4x reduction */ pix1 = pixMorphSequence(pixs, "r11", 0); boxa1 = pixConnCompBB(pix1, 8); @@ -298,7 +301,10 @@ boxaRemoveVGaps(BOXA *boxa) { l_int32 nbox, i, y1, h1, y2, h2, delta; - nbox = boxaGetCount(boxa); + if (!boxa) + return ERROR_INT("boxa not defined", __func__, 1); + if ((nbox = boxaGetCount(boxa)) == 0); + return ERROR_INT("boxa is empty", __func__, 1); for (i = 0; i < nbox - 1; i++) { boxaGetBoxGeometry(boxa, i, NULL, &y1, NULL, &h1); boxaGetBoxGeometry(boxa, i + 1, NULL, &y2, NULL, &h2); diff --git a/src/pix1.c b/src/pix1.c index 550545072..e21f45730 100644 --- a/src/pix1.c +++ b/src/pix1.c @@ -143,7 +143,7 @@ * ----------------------------------------- * * Memory management of the (image) data field in the pix is - * handled differently from that in the colormap or text fields. + * handled differently from that in the colormap and text fields. * For colormap and text, the functions pixSetColormap() and * pixSetText() remove the existing heap data and insert the * new data. For the image data, pixSetData() just reassigns the diff --git a/src/ptafunc2.c b/src/ptafunc2.c index 53de19e38..e5bed23e1 100644 --- a/src/ptafunc2.c +++ b/src/ptafunc2.c @@ -633,7 +633,6 @@ l_hmapCreateFromPta(PTA *pta) { l_int32 i, n, x, y; l_uint64 key; -L_HASHITEM *hitem; L_HASHMAP *hmap; if (!pta) @@ -645,7 +644,7 @@ L_HASHMAP *hmap; for (i = 0; i < n; i++) { ptaGetIPt(pta, i, &x, &y); l_hashPtToUint64(x, y, &key); - hitem = l_hmapLookup(hmap, key, i, L_HMAP_CREATE); + l_hmapLookup(hmap, key, i, L_HMAP_CREATE); } return hmap; } diff --git a/src/sarray2.c b/src/sarray2.c index 0653eb7f2..30161ba95 100644 --- a/src/sarray2.c +++ b/src/sarray2.c @@ -428,7 +428,6 @@ l_hmapCreateFromSarray(SARRAY *sa) l_int32 i, n; l_uint64 key; char *str; -L_HASHITEM *hitem; L_HASHMAP *hmap; if (!sa) @@ -440,7 +439,7 @@ L_HASHMAP *hmap; for (i = 0; i < n; i++) { str = sarrayGetString(sa, i, L_NOCOPY); l_hashStringToUint64Fast(str, &key); - hitem = l_hmapLookup(hmap, key, i, L_HMAP_CREATE); + l_hmapLookup(hmap, key, i, L_HMAP_CREATE); } return hmap; } diff --git a/src/utils2.c b/src/utils2.c index 397dd20a9..10383bb60 100644 --- a/src/utils2.c +++ b/src/utils2.c @@ -361,16 +361,17 @@ stringReplace(char **pdest, * \brief stringLength() * * \param[in] src string can be null or NULL-terminated string - * \param[in] size size of src buffer - * \return length of src in bytes. + * \param[in] size number of bytes to check; e.g., size of src buffer + * \return length of src in bytes; 0 if no bytes are found; + * %size on error when NUL byte is not found. * *
  * Notes:
- *      (1) Safe implementation of strlen that only checks size bytes
+ *      (1) Safe implementation of strlen that only checks %size bytes
  *          for trailing NUL.
  *      (2) Valid returned string lengths are between 0 and size - 1.
- *          If size bytes are checked without finding a NUL byte, then
- *          an error is indicated by returning size.
+ *          If %size bytes are checked without finding a NUL byte, then
+ *          an error is indicated by returning %size.
  * 
*/ l_int32 @@ -380,15 +381,18 @@ stringLength(const char *src, l_int32 i; if (!src) - return ERROR_INT("src not defined", __func__, 0); - if (size < 1) return 0; + if (size < 1) + return ERROR_INT("size < 1; too small", __func__, 0); for (i = 0; i < size; i++) { if (src[i] == '\0') return i; } - return size; /* didn't find a NUL byte */ + + /* Didn't find a NUL byte */ + L_ERROR("NUL byte not found in %d bytes\n", __func__, size); + return size; } @@ -396,7 +400,7 @@ l_int32 i; * \brief stringCat() * * \param[in] dest null-terminated byte buffer - * \param[in] size size of dest + * \param[in] size size of dest buffer * \param[in] src string can be null or NULL-terminated string * \return number of bytes added to dest; -1 on error * @@ -433,9 +437,9 @@ l_int32 lendest, lensrc; return ERROR_INT("no terminating nul byte", __func__, -1); lensrc = stringLength(src, size); if (lensrc == 0) - return 0; - n = (lendest + lensrc > size - 1 ? 0 : lensrc); - if (n < 1) + return 0; /* nothing added to dest */ + n = (lendest + lensrc > size - 1) ? 0 : lensrc; + if (n == 0) return ERROR_INT("dest too small for append", __func__, -1); for (i = 0; i < n; i++) diff --git a/sw.cpp b/sw.cpp index cb60b320b..29c992c67 100644 --- a/sw.cpp +++ b/sw.cpp @@ -19,7 +19,7 @@ void build(Solution &s) t += "org.sw.demo.webmproject.webp"_dep; }; - auto &leptonica = s.addTarget("danbloomberg.leptonica", "1.84.0"); + auto &leptonica = s.addTarget("danbloomberg.leptonica", "1.84.1"); leptonica += Git("https://github.com/DanBloomberg/leptonica"); { diff --git a/version-notes.html b/version-notes.html index 66988f8b4..6d1dce06f 100644 --- a/version-notes.html +++ b/version-notes.html @@ -89,6 +89,15 @@

 
+1.84.1  Not yet released
+        * Remove support for openjpeg versions < 2.1.
+        * Improve error handling for stringLength() and stringCat(), and
+          add tests in prog/string_reg.
+        * Source files changed: blend.c, dnafunc1.c, jp2kio.c, pageseg.c,
+          partify.c, pix1.c, ptafunc2.c, sarray2.c, utils2.c
+        * Prog files changed: boxa3_reg.c, string_reg.c, writetext_reg.c,
+          deskew_it.c
+
 1.84.0  Dec 23, 2023
         * Add getPdfPageCount() to find the number of pages in a pdf file.
         * Add getPdfPageSizes() and getPdfMediaBoxSizes() to find the