//********************************************************************************
//* File       : gString.cpp                                                     *
//* Author     : Mahlon R. Smith                                                 *
//*              Copyright (c) 2011-2026 Mahlon R. Smith, The Software Samurai   *
//*                 GNU GPL copyright notice located in gString.hpp              *
//* Date       : 12-May-2025                                                     *
//* Version    : (see gStringVersion string below)                               *
//*                                                                              *
//* Description: This class is used to convert between UTF-8 character           *
//* encoding and wchar_t (UTF-32 character encoding. Text formatting and         *
//* statistical information for analysis and display of the data is generated.   *
//*                                                                              *
//* This is not a comprehensive string class, but a highly-specialized,          * 
//* single-purpose string class for text encoding conversions and formatting.    *
//* The gString classe was developed to support the NcDialog class,              *
//* NCurses class, NcWindow classes and their descendants, and is a compromise   *
//* between the full functionality of Glib::ustring and the need to keep the     *
//* NCurses, NcWindow, and NcDialog classes independent from third-party         *
//* libraries. The gString class has evolved over time to be a useful tool       *
//* for any application which needs to format and analyze text data.             *
//*                                                                              *
//* The gString class is a stand-alone class, that is, it requires no third-     *
//* party source or libraries beyond the standard C and C++ libraries found in   *
//* all GNU compiler releases. Note however that the flag '_XOPEN_SOURCE'        *
//* must be defined.                                                             *
//*                                                                              *
//* Dynamic memory allocation from the 'heap' is performed during instantion     *
//* of the object, and is maintained until the destructor is called, that is,    *
//* when the object goes out-of-scope.                                           *
//*                                                                              *
//* This class is relatively fast and is useful for text data analysis and       *
//* manipulation, but is rather inefficient for data storage (although perhaps   *
//* no more inefficient than std::string() or std::wstring()).                   *
//*                                                                              *
//* ----  ----  ----  ----  ----  ----  ----  ----  ----  ----  ----  ----  ---- *
//* Developed using:                                                             *
//*   GNU G++ (Gcc v: 4.4.2 through 13.3.1-3)                                    *
//*   GNOME terminal 2.28.2 through 3.54.0 (GNOME 47)                            *
//*   Fedora Linux 12 through 41                                                 *
//*                                                                              *
//********************************************************************************
//* Version History (most recent first):                                         *
//*                                                                              *
//* v: 0.0.37 04-Feb-2025                                                        *
//*   - Eliminate all instances of static gString.                               *
//*   - Remove all code which was living under the BACKWARD_COMPATIBILITY        *
//      conditional-compile declaration.                                         *
//*   - Consolidate the separate dynamic allocations for wchar_t[], char[] and   *
//*     short[] arrays into a single allocation. This reduces stress on the      *
//*     Linux memory-management sub-system, which is one of the weaker elements  *
//*     of Linux (especially under Wayland). See allocate() method for details.  *
//*   - Rename the experimental bString class to the mainline gString class.     *
//*   - Rename the debugging file as 'gsdbg.txt'.                                *
//*   - Update the Texinfo documentation to reflect recent changes.              *
//*   - Establish the updated functionality as the mainline development stream:  *
//*     Copy back to base source directory under NcDialog API, Dialog1           *
//*                                                                              *
//* v: 0.0.36 01-Nov-2024                                                        *
//*   - Design of the 'bsmeter' test utilty has been completed, and all bString  *
//*     functionality exercised by that utility has been verified.               *
//*     Be aware, however, that the bString class is still in early days, and    *
//*     that problems may occasionally arise until the code has stablized.       *
//*   - Specifications are still fluid; functionality may be added or removed    *
//*     in response to user input.                                               *
//*   - Create "gString.lib" which combines the existing gString class and       *
//*     the experimental bString class. Integrate this library into our AnsiCmd  *
//*     package for real-world testing.                                          *
//*   - Update dumpBstring() to calculate the isAscii flag.                      *
//*   - Define the BACKWARD_COMPATIBILITY declaration to allow the 'bsmeter'     *
//*     test application to perform backward-compatibility tests. This also      *
//*     prevents duplication of static definitions between the classes.          *
//*   - The expansion of the buffers has required increasing the size of some    *
//*     key integer values from 'short' to 'int32_t'. Where this caused          *
//*     ambiguity in the prototypes, then BOTH integer sizes are prototyped.     *
//*   - Bug Fix: The after(wchar_t) overload was not passing the start offset.   *
//*   - Implement a locale-aware version of the compare() method: compcoll().    *
//*     This varient uses the 'wcscoll' function to perform comparisons on       *
//*     UTF-32 string data. The 'strcoll' (UTF-8) function is not referenced.    *
//*   - Implement format-specification overloads for the insert() method.        *
//*     These complement the format-spec versions of the append() method.        *
//*   - Update gString documentation to include information about early releases *
//*     of the bString class.                                                    *
//*                                                                              *
//* v: 0.0.35 06-Oct-2024                                                        *
//*   - Independent development branch from the gString class, v:0.0.34.         *
//*     The bString (BetaString) class is functionally identical to the gString  *
//*     class, as much as possible, for painless switching between the two,      *
//*     _and_ for re-merging the branches in a future release.                   *
//*   - Copy the structure of the gString class, replacing the fixed buffer      *
//*     sizes for 'gStr', 'uStr' and 'colWidth' with dynamic memory allocations. *
//*     This allows the bString object's character buffer to be sized between:   *
//*      Minimum: gsALLOCMIN (this is one kibibyte, the same size as gString).   *
//*               This is also the default allocation: gsALLOCDFLT.              *
//*      Maximum: gsALLOCMAX which is defined as one mibibyte.                   *
//*   - The bString class functionally diverges from gString class in the        *
//*     following areas:                                                         *
//*   - During instantiation, and when text is modified dynamic memory allocation*
//*     will be performed as necessary to prevent truncation of the data.        *
//*     Refer to the documentation for a description of memory allocation.       *
//*   - The "reAlloc" method may be used to manually increase or decrease the    *
//*     size of the storage buffer in increments of gsALLOCMIN.                  *
//*   - The gString constructor which takes a gsForm-class object as a           *
//*     parameter has been removed for the bString class. This functionality     *
//*     has been deprecated for several years, and has been deleted from this    *
//*     development branch.                                                      *
//*   - A method has been added to allow assignment of a gString object to       *
//*     the bString object (operator=). The reciprocal assignment:               *
//*     gString = bString is not available at this time.                         *
//*   - Methods have been added to allow comparison between a gString object     *
//*     and a bString object (operator== and operator!= and compare(gString&).   *
//*   - The wAlloc() and uAlloc() methods report the current buffer size for     *
//*     UTF-32 and UTF-8 encodings, respectively.                                *
//*   - The freeSpace() method reports the amount of unused space remaining      *
//*     in the buffer. (expressed as number of UTF-32 characters)                *
//*   - Direct support for unsigned byte data has been enhanced, so that the     *
//*     need for casting of data pointers has been reduced.                      *
//*     - Technical Note:                                                        *
//*       The UTF-8 data are stored in a 'char' (signed byte) array ONLY         *
//*       because that is what the format-conversion functions of the C++        *
//*       library expect. Be aware that UTF-8 data are actually unsigned-byte    *
//*       (uint8_t) data. The compiler will perform the cast automagically       *
//*       under most (but not all) circumstances. An explicit cast is sometimes  *
//*       necessary to quiet a compiler warning. This is safe and effective.     *
//*   - Automatic re-sizing of storage buffers during instantiation and          *
//*     initializations is implemented in the current release; however, these    *
//*     algorithms will not be considered as stable until additional real-world  *
//*     test data are available.                                                 *
//*     The 'bsmeter' test utility's '-r' (reallocate) test may be used to       *
//*     validate the operations for which automatic reallocation occurs.         *
//*     - Under extreme circumstances, that is, when the text length is greater  *
//*       than the maximum storage allocation, the text may be truncated.        *
//*                                                                              *
//* v: 0.0.34 06-Oct-2024                                                        *
//*   - Bug Fix: In formatQInt(), range check for 10-terabyte limit was not      *
//*     robust. If 10Tb limit was exceeded, formatting might have been truncated.*
//*                                                                              *
//* v: 0.0.33 25-Sep-2023                                                        *
//*   - Add the new method: formatParagraph().                                   *
//*     Format the text into a rectangular (paragraph) format of the specified   *
//*     dimensions.                                                              *
//*   - For the erase() group of methods, add an optional parameter, "all",      *
//*     which specifies that all instances of the specified substring following  *
//*     the specified offset are to be deleted.                                  *
//*     - This required a semi-transparent change to the single-character-erase  *
//*       overload to resolve a conflict which causes a compiler warning.        *
//*       The 'wchar_t src' parameter becomes 'const wchar_t& src'.              *
//*       This should not impact existing application source code.               *
//*   - Add the new method: findr() -- This is the reverse of the find() method. *
//*     Reverse search: scan backward from the specified offset for a matching   *
//*     substring.                                                               *
//*   - Add overloads to the following methods to accept unsigned char (uint8_t) *
//*     strings as parameters without having to cast them: constructor,          *
//*     operator=, copy(), substr(), append(), insert(), loadChars().            *
//*   - Update source code comments.                                             *
//*   - Add include file: 'cstdint' to gString.hpp                               *
//*     This file was formerly included by 'csdtlib' but as of c++13 is no       *
//*     longer automatically included. This change caused the definition group:  *
//*     int8_t ... int64_t and uint8_t ... uint64_t to be undefined.             *
//*     This in turn caused compilation of the gString code to fail.             *
//*   - Update documentation.                                                    *
//*   - Standalone package posted to website: 30-May-2024                        *
//*                                                                              *
//* v: 0.0.32 29-May-2021                                                        *
//*   - Add new method, rightJustify() which shifts the text to the right to     *
//*     fill the specified field width.                                          *
//*   - Beta testers have confirmed recent updates, so conditional compilations  *
//*     used for updates to the 'textReverse()' method have been removed.        *
//*                                                                              *
//* v: 0.0.31 01-Feb-2021                                                        *
//*   - Bug Fix: When calculating column width, the wcwidth() library function   *
//*     returns '-1' for control characters. This skewed the actual column       *
//*     count for strings containing control characters. Control characters      *
//*     are now reported as having zero (0) width.                               *
//*   - Add an optional parameter to the padCols() method. 'centered'            *
//*     instructs that the padding will be divided equally between the           *
//*     beginning and end of the data.                                           *
//*   - Enhance the textReverse() method to optionally process substrings        *
//*     delimited by newline characters as separate, logical lines of a          *
//*     paragraph. Padding may also be added to right-justify the data.          *
//*     This is helpful for columnar data and mixed LTR/RTL data.                *
//*   - Posted 28-May-2021                                                       *
//*                                                                              *
//* v: 0.0.30 11-Dec-2020                                                        *
//*   - Implement textReverse() method. This method is useful for manipulating   *
//*     RTL (Right-To-Left) text or mixed RTL/LTR text, when the display         *
//*     routine either ignores the RTL language or tries to be "helpful" by      *
//*     automagically (and incorrectly) reformatting the data.                   *
//*   - Posted 26-Dec-2020                                                       *
//*                                                                              *
//* v: 0.0.29 03-Jun-2020                                                        *
//*   - Overload 'gscanf()' to allow scan to begin from specified offset into    *
//*     (wide) character data.                                                   *
//*   - Posted 04-Jun-2020                                                       *
//*                                                                              *
//* v: 0.0.28 11-May-2020                                                        *
//*   - Implement 'gscanf()' scan-and-format method based on the C-library       *
//*     "swscanf" function. Template string may be either a const char* OR       *
//*     a const wchar_t*.                                                        *
//*   - Bug Fix: Count of formatting specifications was not ignoring a           *
//*     literal '%' character as it should. The bug would only manifest if the   *
//*     formatting specification had the maximum number of format specifiers     *
//*     (%s %d %X, etc.)                                                         *
//*   - Posted 02-Jun-2020                                                       *
//*                                                                              *
//* v: 0.0.27 11-Dec-2019                                                        *
//*   - Bug Fix: In the fqiRound() method there was a formatting error.          *
//*     If there are no displayed digits to the right of the decimal point,      *
//*     the the point itself should not be displayed. This was caused by         *
//*     referencing an uninititalized index. (Bad programmer, go to your room!)  *
//*   - Posted 15-Dec-2019                                                       *
//*                                                                              *
//* v: 0.0.26 13-Feb-2016                                                        *
//*   - Implement the 'findlast' method group.                                   *
//*   - Overload the 'after' method to take a 'src' argument of a single,        *
//*     wchar_t character. This is consistent with the 'find' and 'findlast'     *
//*     methods.                                                                 *
//*   - Declare some unchanging parameters as 'const'. No functionality change,  *
//*     but more safety.                                                         *
//*   - Bug Fix: in 'composeFieldwidthBugFix' method. (how embarrassing!)        *
//*     For composing strings with field-width specifier, if string to be        *
//*     padded contained multi-column characters, the NULL terminator was        *
//*     _sometimes_ being positioned incorrectly.                                *
//*   - Bug Fix: in 'compare' method. Narrowing of int to short in return        *
//*     value from wcsncmp/wcsncasecmp functions was causing a bad compare       *
//*     between ASCII and CJK characters which could produce very large          *
//*     numerical differences between characters.                                *
//*   - Bug Fix: In the find() method, if caller specified an over-range         *
//*     offset, offset was set to ZERO. This could have caused a caller's        *
//*     sequential find to loop forever. Now if offset parameter is overrange,   *
//*     it is set to index the null terminator.                                  *
//*   - Bug Fix: In the after() method, if caller's substring was not found,     *
//*     then returned index was not -1 as it should have been.                   *
//*   - Implement the 'strip' method.                                            *
//*   - Implement the 'padCols' method.                                          *
//*   - Implement the 'loadChars' method.                                        *
//*   - Implement the 'findx' method.                                            *
//*   - Implement the 'scan' method.                                             *
//*   - Update online docs to reflect these additions and changes.               *
//*   - Posted 01-Sep-2019                                                       *
//*                                                                              *
//* v: 0.0.25 20-Aug-2015                                                        *
//*   - Implement the 'erase' method group.                                      *
//*   - Implement the 'replace' method group.                                    *
//*   - Implement formatted-data overloads for 'append' method.                  *
//*   - Update 'compare' method group to support both case-sensitive and         *
//*     case-insensitive comparisons.                                            *
//*     IMPORTANT NOTE: CHANGES TO EXISTING APPLICATION CODE ARE REQUIRED.       *
//*        Any code which currently uses the _optional_ parameters of the        *
//*        'compare' methods will need to be updated to reflect the changes      *
//*        to the 'compare' prototypes.                                          *
//*   - Add constructor group for creating formatted-integer fields during       *
//*     instantiation. Requested by testers.                                     *
//*   - Redefine the 'formatInteger' method group to the 'formatInt' method      *
//*     method group. Add support for signed values, greater field width and     *
//*     a choice of unit suffixes. Also, the formatting algorithm is much more   *
//*     efficient (most floating-point calculations have been removed).          *
//*     IMPORTANT NOTE: CHANGES TO EXISTING APPLICATION CODE ARE REQUIRED.       *
//*        Any code which currently uses the the 'formatInteger' methods will    *
//*        need to be updated. Only the method name needs to be changed          *
//*        because only the first three parameters had been implemented anyway.  *
//*   - Remove the old 'limitCharacters' and 'limitColumns' method stubs.        *
//*     (see v:0.0.23, below)                                                    *
//*   - Update online docs to reflect these additions and changes.               *
//*                                                                              *
//* v: 0.0.24 25-Feb-2015                                                        *
//*   - Move gstr(void) from header file into cpp file.                          *
//*   - Move ustr(void) from header file into cpp file.                          *
//*   - Implement the output stream insertion operator (operator<<) for both     *
//*     narrow and wide streams. This required including the <iostream> header.  *
//*                                                                              *
//* v: 0.0.23 05-Feb-2015                                                        *
//*   - Shorten name of limitCharacters method to limitChars.                    *
//*     Shorten name of limitColumns method to limitCols.                        *
//*     Functionality is unchanged.                                              *
//*     (stubs for the old names remain temporarily so old code won't break)     *
//*   - Update and reformat Texinfo documentation.                               *
//*                                                                              *
//* v: 0.0.22 03-Jul-2014                                                        *
//*   - Add 'compose' constructor. Similar to the 'compose' member method,       *
//*     but immediately formats the input data during instantiation.             *
//*                                                                              *
//* v: 0.0.21 20-Apr-2014                                                        *
//*   - Fix string termination bug in 'append()'.                                *
//*   - Add 'insert()' method group to insert new text at specified offset       *
//*     in existing text.                                                        *
//*   - Overload 'append()' method for appending a single, wchar_t character.    *
//*   - Add 'operator!=' method.                                                 *
//*                                                                              *
//* v: 0.0.20 22-Oct-2013                                                        *
//*   - Bug fix to differentiate between 'int' size and 'long int' size.         *
//*     Although these are the same on 32-bit Wintel hardware, other platforms   *
//*     (e.g. 64-bit Wintel) may have different widths for 'int' and 'long int'  *
//*   - Convert fixed-arg version of 'compose' method to a pair of variadic      *
//*     methods: one for a 'printf'-style formatting string, and one for a       *
//*     'wprintf'-style formatting string. Note, however, that the ANSI-C/C++    *
//*     specification does not yet support wide-character formatting strings,    *
//*     so the compiler cannot check validity of the wchar_t formatting string.  *
//*   - Add the formatInteger() methods to create a fixed-width display string   *
//*     representing a 16-bit, 32-bit or 64-bit integer. This functionality is   *
//*     the wide version of a FormatInteger() method created for another         *
//*     project.                                                                 *
//*                                                                              *
//* v: 0.0.19 17-Jun-2013                                                        *
//*   - Add methods shiftChars() and shiftCols().                                *
//*   - Add method clear() to reset all internal members to empty string.        *
//*   - Add an extension to the swprintf formatting options for the 'compose'    *
//*     method: %b , %B  (binary integer formatting) See binaryFormat() method.  *
//*   - Modify the 'compose' method to return a const pointer to the wchar_t     *
//*     data. This allows the result of the compose method to be directly        *
//*     inserted into the std::wcout output stream. (It's cool!)                 *
//*                                                                              *
//* v: 0.0.18 17-Apr-2013                                                        *
//*   - Compensate for a logical error in the standard library swprintf()        *
//*     function string conversions. See notes in composeFieldwidthBugFix()      *
//*     method.                                                                  *
//*                                                                              *
//* v: 0.0.17 11-Mar-2012                                                        *
//*   - Due to changes in the ncurses library between v:5.7 and v:5.9, it has    *
//*     become necessary to pass only one wide character at a time to            *
//*     wadd_wch() via the ncout_t class (cchar_t structure).                    *
//*     For this reason, it has become unnecessary and inefficient to            *
//*     pre-populate an array of ncout_t objects for output to the display.      *
//*     Therefore, all support for ncurses character-output 'chunks' has been    *
//*     removed. In addition, to achieve full independence from ncurses, we      *
//*     have removed the data member 'colorAttr' (color attribute) and the       *
//*     associated methods. These changes yield a savings of 2,600 bytes of      *
//*     code space in the gString class, and a savings of 6,100 bytes of data    *
//*     per gString instance.                                                    *
//*                                                                              *
//* v: 0.0.16 15-Jan-2012                                                        *
//*   - For methods that do not change the internal data members, make the       *
//*     methods 'const' to so indicate.                                          *
//*   - Fix bug in limitCharacters(). gsChars member was not counting the        *
//*     NULLCHAR in the character total.                                         *
//*                                                                              *
//* v: 0.0.15 02-Oct-2011                                                        *
//*   - Convert the conversion methods to use the reentrant, restartable         *
//*     mbsrtowcs() and wcsrtombs() library routines, rather than the older      *
//*     mbstowcs() and wcstombs().                                               *
//*   - Implement error-recovery methods Bytewise_u2w() and Bytewise_w2u().      *
//*     These methods are called if the string conversion fails due to an        *
//*     invalid multi-byte UTF-8 character or invalid wchar_t character,         *
//*     respectively. Converts the input up to the offending character,          *
//*     null-terminates both wchar_t and UTF-8 strings, and discards remaining   *
//*     input.                                                                   *
//*                                                                              *
//* v: 0.0.14 19-Sep-2011                                                        *
//*   - Add 'append' methods for both UTF-8 and wchar_t data.                    *
//*                                                                              *
//* v: 0.0.13 16-Aug-2011                                                        *
//*   - Add a constructor and 'operator=' for creating a formatted string        *
//*     using gsForm class to pass the parameters.                               *
//*   - Add 'compose()' method for creating formatted strings. This uses the     *
//*     gsForm class implicitly.                                                 *
//*                                                                              *
//* v: 0.0.12 01-Jul-2011                                                        *
//*   - Add array of character column width values. Can be used to facilitate    *
//*     scrolling through data.                                                  *
//*   - Modified the Reinit() method so that a NULL string is properly           *
//*     represented as having gsChars==1 and utfBytes==1.                        *
//*   - Handle the special case of a constructor or operator= method receiving   *
//*     a NULL string.                                                           *
//*   - Made gsout() method public even though it should only be called by the   *
//*     NCurses and NcWindow class output routines. This was done to             *
//*     fully-protect the gString class data members from the clumsy NCurses     *
//*     class and NcWindow class developer, namely me. :-)                       *
//*   - Add stubs for data-shift and substring methods to facilitate dynamic     *
//*     edit of text strings in NcDialog-class controls. (not currently used)    *
//*                                                                              *
//* v: 0.0.11 16-Apr-2011                                                        *
//*   - Protect constructors and assignment operators from NULL-pointer          *
//*     parameters.                                                              *
//*                                                                              *
//* v: 0.0.10 20-Mar-2011                                                        *
//*   - Bump the version number to indicate that the basic functionality is      *
//*     complete and moderately-well tested.                                     *
//*                                                                              *
//* v: 0.0.01 06-Mar-2011                                                        *
//*   - First try at defining and implementing the needed functionality.         *
//*   - To maintain independence from curses.h, we have created our own          *
//*     'typedef ncattr unsigned int' data type for color attributes, rather     *
//*     than use the 'attr_t' typedef in the ncurses library.                    *
//*   - For the same reason we have defined the ncout_t class to resemble the    *
//*     ncurses 'struct cchar_t'. If some huge change in the ncurses library     *
//*     occurs (unlikely), we will have to revisit this design decision.         *
//********************************************************************************
//* Programmer's Notes:                                                          *
//* 1) Note that the isAscii data member is not initialized until the isASCII()  *
//*    method is called; therefore, do not reference the isAscii data member     *
//*    within any class method without a previous call to isASCII().             *
//* 2) This class could be significantly expanded to emulate more of the         *
//*    'ustring' functionality.                                                  *
//*    If a full-featured string class that understands UTF-8 encoded data is    *
//*    needed, we recommend the glibmm Glib::ustring class which is actually     *
//*    rather sexy...                                                            *
//* 3) Note: Now that the compiler's preprocessor does full type-checking on     *
//*    the formatting template for the 'compose' method and the compose          *
//*    constructor, it complains about our custom '%b' format specification.     *
//*    The wide template is not type-checked, so:                                *
//*       Instead of:                                                            *
//*             gs.compose( "bitmask: %b", &wk.mevent.eventType );               *
//*       Use this (not type checked by the preprocessor):                       *
//*             gs.compose( L"bitmask: %b", &wk.mevent.eventType );              *
//*    We COULD insert a pragma to silence the warning, or we COULD remove the   *
//*    type-check parameter from the 'char fmt' prototype. We don't want to      *
//*    do either at this time.                                                   *
//*                                                                              *
//*  -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   *
//* TO DO LIST:                                                                  *
//* -----------                                                                  *
//* -- Rename all bString to gString.                                            *
//*                                                                              *
//* -- Integrate comments.                                                       *
//*                                                                              *
//* -- Integrate version number.                                                 *
//*                                                                              *
//* -- Potential bug in compose(): If exactly gsFormMAXARGS arguments, and if    *
//*    there is a trailing newline, that newline might be lost.                  *
//*                                                                              *
//* -- 'formatInt' potential enhancements:                                       *
//*    -- see note in 'compose' method about integration of integer formatting   *
//*       with conversion formats.                                               *
//*       -- Under consideration:                                                *
//*          Use the '*' field-width specifier to get the width for formatInt    *
//*          call if the conversion is for integer type. Then we could           *
//*          substitute a %S for the '*i' conversion. A %*.*i would indicate     *
//*          the width AND to strip leading spaces in the output.                *
//*          short  sfWidth = 13 ;                                               *
//*          UINT64 sysfree = 482576534008 ;                                     *
//*          gs.compose( L"System freespace: %*llu", &sfWidth, &sysfree ) ;      *
//*    -- Under consideration:                                                   *
//*       Alternatively, we could add 'insert' a formatted integer               *
//*       gs.insert( int iVal, short fWidth, short offset,                       *
//*                  bool lJust = false, ... ) ;                                 *
//*    -- Alternatively, we could use a precision specified with integers        *
//*       since integer values do not take a precision.                          *
//*       UINT64 sysfree = 482576534008 ;                                        *
//*       gs.compose( L"System freespace: %.13llu", &sysfree ) ;                 *
//*       For stripping the leading spaces (left-justify):                       *
//*       gs.compose( L"System freespace: %-.13llu", &sysfree ) ;                *
//*    -- What would it take to support values to 999.5000...TBytes?             *
//*       Values >= 10.000... TBytes WOULD ALWAYS BE COMPRESSED.                 *
//*    -- Note that a 64-bit signed or unsigned value with comma seperators      *
//*       requires twenty-six(26) columns. Should FI_MAX_8BYTE_WIDTH be          *
//*       increased to accomidate this?                                          *
//*                                                                              *
//* -- Add a constructor which uses a wchar_t formatting string.                 *
//*    (similar to the compose(const wchar_t* wptr, ...) method)                 *
//*                                                                              *
//* -- Create branch version of gString which does not use gsForm, but instead   *
//*    uses by-value parameters to replace pointer values. Although the pointer  *
//*    version works well and efficiently, when the application programmer       *
//*    makes a mistake, and passes a value instead of a pointer, the compiler    *
//*    goes ape-shit. Also, the standard swprintf() uses pass-by-value (with     *
//*    integer promotion), so the user will not have to learn a new skill set.   *
//*                                                                              *
//* -- Implement a ">>" (input stream) method of initializing the object         *
//*    from stdin.  wcin >> gsIn ;  OR  cin >> gsIn ;                            *
//*                                                                              *
//*                                                                              *
//*                                                                              *
//********************************************************************************

//*****************
//* Include Files *
//*****************
#include "gString.hpp"     //* Class definition

//**  ENABLED FOR DEBUGGING ONLY - NOT USED IN PRODUCTION CODE **
#if DEBUG_GSTRING != 0
const char* const gsDebugFile = "gsdbg.txt" ;
#include <fstream>         //* File I/O definitions
using namespace std ;
static gString gsdbg ;
static wchar_t dbmsg[gsALLOCMIN] = { L"no errors" } ;
#endif   //** DEBUG_GSTRING - ENABLED FOR DEBUGGING ONLY **


//*********************
//* Local Definitions *
//*********************
#define ZERO (0)
#define NULLCHAR ('\0')       // NULL terminator character
#define TILDE ('~')           // Highest ASCII character code
#define SPACE (' ')           // ASCII space character
#define DSPACE (0x3000)       // CJK 2-column space character
#define TAB ('\t')            // Horizontal TAB character
#define VTAB ('\v')           // Vertical TAB character
#define NEWLINE ('\n')        // Newline character
#define CR ('\r')             // Carriage-return character
#define FF ('\f')             // Form-feed character

//* Actual maximum characters stored NOT including the null terminator. *
// Programmer"s Note: The maximum character count is limited at gsALLOCMAX-1.
// This means that we copy no more than gsALLOCMAX-2, then add the null terminator.
// This prevents the user from seeing that we pad the max to 1.0M + 1.
static const int32_t REALMAX = gsALLOCMAX - 2 ;

const short maxBPC = 6 ;      // maximum number of bytes in a UTF-8 character

//*********************
//* Local Data        *
//*********************

static const char gStringVersion[] = "0.0.37" ; //* Class version number

//* Enable/disable binary output extension to swprintf function *
#define BINARY_EXTENSION (1)

//* Maximum size of the 'uStr' buffer. (4 times the max characters) *
const int32_t BYTEMAX = (gsALLOCMAX - 1) * 4 ;

//* Length of an output buffer for formatInt) method group.       *
//* This will hold the maximum (+/-9.99Tbyte), signed 'long long' *
//* value, in comma-separated format, plus 3 characters of suffix.*
const short FI_BUFFER_SIZE = (36) ;

//* Minimum field width for compressed interger formatting.*
//* 2-column fields: -9 through 99 only                    *
//* 1-column fields:  0 through  9 only                    *
const short FI_CRITICAL_WIDTH = 3 ;

const unsigned long long TERA10  =  10000000000000 ; // this is: 0x000009184E72A000
const long long TERA10m = -10000000000000 ;          // this is: 0xFFFFF6E7B18D6000
const unsigned long long MEGABYTE = 1000000 ;
const long long MEGABYTEm = -1000000 ;
const unsigned long long GIGABYTE = 1000000000 ;
const long long GIGABYTEm = -1000000000 ;
const unsigned long long TERABYTE = 1000000000000 ;
const long long TERABYTEm = -1000000000000 ;

const wchar_t POINT = L'.' ;
const wchar_t COMMA = L',' ;
const wchar_t HASH  = L'#' ;
const wchar_t PLUS  = L'+' ;
const wchar_t MINUS = L'-' ;


//********************************************************************************
//**:G:                     Constructor Group                                   **
//********************************************************************************

//*************************
//*      ~gString         *
//*************************
//********************************************************************************
//* Destructor. Return all resources to the system.                              *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

gString::~gString ( void )
{

   //* Release the dynamic allocation *
   if ( this->gStr != NULL )
   {
      delete [] this->gStr ;  this->gStr = NULL ;
      this->uStr = NULL ;     this->cWid = NULL ;
   }

}  //* End ~gString() *

//*************************
//*       gString         *
//*************************
//********************************************************************************
//* Default Constructor:                                                         *
//* Initialize members using default gsALLOCDFLT  storage capacity.              *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

gString::gString ( void )
{

   this->allocate ( gsALLOCDFLT ) ; // perform dynamic storage allocation
   this->reset () ;                 // initialize all members

}  //* End gString() *

//*************************
//*       gString         *
//*************************
//********************************************************************************
//* Constructor: Initialize members to default values with empty string.         *
//*                                                                              *
//* This constructor is used to specify the initial storage capacity for the     *
//* gString object. This may be useful if it is known that the data that the     *
//* object will hold will be larger than the default storage capacity.           *
//*                                                                              *
//* Input  : charAlloc: storage capacity                                         *
//*             interpreted as the maximum number of wchar_t (32-bit) characters *
//*             for the storage buffer.                                          *
//*             Range: gsALLOCMIN <= charAlloc <= gsALLOCMAX                     *
//*             -- The minimum allocation of gsALLOCDMIN will result in the      *
//*                same storage capacity at the default constructor.             *
//*             -- The maximum allocation of gsALLOCMAX is 1000 times the        *
//*                minimum allocation.                                           *
//*             The specified value is normalized by rounding upward to the      *
//*             nearest multiple of gsALLOCMIN.                                  *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

gString::gString ( int32_t charAlloc )
{

   this->allocate ( charAlloc ) ;   // perform dynamic storage allocation
   this->reset () ;                 // initialize all members

}  //* End gString() *

//*************************
//*       gString         *
//*************************
//********************************************************************************
//* Constructor: Convert specified UTF-8-encoded source to gString.              *
//*                                                                              *
//*                                                                              *
//* Input  : usrc     : pointer to a UTF-8-encoded, null-terminated string       *
//*          charLimit: (optional, -1 by default) maximum number of source       *
//*                     characters to be stored (not including nullchar).        *
//*                     This is the number of characters (NOT the number of      *
//*                     UTF-8 bytes).                                            *
//*                     If not specified, then all source data will be stored    *
//*                     up to the maximum storage limit.                         *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************
//* Note on allocation:                                                          *
//* In most cases the source data will fit easily into the default allocation:   *
//* (gsALLOCMED bytes, gsALLOCMIN chars). However, if source data would overflow *
//* the default allocation, then allocation is increased.                        *
//*                                                                              *
//* Number of UTF-32 characters will never be more than UTF-8 bytes,             *
//* so it is safe to allocate number of characters equivalent to byte count.     *
//********************************************************************************

gString::gString ( const char* usrc, int32_t charLimit )
{
   int32_t walloc = gsALLOCDFLT ;         // default allocation for character buffer
   if ( charLimit > gsALLOCMAX )          // range-check the source input limit
      charLimit = gsALLOCMAX ;

   //* Default limit indicates that all source data are to be stored. *
   //* Count the source bytes.
   if ( (charLimit <= ZERO) && (usrc != NULL) )
   {
      //* Count the source bytes and assume that byte count >= character count.*
      //* (This pre-count is inefficient, but safer than assuming no overflow.) *
      walloc = -1 ;
      while ( usrc[++walloc] != NULLCHAR ) ;
      if ( walloc > gsALLOCMAX )          // source data will be truncated
         walloc = gsALLOCMAX ;
   }
   if ( charLimit > gsALLOCDFLT )         // if non-default character limit specified
      walloc = charLimit ;

   //* Allocation will be rounded to next multiple of gsALLOCMIN. *
   this->allocate ( walloc ) ;

   //* Reinitialize all data members.           *
   //* Initialize both UTF-32 and UTF-8 data.   *
   //* Reallocation will occur if necessary.    *
   this->operator= ( usrc ) ;
   //* If charLimit < actual, truncate to limit.*
   if ( charLimit < (this->gsChars - 1) )
      this->limitChars ( charLimit ) ;

}  //* End gString() *

//****************************************
//* Constructor with uint8_t source data *
//****************************************
gString::gString ( const uint8_t* usrc, int32_t charLimit )
{
   int32_t walloc = gsALLOCDFLT ;
   if ( charLimit > gsALLOCMAX )          // range-check the limit
      charLimit = gsALLOCMAX ;
   if ( (charLimit <= ZERO) && (usrc != NULL) )
   {
      //* Count the source bytes and assume that byte count >= character count.*
      //* (This pre-count is inefficient, but safer than assuming no overflow.) *
      walloc = -1 ;
      while ( usrc[++walloc] != NULLCHAR ) ;
      if ( walloc > gsALLOCMAX )          // source data will be truncated
         walloc = gsALLOCMAX ;
   }
   else if ( charLimit > gsALLOCDFLT )
      walloc = charLimit ;
   //* Allocation will be rounded to next multiple of gsALLOCMIN. *
   this->allocate ( walloc ) ;

   //* Reinitialize all data members.           *
   //* Initialize both UTF-32 and UTF-8 data.   *
   //* Reallocation will occur if necessary.    *
   this->operator= ( (const char*)usrc ) ;
   //* If charLimit < actual, truncate to limit.*
   if ( charLimit < (this->gsChars - 1) )
      this->limitChars ( charLimit ) ;

}  //* End gString() *

//*************************
//*       gString         *
//*************************
//********************************************************************************
//* Constructor: Convert specified wchar_t (UTF-32 encoded) source to gString.   *
//*                                                                              *
//* Input  : wsrc     : pointer to a wchar_t-encoded, null-terminated string     *
//*          charLimit: (optional, -1 by default) maximum number of source       *
//*                     characters to be stored (not including nullchar).        *
//*                     If not specified, then all source data will be stored    *
//*                     up to the maximum storage limit.                         *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

gString::gString ( const wchar_t* wsrc, int32_t charLimit )
{
   int32_t walloc = gsALLOCDFLT ;
   if ( charLimit > gsALLOCMAX )          // range-check the limit
      charLimit = gsALLOCMAX ;
   if ( (charLimit <= ZERO ) && (wsrc != NULL) )
   {
      //* Count the source characters.                                          *
      //* (This pre-count is inefficient, but safer than assuming no overflow.) *
      walloc = -1 ;
      while ( wsrc[++walloc] != NULLCHAR ) ;
      if ( walloc > gsALLOCMAX )          // source data will be truncated
         walloc = gsALLOCMAX ;
   }
   else if ( charLimit > gsALLOCDFLT )
      walloc = charLimit ;

   //* Allocation will be rounded to next multiple of gsALLOCMIN. *
   this->allocate ( walloc ) ;

   //* Reinitialize all data members.           *
   //* Initialize both UTF-32 and UTF-8 data.   *
   //* Reallocation will occur if necessary.    *
   this->operator= ( wsrc ) ;
   //* If charLimit < actual, truncate to limit.*
   if ( charLimit < (this->gsChars - 1) )
      this->limitChars ( charLimit ) ;

}  //* End gString() *

//*************************
//*       gString         *
//*************************
//********************************************************************************
//* Constructor: Copy specified source object.                                   *
//* This constructor handles the following constructs:                           *
//*  gString gs( gString_object ) ;                                              *
//*  gString gs = gString_object ;                                               *
//*                                                                              *
//* Input  : bsrc : (by reference) object containing source data.                *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

gString::gString ( const gString& bsrc )
{
   //* 1) Allocate dynamic storage equal to source.         *
   //* 2) Initialize all data members.                      *
   //* 3) Copy to UTF-32 storage AND also convert to UTF-8. *
   this->allocate ( bsrc.gsw ) ;
   this->reset() ;
   this->utf32_2_utf8 ( bsrc.gstr(), this->gsw ) ;

}  //* End gString() *

//*************************
//*       gString         *
//*************************
//********************************************************************************
//* Constructor: Convert formatting specification and its arguments to gString.  *
//*              Please refer to compose() method for more information.          *
//*              Important Note: The storage capacity for this constructor is    *
//*              fixed at gsALLOCDFLT, but will be automatically expanded        *
//*              if necessary to accomodate the formatted data.                  *
//*                                                                              *
//* Input  : fmt  : a format specification string in the style of sprintf(),     *
//*                 swprintf() and related formatting C/C++ functions.           *
//*          arg1 : pointer to first value to be converted according to 'fmt'    *
//*          ...  : optional arguments (between ZERO and gsfMAXARGS - 1)         *
//*                 Each optional argument is a POINTER (address of) the value   *
//*                 to be formatted.                                             *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

gString::gString ( const char* fmt, const void* arg1, ... )
{
   this->allocate ( gsALLOCDFLT ) ;       // perform dynamic storage allocation
   this->reset() ;                        // initialize all members

   //* Initialize the formatting structure.                       *
   //* Point to the formatting string and count the format specs. *
   gString gsfmt( fmt ) ;
   gsForm gsf { gsfmt.gstr() } ;
   short specCount = ZERO ;
   for ( short i = ZERO ; gsf.fmtSpec[i] != NULLCHAR ; i++ )
   {
      if ( gsf.fmtSpec[i] == L'%' )
      {
         if ( gsf.fmtSpec[i+1] != L'%' )  ++specCount ;
         else  ++i ;    // step over literal '%'
      }
      /* See implementation notes in header of 'compose' method */
      if ( specCount > gsFormMAXARGS ) { specCount = gsFormMAXARGS ; break ; }
   }

   //* Copy the source-data pointers to our formatting structure. *
   short i = ZERO ;              // target-argument index
   gsf.argList[i++] = arg1 ;     // get the fixed argument
   va_list argList ;
   va_start ( argList, ZERO ) ;
   for ( ; i < specCount ; i++ ) // get contents of variable-length arg list
      gsf.argList[i] = va_arg ( argList, void* ) ;
   va_end ( argList ) ;

   //* Convert the formatting instructions into formatted text data *
   wchar_t* wsrc = new wchar_t[gsALLOCMAX] ;
   int formattedChars ;
   if ( (formattedChars = this->gsForm_2_gs ( gsf, wsrc, gsALLOCMAX )) > this->gsw )
      this->reallocate ( formattedChars ) ;

   //* Reinitialize all data members and convert *
   //* source text from wchar_t to UTF-8.        *
   this->reset() ;
   if ( *wsrc != NULLCHAR )
      this->utf32_2_utf8 ( wsrc ) ;
   delete [] wsrc ;                       // release the dynamic allocation

}  //* End gString() *

//*************************
//*       gString         *
//*************************
//********************************************************************************
//* Constructor: Convert specified integer value to gString.                     *
//*              Please see the 'formatInt' method for formatting details.       *
//*              Important Note: The storage capacity for this group of          *
//*              constructors is fixed at gsALLOCDFLT.                           *
//*                                                                              *
//* Input  : iVal  : integer value to be converted                               *
//*                  Supported value range: plus/minus 9.999999999999 terabytes  *
//*          fWidth: field width (number of display columns)                     *
//*                  range: 1 to FI_MAX_FIELDWIDTH                               *
//*          lJust : (optional, false by default)                                *
//*                  if true, strip leading spaces to left-justify the value     *
//*                  in the field. (resulting string may be less than fWidth)    *
//*          sign  : (optional, false by default)                                *
//*                  'false' : only negative values are signed                   *
//*                  'true'  : always prepend a '+' or '-' sign.                 *
//*          kibi  : (optional, false by default)                                *
//*                  'false' : calculate as a decimal value (powers of 10)       *
//*                            kilobyte, megabyte, gigabyte, terabyte            *
//*                  'true'  : calculate as a binary value (powers of 2)         *
//*                            kibibyte, mebibyte, gibibyte, tebibyte            *
//*          units : (optional) member of enum fiUnits (fiK by default)          *
//*                  specifies the format for the units suffix.                  *
//*                  Note that if the uncompressed value fits in the field,      *
//*                  then this parameter is ignored.                             *
//*                                                                              *
//* Returns: nothing                                                             *
//*          Note: if field overflow, field will be filled with '#' characters.  *
//********************************************************************************
//* Programmer's Note: The actual formatting is done on an unsigned long long    *
//* value. Therefore, we must be sure that the sign-extension for the value      *
//* passed to us carries through any assignment to the wider, unsigned integer.  *
//*                                                                              *
//* If caller casts a signed source value, then the bit pattern _should be_      *
//* correct; however, this has not been comprehensively tested.                  *
//********************************************************************************

gString::gString ( short iVal, short fWidth, bool lJust, 
                   bool sign, bool kibi, fiUnits units )
{
   this->allocate ( gsALLOCDFLT ) ;       // perform dynamic storage allocation
   this->reset() ;                        // initialize all members

   unsigned long long llVal = iVal ;      // sign-extend the source value
   this->formatQInt ( llVal, fWidth, lJust, true, sign, kibi, units ) ;

}  //* End gString() *

gString::gString ( unsigned short iVal, short fWidth, bool lJust, 
                   bool sign, bool kibi, fiUnits units )
{
   this->allocate ( gsALLOCDFLT ) ;       // perform dynamic storage allocation
   this->reset() ;                        // initialize all members

   unsigned long long llVal = iVal ;      // sign-extend the source value
   this->formatQInt ( llVal, fWidth, lJust, false, sign, kibi, units ) ;

}  //* End gString() *

gString::gString ( int iVal, short fWidth, bool lJust, 
                   bool sign, bool kibi, fiUnits units )
{
   this->allocate ( gsALLOCDFLT ) ;       // perform dynamic storage allocation
   this->reset() ;                        // initialize all members

   unsigned long long llVal = (int)iVal ; // sign-extend the source value
   this->formatQInt ( llVal, fWidth, lJust, true, sign, kibi, units ) ;

}  //* End gString() *

gString::gString ( unsigned int iVal, short fWidth, bool lJust, 
                   bool sign, bool kibi, fiUnits units )
{
   this->allocate ( gsALLOCDFLT );        // perform dynamic storage allocation
   this->reset() ;                        // initialize all members

   unsigned long long llVal = iVal ;      // sign-extend the source value
   this->formatQInt ( llVal, fWidth, lJust, false, sign, kibi, units ) ;

}  //* End gString() *

gString::gString ( long iVal, short fWidth, bool lJust, 
                   bool sign, bool kibi, fiUnits units )
{
   this->allocate ( gsALLOCDFLT ) ;       // perform dynamic storage allocation
   this->reset() ;                        // initialize all members

   unsigned long long llVal = (long)iVal ; // sign-extend the source value
   this->formatQInt ( llVal, fWidth, lJust, true, sign, kibi, units ) ;

}  //* End gString() *

gString::gString ( unsigned long iVal, short fWidth, bool lJust, 
                   bool sign, bool kibi, fiUnits units )
{
   this->allocate ( gsALLOCDFLT ) ;       // perform dynamic storage allocation
   this->reset() ;                        // initialize all members

   unsigned long long llVal = iVal ;      // sign-extend the source value
   this->formatQInt ( llVal, fWidth, lJust, false, sign, kibi, units ) ;

}  //* End gString() *

gString::gString ( long long iVal, short fWidth, bool lJust, 
                   bool sign, bool kibi, fiUnits units )
{
   this->allocate ( gsALLOCDFLT ) ;       // perform dynamic storage allocation
   this->reset() ;                        // initialize all members

   unsigned long long llVal = (long long)iVal ; // sign-extend the source value
   this->formatQInt ( llVal, fWidth, lJust, true, sign, kibi, units ) ;

}  //* End gString() *

gString::gString ( unsigned long long iVal, short fWidth, bool lJust, 
                   bool sign, bool kibi, fiUnits units )
{
   this->allocate ( gsALLOCDFLT ) ;       // perform dynamic storage allocation
   this->reset() ;                        // initialize all members

   this->formatQInt ( iVal, fWidth, lJust, false, sign, kibi, units ) ;

}  //* End gString() *


//********************************************************************************
//**:G:                    Initialization Group                                 **
//********************************************************************************

//*************************
//*       compose         *
//*************************
//********************************************************************************
//* Create formatted text data from a format specification string including      *
//* between ZERO and gsfMAXARGS format specifications and their corresponding    *
//* argument pointers..                                                          *
//*                                                                              *
//* Supported data types:                                                        *
//*  %d, %i  integer (decimal)                                                   *
//*  %o      integer (octal)                                                     *
//*  %u      integer (unsigned)                                                  *
//*  %x, %X  integer (hex lower or upper case)                                   *
//*  %f      floating point (fixed point)                                        *
//*  %e, %E  floating point (scientific notation, lower/uppercase)               *
//*  %g, %G  floating point (normal/exponential, lower/uppercase)                *
//*  %a, %A  floating point (hex fraction)                                       *
//*  %c      character                                                           *
//*  %C      character (alias for %lc)                                           *
//*  %s      string                                                              *
//*  %S      string (alias for %ls)                                              *
//*  %p      pointer                                                             *
//*  %b, %B  (extension to swprintf - see description below)                     *
//*  %m      capture 'errno' description string (see /usr/include/errno.h)       *
//*  %n      number of characters printed so far                                 *
//*          (value written to corresponding argument's location)                *
//*  %%      literal '%'                                                         *
//*                                                                              *
//* See man pages for the C/C++ function 'swprintf' or                           *
//* 'Table of Output Conversions' for additional details.                        *
//* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  *
//*                                                                              *
//* Input  : fmt  : a format specification string in the style of sprintf(),     *
//*                 swprintf() and related formatting C/C++ functions.           *
//*          ...  : optional arguments (between ZERO and gsfMAXARGS)             *
//*                 Each optional argument is a POINTER (address of) the value   *
//*                 to be formatted.                                             *
//*                 - Important Note: There must be AT LEAST as many optional    *
//*                   arguments as the number of format specifiers defined in    *
//*                   the formatting string. Excess arguments will be ignored;   *
//*                   HOWEVER, too few arguments will result in an application   *
//*                   crash and ridicule from your peers.                        *
//*                                                                              *
//* Returns: const wchar_t* to formatted data                                    *
//********************************************************************************
//* Implementation Notes:                                                        *
//* =====================                                                        *
//*                                                                              *
//* 1) Unsupported data types                                                    *
//*    ======================                                                    *
//*    Conversion modifiers that are not fully supported at this time:           *
//*                       'j', 'z', 't', '%['                                    *
//*    Note: The '*' field-width specification or precision specification        *
//*          which uses the following argument as the width/precision value      *
//*          IS NOT supported at this time.                                      *
//*                                                                              *
//* 2) Binary-format specifier                                                   *
//*    =======================                                                   *
//*    We implement an extension to the swprintf output-conversion-specifiers    *
//*    for binary formatted output. We have found this formatting option useful  *
//*    when working with bit masks and for verifying bit-shifting operations.    *
//*    - Base formatting specifier:                                              *
//*       %b , %B                                                                *
//*    - Format modifiers for DATA SIZE are the same as for swprintf:            *
//*       hh , h , l , ll , L , q                                                *
//*    - Format modifier for prepending of a type indicator.                     *
//*       '#' (hash character)                                                   *
//*       This is the same principle as for prepending a '0x' indicator to hex   *
//*       output, and will place either a 'b' or 'B' character at the beginning  *
//*       of the output. Examples:  %#hhb -> b0101.1010   %#hhB -> B0101.1010    *
//*    - Format modifier for appending of a type indicator.                      *
//*       '-#' (minus sign and hash character)                                   *
//*       Rather than prepending the indicator, the indicator will be append     *
//*       to the end of the output.                                              *
//*       Examples: %-#hhb -> 0101.1010b   %-#hhB -> 0101.1010B                  *
//*    - Format modifier for specifying the group-seperator character.           *
//*       By default, the bit groups are seperated by a '.' (fullstop)           *
//*       character. To specify an alternate seperator character:                *
//*       % hB -> 0111 0101 1010 0001    (' ' (space) character as seperator)    *
//*       %_hB -> 0111_0101_1010_0001    ('_' (underscore) char as seperator)    *
//*       %#/hB -> B0111/0101/1010/0001  ('/' (slash) character as seperator)    *
//*       %-#-hB -> 0111-0101-1010-0001B ('-' (dash) character as seperator)     *
//*       Valid seperator characters are any printable ASCII character           *
//*       that IS NOT alphabetical, IS NOT a number, and IS NOT a '.'(fullstop)  *
//*    - Format modifier for specifying bit grouping.                            *
//*       By default, bits are formatted in groups of four (4 nybble); however,  *
//*       if desired, bits can be formatted in groups of eight (8 byte):         *
//*       %.8hB -> 01110101.10100001                                             *
//*       %-.8hB -> 01110101-10100001                                            *
//*       %# .8hB -> B01110101 10100001                                          *
//*       %-#`.8hb -> 01110101`10100001b                                         *
//*                                                                              *
//* 3) Format-specification Deficiencies                                         *
//*    =================================                                         *
//*    The compiler will scan your UTF-8 formatting string for valid             *
//*    formatting specifications and warn you of potential problems.             *
//*      HOWEVER:                                                                *
//*    The ANSI-C/C++ specification does not yet support parsing of wchar_t      *
//*    (swprintf-style) formatting strings. If you pass a wchar_t formatting     *
//*    string, the compiler will not be able to warn of potential problems,      *
//*    so construct your formatting string carefully. See gcc documentation      *
//*    for more information:  info -f gcc.info -n 'Function Attributes'          *
//*      ADDITIONALLY:                                                           *
//*    Because the optional parameters are passed as POINTERS, similar to the    *
//*    C/C++ library function 'sscanf' and friends, the compiler cannot perform  *
//*    automatic promotions from short int* to int* or from float* to double*,   *
//*    and so-on. For this reason, please use care that the data type you        *
//*    specify in the formatting string matches the data type of the variable    *
//*    referenced by its parameter pointer. For example, the conversion          *
//*    specifications:  %d, %i, %u, %x, and %X  assume a parameter of 'int'      *
//*    size If your actual variable declaration is for a 8-bit int, short int,   *
//*    long int or long long int, then explicitly indicate this in the           *
//*    corresponding conversion specification.                                   *
//*    Examples:                                                                 *
//*      char      Greeting[] = { "Hello!" } ;                                   *
//*      int       iValue ;                                                      *
//*      long long int qValue ;                                                  *
//*      long int  lValue ;                                                      *
//*      short int sValue1, sValue2, sValue3 ;                                   *
//*      bool      flagValue ;                                                   *
//*      float     fltValue ;                                                    *
//*      double    dblValue ;                                                    *
//*      gString gs ;                                                            *
//*      gs.compose( "%s - %d %12hd, %-hi, %#hx %08lXh %llu %hhd",               *
//*                  Greeting, &iValue, &sValue1, &sValue2, &sValue3,            *
//*                  &lValue, &qValue, &flagValue );                             *
//*      gs.compose( "floating downstream:%10.2f and doubling our pace:%.4lf",   *
//*                  &fltValue, &dblValue ) ;                                    *
//*                                                                              *
//*      Note: Use care when formatting an 8-bit integer: 'char' ,               *
//*            'unsigned char' or a boolean value declared with the 'bool'       *
//*            keyword. For these types, use the 'hh' conversion modifier.       *
//*            Examples: %hhd , %hhu , %hhX                                      *
//*                                                                              *
//* 4) 'swprintf' bug-fix                                                        *
//*    ==================                                                        *
//*    The standard library 'swprintf' function has a design flaw for format     *
//*    specifications that include a field-width specifier.                      *
//*    'swprintf' pads the string to the specified number of CHARACTERS, not     *
//*    the number of COLUMNS as it should do. For (Arabic) numeric source        *
//*    values this is not a problem because one character == one display         *
//*    column. For string source data, however, if the source string contains    *
//*    characters that require more than one display column each, then the       *
//*    output may be too wide.                                                   *
//*    Therefore, for string-source-formatting specifications ONLY               *
//*          (examples: "%12s"  "%-6s"  "%16ls"  "%5S"  "%-24S")                 *
//*    we compensate for this anomalous behavior by interpreting the             *
//*    field-width specifier as number-of-columns, NOT number-of-characters.     *
//*    This will result in output that appears different (and better) than       *
//*    output created directly by the 'swprintf' function.                       *
//*                                                                              *
//* 5) Parameter type checking                                                   *
//*    =======================                                                   *
//*    Unfortunately, type-checking of wchar_t formatting strings is not yet     *
//*    supported by the gnu (v:4.8.0) compiler, (but see wchar.h which is        *
//*    preparing for future expansion). Thus, use care when constructing your    *
//*    'wchar_t fmt' formatting string. The 'char fmt' string is type-checked.   *
//*                                                                              *
//*                                                                              *
//********************************************************************************
//* Programmer's Notes:                                                          *
//*  See gcc.info -n 'Function Attributes' for the __attribute__ keyword and     *
//*  the format (ARCHETYPE, STRING-INDEX, FIRST-TO-CHECK); definition.           *
//*  where:                                                                      *
//*    ARCHETYPE      is the built-in compiler template to use for typechecking  *
//*    STRING-INDEX   is the (1-based) index of the formatting string            *
//*                   (there is an invisible zero-th argument, so formatting     *
//*                   (string index is '2')                                      *
//*    FIRST-TO-CHECK is the (1-based) index of the first argument to check      *
//*                   against the formatting string (a zero '0' for this         *
//*                   parmeter indicates that args should not be checked)        *
//*                                                                              *
//* NOTE: INITIALLY, WE USE THE EXISTING CONVERSION CODE.                        *
//*       LATER, WE CAN HAVE AN ARBITRARY NUMBER OF SPECS/ARGS.                  *
//*       THIS REQUIRES RE-DEFINITION OF THE gsForm CLASS TO USE DYNAMIC         *
//*       ALLOCATION FOR void** args[]; AND A COUNT OF POINTERS IN THE LIST.     *
//*                                                                              *
//* We _could_ redefine the 'compose' method to take advantage of the            *
//* compiler's automatic promotion of integral types to int values, etc.         *
//*                                                                              *
//*   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -      *
//*  Integration of formatted-integer field conversions                          *
//*  ==================================================                          *
//*  For swprintf, specifying a precision with integer formatting indicates      *
//*  the minimum number of digits to produce. However, we could co-opt this      *
//*  for our own purposes.                                                       *
//*  'd' and 'i' indicate signed value                                           *
//*  'u'         indicates unsigned value                                        *
//*  'x' and 'X' do not apply to integer fields                                  *
//*  '-'      == left justify                                                    *
//*  '+'      == force a +/- sign                                                *
//*  '%nd' or '%nu' indicate that swprintf formatting should be used.            *
//*  "%y.zd" or "%y.zu" (precision specified)                                    *
//*      indicates that field formatting should be used.                         *
//*      'y' is the field width, and 'z' is the suffix option.                   *
//*                                                                              *
//*  We could signal integer-field formatting with the '*' modifier:             *
//*  "%*.zd" or "%*.zu" with the preceeding argument as the field width:         *
//*      gs.compose( "Formatted field: %*u", FI_MAX_FIELDWIDTH, &uValue );       *
//*                                                                              *
//*  We could also let the 'd', 'i', 'u' specifiers alone and define our own     *
//*  specified for integer-field formatting.                                     *
//*                                                                              *
//*                                                                              *
//*                                                                              *
//*                                                                              *
//********************************************************************************

const wchar_t* gString::compose ( const wchar_t* fmt, ... )
{
   //* Initialize the formatting structure.                               *
   //* Point to the wchar_t formatting string and count the format specs. *
   gsForm gsf { fmt } ;
   short specCount = ZERO ;
   for ( short i = ZERO ; gsf.fmtSpec[i] != NULLCHAR ; i++ )
   {
      if ( gsf.fmtSpec[i] == L'%' )
      {
         if ( gsf.fmtSpec[i+1] != L'%' )  ++specCount ;
         else  ++i ;    // step over literal '%'
      }
      /* See note above */
      if ( specCount > gsFormMAXARGS ) { specCount = gsFormMAXARGS ; break ; }
   }

   //* Copy the source-data pointers to our formatting structure. *
   va_list argList ;
   va_start ( argList, ZERO ) ;
   for ( short i = ZERO ; i < specCount ; i++ )
      gsf.argList[i] = va_arg ( argList, void* ) ;
   va_end ( argList ) ;

   //* Convert the formatting instructions into formatted text data *
   wchar_t* wsrc = new wchar_t[gsALLOCMAX] ;
   int formattedChars ;
   if ( (formattedChars = this->gsForm_2_gs ( gsf, wsrc, gsALLOCMAX )) > this->gsw )
      this->reallocate ( formattedChars ) ;

   //* Reinitialize all data members and convert *
   //* source text from wchar_t to UTF-8.        *
   this->reset() ;
   if ( *wsrc != NULLCHAR )
      this->utf32_2_utf8 ( wsrc ) ;
   delete [] wsrc ;                       // release the dynamic allocation

   return this->gStr ;

}  //* End compose() *

const wchar_t* gString::compose ( const char* fmt, ... )
{
   //* Formatting string is UTF-8 so that compiler can check the call     *
   //* arguments; however, we need it to be wchar_t.                      *
   gString gsfmt( fmt ) ;

   //* Initialize the formatting structure.                               *
   //* Point to the wchar_t formatting string and count the format specs. *
   gsForm gsf { gsfmt.gstr() } ;
   short specCount = ZERO ;
   for ( short i = ZERO ; gsf.fmtSpec[i] != NULLCHAR ; i++ )
   {
      if ( gsf.fmtSpec[i] == L'%' )
      {
         if ( gsf.fmtSpec[i+1] != L'%' )  ++specCount ;
         else  ++i ;    // step over literal '%'
      }
      /* See note above */
      if ( specCount > gsFormMAXARGS ) { specCount = gsFormMAXARGS ; break ; }
   }

   //* Copy the source-data pointers to our formatting structure. *
   va_list argList ;
   va_start ( argList, ZERO ) ;
   for ( short i = ZERO ; i < specCount ; i++ )
      gsf.argList[i] = va_arg ( argList, void* ) ;
   va_end ( argList ) ;

   //* Convert the formatting instructions into formatted text data *
   wchar_t* wsrc = new wchar_t[gsALLOCMAX] ;
   int formattedChars ;
   if ( (formattedChars = this->gsForm_2_gs ( gsf, wsrc, gsALLOCMAX )) > this->gsw )
      this->reallocate ( formattedChars ) ;

   //* Reinitialize all data members and convert *
   //* source text from wchar_t to UTF-8.        *
   this->reset() ;
   if ( *wsrc != NULLCHAR )
      this->utf32_2_utf8 ( wsrc ) ;
   delete [] wsrc ;                       // release the dynamic allocation

   return this->gStr ;

}  //* End compose() *

//*************************
//*       formatInt       *
//*************************
//********************************************************************************
//* Convert an integer value into a formatted display string of the specified    *
//* width. Value is right-justified in the field, with leading spaces added      *
//* if necessary (but see 'lJust' parameter).                                    *
//* Maximum field width is FI_MAX_FIELDWIDTH. This is wide enough to display     *
//* a 13-digit, signed and comma-formatted value: '+9,876,543,210,777'           *
//*                                                                              *
//* Actual formatting of the value depends on the combination of:                *
//*   a) magnitude of the value                                                  *
//*   b) whether it is a signed value                                            *
//*   c) the specified field-width                                               *
//*   d) the specified suffix format                                             *
//*   e) locale-specific grouping of digits according the LC_NUMERIC locale      *
//*      environment variable                                                    *
//* Examples are for the 'C' and U.S. English locale with default suffix.        *
//* 1) Simple comma formatted output if specified field-width is sufficient.     *
//*    Examples:  345    654,345    782,654,345    4,294,967,295                 *
//* 2) Compression of the value to fit a specified field width.                  *
//*    Examples:  12.3K    999K    12.345M    1.234G    4.3G                     *
//* Please see test application, 'bsmeter', for more examples.                   *
//*                                                                              *
//* Input  : iVal  : integer value to be converted                               *
//*                  Supported value range: plus/minus 9.999999999999 terabytes  *
//*          fWidth: field width (number of display columns)                     *
//*                  range: 1 to FI_MAX_FIELDWIDTH                               *
//*          lJust : (optional, false by default)                                *
//*                  if true, strip leading spaces to left-justify the value     *
//*                  in the field. (resulting string may be less than fWidth)    *
//*          sign  : (optional, false by default)                                *
//*                  'false' : only negative values are signed                   *
//*                  'true'  : always prepend a '+' or '-' sign.                 *
//*          kibi  : (optional, false by default)                                *
//*                  'false' : calculate as a decimal value (powers of 10)       *
//*                            kilobyte, megabyte, gigabyte, terabyte            *
//*                  'true'  : calculate as a binary value (powers of 2)         *
//*                            kibibyte, mebibyte, gibibyte, tebibyte            *
//*         units  : (optional) member of enum fiUnits (fiK by default)          *
//*                  specifies the format for the units suffix.                  *
//*                  Note that if the uncompressed value fits in the field,      *
//*                  then this parameter is ignored.                             *
//*                                                                              *
//* Returns: 'true' if successful                                                *
//*          'false' if field overflow (field will be filled with '#' chars)     *
//*                  note that oveflow can occur ONLY:                           *
//*                  if fWidth <= 3 OR                                           *
//*                  if iVal <= -10.0 terabytes OR iVal >= 10.0 terabytes        *
//*                  if units is a multi-column substring, then some values      *
//*                     will overflow 4-column, 5-column, and 6-column fields    *
//********************************************************************************
//* Programmer's Note: The actual formatting is done on an unsigned long long    *
//* value. Therefore, we must be sure that the sign-extension for the value      *
//* passed to us carries through any assignment to the wider, unsigned integer.  *
//*                                                                              *
//* If caller casts a signed source value, then the bit pattern _should be_      *
//* correct; however, this has not been comprehensively tested.                  *
//*                                                                              *
//* Programmer's Note: Every effort has been made to retain integer-width        *
//* independence, although this comes at the cost of a slight performance        *
//* penalty, i.e. no bit-twiddling is performed during formatting operation.     *
//*                                                                              *
//* The method has been verified for 32-bit Wintel and 64-bit Wintel systems.    *
//* Both of these platforms use 8-byte long long integers.                       *
//*                                                                              *
//* This method may fail on 16-bit platforms, or on platforms where a            *
//* long long integer is less than eight(8) bytes. If you are developing for     *
//* such a system, please send a note about your experiences to the author       *
//* via website.                                                                 *
//********************************************************************************

bool gString::formatInt ( short iVal, short fWidth, bool lJust, bool sign, 
                          bool kibi, fiUnits units )
{
   unsigned long long llVal = (short)iVal ;     // sign-extend the source value
   return ( this->formatQInt ( llVal, fWidth, lJust, true, sign, kibi, units ) ) ;

}  //* End formatInt() *
bool gString::formatInt ( unsigned short iVal, short fWidth, bool lJust, 
                          bool sign, bool kibi, fiUnits units )
{
   //* Sign-extend the source value *
   unsigned long long llVal = iVal ;            // sign-extend the source value
   return ( this->formatQInt ( llVal, fWidth, lJust, false, sign, kibi, units ) ) ;

}  //* End formatInt() *
bool gString::formatInt ( int iVal, short fWidth, bool lJust, 
                          bool sign, bool kibi, fiUnits units )
{
   unsigned long long llVal = (int)iVal ;       // sign-extend the source value
   return ( this->formatQInt ( llVal, fWidth, lJust, true, sign, kibi, units ) ) ;

}  //* End formatInt() *
bool gString::formatInt ( unsigned int iVal, short fWidth, bool lJust, 
                  bool sign, bool kibi, fiUnits units )
{
   unsigned long long llVal = iVal ;            // sign-extend the source value
   return ( this->formatQInt ( llVal, fWidth, lJust, false, sign, kibi, units ) ) ;

}  //* End formatInt() *
bool gString::formatInt ( long iVal, short fWidth, bool lJust, bool sign, 
                  bool kibi, fiUnits units )
{
   unsigned long long llVal = (long)iVal ;      // sign-extend the source value
   return ( this->formatQInt ( llVal, fWidth, lJust, true, sign, kibi, units ) ) ;

}  //* End formatInt() *
bool gString::formatInt ( unsigned long iVal, short fWidth, bool lJust, bool sign, 
                  bool kibi, fiUnits units )
{
   unsigned long long llVal = iVal ;            // sign-extend the source value
   return ( this->formatQInt ( llVal, fWidth, lJust, false, sign, kibi, units ) ) ;

}  //* End formatInt() *
bool gString::formatInt ( long long iVal, short fWidth, bool lJust, bool sign, 
                  bool kibi, fiUnits units )
{
   unsigned long long llVal = (long long)iVal ; // sign-extend the source value
   return ( this->formatQInt ( llVal, fWidth, lJust, true, sign, kibi, units ) ) ;

}  //* End formatInt() *
bool gString::formatInt ( unsigned long long iVal, short fWidth, bool lJust, 
                  bool sign, bool kibi, fiUnits units )
{
   return ( this->formatQInt ( iVal, fWidth, lJust, false, sign, kibi, units ) ) ;

}  //* End formatInt() *

//*************************
//*     operator=         *
//*************************
//********************************************************************************
//* Assignment operator: converts UTF-8-encoded source to gString.               *
//* If source data are larger than target capacity, capacity will be increased   *
//* to accomodate the specified data.                                            *
//*                                                                              *
//* Input  : pointer to an array of UTF-8-encoded characters                     *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void gString::operator= ( const char* usrc )
{
   //* Reinitialize all data members and convert *
   //* source text from UTF-8 to wchar_t.        *
   this->reset() ;
   if ( usrc != NULL )
      this->utf8_2_utf32 ( usrc ) ;

   //* If data truncated during initialization, *
   //* increase storage allocation.             *
   if ( (this->buffer_overrun ()) )
      this->reallocate ( usrc ) ;

}  //* End operator=() *
void gString::operator= ( const uint8_t* usrc )
{
   this->operator= ( (const char*)usrc ) ;
}  //* End operator=() *

//*************************
//*     operator=         *
//*************************
//********************************************************************************
//* Assignment operator: converts wchar_t ('wide') source to gString.            *
//* If source data are larger than target capacity, capacity will be increased   *
//* to accomodate the specified data.                                            *
//*                                                                              *
//* Input  : pointer to an array of wchar_t 'wide' characters                    *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void gString::operator= ( const wchar_t* wsrc )
{

   //* Reinitialize all data members and convert *
   //* source text from wchar_t to UTF-8.        *
   this->reset() ;
   if ( wsrc != NULL )
      this->utf32_2_utf8 ( wsrc ) ;

   //* If data truncated during initialization, *
   //* increase storage allocation.             *
   if ( (this->buffer_overrun ()) )
      this->reallocate ( NULL, wsrc ) ;

}  //* End operator=() *

//*************************
//*     operator=         *
//*************************
//********************************************************************************
//* Assignment operator: Copies one gString object to another.                   *
//* Important Note On Storage Capacity:                                          *
//*  -- If target capacity is smaller than source capacity, then target capacity *
//*     will be increased to match source _without regard_ to the amount of      *
//*     source data.                                                             *
//*  -- If target capacity is equal to or larger than source capacity, then its  *
//*     capicity is not modified.                                                *
//*                                                                              *
//* Input  : an initialized gsForm object (by reference)                         *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void gString::operator= ( const gString& bssrc )
{
   if ( bssrc.gsw > this->gsw )           // if source buffer is larger
      this->reallocate ( bssrc.gsw ) ;    // expand target buffer
   this->utf32_2_utf8 ( bssrc.gstr() ) ;  // initialize target data

}  //* End operator=() *

//*************************
//*       loadChars       *
//*************************
//********************************************************************************
//* Load the specified number of characters from the source data.                *
//* By default, the new text REPLACES the existing text; however, the 'append'   *
//* parameter allows the new text to be appended to the existing text.           *
//* Combined character count is limited to maximum storage capacity.             *
//*                                                                              *
//* Input  : usrc     : pointer to a UTF-8 encoded string (char* or uint8_t*)    *
//*                     OR                                                       *
//*          wsrc     : pointer to a UTF-32 (wchar_t) encoded string             *
//*          charLimit: maximum number of characters (not bytes) from source     *
//*                     array to load. Range: 1 through target storage capacity  *
//*                     The count should not include the NULL terminator         *
//*          append   : (optional, 'false' by default)                           *
//*                     if 'false', replace existing text with specified text    *
//*                     if 'true', append new text to existing text              *
//*                                                                              *
//* Returns: number of characters in modified string (incl. NULL terminator)     *
//********************************************************************************

int32_t gString::loadChars ( const wchar_t* wsrc, int32_t charLimit, bool append )
{
   if ( charLimit <= ZERO )            // prevent stupidity
      charLimit = 1 ;
   else if ( charLimit >= gsALLOCMAX ) // and buffer oversize
      charLimit = gsALLOCMAX - 1 ;

   if ( wsrc != NULL && *wsrc != NULLCHAR )
   {
      if ( append != false )
      {
         //* Expected total for concatenated data *
         int32_t trgChars = charLimit + this->gsChars - 1 ;
         wchar_t* wbuff = new wchar_t[trgChars + gsALLOCMIN] ;// work buffer
         int32_t si = ZERO, ti = ZERO ;   // source and target indices
         if ( trgChars > REALMAX ) { trgChars = REALMAX ; } // limit to actual maximum

         //* If concatenated data will fit within existing buffer space. *
         if ( trgChars < (this->gsw - 2) )
         {
            while ( (si < charLimit) && (wsrc[si] != NULLCHAR) )
               wbuff[ti++] = wsrc[si++] ;
            wbuff[ti] = NULLCHAR ;        // terminate the data
            this->append ( wbuff ) ;      // append to existing data
         }
         //* If reallocation needed to accomodate concatenated data *
         else //( trgChars >= (this->gsw - 2) )
         {
            //* Save existing data (not incl. null terminator) *
            while ( si < (this->gsChars - 1) )
               wbuff[ti++] = this->gStr[si++] ;
            //* Save data to be appended *       
            for ( si = ZERO ; (si < charLimit) && 
                  (wsrc[si] != NULLCHAR) && (ti < trgChars) ; )
            { wbuff[ti++] = wsrc[si++] ; }               
            wbuff[ti] = NULLCHAR ;        // terminate the data
            this->operator= ( wbuff ) ;   // save concatenated data
         }
         delete [] wbuff ;                // release the dynamic allocation
      }
      else                                // replace existing data
      {
         this->reset() ;                  // erase existing data

         this->utf32_2_utf8 ( wsrc, charLimit ) ;

         //* If data truncated during initialization, *
         //* increase storage allocation.             *
         if ( (this->buffer_overrun ()) )
         {
            this->reallocate ( NULL, wsrc ) ;
            if ( this->gsChars > charLimit )
               this->limitChars ( charLimit - 1 ) ;
         }
      }
   }
   return this->gsChars ;

}  //* End loadChars() *

//********************************************************************************
//* loadChars from byte source data                                              *
//********************************************************************************
int32_t gString::loadChars ( const char* usrc, int32_t charLimit, bool append )
{
   if ( usrc != NULL && *usrc != NULLCHAR )
   {
      if ( charLimit <= ZERO )            // prevent stupidity
         charLimit = 1 ;                         
      else if ( charLimit >= gsALLOCMAX )
         charLimit = gsALLOCMAX - 1 ;

      gString gstmp( charLimit + gsALLOCMIN ) ;
      gstmp = usrc ;
      gstmp.limitChars( charLimit ) ;
      this->loadChars ( gstmp.gstr(), charLimit, append ) ;
   }
   return this->gsChars ;

}  //* End loadChars() *
int32_t gString::loadChars ( const uint8_t* usrc, int32_t charLimit, bool append )
{
   return ( (this->loadChars ( (const char*)usrc, charLimit, append )) ) ;
}  //* End loadChars() *


//********************************************************************************
//**:G:                     Statistics Group                                    **
//********************************************************************************

//*************************
//*         gstr          *
//*************************
//********************************************************************************
//* Return a const pointer to the array of wchar_t (wide) characters.            *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: pointer to array of wchar_t characters                              *
//********************************************************************************

const wchar_t* gString::gstr ( void ) const
{
   return ( this->gStr ) ;       // pointer to array

}  //* End gstr() *

//*************************
//*         gstr          *
//*************************
//********************************************************************************
//* Return a const pointer to the array of wchar_t (wide) characters.            *
//*                                                                              *
//* Input  : charCount : (by reference, initial value ignored)                   *
//*                      receives number of characters in array,                 *
//*                      including null terminator                               *
//*                                                                              *
//* Returns: pointer to array of wchar_t characters                              *
//********************************************************************************

const wchar_t* gString::gstr ( int32_t& charCount ) const
{
   charCount = this->gsChars ;   // characters in the array
   return ( this->gStr ) ;       // pointer to array

}  //* End gstr() *

//*************************
//*         ustr          *
//*************************
//********************************************************************************
//* Return a const pointer to the array of UTF-8-encoded char characters.        *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: pointer to array of const char characters                           *
//********************************************************************************

const char* gString::ustr ( void ) const 
{
   return ( this->uStr ) ;       // pointer to array

}  //* End ustr() *

//*************************
//*         ustr          *
//*************************
//********************************************************************************
//* Return a const pointer to the array of UTF-8-encoded char characters.        *
//*                                                                              *
//* Input  : charCount:(by reference, initial value ignored)                     *
//*          receives number of characters in array, including null terminator   *
//*        : byteCount:(by reference, initial value ignored)                     *
//*          receives number of data bytes in array                              *
//*                                                                              *
//* Returns: pointer to array of const char characters                           *
//********************************************************************************

const char* gString::ustr ( int32_t& charCount, int32_t& byteCount ) const 
{
   charCount = this->gsChars ;   // characters in the array
   byteCount = this->utfBytes ;  // bytes in the array
   return ( this->uStr ) ;       // pointer to array

}  //* End ustr() *

//*************************
//*       gschars         *
//*************************
//********************************************************************************
//* Returns the number of characters in the string including null terminator.    *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: number of characters in the string                                  *
//********************************************************************************

int32_t gString::gschars ( void ) const
{

   return this->gsChars ;

}  //* End gschars() *

//*************************
//*       gscols          *
//*************************
//********************************************************************************
//* Returns the number of columns required to display the string.                *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: number of columns needed to display the data                        *
//********************************************************************************

int32_t gString::gscols ( void ) const
{

   return this->gsCols ;

}  //* End gscols() *

//*************************
//*        gscols         *
//*************************
//********************************************************************************
//* Returns a pointer to an array of column counts, one for each character       *
//* of the text data. Note that number of entries == number of characters.       *
//*                                                                              *
//* Input  : charCount (by reference, initial value ignored)                     *
//*           on return, contains number of characters in the string including   *
//*           the null terminator.                                               *
//*                                                                              *
//* Returns: pointer to number of columns needed for display of each character   *
//********************************************************************************

const short* gString::gscols ( int32_t& charCount ) const
{

   charCount = this->gsChars ;
   return this->cWid ;

}  //* End gscols() *

//*************************
//*      utfbytes         *
//*************************
//********************************************************************************
//* Returns the number of bytes in the UTF-8-encoded string.                     *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: number of bytes in UTF-8 string                                     *
//********************************************************************************

int32_t gString::utfbytes ( void ) const
{

   return this->utfBytes ;

}  //* End utfbytes() *

//*************************
//*      isASCII          *
//*************************
//********************************************************************************
//* Scan the data to determine whether it is pure, 7-bit ASCII.                  *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: 'true' if data are pure, 7-bit ASCII, else 'false'                  *
//********************************************************************************

bool gString::isASCII ( void )
{
   this->isAscii = true ;
   if ( this->gsChars > ZERO )
   {
      for ( int32_t i = ZERO ; this->uStr[i] != NULLCHAR ; i++ )
      {
         if ( this->uStr[i] < ZERO || this->uStr[i] > TILDE )
         {
            this->isAscii = false ;
            break ;
         }
      }
   }
   return this->isAscii ;

}  //* End isASCII() *

//*************************
//*   wAlloc and uAllow   *
//*************************
//********************************************************************************
//* Report data storage allocation for the gString object:                       *
//* Note that this the maximum UTF-32 (wchar_t) characters (or UTF-8 byte)       *
//* capacity, NOT the current number of characters or bytes stored.              *
//*   wAlloc : returns the storage allocation for UTF-32 (wchar_t) characters    *
//*   uAlloc : returns the storage allocation for UTF-8 bytes                    *
//*                                                                              *
//* Input  : none                                                                *
//* Returns: size of dynamic storage allocation in wchar_t words                 *
//*          OR                                                                  *
//*          size of dynamic storage allocation in uint8_t (char) bytes          *
//*         (overflow padding is not included in the reported count)             *
//********************************************************************************

int32_t gString::wAlloc ( void ) const { return ( (this->gsw - 1) ) ; }
int32_t gString::uAlloc ( void ) const { return ( (this->gsu - 4) ) ; }

//*************************
//*       freeSpace       *
//*************************
//********************************************************************************
//* Reports the amount of free buffer space available, expressed as a count of   *
//* UTF-32 (wchar_t) characters.                                                 *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: available buffer space                                              *
//*          Note: If zero(0) is returned, data may have been truncated to fit.  *
//********************************************************************************
//* Note: We return one less character than is actually available, so caller     *
//*       does not need to worry about the null terminator.                      *
//********************************************************************************

int32_t gString::freeSpace ( void ) const
{
   int32_t free = this->gsw - this->gsChars - 1 ;
   if ( free < ZERO ) { free = ZERO ; }
   return free ;

}

//*************************
//*  Get_gString_Version  *
//*************************
//********************************************************************************
//* Return a pointer to gString class version number string                      *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: pointer to version string                                           *
//********************************************************************************

const char* gString::Get_gString_Version ( void ) const
{

   return gStringVersion ;

}  //* End Get_gString_Version() *


//********************************************************************************
//**:G:                   Data Modification Group                               **
//********************************************************************************

//*************************
//*       append          *
//*************************
//********************************************************************************
//* Append text to existing gString text data.                                   *
//* Please BE CERTAIN that the source data are null terminated.                  *
//*                                                                              *
//* Input  : wPtr: pointer to array of wchar_t 'wide' text to be appended        *
//*           OR                                                                 *
//*          uPtr: pointer to array of char UTF-8 text to be appended            *
//*           OR                                                                 *
//*          wChar: a single, 'wide' character                                   *
//*                                                                              *
//* Returns: number of characters in resulting string (incl. NULL terminator)    *
//*          Note: If the operation exceeds storage capacity, excess data        *
//*                will be truncated.                                            *
//********************************************************************************

int32_t gString::append ( const wchar_t* wPtr )
{
   if ( wPtr != NULL && *wPtr != NULLCHAR )
   {
      int32_t origCnt  = this->gsChars - 1,  // existing chars (not incl. null terminator)
              srcCnt = -1,                   // source characters _including_ nullchar
              trgCnt,                        // concatenated-data size
              srcIndex = ZERO,               // index head of existing data
              trgIndex = this->gsChars - 2 ; // reference (before) the null terminator
      while( wPtr[++srcCnt] != NULLCHAR ) ;
      ++srcCnt ;

      //* If combined size is greater than current allocation, *
      //* expand the allocation.                               *
      if ( (trgCnt = origCnt + srcCnt) >= (this->gsw - 1) )
      {
         wchar_t* wbuff = new wchar_t[trgCnt + 64] ;
         //* Copy source data, not including null terminator.  *
         for ( trgIndex = ZERO ; srcIndex < (this->gsChars - 1) ; )
            wbuff[trgIndex++] = this->gStr[srcIndex++] ;
         //* Append the new data *
         srcIndex = ZERO ;
         while ( wPtr[srcIndex] != NULLCHAR )
            wbuff[trgIndex++] = wPtr[srcIndex++] ;
         wbuff[trgIndex] = NULLCHAR ;        // terminate the string
         this->reallocate ( trgCnt ) ;       // expand data storage
         this->operator= ( wbuff ) ;         // save the concatenated data
         delete [] wbuff ;
      }

      //* Otherwise, combined data will fit in current buffer. *
      else
      {
         do
         { this->gStr[++trgIndex] = wPtr[srcIndex++] ; }
         while ( (this->gStr[trgIndex] != NULLCHAR) && (trgIndex < trgCnt) ) ;
         this->gStr[trgIndex] = NULLCHAR ;   // be very sure that we are terminated
         this->gsChars = trgIndex + 1 ;

         //* Convert wide data to UTF-8 data and recalculate statistics *
         this->utf32_2_utf8 () ;
      }
   }
   return this->gsChars ;

}  //* End append() *
int32_t gString::append ( const char* uPtr )
{
   //* Note that the constructor allocates   *
   //* sufficient space for the source data. *
   gString gs( uPtr ) ;
   return ( (this->append( gs.gstr() )) ) ;

}  //* End append() *
int32_t gString::append ( const uint8_t* uPtr )
{
   return ( (this->append ( (const char*)uPtr )) ) ;
}  //* End append() *
int32_t gString::append ( const wchar_t wChar )
{
   wchar_t wstr[] = { wChar, NULLCHAR } ;
   return ( (this->append( wstr )) ) ;
}  //* End append() *

//*************************
//*       append          *
//*************************
//********************************************************************************
//* Append formatted text data to existing gString text data.                    *
//*                                                                              *
//* Please refer to compose() method for more information on converting data     *
//* using a format-specification string.                                         *
//*                                                                              *
//* Input  : fmt  : a format specification string in the style of sprintf(),     *
//*                 swprintf() and related formatting C/C++ functions. (see note)*
//*          arg1 : pointer to first value to be converted by 'fmt'              *
//*          ...  : optional arguments (between ZERO and gsfMAXARGS - 1)         *
//*                 Each optional argument is a POINTER (address of) the value   *
//*                 to be formatted.                                             *
//*                                                                              *
//* Returns: number of characters in resulting string (incl. NULL terminator)    *
//*          Note: If the operation exceeds storage capacity, excess data        *
//*                will be truncated.                                            *
//********************************************************************************
//* Note On Format Specification:                                                *
//* The format specification may be either a 'char' string or a 'wchar_t' string.*
//* Note that compiler validates the 'char' formatting specification             *
//* (via the "__attribute__" macro), but the compiler ignores the 'wchar_t'      *
//* specification. This is intentional, and allows implementation of extensions  *
//* to the available specifiers, e.g. binary formatting.                         *
//********************************************************************************

int32_t gString::append ( const wchar_t* fmt, const void* arg1, ... )
{
   //* Initialize the formatting structure.                       *
   //* Point to the formatting string and count the format specs. *
   gsForm gsf { fmt } ;
   short specCount = ZERO ;
   for ( short i = ZERO ; gsf.fmtSpec[i] != NULLCHAR ; i++ )
   {
      if ( gsf.fmtSpec[i] == L'%' )
      {
         if ( gsf.fmtSpec[i+1] != L'%' )  ++specCount ;
         else  ++i ;    // step over literal '%'
      }
      /* See implementation notes in header of 'compose' method */
      if ( specCount > gsFormMAXARGS ) { specCount = gsFormMAXARGS ; break ; }
   }

   //* Copy the source-data pointers to our formatting structure. *
   short i = ZERO ;              // target-argument index
   gsf.argList[i++] = arg1 ;     // get the fixed argument
   va_list argList ;
   va_start ( argList, ZERO ) ;
   for ( ; i < specCount ; i++ ) // get contents of variable-length arg list
      gsf.argList[i] = va_arg ( argList, void* ) ;
   va_end ( argList ) ;

   //* Convert the formatting instructions into formatted text data. *
   //* If concatenated data is larger than current buffer size,      *
   //* expand the buffer size.                                       *
   //* NOTE: The temp buffer is allocated to maximum capacity.       *
   wchar_t* wsrc = new wchar_t[gsALLOCMAX] ;
   int formattedChars ;
   if ( (formattedChars = this->gsForm_2_gs ( gsf, wsrc, gsALLOCMAX )) > this->gsw )
      this->reallocate ( this->gsChars + formattedChars ) ;

   //* Append the formatted data to existing data.     *
   //* Note that buffers will be re-sized as necessary.*
   int32_t chCnt = this->append ( wsrc ) ;
   delete [] wsrc ;                       // release the dynamic allocation
   return chCnt ;

}  //* End append() *

int32_t gString::append ( const char* fmt, const void* arg1, ... )
{
   //* Initialize the formatting structure.                       *
   //* Point to the formatting string and count the format specs. *
   gString gsfmt( fmt ) ;
   gsForm gsf { gsfmt.gstr() } ;
   short specCount = ZERO ;
   for ( short i = ZERO ; gsf.fmtSpec[i] != NULLCHAR ; i++ )
   {
      if ( gsf.fmtSpec[i] == L'%' )
      {
         if ( gsf.fmtSpec[i+1] != L'%' )  ++specCount ;
         else  ++i ;    // step over literal '%'
      }
      /* See implementation notes in header of 'compose' method */
      if ( specCount > gsFormMAXARGS ) { specCount = gsFormMAXARGS ; break ; }
   }

   //* Copy the source-data pointers to our formatting structure. *
   short i = ZERO ;              // target-argument index
   gsf.argList[i++] = arg1 ;     // get the fixed argument
   va_list argList ;
   va_start ( argList, ZERO ) ;
   for ( ; i < specCount ; i++ ) // get contents of variable-length arg list
      gsf.argList[i] = va_arg ( argList, void* ) ;
   va_end ( argList ) ;

   //* Convert the formatting instructions into formatted text data. *
   //* If concatenated data is larger than current buffer size,      *
   //* expand the buffer size.                                       *
   //* NOTE: The temp buffer is allocated to maximum capacity.       *
   wchar_t* wsrc = new wchar_t[gsALLOCMAX] ;
   int formattedChars ;
   if ( (formattedChars = this->gsForm_2_gs ( gsf, wsrc, gsALLOCMAX )) > this->gsw )
      this->reallocate ( this->gsChars + formattedChars ) ;

   //* Append the formatted data to existing data.     *
   //* Note that buffers will be re-sized as necessary.*
   int chCnt = this->append ( wsrc ) ;
   delete [] wsrc ;                       // release the dynamic allocaton
   return chCnt ;

}  //* End append() *

//*************************
//*       insert          *
//*************************
//********************************************************************************
//* Insert text into existing gString text data up to a combined length of       *
//* storage capacity. Characters in excess of the maximum will be truncated.     *
//*                                                                              *
//* Input  : wPtr  : pointer to array of wchar_t 'wide' text to be inserted      *
//*           OR                                                                 *
//*          uPtr  : pointer to array of char UTF-8 text to be inserted          *
//*           OR                                                                 *
//*          wChar : a single wchar_t 'wide' character                           *
//*          offset: (optional, ZERO by default)                                 *
//*                  character offset at which to insert specified text into     *
//*                  existing text.                                              *
//*                  Note: if specified 'offset' > number of characters in       *
//*                        existing text, then acts like 'append' method.        *
//* Returns: number of characters in resulting string (incl. NULL terminator)    *
//*          Note: If the operation exceeds storage capacity, excess data        *
//*                will be truncated.                                            *
//********************************************************************************
//* Programmer's Note: Caller's data can be of arbitrary length; up to a         *
//* combined total of gsALLOCMAX.                                                *
//*                                                                              *
//* Scenarios for data insertion:                                                *
//*  1) insert at head of text: offset == ZERO.                                  *
//*  2) insert at offset > zero && offset < nulIndex                             *
//*  3) insert at tail of text: offset >= nulIndex                               *
//*                                                                              *
//* Programmer's Note: We COULD HAVE offered a method for inserting one          *
//* single-byte character; however, we do not. UTF-8 characters are multibyte    *
//* by nature, so we would have to deny caller's request if the character        *   
//* provided was not a valid, single-byte character. That would not be elegant.  *
//********************************************************************************

int32_t gString::insert ( const wchar_t* wPtr, int32_t offset )
{
   if ( wPtr != NULL && *wPtr != NULLCHAR )
   {
      int32_t nulIndex = (this->gsChars - 1),  // index null terminator
              srcChars = ZERO,            // number of characters to be inserted
              srcIndex = ZERO,            // index into wPtr data
              trgIndex = ZERO,            // index into trg buffer
              gstIndex = ZERO,            // index ino this->gStr[]
              maxIndex ;                  // end of target buffer

     if ( offset > nulIndex )            // range-check specified offset
         offset = nulIndex ;

      //* Count the source characters. *
      while ( wPtr[srcChars] != NULLCHAR )
         ++srcChars ;
      ++srcChars ;                        // include the null terminator
      maxIndex = this->gsw + srcChars ;   // size of temp buffer

      //* Allocate a temp buffer large enough to *
      //* accomodate the concatenated data.      *
      // Programmer"s Note: The maximum character count is limited at gsALLOCMAX-1.
      // This means that we copy no more than gsALLOCMAX-2, then add the null terminator.
      // This prevents the user from seeing that we pad the max to 1.0M + 1.
      wchar_t* trg = new wchar_t[maxIndex] ; // temp work buffer
      if ( maxIndex > REALMAX ) { maxIndex = REALMAX ; }

      if ( offset == ZERO )
      {
         while ( wPtr[srcIndex] != NULLCHAR && trgIndex < maxIndex )
            trg[trgIndex++] = wPtr[srcIndex++] ;
      }

      //* If new data is to be inserted WITHIN the existing string, *
      //* write the 'before' text to the work buffer.               *
      else if ( (offset > ZERO) && (offset < nulIndex) )
      {
         for ( ; trgIndex < offset && trgIndex < nulIndex ; trgIndex++ )
            trg[trgIndex] = this->gStr[trgIndex] ;
         gstIndex = trgIndex ;   // remember where we parked

         //* Append the new text to 'before'text. *
         while ( wPtr[srcIndex] != NULLCHAR && trgIndex < maxIndex )
            trg[trgIndex++] = wPtr[srcIndex++] ;
      }

      //* Append the 'after' text (if any). *
      while ( this->gStr[gstIndex] != NULLCHAR && trgIndex < maxIndex )
         trg[trgIndex++] = this->gStr[gstIndex++] ;

      //* Append the new text to the tail of existing text. *
      if ( offset == nulIndex )
      {
         while ( wPtr[srcIndex] != NULLCHAR && trgIndex < maxIndex )
            trg[trgIndex++] = wPtr[srcIndex++] ;
      }

      //* Be very sure that the string is terminated *
      trg[trgIndex] = NULLCHAR ;

      //* If combined count > current buffer size, then  *
      //* re-allocate (if not already at max).           *
      if ( trgIndex >= this->gsw )
         this->reallocate ( trgIndex ) ;

      //* Copy the concatenated string to member *
      //* storage and update statistics.         *
      this->operator=( trg ) ;

      delete [] trg ;                     // release the dynamic allocation                  
   }
   return this->gsChars ;

}  //* End insert() *

int32_t gString::insert ( const char* uPtr, int32_t offset )
{
   gString  gs( uPtr ) ;   // auto-size temp object to fit source text
   return ( (this->insert( gs.gstr(), offset )) ) ;

}  //* End insert() *

int32_t gString::insert ( const uint8_t* uPtr, int32_t offset )
{
   return ( (this->insert ( (const char*)uPtr, offset )) ) ;
}  //* End insert() *

int32_t gString::insert ( wchar_t wChar, int32_t offset )          
{
   wchar_t wString[2] = { wChar, NULLCHAR } ;
   return ( (this->insert( wString, offset )) ) ;

}  //* End insert() *

//*************************
//*        insert         *
//*************************
//********************************************************************************
//* Insert formatted text data into existing text data at the specified offset.  *
//*                                                                              *
//* Please refer to compose() method for more information on converting data     *
//* using a format-specification string.                                         *
//*                                                                              *
//* Input  : offset: a format specification string in the style of sprintf(),    *
//*                  Set to zero(0) to insert data a head of buffer.             *
//*          fmt   : a format specification string in the style of sprintf(),    *
//*                  swprintf() and related formatting C/C++ functions.          *
//*                  Format spec may be either char data or wchar_t data.        *
//*          arg1  : pointer to first value to be converted by 'fmt'             *
//*          ...   : optional arguments (between ZERO and gsfMAXARGS - 1)        *
//*                  Each optional argument is a POINTER (address of) the value  *
//*                  to be formatted.                                            *
//*                                                                              *
//* Returns: number of characters in resulting string (incl. NULL terminator)    *
//*          Note: If the operation exceeds storage capacity, excess data        *
//*                will be truncated.                                            *
//********************************************************************************
//* Note that the identifier used by the compiler can't be used here because     *
//* the format specification must be the first argument. Is there a way to       *
//* get around this?                                                             *
//*            __attribute__ ((format (gnu_wprintf, 2, 0)))                      *
//********************************************************************************

int32_t gString::insert( int32_t offset, const wchar_t* fmt, const void* arg1, ... )
{
   wchar_t* wsrc = new wchar_t[gsALLOCMAX] ; // work buffer is maximum allocation
   int32_t formattedChars,                // length of formatted data string
           totChars = ZERO,               // length of concatenated data
           freeSpace = gsALLOCMAX ;       // free space in work buffer

   if ( offset < ZERO )                   // range-check the offset
      offset = ZERO ;
   else if ( offset >= this->gsChars )
      offset = this->gsChars - 1 ;        // reference null terminator

   //* Initialize the formatting structure.                       *
   //* Point to the formatting string and count the format specs. *
   gsForm gsf { fmt } ;
   short specCount = ZERO ;
   for ( short i = ZERO ; gsf.fmtSpec[i] != NULLCHAR ; i++ )
   {
      if ( gsf.fmtSpec[i] == L'%' )
      {
         if ( gsf.fmtSpec[i+1] != L'%' )  ++specCount ;
         else  ++i ;    // step over literal '%'
      }
      /* See implementation notes in header of 'compose' method */
      if ( specCount > gsFormMAXARGS ) { specCount = gsFormMAXARGS ; break ; }
   }

   //* Copy the source-data pointers to our formatting structure. *
   short i = ZERO ;              // target-argument index
   gsf.argList[i++] = arg1 ;     // get the fixed argument
   va_list argList ;
   va_start ( argList, ZERO ) ;
   for ( ; i < specCount ; i++ ) // get contents of variable-length arg list
      gsf.argList[i] = va_arg ( argList, void* ) ;
   va_end ( argList ) ;

   //* If offset is not at head of existing data, copy *
   //* the data _before_ the offset to the work buffer.*
   if ( offset > ZERO )
   {
      for ( totChars = ZERO ; totChars < offset ; ++totChars )
         wsrc[totChars] = this->gStr[totChars] ;
      wsrc[totChars] = NULLCHAR ;         // (be safe)
      freeSpace = gsALLOCMAX - totChars ;
   }
   
   //* Convert the formatting instructions into formatted text data. *
   formattedChars = this->gsForm_2_gs ( gsf, &wsrc[totChars], freeSpace ) ;
   totChars += formattedChars ;

   //* If offset is not a tail of original data, *
   //* copy remainder of text to work buffer.    *
   if ( offset < (this->gsChars - 1) )
   {
      for ( --totChars ;                  // reference null terminator 
            (offset < this->gsChars) && (totChars < REALMAX) ; 
            ++totChars, ++offset )
      { wsrc[totChars] = this->gStr[offset] ; }
   }

   if ( totChars > REALMAX )           // keep total below gsALLOCMAX
      totChars = REALMAX ;
   wsrc[totChars] = NULLCHAR ;         // be sure string is terminated

   //* If concatenated data exceed current buffer allocation, *
   //* reallocate the buffers.                                *
   if ( totChars > this->gsw )
      this->reallocate ( totChars ) ;

   //* Copy the new data to storage, and update data stats. *
   this->operator=( wsrc ) ;

   delete [] wsrc ;                       // release the dynamic allocation

   return this->gsChars ;
   
}  //* End insert() *

int32_t gString::insert( int32_t offset, const char* fmt, const void* arg1, ... )
{
   wchar_t* wsrc = new wchar_t[gsALLOCMAX] ; // work buffer is maximum allocation
   int32_t formattedChars,                // length of formatted data string
           totChars = ZERO,               // length of concatenated data
           freeSpace = gsALLOCMAX ;       // free space in work buffer

   if ( offset < ZERO )                   // range-check the offset
      offset = ZERO ;
   else if ( offset >= this->gsChars )
      offset = this->gsChars - 1 ;        // reference null terminator

   //* Initialize the formatting structure.                               *
   //* Formatting string is UTF-8; however, we need it to be wchar_t.     *
   //* Point to the wchar_t formatting string and count the format specs. *
   gString gsfmt( fmt ) ;
   gsForm gsf { gsfmt.gstr() } ;
   short specCount = ZERO ;
   for ( short i = ZERO ; gsf.fmtSpec[i] != NULLCHAR ; i++ )
   {
      if ( gsf.fmtSpec[i] == L'%' )
      {
         if ( gsf.fmtSpec[i+1] != L'%' )  ++specCount ;
         else  ++i ;    // step over literal '%'
      }
      /* See implementation notes in header of 'compose' method */
      if ( specCount > gsFormMAXARGS ) { specCount = gsFormMAXARGS ; break ; }
   }

   //* Copy the source-data pointers to our formatting structure. *
   short i = ZERO ;              // target-argument index
   gsf.argList[i++] = arg1 ;     // get the fixed argument
   va_list argList ;
   va_start ( argList, ZERO ) ;
   for ( ; i < specCount ; i++ ) // get contents of variable-length arg list
      gsf.argList[i] = va_arg ( argList, void* ) ;
   va_end ( argList ) ;

   //* If offset is not at head of existing data, copy *
   //* the data _before_ the offset to the work buffer.*
   if ( offset > ZERO )
   {
      for ( totChars = ZERO ; totChars < offset ; ++totChars )
         wsrc[totChars] = this->gStr[totChars] ;
      wsrc[totChars] = NULLCHAR ;         // (be safe)
      freeSpace = gsALLOCMAX - totChars ;
   }
   
   //* Convert the formatting instructions into formatted text data. *
   formattedChars = this->gsForm_2_gs ( gsf, &wsrc[totChars], freeSpace ) ;
   totChars += formattedChars ;

   //* If offset is not at tail of original data, *
   //* copy remainder of text to work buffer.     *
   if ( offset < (this->gsChars - 1) )
   {
      for ( --totChars ;                  // reference null terminator 
            (offset < this->gsChars) && (totChars < REALMAX) ; 
            ++totChars, ++offset )
      { wsrc[totChars] = this->gStr[offset] ; }
   }

   if ( totChars > REALMAX )           // keep total below gsALLOCMAX
      totChars = REALMAX ;
   wsrc[totChars] = NULLCHAR ;         // be sure string is terminated

   //* If concatenated data exceed current buffer allocation, *
   //* reallocate the buffers.                                *
   if ( totChars > this->gsw )
      this->reallocate ( totChars ) ;

   //* Copy the new data to storage, and update data stats. *
   this->operator=( wsrc ) ;

   delete [] wsrc ;                       // release the dynamic allocation

   return this->gsChars ;
   
}  //* End insert() *

//*************************
//*         erase         *
//*************************
//********************************************************************************
//* Scan the data for a matching substring, and if found, erase (delete) the     *
//* first occurance of the substring.                                            *
//*                                                                              *
//* Actual comparison is always performed against the 'wide' character array     *
//* using wcsncasecmp (or wcsncmp). Null terminator character IS NOT included    *
//* in the comparison. These comparisons are not locale dependent.               *
//*                                                                              *
//* Input  : src    : source data to be matched, one of the following:           *
//*                   -- pointer to a wchar_t string                             *
//*                   -- pointer to a UTF-8 string                               *
//*                   -- a gString object containing the source (by reference)   *
//*                   -- a single, wchar_t character                             *
//*          offset : (optional, ZERO by default)                                *
//*                   character index at which to begin search                   *
//*                   NOTE: This is a character index, NOT a byte offset.        *
//*          casesen: (optional, 'false' by default)                             *
//*                   if 'false', then scan IS NOT case sensitive                *
//*                   if 'true, then scan IS case sensitive                      *
//*          all    : (optional, 'false' by default)                             *
//*                   if 'false', then only the first instance of the substring  *
//*                               will be deleted                                *
//*                   if 'true',  then all instances of the specified substring  *
//*                               from 'offset' forward will be deleted          *
//*                                                                              *
//* Returns: index of first character following the deleted sequence             *
//*          Note: This is the wchar_t character index, NOT a byte index         *
//*          Returns (-1) if:                                                    *
//*            a) no matching substring found                                    *
//*            b) 'offset' out-of-range                                          *
//*            c) 'src' is an empty string or a NULL character                   *
//********************************************************************************

int32_t gString::erase ( const gString& src, int32_t offset, bool casesen, bool all )
{
   int32_t fi,                      // index of first instance
           len = src.gschars() - 1, // length of source (not incl. nullchar)
           status = (-1) ;          // return value

   if ( (len > ZERO) && (offset >= ZERO) && (offset < (this->gsChars - 1)) )
   {
      while ( (fi = this->find( src, offset, casesen )) >= ZERO )
      {
         //short len = src.gschars() - 1 ;
         status = this->erase( fi, len ) ; // erase the substring
         if ( ! all )                      // if a single erasure, we're done
            break ;
      }
   }
   return status ;

}  //* End erase() *
int32_t gString::erase ( const char* src, int32_t offset, bool casesen, bool all )
{
   gString bs( this->gsw ) ;
   bs = src ;
   return ( (this->erase( bs, offset, casesen, all )) ) ;
}  //* End erase() *
int32_t gString::erase ( const uint8_t* src, int32_t offset, bool casesen, bool all )
{
   gString bs( this->gsw ) ;
   bs = src ;
   return ( (this->erase( bs, offset, casesen, all )) ) ;
}  //* End erase() *
int32_t gString::erase ( const wchar_t* src, int32_t offset, bool casesen, bool all )
{
   gString bs( this->gsw ) ;
   bs = src ;
   return ( (this->erase( bs, offset, casesen, all )) ) ;
}  //* End erase() *
int32_t gString::erase ( wchar_t src, int32_t offset, bool casesen, bool all )
{
   wchar_t wsrc[2] = { src, L'\0' } ;
   return ( (this->erase( wsrc, offset, casesen, all )) ) ;
}  //* End erase() *

//*************************
//*         erase         *
//*************************
//********************************************************************************
//* Erase (delete) the data sequence specified by 'offset' and 'length'.         *
//* 'offset' is the index of the first character to be deleted, and 'length'     *
//* specifies the number of characters to delete.                                *
//*   1) If the 'length' specifies deletion of more characters than remain,      *
//*      then the 'erase' method has the same effect as calling the              *
//*      'limitChars' method.                                                    *
//*   2) If the defaults for both 'offset' and length' are used, then the        *
//*      'erase' method has the same effect as calling the 'clear' method.       *
//*   3) Note that the NULL terminator will never be deleted.                    *
//*   4) If:  a) offset < ZERO,                                                  *
//*           b) offset >= number of characters,                                 *
//*           c) length <= ZERO                                                  *
//*      then data will not be modified and (-1) will be returned.               *
//*                                                                              *
//* Input  : offset : (optional, ZERO by default)                                *
//*                   index of first character of sequence to be erased          *
//*                   NOTE: This is a character index, NOT a byte offset.        *
//*          length : (optional, gsALLOCMAX by default)                          *
//*                   if not specified, then erase all characters from           *
//*                      'offset' to end of data                                 *
//*                   if specified, then erase the specified number of           *
//*                      characters beginning at 'offset'                        *
//* Returns: index of first character following the deleted sequence             *
//*          Note: This is the wchar_t character index, NOT a byte index         *
//*          Returns (-1) if parameter out-of-range (data not modified)          *
//********************************************************************************

int32_t gString::erase ( int32_t offset, int32_t length )
{
   int32_t maxchars = this->gsChars - 1,  // source characters (not incl. nullchar)
           index = (-1) ;                 // return value

   if ( (offset >= ZERO) && (offset < maxchars) && (length > ZERO) )
   {
      //* Erase ALL data *
      if ( (offset == ZERO) && (length > maxchars) )
      { this->clear() ; index = ZERO ; }
      //* Erase from offset to end-of-data *
      else if ( (offset + length) >= maxchars )
      {
         this->limitChars( offset ) ;
         index = this->gsChars - 1 ;
      }
      //* Else, do it the hard way. *
      else
      {
         wchar_t* wbuff = new wchar_t[this->gsw] ;
         int32_t ti = ZERO, si = ZERO ;   // source and target indices
         while ( ti < offset )            // copy data up to offset
            wbuff[ti++] = this->gStr[si++] ;
         si += length ;                   // step over data to discarded
         do
         { wbuff[ti] = this->gStr[si++] ; }
         while ( wbuff[ti++] != NULLCHAR ) ;

         this->utf32_2_utf8 ( wbuff ) ;   // initialize with our new data
         index = offset ;                 // index of insertion point
         delete [] wbuff ;                // release the dynamic allocation
      }
   }
   return index ;

}  //* End erase() *

//*************************
//*        replace        *
//*************************
//********************************************************************************
//* Replace the specified source substring with the provided substring.          *
//*                                                                              *
//* Actual comparison is always performed against the 'wide' character array     *
//* using wcsncasecmp (or wcsncmp). Null terminator character IS NOT included    *
//* in the comparison. These comparisons are not locale dependent.               *
//*                                                                              *
//* Input  : srctxt : source data to be matched, one of the following:           *
//*                   -- pointer to a UTF-8 string                               *
//*                   -- pointer to a wchar_t string                             *
//*                   -- a single, wchar_t character                             *
//*          newtxt : data to overwrite existing text, one of the following:     *
//*                   -- pointer to a UTF-8 string                               *
//*                   -- pointer to a wchar_t string                             *
//*                   -- a single, wchar_t character                             *
//*          offset : (optional, ZERO by default)                                *
//*                   character index at which to begin search                   *
//*                   NOTE: This is a character index, NOT a byte offset.        *
//*          casesen: (optional, 'false' by default)                             *
//*                   if 'false', then scan IS NOT case sensitive                *
//*                   if 'true, then scan IS case sensitive                      *
//*          all    : (optional, 'false' by default)                             *
//*                   if 'false', then replace only the first occurance found    *
//*                   if 'true',  then replace all occurances of the specified   *
//*                               substring                                      *
//* Returns: 'true' if successful                                                *
//*          returns 'false' if error (existing data not modified):              *
//*            a) no matching source substring found                             *
//*            b) 'src' is a empty string or a null character                    *
//*            c) offset < ZERO or offset is beyond existing data                *
//*            d) modifying the data would cause buffer overflow (>gsALLOCMAX)   *
//********************************************************************************

bool gString::replace ( const wchar_t* srctxt, const wchar_t* newtxt, int32_t offset, 
                        bool casesen, bool all )
{
   gString bss( this->gsw ) ;
   bss = srctxt ;
   gString bsr( this->gsw ) ;
   bsr = newtxt ;
   return ( (this->replace( bss, bsr, offset, casesen, all )) ) ;

}  //* End replace() *

bool gString::replace ( const wchar_t* srctxt, const char* newtxt, int32_t offset,
                        bool casesen, bool all )
{
   gString bss( this->gsw ) ;
   bss = srctxt ;
   gString bsr( this->gsw ) ;
   bsr = newtxt ;
   return ( (this->replace( bss, bsr, offset, casesen, all )) ) ;

}  //* End replace() *

bool gString::replace ( const wchar_t* srctxt, const wchar_t newtxt, int32_t offset,
                        bool casesen, bool all )
{
   gString bss( this->gsw ) ;
   bss = srctxt ;
   gString bsr( "%C", &newtxt ) ;
   return ( (this->replace( bss, bsr, offset, casesen, all )) ) ;

}  //* End replace() *

bool gString::replace ( const char* srctxt, const wchar_t* newtxt, int32_t offset,
                        bool casesen, bool all )
{
   gString bss( this->gsw ) ;
   bss = srctxt ;
   gString bsr( this->gsw ) ;
   bsr = newtxt ;
   return ( (this->replace( bss, bsr, offset, casesen, all )) ) ;

}  //* End replace() *

bool gString::replace ( const char* srctxt, const char* newtxt, int32_t offset,
                        bool casesen, bool all )
{
   gString bss( this->gsw ) ;
   bss = srctxt ;
   gString bsr( this->gsw ) ;
   bsr = newtxt ;
   return ( (this->replace( bss, bsr, offset, casesen, all )) ) ;

}  //* End replace() *

bool gString::replace ( const char* srctxt, const wchar_t newtxt, int32_t offset,
                        bool casesen, bool all )
{
   gString bss( this->gsw ) ;
   bss = srctxt ;
   gString bsr( "%C", &newtxt ) ;
   return ( (this->replace( bss, bsr, offset, casesen, all )) ) ;

}  //* End replace() *

bool gString::replace ( const wchar_t srctxt, const wchar_t* newtxt, int32_t offset,
                        bool casesen, bool all )
{
   gString bss( "%C", &srctxt ) ;
   gString bsr( this->gsw ) ;
   bsr = newtxt ;
   return ( (this->replace( bss, bsr, offset, casesen, all )) ) ;

}  //* End replace() *

bool gString::replace ( const wchar_t srctxt, const char* newtxt, int32_t offset,
                        bool casesen, bool all )
{
   gString bss( "%C", &srctxt ) ;
   gString bsr( this->gsw ) ;
   bsr = newtxt ;
   return ( (this->replace( bss, bsr, offset, casesen, all )) ) ;

}  //* End replace() *

bool gString::replace ( const wchar_t srctxt, const wchar_t newtxt, int32_t offset,
                        bool casesen, bool all )
{
   gString bss( "%C", &srctxt ) ;
   gString bsr( "%C", &newtxt ) ;
   return ( (this->replace( bss, bsr, offset, casesen, all )) ) ;

}  //* End replace() *

//*************************
//*      shiftChars       *
//*************************
//********************************************************************************
//* Shift text data by the specified number of characters.                       *
//*                                                                              *
//* Input  : shiftCount: < ZERO: shift data to the left, discarding the          *
//*                              specified number of characters from the         *
//*                              beginning of the array                          *
//*                      > ZERO: shift data to the right, padding the vacated    *
//*                              positions on the left with 'padChar'            *
//*                      ==ZERO: do nothing                                      *
//*          padChar   : (optional, SPACE character, 0x20 by default)            *
//*                      when shifting data to the right, use this character     *
//*                      to fill the vacated character positions                 *
//*                                                                              *
//* Returns: number of characters in adjusted array                              *
//********************************************************************************

int32_t gString::shiftChars ( int32_t shiftCount, wchar_t padChar )
{
   if ( shiftCount > ZERO )
   {
      if ( shiftCount < (this->gsw - 1) )
      {
         int32_t sIndex = this->gsChars - 1,    // source index
                 tIndex = sIndex + shiftCount ; // target index
         this->gsChars += shiftCount ;
         //* If we will shift part of the data out on the right *
         if ( this->gsChars >= this->gsw  )
         {
            sIndex -= this->gsChars - this->gsw + 1 ;
            this->gsChars = this->gsw - 1 ;
            tIndex = this->gsChars - 1 ;
            this->gStr[tIndex--] = NULLCHAR ;
         }
         while( sIndex >= ZERO )
            this->gStr[tIndex--] = this->gStr[sIndex--] ;
         while ( tIndex >= ZERO )
            this->gStr[tIndex--] = padChar ;
         this->utf32_2_utf8 () ;       // update all status to match new data
      }
      else                             // shift everything out
         this->reset () ;
   }
   else if ( shiftCount < ZERO )
   {
      shiftCount = ~shiftCount + 1 ;
      if ( shiftCount < this->gsChars )
      {
         int32_t sIndex = shiftCount,     // source index
                 tIndex = ZERO ;          // target index
         for ( ; this->gStr[sIndex] != NULLCHAR ; tIndex++ )
            this->gStr[tIndex] = this->gStr[sIndex++] ;
         this->gStr[tIndex++] = NULLCHAR ;
         this->gsChars = tIndex ;
         this->utf32_2_utf8 () ;          // update all status to match new data
      }
      else                                // shift everything out
         this->reset () ;
   }
   return this->gsChars ;

}  //* End shiftChars() *

//*************************
//*       shiftCols       *
//*************************
//********************************************************************************
//* Shift text data by the specified number of display columns.                  *
//*                                                                              *
//* Input  : shiftCount: < ZERO: shift data to the left, discarding the          *
//*                              number of characters equivalent to the          *
//*                              specified number of display columns             *
//*                              NOTE: May discard one extra column if count     *
//*                              falls within a multi-column character.          *
//*                      > ZERO: shift data to the right, padding the vacated    *
//*                              positions on the left with 'padChar'.           *
//*                              NOTE: If the shift is very large, it may cause  *
//*                              some text to be shifted out on the right.       *
//*                      ==ZERO: do nothing                                      *
//*          padChar   : (optional, SPACE character, 0x20 by default)            *
//*                      when shifting data to the right, use this character     *
//*                      to fill the vacated column positions                    *
//*                      NOTE: Specify a one-column character ONLY as the        *
//*                      padding character. (multi-column characters ignored)    *
//* Returns: number of columns in adjusted array                                 *
//********************************************************************************

int32_t gString::shiftCols ( int32_t shiftCount, wchar_t padChar )
{
   if ( shiftCount > ZERO )
   {
      if ( (wcwidth ( padChar )) != 1 )   // test width of padding character
         padChar = SPACE ;

      //* Max possible display columns == columns used + characters unused *
      int32_t possibleCols = this->gsCols + ((this->gsw - 1) - this->gsChars) ;
      if ( shiftCount < possibleCols )
      {
         int32_t sIndex = this->gsChars - 1, // source index
                 tIndex = sIndex,            // target index
                 xIndex = ZERO,              // terminal index
                 xCols  = ZERO ;             // column counter
         if ( shiftCount < this->gsCols )
         {
            while ( xCols < shiftCount && this->gStr[xIndex] != NULLCHAR )
               xCols += this->cWid[xIndex++] ;
            xIndex += xCols - xIndex ;
         }
         else  // shift >= length of the array
            xIndex = shiftCount ;
         tIndex += xIndex ;
         this->gsChars += xIndex ;

         //* If we will shift part of the data out on the right.*
         //* Note that this should seldom, if ever happen.      *
         if ( this->gsChars > this->gsw )
         {
            sIndex -= this->gsChars - this->gsw + 1 ;
            this->gsChars = this->gsw ;
            tIndex = this->gsChars - 1 ;
            this->gStr[tIndex--] = NULLCHAR ;
         }
         while ( sIndex >= ZERO )
            this->gStr[tIndex--] = this->gStr[sIndex--] ;
         while ( tIndex >= ZERO )
            this->gStr[tIndex--] = padChar ;
         this->utf32_2_utf8 () ;          // update all status to match new data
      }
      else                                // shift everything out
         this->reset () ;
   }
   else if ( shiftCount < ZERO )
   {
      shiftCount = ~shiftCount + 1 ;
      if ( shiftCount < this->gsCols )
      {
         int32_t xCols  = ZERO,           // number of columns for discard chars
                 xIndex = ZERO,           // number of discard chars
                 tIndex = ZERO ;          // target index
         while ( xCols < shiftCount && this->gStr[xIndex] != NULLCHAR )
            xCols += this->cWid[xIndex++] ;
         for ( ; this->gStr[xIndex] != NULLCHAR ; tIndex++ )
            this->gStr[tIndex] = this->gStr[xIndex++] ;
         this->gStr[tIndex++] = NULLCHAR ;
         this->gsChars = tIndex ;
         this->utf32_2_utf8 () ;          // update all status to match new data
      }
      else        // shift everything out
         this->reset () ;
   }
   return this->gsCols ;

}  //* End shiftCols() *

//*************************
//*     rightJustify      *
//*************************
//********************************************************************************
//* Shift existing data to the right, so that the full width of the data is      *
//* equal to the specified number of columns.                                    *
//*                                                                              *
//* If the width of the existing data is greater-than-or-equal-to the specified  *
//* field width, then the data will not be modified.                             *
//*                                                                              *
//* Input  : fieldWidth: number of columns for the adjusted data array           *
//*          padChar   : (optional, SPACE character, 0x20 by default)            *
//*                      character with which to pad the data                    *
//*                      NOTE: Specify a one-column character ONLY as the        *
//*                      padding character. (multi-column characters ignored)    *
//* Returns: number of columns in adjusted array                                 *
//********************************************************************************

int32_t gString::rightJustify ( int32_t fieldWidth, wchar_t padChar )
{
   int32_t colShift = fieldWidth - this->gsCols ;

   if ( colShift > ZERO )
      this->shiftCols ( colShift, padChar ) ;

   return this->gsCols ;

}  //* End rightJustify() *

//*************************
//*        padCols        *
//*************************
//********************************************************************************
//* Add padding to the end of the existing data to equal the specified number    *
//* of columns.                                                                  *
//*                                                                              *
//* If 'centered' flag is set, divide the padding equally between the beginning  *
//* and end of the data. Note that if an odd number of columns is to be added,   *
//* then the odd column will be placed at the end of the data.                   *
//*                                                                              *
//* a) Padding will be added until either the specified number of columns is     *
//*    reached, OR until the array contains the maximum number of characters     *
//*    (storage limit).                                                          *
//* b) If 'fieldWidth' <= current width, then data will not be modified.         *
//*                                                                              *
//* Input  : fieldWidth: number of columns for the adjusted data array           *
//*          padChar   : (optional, SPACE character, 0x20 by default)            *
//*                      character with which to pad the data                    *
//*                      NOTE: Specify a one-column character ONLY as the        *
//*                      padding character. (multi-column characters ignored)    *
//*          centered  : (optional, 'false' by default)                          *
//*                      if 'false', all padding will be appended at the end     *
//*                                  of the existing data                        *
//*                      if 'true',  padding will be equally divided between     *
//*                                  the beginning and end of the existing data  *
//* Returns: number of columns in adjusted array                                 *
//********************************************************************************

int32_t gString::padCols ( int32_t fieldWidth, wchar_t padChar, bool centered )
{
   if ( fieldWidth > this->gsCols )
   {
      if ( (wcwidth ( padChar )) != 1 )   // test width of padding character
         padChar = SPACE ;

      int32_t chars2add = fieldWidth - this->gsCols,
              maxWidth = this->gsw - 2,   // buffer capacity
              insertChars = ZERO,         // number of chars/cols to insert at head
              appendChars,                // number of chars/cols to append
              trgIndex = this->gsChars - 2 ; // index character before null terminator

      //* Check for empty string. (prevent index out-of-range).*
      if ( trgIndex < ZERO )
         centered = false ;

      //* Prevent buffer overflow by limiting total *
      //* characters to size of the buffer minus 2. *
      if ( (this->gsChars + chars2add) > maxWidth )
         chars2add = maxWidth - this->gsChars ;
      appendChars = chars2add ;           // total padding characters

      if ( centered )
      {
         insertChars = chars2add / 2 ;
         appendChars /= 2 ;
         if ( (chars2add % 2) > ZERO )
            ++appendChars ;
         if ( insertChars > ZERO )
         {
            int32_t srcIndex = trgIndex ;    // index the char before null
            trgIndex += insertChars ;
            int32_t appendIndex = trgIndex ; // new last char before null
            while ( srcIndex >= ZERO )
               this->gStr[trgIndex--] = this->gStr[srcIndex--] ;
            while ( trgIndex >= ZERO )
               this->gStr[trgIndex--] = padChar ;
            trgIndex = appendIndex ;         // set new target index
         }
      }
      while ( (appendChars-- > ZERO) && (trgIndex < (this->gsw - 2)) )
         this->gStr[++trgIndex] = padChar ;
      this->gStr[++trgIndex] = NULLCHAR ;    // terminate the string
      this->gsChars = trgIndex + 1 ;         // new char count

      //* Convert wide data to UTF-8 data and recalculate statistics *
      this->utf32_2_utf8 () ;
   }
   return this->gsCols ;

}  //* End padCols() *

//*************************
//*         strip         *
//*************************
//********************************************************************************
//* Strip leading and/or trailing whitespace.                                    *
//* Recognizes both the ASCII space 0x20 and the CJK (two-column) space 0x3000   *
//* as well as TAB (0x09), VTAB (0x0B), Newline (0x0A), Carriage-return (0x0D),  *
//* and Form-feed (0x0C).                                                        *
//*                                                                              *
//* Input  : leading  : (optional, 'true' by default)                            *
//*                     If 'true',  remove whitespace from beginning of string.  *
//*                     If 'false', leading whitespace is unchanged.             *
//*          trailing : (optional, 'true' by default)                            *
//*                     If 'true',  remove whitespace from end of string.        *
//*                     If 'false', trailing whitespace is unchanged.            *
//* Returns: number of characters in modified string (incl. NULL terminator)     *
//********************************************************************************

int32_t gString::strip ( bool leading, bool trailing )
{
   if ( this->gsChars > 1 )
   {
      int32_t index ;

      if ( leading )
      {
         for ( index = ZERO ; 
               (this->gStr[index] == SPACE) || (this->gStr[index] == DSPACE)  ||
               (this->gStr[index] == TAB)   || (this->gStr[index] == NEWLINE) ||
               (this->gStr[index] == VTAB)  || (this->gStr[index] == CR)      ||
               (this->gStr[index] == FF) ;
               ++index ) ;
         this->shiftChars( -(index) ) ;
      }
      if ( trailing )
      {
         for ( index = this->gsChars - 2 ;   // reference last char before nullchar 
               (this->gStr[index] == SPACE) || (this->gStr[index] == DSPACE)  ||
               (this->gStr[index] == TAB)   || (this->gStr[index] == NEWLINE) ||
               (this->gStr[index] == VTAB)  || (this->gStr[index] == CR)      ||
               (this->gStr[index] == FF) ;
               --index ) ;
         this->limitChars( index + 1 ) ;
      }
   }
   return this->gsChars ;

}  //* End strip() *

//*************************
//*      limitChars       *
//*************************
//********************************************************************************
//* Truncate the existing data to no more than charCount display characters.     *
//* Insert a null terminator after the specified number of characters            *
//*                                                                              *
//* Input  : maximum number of characters to be retained.                        *
//*          (not including NULL) Range: 1 to storage capacity: wAlloc() - 1     *
//*                                                                              *
//* Returns: number of characters in tne adjusted data (including nullchar)      *
//********************************************************************************

int32_t gString::limitChars ( int32_t charCount )
{
   if ( charCount < ZERO || charCount >= this->gsw ) // range check
      charCount = this->gsw - 1 ;
   this->gStr[charCount] = NULLCHAR ;  // truncate the wchar_t array
   //* Recount the wide characters *
   for ( this->gsChars = ZERO ; this->gStr[this->gsChars] != NULLCHAR ; ++this->gsChars ) ;
   ++this->gsChars ;                   // count the NULLCHAR
   this->utf32_2_utf8 () ;             // reconvert from wchar_t to UTF-8
   return this->gsChars ;

}  //* End limitChars() *

//*************************
//*       limitCols       *
//*************************
//********************************************************************************
//* Truncate the existing data to no more than colCount display columns.         *
//* Insert a null terminator after the number of characters required to fill     *
//* the specified number of display columns.                                     *
//*                                                                              *
//* Note that some characters such as '\n' and '\0' have zero width.             *
//*                                                                              *
//* Input  : maximum number of display columns available for data display        *
//*          Range: 1 to storage capacity.                                       *
//*                                                                              *
//* Returns: number of columns of display data retained                          *
//********************************************************************************

int32_t gString::limitCols ( int32_t colCount )
{
   // Programmer's Note: As a design decision, the upper limit is set to 
   // the size of the character buffer times two(2) columns per character.
   if ( colCount < ZERO || colCount > (this->gsw * 2) )
      colCount = (this->gsw * 2) ;
   this->columnCount ( colCount ) ;
   return this->gsCols ;

}  //* End limitCols() *

//*************************
//*      textReverse      *
//*************************
//********************************************************************************
//* Reverse the order of characters in the text string.                          *
//* If RTL text data displayed by your application is not formatted as desired,  *
//* then this method may be used to reverse the character order before writing   *
//* to the display. This is useful for manipulating both RTL (Right-To-Left)     *
//* language text and mixed RTL/LTR text.                                        *
//*   Example:  "?םיירהצ תחוראל ןכומ התא םאה"   (displayed here correctly)       *
//*   Becomes:  "האם אתה מוכן לארוחת צהריים?"   (inverted character sequence)    *
//*                                                                              *
//* Although modern web browsers (Firefox, Opera, Chromium, etc.) _usually_      *
//* handle RTL text correctly, other applications often do not.                  *
//* This is expecially true of terminal emulator software. GNOME Terminal and    *
//* KDE Konsole for instance, try to be "helpful" by automatically re-ordering   *
//* the text and/or moving terminating punctuation to the opposite end of the    *
//* string. Unfortunately these applications really do not understand what       *
//* they are doing. They recognize when text belongs to an RTL language, but     *
//* cannot understand whether it is in the proper order, which means that the    *
//* text _may be_ displayed incorrectly.                                         *
//*                                                                              *
//* Input  : punct : (optional, 'false' by default)                              *
//*                  if 'false', invert entire string                            *
//*                  if 'true' AND if a punctuation mark is seen at either end   *
//*                     of the string, invert everything except the punctuation  *
//*                     mark(s). typically one of the following:                 *
//*                     '.' ',' '?' '!' ';' ':' but see note.                    *
//*          para  : (optional, 'false' by default)                              *
//*                  if 'false', invert data as a single character stream        *
//*                  if 'true',  invert data separately for each logical line    *
//*                              (line data are separated by newlines ('\n')     *
//*          rjust : (optional, 'false' by default)                              *
//*                  if 'false', do not insert right-justification padding       *
//*                  if 'true',  insert padding for each logical line to         *
//*                              right-justify the data                          *
//*                              (used to right-justify LTR output)              *
//*                              Note that right-justification is performed      *
//*                              ONLY if the 'para' parameter is true.           *
//*                              Otherwise, 'rjust' will be ignored.             *
//*                                                                              *
//* Returns: number of wchar_t characters in gString object                      *
//*          (if return value >= storage limit, data may have been truncated)    *
//********************************************************************************
//* Programmer's Note: The 'ispunct()' function is locale specific, so if a      *
//* Spanish question ("¿Estás lista para el almuerzo?") is used with the 'C'     *
//* locale or another non-Spanish locale, then the inverted question mark '¿'    *
//* may not be recognized as punctuation.                                        *
//* This problem may occur with any inverted, mirrored or non-ASCII punctuation  *
//* character. See Unicode 'Other Punctuation'.                                  *
//*           <https://www.compart.com/en/unicode/category/Po>                   *
//* Rather than rely on the locale of a given application, we have created an    *
//* array of the most common punctuation characters used in modern languages.    *
//* If your application uses a punctuation character not in the list, please     *
//* send us a note and we will add it to the list.                               *
//*                                                                              *
//* Programmer's Note: The various combinations of the 'para' and 'rjust'        *
//* flags, coupled with LTR/RTL output streams provides flexibility in data      *
//* formatting.                                                                  *
//*                                                                              *
//* 'para'  'rjust'  LTR out  RTL out  output format                             *
//* ------  -------  -------  -------  ---------------------------------------   *
//*   -        -        x        -     reversed as a single string, LTR output   *
//*   -        -        -        x     reversed as a single string, RTL output   *
//*   x        -        x        -     reversed by line, LTR output              *
//*   -        x        x        -     'rjust' is ignored when 'para' == false   *
//*   x        x        x        -     reversed by line, justified, LTR output   *
//*   x        -        -        x     reversed by line, RTL output              *
//*   -        x        -        x     'rjust' is ignored when 'para' == false   *
//*   x        x        -        x     reversed by line, justified, RTL output   *
//********************************************************************************

int32_t gString::textReverse ( bool punct, bool para, bool rjust )
{
   //* Buffer must contain at least two characters PLUS the null *
   //* terminator, otherwise there is nothing to be reversed.    *
   if ( this->gsChars > 2 )
   {
      if ( ! para )
         return ( (this->textReverse_all ( punct )) ) ;

      gString bst( this->gsw ),   // substring concatenation
              bsr( this->gsw ) ;  // substring reversals
      int32_t windx = ZERO,       // index into gStr[]
              cindx = ZERO,       // index into column-count array
              width = ZERO,       // max width per line
              linewidth ;         // temp column counter

      //* If specified, calculate the padding for right justification *
      if ( rjust )
      {
         do
         {
            linewidth = ZERO ;
            while ( (this->gStr[cindx] != L'\n') && (this->gStr[cindx] != L'\0') )
               linewidth += this->cWid[cindx++] ;
            if ( linewidth > width )
               width = linewidth ;
         }
         while ( ++cindx < (this->gsChars - 1) ) ;
      }

      //* Parse the paragraph into its individual logical *
      //* lines and reverse each line independently.      *
      do
      {
         //* Capture the substring (excluding newline) *
         while ( this->gStr[windx] != L'\n' && this->gStr[windx] != L'\0' )
            bsr.append( this->gStr[windx++] ) ;

         if ( bsr.gschars() > 1 )         // if non-empty substring
         {
            bsr.textReverse( punct ) ;    // reverse substring text

            //* If specified, add padding to right-justify the text *
            if ( rjust )
            {
               while ( (bsr.gscols()) < width )
                  bsr.insert( L' ' ) ;
            }
            bst.append( bsr.gstr() ) ;    // append substring to processed data
            if ( this->gStr[windx++] == L'\n' ) // end of logical text line
               bst.append( L'\n' ) ;

            //* If buffer is full, we're done. Source data may be truncated.*
            if ( (bst.gschars()) >= this->gsw )
               break ;
         }
         bsr.clear() ;                    // discard the completed substring
      }
      while ( windx < (this->gsChars - 1) ) ;

      //* Replace existing text with the re-formatted text *
      bst.copy( this->gStr, this->gsw ) ;
      this->gsChars = bst.gschars() ;
      this->utf32_2_utf8 () ;
   }

   return ( this->gsChars ) ;

}  //* End textReverse() *

//*************************
//*    formatParagraph    *
//*************************
//********************************************************************************
//* Reformat the text so that when written to the display, it will fit within    *
//* the specified rectangular area.                                              *
//*                                                                              *
//*  1) Remove all newlines from the text.                                       *
//*  2) Perform word wrap to limit width of each row of the paragraph.           *
//*  3) If necessary, truncate the text to limit the height of the paragraph.    *
//*     (but see the 'trunc' and 'truncIndex' parameters)                        *
//*                                                                              *
//* Input  : maxRows  : maximum rows for message (>= 1)                          *
//*          maxCols  : maximum columns on any message row (>= 4)                *
//*          trunc    : (optional, 'true' by default)                            *
//*                     if 'true',  truncate the data if necessary to ensure     *
//*                                 that the data do not extend beyond the       *
//*                                 specified target area                        *
//*                     if 'false', format the entire source text array, even    *
//*                                 if doing so requires violating the specified *
//*                                 height of the target area (maxRows).         *
//*                                 (see also 'truncIndex' parameter)            *
//*          hyphenbrk: (optional, 'false' by default)                           *
//*                     if 'false', automatic line breaks occur at space ' '     *
//*                                 characters only.                             *
//*                                 Special Case: the following CJK ideographs   *
//*                                 are also recognized as whitespace            *
//*                                 (see documentation for details)              *
//*                                  '、' comma U+3001 and                        *
//*                                  '。' full stop U+3002                        *
//*                     if 'true',  in addition to the space ' ' characters,     *
//*                                 enable line break at:                        *
//*                                  ASCII hyphen    '-' (2dh)                   *
//*                                  Unicode &mdash; '—' (2014h)                 *
//*                                  Unicode &ndash; '–' (2013h)                 *
//*                                  Unicode &minus; '−' (2212h)                 *
//*                                  Unicode &shy;       (00ADh) (soft hyphen)   *
//*          truncIndex: (optional, null pointer by default)                     *
//*                      -- referenced _only_ when the 'trunc' flag is reset     *
//*                      if specified, points to a variable to receive the       *
//*                      index at which the data _would_have_been_ truncted to   *
//*                      fit the specified height of the target area if the      *
//*                      'trunc' flag had been set.                              *
//*                      a) A positive value indicates that the data have not    *
//*                         been truncated, AND that the data extend beyond      *
//*                         the specified target area.                           *
//*                      b) A negative value indicates that it was not necessary *
//*                         to truncate the data i.e. the data fit entirely      *
//*                         within the target area.                              *
//*                                                                              *
//* Returns: number of text rows in the formatted output                         *
//********************************************************************************
//* Notes on automatic word wrap:                                                *
//* -----------------------------                                                *
//* See documentation.                                                           *
//********************************************************************************

int32_t gString::formatParagraph ( int32_t maxRows, int32_t maxCols, bool trunc, 
                        bool hyphenbrk, int32_t *truncIndex )
{
   const wchar_t CJKSPACE  = 0x3000 ;  // CJK space       U+3000 (two-column character)
   const wchar_t CJKCOMMA  = 0x3001 ;  // CJK comma       U+3001 (two-column character)
   const wchar_t CJKSTOP   = 0x3002 ;  // CJK full stop   U+3002 (two-column character)
   const wchar_t DASH      = L'-' ;    // ASCII hyphen/minus U+002D
   const wchar_t uMINUS    = 0x2212 ;  // Unicode &minus; U+2012
   const wchar_t nDASH     = 0x2013 ;  // Unicode &ndash; U+2013
   const wchar_t mDASH     = 0x2014 ;  // Unicode &mdash; U+2014
   const wchar_t sDASH     = 0x00AD ;  // Unicode &shy;   U+00AD (soft hyphen)

   wchar_t* wbuff = new wchar_t[this->gsw + 2] ; // work buffer
   wchar_t wch ;                          // character under test
   int32_t srcChars,                      // number of characters in source text (not incl. null)
           newRows = 1,                   // rows of text in re-formatted data
           token  = ZERO,                 // number of columns in token being scanned
           colacc = ZERO,                 // column accumulator
           sindx  = ZERO,                 // source data index
           tindx  = ZERO,                 // target buffer index
           sbindx = ZERO,                 // source break index
           tbindx = ZERO ;                // temporary index

   //* Range check dimensions *
   if ( maxRows < 1 )   { maxRows = 1 ; } // (note minimun necessary height)
   if ( maxCols < 4 )   { maxCols = 4 ; } // (note arbitrary minimum width)

   if ( truncIndex != NULL )              // if provided, initialize truncation index
      *truncIndex = -1 ;

   //* Remove all newline characters in the text *
   while ( (sindx = this->find( NEWLINE, sindx )) >= ZERO )
      this->erase( sindx, 1 ) ;
   srcChars = this->gsChars - 1 ;         // number of source characters not incl. nullchar

   //* Populate each output row with no more than maxCols columns *
   for ( sindx = ZERO ; (sindx < srcChars) && (tindx < this->gsw) ; ++sindx )
   {
      wch = this->gStr[sindx] ;

      //* If end-of-word (potential word break point), remember its indices *
      if ( wch == TAB ) { wch = SPACE ; } // filter the nasty tab character
      if ( (wch == SPACE) || (wch == CJKSPACE) || (wch == CJKCOMMA) || (wch == CJKSTOP) ||
           (hyphenbrk && 
            ((wch == DASH) || (wch == mDASH) || (wch == nDASH) || 
             (wch == uMINUS) || (wch == sDASH))) )
      { sbindx = sindx ; tbindx = tindx ; token = ZERO ; }

      token  += this->cWid[sindx] ;       // add width of current char to token accumulator
      colacc += this->cWid[sindx] ;       // add width of current char to row accumulator

      //* If space is available on the row, write source to target *
      //* (At least one column will remain available for potential *
      //*  word wrap on next character.)                           *
      if ( colacc < maxCols )
         wbuff[tindx++] = wch ;

      //* End of row reached. Execute word wrap. *
      else
      {
         //* If current source character would overfill *
         //* the row, push it to the next row.          *
         // Programmer's Note: This will happen only when the source data 
         // include multi-column characters.
         if ( colacc > maxCols )
         {
            token -= this->cWid[sindx] ; // subtract width of current source character
            --sindx ;
            --tindx ;
         }

         //* If the distance to the most recent potential break point *
         //* index is "small," use that index for the line break.     *
         if ( token < (maxCols / 2) )
         {
            tindx = tbindx ;
            wbuff[tindx++] = this->gStr[sindx = sbindx] ;
            wbuff[tindx++] = NEWLINE ;
         }

         //* Otherwise, the current token is "long" compared to the   *
         //* width of the target area, so break the line at the       *
         //* current index. (see hyphenation notes above)             *
         else
         {
            //* Insert a hyphen to indicate the break _unless_ we are *
            //* breaking within a token with multi-column characters. *
            if ( this->cWid[sindx - 1] == 1 )
               wbuff[tindx++] = nDASH ;

            wbuff[tindx++] = NEWLINE ;
            --sindx ;
            sbindx = sindx ;
            tbindx = tindx ;
         }
         if ( ++newRows > maxRows )       // max height reached
         {
            if ( trunc )                  // discard the newline and terminate the loop
            { --tindx ; break ; }
            else if ( (truncIndex != NULL) && (*truncIndex < ZERO) )
               *truncIndex = tindx - 1 ;  // indicate where we _would have_ truncated
         }
         token = ZERO ;
         colacc = ZERO ;
      }
   }     // for(;;)

   if ( wbuff[tindx - 1] == NEWLINE )     // If a trailing newline, remove it.
   { --tindx ; --newRows ; }
   wbuff[tindx] = NULLCHAR ;              // terminate the string
   this->operator= ( wbuff ) ;            // write the new data to our members

   delete [] wbuff ;                      // release the dynamic allocation

   return newRows ;

}  //* End formatParagraph() *

//*************************
//*        clear          *
//*************************
//********************************************************************************
//* Reset contents to an empty string i.e. "". The data will consist of a        *
//* single, NULLCHAR character. The character and byte counts are set to         *
//* 1 (one), and the column count is zero.                                       *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void gString::clear ( void )
{

   this->reset () ;

}  //* End clear() *

//*************************
//*        reAlloc        *
//*************************
//********************************************************************************
//* Increase or decrease the size of storage buffer by the specified number      *
//* of gsALLOCMIN increments.                                                    *
//* The buffer is sized in multiples of gsALLOCMIN in the range:                 *
//*                 gsALLOCMIN <= size <= gsALLOCMAX                             *
//*                 1024 chars            1,024,000 chars                        *
//*                                                                              *
//* Input  : steps : number of steps to increase or decrease in buffer size.     *
//*                  Range: +/-999                                               *
//*                  If value out of range, will be set to upper/lower limit.    *
//*          clear : (optional, 'false' by default)                              *
//*                  if 'false', if possible, retain the existing contents       *
//*                              of the buffer. (Note that if buffer size is     *
//*                              reduced, some data may be truncated.)           *
//*                  if 'true',  erase any existing text in the buffer           *
//*                              (same as 'clear' method, above)                 *
//*                                                                              *
//* Returns: new buffer capacity expressed as the number of wchar_t characters   *
//********************************************************************************

int32_t gString::reAlloc ( int32_t steps, bool clear )
{
   wchar_t* wbuff = NULL ;
   int32_t  newSize = this->gsw + (steps * gsALLOCMIN) ;
   if ( newSize < gsALLOCMIN ) { newSize = gsALLOCMIN ; }   // range check
   else if ( newSize > gsALLOCMAX ) { newSize = gsALLOCMAX ; }

   if ( ! clear )
   {
      wbuff = new wchar_t[this->gsw] ;    // allocate temp storage
      this->copy ( wbuff, this->gsw ) ;   // copy data to temp buffer
   }

   this->reallocate ( newSize ) ;         // set new buffer size

   if ( ! clear )                         // if data saved, restore it
      this->utf32_2_utf8 ( wbuff ) ;

   if ( wbuff != NULL )                   // release the dynamic allocation
      delete [] wbuff ;

   return this->gsw ;

}  //* End reAlloc() *


//********************************************************************************
//**:G:                        Analysis Group                                   **
//********************************************************************************

//*************************
//*     operator==        *
//*************************
//********************************************************************************
//* Comparison operator: Compares the text content of two gString objects.       *
//* The comparison is performed against the objects' wchar_t character arrays,   *
//* and is case-sensitive.                                                       *
//*                                                                              *
//* Input  : (by reference) gString object to be compared                        *
//*                                                                              *
//* Returns: 'true' if the strings are identical, else 'false'                   *
//********************************************************************************

bool gString::operator== ( const gString& bs2 ) const
{

   int32_t diff = this->compare ( bs2.gstr() ) ;
   return ( bool(diff == ZERO) ) ;

}  //* End operator==() *

//*************************
//*     operator!=        *
//*************************
//********************************************************************************
//* Comparison operator: Compares the text content of two gString objects.       *
//* The comparison is performed against the objects' wchar_t character arrays,   *
//* and is case-sensitive.                                                       *
//*                                                                              *
//* Input  : (by reference) gString object to be compared                        *
//*                                                                              *
//* Returns: 'true' if the strings differ, else 'false'                          *
//********************************************************************************

bool gString::operator!= ( const gString& bs2 ) const
{

   int32_t diff = this->compare ( bs2.gstr() ) ;
   return ( bool(diff != ZERO) ) ;

}  //* End operator!=() *

//*************************
//*        compare        *
//*************************
//********************************************************************************
//* Compares the text content of the gString object with the specified text.     *
//* The comparison is performed against the source and target wchar_t character  *
//* arrays. The relationship between upper-case and lower-case characters is     *
//* locale dependent.                                                            *
//*                                                                              *
//* Input  : uStr     : (UTF-8 string) to be compared                            *
//*             OR                                                               *
//*          wStr     : (wchar_t string) to be compared                          *
//*             OR                                                               *
//*          gs       : (gString object by reference) data to be compared        *
//*          casesen  : (optional, 'true' by default)                            *
//*                     if 'true' perform case-sensitive comparison              *
//*                     if 'false' perform case-insensitive comparison           *
//*          length   : (optional, -1 by default. i.e. compare to end)           *
//*                     maximum number of characters to compare                  *
//*                     (applies to 'uStr' and 'wStr' source parameters only)    *
//*          offset   : (optional, ZERO by default)                              *
//*                     If specified, equals the character offset into the       *
//*                     gString character array at which to begin comparison.    *
//*                     (applies to 'uStr' and 'wStr' source parameters only)    *
//*                                                                              *
//* Returns: using the rules of the 'wcsncmp' (or 'wcsncasecmp') library         *
//*          function (see string.h):                                            *
//*     ZERO, text data are identical                                            *
//*   > ZERO, first differing char of stored data is numerically larger.         *
//*   < ZERO, first differing char of stored data is numerically smaller.        *
//********************************************************************************

int32_t gString::compare ( const wchar_t* wStr, bool casesen, int32_t length, int32_t offset ) const
{
   int32_t status = ZERO ;                // return value
   if ( length < ZERO )                   // if default, set to max
      length = gsALLOCMAX ;

   if ( casesen )
      status = wcsncmp ( &this->gStr[offset], wStr, length ) ;
   else
      status = wcsncasecmp ( &this->gStr[offset], wStr, length ) ;

   if ( status < ZERO )    // normalize return value (may be unnecessary)
      status = -1 ;
   else if ( status > ZERO )
      status = 1 ;
   return status ;

}  //* End compare() *
int32_t gString::compare ( const char* uStr, bool casesen, int32_t length, int32_t offset ) const
{
   gString gs( uStr ) ;
   return ( (this->compare ( gs.gstr(), casesen, length, offset )) ) ;
}  //* End compare() *
int32_t gString::compare ( const uint8_t* uStr, bool casesen, int32_t length, int32_t offset ) const
{
   return ( (this->compare ( (const char*)(uStr), casesen, length, offset )) ) ;
}  //* End compare() *
int32_t gString::compare ( const gString& gStr, bool casesen ) const
{
   return ( (this->compare ( gStr.gstr(), casesen )) ) ;
}  //* End compare() *

//*************************
//*       compcoll        *
//*************************
//********************************************************************************
//* Compares the text content of the gString object with the specified text.     *
//* The comparison is performed according to the active "locale" within the      *
//* application. This comparison is known as the "collation order" or            *
//* "dictionary order" comparison where lowercase letters are considered as      *
//* less than their uppercase equivalents. Characters with diacritical markers   *
//* and unitary character sequences (e.g. Spanish 'll') will also be sorted      *
//* according to the rules of the active locale.                                 *
//*                                                                              *
//* a) If the application has set the locale, the rules of that locale will be   *
//*    used for the comparison.                                                  *
//* b) If the application has not specified the locale, then the so-called       *
//*    POSIX C/C++ pseudo-locale will be used.                                   *
//* c) Note that for some languages, Chinese and Japanese for instance, there    *
//*    is no "dictionary" order for the characters. Instead, dictionaries rely   *
//*    on sound markers, e.g. pinyin transliteration, stroke count, or other     *
//*    criteria. The 'compcoll' is not effective for these languages.            *
//* d) Note that if the source data contain mixed languages, the 'wcscoll'       *
//*    function will be ineffective and misleading.                              *
//* e) Technical Note: The 'wcscoll' librarary function is also _considerably_   *
//*    slower than the 'wcscmp' (numeric sort) function because it scans the     *
//*    data multiple times.                                                      *
//*                                                                              *
//* Note: Regardless of source data format, this method uses the C-library       *
//*       'wcscoll' function exclusively to perform the comparison.              *
//*                                                                              *
//* Input  : wStr     : (UTF-32 string) to be compared                           *
//*             OR                                                               *
//*          uStr     : (UTF-8 string) to be compared                            *
//*             OR                                                               *
//*          bs       : (gString object by reference) data to be compared        *
//*                                                                              *
//* Returns: using the rules of the 'wcscoll' library function (see wchar.h):    *
//*     ZERO, text data are identical                                            *
//*   > ZERO, first differing char of stored data is larger.                     *
//*   < ZERO, first differing char of stored data is smaller.                    *
//********************************************************************************

int32_t gString::compcoll ( const wchar_t* wStr ) const
{
   int32_t status = ZERO ;                // return value

   status = wcscoll ( this->gStr, wStr ) ;

   if ( status < ZERO )    // normalize return value (may be unnecessary)
      status = -1 ;
   else if ( status > ZERO )
      status = 1 ;
   return status ;

}  //* End compcoll() *
int32_t gString::compcoll ( const char* uStr ) const
{
   gString bs( uStr ) ;
   return ( (this->compcoll ( bs.gstr() )) ) ;
}  //* End compcoll() *
int32_t gString::compcoll ( const gString& bs ) const
{
   return ( (this->compcoll ( bs.gstr() )) ) ;
}  //* End compcoll() *

//*************************
//*         find          *
//*************************
//********************************************************************************
//* Scan the data for a matching substring and if found, return the index at     *
//* which the first substring match begins.                                      *
//*                                                                              *
//* Actual comparison is always performed against the 'wide' character array     *
//* using wcsncasecmp (or wcsncmp). Null terminator character IS NOT included    *
//* in the comparison. These comparisons are not locale dependent.               *
//*                                                                              *
//* Input  : src    : source data to be matched, one of the following:           *
//*                   -- pointer to a UTF-8 string                               *
//*                   -- pointer to a wchar_t string                             *
//*                   -- a gString object containing the source (by reference)   *
//*                   -- a single, wchar_t character                             *
//*          offset : (optional, ZERO by default)                                *
//*                   character index at which to begin search                   *
//*                   -- if out-of-range, then same as if not specified          *
//*          casesen: (optional, 'false' by default)                             *
//*                   if 'false', then scan IS NOT case sensitive                *
//*                   if 'true, then scan IS case sensitive                      *
//*          maxcmp : (optional, (-1) by default)                                *
//*                   -- if not specified, then scan for a match of all          *
//*                      characters in 'src', beginning at 'offset'              *
//*                      (nullchar terminator is not included in search)         *
//*                   -- if specified, then scan for a match of only the first   *
//*                      'maxcmp' characters of 'src'                            *
//*                   -- if out-of-range, then same as if not specified          *
//* Returns: index of matching substring or (-1) if no match found               *
//*          Note: This is the wchar_t character index, NOT a byte index         *
//********************************************************************************
//* NOTE:                                                                        *
//* To find second or subsequent match:                                          *
//* gString gs( L"The quick brown fox is quicker than a turkey" ) ;              *
//* int32_t index = gs.find( L"quick" ) ;        // first occurance              *
//*         index = gs.find( L"quick", index ) ; // second occurance             *
//*                                                                              *
//* NOTE:                                                                        *
//* Both 'offset' and 'maxcmp' are range-checked before beginning the scan,      *
//* and if out-of-range, are set to default values.                              *
//*                                                                              *
//********************************************************************************

int32_t gString::find ( const gString& src, int32_t offset, bool casesen, int32_t maxcmp ) const
{
   const wchar_t* srcptr = src.gstr() ;   // pointer to source data
   wchar_t first = *srcptr,   // first character in source (as received && opposite case)
           firstalt = (iswlower ( first )) ? towupper ( first ) : tolower( first ) ;
   int32_t tcnt = this->gsChars - 1,      // index of target nullchar
           scnt = src.gsChars - 1,        // index of source nullchar
           index = -1 ;                   // return value

   //* Range check the offset *
   if ( offset < ZERO )       // if under-range, set to ZERO
      offset = ZERO ;
   else if ( offset > tcnt )  // if over-range, reference the NULLCHAR
      offset = tcnt ;         // (loop will not execute)

   //* Number of source characters to be matched.*
   if ( maxcmp <= ZERO || maxcmp >= scnt )
      maxcmp = scnt ;

   for ( int32_t ti = offset ; ti < tcnt ; ti++ )
   {
      //* Match the first source character, *
      //* then perform string comparison.   *
      if ( this->gStr[ti] == first || this->gStr[ti] == firstalt )
      {
         if ( casesen == false )          // case-insensitive comparison
         {
            if ( ((wcsncasecmp ( &this->gStr[ti], srcptr, maxcmp)) == ZERO) )
            { index = ti ; break ; }
         }
         else                             // case-sensitive comparison
         {
            if ( ((wcsncmp ( &this->gStr[ti], srcptr, maxcmp)) == ZERO) )
            { index = ti ;  break ; }
         }
      }
   }
   return index ;

}  //* End find() *

int32_t gString::find ( const char* src, int32_t offset, bool casesen, int32_t maxcmp ) const
{
   gString bs( this->gsw ) ;
   bs = src ;
   return ( (this->find( bs, offset, casesen, maxcmp )) ) ;
}  //* End find() *
int32_t gString::find ( const uint8_t* src, int32_t offset, bool casesen, int32_t maxcmp ) const
{
   return ( (this->find( (const char*)(src), offset, casesen, maxcmp )) ) ;
}  //* End find() *
int32_t gString::find ( const wchar_t* src, int32_t offset, bool casesen, int32_t maxcmp ) const
{
   gString bs( this->gsw ) ;
   bs = src ;
   return ( (this->find( bs, offset, casesen, maxcmp )) ) ;
}  //* End find() *
int32_t gString::find ( wchar_t src, int32_t offset, bool casesen, int32_t maxcmp ) const
{  // Note: this is inefficient, but consolidates the code. (note also that 'maxcmp' is ignored)
   wchar_t wsrc[2] = { src, L'\0' } ;
   return ( (this->find( wsrc, offset, casesen, 1 )) ) ;
}  //* End find() *

//********************************************************************************
//* Scan the data for the last occurance of the matching substring and if        *
//* found, return the index at which the substring match occurs.                 *
//*                                                                              *
//* Actual comparison is always performed against the 'wide' character array     *
//* using wcsncasecmp (or wcsncmp). Null terminator character IS NOT included    *
//* in the comparison. These comparisons are not locale dependent.               *
//*                                                                              *
//* Input  : src    : source data to be matched, one of the following:           *
//*                   -- pointer to a wchar_t string                             *
//*                   -- pointer to a UTF-8 string                               *
//*                   -- a single, wchar_t character                             *
//*                   -- a gString object containing the source (by reference)   *
//*          casesen: (optional, 'false' by default)                             *
//*                   if 'false', then scan IS NOT case sensitive                *
//*                   if 'true, then scan IS case sensitive                      *
//* Returns: index of last matching substring or (-1) if no match found          *
//*          Note: This is the wchar_t character index, NOT a byte index         *
//********************************************************************************

int32_t gString::findlast ( const gString& src, bool casesen ) const
{
   int32_t lastIndex = this->gsChars - 2, // reference char before nullchar
           off = -1,                      // character offset
           index = -1 ;                   // return value


   while ( (off = this->find( src, ++off, casesen )) >= ZERO )
   {
      if ( (index = off) >= lastIndex )  // match of last char, stop scan
         break ;
   }
   return index ;

}  //* End findlast() *
int32_t gString::findlast ( const wchar_t* src, bool casesen ) const
{
   gString bs( this->gsw ) ;
   bs = src ;
   return ( (this->findlast ( bs, casesen )) ) ;
}  //* End findlast() *
int32_t gString::findlast ( const char* src, bool casesen ) const
{
   gString bs( this->gsw ) ;
   bs = src ;
   return ( (this->findlast ( bs, casesen )) ) ;
}  //* End findlast() *
int32_t gString::findlast ( const uint8_t* src, bool casesen ) const
{
   return ( (this->findlast( (const char*)(src), casesen )) ) ;
}  //* End findlast() *
int32_t gString::findlast ( const wchar_t src, bool casesen ) const
{
   wchar_t wsrc[2] = { src, L'\0' } ;
   return ( (this->findlast( wsrc, casesen )) ) ;
}  //* End findlast() *

//*************************
//*         after         *
//*************************
//********************************************************************************
//* This method is very similar to the 'find()' method above, but instead of     *
//* returning the index to the beginning of the substring, returns the index     *
//* of the character which FOLLOWS the substring.                                *
//*                                                                              *
//* Input  : src    : source data to be matched, one of the following:           *
//*                   -- pointer to a UTF-8 string                               *
//*                   -- pointer to a wchar_t string                             *
//*                   -- a single, wchar_t character                             *
//*                   -- a gString object containing the source (by reference)   *
//*          offset : (optional, ZERO by default)                                *
//*                   character index at which to begin search                   *
//*                   -- if out-of-range, then same as if not specified          *
//*          casesen: (optional, 'false' by default)                             *
//*                   if 'false', then scan IS NOT case sensitive                *
//*                   if 'true, then scan IS case sensitive                      *
//* Returns: index of the character which follows the matching substring         *
//*            or (-1) if no match found                                         *
//*          Note: This is the wchar_t character index, NOT a byte index.        *
//*          Note: Index will never reference data beyond the NULL terminator.   *
//********************************************************************************

int32_t gString::after ( const gString& src, int32_t offset, bool casesen ) const
{
   int32_t index = this->find ( src, offset, casesen ) ;
   if ( index >= ZERO )
      index += src.gschars() - 1 ;
   return index ;

}  //* End after() *
int32_t gString::after ( const char* src, int32_t offset, bool casesen ) const
{
   gString bs( this->gsw ) ;
   bs = src ;
   return ( (this->after( bs, offset, casesen )) ) ;
}  //* End after() *
int32_t gString::after ( const uint8_t* src, int32_t offset, bool casesen ) const
{
   return ( (this->after( (const char*)(src), offset, casesen )) ) ;
}  //* End after() *
int32_t gString::after ( const wchar_t* src, int32_t offset, bool casesen ) const
{
   gString bs( this->gsw ) ;
   bs = src ;
   return ( (this->after( bs, offset, casesen )) ) ;
}  //* End after() *
int32_t gString::after ( const wchar_t src, int32_t offset, bool casesen ) const
{
   gString bs( "%C", &src ) ;
   return ( (this->after( bs, offset, casesen )) ) ;
}  //* End after() *

//*************************
//*         findr         *
//*************************
//********************************************************************************
//* Scan the data beginning at the specified offset and moving toward the head   *
//* of the string (toward offset zero). Locate the matching substring and if     *
//* found, return the index of the first match.                                  *
//*                                                                              *
//* Actual comparison is always performed against the 'wide' character array     *
//* using wcsncasecmp (or wcsncmp). Null terminator character IS NOT included    *
//* in the comparison. These comparisons are not locale dependent.               *
//*                                                                              *
//* Input  : src    : source data to be matched, one of the following:           *
//*                   -- pointer to a UTF-8 string                               *
//*                   -- pointer to a wchar_t string                             *
//*                   -- a single, wchar_t character (by reference)              *
//*                   -- a gString object containing the source (by reference)   *
//*          offset : (optional, -1 by default, indicating index of null         *
//*                    terminator minus one)                                     *
//*                   character index at which to begin search                   *
//*                   -- if out-of-range, then same as if not specified          *
//*          casesen: (optional, 'false' by default)                             *
//*                   if 'false', then scan IS NOT case sensitive                *
//*                   if 'true, then scan IS case sensitive                      *
//*                                                                              *
//* Returns: index of the first (leftmost) character of the matching substring   *
//*          or (-1) if no match found                                           *
//*          Note: This is the wchar_t character index, NOT a byte index         *
//********************************************************************************

int32_t gString::findr ( const gString& src, int32_t offset, bool casesen ) const
{
   const wchar_t* srcptr = src.gstr() ;   // pointer to source string
   wchar_t first = *srcptr,               // first character in source
           firstalt = (iswlower ( first )) ? towupper ( first ) : towlower ( first ) ;
   int32_t tcnt = this->gsChars - 1,      // index of target nullchar
           maxcmp = src.gschars() - 1,    // length of substring (not incl. nullchar)
           index = -1 ;                   // return value

   //* Range check the offset (a negative value indicates start-at-end) *
   if ( (offset < ZERO) || (offset > tcnt) )
      offset = tcnt ;

   for ( int32_t ti = offset ; ti >= ZERO ; --ti )
   {
      if ( this->gStr[ti] == first || this->gStr[ti] == firstalt )
      {
         if ( casesen == false )
         {
            if ( ((wcsncasecmp ( &this->gStr[ti], srcptr, maxcmp)) == ZERO) )
            { index = ti ; break ; }
         }
         else
         {
            if ( ((wcsncmp ( &this->gStr[ti], srcptr, maxcmp)) == ZERO) )
            { index = ti ; break ; }
         }
      }
   }        // for(;;)
   return index ;

}  //* End findr() *

int32_t gString::findr ( const char* src, int32_t offset, bool casesen ) const
{
   gString bs( this->gsw ) ;
   bs = src ;
   return ( (this->findr ( bs, offset, casesen )) ) ;
}  //* End findr() *
int32_t gString::findr ( const uint8_t* src, int32_t offset, bool casesen ) const
{
   return ( (this->findr ( (const char*)(src), offset, casesen )) ) ;
}  //* End findr() *
int32_t gString::findr ( const wchar_t* src, int32_t offset, bool casesen ) const
{
   gString bs( this->gsw ) ;
   bs = src ;
   return ( (this->findr ( bs, offset, casesen )) ) ;
}  //* End findr() *
int32_t gString::findr ( const wchar_t src, int32_t offset, bool casesen ) const
{
   wchar_t wbuff[2] = { src, L'\0' } ;
   return ( (this->findr ( wbuff, offset, casesen )) ) ;
}  //* End findr() *

//*************************
//*         findx         *
//*************************
//********************************************************************************
//* Scan the text and locate the first character which DOES NOT match the        *
//* specified character. This method is used primarily to scan past the end of   *
//* a sequence of space (' ') (20 hex) characters, but may be used to skip       *
//* over any sequence of a single, repeated character.                           *
//*                                                                              *
//* See also the 'scan' method which scans to the first non-whitespace           *
//* character.                                                                   *
//*                                                                              *
//* Input  : srcChar: (optional, L' ' by default) character to be skipped over   *
//*          offset : (optional, ZERO by default)                                *
//*                   if specified, the character offset into the array at which *
//*                   to begin the scan.                                         *
//*                                                                              *
//* Returns: a) If successful, returns the index of first character which        *
//*             DOES NOT match specified character.                              *
//*          b) If the scan finds no character which is different from the       *
//*             specified character, OR if 'offset' is out of range, OR if the   *
//*             specified character is the null character, the return value      *
//*             indexes the null terminator of the array.                        *
//*          Note: This is the wchar_t character index, NOT a byte index         *
//********************************************************************************

int32_t gString::findx ( wchar_t srcChar, int32_t offset ) const
{
   int32_t indx = ((offset >= ZERO) && (offset < this->gsChars)) ? 
                 offset : (this->gsChars - 1) ;

   if ( srcChar != NULLCHAR )
   {
      while ( this->gStr[indx] == srcChar )
         ++indx ;
   }
   else
      indx = (this->gsChars - 1) ;

   return indx ;

}  //* End findx() *

//*************************
//*         scan          *
//*************************
//********************************************************************************
//* Scans the text array and returns the index of the first non-whitespace       *
//* character found.                                                             *
//* Whitespace characters are:                                                   *
//*    0x20   single-column ASCII space \x20                                     *
//*    0x3000 two-column CJK space      \x3000                                   *
//*    0x0A   linefeed character        '\n'                                     *
//*    0x0D   carriage-return character '\r'                                     *
//*    0x09   tab character             '\t'                                     *
//*    0x0B   vertical-tab character    \x0B                                     *
//*    0x0C   formfeed character        \x0C                                     *
//*                                                                              *
//* Input  : offset : (optional, ZERO by default)                                *
//*                   if specified, the character offset into the array at which *
//*                   to begin the scan.                                         *
//*                                                                              *
//* Returns: a) If successful, returns the index of first non-whitespace         *
//*             character                                                        *
//*          b) If the scan finds no non-whitespace character OR if 'offset'     *
//*             is out of range, the return value indexes the null terminator    *
//*             of the array.                                                    *
//*          Note: This is the wchar_t character index, NOT a byte index         *
//********************************************************************************

int32_t gString::scan ( int32_t offset ) const
{
   int32_t indx = ((offset >= ZERO) && (offset < this->gsChars)) ? 
                  offset : (this->gsChars - 1) ;

   while ( (this->gStr[indx] == SPACE) || (this->gStr[indx] == DSPACE)  ||
           (this->gStr[indx] == TAB)   || (this->gStr[indx] == NEWLINE) ||
           (this->gStr[indx] == VTAB)  || (this->gStr[indx] == CR)      ||
           (this->gStr[indx] == FF) )
   { ++indx ; }

   return indx ;

}  //* End scan() *

//*************************
//*        gscanf         *
//*************************
//********************************************************************************
//* Scan the text data and extract data according to the specified formatting    *
//* template.                                                                    *
//* (This is an implementation of the standard C-library 'swscanf' function.)    *
//*                                                                              *
//* Input  : fmt  : a format specification template in the style of swscanf(),   *
//*                 sscanf() and related C/C++ functions.                        *
//*                 Template may be either a const char* OR a const wchar_t*.    *
//*                                                                              *
//*          ...  : optional arguments                                           *
//*                 Each optional argument is a POINTER to (address of) the      *
//*                 variable to receive the formatted data.                      *
//*                 - Important Note: There must be AT LEAST as many optional    *
//*                   arguments as the number of format specifiers defined in    *
//*                   the formatting template. Excess arguments will be          *
//*                   ignored; HOWEVER, too few arguments will return an         *
//*                   error condition. (see below)                               *
//*                                                                              *
//* Returns: number of items captured and converted                              *
//*          returns 0 if:                                                       *
//*            a) number of format specifications in 'fmt' > gsFormMAXARGS.      *
//*            b) number of format specifications in 'fmt' > number of           *
//*               optional arguments (pointers to target variables) provided.    *
//********************************************************************************

int32_t gString::gscanf ( const wchar_t* fmt, ... ) const
{
   gsForm gsf { fmt } ;       // copy formatting string to formatting structure
   int32_t argCount  = ZERO ; // number of arguments in arg list

   //* Copy the source-data pointers to our formatting structure. *
   va_list argList ;
   va_start ( argList, ZERO ) ;
   for ( int32_t i = ZERO ; i < gsFormMAXARGS ; i++ )
   {
      if ( (gsf.argList[i] = va_arg ( argList, void* )) != NULL )
         ++argCount ;
      else
         break ;
   }
   va_end ( argList ) ;

   return ( (this->gscanf ( gsf, argCount, ZERO )) ) ;

}  //* End gscanf() *

int32_t gString::gscanf ( const char* fmt, ... ) const
{
   gString gs( fmt ) ;
   gsForm  gsf { gs.gstr() } ;
   int32_t argCount = ZERO ;

   //* Copy the source-data pointers to our formatting structure. *
   va_list argList ;
   va_start ( argList, ZERO ) ;
   for ( int32_t i = ZERO ; i < gsFormMAXARGS ; i++ )
   {
      if ( (gsf.argList[i] = va_arg ( argList, void* )) != NULL )
         ++argCount ;
      else
         break ;
   }
   va_end ( argList ) ;

   return ( (this->gscanf ( gsf, argCount, ZERO )) ) ;

}  //* End gscanf() *

//*************************
//*        gscanf         *
//*************************
//********************************************************************************
//* Scan the text data and extract data according to the specified formatting    *
//* template.                                                                    *
//* (This is an implementation of the standard C-library 'swscanf' function.)    *
//*                                                                              *
//* Input  : offset: character offset at which to begin the scan                 *
//*                  Important Note: This IS NOT a byte offset.                  *
//*                  If offset < 0 || offset > length of data, offset will       *
//*                  be silently set to 0.                                       *
//*          fmt   : a format specification template in the style of swscanf(),  *
//*                  sscanf() and related C/C++ functions.                       *
//*                  Template may be either a const char* OR a const wchar_t*.   *
//*                                                                              *
//*          ...   : optional arguments                                          *
//*                  Each optional argument is a POINTER to (address of) the     *
//*                  variable to receive the formatted data.                     *
//*                  - Important Note: There must be AT LEAST as many optional   *
//*                    arguments as the number of format specifiers defined in   *
//*                    the formatting template. Excess arguments will be         *
//*                    ignored; HOWEVER, too few arguments will return an        *
//*                    error condition. (see below)                              *
//*                                                                              *
//* Returns: number of items captured and converted                              *
//*          returns 0 if:                                                       *
//*            a) number of format specifications in 'fmt' > gsFormMAXARGS.      *
//*            b) number of format specifications in 'fmt' > number of           *
//*               optional arguments (pointers to target variables) provided.    *
//********************************************************************************

int32_t gString::gscanf ( int32_t offset, const wchar_t* fmt, ... ) const
{
   gsForm gsf { fmt } ;       // copy formatting string to formatting structure
   int32_t argCount  = ZERO ; // number of arguments in arg list

   //* Range check caller's offset value *
   if ( (offset < ZERO) || (offset >= (this->gsChars - 1)) )
      offset = ZERO ;

   //* Range check caller's offset value *
   if ( (offset < ZERO) || (offset >= (this->gsChars - 1)) )
      offset = ZERO ;

   //* Copy the source-data pointers to our formatting structure. *
   va_list argList ;
   va_start ( argList, ZERO ) ;
   for ( int32_t i = ZERO ; i < gsFormMAXARGS ; i++ )
   {
      if ( (gsf.argList[i] = va_arg ( argList, void* )) != NULL )
         ++argCount ;
      else
         break ;
   }
   va_end ( argList ) ;

   return ( (this->gscanf ( gsf, argCount, offset )) ) ;

}  //* End gscanf() *

int32_t gString::gscanf ( int32_t offset, const char* fmt, ... ) const
{
   gString gstmp( fmt ) ;        // convert format string to wchar_t
   gsForm gsf { gstmp.gstr() } ; // copy formatting string to formatting structure
   int32_t argCount  = ZERO ;    // number of arguments in arg list

   //* Range check caller's offset value *
   if ( (offset < ZERO) || (offset >= (this->gsChars - 1)) )
      offset = ZERO ;

   //* Copy the source-data pointers to our formatting structure. *
   va_list argList ;
   va_start ( argList, ZERO ) ;
   for ( int32_t i = ZERO ; i < gsFormMAXARGS ; i++ )
   {
      if ( (gsf.argList[i] = va_arg ( argList, void* )) != NULL )
         ++argCount ;
      else
         break ;
   }
   va_end ( argList ) ;

   return ( (this->gscanf ( gsf, argCount, offset )) ) ;

}  //* End gscanf() *


//********************************************************************************
//**:G:                         Output Group                                    **
//********************************************************************************

//*************************
//*        copy           *
//*************************
//********************************************************************************
//* Copy gString text to specified target buffer. (source data unchanged)        *
//*                                                                              *
//* Input  : uTarget : pointer to target array to receive UTF-8-encoded text     *
//*          maxBytes: maximum number of bytes to copy (incl. null terminator)   *
//*          maxCols : (optional, default == -1, i.e. count characters only)     *
//*                    maximum number of display-columns to copy                 *
//*                                                                              *
//* Returns: number of bytes copied (incl. null terminator)                      *
//********************************************************************************
//* Programmer's Note: If source data bytes greater than byte limit, copy        *
//* up to the whole UTF-8 character <= byte limit. This avoids creating an       *
//* invalid character in the target array.                                       *
//********************************************************************************

int32_t gString::copy ( char* uTarget, int32_t maxBytes, int32_t maxCols ) const
{
   int32_t copiedBytes = this->utfBytes ; // return value

   //* If 'maxCols' not specified, set to maximum value. *
   if ( maxCols < ZERO ) { maxCols = (gsALLOCMAX * 2) ; }

   //* If source data is within the specified limits, do the easy copy *
   if ( this->utfBytes <= maxBytes && this->gsCols <= maxCols )
   {
      for ( int32_t i = ZERO ; i < this->utfBytes ; i++ )
         uTarget[i] = this->uStr[i] ;
   }
   //* Else, caller wants less that the whole string. *
   //* We preserve the original data in this object.  *
   else
   {
      gString gs( this->gsw ) ;
      gs = this->gStr ;
      if ( gs.utfbytes() > maxBytes )
         gs.limitBytes( maxBytes ) ;
      if ( gs.gscols() > maxCols )
         gs.limitCols( maxCols ) ;
      int32_t newBytes = gs.utfbytes() ;  // adjusted byte count
      for ( int32_t i = ZERO ; i < newBytes ; ++i )
         uTarget[i] = this->uStr[i] ;
      uTarget[newBytes - 1] = NULLCHAR ;  // ensure that target is null-terminated
   }
   return copiedBytes ;

}  //* End copy() *
int32_t gString::copy ( uint8_t* uTarget, int32_t maxBytes, int32_t maxCols ) const
{
   return ( (this->copy ( (char*)uTarget, maxBytes, maxCols )) ) ;
}  //* End copy() *

//*************************
//*        copy           *
//*************************
//********************************************************************************
//* Copy gString text to specified target buffer. (source data unchanged)        *
//*                                                                              *
//* Input  : wTarget : pointer to target array to receive wchar_t 'wide' text    *
//*          maxChars: maximum number of characters to copy (incl. null)         *
//*          maxCols : (optional, default == -1, i.e. count characters only)     *
//*                    maximum number of display-columns to copy                 *
//*                                                                              *
//* Returns: number of characters copied (incl. null terminator)                 *
//********************************************************************************

int32_t gString::copy ( wchar_t* wTarget, int32_t maxChars, int32_t maxCols ) const
{
   int32_t copiedChars = this->gsChars ;    // return value

   //* If 'maxCols' not specified, set to maximum value. *
   if ( maxCols < ZERO ) { maxCols = (gsALLOCMAX * 2) ; }

   //* If source data is within the specified limits, do the easy copy *
   if ( this->gsChars <= maxChars && this->gsCols <= maxCols )
   {
      for ( int32_t i = ZERO ; i < this->gsChars ; ++i )
         wTarget[i] = this->gStr[i] ;
   }

   //* Else, caller wants less that the whole string. *
   //* We preserve the original data in this object.  *
   else
   {
      gString gs( this->gsw ) ;
      gs = this->gStr ;
      if ( gs.gschars() > maxChars )
         gs.limitChars( (maxChars - 1) ) ;
      if ( gs.gscols() > maxCols )
         gs.limitCols( maxCols ) ;
      int32_t newChars = gs.gschars() ;   // adjusted char count
      for ( int32_t i = ZERO ; i < newChars ; ++i )
         wTarget[i] = this->gStr[i] ;
      wTarget[newChars - 1] = NULLCHAR ;  // ensure that target is null-terminated
      copiedChars = newChars ;
   }
   return copiedChars ;

}  //* End copy() *

//*************************
//*        substr         *
//*************************
//********************************************************************************
//* Copy the specified character range to specified target buffer.               *
//*                                                                              *
//* If target buffer is a char*, then data returned is a UTF-8 text string.      *
//* If target buffer is a wchar_t*, then data returned is a wchar_t (wide)       *
//*   text string.                                                               *
//*   IMPORTANT NOTE: It is the caller's responsibility to specify               *
//*   a target buffer large enough to hold the data. Recommended:                *
//*          wchar_t wbuff[gsALLOCMED]; or char ubuff[gsALLOCMED * 4]            *
//* If target buffer is a gString object, then both UTF-8 and wchar_t data are   *
//* returned (with no chance of target buffer overflow :).                       *
//*                                                                              *
//* Input  : trg     : (by reference, initial contents ignored)                  *
//*                    receives null-terminated contents of specified            *
//*                    character range                                           *
//*          offset  : character index at which substring begins                 *
//*                    (this IS NOT a byte index)                                *
//*          charCnt : number of characters to copy (not incl. NULL terminator)  *
//*                                                                              *
//* Returns: if target is a wchar_t* or gString object, then returns number of   *
//*            characters written to target (not including the NULL terminator)  *
//*          if target is a char*, then returns number of bytes written to       *
//*            target (not including the NULL terminator)                        *
//*                                                                              *
//*          Note: returns ZERO if either 'offset' or 'charCnt' out of range     *
//*              Note that if 'charCnt' extends beyond the end of the source     *
//*              data, then the available data are returned.                     *
//********************************************************************************

int32_t gString::substr ( gString& trg, int32_t offset, int32_t charCnt ) const
{
   int32_t status = ZERO ;                // return value
   trg.clear() ;                          // initialize caller's buffer

   if ( (offset >= ZERO) && (offset < (this->gsChars - 1)) && (charCnt > ZERO) )
   {
      wchar_t* t = new wchar_t[this->gsw] ; // temporary buffer
      int32_t ti = ZERO ;                 // index into target buffer
      while ( ti < charCnt )              // copy source to target
      {
         t[ti] = this->gStr[offset++] ;
         if ( t[ti] == NULLCHAR )         // if end of source, we're done
            break ;
         ++ti ;                           // advance target index
      }
      t[ti] = NULLCHAR ;                  // terminate the string
      trg = t ;                           // copy to caller's object
      status = trg.gschars() - 1 ;        // return char count (not incl. nullchar)
      delete [] t ;                       // release the dynamic allocation
   }
   return status ;

}  //* End substr() *
int32_t gString::substr ( char* trg, int32_t offset, int32_t charCnt ) const
{
   gString gs( this->gsw ) ;
   int32_t subchars = this->substr ( gs, offset, charCnt ) ;
   gs.copy( trg, gs.utfbytes() ) ;
   return ( subchars ) ;
}  //* End substr() *
int32_t gString::substr ( uint8_t* trg, int32_t offset, int32_t charCnt ) const
{
   return ( (this->substr( (char*)(trg), offset, charCnt )) ) ;
}  //* End substr() *
int32_t gString::substr ( wchar_t* trg, int32_t offset, int32_t charCnt ) const
{
   gString gs( this->gsw ) ;
   int32_t subchars = this->substr ( gs, offset, charCnt ) ;
   gs.copy( trg, gs.gschars() ) ;
   return ( subchars ) ;
}  //* End substr() *

//*************************
//*      operator<<       *
//*************************
//********************************************************************************
//* !! NON-MEMBER METHOD !!                                                      *
//* Insertion operator: Insert the contents of the gString object into the       *
//* standard output stream.                                                      *
//*                                                                              *
//* Overloaded operator allows access to the 'wcout' (wide) standard output      *
//* stream OR access to the 'cout' (narrow) standard output stream.              *
//*                                                                              *
//* IMPORTANT NOTE: It is recommended that the 'wcout' (wide) stream version     *
//*                 be used exclusively.                                         *
//*                                                                              *
//* Input  : implied reference to output stream                                  *
//*          implied reference to gString object                                 *
//*                                                                              *
//* Returns: reference to the specified output stream                            *
//********************************************************************************
//* Note that due to the way the output stream is defined, you cannot mix        *
//* 'cout' (narrow) and 'wcout' (wide) output indiscriminately.                  *
//*  -- If 'wcout' is called first, then 'cout' is disabled.                     *
//*  -- If 'cout' is called first, then both narrow and wide channels are        *
//*     active.                                                                  *
//*  -- 'wcout' handles both narrow and wide data, however, cout handles ONLY    *
//*     narrow data.                                                             *
//* This is not related to gString, but is a characteristic of the C++ output    *
//* stream itself. We recommend that you always use the 'wcout' stream for       *
//* both narrow and wide text data.                                              *
//********************************************************************************

std::wostream& operator << ( std::wostream& os, const gString& bsref )
{
   os << bsref.gStr ;
   return os ;
}  //* End operator<<() *
std::ostream& operator << ( std::ostream& os, const gString& bsref )
{
   os << bsref.uStr ;
   return os ;
}  //* End operator<<() *

#if DEBUG_GSTRING != 0
//*************************
//*      dumpGstring      *
//*************************
//********************************************************************************
//* Public Method - For Debugging Only:                                          *
//* -----------------------------------                                          *
//* Get a snapshot of the gString data members.                                  *
//*                                                                              *
//* Input  : gstr    : array of wchar_t characters                               *
//*          ustr    : array of UTF-8 bytes                                      *
//*          cwid    : array of column widths                                    *
//*          gsw     : size of gstr array                                        *
//*          gsu     : size of ustr array                                        *
//*          bsco    : number of display columns required for stored data        *
//*          bsch    : number of wchar_t characters stored (incl. nullchar)      *
//*          utfb    : number of UTF-8 bytes stored (incl.nullchar)              *
//*          isas    : set if stored data are pure ASCII data                    *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void gString::dumpGstring ( const wchar_t*& gstr, const char*& ustr, const short*& cwid, 
                            int32_t& gsw, int32_t& gsu, int32_t& bsch, int32_t& bsco, 
                            int32_t& utfb, bool& isas ) const
{
   gstr = this->gStr ;
   ustr = this->uStr ;
   cwid = this->cWid ;
   gsw  = this->gsw ;
   gsu  = this->gsu ;
   bsch = this->gsChars ;
   bsco = this->gsCols ;
   utfb = this->utfBytes ;

   //* The isAscii flag is initialized only when requested *
   //* by the application, so calculate its state locally. *
   isas = true ;
   if ( this->gsChars > ZERO )
   {
      for ( int32_t i = ZERO ; this->uStr[i] != NULLCHAR ; i++ )
      {
         if ( this->uStr[i] < ZERO || this->uStr[i] > TILDE )
         { isas = false ; break ; }
      }
   }

}  //* End dumpGstring() *

//*************************
//*         dbMsg         *
//*************************
//********************************************************************************
//* FOR DEBUGGING ONLY! Application can retrieve most recent debug message.      *
//* Input  : bsmsg : target gString (by reference)                               *
//* Returns: nothing                                                             *
//********************************************************************************

void gString::dbMsg ( gString& bsmsg ) const
{
   bsmsg = dbmsg ;
}  //* End dbMsg() *
#endif   // DEBUG_GSTRING


//********************************************************************************
//**:G:                       PROTECTED METHODS                                 **
//********************************************************************************
// None at this time.

//********************************************************************************
//**:G:                        PRIVATE METHODS                                  **
//********************************************************************************

//*************************
//*       allocate        *
//*************************
//********************************************************************************
//* Private Method:                                                              *
//* ---------------                                                              *
//* Allocate dynamic storage for the class.                                      *
//*  a) A single allocation is performed which is large enough to contain all    *
//*     three arrays: gStr=wchar_t[], uStr=char[] and cWid=short[].              *
//*     The pointers to each array are initialized.                              *
//*  b) The available space for each buffer is recorded:                         *
//*     -- 'gsw' indicates the number of elements in each of gStr[] and cWid[].  *
//*     -- 'gsu' indicates the number of elements in uStr[].                     *
//*                                                                              *
//* Avoid Memory Leaks:                                                          *
//* ===================                                                          *
//* There should NEVER be a previous allocation attached to member pointer(s).   *
//*  a) When called by the constructors, allocation has not yet occurred.        *
//*  b) When called by reallocate(), release of dynamic storage has been         *
//*     performed by caller.                                                     *
//*                                                                              *
//* Notes On Allocation:                                                         *
//* ====================                                                         *
//* 1) An allocation which is not an even multiple of gsALLOCMIN is extended     *
//*    to the next multiple of gsALLOCMIN. This guards against buffer overrun.   *
//* 2) Padding of 2 integer elements (8 bytes) of unused space is positioned     *
//*    between successive buffers within the allocated space. This is done in    *
//*    case your humble author can't count. (it happens to the best of us :-)    *
//* 3) If the allocation fails:                                                  *
//*    a) If a throwing overload were used, the system would throw an exception  *
//*       on failure, causing a segmentation fault.                              *
//*    b) This method uses a 'nothrow' overload loop in case the allocation      *
//*       fails. It is theoretically possible, but unlikely that we could        *
//*       get stuck in the allocate/retry loop. If this were to happen, it would *
//*       indicate a more serious hardware/firmware problem in the host system.  *
//*       See also: C++ documentation for new(std::nothrow).                     *
//*                                                                              *
//* Input  : walloc : number of wchar_t (UTF-32) characters to allocate for the  *
//*                   'gStr' member. The capacity of the remaining buffers is    *
//*                   calculated from this value.                                *
//*                                                                              *
//* Returns: number of UTF-32 elements allocated to 'gStr' member                *
//********************************************************************************

int32_t gString::allocate ( int32_t walloc )
{
   if ( walloc < gsALLOCMIN )             // range checking
      walloc = gsALLOCMIN ;
   else if ( (walloc % gsALLOCMIN) > ZERO ) // round up to next increment
      walloc += gsALLOCMIN - (walloc % gsALLOCMIN) ;
   if ( walloc > gsALLOCMAX )
      walloc = gsALLOCMAX ;

   const int32_t allocPAD = 6 ;           // safety padding (2 per array)
   int32_t allocCnt = (walloc * 2 + walloc / 2 + allocPAD) ; // total allocation space
   do
   {
      this->gStr = new(std::nothrow) wchar_t[allocCnt] ;
   } while ( this->gStr == NULL ) ;
   this->gsw = walloc ;                            // size of wchar_t and cWid arrays
   this->gsu = walloc * 4 ;                        // size of char array
   this->uStr = (char*)(&this->gStr[walloc + 2]) ; // pointer to byte array
   this->cWid = (short*)(&this->gStr[walloc * 2 + 4]) ;// pointer to char-width array
   return this->gsw ;

}  //* allocate() *

//*************************
//*      reallocate       *
//*************************
//********************************************************************************
//* Private Method:                                                              *
//* ---------------                                                              *
//* Allocate dynamic storage for the class.                                      *
//*                                                                              *
//* Note: The existing allocation (if any) is released.                          *
//* Note: Each allocation is extended by one unit. Guards against buffer overrun.*
//* Note: If the allocation fails, the system will throw an exception.           *
//*       While this is unlikely, it is possible.                                *
//*       See also: C++ documentation for new(std::nothrow).                     *
//*                                                                              *
//* Input  : walloc : number of wchar_t character to allocate for 'gStr' member. *
//*                                                                              *
//* Returns: number of UTF-32 elements allocated to 'gStr' member                *
//********************************************************************************

int32_t gString::reallocate ( int32_t walloc )
{
   if ( this->gStr != NULL )
   {
      delete [] this->gStr ;
      this->gStr = NULL ;
      this->uStr = NULL ;
      this->cWid = NULL ;
   }

   this->allocate ( walloc ) ;
   this->reset () ;
   return this->gsw ;

}  //* reallocate() *

//*************************
//*      reallocate       *
//*************************
//********************************************************************************
//* Private Method:                                                              *
//* ---------------                                                              *
//* Allocate/reallocate dynamic storage to accomodate the specified data.        *
//*                                                                              *
//* Byte/Character count is used to determine the minimum buffer space needed;   *
//* then the allocation is rounded upward to the next multiple of gsALLOCMIN,    *
//* limited by the maximum buffer size.                                          *
//*                                                                              *
//* This method combines UTF-8 and UTF-32 reallocation.                          *
//* EXACTLY ONE of the arguments should be non-null. If 'usrc' is not-null,      *
//* then 'wsrc' will be ignored. If 'usrc' is null, then 'wsrc' must not be null.*
//*                                                                              *
//* Input  : usrc  : pointer to UTF-8 initialization data                        *
//*          wsrc  : (optional) pointer to UTF-32 initialization data            *
//*                                                                              *
//* Returns: 'true'  if data fits entirely within storage capacity,              *
//*          'false' if data truncated                                           *
//********************************************************************************

bool gString::reallocate ( const char* usrc, const wchar_t* wsrc )
{
   int32_t cnt = -1 ;
   bool    status = false ;               // return value

   if ( usrc != NULL )                    // count the bytes
   {
      while ( usrc[++cnt] != NULLCHAR ) ;
      ++cnt ;                             // incl. null terminator
      if ( cnt > BYTEMAX )                // range limit
         cnt = BYTEMAX ;
      //* Programmer's Note:The conversion from byte count to character count 
      //* is expensive, no matter how it is done, so we do it the bulletproof 
      //* way by creating an instance to do it.
      gString gs( gsALLOCMAX ) ;
      gs = usrc ;
      cnt = gs.gschars() ;
      this->reallocate ( cnt ) ;          // reallocate the member buffers
      this->utf8_2_utf32 ( usrc ) ;       // reinitialize using UTF-8 data
   }
   else if ( wsrc != NULL )               // count the characters
   {
      while ( wsrc[++cnt] != NULLCHAR ) ;
      ++cnt ;                             // incl. null terminator
      if ( cnt > gsALLOCMAX )             // range limit
         cnt = gsALLOCMAX ;
      this->reallocate ( cnt ) ;          // reallocate the member buffers
      this->utf32_2_utf8 ( wsrc ) ;       // reinitialize using UTF-32 data
   }
   if ( cnt < this->gsw )
      status = true ;

   return status ;

}  //* End reallocate() *

//*************************
//*         reset         *
//*************************
//********************************************************************************
//* PRIVATE METHOD:                                                              *
//* ---------------                                                              *
//* Reinitialize the data members to indicate a NULL string "\0".                *
//*                                                                              *
//*                                                                              *
//* Input  : initAll: (optional, 'true' by default)                              *
//*                   if 'true' reinitialize all data members (discard text data)*
//*                   if 'false' reinitialize everything EXCEPT:                 *
//*                    gStr[] contents and gsChars (character count).            *
//* Returns: nothing                                                             *
//********************************************************************************

void gString::reset ( bool initAll )
{

   if ( initAll != false )
   {
      this->gStr[ZERO] = NULLCHAR ;
      this->gsChars = 1 ;  // A NULL string has 1 character
   }
   this->uStr[ZERO] = NULLCHAR ;
   this->cWid[ZERO] = ZERO ;
   this->utfBytes = 1 ;    // a NULL string has 1 byte
   this->gsCols = ZERO ;   // and occupies no columns
   this->isAscii = true ;  // a NULL string is an ASCII string

}  //* End reset() *

//*************************
//*    buffer_overrun     *
//*************************
//********************************************************************************
//* PRIVATE METHOD:                                                              *
//* ---------------                                                              *
//* Test for _potential_ buffer overrun.                                         *
//* Note that buffer has not actually been overrun; however, the initialization  *
//* data may have been truncated.                                                *
//* This test determines whether the character buffer is full (or very nearly    *
//* full), and whether maximum allocation has been reached.                      *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: 'true'  if overflow of existing storage allocation AND              *
//*                  additional allocation space is available                    *
//*          'false' current data fits entirely within the current buffer, OR    *
//*                  data overflow AND additional space can be allocated.        *
//********************************************************************************

bool gString::buffer_overrun ( void )
{

   return ( bool((this->gsChars >= (this->gsw - 4)) && (this->gsw < gsALLOCMAX)) ) ;

}  //* End buffer_overrun() *

//*************************
//*     utf8_2_utf32      *
//*************************
//********************************************************************************
//* Private Method:                                                              *
//* ---------------                                                              *
//* Convert a UTF-8-encoded string of up to 'charLimit' characters, to a wchar_t *
//* (wide character) string (null-terminated). Saved to 'gStr' member.           *
//*                                                                              *
//* Input  : usrc     : pointer to null-terminated UTF-8-encoded string          *
//*                     (includes ASCII strings as well as multi-byte characters)*
//*                     (Assumes all class data members have been reinitialized) *
//*          charLimit: (optional, -1 by default) max characters to convert      *
//*                     If not specified, convert all source characters up to    *
//*                     the storage limit.                                       *
//*                                                                              *
//* Returns: nothing                                                             *
//*          On return, the following data members are initialized:              *
//*           gStr     : array of converted characters                           *
//*           gsChars  : number of converted characters                          *
//*                      (ZERO if conversion error - but see bytewise_u2w())     *
//*           gsCols   : number of display columns needed to display gStr        *
//*           uStr     : UTF-8 source data, truncated if necessary               *
//*           utfBytes : number of bytes in (adjusted) source data               *
//********************************************************************************
//* Programmer's Note:                                                           *
//* The ISOC90 function, mbstowcs() is considered to be dangerous in that it     *
//* is not re-entrant or 're-startable'. For this reason, we use the             *
//* restartable version, mbsrtowcs(). This necessitates an understanding of      *
//* the mbstate_t structure.                                                     *
//*                                                                              *
//*  typedef struct                                                              *
//*  {                                                                           *
//*    int __count;                                                              *
//*    union                                                                     *
//*    {                                                                         *
//*      wint_t __wch;                                                           *
//*      char __wchb[4];                                                         *
//*    } __value;            /* Value so far.  */                                *
//*  } __mbstate_t;                                                              *
//*                                                                              *
//* On return from mbsrtowcs() this structure is updated with the current        *
//* 'state'. If NULLCHAR terminator was processed and there was no error, this   *
//* is the 'initial' state.                                                      *
//*                     count == 0  ,   value == 0                               *
//* Otherwise an intermediate state is returned, reflecting what had happened    *
//* within the called function during the last character processed. This is      *
//* not particularly interesting to us.                                          *
//*  For a limited number of character conversions (NULLCHAR not processed):     *
//*   (only ASCII)      count == 0  ,   value == 0                               *
//*   (multi-byte)      count == 0  ,   value == some value > ZERO,              *
//*                       but not necessarily the most recent multi-byte         *
//*                       character processed. Unable to determine the meaning   *
//*                       of the value without serious experimentation, which    *
//*                       I'm not willing to do at this time...)                 *
//*  For an invalid multi-byte character encountered:                            *
//*                     count == 0  ,   value == 0  (for some random errors)     *
//*                                                                              *
//* What we want is to be sure that processing for each string begins in the     *
//* initial state and that any calls to the function by other threads will       *
//* not affect the processing in this one.                                       *
//*                                                                              *
//* Frankly, this whole alarmist scenario described in the man pages seems       *
//* bogus for any but the most esoteric of operations. Most character sets are   *
//* stateless, and ALL UTF-8-encoded text is stateless. Although it is possible  *
//* that the application is running in a non-UTF-8 environment, all Linux        *
//* flavours fully support UTF-8 multi-byte encoding, so a non-compliant system  *
//* will deserve whatever mess it creates.                                       *
//*                                                                              *
//* size_t mbstrowcs(wchar_t* DST, const char* SRC, size_t LEN, mbstate_t* PS);  *
//* - returns number of wide characters converted OR -1 if invalid multi-byte    *
//*   char in source                                                             *
//* - DST is the target buffer to hold the wchar_t converted data                *
//* - SRC is the multi-byte source string (for us, always UTF-8)                 *
//*   Note: according to the man page:                                           *
//*      "If DST is not null,`mbsrtowcs' stores in the pointer pointed to by     *
//*       SRC either a null pointer (if the NUL byte in the input string was     *
//*       reached) or the address of the byte following the last converted       *
//*       multibyte character."                                                  *
//*   Frankly, we didn't know what the hell this meant, but in fact *SRC is      *
//*   modified by the call. This is beyond reasoning, but we protect the         *
//*   our input parameter 'const char* usrc' from modification, so we can        *
//*   use it later to save a copy of caller's multi-byte data string.            *
//* - LEN is max number of wchar_t characters to convert                         *
//* - PS is the 'processing state'                                               *
//*                                                                              *
//* - If value returned from conversion == charLimit, NULLCHAR may or may not    *
//*   have been converted.                                                       *
//*   - If null terminator was converted, it is at [gsChars-1], but was not      *
//*     included in the character total, so increment the count.                 *
//*   - If terminator was not converted, then terminate the string by replacing  *
//*     the last character converted (no change in character count).             *
//*                                                                              *
//********************************************************************************

void gString::utf8_2_utf32 ( const char* usrc, int32_t charLimit )
{
   //* If default limit OR if limit is over-range, set to buffer size. *
   if ( (charLimit < ZERO) || (charLimit >= this->gsw) )
   { if ( (charLimit = this->gsw - 1) > REALMAX ) { charLimit = REALMAX ; } }

   //* Text data will change, so reset everything. *
   this->reset () ;

   //* Convert UTF-8 source to wchar_t target *
   mbstate_t   ps = { ZERO, { ZERO } } ;
   const char*  us = usrc ;   // protect the method parameter from modification
   const char** srcPtr = &us ;// see note above
   if ( (this->gsChars = mbsrtowcs ( this->gStr, srcPtr, charLimit, &ps )) > ZERO )
   {
      //* It's possible that no null terminator was appended during         *
      //* conversion, so do it now. Even if it was appended, it wasn't      *
      //* counted in the conversion total, so count it now.                 *
      //* If gsChars < charLimit, NULLCHAR was converted but not counted,   *
      //* so count it.                                                      *
      if ( this->gsChars < charLimit )
      {
         ++this->gsChars ;

         //* Copy the source to our local buffer.                           *
         //* Because the entire string was converted, simply copy it.       *
         int32_t i = -1 ;
         do
         {
            ++i ;
            this->uStr[i] = usrc[i] ;
         }
         while ( this->uStr[i] != NULLCHAR ) ;
         this->utfBytes = ++i ;     // include the null terminator in the count
         this->columnCount () ;     // count the resulting display columns
      }
      //* Else, gsChars == charLimit. See note above. *
      else
      {
         if ( this->gStr[this->gsChars - 1] != NULLCHAR )
            this->gStr[this->gsChars - 1] = NULLCHAR ;
         else
            ++this->gsChars ;

         //* Copy the source to our local buffer.                           *
         //* Because the string was truncated, we cannot be sure how many   *
         //* bytes to copy from the source string, so we re-convert to      *
         //* UTF-8 to get the number of bytes. We 'know' results will fit   *
         //* in our buffer. (Yes, this is too fancy for words. :)           *
         this->utf32_2_utf8 () ;
      }
   }
   else                             // conversion error
   {  //* If a conversion error, convert as much of the string as possible. *
      this->bytewise_u2w ( usrc, charLimit ) ;
      this->columnCount () ;        // count the resulting display columns
   }

}  //* End utf8_2_utf32() *

//*************************
//*     bytewise_u2w      *
//*************************
//********************************************************************************
//* Private Method:                                                              *
//* ---------------                                                              *
//* Convert the UTF-8 data in usrc to wchar_t data and store it in gStr.         *
//* This method is called ONLY when there is an error in conversion of the       *
//* string as a whole (see utf8_2_utf32()).                                      *
//* Convert one character at a time until the null terminator has been           *
//* converted, OR a conversion error is encountered.                             *
//* Both gStr and uStr will be null-terminated on return.                        *
//*                                                                              *
//* Input  : usrc     : pointer to null-terminated UTF-8-encoded string          *
//*                     (includes ASCII strings as well as multi-byte characters)*
//*                     (Assumes all class data members have been reinitialized) *
//*          charLimit: maximum number of source characters to convert           *
//*                                                                              *
//* Returns: nothing                                                             *
//*          On return, the following data members are initialized:              *
//*           gStr     : array of converted characters up to conversion error    *
//*                      (all valid, and null-terminated)                        *
//*           gsChars  : number of characters successfully converted             *
//*                      includng null terminator                                *
//*           gsCols   : number of display columns needed to display gStr        *
//*           uStr     : truncated array of successfully-converted characters    *
//*                      (all valid, and null-terminated)                        *
//*           utfBytes : number of bytes in remaining character codes            *
//********************************************************************************
//* Note: mbrtowc() returns one of the following:                                *
//*       >0 == bytes in converted multi-byte character                          *
//*        0 == NULLCHAR converted                                               *
//*       -1 == invalid multi-byte sequence                                      *
//*       -2 == multi-byte char is incomplete at maxBPC (not very likely)        *
//*                                                                              *
//********************************************************************************
//* -- Function: size_t mbrtowc (wchar_t *restrict PWC, const char               *
//*          *restrict S, size_t N, mbstate_t *restrict PS)                      *
//*    The `mbrtowc' function ("multibyte restartable to wide character")        *
//*    converts the next multibyte character in the string pointed to by         *
//*    S into a wide character and stores it in the wide character string        *
//*    pointed to by PWC.  The conversion is performed according to the          *
//*    locale currently selected for the `LC_CTYPE' category.  If the            *
//*    conversion for the character set used in the locale requires a            *
//*    state, the multibyte string is interpreted in the state                   *
//*    represented by the object pointed to by PS.  If PS is a null              *
//*    pointer, a static, internal state variable used only by the               *
//*    `mbrtowc' function is used.                                               *
//*                                                                              *
//*    If the next multibyte character corresponds to the NUL wide               *
//*    character, the return value of the function is 0 and the state            *
//*    object is afterwards in the initial state.  If the next N or fewer        *
//*    bytes form a correct multibyte character, the return value is the         *
//*    number of bytes starting from S that form the multibyte character.        *
//*    The conversion state is updated according to the bytes consumed in        *
//*    the conversion.  In both cases the wide character (either the             *
//*    `L'\0'' or the one found in the conversion) is stored in the              *
//*    string pointed to by PWC if PWC is not null.                              *
//*                                                                              *
//*    If the first N bytes of the multibyte string possibly form a valid        *
//*    multibyte character but there are more than N bytes needed to             *
//*    complete it, the return value of the function is `(size_t) -2' and        *
//*    no value is stored.  Please note that this can happen even if N           *
//*    has a value greater than or equal to `MB_CUR_MAX' since the input         *
//*    might contain redundant shift sequences.                                  *
//*                                                                              *
//*    If the first `n' bytes of the multibyte string cannot possibly form       *
//*    a valid multibyte character, no value is stored, the global               *
//*    variable `errno' is set to the value `EILSEQ', and the function           *
//*    returns `(size_t) -1'.  The conversion state is afterwards                *
//*    undefined.                                                                *
//********************************************************************************

void gString::bytewise_u2w ( const char* usrc, int32_t charLimit )
{
   //* Reinitialize all data members *
   this->reset() ;

   wchar_t wtmp ;                // converted character
   int32_t uBytes,               // UTF-8 bytes for converted character
           wIndex = ZERO,        // index into gStr
           uIndex = ZERO ;       // index into uStr and usrc
   bool    done = false ;
   while ( ! done && wIndex < charLimit )
   {
      mbstate_t   ps = { ZERO, { ZERO } } ;// 'state' structure (initial state)
      uBytes = mbrtowc ( &wtmp, &usrc[uIndex], maxBPC, &ps ) ;

      //* If valid (non-null) wchar_t character stored in wtmp *
      if ( uBytes > ZERO )
      {
         this->gStr[wIndex++] = wtmp ;
         for ( int32_t i = ZERO ; i < uBytes ; i++ ) // store multi-byte character
         {
            this->uStr[uIndex] = usrc[uIndex] ;
            ++uIndex ;
         }
      }
      //* If null terminator has just been converted OR                  *
      //* if no multi-byte sequence does not represent a valid character *
      else
      {
         this->uStr[uIndex] = NULLCHAR ;  // terminate both strings
         this->gStr[wIndex] = NULLCHAR ;
         this->gsChars = wIndex + 1 ;     // number of valid characters
         this->utfBytes = uIndex + 1 ;    // number of valid UTF-8 bytes stored
         done = true ;
      }
   }

}  //* End bytewise_u2w() *

//*************************
//*     utf32_2_utf8      *
//*************************
//********************************************************************************
//* Convert the wchar_t string in gStr to a UTF-8-encoded, multi-byte-character  *
//* string.                                                                      *
//*                                                                              *
//* Input  : wsrc     : (optional, NULL pointer by default) indicating that)     *
//*                     (operation will use existing data in gStr[] member.)     *
//*                     If specified, process the null-terminated array of       *
//*                     wchar_t source characters.                               *
//*          charLimit: (optional, -1 by default) max characters to convert      *
//*                     If not specified, convert all source characters up to    *
//*                     the storage limit.                                       *
//*                                                                              *
//* Returns: nothing                                                             *
//*          On return, all data members are reinitialied with the possible      *
//*           exception of gStr[] and gsChars which MAY remain unchanged.        *
//********************************************************************************

void gString::utf32_2_utf8 ( const wchar_t* wsrc, int32_t charLimit )
{
   //* If default limit OR if limit is over-range, set to buffer size. *
   if ( (charLimit < ZERO) || (charLimit >= this->gsw) )
   { if ( (charLimit = this->gsw - 1) > REALMAX ) { charLimit = REALMAX ; } }

   //* Text data will change, so reset everything but gStr[] and gsChars *
   this->reset ( false ) ;

   //* If external data specified, copy it to gStr data member *
   if ( wsrc != NULL )
   {
      int32_t i = -1 ;
      do
      {
         if ( ++i < charLimit )     // index wchar_t storage
            this->gStr[i] = wsrc[i] ;  // store the character

         //* Target buffer full. *
         else
         {
            if ( charLimit == this->gsw ) // if buffer is full
               i = this->gsw - 1 ;       // max buffer index (position for NULL)
               // else explicit limit (non-null character count) reached
            this->gStr[i] = NULLCHAR ; // either way, terminate the string
         }
      }
      while ( this->gStr[i] != NULLCHAR ) ;
      this->gsChars = i + 1 ;       // character count incl. NULL
   }

   //* If we have a non_NULL string, convert it *
   if ( *this->gStr != NULLCHAR )
   {
      //* Convert wchar_t string to UTF-8 format *
      char* tmpstr = new char[this->gsu] ;
      int32_t maxbytes = this->gsu - 2 ;
      int32_t tmpbytes ;
      mbstate_t   ps = { ZERO, { ZERO } } ;
      const wchar_t*  ws = this->gStr ;// protect buffer pointer from modification
      const wchar_t** srcPtr = &ws ;   // see note in utf8_2_utf32()
      if ( (tmpbytes = wcsrtombs ( tmpstr, srcPtr, maxbytes, &ps )) > ZERO )
      {
         //* It's possible that no null terminator was appended during         *
         //* conversion, so do it now. Even if it was appended, it wasn't      *
         //* counted in the  conversion total, so count it now.                *
         //* If tmpbytes < maxbytes, NULLCHAR was converted but not counted,   *
         //* so count it.                                                      *
         if ( tmpbytes < maxbytes )
            ++tmpbytes ;
         //* Else, tmpbytes == maxbytes, NULLCHAR may or may not have been     *
         //* converted. If it was, just count it, if not, add null char.       *
         else
         {
            if ( tmpstr[tmpbytes-1] != NULLCHAR )
               tmpstr[tmpbytes] = NULLCHAR ;
            ++tmpbytes ;
         }

         //* Copy converted string to uStr and update counters *
         int32_t i = -1 ;
         do
         {
            ++i ;
            this->uStr[i] = tmpstr[i] ;
         }
         while ( this->uStr[i] != NULLCHAR ) ;
         this->utfBytes = tmpbytes ;
      }
      else                                // conversion error (or NULL string)
      {  //* If a conversion error, convert as much of the string as possible. *
         this->bytewise_w2u ( charLimit ) ;
      }
      delete [] tmpstr ;                  // release dynamic alocation
      this->columnCount () ;              // count the resulting display columns
   }

}  //* End utf32_2_utf8() *

//*************************
//*     bytewise_w2u      *
//*************************
//********************************************************************************
//* Convert the wchar_t data in gStr to UTF-8 data and store it in uStr.         *
//* This method is called ONLY when there is an error in conversion of the       *
//* string as a whole (see utf32_2_utf8()).                                      *
//* Convert one character at a time until the null terminator has been           *
//* converted, OR a conversion error is encountered.                             *
//* Both gStr and uStr will be null-terminated on return.                        *
//*                                                                              *
//*                                                                              *
//* Input  : charLimit : maximum number of characters to convert.                *
//*                                                                              *
//* Returns: nothing                                                             *
//*          On return, the following data members are initialized:              *
//*           uStr     : array of converted characters up to conversion error    *
//*                      (all valid, and null-terminated)                        *
//*           utfBytes : number of bytes in converted array                      *
//*           gStr     : truncated array of successfully-converted characters    *
//*                      (all valid, and null-terminated)                        *
//*           gsChars  : number of remaining characters, incl. null terminator   *
//********************************************************************************
//* Programmer's Note: It is unlikely that this method will ever be called.      *
//* We have verified that the method correctly converts the entire string,       *
//* terminating at NULLCHAR or any invalid wchar_t character.                    *
//* However, only wchar_t values with bit 31 set induce an error return from     *
//* either the wcsrtombs() and wcrtomb() library functions, so an application    *
//* would have to work really hard to screw up that badly.                       *
//********************************************************************************

void gString::bytewise_w2u ( int32_t charLimit )
{
   //* Text data will change, so reset everything but gStr[] and gsChars *
   this->reset ( false ) ;

   //* If we have a non_NULL string, convert it *
   if ( *this->gStr != NULLCHAR )
   {
      int32_t maxbytes = this->gsu - 2,  // output buffer limit
              uBytes,                    // UTF-8 bytes for converted character
              wIndex = ZERO,             // index into gStr
              uIndex = ZERO ;            // index into uStr
      bool  done = false ;
      while ( ! done && wIndex < charLimit )
      {
         mbstate_t   ps = { ZERO, { ZERO } } ;// 'state' structure (initial state)
         uBytes = wcrtomb ( &this->uStr[uIndex], this->gStr[wIndex], &ps ) ;
         //* If null terminator has just been converted OR         *
         //* if no multi-byte representation for this character OR *
         //* if output buffer is full (rather unlikely).           *
         if ( this->gStr[wIndex] == NULLCHAR || uBytes < ZERO 
             || ((uIndex + uBytes) >= maxbytes) )
         {
            if ( (uIndex + uBytes) == maxbytes )
            {
               ++wIndex ;
               uIndex += uBytes ;
            }
            this->uStr[uIndex] = NULLCHAR ;  // terminate both strings
            this->gStr[wIndex] = NULLCHAR ;
            this->gsChars = wIndex + 1 ;     // number of valid characters
            this->utfBytes = uIndex + 1 ;    // number of valid UTF-8 bytes stored
            done = true ;
         }
         //* Valid (non-null) multi-byte character stored *
         else
         {
            ++wIndex ;
            uIndex += uBytes ;
         }
      }
   }   

}  //* End bytewise_w2u() *

//*************************
//*        gscanf         *
//*************************
//********************************************************************************
//* PRIVATE METHOD:                                                              *
//* ---------------                                                              *
//* Called by public gscanf() methods.                                           *
//*    a) Count the number of format specifications.                             *
//*    b) Validate the input data                                                *
//*    c) Call the C-language swscanf() function to extract the specified data.  *
//*                                                                              *
//* Input  : gsf      : an initialized gsForm object:                            *
//*                     'fmtSpec' member contains the formatting string          *
//*                     'argList' array contains pointers to arguemnts           *
//*          argCount : number of argument pointers in 'argList' array           *
//*          offset   : offset into data at which to begin the scan              *
//*                                                                              *
//* Returns: number of items captured and converted                              *
//*          returns 0 if:                                                       *
//*            a) number of format specifications in 'fmt' > gsFormMAXARGS.      *
//*            b) number of format specifications in 'fmt' > number of           *
//*               optional arguments (pointers to target variables) provided.    *
//********************************************************************************

int32_t gString::gscanf ( const gsForm& gsf, int32_t argCount, int32_t offset ) const
{
   int32_t specCount = ZERO,        // conversion specifications in 'fmt'
           convCount = ZERO ;       // return value (# of conversions)
   bool    overflow  = false ;      // 'true' if too many specs

   //* Count the number of format specifiers.*
   for ( int32_t i = ZERO ; gsf.fmtSpec[i] != NULLCHAR ; i++ )
   {
      if ( gsf.fmtSpec[i] == L'%' )
      {
         if ( gsf.fmtSpec[i+1] != L'%' )  ++specCount ;
         else  ++i ;    // step over literal '%'
      }
      if ( specCount > gsFormMAXARGS )
      { specCount = gsFormMAXARGS ; overflow = true ; break ; }
   }

   //* If conversion specifications <= maximum conversions, AND *
   //* if argCount >= specCount, then perform the scan.         *
   if ( ! overflow && (argCount >= specCount) )
   {
      // Programmer's Note: The pointer array below is hard-coded at 
      // twenty-four(24) items. If the definition of gsFormMAXARGS changes, 
      // this array must be updated to match.
      convCount = 
         swscanf ( &this->gStr[offset], gsf.fmtSpec,
                   gsf.argList[ 0], gsf.argList[ 1], gsf.argList[ 2],
                   gsf.argList[ 3], gsf.argList[ 4], gsf.argList[ 5],
                   gsf.argList[ 6], gsf.argList[ 7], gsf.argList[ 8],
                   gsf.argList[ 9], gsf.argList[10], gsf.argList[11],
                   gsf.argList[12], gsf.argList[13], gsf.argList[14],
                   gsf.argList[15], gsf.argList[16], gsf.argList[17],
                   gsf.argList[18], gsf.argList[19], gsf.argList[20],
                   gsf.argList[21], gsf.argList[22], gsf.argList[23] ) ;
   }
   return convCount ;

}  //* End gscanf() *

//*************************
//*      formatQInt       *
//*************************
//********************************************************************************
//* PRIVATE METHOD:                                                              *
//* ---------------                                                              *
//* Convert an integer value into a formatted display string of the specified    *
//* width. Value is right-justified in the field, with leading spaces added      *
//* if necessary (but see 'lJust' parameter).                                    *
//* Maximum field width is FI_MAX_FIELDWIDTH. This is wide enough to display     *
//* a 13-digit, signed and comma-formatted value: '+9,876,543,210,777'           *
//* i.e. plus-or-minus 9.999999999999 terabytes or tebibytes                     *
//*                                                                              *
//* Actual formatting of the value depends on the combination of:                *
//*   a) magnitude of the value                                                  *
//*   b) whether it is a signed value                                            *
//*   c) the specified field-width                                               *
//*   d) the specified suffix format                                             *
//*   e) locale-specific grouping of digits according the LC_NUMERIC locale      *
//*      environment variable                                                    *
//*                                                                              *
//* Input  : iVal  : integer value to be converted                               *
//*                  Supported value range: plus/minus 9.999999999999 terabytes  *
//*          fWidth: field width (number of display columns)                     *
//*                  range: 1 to FI_MAX_FIELDWIDTH                               *
//*          lJust : 'false' : retain leading spaces up to 'fWidth'              *
//*                  'true'  : strip leading spaces to left-justify the value    *
//*                            in the field. (result may be less than fWidth)    *
//*          sVal  : 'false' : iVal is an _unsigned_ value                       *
//*                  'true'  : iVal is a _signed_ value                          *
//*          sign  : 'false' : only negative values are signed                   *
//*                  'true'  : always prepend a '+' or '-' sign.                 *
//*          kibi  : 'false' : calculate as a decimal value (powers of 10)       *
//*                            kilobyte, megabyte, gigabyte                      *
//*                  'true'  : calculate as a binary value (powers of 2)         *
//*                            kibibyte, mebibyte, gibibyte                      *
//*         units  : member of enum fiUnits                                      *
//*                  specifies the format for the units suffix.                  *
//*                                                                              *
//* Returns: 'true' if successful                                                *
//*          'false' if field overflow (field will be filled with '#' chars)     *
//*                  note that oveflow can occur ONLY:                           *
//*                  if fWidth <= 3 OR if iVal >= 10. terabytes                  *
//********************************************************************************
//* Programmer's Note: There is a GNU extension to integer formatting which      *
//* allows grouping of digits according to the environment variable              *
//* 'LC_NUMERIC'.                                                                *
//* This is locale-specific and is indicated with an apostrophe format           *
//* specifier" "%'lld" or "%'llu". We aren't sure that we can trust this, but    *
//* if we don't, we will not get the locale-specific grouping.                   *
//*                                                                              *
//* -- The swprintf() automagic rounding seems to be reliable, but keep an       *
//*    eye on it.                                                                *
//* -- Floating-point rounding is notoriously surprising, so we do as little     *
//*    rounding in floating-point as possible.                                   *
//*                                                                              *
//* -- For three-column field formatting, see notes in 'fqiThreeColumn'.         *
//*                                                                              *
//********************************************************************************

bool gString::formatQInt ( unsigned long long iVal, short fWidth, bool lJust, 
                           bool sVal, bool sign, bool kibi, fiUnits units )
{
   wchar_t wsrc[FI_BUFFER_SIZE] ;         // work buffer
   short t, s ;                           // shift indices
   bool valueFits = true ;                // return value

   //* Range check the field width parameter.*
   if ( fWidth < 1 )
      fWidth = 1 ;
   else if ( fWidth > FI_MAX_FIELDWIDTH )
      fWidth = FI_MAX_FIELDWIDTH ;

   //* Verify that 'units' is actually a member of fiUnits.*
   switch ( units )
   {
      case fiK:   break ;
      case fik:   break ;
      case fiKb:  break ;
      case fikB:  break ;
      case fiKiB: break ;
      default: units = fiK ;  break ;  // use the default suffix
   }

   //* Do a gross range check on the source value *
   long long sgnVal = (long long)iVal ;
   if ( (!sVal && iVal < TERA10) || 
        (sVal && ((sgnVal > TERA10m) && (sgnVal < (long long)TERA10))) )
   {
      // Programmer's Note: swprintf() will fail to insert the thousands
      // separators if the application is running in the 'C' (default) locale.
      // This is because the 'C' locale defines an empty string as the separator character.

      //* Format the full-width value with group separators.*
      if ( sign )    // signed integer ( plus or minus )
         swprintf ( wsrc, (FI_MAX_8BYTE_WIDTH + 1), L"%+'*lld", FI_MAX_8BYTE_WIDTH, iVal ) ;
      else if ( sVal )    // signed integer ( minus only )
         swprintf ( wsrc, (FI_MAX_8BYTE_WIDTH + 1), L"%'*lld", FI_MAX_8BYTE_WIDTH, iVal ) ;
      else           // unsigned integer
         swprintf ( wsrc, (FI_MAX_8BYTE_WIDTH + 1), L"%'*llu", FI_MAX_8BYTE_WIDTH, iVal ) ;

      //* Count the non-space characters *
      short nonSpace = ZERO ;
      for ( short i = ZERO ; wsrc[i] != NULLCHAR ; ++i )
      {
         if ( wsrc[i] != SPACE )
            ++nonSpace ;
      }

      //* If the uncompressed value fits within the specified field width, *
      //* then shift out any unneeded leading spaces.                      *
      if ( nonSpace <= fWidth )
      {
         if ( fWidth < FI_MAX_8BYTE_WIDTH )
         {
            t = ZERO ;
            s = FI_MAX_8BYTE_WIDTH - fWidth ;
            while ( s < FI_BUFFER_SIZE )
            {
               wsrc[t] = wsrc[s++] ;
               if ( wsrc[t] == NULLCHAR )
                  break ;
               ++t ;
            }
         }
      }

      //* Value string needs to be compressed to fit the field width.*
      else
      {
         #define DEBUG_COMP (0)  // DEBUGGING ONLY
         #if DEBUG_COMP != 0 // DEBUGGING ONLY
         gString gsd ;
         ofstream ofs ( gsDebugFile, ofstream::out | ofstream::app ) ;
         if ( ofs.is_open() )                         // if output file open
         {
            gsd = wsrc ;
            ofs << "compress\n========" << endl ;
         }
         #endif   // DEBUG_COMP

         //* Determine the value range and calculate the suffix string.*
         double dblVal = iVal ;
         if ( sVal )
            dblVal = sgnVal ;
         short vRange = ZERO ;   // 1==Kb, 2==Mb, 3==Gb, 4==Tb
         gString suffix( this->fqiAppendSuffix ( vRange, dblVal, units, sVal ) ) ;

         #if DEBUG_COMP != 0 // DEBUGGING ONLY
         gsd.compose( "fWidth:%02hd  suffix:'%S' dblVal:%lf  vRange:%hd", 
                      &fWidth, suffix.gstr(), &dblVal, &vRange ) ;
         ofs << gsd.ustr() << endl ;
         #endif   // DEBUG_COMP

         //* If field width > critical width, compress the data into the field.*
         if ( fWidth > FI_CRITICAL_WIDTH )
         {
            //** KByte range **
            if ( vRange == 1 )
            {  //* Format for floating-point kilobytes or kibibytes *
               dblVal /= kibi ? 1024.0 : 1000.0 ;
               if ( sign )    // signed integer ( plus or minus )
                  swprintf ( wsrc, (FI_MAX_8BYTE_WIDTH + 1), L"%+1.3lf%S", dblVal, suffix.gstr() ) ;
               else           // unsigned integer, OR signed integer ( minus only )
                  swprintf ( wsrc, (FI_MAX_8BYTE_WIDTH + 1), L"%1.3lf%S", dblVal, suffix.gstr() ) ;
            }
            //** MByte range **
            else if ( vRange == 2 )
            {  //* Format for floating-point megabytes or mebibytes *
               dblVal /= kibi ? 1024000.0 : 1000000.0 ;
               if ( sign )    // signed integer ( plus or minus )
                  swprintf ( wsrc, (FI_MAX_8BYTE_WIDTH + 1), L"%+1.6lf%S", dblVal, suffix.gstr() ) ;
               else           // unsigned integer, OR signed integer ( minus only )
                  swprintf ( wsrc, (FI_MAX_8BYTE_WIDTH + 1), L"%1.6lf%S", dblVal, suffix.gstr() ) ;
            }
            //** GByte range **
            else if ( vRange == 3 )
            {  //* Format for floating-point gigabytes or gibibytes *
               dblVal /= kibi ? 1024000000.0 : 1000000000.0 ;
               if ( sign )    // signed integer ( plus or minus )
                  swprintf ( wsrc, (FI_MAX_8BYTE_WIDTH + 1), L"%+1.9lf%S", dblVal, suffix.gstr() ) ;
               else if ( sVal )    // signed integer ( minus only )
                  swprintf ( wsrc, (FI_MAX_8BYTE_WIDTH + 1), L"%1.9lf%S", dblVal, suffix.gstr() ) ;
               else           // unsigned integer
                  swprintf ( wsrc, (FI_MAX_8BYTE_WIDTH + 1), L"%1.9lf%S", dblVal, suffix.gstr() ) ;
            }
            //** TByte range **
            else if ( vRange == 4 )
            {  //* Format for floating-point terabytes or tebibytes *
               dblVal /= kibi ? 1024000000000.0 : 1000000000000.0 ;
               if ( sign )    // signed integer ( plus or minus )
                  swprintf ( wsrc, (FI_MAX_8BYTE_WIDTH + 1), L"%+1.12lf%S", dblVal, suffix.gstr() ) ;
               else if ( sVal )    // signed integer ( minus only )
                  swprintf ( wsrc, (FI_MAX_8BYTE_WIDTH + 1), L"%1.12lf%S", dblVal, suffix.gstr() ) ;
               else           // unsigned integer
                  swprintf ( wsrc, (FI_MAX_8BYTE_WIDTH + 1), L"%1.12lf%S", dblVal, suffix.gstr() ) ;
            }
            else
            { /* arriving here would be a program error */ }

            #if DEBUG_COMP != 0 // DEBUGGING ONLY
            gsd.compose( "vRange(%hd): '%S'  (%lf)", &vRange, wsrc, &dblVal ) ;
            ofs << gsd.ustr() << endl ;
            #endif   // DEBUG_COMP

            //* Determine the width of the resulting field *
            gString gt( wsrc ) ;
            short gtCols = gt.gscols() ;
            if ( gtCols > fWidth )
            {
               if ( (valueFits = this->fqiRound ( gt, fWidth )) )
                  gt.copy( wsrc, FI_BUFFER_SIZE ) ;

               #if DEBUG_COMP != 0 // DEBUGGING ONLY
               gsd.compose( "Round(%hhd): '%S'  (%lf)", &valueFits, gt.gstr(), &dblVal ) ;
               ofs << gsd.ustr() << endl ;
               #endif   // DEBUG_COMP
            }
            else ;   // unmodified string fits into field
         }

         //*******************************************************************
         //* Field width is == critical width (3), compress data if possible.*
         //*******************************************************************
         else if ( fWidth == FI_CRITICAL_WIDTH )
         {  //* For three-column fields, our formatting options are limited.*
            //* See notes in method header.                                 *

            #if DEBUG_COMP != 0 // DEBUGGING ONLY
            gsd.compose( "ThreeCol: '%S'  (%lf)  suffix:'%S'", wsrc, &dblVal, suffix.gstr() ) ;
            ofs << gsd.ustr() << endl ;
            #endif   // DEBUG_COMP

            gString gt( wsrc ) ;
            valueFits = this->fqiThreeColumn ( gt, suffix, dblVal, vRange,
                                               sVal, sign, kibi ) ;
            #if DEBUG_COMP != 0 // DEBUGGING ONLY
            gsd.compose( "ThreeCol: '%S'  (%lf)  suffix:'%S'", wsrc, &dblVal, suffix.gstr() ) ;
            ofs << gsd.ustr() << endl ;
            #endif   // DEBUG_COMP

            if ( valueFits )
               gt.copy( wsrc, FI_BUFFER_SIZE ) ;
         }

         //************************************************************
         //* Due to potentially large rounding inaccuracies, one- and *
         //* two-column fields may hold only uncompressed data.       *
         //************************************************************
         else
         {
            valueFits = false ;
         }

         #if DEBUG_COMP != 0 // DEBUGGING ONLY
         ofs.close() ;
         #endif   // DEBUG_COMP
      }

      //* If left-justification specified *
      if ( valueFits && lJust && (wsrc[0] == SPACE) )
      {
         t = s = ZERO ;
         while ( wsrc[s] == SPACE )
            ++s ;
         while ( s < FI_BUFFER_SIZE )
         {
            wsrc[t] = wsrc[s++] ;
            if ( wsrc[t] == NULLCHAR )
               break ;
            ++t ;
         }
      }
   }     // gross range check
   else
      valueFits = false ;

   //* If field overflow, set the indicator characters *
   if ( !valueFits )
   {
      short i = ZERO ;
      while ( i < fWidth )
         wsrc[i++] = HASH ;
      wsrc[i] = NULLCHAR ;
   }

   this->reset() ;                        // initialize all our internal data
   this->utf32_2_utf8 ( wsrc ) ;
   return valueFits ;

}  //* End formatQInt() *

//*************************
//*       fqiRound        *
//*************************
//********************************************************************************
//* PRIVATE METHOD:                                                              *
//* ---------------                                                              *
//* Called by 'formatQInt()' round the value represented by the string up or     *
//* down to fit the value into the specified field width.                        *
//* 1) determine how many digits need to be removed                              *
//* 2) if entire decimal fraction removed, then also remove decimal point        *
//* 3) determine whether the removed digits will cause round-up or truncate      *
//*    - if truncate, then simply erase the digits                               *
//*    - if round up, then round the remaining digits                            *
//* 4) if rounding up causes creation of a new MSD (most-significant-digit)      *
//*    - if value still fits in the field, we're done                            *
//*    - if rounding causes a field overflow, remove another decimal place to    *
//*      make room if possible                                                   *
//*    - if result is a whole number, then test for value crossing the next      *
//*      multiple of 1,000                                                       *
//*      - if yes, then divide the value by 1,000 and update the suffix, then    *
//*        fit the new n.nnn...Z value if possible                               *
//*        - if it fits, return success                                          *
//*        - else, return failure                                                *
//*      - if no, then we cannot compress the value any further.                 *
//*        - if it fits, return success                                          *
//*        - else, return failure                                                *
//* 5) pad on left if necessary to fully fill the field                          *
//*                                                                              *
//*                                                                              *
//* Input  : gt      : (by reference) source string                              *
//*          fWidth  : width of target field                                     *
//*                                                                              *
//* Returns: 'true'  if string now fits in the target field                      *
//*          'false' if string is still too wide for field                       *
//********************************************************************************
//* Note: We make the assumption that each digit and each symbol is a            *
//*       single-column character. If this is not true, we will crash and burn.  *
//*                                                                              *
//********************************************************************************

bool gString::fqiRound ( gString& gt, short fWidth )
{
   wchar_t wsrc[FI_BUFFER_SIZE] ;      // work buffer
   gt.copy( wsrc, FI_BUFFER_SIZE ) ;   // make a working copy
   long long int cmpVal,               // comparison value for removed digits
                 rndVal ;              // integer representation of digits being removed
   short pi = ZERO,                    // index of decimal point
         di = ZERO,                    // index of decimal fraction
         si = ZERO,                    // index of suffix substring
         ri = ZERO,                    // index of digits to be removed
         remCols = gt.gscols() - fWidth, // number of excess columns
         decCount = ZERO,              // number of decimal digits
         s = ZERO, t = ZERO ;          // source and target indices
   bool  fits = true ;                 // return value

   //* Find the decimal point and the decimal fraction.            *
   //* (note that some locales may use '.' as separator character) *
   if ( (t = gt.findlast( POINT )) >= ZERO )
      pi = t ;
   di = pi + 1 ;

   //* Find the suffix *
   for ( si = di ; wsrc[si] <= L'9' ; ++si ) ;
   decCount = si - di ;       // difference is the number of decimal places

   //* Calculate the round-up threshold *
   cmpVal = 5 ;
   for ( short i = 1 ; i < decCount ; ++i )
   {
      if ( i == remCols )
         break ;
      cmpVal *= 10 ;
   }

   //* Set index of digits to be removed and scan their value *
   if ( remCols <= decCount )
      ri = si - remCols ;
   else
      ri = di ;
   swscanf ( &wsrc[ri], L"%lld",  &rndVal ) ;
   if ( remCols >= decCount )
      --ri ;

   //* Shift suffix to the left, removing targeted LSDs. *
   s = si ;          // index the suffix
   si = t = ri ;     // index the target
   while ( wsrc[s] != NULLCHAR )
      wsrc[t++] = wsrc[s++] ;
   wsrc[t] = NULLCHAR ;

   //* If rounding the value upward *
   if ( rndVal > cmpVal )
   {
      for ( s = t - 1 ; wsrc[s] > L'9' ; --s ) ; // index the least-significant digit
      while ( s >= ZERO )
      {
         if ( wsrc[s] == POINT )    // step over the decimal point (if any)
            --s ;
         if ( wsrc[s] >= L'0' && wsrc[s] <= L'8' )
         {  //* Incrementing digit will not cause a carry to next *
            //* digit, so value is fully rounded and we're done.  *
            ++wsrc[s] ;
            break ;
         }
         else
         {
            //* If the indexed character is a digit, update it.*
            if ( wsrc[s] == L'9' )
               wsrc[s--] = L'0' ;

            //* If the MSD has been updated, and we still have *
            //* a remainder, then insert a '1' as the new MSD. *
            if ( s < ZERO || (wsrc[s] == MINUS || wsrc[s] == PLUS) )
            {  //* Save the sign character, if any.*
               wchar_t signChar = SPACE ;
               if ( (s >= ZERO) && ((wsrc[s] == MINUS) || (wsrc[s] == PLUS)) )
                  signChar = wsrc[s] ;

               //* If the MSD has been updated, and we still have *
               //* a remainder, then insert a '1' as the new MSD. *
               short ip = s + 1 ;         // insertion index
               for ( s = ZERO ; wsrc[s] != NULLCHAR ; ++s ) ;
               t = s + 1 ;
               while ( s >= ip )
                  wsrc[t--] = wsrc[s--] ;
               wsrc[ip] = L'1' ;

               //* If data overflow, reformat the data *
               //* Scan the data as a floating-point value.*
               double dblVal ;
               swscanf ( &wsrc[ip], L"%lf", &dblVal ) ;
               gt = wsrc ;
               if ( (gt.gscols() > fWidth) || dblVal >= 1000.0 )
               {
                  //* If the integer portion of value >= 1000 *
                  //* then divide by 1000 and bump the suffix.*
                  if ( dblVal >= 1000.0 )
                  {
                     if ( (fits = this->fqiBumpSuffix ( wsrc, gt )) )
                     {
                        dblVal /= 1000.0 ;   // integer should now be a single digit
                        //* Determine number of decimal places for the new value.*
                        decCount = fWidth - 
                           ((signChar == SPACE ? 0 : 1) + gt.gscols() + 2) ;
                        if ( decCount == -1 )   // if decimal point is unneeded
                           decCount = ZERO ;
                        if ( decCount < ZERO )
                           fits = false ;
                     }
                  }
                  else
                  {  //* Isolate the suffix while counting   *
                     //* the digits to the left of the point.*
                     // Programmer's Note: It is possible to round upward to
                     // 10Tb or downward to -10Tb even though values >= 10Tb
                     // are not supported by the algorithm.
                     short iCount = ZERO ;
                     bool  pFound = false ;
                     for ( si = ZERO ; wsrc[si] <= L'9' ; ++si )
                     {
                        if ( wsrc[si] == POINT )
                           pFound = true ;
                        if ( !pFound && (wsrc[si] >= L'0' && wsrc[si] <= L'9') )
                           ++iCount ;
                     }
                     gt = &wsrc[si] ;        // save the suffix

                     //* Determine number of decimal places for the new value.*
                     decCount = fWidth - 
                        ((signChar == SPACE ? 0 : 1) + iCount + gt.gscols() + 2) ;
                     if ( decCount == -1 )   // if decimal point is unneeded
                        decCount = ZERO ;
                     if ( decCount < ZERO )
                        fits = false ;
                  }

                  //* Reformat the data *
                  if ( fits )
                  {
                     wchar_t temPlate[32] ;
                     swprintf ( temPlate, 32, L"%%1.%hdlf%%S", decCount ) ;
                     swprintf ( wsrc, FI_BUFFER_SIZE, temPlate, dblVal, gt.gstr() ) ;
   
                     if ( signChar != SPACE )
                     {
                        for ( s = ZERO ; wsrc[s] != NULLCHAR ; ++s ) ;
                        t = s + 1 ;
                        while ( s >= ZERO )
                           wsrc[t--] = wsrc[s--] ;
                        wsrc[ZERO] = signChar ;
                     }
                  }
               }     // if(gt.gscols()>fWidth)
               break ;
            }
         }
      }
   }

   //* If the string now fits within the field *
   gt = wsrc ;
   if ( gt.gscols() <= fWidth )
   {
      //* Pad on left to fill field *
      while ( gt.gscols() < fWidth )
         gt.insert( SPACE ) ;
   }
   else
      fits = false ;

   return fits ;

}  //* End fqiRound() *

//*************************
//*    fqiThreeColumn     *
//*************************
//********************************************************************************
//* PRIVATE METHOD:                                                              *
//* Called by 'formatQInt()' to compress data into a three-column field.         *
//*                                                                              *
//* Input  : gt      : (by reference) source string                              *
//*          suffix  : (by reference) suffix string                              *
//*          dblVal  : input value as a floating-point number                    *
//*          vRange  : value range: 1==Kb, 2==Mb, 3==Gb, 4==Tb                   *
//*          sVal    : signed value                                              *
//*          sign    : forced sign                                               *
//*          kibi    : kibibytes, mebibytes, gibibytes tebibytes                 *
//*                                                                              *
//* Returns: 'true'  if string now fits in the target field                      *
//*          'false' if string is still too wide for field                       *
//********************************************************************************
//* Notes on three-column field width:                                           *
//* Practically, we have only the following formatting options for 3-column      *
//* fields:                                                                      *
//* -- For no suffix:                                                            *
//*      unsigned values must be >= __0 && <= 999                                *
//*        signed values must be >= -99 && <= +99                                *
//* -- For a one-column suffix:                                                  *
//*      unsigned values must be >= _1K && <= 99K, OR                            *
//*                              >= _1M && <= 99M, OR                            *
//*                              >= _1G && <= 99G, OR                            *
//*                              >= _1T && <= _9T                                *
//*        signed values must be >= -9K && <= +9K, OR                            *
//*                              >= -9M && <= +9M, OR                            *
//*                              >= -9G && <= +9G, OR                            *
//*                              >= -9T && <= +9T                                *
//*                                                                              *
//* We must make a value judgement on rounding a value to the next multiple      *
//* of 1000. The choices are:                                                    *
//*    a) if ( > 500.0z ) round up, else truncate                                *
//*       if ( < -500.0z ) round down, else truncate                             *
//*    b) if ( > 950.0z ) round up, else truncate                                *
//*       if ( < -950.0z ) round down, else truncate                             *
//*    c) if ( > 99.5z || < -9.5z ) report overflow                              *
//* where 'z' is the current suffix character. All are valid rounding choices,   *
//* but to discourage the use of three-column fields, we go with option 'c',     *
//* and do not round from Kbytes to Mbytes, etc. with the following exceptions:  *
//*      a) round up from    0.95...1 to  1z                                     *
//*      b) round down from -0.95...1 to -1z                                     *
//*                                                                              *
//* IMPORTANT NOTE: This is a complex algorithm, and therefore fragile and       *
//*                 likely to break. Keep an eye on it.                          *
//*                 May be able to simplify the algorithm in a future revision.  *
//*                                                                              *
//* -- For a two-column or three-column suffix, the range of values that will    *
//*    fit into a three-column field are not worth worring about.                *
//* -- Field width of two columns can hold only >= -9 && <= 99                   *
//* -- Field width of one column can hold only >= 0 && <= 9                      *
//*                                                                              *
//********************************************************************************

bool gString::fqiThreeColumn ( gString& gt, gString& suffix, double dblVal, 
                               short vRange, bool sVal, bool sign, bool kibi )
{
   wchar_t wsrc[FI_BUFFER_SIZE] ;         // work buffer
   bool fits = true ;                  // return value

   //* Test the width of the suffix string. Must be a one-column suffix. *
   if ( suffix.gscols() == 1 )
   {
      if ( vRange == 1 )
      {
         dblVal /= kibi ? 1024.0 : 1000.0 ;

         //* Round to nearest Kbyte (excludes -0.950K through 0.000) *
         if ( ((dblVal >= -9.500) && (dblVal < -0.9500)) ||
              ( (dblVal > 0.9500 ) && (dblVal <= 9.500)) )
         {
            int targVal = (int)dblVal ;
            double remVal = dblVal - targVal ;
            if ( remVal > 0.500 )
               ++targVal ;
            else if ( remVal < -0.500 )
               --targVal ;
            if ( targVal != ZERO )
            {
               if ( sign )
                  swprintf ( wsrc, FI_BUFFER_SIZE, L"%+d%S", targVal, suffix.gstr() ) ;
               else if ( sVal )
                  swprintf ( wsrc, FI_BUFFER_SIZE, L"%2d%S", targVal, suffix.gstr() ) ;
               else
                  swprintf ( wsrc, FI_BUFFER_SIZE, L"% 2u%S", targVal, suffix.gstr() ) ;
            }
            else
            {  //* Do not report " 0K" value *
               fits = false ;
            }
         }
         //* Positive 0 through 99 Kbytes *
         else if ( !sVal && !sign && (dblVal <= 99.500) )
         {
            unsigned int targVal = (unsigned int)dblVal ;
            double remVal = dblVal - targVal ;
            if ( remVal > 0.500 )
               ++targVal ;
            swprintf ( wsrc, FI_BUFFER_SIZE, L"% 2u%S", targVal, suffix.gstr() ) ;
         }
         //* Can we round up to one megabyte? *
         else if ( dblVal > 950.000)
         {
            swprintf ( wsrc, FI_BUFFER_SIZE, L" 1%S", suffix.gstr() ) ;
            this->fqiBumpSuffix ( wsrc, gt ) ;
         }
         //* Can we round down to minus one megabyte? *
         else if ( dblVal < -950.000 )
         {
            swprintf ( wsrc, FI_BUFFER_SIZE, L"-1%S", suffix.gstr() ) ;
            this->fqiBumpSuffix ( wsrc, gt ) ;
         }
         else
            fits = false ;
      }
      else if ( vRange == 2 )
      {
         dblVal /= kibi ? 1024000.0 : 1000000.0 ;

         //* Round to nearest Mbyte *
         if ( ((dblVal >= -9.500) && (dblVal < -0.950000)) ||
              ( (dblVal > 0.950000 ) && (dblVal <= 9.500000)) )
         {
            int targVal = (int)dblVal ;
            double remVal = dblVal - targVal ;
            if ( remVal > 0.500 )
               ++targVal ;
            else if ( remVal < -0.500 )
               --targVal ;
            if ( targVal != ZERO )
            {
               if ( sign )
                  swprintf ( wsrc, FI_BUFFER_SIZE, L"%+d%S", targVal, suffix.gstr() ) ;
               else if ( sVal )
                  swprintf ( wsrc, FI_BUFFER_SIZE, L"%2d%S", targVal, suffix.gstr() ) ;
               else
                  swprintf ( wsrc, FI_BUFFER_SIZE, L"% 2u%S", targVal, suffix.gstr() ) ;
            }
            else
            {  //* Do not report " 0M" value *
               fits = false ;
            }
         }
         //* Positive 0 through 99 Mbytes *
         else if ( !sVal && !sign && (dblVal <= 99.500) )
         {
            unsigned int targVal = (unsigned int)dblVal ;
            double remVal = dblVal - targVal ;
            if ( remVal > 0.500 )
               ++targVal ;
            swprintf ( wsrc, FI_BUFFER_SIZE, L"% 2u%S", targVal, suffix.gstr() ) ;
         }
         //* Can we round up to one gigabyte? *
         else if ( dblVal > 950.000)
         {
            swprintf ( wsrc, FI_BUFFER_SIZE, L" 1%S", suffix.gstr() ) ;
            this->fqiBumpSuffix ( wsrc, gt ) ;
         }
         //* Can we round down to minus one gigabyte? *
         else if ( dblVal < -950.000 )
         {
            swprintf ( wsrc, FI_BUFFER_SIZE, L"-1%S", suffix.gstr() ) ;
            this->fqiBumpSuffix ( wsrc, gt ) ;
         }
         else
            fits = false ;
      }
      else if ( vRange == 3 )
      {
         dblVal /= kibi ? 1024000000.0 : 1000000000.0 ;

         //* Round to nearest Gbyte *
         if ( ((dblVal >= -9.500) && (dblVal < -0.950000)) ||
              ( (dblVal > 0.950000 ) && (dblVal <= 9.500000)) )
         {
            int targVal = (int)dblVal ;
            double remVal = dblVal - targVal ;
            if ( remVal > 0.500 )
               ++targVal ;
            else if ( remVal < -0.500 )
               --targVal ;
            if ( targVal != ZERO )
            {
               if ( sign )
                  swprintf ( wsrc, FI_BUFFER_SIZE, L"%+d%S", targVal, suffix.gstr() ) ;
               else if ( sVal )
                  swprintf ( wsrc, FI_BUFFER_SIZE, L"%2d%S", targVal, suffix.gstr() ) ;
               else
                  swprintf ( wsrc, FI_BUFFER_SIZE, L"% 2u%S", targVal, suffix.gstr() ) ;
            }
            else
            {  //* Do not report " 0G" value *
               fits = false ;
            }
         }
         //* Positive 0 through 99 Gbytes *
         else if ( !sVal && !sign && (dblVal <= 99.500) )
         {
            unsigned int targVal = (unsigned int)dblVal ;
            double remVal = dblVal - targVal ;
            if ( remVal > 0.500 )
               ++targVal ;
            swprintf ( wsrc, FI_BUFFER_SIZE, L"% 2u%S", targVal, suffix.gstr() ) ;
         }
         //* Can we round up to one terabyte? *
         else if ( dblVal > 950.000)
         {
            swprintf ( wsrc, FI_BUFFER_SIZE, L" 1%S", suffix.gstr() ) ;
            this->fqiBumpSuffix ( wsrc, gt ) ;
         }
         //* Can we round down to minus one terabyte? *
         else if ( dblVal < -950.000 )
         {
            swprintf ( wsrc, FI_BUFFER_SIZE, L"-1%S", suffix.gstr() ) ;
            this->fqiBumpSuffix ( wsrc, gt ) ;
         }
         else
            fits = false ;
      }
      else  // vRange==4 (TBytes)
      {
         dblVal /= kibi ? 1024000000000.0 : 1000000000000.0 ;

         //* Round to nearest Tbyte *
         if ( ((dblVal >= -9.500) && (dblVal < -0.950000)) ||
              ( (dblVal > 0.950000 ) && (dblVal <= 9.500000)) )
         {
            int targVal = (int)dblVal ;
            double remVal = dblVal - targVal ;
            if ( remVal > 0.500 )
               ++targVal ;
            else if ( remVal < -0.500 )
               --targVal ;
            if ( targVal != ZERO )
            {
               if ( sign )
                  swprintf ( wsrc, FI_BUFFER_SIZE, L"%+d%S", targVal, suffix.gstr() ) ;
               else if ( sVal )
                  swprintf ( wsrc, FI_BUFFER_SIZE, L"%2d%S", targVal, suffix.gstr() ) ;
               else
                  swprintf ( wsrc, FI_BUFFER_SIZE, L"% 2u%S", targVal, suffix.gstr() ) ;
            }
            else
            {  //* Do not report " 0G" value *
               fits = false ;
            }
         }
         //* Positive 0 through 99 Tbytes *
         // Programmer's Note: It is possible to round upward to 10Tb
         // even though values >= 10Tb are not supported by the algorithm.
         else if ( !sVal && !sign && (dblVal <= 99.500) )
         {
            unsigned int targVal = (unsigned int)dblVal ;
            double remVal = dblVal - targVal ;
            if ( remVal > 0.500 )
               ++targVal ;
            swprintf ( wsrc, FI_BUFFER_SIZE, L"% 2u%S", targVal, suffix.gstr() ) ;
         }
         else
            fits = false ;
      }
   }
   else     // suffix too wide for accurate value rounding
      fits = false ;

   gt = wsrc ;
   return fits ;

}  //* End fqiThreeColumn()

//*************************
//*    fqiAppendSuffix    *
//*************************
//********************************************************************************
//* PRIVATE METHOD:                                                              *
//* Called by 'formatQInt()' to determine which units suffix should be           *
//* appended to the formatted integer string.                                    *
//*                                                                              *
//* Note that caller has already performed the +/- 10Tb range check.             *
//*                                                                              *
//* Input  : vRange  : (by reference) receives the value range:                  *
//*                    1==Kb, 2==Mb, 3==Gb, 4==Tb                                *
//*          dblVal  : value to be ranged                                        *
//*          units   : member of enum fiUnits                                    *
//*          sVal    : if source value is to be interpreted a signed value       *
//*                                                                              *
//* Returns: const pointer to the wchar_t suffix string                          *
//********************************************************************************

const wchar_t* gString::fqiAppendSuffix ( short& vRange, double dblVal,
                                          fiUnits units, bool sVal )
{
   const wchar_t* wPtr ;
   if ( sVal ) // signed integer
   {
      if ( (dblVal > MEGABYTEm) && (dblVal < MEGABYTE) )       vRange = 1 ;
      else if ( (dblVal > GIGABYTEm) && (dblVal < GIGABYTE) )  vRange = 2 ;
      else if ( (dblVal > TERABYTEm) && (dblVal < TERABYTE) )  vRange = 3 ;
      else                                                     vRange = 4 ;
   }
   else        // unsigned integer
   {
      if ( dblVal < MEGABYTE )                                 vRange = 1 ;
      else if ( dblVal < GIGABYTE )                            vRange = 2 ;
      else if ( dblVal < TERABYTE )                            vRange = 3 ;
      else                                                     vRange = 4 ;
   }

   if ( vRange == 1 )
   {
      switch ( units )
      {
         case fiK:   wPtr = L"K" ;      break ;
         case fik:   wPtr = L"k" ;      break ;
         case fiKb:  wPtr = L"Kb" ;     break ;
         case fikB:  wPtr = L"kB" ;     break ;
         case fiKiB: wPtr = L"KiB" ;    break ;
      }
   }
   else if ( vRange == 2 )
   {
      switch ( units )
      {
         case fiK:   wPtr = L"M" ;      break ;
         case fik:   wPtr = L"m" ;      break ;
         case fiKb:  wPtr = L"Mb" ;     break ;
         case fikB:  wPtr = L"mB" ;     break ;
         case fiKiB: wPtr = L"MiB" ;    break ;
      }
   }
   else if ( vRange == 3 )
   {
      switch ( units )
      {
         case fiK:   wPtr = L"G" ;      break ;
         case fik:   wPtr = L"g" ;      break ;
         case fiKb:  wPtr = L"Gb" ;     break ;
         case fikB:  wPtr = L"gB" ;     break ;
         case fiKiB: wPtr = L"GiB" ;    break ;
      }
   }
   else     // vRange == 4
   {
      switch ( units )
      {
         case fiK:   wPtr = L"T" ;      break ;
         case fik:   wPtr = L"t" ;      break ;
         case fiKb:  wPtr = L"Tb" ;     break ;
         case fikB:  wPtr = L"tB" ;     break ;
         case fiKiB: wPtr = L"TiB" ;    break ;
      }
   }
   return wPtr ;

}  //* End fqiAppendSuffix() *

//*************************
//*     fqiBumpSuffix     *
//*************************
//********************************************************************************
//* PRIVATE METHOD                                                               *
//* Called by 'formatQInt' rounding methods if a round-up causes an overflow     *
//* into the next multiple of 1000.                                              *
//*                                                                              *
//*               k-to-m, m-to-g, g-to-t, K-to-M, M-to-G, G-to-T                 *
//*                                                                              *
//* Input  : wsrc   : source data                                                *
//*          gt     : (by reference)                                             *
//*                   on successful return, contains suffix string               *
//*                                                                              *
//* Returns: 'true'  if successful                                               *
//* Returns: 'false' if overflow beyond 9.999... terabytes/tebibytes             *
//********************************************************************************

bool gString::fqiBumpSuffix ( wchar_t* wsrc, gString& gt )
{
   short t ;                  // char index
   bool fits = true ;         // return value

   // Note: Both '+' and '-' are less than '0'
   for ( t = ZERO ; wsrc[t] <= L'9' ; ++t ) ;
   switch ( wsrc[t] )
   {
      case L'K':  wsrc[t] = L'M' ;     break ;
      case L'M':  wsrc[t] = L'G' ;     break ;
      case L'G':  wsrc[t] = L'T' ;     break ;
      case L'k':  wsrc[t] = L'm' ;     break ;
      case L'm':  wsrc[t] = L'g' ;     break ;
      case L'g':  wsrc[t] = L't' ;     break ;
      default:
         // If we would round beyond terabyte range, then its an overflow.
         fits = false ;
         break ;
   }
   if ( fits )
      gt = &wsrc[t] ;

   return fits ;

}  //* End fqiBumpSuffix() *


//*************************
//*        replace        *
//*************************
//********************************************************************************
//* PRIVATE METHOD: Called by the public 'replace' methods.                      *
//* ---------------                                                              *
//* Replace the specified source substring with the provided substring.          *
//*                                                                              *
//* Input  : src    : source data to be matched                                  *
//*          newtxt : data to overwrite existing text                            *
//*          offset : character index at which to begin search                   *
//*          casesen: if 'false', then scan IS NOT case sensitive                *
//*                   if 'true, then scan IS case sensitive                      *
//*          all    : if 'false', then replace only the first occurance found    *
//*                   if 'true',  then replace all occurances of the specified   *
//*                               substring                                      *
//*                                                                              *
//* Returns: 'true' if successful                                                *
//*          returns 'false' if error (existing data not modified):              *
//*            a) no matching source substring found                             *
//*            b) 'src' is a empty string or a null character                    *
//*            c) offset < ZERO or offset is beyond existing data                *
//*            d) modifying the data would cause buffer overflow (>gsALLOCMAX)   *
//********************************************************************************

bool gString::replace ( const gString& src, const gString& newtxt, 
                        int32_t offset, bool casesen, bool all )
{
   bool status = false ;

   if ( (src.gschars() > 1) && (offset >= ZERO) && 
        (offset < (this->gsChars - 1)) )
   {
      if ( src != newtxt )
      {
         wchar_t* wbk = new wchar_t[this->gsw] ; // make a backup of existing data
         this->copy( wbk, this->gsw ) ;

         //* Determine number of occurances of 'src' to be replaced. *
         //* If all==false, then this will be ZERO or 1.             *
         int32_t instances = ZERO,
                 ip = offset ;
         do
         {
            if ( (ip = this->find( src, ip, casesen )) >= ZERO )
            {
               ++instances ;
               ++ip ;
            }
         }
         while ( ip >= ZERO && all != false ) ;

         //* Test for potential buffer overflow and *
         //* expand buffer if necessary.            *
         if ( (newtxt.gschars()) > (src.gschars()) )
         {
            int free_space = this->freeSpace (),
                need_space = this->gsChars + ((newtxt.gschars()) - (src.gschars())) * instances,
                expand_by = need_space - free_space ;
            if ( expand_by > ZERO )
               this->reAlloc ( 1 + expand_by / gsALLOCMIN ) ;
         }

         ip = offset ;
         for ( ; instances > ZERO && ip >= ZERO ; instances-- )
         {
            //* Erase existing string and get insertion point *
            if ( (ip = this->erase( src, ip, casesen )) >= ZERO )
            {
               //* Insert the replacement data and  *
               //* step past the just-inserted text.*
               if ( (this->insert( newtxt.gstr(), ip )) < this->gsw )
               {
                  ip = this->after( newtxt, ip ) ;
                  status = true ;
               }
               else
               {  //* Buffer overflow, restore original data.*
                  this->utf32_2_utf8 ( wbk ) ;
                  status = false ;
                  break ;
               }
            }
         }

         delete [] wbk ;                  // release the dynamic allocation
      }

      //* If caller is trying to fool us with   *
      //* identical source and replacement text *
      else
         status = true ;
   }
   return status ;

}  //* End replace() *

//*************************
//*    textReverse_all    *
//*************************
//********************************************************************************
//* Private Method. Called ONLY by the public 'textReverse()' method.            *
//* ---------------                                                              *
//* Reverse the order of characters in the text string.                          *
//* See notes in parent method.                                                  *
//*                                                                              *
//* Input  : punct : if 'false', invert entire string                            *
//*                  if 'true' AND if a punctuation mark is seen at either end   *
//*                     of the string, invert everything except the punctuation  *
//*                     mark(s).                                                 *
//*                                                                              *
//* Returns: number of wchar_t characters in gString object                      *
//********************************************************************************

int32_t gString::textReverse_all ( bool punct )
{
   const short punctCHARS = 26 ;
   const wchar_t punctChars[punctCHARS] = 
   {  // U+0021 U+002C U+002E U+003A U+003D U+003F U+00A1 U+00BF U+037E
         L'!',  L',',  L'.',  L':',  L';',  L'?',  L'¡',  L'¿',  L';',
      // U+0387 U+055C U+055D U+055E U+0589 U+060C U+061B U+061F U+204F
         L'·',  L'՜',  L'՝',  L'՞',  L'։',  L'،',  L'؛',  L'؟',  L'⁏',
      // U+2E2E U+2E41 U+FF01 U+FF0C U+FF0E U+FF1A U+FF1B U+FF1F 
         L'⸮',  L'⹁',  L'！',  L'，',  L'．',  L'：',  L'；',  L'？', 
   } ;

   wchar_t wtmp ;
   int32_t src = this->gsChars - 2,       // reference last char before nullchar
           trg = ZERO ;                   // reference head of buffer

   if ( punct )
   {
      for ( int32_t i = ZERO ; i < punctCHARS ; ++i )
      {
         if ( this->gStr[trg] == punctChars[i] )
         { ++trg ; break ; }
      }
      for ( int32_t i = ZERO ; i < punctCHARS ; ++i )
      {
         if ( this->gStr[src] == punctChars[i] )
         { --src ; break ; }
      }
   }

   while ( src > trg )
   {
      wtmp = this->gStr[trg] ;
      this->gStr[trg++] = this->gStr[src] ;
      this->gStr[src--] = wtmp ;
   }
   this->utf32_2_utf8 () ;

   return ( this->gsChars ) ;

}  //* End textReverse_all() *

//*************************
//*      gsForm_2_gs      *
//*************************
//********************************************************************************
//* Private Method:                                                              *
//* ---------------                                                              *
//* Convert gsForm-class data to gString data.                                   *
//* This is used to convert data captured through a formatting specification,    *
//* compose(), append() etc.                                                     *
//*                                                                              *
//* Input  : gsf : an initialized gsForm class object containing formatting      *
//*                specification string and pointers to data values              *
//*          wsrc: pointer to caller's buffer to hold formatted wchar_t data     *
//*                (To avoid problems, 'wsrc S/B gsALLOCMAX.)                    *
//*          len : length of 'wsrc' buffer                                       *
//*                                                                              *
//* Returns: number of characters written to 'wsrc'.                             *
//********************************************************************************
//* Programmer's Note:                                                           *
//* - swprintf() returns the number of characters written, (excluding the null   *
//*   terminator). If a value < ZERO is returned, then the output was            *
//*   truncated due to lack of output-buffer space.                              *
//* - Because we call swprintf() with only one format specification at a time,   *
//*   if return value < ZERO, it means that the formatting operation was not     *
//*   performed at all, and that the output string has not been modified by      *
//*   the library call.                                                          *
//* - In this case, caller may be surprised to find that the output was          *
//*   truncated although the buffer was not completely filled.                   *
//*   For this reason, for 's' and 'S' format specifications ONLY, the string    *
//*   which caused the swprintf() error is truncated to fit the available space, *
//*   and then added to the target buffer. This causes the return value to       *
//*   be zero, indicating that the target buffer is completely full.             *
//*   Caller can then either accept the truncated data as presented, OR resize   *
//*   the storage capacity and try again.                                        *
//*   - Because all callers currently set the 'len' parameter explicitly to the  *
//*     maximum allocation space, the truncated data will probably be the        *
//*     final form.                                                              *
//*   - Note that the 'gsmeter' test app, test#'Ba' comes up to this limit       *
//*     without tripping the swprintf error. See gsmBufferOverflowA().           *
//*     The gsmeter test gsmBufferOverflowB() intentionally invokes the          *
//*     swprintf error for 's' and 'S' format specifiers to validate the         *
//*     recovery algorithm described above.                                      *
//*   - Note that it is _extremely_ unlikely that swprintf() will return an      *
//*     error for any format specifier other than 's' and 'S'; but if it does    *
//*     occur for numeric or other non-string data, the value will simply not    *
//*     be converted for output.                                                 *
//********************************************************************************

int gString::gsForm_2_gs ( const gsForm& gsf, wchar_t* wsrc, int len )
{
   const int32_t fmtSPACE = 32 ;
   wchar_t  fmt[fmtSPACE] ;         // holds format specification for swprintf() call
   int      swret  = ZERO ;         // if swprintf returns < zero, buffer is full
   int32_t  wsrci  = ZERO,          // index into wsrc[]
            fmti,                   // index into fmt[]
            fsi    = ZERO,          // index into gsf.fmtSpec[]
            pCount = ZERO,          // number of conversion symbols found
            wsrcSpace = len - 1 ;   // remaining free space in wsrc buffer


   *wsrc = NULLCHAR ;            // start with an empty output buffer

   while ( (gsf.fmtSpec[fsi] != NULLCHAR) && (pCount < gsFormMAXARGS) && (wsrcSpace > 1) )
   {
      //* Scan for next conversion symbol, copying literal data as-is *
      if ( gsf.fmtSpec[fsi] != '%' )
      {
         wsrc[wsrci++] = gsf.fmtSpec[fsi++] ;
         wsrc[wsrci] = NULLCHAR ;   // keep string terminated at all times
         --wsrcSpace ;
      }
      else
      {  //* Conversion symbol found. Isolate the conversion *
         //* specification for this argument.                *
         fmti = ZERO ;
         fmt[fmti++] = gsf.fmtSpec[fsi++] ;    // save conversion symbol
         do
         {
            if (   (gsf.fmtSpec[fsi] == 's') || (gsf.fmtSpec[fsi] == 'S')
                || (gsf.fmtSpec[fsi] == 'c') || (gsf.fmtSpec[fsi] == 'C')
                || (gsf.fmtSpec[fsi] == 'd') || (gsf.fmtSpec[fsi] == 'i')
                || (gsf.fmtSpec[fsi] == 'x') || (gsf.fmtSpec[fsi] == 'X')
                || (gsf.fmtSpec[fsi] == 'u') || (gsf.fmtSpec[fsi] == 'o')
                || (gsf.fmtSpec[fsi] == 'f') || (gsf.fmtSpec[fsi] == 'p')
                || (gsf.fmtSpec[fsi] == 'e') || (gsf.fmtSpec[fsi] == 'E')
                || (gsf.fmtSpec[fsi] == 'g') || (gsf.fmtSpec[fsi] == 'G')
                || (gsf.fmtSpec[fsi] == 'a') || (gsf.fmtSpec[fsi] == 'A')
                || (gsf.fmtSpec[fsi] == 'n') || (gsf.fmtSpec[fsi] == 'm')
               )
            {
               //* Determine the data type for this conversion *
               char convType ;         // data type for converting argument
               fmt[fmti++] = convType = gsf.fmtSpec[fsi++] ; // save the data type
               fmt[fmti] = NULLCHAR ;  // terminate the specification string
               fmti -= 2 ;             // reference 'long' or 'short' modifier, if any
               if ( convType == 's' && fmt[fmti] == 'l' )
                  convType = 'S' ;     // wide character string
               else if ( convType == 'c' && fmt[fmti] == 'l' )
                  convType = 'C' ;     // wide character

               //* Integer specifiers *
               else if (   convType == 'd' || convType == 'i' || convType == 'x' 
                        || convType == 'X' || convType == 'u' || convType == 'o' )
               {  //* Determine the 'sizeof' flag for integer source *
                  if ( fmt[fmti] == 'h' )
                     convType = 'd' ;           // short int size
                  else if ( fmt[fmti] == 'q' || fmt[fmti] == 'L' 
                           || (fmti >= 2 && fmt[fmti] == 'l' && fmt[fmti-1] == 'l') )
                     convType = 'q' ;           // quad int (64-bit) size
                  else if ( fmt[fmti] == 'l' )
                     convType = 'D' ;           // long int size
                  else
                     convType = 'i' ;           // 'int' size
               }

               //* Floating-point specifiers *
               else if (   convType == 'f' 
                        || convType == 'e' || convType == 'E' 
                        || convType == 'g' || convType == 'G'
                        || convType == 'a' || convType == 'A'
                       )
               {
                  if ( fmt[fmti] == 'l' )
                     convType = 'F' ;
                  else if ( fmt[fmti] == 'L' )
                     convType = 'Q' ;
                  else
                     convType = 'f' ;
               }

               //* Target integer to receive character count. (rarely seen) *
               else if ( convType == 'n' )
               {
                  if ( fmt[fmti] == 'h' )
                     convType = 'n' ;
                  else
                     convType = 'N' ;
               }

               //* Create formatted output for this argument *
               swret = ZERO ; // initialize swprintf return value
                              // (in case it isn't called)
               if ( convType == 's' )
               {
                  // (see notes in composeFieldwidthBugFix())
                  if ( fmt[1] == 's' )       // no width specification
                  {
                     swret = swprintf ( &wsrc[wsrci], wsrcSpace, fmt, 
                                    (const char*)gsf.argList[pCount] ) ;
                  }
                  else
                  {
                     gString gs( (const char*)gsf.argList[pCount] ) ;
                     swret = composeFieldwidthBugFix ( convType, &wsrc[wsrci], wsrcSpace, fmt, gs ) ;
                  }
                  if ( swret < ZERO )
                  {
                     gString gstmp( gsALLOCMAX ) ;
                     gstmp.loadChars( (const char*)gsf.argList[pCount], wsrcSpace ) ;
                     gstmp.copy( &wsrc[wsrci], wsrcSpace ) ;
                     swret = gstmp.gschars() ;
                  }
               }
               else if ( convType == 'S' )
               {
                  if ( fmt[1] == 'S' || fmt[1] == 'l' )// no width specification
                  {
                     swret = swprintf ( &wsrc[wsrci], wsrcSpace, fmt, 
                                    (const wchar_t*)gsf.argList[pCount] ) ;
                  }
                  else
                  {
                     gString gs( (const wchar_t*)gsf.argList[pCount] ) ;
                     swret = composeFieldwidthBugFix ( convType, &wsrc[wsrci], wsrcSpace, fmt, gs ) ;
                  }
                  if ( swret < ZERO )
                  {
                     gString gstmp( gsALLOCMAX ) ;
                     gstmp.loadChars( (const wchar_t*)gsf.argList[pCount], wsrcSpace ) ;
                     gstmp.copy( &wsrc[wsrci], wsrcSpace ) ;
                     swret = gstmp.gschars() ;
                  }
               }
               else if ( convType == 'c' )
                  swret = swprintf ( &wsrc[wsrci], wsrcSpace, fmt, 
                                 *((const char*)(gsf.argList[pCount])) ) ;
               else if ( convType == 'C' )
                  swret = swprintf ( &wsrc[wsrci], wsrcSpace, fmt, 
                                 *((const wchar_t*)(gsf.argList[pCount])) ) ;
               else if ( convType == 'd' )
                  swret = swprintf ( &wsrc[wsrci], wsrcSpace, fmt, 
                                 *((short*)(gsf.argList[pCount])) ) ;
               else if ( convType == 'i' )
                  swret = swprintf ( &wsrc[wsrci], wsrcSpace, fmt, 
                                 *((int*)(gsf.argList[pCount])) ) ;
               else if ( convType == 'D' )
                  swret = swprintf ( &wsrc[wsrci], wsrcSpace, fmt, 
                                 *((long int*)(gsf.argList[pCount])) ) ;
               else if ( convType == 'q' )
                  swret = swprintf ( &wsrc[wsrci], wsrcSpace, fmt, 
                                 *((long long int*)(gsf.argList[pCount])) ) ;
               else if ( convType == 'f' )
                  swret = swprintf ( &wsrc[wsrci], wsrcSpace, fmt, 
                                 *((float*)(gsf.argList[pCount])) ) ;
               else if ( convType == 'F' )
                  swret = swprintf ( &wsrc[wsrci], wsrcSpace, fmt, 
                                 *((double*)(gsf.argList[pCount])) ) ;
               else if ( convType == 'Q' )
                  swret = swprintf ( &wsrc[wsrci], wsrcSpace, fmt, 
                                 *((long double*)(gsf.argList[pCount])) ) ;
               else if ( convType == 'p' )
               {
                  // NOTE: There is a bug in swprintf() which renders a NULL
                  //       pointer as "(" rather than as "(nil)".
                  if ( gsf.argList[pCount] != NULL )
                     swret = swprintf ( &wsrc[wsrci], wsrcSpace, fmt,
                                        gsf.argList[pCount] ) ;
                  else
                     swret = swprintf ( &wsrc[wsrci], wsrcSpace, L"%S", L"(nil)" ) ;
               }
               else if ( convType == 'm' )
                  swret = swprintf ( &wsrc[wsrci], wsrcSpace, fmt, 
                                 gsf.argList[pCount] ) ;
               else if ( convType == 'n' )
               {
                  *((short*)(gsf.argList[pCount])) = wsrci ;
                  wsrc[wsrci] = NULLCHAR ; // produces no output to formatted string
                  swret = ZERO ;
               }
               else if ( convType == 'N' )
               {
                  *((int*)(gsf.argList[pCount])) = (int)wsrci ;
                  wsrc[wsrci] = NULLCHAR ; // produces no output to formatted string
               }
               else  // unsupported data types 'n' and unknown types
               { 
                  wsrc[wsrci++] = '#' ;
                  wsrc[wsrci] = NULLCHAR ;
               }

               //* Find end of string (see important note in method header) *
               if ( swret >= ZERO )
               {
                  while ( wsrc[wsrci] != NULLCHAR )
                     ++wsrci ;
               }
               else  // buffer full
               {
                  wsrc[len - 1] = NULLCHAR ; // be sure data are null terminated
                  wsrcSpace = ZERO ;         // indicate loop termination
               }
               ++pCount ;        // reference next argument
               break ;           // finished with this conversion specification
            }  // if()

            #if BINARY_EXTENSION != 0
            else if ( (gsf.fmtSpec[fsi] == 'b') || (gsf.fmtSpec[fsi] == 'B') )
            {  //* Binary output extension to swprintf *
               //* (see notes in binaryFormat method)  *
               //* Determine the data type and modifiers for this conversion.*
               fmt[fmti++] = gsf.fmtSpec[fsi] ; // save the data type
               fmt[fmti] = NULLCHAR ;  // terminate the specification string
               fmti -= 2 ;             // reference 'long' or 'short' modifier, if any

               long long int intData ;
               wchar_t convBuff[100],  // receives formatted data
                       convType = gsf.fmtSpec[fsi++], // indicator: 'b' or 'B'
                       sepType = L'.' ;// default group seperator character
               short dataType,         // number of bytes in source integer
                     grpType = 4,      // grouping: by-4 or by-8
                     i = 1 ;
               bool appendType = true ;// assume append indicator (or none)

               //* Determine whether alternate seperation character specified.*
               if ( fmt[i] == '#' )    ++i ;
               else if ( fmt[i] == '-' && fmt[i+1] == '#' )   i += 2 ;
               for ( ; fmt[i] != NULLCHAR ; i++ )
               {
                  if ( ((fmt[i] >= SPACE && fmt[i] <= '/') && fmt[i] != '.') || 
                       (fmt[i] >= ':' && fmt[i] <= '@') || (fmt[i] == '[') || 
                       (fmt[i] >= ']' && fmt[i] <= '`') || 
                       (fmt[i] >= '{' && fmt[i] <= '~') )
                  { sepType = fmt[i] ; break ; }
               }

               //* Determine whether group-of-8 spacing specified.*
               //* ".8" (group-of-4 spacing is the default)       *
               for ( i = ZERO ; fmt[i] != NULLCHAR ; i++ )
               { if ( fmt[i] == '.' && fmt[i+1] == '8' ) { grpType = 8 ; break ; } }

               //* Initialize the data, data type and 'appendType' flag. *
               if ( fmt[fmti] == 'h' )
               {
                  if ( fmti >= 2 && fmt[fmti-1] == 'h' )
                  {
                     char x = *((char*)(gsf.argList[pCount])) ;
                     intData = (long long int)x ;
                     dataType = sizeof(char) ;     // byte int source
                  }
                  else
                  {
                     short x = *((short*)(gsf.argList[pCount])) ;
                     intData = (long long int)x ;
                     dataType = sizeof(short int) ;// short int source
                  }
               }
               else if ( fmt[fmti] == 'q' || fmt[fmti] == 'L' 
                        || (fmti >= 2 && fmt[fmti] == 'l' && fmt[fmti-1] == 'l') )
               {
                  intData = *((long long int*)(gsf.argList[pCount])) ;
                  dataType = sizeof(long long int) ;
               }
               else if ( fmt[fmti] == 'l' )
               {
                  long x = *((long*)(gsf.argList[pCount])) ;
                  intData = (long long int)x ;
                  dataType = sizeof(long int) ; // long int source
               }
               else
               {
                  int x = *((int*)(gsf.argList[pCount])) ;
                  intData = (long long int)x ;
                  dataType = sizeof(int) ;   // int source
               }
               if ( fmt[1] == '#' )    // prepend 'b' or 'B'
                  appendType = false ;
               else if ( fmt[1] != '-' || fmt[2] != '#' )
                  convType = NULLCHAR ;
               //* Format the data *
               this->binaryFormat ( convBuff, convType, sepType, intData, dataType, grpType, appendType ) ;

               //* Add the formatted data to the output *
               for ( short i = ZERO ; convBuff[i] != NULLCHAR ; i++ )
               {
                  if ( wsrci < (len - 1) )
                     wsrc[wsrci++] = convBuff[i] ;
                  else
                  {
                     wsrci = len - 1 ;
                     break ;
                  }
               }
               wsrc[wsrci] = NULLCHAR ;
               ++pCount ;        // reference next argument
               break ;
            }
            #endif   // BINARY_EXTENSION

            else if ( gsf.fmtSpec[fsi] == '%' )
            {  //* Copy literal '%' to output string *
               wsrc[wsrci++] = gsf.fmtSpec[fsi++] ;
               wsrc[wsrci] = NULLCHAR ;
               break ;
            }
            else if ( gsf.fmtSpec[fsi] == NULLCHAR )
            {  //* Unsupported data type encountered *
               wsrc[wsrci++] = '#' ;
               wsrc[wsrci] = NULLCHAR ;
               break ;
            }
            else
            {  //* Copy specification modifier to specification string *
               fmt[fmti++] = gsf.fmtSpec[fsi++] ;
            }
         }
         while ( gsf.fmtSpec[fsi] != NULLCHAR ) ;

         //* Recalculate remaining free space in target buffer *
         wsrcSpace = len - wsrci - 1 ;
      }  // else
   }  // while()

   //* Be absolutely sure the string is terminated *
   if ( wsrci >= len )
      wsrci = len - 1 ;
   wsrc[wsrci] = NULLCHAR ;

   return ( len - wsrcSpace ) ;   // return word count incl. nullchar

}  //* End gsForm_2_gs() *

//*************************
//*     limitBytes        *
//*************************
//********************************************************************************
//* Private Method:                                                              *
//* ---------------                                                              *
//* Truncate the existing data to no more than maxBytes bytes.                   *
//* This method is used internally by the copy() method to prevent overflow      *
//* of caller's buffer. This method IS NOT publically available.                 *
//* The actual number of bytes in the truncated string is determined by the      *
//* number of whole-character byte sequences <= maxBytes. This avoids a          *
//* partial character sequence at the end of the string. The NULLCHAR            *
//* terminating character is included in the number of bytes retained.           *
//*                                                                              *
//* Input  : maximum number of bytes to be retained                              *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void gString::limitBytes ( int32_t maxBytes )
{
   if ( maxBytes > 1 && maxBytes < this->utfBytes )
   {
      int32_t bCount = ZERO,     // number of bytes retained
              btmp ;             // bytes in an individual character-byte sequence

      --maxBytes ;               // leave room for NULLCHAR terminator
      while ( bCount < maxBytes )
      {
         btmp = mbrlen ( &this->uStr[bCount], maxBPC, NULL ) ;
         if ( (bCount + btmp) <= maxBytes )
            bCount += btmp ;
         else
            break ;
      }
      char* uSource = new char[this->gsu + 4] ; // temporary buffer
      for ( int32_t i = ZERO ; i < bCount ; i++ )
         uSource[i] = this->uStr[i] ;
      uSource[bCount++] = NULLCHAR ;
      this->reset () ;        // reinitialize all data members
      this->utf8_2_utf32 ( uSource ) ;
      delete [] uSource ;     // release the dynamic allocation
   }

}  //* End limitBytes() *

//*************************
//*      columnCount      *
//*************************
//********************************************************************************
//* Private Method:                                                              *
//* ---------------                                                              *
//* Count the number of display columns required to display the data.            *
//* Optionally, truncate the data to limit the number of columns required.       *
//*                                                                              *
//* Input  : maxCols (optional, default == -1)                                   *
//*                  if specified, sets maximum number of display-output         *
//*                  columns allowed in string.                                  *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void gString::columnCount ( int32_t maxCols )
{
   int32_t colCount ;                      // target character width
   this->gsCols = ZERO ;                  // reset column count

   if ( maxCols < ZERO ) { maxCols = (this->gsw * 2) ; } // if default, then no limit

   for ( int32_t i = ZERO ; i < this->gsChars ; i++ )
   {  //* Get width in display columns for the character *
      // Programmer's Note: Control characters ('\n', '\t' etc.) return -1.
      if ( (colCount = wcwidth ( this->gStr[i] )) < ZERO )
         colCount = ZERO ;
      this->cWid[i] = colCount ;          // save column width to tracking array
      this->gsCols += colCount ;          // add char columns to running total
      if ( this->gsCols > maxCols )       // if max column count reached
      {
         this->gStr[i] = NULLCHAR ;       // discard most recent char examined
         this->cWid[i] = ZERO ;
         this->gsCols -= colCount ;
         this->gsChars = i + 1 ;          // new char count (incl. null)
         //* This call will trash our new column data, so protect it. *
         int32_t tempgsCols = this->gsCols,
                 tempcWidth = this->cWid[ZERO] ;
         this->utf32_2_utf8 () ;          // uStr && utfBytes
         this->gsCols = tempgsCols ;
         this->cWid[ZERO] = tempcWidth ;
         break ;
      }
   }
}  //* End columnCount() *

#if BINARY_EXTENSION != 0
//*************************
//*     binaryFormat      *
//*************************
//********************************************************************************
//* Private Method                                                               *
//* --------------                                                               *
//* Implements a binary-output extension to the standard swprintf output-format  *
//* specifiers.                                                                  *
//*                                                                              *
//* Input  : convBuff : receives formatted data                                  *
//*          convType : L'b' , L'B' (conversion specifier) or NULLCHAR           *
//*          sepType  : group seperator character                                *
//*          intData  : source data in long-long int format                      *
//*          dataType : data type at *intData: i.e. number of bytes in           *
//*                     source integer                                           *
//*          grpType  : bit grouping: 4 == by-4,  8 == by-8                      *
//*                                                                              *
//*          append   : if 'true, then append the 'b', 'B' (or NULLCHAR)         *
//*                      to end of string                                        *
//*                     if 'false', then prepend the 'b' or 'B' character        *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************
//*                                                                              *
//*                                                                              *
//*                                                                              *
//*   Examples:                                                                  *
//*    %b   , %B     integer source                                              *
//*    %lb  , %lB    long integer source                                         *
//*    %llb , %llB , %qb , %qB , %Lb , %LB  long long integer (64-bit) source    *
//*    %hb  , %hB    short integer source                                        *
//*    %hhb , %hhB   byte integer source                                         *
//*    %#b  , %#B    prepend a 'b' or 'B' character                              *
//*    %-#b , %-#B   append a 'b' or 'B' character                               *
//*                                                                              *
//*   Output is in groups of four (4) bits (default) or eight (8) bits           *
//*   Group seperator character is '.' (default) or specified character in       *
//*    the range ' ' 20h through '/' 2Fh  OR  ':' 3Ah through '@' 40h  OR        *
//*              '[' 5Bh, ']' 5Dh, '^' 5Eh, '_' 5Fh, '`' 60h,                    *
//*              '{' 7Bh, '|' 7Ch, '}' 7Dh, '~' 7Eh                              *
//*   Examples: %b      1011.0111.1111.0000.0111.0101.1010.0001                  *
//*             %#hB   B0111.0101.1010 0001                                      *
//*             %-#hB   0111.0101.1010.0001B                                     *
//*             %-#hhb  0101b                                                    *
//*             % B     1011 0111 1111 0000 0111 0101 1010 0001                  *
//*             %-hb    0111-0101-1010-0001                                      *
//*             %#_hb   b0111_0101_1010_0001                                     *
//*             %.4hb   0111.0101.1010.0001                                      *
//*             % .8b   10110111 11110000 01110101 10100001                      *
//*             %-#_.8B 10110111_11110000_01110101_10100001B                     *
//*                                                                              *
//*                                                                              *
//********************************************************************************

void gString::binaryFormat ( wchar_t* convBuff, wchar_t convType, wchar_t sepType, 
                             long long int intData, short dataType, short grpType, 
                             bool append )
{
   int32_t obIndex,        // output-buffer index
           bitCount ;      // number of data bits to process
   switch ( dataType )
   {
      case 1:     obIndex = grpType == 4 ? 10 :  9 ; bitCount =  8 ; break ;
      case 2:     obIndex = grpType == 4 ? 20 : 18 ; bitCount = 16 ; break ;
      case 4:     obIndex = grpType == 4 ? 40 : 36 ; bitCount = 32 ; break ;
      case 8:     obIndex = grpType == 4 ? 80 : 72 ; bitCount = 64 ; break ;
      default:    // programmer error
         obIndex = 1 ;
         bitCount = ZERO ;
         *convBuff = NULLCHAR ;
         break ;
   }
   convBuff[obIndex--] = NULLCHAR ;    // terminate the string
   if ( append != false )
      convBuff[obIndex--] = convType ; // 'b' or 'B' indicator (if specified)
   long long int msk = 0x0000000000000001 ;
   for ( short i = ZERO ; i < bitCount ; i++ )
   {
      if ( i > ZERO && ((i % grpType) == ZERO) )
         convBuff[obIndex--] = sepType ;
      if ( intData & msk ) convBuff[obIndex--] = L'1' ;
      else                 convBuff[obIndex--] = L'0' ;
      msk <<= 1 ;
   }
   if ( append == false )
      convBuff[obIndex] = convType ;

}  //* End binaryFormat() *
#endif   // BINARY_EXTENSION

//***************************
//* composeFieldwidthBugFix *
//***************************
//********************************************************************************
//* Compensate for a logical error in the standard library swprintf() function   *
//* string conversions.                                                          *
//*                                                                              *
//* Input  : convType : conversion-type code (currently unused)                  *
//*          wsrcPtr  : target buffer                                            *
//*          wsrcSpace: free space remaining in buffer                           *
//*          fmt      : output-formatting specification "%s" or "%S"             *
//*                     which includes a field-width specification               *
//*          gsSrc    : source string                                            *
//*                                                                              *
//* Returns: number of characters written to target                              *
//********************************************************************************
//* Due to a bug in the stdlib 'swprintf' function, we have modified the output  *
//* formatting for the 'compose' method to compensate for the following          *
//* scenario. The 'swprintf' incorrectly interprets the field-width              *
//* specification as number-of-characters, rather than as number-of-columns as   *
//* it should. This is not a problem so long as 1 character == 1 column, with    *
//* Arabic numbers for instance.                                                 *
//* However, for conversion specifiers, where the source data contains           *
//* characters that require more than one display column, anomalous formatting   *
//* will occur. Specifically, if display columns > character count, then the     *
//* output string will be too wide.                                              *
//*                                                                              *
//* Specifically, for the "%s" and "%S" conversion specifiers that contain a     *
//* a field-width specification, we must manually interpret the field width as   *
//* number of display columns. We don't want to do this because it is a          *
//* performance hit AND because some users may actually rely upon the            *
//* unfortunate design choice in the printf family of functions.                 *
//*                                                                              *
//* For these reasons, we continue to use the 'swprintf' output directly for     *
//* "%s" and "%S" conversions if:                                                *
//*  a) no field-width specifier is provided (handled by caller), or             *
//*  b) the source data contains only 1-character == 1-column data               *
//*                                                                              *
//* We manually create output of the appropriate column width for the following  *
//* set of circumstances.                                                        *
//*  1) if a field-width specification is provided with a string formatting      *
//*     spec, and                                                                *
//*  2) if the provided string contains characters which require more than one   *
//*     display column.                                                          *
//* We then (rather redundantly) call 'swprintf' to copy the fully-formated      *
//* string to the output buffer.                                                 *
//*                                                                              *
//* NOTE: In future, we may want to use this algorithm for character data        *
//*       as well (%5c  %-2C  etc) or even for numeric data, but at this time    *
//*       it seems wasteful and unnecessary.                                     *
//*                                                                              *
//*  --------------------------------------------------------------------------  *
//* Note that the source data are in a gString object, and that 16-bit integers  *
//* are used for the actual calculation. This is faster. yet still safe.         *
//* Test the algorithm:                                                          *
//* -------------------                                                          *
//* const char* nTest = "All narrow text, 28 columns." ;                         *
//* const wchar_t* wTest = L"Mixed narrow/wide 每件 2.5元。 text, 48 columns." ;     *
//* gString gs (gsALLOCMIN) ;                                                    *
//* gs.compose( "'%s'\n'%36s'\n'%-36s'\n |                                  |",  *
//*             nTest, nTest, nTest ) ;                                          *
//* wcout << gs << endl ;                                                        *
//* gs = wTest ;                                                                 *
//* gs.compose( "'%S'\n |                                              |\n"      *
//*             "'%56S'\n'%-56S'\n"                                              *
//*             " |                                                      |\n"    *
//*             , wTest, wTest, wTest ) ;                                        *
//* wcout << gs << endl ;                                                        *
//*                                                                              *
//********************************************************************************

int32_t gString::composeFieldwidthBugFix ( char convType, wchar_t* wsrcPtr, 
                                           int32_t wsrcSpace, 
                                           const wchar_t* fmt, 
                                           const gString& gsSrc )
{
   int32_t swret = ZERO ;                 // value returned by swprintf
   short   fWidth, absfWidth,
           srcCols = gsSrc.gscols(),
           srcChars = gsSrc.gschars() ;
   swscanf ( fmt, L"%%%hd", &fWidth ) ;     // get the field width
   absfWidth = fWidth >= ZERO ? fWidth : (~fWidth) + 1 ;
   gString gsfmt ;
   gsfmt.compose( L"%%%hdS", &fWidth ) ;

   if ( (srcChars - 1) == (srcCols) )
   {
      swret = swprintf ( wsrcPtr, wsrcSpace, gsfmt.gstr(), gsSrc.gstr() ) ;
   }
   else
   {
      wchar_t wbuf[gsALLOCDFLT] ;
      if ( fWidth < ZERO )          // left-justified
      {
         gsSrc.copy( wbuf, srcChars ) ;
         short padCols = absfWidth - srcCols ;
         short i = srcChars - 1 ;
         for ( short pc = padCols ; pc > ZERO ; --pc )
            wbuf[i++] = SPACE ;
         wbuf[i] = NULLCHAR ;
      }
      else                          // right-justified
      {
         short padCols = absfWidth - srcCols ;
         short i = ZERO ;
         for ( short pc = padCols ; pc > ZERO ; --pc )
            wbuf[i++] = SPACE ;
         gsSrc.copy( &wbuf[i], srcChars ) ;
      }
      swret = swprintf ( wsrcPtr, wsrcSpace, L"%S", wbuf ) ;
   }
   return swret ;

}  //* End composeFieldwidthBugFix() *

//*************************
//*                       *
//*************************
//********************************************************************************
//*                                                                              *
//*                                                                              *
//*                                                                              *
//* Input  :                                                                     *
//*                                                                              *
//* Returns:                                                                     *
//********************************************************************************

