[NTGDI][FREETYPE] FontLink: Request sub-font sizes (#8806)

A bug fix of FontLink'ed glyph size.
JIRA issue: CORE-20470
- Add FONTLINK_ENTRY structure
  and g_FontLinkEntries variable.
- Delete font-link cache mechanism.
- Don't access the registry while
  rendering is in progress.
- Request sub-font sizes while
  drawing FontLink text.
This commit is contained in:
Katayama Hirofumi MZ
2026-04-07 14:06:50 +09:00
committed by GitHub
parent 10092f8466
commit 4671b481a3
+267 -230
View File
@@ -39,24 +39,25 @@ typedef struct _FONTLINK
PSHARED_FACE SharedFace;
} FONTLINK, *PFONTLINK;
typedef struct _FONTLINK_ENTRY
{
LIST_ENTRY ListEntry;
WCHAR lfFaceName[LF_FACESIZE];
PZZWSTR pszzFontLink;
} FONTLINK_ENTRY, *PFONTLINK_ENTRY;
typedef struct _FONTLINK_CHAIN
{
LIST_ENTRY FontLinkList; //< List of FONTLINK's
LOGFONTW LogFont;
PZZWSTR pszzFontLink;
PCZZWSTR pszzFontLink;
PTEXTOBJ pBaseTextObj;
FT_Face pDefFace;
} FONTLINK_CHAIN, *PFONTLINK_CHAIN;
typedef struct _FONTLINK_CACHE
{
LIST_ENTRY ListEntry;
LOGFONTW LogFont;
FONTLINK_CHAIN Chain;
} FONTLINK_CACHE, *PFONTLINK_CACHE;
#define FONTLINK_DEFAULT_CHAR 0x30FB // U+30FB (KATAKANA MIDDLE DOT)
static RTL_STATIC_LIST_HEAD(g_FontLinkEntries); // The list of FONTLINK_ENTRY
static DWORD s_chFontLinkDefaultChar = FONTLINK_DEFAULT_CHAR;
static WCHAR s_szDefFontLinkFileName[MAX_PATH] = L"";
static WCHAR s_szDefFontLinkFontName[MAX_PATH] = L"";
@@ -64,10 +65,6 @@ static BOOL s_fFontLinkUseAnsi = FALSE;
static BOOL s_fFontLinkUseOem = FALSE;
static BOOL s_fFontLinkUseSymbol = FALSE;
#define MAX_FONTLINK_CACHE 128
static RTL_STATIC_LIST_HEAD(g_FontLinkCache); // The list of FONTLINK_CACHE
static LONG g_nFontLinkCacheCount = 0;
static inline NTSTATUS
FontLink_LoadSettings(VOID)
{
@@ -190,103 +187,6 @@ FontLink_Chain_IsPopulated(const FONTLINK_CHAIN *pChain)
return pChain->LogFont.lfFaceName[0];
}
static VOID
FontLink_Chain_Free(
_Inout_ PFONTLINK_CHAIN pChain)
{
PLIST_ENTRY Entry;
PFONTLINK pLink;
if (!FontLink_Chain_IsPopulated(pChain)) // The chain is not populated yet
return;
if (pChain->pszzFontLink)
ExFreePoolWithTag(pChain->pszzFontLink, TAG_FONT);
while (!IsListEmpty(&pChain->FontLinkList))
{
Entry = RemoveHeadList(&pChain->FontLinkList);
pLink = CONTAINING_RECORD(Entry, FONTLINK, ListEntry);
FontLink_Destroy(pLink);
}
}
static inline VOID
FontLink_AddCache(
_In_ PFONTLINK_CACHE pCache)
{
PLIST_ENTRY Entry;
/* Add the new cache entry to the top of the cache list */
++g_nFontLinkCacheCount;
InsertHeadList(&g_FontLinkCache, &pCache->ListEntry);
/* If there are too many cache entries in the list, remove the oldest one at the bottom */
if (g_nFontLinkCacheCount > MAX_FONTLINK_CACHE)
{
ASSERT(!IsListEmpty(&g_FontLinkCache));
Entry = RemoveTailList(&g_FontLinkCache);
--g_nFontLinkCacheCount;
pCache = CONTAINING_RECORD(Entry, FONTLINK_CACHE, ListEntry);
FontLink_Chain_Free(&pCache->Chain);
ExFreePoolWithTag(pCache, TAG_FONT);
}
}
/// Add the chain to the cache (g_FontLinkCache) if the chain had been populated.
/// @param pChain The chain.
static inline VOID
FontLink_Chain_Finish(
_Inout_ PFONTLINK_CHAIN pChain)
{
PFONTLINK_CACHE pCache;
if (!FontLink_Chain_IsPopulated(pChain))
return; // The chain is not populated yet
pCache = ExAllocatePoolWithTag(PagedPool, sizeof(FONTLINK_CACHE), TAG_FONT);
if (!pCache)
return; // Out of memory
pCache->LogFont = pChain->LogFont;
pCache->Chain = *pChain;
IntRebaseList(&pCache->Chain.FontLinkList, &pChain->FontLinkList);
FontLink_AddCache(pCache);
}
static inline PFONTLINK_CACHE
FontLink_FindCache(
_In_ const LOGFONTW* pLogFont)
{
PLIST_ENTRY Entry;
PFONTLINK_CACHE pLinkCache;
for (Entry = g_FontLinkCache.Flink; Entry != &g_FontLinkCache; Entry = Entry->Flink)
{
pLinkCache = CONTAINING_RECORD(Entry, FONTLINK_CACHE, ListEntry);
if (RtlEqualMemory(&pLinkCache->LogFont, pLogFont, sizeof(LOGFONTW)))
return pLinkCache;
}
return NULL;
}
static inline VOID
FontLink_CleanupCache(VOID)
{
PLIST_ENTRY Entry;
PFONTLINK_CACHE pLinkCache;
while (!IsListEmpty(&g_FontLinkCache))
{
Entry = RemoveHeadList(&g_FontLinkCache);
pLinkCache = CONTAINING_RECORD(Entry, FONTLINK_CACHE, ListEntry);
FontLink_Chain_Free(&pLinkCache->Chain);
ExFreePoolWithTag(pLinkCache, TAG_FONT);
}
g_nFontLinkCacheCount = 0;
}
/* TPMF_FIXED_PITCH is confusing; brain-dead api */
#ifndef _TMPF_VARIABLE_PITCH
#define _TMPF_VARIABLE_PITCH TMPF_FIXED_PITCH
@@ -972,6 +872,78 @@ IntLoadFontSubstList(PLIST_ENTRY pHead)
return NT_SUCCESS(Status);
}
static NTSTATUS FontLink_AddEntry(LPCWSTR lfFaceName, PZZWSTR pszzFontLink)
{
SIZE_T FontLinkSize = SZZ_GetSize(pszzFontLink);
PZZWSTR pszz = ExAllocatePoolWithTag(PagedPool, FontLinkSize, TAG_FONT);
if (!pszz)
return STATUS_NO_MEMORY;
RtlCopyMemory(pszz, pszzFontLink, FontLinkSize);
PFONTLINK_ENTRY pEntry = ExAllocatePoolWithTag(PagedPool, sizeof(FONTLINK_ENTRY), TAG_FONT);
if (!pEntry)
{
ExFreePoolWithTag(pszz, TAG_FONT);
return STATUS_NO_MEMORY;
}
pEntry->pszzFontLink = pszz;
RtlStringCchCopyW(pEntry->lfFaceName, _countof(pEntry->lfFaceName), lfFaceName);
InsertHeadList(&g_FontLinkEntries, &pEntry->ListEntry);
return STATUS_SUCCESS;
}
static void FontLink_DeleteEntries(VOID)
{
PLIST_ENTRY Entry;
PFONTLINK_ENTRY pEntry;
for (Entry = g_FontLinkEntries.Flink; Entry != &g_FontLinkEntries; Entry = Entry->Flink)
{
pEntry = CONTAINING_RECORD(Entry, FONTLINK_ENTRY, ListEntry);
ExFreePoolWithTag(pEntry->pszzFontLink, TAG_FONT);
ExFreePoolWithTag(pEntry, TAG_FONT);
}
}
static NTSTATUS FontLink_PopulateEntries(VOID)
{
NTSTATUS Status;
HKEY hKey;
DWORD cchName, cbData, dwType;
WCHAR szName[LF_FACESIZE], szzFontLink[300];
// Open the registry key
Status = RegOpenKey(
L"\\Registry\\Machine\\Software\\Microsoft\\Windows NT\\CurrentVersion\\FontLink\\SystemLink",
&hKey);
if (!NT_SUCCESS(Status))
return Status;
for (DWORD dwIndex = 0;; ++dwIndex)
{
cchName = _countof(szName);
cbData = sizeof(szzFontLink);
Status = RegEnumValueW(hKey, dwIndex, szName, &cchName, &dwType, szzFontLink, &cbData);
if (!NT_SUCCESS(Status))
break;
if (dwType != REG_MULTI_SZ)
continue;
// Ensure double-NUL-terminated
szzFontLink[_countof(szzFontLink) - 1] = UNICODE_NULL;
szzFontLink[_countof(szzFontLink) - 2] = UNICODE_NULL;
DPRINT1("szName: %S\n", szName);
DPRINT1("szzFontLink: %S\n", szzFontLink);
FontLink_AddEntry(szName, szzFontLink);
}
ZwClose(hKey); // Close the registry key
return STATUS_SUCCESS;
}
BOOL FASTCALL
InitFontSupport(VOID)
{
@@ -1010,6 +982,7 @@ InitFontSupport(VOID)
FontLink_LoadSettings();
FontLink_LoadDefaultFonts();
FontLink_LoadDefaultCharset();
FontLink_PopulateEntries();
return TRUE;
}
@@ -1022,9 +995,6 @@ FreeFontSupport(VOID)
PFONTSUBST_ENTRY pSubstEntry;
PFONT_ENTRY pFontEntry;
// Cleanup the FontLink cache
FontLink_CleanupCache();
// Free font cache list
pHead = &g_FontCacheListHead;
while (!IsListEmpty(pHead))
@@ -1052,6 +1022,8 @@ FreeFontSupport(VOID)
CleanupFontEntry(pFontEntry);
}
FontLink_DeleteEntries();
if (g_FreeTypeLibrary)
{
FT_Done_Library(g_FreeTypeLibrary);
@@ -1229,97 +1201,6 @@ static const WCHAR s_szzDefFixedFontLink[] =
L"gulim.ttc,GulimChe\0"
L"\0";
static NTSTATUS
FontLink_Chain_LoadReg(
_Inout_ PFONTLINK_CHAIN pChain,
_Inout_ PLOGFONTW pLF)
{
NTSTATUS Status;
HKEY hKey;
DWORD cbData;
WCHAR szzFontLink[512];
SIZE_T FontLinkSize;
PZZWSTR pszzFontLink = NULL;
ASSERT(pLF->lfFaceName[0]);
// Open the registry key
Status = RegOpenKey(
L"\\Registry\\Machine\\Software\\Microsoft\\Windows NT\\CurrentVersion\\FontLink\\SystemLink",
&hKey);
if (!NT_SUCCESS(Status))
return Status;
// Load the FontLink entry
cbData = sizeof(szzFontLink);
Status = RegQueryValue(hKey, pLF->lfFaceName, REG_MULTI_SZ, szzFontLink, &cbData);
if (!NT_SUCCESS(Status) &&
(Status != STATUS_BUFFER_OVERFLOW) && (Status != STATUS_BUFFER_TOO_SMALL))
{
// Retry with substituted
SubstituteFontRecurse(pLF);
cbData = sizeof(szzFontLink);
Status = RegQueryValue(hKey, pLF->lfFaceName, REG_MULTI_SZ, szzFontLink, &cbData);
}
if ((Status == STATUS_BUFFER_OVERFLOW) || (Status == STATUS_BUFFER_TOO_SMALL))
{
// Buffer is too small. Retry with larger buffer
if (cbData >= 2 * sizeof(WCHAR)) // Sanity check
{
FontLinkSize = cbData;
pszzFontLink = ExAllocatePoolWithTag(PagedPool, FontLinkSize, TAG_FONT);
if (!pszzFontLink)
{
ZwClose(hKey); // Close the registry key
return STATUS_NO_MEMORY;
}
Status = RegQueryValue(hKey, pLF->lfFaceName, REG_MULTI_SZ, pszzFontLink, &cbData);
if (!NT_SUCCESS(Status))
{
ExFreePoolWithTag(pszzFontLink, TAG_FONT);
pszzFontLink = NULL;
}
}
}
ZwClose(hKey); // Close the registry key
if (!NT_SUCCESS(Status)) // Failed to get registry value
{
// Use default value
ASSERT(sizeof(szzFontLink) >= sizeof(s_szzDefFontLink));
ASSERT(sizeof(szzFontLink) >= sizeof(s_szzDefFixedFontLink));
if (!(pLF->lfPitchAndFamily & FIXED_PITCH))
RtlCopyMemory(szzFontLink, s_szzDefFontLink, sizeof(s_szzDefFontLink));
else
RtlCopyMemory(szzFontLink, s_szzDefFixedFontLink, sizeof(s_szzDefFixedFontLink));
}
if (pszzFontLink)
{
// Ensure double-NUL-terminated
ASSERT(FontLinkSize / sizeof(WCHAR) >= 2);
pszzFontLink[FontLinkSize / sizeof(WCHAR) - 1] = UNICODE_NULL;
pszzFontLink[FontLinkSize / sizeof(WCHAR) - 2] = UNICODE_NULL;
}
else
{
// Ensure double-NUL-terminated
szzFontLink[_countof(szzFontLink) - 1] = UNICODE_NULL;
szzFontLink[_countof(szzFontLink) - 2] = UNICODE_NULL;
FontLinkSize = SZZ_GetSize(szzFontLink);
pszzFontLink = ExAllocatePoolWithTag(PagedPool, FontLinkSize, TAG_FONT);
if (!pszzFontLink)
return STATUS_NO_MEMORY;
RtlCopyMemory(pszzFontLink, szzFontLink, FontLinkSize);
}
pChain->pszzFontLink = pszzFontLink;
return STATUS_SUCCESS;
}
static inline PFONTLINK
FontLink_Chain_FindLink(
PFONTLINK_CHAIN pChain,
@@ -1379,6 +1260,44 @@ FontLink_Create(
return pLink;
}
static PFONTLINK_ENTRY
FontLink_FindEntry(const LOGFONTW *pLogFont)
{
PLIST_ENTRY Entry;
PFONTLINK_ENTRY pEntry;
LOGFONTW lf = *pLogFont;
SubstituteFontRecurse(&lf);
for (Entry = g_FontLinkEntries.Flink; Entry != &g_FontLinkEntries; Entry = Entry->Flink)
{
pEntry = CONTAINING_RECORD(Entry, FONTLINK_ENTRY, ListEntry);
if (!_wcsicmp(pEntry->lfFaceName, lf.lfFaceName))
return pEntry;
}
return NULL;
}
static NTSTATUS
FontLink_Chain_FindEntry(
_Inout_ PFONTLINK_CHAIN pChain,
_Inout_ PLOGFONTW pLF)
{
PFONTLINK_ENTRY pEntry = FontLink_FindEntry(pLF);
if (!pEntry)
{
if (!(pLF->lfPitchAndFamily & FIXED_PITCH))
pChain->pszzFontLink = s_szzDefFontLink;
else
pChain->pszzFontLink = s_szzDefFixedFontLink;
return STATUS_SUCCESS;
}
pChain->pszzFontLink = pEntry->pszzFontLink;
return STATUS_SUCCESS;
}
static NTSTATUS
FontLink_Chain_Populate(
_Inout_ PFONTLINK_CHAIN pChain)
@@ -1388,8 +1307,7 @@ FontLink_Chain_Populate(
LOGFONTW lfBase;
PTEXTOBJ pTextObj = pChain->pBaseTextObj;
PFONTGDI pFontGDI;
PWSTR pszLink;
PFONTLINK_CACHE pLinkCache;
PCWSTR pszLink;
WCHAR szEntry[MAX_PATH];
BOOL bFixCharSet;
@@ -1418,21 +1336,10 @@ FontLink_Chain_Populate(
if (bFixCharSet)
lfBase.lfCharSet = DEFAULT_CHARSET;
// Use cache if any
pLinkCache = FontLink_FindCache(&lfBase);
if (pLinkCache)
{
RemoveEntryList(&pLinkCache->ListEntry);
*pChain = pLinkCache->Chain;
IntRebaseList(&pChain->FontLinkList, &pLinkCache->Chain.FontLinkList);
ExFreePoolWithTag(pLinkCache, TAG_FONT);
return STATUS_SUCCESS;
}
pChain->LogFont = lfBase;
// Load FontLink entry from registry
Status = FontLink_Chain_LoadReg(pChain, &pChain->LogFont);
// Load FontLink entry
Status = FontLink_Chain_FindEntry(pChain, &pChain->LogFont);
if (!NT_SUCCESS(Status))
return Status;
@@ -4076,13 +3983,6 @@ IntRequestFontSize(PDC dc, PFONTGDI FontGDI, LONG lfWidth, LONG lfHeight)
if (lfHeight == -1)
lfHeight = -2;
if (FontGDI->Magic == FONTGDI_MAGIC &&
FontGDI->lfHeight == lfHeight &&
FontGDI->lfWidth == lfWidth)
{
return 0; /* Cached */
}
ASSERT_FREETYPE_LOCK_HELD();
pOS2 = (TT_OS2 *)FT_Get_Sfnt_Table(face, FT_SFNT_OS2);
pHori = (TT_HoriHeader *)FT_Get_Sfnt_Table(face, FT_SFNT_HHEA);
@@ -4187,6 +4087,127 @@ IntRequestFontSize(PDC dc, PFONTGDI FontGDI, LONG lfWidth, LONG lfHeight)
return FT_Request_Size(face, &req);
}
static FT_Error
IntRequestFontSizeEx(FT_Face face, const LOGFONTW *plf)
{
FT_Error error;
FT_Size_RequestRec req;
TT_OS2 *pOS2;
TT_HoriHeader *pHori;
LONG lfHeight = plf->lfHeight, lfWidth = plf->lfWidth;
FT_WinFNT_HeaderRec WinFNT;
LONG Ascent, Descent, Sum, EmHeight;
LONG tmAscent, tmDescent, tmHeight, tmInternalLeading;
lfWidth = abs(lfWidth);
if (lfHeight == 0)
{
if (lfWidth == 0)
{
DPRINT("lfHeight and lfWidth are zero.\n");
lfHeight = -16;
}
else
{
lfHeight = lfWidth;
}
}
if (lfHeight == -1)
lfHeight = -2;
ASSERT_FREETYPE_LOCK_HELD();
pOS2 = (TT_OS2 *)FT_Get_Sfnt_Table(face, FT_SFNT_OS2);
pHori = (TT_HoriHeader *)FT_Get_Sfnt_Table(face, FT_SFNT_HHEA);
if (!pOS2 || !pHori)
{
error = FT_Get_WinFNT_Header(face, &WinFNT);
if (error)
{
DPRINT1("%s: Failed to request font size.\n", face->family_name);
return error;
}
return 0;
}
/*
* NOTE: We cast TT_OS2.usWinAscent and TT_OS2.usWinDescent to signed FT_Short.
* Why? See: https://learn.microsoft.com/en-us/typography/opentype/spec/os2#uswindescent
*
* > usWinDescent is "usually" a positive value ...
*
* We can read it as "not always". See CORE-14994.
* See also: https://learn.microsoft.com/en-us/typography/opentype/spec/os2#fsselection
*/
#define FM_SEL_USE_TYPO_METRICS 0x80
if (lfHeight > 0)
{
/* case (A): lfHeight is positive */
Sum = (FT_Short)pOS2->usWinAscent + (FT_Short)pOS2->usWinDescent;
if (Sum == 0 || (pOS2->fsSelection & FM_SEL_USE_TYPO_METRICS))
{
Ascent = pHori->Ascender;
Descent = -pHori->Descender;
Sum = Ascent + Descent;
}
else
{
Ascent = (FT_Short)pOS2->usWinAscent;
Descent = (FT_Short)pOS2->usWinDescent;
}
tmAscent = FT_MulDiv(lfHeight, Ascent, Sum);
tmDescent = FT_MulDiv(lfHeight, Descent, Sum);
tmHeight = tmAscent + tmDescent;
tmInternalLeading = tmHeight - FT_MulDiv(lfHeight, face->units_per_EM, Sum);
}
else if (lfHeight < 0)
{
/* case (B): lfHeight is negative */
if (pOS2->fsSelection & FM_SEL_USE_TYPO_METRICS)
{
tmAscent = FT_MulDiv(-lfHeight, pHori->Ascender, face->units_per_EM);
tmDescent = FT_MulDiv(-lfHeight, -pHori->Descender, face->units_per_EM);
}
else
{
tmAscent = FT_MulDiv(-lfHeight, (FT_Short)pOS2->usWinAscent, face->units_per_EM);
tmDescent = FT_MulDiv(-lfHeight, (FT_Short)pOS2->usWinDescent, face->units_per_EM);
}
tmHeight = tmAscent + tmDescent;
tmInternalLeading = tmHeight + lfHeight;
}
#undef FM_SEL_USE_TYPO_METRICS
EmHeight = tmHeight - tmInternalLeading;
EmHeight = max(EmHeight, 1);
EmHeight = min(EmHeight, USHORT_MAX);
#if 1
/* I think this is wrong implementation but its test result is better. */
if (lfWidth != 0)
req.width = FT_MulDiv(lfWidth, face->units_per_EM, pOS2->xAvgCharWidth) << 6;
#else
/* I think this is correct implementation but it is mismatching to the
other metric functions. The test result is bad. */
if (lfWidth != 0)
req.width = (FT_MulDiv(lfWidth, 96 * 5, 72 * 3) << 6); /* ??? FIXME */
#endif
else
req.width = 0;
/* HACK: We do not handle small widths well, so just use zero for these. See CORE-19870. */
if (lfWidth < 10)
req.width = 0;
req.type = FT_SIZE_REQUEST_TYPE_NOMINAL;
req.height = (EmHeight << 6);
req.horiResolution = 0;
req.vertResolution = 0;
return FT_Request_Size(face, &req);
}
BOOL FASTCALL
TextIntUpdateSize(PDC dc,
PTEXTOBJ TextObj,
@@ -4336,6 +4357,25 @@ FontLink_Chain_Dump(
#endif
}
static BOOL
IntNeedRequestFontSize(PFONTLINK_CHAIN pChain, FT_Face face, PLIST_ENTRY TargetEntry)
{
PLIST_ENTRY Entry, Head = &pChain->FontLinkList;
PFONTLINK pFontLink;
for (Entry = Head->Flink; Entry != Head; Entry = Entry->Flink)
{
pFontLink = CONTAINING_RECORD(Entry, FONTLINK, ListEntry);
if (!FontLink_PrepareFontInfo(pFontLink))
continue; // This link is not useful, check the next one
if (pFontLink->SharedFace->Face == face || Entry == TargetEntry)
return FALSE;
}
return TRUE;
}
/// Search the target glyph and update the current font info.
/// @return The glyph index
static UINT
@@ -4382,6 +4422,8 @@ FontLink_Chain_FindGlyph(
// The target glyph is found in the chain
DPRINT("code: 0x%08X, index: 0x%08X\n", code, index);
pCache->Hashed.Face = *pFace = face;
if (IntNeedRequestFontSize(pChain, face, Entry))
IntRequestFontSizeEx(face, &pChain->LogFont);
FT_Set_Transform(face, &pCache->Hashed.matTransform, NULL);
return index;
}
@@ -5042,8 +5084,6 @@ TextIntGetTextExtentPoint(
Size->cy = ascender + descender;
}
FontLink_Chain_Finish(&Chain);
return TRUE;
}
@@ -6840,7 +6880,6 @@ IntExtTextOutW(
if (!IntGetTextDisposition(&DeltaX64, &DeltaY64, String, Count, Dx, &Cache,
fuOptions, bNoTransform, &Chain))
{
FontLink_Chain_Finish(&Chain);
IntUnLockFreeType();
bResult = FALSE;
goto Cleanup;
@@ -7160,8 +7199,6 @@ IntExtTextOutW(
}
}
FontLink_Chain_Finish(&Chain);
IntUnLockFreeType();
EXLATEOBJ_vCleanup(&exloRGB2Dst);