From 9226bfba49a53e7e856d885bdb969c07d8388aba Mon Sep 17 00:00:00 2001 From: Julian Storer Date: Wed, 8 Jun 2011 09:57:43 +0100 Subject: [PATCH] Fixed win32 horizontal scroll wheel direction. Big internal refactoring of win32 typeface implementation. Added some functionality to SortedSet. Fixed a mac main menu bug. --- juce_amalgamated.cpp | 296 +++++++---- juce_amalgamated.h | 60 ++- src/containers/juce_SortedSet.h | 53 +- src/core/juce_StandardHeader.h | 2 +- .../graphics/fonts/juce_CustomTypeface.cpp | 46 +- src/gui/graphics/fonts/juce_CustomTypeface.h | 2 - src/gui/graphics/fonts/juce_Typeface.cpp | 19 +- src/gui/graphics/fonts/juce_Typeface.h | 3 +- src/native/mac/juce_mac_MainMenu.mm | 5 +- src/native/windows/juce_win32_Fonts.cpp | 467 +++++++++--------- src/native/windows/juce_win32_Windowing.cpp | 2 +- 11 files changed, 552 insertions(+), 403 deletions(-) diff --git a/juce_amalgamated.cpp b/juce_amalgamated.cpp index 04b7a8aed5..db0ea0793f 100644 --- a/juce_amalgamated.cpp +++ b/juce_amalgamated.cpp @@ -93112,7 +93112,7 @@ END_JUCE_NAMESPACE BEGIN_JUCE_NAMESPACE Typeface::Typeface (const String& name_) noexcept - : name (name_), isFallbackFont (false) + : name (name_) { } @@ -93123,9 +93123,18 @@ Typeface::~Typeface() const Typeface::Ptr Typeface::getFallbackTypeface() { const Font fallbackFont (Font::getFallbackFontName(), 10, 0); - Typeface* t = fallbackFont.getTypeface(); - t->isFallbackFont = true; - return t; + return fallbackFont.getTypeface(); +} + +EdgeTable* Typeface::getEdgeTableForGlyph (int glyphNumber, const AffineTransform& transform) +{ + Path path; + + if (getOutlineForGlyph (glyphNumber, path) && ! path.isEmpty()) + return new EdgeTable (path.getBoundsTransformed (transform).getSmallestIntegerContainer().expanded (1, 0), + path, transform); + + return nullptr; } END_JUCE_NAMESPACE @@ -93319,34 +93328,6 @@ CustomTypeface::GlyphInfo* CustomTypeface::findGlyph (const juce_wchar character return nullptr; } -CustomTypeface::GlyphInfo* CustomTypeface::findGlyphSubstituting (const juce_wchar character) noexcept -{ - GlyphInfo* glyph = findGlyph (character, true); - - if (glyph == nullptr) - { - if (CharacterFunctions::isWhitespace (character) && character != L' ') - glyph = findGlyph (L' ', true); - - if (glyph == nullptr) - { - const Font fallbackFont (Font::getFallbackFontName(), 10, 0); - Typeface* const fallbackTypeface = fallbackFont.getTypeface(); - if (fallbackTypeface != nullptr && fallbackTypeface != this) - { - Path path; - fallbackTypeface->getOutlineForGlyph (character, path); - addGlyph (character, path, fallbackTypeface->getStringWidth (String::charToString (character))); - } - - if (glyph == nullptr) - glyph = findGlyph (defaultCharacter, true); - } - } - - return glyph; -} - bool CustomTypeface::loadGlyphIfPossible (const juce_wchar /*characterNeeded*/) { return false; @@ -93448,13 +93429,13 @@ float CustomTypeface::getStringWidth (const String& text) while (! t.isEmpty()) { const juce_wchar c = t.getAndAdvance(); - const GlyphInfo* const glyph = findGlyphSubstituting (c); + const GlyphInfo* const glyph = findGlyph (c, true); - if (glyph == nullptr && ! isFallbackFont) + if (glyph == nullptr) { const Typeface::Ptr fallbackTypeface (Typeface::getFallbackTypeface()); - if (fallbackTypeface != nullptr) + if (fallbackTypeface != nullptr && fallbackTypeface != this) x += fallbackTypeface->getStringWidth (String::charToString (c)); } @@ -93476,11 +93457,11 @@ void CustomTypeface::getGlyphPositions (const String& text, Array & resultG const juce_wchar c = t.getAndAdvance(); const GlyphInfo* const glyph = findGlyph (c, true); - if (glyph == nullptr && ! isFallbackFont) + if (glyph == nullptr) { const Typeface::Ptr fallbackTypeface (Typeface::getFallbackTypeface()); - if (fallbackTypeface != nullptr) + if (fallbackTypeface != nullptr && fallbackTypeface != this) { Array subGlyphs; Array subOffsets; @@ -93508,11 +93489,11 @@ bool CustomTypeface::getOutlineForGlyph (int glyphNumber, Path& path) { const GlyphInfo* const glyph = findGlyph ((juce_wchar) glyphNumber, true); - if (glyph == nullptr && ! isFallbackFont) + if (glyph == nullptr) { const Typeface::Ptr fallbackTypeface (Typeface::getFallbackTypeface()); - if (fallbackTypeface != nullptr) + if (fallbackTypeface != nullptr && fallbackTypeface != this) fallbackTypeface->getOutlineForGlyph (glyphNumber, path); } @@ -93529,11 +93510,11 @@ EdgeTable* CustomTypeface::getEdgeTableForGlyph (int glyphNumber, const AffineTr { const GlyphInfo* const glyph = findGlyph ((juce_wchar) glyphNumber, true); - if (glyph == nullptr && ! isFallbackFont) + if (glyph == nullptr) { const Typeface::Ptr fallbackTypeface (Typeface::getFallbackTypeface()); - if (fallbackTypeface != nullptr) + if (fallbackTypeface != nullptr && fallbackTypeface != this) return fallbackTypeface->getEdgeTableForGlyph (glyphNumber, transform); } @@ -246567,19 +246548,6 @@ public: return dc; } - KERNINGPAIR* getKerningPairs (int& numKPs_) - { - if (kps == nullptr) - { - numKPs = GetKerningPairs (dc, 0, 0); - kps.calloc (numKPs); - GetKerningPairs (dc, numKPs, kps); - } - - numKPs_ = numKPs; - return kps; - } - private: HFONT fontH; @@ -246613,70 +246581,99 @@ private: juce_ImplementSingleton_SingleThreaded (FontDCHolder); -class WindowsTypeface : public CustomTypeface +class WindowsTypeface : public Typeface { public: WindowsTypeface (const Font& font) + : Typeface (font.getTypefaceName()), + ascent (1.0f), + defaultGlyph (-1), + bold (font.isBold()), + italic (font.isItalic()) { - HDC dc = FontDCHolder::getInstance()->loadFont (font.getTypefaceName(), - font.isBold(), font.isItalic(), 0); + HDC dc = getDC(); TEXTMETRIC tm; - tm.tmAscent = tm.tmHeight = 1; - tm.tmDefaultChar = 0; - GetTextMetrics (dc, &tm); - - setCharacteristics (font.getTypefaceName(), - tm.tmAscent / (float) tm.tmHeight, - font.isBold(), font.isItalic(), - tm.tmDefaultChar); + if (GetTextMetrics (dc, &tm)) + { + ascent = tm.tmAscent / (float) tm.tmHeight; + defaultGlyph = getGlyphForChar (dc, tm.tmDefaultChar); + createKerningPairs (dc, (float) tm.tmHeight); + } } - bool loadGlyphIfPossible (juce_wchar character) + float getAscent() const { return ascent; } + float getDescent() const { return 1.0f - ascent; } + + float getStringWidth (const String& text) { - HDC dc = FontDCHolder::getInstance()->loadFont (name, isBold, isItalic, 0); + HDC dc = getDC(); + const CharPointer_UTF16 utf16 (text.toUTF16()); + const int numChars = utf16.length(); + HeapBlock results (numChars + 1); + results[numChars] = -1; + float x = 0; - GLYPHMETRICS gm; - - // if this is the fallback font, skip checking for the glyph's existence. This is because - // with fonts like Tahoma, GetGlyphIndices can say that a glyph doesn't exist, but it still - // gets correctly created later on. - if (! isFallbackFont) + if (GetGlyphIndices (dc, utf16, numChars, reinterpret_cast (results.getData()), + GGI_MARK_NONEXISTING_GLYPHS) != GDI_ERROR) { - const WCHAR charToTest[] = { (WCHAR) character, 0 }; - WORD index = 0; + for (int i = 0; i < numChars; ++i) + x += getKerning (dc, results[i], results[i + 1]); + } - if (GetGlyphIndices (dc, charToTest, 1, &index, GGI_MARK_NONEXISTING_GLYPHS) != GDI_ERROR - && index == 0xffff) + return x; + } + + void getGlyphPositions (const String& text, Array & resultGlyphs, Array & xOffsets) + { + HDC dc = getDC(); + const CharPointer_UTF16 utf16 (text.toUTF16()); + const int numChars = utf16.length(); + HeapBlock results (numChars + 1); + results[numChars] = -1; + float x = 0; + + if (GetGlyphIndices (dc, utf16, numChars, reinterpret_cast (results.getData()), + GGI_MARK_NONEXISTING_GLYPHS) != GDI_ERROR) + { + resultGlyphs.ensureStorageAllocated (numChars); + xOffsets.ensureStorageAllocated (numChars + 1); + + for (int i = 0; i < numChars; ++i) { - return false; + resultGlyphs.add (results[i]); + xOffsets.add (x); + x += getKerning (dc, results[i], results[i + 1]); } } - Path glyphPath; + xOffsets.add (x); + } + + bool getOutlineForGlyph (int glyphNumber, Path& glyphPath) + { + HDC dc = getDC(); TEXTMETRIC tm; if (! GetTextMetrics (dc, &tm)) - { - addGlyph (character, glyphPath, 0); - return true; - } + return false; - const float height = (float) tm.tmHeight; - static const MAT2 identityMatrix = { { 0, 1 }, { 0, 0 }, { 0, 0 }, { 0, 1 } }; + if (glyphNumber < 0) + glyphNumber = defaultGlyph; - const int bufSize = GetGlyphOutline (dc, character, GGO_NATIVE, + GLYPHMETRICS gm; + const int bufSize = GetGlyphOutline (dc, glyphNumber, GGO_NATIVE | GGO_GLYPH_INDEX, &gm, 0, 0, &identityMatrix); if (bufSize > 0) { HeapBlock data (bufSize); - - GetGlyphOutline (dc, character, GGO_NATIVE, &gm, + GetGlyphOutline (dc, glyphNumber, GGO_NATIVE | GGO_GLYPH_INDEX, &gm, bufSize, data, &identityMatrix); const TTPOLYGONHEADER* pheader = reinterpret_cast (data.getData()); + const float height = (float) tm.tmHeight; const float scaleX = 1.0f / height; const float scaleY = -1.0f / height; @@ -246737,25 +246734,117 @@ public: } } - addGlyph (character, glyphPath, gm.gmCellIncX / height); - - int numKPs; - const KERNINGPAIR* const kps = FontDCHolder::getInstance()->getKerningPairs (numKPs); - - for (int i = 0; i < numKPs; ++i) - { - if (kps[i].wFirst == character) - addKerningPair (kps[i].wFirst, kps[i].wSecond, - kps[i].iKernAmount / height); - } - return true; } private: + static const MAT2 identityMatrix; + float ascent; + int defaultGlyph; + bool bold, italic; + + struct KerningPair + { + int glyph1, glyph2; + float kerning; + + bool operator== (const KerningPair& other) const noexcept + { + return glyph1 == other.glyph1 && glyph2 == other.glyph2; + } + + bool operator< (const KerningPair& other) const noexcept + { + return glyph1 < other.glyph1 + || (glyph1 == other.glyph1 && glyph2 < other.glyph2); + } + }; + + SortedSet kerningPairs; + + void createKerningPairs (HDC dc, const float height) + { + HeapBlock rawKerning; + const int numKPs = GetKerningPairs (dc, 0, 0); + rawKerning.calloc (numKPs); + GetKerningPairs (dc, numKPs, rawKerning); + + kerningPairs.ensureStorageAllocated (numKPs); + + for (int i = 0; i < numKPs; ++i) + { + KerningPair kp; + kp.glyph1 = getGlyphForChar (dc, rawKerning[i].wFirst); + kp.glyph2 = getGlyphForChar (dc, rawKerning[i].wSecond); + + const int standardWidth = getGlyphWidth (dc, kp.glyph1); + kp.kerning = (standardWidth + rawKerning[i].iKernAmount) / height; + kerningPairs.add (kp); + + kp.glyph2 = -1; // add another entry for the standard width version.. + kp.kerning = standardWidth / height; + kerningPairs.add (kp); + } + } + + static int getGlyphForChar (HDC dc, juce_wchar character) + { + const WCHAR charToTest[] = { (WCHAR) character, 0 }; + WORD index = 0; + + if (GetGlyphIndices (dc, charToTest, 1, &index, GGI_MARK_NONEXISTING_GLYPHS) == GDI_ERROR + || index == 0xffff) + return -1; + + return index; + } + + static int getGlyphWidth (HDC dc, int glyphNumber) + { + GLYPHMETRICS gm; + gm.gmCellIncX = 0; + GetGlyphOutline (dc, glyphNumber, GGO_NATIVE | GGO_GLYPH_INDEX, &gm, 0, 0, &identityMatrix); + return gm.gmCellIncX; + } + + float getKerning (HDC dc, const int glyph1, const int glyph2) + { + KerningPair kp; + kp.glyph1 = glyph1; + kp.glyph2 = glyph2; + int index = kerningPairs.indexOf (kp); + + if (index < 0) + { + kp.glyph2 = -1; + index = kerningPairs.indexOf (kp); + + if (index < 0) + { + TEXTMETRIC tm; + if (! GetTextMetrics (dc, &tm)) + return 0; + + kp.glyph2 = -1; + kp.kerning = getGlyphWidth (dc, kp.glyph1) / (float) tm.tmHeight; + kerningPairs.add (kp); + return kp.kerning; + } + } + + return kerningPairs.getReference (index).kerning; + } + + HDC getDC() const + { + return FontDCHolder::getInstance()->loadFont (name, bold, italic, 0); + } + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WindowsTypeface); }; +const MAT2 WindowsTypeface::identityMatrix = { { 0, 1 }, { 0, 0 }, { 0, 0 }, { 0, 1 } }; + const Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font) { return new WindowsTypeface (font); @@ -249131,7 +249220,7 @@ private: peer = this; peer->handleMouseWheel (0, peer->globalToLocal (globalPos), getMouseEventTime(), - isVertical ? 0.0f : amount, + isVertical ? 0.0f : -amount, isVertical ? amount : 0.0f); } @@ -281677,7 +281766,10 @@ public: [menu setAutoenablesItems: false]; [menu update]; [parentItem setTag: tag]; - [parentItem setSubmenu: menu]; + + if (! [[parentItem submenu] equals: menu]) // NB this comparison is needed to avoid a strange + [parentItem setSubmenu: menu]; // crash deep inside Apple code when no windows are focused.. + [menu release]; } diff --git a/juce_amalgamated.h b/juce_amalgamated.h index 3a30cad269..8bcfd17a42 100644 --- a/juce_amalgamated.h +++ b/juce_amalgamated.h @@ -73,7 +73,7 @@ namespace JuceDummyNamespace {} */ #define JUCE_MAJOR_VERSION 1 #define JUCE_MINOR_VERSION 53 -#define JUCE_BUILDNUMBER 96 +#define JUCE_BUILDNUMBER 97 /** Current Juce version number. @@ -14735,7 +14735,7 @@ public: return false; for (int i = numUsed; --i >= 0;) - if (data.elements[i] != other.data.elements[i]) + if (! (data.elements[i] == other.data.elements[i])) return false; return true; @@ -14818,6 +14818,21 @@ public: return data.elements [index]; } + /** Returns a direct reference to one of the elements in the set, without checking the index passed in. + + This is like getUnchecked, but returns a direct reference to the element, so that + you can alter it directly. Obviously this can be dangerous, so only use it when + absolutely necessary. + + @param index the index of the element being requested (0 is the first element in the array) + */ + inline ElementType& getReference (const int index) const noexcept + { + const ScopedLockType lock (getLock()); + jassert (isPositiveAndBelow (index, numUsed)); + return data.elements [index]; + } + /** Returns the first element in the set, or 0 if the set is empty. @see operator[], getUnchecked, getLast @@ -14885,10 +14900,10 @@ public: if (halfway == start) return -1; - else if (elementToLookFor >= data.elements [halfway]) - start = halfway; - else + else if (elementToLookFor < data.elements [halfway]) end = halfway; + else + start = halfway; } } } @@ -14921,10 +14936,10 @@ public: if (halfway == start) return false; - else if (elementToLookFor >= data.elements [halfway]) - start = halfway; - else + else if (elementToLookFor < data.elements [halfway]) end = halfway; + else + start = halfway; } } } @@ -14959,17 +14974,17 @@ public: if (halfway == start) { - if (newElement >= data.elements [halfway]) - insertInternal (start + 1, newElement); - else + if (newElement < data.elements [halfway]) insertInternal (start, newElement); + else + insertInternal (start + 1, newElement); break; } - else if (newElement >= data.elements [halfway]) - start = halfway; - else + else if (newElement < data.elements [halfway]) end = halfway; + else + start = halfway; } } } @@ -15137,6 +15152,18 @@ public: data.shrinkToNoMoreThan (numUsed); } + /** Increases the set's internal storage to hold a minimum number of elements. + + Calling this before adding a large known number of elements means that + the set won't have to keep dynamically resizing itself as the elements + are added, and it'll therefore be more efficient. + */ + void ensureStorageAllocated (const int minNumElements) + { + const ScopedLockType lock (getLock()); + data.ensureAllocatedSize (minNumElements); + } + /** Returns the CriticalSection that locks this array. To lock, you can call getLock().enter() and getLock().exit(), or preferably use an object of ScopedLockType as an RAII lock for it. @@ -25132,7 +25159,7 @@ public: virtual bool getOutlineForGlyph (int glyphNumber, Path& path) = 0; /** Returns a new EdgeTable that contains the path for the givem glyph, with the specified transform applied. */ - virtual EdgeTable* getEdgeTableForGlyph (int glyphNumber, const AffineTransform& transform) = 0; + virtual EdgeTable* getEdgeTableForGlyph (int glyphNumber, const AffineTransform& transform); /** Returns true if the typeface uses hinting. */ virtual bool isHinted() const { return false; } @@ -25143,7 +25170,6 @@ public: protected: String name; - bool isFallbackFont; explicit Typeface (const String& name) noexcept; @@ -67847,7 +67873,6 @@ public: void getGlyphPositions (const String& text, Array & glyphs, Array& xOffsets); bool getOutlineForGlyph (int glyphNumber, Path& path); EdgeTable* getEdgeTableForGlyph (int glyphNumber, const AffineTransform& transform); - int getGlyphForCharacter (juce_wchar character); protected: @@ -67871,7 +67896,6 @@ private: short lookupTable [128]; GlyphInfo* findGlyph (const juce_wchar character, bool loadIfNeeded) noexcept; - GlyphInfo* findGlyphSubstituting (juce_wchar character) noexcept; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CustomTypeface); }; diff --git a/src/containers/juce_SortedSet.h b/src/containers/juce_SortedSet.h index 3d5d44d08a..3345a385d4 100644 --- a/src/containers/juce_SortedSet.h +++ b/src/containers/juce_SortedSet.h @@ -119,7 +119,7 @@ public: return false; for (int i = numUsed; --i >= 0;) - if (data.elements[i] != other.data.elements[i]) + if (! (data.elements[i] == other.data.elements[i])) return false; return true; @@ -204,6 +204,21 @@ public: return data.elements [index]; } + /** Returns a direct reference to one of the elements in the set, without checking the index passed in. + + This is like getUnchecked, but returns a direct reference to the element, so that + you can alter it directly. Obviously this can be dangerous, so only use it when + absolutely necessary. + + @param index the index of the element being requested (0 is the first element in the array) + */ + inline ElementType& getReference (const int index) const noexcept + { + const ScopedLockType lock (getLock()); + jassert (isPositiveAndBelow (index, numUsed)); + return data.elements [index]; + } + /** Returns the first element in the set, or 0 if the set is empty. @see operator[], getUnchecked, getLast @@ -273,10 +288,10 @@ public: if (halfway == start) return -1; - else if (elementToLookFor >= data.elements [halfway]) - start = halfway; - else + else if (elementToLookFor < data.elements [halfway]) end = halfway; + else + start = halfway; } } } @@ -309,10 +324,10 @@ public: if (halfway == start) return false; - else if (elementToLookFor >= data.elements [halfway]) - start = halfway; - else + else if (elementToLookFor < data.elements [halfway]) end = halfway; + else + start = halfway; } } } @@ -348,17 +363,17 @@ public: if (halfway == start) { - if (newElement >= data.elements [halfway]) - insertInternal (start + 1, newElement); - else + if (newElement < data.elements [halfway]) insertInternal (start, newElement); + else + insertInternal (start + 1, newElement); break; } - else if (newElement >= data.elements [halfway]) - start = halfway; - else + else if (newElement < data.elements [halfway]) end = halfway; + else + start = halfway; } } } @@ -529,6 +544,18 @@ public: data.shrinkToNoMoreThan (numUsed); } + /** Increases the set's internal storage to hold a minimum number of elements. + + Calling this before adding a large known number of elements means that + the set won't have to keep dynamically resizing itself as the elements + are added, and it'll therefore be more efficient. + */ + void ensureStorageAllocated (const int minNumElements) + { + const ScopedLockType lock (getLock()); + data.ensureAllocatedSize (minNumElements); + } + //============================================================================== /** Returns the CriticalSection that locks this array. To lock, you can call getLock().enter() and getLock().exit(), or preferably use diff --git a/src/core/juce_StandardHeader.h b/src/core/juce_StandardHeader.h index 949afcd7fb..bacc728081 100644 --- a/src/core/juce_StandardHeader.h +++ b/src/core/juce_StandardHeader.h @@ -33,7 +33,7 @@ */ #define JUCE_MAJOR_VERSION 1 #define JUCE_MINOR_VERSION 53 -#define JUCE_BUILDNUMBER 96 +#define JUCE_BUILDNUMBER 97 /** Current Juce version number. diff --git a/src/gui/graphics/fonts/juce_CustomTypeface.cpp b/src/gui/graphics/fonts/juce_CustomTypeface.cpp index 1bfe2949c7..3f530c5154 100644 --- a/src/gui/graphics/fonts/juce_CustomTypeface.cpp +++ b/src/gui/graphics/fonts/juce_CustomTypeface.cpp @@ -222,34 +222,6 @@ CustomTypeface::GlyphInfo* CustomTypeface::findGlyph (const juce_wchar character return nullptr; } -CustomTypeface::GlyphInfo* CustomTypeface::findGlyphSubstituting (const juce_wchar character) noexcept -{ - GlyphInfo* glyph = findGlyph (character, true); - - if (glyph == nullptr) - { - if (CharacterFunctions::isWhitespace (character) && character != L' ') - glyph = findGlyph (L' ', true); - - if (glyph == nullptr) - { - const Font fallbackFont (Font::getFallbackFontName(), 10, 0); - Typeface* const fallbackTypeface = fallbackFont.getTypeface(); - if (fallbackTypeface != nullptr && fallbackTypeface != this) - { - Path path; - fallbackTypeface->getOutlineForGlyph (character, path); - addGlyph (character, path, fallbackTypeface->getStringWidth (String::charToString (character))); - } - - if (glyph == nullptr) - glyph = findGlyph (defaultCharacter, true); - } - } - - return glyph; -} - bool CustomTypeface::loadGlyphIfPossible (const juce_wchar /*characterNeeded*/) { return false; @@ -352,13 +324,13 @@ float CustomTypeface::getStringWidth (const String& text) while (! t.isEmpty()) { const juce_wchar c = t.getAndAdvance(); - const GlyphInfo* const glyph = findGlyphSubstituting (c); + const GlyphInfo* const glyph = findGlyph (c, true); - if (glyph == nullptr && ! isFallbackFont) + if (glyph == nullptr) { const Typeface::Ptr fallbackTypeface (Typeface::getFallbackTypeface()); - if (fallbackTypeface != nullptr) + if (fallbackTypeface != nullptr && fallbackTypeface != this) x += fallbackTypeface->getStringWidth (String::charToString (c)); } @@ -380,11 +352,11 @@ void CustomTypeface::getGlyphPositions (const String& text, Array & resultG const juce_wchar c = t.getAndAdvance(); const GlyphInfo* const glyph = findGlyph (c, true); - if (glyph == nullptr && ! isFallbackFont) + if (glyph == nullptr) { const Typeface::Ptr fallbackTypeface (Typeface::getFallbackTypeface()); - if (fallbackTypeface != nullptr) + if (fallbackTypeface != nullptr && fallbackTypeface != this) { Array subGlyphs; Array subOffsets; @@ -412,11 +384,11 @@ bool CustomTypeface::getOutlineForGlyph (int glyphNumber, Path& path) { const GlyphInfo* const glyph = findGlyph ((juce_wchar) glyphNumber, true); - if (glyph == nullptr && ! isFallbackFont) + if (glyph == nullptr) { const Typeface::Ptr fallbackTypeface (Typeface::getFallbackTypeface()); - if (fallbackTypeface != nullptr) + if (fallbackTypeface != nullptr && fallbackTypeface != this) fallbackTypeface->getOutlineForGlyph (glyphNumber, path); } @@ -433,11 +405,11 @@ EdgeTable* CustomTypeface::getEdgeTableForGlyph (int glyphNumber, const AffineTr { const GlyphInfo* const glyph = findGlyph ((juce_wchar) glyphNumber, true); - if (glyph == nullptr && ! isFallbackFont) + if (glyph == nullptr) { const Typeface::Ptr fallbackTypeface (Typeface::getFallbackTypeface()); - if (fallbackTypeface != nullptr) + if (fallbackTypeface != nullptr && fallbackTypeface != this) return fallbackTypeface->getEdgeTableForGlyph (glyphNumber, transform); } diff --git a/src/gui/graphics/fonts/juce_CustomTypeface.h b/src/gui/graphics/fonts/juce_CustomTypeface.h index 27062a4a42..a8af392ba6 100644 --- a/src/gui/graphics/fonts/juce_CustomTypeface.h +++ b/src/gui/graphics/fonts/juce_CustomTypeface.h @@ -113,7 +113,6 @@ public: void getGlyphPositions (const String& text, Array & glyphs, Array& xOffsets); bool getOutlineForGlyph (int glyphNumber, Path& path); EdgeTable* getEdgeTableForGlyph (int glyphNumber, const AffineTransform& transform); - int getGlyphForCharacter (juce_wchar character); protected: //============================================================================== @@ -138,7 +137,6 @@ private: short lookupTable [128]; GlyphInfo* findGlyph (const juce_wchar character, bool loadIfNeeded) noexcept; - GlyphInfo* findGlyphSubstituting (juce_wchar character) noexcept; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CustomTypeface); }; diff --git a/src/gui/graphics/fonts/juce_Typeface.cpp b/src/gui/graphics/fonts/juce_Typeface.cpp index 0835f35288..bd4d9b3d43 100644 --- a/src/gui/graphics/fonts/juce_Typeface.cpp +++ b/src/gui/graphics/fonts/juce_Typeface.cpp @@ -29,11 +29,12 @@ BEGIN_JUCE_NAMESPACE #include "juce_Typeface.h" #include "juce_Font.h" +#include "../contexts/juce_EdgeTable.h" //============================================================================== Typeface::Typeface (const String& name_) noexcept - : name (name_), isFallbackFont (false) + : name (name_) { } @@ -44,10 +45,20 @@ Typeface::~Typeface() const Typeface::Ptr Typeface::getFallbackTypeface() { const Font fallbackFont (Font::getFallbackFontName(), 10, 0); - Typeface* t = fallbackFont.getTypeface(); - t->isFallbackFont = true; - return t; + return fallbackFont.getTypeface(); +} + +EdgeTable* Typeface::getEdgeTableForGlyph (int glyphNumber, const AffineTransform& transform) +{ + Path path; + + if (getOutlineForGlyph (glyphNumber, path) && ! path.isEmpty()) + return new EdgeTable (path.getBoundsTransformed (transform).getSmallestIntegerContainer().expanded (1, 0), + path, transform); + + return nullptr; } + END_JUCE_NAMESPACE diff --git a/src/gui/graphics/fonts/juce_Typeface.h b/src/gui/graphics/fonts/juce_Typeface.h index eab11448cb..47a3f90943 100644 --- a/src/gui/graphics/fonts/juce_Typeface.h +++ b/src/gui/graphics/fonts/juce_Typeface.h @@ -113,7 +113,7 @@ public: virtual bool getOutlineForGlyph (int glyphNumber, Path& path) = 0; /** Returns a new EdgeTable that contains the path for the givem glyph, with the specified transform applied. */ - virtual EdgeTable* getEdgeTableForGlyph (int glyphNumber, const AffineTransform& transform) = 0; + virtual EdgeTable* getEdgeTableForGlyph (int glyphNumber, const AffineTransform& transform); /** Returns true if the typeface uses hinting. */ virtual bool isHinted() const { return false; } @@ -125,7 +125,6 @@ public: protected: //============================================================================== String name; - bool isFallbackFont; explicit Typeface (const String& name) noexcept; diff --git a/src/native/mac/juce_mac_MainMenu.mm b/src/native/mac/juce_mac_MainMenu.mm index 7817e69bfb..362f47d3d2 100644 --- a/src/native/mac/juce_mac_MainMenu.mm +++ b/src/native/mac/juce_mac_MainMenu.mm @@ -121,7 +121,10 @@ public: [menu setAutoenablesItems: false]; [menu update]; [parentItem setTag: tag]; - [parentItem setSubmenu: menu]; + + if (! [[parentItem submenu] equals: menu]) // NB this comparison is needed to avoid a strange + [parentItem setSubmenu: menu]; // crash deep inside Apple code when no windows are focused.. + [menu release]; } diff --git a/src/native/windows/juce_win32_Fonts.cpp b/src/native/windows/juce_win32_Fonts.cpp index 8c3549d36a..12e1a8dae7 100644 --- a/src/native/windows/juce_win32_Fonts.cpp +++ b/src/native/windows/juce_win32_Fonts.cpp @@ -29,41 +29,44 @@ //============================================================================== -static int CALLBACK wfontEnum2 (ENUMLOGFONTEXW* lpelfe, NEWTEXTMETRICEXW*, int type, LPARAM lParam) +namespace FontEnumerators { - if (lpelfe != nullptr && (type & RASTER_FONTTYPE) == 0) + int CALLBACK fontEnum2 (ENUMLOGFONTEXW* lpelfe, NEWTEXTMETRICEXW*, int type, LPARAM lParam) { - const String fontName (lpelfe->elfLogFont.lfFaceName); + if (lpelfe != nullptr && (type & RASTER_FONTTYPE) == 0) + { + const String fontName (lpelfe->elfLogFont.lfFaceName); - ((StringArray*) lParam)->addIfNotAlreadyThere (fontName.removeCharacters ("@")); + ((StringArray*) lParam)->addIfNotAlreadyThere (fontName.removeCharacters ("@")); + } + + return 1; } - return 1; -} - -static int CALLBACK wfontEnum1 (ENUMLOGFONTEXW* lpelfe, NEWTEXTMETRICEXW*, int type, LPARAM lParam) -{ - if (lpelfe != nullptr && (type & RASTER_FONTTYPE) == 0) + int CALLBACK fontEnum1 (ENUMLOGFONTEXW* lpelfe, NEWTEXTMETRICEXW*, int type, LPARAM lParam) { - LOGFONTW lf = { 0 }; - lf.lfWeight = FW_DONTCARE; - lf.lfOutPrecision = OUT_OUTLINE_PRECIS; - lf.lfQuality = DEFAULT_QUALITY; - lf.lfCharSet = DEFAULT_CHARSET; - lf.lfClipPrecision = CLIP_DEFAULT_PRECIS; - lf.lfPitchAndFamily = FF_DONTCARE; + if (lpelfe != nullptr && (type & RASTER_FONTTYPE) == 0) + { + LOGFONTW lf = { 0 }; + lf.lfWeight = FW_DONTCARE; + lf.lfOutPrecision = OUT_OUTLINE_PRECIS; + lf.lfQuality = DEFAULT_QUALITY; + lf.lfCharSet = DEFAULT_CHARSET; + lf.lfClipPrecision = CLIP_DEFAULT_PRECIS; + lf.lfPitchAndFamily = FF_DONTCARE; - const String fontName (lpelfe->elfLogFont.lfFaceName); - fontName.copyToUTF16 (lf.lfFaceName, sizeof (lf.lfFaceName)); + const String fontName (lpelfe->elfLogFont.lfFaceName); + fontName.copyToUTF16 (lf.lfFaceName, sizeof (lf.lfFaceName)); - HDC dc = CreateCompatibleDC (0); - EnumFontFamiliesEx (dc, &lf, - (FONTENUMPROCW) &wfontEnum2, - lParam, 0); - DeleteDC (dc); + HDC dc = CreateCompatibleDC (0); + EnumFontFamiliesEx (dc, &lf, + (FONTENUMPROCW) &fontEnum2, + lParam, 0); + DeleteDC (dc); + } + + return 1; } - - return 1; } StringArray Font::findAllTypefaceNames() @@ -81,7 +84,7 @@ StringArray Font::findAllTypefaceNames() lf.lfPitchAndFamily = FF_DONTCARE; EnumFontFamiliesEx (dc, &lf, - (FONTENUMPROCW) &wfontEnum1, + (FONTENUMPROCW) &FontEnumerators::fontEnum1, (LPARAM) &results, 0); } @@ -111,203 +114,109 @@ void Font::getPlatformDefaultFontNames (String& defaultSans, String& defaultSeri } } - //============================================================================== -class FontDCHolder : private DeletedAtShutdown -{ -public: - //============================================================================== - FontDCHolder() - : fontH (0), previousFontH (0), dc (0), numKPs (0), size (0), - bold (false), italic (false) - { - } - - ~FontDCHolder() - { - deleteDCAndFont(); - clearSingletonInstance(); - } - - juce_DeclareSingleton_SingleThreaded_Minimal (FontDCHolder); - - //============================================================================== - HDC loadFont (const String& fontName_, const bool bold_, const bool italic_, const int size_) - { - if (fontName != fontName_ || bold != bold_ || italic != italic_ || size != size_) - { - fontName = fontName_; - bold = bold_; - italic = italic_; - size = size_; - - deleteDCAndFont(); - - dc = CreateCompatibleDC (0); - SetMapperFlags (dc, 0); - SetMapMode (dc, MM_TEXT); - - LOGFONTW lf = { 0 }; - lf.lfCharSet = DEFAULT_CHARSET; - lf.lfClipPrecision = CLIP_DEFAULT_PRECIS; - lf.lfOutPrecision = OUT_OUTLINE_PRECIS; - lf.lfPitchAndFamily = DEFAULT_PITCH | FF_DONTCARE; - lf.lfQuality = PROOF_QUALITY; - lf.lfItalic = (BYTE) (italic ? TRUE : FALSE); - lf.lfWeight = bold ? FW_BOLD : FW_NORMAL; - lf.lfHeight = size > 0 ? size : -256; - fontName.copyToUTF16 (lf.lfFaceName, sizeof (lf.lfFaceName)); - - HFONT standardSizedFont = CreateFontIndirect (&lf); - - if (standardSizedFont != 0) - { - if ((previousFontH = SelectObject (dc, standardSizedFont)) != 0) - { - fontH = standardSizedFont; - - if (size == 0) - { - OUTLINETEXTMETRIC otm; - if (GetOutlineTextMetrics (dc, sizeof (otm), &otm) != 0) - { - lf.lfHeight = -(int) otm.otmEMSquare; - fontH = CreateFontIndirect (&lf); - - SelectObject (dc, fontH); - DeleteObject (standardSizedFont); - } - } - } - } - } - - return dc; - } - - //============================================================================== - KERNINGPAIR* getKerningPairs (int& numKPs_) - { - if (kps == nullptr) - { - numKPs = GetKerningPairs (dc, 0, 0); - kps.calloc (numKPs); - GetKerningPairs (dc, numKPs, kps); - } - - numKPs_ = numKPs; - return kps; - } - - -private: - //============================================================================== - HFONT fontH; - HGDIOBJ previousFontH; - HDC dc; - String fontName; - HeapBlock kps; - int numKPs, size; - bool bold, italic; - - void deleteDCAndFont() - { - if (dc != 0) - { - SelectObject (dc, previousFontH); // Replacing the previous font before deleting the DC avoids a warning in BoundsChecker - DeleteDC (dc); - dc = 0; - } - - if (fontH != 0) - { - DeleteObject (fontH); - fontH = 0; - } - - kps.free(); - } - - JUCE_DECLARE_NON_COPYABLE (FontDCHolder); -}; - -juce_ImplementSingleton_SingleThreaded (FontDCHolder); - - -//============================================================================== -class WindowsTypeface : public CustomTypeface +class WindowsTypeface : public Typeface { public: WindowsTypeface (const Font& font) + : Typeface (font.getTypefaceName()), + fontH (0), + previousFontH (0), + dc (CreateCompatibleDC (0)), + ascent (1.0f), + defaultGlyph (-1), + bold (font.isBold()), + italic (font.isItalic()) { - HDC dc = FontDCHolder::getInstance()->loadFont (font.getTypefaceName(), - font.isBold(), font.isItalic(), 0); + loadFont(); - TEXTMETRIC tm; - tm.tmAscent = tm.tmHeight = 1; - tm.tmDefaultChar = 0; - GetTextMetrics (dc, &tm); - - setCharacteristics (font.getTypefaceName(), - tm.tmAscent / (float) tm.tmHeight, - font.isBold(), font.isItalic(), - tm.tmDefaultChar); + if (GetTextMetrics (dc, &tm)) + { + ascent = tm.tmAscent / (float) tm.tmHeight; + defaultGlyph = getGlyphForChar (dc, tm.tmDefaultChar); + createKerningPairs (dc, (float) tm.tmHeight); + } } - bool loadGlyphIfPossible (juce_wchar character) + ~WindowsTypeface() { - HDC dc = FontDCHolder::getInstance()->loadFont (name, isBold, isItalic, 0); + SelectObject (dc, previousFontH); // Replacing the previous font before deleting the DC avoids a warning in BoundsChecker + DeleteDC (dc); - GLYPHMETRICS gm; + if (fontH != 0) + DeleteObject (fontH); + } - // if this is the fallback font, skip checking for the glyph's existence. This is because - // with fonts like Tahoma, GetGlyphIndices can say that a glyph doesn't exist, but it still - // gets correctly created later on. - if (! isFallbackFont) + float getAscent() const { return ascent; } + float getDescent() const { return 1.0f - ascent; } + + float getStringWidth (const String& text) + { + const CharPointer_UTF16 utf16 (text.toUTF16()); + const int numChars = utf16.length(); + HeapBlock results (numChars + 1); + results[numChars] = -1; + float x = 0; + + if (GetGlyphIndices (dc, utf16, numChars, reinterpret_cast (results.getData()), + GGI_MARK_NONEXISTING_GLYPHS) != GDI_ERROR) { - const WCHAR charToTest[] = { (WCHAR) character, 0 }; - WORD index = 0; + for (int i = 0; i < numChars; ++i) + x += getKerning (dc, results[i], results[i + 1]); + } - if (GetGlyphIndices (dc, charToTest, 1, &index, GGI_MARK_NONEXISTING_GLYPHS) != GDI_ERROR - && index == 0xffff) + return x; + } + + void getGlyphPositions (const String& text, Array & resultGlyphs, Array & xOffsets) + { + const CharPointer_UTF16 utf16 (text.toUTF16()); + const int numChars = utf16.length(); + HeapBlock results (numChars + 1); + results[numChars] = -1; + float x = 0; + + if (GetGlyphIndices (dc, utf16, numChars, reinterpret_cast (results.getData()), + GGI_MARK_NONEXISTING_GLYPHS) != GDI_ERROR) + { + resultGlyphs.ensureStorageAllocated (numChars); + xOffsets.ensureStorageAllocated (numChars + 1); + + for (int i = 0; i < numChars; ++i) { - return false; + resultGlyphs.add (results[i]); + xOffsets.add (x); + x += getKerning (dc, results[i], results[i + 1]); } } - Path glyphPath; + xOffsets.add (x); + } - TEXTMETRIC tm; - if (! GetTextMetrics (dc, &tm)) - { - addGlyph (character, glyphPath, 0); - return true; - } + bool getOutlineForGlyph (int glyphNumber, Path& glyphPath) + { + if (glyphNumber < 0) + glyphNumber = defaultGlyph; - const float height = (float) tm.tmHeight; - static const MAT2 identityMatrix = { { 0, 1 }, { 0, 0 }, { 0, 0 }, { 0, 1 } }; - - const int bufSize = GetGlyphOutline (dc, character, GGO_NATIVE, + GLYPHMETRICS gm; + const int bufSize = GetGlyphOutline (dc, glyphNumber, GGO_NATIVE | GGO_GLYPH_INDEX, &gm, 0, 0, &identityMatrix); if (bufSize > 0) { HeapBlock data (bufSize); - - GetGlyphOutline (dc, character, GGO_NATIVE, &gm, + GetGlyphOutline (dc, glyphNumber, GGO_NATIVE | GGO_GLYPH_INDEX, &gm, bufSize, data, &identityMatrix); const TTPOLYGONHEADER* pheader = reinterpret_cast (data.getData()); - const float scaleX = 1.0f / height; - const float scaleY = -1.0f / height; + const float scaleX = 1.0f / tm.tmHeight; + const float scaleY = -scaleX; while ((char*) pheader < data + bufSize) { - float x = scaleX * pheader->pfxStart.x.value; - float y = scaleY * pheader->pfxStart.y.value; - - glyphPath.startNewSubPath (x, y); + glyphPath.startNewSubPath (scaleX * pheader->pfxStart.x.value, + scaleY * pheader->pfxStart.y.value); const TTPOLYCURVE* curve = (const TTPOLYCURVE*) ((const char*) pheader + sizeof (TTPOLYGONHEADER)); const char* const curveEnd = ((const char*) pheader) + pheader->cb; @@ -317,12 +226,8 @@ public: if (curve->wType == TT_PRIM_LINE) { for (int i = 0; i < curve->cpfx; ++i) - { - x = scaleX * curve->apfx[i].x.value; - y = scaleY * curve->apfx[i].y.value; - - glyphPath.lineTo (x, y); - } + glyphPath.lineTo (scaleX * curve->apfx[i].x.value, + scaleY * curve->apfx[i].y.value); } else if (curve->wType == TT_PRIM_QSPLINE) { @@ -330,23 +235,16 @@ public: { const float x2 = scaleX * curve->apfx[i].x.value; const float y2 = scaleY * curve->apfx[i].y.value; - float x3, y3; + float x3 = scaleX * curve->apfx[i + 1].x.value; + float y3 = scaleY * curve->apfx[i + 1].y.value; if (i < curve->cpfx - 2) { - x3 = 0.5f * (x2 + scaleX * curve->apfx[i + 1].x.value); - y3 = 0.5f * (y2 + scaleY * curve->apfx[i + 1].y.value); - } - else - { - x3 = scaleX * curve->apfx[i + 1].x.value; - y3 = scaleY * curve->apfx[i + 1].y.value; + x3 = 0.5f * (x2 + x3); + y3 = 0.5f * (y2 + y3); } glyphPath.quadraticTo (x2, y2, x3, y3); - - x = x3; - y = y3; } } @@ -359,25 +257,150 @@ public: } } - addGlyph (character, glyphPath, gm.gmCellIncX / height); - - int numKPs; - const KERNINGPAIR* const kps = FontDCHolder::getInstance()->getKerningPairs (numKPs); - - for (int i = 0; i < numKPs; ++i) - { - if (kps[i].wFirst == character) - addKerningPair (kps[i].wFirst, kps[i].wSecond, - kps[i].iKernAmount / height); - } - return true; } private: + static const MAT2 identityMatrix; + HFONT fontH; + HGDIOBJ previousFontH; + HDC dc; + TEXTMETRIC tm; + float ascent; + int defaultGlyph; + bool bold, italic; + + struct KerningPair + { + int glyph1, glyph2; + float kerning; + + bool operator== (const KerningPair& other) const noexcept + { + return glyph1 == other.glyph1 && glyph2 == other.glyph2; + } + + bool operator< (const KerningPair& other) const noexcept + { + return glyph1 < other.glyph1 + || (glyph1 == other.glyph1 && glyph2 < other.glyph2); + } + }; + + SortedSet kerningPairs; + + void loadFont() + { + SetMapperFlags (dc, 0); + SetMapMode (dc, MM_TEXT); + + LOGFONTW lf = { 0 }; + lf.lfCharSet = DEFAULT_CHARSET; + lf.lfClipPrecision = CLIP_DEFAULT_PRECIS; + lf.lfOutPrecision = OUT_OUTLINE_PRECIS; + lf.lfPitchAndFamily = DEFAULT_PITCH | FF_DONTCARE; + lf.lfQuality = PROOF_QUALITY; + lf.lfItalic = (BYTE) (italic ? TRUE : FALSE); + lf.lfWeight = bold ? FW_BOLD : FW_NORMAL; + lf.lfHeight = -256; + name.copyToUTF16 (lf.lfFaceName, sizeof (lf.lfFaceName)); + + HFONT standardSizedFont = CreateFontIndirect (&lf); + + if (standardSizedFont != 0) + { + if ((previousFontH = SelectObject (dc, standardSizedFont)) != 0) + { + fontH = standardSizedFont; + + OUTLINETEXTMETRIC otm; + if (GetOutlineTextMetrics (dc, sizeof (otm), &otm) != 0) + { + lf.lfHeight = -(int) otm.otmEMSquare; + fontH = CreateFontIndirect (&lf); + + SelectObject (dc, fontH); + DeleteObject (standardSizedFont); + } + } + } + } + + void createKerningPairs (HDC dc, const float height) + { + HeapBlock rawKerning; + const int numKPs = GetKerningPairs (dc, 0, 0); + rawKerning.calloc (numKPs); + GetKerningPairs (dc, numKPs, rawKerning); + + kerningPairs.ensureStorageAllocated (numKPs); + + for (int i = 0; i < numKPs; ++i) + { + KerningPair kp; + kp.glyph1 = getGlyphForChar (dc, rawKerning[i].wFirst); + kp.glyph2 = getGlyphForChar (dc, rawKerning[i].wSecond); + + const int standardWidth = getGlyphWidth (dc, kp.glyph1); + kp.kerning = (standardWidth + rawKerning[i].iKernAmount) / height; + kerningPairs.add (kp); + + kp.glyph2 = -1; // add another entry for the standard width version.. + kp.kerning = standardWidth / height; + kerningPairs.add (kp); + } + } + + static int getGlyphForChar (HDC dc, juce_wchar character) + { + const WCHAR charToTest[] = { (WCHAR) character, 0 }; + WORD index = 0; + + if (GetGlyphIndices (dc, charToTest, 1, &index, GGI_MARK_NONEXISTING_GLYPHS) == GDI_ERROR + || index == 0xffff) + return -1; + + return index; + } + + static int getGlyphWidth (HDC dc, int glyphNumber) + { + GLYPHMETRICS gm; + gm.gmCellIncX = 0; + GetGlyphOutline (dc, glyphNumber, GGO_NATIVE | GGO_GLYPH_INDEX, &gm, 0, 0, &identityMatrix); + return gm.gmCellIncX; + } + + float getKerning (HDC dc, const int glyph1, const int glyph2) + { + KerningPair kp; + kp.glyph1 = glyph1; + kp.glyph2 = glyph2; + int index = kerningPairs.indexOf (kp); + + if (index < 0) + { + kp.glyph2 = -1; + index = kerningPairs.indexOf (kp); + + if (index < 0) + { + kp.glyph2 = -1; + kp.kerning = getGlyphWidth (dc, kp.glyph1) / (float) tm.tmHeight; + kerningPairs.add (kp); + return kp.kerning; + } + } + + return kerningPairs.getReference (index).kerning; + } + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WindowsTypeface); }; +const MAT2 WindowsTypeface::identityMatrix = { { 0, 1 }, { 0, 0 }, { 0, 0 }, { 0, 1 } }; + + const Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font) { return new WindowsTypeface (font); diff --git a/src/native/windows/juce_win32_Windowing.cpp b/src/native/windows/juce_win32_Windowing.cpp index a7e5d3e05b..2c16e7ec4b 100644 --- a/src/native/windows/juce_win32_Windowing.cpp +++ b/src/native/windows/juce_win32_Windowing.cpp @@ -1522,7 +1522,7 @@ private: peer = this; peer->handleMouseWheel (0, peer->globalToLocal (globalPos), getMouseEventTime(), - isVertical ? 0.0f : amount, + isVertical ? 0.0f : -amount, isVertical ? amount : 0.0f); }