reactos/win32ss/user/winsrv/concfg/font.c
Hermès Bélusca-Maïto c4e2826c73
[CONSOLE][CONCFG][CONSRV] Provide support for specified additional TrueType fonts + bugfixes.
CORE-12451 CORE-13182 CORE-13196

- CONSOLE: Initialize the additional TrueType fonts cache.
  * Fix the font preview when a TrueType font has been selected.
  * Refresh the available fonts and the font preview when the selected
    code page is changed (Work In Progress), or when the OS pool of
    font resources has changed (WM_FONTCHANGE message).

- CONCFG: Implement support for the additional TrueType fonts cache:
  the contents of the cache is enumerated under the registry key
    HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Console\TrueTypeFont

  * Add helper functions and macros.
  * In CreateConsoleFontEx(), set the mandatory font pitch&family flags
    and remove those that we do not support.
  * In IsValidConsoleFont2(), update the validity checks and the
    documentation links.

- CONSRV: Load/refresh the additional TrueType fonts cache when needed.
2019-05-19 23:10:41 +02:00

551 lines
17 KiB
C

/*
* COPYRIGHT: See COPYING in the top level directory
* PROJECT: ReactOS Console Server DLL
* FILE: win32ss/user/winsrv/concfg/font.c
* PURPOSE: Console Fonts Management
* PROGRAMMERS: Hermes Belusca-Maito (hermes.belusca@sfr.fr)
* 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>
/* GLOBALS ********************************************************************/
// RTL_STATIC_LIST_HEAD(TTFontCache);
LIST_ENTRY TTFontCache = {&TTFontCache, &TTFontCache};
/* FUNCTIONS ******************************************************************/
/* Retrieves the character set associated with a given code page */
BYTE
CodePageToCharSet(
IN UINT CodePage)
{
CHARSETINFO CharInfo;
if (TranslateCharsetInfo(UlongToPtr(CodePage), &CharInfo, TCI_SRCCODEPAGE))
return CharInfo.ciCharset;
else
return DEFAULT_CHARSET;
}
HFONT
CreateConsoleFontEx(
IN LONG Height,
IN LONG Width OPTIONAL,
IN OUT LPWSTR FaceName, // Points to a WCHAR array of LF_FACESIZE elements
IN ULONG FontFamily,
IN ULONG FontWeight,
IN UINT CodePage)
{
LOGFONTW lf;
RtlZeroMemory(&lf, sizeof(lf));
lf.lfHeight = Height;
lf.lfWidth = Width;
lf.lfEscapement = 0;
lf.lfOrientation = 0; // TA_BASELINE; // TA_RTLREADING; when the console supports RTL?
// lf.lfItalic = lf.lfUnderline = lf.lfStrikeOut = FALSE;
lf.lfWeight = FontWeight;
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 | FontFamily) &
~(VARIABLE_PITCH | FF_DECORATIVE | FF_ROMAN | FF_SCRIPT | FF_SWISS));
if (!IsValidConsoleFont(FaceName, CodePage))
StringCchCopyW(FaceName, LF_FACESIZE, L"Terminal");
StringCchCopyNW(lf.lfFaceName, ARRAYSIZE(lf.lfFaceName),
FaceName, LF_FACESIZE);
return CreateFontIndirectW(&lf);
}
HFONT
CreateConsoleFont2(
IN LONG Height,
IN LONG Width OPTIONAL,
IN OUT PCONSOLE_STATE_INFO ConsoleInfo)
{
return CreateConsoleFontEx(Height,
Width,
ConsoleInfo->FaceName,
ConsoleInfo->FontFamily,
ConsoleInfo->FontWeight,
ConsoleInfo->CodePage);
}
HFONT
CreateConsoleFont(
IN OUT 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 CreateConsoleFontEx((LONG)(ULONG)ConsoleInfo->FontSize.Y,
(LONG)(ULONG)ConsoleInfo->FontSize.X,
ConsoleInfo->FaceName,
ConsoleInfo->FontFamily,
ConsoleInfo->FontWeight,
ConsoleInfo->CodePage);
}
BOOL
GetFontCellSize(
IN HDC hDC OPTIONAL,
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)
{
DPRINT1("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))
{
DPRINT1("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;
}
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/master/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) )
{
DPRINT1("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))
{
DPRINT1("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'@')
{
DPRINT1("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)
#if 1 // FIXME: Temporary HACK!
&& wcscmp(FaceName, L"Terminal") != 0
#endif
)
{
DPRINT1("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))
{
DPRINT1("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))
{
DPRINT1("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, L"Terminal") != 0)
{
DPRINT1("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))
{
DPRINT1("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;
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;
}
BOOL
IsValidConsoleFont(
IN LPCWSTR 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 = DEFAULT_CHARSET; // 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;
}
/*
* 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
*
* This function initializes the cache of the fonts listed there.
*/
VOID
InitTTFontCache(VOID)
{
BOOLEAN Success;
HKEY hKeyTTFonts; // hKey;
DWORD dwNumValues = 0;
DWORD dwIndex;
DWORD dwType;
WCHAR szValueName[MAX_PATH];
DWORD dwValueName;
WCHAR szValue[LF_FACESIZE] = L"";
DWORD dwValue;
PTT_FONT_ENTRY FontEntry;
PWCHAR pszNext = NULL;
UINT CodePage;
if (!IsListEmpty(&TTFontCache))
return;
// InitializeListHead(&TTFontCache);
/* Open the key */
// "\\Registry\\Machine\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Console\\TrueTypeFont"
Success = (RegOpenKeyExW(HKEY_LOCAL_MACHINE,
L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Console\\TrueTypeFont",
0,
KEY_READ,
&hKeyTTFonts) == ERROR_SUCCESS);
if (!Success)
return;
/* Enumerate each value */
if (RegQueryInfoKeyW(hKeyTTFonts, NULL, NULL, NULL, NULL, NULL, NULL,
&dwNumValues, NULL, NULL, NULL, NULL) != ERROR_SUCCESS)
{
DPRINT("ConCfgReadUserSettings: RegQueryInfoKeyW failed\n");
RegCloseKey(hKeyTTFonts);
return;
}
for (dwIndex = 0; dwIndex < dwNumValues; dwIndex++)
{
dwValue = sizeof(szValue);
dwValueName = ARRAYSIZE(szValueName);
if (RegEnumValueW(hKeyTTFonts, dwIndex, szValueName, &dwValueName, NULL, &dwType, (BYTE*)szValue, &dwValue) != ERROR_SUCCESS)
{
DPRINT1("InitTTFontCache: RegEnumValueW failed, continuing...\n");
continue;
}
/* 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)
{
DPRINT1("InitTTFontCache: Failed to allocate memory, continuing...\n");
continue;
}
FontEntry->CodePage = CodePage;
pszNext = szValue;
/* Check whether bold is disabled for this font */
if (*pszNext == L'*')
{
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 == L'*')
{
FontEntry->DisableBold = TRUE;
++pszNext;
}
// else, keep the original setting.
/* Copy the alternate font name */
StringCchCopyNW(FontEntry->FaceNameAlt, ARRAYSIZE(FontEntry->FaceNameAlt),
pszNext, wcslen(pszNext));
}
InsertTailList(&TTFontCache, &FontEntry->Entry);
}
/* Close the key and quit */
RegCloseKey(hKeyTTFonts);
}
VOID
ClearTTFontCache(VOID)
{
PLIST_ENTRY Entry;
PTT_FONT_ENTRY FontEntry;
while (!IsListEmpty(&TTFontCache))
{
Entry = RemoveHeadList(&TTFontCache);
FontEntry = CONTAINING_RECORD(Entry, TT_FONT_ENTRY, Entry);
RtlFreeHeap(RtlGetProcessHeap(), 0, FontEntry);
}
InitializeListHead(&TTFontCache);
}
VOID
RefreshTTFontCache(VOID)
{
ClearTTFontCache();
InitTTFontCache();
}
PTT_FONT_ENTRY
FindCachedTTFont(
IN LPCWSTR FaceName,
IN UINT CodePage)
{
PLIST_ENTRY Entry;
PTT_FONT_ENTRY FontEntry;
/* Search for the font in the cache */
for (Entry = TTFontCache.Flink;
Entry != &TTFontCache;
Entry = Entry->Flink)
{
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 a match if we don't look at the code pages, or when they match */
if ((CodePage == INVALID_CP) || (CodePage == FontEntry->CodePage))
{
return FontEntry;
}
}
}
return NULL;
}
/* EOF */