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