Browse Source

Fixed win32 horizontal scroll wheel direction. Big internal refactoring of win32 typeface implementation. Added some functionality to SortedSet. Fixed a mac main menu bug.

tags/2021-05-28
Julian Storer 14 years ago
parent
commit
9226bfba49
11 changed files with 533 additions and 384 deletions
  1. +189
    -97
      juce_amalgamated.cpp
  2. +42
    -18
      juce_amalgamated.h
  3. +40
    -13
      src/containers/juce_SortedSet.h
  4. +1
    -1
      src/core/juce_StandardHeader.h
  5. +9
    -37
      src/gui/graphics/fonts/juce_CustomTypeface.cpp
  6. +0
    -2
      src/gui/graphics/fonts/juce_CustomTypeface.h
  7. +15
    -4
      src/gui/graphics/fonts/juce_Typeface.cpp
  8. +1
    -2
      src/gui/graphics/fonts/juce_Typeface.h
  9. +4
    -1
      src/native/mac/juce_mac_MainMenu.mm
  10. +231
    -208
      src/native/windows/juce_win32_Fonts.cpp
  11. +1
    -1
      src/native/windows/juce_win32_Windowing.cpp

+ 189
- 97
juce_amalgamated.cpp View File

@@ -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];
}



+ 42
- 18
juce_amalgamated.h View File

@@ -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);
};


+ 40
- 13
src/containers/juce_SortedSet.h View File

@@ -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


+ 1
- 1
src/core/juce_StandardHeader.h View File

@@ -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.


+ 9
- 37
src/gui/graphics/fonts/juce_CustomTypeface.cpp View File

@@ -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);
}


+ 0
- 2
src/gui/graphics/fonts/juce_CustomTypeface.h View File

@@ -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);
};


+ 15
- 4
src/gui/graphics/fonts/juce_Typeface.cpp View File

@@ -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

+ 1
- 2
src/gui/graphics/fonts/juce_Typeface.h View File

@@ -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;


+ 4
- 1
src/native/mac/juce_mac_MainMenu.mm View File

@@ -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];
}


+ 231
- 208
src/native/windows/juce_win32_Fonts.cpp View File

@@ -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);


+ 1
- 1
src/native/windows/juce_win32_Windowing.cpp View File

@@ -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);
}


Loading…
Cancel
Save