From d8c065c81fbaace966f487677ae4410fa4ec60e9 Mon Sep 17 00:00:00 2001 From: jules Date: Thu, 19 Dec 2013 15:39:30 +0000 Subject: [PATCH] Added functionality for loading in-memory fonts! See Typeface::createSystemTypefaceFor() --- modules/juce_graphics/fonts/juce_Typeface.h | 6 + .../native/juce_android_Fonts.cpp | 6 + .../native/juce_freetype_Fonts.cpp | 74 +++++--- .../juce_graphics/native/juce_linux_Fonts.cpp | 5 + .../juce_graphics/native/juce_mac_Fonts.mm | 88 ++++++++-- .../juce_graphics/native/juce_win32_Fonts.cpp | 162 ++++++++++++++++-- 6 files changed, 283 insertions(+), 58 deletions(-) diff --git a/modules/juce_graphics/fonts/juce_Typeface.h b/modules/juce_graphics/fonts/juce_Typeface.h index 4560bb3346..9c2ae31ef3 100644 --- a/modules/juce_graphics/fonts/juce_Typeface.h +++ b/modules/juce_graphics/fonts/juce_Typeface.h @@ -64,6 +64,12 @@ public: /** Creates a new system typeface. */ static Ptr createSystemTypefaceFor (const Font& font); + /** Attempts to create a font from some raw font file data (e.g. a TTF or OTF file image). + The system will take its own internal copy of the data, so you can free the block once + this method has returned. + */ + static Ptr createSystemTypefaceFor (const void* fontFileData, size_t fontFileDataSize); + //============================================================================== /** Destructor. */ virtual ~Typeface(); diff --git a/modules/juce_graphics/native/juce_android_Fonts.cpp b/modules/juce_graphics/native/juce_android_Fonts.cpp index ab782c4f36..837dd6b680 100644 --- a/modules/juce_graphics/native/juce_android_Fonts.cpp +++ b/modules/juce_graphics/native/juce_android_Fonts.cpp @@ -315,6 +315,12 @@ Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font) return new AndroidTypeface (font); } +Typeface::Ptr Typeface::createSystemTypefaceFor (const void*, size_t) +{ + jassertfalse; // not yet implemented! + return nullptr; +} + void Typeface::scanFolderForFonts (const File&) { jassertfalse; // not available unless using FreeType diff --git a/modules/juce_graphics/native/juce_freetype_Fonts.cpp b/modules/juce_graphics/native/juce_freetype_Fonts.cpp index 2ce9593ae3..43a6436e91 100644 --- a/modules/juce_graphics/native/juce_freetype_Fonts.cpp +++ b/modules/juce_graphics/native/juce_freetype_Fonts.cpp @@ -56,6 +56,14 @@ struct FTFaceWrapper : public ReferenceCountedObject face = 0; } + FTFaceWrapper (const FTLibWrapper::Ptr& ftLib, const void* data, size_t dataSize, int faceIndex) + : face (0), library (ftLib), savedFaceData (data, dataSize) + { + if (FT_New_Memory_Face (ftLib->library, (const FT_Byte*) savedFaceData.getData(), + (FT_Long) savedFaceData.getSize(), faceIndex, &face) != 0) + face = 0; + } + ~FTFaceWrapper() { if (face != 0) @@ -64,8 +72,9 @@ struct FTFaceWrapper : public ReferenceCountedObject FT_Face face; FTLibWrapper::Ptr library; + MemoryBlock savedFaceData; - typedef ReferenceCountedObjectPtr Ptr; + typedef ReferenceCountedObjectPtr Ptr; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FTFaceWrapper) }; @@ -106,6 +115,25 @@ public: }; //============================================================================== + static FTFaceWrapper::Ptr selectUnicodeCharmap (FTFaceWrapper* face) + { + if (face != nullptr) + if (FT_Select_Charmap (face->face, ft_encoding_unicode) != 0) + FT_Set_Charmap (face->face, face->face->charmaps[0]); + + return face; + } + + FTFaceWrapper::Ptr createFace (const void* data, size_t dataSize, int index) + { + return selectUnicodeCharmap (new FTFaceWrapper (library, data, dataSize, index)); + } + + FTFaceWrapper::Ptr createFace (const File& file, int index) + { + return selectUnicodeCharmap (new FTFaceWrapper (library, file, index)); + } + FTFaceWrapper::Ptr createFace (const String& fontName, const String& fontStyle) { const KnownTypeface* ftFace = matchTypeface (fontName, fontStyle); @@ -114,16 +142,7 @@ public: if (ftFace == nullptr) ftFace = matchTypeface (fontName, String()); if (ftFace != nullptr) - { - if (FTFaceWrapper::Ptr face = new FTFaceWrapper (library, ftFace->file, ftFace->faceIndex)) - { - // If there isn't a unicode charmap then select the first one. - if (FT_Select_Charmap (face->face, ft_encoding_unicode) != 0) - FT_Set_Charmap (face->face, face->face->charmaps[0]); - - return face; - } - } + return createFace (ftFace->file, ftFace->faceIndex); return nullptr; } @@ -272,20 +291,27 @@ class FreeTypeTypeface : public CustomTypeface { public: FreeTypeTypeface (const Font& font) - : faceWrapper (FTTypefaceList::getInstance() - ->createFace (font.getTypefaceName(), font.getTypefaceStyle())) + : faceWrapper (FTTypefaceList::getInstance()->createFace (font.getTypefaceName(), + font.getTypefaceStyle())) { if (faceWrapper != nullptr) - { - setCharacteristics (font.getTypefaceName(), - font.getTypefaceStyle(), - faceWrapper->face->ascender / (float) (faceWrapper->face->ascender - faceWrapper->face->descender), - L' '); - } - else - { - DBG ("Failed to create typeface: " << font.toString()); - } + initialiseCharacteristics (font.getTypefaceName(), + font.getTypefaceStyle()); + } + + FreeTypeTypeface (const void* data, size_t dataSize) + : faceWrapper (FTTypefaceList::getInstance()->createFace (data, dataSize, 0)) + { + if (faceWrapper != nullptr) + initialiseCharacteristics (faceWrapper->face->family_name, + faceWrapper->face->style_name); + } + + void initialiseCharacteristics (const String& name, const String& style) + { + setCharacteristics (name, style, + faceWrapper->face->ascender / (float) (faceWrapper->face->ascender - faceWrapper->face->descender), + L' '); } bool loadGlyphIfPossible (const juce_wchar character) @@ -295,7 +321,7 @@ public: FT_Face face = faceWrapper->face; const unsigned int glyphIndex = FT_Get_Char_Index (face, character); - if (FT_Load_Glyph (face, glyphIndex, FT_LOAD_NO_SCALE | FT_LOAD_NO_BITMAP | FT_LOAD_IGNORE_TRANSFORM) == 0 + if (FT_Load_Glyph (face, glyphIndex, FT_LOAD_NO_SCALE | FT_LOAD_NO_BITMAP | FT_LOAD_IGNORE_TRANSFORM | FT_LOAD_NO_HINTING) == 0 && face->glyph->format == ft_glyph_format_outline) { const float scale = 1.0f / (float) (face->ascender - face->descender); diff --git a/modules/juce_graphics/native/juce_linux_Fonts.cpp b/modules/juce_graphics/native/juce_linux_Fonts.cpp index 4a21c09038..0869ec719c 100644 --- a/modules/juce_graphics/native/juce_linux_Fonts.cpp +++ b/modules/juce_graphics/native/juce_linux_Fonts.cpp @@ -69,6 +69,11 @@ Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font) return new FreeTypeTypeface (font); } +Typeface::Ptr Typeface::createSystemTypefaceFor (const void* data, size_t dataSize) +{ + return new FreeTypeTypeface (data, dataSize); +} + void Typeface::scanFolderForFonts (const File& folder) { FTTypefaceList::getInstance()->scanFontPaths (StringArray (folder.getFullPathName())); diff --git a/modules/juce_graphics/native/juce_mac_Fonts.mm b/modules/juce_graphics/native/juce_mac_Fonts.mm index 18eca7719d..2dfa209a88 100644 --- a/modules/juce_graphics/native/juce_mac_Fonts.mm +++ b/modules/juce_graphics/native/juce_mac_Fonts.mm @@ -496,28 +496,73 @@ public: if (ctFontRef != nullptr) { - const float ctAscent = std::abs ((float) CTFontGetAscent (ctFontRef)); - const float ctDescent = std::abs ((float) CTFontGetDescent (ctFontRef)); - const float ctTotalHeight = ctAscent + ctDescent; - - ascent = ctAscent / ctTotalHeight; - unitsToHeightScaleFactor = 1.0f / ctTotalHeight; - pathTransform = AffineTransform::identity.scale (unitsToHeightScaleFactor); - fontRef = CTFontCopyGraphicsFont (ctFontRef, nullptr); - fontHeightToPointsFactor = referenceFontSize / ctTotalHeight; - - const short zero = 0; - CFNumberRef numberRef = CFNumberCreate (0, kCFNumberShortType, &zero); - - CFStringRef keys[] = { kCTFontAttributeName, kCTLigatureAttributeName }; - CFTypeRef values[] = { ctFontRef, numberRef }; - attributedStringAtts = CFDictionaryCreate (nullptr, (const void**) &keys, (const void**) &values, numElementsInArray (keys), - &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); - CFRelease (numberRef); + initialiseMetrics(); } } + OSXTypeface (const void* data, size_t dataSize) + : Typeface (String(), String()), + fontRef (nullptr), + ctFontRef (nullptr), + fontHeightToPointsFactor (1.0f), + renderingTransform (CGAffineTransformIdentity), + attributedStringAtts (nullptr), + ascent (0.0f), + unitsToHeightScaleFactor (0.0f) + { + CFDataRef cfData = CFDataCreate (kCFAllocatorDefault, (const UInt8*) data, (CFIndex) dataSize); + CGDataProviderRef provider = CGDataProviderCreateWithCFData (cfData); + CFRelease (cfData); + + fontRef = CGFontCreateWithDataProvider (provider); + CGDataProviderRelease (provider); + + if (fontRef != nullptr) + { + ctFontRef = CTFontCreateWithGraphicsFont (fontRef, referenceFontSize, nullptr, nullptr); + + if (ctFontRef != nullptr) + { + if (CFStringRef fontName = CTFontCopyName (ctFontRef, kCTFontFamilyNameKey)) + { + name = String::fromCFString (fontName); + CFRelease (fontName); + } + + if (CFStringRef fontStyle = CTFontCopyName (ctFontRef, kCTFontStyleNameKey)) + { + style = String::fromCFString (fontStyle); + CFRelease (fontStyle); + } + + initialiseMetrics(); + } + } + } + + void initialiseMetrics() + { + const float ctAscent = std::abs ((float) CTFontGetAscent (ctFontRef)); + const float ctDescent = std::abs ((float) CTFontGetDescent (ctFontRef)); + const float ctTotalHeight = ctAscent + ctDescent; + + ascent = ctAscent / ctTotalHeight; + unitsToHeightScaleFactor = 1.0f / ctTotalHeight; + pathTransform = AffineTransform::identity.scale (unitsToHeightScaleFactor); + + fontHeightToPointsFactor = referenceFontSize / ctTotalHeight; + + const short zero = 0; + CFNumberRef numberRef = CFNumberCreate (0, kCFNumberShortType, &zero); + + CFStringRef keys[] = { kCTFontAttributeName, kCTLigatureAttributeName }; + CFTypeRef values[] = { ctFontRef, numberRef }; + attributedStringAtts = CFDictionaryCreate (nullptr, (const void**) &keys, (const void**) &values, numElementsInArray (keys), + &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + CFRelease (numberRef); + } + ~OSXTypeface() { if (attributedStringAtts != nullptr) @@ -730,7 +775,7 @@ StringArray Font::findAllTypefaceStyles (const String& family) CTFontDescriptorRef ctFontDescRef = CTFontDescriptorCreateWithAttributes (fontDescAttributes); CFRelease (fontDescAttributes); - CFArrayRef fontFamilyArray = CFArrayCreate(kCFAllocatorDefault, (const void**) &ctFontDescRef, 1, &kCFTypeArrayCallBacks); + CFArrayRef fontFamilyArray = CFArrayCreate (kCFAllocatorDefault, (const void**) &ctFontDescRef, 1, &kCFTypeArrayCallBacks); CFRelease (ctFontDescRef); CTFontCollectionRef fontCollectionRef = CTFontCollectionCreateWithFontDescriptors (fontFamilyArray, nullptr); @@ -1202,6 +1247,11 @@ Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font) return new OSXTypeface (font); } +Typeface::Ptr Typeface::createSystemTypefaceFor (const void* data, size_t dataSize) +{ + return new OSXTypeface (data, dataSize); +} + void Typeface::scanFolderForFonts (const File&) { jassertfalse; // not implemented on this platform diff --git a/modules/juce_graphics/native/juce_win32_Fonts.cpp b/modules/juce_graphics/native/juce_win32_Fonts.cpp index b8455eb4fd..eaa7678440 100644 --- a/modules/juce_graphics/native/juce_win32_Fonts.cpp +++ b/modules/juce_graphics/native/juce_win32_Fonts.cpp @@ -22,6 +22,115 @@ ============================================================================== */ +/* This is some quick-and-dirty code to extract the typeface name from a lump of TTF file data. + It's needed because although win32 will happily load a TTF file from in-memory data, it won't + tell you the name of the damned font that it just loaded.. and in order to actually use the font, + you need to know its name!! Anyway, this awful hack seems to work for most fonts. +*/ +namespace TTFNameExtractor +{ + struct OffsetTable + { + uint32 version; + uint16 numTables, searchRange, entrySelector, rangeShift; + }; + + struct TableDirectory + { + char tag[4]; + uint32 checkSum, offset, length; + }; + + struct NamingTable + { + uint16 formatSelector; + uint16 numberOfNameRecords; + uint16 offsetStartOfStringStorage; + }; + + struct NameRecord + { + uint16 platformID, encodingID, languageID; + uint16 nameID, stringLength, offsetFromStorageArea; + }; + + static String parseNameRecord (MemoryInputStream& input, const NameRecord& nameRecord, + const int64 directoryOffset, const int64 offsetOfStringStorage) + { + String result; + const int64 oldPos = input.getPosition(); + input.setPosition (directoryOffset + offsetOfStringStorage + ByteOrder::swapIfLittleEndian (nameRecord.offsetFromStorageArea)); + const int stringLength = (int) ByteOrder::swapIfLittleEndian (nameRecord.stringLength); + const int platformID = ByteOrder::swapIfLittleEndian (nameRecord.platformID); + + if (platformID == 0 || platformID == 3) + { + const int numChars = stringLength / 2 + 1; + HeapBlock buffer; + buffer.calloc (numChars + 1); + input.read (buffer, stringLength); + + for (int i = 0; i < numChars; ++i) + buffer[i] = ByteOrder::swapIfLittleEndian (buffer[i]); + + static_jassert (sizeof (CharPointer_UTF16::CharType) == sizeof (uint16)); + result = CharPointer_UTF16 ((CharPointer_UTF16::CharType*) buffer.getData()); + } + else + { + HeapBlock buffer; + buffer.calloc (stringLength + 1); + input.read (buffer, stringLength); + result = CharPointer_UTF8 (buffer.getData()); + } + + input.setPosition (oldPos); + return result; + } + + static String parseNameTable (MemoryInputStream& input, int64 directoryOffset) + { + input.setPosition (directoryOffset); + + NamingTable namingTable = { 0 }; + input.read (&namingTable, sizeof (namingTable)); + + for (int i = 0; i < (int) ByteOrder::swapIfLittleEndian (namingTable.numberOfNameRecords); ++i) + { + NameRecord nameRecord = { 0 }; + input.read (&nameRecord, sizeof (nameRecord)); + + if (ByteOrder::swapIfLittleEndian (nameRecord.nameID) == 4) + { + const String result (parseNameRecord (input, nameRecord, directoryOffset, + ByteOrder::swapIfLittleEndian (namingTable.offsetStartOfStringStorage))); + + if (result.isNotEmpty()) + return result; + } + } + + return String(); + } + + static String getTypefaceNameFromFile (MemoryInputStream& input) + { + OffsetTable offsetTable = { 0 }; + input.read (&offsetTable, sizeof (offsetTable)); + + for (int i = 0; i < (int) ByteOrder::swapIfLittleEndian (offsetTable.numTables); ++i) + { + TableDirectory tableDirectory = { 0 }; + input.read (&tableDirectory, sizeof (tableDirectory)); + + if (String (tableDirectory.tag, sizeof (tableDirectory.tag)).equalsIgnoreCase ("name")) + return parseNameTable (input, ByteOrder::swapIfLittleEndian (tableDirectory.offset)); + } + + return String(); + } +} + namespace FontEnumerators { static int CALLBACK fontEnum2 (ENUMLOGFONTEXW* lpelfe, NEWTEXTMETRICEXW*, int type, LPARAM lParam) @@ -204,23 +313,29 @@ class WindowsTypeface : public Typeface { public: WindowsTypeface (const Font& font) - : Typeface (font.getTypefaceName(), - font.getTypefaceStyle()), - fontH (0), - previousFontH (0), - dc (CreateCompatibleDC (0)), + : Typeface (font.getTypefaceName(), font.getTypefaceStyle()), + fontH (0), previousFontH (0), + dc (CreateCompatibleDC (0)), memoryFont (0), ascent (1.0f), heightToPointsFactor (1.0f), defaultGlyph (-1) { - loadFont(); + loadFont (name); + } - if (GetTextMetrics (dc, &tm)) - { - heightToPointsFactor = (72.0f / GetDeviceCaps (dc, LOGPIXELSY)) * heightInPoints / (float) tm.tmHeight; - ascent = tm.tmAscent / (float) tm.tmHeight; - defaultGlyph = getGlyphForChar (dc, tm.tmDefaultChar); - createKerningPairs (dc, (float) tm.tmHeight); - } + WindowsTypeface (const void* data, size_t dataSize) + : Typeface (String(), String()), + fontH (0), previousFontH (0), + dc (CreateCompatibleDC (0)), memoryFont (0), + ascent (1.0f), heightToPointsFactor (1.0f), + defaultGlyph (-1) + { + DWORD numInstalled = 0; + + memoryFont = AddFontMemResourceEx (const_cast (data), (DWORD) dataSize, + nullptr, &numInstalled); + + MemoryInputStream m (data, dataSize, false); + loadFont (TTFNameExtractor::getTypefaceNameFromFile (m)); } ~WindowsTypeface() @@ -230,6 +345,9 @@ public: if (fontH != 0) DeleteObject (fontH); + + if (memoryFont != 0) + RemoveFontMemResourceEx (memoryFont); } float getAscent() const { return ascent; } @@ -353,6 +471,7 @@ private: HGDIOBJ previousFontH; HDC dc; TEXTMETRIC tm; + HANDLE memoryFont; float ascent, heightToPointsFactor; int defaultGlyph, heightInPoints; @@ -375,7 +494,7 @@ private: SortedSet kerningPairs; - void loadFont() + void loadFont (const String& faceName) { SetMapperFlags (dc, 0); SetMapMode (dc, MM_TEXT); @@ -389,7 +508,7 @@ private: lf.lfItalic = (BYTE) (style == "Italic" ? TRUE : FALSE); lf.lfWeight = style == "Bold" ? FW_BOLD : FW_NORMAL; lf.lfHeight = -256; - name.copyToUTF16 (lf.lfFaceName, sizeof (lf.lfFaceName)); + faceName.copyToUTF16 (lf.lfFaceName, sizeof (lf.lfFaceName)); HFONT standardSizedFont = CreateFontIndirect (&lf); @@ -411,6 +530,14 @@ private: } } } + + if (GetTextMetrics (dc, &tm)) + { + heightToPointsFactor = (72.0f / GetDeviceCaps (dc, LOGPIXELSY)) * heightInPoints / (float) tm.tmHeight; + ascent = tm.tmAscent / (float) tm.tmHeight; + defaultGlyph = getGlyphForChar (dc, tm.tmDefaultChar); + createKerningPairs (dc, (float) tm.tmHeight); + } } void createKerningPairs (HDC dc, const float height) @@ -499,6 +626,11 @@ Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font) return new WindowsTypeface (font); } +Typeface::Ptr Typeface::createSystemTypefaceFor (const void* data, size_t dataSize) +{ + return new WindowsTypeface (data, dataSize); +} + void Typeface::scanFolderForFonts (const File&) { jassertfalse; // not implemented on this platform