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 LpkDllInitialize( 165 _In_ HANDLE hDll, 166 _In_ ULONG dwReason, 167 _In_opt_ PVOID pReserved) 168 { 169 UNREFERENCED_PARAMETER(pReserved); 170 171 switch (dwReason) 172 { 173 case DLL_PROCESS_ATTACH: 174 DisableThreadLibraryCalls(hDll); 175 /* Tell usp10 it is activated usp10 */ 176 //LpkPresent(); 177 LPK_ApplyMirroring(); 178 break; 179 180 default: 181 break; 182 } 183 184 return TRUE; 185 } 186 187 188 /* 189 * @implemented 190 */ 191 BOOL 192 WINAPI 193 LpkExtTextOut( 194 HDC hdc, 195 int x, 196 int y, 197 UINT fuOptions, 198 const RECT *lprc, 199 LPCWSTR lpString, 200 UINT uCount, 201 const INT *lpDx, 202 INT unknown) 203 { 204 LPWORD glyphs = NULL; 205 LPWSTR reordered_str = NULL; 206 INT cGlyphs; 207 DWORD dwSICFlags = SIC_COMPLEX; 208 BOOL bResult, bReorder; 209 210 UNREFERENCED_PARAMETER(unknown); 211 212 fuOptions |= ETO_IGNORELANGUAGE; 213 214 /* Check text direction */ 215 if ((GetLayout(hdc) & LAYOUT_RTL) || (GetTextAlign(hdc) & TA_RTLREADING)) 216 fuOptions |= ETO_RTLREADING; 217 218 /* If text direction is RTL change flag to account neutral characters */ 219 if (fuOptions & ETO_RTLREADING) 220 dwSICFlags |= SIC_NEUTRAL; 221 222 /* Check if the string requires complex script processing and not a "glyph indices" array */ 223 if (ScriptIsComplex(lpString, uCount, dwSICFlags) == S_OK && !(fuOptions & ETO_GLYPH_INDEX)) 224 { 225 /* reordered_str is used as fallback in case the glyphs array fails to generate, 226 BIDI_Reorder() doesn't attempt to write into reordered_str if memory allocation fails */ 227 reordered_str = HeapAlloc(GetProcessHeap(), 0, uCount * sizeof(WCHAR)); 228 229 bReorder = BIDI_Reorder(hdc, lpString, uCount, GCP_REORDER, 230 (fuOptions & ETO_RTLREADING) ? WINE_GCPW_FORCE_RTL : WINE_GCPW_FORCE_LTR, 231 reordered_str, uCount, NULL, &glyphs, &cGlyphs); 232 233 /* Now display the reordered text if any of the arrays is valid and if BIDI_Reorder() succeeded */ 234 if ((glyphs || reordered_str) && bReorder) 235 { 236 if (glyphs) 237 { 238 fuOptions |= ETO_GLYPH_INDEX; 239 uCount = cGlyphs; 240 } 241 242 bResult = ExtTextOutW(hdc, x, y, fuOptions, lprc, 243 glyphs ? (LPWSTR)glyphs : reordered_str, uCount, lpDx); 244 } 245 else 246 { 247 WARN("BIDI_Reorder failed, falling back to original string.\n"); 248 bResult = ExtTextOutW(hdc, x, y, fuOptions, lprc, lpString, uCount, lpDx); 249 } 250 251 HeapFree(GetProcessHeap(), 0, glyphs); 252 HeapFree(GetProcessHeap(), 0, reordered_str); 253 254 return bResult; 255 } 256 257 return ExtTextOutW(hdc, x, y, fuOptions, lprc, lpString, uCount, lpDx); 258 } 259 260 /* 261 * @implemented 262 */ 263 DWORD 264 WINAPI 265 LpkGetCharacterPlacement( 266 HDC hdc, 267 LPCWSTR lpString, 268 INT uCount, 269 INT nMaxExtent, 270 LPGCP_RESULTSW lpResults, 271 DWORD dwFlags, 272 DWORD dwUnused) 273 { 274 DWORD ret = 0; 275 HRESULT hr; 276 SCRIPT_STRING_ANALYSIS ssa; 277 LPWORD lpGlyphs = NULL; 278 SIZE size; 279 UINT nSet, i; 280 INT cGlyphs; 281 282 UNREFERENCED_PARAMETER(dwUnused); 283 284 /* Sanity check (most likely a direct call) */ 285 if (!(dwFlags & GCP_REORDER)) 286 return GetCharacterPlacementW(hdc, lpString, uCount, nMaxExtent, lpResults, dwFlags); 287 288 nSet = (UINT)uCount; 289 if (nSet > lpResults->nGlyphs) 290 nSet = lpResults->nGlyphs; 291 292 BIDI_Reorder(hdc, lpString, uCount, dwFlags, WINE_GCPW_FORCE_LTR, lpResults->lpOutString, 293 nSet, lpResults->lpOrder, &lpGlyphs, &cGlyphs); 294 295 lpResults->nGlyphs = (UINT)cGlyphs; 296 297 if (lpResults->lpGlyphs) 298 { 299 if (lpGlyphs) 300 StringCchCopyW(lpResults->lpGlyphs, cGlyphs, lpGlyphs); 301 else if (lpResults->lpOutString) 302 GetGlyphIndicesW(hdc, lpResults->lpOutString, nSet, lpResults->lpGlyphs, 0); 303 } 304 305 if (lpResults->lpDx) 306 { 307 int c; 308 309 /* If glyph shaping was requested */ 310 if (dwFlags & GCP_GLYPHSHAPE) 311 { 312 if (lpResults->lpGlyphs) 313 { 314 for (i = 0; i < lpResults->nGlyphs; i++) 315 { 316 if (GetCharWidthI(hdc, 0, 1, (WORD *)&lpResults->lpGlyphs[i], &c)) 317 lpResults->lpDx[i] = c; 318 } 319 } 320 } 321 322 else 323 { 324 for (i = 0; i < nSet; i++) 325 { 326 if (GetCharWidth32W(hdc, lpResults->lpOutString[i], lpResults->lpOutString[i], &c)) 327 lpResults->lpDx[i] = c; 328 } 329 } 330 } 331 332 if (lpResults->lpCaretPos) 333 { 334 int pos = 0; 335 336 hr = ScriptStringAnalyse(hdc, lpString, nSet, (3 * nSet / 2 + 16), -1, SSA_GLYPHS, -1, 337 NULL, NULL, NULL, NULL, NULL, &ssa); 338 if (hr == S_OK) 339 { 340 for (i = 0; i < nSet; i++) 341 { 342 if (ScriptStringCPtoX(ssa, i, FALSE, &pos) == S_OK) 343 lpResults->lpCaretPos[i] = pos; 344 } 345 ScriptStringFree(&ssa); 346 } 347 else 348 { 349 lpResults->lpCaretPos[0] = 0; 350 for (i = 1; i < nSet; i++) 351 { 352 if (GetTextExtentPoint32W(hdc, &(lpString[i - 1]), 1, &size)) 353 lpResults->lpCaretPos[i] = (pos += size.cx); 354 } 355 } 356 } 357 358 if (GetTextExtentPoint32W(hdc, lpString, uCount, &size)) 359 ret = MAKELONG(size.cx, size.cy); 360 361 HeapFree(GetProcessHeap(), 0, lpGlyphs); 362 363 return ret; 364 } 365 366 /* Stripped down version of DrawText(), can only draw single line text and Prefix underscore 367 * (only on the last found amperstand). 368 * Only flags to be found to be of use in testing: 369 * 370 * DT_NOPREFIX - Draw the string as is without removal of the amperstands and without underscore 371 * DT_HIDEPREFIX - Draw the string without underscore 372 * DT_PREFIXONLY - Draw only the underscore 373 * 374 * Without any of these flags the behavior is the string being drawn without the amperstands and 375 * with the underscore. 376 * user32 has an equivalent function - UserLpkPSMTextOut(). 377 * 378 * Note: lpString does not need to be null terminated. 379 */ 380 INT WINAPI LpkPSMTextOut(HDC hdc, int x, int y, LPCWSTR lpString, int cString, DWORD dwFlags) 381 { 382 SIZE size; 383 TEXTMETRICW tm; 384 int prefix_offset, len; 385 LPWSTR display_str = NULL; 386 387 if (!lpString || cString <= 0) 388 return 0; 389 390 if (dwFlags & DT_NOPREFIX) 391 { 392 LpkExtTextOut(hdc, x, y, 0, NULL, lpString, cString, NULL, 0); 393 GetTextExtentPointW(hdc, lpString, cString, &size); 394 return size.cx; 395 } 396 397 display_str = HeapAlloc(GetProcessHeap(), 0, (cString + 1) * sizeof(WCHAR)); 398 399 if (!display_str) 400 return 0; 401 402 PSM_PrepareToDraw(lpString, cString, display_str, &len); 403 404 if (!(dwFlags & DT_PREFIXONLY)) 405 LpkExtTextOut(hdc, x, y, 0, NULL, display_str, len, NULL, 0); 406 407 if (!(dwFlags & DT_HIDEPREFIX)) 408 { 409 prefix_offset = PSM_FindLastPrefix(lpString, cString); 410 GetTextMetricsW(hdc, &tm); 411 LPK_DrawUnderscore(hdc, x, y + tm.tmAscent + 1, display_str, len, prefix_offset); 412 } 413 414 GetTextExtentPointW(hdc, display_str, len + 1, &size); 415 HeapFree(GetProcessHeap(), 0, display_str); 416 417 return size.cx; 418 } 419 420 /* 421 * @implemented 422 */ 423 BOOL 424 WINAPI 425 LpkGetTextExtentExPoint( 426 HDC hdc, 427 LPCWSTR lpString, 428 INT cString, 429 INT nMaxExtent, 430 LPINT lpnFit, 431 LPINT lpnDx, 432 LPSIZE lpSize, 433 DWORD dwUnused, 434 int unknown) 435 { 436 SCRIPT_STRING_ANALYSIS ssa; 437 HRESULT hr; 438 INT i, extent, *Dx; 439 TEXTMETRICW tm; 440 441 UNREFERENCED_PARAMETER(dwUnused); 442 UNREFERENCED_PARAMETER(unknown); 443 444 if (cString < 0 || !lpSize) 445 return FALSE; 446 447 if (cString == 0 || !lpString) 448 { 449 lpSize->cx = 0; 450 lpSize->cy = 0; 451 return TRUE; 452 } 453 454 /* Check if any processing is required */ 455 if (ScriptIsComplex(lpString, cString, SIC_COMPLEX) != S_OK) 456 goto fallback; 457 458 hr = ScriptStringAnalyse(hdc, lpString, cString, 3 * cString / 2 + 16, -1, 459 SSA_GLYPHS, 0, NULL, NULL, NULL, NULL, NULL, &ssa); 460 if (hr != S_OK) 461 goto fallback; 462 463 /* Use logic from TextIntGetTextExtentPoint() */ 464 Dx = HeapAlloc(GetProcessHeap(), 0, cString * sizeof(INT)); 465 if (!Dx) 466 { 467 ScriptStringFree(&ssa); 468 goto fallback; 469 } 470 471 if (lpnFit) 472 *lpnFit = 0; 473 474 ScriptStringGetLogicalWidths(ssa, Dx); 475 476 for (i = 0, extent = 0; i < cString; i++) 477 { 478 extent += Dx[i]; 479 480 if (extent <= nMaxExtent && lpnFit) 481 *lpnFit = i + 1; 482 483 if (lpnDx) 484 lpnDx[i] = extent; 485 } 486 487 HeapFree(GetProcessHeap(), 0, Dx); 488 ScriptStringFree(&ssa); 489 490 if (!GetTextMetricsW(hdc, &tm)) 491 return GetTextExtentExPointWPri(hdc, lpString, cString, 0, NULL, NULL, lpSize); 492 493 lpSize->cx = extent; 494 lpSize->cy = tm.tmHeight; 495 496 return TRUE; 497 498 fallback: 499 return GetTextExtentExPointWPri(hdc, lpString, cString, nMaxExtent, lpnFit, lpnDx, lpSize); 500 } 501