| @@ -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 <int>& 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 <int> subGlyphs; | |||
| Array <float> 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<int16> results (numChars + 1); | |||
| results[numChars] = -1; | |||
| float x = 0; | |||
| GLYPHMETRICS gm; | |||
| if (GetGlyphIndices (dc, utf16, numChars, reinterpret_cast <WORD*> (results.getData()), | |||
| GGI_MARK_NONEXISTING_GLYPHS) != GDI_ERROR) | |||
| { | |||
| for (int i = 0; i < numChars; ++i) | |||
| x += getKerning (dc, results[i], results[i + 1]); | |||
| } | |||
| // 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) | |||
| return x; | |||
| } | |||
| void getGlyphPositions (const String& text, Array <int>& resultGlyphs, Array <float>& xOffsets) | |||
| { | |||
| HDC dc = getDC(); | |||
| const CharPointer_UTF16 utf16 (text.toUTF16()); | |||
| const int numChars = utf16.length(); | |||
| HeapBlock<int16> results (numChars + 1); | |||
| results[numChars] = -1; | |||
| float x = 0; | |||
| if (GetGlyphIndices (dc, utf16, numChars, reinterpret_cast <WORD*> (results.getData()), | |||
| GGI_MARK_NONEXISTING_GLYPHS) != GDI_ERROR) | |||
| { | |||
| const WCHAR charToTest[] = { (WCHAR) character, 0 }; | |||
| WORD index = 0; | |||
| resultGlyphs.ensureStorageAllocated (numChars); | |||
| xOffsets.ensureStorageAllocated (numChars + 1); | |||
| if (GetGlyphIndices (dc, charToTest, 1, &index, GGI_MARK_NONEXISTING_GLYPHS) != GDI_ERROR | |||
| && index == 0xffff) | |||
| 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<char> 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<TTPOLYGONHEADER*> (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); | |||
| return true; | |||
| } | |||
| int numKPs; | |||
| const KERNINGPAIR* const kps = FontDCHolder::getInstance()->getKerningPairs (numKPs); | |||
| 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<KerningPair> kerningPairs; | |||
| void createKerningPairs (HDC dc, const float height) | |||
| { | |||
| HeapBlock<KERNINGPAIR> 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) | |||
| { | |||
| if (kps[i].wFirst == character) | |||
| addKerningPair (kps[i].wFirst, kps[i].wSecond, | |||
| kps[i].iKernAmount / height); | |||
| 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); | |||
| } | |||
| } | |||
| return true; | |||
| 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); | |||
| } | |||
| private: | |||
| 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]; | |||
| } | |||
| @@ -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 <int>& glyphs, Array<float>& 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); | |||
| }; | |||
| @@ -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 | |||
| @@ -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. | |||
| @@ -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 <int>& 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 <int> subGlyphs; | |||
| Array <float> 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); | |||
| } | |||
| @@ -113,7 +113,6 @@ public: | |||
| void getGlyphPositions (const String& text, Array <int>& glyphs, Array<float>& 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); | |||
| }; | |||
| @@ -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 | |||
| @@ -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; | |||
| @@ -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]; | |||
| } | |||
| @@ -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 | |||
| class WindowsTypeface : public Typeface | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| FontDCHolder() | |||
| : fontH (0), previousFontH (0), dc (0), numKPs (0), size (0), | |||
| bold (false), italic (false) | |||
| { | |||
| } | |||
| ~FontDCHolder() | |||
| 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()) | |||
| { | |||
| deleteDCAndFont(); | |||
| clearSingletonInstance(); | |||
| } | |||
| juce_DeclareSingleton_SingleThreaded_Minimal (FontDCHolder); | |||
| loadFont(); | |||
| //============================================================================== | |||
| HDC loadFont (const String& fontName_, const bool bold_, const bool italic_, const int size_) | |||
| { | |||
| if (fontName != fontName_ || bold != bold_ || italic != italic_ || size != size_) | |||
| if (GetTextMetrics (dc, &tm)) | |||
| { | |||
| 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); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| ascent = tm.tmAscent / (float) tm.tmHeight; | |||
| defaultGlyph = getGlyphForChar (dc, tm.tmDefaultChar); | |||
| createKerningPairs (dc, (float) tm.tmHeight); | |||
| } | |||
| return dc; | |||
| } | |||
| //============================================================================== | |||
| KERNINGPAIR* getKerningPairs (int& numKPs_) | |||
| ~WindowsTypeface() | |||
| { | |||
| if (kps == nullptr) | |||
| { | |||
| numKPs = GetKerningPairs (dc, 0, 0); | |||
| kps.calloc (numKPs); | |||
| GetKerningPairs (dc, numKPs, kps); | |||
| } | |||
| SelectObject (dc, previousFontH); // Replacing the previous font before deleting the DC avoids a warning in BoundsChecker | |||
| DeleteDC (dc); | |||
| numKPs_ = numKPs; | |||
| return kps; | |||
| if (fontH != 0) | |||
| DeleteObject (fontH); | |||
| } | |||
| float getAscent() const { return ascent; } | |||
| float getDescent() const { return 1.0f - ascent; } | |||
| private: | |||
| //============================================================================== | |||
| HFONT fontH; | |||
| HGDIOBJ previousFontH; | |||
| HDC dc; | |||
| String fontName; | |||
| HeapBlock <KERNINGPAIR> kps; | |||
| int numKPs, size; | |||
| bool bold, italic; | |||
| void deleteDCAndFont() | |||
| float getStringWidth (const String& text) | |||
| { | |||
| 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) | |||
| const CharPointer_UTF16 utf16 (text.toUTF16()); | |||
| const int numChars = utf16.length(); | |||
| HeapBlock<int16> results (numChars + 1); | |||
| results[numChars] = -1; | |||
| float x = 0; | |||
| if (GetGlyphIndices (dc, utf16, numChars, reinterpret_cast <WORD*> (results.getData()), | |||
| GGI_MARK_NONEXISTING_GLYPHS) != GDI_ERROR) | |||
| { | |||
| DeleteObject (fontH); | |||
| fontH = 0; | |||
| for (int i = 0; i < numChars; ++i) | |||
| x += getKerning (dc, results[i], results[i + 1]); | |||
| } | |||
| kps.free(); | |||
| } | |||
| JUCE_DECLARE_NON_COPYABLE (FontDCHolder); | |||
| }; | |||
| juce_ImplementSingleton_SingleThreaded (FontDCHolder); | |||
| //============================================================================== | |||
| class WindowsTypeface : public CustomTypeface | |||
| { | |||
| public: | |||
| WindowsTypeface (const Font& font) | |||
| { | |||
| HDC dc = FontDCHolder::getInstance()->loadFont (font.getTypefaceName(), | |||
| font.isBold(), font.isItalic(), 0); | |||
| 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); | |||
| return x; | |||
| } | |||
| bool loadGlyphIfPossible (juce_wchar character) | |||
| void getGlyphPositions (const String& text, Array <int>& resultGlyphs, Array <float>& xOffsets) | |||
| { | |||
| HDC dc = FontDCHolder::getInstance()->loadFont (name, isBold, isItalic, 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) | |||
| const CharPointer_UTF16 utf16 (text.toUTF16()); | |||
| const int numChars = utf16.length(); | |||
| HeapBlock<int16> results (numChars + 1); | |||
| results[numChars] = -1; | |||
| float x = 0; | |||
| if (GetGlyphIndices (dc, utf16, numChars, reinterpret_cast <WORD*> (results.getData()), | |||
| GGI_MARK_NONEXISTING_GLYPHS) != GDI_ERROR) | |||
| { | |||
| const WCHAR charToTest[] = { (WCHAR) character, 0 }; | |||
| WORD index = 0; | |||
| resultGlyphs.ensureStorageAllocated (numChars); | |||
| xOffsets.ensureStorageAllocated (numChars + 1); | |||
| if (GetGlyphIndices (dc, charToTest, 1, &index, GGI_MARK_NONEXISTING_GLYPHS) != GDI_ERROR | |||
| && index == 0xffff) | |||
| 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; | |||
| TEXTMETRIC tm; | |||
| if (! GetTextMetrics (dc, &tm)) | |||
| { | |||
| addGlyph (character, glyphPath, 0); | |||
| return true; | |||
| } | |||
| xOffsets.add (x); | |||
| } | |||
| const float height = (float) tm.tmHeight; | |||
| static const MAT2 identityMatrix = { { 0, 1 }, { 0, 0 }, { 0, 0 }, { 0, 1 } }; | |||
| bool getOutlineForGlyph (int glyphNumber, Path& glyphPath) | |||
| { | |||
| 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<char> 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<TTPOLYGONHEADER*> (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); | |||
| 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<KerningPair> 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<KERNINGPAIR> rawKerning; | |||
| const int numKPs = GetKerningPairs (dc, 0, 0); | |||
| rawKerning.calloc (numKPs); | |||
| GetKerningPairs (dc, numKPs, rawKerning); | |||
| int numKPs; | |||
| const KERNINGPAIR* const kps = FontDCHolder::getInstance()->getKerningPairs (numKPs); | |||
| kerningPairs.ensureStorageAllocated (numKPs); | |||
| for (int i = 0; i < numKPs; ++i) | |||
| { | |||
| if (kps[i].wFirst == character) | |||
| addKerningPair (kps[i].wFirst, kps[i].wSecond, | |||
| kps[i].iKernAmount / height); | |||
| 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); | |||
| } | |||
| } | |||
| return true; | |||
| 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; | |||
| } | |||
| private: | |||
| 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); | |||
| @@ -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); | |||
| } | |||