From 246f24bd7f69916b171b8e12fc312e8fcd8c5ad0 Mon Sep 17 00:00:00 2001 From: Mark Jansen Date: Fri, 14 Apr 2017 18:22:57 +0000 Subject: [PATCH] [WIN32SS] Cleanup fonts at process destruction + implement font memory reference counting. Thanks to everyone involved in reviewing this code! (See CR-112) CORE-13056 svn path=/trunk/; revision=74309 --- reactos/win32ss/gdi/eng/engobjects.h | 12 +- reactos/win32ss/gdi/ntgdi/font.h | 3 +- reactos/win32ss/gdi/ntgdi/freetype.c | 311 ++++++++++++--- reactos/win32ss/gdi/ntgdi/init.c | 2 + reactos/win32ss/gdi/ntgdi/text.h | 1 + .../apitests/gdi32/AddFontMemResourceEx.c | 353 +++++++++++++----- .../apitests/gdi32/Shadows_Into_Light.ttf | Bin 0 -> 29180 bytes rostests/apitests/gdi32/TTCTestV.ttc | Bin 0 -> 4120 bytes rostests/apitests/gdi32/resource.rc | 2 + 9 files changed, 523 insertions(+), 161 deletions(-) create mode 100644 rostests/apitests/gdi32/Shadows_Into_Light.ttf create mode 100644 rostests/apitests/gdi32/TTCTestV.ttc diff --git a/reactos/win32ss/gdi/eng/engobjects.h b/reactos/win32ss/gdi/eng/engobjects.h index 584ebe7e408..333b6e11bf4 100644 --- a/reactos/win32ss/gdi/eng/engobjects.h +++ b/reactos/win32ss/gdi/eng/engobjects.h @@ -107,9 +107,17 @@ typedef struct _FLOATGDI { ULONG Dummy; } FLOATGDI; +typedef struct _SHARED_MEM { + PVOID Buffer; + ULONG BufferSize; + BOOL IsMapping; + LONG RefCount; +} SHARED_MEM, *PSHARED_MEM; + typedef struct _SHARED_FACE { - FT_Face Face; - LONG RefCount; + FT_Face Face; + LONG RefCount; + PSHARED_MEM Memory; } SHARED_FACE, *PSHARED_FACE; typedef struct _FONTGDI { diff --git a/reactos/win32ss/gdi/ntgdi/font.h b/reactos/win32ss/gdi/ntgdi/font.h index 814b18d846f..0383fe5c510 100644 --- a/reactos/win32ss/gdi/ntgdi/font.h +++ b/reactos/win32ss/gdi/ntgdi/font.h @@ -54,8 +54,7 @@ typedef struct FONTSUBST_ENTRY typedef struct GDI_LOAD_FONT { PUNICODE_STRING pFileName; - PVOID Buffer; - ULONG BufferSize; + PSHARED_MEM Memory; DWORD Characteristics; UNICODE_STRING RegValueName; BOOL IsTrueType; diff --git a/reactos/win32ss/gdi/ntgdi/freetype.c b/reactos/win32ss/gdi/ntgdi/freetype.c index 14e41d5d511..b2bebd7d1cf 100644 --- a/reactos/win32ss/gdi/ntgdi/freetype.c +++ b/reactos/win32ss/gdi/ntgdi/freetype.c @@ -58,39 +58,6 @@ static const UNICODE_STRING FixedSysW = RTL_CONSTANT_STRING(L"FixedSys"); static UNICODE_STRING FontRegPath = RTL_CONSTANT_STRING(L"\\REGISTRY\\Machine\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Fonts"); -static PSHARED_FACE -SharedFace_Create(FT_Face Face) -{ - PSHARED_FACE Ptr; - Ptr = ExAllocatePoolWithTag(PagedPool, sizeof(SHARED_FACE), TAG_FONT); - if (Ptr) - { - Ptr->Face = Face; - Ptr->RefCount = 1; - } - return Ptr; -} - -static void -SharedFace_AddRef(PSHARED_FACE Ptr) -{ - ++Ptr->RefCount; -} - -static void -SharedFace_Release(PSHARED_FACE Ptr) -{ - if (Ptr->RefCount <= 0) - return; - - --Ptr->RefCount; - if (Ptr->RefCount == 0) - { - FT_Done_Face(Ptr->Face); - ExFreePoolWithTag(Ptr, TAG_FONT); - } -} - /* The FreeType library is not thread safe, so we have to serialize access to it */ @@ -106,12 +73,18 @@ static BOOL RenderingEnabled = TRUE; #define IntUnLockGlobalFonts \ ExReleaseFastMutexUnsafeAndLeaveCriticalRegion(FontListLock) +#define ASSERT_GLOBALFONTS_LOCK_HELD() \ + ASSERT(FreeTypeLock->Owner == KeGetCurrentThread()) + #define IntLockFreeType \ ExEnterCriticalRegionAndAcquireFastMutexUnsafe(FreeTypeLock) #define IntUnLockFreeType \ ExReleaseFastMutexUnsafeAndLeaveCriticalRegion(FreeTypeLock) +#define ASSERT_FREETYPE_LOCK_HELD() \ + ASSERT(FreeTypeLock->Owner == KeGetCurrentThread()) + #define MAX_FONT_CACHE 256 static LIST_ENTRY FontCacheListHead; @@ -189,6 +162,129 @@ static const CHARSETINFO FontTci[MAXTCIINDEX] = /* list head */ static RTL_STATIC_LIST_HEAD(FontSubstListHead); +static void +SharedMem_AddRef(PSHARED_MEM Ptr) +{ + ASSERT_FREETYPE_LOCK_HELD(); + + ++Ptr->RefCount; +} + +static PSHARED_FACE +SharedFace_Create(FT_Face Face, PSHARED_MEM Memory) +{ + PSHARED_FACE Ptr; + Ptr = ExAllocatePoolWithTag(PagedPool, sizeof(SHARED_FACE), TAG_FONT); + if (Ptr) + { + Ptr->Face = Face; + Ptr->RefCount = 1; + Ptr->Memory = Memory; + SharedMem_AddRef(Memory); + DPRINT("Creating SharedFace for %s\n", Face->family_name); + } + return Ptr; +} + +static PSHARED_MEM +SharedMem_Create(PBYTE Buffer, ULONG BufferSize, BOOL IsMapping) +{ + PSHARED_MEM Ptr; + Ptr = ExAllocatePoolWithTag(PagedPool, sizeof(SHARED_MEM), TAG_FONT); + if (Ptr) + { + Ptr->Buffer = Buffer; + Ptr->BufferSize = BufferSize; + Ptr->RefCount = 1; + Ptr->IsMapping = IsMapping; + DPRINT("Creating SharedMem for %p (%i, %p)\n", Buffer, IsMapping, Ptr); + } + return Ptr; +} + +static void +SharedFace_AddRef(PSHARED_FACE Ptr) +{ + ASSERT_FREETYPE_LOCK_HELD(); + + ++Ptr->RefCount; +} + +static void +RemoveCachedEntry(PFONT_CACHE_ENTRY Entry) +{ + ASSERT_FREETYPE_LOCK_HELD(); + + FT_Done_Glyph((FT_Glyph)Entry->BitmapGlyph); + RemoveEntryList(&Entry->ListEntry); + ExFreePoolWithTag(Entry, TAG_FONT); + FontCacheNumEntries--; + ASSERT(FontCacheNumEntries <= MAX_FONT_CACHE); +} + +static void +RemoveCacheEntries(FT_Face Face) +{ + PLIST_ENTRY CurrentEntry; + PFONT_CACHE_ENTRY FontEntry; + + ASSERT_FREETYPE_LOCK_HELD(); + + CurrentEntry = FontCacheListHead.Flink; + while (CurrentEntry != &FontCacheListHead) + { + FontEntry = CONTAINING_RECORD(CurrentEntry, FONT_CACHE_ENTRY, ListEntry); + CurrentEntry = CurrentEntry->Flink; + + if (FontEntry->Face == Face) + { + RemoveCachedEntry(FontEntry); + } + } +} + +static void SharedMem_Release(PSHARED_MEM Ptr) +{ + ASSERT_FREETYPE_LOCK_HELD(); + ASSERT(Ptr->RefCount > 0); + + if (Ptr->RefCount <= 0) + return; + + --Ptr->RefCount; + if (Ptr->RefCount == 0) + { + DPRINT("Releasing SharedMem for %p (%i, %p)\n", Ptr->Buffer, Ptr->IsMapping, Ptr); + if (Ptr->IsMapping) + MmUnmapViewInSystemSpace(Ptr->Buffer); + else + ExFreePoolWithTag(Ptr->Buffer, TAG_FONT); + ExFreePoolWithTag(Ptr, TAG_FONT); + } +} + +static void +SharedFace_Release(PSHARED_FACE Ptr) +{ + IntLockFreeType; + ASSERT(Ptr->RefCount > 0); + + if (Ptr->RefCount <= 0) + return; + + --Ptr->RefCount; + if (Ptr->RefCount == 0) + { + DPRINT("Releasing SharedFace for %s\n", Ptr->Face->family_name); + RemoveCacheEntries(Ptr->Face); + FT_Done_Face(Ptr->Face); + SharedMem_Release(Ptr->Memory); + ExFreePoolWithTag(Ptr, TAG_FONT); + } + IntUnLockFreeType; +} + + /* * IntLoadFontSubstList --- loads the list of font substitutes */ @@ -677,8 +773,6 @@ IntGdiLoadFontsFromMemory(PGDI_LOAD_FONT pLoadFont, FT_WinFNT_HeaderRec WinFNT; INT FontCount = 0, CharSetCount = 0; PUNICODE_STRING pFileName = pLoadFont->pFileName; - PVOID Buffer = pLoadFont->Buffer; - ULONG BufferSize = pLoadFont->BufferSize; DWORD Characteristics = pLoadFont->Characteristics; PUNICODE_STRING pValueName = &pLoadFont->RegValueName; TT_OS2 * pOS2; @@ -693,30 +787,37 @@ IntGdiLoadFontsFromMemory(PGDI_LOAD_FONT pLoadFont, IntLockFreeType; Error = FT_New_Memory_Face( library, - Buffer, - BufferSize, + pLoadFont->Memory->Buffer, + pLoadFont->Memory->BufferSize, ((FontIndex != -1) ? FontIndex : 0), &Face); + + if (!Error) + SharedFace = SharedFace_Create(Face, pLoadFont->Memory); + IntUnLockFreeType; if (FT_IS_SFNT(Face)) pLoadFont->IsTrueType = TRUE; - if (!Error) - SharedFace = SharedFace_Create(Face); if (Error || SharedFace == NULL) { + if (SharedFace) + SharedFace_Release(SharedFace); + if (Error == FT_Err_Unknown_File_Format) DPRINT1("Unknown font file format\n"); else - DPRINT1("Error reading font file (error code: %d)\n", Error); + DPRINT1("Error reading font (error code: %d)\n", Error); return 0; /* failure */ } } else { Face = SharedFace->Face; + IntLockFreeType; SharedFace_AddRef(SharedFace); + IntUnLockFreeType; } /* allocate a FONT_ENTRY */ @@ -1006,8 +1107,7 @@ IntGdiAddFontResource(PUNICODE_STRING FileName, DWORD Characteristics) } LoadFont.pFileName = FileName; - LoadFont.Buffer = Buffer; - LoadFont.BufferSize = ViewSize; + LoadFont.Memory = SharedMem_Create(Buffer, ViewSize, TRUE); LoadFont.Characteristics = Characteristics; RtlInitUnicodeString(&LoadFont.RegValueName, NULL); LoadFont.IsTrueType = FALSE; @@ -1016,6 +1116,11 @@ IntGdiAddFontResource(PUNICODE_STRING FileName, DWORD Characteristics) ObDereferenceObject(SectionObject); + /* Release our copy */ + IntLockFreeType; + SharedMem_Release(LoadFont.Memory); + IntUnLockFreeType; + if (FontCount > 0) { if (LoadFont.IsTrueType) @@ -1070,17 +1175,17 @@ IntGdiAddFontMemResource(PVOID Buffer, DWORD dwSize, PDWORD pNumAdded) INT FontCount; HANDLE Ret = 0; - /* We leak this buffer for now, same as all fonts do with their buffer! */ - LoadFont.Buffer = ExAllocatePoolWithTag(PagedPool, dwSize, TAG_FONT); - if (!LoadFont.Buffer) + PVOID BufferCopy = ExAllocatePoolWithTag(PagedPool, dwSize, TAG_FONT); + + if (!BufferCopy) { *pNumAdded = 0; return NULL; } - memcpy(LoadFont.Buffer, Buffer, dwSize); + memcpy(BufferCopy, Buffer, dwSize); LoadFont.pFileName = NULL; - LoadFont.BufferSize = dwSize; + LoadFont.Memory = SharedMem_Create(BufferCopy, dwSize, FALSE); LoadFont.Characteristics = FR_PRIVATE | FR_NOT_ENUM; RtlInitUnicodeString(&LoadFont.RegValueName, NULL); LoadFont.IsTrueType = FALSE; @@ -1089,7 +1194,11 @@ IntGdiAddFontMemResource(PVOID Buffer, DWORD dwSize, PDWORD pNumAdded) RtlFreeUnicodeString(&LoadFont.RegValueName); - *pNumAdded = FontCount; + /* Release our copy */ + IntLockFreeType; + SharedMem_Release(LoadFont.Memory); + IntUnLockFreeType; + if (FontCount > 0) { EntryCollection = ExAllocatePoolWithTag(PagedPool, sizeof(FONT_ENTRY_COLL_MEM), TAG_FONT); @@ -1104,12 +1213,27 @@ IntGdiAddFontMemResource(PVOID Buffer, DWORD dwSize, PDWORD pNumAdded) Ret = (HANDLE)EntryCollection->Handle; } } + *pNumAdded = FontCount; return Ret; } // FIXME: Add RemoveFontResource +static VOID FASTCALL +CleanupFontEntry(PFONT_ENTRY FontEntry) +{ + PFONTGDI FontGDI = FontEntry->Font; + PSHARED_FACE SharedFace = FontGDI->SharedFace; + + if (FontGDI->Filename) + ExFreePoolWithTag(FontGDI->Filename, GDITAG_PFF); + + EngFreeMem(FontGDI); + SharedFace_Release(SharedFace); + ExFreePoolWithTag(FontEntry, TAG_FONT); +} + VOID FASTCALL IntGdiCleanupMemEntry(PFONT_ENTRY_MEM Head) { @@ -1121,14 +1245,31 @@ IntGdiCleanupMemEntry(PFONT_ENTRY_MEM Head) Entry = RemoveHeadList(&Head->ListEntry); FontEntry = CONTAINING_RECORD(Entry, FONT_ENTRY_MEM, ListEntry); - // Delete FontEntry->Entry (FONT_ENTRY*) + CleanupFontEntry(FontEntry->Entry); ExFreePoolWithTag(FontEntry, TAG_FONT); } - // Delete Head->Entry (FONT_ENTRY*) + CleanupFontEntry(Head->Entry); ExFreePoolWithTag(Head, TAG_FONT); } +static VOID FASTCALL +UnlinkFontMemCollection(PFONT_ENTRY_COLL_MEM Collection) +{ + PFONT_ENTRY_MEM FontMemEntry = Collection->Entry; + PLIST_ENTRY ListEntry; + RemoveEntryList(&Collection->ListEntry); + + do { + /* Also unlink the FONT_ENTRY stuff from the PrivateFontListHead */ + RemoveEntryList(&FontMemEntry->Entry->ListEntry); + + ListEntry = FontMemEntry->ListEntry.Flink; + FontMemEntry = CONTAINING_RECORD(ListEntry, FONT_ENTRY_MEM, ListEntry); + + } while (FontMemEntry != Collection->Entry); +} + BOOL FASTCALL IntGdiRemoveFontMemResource(HANDLE hMMFont) { @@ -1146,7 +1287,7 @@ IntGdiRemoveFontMemResource(HANDLE hMMFont) if (CurrentEntry->Handle == (UINT)hMMFont) { EntryCollection = CurrentEntry; - RemoveEntryList(Entry); + UnlinkFontMemCollection(CurrentEntry); break; } @@ -1164,6 +1305,52 @@ IntGdiRemoveFontMemResource(HANDLE hMMFont) } +VOID FASTCALL +IntGdiCleanupPrivateFontsForProcess(VOID) +{ + PPROCESSINFO Win32Process = PsGetCurrentProcessWin32Process(); + PLIST_ENTRY Entry; + PFONT_ENTRY_COLL_MEM EntryCollection; + + DPRINT("IntGdiCleanupPrivateFontsForProcess()\n"); + do { + Entry = NULL; + EntryCollection = NULL; + + IntLockProcessPrivateFonts(Win32Process); + if (!IsListEmpty(&Win32Process->PrivateMemFontListHead)) + { + Entry = Win32Process->PrivateMemFontListHead.Flink; + EntryCollection = CONTAINING_RECORD(Entry, FONT_ENTRY_COLL_MEM, ListEntry); + UnlinkFontMemCollection(EntryCollection); + } + IntUnLockProcessPrivateFonts(Win32Process); + + if (EntryCollection) + { + IntGdiCleanupMemEntry(EntryCollection->Entry); + ExFreePoolWithTag(EntryCollection, TAG_FONT); + } + else + { + /* No Mem fonts anymore, see if we have any other private fonts left */ + Entry = NULL; + IntLockProcessPrivateFonts(Win32Process); + if (!IsListEmpty(&Win32Process->PrivateFontListHead)) + { + Entry = RemoveHeadList(&Win32Process->PrivateFontListHead); + } + IntUnLockProcessPrivateFonts(Win32Process); + + if (Entry) + { + CleanupFontEntry(CONTAINING_RECORD(Entry, FONT_ENTRY, ListEntry)); + } + } + + } while (Entry); +} + BOOL FASTCALL IntIsFontRenderingEnabled(VOID) { @@ -1737,7 +1924,7 @@ FindFaceNameInList(PUNICODE_STRING FaceName, PLIST_ENTRY Head) Entry = Head->Flink; while (Entry != Head) { - CurrentEntry = (PFONT_ENTRY) CONTAINING_RECORD(Entry, FONT_ENTRY, ListEntry); + CurrentEntry = CONTAINING_RECORD(Entry, FONT_ENTRY, ListEntry); FontGDI = CurrentEntry->Font; ASSERT(FontGDI); @@ -1774,7 +1961,8 @@ FindFaceNameInLists(PUNICODE_STRING FaceName) PPROCESSINFO Win32Process; PFONTGDI Font; - /* Search the process local list */ + /* Search the process local list. + We do not have to search the 'Mem' list, since those fonts are linked in the PrivateFontListHead */ Win32Process = PsGetCurrentProcessWin32Process(); IntLockProcessPrivateFonts(Win32Process); Font = FindFaceNameInList(FaceName, &Win32Process->PrivateFontListHead); @@ -2333,10 +2521,12 @@ ftGdiGlyphCacheGet( PLIST_ENTRY CurrentEntry; PFONT_CACHE_ENTRY FontEntry; + ASSERT_FREETYPE_LOCK_HELD(); + CurrentEntry = FontCacheListHead.Flink; while (CurrentEntry != &FontCacheListHead) { - FontEntry = (PFONT_CACHE_ENTRY)CurrentEntry; + FontEntry = CONTAINING_RECORD(CurrentEntry, FONT_CACHE_ENTRY, ListEntry); if ((FontEntry->Face == Face) && (FontEntry->GlyphIndex == GlyphIndex) && (FontEntry->Height == Height) && @@ -2412,6 +2602,8 @@ ftGdiGlyphCacheSet( FT_Bitmap AlignedBitmap; FT_BitmapGlyph BitmapGlyph; + ASSERT_FREETYPE_LOCK_HELD(); + error = FT_Get_Glyph(GlyphSlot, &GlyphCopy); if (error) { @@ -2455,13 +2647,10 @@ ftGdiGlyphCacheSet( NewEntry->mxWorldToDevice = *pmx; InsertHeadList(&FontCacheListHead, &NewEntry->ListEntry); - if (FontCacheNumEntries++ > MAX_FONT_CACHE) + if (++FontCacheNumEntries > MAX_FONT_CACHE) { - NewEntry = (PFONT_CACHE_ENTRY)FontCacheListHead.Blink; - FT_Done_Glyph((FT_Glyph)NewEntry->BitmapGlyph); - RemoveTailList(&FontCacheListHead); - ExFreePoolWithTag(NewEntry, TAG_FONT); - FontCacheNumEntries--; + NewEntry = CONTAINING_RECORD(FontCacheListHead.Blink, FONT_CACHE_ENTRY, ListEntry); + RemoveCachedEntry(NewEntry); } return BitmapGlyph; @@ -4116,7 +4305,7 @@ FindBestFontFromList(FONTOBJ **FontObj, ULONG *MatchPenalty, LOGFONTW *LogFont, Entry = Head->Flink; while (Entry != Head) { - CurrentEntry = (PFONT_ENTRY) CONTAINING_RECORD(Entry, FONT_ENTRY, ListEntry); + CurrentEntry = CONTAINING_RECORD(Entry, FONT_ENTRY, ListEntry); FontGDI = CurrentEntry->Font; ASSERT(FontGDI); Face = FontGDI->SharedFace->Face; diff --git a/reactos/win32ss/gdi/ntgdi/init.c b/reactos/win32ss/gdi/ntgdi/init.c index 195fb19fd0a..35bab0f6029 100644 --- a/reactos/win32ss/gdi/ntgdi/init.c +++ b/reactos/win32ss/gdi/ntgdi/init.c @@ -50,6 +50,8 @@ GdiProcessDestroy(PEPROCESS Process) ASSERT(ppiCurrent); ASSERT(ppiCurrent->peProcess == Process); + IntGdiCleanupPrivateFontsForProcess(); + /* And GDI ones too */ GDI_CleanupForProcess(Process); diff --git a/reactos/win32ss/gdi/ntgdi/text.h b/reactos/win32ss/gdi/ntgdi/text.h index e0b7c38fcb9..b0e1e0c61f6 100644 --- a/reactos/win32ss/gdi/ntgdi/text.h +++ b/reactos/win32ss/gdi/ntgdi/text.h @@ -112,6 +112,7 @@ BOOL FASTCALL IntIsFontRenderingEnabled(VOID); VOID FASTCALL IntEnableFontRendering(BOOL Enable); ULONG FASTCALL FontGetObject(PTEXTOBJ TextObj, ULONG Count, PVOID Buffer); VOID FASTCALL IntLoadSystemFonts(VOID); +VOID FASTCALL IntGdiCleanupPrivateFontsForProcess(VOID); INT FASTCALL IntGdiAddFontResource(PUNICODE_STRING FileName, DWORD Characteristics); HANDLE FASTCALL IntGdiAddFontMemResource(PVOID Buffer, DWORD dwSize, PDWORD pNumAdded); BOOL FASTCALL IntGdiRemoveFontMemResource(HANDLE hMMFont); diff --git a/rostests/apitests/gdi32/AddFontMemResourceEx.c b/rostests/apitests/gdi32/AddFontMemResourceEx.c index b249998e785..d5587befcfa 100644 --- a/rostests/apitests/gdi32/AddFontMemResourceEx.c +++ b/rostests/apitests/gdi32/AddFontMemResourceEx.c @@ -4,7 +4,9 @@ * PURPOSE: Test for AddFontMemResourceEx * PROGRAMMERS: Mark Jansen * - * PanosePitchTest by Katayama Hirofumi MZ, licensed under CC BY + * PanosePitchTest + TTCTestV by Katayama Hirofumi MZ, licensed under CC BY + * Shadows_Into_Light by Kimberly Geswein, licensed under OFL + * Captured from firefox, embedded on reactos.org */ @@ -12,102 +14,260 @@ #include #include +typedef struct _fnt_res +{ + const char* FontName; + TEXTMETRICA tm; +} fnt_res; -static void test_font_caps(HDC hdc) +typedef struct _fnt_test +{ + const char* ResourceName; + int NumFaces; + fnt_res res[4]; +} fnt_test; + + + +static fnt_test test_data[] = +{ + { + .ResourceName = "PanosePitchTest.ttf", + .NumFaces = 2, + .res = + { + { + .FontName = "PanosePitchTest", + .tm.tmHeight = 11, + .tm.tmAscent = 11, + .tm.tmDescent = 0, + .tm.tmInternalLeading = -5, + .tm.tmExternalLeading = 1, + .tm.tmAveCharWidth = 8, + .tm.tmMaxCharWidth = 11, + .tm.tmWeight = FW_NORMAL, + .tm.tmOverhang = 0, + .tm.tmDigitizedAspectX = 96, + .tm.tmDigitizedAspectY = 96, + .tm.tmFirstChar = 63, + .tm.tmLastChar = 65, + .tm.tmDefaultChar = 165, + .tm.tmBreakChar = 65, + .tm.tmItalic = 0, + .tm.tmUnderlined = 0, + .tm.tmStruckOut = 0, + .tm.tmPitchAndFamily = TMPF_TRUETYPE | TMPF_VECTOR, + .tm.tmCharSet = SHIFTJIS_CHARSET, + }, + { + .FontName = "@PanosePitchTest", + .tm.tmHeight = 11, + .tm.tmAscent = 11, + .tm.tmDescent = 0, + .tm.tmInternalLeading = -5, + .tm.tmExternalLeading = 1, + .tm.tmAveCharWidth = 8, + .tm.tmMaxCharWidth = 11, + .tm.tmWeight = FW_NORMAL, + .tm.tmOverhang = 0, + .tm.tmDigitizedAspectX = 96, + .tm.tmDigitizedAspectY = 96, + .tm.tmFirstChar = 63, + .tm.tmLastChar = 65, + .tm.tmDefaultChar = 165, + .tm.tmBreakChar = 65, + .tm.tmItalic = 0, + .tm.tmUnderlined = 0, + .tm.tmStruckOut = 0, + .tm.tmPitchAndFamily = TMPF_TRUETYPE | TMPF_VECTOR, + .tm.tmCharSet = SHIFTJIS_CHARSET, + }, + }, + }, + { + .ResourceName = "TTCTestV.ttc", + .NumFaces = 3, + .res = + { + { + .FontName = "No1Of3in1", + .tm.tmHeight = 12, + .tm.tmAscent = 12, + .tm.tmDescent = 0, + .tm.tmInternalLeading = -4, + .tm.tmExternalLeading = 1, + .tm.tmAveCharWidth = -525, + .tm.tmMaxCharWidth = 6, + .tm.tmWeight = FW_NORMAL, + .tm.tmOverhang = 0, + .tm.tmDigitizedAspectX = 96, + .tm.tmDigitizedAspectY = 96, + .tm.tmFirstChar = 63, + .tm.tmLastChar = 65, + .tm.tmDefaultChar = 64, + .tm.tmBreakChar = 65, + .tm.tmItalic = 0, + .tm.tmUnderlined = 0, + .tm.tmStruckOut = 0, + .tm.tmPitchAndFamily = TMPF_TRUETYPE | TMPF_VECTOR | TMPF_FIXED_PITCH, + .tm.tmCharSet = ANSI_CHARSET, + }, + { + .FontName = "No2Of3in1", + .tm.tmHeight = 12, + .tm.tmAscent = 12, + .tm.tmDescent = 0, + .tm.tmInternalLeading = -4, + .tm.tmExternalLeading = 1, + .tm.tmAveCharWidth = 8, + .tm.tmMaxCharWidth = 7, + .tm.tmWeight = FW_NORMAL, + .tm.tmOverhang = 0, + .tm.tmDigitizedAspectX = 96, + .tm.tmDigitizedAspectY = 96, + .tm.tmFirstChar = 63, + .tm.tmLastChar = 65, + .tm.tmDefaultChar = 64, + .tm.tmBreakChar = 65, + .tm.tmItalic = 0, + .tm.tmUnderlined = 0, + .tm.tmStruckOut = 0, + .tm.tmPitchAndFamily = TMPF_TRUETYPE | TMPF_VECTOR | TMPF_FIXED_PITCH, + .tm.tmCharSet = ANSI_CHARSET, + }, + { + .FontName = "No3Of3in1V", + .tm.tmHeight = 12, + .tm.tmAscent = 12, + .tm.tmDescent = 0, + .tm.tmInternalLeading = -4, + .tm.tmExternalLeading = 1, + .tm.tmAveCharWidth = 8, + .tm.tmMaxCharWidth = 13, + .tm.tmWeight = FW_NORMAL, + .tm.tmOverhang = 0, + .tm.tmDigitizedAspectX = 96, + .tm.tmDigitizedAspectY = 96, + .tm.tmFirstChar = 63, + .tm.tmLastChar = 65, + .tm.tmDefaultChar = 64, + .tm.tmBreakChar = 65, + .tm.tmItalic = 0, + .tm.tmUnderlined = 0, + .tm.tmStruckOut = 0, + .tm.tmPitchAndFamily = FF_MODERN | TMPF_TRUETYPE | TMPF_VECTOR, + .tm.tmCharSet = ANSI_CHARSET, + }, + }, + }, + { + .ResourceName = "Shadows_Into_Light.ttf", + .NumFaces = 1, + .res = + { + { + .FontName = "ufaXaAlLOxCUGYJ7KN51UP2Q==", + .tm.tmHeight = 26, + .tm.tmAscent = 19, + .tm.tmDescent = 7, + .tm.tmInternalLeading = 10, + .tm.tmExternalLeading = 0, + .tm.tmAveCharWidth = 7, + .tm.tmMaxCharWidth = 23, + .tm.tmWeight = FW_NORMAL, + .tm.tmOverhang = 0, + .tm.tmDigitizedAspectX = 96, + .tm.tmDigitizedAspectY = 96, + .tm.tmFirstChar = 30, + .tm.tmLastChar = 255, + .tm.tmDefaultChar = 31, + .tm.tmBreakChar = 32, + .tm.tmItalic = 0, + .tm.tmUnderlined = 0, + .tm.tmStruckOut = 0, + .tm.tmPitchAndFamily = TMPF_TRUETYPE | TMPF_VECTOR | TMPF_FIXED_PITCH, + .tm.tmCharSet = ANSI_CHARSET, + }, + }, + }, +}; + + +#define ok_int2(expression) \ + do { \ + int _value = (expression); \ + ok(_value == (res->expression), "Wrong value for '%s', expected: %d, got: %d for %s/%s\n", \ + #expression, (int)(res->expression), _value, test_name, res->FontName); \ + } while (0) + +#define ok_hex2(expression) \ + do { \ + int _value = (expression); \ + ok(_value == (res->expression), "Wrong value for '%s', expected: 0x%x, got: 0x%x for %s/%s\n", \ + #expression, (int)(res->expression), _value, test_name, res->FontName); \ + } while (0) + + +static void test_font_caps(HDC hdc, int test_index) { HGDIOBJ old; TEXTMETRICA tm = { 0 }; char name[64]; BOOL ret; - HFONT font = CreateFont(0, 0, 0, 0, FW_DONTCARE, FALSE, FALSE, FALSE, DEFAULT_CHARSET, - OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH, TEXT("PanosePitchTest")); + HFONT font; + int n; + const char* test_name = test_data[test_index].ResourceName; - if (font) + for (n = 0; test_data[test_index].res[n].FontName; ++n) { - old = SelectObject(hdc, font); + fnt_res* res = test_data[test_index].res + n; + font = CreateFontA(0, 0, 0, 0, FW_DONTCARE, FALSE, FALSE, FALSE, DEFAULT_CHARSET, + OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH, res->FontName); - memset(&tm, 0xaa, sizeof(tm)); - ret = GetTextMetricsA(hdc, &tm); - ok_int(ret, TRUE); - - SetLastError(0xdeadbeef); - ret = GetTextFaceA(hdc, sizeof(name), name); - ok(ret, "GetTextFaceA error %lu\n", GetLastError()); - if (ret) + if (font) { - ok_str(name, "PanosePitchTest"); + old = SelectObject(hdc, font); + + memset(&tm, 0xaa, sizeof(tm)); + ret = GetTextMetricsA(hdc, &tm); + ok(ret, "GetTextMetricsA() for %s/%s\n", test_name, res->FontName); + + SetLastError(0xdeadbeef); + ret = GetTextFaceA(hdc, sizeof(name), name); + ok(ret, "GetTextFaceA error %lu for %s/%s\n", GetLastError(), test_name, res->FontName); + if (ret) + { + ok(!strcmp(name, res->FontName), "FontName was %s, expected %s for %s/%s", name, res->FontName, test_name, res->FontName); + } + + ok_int2(tm.tmHeight); + ok_int2(tm.tmAscent); + ok_int2(tm.tmDescent); + ok_int2(tm.tmInternalLeading); + ok_int2(tm.tmExternalLeading); + ok_int2(tm.tmAveCharWidth); + ok_int2(tm.tmMaxCharWidth); + ok_int2(tm.tmWeight); + ok_int2(tm.tmOverhang); + ok_int2(tm.tmDigitizedAspectX); + ok_int2(tm.tmDigitizedAspectY); + ok_int2(tm.tmFirstChar); + ok_int2(tm.tmLastChar); + ok_int2(tm.tmDefaultChar); + ok_int2(tm.tmBreakChar); + ok_int2(tm.tmItalic); + ok_int2(tm.tmUnderlined); + ok_int2(tm.tmStruckOut); + ok_hex2(tm.tmPitchAndFamily); + ok_int2(tm.tmCharSet); + + SelectObject(hdc, old); + DeleteObject(font); } - - ok_int(tm.tmHeight, 11); - ok_int(tm.tmAscent, 11); - ok_int(tm.tmDescent, 0); - ok_int(tm.tmInternalLeading, -5); - ok_int(tm.tmExternalLeading, 1); - ok_int(tm.tmAveCharWidth, 8); - ok_int(tm.tmMaxCharWidth, 11); - ok_int(tm.tmWeight, FW_NORMAL); - ok_int(tm.tmOverhang, 0); - ok_int(tm.tmDigitizedAspectX, 96); - ok_int(tm.tmDigitizedAspectY, 96); - ok_int(tm.tmFirstChar, 63); - ok_int(tm.tmLastChar, 65); - ok_int(tm.tmDefaultChar, 165); - ok_int(tm.tmBreakChar, 65); - ok_int(tm.tmItalic, 0); - ok_int(tm.tmUnderlined, 0); - ok_int(tm.tmStruckOut, 0); - ok_hex(tm.tmPitchAndFamily, TMPF_TRUETYPE | TMPF_VECTOR); - ok_int(tm.tmCharSet, SHIFTJIS_CHARSET); - - SelectObject(hdc, old); - DeleteObject(font); - } - - font = CreateFont(0, 0, 0, 0, FW_DONTCARE, FALSE, FALSE, FALSE, DEFAULT_CHARSET, - OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH, TEXT("@PanosePitchTest")); - - if (font) - { - old = SelectObject(hdc, font); - - memset(&tm, 0xaa, sizeof(tm)); - ret = GetTextMetricsA(hdc, &tm); - ok_int(ret, TRUE); - - SetLastError(0xdeadbeef); - ret = GetTextFaceA(hdc, sizeof(name), name); - ok(ret, "GetTextFaceA error %lu\n", GetLastError()); - if (ret) - { - ok_str(name, "@PanosePitchTest"); - } - - ok_int(tm.tmHeight, 11); - ok_int(tm.tmAscent, 11); - ok_int(tm.tmDescent, 0); - ok_int(tm.tmInternalLeading, -5); - ok_int(tm.tmExternalLeading, 1); - ok_int(tm.tmAveCharWidth, 8); - ok_int(tm.tmMaxCharWidth, 11); - ok_int(tm.tmWeight, FW_NORMAL); - ok_int(tm.tmOverhang, 0); - ok_int(tm.tmDigitizedAspectX, 96); - ok_int(tm.tmDigitizedAspectY, 96); - ok_int(tm.tmFirstChar, 63); - ok_int(tm.tmLastChar, 65); - ok_int(tm.tmDefaultChar, 165); - ok_int(tm.tmBreakChar, 65); - ok_int(tm.tmItalic, 0); - ok_int(tm.tmUnderlined, 0); - ok_int(tm.tmStruckOut, 0); - ok_hex(tm.tmPitchAndFamily, TMPF_TRUETYPE | TMPF_VECTOR); - ok_int(tm.tmCharSet, SHIFTJIS_CHARSET); - - SelectObject(hdc, old); - DeleteObject(font); } } + /* Not working as of 2017-04-08 on ReactOS */ static BOOL is_font_available(HDC hdc, const char* fontName) { @@ -122,7 +282,7 @@ static BOOL is_font_available(HDC hdc, const char* fontName) SetLastError(0xdeadbeef); ret = GetTextFaceA(hdc, sizeof(name), name); - ok(ret, "GetTextFaceA error %lu\n", GetLastError()); + ok(ret, "GetTextFaceA error %lu for %s\n", GetLastError(), fontName); SelectObject(hdc, old); DeleteObject(font); @@ -145,16 +305,20 @@ START_TEST(AddFontMemResourceEx) LPVOID pFont; HANDLE hFont; + fnt_test* data; + int n; HDC hdc = CreateCompatibleDC(NULL); BOOL is_font_available_broken = is_font_available(hdc, "Nonexisting font name here"); - ok(!is_font_available_broken, "Validating font is broken! (CORE-13053) !\n"); + ok(!is_font_available_broken, "Validating font is broken! (CORE-13053)!\n"); - if (is_font_available_broken || !is_font_available(hdc, "PanosePitchTest")) + for (n = 0; n < _countof(test_data); ++n) { + data = test_data + n; + mod = GetModuleHandle(NULL); - hRsrc = FindResource(mod, TEXT("PanosePitchTest.ttf"), MAKEINTRESOURCE(RT_RCDATA)); + hRsrc = FindResourceA(mod, data->ResourceName, MAKEINTRESOURCE(RT_RCDATA)); hTemplate = LoadResource(mod, hRsrc); dwSize = SizeofResource(mod, hRsrc); @@ -162,30 +326,27 @@ START_TEST(AddFontMemResourceEx) dwNumFonts = 0; hFont = AddFontMemResourceEx(pFont, dwSize, NULL, &dwNumFonts); - ok_int(dwNumFonts, 2); - ok(hFont != NULL, "Expected valid handle\n"); + ok(dwNumFonts == data->NumFaces, "dwNumFonts was %lu, expected %d for %s\n", dwNumFonts, data->NumFaces, data->ResourceName); + ok(hFont != NULL, "Expected valid handle for %s\n", data->ResourceName); if (hFont) { - test_font_caps(hdc); + test_font_caps(hdc, n); RemoveFontMemResourceEx(hFont); if (!is_font_available_broken) { - ok (!is_font_available(hdc, "PanosePitchTest"), "Expected font to be unregistered again\n"); + ok (!is_font_available(hdc, data->ResourceName), "Expected font to be unregistered again for %s\n", data->ResourceName); } else { - skip("Font unregister test\n"); + skip("Font unregister test for %s\n", data->ResourceName); } } UnlockResource(hTemplate); FreeResource(hTemplate); } - else - { - skip("Font PanosePitchTest already available\n"); - } + DeleteDC(hdc); } diff --git a/rostests/apitests/gdi32/Shadows_Into_Light.ttf b/rostests/apitests/gdi32/Shadows_Into_Light.ttf new file mode 100644 index 0000000000000000000000000000000000000000..620f7735528a61235e8fc196ab57b7b24fe6f0cf GIT binary patch literal 29180 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 literal 0 HcmV?d00001 diff --git a/rostests/apitests/gdi32/TTCTestV.ttc b/rostests/apitests/gdi32/TTCTestV.ttc new file mode 100644 index 0000000000000000000000000000000000000000..e85ae5ee2d640bb55fd42de1c8471593e7fccd64 GIT binary patch literal 4120 zcmeHJTWnNS6y4`Orcc^wsYppBcK}OiXqgAl(o84?S`?uml1i$k9m}*+X=ldiK&Ti} zqp?PT_#i$Y5o7!_A<{2}L{T)v#J~q75`rN?68MmSMEHUk*FN`5VP=Xzg%1)C5AGoutKMFj5EYCOtoZHhk z*!{)HLkob<14l!_z{dRK;k88Odf8{gK0 z9lv9I1I8z0>#vvhRbc(sSTAiYSn+)oQ69Q&tGPTe9k>%%*(#t<-W$O8Wb4#L{#}?4 z-`~MjCk`B^Y@h3?kBJIc7u(j^@COx)0$0j&{j%c-FrWK`Z2f$+elbzu^H{%|&$aNx zPd?x)!17!j@6jtnlh}4?tGSE1fL8(Aw#NBfC-M5!xi$fZfaSUVc(57tq_<#etvuIP z32{`+_Ec}-xcRj75-`WrtSyL};?EqA?aT${vxg&yM$4aVFH7PL8NK z5?N9LujgK&^zZ}mtP1F2;cMVw+=M)m@8l0^CJBeFB>2SINX;3fnv#}+<9=`HKko2< zfGO`A67Y|P#|2+hYkT0u9I8DaXr6y0$Dm8z0}k_ye?-?n(gB^>CbLI!9O8z51n#@! z^_93vX8THJxZKkEQJgHQ!tF2z zUoW{~dmYUMy@>)C3t&bs=0>QWZsQ4k_GQy;dC0S0?E_XR+-U{x5x_#4BFpj4$6%2?EZ zp1rHh_h3d13Qo>UgNt^AYy09LBa3r`|~>=BK5Ba zb4MaMxk*U~F8*3l0~eSNIHyG?9TyiM5jic8^P+QjSQj^sDfy87k@v~<zk99;J{&Vybna7QpwHf|%;htd$8F?%@?cy}@c(2|lcamnS7rd!zGiHEZ z3cg*NSAt@>2L3tucCc^Rd#%m+xZrs;ugalaiq@%;&MVrWeBn|w*XCA5JHX$pYHE6I zE=#M;+tnPMR?&xwHmFP#D4J{YTtz#;?|AsN*_W2{ZCsswcGiRbH|05V