/*++

Copyright (c) 1991-2000,  Microsoft Corporation  All rights reserved.

Module Name:

    map.c

Abstract:

    This file contains functions that deal with map tables.

    APIs found in this file:
      FoldStringW
      LCMapStringW

Revision History:

    05-31-91    JulieB    Created.

--*/



//
//  Include Files.
//

#include "nls.h"
#include "jamo.h"




//
//  Constant Declarations.
//

//
//  Invalid weight value.
//
#define MAP_INVALID_UW       0xffff

//
//  Number of bytes in each weight.
//
//  Note: Total number of bytes is limited by MAX_WEIGHTS definition.
//        The padding is needed if SW is not on a WORD boundary.
//
#define NUM_BYTES_UW         8
#define NUM_BYTES_DW         1
#define NUM_BYTES_CW         1
#define NUM_BYTES_XW         4
#define NUM_BYTES_PADDING    0
#define NUM_BYTES_SW         4

//
//  Flags to drop the 3rd weight (CW).
//
#define NORM_DROP_CW         (NORM_IGNORECASE | NORM_IGNOREWIDTH)

//
//  XW Values for FE Special Weights.
//
BYTE pXWDrop[] =                  // values to drop from XW
{
    0xc6,                         // weight 4
    0x03,                         // weight 5
    0xe4,                         // weight 6
    0xc5                          // weight 7
};
BYTE pXWSeparator[] =             // separator values for XW
{
    0xff,                         // weight 4
    0x02,                         // weight 5
    0xff,                         // weight 6
    0xff                          // weight 7
};




//
//  Forward Declarations.
//

int
FoldCZone(
    LPCWSTR pSrc,
    int cchSrc,
    LPWSTR pDest,
    int cchDest);

int
FoldDigits(
    LPCWSTR pSrc,
    int cchSrc,
    LPWSTR pDest,
    int cchDest);

int
FoldCZone_Digits(
    LPCWSTR pSrc,
    int cchSrc,
    LPWSTR pDest,
    int cchDest);

int FoldLigatures(
    LPCWSTR pSrc,
    int cchSrc,
    LPWSTR pDest,
    int cchDest);

int
FoldPreComposed(
    LPCWSTR pSrc,
    int cchSrc,
    LPWSTR pDest,
    int cchDest);

int
FoldComposite(
    LPCWSTR pSrc,
    int cchSrc,
    LPWSTR pDest,
    int cchDest);

int
MapCase(
    PLOC_HASH pHashN,
    LPCWSTR pSrc,
    int cchSrc,
    LPWSTR pDest,
    int cchDest,
    PCASE pCaseTbl);

int
MapSortKey(
    PLOC_HASH pHashN,
    DWORD dwFlags,
    LPCWSTR pSrc,
    int cchSrc,
    LPBYTE pDest,
    int cchDest,
    BOOL fModify);

int
MapNormalization(
    PLOC_HASH pHashN,
    DWORD dwFlags,
    LPCWSTR pSrc,
    int cchSrc,
    LPWSTR pDest,
    int cchDest);

int
MapKanaWidth(
    PLOC_HASH pHashN,
    DWORD dwFlags,
    LPCWSTR pSrc,
    int cchSrc,
    LPWSTR pDest,
    int cchDest);

int
MapHalfKana(
    LPCWSTR pSrc,
    int cchSrc,
    LPWSTR pDest,
    int cchDest,
    PKANA pKana,
    PCASE pCase);

int
MapFullKana(
    LPCWSTR pSrc,
    int cchSrc,
    LPWSTR pDest,
    int cchDest,
    PKANA pKana,
    PCASE pCase);

int
MapTraditionalSimplified(
    PLOC_HASH pHashN,
    DWORD dwFlags,
    LPCWSTR pSrc,
    int cchSrc,
    LPWSTR pDest,
    int cchDest,
    PCHINESE pChinese);





//-------------------------------------------------------------------------//
//                             API ROUTINES                                //
//-------------------------------------------------------------------------//


////////////////////////////////////////////////////////////////////////////
//
//  FoldStringW
//
//  Maps one wide character string to another performing the specified
//  translation.  This mapping routine only takes flags that are locale
//  independent.
//
//  05-31-91    JulieB    Created.
////////////////////////////////////////////////////////////////////////////

int WINAPI FoldStringW(
    DWORD dwMapFlags,
    LPCWSTR lpSrcStr,
    int cchSrc,
    LPWSTR lpDestStr,
    int cchDest)
{
    int Count = 0;                // word count


    //
    //  Invalid Parameter Check:
    //     - length of src string is 0
    //     - either buffer size is negative (except cchSrc == -1)
    //     - src string is NULL
    //     - length of dest string is NOT zero AND dest string is NULL
    //     - same buffer - src = destination
    //
    //     - flags are checked in switch statement below
    //
    if ((cchSrc == 0) || (cchDest < 0) ||
        (lpSrcStr == NULL) ||
        ((cchDest != 0) && (lpDestStr == NULL)) ||
        (lpSrcStr == lpDestStr))
    {
        SetLastError(ERROR_INVALID_PARAMETER);
        return (0);
    }

    //
    //  If cchSrc is -1, then the source string is null terminated and we
    //  need to get the length of the source string.  Add one to the
    //  length to include the null termination.
    //  (This will always be at least 1.)
    //
    if (cchSrc <= -1)
    {
        cchSrc = NlsStrLenW(lpSrcStr) + 1;
    }

    //
    //  Map the string based on the given flags.
    //
    switch (dwMapFlags)
    {
        case ( MAP_FOLDCZONE ) :
        {
            //
            //  Map the string to fold the Compatibility Zone.
            //
            Count = FoldCZone( lpSrcStr,
                               cchSrc,
                               lpDestStr,
                               cchDest );
            break;
        }
        case ( MAP_FOLDDIGITS ) :
        {
            //
            //  Map the string to fold the Ascii Digits.
            //
            Count = FoldDigits( lpSrcStr,
                                cchSrc,
                                lpDestStr,
                                cchDest );
            break;
        }
        case ( MAP_EXPAND_LIGATURES ) :
        {
            //
            //  Map the string to expand all Ligatures.
            //
            Count = FoldLigatures( lpSrcStr,
                                   cchSrc,
                                   lpDestStr,
                                   cchDest );
            break;
        }
        case ( MAP_PRECOMPOSED ) :
        {
            //
            //  Map the string to compress all composite forms of
            //  characters to their precomposed form.
            //
            Count = FoldPreComposed( lpSrcStr,
                                     cchSrc,
                                     lpDestStr,
                                     cchDest );
            break;
        }
        case ( MAP_COMPOSITE ) :
        {
            //
            //  Map the string to expand out all precomposed characters
            //  to their composite form.
            //
            Count = FoldComposite( lpSrcStr,
                                   cchSrc,
                                   lpDestStr,
                                   cchDest );
            break;
        }
        case ( MAP_FOLDCZONE | MAP_FOLDDIGITS ) :
        {
            //
            //  Map the string to fold the Compatibility Zone and fold the
            //  Ascii Digits.
            //
            Count = FoldCZone_Digits( lpSrcStr,
                                      cchSrc,
                                      lpDestStr,
                                      cchDest );
            break;
        }
        case ( MAP_EXPAND_LIGATURES | MAP_FOLDCZONE ) :
        {
            //
            //  Map the string to expand the ligatures and fold the
            //  Compatibility Zone.
            //
            Count = FoldLigatures( lpSrcStr,
                                   cchSrc,
                                   lpDestStr,
                                   cchDest );
            Count = FoldCZone( lpDestStr,
                               Count,
                               lpDestStr,
                               cchDest );
            break;
        }
        case ( MAP_EXPAND_LIGATURES | MAP_FOLDDIGITS ) :
        {
            //
            //  Map the string to expand the ligatures and fold the
            //  Ascii Digits.
            //
            Count = FoldLigatures( lpSrcStr,
                                   cchSrc,
                                   lpDestStr,
                                   cchDest );
            Count = FoldDigits( lpDestStr,
                                Count,
                                lpDestStr,
                                cchDest );
            break;
        }
        case ( MAP_EXPAND_LIGATURES | MAP_FOLDCZONE | MAP_FOLDDIGITS ) :
        {
            //
            //  Map the string to expand the ligatures, fold the
            //  Compatibility Zone and fold the Ascii Digits.
            //
            Count = FoldLigatures( lpSrcStr,
                                   cchSrc,
                                   lpDestStr,
                                   cchDest );
            Count = FoldCZone_Digits( lpDestStr,
                                      Count,
                                      lpDestStr,
                                      cchDest );
            break;
        }
        case ( MAP_PRECOMPOSED | MAP_FOLDCZONE ) :
        {
            //
            //  Map the string to convert to precomposed forms and to
            //  fold the Compatibility Zone.
            //
            Count = FoldPreComposed( lpSrcStr,
                                     cchSrc,
                                     lpDestStr,
                                     cchDest );
            Count = FoldCZone( lpDestStr,
                               Count,
                               lpDestStr,
                               cchDest );
            break;
        }
        case ( MAP_PRECOMPOSED | MAP_FOLDDIGITS ) :
        {
            //
            //  Map the string to convert to precomposed forms and to
            //  fold the Ascii Digits.
            //
            Count = FoldPreComposed( lpSrcStr,
                                     cchSrc,
                                     lpDestStr,
                                     cchDest );
            Count = FoldDigits( lpDestStr,
                                Count,
                                lpDestStr,
                                cchDest );
            break;
        }
        case ( MAP_PRECOMPOSED | MAP_FOLDCZONE | MAP_FOLDDIGITS ) :
        {
            //
            //  Map the string to convert to precomposed forms,
            //  fold the Compatibility Zone, and fold the Ascii Digits.
            //
            Count = FoldPreComposed( lpSrcStr,
                                     cchSrc,
                                     lpDestStr,
                                     cchDest );
            Count = FoldCZone_Digits( lpDestStr,
                                      Count,
                                      lpDestStr,
                                      cchDest );
            break;
        }
        case ( MAP_COMPOSITE | MAP_FOLDCZONE ) :
        {
            //
            //  Map the string to convert to composite forms and to
            //  fold the Compatibility Zone.
            //
            Count = FoldComposite( lpSrcStr,
                                   cchSrc,
                                   lpDestStr,
                                   cchDest );
            Count = FoldCZone( lpDestStr,
                               Count,
                               lpDestStr,
                               cchDest );
            break;
        }
        case ( MAP_COMPOSITE | MAP_FOLDDIGITS ) :
        {
            //
            //  Map the string to convert to composite forms and to
            //  fold the Ascii Digits.
            //
            Count = FoldComposite( lpSrcStr,
                                   cchSrc,
                                   lpDestStr,
                                   cchDest );
            Count = FoldDigits( lpDestStr,
                                Count,
                                lpDestStr,
                                cchDest );
            break;
        }
        case ( MAP_COMPOSITE | MAP_FOLDCZONE | MAP_FOLDDIGITS ) :
        {
            //
            //  Map the string to convert to composite forms,
            //  fold the Compatibility Zone, and fold the Ascii Digits.
            //
            Count = FoldComposite( lpSrcStr,
                                   cchSrc,
                                   lpDestStr,
                                   cchDest );
            Count = FoldCZone_Digits( lpDestStr,
                                      Count,
                                      lpDestStr,
                                      cchDest );
            break;
        }
        default :
        {
            SetLastError(ERROR_INVALID_FLAGS);
            return (0);
        }
    }

    //
    //  Return the number of characters written to the buffer.
    //  Or, if cchDest == 0, then return the number of characters
    //  that would have been written to the buffer.
    //
    return (Count);
}


////////////////////////////////////////////////////////////////////////////
//
//  LCMapStringW
//
//  Maps one wide character string to another performing the specified
//  translation.  This mapping routine only takes flags that are locale
//  dependent.
//
//  05-31-91    JulieB    Created.
//  07-26-93    JulieB    Added new flags for NT-J.
////////////////////////////////////////////////////////////////////////////

int WINAPI LCMapStringW(
    LCID Locale,
    DWORD dwMapFlags,
    LPCWSTR lpSrcStr,
    int cchSrc,
    LPWSTR lpDestStr,
    int cchDest)
{
    PLOC_HASH pHashN;             // ptr to LOC hash node
    int Count = 0;                // word count or byte count
    int ctr;                      // loop counter


    //
    //  Invalid Parameter Check:
    //     - validate LCID
    //     - length of src string is 0
    //     - destination buffer size is negative
    //     - src string is NULL
    //     - length of dest string is NOT zero AND dest string is NULL
    //     - same buffer - src = destination
    //              if not UPPER or LOWER or
    //              UPPER or LOWER used with Japanese flags
    //
    VALIDATE_LANGUAGE(Locale, pHashN, dwMapFlags & LCMAP_LINGUISTIC_CASING, TRUE);
    if ( (pHashN == NULL) ||
         (cchSrc == 0) || (cchDest < 0) || (lpSrcStr == NULL) ||
         ((cchDest != 0) && (lpDestStr == NULL)) ||
         ((lpSrcStr == lpDestStr) &&
          ((!(dwMapFlags & (LCMAP_UPPERCASE | LCMAP_LOWERCASE))) ||
           (dwMapFlags & (LCMAP_HIRAGANA | LCMAP_KATAKANA |
                          LCMAP_HALFWIDTH | LCMAP_FULLWIDTH)))) )
    {
        SetLastError(ERROR_INVALID_PARAMETER);
        return (0);
    }

    //
    //  Invalid Flags Check:
    //     - flags other than valid ones or 0
    //     - (any NORM_ flag) AND (any LCMAP_ flag except byterev and sortkey)
    //     - (NORM_ flags for sortkey) AND (NOT LCMAP_SORTKEY)
    //     - more than one of lower, upper, sortkey
    //     - more than one of hiragana, katakana, sortkey
    //     - more than one of half width, full width, sortkey
    //     - more than one of traditional, simplified, sortkey
    //     - (LINGUISTIC flag) AND (NOT LCMAP_UPPER OR LCMAP_LOWER)
    //
    dwMapFlags &= (~LOCALE_USE_CP_ACP);
    if ( (dwMapFlags & LCMS_INVALID_FLAG) || (dwMapFlags == 0) ||
         ((dwMapFlags & (NORM_ALL | SORT_STRINGSORT)) &&
          (dwMapFlags & LCMAP_NO_NORM)) ||
         ((dwMapFlags & NORM_SORTKEY_ONLY) &&
          (!(dwMapFlags & LCMAP_SORTKEY))) ||
         (MORE_THAN_ONE(dwMapFlags, LCMS1_SINGLE_FLAG)) ||
         (MORE_THAN_ONE(dwMapFlags, LCMS2_SINGLE_FLAG)) ||
         (MORE_THAN_ONE(dwMapFlags, LCMS3_SINGLE_FLAG)) ||
         (MORE_THAN_ONE(dwMapFlags, LCMS4_SINGLE_FLAG)) ||
         ((dwMapFlags & LCMAP_LINGUISTIC_CASING) &&
          (!(dwMapFlags & (LCMAP_UPPERCASE | LCMAP_LOWERCASE)))) )
    {
        SetLastError(ERROR_INVALID_FLAGS);
        return (0);
    }

    //
    //  If cchSrc is -1, then the source string is null terminated and we
    //  need to get the length of the source string.  Add one to the
    //  length to include the null termination.
    //  (This will always be at least 1.)
    //
    if (cchSrc <= -1)
    {
        cchSrc = NlsStrLenW(lpSrcStr) + 1;
    }

    //
    //  Map the string based on the given flags.
    //
    if (dwMapFlags & LCMAP_SORTKEY)
    {
        //
        //  Map the string to its sortkey.
        //
        //  NOTE:  This returns the number of BYTES, instead of the
        //         number of wide characters (words).
        //
        Count = MapSortKey( pHashN,
                            dwMapFlags,
                            lpSrcStr,
                            cchSrc,
                            (LPBYTE)lpDestStr,
                            cchDest,
                            IS_KOREAN(Locale) );
    }
    else
    {
        switch (dwMapFlags & ~(LCMAP_BYTEREV | LCMAP_LINGUISTIC_CASING))
        {
            case ( LCMAP_LOWERCASE ) :
            {
                //
                //  Map the string to Lower Case.
                //
                Count = MapCase( pHashN,
                                 lpSrcStr,
                                 cchSrc,
                                 lpDestStr,
                                 cchDest,
                                 (dwMapFlags & LCMAP_LINGUISTIC_CASING)
                                     ? pHashN->pLowerLinguist
                                     : pHashN->pLowerCase );
                break;
            }
            case ( LCMAP_UPPERCASE ) :
            {
                //
                //  Map the string to Upper Case.
                //
                Count = MapCase( pHashN,
                                 lpSrcStr,
                                 cchSrc,
                                 lpDestStr,
                                 cchDest,
                                 (dwMapFlags & LCMAP_LINGUISTIC_CASING)
                                     ? pHashN->pUpperLinguist
                                     : pHashN->pUpperCase );
                break;
            }
            case ( NORM_IGNORENONSPACE )                      :
            case ( NORM_IGNORESYMBOLS )                       :
            case ( NORM_IGNORENONSPACE | NORM_IGNORESYMBOLS ) :
            {
                //
                //  Map the string to strip out nonspace marks and/or symbols.
                //
                Count = MapNormalization( pHashN,
                                          dwMapFlags & ~LCMAP_BYTEREV,
                                          lpSrcStr,
                                          cchSrc,
                                          lpDestStr,
                                          cchDest );
                break;
            }
            case ( LCMAP_TRADITIONAL_CHINESE ) :
            case ( LCMAP_TRADITIONAL_CHINESE | LCMAP_LOWERCASE ) :
            case ( LCMAP_TRADITIONAL_CHINESE | LCMAP_UPPERCASE) :
            {
                //
                //  Map the string to Traditional Chinese.
                //
                Count = MapTraditionalSimplified( pHashN,
                                                  dwMapFlags & ~LCMAP_BYTEREV,
                                                  lpSrcStr,
                                                  cchSrc,
                                                  lpDestStr,
                                                  cchDest,
                                                  pTblPtrs->pTraditional );
                break;
            }
            case ( LCMAP_SIMPLIFIED_CHINESE )  :
            case ( LCMAP_SIMPLIFIED_CHINESE | LCMAP_LOWERCASE )  :
            case ( LCMAP_SIMPLIFIED_CHINESE | LCMAP_UPPERCASE )  :
            {
                //
                //  Map the string to Simplified Chinese.
                //
                Count = MapTraditionalSimplified( pHashN,
                                                  dwMapFlags & ~LCMAP_BYTEREV,
                                                  lpSrcStr,
                                                  cchSrc,
                                                  lpDestStr,
                                                  cchDest,
                                                  pTblPtrs->pSimplified );
                break;
            }
            default :
            {
                //
                //  Make sure the Chinese flags are not used with the
                //  Japanese flags.
                //
                if (dwMapFlags &
                     (LCMAP_TRADITIONAL_CHINESE | LCMAP_SIMPLIFIED_CHINESE))
                {
                    SetLastError(ERROR_INVALID_FLAGS);
                    return (0);
                }

                //
                //  The only flags not yet handled are the variations
                //  containing the Kana and/or Width flags.
                //  This handles all variations for:
                //      LCMAP_HIRAGANA
                //      LCMAP_KATAKANA
                //      LCMAP_HALFWIDTH
                //      LCMAP_FULLWIDTH
                //
                //      Allow LCMAP_LOWERCASE and LCMAP_UPPERCASE
                //      in combination with the kana and width flags.
                //
                Count = MapKanaWidth( pHashN,
                                      dwMapFlags & ~LCMAP_BYTEREV,
                                      lpSrcStr,
                                      cchSrc,
                                      lpDestStr,
                                      cchDest );
                break;
            }
        }
    }

    //
    //  Always check LCMAP_BYTEREV last and do it in place.
    //  LCMAP_BYTEREV may be used in combination with any other flag
    //  (except ignore case without sortkey) or by itself.
    //
    if (dwMapFlags & LCMAP_BYTEREV)
    {
        //
        //  Reverse the bytes of each word in the string.
        //
        if (dwMapFlags == LCMAP_BYTEREV)
        {
            //
            //  Byte Reversal flag is used by itself.
            //
            //  Make sure that the size of the destination buffer is
            //  larger than zero.  If it is zero, return the size of
            //  the source string only.  Do NOT touch lpDestStr.
            //
            if (cchDest != 0)
            {
                //
                //  Flag is used by itself.  Reverse the bytes from
                //  the source string and store them in the destination
                //  string.
                //
                if (cchSrc > cchDest)
                {
                    SetLastError(ERROR_INSUFFICIENT_BUFFER);
                    return (0);
                }

                for (ctr = 0; ctr < cchSrc; ctr++)
                {
                    lpDestStr[ctr] = MAKEWORD( HIBYTE(lpSrcStr[ctr]),
                                               LOBYTE(lpSrcStr[ctr]) );
                }
            }

            //
            //  Return the size of the source string.
            //
            Count = cchSrc;
        }
        else
        {
            //
            //  Make sure that the size of the destination buffer is
            //  larger than zero.  If it is zero, return the count and
            //  do NOT touch lpDestStr.
            //
            if (cchDest != 0)
            {
                //
                //  Check for sortkey flag.
                //
                if (dwMapFlags & LCMAP_SORTKEY)
                {
                    //
                    //  Sortkey flag is also set, so 'Count' contains the
                    //  number of BYTES instead of the number of words.
                    //
                    //  Reverse the bytes in place in the destination string.
                    //  No need to check the size of the destination buffer
                    //  here - it's been done elsewhere.
                    //
                    for (ctr = 0; ctr < Count / 2; ctr++)
                    {
                        lpDestStr[ctr] = MAKEWORD( HIBYTE(lpDestStr[ctr]),
                                                   LOBYTE(lpDestStr[ctr]) );
                    }
                }
                else
                {
                    //
                    //  Flag is used in combination with another flag.
                    //  Reverse the bytes in place in the destination string.
                    //  No need to check the size of the destination buffer
                    //  here - it's been done elsewhere.
                    //
                    for (ctr = 0; ctr < Count; ctr++)
                    {
                        lpDestStr[ctr] = MAKEWORD( HIBYTE(lpDestStr[ctr]),
                                                   LOBYTE(lpDestStr[ctr]) );
                    }
                }
            }
        }
    }

    //
    //  Return the number of characters (or number of bytes for sortkey)
    //  written to the buffer.
    //
    return (Count);
}




//-------------------------------------------------------------------------//
//                           INTERNAL ROUTINES                             //
//-------------------------------------------------------------------------//


////////////////////////////////////////////////////////////////////////////
//
//  FoldCZone
//
//  Stores the compatibility zone values for the given string in the
//  destination buffer, and returns the number of wide characters
//  written to the buffer.
//
//  02-01-93    JulieB    Created.
////////////////////////////////////////////////////////////////////////////

int FoldCZone(
    LPCWSTR pSrc,
    int cchSrc,
    LPWSTR pDest,
    int cchDest)
{
    int ctr;                      // loop counter


    //
    //  If the destination value is zero, then just return the
    //  length of the source string.  Do NOT touch pDest.
    //
    if (cchDest == 0)
    {
        return (cchSrc);
    }

    //
    //  If cchSrc is greater than cchDest, then the destination buffer
    //  is too small to hold the new string.  Return an error.
    //
    if (cchSrc > cchDest)
    {
        SetLastError(ERROR_INSUFFICIENT_BUFFER);
        return (0);
    }

    //
    //  Fold the Compatibility Zone and store it in the destination string.
    //
    for (ctr = 0; ctr < cchSrc; ctr++)
    {
        pDest[ctr] = GET_FOLD_CZONE(pTblPtrs->pCZone, pSrc[ctr]);
    }

    //
    //  Return the number of wide characters written.
    //
    return (ctr);
}


////////////////////////////////////////////////////////////////////////////
//
//  FoldDigits
//
//  Stores the ascii digits values for the given string in the
//  destination buffer, and returns the number of wide characters
//  written to the buffer.
//
//  02-01-93    JulieB    Created.
////////////////////////////////////////////////////////////////////////////

int FoldDigits(
    LPCWSTR pSrc,
    int cchSrc,
    LPWSTR pDest,
    int cchDest)
{
    int ctr;                      // loop counter


    //
    //  If the destination value is zero, then just return the
    //  length of the source string.  Do NOT touch pDest.
    //
    if (cchDest == 0)
    {
        return (cchSrc);
    }

    //
    //  If cchSrc is greater than cchDest, then the destination buffer
    //  is too small to hold the new string.  Return an error.
    //
    if (cchSrc > cchDest)
    {
        SetLastError(ERROR_INSUFFICIENT_BUFFER);
        return (0);
    }

    //
    //  Fold the Ascii Digits and store it in the destination string.
    //
    for (ctr = 0; ctr < cchSrc; ctr++)
    {
        pDest[ctr] = GET_ASCII_DIGITS(pTblPtrs->pADigit, pSrc[ctr]);
    }

    //
    //  Return the number of wide characters written.
    //
    return (ctr);
}


////////////////////////////////////////////////////////////////////////////
//
//  FoldCZone_Digits
//
//  Stores the compatibility zone and ascii digits values for the given
//  string in the destination buffer, and returns the number of wide
//  characters written to the buffer.
//
//  02-01-93    JulieB    Created.
////////////////////////////////////////////////////////////////////////////

int FoldCZone_Digits(
    LPCWSTR pSrc,
    int cchSrc,
    LPWSTR pDest,
    int cchDest)
{
    int ctr;                      // loop counter


    //
    //  If the destination value is zero, then just return the
    //  length of the source string.  Do NOT touch pDest.
    //
    if (cchDest == 0)
    {
        return (cchSrc);
    }

    //
    //  If cchSrc is greater than cchDest, then the destination buffer
    //  is too small to hold the new string.  Return an error.
    //
    if (cchSrc > cchDest)
    {
        SetLastError(ERROR_INSUFFICIENT_BUFFER);
        return (0);
    }

    //
    //  Fold the compatibility zone and the ascii digits values and store
    //  it in the destination string.
    //
    for (ctr = 0; ctr < cchSrc; ctr++)
    {
        pDest[ctr] = GET_FOLD_CZONE(pTblPtrs->pCZone, pSrc[ctr]);
        pDest[ctr] = GET_ASCII_DIGITS(pTblPtrs->pADigit, pDest[ctr]);
    }

    //
    //  Return the number of wide characters written.
    //
    return (ctr);
}


////////////////////////////////////////////////////////////////////////////
//
//  FoldLigatures
//
//  Stores the expanded ligature values for the given string in the
//  destination buffer, and returns the number of wide characters
//  written to the buffer.
//
//  10-15-96    JulieB    Created.
////////////////////////////////////////////////////////////////////////////

int FoldLigatures(
    LPCWSTR pSrc,
    int cchSrc,
    LPWSTR pDest,
    int cchDest)
{
    int ctr  = 0;                 // source char counter
    int ctr2 = 0;                 // destination char counter
    DWORD Weight;                 // sort weight - used for expansions


    //
    //  Make sure the default sorting table is available.  If not,
    //  return an error.
    //
    if (pTblPtrs->pDefaultSortkey == NULL)
    {
        KdPrint(("NLSAPI: No Default Sorting Table Loaded.\n"));
        SetLastError(ERROR_FILE_NOT_FOUND);
        return (0);
    }

    //
    //  If the destination value is zero, then just return the
    //  length of the string that would be returned.  Do NOT touch pDest.
    //
    if (cchDest == 0)
    {
        //
        //  Convert the source string to expand all ligatures and calculate
        //  the number of characters that would have been written to a
        //  destination buffer.
        //
        while (ctr < cchSrc)
        {
            Weight = MAKE_SORTKEY_DWORD((pTblPtrs->pDefaultSortkey)[pSrc[ctr]]);
            if (GET_SCRIPT_MEMBER(&Weight) == EXPANSION)
            {
                do
                {
                    ctr2++;
                    Weight = MAKE_SORTKEY_DWORD(
                        (pTblPtrs->pDefaultSortkey)[GET_EXPANSION_2(&Weight)]);
                } while (GET_SCRIPT_MEMBER(&Weight) == EXPANSION);
                ctr2++;
            }
            else
            {
                ctr2++;
            }
            ctr++;
        }
    }
    else
    {
        //
        //  Convert the source string to expand all ligatures and store
        //  the result in the destination buffer.
        //
        while ((ctr < cchSrc) && (ctr2 < cchDest))
        {
            Weight = MAKE_SORTKEY_DWORD((pTblPtrs->pDefaultSortkey)[pSrc[ctr]]);
            if (GET_SCRIPT_MEMBER(&Weight) == EXPANSION)
            {
                do
                {
                    if ((ctr2 + 1) < cchDest)
                    {
                        pDest[ctr2]     = GET_EXPANSION_1(&Weight);
                        pDest[ctr2 + 1] = GET_EXPANSION_2(&Weight);
                        ctr2++;
                    }
                    else
                    {
                        ctr2++;
                        break;
                    }
                    Weight = MAKE_SORTKEY_DWORD(
                                 (pTblPtrs->pDefaultSortkey)[pDest[ctr2]]);
                } while (GET_SCRIPT_MEMBER(&Weight) == EXPANSION);

                if (ctr2 >= cchDest)
                {
                    break;
                }
                ctr2++;
            }
            else
            {
                pDest[ctr2] = pSrc[ctr];
                ctr2++;
            }
            ctr++;
        }
    }

    //
    //  Make sure destination buffer was large enough.
    //
    if (ctr < cchSrc)
    {
        SetLastError(ERROR_INSUFFICIENT_BUFFER);
        return (0);
    }

    //
    //  Return the number of wide characters written.
    //
    return (ctr2);
}


////////////////////////////////////////////////////////////////////////////
//
//  FoldPreComposed
//
//  Stores the precomposed values for the given string in the
//  destination buffer, and returns the number of wide characters
//  written to the buffer.
//
//  02-01-93    JulieB    Created.
////////////////////////////////////////////////////////////////////////////

int FoldPreComposed(
    LPCWSTR pSrc,
    int cchSrc,
    LPWSTR pDest,
    int cchDest)
{
    int ctr  = 0;                 // source char counter
    int ctr2 = 0;                 // destination char counter
    WCHAR wch = 0;                // wchar holder


    //
    //  Make sure the default sorting table is available.  If not,
    //  return an error.
    //
    if (pTblPtrs->pDefaultSortkey == NULL)
    {
        KdPrint(("NLSAPI: No Default Sorting Table Loaded.\n"));
        SetLastError(ERROR_FILE_NOT_FOUND);
        return (0);
    }

    //
    //  If the destination value is zero, then just return the
    //  length of the string that would be returned.  Do NOT touch pDest.
    //
    if (cchDest == 0)
    {
        //
        //  Convert the source string to precomposed and calculate the
        //  number of characters that would have been written to a
        //  destination buffer.
        //
        while (ctr < cchSrc)
        {
            if ((ctr2 != 0) &&
                (IS_NONSPACE_ONLY(pTblPtrs->pDefaultSortkey, pSrc[ctr])))
            {
                //
                //  Composite form.  Write the precomposed form.
                //
                //  If the precomposed character is written to the buffer,
                //  do NOT increment the destination pointer or the
                //  character count (the precomposed character was
                //  written over the previous character).
                //
                if (wch)
                {
                    if ((wch = GetPreComposedChar(pSrc[ctr], wch)) == 0)
                    {
                        //
                        //  No translation for composite form, so just
                        //  increment the destination counter.
                        //
                        ctr2++;
                    }
                }
                else
                {
                    if ((wch = GetPreComposedChar( pSrc[ctr],
                                                   pSrc[ctr - 1] )) == 0)
                    {
                        //
                        //  No translation for composite form, so just
                        //  increment the destination counter.
                        //
                        ctr2++;
                    }
                }
            }
            else
            {
                //
                //  Not part of a composite character, so just
                //  increment the destination counter.
                //
                wch = 0;
                ctr2++;
            }
            ctr++;
        }
    }
    else
    {
        //
        //  Convert the source string to precomposed and store it in the
        //  destination string.
        //
        while ((ctr < cchSrc) && (ctr2 < cchDest))
        {
            if ((ctr2 != 0) &&
                (IS_NONSPACE_ONLY(pTblPtrs->pDefaultSortkey, pSrc[ctr])))
            {
                //
                //  Composite form.  Write the precomposed form.
                //
                //  If the precomposed character is written to the buffer,
                //  do NOT increment the destination pointer or the
                //  character count (the precomposed character was
                //  written over the previous character).
                //
                wch = pDest[ctr2 - 1];
                if ((pDest[ctr2 - 1] =
                         GetPreComposedChar( pSrc[ctr],
                                             pDest[ctr2 - 1] )) == 0)
                {
                    //
                    //  No translation for composite form, so must
                    //  rewrite the base character and write the
                    //  composite character.
                    //
                    pDest[ctr2 - 1] = wch;
                    pDest[ctr2] = pSrc[ctr];
                    ctr2++;
                }
            }
            else
            {
                //
                //  Not part of a composite character, so just write
                //  the character to the destination string.
                //
                pDest[ctr2] = pSrc[ctr];
                ctr2++;
            }
            ctr++;
        }
    }

    //
    //  Make sure destination buffer was large enough.
    //
    if (ctr < cchSrc)
    {
        SetLastError(ERROR_INSUFFICIENT_BUFFER);
        return (0);
    }

    //
    //  Return the number of wide characters written.
    //
    return (ctr2);
}


////////////////////////////////////////////////////////////////////////////
//
//  FoldComposite
//
//  Stores the composite values for the given string in the
//  destination buffer, and returns the number of wide characters
//  written to the buffer.
//
//  02-01-93    JulieB    Created.
////////////////////////////////////////////////////////////////////////////

int FoldComposite(
    LPCWSTR pSrc,
    int cchSrc,
    LPWSTR pDest,
    int cchDest)
{
    int ctr  = 0;                 // source char counter
    int ctr2 = 0;                 // destination char counter
    LPWSTR pEndDest;              // ptr to end of destination string
    WCHAR pTmp[MAX_COMPOSITE];    // tmp buffer for composite chars


    //
    //  If the destination value is zero, then just return the
    //  length of the string that would be returned.  Do NOT touch pDest.
    //
    if (cchDest == 0)
    {
        //
        //  Get the end of the tmp buffer.
        //
        pEndDest = (LPWSTR)pTmp + MAX_COMPOSITE;

        //
        //  Convert the source string to precomposed and calculate the
        //  number of characters that would have been written to a
        //  destination buffer.
        //
        while (ctr < cchSrc)
        {
            //
            //  Write the character to the destination string.
            //
            *pTmp = pSrc[ctr];

            //
            //  See if it needs to be expanded to its composite form.
            //
            //  If no composite form is found, the routine returns 1 for
            //  the base character.  Simply increment by the return value.
            //
            ctr2 += InsertCompositeForm(pTmp, pEndDest);

            //
            //  Increment the source string counter.
            //
            ctr++;
        }
    }
    else
    {
        //
        //  Get the end of the destination string.
        //
        pEndDest = (LPWSTR)pDest + cchDest;

        //
        //  Convert the source string to precomposed and store it in the
        //  destination string.
        //
        while ((ctr < cchSrc) && (ctr2 < cchDest))
        {
            //
            //  Write the character to the destination string.
            //
            pDest[ctr2] = pSrc[ctr];

            //
            //  See if it needs to be expanded to its composite form.
            //
            //  If no composite form is found, the routine returns 1 for
            //  the base character.  Simply increment by the return value.
            //
            ctr2 += InsertCompositeForm(&(pDest[ctr2]), pEndDest);

            //
            //  Increment the source string counter.
            //
            ctr++;
        }
    }

    //
    //  Make sure destination buffer was large enough.
    //
    if (ctr < cchSrc)
    {
        SetLastError(ERROR_INSUFFICIENT_BUFFER);
        return (0);
    }

    //
    //  Return the number of wide characters written.
    //
    return (ctr2);
}


////////////////////////////////////////////////////////////////////////////
//
//  MapCase
//
//  Stores the lower or upper case values for the given string in the
//  destination buffer, and returns the number of wide characters written to
//  the buffer.
//
//  05-31-91    JulieB    Created.
////////////////////////////////////////////////////////////////////////////

int MapCase(
    PLOC_HASH pHashN,
    LPCWSTR pSrc,
    int cchSrc,
    LPWSTR pDest,
    int cchDest,
    PCASE pCaseTbl)
{
    int ctr;                      // loop counter


    //
    //  If the destination value is zero, then just return the
    //  length of the source string.  Do NOT touch pDest.
    //
    if (cchDest == 0)
    {
        return (cchSrc);
    }

    //
    //  If cchSrc is greater than cchDest, then the destination buffer
    //  is too small to hold the lower or upper case string.  Return an
    //  error.
    //
    if (cchSrc > cchDest)
    {
        SetLastError(ERROR_INSUFFICIENT_BUFFER);
        return (0);
    }

    //
    //  Lower or Upper case the source string and store it in the
    //  destination string.
    //
    for (ctr = 0; ctr < cchSrc; ctr++)
    {
        pDest[ctr] = GET_LOWER_UPPER_CASE(pCaseTbl, pSrc[ctr]);
    }

    //
    //  Return the number of wide characters written.
    //
    return (ctr);
}


////////////////////////////////////////////////////////////////////////////
//
//  SPECIAL_CASE_HANDLER
//
//  Handles all of the special cases for each character.  This includes only
//  the valid values less than or equal to MAX_SPECIAL_CASE.
//
//  DEFINED AS A MACRO.
//
//  11-04-92    JulieB    Created.
////////////////////////////////////////////////////////////////////////////

#define EXTRA_WEIGHT_POS(WtNum)        (*(pPosXW + (WtNum * WeightLen)))

#define SPECIAL_CASE_HANDLER( SM,                                           \
                              pWeight,                                      \
                              pSortkey,                                     \
                              pExpand,                                      \
                              Position,                                     \
                              fStringSort,                                  \
                              fIgnoreSymbols,                               \
                              pCur,                                         \
                              pBegin,                                       \
                              fModify )                                     \
{                                                                           \
    PSORTKEY pExpWt;              /* weight of 1 expansion char */          \
    BYTE AW;                      /* alphanumeric weight */                 \
    BYTE XW;                      /* case weight value with extra bits */   \
    DWORD PrevWt;                 /* previous weight */                     \
    BYTE PrevSM;                  /* previous script member */              \
    BYTE PrevAW;                  /* previuos alphanumeric weight */        \
    BYTE PrevCW;                  /* previuos case weight */                \
    LPWSTR pPrev;                 /* ptr to previous char */                \
                                                                            \
                                                                            \
    switch (SM)                                                             \
    {                                                                       \
        case ( UNSORTABLE ) :                                               \
        {                                                                   \
            /*                                                              \
             *  Character is unsortable, so skip it.                        \
             */                                                             \
            break;                                                          \
        }                                                                   \
        case ( NONSPACE_MARK ) :                                            \
        {                                                                   \
            /*                                                              \
             *  Character is a nonspace mark, so only store                 \
             *  the diacritic weight.                                       \
             */                                                             \
            if (pPosDW > pDW)                                               \
            {                                                               \
                (*(pPosDW - 1)) += GET_DIACRITIC(pWeight);                  \
            }                                                               \
            else                                                            \
            {                                                               \
                *pPosDW = GET_DIACRITIC(pWeight);                           \
                pPosDW++;                                                   \
            }                                                               \
                                                                            \
            break;                                                          \
        }                                                                   \
        case ( EXPANSION ) :                                                \
        {                                                                   \
            /*                                                              \
             *  Expansion character - one character has 2                   \
             *  different weights.  Store each weight separately.           \
             */                                                             \
            pExpWt = &(pSortkey[(pExpand[GET_EXPAND_INDEX(pWeight)]).UCP1]); \
            *pPosUW = GET_UNICODE_MOD(pExpWt, fModify);                     \
            *pPosDW = GET_DIACRITIC(pExpWt);                                \
            *pPosCW = GET_CASE(pExpWt) & CaseMask;                          \
            pPosUW++;                                                       \
            pPosDW++;                                                       \
            pPosCW++;                                                       \
                                                                            \
            pExpWt = &(pSortkey[(pExpand[GET_EXPAND_INDEX(pWeight)]).UCP2]); \
            while (GET_SCRIPT_MEMBER(pExpWt) == EXPANSION)                  \
            {                                                               \
                pWeight = pExpWt;                                           \
                pExpWt = &(pSortkey[(pExpand[GET_EXPAND_INDEX(pWeight)]).UCP1]); \
                *pPosUW = GET_UNICODE_MOD(pExpWt, fModify);                 \
                *pPosDW = GET_DIACRITIC(pExpWt);                            \
                *pPosCW = GET_CASE(pExpWt) & CaseMask;                      \
                pPosUW++;                                                   \
                pPosDW++;                                                   \
                pPosCW++;                                                   \
                pExpWt = &(pSortkey[(pExpand[GET_EXPAND_INDEX(pWeight)]).UCP2]); \
            }                                                               \
            *pPosUW = GET_UNICODE_MOD(pExpWt, fModify);                     \
            *pPosDW = GET_DIACRITIC(pExpWt);                                \
            *pPosCW = GET_CASE(pExpWt) & CaseMask;                          \
            pPosUW++;                                                       \
            pPosDW++;                                                       \
            pPosCW++;                                                       \
                                                                            \
            break;                                                          \
        }                                                                   \
        case ( PUNCTUATION ) :                                              \
        {                                                                   \
            if (!fStringSort)                                               \
            {                                                               \
                /*                                                          \
                 *  Word Sort Method.                                       \
                 *                                                          \
                 *  Character is punctuation, so only store the special     \
                 *  weight.                                                 \
                 */                                                         \
                *((LPBYTE)pPosSW)       = HIBYTE(GET_POSITION_SW(Position)); \
                *(((LPBYTE)pPosSW) + 1) = LOBYTE(GET_POSITION_SW(Position)); \
                pPosSW++;                                                   \
                *pPosSW = GET_SPECIAL_WEIGHT(pWeight);                      \
                pPosSW++;                                                   \
                                                                            \
                break;                                                      \
            }                                                               \
                                                                            \
            /*                                                              \
             *  If using STRING sort method, treat punctuation the same     \
             *  as symbol.  So, FALL THROUGH to the symbol cases.           \
             */                                                             \
        }                                                                   \
        case ( SYMBOL_1 ) :                                                 \
        case ( SYMBOL_2 ) :                                                 \
        case ( SYMBOL_3 ) :                                                 \
        case ( SYMBOL_4 ) :                                                 \
        case ( SYMBOL_5 ) :                                                 \
        {                                                                   \
            /*                                                              \
             *  Character is a symbol.                                      \
             *  Store the Unicode weights ONLY if the NORM_IGNORESYMBOLS    \
             *  flag is NOT set.                                            \
             */                                                             \
            if (!fIgnoreSymbols)                                            \
            {                                                               \
                *pPosUW = GET_UNICODE_MOD(pWeight, fModify);                \
                *pPosDW = GET_DIACRITIC(pWeight);                           \
                *pPosCW = GET_CASE(pWeight) & CaseMask;                     \
                pPosUW++;                                                   \
                pPosDW++;                                                   \
                pPosCW++;                                                   \
            }                                                               \
                                                                            \
            break;                                                          \
        }                                                                   \
        case ( FAREAST_SPECIAL ) :                                          \
        {                                                                   \
            /*                                                              \
             *  Get the alphanumeric weight and the case weight of the      \
             *  current code point.                                         \
             */                                                             \
            AW = GET_ALPHA_NUMERIC(pWeight);                                \
            XW = (GET_CASE(pWeight) & CaseMask) | CASE_XW_MASK;             \
                                                                            \
            /*                                                              \
             *  Special case Repeat and Cho-On.                             \
             *    AW = 0  =>  Repeat                                        \
             *    AW = 1  =>  Cho-On                                        \
             *    AW = 2+ =>  Kana                                          \
             */                                                             \
            if (AW <= MAX_SPECIAL_AW)                                       \
            {                                                               \
                /*                                                          \
                 *  If the script member of the previous character is       \
                 *  invalid, then give the special character an             \
                 *  invalid weight (highest possible weight) so that it     \
                 *  will sort AFTER everything else.                        \
                 */                                                         \
                pPrev = pCur - 1;                                           \
                *pPosUW = MAP_INVALID_UW;                                   \
                while (pPrev >= pBegin)                                     \
                {                                                           \
                    PrevWt = GET_DWORD_WEIGHT(pHashN, *pPrev);              \
                    PrevSM = GET_SCRIPT_MEMBER(&PrevWt);                    \
                    if (PrevSM < FAREAST_SPECIAL)                           \
                    {                                                       \
                        if (PrevSM != EXPANSION)                            \
                        {                                                   \
                            /*                                              \
                             *  UNSORTABLE or NONSPACE_MARK.                \
                             *                                              \
                             *  Just ignore these, since we only care       \
                             *  about the previous UW value.                \
                             */                                             \
                            pPrev--;                                        \
                            continue;                                       \
                        }                                                   \
                    }                                                       \
                    else if (PrevSM == FAREAST_SPECIAL)                     \
                    {                                                       \
                        PrevAW = GET_ALPHA_NUMERIC(&PrevWt);                \
                        if (PrevAW <= MAX_SPECIAL_AW)                       \
                        {                                                   \
                            /*                                              \
                             *  Handle case where two special chars follow  \
                             *  each other.  Keep going back in the string. \
                             */                                             \
                            pPrev--;                                        \
                            continue;                                       \
                        }                                                   \
                                                                            \
                        *pPosUW = MAKE_UNICODE_WT(KANA, PrevAW, fModify);   \
                                                                            \
                        /*                                                  \
                         *  Only build weights 4, 5, 6, and 7 if the        \
                         *  previous character is KANA.                     \
                         *                                                  \
                         *  Always:                                         \
                         *    4W = previous CW  &  ISOLATE_SMALL            \
                         *    6W = previous CW  &  ISOLATE_KANA             \
                         *                                                  \
                         */                                                 \
                        PrevCW = (GET_CASE(&PrevWt) & CaseMask) |           \
                                 CASE_XW_MASK;                              \
                                                                            \
                        EXTRA_WEIGHT_POS(0) = PrevCW & ISOLATE_SMALL;       \
                        EXTRA_WEIGHT_POS(2) = PrevCW & ISOLATE_KANA;        \
                                                                            \
                        if (AW == AW_REPEAT)                                \
                        {                                                   \
                            /*                                              \
                             *  Repeat:                                     \
                             *    UW = previous UW   (set above)            \
                             *    5W = WT_FIVE_REPEAT                       \
                             *    7W = previous CW  &  ISOLATE_WIDTH        \
                             */                                             \
                            EXTRA_WEIGHT_POS(1) = WT_FIVE_REPEAT;           \
                            EXTRA_WEIGHT_POS(3) = PrevCW & ISOLATE_WIDTH;   \
                        }                                                   \
                        else                                                \
                        {                                                   \
                            /*                                              \
                             *  Cho-On:                                     \
                             *    UW = previous UW  &  CHO_ON_UW_MASK       \
                             *    5W = WT_FIVE_CHO_ON                       \
                             *    7W = current  CW  &  ISOLATE_WIDTH        \
                             */                                             \
                            *pPosUW &= CHO_ON_UW_MASK;                      \
                            EXTRA_WEIGHT_POS(1) = WT_FIVE_CHO_ON;           \
                            EXTRA_WEIGHT_POS(3) = XW & ISOLATE_WIDTH;       \
                        }                                                   \
                                                                            \
                        pPosXW++;                                           \
                    }                                                       \
                    else                                                    \
                    {                                                       \
                        *pPosUW = GET_UNICODE_MOD(&PrevWt, fModify);        \
                    }                                                       \
                                                                            \
                    break;                                                  \
                }                                                           \
                                                                            \
                /*                                                          \
                 *  Make sure there is a valid UW.  If not, quit out        \
                 *  of switch case.                                         \
                 */                                                         \
                if (*pPosUW == MAP_INVALID_UW)                              \
                {                                                           \
                    pPosUW++;                                               \
                    break;                                                  \
                }                                                           \
            }                                                               \
            else                                                            \
            {                                                               \
                /*                                                          \
                 *  Kana:                                                   \
                 *    SM = KANA                                             \
                 *    AW = current AW                                       \
                 *    4W = current CW  &  ISOLATE_SMALL                     \
                 *    5W = WT_FIVE_KANA                                     \
                 *    6W = current CW  &  ISOLATE_KANA                      \
                 *    7W = current CW  &  ISOLATE_WIDTH                     \
                 */                                                         \
                *pPosUW = MAKE_UNICODE_WT(KANA, AW, fModify);               \
                EXTRA_WEIGHT_POS(0) = XW & ISOLATE_SMALL;                   \
                EXTRA_WEIGHT_POS(1) = WT_FIVE_KANA;                         \
                EXTRA_WEIGHT_POS(2) = XW & ISOLATE_KANA;                    \
                EXTRA_WEIGHT_POS(3) = XW & ISOLATE_WIDTH;                   \
                                                                            \
                pPosXW++;                                                   \
            }                                                               \
                                                                            \
            /*                                                              \
             *  Always:                                                     \
             *    DW = current DW                                           \
             *    CW = minimum CW                                           \
             */                                                             \
            *pPosDW = GET_DIACRITIC(pWeight);                               \
            *pPosCW = MIN_CW;                                               \
                                                                            \
            pPosUW++;                                                       \
            pPosDW++;                                                       \
            pPosCW++;                                                       \
                                                                            \
            break;                                                          \
        }                                                                   \
        case ( JAMO_SPECIAL ) :                                             \
        {                                                                   \
            /*                                                              \
             *  See if it's a leading Jamo.                                 \
             */                                                             \
            if (IsLeadingJamo(*pPos))                                       \
            {                                                               \
                int OldHangulCount;   /* number of old Hangul found */      \
                WORD JamoUW;                                                \
                BYTE JamoXW[3];                                             \
                                                                            \
                /*                                                          \
                 *  If the characters beginning from pPos are a valid old   \
                 *  Hangul composition, create the sortkey according to     \
                 *  the old Hangul rule.                                    \
                 */                                                         \
                if ((OldHangulCount =                                       \
                        MapOldHangulSortKey( pHashN,                        \
                                             pPos,                          \
                                             cchSrc - PosCtr,               \
                                             &JamoUW,                       \
                                             JamoXW,                        \
                                             fModify )) > 0)                \
                {                                                           \
                    *pPosUW = JamoUW;                                       \
                    pPosUW++;                                               \
                    *pPosUW = MAKE_UNICODE_WT(SM_UW_XW, JamoXW[0], FALSE);  \
                    pPosUW++;                                               \
                    *pPosUW = MAKE_UNICODE_WT(SM_UW_XW, JamoXW[1], FALSE);  \
                    pPosUW++;                                               \
                    *pPosUW = MAKE_UNICODE_WT(SM_UW_XW, JamoXW[2], FALSE);  \
                    pPosUW++;                                               \
                                                                            \
                    *pPosDW = MIN_DW;                                       \
                    *pPosCW = MIN_CW;                                       \
                    pPosDW++;                                               \
                    pPosCW++;                                               \
                                                                            \
                    /*                                                      \
                     *  Decrement OldHangulCount because the for loop will  \
                     *  increase PosCtr and pPos as well.                   \
                     */                                                     \
                    OldHangulCount--;                                       \
                    PosCtr += OldHangulCount;                               \
                    pPos += OldHangulCount;                                 \
                                                                            \
                    break;                                                  \
                }                                                           \
            }                                                               \
                                                                            \
            /*                                                              \
             *  Otherwise, fall back to the normal behavior.                \
             *                                                              \
             *  No special case on character, so store the various          \
             *  weights for the character.                                  \
             */                                                             \
                                                                            \
            /*                                                              \
             *  We store the real script member in the diacritic weight     \
             *  since both the diacritic weight and case weight are not     \
             *  used in Korean.                                             \
             */                                                             \
            *pPosUW = MAKE_UNICODE_WT( GET_DIACRITIC(pWeight),              \
                                       GET_ALPHA_NUMERIC(pWeight),          \
                                       fModify );                           \
            *pPosDW = MIN_DW;                                               \
            *pPosCW = GET_CASE(pWeight);                                    \
            pPosUW++;                                                       \
            pPosDW++;                                                       \
            pPosCW++;                                                       \
                                                                            \
            break;                                                          \
        }                                                                   \
        case ( EXTENSION_A ) :                                              \
        {                                                                   \
            /*                                                              \
             *  UW = SM_EXT_A, AW_EXT_A, AW, DW                             \
             *  DW = miniumum DW                                            \
             *  CW = minimum CW                                             \
             */                                                             \
            *pPosUW = MAKE_UNICODE_WT(SM_EXT_A, AW_EXT_A, fModify);         \
            pPosUW++;                                                       \
                                                                            \
            *pPosUW = MAKE_UNICODE_WT( GET_ALPHA_NUMERIC(pWeight),          \
                                       GET_DIACRITIC(pWeight),              \
                                       FALSE );                             \
            pPosUW++;                                                       \
                                                                            \
            *pPosDW = MIN_DW;                                               \
            *pPosCW = MIN_CW;                                               \
            pPosDW++;                                                       \
            pPosCW++;                                                       \
                                                                            \
            break;                                                          \
        }                                                                   \
    }                                                                       \
}


////////////////////////////////////////////////////////////////////////////
//
//  MapSortKey
//
//  Stores the sortkey weights for the given string in the destination
//  buffer and returns the number of BYTES written to the buffer.
//
//  11-04-92    JulieB    Created.
////////////////////////////////////////////////////////////////////////////

int MapSortKey(
    PLOC_HASH pHashN,
    DWORD dwFlags,
    LPCWSTR pSrc,
    int cchSrc,
    LPBYTE pDest,
    int cbDest,
    BOOL fModify)
{
    register int WeightLen;       // length of one set of weights
    LPWSTR pUW;                   // ptr to Unicode Weights
    LPBYTE pDW;                   // ptr to Diacritic Weights
    LPBYTE pCW;                   // ptr to Case Weights
    LPBYTE pXW;                   // ptr to Extra Weights
    LPWSTR pSW;                   // ptr to Special Weights
    LPWSTR pPosUW;                // ptr to position in pUW buffer
    LPBYTE pPosDW;                // ptr to position in pDW buffer
    LPBYTE pPosCW;                // ptr to position in pCW buffer
    LPBYTE pPosXW;                // ptr to position in pXW buffer
    LPWSTR pPosSW;                // ptr to position in pSW buffer
    PSORTKEY pWeight;             // ptr to weight of character
    BYTE SM;                      // script member value
    BYTE CaseMask;                // mask for case weight
    int PosCtr;                   // position counter in string
    LPWSTR pPos;                  // ptr to position in string
    LPBYTE pTmp;                  // ptr to go through UW, XW, and SW
    LPBYTE pPosTmp;               // ptr to tmp position in XW
    PCOMPRESS_2 pComp2;           // ptr to compression 2 list
    PCOMPRESS_3 pComp3;           // ptr to compression 3 list
    WORD pBuffer[MAX_SORTKEY_BUF_LEN]; // buffer to hold weights
    int ctr;                      // loop counter
    BOOL IfDblCompress;           // if double compress possibility
    BOOL fStringSort;             // if using string sort method
    BOOL fIgnoreSymbols;          // if ignore symbols flag is set


    //
    //  Make sure the sorting information is available in the system.
    //
    if ((pHashN->pSortkey == NULL) ||
        (pHashN->IfIdeographFailure == TRUE))
    {
        KdPrint(("NLSAPI: Appropriate Sorting Tables Not Loaded.\n"));
        SetLastError(ERROR_FILE_NOT_FOUND);
        return (0);
    }

    //
    //  See if the length of the string is too large for the static
    //  buffer.  If so, allocate a buffer that is large enough.
    //
    if (cchSrc > MAX_SMALL_BUF_LEN)
    {
        //
        //  Allocate buffer to hold all of the weights.
        //     (cchSrc) * (max # of expansions) * (# of weights)
        //
        WeightLen = cchSrc * MAX_EXPANSION;
        if ((pUW = (LPWSTR)NLS_ALLOC_MEM( WeightLen * MAX_WEIGHTS *
                                          sizeof(WCHAR) )) == NULL)
        {
            SetLastError(ERROR_OUTOFMEMORY);
            return (0);
        }
    }
    else
    {
        WeightLen = MAX_SMALL_BUF_LEN * MAX_EXPANSION;
        pUW = (LPWSTR)pBuffer;
    }

    //
    //  Set the case weight mask based on the given flags.
    //  If none or all of the ignore case flags are set, then
    //  just leave the mask as 0xff.
    //
    CaseMask = 0xff;
    switch (dwFlags & NORM_ALL_CASE)
    {
        case ( NORM_IGNORECASE ) :
        {
            CaseMask &= CASE_UPPER_MASK;
            break;
        }
        case ( NORM_IGNOREKANATYPE ) :
        {
            CaseMask &= CASE_KANA_MASK;
            break;
        }
        case ( NORM_IGNOREWIDTH ) :
        {
            CaseMask &= CASE_WIDTH_MASK;
            break;
        }
        case ( NORM_IGNORECASE | NORM_IGNOREKANATYPE ) :
        {
            CaseMask &= (CASE_UPPER_MASK & CASE_KANA_MASK);
            break;
        }
        case ( NORM_IGNORECASE | NORM_IGNOREWIDTH ) :
        {
            CaseMask &= (CASE_UPPER_MASK & CASE_WIDTH_MASK);
            break;
        }
        case ( NORM_IGNOREKANATYPE | NORM_IGNOREWIDTH ) :
        {
            CaseMask &= (CASE_KANA_MASK & CASE_WIDTH_MASK);
            break;
        }
        case ( NORM_IGNORECASE | NORM_IGNOREKANATYPE | NORM_IGNOREWIDTH ) :
        {
            CaseMask &= (CASE_UPPER_MASK & CASE_KANA_MASK & CASE_WIDTH_MASK);
            break;
        }
    }

    //
    //  Set pointers to positions of weights in buffer.
    //
    //      UW  =>  4 word length  (extension A and Jamo need extra words)
    //      DW  =>  byte   length
    //      CW  =>  byte   length
    //      XW  =>  4 byte length  (4 weights, 1 byte each)   FE Special
    //      SW  =>  dword  length  (2 words each)
    //
    //  Note: SW must start on a WORD boundary, so XW needs to be padded
    //        appropriately.
    //
    pDW     = (LPBYTE)(pUW + (WeightLen * (NUM_BYTES_UW / sizeof(WCHAR))));
    pCW     = (LPBYTE)(pDW + (WeightLen *  NUM_BYTES_DW));
    pXW     = (LPBYTE)(pCW + (WeightLen *  NUM_BYTES_CW));
    pSW     = (LPWSTR)(pXW + (WeightLen * (NUM_BYTES_XW + NUM_BYTES_PADDING)));
    pPosUW  = pUW;
    pPosDW  = pDW;
    pPosCW  = pCW;
    pPosXW  = pXW;
    pPosSW  = pSW;

    //
    //  Initialize flags and loop values.
    //
    fStringSort = dwFlags & SORT_STRINGSORT;
    fIgnoreSymbols = dwFlags & NORM_IGNORESYMBOLS;
    pPos = (LPWSTR)pSrc;
    PosCtr = 1;

    //
    //  Check if given locale has compressions.
    //
    if (pHashN->IfCompression == FALSE)
    {
        //
        //  Go through string, code point by code point.
        //
        //  No compressions exist in the given locale, so
        //  DO NOT check for them.
        //
        for (; PosCtr <= cchSrc; PosCtr++, pPos++)
        {
            //
            //  Get weights.
            //
            pWeight = &((pHashN->pSortkey)[*pPos]);
            SM = GET_SCRIPT_MEMBER(pWeight);

            if (SM > MAX_SPECIAL_CASE)
            {
                //
                //  No special case on character, so store the
                //  various weights for the character.
                //
                *pPosUW = GET_UNICODE_MOD(pWeight, fModify);
                *pPosDW = GET_DIACRITIC(pWeight);
                *pPosCW = GET_CASE(pWeight) & CaseMask;
                pPosUW++;
                pPosDW++;
                pPosCW++;
            }
            else
            {
                SPECIAL_CASE_HANDLER( SM,
                                      pWeight,
                                      pHashN->pSortkey,
                                      pTblPtrs->pExpansion,
                                      pPosUW - pUW + 1,
                                      fStringSort,
                                      fIgnoreSymbols,
                                      pPos,
                                      (LPWSTR)pSrc,
                                      fModify );
            }
        }
    }
    else if (pHashN->IfDblCompression == FALSE)
    {
        //
        //  Go through string, code point by code point.
        //
        //  Compressions DO exist in the given locale, so
        //  check for them.
        //
        //  No double compressions exist in the given locale,
        //  so DO NOT check for them.
        //
        for (; PosCtr <= cchSrc; PosCtr++, pPos++)
        {
            //
            //  Get weights.
            //
            pWeight = &((pHashN->pSortkey)[*pPos]);
            SM = GET_SCRIPT_MEMBER(pWeight);

            if (SM > MAX_SPECIAL_CASE)
            {
                //
                //  No special case on character, but must check for
                //  compression characters.
                //
                switch (GET_COMPRESSION(pWeight))
                {
                    case ( COMPRESS_3_MASK ) :
                    {
                        if ((PosCtr + 2) <= cchSrc)
                        {
                            ctr = pHashN->pCompHdr->Num3;
                            pComp3 = pHashN->pCompress3;
                            for (; ctr > 0; ctr--, pComp3++)
                            {
                                if ((pComp3->UCP1 == *pPos) &&
                                    (pComp3->UCP2 == *(pPos + 1)) &&
                                    (pComp3->UCP3 == *(pPos + 2)))
                                {
                                    pWeight = &(pComp3->Weights);
                                    *pPosUW = GET_UNICODE_MOD(pWeight, fModify);
                                    *pPosDW = GET_DIACRITIC(pWeight);
                                    *pPosCW = GET_CASE(pWeight) & CaseMask;
                                    pPosUW++;
                                    pPosDW++;
                                    pPosCW++;

                                    //
                                    //  Add only two to source, since one
                                    //  will be added by "for" structure.
                                    //
                                    pPos += 2;
                                    PosCtr += 2;
                                    break;
                                }
                            }
                            if (ctr > 0)
                            {
                                break;
                            }
                        }

                        //
                        //  Fall through if not found.
                        //
                    }
                    case ( COMPRESS_2_MASK ) :
                    {
                        if ((PosCtr + 1) <= cchSrc)
                        {
                            ctr = pHashN->pCompHdr->Num2;
                            pComp2 = pHashN->pCompress2;
                            for (; ctr > 0; ctr--, pComp2++)
                            {
                                if ((pComp2->UCP1 == *pPos) &&
                                    (pComp2->UCP2 == *(pPos + 1)))
                                {
                                    pWeight = &(pComp2->Weights);
                                    *pPosUW = GET_UNICODE_MOD(pWeight, fModify);
                                    *pPosDW = GET_DIACRITIC(pWeight);
                                    *pPosCW = GET_CASE(pWeight) & CaseMask;
                                    pPosUW++;
                                    pPosDW++;
                                    pPosCW++;

                                    //
                                    //  Add only one to source, since one
                                    //  will be added by "for" structure.
                                    //
                                    pPos++;
                                    PosCtr++;
                                    break;
                                }
                            }
                            if (ctr > 0)
                            {
                                break;
                            }
                        }

                        //
                        //  Fall through if not found.
                        //
                    }
                    default :
                    {
                        //
                        //  No possible compression for character, so store
                        //  the various weights for the character.
                        //
                        *pPosUW = GET_UNICODE_SM_MOD(pWeight, SM, fModify);
                        *pPosDW = GET_DIACRITIC(pWeight);
                        *pPosCW = GET_CASE(pWeight) & CaseMask;
                        pPosUW++;
                        pPosDW++;
                        pPosCW++;
                    }
                }
            }
            else
            {
                SPECIAL_CASE_HANDLER( SM,
                                      pWeight,
                                      pHashN->pSortkey,
                                      pTblPtrs->pExpansion,
                                      pPosUW - pUW + 1,
                                      fStringSort,
                                      fIgnoreSymbols,
                                      pPos,
                                      (LPWSTR)pSrc,
                                      fModify );
            }
        }
    }
    else
    {
        //
        //  Go through string, code point by code point.
        //
        //  Compressions DO exist in the given locale, so
        //  check for them.
        //
        //  Double Compressions also exist in the given locale,
        //  so check for them.
        //
        for (; PosCtr <= cchSrc; PosCtr++, pPos++)
        {
            //
            //  Get weights.
            //
            pWeight = &((pHashN->pSortkey)[*pPos]);
            SM = GET_SCRIPT_MEMBER(pWeight);

            if (SM > MAX_SPECIAL_CASE)
            {
                //
                //  No special case on character, but must check for
                //  compression characters and double compression
                //  characters.
                //
                IfDblCompress =
                  (((PosCtr + 1) <= cchSrc) &&
                   ((GET_DWORD_WEIGHT(pHashN, *pPos) & CMP_MASKOFF_CW) ==
                    (GET_DWORD_WEIGHT(pHashN, *(pPos + 1)) & CMP_MASKOFF_CW)))
                   ? 1
                   : 0;

                switch (GET_COMPRESSION(pWeight))
                {
                    case ( COMPRESS_3_MASK ) :
                    {
                        if (IfDblCompress)
                        {
                            if ((PosCtr + 3) <= cchSrc)
                            {
                                ctr = pHashN->pCompHdr->Num3;
                                pComp3 = pHashN->pCompress3;
                                for (; ctr > 0; ctr--, pComp3++)
                                {
                                    if ((pComp3->UCP1 == *(pPos + 1)) &&
                                        (pComp3->UCP2 == *(pPos + 2)) &&
                                        (pComp3->UCP3 == *(pPos + 3)))
                                    {
                                        pWeight = &(pComp3->Weights);
                                        *pPosUW = GET_UNICODE_MOD(pWeight, fModify);
                                        *pPosDW = GET_DIACRITIC(pWeight);
                                        *pPosCW = GET_CASE(pWeight) & CaseMask;
                                        *(pPosUW + 1) = *pPosUW;
                                        *(pPosDW + 1) = *pPosDW;
                                        *(pPosCW + 1) = *pPosCW;
                                        pPosUW += 2;
                                        pPosDW += 2;
                                        pPosCW += 2;

                                        //
                                        //  Add only three to source, since one
                                        //  will be added by "for" structure.
                                        //
                                        pPos += 3;
                                        PosCtr += 3;
                                        break;
                                    }
                                }
                                if (ctr > 0)
                                {
                                    break;
                                }
                            }
                        }

                        //
                        //  Fall through if not found.
                        //
                        if ((PosCtr + 2) <= cchSrc)
                        {
                            ctr = pHashN->pCompHdr->Num3;
                            pComp3 = pHashN->pCompress3;
                            for (; ctr > 0; ctr--, pComp3++)
                            {
                                if ((pComp3->UCP1 == *pPos) &&
                                    (pComp3->UCP2 == *(pPos + 1)) &&
                                    (pComp3->UCP3 == *(pPos + 2)))
                                {
                                    pWeight = &(pComp3->Weights);
                                    *pPosUW = GET_UNICODE_MOD(pWeight, fModify);
                                    *pPosDW = GET_DIACRITIC(pWeight);
                                    *pPosCW = GET_CASE(pWeight) & CaseMask;
                                    pPosUW++;
                                    pPosDW++;
                                    pPosCW++;

                                    //
                                    //  Add only two to source, since one
                                    //  will be added by "for" structure.
                                    //
                                    pPos += 2;
                                    PosCtr += 2;
                                    break;
                                }
                            }
                            if (ctr > 0)
                            {
                                break;
                            }
                        }
                        //
                        //  Fall through if not found.
                        //
                    }
                    case ( COMPRESS_2_MASK ) :
                    {
                        if (IfDblCompress)
                        {
                            if ((PosCtr + 2) <= cchSrc)
                            {
                                ctr = pHashN->pCompHdr->Num2;
                                pComp2 = pHashN->pCompress2;
                                for (; ctr > 0; ctr--, pComp2++)
                                {
                                    if ((pComp2->UCP1 == *(pPos + 1)) &&
                                        (pComp2->UCP2 == *(pPos + 2)))
                                    {
                                        pWeight = &(pComp2->Weights);
                                        *pPosUW = GET_UNICODE_MOD(pWeight, fModify);
                                        *pPosDW = GET_DIACRITIC(pWeight);
                                        *pPosCW = GET_CASE(pWeight) & CaseMask;
                                        *(pPosUW + 1) = *pPosUW;
                                        *(pPosDW + 1) = *pPosDW;
                                        *(pPosCW + 1) = *pPosCW;
                                        pPosUW += 2;
                                        pPosDW += 2;
                                        pPosCW += 2;

                                        //
                                        //  Add only two to source, since one
                                        //  will be added by "for" structure.
                                        //
                                        pPos += 2;
                                        PosCtr += 2;
                                        break;
                                    }
                                }
                                if (ctr > 0)
                                {
                                    break;
                                }
                            }
                        }

                        //
                        //  Fall through if not found.
                        //
                        if ((PosCtr + 1) <= cchSrc)
                        {
                            ctr = pHashN->pCompHdr->Num2;
                            pComp2 = pHashN->pCompress2;
                            for (; ctr > 0; ctr--, pComp2++)
                            {
                                if ((pComp2->UCP1 == *pPos) &&
                                    (pComp2->UCP2 == *(pPos + 1)))
                                {
                                    pWeight = &(pComp2->Weights);
                                    *pPosUW = GET_UNICODE_MOD(pWeight, fModify);
                                    *pPosDW = GET_DIACRITIC(pWeight);
                                    *pPosCW = GET_CASE(pWeight) & CaseMask;
                                    pPosUW++;
                                    pPosDW++;
                                    pPosCW++;

                                    //
                                    //  Add only one to source, since one
                                    //  will be added by "for" structure.
                                    //
                                    pPos++;
                                    PosCtr++;
                                    break;
                                }
                            }
                            if (ctr > 0)
                            {
                                break;
                            }
                        }

                        //
                        //  Fall through if not found.
                        //
                    }
                    default :
                    {
                        //
                        //  No possible compression for character, so store
                        //  the various weights for the character.
                        //
                        *pPosUW = GET_UNICODE_SM_MOD(pWeight, SM, fModify);
                        *pPosDW = GET_DIACRITIC(pWeight);
                        *pPosCW = GET_CASE(pWeight) & CaseMask;
                        pPosUW++;
                        pPosDW++;
                        pPosCW++;
                    }
                }
            }
            else
            {
                SPECIAL_CASE_HANDLER( SM,
                                      pWeight,
                                      pHashN->pSortkey,
                                      pTblPtrs->pExpansion,
                                      pPosUW - pUW + 1,
                                      fStringSort,
                                      fIgnoreSymbols,
                                      pPos,
                                      (LPWSTR)pSrc,
                                      fModify );
            }
        }
    }

    //
    //  Store the final sortkey weights in the destination buffer.
    //
    //  PosCtr will be a BYTE count.
    //
    PosCtr = 0;

    //
    //  If the destination value is zero, then just return the
    //  length of the string that would be returned.  Do NOT touch pDest.
    //
    if (cbDest == 0)
    {
        //
        //  Count the Unicode Weights.
        //
        PosCtr += (int)((LPBYTE)pPosUW - (LPBYTE)pUW);

        //
        //  Count the Separator.
        //
        PosCtr++;

        //
        //  Count the Diacritic Weights.
        //
        //    - Eliminate minimum DW.
        //    - Count the number of diacritic weights.
        //
        if (!(dwFlags & NORM_IGNORENONSPACE))
        {
            pPosDW--;
            if (pHashN->IfReverseDW == TRUE)
            {
                //
                //  Reverse diacritics:
                //    - remove diacritics from left  to right.
                //    - count  diacritics from right to left.
                //
                while ((pDW <= pPosDW) && (*pDW <= MIN_DW))
                {
                    pDW++;
                }
                PosCtr += (int)(pPosDW - pDW + 1);
            }
            else
            {
                //
                //  Regular diacritics:
                //    - remove diacritics from right to left.
                //    - count  diacritics from left  to right.
                //
                while ((pPosDW >= pDW) && (*pPosDW <= MIN_DW))
                {
                    pPosDW--;
                }
                PosCtr += (int)(pPosDW - pDW + 1);
            }
        }

        //
        //  Count the Separator.
        //
        PosCtr++;

        //
        //  Count the Case Weights.
        //
        //    - Eliminate minimum CW.
        //    - Count the number of case weights.
        //
        if ((dwFlags & NORM_DROP_CW) != NORM_DROP_CW)
        {
            pPosCW--;
            while ((pPosCW >= pCW) && (*pPosCW <= MIN_CW))
            {
                pPosCW--;
            }
            PosCtr += (int)(pPosCW - pCW + 1);
        }

        //
        //  Count the Separator.
        //
        PosCtr++;

        //
        //  Count the Extra Weights for Far East Special.
        //
        //    - Eliminate unnecessary XW.
        //    - Count the number of extra weights and separators.
        //
        if (pXW < pPosXW)
        {
            if (dwFlags & NORM_IGNORENONSPACE)
            {
                //
                //  Ignore 4W and 5W.  Must count separators for
                //  4W and 5W, though.
                //
                PosCtr += 2;
                ctr = 2;
            }
            else
            {
                ctr = 0;
            }

            pPosXW--;
            for (; ctr < NUM_BYTES_XW; ctr++)
            {
                pTmp = pXW + (WeightLen * ctr);
                pPosTmp = pPosXW + (WeightLen * ctr);
                while ((pPosTmp >= pTmp) && (*pPosTmp == pXWDrop[ctr]))
                {
                    pPosTmp--;
                }
                PosCtr += (int)(pPosTmp - pTmp + 1);

                //
                //  Count the Separator.
                //
                PosCtr++;
            }
        }

        //
        //  Count the Separator.
        //
        PosCtr++;

        //
        //  Count the Special Weights.
        //
        if (!fIgnoreSymbols)
        {
            PosCtr += (int)((LPBYTE)pPosSW - (LPBYTE)pSW);
        }

        //
        //  Count the Terminator.
        //
        PosCtr++;
    }
    else
    {
        //
        //  Store the Unicode Weights in the destination buffer.
        //
        //    - Make sure destination buffer is large enough.
        //    - Copy unicode weights to destination buffer.
        //
        //  NOTE:  cbDest is the number of BYTES.
        //         Also, must add one to length for separator.
        //
        if (cbDest < (((LPBYTE)pPosUW - (LPBYTE)pUW) + 1))
        {
            NLS_FREE_TMP_BUFFER(pUW, pBuffer);
            SetLastError(ERROR_INSUFFICIENT_BUFFER);
            return (0);
        }
        pTmp = (LPBYTE)pUW;
        while (pTmp < (LPBYTE)pPosUW)
        {
            //
            //  Copy Unicode weight to destination buffer.
            //
            //  NOTE:  Unicode Weight is stored in the data file as
            //             Alphanumeric Weight, Script Member
            //         so that the WORD value will be read correctly.
            //
            pDest[PosCtr]     = *(pTmp + 1);
            pDest[PosCtr + 1] = *pTmp;
            PosCtr += 2;
            pTmp += 2;
        }

        //
        //  Copy Separator to destination buffer.
        //
        //  Destination buffer is large enough to hold the separator,
        //  since it was checked with the Unicode weights above.
        //
        pDest[PosCtr] = SORTKEY_SEPARATOR;
        PosCtr++;

        //
        //  Store the Diacritic Weights in the destination buffer.
        //
        //    - Eliminate minimum DW.
        //    - Make sure destination buffer is large enough.
        //    - Copy diacritic weights to destination buffer.
        //
        if (!(dwFlags & NORM_IGNORENONSPACE))
        {
            pPosDW--;
            if (pHashN->IfReverseDW == TRUE)
            {
                //
                //  Reverse diacritics:
                //    - remove diacritics from left  to right.
                //    - store  diacritics from right to left.
                //
                while ((pDW <= pPosDW) && (*pDW <= MIN_DW))
                {
                    pDW++;
                }
                if ((cbDest - PosCtr) <= (pPosDW - pDW + 1))
                {
                    NLS_FREE_TMP_BUFFER(pUW, pBuffer);
                    SetLastError(ERROR_INSUFFICIENT_BUFFER);
                    return (0);
                }
                while (pPosDW >= pDW)
                {
                    pDest[PosCtr] = *pPosDW;
                    PosCtr++;
                    pPosDW--;
                }
            }
            else
            {
                //
                //  Regular diacritics:
                //    - remove diacritics from right to left.
                //    - store  diacritics from left  to right.
                //
                while ((pPosDW >= pDW) && (*pPosDW <= MIN_DW))
                {
                    pPosDW--;
                }
                if ((cbDest - PosCtr) <= (pPosDW - pDW + 1))
                {
                    NLS_FREE_TMP_BUFFER(pUW, pBuffer);
                    SetLastError(ERROR_INSUFFICIENT_BUFFER);
                    return (0);
                }
                while (pDW <= pPosDW)
                {
                    pDest[PosCtr] = *pDW;
                    PosCtr++;
                    pDW++;
                }
            }
        }

        //
        //  Copy Separator to destination buffer if the destination
        //  buffer is large enough.
        //
        if (PosCtr == cbDest)
        {
            NLS_FREE_TMP_BUFFER(pUW, pBuffer);
            SetLastError(ERROR_INSUFFICIENT_BUFFER);
            return (0);
        }
        pDest[PosCtr] = SORTKEY_SEPARATOR;
        PosCtr++;

        //
        //  Store the Case Weights in the destination buffer.
        //
        //    - Eliminate minimum CW.
        //    - Make sure destination buffer is large enough.
        //    - Copy case weights to destination buffer.
        //
        if ((dwFlags & NORM_DROP_CW) != NORM_DROP_CW)
        {
            pPosCW--;
            while ((pPosCW >= pCW) && (*pPosCW <= MIN_CW))
            {
                pPosCW--;
            }
            if ((cbDest - PosCtr) <= (pPosCW - pCW + 1))
            {
                NLS_FREE_TMP_BUFFER(pUW, pBuffer);
                SetLastError(ERROR_INSUFFICIENT_BUFFER);
                return (0);
            }
            while (pCW <= pPosCW)
            {
                pDest[PosCtr] = *pCW;
                PosCtr++;
                pCW++;
            }
        }

        //
        //  Copy Separator to destination buffer if the destination
        //  buffer is large enough.
        //
        if (PosCtr == cbDest)
        {
            NLS_FREE_TMP_BUFFER(pUW, pBuffer);
            SetLastError(ERROR_INSUFFICIENT_BUFFER);
            return (0);
        }
        pDest[PosCtr] = SORTKEY_SEPARATOR;
        PosCtr++;

        //
        //  Store the Extra Weights in the destination buffer for
        //  Far East Special.
        //
        //    - Eliminate unnecessary XW.
        //    - Make sure destination buffer is large enough.
        //    - Copy extra weights to destination buffer.
        //
        if (pXW < pPosXW)
        {
            if (dwFlags & NORM_IGNORENONSPACE)
            {
                //
                //  Ignore 4W and 5W.  Must count separators for
                //  4W and 5W, though.
                //
                if ((cbDest - PosCtr) <= 2)
                {
                    NLS_FREE_TMP_BUFFER(pUW, pBuffer);
                    SetLastError(ERROR_INSUFFICIENT_BUFFER);
                    return (0);
                }

                pDest[PosCtr] = pXWSeparator[0];
                pDest[PosCtr + 1] = pXWSeparator[1];
                PosCtr += 2;
                ctr = 2;
            }
            else
            {
                ctr = 0;
            }

            pPosXW--;
            for (; ctr < NUM_BYTES_XW; ctr++)
            {
                pTmp = pXW + (WeightLen * ctr);
                pPosTmp = pPosXW + (WeightLen * ctr);
                while ((pPosTmp >= pTmp) && (*pPosTmp == pXWDrop[ctr]))
                {
                    pPosTmp--;
                }
                if ((cbDest - PosCtr) <= (pPosTmp - pTmp + 1))
                {
                    NLS_FREE_TMP_BUFFER(pUW, pBuffer);
                    SetLastError(ERROR_INSUFFICIENT_BUFFER);
                    return (0);
                }
                while (pTmp <= pPosTmp)
                {
                    pDest[PosCtr] = *pTmp;
                    PosCtr++;
                    pTmp++;
                }

                //
                //  Copy Separator to destination buffer.
                //
                pDest[PosCtr] = pXWSeparator[ctr];
                PosCtr++;
            }
        }

        //
        //  Copy Separator to destination buffer if the destination
        //  buffer is large enough.
        //
        if (PosCtr == cbDest)
        {
            NLS_FREE_TMP_BUFFER(pUW, pBuffer);
            SetLastError(ERROR_INSUFFICIENT_BUFFER);
            return (0);
        }
        pDest[PosCtr] = SORTKEY_SEPARATOR;
        PosCtr++;

        //
        //  Store the Special Weights in the destination buffer.
        //
        //    - Make sure destination buffer is large enough.
        //    - Copy special weights to destination buffer.
        //
        if (!fIgnoreSymbols)
        {
            if ((cbDest - PosCtr) <= (((LPBYTE)pPosSW - (LPBYTE)pSW)))
            {
                NLS_FREE_TMP_BUFFER(pUW, pBuffer);
                SetLastError(ERROR_INSUFFICIENT_BUFFER);
                return (0);
            }
            pTmp = (LPBYTE)pSW;
            while (pTmp < (LPBYTE)pPosSW)
            {
                pDest[PosCtr]     = *pTmp;
                pDest[PosCtr + 1] = *(pTmp + 1);

                //
                //  NOTE:  Special Weight is stored in the data file as
                //             Weight, Script
                //         so that the WORD value will be read correctly.
                //
                pDest[PosCtr + 2] = *(pTmp + 3);
                pDest[PosCtr + 3] = *(pTmp + 2);

                PosCtr += 4;
                pTmp += 4;
            }
        }

        //
        //  Copy Terminator to destination buffer if the destination
        //  buffer is large enough.
        //
        if (PosCtr == cbDest)
        {
            NLS_FREE_TMP_BUFFER(pUW, pBuffer);
            SetLastError(ERROR_INSUFFICIENT_BUFFER);
            return (0);
        }
        pDest[PosCtr] = SORTKEY_TERMINATOR;
        PosCtr++;
    }

    //
    //  Free the buffer used for the weights, if one was allocated.
    //
    NLS_FREE_TMP_BUFFER(pUW, pBuffer);

    //
    //  Return number of BYTES written to destination buffer.
    //
    return (PosCtr);
}


////////////////////////////////////////////////////////////////////////////
//
//  MapNormalization
//
//  Stores the result of the normalization for the given string in the
//  destination buffer, and returns the number of wide characters written
//  to the buffer.
//
//  11-04-92    JulieB    Created.
////////////////////////////////////////////////////////////////////////////

int MapNormalization(
    PLOC_HASH pHashN,
    DWORD dwFlags,
    LPCWSTR pSrc,
    int cchSrc,
    LPWSTR pDest,
    int cchDest)
{
    int ctr;                      // source char counter
    int ctr2 = 0;                 // destination char counter

    //
    //  Make sure the ctype table is available in the system.
    //
    if (GetCTypeFileInfo())
    {
        SetLastError(ERROR_FILE_NOT_FOUND);
        return (0);
    }

    //
    //  Make sure the sorting information is available in the system.
    //
    if ((pHashN->pSortkey == NULL) ||
        (pHashN->IfIdeographFailure == TRUE))
    {
        KdPrint(("NLSAPI: Appropriate Sorting Tables Not Loaded.\n"));
        SetLastError(ERROR_FILE_NOT_FOUND);
        return (0);
    }

    //
    //  Normalize based on the flags.
    //
    switch (dwFlags)
    {
        case ( NORM_IGNORENONSPACE ) :
        {
            //
            //  If the destination value is zero, then only return
            //  the count of characters.  Do NOT touch pDest.
            //
            if (cchDest == 0)
            {
                //
                //  Count the number of characters that would be written
                //  to the destination buffer.
                //
                for (ctr = 0, ctr2 = 0; ctr < cchSrc; ctr++)
                {
                    if (!IS_NONSPACE(pHashN->pSortkey, pSrc[ctr]))
                    {
                        //
                        //  Not a nonspacing character, so just write the
                        //  character to the destination string.
                        //
                        ctr2++;
                    }
                    else if (!(IS_NONSPACE_ONLY(pHashN->pSortkey, pSrc[ctr])))
                    {
                        //
                        //  PreComposed Form.  Write the base character only.
                        //
                        ctr2++;
                    }
                    //
                    //  Else - nonspace character only, so don't write
                    //         anything.
                    //
                }
            }
            else
            {
                //
                //  Store the normalized string in the destination string.
                //
                for (ctr = 0, ctr2 = 0; (ctr < cchSrc) && (ctr2 < cchDest);
                     ctr++)
                {
                    if (!IS_NONSPACE(pHashN->pSortkey, pSrc[ctr]))
                    {
                        //
                        //  Not a nonspacing character, so just write the
                        //  character to the destination string.
                        //
                        pDest[ctr2] = pSrc[ctr];
                        ctr2++;
                    }
                    else if (!(IS_NONSPACE_ONLY(pHashN->pSortkey, pSrc[ctr])))
                    {
                        //
                        //  PreComposed Form.  Write the base character only.
                        //
                        GET_BASE_CHAR(pSrc[ctr], pDest[ctr2]);
                        if (pDest[ctr2] == 0)
                        {
                            //
                            //  No translation for precomposed character,
                            //  so must write the precomposed character.
                            //
                            pDest[ctr2] = pSrc[ctr];
                        }
                        ctr2++;
                    }
                    //
                    //  Else - nonspace character only, so don't write
                    //         anything.
                    //
                }
            }

            break;
        }
        case ( NORM_IGNORESYMBOLS ) :
        {
            //
            //  If the destination value is zero, then only return
            //  the count of characters.  Do NOT touch pDest.
            //
            if (cchDest == 0)
            {
                //
                //  Count the number of characters that would be written
                //  to the destination buffer.
                //
                for (ctr = 0, ctr2 = 0; ctr < cchSrc; ctr++)
                {
                    if (!IS_SYMBOL(pHashN->pSortkey, pSrc[ctr]))
                    {
                        //
                        //  Not a symbol, so write the character.
                        //
                        ctr2++;
                    }
                }
            }
            else
            {
                //
                //  Store the normalized string in the destination string.
                //
                for (ctr = 0, ctr2 = 0; (ctr < cchSrc) && (ctr2 < cchDest);
                     ctr++)
                {
                    if (!IS_SYMBOL(pHashN->pSortkey, pSrc[ctr]))
                    {
                        //
                        //  Not a symbol, so write the character.
                        //
                        pDest[ctr2] = pSrc[ctr];
                        ctr2++;
                    }
                }
            }

            break;
        }
        case ( NORM_IGNORENONSPACE | NORM_IGNORESYMBOLS ) :
        {
            //
            //  If the destination value is zero, then only return
            //  the count of characters.  Do NOT touch pDest.
            //
            if (cchDest == 0)
            {
                //
                //  Count the number of characters that would be written
                //  to the destination buffer.
                //
                for (ctr = 0, ctr2 = 0; ctr < cchSrc; ctr++)
                {
                    if (!IS_SYMBOL(pHashN->pSortkey, pSrc[ctr]))
                    {
                        //
                        //  Not a symbol, so check for nonspace.
                        //
                        if (!IS_NONSPACE(pHashN->pSortkey, pSrc[ctr]))
                        {
                            //
                            //  Not a nonspacing character, so just write the
                            //  character to the destination string.
                            //
                            ctr2++;
                        }
                        else if (!(IS_NONSPACE_ONLY( pHashN->pSortkey,
                                                     pSrc[ctr] )))
                        {
                            //
                            //  PreComposed Form.  Write the base character
                            //  only.
                            //
                            ctr2++;
                        }
                        //
                        //  Else - nonspace character only, so don't write
                        //         anything.
                        //
                    }
                }
            }
            else
            {
                //
                //  Store the normalized string in the destination string.
                //
                for (ctr = 0, ctr2 = 0; (ctr < cchSrc) && (ctr2 < cchDest);
                     ctr++)
                {
                    //
                    //  Check for symbol and nonspace.
                    //
                    if (!IS_SYMBOL(pHashN->pSortkey, pSrc[ctr]))
                    {
                        //
                        //  Not a symbol, so check for nonspace.
                        //
                        if (!IS_NONSPACE(pHashN->pSortkey, pSrc[ctr]))
                        {
                            //
                            //  Not a nonspacing character, so just write the
                            //  character to the destination string.
                            //
                            pDest[ctr2] = pSrc[ctr];
                            ctr2++;
                        }
                        else if (!(IS_NONSPACE_ONLY( pHashN->pSortkey,
                                                     pSrc[ctr] )))
                        {
                            //
                            //  PreComposed Form.  Write the base character
                            //  only.
                            //
                            GET_BASE_CHAR(pSrc[ctr], pDest[ctr2]);
                            if (pDest[ctr2] == 0)
                            {
                                //
                                //  No translation for precomposed character,
                                //  so must write the precomposed character.
                                //
                                pDest[ctr2] = pSrc[ctr];
                            }
                            ctr2++;
                        }
                        //
                        //  Else - nonspace character only, so don't write
                        //         anything.
                        //
                    }
                }
            }

            break;
        }
    }

    //
    //  Return the number of wide characters written.
    //
    return (ctr2);
}


////////////////////////////////////////////////////////////////////////////
//
//  MapKanaWidth
//
//  Stores the result of the Kana, Width, and/or Casing mappings for the
//  given string in the destination buffer, and returns the number of wide
//  characters written to the buffer.
//
//  07-26-93    JulieB    Created.
////////////////////////////////////////////////////////////////////////////

int MapKanaWidth(
    PLOC_HASH pHashN,
    DWORD dwFlags,
    LPCWSTR pSrc,
    int cchSrc,
    LPWSTR pDest,
    int cchDest)
{
    int ctr;                 // loop counter
    PCASE pCase;             // ptr to case table (if case flag is set)


    //
    //  See if lower or upper case flags are present.
    //
    if (dwFlags & LCMAP_LOWERCASE)
    {
        pCase = (dwFlags & LCMAP_LINGUISTIC_CASING)
                    ? pHashN->pLowerLinguist
                    : pHashN->pLowerCase;
    }
    else if (dwFlags & LCMAP_UPPERCASE)
    {
        pCase = (dwFlags & LCMAP_LINGUISTIC_CASING)
                    ? pHashN->pUpperLinguist
                    : pHashN->pUpperCase;
    }
    else
    {
        pCase = NULL;
    }

    //
    //  Remove lower, upper, and linguistic casing flags.
    //
    dwFlags &= ~(LCMAP_LOWERCASE | LCMAP_UPPERCASE | LCMAP_LINGUISTIC_CASING);

    //
    //  Map the string based on the given flags.
    //
    switch (dwFlags)
    {
        case ( LCMAP_HIRAGANA ) :
        case ( LCMAP_KATAKANA ) :
        {
            //
            //  If the destination value is zero, then just return the
            //  length of the source string.  Do NOT touch pDest.
            //
            if (cchDest == 0)
            {
                return (cchSrc);
            }

            //
            //  If cchSrc is greater than cchDest, then the destination
            //  buffer is too small to hold the string.  Return an error.
            //
            if (cchSrc > cchDest)
            {
                SetLastError(ERROR_INSUFFICIENT_BUFFER);
                return (0);
            }

            if (dwFlags == LCMAP_HIRAGANA)
            {
                //
                //  Map all Katakana full width to Hiragana full width.
                //  Katakana half width will remain Katakana half width.
                //
                if (pCase)
                {
                    for (ctr = 0; ctr < cchSrc; ctr++)
                    {
                        pDest[ctr] = GET_KANA(pTblPtrs->pHiragana, pSrc[ctr]);

                        pDest[ctr] = GET_LOWER_UPPER_CASE(pCase, pDest[ctr]);
                    }
                }
                else
                {
                    for (ctr = 0; ctr < cchSrc; ctr++)
                    {
                        pDest[ctr] = GET_KANA(pTblPtrs->pHiragana, pSrc[ctr]);
                    }
                }
            }
            else
            {
                //
                //  Map all Hiragana full width to Katakana full width.
                //  Hiragana half width does not exist.
                //
                if (pCase)
                {
                    for (ctr = 0; ctr < cchSrc; ctr++)
                    {
                        pDest[ctr] = GET_KANA(pTblPtrs->pKatakana, pSrc[ctr]);

                        pDest[ctr] = GET_LOWER_UPPER_CASE(pCase, pDest[ctr]);
                    }
                }
                else
                {
                    for (ctr = 0; ctr < cchSrc; ctr++)
                    {
                        pDest[ctr] = GET_KANA(pTblPtrs->pKatakana, pSrc[ctr]);
                    }
                }
            }

            //
            //  Return the number of characters mapped.
            //
            return (cchSrc);

            break;
        }
        case ( LCMAP_HALFWIDTH ) :
        {
            //
            //  Map all chars to half width.
            //
            return (MapHalfKana( pSrc,
                                 cchSrc,
                                 pDest,
                                 cchDest,
                                 NULL,
                                 pCase ));

            break;
        }
        case ( LCMAP_FULLWIDTH ) :
        {
            //
            //  Map all chars to full width.
            //
            return (MapFullKana( pSrc,
                                 cchSrc,
                                 pDest,
                                 cchDest,
                                 NULL,
                                 pCase ));

            break;
        }
        case ( LCMAP_HIRAGANA | LCMAP_HALFWIDTH ) :
        {
            //
            //  This combination of flags is strange, because
            //  Hiragana is only full width.  So, the Hiragana flag
            //  is the most important.  Full width Katakana will be
            //  mapped to full width Hiragana, not half width
            //  Katakana.
            //
            //  Map to Hiragana, then Half Width.
            //
            return (MapHalfKana( pSrc,
                                 cchSrc,
                                 pDest,
                                 cchDest,
                                 pTblPtrs->pHiragana,
                                 pCase ));

            break;
        }
        case ( LCMAP_HIRAGANA | LCMAP_FULLWIDTH ) :
        {
            //
            //  Since Hiragana is only FULL width, the mapping to
            //  width must be done first to convert all half width
            //  Katakana to full width Katakana before trying to
            //  map to Hiragana.
            //
            //  Map to Full Width, then Hiragana.
            //
            return (MapFullKana( pSrc,
                                 cchSrc,
                                 pDest,
                                 cchDest,
                                 pTblPtrs->pHiragana,
                                 pCase ));

            break;
        }
        case ( LCMAP_KATAKANA | LCMAP_HALFWIDTH ) :
        {
            //
            //  Since Hiragana is only FULL width, the mapping to
            //  Katakana must be done first to convert all Hiragana
            //  to Katakana before trying to map to half width.
            //
            //  Map to Katakana, then Half Width.
            //
            return (MapHalfKana( pSrc,
                                 cchSrc,
                                 pDest,
                                 cchDest,
                                 pTblPtrs->pKatakana,
                                 pCase ));

            break;
        }
        case ( LCMAP_KATAKANA | LCMAP_FULLWIDTH ) :
        {
            //
            //  Since Hiragana is only FULL width, it doesn't matter
            //  which way the mapping is done for this combination.
            //
            //  Map to Full Width, then Katakana.
            //
            return (MapFullKana( pSrc,
                                 cchSrc,
                                 pDest,
                                 cchDest,
                                 pTblPtrs->pKatakana,
                                 pCase ));

            break;
        }
        default :
        {
            //
            //  Return error.
            //
            return (0);
        }
    }
}


////////////////////////////////////////////////////////////////////////////
//
//  MapHalfKana
//
//  Stores the result of the half width and Kana mapping for the given string
//  in the destination buffer, and returns the number of wide characters
//  written to the buffer.
//
//  This first converts the precomposed characters to their composite forms,
//  and then maps all characters to their half width forms.  This handles the
//  case where the full width precomposed form should map to TWO half width
//  code points (composite form).  The half width precomposed forms do not
//  exist in Unicode.
//
//  11-04-93    JulieB    Created.
////////////////////////////////////////////////////////////////////////////

int MapHalfKana(
    LPCWSTR pSrc,
    int cchSrc,
    LPWSTR pDest,
    int cchDest,
    PKANA pKana,
    PCASE pCase)
{
    int Count;                    // count of characters written
    int ctr = 0;                  // loop counter
    int ct;                       // loop counter
    LPWSTR pBuf;                  // ptr to destination buffer
    LPWSTR pEndBuf;               // ptr to end of destination buffer
    LPWSTR pPosDest;              // ptr to position in destination buffer
    LPWSTR *ppIncr;               // points to ptr to increment
    WCHAR pTmp[MAX_COMPOSITE];    // ptr to temporary buffer
    LPWSTR pEndTmp;               // ptr to end of temporary buffer


    //
    //  Initialize the destination pointers.
    //
    pEndTmp = pTmp + MAX_COMPOSITE;
    if (cchDest == 0)
    {
        //
        //  Do not touch the pDest pointer.  Use the pTmp buffer and
        //  initialize the end pointer.
        //
        pBuf = pTmp;
        pEndBuf = pEndTmp;

        //
        //  This is a bogus pointer and will never be touched.  It just
        //  increments this pointer into oblivion.
        //
        pDest = pBuf;
        ppIncr = &pDest;
    }
    else
    {
        //
        //  Initialize the pointers.  Use the pDest buffer.
        //
        pBuf = pDest;
        pEndBuf = pBuf + cchDest;
        ppIncr = &pBuf;
    }

    //
    //  Search through the source string.  Convert all precomposed
    //  forms to their composite form before converting to half width.
    //
    while ((ctr < cchSrc) && (pBuf < pEndBuf))
    {
        //
        //  Get the character to convert.  If we need to convert to
        //  kana, do it.
        //
        if (pKana)
        {
            *pTmp = GET_KANA(pKana, pSrc[ctr]);
        }
        else
        {
            *pTmp = pSrc[ctr];
        }

        //
        //  Convert to its composite form (if exists).
        //
        //  NOTE: Must use the tmp buffer in case the destination buffer
        //        isn't large enough to hold the composite form.
        //
        Count = InsertCompositeForm(pTmp, pEndTmp);

        //
        //  Convert to half width (if exists) and case (if appropriate).
        //
        pPosDest = pTmp;
        if (pCase)
        {
            for (ct = Count; ct > 0; ct--)
            {
                *pPosDest = GET_HALF_WIDTH(pTblPtrs->pHalfWidth, *pPosDest);

                *pPosDest = GET_LOWER_UPPER_CASE(pCase, *pPosDest);

                pPosDest++;
            }
        }
        else
        {
            for (ct = Count; ct > 0; ct--)
            {
                *pPosDest = GET_HALF_WIDTH(pTblPtrs->pHalfWidth, *pPosDest);
                pPosDest++;
            }
        }

        //
        //  Convert back to its precomposed form (if exists).
        //
        if (Count > 1)
        {
            //
            //  Get the precomposed form.
            //
            //  ct is the number of code points used from the
            //  composite form.
            //
            ct = InsertPreComposedForm(pTmp, pPosDest, pBuf);
            if (ct > 1)
            {
                //
                //  Precomposed form was found.  Need to make sure all
                //  of the composite chars were used.
                //
                if (ct == Count)
                {
                    //
                    //  All composite chars were used.  Increment by 1.
                    //
                    (*ppIncr)++;
                }
                else
                {
                    //
                    //  Not all composite chars were used.  Need to copy
                    //  the rest of the composite chars from the tmp buffer
                    //  to the destination buffer.
                    //
                    (*ppIncr)++;
                    Count -= ct;
                    if (pBuf + Count > pEndBuf)
                    {
                        break;
                    }
                    RtlMoveMemory(pBuf, pTmp + ct, Count * sizeof(WCHAR));
                    (*ppIncr) += Count;
                }
            }
            else
            {
                //
                //  Precomposed form was NOT found.  Need to copy the
                //  composite form from the tmp buffer to the destination
                //  buffer.
                //
                if (pBuf + Count > pEndBuf)
                {
                    break;
                }
                RtlMoveMemory(pBuf, pTmp, Count * sizeof(WCHAR));
                (*ppIncr) += Count;
            }
        }
        else
        {
            //
            //  Only one character (no composite form), so just copy it
            //  from the tmp buffer to the destination buffer.
            //
            *pBuf = *pTmp;
            (*ppIncr)++;
        }

        ctr++;
    }

    //
    //  Return the appropriate number of characters.
    //
    if (cchDest == 0)
    {
        //
        //  Return the number of characters written to the buffer.
        //
        return ((int)((*ppIncr) - pTmp));
    }
    else
    {
        //
        //  Make sure the given buffer was large enough to hold the
        //  mapping.
        //
        if (ctr < cchSrc)
        {
            SetLastError(ERROR_INSUFFICIENT_BUFFER);
            return (0);
        }

        //
        //  Return the number of characters written to the buffer.
        //
        return ((int)((*ppIncr) - pDest));
    }
}


////////////////////////////////////////////////////////////////////////////
//
//  MapFullKana
//
//  Stores the result of the full width and Kana mapping for the given string
//  in the destination buffer, and returns the number of wide characters
//  written to the buffer.
//
//  This first converts the characters to full width, and then maps all
//  composite characters to their precomposed forms.  This handles the case
//  where the half width composite form (TWO code points) should map to a
//  full width precomposed form (ONE full width code point).  The half
//  width precomposed forms do not exist in Unicode and we need the full
//  width precomposed forms to round trip with the TWO half width code
//  points.
//
//  11-04-93    JulieB    Created.
////////////////////////////////////////////////////////////////////////////

int MapFullKana(
    LPCWSTR pSrc,
    int cchSrc,
    LPWSTR pDest,
    int cchDest,
    PKANA pKana,
    PCASE pCase)
{
    int Count;                    // count of characters
    LPWSTR pPosSrc;               // ptr to position in source buffer
    LPWSTR pEndSrc;               // ptr to end of source buffer
    LPWSTR pBuf;                  // ptr to destination buffer
    LPWSTR pEndBuf;               // ptr to end of destination buffer
    LPWSTR *ppIncr;               // points to ptr to increment
    WCHAR pTmp[MAX_COMPOSITE];    // ptr to temporary buffer


    //
    //  Initialize source string pointers.
    //
    pPosSrc = (LPWSTR)pSrc;
    pEndSrc = pPosSrc + cchSrc;

    //
    //  Initialize the destination pointers.
    //
    if (cchDest == 0)
    {
        //
        //  Do not touch the pDest pointer.  Use the pTmp buffer and
        //  initialize the end pointer.
        //
        pBuf = pTmp;
        pEndBuf = pTmp + MAX_COMPOSITE;

        //
        //  This is a bogus pointer and will never be touched.  It just
        //  increments this pointer into oblivion.
        //
        pDest = pBuf;
        ppIncr = &pDest;
    }
    else
    {
        //
        //  Initialize the pointers.  Use the pDest buffer.
        //
        pBuf = pDest;
        pEndBuf = pBuf + cchDest;
        ppIncr = &pBuf;
    }

    //
    //  Search through the source string.  Convert all composite
    //  forms to their precomposed form before converting to full width.
    //
    while ((pPosSrc < pEndSrc) && (pBuf < pEndBuf))
    {
        //
        //  Convert a composite form to its full width precomposed
        //  form (if exists).  Also, convert to case if necessary.
        //
        Count = InsertFullWidthPreComposedForm( pPosSrc,
                                                pEndSrc,
                                                pBuf,
                                                pCase );
        pPosSrc += Count;

        //
        //  Convert to kana if necessary.
        //
        if (pKana)
        {
            *pBuf = GET_KANA(pKana, *pBuf);
        }

        //
        //  Increment the destination pointer.
        //
        (*ppIncr)++;
    }

    //
    //  Return the appropriate number of characters.
    //
    if (cchDest == 0)
    {
        //
        //  Return the number of characters written to the buffer.
        //
        return ((int)((*ppIncr) - pTmp));
    }
    else
    {
        //
        //  Make sure the given buffer was large enough to hold the
        //  mapping.
        //
        if (pPosSrc < pEndSrc)
        {
            SetLastError(ERROR_INSUFFICIENT_BUFFER);
            return (0);
        }

        //
        //  Return the number of characters written to the buffer.
        //
        return ((int)((*ppIncr) - pDest));
    }
}


////////////////////////////////////////////////////////////////////////////
//
//  MapTraditionalSimplified
//
//  Stores the appropriate Traditional or Simplified Chinese values in the
//  destination buffer, and returns the number of wide characters
//  written to the buffer.
//
//  05-07-96    JulieB    Created.
////////////////////////////////////////////////////////////////////////////

int MapTraditionalSimplified(
    PLOC_HASH pHashN,
    DWORD dwFlags,
    LPCWSTR pSrc,
    int cchSrc,
    LPWSTR pDest,
    int cchDest,
    PCHINESE pChinese)
{
    int ctr;                 // loop counter
    PCASE pCase;             // ptr to case table (if case flag is set)


    //
    //  If the destination value is zero, then just return the
    //  length of the source string.  Do NOT touch pDest.
    //
    if (cchDest == 0)
    {
        return (cchSrc);
    }

    //
    //  If cchSrc is greater than cchDest, then the destination buffer
    //  is too small to hold the new string.  Return an error.
    //
    if (cchSrc > cchDest)
    {
        SetLastError(ERROR_INSUFFICIENT_BUFFER);
        return (0);
    }

    //
    //  See if lower or upper case flags are present.
    //
    if (dwFlags & LCMAP_LOWERCASE)
    {
        pCase = (dwFlags & LCMAP_LINGUISTIC_CASING)
                    ? pHashN->pLowerLinguist
                    : pHashN->pLowerCase;
    }
    else if (dwFlags & LCMAP_UPPERCASE)
    {
        pCase = (dwFlags & LCMAP_LINGUISTIC_CASING)
                    ? pHashN->pUpperLinguist
                    : pHashN->pUpperCase;
    }
    else
    {
        pCase = NULL;
    }

    //
    //  Map to Traditional/Simplified and store it in the destination string.
    //  Also map the case, if appropriate.
    //
    if (pCase)
    {
        for (ctr = 0; ctr < cchSrc; ctr++)
        {
            pDest[ctr] = GET_CHINESE(pChinese, pSrc[ctr]);

            pDest[ctr] = GET_LOWER_UPPER_CASE(pCase, pDest[ctr]);
        }
    }
    else
    {
        for (ctr = 0; ctr < cchSrc; ctr++)
        {
            pDest[ctr] = GET_CHINESE(pChinese, pSrc[ctr]);
        }
    }

    //
    //  Return the number of wide characters written.
    //
    return (ctr);
}
