From 4671b481a38f906a8ffc822b9492f2c942ad1b3e Mon Sep 17 00:00:00 2001 From: Katayama Hirofumi MZ Date: Tue, 7 Apr 2026 14:06:50 +0900 Subject: [PATCH] [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. --- win32ss/gdi/ntgdi/freetype.c | 497 +++++++++++++++++++---------------- 1 file changed, 267 insertions(+), 230 deletions(-) diff --git a/win32ss/gdi/ntgdi/freetype.c b/win32ss/gdi/ntgdi/freetype.c index 9673b7b24a1..188ba30199f 100644 --- a/win32ss/gdi/ntgdi/freetype.c +++ b/win32ss/gdi/ntgdi/freetype.c @@ -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);