xref: /reactos/win32ss/user/winsrv/concfg/font.c (revision 019f21ee)
1 /*
2  * COPYRIGHT:       See COPYING in the top level directory
3  * PROJECT:         ReactOS Console Server DLL
4  * FILE:            win32ss/user/winsrv/concfg/font.c
5  * PURPOSE:         Console Fonts Management
6  * PROGRAMMERS:     Hermes Belusca-Maito (hermes.belusca@sfr.fr)
7  *                  Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com)
8  */
9 
10 /* INCLUDES *******************************************************************/
11 
12 #include "precomp.h"
13 #include <winuser.h>
14 
15 #include "settings.h"
16 #include "font.h"
17 // #include "concfg.h"
18 
19 #define NDEBUG
20 #include <debug.h>
21 
22 
23 /* GLOBALS ********************************************************************/
24 
25 // RTL_STATIC_LIST_HEAD(TTFontCache);
26 LIST_ENTRY TTFontCache = {&TTFontCache, &TTFontCache};
27 
28 /* FUNCTIONS ******************************************************************/
29 
30 /* Retrieves the character set associated with a given code page */
31 BYTE
32 CodePageToCharSet(
33     IN UINT CodePage)
34 {
35     CHARSETINFO CharInfo;
36     if (TranslateCharsetInfo(UlongToPtr(CodePage), &CharInfo, TCI_SRCCODEPAGE))
37         return CharInfo.ciCharset;
38     else
39         return DEFAULT_CHARSET;
40 }
41 
42 HFONT
43 CreateConsoleFontEx(
44     IN LONG Height,
45     IN LONG Width OPTIONAL,
46     IN OUT LPWSTR FaceName, // Points to a WCHAR array of LF_FACESIZE elements
47     IN ULONG FontFamily,
48     IN ULONG FontWeight,
49     IN UINT  CodePage)
50 {
51     LOGFONTW lf;
52 
53     RtlZeroMemory(&lf, sizeof(lf));
54 
55     lf.lfHeight = Height;
56     lf.lfWidth  = Width;
57 
58     lf.lfEscapement  = 0;
59     lf.lfOrientation = 0; // TA_BASELINE; // TA_RTLREADING; when the console supports RTL?
60     // lf.lfItalic = lf.lfUnderline = lf.lfStrikeOut = FALSE;
61     lf.lfWeight  = FontWeight;
62     lf.lfCharSet = CodePageToCharSet(CodePage);
63     lf.lfOutPrecision  = OUT_DEFAULT_PRECIS;
64     lf.lfClipPrecision = CLIP_DEFAULT_PRECIS;
65     lf.lfQuality = DEFAULT_QUALITY;
66 
67     /* Set the mandatory flags and remove those that we do not support */
68     lf.lfPitchAndFamily = (BYTE)( (FIXED_PITCH | FF_MODERN | FontFamily) &
69                                  ~(VARIABLE_PITCH | FF_DECORATIVE | FF_ROMAN | FF_SCRIPT | FF_SWISS));
70 
71     if (!IsValidConsoleFont(FaceName, CodePage))
72     {
73         StringCchCopyW(FaceName, LF_FACESIZE, L"Terminal");
74         if (IsCJKCodePage(CodePage))
75         {
76             lf.lfCharSet = ANSI_CHARSET;
77         }
78     }
79 
80     StringCchCopyNW(lf.lfFaceName, ARRAYSIZE(lf.lfFaceName),
81                     FaceName, LF_FACESIZE);
82 
83     return CreateFontIndirectW(&lf);
84 }
85 
86 HFONT
87 CreateConsoleFont2(
88     IN LONG Height,
89     IN LONG Width OPTIONAL,
90     IN OUT PCONSOLE_STATE_INFO ConsoleInfo)
91 {
92     return CreateConsoleFontEx(Height,
93                                Width,
94                                ConsoleInfo->FaceName,
95                                ConsoleInfo->FontFamily,
96                                ConsoleInfo->FontWeight,
97                                ConsoleInfo->CodePage);
98 }
99 
100 HFONT
101 CreateConsoleFont(
102     IN OUT PCONSOLE_STATE_INFO ConsoleInfo)
103 {
104     /*
105      * Format:
106      * Width  = FontSize.X = LOWORD(FontSize);
107      * Height = FontSize.Y = HIWORD(FontSize);
108      */
109     /* NOTE: FontSize is always in cell height/width units (pixels) */
110     return CreateConsoleFontEx((LONG)(ULONG)ConsoleInfo->FontSize.Y,
111                                (LONG)(ULONG)ConsoleInfo->FontSize.X,
112                                ConsoleInfo->FaceName,
113                                ConsoleInfo->FontFamily,
114                                ConsoleInfo->FontWeight,
115                                ConsoleInfo->CodePage);
116 }
117 
118 BOOL
119 GetFontCellSize(
120     IN HDC hDC OPTIONAL,
121     IN HFONT hFont,
122     OUT PUINT Height,
123     OUT PUINT Width)
124 {
125     BOOL Success = FALSE;
126     HDC hOrgDC = hDC;
127     HFONT hOldFont;
128     // LONG LogSize, PointSize;
129     LONG CharWidth, CharHeight;
130     TEXTMETRICW tm;
131     // SIZE CharSize;
132 
133     if (!hDC)
134         hDC = GetDC(NULL);
135 
136     hOldFont = SelectObject(hDC, hFont);
137     if (hOldFont == NULL)
138     {
139         DPRINT1("GetFontCellSize: SelectObject failed\n");
140         goto Quit;
141     }
142 
143 /*
144  * See also: Display_SetTypeFace in applications/fontview/display.c
145  */
146 
147     /*
148      * Note that the method with GetObjectW just returns
149      * the original parameters with which the font was created.
150      */
151     if (!GetTextMetricsW(hDC, &tm))
152     {
153         DPRINT1("GetFontCellSize: GetTextMetrics failed\n");
154         goto Cleanup;
155     }
156 
157     CharHeight = tm.tmHeight + tm.tmExternalLeading;
158 
159 #if 0
160     /* Measure real char width more precisely if possible */
161     if (GetTextExtentPoint32W(hDC, L"R", 1, &CharSize))
162         CharWidth = CharSize.cx;
163 #else
164     CharWidth = tm.tmAveCharWidth; // tm.tmMaxCharWidth;
165 #endif
166 
167 #if 0
168     /*** Logical to Point size ***/
169     LogSize   = tm.tmHeight - tm.tmInternalLeading;
170     PointSize = MulDiv(LogSize, 72, GetDeviceCaps(hDC, LOGPIXELSY));
171     /*****************************/
172 #endif
173 
174     *Height = (UINT)CharHeight;
175     *Width  = (UINT)CharWidth;
176     Success = TRUE;
177 
178 Cleanup:
179     SelectObject(hDC, hOldFont);
180 Quit:
181     if (!hOrgDC)
182         ReleaseDC(NULL, hDC);
183 
184     return Success;
185 }
186 
187 BOOL
188 IsValidConsoleFont2(
189     IN PLOGFONTW lplf,
190     IN PNEWTEXTMETRICW lpntm,
191     IN DWORD FontType,
192     IN UINT CodePage)
193 {
194     LPCWSTR FaceName = lplf->lfFaceName;
195 
196     /*
197      * According to: https://web.archive.org/web/20140901124501/http://support.microsoft.com/kb/247815
198      * "Necessary criteria for fonts to be available in a command window",
199      * the criteria for console-eligible fonts are as follows:
200      * - The font must be a fixed-pitch font.
201      * - The font cannot be an italic font.
202      * - The font cannot have a negative A or C space.
203      * - If it is a TrueType font, it must be FF_MODERN.
204      * - If it is not a TrueType font, it must be OEM_CHARSET.
205      *
206      * Non documented: vertical fonts are forbidden (their name start with a '@').
207      *
208      * Additional criteria for Asian installations:
209      * - If it is not a TrueType font, the face name must be "Terminal".
210      * - If it is an Asian TrueType font, it must also be an Asian character set.
211      *
212      * See also Raymond Chen's blog: https://devblogs.microsoft.com/oldnewthing/?p=26843
213      * and MIT-licensed Microsoft Terminal source code: https://github.com/microsoft/Terminal/blob/master/src/propsheet/misc.cpp
214      * for other details.
215      *
216      * To install additional TrueType fonts to be available for the console,
217      * add entries of type REG_SZ named "0", "00" etc... in:
218      * HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Console\TrueTypeFont
219      * The names of the fonts listed there should match those in:
220      * HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Fonts
221      */
222 
223     /*
224      * In ReactOS we relax some of the criteria:
225      * - We allow fixed-pitch FF_MODERN (Monospace) TrueType fonts
226      *   that can be italic or have negative A or C space.
227      * - If it is not a TrueType font, it can be from another character set
228      *   than OEM_CHARSET. When an Asian codepage is active however, we require
229      *   that this non-TrueType font has an Asian character set.
230      */
231 
232     /* Reject variable-width fonts ... */
233     if ( ( ((lplf->lfPitchAndFamily & 0x03) != FIXED_PITCH)
234 #if 0 /* Reject italic and TrueType fonts with negative A or C space ... */
235            || (lplf->lfItalic)
236            || !(lpntm->ntmFlags & NTM_NONNEGATIVE_AC)
237 #endif
238          ) &&
239         /* ... if they are not in the list of additional TrueType fonts to include */
240          !IsAdditionalTTFont(FaceName) )
241     {
242         DPRINT1("Font '%S' rejected because it%s (lfPitchAndFamily = %d)\n",
243                 FaceName,
244                 !(lplf->lfPitchAndFamily & FIXED_PITCH) ? "'s not FIXED_PITCH"
245                     : (!(lpntm->ntmFlags & NTM_NONNEGATIVE_AC) ? " has negative A or C space"
246                                                                : " is broken"),
247                 lplf->lfPitchAndFamily);
248         return FALSE;
249     }
250 
251     /* Reject TrueType fonts that are not FF_MODERN */
252     if ((FontType == TRUETYPE_FONTTYPE) && ((lplf->lfPitchAndFamily & 0xF0) != FF_MODERN))
253     {
254         DPRINT1("TrueType font '%S' rejected because it's not FF_MODERN (lfPitchAndFamily = %d)\n",
255                 FaceName, lplf->lfPitchAndFamily);
256         return FALSE;
257     }
258 
259     /* Reject vertical fonts (tategaki) */
260     if (FaceName[0] == L'@')
261     {
262         DPRINT1("Font '%S' rejected because it's vertical\n", FaceName);
263         return FALSE;
264     }
265 
266     /* Is the current code page Chinese, Japanese or Korean? */
267     if (IsCJKCodePage(CodePage))
268     {
269         /* It's CJK */
270 
271         if (FontType == TRUETYPE_FONTTYPE)
272         {
273             /*
274              * Here we are inclusive and check for any CJK character set,
275              * instead of looking just at the current one via CodePageToCharSet().
276              */
277             if (!IsCJKCharSet(lplf->lfCharSet)
278 #if 1 // FIXME: Temporary HACK!
279                 && wcscmp(FaceName, L"Terminal") != 0
280 #endif
281                )
282             {
283                 DPRINT1("TrueType font '%S' rejected because it's not Asian charset (lfCharSet = %d)\n",
284                         FaceName, lplf->lfCharSet);
285                 return FALSE;
286             }
287 
288             /*
289              * If this is a cached TrueType font that is used only for certain
290              * code pages, verify that the charset it claims is the correct one.
291              *
292              * Since there may be multiple entries for a cached TrueType font,
293              * a general one (code page == 0) and one or more for explicit
294              * code pages, we need to perform two search queries instead of
295              * just one and retrieving the code page for this entry.
296              */
297             if (IsAdditionalTTFont(FaceName) && !IsAdditionalTTFontCP(FaceName, 0) &&
298                 !IsCJKCharSet(lplf->lfCharSet))
299             {
300                 DPRINT1("Cached TrueType font '%S' rejected because it claims a code page that is not Asian charset (lfCharSet = %d)\n",
301                         FaceName, lplf->lfCharSet);
302                 return FALSE;
303             }
304         }
305         else
306         {
307             /* Reject non-TrueType fonts that do not have an Asian character set */
308             if (!IsCJKCharSet(lplf->lfCharSet) && (lplf->lfCharSet != OEM_CHARSET))
309             {
310                 DPRINT1("Non-TrueType font '%S' rejected because it's not Asian charset or OEM_CHARSET (lfCharSet = %d)\n",
311                         FaceName, lplf->lfCharSet);
312                 return FALSE;
313             }
314 
315             /* Reject non-TrueType fonts that are not Terminal */
316             if (wcscmp(FaceName, L"Terminal") != 0)
317             {
318                 DPRINT1("Non-TrueType font '%S' rejected because it's not 'Terminal'\n", FaceName);
319                 return FALSE;
320             }
321         }
322     }
323     else
324     {
325         /* Not CJK */
326 
327         /* Reject non-TrueType fonts that are not OEM or similar */
328         if ((FontType != TRUETYPE_FONTTYPE) &&
329             (lplf->lfCharSet != ANSI_CHARSET) &&
330             (lplf->lfCharSet != DEFAULT_CHARSET) &&
331             (lplf->lfCharSet != OEM_CHARSET))
332         {
333             DPRINT1("Non-TrueType font '%S' rejected because it's not ANSI_CHARSET or DEFAULT_CHARSET or OEM_CHARSET (lfCharSet = %d)\n",
334                     FaceName, lplf->lfCharSet);
335             return FALSE;
336         }
337     }
338 
339     /* All good */
340     return TRUE;
341 }
342 
343 typedef struct _IS_VALID_CONSOLE_FONT_PARAM
344 {
345     BOOL IsValidFont;
346     UINT CodePage;
347 } IS_VALID_CONSOLE_FONT_PARAM, *PIS_VALID_CONSOLE_FONT_PARAM;
348 
349 static BOOL CALLBACK
350 IsValidConsoleFontProc(
351     IN PLOGFONTW lplf,
352     IN PNEWTEXTMETRICW lpntm,
353     IN DWORD  FontType,
354     IN LPARAM lParam)
355 {
356     PIS_VALID_CONSOLE_FONT_PARAM Param = (PIS_VALID_CONSOLE_FONT_PARAM)lParam;
357     Param->IsValidFont = IsValidConsoleFont2(lplf, lpntm, FontType, Param->CodePage);
358 
359     /* Stop the enumeration now */
360     return FALSE;
361 }
362 
363 BOOL
364 IsValidConsoleFont(
365     IN LPCWSTR FaceName,
366     IN UINT CodePage)
367 {
368     IS_VALID_CONSOLE_FONT_PARAM Param;
369     HDC hDC;
370     LOGFONTW lf;
371 
372     Param.IsValidFont = FALSE;
373     Param.CodePage = CodePage;
374 
375     RtlZeroMemory(&lf, sizeof(lf));
376     lf.lfCharSet = DEFAULT_CHARSET; // CodePageToCharSet(CodePage);
377     // lf.lfPitchAndFamily = FIXED_PITCH | FF_MODERN;
378     StringCchCopyW(lf.lfFaceName, ARRAYSIZE(lf.lfFaceName), FaceName);
379 
380     hDC = GetDC(NULL);
381     EnumFontFamiliesExW(hDC, &lf, (FONTENUMPROCW)IsValidConsoleFontProc, (LPARAM)&Param, 0);
382     ReleaseDC(NULL, hDC);
383 
384     return Param.IsValidFont;
385 }
386 
387 /*
388  * To install additional TrueType fonts to be available for the console,
389  * add entries of type REG_SZ named "0", "00" etc... in:
390  * HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Console\TrueTypeFont
391  * The names of the fonts listed there should match those in:
392  * HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Fonts
393  *
394  * This function initializes the cache of the fonts listed there.
395  */
396 VOID
397 InitTTFontCache(VOID)
398 {
399     BOOLEAN Success;
400     HKEY  hKeyTTFonts; // hKey;
401     DWORD dwNumValues = 0;
402     DWORD dwIndex;
403     DWORD dwType;
404     WCHAR szValueName[MAX_PATH];
405     DWORD dwValueName;
406     WCHAR szValue[LF_FACESIZE] = L"";
407     DWORD dwValue;
408     PTT_FONT_ENTRY FontEntry;
409     PWCHAR pszNext = NULL;
410     UINT CodePage;
411 
412     if (!IsListEmpty(&TTFontCache))
413         return;
414     // InitializeListHead(&TTFontCache);
415 
416     /* Open the key */
417     // "\\Registry\\Machine\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Console\\TrueTypeFont"
418     Success = (RegOpenKeyExW(HKEY_LOCAL_MACHINE,
419                              L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Console\\TrueTypeFont",
420                              0,
421                              KEY_READ,
422                              &hKeyTTFonts) == ERROR_SUCCESS);
423     if (!Success)
424         return;
425 
426     /* Enumerate each value */
427     if (RegQueryInfoKeyW(hKeyTTFonts, NULL, NULL, NULL, NULL, NULL, NULL,
428                          &dwNumValues, NULL, NULL, NULL, NULL) != ERROR_SUCCESS)
429     {
430         DPRINT("ConCfgReadUserSettings: RegQueryInfoKeyW failed\n");
431         RegCloseKey(hKeyTTFonts);
432         return;
433     }
434 
435     for (dwIndex = 0; dwIndex < dwNumValues; dwIndex++)
436     {
437         dwValue = sizeof(szValue);
438         dwValueName = ARRAYSIZE(szValueName);
439         if (RegEnumValueW(hKeyTTFonts, dwIndex, szValueName, &dwValueName, NULL, &dwType, (BYTE*)szValue, &dwValue) != ERROR_SUCCESS)
440         {
441             DPRINT1("InitTTFontCache: RegEnumValueW failed, continuing...\n");
442             continue;
443         }
444         /* Only (multi-)string values are supported */
445         if ((dwType != REG_SZ) && (dwType != REG_MULTI_SZ))
446             continue;
447 
448         /* The value name is a code page (in decimal), validate it */
449         CodePage = wcstoul(szValueName, &pszNext, 10);
450         if (*pszNext)
451             continue; // Non-numerical garbage followed...
452         // IsValidCodePage(CodePage);
453 
454         FontEntry = RtlAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*FontEntry));
455         if (!FontEntry)
456         {
457             DPRINT1("InitTTFontCache: Failed to allocate memory, continuing...\n");
458             continue;
459         }
460 
461         FontEntry->CodePage = CodePage;
462 
463         pszNext = szValue;
464 
465         /* Check whether bold is disabled for this font */
466         if (*pszNext == L'*')
467         {
468             FontEntry->DisableBold = TRUE;
469             ++pszNext;
470         }
471         else
472         {
473             FontEntry->DisableBold = FALSE;
474         }
475 
476         /* Copy the font name */
477         StringCchCopyNW(FontEntry->FaceName, ARRAYSIZE(FontEntry->FaceName),
478                         pszNext, wcslen(pszNext));
479 
480         if (dwType == REG_MULTI_SZ)
481         {
482             /* There may be an alternate face name as the second string */
483             pszNext += wcslen(pszNext) + 1;
484 
485             /* Check whether bold is disabled for this font */
486             if (*pszNext == L'*')
487             {
488                 FontEntry->DisableBold = TRUE;
489                 ++pszNext;
490             }
491             // else, keep the original setting.
492 
493             /* Copy the alternate font name */
494             StringCchCopyNW(FontEntry->FaceNameAlt, ARRAYSIZE(FontEntry->FaceNameAlt),
495                             pszNext, wcslen(pszNext));
496         }
497 
498         InsertTailList(&TTFontCache, &FontEntry->Entry);
499     }
500 
501     /* Close the key and quit */
502     RegCloseKey(hKeyTTFonts);
503 }
504 
505 VOID
506 ClearTTFontCache(VOID)
507 {
508     PLIST_ENTRY Entry;
509     PTT_FONT_ENTRY FontEntry;
510 
511     while (!IsListEmpty(&TTFontCache))
512     {
513         Entry = RemoveHeadList(&TTFontCache);
514         FontEntry = CONTAINING_RECORD(Entry, TT_FONT_ENTRY, Entry);
515         RtlFreeHeap(RtlGetProcessHeap(), 0, FontEntry);
516     }
517     InitializeListHead(&TTFontCache);
518 }
519 
520 VOID
521 RefreshTTFontCache(VOID)
522 {
523     ClearTTFontCache();
524     InitTTFontCache();
525 }
526 
527 PTT_FONT_ENTRY
528 FindCachedTTFont(
529     IN LPCWSTR FaceName,
530     IN UINT CodePage)
531 {
532     PLIST_ENTRY Entry;
533     PTT_FONT_ENTRY FontEntry;
534 
535     /* Search for the font in the cache */
536     for (Entry = TTFontCache.Flink;
537          Entry != &TTFontCache;
538          Entry = Entry->Flink)
539     {
540         FontEntry = CONTAINING_RECORD(Entry, TT_FONT_ENTRY, Entry);
541 
542         /* NOTE: The font face names are case-sensitive */
543         if ((wcscmp(FontEntry->FaceName   , FaceName) == 0) ||
544             (wcscmp(FontEntry->FaceNameAlt, FaceName) == 0))
545         {
546             /* Return a match if we don't look at the code pages, or when they match */
547             if ((CodePage == INVALID_CP) || (CodePage == FontEntry->CodePage))
548             {
549                 return FontEntry;
550             }
551         }
552     }
553     return NULL;
554 }
555 
556 /* EOF */
557