[WIN32SS] Cleanup fonts at process destruction + implement font memory reference...
authorMark Jansen <mark.jansen@reactos.org>
Fri, 14 Apr 2017 18:22:57 +0000 (18:22 +0000)
committerMark Jansen <mark.jansen@reactos.org>
Fri, 14 Apr 2017 18:22:57 +0000 (18:22 +0000)
Thanks to everyone involved in reviewing this code! (See CR-112)
CORE-13056

svn path=/trunk/; revision=74309

reactos/win32ss/gdi/eng/engobjects.h
reactos/win32ss/gdi/ntgdi/font.h
reactos/win32ss/gdi/ntgdi/freetype.c
reactos/win32ss/gdi/ntgdi/init.c
reactos/win32ss/gdi/ntgdi/text.h
rostests/apitests/gdi32/AddFontMemResourceEx.c
rostests/apitests/gdi32/Shadows_Into_Light.ttf [new file with mode: 0644]
rostests/apitests/gdi32/TTCTestV.ttc [new file with mode: 0644]
rostests/apitests/gdi32/resource.rc

index 584ebe7..333b6e1 100644 (file)
@@ -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 {
index 814b18d..0383fe5 100644 (file)
@@ -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;
index 14e41d5..b2bebd7 100644 (file)
@@ -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;
index 195fb19..35bab0f 100644 (file)
@@ -50,6 +50,8 @@ GdiProcessDestroy(PEPROCESS Process)
     ASSERT(ppiCurrent);
     ASSERT(ppiCurrent->peProcess == Process);
 
+    IntGdiCleanupPrivateFontsForProcess();
+
     /* And GDI ones too */
     GDI_CleanupForProcess(Process);
 
index e0b7c38..b0e1e0c 100644 (file)
@@ -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);
index b249998..d5587be 100644 (file)
@@ -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
  */
 
 
 #include <wingdi.h>
 #include <winuser.h>
 
+typedef struct _fnt_res
+{
+    const char* FontName;
+    TEXTMETRICA tm;
+} fnt_res;
+
+typedef struct _fnt_test
+{
+    const char* ResourceName;
+    int NumFaces;
+    fnt_res res[4];
+} fnt_test;
+
+
 
-static void test_font_caps(HDC hdc)
+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);
-
-        memset(&tm, 0xaa, sizeof(tm));
-        ret = GetTextMetricsA(hdc, &tm);
-        ok_int(ret, TRUE);
+        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);
 
-        SetLastError(0xdeadbeef);
-        ret = GetTextFaceA(hdc, sizeof(name), name);
-        ok(ret, "GetTextFaceA error %lu\n", GetLastError());
-        if (ret)
+        if (font)
         {
-            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);
-    }
-
-    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"));
+            old = SelectObject(hdc, font);
 
-    if (font)
-    {
-        old = SelectObject(hdc, font);
+            memset(&tm, 0xaa, sizeof(tm));
+            ret = GetTextMetricsA(hdc, &tm);
+            ok(ret, "GetTextMetricsA() for %s/%s\n", test_name, 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 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);
+            }
 
-        SetLastError(0xdeadbeef);
-        ret = GetTextFaceA(hdc, sizeof(name), name);
-        ok(ret, "GetTextFaceA error %lu\n", GetLastError());
-        if (ret)
-        {
-            ok_str(name, "@PanosePitchTest");
+            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);
     }
 }
 
+
 /* 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 (file)
index 0000000..620f773
Binary files /dev/null and b/rostests/apitests/gdi32/Shadows_Into_Light.ttf differ
diff --git a/rostests/apitests/gdi32/TTCTestV.ttc b/rostests/apitests/gdi32/TTCTestV.ttc
new file mode 100644 (file)
index 0000000..e85ae5e
Binary files /dev/null and b/rostests/apitests/gdi32/TTCTestV.ttc differ
index fbf571f..71ccce9 100644 (file)
@@ -2,3 +2,5 @@
 
 ReactOSTestTahoma.ttf RCDATA ReactOSTestTahoma.ttf
 PanosePitchTest.ttf RCDATA PanosePitchTest.ttf
+TTCTestV.ttc RCDATA TTCTestV.ttc
+Shadows_Into_Light.ttf RCDATA Shadows_Into_Light.ttf