[LPK] Fix for CORE-16115. (#1654)
[reactos.git] / dll / win32 / lpk / lpk.c
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 }