1 /* 2 * PROJECT: ReactOS Console Server DLL 3 * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) 4 * PURPOSE: Console GDI Fonts Management. 5 * COPYRIGHT: Copyright 2017-2022 Hermès Bélusca-Maïto 6 * Copyright 2017 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com) 7 */ 8 9 /* INCLUDES *******************************************************************/ 10 11 #include "precomp.h" 12 #include <winuser.h> 13 14 #include "settings.h" 15 #include "font.h" 16 // #include "concfg.h" 17 18 #define NDEBUG 19 #include <debug.h> 20 21 #define DBGFNT DPRINT 22 #define DBGFNT1 DPRINT1 23 24 25 /* GLOBALS ********************************************************************/ 26 27 #define TERMINAL_FACENAME L"Terminal" 28 #define DEFAULT_NON_DBCS_FONTFACE L"Lucida Console" // L"Consolas" 29 #define DEFAULT_TT_FONT_FACENAME L"__DefaultTTFont__" 30 31 /* TrueType font list cache */ 32 SINGLE_LIST_ENTRY TTFontCache = { NULL }; 33 34 // NOTE: Used to tag code that makes sense only with a font cache. 35 // #define FONT_CACHE_PRESENT 36 37 38 /* FUNCTIONS ******************************************************************/ 39 40 /** 41 * @brief 42 * Retrieves the character set associated with a given code page. 43 * 44 * @param[in] CodePage 45 * The code page to convert. 46 * 47 * @return 48 * The character set corresponding to the code page, or @b DEFAULT_CHARSET. 49 **/ 50 BYTE 51 CodePageToCharSet( 52 _In_ UINT CodePage) 53 { 54 CHARSETINFO CharInfo; 55 if (TranslateCharsetInfo(UlongToPtr(CodePage), &CharInfo, TCI_SRCCODEPAGE)) 56 return (BYTE)CharInfo.ciCharset; 57 else 58 return DEFAULT_CHARSET; 59 } 60 61 /*****************************************************************************/ 62 63 typedef struct _FIND_SUITABLE_FONT_PROC_PARAM 64 { 65 /* Search criteria */ 66 _In_reads_or_z_(LF_FACESIZE) PCWSTR AltFaceName; 67 FONT_DATA SearchFont; 68 UINT CodePage; 69 BOOL StrictSearch; // TRUE to do strict search; FALSE for relaxed criteria. 70 71 /* Candidate font data */ 72 BOOL FontFound; // TRUE/FALSE if we have/haven't found a suitable font. 73 FONT_DATA CandidateFont; 74 WCHAR CandidateFaceName[LF_FACESIZE]; 75 } FIND_SUITABLE_FONT_PROC_PARAM, *PFIND_SUITABLE_FONT_PROC_PARAM; 76 77 #define TM_IS_TT_FONT(x) (((x) & TMPF_TRUETYPE) == TMPF_TRUETYPE) 78 #define SIZE_EQUAL(s1, s2) (((s1).X == (s2).X) && ((s1).Y == (s2).Y)) 79 80 /** 81 * @brief EnumFontFamiliesEx() callback helper for FindSuitableFont(). 82 * 83 * @remark 84 * It implements a nearly-identical console-suitable font search 85 * algorithm based on the one from FindCreateFont() 86 * https://github.com/microsoft/terminal/blob/main/src/propsheet/fontdlg.cpp#L1113 87 * excepting that for now, it does not support an internal font cache. 88 **/ 89 static BOOL CALLBACK 90 FindSuitableFontProc( 91 _In_ PLOGFONTW lplf, 92 _In_ PNEWTEXTMETRICW lpntm, 93 _In_ DWORD FontType, 94 _In_ LPARAM lParam) 95 { 96 PFIND_SUITABLE_FONT_PROC_PARAM Param = (PFIND_SUITABLE_FONT_PROC_PARAM)lParam; 97 PFONT_DATA SearchFont = &Param->SearchFont; 98 99 if (!IsValidConsoleFont2(lplf, lpntm, FontType, Param->CodePage)) 100 { 101 /* This font does not suit us; continue enumeration */ 102 return TRUE; 103 } 104 105 #ifndef FONT_CACHE_PRESENT 106 /* 107 * Since we don't cache all the possible font sizes for TrueType fonts, 108 * we cannot check our requested size (and weight) against the enumerated 109 * one; therefore reset the enumerated values to the requested ones. 110 * On the contrary, Raster fonts get their specific font sizes (and weights) 111 * enumerated separately, so for them we can keep the enumerated values. 112 */ 113 if (FontType == TRUETYPE_FONTTYPE) 114 { 115 lplf->lfHeight = SearchFont->Size.Y; 116 lplf->lfWidth = 0; // SearchFont->Size.X; 117 lplf->lfWeight = FW_NORMAL; 118 } 119 #endif 120 121 if (Param->StrictSearch) 122 { 123 /* 124 * Find whether this is an exact match. 125 */ 126 127 /* If looking for a particular family, skip non-matches */ 128 if ((SearchFont->Family != 0) && 129 ((BYTE)SearchFont->Family != (lplf->lfPitchAndFamily & 0xF0))) 130 { 131 /* Continue enumeration */ 132 return TRUE; 133 } 134 135 /* Skip non-matching sizes */ 136 #if 0 137 if ((FontInfo[i].SizeWant.Y != Size.Y) && 138 !SIZE_EQUAL(FontInfo[i].Size, Size)) 139 #endif 140 if ((lplf->lfHeight != SearchFont->Size.Y) && 141 !(lplf->lfWidth == SearchFont->Size.X && 142 lplf->lfHeight == SearchFont->Size.Y)) 143 { 144 /* Continue enumeration */ 145 return TRUE; 146 } 147 148 /* Skip non-matching weights */ 149 if ((SearchFont->Weight != 0) && 150 (SearchFont->Weight != lplf->lfWeight)) 151 { 152 /* Continue enumeration */ 153 return TRUE; 154 } 155 156 /* NOTE: We are making the font enumeration at fixed CharSet, 157 * with the one specified in the parameter block. */ 158 ASSERT(lplf->lfCharSet == SearchFont->CharSet); 159 160 if ((FontType != TRUETYPE_FONTTYPE) && // !TM_IS_TT_FONT(lpntm->tmPitchAndFamily) 161 (lplf->lfCharSet != SearchFont->CharSet) && 162 !(lplf->lfCharSet == OEM_CHARSET && IsCJKCodePage(Param->CodePage))) // g_fEastAsianSystem 163 { 164 /* Continue enumeration */ 165 return TRUE; 166 } 167 168 /* 169 * Size (and maybe family) match. If we don't care about the name or 170 * if it matches, use this font. Otherwise, if name doesn't match and 171 * it is a raster font, consider it. 172 * 173 * NOTE: The font face names are case-sensitive. 174 */ 175 if (!SearchFont->FaceName || !*(SearchFont->FaceName) || 176 (wcscmp(lplf->lfFaceName, SearchFont->FaceName) == 0) || 177 (wcscmp(lplf->lfFaceName, Param->AltFaceName) == 0)) 178 { 179 // FontIndex = i; 180 181 PFONT_DATA CandidateFont = &Param->CandidateFont; 182 183 CandidateFont->FaceName = Param->CandidateFaceName; 184 StringCchCopyNW(Param->CandidateFaceName, 185 ARRAYSIZE(Param->CandidateFaceName), 186 lplf->lfFaceName, ARRAYSIZE(lplf->lfFaceName)); 187 188 CandidateFont->Weight = lplf->lfWeight; 189 CandidateFont->Family = (lplf->lfPitchAndFamily & 0xF0); 190 191 CandidateFont->Size.X = lplf->lfWidth; 192 CandidateFont->Size.Y = lplf->lfHeight; 193 194 CandidateFont->CharSet = lplf->lfCharSet; 195 196 /* The font is found, stop enumeration */ 197 Param->FontFound = TRUE; 198 return FALSE; 199 } 200 else if (FontType != TRUETYPE_FONTTYPE) // !TM_IS_TT_FONT(lpntm->tmPitchAndFamily) 201 { 202 // FontIndex = i; 203 204 PFONT_DATA CandidateFont = &Param->CandidateFont; 205 206 CandidateFont->FaceName = Param->CandidateFaceName; 207 StringCchCopyNW(Param->CandidateFaceName, 208 ARRAYSIZE(Param->CandidateFaceName), 209 lplf->lfFaceName, ARRAYSIZE(lplf->lfFaceName)); 210 211 CandidateFont->Weight = lplf->lfWeight; 212 CandidateFont->Family = (lplf->lfPitchAndFamily & 0xF0); 213 214 CandidateFont->Size.X = lplf->lfWidth; 215 CandidateFont->Size.Y = lplf->lfHeight; 216 217 CandidateFont->CharSet = lplf->lfCharSet; 218 219 /* A close Raster Font fit was found; only the name doesn't match. 220 * Continue enumeration to see whether we can find better. */ 221 Param->FontFound = TRUE; 222 } 223 } 224 else // !Param->StrictSearch 225 { 226 /* 227 * Failed to find exact match, even after enumeration, so now 228 * try to find a font of same family and same size or bigger. 229 */ 230 231 if (IsCJKCodePage(Param->CodePage)) // g_fEastAsianSystem 232 { 233 if ((SearchFont->Family != 0) && 234 ((BYTE)SearchFont->Family != (lplf->lfPitchAndFamily & 0xF0))) 235 { 236 /* Continue enumeration */ 237 return TRUE; 238 } 239 240 if ((FontType != TRUETYPE_FONTTYPE) && // !TM_IS_TT_FONT(lpntm->tmPitchAndFamily) 241 (lplf->lfCharSet != SearchFont->CharSet)) 242 { 243 /* Continue enumeration */ 244 return TRUE; 245 } 246 } 247 else 248 { 249 if (// (SearchFont->Family != 0) && 250 ((BYTE)SearchFont->Family != (lplf->lfPitchAndFamily & 0xF0))) 251 { 252 /* Continue enumeration */ 253 return TRUE; 254 } 255 } 256 257 if ((lplf->lfHeight >= SearchFont->Size.Y) && 258 (lplf->lfWidth >= SearchFont->Size.X)) 259 { 260 /* Same family, size >= desired */ 261 // FontIndex = i; 262 263 PFONT_DATA CandidateFont = &Param->CandidateFont; 264 265 CandidateFont->FaceName = Param->CandidateFaceName; 266 StringCchCopyNW(Param->CandidateFaceName, 267 ARRAYSIZE(Param->CandidateFaceName), 268 lplf->lfFaceName, ARRAYSIZE(lplf->lfFaceName)); 269 270 CandidateFont->Weight = lplf->lfWeight; 271 CandidateFont->Family = (lplf->lfPitchAndFamily & 0xF0); 272 273 CandidateFont->Size.X = lplf->lfWidth; 274 CandidateFont->Size.Y = lplf->lfHeight; 275 276 CandidateFont->CharSet = lplf->lfCharSet; 277 278 /* The font is found, stop enumeration */ 279 Param->FontFound = TRUE; 280 return FALSE; 281 } 282 } 283 284 /* Continue enumeration */ 285 return TRUE; 286 } 287 288 /** 289 * @brief 290 * Finds a font suitable for the given code page, based on the current font 291 * and its characteristics provided in input. 292 * 293 * @param[in,out] FontData 294 * In input: The face name and characteristics of the font to search for, 295 * possibly getting a best match. 296 * In output: The face name and characteristics of the suitable font, 297 * in case of success. 298 * 299 * @param[in] CodePage 300 * The code page the font has to support. 301 * 302 * @return 303 * @b TRUE in case a suitable font has been found. Its name and characteristics 304 * are returned in @b FontData. @b FALSE if no suitable font has been found. 305 **/ 306 static BOOL 307 FindSuitableFont( 308 _Inout_ PFONT_DATA FontData, 309 _In_ UINT CodePage) 310 { 311 FIND_SUITABLE_FONT_PROC_PARAM Param; 312 _Inout_updates_z_(LF_FACESIZE) PWSTR FaceName; 313 HDC hDC; 314 LOGFONTW lf; 315 PTT_FONT_ENTRY FontEntry; 316 317 /* Save the original FaceName pointer */ 318 FaceName = FontData->FaceName; 319 320 /* Save our current search criteria */ 321 RtlZeroMemory(&Param, sizeof(Param)); 322 Param.SearchFont = *FontData; 323 324 Param.SearchFont.CharSet = CodePageToCharSet(CodePage); 325 Param.CodePage = CodePage; 326 327 if (/* !FaceName || */ !*FaceName) 328 { 329 /* Find and use a default Raster font */ 330 331 /* Use "Terminal" as the fallback */ 332 StringCchCopyW(FaceName, LF_FACESIZE, TERMINAL_FACENAME); 333 #if 0 334 // FIXME: CJK font choose workaround: Don't choose Asian 335 // charset font if there is no preferred font for CJK. 336 if (IsCJKCodePage(CodePage)) 337 FontData->CharSet = ANSI_CHARSET; 338 #endif 339 FontData->Family &= ~TMPF_TRUETYPE; 340 } 341 else if (wcscmp(FaceName, DEFAULT_TT_FONT_FACENAME) == 0) 342 { 343 /* Find and use a default TrueType font */ 344 FontEntry = FindCachedTTFont(NULL, CodePage); 345 if (FontEntry) 346 { 347 StringCchCopyW(FaceName, LF_FACESIZE, FontEntry->FaceName); 348 } 349 else 350 { 351 StringCchCopyW(FaceName, LF_FACESIZE, DEFAULT_NON_DBCS_FONTFACE); 352 } 353 FontData->Family |= TMPF_TRUETYPE; 354 } 355 356 /* Search for a TrueType alternative face name */ 357 FontEntry = FindCachedTTFont(FaceName, CodePage); 358 if (FontEntry) 359 { 360 /* NOTE: The font face names are case-sensitive */ 361 if (wcscmp(FontEntry->FaceName, FaceName) == 0) 362 Param.AltFaceName = FontEntry->FaceNameAlt; 363 else if (wcscmp(FontEntry->FaceNameAlt, FaceName) == 0) 364 Param.AltFaceName = FontEntry->FaceName; 365 } 366 else 367 { 368 Param.AltFaceName = FaceName; 369 } 370 371 /* Initialize the search: start with a strict search, then a relaxed one */ 372 Param.FontFound = FALSE; 373 374 Param.StrictSearch = TRUE; 375 SearchAgain: 376 /* 377 * Enumerate all fonts with the given character set. 378 * We will match them with the search criteria. 379 */ 380 RtlZeroMemory(&lf, sizeof(lf)); 381 lf.lfCharSet = Param.SearchFont.CharSet; 382 // lf.lfPitchAndFamily = FIXED_PITCH | FF_MODERN; 383 384 hDC = GetDC(NULL); 385 EnumFontFamiliesExW(hDC, &lf, (FONTENUMPROCW)FindSuitableFontProc, (LPARAM)&Param, 0); 386 ReleaseDC(NULL, hDC); 387 388 /* If we failed to find any font, search again with relaxed criteria */ 389 if (Param.StrictSearch && !Param.FontFound) 390 { 391 Param.StrictSearch = FALSE; 392 goto SearchAgain; 393 } 394 395 /* If no font was found again, return failure */ 396 if (!Param.FontFound) 397 return FALSE; 398 399 /* Return the font details */ 400 *FontData = Param.CandidateFont; 401 FontData->FaceName = FaceName; // Restore the original FaceName pointer. 402 StringCchCopyNW(FaceName, LF_FACESIZE, 403 Param.CandidateFaceName, 404 ARRAYSIZE(Param.CandidateFaceName)); 405 406 return TRUE; 407 } 408 409 /** 410 * @brief 411 * Validates and creates a suitable console font based on the font 412 * characteristics given in input. 413 * 414 * @param[in] FontData 415 * The face name and characteristics of the font to create. 416 * 417 * @param[in] CodePage 418 * The code page the font has to support. 419 * 420 * @return 421 * A GDI handle to the created font, or @b NULL in case of failure. 422 **/ 423 static HFONT 424 CreateConsoleFontWorker( 425 _In_ PFONT_DATA FontData, 426 _In_ UINT CodePage) 427 { 428 LOGFONTW lf; 429 430 RtlZeroMemory(&lf, sizeof(lf)); 431 432 lf.lfHeight = (LONG)(ULONG)FontData->Size.Y; 433 lf.lfWidth = (LONG)(ULONG)FontData->Size.X; 434 435 lf.lfEscapement = 0; 436 lf.lfOrientation = 0; // TA_BASELINE; // TA_RTLREADING; when the console supports RTL? 437 // lf.lfItalic = lf.lfUnderline = lf.lfStrikeOut = FALSE; 438 lf.lfWeight = FontData->Weight; 439 lf.lfCharSet = CodePageToCharSet(CodePage); 440 lf.lfOutPrecision = OUT_DEFAULT_PRECIS; 441 lf.lfClipPrecision = CLIP_DEFAULT_PRECIS; 442 lf.lfQuality = DEFAULT_QUALITY; 443 444 /* Set the mandatory flags and remove those that we do not support */ 445 lf.lfPitchAndFamily = (BYTE)( (FIXED_PITCH | FF_MODERN | FontData->Family) & 446 ~(VARIABLE_PITCH | FF_DECORATIVE | FF_ROMAN | FF_SCRIPT | FF_SWISS)); 447 448 if (!IsValidConsoleFont(FontData->FaceName, CodePage)) 449 return NULL; 450 451 StringCchCopyNW(lf.lfFaceName, ARRAYSIZE(lf.lfFaceName), 452 FontData->FaceName, LF_FACESIZE); 453 454 return CreateFontIndirectW(&lf); 455 } 456 457 /*****************************************************************************/ 458 459 /** 460 * @brief 461 * Validates and creates a suitable console font based on the font 462 * characteristics given in input. 463 * 464 * @param[in] Height 465 * The font height in cell units (pixels). 466 * 467 * @param[in,opt] Width 468 * The font width in cell units (pixels). 469 * 470 * @param[in,out] FaceName 471 * A pointer to a maximally @b LF_FACESIZE-sized buffer. 472 * In input: The buffer contains the face name of the font to try to create. 473 * In output: The buffer receives the face name of the font that has been 474 * created, in case of success. It may, or may not be, identical to the face 475 * name provided in input, in case a substitute font has been chosen. 476 * 477 * @param[in] FontWeight 478 * The font weight. 479 * 480 * @param[in] FontFamily 481 * The font family. 482 * 483 * @param[in] CodePage 484 * The code page the font has to support. 485 * 486 * @param[in] UseDefaultFallback 487 * Whether (@b TRUE) or not (@b FALSE) to use a default fallback font in case 488 * neither the specified font nor any substitute font could be found and 489 * created for the specified code page. 490 * 491 * @param[out] FontData 492 * The face name and characteristics of the created font. 493 * 494 * @return 495 * A GDI handle to the created font, or @b NULL in case of failure. 496 * 497 * @remark 498 * Similar to FindCreateFont() 499 * https://github.com/microsoft/terminal/blob/main/src/propsheet/fontdlg.cpp#L1113 500 * but: 501 * - does not support an internal font cache for now; 502 * - returns a font handle (and not a font index to the cache). 503 **/ 504 HFONT 505 CreateConsoleFontEx( 506 _In_ LONG Height, 507 _In_opt_ LONG Width, 508 _Inout_updates_z_(LF_FACESIZE) 509 PWSTR FaceName, 510 _In_ ULONG FontWeight, 511 _In_ ULONG FontFamily, 512 _In_ UINT CodePage, 513 _In_ BOOL UseDefaultFallback, 514 _Out_ PFONT_DATA FontData) 515 { 516 HFONT hFont; 517 518 FontData->FaceName = FaceName; 519 FontData->Weight = FontWeight; 520 FontData->Family = FontFamily; 521 /* NOTE: FontSize is always in cell height/width units (pixels) */ 522 FontData->Size.X = Width; 523 FontData->Size.Y = Height; 524 FontData->CharSet = 0; // CodePageToCharSet(CodePage); 525 526 if (/* !FaceName || */ !*FaceName || wcscmp(FaceName, DEFAULT_TT_FONT_FACENAME) == 0) 527 { 528 /* We do not have an actual font face name yet and should find one. 529 * Call FindSuitableFont() to determine the default font to use. */ 530 } 531 else 532 { 533 hFont = CreateConsoleFontWorker(FontData, CodePage); 534 if (hFont) 535 return hFont; 536 537 DBGFNT1("CreateConsoleFont('%S') failed - Try to find a suitable font...\n", 538 FaceName); 539 } 540 541 /* 542 * We could not create a font with the default settings. 543 * Try to find a suitable font and retry. 544 */ 545 if (!FindSuitableFont(FontData, CodePage)) 546 { 547 /* We could not find any suitable font, fall back 548 * to some default one if required to do so. */ 549 DBGFNT1("FindSuitableFont could not find anything - %s\n", 550 UseDefaultFallback ? "Falling back to 'Terminal'" 551 : "Bailing out"); 552 553 /* No fallback: no font! */ 554 if (!UseDefaultFallback) 555 return NULL; 556 557 // 558 // FIXME: See also !*FaceName case in FindSuitableFont(). 559 // 560 /* Use "Terminal" as the fallback */ 561 StringCchCopyW(FaceName, LF_FACESIZE, TERMINAL_FACENAME); 562 #if 0 563 // FIXME: CJK font choose workaround: Don't choose Asian 564 // charset font if there is no preferred font for CJK. 565 if (IsCJKCodePage(CodePage)) 566 FontData->CharSet = ANSI_CHARSET; 567 #endif 568 FontData->Family &= ~TMPF_TRUETYPE; 569 } 570 else 571 { 572 DBGFNT1("FindSuitableFont found: '%S', size (%d x %d)\n", 573 FaceName, FontData->Size.X, FontData->Size.Y); 574 } 575 576 /* Retry creating the font */ 577 hFont = CreateConsoleFontWorker(FontData, CodePage); 578 if (!hFont) 579 DBGFNT1("CreateConsoleFont('%S') failed\n", FaceName); 580 581 return hFont; 582 } 583 584 /** 585 * @brief 586 * A wrapper for CreateConsoleFontEx(). 587 * 588 * @param[in] Height 589 * The font height in cell units (pixels). 590 * 591 * @param[in,opt] Width 592 * The font width in cell units (pixels). 593 * 594 * @param[in,out] ConsoleInfo 595 * A pointer to console settings information, containing in particular 596 * (in input) the face name and characteristics of the font to create 597 * with the current console code page. 598 * In output, the font information gets updated. 599 * Note that a default fallback font is always being used in case neither 600 * the specified font nor any substitute font could be found and created 601 * for the specified code page. 602 * 603 * @return 604 * A GDI handle to the created font, or @b NULL in case of failure. 605 * 606 * @see CreateConsoleFontEx(), CreateConsoleFont() 607 **/ 608 HFONT 609 CreateConsoleFont2( 610 _In_ LONG Height, 611 _In_opt_ LONG Width, 612 _Inout_ PCONSOLE_STATE_INFO ConsoleInfo) 613 { 614 FONT_DATA FontData; 615 HFONT hFont; 616 617 hFont = CreateConsoleFontEx(Height, 618 Width, 619 ConsoleInfo->FaceName, 620 ConsoleInfo->FontWeight, 621 ConsoleInfo->FontFamily, 622 ConsoleInfo->CodePage, 623 TRUE, // UseDefaultFallback 624 &FontData); 625 if (hFont) 626 { 627 ConsoleInfo->FontWeight = FontData.Weight; 628 ConsoleInfo->FontFamily = FontData.Family; 629 } 630 631 return hFont; 632 } 633 634 /** 635 * @brief 636 * A wrapper for CreateConsoleFontEx(). 637 * 638 * @param[in,out] ConsoleInfo 639 * A pointer to console settings information, containing in particular 640 * (in input) the face name and characteristics of the font to create 641 * with the current console code page. 642 * In output, the font information gets updated. 643 * Note that a default fallback font is always being used in case neither 644 * the specified font nor any substitute font could be found and created 645 * for the specified code page. 646 * 647 * @return 648 * A GDI handle to the created font, or @b NULL in case of failure. 649 * 650 * @see CreateConsoleFontEx(), CreateConsoleFont2() 651 **/ 652 HFONT 653 CreateConsoleFont( 654 _Inout_ PCONSOLE_STATE_INFO ConsoleInfo) 655 { 656 /* 657 * Format: 658 * Width = FontSize.X = LOWORD(FontSize); 659 * Height = FontSize.Y = HIWORD(FontSize); 660 */ 661 /* NOTE: FontSize is always in cell height/width units (pixels) */ 662 return CreateConsoleFont2((LONG)(ULONG)ConsoleInfo->FontSize.Y, 663 (LONG)(ULONG)ConsoleInfo->FontSize.X, 664 ConsoleInfo); 665 } 666 667 /** 668 * @brief 669 * Retrieves the cell size for a console font. 670 * 671 * @param[in,opt] hDC 672 * An optional GDI device context handle. 673 * 674 * @param[in] hFont 675 * The GDI handle to the font. 676 * 677 * @param[out] Height 678 * In case of success, receives the cell height size (in pixels). 679 * 680 * @param[out] Width 681 * In case of success, receives the cell height size (in pixels). 682 * 683 * @return 684 * @b TRUE if success, @b FALSE in case of failure. 685 **/ 686 _Success_(return) 687 BOOL 688 GetFontCellSize( 689 _In_opt_ HDC hDC, 690 _In_ HFONT hFont, 691 _Out_ PUINT Height, 692 _Out_ PUINT Width) 693 { 694 BOOL Success = FALSE; 695 HDC hOrgDC = hDC; 696 HFONT hOldFont; 697 // LONG LogSize, PointSize; 698 LONG CharWidth, CharHeight; 699 TEXTMETRICW tm; 700 // SIZE CharSize; 701 702 if (!hDC) 703 hDC = GetDC(NULL); 704 705 hOldFont = SelectObject(hDC, hFont); 706 if (hOldFont == NULL) 707 { 708 DBGFNT1("GetFontCellSize: SelectObject failed\n"); 709 goto Quit; 710 } 711 712 /* 713 * See also: Display_SetTypeFace in applications/fontview/display.c 714 */ 715 716 /* 717 * Note that the method with GetObjectW just returns 718 * the original parameters with which the font was created. 719 */ 720 if (!GetTextMetricsW(hDC, &tm)) 721 { 722 DBGFNT1("GetFontCellSize: GetTextMetrics failed\n"); 723 goto Cleanup; 724 } 725 726 CharHeight = tm.tmHeight + tm.tmExternalLeading; 727 728 #if 0 729 /* Measure real char width more precisely if possible */ 730 if (GetTextExtentPoint32W(hDC, L"R", 1, &CharSize)) 731 CharWidth = CharSize.cx; 732 #else 733 CharWidth = tm.tmAveCharWidth; // tm.tmMaxCharWidth; 734 #endif 735 736 #if 0 737 /*** Logical to Point size ***/ 738 LogSize = tm.tmHeight - tm.tmInternalLeading; 739 PointSize = MulDiv(LogSize, 72, GetDeviceCaps(hDC, LOGPIXELSY)); 740 /*****************************/ 741 #endif 742 743 *Height = (UINT)CharHeight; 744 *Width = (UINT)CharWidth; 745 Success = TRUE; 746 747 Cleanup: 748 SelectObject(hDC, hOldFont); 749 Quit: 750 if (!hOrgDC) 751 ReleaseDC(NULL, hDC); 752 753 return Success; 754 } 755 756 /** 757 * @brief 758 * Validates whether a given font can be supported in the console, 759 * under the specified code page. 760 * 761 * @param[in] lplf 762 * @param[in] lpntm 763 * @param[in] FontType 764 * The GDI font characteristics of the font to validate. 765 * 766 * @param[in] CodePage 767 * The code page the font has to support. 768 * 769 * @return 770 * @b TRUE if the font is valid and supported in the console, 771 * @b FALSE if not. 772 * 773 * @remark 774 * Equivalent of the font validation tests in FontEnumForV2Console() 775 * (or the more restrictive ones in FontEnum()) 776 * https://github.com/microsoft/terminal/blob/main/src/propsheet/misc.cpp#L465 777 * https://github.com/microsoft/terminal/blob/main/src/propsheet/misc.cpp#L607 778 * 779 * @see IsValidConsoleFont() 780 **/ 781 BOOL 782 IsValidConsoleFont2( 783 _In_ PLOGFONTW lplf, 784 _In_ PNEWTEXTMETRICW lpntm, 785 _In_ DWORD FontType, 786 _In_ UINT CodePage) 787 { 788 LPCWSTR FaceName = lplf->lfFaceName; 789 790 /* 791 * According to: https://web.archive.org/web/20140901124501/http://support.microsoft.com/kb/247815 792 * "Necessary criteria for fonts to be available in a command window", 793 * the criteria for console-eligible fonts are as follows: 794 * - The font must be a fixed-pitch font. 795 * - The font cannot be an italic font. 796 * - The font cannot have a negative A or C space. 797 * - If it is a TrueType font, it must be FF_MODERN. 798 * - If it is not a TrueType font, it must be OEM_CHARSET. 799 * 800 * Non documented: vertical fonts are forbidden (their name start with a '@'). 801 * 802 * Additional criteria for Asian installations: 803 * - If it is not a TrueType font, the face name must be "Terminal". 804 * - If it is an Asian TrueType font, it must also be an Asian character set. 805 * 806 * See also Raymond Chen's blog: https://devblogs.microsoft.com/oldnewthing/?p=26843 807 * and MIT-licensed Microsoft Terminal source code: https://github.com/microsoft/terminal/blob/main/src/propsheet/misc.cpp 808 * for other details. 809 * 810 * To install additional TrueType fonts to be available for the console, 811 * add entries of type REG_SZ named "0", "00" etc... in: 812 * HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Console\TrueTypeFont 813 * The names of the fonts listed there should match those in: 814 * HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Fonts 815 */ 816 817 /* 818 * In ReactOS we relax some of the criteria: 819 * - We allow fixed-pitch FF_MODERN (Monospace) TrueType fonts 820 * that can be italic or have negative A or C space. 821 * - If it is not a TrueType font, it can be from another character set 822 * than OEM_CHARSET. When an Asian codepage is active however, we require 823 * that this non-TrueType font has an Asian character set. 824 */ 825 826 /* Reject variable-width fonts ... */ 827 if ( ( ((lplf->lfPitchAndFamily & 0x03) != FIXED_PITCH) 828 #if 0 /* Reject italic and TrueType fonts with negative A or C space ... */ 829 || (lplf->lfItalic) 830 || !(lpntm->ntmFlags & NTM_NONNEGATIVE_AC) 831 #endif 832 ) && 833 /* ... if they are not in the list of additional TrueType fonts to include */ 834 !IsAdditionalTTFont(FaceName) ) 835 { 836 DBGFNT("Font '%S' rejected because it%s (lfPitchAndFamily = %d)\n", 837 FaceName, 838 !(lplf->lfPitchAndFamily & FIXED_PITCH) ? "'s not FIXED_PITCH" 839 : (!(lpntm->ntmFlags & NTM_NONNEGATIVE_AC) ? " has negative A or C space" 840 : " is broken"), 841 lplf->lfPitchAndFamily); 842 return FALSE; 843 } 844 845 /* Reject TrueType fonts that are not FF_MODERN */ 846 if ((FontType == TRUETYPE_FONTTYPE) && ((lplf->lfPitchAndFamily & 0xF0) != FF_MODERN)) 847 { 848 DBGFNT("TrueType font '%S' rejected because it's not FF_MODERN (lfPitchAndFamily = %d)\n", 849 FaceName, lplf->lfPitchAndFamily); 850 return FALSE; 851 } 852 853 /* Reject vertical fonts (tategaki) */ 854 if (FaceName[0] == L'@') 855 { 856 DBGFNT("Font '%S' rejected because it's vertical\n", FaceName); 857 return FALSE; 858 } 859 860 /* Is the current code page Chinese, Japanese or Korean? */ 861 if (IsCJKCodePage(CodePage)) 862 { 863 /* It's CJK */ 864 865 if (FontType == TRUETYPE_FONTTYPE) 866 { 867 /* 868 * Here we are inclusive and check for any CJK character set, 869 * instead of looking just at the current one via CodePageToCharSet(). 870 */ 871 if (!IsCJKCharSet(lplf->lfCharSet)) 872 { 873 DBGFNT("TrueType font '%S' rejected because it's not Asian charset (lfCharSet = %d)\n", 874 FaceName, lplf->lfCharSet); 875 return FALSE; 876 } 877 878 /* 879 * If this is a cached TrueType font that is used only for certain 880 * code pages, verify that the charset it claims is the correct one. 881 * 882 * Since there may be multiple entries for a cached TrueType font, 883 * a general one (code page == 0) and one or more for explicit 884 * code pages, we need to perform two search queries instead of 885 * just one and retrieving the code page for this entry. 886 */ 887 if (IsAdditionalTTFont(FaceName) && !IsAdditionalTTFontCP(FaceName, 0) && 888 !IsCJKCharSet(lplf->lfCharSet)) 889 { 890 DBGFNT("Cached TrueType font '%S' rejected because it claims a code page that is not Asian charset (lfCharSet = %d)\n", 891 FaceName, lplf->lfCharSet); 892 return FALSE; 893 } 894 } 895 else 896 { 897 /* Reject non-TrueType fonts that do not have an Asian character set */ 898 if (!IsCJKCharSet(lplf->lfCharSet) && (lplf->lfCharSet != OEM_CHARSET)) 899 { 900 DBGFNT("Non-TrueType font '%S' rejected because it's not Asian charset or OEM_CHARSET (lfCharSet = %d)\n", 901 FaceName, lplf->lfCharSet); 902 return FALSE; 903 } 904 905 /* Reject non-TrueType fonts that are not Terminal */ 906 if (wcscmp(FaceName, TERMINAL_FACENAME) != 0) 907 { 908 DBGFNT("Non-TrueType font '%S' rejected because it's not 'Terminal'\n", FaceName); 909 return FALSE; 910 } 911 } 912 } 913 else 914 { 915 /* Not CJK */ 916 917 /* Reject non-TrueType fonts that are not OEM or similar */ 918 if ((FontType != TRUETYPE_FONTTYPE) && 919 (lplf->lfCharSet != ANSI_CHARSET) && 920 (lplf->lfCharSet != DEFAULT_CHARSET) && 921 (lplf->lfCharSet != OEM_CHARSET)) 922 { 923 DBGFNT("Non-TrueType font '%S' rejected because it's not ANSI_CHARSET or DEFAULT_CHARSET or OEM_CHARSET (lfCharSet = %d)\n", 924 FaceName, lplf->lfCharSet); 925 return FALSE; 926 } 927 } 928 929 /* All good */ 930 return TRUE; 931 } 932 933 typedef struct _IS_VALID_CONSOLE_FONT_PARAM 934 { 935 BOOL IsValidFont; 936 UINT CodePage; 937 } IS_VALID_CONSOLE_FONT_PARAM, *PIS_VALID_CONSOLE_FONT_PARAM; 938 939 /** 940 * @brief EnumFontFamiliesEx() callback helper for IsValidConsoleFont(). 941 **/ 942 static BOOL CALLBACK 943 IsValidConsoleFontProc( 944 _In_ PLOGFONTW lplf, 945 _In_ PNEWTEXTMETRICW lpntm, 946 _In_ DWORD FontType, 947 _In_ LPARAM lParam) 948 { 949 PIS_VALID_CONSOLE_FONT_PARAM Param = (PIS_VALID_CONSOLE_FONT_PARAM)lParam; 950 Param->IsValidFont = IsValidConsoleFont2(lplf, lpntm, FontType, Param->CodePage); 951 952 /* Stop the enumeration now */ 953 return FALSE; 954 } 955 956 /** 957 * @brief 958 * Validates whether a given font can be supported in the console, 959 * under the specified code page. 960 * 961 * @param[in] FaceName 962 * The face name of the font to validate. 963 * 964 * @param[in] CodePage 965 * The code page the font has to support. 966 * 967 * @return 968 * @b TRUE if the font is valid and supported in the console, 969 * @b FALSE if not. 970 * 971 * @see IsValidConsoleFont2() 972 **/ 973 BOOL 974 IsValidConsoleFont( 975 // _In_reads_or_z_(LF_FACESIZE) 976 _In_ PCWSTR FaceName, 977 _In_ UINT CodePage) 978 { 979 IS_VALID_CONSOLE_FONT_PARAM Param; 980 HDC hDC; 981 LOGFONTW lf; 982 983 Param.IsValidFont = FALSE; 984 Param.CodePage = CodePage; 985 986 RtlZeroMemory(&lf, sizeof(lf)); 987 lf.lfCharSet = CodePageToCharSet(CodePage); 988 // lf.lfPitchAndFamily = FIXED_PITCH | FF_MODERN; 989 StringCchCopyW(lf.lfFaceName, ARRAYSIZE(lf.lfFaceName), FaceName); 990 991 hDC = GetDC(NULL); 992 EnumFontFamiliesExW(hDC, &lf, (FONTENUMPROCW)IsValidConsoleFontProc, (LPARAM)&Param, 0); 993 ReleaseDC(NULL, hDC); 994 995 return Param.IsValidFont; 996 } 997 998 999 /** 1000 * @brief 1001 * Initializes the console TrueType font cache. 1002 * 1003 * @remark 1004 * To install additional TrueType fonts to be available for the console, 1005 * add entries of type REG_SZ named "0", "00" etc... in: 1006 * HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Console\TrueTypeFont 1007 * The names of the fonts listed there should match those in: 1008 * HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Fonts 1009 * 1010 * @return None. 1011 **/ 1012 VOID 1013 InitTTFontCache(VOID) 1014 { 1015 LRESULT lResult; 1016 HKEY hKey; 1017 DWORD dwIndex, dwType; 1018 WCHAR szValueName[MAX_PATH]; 1019 DWORD cchValueName; 1020 WCHAR szValue[LF_FACESIZE] = L""; 1021 DWORD cbValue; 1022 UINT CodePage; 1023 PTT_FONT_ENTRY FontEntry; 1024 PWCHAR pszNext; 1025 1026 if (TTFontCache.Next != NULL) 1027 return; 1028 // TTFontCache.Next = NULL; 1029 1030 /* Open the Console\TrueTypeFont key */ 1031 // "\\Registry\\Machine\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Console\\TrueTypeFont" 1032 if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, 1033 L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Console\\TrueTypeFont", 1034 0, 1035 KEY_QUERY_VALUE, 1036 &hKey) != ERROR_SUCCESS) 1037 { 1038 return; 1039 } 1040 1041 /* Enumerate all the available TrueType console fonts */ 1042 for (dwIndex = 0, cchValueName = ARRAYSIZE(szValueName), 1043 cbValue = sizeof(szValue); 1044 (lResult = RegEnumValueW(hKey, dwIndex, 1045 szValueName, &cchValueName, 1046 NULL, &dwType, 1047 (PBYTE)szValue, &cbValue)) != ERROR_NO_MORE_ITEMS; 1048 ++dwIndex, cchValueName = ARRAYSIZE(szValueName), 1049 cbValue = sizeof(szValue)) 1050 { 1051 /* Ignore if we failed for another reason, e.g. because 1052 * the value name is too long (and thus, invalid). */ 1053 if (lResult != ERROR_SUCCESS) 1054 continue; 1055 1056 /* Validate the value name (exclude the unnamed value) */ 1057 if (!cchValueName || (*szValueName == UNICODE_NULL)) 1058 continue; 1059 /* Too large value names have already been handled with ERROR_MORE_DATA */ 1060 ASSERT((cchValueName < ARRAYSIZE(szValueName)) && 1061 (szValueName[cchValueName] == UNICODE_NULL)); 1062 1063 /* Only (multi-)string values are supported */ 1064 if ((dwType != REG_SZ) && (dwType != REG_MULTI_SZ)) 1065 continue; 1066 1067 /* The value name is a code page (in decimal), validate it */ 1068 CodePage = wcstoul(szValueName, &pszNext, 10); 1069 if (*pszNext) 1070 continue; // Non-numerical garbage followed... 1071 // IsValidCodePage(CodePage); 1072 1073 FontEntry = RtlAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*FontEntry)); 1074 if (!FontEntry) 1075 { 1076 DBGFNT1("InitTTFontCache: Failed to allocate memory, continuing...\n"); 1077 continue; 1078 } 1079 1080 FontEntry->CodePage = CodePage; 1081 1082 pszNext = szValue; 1083 1084 /* Check whether bold is disabled for this font */ 1085 if (*pszNext == BOLD_MARK) 1086 { 1087 FontEntry->DisableBold = TRUE; 1088 ++pszNext; 1089 } 1090 else 1091 { 1092 FontEntry->DisableBold = FALSE; 1093 } 1094 1095 /* Copy the font name */ 1096 StringCchCopyNW(FontEntry->FaceName, ARRAYSIZE(FontEntry->FaceName), 1097 pszNext, wcslen(pszNext)); 1098 1099 if (dwType == REG_MULTI_SZ) 1100 { 1101 /* There may be an alternate face name as the second string */ 1102 pszNext += wcslen(pszNext) + 1; 1103 1104 /* Check whether bold is disabled for this font */ 1105 if (*pszNext == BOLD_MARK) 1106 { 1107 FontEntry->DisableBold = TRUE; 1108 ++pszNext; 1109 } 1110 // else, keep the original setting. 1111 1112 /* Copy the alternate font name */ 1113 StringCchCopyNW(FontEntry->FaceNameAlt, ARRAYSIZE(FontEntry->FaceNameAlt), 1114 pszNext, wcslen(pszNext)); 1115 } 1116 1117 PushEntryList(&TTFontCache, &FontEntry->Entry); 1118 } 1119 1120 /* Close the key and quit */ 1121 RegCloseKey(hKey); 1122 } 1123 1124 /** 1125 * @brief 1126 * Clears the console TrueType font cache. 1127 * 1128 * @return None. 1129 **/ 1130 VOID 1131 ClearTTFontCache(VOID) 1132 { 1133 PSINGLE_LIST_ENTRY Entry; 1134 PTT_FONT_ENTRY FontEntry; 1135 1136 while (TTFontCache.Next != NULL) 1137 { 1138 Entry = PopEntryList(&TTFontCache); 1139 FontEntry = CONTAINING_RECORD(Entry, TT_FONT_ENTRY, Entry); 1140 RtlFreeHeap(RtlGetProcessHeap(), 0, FontEntry); 1141 } 1142 TTFontCache.Next = NULL; 1143 } 1144 1145 /** 1146 * @brief 1147 * Refreshes the console TrueType font cache, 1148 * by clearing and re-initializing it. 1149 * 1150 * @return None. 1151 **/ 1152 VOID 1153 RefreshTTFontCache(VOID) 1154 { 1155 ClearTTFontCache(); 1156 InitTTFontCache(); 1157 } 1158 1159 /** 1160 * @brief 1161 * Searches for a font in the console TrueType font cache, 1162 * with the specified code page. 1163 * 1164 * @param[in,opt] FaceName 1165 * An optional pointer to a maximally @b LF_FACESIZE-sized buffer. 1166 * The buffer contains the face name of the font to search for. 1167 * 1168 * - If FaceName != NULL, search for the named font that should 1169 * match the provided code page (when CodePage != INVALID_CP). 1170 * 1171 * - If FaceName == NULL, search for a font with the provided 1172 * code page. In this case, CodePage cannot be == INVALID_CP, 1173 * otherwise the search fails. 1174 * 1175 * @param[in] CodePage 1176 * The code page the font has to support, or @b INVALID_CP when 1177 * searching a font by face name only. 1178 * 1179 * @return 1180 * A pointer to the cache entry for the font, or @b NULL if not found. 1181 **/ 1182 PTT_FONT_ENTRY 1183 FindCachedTTFont( 1184 _In_reads_or_z_opt_(LF_FACESIZE) 1185 PCWSTR FaceName, 1186 _In_ UINT CodePage) 1187 { 1188 PSINGLE_LIST_ENTRY Entry; 1189 PTT_FONT_ENTRY FontEntry; 1190 1191 if (FaceName) 1192 { 1193 /* Search for the named font */ 1194 for (Entry = TTFontCache.Next; 1195 Entry != NULL; 1196 Entry = Entry->Next) 1197 { 1198 FontEntry = CONTAINING_RECORD(Entry, TT_FONT_ENTRY, Entry); 1199 1200 /* NOTE: The font face names are case-sensitive */ 1201 if ((wcscmp(FontEntry->FaceName , FaceName) == 0) || 1202 (wcscmp(FontEntry->FaceNameAlt, FaceName) == 0)) 1203 { 1204 /* Return the font if we don't search by code page, or when they match */ 1205 if ((CodePage == INVALID_CP) || (CodePage == FontEntry->CodePage)) 1206 { 1207 return FontEntry; 1208 } 1209 } 1210 } 1211 } 1212 else if (CodePage != INVALID_CP) 1213 { 1214 /* Search for a font with the specified code page */ 1215 for (Entry = TTFontCache.Next; 1216 Entry != NULL; 1217 Entry = Entry->Next) 1218 { 1219 FontEntry = CONTAINING_RECORD(Entry, TT_FONT_ENTRY, Entry); 1220 1221 /* Return the font if the code pages match */ 1222 if (CodePage == FontEntry->CodePage) 1223 return FontEntry; 1224 } 1225 } 1226 1227 return NULL; 1228 } 1229 1230 /* EOF */ 1231