'../tests/EmptyPathTest.cpp',
'../tests/FillPathTest.cpp',
'../tests/FlateTest.cpp',
+ '../tests/FontHostStreamTest.cpp',
'../tests/FontHostTest.cpp',
'../tests/GeometryTest.cpp',
'../tests/GLInterfaceValidation.cpp',
'pdf.gyp:pdf',
'utils.gyp:utils',
],
+ 'conditions': [
+ [ 'skia_os == "mac"', {
+ 'sources!': [
+ #mac port currently does not support fonts from streams.
+ '../tests/FontHostStreamTest.cpp',
+ ],
+ }],
+ ],
},
],
}
* 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 <tchar.h>
+#include <usp10.h>
+#include <objbase.h>
// always packed xxRRGGBB
typedef uint32_t SkGdiRGB;
}
};
+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<LogFontTypeface*>(face);
+ LogFontTypeface* lface = static_cast<LogFontTypeface*>(face);
const LOGFONT* lf = reinterpret_cast<const LOGFONT*>(ctx);
- return get_style(lface->fLogFont) == requestedStyle &&
+ return lface &&
+ get_style(lface->fLogFont) == requestedStyle &&
!memcmp(&lface->fLogFont, lf, sizeof(LOGFONT));
}
}
/**
+ * 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<const LogFontTypeface*>(face)->fLogFont;
}
}
}
static void ensure_typeface_accessible(SkFontID fontID) {
- LogFontTypeface* face = (LogFontTypeface*)SkTypefaceCache::FindByID(fontID);
+ LogFontTypeface* face = static_cast<LogFontTypeface*>(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<LogFontTypeface*>(SkTypefaceCache::FindByID(fontID));
if (face) {
*lf = face->fLogFont;
} else {
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<void*>(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) {
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);
}
#endif
}
-
-#endif // WIN32
* 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"
}
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_OT_BYTE*>(sk_malloc_throw(newDataSize));
+ SkAutoTUnref<SkData> rewrittenFontData = SkAutoTUnref<SkData>(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<SkSFNTTableDirectoryEntry*>(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<SkSFNTTableDirectoryEntry*>(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<SkOTTableName*>(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<SkOTTableNameRecord*>(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<SK_OT_USHORT*>(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<SK_OT_ULONG*>(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<SkOTTableHead*>(data + headTableOffset);
+ headTable->checksumAdjustment = SkEndian_SwapBE32(0);
+ uint32_t unadjustedFontChecksum = SkOTUtils::CalcTableChecksum(reinterpret_cast<SK_OT_ULONG*>(data), originalDataSize + nameTablePhysicalSize);
+ headTable->checksumAdjustment = SkEndian_SwapBE32(SkOTTableHead::fontChecksum - unadjustedFontChecksum);
+ }
+ }
+
+ return rewrittenFontData.detach();
+}
#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
#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;
--- /dev/null
+/*
+ * 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)