From a550199c6f37e1b05a386ea57eee4c40cc91d84d Mon Sep 17 00:00:00 2001 From: "bungeman@google.com" Date: Fri, 18 May 2012 19:06:41 +0000 Subject: [PATCH] CreateTypefaceFromStream for GDI. http://codereview.appspot.com/5616047/ git-svn-id: http://skia.googlecode.com/svn/trunk@4001 2bbb7eff-a529-9590-31e7-b0007b416f81 --- gyp/tests.gyp | 9 ++ src/ports/SkFontHost_win.cpp | 209 +++++++++++++++++++++++++++++++++++-------- src/sfnt/SkOTUtils.cpp | 134 +++++++++++++++++++++++++++ src/sfnt/SkOTUtils.h | 20 +++++ src/sfnt/SkSFNTHeader.h | 1 + tests/FontHostStreamTest.cpp | 109 ++++++++++++++++++++++ 6 files changed, 445 insertions(+), 37 deletions(-) create mode 100644 tests/FontHostStreamTest.cpp diff --git a/gyp/tests.gyp b/gyp/tests.gyp index ec850a4..631d280 100644 --- a/gyp/tests.gyp +++ b/gyp/tests.gyp @@ -38,6 +38,7 @@ '../tests/EmptyPathTest.cpp', '../tests/FillPathTest.cpp', '../tests/FlateTest.cpp', + '../tests/FontHostStreamTest.cpp', '../tests/FontHostTest.cpp', '../tests/GeometryTest.cpp', '../tests/GLInterfaceValidation.cpp', @@ -94,6 +95,14 @@ 'pdf.gyp:pdf', 'utils.gyp:utils', ], + 'conditions': [ + [ 'skia_os == "mac"', { + 'sources!': [ + #mac port currently does not support fonts from streams. + '../tests/FontHostStreamTest.cpp', + ], + }], + ], }, ], } diff --git a/src/ports/SkFontHost_win.cpp b/src/ports/SkFontHost_win.cpp index 20c54a3..cf23e93 100755 --- a/src/ports/SkFontHost_win.cpp +++ b/src/ports/SkFontHost_win.cpp @@ -6,23 +6,23 @@ * found in the LICENSE file. */ - -#include "SkColorFilter.h" -#include "SkString.h" -#include "SkEndian.h" -#include "SkFontHost.h" -#include "SkDescriptor.h" #include "SkAdvancedTypefaceMetrics.h" +#include "SkBase64.h" +#include "SkData.h" +#include "SkDescriptor.h" +#include "SkFontHost.h" +#include "SkOTUtils.h" #include "SkStream.h" +#include "SkString.h" #include "SkThread.h" #include "SkTypeface_win.h" #include "SkTypefaceCache.h" #include "SkUtils.h" -#ifdef WIN32 -#include "windows.h" -#include "tchar.h" -#include "usp10.h" +#include "SkTypes.h" +#include +#include +#include // always packed xxRRGGBB typedef uint32_t SkGdiRGB; @@ -158,16 +158,47 @@ public: } }; +class FontMemResourceTypeface : public LogFontTypeface { +public: + /** + * Takes ownership of fontMemResource. + */ + FontMemResourceTypeface(SkTypeface::Style style, SkFontID fontID, const LOGFONT& lf, HANDLE fontMemResource) : + LogFontTypeface(style, fontID, lf), fFontMemResource(fontMemResource) {} + + HANDLE fFontMemResource; + + /** + * The created FontMemResourceTypeface takes ownership of fontMemResource. + */ + static FontMemResourceTypeface* Create(const LOGFONT& lf, HANDLE fontMemResource) { + SkTypeface::Style style = get_style(lf); + SkFontID fontID = SkTypefaceCache::NewFontID(); + return new FontMemResourceTypeface(style, fontID, lf, fontMemResource); + } + +protected: + virtual void weak_dispose() const SK_OVERRIDE { + RemoveFontMemResourceEx(fFontMemResource); + //SkTypefaceCache::Remove(this); + INHERITED::weak_dispose(); + } + +private: + typedef LogFontTypeface INHERITED; +}; + static const LOGFONT& get_default_font() { static LOGFONT gDefaultFont; return gDefaultFont; } static bool FindByLogFont(SkTypeface* face, SkTypeface::Style requestedStyle, void* ctx) { - LogFontTypeface* lface = reinterpret_cast(face); + LogFontTypeface* lface = static_cast(face); const LOGFONT* lf = reinterpret_cast(ctx); - return get_style(lface->fLogFont) == requestedStyle && + return lface && + get_style(lface->fLogFont) == requestedStyle && !memcmp(&lface->fLogFont, lf, sizeof(LOGFONT)); } @@ -187,13 +218,24 @@ SkTypeface* SkCreateTypefaceFromLOGFONT(const LOGFONT& origLF) { } /** + * The created SkTypeface takes ownership of fontMemResource. + */ +SkTypeface* SkCreateFontMemResourceTypefaceFromLOGFONT(const LOGFONT& origLF, HANDLE fontMemResource) { + LOGFONT lf = origLF; + make_canonical(&lf); + FontMemResourceTypeface* face = FontMemResourceTypeface::Create(lf, fontMemResource); + SkTypefaceCache::Add(face, get_style(lf), false); + return face; +} + +/** * This guy is public */ void SkLOGFONTFromTypeface(const SkTypeface* face, LOGFONT* lf) { if (NULL == face) { *lf = get_default_font(); } else { - *lf = ((const LogFontTypeface*)face)->fLogFont; + *lf = static_cast(face)->fLogFont; } } @@ -205,14 +247,14 @@ SkFontID SkFontHost::NextLogicalFont(SkFontID currFontID, SkFontID origFontID) { } static void ensure_typeface_accessible(SkFontID fontID) { - LogFontTypeface* face = (LogFontTypeface*)SkTypefaceCache::FindByID(fontID); + LogFontTypeface* face = static_cast(SkTypefaceCache::FindByID(fontID)); if (face) { SkFontHost::EnsureTypefaceAccessible(*face); } } static void GetLogFontByID(SkFontID fontID, LOGFONT* lf) { - LogFontTypeface* face = (LogFontTypeface*)SkTypefaceCache::FindByID(fontID); + LogFontTypeface* face = static_cast(SkTypefaceCache::FindByID(fontID)); if (face) { *lf = face->fLogFont; } else { @@ -1291,11 +1333,122 @@ Error: return info; } +//Dummy representation of a Base64 encoded GUID from create_unique_font_name. +#define BASE64_GUID_ID "XXXXXXXXXXXXXXXXXXXXXXXX" +//Length of GUID representation from create_id, including NULL terminator. +#define BASE64_GUID_ID_LEN SK_ARRAY_COUNT(BASE64_GUID_ID) + +SK_COMPILE_ASSERT(BASE64_GUID_ID_LEN < LF_FACESIZE, GUID_longer_than_facesize); + +/** + NameID 6 Postscript names cannot have the character '/'. + It would be easier to hex encode the GUID, but that is 32 bytes, + and many systems have issues with names longer than 28 bytes. + The following need not be any standard base64 encoding. + The encoded value is never decoded. +*/ +static const char postscript_safe_base64_encode[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789-_="; + +/** + Formats a GUID into Base64 and places it into buffer. + buffer should have space for at least BASE64_GUID_ID_LEN characters. + The string will always be null terminated. + XXXXXXXXXXXXXXXXXXXXXXXX0 + */ +static void format_guid_b64(const GUID& guid, char* buffer, size_t bufferSize) { + SkASSERT(bufferSize >= BASE64_GUID_ID_LEN); + size_t written = SkBase64::Encode(&guid, sizeof(guid), buffer, postscript_safe_base64_encode); + SkASSERT(written < LF_FACESIZE); + buffer[written] = '\0'; +} + +/** + Creates a Base64 encoded GUID and places it into buffer. + buffer should have space for at least BASE64_GUID_ID_LEN characters. + The string will always be null terminated. + XXXXXXXXXXXXXXXXXXXXXXXX0 + */ +static HRESULT create_unique_font_name(char* buffer, size_t bufferSize) { + GUID guid = {}; + if (FAILED(CoCreateGuid(&guid))) { + return E_UNEXPECTED; + } + format_guid_b64(guid, buffer, bufferSize); + + return S_OK; +} + +/** + Introduces a font to GDI. On failure will return NULL. The returned handle + should eventually be passed to RemoveFontMemResourceEx. +*/ +static HANDLE activate_font(SkData* fontData) { + DWORD numFonts = 0; + //AddFontMemResourceEx just copies the data, but does not specify const. + HANDLE fontHandle = AddFontMemResourceEx(const_cast(fontData->data()), + fontData->size(), + 0, + &numFonts); + + if (fontHandle != NULL && numFonts < 1) { + RemoveFontMemResourceEx(fontHandle); + return NULL; + } + + return fontHandle; +} + +static void logfont_for_name(const char* familyName, LOGFONT& lf) { + memset(&lf, 0, sizeof(LOGFONT)); +#ifdef UNICODE + // Get the buffer size needed first. + size_t str_len = ::MultiByteToWideChar(CP_UTF8, 0, familyName, + -1, NULL, 0); + // Allocate a buffer (str_len already has terminating null + // accounted for). + wchar_t *wideFamilyName = new wchar_t[str_len]; + // Now actually convert the string. + ::MultiByteToWideChar(CP_UTF8, 0, familyName, -1, + wideFamilyName, str_len); + ::wcsncpy(lf.lfFaceName, wideFamilyName, LF_FACESIZE); + delete [] wideFamilyName; + lf.lfFaceName[LF_FACESIZE-1] = L'\0'; +#else + ::strncpy(lf.lfFaceName, familyName, LF_FACESIZE); + lf.lfFaceName[LF_FACESIZE-1] = '\0'; +#endif +} + SkTypeface* SkFontHost::CreateTypefaceFromStream(SkStream* stream) { + // Create a unique and unpredictable font name. + // Avoids collisions and access from CSS. + char familyName[BASE64_GUID_ID_LEN]; + const int familyNameSize = SK_ARRAY_COUNT(familyName); + if (FAILED(create_unique_font_name(familyName, familyNameSize))) { + return NULL; + } + + // Change the name of the font. + SkData* rewrittenFontData = SkOTUtils::RenameFont(stream, familyName, familyNameSize-1); + if (NULL == rewrittenFontData) { + return NULL; + } + SkAutoUnref aur = SkAutoUnref(rewrittenFontData); - //Should not be used on Windows, keep linker happy - SkASSERT(false); - return SkCreateTypefaceFromLOGFONT(get_default_font()); + // Register the font with GDI. + HANDLE fontReference = activate_font(rewrittenFontData); + if (NULL == fontReference) { + return NULL; + } + + // Create the typeface. + LOGFONT lf; + logfont_for_name(familyName, lf); + + return SkCreateFontMemResourceTypefaceFromLOGFONT(lf, fontReference); } SkStream* SkFontHost::OpenStream(SkFontID uniqueID) { @@ -1358,23 +1511,7 @@ SkTypeface* SkFontHost::CreateTypeface(const SkTypeface* familyFace, LogFontTypeface* face = (LogFontTypeface*)familyFace; lf = face->fLogFont; } else { - memset(&lf, 0, sizeof(LOGFONT)); -#ifdef UNICODE - // Get the buffer size needed first. - size_t str_len = ::MultiByteToWideChar(CP_UTF8, 0, familyName, - -1, NULL, 0); - // Allocate a buffer (str_len already has terminating null - // accounted for). - wchar_t *wideFamilyName = new wchar_t[str_len]; - // Now actually convert the string. - ::MultiByteToWideChar(CP_UTF8, 0, familyName, -1, - wideFamilyName, str_len); - ::wcsncpy(lf.lfFaceName, wideFamilyName, LF_FACESIZE); - delete [] wideFamilyName; -#else - ::strncpy(lf.lfFaceName, familyName, LF_FACESIZE); -#endif - lf.lfFaceName[LF_FACESIZE-1] = '\0'; + logfont_for_name(familyName, lf); } setStyle(&lf, style); return SkCreateTypefaceFromLOGFONT(lf); @@ -1446,5 +1583,3 @@ void SkFontHost::FilterRec(SkScalerContext::Rec* rec) { } #endif } - -#endif // WIN32 diff --git a/src/sfnt/SkOTUtils.cpp b/src/sfnt/SkOTUtils.cpp index 1301a06..3e7992f 100644 --- a/src/sfnt/SkOTUtils.cpp +++ b/src/sfnt/SkOTUtils.cpp @@ -5,6 +5,12 @@ * found in the LICENSE file. */ +#include "SkData.h" +#include "SkEndian.h" +#include "SkSFNTHeader.h" +#include "SkStream.h" +#include "SkOTTable_head.h" +#include "SkOTTable_name.h" #include "SkOTTableTypes.h" #include "SkOTUtils.h" @@ -16,3 +22,131 @@ uint32_t SkOTUtils::CalcTableChecksum(SK_OT_ULONG *data, size_t length) { } return sum; } + +SkData* SkOTUtils::RenameFont(SkStream* fontData, + const char* fontName, int fontNameLen) { + + // Get the sfnt header. + SkSFNTHeader sfntHeader; + if (fontData->read(&sfntHeader, sizeof(sfntHeader)) < sizeof(sfntHeader)) { + return NULL; + } + + // Find the existing 'name' table. + int tableIndex; + SkSFNTTableDirectoryEntry tableEntry; + int numTables = SkEndian_SwapBE16(sfntHeader.numTables); + for (tableIndex = 0; tableIndex < numTables; ++tableIndex) { + if (fontData->read(&tableEntry, sizeof(tableEntry)) < sizeof(tableEntry)) { + return NULL; + } + if ('name' == SkEndian_SwapBE32(tableEntry.tag)) { + break; + } + } + if (tableIndex == numTables) { + return NULL; + } + + if (!fontData->rewind()) { + return NULL; + } + + // The required 'name' record types: Family, Style, Unique, Full and PostScript. + const SkOTTableNameRecord::NameID::Predefined::Value namesToCreate[] = { + SkOTTableNameRecord::NameID::Predefined::FontFamilyName, + SkOTTableNameRecord::NameID::Predefined::FontSubfamilyName, + SkOTTableNameRecord::NameID::Predefined::UniqueFontIdentifier, + SkOTTableNameRecord::NameID::Predefined::FullFontName, + SkOTTableNameRecord::NameID::Predefined::PostscriptName, + }; + const int namesCount = SK_ARRAY_COUNT(namesToCreate); + + // Copy the data, leaving out the old name table. + // In theory, we could also remove the DSIG table if it exists. + size_t nameTableLogicalSize = sizeof(SkOTTableName) + (namesCount * sizeof(SkOTTableNameRecord)) + (fontNameLen * sizeof(wchar_t)); + size_t nameTablePhysicalSize = (nameTableLogicalSize + 3) & ~3; // Rounded up to a multiple of 4. + + size_t oldNameTablePhysicalSize = (SkEndian_SwapBE32(tableEntry.logicalLength) + 3) & ~3; // Rounded up to a multiple of 4. + size_t oldNameTableOffset = SkEndian_SwapBE32(tableEntry.offset); + + //originalDataSize is the size of the original data without the name table. + size_t originalDataSize = fontData->getLength() - oldNameTablePhysicalSize; + size_t newDataSize = originalDataSize + nameTablePhysicalSize; + + SK_OT_BYTE* data = static_cast(sk_malloc_throw(newDataSize)); + SkAutoTUnref rewrittenFontData = SkAutoTUnref(SkData::NewFromMalloc(data, newDataSize)); + + if (fontData->read(data, oldNameTableOffset) < oldNameTableOffset) { + return NULL; + } + if (fontData->skip(oldNameTablePhysicalSize) < oldNameTablePhysicalSize) { + return NULL; + } + if (fontData->read(data + oldNameTableOffset, originalDataSize - oldNameTableOffset) < originalDataSize - oldNameTableOffset) { + return NULL; + } + + //Fix up the offsets of the directory entries after the old 'name' table entry. + SkSFNTTableDirectoryEntry* currentEntry = reinterpret_cast(data + sizeof(SkSFNTHeader)); + SkSFNTTableDirectoryEntry* endEntry = currentEntry + numTables; + SkSFNTTableDirectoryEntry* headTableEntry = NULL; + for (; currentEntry < endEntry; ++currentEntry) { + uint32_t oldOffset = SkEndian_SwapBE32(currentEntry->offset); + if (oldOffset > oldNameTableOffset) { + currentEntry->offset = SkEndian_SwapBE32(oldOffset - oldNameTablePhysicalSize); + } + if ('head' == SkEndian_SwapBE32(tableEntry.tag)) { + headTableEntry = currentEntry; + } + } + + // Make the table directory entry point to the new 'name' table. + SkSFNTTableDirectoryEntry* nameTableEntry = reinterpret_cast(data + sizeof(SkSFNTHeader)) + tableIndex; + nameTableEntry->logicalLength = SkEndian_SwapBE32(nameTableLogicalSize); + nameTableEntry->offset = SkEndian_SwapBE32(originalDataSize); + + // Write the new 'name' table after the original font data. + SkOTTableName* nameTable = reinterpret_cast(data + originalDataSize); + unsigned short stringOffset = sizeof(SkOTTableName) + (namesCount * sizeof(SkOTTableNameRecord)); + nameTable->format = SkOTTableName::format_0; + nameTable->count = SkEndian_SwapBE16(namesCount); + nameTable->stringOffset = SkEndian_SwapBE16(stringOffset); + + SkOTTableNameRecord* nameRecords = reinterpret_cast(data + originalDataSize + sizeof(SkOTTableName)); + for (int i = 0; i < namesCount; ++i) { + nameRecords[i].platformID.value = SkOTTableNameRecord::PlatformID::Windows; + nameRecords[i].encodingID.windows.value = SkOTTableNameRecord::EncodingID::Windows::UnicodeBMPUCS2; + nameRecords[i].languageID.windows.value = SkOTTableNameRecord::LanguageID::Windows::English_UnitedStates; + nameRecords[i].nameID.predefined.value = namesToCreate[i]; + nameRecords[i].offset = SkEndian_SwapBE16(0); + nameRecords[i].length = SkEndian_SwapBE16(fontNameLen * sizeof(wchar_t)); + } + + SK_OT_USHORT* nameString = reinterpret_cast(data + originalDataSize + stringOffset); + for (int i = 0; i < fontNameLen; ++i) { + nameString[i] = SkEndian_SwapBE16(fontName[i]); + } + + unsigned char* logical = data + originalDataSize + nameTableLogicalSize; + unsigned char* physical = data + originalDataSize + nameTablePhysicalSize; + for (; logical < physical; ++logical) { + *logical = 0; + } + + // Update the table checksum in the directory entry. + nameTableEntry->checksum = SkEndian_SwapBE32(SkOTUtils::CalcTableChecksum(reinterpret_cast(nameTable), nameTableLogicalSize)); + + // Update the checksum adjustment in the head table. + if (headTableEntry) { + size_t headTableOffset = SkEndian_SwapBE32(headTableEntry->offset); + if (headTableOffset + sizeof(SkOTTableHead) < originalDataSize) { + SkOTTableHead* headTable = reinterpret_cast(data + headTableOffset); + headTable->checksumAdjustment = SkEndian_SwapBE32(0); + uint32_t unadjustedFontChecksum = SkOTUtils::CalcTableChecksum(reinterpret_cast(data), originalDataSize + nameTablePhysicalSize); + headTable->checksumAdjustment = SkEndian_SwapBE32(SkOTTableHead::fontChecksum - unadjustedFontChecksum); + } + } + + return rewrittenFontData.detach(); +} diff --git a/src/sfnt/SkOTUtils.h b/src/sfnt/SkOTUtils.h index 7a58b4d..1398de6 100644 --- a/src/sfnt/SkOTUtils.h +++ b/src/sfnt/SkOTUtils.h @@ -9,9 +9,29 @@ #define SkOTUtils_DEFINED #include "SkOTTableTypes.h" +class SkStream; struct SkOTUtils { + /** + * Calculates the OpenType checksum for data. + */ static uint32_t CalcTableChecksum(SK_OT_ULONG *data, size_t length); + + /** + * Renames an sfnt font. On failure (invalid data or not an sfnt font) + * returns NULL. + * + * Essentially, this removes any existing 'name' table and replaces it + * with a new one in which FontFamilyName, FontSubfamilyName, + * UniqueFontIdentifier, FullFontName, and PostscriptName are fontName. + * + * The new 'name' table records will be written with the Windows, + * UnicodeBMPUCS2, and English_UnitedStates settings. + * + * fontName and fontNameLen must be specified in terms of ASCII chars. + */ + static SkData* RenameFont(SkStream* fontData, + const char* fontName, int fontNameLen); }; #endif diff --git a/src/sfnt/SkSFNTHeader.h b/src/sfnt/SkSFNTHeader.h index a36dfa4..bf01ac9 100644 --- a/src/sfnt/SkSFNTHeader.h +++ b/src/sfnt/SkSFNTHeader.h @@ -9,6 +9,7 @@ #define SkSFNTHeader_DEFINED #include "SkEndian.h" +#include "SkOTTableTypes.h" //All SK_SFNT_ prefixed types should be considered as big endian. typedef uint16_t SK_SFNT_USHORT; diff --git a/tests/FontHostStreamTest.cpp b/tests/FontHostStreamTest.cpp new file mode 100644 index 0000000..b1c49f8 --- /dev/null +++ b/tests/FontHostStreamTest.cpp @@ -0,0 +1,109 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "SkTypes.h" + +#include "Test.h" +#include "SkBitmap.h" +#include "SkCanvas.h" +#include "SkColor.h" +#include "SkFontHost.h" +#include "SkGraphics.h" +#include "SkPaint.h" +#include "SkPoint.h" +#include "SkRect.h" +#include "SkTypeface.h" + +/////////////////////////////////////////////////////////////////////////////// + +static const SkColor bgColor = SK_ColorWHITE; + +static void create(SkBitmap* bm, SkIRect bound, SkBitmap::Config config) { + bm->setConfig(config, bound.width(), bound.height()); + bm->allocPixels(); +} + +static void drawBG(SkCanvas* canvas) { + canvas->drawColor(bgColor); +} + +/** Assumes that the ref draw was completely inside ref canvas -- + implies that everything outside is "bgColor". + Checks that all overlap is the same and that all non-overlap on the + ref is "bgColor". + */ +static bool compare(const SkBitmap& ref, const SkIRect& iref, + const SkBitmap& test, const SkIRect& itest) +{ + const int xOff = itest.fLeft - iref.fLeft; + const int yOff = itest.fTop - iref.fTop; + + SkAutoLockPixels alpRef(ref); + SkAutoLockPixels alpTest(test); + + for (int y = 0; y < test.height(); ++y) { + for (int x = 0; x < test.width(); ++x) { + SkColor testColor = test.getColor(x, y); + int refX = x + xOff; + int refY = y + yOff; + SkColor refColor; + if (refX >= 0 && refX < ref.width() && + refY >= 0 && refY < ref.height()) + { + refColor = ref.getColor(refX, refY); + } else { + refColor = bgColor; + } + if (refColor != testColor) { + return false; + } + } + } + return true; +} + +static void test_fontHostStream(skiatest::Reporter* reporter) { + + { + SkPaint paint; + paint.setColor(SK_ColorGRAY); + paint.setTextSize(SkIntToScalar(30)); + + paint.setTypeface(SkTypeface::CreateFromName("Georgia", SkTypeface::kNormal))->unref(); + + SkIRect origRect = SkIRect::MakeWH(64, 64); + SkBitmap origBitmap; + create(&origBitmap, origRect, SkBitmap::kARGB_8888_Config); + SkCanvas origCanvas(origBitmap); + + SkIRect streamRect = SkIRect::MakeWH(64, 64); + SkBitmap streamBitmap; + create(&streamBitmap, streamRect, SkBitmap::kARGB_8888_Config); + SkCanvas streamCanvas(streamBitmap); + + SkPoint point = SkPoint::Make(24, 32); + + // Test: origTypeface and streamTypeface from orig data draw the same + drawBG(&origCanvas); + origCanvas.drawText("A", 1, point.fX, point.fY, paint); + + SkTypeface* origTypeface = paint.getTypeface(); + const SkFontID typefaceID = SkTypeface::UniqueID(origTypeface); + SkStream* fontData = SkFontHost::OpenStream(typefaceID); + SkTypeface* streamTypeface = SkTypeface::CreateFromStream(fontData); + paint.setTypeface(streamTypeface)->unref(); + drawBG(&streamCanvas); + streamCanvas.drawPosText("A", 1, &point, paint); + + REPORTER_ASSERT(reporter, + compare(origBitmap, origRect, streamBitmap, streamRect)); + } + //Make sure the typeface is deleted and removed. + SkGraphics::PurgeFontCache(); +} + +#include "TestClassDef.h" +DEFINE_TESTCLASS("FontHost::CreateTypefaceFromStream", FontHostStreamTestClass, test_fontHostStream) -- 2.7.4