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