//-------------------------------------------------------------------------------------- // File: DXUTUniBuffer.cpp // // Desc: CUniBuffer, the Uniscribe-backed text buffer behind the edit controls // // Copyright (c) Microsoft Corporation. All rights reserved. //-------------------------------------------------------------------------------------- #include "dxstdafx.h" // omits operator delete's throw(), which forces unwind frames into // every dtor that deletes #include #include "DXUTgui.h" #define UNISCRIBE_DLLNAME "\\usp10.dll" #define GETPROCADDRESS( Module, APIName, Temp ) \ Temp = GetProcAddress( Module, #APIName ); \ if( Temp ) \ *(FARPROC*)&_##APIName = Temp #define PLACEHOLDERPROC( APIName ) \ _##APIName = Dummy_##APIName // DXUT_MAX_EDITBOXLENGTH is the maximum string length allowed in edit boxes, // including the NULL terminator. // // Uniscribe does not support strings having bigger-than-16-bits length. // This means that the string must be less than 65536 characters long, // including the NULL terminator. #define DXUT_MAX_EDITBOXLENGTH 0xFFFF // stays in DXUTgui.cpp; retail reaches it across the tu boundary LPCWSTR MaskText( LPCWSTR wszText ); // Static member initialization HINSTANCE CUniBuffer::s_hDll = NULL; HRESULT (WINAPI *CUniBuffer::_ScriptApplyDigitSubstitution)( const SCRIPT_DIGITSUBSTITUTE*, SCRIPT_CONTROL*, SCRIPT_STATE* ) = Dummy_ScriptApplyDigitSubstitution; HRESULT (WINAPI *CUniBuffer::_ScriptStringAnalyse)( HDC, const void *, int, int, int, DWORD, int, SCRIPT_CONTROL*, SCRIPT_STATE*, const int*, SCRIPT_TABDEF*, const BYTE*, SCRIPT_STRING_ANALYSIS* ) = Dummy_ScriptStringAnalyse; HRESULT (WINAPI *CUniBuffer::_ScriptStringCPtoX)( SCRIPT_STRING_ANALYSIS, int, BOOL, int* ) = Dummy_ScriptStringCPtoX; HRESULT (WINAPI *CUniBuffer::_ScriptStringXtoCP)( SCRIPT_STRING_ANALYSIS, int, int*, int* ) = Dummy_ScriptStringXtoCP; HRESULT (WINAPI *CUniBuffer::_ScriptStringFree)( SCRIPT_STRING_ANALYSIS* ) = Dummy_ScriptStringFree; const SCRIPT_LOGATTR* (WINAPI *CUniBuffer::_ScriptString_pLogAttr)( SCRIPT_STRING_ANALYSIS ) = Dummy_ScriptString_pLogAttr; const int* (WINAPI *CUniBuffer::_ScriptString_pcOutChars)( SCRIPT_STRING_ANALYSIS ) = Dummy_ScriptString_pcOutChars; //-------------------------------------------------------------------------------------- void CUniBuffer::Initialize() { if( s_hDll ) // Only need to do once return; TCHAR wszPath[MAX_PATH+1]; if( !::GetSystemDirectory( wszPath, MAX_PATH+1 ) ) return; // Verify whether it is safe to concatenate these strings int len1 = lstrlen(wszPath); int len2 = lstrlen(UNISCRIBE_DLLNAME); if (len1 + len2 > MAX_PATH) { return; } // We have verified that the concatenated string will fit into wszPath, // so it is safe to concatenate them. StringCchCat( wszPath, MAX_PATH, UNISCRIBE_DLLNAME ); s_hDll = LoadLibrary( wszPath ); if( s_hDll ) { FARPROC Temp; GETPROCADDRESS( s_hDll, ScriptApplyDigitSubstitution, Temp ); GETPROCADDRESS( s_hDll, ScriptStringAnalyse, Temp ); GETPROCADDRESS( s_hDll, ScriptStringCPtoX, Temp ); GETPROCADDRESS( s_hDll, ScriptStringXtoCP, Temp ); GETPROCADDRESS( s_hDll, ScriptStringFree, Temp ); GETPROCADDRESS( s_hDll, ScriptString_pLogAttr, Temp ); GETPROCADDRESS( s_hDll, ScriptString_pcOutChars, Temp ); } } //-------------------------------------------------------------------------------------- void CUniBuffer::Uninitialize() { if( s_hDll ) { PLACEHOLDERPROC( ScriptApplyDigitSubstitution ); PLACEHOLDERPROC( ScriptStringAnalyse ); PLACEHOLDERPROC( ScriptStringCPtoX ); PLACEHOLDERPROC( ScriptStringXtoCP ); PLACEHOLDERPROC( ScriptStringFree ); PLACEHOLDERPROC( ScriptString_pLogAttr ); PLACEHOLDERPROC( ScriptString_pcOutChars ); FreeLibrary( s_hDll ); s_hDll = NULL; } } //-------------------------------------------------------------------------------------- bool CUniBuffer::SetBufferSize( int nNewSize ) { // If the current size is already the maximum allowed, // we can't possibly allocate more. if( m_nBufferSize == DXUT_MAX_EDITBOXLENGTH ) return false; int nAllocateSize = ( nNewSize == -1 || nNewSize < m_nBufferSize * 2 ) ? ( m_nBufferSize ? m_nBufferSize * 2 : 256 ) : nNewSize * 2; // Cap the buffer size at the maximum allowed. if( nAllocateSize > DXUT_MAX_EDITBOXLENGTH ) nAllocateSize = DXUT_MAX_EDITBOXLENGTH; WCHAR *pTempBuffer = new WCHAR[nAllocateSize]; if( !pTempBuffer ) return false; if( m_pwszBuffer ) { CopyMemory( pTempBuffer, m_pwszBuffer, m_nBufferSize * sizeof(WCHAR) ); delete[] m_pwszBuffer; } else { ZeroMemory( pTempBuffer, sizeof(WCHAR) * nAllocateSize ); } m_pwszBuffer = pTempBuffer; m_nBufferSize = nAllocateSize; return true; } //-------------------------------------------------------------------------------------- // Uniscribe -- Analyse() analyses the string in the buffer //-------------------------------------------------------------------------------------- HRESULT CUniBuffer::Analyse() { if( m_Analysis ) _ScriptStringFree( &m_Analysis ); SCRIPT_CONTROL ScriptControl; // For uniscribe SCRIPT_STATE ScriptState; // For uniscribe ZeroMemory( &ScriptControl, sizeof(ScriptControl) ); ZeroMemory( &ScriptState, sizeof(ScriptState) ); _ScriptApplyDigitSubstitution ( NULL, &ScriptControl, &ScriptState ); if( !m_pFontNode ) return E_FAIL; HRESULT hr; if( !field_12 ) { hr = _ScriptStringAnalyse( m_pFontNode->pFont ? m_pFontNode->pFont->GetDC() : NULL, m_pwszBuffer, lstrlenW( m_pwszBuffer ) + 1, // NULL is also analyzed. lstrlenW( m_pwszBuffer ) * 3 / 2 + 16, -1, SSA_BREAK | SSA_GLYPHS | SSA_FALLBACK | SSA_LINK, 0, &ScriptControl, &ScriptState, NULL, NULL, NULL, &m_Analysis ); } else { hr = _ScriptStringAnalyse( m_pFontNode->pFont ? m_pFontNode->pFont->GetDC() : NULL, MaskText( m_pwszBuffer ), lstrlenW( MaskText( m_pwszBuffer ) ) + 1, // NULL is also analyzed. lstrlenW( MaskText( m_pwszBuffer ) ) * 3 / 2 + 16, -1, SSA_BREAK | SSA_GLYPHS | SSA_FALLBACK | SSA_LINK, 0, &ScriptControl, &ScriptState, NULL, NULL, NULL, &m_Analysis ); } if( SUCCEEDED( hr ) ) m_bAnalyseRequired = false; // Analysis is up-to-date return hr; } //-------------------------------------------------------------------------------------- CUniBuffer::CUniBuffer( int nInitialSize ) { CUniBuffer::Initialize(); // ensure static vars are properly init'ed first m_nBufferSize = 0; m_pwszBuffer = NULL; m_bAnalyseRequired = true; m_Analysis = NULL; m_pFontNode = NULL; field_8 = 0; field_12 = 0; if( nInitialSize > 0 ) SetBufferSize( nInitialSize ); } //-------------------------------------------------------------------------------------- CUniBuffer::~CUniBuffer() { delete[] m_pwszBuffer; if( m_Analysis ) _ScriptStringFree( &m_Analysis ); } //-------------------------------------------------------------------------------------- WCHAR& CUniBuffer::operator[]( int n ) // No param checking { // This version of operator[] is called only // if we are asking for write access, so // re-analysis is required. m_bAnalyseRequired = true; return m_pwszBuffer[n]; } //-------------------------------------------------------------------------------------- void CUniBuffer::Clear() { *m_pwszBuffer = L'\0'; m_bAnalyseRequired = true; } //-------------------------------------------------------------------------------------- // Inserts the char at specified index. // If nIndex == -1, insert to the end. //-------------------------------------------------------------------------------------- bool CUniBuffer::InsertChar( int nIndex, WCHAR tchr ) { assert( nIndex >= 0 ); if( nIndex < 0 || nIndex > lstrlenW( m_pwszBuffer ) ) return false; // invalid index // Check for maximum length allowed if( GetTextSize() + 1 >= DXUT_MAX_EDITBOXLENGTH ) return false; if( lstrlenW( m_pwszBuffer ) + 1 >= m_nBufferSize ) { if( !SetBufferSize( -1 ) ) return false; // out of memory } assert( m_nBufferSize >= 2 ); // Shift the characters after the index, start by copying the null terminator WCHAR* dest = m_pwszBuffer + lstrlenW(m_pwszBuffer)+1; WCHAR* stop = m_pwszBuffer + nIndex; WCHAR* src = dest - 1; while( dest > stop ) { *dest-- = *src--; } // Set new character m_pwszBuffer[ nIndex ] = tchr; m_bAnalyseRequired = true; return true; } bool CUniBuffer::InsertChar(int nIndex, CHAR tchr) { WCHAR WideCharStr[2]; ZeroMemory(&WideCharStr[0], sizeof(WideCharStr)); if(field_8) { CHAR MultiByteStr[3]; MultiByteStr[0] = field_8; MultiByteStr[1] = tchr; MultiByteStr[2] = 0; MultiByteToWideChar(CP_ACP, 0, MultiByteStr, 2, WideCharStr, 1); InsertChar(nIndex, WideCharStr[0]); field_8 = 0; return true; } else if(IsDBCSLeadByteEx(CP_ACP, tchr)) { field_8 = tchr; return false; } else { MultiByteToWideChar(CP_ACP, 0, &tchr, 1, WideCharStr, 1); InsertChar(nIndex, WideCharStr[0]); return true; } } // overwrite variant of InsertChar; the buffer index assignment is samp's, not a typo of ours bool CUniBuffer::SetChar(int nIndex, CHAR tchr) { WCHAR WideCharStr[2]; ZeroMemory(&WideCharStr[0], sizeof(WideCharStr)); if(field_8) { CHAR MultiByteStr[3]; MultiByteStr[0] = field_8; MultiByteStr[1] = tchr; MultiByteStr[2] = 0; MultiByteToWideChar(CP_ACP, 0, MultiByteStr, 2, WideCharStr, 1); InsertChar(nIndex, WideCharStr[0]); field_8 = 0; return true; } else if(IsDBCSLeadByteEx(CP_ACP, tchr)) { field_8 = tchr; return false; } else { MultiByteToWideChar(CP_ACP, 0, &tchr, 1, WideCharStr, 1); this[nIndex] = WideCharStr[0]; return true; } } //-------------------------------------------------------------------------------------- // Removes the char at specified index. // If nIndex == -1, remove the last char. //-------------------------------------------------------------------------------------- bool CUniBuffer::RemoveChar( int nIndex ) { if( !lstrlenW( m_pwszBuffer ) || nIndex < 0 || nIndex >= lstrlenW( m_pwszBuffer ) ) return false; // Invalid index MoveMemory( m_pwszBuffer + nIndex, m_pwszBuffer + nIndex + 1, sizeof(WCHAR) * ( lstrlenW( m_pwszBuffer ) - nIndex ) ); m_bAnalyseRequired = true; return true; } //-------------------------------------------------------------------------------------- // Inserts the first nCount characters of the string pStr at specified index. // If nCount == -1, the entire string is inserted. // If nIndex == -1, insert to the end. //-------------------------------------------------------------------------------------- bool CUniBuffer::InsertString( int nIndex, const WCHAR *pStr, int nCount ) { assert( nIndex >= 0 ); if( nIndex > lstrlenW( m_pwszBuffer ) ) return false; // invalid index if( -1 == nCount ) nCount = lstrlenW( pStr ); // Check for maximum length allowed if( GetTextSize() + nCount >= DXUT_MAX_EDITBOXLENGTH ) return false; if( lstrlenW( m_pwszBuffer ) + nCount >= m_nBufferSize ) { if( !SetBufferSize( lstrlenW( m_pwszBuffer ) + nCount + 1 ) ) return false; // out of memory } MoveMemory( m_pwszBuffer + nIndex + nCount, m_pwszBuffer + nIndex, sizeof(WCHAR) * ( lstrlenW( m_pwszBuffer ) - nIndex + 1 ) ); CopyMemory( m_pwszBuffer + nIndex, pStr, nCount * sizeof(WCHAR) ); m_bAnalyseRequired = true; return true; } //-------------------------------------------------------------------------------------- bool CUniBuffer::SetText( LPCWSTR wszText ) { assert( wszText != NULL ); int nRequired = int(lstrlenW( wszText ) + 1); // Check for maximum length allowed if( nRequired >= DXUT_MAX_EDITBOXLENGTH ) return false; while( GetBufferSize() < nRequired ) if( !SetBufferSize( -1 ) ) break; // Check again in case out of memory occurred inside while loop. if( GetBufferSize() >= nRequired ) { StringCchCopyW( m_pwszBuffer, GetBufferSize(), wszText ); m_bAnalyseRequired = true; return true; } else return false; } //-------------------------------------------------------------------------------------- void ConvertAnsiToWide(LPCSTR wszSrc, LPWSTR wszDest, int nSize) { memset(wszDest, 0, sizeof(WCHAR) * nSize); int nCount = MultiByteToWideChar(CP_ACP, 0, wszSrc, strlen(wszSrc), NULL, 0); if(nCount < nSize) MultiByteToWideChar(CP_ACP, 0, wszSrc, strlen(wszSrc), wszDest, nCount); } //-------------------------------------------------------------------------------------- bool CUniBuffer::SetText( LPCTSTR wszText ) { if(lstrlenA(wszText) > 1024) return false; WCHAR szBuffer[2049]; ConvertAnsiToWide(wszText, szBuffer, 2048); SetText(szBuffer); return true; } //-------------------------------------------------------------------------------------- HRESULT CUniBuffer::CPtoX( int nCP, BOOL bTrail, int *pX ) { assert( pX ); *pX = 0; // Default HRESULT hr = S_OK; if( m_bAnalyseRequired ) hr = Analyse(); if( SUCCEEDED( hr ) ) hr = _ScriptStringCPtoX( m_Analysis, nCP, bTrail, pX ); return hr; } //-------------------------------------------------------------------------------------- HRESULT CUniBuffer::XtoCP( int nX, int *pCP, int *pnTrail ) { assert( pCP && pnTrail ); *pCP = 0; *pnTrail = FALSE; // Default HRESULT hr = S_OK; if( m_bAnalyseRequired ) hr = Analyse(); if( SUCCEEDED( hr ) ) hr = _ScriptStringXtoCP( m_Analysis, nX, pCP, pnTrail ); // If the coordinate falls outside the text region, we // can get character positions that don't exist. We must // filter them here and convert them to those that do exist. if( *pCP == -1 && *pnTrail == TRUE ) { *pCP = 0; *pnTrail = FALSE; } else if( *pCP > lstrlenW( m_pwszBuffer ) && *pnTrail == FALSE ) { *pCP = lstrlenW( m_pwszBuffer ); *pnTrail = TRUE; } return hr; } //-------------------------------------------------------------------------------------- void CUniBuffer::GetPriorItemPos( int nCP, int *pPrior ) { *pPrior = nCP; // Default is the char itself if( m_bAnalyseRequired ) if( FAILED( Analyse() ) ) return; const SCRIPT_LOGATTR *pLogAttr = _ScriptString_pLogAttr( m_Analysis ); if( !pLogAttr ) return; if( !_ScriptString_pcOutChars( m_Analysis ) ) return; int nInitial = *_ScriptString_pcOutChars( m_Analysis ); if( nCP - 1 < nInitial ) nInitial = nCP - 1; for( int i = nInitial; i > 0; --i ) if( pLogAttr[i].fWordStop || // Either the fWordStop flag is set ( !pLogAttr[i].fWhiteSpace && // Or the previous char is whitespace but this isn't. pLogAttr[i-1].fWhiteSpace ) ) { *pPrior = i; return; } // We have reached index 0. 0 is always a break point, so simply return it. *pPrior = 0; } //-------------------------------------------------------------------------------------- void CUniBuffer::GetNextItemPos( int nCP, int *pPrior ) { *pPrior = nCP; // Default is the char itself HRESULT hr = S_OK; if( m_bAnalyseRequired ) hr = Analyse(); if( FAILED( hr ) ) return; const SCRIPT_LOGATTR *pLogAttr = _ScriptString_pLogAttr( m_Analysis ); if( !pLogAttr ) return; if( !_ScriptString_pcOutChars( m_Analysis ) ) return; int nInitial = *_ScriptString_pcOutChars( m_Analysis ); if( nCP + 1 < nInitial ) nInitial = nCP + 1; for( int i = nInitial; i < *_ScriptString_pcOutChars( m_Analysis ) - 1; ++i ) { if( pLogAttr[i].fWordStop ) // Either the fWordStop flag is set { *pPrior = i; return; } else if( pLogAttr[i].fWhiteSpace && // Or this whitespace but the next char isn't. !pLogAttr[i+1].fWhiteSpace ) { *pPrior = i+1; // The next char is a word stop return; } } // We have reached the end. It's always a word stop, so simply return it. *pPrior = *_ScriptString_pcOutChars( m_Analysis ) - 1; }