xref: /reactos/dll/win32/lpk/lpk.c (revision 4572aad1)
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