1 /* 2 * PROJECT: ReactOS LPK 3 * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+) 4 * PURPOSE: Language Pack DLL. 5 * PROGRAMMERS: Magnus Olsen (greatlrd) 6 * Baruch Rutman (peterooch at gmail dot com) 7 */ 8 9 #include "ros_lpk.h" 10 11 WINE_DEFAULT_DEBUG_CHANNEL(bidi); 12 13 LPK_LPEDITCONTROL_LIST LpkEditControl = {EditCreate, EditIchToXY, EditMouseToIch, EditCchInWidth, 14 EditGetLineWidth, EditDrawText, EditHScroll, EditMoveSelection, 15 EditVerifyText, EditNextWord, EditSetMenu, EditProcessMenu, 16 EditCreateCaret, EditAdjustCaret}; 17 18 #define PREFIX 38 19 #define ALPHA_PREFIX 30 /* Win16: Alphabet prefix */ 20 #define KANA_PREFIX 31 /* Win16: Katakana prefix */ 21 22 static int PSM_FindLastPrefix(LPCWSTR str, int count) 23 { 24 int i, prefix_count = 0, index = -1; 25 26 for (i = 0; i < count - 1; i++) 27 { 28 if (str[i] == PREFIX && str[i + 1] != PREFIX) 29 { 30 index = i - prefix_count; 31 prefix_count++; 32 } 33 else if (str[i] == PREFIX && str[i + 1] == PREFIX) 34 { 35 i++; 36 } 37 } 38 return index; 39 } 40 41 static void PSM_PrepareToDraw(LPCWSTR str, INT count, LPWSTR new_str, LPINT new_count) 42 { 43 int len, i = 0, j = 0; 44 45 while (i < count) 46 { 47 if (str[i] == PREFIX || (iswspace(str[i]) && str[i] != L' ')) 48 { 49 if (i < count - 1 && str[i + 1] == PREFIX) 50 new_str[j++] = str[i++]; 51 else 52 i++; 53 } 54 else 55 { 56 new_str[j++] = str[i++]; 57 } 58 } 59 60 new_str[j] = L'\0'; 61 len = wcslen(new_str); 62 *new_count = len; 63 } 64 65 /* Can be used with also LpkDrawTextEx() if it will be implemented */ 66 static void LPK_DrawUnderscore(HDC hdc, int x, int y, LPCWSTR str, int count, int offset) 67 { 68 SCRIPT_STRING_ANALYSIS ssa; 69 DWORD dwSSAFlags = SSA_GLYPHS; 70 int prefix_x; 71 int prefix_end; 72 int pos; 73 SIZE size; 74 HPEN hpen; 75 HPEN oldPen; 76 HRESULT hr = S_FALSE; 77 78 if (offset == -1) 79 return; 80 81 if (ScriptIsComplex(str, count, SIC_COMPLEX) == S_OK) 82 { 83 if (GetLayout(hdc) & LAYOUT_RTL || GetTextAlign(hdc) & TA_RTLREADING) 84 dwSSAFlags |= SSA_RTL; 85 86 hr = ScriptStringAnalyse(hdc, str, count, (3 * count / 2 + 16), 87 -1, dwSSAFlags, -1, NULL, NULL, NULL, NULL, NULL, &ssa); 88 } 89 90 if (hr == S_OK) 91 { 92 ScriptStringCPtoX(ssa, offset, FALSE, &pos); 93 prefix_x = x + pos; 94 ScriptStringCPtoX(ssa, offset, TRUE, &pos); 95 prefix_end = x + pos; 96 ScriptStringFree(&ssa); 97 } 98 else 99 { 100 GetTextExtentPointW(hdc, str, offset, &size); 101 prefix_x = x + size.cx; 102 GetTextExtentPointW(hdc, str, offset + 1, &size); 103 prefix_end = x + size.cx - 1; 104 } 105 hpen = CreatePen(PS_SOLID, 1, GetTextColor(hdc)); 106 oldPen = SelectObject(hdc, hpen); 107 MoveToEx(hdc, prefix_x, y, NULL); 108 LineTo(hdc, prefix_end, y); 109 SelectObject(hdc, oldPen); 110 DeleteObject(hpen); 111 } 112 113 /* Code taken from the GetProcessDefaultLayout() function from Wine's user32 114 * Wine version 3.17 115 * 116 * This function should be called from LpkInitialize(), 117 * which is in turn called by GdiInitializeLanguagePack() (from gdi32). 118 * TODO: Move call from LpkDllInitialize() to LpkInitialize() when latter 119 * function is implemented. 120 */ 121 static void LPK_ApplyMirroring() 122 { 123 static const WCHAR translationW[] = { '\\','V','a','r','F','i','l','e','I','n','f','o', 124 '\\','T','r','a','n','s','l','a','t','i','o','n', 0 }; 125 static const WCHAR filedescW[] = { '\\','S','t','r','i','n','g','F','i','l','e','I','n','f','o', 126 '\\','%','0','4','x','%','0','4','x', 127 '\\','F','i','l','e','D','e','s','c','r','i','p','t','i','o','n',0 }; 128 WCHAR *str, buffer[MAX_PATH]; 129 #ifdef __REACTOS__ 130 DWORD i, version_layout = 0; 131 UINT len; 132 #else 133 DWORD i, len, version_layout = 0; 134 #endif 135 DWORD user_lang = GetUserDefaultLangID(); 136 DWORD *languages; 137 void *data = NULL; 138 139 GetModuleFileNameW( 0, buffer, MAX_PATH ); 140 if (!(len = GetFileVersionInfoSizeW( buffer, NULL ))) goto done; 141 if (!(data = HeapAlloc( GetProcessHeap(), 0, len ))) goto done; 142 if (!GetFileVersionInfoW( buffer, 0, len, data )) goto done; 143 if (!VerQueryValueW( data, translationW, (void **)&languages, &len ) || !len) goto done; 144 145 len /= sizeof(DWORD); 146 for (i = 0; i < len; i++) if (LOWORD(languages[i]) == user_lang) break; 147 if (i == len) /* try neutral language */ 148 for (i = 0; i < len; i++) 149 if (LOWORD(languages[i]) == MAKELANGID( PRIMARYLANGID(user_lang), SUBLANG_NEUTRAL )) break; 150 if (i == len) i = 0; /* default to the first one */ 151 152 sprintfW( buffer, filedescW, LOWORD(languages[i]), HIWORD(languages[i]) ); 153 if (!VerQueryValueW( data, buffer, (void **)&str, &len )) goto done; 154 TRACE( "found description %s\n", debugstr_w( str )); 155 if (str[0] == 0x200e && str[1] == 0x200e) version_layout = LAYOUT_RTL; 156 157 done: 158 HeapFree( GetProcessHeap(), 0, data ); 159 SetProcessDefaultLayout(version_layout); 160 } 161 162 BOOL 163 WINAPI 164 DllMain( 165 HANDLE hDll, 166 DWORD dwReason, 167 LPVOID lpReserved) 168 { 169 170 return LpkDllInitialize(hDll,dwReason,lpReserved); 171 } 172 173 BOOL 174 WINAPI 175 LpkDllInitialize( 176 HANDLE hDll, 177 DWORD dwReason, 178 LPVOID lpReserved) 179 { 180 switch(dwReason) 181 { 182 case DLL_PROCESS_ATTACH: 183 DisableThreadLibraryCalls(hDll); 184 /* Tell usp10 it is activated usp10 */ 185 //LpkPresent(); 186 LPK_ApplyMirroring(); 187 break; 188 189 default: 190 break; 191 } 192 193 return TRUE; 194 } 195 196 197 /* 198 * @implemented 199 */ 200 BOOL 201 WINAPI 202 LpkExtTextOut( 203 HDC hdc, 204 int x, 205 int y, 206 UINT fuOptions, 207 const RECT *lprc, 208 LPCWSTR lpString, 209 UINT uCount, 210 const INT *lpDx, 211 INT unknown) 212 { 213 LPWORD glyphs = NULL; 214 LPWSTR reordered_str = NULL; 215 INT cGlyphs; 216 DWORD dwSICFlags = SIC_COMPLEX; 217 BOOL bResult, bReorder; 218 219 UNREFERENCED_PARAMETER(unknown); 220 221 fuOptions |= ETO_IGNORELANGUAGE; 222 223 /* Check text direction */ 224 if ((GetLayout(hdc) & LAYOUT_RTL) || (GetTextAlign(hdc) & TA_RTLREADING)) 225 fuOptions |= ETO_RTLREADING; 226 227 /* If text direction is RTL change flag to account neutral characters */ 228 if (fuOptions & ETO_RTLREADING) 229 dwSICFlags |= SIC_NEUTRAL; 230 231 /* Check if the string requires complex script processing and not a "glyph indices" array */ 232 if (ScriptIsComplex(lpString, uCount, dwSICFlags) == S_OK && !(fuOptions & ETO_GLYPH_INDEX)) 233 { 234 /* reordered_str is used as fallback in case the glyphs array fails to generate, 235 BIDI_Reorder() doesn't attempt to write into reordered_str if memory allocation fails */ 236 reordered_str = HeapAlloc(GetProcessHeap(), 0, uCount * sizeof(WCHAR)); 237 238 bReorder = BIDI_Reorder(hdc, lpString, uCount, GCP_REORDER, 239 (fuOptions & ETO_RTLREADING) ? WINE_GCPW_FORCE_RTL : WINE_GCPW_FORCE_LTR, 240 reordered_str, uCount, NULL, &glyphs, &cGlyphs); 241 242 /* Now display the reordered text if any of the arrays is valid and if BIDI_Reorder() succeeded */ 243 if ((glyphs || reordered_str) && bReorder) 244 { 245 if (glyphs) 246 { 247 fuOptions |= ETO_GLYPH_INDEX; 248 uCount = cGlyphs; 249 } 250 251 bResult = ExtTextOutW(hdc, x, y, fuOptions, lprc, 252 glyphs ? (LPWSTR)glyphs : reordered_str, uCount, lpDx); 253 } 254 else 255 { 256 WARN("BIDI_Reorder failed, falling back to original string.\n"); 257 bResult = ExtTextOutW(hdc, x, y, fuOptions, lprc, lpString, uCount, lpDx); 258 } 259 260 HeapFree(GetProcessHeap(), 0, glyphs); 261 HeapFree(GetProcessHeap(), 0, reordered_str); 262 263 return bResult; 264 } 265 266 return ExtTextOutW(hdc, x, y, fuOptions, lprc, lpString, uCount, lpDx); 267 } 268 269 /* 270 * @implemented 271 */ 272 DWORD 273 WINAPI 274 LpkGetCharacterPlacement( 275 HDC hdc, 276 LPCWSTR lpString, 277 INT uCount, 278 INT nMaxExtent, 279 LPGCP_RESULTSW lpResults, 280 DWORD dwFlags, 281 DWORD dwUnused) 282 { 283 DWORD ret = 0; 284 HRESULT hr; 285 SCRIPT_STRING_ANALYSIS ssa; 286 LPWORD lpGlyphs = NULL; 287 SIZE size; 288 UINT nSet, i; 289 INT cGlyphs; 290 291 UNREFERENCED_PARAMETER(dwUnused); 292 293 /* Sanity check (most likely a direct call) */ 294 if (!(dwFlags & GCP_REORDER)) 295 return GetCharacterPlacementW(hdc, lpString, uCount, nMaxExtent, lpResults, dwFlags); 296 297 nSet = (UINT)uCount; 298 if (nSet > lpResults->nGlyphs) 299 nSet = lpResults->nGlyphs; 300 301 BIDI_Reorder(hdc, lpString, uCount, dwFlags, WINE_GCPW_FORCE_LTR, lpResults->lpOutString, 302 nSet, lpResults->lpOrder, &lpGlyphs, &cGlyphs); 303 304 lpResults->nGlyphs = (UINT)cGlyphs; 305 306 if (lpResults->lpGlyphs) 307 { 308 if (lpGlyphs) 309 StringCchCopyW(lpResults->lpGlyphs, cGlyphs, lpGlyphs); 310 else if (lpResults->lpOutString) 311 GetGlyphIndicesW(hdc, lpResults->lpOutString, nSet, lpResults->lpGlyphs, 0); 312 } 313 314 if (lpResults->lpDx) 315 { 316 int c; 317 318 /* If glyph shaping was requested */ 319 if (dwFlags & GCP_GLYPHSHAPE) 320 { 321 if (lpResults->lpGlyphs) 322 { 323 for (i = 0; i < lpResults->nGlyphs; i++) 324 { 325 if (GetCharWidthI(hdc, 0, 1, (WORD *)&lpResults->lpGlyphs[i], &c)) 326 lpResults->lpDx[i] = c; 327 } 328 } 329 } 330 331 else 332 { 333 for (i = 0; i < nSet; i++) 334 { 335 if (GetCharWidth32W(hdc, lpResults->lpOutString[i], lpResults->lpOutString[i], &c)) 336 lpResults->lpDx[i] = c; 337 } 338 } 339 } 340 341 if (lpResults->lpCaretPos) 342 { 343 int pos = 0; 344 345 hr = ScriptStringAnalyse(hdc, lpString, nSet, (3 * nSet / 2 + 16), -1, SSA_GLYPHS, -1, 346 NULL, NULL, NULL, NULL, NULL, &ssa); 347 if (hr == S_OK) 348 { 349 for (i = 0; i < nSet; i++) 350 { 351 if (ScriptStringCPtoX(ssa, i, FALSE, &pos) == S_OK) 352 lpResults->lpCaretPos[i] = pos; 353 } 354 ScriptStringFree(&ssa); 355 } 356 else 357 { 358 lpResults->lpCaretPos[0] = 0; 359 for (i = 1; i < nSet; i++) 360 { 361 if (GetTextExtentPoint32W(hdc, &(lpString[i - 1]), 1, &size)) 362 lpResults->lpCaretPos[i] = (pos += size.cx); 363 } 364 } 365 } 366 367 if (GetTextExtentPoint32W(hdc, lpString, uCount, &size)) 368 ret = MAKELONG(size.cx, size.cy); 369 370 HeapFree(GetProcessHeap(), 0, lpGlyphs); 371 372 return ret; 373 } 374 375 /* Stripped down version of DrawText(), can only draw single line text and Prefix underscore 376 * (only on the last found amperstand). 377 * Only flags to be found to be of use in testing: 378 * 379 * DT_NOPREFIX - Draw the string as is without removal of the amperstands and without underscore 380 * DT_HIDEPREFIX - Draw the string without underscore 381 * DT_PREFIXONLY - Draw only the underscore 382 * 383 * Without any of these flags the behavior is the string being drawn without the amperstands and 384 * with the underscore. 385 * user32 has an equivalent function - UserLpkPSMTextOut(). 386 * 387 * Note: lpString does not need to be null terminated. 388 */ 389 INT WINAPI LpkPSMTextOut(HDC hdc, int x, int y, LPCWSTR lpString, int cString, DWORD dwFlags) 390 { 391 SIZE size; 392 TEXTMETRICW tm; 393 int prefix_offset, len; 394 LPWSTR display_str = NULL; 395 396 if (!lpString || cString <= 0) 397 return 0; 398 399 if (dwFlags & DT_NOPREFIX) 400 { 401 LpkExtTextOut(hdc, x, y, 0, NULL, lpString, cString, NULL, 0); 402 GetTextExtentPointW(hdc, lpString, cString, &size); 403 return size.cx; 404 } 405 406 display_str = HeapAlloc(GetProcessHeap(), 0, (cString + 1) * sizeof(WCHAR)); 407 408 if (!display_str) 409 return 0; 410 411 PSM_PrepareToDraw(lpString, cString, display_str, &len); 412 413 if (!(dwFlags & DT_PREFIXONLY)) 414 LpkExtTextOut(hdc, x, y, 0, NULL, display_str, len, NULL, 0); 415 416 if (!(dwFlags & DT_HIDEPREFIX)) 417 { 418 prefix_offset = PSM_FindLastPrefix(lpString, cString); 419 GetTextMetricsW(hdc, &tm); 420 LPK_DrawUnderscore(hdc, x, y + tm.tmAscent + 1, display_str, len, prefix_offset); 421 } 422 423 GetTextExtentPointW(hdc, display_str, len + 1, &size); 424 HeapFree(GetProcessHeap(), 0, display_str); 425 426 return size.cx; 427 } 428 429 /* 430 * @implemented 431 */ 432 BOOL 433 WINAPI 434 LpkGetTextExtentExPoint( 435 HDC hdc, 436 LPCWSTR lpString, 437 INT cString, 438 INT nMaxExtent, 439 LPINT lpnFit, 440 LPINT lpnDx, 441 LPSIZE lpSize, 442 DWORD dwUnused, 443 int unknown) 444 { 445 SCRIPT_STRING_ANALYSIS ssa; 446 HRESULT hr; 447 INT i, extent, *Dx; 448 TEXTMETRICW tm; 449 450 UNREFERENCED_PARAMETER(dwUnused); 451 UNREFERENCED_PARAMETER(unknown); 452 453 if (cString < 0 || !lpSize) 454 return FALSE; 455 456 if (cString == 0 || !lpString) 457 { 458 lpSize->cx = 0; 459 lpSize->cy = 0; 460 return TRUE; 461 } 462 463 /* Check if any processing is required */ 464 if (ScriptIsComplex(lpString, cString, SIC_COMPLEX) != S_OK) 465 goto fallback; 466 467 hr = ScriptStringAnalyse(hdc, lpString, cString, 3 * cString / 2 + 16, -1, 468 SSA_GLYPHS, 0, NULL, NULL, NULL, NULL, NULL, &ssa); 469 if (hr != S_OK) 470 goto fallback; 471 472 /* Use logic from TextIntGetTextExtentPoint() */ 473 Dx = HeapAlloc(GetProcessHeap(), 0, cString * sizeof(INT)); 474 if (!Dx) 475 { 476 ScriptStringFree(&ssa); 477 goto fallback; 478 } 479 480 if (lpnFit) 481 *lpnFit = 0; 482 483 ScriptStringGetLogicalWidths(ssa, Dx); 484 485 for (i = 0, extent = 0; i < cString; i++) 486 { 487 extent += Dx[i]; 488 489 if (extent <= nMaxExtent && lpnFit) 490 *lpnFit = i + 1; 491 492 if (lpnDx) 493 lpnDx[i] = extent; 494 } 495 496 HeapFree(GetProcessHeap(), 0, Dx); 497 ScriptStringFree(&ssa); 498 499 if (!GetTextMetricsW(hdc, &tm)) 500 return GetTextExtentExPointWPri(hdc, lpString, cString, 0, NULL, NULL, lpSize); 501 502 lpSize->cx = extent; 503 lpSize->cy = tm.tmHeight; 504 505 return TRUE; 506 507 fallback: 508 return GetTextExtentExPointWPri(hdc, lpString, cString, nMaxExtent, lpnFit, lpnDx, lpSize); 509 } 510