reactos/win32ss/user/winsrv/concfg/font.c
Hermès Bélusca-Maïto 2ae45e0985
[CONCFG:FONT] Implement support for the Windows 10 "__DefaultTTFont__" special FaceName value. (#4337)
This special value tells the console to default to whichever font
is deemed most appropriate for the current code page.

Add Doxygen documentation for FindCachedTTFont().
2022-02-08 15:59:10 +01:00

1231 lines
38 KiB
C

/*
* PROJECT: ReactOS Console Server DLL
* LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
* PURPOSE: Console GDI Fonts Management.
* COPYRIGHT: Copyright 2017-2022 Hermès Bélusca-Maïto
* Copyright 2017 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com)
*/
/* INCLUDES *******************************************************************/
#include "precomp.h"
#include <winuser.h>
#include "settings.h"
#include "font.h"
// #include "concfg.h"
#define NDEBUG
#include <debug.h>
#define DBGFNT DPRINT
#define DBGFNT1 DPRINT1
/* GLOBALS ********************************************************************/
#define TERMINAL_FACENAME L"Terminal"
#define DEFAULT_NON_DBCS_FONTFACE L"Lucida Console" // L"Consolas"
#define DEFAULT_TT_FONT_FACENAME L"__DefaultTTFont__"
/* TrueType font list cache */
SINGLE_LIST_ENTRY TTFontCache = { NULL };
// NOTE: Used to tag code that makes sense only with a font cache.
// #define FONT_CACHE_PRESENT
/* FUNCTIONS ******************************************************************/
/**
* @brief
* Retrieves the character set associated with a given code page.
*
* @param[in] CodePage
* The code page to convert.
*
* @return
* The character set corresponding to the code page, or @b DEFAULT_CHARSET.
**/
BYTE
CodePageToCharSet(
_In_ UINT CodePage)
{
CHARSETINFO CharInfo;
if (TranslateCharsetInfo(UlongToPtr(CodePage), &CharInfo, TCI_SRCCODEPAGE))
return (BYTE)CharInfo.ciCharset;
else
return DEFAULT_CHARSET;
}
/*****************************************************************************/
typedef struct _FIND_SUITABLE_FONT_PROC_PARAM
{
/* Search criteria */
_In_reads_or_z_(LF_FACESIZE) PCWSTR AltFaceName;
FONT_DATA SearchFont;
UINT CodePage;
BOOL StrictSearch; // TRUE to do strict search; FALSE for relaxed criteria.
/* Candidate font data */
BOOL FontFound; // TRUE/FALSE if we have/haven't found a suitable font.
FONT_DATA CandidateFont;
WCHAR CandidateFaceName[LF_FACESIZE];
} FIND_SUITABLE_FONT_PROC_PARAM, *PFIND_SUITABLE_FONT_PROC_PARAM;
#define TM_IS_TT_FONT(x) (((x) & TMPF_TRUETYPE) == TMPF_TRUETYPE)
#define SIZE_EQUAL(s1, s2) (((s1).X == (s2).X) && ((s1).Y == (s2).Y))
/**
* @brief EnumFontFamiliesEx() callback helper for FindSuitableFont().
*
* @remark
* It implements a nearly-identical console-suitable font search
* algorithm based on the one from FindCreateFont()
* https://github.com/microsoft/terminal/blob/main/src/propsheet/fontdlg.cpp#L1113
* excepting that for now, it does not support an internal font cache.
**/
static BOOL CALLBACK
FindSuitableFontProc(
_In_ PLOGFONTW lplf,
_In_ PNEWTEXTMETRICW lpntm,
_In_ DWORD FontType,
_In_ LPARAM lParam)
{
PFIND_SUITABLE_FONT_PROC_PARAM Param = (PFIND_SUITABLE_FONT_PROC_PARAM)lParam;
PFONT_DATA SearchFont = &Param->SearchFont;
if (!IsValidConsoleFont2(lplf, lpntm, FontType, Param->CodePage))
{
/* This font does not suit us; continue enumeration */
return TRUE;
}
#ifndef FONT_CACHE_PRESENT
/*
* Since we don't cache all the possible font sizes for TrueType fonts,
* we cannot check our requested size (and weight) against the enumerated
* one; therefore reset the enumerated values to the requested ones.
* On the contrary, Raster fonts get their specific font sizes (and weights)
* enumerated separately, so for them we can keep the enumerated values.
*/
if (FontType == TRUETYPE_FONTTYPE)
{
lplf->lfHeight = SearchFont->Size.Y;
lplf->lfWidth = 0; // SearchFont->Size.X;
lplf->lfWeight = FW_NORMAL;
}
#endif
if (Param->StrictSearch)
{
/*
* Find whether this is an exact match.
*/
/* If looking for a particular family, skip non-matches */
if ((SearchFont->Family != 0) &&
((BYTE)SearchFont->Family != (lplf->lfPitchAndFamily & 0xF0)))
{
/* Continue enumeration */
return TRUE;
}
/* Skip non-matching sizes */
#if 0
if ((FontInfo[i].SizeWant.Y != Size.Y) &&
!SIZE_EQUAL(FontInfo[i].Size, Size))
#endif
if ((lplf->lfHeight != SearchFont->Size.Y) &&
!(lplf->lfWidth == SearchFont->Size.X &&
lplf->lfHeight == SearchFont->Size.Y))
{
/* Continue enumeration */
return TRUE;
}
/* Skip non-matching weights */
if ((SearchFont->Weight != 0) &&
(SearchFont->Weight != lplf->lfWeight))
{
/* Continue enumeration */
return TRUE;
}
/* NOTE: We are making the font enumeration at fixed CharSet,
* with the one specified in the parameter block. */
ASSERT(lplf->lfCharSet == SearchFont->CharSet);
if ((FontType != TRUETYPE_FONTTYPE) && // !TM_IS_TT_FONT(lpntm->tmPitchAndFamily)
(lplf->lfCharSet != SearchFont->CharSet) &&
!(lplf->lfCharSet == OEM_CHARSET && IsCJKCodePage(Param->CodePage))) // g_fEastAsianSystem
{
/* Continue enumeration */
return TRUE;
}
/*
* Size (and maybe family) match. If we don't care about the name or
* if it matches, use this font. Otherwise, if name doesn't match and
* it is a raster font, consider it.
*
* NOTE: The font face names are case-sensitive.
*/
if (!SearchFont->FaceName || !*(SearchFont->FaceName) ||
(wcscmp(lplf->lfFaceName, SearchFont->FaceName) == 0) ||
(wcscmp(lplf->lfFaceName, Param->AltFaceName) == 0))
{
// FontIndex = i;
PFONT_DATA CandidateFont = &Param->CandidateFont;
CandidateFont->FaceName = Param->CandidateFaceName;
StringCchCopyNW(Param->CandidateFaceName,
ARRAYSIZE(Param->CandidateFaceName),
lplf->lfFaceName, ARRAYSIZE(lplf->lfFaceName));
CandidateFont->Weight = lplf->lfWeight;
CandidateFont->Family = (lplf->lfPitchAndFamily & 0xF0);
CandidateFont->Size.X = lplf->lfWidth;
CandidateFont->Size.Y = lplf->lfHeight;
CandidateFont->CharSet = lplf->lfCharSet;
/* The font is found, stop enumeration */
Param->FontFound = TRUE;
return FALSE;
}
else if (FontType != TRUETYPE_FONTTYPE) // !TM_IS_TT_FONT(lpntm->tmPitchAndFamily)
{
// FontIndex = i;
PFONT_DATA CandidateFont = &Param->CandidateFont;
CandidateFont->FaceName = Param->CandidateFaceName;
StringCchCopyNW(Param->CandidateFaceName,
ARRAYSIZE(Param->CandidateFaceName),
lplf->lfFaceName, ARRAYSIZE(lplf->lfFaceName));
CandidateFont->Weight = lplf->lfWeight;
CandidateFont->Family = (lplf->lfPitchAndFamily & 0xF0);
CandidateFont->Size.X = lplf->lfWidth;
CandidateFont->Size.Y = lplf->lfHeight;
CandidateFont->CharSet = lplf->lfCharSet;
/* A close Raster Font fit was found; only the name doesn't match.
* Continue enumeration to see whether we can find better. */
Param->FontFound = TRUE;
}
}
else // !Param->StrictSearch
{
/*
* Failed to find exact match, even after enumeration, so now
* try to find a font of same family and same size or bigger.
*/
if (IsCJKCodePage(Param->CodePage)) // g_fEastAsianSystem
{
if ((SearchFont->Family != 0) &&
((BYTE)SearchFont->Family != (lplf->lfPitchAndFamily & 0xF0)))
{
/* Continue enumeration */
return TRUE;
}
if ((FontType != TRUETYPE_FONTTYPE) && // !TM_IS_TT_FONT(lpntm->tmPitchAndFamily)
(lplf->lfCharSet != SearchFont->CharSet))
{
/* Continue enumeration */
return TRUE;
}
}
else
{
if (// (SearchFont->Family != 0) &&
((BYTE)SearchFont->Family != (lplf->lfPitchAndFamily & 0xF0)))
{
/* Continue enumeration */
return TRUE;
}
}
if ((lplf->lfHeight >= SearchFont->Size.Y) &&
(lplf->lfWidth >= SearchFont->Size.X))
{
/* Same family, size >= desired */
// FontIndex = i;
PFONT_DATA CandidateFont = &Param->CandidateFont;
CandidateFont->FaceName = Param->CandidateFaceName;
StringCchCopyNW(Param->CandidateFaceName,
ARRAYSIZE(Param->CandidateFaceName),
lplf->lfFaceName, ARRAYSIZE(lplf->lfFaceName));
CandidateFont->Weight = lplf->lfWeight;
CandidateFont->Family = (lplf->lfPitchAndFamily & 0xF0);
CandidateFont->Size.X = lplf->lfWidth;
CandidateFont->Size.Y = lplf->lfHeight;
CandidateFont->CharSet = lplf->lfCharSet;
/* The font is found, stop enumeration */
Param->FontFound = TRUE;
return FALSE;
}
}
/* Continue enumeration */
return TRUE;
}
/**
* @brief
* Finds a font suitable for the given code page, based on the current font
* and its characteristics provided in input.
*
* @param[in,out] FontData
* In input: The face name and characteristics of the font to search for,
* possibly getting a best match.
* In output: The face name and characteristics of the suitable font,
* in case of success.
*
* @param[in] CodePage
* The code page the font has to support.
*
* @return
* @b TRUE in case a suitable font has been found. Its name and characteristics
* are returned in @b FontData. @b FALSE if no suitable font has been found.
**/
static BOOL
FindSuitableFont(
_Inout_ PFONT_DATA FontData,
_In_ UINT CodePage)
{
FIND_SUITABLE_FONT_PROC_PARAM Param;
_Inout_updates_z_(LF_FACESIZE) PWSTR FaceName;
HDC hDC;
LOGFONTW lf;
PTT_FONT_ENTRY FontEntry;
/* Save the original FaceName pointer */
FaceName = FontData->FaceName;
/* Save our current search criteria */
RtlZeroMemory(&Param, sizeof(Param));
Param.SearchFont = *FontData;
Param.SearchFont.CharSet = CodePageToCharSet(CodePage);
Param.CodePage = CodePage;
if (/* !FaceName || */ !*FaceName)
{
/* Find and use a default Raster font */
/* Use "Terminal" as the fallback */
StringCchCopyW(FaceName, LF_FACESIZE, TERMINAL_FACENAME);
#if 0
// FIXME: CJK font choose workaround: Don't choose Asian
// charset font if there is no preferred font for CJK.
if (IsCJKCodePage(CodePage))
FontData->CharSet = ANSI_CHARSET;
#endif
FontData->Family &= ~TMPF_TRUETYPE;
}
else if (wcscmp(FaceName, DEFAULT_TT_FONT_FACENAME) == 0)
{
/* Find and use a default TrueType font */
FontEntry = FindCachedTTFont(NULL, CodePage);
if (FontEntry)
{
StringCchCopyW(FaceName, LF_FACESIZE, FontEntry->FaceName);
}
else
{
StringCchCopyW(FaceName, LF_FACESIZE, DEFAULT_NON_DBCS_FONTFACE);
}
FontData->Family |= TMPF_TRUETYPE;
}
/* Search for a TrueType alternative face name */
FontEntry = FindCachedTTFont(FaceName, CodePage);
if (FontEntry)
{
/* NOTE: The font face names are case-sensitive */
if (wcscmp(FontEntry->FaceName, FaceName) == 0)
Param.AltFaceName = FontEntry->FaceNameAlt;
else if (wcscmp(FontEntry->FaceNameAlt, FaceName) == 0)
Param.AltFaceName = FontEntry->FaceName;
}
else
{
Param.AltFaceName = FaceName;
}
/* Initialize the search: start with a strict search, then a relaxed one */
Param.FontFound = FALSE;
Param.StrictSearch = TRUE;
SearchAgain:
/*
* Enumerate all fonts with the given character set.
* We will match them with the search criteria.
*/
RtlZeroMemory(&lf, sizeof(lf));
lf.lfCharSet = Param.SearchFont.CharSet;
// lf.lfPitchAndFamily = FIXED_PITCH | FF_MODERN;
hDC = GetDC(NULL);
EnumFontFamiliesExW(hDC, &lf, (FONTENUMPROCW)FindSuitableFontProc, (LPARAM)&Param, 0);
ReleaseDC(NULL, hDC);
/* If we failed to find any font, search again with relaxed criteria */
if (Param.StrictSearch && !Param.FontFound)
{
Param.StrictSearch = FALSE;
goto SearchAgain;
}
/* If no font was found again, return failure */
if (!Param.FontFound)
return FALSE;
/* Return the font details */
*FontData = Param.CandidateFont;
FontData->FaceName = FaceName; // Restore the original FaceName pointer.
StringCchCopyNW(FaceName, LF_FACESIZE,
Param.CandidateFaceName,
ARRAYSIZE(Param.CandidateFaceName));
return TRUE;
}
/**
* @brief
* Validates and creates a suitable console font based on the font
* characteristics given in input.
*
* @param[in] FontData
* The face name and characteristics of the font to create.
*
* @param[in] CodePage
* The code page the font has to support.
*
* @return
* A GDI handle to the created font, or @b NULL in case of failure.
**/
static HFONT
CreateConsoleFontWorker(
_In_ PFONT_DATA FontData,
_In_ UINT CodePage)
{
LOGFONTW lf;
RtlZeroMemory(&lf, sizeof(lf));
lf.lfHeight = (LONG)(ULONG)FontData->Size.Y;
lf.lfWidth = (LONG)(ULONG)FontData->Size.X;
lf.lfEscapement = 0;
lf.lfOrientation = 0; // TA_BASELINE; // TA_RTLREADING; when the console supports RTL?
// lf.lfItalic = lf.lfUnderline = lf.lfStrikeOut = FALSE;
lf.lfWeight = FontData->Weight;
lf.lfCharSet = CodePageToCharSet(CodePage);
lf.lfOutPrecision = OUT_DEFAULT_PRECIS;
lf.lfClipPrecision = CLIP_DEFAULT_PRECIS;
lf.lfQuality = DEFAULT_QUALITY;
/* Set the mandatory flags and remove those that we do not support */
lf.lfPitchAndFamily = (BYTE)( (FIXED_PITCH | FF_MODERN | FontData->Family) &
~(VARIABLE_PITCH | FF_DECORATIVE | FF_ROMAN | FF_SCRIPT | FF_SWISS));
if (!IsValidConsoleFont(FontData->FaceName, CodePage))
return NULL;
StringCchCopyNW(lf.lfFaceName, ARRAYSIZE(lf.lfFaceName),
FontData->FaceName, LF_FACESIZE);
return CreateFontIndirectW(&lf);
}
/*****************************************************************************/
/**
* @brief
* Validates and creates a suitable console font based on the font
* characteristics given in input.
*
* @param[in] Height
* The font height in cell units (pixels).
*
* @param[in,opt] Width
* The font width in cell units (pixels).
*
* @param[in,out] FaceName
* A pointer to a maximally @b LF_FACESIZE-sized buffer.
* In input: The buffer contains the face name of the font to try to create.
* In output: The buffer receives the face name of the font that has been
* created, in case of success. It may, or may not be, identical to the face
* name provided in input, in case a substitute font has been chosen.
*
* @param[in] FontWeight
* The font weight.
*
* @param[in] FontFamily
* The font family.
*
* @param[in] CodePage
* The code page the font has to support.
*
* @param[in] UseDefaultFallback
* Whether (@b TRUE) or not (@b FALSE) to use a default fallback font in case
* neither the specified font nor any substitute font could be found and
* created for the specified code page.
*
* @param[out] FontData
* The face name and characteristics of the created font.
*
* @return
* A GDI handle to the created font, or @b NULL in case of failure.
*
* @remark
* Similar to FindCreateFont()
* https://github.com/microsoft/terminal/blob/main/src/propsheet/fontdlg.cpp#L1113
* but:
* - does not support an internal font cache for now;
* - returns a font handle (and not a font index to the cache).
**/
HFONT
CreateConsoleFontEx(
_In_ LONG Height,
_In_opt_ LONG Width,
_Inout_updates_z_(LF_FACESIZE)
PWSTR FaceName,
_In_ ULONG FontWeight,
_In_ ULONG FontFamily,
_In_ UINT CodePage,
_In_ BOOL UseDefaultFallback,
_Out_ PFONT_DATA FontData)
{
HFONT hFont;
FontData->FaceName = FaceName;
FontData->Weight = FontWeight;
FontData->Family = FontFamily;
/* NOTE: FontSize is always in cell height/width units (pixels) */
FontData->Size.X = Width;
FontData->Size.Y = Height;
FontData->CharSet = 0; // CodePageToCharSet(CodePage);
if (/* !FaceName || */ !*FaceName || wcscmp(FaceName, DEFAULT_TT_FONT_FACENAME) == 0)
{
/* We do not have an actual font face name yet and should find one.
* Call FindSuitableFont() to determine the default font to use. */
}
else
{
hFont = CreateConsoleFontWorker(FontData, CodePage);
if (hFont)
return hFont;
DBGFNT1("CreateConsoleFont('%S') failed - Try to find a suitable font...\n",
FaceName);
}
/*
* We could not create a font with the default settings.
* Try to find a suitable font and retry.
*/
if (!FindSuitableFont(FontData, CodePage))
{
/* We could not find any suitable font, fall back
* to some default one if required to do so. */
DBGFNT1("FindSuitableFont could not find anything - %s\n",
UseDefaultFallback ? "Falling back to 'Terminal'"
: "Bailing out");
/* No fallback: no font! */
if (!UseDefaultFallback)
return NULL;
//
// FIXME: See also !*FaceName case in FindSuitableFont().
//
/* Use "Terminal" as the fallback */
StringCchCopyW(FaceName, LF_FACESIZE, TERMINAL_FACENAME);
#if 0
// FIXME: CJK font choose workaround: Don't choose Asian
// charset font if there is no preferred font for CJK.
if (IsCJKCodePage(CodePage))
FontData->CharSet = ANSI_CHARSET;
#endif
FontData->Family &= ~TMPF_TRUETYPE;
}
else
{
DBGFNT1("FindSuitableFont found: '%S', size (%d x %d)\n",
FaceName, FontData->Size.X, FontData->Size.Y);
}
/* Retry creating the font */
hFont = CreateConsoleFontWorker(FontData, CodePage);
if (!hFont)
DBGFNT1("CreateConsoleFont('%S') failed\n", FaceName);
return hFont;
}
/**
* @brief
* A wrapper for CreateConsoleFontEx().
*
* @param[in] Height
* The font height in cell units (pixels).
*
* @param[in,opt] Width
* The font width in cell units (pixels).
*
* @param[in,out] ConsoleInfo
* A pointer to console settings information, containing in particular
* (in input) the face name and characteristics of the font to create
* with the current console code page.
* In output, the font information gets updated.
* Note that a default fallback font is always being used in case neither
* the specified font nor any substitute font could be found and created
* for the specified code page.
*
* @return
* A GDI handle to the created font, or @b NULL in case of failure.
*
* @see CreateConsoleFontEx(), CreateConsoleFont()
**/
HFONT
CreateConsoleFont2(
_In_ LONG Height,
_In_opt_ LONG Width,
_Inout_ PCONSOLE_STATE_INFO ConsoleInfo)
{
FONT_DATA FontData;
HFONT hFont;
hFont = CreateConsoleFontEx(Height,
Width,
ConsoleInfo->FaceName,
ConsoleInfo->FontWeight,
ConsoleInfo->FontFamily,
ConsoleInfo->CodePage,
TRUE, // UseDefaultFallback
&FontData);
if (hFont)
{
ConsoleInfo->FontWeight = FontData.Weight;
ConsoleInfo->FontFamily = FontData.Family;
}
return hFont;
}
/**
* @brief
* A wrapper for CreateConsoleFontEx().
*
* @param[in,out] ConsoleInfo
* A pointer to console settings information, containing in particular
* (in input) the face name and characteristics of the font to create
* with the current console code page.
* In output, the font information gets updated.
* Note that a default fallback font is always being used in case neither
* the specified font nor any substitute font could be found and created
* for the specified code page.
*
* @return
* A GDI handle to the created font, or @b NULL in case of failure.
*
* @see CreateConsoleFontEx(), CreateConsoleFont2()
**/
HFONT
CreateConsoleFont(
_Inout_ PCONSOLE_STATE_INFO ConsoleInfo)
{
/*
* Format:
* Width = FontSize.X = LOWORD(FontSize);
* Height = FontSize.Y = HIWORD(FontSize);
*/
/* NOTE: FontSize is always in cell height/width units (pixels) */
return CreateConsoleFont2((LONG)(ULONG)ConsoleInfo->FontSize.Y,
(LONG)(ULONG)ConsoleInfo->FontSize.X,
ConsoleInfo);
}
/**
* @brief
* Retrieves the cell size for a console font.
*
* @param[in,opt] hDC
* An optional GDI device context handle.
*
* @param[in] hFont
* The GDI handle to the font.
*
* @param[out] Height
* In case of success, receives the cell height size (in pixels).
*
* @param[out] Width
* In case of success, receives the cell height size (in pixels).
*
* @return
* @b TRUE if success, @b FALSE in case of failure.
**/
_Success_(return)
BOOL
GetFontCellSize(
_In_opt_ HDC hDC,
_In_ HFONT hFont,
_Out_ PUINT Height,
_Out_ PUINT Width)
{
BOOL Success = FALSE;
HDC hOrgDC = hDC;
HFONT hOldFont;
// LONG LogSize, PointSize;
LONG CharWidth, CharHeight;
TEXTMETRICW tm;
// SIZE CharSize;
if (!hDC)
hDC = GetDC(NULL);
hOldFont = SelectObject(hDC, hFont);
if (hOldFont == NULL)
{
DBGFNT1("GetFontCellSize: SelectObject failed\n");
goto Quit;
}
/*
* See also: Display_SetTypeFace in applications/fontview/display.c
*/
/*
* Note that the method with GetObjectW just returns
* the original parameters with which the font was created.
*/
if (!GetTextMetricsW(hDC, &tm))
{
DBGFNT1("GetFontCellSize: GetTextMetrics failed\n");
goto Cleanup;
}
CharHeight = tm.tmHeight + tm.tmExternalLeading;
#if 0
/* Measure real char width more precisely if possible */
if (GetTextExtentPoint32W(hDC, L"R", 1, &CharSize))
CharWidth = CharSize.cx;
#else
CharWidth = tm.tmAveCharWidth; // tm.tmMaxCharWidth;
#endif
#if 0
/*** Logical to Point size ***/
LogSize = tm.tmHeight - tm.tmInternalLeading;
PointSize = MulDiv(LogSize, 72, GetDeviceCaps(hDC, LOGPIXELSY));
/*****************************/
#endif
*Height = (UINT)CharHeight;
*Width = (UINT)CharWidth;
Success = TRUE;
Cleanup:
SelectObject(hDC, hOldFont);
Quit:
if (!hOrgDC)
ReleaseDC(NULL, hDC);
return Success;
}
/**
* @brief
* Validates whether a given font can be supported in the console,
* under the specified code page.
*
* @param[in] lplf
* @param[in] lpntm
* @param[in] FontType
* The GDI font characteristics of the font to validate.
*
* @param[in] CodePage
* The code page the font has to support.
*
* @return
* @b TRUE if the font is valid and supported in the console,
* @b FALSE if not.
*
* @remark
* Equivalent of the font validation tests in FontEnumForV2Console()
* (or the more restrictive ones in FontEnum())
* https://github.com/microsoft/terminal/blob/main/src/propsheet/misc.cpp#L465
* https://github.com/microsoft/terminal/blob/main/src/propsheet/misc.cpp#L607
*
* @see IsValidConsoleFont()
**/
BOOL
IsValidConsoleFont2(
_In_ PLOGFONTW lplf,
_In_ PNEWTEXTMETRICW lpntm,
_In_ DWORD FontType,
_In_ UINT CodePage)
{
LPCWSTR FaceName = lplf->lfFaceName;
/*
* According to: https://web.archive.org/web/20140901124501/http://support.microsoft.com/kb/247815
* "Necessary criteria for fonts to be available in a command window",
* the criteria for console-eligible fonts are as follows:
* - The font must be a fixed-pitch font.
* - The font cannot be an italic font.
* - The font cannot have a negative A or C space.
* - If it is a TrueType font, it must be FF_MODERN.
* - If it is not a TrueType font, it must be OEM_CHARSET.
*
* Non documented: vertical fonts are forbidden (their name start with a '@').
*
* Additional criteria for Asian installations:
* - If it is not a TrueType font, the face name must be "Terminal".
* - If it is an Asian TrueType font, it must also be an Asian character set.
*
* See also Raymond Chen's blog: https://devblogs.microsoft.com/oldnewthing/?p=26843
* and MIT-licensed Microsoft Terminal source code: https://github.com/microsoft/terminal/blob/main/src/propsheet/misc.cpp
* for other details.
*
* To install additional TrueType fonts to be available for the console,
* add entries of type REG_SZ named "0", "00" etc... in:
* HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Console\TrueTypeFont
* The names of the fonts listed there should match those in:
* HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Fonts
*/
/*
* In ReactOS we relax some of the criteria:
* - We allow fixed-pitch FF_MODERN (Monospace) TrueType fonts
* that can be italic or have negative A or C space.
* - If it is not a TrueType font, it can be from another character set
* than OEM_CHARSET. When an Asian codepage is active however, we require
* that this non-TrueType font has an Asian character set.
*/
/* Reject variable-width fonts ... */
if ( ( ((lplf->lfPitchAndFamily & 0x03) != FIXED_PITCH)
#if 0 /* Reject italic and TrueType fonts with negative A or C space ... */
|| (lplf->lfItalic)
|| !(lpntm->ntmFlags & NTM_NONNEGATIVE_AC)
#endif
) &&
/* ... if they are not in the list of additional TrueType fonts to include */
!IsAdditionalTTFont(FaceName) )
{
DBGFNT("Font '%S' rejected because it%s (lfPitchAndFamily = %d)\n",
FaceName,
!(lplf->lfPitchAndFamily & FIXED_PITCH) ? "'s not FIXED_PITCH"
: (!(lpntm->ntmFlags & NTM_NONNEGATIVE_AC) ? " has negative A or C space"
: " is broken"),
lplf->lfPitchAndFamily);
return FALSE;
}
/* Reject TrueType fonts that are not FF_MODERN */
if ((FontType == TRUETYPE_FONTTYPE) && ((lplf->lfPitchAndFamily & 0xF0) != FF_MODERN))
{
DBGFNT("TrueType font '%S' rejected because it's not FF_MODERN (lfPitchAndFamily = %d)\n",
FaceName, lplf->lfPitchAndFamily);
return FALSE;
}
/* Reject vertical fonts (tategaki) */
if (FaceName[0] == L'@')
{
DBGFNT("Font '%S' rejected because it's vertical\n", FaceName);
return FALSE;
}
/* Is the current code page Chinese, Japanese or Korean? */
if (IsCJKCodePage(CodePage))
{
/* It's CJK */
if (FontType == TRUETYPE_FONTTYPE)
{
/*
* Here we are inclusive and check for any CJK character set,
* instead of looking just at the current one via CodePageToCharSet().
*/
if (!IsCJKCharSet(lplf->lfCharSet))
{
DBGFNT("TrueType font '%S' rejected because it's not Asian charset (lfCharSet = %d)\n",
FaceName, lplf->lfCharSet);
return FALSE;
}
/*
* If this is a cached TrueType font that is used only for certain
* code pages, verify that the charset it claims is the correct one.
*
* Since there may be multiple entries for a cached TrueType font,
* a general one (code page == 0) and one or more for explicit
* code pages, we need to perform two search queries instead of
* just one and retrieving the code page for this entry.
*/
if (IsAdditionalTTFont(FaceName) && !IsAdditionalTTFontCP(FaceName, 0) &&
!IsCJKCharSet(lplf->lfCharSet))
{
DBGFNT("Cached TrueType font '%S' rejected because it claims a code page that is not Asian charset (lfCharSet = %d)\n",
FaceName, lplf->lfCharSet);
return FALSE;
}
}
else
{
/* Reject non-TrueType fonts that do not have an Asian character set */
if (!IsCJKCharSet(lplf->lfCharSet) && (lplf->lfCharSet != OEM_CHARSET))
{
DBGFNT("Non-TrueType font '%S' rejected because it's not Asian charset or OEM_CHARSET (lfCharSet = %d)\n",
FaceName, lplf->lfCharSet);
return FALSE;
}
/* Reject non-TrueType fonts that are not Terminal */
if (wcscmp(FaceName, TERMINAL_FACENAME) != 0)
{
DBGFNT("Non-TrueType font '%S' rejected because it's not 'Terminal'\n", FaceName);
return FALSE;
}
}
}
else
{
/* Not CJK */
/* Reject non-TrueType fonts that are not OEM or similar */
if ((FontType != TRUETYPE_FONTTYPE) &&
(lplf->lfCharSet != ANSI_CHARSET) &&
(lplf->lfCharSet != DEFAULT_CHARSET) &&
(lplf->lfCharSet != OEM_CHARSET))
{
DBGFNT("Non-TrueType font '%S' rejected because it's not ANSI_CHARSET or DEFAULT_CHARSET or OEM_CHARSET (lfCharSet = %d)\n",
FaceName, lplf->lfCharSet);
return FALSE;
}
}
/* All good */
return TRUE;
}
typedef struct _IS_VALID_CONSOLE_FONT_PARAM
{
BOOL IsValidFont;
UINT CodePage;
} IS_VALID_CONSOLE_FONT_PARAM, *PIS_VALID_CONSOLE_FONT_PARAM;
/**
* @brief EnumFontFamiliesEx() callback helper for IsValidConsoleFont().
**/
static BOOL CALLBACK
IsValidConsoleFontProc(
_In_ PLOGFONTW lplf,
_In_ PNEWTEXTMETRICW lpntm,
_In_ DWORD FontType,
_In_ LPARAM lParam)
{
PIS_VALID_CONSOLE_FONT_PARAM Param = (PIS_VALID_CONSOLE_FONT_PARAM)lParam;
Param->IsValidFont = IsValidConsoleFont2(lplf, lpntm, FontType, Param->CodePage);
/* Stop the enumeration now */
return FALSE;
}
/**
* @brief
* Validates whether a given font can be supported in the console,
* under the specified code page.
*
* @param[in] FaceName
* The face name of the font to validate.
*
* @param[in] CodePage
* The code page the font has to support.
*
* @return
* @b TRUE if the font is valid and supported in the console,
* @b FALSE if not.
*
* @see IsValidConsoleFont2()
**/
BOOL
IsValidConsoleFont(
// _In_reads_or_z_(LF_FACESIZE)
_In_ PCWSTR FaceName,
_In_ UINT CodePage)
{
IS_VALID_CONSOLE_FONT_PARAM Param;
HDC hDC;
LOGFONTW lf;
Param.IsValidFont = FALSE;
Param.CodePage = CodePage;
RtlZeroMemory(&lf, sizeof(lf));
lf.lfCharSet = CodePageToCharSet(CodePage);
// lf.lfPitchAndFamily = FIXED_PITCH | FF_MODERN;
StringCchCopyW(lf.lfFaceName, ARRAYSIZE(lf.lfFaceName), FaceName);
hDC = GetDC(NULL);
EnumFontFamiliesExW(hDC, &lf, (FONTENUMPROCW)IsValidConsoleFontProc, (LPARAM)&Param, 0);
ReleaseDC(NULL, hDC);
return Param.IsValidFont;
}
/**
* @brief
* Initializes the console TrueType font cache.
*
* @remark
* To install additional TrueType fonts to be available for the console,
* add entries of type REG_SZ named "0", "00" etc... in:
* HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Console\TrueTypeFont
* The names of the fonts listed there should match those in:
* HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Fonts
*
* @return None.
**/
VOID
InitTTFontCache(VOID)
{
LRESULT lResult;
HKEY hKey;
DWORD dwIndex, dwType;
WCHAR szValueName[MAX_PATH];
DWORD cchValueName;
WCHAR szValue[LF_FACESIZE] = L"";
DWORD cbValue;
UINT CodePage;
PTT_FONT_ENTRY FontEntry;
PWCHAR pszNext;
if (TTFontCache.Next != NULL)
return;
// TTFontCache.Next = NULL;
/* Open the Console\TrueTypeFont key */
// "\\Registry\\Machine\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Console\\TrueTypeFont"
if (RegOpenKeyExW(HKEY_LOCAL_MACHINE,
L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Console\\TrueTypeFont",
0,
KEY_QUERY_VALUE,
&hKey) != ERROR_SUCCESS)
{
return;
}
/* Enumerate all the available TrueType console fonts */
for (dwIndex = 0, cchValueName = ARRAYSIZE(szValueName),
cbValue = sizeof(szValue);
(lResult = RegEnumValueW(hKey, dwIndex,
szValueName, &cchValueName,
NULL, &dwType,
(PBYTE)szValue, &cbValue)) != ERROR_NO_MORE_ITEMS;
++dwIndex, cchValueName = ARRAYSIZE(szValueName),
cbValue = sizeof(szValue))
{
/* Ignore if we failed for another reason, e.g. because
* the value name is too long (and thus, invalid). */
if (lResult != ERROR_SUCCESS)
continue;
/* Validate the value name (exclude the unnamed value) */
if (!cchValueName || (*szValueName == UNICODE_NULL))
continue;
/* Too large value names have already been handled with ERROR_MORE_DATA */
ASSERT((cchValueName < ARRAYSIZE(szValueName)) &&
(szValueName[cchValueName] == UNICODE_NULL));
/* Only (multi-)string values are supported */
if ((dwType != REG_SZ) && (dwType != REG_MULTI_SZ))
continue;
/* The value name is a code page (in decimal), validate it */
CodePage = wcstoul(szValueName, &pszNext, 10);
if (*pszNext)
continue; // Non-numerical garbage followed...
// IsValidCodePage(CodePage);
FontEntry = RtlAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*FontEntry));
if (!FontEntry)
{
DBGFNT1("InitTTFontCache: Failed to allocate memory, continuing...\n");
continue;
}
FontEntry->CodePage = CodePage;
pszNext = szValue;
/* Check whether bold is disabled for this font */
if (*pszNext == BOLD_MARK)
{
FontEntry->DisableBold = TRUE;
++pszNext;
}
else
{
FontEntry->DisableBold = FALSE;
}
/* Copy the font name */
StringCchCopyNW(FontEntry->FaceName, ARRAYSIZE(FontEntry->FaceName),
pszNext, wcslen(pszNext));
if (dwType == REG_MULTI_SZ)
{
/* There may be an alternate face name as the second string */
pszNext += wcslen(pszNext) + 1;
/* Check whether bold is disabled for this font */
if (*pszNext == BOLD_MARK)
{
FontEntry->DisableBold = TRUE;
++pszNext;
}
// else, keep the original setting.
/* Copy the alternate font name */
StringCchCopyNW(FontEntry->FaceNameAlt, ARRAYSIZE(FontEntry->FaceNameAlt),
pszNext, wcslen(pszNext));
}
PushEntryList(&TTFontCache, &FontEntry->Entry);
}
/* Close the key and quit */
RegCloseKey(hKey);
}
/**
* @brief
* Clears the console TrueType font cache.
*
* @return None.
**/
VOID
ClearTTFontCache(VOID)
{
PSINGLE_LIST_ENTRY Entry;
PTT_FONT_ENTRY FontEntry;
while (TTFontCache.Next != NULL)
{
Entry = PopEntryList(&TTFontCache);
FontEntry = CONTAINING_RECORD(Entry, TT_FONT_ENTRY, Entry);
RtlFreeHeap(RtlGetProcessHeap(), 0, FontEntry);
}
TTFontCache.Next = NULL;
}
/**
* @brief
* Refreshes the console TrueType font cache,
* by clearing and re-initializing it.
*
* @return None.
**/
VOID
RefreshTTFontCache(VOID)
{
ClearTTFontCache();
InitTTFontCache();
}
/**
* @brief
* Searches for a font in the console TrueType font cache,
* with the specified code page.
*
* @param[in,opt] FaceName
* An optional pointer to a maximally @b LF_FACESIZE-sized buffer.
* The buffer contains the face name of the font to search for.
*
* - If FaceName != NULL, search for the named font that should
* match the provided code page (when CodePage != INVALID_CP).
*
* - If FaceName == NULL, search for a font with the provided
* code page. In this case, CodePage cannot be == INVALID_CP,
* otherwise the search fails.
*
* @param[in] CodePage
* The code page the font has to support, or @b INVALID_CP when
* searching a font by face name only.
*
* @return
* A pointer to the cache entry for the font, or @b NULL if not found.
**/
PTT_FONT_ENTRY
FindCachedTTFont(
_In_reads_or_z_opt_(LF_FACESIZE)
PCWSTR FaceName,
_In_ UINT CodePage)
{
PSINGLE_LIST_ENTRY Entry;
PTT_FONT_ENTRY FontEntry;
if (FaceName)
{
/* Search for the named font */
for (Entry = TTFontCache.Next;
Entry != NULL;
Entry = Entry->Next)
{
FontEntry = CONTAINING_RECORD(Entry, TT_FONT_ENTRY, Entry);
/* NOTE: The font face names are case-sensitive */
if ((wcscmp(FontEntry->FaceName , FaceName) == 0) ||
(wcscmp(FontEntry->FaceNameAlt, FaceName) == 0))
{
/* Return the font if we don't search by code page, or when they match */
if ((CodePage == INVALID_CP) || (CodePage == FontEntry->CodePage))
{
return FontEntry;
}
}
}
}
else if (CodePage != INVALID_CP)
{
/* Search for a font with the specified code page */
for (Entry = TTFontCache.Next;
Entry != NULL;
Entry = Entry->Next)
{
FontEntry = CONTAINING_RECORD(Entry, TT_FONT_ENTRY, Entry);
/* Return the font if the code pages match */
if (CodePage == FontEntry->CodePage)
return FontEntry;
}
}
return NULL;
}
/* EOF */