/*******************************************************************************
;*******************************************************************************
;**                                                                           **
;**                  COPYRIGHT 2001-2012 NUANCE COMMUNICATIONS                **
;**                                                                           **
;**               NUANCE COMMUNICATIONS PROPRIETARY INFORMATION               **
;**                                                                           **
;**     This software is supplied under the terms of a license agreement      **
;**     or non-disclosure agreement with Nuance Communications and may not    **
;**     be copied or disclosed except in accordance with the terms of that    **
;**     agreement.                                                            **
;**                                                                           **
;*******************************************************************************
;**                                                                           **
;**     FileName: et9kdb.c                                                    **
;**                                                                           **
;**  Description: Keyboard Database input module module for ET9               **
;**                                                                           **
;*******************************************************************************
;******************************************************************************/

/*! \addtogroup et9imkdb Functions for XT9 Keyboard Module
* Keyboard database input for generic XT9.
* @{
*/

#include "et9kdb.h"
#include "et9imu.h"
#include "et9misc.h"
#include "et9sym.h"
#include "et9kbdef.h"

#ifdef ET9_KDB_MODULE


#ifdef ET9_DEBUGLOG5
#ifdef _WIN32
#pragma message ("*** ET9_DEBUGLOG5 ACTIVATED ***")
#endif
#include <stdio.h>
#include <string.h>
#ifdef XT9_ANDROID_BUILD
static const char pczzzET9KDB[] = "/sdcard/data/zzzET9KDB.txt";
#else
static const char pczzzET9KDB[] = "zzzET9KDB.txt";
#endif
#define WLOG5(q) { if (pLogFile5 == NULL) { pLogFile5 = fopen(pczzzET9KDB, "w"); } { q fflush(pLogFile5); } }
static FILE *pLogFile5 = NULL;
#else
#define WLOG5(q)
#endif

#ifdef ET9_DEBUGLOG5B
#ifdef _WIN32
#pragma message ("*** ET9_DEBUGLOG5B ACTIVATED ***")
#endif
#define WLOG5B(q) WLOG5(q)
#else
#define WLOG5B(q)
#endif


#ifdef ET9_DEBUGLOG6
#ifdef _WIN32
#pragma message ("*** ET9_DEBUGLOG6 ACTIVATED ***")
#endif
#include <stdio.h>
#include <string.h>
#ifdef XT9_ANDROID_BUILD
static const char pczzzET9KDBTRACE[] = "/sdcard/data/zzzET9KDBTRACE.txt";
#else
static const char pczzzET9KDBTRACE[] = "zzzET9KDBTRACE.txt";
#endif
#define WLOG6(q) { if (pLogFile6 == NULL) { pLogFile6 = fopen(pczzzET9KDBTRACE, "w"); } { q fflush(pLogFile6); } }
static FILE *pLogFile6 = NULL;
#else
#define WLOG6(q)
#endif

#ifdef ET9_DEBUGLOG6B
#ifdef _WIN32
#pragma message ("*** ET9_DEBUGLOG6B ACTIVATED ***")
#endif
#define WLOG6B(q) WLOG6(q)
#else
#define WLOG6B(q)
#endif


/* ************************************************************************************************************** */
/* * PROTOTYPES ************************************************************************************************* */
/* ************************************************************************************************************** */

#ifdef ET9_KDB_TRACE_MODULE
extern const ET9U8 _pbXt9Trace[];
#endif

/* ************************************************************************************************************** */
/* * DEFINES / CONSTANTS **************************************************************************************** */
/* ************************************************************************************************************** */

#ifndef ET9CHAR
#define ET9CHAR                     char
#endif

#define UNDEFINED_KDB_PAGE          0xFFFE                          /* < -IDR-                                                                                       */

#define ET9_KDB_MAX_REGIONS         ET9MAXBASESYMBS                 /* < -IDR-         */

#define ET9_KDB_KEY_UNDEFINED       0xFFFF                          /* < -IDR-         */
#define ET9_KDB_KEY_OUTOFBOUND      0xFFFE                          /* < -IDR-         */

#define ET9_KDB_COORD_OUTOFBOUND    0xF000                          /* < -IDR-         */

static const ET9FLOAT               fPiR = 3.14159265358979323846f; /* < -IDR-         */

static const ET9FLOAT               fPosRound = 0.5f;               /* < -IDR-         */
static const ET9FLOAT               fSizeRound = 0.1f;              /* < -IDR-         */

#ifndef _ET9_KDB_GESTURE_MARGIN_FACTOR
#define _ET9_KDB_GESTURE_MARGIN_FACTOR      (1.0/3.0)
#endif

/* ************************************************************************************************************** */
/* * STRUCTS ETC ************************************************************************************************ */
/* ************************************************************************************************************** */

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 */

typedef struct ET9DirectedPos_s
{
    ET9TracePoint   sPos;               /**< xxx */
    ET9TracePoint   sL1;                /**< xxx */
    ET9TracePoint   sL2;                /**< xxx */
    ET9BOOL         bBlendFunctionKey;  /**< xxx */
    ET9U16          wExpectedKey;       /**< xxx */
} ET9DirectedPos;                       /**< xxx */

#define __InitDirectedPos(pDirectedPos)  { _ET9ClearMem((ET9U8*)(pDirectedPos), sizeof(ET9DirectedPos)); (pDirectedPos)->wExpectedKey = ET9_KDB_KEY_UNDEFINED; }

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 */

typedef struct ET9MatchKey_s
{
#ifdef _ET9_KDB_REG_PROB_V2
    ET9FLOAT        fDistSQ;        /**< xxx */
    ET9U8           bFreq;          /**< xxx */
    ET9BOOL         bCenter;        /**< xxx */
    ET9BOOL         bLimited;       /**< xxx */
    ET9BOOL         bFunctionKey;   /**< xxx */
    ET9KdbAreaInfo  *pArea;         /**< xxx */
#else
    ET9FLOAT        fFreq;          /**< xxx */
    ET9U8           bFreq;          /**< xxx */
    ET9U8           bCenter;        /**< xxx */
    ET9BOOL         bLimited;       /**< xxx */
    ET9BOOL         bFunctionKey;   /**< xxx */
    ET9UINT         nOverlap;       /**< xxx */
    ET9KdbAreaInfo  *pArea;         /**< xxx */
#endif /* _ET9_KDB_REG_PROB_V2 */
} ET9MatchKey;                      /**< xxx */

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                     
 */

typedef enum ET9LOADSYMBACTION_e {

    LOADSYMBACTION_NEW,             /* < -IDR-                         */
    LOADSYMBACTION_RELOAD,          /* < -IDR-                                */
    LOADSYMBACTION_APPEND,          /* < -IDR-                                   */

    LOADSYMBACTION_LAST             /* < -IDR-              */
} ET9LOADSYMBACTION;

/* ************************************************************************************************************** */
/* * PROTOTYPES ************************************************************************************************* */
/* ************************************************************************************************************** */

static void ET9LOCALCALL __CalculateRegionality_Generic (ET9KDBInfo  * const pKDBInfo);

static ET9STATUS ET9LOCALCALL __KDBLoadPage(ET9KDBInfo      * const pKDBInfo,
                                            const ET9U32            dwKdbNum,
                                            const ET9U16            wPageNum,
                                            ET9U16          * const pwTotalKeys);

static void ET9LOCALCALL __ProcessMultitap(ET9KDBInfo            * const pKDBInfo,
                                           ET9BOOL                       bIsFirstPress,
                                           ET9SYMB               * const psFunctionKey,
                                           const ET9U8                   bCurrIndexInList,
                                           const ET9U16                  wMTLastInput,
                                           const ET9LOADSYMBACTION       eLoadAction);

static void ET9LOCALCALL __ProcessWordSymInfo(ET9KDBInfo        * const pKDBInfo);

/* ************************************************************************************************************** */
/* ************************************************************************************************************** */
/* ************************************************************************************************************** */

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                            
 *
 *                                                               
 *                                                          
 *
 *             
 */

static void ET9LOCALCALL __InvalidateKDBs(ET9KDBInfo    * const pKDBInfo,
                                          const ET9BOOL         bAssureKDB)
{
    ET9UINT nCount;
    ET9KdbLayoutInfo *pLayoutInfo;

    pLayoutInfo = &pKDBInfo->Private.pLayoutInfos[0];
    for (nCount = ET9_KDB_MAX_PAGE_CACHE; nCount; --nCount, ++pLayoutInfo) {
        pLayoutInfo->bOk = 0;
    }

    pKDBInfo->Private.bKDBLoaded = 0;

    if (bAssureKDB) {
        ET9KDB_SetKdbNum(pKDBInfo, pKDBInfo->dwFirstKdbNum, pKDBInfo->wFirstPageNum, pKDBInfo->dwSecondKdbNum, pKDBInfo->wSecondPageNum);
    }
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                       
 *
 *                                                               
 *                                                                          
 *
 *                             
 */

static ET9STATUS ET9LOCALCALL __ET9KDB_BasicValidityCheck(ET9KDBInfo    * const pKDBInfo,
                                                          const ET9BOOL         bKDBCheck)
{
    if (!pKDBInfo) {
        return ET9STATUS_INVALID_MEMORY;
    }

    if (pKDBInfo->Private.wInfoInitOK != ET9GOODSETUP) {
        return ET9STATUS_NO_INIT;
    }

    if (pKDBInfo->Private.bIsLoadingKDB) {
        return ET9STATUS_KDB_IS_LOADING;
    }

    if (bKDBCheck && pKDBInfo->Private.wKDBInitOK != ET9GOODSETUP) {
        return ET9STATUS_NO_DB_INIT;
    }

    if (!pKDBInfo->Private.pWordSymbInfo) {
        return ET9STATUS_INVALID_MEMORY;
    }

    if (pKDBInfo->Private.pWordSymbInfo->wInitOK != ET9GOODSETUP) {
        return ET9STATUS_NO_INIT;
    }

    /* validate locale */

    {
        ET9WordSymbInfo * const pWordSymbInfo = pKDBInfo->Private.pWordSymbInfo;

        if (pWordSymbInfo->Private.bManualLocale &&
            pWordSymbInfo->Private.dwLocale != pKDBInfo->Private.dwKDBCacheLocale) {

            pKDBInfo->Private.dwKDBCacheLocale = pWordSymbInfo->Private.dwLocale;

            __InvalidateKDBs(pKDBInfo, bKDBCheck);
        }
    }

    /* done */

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                       
 *
 *                                                               
 *
 *                             
 */

static ET9STATUS ET9LOCALCALL __ET9KDB_LoadValidityCheck(ET9KDBInfo    * const pKDBInfo)
{
    if (!pKDBInfo) {
        return ET9STATUS_INVALID_MEMORY;
    }

    if (pKDBInfo->Private.wInfoInitOK != ET9GOODSETUP) {
        return ET9STATUS_NO_INIT;
    }

    if (!pKDBInfo->Private.bIsLoadingKDB) {
        return ET9STATUS_KDB_IS_NOT_LOADING;
    }

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                      
 *
 *                                                                 
 *                                          
 *
 *                             
 */

static ET9STATUS ET9LOCALCALL __ET9KDB_RequestMultitapTimeout(ET9KDBInfo        * const pKDBInfo,
                                                              const ET9U16              wKey)
{
    ET9KDB_Request sRequest;

    ET9Assert(pKDBInfo);

    sRequest.eType = ET9_KDB_REQ_TIMEOUT;
    sRequest.data.sTimeout.eTimerType = ET9_KDB_TMRMULT;
    sRequest.data.sTimeout.nTimerID   = (ET9INT)(wKey + 1);

    return pKDBInfo->ET9Handle_KDB_Request(pKDBInfo, pKDBInfo->Private.pWordSymbInfo, &sRequest);
}

/* ************************************************************************************************************** */
/* * KEYBOARD LOGGING ******************************************************************************************* */
/* ************************************************************************************************************** */

#ifdef _ET9_KDB_KEYBOARD_LOG
#ifndef _ET9_KDB_KEYBOARD_ACCESS
#define _ET9_KDB_KEYBOARD_ACCESS
#endif
#endif

#ifdef _ET9_KDB_KEYBOARD_ACCESS

#include <stdio.h>

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                         
 *
 *                                                                    
 *                                           
 *                                                   
 *                                              
 *                                                                                                 
 *
 *             
 */

static void ET9LOCALCALL __GetKeyboardPageXML(ET9KDBInfo      * const pKDBInfo,
                                              ET9U8           * const pbData,
                                              const ET9U32            dwMaxDataSize,
                                              ET9U32          * const pdwDataSize,
                                              const ET9BOOL           bForceFullInfo)
{
    ET9KdbLayoutInfo * const pLayoutInfo = pKDBInfo->Private.pCurrLayoutInfo;

    static ET9UINT nCurrHistoryIndex = 0;

    static ET9U32 pdwHashHistory[50] = { 0 };

    const ET9UINT nHistorySize = (ET9UINT)(sizeof(pdwHashHistory)/sizeof(ET9U32));

    ET9CHAR * const pcData = (char*)pbData;
    ET9CHAR * const pcDataEnd = (char*)(pbData + dwMaxDataSize);

    ET9CHAR *pcCurr;

    pcCurr = pcData;

    WLOG5(fprintf(pLogFile5, "__GetKeyboardPageXML, bSecondaryID %02x, bPrimaryID %02x, dwLocale %08x, dwMaxDataSize %u\n", pLayoutInfo->bSecondaryID, pLayoutInfo->bPrimaryID, pKDBInfo->Private.pWordSymbInfo->Private.dwLocale, dwMaxDataSize);)

    *pdwDataSize = 0;

    if (dwMaxDataSize < 5000) {
        return;
    }

    pcCurr += sprintf(pcCurr, "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n\n");

    if (bForceFullInfo) {
        pcCurr += sprintf(pcCurr, "<!-- Generated from loaded keyboard! (locale 0x%08x) -->\n\n", pKDBInfo->Private.pWordSymbInfo->Private.dwLocale);
    }
    else {
        pcCurr += sprintf(pcCurr, "<!-- Generated from loaded keyboard! -->\n\n");
    }

    pcCurr += sprintf(pcCurr, "<keyboard\n");
    pcCurr += sprintf(pcCurr, "    secondaryId=\"0x%02x\"\n", pLayoutInfo->bSecondaryID);
    pcCurr += sprintf(pcCurr, "    primaryId=\"0x%02x\"\n", pLayoutInfo->bPrimaryID);
    pcCurr += sprintf(pcCurr, "    defaultLayoutWidth=\"%u\"\n", pLayoutInfo->wLayoutWidth);
    pcCurr += sprintf(pcCurr, "    defaultLayoutHeight=\"%u\"\n", pLayoutInfo->wLayoutHeight);
    pcCurr += sprintf(pcCurr, "    addAltChars=\"false\"\n");
    pcCurr += sprintf(pcCurr, "    >\n\n");

    pcCurr += sprintf(pcCurr, "    <area conditionValue=\"0\">\n\n");

    {
        ET9UINT nIndex;

        for (nIndex = 0; nIndex < pLayoutInfo->nKeyAreaCount; ++nIndex) {

            ET9KdbAreaInfo const * const pArea = &pLayoutInfo->pKeyAreas[nIndex];

            if ((pcDataEnd - pcCurr) < 200) {
                return;
            }

            pcCurr += sprintf(pcCurr, "        <key\n");

            {
                ET9CHAR pcString[64];

                if (pArea->eKeyType == ET9KTFUNCTION) {
                    pcString[0] = 0;
                }
                else if (pArea->eKeyType == ET9KTSTRING) {

                    ET9U8 bCurrLen;
                    ET9UINT nCharIndex;

                    bCurrLen = 0;
                    for (nCharIndex = 0; nCharIndex < pArea->nCharCount && bCurrLen + 6 < sizeof(pcString); ++nCharIndex) {
                        bCurrLen += _ET9SymbToUtf8(pArea->psChars[nCharIndex], (ET9U8*)&pcString[bCurrLen]);
                    }
                    pcString[bCurrLen] = 0;
                }
                else {

                    const ET9SymbClass eClassFirst = _ET9_GetSymbolClass(pArea->psChars[0]);

                    ET9UINT nCharIndex;

                    pcString[0] = 0;

                    for (nCharIndex = 0; nCharIndex < __ET9Min(pArea->nCharCount, 5); ++nCharIndex) {

                        const ET9SymbClass eClassThis = _ET9_GetSymbolClass(pArea->psChars[nCharIndex]);

                        if (!nCharIndex ||
                            (eClassFirst == ET9_AlphaSymbClass && eClassThis != eClassFirst) ||
                            (eClassFirst == ET9_PunctSymbClass) ||
                            (eClassFirst == ET9_NumbrSymbClass)) {

                            if (nCharIndex > 1 && eClassFirst == ET9_NumbrSymbClass && pArea->psChars[1] < 0x7F && pArea->psChars[nCharIndex] > 0x7f) {
                            }
                            else if (pArea->psChars[nCharIndex] == '"') {
                                strcat(pcString, "&quot;");
                            }
                            else if (pArea->psChars[nCharIndex] == '<') {
                                strcat(pcString, "&lt;");
                            }
                            else if (pArea->psChars[nCharIndex] == '&') {
                                strcat(pcString, "&amp;");
                            }
                            else {

                                ET9CHAR pcTmp[8];

                                pcTmp[_ET9SymbToUtf8(pArea->psChars[nCharIndex], (ET9U8*)pcTmp)] = 0;

                                strcat(pcString, pcTmp);
                            }
                        }
                    }
                }

                if (pcString[0]) {
                    pcCurr += sprintf(pcCurr, "            keyLabel=\"%s\"\n", pcString);
                }
            }

            pcCurr += sprintf(pcCurr, "            keyType=\"%s\"\n", (pArea->eKeyType == ET9KTFUNCTION) ? "function" :
                                                                      (pArea->eKeyType == ET9KTSTRING) ? "string" :
                                                                      (pArea->eKeyType == ET9KTSMARTPUNCT) ? "smartPunct" :
                                                                      (pArea->eInputType == ET9REGIONALKEY) ? "regional" : "nonRegional");

            pcCurr += sprintf(pcCurr, "            keyLeft=\"%udp\"\n", pArea->sRegion.wLeft);
            pcCurr += sprintf(pcCurr, "            keyTop=\"%udp\"\n", pArea->sRegion.wTop);
            pcCurr += sprintf(pcCurr, "            keyWidth=\"%udp\"\n", (pArea->sRegion.wRight - pArea->sRegion.wLeft + 1));
            pcCurr += sprintf(pcCurr, "            keyHeight=\"%udp\"\n", (pArea->sRegion.wBottom - pArea->sRegion.wTop + 1));

            /* keyCodes */

            if (pArea->nCharCount) {

                ET9UINT nCodeIndex;

                pcCurr += sprintf(pcCurr, "            keyCodes=\"");

                for (nCodeIndex = 0; nCodeIndex < pArea->nCharCount; ++nCodeIndex) {

                    if ((pcDataEnd - pcCurr) < 100) {
                        return;
                    }

                    if (nCodeIndex) {
                        pcCurr += sprintf(pcCurr, ",");
                    }

                    pcCurr += sprintf(pcCurr, "0x%04x", pArea->psChars[nCodeIndex]);
                }

                pcCurr += sprintf(pcCurr, "\"\n");
            }

            /* keyCodesShifted */

            if (pArea->nShiftedCharCount) {

                ET9UINT nCodeIndex;

                pcCurr += sprintf(pcCurr, "            keyCodesShifted =\"");

                for (nCodeIndex = 0; nCodeIndex < pArea->nShiftedCharCount; ++nCodeIndex) {

                    if ((pcDataEnd - pcCurr) < 100) {
                        return;
                    }

                    if (nCodeIndex) {
                        pcCurr += sprintf(pcCurr, ",");
                    }

                    pcCurr += sprintf(pcCurr, "0x%04x", pArea->psShiftedChars[nCodeIndex]);
                }

                pcCurr += sprintf(pcCurr, "\"\n");
            }

            /* keyMultitapCodes */

            if (pArea->nMultitapCharCount) {

                ET9UINT nCodeIndex;

                pcCurr += sprintf(pcCurr, "            keyMultitapCodes =\"");

                for (nCodeIndex = 0; nCodeIndex < pArea->nMultitapCharCount; ++nCodeIndex) {

                    if ((pcDataEnd - pcCurr) < 100) {
                        return;
                    }

                    if (nCodeIndex) {
                        pcCurr += sprintf(pcCurr, ",");
                    }

                    pcCurr += sprintf(pcCurr, "0x%04x", pArea->psMultitapChars[nCodeIndex]);
                }

                pcCurr += sprintf(pcCurr, "\"\n");
            }

            /* keyMultitapShiftedCodes */

            if (pArea->nMultitapShiftedCharCount) {

                ET9UINT nCodeIndex;

                pcCurr += sprintf(pcCurr, "            keyMultitapShiftedCodes =\"");

                for (nCodeIndex = 0; nCodeIndex < pArea->nMultitapShiftedCharCount; ++nCodeIndex) {

                    if ((pcDataEnd - pcCurr) < 100) {
                        return;
                    }

                    if (nCodeIndex) {
                        pcCurr += sprintf(pcCurr, ",");
                    }

                    pcCurr += sprintf(pcCurr, "0x%04x", pArea->psMultitapShiftedChars[nCodeIndex]);
                }

                pcCurr += sprintf(pcCurr, "\"\n");
            }

            /* end of key */

            pcCurr += sprintf(pcCurr, "        />\n\n");
        }
    }

    if ((pcDataEnd - pcCurr) < 100) {
        return;
    }

    pcCurr += sprintf(pcCurr, "    </area>\n\n");
    pcCurr += sprintf(pcCurr, "</keyboard>\n");

    {
        const ET9U32 dwDataSize = (ET9U32)(pcCurr - pcData);
        const ET9U32 dwHashValue = _ET9ByteCheckSum(pbData, dwDataSize);

        ET9UINT nIndex;

        for (nIndex = 0; nIndex < nHistorySize; ++nIndex) {

            if (dwHashValue == pdwHashHistory[nIndex]) {
                break;
            }
        }

        WLOG5(fprintf(pLogFile5, "__GetKeyboardPageXML, dwDataSize %u, dwHashValue %08x, nIndex %u (%u)\n", dwDataSize, dwHashValue, nIndex, nHistorySize);)

        if (nIndex >= nHistorySize) {

            pdwHashHistory[nCurrHistoryIndex] = dwHashValue;

            ++nCurrHistoryIndex;

            if (nCurrHistoryIndex >= nHistorySize) {
                nCurrHistoryIndex = 0;
            }
        }
        else if (!bForceFullInfo) {

            pcCurr = pcData;
        }

        if (!bForceFullInfo) {
            pcCurr += sprintf(pcCurr, "<keyboard secondaryId=\"%02x\" primaryId=\"%02x\" hash=\"%08x\" />\n", pLayoutInfo->bSecondaryID, pLayoutInfo->bPrimaryID, dwHashValue);
        }
    }

    *pdwDataSize = (ET9U32)(pcCurr - pcData);
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                         
 *
 *                                                                    
 *                                           
 *                                                   
 *                                              
 *
 *             
 */

void ET9FARCALL _ET9_GetKeyboardPageXML(ET9KDBInfo      * const pKDBInfo,
                                        ET9U8           * const pbData,
                                        const ET9U32            dwMaxDataSize,
                                        ET9U32          * const pdwDataSize)
{
    ET9STATUS eStatus;

    eStatus = __ET9KDB_BasicValidityCheck(pKDBInfo, 1);

    if (eStatus) {

        if (pdwDataSize && pbData) {
            *pdwDataSize = sprintf((char*)pbData, "<keyboard status=\"%u\" />\n", eStatus);
        }

        return;
    }

    if (!pdwDataSize || !pbData) {
        return;
    }

    __GetKeyboardPageXML(pKDBInfo, pbData, dwMaxDataSize, pdwDataSize, 0);
}

#endif /* _ET9_KDB_KEYBOARD_ACCESS */

#ifdef _ET9_KDB_KEYBOARD_LOG

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                          
 *
 *                                                               
 *
 *             
 */

static void ET9LOCALCALL __LogKeyboardPage(ET9KDBInfo      * const pKDBInfo)
{
    ET9KdbLayoutInfo * const pLayoutInfo = pKDBInfo->Private.pCurrLayoutInfo;

    FILE *pfLog;

    ET9CHAR pcName[64];
    ET9CHAR pcFullName[128];

    ET9U32 dwLogDataUsed;

    static ET9U8 pbLogData[50000];

    sprintf(pcName, "zzzKeyboard_%04x_%04X_%ux%u.xml", ((pLayoutInfo->bSecondaryID << 8) + pLayoutInfo->bPrimaryID), (pKDBInfo->Private.pWordSymbInfo->Private.dwLocale & 0xFFFF), pLayoutInfo->wLayoutWidth, pLayoutInfo->wLayoutHeight);

#ifdef XT9_ANDROID_BUILD
    strcpy(pcFullName, "/sdcard/data/");
#else
    strcpy(pcFullName, ".\\");
#endif

    strcat(pcFullName, pcName);

    pfLog = fopen(pcFullName, "w");

    if (!pfLog) {
        return;
    }

    __GetKeyboardPageXML(pKDBInfo, pbLogData, (ET9U32)sizeof(pbLogData), &dwLogDataUsed, 1);

    if (dwLogDataUsed) {

        fwrite(pbLogData, dwLogDataUsed, 1, pfLog);
    }

    fclose(pfLog);
}

#else
#define __LogKeyboardPage(pKDBInfo)
#endif /* _ET9_KDB_KEYBOARD_LOG */

/* ************************************************************************************************************** */
/* * DIACRITICS ************************************************************************************************* */
/* ************************************************************************************************************** */

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                                                    
 */

static const ET9U8 __JIS_DiacriticTable_FW[] =
{
    /*      0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F */
    /*A*/   2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  4,  2,  4,  2,  4,
    /*B*/   2,  4,  2,  4,  2,  4,  2,  4,  2,  4,  2,  4,  2,  4,  2,  4,
    /*C*/   2,  4,  2,  4,  8,  2,  4,  2,  4,  2,  2,  2,  2,  2,  2,  4,
    /*D*/   8,  2,  4,  8,  2,  4,  8,  2,  4,  8,  2,  4,  8,  2,  2,  2,
    /*E*/   2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,
    /*F*/   2,  2,  2,  2,  8,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1
};

#define JIS_CLASS_OTHER         1
#define JIS_CLASS_NONE          2
#define JIS_CLASS_DAKUTEN       4
#define JIS_CLASS_HANDAKUTEN    8

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                  
 */

typedef enum JIS_FILTERMODE_e {

    JIS_FILTERMODE_ALL,             /* < -IDR-                 */
    JIS_FILTERMODE_NONE_INC,        /* < -IDR-                                    */
    JIS_FILTERMODE_NONE,            /* < -IDR-                                           */
    JIS_FILTERMODE_DAKUTEN,         /* < -IDR-                  */
    JIS_FILTERMODE_HANDAKUTEN,      /* < -IDR-                     */

    JIS_FILTERMODE_LAST             /* < -IDR-              */
} JIS_FILTERMODE;

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                   
 *
 *                                                   
 *
 *                  
 */

ET9INLINE static ET9U8 ET9LOCALCALL __JIS_DiacriticGetClass(const ET9SYMB   sSymb)
{
    if (sSymb >= 0x829F && sSymb <= 0x82FE) {
        return __JIS_DiacriticTable_FW[sSymb - 0x829F];
    }
    else if (sSymb >= 0x8340 && sSymb <= 0x839F) {
        return __JIS_DiacriticTable_FW[sSymb - 0x8340];
    }
    else {
        return JIS_CLASS_OTHER;
    }
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                                  
 *
 *                                                              
 *                                                   
 *
 *                                                     
 */

static ET9STATUS ET9LOCALCALL __DefaultDiacriticFilterShiftJis(void         * const pFilterSymbInfo,
                                                               const ET9SYMB        sSymb)
{
    ET9KDBInfo   * const pKDBInfo = (ET9KDBInfo*)pFilterSymbInfo;

    ET9BOOL         bAllow;
    const ET9U8     bClass = __JIS_DiacriticGetClass(sSymb);

    ET9Assert(pFilterSymbInfo);

    /* filter */

    switch (pKDBInfo->Private.bCurrDiacriticState)
    {
        default:
        case JIS_FILTERMODE_ALL:
            bAllow = 1;
            break;
        case JIS_FILTERMODE_NONE_INC:
            bAllow = (ET9BOOL)(bClass & (JIS_CLASS_NONE | JIS_CLASS_OTHER));
            break;
        case JIS_FILTERMODE_NONE:
            bAllow = (ET9BOOL)(bClass & JIS_CLASS_NONE);
            break;
        case JIS_FILTERMODE_DAKUTEN:
            bAllow = (ET9BOOL)(bClass & JIS_CLASS_DAKUTEN);
            break;
        case JIS_FILTERMODE_HANDAKUTEN:
            bAllow = (ET9BOOL)(bClass & JIS_CLASS_HANDAKUTEN);
            break;
    }

    /* done */

    WLOG5B(fprintf(pLogFile5, "__DefaultDiacriticFilterShiftJis, bCurrDiacriticState %u, sSymb %04x, bClass %u, bAllow %u\n", pKDBInfo->Private.bCurrDiacriticState, sSymb, bClass, bAllow);)

    return bAllow ? ET9STATUS_NONE : ET9STATUS_ERROR;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                                     
 *
 *                                                              
 *
 *             
 */

static void ET9LOCALCALL __DefaultDiacriticFilterResetShiftJis(void    * const pFilterSymbInfo)
{
    ET9KDBInfo   * const pKDBInfo = (ET9KDBInfo*)pFilterSymbInfo;

    ET9Assert(pFilterSymbInfo);

    if (ET9_KDB_MULTITAP_MODE(pKDBInfo->dwStateBits)) {
        pKDBInfo->Private.bCurrDiacriticState = (ET9U8)JIS_FILTERMODE_NONE_INC;
    }
    else {
        pKDBInfo->Private.bCurrDiacriticState = (ET9U8)JIS_FILTERMODE_ALL;
    }
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                                     
 *
 *                                                              
 *                                                                        
 *
 *                                         
 */

static ET9STATUS ET9LOCALCALL __DefaultDiacriticFilterCountShiftJis(void    * const pFilterSymbInfo,
                                                                    ET9U8   * const pbFilterCount)
{
    ET9KDBInfo   * const pKDBInfo = (ET9KDBInfo*)pFilterSymbInfo;

    ET9Assert(pFilterSymbInfo);
    ET9Assert(pbFilterCount);

    if (ET9_KDB_MULTITAP_MODE(pKDBInfo->dwStateBits)) {
        *pbFilterCount = 4;
    }
    else {
        *pbFilterCount = 3;
    }

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                                     
 *
 *                                                              
 *
 *                                         
 */

static ET9STATUS ET9LOCALCALL __DefaultDiacriticFilterNextShiftJis(void    * const pFilterSymbInfo)
{
    ET9KDBInfo   * const pKDBInfo = (ET9KDBInfo*)pFilterSymbInfo;

    JIS_FILTERMODE eMode = (JIS_FILTERMODE)pKDBInfo->Private.bCurrDiacriticState;

    ET9Assert(pFilterSymbInfo);

    if (ET9_KDB_MULTITAP_MODE(pKDBInfo->dwStateBits)) {
        switch (eMode)
        {
            case JIS_FILTERMODE_NONE_INC:
                eMode = JIS_FILTERMODE_DAKUTEN;
                break;
            case JIS_FILTERMODE_DAKUTEN:
                eMode = JIS_FILTERMODE_HANDAKUTEN;
                break;
            case JIS_FILTERMODE_HANDAKUTEN:
                eMode = JIS_FILTERMODE_NONE;
                break;
            default:
            case JIS_FILTERMODE_NONE:
                eMode = JIS_FILTERMODE_NONE_INC;
                break;
        }
    }
    else {
        switch (eMode)
        {
            case JIS_FILTERMODE_ALL:
                eMode = JIS_FILTERMODE_DAKUTEN;
                break;
            case JIS_FILTERMODE_DAKUTEN:
                eMode = JIS_FILTERMODE_HANDAKUTEN;
                break;
            default:
            case JIS_FILTERMODE_HANDAKUTEN:
                eMode = JIS_FILTERMODE_ALL;
                break;
        }
    }

    pKDBInfo->Private.bCurrDiacriticState = (ET9U8)eMode;

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                                 
 *
 *                                                               
 *                                           
 *                                            
 *
 *                                                    
 */

static ET9BOOL ET9LOCALCALL __DefaultDiacriticFilterGroupShiftJis(void              * const pFilterSymbInfo,
                                                                  const ET9SYMB             sSymb1,
                                                                  const ET9SYMB             sSymb2)
{
    ET9SYMB sLow;
    ET9SYMB sHigh;

    ET9_UNUSED(pFilterSymbInfo);

    if (sSymb1 == sSymb2) {
        return 1;
    }
    else if (sSymb1 < sSymb2) {
        sLow = sSymb1;
        sHigh = sSymb2;
    }
    else {
        sLow = sSymb2;
        sHigh = sSymb1;
    }

    {
        const ET9U8   bClassLow = __JIS_DiacriticGetClass(sLow);
        const ET9U8   bClassHigh = __JIS_DiacriticGetClass(sHigh);

        return (ET9BOOL)((bClassLow == JIS_CLASS_NONE    && bClassHigh == JIS_CLASS_DAKUTEN    && (sLow + 1) == sHigh) ||
                         (bClassLow == JIS_CLASS_NONE    && bClassHigh == JIS_CLASS_HANDAKUTEN && (sLow + 2) == sHigh) ||
                         (bClassLow == JIS_CLASS_DAKUTEN && bClassHigh == JIS_CLASS_HANDAKUTEN && (sLow + 1) == sHigh));
    }
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                       
 *                                                    
 *
 *                                                                       
 *
 *             
 */

ET9INLINE static void ET9LOCALCALL __FilterSymbReset(ET9KDBInfo   * const pKDBInfo)
{
    ET9Assert(pKDBInfo);

    pKDBInfo->Private.sKdbAction.dwCurrChecksum = 0;

    if (pKDBInfo->Private.pFilterSymbReset) {

        pKDBInfo->Private.pFilterSymbReset(pKDBInfo->Private.pFilterSymbInfo);
    }
}

/* ************************************************************************************************************** */
/* * UTIL ******************************************************************************************************* */
/* ************************************************************************************************************** */

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                         
 *
 *                                                                       
 *
 *             
 */

static void ET9LOCALCALL __MirrorWsiState(ET9KDBInfo * const pKDBInfo)
{
    ET9WordSymbInfo * const pWordSymbInfo = pKDBInfo->Private.pWordSymbInfo;

    if (ET9SHIFT_MODE(pWordSymbInfo->dwStateBits)) {
        pKDBInfo->Private.eShiftState = ET9SHIFT;
    }
    else if (ET9CAPS_MODE(pWordSymbInfo->dwStateBits)) {
        pKDBInfo->Private.eShiftState = ET9CAPSLOCK;
    }
    else {
        pKDBInfo->Private.eShiftState = ET9NOSHIFT;
    }
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                   
 *
 *                                                     
 *
 *                                              
 */

static ET9U32 ET9LOCALCALL __CalculateLastWordSymbChecksum(ET9WordSymbInfo   * const pWordSymbInfo)
{
    ET9U8                   bBaseCount;
    ET9DataPerBaseSym       *pDPBS;
    ET9U32                  dwHashValue = 0;
    const ET9U8             bNumSymbs = pWordSymbInfo->bNumSymbs;
    ET9SymbInfo     * const pSymbInfo = pWordSymbInfo->SymbsInfo + bNumSymbs - 1;

    ET9Assert(pWordSymbInfo);

    /* check if it's a valid symbol */

    if (!bNumSymbs || !pSymbInfo->bNumBaseSyms || !pSymbInfo->DataPerBaseSym->bNumSymsToMatch) {
        return 0;
    }

    /* mark position */

    dwHashValue = bNumSymbs;

    /* calculate */

    pDPBS = pSymbInfo->DataPerBaseSym;
    for (bBaseCount = pSymbInfo->bNumBaseSyms; bBaseCount; --bBaseCount, ++pDPBS) {

        ET9U8       bCharCount;
        ET9SYMB     *psChar;
        ET9SYMB     *psUpperChar;

        psChar = pDPBS->sChar;
        psUpperChar = pDPBS->sUpperCaseChar;
        for (bCharCount = pDPBS->bNumSymsToMatch; bCharCount; --bCharCount, ++psChar, ++psUpperChar) {
            dwHashValue = *psChar + (65599 * dwHashValue);
            dwHashValue = *psUpperChar + (65599 * dwHashValue);
        }
    }

    /* must be non zero - very unlikely, but anyway */

    if (!dwHashValue) {
        dwHashValue = 1;
    }

    /* done */

    return dwHashValue;
}

#ifdef ET9_KDB_TRACE_MODULE

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                                                       
 *
 *                                                                       
 *                                                          
 *
 *                           
 */

ET9INLINE static ET9FLOAT ET9LOCALCALL __ScaleCoordinateToKdbX_f(ET9KDBInfo     const * const pKDBInfo,
                                                                 const ET9FLOAT               fX)
{
    if (!pKDBInfo->Private.wScaleToLayoutWidth) {
        return fX - pKDBInfo->Private.wLayoutOffsetX;
    }
    else {

        const ET9FLOAT fLayoutWidth  = (ET9FLOAT)pKDBInfo->Private.pCurrLayoutInfo->wLayoutWidth;
        const ET9FLOAT fScaleToLayoutWidth  = (ET9FLOAT)pKDBInfo->Private.wScaleToLayoutWidth;

        return (fX - pKDBInfo->Private.wLayoutOffsetX) * fLayoutWidth / fScaleToLayoutWidth;
    }
}

#endif

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                                                       
 *
 *                                                                       
 *                                                          
 *
 *                           
 */

ET9INLINE static ET9U32 ET9LOCALCALL __ScaleCoordinateToKdbX(ET9KDBInfo     const * const pKDBInfo,
                                                             const ET9UINT                nX)
{
    if (nX < pKDBInfo->Private.wLayoutOffsetX) {
        return ET9_KDB_COORD_OUTOFBOUND;
    }
    else if (!pKDBInfo->Private.wScaleToLayoutWidth) {
        return nX - pKDBInfo->Private.wLayoutOffsetX;
    }
    else {

        const ET9U32 dwLayoutWidth  = pKDBInfo->Private.pCurrLayoutInfo->wLayoutWidth;
        const ET9U32 dwScaleToLayoutWidth  = pKDBInfo->Private.wScaleToLayoutWidth;

        const ET9U32 dwNumX = (nX - pKDBInfo->Private.wLayoutOffsetX) * dwLayoutWidth;
        const ET9U32 dwRestX = dwNumX % dwScaleToLayoutWidth;
        const ET9U32 dwX = (dwNumX / dwScaleToLayoutWidth ) + (dwRestX > (dwScaleToLayoutWidth / 2) ? 1 : 0);

        return dwX;
    }
}

#ifdef ET9_KDB_TRACE_MODULE

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                                                      
 *
 *                                                                       
 *                                                          
 *
 *                           
 */

ET9INLINE static ET9FLOAT ET9LOCALCALL __ScaleCoordinateToKdbY_f(ET9KDBInfo     const * const pKDBInfo,
                                                                 const ET9FLOAT               fY)
{
    if (!pKDBInfo->Private.wScaleToLayoutHeight) {
        return fY - pKDBInfo->Private.wLayoutOffsetY;
    }
    else {

        const ET9FLOAT fLayoutHeight = (ET9FLOAT)pKDBInfo->Private.pCurrLayoutInfo->wLayoutHeight;
        const ET9FLOAT fScaleToLayoutHeight = (ET9FLOAT)pKDBInfo->Private.wScaleToLayoutHeight;

        return (fY - pKDBInfo->Private.wLayoutOffsetY) * fLayoutHeight / fScaleToLayoutHeight;
    }
}

#endif

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                                                      
 *
 *                                                                       
 *                                                          
 *
 *                           
 */

ET9INLINE static ET9U32 ET9LOCALCALL __ScaleCoordinateToKdbY(ET9KDBInfo     const * const pKDBInfo,
                                                             const ET9UINT                nY)
{
    if (nY < pKDBInfo->Private.wLayoutOffsetY) {
        return ET9_KDB_COORD_OUTOFBOUND;
    }
    else if (!pKDBInfo->Private.wScaleToLayoutHeight) {
        return nY - pKDBInfo->Private.wLayoutOffsetY;
    }
    else {

        const ET9U32 dwLayoutHeight = pKDBInfo->Private.pCurrLayoutInfo->wLayoutHeight;
        const ET9U32 dwScaleToLayoutHeight = pKDBInfo->Private.wScaleToLayoutHeight;

        const ET9U32 dwNumY = (nY - pKDBInfo->Private.wLayoutOffsetY) * dwLayoutHeight;
        const ET9U32 dwRestY = dwNumY % dwScaleToLayoutHeight;
        const ET9U32 dwY = (dwNumY / dwScaleToLayoutHeight) + (dwRestY > (dwScaleToLayoutHeight / 2) ? 1 : 0);

        return dwY;
    }
}

#ifdef ET9_KDB_TRACE_MODULE

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                                                      
 *
 *                                                                       
 *                                                             
 *
 *                           
 */

ET9INLINE static ET9FLOAT ET9LOCALCALL __ScaleCoordinateToIntegrationX_f(ET9KDBInfo     const * const pKDBInfo,
                                                                         const ET9FLOAT               fX)
{
    if (!pKDBInfo->Private.wScaleToLayoutWidth) {
        return fX + pKDBInfo->Private.wLayoutOffsetX;
    }
    else {

        const ET9FLOAT fLayoutWidth = (ET9FLOAT)pKDBInfo->Private.pCurrLayoutInfo->wLayoutWidth;
        const ET9FLOAT fScaleToLayoutWidth = (ET9FLOAT)pKDBInfo->Private.wScaleToLayoutWidth;

        return (fX * fScaleToLayoutWidth / fLayoutWidth) + pKDBInfo->Private.wLayoutOffsetX;
    }
}

#endif

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                                                      
 *
 *                                                                       
 *                                                             
 *
 *                           
 */

ET9INLINE static ET9U32 ET9LOCALCALL __ScaleCoordinateToIntegrationX(ET9KDBInfo     const * const pKDBInfo,
                                                                     const ET9UINT                nX)
{
    if (!pKDBInfo->Private.wScaleToLayoutWidth) {
        return nX + pKDBInfo->Private.wLayoutOffsetX;
    }
    else {

        const ET9U32 dwLayoutWidth  = pKDBInfo->Private.pCurrLayoutInfo->wLayoutWidth;
        const ET9U32 dwScaleToLayoutWidth  = pKDBInfo->Private.wScaleToLayoutWidth;

        const ET9U32 dwNumX = nX * dwScaleToLayoutWidth;
        const ET9U32 dwRestX = dwNumX % dwLayoutWidth;
        const ET9U32 dwX = (dwNumX / dwLayoutWidth ) + (dwRestX > (dwLayoutWidth / 2) ? 1 : 0);

        return dwX + pKDBInfo->Private.wLayoutOffsetX;
    }
}

#ifdef ET9_KDB_TRACE_MODULE

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                                                      
 *
 *                                                                       
 *                                                             
 *
 *                           
 */

ET9INLINE static ET9FLOAT ET9LOCALCALL __ScaleCoordinateToIntegrationY_f(ET9KDBInfo     const * const pKDBInfo,
                                                                         const ET9FLOAT               fY)
{
    if (!pKDBInfo->Private.wScaleToLayoutHeight) {
        return fY + pKDBInfo->Private.wLayoutOffsetY;
    }
    else {

        const ET9FLOAT fLayoutHeight = (ET9FLOAT)pKDBInfo->Private.pCurrLayoutInfo->wLayoutHeight;
        const ET9FLOAT fScaleToLayoutHeight = (ET9FLOAT)pKDBInfo->Private.wScaleToLayoutHeight;

        return (fY * fScaleToLayoutHeight / fLayoutHeight) + pKDBInfo->Private.wLayoutOffsetY;
    }
}

#endif

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                                                      
 *
 *                                                                       
 *                                                             
 *
 *                           
 */

ET9INLINE static ET9U32 ET9LOCALCALL __ScaleCoordinateToIntegrationY(ET9KDBInfo     const * const pKDBInfo,
                                                                     const ET9UINT                nY)
{
    if (!pKDBInfo->Private.wScaleToLayoutHeight) {
        return nY + pKDBInfo->Private.wLayoutOffsetY;
    }
    else {

        const ET9U32 dwLayoutHeight = pKDBInfo->Private.pCurrLayoutInfo->wLayoutHeight;
        const ET9U32 dwScaleToLayoutHeight = pKDBInfo->Private.wScaleToLayoutHeight;

        const ET9U32 dwNumY = nY * dwScaleToLayoutHeight;
        const ET9U32 dwRestY = dwNumY % dwLayoutHeight;
        const ET9U32 dwY = (dwNumY / dwLayoutHeight) + (dwRestY > (dwLayoutHeight / 2) ? 1 : 0);

        return dwY + pKDBInfo->Private.wLayoutOffsetY;
    }
}

#ifdef ET9_KDB_TRACE_MODULE

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                                               
 *
 *                                                                       
 *                                     
 *                                        
 *
 *                           
 */

ET9INLINE static ET9TracePoint_f* ET9LOCALCALL __ScaleToIntegration_f(ET9KDBInfo         const * const pKDBInfo,
                                                                      ET9TracePoint_f    const * const pPointSrc,
                                                                      ET9TracePoint_f          * const pPointDst)
{
    pPointDst->fX = __ScaleCoordinateToIntegrationX_f(pKDBInfo, pPointSrc->fX);
    pPointDst->fY = __ScaleCoordinateToIntegrationY_f(pKDBInfo, pPointSrc->fY);

    return pPointDst;
}

#endif

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                                   
 *                                   
 *                                   
 *                                   
 *
 *            
 */

ET9INLINE static ET9FLOAT ET9LOCALCALL __LinesAngle (ET9TracePoint_f const * const pTpL11,
                                                     ET9TracePoint_f const * const pTpL12,
                                                     ET9TracePoint_f const * const pTpL21,
                                                     ET9TracePoint_f const * const pTpL22)
{
    const ET9FLOAT fL1X = pTpL11->fX - pTpL12->fX;
    const ET9FLOAT fL1Y = pTpL11->fY - pTpL12->fY;
    const ET9FLOAT fL2X = pTpL21->fX - pTpL22->fX;
    const ET9FLOAT fL2Y = pTpL21->fY - pTpL22->fY;

    const ET9FLOAT fY = fL1X * fL2Y - fL1Y * fL2X;
    const ET9FLOAT fX = fL1X * fL2X + fL1Y * fL2Y;

    const ET9FLOAT fAngleR = _ET9atan2_f(fY, fX);

    const ET9FLOAT fAngleD = (ET9FLOAT)(fAngleR * 180.0 / fPiR);

    return fAngleD;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                  
 *
 *                                        
 *                                                  
 *
 *             
 */

static void ET9LOCALCALL __SortU16Array (ET9U16 * const pwArray, const ET9UINT nCount)
{
    ET9UINT nDirty;
    ET9UINT nIndex;

    for (nDirty = 1; nDirty;) {

        nDirty = 0;

        for (nIndex = 0; nIndex + 1 < nCount; ++nIndex) {

            if (pwArray[nIndex] > pwArray[nIndex + 1]) {

                const ET9U16 nTmp = pwArray[nIndex];
                pwArray[nIndex] = pwArray[nIndex + 1];
                pwArray[nIndex + 1] = nTmp;

                nDirty = 1;
            }
        }
    }
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                            
 *
 *                            
 *                            
 *
 *                          
 */

ET9INLINE static ET9UINT ET9LOCALCALL __GetRegionOverlap(ET9Region const * const pRegion1,
                                                         ET9Region const * const pRegion2)
{
    if (pRegion1->wRight  < pRegion2->wLeft ||
        pRegion2->wRight  < pRegion1->wLeft ||
        pRegion1->wBottom < pRegion2->wTop  ||
        pRegion2->wBottom < pRegion1->wTop) {

        return 0;
    }
    else {

        const ET9UINT nOverlapX = __ET9Min(pRegion1->wRight,  pRegion2->wRight)  - __ET9Max(pRegion1->wLeft, pRegion2->wLeft) + 1;
        const ET9UINT nOverlapY = __ET9Min(pRegion1->wBottom, pRegion2->wBottom) - __ET9Max(pRegion1->wTop,  pRegion2->wTop)  + 1;

        return nOverlapX * nOverlapY;
    }
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                            
 *
 *                            
 *                            
 *                            
 *                            
 *                            
 *
 *             
 */

ET9INLINE static void ET9LOCALCALL __CreateRegion(ET9Region     * const pRegion,
                                                  const ET9UINT         nX,
                                                  const ET9UINT         nY,
                                                  const ET9UINT         nW,
                                                  const ET9UINT         nH)
{
    const ET9UINT nMax = 0xFFFF;

    ET9UINT nLeft;
    ET9UINT nTop;
    ET9UINT nRight;
    ET9UINT nBottom;

    nLeft   = nX - (nW / 2);
    nTop    = nY - (nH / 2);
    nRight  = nLeft + nW - 1;
    nBottom = nTop  + nH - 1;

    if (nLeft > nX) {
        nLeft = 0;
    }

    if (nTop > nY) {
        nTop = 0;
    }

    if (nRight < nLeft) {
        nRight = nMax;
    }

    if (nBottom < nTop) {
        nBottom = nMax;
    }

    pRegion->wLeft   = (ET9U16)(nLeft   < nMax ? nLeft   : nMax);
    pRegion->wTop    = (ET9U16)(nTop    < nMax ? nTop    : nMax);
    pRegion->wRight  = (ET9U16)(nRight  < nMax ? nRight  : nMax);
    pRegion->wBottom = (ET9U16)(nBottom < nMax ? nBottom : nMax);
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                               
 *
 *                            
 *                            
 *
 *                          
 */

ET9INLINE static ET9FLOAT ET9LOCALCALL __PointToKeyDistSQ(ET9TracePoint       const * const pPos,
                                                          ET9KdbAreaInfo      const * const pArea)
{
    const ET9U16 wX = (ET9U16)pPos->nX;
    const ET9U16 wY = (ET9U16)pPos->nY;

    ET9FLOAT fDistX;
    ET9FLOAT fDistY;

    if (pArea->sKeyCore.wLeft == pArea->sKeyCore.wRight &&
        pArea->sKeyCore.wTop == pArea->sKeyCore.wBottom) {

        fDistX = (ET9FLOAT)wX - (ET9FLOAT)pArea->sKeyCore.wLeft;
        fDistY = (ET9FLOAT)wY - (ET9FLOAT)pArea->sKeyCore.wTop;
    }
    else if (wY < pArea->sKeyCore.wTop) {

        if (wX < pArea->sKeyCore.wLeft) {

            fDistX = (ET9FLOAT)wX - (ET9FLOAT)pArea->sKeyCore.wLeft;
            fDistY = (ET9FLOAT)wY - (ET9FLOAT)pArea->sKeyCore.wTop;
        }
        else if (wX > pArea->sKeyCore.wRight) {

            fDistX = (ET9FLOAT)wX - (ET9FLOAT)pArea->sKeyCore.wRight;
            fDistY = (ET9FLOAT)wY - (ET9FLOAT)pArea->sKeyCore.wTop;
        }
        else {

            fDistX = 0.0f;
            fDistY = (ET9FLOAT)wY - (ET9FLOAT)pArea->sKeyCore.wTop;
        }
    }
    else if (wY > pArea->sKeyCore.wBottom) {

        if (wX < pArea->sKeyCore.wLeft) {

            fDistX = (ET9FLOAT)wX - (ET9FLOAT)pArea->sKeyCore.wLeft;
            fDistY = (ET9FLOAT)wY - (ET9FLOAT)pArea->sKeyCore.wBottom;
        }
        else if (wX > pArea->sKeyCore.wRight) {

            fDistX = (ET9FLOAT)wX - (ET9FLOAT)pArea->sKeyCore.wRight;
            fDistY = (ET9FLOAT)wY - (ET9FLOAT)pArea->sKeyCore.wBottom;
        }
        else {

            fDistX = 0.0f;
            fDistY = (ET9FLOAT)wY - (ET9FLOAT)pArea->sKeyCore.wBottom;
        }
    }
    else if (wX < pArea->sKeyCore.wLeft) {

        if (wY < pArea->sKeyCore.wTop) {

            fDistX = (ET9FLOAT)wX - (ET9FLOAT)pArea->sKeyCore.wLeft;
            fDistY = (ET9FLOAT)wY - (ET9FLOAT)pArea->sKeyCore.wTop;
        }
        else if (wY > pArea->sKeyCore.wBottom) {

            fDistX = (ET9FLOAT)wX - (ET9FLOAT)pArea->sKeyCore.wLeft;
            fDistY = (ET9FLOAT)wY - (ET9FLOAT)pArea->sKeyCore.wBottom;
        }
        else {

            fDistX = (ET9FLOAT)wX - (ET9FLOAT)pArea->sKeyCore.wLeft;
            fDistY = 0.0f;
        }
    }
    else if (wX > pArea->sKeyCore.wRight) {

        if (wY < pArea->sKeyCore.wTop) {

            fDistX = (ET9FLOAT)wX - (ET9FLOAT)pArea->sKeyCore.wRight;
            fDistY = (ET9FLOAT)wY - (ET9FLOAT)pArea->sKeyCore.wTop;
        }
        else if (wY > pArea->sKeyCore.wBottom) {

            fDistX = (ET9FLOAT)wX - (ET9FLOAT)pArea->sKeyCore.wRight;
            fDistY = (ET9FLOAT)wY - (ET9FLOAT)pArea->sKeyCore.wBottom;
        }
        else {

            fDistX = (ET9FLOAT)wX - (ET9FLOAT)pArea->sKeyCore.wRight;
            fDistY = 0.0f;
        }
    }
    else {

        fDistX = 0.0f;
        fDistY = 0.0f;
    }

    {
        const ET9FLOAT fDistSQ = fDistX * fDistX + fDistY * fDistY;

        return fDistSQ;
    }
}

/* ************************************************************************************************************** */
/* * DYNAMIC **************************************************************************************************** */
/* ************************************************************************************************************** */

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                  
 *
 *                                                               
 *
 *             
 */

static void ET9LOCALCALL __CalculateKeySize_Dynamic (ET9KDBInfo  * const pKDBInfo)
{
    ET9KdbLayoutInfo * const pLayoutInfo = pKDBInfo->Private.pCurrLayoutInfo;

    ET9U16 pwWidths[ET9_KDB_MAX_KEYS];
    ET9U16 pwHeights[ET9_KDB_MAX_KEYS];

    ET9UINT nCount;
    ET9UINT nItemCount;
    ET9U16 *pwWidth;
    ET9U16 *pwHeight;
    ET9KdbAreaInfo *pArea;

    WLOG5(fprintf(pLogFile5, "__CalculateKeySize_Dynamic\n");)

    nItemCount = 0;
    pwWidth = &pwWidths[0];
    pwHeight = &pwHeights[0];
    pArea = pLayoutInfo->pKeyAreas;
    for (nCount = pLayoutInfo->nKeyAreaCount; nCount; --nCount, ++pArea) {

        if (pArea->eKeyType == ET9KTFUNCTION) {
            continue;
        }

        *pwWidth = pArea->sRegion.wRight - pArea->sRegion.wLeft + 1;
        *pwHeight = pArea->sRegion.wBottom - pArea->sRegion.wTop + 1;

        ++pwWidth;
        ++pwHeight;
        ++nItemCount;
    }

    if (nItemCount) {

        __SortU16Array(pwWidths, nItemCount);
        __SortU16Array(pwHeights, nItemCount);

        pLayoutInfo->nMinKeyWidth = pwWidths[0];
        pLayoutInfo->nMinKeyHeight = pwHeights[0];

        pLayoutInfo->nMedianKeyWidth = pwWidths[nItemCount / 2];
        pLayoutInfo->nMedianKeyHeight = pwHeights[nItemCount / 2];
    }
    else {

        WLOG5(fprintf(pLogFile5, "  no valid keys\n");)

        pLayoutInfo->nMinKeyWidth = 10;
        pLayoutInfo->nMinKeyHeight = 10;

        pLayoutInfo->nMedianKeyWidth = 10;
        pLayoutInfo->nMedianKeyHeight = 10;
    }

    WLOG5(fprintf(pLogFile5, "  nMinKeyWidth %2u, nMinKeyHeight %2u, nMedianKeyWidth %2u, nMedianKeyHeight %2u\n", pLayoutInfo->nMinKeyWidth, pLayoutInfo->nMinKeyHeight, pLayoutInfo->nMedianKeyWidth, pLayoutInfo->nMedianKeyHeight);)
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                     
 *
 *                                                               
 *
 *             
 */

static void ET9LOCALCALL __CalculateKeyCoreSize_Dynamic (ET9KDBInfo  * const pKDBInfo)
{
    ET9KdbLayoutInfo * const pLayoutInfo = pKDBInfo->Private.pCurrLayoutInfo;

    const ET9UINT nHalfMedianWidth = (pLayoutInfo->nMedianKeyWidth + 1) / 2;
    const ET9UINT nHalfMedianHeight = (pLayoutInfo->nMedianKeyHeight + 1) / 2;

    ET9UINT nCount;
    ET9KdbAreaInfo *pArea;

    WLOG5(fprintf(pLogFile5, "__CalculateKeyCoreSize_Dynamic\n");)

    pArea = pLayoutInfo->pKeyAreas;
    for (nCount = pLayoutInfo->nKeyAreaCount; nCount; --nCount, ++pArea) {

        const ET9UINT nKeyWidth = pArea->sRegion.wRight - pArea->sRegion.wLeft + 1;
        const ET9UINT nKeyHeight = pArea->sRegion.wBottom - pArea->sRegion.wTop + 1;

        ET9Region sKeyCore;

        if (nHalfMedianWidth && (nKeyWidth > 2 * nHalfMedianWidth)) {

            sKeyCore.wLeft   = (ET9U16)(pArea->sRegion.wLeft + nHalfMedianWidth);
            sKeyCore.wRight  = (ET9U16)(pArea->sRegion.wRight - nHalfMedianWidth);
        }
        else {

            sKeyCore.wLeft   = (ET9U16)pArea->nCenterX;
            sKeyCore.wRight  = (ET9U16)pArea->nCenterX;
        }

        if (nHalfMedianHeight && (nKeyHeight > 2 * nHalfMedianHeight)) {

            sKeyCore.wTop   = (ET9U16)(pArea->sRegion.wTop + nHalfMedianHeight);
            sKeyCore.wBottom  = (ET9U16)(pArea->sRegion.wBottom - nHalfMedianHeight);
        }
        else {

            sKeyCore.wTop    = (ET9U16)pArea->nCenterY;
            sKeyCore.wBottom = (ET9U16)pArea->nCenterY;
        }

        ET9Assert(sKeyCore.wLeft <= sKeyCore.wRight);
        ET9Assert(sKeyCore.wLeft >= pArea->sRegion.wLeft);
        ET9Assert(sKeyCore.wRight <= pArea->sRegion.wRight);

        ET9Assert(sKeyCore.wTop <= sKeyCore.wBottom);
        ET9Assert(sKeyCore.wTop >= pArea->sRegion.wTop);
        ET9Assert(sKeyCore.wBottom <= pArea->sRegion.wBottom);

        pArea->sKeyCore = sKeyCore;
    }
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                              
 *
 *                                                               
 *
 *             
 */

static void ET9LOCALCALL __CalculateCoreRadius_Dynamic (ET9KDBInfo  * const pKDBInfo)
{
#ifdef _ET9_KDB_REG_PROB_V2

    ET9KdbLayoutInfo * const pLayoutInfo = pKDBInfo->Private.pCurrLayoutInfo;

    ET9FLOAT pfMaxRadiusSQ[ET9KT_LAST];

    ET9UINT nCount;
    ET9UINT nIndex;
    ET9KdbAreaInfo *pArea;

    WLOG5(fprintf(pLogFile5, "__CalculateCoreRadius_Dynamic\n");)

    for (nIndex = 0; nIndex < ET9KT_LAST; ++nIndex) {
        pfMaxRadiusSQ[nIndex] = 1;
    }

    pArea = pLayoutInfo->pKeyAreas;
    for (nCount = pLayoutInfo->nKeyAreaCount; nCount; --nCount, ++pArea) {

        ET9Region sThisArea;

        ET9UINT nNeighborCount;
        ET9KdbAreaInfo *pNeighborArea;

#if 0
        const ET9FLOAT fThisX = pLayoutInfo->fCoreScaleX * pArea->nCenterX;
        const ET9FLOAT fThisY = pLayoutInfo->fCoreScaleY * pArea->nCenterY;
#else
        const ET9FLOAT fThisX = (ET9FLOAT)pArea->nCenterX;
        const ET9FLOAT fThisY = (ET9FLOAT)pArea->nCenterY;
#endif

        __CreateRegion(&sThisArea, pArea->nCenterX, pArea->nCenterY, pLayoutInfo->nBoxWidth, pLayoutInfo->nBoxHeight);

        WLOG5(fprintf(pLogFile5, "  key '%c' - neighbors ", pArea->psChars[0]);)

        if (pArea->eKeyType == ET9KTFUNCTION) {
            continue;
        }

        pNeighborArea = pLayoutInfo->pKeyAreas;
        for (nNeighborCount = pLayoutInfo->nKeyAreaCount; nNeighborCount; --nNeighborCount, ++pNeighborArea) {

            if (pNeighborArea == pArea) {
                continue;
            }

            if (pNeighborArea->eKeyType != pArea->eKeyType) {
                continue;
            }

            if (!((sThisArea.wRight  >= pNeighborArea->sRegion.wLeft && pNeighborArea->sRegion.wRight  >= sThisArea.wLeft) &&
                  (sThisArea.wBottom >= pNeighborArea->sRegion.wTop  && pNeighborArea->sRegion.wBottom >= sThisArea.wTop))) {

                /* no overlap */

                continue;
            }

            WLOG5(fprintf(pLogFile5, "%c", pNeighborArea->psChars[0]);)

            {
#if 0
                const ET9FLOAT fNeighborX = pLayoutInfo->fCoreScaleX * pNeighborArea->nCenterX;
                const ET9FLOAT fNeighborY = pLayoutInfo->fCoreScaleY * pNeighborArea->nCenterY;
#else
                const ET9FLOAT fNeighborX = (ET9FLOAT)pNeighborArea->nCenterX;
                const ET9FLOAT fNeighborY = (ET9FLOAT)pNeighborArea->nCenterY;
#endif

                const ET9FLOAT fDistanceSQ = (fThisX - fNeighborX) * (fThisX - fNeighborX) + (fThisY - fNeighborY) * (fThisY - fNeighborY);

                if (pfMaxRadiusSQ[pArea->eKeyType] < fDistanceSQ) {
                    pfMaxRadiusSQ[pArea->eKeyType] = fDistanceSQ;
                }
            }
        }

        WLOG5(fprintf(pLogFile5, "\n");)
    }

    WLOG5(
        for (nIndex = 0; nIndex < ET9KT_LAST; ++nIndex) {
            fprintf(pLogFile5, "  radius[%u] %5.1f\n", nIndex, _ET9sqrt_f(pfMaxRadiusSQ[nIndex]));
        })

    {
        const ET9FLOAT fMaxRadiusSQ = (pfMaxRadiusSQ[ET9KTLETTER] > 1) ? pfMaxRadiusSQ[ET9KTLETTER] :
                                      (pfMaxRadiusSQ[ET9KTPUNCTUATION] > 1) ? pfMaxRadiusSQ[ET9KTPUNCTUATION] :
                                      (pfMaxRadiusSQ[ET9KTSMARTPUNCT] > 1) ? pfMaxRadiusSQ[ET9KTSMARTPUNCT] :
                                      (pfMaxRadiusSQ[ET9KTNUMBER] > 1) ? pfMaxRadiusSQ[ET9KTNUMBER] :
                                      (pfMaxRadiusSQ[ET9KTSTRING] > 1) ? pfMaxRadiusSQ[ET9KTSTRING] :
                                      pfMaxRadiusSQ[ET9KTFUNCTION];

        pLayoutInfo->fCoreRadiusSQ = fMaxRadiusSQ;

        WLOG5(fprintf(pLogFile5, "  core radius %5.1f (%3.1f)\n", _ET9sqrt_f(pLayoutInfo->fCoreRadiusSQ), pLayoutInfo->fCoreRadiusSQ);)
    }

#else

    ET9_UNUSED(pKDBInfo);

#endif /* _ET9_KDB_REG_PROB_V2 */
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                                  
 *
 *                                                               
 *
 *             
 */

static void ET9LOCALCALL __CalculateShiftGestureKeyboardInfo_Dynamic (ET9KDBInfo  * const pKDBInfo)
{
    ET9KdbLayoutInfo * const pLayoutInfo = pKDBInfo->Private.pCurrLayoutInfo;

    ET9U16 wMinX;
    ET9U16 wMinY;

    ET9UINT nCount;
    ET9KdbAreaInfo *pArea;

    WLOG5(fprintf(pLogFile5, "__CalculateShiftGestureKeyboardInfo_Dynamic\n");)

    wMinX = 0xFFFF;
    wMinY = 0xFFFF;

    pArea = pLayoutInfo->pKeyAreas;
    for (nCount = pLayoutInfo->nKeyAreaCount; nCount; --nCount, ++pArea) {

        if (pArea->sRegion.wLeft < wMinX) {
            wMinX = pArea->sRegion.wLeft;
        }
        if (pArea->sRegion.wTop < wMinY) {
            wMinY = pArea->sRegion.wTop;
        }
    }

    if (pLayoutInfo->nKeyAreaCount) {

        pLayoutInfo->wTopOfTopMostKey = wMinY;
        pLayoutInfo->wLeftOfLeftMostKey = wMinX;
    }
    else {

        WLOG5(fprintf(pLogFile5, "  no keys\n");)

        pLayoutInfo->wTopOfTopMostKey = 0;
        pLayoutInfo->wLeftOfLeftMostKey = 0;
    }

    WLOG5(fprintf(pLogFile5, "  wTopOfTopMostKey %u, wLeftOfLeftMostKey %u\n", pLayoutInfo->wTopOfTopMostKey, pLayoutInfo->wLeftOfLeftMostKey);)
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                         
 *
 *                                                               
 *
 *             
 */

static void ET9LOCALCALL __CalculateShiftGestureInfo_Dynamic (ET9KDBInfo  * const pKDBInfo)
{
    ET9KdbLayoutInfo * const pLayoutInfo = pKDBInfo->Private.pCurrLayoutInfo;

    __CalculateShiftGestureKeyboardInfo_Dynamic(pKDBInfo);

    WLOG5(fprintf(pLogFile5, "__CalculateShiftGestureInfo_Dynamic\n");)

    if (pLayoutInfo->nKeyAreaCount) {

        /* Set the initial value to 1/3 key height. */
        pLayoutInfo->wShiftGestureMargin = (ET9U16)(pLayoutInfo->nMedianKeyHeight * _ET9_KDB_GESTURE_MARGIN_FACTOR);
    }
    else {

        pLayoutInfo->wShiftGestureMargin = 0;
    }

    if (pLayoutInfo->wTopOfTopMostKey >= pLayoutInfo->wShiftGestureMargin) {
        pLayoutInfo->wTopOfShiftGestureMargin = pLayoutInfo->wTopOfTopMostKey - pLayoutInfo->wShiftGestureMargin;
    }
    else {
        pLayoutInfo->wTopOfShiftGestureMargin = 0;
    }

    WLOG5(fprintf(pLogFile5, "  wShiftGestureMargin %u, wTopOfShiftGestureMargin %u\n", pLayoutInfo->wShiftGestureMargin, pLayoutInfo->wTopOfShiftGestureMargin);)
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                     
 *
 *                                                               
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __KDBLoadPage_PostProcess_Dynamic(ET9KDBInfo  * const pKDBInfo)
{
    WLOG5(fprintf(pLogFile5, "__KDBLoadPage_PostProcess_Dynamic\n");)

    if (!pKDBInfo->Private.pCurrLayoutInfo->nKeyAreaCount) {
        return ET9STATUS_KDB_PAGE_HAS_NO_KEYS;
    }

    __CalculateKeySize_Dynamic(pKDBInfo);

    __CalculateRegionality_Generic(pKDBInfo);

    __CalculateKeyCoreSize_Dynamic(pKDBInfo);

    __CalculateCoreRadius_Dynamic(pKDBInfo);

    __CalculateShiftGestureInfo_Dynamic(pKDBInfo);

    ET9Assert(pKDBInfo->Private.pCurrLayoutInfo->nBoxWidth);
    ET9Assert(pKDBInfo->Private.pCurrLayoutInfo->nBoxHeight);
    ET9Assert(pKDBInfo->Private.pCurrLayoutInfo->nMinKeyWidth);
    ET9Assert(pKDBInfo->Private.pCurrLayoutInfo->nMinKeyHeight);
    ET9Assert(pKDBInfo->Private.pCurrLayoutInfo->nMedianKeyWidth);
    ET9Assert(pKDBInfo->Private.pCurrLayoutInfo->nMedianKeyHeight);

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                      
 *                                     
 *
 *                                                               
 *                                     
 *                                         
 *                                           
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __KDBLoadPage_Dynamic(ET9KDBInfo      * const pKDBInfo,
                                                    const ET9U32            dwKdbNum,
                                                    const ET9U16            wPageNum,
                                                    ET9U16          * const pwTotalKeys)
{
    ET9STATUS eStatus;

    /* applicable? */

    if (!pKDBInfo->pKDBLoadData) {
        return ET9STATUS_READ_DB_FAIL;
    }

    if (!dwKdbNum) {
        return ET9STATUS_INVALID_KDB_NUM;
    }

    /* check cache */

    {
        ET9UINT nCount;
        ET9KdbLayoutInfo *pLayoutInfo;

        pLayoutInfo = &pKDBInfo->Private.pLayoutInfos[0];
        for (nCount = ET9_KDB_MAX_PAGE_CACHE; nCount; --nCount, ++pLayoutInfo) {

            if (pLayoutInfo->bOk && pLayoutInfo->bDynamic && pLayoutInfo->dwKdbNum == dwKdbNum && pLayoutInfo->wPageNum == wPageNum) {

                WLOG5(fprintf(pLogFile5, "__KDBLoadPage_Dynamic, using cache, wKdbNum %2x, wPageNum %2u\n", pLayoutInfo->dwKdbNum, pLayoutInfo->wPageNum);)

                if (pKDBInfo->Private.pCurrLayoutInfo != pLayoutInfo) {
                    ++pKDBInfo->Private.nLoadID;
                }

                pKDBInfo->Private.pCurrLayoutInfo = pLayoutInfo;

                pKDBInfo->Private.bKDBLoaded = 1;

                pKDBInfo->dwKdbNum = pLayoutInfo->dwKdbNum;
                pKDBInfo->wTotalPages = pLayoutInfo->wTotalPages;

                pKDBInfo->Private.wPageNum = wPageNum;
                pKDBInfo->Private.wPageKeyNum = (ET9U16)pKDBInfo->Private.pCurrLayoutInfo->nKeyAreaCount;

                pKDBInfo->Private.wLayoutWidth = pKDBInfo->Private.pCurrLayoutInfo->wLayoutWidth;
                pKDBInfo->Private.wLayoutHeight = pKDBInfo->Private.pCurrLayoutInfo->wLayoutHeight;

                if (pwTotalKeys){
                    *pwTotalKeys = pKDBInfo->Private.wPageKeyNum;
                }

                return ET9STATUS_NONE;
            }
        }
    }

    WLOG5(fprintf(pLogFile5, "__KDBLoadPage_Dynamic, actual load, wKdbNum %2x, wPageNum %2u\n", dwKdbNum, wPageNum);)

    /* pick cache */

    ++pKDBInfo->Private.pLastLayoutInfo;

    if (pKDBInfo->Private.pLastLayoutInfo < pKDBInfo->Private.pLayoutInfos ||
        pKDBInfo->Private.pLastLayoutInfo - pKDBInfo->Private.pLayoutInfos >= ET9_KDB_MAX_PAGE_CACHE) {
        pKDBInfo->Private.pLastLayoutInfo = &pKDBInfo->Private.pLayoutInfos[0];
    }

    pKDBInfo->Private.pCurrLayoutInfo = pKDBInfo->Private.pLastLayoutInfo;

    /* actual load */

    ++pKDBInfo->Private.nLoadID;

    pKDBInfo->Private.pCurrLayoutInfo->bOk = 0;
    pKDBInfo->Private.pCurrLayoutInfo->bDynamic = 1;

    pKDBInfo->dwKdbNum = dwKdbNum;                                        /* this is the identfier for what to read in through the callback */
    pKDBInfo->wTotalPages = 0;

    pKDBInfo->Private.pCurrLayoutInfo->dwKdbNum = dwKdbNum;
    pKDBInfo->Private.pCurrLayoutInfo->wPageNum = wPageNum;

    pKDBInfo->Private.bKDBLoaded = 0;
    pKDBInfo->Private.bIsLoadingKDB = 1;

    eStatus = ET9KDB_Load_Reset(pKDBInfo);

    if (eStatus) {
        pKDBInfo->Private.bIsLoadingKDB = 0;
        return eStatus;
    }

    eStatus = pKDBInfo->pKDBLoadData(pKDBInfo, dwKdbNum, wPageNum);

    pKDBInfo->Private.bIsLoadingKDB = 0;

    if (eStatus) {
        return eStatus;
    }

    eStatus = __KDBLoadPage_PostProcess_Dynamic(pKDBInfo);

    if (eStatus) {
        return eStatus;
    }

    /* validate */

    {
        const ET9U8 bExpectedPrimary = (ET9U8)(dwKdbNum & PKDBID_MASK);
        const ET9U8 bExpectedSecondary = (ET9U8)((dwKdbNum & SKDBID_MASK) >> 8);

        /* check primary id */

        if (bExpectedPrimary != pKDBInfo->Private.pCurrLayoutInfo->bPrimaryID) {
            return ET9STATUS_KDB_ID_MISMATCH;
        }

        /* check secondary id */

        if (bExpectedSecondary != pKDBInfo->Private.pCurrLayoutInfo->bSecondaryID) {
            return ET9STATUS_KDB_ID_MISMATCH;
        }
    }

    /* loaded ok */

    {
        ET9KdbLayoutInfo * const pLayoutInfo = pKDBInfo->Private.pCurrLayoutInfo;

        pLayoutInfo->bOk = 1;

        pKDBInfo->dwKdbNum = pLayoutInfo->dwKdbNum;
        pKDBInfo->wTotalPages = pLayoutInfo->wTotalPages;

        pKDBInfo->Private.bKDBLoaded = 1;
        pKDBInfo->Private.wPageNum = wPageNum;
        pKDBInfo->Private.wPageKeyNum = (ET9U16)pLayoutInfo->nKeyAreaCount;

        pKDBInfo->Private.wLayoutWidth = pLayoutInfo->wLayoutWidth;
        pKDBInfo->Private.wLayoutHeight = pLayoutInfo->wLayoutHeight;
    }

    if (pwTotalKeys) {
        *pwTotalKeys = pKDBInfo->Private.wPageKeyNum;
    }

    /* log keyboard page */

    __LogKeyboardPage(pKDBInfo);

    /* done */

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                
 *
 *                                                                                                               
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __KDBValidate_Dynamic(ET9KDBInfo  * const pKDBInfo)
{
    ET9STATUS eStatus;

    ET9U16 wPageNum;
    ET9U16 wTotalKeys;
    ET9U16 wTotalPages;

    /* applicable? */

    if (!pKDBInfo->pKDBLoadData) {
        return ET9STATUS_INVALID_KDB_NUM;
    }

    WLOG5(fprintf(pLogFile5, "__KDBValidate_Dynamic, wKdbNum %2x\n", pKDBInfo->dwKdbNum);)

    /* loop pages
       - missing KDB should return ET9STATUS_INVALID_KDB_NUM
       - other errors are passed through */

    wTotalPages = 1;
    for (wPageNum = 0; wPageNum < wTotalPages; ++wPageNum) {

        eStatus = __KDBLoadPage_Dynamic(pKDBInfo, pKDBInfo->dwKdbNum, wPageNum, &wTotalKeys);

        if (eStatus) {
            return eStatus;
        }

        if (!wTotalKeys) {
            return ET9STATUS_KDB_PAGE_HAS_NO_KEYS;
        }

        if (!wPageNum) {
            wTotalPages = pKDBInfo->wTotalPages;
        }
        else if (wTotalPages != pKDBInfo->wTotalPages) {
            return ET9STATUS_KDB_INCONSISTENT_PAGE_COUNT;
        }
    }

    /* done */

    return ET9STATUS_NONE;
}

/* ************************************************************************************************************** */
/* * GENERIC ***************************************************************************************************** */
/* ************************************************************************************************************** */

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                                     
 *
 *                                                               
 *
 *             
 */

static void ET9LOCALCALL __CalculateRegionality_Generic (ET9KDBInfo  * const pKDBInfo)
{
    ET9KdbLayoutInfo * const pLayoutInfo = pKDBInfo->Private.pCurrLayoutInfo;

    ET9UINT nCount;
    ET9KdbAreaInfo *pArea;

    ET9UINT nMinWidth = 0;
    ET9UINT nMinHeight = 0;
    ET9UINT nMinOverlap = 0;

    WLOG5(fprintf(pLogFile5, "__CalculateRegionality_Generic\n");)

    /* calculate min box */

    pArea = pLayoutInfo->pKeyAreas;
    for (nCount = pLayoutInfo->nKeyAreaCount; nCount; --nCount, ++pArea) {

        ET9Region sThisRegion;

        ET9UINT nNeighborCount;
        ET9KdbAreaInfo *pNeighborArea;

        if (pArea->eKeyType == ET9KTFUNCTION) {
            continue;
        }

        WLOG5B(fprintf(pLogFile5, "  area %2u, nCenterX %5u, nCenterY %5u\n", (int)(pArea - pLayoutInfo->pKeyAreas), pArea->nCenterX, pArea->nCenterY);)

        __CreateRegion(&sThisRegion, pArea->nCenterX, pArea->nCenterY, pLayoutInfo->nMedianKeyWidth * 2 - 1, pLayoutInfo->nMedianKeyHeight * 2 - 1);

        pNeighborArea = pLayoutInfo->pKeyAreas;
        for (nNeighborCount = pLayoutInfo->nKeyAreaCount; nNeighborCount; --nNeighborCount, ++pNeighborArea) {

            if (pNeighborArea == pArea) {
                continue;
            }

            if (pNeighborArea->eKeyType != pArea->eKeyType) {
                continue;
            }

            if (!__GetRegionOverlap(&sThisRegion, &pNeighborArea->sRegion)) {
                continue;
            }

            WLOG5B(fprintf(pLogFile5, "    neighbor %2u, wLeft %5u, wTop %5u, wRight %5u, wBottom %5u\n", (int)(pNeighborArea - pLayoutInfo->pKeyAreas), pNeighborArea->sRegion.wLeft, pNeighborArea->sRegion.wTop, pNeighborArea->sRegion.wRight, pNeighborArea->sRegion.wBottom);)

            {
                ET9UINT nW = 0;
                ET9UINT nH = 0;

                if (pNeighborArea->sRegion.wRight < pArea->nCenterX) {
                    nW = pArea->nCenterX - pNeighborArea->sRegion.wRight;
                }
                else if (pNeighborArea->sRegion.wLeft > pArea->nCenterX) {
                    nW = pNeighborArea->sRegion.wLeft - pArea->nCenterX;
                }

                if (pNeighborArea->sRegion.wBottom < pArea->nCenterY) {
                    nH = pArea->nCenterY - pNeighborArea->sRegion.wBottom;
                }
                else if (pNeighborArea->sRegion.wTop > pArea->nCenterY) {
                    nH = pNeighborArea->sRegion.wTop - pArea->nCenterY;
                }

                WLOG5B(fprintf(pLogFile5, "      nW %5u, nH %5u\n", nW, nH);)

                if (nMinWidth < nW) {
                    nMinWidth = nW;
                }
                if (nMinHeight < nH) {
                    nMinHeight = nH;
                }
            }
        }
    }

    {
        const ET9UINT nBoxWidth = (ET9UINT)(nMinWidth * 2 * 1.3 + 1);
        const ET9UINT nBoxHeight = (ET9UINT)(nMinHeight * 2 * 1.3 + 1);

        const ET9UINT nDefaultWidth = pLayoutInfo->nMedianKeyWidth * 2 - 1;
        const ET9UINT nDefaultHeight = pLayoutInfo->nMedianKeyHeight * 2 - 1;

        if (nMinWidth && nBoxWidth < nDefaultWidth) {
            pLayoutInfo->nBoxWidth = nBoxWidth;
        }
        else {
            pLayoutInfo->nBoxWidth = nDefaultWidth;
        }

        if (nMinHeight && nBoxHeight < nDefaultHeight) {
            pLayoutInfo->nBoxHeight = nBoxHeight;
        }
        else {
            pLayoutInfo->nBoxHeight = nDefaultHeight;
        }

        WLOG5(fprintf(pLogFile5, "  box width %2u (%2u), height %2u (%2u)\n", pLayoutInfo->nBoxWidth, nDefaultWidth, pLayoutInfo->nBoxHeight, nDefaultHeight);)
    }

    /* calculate min overlap */

    pArea = pLayoutInfo->pKeyAreas;
    for (nCount = pLayoutInfo->nKeyAreaCount; nCount; --nCount, ++pArea) {

        ET9Region sThisRegion;

        ET9UINT nNeighborCount;
        ET9KdbAreaInfo *pNeighborArea;

        if (pArea->eKeyType == ET9KTFUNCTION) {
            continue;
        }

        __CreateRegion(&sThisRegion, pArea->nCenterX, pArea->nCenterY, pLayoutInfo->nBoxWidth, pLayoutInfo->nBoxHeight);

        pNeighborArea = pLayoutInfo->pKeyAreas;
        for (nNeighborCount = pLayoutInfo->nKeyAreaCount; nNeighborCount; --nNeighborCount, ++pNeighborArea) {

            if (pNeighborArea == pArea) {
                continue;
            }

            if (pNeighborArea->eKeyType != pArea->eKeyType) {
                continue;
            }

            {
                const ET9UINT nOverlap = __GetRegionOverlap(&sThisRegion, &pNeighborArea->sRegion);

                if (nOverlap) {

                    if (!nMinOverlap || nMinOverlap > nOverlap) {
                        nMinOverlap = nOverlap;
                    }
                }
            }
        }
    }

    pLayoutInfo->nMinKeyOverlap = nMinOverlap / 2;

    WLOG5(fprintf(pLogFile5, "  min key overlap %u\n", pLayoutInfo->nMinKeyOverlap);)

#ifdef ET9_DEBUG

    /* validate */

    pArea = pLayoutInfo->pKeyAreas;
    for (nCount = pLayoutInfo->nKeyAreaCount; nCount; --nCount, ++pArea) {

        ET9Region sBoxRegion;
        ET9Region sMedianRegion;

        ET9UINT nNeighborCount;
        ET9KdbAreaInfo *pNeighborArea;

        if (pArea->eKeyType == ET9KTFUNCTION) {
            continue;
        }

        __CreateRegion(&sBoxRegion, pArea->nCenterX, pArea->nCenterY, pLayoutInfo->nBoxWidth, pLayoutInfo->nBoxHeight);
        __CreateRegion(&sMedianRegion, pArea->nCenterX, pArea->nCenterY, pLayoutInfo->nMedianKeyWidth * 2 - 1, pLayoutInfo->nMedianKeyHeight * 2 - 1);

        pNeighborArea = pLayoutInfo->pKeyAreas;
        for (nNeighborCount = pLayoutInfo->nKeyAreaCount; nNeighborCount; --nNeighborCount, ++pNeighborArea) {

            if (pNeighborArea == pArea) {
                continue;
            }

            if (pNeighborArea->eKeyType != pArea->eKeyType) {
                continue;
            }

            {
                const ET9UINT nBoxOverlap = __GetRegionOverlap(&sBoxRegion, &pNeighborArea->sRegion);
                const ET9UINT nMedianOverlap = __GetRegionOverlap(&sMedianRegion, &pNeighborArea->sRegion);

                if (!nBoxOverlap && nMedianOverlap) {
                    ET9Assert(0);
                }
            }
        }
    }

#endif
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                 
 *
 *                                                                                                               
 *                                            
 *
 *                                                        
 */

ET9INLINE static ET9KdbAreaInfo const * ET9LOCALCALL __GetKeyAreaFromKey_Generic(ET9KDBInfo   const * const pKDBInfo,
                                                                                 const ET9U16               wKey)
{
    ET9KdbLayoutInfo * const pLayoutInfo = pKDBInfo->Private.pCurrLayoutInfo;

    ET9UINT nCount;
    ET9KdbAreaInfo const * pArea;

    pArea = pLayoutInfo->pKeyAreas;
    for (nCount = pLayoutInfo->nKeyAreaCount; nCount; --nCount, ++pArea) {

        /* include function keys */

        if (pArea->wKeyIndex == wKey) {
            return pArea;
        }
    }

    return NULL;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                                                
 *
 *                                                                                                               
 *                                                                                                     
 *                                                                                                   
 *
 *                                                        
 */

ET9INLINE static ET9KdbAreaInfo const * ET9LOCALCALL __GetKeyAreaFromTap_Generic(ET9KDBInfo    * const pKDBInfo,
                                                                                 const ET9U16          wX,
                                                                                 const ET9U16          wY)
{
    ET9KdbLayoutInfo * const pLayoutInfo = pKDBInfo->Private.pCurrLayoutInfo;

    ET9UINT nCount;
    ET9KdbAreaInfo const * pArea;

    pArea = pLayoutInfo->pKeyAreas;
    for (nCount = pLayoutInfo->nKeyAreaCount; nCount; --nCount, ++pArea) {

        /* include function keys */

        if (wX >= pArea->sRegion.wLeft && wX <= pArea->sRegion.wRight && wY >= pArea->sRegion.wTop && wY <= pArea->sRegion.wBottom) {
            return pArea;
        }
    }

    return NULL;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                        
 *
 *                                                                       
 *                                          
 *                                                          
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __GetFirstTapChar_Generic(ET9KDBInfo            * const pKDBInfo,
                                                        ET9DirectedPos        * const pDirectedPos,
                                                        ET9SYMB               * const psFirstChar)
{
    ET9KdbLayoutInfo * const pLayoutInfo = pKDBInfo->Private.pCurrLayoutInfo;

    const ET9UINT nX = pDirectedPos->sPos.nX;
    const ET9UINT nY = pDirectedPos->sPos.nY;

    ET9UINT nCount;
    ET9KdbAreaInfo const * pArea;

    *psFirstChar = 0;

    pArea = pLayoutInfo->pKeyAreas;
    for (nCount = pLayoutInfo->nKeyAreaCount; nCount; --nCount, ++pArea) {

        /* allow function keys (needed to to be able to tap a FK) */

        if (nX >= pArea->sRegion.wLeft && nX <= pArea->sRegion.wRight && nY >= pArea->sRegion.wTop && nY <= pArea->sRegion.wBottom) {

            *psFirstChar = pArea->psChars[0];
            return ET9STATUS_NONE;
        }
    }

    if (!*psFirstChar) {
        return ET9STATUS_ERROR;
    }

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                              /                                 
 *
 *                                                                       
 *                                               
 *                                                                              
 *                                                           
 *                                                             
 *                                                                                                               
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __FindSymbol_Generic(ET9KDBInfo        * const pKDBInfo,
                                                   const ET9SYMB             sSymbol,
                                                   ET9U8             * const pbyRegionalKey,
                                                   ET9U16            * const pwKeyIndex,
                                                   ET9Region         * const pKeyRegion,
                                                   const ET9BOOL             bInitialSymCheck)
{
    ET9KdbLayoutInfo * const pLayoutInfo = pKDBInfo->Private.pCurrLayoutInfo;

    ET9UINT nCount;
    ET9KdbAreaInfo const * pArea;
    ET9KdbAreaInfo const * pBestArea = NULL;

    pArea = pLayoutInfo->pKeyAreas;
    for (nCount = pLayoutInfo->nKeyAreaCount; nCount; --nCount, ++pArea) {

        ET9SYMB *psSymb;
        ET9UINT nCharCount;

        psSymb = pArea->psChars;
        for (nCharCount = (bInitialSymCheck ? 1 : pArea->nCharCount); nCharCount; --nCharCount, ++psSymb) {
            if (sSymbol == *psSymb) {
                pBestArea = pArea;
                break;
            }
        }

        /* initial? */

        if (nCharCount && nCharCount == pArea->nCharCount) {
            break;
        }

        /* potentially handle nShiftedCharCount/psShiftedChars here */
    }

    if (!pBestArea) {
        return ET9STATUS_ERROR;
    }

    *pwKeyIndex = pBestArea->wKeyIndex;
    *pKeyRegion = pBestArea->sRegion;
    *pbyRegionalKey = (pBestArea->eInputType == ET9REGIONALKEY) ? 1 : 0;

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                     
 *
 *                                                                       
 *
 *                 
 */

static ET9U32 ET9LOCALCALL __ComputeContentChecksum_Generic(ET9KDBInfo   * const pKDBInfo)
{
    ET9KdbLayoutInfo * const pLayoutInfo = pKDBInfo->Private.pCurrLayoutInfo;

    ET9U32 dwChecksum = 0;

    ET9UINT nCount;
    ET9KdbAreaInfo const * pArea;

    dwChecksum = pLayoutInfo->wLayoutWidth;
    dwChecksum = pLayoutInfo->wLayoutHeight + (65599 * dwChecksum);

    pArea = pLayoutInfo->pKeyAreas;
    for (nCount = pLayoutInfo->nKeyAreaCount; nCount; --nCount, ++pArea) {

        /* include function keys */

        dwChecksum = pArea->wKeyIndex + (65599 * dwChecksum);
        dwChecksum = pArea->eKeyType + (65599 * dwChecksum);
        dwChecksum = pArea->eInputType + (65599 * dwChecksum);
    }

    return dwChecksum;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                        
 *
 *                                                                                                           
 *                                                                                                                                                                                             
 *                                                                                                                                       
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __GetKdbVersion_Generic(ET9KDBInfo  * const pKDBInfo,
                                                      ET9SYMB     * const psKDBVerBuf,
                                                      ET9U16      * const pwBufSize)
{
    ET9STATUS               eStatus;

    static const ET9U8      byTemplateStr[] = "XT9 KDB Taa.bb Lcc.dd.ee Vff.gg Taa.bb Lcc.dd.ee Vff.gg";

    ET9U8           const * pbyVer;
    ET9SYMB                 *psTmp;
    ET9SYMB                 *psVerBuf = psKDBVerBuf;
    ET9U32                  dwOldKdbNum;
    ET9U16                  wOldPageNum;

    WLOG5(fprintf(pLogFile5, "__GetKdbVersion_Generic, pKDBInfo = %p\n", pKDBInfo);)

    dwOldKdbNum = pKDBInfo->dwKdbNum;
    wOldPageNum = pKDBInfo->Private.wPageNum;

    /* size does NOT INCLUDE NULL terminator */

    *pwBufSize = 31;

    /* Copy template string. */

    pbyVer = byTemplateStr;
    psTmp  = psKDBVerBuf;

    while (*pbyVer) {
        *psTmp++ = (ET9SYMB)*pbyVer++;
    }

    eStatus = __KDBLoadPage(pKDBInfo, pKDBInfo->dwFirstKdbNum, 0, NULL);

    if (eStatus) {
        return eStatus;
    }

    /* dynamic/static */

    {
        ET9KdbLayoutInfo * const pLayoutInfo = pKDBInfo->Private.pCurrLayoutInfo;

        /* KDB version */
        psVerBuf += 9;                              /* Skip "DB T"   */

        /* Database type */
        _ET9BinaryToHex(pLayoutInfo->bDatabaseType, psVerBuf);
        psVerBuf += 3;                              /* Skip "aa."    */

        /* Layout ver */
        _ET9BinaryToHex(pLayoutInfo->bLayoutVer, psVerBuf);
        psVerBuf += 4;                              /* Skip "bb L"   */

        /* Primary language ID */
        _ET9BinaryToHex(pLayoutInfo->bPrimaryID, psVerBuf);
        psVerBuf += 3;                              /* Skip "cc."    */

        /* Secondary language ID */
        _ET9BinaryToHex(pLayoutInfo->bSecondaryID, psVerBuf);
        psVerBuf += 3;                              /* Skip "dd."    */

        /* add symbol class as 'fixed' unicode value */
        _ET9BinaryToHex(pLayoutInfo->bSymbolClass, psVerBuf);
        psVerBuf += 4;                              /* Skip "ee V"    */

        /* Contents majorversion. */
        _ET9BinaryToHex(pLayoutInfo->bContentsMajor, psVerBuf);
        psVerBuf += 3;                              /* Skip "ff."    */

        /* Contents minor version. */
        _ET9BinaryToHex(pLayoutInfo->bContentsMinor, psVerBuf);
    }

    if (!_ET9KDBSecondKDBSupported(pKDBInfo)) {
        __KDBLoadPage(pKDBInfo, dwOldKdbNum, wOldPageNum, NULL);
        return ET9STATUS_NONE;
    }

    eStatus = __KDBLoadPage(pKDBInfo, pKDBInfo->dwSecondKdbNum, 0, NULL);

    if (eStatus) {
        return eStatus;
    }

    {
        ET9KdbLayoutInfo * const pLayoutInfo = pKDBInfo->Private.pCurrLayoutInfo;

        *pwBufSize = 55;

        /* KDB version */
        psVerBuf += 4;                              /* Skip "DB T"   */

        /* Database type */
        _ET9BinaryToHex(pLayoutInfo->bDatabaseType, psVerBuf);
        psVerBuf += 3;                              /* Skip "aa."    */

        /* Layout ver */
        _ET9BinaryToHex(pLayoutInfo->bLayoutVer, psVerBuf);
        psVerBuf += 4;                              /* Skip "bb L"   */

        /* Primary language ID */
        _ET9BinaryToHex(pLayoutInfo->bPrimaryID, psVerBuf);
        psVerBuf += 3;                              /* Skip "cc."    */

        /* Secondary language ID */
        _ET9BinaryToHex(pLayoutInfo->bSecondaryID, psVerBuf);
        psVerBuf += 3;                              /* Skip "dd."    */

        /* add symbol class as 'fixed' unicode value */
        _ET9BinaryToHex(pLayoutInfo->bSymbolClass, psVerBuf);
        psVerBuf += 4;                              /* Skip "ee V"    */

        /* Contents majorversion. */
        _ET9BinaryToHex(pLayoutInfo->bContentsMajor, psVerBuf);
        psVerBuf += 3;                              /* Skip "ff."    */

        /* Contents minor version. */
        _ET9BinaryToHex(pLayoutInfo->bContentsMinor, psVerBuf);
    }

    __KDBLoadPage(pKDBInfo, dwOldKdbNum, wOldPageNum, NULL);

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                         
 *
 *                                                   
 *                                                        
 *                                                             
 *
 *                                               
 */

ET9INLINE static ET9BOOL ET9LOCALCALL __IsBlendableArea_Generic(ET9KdbAreaInfo const * const pTopArea,
                                                                ET9KdbAreaInfo const * const pTestArea,
                                                                ET9BOOL              * const pbLimited)
{
    WLOG5(fprintf(pLogFile5, "__IsBlendableArea_Generic\n");)

    if (pTopArea->nCharCount > ET9MAXALTSYMBS ||
        pTopArea->nShiftedCharCount > ET9MAXALTSYMBS ||
        pTestArea->nCharCount > ET9MAXALTSYMBS ||
        pTestArea->nShiftedCharCount > ET9MAXALTSYMBS) {

        WLOG5(fprintf(pLogFile5, "  no - too many chars\n");)

       return 0;
    }

#if 1

    if ((pTopArea->eInputType == ET9DISCRETEKEY  || pTopArea->eKeyType == ET9KTSMARTPUNCT) &&
        (pTestArea->eInputType == ET9DISCRETEKEY || pTestArea->eKeyType == ET9KTSMARTPUNCT)) {

        WLOG5(fprintf(pLogFile5, "  no - discrete & discrete\n");)

        return 0;
    }

#else

    if (pTopArea->eKeyType == ET9KTSMARTPUNCT && pTestArea->eKeyType == ET9KTSMARTPUNCT) {

        WLOG5(fprintf(pLogFile5, "  no - smartpunct & smartpunct\n");)

        return 0;
    }

    if ((pTopArea->eInputType  == ET9DISCRETEKEY  && (pTopArea->eKeyType  == ET9KTLETTER || pTopArea->eKeyType  == ET9KTSMARTPUNCT)) ||
        (pTestArea->eInputType == ET9DISCRETEKEY  && (pTestArea->eKeyType == ET9KTLETTER || pTestArea->eKeyType == ET9KTSMARTPUNCT))) {

        WLOG5(fprintf(pLogFile5, "  no - discrete-LS & discrete-LS\n");)

        return 0;
    }

#endif

    switch (pTopArea->eKeyType)
    {
        case ET9KTLETTER:
        case ET9KTPUNCTUATION:
        case ET9KTNUMBER:
        case ET9KTSMARTPUNCT:
        case ET9KTFUNCTION:
            break;
        default:
            WLOG5(fprintf(pLogFile5, "  no - non mixable type (top)\n");)
            return 0;
    }

    switch (pTestArea->eKeyType)
    {
        case ET9KTLETTER:
        case ET9KTPUNCTUATION:
        case ET9KTNUMBER:
        case ET9KTSMARTPUNCT:
        case ET9KTFUNCTION:
            break;
        default:
            WLOG5(fprintf(pLogFile5, "  no - non mixable type (test)\n");)
            return 0;
    }

    *pbLimited = (pTopArea->eKeyType == pTestArea->eKeyType) ? 0 : 1;

    WLOG5(fprintf(pLogFile5, "  yes, limited %u\n", *pbLimited);)

    return 1;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                 
 *
 *                                                                                                               
 *                                                                      
 *                                                                                 
 *                                                               
 *                                                                                
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __FindRegionalMatchKeys_Generic(ET9KDBInfo              * const pKDBInfo,
                                                              ET9DirectedPos          * const pDirectedPos,
                                                              ET9MatchKey             * const pMatchKeys,
                                                              const ET9UINT                   nMaxKeys,
                                                              ET9UINT                 * const pnKeyCount)
{
#ifdef _ET9_KDB_REG_PROB_V2

    ET9KdbLayoutInfo * const pLayoutInfo = pKDBInfo->Private.pCurrLayoutInfo;

    const ET9FLOAT fRadiusSQ = pLayoutInfo->fCoreRadiusSQ;

    ET9UINT         nCount;
    ET9UINT         nKeyCount;
    ET9KdbAreaInfo  *pArea;

    ET9Region       sTargetRegion;

    /* calculate target area */

    {
        ET9UINT nTargetWidth = pLayoutInfo->nBoxWidth;
        ET9UINT nTargetHeight = pLayoutInfo->nBoxHeight;

        if (pDirectedPos->sL1.nX || pDirectedPos->sL1.nY || pDirectedPos->sL2.nX || pDirectedPos->sL2.nY) {

            ET9TracePoint_f sL1P1;
            ET9TracePoint_f sL1P2;
            ET9TracePoint_f sL2P1;
            ET9TracePoint_f sL2P2;

            sL1P1.fX = 0;
            sL1P1.fY = 0;
            sL1P2.fX = 1;
            sL1P2.fY = 0;

            sL2P1.fX = (ET9FLOAT)pDirectedPos->sL1.nX;
            sL2P1.fY = (ET9FLOAT)pDirectedPos->sL1.nY;
            sL2P2.fX = (ET9FLOAT)pDirectedPos->sL2.nX;
            sL2P2.fY = (ET9FLOAT)pDirectedPos->sL2.nY;

            {
                const ET9FLOAT fAngle = __LinesAngle(&sL1P1, &sL1P2, &sL2P1, &sL2P2);

                const ET9FLOAT fAngleAbs = __ET9Abs(fAngle);

                if (fAngleAbs <= 30.0f || fAngleAbs >= 150.0f) {
                    nTargetHeight = (ET9UINT)(nTargetHeight * 0.6);
                }
                else if (fAngleAbs >= 60.0f && fAngleAbs <= 120.0f) {
                    nTargetWidth = (ET9UINT)(nTargetWidth * 0.6);
                }
            }
        }

        __CreateRegion(&sTargetRegion,
                       pDirectedPos->sPos.nX,
                       pDirectedPos->sPos.nY,
                       nTargetWidth,
                       nTargetHeight);
    }

    /* find overlapping keys */

    nKeyCount = 0;
    pArea = pLayoutInfo->pKeyAreas;
    for (nCount = pLayoutInfo->nKeyAreaCount; nCount; --nCount, ++pArea) {

        const ET9U8 bCenter = (pDirectedPos->wExpectedKey == pArea->wKeyIndex) ? 2 :
                                (pDirectedPos->sPos.nX >= pArea->sRegion.wLeft &&
                                 pDirectedPos->sPos.nX <= pArea->sRegion.wRight &&
                                 pDirectedPos->sPos.nY >= pArea->sRegion.wTop &&
                                 pDirectedPos->sPos.nY <= pArea->sRegion.wBottom) ? 1 : 0;

        const ET9UINT nOverlap = __GetRegionOverlap(&sTargetRegion, &pArea->sRegion);

        if (bCenter || nOverlap > pLayoutInfo->nMinKeyOverlap) {

            const ET9FLOAT fDistSQ = __PointToKeyDistSQ(&pDirectedPos->sPos, pArea);

            ET9UINT nInsertIndex;

            WLOG5(fprintf(pLogFile5, "  adding key (%04x), fDist %5.1f, bCenter %c\n", pArea->psChars[0], _ET9sqrt_f(fDistSQ), (bCenter ? 'Y' : 'N'));)

            for (nInsertIndex = 0; nInsertIndex < nKeyCount; ++nInsertIndex) {
                if (bCenter < pMatchKeys[nInsertIndex].bCenter) {
                    continue;
                }
                if ((bCenter > pMatchKeys[nInsertIndex].bCenter) ||
                    (fDistSQ < pMatchKeys[nInsertIndex].fDistSQ && bCenter == pMatchKeys[nInsertIndex].bCenter)) {
                    break;
                }
            }

            if (nInsertIndex < nKeyCount) {

                ET9UINT nMoveIndex;

                for (nMoveIndex = (nKeyCount >= nMaxKeys ? (nMaxKeys - 1) : nKeyCount); nMoveIndex > nInsertIndex; --nMoveIndex) {
                    pMatchKeys[nMoveIndex] = pMatchKeys[nMoveIndex - 1];
                }
            }

            if (nInsertIndex >= nMaxKeys) {
                WLOG5(fprintf(pLogFile5, "  too many keys, skipping (%04x)\n", pArea->psChars[0]);)
                continue;
            }

            pMatchKeys[nInsertIndex].pArea = pArea;
            pMatchKeys[nInsertIndex].bCenter = bCenter;
            pMatchKeys[nInsertIndex].bLimited = 0;
            pMatchKeys[nInsertIndex].fDistSQ = fDistSQ;
            pMatchKeys[nInsertIndex].bFunctionKey = (pArea->eKeyType == ET9KTFUNCTION) ? 1 : 0;

            if (nKeyCount < nMaxKeys) {
                ++nKeyCount;
            }
        }
    }

    /* truncate regions from discrete top's and filter mixed types */

    if (nKeyCount > 1) {

        ET9KdbAreaInfo const * const pTopArea = pMatchKeys[0].pArea;

        const ET9BOOL bUsingTrace = pDirectedPos->bBlendFunctionKey;

        const ET9BOOL bForceDiscrete = (ET9BOOL)(!bUsingTrace && (ET9_KDB_DISCRETE_MODE(pKDBInfo->dwStateBits) ||
                                                                  ET9_KDB_MULTITAP_MODE(pKDBInfo->dwStateBits)));

        ET9Assert(pTopArea);

        if (bForceDiscrete) {
            nKeyCount = 1;
        }

        {
            ET9UINT nIndex;

            for (nIndex = 1; nIndex < nKeyCount; ++nIndex) {

                ET9MatchKey * const pThisMatchKey = &pMatchKeys[nIndex];

                ET9KdbAreaInfo const * const pThisArea = pThisMatchKey->pArea;

                ET9Assert(pThisArea);

                if (!__IsBlendableArea_Generic(pTopArea, pThisArea, &pThisMatchKey->bLimited)) {

                    pMatchKeys[nIndex] = pMatchKeys[nKeyCount - 1];
                    --nKeyCount;
                    --nIndex;
                }
            }
        }
    }

    /* assign actual freqs */

    {
        ET9UINT nIndex;

        for (nIndex = 0; nIndex < nKeyCount; ++nIndex) {

#if 1
            const ET9FLOAT fFreq = (ET9FLOAT)(((fRadiusSQ - pMatchKeys[nIndex].fDistSQ) / fRadiusSQ) * 255.0 + 0.5);
#elif 0
            const ET9FLOAT fFreq = (ET9FLOAT)((((_ET9sqrt_f(fRadiusSQ) - _ET9sqrt_f(pMatchKeys[nIndex].fDistSQ)) * (_ET9sqrt_f(fRadiusSQ) - _ET9sqrt_f(pMatchKeys[nIndex].fDistSQ))) / fRadiusSQ) * 255.0 + 0.5);
#else
            const ET9FLOAT fDist = _ET9sqrt_f(pMatchKeys[nIndex].fDistSQ);
            const ET9FLOAT fRadius = _ET9sqrt_f(fRadiusSQ);
            const ET9FLOAT fFreq = (ET9FLOAT)((((fRadius - fDist) * (fRadius - fDist) * (fRadius - fDist)) / (fRadius * fRadius * fRadius)) * 255.0 + 0.5);
#endif

            ET9U8 bFreq;

            if (fFreq <= 1) {
                bFreq = 1;
            }
            else if (fFreq >= 255) {
                bFreq = 255;
            }
            else {
                bFreq = (ET9U8)fFreq;
            }

            pMatchKeys[nIndex].bFreq = bFreq;

            WLOG5(fprintf(pLogFile5, "  [%2u] bFreq %3u, fFreq %8f, fDist %5.1\n", nIndex, pMatchKeys[nIndex].bFreq, fFreq, _ET9sqrt_f(pMatchKeys[nIndex].fDistSQ));)
        }
    }

    /* assure order */

    {
        ET9BOOL bDirty;
        ET9UINT nIndex;

        for (bDirty = 1; bDirty; ) {

            bDirty = 0;

            for (nIndex = 0; nIndex + 1 < nKeyCount; ++nIndex) {

                if ((pMatchKeys[nIndex].bCenter < pMatchKeys[nIndex + 1].bCenter) ||
                    (pMatchKeys[nIndex].bCenter == pMatchKeys[nIndex + 1].bCenter &&
                     pMatchKeys[nIndex].bFunctionKey && !pMatchKeys[nIndex + 1].bFunctionKey) ||
                    (pMatchKeys[nIndex].bCenter == pMatchKeys[nIndex + 1].bCenter &&
                     pMatchKeys[nIndex].bFunctionKey == pMatchKeys[nIndex + 1].bFunctionKey &&
                     pMatchKeys[nIndex].bFreq < pMatchKeys[nIndex + 1].bFreq)) {

                    const ET9MatchKey sTmp = pMatchKeys[nIndex];

                    pMatchKeys[nIndex] = pMatchKeys[nIndex + 1];
                    pMatchKeys[nIndex + 1] = sTmp;

                    bDirty = 1;
                }
            }
        }
    }

#ifdef ET9_DEBUG

    /* validate */

    {
        ET9UINT nIndex;

        for (nIndex = 0; nIndex + 1 < nKeyCount; ++nIndex) {
            ET9Assert(pMatchKeys[nIndex].bFreq >= pMatchKeys[nIndex + 1].bFreq ||
                      !pMatchKeys[nIndex].bFunctionKey ||
                      pMatchKeys[nIndex + 1].bFunctionKey ||
                      pMatchKeys[nIndex].bCenter > pMatchKeys[nIndex + 1].bCenter);
        }
    }

#endif

    /* done */

    *pnKeyCount = nKeyCount;

    return ET9STATUS_NONE;

#else

    ET9KdbLayoutInfo * const pLayoutInfo = pKDBInfo->Private.pCurrLayoutInfo;

    ET9UINT         nCount;
    ET9UINT         nKeyCount;
    ET9UINT         nTargetArea;
    ET9KdbAreaInfo  *pArea;

    ET9Region       sTargetRegion;

    WLOG5(fprintf(pLogFile5, "__FindRegionalMatchKeys_Generic\n");)

    {
        ET9UINT nTargetWidth = pLayoutInfo->nBoxWidth;
        ET9UINT nTargetHeight = pLayoutInfo->nBoxHeight;

        if (pDirectedPos->sL1.nX || pDirectedPos->sL1.nY || pDirectedPos->sL2.nX || pDirectedPos->sL2.nY) {

            ET9TracePoint_f sL1P1;
            ET9TracePoint_f sL1P2;
            ET9TracePoint_f sL2P1;
            ET9TracePoint_f sL2P2;

            sL1P1.fX = 0;
            sL1P1.fY = 0;
            sL1P2.fX = 1;
            sL1P2.fY = 0;

            sL2P1.fX = (ET9FLOAT)pDirectedPos->sL1.nX;
            sL2P1.fY = (ET9FLOAT)pDirectedPos->sL1.nY;
            sL2P2.fX = (ET9FLOAT)pDirectedPos->sL2.nX;
            sL2P2.fY = (ET9FLOAT)pDirectedPos->sL2.nY;

            {
                const ET9FLOAT fAngle = __LinesAngle(&sL1P1, &sL1P2, &sL2P1, &sL2P2);

                const ET9FLOAT fAngleAbs = __ET9Abs(fAngle);

                if (fAngleAbs <= 30.0f || fAngleAbs >= 150.0f) {
                    nTargetHeight = (ET9UINT)(nTargetHeight * 0.6);
                }
                else if (fAngleAbs >= 60.0f && fAngleAbs <= 120.0f) {
                    nTargetWidth = (ET9UINT)(nTargetWidth * 0.6);
                }
            }
        }

        __CreateRegion(&sTargetRegion,
                       pDirectedPos->sPos.nX,
                       pDirectedPos->sPos.nY,
                       nTargetWidth,
                       nTargetHeight);

        nTargetArea = nTargetWidth * nTargetHeight;
    }

    /* find overlapping keys */

    nKeyCount = 0;
    pArea = pLayoutInfo->pKeyAreas;
    for (nCount = pLayoutInfo->nKeyAreaCount; nCount; --nCount, ++pArea) {

        const ET9UINT nOverlap = __GetRegionOverlap(&sTargetRegion, &pArea->sRegion);

        const ET9U8 bCenter = (pDirectedPos->wExpectedKey == pArea->wKeyIndex) ? 2 :
                                (pDirectedPos->sPos.nX >= pArea->sRegion.wLeft &&
                                 pDirectedPos->sPos.nX <= pArea->sRegion.wRight &&
                                 pDirectedPos->sPos.nY >= pArea->sRegion.wTop &&
                                 pDirectedPos->sPos.nY <= pArea->sRegion.wBottom) ? 1 : 0;

        ET9Assert(pArea->wKeyIndex != ET9_KDB_KEY_UNDEFINED);

        if (nOverlap > pLayoutInfo->nMinKeyOverlap) {

            ET9UINT nInsertIndex;

            WLOG5(fprintf(pLogFile5, "  adding key (%04x), nOverlap %5u, bCenter %u\n", pArea->psChars[0], nOverlap, bCenter);)

            for (nInsertIndex = 0; nInsertIndex < nKeyCount; ++nInsertIndex) {
                if (bCenter < pMatchKeys[nInsertIndex].bCenter) {
                    continue;
                }
                if ((bCenter > pMatchKeys[nInsertIndex].bCenter) ||
                    (nOverlap > pMatchKeys[nInsertIndex].nOverlap && bCenter == pMatchKeys[nInsertIndex].bCenter)) {
                    break;
                }
            }

            if (nInsertIndex < nKeyCount) {

                ET9UINT nMoveIndex;

                for (nMoveIndex = (nKeyCount >= nMaxKeys ? (nMaxKeys - 1) : nKeyCount); nMoveIndex > nInsertIndex; --nMoveIndex) {
                    pMatchKeys[nMoveIndex] = pMatchKeys[nMoveIndex - 1];
                }
            }

            if (nInsertIndex >= nMaxKeys) {
                WLOG5(fprintf(pLogFile5, "  too many keys, skipping (%04x)\n", pArea->psChars[0]);)
                continue;
            }

            pMatchKeys[nInsertIndex].pArea = pArea;
            pMatchKeys[nInsertIndex].bCenter = bCenter;
            pMatchKeys[nInsertIndex].bLimited = 0;
            pMatchKeys[nInsertIndex].nOverlap = nOverlap;
            pMatchKeys[nInsertIndex].bFunctionKey = (pArea->eKeyType == ET9KTFUNCTION) ? 1 : 0;

            if (nKeyCount < nMaxKeys) {
                ++nKeyCount;
            }
        }
    }

    /* truncate regions from discrete top's and filter mixed types */

    if (nKeyCount > 1) {

        ET9KdbAreaInfo const * const pTopArea = pMatchKeys[0].pArea;

        const ET9BOOL bUsingTrace = pDirectedPos->bBlendFunctionKey;

        const ET9BOOL bForceDiscrete = (ET9BOOL)(!bUsingTrace && (ET9_KDB_DISCRETE_MODE(pKDBInfo->dwStateBits) ||
                                                                  ET9_KDB_MULTITAP_MODE(pKDBInfo->dwStateBits)));

        ET9Assert(pTopArea);

        if (bForceDiscrete) {
            nKeyCount = 1;
        }

        {
            ET9UINT nIndex;

            for (nIndex = 1; nIndex < nKeyCount; ++nIndex) {

                ET9MatchKey * const pThisMatchKey = &pMatchKeys[nIndex];

                ET9KdbAreaInfo const * const pThisArea = pThisMatchKey->pArea;

                ET9Assert(pThisArea);

                if (!__IsBlendableArea_Generic(pTopArea, pThisArea, &pThisMatchKey->bLimited)) {

                    pMatchKeys[nIndex] = pMatchKeys[nKeyCount - 1];
                    --nKeyCount;
                    --nIndex;
                }
            }
        }
    }

    /* assign actual freqs */

    {
        ET9UINT nIndex;
        ET9UINT nTotOverlap;
        ET9FLOAT fTotFreq;

        fTotFreq = 0;
        nTotOverlap = 0;
        for (nIndex = 0; nIndex < nKeyCount; ++nIndex) {

            const ET9UINT nOverlap = pMatchKeys[nIndex].nOverlap;

            ET9FLOAT fFreq = (ET9FLOAT)nOverlap;

            WLOG5(fprintf(pLogFile5, "  -- [%2u] fFreq %8f\n", nIndex, fFreq);)

            if (fFreq <= 0.0) {
                WLOG5(fprintf(pLogFile5, "  -- zero freq made non zero (small)\n");)
                fFreq = (ET9FLOAT)0.00001;
            }

            fTotFreq += fFreq;
            nTotOverlap += nOverlap;

            pMatchKeys[nIndex].fFreq = fFreq;
        }

        /* adjust for the unused area - improves e.g. edge keys (less neighbors because of edge cases) */

        if (nTotOverlap < nTargetArea) {

            const ET9UINT nDiff = nTargetArea - nTotOverlap;

            WLOG5(fprintf(pLogFile5, "  -- adjusting for unused, nDiff %u\n", nDiff);)

            fTotFreq += (ET9FLOAT)nDiff;
        }

        if (fTotFreq == 0) {
            WLOG5(fprintf(pLogFile5, "  -- zero tot freq made non zero\n");)
            fTotFreq = 1;
        }

        for (nIndex = 0; nIndex < nKeyCount; ++nIndex) {

            const ET9FLOAT fFreq = (ET9FLOAT)(pMatchKeys[nIndex].fFreq / fTotFreq * 255.0 + 0.5);

            ET9U8 bFreq;

            if (fFreq <= 1) {
                bFreq = 1;
            }
            else if (fFreq >= 255) {
                bFreq = 255;
            }
            else {
                bFreq = (ET9U8)fFreq;
            }

            pMatchKeys[nIndex].bFreq = bFreq;

            WLOG5(fprintf(pLogFile5, "  [%2u] bFreq %3u, fFreq %8f, nOverlap %6u\n", nIndex, pMatchKeys[nIndex].bFreq, pMatchKeys[nIndex].fFreq, pMatchKeys[nIndex].nOverlap);)
        }
    }

    /* assure order */

    {
        ET9BOOL bDirty;
        ET9UINT nIndex;

        for (bDirty = 1; bDirty; ) {

            bDirty = 0;

            for (nIndex = 0; nIndex + 1 < nKeyCount; ++nIndex) {

                if ((pMatchKeys[nIndex].bCenter < pMatchKeys[nIndex + 1].bCenter) ||
                    (pMatchKeys[nIndex].bCenter == pMatchKeys[nIndex + 1].bCenter &&
                     pMatchKeys[nIndex].bFunctionKey && !pMatchKeys[nIndex + 1].bFunctionKey) ||
                    (pMatchKeys[nIndex].bCenter == pMatchKeys[nIndex + 1].bCenter &&
                     pMatchKeys[nIndex].bFunctionKey == pMatchKeys[nIndex + 1].bFunctionKey &&
                     pMatchKeys[nIndex].bFreq < pMatchKeys[nIndex + 1].bFreq)) {

                    const ET9MatchKey sTmp = pMatchKeys[nIndex];

                    pMatchKeys[nIndex] = pMatchKeys[nIndex + 1];
                    pMatchKeys[nIndex + 1] = sTmp;

                    bDirty = 1;
                }
            }
        }
    }

#ifdef ET9_DEBUG

    /* validate */

    {
        ET9UINT nIndex;

        for (nIndex = 0; nIndex + 1 < nKeyCount; ++nIndex) {
            ET9Assert(pMatchKeys[nIndex].bFreq >= pMatchKeys[nIndex + 1].bFreq ||
                      !pMatchKeys[nIndex].bFunctionKey ||
                      pMatchKeys[nIndex + 1].bFunctionKey ||
                      pMatchKeys[nIndex].bCenter > pMatchKeys[nIndex + 1].bCenter);
        }
    }

#endif

    /* done */

    *pnKeyCount = nKeyCount;

    return ET9STATUS_NONE;

#endif /* _ET9_KDB_REG_PROB_V2 */
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                 
 *
 *                                                                       
 *                                   
 *                                   
 *                                   
 *                                   
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __LocateKdbKey_Generic(ET9KDBInfo              * const pKDBInfo,
                                                     ET9KdbAreaInfo    const * const pKeyArea,
                                                     ET9U8                   * const pbType,
                                                     ET9UINT                 * const pnCharCount,
                                                     ET9SYMB          const ** const ppsChars)
{
    if (!pKeyArea) {
        return ET9STATUS_ERROR;
    }

    *pbType = (ET9U8)pKeyArea->eKeyType;

    if (pKDBInfo->Private.eShiftState && pKeyArea->nShiftedCharCount) {
        *pnCharCount = pKeyArea->nShiftedCharCount;
        *ppsChars = pKeyArea->psShiftedChars;
    }
    else {
        *pnCharCount = pKeyArea->nCharCount;
        *ppsChars = pKeyArea->psChars;
    }

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                                                                       
 *                                      
 *                                                         
 *                                                                         
 *                                                              
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __LoadKeyCharsType_Generic(ET9KDBInfo              * const pKDBInfo,
                                                         ET9KdbAreaInfo    const * const pKeyArea,
                                                         ET9SymbInfo             * const pSymbInfo,
                                                         ET9U8                   * const pbSymbsUsed,
                                                         const ET9U32                    dwLdbId)
{
    ET9STATUS eStatus;
    ET9UINT   nIndex;
    ET9UINT   nNumCharsToLoad;
    ET9U8     bThisKeyType;
    ET9SYMB   *psSym;
    ET9SYMB   *psUpperSym;
    ET9SYMB   const *psChar;
    ET9SYMB   const *psChars;
    ET9DataPerBaseSym *pDataPerBaseSym;

    WLOG5(fprintf(pLogFile5, "__LoadKeyCharsType_Generic, pKDBInfo = %p\n", /* pKDBInfo */ 0);)

    ET9Assert(pKDBInfo);
    ET9Assert(pbSymbsUsed);

    eStatus = __LocateKdbKey_Generic(pKDBInfo, pKeyArea, &bThisKeyType, &nNumCharsToLoad, &psChars);

    if (eStatus) {
        ET9Assert(0);
        return eStatus;
    }

    pDataPerBaseSym = &pSymbInfo->DataPerBaseSym[pSymbInfo->bNumBaseSyms];

    *pbSymbsUsed = 1;
    pDataPerBaseSym->bNumSymsToMatch = 0;

    /* load chars */

    psChar = psChars;
    psSym = pDataPerBaseSym->sChar;
    psUpperSym = pDataPerBaseSym->sUpperCaseChar;

    for (nIndex = 0; nIndex < nNumCharsToLoad; ++nIndex, ++psChar) {

        if (pDataPerBaseSym->bNumSymsToMatch >= ET9MAXALTSYMBS) {

            if (pSymbInfo->eInputType == ET9DISCRETEKEY || pSymbInfo->bSymbType == ET9KTSMARTPUNCT) {

                if (pSymbInfo->bNumBaseSyms >= ET9MAXBASESYMBS) {
                    return ET9STATUS_OUT_OF_RANGE_MAXBASESYMBS;
                }

                ++pDataPerBaseSym;
                ++*pbSymbsUsed;

                pDataPerBaseSym->bSymFreq = 0;
                pDataPerBaseSym->bNumSymsToMatch = 0;
                pDataPerBaseSym->bDefaultCharIndex = 0;

                psSym = pDataPerBaseSym->sChar;
                psUpperSym = pDataPerBaseSym->sUpperCaseChar;
            }
            else {
                return ET9STATUS_OUT_OF_RANGE_MAXALTSYMBS;
            }
        }

        WLOG5B(fprintf(pLogFile5, "  Loading = %04x\n", *psChar);)

        if (pKDBInfo->Private.pFilterSymb && pKDBInfo->Private.pFilterSymb(pKDBInfo->Private.pFilterSymbInfo, *psChar)) {
            WLOG5B(fprintf(pLogFile5, "    Suppressed\n");)
            continue;
        }

        WLOG5B(fprintf(pLogFile5, "    Loaded\n");)

        ++pDataPerBaseSym->bNumSymsToMatch;

        *psSym++ = *psChar;
        *psUpperSym++ = _ET9SymToOther(*psChar, dwLdbId);

        WLOG5B(fprintf(pLogFile5, "    Upper = %04x\n", *(psUpperSym - 1));)
    }

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                                                                                                               
 *                                   
 *                                   
 *                                   
 *                                   
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __ProcessAmbigTap_Regional_Generic(ET9KDBInfo              * const pKDBInfo,
                                                                 ET9SymbInfo             * const pSymbInfo,
                                                                 ET9DirectedPos          * const pDirectedPos,
                                                                 const ET9LOADSYMBACTION         eLoadAction,
                                                                 const ET9U32                    dwLdbId)
{
    ET9STATUS       eStatus;
    ET9UINT         nIndex;
    ET9UINT         nKeyCount;

    ET9MatchKey     pMatchKeys[ET9_KDB_MAX_REGIONS];

    WLOG5(fprintf(pLogFile5, "__ProcessAmbigTap_Regional_Generic, pKDBInfo = %p, eLoadAction = %d\n", pKDBInfo, (int)eLoadAction);)

    ET9Assert(pKDBInfo);
    ET9Assert(pSymbInfo);

    eStatus = __FindRegionalMatchKeys_Generic(pKDBInfo, pDirectedPos, pMatchKeys, ET9_KDB_MAX_REGIONS, &nKeyCount);

    if (eStatus) {
        return eStatus;
    }

    if (!nKeyCount) {
        return ET9STATUS_NO_KEY;
    }

    {
        ET9KdbAreaInfo const * pArea = pMatchKeys[0].pArea;

        ET9Assert(pArea);

        pSymbInfo->bSymbType = (ET9U8)pArea->eKeyType;
        pSymbInfo->eInputType = pArea->eInputType;

        if (pSymbInfo->eInputType == ET9REGIONALKEY && nKeyCount == 1 && (ET9_KDB_DISCRETE_MODE(pKDBInfo->dwStateBits) || ET9_KDB_MULTITAP_MODE(pKDBInfo->dwStateBits))) {
            pSymbInfo->eInputType = ET9DISCRETEKEY;
        }
    }

#if 0
#ifdef ET9_DEBUG
    {
        ET9UINT nFreqTot = 0;

        for (nIndex = 0; nIndex < nKeyCount; ++nIndex) {
            nFreqTot += pMatchKeys[nIndex].bFreq;
        }

        ET9Assert(nFreqTot > 200);
    }
#endif
#endif

    if (eLoadAction != LOADSYMBACTION_APPEND){
        pSymbInfo->bNumBaseSyms = 0;
        pSymbInfo->pKdbKey = pMatchKeys[0].pArea;
    }

    for (nIndex = 0; nIndex < nKeyCount; ++nIndex) {

        ET9U8 bSymbsUsed;

        if (pSymbInfo->bNumBaseSyms >= ET9MAXBASESYMBS) {
            break;
        }

        eStatus = __LoadKeyCharsType_Generic(pKDBInfo,
                                             pMatchKeys[nIndex].pArea,
                                             pSymbInfo,
                                             &bSymbsUsed,
                                             dwLdbId);

        if (eStatus) {
            return eStatus;
        }

        {
            ET9U8 bSymbUsedCount;
            ET9DataPerBaseSym *pDPBSym;

            pDPBSym = &pSymbInfo->DataPerBaseSym[pSymbInfo->bNumBaseSyms];
            for (bSymbUsedCount = bSymbsUsed; bSymbUsedCount; --bSymbUsedCount, ++pDPBSym) {

                pDPBSym->bSymFreq = pMatchKeys[nIndex].bFreq;
                pDPBSym->bLimited = pMatchKeys[nIndex].bLimited;

                ET9Assert(pDPBSym->bSymFreq);
                ET9Assert(pDPBSym->bNumSymsToMatch || eLoadAction == LOADSYMBACTION_RELOAD);
            }
        }

        pSymbInfo->bNumBaseSyms = (ET9U8)(pSymbInfo->bNumBaseSyms + bSymbsUsed);
    }

    ET9Assert(pSymbInfo->bNumBaseSyms);

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                                                                                                               
 *                                   
 *                                   
 *                                                                         
 *                                   
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __ProcessTap_Regional_Generic(ET9KDBInfo             * const pKDBInfo,
                                                            ET9DirectedPos         * const pDirectedPos,
                                                            const ET9U8                    bCurrIndexInList,
                                                            ET9SYMB                * const psFunctionKey,
                                                            const ET9LOADSYMBACTION        eLoadAction)
{
    ET9WordSymbInfo * const pWordSymbInfo = pKDBInfo->Private.pWordSymbInfo;

    ET9STATUS    eStatus;
    ET9U8        bNumSymbs;

    WLOG5(fprintf(pLogFile5, "__ProcessTap_Regional_Generic, pKDBInfo = %p, eLoadAction = %d\n", pKDBInfo, (int)eLoadAction);)

    ET9Assert(pKDBInfo);

#ifdef _ET9_TAP_341_218
    pDirectedPos->sPos.nX = 341;
    pDirectedPos->sPos.nY = 218;
#endif

    if (!pDirectedPos->bBlendFunctionKey) {

        const ET9KdbAreaInfo *pArea = __GetKeyAreaFromTap_Generic(pKDBInfo, (ET9U16)pDirectedPos->sPos.nX, (ET9U16)pDirectedPos->sPos.nY);

        if (pArea && pArea->eKeyType == ET9KTFUNCTION) {
            *psFunctionKey = pArea->psChars[0];
            return ET9STATUS_NONE;
        }

        {
            const ET9U8 bAddingSymb = ((!pArea || pArea->eKeyType != ET9KTFUNCTION) && !pDirectedPos->sL1.nX && !pDirectedPos->sL1.nY && !pDirectedPos->sL2.nX && !pDirectedPos->sL2.nY) ? 1 : 0;

            _ET9ImminentSymb(pWordSymbInfo, bCurrIndexInList, (ET9BOOL)(pArea && (pArea->eKeyType == ET9KTSMARTPUNCT || pArea->eKeyType == ET9KTPUNCTUATION)), bAddingSymb);
        }
    }

    __MirrorWsiState(pKDBInfo);

    if (eLoadAction != LOADSYMBACTION_NEW) {

        ET9Assert(pWordSymbInfo->bNumSymbs);

        if (!pWordSymbInfo->bNumSymbs) {
            return ET9STATUS_ERROR;
        }

        --pWordSymbInfo->bNumSymbs;
    }

    bNumSymbs = (ET9U8)(pWordSymbInfo->bNumSymbs + 1);

    if (bNumSymbs > ET9MAXWORDSIZE) {
        return ET9STATUS_FULL;
    }

    if (eLoadAction != LOADSYMBACTION_APPEND) {
        _ET9ClearMem((ET9U8*)&pWordSymbInfo->SymbsInfo[bNumSymbs - 1], sizeof(ET9SymbInfo));
    }

    eStatus = __ProcessAmbigTap_Regional_Generic(pKDBInfo, &pWordSymbInfo->SymbsInfo[bNumSymbs - 1], pDirectedPos, eLoadAction, pWordSymbInfo->Private.dwLocale);

    if (eStatus) {
        return eStatus;
    }

    __ProcessWordSymInfo(pKDBInfo);

    pWordSymbInfo->SymbsInfo[bNumSymbs - 1].wKeyIndex = ET9UNDEFINEDKEYVALUE;
    pWordSymbInfo->SymbsInfo[bNumSymbs - 1].wTapX = (ET9U16)pDirectedPos->sPos.nX;
    pWordSymbInfo->SymbsInfo[bNumSymbs - 1].wTapY = (ET9U16)pDirectedPos->sPos.nY;

    return eStatus;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                                                                                                               
 *                                   
 *                                                                         
 *                                   
 *                                   
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __ProcessAmbigTap_CurrentKdb_Generic(ET9KDBInfo           * const pKDBInfo,
                                                                   ET9DirectedPos       * const pDirectedPos,
                                                                   ET9SYMB              * const psFunctionKey,
                                                                   const ET9U8                  bCurrIndexInList,
                                                                   const ET9LOADSYMBACTION      eLoadAction)
{
    ET9WordSymbInfo * const pWordSymbInfo = pKDBInfo->Private.pWordSymbInfo;

    ET9STATUS eStatus;

    WLOG5(fprintf(pLogFile5, "__ProcessAmbigTap_CurrentKdb_Generic, pKDBInfo = %p, eLoadAction = %d, wX = %d, wY = %d\n", pKDBInfo, (int)eLoadAction, pDirectedPos->sPos.nX, pDirectedPos->sPos.nY);)

    if (pKDBInfo->Private.sKdbAction.bShiftState) {
        pWordSymbInfo->dwStateBits |= ET9STATE_SHIFT_MASK;
    }

    eStatus = __ProcessTap_Regional_Generic(pKDBInfo, pDirectedPos, bCurrIndexInList, psFunctionKey, eLoadAction);

    return eStatus;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                              
 *
 *                                                                                                               
 *                                            
 *                                                                      
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __FindKeyTapPos_Generic(ET9KDBInfo              * const pKDBInfo,
                                                      const ET9U16                    wKeyIndex,
                                                      ET9DirectedPos          * const pDirectedPos)
{
    ET9KdbLayoutInfo * const pLayoutInfo = pKDBInfo->Private.pCurrLayoutInfo;

    ET9UINT             nCount;
    ET9KdbAreaInfo      *pArea;

    WLOG5(fprintf(pLogFile5, "__FindKeyTapPos_Generic\n");)

    __InitDirectedPos(pDirectedPos);

    pArea = pLayoutInfo->pKeyAreas;
    for (nCount = pLayoutInfo->nKeyAreaCount; nCount; --nCount, ++pArea) {

        /* include function keys */

        if (wKeyIndex == pArea->wKeyIndex) {

            pDirectedPos->sPos.nX = pArea->nCenterX;
            pDirectedPos->sPos.nY = pArea->nCenterY;

            return ET9STATUS_NONE;
        }
    }

    return ET9STATUS_OUT_OF_RANGE;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                                                                                                               
 *                                            
 *                                   
 *                                                                         
 *                                   
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __ProcessKey_Regional_Generic(ET9KDBInfo             * const pKDBInfo,
                                                            const ET9U16                   wKeyIndex,
                                                            const ET9U8                    bCurrIndexInList,
                                                            ET9SYMB                * const psFunctionKey,
                                                            const ET9LOADSYMBACTION        eLoadAction)
{
    ET9WordSymbInfo * const pWordSymbInfo = pKDBInfo->Private.pWordSymbInfo;

    ET9STATUS       eStatus;
    ET9U8           bNumSymbs;
    ET9DirectedPos  sDirectedPos;

    WLOG5(fprintf(pLogFile5, "__ProcessKey_Regional_Generic, pKDBInfo = %p, eLoadAction = %d\n", pKDBInfo, (int)eLoadAction);)

    ET9Assert(pKDBInfo);

    {
        const ET9KdbAreaInfo *pArea = __GetKeyAreaFromKey_Generic(pKDBInfo, wKeyIndex);

        if (pArea && pArea->eKeyType == ET9KTFUNCTION) {
            *psFunctionKey = pArea->psChars[0];
            return ET9STATUS_NONE;
        }

        _ET9ImminentSymb(pWordSymbInfo, bCurrIndexInList, (ET9BOOL)(pArea && (pArea->eKeyType == ET9KTSMARTPUNCT || pArea->eKeyType == ET9KTPUNCTUATION)), 1);
    }

    eStatus = __FindKeyTapPos_Generic(pKDBInfo, wKeyIndex, &sDirectedPos);

    if (eStatus) {
        return eStatus;
    }

    __MirrorWsiState(pKDBInfo);

    if (eLoadAction != LOADSYMBACTION_NEW) {

        ET9Assert(pWordSymbInfo->bNumSymbs);

        if (!pWordSymbInfo->bNumSymbs) {
            return ET9STATUS_ERROR;
        }

        --pWordSymbInfo->bNumSymbs;
    }

    bNumSymbs = (ET9U8)(pWordSymbInfo->bNumSymbs + 1);

    if (bNumSymbs > ET9MAXWORDSIZE) {
        return ET9STATUS_FULL;
    }

    if (eLoadAction != LOADSYMBACTION_APPEND) {
        _ET9ClearMem((ET9U8*)&pWordSymbInfo->SymbsInfo[bNumSymbs - 1], sizeof(ET9SymbInfo));
    }

    eStatus = __ProcessAmbigTap_Regional_Generic(pKDBInfo, &pWordSymbInfo->SymbsInfo[bNumSymbs - 1], &sDirectedPos, eLoadAction, pWordSymbInfo->Private.dwLocale);

    if (eStatus) {
        return eStatus;
    }

    __ProcessWordSymInfo(pKDBInfo);

    pWordSymbInfo->SymbsInfo[bNumSymbs - 1].wKeyIndex = wKeyIndex;
    pWordSymbInfo->SymbsInfo[bNumSymbs - 1].wTapX = ET9UNDEFINEDTAPVALUE;
    pWordSymbInfo->SymbsInfo[bNumSymbs - 1].wTapY = ET9UNDEFINEDTAPVALUE;

    return eStatus;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                          
 *
 *                                                                       
 *                                       
 *                                                                    
 *                                                                  
 *                                                   
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __ProcessAmbigKey_CurrentKdb_Generic(ET9KDBInfo           * const pKDBInfo,
                                                                   const ET9U16                 wKeyIndex,
                                                                   ET9SYMB              * const psFunctionKey,
                                                                   const ET9U8                  bCurrIndexInList,
                                                                   const ET9LOADSYMBACTION      eLoadAction)
{
    ET9WordSymbInfo * const pWordSymbInfo = pKDBInfo->Private.pWordSymbInfo;

    ET9STATUS eStatus;

    WLOG5(fprintf(pLogFile5, "__ProcessAmbigKey_CurrentKdb_Generic, pKDBInfo = %p, eLoadAction = %d, wKeyIndex = %d\n", pKDBInfo, (int)eLoadAction, wKeyIndex);)

    if (pKDBInfo->Private.sKdbAction.bShiftState) {
        pWordSymbInfo->dwStateBits |= ET9STATE_SHIFT_MASK;
    }

    eStatus = __ProcessKey_Regional_Generic(pKDBInfo, wKeyIndex, bCurrIndexInList, psFunctionKey, eLoadAction);

    return eStatus;
}

/* ************************************************************************************************************** */
/* * LOCAL ****************************************************************************************************** */
/* ************************************************************************************************************** */

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                     
 *
 *                                                                       
 *
 *                 
 */

static ET9U32 ET9LOCALCALL __ComputeContentChecksum(ET9KDBInfo   * const pKDBInfo)
{
    ET9U32 dwChecksum;

    dwChecksum = __ComputeContentChecksum_Generic(pKDBInfo);

    return dwChecksum;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                      
 *                                     
 *
 *                                                               
 *                                     
 *                                         
 *                                           
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __KDBLoadPage(ET9KDBInfo      * const pKDBInfo,
                                            const ET9U32            dwKdbNum,
                                            const ET9U16            wPageNum,
                                            ET9U16          * const pwTotalKeys)
{
    ET9STATUS eStatus;

    ET9Assert(pKDBInfo);

    WLOG5(fprintf(pLogFile5, "__KDBLoadPage, loading, pKDBInfo = %p, wKdbNum = %04x (%04X), wPageNum = %04x (%04X)\n", pKDBInfo, dwKdbNum, pKDBInfo->dwKdbNum, wPageNum, pKDBInfo->Private.wPageNum);)

    /* clear init status marker */

    pKDBInfo->Private.wKDBInitOK = 0;

    /* load */

    eStatus = __KDBLoadPage_Dynamic(pKDBInfo, dwKdbNum, wPageNum, pwTotalKeys);

    if (eStatus) {
        return eStatus;
    }

    /* KDB ok now */

    pKDBInfo->Private.wKDBInitOK = ET9GOODSETUP;

    /* inform about kdb loaded */

    if (pKDBInfo->ET9Handle_KDB_Request) {

        ET9KDB_Request sRequest;

        sRequest.eType = ET9_KDB_REQ_PAGE_LOADED;
        sRequest.data.sPageLoaded.dwKdbNum = dwKdbNum;
        sRequest.data.sPageLoaded.wPageNum = wPageNum;

        (void)pKDBInfo->ET9Handle_KDB_Request(pKDBInfo, NULL, &sRequest);
    }

    /* done */

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                               
 *
 *                                                                    
 *                                                           
 *
 *             
 */

static void ET9LOCALCALL __SetSymShiftState(ET9WordSymbInfo   * const pWordSymbInfo,
                                            ET9SymbInfo       * const pSymbInfo)
{
    ET9Assert(pWordSymbInfo);
    ET9Assert(pSymbInfo);

    if (ET9SHIFT_MODE(pWordSymbInfo->dwStateBits)) {
        pSymbInfo->eShiftState = ET9SHIFT;
    }
    else if (ET9CAPS_MODE(pWordSymbInfo->dwStateBits)) {
        pSymbInfo->eShiftState = ET9CAPSLOCK;
    }
    else {
        pSymbInfo->eShiftState = ET9NOSHIFT;
    }

    pWordSymbInfo->Private.eLastShiftState = pSymbInfo->eShiftState;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                                 
 *
 *                                                                       
 *
 *                                                                    
 */

static void ET9LOCALCALL __ProcessWordSymInfo(ET9KDBInfo        * const pKDBInfo)
{
    ET9WordSymbInfo * const pWordSymbInfo = pKDBInfo->Private.pWordSymbInfo;

    const ET9U8         bNumSymbs = (ET9U8)(pWordSymbInfo->bNumSymbs + 1);
    ET9SymbInfo * const pSymbInfo = &pWordSymbInfo->SymbsInfo[bNumSymbs - 1];
    ET9BOOL             bMultiSymbInput = pSymbInfo->bSymbType == ET9KTSTRING ? 1 : 0;

    ET9Assert(pKDBInfo);
    ET9Assert(pWordSymbInfo);

    _ET9InvalidateOneSymb(pWordSymbInfo, (ET9U8)(bNumSymbs - 1));

    /* first check to see of the input was multiple characters, such as an emoticon, in which case we are
       going to make modifications to the pWordSymbInfo */

    if (bMultiSymbInput) {

        ET9UINT                     nCount;
        ET9U16                      wInputIndex;
        ET9DataPerBaseSym   * const pDPBSym = pSymbInfo->DataPerBaseSym;
        const ET9UINT               nNumStringChars = pDPBSym->bNumSymsToMatch;

        if (!pWordSymbInfo->bNumSymbs) {
            wInputIndex = 1;
        }
        else {
            wInputIndex = pWordSymbInfo->SymbsInfo[pWordSymbInfo->bNumSymbs-1].wInputIndex + 1;
        }

        for (nCount = nNumStringChars; nCount; --nCount) {

            if (bNumSymbs - 1 + nCount <= ET9MAXWORDSIZE) {

                ET9SymbInfo         * const pTempSymbInfo = &pWordSymbInfo->SymbsInfo[bNumSymbs + nCount - 2];
                ET9DataPerBaseSym   * const pDPBSymTemp = pTempSymbInfo->DataPerBaseSym;

                _ET9InvalidateOneSymb(pWordSymbInfo, (ET9U8)(bNumSymbs + nCount - 2));

                pTempSymbInfo->bNumBaseSyms = 1;
                pTempSymbInfo->eInputType = ET9MULTISYMBEXPLICIT;
                pTempSymbInfo->wInputIndex = wInputIndex;
                pTempSymbInfo->wKeyIndex = ET9UNDEFINEDKEYVALUE;
                pTempSymbInfo->wTapX = ET9UNDEFINEDTAPVALUE;
                pTempSymbInfo->wTapY = ET9UNDEFINEDTAPVALUE;
                pTempSymbInfo->eShiftState = ET9NOSHIFT;
                pTempSymbInfo->bForcedLowercase = 0;
                pTempSymbInfo->bAmbigType = (ET9U8)ET9EXACT;
                pTempSymbInfo->bTraceProbability = 0;
                pTempSymbInfo->bTraceIndex = 0;
                pTempSymbInfo->bFreqsInvalidated = 1;
                pTempSymbInfo->dwKdb1 = 0;
                pTempSymbInfo->wPage1 = 0;
                pTempSymbInfo->dwKdb2 = 0;
                pTempSymbInfo->wPage2 = 0;

                pDPBSymTemp->bNumSymsToMatch = 1;
                pDPBSymTemp->bSymFreq = 2;
                pDPBSymTemp->sChar[0] = pDPBSym->sChar[nCount - 1];
                pDPBSymTemp->sUpperCaseChar[0] = pDPBSym->sChar[nCount - 1];

                pWordSymbInfo->Private.eLastShiftState = pTempSymbInfo->eShiftState;

                ++pWordSymbInfo->bNumSymbs;
            }
        }

        /* zero out rest of data from original pSymbInfo */

        {
            ET9UINT nIndex;

            for (nIndex = 1; nIndex < nNumStringChars; ++nIndex) {
                pDPBSym->sChar[nIndex] = 0;
                pDPBSym->sUpperCaseChar[nIndex] = 0;
            }
        }

        return;
    }

#ifdef ET9_DEBUG

    {
        /* verify that all symb info chars are valid (non zero) */

        ET9U16             bKeyIndx;
        ET9SymbInfo        *pSymbInfo;
        ET9DataPerBaseSym  *pSymData;
        ET9SYMB            *pLower;
        ET9SYMB            *pUpper;

        pSymbInfo = &pWordSymbInfo->SymbsInfo[pWordSymbInfo->bNumSymbs];
        pSymData  = pSymbInfo->DataPerBaseSym;

        for (bKeyIndx = pSymbInfo->bNumBaseSyms; bKeyIndx; --bKeyIndx, ++pSymData) {

            ET9UINT nCount;

            ET9Assert(pSymData->bSymFreq || pKDBInfo->Private.pFilterSymb);
            ET9Assert(pSymData->bNumSymsToMatch || pKDBInfo->Private.pFilterSymb);

            pLower = pSymData->sChar;
            pUpper = pSymData->sUpperCaseChar;

            for (nCount = pSymData->bNumSymsToMatch; nCount; --nCount, ++pLower, ++pUpper) {
                ET9Assert(*pLower && *pUpper);
            }
        }
    }

#endif

    pSymbInfo->bTraceProbability = 0;
    pSymbInfo->bTraceIndex = 0;
    pSymbInfo->bFreqsInvalidated = 1;

    __SetSymShiftState(pWordSymbInfo, pSymbInfo);

    ++pWordSymbInfo->bNumSymbs;

    /* shut off shift bit */

    if (!ET9_KDB_MULTITAP_MODE(pKDBInfo->dwStateBits)) {

        pWordSymbInfo->dwStateBits &= ~ET9STATE_SHIFT_MASK;
    }
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                               
 *
 *                                                                       
 *                                                                           
 *                                                                         
 *                                                                  
 *                                                                        
 *                                                   
 *
 *             
 */

static void ET9LOCALCALL __ProcessMultitap(ET9KDBInfo            * const pKDBInfo,
                                           ET9BOOL                       bIsFirstPress,
                                           ET9SYMB               * const psFunctionKey,
                                           const ET9U8                   bCurrIndexInList,
                                           const ET9U16                  wMTLastInput,
                                           const ET9LOADSYMBACTION       eLoadAction)
{
    ET9WordSymbInfo * const pWordSymbInfo = pKDBInfo->Private.pWordSymbInfo;

    ET9SymbInfo * const pSymbInfo = &pWordSymbInfo->SymbsInfo[pWordSymbInfo->bNumSymbs-1];

    ET9SYMB             sFirstSymb;
    ET9SYMB             sPreviousActiveSymb = 0;    /* silly compiler warning - init */
    ET9DataPerBaseSym   *pDPBS;

    WLOG5(fprintf(pLogFile5, "__ProcessMultitap, pKDBInfo = %p, eLoadAction = %d, bIsFirstPress = %d\n", pKDBInfo, (int)eLoadAction, bIsFirstPress);)

    ET9_UNUSED(psFunctionKey);
    ET9_UNUSED(bCurrIndexInList);

    ET9Assert(pKDBInfo);
    ET9Assert(pWordSymbInfo);
    ET9Assert(psFunctionKey);

    /* before destroying info, save previous symb for reload */

    if (eLoadAction == LOADSYMBACTION_RELOAD) {

        if (pKDBInfo->Private.bMTSymbCount) {

            if (pKDBInfo->Private.bMTLastSymbIndex < pKDBInfo->Private.bMTSymbCount) {
                sPreviousActiveSymb = pKDBInfo->Private.sMTSymbs[pKDBInfo->Private.bMTLastSymbIndex];
            }
            else {
                sPreviousActiveSymb = 0;
            }

            ET9Assert(sPreviousActiveSymb && sPreviousActiveSymb != (ET9SYMB)0xcccc && sPreviousActiveSymb != 0xFFFF);
        }

        WLOG5(fprintf(pLogFile5, "  reload, bMTLastSymbIndex = %d, bMTSymbCount = %d, sPreviousActiveSymb = %04X\n", pKDBInfo->Private.bMTLastSymbIndex, pKDBInfo->Private.bMTSymbCount, sPreviousActiveSymb);)
    }

    /* when needed, retrieve all symbs - basis for continued work */

    if (bIsFirstPress || eLoadAction == LOADSYMBACTION_RELOAD) {

        ET9U8 bBaseSymbCount;

        WLOG5(fprintf(pLogFile5, "  loading MT src\n");)

        pKDBInfo->Private.pMTKdbKey = pSymbInfo->pKdbKey;
        pKDBInfo->Private.bMTSymbCountSrc = 0;
        bBaseSymbCount = pSymbInfo->bNumBaseSyms;
        pDPBS = pSymbInfo->DataPerBaseSym;

        while (pKDBInfo->Private.bMTSymbCountSrc < ET9_KDB_MAX_MT_SYMBS) {

            ET9U8 bSymbCount;
            ET9SYMB *psSymb;
            ET9SYMB *psSymbLook;

            if (!bBaseSymbCount) {
                break;
            }

            psSymb = pDPBS->sChar;
            for (bSymbCount = pDPBS->bNumSymsToMatch; bSymbCount;  --bSymbCount, ++psSymb) {

                ET9U8 bCount;

                if (pKDBInfo->Private.bMTSymbCountSrc >= ET9_KDB_MAX_MT_SYMBS) {
                    break;
                }

                psSymbLook = pKDBInfo->Private.sMTSymbsSrc;
                for (bCount = pKDBInfo->Private.bMTSymbCountSrc; bCount; --bCount, ++psSymbLook) {
                    if (*psSymb == *psSymbLook) {
                        break;
                    }
                }
                if (bCount) {
                    continue;
                }

                ET9Assert(*psSymb && *psSymb != (ET9SYMB)0xcccc);

                *psSymbLook = *psSymb;
                ++pKDBInfo->Private.bMTSymbCountSrc;
            }

            ++pDPBS;
            --bBaseSymbCount;
        }
    }

    /* setup sequence based on symbol convert result, shift state etc */
    /* both case shift and convert can (individually) change the character sequnce (and character count) */

    _ET9InvalidateOneSymb(pWordSymbInfo, (ET9U8)(pWordSymbInfo->bNumSymbs-1));

    __SetSymShiftState(pWordSymbInfo, pSymbInfo);

    if (bIsFirstPress ||
        eLoadAction == LOADSYMBACTION_RELOAD ||
        pKDBInfo->Private.eMTLastShiftState != pSymbInfo->eShiftState) {

        pKDBInfo->Private.eMTLastShiftState = pSymbInfo->eShiftState;

        /* either pull from the symb data from above (src) or use dedicated MT info */

        if (pKDBInfo->Private.pMTKdbKey && pKDBInfo->Private.pMTKdbKey->nMultitapCharCount) {

            const ET9BOOL bCombinedCase = pKDBInfo->Private.pMTKdbKey->nMultitapShiftedCharCount ? 0 : 1;

            ET9UINT nCount;
            ET9UINT nTotCount;
            ET9SYMB *psSymb;

            WLOG5(fprintf(pLogFile5, "  loading MT seq (from actual MT info), bCombinedCase %u, shift %u\n", bCombinedCase, pSymbInfo->eShiftState);)

            if (!pSymbInfo->eShiftState || bCombinedCase) {
                nTotCount = pKDBInfo->Private.pMTKdbKey->nMultitapCharCount;
                psSymb = pKDBInfo->Private.pMTKdbKey->psMultitapChars;
            }
            else {
                nTotCount = pKDBInfo->Private.pMTKdbKey->nMultitapShiftedCharCount;
                psSymb = pKDBInfo->Private.pMTKdbKey->psMultitapShiftedChars;
            }

            pKDBInfo->Private.bMTSymbCount = 0;
            pKDBInfo->Private.bMTSymbCountSrc = 0;

            for (nCount = nTotCount; nCount; --nCount, ++psSymb) {

                ET9SYMB sSymb;
                ET9SYMB sSymbCnv;

                /* get symb from the saved source and apply case shift (things can become duplicates by this) */

                if (bCombinedCase) {

                    if (pSymbInfo->eShiftState) {
                        sSymb = _ET9SymToUpper(*psSymb, pWordSymbInfo->Private.dwLocale);
                    }
                    else {
                        sSymb = _ET9SymToLower(*psSymb, pWordSymbInfo->Private.dwLocale);
                    }
                }
                else {
                    sSymb = *psSymb;
                }

                /* filter suppression? */

                if (pKDBInfo->Private.pFilterSymb && pKDBInfo->Private.pFilterSymb(pKDBInfo->Private.pFilterSymbInfo, sSymb)) {
                    WLOG5B(fprintf(pLogFile5, "    Suppressed %04x\n", sSymb);)
                    continue;
                }

                /* OTFM convert */

                sSymbCnv = sSymb;

                if (pKDBInfo->Private.pConvertSymb) {
                    pKDBInfo->Private.pConvertSymb(pKDBInfo->Private.pConvertSymbInfo, &sSymbCnv);
                }

                /* add the resulting symb to the current sequence unless it's a duplicate */

                {
                    ET9U8 bSymbCount;
                    ET9SYMB *psSymbLook;

                    psSymbLook = pKDBInfo->Private.sMTSymbs;
                    for (bSymbCount = pKDBInfo->Private.bMTSymbCount; bSymbCount; --bSymbCount, ++psSymbLook) {
                        if (*psSymbLook == sSymbCnv) {
                            break;
                        }
                    }

                    if (!bSymbCount) {
                        pKDBInfo->Private.sMTSymbs[pKDBInfo->Private.bMTSymbCount++] = sSymbCnv;
                    }
                }

                pKDBInfo->Private.sMTSymbsSrc[pKDBInfo->Private.bMTSymbCountSrc] = sSymb;
                pKDBInfo->Private.sMTSymbsCnv[pKDBInfo->Private.bMTSymbCountSrc] = sSymbCnv;
                ++pKDBInfo->Private.bMTSymbCountSrc;

                if (pKDBInfo->Private.bMTSymbCountSrc >= ET9_KDB_MAX_MT_SYMBS) {
                    break;
                }
            }
        }
        else {

            ET9U8 bCount;
            ET9U8 bSymbCount;
            ET9SYMB *psSymb;
            ET9SYMB *psSymbConv;
            ET9SYMB *psSymbLook;

            WLOG5(fprintf(pLogFile5, "  loading MT seq (from symb info)\n");)

            pKDBInfo->Private.bMTSymbCount = 0;
            psSymb = pKDBInfo->Private.sMTSymbsSrc;
            psSymbConv = pKDBInfo->Private.sMTSymbsCnv;
            for (bCount = pKDBInfo->Private.bMTSymbCountSrc; bCount; --bCount, ++psSymb, ++psSymbConv) {

                /* get symb from the saved source and apply case shift (things can become duplicates by this) */

                if (pSymbInfo->eShiftState) {
                    *psSymbConv = _ET9SymToUpper(*psSymb, pWordSymbInfo->Private.dwLocale);
                }
                else {
                    *psSymbConv = _ET9SymToLower(*psSymb, pWordSymbInfo->Private.dwLocale);
                }

                /* OTFM convert */

                if (pKDBInfo->Private.pConvertSymb) {
                    pKDBInfo->Private.pConvertSymb(pKDBInfo->Private.pConvertSymbInfo, psSymbConv);
                }

                /* add the resulting symb to the current sequence unless it's a duplicate */

                psSymbLook = pKDBInfo->Private.sMTSymbs;
                for (bSymbCount = pKDBInfo->Private.bMTSymbCount; bSymbCount; --bSymbCount, ++psSymbLook) {
                    if (*psSymbLook == *psSymbConv) {
                        break;
                    }
                }
                if (!bSymbCount) {
                    *psSymbLook = *psSymbConv;
                    ++pKDBInfo->Private.bMTSymbCount;
                }
            }
        }
    }

    /* log MT src */

    WLOG5({
        ET9U8 bIndex;

        fprintf(pLogFile5, "  MTSymbsSrc = ");

        for (bIndex = 0; bIndex < pKDBInfo->Private.bMTSymbCountSrc; ++bIndex) {
            fprintf(pLogFile5, "%04X ", pKDBInfo->Private.sMTSymbsSrc[bIndex]);
        }

        fprintf(pLogFile5, "\n");
    })

    /* log MT */

    WLOG5({
        ET9U8 bIndex;

        fprintf(pLogFile5, "  MTSymbs = ");

        for (bIndex = 0; bIndex < pKDBInfo->Private.bMTSymbCount; ++bIndex) {
            fprintf(pLogFile5, "%04X ", pKDBInfo->Private.sMTSymbs[bIndex]);
        }

        fprintf(pLogFile5, "\n");
    })

    /* first press OR loop through MT array until find last char and deliver the next. */

    WLOG5(fprintf(pLogFile5, "  bMTLastSymbIndex = %d (before)\n", pKDBInfo->Private.bMTLastSymbIndex);)

    ET9Assert(pKDBInfo->Private.bMTLastSymbIndex != 0xFF);

    if (!pKDBInfo->Private.bMTSymbCount) {
        /* change nothing, will exit soon and come back for a new round */
    }
    else if (eLoadAction == LOADSYMBACTION_RELOAD) {
        /* change nothing, will be handled using the previous active symb below */
    }
    else if (bIsFirstPress) {
        pKDBInfo->Private.bMTLastSymbIndex = 0;
    }
    else {
        pKDBInfo->Private.bMTLastSymbIndex = (ET9U8)((pKDBInfo->Private.bMTLastSymbIndex + 1) % pKDBInfo->Private.bMTSymbCount);
    }

    WLOG5(fprintf(pLogFile5, "  bMTLastSymbIndex = %d (after)\n", pKDBInfo->Private.bMTLastSymbIndex);)

    ET9Assert(pKDBInfo->Private.bMTLastSymbIndex != 0xFF);

    /* setup the actual symbol */

    _ET9ClearMem((ET9U8 *)pSymbInfo, sizeof(ET9SymbInfo));

    pKDBInfo->Private.wMTLastInput = wMTLastInput;

    pSymbInfo->wTapX = pKDBInfo->Private.sKdbAction.bIsKeyAction ? ET9UNDEFINEDTAPVALUE : pKDBInfo->Private.sKdbAction.u.tapAction.wX;
    pSymbInfo->wTapY = pKDBInfo->Private.sKdbAction.bIsKeyAction ? ET9UNDEFINEDTAPVALUE : pKDBInfo->Private.sKdbAction.u.tapAction.wY;
    pSymbInfo->wKeyIndex = pKDBInfo->Private.sKdbAction.bIsKeyAction ? pKDBInfo->Private.sKdbAction.u.keyAction.wKeyIndex : ET9UNDEFINEDKEYVALUE;
    pSymbInfo->bAmbigType = (ET9U8)ET9EXACT;
    pSymbInfo->eInputType = ET9MULTITAPKEY;
    pSymbInfo->bTraceProbability = 0;
    pSymbInfo->bTraceIndex = 0;
    pSymbInfo->bFreqsInvalidated = 1;

    __SetSymShiftState(pWordSymbInfo, pSymbInfo);

#if 0
    {
        const ET9BOOL bAutoCap = 0;

        if (bAutoCap)  {
            pSymbInfo->eShiftState = ET9SHIFT;
            pWordSymbInfo->Private.eLastShiftState = ET9SHIFT;
        }
    }
#endif

    pSymbInfo->bNumBaseSyms = 1;
    pDPBS = pSymbInfo->DataPerBaseSym;
    pDPBS->bNumSymsToMatch = 0;
    pDPBS->bSymFreq = 0xFF;
    pDPBS->bDefaultCharIndex = 0;

    /* check empty */

    if (!pKDBInfo->Private.bMTSymbCount) {
        return;
    }

    /* on reload, use the previous active symb to find the right "last" index */

    if (eLoadAction == LOADSYMBACTION_RELOAD) {

        if (pKDBInfo->Private.pFilterSymbGroup) {

            ET9U8           bIndex;
            const ET9U8     bMTSymbCount = pKDBInfo->Private.bMTSymbCount;

            for (bIndex = 0; bIndex < bMTSymbCount; ++bIndex) {

                if (pKDBInfo->Private.pFilterSymbGroup(pKDBInfo->Private.pFilterSymbInfo,
                                                       sPreviousActiveSymb,
                                                       pKDBInfo->Private.sMTSymbs[bIndex])) {

                    WLOG5(fprintf(pLogFile5, "  reload, in group @ index %d\n", bIndex);)

                    pKDBInfo->Private.bMTLastSymbIndex = bIndex;
                    break;
                }
            }
        }

        if (pKDBInfo->Private.bMTLastSymbIndex >= pKDBInfo->Private.bMTSymbCount) {
            pKDBInfo->Private.bMTLastSymbIndex = 0;
        }

        WLOG5(fprintf(pLogFile5, "  reload, bMTLastSymbIndex = %d (after group)\n", pKDBInfo->Private.bMTLastSymbIndex);)
    }

    /* get the first symb */

    sFirstSymb = pKDBInfo->Private.sMTSymbs[pKDBInfo->Private.bMTLastSymbIndex];

    ET9Assert(sFirstSymb && sFirstSymb != (ET9SYMB)0xcccc && sFirstSymb != 0xFFFF);

    /* the zero element should have exact shift state only */

    pDPBS->sChar[pDPBS->bNumSymsToMatch] = sFirstSymb;
    pDPBS->sUpperCaseChar[pDPBS->bNumSymsToMatch] = sFirstSymb;

    ++pDPBS->bNumSymsToMatch;

    /* then again shifted */

    pDPBS->sChar[pDPBS->bNumSymsToMatch] = _ET9SymToLower(sFirstSymb, pWordSymbInfo->Private.dwLocale);
    pDPBS->sUpperCaseChar[pDPBS->bNumSymsToMatch] = _ET9SymToUpper(sFirstSymb, pWordSymbInfo->Private.dwLocale);

    ++pDPBS->bNumSymsToMatch;

    /* then all possible collapsed symbols */

    {
        ET9U8 bCount;
        ET9SYMB *psSymb;
        ET9SYMB *psSymbConv;

        psSymb = pKDBInfo->Private.sMTSymbsSrc;
        psSymbConv = pKDBInfo->Private.sMTSymbsCnv;
        for (bCount = pKDBInfo->Private.bMTSymbCountSrc; bCount; --bCount, ++psSymb, ++psSymbConv) {

            if (*psSymbConv != sFirstSymb) {
                continue;
            }

            if (pDPBS->bNumSymsToMatch >= ET9MAXBASESYMBS) {
                ++pDPBS;
                ++pSymbInfo->bNumBaseSyms;
                pDPBS->bNumSymsToMatch = 0;
                pDPBS->bSymFreq = 1;
                pDPBS->bDefaultCharIndex = 0;
            }

            ET9Assert(*psSymb && *psSymb != (ET9SYMB)0xcccc && *psSymb != (ET9SYMB)0xFFFF);

            pDPBS->sChar[pDPBS->bNumSymsToMatch] = _ET9SymToLower(*psSymb, pWordSymbInfo->Private.dwLocale);
            pDPBS->sUpperCaseChar[pDPBS->bNumSymsToMatch] = _ET9SymToUpper(*psSymb, pWordSymbInfo->Private.dwLocale);

            ++pDPBS->bNumSymsToMatch;
        }
    }

    /* request timeout */

    if (eLoadAction == LOADSYMBACTION_NEW) {

        pKDBInfo->dwStateBits |= ET9_KDB_INSERT_MASK;

        if (pKDBInfo->ET9Handle_KDB_Request) {
            __ET9KDB_RequestMultitapTimeout(pKDBInfo, wMTLastInput);
        }
    }
}

#ifdef ET9_KDB_TRACE_MODULE

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                               
 *
 *                                         
 *
 *                                   
 *                                   
 *                                   
 *                                   
 *
 *                                                         
 */

static ET9BOOL ET9LOCALCALL __KeyAreasFindArea (ET9KDBInfo        const * const pKDBInfo,
                                                const ET9FLOAT                  fX,
                                                const ET9FLOAT                  fY,
                                                ET9UINT                 * const pnAreaID)
{
    ET9KdbLayoutInfo * const pLayoutInfo = pKDBInfo->Private.pCurrLayoutInfo;

    ET9KdbAreaInfo *pBestArea = NULL;

    WLOG5B(fprintf(pLogFile5, "__KeyAreasFindArea %5.1f %5.1f\n", fX, fY);)

    if (fX >= pKDBInfo->Private.pCurrLayoutInfo->wLayoutWidth || fY >= pKDBInfo->Private.pCurrLayoutInfo->wLayoutHeight) {

        WLOG5B(fprintf(pLogFile5, "  out-of-bounds\n");)

        if (pnAreaID) {
            *pnAreaID = ET9_KDB_KEY_OUTOFBOUND;
        }

        return 0;
    }

    {
        ET9UINT nCount;
        ET9KdbAreaInfo *pArea;

        ET9UINT nBestOverlap;

        ET9Region sTargetRegion;

        {
            const ET9U16 wX = (ET9U16)(fX + 0.5);
            const ET9U16 wY = (ET9U16)(fY + 0.5);

            const ET9U16 wTargetOffsetX = (ET9U16)(pLayoutInfo->nMinKeyWidth / 2);
            const ET9U16 wTargetOffsetY = (ET9U16)(pLayoutInfo->nMinKeyHeight / 2);

            const ET9U16 wOffsetX = wX >= wTargetOffsetX ? wTargetOffsetX : wX;
            const ET9U16 wOffsetY = wY >= wTargetOffsetY ? wTargetOffsetY : wY;

            sTargetRegion.wLeft = (ET9U16)(wX - wOffsetX);
            sTargetRegion.wTop = (ET9U16)(wY - wOffsetY);
            sTargetRegion.wRight = (ET9U16)(sTargetRegion.wLeft + pLayoutInfo->nMinKeyWidth - 1);
            sTargetRegion.wBottom = (ET9U16)(sTargetRegion.wTop + pLayoutInfo->nMinKeyHeight - 1);
        }

        WLOG5B(fprintf(pLogFile5, "  sTargetRegion [%3u %3u %3u %3u]\n", sTargetRegion.wLeft, sTargetRegion.wTop, sTargetRegion.wRight, sTargetRegion.wBottom);)

        nBestOverlap = 0;
        pArea = pLayoutInfo->pKeyAreas;
        for (nCount = pLayoutInfo->nKeyAreaCount; nCount; --nCount, ++pArea) {

            const ET9UINT nOverlap = __GetRegionOverlap(&sTargetRegion, &pArea->sRegion);

            const ET9U8 bCenter = (fX >= pArea->sRegion.wLeft &&
                                   fX <= pArea->sRegion.wRight &&
                                   fY >= pArea->sRegion.wTop &&
                                   fY <= pArea->sRegion.wBottom) ? 1 : 0;

            WLOG5B(fprintf(pLogFile5, "  key region [%3u %3u %3u %3u], nOverlap %5u, bCenter %u\n", pArea->sRegion.wLeft, pArea->sRegion.wTop, pArea->sRegion.wRight, pArea->sRegion.wBottom, nOverlap, bCenter);)

            if (bCenter) {
                pBestArea = pArea;
                break;
            }

            if (nBestOverlap < nOverlap) {

                WLOG5B(fprintf(pLogFile5, "    new best (%u > %u)\n", nOverlap, nBestOverlap);)

                nBestOverlap = nOverlap;
                pBestArea = pArea;
            }
        }
    }

    if (!pBestArea) {

        if (pnAreaID) {
            *pnAreaID = ET9_KDB_KEY_UNDEFINED;
        }

        return 0;
    }

    if (pnAreaID) {
        *pnAreaID = pBestArea->wKeyIndex;
    }

    WLOG5B(fprintf(pLogFile5, "  found key %u\n", pBestArea->wKeyIndex);)

    return 1;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                                   
 *                                   
 *                                   
 *                                   
 *
 *            
 */

static void ET9LOCALCALL __KeyAreasGetKeyCenter (ET9KDBInfo         * const pKDBInfo,
                                                 const ET9U16               wKey,
                                                 ET9UINT            * const pnX,
                                                 ET9UINT            * const pnY)
{
    ET9KdbAreaInfo      const * pArea = __GetKeyAreaFromKey_Generic(pKDBInfo, wKey);

    if (!pArea) {
        *pnX = 0;
        *pnY = 0;
        return;
    }

    *pnX = pArea->nCenterX;
    *pnY = pArea->nCenterY;
}

#endif

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                          
 *
 *                                                                       
 *                                       
 *                                                                    
 *                                                                  
 *                                                   
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __ProcessAmbigKey_CurrentKdb(ET9KDBInfo           * const pKDBInfo,
                                                           const ET9U16                 wKeyIndex,
                                                           ET9SYMB              * const psFunctionKey,
                                                           const ET9U8                  bCurrIndexInList,
                                                           const ET9LOADSYMBACTION      eLoadAction)
{
    ET9STATUS eStatus;

    eStatus = __ProcessAmbigKey_CurrentKdb_Generic(pKDBInfo,
                                                   wKeyIndex,
                                                   psFunctionKey,
                                                   bCurrIndexInList,
                                                   eLoadAction);

    return eStatus;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                      
 *
 *                                                                       
 *                                       
 *                                                                    
 *                                                                  
 *                                                   
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __ProcessAmbigKey_ActiveKdbs(ET9KDBInfo           * const pKDBInfo,
                                                           const ET9U16                 wKeyIndex,
                                                           ET9SYMB              * const psFunctionKey,
                                                           const ET9U8                  bCurrIndexInList,
                                                           const ET9LOADSYMBACTION      eLoadAction)
{
    ET9WordSymbInfo * const pWordSymbInfo = pKDBInfo->Private.pWordSymbInfo;

    ET9SymbInfo * const pFirstNewSymbInfo = &pWordSymbInfo->SymbsInfo[pWordSymbInfo->bNumSymbs];

    ET9STATUS eStatus;

    ET9BOOL bFirstOk;

    ET9U32 dwKdb1;
    ET9U32 dwKdb2;
    ET9U16 wPage1;
    ET9U16 wPage2;

    WLOG5(fprintf(pLogFile5, "__ProcessAmbigKey_ActiveKdbs, pKDBInfo = %p, eLoadAction = %d\n", pKDBInfo, (int)eLoadAction);)

    ET9Assert(pKDBInfo);
    ET9Assert(ET9_KDB_AMBIGUOUS_MODE(pKDBInfo->dwStateBits));

    /* figure out load order */

    if (_ET9KDBSecondKDBSupported(pKDBInfo)) {

        if (pWordSymbInfo->Private.bSwitchLanguage) {
            dwKdb1  = pKDBInfo->dwSecondKdbNum;
            wPage1 = pKDBInfo->wSecondPageNum;
            dwKdb2  = pKDBInfo->dwFirstKdbNum;
            wPage2 = pKDBInfo->wFirstPageNum;
        }
        else {
            dwKdb1  = pKDBInfo->dwFirstKdbNum;
            wPage1 = pKDBInfo->wFirstPageNum;
            dwKdb2  = pKDBInfo->dwSecondKdbNum;
            wPage2 = pKDBInfo->wSecondPageNum;
        }
    }
    else {
        dwKdb1  = pKDBInfo->dwFirstKdbNum;
        wPage1 = pKDBInfo->wFirstPageNum;
        dwKdb2  = 0;
        wPage2 = 0;
    }

    /* load number 1 */

    bFirstOk = 0;

    eStatus = __KDBLoadPage(pKDBInfo, dwKdb1, wPage1, NULL);

    if (!eStatus) {

        eStatus = __ProcessAmbigKey_CurrentKdb(pKDBInfo, wKeyIndex, psFunctionKey, bCurrIndexInList, eLoadAction);

        if (!eStatus) {
            bFirstOk = 1;
        }

        /* make sure that we don't append on full */

        if (eStatus == ET9STATUS_FULL) {
            return eStatus;
        }
    }

    /* load number 2 */

    if (dwKdb2) {

        eStatus = __KDBLoadPage(pKDBInfo, dwKdb2, wPage2, NULL);

        if (!eStatus) {

            eStatus = __ProcessAmbigKey_CurrentKdb(pKDBInfo, wKeyIndex, psFunctionKey, bCurrIndexInList, (bFirstOk ? LOADSYMBACTION_APPEND : eLoadAction));
        }
    }

    /* history */

    if (eLoadAction == LOADSYMBACTION_NEW && !eStatus) {
        pFirstNewSymbInfo->dwKdb1 = dwKdb1;
        pFirstNewSymbInfo->wPage1 = wPage1;
        pFirstNewSymbInfo->dwKdb2 = dwKdb2;
        pFirstNewSymbInfo->wPage2 = wPage2;
    }

    /* done */

    return eStatus;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                          
 *
 *                                                                       
 *                                          
 *                                                                    
 *                                                                  
 *                                                   
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __ProcessAmbigTap_CurrentKdb(ET9KDBInfo           * const pKDBInfo,
                                                           ET9DirectedPos       * const pDirectedPos,
                                                           ET9SYMB              * const psFunctionKey,
                                                           const ET9U8                  bCurrIndexInList,
                                                           const ET9LOADSYMBACTION      eLoadAction)
{
    ET9STATUS eStatus;

    *psFunctionKey = 0;

    eStatus = __ProcessAmbigTap_CurrentKdb_Generic(pKDBInfo,
                                                   pDirectedPos,
                                                   psFunctionKey,
                                                   bCurrIndexInList,
                                                   eLoadAction);

    return eStatus;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                      
 *
 *                                                                       
 *                                          
 *                                                                    
 *                                                                  
 *                                                   
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __ProcessAmbigTap_ActiveKdbs(ET9KDBInfo           * const pKDBInfo,
                                                           ET9DirectedPos       * const pDirectedPos,
                                                           ET9SYMB              * const psFunctionKey,
                                                           const ET9U8                  bCurrIndexInList,
                                                           const ET9LOADSYMBACTION      eLoadAction)
{
    ET9WordSymbInfo * const pWordSymbInfo = pKDBInfo->Private.pWordSymbInfo;

    ET9SymbInfo * const pFirstNewSymbInfo = &pWordSymbInfo->SymbsInfo[pWordSymbInfo->bNumSymbs];

    ET9STATUS   eStatus;

    ET9BOOL     bFirstOk;

    ET9U32      dwKdb1;
    ET9U32      dwKdb2;
    ET9U16      wPage1;
    ET9U16      wPage2;

    WLOG5(fprintf(pLogFile5, "__ProcessAmbigTap_ActiveKdbs, pKDBInfo = %p, eLoadAction = %d\n", pKDBInfo, (int)eLoadAction);)

    ET9Assert(pKDBInfo);
    ET9Assert(ET9_KDB_AMBIGUOUS_MODE(pKDBInfo->dwStateBits));

    /* figure out load order */

    if (_ET9KDBSecondKDBSupported(pKDBInfo)) {

        if (pWordSymbInfo->Private.bSwitchLanguage) {
            dwKdb1 = pKDBInfo->dwSecondKdbNum;
            wPage1 = pKDBInfo->wSecondPageNum;
            dwKdb2 = pKDBInfo->dwFirstKdbNum;
            wPage2 = pKDBInfo->wFirstPageNum;
        }
        else {
            dwKdb1 = pKDBInfo->dwFirstKdbNum;
            wPage1 = pKDBInfo->wFirstPageNum;
            dwKdb2 = pKDBInfo->dwSecondKdbNum;
            wPage2 = pKDBInfo->wSecondPageNum;
        }
    }
    else {
        dwKdb1 = pKDBInfo->dwFirstKdbNum;
        wPage1 = pKDBInfo->wFirstPageNum;
        dwKdb2 = 0;
        wPage2 = 0;
    }

    /* load number 1 */

    bFirstOk = 0;

    eStatus = __KDBLoadPage(pKDBInfo, dwKdb1, wPage1, NULL);     /* should really be a "directed" version */

    if (!eStatus) {

        eStatus = __ProcessAmbigTap_CurrentKdb(pKDBInfo, pDirectedPos, psFunctionKey, bCurrIndexInList, eLoadAction);

        if (!eStatus) {
            bFirstOk = 1;
        }

        /* make sure that we don't append on full */

        if (eStatus == ET9STATUS_FULL) {
            return eStatus;
        }
    }

    /* load number 2 */

    if (dwKdb2) {

        eStatus = __KDBLoadPage(pKDBInfo, dwKdb2, wPage2, NULL);     /* should really be a "directed" version */

        if (!eStatus) {

            eStatus = __ProcessAmbigTap_CurrentKdb(pKDBInfo, pDirectedPos, psFunctionKey, bCurrIndexInList, (bFirstOk ? LOADSYMBACTION_APPEND : eLoadAction));
        }
    }

    /* history */

    if (eLoadAction == LOADSYMBACTION_NEW && !eStatus) {
        pFirstNewSymbInfo->dwKdb1 = dwKdb1;
        pFirstNewSymbInfo->wPage1 = wPage1;
        pFirstNewSymbInfo->dwKdb2 = dwKdb2;
        pFirstNewSymbInfo->wPage2 = wPage2;
    }

    /* done */

    return eStatus;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                           
 *
 *                                                                       
 *                                       
 *                                                                    
 *                                                                  
 *                                                   
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __ProcessMultitapKey_CurrentKdb(ET9KDBInfo            * const pKDBInfo,
                                                              const ET9U16                  wKeyIndex,
                                                              ET9SYMB               * const psFunctionKey,
                                                              const ET9U8                   bCurrIndexInList,
                                                              const ET9LOADSYMBACTION       eLoadAction)
{
    ET9WordSymbInfo * const pWordSymbInfo = pKDBInfo->Private.pWordSymbInfo;

    ET9STATUS  eStatus;
    ET9BOOL    bIsFirstPress;

    WLOG5(fprintf(pLogFile5, "__ProcessMultitapKey_CurrentKdb, pKDBInfo = %p, eLoadAction = %d\n", pKDBInfo, (int)eLoadAction);)

    ET9Assert(pKDBInfo);
    ET9Assert(psFunctionKey);

    bIsFirstPress = (ET9BOOL)(!(ET9_KDB_INSERT_MODE(pKDBInfo->dwStateBits)) ||
                              !pWordSymbInfo->bNumSymbs ||
                              wKeyIndex != pKDBInfo->Private.wMTLastInput);

    if (pKDBInfo->Private.sKdbAction.bShiftState) {
        pWordSymbInfo->dwStateBits |= ET9STATE_SHIFT_MASK;
    }

    if (bIsFirstPress || eLoadAction == LOADSYMBACTION_RELOAD) {

        /* first process it as if it had been ambiguous, this gets the MT list from the database. */

        eStatus = __ProcessAmbigKey_CurrentKdb(pKDBInfo, wKeyIndex, psFunctionKey, bCurrIndexInList, eLoadAction);

        if (eStatus || *psFunctionKey) {
            return eStatus;
        }
    }

    /* if there is only one character in multi-tap sequence, second press of the key will accept the character */

    else if (pKDBInfo->Private.bMTSymbCount == 1) {

        pWordSymbInfo->dwStateBits &= ~ET9STATE_SHIFT_MASK;
        pKDBInfo->dwStateBits &= ~ET9_KDB_INSERT_MASK;

        return ET9STATUS_NONE;
    }

    __ProcessMultitap(pKDBInfo, bIsFirstPress, psFunctionKey, bCurrIndexInList, wKeyIndex, eLoadAction);

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                        
 *
 *                                                                       
 *                                       
 *                                                                    
 *                                                                  
 *                                                   
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __ProcessMultitapKey_ActiveKdbs(ET9KDBInfo           * const pKDBInfo,
                                                              const ET9U16                 wKeyIndex,
                                                              ET9SYMB              * const psFunctionKey,
                                                              const ET9U8                  bCurrIndexInList,
                                                              const ET9LOADSYMBACTION      eLoadAction)
{
    ET9WordSymbInfo * const pWordSymbInfo = pKDBInfo->Private.pWordSymbInfo;

    ET9SymbInfo * const pFirstNewSymbInfo = &pWordSymbInfo->SymbsInfo[pWordSymbInfo->bNumSymbs];

    ET9STATUS       eStatus;

    ET9U32          dwKdb1;
    ET9U16          wPage1;

    WLOG5(fprintf(pLogFile5, "__ProcessMultitapKey_ActiveKdbs, pKDBInfo = %p, eLoadAction = %d\n", pKDBInfo, (int)eLoadAction);)

    ET9Assert(ET9_KDB_MULTITAP_MODE(pKDBInfo->dwStateBits));

    /* figure out load order */

    if (pWordSymbInfo->Private.bSwitchLanguage && _ET9KDBSecondKDBSupported(pKDBInfo)) {

        dwKdb1 = pKDBInfo->dwSecondKdbNum;
        wPage1 = pKDBInfo->wSecondPageNum;
    }
    else {
        dwKdb1 = pKDBInfo->dwFirstKdbNum;
        wPage1 = pKDBInfo->wFirstPageNum;
    }

    /* load number 1 */

    eStatus = __KDBLoadPage(pKDBInfo, dwKdb1, wPage1, NULL);

    if (!eStatus) {

        eStatus = __ProcessMultitapKey_CurrentKdb(pKDBInfo, wKeyIndex, psFunctionKey, bCurrIndexInList, eLoadAction);
    }

    /* history */

    if (eLoadAction == LOADSYMBACTION_NEW && !eStatus) {
        pFirstNewSymbInfo->dwKdb1 = dwKdb1;
        pFirstNewSymbInfo->wPage1 = wPage1;
        pFirstNewSymbInfo->dwKdb2 = 0;
        pFirstNewSymbInfo->wPage2 = 0;
    }

    /* done */

    return eStatus;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                        
 *
 *                                                                       
 *                                          
 *                                                          
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __GetFirstTapChar(ET9KDBInfo            * const pKDBInfo,
                                                ET9DirectedPos        * const pDirectedPos,
                                                ET9SYMB               * const psFirstChar)
{
    ET9STATUS eStatus;

    eStatus = __GetFirstTapChar_Generic(pKDBInfo, pDirectedPos, psFirstChar);

    return eStatus;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                          
 *
 *                                                                       
 *                                          
 *                                                                    
 *                                                                  
 *                                                   
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __ProcessMultitapTap_CurrentKdb(ET9KDBInfo           * const pKDBInfo,
                                                              ET9DirectedPos       * const pDirectedPos,
                                                              ET9SYMB              * const psFunctionKey,
                                                              const ET9U8                  bCurrIndexInList,
                                                              const ET9LOADSYMBACTION      eLoadAction)
{
    ET9WordSymbInfo * const pWordSymbInfo = pKDBInfo->Private.pWordSymbInfo;

    ET9STATUS  eStatus;
    ET9BOOL    bIsFirstPress;
    ET9SYMB    sFirstChar;

    WLOG5(fprintf(pLogFile5, "__ProcessMultitapTap_CurrentKdb, pKDBInfo = %p, eLoadAction = %d, wX = %d, wY = %d\n", pKDBInfo, (int)eLoadAction, pDirectedPos->sPos.nX, pDirectedPos->sPos.nY);)

    ET9Assert(pKDBInfo);
    ET9Assert(psFunctionKey);

    eStatus = __GetFirstTapChar(pKDBInfo, pDirectedPos, &sFirstChar);

    if (eStatus) {
        return eStatus;
    }

    bIsFirstPress = (ET9BOOL)(!(ET9_KDB_INSERT_MODE(pKDBInfo->dwStateBits)) ||
                              !pWordSymbInfo->bNumSymbs ||
                              sFirstChar != pKDBInfo->Private.wMTLastInput);

    if (pKDBInfo->Private.sKdbAction.bShiftState) {
        pWordSymbInfo->dwStateBits |= ET9STATE_SHIFT_MASK;
    }

    if (bIsFirstPress || eLoadAction == LOADSYMBACTION_RELOAD) {

        /* first process it as if it had been ambiguous, this gets the MT list from the database. */

        eStatus = __ProcessAmbigTap_CurrentKdb(pKDBInfo, pDirectedPos, psFunctionKey, bCurrIndexInList, eLoadAction);

        if (eStatus || *psFunctionKey) {
            return eStatus;
        }
    }

    /* if there is only one character in multi-tap sequence, second press of the key will accept the character */

    else if (pKDBInfo->Private.bMTSymbCount == 1) {

        pWordSymbInfo->dwStateBits &= ~ET9STATE_SHIFT_MASK;
        pKDBInfo->dwStateBits &= ~ET9_KDB_INSERT_MASK;

        return ET9STATUS_NONE;
    }

    __ProcessMultitap(pKDBInfo, bIsFirstPress, psFunctionKey, bCurrIndexInList, sFirstChar, eLoadAction);

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                        
 *
 *                                                                       
 *                                          
 *                                                                    
 *                                                                  
 *                                                   
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __ProcessMultitapTap_ActiveKdbs(ET9KDBInfo           * const pKDBInfo,
                                                              ET9DirectedPos       * const pDirectedPos,
                                                              ET9SYMB              * const psFunctionKey,
                                                              const ET9U8                  bCurrIndexInList,
                                                              const ET9LOADSYMBACTION      eLoadAction)
{
    ET9WordSymbInfo * const pWordSymbInfo = pKDBInfo->Private.pWordSymbInfo;

    ET9STATUS       eStatus;

    ET9U32          dwKdb1;
    ET9U16          wPage1;

    WLOG5(fprintf(pLogFile5, "__ProcessMultitapTap_ActiveKdbs, pKDBInfo = %p, eLoadAction = %d\n", pKDBInfo, (int)eLoadAction);)

    ET9Assert(pKDBInfo);
    ET9Assert(ET9_KDB_MULTITAP_MODE(pKDBInfo->dwStateBits));

    /* figure out load order */

    if (pWordSymbInfo->Private.bSwitchLanguage && _ET9KDBSecondKDBSupported(pKDBInfo)) {

        dwKdb1 = pKDBInfo->dwSecondKdbNum;
        wPage1 = pKDBInfo->wSecondPageNum;
    }
    else {
        dwKdb1 = pKDBInfo->dwFirstKdbNum;
        wPage1 = pKDBInfo->wFirstPageNum;
    }

    /* load number 1 */

    eStatus = __KDBLoadPage(pKDBInfo, dwKdb1, wPage1, NULL);

    if (!eStatus) {

        eStatus = __ProcessMultitapTap_CurrentKdb(pKDBInfo, pDirectedPos, psFunctionKey, bCurrIndexInList, eLoadAction);
    }

    return eStatus;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                                             
 *
 *                                                                                                               
 *                                               
 *                                                                                                                                                                                              
 *                                                                                                           
 *                                                          
 *                                                                                                                     
 *                                                                                                                                   
 *                                                                                                                         /                                                                                                                                        
 *                                                                                                   
 *                                                                                                                       
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __ProcessTap(ET9KDBInfo      * const pKDBInfo,
                                           ET9DirectedPos  * const pDirectedPos,
                                           const ET9U8             bCurrIndexInList,
                                           ET9SYMB         * const psFunctionKey)
{
    ET9WordSymbInfo * const pWordSymbInfo = pKDBInfo->Private.pWordSymbInfo;

    ET9STATUS eStatus;

    WLOG5(fprintf(pLogFile5, "\n__ProcessTap, pKDBInfo = %p\n", pKDBInfo);)

    __FilterSymbReset(pKDBInfo);

    pKDBInfo->Private.sKdbAction.bIsKeyAction = 0;
    pKDBInfo->Private.sKdbAction.bCurrIndexInList = bCurrIndexInList;
    pKDBInfo->Private.sKdbAction.bShiftState = ET9SHIFT_MODE(pWordSymbInfo->dwStateBits) ? 1 : 0;
    pKDBInfo->Private.sKdbAction.u.tapAction.wX = (ET9U16)pDirectedPos->sPos.nX;
    pKDBInfo->Private.sKdbAction.u.tapAction.wY = (ET9U16)pDirectedPos->sPos.nY;

    *psFunctionKey = 0;

    if (ET9_KDB_AMBIGUOUS_MODE(pKDBInfo->dwStateBits)) {

        pKDBInfo->dwStateBits &= ~ET9_KDB_INSERT_MASK;

        eStatus = __ProcessAmbigTap_ActiveKdbs(pKDBInfo, pDirectedPos, psFunctionKey, bCurrIndexInList, LOADSYMBACTION_NEW);
    }
    else if (ET9_KDB_MULTITAP_MODE(pKDBInfo->dwStateBits)) {

        eStatus = __ProcessMultitapTap_ActiveKdbs(pKDBInfo, pDirectedPos, psFunctionKey, bCurrIndexInList, LOADSYMBACTION_NEW);
    }
    else {
        return ET9STATUS_KDB_BAD_INPUT_MODE;
    }

    /* error above? */

    if (eStatus) {
        return eStatus;
    }

    /* shut off shift bit */

    if (!ET9_KDB_INSERT_MODE(pKDBInfo->dwStateBits) && !*psFunctionKey) {
        pWordSymbInfo->dwStateBits &= ~ET9STATE_SHIFT_MASK;
    }

    if (pWordSymbInfo->bNumSymbs == ET9MAXLDBWORDSIZE) {

        if (_ET9IsMagicStringKey(pWordSymbInfo)) {

            (void)ET9KDB_GetKdbVersion(pKDBInfo,
                                       pWordSymbInfo->Private.szIDBVersion,
                                       ET9MAXVERSIONSTR,
                                       &pWordSymbInfo->Private.wIDBVersionStrSize);
        }
    }

    /* mark valid if not a function key */

    if (!*psFunctionKey && pKDBInfo->Private.pFilterSymb) {
        pKDBInfo->Private.sKdbAction.dwCurrChecksum = __CalculateLastWordSymbChecksum(pWordSymbInfo);
    }

    /* turn off correction inhibit */

    if (!*psFunctionKey) {
        pWordSymbInfo->Private.bRequiredInhibitCorrection = 0;  /* not the override */
    }

    /* done */

    return ET9STATUS_NONE;
}

/* ************************************************************************************************************** */
/* * PUBLIC ***************************************************************************************************** */
/* ************************************************************************************************************** */

/*---------------------------------------------------------------------------*/
/**
 * @brief Initializes the Keyboard Input Module.
 *
 * @param[in]     pKDBInfo                  Pointer to the Keyboard Input Module Information Data Structure (ET9KDBInfo).
 * @param[in]     pWordSymbInfo             Pointer to word symbol info structure.
 * @param[in]     dwFirstKdbNum             Identification number of the keyboard to be used by the first language.
 * @param[in]     wFirstPageNum             Current page number of the keyboard to be used by the first language. Valid values range from 1 to the maximum number of keyboard pages indicated in the KDB text file.
 * @param[in]     dwSecondKdbNum            Identification number of the keyboard to be used by the second language.<br>
 *                                          Set this parameter to 0 if you are not implementing the bilingual feature
 * @param[in]     wSecondPageNum            Current page number of the keyboard to be used used by the second language.<br>
 *                                          Set this parameter to NULL if you are not implementing the bilingual feature.
 * @param[in]     pKDBLoadData              Pointer to the ET9KDBLOADCALLBACK() function.
 * @param[in]     ET9Handle_KDB_Request     Pointer to the ET9KDBREQUESTCALLBACK() function you must implement.
 * @param[in]     pPublicExtension          A value the integration layer can set and then retrieve in pKdbInfo->pPublicExtension.
 *
 * @retval ET9STATUS_NONE               Function call was handled successfully.
 * @retval ET9STATUS_INVALID_MEMORY     At least one pointer passed as a function argument was set to null.
 * @retval ET9STATUS_NO_INIT            The Keyboard Input Module has not been initialized. Call ET9KDB_Init().
 * @retval ET9STATUS_ABORT              XT9 has encountered a severe error. Often this status is returned because the integration layer did not allocate sufficient memory for required data structures or because required identification numbers (such as for the LDB and keyboard) are set to 0.
 * @retval ET9STATUS_DB_CORE_INCOMP     The keyboard and Keyboard Input Module are not compatible. Contact your Nuance support engineer for assistance.
 * @retval ET9STATUS_INVALID_DB_TYPE    The keyboard specified by wKDBNum is not valid. Contact your Nuance support engineer for assistance.
 * @retval ET9STATUS_INVALID_KDB_PAGE   The page specified by wPageNum is not valid for the keyboard specified by wKDBNum.
 * @retval ET9STATUS_KDB_VERSION_ERROR  The version information in the keyboard specified by wKDBNum is not valid. Contact your Nuance support engineer for assistance.
 * @retval ET9STATUS_NO_KEY             No keys are associated with the keyboard page specified by wPageNum.
 * @retval ET9STATUS_READ_DB_FAIL       The XT9 core cannot read data from the keyboard. This typically indicates a problem with the ET9KDBREADCALLBACK() function you have implemented.
 * @retval ET9STATUS_WRONG_OEMID        The OEM ID in the keyboard information does not match the XT9 core OEM ID. Contact your Nuance support engineer for assistance.
 *
 * @remarks The integration layer must initialize this module before it can be used for text entry. After calling ET9KDB_Init,
 * the integration layer should initialize the Linguistic Module (if it is not already initialized).<br>
 * When the integration layer initializes the Keyboard Input Module, the module is set by default to Ambiguous mode and a lowercase shift state.
 *
 */

ET9STATUS ET9FARCALL ET9KDB_Init(ET9KDBInfo                 * const pKDBInfo,
                                 ET9WordSymbInfo            * const pWordSymbInfo,
                                 const ET9U32                       dwFirstKdbNum,
                                 const ET9U16                       wFirstPageNum,
                                 const ET9U32                       dwSecondKdbNum,
                                 const ET9U16                       wSecondPageNum,
                                 const ET9KDBLOADCALLBACK           pKDBLoadData,
                                 const ET9KDBREQUESTCALLBACK        ET9Handle_KDB_Request,
                                 void                       * const pPublicExtension)
{
    ET9STATUS eStatus;

    WLOG5(fprintf(pLogFile5, "\nET9KDB_Init, pKDBInfo = %p\n", pKDBInfo);)

    WLOG5(fprintf(pLogFile5, "\n");)
    WLOG5(fprintf(pLogFile5, "  sizeof(ET9KDBInfo)                  = %6u\n", sizeof(ET9KDBInfo));)
    WLOG5(fprintf(pLogFile5, "    sizeof(pLayoutInfos)              = %6u\n", sizeof(ET9KdbLayoutInfo) * ET9_KDB_MAX_PAGE_CACHE);)
    WLOG5(fprintf(pLogFile5, "      sizeof(ET9KdbLayoutInfo)        = %6u\n", sizeof(ET9KdbLayoutInfo));)
    WLOG5(fprintf(pLogFile5, "    sizeof(wm)                        = %6u\n", sizeof(pKDBInfo->Private.wm));)
    WLOG5(fprintf(pLogFile5, "      sizeof(traceEvent)              = %6u\n", sizeof(pKDBInfo->Private.wm.traceEvent));)
    WLOG5(fprintf(pLogFile5, "        sizeof(ET9TracePointExt)      = %6u\n", sizeof(ET9TracePointExt));)
    WLOG5(fprintf(pLogFile5, "      sizeof(xmlReader)               = %6u\n", sizeof(pKDBInfo->Private.wm.xmlReader));)
    WLOG5(fprintf(pLogFile5, "        sizeof(ET9KdbXmlKeyboardInfo) = %6u\n", sizeof(pKDBInfo->Private.wm.xmlReader.sKeyboardInfo));)
    WLOG5(fprintf(pLogFile5, "          sizeof(ET9KdbXmlKey)        = %6u (%6u)\n", sizeof(ET9KdbXmlKey), sizeof(ET9KdbXmlKey) * ET9_KDB_MAX_KEYS);)
    WLOG5(fprintf(pLogFile5, "          sizeof(ET9KdbXmlRow)        = %6u (%6u)\n", sizeof(ET9KdbXmlRow), sizeof(ET9KdbXmlKey) * ET9_KDB_MAX_ROWS);)
    WLOG5(fprintf(pLogFile5, "          sizeof(ET9KdbXmlKeyboard)   = %6u\n", sizeof(ET9KdbXmlKeyboard));)
    WLOG5(fprintf(pLogFile5, "          sizeof(psSymbPool)          = %6u\n", sizeof(pKDBInfo->Private.wm.xmlReader.sKeyboardInfo.psSymbPool));)
    WLOG5(fprintf(pLogFile5, "\n");)

#ifdef ET9_KDB_TRACE_MODULE
    if (_ET9ByteStringCheckSum(_pbXt9Trace) != 4250608233U) {
        return ET9STATUS_ERROR;
    }
#endif

    if (!pKDBInfo || !pWordSymbInfo || !pKDBLoadData) {
        return ET9STATUS_INVALID_MEMORY;
    }

    _ET9ClearMem((ET9U8*)pKDBInfo, sizeof(ET9KDBInfo));

    eStatus = _ET9WordSymbInit(pWordSymbInfo);

    if (eStatus) {
        return eStatus;
    }

    if (pWordSymbInfo->Private.bManualLocale) {
        pKDBInfo->Private.dwKDBCacheLocale = pWordSymbInfo->Private.dwLocale;
    }

    pKDBInfo->ET9Handle_KDB_Request = ET9Handle_KDB_Request;
    pKDBInfo->pKDBLoadData = pKDBLoadData;
    pKDBInfo->pPublicExtension = pPublicExtension;
    pKDBInfo->Private.wInfoInitOK = ET9GOODSETUP;
    pKDBInfo->Private.pWordSymbInfo = pWordSymbInfo;
    pKDBInfo->Private.pConvertSymb = NULL;
    pKDBInfo->Private.pConvertSymbInfo = NULL;

    pKDBInfo->Private.pCurrLayoutInfo = &pKDBInfo->Private.pLayoutInfos[0];
    pKDBInfo->Private.pLastLayoutInfo = pKDBInfo->Private.pCurrLayoutInfo;

    if (ET9_GetSymbolEncoding() == ET9SYMBOL_ENCODING_SHIFTJIS) {
        pKDBInfo->Private.pFilterSymb = __DefaultDiacriticFilterShiftJis;
        pKDBInfo->Private.pFilterSymbReset = __DefaultDiacriticFilterResetShiftJis;
        pKDBInfo->Private.pFilterSymbCount = __DefaultDiacriticFilterCountShiftJis;
        pKDBInfo->Private.pFilterSymbNext = __DefaultDiacriticFilterNextShiftJis;
        pKDBInfo->Private.pFilterSymbGroup = __DefaultDiacriticFilterGroupShiftJis;
        pKDBInfo->Private.pFilterSymbInfo = pKDBInfo;
    }
    else {
        pKDBInfo->Private.pFilterSymb = NULL;
        pKDBInfo->Private.pFilterSymbReset = NULL;
        pKDBInfo->Private.pFilterSymbInfo = NULL;
        pKDBInfo->Private.pFilterSymbCount = NULL;
        pKDBInfo->Private.pFilterSymbNext = NULL;
        pKDBInfo->Private.pFilterSymbGroup = NULL;
    }

    /* initialize in lower case ambiguous mode */

    pKDBInfo->dwStateBits = ET9_KDB_AMBIGUOUS_MODE_MASK;

    pKDBInfo->dwFirstKdbNum = ET9PLIDNone;
    pKDBInfo->dwSecondKdbNum = ET9PLIDNone;
    pKDBInfo->dwKdbNum = ET9PLIDNone;

    eStatus = ET9KDB_SetKdbNum(pKDBInfo, dwFirstKdbNum, wFirstPageNum, dwSecondKdbNum, wSecondPageNum);

    if (eStatus) {
        pKDBInfo->dwStateBits = 0;
        return eStatus;
    }

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/**
 * @brief Activates a keyboard by specifying its keyboard identification number.
 *
 * @param[in]     pKDBInfo          Pointer to the Keyboard Input Module Information Data Structure (ET9KDBInfo).
 * @param[in]     dwFirstKdbNum     Identification number of the keyboard to be used by the first language.
 * @param[in]     wFirstPageNum     Initial page number to be loaded for the keyboard used by the first language.
 *                                  Valid values range from 1 to the maximum number of keyboard pages (indicated in the KDB text file) minus 1.
 * @param[in]     dwSecondKdbNum    Identification number of the keyboard to be used by the second language.<br>
 *                                  Set this parameter to 0 if you are not implementing the bilingual feature.
 * @param[in]     wSecondPageNum    Initial page number to be loaded for the keyboard used by the second language.
 *                                  Set this parameter to NULL if you are not implementing the bilingual feature.
 *
 * @retval ET9STATUS_NONE           Function call was handled successfully.
 * @retval ET9STATUS_INVALID_MEMORY At least one pointer passed as a function argument was set to null.
 * @retval ET9STATUS_NO_INIT        The Keyboard Input Module has not been initialized. Call ET9KDB_Init().
 * @retval ET9STATUS_ABORT          XT9 has encountered a severe error. Often this status is returned because the integration layer did not allocate sufficient memory for required data structures or because required identification numbers (such as for the LDB and keyboard) are set to 0.
 * @retval ET9STATUS_KDB_VERSION_ERROR  The version information in the keyboard specified by wKDBNum is not valid.
 *                                      Contact your Nuance support engineer for assistance.
 * @retval ET9STATUS_INVALID_DB_TYPE    The keyboard specified by wKDBNum is not valid. Contact your Nuance support engineer for assistance.
 * @retval ET9STATUS_DB_CORE_INCOMP     The keyboard and Keyboard Input Module are not compatible. Contact your Nuance support engineer for assistance.
 * @retval ET9STATUS_INVALID_KDB_PAGE   The page specified by wPageNum is not valid for the keyboard specified by wKDBNum.
 * @retval ET9STATUS_NO_KEY             No keys are associated with the keyboard page specified by wPageNum.
 * @retval ET9STATUS_READ_DB_FAIL   The XT9 core cannot read data from the keyboard. This typically indicates a problem with the ET9KDBREADCALLBACK() function you have implemented.
 *
 * @remarks When calling this function, the integration layer specifies which.
 * keyboard is becoming active and which page of the keyboard is to be set as the current page.<br>
 * To retrieve the identification number of the active keyboard, the integration layer calls
 * ET9KDB_GetKdbNum(). To retrieve the page number of the active keyboard's current page, the
 * integration layer calls ET9KDB_GetPageNum().
 */

ET9STATUS ET9FARCALL ET9KDB_SetKdbNum(ET9KDBInfo    * const pKDBInfo,
                                      const ET9U32          dwFirstKdbNum,
                                      const ET9U16          wFirstPageNum,
                                      const ET9U32          dwSecondKdbNum,
                                      const ET9U16          wSecondPageNum)
{
    ET9STATUS   eStatus;

    ET9U16       wNumKeysFirstKDB = 0;
    ET9U16       wNumKeysSecondKDB = 0;
    ET9U32       dwFirstChecksum = 0;
    ET9U32       dwSecondChecksum = 0;
    ET9U16       wTotalFirstPages = 0;
    ET9U16       wTotalSecondPages = 0;
    ET9U32       dwOrigFirstKdbNum;
    ET9U16       wOrigFirstPageNum;
    ET9U32       dwOrigSecondKdbNum;
    ET9U16       wOrigSecondPageNum;

    WLOG5(fprintf(pLogFile5, "ET9KDB_SetKdbNum, pKDBInfo = %p\n", pKDBInfo);)

    eStatus = __ET9KDB_BasicValidityCheck(pKDBInfo, 0);

    if (eStatus) {
        return eStatus;
    }

    /* Need valid kdb to load second page */

    if (wSecondPageNum && ((dwSecondKdbNum & ET9PLIDMASK) == ET9PLIDNone)) {
        return ET9STATUS_NEED_KDB_TO_LOAD_PAGE;
    }

    /* clear insert mask */

    pKDBInfo->dwStateBits &= ~ET9_KDB_INSERT_MASK;

    /* remember previous values */

    dwOrigFirstKdbNum = pKDBInfo->dwFirstKdbNum;
    wOrigFirstPageNum = pKDBInfo->wFirstPageNum;
    dwOrigSecondKdbNum = pKDBInfo->dwSecondKdbNum;
    wOrigSecondPageNum = pKDBInfo->wSecondPageNum;

    /* reset filtering */

    __FilterSymbReset(pKDBInfo);

    pKDBInfo->Private.sKdbAction.dwCurrChecksum = 0;

    pKDBInfo->dwFirstKdbNum = dwFirstKdbNum;
    pKDBInfo->wFirstPageNum = wFirstPageNum;
    pKDBInfo->dwSecondKdbNum = ((dwSecondKdbNum & ET9PLIDMASK) != ET9PLIDNone) ? dwSecondKdbNum : 0;
    pKDBInfo->wSecondPageNum = ((dwSecondKdbNum & ET9PLIDMASK) != ET9PLIDNone) ? wSecondPageNum : 0;

    if (dwSecondKdbNum && ((dwSecondKdbNum & ET9PLIDMASK) != ET9PLIDNone)) {

        eStatus = __KDBLoadPage(pKDBInfo, dwSecondKdbNum, wSecondPageNum, &wNumKeysSecondKDB);

        if (eStatus) {
            /* restore */
            pKDBInfo->dwFirstKdbNum = dwOrigFirstKdbNum;
            pKDBInfo->wFirstPageNum = wOrigFirstPageNum;
            pKDBInfo->dwSecondKdbNum = dwOrigSecondKdbNum;
            pKDBInfo->wSecondPageNum = wOrigSecondPageNum;

            return eStatus;
        }

        wTotalSecondPages = pKDBInfo->wTotalPages;

        dwSecondChecksum = __ComputeContentChecksum(pKDBInfo);
    }

    eStatus = __KDBLoadPage(pKDBInfo, dwFirstKdbNum, wFirstPageNum, &wNumKeysFirstKDB);

    if (eStatus) {
        /* restore */
        pKDBInfo->dwFirstKdbNum = dwOrigFirstKdbNum;
        pKDBInfo->wFirstPageNum = wOrigFirstPageNum;
        pKDBInfo->dwSecondKdbNum = dwOrigSecondKdbNum;
        pKDBInfo->wSecondPageNum = wOrigSecondPageNum;

        return eStatus;
    }

    wTotalFirstPages = pKDBInfo->wTotalPages;

    dwFirstChecksum = __ComputeContentChecksum(pKDBInfo);

    if ((dwFirstKdbNum != dwSecondKdbNum) &&
        ((dwFirstKdbNum & ET9PLIDMASK)  != ET9PLIDNone) &&
        ((dwSecondKdbNum & ET9PLIDMASK) != ET9PLIDNone) &&
        ((wTotalFirstPages != wTotalSecondPages) ||
        (wNumKeysFirstKDB != wNumKeysSecondKDB) ||
        (dwFirstChecksum != dwSecondChecksum))) {

        /* restore */
        pKDBInfo->dwFirstKdbNum = dwOrigFirstKdbNum;
        pKDBInfo->wFirstPageNum = wOrigFirstPageNum;
        pKDBInfo->dwSecondKdbNum = dwOrigSecondKdbNum;
        pKDBInfo->wSecondPageNum = wOrigSecondPageNum;

        return ET9STATUS_KDB_MISMATCH;
    }

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/**
 * @brief Retrieves the identification number of the active keyboard.
 *
 * @param[in]     pKDBInfo          Pointer to the Keyboard Input Module Information Data Structure (ET9KDBInfo).
 * @param[out]    pdwKdbNum         Pointer to a value indicating the identification number of the first language keyboard.
 * @param[out]    pdwSecondKdbNum   Pointer to a value indicating the identification number of the second language keyboard.
 *                                  Set this parameter to 0 if you are not implementing the bilingual feature.
 *
 * @retval ET9STATUS_NONE           Function call was handled successfully.
 * @retval ET9STATUS_INVALID_MEMORY At least one pointer passed as a function argument was set to null.
 * @retval ET9STATUS_NO_INIT        The Keyboard Input Module has not been initialized. Call ET9KDB_Init().
 *
 * @see ET9KDB_SetKdbNum(), ET9KDB_GetPageNum()
 */

ET9STATUS ET9FARCALL ET9KDB_GetKdbNum(ET9KDBInfo * const pKDBInfo,
                                      ET9U32     * const pdwKdbNum,
                                      ET9U32     * const pdwSecondKdbNum)
{
    ET9STATUS eStatus;

    eStatus = __ET9KDB_BasicValidityCheck(pKDBInfo, 1);

    if (eStatus) {
        return eStatus;
    }

    ET9Assert(pKDBInfo);

    if (!pdwKdbNum) {
        return ET9STATUS_INVALID_MEMORY;
    }

    if (_ET9KDBSecondKDBSupported(pKDBInfo) && (!pdwSecondKdbNum)) {
        return ET9STATUS_INVALID_MEMORY;
    }

    *pdwKdbNum = pKDBInfo->dwFirstKdbNum;

    if (pdwSecondKdbNum) {
        *pdwSecondKdbNum = pKDBInfo->dwSecondKdbNum;
    }

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/**
 * @brief Sets the current page of the active keyboard by specifying its page number.
 *
 * @param[in]     pKDBInfo          Pointer to the Keyboard Input Module Information Data Structure (ET9KDBInfo).
 * @param[in]     wFirstPageNum     Initial page number to be loaded for the keyboard used by the first language.
 *                              Valid values range from 0 to the maximum number of keyboard pages
 *                              (indicated in the KDB text file) minus 1.
 * @param[in]     wSecondPageNum    Initial page number to be loaded for the keyboard used by the second language.<br>
 *                              Set this parameter to NULL if you are not implementing the bilingual feature.
 *
 * @retval ET9STATUS_NONE               Function call was handled successfully.
 * @retval ET9STATUS_INVALID_MEMORY     At least one pointer passed as a function argument was set to null.
 * @retval ET9STATUS_NO_INIT            The Keyboard Input Module has not been initialized. Call ET9KDB_Init().
 * @retval ET9STATUS_INVALID_KDB_PAGE   The page number of the current keyboard page is not valid.
 *                                      If you receive this status, contact your Nuance support engineer for assistance.
 * @retval ET9STATUS_NO_KEY             No keys are associated with the keyboard page specified by wPageNum.
 * @retval ET9STATUS_READ_DB_FAIL       The XT9 core cannot read data from the keyboard. This typically indicates a problem with the ET9KDBREADCALLBACK() function you have implemented.
 *
 * @see ET9KDB_GetPageNum(), ET9KDB_SetKdbNum()
 */

ET9STATUS ET9FARCALL ET9KDB_SetPageNum(ET9KDBInfo * const pKDBInfo,
                                       const ET9U16       wFirstPageNum,
                                       const ET9U16       wSecondPageNum)
{
    ET9STATUS  eStatus;
    ET9U16     wSavedFirstPageNum;
    ET9U16     wSavedSecondPageNum;
    ET9U16     wNumKeysFirstKDB = 0;
    ET9U16     wNumKeysSecondKDB = 0;
    ET9U32     dwFirstChecksum = 0;
    ET9U32     dwSecondChecksum = 0;

    WLOG5(fprintf(pLogFile5, "ET9KDB_SetPageNum, pKDBInfo = %p\n", pKDBInfo);)

    eStatus = __ET9KDB_BasicValidityCheck(pKDBInfo, 1);

    if (eStatus) {
        return eStatus;
    }

    /* Need valid kdb to load second page */

    if (wSecondPageNum && !pKDBInfo->dwSecondKdbNum) {
        return ET9STATUS_NEED_KDB_TO_LOAD_PAGE;
    }

    /* clear insert mask */

    pKDBInfo->dwStateBits &= ~ET9_KDB_INSERT_MASK;

    /* reset filtering */

    __FilterSymbReset(pKDBInfo);

    /* */

    wSavedFirstPageNum = pKDBInfo->wFirstPageNum;
    pKDBInfo->wFirstPageNum = wFirstPageNum;

    eStatus = __KDBLoadPage(pKDBInfo, pKDBInfo->dwFirstKdbNum, wFirstPageNum, &wNumKeysFirstKDB);

    if (eStatus) {

        /* load was unsuccessful, replace original page (_may_ be good) */

        pKDBInfo->wFirstPageNum = wSavedFirstPageNum;
        __KDBLoadPage(pKDBInfo, pKDBInfo->dwFirstKdbNum, wSavedFirstPageNum, NULL);

        return eStatus;
    }

    dwFirstChecksum = __ComputeContentChecksum(pKDBInfo);

    if (_ET9KDBSecondKDBSupported(pKDBInfo)) {

        wSavedSecondPageNum = pKDBInfo->wSecondPageNum;
        pKDBInfo->wSecondPageNum = wSecondPageNum;

        eStatus = __KDBLoadPage(pKDBInfo, pKDBInfo->dwSecondKdbNum, wSecondPageNum, &wNumKeysSecondKDB);

        if (eStatus) {

            /* if load was unsuccessful, replace original page (_may_ be good) */

            pKDBInfo->wSecondPageNum = wSavedSecondPageNum;
            __KDBLoadPage(pKDBInfo, pKDBInfo->dwSecondKdbNum, wSavedSecondPageNum, NULL);

            return eStatus;
        }

        dwSecondChecksum = __ComputeContentChecksum(pKDBInfo);

        if ((pKDBInfo->dwFirstKdbNum != pKDBInfo->dwSecondKdbNum) &&
            ((pKDBInfo->dwFirstKdbNum & ET9PLIDMASK)  != ET9PLIDNone) &&
            ((pKDBInfo->dwSecondKdbNum & ET9PLIDMASK) != ET9PLIDNone) &&
            ((wNumKeysFirstKDB != wNumKeysSecondKDB) ||
            (dwFirstChecksum != dwSecondChecksum))) {

            /* if KDB mismatch, replace original page (_may_ be good) */

            pKDBInfo->wFirstPageNum = wSavedFirstPageNum;
            (void)__KDBLoadPage(pKDBInfo, pKDBInfo->dwFirstKdbNum, wSavedFirstPageNum, NULL);

            pKDBInfo->wSecondPageNum = wSavedSecondPageNum;
            (void)__KDBLoadPage(pKDBInfo, pKDBInfo->dwSecondKdbNum, wSavedSecondPageNum, NULL);

            return ET9STATUS_KDB_MISMATCH;
        }
    }

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/**
 * @brief Retrieves the page number of the active keyboard's current page.
 *
 * @param[in]     pKDBInfo          Pointer to the Keyboard Input Module Information Data Structure (ET9KDBInfo).
 * @param[out]    pwFirstPageNum   Pointer to current page number of the first language keyboard.
 *                              Valid values range from 1 to the maximum number of keyboard pages indicated in the KDB text file.
 * @param[out]    pwSecondPageNum  Pointer to the current page number of the second language keyboard.<br>
 *                              Set this parameter to NULL if you are not implementing the bilingual feature.
 *
 * @retval ET9STATUS_NONE               Function call was handled successfully.
 * @retval ET9STATUS_INVALID_MEMORY     At least one pointer passed as a function argument was set to null.
 * @retval ET9STATUS_NO_INIT            The Keyboard Input Module has not been initialized. Call ET9KDB_Init().
 * @retval ET9STATUS_INVALID_KDB_PAGE   The page number of the current keyboard page is not valid.
 *                                      If you receive this status, contact your Nuance support engineer for assistance.
 *
 * @see ET9KDB_GetKdbNum(), ET9KDB_SetPageNum
 */

ET9STATUS ET9FARCALL ET9KDB_GetPageNum(ET9KDBInfo * const pKDBInfo,
                                       ET9U16     * const pwFirstPageNum,
                                       ET9U16     * const pwSecondPageNum)
{
    ET9STATUS   eStatus;

    eStatus = __ET9KDB_BasicValidityCheck(pKDBInfo, 1);

    if (eStatus) {
        return eStatus;
    }

    ET9Assert(pKDBInfo);

    if (!pwFirstPageNum) {
        return ET9STATUS_INVALID_MEMORY;
    }

    if (_ET9KDBSecondKDBSupported(pKDBInfo) && (!pwSecondPageNum)) {
        return ET9STATUS_INVALID_MEMORY;
    }

    *pwFirstPageNum = 0;

    /* Added sanity check */

    if ((pKDBInfo->wTotalPages <= pKDBInfo->wFirstPageNum) ||
        (pKDBInfo->wTotalPages <= pKDBInfo->wSecondPageNum)) {
        return ET9STATUS_INVALID_KDB_PAGE;
    }

    *pwFirstPageNum = pKDBInfo->wFirstPageNum;

    if (pwSecondPageNum) {
        *pwSecondPageNum = pKDBInfo->wSecondPageNum;
    }

    return eStatus;
}

/*---------------------------------------------------------------------------*/
/**
 * @brief Validates the integrity of a Keyboard Database.
 *
 * @param[in]     pKDBInfo          Pointer to the Keyboard Input Module Information Data Structure (ET9KDBInfo).
 * @param[in]     dwKdbNum          Keyboard identification number of the keyboard to be validated.
 * @param[in]     pKDBLoadData      Pointer to the ET9KDBLOADCALLBACK() function.
 *
 * @retval ET9STATUS_NONE           Function call was handled successfully.
 * @retval ET9STATUS_INVALID_MEMORY At least one pointer passed as a function argument was set to null.
 * @retval ET9STATUS_NO_INIT        The Keyboard Input Module has not been initialized. Call ET9KDB_Init().
 * @retval ET9STATUS_CORRUPT_DB     The keyboard is corrupted. Contact your Nuance support engineer for assistance.
 * @retval ET9STATUS_READ_DB_FAIL   The XT9 core cannot read data from the keyboard. This typically indicates a problem with the ET9KDBREADCALLBACK() function you have implemented.
 *
 * @remarks XT9 generates a checksum for the database specified by the value of wKDBNum and compares the checksum to a previously stored value.<br>
 * The integration layer must initialize the Keyboard Input Module (by calling ET9KDB_Init) before calling ET9KDB_Validate.
 *
 */

ET9STATUS ET9FARCALL ET9KDB_Validate(ET9KDBInfo         * const pKDBInfo,
                                     const ET9U32               dwKdbNum,
                                     const ET9KDBLOADCALLBACK   pKDBLoadData
                                     )
{
    if (!pKDBInfo || !pKDBLoadData) {
        return ET9STATUS_INVALID_MEMORY;
    }

    WLOG5(fprintf(pLogFile5, "ET9KDB_Validate, wKdbNum = %8x\n", dwKdbNum);)

    {
        ET9STATUS                   eStatus;
        const ET9BOOL               bOldKDBLoaded = pKDBInfo->Private.bKDBLoaded;
        const ET9U32                dwOldKdbNum = pKDBInfo->dwKdbNum;
        const ET9U16                wOldPageNum = pKDBInfo->Private.wPageNum;
        const ET9KDBLOADCALLBACK    wOldKDBLoadCallback = pKDBInfo->pKDBLoadData;

        pKDBInfo->dwKdbNum = dwKdbNum;
        pKDBInfo->pKDBLoadData = pKDBLoadData;
        pKDBInfo->Private.bKDBLoaded = 0;

        /* validate */

        eStatus = __KDBValidate_Dynamic(pKDBInfo);


        /* restore previous kdb */

        pKDBInfo->dwKdbNum = 0;
        pKDBInfo->pKDBLoadData = wOldKDBLoadCallback;

        if (bOldKDBLoaded) {

            WLOG5(fprintf(pLogFile5, "ET9KDB_Validate, restoring = %8x\n", dwOldKdbNum);)

            __KDBLoadPage(pKDBInfo, dwOldKdbNum, wOldPageNum, NULL);
        }

        /* done*/

        return eStatus;
    }
}

/*---------------------------------------------------------------------------*/
/**
 * @brief Retrieves version information for the active keyboard.
 *
 * @param[in]     pKDBInfo      Pointer to the Keyboard Input Module Information Data Structure (ET9KDBInfo).
 * @param[out]    psKDBVerBuf   Pointer to a buffer to which the Keyboard Input Module will write the version information. The buffer should be at least ET9MAXVERSIONSTR characters in length.
 * @param[in]     wBufMaxSize   Length in characters of the buffer pointed to by psKDBVerBuf.
 * @param[out]    pwBufSize     Pointer to a value indicating the length in characters of the version information written to psKDBVerBuf.
 *
 * @retval ET9STATUS_NONE               Function call was handled successfully.
 * @retval ET9STATUS_INVALID_MEMORY     At least one pointer passed as a function argument was set to null.
 * @retval ET9STATUS_NO_INIT            The Keyboard Input Module has not been initialized. Call ET9KDB_Init().
 * @retval ET9STATUS_NO_MEMORY          The buffer pointed to by psKDBVerBuf is too small to store the version information.
 * @retval ET9STATUS_READ_DB_FAIL       The XT9 core cannot read data from the keyboard. This typically indicates a problem with the ET9KDBREADCALLBACK() function you have implemented.
 *
 * @remarks The Keyboard Input Module returns this information as a non-null-terminated string. You can use pwBufSize to get the length of the string.
 * The version information is formatted as <tt>ET9 KDB Taa.bb Lcc.dd.ee Vff.gg</tt>, where:<br>
 * \c aa = Database type.<br>
 * \c bb = Database layout version number.<br>
 * \c cc = Primary keyboard ID.<br>
 * \c dd = Secondary keyboard ID.<br>
 * \c ee = Symbol class.<br>
 * \c ff = Major version information.<br>
 * \c gg = Minor version information.<br>
 * Each letter represents one hexadecimal character. Following is an example of the version information for a keyboard:<br>
 * <tt>ET9 KDB T03.03 LF0.01.0F V02.00</tt>.
 */

ET9STATUS ET9FARCALL ET9KDB_GetKdbVersion(ET9KDBInfo    * const pKDBInfo,
                                          ET9SYMB       * const psKDBVerBuf,
                                          const ET9U16          wBufMaxSize,
                                          ET9U16        * const pwBufSize)
{
    ET9STATUS eStatus;

    WLOG5(fprintf(pLogFile5, "ET9KDB_GetKdbVersion, pKDBInfo = %p\n", pKDBInfo);)

    if (pwBufSize) {
        *pwBufSize = 0;
    }

    eStatus = __ET9KDB_BasicValidityCheck(pKDBInfo, 1);

    if (eStatus) {
        return eStatus;
    }

    if (!psKDBVerBuf || !pwBufSize) {
        return ET9STATUS_INVALID_MEMORY;
    }

    if (wBufMaxSize < ET9MAXVERSIONSTR) {
        return ET9STATUS_NO_MEMORY;
    }

    eStatus = __GetKdbVersion_Generic(pKDBInfo, psKDBVerBuf, pwBufSize);

    return eStatus;
}

/*---------------------------------------------------------------------------*/
/**
 * @brief Informs the Keyboard Input Module that the user has entered a symbol.
 *
 * @param[in]     pKDBInfo          Pointer to the Keyboard Input Module Information Data Structure (ET9KDBInfo).
 * @param[in]     sSymbol           Symbol to be searched for by the Keyboard Input Module.
 * @param[in]     dwTimeMS          Timing info in MS. Optional, can be 0 to indicate no timing available.
 * @param[in]     bCurrIndexInList  0-based index value that indicates which word in the selection list is currently selected. XT9 locks this word before it adds the specified input value.<br>
 *                                  If there is no current input sequence, set the value of this parameter to
 *                                  ET9_NO_ACTIVE_INDEX.<br>
 *                                  If the integration layer passes as an invalid argument for this parameter, XT9 will
 *                                  still add the new input, but it will not lock the word. Also, it will not return an error status.
 * @param[out]    psFunctionKey     Pointer to a buffer where the Keyboard Input Module stores information about the tapped/pressed key if it is a function key requiring action by the integration layer.
 *                                  (for example, changing the shift state or input mode).<br>
 *                                  If the key that was pressed or tapped is not a function key, XT9 sets the value to 0.
 * @param[out]    bInitialSymCheck  Specifes whether matching is performed against the first symbol assigned to each key or ALL symbols assigned to each key. Valid values are:<br>
 *                                  <ul><li>TRUE - Initial symbol
 *                                      <li>FALSE - All symbols</ul>
 *
 * @retval ET9STATUS_NONE               Function call was handled successfully.
 * @retval ET9STATUS_INVALID_MEMORY     At least one pointer passed as a function argument was set to null.
 * @retval ET9STATUS_NO_INIT            The Keyboard Input Module has not been initialized. Call ET9KDB_Init().
 *
 * @remarks Before calling this function, the integration layer must have previously initialized the Keyboard Input Module by calling ET9KDB_Init().
 * When the integration layer calls ET9KDB_ProcessKeyBySymbol, the Keyboard Input Module stores information about the key that was pressed/tapped in the
 * instance of ET9WordSymbInfo pointed to by pWordSymbInfo.
 *
 */

ET9STATUS ET9FARCALL ET9KDB_ProcessKeyBySymbol(ET9KDBInfo      * const pKDBInfo,
                                               const ET9SYMB           sSymbol,
                                               const ET9U32            dwTimeMS,
                                               const ET9U8             bCurrIndexInList,
                                               ET9SYMB         * const psFunctionKey,
                                               const ET9BOOL           bInitialSymCheck)
{
    ET9STATUS   eStatus;
    ET9U8       byRegionalKey;
    ET9U16      wKeyIndex;
    ET9Region   KeyRegion;

    eStatus = __ET9KDB_BasicValidityCheck(pKDBInfo, 1);

    if (eStatus) {
        return eStatus;
    }

    if (!psFunctionKey) {
        return ET9STATUS_INVALID_MEMORY;
    }

    /* first find the right key index */

    {
        ET9WordSymbInfo * const pWordSymbInfo = pKDBInfo->Private.pWordSymbInfo;

        ET9SYMB sLower = _ET9SymToLower(sSymbol, pWordSymbInfo->Private.dwLocale);

        eStatus = _ET9KDB_FindSymbol(pKDBInfo,
                                     sLower,
                                     pKDBInfo->dwFirstKdbNum,
                                     pKDBInfo->wFirstPageNum,
                                     &byRegionalKey,
                                     &wKeyIndex,
                                     &KeyRegion,
                                     bInitialSymCheck);

        if (eStatus) {

            ET9SYMB sUpper = _ET9SymToUpper(sSymbol, pWordSymbInfo->Private.dwLocale);

            if (sUpper != sLower) {

                eStatus = _ET9KDB_FindSymbol(pKDBInfo,
                                             sUpper,
                                             pKDBInfo->dwFirstKdbNum,
                                             pKDBInfo->wFirstPageNum,
                                             &byRegionalKey,
                                             &wKeyIndex,
                                             &KeyRegion,
                                             bInitialSymCheck);
            }
        }

        if (eStatus && _ET9KDBSecondKDBSupported(pKDBInfo)) {

            eStatus = _ET9KDB_FindSymbol(pKDBInfo,
                                         sLower,
                                         pKDBInfo->dwSecondKdbNum,
                                         pKDBInfo->wSecondPageNum,
                                         &byRegionalKey,
                                         &wKeyIndex,
                                         &KeyRegion,
                                         bInitialSymCheck);

            if (eStatus) {

                ET9SYMB sUpper = _ET9SymToUpper(sSymbol, pWordSymbInfo->Private.dwLocale);

                if (sUpper != sLower) {

                    eStatus = _ET9KDB_FindSymbol(pKDBInfo,
                                                 sUpper,
                                                 pKDBInfo->dwSecondKdbNum,
                                                 pKDBInfo->wSecondPageNum,
                                                 &byRegionalKey,
                                                 &wKeyIndex,
                                                 &KeyRegion,
                                                 bInitialSymCheck);
                }
            }
        }

    }

    if (eStatus) {
        return eStatus;
    }

    /* now do process key */

    return ET9KDB_ProcessKey(pKDBInfo, wKeyIndex, dwTimeMS, bCurrIndexInList, psFunctionKey);
}

/*---------------------------------------------------------------------------*/
/**
 * @brief Informs the Keyboard Input Module that the user has entered key-based input.
 *
 * @param[in]     pKDBInfo          Pointer to the Keyboard Input Module Information Data Structure (ET9KDBInfo).
 * @param[in]     wKeyIndex         Index value of the key that was tapped or pressed. Key index values are specified in the key-mapping file you received when you ordered a keyboard.
 * @param[in]     dwTimeMS          Timing info in MS. Optional, can be 0 to indicate no timing available.
 * @param[in]     bCurrIndexInList  0-based index value that indicates which word in the selection list is currently selected. XT9 locks this word before it adds the specified input value.<br>
 *                                  If there is no current input sequence, set the value of this parameter to
 *                                  ET9_NO_ACTIVE_INDEX.<br>
 *                                  If the integration layer passes as an invalid argument for this parameter, XT9 will
 *                                  still add the new input, but it will not lock the word. Also, it will not return an error status.
 * @param[out]    psFunctionKey     Pointer to a buffer where the Keyboard Input Module stores information about the tapped/pressed key if it is a function key requiring action by the integration layer (for example, changing the shift state or input mode).<br>
 *                                  Function key values are part of the ET9KDBKEYDEF enumeration.<br>
 *                                  If the key that was pressed or tapped is not a function key, XT9 sets the value to 0.
 *
 * @retval ET9STATUS_NONE               Function call was handled successfully.
 * @retval ET9STATUS_INVALID_MEMORY     At least one pointer passed as a function argument was set to null.
 * @retval ET9STATUS_NO_INIT            The Keyboard Input Module has not been initialized. Call ET9KDB_Init().
 * @retval ET9STATUS_ERROR              General error status.
 * @retval ET9STATUS_OUT_OF_RANGE       The value specified by wKeyIndex is not valid.
 * @retval ET9STATUS_FULL               The active word is already the maximum size allowed (ET9MAXWORDSIZE).
 * @retval ET9STATUS_READ_DB_FAIL       The XT9 core cannot read data from the keyboard. This typically indicates a problem with the ET9KDBREADCALLBACK() function you have implemented.
 *
 * @remarks This function applies to either ambiguous or multitap input. Before calling this function, the integration.
 * layer must have previously initialized the Keyboard Input Module by calling ET9KDB_Init(). When the integration layer
 * calls ET9KDB_ProcessKey, the Keyboard Input Module stores information about the key that was pressed/tapped in the
 * instance of ET9WordSymbInfo pointed to by pWordSymbInfo.
 *
 */

ET9STATUS ET9FARCALL ET9KDB_ProcessKey(ET9KDBInfo      * const pKDBInfo,
                                       const ET9U16            wKeyIndex,
                                       const ET9U32            dwTimeMS,
                                       const ET9U8             bCurrIndexInList,
                                       ET9SYMB         * const psFunctionKey)
{
    ET9STATUS    eStatus;

    ET9_UNUSED(dwTimeMS);

    WLOG5(fprintf(pLogFile5, "\nET9KDB_ProcessKey, pKDBInfo = %p\n", pKDBInfo);)

    eStatus = __ET9KDB_BasicValidityCheck(pKDBInfo, 1);

    if (eStatus) {
        return eStatus;
    }

    if (!psFunctionKey) {
        return ET9STATUS_INVALID_MEMORY;
    }

    {
        ET9WordSymbInfo * const pWordSymbInfo = pKDBInfo->Private.pWordSymbInfo;

        __FilterSymbReset(pKDBInfo);

        pKDBInfo->Private.sKdbAction.bIsKeyAction = 1;
        pKDBInfo->Private.sKdbAction.bCurrIndexInList = bCurrIndexInList;
        pKDBInfo->Private.sKdbAction.bShiftState = ET9SHIFT_MODE(pWordSymbInfo->dwStateBits) ? 1 : 0;
        pKDBInfo->Private.sKdbAction.u.keyAction.wKeyIndex = wKeyIndex;

        *psFunctionKey = 0;

        /* modes */

        if (ET9_KDB_AMBIGUOUS_MODE(pKDBInfo->dwStateBits)) {

            /* state changes */

            pKDBInfo->dwStateBits &= ~ET9_KDB_INSERT_MASK;

            /* process KDBs */

            eStatus = __ProcessAmbigKey_ActiveKdbs(pKDBInfo, wKeyIndex, psFunctionKey, bCurrIndexInList, LOADSYMBACTION_NEW);
        }
        else if (ET9_KDB_MULTITAP_MODE(pKDBInfo->dwStateBits)) {

            /* if insert mask is on and this is a new key, unshift. */

            if (ET9_KDB_INSERT_MODE(pKDBInfo->dwStateBits) && wKeyIndex != pKDBInfo->Private.wMTLastInput) {
                pWordSymbInfo->dwStateBits &= ~ET9STATE_SHIFT_MASK;
                pKDBInfo->Private.sKdbAction.bShiftState = 0;
            }

            /* process KDBs */

            eStatus = __ProcessMultitapKey_ActiveKdbs(pKDBInfo, wKeyIndex, psFunctionKey, bCurrIndexInList, LOADSYMBACTION_NEW);
        }
        else {
            return ET9STATUS_KDB_BAD_INPUT_MODE;
        }

        /* error above? */

        if (eStatus) {
            return eStatus;
        }

        if (!*psFunctionKey) {
            _ET9TrackInputEvents(pWordSymbInfo, ET9InputEvent_add);
        }

        /* shut off shift bit */

        if (!ET9_KDB_INSERT_MODE(pKDBInfo->dwStateBits) && !*psFunctionKey) {
            pWordSymbInfo->dwStateBits &= ~ET9STATE_SHIFT_MASK;
        }

        /* magic string? */

        if (pWordSymbInfo->bNumSymbs == ET9MAXLDBWORDSIZE) {

            if (_ET9IsMagicStringKey(pWordSymbInfo)) {

                eStatus = ET9KDB_GetKdbVersion(pKDBInfo,
                                               pWordSymbInfo->Private.szIDBVersion,
                                               ET9MAXVERSIONSTR,
                                               &pWordSymbInfo->Private.wIDBVersionStrSize);

                if (eStatus) {
                    return eStatus;
                }
            }
        }

        /* mark valid if not a function key (assign checksum) */

        if (!*psFunctionKey && pKDBInfo->Private.pFilterSymb) {
            pKDBInfo->Private.sKdbAction.dwCurrChecksum = __CalculateLastWordSymbChecksum(pWordSymbInfo);
        }

        /* turn off correction inhibit */

        if (!*psFunctionKey) {
            pWordSymbInfo->Private.bRequiredInhibitCorrection = 0;  /* not the override */
        }

        /* done */

        return ET9STATUS_NONE;
    }
}

/*---------------------------------------------------------------------------*/
/**
 * @brief Informs the Keyboard Input Module that the user has entered tap-based input.
 *
 * @param[in]     pKDBInfo          Pointer to the Keyboard Input Module Information Data Structure (ET9KDBInfo).
 * @param[in]     wX                Horizontal (x) coordinate of the tap.
 * @param[in]     wY                Vertical (y) coordinate of the tap.
 * @param[in]     dwTimeMS          Timing info in MS. Optional, can be 0 to indicate no timing available.
 * @param[in]     bCurrIndexInList  0-based index value that indicates which word in the selection list is currently selected. XT9 locks this word before it adds the specified input value.<br>
 *                                  If there is no current input sequence, set the value of this parameter to
 *                                  ET9_NO_ACTIVE_INDEX.<br>
 *                                  If the integration layer passes as an invalid argument for this parameter, XT9 will
 *                                  still add the new input, but it will not lock the word. Also, it will not return an error status.
 * @param[out]    psFunctionKey     Pointer to a buffer where the Keyboard Input Module stores information about the tapped/pressed key if it is a function key requiring action by the integration layer (for example, changing the shift state or input mode).<br>
 *                                  Function key values are part of the ET9KDBKEYDEF enumeration.<br>
 *                                  If the key that was pressed or tapped is not a function key, XT9 sets the value to 0.
 *
 * @retval ET9STATUS_NONE               Function call was handled successfully.
 * @retval ET9STATUS_INVALID_MEMORY     At least one pointer passed as a function argument was set to null.
 * @retval ET9STATUS_NO_INIT            The Keyboard Input Module has not been initialized. Call ET9KDB_Init().
 * @retval ET9STATUS_ERROR              General error status.
 * @retval ET9STATUS_KDB_OUT_OF_RANGE   The location of the tap (specified by the values of wX and wY) are outside the valid range for the active keyboard's current page.
 * @retval ET9STATUS_FULL               The active word is already the maximum size allowed (ET9MAXWORDSIZE).
 * @retval ET9STATUS_READ_DB_FAIL       The XT9 core cannot read data from the keyboard. This typically indicates a problem with the ET9KDBREADCALLBACK() function you have implemented.
 * @retval ET9STATUS_NO_KEY             No keys are associated with the current page of the active keyboard.
 *
 * @remarks This function applies to either ambiguous or multitap input. Before calling this function, the integration layer must have previously initialized the Keyboard Input Module by calling ET9KDB_Init().
 * When the integration layer calls ET9KDB_ProcessTap, the Keyboard Input Module stores information about the key that was pressed/tapped in the
 * instance of ET9WordSymbInfo pointed to by pWordSymbInfo.
 *
 */

ET9STATUS ET9FARCALL ET9KDB_ProcessTap(ET9KDBInfo      * const pKDBInfo,
                                       const ET9U16            wX,
                                       const ET9U16            wY,
                                       const ET9U32            dwTimeMS,
                                       const ET9U8             bCurrIndexInList,
                                       ET9SYMB         * const psFunctionKey)
{
    ET9STATUS    eStatus;

    ET9_UNUSED(dwTimeMS);

    WLOG5(fprintf(pLogFile5, "\nET9KDB_ProcessTap, pKDBInfo = %p\n", pKDBInfo);)

    eStatus = __ET9KDB_BasicValidityCheck(pKDBInfo, 1);

    if (eStatus) {
        return eStatus;
    }

    if (!psFunctionKey) {
        return ET9STATUS_INVALID_MEMORY;
    }

    {
        ET9DirectedPos sDirectedPos;

        __InitDirectedPos(&sDirectedPos);

        sDirectedPos.sPos.nX = (ET9UINT)__ScaleCoordinateToKdbX(pKDBInfo, wX);
        sDirectedPos.sPos.nY = (ET9UINT)__ScaleCoordinateToKdbY(pKDBInfo, wY);

        eStatus = __ProcessTap(pKDBInfo, &sDirectedPos, bCurrIndexInList, psFunctionKey);
    }

    if (eStatus) {
        return eStatus;
    }

    if (!*psFunctionKey) {
        _ET9TrackInputEvents(pKDBInfo->Private.pWordSymbInfo, ET9InputEvent_add);
    }

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                              /                                 
 *
 *                                                                       
 *                                               
 *                                             
 *                                         
 *                                                                              
 *                                                           
 *                                                             
 *                                                                                                               
 *
 *                                                                    
 */

ET9STATUS ET9FARCALL _ET9KDB_FindSymbol(ET9KDBInfo          * const pKDBInfo,
                                        const ET9SYMB               sSymbol,
                                        const ET9U32                dwKdbNum,
                                        const ET9U16                wPageNum,
                                        ET9U8               * const pbyRegionalKey,
                                        ET9U16              * const pwKeyIndex,
                                        ET9Region           * const pKeyRegion,
                                        const ET9BOOL               bInitialSymCheck)
{
    ET9STATUS eStatus;

    WLOG5(fprintf(pLogFile5, "_ET9KDB_FindSymbol, pKDBInfo = %p\n", pKDBInfo);)

    eStatus = __ET9KDB_BasicValidityCheck(pKDBInfo, 1);

    if (eStatus) {
        return eStatus;
    }

    if (!pbyRegionalKey || ! pwKeyIndex || !pKeyRegion) {
        return ET9STATUS_INVALID_MEMORY;
    }

    eStatus = __KDBLoadPage(pKDBInfo, dwKdbNum, wPageNum, NULL);

    if (eStatus) {
        return eStatus;
    }

    *pwKeyIndex = ET9UNDEFINEDKEYVALUE;

    eStatus = __FindSymbol_Generic(pKDBInfo, sSymbol, pbyRegionalKey, pwKeyIndex, pKeyRegion, bInitialSymCheck);

    if (*pwKeyIndex == ET9UNDEFINEDKEYVALUE) {
        return ET9STATUS_NO_KEY;
    }

    if (eStatus) {
        return eStatus;
    }

    /* scale to integration space */

    pKeyRegion->wLeft =   (ET9U16)__ScaleCoordinateToIntegrationX(pKDBInfo, pKeyRegion->wLeft);
    pKeyRegion->wRight =  (ET9U16)__ScaleCoordinateToIntegrationX(pKDBInfo, pKeyRegion->wRight);
    pKeyRegion->wTop =    (ET9U16)__ScaleCoordinateToIntegrationY(pKDBInfo, pKeyRegion->wTop);
    pKeyRegion->wBottom = (ET9U16)__ScaleCoordinateToIntegrationY(pKDBInfo, pKeyRegion->wBottom);

    /* done */

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/**
 * @brief Indicates the expiration of a timer requested by the Keyboard Input Module.
 *
 * @param[in]     pKDBInfo          Pointer to the Keyboard Input Module Information Data Structure (ET9KDBInfo).
 *
 * @retval ET9STATUS_NONE           Function call was handled successfully.
 * @retval ET9STATUS_INVALID_MEMORY At least one pointer passed as a function argument was set to null.
 * @retval ET9STATUS_NO_INIT        The Keyboard Input Module has not been initialized. Call ET9KDB_Init().
 *
 * @remarks The timer is used when the Keyboard Input Module is in Multitap mode and a multitap sequence is active. When the integration layer calls ET9KDB_TimeOut,
 * XT9 assumes the current character is the desired one, and the multitap sequence becomes inactive.
 *
 * @note If the integration layer has not been rebuilding the selection list with each key press or tap during the active multitap sequence, it should do so once it reports the expiration of a timer.
 */

ET9STATUS ET9FARCALL ET9KDB_TimeOut(ET9KDBInfo * const pKDBInfo)
{
    ET9STATUS eStatus;

    eStatus = __ET9KDB_BasicValidityCheck(pKDBInfo, 1);

    if (eStatus) {
        return eStatus;
    }

    pKDBInfo->Private.pWordSymbInfo->dwStateBits &= ~ET9STATE_SHIFT_MASK;
    pKDBInfo->dwStateBits &= ~ET9_KDB_INSERT_MASK;

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/**
 * @brief Sets the Keyboard Input Module to Ambiguous mode.
 *
 * @param[in]     pKDBInfo          Pointer to the Keyboard Input Module Information Data Structure (ET9KDBInfo).
 * @param[in]     wFirstPageNum     Current page number of the first language keyboard. Valid values range from 0 to the maximum number of keyboard pages (indicated in the KDB text file) minus 1.<br>
 *                                  Because ambiguous text entry and multitap text entry typically require different
 *                                  keyboards, XT9 allows the integration layer to set a new keyboard page when setting
 *                                  the Keyboard Input Module to Ambiguous mode.
 * @param[in]     wSecondPageNum    Current page number of the second language keyboard. Set this parameter to NULL if you are not implementing the bilingual feature.<br>
 *                                  Because ambiguous text entry and multitap text entry typically require different
 *                                  keyboards, XT9 allows the integration layer to set a new keyboard page when setting
 *                                  the Keyboard Input Module to Ambiguous mode.
 *
 * @retval ET9STATUS_NONE               Function call was handled successfully.
 * @retval ET9STATUS_INVALID_MEMORY     At least one pointer passed as a function argument was set to null.
 * @retval ET9STATUS_NO_INIT            The Keyboard Input Module has not been initialized. Call ET9KDB_Init().
 * @retval ET9STATUS_INVALID_KDB        The page specified by wPageNum is not valid for the active keyboard.
 * @retval ET9STATUS_READ_DB_FAIL       The XT9 core cannot read data from the keyboard. This typically indicates a problem with the ET9KDBREADCALLBACK() function you have implemented.
 * @retval ET9STATUS_NO_KEY             No keys are associated with the keyboard page specified by wPageNum.
 *
 * @remarks This text-entry mode is designed to support keypads in which a single key represents multiple characters.
 * In this mode, the user presses a key once for each character, and XT9 determines which character is most likely desired.<br>
 * To set the Keyboard Input Module to Multitap mode, in which the user repeatedly presses a key to cycle to the desired
 * character, the integration layer calls ET9KDB_SetMultiTapMode().<br>
 * When the integration layer switches text-entry modes (by calling either ET9KDB_SetMultiTapMode() or ET9KDB_SetAmbigMode),
 * the Keyboard Input Module checks whether there is an active word. If there is, the module locks the default active
 * word before switching modes. This ensures the active word is not inadvertently changed by a mode change.<br>
 * By default, when the integration layer initializes the Keyboard Input Module, it is set to Ambiguous mode. If you do
 * not use multitap text entry in your XT9 implementation, it will not be necessary to call this function, as the module
 * will always be in Ambiguous mode.
 */

ET9STATUS ET9FARCALL ET9KDB_SetAmbigMode(ET9KDBInfo      * const pKDBInfo,
                                         const ET9U16            wFirstPageNum,
                                         const ET9U16            wSecondPageNum)
{
    ET9STATUS eStatus;

    eStatus = __ET9KDB_BasicValidityCheck(pKDBInfo, 1);

    if (eStatus) {
        return eStatus;
    }

    eStatus = ET9KDB_SetPageNum(pKDBInfo, wFirstPageNum, wSecondPageNum);

    if (eStatus) {
        return eStatus;
    }

    pKDBInfo->dwStateBits &= ~(ET9_KDB_MULTITAP_MODE_MASK);
    pKDBInfo->dwStateBits |= ET9_KDB_AMBIGUOUS_MODE_MASK;

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/**
 * @brief Sets the Keyboard Input Module to Multitap mode.
 *
 * @param[in]     pKDBInfo          Pointer to the Keyboard Input Module Information Data Structure (ET9KDBInfo).
 * @param[in]     wFirstPageNum     Current page number of the first language keyboard. Valid values range from 0 to the maximum number of keyboard pages (indicated in the KDB text file).<br>
 *                                  Because ambiguous text entry and multitap text entry typically require different
 *                                  keyboards, XT9 allows the integration layer to set a new keyboard page when setting
 *                                  the Keyboard Input Module to Multitap mode.<br>
 *                                  To keep the current keyboard page, set this value to ET9KDBInfo.wPageNum.
 * @param[in]     wSecondPageNum    Current page number of the second language keyboard. Set this parameter to NULL if you are not implementing the bilingual feature.<br>
 *                                  Because ambiguous text entry and multitap text entry typically require different
 *                                  keyboards, XT9 allows the integration layer to set a new keyboard page when setting
 *                                  the Keyboard Input Module to Multitap mode.<br>
 *                                  To keep the current keyboard page, set this value to ET9KDBInfo.wPageNum.
 *
 * @retval ET9STATUS_NONE               Function call was handled successfully.
 * @retval ET9STATUS_INVALID_MEMORY     At least one pointer passed as a function argument was set to null.
 * @retval ET9STATUS_NO_INIT            The Keyboard Input Module has not been initialized. Call ET9KDB_Init().
 * @retval ET9STATUS_INVALID_KDB_PAGE   The page specified by wPageNum is not valid for the keyboard specified by wPageNum.
 * @retval ET9STATUS_READ_DB_FAIL       The XT9 core cannot read data from the keyboard. This typically indicates a problem with the ET9KDBREADCALLBACK() function you have implemented.
 * @retval ET9STATUS_NO_KEY             No keys are associated with the keyboard page specified by wPageNum.
 *
 * @see ET9KDB_SetDiscreteMode(), ET9KDB_SetRegionalMode()
 *
 * @remarks This text-entry mode is designed to support keypads in which a single key represents multiple characters.
 * In this mode, the user repeatedly presses a key to cycle around to the desired character.<br>
 * To set the Keyboard Input Module to Ambiguous mode, in which the user presses a key once for each character, and XT9
 * determines which character is most likely desired, the integration layer calls ET9KDB_SetAmbigMode.<br>
 * When the integration layer switches text-entry modes (by calling either ET9KDB_SetAmbigMode() or ET9KDB_SetMultiTapMode),
 * the Keyboard Input Module checks whether there is an active word. If there is, the module locks the default active
 * word before switching modes. This ensures the active word is not inadvertently changed by a mode change.<br>
 * By default, when the integration layer initializes the Keyboard Input Module, it is set to Ambiguous mode.
 */

ET9STATUS ET9FARCALL ET9KDB_SetMultiTapMode(ET9KDBInfo      * const pKDBInfo,
                                            const ET9U16            wFirstPageNum,
                                            const ET9U16            wSecondPageNum)
{
    ET9STATUS eStatus;

    eStatus = __ET9KDB_BasicValidityCheck(pKDBInfo, 1);

    if (eStatus) {
        return eStatus;
    }

    eStatus = ET9KDB_SetPageNum(pKDBInfo, wFirstPageNum, wSecondPageNum);

    if (eStatus) {
        return eStatus;
    }

    pKDBInfo->dwStateBits &= ~(ET9_KDB_AMBIGUOUS_MODE_MASK);
    pKDBInfo->dwStateBits |= ET9_KDB_MULTITAP_MODE_MASK;

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                     
 *
 *                                                                            
 *                                                
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __ET9KDB_BuildSavedWord(ET9KDBInfo        * const pKDBInfo,
                                                      ET9SavedInputWord * const pSavedWord)
{
    ET9WordSymbInfo * const pWordSymbInfo = pKDBInfo->Private.pWordSymbInfo;

    ET9SavedInputWords  * const pSavedInputWords = &pWordSymbInfo->Private.sSavedInputWords;

    ET9STATUS                   eStatus = ET9STATUS_NONE;
    ET9U16                      wCount;
    ET9SYMB                     sFunctionKey;
    ET9SavedInputInfo           *pSavedInfo;

    const ET9U32 dwOldFirstKdbNum           = pKDBInfo->dwFirstKdbNum;
    const ET9U16 wOldFirstPageNum           = pKDBInfo->wFirstPageNum;
    const ET9U32 dwOldSecondKdbNum          = pKDBInfo->dwSecondKdbNum;
    const ET9U16 wOldSecondPageNum          = pKDBInfo->wSecondPageNum;
    const ET9U16 wOldwLayoutOffsetX         = pKDBInfo->Private.wLayoutOffsetX;
    const ET9U16 wOldwLayoutOffsetY         = pKDBInfo->Private.wLayoutOffsetY;
    const ET9U16 wOldScaleToLayoutWidth     = pKDBInfo->Private.wScaleToLayoutWidth;
    const ET9U16 wOldScaleToLayoutHeight    = pKDBInfo->Private.wScaleToLayoutHeight;

    ET9Assert(pKDBInfo);
    ET9Assert(pSavedWord);
    ET9Assert(pSavedWord->wStorePos != UNDEFINED_STORE_INDEX);
    ET9Assert(pSavedWord->wStorePos < ET9SAVEINPUTSTORESIZE);

    WLOG5(fprintf(pLogFile5, "__ET9KDB_BuildSavedWord, kdb = %08x, page = %d, kdbinfo = %p\n", pKDBInfo->dwFirstKdbNum, pKDBInfo->wFirstPageNum, pKDBInfo);)

    /* run the input data */

    ET9ClearAllSymbs(pWordSymbInfo);

    pWordSymbInfo->Private.bSwitchLanguage = pSavedWord->bSwitchLanguage;

    pSavedInfo = &pSavedInputWords->sInputs[pSavedWord->wStorePos];

    {
        ET9STATUS wKdbStatus;

        wKdbStatus = ET9KDB_SetKeyboardSize(pKDBInfo, 0, 0);

        if (wKdbStatus) {
            ET9Assert(0);
            return wKdbStatus;
        }

        wKdbStatus = ET9KDB_SetKeyboardOffset(pKDBInfo, 0, 0);

        if (wKdbStatus) {
            ET9Assert(0);
            return wKdbStatus;
        }
    }

    for (wCount = pSavedWord->wInputLen; wCount; --wCount, ++pSavedInfo) {

        ET9SavedInputSymb const * const pSavedSymb = &pSavedInfo->u.sInputSymb;

        ET9BOOL bUsedExplicit = 0;
        ET9SymbInfo * pSymbInfo = &pWordSymbInfo->SymbsInfo[pWordSymbInfo->bNumSymbs];

        ET9Assert(pSavedInfo->eKind == _ET9_SavedInputInfoKind_symb);

        /* handle KDB switch */

        if ((pSavedSymb->dwKdb1 || pSavedSymb->dwKdb2) &&
            (pKDBInfo->dwFirstKdbNum != pSavedSymb->dwKdb1 ||
             pKDBInfo->wFirstPageNum != pSavedSymb->wPage1 ||
             pKDBInfo->dwSecondKdbNum != pSavedSymb->dwKdb2 ||
             pKDBInfo->wSecondPageNum != pSavedSymb->wPage2)) {

            ET9STATUS wKdbStatus;

            WLOG5(fprintf(pLogFile5, "__ET9KDB_BuildSavedWord, changing KDB setting\n");)

            wKdbStatus = ET9KDB_SetKdbNum(pKDBInfo,
                                          pSavedSymb->dwKdb1,
                                          pSavedSymb->wPage1,
                                          pSavedSymb->dwKdb2,
                                          pSavedSymb->wPage2);

            if (wKdbStatus) {
                (void)ET9KDB_SetKeyboardOffset(pKDBInfo, wOldwLayoutOffsetX, wOldwLayoutOffsetY);
                (void)ET9KDB_SetKeyboardSize(pKDBInfo, wOldScaleToLayoutWidth, wOldScaleToLayoutHeight);
                (void)ET9KDB_SetKdbNum(pKDBInfo, dwOldFirstKdbNum, wOldFirstPageNum, dwOldSecondKdbNum, wOldSecondPageNum);
                return wKdbStatus;
            }
        }

        if (pSavedSymb->bTraceIndex) {

            /* handle trace symbs */

            ET9U16 wExploreCount;
            ET9U16 wSavePointSymbCount;
            ET9SavedInputInfo *pExploreSavedInfo;

            ET9UINT nPointCount;
            ET9TracePoint pPoints[ET9_TRACE_MAX_POINTS];

            nPointCount = 0;
            wSavePointSymbCount = 0;
            pExploreSavedInfo = pSavedInfo + 1;

            for (wExploreCount = wCount - 1; wExploreCount; --wExploreCount, ++pExploreSavedInfo) {

                if (pExploreSavedInfo->eKind == _ET9_SavedInputInfoKind_points) {

                    ET9SavedInputPoints const * const pSavedPoints = &pExploreSavedInfo->u.sInputPoints;

                    ET9U8 bIndex;

                    for (bIndex = 0; bIndex < pSavedPoints->bCount; ++bIndex) {

                        ET9Assert(nPointCount < ET9_TRACE_MAX_POINTS);

                        pPoints[nPointCount].nX = pSavedPoints->pwX[bIndex];
                        pPoints[nPointCount].nY = pSavedPoints->pwY[bIndex];
                        ++nPointCount;
                    }

                    ++wSavePointSymbCount;
                }
                else {
                    break;
                }
            }

#ifdef ET9_DEBUGLOG5
            {
                ET9UINT nIndex;
                WLOG5(fprintf(pLogFile5, "__ET9KDB_BuildSavedWord, calling ET9KDB_ProcessTrace, nPointCount %u, ", nPointCount);)
                for (nIndex = 0; nIndex < nPointCount; ++nIndex) {
                    WLOG5(fprintf(pLogFile5, "[%u,%u]", pPoints[nIndex].nX, pPoints[nIndex].nY);)
                }
                WLOG5(fprintf(pLogFile5, "\n");)
            }
#endif

#ifdef ET9_KDB_TRACE_MODULE

            eStatus = ET9KDB_ProcessTrace(pKDBInfo,
                                          pPoints,
                                          nPointCount,
                                          NULL,
                                          ET9_NO_ACTIVE_INDEX,
                                          &sFunctionKey);

            if (eStatus) {
                WLOG5(fprintf(pLogFile5, "__ET9KDB_BuildSavedWord, process trace error (1)\n");)
                break;
            }

            /* potentially reattach some properties */

            pSymbInfo->bLocked              = pSavedSymb->bLocked;
            pSymbInfo->eShiftState          = pSavedSymb->eShiftState;
            pSymbInfo->bForcedLowercase     = pSavedSymb->bForcedLowercase;

            /* skip the point symbs */

            wCount = (ET9U16)(wCount - wSavePointSymbCount);
            pSavedInfo += wSavePointSymbCount;

#endif /* ET9_KDB_TRACE_MODULE */
        }
        else {

            /* handle tap symbs */

            pSymbInfo = &pWordSymbInfo->SymbsInfo[pWordSymbInfo->bNumSymbs];

            switch (pSavedSymb->eInputType) {

                case ET9DISCRETEKEY:
                case ET9REGIONALKEY:
                    if (pSavedSymb->wTapX != ET9UNDEFINEDTAPVALUE && pSavedSymb->wTapY != ET9UNDEFINEDTAPVALUE) {

                        WLOG5(fprintf(pLogFile5, "__ET9KDB_BuildSavedWord, calling ET9KDB_ProcessTap\n");)

                        eStatus = ET9KDB_ProcessTap(pKDBInfo,
                                                    pSavedSymb->wTapX,
                                                    pSavedSymb->wTapY,
                                                    0,
                                                    ET9_NO_ACTIVE_INDEX,
                                                    &sFunctionKey);

                        if (eStatus) {
                            WLOG5(fprintf(pLogFile5, "__ET9KDB_BuildSavedWord, ET9KDB_ProcessTap error\n");)
                        }
                    }
                    else {

                        WLOG5(fprintf(pLogFile5, "__ET9KDB_BuildSavedWord, calling ET9KDB_ProcessKey\n");)

                        eStatus = ET9KDB_ProcessKey(pKDBInfo,
                                                    pSavedSymb->wKeyIndex,
                                                    0,
                                                    ET9_NO_ACTIVE_INDEX,
                                                    &sFunctionKey);

                        if (eStatus) {
                            WLOG5(fprintf(pLogFile5, "__ET9KDB_BuildSavedWord, ET9KDB_ProcessKey error\n");)
                        }
                    }

                    if (!eStatus && (sFunctionKey ||
                                     !pWordSymbInfo->bNumSymbs ||
                                     pWordSymbInfo->SymbsInfo[pWordSymbInfo->bNumSymbs - 1].bSymbType == ET9KTFUNCTION)) {

                        WLOG5(fprintf(pLogFile5, "__ET9KDB_BuildSavedWord, got a function key\n");)

                        eStatus = ET9STATUS_ERROR;
                    }
                    break;

                case ET9HANDWRITING:
                case ET9MULTITAPKEY:
                case ET9CUSTOMSET:
                case ET9EXPLICITSYM:
                case ET9MULTISYMBEXPLICIT:
                default:

                    WLOG5(fprintf(pLogFile5, "__ET9KDB_BuildSavedWord, calling ET9AddExplicitSymb\n");)

                    eStatus = ET9AddExplicitSymb(pWordSymbInfo,
                                                 pSavedSymb->sSymb,
                                                 0,
                                                 ET9NOSHIFT,
                                                 ET9_NO_ACTIVE_INDEX);

                    bUsedExplicit = 1;

                    if (eStatus) {
                        WLOG5(fprintf(pLogFile5, "__ET9KDB_BuildSavedWord, ET9AddExplicitSymb error\n");)
                    }
                    break;
            }

            if (eStatus) {
                WLOG5(fprintf(pLogFile5, "__ET9KDB_BuildSavedWord, process error\n");)
                break;
            }

            {
                ET9Assert(pSymbInfo == &pWordSymbInfo->SymbsInfo[pWordSymbInfo->bNumSymbs - 1]);

                pSymbInfo->wTapX                = pSavedSymb->wTapX;
                pSymbInfo->wTapY                = pSavedSymb->wTapY;
                pSymbInfo->wKeyIndex            = pSavedSymb->wKeyIndex;
                pSymbInfo->wInputIndex          = pSavedSymb->bInputIndex;
                pSymbInfo->bLocked              = pSavedSymb->bLocked;
                pSymbInfo->eInputType           = pSavedSymb->eInputType;
                pSymbInfo->eShiftState          = pSavedSymb->eShiftState;
                pSymbInfo->bForcedLowercase     = pSavedSymb->bForcedLowercase;
                pSymbInfo->bTraceProbability    = pSavedSymb->bTraceProbability;
                pSymbInfo->bTraceIndex          = pSavedSymb->bTraceIndex;
                pSymbInfo->dwKdb1               = pSavedSymb->dwKdb1;
                pSymbInfo->wPage1               = pSavedSymb->wPage1;
                pSymbInfo->dwKdb2               = pSavedSymb->dwKdb2;
                pSymbInfo->wPage2               = pSavedSymb->wPage2;
                pSymbInfo->sLockedSymb          = pSavedSymb->sLockedSymb;

                if (bUsedExplicit) {

                    pSymbInfo->DataPerBaseSym[0].sChar[0] = pSavedSymb->sSymb;

                    if (pSavedSymb->eInputType != ET9MULTISYMBEXPLICIT) {

                        WLOG5(fprintf(pLogFile5, "__ET9KDB_BuildSavedWord, (explicit) will try to add upper/lower case char versions\n");)

                        if (pSymbInfo->DataPerBaseSym[0].bNumSymsToMatch < ET9MAXALTSYMBS) {

                            const ET9U8 bIndex = pSymbInfo->DataPerBaseSym[0].bNumSymsToMatch;

                            pSymbInfo->DataPerBaseSym[0].sChar[bIndex] = _ET9SymToLower(pSymbInfo->DataPerBaseSym[0].sChar[0], pWordSymbInfo->Private.dwLocale);
                            pSymbInfo->DataPerBaseSym[0].sUpperCaseChar[bIndex] = _ET9SymToUpper(pSymbInfo->DataPerBaseSym[0].sUpperCaseChar[0], pWordSymbInfo->Private.dwLocale);

                            if (pSymbInfo->DataPerBaseSym[0].sChar[bIndex] != pSymbInfo->DataPerBaseSym[0].sChar[0] ||
                                pSymbInfo->DataPerBaseSym[0].sUpperCaseChar[bIndex] != pSymbInfo->DataPerBaseSym[0].sUpperCaseChar[0]) {

                                ++pSymbInfo->DataPerBaseSym[0].bNumSymsToMatch;
                            }
                        }
                    }
                }
            }
        }
    } /* loop */

    /* restore KDB settings */

    (void)ET9KDB_SetKeyboardOffset(pKDBInfo, wOldwLayoutOffsetX, wOldwLayoutOffsetY);
    (void)ET9KDB_SetKeyboardSize(pKDBInfo, wOldScaleToLayoutWidth, wOldScaleToLayoutHeight);

    /* do not restore pWordSymbInfo->Private.bSwitchLanguage */

    if ((dwOldFirstKdbNum || dwOldSecondKdbNum) &&
        (pKDBInfo->dwFirstKdbNum != dwOldFirstKdbNum ||
         pKDBInfo->wFirstPageNum != wOldFirstPageNum ||
         pKDBInfo->dwSecondKdbNum != dwOldSecondKdbNum ||
         pKDBInfo->wSecondPageNum != wOldSecondPageNum)) {

        ET9STATUS wKdbStatus;

        WLOG5(fprintf(pLogFile5, "__ET9KDB_BuildSavedWord, restoring KDB setting\n");)

        wKdbStatus = ET9KDB_SetKdbNum(pKDBInfo,
                                      dwOldFirstKdbNum,
                                      wOldFirstPageNum,
                                      dwOldSecondKdbNum,
                                      wOldSecondPageNum);

        if (wKdbStatus) {
            return wKdbStatus;
        }
    }

    /* validate */

    if (eStatus) {
        WLOG5(fprintf(pLogFile5, "__ET9KDB_BuildSavedWord, got error %d\n", eStatus);)
        return ET9STATUS_ERROR;
    }

    /* "restore" the last known shift state (affects e.g. completions, or else the required word might not be found) */

    pWordSymbInfo->Private.eLastShiftState = pSavedWord->eLastShiftState;
    pWordSymbInfo->Private.eRequiredLastShiftState = pSavedWord->eLastShiftState;

    /* done */

    WLOG5(fprintf(pLogFile5, "__ET9KDB_BuildSavedWord, done\n");)

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/**
 * @brief Reselects a word from saved or recreated data.
 *
 * @param[in]     pKDBInfo                  Pointer to the Keyboard Input Module Information Data Structure (ET9KDBInfo).
 * @param[in]     psWord                    Word to be reselected.
 * @param[in]     wWordLen                  Length of the word to be reselected.
 *
 * @retval ET9STATUS_NONE           Function call was handled successfully.
 * @retval ET9STATUS_INVALID_MEMORY At least one pointer passed as a function argument was set to null.
 * @retval ET9STATUS_NO_INIT        The Keyboard Input Module has not been initialized. Call ET9KDB_Init().
 *
 * @remarks When the user highlights a previously accepted word, the word is reinstated from memory, or.
 * recreated, with its original input sequence and key values. The integration layer can then
 * request that XT9 build a selection list based on the data.<br>
 * This is useful in situations where a user will go back to correct or modify a word. Rather than
 * retyping an entire word, a selection list based on the original word attributes is made available.
 * The user can choose a word from the selection list or modify the reselected text to update it.
 */

ET9STATUS ET9FARCALL ET9KDB_ReselectWord(ET9KDBInfo         * const pKDBInfo,
                                         ET9SYMB      const * const psWord,
                                         const ET9U16               wWordLen)
{
    ET9STATUS       eStatus;
    ET9BOOL         bRecreateInput = 1;
    ET9U32          dwOldKdbState;
    ET9U16          wPageNum;

    WLOG5(fprintf(pLogFile5, "ET9KDB_ReselectWord\n");)

    /* validate */

    eStatus = __ET9KDB_BasicValidityCheck(pKDBInfo, 1);

    if (eStatus) {
        return eStatus;
    }

    if (!psWord) {
        return ET9STATUS_INVALID_MEMORY;
    }
    if (!wWordLen || wWordLen > ET9MAXWORDSIZE) {
        return ET9STATUS_OUT_OF_RANGE;
    }

    /* break insert mode */

    pKDBInfo->dwStateBits &= ~ET9_KDB_INSERT_MASK;

    /* reset filtering (might have to try and capture it instead) */

    __FilterSymbReset(pKDBInfo);

    /* record kdb state, and set new state */

    dwOldKdbState = pKDBInfo->dwStateBits;

    pKDBInfo->dwStateBits = (pKDBInfo->dwStateBits & ET9_KDB_DISCRETE_MASK) | ET9_KDB_AMBIGUOUS_MODE_MASK;

    /* first - look for a saved word */
    {
        ET9WordSymbInfo * const pWordSymbInfo = pKDBInfo->Private.pWordSymbInfo;

        ET9SavedInputWords  * const pSavedInputWords = &pWordSymbInfo->Private.sSavedInputWords;

        const ET9U32 dwStringHash = _ET9SymbStringCheckSum(psWord, wWordLen);

        ET9U16 wWordIndex;
        ET9U16 wWordCount;

        wWordIndex = pSavedInputWords->wCurrInputSaveIndex;

        for (wWordCount = ET9MAXSAVEINPUTWORDS;
             wWordCount;
             --wWordCount,
             wWordIndex = (ET9U16)(wWordIndex == 0 ? (ET9MAXSAVEINPUTWORDS - 1) : (wWordIndex - 1))) {

            ET9SavedInputWord * const pSavedWord = &pSavedInputWords->pSavedWords[wWordIndex];

            WLOG5(fprintf(pLogFile5, "ET9KDB_ReselectWord, trying index %d\n", wWordIndex);)

            if (pSavedWord->wStorePos == UNDEFINED_STORE_INDEX) {
                WLOG5(fprintf(pLogFile5, "ET9KDB_ReselectWord, found undefined word, break\n");)
                break;
            }

            ET9Assert(pSavedWord->wStorePos < ET9SAVEINPUTSTORESIZE);

            if (pSavedWord->dwStringHash == dwStringHash) {

                WLOG5(fprintf(pLogFile5, "ET9KDB_ReselectWord, found saved word\n");)

                if (!__ET9KDB_BuildSavedWord(pKDBInfo, pSavedWord)) {
                    bRecreateInput = 0;
                }

                break;
            }
        }
    }

    /* otherwise - set up input symbols according to the word and kdb */

    if (bRecreateInput) {

        ET9WordSymbInfo * const pWordSymbInfo = pKDBInfo->Private.pWordSymbInfo;

        ET9U16              wCount;
        ET9U16              wKeyIndex;
        ET9SYMB             sFunctionKey;
        ET9Region           sKeyRegion;
        ET9SYMB       const *psSymb;
        ET9SymbInfo         *pSymbInfo;
        ET9U8               byRegionalKey;

        WLOG5(fprintf(pLogFile5, "ET9KDB_ReselectWord, recreating word\n");)

        ET9ClearAllSymbs(pWordSymbInfo);

        psSymb = psWord;
        pSymbInfo = pWordSymbInfo->SymbsInfo;

        for (wCount = wWordLen; wCount; --wCount, ++psSymb, ++pSymbInfo) {

            const ET9SYMB sTargetSymb = *psSymb;
            const ET9SYMB sTargetSymbLC = _ET9SymToLower(sTargetSymb, pWordSymbInfo->Private.dwLocale);

            /* try adding it using current kdb, if it fails try explicit entry */

            if (pKDBInfo->dwFirstKdbNum == pKDBInfo->dwKdbNum) {
                wPageNum = pKDBInfo->wFirstPageNum;
            }
            else {
                wPageNum = pKDBInfo->wSecondPageNum;
            }

            /* first top only */

            eStatus = _ET9KDB_FindSymbol(pKDBInfo,
                                         sTargetSymbLC,
                                         pKDBInfo->dwKdbNum,
                                         wPageNum,
                                         &byRegionalKey,
                                         &wKeyIndex,
                                         &sKeyRegion,
                                         1);

            /* if not found try any */

            if (eStatus) {

                eStatus = _ET9KDB_FindSymbol(pKDBInfo,
                                             sTargetSymbLC,
                                             pKDBInfo->dwKdbNum,
                                             wPageNum,
                                             &byRegionalKey,
                                             &wKeyIndex,
                                             &sKeyRegion,
                                             0);
            }

            if (!eStatus) {

                eStatus = ET9KDB_ProcessKey(pKDBInfo, wKeyIndex, 0, ET9_NO_ACTIVE_INDEX, &sFunctionKey);

                if (!eStatus && sFunctionKey) {
                    eStatus = ET9STATUS_ERROR;
                }
                else if (!eStatus && (pWordSymbInfo->bNumSymbs &&
                                      pWordSymbInfo->SymbsInfo[pWordSymbInfo->bNumSymbs - 1].bSymbType == ET9KTFUNCTION)) {

                    WLOG5(fprintf(pLogFile5, "ET9KDB_ReselectWord, got a function key\n");)

                    --pWordSymbInfo->bNumSymbs;

                    eStatus = ET9STATUS_ERROR;
                }

            }

            if (!eStatus) {

                /* assure that "exact" is the expected char */

                ET9DataPerBaseSym * const pBaseSymb = &pWordSymbInfo->SymbsInfo[pWordSymbInfo->bNumSymbs - 1].DataPerBaseSym[0];

                if (pBaseSymb->sChar[0] != sTargetSymb && pBaseSymb->sUpperCaseChar[0] != sTargetSymb) {

                    ET9U8 bIndex;

                    for (bIndex = 1; bIndex < pBaseSymb->bNumSymsToMatch; ++bIndex) {

                        if (pBaseSymb->sChar[bIndex] == sTargetSymb || pBaseSymb->sUpperCaseChar[bIndex] == sTargetSymb) {

                            ET9SYMB sTmp;

                            sTmp = pBaseSymb->sChar[0];
                            pBaseSymb->sChar[0] = pBaseSymb->sChar[bIndex];
                            pBaseSymb->sChar[bIndex] = sTmp;

                            sTmp = pBaseSymb->sUpperCaseChar[0];
                            pBaseSymb->sUpperCaseChar[0] = pBaseSymb->sUpperCaseChar[bIndex];
                            pBaseSymb->sUpperCaseChar[bIndex] = sTmp;

                            break;
                        }
                    }
                }
            }

            if (eStatus) {

                WLOG5(fprintf(pLogFile5, "ET9KDB_ReselectWord, failed to find symbol %04x\n", _ET9SymToLower(*psSymb, pWordSymbInfo->Private.dwLocale));)

                eStatus = ET9AddExplicitSymb(pWordSymbInfo, *psSymb, 0, ET9NOSHIFT, ET9_NO_ACTIVE_INDEX);

                ET9Assert(eStatus ||
                          !pWordSymbInfo->bNumSymbs ||
                          pWordSymbInfo->SymbsInfo[pWordSymbInfo->bNumSymbs - 1].bSymbType != ET9KTFUNCTION);
            }

            if (eStatus) {

                WLOG5(fprintf(pLogFile5, "ET9KDB_ReselectWord, even explicit failed\n");)

                pKDBInfo->dwStateBits = dwOldKdbState;
                ET9ClearAllSymbs(pWordSymbInfo);
                return eStatus;
            }

            /* set shift according to word info */

            if (_ET9SymIsUpper(*psSymb, pWordSymbInfo->Private.dwLocale)) {
                pSymbInfo->eShiftState = ET9SHIFT;
            }
            else {
                pSymbInfo->eShiftState = ET9NOSHIFT;
            }
        }
    }

    /* set up the required word */

    {
        ET9WordSymbInfo * const pWordSymbInfo = pKDBInfo->Private.pWordSymbInfo;

        ET9SimpleWord * const pReqWord = &pWordSymbInfo->Private.sRequiredWord;

        _ET9InitSimpleWord(pReqWord);

        pReqWord->wLen = wWordLen;

        _ET9SymCopy(pReqWord->sString, psWord, wWordLen);

        pWordSymbInfo->Private.bRequiredLocate = 1;
        pWordSymbInfo->Private.bRequiredVerifyInput = 1;
        pWordSymbInfo->Private.bRequiredInhibitOverride = 0;
        pWordSymbInfo->Private.bRequiredInhibitCorrection = 0;
        pWordSymbInfo->Private.bRequiredHasRegionalInfo = _ET9HasRegionalInfo(pWordSymbInfo);

        _ET9InvalidateSelList(pWordSymbInfo);
    }

    /* restore kdb state */

    pKDBInfo->dwStateBits = dwOldKdbState;

    /* done */

    WLOG5(fprintf(pLogFile5, "ET9KDB_ReselectWord, input done ok\n");)

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/**
 * @brief Next diacritic state. Used for implementations of XT9 Japanese or T9 Nav Core.
 *
 * @param[in]     pKDBInfo      Pointer to the Keyboard Input Module Information Data Structure (ET9KDBInfo).
 *
 * @retval ET9STATUS_NONE           Function call was handled successfully.
 * @retval ET9STATUS_INVALID_MEMORY At least one pointer passed as a function argument was set to null.
 * @retval ET9STATUS_NO_INIT        The Keyboard Input Module has not been initialized. Call ET9KDB_Init().
 *
 */

ET9STATUS ET9FARCALL ET9KDB_NextDiacritic(ET9KDBInfo * const pKDBInfo)
{
    ET9STATUS   eStatus;
    ET9U8       bFilterCount;

    WLOG5(fprintf(pLogFile5, "\nET9KDB_NextDiacritic, pKDBInfo = %p\n", pKDBInfo);)

    eStatus = __ET9KDB_BasicValidityCheck(pKDBInfo, 1);

    if (eStatus) {
        return eStatus;
    }

    if (!pKDBInfo->Private.pWordSymbInfo->bNumSymbs) {
        return ET9STATUS_NONE;
    }

    /* turn off insert mode */

    pKDBInfo->dwStateBits &= ~ET9_KDB_INSERT_MASK;

    /* check if applicable */

    if (!pKDBInfo->Private.pFilterSymb || !pKDBInfo->Private.pFilterSymbCount || !pKDBInfo->Private.pFilterSymbNext) {
        return ET9STATUS_ERROR;
    }

    /* verify that the current symb is the last known one */

    if (!pKDBInfo->Private.sKdbAction.dwCurrChecksum ||
        pKDBInfo->Private.sKdbAction.dwCurrChecksum != __CalculateLastWordSymbChecksum(pKDBInfo->Private.pWordSymbInfo)) {

        return ET9STATUS_NONE;
    }

    /* get filter count */

    eStatus = pKDBInfo->Private.pFilterSymbCount(pKDBInfo->Private.pFilterSymbInfo, &bFilterCount);

    if (eStatus) {
        return eStatus;
    }

    if (!bFilterCount) {
        return ET9STATUS_NONE;
    }

    /* move to next state */

    {
        ET9WordSymbInfo * const pWordSymbInfo = pKDBInfo->Private.pWordSymbInfo;

        const ET9U8             bNumSymbs = pWordSymbInfo->bNumSymbs;
        ET9SymbInfo     * const pSymbInfo = pWordSymbInfo->SymbsInfo + bNumSymbs - 1;
        ET9U8                   bCount;
        const ET9U32            dwOldCheckSum = pKDBInfo->Private.sKdbAction.dwCurrChecksum;

        /* clear checksum, so we know if we picked up something at the end */

        pKDBInfo->Private.sKdbAction.dwCurrChecksum = 0;

        /* loop over the diac states */

        for (bCount = bFilterCount; bCount; --bCount) {

            WLOG5(fprintf(pLogFile5, "  bCount = %d (%d)\n", bCount, bFilterCount);)

            /* next filter setting */

            eStatus = pKDBInfo->Private.pFilterSymbNext(pKDBInfo->Private.pFilterSymbInfo);

            if (eStatus) {
                return eStatus;
            }

            /* execute filter (reload) */

            {
                ET9U16 sFunctionKey = 0;

                ET9DirectedPos sDirectedPos;

                __InitDirectedPos(&sDirectedPos);

                sDirectedPos.sPos.nX = (ET9UINT)pKDBInfo->Private.sKdbAction.u.tapAction.wX;
                sDirectedPos.sPos.nY = (ET9UINT)pKDBInfo->Private.sKdbAction.u.tapAction.wY;

                switch (pSymbInfo->eInputType)
                {
                    case ET9DISCRETEKEY:
                    case ET9REGIONALKEY:
                        if (pKDBInfo->Private.sKdbAction.bIsKeyAction) {
                            eStatus = __ProcessAmbigKey_ActiveKdbs(pKDBInfo,
                                                                   pKDBInfo->Private.sKdbAction.u.keyAction.wKeyIndex,
                                                                   &sFunctionKey,
                                                                   pKDBInfo->Private.sKdbAction.bCurrIndexInList,
                                                                   LOADSYMBACTION_RELOAD);
                        }
                        else {
                            eStatus = __ProcessAmbigTap_ActiveKdbs(pKDBInfo,
                                                                   &sDirectedPos,
                                                                   &sFunctionKey,
                                                                   pKDBInfo->Private.sKdbAction.bCurrIndexInList,
                                                                   LOADSYMBACTION_RELOAD);
                        }
                        break;
                    case ET9MULTITAPKEY:
                        if (pKDBInfo->Private.sKdbAction.bIsKeyAction) {
                            eStatus = __ProcessMultitapKey_ActiveKdbs(pKDBInfo,
                                                                      pKDBInfo->Private.sKdbAction.u.keyAction.wKeyIndex,
                                                                      &sFunctionKey,
                                                                      pKDBInfo->Private.sKdbAction.bCurrIndexInList,
                                                                      LOADSYMBACTION_RELOAD);
                        }
                        else {
                            eStatus = __ProcessMultitapTap_ActiveKdbs(pKDBInfo,
                                                                      &sDirectedPos,
                                                                      &sFunctionKey,
                                                                      pKDBInfo->Private.sKdbAction.bCurrIndexInList,
                                                                      LOADSYMBACTION_RELOAD);
                        }
                        break;
                    default:
                        return ET9STATUS_NONE;
                }

                if (eStatus) {
                    ET9Assert(0);
                    return eStatus;
                }

                ET9Assert(!sFunctionKey);
                ET9Assert(pKDBInfo->Private.pWordSymbInfo->bNumSymbs == bNumSymbs);
            }

            /* filter applicable? (change detected?) */

            {
                const ET9U32  dwNewCheckSum = __CalculateLastWordSymbChecksum(pWordSymbInfo);

                WLOG5(fprintf(pLogFile5, "  dwNewCheckSum = %d, dwOldCheckSum = %d\n", dwNewCheckSum, dwOldCheckSum);)

                /* either we find a new ok state, or we are back to the first one ok */

                if (dwNewCheckSum && (dwNewCheckSum != dwOldCheckSum || bCount == 1)) {
                    pKDBInfo->Private.sKdbAction.dwCurrChecksum = dwNewCheckSum;
                    return ET9STATUS_NONE;
                }
            }
        }
    }

    /* done - nothing happened */

    ET9Assert(pKDBInfo->Private.sKdbAction.dwCurrChecksum);

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/**
 * @brief Sets the Keyboard Input Module to regional mode.
 *
 * @param[in]     pKDBInfo      Pointer to the Keyboard Input Module Information Data Structure (ET9KDBInfo).
 *
 * @retval ET9STATUS_NONE       Function call was handled successfully.
 * @retval ET9STATUS_INVALID_MEMORY At least one pointer passed as a function argument was set to null.
 * @retval ET9STATUS_NO_INIT        The Keyboard Input Module has not been initialized. Call ET9KDB_Init().
 *
 * @see ET9KDB_SetDiscreteMode(), ET9KDB_SetMultiTapMode()
 *
 * @remarks Regional mode handles imprecise input.With imprecise input it is possible that the input provided may not have been what a person intended.
 * For example, a person may have intended to tap the "s" key on a virtual keyboard but actually tapped the "a" key. Regional mode takes
 * neighboring keys into consideration when extrapolating the best matches for the selection list.<br>
 * When text is entered through regional mode, selection list results are searched for based on the
 * specific character tapped with the stylus.
 */

ET9STATUS ET9FARCALL ET9KDB_SetRegionalMode(ET9KDBInfo * const pKDBInfo)
{
    ET9STATUS       eStatus;

    eStatus = __ET9KDB_BasicValidityCheck(pKDBInfo, 1);

    if (eStatus) {
        return eStatus;
    }

    /* set regional mask */

    pKDBInfo->dwStateBits &= ~ET9_KDB_DISCRETE_MASK;

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/**
 * @brief Sets the Keyboard Input Module to discrete mode.
 *
 * @param[in]     pKDBInfo  Pointer to the Keyboard Input Module Information Data Structure (ET9KDBInfo).
 *
 * @retval ET9STATUS_NONE           Function call was handled successfully.
 * @retval ET9STATUS_INVALID_MEMORY At least one pointer passed as a function argument was set to null.
 * @retval ET9STATUS_NO_INIT        The Keyboard Input Module has not been initialized. Call ET9KDB_Init().
 *
 * @see ET9KDB_SetRegionalMode(), ET9KDB_SetMultiTapMode()
 *
 * @remarks Discrete mode handles precise input. With precise input there is no doubt as to what input was intended.
 * For example, a person presses the "a" key on a keyboard or the "2" key on a telephone keypad. Results in the selection list are based strictly on the precise key pressed or tapped.<br>
 * When text is entered through discrete mode, selection list results are searched for based on the
 * "first" character mapped to a key. In a standard Keyboard Database, the first character is the
 * number associated with the key.<br>
 * To enable regional mode, which handles imprecise key input, the integration layer calls ET9KDB_SetRegionalMode().
 */

ET9STATUS ET9FARCALL ET9KDB_SetDiscreteMode(ET9KDBInfo * const pKDBInfo)
{
    ET9STATUS eStatus;

    eStatus = __ET9KDB_BasicValidityCheck(pKDBInfo, 1);

    if (eStatus) {
        return eStatus;
    }

    /* set regional mask */

    pKDBInfo->dwStateBits |= ET9_KDB_DISCRETE_MASK;

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/**
 * @brief Sets the convert symbol feature.
 *
 * @param[in]     pKDBInfo              Pointer to the Keyboard Input Module Information Data Structure (ET9KDBInfo).
 * @param[in]     pConvertSymb          Pointer to a ET9CONVERTSYMBCALLBACK() or NULL to disable the feature.
 * @param[out]    pConvertSymbInfo     Pointer passed back from ET9CONVERTSYMBCALLBACK().
 *
 * @retval ET9STATUS_NONE           Function call was handled successfully.
 * @retval ET9STATUS_INVALID_MEMORY At least one pointer passed as a function argument was set to null.
 * @retval ET9STATUS_NO_INIT        The Keyboard Input Module has not been initialized. Call ET9KDB_Init().
 *
 * @remarks This function controls the convert symbol feature (on-the-fly-mapping) for the Keyboard Input Module. There are partner function(s) that should be set in the lingustic engines.
 * This feature applies to what goes into the selection list as well as e.g. multitap symbols. This
 * function will be called for every symbol handled. Thus, performance-wise it's important to
 * keep the convert function fast.
 *
 * @note Nuance recommends that this function should be set consistently in order to minimize unexpected behavior.
 *
 */

ET9STATUS ET9FARCALL ET9KDB_SetConvertSymb(ET9KDBInfo                   * const pKDBInfo,
                                           const ET9CONVERTSYMBCALLBACK         pConvertSymb,
                                           void                         * const pConvertSymbInfo)
{
    ET9STATUS eStatus;

    eStatus = __ET9KDB_BasicValidityCheck(pKDBInfo, 1);

    if (eStatus) {
        return eStatus;
    }

    pKDBInfo->Private.pConvertSymb     = pConvertSymb;
    pKDBInfo->Private.pConvertSymbInfo = pConvertSymbInfo;

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/**
 * @brief Retrieves the current multitap sequence for the current key.
 *
 * @param[in]     pKDBInfo               Pointer to the Keyboard Input Module Information Data Structure (ET9KDBInfo).
 * @param[in]     psMultiTapSequenceBuf  Pointer to the multitap sequence buffer.
 * @param[in]     wBufSize               Size of the multitap sequence buffer.
 * @param[in]     pwTotalSymbs           Pointer to the total number of symbols in the multitap sequence buffer.
 * @param[in]     pbCurrSelSymbIndex     Pointer to the index of the currently selected symbol in the multitap sequence.
 *
 * @retval ET9STATUS_NONE               Function call was handled successfully.
 * @retval ET9STATUS_INVALID_MEMORY     At least one pointer passed as a function argument was set to null.
 * @retval ET9STATUS_NO_INIT            The Keyboard Input Module has not been initialized. Call ET9KDB_Init().
 * @retval ET9STATUS_INVALID_KDB_PAGE   The page number of the current keyboard page is not valid.
 *                                      If you receive this status, contact your Nuance support engineer for assistance
 *
 * @see ET9KDB_GetKdbNum(), ET9KDB_SetPageNum()
 *
 */

ET9STATUS ET9FARCALL ET9KDB_GetMultiTapSequence(ET9KDBInfo   * const pKDBInfo,
                                                ET9SYMB      * const psMultiTapSequenceBuf,
                                                const ET9U16         wBufSize,
                                                ET9U16       * const pwTotalSymbs,
                                                ET9U8        * const pbCurrSelSymbIndex)
{
    ET9STATUS   eStatus;
    ET9SYMB     *pBuffSrc;
    ET9SYMB     *pBuffDest;
    ET9INT      nTotalCounts;

    eStatus = __ET9KDB_BasicValidityCheck(pKDBInfo, 1);

    if (eStatus) {
        return eStatus;
    }

    if (!psMultiTapSequenceBuf || !pwTotalSymbs || !pbCurrSelSymbIndex) {
        return ET9STATUS_INVALID_MEMORY;
    }

    if (!wBufSize || wBufSize < ET9_KDB_MAX_MT_SYMBS) {
        return ET9STATUS_BUFFER_TOO_SMALL;
    }

    nTotalCounts = *pwTotalSymbs = pKDBInfo->Private.bMTSymbCount;
    pBuffSrc = pKDBInfo->Private.sMTSymbs;
    pBuffDest = psMultiTapSequenceBuf;

    while (nTotalCounts-- > 0) {
        *pBuffDest++ = *pBuffSrc++;
    }

    *pbCurrSelSymbIndex = pKDBInfo->Private.bMTLastSymbIndex;

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                        
 *
 *                                                                                                               
 *                                                                    
 *                                                                                    
 *
 *                                                                    
 */

static void ET9LOCALCALL __KeyAreaToKeyPoint(ET9KDBInfo             * const pKDBInfo,
                                             ET9KdbAreaInfo   const * const pArea,
                                             ET9KeyPoint            * const pPoint)
{
    pPoint->eKeyType = pArea->eKeyType;
    pPoint->eInputType = pArea->eInputType;
    pPoint->wKey = pArea->wKeyIndex;
    pPoint->sTopSymb = pArea->psChars[0];
    pPoint->nSymbCount = pArea->nCharCount;
    pPoint->psSymbs = &pArea->psChars[0];

    pPoint->nX = (ET9UINT)__ScaleCoordinateToIntegrationX(pKDBInfo, pArea->nCenterX);
    pPoint->nY = (ET9UINT)__ScaleCoordinateToIntegrationY(pKDBInfo, pArea->nCenterY);

    pPoint->sArea.wLeft   = (ET9U16)__ScaleCoordinateToIntegrationX(pKDBInfo, pArea->sRegion.wLeft);
    pPoint->sArea.wTop    = (ET9U16)__ScaleCoordinateToIntegrationY(pKDBInfo, pArea->sRegion.wTop);
    pPoint->sArea.wRight  = (ET9U16)__ScaleCoordinateToIntegrationX(pKDBInfo, pArea->sRegion.wRight);
    pPoint->sArea.wBottom = (ET9U16)__ScaleCoordinateToIntegrationY(pKDBInfo, pArea->sRegion.wBottom);
}

/*---------------------------------------------------------------------------*/
/** \ingroup et9trace
 * Get information about keys in the currently active KDB.
 * This information can be used for mainly debug purposes to draw key positions on the screen to verify key centers.
 *
 * @param[in]     pKDBInfo          Pointer to the Keyboard Input Module Information Data Structure (ET9KDBInfo).
 * @param[in,out] pPoints           Pointer to a list of key points to receive key information.
 * @param[in]     nMaxPointCount    Max number of points that the pPoints array can hold.
 * @param[out]    pnPointCount      Pointer to receive the number of points in the list.
 *
 * @return ET9STATUS_NONE on success, otherwise return ET9 error code.
 */

ET9STATUS ET9FARCALL ET9KDB_GetKeyPositions(ET9KDBInfo             * const pKDBInfo,
                                            ET9KeyPoint            * const pPoints,
                                            const ET9UINT                  nMaxPointCount,
                                            ET9UINT                * const pnPointCount)
{
    ET9STATUS    eStatus;

    WLOG5(fprintf(pLogFile5, "ET9KDB_GetKeyPositions\n");)

    eStatus = __ET9KDB_BasicValidityCheck(pKDBInfo, 1);

    if (eStatus) {
        return eStatus;
    }

    if (!pPoints || !pnPointCount) {
        return ET9STATUS_INVALID_MEMORY;
    }

    /* init */

    *pnPointCount = 0;

    if (!pKDBInfo->Private.pCurrLayoutInfo->nKeyAreaCount) {
        return ET9STATUS_NONE;
    }

    /* assure some key values */

    ET9Assert(pKDBInfo->Private.pCurrLayoutInfo->nBoxWidth);
    ET9Assert(pKDBInfo->Private.pCurrLayoutInfo->nBoxHeight);
    ET9Assert(pKDBInfo->Private.pCurrLayoutInfo->nMinKeyWidth);
    ET9Assert(pKDBInfo->Private.pCurrLayoutInfo->nMinKeyHeight);
    ET9Assert(pKDBInfo->Private.pCurrLayoutInfo->nMedianKeyWidth);
    ET9Assert(pKDBInfo->Private.pCurrLayoutInfo->nMedianKeyHeight);

    /* big enough? */

    if (nMaxPointCount < pKDBInfo->Private.pCurrLayoutInfo->nKeyAreaCount) {
        return ET9STATUS_BAD_PARAM;
    }

    /* get positions */

    {
        ET9KdbLayoutInfo * const pLayoutInfo = pKDBInfo->Private.pCurrLayoutInfo;

        ET9UINT nIndex;

        for (nIndex = 0; nIndex < pLayoutInfo->nKeyAreaCount; ++nIndex) {

            __KeyAreaToKeyPoint(pKDBInfo, &pLayoutInfo->pKeyAreas[nIndex], &pPoints[nIndex]);

            WLOG5(fprintf(pLogFile5, "  [%2u] x %3u, y %3u, key %2u, top %c\n",
                                     nIndex,
                                     pPoints[nIndex].nX,
                                     pPoints[nIndex].nY,
                                     pPoints[nIndex].wKey,
                                     (char)pPoints[nIndex].sTopSymb);)
        }

        *pnPointCount = pLayoutInfo->nKeyAreaCount;
    }

    /* done */

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/**
 * Get information about the key associated with a tap position.
 * This function can only be used with keyboards that support key regions (e.g. dynamic KDBs).
 *
 * @param[in]     pKDBInfo          Pointer to the Keyboard Input Module Information Data Structure (ET9KDBInfo).
 * @param[in]     wX                Horizontal (x) coordinate of the tap.
 * @param[in]     wY                Vertical (y) coordinate of the tap.
 * @param[in,out] pPoint            Pointer to a key point to receive key information.
 *
 * @return ET9STATUS_NONE on success, otherwise return ET9 error code.
 */

ET9STATUS ET9FARCALL ET9KDB_GetKeyPositionByTap(ET9KDBInfo             * const pKDBInfo,
                                                const ET9U16                   wX,
                                                const ET9U16                   wY,
                                                ET9KeyPoint            * const pPoint)
{
    ET9STATUS    eStatus;

    WLOG5(fprintf(pLogFile5, "ET9KDB_GetKeyPositionByTap\n");)

    eStatus = __ET9KDB_BasicValidityCheck(pKDBInfo, 1);

    if (eStatus) {
        return eStatus;
    }

    if (!pPoint ) {
        return ET9STATUS_INVALID_MEMORY;
    }

    {
        const ET9U16 wKdbX = (ET9U16)__ScaleCoordinateToKdbX(pKDBInfo, wX);
        const ET9U16 wKdbY = (ET9U16)__ScaleCoordinateToKdbY(pKDBInfo, wY);

        ET9KdbAreaInfo const * pArea = __GetKeyAreaFromTap_Generic(pKDBInfo, wKdbX, wKdbY);

        if (!pArea) {
            return ET9STATUS_NO_KEY;
        }

        __KeyAreaToKeyPoint(pKDBInfo, pArea, pPoint);
    }

    /* done */

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/** \ingroup et9trace
 * Set keyboard scaling.
 * A keyboard can be scaled if the only difference is a true scaling in x-y coordinates.
 * Coordinates must be in this new space, key position data will be returned in this space.
 * Setting scaling to zero resets it to KDB size (turns off scaling).
 *
 * @param[in]     pKDBInfo          Pointer to the Keyboard Input Module Information Data Structure (ET9KDBInfo).
 * @param[in]     wLayoutWidth      Layout width to scale to.
 * @param[in]     wLayoutHeight     Layout height to scale to.
 *
 * @return ET9STATUS_NONE on success, otherwise return ET9 error code.
 */

ET9STATUS ET9FARCALL ET9KDB_SetKeyboardSize(ET9KDBInfo          * const pKDBInfo,
                                            const ET9U16                wLayoutWidth,
                                            const ET9U16                wLayoutHeight)
{
    ET9STATUS eStatus;

    WLOG5(fprintf(pLogFile5, "ET9KDB_SetKeyboardSize, width %u, height %u\n", wLayoutWidth, wLayoutHeight);)

    eStatus = __ET9KDB_BasicValidityCheck(pKDBInfo, 1);

    if (eStatus) {
        return eStatus;
    }

    if ((wLayoutWidth && !wLayoutHeight) || (!wLayoutWidth && wLayoutHeight)) {
        return ET9STATUS_ERROR;
    }

    pKDBInfo->Private.wScaleToLayoutWidth = wLayoutWidth;
    pKDBInfo->Private.wScaleToLayoutHeight = wLayoutHeight;

    if (pKDBInfo->ET9Handle_KDB_Request) {

        ET9KDB_Request sRequest;

        sRequest.eType = ET9_KDB_REQ_LAYOUT_CHANGED;

        (void)pKDBInfo->ET9Handle_KDB_Request(pKDBInfo, NULL, &sRequest);
    }

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/**
 * Get keyboard size (scaled).
 *
 * @param[in]     pKDBInfo          Pointer to the Keyboard Input Module Information Data Structure (ET9KDBInfo).
 * @param[out]    pwLayoutWidth     Layout width to scale to.
 * @param[out]    pwLayoutHeight    Layout height to scale to.
 *
 * @return ET9STATUS_NONE on success, otherwise return ET9 error code.
 */

ET9STATUS ET9FARCALL ET9KDB_GetKeyboardSize(ET9KDBInfo       * const pKDBInfo,
                                            ET9U16           * const pwLayoutWidth,
                                            ET9U16           * const pwLayoutHeight)
{
    ET9STATUS eStatus;

    WLOG5(fprintf(pLogFile5, "ET9KDB_GetKeyboardSize\n");)

    eStatus = __ET9KDB_BasicValidityCheck(pKDBInfo, 1);

    if (eStatus) {
        return eStatus;
    }

    ET9Assert(pKDBInfo);

    if (!pwLayoutWidth || !pwLayoutHeight) {
        return ET9STATUS_INVALID_MEMORY;
    }

    *pwLayoutWidth = pKDBInfo->Private.wScaleToLayoutWidth ? pKDBInfo->Private.wScaleToLayoutWidth : pKDBInfo->Private.wLayoutWidth;
    *pwLayoutHeight = pKDBInfo->Private.wScaleToLayoutHeight ? pKDBInfo->Private.wScaleToLayoutHeight : pKDBInfo->Private.wLayoutHeight;

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/**
 * Get keyboard default size (non scaled).
 *
 * @param[in]     pKDBInfo          Pointer to the Keyboard Input Module Information Data Structure (ET9KDBInfo).
 * @param[out]    pwLayoutWidth     Layout width to scale to.
 * @param[out]    pwLayoutHeight    Layout height to scale to.
 *
 * @return ET9STATUS_NONE on success, otherwise return ET9 error code.
 */

ET9STATUS ET9FARCALL ET9KDB_GetKeyboardDefaultSize(ET9KDBInfo       * const pKDBInfo,
                                                   ET9U16           * const pwLayoutWidth,
                                                   ET9U16           * const pwLayoutHeight)
{
    ET9STATUS eStatus;

    WLOG5(fprintf(pLogFile5, "ET9KDB_GetKeyboardDefaultSize\n");)

    eStatus = __ET9KDB_BasicValidityCheck(pKDBInfo, 1);

    if (eStatus) {
        return eStatus;
    }

    ET9Assert(pKDBInfo);

    if (!pwLayoutWidth || !pwLayoutHeight) {
        return ET9STATUS_INVALID_MEMORY;
    }

    *pwLayoutWidth = pKDBInfo->Private.wLayoutWidth;
    *pwLayoutHeight = pKDBInfo->Private.wLayoutHeight;

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/** \ingroup et9trace
 * Set keyboard offset.
 * A keyboard can be given an offset if for example it isn't positioned in the uppper left corner of a window.
 * Coordinates must be in this new space, key position data will be returned in this space.
 *
 * @param[in]     pKDBInfo          Pointer to the Keyboard Input Module Information Data Structure (ET9KDBInfo).
 * @param[in]     wLayoutOffsetX    Layout offset to transform X.
 * @param[in]     wLayoutOffsetY    Layout offset to transform Y.
 *
 * @return ET9STATUS_NONE on success, otherwise return ET9 error code.
 */

ET9STATUS ET9FARCALL ET9KDB_SetKeyboardOffset(ET9KDBInfo          * const pKDBInfo,
                                              const ET9U16                wLayoutOffsetX,
                                              const ET9U16                wLayoutOffsetY)
{
    ET9STATUS eStatus;

    WLOG5(fprintf(pLogFile5, "ET9KDB_SetKeyboardOffset, x %u, y %u\n", wLayoutOffsetX, wLayoutOffsetY);)

    eStatus = __ET9KDB_BasicValidityCheck(pKDBInfo, 1);

    if (eStatus) {
        return eStatus;
    }

    pKDBInfo->Private.wLayoutOffsetX = wLayoutOffsetX;
    pKDBInfo->Private.wLayoutOffsetY = wLayoutOffsetY;

    if (pKDBInfo->ET9Handle_KDB_Request) {

        ET9KDB_Request sRequest;

        sRequest.eType = ET9_KDB_REQ_LAYOUT_CHANGED;

        (void)pKDBInfo->ET9Handle_KDB_Request(pKDBInfo, NULL, &sRequest);
    }

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/**
 * Get the Y coordinate for the top of the shift gesture margin.
 * Coordinates above this coordinate invoke the shift gesture.
 *
 * @param[in]     pKDBInfo          Pointer to the Keyboard Input Module Information Data Structure (ET9KDBInfo).
 * @param[out]    pdwTopOfMarginY   Pointer to integer to receive Y coordinate for the top of the shift gesture margin.
 *
 * @return ET9STATUS_NONE on success, otherwise return T9 error code.
 */

ET9STATUS ET9FARCALL ET9KDB_GetTopOfShiftGestureMargin(ET9KDBInfo  * const pKDBInfo,
                                                       ET9U32      * const pdwTopOfMarginY)
{
    ET9STATUS eStatus;

    WLOG5(fprintf(pLogFile5, "ET9KDB_GetShiftGestureMargin\n");)

    eStatus = __ET9KDB_BasicValidityCheck(pKDBInfo, 1);

    if (eStatus) {
        return eStatus;
    }

    ET9Assert(pKDBInfo);

    if (!pdwTopOfMarginY) {
        return ET9STATUS_INVALID_MEMORY;
    }

    *pdwTopOfMarginY = __ScaleCoordinateToIntegrationY(pKDBInfo, pKDBInfo->Private.pCurrLayoutInfo->wTopOfShiftGestureMargin);

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/**
 * Set the Y coordinate for the top of the shift gesture margin.
 * Coordinates above this coordinate invoke the shift gesture.
 *
 * @param[in]     pKDBInfo          Pointer to the Keyboard Input Module Information Data Structure (ET9KDBInfo).
 * @param[in]     dwTopOfMarginY    Integer with Y coordinate for the top of the shift gesture margin.
 *
 * @return ET9STATUS_NONE on success, otherwise return T9 error code.
 */

ET9STATUS ET9FARCALL ET9KDB_SetTopOfShiftGestureMargin(ET9KDBInfo  * const pKDBInfo,
                                                       const ET9U32        dwTopOfMarginY)
{
    ET9STATUS eStatus;
    ET9U16 dwScaledTopOfMarginY;

    WLOG5(fprintf(pLogFile5, "ET9KDB_SetShiftGestureMargin\n");)

    eStatus = __ET9KDB_BasicValidityCheck(pKDBInfo, 1);

    if (eStatus) {
        return eStatus;
    }

    ET9Assert(pKDBInfo);

    dwScaledTopOfMarginY = (ET9U16)__ScaleCoordinateToKdbY(pKDBInfo, (ET9U16)dwTopOfMarginY);

    if (dwScaledTopOfMarginY <= pKDBInfo->Private.pCurrLayoutInfo->wTopOfTopMostKey) {

        pKDBInfo->Private.pCurrLayoutInfo->wTopOfShiftGestureMargin = dwScaledTopOfMarginY;
        pKDBInfo->Private.pCurrLayoutInfo->wShiftGestureMargin = pKDBInfo->Private.pCurrLayoutInfo->wTopOfTopMostKey - dwScaledTopOfMarginY;

        return ET9STATUS_NONE;
    }
    else {
        return ET9STATUS_BAD_PARAM;
    }
}

/* ************************************************************************************************************** */
/* * PUBLIC - KDB LOAD ****************************************************************************************** */
/* ************************************************************************************************************** */

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                         
 *
 *                            
 *                            
 *
 *                                             
 */

ET9INLINE static ET9BOOL ET9LOCALCALL __CheckKeyRegionOverlap(ET9KdbLayoutInfo  const * const pLayoutInfo,
                                                              ET9Region         const * const pRegion)
{
    ET9UINT                 nCount;
    ET9KdbAreaInfo    const *pArea;

    pArea = pLayoutInfo->pKeyAreas;
    for (nCount = pLayoutInfo->nKeyAreaCount; nCount; --nCount, ++pArea) {

        /* include all keys */

        if ((pRegion->wRight  >= pArea->sRegion.wLeft && pArea->sRegion.wRight  >= pRegion->wLeft) &&
            (pRegion->wBottom >= pArea->sRegion.wTop  && pArea->sRegion.wBottom >= pRegion->wTop)) {
            return 1;
        }
    }

    return 0;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                              
 *
 *                            
 *                            
 *
 *                                          
 */

ET9INLINE static ET9BOOL ET9LOCALCALL __CheckIndexUsed(ET9KdbLayoutInfo  const * const pLayoutInfo,
                                                       const ET9U16                    wKeyIndex)
{
    ET9UINT                 nCount;
    ET9KdbAreaInfo    const *pArea;

    pArea = pLayoutInfo->pKeyAreas;
    for (nCount = pLayoutInfo->nKeyAreaCount; nCount; --nCount, ++pArea) {

        /* include all keys */

        if (pArea->wKeyIndex == wKeyIndex) {
            return 1;
        }
    }

    return 0;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                      
 *
 *                            
 *                            
 *
 *                                           
 */

ET9INLINE static ET9BOOL ET9LOCALCALL __ValidateCharValues(ET9SYMB       const * const psString,
                                                           const ET9UINT               nStrLen)
{
    ET9UINT nCount;
    ET9SYMB const * psChar;

    psChar = psString;
    for (nCount = nStrLen; nCount; --nCount, ++psChar) {

        /* null/zero is not allowed */

        if (!*psChar) {
            return 0;
        }

        /* allow these specific chars to be both function key and normal key */

        if (*psChar <= 0x20) {
            continue;
        }

        /* not allowed to overlap with function keys */

        if (ET9IsFunctionKeySymbol(*psChar)) {
            return 0;
        }
    }

    return 1;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                           
 *
 *                                                                                
 *                                                           
 *                                                          
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __AssureInAmbigSet(ET9KDBInfo         * const pKDBInfo,
                                                 const ET9UINT              nCharCount,
                                                 ET9SYMB      const * const psChars)
{
    ET9UINT nCount;
    ET9SYMB const *psSymb;

    ET9UINT nAddCount;
    ET9SYMB psAddChars[ET9_KDB_MAX_KEY_CHARS];

    ET9SYMB * const psDedupe = &pKDBInfo->Private.wm.xmlReader.psDedupe[0];

    /* look for adds */

    nAddCount = 0;

    psSymb = psChars;
    for (nCount = nCharCount; nCount; --nCount, ++psSymb) {

        const ET9SYMB sSymb = _ET9SymToLower(*psSymb, pKDBInfo->Private.pWordSymbInfo->Private.dwLocale);

        const ET9UINT nHash = sSymb % ET9_KDB_XML_MAX_DEDUPE_CHARS;

        if (!psDedupe[nHash]) {

            if (nAddCount >= ET9_KDB_MAX_KEY_CHARS) {
                return ET9STATUS_KDB_HAS_TOO_MANY_CHARS;
            }

            psDedupe[nHash] = sSymb;
            psAddChars[nAddCount++] = sSymb;
        }
        else if (psDedupe[nHash] == sSymb) {
        }
        else {

            ET9UINT nLook;

            for (nLook = nHash; psDedupe[nLook]; ) {
                ++nLook;
                if (nLook >= ET9_KDB_XML_MAX_DEDUPE_CHARS) {
                    nLook = 0;
                }

                if (psDedupe[nHash] == sSymb) {
                    break;
                }
            }

            if (!psDedupe[nHash]) {

                if (nAddCount >= ET9_KDB_MAX_KEY_CHARS) {
                    return ET9STATUS_KDB_HAS_TOO_MANY_CHARS;
                }

                psDedupe[nHash] = sSymb;
                psAddChars[nAddCount++] = sSymb;
            }
        }
    }

    if (!nAddCount) {
        return ET9STATUS_NONE;
    }

    /* actually add */

    {
        ET9KdbLayoutInfo * const pLayoutInfo = pKDBInfo->Private.pCurrLayoutInfo;
        ET9KdbAreaInfo * const pArea = &pLayoutInfo->pKeyAreas[pLayoutInfo->nKeyAreaCount - 1];

        const ET9UINT nCharsToMove = (ET9UINT)(pLayoutInfo->nCharPoolCount - pArea->nCharCount - (pArea->psChars - pLayoutInfo->psCharPool));

        if (pLayoutInfo->nCharPoolCount + nAddCount > ET9_KDB_MAX_POOL_CHARS) {
            return ET9STATUS_KDB_HAS_TOO_MANY_CHARS;
        }

        _ET9SymMove(&pArea->psChars[pArea->nCharCount + nAddCount], &pArea->psChars[pArea->nCharCount], nCharsToMove);

        _ET9SymCopy(&pArea->psChars[pArea->nCharCount], psAddChars, nAddCount);

        pArea->nCharCount += nAddCount;
        pLayoutInfo->nCharPoolCount += nAddCount;

        /* adjust active pointers */

        if (pArea->psShiftedChars) {
            pArea->psShiftedChars += nAddCount;
        }

        if (pArea->psMultitapChars) {
            pArea->psMultitapChars += nAddCount;
        }

        if (pArea->psMultitapShiftedChars) {
            pArea->psMultitapShiftedChars += nAddCount;
        }
    }

    /* done */

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/**
 * @brief Set keyboard properties, available when loading a dynamic keyboard.
 *
 * @param[in]     pKDBInfo              Pointer to keyboard information structure.
 * @param[in]     bMajorVersion         Major version.
 * @param[in]     bMinorVersion         Minor version.
 * @param[in]     bPrimaryID            Primary ID.
 * @param[in]     bSecondaryID          Secondary ID.
 * @param[in]     wLayoutWidth          Keyboard width.
 * @param[in]     wLayoutHeight         Keyboard height.
 * @param[in]     wTotalPages           Total number of pages in the the keyboard.
 *
 * @return ET9STATUS_NONE on success, otherwise return ET9 error code.
 */

ET9STATUS ET9FARCALL ET9KDB_Load_SetProperties(ET9KDBInfo       * const pKDBInfo,
                                               const ET9U8              bMajorVersion,
                                               const ET9U8              bMinorVersion,
                                               const ET9U8              bPrimaryID,
                                               const ET9U8              bSecondaryID,
                                               const ET9U16             wLayoutWidth,
                                               const ET9U16             wLayoutHeight,
                                               const ET9U16             wTotalPages)
{
    ET9STATUS eStatus;

    WLOG5(fprintf(pLogFile5, "ET9KDB_Load_SetProperties, pKDBInfo = %p, wLayoutWidth %u, wLayoutHeight %u, wTotalPages %u\n", pKDBInfo, wLayoutWidth, wLayoutHeight, wTotalPages);)

    eStatus = __ET9KDB_LoadValidityCheck(pKDBInfo);

    if (eStatus) {
        return eStatus;
    }

    if (pKDBInfo->Private.eLoadState != ET9KDBLOADSTATES_START) {
        return ET9STATUS_KDB_WRONG_LOAD_STATE;
    }

    if (!wLayoutWidth || !wLayoutHeight) {
        return ET9STATUS_KDB_BAD_LAYOUT_SIZE;
    }

    if (!wTotalPages) {
        return ET9STATUS_KDB_BAD_PAGE_COUNT;
    }

    {
        ET9KdbLayoutInfo * const pLayoutInfo = pKDBInfo->Private.pCurrLayoutInfo;

        pLayoutInfo->bPrimaryID = bPrimaryID;
        pLayoutInfo->bSecondaryID = bSecondaryID;
        pLayoutInfo->bContentsMajor = bMajorVersion;
        pLayoutInfo->bContentsMinor = bMinorVersion;
        pLayoutInfo->wTotalPages = wTotalPages;
        pLayoutInfo->wLayoutWidth = wLayoutWidth;
        pLayoutInfo->wLayoutHeight = wLayoutHeight;
    }

    pKDBInfo->Private.eLoadState = ET9KDBLOADSTATES_HAS_PROPERTIES;

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/**
 * @brief Add a key to a keyboard, available when loading a dynamic keyboard.
 *
 * @param[in]     pKDBInfo              Pointer to keyboard information structure.
 * @param[in]     wKeyIndex             Key index - can be ET9_KDB_LOAD_UNDEF_VALUE for an automatic sequence 0-N.
 * @param[in]     eKeyType              Type of key, see ET9LOADKEYTYPE.
 * @param[in]     wLeft                 Left side of key reagion.
 * @param[in]     wTop                  Top of key reagion.
 * @param[in]     wRight                Right side of key reagion.
 * @param[in]     wBottom               Bottom of key reagion.
 * @param[in]     nCharCount            Number of characters.
 * @param[in]     psChars               Array of characters.
 *
 * @return ET9STATUS_NONE on success, otherwise return ET9 error code.
 */

ET9STATUS ET9FARCALL ET9KDB_Load_AddKey(ET9KDBInfo          * const pKDBInfo,
                                        const ET9U16                wKeyIndex,
                                        const ET9LOADKEYTYPE        eKeyType,
                                        const ET9U16                wLeft,
                                        const ET9U16                wTop,
                                        const ET9U16                wRight,
                                        const ET9U16                wBottom,
                                        const ET9UINT               nCharCount,
                                        ET9SYMB       const * const psChars)
{
    ET9STATUS eStatus;

    WLOG5(fprintf(pLogFile5, "ET9KDB_Load_AddKey, pKDBInfo = %p, wKeyIndex %04x, eKeyType %u, top %04x (%c) [%3u %3u %3u %3u]\n", /* pKDBInfo */ 0, wKeyIndex, eKeyType, (psChars ? psChars[0] : 0), (char)(psChars && eKeyType != ET9LKT_FUNCTION ? psChars[0] : 0), wLeft, wTop, wRight, wBottom);)

    eStatus = __ET9KDB_LoadValidityCheck(pKDBInfo);

    if (eStatus) {
        return eStatus;
    }

    if (pKDBInfo->Private.eLoadState != ET9KDBLOADSTATES_HAS_PROPERTIES &&
        pKDBInfo->Private.eLoadState != ET9KDBLOADSTATES_HAS_KEY) {
        return ET9STATUS_KDB_WRONG_LOAD_STATE;
    }

    if (wLeft > wRight ||
        wTop > wBottom) {
        return ET9STATUS_KDB_KEY_BAD_REGION;
    }

    if (eKeyType >= ET9LKT_LAST ||
        !nCharCount ||
        !psChars) {
        return ET9STATUS_BAD_PARAM;
    }

    if (eKeyType == ET9LKT_FUNCTION && nCharCount > 1) {
        return ET9STATUS_BAD_PARAM;
    }

    if (eKeyType == ET9LKT_FUNCTION && !ET9IsFunctionKeySymbol(*psChars)) {
        return ET9STATUS_INVALID_TEXT;
    }

    if (wRight >= pKDBInfo->Private.pCurrLayoutInfo->wLayoutWidth ||
        wBottom >= pKDBInfo->Private.pCurrLayoutInfo->wLayoutHeight) {
        return ET9STATUS_KDB_KEY_OUTSIDE_KEYBOARD;
    }

    if (eKeyType != ET9LKT_FUNCTION && !__ValidateCharValues(psChars, nCharCount)) {
        return ET9STATUS_INVALID_TEXT;
    }

    {
        ET9KdbLayoutInfo * const pLayoutInfo = pKDBInfo->Private.pCurrLayoutInfo;

        if (pLayoutInfo->nKeyAreaCount >= ET9_KDB_MAX_KEYS) {
            return ET9STATUS_KDB_HAS_TOO_MANY_KEYS;
        }

        if (nCharCount > ET9_KDB_XML_MAX_DEDUPE_CHARS) {
            return ET9STATUS_KDB_KEY_HAS_TOO_MANY_CHARS;
        }

        if (pLayoutInfo->nCharPoolCount + nCharCount > ET9_KDB_MAX_POOL_CHARS) {
            return ET9STATUS_KDB_HAS_TOO_MANY_CHARS;
        }

        {
            ET9KdbAreaInfo * const pArea = &pLayoutInfo->pKeyAreas[pLayoutInfo->nKeyAreaCount];

            if (wKeyIndex == ET9_KDB_LOAD_UNDEF_VALUE) {
                pArea->wKeyIndex = (ET9U16)pLayoutInfo->nKeyAreaCount;
            }
            else {
                pArea->wKeyIndex = wKeyIndex;
            }

            if (__CheckIndexUsed(pLayoutInfo, pArea->wKeyIndex)) {
                return ET9STATUS_KDB_KEY_INDEX_ALREADY_USED;
            }

            pArea->sRegion.wLeft = wLeft;
            pArea->sRegion.wTop = wTop;
            pArea->sRegion.wRight = wRight;
            pArea->sRegion.wBottom = wBottom;

            if (__CheckKeyRegionOverlap(pLayoutInfo, &pArea->sRegion)) {
                return ET9STATUS_KDB_KEY_OVERLAP;
            }

            pArea->nCenterX = (wLeft + wRight) / 2;
            pArea->nCenterY = (wTop + wBottom) / 2;

            {
                ET9UINT nCount;
                ET9SYMB const *psSymb;

                ET9SYMB * const psDedupe = &pKDBInfo->Private.wm.xmlReader.psDedupe[0];

                _ET9ClearMem((ET9U8*)psDedupe, ET9_KDB_XML_MAX_DEDUPE_CHARS * sizeof(ET9SYMB));

                psSymb = psChars;
                for (nCount = nCharCount; nCount; --nCount, ++psSymb) {

                    const ET9UINT nHash = *psSymb % ET9_KDB_XML_MAX_DEDUPE_CHARS;

                    if (!psDedupe[nHash]) {
                        psDedupe[nHash] = *psSymb;
                    }
                    else if (psDedupe[nHash] == *psSymb) {
                        return ET9STATUS_KDB_KEY_HAS_REPEAT_CHARS;
                    }
                    else {

                        ET9UINT nLook;

                        for (nLook = nHash; psDedupe[nLook]; ) {
                            ++nLook;
                            if (nLook >= ET9_KDB_XML_MAX_DEDUPE_CHARS) {
                                nLook = 0;
                            }

                            if (psDedupe[nHash] == *psSymb) {
                                return ET9STATUS_KDB_KEY_HAS_REPEAT_CHARS;
                            }
                        }

                        psDedupe[nLook] = *psSymb;
                    }
                }
            }

            pArea->psChars = &pLayoutInfo->psCharPool[pLayoutInfo->nCharPoolCount];

            _ET9SymCopy(pArea->psChars, psChars, nCharCount);

            pArea->nCharCount = nCharCount;
            pLayoutInfo->nCharPoolCount += nCharCount;

            pArea->nShiftedCharCount = 0;
            pArea->psShiftedChars = NULL;
            pArea->nMultitapCharCount = 0;
            pArea->psMultitapChars = NULL;
            pArea->nMultitapShiftedCharCount = 0;
            pArea->psMultitapShiftedChars = NULL;

            {
                ET9UINT nIndex;
                ET9UINT nBestClass;
                ET9SymbClass eBestClass;
                ET9BOOL bHasBPMF = 0;
                ET9SymbClass eFirstClass = ET9_LastSymbClass;
                ET9UINT nClassCount[ET9_LastSymbClass + 1] = { 0 };

                for (nIndex = 0; nIndex < 5 && nIndex < pArea->nCharCount; ++nIndex) {

                    const ET9SYMB sSymb = pArea->psChars[nIndex];

                    const ET9SymbClass eClass = _ET9_GetSymbolClass(sSymb);

                    ++nClassCount[eClass];

                    if (!nIndex) {
                        eFirstClass = eClass;
                    }

                    if (sSymb >= 0x3105 && sSymb <= 0x312D) {
                        bHasBPMF = 1;
                    }
                }

                nBestClass = ET9_LastSymbClass;
                for (nIndex = 0; nIndex < ET9_LastSymbClass; ++nIndex) {
                    if (nClassCount[nBestClass] < nClassCount[nIndex]) {
                        nBestClass = nIndex;
                    }
                }

                eBestClass = (ET9SymbClass)nBestClass;

                if (eKeyType == ET9LKT_SMARTPUNCT) {

                    if (eBestClass != ET9_PunctSymbClass && eBestClass != ET9_NumbrSymbClass && !bHasBPMF) {
                        return ET9STATUS_KDB_INCORRECT_TYPE_FOR_KEY;
                    }

                    pArea->eKeyType = ET9KTSMARTPUNCT;
                    pArea->eInputType = ET9DISCRETEKEY;
                }
                else if (eKeyType == ET9LKT_REGIONAL) {

                    if (eBestClass == ET9_PunctSymbClass || (eBestClass == ET9_NumbrSymbClass && !bHasBPMF)) {

                        /* might want to reconsider these "errors" and how to apply them */

                        if (eFirstClass != ET9_AlphaSymbClass) {
                            return ET9STATUS_KDB_INCORRECT_TYPE_FOR_KEY;
                        }
                    }

                    pArea->eKeyType = ET9KTLETTER;
                    pArea->eInputType = ET9REGIONALKEY;
                }
                else if (eKeyType == ET9LKT_NONREGIONAL) {

                    switch (eBestClass)
                    {
                        case ET9_PunctSymbClass:
                            pArea->eKeyType = ET9KTPUNCTUATION;
                            break;
                        case ET9_NumbrSymbClass:
                            pArea->eKeyType = ET9KTNUMBER;
                            break;
                        default:
                            pArea->eKeyType = ET9KTLETTER;
                            break;
                    }

                    pArea->eInputType = ET9DISCRETEKEY;
                }
                else if (eKeyType == ET9LKT_STRING) {

                    pArea->eKeyType = ET9KTSTRING;
                    pArea->eInputType = ET9DISCRETEKEY;
                }
                else if (eKeyType == ET9LKT_FUNCTION) {

                    pArea->eKeyType = ET9KTFUNCTION;
                    pArea->eInputType = ET9REGIONALKEY;
                }
                else {
                    return ET9STATUS_ERROR;
                }
            }

            ++pLayoutInfo->nKeyAreaCount;
        }
    }

    pKDBInfo->Private.eLoadState = ET9KDBLOADSTATES_HAS_KEY;

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/**
 * @brief Attach shifted symbols to a key, available when loading a dynamic keyboard. Optional, only needed for non default shifted sequence.
 *
 * If the list is empty then this is a no-op (will not store an empty list).
 *
 * @param[in]     pKDBInfo                      Pointer to keyboard information structure.
 * @param[in]     nShiftedCharCount             Number of shifted characters.
 * @param[in]     psShiftedChars                Array of shifted characters.
 *
 * @return ET9STATUS_NONE on success, otherwise return ET9 error code.
 */

ET9STATUS ET9FARCALL ET9KDB_Load_AttachShiftedChars(ET9KDBInfo          * const pKDBInfo,
                                                    const ET9UINT               nShiftedCharCount,
                                                    ET9SYMB       const * const psShiftedChars)
{
    ET9STATUS eStatus;

    WLOG5(fprintf(pLogFile5, "ET9KDB_Load_AttachShiftedChars, pKDBInfo = %p\n", pKDBInfo);)

    eStatus = __ET9KDB_LoadValidityCheck(pKDBInfo);

    if (eStatus) {
        return eStatus;
    }

    if (pKDBInfo->Private.eLoadState != ET9KDBLOADSTATES_HAS_KEY) {
        return ET9STATUS_KDB_WRONG_LOAD_STATE;
    }

    if (!nShiftedCharCount) {
        return ET9STATUS_NONE;
    }

    if (!psShiftedChars) {
        return ET9STATUS_BAD_PARAM;
    }

    if (!__ValidateCharValues(psShiftedChars, nShiftedCharCount)) {
        return ET9STATUS_INVALID_TEXT;
    }

    {
        ET9KdbLayoutInfo * const pLayoutInfo = pKDBInfo->Private.pCurrLayoutInfo;
        ET9KdbAreaInfo * const pArea = &pLayoutInfo->pKeyAreas[pLayoutInfo->nKeyAreaCount - 1];

        if (pArea->eKeyType == ET9KTFUNCTION) {
            return ET9STATUS_KDB_UNEXPECTED_ATTRIBUTE;
        }

        if (pArea->nShiftedCharCount) {
            return ET9STATUS_KDB_REPEAT_LOAD_ATTACH;
        }

        eStatus = __AssureInAmbigSet(pKDBInfo, nShiftedCharCount, psShiftedChars);

        if (eStatus) {
            return eStatus;
        }

        if (pLayoutInfo->nCharPoolCount + nShiftedCharCount > ET9_KDB_MAX_POOL_CHARS) {
            return ET9STATUS_KDB_HAS_TOO_MANY_CHARS;
        }

        pArea->psShiftedChars = &pLayoutInfo->psCharPool[pLayoutInfo->nCharPoolCount];

        _ET9SymCopy(pArea->psShiftedChars, psShiftedChars, nShiftedCharCount);

        pArea->nShiftedCharCount = nShiftedCharCount;
        pLayoutInfo->nCharPoolCount += nShiftedCharCount;
    }

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/**
 * @brief Attach multitap symbols to a key, available when loading a dynamic keyboard. Optional, only needed for non default multitap sequence.
 *
 * If both lists are empty then this is a no-op (will not store two empty lists).
 *
 * @param[in]     pKDBInfo                      Pointer to keyboard information structure.
 * @param[in]     nCharMultitapCount            Number of multitap characters.
 * @param[in]     psMultitapChars               Array of multitap characters.
 * @param[in]     nCharMultitapShiftedCount     Number of shifted multitap characters (optional, only needed when normal case conversion isn't applicable).
 * @param[in]     psMultitapShiftedChars        Array of shifted multitap characters (optional, only needed when normal case conversion isn't applicable).
 *
 * @return ET9STATUS_NONE on success, otherwise return ET9 error code.
 */

ET9STATUS ET9FARCALL ET9KDB_Load_AttachMultitapInfo(ET9KDBInfo          * const pKDBInfo,
                                                    const ET9UINT               nCharMultitapCount,
                                                    ET9SYMB       const * const psMultitapChars,
                                                    const ET9UINT               nCharMultitapShiftedCount,
                                                    ET9SYMB       const * const psMultitapShiftedChars)
{
    ET9STATUS eStatus;

    WLOG5(fprintf(pLogFile5, "ET9KDB_Load_AttachMultitapInfo, pKDBInfo = %p\n", pKDBInfo);)

    eStatus = __ET9KDB_LoadValidityCheck(pKDBInfo);

    if (eStatus) {
        return eStatus;
    }

    if (pKDBInfo->Private.eLoadState != ET9KDBLOADSTATES_HAS_KEY) {
        return ET9STATUS_KDB_WRONG_LOAD_STATE;
    }

    if (!nCharMultitapCount && !nCharMultitapShiftedCount) {
        return ET9STATUS_NONE;
    }

    if (!nCharMultitapCount ||
        !psMultitapChars) {
        return ET9STATUS_BAD_PARAM;
    }

    if (nCharMultitapShiftedCount && !psMultitapShiftedChars) {
        return ET9STATUS_BAD_PARAM;
    }

    if (!__ValidateCharValues(psMultitapChars, nCharMultitapCount)) {
        return ET9STATUS_INVALID_TEXT;
    }

    if (!__ValidateCharValues(psMultitapShiftedChars, nCharMultitapShiftedCount)) {
        return ET9STATUS_INVALID_TEXT;
    }

    {
        ET9KdbLayoutInfo * const pLayoutInfo = pKDBInfo->Private.pCurrLayoutInfo;
        ET9KdbAreaInfo * const pArea = &pLayoutInfo->pKeyAreas[pLayoutInfo->nKeyAreaCount - 1];

        if (pArea->eKeyType == ET9KTFUNCTION) {
            return ET9STATUS_KDB_UNEXPECTED_ATTRIBUTE;
        }

        if (pArea->nMultitapCharCount) {
            return ET9STATUS_KDB_REPEAT_LOAD_ATTACH;
        }

        eStatus = __AssureInAmbigSet(pKDBInfo, nCharMultitapCount, psMultitapChars);

        if (eStatus) {
            return eStatus;
        }

        eStatus = __AssureInAmbigSet(pKDBInfo, nCharMultitapShiftedCount, psMultitapShiftedChars);

        if (eStatus) {
            return eStatus;
        }

        if (pLayoutInfo->nCharPoolCount + nCharMultitapCount + nCharMultitapShiftedCount > ET9_KDB_MAX_POOL_CHARS) {
            return ET9STATUS_KDB_HAS_TOO_MANY_CHARS;
        }

        pArea->psMultitapChars = &pLayoutInfo->psCharPool[pLayoutInfo->nCharPoolCount];

        _ET9SymCopy(pArea->psMultitapChars, psMultitapChars, nCharMultitapCount);

        pArea->nMultitapCharCount = nCharMultitapCount;
        pLayoutInfo->nCharPoolCount += nCharMultitapCount;

        if (nCharMultitapShiftedCount) {

            pArea->psMultitapShiftedChars = &pLayoutInfo->psCharPool[pLayoutInfo->nCharPoolCount];

            _ET9SymCopy(pArea->psMultitapShiftedChars, psMultitapShiftedChars, nCharMultitapShiftedCount);

            pArea->nMultitapShiftedCharCount = nCharMultitapShiftedCount;
            pLayoutInfo->nCharPoolCount += nCharMultitapShiftedCount;
        }
    }

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/**
 * @brief Reset the dynamic keyboard (and start over with a new one), available when loading a dynamic keyboard.
 *
 * @param[in]     pKDBInfo              Pointer to keyboard information structure.
 *
 * @return ET9STATUS_NONE on success, otherwise return ET9 error code.
 */

ET9STATUS ET9FARCALL ET9KDB_Load_Reset(ET9KDBInfo   * const pKDBInfo)
{
    ET9STATUS eStatus;

    WLOG5(fprintf(pLogFile5, "ET9KDB_Load_Reset, pKDBInfo = %p\n", pKDBInfo);)

    eStatus = __ET9KDB_LoadValidityCheck(pKDBInfo);

    if (eStatus) {
        return eStatus;
    }

    {
        ET9KdbLayoutInfo * const pLayoutInfo = pKDBInfo->Private.pCurrLayoutInfo;

        pKDBInfo->wTotalPages = 0;

        pKDBInfo->Private.pCurrLayoutInfo->wLayoutWidth = 0;
        pKDBInfo->Private.pCurrLayoutInfo->wLayoutHeight = 0;
        pKDBInfo->Private.eLoadState = ET9KDBLOADSTATES_START;

        pLayoutInfo->bDatabaseType = 0;
        pLayoutInfo->bLayoutVer = 0;
        pLayoutInfo->bPrimaryID = 0;
        pLayoutInfo->bSecondaryID = 0;
        pLayoutInfo->bSymbolClass = 15;
        pLayoutInfo->bContentsMajor = 0;
        pLayoutInfo->bContentsMinor = 0;
        pLayoutInfo->nBoxWidth = 0;
        pLayoutInfo->nBoxHeight = 0;
        pLayoutInfo->nMinKeyWidth = 0;
        pLayoutInfo->nMinKeyHeight = 0;
        pLayoutInfo->nMedianKeyWidth = 0;
        pLayoutInfo->nMedianKeyHeight = 0;
        pLayoutInfo->nKeyAreaCount = 0;
        pLayoutInfo->nCharPoolCount = 0;
    }

    return ET9STATUS_NONE;
}

/* ************************************************************************************************************** */
/* * Classic Reader ********************************************************************************************* */
/* ************************************************************************************************************** */

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *               
 */

typedef enum ET9_classic_token_e {

    ET9_classic_token_LeftBracket,
    ET9_classic_token_RightBracket,
    ET9_classic_token_Period,
    ET9_classic_token_Equal,

    ET9_classic_token_Layout,
    ET9_classic_token_Information,
    ET9_classic_token_Contents,
    ET9_classic_token_Major,
    ET9_classic_token_Minor,
    ET9_classic_token_Ver,
    ET9_classic_token_OEM,
    ET9_classic_token_ID,
    ET9_classic_token_Primary,
    ET9_classic_token_Secondary,
    ET9_classic_token_Width,
    ET9_classic_token_Height,
    ET9_classic_token_Total,
    ET9_classic_token_Pages,
    ET9_classic_token_Page,
    ET9_classic_token_Keys,
    ET9_classic_token_Key,
    ET9_classic_token_Left,
    ET9_classic_token_Top,
    ET9_classic_token_Right,
    ET9_classic_token_Bottom,
    ET9_classic_token_Type,
    ET9_classic_token_Chars,
    ET9_classic_token_Char,
    ET9_classic_token_Regional,
    ET9_classic_token_NonRegional,
    ET9_classic_token_SmartPunct,
    ET9_classic_token_String,
    ET9_classic_token_Function,
    ET9_classic_token_Symbol,
    ET9_classic_token_Bytes,
    ET9_classic_token_Class,

    ET9_classic_token_Number,

    ET9_classic_token_EOF,

    ET9_classic_token_Error,

    ET9_classic_token_Last
} ET9_classic_token;

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                  
 */

typedef struct ET9KdbClassicReaderInfo_s {

    ET9KDBInfo                  *pKDBInfo;                          /* < -IDR-     */

    ET9_classic_token           eCurrToken;                         /* < -IDR-     */
    ET9UINT                     nCurrNumber;                        /* < -IDR-     */

    ET9UINT                     nCurrLine;                          /* < -IDR-     */
    ET9UINT                     nObjectLine;                        /* < -IDR-     */

    ET9U8               const * pbEndChar;                          /* < -IDR-     */
    ET9U8               const * pbCurrChar;                         /* < -IDR-     */

    ET9U8               const * pbContent;                          /* < -IDR-     */
    ET9U32                      dwContentLen;                       /* < -IDR-     */

    ET9U16                      wTotalPages;                        /* < -IDR-     */
} ET9KdbClassicReaderInfo;                                          /* < -IDR-     */

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                                             
 *
 *                                             
 */

static ET9BOOL ET9LOCALCALL __ClassicReader_SkipUntilLeftBracket(ET9KdbClassicReaderInfo  * const pReaderInfo)
{
    if (pReaderInfo->eCurrToken == ET9_classic_token_Error) {
        return 0;
    }

    if (pReaderInfo->eCurrToken == ET9_classic_token_EOF) {
        pReaderInfo->eCurrToken = ET9_classic_token_Error;
        return 0;
    }

    /* until left bracket */

    while (pReaderInfo->pbCurrChar < pReaderInfo->pbEndChar) {

        if (*pReaderInfo->pbCurrChar == '/' && (pReaderInfo->pbCurrChar + 1) < pReaderInfo->pbEndChar && *(pReaderInfo->pbCurrChar + 1) == '/') {

            pReaderInfo->pbCurrChar += 2;

            while (pReaderInfo->pbCurrChar < pReaderInfo->pbEndChar && *pReaderInfo->pbCurrChar != 0xA && *pReaderInfo->pbCurrChar != 0xD) {
                ++pReaderInfo->pbCurrChar;
            }
        }
        else if (*pReaderInfo->pbCurrChar == 0xA && (pReaderInfo->pbCurrChar + 1) < pReaderInfo->pbEndChar && *(pReaderInfo->pbCurrChar + 1) == 0xD) {
            pReaderInfo->pbCurrChar += 2;
            ++pReaderInfo->nCurrLine;
        }
        else if (*pReaderInfo->pbCurrChar == 0xD && (pReaderInfo->pbCurrChar + 1) < pReaderInfo->pbEndChar && *(pReaderInfo->pbCurrChar + 1) == 0xA) {
            pReaderInfo->pbCurrChar += 2;
            ++pReaderInfo->nCurrLine;
        }
        else if (*pReaderInfo->pbCurrChar == 0xA) {
            ++pReaderInfo->pbCurrChar;
            ++pReaderInfo->nCurrLine;
        }
        else if (*pReaderInfo->pbCurrChar == 0xD) {
            ++pReaderInfo->pbCurrChar;
            ++pReaderInfo->nCurrLine;
        }
        else if (*pReaderInfo->pbCurrChar == '[') {
            pReaderInfo->eCurrToken = ET9_classic_token_LeftBracket;
            ++pReaderInfo->pbCurrChar;
            return 1;
        }
        else {
            ++pReaderInfo->pbCurrChar;
        }
    }

    /* not found */

    pReaderInfo->eCurrToken = ET9_classic_token_EOF;
    return 1;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                                             
 *
 *                                             
 */

static ET9BOOL ET9LOCALCALL __ClassicReader_GetNextToken(ET9KdbClassicReaderInfo  * const pReaderInfo)
{
    if (pReaderInfo->eCurrToken == ET9_classic_token_Error) {
        return 0;
    }

    if (pReaderInfo->eCurrToken == ET9_classic_token_EOF) {
        pReaderInfo->eCurrToken = ET9_classic_token_Error;
        return 0;
    }

    /* white space and comments */

    while (pReaderInfo->pbCurrChar < pReaderInfo->pbEndChar &&
           ((*pReaderInfo->pbCurrChar <= 0x20) ||
            (*pReaderInfo->pbCurrChar == '/' && (pReaderInfo->pbCurrChar + 1) < pReaderInfo->pbEndChar && *(pReaderInfo->pbCurrChar + 1) == '/'))) {

        if (*pReaderInfo->pbCurrChar == '/' && (pReaderInfo->pbCurrChar + 1) < pReaderInfo->pbEndChar && *(pReaderInfo->pbCurrChar + 1) == '/') {

            pReaderInfo->pbCurrChar += 2;

            while (pReaderInfo->pbCurrChar < pReaderInfo->pbEndChar && *pReaderInfo->pbCurrChar != 0xA && *pReaderInfo->pbCurrChar != 0xD) {
                ++pReaderInfo->pbCurrChar;
            }
        }
        else if (*pReaderInfo->pbCurrChar == 0xA && (pReaderInfo->pbCurrChar + 1) < pReaderInfo->pbEndChar && *(pReaderInfo->pbCurrChar + 1) == 0xD) {
            pReaderInfo->pbCurrChar += 2;
            ++pReaderInfo->nCurrLine;
        }
        else if (*pReaderInfo->pbCurrChar == 0xD && (pReaderInfo->pbCurrChar + 1) < pReaderInfo->pbEndChar && *(pReaderInfo->pbCurrChar + 1) == 0xA) {
            pReaderInfo->pbCurrChar += 2;
            ++pReaderInfo->nCurrLine;
        }
        else if (*pReaderInfo->pbCurrChar == 0xA) {
            ++pReaderInfo->pbCurrChar;
            ++pReaderInfo->nCurrLine;
        }
        else if (*pReaderInfo->pbCurrChar == 0xD) {
            ++pReaderInfo->pbCurrChar;
            ++pReaderInfo->nCurrLine;
        }
        else {
            ++pReaderInfo->pbCurrChar;
        }
    }

    if (pReaderInfo->pbCurrChar >= pReaderInfo->pbEndChar) {
        pReaderInfo->eCurrToken = ET9_classic_token_EOF;
        return 1;
    }

    /* delimiters */

    switch (*pReaderInfo->pbCurrChar)
    {
        case '[':
            pReaderInfo->eCurrToken = ET9_classic_token_LeftBracket;
            ++pReaderInfo->pbCurrChar;
            return 1;
        case ']':
            pReaderInfo->eCurrToken = ET9_classic_token_RightBracket;
            ++pReaderInfo->pbCurrChar;
            return 1;
        case '.':
            pReaderInfo->eCurrToken = ET9_classic_token_Period;
            ++pReaderInfo->pbCurrChar;
            return 1;
        case '=':
            pReaderInfo->eCurrToken = ET9_classic_token_Equal;
            ++pReaderInfo->pbCurrChar;
            return 1;
    }

    /* hexadecimal number */

    if (*pReaderInfo->pbCurrChar == '0' && (pReaderInfo->pbCurrChar + 1) < pReaderInfo->pbEndChar && (*(pReaderInfo->pbCurrChar + 1) == 'x' || *(pReaderInfo->pbCurrChar + 1) == 'X')) {

        pReaderInfo->pbCurrChar += 2;

        pReaderInfo->nCurrNumber = 0;

        while (pReaderInfo->pbCurrChar < pReaderInfo->pbEndChar &&
               ((*pReaderInfo->pbCurrChar >= '0' && *pReaderInfo->pbCurrChar <= '9') ||
                (*pReaderInfo->pbCurrChar >= 'a' && *pReaderInfo->pbCurrChar <= 'f') ||
                (*pReaderInfo->pbCurrChar >= 'A' && *pReaderInfo->pbCurrChar <= 'F'))) {

            pReaderInfo->nCurrNumber *= 16;

            if (*pReaderInfo->pbCurrChar >= '0' && *pReaderInfo->pbCurrChar <= '9') {
                pReaderInfo->nCurrNumber += *pReaderInfo->pbCurrChar - '0';
            }
            else if (*pReaderInfo->pbCurrChar >= 'a' && *pReaderInfo->pbCurrChar <= 'f') {
                pReaderInfo->nCurrNumber += (*pReaderInfo->pbCurrChar - 'a') + 10;
            }
            else {
                pReaderInfo->nCurrNumber += (*pReaderInfo->pbCurrChar - 'A') + 10;
            }

            ++pReaderInfo->pbCurrChar;
        }

        pReaderInfo->eCurrToken = ET9_classic_token_Number;
        return 1;
    }

    /* decimal number */

    if (*pReaderInfo->pbCurrChar >= '0' && *pReaderInfo->pbCurrChar <= '9') {

        pReaderInfo->nCurrNumber = 0;

        while (pReaderInfo->pbCurrChar < pReaderInfo->pbEndChar && *pReaderInfo->pbCurrChar >= '0' && *pReaderInfo->pbCurrChar <= '9') {
            pReaderInfo->nCurrNumber *= 10;
            pReaderInfo->nCurrNumber += *pReaderInfo->pbCurrChar - '0';
            ++pReaderInfo->pbCurrChar;
        }

        pReaderInfo->eCurrToken = ET9_classic_token_Number;
        return 1;
    }

    /* keywords & function values */

    {
        ET9UINT nLen;
        ET9U32 dwHashValue;

        nLen = 0;
        dwHashValue = 0;

        while (pReaderInfo->pbCurrChar < pReaderInfo->pbEndChar &&
               ((*pReaderInfo->pbCurrChar >= 'a' && *pReaderInfo->pbCurrChar <= 'z') ||
                (*pReaderInfo->pbCurrChar >= 'A' && *pReaderInfo->pbCurrChar <= 'Z') ||
                (*pReaderInfo->pbCurrChar >= '0' && *pReaderInfo->pbCurrChar <= '9') ||
                (*pReaderInfo->pbCurrChar == '_'))) {

            ET9U8 bChar = *pReaderInfo->pbCurrChar;

            ++nLen;
            ++pReaderInfo->pbCurrChar;

            if (nLen >= 32) {
                pReaderInfo->eCurrToken = ET9_classic_token_Error;
                return 0;
            }

            if (bChar >= 'A' && bChar <= 'Z') {
                bChar = bChar + 0x20;
            }

            dwHashValue = bChar + (65599 * dwHashValue);
        }

        if (nLen) {

            pReaderInfo->eCurrToken = ET9_classic_token_Function;

            switch (dwHashValue)
            {
                case 0x00691a3b: pReaderInfo->eCurrToken = ET9_classic_token_ID; return 1;
                case 0x3515943f: pReaderInfo->eCurrToken = ET9_classic_token_Key; return 1;
                case 0x370dd237: pReaderInfo->eCurrToken = ET9_classic_token_OEM; return 1;
                case 0x398e2235: pReaderInfo->eCurrToken = ET9_classic_token_Top; return 1;
                case 0x3a803ec3: pReaderInfo->eCurrToken = ET9_classic_token_Ver; return 1;
                case 0x31ba1e96: pReaderInfo->eCurrToken = ET9_classic_token_Char; return 1;
                case 0xa48e7bf4: pReaderInfo->eCurrToken = ET9_classic_token_Keys; return 1;
                case 0xd3024807: pReaderInfo->eCurrToken = ET9_classic_token_Left; return 1;
                case 0x8b264d2f: pReaderInfo->eCurrToken = ET9_classic_token_Page; return 1;
                case 0x511c067a: pReaderInfo->eCurrToken = ET9_classic_token_Type; return 1;
                case 0x5b63875d: pReaderInfo->eCurrToken = ET9_classic_token_Chars; return 1;
                case 0xc15b12f9: pReaderInfo->eCurrToken = ET9_classic_token_Major; return 1;
                case 0x3789d6f5: pReaderInfo->eCurrToken = ET9_classic_token_Minor; return 1;
                case 0x8b9bff04: pReaderInfo->eCurrToken = ET9_classic_token_Pages; return 1;
                case 0x87ae43bc: pReaderInfo->eCurrToken = ET9_classic_token_Right; return 1;
                case 0x2d0fae04: pReaderInfo->eCurrToken = ET9_classic_token_Total; return 1;
                case 0xd9ddf326: pReaderInfo->eCurrToken = ET9_classic_token_Width; return 1;
                case 0x3f1f244b: pReaderInfo->eCurrToken = ET9_classic_token_Bottom; return 1;
                case 0x16b4f247: pReaderInfo->eCurrToken = ET9_classic_token_Height; return 1;
                case 0x4c1f866a: pReaderInfo->eCurrToken = ET9_classic_token_Layout; return 1;
                case 0x6e1b7ba2: pReaderInfo->eCurrToken = ET9_classic_token_Primary; return 1;
                case 0xcc711e1a: pReaderInfo->eCurrToken = ET9_classic_token_Contents; return 1;
                case 0x479f9c3f: pReaderInfo->eCurrToken = ET9_classic_token_Regional; return 1;
                case 0x26d43a74: pReaderInfo->eCurrToken = ET9_classic_token_Secondary; return 1;
                case 0xf2728211: pReaderInfo->eCurrToken = ET9_classic_token_SmartPunct; return 1;
                case 0xeeaf320c: pReaderInfo->eCurrToken = ET9_classic_token_Information; return 1;
                case 0xf6f5c76c: pReaderInfo->eCurrToken = ET9_classic_token_NonRegional; return 1;
                case 0xa9362831: pReaderInfo->eCurrToken = ET9_classic_token_String; return 1;
                case 0xef6067b8: pReaderInfo->eCurrToken = ET9_classic_token_Function; return 1;
                case 0x10c08338: pReaderInfo->eCurrToken = ET9_classic_token_Symbol; return 1;
                case 0x37b9286b: pReaderInfo->eCurrToken = ET9_classic_token_Bytes; return 1;
                case 0x157fca98: pReaderInfo->eCurrToken = ET9_classic_token_Class; return 1;

                case 0xf3dd08c3: pReaderInfo->nCurrNumber = ET9KEY_RELOAD         ; return 1;
                case 0x1a794346: pReaderInfo->nCurrNumber = ET9KEY_OK             ; return 1;
                case 0xeebac284: pReaderInfo->nCurrNumber = ET9KEY_CANCEL         ; return 1;
                case 0x005d4a91: pReaderInfo->nCurrNumber = ET9KEY_LEFT           ; return 1;
                case 0x1a7f44c5: pReaderInfo->nCurrNumber = ET9KEY_UP             ; return 1;
                case 0xb39de3b2: pReaderInfo->nCurrNumber = ET9KEY_RIGHT          ; return 1;
                case 0x912463cc: pReaderInfo->nCurrNumber = ET9KEY_DOWN           ; return 1;
                case 0x2d1de451: pReaderInfo->nCurrNumber = ET9KEY_BACK           ; return 1;
                case 0xc989d9ab: pReaderInfo->nCurrNumber = ET9KEY_TAB            ; return 1;
                case 0xf3a47d98: pReaderInfo->nCurrNumber = ET9KEY_PREVTAB        ; return 1;
                case 0x4355a423: pReaderInfo->nCurrNumber = ET9KEY_CLEAR          ; return 1;
                case 0x5169af1d: pReaderInfo->nCurrNumber = ET9KEY_NEW_LINE       ; return 1;
                case 0x6b18effa: pReaderInfo->nCurrNumber = ET9KEY_RETURN         ; return 1;
                case 0x028a8134: pReaderInfo->nCurrNumber = ET9KEY_CLOSE_SEL_LIST ; return 1;
                case 0x2eec1d49: pReaderInfo->nCurrNumber = ET9KEY_MENU           ; return 1;
                case 0xc9fd9078: pReaderInfo->nCurrNumber = ET9KEY_SHIFT          ; return 1;
                case 0xd644d753: pReaderInfo->nCurrNumber = ET9KEY_CONTROL        ; return 1;
                case 0xc039b5df: pReaderInfo->nCurrNumber = ET9KEY_ALT            ; return 1;
                case 0xbe7e7b6c: pReaderInfo->nCurrNumber = ET9KEY_PAUSE          ; return 1;
                case 0x1317e91f: pReaderInfo->nCurrNumber = ET9KEY_CAPS_LOCK      ; return 1;
                case 0x0dd0267f: pReaderInfo->nCurrNumber = ET9KEY_OPTION         ; return 1;
                case 0x999ad590: pReaderInfo->nCurrNumber = ET9KEY_EMOTICON       ; return 1;
                case 0x66c9903d: pReaderInfo->nCurrNumber = ET9KEY_ACCENTEDLAYOUT ; return 1;
                case 0xc507242c: pReaderInfo->nCurrNumber = ET9KEY_SYMBOLLAYOUT   ; return 1;
                case 0xc64933cd: pReaderInfo->nCurrNumber = ET9KEY_DIGITLAYOUT    ; return 1;
                case 0xd8cdf85a: pReaderInfo->nCurrNumber = ET9KEY_PUNCTLAYOUT    ; return 1;
                case 0xf1622fed: pReaderInfo->nCurrNumber = ET9KEY_MAINLAYOUT     ; return 1;
                case 0x294bb7f4: pReaderInfo->nCurrNumber = ET9KEY_MULTITAP       ; return 1;
                case 0xe0f61e8b: pReaderInfo->nCurrNumber = ET9KEY_ESCAPE         ; return 1;
                case 0xcf859d20: pReaderInfo->nCurrNumber = ET9KEY_PRIOR          ; return 1;
                case 0x5d7cf07d: pReaderInfo->nCurrNumber = ET9KEY_NEXT           ; return 1;
                case 0xc233f451: pReaderInfo->nCurrNumber = ET9KEY_END            ; return 1;
                case 0x4b35a449: pReaderInfo->nCurrNumber = ET9KEY_HOME           ; return 1;
                case 0x3a40999c: pReaderInfo->nCurrNumber = ET9KEY_SPACE          ; return 1;
                case 0xa933f762: pReaderInfo->nCurrNumber = ET9KEY_LANGUAGE       ; return 1;
                case 0xa7872c4e: pReaderInfo->nCurrNumber = ET9KEY_UNDO           ; return 1;
                case 0x17842e88: pReaderInfo->nCurrNumber = ET9KEY_REDO           ; return 1;
                case 0x4838450c: pReaderInfo->nCurrNumber = ET9KEY_HIDE           ; return 1;
                case 0x4448bb41: pReaderInfo->nCurrNumber = ET9KEY_COMMAND        ; return 1;
                case 0x22b5f979: pReaderInfo->nCurrNumber = ET9KEY_WINDOWS        ; return 1;
                case 0x0e411a5d: pReaderInfo->nCurrNumber = ET9KEY_SOFT1          ; return 1;
                case 0x0e411a5e: pReaderInfo->nCurrNumber = ET9KEY_SOFT2          ; return 1;
                case 0x0e411a5f: pReaderInfo->nCurrNumber = ET9KEY_SOFT3          ; return 1;
                case 0x0e411a60: pReaderInfo->nCurrNumber = ET9KEY_SOFT4          ; return 1;
                case 0x1c5785da: pReaderInfo->nCurrNumber = ET9KEY_OEMLAYOUT1     ; return 1;
                case 0x1c5785db: pReaderInfo->nCurrNumber = ET9KEY_OEMLAYOUT2     ; return 1;
                case 0x1c5785dc: pReaderInfo->nCurrNumber = ET9KEY_OEMLAYOUT3     ; return 1;
                case 0x1c5785dd: pReaderInfo->nCurrNumber = ET9KEY_OEMLAYOUT4     ; return 1;
                case 0xd7ccb173: pReaderInfo->nCurrNumber = ET9KEY_OEM_01         ; return 1;
                case 0xd7ccb174: pReaderInfo->nCurrNumber = ET9KEY_OEM_02         ; return 1;
                case 0xd7ccb175: pReaderInfo->nCurrNumber = ET9KEY_OEM_03         ; return 1;
                case 0xd7ccb176: pReaderInfo->nCurrNumber = ET9KEY_OEM_04         ; return 1;
                case 0xd7ccb177: pReaderInfo->nCurrNumber = ET9KEY_OEM_05         ; return 1;
                case 0xd7ccb178: pReaderInfo->nCurrNumber = ET9KEY_OEM_06         ; return 1;
                case 0xd7ccb179: pReaderInfo->nCurrNumber = ET9KEY_OEM_07         ; return 1;
                case 0xd7ccb17a: pReaderInfo->nCurrNumber = ET9KEY_OEM_08         ; return 1;
                case 0xd7ccb17b: pReaderInfo->nCurrNumber = ET9KEY_OEM_09         ; return 1;
                case 0xd7ccb1a3: pReaderInfo->nCurrNumber = ET9KEY_OEM_0A         ; return 1;
                case 0xd7ccb1a4: pReaderInfo->nCurrNumber = ET9KEY_OEM_0B         ; return 1;
                case 0xd7ccb1a5: pReaderInfo->nCurrNumber = ET9KEY_OEM_0C         ; return 1;
                case 0xd7ccb1a6: pReaderInfo->nCurrNumber = ET9KEY_OEM_0D         ; return 1;
                case 0xd7ccb1a7: pReaderInfo->nCurrNumber = ET9KEY_OEM_0E         ; return 1;
                case 0xd7ccb1a8: pReaderInfo->nCurrNumber = ET9KEY_OEM_0F         ; return 1;
                case 0xd7cdb1b1: pReaderInfo->nCurrNumber = ET9KEY_OEM_10         ; return 1;
                case 0xd7cdb1b2: pReaderInfo->nCurrNumber = ET9KEY_OEM_11         ; return 1;
                case 0xd7cdb1b3: pReaderInfo->nCurrNumber = ET9KEY_OEM_12         ; return 1;
                case 0xd7cdb1b4: pReaderInfo->nCurrNumber = ET9KEY_OEM_13         ; return 1;
                case 0xd7cdb1b5: pReaderInfo->nCurrNumber = ET9KEY_OEM_14         ; return 1;
                case 0xd7cdb1b6: pReaderInfo->nCurrNumber = ET9KEY_OEM_15         ; return 1;
                case 0xd7cdb1b7: pReaderInfo->nCurrNumber = ET9KEY_OEM_16         ; return 1;
                case 0xd7cdb1b8: pReaderInfo->nCurrNumber = ET9KEY_OEM_17         ; return 1;
                case 0xd7cdb1b9: pReaderInfo->nCurrNumber = ET9KEY_OEM_18         ; return 1;
                case 0xd7cdb1ba: pReaderInfo->nCurrNumber = ET9KEY_OEM_19         ; return 1;
                case 0xd7cdb1e2: pReaderInfo->nCurrNumber = ET9KEY_OEM_1A         ; return 1;
                case 0xd7cdb1e3: pReaderInfo->nCurrNumber = ET9KEY_OEM_1B         ; return 1;
                case 0xd7cdb1e4: pReaderInfo->nCurrNumber = ET9KEY_OEM_1C         ; return 1;
                case 0xd7cdb1e5: pReaderInfo->nCurrNumber = ET9KEY_OEM_1D         ; return 1;
                case 0xd7cdb1e6: pReaderInfo->nCurrNumber = ET9KEY_OEM_1E         ; return 1;
                case 0xd7cdb1e7: pReaderInfo->nCurrNumber = ET9KEY_OEM_1F         ; return 1;

                default:
                    pReaderInfo->eCurrToken = ET9_classic_token_Error;
                    return 0;
            }
        }
    }

    /* nothing recognized */

    pReaderInfo->eCurrToken = ET9_classic_token_Error;

    return 0;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                                             
 *                                 
 *
 *                                             
 */

ET9INLINE static ET9BOOL ET9LOCALCALL __ClassicReader_ConsumeToken(ET9KdbClassicReaderInfo    * const pReaderInfo,
                                                                   const ET9_classic_token            eToken)
{
    if (pReaderInfo->eCurrToken != eToken) {
        return 0;
    }

    if (!__ClassicReader_GetNextToken(pReaderInfo)) {
        return 0;
    }

    return 1;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                                             
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __ClassicReader_ReadProperties(ET9KdbClassicReaderInfo  * const pReaderInfo)
{
    ET9U8   bPrimaryID     = 0;
    ET9U8   bSecondaryID   = 0;
    ET9U8   bContentsMajor = 0;
    ET9U8   bContentsMinor = 0;
    ET9U16  wLayoutWidth   = 0;
    ET9U16  wLayoutHeight  = 0;

    if (pReaderInfo->eCurrToken == ET9_classic_token_Error) {
        return ET9STATUS_KDB_SYNTAX_ERROR;
    }

    while (pReaderInfo->eCurrToken != ET9_classic_token_LeftBracket) {
        if (!__ClassicReader_GetNextToken(pReaderInfo)) {
            return ET9STATUS_KDB_SYNTAX_ERROR;
        }
    }

    if (!(__ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_LeftBracket) &&
          __ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Layout) &&
          __ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Information) &&
          __ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_RightBracket))) {
        return ET9STATUS_KDB_SYNTAX_ERROR;
    }

    while (pReaderInfo->eCurrToken != ET9_classic_token_LeftBracket) {

        switch (pReaderInfo->eCurrToken)
        {
            case ET9_classic_token_Contents:
                __ClassicReader_GetNextToken(pReaderInfo);
                switch (pReaderInfo->eCurrToken)
                {
                    case ET9_classic_token_Major:
                        if (!(__ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Major) &&
                              __ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Ver) &&
                              __ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Equal) &&
                              __ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Number))) {
                            return ET9STATUS_KDB_SYNTAX_ERROR;
                        }
                        bContentsMajor = (ET9U8)pReaderInfo->nCurrNumber;
                        if (bContentsMajor != 3) {
                            return ET9STATUS_KDB_VERSION_ERROR;
                        }
                        break;
                    case ET9_classic_token_Minor:
                        if (!(__ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Minor) &&
                              __ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Ver) &&
                              __ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Equal) &&
                              __ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Number))) {
                            return ET9STATUS_KDB_SYNTAX_ERROR;
                        }
                        bContentsMinor = (ET9U8)pReaderInfo->nCurrNumber;
                        break;
                    default:
                        return ET9STATUS_KDB_SYNTAX_ERROR;
                }
                break;
            case ET9_classic_token_OEM:
                if (!(__ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_OEM) &&
                      __ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_ID) &&
                      __ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Equal) &&
                      __ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Number))) {
                    return ET9STATUS_KDB_SYNTAX_ERROR;
                }
                break;
            case ET9_classic_token_Symbol:
                __ClassicReader_GetNextToken(pReaderInfo);
                switch (pReaderInfo->eCurrToken)
                {
                    case ET9_classic_token_Bytes:
                        if (!(__ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Bytes) &&
                              __ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Equal) &&
                              __ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Number))) {
                            return ET9STATUS_KDB_SYNTAX_ERROR;
                        }
                        if (pReaderInfo->nCurrNumber != 2) {
                            return ET9STATUS_KDB_ATTRIBUTE_VALUE_ERROR;
                        }
                        break;
                    case ET9_classic_token_Class:
                        if (!(__ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Class) &&
                              __ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Equal) &&
                              __ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Number))) {
                            return ET9STATUS_KDB_SYNTAX_ERROR;
                        }
                        if (pReaderInfo->nCurrNumber != 15) {
                            return ET9STATUS_KDB_ATTRIBUTE_VALUE_ERROR;
                        }
                        break;
                    default:
                        return ET9STATUS_KDB_SYNTAX_ERROR;
                }
                break;
            case ET9_classic_token_Primary:
                if (!(__ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Primary) &&
                      __ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_ID) &&
                      __ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Equal) &&
                      __ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Number))) {
                    return ET9STATUS_KDB_SYNTAX_ERROR;
                }
                bPrimaryID = (ET9U8)pReaderInfo->nCurrNumber;
                break;
            case ET9_classic_token_Secondary:
                if (!(__ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Secondary) &&
                      __ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_ID) &&
                      __ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Equal) &&
                      __ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Number))) {
                    return ET9STATUS_KDB_SYNTAX_ERROR;
                }
                bSecondaryID = (ET9U8)pReaderInfo->nCurrNumber;
                break;
            case ET9_classic_token_Layout:
                __ClassicReader_GetNextToken(pReaderInfo);
                switch (pReaderInfo->eCurrToken)
                {
                    case ET9_classic_token_Width:
                        if (!(__ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Width) &&
                              __ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Equal) &&
                              __ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Number))) {
                            return ET9STATUS_KDB_SYNTAX_ERROR;
                        }
                        wLayoutWidth = (ET9U16)pReaderInfo->nCurrNumber;
                        break;
                    case ET9_classic_token_Height:
                        if (!(__ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Height) &&
                              __ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Equal) &&
                              __ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Number))) {
                            return ET9STATUS_KDB_SYNTAX_ERROR;
                        }
                        wLayoutHeight = (ET9U16)pReaderInfo->nCurrNumber;
                        break;
                    default:
                        return ET9STATUS_KDB_SYNTAX_ERROR;
                }
                break;
            case ET9_classic_token_Total:
                if (!(__ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Total) &&
                      __ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Pages) &&
                      __ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Equal) &&
                      __ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Number))) {
                    return ET9STATUS_KDB_SYNTAX_ERROR;
                }
                pReaderInfo->wTotalPages = (ET9U16)pReaderInfo->nCurrNumber;
                break;
            default:
                return ET9STATUS_KDB_SYNTAX_ERROR;
        }
    }

    {
        ET9STATUS eStatus;

        eStatus = ET9KDB_Load_SetProperties(pReaderInfo->pKDBInfo,
                                            bContentsMajor,
                                            bContentsMinor,
                                            bPrimaryID,
                                            bSecondaryID,
                                            wLayoutWidth,
                                            wLayoutHeight,
                                            pReaderInfo->wTotalPages);

        if (eStatus) {
            return eStatus;
        }
    }

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                                             
 *                                          
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __ClassicReader_ReadKey(ET9KdbClassicReaderInfo   * const pReaderInfo,
                                                      const ET9UINT                     nKeyNumber)
{
    const ET9UINT   nKeySourceLine = pReaderInfo->nCurrLine;

    ET9UINT         nCount;
    ET9U16          wLeft = 0xFFFF;
    ET9U16          wTop = 0xFFFF;
    ET9U16          wRight = 0xFFFF;
    ET9U16          wBottom = 0xFFFF;
    ET9LOADKEYTYPE  eKeyType;
    ET9UINT         nCharCount;
    ET9SYMB         psChars[ET9_KDB_MAX_KEY_CHARS];

    for (nCount = 4; nCount; --nCount) {

        ET9_classic_token eToken;

        if (!(__ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Key) &&
              __ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Number) &&
              pReaderInfo->nCurrNumber == nKeyNumber &&
              __ClassicReader_ConsumeToken(pReaderInfo, (eToken = pReaderInfo->eCurrToken)) &&
              __ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Equal) &&
              __ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Number))) {
            return ET9STATUS_KDB_SYNTAX_ERROR;
        }

        switch (eToken)
        {
            case ET9_classic_token_Left:
                wLeft = (ET9U16)pReaderInfo->nCurrNumber;
                break;
            case ET9_classic_token_Top:
                wTop = (ET9U16)pReaderInfo->nCurrNumber;
                break;
            case ET9_classic_token_Right:
                wRight = (ET9U16)pReaderInfo->nCurrNumber;
                break;
            case ET9_classic_token_Bottom:
                wBottom = (ET9U16)pReaderInfo->nCurrNumber;
                break;
            default:
                return ET9STATUS_KDB_SYNTAX_ERROR;
        }
    }

    if (wLeft == 0xFFFF || wTop == 0xFFFF || wRight == 0xFFFF || wBottom == 0xFFFF) {
        return ET9STATUS_KDB_SYNTAX_ERROR;
    }

    if (!(__ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Key) &&
          __ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Number) &&
          pReaderInfo->nCurrNumber == nKeyNumber &&
          __ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Type) &&
          __ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Equal))) {
        return ET9STATUS_KDB_SYNTAX_ERROR;
    }

    switch (pReaderInfo->eCurrToken)
    {
        case ET9_classic_token_Regional:
            eKeyType = ET9LKT_REGIONAL;
            break;
        case ET9_classic_token_NonRegional:
            eKeyType = ET9LKT_NONREGIONAL;
            break;
        case ET9_classic_token_SmartPunct:
            eKeyType = ET9LKT_SMARTPUNCT;
            break;
        case ET9_classic_token_String:
            eKeyType = ET9LKT_STRING;
            break;
        case ET9_classic_token_Function:
            eKeyType = ET9LKT_FUNCTION;
            break;
        default:
            return ET9STATUS_KDB_SYNTAX_ERROR;
    }

    __ClassicReader_GetNextToken(pReaderInfo);

    if (!(__ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Key) &&
          __ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Number) &&
          pReaderInfo->nCurrNumber == nKeyNumber &&
          __ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Total) &&
          __ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Chars) &&
          __ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Equal) &&
          __ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Number))) {
        return ET9STATUS_KDB_SYNTAX_ERROR;
    }

    nCharCount = pReaderInfo->nCurrNumber;

    if (!nCharCount) {
        return ET9STATUS_KDB_KEY_HAS_TOO_FEW_CHARS;
    }

    if (nCharCount > ET9_KDB_MAX_KEY_CHARS) {
        return ET9STATUS_KDB_KEY_HAS_TOO_MANY_CHARS;
    }

    if (eKeyType == ET9LKT_FUNCTION && nCharCount > 1) {
        return ET9STATUS_KDB_KEY_HAS_TOO_MANY_CHARS;
    }

    {
        ET9UINT nIndex;

        for (nIndex = 0; nIndex < nCharCount; ++nIndex) {

            if (!(__ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Key) &&
                  __ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Number) &&
                  pReaderInfo->nCurrNumber == nKeyNumber &&
                  __ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Char) &&
                  __ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Number) &&
                  pReaderInfo->nCurrNumber == nIndex &&
                  __ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Equal) &&
                  (pReaderInfo->eCurrToken == ET9_classic_token_Number || pReaderInfo->eCurrToken == ET9_classic_token_Function))) {
                return ET9STATUS_KDB_SYNTAX_ERROR;
            }

            if (eKeyType != ET9LKT_FUNCTION && pReaderInfo->eCurrToken == ET9_classic_token_Function) {
                return ET9STATUS_KDB_SYNTAX_ERROR;
            }

            psChars[nIndex] = (ET9SYMB)pReaderInfo->nCurrNumber;

            if (!__ClassicReader_ConsumeToken(pReaderInfo, pReaderInfo->eCurrToken)) {
                return ET9STATUS_KDB_SYNTAX_ERROR;
            }
        }
    }

    {
        ET9STATUS eStatus;

        eStatus = ET9KDB_Load_AddKey(pReaderInfo->pKDBInfo,
                                     ET9_KDB_LOAD_UNDEF_VALUE,  /* key index */
                                     eKeyType,
                                     wLeft,
                                     wTop,
                                     wRight,
                                     wBottom,
                                     nCharCount,
                                     psChars);

        if (eStatus) {
            pReaderInfo->nObjectLine = nKeySourceLine;
            return eStatus;
        }
    }

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                                             
 *                                           
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __ClassicReader_ReadPage(ET9KdbClassicReaderInfo  * const pReaderInfo,
                                                       const ET9U16                     wPageNum)
{
    ET9UINT nTotalKeys;

    if (pReaderInfo->eCurrToken == ET9_classic_token_Error) {
        return ET9STATUS_KDB_SYNTAX_ERROR;
    }

    if (wPageNum >= pReaderInfo->wTotalPages) {
        return ET9STATUS_INVALID_KDB_PAGE;
    }

    for (;;) {

        while (pReaderInfo->eCurrToken != ET9_classic_token_LeftBracket) {
            if (!__ClassicReader_SkipUntilLeftBracket(pReaderInfo)) {
                return ET9STATUS_KDB_PAGE_NOT_FOUND;
            }
        }

        if (!(__ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_LeftBracket) &&
              __ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Page) &&
              __ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Number))) {
            return ET9STATUS_KDB_SYNTAX_ERROR;
        }

        if (pReaderInfo->nCurrNumber == wPageNum) {
            break;
        }

        if (pReaderInfo->nCurrNumber > wPageNum) {
            return ET9STATUS_KDB_PAGE_NOT_FOUND;
        }
    }

    if (!(__ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Period) &&
          __ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Number) &&
          __ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_RightBracket))) {
        return ET9STATUS_KDB_SYNTAX_ERROR;
    }

    if (!(__ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Total) &&
          __ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Keys) &&
          __ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Equal) &&
          __ClassicReader_ConsumeToken(pReaderInfo, ET9_classic_token_Number))) {
        return ET9STATUS_KDB_SYNTAX_ERROR;
    }

    nTotalKeys = (ET9U16)pReaderInfo->nCurrNumber;

    {
        ET9STATUS   eStatus;
        ET9UINT     nIndex;

        for (nIndex = 0; nIndex < nTotalKeys; ++nIndex) {

            eStatus = __ClassicReader_ReadKey(pReaderInfo, nIndex);

            if (eStatus) {
                return eStatus;
            }
        }
    }

    if (pReaderInfo->eCurrToken != ET9_classic_token_LeftBracket && pReaderInfo->eCurrToken != ET9_classic_token_EOF) {
        return ET9STATUS_KDB_UNEXPECTED_CONTENT;
    }

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/**
 * @brief Load a dynamically using the simplified format for KDB descriptions.
 *
 * @param[in]     pKDBInfo            Pointer to keyboard information structure.
 * @param[in]     wPageNum            The target page number.
 * @param[in]     pbContent           Pointer to the content.
 * @param[in]     dwContentLen        Content length.
 * @param[out]    pnErrorLine         If an error occurs, this is an indication to where it happened (optional).
 *
 * @return ET9STATUS_NONE on success, otherwise return ET9 error code.
 */

ET9STATUS ET9FARCALL ET9KDB_Load_TextKDB(ET9KDBInfo             * const pKDBInfo,
                                         const ET9U16                   wPageNum,
                                         ET9U8            const * const pbContent,
                                         const ET9U32                   dwContentLen,
                                         ET9UINT                * const pnErrorLine)
{
    ET9STATUS eStatus;

    WLOG5(fprintf(pLogFile5, "ET9KDB_Load_TextKDB, pKDBInfo = %p, wPageNum %u\n", pKDBInfo, wPageNum);)

    eStatus = __ET9KDB_LoadValidityCheck(pKDBInfo);

    if (eStatus) {
        return eStatus;
    }

    if (!pbContent ||
        !dwContentLen) {
        return ET9STATUS_BAD_PARAM;
    }

    if ((dwContentLen > 1 && (pbContent[0] == 0xFF && pbContent[1] == 0xFE)) || (pbContent[0] == 0xFE && pbContent[1] == 0xFF)) {
        return ET9STATUS_KDB_WRONG_CONTENT_ENCODING;
    }

    if (pnErrorLine) {
        *pnErrorLine = 0;
    }

    {
        ET9KdbClassicReaderInfo sReaderInfo;

        _ET9ClearMem((ET9U8*)&sReaderInfo, sizeof(sReaderInfo));

        sReaderInfo.pKDBInfo = pKDBInfo;
        sReaderInfo.nCurrLine = 1;
        sReaderInfo.pbEndChar = &pbContent[dwContentLen];
        sReaderInfo.pbCurrChar = pbContent;
        sReaderInfo.pbContent = pbContent;
        sReaderInfo.dwContentLen = dwContentLen;
        sReaderInfo.eCurrToken = ET9_classic_token_Last;

        if (!__ClassicReader_GetNextToken(&sReaderInfo)) {
            return ET9STATUS_KDB_SYNTAX_ERROR;
        }

        eStatus = __ClassicReader_ReadProperties(&sReaderInfo);

        if (eStatus) {

            if (pnErrorLine) {
                *pnErrorLine = sReaderInfo.nObjectLine ? sReaderInfo.nObjectLine : sReaderInfo.nCurrLine;
            }

            return eStatus;
        }

        eStatus = __ClassicReader_ReadPage(&sReaderInfo, wPageNum);

        if (eStatus) {

            if (pnErrorLine) {
                *pnErrorLine = sReaderInfo.nObjectLine ? sReaderInfo.nObjectLine : sReaderInfo.nCurrLine;
            }

            return eStatus;
        }

        if (sReaderInfo.eCurrToken == ET9_classic_token_Error) {

            WLOG5(fprintf(pLogFile5, "  error @ line %u\n", sReaderInfo.nCurrLine);)

            if (pnErrorLine) {
                *pnErrorLine = sReaderInfo.nObjectLine ? sReaderInfo.nObjectLine : sReaderInfo.nCurrLine;
            }

            return ET9STATUS_KDB_SYNTAX_ERROR;
        }
    }

    return ET9STATUS_NONE;
}

/* ************************************************************************************************************** */
/* * XML Reader ************************************************************************************************* */
/* ************************************************************************************************************** */

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *           
 */

typedef enum ET9_xml_token_e {

    ET9_xml_token_A_addAltChars,
    ET9_xml_token_A_conditionValue,
    ET9_xml_token_A_defaultLayoutWidth,
    ET9_xml_token_A_defaultLayoutHeight,
    ET9_xml_token_A_horizontalBorder,
    ET9_xml_token_A_horizontalGap,
    ET9_xml_token_A_keyTop,
    ET9_xml_token_A_keyLeft,
    ET9_xml_token_A_keyWidth,
    ET9_xml_token_A_keyHeight,
    ET9_xml_token_A_keyIcon,
    ET9_xml_token_A_keyCodes,
    ET9_xml_token_A_keyCodesShifted,
    ET9_xml_token_A_keyLabel,
    ET9_xml_token_A_keyLabelShifted,
    ET9_xml_token_A_keyMultitapChars,
    ET9_xml_token_A_keyMultitapShiftedChars,
    ET9_xml_token_A_keyMultitapCodes,
    ET9_xml_token_A_keyMultitapShiftedCodes,
    ET9_xml_token_A_keyName,
    ET9_xml_token_A_keyPopupChars,
    ET9_xml_token_A_keyPopupShiftedChars,
    ET9_xml_token_A_keyPopupCodes,
    ET9_xml_token_A_keyPopupShiftedCodes,
    ET9_xml_token_A_keyType,
    ET9_xml_token_A_majorVersion,
    ET9_xml_token_A_minorVersion,
    ET9_xml_token_A_verticalBorder,
    ET9_xml_token_A_verticalGap,
    ET9_xml_token_A_voidSize,
    ET9_xml_token_A_primaryId,
    ET9_xml_token_A_secondaryId,
    ET9_xml_token_A_supportsExact,

    ET9_xml_token_A_unknown,

    ET9_xml_token_AttributeCount,

    ET9_xml_token_TagStartKeyboard,
    ET9_xml_token_TagEndKeyboard,
    ET9_xml_token_TagStartArea,
    ET9_xml_token_TagEndArea,
    ET9_xml_token_TagStartRow,
    ET9_xml_token_TagEndRow,
    ET9_xml_token_TagStartKey,
    ET9_xml_token_TagStartVoid,

    ET9_xml_token_TagEnd,
    ET9_xml_token_TagEndFinal,

    ET9_xml_token_String,

    ET9_xml_token_Equal,

    ET9_xml_token_EOF,

    ET9_xml_token_Error,

    ET9_xml_token_Last

} ET9_xml_token;

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *              
 */

typedef enum ET9_xml_constant_e {

    ET9_xml_C_regional,
    ET9_xml_C_nonRegional,
    ET9_xml_C_smartPunct,
    ET9_xml_C_string,
    ET9_xml_C_function,
    ET9_xml_C_true,
    ET9_xml_C_false,

    ET9_xml_C_Unknown,

    ET9_xml_C_Last

} ET9_xml_constant;

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *             
 */

typedef enum ET9_xml_entity_e {

    ET9_xml_E_keyboard,
    ET9_xml_E_row,
    ET9_xml_E_key,

    ET9_xml_E_Count

} ET9_xml_entity;

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *               
 */

typedef enum ET9_xml_copyOp_e {

    ET9_xml_copyOp_none,
    ET9_xml_copyOp_toLower,

    ET9_xml_copyOp_Count

} ET9_xml_copyOp;

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                              
 */

typedef struct ET9KdbXmlReaderContext_s {

    ET9U8                       bPrimaryID;                                     /* < -IDR-     */
    ET9U8                       bSecondaryID;                                   /* < -IDR-     */
    ET9U8                       bMajorVersion;                                  /* < -IDR-     */
    ET9U8                       bMinorVersion;                                  /* < -IDR-     */
    ET9U16                      wDefaultLayoutWidth;                            /* < -IDR-     */
    ET9U16                      wDefaultLayoutHeight;                           /* < -IDR-     */

    ET9_xml_booleanValue        eAddAltChars;                                   /* < -IDR-     */
    ET9_xml_booleanValue        eSupportsExact;                                 /* < -IDR-     */

    ET9FLOAT                    fKeyTop;                                        /* < -IDR-     */
    ET9_xml_valueSuffix         eKeyTopSuffix;                                  /* < -IDR-     */

    ET9FLOAT                    fKeyLeft;                                       /* < -IDR-     */
    ET9_xml_valueSuffix         eKeyLeftSuffix;                                 /* < -IDR-     */

    ET9FLOAT                    fKeyWidth;                                      /* < -IDR-     */
    ET9_xml_valueSuffix         eKeyWidthSuffix;                                /* < -IDR-     */

    ET9FLOAT                    fKeyHeight;                                     /* < -IDR-     */
    ET9_xml_valueSuffix         eKeyHeightSuffix;                               /* < -IDR-     */

    ET9FLOAT                    fVoidSize;                                      /* < -IDR-     */
    ET9_xml_valueSuffix         eVoidSizeSuffix;                                /* < -IDR-     */

    ET9FLOAT                    fVerticalBorder;                                /* < -IDR-     */
    ET9_xml_valueSuffix         eVerticalBorderSuffix;                          /* < -IDR-     */

    ET9FLOAT                    fHorizontalBorder;                              /* < -IDR-     */
    ET9_xml_valueSuffix         eHorizontalBorderSuffix;                        /* < -IDR-     */

    ET9FLOAT                    fVerticalGap;                                   /* < -IDR-     */
    ET9_xml_valueSuffix         eVerticalGapSuffix;                             /* < -IDR-     */

    ET9FLOAT                    fHorizontalGap;                                 /* < -IDR-     */
    ET9_xml_valueSuffix         eHorizontalGapSuffix;                           /* < -IDR-     */

    ET9UINT                     nConditionValue;                                /* < -IDR-     */

    ET9_xml_keyType             eKeyType;                                       /* < -IDR-     */
    ET9_xml_rowType             eRowType;                                       /* < -IDR-     */

    ET9UINT                     nIconCharCount;                                 /* < -IDR-     */
    ET9SYMB                     *psIconChars;                                   /* < -IDR-     */

    ET9UINT                     nLabelCharCount;                                /* < -IDR-     */
    ET9SYMB                     *psLabelChars;                                  /* < -IDR-     */

    ET9UINT                     nLabelShiftedCharCount;                         /* < -IDR-     */
    ET9SYMB                     *psLabelShiftedChars;                           /* < -IDR-     */

    ET9UINT                     nKeyCharCount;                                  /* < -IDR-     */
    ET9SYMB                     *psKeyChars;                                    /* < -IDR-     */

    ET9UINT                     nKeyShiftedCharCount;                           /* < -IDR-     */
    ET9SYMB                     *psKeyShiftedChars;                             /* < -IDR-     */

    ET9UINT                     nKeyPopupCharCount;                             /* < -IDR-     */
    ET9SYMB                     *psKeyPopupChars;                               /* < -IDR-     */

    ET9UINT                     nKeyPopupShiftedCharCount;                      /* < -IDR-     */
    ET9SYMB                     *psKeyPopupShiftedChars;                        /* < -IDR-     */

    ET9UINT                     nKeyMultitapCharCount;                          /* < -IDR-     */
    ET9SYMB                     *psKeyMultitapChars;                            /* < -IDR-     */

    ET9UINT                     nKeyMultitapShiftedCharCount;                   /* < -IDR-     */
    ET9SYMB                     *psKeyMultitapShiftedChars;                     /* < -IDR-     */

    ET9BOOL                     pbAttributeSet[ET9_xml_token_AttributeCount];   /* < -IDR-     */

} ET9KdbXmlReaderContext;                                                       /* < -IDR-     */

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                              
 */

typedef struct ET9KdbXmlReaderInfo_s {

    ET9KDBInfo                  *pKDBInfo;                                      /* < -IDR-     */

    ET9_xml_token               eCurrToken;                                     /* < -IDR-     */

    ET9UINT                     nCurrLine;                                      /* < -IDR-     */

    ET9UINT                     nUnknownAttributes;                             /* < -IDR-     */

    ET9U16                      wLayoutWidth;                                   /* < -IDR-     */
    ET9U16                      wLayoutHeight;                                  /* < -IDR-     */
    ET9UINT                     nConditionValue;                                /* < -IDR-     */

    ET9_xml_token               eCurrAttribute;                                 /* < -IDR-     */
    ET9_xml_constant            eCurrConstant;                                  /* < -IDR-     */
    ET9UINT                     nCurrInt;                                       /* < -IDR-     */
    ET9FLOAT                    fCurrFloat;                                     /* < -IDR-     */
    ET9_xml_valueSuffix         eCurrValueSuffix;                               /* < -IDR-     */
    ET9UINT                     nCurrCharCount;                                 /* < -IDR-     */
    ET9SYMB                     psCurrChars[ET9_KDB_MAX_KEY_CHARS];             /* < -IDR-     */

    ET9KdbXmlReaderContext      pContexts[ET9_xml_E_Count];                     /* < -IDR-     */

    ET9U8               const * pbEndChar;                                      /* < -IDR-     */
    ET9U8               const * pbCurrChar;                                     /* < -IDR-     */

    ET9U8               const * pbContent;                                      /* < -IDR-     */
    ET9U32                      dwContentLen;                                   /* < -IDR-     */
} ET9KdbXmlReaderInfo;                                                          /* < -IDR-     */

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                           
 *
 *                         
 */

static ET9_xml_keyType ET9LOCALCALL __XmlReader_ConstantToKeyType(const ET9_xml_constant eConstant)
{
    switch (eConstant)
    {
        case ET9_xml_C_regional:
            return ET9_xml_keyType_regional;
        case ET9_xml_C_nonRegional:
            return ET9_xml_keyType_nonRegional;
        case ET9_xml_C_smartPunct:
            return ET9_xml_keyType_smartPunct;
        case ET9_xml_C_string:
            return ET9_xml_keyType_string;
        case ET9_xml_C_function:
            return ET9_xml_keyType_function;

        default:
            ET9Assert(0);
            return ET9_xml_keyType_undef;
    }
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                           
 *
 *                         
 */

static ET9LOADKEYTYPE ET9LOCALCALL __XmlReader_XmlKeyTypeToLKT(const ET9_xml_keyType eKeyType)
{
    switch (eKeyType)
    {
        case ET9_xml_keyType_regional:
            return ET9LKT_REGIONAL;
        case ET9_xml_keyType_nonRegional:
            return ET9LKT_NONREGIONAL;
        case ET9_xml_keyType_smartPunct:
            return ET9LKT_SMARTPUNCT;
        case ET9_xml_keyType_string:
            return ET9LKT_STRING;
        case ET9_xml_keyType_function:
            return ET9LKT_FUNCTION;

        default:
            ET9Assert(0);
            return ET9LKT_LAST;
    }
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                           
 *
 *                         
 */

static ET9_xml_booleanValue ET9LOCALCALL __XmlReader_ConstantToBooleanValue(const ET9_xml_constant eConstant)
{
    switch (eConstant)
    {
        case ET9_xml_C_true:
            return ET9_xml_booleanValue_yes;
        case ET9_xml_C_false:
            return ET9_xml_booleanValue_no;
        default:
            ET9Assert(0);
            return ET9_xml_booleanValue_undef;
    }
}

#ifdef ET9_DEBUGLOG5

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                           
 *
 *                                             
 */

static char const * ET9LOCALCALL __XmlReader_AttributeToString(const ET9_xml_token eToken)
{
    switch (eToken)
    {
        case ET9_xml_token_A_keyTop:                    return "keyTop";
        case ET9_xml_token_A_keyLeft:                   return "keyLeft";
        case ET9_xml_token_A_keyWidth:                  return "keyWidth";
        case ET9_xml_token_A_keyHeight:                 return "keyHeight";
        case ET9_xml_token_A_verticalBorder:            return "verticalBorder";
        case ET9_xml_token_A_horizontalBorder:          return "horizontalBorder";
        case ET9_xml_token_A_verticalGap:               return "verticalGap";
        case ET9_xml_token_A_horizontalGap:             return "horizontalGap";
        case ET9_xml_token_A_majorVersion:              return "majorVersion";
        case ET9_xml_token_A_minorVersion:              return "minorVersion";
        case ET9_xml_token_A_primaryId:                 return "primaryId";
        case ET9_xml_token_A_secondaryId:               return "secondaryId";
        case ET9_xml_token_A_supportsExact:             return "supportsExact";
        case ET9_xml_token_A_conditionValue:            return "conditionValue";
        case ET9_xml_token_A_keyType:                   return "keyType";
        case ET9_xml_token_A_keyIcon:                   return "keyIcon";
        case ET9_xml_token_A_keyName:                   return "keyName";
        case ET9_xml_token_A_keyLabel:                  return "keyLabel";
        case ET9_xml_token_A_keyLabelShifted:           return "keyLabelShifted";
        case ET9_xml_token_A_keyCodes:                  return "keyCodes";
        case ET9_xml_token_A_keyCodesShifted:           return "keyCodesShifted";
        case ET9_xml_token_A_keyPopupChars:             return "keyPopupChars";
        case ET9_xml_token_A_keyPopupShiftedChars:      return "keyPopupShiftedChars";
        case ET9_xml_token_A_keyPopupCodes:             return "keyPopupCodes";
        case ET9_xml_token_A_keyPopupShiftedCodes:      return "keyPopupShiftedCodes";
        case ET9_xml_token_A_keyMultitapChars:          return "keyMultitapChars";
        case ET9_xml_token_A_keyMultitapShiftedChars:   return "keyMultitapShiftedChars";
        case ET9_xml_token_A_keyMultitapCodes:          return "keyMultitapCodes";
        case ET9_xml_token_A_keyMultitapShiftedCodes:   return "keyMultitapShiftedCodes";
        case ET9_xml_token_A_voidSize:                  return "voidSize";
        case ET9_xml_token_A_unknown:                   return "<unknown>";
        default: return "<\?\?\?>";
    }
}

#endif

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                                             
 *
 *                                             
 */

static ET9BOOL ET9LOCALCALL __XmlReader_ExtractConstant(ET9KdbXmlReaderInfo  * const pReaderInfo)
{
    ET9KDBPrivate * const pPrivate = &pReaderInfo->pKDBInfo->Private;

    ET9SYMB *psSymb;
    ET9UINT nCount;
    ET9U32 dwHashValue;

    dwHashValue = 0;
    psSymb = &pPrivate->wm.xmlReader.psString[0];
    for (nCount = pPrivate->wm.xmlReader.nStringLen; nCount; --nCount, ++psSymb) {
        dwHashValue = *psSymb + (65599 * dwHashValue);
    }

    switch (dwHashValue)
    {
        case 0x479f9c3f: pReaderInfo->eCurrConstant = ET9_xml_C_regional; break;
        case 0x94bf8f8c: pReaderInfo->eCurrConstant = ET9_xml_C_nonRegional; break;
        case 0x74e6a1f1: pReaderInfo->eCurrConstant = ET9_xml_C_smartPunct; break;
        case 0xa9362831: pReaderInfo->eCurrConstant = ET9_xml_C_string; break;
        case 0xef6067b8: pReaderInfo->eCurrConstant = ET9_xml_C_function; break;
        case 0x4dae9b2e: pReaderInfo->eCurrConstant = ET9_xml_C_true; break;
        case 0xe6e499e3: pReaderInfo->eCurrConstant = ET9_xml_C_false; break;

        default:         pReaderInfo->eCurrConstant = ET9_xml_C_Unknown; break;
    }

    return 1;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                                             
 *
 *                                             
 */

static ET9BOOL ET9LOCALCALL __XmlReader_ExtractName(ET9KdbXmlReaderInfo  * const pReaderInfo)
{
    ET9KDBPrivate * const pPrivate = &pReaderInfo->pKDBInfo->Private;

    ET9SYMB *psSymb;
    ET9UINT nCount;
    ET9U32 dwHashValue;

    dwHashValue = 0;
    psSymb = &pPrivate->wm.xmlReader.psString[0];
    for (nCount = pPrivate->wm.xmlReader.nStringLen; nCount; --nCount, ++psSymb) {
        dwHashValue = *psSymb + (65599 * dwHashValue);
    }

    switch (dwHashValue)
    {
        case 0xab4cb8e3: pReaderInfo->psCurrChars[0] = ET9KEY_RELOAD         ; break;
        case 0x40f92366: pReaderInfo->psCurrChars[0] = ET9KEY_OK             ; break;
        case 0xa62a72a4: pReaderInfo->psCurrChars[0] = ET9KEY_CANCEL         ; break;
        case 0x348f12b1: pReaderInfo->psCurrChars[0] = ET9KEY_LEFT           ; break;
        case 0x40ff24e5: pReaderInfo->psCurrChars[0] = ET9KEY_UP             ; break;
        case 0x53fe2372: pReaderInfo->psCurrChars[0] = ET9KEY_RIGHT          ; break;
        case 0xc5562bec: pReaderInfo->psCurrChars[0] = ET9KEY_DOWN           ; break;
        case 0x614fac71: pReaderInfo->psCurrChars[0] = ET9KEY_BACK           ; break;
        case 0x2322016b: pReaderInfo->psCurrChars[0] = ET9KEY_TAB            ; break;
        case 0xc840d558: pReaderInfo->psCurrChars[0] = ET9KEY_PREVTAB        ; break;
        case 0xe3b5e3e3: pReaderInfo->psCurrChars[0] = ET9KEY_CLEAR          ; break;
        case 0x792f275d: pReaderInfo->psCurrChars[0] = ET9KEY_NEW_LINE       ; break;
        case 0x2288a01a: pReaderInfo->psCurrChars[0] = ET9KEY_RETURN         ; break;
        case 0xc72d7194: pReaderInfo->psCurrChars[0] = ET9KEY_CLOSE_SEL_LIST ; break;
        case 0x631de569: pReaderInfo->psCurrChars[0] = ET9KEY_MENU           ; break;
        case 0x6a5dd038: pReaderInfo->psCurrChars[0] = ET9KEY_SHIFT          ; break;
        case 0xaae12f13: pReaderInfo->psCurrChars[0] = ET9KEY_CONTROL        ; break;
        case 0x19d1dd9f: pReaderInfo->psCurrChars[0] = ET9KEY_ALT            ; break;
        case 0x5edebb2c: pReaderInfo->psCurrChars[0] = ET9KEY_PAUSE          ; break;
        case 0x0cf038ff: pReaderInfo->psCurrChars[0] = ET9KEY_CAPS_LOCK      ; break;
        case 0xc53fd69f: pReaderInfo->psCurrChars[0] = ET9KEY_OPTION         ; break;
        case 0x43d46db0: pReaderInfo->psCurrChars[0] = ET9KEY_EMOTICON       ; break;
        case 0x4aa8e05d: pReaderInfo->psCurrChars[0] = ET9KEY_ACCENTEDLAYOUT ; break;
        case 0x8b788c4c: pReaderInfo->psCurrChars[0] = ET9KEY_SYMBOLLAYOUT   ; break;
        case 0xe3b9bb8d: pReaderInfo->psCurrChars[0] = ET9KEY_DIGITLAYOUT    ; break;
        case 0xf63e801a: pReaderInfo->psCurrChars[0] = ET9KEY_PUNCTLAYOUT    ; break;
        case 0xf7f1b00d: pReaderInfo->psCurrChars[0] = ET9KEY_MAINLAYOUT     ; break;
        case 0xd3855014: pReaderInfo->psCurrChars[0] = ET9KEY_MULTITAP       ; break;
        case 0x9865ceab: pReaderInfo->psCurrChars[0] = ET9KEY_ESCAPE         ; break;
        case 0x6fe5dce0: pReaderInfo->psCurrChars[0] = ET9KEY_PRIOR          ; break;
        case 0x91aeb89d: pReaderInfo->psCurrChars[0] = ET9KEY_NEXT           ; break;
        case 0x1bcc1c11: pReaderInfo->psCurrChars[0] = ET9KEY_END            ; break;
        case 0x7f676c69: pReaderInfo->psCurrChars[0] = ET9KEY_HOME           ; break;
        case 0xdaa0d95c: pReaderInfo->psCurrChars[0] = ET9KEY_SPACE          ; break;
        case 0x536d8f82: pReaderInfo->psCurrChars[0] = ET9KEY_LANGUAGE       ; break;
        case 0xdbb8f46e: pReaderInfo->psCurrChars[0] = ET9KEY_UNDO           ; break;
        case 0x4bb5f6a8: pReaderInfo->psCurrChars[0] = ET9KEY_REDO           ; break;
        case 0x7c6a0d2c: pReaderInfo->psCurrChars[0] = ET9KEY_HIDE           ; break;
        case 0x18e51301: pReaderInfo->psCurrChars[0] = ET9KEY_COMMAND        ; break;
        case 0xf7525139: pReaderInfo->psCurrChars[0] = ET9KEY_WINDOWS        ; break;
        case 0xaea15a3d: pReaderInfo->psCurrChars[0] = ET9KEY_SOFT1          ; break;
        case 0xaea15a3e: pReaderInfo->psCurrChars[0] = ET9KEY_SOFT2          ; break;
        case 0xaea15a3f: pReaderInfo->psCurrChars[0] = ET9KEY_SOFT3          ; break;
        case 0xaea15a40: pReaderInfo->psCurrChars[0] = ET9KEY_SOFT4          ; break;
        case 0x22e7061a: pReaderInfo->psCurrChars[0] = ET9KEY_OEMLAYOUT1     ; break;
        case 0x22e7061b: pReaderInfo->psCurrChars[0] = ET9KEY_OEMLAYOUT2     ; break;
        case 0x22e7061c: pReaderInfo->psCurrChars[0] = ET9KEY_OEMLAYOUT3     ; break;
        case 0x22e7061d: pReaderInfo->psCurrChars[0] = ET9KEY_OEMLAYOUT4     ; break;
        case 0x9f1e59b3: pReaderInfo->psCurrChars[0] = ET9KEY_OEM_01         ; break;
        case 0x9f1e59b4: pReaderInfo->psCurrChars[0] = ET9KEY_OEM_02         ; break;
        case 0x9f1e59b5: pReaderInfo->psCurrChars[0] = ET9KEY_OEM_03         ; break;
        case 0x9f1e59b6: pReaderInfo->psCurrChars[0] = ET9KEY_OEM_04         ; break;
        case 0x9f1e59b7: pReaderInfo->psCurrChars[0] = ET9KEY_OEM_05         ; break;
        case 0x9f1e59b8: pReaderInfo->psCurrChars[0] = ET9KEY_OEM_06         ; break;
        case 0x9f1e59b9: pReaderInfo->psCurrChars[0] = ET9KEY_OEM_07         ; break;
        case 0x9f1e59ba: pReaderInfo->psCurrChars[0] = ET9KEY_OEM_08         ; break;
        case 0x9f1e59bb: pReaderInfo->psCurrChars[0] = ET9KEY_OEM_09         ; break;
        case 0x9f1e59c3: pReaderInfo->psCurrChars[0] = ET9KEY_OEM_0A         ; break;
        case 0x9f1e59c4: pReaderInfo->psCurrChars[0] = ET9KEY_OEM_0B         ; break;
        case 0x9f1e59c5: pReaderInfo->psCurrChars[0] = ET9KEY_OEM_0C         ; break;
        case 0x9f1e59c6: pReaderInfo->psCurrChars[0] = ET9KEY_OEM_0D         ; break;
        case 0x9f1e59c7: pReaderInfo->psCurrChars[0] = ET9KEY_OEM_0E         ; break;
        case 0x9f1e59c8: pReaderInfo->psCurrChars[0] = ET9KEY_OEM_0F         ; break;
        case 0x9f1f59f1: pReaderInfo->psCurrChars[0] = ET9KEY_OEM_10         ; break;
        case 0x9f1f59f2: pReaderInfo->psCurrChars[0] = ET9KEY_OEM_11         ; break;
        case 0x9f1f59f3: pReaderInfo->psCurrChars[0] = ET9KEY_OEM_12         ; break;
        case 0x9f1f59f4: pReaderInfo->psCurrChars[0] = ET9KEY_OEM_13         ; break;
        case 0x9f1f59f5: pReaderInfo->psCurrChars[0] = ET9KEY_OEM_14         ; break;
        case 0x9f1f59f6: pReaderInfo->psCurrChars[0] = ET9KEY_OEM_15         ; break;
        case 0x9f1f59f7: pReaderInfo->psCurrChars[0] = ET9KEY_OEM_16         ; break;
        case 0x9f1f59f8: pReaderInfo->psCurrChars[0] = ET9KEY_OEM_17         ; break;
        case 0x9f1f59f9: pReaderInfo->psCurrChars[0] = ET9KEY_OEM_18         ; break;
        case 0x9f1f59fa: pReaderInfo->psCurrChars[0] = ET9KEY_OEM_19         ; break;
        case 0x9f1f5a02: pReaderInfo->psCurrChars[0] = ET9KEY_OEM_1A         ; break;
        case 0x9f1f5a03: pReaderInfo->psCurrChars[0] = ET9KEY_OEM_1B         ; break;
        case 0x9f1f5a04: pReaderInfo->psCurrChars[0] = ET9KEY_OEM_1C         ; break;
        case 0x9f1f5a05: pReaderInfo->psCurrChars[0] = ET9KEY_OEM_1D         ; break;
        case 0x9f1f5a06: pReaderInfo->psCurrChars[0] = ET9KEY_OEM_1E         ; break;
        case 0x9f1f5a07: pReaderInfo->psCurrChars[0] = ET9KEY_OEM_1F         ; break;

        default:
            return 0;
    }

    pReaderInfo->nCurrCharCount = 1;

    return 1;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                                             
 *                               
 *                               
 *
 *                                  
 */

static ET9UINT ET9LOCALCALL __XmlReader_ReadInt(ET9KdbXmlReaderInfo     * const pReaderInfo,
                                                const ET9UINT                   nStartPos,
                                                ET9UINT                 * const pnValue)
{
    ET9KDBPrivate * const pPrivate = &pReaderInfo->pKDBInfo->Private;

    ET9SYMB *psSymb;
    ET9UINT nCount;
    ET9UINT nValue;

    if (nStartPos >= pPrivate->wm.xmlReader.nStringLen) {
        return 0;
    }

    nCount = pPrivate->wm.xmlReader.nStringLen - nStartPos;
    psSymb = &pPrivate->wm.xmlReader.psString[nStartPos];

    if (nCount >= 2 && *psSymb == '0' && (*(psSymb + 1) == 'x' || *(psSymb + 1) == 'X')) {

        nCount -= 2;
        psSymb += 2;

        nValue = 0;
        for (; nCount; --nCount, ++psSymb) {
            if (*psSymb >= '0' && *psSymb <= '9') {
                nValue = nValue * 16 + (*psSymb - '0');
            }
            else if (*psSymb >= 'a' && *psSymb <= 'f') {
                nValue = nValue * 16 + (*psSymb - 'a' + 10);
            }
            else if (*psSymb >= 'A' && *psSymb <= 'F') {
                nValue = nValue * 16 + (*psSymb - 'A' + 10);
            }
            else {
                break;
            }
        }
    }
    else {

        nValue = 0;
        for (; nCount; --nCount, ++psSymb) {
            if (*psSymb >= '0' && *psSymb <= '9') {
                nValue = nValue * 10 + (*psSymb - '0');
            }
            else {
                break;
            }
        }
    }

    *pnValue = nValue;

    return (ET9UINT)(psSymb - &pPrivate->wm.xmlReader.psString[nStartPos]);
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                                             
 *
 *                                             
 */

static ET9BOOL ET9LOCALCALL __XmlReader_ExtractInt(ET9KdbXmlReaderInfo  * const pReaderInfo)
{
    ET9KDBPrivate * const pPrivate = &pReaderInfo->pKDBInfo->Private;

    ET9UINT nConsumedCount;

    if (!pPrivate->wm.xmlReader.nStringLen) {
        return 0;
    }

    nConsumedCount = __XmlReader_ReadInt(pReaderInfo, 0, &pReaderInfo->nCurrInt);

    if (nConsumedCount != pPrivate->wm.xmlReader.nStringLen) {
        return 0;
    }

    return 1;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                                             
 *
 *                                             
 */

static ET9BOOL ET9LOCALCALL __XmlReader_ExtractFloatWithSuffix(ET9KdbXmlReaderInfo  * const pReaderInfo)
{
    ET9KDBPrivate * const pPrivate = &pReaderInfo->pKDBInfo->Private;

    ET9SYMB *psSymb;
    ET9UINT nCount;
    ET9FLOAT fValue;
    ET9_xml_valueSuffix eSuffix;

    if (!pPrivate->wm.xmlReader.nStringLen) {
        return 0;
    }

    fValue = 0;
    psSymb = &pPrivate->wm.xmlReader.psString[0];
    for (nCount = pPrivate->wm.xmlReader.nStringLen; nCount; --nCount, ++psSymb) {
        if (*psSymb >= '0' && *psSymb <= '9') {
            fValue = fValue * 10 + (*psSymb - '0');
        }
        else {
            break;
        }
    }

    if (nCount == pPrivate->wm.xmlReader.nStringLen) {
        return 0;
    }

    if (nCount && *psSymb == '.') {

        const ET9UINT nStartCount = nCount;

        ET9FLOAT fDiv = 1;

        --nCount;
        ++psSymb;

        for (; nCount; --nCount, ++psSymb) {
            if (*psSymb >= '0' && *psSymb <= '9') {
                fDiv = fDiv * 10;
                fValue = fValue + ((ET9FLOAT)(*psSymb - '0') / fDiv);
            }
            else {
                break;
            }
        }

        if (nCount + 1 == nStartCount) {
            return 0;
        }
    }

    if (!nCount) {
        return 0;
    }

    if (nCount == 1 && *psSymb == '%') {
        eSuffix = ET9_xml_valueSuffix_percent;
    }
    else if (nCount == 2 && *psSymb == 'd' && *(psSymb + 1) == 'p') {
        eSuffix = ET9_xml_valueSuffix_pixelsDI;
    }
    else {
        return 0;
    }

    pReaderInfo->fCurrFloat = fValue;
    pReaderInfo->eCurrValueSuffix = eSuffix;

    return 1;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                                             
 *
 *                                             
 */

static ET9BOOL ET9LOCALCALL __XmlReader_ExtractChars(ET9KdbXmlReaderInfo  * const pReaderInfo)
{
    ET9KDBPrivate * const pPrivate = &pReaderInfo->pKDBInfo->Private;

    if (pPrivate->wm.xmlReader.nStringLen > ET9_KDB_MAX_KEY_CHARS) {
        return 0;
    }

    _ET9SymCopy(pReaderInfo->psCurrChars, pPrivate->wm.xmlReader.psString, pPrivate->wm.xmlReader.nStringLen);

    pReaderInfo->nCurrCharCount = pPrivate->wm.xmlReader.nStringLen;

    return 1;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                                             
 *
 *                                             
 */

static ET9BOOL ET9LOCALCALL __XmlReader_ExtractCodes(ET9KdbXmlReaderInfo  * const pReaderInfo)
{
    ET9KDBPrivate * const pPrivate = &pReaderInfo->pKDBInfo->Private;

    const ET9UINT nTotCount = pPrivate->wm.xmlReader.nStringLen;

    ET9UINT nTotConsumedCount;

    pReaderInfo->nCurrCharCount = 0;

    for (nTotConsumedCount = 0; nTotConsumedCount < nTotCount;) {

        ET9UINT nValue;
        ET9UINT nConsumedCount;

        if (nTotConsumedCount) {
            if (pPrivate->wm.xmlReader.psString[nTotConsumedCount] == ',') {
                ++nTotConsumedCount;
            }
            else {
                return 0;
            }
        }

        while (nTotConsumedCount < nTotCount && pPrivate->wm.xmlReader.psString[nTotConsumedCount] <= 0x20) {
            ++nTotConsumedCount;
        }

        nConsumedCount = __XmlReader_ReadInt(pReaderInfo, nTotConsumedCount, &nValue);

        if (!nConsumedCount) {
            return 0;
        }

        if (!nValue || nValue > 0xFFFF) {
            return 0;
        }

        if (pReaderInfo->nCurrCharCount >= ET9_KDB_MAX_KEY_CHARS) {
            return 0;
        }

        pReaderInfo->psCurrChars[pReaderInfo->nCurrCharCount++] = (ET9SYMB)nValue;

        nTotConsumedCount += nConsumedCount;
    }

    if (nTotConsumedCount != nTotCount) {
            return 0;
    }

    return 1;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                               
 *                               
 *                               
 *                               
 *                               
 *
 *                                             
 */

static ET9BOOL ET9LOCALCALL __XmlReader_DecodeString(ET9U8        const * const pbFirst,
                                                     ET9U8        const * const pbLast,
                                                     ET9SYMB            * const psString,
                                                     const ET9UINT              nMaxStringLen,
                                                     ET9UINT            * const pnStringLen)
{
    ET9SYMB  * const psEnd = &psString[nMaxStringLen];
    ET9U8      const *pbCurr;
    ET9SYMB          *psChar;

    *pnStringLen = 0;

    psChar = &psString[0];
    for (pbCurr = pbFirst; pbCurr <= pbLast;) {

        ET9U8 bCharsConsumed;

        if (psChar + 1 >= psEnd) {
            return 0;
        }

        if (*pbCurr == '&') {
            bCharsConsumed = _ET9DecodeSpecialChar(pbCurr, pbLast + 1, psChar);
        }
        else if (*pbCurr >= 0xC0) {
            bCharsConsumed = _ET9Utf8ToSymb(pbCurr, pbLast + 1, psChar);
        }
        else {
            *psChar = *pbCurr;
            bCharsConsumed = 1;
        }

        if (!bCharsConsumed || !*psChar) {
            return 0;
        }

        pbCurr += bCharsConsumed;
        ++psChar;
    }

    *pnStringLen = (ET9UINT)(psChar - &psString[0]);

    WLOG5({
        ET9UINT nCount;
        ET9SYMB const * psCurr;

        fprintf(pLogFile5, "  string: ");

        psCurr = &psString[0];
        for (nCount = *pnStringLen; nCount; --nCount, ++psCurr) {

            if (*psCurr > 0x20 && *psCurr <= 0x7F) {
                fprintf(pLogFile5, "%c", (char)*psCurr);
            }
            else {
                fprintf(pLogFile5, "<%x>", (int)*psCurr);
            }
        }

        fprintf(pLogFile5, "\n");
    })

    return 1;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                                             
 *
 *                                             
 */

static ET9BOOL ET9LOCALCALL __XmlReader_GetNextToken(ET9KdbXmlReaderInfo  * const pReaderInfo)
{
    if (pReaderInfo->eCurrToken == ET9_xml_token_Error) {
        return 0;
    }

    if (pReaderInfo->eCurrToken == ET9_xml_token_EOF) {
        pReaderInfo->eCurrToken = ET9_xml_token_Error;
        return 0;
    }

    /* white space, comments etc */

    for (; pReaderInfo->pbCurrChar < pReaderInfo->pbEndChar; ) {

        ET9U8 const * const pbStart = pReaderInfo->pbCurrChar;

        /* white space */

        while (pReaderInfo->pbCurrChar < pReaderInfo->pbEndChar &&
               *pReaderInfo->pbCurrChar <= 0x20) {

            if (*pReaderInfo->pbCurrChar == 0xA) {
                ++pReaderInfo->pbCurrChar;
                if (*pReaderInfo->pbCurrChar == 0xD) {
                    ++pReaderInfo->pbCurrChar;
                }
                ++pReaderInfo->nCurrLine;
            }
            else if (*pReaderInfo->pbCurrChar == 0xD) {
                ++pReaderInfo->pbCurrChar;
                if (*pReaderInfo->pbCurrChar == 0xA) {
                    ++pReaderInfo->pbCurrChar;
                }
                ++pReaderInfo->nCurrLine;
            }
            else {
                ++pReaderInfo->pbCurrChar;
            }
        }

        /* comments <!-- --> */

        if ((pReaderInfo->pbCurrChar + 4) < pReaderInfo->pbEndChar &&
            pReaderInfo->pbCurrChar[0] == '<' &&
            pReaderInfo->pbCurrChar[1] == '!' &&
            pReaderInfo->pbCurrChar[2] == '-' &&
            pReaderInfo->pbCurrChar[3] == '-') {

            pReaderInfo->pbCurrChar += 4;

            while ((pReaderInfo->pbCurrChar + 3) < pReaderInfo->pbEndChar) {

                if (pReaderInfo->pbCurrChar[0] == '-' &&
                    pReaderInfo->pbCurrChar[1] == '-' &&
                    pReaderInfo->pbCurrChar[2] == '>') {

                    pReaderInfo->pbCurrChar += 3;
                    break;
                }

                if (*pReaderInfo->pbCurrChar == 0xA) {
                    ++pReaderInfo->pbCurrChar;
                    if (*pReaderInfo->pbCurrChar == 0xD) {
                        ++pReaderInfo->pbCurrChar;
                    }
                    ++pReaderInfo->nCurrLine;
                }
                else if (*pReaderInfo->pbCurrChar == 0xD) {
                    ++pReaderInfo->pbCurrChar;
                    if (*pReaderInfo->pbCurrChar == 0xA) {
                        ++pReaderInfo->pbCurrChar;
                    }
                    ++pReaderInfo->nCurrLine;
                }
                else {
                    ++pReaderInfo->pbCurrChar;
                }
            }
        }

        /* xml declaration <?xml ?> */

        if ((pReaderInfo->pbCurrChar + 5) < pReaderInfo->pbEndChar &&
            pReaderInfo->pbCurrChar[0] == '<' &&
            pReaderInfo->pbCurrChar[1] == '?' &&
            pReaderInfo->pbCurrChar[2] == 'x' &&
            pReaderInfo->pbCurrChar[3] == 'm' &&
            pReaderInfo->pbCurrChar[4] == 'l') {

            pReaderInfo->pbCurrChar += 5;

            while ((pReaderInfo->pbCurrChar + 2) < pReaderInfo->pbEndChar) {

                if (pReaderInfo->pbCurrChar[0] == '?' &&
                    pReaderInfo->pbCurrChar[1] == '>') {

                    pReaderInfo->pbCurrChar += 2;
                    break;
                }

                if (*pReaderInfo->pbCurrChar == 0xA && (pReaderInfo->pbCurrChar + 1) < pReaderInfo->pbEndChar && *(pReaderInfo->pbCurrChar + 1) == 0xD) {
                    pReaderInfo->pbCurrChar += 2;
                    ++pReaderInfo->nCurrLine;
                }
                else if (*pReaderInfo->pbCurrChar == 0xD && (pReaderInfo->pbCurrChar + 1) < pReaderInfo->pbEndChar && *(pReaderInfo->pbCurrChar + 1) == 0xA) {
                    pReaderInfo->pbCurrChar += 2;
                    ++pReaderInfo->nCurrLine;
                }
                else if (*pReaderInfo->pbCurrChar == 0xA) {
                    ++pReaderInfo->pbCurrChar;
                    ++pReaderInfo->nCurrLine;
                }
                else if (*pReaderInfo->pbCurrChar == 0xD) {
                    ++pReaderInfo->pbCurrChar;
                    ++pReaderInfo->nCurrLine;
                }
                else {
                    ++pReaderInfo->pbCurrChar;
                }
            }
        }

        /* consumed any? */

        if (pbStart == pReaderInfo->pbCurrChar) {
            break;
        }
    }

    if (pReaderInfo->pbCurrChar >= pReaderInfo->pbEndChar) {
        pReaderInfo->eCurrToken = ET9_xml_token_EOF;
        return 1;
    }

    /* delimiters */

    switch (*pReaderInfo->pbCurrChar)
    {
        case '>':
            pReaderInfo->eCurrToken = ET9_xml_token_TagEnd;
            ++pReaderInfo->pbCurrChar;
            return 1;
        case '/':
            if ((pReaderInfo->pbCurrChar + 1) < pReaderInfo->pbEndChar && *(pReaderInfo->pbCurrChar + 1) == '>') {
                pReaderInfo->eCurrToken = ET9_xml_token_TagEndFinal;
                pReaderInfo->pbCurrChar += 2;
                return 1;
            }
            break;
        case '=':
            pReaderInfo->eCurrToken = ET9_xml_token_Equal;
            ++pReaderInfo->pbCurrChar;
            return 1;
        default:
            break;
    }

    /* tags */

    if (*pReaderInfo->pbCurrChar == '<') {

        ET9UINT nLen;
        ET9U32 dwHashValue;

        ++pReaderInfo->pbCurrChar;

        nLen = 0;
        dwHashValue = 0;

        while (pReaderInfo->pbCurrChar < pReaderInfo->pbEndChar &&
               *pReaderInfo->pbCurrChar != '>' &&
               *pReaderInfo->pbCurrChar >  0x20) {

            ET9U8 bChar = *pReaderInfo->pbCurrChar;

            ++nLen;
            ++pReaderInfo->pbCurrChar;

            if (nLen >= 32) {
                pReaderInfo->eCurrToken = ET9_xml_token_Error;
                return 0;
            }

            dwHashValue = bChar + (65599 * dwHashValue);
        }

        if (nLen) {

            switch (dwHashValue)
            {
                case 0x7f763807: pReaderInfo->eCurrToken = ET9_xml_token_TagStartKeyboard; return 1;
                case 0x41301a36: pReaderInfo->eCurrToken = ET9_xml_token_TagEndKeyboard; return 1;
                case 0xd99d190d: pReaderInfo->eCurrToken = ET9_xml_token_TagStartArea; return 1;
                case 0x52028a3c: pReaderInfo->eCurrToken = ET9_xml_token_TagEndArea; return 1;
                case 0x3892033a: pReaderInfo->eCurrToken = ET9_xml_token_TagStartRow; return 1;
                case 0xc352564b: pReaderInfo->eCurrToken = ET9_xml_token_TagEndRow; return 1;
                case 0x3515943f: pReaderInfo->eCurrToken = ET9_xml_token_TagStartKey; return 1;
                case 0xa9360b34: pReaderInfo->eCurrToken = ET9_xml_token_TagStartVoid; return 1;

                default:
                    pReaderInfo->eCurrToken = ET9_xml_token_Error;
                    return 0;
            }
        }
        else {
            pReaderInfo->eCurrToken = ET9_xml_token_Error;
            return 0;
        }
    }

    /* string */

    if (*pReaderInfo->pbCurrChar == '"') {

        ET9U8 const * const pbStart = pReaderInfo->pbCurrChar + 1;

        ET9U8 const * pbLook;

        for (pbLook = pbStart; pbLook < pReaderInfo->pbEndChar && *pbLook != '"'; ++pbLook) {
            if (*pbLook == 0xA) {
                if (*(pbLook + 1) == 0xD) {
                    ++pbLook;
                }
                ++pReaderInfo->nCurrLine;
            }
            else if (*pbLook == 0xD) {
                if (*(pbLook + 1) == 0xA) {
                    ++pbLook;
                }
                ++pReaderInfo->nCurrLine;
            }
        }

        if (pbLook >= pReaderInfo->pbEndChar) {
            pReaderInfo->eCurrToken = ET9_xml_token_Error;
            return 0;
        }

        if (!__XmlReader_DecodeString(pbStart,
                                      pbLook - 1,
                                      pReaderInfo->pKDBInfo->Private.wm.xmlReader.psString,
                                      ET9_KDB_XML_MAX_STRING_CHARS,
                                      &pReaderInfo->pKDBInfo->Private.wm.xmlReader.nStringLen)) {

            pReaderInfo->eCurrToken = ET9_xml_token_Error;
            return 0;
        }

        pReaderInfo->pbCurrChar = pbLook + 1;
        pReaderInfo->eCurrToken = ET9_xml_token_String;
        return 1;
    }

    /* attribute name */

    {
        ET9UINT nLen;
        ET9U32 dwHashValue;

        nLen = 0;
        dwHashValue = 0;

        while (pReaderInfo->pbCurrChar < pReaderInfo->pbEndChar &&
               *pReaderInfo->pbCurrChar != '=' &&
               *pReaderInfo->pbCurrChar != '/' &&
               *pReaderInfo->pbCurrChar != '>' &&
               *pReaderInfo->pbCurrChar >  0x20) {

            ET9U8 bChar = *pReaderInfo->pbCurrChar;

            ++nLen;
            ++pReaderInfo->pbCurrChar;

            if (nLen >= 64) {
                pReaderInfo->eCurrToken = ET9_xml_token_Error;
                return 0;
            }

            dwHashValue = bChar + (65599 * dwHashValue);
        }

        if (nLen) {

            switch (dwHashValue)
            {
                case 0x3852fd16: pReaderInfo->eCurrToken = ET9_xml_token_A_keyTop; return 1;
                case 0x60552566: pReaderInfo->eCurrToken = ET9_xml_token_A_keyLeft; return 1;
                case 0x7ea16d87: pReaderInfo->eCurrToken = ET9_xml_token_A_keyWidth; return 1;
                case 0x1d311026: pReaderInfo->eCurrToken = ET9_xml_token_A_keyHeight; return 1;
                case 0xf1327f75: pReaderInfo->eCurrToken = ET9_xml_token_A_voidSize; return 1;
                case 0x643da662: pReaderInfo->eCurrToken = ET9_xml_token_A_verticalBorder; return 1;
                case 0x41085ab0: pReaderInfo->eCurrToken = ET9_xml_token_A_horizontalBorder; return 1;
                case 0x12b166a0: pReaderInfo->eCurrToken = ET9_xml_token_A_verticalGap; return 1;
                case 0x152837d2: pReaderInfo->eCurrToken = ET9_xml_token_A_horizontalGap; return 1;
                case 0x147e59df: pReaderInfo->eCurrToken = ET9_xml_token_A_majorVersion; return 1;
                case 0x0e9fcee3: pReaderInfo->eCurrToken = ET9_xml_token_A_minorVersion; return 1;
                case 0xf21cdcfd: pReaderInfo->eCurrToken = ET9_xml_token_A_primaryId; return 1;
                case 0xc5bf52cf: pReaderInfo->eCurrToken = ET9_xml_token_A_secondaryId; return 1;
                case 0xcd0ab57b: pReaderInfo->eCurrToken = ET9_xml_token_A_supportsExact; return 1;
                case 0xfe6d1236: pReaderInfo->eCurrToken = ET9_xml_token_A_conditionValue; return 1;
                case 0xde6ee3d9: pReaderInfo->eCurrToken = ET9_xml_token_A_keyType; return 1;
                case 0xd3cd9658: pReaderInfo->eCurrToken = ET9_xml_token_A_keyIcon; return 1;
                case 0xbb718a8a: pReaderInfo->eCurrToken = ET9_xml_token_A_keyName; return 1;
                case 0x1e37afd5: pReaderInfo->eCurrToken = ET9_xml_token_A_keyLabel; return 1;
                case 0x69648d8c: pReaderInfo->eCurrToken = ET9_xml_token_A_keyLabelShifted; return 1;
                case 0x4743e247: pReaderInfo->eCurrToken = ET9_xml_token_A_keyCodes; return 1;
                case 0x67f1029a: pReaderInfo->eCurrToken = ET9_xml_token_A_keyCodesShifted; return 1;
                case 0xe4e33430: pReaderInfo->eCurrToken = ET9_xml_token_A_keyPopupChars; return 1;
                case 0x0432e429: pReaderInfo->eCurrToken = ET9_xml_token_A_keyPopupShiftedChars; return 1;
                case 0x2c0014b9: pReaderInfo->eCurrToken = ET9_xml_token_A_keyPopupCodes; return 1;
                case 0x4b4fc4b2: pReaderInfo->eCurrToken = ET9_xml_token_A_keyPopupShiftedCodes; return 1;
                case 0x887fa4b4: pReaderInfo->eCurrToken = ET9_xml_token_A_keyMultitapChars; return 1;
                case 0xc47a1aa5: pReaderInfo->eCurrToken = ET9_xml_token_A_keyMultitapShiftedChars; return 1;
                case 0xcf9c853d: pReaderInfo->eCurrToken = ET9_xml_token_A_keyMultitapCodes; return 1;
                case 0x0b96fb2e: pReaderInfo->eCurrToken = ET9_xml_token_A_keyMultitapShiftedCodes; return 1;
                case 0x318187bb: pReaderInfo->eCurrToken = ET9_xml_token_A_defaultLayoutWidth; return 1;
                case 0x3c8b82f2: pReaderInfo->eCurrToken = ET9_xml_token_A_defaultLayoutHeight; return 1;
                case 0x47666e95: pReaderInfo->eCurrToken = ET9_xml_token_A_addAltChars; return 1;

                default:
                    pReaderInfo->eCurrToken = ET9_xml_token_A_unknown;
                    return 1;
            }
        }
    }

    /* nothing recognized */

    pReaderInfo->eCurrToken = ET9_xml_token_Error;

    return 0;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                                             
 *                                 
 *
 *                                             
 */

ET9INLINE static ET9BOOL ET9LOCALCALL __XmlReader_ConsumeToken(ET9KdbXmlReaderInfo        * const pReaderInfo,
                                                               const ET9_xml_token                eToken)
{
    if (pReaderInfo->eCurrToken != eToken) {
        return 0;
    }

    if (!__XmlReader_GetNextToken(pReaderInfo)) {
        return 0;
    }

    return 1;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                                             
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __XmlReader_ReadAttribute(ET9KdbXmlReaderInfo  * const pReaderInfo)
{
    WLOG5(fprintf(pLogFile5, "__XmlReader_ReadAttribute\n");)

    if (pReaderInfo->eCurrToken == ET9_xml_token_Error) {
        return ET9STATUS_KDB_SYNTAX_ERROR;
    }

    pReaderInfo->eCurrAttribute = pReaderInfo->eCurrToken;

    switch (pReaderInfo->eCurrAttribute)
    {
        case ET9_xml_token_A_keyTop:
        case ET9_xml_token_A_keyLeft:
        case ET9_xml_token_A_keyWidth:
        case ET9_xml_token_A_keyHeight:
        case ET9_xml_token_A_verticalBorder:
        case ET9_xml_token_A_horizontalBorder:
        case ET9_xml_token_A_verticalGap:
        case ET9_xml_token_A_horizontalGap:
        case ET9_xml_token_A_majorVersion:
        case ET9_xml_token_A_minorVersion:
        case ET9_xml_token_A_primaryId:
        case ET9_xml_token_A_secondaryId:
        case ET9_xml_token_A_supportsExact:
        case ET9_xml_token_A_conditionValue:
        case ET9_xml_token_A_keyType:
        case ET9_xml_token_A_keyIcon:
        case ET9_xml_token_A_keyName:
        case ET9_xml_token_A_keyLabel:
        case ET9_xml_token_A_keyLabelShifted:
        case ET9_xml_token_A_keyCodes:
        case ET9_xml_token_A_keyCodesShifted:
        case ET9_xml_token_A_keyPopupChars:
        case ET9_xml_token_A_keyPopupShiftedChars:
        case ET9_xml_token_A_keyPopupCodes:
        case ET9_xml_token_A_keyPopupShiftedCodes:
        case ET9_xml_token_A_keyMultitapChars:
        case ET9_xml_token_A_keyMultitapShiftedChars:
        case ET9_xml_token_A_keyMultitapCodes:
        case ET9_xml_token_A_keyMultitapShiftedCodes:
        case ET9_xml_token_A_addAltChars:
        case ET9_xml_token_A_voidSize:
        case ET9_xml_token_A_defaultLayoutWidth:
        case ET9_xml_token_A_defaultLayoutHeight:
            break;
        case ET9_xml_token_A_unknown:
            ++pReaderInfo->nUnknownAttributes;
            break;
        default:
            return ET9STATUS_KDB_SYNTAX_ERROR;
    }

    WLOG5(fprintf(pLogFile5, "  attribute '%s'\n", __XmlReader_AttributeToString(pReaderInfo->eCurrAttribute));)

    if (!__XmlReader_ConsumeToken(pReaderInfo, pReaderInfo->eCurrToken)) {
        return ET9STATUS_KDB_SYNTAX_ERROR;
    }

    if (!__XmlReader_ConsumeToken(pReaderInfo, ET9_xml_token_Equal)) {
        return ET9STATUS_KDB_SYNTAX_ERROR;
    }

    if (!__XmlReader_ConsumeToken(pReaderInfo, ET9_xml_token_String)) {
        return ET9STATUS_KDB_SYNTAX_ERROR;
    }

    /* extract corresponding values from the string */

    switch (pReaderInfo->eCurrAttribute)
    {
        case ET9_xml_token_A_keyTop:
        case ET9_xml_token_A_keyLeft:
        case ET9_xml_token_A_keyWidth:
        case ET9_xml_token_A_keyHeight:
        case ET9_xml_token_A_voidSize:
        case ET9_xml_token_A_verticalBorder:
        case ET9_xml_token_A_horizontalBorder:
        case ET9_xml_token_A_verticalGap:
        case ET9_xml_token_A_horizontalGap:
            if (!__XmlReader_ExtractFloatWithSuffix(pReaderInfo)) {
                return ET9STATUS_KDB_ATTRIBUTE_VALUE_ERROR;
            }
            break;

        case ET9_xml_token_A_majorVersion:
        case ET9_xml_token_A_minorVersion:
        case ET9_xml_token_A_primaryId:
        case ET9_xml_token_A_secondaryId:
            if (!__XmlReader_ExtractInt(pReaderInfo)) {
                return ET9STATUS_KDB_ATTRIBUTE_VALUE_ERROR;
            }
            if (pReaderInfo->nCurrInt > 0xFF) {
                return ET9STATUS_KDB_ATTRIBUTE_VALUE_ERROR;
            }
            break;

        case ET9_xml_token_A_defaultLayoutWidth:
        case ET9_xml_token_A_defaultLayoutHeight:
            if (!__XmlReader_ExtractInt(pReaderInfo)) {
                return ET9STATUS_KDB_ATTRIBUTE_VALUE_ERROR;
            }
            if (pReaderInfo->nCurrInt > 0xFFFF) {
                return ET9STATUS_KDB_ATTRIBUTE_VALUE_ERROR;
            }
            break;

        case ET9_xml_token_A_conditionValue:
            if (!__XmlReader_ExtractInt(pReaderInfo)) {
                return ET9STATUS_KDB_ATTRIBUTE_VALUE_ERROR;
            }
            if (pReaderInfo->nCurrInt > 0xFFFE) {
                return ET9STATUS_KDB_ATTRIBUTE_VALUE_ERROR;
            }
            {
                ET9KDBPrivate * const pPrivate = &pReaderInfo->pKDBInfo->Private;
                ET9KdbXmlKeyboardInfo * const pKeyboardInfo = &pPrivate->wm.xmlReader.sKeyboardInfo;
                ET9KdbXmlKeyboard * const pKeyboard = &pKeyboardInfo->sKeyboard;

                if (pKeyboard->wConditionValueMax < pReaderInfo->nCurrInt) {
                    pKeyboard->wConditionValueMax = (ET9U16)pReaderInfo->nCurrInt;
                }
            }
            break;

        case ET9_xml_token_A_addAltChars:
        case ET9_xml_token_A_supportsExact:
            if (!__XmlReader_ExtractConstant(pReaderInfo)) {
                return ET9STATUS_KDB_ATTRIBUTE_VALUE_ERROR;
            }
            switch (pReaderInfo->eCurrConstant)
            {
                case ET9_xml_C_true:
                case ET9_xml_C_false:
                    break;
                default:
                    return ET9STATUS_KDB_ATTRIBUTE_VALUE_ERROR;
            }
            break;

        case ET9_xml_token_A_keyType:
            if (!__XmlReader_ExtractConstant(pReaderInfo)) {
                return ET9STATUS_KDB_ATTRIBUTE_VALUE_ERROR;
            }
            switch (pReaderInfo->eCurrConstant)
            {
                case ET9_xml_C_regional:
                case ET9_xml_C_nonRegional:
                case ET9_xml_C_smartPunct:
                case ET9_xml_C_string:
                case ET9_xml_C_function:
                    break;
                default:
                    return ET9STATUS_KDB_ATTRIBUTE_VALUE_ERROR;
            }
            break;

        case ET9_xml_token_A_keyIcon:
            if (!__XmlReader_ExtractChars(pReaderInfo)) {
                return ET9STATUS_KDB_ATTRIBUTE_VALUE_ERROR;
            }
            break;

        case ET9_xml_token_A_keyName:
            if (!__XmlReader_ExtractName(pReaderInfo)) {
                return ET9STATUS_KDB_ATTRIBUTE_VALUE_ERROR;
            }
            break;

        case ET9_xml_token_A_keyLabel:
        case ET9_xml_token_A_keyLabelShifted:
        case ET9_xml_token_A_keyPopupChars:
        case ET9_xml_token_A_keyPopupShiftedChars:
        case ET9_xml_token_A_keyMultitapChars:
        case ET9_xml_token_A_keyMultitapShiftedChars:
            if (!__XmlReader_ExtractChars(pReaderInfo)) {
                return ET9STATUS_KDB_ATTRIBUTE_VALUE_ERROR;
            }
            break;

        case ET9_xml_token_A_keyCodes:
        case ET9_xml_token_A_keyCodesShifted:
        case ET9_xml_token_A_keyPopupCodes:
        case ET9_xml_token_A_keyPopupShiftedCodes:
        case ET9_xml_token_A_keyMultitapCodes:
        case ET9_xml_token_A_keyMultitapShiftedCodes:
            if (!__XmlReader_ExtractCodes(pReaderInfo)) {
                return ET9STATUS_KDB_ATTRIBUTE_VALUE_ERROR;
            }
            break;

        default:
            break;
    }

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                                             
 *                               
 *                               
 *                               
 *                               
 *                               
 *                               
 *                               
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __XmlReader_AppendString(ET9KdbXmlReaderInfo      * const pReaderInfo,
                                                       ET9KdbXmlKeyboardInfo    * const pKeyboardInfo,
                                                       ET9SYMB                  * const psDst,
                                                       ET9UINT                  * const pnDst,
                                                       ET9SYMB            const * const psSrc,
                                                       const ET9UINT                    nSrc,
                                                       const ET9_xml_copyOp             eCopyOp,
                                                       ET9SYMB                  * const psDedupe)
{
    if (pKeyboardInfo->nUsedSymbs + nSrc > ET9_KDB_XML_MAX_POOL_CHARS) {
        return ET9STATUS_KDB_HAS_TOO_MANY_CHARS;
    }

    if (*pnDst + nSrc > ET9_KDB_XML_MAX_DEDUPE_CHARS) {
        return ET9STATUS_KDB_KEY_HAS_TOO_MANY_CHARS;
    }

    if (&psDst[*pnDst] != &pKeyboardInfo->psSymbPool[pKeyboardInfo->nUsedSymbs]) {
        return ET9STATUS_ERROR;
    }

    if (nSrc) {

        ET9UINT nCount;
        ET9SYMB const *psSrcSymb;
        ET9SYMB *psDstSymb;

        psSrcSymb = psSrc;
        psDstSymb = &psDst[*pnDst];
        for (nCount = nSrc; nCount; --nCount, ++psSrcSymb) {

            const ET9SYMB sSymb = (eCopyOp == ET9_xml_copyOp_toLower) ? _ET9SymToLower(*psSrcSymb, pReaderInfo->pKDBInfo->Private.pWordSymbInfo->Private.dwLocale) : *psSrcSymb;

            if (psDedupe) {

                const ET9UINT nHash = sSymb % ET9_KDB_XML_MAX_DEDUPE_CHARS;

                if (!psDedupe[nHash]) {
                    psDedupe[nHash] = sSymb;
                }
                else if (psDedupe[nHash] == sSymb) {
                    continue;
                }
                else {

                    ET9UINT nLook;

                    for (nLook = nHash; psDedupe[nLook]; ) {
                        ++nLook;
                        if (nLook >= ET9_KDB_XML_MAX_DEDUPE_CHARS) {
                            nLook = 0;
                        }

                        if (psDedupe[nHash] == sSymb) {
                            break;
                        }
                    }

                    if (psDedupe[nLook]) {
                        continue;
                    }

                    psDedupe[nLook] = sSymb;
                }
            }

            *psDstSymb++ = sSymb;
        }

        {
            const ET9UINT nAddCount = (ET9UINT)(psDstSymb - &psDst[*pnDst]);

            *pnDst += nAddCount;

            pKeyboardInfo->nUsedSymbs += nAddCount;
        }
    }

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                                             
 *                               
 *                               
 *                               
 *                               
 *                               
 *                               
 *                               
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __XmlReader_CreateString(ET9KdbXmlReaderInfo      * const pReaderInfo,
                                                       ET9KdbXmlKeyboardInfo    * const pKeyboardInfo,
                                                       ET9SYMB                 ** const ppsDst,
                                                       ET9UINT                  * const pnDst,
                                                       ET9SYMB                  * const psSrc,
                                                       const ET9UINT                    nSrc,
                                                       const ET9_xml_copyOp             eCopyOp,
                                                       ET9SYMB                  * const psDedupe)
{
    if (pKeyboardInfo->nUsedSymbs + nSrc > ET9_KDB_XML_MAX_POOL_CHARS) {
        return ET9STATUS_KDB_HAS_TOO_MANY_CHARS;
    }

    if (psDedupe) {
        _ET9ClearMem((ET9U8*)psDedupe, ET9_KDB_XML_MAX_DEDUPE_CHARS * sizeof(ET9SYMB));
    }

    *ppsDst = &pKeyboardInfo->psSymbPool[pKeyboardInfo->nUsedSymbs];

    *pnDst = 0;

    return __XmlReader_AppendString(pReaderInfo, pKeyboardInfo, *ppsDst, pnDst, psSrc, nSrc, eCopyOp, psDedupe);
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                                             
 *                               
 *                               
 *                               
 *                               
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __XmlReader_AppendAltCharsLower(ET9KdbXmlReaderInfo      * const pReaderInfo,
                                                              ET9KdbXmlKeyboardInfo    * const pKeyboardInfo,
                                                              ET9SYMB                  * const psDst,
                                                              ET9UINT                  * const pnDst,
                                                              ET9SYMB                  * const psDedupe)
{
    ET9UINT nCount;
    ET9SYMB *psSymb;

    psSymb = psDst;
    for (nCount = *pnDst; nCount; --nCount, ++psSymb) {

        ET9SYMB const *psAlts = _ET9_GetAltCharsLower(*psSymb);

        if (psAlts) {

            ET9STATUS eStatus;
            ET9UINT nAltCount;

            for (nAltCount = 0; psAlts[nAltCount]; ++nAltCount) {
            }

            eStatus = __XmlReader_AppendString(pReaderInfo, pKeyboardInfo, psDst, pnDst, psAlts, nAltCount, ET9_xml_copyOp_none, psDedupe);

            if (eStatus) {
                return eStatus;
            }
        }
    }

    pKeyboardInfo->bAltCharsAdded = 1;

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                                             
 *                               
 *                               
 *                               
 *                               
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __XmlReader_AppendAltCharsUpper(ET9KdbXmlReaderInfo      * const pReaderInfo,
                                                              ET9KdbXmlKeyboardInfo    * const pKeyboardInfo,
                                                              ET9SYMB                  * const psDst,
                                                              ET9UINT                  * const pnDst,
                                                              ET9SYMB                  * const psDedupe)
{
    ET9UINT nCount;
    ET9SYMB *psSymb;

    psSymb = psDst;
    for (nCount = *pnDst; nCount; --nCount, ++psSymb) {

        ET9SYMB const *psAlts = _ET9_GetAltCharsUpper(*psSymb);

        if (psAlts) {

            ET9STATUS eStatus;
            ET9UINT nAltCount;

            for (nAltCount = 0; psAlts[nAltCount]; ++nAltCount) {
            }

            eStatus = __XmlReader_AppendString(pReaderInfo, pKeyboardInfo, psDst, pnDst, psAlts, nAltCount, ET9_xml_copyOp_none, psDedupe);

            if (eStatus) {
                return eStatus;
            }
        }
    }

    pKeyboardInfo->bAltCharsAdded = 1;

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                                             
 *                                             
 *
 *                                                                    
 */

static void ET9LOCALCALL __XmlReader_PruneAltChars(ET9KdbXmlReaderInfo   * const pReaderInfo,
                                                   ET9KdbXmlKeyboardInfo * const pKeyboardInfo)
{
    ET9SYMB * const psDedupe = &pReaderInfo->pKDBInfo->Private.wm.xmlReader.psDedupe[0];

    ET9UINT nCount;
    ET9KdbXmlKey *pKey;

    WLOG5(fprintf(pLogFile5, "__XmlReader_PruneAltChars\n");)

    if (!pKeyboardInfo->bAltCharsAdded) {
        return;
    }

    _ET9ClearMem((ET9U8*)psDedupe, ET9_KDB_XML_MAX_DEDUPE_CHARS * sizeof(ET9SYMB));

    pKey = &pKeyboardInfo->pKeyPool[0];
    for (nCount = pKeyboardInfo->nUsedKeys; nCount; --nCount, ++pKey) {

        if (pKey->eKeyType == ET9_xml_keyType_regional || pKey->eKeyType == ET9_xml_keyType_nonRegional) {

            const ET9SYMB sSymb = pKey->psKeyChars[0];

            const ET9UINT nHash = sSymb % ET9_KDB_XML_MAX_DEDUPE_CHARS;

            if (!psDedupe[nHash]) {
                psDedupe[nHash] = sSymb;
            }
            else if (psDedupe[nHash] == sSymb) {
                continue;
            }
            else {

                ET9UINT nLook;

                for (nLook = nHash; psDedupe[nLook]; ) {
                    ++nLook;
                    if (nLook >= ET9_KDB_XML_MAX_DEDUPE_CHARS) {
                        nLook = 0;
                    }
                }

                psDedupe[nLook] = sSymb;
            }
        }
    }

    pKey = &pKeyboardInfo->pKeyPool[0];
    for (nCount = pKeyboardInfo->nUsedKeys; nCount; --nCount, ++pKey) {

        if (pKey->eKeyType == ET9_xml_keyType_regional || pKey->eKeyType == ET9_xml_keyType_nonRegional) {

            ET9UINT nIndex;

            for (nIndex = 1; nIndex < pKey->nKeyCharCount; ++nIndex) {

                const ET9SYMB sSymb = pKey->psKeyChars[nIndex];

                const ET9UINT nHash = sSymb % ET9_KDB_XML_MAX_DEDUPE_CHARS;

                if (!psDedupe[nHash]) {
                    continue;
                }
                else if (psDedupe[nHash] == sSymb) {
                }
                else {

                    ET9UINT nLook;

                    for (nLook = nHash; psDedupe[nLook] && psDedupe[nLook] != sSymb; ) {
                        ++nLook;
                        if (nLook >= ET9_KDB_XML_MAX_DEDUPE_CHARS) {
                            nLook = 0;
                        }
                    }

                    if (!psDedupe[nLook]) {
                        continue;
                    }
                }

                /* remove char */

                pKey->psKeyChars[nIndex] = pKey->psKeyChars[pKey->nKeyCharCount - 1];
                --pKey->nKeyCharCount;
                --nIndex;
            }
        }
    }
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                                             
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __XmlReader_ReadKeyAttributes(ET9KdbXmlReaderInfo  * const pReaderInfo)
{
    ET9STATUS eStatus;
    ET9KdbXmlReaderContext * const pContext = &pReaderInfo->pContexts[ET9_xml_E_key];
    const ET9UINT nKeySourceLine = pReaderInfo->nCurrLine;

    WLOG5(fprintf(pLogFile5, "__XmlReader_ReadKeyAttributes\n");)

    if (pReaderInfo->eCurrToken == ET9_xml_token_Error) {
        return ET9STATUS_KDB_SYNTAX_ERROR;
    }

    *pContext = *(pContext - 1);
    _ET9ClearMem((ET9U8*)pContext->pbAttributeSet, sizeof(pContext->pbAttributeSet));

    while (pReaderInfo->eCurrToken != ET9_xml_token_TagEnd &&
           pReaderInfo->eCurrToken != ET9_xml_token_TagEndFinal) {

        eStatus = __XmlReader_ReadAttribute(pReaderInfo);

        if (eStatus) {
            return eStatus;
        }

        if (pReaderInfo->eCurrAttribute == ET9_xml_token_A_unknown) {
            continue;
        }

        if (pContext->pbAttributeSet[pReaderInfo->eCurrAttribute]) {
            return ET9STATUS_KDB_DUPLICATE_ATTRIBUTE;
        }

        pContext->pbAttributeSet[pReaderInfo->eCurrAttribute] = 1;

        switch (pReaderInfo->eCurrAttribute)
        {
            case ET9_xml_token_A_keyType:
                pContext->eKeyType = __XmlReader_ConstantToKeyType(pReaderInfo->eCurrConstant);
                break;
            case ET9_xml_token_A_keyTop:
                pContext->fKeyTop = pReaderInfo->fCurrFloat;
                pContext->eKeyTopSuffix = pReaderInfo->eCurrValueSuffix;
                break;
            case ET9_xml_token_A_keyLeft:
                pContext->fKeyLeft = pReaderInfo->fCurrFloat;
                pContext->eKeyLeftSuffix = pReaderInfo->eCurrValueSuffix;
                break;
            case ET9_xml_token_A_keyWidth:
                pContext->fKeyWidth = pReaderInfo->fCurrFloat;
                pContext->eKeyWidthSuffix = pReaderInfo->eCurrValueSuffix;
                break;
            case ET9_xml_token_A_keyHeight:
                pContext->fKeyHeight = pReaderInfo->fCurrFloat;
                pContext->eKeyHeightSuffix = pReaderInfo->eCurrValueSuffix;
                break;
            case ET9_xml_token_A_horizontalGap:
                pContext->fHorizontalGap = pReaderInfo->fCurrFloat;
                pContext->eHorizontalGapSuffix = pReaderInfo->eCurrValueSuffix;
                break;
            case ET9_xml_token_A_keyIcon:
                if (pReaderInfo->nCurrCharCount > ET9_KDB_XML_MAX_ICON_CHARS) {
                    return ET9STATUS_KDB_ATTRIBUTE_VALUE_ERROR;
                }
                pContext->nIconCharCount = pReaderInfo->nCurrCharCount;
                _ET9SymCopy(pContext->psIconChars, pReaderInfo->psCurrChars, pReaderInfo->nCurrCharCount);
                break;
            case ET9_xml_token_A_keyName:
                if (pReaderInfo->nCurrCharCount > ET9_KDB_MAX_KEY_CHARS) {
                    return ET9STATUS_KDB_ATTRIBUTE_VALUE_ERROR;
                }
                pContext->nKeyCharCount = pReaderInfo->nCurrCharCount;
                _ET9SymCopy(pContext->psKeyChars, pReaderInfo->psCurrChars, pReaderInfo->nCurrCharCount);
                break;
            case ET9_xml_token_A_keyLabel:
                if (pReaderInfo->nCurrCharCount > ET9_KDB_XML_MAX_LABEL_CHARS) {
                    return ET9STATUS_KDB_ATTRIBUTE_VALUE_ERROR;
                }
                pContext->nLabelCharCount = pReaderInfo->nCurrCharCount;
                _ET9SymCopy(pContext->psLabelChars, pReaderInfo->psCurrChars, pReaderInfo->nCurrCharCount);
                break;
            case ET9_xml_token_A_keyLabelShifted:
                if (pReaderInfo->nCurrCharCount > ET9_KDB_XML_MAX_LABEL_CHARS) {
                    return ET9STATUS_KDB_ATTRIBUTE_VALUE_ERROR;
                }
                pContext->nLabelShiftedCharCount = pReaderInfo->nCurrCharCount;
                _ET9SymCopy(pContext->psLabelShiftedChars, pReaderInfo->psCurrChars, pReaderInfo->nCurrCharCount);
                break;
            case ET9_xml_token_A_keyCodes:
                if (pReaderInfo->nCurrCharCount > ET9_KDB_MAX_KEY_CHARS) {
                    return ET9STATUS_KDB_ATTRIBUTE_VALUE_ERROR;
                }
                pContext->nKeyCharCount = pReaderInfo->nCurrCharCount;
                _ET9SymCopy(pContext->psKeyChars, pReaderInfo->psCurrChars, pReaderInfo->nCurrCharCount);
                break;
            case ET9_xml_token_A_keyCodesShifted:
                if (pReaderInfo->nCurrCharCount > ET9_KDB_MAX_KEY_CHARS) {
                    return ET9STATUS_KDB_ATTRIBUTE_VALUE_ERROR;
                }
                pContext->nKeyShiftedCharCount = pReaderInfo->nCurrCharCount;
                _ET9SymCopy(pContext->psKeyShiftedChars, pReaderInfo->psCurrChars, pReaderInfo->nCurrCharCount);
                break;
            case ET9_xml_token_A_keyPopupChars:
            case ET9_xml_token_A_keyPopupCodes:
                if (pReaderInfo->nCurrCharCount > ET9_KDB_XML_MAX_POPUP_CHARS) {
                    return ET9STATUS_KDB_ATTRIBUTE_VALUE_ERROR;
                }
                pContext->nKeyPopupCharCount = pReaderInfo->nCurrCharCount;
                _ET9SymCopy(pContext->psKeyPopupChars, pReaderInfo->psCurrChars, pReaderInfo->nCurrCharCount);
                break;
            case ET9_xml_token_A_keyPopupShiftedChars:
            case ET9_xml_token_A_keyPopupShiftedCodes:
                if (pReaderInfo->nCurrCharCount > ET9_KDB_XML_MAX_POPUP_CHARS) {
                    return ET9STATUS_KDB_ATTRIBUTE_VALUE_ERROR;
                }
                pContext->nKeyPopupShiftedCharCount = pReaderInfo->nCurrCharCount;
                _ET9SymCopy(pContext->psKeyPopupShiftedChars, pReaderInfo->psCurrChars, pReaderInfo->nCurrCharCount);
                break;
            case ET9_xml_token_A_keyMultitapChars:
            case ET9_xml_token_A_keyMultitapCodes:
                if (pReaderInfo->nCurrCharCount > ET9_KDB_XML_MAX_MULTITAP_CHARS) {
                    return ET9STATUS_KDB_ATTRIBUTE_VALUE_ERROR;
                }
                pContext->nKeyMultitapCharCount = pReaderInfo->nCurrCharCount;
                _ET9SymCopy(pContext->psKeyMultitapChars, pReaderInfo->psCurrChars, pReaderInfo->nCurrCharCount);
                break;
            case ET9_xml_token_A_keyMultitapShiftedChars:
            case ET9_xml_token_A_keyMultitapShiftedCodes:
                if (pReaderInfo->nCurrCharCount > ET9_KDB_XML_MAX_MULTITAP_CHARS) {
                    return ET9STATUS_KDB_ATTRIBUTE_VALUE_ERROR;
                }
                pContext->nKeyMultitapShiftedCharCount = pReaderInfo->nCurrCharCount;
                _ET9SymCopy(pContext->psKeyMultitapShiftedChars, pReaderInfo->psCurrChars, pReaderInfo->nCurrCharCount);
                break;
            default:
                return ET9STATUS_KDB_UNEXPECTED_ATTRIBUTE;
        }
    }

    if (!pContext->pbAttributeSet[ET9_xml_token_A_keyType]) {
        return ET9STATUS_KDB_MISSING_ATTRIBUTE;
    }

    if (pContext->pbAttributeSet[ET9_xml_token_A_keyLabelShifted] &&
        !pContext->pbAttributeSet[ET9_xml_token_A_keyLabel]) {
        return ET9STATUS_KDB_MISSING_ATTRIBUTE;
    }

    if (pContext->pbAttributeSet[ET9_xml_token_A_keyCodesShifted] &&
        !pContext->pbAttributeSet[ET9_xml_token_A_keyCodes]) {
        return ET9STATUS_KDB_MISSING_ATTRIBUTE;
    }

    if (pContext->pbAttributeSet[ET9_xml_token_A_keyPopupChars] &&
        pContext->pbAttributeSet[ET9_xml_token_A_keyPopupCodes]) {
        return ET9STATUS_KDB_UNEXPECTED_ATTRIBUTE;
    }

    if (pContext->pbAttributeSet[ET9_xml_token_A_keyPopupShiftedChars] &&
        pContext->pbAttributeSet[ET9_xml_token_A_keyPopupShiftedCodes]) {
        return ET9STATUS_KDB_UNEXPECTED_ATTRIBUTE;
    }

    if (pContext->pbAttributeSet[ET9_xml_token_A_keyMultitapChars] &&
        pContext->pbAttributeSet[ET9_xml_token_A_keyMultitapCodes]) {
        return ET9STATUS_KDB_UNEXPECTED_ATTRIBUTE;
    }

    if (pContext->pbAttributeSet[ET9_xml_token_A_keyMultitapShiftedChars] &&
        pContext->pbAttributeSet[ET9_xml_token_A_keyMultitapShiftedCodes]) {
        return ET9STATUS_KDB_UNEXPECTED_ATTRIBUTE;
    }

    if (pContext->pbAttributeSet[ET9_xml_token_A_keyMultitapShiftedChars] &&
        !pContext->pbAttributeSet[ET9_xml_token_A_keyMultitapChars]) {
        return ET9STATUS_KDB_MISSING_ATTRIBUTE;
    }

    if (pContext->pbAttributeSet[ET9_xml_token_A_keyMultitapShiftedCodes] &&
        !pContext->pbAttributeSet[ET9_xml_token_A_keyMultitapCodes]) {
        return ET9STATUS_KDB_MISSING_ATTRIBUTE;
    }

    if (pContext->eKeyType == ET9_xml_keyType_function) {
        if (pContext->pbAttributeSet[ET9_xml_token_A_keyLabelShifted] ||
            pContext->pbAttributeSet[ET9_xml_token_A_keyCodesShifted] ||
            pContext->pbAttributeSet[ET9_xml_token_A_keyPopupChars] ||
            pContext->pbAttributeSet[ET9_xml_token_A_keyPopupShiftedChars] ||
            pContext->pbAttributeSet[ET9_xml_token_A_keyPopupCodes] ||
            pContext->pbAttributeSet[ET9_xml_token_A_keyPopupShiftedCodes] ||
            pContext->pbAttributeSet[ET9_xml_token_A_keyMultitapChars] ||
            pContext->pbAttributeSet[ET9_xml_token_A_keyMultitapShiftedChars] ||
            pContext->pbAttributeSet[ET9_xml_token_A_keyMultitapCodes] ||
            pContext->pbAttributeSet[ET9_xml_token_A_keyMultitapShiftedCodes]) {
            return ET9STATUS_KDB_UNEXPECTED_ATTRIBUTE;
        }
        if (!pContext->pbAttributeSet[ET9_xml_token_A_keyName] && !pContext->pbAttributeSet[ET9_xml_token_A_keyCodes]) {
            return ET9STATUS_KDB_MISSING_ATTRIBUTE;
        }
    }
    else {
        if (pContext->pbAttributeSet[ET9_xml_token_A_keyName]) {
            return ET9STATUS_KDB_UNEXPECTED_ATTRIBUTE;
        }
    }

    if (pContext->eRowType == ET9_xml_rowType_keys) {
        if (pContext->pbAttributeSet[ET9_xml_token_A_keyTop] ||
            pContext->pbAttributeSet[ET9_xml_token_A_keyLeft] ||
            pContext->pbAttributeSet[ET9_xml_token_A_keyHeight]) {
            return ET9STATUS_KDB_UNEXPECTED_ATTRIBUTE;
        }
    }
    else if (pContext->eRowType == ET9_xml_rowType_area) {
        if (!pContext->pbAttributeSet[ET9_xml_token_A_keyTop] ||
            !pContext->pbAttributeSet[ET9_xml_token_A_keyLeft]) {
            return ET9STATUS_KDB_MISSING_ATTRIBUTE;
        }
        if (pContext->eKeyTopSuffix == ET9_xml_valueSuffix_undef ||
            pContext->eKeyLeftSuffix == ET9_xml_valueSuffix_undef ||
            pContext->eKeyWidthSuffix == ET9_xml_valueSuffix_undef ||
            pContext->eKeyHeightSuffix == ET9_xml_valueSuffix_undef) {
            return ET9STATUS_KDB_MISSING_ATTRIBUTE;
        }
    }

    if (pContext->nConditionValue == pReaderInfo->nConditionValue) {

        ET9KDBPrivate * const pPrivate = &pReaderInfo->pKDBInfo->Private;
        ET9SYMB * const psDedupe = &pReaderInfo->pKDBInfo->Private.wm.xmlReader.psDedupe[0];
        ET9KdbXmlKeyboardInfo * const pKeyboardInfo = &pPrivate->wm.xmlReader.sKeyboardInfo;
        ET9KdbXmlKeyboard * const pKeyboard = &pKeyboardInfo->sKeyboard;
        ET9KdbXmlRow * const pRow = &pKeyboard->pRows[pKeyboard->nRowCount - 1];
        ET9KdbXmlKey * const pKey = &pRow->pKeys[pRow->nKeyCount];

        const ET9BOOL bAddAltChars = (pContext->eAddAltChars != ET9_xml_booleanValue_no && (pContext->eKeyType == ET9_xml_keyType_regional || pContext->eKeyType == ET9_xml_keyType_nonRegional)) ? 1 : 0;

        if (pKeyboardInfo->nUsedKeys >= ET9_KDB_MAX_KEYS) {
            return ET9STATUS_KDB_HAS_TOO_MANY_KEYS;
        }

        ++pRow->nKeyCount;
        ++pKeyboardInfo->nUsedKeys;

        pKey->nSourceLine           = nKeySourceLine;

        pKey->fKeyWidth             = pContext->fKeyWidth;
        pKey->eKeyWidthSuffix       = pContext->eKeyWidthSuffix;

        pKey->fHorizontalGap        = pContext->fHorizontalGap;
        pKey->eHorizontalGapSuffix  = pContext->eHorizontalGapSuffix;

        pKey->eKeyType              = pContext->eKeyType;


        pKey->fKeyTop               = pContext->fKeyTop;
        pKey->eKeyTopSuffix         = pContext->eKeyTopSuffix;

        pKey->fKeyLeft              = pContext->fKeyLeft;
        pKey->eKeyLeftSuffix        = pContext->eKeyLeftSuffix;

        pKey->fKeyHeight            = pContext->fKeyHeight;
        pKey->eKeyHeightSuffix      = pContext->eKeyHeightSuffix;

        eStatus = __XmlReader_CreateString(pReaderInfo,
                                           pKeyboardInfo,
                                           &pKey->psIconChars,
                                           &pKey->nIconCharCount,
                                           pContext->psIconChars,
                                           pContext->nIconCharCount,
                                           ET9_xml_copyOp_none,
                                           NULL);

        if (eStatus) {
            return eStatus;
        }

        eStatus = __XmlReader_CreateString(pReaderInfo,
                                           pKeyboardInfo,
                                           &pKey->psLabelChars,
                                           &pKey->nLabelCharCount,
                                           pContext->psLabelChars,
                                           pContext->nLabelCharCount,
                                           ET9_xml_copyOp_none,
                                           NULL);

        if (eStatus) {
            return eStatus;
        }

        eStatus = __XmlReader_CreateString(pReaderInfo,
                                           pKeyboardInfo,
                                           &pKey->psLabelShiftedChars,
                                           &pKey->nLabelShiftedCharCount,
                                           pContext->psLabelShiftedChars,
                                           pContext->nLabelShiftedCharCount,
                                           ET9_xml_copyOp_none,
                                           NULL);

        if (eStatus) {
            return eStatus;
        }

        eStatus = __XmlReader_CreateString(pReaderInfo,
                                           pKeyboardInfo,
                                           &pKey->psKeyPopupChars,
                                           &pKey->nKeyPopupCharCount,
                                           pContext->psKeyPopupChars,
                                           pContext->nKeyPopupCharCount,
                                           ET9_xml_copyOp_none,
                                           NULL);

        if (eStatus) {
            return eStatus;
        }

        eStatus = __XmlReader_CreateString(pReaderInfo,
                                           pKeyboardInfo,
                                           &pKey->psKeyPopupShiftedChars,
                                           &pKey->nKeyPopupShiftedCharCount,
                                           pContext->psKeyPopupShiftedChars,
                                           pContext->nKeyPopupShiftedCharCount,
                                           ET9_xml_copyOp_none,
                                           NULL);

        if (eStatus) {
            return eStatus;
        }

        eStatus = __XmlReader_CreateString(pReaderInfo,
                                           pKeyboardInfo,
                                           &pKey->psKeyMultitapChars,
                                           &pKey->nKeyMultitapCharCount,
                                           pContext->psKeyMultitapChars,
                                           pContext->nKeyMultitapCharCount,
                                           ET9_xml_copyOp_none,
                                           NULL);

        if (eStatus) {
            return eStatus;
        }

        eStatus = __XmlReader_CreateString(pReaderInfo,
                                           pKeyboardInfo,
                                           &pKey->psKeyMultitapShiftedChars,
                                           &pKey->nKeyMultitapShiftedCharCount,
                                           pContext->psKeyMultitapShiftedChars,
                                           pContext->nKeyMultitapShiftedCharCount,
                                           ET9_xml_copyOp_none,
                                           NULL);

        if (eStatus) {
            return eStatus;
        }

        /* unshifted */

        eStatus = __XmlReader_CreateString(pReaderInfo,
                                           pKeyboardInfo,
                                           &pKey->psKeyChars,
                                           &pKey->nKeyCharCount,
                                           NULL,
                                           0,
                                           ET9_xml_copyOp_none,
                                           psDedupe);

        if (eStatus) {
            return eStatus;
        }

        if (pKey->eKeyType != ET9_xml_keyType_function) {

            eStatus = __XmlReader_AppendString(pReaderInfo,
                                               pKeyboardInfo,
                                               pKey->psKeyChars,
                                               &pKey->nKeyCharCount,
                                               pContext->psLabelChars,
                                               pContext->nLabelCharCount,
                                               pKey->eKeyType == ET9_xml_keyType_string ? ET9_xml_copyOp_none : ET9_xml_copyOp_toLower,
                                               psDedupe);

            if (eStatus) {
                return eStatus;
            }

            eStatus = __XmlReader_AppendString(pReaderInfo,
                                               pKeyboardInfo,
                                               pKey->psKeyChars,
                                               &pKey->nKeyCharCount,
                                               pContext->psLabelShiftedChars,
                                               pContext->nLabelShiftedCharCount,
                                               ET9_xml_copyOp_toLower,
                                               psDedupe);

            if (eStatus) {
                return eStatus;
            }
        }

        eStatus = __XmlReader_AppendString(pReaderInfo,
                                           pKeyboardInfo,
                                           pKey->psKeyChars,
                                           &pKey->nKeyCharCount,
                                           pContext->psKeyMultitapChars,
                                           pContext->nKeyMultitapCharCount,
                                           ET9_xml_copyOp_none,
                                           psDedupe);

        if (eStatus) {
            return eStatus;
        }

        eStatus = __XmlReader_AppendString(pReaderInfo,
                                           pKeyboardInfo,
                                           pKey->psKeyChars,
                                           &pKey->nKeyCharCount,
                                           pContext->psKeyMultitapShiftedChars,
                                           pContext->nKeyMultitapShiftedCharCount,
                                           ET9_xml_copyOp_toLower,
                                           psDedupe);

        if (eStatus) {
            return eStatus;
        }

        eStatus = __XmlReader_AppendString(pReaderInfo,
                                           pKeyboardInfo,
                                           pKey->psKeyChars,
                                           &pKey->nKeyCharCount,
                                           pContext->psKeyChars,
                                           pContext->nKeyCharCount,
                                           ET9_xml_copyOp_none,
                                           psDedupe);

        if (eStatus) {
            return eStatus;
        }

        eStatus = __XmlReader_AppendString(pReaderInfo,
                                           pKeyboardInfo,
                                           pKey->psKeyChars,
                                           &pKey->nKeyCharCount,
                                           pContext->psKeyShiftedChars,
                                           pContext->nKeyShiftedCharCount,
                                           ET9_xml_copyOp_toLower,
                                           psDedupe);

        if (eStatus) {
            return eStatus;
        }

        eStatus = __XmlReader_AppendString(pReaderInfo,
                                           pKeyboardInfo,
                                           pKey->psKeyChars,
                                           &pKey->nKeyCharCount,
                                           pContext->psKeyPopupChars,
                                           pContext->nKeyPopupCharCount,
                                           ET9_xml_copyOp_none,
                                           psDedupe);

        if (eStatus) {
            return eStatus;
        }

        eStatus = __XmlReader_AppendString(pReaderInfo,
                                           pKeyboardInfo,
                                           pKey->psKeyChars,
                                           &pKey->nKeyCharCount,
                                           pContext->psKeyPopupShiftedChars,
                                           pContext->nKeyPopupShiftedCharCount,
                                           ET9_xml_copyOp_toLower,
                                           psDedupe);

        if (eStatus) {
            return eStatus;
        }

        if (bAddAltChars) {

            eStatus = __XmlReader_AppendAltCharsLower(pReaderInfo,
                                                      pKeyboardInfo,
                                                      pKey->psKeyChars,
                                                      &pKey->nKeyCharCount,
                                                      psDedupe);

            if (eStatus) {
                return eStatus;
            }
        }

        /* shifted */

        eStatus = __XmlReader_CreateString(pReaderInfo,
                                           pKeyboardInfo,
                                           &pKey->psKeyShiftedChars,
                                           &pKey->nKeyShiftedCharCount,
                                           NULL,
                                           0,
                                           ET9_xml_copyOp_none,
                                           psDedupe);

        if (eStatus) {
            return eStatus;
        }

        if (pKey->eKeyType != ET9_xml_keyType_function) {

            eStatus = __XmlReader_AppendString(pReaderInfo,
                                               pKeyboardInfo,
                                               pKey->psKeyShiftedChars,
                                               &pKey->nKeyShiftedCharCount,
                                               pContext->psLabelShiftedChars,
                                               pContext->nLabelShiftedCharCount,
                                               ET9_xml_copyOp_none,
                                               psDedupe);

            if (eStatus) {
                return eStatus;
            }
        }

        eStatus = __XmlReader_AppendString(pReaderInfo,
                                           pKeyboardInfo,
                                           pKey->psKeyShiftedChars,
                                           &pKey->nKeyShiftedCharCount,
                                           pContext->psKeyMultitapShiftedChars,
                                           pContext->nKeyMultitapShiftedCharCount,
                                           ET9_xml_copyOp_none,
                                           psDedupe);

        if (eStatus) {
            return eStatus;
        }

        eStatus = __XmlReader_AppendString(pReaderInfo,
                                           pKeyboardInfo,
                                           pKey->psKeyShiftedChars,
                                           &pKey->nKeyShiftedCharCount,
                                           pContext->psKeyShiftedChars,
                                           pContext->nKeyShiftedCharCount,
                                           ET9_xml_copyOp_none,
                                           psDedupe);

        if (eStatus) {
            return eStatus;
        }

        eStatus = __XmlReader_AppendString(pReaderInfo,
                                           pKeyboardInfo,
                                           pKey->psKeyShiftedChars,
                                           &pKey->nKeyShiftedCharCount,
                                           pContext->psKeyPopupShiftedChars,
                                           pContext->nKeyPopupShiftedCharCount,
                                           ET9_xml_copyOp_none,
                                           psDedupe);

        if (eStatus) {
            return eStatus;
        }

        if (bAddAltChars) {

            eStatus = __XmlReader_AppendAltCharsUpper(pReaderInfo,
                                                      pKeyboardInfo,
                                                      pKey->psKeyShiftedChars,
                                                      &pKey->nKeyShiftedCharCount,
                                                      psDedupe);

            if (eStatus) {
                return eStatus;
            }
        }

        /* must have chars by now */

        if (!pKey->nKeyCharCount) {
            return ET9STATUS_KDB_KEY_HAS_TOO_FEW_CHARS;
        }
    }

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                                             
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __XmlReader_ReadKey(ET9KdbXmlReaderInfo  * const pReaderInfo)
{
    ET9STATUS eStatus;

    WLOG5(fprintf(pLogFile5, "__XmlReader_ReadKey\n");)

    if (pReaderInfo->eCurrToken == ET9_xml_token_Error) {
        return ET9STATUS_KDB_SYNTAX_ERROR;
    }

    if (!__XmlReader_ConsumeToken(pReaderInfo, ET9_xml_token_TagStartKey)) {
        return ET9STATUS_KDB_SYNTAX_ERROR;
    }

    eStatus = __XmlReader_ReadKeyAttributes(pReaderInfo);

    if (eStatus) {
        return eStatus;
    }

    if (!__XmlReader_ConsumeToken(pReaderInfo, ET9_xml_token_TagEndFinal)) {
        return ET9STATUS_KDB_SYNTAX_ERROR;
    }

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                                             
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __XmlReader_ReadKeyVoidAttributes(ET9KdbXmlReaderInfo  * const pReaderInfo)
{
    ET9STATUS eStatus;
    ET9KdbXmlReaderContext * const pContext = &pReaderInfo->pContexts[ET9_xml_E_key];

    WLOG5(fprintf(pLogFile5, "__XmlReader_ReadKeyVoidAttributes\n");)

    if (pReaderInfo->eCurrToken == ET9_xml_token_Error) {
        return ET9STATUS_KDB_SYNTAX_ERROR;
    }

    *pContext = *(pContext - 1);
    _ET9ClearMem((ET9U8*)pContext->pbAttributeSet, sizeof(pContext->pbAttributeSet));

    while (pReaderInfo->eCurrToken != ET9_xml_token_TagEnd &&
           pReaderInfo->eCurrToken != ET9_xml_token_TagEndFinal) {

        eStatus = __XmlReader_ReadAttribute(pReaderInfo);

        if (eStatus) {
            return eStatus;
        }

        if (pReaderInfo->eCurrAttribute == ET9_xml_token_A_unknown) {
            continue;
        }

        if (pContext->pbAttributeSet[pReaderInfo->eCurrAttribute]) {
            return ET9STATUS_KDB_DUPLICATE_ATTRIBUTE;
        }

        pContext->pbAttributeSet[pReaderInfo->eCurrAttribute] = 1;

        switch (pReaderInfo->eCurrAttribute)
        {
            case ET9_xml_token_A_voidSize:
                pContext->fVoidSize = pReaderInfo->fCurrFloat;
                pContext->eVoidSizeSuffix = pReaderInfo->eCurrValueSuffix;
                break;
            case ET9_xml_token_A_unknown:
            default:
                return ET9STATUS_KDB_UNEXPECTED_ATTRIBUTE;
        }
    }

    if (pContext->nConditionValue == pReaderInfo->nConditionValue) {

        ET9KDBPrivate * const pPrivate = &pReaderInfo->pKDBInfo->Private;
        ET9KdbXmlKeyboardInfo * const pKeyboardInfo = &pPrivate->wm.xmlReader.sKeyboardInfo;
        ET9KdbXmlKeyboard * const pKeyboard = &pKeyboardInfo->sKeyboard;
        ET9KdbXmlRow * const pRow = &pKeyboard->pRows[pKeyboard->nRowCount - 1];
        ET9KdbXmlKey * const pKey = &pRow->pKeys[pRow->nKeyCount];

        if (pKeyboardInfo->nUsedKeys >= ET9_KDB_MAX_KEYS) {
            return ET9STATUS_KDB_HAS_TOO_MANY_KEYS;
        }

        ++pRow->nKeyCount;
        ++pKeyboardInfo->nUsedKeys;

        pKey->eKeyType = ET9_xml_keyType_void;

        pKey->fVoidSize             = pContext->fVoidSize;
        pKey->eVoidSizeSuffix       = pContext->eVoidSizeSuffix;
    }

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                                             
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __XmlReader_ReadKeyVoid(ET9KdbXmlReaderInfo  * const pReaderInfo)
{
    ET9STATUS eStatus;

    WLOG5(fprintf(pLogFile5, "__XmlReader_ReadKeyVoid\n");)

    if (pReaderInfo->eCurrToken == ET9_xml_token_Error) {
        return ET9STATUS_KDB_SYNTAX_ERROR;
    }

    if (!__XmlReader_ConsumeToken(pReaderInfo, ET9_xml_token_TagStartVoid)) {
        return ET9STATUS_KDB_SYNTAX_ERROR;
    }

    eStatus = __XmlReader_ReadKeyVoidAttributes(pReaderInfo);

    if (eStatus) {
        return eStatus;
    }

    if (!__XmlReader_ConsumeToken(pReaderInfo, ET9_xml_token_TagEndFinal)) {
        return ET9STATUS_KDB_SYNTAX_ERROR;
    }

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                                             
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __XmlReader_ReadRowAttributes(ET9KdbXmlReaderInfo  * const pReaderInfo)
{
    ET9STATUS eStatus;
    ET9KdbXmlReaderContext * const pContext = &pReaderInfo->pContexts[ET9_xml_E_row];

    WLOG5(fprintf(pLogFile5, "__XmlReader_ReadRowAttributes\n");)

    if (pReaderInfo->eCurrToken == ET9_xml_token_Error) {
        return ET9STATUS_KDB_SYNTAX_ERROR;
    }

    *pContext = *(pContext - 1);
    _ET9ClearMem((ET9U8*)pContext->pbAttributeSet, sizeof(pContext->pbAttributeSet));

    pContext->eRowType = ET9_xml_rowType_keys;

    while (pReaderInfo->eCurrToken != ET9_xml_token_TagEnd &&
           pReaderInfo->eCurrToken != ET9_xml_token_TagEndFinal) {

        eStatus = __XmlReader_ReadAttribute(pReaderInfo);

        if (eStatus) {
            return eStatus;
        }

        if (pReaderInfo->eCurrAttribute == ET9_xml_token_A_unknown) {
            continue;
        }

        if (pContext->pbAttributeSet[pReaderInfo->eCurrAttribute]) {
            return ET9STATUS_KDB_DUPLICATE_ATTRIBUTE;
        }

        pContext->pbAttributeSet[pReaderInfo->eCurrAttribute] = 1;

        switch (pReaderInfo->eCurrAttribute)
        {
            case ET9_xml_token_A_keyWidth:
                pContext->fKeyWidth = pReaderInfo->fCurrFloat;
                pContext->eKeyWidthSuffix = pReaderInfo->eCurrValueSuffix;
                break;
            case ET9_xml_token_A_keyHeight:
                pContext->fKeyHeight = pReaderInfo->fCurrFloat;
                pContext->eKeyHeightSuffix = pReaderInfo->eCurrValueSuffix;
                break;
            case ET9_xml_token_A_verticalGap:
                pContext->fVerticalGap = pReaderInfo->fCurrFloat;
                pContext->eVerticalGapSuffix = pReaderInfo->eCurrValueSuffix;
                break;
            case ET9_xml_token_A_horizontalGap:
                pContext->fHorizontalGap = pReaderInfo->fCurrFloat;
                pContext->eHorizontalGapSuffix = pReaderInfo->eCurrValueSuffix;
                break;
            case ET9_xml_token_A_conditionValue:
                pContext->nConditionValue = pReaderInfo->nCurrInt;
                break;
            default:
                return ET9STATUS_KDB_UNEXPECTED_ATTRIBUTE;
        }
    }

    if (pContext->nConditionValue == pReaderInfo->nConditionValue) {

        ET9KDBPrivate * const pPrivate = &pReaderInfo->pKDBInfo->Private;
        ET9KdbXmlKeyboardInfo * const pKeyboardInfo = &pPrivate->wm.xmlReader.sKeyboardInfo;
        ET9KdbXmlKeyboard * const pKeyboard = &pKeyboardInfo->sKeyboard;
        ET9KdbXmlRow * const pRow = &pKeyboard->pRows[pKeyboard->nRowCount];

        if (pKeyboardInfo->nUsedRows >= ET9_KDB_MAX_ROWS) {
            return ET9STATUS_KDB_HAS_TOO_MANY_ROWS;
        }

        ++pKeyboard->nRowCount;
        ++pKeyboardInfo->nUsedRows;

        pRow->eRowType = pContext->eRowType;

        pRow->nKeyCount = 0;
        pRow->pKeys = &pKeyboardInfo->pKeyPool[pKeyboardInfo->nUsedKeys];

        pRow->fKeyHeight            = pContext->fKeyHeight;
        pRow->eKeyHeightSuffix      = pContext->eKeyHeightSuffix;

        pRow->fVerticalGap          = pContext->fVerticalGap;
        pRow->eVerticalGapSuffix    = pContext->eVerticalGapSuffix;
    }

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                                             
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __XmlReader_ReadRow(ET9KdbXmlReaderInfo  * const pReaderInfo)
{
    ET9STATUS eStatus;

    WLOG5(fprintf(pLogFile5, "__XmlReader_ReadRow\n");)

    if (pReaderInfo->eCurrToken == ET9_xml_token_Error) {
        return ET9STATUS_KDB_SYNTAX_ERROR;
    }

    if (!__XmlReader_ConsumeToken(pReaderInfo, ET9_xml_token_TagStartRow)) {
        return ET9STATUS_KDB_SYNTAX_ERROR;
    }

    eStatus = __XmlReader_ReadRowAttributes(pReaderInfo);

    if (eStatus) {
        return eStatus;
    }

    if (!__XmlReader_ConsumeToken(pReaderInfo, ET9_xml_token_TagEnd)) {
        return ET9STATUS_KDB_SYNTAX_ERROR;
    }

    while (pReaderInfo->eCurrToken == ET9_xml_token_TagStartKey ||
           pReaderInfo->eCurrToken == ET9_xml_token_TagStartVoid) {

        if (pReaderInfo->eCurrToken == ET9_xml_token_TagStartKey) {
            eStatus = __XmlReader_ReadKey(pReaderInfo);
        }
        else {
            eStatus = __XmlReader_ReadKeyVoid(pReaderInfo);
        }

        if (eStatus) {
            return eStatus;
        }
    }

    if (!(__XmlReader_ConsumeToken(pReaderInfo, ET9_xml_token_TagEndRow) &&
          __XmlReader_ConsumeToken(pReaderInfo, ET9_xml_token_TagEnd))) {
        return ET9STATUS_KDB_SYNTAX_ERROR;
    }

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                                             
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __XmlReader_ReadAreaAttributes(ET9KdbXmlReaderInfo  * const pReaderInfo)
{
    ET9STATUS eStatus;
    ET9KdbXmlReaderContext * const pContext = &pReaderInfo->pContexts[ET9_xml_E_row];

    WLOG5(fprintf(pLogFile5, "__XmlReader_ReadAreaAttributes\n");)

    if (pReaderInfo->eCurrToken == ET9_xml_token_Error) {
        return ET9STATUS_KDB_SYNTAX_ERROR;
    }

    *pContext = *(pContext - 1);
    _ET9ClearMem((ET9U8*)pContext->pbAttributeSet, sizeof(pContext->pbAttributeSet));

    pContext->eRowType = ET9_xml_rowType_area;

    while (pReaderInfo->eCurrToken != ET9_xml_token_TagEnd &&
           pReaderInfo->eCurrToken != ET9_xml_token_TagEndFinal) {

        eStatus = __XmlReader_ReadAttribute(pReaderInfo);

        if (eStatus) {
            return eStatus;
        }

        if (pReaderInfo->eCurrAttribute == ET9_xml_token_A_unknown) {
            continue;
        }

        if (pContext->pbAttributeSet[pReaderInfo->eCurrAttribute]) {
            return ET9STATUS_KDB_DUPLICATE_ATTRIBUTE;
        }

        pContext->pbAttributeSet[pReaderInfo->eCurrAttribute] = 1;

        switch (pReaderInfo->eCurrAttribute)
        {
            case ET9_xml_token_A_keyWidth:
                pContext->fKeyWidth = pReaderInfo->fCurrFloat;
                pContext->eKeyWidthSuffix = pReaderInfo->eCurrValueSuffix;
                break;
            case ET9_xml_token_A_keyHeight:
                pContext->fKeyHeight = pReaderInfo->fCurrFloat;
                pContext->eKeyHeightSuffix = pReaderInfo->eCurrValueSuffix;
                break;
            case ET9_xml_token_A_conditionValue:
                pContext->nConditionValue = pReaderInfo->nCurrInt;
                break;
            case ET9_xml_token_A_addAltChars:
                pContext->eAddAltChars = __XmlReader_ConstantToBooleanValue(pReaderInfo->eCurrConstant);
                break;

            default:
                return ET9STATUS_KDB_UNEXPECTED_ATTRIBUTE;
        }
    }

    if (pContext->nConditionValue == pReaderInfo->nConditionValue) {

        ET9KDBPrivate * const pPrivate = &pReaderInfo->pKDBInfo->Private;
        ET9KdbXmlKeyboardInfo * const pKeyboardInfo = &pPrivate->wm.xmlReader.sKeyboardInfo;
        ET9KdbXmlKeyboard * const pKeyboard = &pKeyboardInfo->sKeyboard;
        ET9KdbXmlRow * const pRow = &pKeyboard->pRows[pKeyboard->nRowCount];

        if (pKeyboardInfo->nUsedRows >= ET9_KDB_MAX_ROWS) {
            return ET9STATUS_KDB_HAS_TOO_MANY_ROWS;
        }

        ++pKeyboard->nRowCount;
        ++pKeyboardInfo->nUsedRows;

        pRow->eRowType = pContext->eRowType;

        pRow->nKeyCount = 0;
        pRow->pKeys = &pKeyboardInfo->pKeyPool[pKeyboardInfo->nUsedKeys];
    }

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                                             
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __XmlReader_ReadArea(ET9KdbXmlReaderInfo  * const pReaderInfo)
{
    ET9STATUS eStatus;

    WLOG5(fprintf(pLogFile5, "__XmlReader_ReadArea\n");)

    if (pReaderInfo->eCurrToken == ET9_xml_token_Error) {
        return ET9STATUS_KDB_SYNTAX_ERROR;
    }

    if (!__XmlReader_ConsumeToken(pReaderInfo, ET9_xml_token_TagStartArea)) {
        return ET9STATUS_KDB_SYNTAX_ERROR;
    }

    eStatus = __XmlReader_ReadAreaAttributes(pReaderInfo);

    if (eStatus) {
        return eStatus;
    }

    if (!__XmlReader_ConsumeToken(pReaderInfo, ET9_xml_token_TagEnd)) {
        return ET9STATUS_KDB_SYNTAX_ERROR;
    }

    while (pReaderInfo->eCurrToken == ET9_xml_token_TagStartKey) {

        eStatus = __XmlReader_ReadKey(pReaderInfo);

        if (eStatus) {
            return eStatus;
        }
    }

    if (!(__XmlReader_ConsumeToken(pReaderInfo, ET9_xml_token_TagEndArea) &&
          __XmlReader_ConsumeToken(pReaderInfo, ET9_xml_token_TagEnd))) {
        return ET9STATUS_KDB_SYNTAX_ERROR;
    }

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                                             
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __XmlReader_ReadRowVoidAttributes(ET9KdbXmlReaderInfo  * const pReaderInfo)
{
    ET9STATUS eStatus;
    ET9KdbXmlReaderContext * const pContext = &pReaderInfo->pContexts[ET9_xml_E_row];

    WLOG5(fprintf(pLogFile5, "__XmlReader_ReadRowVoidAttributes\n");)

    if (pReaderInfo->eCurrToken == ET9_xml_token_Error) {
        return ET9STATUS_KDB_SYNTAX_ERROR;
    }

    *pContext = *(pContext - 1);
    _ET9ClearMem((ET9U8*)pContext->pbAttributeSet, sizeof(pContext->pbAttributeSet));

    pContext->eRowType = ET9_xml_rowType_void;

    while (pReaderInfo->eCurrToken != ET9_xml_token_TagEnd &&
           pReaderInfo->eCurrToken != ET9_xml_token_TagEndFinal) {

        eStatus = __XmlReader_ReadAttribute(pReaderInfo);

        if (eStatus) {
            return eStatus;
        }

        if (pReaderInfo->eCurrAttribute == ET9_xml_token_A_unknown) {
            continue;
        }

        if (pContext->pbAttributeSet[pReaderInfo->eCurrAttribute]) {
            return ET9STATUS_KDB_DUPLICATE_ATTRIBUTE;
        }

        pContext->pbAttributeSet[pReaderInfo->eCurrAttribute] = 1;

        switch (pReaderInfo->eCurrAttribute)
        {
            case ET9_xml_token_A_voidSize:
                pContext->fVoidSize = pReaderInfo->fCurrFloat;
                pContext->eVoidSizeSuffix = pReaderInfo->eCurrValueSuffix;
                break;
            case ET9_xml_token_A_conditionValue:
                pContext->nConditionValue = pReaderInfo->nCurrInt;
                break;
            default:
                return ET9STATUS_KDB_UNEXPECTED_ATTRIBUTE;
        }
    }

    if (pContext->nConditionValue == pReaderInfo->nConditionValue) {

        ET9KDBPrivate * const pPrivate = &pReaderInfo->pKDBInfo->Private;
        ET9KdbXmlKeyboardInfo * const pKeyboardInfo = &pPrivate->wm.xmlReader.sKeyboardInfo;
        ET9KdbXmlKeyboard * const pKeyboard = &pKeyboardInfo->sKeyboard;
        ET9KdbXmlRow * const pRow = &pKeyboard->pRows[pKeyboard->nRowCount];

        if (pKeyboardInfo->nUsedRows >= ET9_KDB_MAX_ROWS) {
            return ET9STATUS_KDB_HAS_TOO_MANY_ROWS;
        }

        ++pKeyboard->nRowCount;
        ++pKeyboardInfo->nUsedRows;

        pRow->eRowType = pContext->eRowType;

        pRow->nKeyCount = 0;
        pRow->pKeys = NULL;

        pRow->fKeyHeight            = 0;
        pRow->eKeyHeightSuffix      = ET9_xml_valueSuffix_undef;

        pRow->fVerticalGap          = 0;
        pRow->eVerticalGapSuffix    = ET9_xml_valueSuffix_undef;

        pRow->fVoidSize             = pContext->fVoidSize;
        pRow->eVoidSizeSuffix       = pContext->eVoidSizeSuffix;
    }

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                                             
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __XmlReader_ReadRowVoid(ET9KdbXmlReaderInfo  * const pReaderInfo)
{
    ET9STATUS eStatus;

    WLOG5(fprintf(pLogFile5, "__XmlReader_ReadRowVoid\n");)

    if (pReaderInfo->eCurrToken == ET9_xml_token_Error) {
        return ET9STATUS_KDB_SYNTAX_ERROR;
    }

    if (!__XmlReader_ConsumeToken(pReaderInfo, ET9_xml_token_TagStartVoid)) {
        return ET9STATUS_KDB_SYNTAX_ERROR;
    }

    eStatus = __XmlReader_ReadRowVoidAttributes(pReaderInfo);

    if (eStatus) {
        return eStatus;
    }

    if (!__XmlReader_ConsumeToken(pReaderInfo, ET9_xml_token_TagEndFinal)) {
        return ET9STATUS_KDB_SYNTAX_ERROR;
    }

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                                             
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __XmlReader_ReadKeyboardAttributes(ET9KdbXmlReaderInfo  * const pReaderInfo)
{
    ET9STATUS eStatus;
    ET9KDBPrivate * const pPrivate = &pReaderInfo->pKDBInfo->Private;
    ET9KdbXmlReaderContext * const pContext = &pReaderInfo->pContexts[ET9_xml_E_keyboard];

    WLOG5(fprintf(pLogFile5, "__XmlReader_ReadKeyboardAttributes\n");)

    if (pReaderInfo->eCurrToken == ET9_xml_token_Error) {
        return ET9STATUS_KDB_SYNTAX_ERROR;
    }

    {
        ET9KdbXmlKeyboardInfo * const pKeyboardInfo = &pPrivate->wm.xmlReader.sKeyboardInfo;

        pKeyboardInfo->nUsedKeys = 0;
        pKeyboardInfo->nUsedRows = 0;
        pKeyboardInfo->nUsedSymbs = 0;
    }

    _ET9ClearMem((ET9U8*)pContext, sizeof(ET9KdbXmlReaderContext));

    pContext->nConditionValue           = pReaderInfo->nConditionValue;

    pContext->psIconChars               = &pPrivate->wm.xmlReader.psIconChars[0];
    pContext->psLabelChars              = &pPrivate->wm.xmlReader.psLabelChars[0];
    pContext->psLabelShiftedChars       = &pPrivate->wm.xmlReader.psLabelShiftedChars[0];
    pContext->psKeyChars                = &pPrivate->wm.xmlReader.psKeyChars[0];
    pContext->psKeyShiftedChars         = &pPrivate->wm.xmlReader.psKeyShiftedChars[0];
    pContext->psKeyPopupChars           = &pPrivate->wm.xmlReader.psKeyPopupChars[0];
    pContext->psKeyPopupShiftedChars    = &pPrivate->wm.xmlReader.psKeyPopupShiftedChars[0];
    pContext->psKeyMultitapChars        = &pPrivate->wm.xmlReader.psKeyMultitapChars[0];
    pContext->psKeyMultitapShiftedChars = &pPrivate->wm.xmlReader.psKeyMultitapShiftedChars[0];

    while (pReaderInfo->eCurrToken != ET9_xml_token_TagEnd &&
           pReaderInfo->eCurrToken != ET9_xml_token_TagEndFinal) {

        eStatus = __XmlReader_ReadAttribute(pReaderInfo);

        if (eStatus) {
            return eStatus;
        }

        if (pReaderInfo->eCurrAttribute == ET9_xml_token_A_unknown) {
            continue;
        }

        if (pContext->pbAttributeSet[pReaderInfo->eCurrAttribute]) {
            return ET9STATUS_KDB_DUPLICATE_ATTRIBUTE;
        }

        pContext->pbAttributeSet[pReaderInfo->eCurrAttribute] = 1;

        switch (pReaderInfo->eCurrAttribute)
        {
            case ET9_xml_token_A_keyWidth:
                pContext->fKeyWidth = pReaderInfo->fCurrFloat;
                pContext->eKeyWidthSuffix = pReaderInfo->eCurrValueSuffix;
                break;
            case ET9_xml_token_A_keyHeight:
                pContext->fKeyHeight = pReaderInfo->fCurrFloat;
                pContext->eKeyHeightSuffix = pReaderInfo->eCurrValueSuffix;
                break;
            case ET9_xml_token_A_verticalBorder:
                pContext->fVerticalBorder = pReaderInfo->fCurrFloat;
                pContext->eVerticalBorderSuffix = pReaderInfo->eCurrValueSuffix;
                break;
            case ET9_xml_token_A_horizontalBorder:
                pContext->fHorizontalBorder = pReaderInfo->fCurrFloat;
                pContext->eHorizontalBorderSuffix = pReaderInfo->eCurrValueSuffix;
                break;
            case ET9_xml_token_A_verticalGap:
                pContext->fVerticalGap = pReaderInfo->fCurrFloat;
                pContext->eVerticalGapSuffix = pReaderInfo->eCurrValueSuffix;
                break;
            case ET9_xml_token_A_horizontalGap:
                pContext->fHorizontalGap = pReaderInfo->fCurrFloat;
                pContext->eHorizontalGapSuffix = pReaderInfo->eCurrValueSuffix;
                break;
            case ET9_xml_token_A_majorVersion:
                pContext->bMajorVersion = (ET9U8)pReaderInfo->nCurrInt;
                break;
            case ET9_xml_token_A_minorVersion:
                pContext->bMinorVersion = (ET9U8)pReaderInfo->nCurrInt;
                break;
            case ET9_xml_token_A_primaryId:
                pContext->bPrimaryID = (ET9U8)pReaderInfo->nCurrInt;
                break;
            case ET9_xml_token_A_secondaryId:
                pContext->bSecondaryID = (ET9U8)pReaderInfo->nCurrInt;
                break;
            case ET9_xml_token_A_supportsExact:
                pContext->eSupportsExact = __XmlReader_ConstantToBooleanValue(pReaderInfo->eCurrConstant);
                break;
            case ET9_xml_token_A_defaultLayoutWidth:
                pContext->wDefaultLayoutWidth = (ET9U16)pReaderInfo->nCurrInt;
                break;
            case ET9_xml_token_A_defaultLayoutHeight:
                pContext->wDefaultLayoutHeight = (ET9U16)pReaderInfo->nCurrInt;
                break;
            case ET9_xml_token_A_addAltChars:
                pContext->eAddAltChars = __XmlReader_ConstantToBooleanValue(pReaderInfo->eCurrConstant);
                break;

            default:
                return ET9STATUS_KDB_UNEXPECTED_ATTRIBUTE;
        }
    }

    if (!pContext->pbAttributeSet[ET9_xml_token_A_primaryId] ||
        !pContext->pbAttributeSet[ET9_xml_token_A_secondaryId]) {

        return ET9STATUS_KDB_MISSING_ATTRIBUTE;
    }

    if (( pContext->pbAttributeSet[ET9_xml_token_A_defaultLayoutWidth] &&
         !pContext->pbAttributeSet[ET9_xml_token_A_defaultLayoutHeight]) ||
        (!pContext->pbAttributeSet[ET9_xml_token_A_defaultLayoutWidth] &&
          pContext->pbAttributeSet[ET9_xml_token_A_defaultLayoutHeight])) {

        return ET9STATUS_KDB_MISSING_ATTRIBUTE;
    }

    {
        ET9KdbXmlKeyboardInfo * const pKeyboardInfo = &pPrivate->wm.xmlReader.sKeyboardInfo;
        ET9KdbXmlKeyboard * const pKeyboard = &pKeyboardInfo->sKeyboard;

        pKeyboard->bPrimaryID               = pContext->bPrimaryID;
        pKeyboard->bSecondaryID             = pContext->bSecondaryID;
        pKeyboard->bMajorVersion            = pContext->bMajorVersion;
        pKeyboard->bMinorVersion            = pContext->bMinorVersion;
        pKeyboard->fVerticalBorder          = pContext->fVerticalBorder;
        pKeyboard->eVerticalBorderSuffix    = pContext->eVerticalBorderSuffix;
        pKeyboard->fHorizontalBorder        = pContext->fHorizontalBorder;
        pKeyboard->eHorizontalBorderSuffix  = pContext->eHorizontalBorderSuffix;
        pKeyboard->eSupportsExact           = pContext->eSupportsExact;

        if (pReaderInfo->wLayoutWidth || pReaderInfo->wLayoutHeight) {

            pKeyboard->wLayoutWidth         = pReaderInfo->wLayoutWidth;
            pKeyboard->wLayoutHeight        = pReaderInfo->wLayoutHeight;
        }
        else {
            pKeyboard->wLayoutWidth         = pContext->wDefaultLayoutWidth;
            pKeyboard->wLayoutHeight        = pContext->wDefaultLayoutHeight;
        }

        pKeyboard->nRowCount = 0;
        pKeyboard->pRows = &pKeyboardInfo->pRowPool[0];
    }

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                                             
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __XmlReader_ReadKeyboard(ET9KdbXmlReaderInfo  * const pReaderInfo)
{
    ET9STATUS eStatus;

    WLOG5(fprintf(pLogFile5, "__XmlReader_ReadKeyboard\n");)

    if (pReaderInfo->eCurrToken == ET9_xml_token_Error) {
        return ET9STATUS_KDB_SYNTAX_ERROR;
    }

    if (!__XmlReader_ConsumeToken(pReaderInfo, ET9_xml_token_TagStartKeyboard)) {
        return ET9STATUS_KDB_SYNTAX_ERROR;
    }

    eStatus = __XmlReader_ReadKeyboardAttributes(pReaderInfo);

    if (eStatus) {
        return eStatus;
    }

    if (!__XmlReader_ConsumeToken(pReaderInfo, ET9_xml_token_TagEnd)) {
        return ET9STATUS_KDB_SYNTAX_ERROR;
    }

    while (pReaderInfo->eCurrToken == ET9_xml_token_TagStartRow ||
           pReaderInfo->eCurrToken == ET9_xml_token_TagStartArea ||
           pReaderInfo->eCurrToken == ET9_xml_token_TagStartVoid) {

        switch (pReaderInfo->eCurrToken)
        {
            case ET9_xml_token_TagStartRow:
               eStatus = __XmlReader_ReadRow(pReaderInfo);
               break;
            case ET9_xml_token_TagStartArea:
               eStatus = __XmlReader_ReadArea(pReaderInfo);
               break;
            case ET9_xml_token_TagStartVoid:
               eStatus = __XmlReader_ReadRowVoid(pReaderInfo);
               break;
            default:
               eStatus = ET9STATUS_KDB_SYNTAX_ERROR;
               break;
        }

        if (eStatus) {
            return eStatus;
        }
    }

    if (!(__XmlReader_ConsumeToken(pReaderInfo, ET9_xml_token_TagEndKeyboard) &&
          __XmlReader_ConsumeToken(pReaderInfo, ET9_xml_token_TagEnd))) {
        return ET9STATUS_KDB_SYNTAX_ERROR;
    }

    if (pReaderInfo->eCurrToken != ET9_xml_token_EOF) {
        return ET9STATUS_KDB_UNEXPECTED_CONTENT;
    }

    __XmlReader_PruneAltChars(pReaderInfo, &pReaderInfo->pKDBInfo->Private.wm.xmlReader.sKeyboardInfo);

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                               
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __XmlReader_BasicGaps(ET9KdbXmlKeyboardInfo * const pKeyboardInfo)
{
    const ET9UINT nGapDefault = 2;

    ET9UINT nCount;
    ET9KdbXmlRow *pRow;
    ET9KdbXmlRow *pPrevRow;
    ET9KdbXmlKey *pKey;
    ET9KdbXmlKey *pPrevKey;

    /* applicable? */

    if (pKeyboardInfo->bAreaBased) {
        return ET9STATUS_NONE;
    }

    /* vertical gaps */

    pPrevRow = NULL;
    pRow = pKeyboardInfo->sKeyboard.pRows;
    for (nCount = pKeyboardInfo->sKeyboard.nRowCount; nCount; --nCount, pPrevRow = pRow++) {

        if (pRow->eRowType == ET9_xml_rowType_void) {

            pRow->nUpGap = 0;
            pRow->nDownGap = 0;

            if (pPrevRow) {
                pPrevRow->nDownGap = 0;
            }

            continue;
        }

        switch (pRow->eVerticalGapSuffix)
        {
            case ET9_xml_valueSuffix_undef:
                pRow->nUpGap = nGapDefault;
                break;
            case ET9_xml_valueSuffix_percent:
                pRow->nUpGap = (ET9UINT)(pRow->fVerticalGap / 100.0 * pKeyboardInfo->sKeyboard.wLayoutHeight + 0.5);
                break;
            case ET9_xml_valueSuffix_pixelsDI:
                pRow->nUpGap = (ET9UINT)pRow->fVerticalGap;
                break;
            default:
                ET9Assert(0);
                break;
        }

        pRow->nDownGap = pRow->nUpGap;

        if (pPrevRow && pPrevRow->eRowType == ET9_xml_rowType_void) {
            pRow->nUpGap = 0;
        }
    }

    /* horizontal gaps */

    pPrevKey = NULL;
    pKey = &pKeyboardInfo->pKeyPool[0];
    for (nCount = pKeyboardInfo->nUsedKeys; nCount; --nCount, pPrevKey = pKey++) {

        if (pKey->eKeyType == ET9_xml_keyType_void) {

            pKey->nLeftGap = 0;
            pKey->nRightGap = 0;

            if (pPrevKey) {
                pPrevKey->nRightGap = 0;
            }

            continue;
        }

        switch (pKey->eHorizontalGapSuffix)
        {
            case ET9_xml_valueSuffix_undef:
                pKey->nLeftGap = nGapDefault;
                break;
            case ET9_xml_valueSuffix_percent:
                pKey->nLeftGap = (ET9UINT)(pKey->fHorizontalGap / 100.0 * pKeyboardInfo->sKeyboard.wLayoutWidth + 0.5);
                break;
            case ET9_xml_valueSuffix_pixelsDI:
                pKey->nLeftGap = (ET9UINT)pKey->fHorizontalGap;
                break;
            default:
                ET9Assert(0);
                break;
        }

        pKey->nRightGap = pKey->nLeftGap;

        if (pPrevKey && pPrevKey->eKeyType == ET9_xml_keyType_void) {
            pKey->nLeftGap = 0;
        }
    }

    /* border */

    switch (pKeyboardInfo->sKeyboard.eVerticalBorderSuffix)
    {
        case ET9_xml_valueSuffix_undef:
            pKeyboardInfo->sKeyboard.nVerticalBorder = nGapDefault;
            break;
        case ET9_xml_valueSuffix_percent:
            pKeyboardInfo->sKeyboard.nVerticalBorder = (ET9UINT)(pKeyboardInfo->sKeyboard.fVerticalBorder / 100.0 * pKeyboardInfo->sKeyboard.wLayoutHeight + 0.5);
            break;
        case ET9_xml_valueSuffix_pixelsDI:
            pKeyboardInfo->sKeyboard.nVerticalBorder = (ET9UINT)pKeyboardInfo->sKeyboard.fVerticalBorder;
            break;
        default:
            ET9Assert(0);
            break;
    }

    switch (pKeyboardInfo->sKeyboard.eHorizontalBorderSuffix)
    {
        case ET9_xml_valueSuffix_undef:
            pKeyboardInfo->sKeyboard.nHorizontalBorder = nGapDefault;
            break;
        case ET9_xml_valueSuffix_percent:
            pKeyboardInfo->sKeyboard.nHorizontalBorder = (ET9UINT)(pKeyboardInfo->sKeyboard.fHorizontalBorder / 100.0 * pKeyboardInfo->sKeyboard.wLayoutWidth + 0.5);
            break;
        case ET9_xml_valueSuffix_pixelsDI:
            pKeyboardInfo->sKeyboard.nHorizontalBorder = (ET9UINT)pKeyboardInfo->sKeyboard.fHorizontalBorder;
            break;
        default:
            ET9Assert(0);
            break;
    }

    /* apply border */

    pRow = &pKeyboardInfo->sKeyboard.pRows[0];

    if (pRow->eRowType != ET9_xml_rowType_void) {
        pRow->nUpGap = pKeyboardInfo->sKeyboard.nVerticalBorder;
    }

    pRow = &pKeyboardInfo->sKeyboard.pRows[pKeyboardInfo->sKeyboard.nRowCount - 1];

    if (pRow->eRowType != ET9_xml_rowType_void) {
        pRow->nDownGap = pKeyboardInfo->sKeyboard.nVerticalBorder;
    }

    pRow = pKeyboardInfo->sKeyboard.pRows;
    for (nCount = pKeyboardInfo->sKeyboard.nRowCount; nCount; --nCount, ++pRow) {

        if (pRow->eRowType == ET9_xml_rowType_keys) {

            pKey = &pRow->pKeys[0];

            if (pKey->eKeyType != ET9_xml_keyType_void) {
                pKey->nLeftGap = pKeyboardInfo->sKeyboard.nHorizontalBorder;
            }

            pKey = &pRow->pKeys[pRow->nKeyCount - 1];

            if (pKey->eKeyType != ET9_xml_keyType_void) {
                pKey->nRightGap = pKeyboardInfo->sKeyboard.nHorizontalBorder;
            }
        }
    }

    /* done */

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                               
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __XmlReader_MergeGaps(ET9KdbXmlKeyboardInfo * const pKeyboardInfo)
{
    ET9UINT nRowCount;
    ET9KdbXmlRow *pRow;
    ET9KdbXmlRow *pPrevRow;

    /* applicable? */

    if (pKeyboardInfo->bAreaBased) {
        return ET9STATUS_NONE;
    }

    /* */

    pPrevRow = NULL;
    pRow = pKeyboardInfo->sKeyboard.pRows;
    for (nRowCount = pKeyboardInfo->sKeyboard.nRowCount; nRowCount; --nRowCount, pPrevRow = pRow++) {

        ET9UINT nKeyCount;
        ET9KdbXmlKey *pKey;
        ET9KdbXmlKey *pPrevKey;

        pPrevKey = NULL;
        pKey = pRow->pKeys;
        for (nKeyCount = pRow->nKeyCount; nKeyCount; --nKeyCount, pPrevKey = pKey++) {

            if (pPrevKey) {

                const ET9UINT nGap = __ET9Max(pPrevKey->nRightGap, pKey->nLeftGap);

                pPrevKey->nRightGap = nGap / 2;
                pKey->nLeftGap = nGap - pPrevKey->nRightGap;
            }
        }

        if (pPrevRow) {

            const ET9UINT nGap = __ET9Max(pPrevRow->nDownGap, pRow->nUpGap);

            pPrevRow->nDownGap = nGap / 2;
            pRow->nUpGap = nGap - pPrevRow->nDownGap;
        }
    }

    /* done */

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                               
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __XmlReader_BasicSize(ET9KdbXmlKeyboardInfo * const pKeyboardInfo)
{
    const ET9UINT nSizeDefault = 10;

    ET9UINT nRowCount;
    ET9KdbXmlRow *pRow;

    /* */

    pRow = pKeyboardInfo->sKeyboard.pRows;
    for (nRowCount = pKeyboardInfo->sKeyboard.nRowCount; nRowCount; --nRowCount, ++pRow) {

        ET9UINT nKeyCount;
        ET9KdbXmlKey *pKey;

        if (pRow->eRowType == ET9_xml_rowType_void) {
            switch (pRow->eVoidSizeSuffix)
            {
                case ET9_xml_valueSuffix_undef:
                    pRow->nHeight = 0;
                    break;
                case ET9_xml_valueSuffix_percent:
                    pRow->nHeight = (ET9UINT)(pRow->fVoidSize / 100.0 * pKeyboardInfo->sKeyboard.wLayoutHeight + fSizeRound);
                    break;
                case ET9_xml_valueSuffix_pixelsDI:
                    pRow->nHeight = (ET9UINT)pRow->fVoidSize;
                    break;
                default:
                    ET9Assert(0);
                    break;
            }
            continue;
        }

        switch (pRow->eKeyHeightSuffix)
        {
            case ET9_xml_valueSuffix_undef:
                pRow->nHeight = nSizeDefault;
                break;
            case ET9_xml_valueSuffix_percent:
                pRow->nHeight = (ET9UINT)(pRow->fKeyHeight / 100.0 * pKeyboardInfo->sKeyboard.wLayoutHeight + fSizeRound);
                break;
            case ET9_xml_valueSuffix_pixelsDI:
                pRow->nHeight = (ET9UINT)pRow->fKeyHeight;
                break;
            default:
                ET9Assert(0);
                break;
       }

        pKey = pRow->pKeys;
        for (nKeyCount = pRow->nKeyCount; nKeyCount; --nKeyCount, ++pKey) {

            if (pKey->eKeyType == ET9_xml_keyType_void) {
                switch (pKey->eVoidSizeSuffix)
                {
                    case ET9_xml_valueSuffix_undef:
                        pKey->nWidth = 0;
                        break;
                    case ET9_xml_valueSuffix_percent:
                        pKey->nWidth = (ET9UINT)(pKey->fVoidSize / 100.0 * pKeyboardInfo->sKeyboard.wLayoutWidth + fSizeRound);
                        break;
                    case ET9_xml_valueSuffix_pixelsDI:
                        pKey->nWidth = (ET9UINT)pKey->fVoidSize;
                        break;
                    default:
                        ET9Assert(0);
                        break;
                }
                continue;
            }

            switch (pKey->eKeyWidthSuffix)
            {
                case ET9_xml_valueSuffix_undef:
                    pKey->nWidth = nSizeDefault;
                    break;
                case ET9_xml_valueSuffix_percent:
                    pKey->nWidth = (ET9UINT)(pKey->fKeyWidth / 100.0 * pKeyboardInfo->sKeyboard.wLayoutWidth + fSizeRound);
                    break;
                case ET9_xml_valueSuffix_pixelsDI:
                    pKey->nWidth = (ET9UINT)pKey->fKeyWidth;
                    break;
                default:
                    ET9Assert(0);
                    break;
            }

            if (pRow->eRowType != ET9_xml_rowType_area) {
                pKey->nTop = 0;
                pKey->nLeft = 0;
                pKey->nHeight = pRow->nHeight;
                continue;
            }

            switch (pKey->eKeyTopSuffix)
            {
                case ET9_xml_valueSuffix_undef:
                    pKey->nTop = nSizeDefault;
                    break;
                case ET9_xml_valueSuffix_percent:
                    pKey->nTop = (ET9UINT)(pKey->fKeyTop / 100.0 * pKeyboardInfo->sKeyboard.wLayoutHeight + fPosRound);
                    break;
                case ET9_xml_valueSuffix_pixelsDI:
                    pKey->nTop = (ET9UINT)pKey->fKeyTop;
                    break;
                default:
                    ET9Assert(0);
                    break;
            }

            switch (pKey->eKeyLeftSuffix)
            {
                case ET9_xml_valueSuffix_undef:
                    pKey->nLeft = nSizeDefault;
                    break;
                case ET9_xml_valueSuffix_percent:
                    pKey->nLeft = (ET9UINT)(pKey->fKeyLeft / 100.0 * pKeyboardInfo->sKeyboard.wLayoutWidth + fPosRound);
                    break;
                case ET9_xml_valueSuffix_pixelsDI:
                    pKey->nLeft = (ET9UINT)pKey->fKeyLeft;
                    break;
                default:
                    ET9Assert(0);
                    break;
            }

            switch (pKey->eKeyHeightSuffix)
            {
                case ET9_xml_valueSuffix_undef:
                    pKey->nHeight = nSizeDefault;
                    break;
                case ET9_xml_valueSuffix_percent:
                    pKey->nHeight = (ET9UINT)(pKey->fKeyHeight / 100.0 * pKeyboardInfo->sKeyboard.wLayoutHeight + fSizeRound);
                    break;
                case ET9_xml_valueSuffix_pixelsDI:
                    pKey->nHeight = (ET9UINT)pKey->fKeyHeight;
                    break;
                default:
                    ET9Assert(0);
                    break;
            }
        }
    }

    /* done */

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                               
 *
 *                    
 */

static ET9UINT ET9LOCALCALL __XmlReader_CalculateOpenKeyVoidCount(ET9KdbXmlRow * const pRow)
{
    ET9UINT nVoidCount;
    ET9UINT nKeyCount;
    ET9KdbXmlKey *pKey;

    nVoidCount = 0;
    pKey = pRow->pKeys;
    for (nKeyCount = pRow->nKeyCount; nKeyCount; --nKeyCount, ++pKey) {
        if (pKey->eKeyType == ET9_xml_keyType_void && !pKey->eVoidSizeSuffix) {
            ++nVoidCount;
        }
    }

    return nVoidCount;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                               
 *
 *                   
 */

static ET9UINT ET9LOCALCALL __XmlReader_CalculateRowWidth(ET9KdbXmlRow * const pRow)
{
    ET9UINT nTotWidth;
    ET9UINT nKeyCount;
    ET9KdbXmlKey *pKey;

    nTotWidth = 0;
    pKey = pRow->pKeys;
    for (nKeyCount = pRow->nKeyCount; nKeyCount; --nKeyCount, ++pKey) {
        nTotWidth += pKey->nLeftGap + pKey->nWidth + pKey->nRightGap;
    }

    return nTotWidth;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                               
 *
 *                            
 */

static ET9UINT ET9LOCALCALL __XmlReader_CalculateRowsHeight(ET9KdbXmlKeyboardInfo * const pKeyboardInfo)
{
    ET9UINT nTotHeight;
    ET9UINT nRowCount;
    ET9KdbXmlRow *pRow;

    nTotHeight = 0;
    pRow = pKeyboardInfo->sKeyboard.pRows;
    for (nRowCount = pKeyboardInfo->sKeyboard.nRowCount; nRowCount; --nRowCount, ++pRow) {
        nTotHeight += pRow->nUpGap + pRow->nHeight + pRow->nDownGap;
    }

    return nTotHeight;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                               
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __XmlReader_ExpandKeyVoids(ET9KdbXmlKeyboardInfo * const pKeyboardInfo)
{
    ET9UINT nRowCount;
    ET9KdbXmlRow *pRow;

    /* applicable? */

    if (pKeyboardInfo->bAreaBased) {
        return ET9STATUS_NONE;
    }

    /* */

    pRow = pKeyboardInfo->sKeyboard.pRows;
    for (nRowCount = pKeyboardInfo->sKeyboard.nRowCount; nRowCount; --nRowCount, ++pRow) {

        const ET9UINT nVoidCount = __XmlReader_CalculateOpenKeyVoidCount(pRow);

        if (nVoidCount) {

            const ET9UINT nRowWidth = __XmlReader_CalculateRowWidth(pRow);

            if (nRowWidth < pKeyboardInfo->sKeyboard.wLayoutWidth) {

                const ET9UINT nDiff = pKeyboardInfo->sKeyboard.wLayoutWidth - nRowWidth;

                ET9UINT nKeyCount;
                ET9KdbXmlKey *pKey;

                ET9UINT nExpandSpace;
                ET9UINT nExpandCount;

                nExpandSpace = nDiff;
                nExpandCount = nVoidCount;

                pKey = pRow->pKeys;
                for (nKeyCount = pRow->nKeyCount; nKeyCount; --nKeyCount, ++pKey) {

                    if (pKey->eKeyType == ET9_xml_keyType_void && !pKey->eVoidSizeSuffix) {

                        const ET9UINT nExpand = nExpandSpace / nExpandCount;

                        pKey->nWidth += nExpand;
                        nExpandSpace -= nExpand;
                        --nExpandCount;

                        if (!nExpandCount) {
                            break;
                        }
                    }
                }
            }
        }
    }

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                               
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __XmlReader_ExpandKeys(ET9KdbXmlKeyboardInfo * const pKeyboardInfo)
{
    ET9UINT nBestDiff;
    ET9UINT nRowCount;
    ET9KdbXmlRow *pRow;

    /* applicable? */

    if (pKeyboardInfo->bAreaBased) {
        return ET9STATUS_NONE;
    }

    /* */

    nBestDiff = 0;
    pRow = pKeyboardInfo->sKeyboard.pRows;
    for (nRowCount = pKeyboardInfo->sKeyboard.nRowCount; nRowCount; --nRowCount, ++pRow) {

        if (pRow->eRowType == ET9_xml_rowType_keys) {

            const ET9UINT nVoidCount = __XmlReader_CalculateOpenKeyVoidCount(pRow);

            if (!nVoidCount) {

                const ET9UINT nRowWidth = __XmlReader_CalculateRowWidth(pRow);

                if (nRowWidth < pKeyboardInfo->sKeyboard.wLayoutWidth) {

                    const ET9UINT nDiff = pKeyboardInfo->sKeyboard.wLayoutWidth - nRowWidth;

                    if (nBestDiff < nDiff) {
                        nBestDiff = nDiff;
                    }
                }
            }
        }
    }

    if (!nBestDiff) {
        return ET9STATUS_NONE;
    }

    pRow = pKeyboardInfo->sKeyboard.pRows;
    for (nRowCount = pKeyboardInfo->sKeyboard.nRowCount; nRowCount; --nRowCount, ++pRow) {

        if (pRow->eRowType == ET9_xml_rowType_keys) {

            const ET9UINT nRowWidth = __XmlReader_CalculateRowWidth(pRow);

            ET9UINT nDiff;
            ET9UINT nRepeatCount;
            ET9UINT nKeyCount;
            ET9KdbXmlKey *pKey;

            if (nRowWidth >= pKeyboardInfo->sKeyboard.wLayoutWidth) {
                continue;
            }

            nDiff = __ET9Min(nBestDiff, (pKeyboardInfo->sKeyboard.wLayoutWidth - nRowWidth));

            for (nRepeatCount = 2; nRepeatCount && nDiff; --nRepeatCount) {

                pKey = pRow->pKeys;
                for (nKeyCount = pRow->nKeyCount; nKeyCount && nDiff; --nKeyCount, ++pKey) {

                    if (pKey->eKeyType == ET9_xml_keyType_void) {
                        continue;
                    }

                    if (pKey->eKeyWidthSuffix != ET9_xml_valueSuffix_pixelsDI) {

                        ET9UINT nPart;

                        nPart = (pKey->nWidth * nBestDiff) / pKeyboardInfo->sKeyboard.wLayoutWidth;

                        if (!nPart) {
                            nPart = 1;
                        }

                        if (nPart > nDiff) {
                            nPart = nDiff;
                        }

                        nDiff -= nPart;
                        pKey->nWidth += nPart;
                    }
                }
            }
        }
    }

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                               
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __XmlReader_ExpandRowVoids(ET9KdbXmlKeyboardInfo * const pKeyboardInfo)
{
    ET9UINT nVoidCount;
    ET9UINT nRowCount;
    ET9KdbXmlRow *pRow;

    /* applicable? */

    if (pKeyboardInfo->bAreaBased) {
        return ET9STATUS_NONE;
    }

    /* */

    nVoidCount = 0;
    pRow = pKeyboardInfo->sKeyboard.pRows;
    for (nRowCount = pKeyboardInfo->sKeyboard.nRowCount; nRowCount; --nRowCount, ++pRow) {
        if (pRow->eRowType == ET9_xml_rowType_void && !pRow->eVoidSizeSuffix) {
            ++nVoidCount;
        }
    }

    if (!nVoidCount) {
        return ET9STATUS_NONE;
    }

    {
        const ET9UINT nRowsHeight = __XmlReader_CalculateRowsHeight(pKeyboardInfo);

        if (pKeyboardInfo->sKeyboard.wLayoutHeight > nRowsHeight) {

            const ET9UINT nDiff = pKeyboardInfo->sKeyboard.wLayoutHeight - nRowsHeight;

            ET9UINT nExpandSpace;
            ET9UINT nExpandCount;

            nExpandSpace = nDiff;
            nExpandCount = nVoidCount;

            pRow = pKeyboardInfo->sKeyboard.pRows;
            for (nRowCount = pKeyboardInfo->sKeyboard.nRowCount; nRowCount; --nRowCount, ++pRow) {

                if (pRow->eRowType == ET9_xml_rowType_void && !pRow->eVoidSizeSuffix) {

                    const ET9UINT nExpand = nExpandSpace / nExpandCount;

                    pRow->nHeight += nExpand;
                    nExpandSpace -= nExpand;
                    --nExpandCount;

                    if (!nExpandCount) {
                        break;
                    }
                }
            }
        }
    }

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                               
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __XmlReader_ExpandRows(ET9KdbXmlKeyboardInfo * const pKeyboardInfo)
{
    const ET9UINT nRowsHeight = __XmlReader_CalculateRowsHeight(pKeyboardInfo);

    /* applicable? */

    if (pKeyboardInfo->bAreaBased) {
        return ET9STATUS_NONE;
    }

    /* */

    if (nRowsHeight >= pKeyboardInfo->sKeyboard.wLayoutHeight) {
        return ET9STATUS_NONE;
    }

    {
        ET9UINT nDiff = pKeyboardInfo->sKeyboard.wLayoutHeight - nRowsHeight;

        ET9UINT nRowCount;
        ET9UINT nRepeatCount;
        ET9KdbXmlRow *pRow;
        ET9KdbXmlRow *pPrevRow;

        pRow = pKeyboardInfo->sKeyboard.pRows;
        for (nRowCount = pKeyboardInfo->sKeyboard.nRowCount; nRowCount && nDiff; --nRowCount, ++pRow) {

            if (pRow->eKeyHeightSuffix != ET9_xml_valueSuffix_pixelsDI && pRow->eRowType != ET9_xml_rowType_void) {
                --nDiff;
                ++pRow->nHeight;
            }
        }

        pPrevRow = NULL;
        pRow = pKeyboardInfo->sKeyboard.pRows;
        for (nRowCount = pKeyboardInfo->sKeyboard.nRowCount; nRowCount && nDiff; --nRowCount, pPrevRow = pRow++) {

            if (!pPrevRow) {
                continue;
            }

            if (pRow->eRowType == ET9_xml_rowType_void || pPrevRow->eRowType == ET9_xml_rowType_void) {
                continue;
            }

            if (pPrevRow->eVerticalGapSuffix != ET9_xml_valueSuffix_pixelsDI) {
                --nDiff;
                ++pPrevRow->nDownGap;
            }
            else if (pRow->eVerticalGapSuffix != ET9_xml_valueSuffix_pixelsDI) {
                --nDiff;
                ++pRow->nUpGap;
            }
        }

        for (nRepeatCount = 2; nRepeatCount; --nRepeatCount) {

            pRow = pKeyboardInfo->sKeyboard.pRows;
            for (nRowCount = pKeyboardInfo->sKeyboard.nRowCount; nRowCount && nDiff; --nRowCount, ++pRow) {

                if (pRow->eKeyHeightSuffix != ET9_xml_valueSuffix_pixelsDI && pRow->eRowType != ET9_xml_rowType_void) {
                    --nDiff;
                    ++pRow->nHeight;
                }
            }
        }
    }

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                               
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __XmlReader_PositionKeys(ET9KdbXmlKeyboardInfo * const pKeyboardInfo)
{
    ET9UINT nCurrY;
    ET9UINT nRowCount;
    ET9KdbXmlRow *pRow;

    /* loop rows */

    nCurrY = 0;
    pRow = pKeyboardInfo->sKeyboard.pRows;
    for (nRowCount = pKeyboardInfo->sKeyboard.nRowCount; nRowCount; --nRowCount, ++pRow) {

        if (pRow->eRowType == ET9_xml_rowType_area) {

            ET9UINT nKeyCount;
            ET9KdbXmlKey *pKey;

            pKey = pRow->pKeys;
            for (nKeyCount = pRow->nKeyCount; nKeyCount; --nKeyCount, ++pKey) {

                pKey->nULX = pKey->nLeft;
                pKey->nULY = pKey->nTop;
            }
        }
        else {

            const ET9UINT nTotRowHeight = pRow->nUpGap + pRow->nHeight + pRow->nDownGap;

            ET9UINT nCurrX;
            ET9UINT nKeyCount;
            ET9KdbXmlKey *pKey;

            nCurrX = 0;
            pKey = pRow->pKeys;
            for (nKeyCount = pRow->nKeyCount; nKeyCount; --nKeyCount, ++pKey) {

                const ET9UINT nTotKeyWidth = pKey->nLeftGap + pKey->nWidth + pKey->nRightGap;

                pKey->nULX = nCurrX + pKey->nLeftGap;
                pKey->nULY = nCurrY + pRow->nUpGap;

                nCurrX += nTotKeyWidth;
            }

            nCurrY += nTotRowHeight;
        }
    }

    /* done */

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                               
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __XmlReader_RemoveVoidRowItems(ET9KdbXmlKeyboardInfo * const pKeyboardInfo)
{
    ET9UINT nRowCount;
    ET9UINT nVoidCount;
    ET9KdbXmlRow *pRow;

    /* applicable? */

    if (pKeyboardInfo->bAreaBased) {
        return ET9STATUS_NONE;
    }

    /* */

    WLOG5(fprintf(pLogFile5, "__XmlReader_RemoveVoidRowItems, nRowCount %u\n", pKeyboardInfo->sKeyboard.nRowCount);)

    nVoidCount = 0;
    pRow = pKeyboardInfo->sKeyboard.pRows;
    for (nRowCount = pKeyboardInfo->sKeyboard.nRowCount; nRowCount; ) {

        if (nVoidCount) {

            WLOG5(fprintf(pLogFile5, "  move %2u <- %2u\n", (int)(pRow - pKeyboardInfo->sKeyboard.pRows), (int)(pRow - pKeyboardInfo->sKeyboard.pRows + nVoidCount));)

            *pRow = *(pRow + nVoidCount);
        }

        if (pRow->eRowType == ET9_xml_rowType_void) {

            WLOG5(fprintf(pLogFile5, "  VOID\n");)

            ++nVoidCount;
            --nRowCount;
            --pKeyboardInfo->sKeyboard.nRowCount;
        }
        else {
            ++pRow;
            --nRowCount;
        }
    }

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                               
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __XmlReader_RemoveVoidKeyItems(ET9KdbXmlKeyboardInfo * const pKeyboardInfo)
{
    ET9UINT nRowCount;
    ET9KdbXmlRow *pRow;

    /* applicable? */

    if (pKeyboardInfo->bAreaBased) {
        return ET9STATUS_NONE;
    }

    /* */

    WLOG5(fprintf(pLogFile5, "__XmlReader_RemoveVoidKeyItems\n");)

    pRow = pKeyboardInfo->sKeyboard.pRows;
    for (nRowCount = pKeyboardInfo->sKeyboard.nRowCount; nRowCount; --nRowCount, ++pRow) {

        ET9UINT nKeyCount;
        ET9UINT nVoidCount;
        ET9KdbXmlKey *pKey;

        WLOG5(fprintf(pLogFile5, "  Row %2u, nKeyCount %2u\n", (int)(pRow - pKeyboardInfo->sKeyboard.pRows), pRow->nKeyCount);)

        nVoidCount = 0;
        pKey = pRow->pKeys;
        for (nKeyCount = pRow->nKeyCount; nKeyCount; ) {

            WLOG5(fprintf(pLogFile5, "    Key %2u\n", (int)(pKey - pRow->pKeys));)

            if (nVoidCount) {

                WLOG5(fprintf(pLogFile5, "      move %2u <- %2u\n", (int)(pKey - pRow->pKeys), (int)(pKey - pRow->pKeys + nVoidCount));)

                *pKey = *(pKey + nVoidCount);
            }

            if (pKey->eKeyType == ET9_xml_keyType_void) {

                WLOG5(fprintf(pLogFile5, "      VOID\n");)

                ++nVoidCount;
                --nKeyCount;
                --pRow->nKeyCount;
            }
            else {
                ++pKey;
                --nKeyCount;
            }
        }
    }

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                               
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __XmlReader_CalculateLayout(ET9KdbXmlKeyboardInfo * const pKeyboardInfo)
{
    ET9STATUS eStatus;
    ET9UINT nCount;
    ET9KdbXmlRow *pRow;

    WLOG5(fprintf(pLogFile5, "__XmlReader_CalculateLayout\n");)

    if (!pKeyboardInfo->sKeyboard.nRowCount) {
        return ET9STATUS_KDB_HAS_TOO_FEW_ROWS;
    }

    {
        ET9UINT nCountRowKeys = 0;
        ET9UINT nCountRowArea = 0;
        ET9UINT nCountRowVoid = 0;

        pRow = pKeyboardInfo->sKeyboard.pRows;
        for (nCount = pKeyboardInfo->sKeyboard.nRowCount; nCount; --nCount, ++pRow) {

            switch (pRow->eRowType)
            {
                case ET9_xml_rowType_keys:
                    ++nCountRowKeys;
                    break;
                case ET9_xml_rowType_area:
                    ++nCountRowArea;
                    break;
                case ET9_xml_rowType_void:
                    ++nCountRowVoid;
                    break;
                default:
                    ET9Assert(0);
                    break;
           }

            if (!pRow->nKeyCount && (pRow->eRowType == ET9_xml_rowType_keys || pRow->eRowType == ET9_xml_rowType_area)) {
                return ET9STATUS_KDB_ROW_HAS_TOO_FEW_KEYS;
            }
        }

        if (nCountRowArea && (nCountRowKeys || nCountRowVoid)) {
            return ET9STATUS_KDB_UNEXPECTED_CONTENT;
        }

        pKeyboardInfo->bAreaBased = nCountRowArea ? 1 : 0;
    }

    eStatus = __XmlReader_BasicGaps(pKeyboardInfo);

    if (eStatus) {
        return eStatus;
    }

    eStatus = __XmlReader_MergeGaps(pKeyboardInfo);

    if (eStatus) {
        return eStatus;
    }

    eStatus = __XmlReader_BasicSize(pKeyboardInfo);

    if (eStatus) {
        return eStatus;
    }

    eStatus = __XmlReader_ExpandKeys(pKeyboardInfo);

    if (eStatus) {
        return eStatus;
    }

    eStatus = __XmlReader_ExpandKeyVoids(pKeyboardInfo);

    if (eStatus) {
        return eStatus;
    }

    eStatus = __XmlReader_ExpandRowVoids(pKeyboardInfo);

    if (eStatus) {
        return eStatus;
    }

    eStatus = __XmlReader_ExpandRows(pKeyboardInfo);

    if (eStatus) {
        return eStatus;
    }

    eStatus = __XmlReader_PositionKeys(pKeyboardInfo);

    if (eStatus) {
        return eStatus;
    }

    eStatus = __XmlReader_RemoveVoidRowItems(pKeyboardInfo);

    if (eStatus) {
        return eStatus;
    }

    eStatus = __XmlReader_RemoveVoidKeyItems(pKeyboardInfo);

    if (eStatus) {
        return eStatus;
    }

    /* done */

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                                             
 *                                                                                             
 *
 *                                                                    
 */

static ET9STATUS ET9LOCALCALL __XmlReader_TransferLayout(ET9KdbXmlReaderInfo  * const pReaderInfo,
                                                         ET9UINT              * const pnErrorLine)
{
    ET9KdbXmlKeyboardInfo * const pKeyboardInfo = &pReaderInfo->pKDBInfo->Private.wm.xmlReader.sKeyboardInfo;

    ET9STATUS eStatus;

    WLOG5(fprintf(pLogFile5, "__XmlReader_TransferLayout\n");)

    {
        ET9KdbXmlKeyboard * const pKeyboard = &pKeyboardInfo->sKeyboard;

        ET9UINT nPass;

        /* properties */

        eStatus = ET9KDB_Load_SetProperties(pReaderInfo->pKDBInfo,
                                            pKeyboard->bMajorVersion,
                                            pKeyboard->bMinorVersion,
                                            pKeyboard->bPrimaryID,
                                            pKeyboard->bSecondaryID,
                                            pKeyboard->wLayoutWidth,
                                            pKeyboard->wLayoutHeight,
                                            pKeyboard->wConditionValueMax + 1);

        if (eStatus) {
            return eStatus;
        }

        /* first non function keys, then function keys */

        for (nPass = 0; nPass < 2; ++nPass) {

            ET9UINT nRowCount;
            ET9KdbXmlRow const *pRow;

            /* loop rows */

            pRow = pKeyboard->pRows;
            for (nRowCount = pKeyboard->nRowCount; nRowCount; --nRowCount, ++pRow) {

                ET9UINT nKeyCount;
                ET9KdbXmlKey const *pKey;

                /* loop keys */

                pKey = pRow->pKeys;
                for (nKeyCount = pRow->nKeyCount; nKeyCount; --nKeyCount, ++pKey) {

                    /* filter */

                    if ((!nPass && pKey->eKeyType == ET9_xml_keyType_function) ||
                        ( nPass && pKey->eKeyType != ET9_xml_keyType_function)) {
                        continue;
                    }

                    /* add key */

                    eStatus = ET9KDB_Load_AddKey(pReaderInfo->pKDBInfo,
                                                 ET9_KDB_LOAD_UNDEF_VALUE,  /* key index */
                                                 __XmlReader_XmlKeyTypeToLKT(pKey->eKeyType),
                                                 (ET9U16)pKey->nULX,
                                                 (ET9U16)pKey->nULY,
                                                 (ET9U16)(pKey->nULX + pKey->nWidth - 1),
                                                 (ET9U16)(pKey->nULY + pKey->nHeight - 1),
                                                 pKey->nKeyCharCount,
                                                 pKey->psKeyChars);

                    if (eStatus) {
                        if (pnErrorLine) {
                            *pnErrorLine = pKey->nSourceLine;
                        }
                        return eStatus;
                    }

                    if (pKey->nKeyShiftedCharCount) {

                        eStatus = ET9KDB_Load_AttachShiftedChars(pReaderInfo->pKDBInfo,
                                                                 pKey->nKeyShiftedCharCount,
                                                                 pKey->psKeyShiftedChars);

                        if (eStatus) {
                            if (pnErrorLine) {
                                *pnErrorLine = pKey->nSourceLine;
                            }
                            return eStatus;
                        }
                    }

                    if (pKey->nKeyMultitapCharCount || pKey->nKeyMultitapShiftedCharCount) {

                        eStatus = ET9KDB_Load_AttachMultitapInfo(pReaderInfo->pKDBInfo,
                                                                 pKey->nKeyMultitapCharCount,
                                                                 pKey->psKeyMultitapChars,
                                                                 pKey->nKeyMultitapShiftedCharCount,
                                                                 pKey->psKeyMultitapShiftedChars);

                        if (eStatus) {
                            if (pnErrorLine) {
                                *pnErrorLine = pKey->nSourceLine;
                            }
                            return eStatus;
                        }
                    }
                }
            }
        }
    }

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                                             
 *                                              
 *                                                
 *
 *                                                                    
 */

ET9INLINE static void ET9LOCALCALL __XmlReader_AdjustPointer(void                        ** const ppPointer,
                                                             ET9KdbXmlKeyboardInfo  const * const pDst,
                                                             ET9KdbXmlKeyboardInfo  const * const pSrc)
{
    ET9U8 *pbPointer = (ET9U8*)*ppPointer;
    ET9U8 const * const pbDst = (ET9U8*)pDst;
    ET9U8 const * const pbSrc = (ET9U8*)pSrc;

    if (pbPointer < pbSrc || pbPointer >= pbSrc + sizeof(ET9KdbXmlKeyboardInfo)) {
        return;
    }

    pbPointer += pbDst - pbSrc;

    *ppPointer = pbPointer;

    ET9Assert(pbPointer >= pbDst && pbPointer < pbDst + sizeof(ET9KdbXmlKeyboardInfo));
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *     
 *
 *                                              
 *                                                
 *
 *             
 */

static void ET9LOCALCALL __XmlReader_CopyKeyboardInfo(ET9KdbXmlKeyboardInfo        * const pDst,
                                                      ET9KdbXmlKeyboardInfo  const * const pSrc)
{
    *pDst = *pSrc;

    /* adjust pointers */

    {
        ET9KdbXmlKeyboard * const pKeyboard = &pDst->sKeyboard;

        ET9UINT nRowCount;
        ET9KdbXmlRow *pRow;

        /* loop rows */

        __XmlReader_AdjustPointer((void**)&pKeyboard->pRows, pDst, pSrc);

        pRow = pKeyboard->pRows;
        for (nRowCount = pKeyboard->nRowCount; nRowCount; --nRowCount, ++pRow) {

            ET9UINT nKeyCount;
            ET9KdbXmlKey *pKey;

            /* loop keys */

            __XmlReader_AdjustPointer((void**)&pRow->pKeys, pDst, pSrc);

            pKey = pRow->pKeys;
            for (nKeyCount = pRow->nKeyCount; nKeyCount; --nKeyCount, ++pKey) {

                __XmlReader_AdjustPointer((void**)&pKey->psIconChars, pDst, pSrc);
                __XmlReader_AdjustPointer((void**)&pKey->psLabelChars, pDst, pSrc);
                __XmlReader_AdjustPointer((void**)&pKey->psLabelShiftedChars, pDst, pSrc);
                __XmlReader_AdjustPointer((void**)&pKey->psKeyChars, pDst, pSrc);
                __XmlReader_AdjustPointer((void**)&pKey->psKeyShiftedChars, pDst, pSrc);
                __XmlReader_AdjustPointer((void**)&pKey->psKeyPopupChars, pDst, pSrc);
                __XmlReader_AdjustPointer((void**)&pKey->psKeyPopupShiftedChars, pDst, pSrc);
                __XmlReader_AdjustPointer((void**)&pKey->psKeyMultitapChars, pDst, pSrc);
                __XmlReader_AdjustPointer((void**)&pKey->psKeyMultitapShiftedChars, pDst, pSrc);
            }
        }
    }
}

/*---------------------------------------------------------------------------*/
/**
 * @brief Load a dynamically using the xml format for KDB descriptions.
 *
 * @param[in]     pKDBInfo              Pointer to keyboard information structure.
 * @param[in]     wLayoutWidth          Layout width.
 * @param[in]     wLayoutHeight         Layout height.
 * @param[in]     nConditionValue       Row/Area inclusion condition (if used).
 * @param[in]     pbContent             Pointer to the content.
 * @param[in]     dwContentLen          Content length.
 * @param[out]    pKeyboardLayout       Pointer to store the actual keyboard layout (optional).
 * @param[out]    pnUnknownAttributes   Number of unknown attributes found (optional).
 * @param[out]    pnErrorLine           If an error occurs, this is an indication to where it happened (optional).
 *
 * @return ET9STATUS_NONE on success, otherwise return ET9 error code.
 */

ET9STATUS ET9FARCALL ET9KDB_Load_XmlKDB(ET9KDBInfo             * const pKDBInfo,
                                        const ET9U16                   wLayoutWidth,
                                        const ET9U16                   wLayoutHeight,
                                        const ET9UINT                  nConditionValue,
                                        ET9U8            const * const pbContent,
                                        const ET9U32                   dwContentLen,
                                        ET9KdbXmlKeyboardInfo  * const pKeyboardLayout,
                                        ET9UINT                * const pnUnknownAttributes,
                                        ET9UINT                * const pnErrorLine)
{
    ET9STATUS eStatus;

    WLOG5(fprintf(pLogFile5, "ET9KDB_Load_XmlKDB, pKDBInfo = %p, nConditionValue %u\n", pKDBInfo, nConditionValue);)

    eStatus = __ET9KDB_LoadValidityCheck(pKDBInfo);

    if (eStatus) {
        return eStatus;
    }

    if (!pbContent ||
        !dwContentLen) {
        return ET9STATUS_BAD_PARAM;
    }

    ++pKDBInfo->Private.nWmUseID;

    if (pKeyboardLayout) {
        pKeyboardLayout->sKeyboard.nRowCount = 0;
    }

    if ((dwContentLen > 1 && (pbContent[0] == 0xFF && pbContent[1] == 0xFE)) || (pbContent[0] == 0xFE && pbContent[1] == 0xFF)) {
        return ET9STATUS_KDB_WRONG_CONTENT_ENCODING;
    }

    if (pnErrorLine) {
        *pnErrorLine = 0;
    }

    if (pnUnknownAttributes) {
        *pnUnknownAttributes = 0;
    }

    {
        ET9KdbXmlReaderInfo sReaderInfo;

        _ET9ClearMem((ET9U8*)&sReaderInfo, sizeof(sReaderInfo));
        _ET9ClearMem((ET9U8*)&pKDBInfo->Private.wm.xmlReader.sKeyboardInfo, sizeof(pKDBInfo->Private.wm.xmlReader.sKeyboardInfo));

        sReaderInfo.pKDBInfo = pKDBInfo;
        sReaderInfo.nCurrLine = 1;
        sReaderInfo.wLayoutWidth = wLayoutWidth;
        sReaderInfo.wLayoutHeight = wLayoutHeight;
        sReaderInfo.nConditionValue = nConditionValue;
        sReaderInfo.pbEndChar = &pbContent[dwContentLen];
        sReaderInfo.pbCurrChar = pbContent;
        sReaderInfo.pbContent = pbContent;
        sReaderInfo.dwContentLen = dwContentLen;
        sReaderInfo.eCurrToken = ET9_xml_token_Last;

        if (!__XmlReader_GetNextToken(&sReaderInfo)) {
            return ET9STATUS_KDB_SYNTAX_ERROR;
        }

        eStatus = __XmlReader_ReadKeyboard(&sReaderInfo);

        if (eStatus) {

            if (pnErrorLine) {
                *pnErrorLine = sReaderInfo.nCurrLine;
            }

            return eStatus;
        }

        if (pnUnknownAttributes) {
            *pnUnknownAttributes = sReaderInfo.nUnknownAttributes;
        }

        eStatus = __XmlReader_CalculateLayout(&pKDBInfo->Private.wm.xmlReader.sKeyboardInfo);

        if (eStatus) {
            return eStatus;
        }

        if (pKeyboardLayout) {
            __XmlReader_CopyKeyboardInfo(pKeyboardLayout, &pKDBInfo->Private.wm.xmlReader.sKeyboardInfo);
        }

        eStatus = __XmlReader_TransferLayout(&sReaderInfo, pnErrorLine);

        if (eStatus) {
            return eStatus;
        }

        /* safety - to assure the info isn't used after this */

        _ET9ClearMem((ET9U8*)&pKDBInfo->Private.wm.xmlReader.sKeyboardInfo, sizeof(pKDBInfo->Private.wm.xmlReader.sKeyboardInfo));
    }

    return ET9STATUS_NONE;
}

/* ************************************************************************************************************** */
/* ************************************************************************************************************** */
/* ************************************************************************************************************** */

#ifdef ET9_LIMITED_PRIVATE_ACCESS

ET9STATUS ET9FARCALL _PRIVATE_ET9KDB_FindSymbol(ET9KDBInfo          * const pKDBInfo,
                                                const ET9SYMB               sSymbol,
                                                const ET9U32                dwKdbNum,
                                                const ET9U16                wPageNum,
                                                ET9U8               * const pbyRegionalKey,
                                                ET9U16              * const pwKeyIndex,
                                                ET9Region           * const pKeyRegion,
                                                const ET9BOOL               bInitialSymCheck)
{
    return _ET9KDB_FindSymbol(pKDBInfo, sSymbol, dwKdbNum, wPageNum, pbyRegionalKey, pwKeyIndex, pKeyRegion, bInitialSymCheck);
}

#endif /* ET9_LIMITED_PRIVATE_ACCESS */

/* ************************************************************************************************************** */
/* ************************************************************************************************************** */
/* ************************************************************************************************************** */

#ifdef ET9_KDB_TRACE_MODULE
#include "et9kdbtrace.c"
#endif /* ET9_KDB_TRACE_MODULE */


#endif  /* ET9_KDB_MODULE */

/*! @} */
/* ----------------------------------< eof >--------------------------------- */
